BugkuCTF 逆向实战:从PyInstaller解包到Android APK分析

📅 2026/7/16 3:32:27 👁️ 阅读次数 📝 编程学习
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

如果遇到反编译失败,可能是代码被混淆了。这时候需要:

  1. pycdc工具尝试反编译
  2. 直接分析字节码:
python -m dis fixed.pyc

去年遇到一道题,出题人用marshal模块二次加密了代码。解决方法是从import marshal附近入手,找到加载代码的loads()调用点,dump出解密后的code对象。

2. Android APK逆向核心流程

2.1 基础工具链配置

Android逆向需要一套工具链:

  • apktool:解包APK文件
  • dex2jar:将dex转换为jar
  • jd-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架构,遇到这种情况:

  1. 解压APK后会有多个classes.dex
  2. 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中快速定位关键代码:

  1. 搜索checkverify等关键词
  2. 查看MainActivityonCreate方法
  3. 注意SharedPreferences读写操作
  4. 查找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变种:自定义码表

遇到加密算法时,我通常会:

  1. 用PEiD的Krypto ANALyzer插件识别
  2. 在IDA中查找初始化向量(IV)
  3. 通过交叉引用定位密钥生成逻辑

某次比赛遇到魔改的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动态调试方案:

  1. Frida:适合hook Java层
Interceptor.attach(Module.findExportByName(null, "strcmp"), { onEnter: function(args) { console.log("strcmp:", args[0].readCString(), args[1].readCString()); } });
  1. 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)))

逆向工程就像侦探破案,需要耐心和技巧的结合。每次遇到新保护机制,都是学习的机会。建议多分析真实样本,积累特征库,这比单纯刷题提升更快。