题目信息:
本题是安卓平台规则2的crackme
文件名:KCTF2022-autumn-android-crackme.apk
公开序列号如下:
name:KCTF
serial:42A4ECA067F54074C3EB2F177ACB06FE1379055CD4FB2211C3BD874FAD9E101D
第二组序列号:
name:5113BF890A692660
serial:FDF7C5A02A114C749970F516F1DB24FE9D6AC65C41B4EEEDD2EDA660D5EFB01E
(通过命令shasum -a 256 KCTF2022-autumn-android-crackme.apk计算apk得文件hash:
5113BF890A69266035BA4AE149F4E88183949F6E5F9B8201B404E0F5F98F29D3)
设计思路:
本题算法延续了KCTF2022-春季赛-第八题迷雾散去的设计方法:
- 对输入的name字符串通过SHA1算法计算得到16字节的hash值
- 对hash值做rc4加密运算得到16字节value1值
- 选取部分代码计算SHA256得到16字节的密钥Key值
- 将输入的64字符长度password转为32字节的16进制表示(如:前8个字符42A4ECA0转换后为4字节的0x42A4ECA0)和Key做异或运算得到value2,当password不足64字节时提示错误
- 比较value1 == value2时,则为正确的密钥对,提示输入正确
- 为避免多解问题,对编辑框输入的小写字符统一进行了大写转换
增加防护技巧:
- 题目设计为双进程模式,算法的校验分别放在两个进程中,并通过管道通信且父子进程使用相互ptrace防调试处理
- 管道通信读写及部分操作如: open/read/getpid等系统调用通过中断svc指令实现
- 父子进程均增加/proc/self/status调试检测
- 算法及防护代码编译后的文件以二进制方式,对函数指令做了vm保护处理
- 增加防frida检查
- 程序中字符串做了加密处理及符号表中函数名清除
题目考查点:
本题属于高阶对抗题目,虽然在算法上与之前想似,故算法部分不是考察重点,此次增加了混淆保护的形态多样性,故保护后的关键函数并没有相同之处,加上防调试及防frida的手段,本题重在考察选手对于复杂指令形态下,如何思考了解函数间关系,抽丝拨缕梳理解决问题的技巧能力。
解题思路:
程序中保留了android_log_print符号调用,并且保留了将序列号中间结果通过参数传递给子函数的调用,攻击者只需要找到该子函数并且将调用android_log_print打印参数的指令填充后,通过日志即可查看到真实serial
vm保护原理:
正常编译后的代码通过反编译工具,逻辑结构很清晰,门槛低的小白都可以拿来用f5分析,所以代码防护很重要,最起码能防部分小白。最初级的加壳保护运行后会解密所以动态调试和dump内存也能获取到原代码,这时保护后勿需还原最终以保护态运行就会提高分析门槛。X86时期辉煌无数的vmprotect技术令大部分人闻风丧胆。该技术放到arm架构同样适用。原理也大同小异:
保护流程主要由三部分组成:反汇编器,解释器vmachine和链接器。
- 反汇编器 负责解析输入二进制文件,输出函数指令和数据流
- 解释器 负责对vm指令和数据流进行解释执行
- 链接器 负责将vmachine嵌入新二进制文件中,并对地址相关的指令做重定位
反汇编器取汇编指令列表中的指令根据指令类型生成对应的模拟代码,
如push/pop, ldr/str, bl, add, mov等,由于对指令进行了重新编码故称为指令模拟
原始代码开始替换为一条b 指令跳转到新生成的模拟代码运行,这样做可以避免其
他函数调用该函数时能正常工作。其余位置可以填充随机字段或者清空。
解释器工作原理:
针对ARM平台ELF格式的二进制程序的代码保护,兼容Arm&Thumb指令集,
首先反编译被保护的函数获取指令流 其次对指令流类型分别处理进行重定位
和需要模拟的指令进行编码生成vmdata最后通过链接器将解释器和原二进制
文件进行patch生成新二进制由于编译后的汇编指令地址相关性,当把指令放到到千里之外,且不影响运行首要任务就是对地址相关指令做重定位
- ldr指令进行分析做重定位如下
.text:000016C2 19 4C LDR R4, =(__stack_chk_guard_ptr - 0x16CC)
.text:000016C4 C3 B0 SUB SP, SP, #0x10C
.text:000016C6 01 AE ADD R6, SP, #0x120+var_11C
.text:000016C8 7C 44 ADD R4, PC
.text:000016CA 24 68 LDR R4, [R4]
…
.text:00001728 A0 38 00 00 off_1728 DCD __stack_chk_guard_ptr - 0x16CC
Thumb指令16C2处指令为ldr r4, [pc, #0x64],
访问的数据地址为 (16C2&0xFFF4) + (PC+4) + 0x64 = 0x1728
意思是从0x1728中获取数据即0x38A0赋值给R4寄存器
R4寄存器在0x16C8地址处会跟PC寄存器相加即0x38A0+(PC+4)=0x4F6C
结合0x16CA指令读内存操作,也就是说程序是在读取0x4F6C地址处的数据
这段代码当移动到其他地址运行时PC寄存器会变为移动后的地址
例如这段代码移动到
.text:00006000 19 4C LDR R4, =(__stack_chk_guard_ptr - 0x16CC)
.text:00006002 C3 B0 SUB SP, SP, #0x10C
.text:00006004 01 AE ADD R6, SP, #0x120+var_11C
.text:00006006 7C 44 ADD R4, PC
.text:00006008 24 68 LDR R4, [R4]
…
.text:0000606C A0 38 00 00 off_1728 DCD __stack_chk_guard_ptr - 0x16CC
移动后0x6000地址指令读取0x606C数据为0x38A0到R4寄存器
在0x6006地址时R4与PC相加结果为0x38A0+(0x6006+4) = 0x98AA
这与原来的0x4F6C是不相符的,为了正确读取0x4F6C地址就需要对
0x606C处的数据做重定位即 0x4F6C = X+(0x6006+4)得出X=0x213C
将0x606C的0x38A0修改为0x213C这样才能确保访问正确
对模拟运行的指令重新编码
如
.text:000016D8 01 F0 D2 FD BL 0x1BA8
- BL指令是相对当前地址到跳转目标地址=0x16D8+0x1BA8 = 0x3280
当运行时如果通过POP {PC}模拟就需要将运行时的真实内存地址即
基地址+偏移地址,假设基地址和偏移地址存储在变量中就可以通过
如下方式进行
LABEL_BASE:
.word 0x62220000
LABEL_DISP:
.word 0x3280
PUSH {R0-R15}
LDR R0, LABEL_BASE
LDR R1, LABEL_DISP
ADD R0, R1
STR R0, [SP, #0x3C]
POP {R0-R12}
ADD SP, #0xC
LDR PC, [SP, #-4]
这样重定位和指令模拟就实现了
通过同样的方法实现其他指令的模拟
关于作者
热爱阅读与运动,专注于互联网安全行业,主攻方向基于二进制的指令虚拟化保护技术,研发的安全产品覆盖诸多平台包括Android,Linux,IOS及物联网平台,致力于通过技术手段,以减少恶意攻击及破坏行为。
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2022-11-21 13:16
被kanxue编辑
,原因: