MiniL-Reverse部分题解

MiniL-Reverse 部分题解

总体而言题目不错,就是后面的混淆写着感觉和吃屎一样(

bigbanana

52MB 的附件里有 53MB 的 bigbanana 视频,我很难评(
打开一看一个巨大 switch,鉴定为 vm,拷 opcode 出来后一个一个看是什么功能
特别注意 opcode 是在 -1 后进入 switch 进行运行的

功能表如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
0   add v8 $0
15 push getchar()
16 print %c v7
239 v7 = v8
240 v10 = v8
241 cmp v7 $0
242 xor v7 v8
243 add v7 $0
244 sub $0 $1
245 push $0
246 pop v7
247 pop v8
248 pop v9
249 pop v10
253 !eq 大香蕉启动!
254 jeq $0 + 2

然后根据此功能表写出汇编器

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
void trans() {
int *pc = instruc;
int v7, v8, v9, v10, eq;
stack<int> stack;
NEXT:
while (*pc) {
switch (*pc-1) {
case 0:
printf("v8 += %d;\n", *(pc + 1));
pc += 2;
break;
case 15:
puts("stack.push(getchar());");
pc++;
break;
case 16:
puts("printf(\"%c\", v7);");
pc++;
break;
case 239:
puts("v7 = v8;");
pc++;
break;
case 240:
puts("v10 = v8;");
pc++;
break;
case 241:
printf("eq = v7 == %d;\n", *(pc + 1));
pc += 2;
break;
case 242:
puts("v7 ^= v8;");
pc++;
break;
case 243:
printf("v7 += %d;\n", *(pc + 1));
pc += 2;
break;
case 244:
printf("*(pc + 1) -= %d;\n", *(pc + 2));
*(pc + 1) -= *(pc + 2);
pc += 3;
break;
case 245:
printf("stack.push(%d);\n", *(pc + 1));
pc += 2;
break;
case 246:
puts("v7 = stack.top();");
puts("stack.pop();");
pc++;
break;
case 247:
puts("v8 = stack.top();");
puts("stack.pop();");
pc++;
break;
case 248:
puts("v9 = stack.top();");
puts("stack.pop();");
pc++;
break;
case 249:
puts("v10 = stack.top();");
puts("stack.pop();");
pc++;
break;
case 253:
puts("if(!eq)printf(\"wrong\");");
pc+=2;
break;
case 254:
puts("if (eq) {");
printf(" pc += %d;\n", *(pc + 1) + 2);
puts("}");
pc += *(pc + 1) + 2;
break;
default:
goto NEXT;
break;
}
}
}

得到 vm 使用的汇编代码,实在是太长了就不放了
之后可以发现后续比对的一些特征

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
stack.push(getchar());
stack.push(getchar());
v8 = stack.top();
stack.pop();
v7 = stack.top();
stack.pop();
v7 += 1766746445;
v8 += 1952656716;
v7 += 0;
v7 ^= v8;
eq = v7 == 489505807;
if(!eq)printf("wrong");
v7 = v8;
stack.push(getchar());
v8 = stack.top();
stack.pop();
v7 += 22;
v8 += 33;
v7 += 1131796;
v7 ^= v8;
eq = v7 == 1953788496;
if(!eq)printf("wrong");

先行读入 2 字符,加上某数字后异或后比对,之后就是读一个字符,简单加法,然后异或比较,只需要爆破一开始的 2 字符,之后的数据也可以爆破得到(其实是可以直接异或算出来的,但是懒得去想了,反正能爆)。数据提取问题,不难发现这些使用立即数以 4 个为一组,在汇编器针对此特征只在 opcode-1 为{0, 241, 243}时打印数字,即可提出其中用于爆破的数据。爆破脚本如下:

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
int main() {
int v7, v8;
v7 = 1766746445;
v8 = 1952656716;
queue<int> start_v7;
queue<char> start;
for (int i = 33; i < 127; ++i) {
for (int j = 33; j < 127; ++j) {
if (((i + v7) ^ (j + v8)) == 489505807) {
start_v7.push(v8 + j);
start.push(i);
start.push(j);
}
}
}
while (!start_v7.empty()) {
v7 = start_v7.front();
start_v7.pop();
char a = start.front();
start.pop();
char b = start.front();
start.pop();
int *curr = mem; // mem 为提取出的数据

bool first = true;
while (*curr) {
v7 += curr[0];
v8 = curr[1];
v7 += curr[2];
for (int i = 33; i < 127; ++i) {
if ((v7 ^ (v8 + i)) == curr[3]) {
if (first) {
printf("%c%c", a, b);
first = false;
}
cout << (char)i;
v7 = v8 + i;
goto FIND;
}
}
printf(" Not Found\n");
break;
FIND:
curr += 4;
if(!(*curr)) {
putchar('\n');
}
}
}
}

得到最终 flag
不管怎么说还是很标准的 vm 题

longlongcall

一吨的 call 混淆,使我的 ida 去世
minil-longlongcall1
观察后不难发现这群 call 混淆机器码全都是 E8 02 00 00 00 C9 C3,编写 idapython 脚本去了
顺便把那些没啥用的 pushfq 和 popfq 去了
脚本如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def pattern_nop(start_addr, end_addr, pattern):
cur_addr = start_addr

while cur_addr < end_addr:
cur_addr = idc.find_binary(cur_addr, SEARCH_DOWN, pattern)
print("patch addr: " + str(hex(cur_addr)))
if cur_addr == idc.BADADDR:
break
else:
my_nop(cur_addr, cur_addr + pattern.count(" ") + 1)

pattern = "E8 02 00 00 00 C9 C3"
cur_addr = 0x11D9
end_addr = 0x1E35
pattern_nop(cur_addr, end_addr, pattern)
pattern = "48 83 C4 08 9D"
pattern_nop(cur_addr, end_addr, pattern)
pattern = "9C 90 90"

然后 apply patch 后重新让 ida 分析(不然要 undefine 一堆函数,懒懒懒懒懒)
发现是一个异常简答的异或,直接写脚本解了

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
#include <bits/stdc++.h>

using namespace std;

char enc[44] = {
-69, -65, -71, -66, -61, -52, -50, -36,
-98, -113, -99, -101, -89, -116, -41, -107,
-80, -83, -67, -76, -120, -81, -110, -48,
-49, -95, -93, -110, -73, -76, -55, -98,
-108, -89, -82, -16, -95, -103, -64, -29,
-76, -76, -65, -29
};



int main() {
for (int i = 0; i <= 43; i+=2) {
for (char a = 33; a < 127; a++) {
for (char b = 33; b < 127; b++) {
char v2 = a + b;
if ((enc[i] ^ v2) == a && (enc[i + 1] ^ v2) == b) {
cout << a << b;
}
}
}
}
}

得到 flag

myrust

rust+安卓,狠狠地踩在我的雷点上
安卓用 jadx 打开一看一个就调用了一个 invokeCheck 检查输入的 flag,但是还 loadLibrary(“myrust”),直接解压 apk 拿到 myrust.so
从字符串表里找到 so 中对应的函数名并找到对应函数。在 mz1 师傅的帮助下发现他加载了 CryptoClass 类,遂回到安卓源代码中,果然找到这个类
里面就是简单 AES 比较后返回结果,但是 AES 直接解码后是乱码不是 flag
mz1 师傅直接使用调试机 hook 找到了 AES 的 key 与输入进行的一次倒序凯撒,byebye 大爹
实际上在 rust 中也能找到着这两部分(事后诸葛亮)

minil-myrust

由于传入类的是一个字符串数组,可以通过 rust 中调用的构造字符串与给数组赋值确定相关逻辑位置。第一个箭头是将字符串” oa0-eikddi1@ecsa “载入,第二、三个箭头指向的函数大致是将字符串反转后给字符串中的每个字符+1。因此 AES 的 key 应该是上面的字符串反转后每个字符 -1 即”btdfA2jeeljf.1bp”,之后 CryptoClass 类中的数据拿出来按这个密钥解密后,再反转每个字符-1即可获取 flag。

olessvm

满地都是混淆,mz1大爹在经过不懈地动态调试后成功找到了主要的加密逻辑部分,之后靠动态调试直接提出数组的数值
minil-olessvm1
解密得到 flag
脚本如下:

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
#include <bits/stdc++.h>

using namespace std;

unsigned char enc[] =
{
0xFC, 0xF1, 0x2D, 0x11, 0x31, 0xC7, 0x19, 0x8A, 0xDA, 0xBC,
0x14, 0x7C, 0x98, 0xEA, 0xDB, 0x65, 0xF7, 0x29, 0xD0, 0x43,
0x48, 0xFC, 0x84, 0x28, 0xF9, 0x29, 0x23, 0xAC, 0x59, 0xCD,
0x51, 0xE0, 0xC2, 0xB8, 0xF7, 0x59, 0x0C, 0x4B, 0xE6, 0x10,
0xDD, 0x59, 0xCC, 0x6D, 0x2B, 0x2A, 0x8C, 0xDA, 0x7B, 0x08,
0x08, 0xA4, 0x06, 0xBB, 0x86, 0xD0, 0x83, 0xD1, 0xFD, 0xE9,
0x8B, 0x35, 0x45, 0x5D, 0x51, 0x4C, 0xD1, 0x72, 0xF6, 0xB8,
0xE6, 0x9E, 0xE2, 0xB7, 0x2D, 0x75, 0x25, 0x71, 0x2B, 0x4B,
0x86, 0x45, 0x87, 0xA1, 0xC9, 0x47, 0xC5, 0x5A, 0x16, 0x5E,
0x1A, 0xD1, 0x17, 0x9D, 0x18, 0x6E, 0x3F, 0xD2, 0x75, 0xE9,
0xE3, 0x51, 0x56, 0xC2, 0x06, 0x04, 0x6D, 0x1A, 0x50, 0x65,
0x7D, 0xFD, 0xA9, 0x12
};

unsigned char key[] =
{
0x91, 0x99, 0x41, 0x7B, 0x79, 0x81, 0x4B, 0xCB, 0xA9, 0xEC,
0x2E, 0x02, 0xCB, 0x94, 0xE5, 0x26, 0x91, 0x0B, 0xA6, 0x0F,
0x28, 0x81, 0xA1, 0x60, 0xD1, 0x52, 0x5F, 0xC4, 0x7A, 0xAD,
0x4F, 0xFF, 0xE2, 0x99, 0xD5, 0x7A, 0x28, 0x6E, 0xC0, 0x37,
0xF5, 0x70, 0xE6, 0x46, 0x07, 0x07, 0xA2, 0xF5, 0x4B, 0x39,
0x3A, 0x97, 0x32, 0x8E, 0xB0, 0xE7, 0xBB, 0xE8, 0xC7, 0xD2,
0xB7, 0x08, 0x7B, 0x62, 0x10, 0x3C, 0x56, 0x72, 0xF8, 0x69,
0x00, 0x00, 0xA0, 0xF4, 0x06, 0x63, 0x46, 0x02, 0x46, 0x02,
0x50, 0xFB, 0x4F, 0x71, 0x4A, 0x00, 0x00, 0x00, 0x5B, 0xA7,
0x4C, 0x9F, 0xF7, 0x7F, 0x00, 0x00, 0x00, 0xFC, 0x4F, 0x71,
0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x69, 0x78, 0x2E, 0x1C, 0x02, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xFC,
0x4F, 0x71, 0x4A, 0x00, 0x00, 0x00, 0xE8, 0x14, 0x4C, 0x9F,
0xF7, 0x7F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x4A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
0x1E, 0x1F, 0xD0, 0x3C, 0x56, 0x72, 0xF8, 0x69, 0x00, 0x00,
0x00, 0x00
};
int main() {
for (int i = 0; i < 0x72; ++i) {
cout << (char)(enc[i] ^ key[i] ^ i);
}
}

obf

屎山中的屎山,全是混淆。在与mz1大爹连蒙带猜各种调试后最终得出加密流程是一个异或和一个 xxtea 和一个异或以及另一个反转后异或,非常的杀时间
不写 wp 了