part 3 反调试原理分析
承接上一篇博文,破解时遗留了两个问题,一个是静态密码什么时候被修改的,一个是反调试部分内容。个人觉得反调试部分内容比较关键,所以就来扒一扒其背后的秘密。这里主要以汇编代码分析为主,结合动态调试,我的目标是训练阅读arm代码,提高效率也是我们值得花时间的。
还是从JNI_OnLoad入手分析,OnLoad函数主要做2件事情:
1、动态解密dlsym、getpid、sprintf、fopen、fgets、strstr、sscanf、kill、sleep、pthread_create、mprotect、cacheflush、lrand48等api的字符串,并调用dlsym函数,获取上诉api的地址,然后填写got(GLOBAL_OFFSET_TABLE)表。
.text:00001BDC ADD R7, R5, #4
.text:00001BE0 MOV R6, #0
.text:00001BE4
.text:00001BE4 loc_1BE4 ; CODE XREF: JNI_OnLoad+5Cj
.text:00001BE4 LDR R0, [R7,R6,LSL#2] ; 取得函数指针
.text:00001BE8 BLX R0 ; _GLOBAL_OFFSET_TABLE_ ; 调用initIATTable函数 解密字符串,初始化got表函数,函数调用完毕后getpid sprintf api地址准备完毕
2、api准备完成之后,调用pthread_create(),启动反调试检测线程。
.text:00001C28 SUB R5, SP, #8
.text:00001C2C MOV SP, R5
.text:00001C30 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1C48)
.text:00001C34 LDR R1, =(ThreadCallback - 0x5FBC)
.text:00001C38 MOV R3, #0
.text:00001C3C STR R8, [R5]
.text:00001C40 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001C44 ADD R2, R1, R0 ; ThreadCallback
.text:00001C48 ADD R0, R9, R0 ; unk_6290
.text:00001C4C MOV R1, #0 ; name
.text:00001C50 LDR R7, [R0,#(pthread_create_IAT - 0x6290)]
.text:00001C54 SUB R0, R11, #-handle ; handle
.text:00001C58 BLX R7 ; pthread_create(r0,r1,r2);启动反调试线程
下面是Jni_OnLoad函数代码
.text:00001B9C JNI_OnLoad
.text:00001B9C
.text:00001B9C handle = -0x20
.text:00001B9C
.text:00001B9C STMFD SP!, {R4-R9,R11,LR}
.text:00001BA0 ADD R11, SP, #0x18
.text:00001BA4 SUB SP, SP, #8
.text:00001BA8 MOV R4, R0
.text:00001BAC LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1BC0)
.text:00001BB0 LDR R9, =(unk_6290 - 0x5FBC)
.text:00001BB4 MOV R8, #0
.text:00001BB8 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001BBC ADD R0, R9, R0 ; unk_6290;获得got表中的unk_6290函数地址
这里相当于got表的一个"首地址"
.text:00001BC0 STR R8, [R0,#(dword_62C8 - 0x6290)];初始化全局变量dword_62C8的值为0
.text:00001BC4 LDR R5, [R0,#(dword_62C4 - 0x6290)] ;
.text:00001BC8 CMP R5, #0
.text:00001BCC BEQ loc_1C28 if(!dword_62C4) {}
.text:00001BD0
.text:00001BD0 loc_1BD0 ; CODE XREF: JNI_OnLoad+74j
.text:00001BD0 LDR R0, [R5]
.text:00001BD4 CMP R0, #1
.text:00001BD8 BLT loc_1BFC
.text:00001BDC ADD R7, R5, #4
.text:00001BE0 MOV R6, #0
.text:00001BE4
.text:00001BE4 loc_1BE4 ; CODE XREF: JNI_OnLoad+5Cj
.text:00001BE4 LDR R0, [R7,R6,LSL#2] ; 取得函数指针
.text:00001BE8 BLX R0 ; _GLOBAL_OFFSET_TABLE_ ; 调用initIATTable函数 解密字符串,初始化got表函数,函数调用完毕后getpid sprintf api地址准备完毕
.text:00001BEC LDR R0, [R5]
.text:00001BF0 ADD R6, R6, #1
.text:00001BF4 CMP R6, R0
.text:00001BF8 BLT loc_1BE4 ;
.text:00001BFC
.text:00001BFC loc_1BFC ; CODE XREF: JNI_OnLoad+3Cj
.text:00001BFC LDR R6, [R5,#0x2C]
.text:00001C00 MOV R0, R5 ; ptr
.text:00001C04 BL free_0
.text:00001C08 MOV R5, R6
.text:00001C0C CMP R6, #0
.text:00001C10 BNE loc_1BD0
.text:00001C14 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1C24)
.text:00001C18 MOV R1, #0
.text:00001C1C ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001C20 ADD R0, R9, R0 ; unk_6290
.text:00001C24 STR R1, [R0,#(dword_62C4 - 0x6290)]
.text:00001C28
.text:00001C28 loc_1C28 ; CODE XREF: JNI_OnLoad+30j
.text:00001C28 SUB R5, SP, #8
.text:00001C2C MOV SP, R5
.text:00001C30 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1C48)
.text:00001C34 LDR R1, =(ThreadCallback - 0x5FBC)
.text:00001C38 MOV R3, #0
.text:00001C3C STR R8, [R5]
.text:00001C40 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001C44 ADD R2, R1, R0 ; ThreadCallback
.text:00001C48 ADD R0, R9, R0 ; unk_6290
.text:00001C4C MOV R1, #0 ; name
.text:00001C50 LDR R7, [R0,#(pthread_create_IAT - 0x6290)]
.text:00001C54 SUB R0, R11, #-handle ; handle
.text:00001C58 BLX R7 ; pthread_create(r0,r1,r2);启动反调试线程
initIATTable()函数
主要工作:
1、拿解密key调用DecrptionIATTable,或者DecrptionIATTable1,或者DecrptionIATTable2,DecrptionIATTable3解密出dlsym、getpid、sprintf、fopen、fgets、strstr、sscanf、kill、sleep、pthread_create、mprotect、
cacheflush、lrand48字符串,并标记已经解密过。这里的解密算法用到4个,有些奇葩啊。
如下代码 dlsym的key为”9HbB”,调用DecrptionIATTable ()解密出”dlsym”字符串。isDlsymInited全局变量用来设置已经解密标志位,也就是下次解密前会先判断字符串是否解密,如果解密了,就不用再次解密了。
.text:00001CDC LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1CEC)
.text:00001CE0 LDR R1, =(encrpty_dlsym - 0x5FBC)
.text:00001CE4 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001CE8 ADD R2, R1, R0
.text:00001CEC LDR R1, =(a9hbb - 0x5FBC)key = "9HbB"
.text:00001CF0 ADD R4, R5, R0 ; unk_6290
.text:00001CF4 ADD R3, R1, R0 ; dlsym的解密key
.text:00001CF8 ADD R0, R4, #0x47
.text:00001CFC MOV R1, #6
.text:00001D00 BL DecrptionIATTable ;解密算法1
.text:00001D04 MOV R0, #1
.text:00001D08 STRB R0, [R4,#(isDlsymInited - 0x6290)]
2、调用dlsym(),挨个获取实际的内存地址,并填写到got表中
.text:00001D74 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1D80)
.text:00001D78 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001D7C ADD R6, R5, R0 ; unk_6290
.text:00001D80 MOV R0, #0xFFFFFFFF
.text:00001D84 ADD R1, R6, #0x5F
.text:00001D88 BLX R4 ; _GLOBAL_OFFSET_TABLE_
.text:00001D8C STR R0, [R6,#(getpid_IAT - 0x6290)]存储到getpid_IAT 地址中
initIATTable()函数代码如下
initIATTable ; DATA XREF: sub_2378+10o
.text:00001CA8 ; .text:off_2398o
.text:00001CA8
.text:00001CA8 var_20 = -0x20
.text:00001CA8 var_1C = -0x1C
.text:00001CA8
.text:00001CA8 STMFD SP!, {R4-R7,R11,LR}
.text:00001CAC SUB SP, SP, #8
.text:00001CB0 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1CC0)
.text:00001CB4 LDR R5, =(unk_6290 - 0x5FBC)
.text:00001CB8 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001CBC ADD R0, R5, R0 ; unk_6290
.text:00001CC0 LDRB R0, [R0,#(isDlsymInited - 0x6290)]
.text:00001CC4 CMP R0, #0
.text:00001CC8 BNE loc_1D0C
.text:00001CCC MOV R1, #4
.text:00001CD0 MOV R0, #0xC5
.text:00001CD4 STR R1, [SP,#0x20+var_20]
.text:00001CD8 STR R0, [SP,#0x20+var_1C]
.text:00001CDC LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1CEC)
.text:00001CE0 LDR R1, =(encrpty_dlsym - 0x5FBC)
.text:00001CE4 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001CE8 ADD R2, R1, R0
.text:00001CEC LDR R1, =(a9hbb - 0x5FBC)
.text:00001CF0 ADD R4, R5, R0 ; unk_6290
.text:00001CF4 ADD R3, R1, R0 ; dlsym的解密key
.text:00001CF8 ADD R0, R4, #0x47
.text:00001CFC MOV R1, #6
.text:00001D00 BL DecrptionIATTable ;解密算法1
.text:00001D04 MOV R0, #1
.text:00001D08 STRB R0, [R4,#(isDlsymInited - 0x6290)]
.text:00001D0C
.text:00001D0C loc_1D0C ; CODE XREF: initIATTable+20j
.text:00001D0C LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1D18)
.text:00001D10 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001D14 ADD R6, R5, R0 ; unk_6290
.text:00001D18 MOV R0, #0xFFFFFFFF ; handle
.text:00001D1C ADD R1, R6, #0x47 ; name
.text:00001D20 BL dlsym_0
.text:00001D24 MOV R4, R0 ; RO为dlsym的代码地址
.text:00001D28 LDRB R0, [R6,#(isGetpidInited - 0x6290)]
.text:00001D2C CMP R0, #0
.text:00001D30 BNE loc_1D74
.text:00001D34 MOV R1, #2
.text:00001D38 MOV R0, #0xD5
.text:00001D3C STR R1, [SP,#0x20+var_20]
.text:00001D40 STR R0, [SP,#0x20+var_1C]
.text:00001D44 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1D54)
.text:00001D48 LDR R1, =(unk_448B - 0x5FBC)
.text:00001D4C ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001D50 ADD R2, R1, R0
.text:00001D54 LDR R1, =(aM7 - 0x5FBC)
.text:00001D58 ADD R6, R5, R0 ; unk_6290
.text:00001D5C ADD R3, R1, R0 ; getpid的解密key
.text:00001D60 ADD R0, R6, #0x5F
.text:00001D64 MOV R1, #7
.text:00001D68 BL DecrptionIATTable
.text:00001D6C MOV R0, #1
.text:00001D70 STRB R0, [R6,#(isGetpidInited - 0x6290)]
.text:00001D74
.text:00001D74 loc_1D74 ; CODE XREF: initIATTable+88j
.text:00001D74 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1D80)
.text:00001D78 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001D7C ADD R6, R5, R0 ; unk_6290
.text:00001D80 MOV R0, #0xFFFFFFFF
.text:00001D84 ADD R1, R6, #0x5F
.text:00001D88 BLX R4 ; _GLOBAL_OFFSET_TABLE_
.text:00001D8C STR R0, [R6,#(getpid_IAT - 0x6290)]
.text:00001D90 LDRB R0, [R6,#(isSprintfInited - 0x6290)]
.text:00001D94 CMP R0, #0
.text:00001D98 BNE loc_1DDC
.text:00001D9C MOV R1, #4
.text:00001DA0 MOV R0, #0xE9
.text:00001DA4 STR R1, [SP,#0x20+var_20]
.text:00001DA8 STR R0, [SP,#0x20+var_1C]
.text:00001DAC LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1DBC)
.text:00001DB0 LDR R1, =(unk_44F3 - 0x5FBC)
.text:00001DB4 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001DB8 ADD R2, R1, R0
.text:00001DBC LDR R1, =(aLnat - 0x5FBC)
.text:00001DC0 ADD R6, R5, R0 ; unk_6290
.text:00001DC4 ADD R3, R1, R0 ; 解密sprintf的key
.text:00001DC8 ADD R0, R6, #0x7C
.text:00001DCC MOV R1, #8
.text:00001DD0 BL DecrptionIATTable1解密算法2
.text:00001DD4 MOV R0, #1
.text:00001DD8 STRB R0, [R6,#(isSprintfInited - 0x6290)]
.text:00001DDC
.text:00001DDC loc_1DDC ; CODE XREF: initIATTable+F0j
.text:00001DDC LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1DE8)
.text:00001DE0 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001DE4 ADD R6, R5, R0 ; unk_6290
.text:00001DE8 MOV R0, #0xFFFFFFFF
.text:00001DEC ADD R1, R6, #0x7C
.text:00001DF0 BLX R4 ; _GLOBAL_OFFSET_TABLE_
.text:00001DF4 STR R0, [R6,#(sprintf_IAT - 0x6290)]
.text:00001DF8 LDRB R0, [R6,#(isFopenInited - 0x6290)]
.text:00001DFC CMP R0, #0
.text:00001E00 BNE loc_1E44
.text:00001E04 MOV R1, #4
.text:00001E08 MOV R0, #0xE7
.text:00001E0C STR R1, [SP,#0x20+var_20]
.text:00001E10 STR R0, [SP,#0x20+var_1C]
.text:00001E14 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1E24)
.text:00001E18 LDR R1, =(a0WE - 0x5FBC)
.text:00001E1C ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001E20 ADD R2, R1, R0 ; "0磢w湧"
.text:00001E24 LDR R1, =(aCoxt - 0x5FBC)
.text:00001E28 ADD R6, R5, R0 ; unk_6290
.text:00001E2C ADD R3, R1, R0 ; 解密fopen的key
.text:00001E30 ADD R0, R6, #0x4D
.text:00001E34 MOV R1, #6
.text:00001E38 BL DecrptionIATTable1
.text:00001E3C MOV R0, #1
.text:00001E40 STRB R0, [R6,#(isFopenInited - 0x6290)]
.text:00001E44
.text:00001E44 loc_1E44 ; CODE XREF: initIATTable+158j
.text:00001E44 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1E50)
.text:00001E48 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001E4C ADD R6, R5, R0 ; unk_6290
.text:00001E50 MOV R0, #0xFFFFFFFF
.text:00001E54 ADD R1, R6, #0x4D
.text:00001E58 BLX R4 ; _GLOBAL_OFFSET_TABLE_
.text:00001E5C STR R0, [R6,#(fopen_IAT - 0x6290)]
.text:00001E60 LDRB R0, [R6,#(isFgetsdInited - 0x6290)]
.text:00001E64 CMP R0, #0
.text:00001E68 BNE loc_1EA4
.text:00001E6C MOV R6, #1
.text:00001E70 MOV R0, #3
.text:00001E74 LDR R1, =(aIU - 0x5FBC)
.text:00001E78 STMEA SP, {R0,R6}
.text:00001E7C LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1E88)
.text:00001E80 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001E84 ADD R2, R1, R0 ; "┑阜罸"
.text:00001E88 LDR R1, =(aBmt - 0x5FBC)
.text:00001E8C ADD R7, R5, R0 ; unk_6290
.text:00001E90 ADD R3, R1, R0 ; 解密fgets的key
.text:00001E94 ADD R0, R7, #0x53
.text:00001E98 MOV R1, #6
.text:00001E9C BL DecrptionIATTable
.text:00001EA0 STRB R6, [R7,#(isFgetsdInited - 0x6290)]
......
.text:000021D8 ADD SP, SP, #8
.text:000021DC LDMFD SP!, {R4-R7,R11,PC}
上诉代码执行完成后got表内容如下:
前面准备工作完成后,就开始调用pthread_create(),函数体在ThreadCallback中。
上篇文件还原过这里的汇编代码,就一个while循环,一个调用反调试检测(AnitDeubging)代码,另外一个是sleep(3),线程休眠3秒钟。
.text:000016A4 ThreadCallback ; DATA XREF: JNI_OnLoad+A8o
.text:000016A4 ; .text:off_1CA4o
.text:000016A4 STMFD SP!, {R4,LR}
.text:000016A8 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x16B8)
.text:000016AC LDR R1, =(unk_6290 - 0x5FBC)
.text:000016B0 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:000016B4 ADD R4, R1, R0 ; unk_6290
.text:000016B8
.text:000016B8 loc_16B8 ; CODE XREF: ThreadCallback+24j
.text:000016B8 BL AnitDeubging
.text:000016BC LDR R1, [R4,#(sleep_IAT - 0x6290)]
.text:000016C0 MOV R0, #3
.text:000016C4 BLX R1 ; 调用sleep(3),休眠3秒钟
.text:000016C8 B loc_16B8
线程函数c代码还原
while(true){
AnitDeubging();
sleep(3);//调用函数
}
接下来是重点了,我们马上要解开反调试秘密了
主要工作:
1、fopen(“/proc/pid/status”)
2、循环fgets()读取文件内容
3、调用strstr定位”TracerPid”,获取后面的TracerPid的值
4、TracerPid为 0,代表程序没有被调试,大于1,则代表有进程附加了。
5、当TracerPid不为0,执行kill(myself_pid),程序退出
调试器检测业务逻辑1
调用getpid api,获得当前app的进程id,紧接着判断”/proc/%d/status”是否已经解密,没有解密,则调用解密函数动态解密,并设置已经解密标志位。
.text:0000130C STMFD SP!, {R4-R11,LR}
.text:00001310 SUB SP, SP, #0x324
.text:00001314 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1328) ; 动态调试变成了0xF3CA6328
.text:00001318 MOV R1, #0x64
.text:0000131C MOV R2, #0
.text:00001320 ADD R4, PC, R0 ; _GLOBAL_OFFSET_TABLE_ ; got表的一个"首地址"
.text:00001324 LDR R0, =(__stack_chk_guard_ptr - 0x5FBC)
.text:00001328 LDR R0, [R0,R4] ; __stack_chk_guard
.text:0000132C LDR R0, [R0]
.text:00001330 STR R0, [SP,#0x348+checkStack] ; 将chk_guard指向的内容,存入当前栈中
.text:00001334 ADD R0, SP, #0x348+lpstrArry
.text:00001338 BL __aeabi_memset_0 ; 调用 memset 初始化100大小的堆栈空间
.text:0000133C LDR R7, =(unk_6290 - 0x5FBC)
.text:00001340 ADD R5, R7, R4 ; unk_6290
.text:00001344 LDR R0, [R5,#(getpid_IAT - 0x6290)] ; 动态调试发现这里是getpid的地址(导入表 0xF7010D1B)
.text:00001348 BLX R0 ; call getpid,返回当前进程的pid
.text:0000134C MOV R8, R0
.text:00001350 LDR R4, [R5,#(sprintf_IAT - 0x6290)] ; sprintf(0xF703E34B)
.text:00001354 LDRB R0, [R5,#(byte_635B - 0x6290)]
.text:00001358 CMP R0, #0 ; byte_635b内容是否为1
.text:0000135C BNE loc_13A0 ; if(byte_635b == 0){
.text:0000135C ; }
.text:00001360 MOV R1, #4
.text:00001364 MOV R0, #0x57
.text:00001368 STR R1, [SP,#0x348+local1]
.text:0000136C STR R0, [SP,#0x348+local2]
.text:00001370 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1380)
.text:00001374 LDR R1, =(dword_4550 - 0x5FBC)
.text:00001378 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:0000137C ADD R2, R1, R0 ; dword_4550
.text:00001380 LDR R1, =(aSL - 0x5FBC)
.text:00001384 ADD R5, R7, R0 ; unk_6290
.text:00001388 ADD R3, R1, R0 ; "s!#L"
.text:0000138C ADD R0, R5, #0xB9
.text:00001390 MOV R1, #0x10
.text:00001394 BL DecrptionIATTable1 ; 解密字符串"/proc/%d/status"
.text:00001398 MOV R0, #1 ; 设置全局变量 byte_635b 标志位为1
.text:0000139C STRB R0, [R5,#(byte_635B - 0x6290)]
调试器检测业务逻辑2
调用sprintf(“/proc/%d/status”,PID),构建当前app进程的状态文件全路径字符串。紧接着解密
“TracerPid”字符串,并设置已经解密过标志位
.text:000013A0 loc_13A0 ; CODE XREF: AnitDeubging+50j
.text:000013A0 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x13B0)
.text:000013A4 MOV R2, R8
.text:000013A8 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:000013AC ADD R6, R7, R0 ; unk_6290
.text:000013B0 ADD R0, SP, #0x348+lpstrArry
.text:000013B4 ADD R1, R6, #0xB9
.text:000013B8 BLX R4 ; call sprintf(lpstrArry,"/proc/%d/status",r2)
.text:000013BC LDR R5, [R6,#(fopen_IAT - 0x6290)]
.text:000013C0 LDRB R0, [R6,#(byte_635C - 0x6290)]
.text:000013C4 CMP R0, #0
.text:000013C8 BNE loc_1404
.text:000013CC MOV R6, #1
.text:000013D0 MOV R0, #0
.text:000013D4 LDR R1, =(unk_454A - 0x5FBC)
.text:000013D8 STMEA SP, {R0,R6}
.text:000013DC LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x13E8)
.text:000013E0 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:000013E4 ADD R2, R1, R0 ; unk_454A
.text:000013E8 LDR R1, =(unk_44FC - 0x5FBC)
.text:000013EC ADD R4, R7, R0 ; unk_6290
.text:000013F0 ADD R3, R1, R0 ; unk_44FC
.text:000013F4 MOV R0, R4
.text:000013F8 MOV R1, #2
.text:000013FC BL DecrptionIATTable2 ; 解密TracerPid字符串
.text:00001400 STRB R6, [R4,#(byte_635C - 0x6290)]
.text:00001404
.text:00001404 loc_1404
调试器检测业务逻辑3
调用fopen打开”/proc/34352/status”文件,并读取0x200大小的内容.
.text:00001404 loc_1404 ; CODE XREF: AnitDeubging+BCj
.text:00001404 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1414)
.text:00001408 STR R8, [SP,#0x348+var_338]
.text:0000140C ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001410 ADD R4, R7, R0 ; unk_6290
.text:00001414 ADD R0, SP, #0x348+lpstrArry
.text:00001418 MOV R1, R4
.text:0000141C BLX R5 ; fopen("/proc/34352/status")
.text:00001420 ADD R6, SP, #0x348+var_28C
.text:00001424 MOV R5, R0 ; 函数返回结果
.text:00001428 MOV R1, #0x200
.text:0000142C MOV R2, #0
.text:00001430 MOV R0, R6
.text:00001434 STR R5, [SP,#0x348+statusFileFd]
.text:00001438 BL __aeabi_memset_0 ; memset(local_28c,0x200,0);感觉这里应该是memset(local_28c,0,0x200)才对
.text:0000143C LDR R3, [R4,#(fgets_IAT - 0x6290)]
.text:00001440 MOV R0, R6
.text:00001444 MOV R1, #0x200
.text:00001448 MOV R2, R5
.text:0000144C
.text:0000144C fgets__ ; fgets
.text:0000144C BLX R3
.text:00001450 MOV R6, R7 ; r6 = unk_6290
.text:00001454 CMP R0, #0 ; 比较结果是否为0
.text:00001458 BEQ loc_161C ; 从跳转来看,不跳转执行了很多代码,不得不怀疑这里到关键点了
调试器检测 业务逻辑4
循环调用fgets读取status文件内容,然后调用strstr(“Name:.yaotong.crackme”,”TracerPid”)查找子串,如果每次查找到,继续读取status文件内容,直到文件末尾。如果查找到TracerPid,则取出”TracerPid: 13654”中的进程ID,这里实际上是./android_server进程的id.判断进程ID是否大于等于1,大于1走 退出程序流程
.text:000014F8 loc_14F8 ; CODE XREF: AnitDeubging+2ECj
.text:000014F8 LDR R5, [R8,#(strstr_IAT - 0x6290)]
.text:000014FC LDRB R0, [R8,#(byte_635D - 0x6290)]
.text:00001500 CMP R0, #0
.text:00001504 BNE loc_154C ; 解密"%s,%d"字符串,如果解密过,则跳过
.text:00001508 MOV R0, #2
.text:0000150C LDR R2, [SP,#0x348+var_324]
.text:00001510 LDR R3, [SP,#0x348+var_328]
.text:00001514 MOV R11, R4
.text:00001518 MOV R4, R6
.text:0000151C MOV R1, #0xA
.text:00001520 STR R0, [SP,#0x348+local1]
.text:00001524 MOV R0, #0x9D
.text:00001528 STR R0, [SP,#0x348+local2]
.text:0000152C LDR R0, [SP,#0x348+var_320]
.text:00001530 ADD R6, R4, R0
.text:00001534 ADD R0, R6, #0x95
.text:00001538 BL DecrptionIATTable
.text:0000153C MOV R0, #1
.text:00001540 STRB R0, [R6,#0xCD]
.text:00001544 MOV R6, R4
.text:00001548 MOV R4, R11
.text:0000154C
.text:0000154C loc_154C ; CODE XREF: AnitDeubging+1F8j
.text:0000154C MOV R0, R9
.text:00001550 MOV R1, R7
.text:00001554 BLX R5 ; example strstr("Name:.yaotong.crackme","TracerPid")
.text:00001558 CMP R0, #0 ; if(r0 != 0){}
.text:0000155C BEQ loc_15DC ; 查找TracerPid字符串
.text:00001560 MOV R0, R4
.text:00001564 MOV R1, #0x80
.text:00001568 MOV R2, #0
.text:0000156C BL __aeabi_memset_0
.text:00001570 MOV R0, #0
.text:00001574 STR R0, [SP,#0x348+local_key]
.text:00001578 LDR R11, [R10,#(sscanf_IAT - 0x6290)]
.text:0000157C LDRB R0, [R10,#(byte_635E - 0x6290)]
.text:00001580 CMP R0, #0
.text:00001584 BNE loc_15BC
.text:00001588 MOV R0, #3
.text:0000158C LDR R2, [SP,#0x348+var_330]
.text:00001590 LDR R3, [SP,#0x348+var_334]
.text:00001594 MOV R1, #6
.text:00001598 STR R0, [SP,#0x348+local1]
.text:0000159C MOV R0, #0x93
.text:000015A0 STR R0, [SP,#0x348+local2]
.text:000015A4 LDR R0, [SP,#0x348+var_32C]
.text:000015A8 ADD R5, R6, R0
.text:000015AC ADD R0, R5, #0x41
.text:000015B0 BL DecrptionIATTable1 ; 解密字符串
.text:000015B4 MOV R0, #1
.text:000015B8 STRB R0, [R5,#0xCE]
.text:000015BC
.text:000015BC loc_15BC ; CODE XREF: AnitDeubging+278j
.text:000015BC LDR R1, [SP,#0x348+var_31C]
.text:000015C0 MOV R0, R9
.text:000015C4 MOV R2, R4
.text:000015C8 ADD R3, SP, #0x348+local_key
.text:000015CC BLX R11 ; 3个参数函数
.text:000015D0 LDR R0, [SP,#0x348+local_key]
.text:000015D4 CMP R0, #1 ; TracerPid :(./android_server)
.text:000015D8 BGE loc_1600 ; TracerPid >= 1 说明正在被调试
.text:000015DC
.text:000015DC loc_15DC ; CODE XREF: AnitDeubging+250j
.text:000015DC LDR R0, [SP,#0x348+fgetsOffsetAddr] ; r0 < 1 继续执行
.text:000015E0 LDR R2, [SP,#0x348+statusFileFd]
.text:000015E4 MOV R1, #0x200
.text:000015E8 LDR R3, [R0,#0x10]
.text:000015EC MOV R0, R9 ; szBuffer
.text:000015F0 BLX R3 ; fgets()
.text:000015F4 CMP R0, #0 ; 结果不等于 0 继续循环,否则跳出循环
.text:000015F8 BNE loc_14F8
.text:000015FC B loc_161C
调试器检测业务逻辑5
如果TracerPid 的值大于等于1,执行kill(pid),退出自身程序,如果等于0,则正常结束流程,等待下一次检测。
.text:00001600 loc_1600 ; CODE XREF: AnitDeubging+2CCj
.text:00001600 LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x1610)
.text:00001604 MOV R1, #9
.text:00001608 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:0000160C ADD R0, R6, R0
.text:00001610 LDR R2, [R0,#gotOffsetOfKill]
.text:00001614 LDR R0, [SP,#0x348+var_338]
.text:00001618 BLX R2 ; __imp___stack_chk_fail ; 实际上调用的是kill(pid)函数
.text:0000161C ; ---------------------------------------------------------------------------
.text:0000161C
.text:0000161C loc_161C ; CODE XREF: AnitDeubging+14Cj
.text:0000161C ; AnitDeubging+2F0j
.text:0000161C LDR R0, =(_GLOBAL_OFFSET_TABLE_ - 0x162C)
.text:00001620 LDR R1, =(__stack_chk_guard_ptr - 0x5FBC)
.text:00001624 ADD R0, PC, R0 ; _GLOBAL_OFFSET_TABLE_
.text:00001628 LDR R0, [R1,R0] ; __stack_chk_guard
.text:0000162C LDR R0, [R0]
.text:00001630 LDR R1, [SP,#0x348+checkStack]
.text:00001634 SUBS R0, R0, R1
.text:00001638 ADDEQ SP, SP, #0x324
.text:0000163C LDMEQFD SP!, {R4-R11,PC}
.text:00001640 BL __stack_chk_fail_0
程序在调试时,我们来看看crackme的status文件的内容
注意图中的第6行TracerPid: 16364
接着验证一下16364到底是哪个进程
图中第3行,PID 16364,正是android_server进程。
反调试技术小结
crackme通过循环读取”/proc/self/status”文件中的”TracerPid”的值,如果值大于1则说明进程被附加了,也就是有程序在调试本app,然后使用kill(self_pid)发送杀死信号给系统,让系统结束掉app的进程。
技术总结
从整个so库代码上来看,crackme的安全编程思想应用的很到位,字符串都是加密保存的,用的时候才解密,这个静态分析带来一定的困啦,解密算法也不只一个,而且解密算法,感觉带混淆。不难猜测破解密码也是加密保存的,运行时再解密,这里我就不分析了,感兴趣的同学可以去分析一下。