-
-
[原创] 看雪.京东 2018CTF 第六题 PWN-noheap writeup
-
发表于: 2018-6-27 21:13 3410
-
1.先检查一下

所有保护全开。
2.反调试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int sub_E87() { puts ( " _____ _____ _____ _ __ __ _____ _____ ___ _____ \n" "| _ \\ | ____| | _ \\ | | \\ \\ / / /___ \\ / _ \\ |_ | / _ \\ \n" "| |_| | | |__ | | | | | | \\ \\/ / ___| | | | | | | | | |_| | \n" "| ___/ | __| | | | | | | \\ / / ___/ | |/| | | | } _ { \n" "| | | |___ | |_| | | | / / | |___ | |_| | | | | |_| | \n" "|_| |_____| |_____/ |_| /_/ |_____| \\_____/ |_| \\_____/ \n" ); setvbuf (stdout, 0LL, 2, 0LL); setvbuf (stdin, 0LL, 2, 0LL); alarm(0x3Cu); //这个影响调试 直接用90全patch掉 puts ( "Welcome !" ); return puts ( "=======================================================================" ); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int sub_E87() { puts ( " _____ _____ _____ _ __ __ _____ _____ ___ _____ \n" "| _ \\ | ____| | _ \\ | | \\ \\ / / /___ \\ / _ \\ |_ | / _ \\ \n" "| |_| | | |__ | | | | | | \\ \\/ / ___| | | | | | | | | |_| | \n" "| ___/ | __| | | | | | | \\ / / ___/ | |/| | | | } _ { \n" "| | | |___ | |_| | | | / / | |___ | |_| | | | | |_| | \n" "|_| |_____| |_____/ |_| /_/ |_____| \\_____/ |_| \\_____/ \n" ); setvbuf (stdout, 0LL, 2, 0LL); setvbuf (stdin, 0LL, 2, 0LL); alarm(0x3Cu); //这个影响调试 直接用90全patch掉 puts ( "Welcome !" ); return puts ( "=======================================================================" ); } |
3.第一关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | unsigned __int64 sub_F0C() { unsigned __int8 buf; // [rsp+Fh] [rbp-51h] unsigned int v2; // [rsp+10h] [rbp-50h] int i; // [rsp+14h] [rbp-4Ch] int fd; // [rsp+18h] [rbp-48h] unsigned int v5; // [rsp+1Ch] [rbp-44h] char s1[8]; // [rsp+20h] [rbp-40h] char v7; // [rsp+28h] [rbp-38h] char s2[8]; // [rsp+30h] [rbp-30h] char v9; // [rsp+38h] [rbp-28h] unsigned __int64 v10; // [rsp+48h] [rbp-18h] v10 = __readfsqword(0x28u); *(_QWORD *)s1 = 0LL; v7 = 0; *(_QWORD *)s2 = 0LL; v9 = 0; v2 = 0; fd = open( "/dev/urandom" , 0); for ( i = 0; i <= 3; ++i ) { read(fd, &buf, 1uLL); v2 = (v2 << 8) + buf % 0x2Bu + 48; } *(_DWORD *)s1 = sub_108A(&v2); *(_DWORD *)&s1[4] = sub_108A(&v2); v5 = sub_10B2(s1, 8LL); printf ( "Hash:%08x\n" , v5); printf ( "Input:" ); sub_14F0(s2, 9LL); close(fd); if ( strcmp (s1, s2) ) exit (0); puts ( "=======================================================================" ); return __readfsqword(0x28u) ^ v10; } |
从/dev/urandom 循环读取四字节,通过一定运算组成一个DWORD数值v2.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | unsigned __int64 sub_F0C() { unsigned __int8 buf; // [rsp+Fh] [rbp-51h] unsigned int v2; // [rsp+10h] [rbp-50h] int i; // [rsp+14h] [rbp-4Ch] int fd; // [rsp+18h] [rbp-48h] unsigned int v5; // [rsp+1Ch] [rbp-44h] char s1[8]; // [rsp+20h] [rbp-40h] char v7; // [rsp+28h] [rbp-38h] char s2[8]; // [rsp+30h] [rbp-30h] char v9; // [rsp+38h] [rbp-28h] unsigned __int64 v10; // [rsp+48h] [rbp-18h] v10 = __readfsqword(0x28u); *(_QWORD *)s1 = 0LL; v7 = 0; *(_QWORD *)s2 = 0LL; v9 = 0; v2 = 0; fd = open( "/dev/urandom" , 0); for ( i = 0; i <= 3; ++i ) { read(fd, &buf, 1uLL); v2 = (v2 << 8) + buf % 0x2Bu + 48; } *(_DWORD *)s1 = sub_108A(&v2); *(_DWORD *)&s1[4] = sub_108A(&v2); v5 = sub_10B2(s1, 8LL); printf ( "Hash:%08x\n" , v5); printf ( "Input:" ); sub_14F0(s2, 9LL); close(fd); if ( strcmp (s1, s2) ) exit (0); puts ( "=======================================================================" ); return __readfsqword(0x28u) ^ v10; } |
从/dev/urandom 循环读取四字节,通过一定运算组成一个DWORD数值v2.
通过函数
1 2 3 4 5 | __int64 __fastcall sub_108A(unsigned int *a1) { *a1 = 214013 * *a1 + 2531011; return *a1; } |
构造一个8个字节的字符串。
1 2 3 4 5 | __int64 __fastcall sub_108A(unsigned int *a1) { *a1 = 214013 * *a1 + 2531011; return *a1; } |
构造一个8个字节的字符串。
IDA反编译的有点问题,其实第二次调用sub_108A时传入的事第一次调用的返回值。
接着利用sub_10b2计算一个hash,并输出出来。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | __int64 __fastcall sub_10B2(unsigned __int8 *a1, int a2) { unsigned __int8 *v2; // rax int v4; // [rsp+0h] [rbp-1Ch] unsigned __int8 *v5; // [rsp+4h] [rbp-18h] unsigned int v6; // [rsp+14h] [rbp-8h] v5 = a1; v4 = a2; v6 = 0; do { v2 = v5++; v6 = 131 * v6 + *v2; --v4; } while ( v4 > 0 ); return v6; } |
再接着要求我们输入一个8字节的字符串,要求跟上边的s1一致。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | __int64 __fastcall sub_10B2(unsigned __int8 *a1, int a2) { unsigned __int8 *v2; // rax int v4; // [rsp+0h] [rbp-1Ch] unsigned __int8 *v5; // [rsp+4h] [rbp-18h] unsigned int v6; // [rsp+14h] [rbp-8h] v5 = a1; v4 = a2; v6 = 0; do { v2 = v5++; v6 = 131 * v6 + *v2; --v4; } while ( v4 > 0 ); return v6; } |
再接着要求我们输入一个8字节的字符串,要求跟上边的s1一致。
这关只能爆破。
从v2的生成代码(v2 = (v2 << 8) + buf % 0x2Bu + 48;)可以看出,v2的每个字节都介于[48+0,48+0x2B)之间。
利用此特性,可获取到hash值之后,爆破出v2的值,进而获取到s1字符串。
爆破代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | import struct import binascii import time def time_me(fn): def _wrapper(*args, **kwargs): start = time . clock () ret=fn(*args, **kwargs) print "%s cost %s second" %(fn.__name__, time . clock () - start) return ret return _wrapper def calc_1(v2): return (214013*v2+2531011)&0xffffffff def mk_string(v2): v2_ret=calc_1(v2) #print hex(v2_ret) s= "" s+= struct .pack( "I" ,v2_ret) v2_ret=calc_1(v2_ret) #print hex(v2_ret) s+= struct .pack( "I" ,v2_ret) #print binascii.b2a_hex(s) return s def calc_2(v2_str): #print "fuck" v6=0 for x in v2_str: v6=(0x83*v6+ord(x))&0xffffffff #print hex(v6) #print hex(v6) return v6 def calc(v2): v2_str=mk_string(v2) v2_ret=calc_2(v2_str) return v2_ret,v2_str #print calc(0x5a314f44) @time_me def Crack(in_value): ret_str= "" for i1 in range(0,0x2b): for i2 in range(0,0x2b): for i3 in range(0,0x2b): for i4 in range(0,0x2b): v2=((i1+0x30)<<24)+((i2+0x30)<<16)+((i3+0x30)<<8)+((i4+0x30)) #print hex(v2) v2_ret,v2_str=calc(v2) #print hex(i),v2_ret,v2_str if v2_ret==in_value: print v2_str,binascii.b2a_hex(v2_str) ret_str=v2_str break return ret_str |
此算法爆破一个值大约需要7-10s.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | import struct import binascii import time def time_me(fn): def _wrapper(*args, **kwargs): start = time . clock () ret=fn(*args, **kwargs) print "%s cost %s second" %(fn.__name__, time . clock () - start) return ret return _wrapper def calc_1(v2): return (214013*v2+2531011)&0xffffffff def mk_string(v2): v2_ret=calc_1(v2) #print hex(v2_ret) s= "" s+= struct .pack( "I" ,v2_ret) v2_ret=calc_1(v2_ret) #print hex(v2_ret) s+= struct .pack( "I" ,v2_ret) #print binascii.b2a_hex(s) return s def calc_2(v2_str): #print "fuck" v6=0 for x in v2_str: v6=(0x83*v6+ord(x))&0xffffffff #print hex(v6) #print hex(v6) return v6 def calc(v2): v2_str=mk_string(v2) v2_ret=calc_2(v2_str) return v2_ret,v2_str #print calc(0x5a314f44) @time_me def Crack(in_value): ret_str= "" for i1 in range(0,0x2b): for i2 in range(0,0x2b): for i3 in range(0,0x2b): for i4 in range(0,0x2b): v2=((i1+0x30)<<24)+((i2+0x30)<<16)+((i3+0x30)<<8)+((i4+0x30)) #print hex(v2) v2_ret,v2_str=calc(v2) #print hex(i),v2_ret,v2_str if v2_ret==in_value: print v2_str,binascii.b2a_hex(v2_str) ret_str=v2_str break return ret_str |
此算法爆破一个值大约需要7-10s.
4.第二关
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | __int64 sub_1470() { __int64 result; // rax __int64 v1; // ST38_8 __int64 v2; // ST90_8 unsigned int v3; // [rsp+74h] [rbp-4h] Menu_1591(); v3 = read_1547(); result = v3; if ( v3 ) { result = v3; if ( v3 <= 3 ) { v1 = qword_2030C0[~((unsigned __int8 )v3 - 1LL) - 7] ^ qword_2030C0[~((unsigned __int8 )v3 - 1LL) - 2]; v2 = qword_2030C0[-11] ^ qword_2030C0[-6]; JUMPOUT(__CS__, qword_2030C0[-7] ^ qword_2030C0[-12]); } } return result; } |
显示菜单并读取出来用户输入选项之后就会进入虚拟机。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | __int64 sub_1470() { __int64 result; // rax __int64 v1; // ST38_8 __int64 v2; // ST90_8 unsigned int v3; // [rsp+74h] [rbp-4h] Menu_1591(); v3 = read_1547(); result = v3; if ( v3 ) { result = v3; if ( v3 <= 3 ) { v1 = qword_2030C0[~((unsigned __int8 )v3 - 1LL) - 7] ^ qword_2030C0[~((unsigned __int8 )v3 - 1LL) - 2]; v2 = qword_2030C0[-11] ^ qword_2030C0[-6]; JUMPOUT(__CS__, qword_2030C0[-7] ^ qword_2030C0[-12]); } } return result; } |
显示菜单并读取出来用户输入选项之后就会进入虚拟机。
虚拟机先不说,先说三个选项。

1.Malloc函数

主要的漏洞就在这个函数。
2.show

3.free

看完这三个函数,刚开始没有发现读取数据时会覆盖vm_code,还以为是house_of_force。https://ctf-wiki.github.io/ctf-wiki/pwn/heap/house_of_force/
但是此题中qword_203240处只能保存最后一次申请的空间,没办法形成此利用提交。
郁闷了好久想起来了之前的比赛,此题出题思路应该是跟 看雪.Wifi万能钥匙 2017CTF年中赛 第九题 Silence Server
https://ctf.pediy.com/game-fight-39.htm
是一样的似乎,要利用虚拟机进行操作。
虚拟机代码:
最后于 2018-6-27 21:15
被lacoucou编辑
,原因:
赞赏
他的文章
赞赏
雪币:
留言: