逆向大部分题都很简单,AK了week1,没什么好记录的,week2和week3有几道题需要记录一下.

week3和week4没怎么打,看着慢慢补充吧…

Reverse

EzDll

先看exe文件,可以看到使用encrypt()函数对flag进行加密,并且从[4*i]猜测是TEA系加密:

image-20231025182319801

DLL动态链接库同样可以使用IDA进行分析,在题目提供的dll文件中我们可以找到encrypt()加密函数:

image-20231025182118379

可以看到这是一个魔改的XTEA加密,所以传递的第二个参数就是密钥,我们将其提取出来,然后尝试进行解密:

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

void XTEA_decryot(uint32_t *v, uint32_t *key) {
uint32_t v0 = v[0], v1 = v[1];
uint32_t delta = 999999999;
uint32_t sum = 1 + delta * 33;

for (int i = 0; i < 33; i++) {
v1 -= (((v0 << 3) ^ (v0 >> 4)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 3) ^ (v1 >> 4)) + v1) ^ (sum + key[sum & 3]);
}

v[0] = v0;
v[1] = v1;
}
int main() {
unsigned char v4[] = {
130, 67, 163, 137, 111, 186, 128, 200, 248, 180,
86, 189, 179, 65, 178, 141, 218, 68, 14, 4,
3, 46, 56, 222, 18, 84, 173, 137, 149, 48,
99, 33, 223, 13, 148, 17, 220, 178, 208, 17,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0
};
uint32_t *v4p = (uint32_t *) v4;

uint32_t key[] = {
276, 1300, 6425, 2064
};

for (int i = 0; i < 9; ++i) {
XTEA_decryot(&v4p[2 * i], key);
}
printf("\n%s\n", (char *) v4);
return 0;
}

发现乱码,说明哪里有问题.

再看main函数和函数列表,发现有反调试的部分,例如这个异或判断,就要求必须是不在调试的时候运行,否则IsDebuggerPresent()会返回非0值:

image-20231025182555465

image-20231025182617217

并且在函数列表发现了Tls相关的函数(别问我是怎么知道这东西的),并且我们知道其常常被用于反调试…

最终我们在一系列"callback"函数中找到了这个该死的对key进行修改的部分:

image-20231025184514929

这里的数组就是key数组.

把这里的修改加上就出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
#include <stdio.h>
#include <stdint.h>

void XTEA_decryot(uint32_t *v, uint32_t *key) {
uint32_t v0 = v[0], v1 = v[1];
uint32_t delta = 999999999;
uint32_t sum = 1 + delta * 33;

for (int i = 0; i < 33; i++) {
v1 -= (((v0 << 3) ^ (v0 >> 4)) + v0) ^ (sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= (((v1 << 3) ^ (v1 >> 4)) + v1) ^ (sum + key[sum & 3]);
}

v[0] = v0;
v[1] = v1;
}

int main() {
unsigned char v4[] = {
130, 67, 163, 137, 111, 186, 128, 200, 248, 180,
86, 189, 179, 65, 178, 141, 218, 68, 14, 4,
3, 46, 56, 222, 18, 84, 173, 137, 149, 48,
99, 33, 223, 13, 148, 17, 220, 178, 208, 17,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0
};
uint32_t *v4p = (uint32_t *) v4;

uint32_t key[] = {
276, 1300, 6425, 2064
};
key[0] = 5;
key[1] = 4 * key[0];
key[2] = (key[1] >> 1) + 3;
key[3] = (key[2] ^ key[1] ^ key[0]) >> 1;

for (int i = 0; i < 9; ++i) {
XTEA_decryot(&v4p[2 * i], key);
}
printf("\n%s\n", (char *) v4);
return 0;
}
image-20231025184837381

结束…

eazy_enc

使用了一个函数指针数组来进行多次加密:

image-20231025185205159

分别分析4个加密函数,最终决定对flag的每一个字符进行爆破,保持加密函数逻辑不变,对爆破的字符进行加密,最后检查是否匹配.

实际上后来发现上面的4个加密函数执行后,密文和明文并不是一一对应的(单射?),原因可能在于第四个加密中的*=52这个操作会有溢出,导致回绕:

image-20231025185608304

实际可能不止这一处,懒得分析了(数学不太行),总之就是要把每一个可行的字符都输出来,尝试肉眼进行匹配(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
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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

int caesar(int before_c, int offset) {
int c = before_c;
if (!isdigit(c)) {
while (offset < 0) {
offset += 26;
}
} else {
while (offset < 0)
offset += 10;
}
if (islower(c)) {
c = c + offset;
return 'a' + (c - 'a') % 26;
} else if (isupper(c)) {
c = c + offset;
return 'A' + (c - 'A') % 26;
} else if (isdigit(c)) {
c = c + offset;
return '0' + (c - '0') % 10;
} else
return (c);
}

int caesar2(int before_c, int offset, int mod_num, int base) {
return (before_c + offset) % mod_num + base;
}

int main() {
unsigned char enc[100] = {0};
enc[0] = -24;
enc[1] = 0x80;
enc[2] = -124;
enc[3] = 8;
enc[4] = 24;
strcpy((char*)&enc[5], "<xh");
enc[9] = 112;
enc[10] = 124;
enc[11] = -108;
enc[12] = -56;
enc[13] = -32;
enc[14] = 16;
enc[15] = -20;
enc[16] = -76;
enc[17] = -84;
enc[18] = 104;
enc[19] = -88;
enc[20] = 12;
enc[21] = 28;
enc[22] = -112;
enc[23] = -52;
enc[24] = 84;
enc[25] = 60;
enc[26] = 20;
enc[27] = -36;
enc[28] = 48;
enc[29] = 0;
int len = 29;

char key[] = "NewStarCTF";
int key_len = strlen(key);
printf("key_len = %d\n",key_len);
printf("enc is:\n");
for (int i = 0; i < len; ++i)
printf("%c", enc[i]);
putchar('\n');
unsigned char dec[100];
for (int i = 0; i < len; ++i) {
for (unsigned char chr = 0; chr <= 128; ++chr) {
dec[i] = chr;

unsigned char temp = dec[i];
if (isupper(temp))
temp = caesar2(temp, -52, 26, 'A');
else if (islower(temp))
temp = caesar2(temp, -89, 26, 'a');
else if (isdigit(temp))
temp = caesar2(temp, -45, 10, '0');
dec[i] = temp;

dec[i] += key[i % key_len];
dec[i] = ~(dec[i]);
dec[i] *= 52;
if (dec[i] == enc[i] && chr>=30 && chr<=128) {
putchar(chr);
// break; // 这里的break;注释掉,以确保所有的匹配项都输出来
}
}
putchar('\n');
}
for (int i = 0; i < len; ++i) {
putchar(dec[i]);
}
/*
暴力结果:
B B
:r r
#u u
"t t
-e e
F F
4o 4 o
:r r
+c c
-e e
I I
!s s
A A
G G
4o o
4o o
,d d
%w w
)a a
'y y
"t t
4o o
G G
-e e
"t t
F F
1l l
)a a
/g g
可知并不是一一映射的关系,可能有多解:
整理猜测得出:(正解为第二个)
BruteF4rceIsAGoodwaytoGetFlag
BruteForceIsAGoodwaytoGetFlag
即flag{BruteForceIsAGoodwaytoGetFlag}
*/
return 0;
}

flag简单地猜测即可得出.

Random_1

多余的话不说,main函数都能分析明白,关键就是找到这个伪随机的种子.

我们确定了程序的平台,编译环境,那么理论上我们只需要找到固定的随机数种子就能生成相同的伪随机数序列,进而实现解密,但是这个seed不好找…

491d18626b30bcf14b6f8c49c2ee9a61

由于题解在很久之后才写,所以这里主要记录一下之前做的时候的一些过程思路,实际思考过程并不是像这篇题解这样的.

实际上我们从导入表进行交叉引用,慢慢找就能找到关键函数.

尝试了动调获取seed失败,我们能够知道这个程序是有反调试手段的,那么从这个角度出发,我们如果能找到反调试的部分在哪里,也许就能顺藤摸瓜找到seed这个种子究竟在哪里设置的.

经过分析某个该死的函数的IDA图形化交叉引用视图,这里忘了是哪个了,当时应该就是从那个_srand()函数出发的…

我们可以发现这个程序的反调试的手段在这个_Z1av指向的函数中:

df3c1046e922e0563bb806abeaa1448e

7aff7b3313843ae0b4cbaf253569a99b

我没记错的话这个a()函数就是_Z1av指向的函数(忘记了)

实际上就是从这里的这个函数进行依次调用的:

df270215e2f8857ce183f1ffd817a8af

依次调用了这个函数指针数组中保存的一系列函数,我们跟进去看就是前图那两个_Z1av_Z1bv,虽然不知道为什么起这么个名字.

实际上这里看到我们要找的关键函数在__init_array_start中,这里就涉及到init_array段了,其中的函数调用早于main()函数,所以我们一下子找不到这个函数,但是他们确确实实地运行了,同样的还有fini_array段,其中的部分是在程序结束时执行的.

那么再往后看,第二个_Z1bv指向的函数同样跟进去一看,就发现这个函数里有种子了(记得这个就是对应着_Z1bv):

c394bd16e43361f9a0c1604cb0a5a53d

那么种子有了,其他部分的加密很简单,直接写脚本就好,一定要在同样的环境下跑—Ubuntu22下运行

解密脚本:

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

int main() {
//在ubuntu22下运行
unsigned char s2[1000];
long long *p = (long long *) s2;
p[0] = 0x3513AB8AB2D7E6EELL;
p[1] = 0x2EEDBA9CB9C97B02LL;
p[2] = 0x16E4F8C8EEFA4FBDLL;
p[3] = 0x383014F4983B6382LL;
p[4] = 0xEA32360C3D843607LL;
p[5] = 42581LL;
p[6] = 0;
unsigned char Table[] =
{
99, 124, 119, 123, 242, 107, 111, 197, 48, 1,
103, 43, 254, 215, 171, 118, 202, 130, 201, 125,
250, 89, 71, 240, 173, 212, 162, 175, 156, 164,
114, 192, 183, 253, 147, 38, 54, 63, 247, 204,
52, 165, 229, 241, 113, 216, 49, 21, 4, 199,
35, 195, 24, 150, 5, 154, 7, 18, 128, 226,
235, 39, 178, 117, 9, 131, 44, 26, 27, 110,
90, 160, 82, 59, 214, 179, 41, 227, 47, 132,
83, 209, 0, 237, 32, 252, 177, 91, 106, 203,
190, 57, 74, 76, 88, 207, 208, 239, 170, 251,
67, 77, 51, 133, 69, 249, 2, 127, 80, 60,
159, 168, 81, 163, 64, 143, 146, 157, 56, 245,
188, 182, 218, 33, 16, 255, 243, 210, 205, 12,
19, 236, 95, 151, 68, 23, 196, 167, 126, 61,
100, 93, 25, 115, 96, 129, 79, 220, 34, 42,
144, 136, 70, 238, 184, 20, 222, 94, 11, 219,
224, 50, 58, 10, 73, 6, 36, 92, 194, 211,
172, 98, 145, 149, 228, 121, 231, 200, 55, 109,
141, 213, 78, 169, 108, 86, 244, 234, 101, 122,
174, 8, 186, 120, 37, 46, 28, 166, 180, 198,
232, 221, 116, 31, 75, 189, 139, 138, 112, 62,
181, 102, 72, 3, 246, 14, 97, 53, 87, 185,
134, 193, 29, 158, 225, 248, 152, 17, 105, 217,
142, 148, 155, 30, 135, 233, 206, 85, 40, 223,
140, 161, 137, 13, 191, 230, 66, 104, 65, 153,
45, 15, 176, 84, 187, 22
};
int len = strlen((char *) s2);
printf("len = %d\n", len);
srand(1400333646);
for (int i = 0; i < len; ++i) {
int rand_v4 = rand();
for (char chr = 0; chr >= 0; ++chr) {
if (Table[(unsigned char) (chr + rand_v4 % 255)] == s2[i]) {
putchar(chr);
break;
}
}
}
return 0;
}

记得就是上面这个,应该没什么问题…

运行结果:

image-20231025191743587

STL_1

不知道STL是什么先去学c++吧…

这个题就是要能够读懂程序,STL经过编译后,IDA分析出来的函数名很长,实际上关键的就是那一点:

image-20231025192248287

熟悉c++的话这题代码分析起来没难度…

难点在于对加密过程进行逆向.

程序中先对字符串进行reverse,然后执行了若干位运算,重点是位运算.

最后的检查flag是否正确还有一部分位运算变换,并且是将4个字符(4字节)作为一个int进行解释,然后进行位操作的.

将一个int的32个位展开,逐步分析各个位操作,最终能整理出如何一点点的进行逆向,最终我们需要多步操作逐渐将这32个位分别进行还原,由于后来写的WP(其实是懒),逻辑直接放代码里了…

解密脚本:

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
#include <iostream>
#include <string>
#include <algorithm>
#include <cstring>
#include <cctype>
#include <stdint.h>

int main() {
uint64_t v15[11];
uint32_t flag[11];
v15[0] = 0x2882D802120ELL;
v15[1] = 0x28529A05954LL;
v15[2] = 0x486088C03LL;
v15[3] = 0xC0FB3B55754LL;
v15[4] = 0xC2B9B7F8651LL;
v15[5] = 0xAE83FB054CLL;
v15[6] = 0x29ABF6DDCB15LL;
v15[7] = 0x10E261FC807LL;
v15[8] = 0x2A82FE86D707LL;
v15[9] = 0xE0CB79A5706LL;
v15[10] = 0x330560890D06LL;

// uint32_t f就是当前处理的4个字符---视为一个4字节的整数进行位操作
// 下面的f[i-j]代表对f的i-j编号的二进制位进行处理
// 例如f[0]代表最低的0位(最右边的位)
// 操作完成后f就是局部的一段明文

// 同理,t就是密文

// f[14-0] = t[14-0]
// f[29-15] = t[29-15] ^ f[14-0]
// f[31-30] = t[31-30] ^ f[16-15]

for (int i = 0; i < 11; ++i) {
uint32_t t = v15[i];
uint32_t f = 0;
f += t & 0x7FFF;
f += (((t >> 15) & 0x7FFF) ^ (f & 0x7FFF)) << 15;
f += (((t >> 30) & 0x3) ^ ((f >> 15) & 0x3)) << 30;

flag[i] = f;
}
unsigned char *p = (unsigned char *) flag;
for (int i = 42; i >= 0; i--) {
p[i] ^= p[i + 1];
}
std::reverse((char*)flag, (char*)flag + 44); // reverse 这个字符串
// std::reverse(p, p + 43);
for (int i = 0; i < 44; i++) {
printf("%c", p[i]);
}
return 0;
}

运行结果:

image-20231025193234519

ez_chal

久远了,就存着个解密代码了.是个魔改XTEA:

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

void XTEA_encrypt(uint32_t *v, uint32_t *key) {
uint32_t v0 = v[0], v1 = v[1];
uint32_t sum = 0, delta = 0x9E3779B9;

for (int i = 0; i < 32; i++) {
v0 += (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
sum += delta;
v1 += (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]);
}
v[0] = v0;
v[1] = v1;
}

void XTEA_decryot(uint32_t *v, uint32_t *key) {
uint32_t v0 = v[0], v1 = v[1];
uint32_t delta = -0x61C88747;
uint32_t sum = delta * 64;

for (int i = 0; i < 64; i++) {
v1 -= v0 ^ (((v0 << 4) ^ (v0 >> 5)) + v0) ^
(sum + key[(sum >> 11) & 3]);
sum -= delta;
v0 -= v1 ^ (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]);
}

v[0] = v0;
v[1] = v1;
}

int main() {
unsigned char v4[] = {
156, 162, 158, 193, 135, 31, 9, 220, 59, 227,
246, 145, 122, 92, 154, 246, 32, 159, 82, 147,
225, 148, 91, 138, 155, 6, 29, 249, 64, 227,
176, 35
};
char key[] = {
78, 101, 119, 83, 116, 97, 114, 33, 78, 101,
119, 83, 116, 97, 114, 33
};
for (int i = 0; i < 4; ++i) {
XTEA_decryot(&((uint32_t *) v4)[2 * i], (uint32_t *) key);
}
printf("%s\n", (char *) v4);
return 0;
}

Let’s GO

GO逆向,有一个反调试:

image-20231114204614556

这里的异或操作是在生成真正的iv,用于后续的AES加密.

调试可得是将位于0x3F56C0的字符串NewStar!NewStar!进行依次异或.(实际上这个字符串是AES的key)


接下来真正的main中,qword_436096存储的就是密文,不过要根据这个十六进制字符串生成字节流才行.

不过这几行纯靠猜…

image-20231114204829077


动调到ASE加密的函数这里,检查参数:image-20231114205117672

可以知道字符串NewStar!NewStar!就是使用到的key.


再后面这里看到是CBC模式,并且前面发现的那个反调试控制的异或操作对应的数组就是iv:

image-20231114205638886

(这么看来前面那个crypto_aes_NewCipher()是初始化加密环境的函数)

现在为止已经分析清楚了,可以编写脚本了:

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
from Crypto.Cipher import AES

# iv
iv_original = [
78, 101, 119, 83, 116, 97, 114, 33, 78, 101,
119, 83, 116, 97, 114, 33
]
iv = ''
for i in iv_original:
iv += chr(i ^ 0x32)
iv = iv.encode()
print(iv)

# key
key = b'NewStar!NewStar!'

# enc
enc_list = [
101, 101, 48, 49, 54, 55, 52, 98, 49, 51,
102, 102, 56, 100, 100, 56, 54, 102, 56, 101,
52, 56, 49, 97, 97, 56, 54, 102, 53, 100,
50, 53, 101, 55, 55, 51, 97, 51, 102, 100,
48, 51, 51, 56, 102, 54, 48, 57, 56, 56,
99, 98, 55, 51, 56, 98, 56, 98, 49, 55,
56, 99, 52, 52
]
enc = ''.join(chr(i) for i in enc_list)
enc = bytes.fromhex(enc)

# decrypt
aes = AES.new(key, mode=AES.MODE_CBC, iv=iv)
print(aes.decrypt(enc))

运行结果:

image-20231114205849539