-
-
[原创]CTF2017第6题分析
-
2017-6-13 10:53 4255
-
之前看这题的要求是要android 5.1以上系统,手上没这种手机,PC跑arm模拟器又慢,准备放弃的,后来发现竟然能在我的华为3C (android 4.2)上运行。
1. 静态分析
用ApkIDE反编译,直接浏览MainActivity.smali,流程很清晰。
invoke-static {v0}, Lcom/miss/rfchen/utils;->check(Ljava/lang/String;)Z
整个验证逻辑就是调用so中的check完成的.
把lib中的librf-chen.so拖到IDA中,可以直接索引check函数
简单浏览了下,发现一些字符串相关的操作,估计跟逻辑有关.
.text:0000295C STRH.W R0, [SP,#arg_22]
.text:00002960 PUSH.W {R4-R10,LR}
.text:000029C6 STRH.W R1, [SP,#arg_24]
.text:000029CA MOVS R1, #0x75
.text:00002A32 STRH.W R1, [SP,#arg_26]
.text:00002A36 MOVS R1, #0x33
.text:00002A9A SUB.W R1, R1, #1
.text:00002A9E STRH.W R1, [SP,#arg_28]
.text:00002AA2 MOVS R1, #0x43
.text:00002B0A STRH.W R1, [SP,#arg_2A]
.text:00002B0E PUSH.W {R4-R10,LR}
后面代码混淆比较多,准备动态调试了.
这里本来想修改这里的跳转逻辑验证下的,发现只要smali文件有修改,就打包不回去了,看到android.support下有个包名是符号,估计跟这个有关,具体不了解,考虑到时间关系,没继续研究这个了,后面有空了再看看.
改smali不行,但是修改AndroidManifest.xml可以,直接加上android:debuggable="true",当然手上有修改参数自己刷过机的调试环境的,可以不用这步.
2. 动态调试
先挂起app (具体的调试步骤,论坛网上都有很详细的介绍):
adb shell am start -D -n com.miss.rfchen/.MainActivity
然后使用IDA附加,跟踪check函数,相比PC,这个又带混淆,调试手段还是少很多,感觉就是单步,然后配合静态IDA看流程,能跳过的函数先跳过,先弄清楚大致流程.
首先是前面拼接了一个字符串JPyjup3eCyJjlkV6DmSmGHQ=,先记录下来。
base: 776A4000
librf_chen.so:776A76C4 BLX R3
librf_chen.so:776A76C6 PUSH.W {R4-R10,LR}
librf_chen.so:776A76CA POP.W {R4-R10,LR}
librf_chen.so:776A76CE B unk_776A76F8 这里r0:指向输入的字符串
后面可以逐步看到对输入字符串的处理,到下面函数,通过看逻辑发现就是base64,
.text:00005AFC my_base64 ; CODE XREF: sub_1A188+9Ep
.text:00005AFC PUSH.W {R4-R10,LR}
.text:00005B00 ADD R7, SP, #0xC
.text:00005B02 MOV R9, R0
.text:00005B04 MOVW R0, #0x5556
.text:00005B08 MOV R10, R1
.text:00005B0A MOVT.W R0, #0x5555
.text:00005B0E SMMUL.W R0, R10, R0
.text:00005B12 ADD.W R0, R0, R0,LSR#31
.text:00005B16 ADD.W R1, R0, R0,LSL#1
.text:00005B1A SUB.W R1, R10, R1
.text:00005B1E CMP R1, #0
然后就是结果和之前的JPyjup3eCyJjlkV6DmSmGHQ=这个串比较.
之前为了快速弄清楚流程,有的函数跳过了,到base64这,发现输入字符串不是app上输入的原始字符串,看来是之前又有变换操作,不过这里发现长度和原始串是一致的.
后面就单独跟踪了base64之前的操作,大概就是查表,
.text:000047EA ADD R10, R4
.text:000047EC UXTB.W R7, R10
.text:000047F0 LDR.W R8, [R6,R7,LSL#2] ; r6: 0x100个int的表,查表操作
.text:000047F4 STR.W R8, [R6,R5,LSL#2]
.text:000047F8 STR.W R4, [R6,R7,LSL#2]
.text:000047FC ADD R4, R8
.text:000047FE UXTB R4, R4
.text:00004800 LDRB.W R5, [R11] ; 读取app中的输入串,单个字符
.text:00004804 LDR.W R4, [R6,R4,LSL#2]
.text:00004808 EOR.W R4, R4, R5
.text:0000480C STRB R4, [R3]
这里涉及到混淆,后来又跟踪了后续处理,基本确定是rc4了,然后就是回溯表的生成,发现8字节的输入key:
70 D6 CB 74 40 B2 A8 72
base: 77461000
librf_chen.so:7747B0BC POP.W {R0,R4,R5,R7,LR}
librf_chen.so:7747B0C0 ADD.W R1, R1, #1
librf_chen.so:7747B0C4 SUB.W R1, R1, #1
librf_chen.so:7747B0C8 BL unk_774665E4 生成rc4表
librf_chen.so:7747B0CC MOV R0, R4
librf_chen.so:7747B0CE MOV R1, R6
librf_chen.so:7747B0D0 MOV R2, R9
不过这里遇到个问题,我用程序生成的表和这个不同,已经int转byte后的,考虑比较晚了,第二天还上班,没跟踪具体逻辑了,直接把表数据扣出来了。
整体流程就是:
对输入串rc4,然后base64,再和目标串JPyjup3eCyJjlkV6DmSmGHQ=比较。
那解密流程就是反过来了:
先对JPyjup3eCyJjlkV6DmSmGHQ= base64解码,然后rc4解密.
3. 程序解密实现
先对JPyjup3eCyJjlkV6DmSmGHQ= base64解码(可以用工具,我这里是node.js):
var str2 = new Buffer('JPyjup3eCyJjlkV6DmSmGHQ=', 'base64').toString('hex');
console.log(str2);
输出:
24fca3ba9dde0b226396457a0e64a61874
这个就是rc4加密后的结果了,现在对这个rc4解密.
unsigned char key_[0x100]=
{
0xB7,0x6B,
0xA6,0xDC,0x11,0x5B, 0x05,0x5A,0x78,0x00,
0x74,0xB2,0x43,0x30, 0x01,0xBE,0xED,0x26,
0x09,0xC7,0x31,0x45, 0xCE,0xCF,0x91,0x70,
0xC6,0xB1,0x5E,0x61, 0x55,0x15,0xBD,0x1A,
0x4B,0xC5,0xF6,0x22, 0xA2,0x72,0xFF,0x42,
0xF3,0x6A,0x8F,0x87, 0x02,0xDF,0x6F,0xB0,
0xBA,0xA4,0x85,0xEA, 0x8E,0x32,0xA1,0x2D,
0xEF,0xAA,0xE3,0xA3, 0xBF,0x2C,0xE0,0x47,
0x3D,0x08,0xB9,0xCC, 0x7D,0x7F,0x6D,0x7A,
0x12,0x1C,0xDB,0x94, 0xFC,0xC0,0xAF,0x0C,
0x62,0x14,0x59,0x24, 0xE8,0x4C,0xCB,0xCD,
0x60,0x39,0xC9,0x3A, 0x98,0x77,0xE2,0xAB,
0xB8,0xD7,0x3C,0x69, 0x53,0x1F,0xCA,0x88,
0x51,0x4D,0x8A,0x36, 0xE7,0xAE,0xD2,0xD1,
0xFB,0x8B,0x5D,0x0D, 0xA8,0x8D,0x0A,0x10,
0x82,0xD4,0x0E,0x2F, 0x57,0x5C,0x20,0x3E,
0xD0,0xB6,0xBC,0x33, 0x4E,0x90,0x17,0xF2,
0xB5,0x03,0x79,0x99, 0x2B,0xFD,0xAD,0xA5,
0x04,0x16,0x40,0x3B, 0x07,0x9E,0xDD,0x48,
0x2E,0x25,0x27,0xA7, 0x89,0xF9,0x95,0x68,
0x1B,0x6E,0x84,0xF7, 0xC4,0xC8,0x71,0x6C,
0x86,0x29,0xB3,0xEC, 0x92,0x80,0x4F,0x41,
0xB4,0xD8,0x58,0x1D, 0xD6,0x44,0x49,0x81,
0x3F,0x67,0x96,0x35, 0xAC,0x21,0xE9,0x64,
0x56,0x19,0x9B,0xD9, 0xC3,0xBB,0x4A,0xD3,
0xE6,0xE4,0x9D,0xD5, 0x8C,0x06,0x83,0x7C,
0x9C,0xC1,0xFA,0x93, 0x97,0x76,0xDA,0x38,
0x73,0x18,0x28,0x13, 0xF4,0x0F,0x23,0x52,
0x9A,0x7B,0xA9,0xF8, 0x9F,0x50,0x65,0xF1,
0x34,0xEE,0xA0,0x37, 0x46,0xE5,0x5F,0x75,
0x7E,0xC2,0xEB,0x66, 0xDE,0xFE,0xF5,0x0B,
0x2A,0xF0,0x63,0xE1, 0x1E,0x54
};
void RC4Transform(char* output, const char* input, int len)
{
int i = 0, j = 0;
for (int k = 0; k < len; k++)
{
i = (i + 1) % 256;
j = (j + key_[i]) % 256;
swap(key_[i], key_[j]);
unsigned char subkey = key_[(key_[i] + key_[j]) % 256];
output[k] = subkey ^ input[k];
}
}
int TestNo6()
{
char cOut[0x50] = { 0 };
char cIn[] = { 0x24,0xfc,0xa3,0xba,0x9d,0xde,0x0b,0x22,0x63,0x96,0x45,0x7a,0x0e,0x64,0xa6,0x18,0x74 };
RC4Transform(cOut, cIn, sizeof(cIn));
OutPutMessage2(0, cOut);
return 0;
}
运行结果:
madebyericky94528
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。