ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

ctf-wiki Android 静态分析综合实战:ISCC、NJCTF 与强网杯三道真题逆向全解析

ctf-wiki Android 静态分析综合实战:ISCC、NJCTF 与强网杯三道真题逆向全解析 文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载导读本篇文章基于 ctf-wiki 仓库 docs/zh-tw/docs/android/basic_reverse/static/complex-example.md 展开系统讲解 Android 静态分析中的综合题目这类题目往往同时涉及 Java 层与 Nativeso层代码需要通过反编译、JNI 逆向、加密算法识别与脚本还原等手段联合求解。读完本文你将掌握 jadx 反编译定位核心逻辑、IDA 分析 JNI 函数、识别 base64 / RC4 / AES 等常见算法以及编写 Python 脚本还原明文flag的完整实战流程并理解其对应的仓库上下文Android 逆向基本介紹、Java 层静态分析示例、原生层静态分析示例。综合题为何综合在 Android 逆向基本介紹 中分析对象被划分为两类Java 层代码与原生层Native代码。单一题目的逆向往往只落在其中一层而綜合題目complex-example则刻意将逻辑分布在两层甚至多段加密流程中Java 层负责 UI 交互、字符串预处理、调用 Native 接口Native 层so负责核心校验或加解密密文可能先经 base64 等编码再进入 XOR、RC4、AES 等密码学变换。因此综合题的求解天然是一条反编译 → 定位关键函数 → 识别算法 → 写脚本还原的流水线。而 Android 关键代码定位 中提到的字符串定位法利用报错信息、Toast 文案、log 定位函数与敏感 API 定位法在这一类题目中同样适用——下文三道题正是这套方法论的完整演练。2017 ISCC Crackonebase64 编码 JNI 字符变换反编译定位主逻辑利用 jadx 对 apk 进行反编译可以得到程序的基本逻辑如下对用户输入的内容进行base64 编码然后在指定长度位置处插入\r\n这个操作对最终校验没有实际影响属于干扰项之后程序将编码后的内容传递给 so 中的check函数进行校验。分析 so 中的 check 函数check是一个典型的 JNI 函数第一个参数a1是JNIEnv *它通过GetByteArrayRegion把 Java 层传入的 byte 数组base64 编码结果拷贝到 C 堆内存中再执行一段字符级变换最后与硬编码字符串比较。反编译得到的核心逻辑如下env a1; len plen; str pstr; v7 malloc(plen); ((*env)-GetByteArrayRegion)(env, str, 0, len, v7); v8 malloc(len 1); memset(v8, 0, len 1); memcpy(v8, v7, len); v9 0; for ( i 0; ; i ) { --v9; if ( i len / 2 ) break; v11 v8[i] - 5; v8[i] v8[len v9]; v8[len v9] v11; } v8[len] 0; v12 strcmp(v8, 0HWYl1SE5UQWFfN?IPEo.UcshU); free(v8); free(v7); return v12 0;这段代码的变换规则需要逐行拆解v9初始为 0循环体开头执行--v9因此循环进行到第i轮时v9 -(i1)v8[len v9]等价于v8[len - i - 1]即从字符串末尾向前取第i个字符变换核心是v8[i]前半段字符先自减 5然后与对称位置的末尾字符v8[len-i-1]互换循环只在i len / 2时进行说明只处理字符串的前半部分与对称位置的字符完成交换后即止最终用strcmp与硬编码密文0HWYl1SE5UQWFfN?IPEo.UcshU比较v12 0视为通过严格说该返回值并不严谨但题目按相等设计。也就是说加密过程 base64(输入) 的前半段逐字符减 5 并与对称位置交换逆向时只需将密文对称换回、再对前半段逐字符加 5最后 base64 解码即可。Python 还原脚本根据上述规则很容易写出对应的恢复代码原文档给出的是 Python 2 版本import base64 def solve(): ans 0HWYl1SE5UQWFfN?IPEo.UcshU length len(ans) flag [0] * length beg 0 end length while beg length / 2: end - 1 flag[beg] chr(ord(ans[end]) 5) flag[end] ans[beg] beg 1 flag .join(flag) print base64.b64decode(flag) if __name__ __main__: solve()脚本思路与 C 代码完全对称beg从头递增、end从尾递减把密文对称位置的字符换回同时把前半段密文逐字符5复原对应加密时的-5。若使用 Python 3只需将最后一行改为print(base64.b64decode(flag))并把除法与索引处理保持一致即可。运行结果如下➜ 2017ISCC python exp.py flag{ISCCJAVANDKYXX}从实战角度看这道题的核心教训在于so 层出现malloc/memcpy/strcmp 硬编码字符串的模式几乎必然是预处理后与密文比较的校验逻辑逆向者的任务就是把字符级变换规则逐行翻译成等价的还原代码。2017 NJCTF easycrackJava 层异或 RC4 加密链程序基本逻辑通过简单逆向可以发现程序的基本逻辑如下监控界面文本框如果文本框内容改变则调用 nativeparseText函数parseText的主要功能如下首先调用 Java 层的函数messageMe获取一个字符串mestr。这个函数的逻辑基本是依次将 packagename 最后一个.后面的字符串的每一个字符与 51 进行异或将结果拼接起来然后以mestr长度为周期将两者进行异或核心逻辑为str[i j] mestr[j] ^ iinput[i j];即以mestr作为周期密钥对输入逐字节异或继而下面以I_am_the_key为密钥使用RC4加密对该部分进行加密然后将结果与最后的compare比较。识别 RC4 的依据在 Native 层如何确认这是 RC4原文档给出了两条非常实用的指纹依据在init函数中有256这个关键字而且基本就是 RC4 密钥的初始化过程RC4 的 KSA 阶段正是以 256 字节的 S 盒为核心crypt函数显然就是一个 RC4 加密函数明显就是 RC4 的加密逻辑PRGA 阶段输出密钥流并与明文异或。这一按特征常数与结构识别算法的手法与 原生层静态分析示例 中通过JNI_OnLoad与RegisterNatives表定位函数名的思路一脉相承先依据 256、S 盒交换等特征判断算法族再验证调用关系。解密脚本还原脚本需要反向走完整条链路RC4 解密 → 周期 XOR 解密 → messageMe 密钥生成如下from Crypto.Cipher import ARC4 def messageme(): name easycrack init 51 ans for c in name: init ord(c) ^ init ans chr(init) return ans def decrypt(cipher,key): plain for i in range(0,len(cipher),len(key)): tmp cipher[i:ilen(key)] plain .join(chr(ord(tmp[i])^ord(key[i])) for i in range(len(tmp))) return plain def main(): rc4 ARC4.new(I_am_the_key) cipher C8E4EF0E4DCCA683088134F8635E970EEAD9E277F314869F7EF5198A2AA4 cipher .join(chr(int(cipher[i:i2], 16)) for i in range(0, len(cipher), 2)) middleplain rc4.decrypt(cipher) mestr messageme() print decrypt(middleplain,mestr) if __name__ __main__: main()脚本要点说明messageme()用包名尾段easycrack逐字符与 51初值异或模拟 Java 层的密钥生成逻辑decrypt()按len(key)为周期切块后逐字节异或对应 Native 层的周期异或密文C8E4...先按两位十六进制转成字节流再交给ARC4解密ARC4即 PyCrypto 对 RC4 的封装对应加密方向为rc4.encrypt。运行结果如下➜ 2017NJCTF-easycrack python exp.py It_s_a_easyCrack_for_beginners ➜ 2017NJCTF-easycrack这道题的核心价值在于展示了一条Java 层生成密钥 Native 层完成加密的跨层密钥协商模式单看 Java 层无法得到完整校验逻辑单看 Native 层又缺少mestr的生成规则必须两层结合才能写出正确的还原脚本。2018 强网杯 picture lock基于 APK 签名的 AES 图片加密程序概览简单分析之后发现这是一个图片加密程序Java 层为 Native 层传入image/下的第一个文件名、希望加密后的图片文件名以及对应 apk 的签名的 md5。加密的密钥并非硬编码而是从 APK 自身签名指纹动态派生——这既防止了直接修改 apk 后仍能解密也要求逆向者必须先拿到原始签名。识别 AES 与还原加密流程由于程序明显是一个加密程序可以使用 IDA 的findcrypto 插件进行识别。结果发现了S 盒而且基本上符合AES的加密流程因此可以基本确定程序的主体是 AES 加密。经过细致分析native 层程序的基本流程如下将传入的签名的 md5 字符串分为两半生成两组密钥每次读入md5sig[i%32]大小的内容根据读入的大小决定使用哪一组密钥奇数使用第二组密钥偶数使用第一组密钥如果读入的大小不够 16 的话就将后面填充为不够的大小例如大小为 12 时填充 4 个0x4此时修改后的内容必然够 16 个字节对前 16 个字节进行AES 加密对于后面的字节将其与md5sig[i%32]依次进行异或。可以看到密文的每个分块长度都由签名 md5 的对应字符决定分块前半段16 字节走 AES、后半段走 XOR属于典型的分段混合加密结构。这里的md5sig[i%32]实际是取 md5 十六进制字符串32 个字符的第i%32个字符的 ASCII 码来充当长度与异或掩码。获取签名 MD5既然知道加密算法那就很容易逆了。首先可以获取签名的 md5使用 JDK 自带的keytool命令➜ picturelock keytool -list -printcert -jarfile picturelock.apk 签名者 #1: 签名: 所有者: CNa, OUb, Oc, Ld, STe, Cff 发布者: CNa, OUb, Oc, Ld, STe, Cff 序列号: 5f4e6be1 有效期为 Fri Sep 09 14:32:36 CST 2016 至 Tue Sep 03 14:32:36 CST 2041 证书指纹: MD5: F8:C4:90:56:E4:CC:F9:A1:1E:09:0E:AF:47:1F:41:8D SHA1: 48:E7:04:5E:E6:0D:9D:8A:25:7C:52:75:E3:65:06:09:A5:CC:A1:3E SHA256: BA:12:C1:3F:D6:0E:0D:EF:17:AE:3A:EE:4E:6A:81:67:82:D0:36:7F:F0:2E:37:CC:AD:5D:6E:86:87:0C:8E:38 签名算法名称: SHA256withRSA 主体公共密钥算法: 2048 位 RSA 密钥 版本: 3 扩展: #1: ObjectId: 2.5.29.14 Criticalityfalse SubjectKeyIdentifier [ KeyIdentifier [ 0000: 71 A3 2A FB D3 F4 A9 A9 2A 74 3F 29 8E 67 8A EA q.*.....*t?).g.. 0010: 3B DD 30 E3 ;.0. ] ]keytool -printcert输出中的证书指纹给出了完整的MD5、SHA1、SHA256值其中 MD5 指纹即为程序所依赖的签名哈希。接下来把带冒号的 MD5 指纹转成小写十六进制字符串作为后续解密脚本的sig➜ picturelock md5valueF8:C4:90:56:E4:CC:F9:A1:1E:09:0E:AF:47:1F:41:8D ➜ picturelock echo $md5value | sed s/://g | tr [:upper:] [:lower:] f8c49056e4ccf9a11e090eaf471f418d这里sed s/://g用于去除冒号tr [:upper:] [:lower:]用于统一转小写——最终得到 32 个字符的签名 md5 字符串与 Native 层md5sig[i%32]的索引规则完全对应。直接使用 AES 库解密已知算法与密钥后可以直接使用已有的 AES 库进行解密#!/usr/bin/env python import itertools sig f8c49056e4ccf9a11e090eaf471f418d from Crypto.Cipher import AES def decode_sig(payload): ans for i in range(len(payload)): ans chr(ord(payload[i]) ^ ord(sig[(16i)%32])) return ans def dec_aes(): data open(flag.jpg.lock, rb).read() jpg_data f open(flag.jpg, wb) idx 0 i 0 cipher1 AES.new(sig[:0x10]) cipher2 AES.new(sig[0x10:]) while idx len(data): read_len ord(sig[i % 32]) payload data[idx:idxread_len] #print([] Read %d bytes % read_len) print([] Totally %d / %d bytes, sig index : %d % (idx, len(data), i)) if read_len % 2 0: f.write(cipher1.decrypt(payload[:0x10])) else: f.write(cipher2.decrypt(payload[:0x10])) f.write(decode_sig(payload[16:])) f.flush() idx read_len i 1 print([] Decoding done ...) f.close() dec_aes()脚本与加密流程逐一对应sig[:0x10]与sig[0x10:]分别是 md5 的前后半段即加密时派生的两组 AES 密钥对应 AES-128每次从密文文件读取read_len ord(sig[i % 32])个字节与 Native 层每次读入md5sig[i%32]大小的内容一致依据read_len % 2的奇偶选择cipher1/cipher2解密前 16 字节payload[:0x10]剩余字节payload[16:]用sig[(16i)%32]逐字节异或还原对应加密时的掩码异或每块解密后写入flag.jpg直到密文读完。运行脚本后即可得到图片解密结果flag 就包含在解密出的图片中。这道题的核心价值在于展示了findcrypto 插件识别 AES 依据签名指纹动态派生密钥的综合逆向场景即使不知道密钥明文只要拿到原始 APK 签名并理解分块规则还原就是翻译代码的过程。综合方法论从三道题看 Android 静态分析实战将三道题串起来可以提炼出一套可复用的综合题求解流程反编译定位入口用 jadx 反编译 apk先找主 Activity 与关键监听函数如 Java 层静态分析示例 中按报错字符串Wrong Password、NOK_LICENCE_MSG反查代码的做法识别 Native 层校验so 层若通过JNI_OnLoadRegisterNatives动态注册函数先读注册表定位真实函数名参考 原生层静态分析示例 中off_400C的解析再用 findcrypto 等插件识别 S 盒、256 常数等算法指纹还原算法链路按编码 → 异或 → RC4/AES的顺序逐层逆向把 C/汇编级变换翻译成 Python 脚本注意运行时干扰项Crackone 的\r\n插入、so 中修改seed的_init_my这类障眼法需要在交叉引用数据时特别留意验证并输出脚本运行得到 flag 后可结合 Android 动态调试 中介绍的 DDMS、IDA 动态调试等手段交叉验证结论。综合题的本质是多层校验的编排而静态分析的取胜之道在于用特征与常量识别算法、用交叉引用梳理数据流、用脚本精确还原每一层变换。掌握这三道真题后你便具备了应对同类 Android 逆向综合题目的完整能力。赞分享文档网络安全教程【免费下载链接】ctf-wikiCome and join us, we need you!项目地址https://gitcode.com/gh_mirrors/ct/ctf-wiki点击查看免费下载相关推荐ctf-wiki 内核态 ROP 提权实战状态保存、swapgs/iretq 返回用户态与强网杯 2018 core 全解析ctf wiki 内核态 ROP 提权实战状态保存、swapgs/iretq 返回用户态与强网杯 2018 core 全解析 本文是 ctf wiki 内核文档网络安全教程ctf-wiki Android 逆向指南Smali 语法与 Dalvik 字节码指令全解ctf wiki Android 逆向指南Smali 语法与 Dalvik 字节码指令全解 Smali 是 DalvikART字节码的助记语法也是 An文档网络安全教程ctf-wiki Windows 逆向:花指令的编写原理、IDA 修复方法与 2017 看雪 CTF 例题动态破解实战ctf wiki Windows 逆向:花指令的编写原理、IDA 修复方法与 2017 看雪 CTF 例题动态破解实战 花指令 junk code 是 Wind文档网络安全教程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表