CTF 安卓逆向 APK 实战:3 种方法定位并解密 Timer 题目中的 Flag

📅 2026/7/8 20:21:47 👁️ 阅读次数 📝 编程学习
CTF 安卓逆向 APK 实战:3 种方法定位并解密 Timer 题目中的 Flag

CTF 安卓逆向 APK 实战:3 种方法定位并解密 Timer 题目中的 Flag

在CTF竞赛中,安卓逆向工程一直是热门挑战方向。本文将以阿里CTF中的Timer题目为例,通过三种不同的技术路线,深入剖析如何高效定位关键代码并获取Flag。不同于简单的解题过程复现,我们将重点探讨逆向工程中的决策逻辑和工具链选择,帮助中高级爱好者建立系统化的分析思维。

1. 环境准备与基础分析

1.1 工具链配置

完整的安卓逆向需要以下工具组合:

  • 静态分析工具:JEB Pro 3.0+、JD-GUI 2.0.1
  • 动态调试工具:Android Studio 4.2 + smalidea插件
  • 辅助工具集
    # APK解包工具 apktool d target.apk # dex转jar工具 d2j-dex2jar.sh classes.dex # 签名工具 keytool + apksigner

1.2 初始文件分析

使用JEB加载APK后,首先检查AndroidManifest.xml:

<activity android:name="net.bluelotus.tomorrow.easyandroid.MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN"/> <category android:name="android.intent.category.LAUNCHER"/> </intent-filter> </activity>

重点关注加载的native库:

static { System.loadLibrary("lhm"); }

2. 静态分析方法对比

2.1 JEB静态反编译

在JEB中定位到MainActivity的核心逻辑:

public void run() { t = System.currentTimeMillis(); now = (int)(t / 1000L); t = 1500L - t % 1000L; if (beg - now <= 0) { tv2.setText("alictf{" + stringFromJNI2(k) + "}"); } if (isPrime(beg - now)) { k += 100; } else { k--; } }

关键发现:

  • beg初始值为当前时间戳+200000秒(约55小时)
  • isPrime()函数控制k值变化
  • Flag生成依赖native方法stringFromJNI2

2.2 JD-GUI辅助分析

通过JD-GUI查看反编译代码时,需注意:

  • 匿名内部类可能显示为MainActivity$1
  • Smali代码可读性优于混淆后的Java代码
  • 使用以下命令增强反编译效果:
    java -jar jd-gui.jar --outputDir src/ classes-dex2jar.jar

2.3 关键算法还原

时间校验逻辑的Python实现:

def is_prime(n): if n <= 3: return n > 1 if n%2==0 or n%3==0: return False i = 5 while i*i <= n: if n%i ==0 or n%(i+2)==0: return False i +=6 return True k = 0 for t in range(200000): if is_prime(t): k += 100 else: k -= 1 print(k) # 输出: 1616384

3. 动态调试方案

3.1 无源码调试配置

  1. 使用apktool反编译后,在Android Studio中导入smali源码:
    apktool d timer.apk -o timer_src
  2. 修改smali代码关键位置:
    # MainActivity$1.smali 第139行 const v3, 1616384 # 直接注入计算好的k值 # 第113行修改条件判断 if-ltz v0, :cond_0 # 原为if-gtz

3.2 调试过程要点

  • onCreate()方法设置断点
  • 使用JEB的调试器附加进程时,需注意:

    调试器可能触发反调试检测,建议修改android:debuggable=true

3.3 内存数据提取

当程序运行到Flag生成位置时,通过DDMS或Frida脚本dump内存:

// Frida脚本打印Flag Interceptor.attach(Module.findExportByName("liblhm.so", "Java_net_bluelotus_tomorrow_easyandroid_MainActivity_stringFromJNI2"), { onLeave: function(retval) { console.log("Flag: " + retval.readUtf8String()); } });

4. 三种方法对比与实践建议

4.1 技术路线对比表

方法类型所需时间技术难度适用场景成功率
静态分析+模拟2小时★★★☆无动态检测逻辑85%
Smali代码修改1.5小时★★★★存在复杂校验逻辑95%
动态调试3小时★★★★☆需要验证内存数据70%

4.2 常见问题解决

  1. APK反编译失败

    • 尝试使用最新版apktool
    • 添加--no-src参数仅解包资源
  2. Native方法定位

    nm -D liblhm.so | grep stringFrom
  3. 签名验证绕过

    # 删除META-INF后再签名 zip -d repack.apk META-INF/* apksigner sign --ks debug.keystore repack.apk

5. 进阶技巧与延伸思考

在实际比赛中,Timer类题目往往存在多个变种。建议掌握以下高级技巧:

  • Hook系统时间函数:通过Frida拦截System.currentTimeMillis()
  • 控制流混淆处理:使用IDAPython分析native代码块
  • 多线程同步问题:注意Handler.postDelayed的时序问题

通过本案例可以看出,优秀的逆向工程师需要具备:

  1. 快速理解业务逻辑的能力
  2. 灵活选择技术路线的判断力
  3. 对安卓系统机制的深入理解

下次遇到类似题目时,不妨先花10分钟分析所有可能的突破口,再选择最适合当前场景的解法。记住,逆向工程中没有"唯一正确"的方法,只有最适合当前情境的解决方案。