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文件后,推荐使用uncompyle6:
uncompyle6 -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 apkdecode='apktool d -f -o decoded' alias dex2jar='d2j-dex2jar.sh -f -o output.jar'2.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 密码算法识别与逆向
常见加密模式识别特征:
- TEA:0x9E3779B9魔数、delta变量
- AES:S盒查找、轮密钥扩展
- RC4:256字节的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[(sum>>11) & 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_server
Windows平台常用:
- 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题需要分析MainActivity:
public 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)))逆向工程就像侦探破案,需要耐心和技巧的结合。每次遇到新保护机制,都是学习的机会。建议多分析真实样本,积累特征库,这比单纯刷题提升更快。