ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CTF逆向题拆解实战:从签到到安卓的完整路径

CTF逆向题拆解实战:从签到到安卓的完整路径 简介这份资源是面向CTF逆向工程学习者与参赛选手的实战题集收录第24至31关的逆向挑战适合已掌握汇编与调试基础、希望突破中高难度题目的读者。压缩包共352个文件约8.8MB以txt题解与说明、dll动态库、udd调试数据、exe可执行程序为主另含ini配置、chm手册、c源码、idb数据库及pcap抓包等辅助文件覆盖静态反汇编、动态调试、栈帧分析、字符串混淆、隐写术与内存取证等方向。已有2490人学习下载说明题目具备一定参考价值。读者可借助IDA Pro、GDB、Stegsolve等工具对照题目理解加密算法逆向、二进制格式解析与脚本自动化分析思路逐步积累从识别入口点到还原解密逻辑的完整经验适合作为CTF逆向专项训练与技能查漏补缺的练习素材。1. 几个 CTF 逆向题从签到到安卓一条能复现的拆题路径很多人第一次打 CTF 逆向卡住的不是不会写代码而是拿到一个二进制或者一个 APK 之后不知道第一刀往哪切。签到题给你一个字符串比较你三分钟出 flag到了安卓逆向、Flutter 逆向、甚至带壳的 so你可能盯着一堆反汇编发呆一整天。我见过太多人把「逆向」理解成硬啃汇编结果在 ctf 逆向入门阶段就被劝退。其实几个 CTF 逆向题拆下来你会发现绝大多数题目的解法是高度模式化的先判类型再找入口然后定位关键比较或解密逻辑最后写脚本还原。这篇笔记就按这个顺序把常见几类逆向题的拆法、参数、脚本和踩过的坑讲清楚适合刚入门想系统过一遍题型的人也适合打了几年还在靠运气出题的老手对照检查自己的流程。下面所有内容都基于公开题型和通用工具链不涉及任何具体赛事的内部信息。2. 先判类型再动手几类 CTF 逆向题的入口识别逆向题最怕一上来就 F5。你得先知道手里这个东西是什么才能决定用 IDA、Ghidra、jadx 还是别的工具。判断类型这一步花两分钟能省你两小时。2.1 用 file 和 strings 做第一轮筛查拿到文件先别急着拖进反汇编器命令行两三条就能定性。# 看文件类型、架构、是否静态链接、有没有符号 file ./challenge # 提取可打印字符串找 flag 格式、提示语、可疑路径 strings -n 6 ./challenge | head -50 # 看有没有加壳或异常节区 readelf -S ./challenge 2/dev/null | grep -E upx|packedfile的输出直接告诉你这是 ELF 还是 PE、x86 还是 ARM、32 位还是 64 位。strings里如果直接出现flag{或者correct、wrong这类比较提示说明这题逻辑很浅可能就是个明文比较。readelf看节区名如果出现UPX0、UPX1那就是 UPX 壳先脱壳再谈别的。参数说明strings -n 6里的 6 是最小字符串长度调小会出很多噪音调大会漏掉短的关键词一般 5 到 8 之间试。head -50只是防止刷屏真正找线索时建议配合grep -i flag。2.2 静态链接和动态链接决定了你的调试策略这一步很多人忽略但它直接影响你能不能下断点。# 看动态链接依赖 ldd ./challenge # 如果是静态链接ldd 会提示 not a dynamic executable # 这时用 nm 或 objdump 看符号表还在不在 nm ./challenge 2/dev/null | grep -i main动态链接的程序main函数通常能在符号表里找到调试器下断点很方便。静态链接的程序符号可能被 strip 掉你得靠入口点_start往后跟或者用 IDA 的签名识别main。如果是安卓题直接上 jadx 看 Java 层so 层再用 IDA 处理。提示遇到 strip 过的静态链接二进制别硬跟_start先用 Ghidra 的自动分析跑一遍它的函数识别比裸看汇编快得多。2.3 安卓和 Flutter 逆向的入口差异安卓题分两种Java 层和 native 层。Java 层用 jadx 反编译 APK直接搜flag、check、verify这些关键词。native 层把lib/下的 so 拖进 IDA重点看JNI_OnLoad和Java_开头的导出函数。Flutter 逆向是近两年热起来的它的 Dart 代码编译成libapp.so普通 IDA 看不了。常见做法是用 Blutter 这类工具先还原 Dart 符号和对象结构再定位关键逻辑。Flutter 题的难点在于字符串和逻辑都被编译进快照strings经常一无所获得靠工具还原。# 安卓 APK 快速看结构 unzip -l app.apk | grep -E classes|lib/ # Flutter 题先确认是不是 Flutter unzip -l app.apk | grep libapp.so如果libapp.so存在基本可以确定是 Flutter直接换工具链别在 jadx 里浪费时间。这一步判断错了后面全是白工。3. 签到题和简单比较题三分钟出 flag 的固定套路签到题是 CTF 逆向里性价比最高的也是新手建立信心的第一站。它的模式极其固定程序读入你的输入和某个目标值比较相等就输出正确。你要做的就是找到那个目标值。3.1 定位比较逻辑的两种方法第一种是字符串交叉引用。在 IDA 里按ShiftF12打开字符串窗口找correct、right、success这类词双击进去按X看交叉引用就能跳到比较逻辑附近。第二种是直接看main的反汇编。签到题通常没有混淆main里就是scanf或fgets读输入然后一个循环逐字符比较。// 典型的签到题逻辑IDA 反编译后大概长这样 int main() { char input[64]; scanf(%63s, input); for (int i 0; i 32; i) { if ((input[i] ^ 0x1f) ! target[i]) { puts(wrong); return 0; } } puts(correct); return 0; }看到这种结构你要做的就是提取target数组然后反向运算。异或是对称的input[i] target[i] ^ 0x1f。3.2 用 Python 还原 flag 的脚本模板# 从 IDA 里把 target 数组抄出来或者用 idc 脚本导出 target [0x66, 0x6c, 0x61, 0x67, 0x7b, 0x2e, 0x2e, 0x2e] # 异或还原 flag .join(chr(b ^ 0x1f) for b in target) print(flag) # 如果是加减法比如 input[i] 3 target[i] flag2 .join(chr(b - 3) for b in target) print(flag2)逻辑说明签到题的运算无非异或、加减、移位、查表这几种。异或和加减可以直接逆移位要注意丢位查表要找到表本身。参数上唯一要注意的是数组长度和字符范围如果还原出来有不可打印字符说明运算方向搞反了或者 key 抄错了。3.3 从 IDA 批量导出数据的实用技巧手动抄数组容易出错尤其是几十上百字节的时候。用 IDA 的 IDC 或 IDAPython 脚本导出更稳。# IDAPython: 导出指定地址开始的 N 个字节 import idc start 0x404060 length 32 data [idc.get_wide_byte(start i) for i in range(length)] print(,.join(hex(b) for b in data))把这段贴进 IDA 的 Python 控制台改一下start和length直接拿到数组。这比手抄快十倍而且不会抄错。我一般会把导出的数据直接存成文件再在外部 Python 里处理避免在 IDA 里反复切换。注意如果目标数组在.rodata段且是字符串形式直接strings或者双击看内容可能更快不一定要写脚本。4. 安卓逆向题从 jadx 到 so 层的完整拆解安卓逆向是 CTF 里占比很高的一类也是新手容易迷路的地方。它的层次多Java 层、native 层、资源文件、甚至还有加固。拆的时候要一层一层剥。4.1 jadx 反编译和关键词定位# 用 jadx 命令行反编译 APK jadx -d output_dir app.apk # 反编译后在源码里搜关键词 grep -rn flag\|check\|verify\|password output_dir/sources/jadx 的图形界面更适合交互式看代码命令行适合批量搜索。搜到可疑的check函数后看它的逻辑如果只是简单的字符串比较直接跟进去看目标值如果调用了 native 方法就要去 so 层。// 典型的 Java 层校验 public boolean check(String input) { return input.equals(flag{...}); // 明文直接出 } // 或者调用 native public native boolean nativeCheck(String input);看到native关键字记下方法名去lib/下的 so 里找对应的Java_包名_类名_方法名导出函数。4.2 so 层定位 JNI 函数的技巧# 看 so 的导出符号 readelf -s libnative.so | grep Java_ # 或者用 nm nm -D libnative.so | grep Java_导出符号里会直接出现Java_com_example_app_MainActivity_nativeCheck这样的名字拖进 IDA 直接跳过去。如果符号被 strip 了就得从JNI_OnLoad里找RegisterNatives注册的函数表那里有函数指针和名字的对应关系。// JNI_OnLoad 里常见的动态注册 static JNINativeMethod methods[] { {nativeCheck, (Ljava/lang/String;)Z, (void*)check_impl}, };找到check_impl的地址跟进去就是真正的校验逻辑。这一步是安卓 native 逆向的核心很多人卡在找不到函数入口其实就是没看JNI_OnLoad。4.3 常见加密和编码的识别安卓题里的校验逻辑经常套一层编码或加密常见的有 Base64、AES、RC4、异或。识别方法看到ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/这种表基本是 Base64。看到固定的 16 字节或 32 字节数组作为 key可能是 AES。看到循环异或key 是单字节或短数组直接逆。import base64 # Base64 还原 encoded ZmxhZ3t... print(base64.b64decode(encoded)) # 单字节异或爆破 cipher bytes.fromhex(...) for k in range(256): plain bytes(b ^ k for b in cipher) if bflag in plain: print(k, plain)参数说明Base64 解码要注意补全有时候题目会去掉填充符。异或爆破的范围是 0 到 255判断条件用flag{或可打印字符比例。如果 key 是多字节就得先猜 key 长度再按位爆破这一步可以用已知明文攻击。提示安卓题如果遇到加固先脱壳再分析。常见做法是用 Frida 在运行时 dump dex或者用脱壳工具处理。脱壳本身是个大话题这里不展开但你要知道遇到libjiagu、libshell这类 so 时先怀疑加固。5. 避坑与排查逆向题里最容易翻车的五个地方逆向题的坑往往不是逻辑难而是环境、工具和细节。下面这五条是我踩过或者见别人踩过的按「现象 → 原因 → 解决」写。5.1 反编译出来是乱码或者函数缺失现象jadx 打开 APK 后关键类显示// decompiled code is not available或者 IDA 里函数识别成一堆sub_XXXX。原因代码被混淆或者加固jadx 无法正常反编译IDA 没有正确识别函数边界通常是二进制被 strip 或者加了花指令。解决安卓题先确认是否加固用脱壳工具处理后再反编译。IDA 里手动调整函数边界按P键把未识别的代码段定义成函数或者用 Ghidra 重新分析。花指令的话找到跳转模式后 patch 掉。5.2 动态调试时断点打不上现象在 IDA 或 gdb 里对某个地址下断点程序跑起来直接跳过或者提示无法写入断点。原因地址是 PIE位置无关可执行文件的偏移实际加载基址变了或者断点下在了未映射的内存区域。解决先看程序是不是 PIEfile输出里有pie executable就是。调试时用start让程序跑起来再根据实际基址加偏移下断点。gdb 里用break *$base0x1234IDA 里开 ASLR 关闭或者手动重定位。5.3 脚本还原出来的 flag 有乱码现象Python 脚本跑完输出一堆不可打印字符或者长度对不上。原因运算方向搞反了比如该减的加了或者数组抄错了多抄少抄了字节或者编码问题比如把 UTF-8 当 GBK 处理。解决先验证运算方向用已知的flag{前缀反推。检查数组长度和题目里的循环次数是否一致。编码问题就统一用bytes处理最后再decode。5.4 Flutter 题在 jadx 里找不到逻辑现象APK 反编译后 Java 层只有壳代码搜不到任何校验逻辑assets里也没有明显线索。原因Flutter 的逻辑全在libapp.so里Java 层只是启动器。解决确认lib/下有libapp.so和libflutter.so后换用 Blutter 或类似工具还原 Dart 快照再定位关键函数。别在 jadx 里死磕。5.5 时间花在无关函数上现象跟了好几个函数发现都是初始化或者日志真正的校验逻辑一直没找到。原因没有先做字符串交叉引用盲目跟调用链。解决回到字符串窗口找correct、wrong、flag这些提示语从提示语反向找引用。这是最快的定位方法比顺着main一路跟高效得多。我一般会先花五分钟把所有可疑字符串的交叉引用过一遍再决定跟哪个函数。6. 进阶技巧用脚本批量处理和验证你的逆向结果单题拆解是基本功但真正拉开效率差距的是把重复动作脚本化。逆向题里大量操作是可以自动化的批量提取字符串、批量爆破、批量验证。6.1 用 Python 做异或和单字节爆破的通用框架def brute_single_byte_xor(cipher, markerbflag): 单字节异或爆破返回所有含 marker 的候选 results [] for k in range(256): plain bytes(b ^ k for b in cipher) if marker in plain: results.append((k, plain)) return results # 用法 cipher bytes.fromhex(1a2b3c4d...) for key, plain in brute_single_byte_xor(cipher): print(fkey{key:#x} plain{plain})逻辑说明这个框架的核心是遍历 0 到 255 所有可能的 key用marker过滤。marker通常是flag{或者题目给的格式前缀。参数上cipher是密文字节串marker根据题目调整。如果题目没有明显 marker可以改成判断可打印字符比例。6.2 多字节异或的已知明文攻击def known_plaintext_attack(cipher, known): 已知明文攻击还原多字节异或的 key key bytes(c ^ p for c, p in zip(cipher, known)) return key # 假设已知明文开头是 flag{ cipher bytes.fromhex(...) known bflag{ key known_plaintext_attack(cipher, known) print(recovered key prefix:, key) # 用还原的 key 解全部 plain bytes(c ^ key[i % len(key)] for i, c in enumerate(cipher)) print(plain)这个技巧在 key 长度未知时特别有用。先用已知明文还原出 key 的前几个字节如果 key 是重复的就能推出完整 key。参数上known要足够长至少覆盖一个完整的 key 周期否则推不出全貌。6.3 用表格对照常见题型的解法题型入口特征常用工具关键操作签到比较strings 有 flag 提示IDA / Ghidra提取目标数组反向运算异或加密循环里有 xor 指令IDA Python单字节爆破或已知明文安卓 Java 层APK 无 so 或 so 无关jadx搜关键词看 equals安卓 native 层有 libnative.soIDA找 JNI_OnLoad 和导出函数Flutter有 libapp.soBlutter还原 Dart 快照再定位加壳节区名异常脱壳工具先脱壳再分析这张表不是让你背而是让你在拿到题的时候快速对照决定第一刀往哪切。我自己的习惯是先file和strings再对照这张表选工具基本不会走弯路。6.4 验证结果的一个小习惯出 flag 之前我一定会做一件事把还原出来的字符串再代回程序跑一遍或者用脚本重新加密看是否等于密文。这一步能过滤掉大部分「看起来像 flag 但其实是巧合」的情况。血泪经验是有次爆破出一串可打印字符兴冲冲提交发现是错的回头一查是 marker 判断太宽松把噪音当成了结果。后来我固定加一步回代验证再没翻过这种车。逆向题做多了你会发现真正决定速度的不是汇编水平而是流程是否固定、工具是否熟练、验证是否严格。把上面这套路径跑顺几个 CTF 逆向题下来你会有自己的手感。希望帮到你。本文还有配套的精品资源点击获取
返回列表