-
-
[原创] 看雪.京东 2018CTF 第六题 PWN-noheap writeup
-
发表于: 2018-6-27 21:13 3302
-
1.先检查一下
所有保护全开。
2.反调试
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("======================================================================="); }
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.第一关
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.
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.
通过函数
__int64 __fastcall sub_108A(unsigned int *a1) { *a1 = 214013 * *a1 + 2531011; return *a1; }
构造一个8个字节的字符串。
__int64 __fastcall sub_108A(unsigned int *a1) { *a1 = 214013 * *a1 + 2531011; return *a1; }
构造一个8个字节的字符串。
IDA反编译的有点问题,其实第二次调用sub_108A时传入的事第一次调用的返回值。
接着利用sub_10b2计算一个hash,并输出出来。
__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一致。
__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字符串。
爆破代码如下:
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.
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.第二关
__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; }
显示菜单并读取出来用户输入选项之后就会进入虚拟机。
__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编辑
,原因:
赞赏
他的文章
看原图
赞赏
雪币:
留言: