WK-CTF

Re 2/3

0x01、so_easy

看上去很正常的一道安卓题,正常jadx看逻辑,很容易就锁定了加密是在native层做的

image-20240714204926598

直接把apk解包出来调调so,自己写的一个简单加密属于是,加密是每八个字节做一组,用于循环的变量v11初始值为255,每做一组循环,v11减5

image-20240714205043736

找到密文比较的地方,v17 += 2LL;,属于是每两个字节做一次比较

image-20240714205130483

逻辑清楚了,但是没两组做一次对密文的check的话不便于爆破,这块儿直接延用上面对于加密的数字的长度,每8个字节取一次check,z3直接嗦了,端序的问题,后面做一下取反就好了

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
from z3 import *

final_flag=''

enc = [0x540A95F0C1BA81AE, 0xF8844E52E24A0314, 0x09FD988F98143EC9, 0x3FC00F01B405AD5E]

xor_value = BitVecVal(0x71234EA7D92996F5, 64)

flag = BitVec('flag', 64)

v11=255
# 创建求解器
s = Solver()
temp = flag
for _ in range(v11,0,-5):
v12 = (2 * temp) ^ xor_value
v12 = If(temp >= 0, 2 * temp, v12)

v13 = (2 * v12) ^ xor_value
v13 = If(v12 >= 0, 2 * v12, v13)

v14 = (2 * v13) ^ xor_value
v14 = If(v13 >= 0, 2 * v13, v14)

v15 = (2 * v14) ^ xor_value
v15 = If(v14 >= 0, 2 * v14, v15)

temp = (2 * v15) ^ xor_value
temp = If(v15 >= 0, 2 * v15, temp)



s.add(temp==enc[0])

# 检查是否有解
if s.check() == sat:
# 如果有解,打印模型
m = s.model()
flag_value = m[flag].as_long()
flag_str = hex(flag_value)
# 去除前缀 '0x',得到纯十六进制字符串
hex_str_without_prefix = flag_str[2:]
byte_str = bytes.fromhex(hex_str_without_prefix)
decoded_str = byte_str.decode('utf-8')
final_flag+=decoded_str[::-1]
else:
print("No solution exists")



flag = BitVec('flag', 64)

v11=255
# 创建求解器
s = Solver()
temp = flag
for _ in range(v11,0,-5):
v12 = (2 * temp) ^ xor_value
v12 = If(temp >= 0, 2 * temp, v12)

v13 = (2 * v12) ^ xor_value
v13 = If(v12 >= 0, 2 * v12, v13)

v14 = (2 * v13) ^ xor_value
v14 = If(v13 >= 0, 2 * v13, v14)

v15 = (2 * v14) ^ xor_value
v15 = If(v14 >= 0, 2 * v14, v15)

temp = (2 * v15) ^ xor_value
temp = If(v15 >= 0, 2 * v15, temp)



s.add(temp==enc[1])

# 检查是否有解
if s.check() == sat:
# 如果有解,打印模型
m = s.model()
flag_value = m[flag].as_long()
flag_str = hex(flag_value)
# 去除前缀 '0x',得到纯十六进制字符串
hex_str_without_prefix = flag_str[2:]
byte_str = bytes.fromhex(hex_str_without_prefix)
decoded_str = byte_str.decode('utf-8')
final_flag+=decoded_str[::-1]
else:
print("No solution exists")

flag = BitVec('flag', 64)

v11=255
# 创建求解器
s = Solver()
temp = flag
for _ in range(v11,0,-5):
v12 = (2 * temp) ^ xor_value
v12 = If(temp >= 0, 2 * temp, v12)

v13 = (2 * v12) ^ xor_value
v13 = If(v12 >= 0, 2 * v12, v13)

v14 = (2 * v13) ^ xor_value
v14 = If(v13 >= 0, 2 * v13, v14)

v15 = (2 * v14) ^ xor_value
v15 = If(v14 >= 0, 2 * v14, v15)

temp = (2 * v15) ^ xor_value
temp = If(v15 >= 0, 2 * v15, temp)



s.add(temp==enc[2])

# 检查是否有解
if s.check() == sat:
# 如果有解,打印模型
m = s.model()
flag_value = m[flag].as_long()
flag_str = hex(flag_value)
# 去除前缀 '0x',得到纯十六进制字符串
hex_str_without_prefix = flag_str[2:]
byte_str = bytes.fromhex(hex_str_without_prefix)
decoded_str = byte_str.decode('utf-8')
final_flag+=decoded_str[::-1]
else:
print("No solution exists")


flag = BitVec('flag', 64)

v11=255
# 创建求解器
s = Solver()
temp = flag
for _ in range(v11,0,-5):
v12 = (2 * temp) ^ xor_value
v12 = If(temp >= 0, 2 * temp, v12)

v13 = (2 * v12) ^ xor_value
v13 = If(v12 >= 0, 2 * v12, v13)

v14 = (2 * v13) ^ xor_value
v14 = If(v13 >= 0, 2 * v13, v14)

v15 = (2 * v14) ^ xor_value
v15 = If(v14 >= 0, 2 * v14, v15)

temp = (2 * v15) ^ xor_value
temp = If(v15 >= 0, 2 * v15, temp)



s.add(temp==enc[3])

# 检查是否有解
if s.check() == sat:
# 如果有解,打印模型
m = s.model()
flag_value = m[flag].as_long()
flag_str = hex(flag_value)
# 去除前缀 '0x',得到纯十六进制字符串
hex_str_without_prefix = flag_str[2:]
byte_str = bytes.fromhex(hex_str_without_prefix)
decoded_str = byte_str.decode('utf-8')
final_flag+=decoded_str[::-1]
else:
print("No solution exists")


print(final_flag)
#WKCTF{2366064af80f669c2cb9519ab}

0x02、quite_easy

比赛结束了才做出来,人麻了

main逻辑基本用处不大,烟雾弹而已,真正有用的逻辑藏在了tls回调函数里面,这题反调也是相当多,相当抽象,得慢慢往后调,tls里面两个函数跟进去之后会发现有爆红,看汇编之后发现有一个很抽象的花指令(应该

image-20240714210635800

经典jz,jnz,但是感觉像花又不像花,他俩跳到的地方又不一样,所以说是相当抽象,当时卡了蛮久的,一开始是nop掉了jnz,发现往后动调下不去,一气之下重新开始审,又试了一下nop掉jz,哇,终于反编译出来了,然后发现 sub_401195 这个函数还是属于是反调,但是能直接patch过去,问题不大,然后真正的逻辑是放在了sub_4014A6里面的一个函数里面,sub_4014A6同样有一个怪异的花,但是同样也是nop掉jz那块儿就能直接过

image-20240714211454735

终于是把这个该死的逻辑找到了,一开始有点懵逼,然后寻思动调看看值是咋变的,因为这块儿几个函数都不知道啥意思

image-20240714211522444

跑起来之后手动过了几个反调,反调是真多,来到逻辑这块儿之后发现sub_1C1663这个函数的交叉引用,然后看到有一手判长,是否等于48,盲猜这个函数是用来取长度的,如果这样的话种子里面的数就应该是32加上89,如此随机数我们也可以模拟出来了,上模拟随机数的脚本,范围限制在0~255

image-20240714212024407

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main() {
srand(121); // 设置种子为121

int v3 = 0;

for (int i = 0; i < 16; i++) {
v3 = rand() % 256; // 生成0到255之间的随机数
printf("0x%02x,", v3); // 以0x开头的十六进制格式输出,确保输出为两位
}

return 0;
}
//0xb1,0x74,0x93,0x32,0xd6,0x13,0xcc,0x85,0x20,0xa8,0xf4,0x96,0x8a,0xd2,0x7d,0x26

后面几串代码单个逻辑能看明白,但是和密文check那块儿有点脱节,索性先猜一手,sub_1C13E3不知道是干嘛的,卡了很久,感觉像是一种溢出的检测或者之类的

image-20240714212502762

最后一部分貌似是对另一个数组有一步操作,跟过去发现是跟摆在main里面的那个check里面的数据的操作

image-20240714212851167

然后其他三个部分正常往回逆居然就出了?!这你敢信,wtm,比赛时候一直在纠结sub_1C13E3这个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
enc = [  0x80, 0xD3, 0x6F, 0xFF, 0x15, 0x03, 0x98, 0x8C, 0xB4, 0x5B, 
0x96, 0xC0, 0x59, 0xAC, 0x18, 0xDF, 0x2D, 0xCE, 0x3F, 0xFB,
0xC4, 0xED, 0xD8, 0xD2, 0xA8, 0x2D, 0xF8, 0x23, 0x9F, 0x22,
0x25, 0xCE]

randdd = [0xb1,0x74,0x93,0x32,0xd6,0x13,0xcc,0x85,0x20,0xa8,0xf4,0x96,0x8a,0xd2,0x7d,0x26]

table = "flag{ed1d665e6516a37ab09f0b7a40}"

for i in range(32):
enc[i] = enc[i] + ord(table[i])& 0xFF

for k in range(0,16):
enc[k] = enc[k]^randdd[k]

for j in range(16,32):
enc[j] = enc[j]^enc[j-16]

for g in range(len(enc)):
print(chr(enc[g]),end='')

#WKCTF{08898c40064d1fc4836db94fe}