├── README.md └── codeql_compile.py /README.md: -------------------------------------------------------------------------------- 1 | # codeql_compile 2 | 自动反编译闭源应用,创建codeql数据库 3 | 4 | 5 | 6 | ## 准备 7 | 首先下载[ecj.jar](https://mvnrepository.com/artifact/org.eclipse.jdt.core.compiler/ecj/4.6.1)和idea提供反编译的java-decompiler.jar,将其放置在脚本的相同目录中。 8 | 9 | 10 | ## 使用方法 11 | 12 | 1.在windows上使用 13 | 2.第二步不是必须项,可以直接执行第一步,然后就可以开始创建数据库 14 | 3.最好限制下要分析的包范围,过大反而不利于分析。 15 | 16 | ### 1、反编译项目 17 | 默认情况下使用java-decompiler.jar进行反编译,会在项目源代码路径的父级目录创建以**项目名+_save+时间戳**命名的目录 18 | 19 | 参数`-a`:指定项目源代码路径 20 | 参数`-d`:指定反编译代码的依赖包路径 21 | ```cmd 22 | python3 codeql_compile.py -a D:\project\java\apps\2\cloud -d D:\project\java\apps\BOOT-INF\lib 23 | ``` 24 | 执行后会在当前目录生成 *[项目名]_save_[时间戳]* 目录,该目录的run.cmd是编译代码的执行文件 25 | 26 | 27 | ### 2、校验反编译 28 | 29 | 对java-decompiler反编译的内容先编译一遍确认失败文件,再使用procyon反编译替换失败文件 30 | 31 | 先下载[procyon.jar](https://github.com/mstrobel/procyon/releases/download/0.6-prerelease/procyon-decompiler-0.6-prerelease.jar),将其放置在脚本的相同目录中 32 | 33 | 参数`-o`:指定成功反编译代码存放的路径,即先前java-decompiler.jar反编译后的路径 34 | 参数`-c`:启用校验 35 | 36 | ```cmd 37 | python3 codeql_compile.py -a D:\project\java\apps\2\cloud -o D:\project\java\apps\2\cloud_save_1641018608 -c 38 | ``` 39 | 40 | ### 3、使用codeql创建数据库 41 | 42 | 参数`--command`:指定生成的run.cmd 43 | ```cmd 44 | D:\codeql.exe database create D:\codeql\databases\demo-database --language="java" --source-root=D:\codeql\demo_save_1641018608 --command="run.cmd" 45 | ``` 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /codeql_compile.py: -------------------------------------------------------------------------------- 1 | import pathlib, zipfile, subprocess 2 | import os, sys, time, re 3 | import argparse 4 | import shutil 5 | 6 | 7 | # 指定自定义java-decompiler的路径 8 | self_java_decompiler_path = r'D:\idea_v2019\IntelliJ IDEA 2019.3.5\plugins\java-decompiler\lib\java-decompiler.jar' 9 | # 指定自定义ecj的路径 10 | self_ecj_path = r"D:\project\java\apps\ecj-4.6.1.jar" 11 | # 指定自定义procyon的路径 12 | self_procyon_path = r"D:\project\java\apps\procyon-decompiler-0.6-prerelease.jar" 13 | 14 | 15 | # 用来校验本地的ecj、java_decompiler路径是否正确 16 | def verify(file_jar, my_path): 17 | if pathlib.Path("./{}".format(file_jar)).is_file(): 18 | return "./{}".format(file_jar) 19 | elif pathlib.Path(my_path).is_file(): 20 | return my_path 21 | return False 22 | 23 | 24 | def java_decompiler_run(): 25 | # 搜索源项目中包含.jar的所有路径 26 | _sub = subprocess.getstatusoutput('java -cp "{}" org.jetbrains.java.decompiler.main.decompiler.ConsoleDecompiler -dgs=true {} {}'.format(java_decompiler_path, app_path, save_path)) 27 | if _sub[0] != 0: 28 | print(_sub[1]) 29 | sys.exit("java_decompiler 执行失败......") 30 | app_jars_path = sorted(pathlib.Path(save_path).glob('**/*.jar')) 31 | # 反编译后会生成jar文件,将文件内容解压出来 32 | for app_jar_path in app_jars_path: 33 | with zipfile.ZipFile(pathlib.Path.joinpath(app_jar_path), mode='r') as zfile: 34 | for file in zfile.namelist(): 35 | zfile.extract(file, pathlib.Path.joinpath(save_path, app_jar_path.name.rstrip('.jar'))) 36 | 37 | pathlib.Path.joinpath(app_jar_path).unlink() 38 | 39 | 40 | # 先尝试编译成class,定位错误文件再使用procyon反编译替换 41 | def check(): 42 | _sub = subprocess.getstatusoutput('{}/run.cmd'.format(save_path)) 43 | # 正则匹配错误文件路径 44 | re_matchs = set(re.findall("ERROR in (.*)? \(at line", _sub[1])) 45 | 46 | # 确认是否未编译生成class 47 | for re_match in re_matchs: 48 | is_file = pathlib.Path(re_match.replace(".java", ".class")).is_file() 49 | if is_file: 50 | re_matchs.remove(re_match) 51 | 52 | app_jars_name = [jar_path.name.rstrip('.jar') for jar_path in pathlib.Path(app_path).glob('**/*.jar')] 53 | error_jars = [app_jar_name for app_jar_name in app_jars_name for re_match in re_matchs if app_jar_name not in re_match] 54 | error_classes = re_matchs.difference(set(error_jars)) 55 | # 使用 procyon 反编译jar包 56 | for app_jar_path in pathlib.Path(app_path).glob('**/*.jar'): 57 | jar_folder = app_jar_path.name.rstrip('.jar') 58 | if jar_folder in error_jars: 59 | _sub = subprocess.getstatusoutput('java -jar "{}" {} -o {}/{}'.format(procyon_path, app_jar_path, save_path, jar_folder)) 60 | 61 | # 使用 procyon 反编译class文件 62 | for class_path in error_classes: 63 | class_path = str(class_path).replace(save_path, app_path).replace(".java", ".class") 64 | _sub = subprocess.getstatusoutput('java -jar "{}" {} -o {}/procyon_class'.format(procyon_path, class_path, save_path)) 65 | # 将反编译后的文件替换原先文件 66 | for class_path in pathlib.Path(save_path+"/procyon_class").glob('**/*.java'): 67 | to_class_path = [class_path for class_path in pathlib.Path(save_path).glob('**/{}'.format(class_path.relative_to("{}/procyon_class".format(save_path)))) if "procyon_class" not in str(class_path)] 68 | shutil.move(str(class_path), str(to_class_path[0])) 69 | 70 | shutil.rmtree(save_path + "/procyon_class") 71 | 72 | 73 | # 创建用来编译的脚本,再codeql创建数据库时使用 74 | def compile_cmd_file_create(): 75 | # 准备待编译的jar包 76 | with open("{}/file.txt".format(save_path), "w+") as f: 77 | for java_path in pathlib.Path(save_path).glob('**/*.java'): 78 | f.write(str(java_path) + "\n") 79 | ecj_absolute_path = pathlib.Path(ecj_path).resolve() 80 | compile_cmd = "java -jar {} -encoding UTF-8 -8 -warn:none -noExit @{}/file.txt".format(ecj_absolute_path, save_path) 81 | 82 | if dependencies_path: 83 | libs = "" 84 | search_jars_path = pathlib.Path(dependencies_path).glob('**/*.jar') 85 | for search_jar_path in search_jars_path: 86 | libs += "{};".format(search_jar_path.name) 87 | compile_cmd = "cd {} && java -jar {} -encoding UTF-8 -classpath \"{}\" -8 -warn:none -noExit @{}/file.txt".format( 88 | dependencies_path, ecj_absolute_path, libs, save_path) 89 | 90 | with open("{}/run.cmd".format(save_path), "w+") as f: 91 | f.write(compile_cmd) 92 | 93 | with open("{}/run.sh".format(save_path), "w+") as f: 94 | f.write(compile_cmd) 95 | 96 | 97 | epilog = r'''Example: 98 | python3 codeql_compile.py -a D:\java\apps\cloud -d "D:\java\apps\cloud\lib" 99 | python3 codeql_compile.py -a D:\java\apps\cloud -o "D:\java\apps\cloud_save_1641018608 -c" 100 | ''' 101 | parse = argparse.ArgumentParser(epilog=epilog, formatter_class=argparse.RawDescriptionHelpFormatter) 102 | parse.add_argument('-a', '--app', help='输入源项目路径') 103 | parse.add_argument('-d', '--dep', help='输入依赖包路径') 104 | parse.add_argument('-c', '--check', help='对java-decompiler反编译内容进行检测', action="store_true") 105 | parse.add_argument('-o', '--out', help='输入刚刚已经反编译好的存放路径') 106 | args = parse.parse_args() 107 | # 指定源项目路径,就是要被反编译的项目路径 108 | app_path = args.app 109 | # 指定依赖包路径,在编译代码时需要指定,否则代码中存在相关依赖会导致编译失败 110 | dependencies_path = args.dep 111 | save_path = args.out 112 | 113 | if app_path is not None and dependencies_path is not None: 114 | ecj_path = verify("ecj.jar", self_ecj_path) 115 | java_decompiler_path = verify("java-decompiler.jar", self_java_decompiler_path) 116 | if ecj_path is False or java_decompiler_path is False: 117 | sys.exit("请在当前目录存放ecj.jar、java-decompiler.jar,或者通过self_java_decompiler_path、self_ecj_path指定自定义路径") 118 | save_path = pathlib.Path.joinpath(pathlib.Path(app_path).parent, "{}_save_{}".format(pathlib.Path(app_path).name, int(time.time()))) 119 | save_path.mkdir() 120 | java_decompiler_run() 121 | compile_cmd_file_create() 122 | 123 | elif args.check is not None and app_path is not None and save_path is not None: 124 | procyon_path = verify("procyon.jar", self_procyon_path) 125 | if procyon_path is False: 126 | sys.exit("请在当前目录存放procyon.jar,或者通过self_procyon_path指定自定义路径") 127 | check() 128 | else: 129 | parse.print_help() 130 | sys.exit() 131 | --------------------------------------------------------------------------------