@Soulghost wrote:
背景
到目前为止 iOS 13 的越狱方案中,只有 tfp0 予以公开,后续步骤均为闭源( jakeajames 的 jelbrekLib 最近刚刚增加了 iOS 13 的支持)。在研究 iOS 13 从 tfp0 到 jailbreak 的过程中发现了诸多困难,iOS 12 的 setuid 提权以及劫持虚函数的 kexec 都有所变化。考虑到从 XNU Source 和 kernelcache 中寻找线索需要消耗大量时间,我决定去逆一波 unc0ver 找线索(抄作业)。然而 unc0ver 为了防止黑恶势力利用,对控制流和字符串都做了混淆,混的亲妈都不认识了 (╯°□°)╯︵ ┻━┻。
寻找 kexec 的线索
为了找到 unc0ver 处理 iOS 13 kexec 的代码,我从 IOConnectTrap6 的 XREF 找到了许多调用点,打算从这些调用点去寻找 init_kexec 的代码,但是发现字符串都做了异或处理,为了找线索就不得不还原这些字符串:
简单看一眼代码可知是最基本的异或混淆,在编译期通过异或修改了静态字符串的值,在函数开始时再重新异或一次来还原:
那么解决方案也就简单了,把这个异或操作重新运算到这些字符串的值上面即可。
自动化解密
思路
要实现自动化解密,一个简单的方法是对执行运行时异或解密的代码进行静态分析,并模拟寄存器状态和内存的值,我们观察一段相关代码:
__text:000000010007A928 ADRP X8, #aA_4@PAGE ; "A�h�������is�a�̙��|���.�����=�������pT"... __text:000000010007A92C ADD X8, X8, #aA_4@PAGEOFF ; "A�h�������is�a�̙��|���.�����=�������pT"... __text:000000010007A930 ADRP X9, #asc_10019DDF0@PAGE ; "\x1B��B� Uu�����\x1B�B������� �����aI�"... __text:000000010007A934 ADD X9, X9, #asc_10019DDF0@PAGEOFF ; "\x1B��B� Uu�����\x1B�B������� �����aI�"... __text:000000010007A938 ADRP X10, #byte_10019DDB0@PAGE __text:000000010007A93C ADD X10, X10, #byte_10019DDB0@PAGEOFF __text:000000010007A940 ADRP X11, #byte_10019DD70@PAGE __text:000000010007A944 ADD X11, X11, #byte_10019DD70@PAGEOFF __text:000000010007A948 ADRP X12, #str_IOGraphicsAccelerator2@PAGE __text:000000010007A94C ADD X12, X12, #str_IOGraphicsAccelerator2@PAGEOFF __text:000000010007A950 LDRB W13, [X12] __text:000000010007A954 MOV W14, #0x64 __text:000000010007A958 EOR W13, W13, W14 __text:000000010007A95C AND W13, W13, #0xFF __text:000000010007A960 STRB W13, [X12]
可以发现这里的代码非常简单,只用到了少量指令,要模拟这些指令是非常简单的。
实现
状态模拟
我目前只分析了 sub_10007A8F8,其中只用到了以下这些指令:
handlers = { 'ADRP': handle_adrp, 'ADD': handle_add, 'LDRB': handle_ldrb, 'STRB': handle_strb, 'MOV': handle_mov, 'EOR': handle_eor, 'AND': handle_and, 'STUR': handle_stur, 'LDUR': handle_ldur }
其中要注意的是
ST*R
和LD*R
可能会操作 stack,这时候我们需要特殊判断并且模拟栈内存,我采用了一个很偷懒的模拟办法,那就是检测[x29, var_xx]
的表达式,然后读写一个模拟 memory 的 dict,其中 key=xx。寄存器只需要处理 X 和 W,这里用 X 来存储;栈上内存用 dict 来存储:
x = [None] * 31 for i in range(31): x[i] = RegX(i) x[29].writeX(0) # simulator stack memory memory = {}
静态分析
通过 IDAPython 提供的 API 从特定地址读取 asm 然后分析即可:
def u0_xorpatch(startAddr, endAddr): if endAddr < startAddr: raise Exception('invalid input params') print('xorpath from {} to {}'.format(hex(startAddr), hex(endAddr))) cursor = startAddr for _ in range(1 + (endAddr - startAddr) // 4): mnem = GetMnem(cursor) print('[*] {} {}'.format(hex(cursor), GetDisasm(cursor))) if mnem in handlers: handlers[mnem](cursor) else: print('[-] Warn: unresolved mnem ' + mnem + ' at ' + str(hex(cursor))) raise Exception('unresolved mnem ' + mnem + ' at ' + str(hex(cursor))) cursor += 4 if __name__ == '__main__': u0_xorpatch(0x10007A928, 0x10007BC7C)
执行效果
在完成脚本后,我尝试解密了 sub_10007A8F8 中的所有字符串,解密段的起始和终止位置非常好找,它是函数开场白后很长的一段代码块:
找到了起始地址和结束地址,将他们写入脚本的入参,然后通过 File -> Script File 加载,这里实验前建议先备份 idb,以防止 binpatch 后写坏数据库,最终效果为:
完整实现
Posts: 3
Participants: 2