背景:
漆黑夜晚的海滩上,发现一个遗落很久的木箱,箱子很重,边角用钢板缝合,尝试用普通的石块工具根本砸不开,
潮水有节奏的拍打着沙滩,饥肠辘辘的漂泊者望着箱子发呆,里面一定有水果罐头或是金币皇冠...
挣扎许久忽然在箱子一角发现印有模糊的PIPEOR,他思索片刻嘴角露出久违的笑容
题目信息:
本题是安卓平台规则2的crackme
文件名:KCTF2022-sprint-android-crackme.apk
公开序列号如下:
name:KCTF
serial:3432356538383237303738386163323436323438663964343063393831366663
第二组序列号:
name:C8EB85C90E69EDC8
serial:3638386461396366623135623535353361323862656630656561326334303931
(通过命令shasum -a 256 KCTF2022-sprint-android-crackme.apk计算apk得文件hash:
c8eb85c90e69edc8ee85e7233c6cb91aa633f6ff5dc4882ca3fc3ba45225d8c1)
设计思路:
本题算法延续了KCTF2021-秋季赛-第八题群狼环伺的设计方法:
- 对输入的name字符串通过SHA1算法计算得到16字节的hash值
- 对hash值做rc4加密运算得到16字节value1值
- 选取部分代码计算SHA256得到16字节的密钥Key值
- 将输入的64字符长度password转为16字节的16进制表示(如:前8个字符64613231转换后为2字节的0xda21)和Key做3DES解密运算得到value2,当password不足64字节时提示错误
- 比较value1 == value2时,则为正确的密钥对,提示输入正确
增加防护技巧:
- 题目设计为双进程模式,算法的校验分别放在两个进程中,并通过管道通信且父子进程使用相互ptrace防调试处理
- 管道通信读写及部分操作如: open/read/getpid等系统调用通过中断svc指令实现
- 父子进程均增加/proc/self/status调试检测
- 算法及防护代码编译后的文件以二进制方式,对函数指令做了vm保护处理
解题思路:
主要是去除防调试功能及调试检测功能
- 对父进程的ptrace操作进行nop填充
- 对父进程中fork后创建ptrace线程pthread_create操作进行nop填充
- 对父子进程中/proc/self/status调试检测均进行nop填充
跟踪管道通信时,子进程read读数据操作,读取到value值后会进行校验,重点关注eor指令对数据进行的运算操作
vm保护原理:
正常编译后的代码通过反编译工具,逻辑结构很清晰,门槛低的小白都可以拿来用f5分析,所以代码防护很重要,最起码能防部分小白。最初级的加壳保护运行后会解密所以动态调试和dump内存也能获取到原代码,这时保护后勿需还原最终以保护态运行就会提高分析门槛。X86时期辉煌无数的vmprotect技术令大部分人闻风丧胆。该技术放到arm架构同样适用。原理也大同小异:
保护流程主要由三部分组成:反汇编器,解释器vmachine和链接器。
- 反汇编器 负责解析输入二进制文件,输出函数指令和数据流
- 解释器 负责对vm指令和数据流进行解释执行
- 链接器 负责将vmachine嵌入新二进制文件中,并对地址相关的指令做重定位
反汇编器取汇编指令列表中的指令根据指令类型生成对应的模拟代码,
如push/pop, ldr/str, bl, add, mov等,由于对指令进行了重新编码故称为指令模拟
原始代码开始替换为一条b 指令跳转到新生成的模拟代码运行,这样做可以避免其
他函数调用该函数时能正常工作。其余位置可以填充随机字段或者清空。
解释器工作原理:
针对ARM平台ELF格式的二进制程序的代码保护,兼容Arm&Thumb指令集,
首先反编译被保护的函数获取指令流 其次对指令流类型分别处理进行重定位
和需要模拟的指令进行编码生成vmdata最后通过链接7⃣️将解释器和原二进制
文件进行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及物联网平台,致力于通过技术手段,以减少恶意攻击及破坏行为
[2022夏季班]《安卓高级研修班(网课)》月薪三万班招生中~
最后于 2022-5-28 10:46
被kanxue编辑
,原因: