前言

hgame 2021的逆向部分题解,一道nc题和一道安卓逆向没有做,每周的题目都看了看,做了四分之三左右,杭电的比赛题目出得很有水平,自己学到了很多东西,wp本来说每周都写的,但是懒,比赛都结束一个月了才偷工减料得写出来。有兴趣的师傅凑合着看看吧吧🤪

RE-week1

一杯阿帕茶

明显的TEA加密标志,后面分析为XXTEA加密

加密后的数据,刚好35位

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
#include <stdint.h>
#include <stdio.h>

#define DELTA 0x9e3779b9
#define MX (((z>>5 ^ y<<2) + (y>>3 ^ z<<4)) ^ ((sum ^ y) + (k[(p&3) ^ e] ^ z)))

void XXTEA(int n, uint32_t* v, uint32_t* k)
{
uint32_t sum, y, z;
uint32_t p, rounds, e;
if (n > 1) {
rounds = 6 + 52 / n;
sum = 0;
z = v[n - 1];
do {
sum += DELTA;
e = (sum >> 2) & 3;
for (p = 0; p < n - 1; p++) {
y = v[p + 1];
z = v[p] += MX;
}
y = v[0];
z = v[n - 1] += MX;
} while (--rounds);
}
else if (n < -1) {
n = -n;
rounds = 6 + 52 / n;
sum = rounds * DELTA;
y = v[0];
do {
e = (sum >> 2) & 3;
for (p = n - 1; p > 0; p--) {
z = v[p - 1];
y = v[p] -= MX;
}
z = v[n - 1];
y = v[0] -= MX;
sum -= DELTA;
} while (--rounds);
}
}

int main()
{
uint32_t arr[] = {3880694563, 3081185334, 1506439138, 2524759489, 3883935348, 1026381030, 2325545814, 2581382044, 1881594093, 1781792173, 4103492874, 1553756062, 468045900, 1730391575, 1383114178, 2890011402, 2227070898, 1885128569, 1548828056, 4214676013, 571971141, 1558401693, 3515474427, 3898332297, 1942540575, 1421197718, 3061626000, 555214026, 2648963476, 794468778, 2816999933, 3272437419, 464379036, 877899850, 2460223225};
uint32_t key[] = { 1,2,3,4 };
XXTEA(-35, arr, key);
for (int i = 0; i < 35; i++) {
printf("%c", arr[i]);
}
return 0;
}

Welcome to reverse world !

简单题,一个异或直接搞定

1
2
3
4
5
6
7
8
from ida_bytes import *

def re():
addr = 0x00007FF7F7513480
flag = ''
for i in range(22):
flag += chr((0xff - i) ^ get_byte(addr +i))
print(flag)

pypy

给了我们python字节码,需要我们自己还原成python代码,下面贴还原过后的代码,加密过程很简单

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import dis

def input_func():
raw_flag = input('give me your flag:\n')
cipher = list(raw_flag[6:-1])
length = len(cipher)

for i in range(length // 2):
cipher[2*i], cipher[2*i+1] = cipher[2*i+1] , cipher[2*i]


res = []
for i in range(length):
res.append(ord(cipher[i]) ^ i)
res = bytes(res).hex()
print('your flag: ' + res)

# print(dis.dis(input_func))

解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def Cip():
flag = ''
res = []
cipher = '30466633346f59213b4139794520572b45514d61583151576638643a'
length = len(cipher)
for i in range(0, length, 2):
res.append(int(cipher[i:i+2],16))
for i in range(len(res)):
flag += chr(res[i] ^ i)
flag = list(flag)
for i in range(len(flag) // 2):
flag[i*2+1], flag[i*2] = flag[i*2],flag[i*2 + 1]
for i in range(len(flag)):
print(flag[i],end='')

# hgame{G00dj0&_H3r3-I$Y@Ur_$L@G!~!~}

RE-week2

helloRe2

有两处检测,第一处直接明文比较,但是注意要倒序

1
password1: 2b0c5e6a3a20b189

第二处开启了另一个线程,利用了第一次检测的输入值异或一次后作为秘钥,进行CBC模式的AES加密,初始向量为0-15

1
2
3
4
5
6
7
8
9
10
11
from Cryptodome.Cipher import AES

key = '2b0c5e6a3a20b189'
key = bytes(ord(key[i])^i for i in range(16))
Cipher = bytes([183, 254, 254, 217, 7, 118, 121, 101, 63, 78, 95, 98, 213, 2, 246, 126])
iv = bytes(i for i in range(16))

aes = AES.new(key,AES.MODE_CBC,iv)
Plain = aes.decrypt(Cipher)
print(Plain)
# 7a4ad6c5671fb313

app-release

不熟悉安卓,但是经过一系列网上找资料后查到了如何逆向APK代码,过程很简单,就是将KEY进行sha256计算成16位的key,进行AES加密后在进行base64编码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import hashlib
import base64
from Cryptodome.Cipher import AES

string = b'A_HIDDEN_KEY'
flag = b'EEB23sI1Wd9Gvhvk1sgWyQZhjilnYwCi5au1guzOaIg5dMAj9qPA7lnIyVoPSdRY'

key = hashlib.sha256(string).digest()
iv = hashlib.md5(string).digest()
Cipher = base64.b64decode(flag)

aes = AES.new(key, AES.MODE_CBC, iv)
Plain = aes.decrypt(Cipher)
print(Plain)

# hgame{jUst_A_3z4pp_write_in_k07l1n}

RE-week3

第三周只做出来一道题

FAKE

简单的smc,函数sub_40699B进行了代码解密工作,首先用idaPython将加密后的代码解码便于静态分析

1
2
3
4
5
6
7
8
from ida_bytes import *

addr1 = 0x0000000000401216
addr2 = 0x0000000000409080
for i in range(1086):
x = get_byte(addr1+i)
y = get_byte(addr2+i)
patch_byte(addr1+i,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
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
input = (char *)CmpData;
memset(CmpData, 0, 0x90uLL);
v38[0] = 55030;
v38[1] = 61095;
v38[2] = 60151;
v38[3] = 57247;
v38[4] = 56780;
v38[5] = 55726;
v38[6] = 46642;
v38[7] = 52931;
v38[8] = 53580;
v38[9] = 50437;
v38[10] = 50062;
v38[11] = 44186;
v38[12] = 44909;
v38[13] = 46490;
v38[14] = 46024;
v38[15] = 44347;
v38[16] = 43850;
v38[17] = 44368;
v38[18] = 54990;
v38[19] = 61884;
v38[20] = 61202;
v38[21] = 58139;
v38[22] = 57730;
v38[23] = 54964;
v38[24] = 48849;
v38[25] = 51026;
v38[26] = 49629;
v38[27] = 48219;
v38[28] = 47904;
v38[29] = 50823;
v38[30] = 46596;
v38[31] = 50517;
v38[32] = 48421;
v38[33] = 46143;
v38[34] = 46102;
v38[35] = 46744;
v37[0] = 'h';
v37[1] = 'g';
v37[2] = 'a';
v37[3] = 'm';
v37[4] = 'e';
v37[5] = '{';
v37[6] = '@';
v37[7] = '_';
v37[8] = 'F';
v37[9] = 'A';
v37[10] = 'K';
v37[11] = 'E';
v37[12] = '_';
v37[13] = 'f';
v37[14] = 'l';
v37[15] = 'a';
v37[16] = 'g';
v37[17] = '!';
v37[18] = '-';
v37[19] = 'd';
v37[20] = 'o';
v37[21] = '_';
v37[22] = 'Y';
v37[23] = '0';
v37[24] = 'u';
v37[25] = '_';
v37[26] = 'k';
v37[27] = 'o';
v37[28] = 'n';
v37[29] = 'w';
v37[30] = '_';
v37[31] = 'S';
v37[32] = 'M';
v37[33] = 'C';
v37[34] = '?';
v37[35] = '}';
v44 = 1;
for ( i = 0; i <= 5; ++i )
{
for ( j = 0; j <= 5; ++j )
{
for ( k = 0; k <= 5; ++k )
{
LODWORD(input) = CmpData[6 * i + j] + v37[6 * k + j] * *(_DWORD *)&a1[24 * i + 4 * k];
CmpData[6 * i + j] = (int)input;
}
}
}

观察后发现就是一个6*6的矩阵运算,z3解之

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from z3 import *

s = Solver()
res = [ 55030, 61095, 60151, 57247, 56780, 55726, 46642, 52931, 53580, 50437, 50062, 44186, 44909, 46490, 46024, 44347, 43850, 44368, 54990, 61884, 61202, 58139, 57730, 54964, 48849, 51026, 49629, 48219, 47904, 50823, 46596, 50517, 48421, 46143, 46102, 46744 ]
x = [BitVec("x_%d" % i, 8) for i in range(36)]
y = [104,103,97,109,101,123,64,95,70,65,75,69,95,102,108,97,103,33,45,100,111,95,89,48,117,95,107,111,110,119,95,83,77,67,63,125]

for i in range(6):
for j in range(6):
key=0
for n in range(6):
key += y[6*n+j]*x[6*i+n]
#print(i*6+j)
s.add(key == res[i*6+j])


flag = ''
if s.check() == sat:
for i in range(36):
char = s.model().eval(x[i]).as_long()
flag += chr(char)
print(flag)

# hgame{E@sy_Se1f-Modifying_C0oodee33}

helloRE3

这道题没有搞出来,后面看wp复现,对于windows消息机制以及带界面的程序逆向不熟悉。

在点击Check时,Dbgview显示order为65并且显示输入长度。

打开ida搜索字符串player找到对应函数,发现有一处 == 65的判定,会给一个值复制为1,x交叉引用发现一只一个函数调用了该全区变量

但是会提示函数无法f5,因为栈指针不平衡。

找到对应的地址,发现有call pop结构,目的是为了将call指令的下一条地址送入rax寄存器中,这里将rax设置为0x00007FF708DE8C3E作为下面rc4加密的秘钥。修复栈指针查看伪代码。image-20210327205950305

加密流程比较简单,order中的数值取反,RC4加密,比较,RC4的key长度为20,值为地址0x00007FF708DE8C3E处开始的硬编码

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
# python解密脚本

from Cryptodome.Cipher import ARC4

def decrypt_rc4():
Cipher = bytes([77, 175, 39, 173, 225, 236, 109, 218, 240, 49, 94, 154, 158, 41, 250, 190, 107, 8, 200, 73])
Key = bytes([144, 144, 88, 72, 137, 69, 208, 101, 72, 139, 4, 37, 96, 0, 0, 0, 72, 139, 64, 2])
rc4 = ARC4.new(Key)
Plain = rc4.decrypt(Cipher)

Plain = list(0xff^i for i in Plain)
return Plain

def find_in_order(Plain):
order = [i for i in range(21,33)]+[i for i in range(37,50)]+[i for i in range(54,76)]
name = list('1234567890-+QWERTYUIOP{}|ASDFGHJKL;\'~ZXCVBNM,./')
key = dict(zip(order,name)) # 生成对应的字典
flag = ''
for i in Plain:
flag += key[i]
print(flag)

find_in_order(decrypt_rc4())

# HGAME{6-K4K.4R+3C4T}

RE-week4

vm

看似很复杂,其实是输入单个字节的加解密,经过动态调试后发现只有异或和减操作,分别对应到的虚拟机指令是4和7,过程为将输入送入vm_eax寄存器,vm_ebx寄存器中是生成的操作数,动态调试即可获得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <stdio.h>
#include <stdint.h>
#include <Windows.h>

int main()
{
char cipher[] = { 9, 230, 79, 183, 219, 46, 130, 173, 232, 54, 118, 198, 240, 23, 103, 162, 247, 231, 74, 122, 235, 244, 58, 112, 237, 36, 2, 126, 175, 246, 59, 128, 191, 207 };
char Xor[] = { 0xfe,0x21,0x44,0x67,0x8a,0xad,0xd0,0xf3,0x16,0x39,0x5c,0x7f,0xa3,0xc5,0xe8,0x0b,0x2e,0x51,0x74,0x97,0xba,0xdd,0x0,0x23,0x46,0x69,0x8c,0xaf,0xd2,0xf5,0x18,0x3b,0x5e,0x81 };
char Sub[] = { 0x7A,0x1A,0xBA,0x5A,0xFA,0x9A,0x3A,0xDA,0x7A,0x1A,0xBA,0x5A,0xFB,0x9A,0x3A,0xDA,0x7A,0x1A,0xBA,0x5A,0xFA,0x9A,0x3A,0xDA,0x7A,0x1A,0xBA,0x5A,0xFA,0x9A,0x3A,0xDA,0x7A,0x1A };
for (int i = 33; i >= 0; i--) {
printf("%c", (cipher[i] + Sub[i]) ^ Xor[i]);
}
}

// hgame{w0W!itS_CpP_wItH_little_vM!}

AFiveSecondChallenge

比赛时没有做出来,对于c#不熟悉,即使主办方在最后放出了白给Hint,还是搞不出矩阵来,看wp慢慢复现。

首先这是一个扫雷游戏,用c#写的,扫雷的逻辑题目中说明的是通过矩阵运算,所以直接用C#反编译器看看能不能直接搞到源码。但是发现矩阵运算函数被动过手脚。

matrix中的数据很多,根据官方给的提示下载文件中给出了il2cppOutPut文件中有两个重要文件AFiveSecondChallengeAssembly-CSharp,这两个文件中有游戏中的所有逻辑。这里只分析CheckBombAt函数。

Assembly-CSharp文件中看到了这些注释,CheckBombAt函数只有一个参数叫Vector2,向量中存储的是数组的坐标,但是matrix数组是一个三维数组,但是这个向量中只存储了x和y坐标。

1
2
3
4
5
System.Boolean AFiveSecondChallenge.BombChecker::CheckBombAt(UnityEngine.Vector2)

BombChecker.CheckBombAt(_position)

BombChecker.CheckBombAt(new Vector2(x - 1, y - 1))

在看AFiveSecondChallenge.cpp文件,直接搜索CheckBomAt关键字,定位到该函数。发现了矩阵与运算的主要逻辑

1
2
3
4
5
6
7
8
9
Vector2_tA85D2DD88578276CA8A8796756458277E72D073D  L_21 = ___vec0; // 循环时的变量
float L_22 = L_21.get_x_0();
V_2 = (((double)((double)((float)il2cpp_codegen_subtract((float)(fmodf(L_22, (3.0f))), (float)(1.0f))))));
double L_23 = V_2;
double L_24 = V_2;
double L_25 = V_0; // y下标
double L_26 = V_2;
double L_27 = V_1; // z下标
return (bool)((((double)((double)il2cpp_codegen_add((double)((double)il2cpp_codegen_add((double)((double)il2cpp_codegen_multiply((double)((double)il2cpp_codegen_multiply((double)L_8, (double)L_23)), (double)L_24)), (double)((double)il2cpp_codegen_multiply((double)L_25, (double)L_26)))), (double)L_27))) > ((double)(0.0)))? 1 : 0);

上面的代码化简后就是下面的数学表达式,提取matrix进行图片绘制。

1
2
v_2 = (j % 3) - 1;
((x * (v_2 * v_2) + y * v_2 + z) > 0) ? 1 : 0;
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
#include <stdio.h>
#include <stdint.h>
#include <Windows.h>

int main()
{
double matrix[45][15][3] = {...}; // 数据太多省略
int map[45][45];
double x, y, z;
int dot;
for (int i = 0; i < 45; i++) {
for (int j = 0; j < 45; j++) {
double v_2 = (j % 3) - 1;
x = matrix[i][j / 3][0];
y = matrix[i][j / 3][1];
z = matrix[i][j / 3][2];
dot = ((x * (v_2 * v_2) + y * v_2 + z) > 0) ? 1 : 0;
map[i][j] = dot;
}
}

for (int i = 0; i < 45; i++) {
for (int j = 0; j < 45; j++) {
printf("%d", map[i][j]);
}
putchar('\n');
}
}

最后大概是这样,可以看到是一个二维码。

再用python的PIL库将其打印成二维码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from PIL import Image

MAX = 45
img = Image.new('RGB',(MAX,MAX))
dot = "111111100011101111100101000001110100101111111100000100001011111010001110100011001001000001101110101110100100000100111010100101001011101101110101101010111001001001001111101101011101101110101110001100101111111011101111101011101100000101011000100101000100011111000001000001111111101010101010101010101010101010101111111000000001100001101111000111101010000100000000101111100001111010101111111000000000101111100000100010001011011110010110001010001010000001000101111000011001000111010110100010011001110010100001000010001111010011000100111000001100100110101111100101101101010101110010010101001010001011101001011011101100000101101110000110010000111101101001010000100101110010010101100101111011101101110000101111101101101000100110110100101000010101010000101010100001110011100000010001001001000100010111111101101100111000011110100010000010100111000011011001010010011000001010101101011111000000101010011011011001011111111100110100101111110001000110111110111111110001100000011111000100011110010100011101001010101001011101011010110010000101101011010101110001100001010011000110010100011100011100001111111101110011011111110001111110111110001111110011111001100101101000010101001001101001100100110100111010001011001011111111011110010010011001000011110111111111101001000100011111011001111110011100011010101010010100001111000011011011001000001000100011111010000111001000100100101001100110001101100010111110010000011011101010111101010100000100001101011010011001110011100010111111000101110101110110010100111001110010111010001101000100001110010100010110000010111111000000010010110100110101110111100011110011101001101111111011001100100011010110100110100011100010011111101000111000111111100000000001110101000011000111001101101100010100111111100101011000101010100000011000101011110100000101100000110101000111001010110100011111101110101111001110001111110000000101111110000101110101111010011010010010011010001111001111101110101110100010000101010010100100101101100100000100111010110100001100000101111111001100111111101000101001110100110101100000100100010"

x = 45
for i in range(45):
for j in range(45):
if dot[i*x+j] == '1':
img.putpixel((i,j),(0,0,0))
else:
img.putpixel((i,j),(0xff,0xff,0xff))


img.show()
img.save('flag.png')

# flag: hgame{YOU~hEn-duO_yOU-X|~DOU-sHi~un1Ty~k4i-fA_de_O}

ollvm

这个程序对里面所有的字符串进行了加密,需要慢慢寻找加密函数,在动态调试后发现了AES的S_box盒子,且发现加密模式为CBC模式,动调出key与iv后直接写脚本解密,初始iv为0-15

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from Cryptodome.Cipher import AES

iv = bytes(i for i in range(16))
Cipher = bytes([145, 179, 193, 235, 20, 93, 213, 206, 58, 29, 48, 228, 112, 108, 107, 215,
105, 120, 121, 2, 163, 165, 223, 27, 253, 28, 2, 137, 20, 32, 122, 253,
36, 82, 248, 169, 249, 241, 107, 28, 15, 93, 80, 91, 236, 66, 209, 140,
184, 18, 207, 44, 169, 105, 49, 70, 253, 155, 234, 222, 200, 191, 148, 105])
key = b'CryptoFAILUREforRSA2048Key!!!!!!'

aes = AES.new(key,AES.MODE_CBC,iv)
Plain = aes.decrypt(Cipher)
print(Plain)

# hgame{cOsm0s_is_still_fight1ng_and_NEVER_GIVE_UP_O0o0o0oO00o00o}