-
-
[原创]看雪CTF2017 第6题
-
2017-6-12 21:21 3399
-
直接jeb打开apk查看流程
00000006 invoke-virtual EditText->getText()Editable, v0 //获取输入值 0000000C move-result-object v0 0000000E invoke-virtual Object->toString()String, v0 00000014 move-result-object v0 00000016 invoke-virtual String->trim()String, v0 0000001C move-result-object v0 0000001E new-instance v1, StringBuilder 00000022 invoke-direct StringBuilder-><init>()V, v1 00000028 invoke-virtual StringBuilder->append(String)StringBuilder, v1, v0 0000002E invoke-virtual StringBuilder->toString()String, v1 00000034 move-result-object v0 00000036 invoke-virtual String->trim()String, v0 0000003C move-result-object v0 0000003E invoke-static utils->check(String)Z, v0 //调用jni中的check函数 00000044 move-result v0
发现验证走的是jni,用ida加载apk中的so,发现so没有加壳,但是有代码混淆,jni函数不是动态注册,可以直接找到check函数地址,
习惯性的看了下字符串列表,发现有一些16进制的字符串
.rodata:0001D5CA a5229c2bd6437c1 DCB "5229C2BD6437C15E526793F96430D0595026",0 .rodata:0001D5CA ; DATA XREF: .text:00003F24o .rodata:0001D5CA ; .text:off_3FA8o .rodata:0001D5EF a13b82d68593952 DCB "13B82D685939526D",0 ; DATA XREF: .text:00003F36o .rodata:0001D5EF ; .text:off_3FACo .rodata:0001D600 a1410ceb874a13b DCB "1410CEB874A13B500B358D",0 ; DATA XREF: .text:00003F52o .rodata:0001D600 ; .text:off_3FB0o .rodata:0001D617 aD79d76c575ac5d DCB "D79D76C575AC5DAFC8B835",0 ; DATA XREF: .text:00003F5Co .rodata:0001D617 ; .text:off_3FB4o .rodata:0001D62E a71897fab1c51cc DCB "71897FAB1C51CCD475927D964F",0 .rodata:0001D62E ; DATA XREF: .text:00003F74o .rodata:0001D62E ; .text:off_3FB8o .rodata:0001D649 a304b53eaf18422 DCB "304B53EAF184220D345051D7A2",0 .rodata:0001D649 ; DATA XREF: .text:00003F7Eo .rodata:0001D649 ; .text:off_3FBCo .rodata:0001D664 a4b1d178c542ef2 DCB "4B1D178C542EF29A1042118047",0 .rodata:0001D664 ; DATA XREF: .text:00003FFEo .rodata:0001D664 ; .text:off_4070o .rodata:0001D67F a221dd6112a3d27 DCB "221DD6112A3D27F7",0 ; DATA XREF: .text:00004008o .rodata:0001D67F ; .text:off_4074o .rodata:0001D690 a973c7eaa8c8b94 DCB "973C7EAA8C8B94DD9D390AA2FD",0 .rodata:0001D690 ; DATA XREF: .text:0000401Eo .rodata:0001D690 ; .text:off_4078o .rodata:0001D6AB a12fa6fdf29f96c DCB "12FA6FDF29F96CD212E77CC039",0 .rodata:0001D6AB ; DATA XREF: .text:0000420Co .rodata:0001D6AB ; .text:off_42B8o .rodata:0001D6C6 a6c600dafa9fe35 DCB "6C600DAFA9FE35C66C7D1EB0B9",0 .rodata:0001D6C6 ; DATA XREF: .text:000042FCo .rodata:0001D6C6 ; .text:off_43D0o .rodata:0001D6E1 a5ddf101b201582 DCB "5DDF101B20158266",0 ; DATA XREF: sub_44BC+7Eo .rodata:0001D6E1 ; .text:off_4578o .rodata:0001D6F2 a93581a05791938 DCB "93581A05791938828C421C",0 ; DATA XREF: sub_2652+26o .rodata:0001D6F2 ; .text:off_27C4o ... .rodata:0001D709 a13db1940305726 DCB "13DB194030572644",0
根据交叉引用到调用处,发现正是这些16进制串的解密函数,分析之后编写python解密脚本如下
def decrypt(ary, key): str = '' n = 0 for i in range(0, len(ary)): if n == 8: n = 0 str += chr(ary[i] ^ key[n]) n += 1 return str if __name__ == '__main__': ary0 = [0x52, 0x29, 0xC2, 0xBD, 0x64, 0x37, 0xc1, 0x5e, 0x52, 0x67, 0x93, 0xf9, 0x64, 0x30, 0xd0, 0x59, 0x50, 0x26] key0 = [0x31, 0x48, 0xB6, 0x9D, 0x4B, 0x47, 0xB3, 0x31] ary1 = [0x13, 0xB8, 0x2D, 0x68, 0x59, 0x39, 0x52, 0x6D] key1 = [0x61, 0xB8, 0x2D, 0x68, 0x59, 0x39, 0x52, 0x6D] ary2 = [0x14, 0x10, 0xCE, 0xB8, 0x74, 0xA1, 0x3B, 0x50, 0x0B, 0x35, 0x8D] key2 = [0x67, 0x69, 0xBD, 0xE7, 0x11, 0xD1, 0x54, 0x3C] ary3 = [0xD7, 0x9D, 0x76, 0xC5, 0x75, 0xAC, 0x5D, 0xAF, 0xC8, 0xB8, 0x35] key3 = [0xA4, 0xE4, 0x05, 0x9A, 0x10, 0xDC, 0x32, 0xC3] ary4 = [0x71, 0x89, 0x7F, 0xAB, 0x1C, 0x51, 0xCC, 0xD4, 0x75, 0x92, 0x7D, 0x96, 0x4F] key4 = [0x01, 0xFD, 0x0D, 0xCA, 0x7F, 0x34, 0x93, 0xA7] ary5 = [0x30, 0x4B, 0x53, 0xEA, 0xF1, 0x84, 0x22, 0x0D, 0x34, 0x50, 0x51, 0xD7, 0xA2] key5 = [0x40, 0x3F, 0x21, 0x8B, 0x92, 0xE1, 0x7D, 0x7E] ary6 = [0x4B, 0x1D, 0x17, 0x8C, 0x54, 0x2E, 0xF2, 0x9A, 0x10, 0x42, 0x11, 0x80, 0x47] key6 = [0x64, 0x6D, 0x65, 0xE3, 0x37, 0x01, 0x9C, 0xFF] ary7 = [0x22, 0x1D, 0xD6, 0x11, 0x2A, 0x3D, 0x27, 0xF7] key7 = [0x50, 0x1D, 0xD6, 0x11, 0x2A, 0x3D, 0x27, 0xF7] ary8 = [0x97, 0x3C, 0x7E, 0xAA, 0x8C, 0x8B, 0x94, 0xDD, 0x9D, 0x39, 0x0A, 0xA2, 0xFD] key8 = [0xA7, 0x0C, 0x4E, 0x9A, 0xBC, 0xBB, 0xA4, 0xED] ary9 = [0x12, 0xFA, 0x6F, 0xDF, 0x29, 0xF9, 0x6C, 0xD2, 0x12, 0xE7, 0x7C, 0xC0, 0x39] key9 = [0x3D, 0x8A, 0x1D, 0xB0, 0x4A, 0xD6, 0x49, 0xB6] ary10 = [0x6C, 0x60, 0x0D, 0xAF, 0xA9, 0xFE, 0x35, 0xC6, 0x6C, 0x7D, 0x1E, 0xB0, 0xB9] key10 = [0x43, 0x10, 0x7F, 0xC0, 0xCA, 0xD1, 0x10, 0xA2] ary11 = [0x5D, 0xDF, 0x10, 0x1B, 0x20, 0x15, 0x82, 0x66] key11 = [0x2A, 0xBE, 0x79, 0x6F, 0x50, 0x7C, 0xE6, 0x66] ary12 = [0x93, 0x58, 0x1A, 0x05, 0x79, 0x19, 0x38, 0x82, 0x8C, 0x42, 0x1C] key12 = [0xC3, 0x0C, 0x48, 0x44, 0x3A, 0x5C, 0x67, 0xC1] ary13 = [0x13, 0xDB, 0x19, 0x40, 0x30, 0x57, 0x26, 0x44] key13 = [0x61, 0xB8, 0x2D, 0x68, 0x59, 0x39, 0x52, 0x6D] print 'str0:%s' % decrypt(ary0, key0) print 'str1:%s' % decrypt(ary1, key1) print 'str2:%s' % decrypt(ary2, key2) print 'str3:%s' % decrypt(ary3, key3) print 'str4:%s' % decrypt(ary4, key4) print 'str5:%s' % decrypt(ary5, key5) print 'str6:%s' % decrypt(ary6, key6) print 'str7:%s' % decrypt(ary7, key7) print 'str8:%s' % decrypt(ary8, key8) print 'str9:%s' % decrypt(ary9, key9) print 'str10:%s' % decrypt(ary10, key10) print 'str11:%s' % decrypt(ary11, key11) print 'str12:%s' % decrypt(ary12, key12) print 'str13:%s' % decrypt(ary13, key13)
运行结果:
str0:cat /proc/%d/wchan str1:r str2:sys_epoll\0 str3:sys_epoll\0 str4:ptrace_stop\0 str5:ptrace_stop\0 str6:/proc/net/tcp str7:r str8:00000000:5D8A str9:/proc/%d/maps str10:/proc/%d/maps str11:waitpid str12:PTRACE_CONT str13:rc4(int)
看来都是些反调试串,rc4(int)可能是用的算法
回到check, 静态跟流程看,发现一开始有个6的判断,大于6进入到一个死循环,猜测为验证的次数
.text:00002850 CMP R0, #6 //判断是否已经验证6次 .text:00002852 BLT loc_2874 .text:00002854 B sub_286C .text:0000286C sub_286C ; CODE XREF: check(_JNIEnv *,_jclass *,_jstring *)+40j .text:0000286C ; sub_286C+4p .text:0000286C PUSH.W {R4-R10,LR} .text:00002870 BL sub_286C .text:00002874 .text:00002874 loc_2874 ; CODE XREF: check(_JNIEnv *,_jclass *,_jstring *)+3Ej .text:00002874 LDR.W R0, =(aAbcdefghijklmn+0x22) ; "ijklmnopqrstuvwxyz0123456789+/=" .text:00002878 PUSH.W {R4-R10,LR} .text:0000287C POP.W {R4-R10,LR} .text:00002880 B sub_28AA
继续跟流程,发现混淆的模式很固定,跳转时使用固定模式的乱跳,进入函数后对指令进行垃圾代码填充,了解了混淆模式后可以写ida脚本来去混淆,我是直接带着混淆看了
程序会把两个字符串给局部变量,分别是Jyu3CJlVDSGQ 和 Pjpeyjk6mmH=
.text:000028F2 MOVS R0, #'J' .text:000028F4 MOVS R1, #'y' .text:0000295C STRH.W R0, [SP,#0x22] .text:000029C6 STRH.W R1, [SP,#0x24] .text:000029CA MOVS R1, #'u' .text:00002A32 STRH.W R1, [SP,#0x26] .text:00002A36 MOVS R1, #'3' .text:00002A9E STRH.W R1, [SP,#0x28] .text:00002AA2 MOVS R1, #'C' .text:00002B0A STRH.W R1, [SP,#0x2A] .text:00002B74 MOVS R1, #0 .text:00002B76 STRH.W R0, [SP,#0x2C] .text:00002B7A MOVS R0, #'l' .text:00002BE2 STRH.W R0, [SP,#0x2E] .text:00002BE6 MOVS R0, #'V' .text:00002C4E STRH.W R0, [SP,#0x30] .text:00002C52 MOVS R0, #'D' .text:00002CBA STRH.W R0, [SP,#0x32] .text:00002CBE MOVS R0, #'S' .text:00002D26 STRH.W R0, [SP,#0x34] .text:00002D2A MOVS R0, #'G' .text:00002D92 STRH.W R0, [SP,#0x36] .text:00002D96 MOVS R0, #'Q' .text:00002DFE STRH.W R0, [SP,#0x38] ;=============================================================== .text:00001A30 MOVS R0, #'P' .text:00001A32 MOVS R1, #'p' .text:00001A34 STRH.W R0, [SP,#0x22] .text:00001A38 MOVS R0, #'j' .text:00001AA0 STRH.W R0, [SP,#0x24] .text:00001B0A MOVS R3, #0 .text:00001B0C STRH.W R1, [SP,#0x26] .text:00001B10 MOVS R1, #'e' .text:00001B78 STRH.W R1, [SP,#0x28] .text:00001B7C MOVS R1, #'y' .text:00001BE4 STRH.W R1, [SP,#0x2A] .text:00001C4E MOVW R1, #0xFFFF .text:00001C52 STRH.W R0, [SP,#0x2C] .text:00001C56 MOVS R0, #'k' .text:00001C58 LDR.W R12, =unk_1E216 .text:00001CC2 MOVS R2, #0 .text:00001CC4 STRH.W R0, [SP,#0x2E] .text:00001CC8 MOVS R0, #'6' .text:00001D30 STRH.W R0, [SP,#0x30] .text:00001D34 MOVS R0, #'m' .text:00001D9C STRH.W R0, [SP,#0x32] .text:00001E06 ADD R12, PC .text:00001E08 STRH.W R0, [SP,#0x34] .text:00001E0C MOVS R0, #'H' .text:00001E74 STRH.W R0, [SP,#0x36] .text:00001E78 MOVS R0, #'=' .text:00001EE0 STRH.W R0, [SP,#0x38]
其他是一些计算逻辑,静态看着太费力,上动态调试,直接附加,并没有遇到反调试,再次跟流程 (以下so基址均为0xB3998000)
发现这两个字符串最终被穿插拼接成一个 JPyjup3eCyJjlkV6DmSmGHQ=,使用base64解密没有明文。继续跟
.text:B399B6B4 LDR.W R0, [R9] .text:B399B6B8 MOV R1, R8 .text:B399B6BA MOVS R2, #0 .text:B399B6BC MOVS R4, #0 .text:B399B6BE LDR.W R3, [R0,#0x2A4] .text:B399B6C2 MOV R0, R9 .text:B399B6C4 BLX R3 //获取输入的字符串 ... ... ... .text:B39B1DA8 PUSH.W {R4-R9,LR} .text:B39B1DAC ADD R7, SP, #0xC .text:B39B1DAE SUB.W SP, SP, #0x408 .text:B39B1DB2 SUB SP, SP, #4 .text:B39B1DB4 MOV R9, R0 .text:B39B1DB6 LDR.W R0, =(__stack_chk_guard_ptr - 0xB39B1DBE) .text:B39B1DBA ADD R0, PC ; __stack_chk_guard_ptr .text:B39B1DBC LDR R0, [R0] ; __stack_chk_guard .text:B39B1DBE LDR R0, [R0] .text:B39B1DC0 STR.W R0, [R7,#var_10] .text:B39B1DC4 BL sub_B39B231C //获取199310124853! .text:B39B1DC8 MOV R8, R0 ... ... ... .text:B39B1EE4 loc_B39B1EE4 //循环,计算输入字符串长度 .text:B39B1EE4 LDRB.W R1, [R0],#1 .text:B39B1EE8 CMP R1, #0 .text:B39B1EEA BNE loc_B39B1EE4 .text:B39B1EEC MVN.W R1, R9 .text:B39B1EF0 ADDS R6, R0, R1 .text:B39B1EF2 ADDS R4, R6, #1 ... .text:B39B1F80 MOV R0, R4 ; size //根据输入字符串长度分配空间 .text:B39B1F82 BLX malloc .text:B39B1F86 MOV R1, R4 .text:B39B1F88 MOV R5, R0 .text:B39B1FF0 BLX __aeabi_memclr .text:B39B1FF4 MOV R4, SP .text:B39B1FF6 MOVS R1, #8 //rc4密钥长度 .text:B39B1FF8 MOV R0, R4 .text:B39B1FFA MOV R2, R8 //R8: 199310124853! .text:B39B20C8 BL sub_B399D5E4 //创建一个[0-FF]的数组,并且根据19931012循环打乱
看到这里,又想到一开始的rc4(int),猜测是rc4加密后转的base64,直接用python解密,脚本如下
from Crypto.Cipher import ARC4 import base64 if __name__ == '__main__': key = '19931012' cipher = ARC4.new(key) print cipher.decrypt(base64.b64decode('JPyjup3eCyJjlkV6DmSmGHQ='))
解出 sn为 madebyericky94528
[培训]《安卓高级研修班(网课)》月薪三万计划,掌握调试、分析还原ollvm、vmp的方法,定制art虚拟机自动化脱壳的方法
赞赏
他的文章
看原图