Reverse

xor

一系列的异或,找对密钥和异或顺序即可.

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
enc_flag = "`agh{^bvuwTooahlYocPtmyiijj|ek'p"
enc_flag_list = [ord(i) for i in enc_flag]
wtf1_ogn = list('63290794207715587679621386735000')
wtf2_ogn = list('41803873625901363092606632787947')
wtf1_ogn_ord = [ord(i) for i in wtf1_ogn]
wtf2_ogn_ord = [ord(i) for i in wtf2_ogn]

wtf1_byte_1 = wtf1_ogn_ord[:16]
wtf1_byte_2 = wtf1_ogn_ord[16:]
wtf2_byte_1 = wtf2_ogn_ord[:16]
wtf2_byte_2 = wtf2_ogn_ord[16:]
# 末尾还要补上一个0
wtf1_byte_1.append(0)
wtf1_byte_2.append(0)
wtf2_byte_1.append(0)
wtf2_byte_2.append(0)

# reverse enc2
copy2_xored_flag = enc_flag_list[:]
copy_xored_flag_1 = copy2_xored_flag[16:]
copy_xored_flag_2 = copy2_xored_flag[:16]

for i in range(16):
copy_xored_flag_2[i] ^= wtf2_byte_2[16 - i]
copy_xored_flag_1[i] ^= wtf2_byte_1[16 - i]

for i in range(16):
copy_xored_flag_2[i] ^= wtf2_byte_1[16 - i]
copy_xored_flag_1[i] ^= wtf2_byte_2[16 - i]

for i in range(16):
copy_xored_flag_2[i] ^= wtf2_byte_2[i]
copy_xored_flag_1[i] ^= wtf2_byte_1[i]

for i in range(16):
copy_xored_flag_2[i] ^= wtf2_byte_1[i]
copy_xored_flag_1[i] ^= wtf2_byte_2[i]

xored_input_flag = ['' for i in range(32)]
for i in range(16):
xored_input_flag[i + 16] = copy_xored_flag_2[i]
xored_input_flag[i] = copy_xored_flag_1[i]

# reverse enc1
copy_input_flag_1 = xored_input_flag[16:]
copy_input_flag_2 = xored_input_flag[:16]

for i in range(16):
copy_input_flag_2[i] ^= wtf1_byte_2[16 - i]
copy_input_flag_1[i] ^= wtf1_byte_1[16 - i]

for i in range(16):
copy_input_flag_2[i] ^= wtf1_byte_1[16 - i]
copy_input_flag_1[i] ^= wtf1_byte_2[16 - i]

for i in range(16):
copy_input_flag_2[i] ^= wtf1_byte_2[i]
copy_input_flag_1[i] ^= wtf1_byte_1[i]

for i in range(16):
copy_input_flag_2[i] ^= wtf1_byte_1[i]
copy_input_flag_1[i] ^= wtf1_byte_2[i]

input_flag = ['' for i in range(32)]
for i in range(16):
input_flag[i + 16] = copy_input_flag_2[i]
input_flag[i] = copy_input_flag_1[i]

flag = ''.join([chr(i) for i in input_flag])
print(flag)

image-20240207103355686

SuperGusser

本题后来发现用了xObf混淆器进行混淆:

image-20240207104914098

动调的话,一步步执行会恢复一部分函数,不过我当时主要还是看汇编动调(累死).

加密循环在loc_40167F处:

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
.text:000000000040167F loc_40167F:                             ; CODE XREF: sub_401530+18F↓j
.text:000000000040167F mov rdx, [rbp-30h]
.text:0000000000401683 mov eax, [rbp-14h]
.text:0000000000401686 cdqe
.text:0000000000401688 movzx ecx, byte ptr [rdx+rax]
.text:000000000040168C mov eax, [rbp-14h]
.text:000000000040168F mov edx, eax
.text:0000000000401691 movzx eax, byte ptr [rbp-19h]
.text:0000000000401695 lea r8d, [rdx+rax]
.text:0000000000401699 movzx edx, byte ptr [rbp-1Ah]
.text:000000000040169D mov eax, edx
.text:000000000040169F shl eax, 4
.text:00000000004016A2 add eax, edx
.text:00000000004016A4 add eax, r8d
.text:00000000004016A7 xor ecx, eax
.text:00000000004016A9 mov rdx, [rbp-30h]
.text:00000000004016AD mov eax, [rbp-14h]
.text:00000000004016B0 cdqe
.text:00000000004016B2 mov [rdx+rax], cl
.text:00000000004016B5 add dword ptr [rbp-14h], 1
.text:00000000004016B9
.text:00000000004016B9 loc_4016B9: ; CODE XREF: sub_401530+14D↑j
.text:00000000004016B9 mov eax, [rbp-14h]
.text:00000000004016BC cmp eax, [rbp-18h]
.text:00000000004016BF jl short loc_40167F

加密后在后面紧跟的0x4016D5处调用了loc_46EEF4,该函数内调用了memcmp()来进行比较.

image-20240207104020663

F7进入后可以看到jmp到memcmp():

image-20240207104127323

当时想着总之最后就是把数据提取出来,反着解密一遍就好了,没有其他的东西.

复盘写WP时看加密过程以为异或的是0x44,但是当时用x64dbg调试发现实际上异或的是0x33(幸好当时用x64dbg了).

这里实际上有反调试,我的x64dbg装了SharpOD反反调试插件所以不受影响,如果使用IDA动调的话,且没有发现kernel32_IsDebuggerPresent的话就会出错.

数据也很好提取,然后异或就好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
'''
# 加密循环位于0x40167F
# memcpy检查在该处指令:0x4016D5 call loc_46EEF4
# IDA加密数据提取:
adr = 0x67fda0
li = []
for i in range(0x26):
tmp = idc.get_wide_byte(adr+i)
li.append(tmp)
print(li)
'''

enc = [
81, 81, 82, 95, 89, 67, 93, 95, 89, 73, 90, 89, 86, 46, 38, 29, 42, 55, 26, 39, 41, 23, 40, 36, 42, 56, 37, 33, 61,
15, 50, 58, 60, 61, 54, 51, 42, 0
]
for i in range(0x25):
# enc[i] = flag[i] ^ (0x33 + i)
enc[i] ^= (0x33 + i)

for i in enc:
print(chr(i), end='')
image-20240207104800338

俄语学习

将所有俄语问题的答案检查(jz)全部patch成jnz:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
start_addr = 0x10022b0
end_addr = 0x1002d7f

key = [0x74,0x1e,0x68]
flag = 0
for i in range(start_addr,end_addr):
temp=[0]*3
for j in range(3):
temp[j] = idc.get_wide_byte(i+j)
for j in range(3):
if temp[j] != key[j]:
flag = 1
break

if flag == 1:
flag = 0
continue
ida_bytes.patch_byte(i,0x75)
print('patched '+str(i))
print('done')

然后动调分析就可以得到后面的数据.

分析可以得知后面是一个标准的RC4:

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

key_bytes_1 = [
53, 109, 53, 100, 53, 119, 53, 100, 53, 98,
53, 110, 53, 109, 53, 100, 53, 119, 53, 100,
53, 98, 53, 110, 53, 109, 53, 100, 53, 119,
53, 100, 53, 98, 53, 110, 142, 0
]

import urllib.parse
import base64


def RC4(s_box, enc=''):
i = j = 0
res = []
for s in enc:
i = (i + 1) % 256
j = (j + s_box[i]) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
t = (s_box[i] + s_box[j]) % 256
k = s_box[t]
res.append(chr(ord(s) ^ k))

return ''.join(res)


enc_flag = '+i&[@Y:g8[&l$f8S8v$Y&e>{'
dec_flag = RC4(sbox2, enc_flag)
dec_flag2 = RC4(sbox1, dec_flag)

for i in range(len(dec_flag2)):
print(chr(ord(dec_flag2[i]) - key_bytes_1[i] + ord('p')), end='')

image-20240207105721516

6502

https://codediy.github.io/nes-zh/easy6502/index.html#snake

在这里直接跑就行

ezpython

用pyinstxtractor反编译后,分析可得是一个ECB模式的SM4,并且key和enc可以从secret中找到:

image-20240207120404910

但是SM4解密发现不正确,后来发现密钥在gmssl中的sm4.pyc中被魔改过,密钥被异或了37:

image-20240207120510078

修改解密脚本后再次运行得到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
from gmssl import sm4

# from secret import key, enc
key = 'BeginCTFBeginCTF'
enc = 'JmjJEAJGMT6F9bmC+Vyxy8Z1lpfaJzdEX6BGG/qgqUjUpQaYSON1CnZyX9YXTEClSRYm7PFZtGxmJw6LPuw1ww=='
print(len(key))
import base64


def pad_pkcs7(data):
'''PKCS#7填充'''
padding_len = 16 - len(data) % 16
padding = bytes([
padding_len] * padding_len)
return data + padding


def unpad_pkcs7(padded_data):
'''PKCS#7去填充'''
padding_len = padded_data[-1]
return padded_data[:-padding_len]


class SM4:

def __init__(self):
self.gmsm4 = sm4.CryptSM4()

def encryptSM4(self, encrypt_key, value):
gmsm4 = self.gmsm4
gmsm4.set_key(encrypt_key.encode(), sm4.SM4_ENCRYPT)
padded_value = pad_pkcs7(value.encode())
encrypt_value = gmsm4.crypt_ecb(padded_value)
return base64.b64encode(encrypt_value)

def decryptSM4(self, decrypt_key, value):
value = base64.b64decode(value)
print(value)
gmsm4 = self.gmsm4
gmsm4.set_key(decrypt_key.encode(), sm4.SM4_DECRYPT)
# 获取gmsm4的密钥
# print(gmsm4.get_key())
decrypt_value = gmsm4.crypt_ecb(value)
print(decrypt_value)
input()
return unpad_pkcs7(decrypt_value)


def main():
print('请输入你的flag:')
flag = input()
sm4_instance = SM4()
flag_1 = sm4_instance.encryptSM4(key, flag)
print(flag_1)
flag_2 = sm4_instance.decryptSM4(key, flag_1)
print(flag_2)
# if flag_1 != enc:
# print('flag错误!!')
# else:
# print('恭喜你获得flag😊😀')
print(flag_1)


def dec():
sm4_instance = SM4()
tmp_key = [chr(ord(i) ^ 37) for i in key]
tmp_key = ''.join(tmp_key)
flag = sm4_instance.decryptSM4(tmp_key, enc)
print(flag)


if __name__ == '__main__':
# main()
dec()

image-20240207120547409

Stick Game

这题很简单,直接在线反混淆,搜索那个关键的分数,就找到flag了.

not_main

VEH反调试,第二次加密为XXTEA,在全局类的析构函数中,引发除0异常进入handler()方法进行加密.

当时没做出来,以为只有这个加密,忘记了main()中那个加密.

(实际上是不知道这个异常何时引发,导致没想到main()这个加密依旧会执行)

标准TEA加密+标准XXTEA加密,分析清楚反调试后很简单:

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
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define DELTA 0x9e3779b9
#define MX (((z>>5^y<<2) + (y>>3^z<<4)) ^ ((sum^y) + (key[(p&3)^e] ^ z)))

void btea(uint32_t *v, int n, uint32_t const key[4]) {
uint32_t y, z, sum;
unsigned p, rounds, e;
if (n > 1) /* Coding Part */
{
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) /* Decoding Part */
{
n = -n;
rounds = 6 + 52 / n;
printf("rounds = %d\n", rounds);
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);
}
}

void TEA_decrypt(uint32_t *v, uint32_t *k) {
uint32_t v0 = v[0], v1 = v[1];
uint32_t delta = 0x9e3779b9;
uint32_t sum = delta * 32;
uint32_t k0 = k[0], k1 = k[1], k2 = k[2], k3 = k[3];

for (int i = 0; i < 32; i++) {
v1 -= ((v0 << 4) + k2) ^ (v0 + sum) ^ ((v0 >> 5) + k3);
v0 -= ((v1 << 4) + k0) ^ (v1 + sum) ^ ((v1 >> 5) + k1);
sum -= delta;
}

v[0] = v0;
v[1] = v1;
}
int main() {
// second encrypt function at Handler()
// XXTEA

// at 0x0100503C
unsigned char enc_data[] = {
0x1B, 0x0F, 0xBE, 0xCF, 0x3F, 0x08, 0xF3, 0x05, 0x3B, 0xE4,
0x20, 0x42, 0xEE, 0xAF, 0x83, 0x33, 0xCE, 0x37, 0x32, 0xFA,
0x6E, 0xA6, 0xAD, 0xEC, 0xA7, 0x7C, 0xD4, 0xA8, 0x77, 0x10,
0xC5, 0xEF
};
uint32_t *v = (uint32_t *)enc_data;
uint32_t const k[4] = { 0x74,0x72,0x75,0x65 };
int n; //n的绝对值表示v的长度,取正表示加密,取负表示解密
// v为要加密的数据是两个32位无符号整数
// k为加密解密密钥,为4个32位无符号整数,即密钥长度为128位
n = sizeof(enc_data) / sizeof(enc_data[0]) / 4;
puts((char *)v); //输出加密前的数据
printf("-DELTA = %#x\n", -DELTA);
printf("n = %d\n", n);
btea(v, -n, k);
puts((char *)v); //输出解密后的数据

uint32_t k2[4] = { 0x66,0x61,0x6b,0x65 };
for (int i = 0;i < n;i += 2) {
TEA_decrypt(v + i, (uint32_t *)k2);
}
puts((char *)v); //输出解密后的数据
return 0;
}

运行结果:

image-20240210115521308

出题人的密码是什么

开局啥也别管,先把反调试给绕过了,有4个反调试,都在main函数中,前2个是调用Win32API进行检查,后2个是获取时间戳,都使用if语句进行判断,直接把jz,jg给patch成jnz和jle即可,结果如下:

image-20240213111000836 image-20240213111323938 image-20240213111349029

输入输出和检查没什么问题,找加密函数并重命名:

image-20240213111509708

进入:

image-20240213111535637

进入:

image-20240213111553478

最后在这里:

image-20240213111626836

第2个很简单,异或常数然后偏移,重点第1个(据官方WP说是魔改的CRC64):

image-20240213111713843

v2的值直接动调得到,加密将48字节的数据分成6个64位块,进行64次循环加密.

比大小实际上是检查高位是否为1,选择是否与v2异或.

尽管看起来有数据丢失,但是v2的值是0x33077D,最低位是1,被异或的值(变量tmp)首先被乘2,也就是左移1位.

最低位一定被补0,如果和v2异或过,那么该位结果一定是1.

因此每轮循环仅需要检查密文的最低位即可确定走了哪条分支.

提取数据很简单,解密脚本如下:

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
#include <stdint.h>
#include <string.h>
#include <stdio.h>
int main() {
uint64_t v2 = 0x33077D;
unsigned char maybe_enc_data[] = {
0xb4, 0xbb, 0xd8, 0xeb, 0xd0, 0x6e, 0xab, 0xca,
0x65, 0x8e, 0x4b, 0xe9, 0x4d, 0xd4, 0x4a, 0xf3, 0x7d, 0x29,
0xc2, 0xf9, 0x95, 0x89, 0xa4, 0x85, 0x9d, 0xcd,
0xdf, 0x77, 0xfd,0x45, 0xcb, 0x5d, 0x7d, 0xfd,
0x93, 0x4b, 0xbc, 0xf6, 0x7c, 0xf3, 0x24,
0x42, 0xf5, 0xd2, 0xdd, 0xe3, 0x56, 0xae,0
};
for (int i = 0;i < 48;++i) {
maybe_enc_data[i] = (maybe_enc_data[i] ^ 0x25) - 5;
printf("%x ", maybe_enc_data[i]);
}
printf("\n");
uint64_t *each = (uint64_t *)maybe_enc_data;
for (int i = 0;i < 6;++i) {
printf("%x\n", each[i]);
for (int j = 0;j < 64;++j) {
if (each[i] & 1) {
each[i] = (each[i] ^ v2) >> 1;
each[i] |= 0x8000000000000000;
} else {
each[i] = each[i] >> 1;
}
}
}
puts((char *)each);
return 0;
}

结果如下:

image-20240213112228396