BugkuCTF 逆向实战:从PyInstaller解包到Android APK分析 1. PyInstaller解包实战从EXE到Pyc逆向分析1.1 识别PyInstaller打包特征遇到CTF逆向题目的可执行文件时第一步永远是文件识别。用file命令查看文件类型时如果显示PE32 executable (GUI) x86-64再用Detect It Easy工具分析通常会看到PyInstaller的标识。这里有个快速判断技巧用十六进制编辑器查看文件末尾PyInstaller打包的文件往往会有MEI标记和明显的Python版本信息。我遇到过不少题目会在资源段隐藏关键信息。比如某次比赛中用Resource Hacker查看资源时发现PYTHONSCRIPT段里藏有加密的Python代码。这时候需要先用pyinstxtractor解包再用uncompyle6反编译pyc文件。1.2 使用pyinstxtractor拆解GitHub上的pyinstxtractor.py是必备工具实测Python 3.8环境运行最稳定。解包命令很简单python pyinstxtractor.py target.exe解包后会生成target.exe_extracted目录里面有几个关键文件PYZ-00.pyz压缩的Python字节码struct文件包含Python版本信息主脚本的pyc文件通常没有文件名这里有个坑点解包出来的pyc文件可能缺少魔数和时间戳。我常用的修复方法是import struct with open(fixed.pyc, wb) as f: f.write(b\x42\x0D\x0D\x0A) # 魔数 f.write(b\x00*4) # 空时间戳 f.write(open(original).read()[8:]) # 跳过原文件头1.3 反编译Pyc文件拿到修复后的pyc文件后推荐使用uncompyle6uncompyle6 -o . fixed.pyc如果遇到反编译失败可能是代码被混淆了。这时候需要用pycdc工具尝试反编译直接分析字节码python -m dis fixed.pyc去年遇到一道题出题人用marshal模块二次加密了代码。解决方法是从import marshal附近入手找到加载代码的loads()调用点dump出解密后的code对象。2. Android APK逆向核心流程2.1 基础工具链配置Android逆向需要一套工具链apktool解包APK文件dex2jar将dex转换为jarjd-gui/jadx查看Java源码frida动态调试建议配置alias提高效率alias apkdecodeapktool d -f -o decoded alias dex2jard2j-dex2jar.sh -f -o output.jar2.2 多层DEX处理技巧现在的APK经常采用多DEX架构遇到这种情况解压APK后会有多个classes.dex用d2j-dex2jar逐个转换for i in $(seq 2 5); do d2j-dex2jar.sh classes$i.dex -o jar$i.jar done某次比赛遇到DEX被分割存储的情况需要先拼接文件with open(merged.dex, wb) as f: for part in sorted(glob(split_*.dex)): f.write(open(part, rb).read())2.3 关键代码定位方法在jd-gui中快速定位关键代码搜索check、verify等关键词查看MainActivity的onCreate方法注意SharedPreferences读写操作查找getString(R.string.xxx)资源引用有个实用技巧是过滤onClick事件find . -name *.smali | xargs grep Landroid/view/View\$OnClickListener最近一道题把关键逻辑藏在assets的so库里需要用IDA分析System.loadLibrary加载的native代码。3. CTF逆向高频考点解析3.1 密码算法识别与逆向常见加密模式识别特征TEA0x9E3779B9魔数、delta变量AESS盒查找、轮密钥扩展RC4256字节的S盒初始化Base64变种自定义码表遇到加密算法时我通常会用PEiD的Krypto ANALyzer插件识别在IDA中查找初始化向量(IV)通过交叉引用定位密钥生成逻辑某次比赛遇到魔改的XXTEA算法关键点在for (i 0; i 32; i) { v0 (((v1 4) ^ (v1 5)) v1) ^ (sum k[sum 3]); sum 0x61C88647; // 特征值 v1 (((v0 4) ^ (v0 5)) v0) ^ (sum k[(sum11) 3]); }3.2 动态调试技巧Android动态调试方案Frida适合hook Java层Interceptor.attach(Module.findExportByName(null, strcmp), { onEnter: function(args) { console.log(strcmp:, args[0].readCString(), args[1].readCString()); } });IDA Pro调试so文件时需要android_serverWindows平台常用x64dbg条件断点设置Cheat Engine内存扫描有个实用技巧是在JNI_OnLoad下断点可以捕获so加载初期的关键操作。4. 实战案例Bugku逆向题解4.1 Easy_Re题解这道题用IDA打开后直接搜索字符串能看到.rdata:00413E34 xmmword_413E34 xmmword 6C665F4433544354h将十六进制转为ASCII就是DUTCTF的开头。关键验证逻辑在if ( !strncmp(Destination, Str2, v5) ) puts(right flag!);动态调试时在strncmp下断点可以dump出Str2的值。4.2 Timer题解这道Android题需要分析MainActivitypublic void run() { if (MainActivity.this.beg - MainActivity.this.now 0) { tv2.setText(alictf{ MainActivity.this.stringFromJNI2(k) }); } }通过修改smali代码绕过时间检测const v0, 0x7FFFFFFF # 替换原beg值4.3 非标准Base64处理遇到自定义码表的Base64可以用这个模板解码import base64 custom_table AaBbCcDdEeFfGgHh...321/ std_table ABCDEFGHIJKLMNOPQRSTUVWXYZ.../ s mTyqm7wjODkrNLcWl0eqO8K8gc1BPk1GNLgUpI trans str.maketrans(custom_table, std_table) print(base64.b64decode(s.translate(trans)))逆向工程就像侦探破案需要耐心和技巧的结合。每次遇到新保护机制都是学习的机会。建议多分析真实样本积累特征库这比单纯刷题提升更快。