记录最近做的一些Android逆向题目。
Accelerate-Time
进去提示只能在固定的时间才能登录,于是先hook this.hour=4。
跟着走发现需要验证username和password。
逻辑比较长且复杂,跟着Password View的监听函数一步步跟找到校验username和password的逻辑。
关注password的监听事件,在这里可以逆向得到username=Android和password=Greatly。
username和password得到验证之后,关注login按钮的监听事件,但是最终还是进入到上述的校验逻辑中。
接着貌似验证过程就结束了。虽然在onCreate()之后就能发现另外一段校验函数,但是这个函数是如何调用的呢?
关注一开始设置的observer,根据LoginActivity.onCreate.2的构造函数,观察的对象就是传进去的当前的LoginActivity。
进一步的,如果当前的LoginActivity发生了变化,会调用到updateUiWithUser()进行二次验证。
在login按钮的点击事件中,会进入下面的函数。
Result oLoggedInUser是用户名和密码校验的返回结果,这里检查是否返回了success,如果是,则把this._LoginResult(MutableLiveData类)设置success的子类。这样的修改会被观察者模式发觉,因此调用回调函数onChanged并进一步调用showLoginFailed()或者updateUiWithUser()。
仔细观察updateUiWithUser()函数,先构造字符串hour+minute+second
计算hash值timeMD5,然后构造字符串flag{+timeMD5+}+username+password
。
但是需要注意的是,username和password是直接使用的其对应的EditText对象,所以拼接的不是Android+Greatly
。
1 | private final void updateUiWithUser(LoggedInUserView arg3) { |
虽然非常奇怪,但是不管是smali代码还是hook LoginActivitykt.encodeMD5()的输入参数,都证明确实直接拼接了EditText对象。
解密的思路非常简单,直接爆破时间,计算24*60*60次。
如果按照flag{MD5(hour+minute+second)}AndroidGreatly
来计算,可以得到时间为4:35:23,且hash值满足要求。
1 | import hashlib |
如果严格按照代码来,则找不出满足要求的时间。
frida爆破代码如下:
1 | //覆盖updateUiWithUser(),改为爆破函数,主动调用LoginActiivtyKt.encodeMD5()。 |
Time-Machine
这道题做得比较久,中途基本想放弃了,不过还是坚持做下来了,值得给自己点赞。(雾
首先看一下Java层,还好没有太多逻辑需要厘清。
要求先后点击两个按钮并获取点击时间,要求第一个时间戳大于第二个时间戳,显然这是不能实现的。(对应题目的名字Time Machine)
可以通过Frida Hook时间类,或者直接修改smali代码,把大于改成小于即可。为了后续IDA动态调试方便,我选择了后者。
1 | public class MainActivity extends AppCompatActivity { |
然后就输入flag并进行检查。
要求flag长度为42,格式为flag{xxxxx}
,flag前三位是e25
,第三到第八位的md5值为1E862D87DB3293B81C7D2934577A22FA
,用somd5
解出来是be952
。
然后把flag剩余的部分交给check函数传到native层去检查。
1 | protected void onCreate(Bundle bundle) { |
IDA分析so文件,本着一直以来的习惯先检查init_array
和JNI_onload
。init_array
有三个函数,其中一个使用了pthread_create_key
,看起来好像没有反调试代码,但是真要IDA动态调试会退出,所以把它们patch过掉。
事实上目前笔者对于如何优雅地patch init_array还是不大了解。
之前尝试过直接修改init_array中元素值为0x0000,但是后面打包安装会出错;也尝试过在函数内部插入POP指令直接返回,但是没有处理好堆栈还是出问题了;这里是把init_array中的值修改为一个无关函数的起始地址来patch掉检测代码的。
过掉反检测代码,检查JNI_onload中的动态注册操作,比较简单,就是把sub_8ED24CF8()函数注册为check()函数。
在sub_8ED24CF8中,ooo000()函数先把传进来的flag做一遍base64编码,然后check()函数把编码后的数组进行抑或并与目标数组比较。
1 | int __fastcall sub_8ED24CF8(JNIEnv *env, int jthiz, int inputFlag) |
先看ooo000()函数,代码中出现了相当明显的base64算法,更换了base64映射表。
注意appendString(retString, base64table[*(int6_4 + ind) ^ (*(int6_4 + ind) >> 3)]);
这段代码。
常规的base64,把3个byte转换为4个int6之后,直接把base64table[int6]追加到密文字符串之后就行了。
这里加了一点处理,追加的是base64table[ int6 ^ (int6>>3) ]。
好在这个过程是可逆的,反过来是:
1 | index = base64table.index(base64cipher[i]) |
1 | int __fastcall ooo000(char *retString, const char *flag) |
于是得到一个base64bytes
数组,在check函数中对它进行校验。
看起来这里的校验逻辑非常复杂,但是不需要一一厘清,只需要关注有意义的那两行代码就好。
根据经验加一点点合理的推测,就是先使用xor_key数组把刚刚得到的base64bytes数组抑或成bytesAfterXor数组,然后和aimBytes比较是否一致。
aimBytes数组的可以直接提取,xor_key存在于bss段,需要确定生成逻辑。但是因为我是动态调试的,所以直接从内存里面dump出来的。
有了这两个数据的具体值,可以还原base64bytes,再根据base64算法,可以还原出flag。
代码在最后。
1 | int __fastcall check(const char *base64bytes) |
1 | # flag还原代码 |
一些不足:
没有对反调试代码逻辑进行细致的梳理,想着是能过就好。
xor_key在bss段,在程序运行过程中生成,没有梳理生成算法,动态调试连过去直接dump内存得到的。
mimic-xctf-hahahaha
强网杯 “拟态”比赛 mobile1
1 | clip0: 5f384050 -> 5fb84050 |
该题先检查输入是否满足要求,然后基于输入数据生成flag。不过检查逻辑都是基于hash的,所以不能解密得到原文。
不过输入的8段数据(8个clip)的检查是相互独立的,所以可以考虑爆破。
接下来分析程序逻辑:
密钥准备:
在WelcomeActivity
中,先生成了相关密钥和hash值。a.SpecBytesMat
是后续计算hash时用到的初始向量矩阵,实际的值是原来值的MD5。a.rndKey
用于选择hash计算的函数,实际的值是原来值抑或0xAB
。a.clips_i_hash_mat
是8个hash值,也是输入的8个clip的hash值。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
33a.SpecBytesMat = new byte[][]{"WIgD1ZNZ0ilJqFpw".getBytes(), "4811tjOZjoiXpjdq".getBytes(), "ALFjcgztxnUaC89v".getBytes(), "ZgHzTu79Zwhoi0PB".getBytes(), "UYBfajKYrDFE1zJs".getBytes(), "yr4PBIjlJg89FpP3".getBytes(), "SFHqaTYDf7EeEevX".getBytes(), "gUwrqaE3nCxKr4Du".getBytes()};
a.rndKeys = new int[]{0xAF, 0xA1, 0xA4, 170, 0xA5, 0xAE, 0xA0, 0xA3}; // (xor 0xAB) -> {0x4,0xa,0xf,0x1,0xe,0x5,0xb,0x8}
a.clips_i_hash_mat = new String[]{"fc7466e55fbf37b1", "78b0be39e63b6837", "c2f9c805d0442203", "c11a61bb60d79dab", "869e650ee55bd9f6", "f2dda5fc021fe2bf", "305044db48fe6174", "d6659b5e2d1059f8"};
public class WelcomeActivity extends h {
// a.b.c.h
public void onCreate(Bundle arg4) {
super.onCreate(arg4);
this.setContentView(0x7F0B001D); // layout:activity_welcome
int ind2 = 0;
int ind1; // xor 0xAB
for(ind1 = 0; true; ++ind1) { // xor 0xAB
int[] v1 = a.rndKeys;
if(ind1 >= v1.length) {
break;
}
v1[ind1] ^= 0xAB;
}
while(ind2 < a.SpecBytesMat.length) {
MessageDigest MD5 = null;
try {
MD5 = MessageDigest.getInstance("MD5");
}
catch(NoSuchAlgorithmException v1_1) {
v1_1.printStackTrace();
}
MD5.update(a.SpecBytesMat[ind2]);
a.SpecBytesMat[ind2] = MD5.digest();
++ind2; // Loop MD5 to bytesMat(iteratively)
}
this.findViewById(0x7F080088).setOnClickListener(new WelcomeActivity.click2MainActivity(this)); // id:go_btn
}
}输入格式检查
先获取输入的8段字符串,然后进行检查。
字符串需要是16进制字符串。(不带0x
)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
53clipsi[0] = v1.p;
clipsi[1] = v1.q;
clipsi[2] = v1.r;
clipsi[3] = v1.s;
clipsi[4] = v1.t;
clipsi[5] = v1.u;
clipsi[6] = v1.v;
clipsi[7] = v1.w;
String[] clips = new String[8];
int v6 = 0;
int v7;
for(v7 = 0; v6 < 8; ++v7) {
String v8 = clipsi[v6].getText().toString(); // 要求输入8x8个字符
if(v8.length() != 8) {
Toast.makeText(this.b, "clips must be enough, try again!", 0).show();
return;
}
clips[v7] = v8;
++v6;
}
int ind = 0;
while(ind < 8) {
byte[] clips_i_bytes = a.hex2Bytes(clips[ind]); // format check 8位16进制字符串
if(clips_i_bytes == null) {
Toast.makeText(this.b, "clips format error, try again!", 0).show();
while(ind < 8) {
clipsi[ind].setText("");
++ind; // clean clips
}
return;
}
......
}
public static byte[] hex2Bytes(String arg6) {
int lens = arg6.length();
byte[] v1 = new byte[lens / 2];
int ind = 0;
while(ind < lens) {
int v3 = Character.digit(arg6.charAt(ind), 16) << 4;
int v4 = Character.digit(arg6.charAt(ind + 1), 16);
if(v3 >= 0 && v4 >= 0) {
v1[ind / 2] = (byte)(v3 + v4);
ind += 2;
continue;
}
return null;
}
return v1;
}格式转换与校验
既然每个clip是8位的16进制字符串,那么它等价于4个byte,也即一个int32
。
于是先转换为4个byte,然后把4个byte的最高位提出来构造为一个int4
记作rndKey
,然后4个byte只保留剩余的7位记作clips_i_bytes
。
设当前检查的是第i
个clip。
接着,调用一个自定义的hash计算函数,要求hash(rndKey, clips_i_bytes)=hash(a.rndKeys[i], clips_i_bytes)=a.clip_i_hash_mat[i]
。
hash内部是一个更为烧脑的计算逻辑,输入的rndKey
会用来选择hash计算算法,输入的clips_i_bytes
是hash的原文,如果rndKey>7
,会调用a.SpecBytesMat
作为hash计算的盐。
不过,考虑到hash计算的特性,在rndKey!=a.rndKeys[i]
的情况下,hash(rndKey, clips_i_bytes)=hash(a.rndKeys[i], clips_i_bytes)
的概率极小,所以不妨直接设定rndKey=a.rndKeys[i]
,由于a.rndKeys[i]已知,且rndKey来自于输入的clip,所以这下就确定了4个bit。
接下来就是枚举剩余的28个bit,使得hash(a.rndKeys[i], clips_i_bytes)=a.clip_i_hash_mat[i]
。(代码见最后)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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229int ind = 0;
while(ind < 8) {
byte[] clips_i_bytes = a.hex2Bytes(clips[ind]); // format check 8位16进制字符串
if(clips_i_bytes == null) {
Toast.makeText(this.b, "clips format error, try again!", 0).show();
while(ind < 8) {
clipsi[ind].setText("");
++ind; // clean clips
}
return;
}
int ind3 = 0;
int rndKey = 0;
while(ind3 < clips_i_bytes.length) {
rndKey = rndKey << 1 | (clips_i_bytes[ind3] & 0x80) >>> 7; // rndKey 是8个byte的最高位组合
clips_i_bytes[ind3] = (byte)(clips_i_bytes[ind3] & 0x7F); // clips_i_bytes 只保留7位,刚好保证在ASCII范围内
++ind3;
}
String clips_i_hash = a.hash_int_string_add0_0_16(rndKey, clips_i_bytes); // rndKeys[ind] == rndKey
if(clips_i_hash != null
&& (clips_i_hash.equals(a.hash_int_string_add0_0_16(a.rndKeys[ind], clips_i_bytes)))
&& (clips_i_hash.equals(a.clips_i_hash_mat[ind])))
{
++ind;
continue;
}
Toast.makeText(this.b, "your clip is not suitable, try again!", 0).show();
while(ind < 8) {
clipsi[ind].setText("");
++ind; // clean clips
}
return;
}
public static String hash_int_string_add0_0_16(int argRndKey, byte[] argBytes) {
String v0_1;
byte[] argBytes1 = argBytes;
if((argRndKey >>> 3 & 1) == 1) {
switch(argRndKey & 7) {
case 0: {
v0_1 = a.SHA512_int_String_add0(argBytes1, a.SpecBytesMat[0]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 1: {
v0_1 = a.SHA512_int_String_add0(argBytes1, a.SpecBytesMat[1]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 2: {
v0_1 = a.SHA512_int_String_add0(argBytes1, a.SpecBytesMat[2]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 3: {
v0_1 = a.SHA512_int_String_add0(argBytes1, a.SpecBytesMat[3]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 4: {
v0_1 = a.SHA512_int_String_add0(argBytes1, a.SpecBytesMat[4]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 5: {
v0_1 = a.SHA512_int_String_add0(argBytes1, a.SpecBytesMat[5]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 6: {
v0_1 = a.SHA512_int_String_add0(argBytes1, a.SpecBytesMat[6]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 7: {
v0_1 = a.SHA512_int_String_add0(argBytes1, a.SpecBytesMat[7]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
default: {
return null;
}
}
}
int v1 = argRndKey & 7;
if(v1 == 0) {
if(argBytes1.length != 0) {
try {
MessageDigest v1_11 = MessageDigest.getInstance("MD2");
v1_11.update(argBytes1);
byte[] v0_13 = v1_11.digest();
char[] v1_12 = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
char[] v2_1 = new char[v0_13.length * 2];
int v3_5 = v0_13.length;
int v4_5 = 0;
int v5_1 = 0;
while(v4_5 < v3_5) {
int v6_1 = v0_13[v4_5];
int v8_1 = v5_1 + 1;
v2_1[v5_1] = v1_12[v6_1 >>> 4 & 15];
v5_1 = v8_1 + 1;
v2_1[v8_1] = v1_12[v6_1 & 15];
++v4_5;
}
v0_1 = new String(v2_1);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
catch(NoSuchAlgorithmException v0_12) {
v0_12.printStackTrace();
return null;
}
}
}
else if(v1 == 1) {
if(argBytes1.length != 0) {
try {
MessageDigest v1_9 = MessageDigest.getInstance("MD5");
v1_9.update(argBytes1);
byte[] v0_11 = v1_9.digest();
char[] v1_10 = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
char[] v2 = new char[v0_11.length * 2];
int v3_4 = v0_11.length;
int v4_4 = 0;
int v5 = 0;
while(v4_4 < v3_4) {
int v6 = v0_11[v4_4];
int v8 = v5 + 1;
v2[v5] = v1_10[v6 >>> 4 & 15];
v5 = v8 + 1;
v2[v8] = v1_10[v6 & 15];
++v4_4;
}
v0_1 = new String(v2);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
catch(NoSuchAlgorithmException v0_10) {
v0_10.printStackTrace();
return null;
}
}
}
else if(v1 == 2) {
if(argBytes1.length != 0) {
try {
MessageDigest v1_7 = MessageDigest.getInstance("SHA-1");
v1_7.update(argBytes1);
byte[] v0_9 = v1_7.digest();
StringBuilder v1_8 = new StringBuilder();
int v3_3 = v0_9.length;
int v4_3;
for(v4_3 = 0; v4_3 < v3_3; ++v4_3) {
v1_8.append(String.format("%02x", ((byte)v0_9[v4_3])));
}
v0_1 = v1_8.toString();
return v0_1 == null ? null : v0_1.substring(0, 16);
}
catch(NoSuchAlgorithmException v0_8) {
v0_8.printStackTrace();
return null;
}
}
}
else if(v1 == 3) {
if(argBytes1.length != 0) {
try {
MessageDigest v1_5 = MessageDigest.getInstance("SHA-224");
v1_5.update(argBytes1);
byte[] v0_7 = v1_5.digest();
StringBuilder v1_6 = new StringBuilder();
int v3_2 = v0_7.length;
int v4_2;
for(v4_2 = 0; v4_2 < v3_2; ++v4_2) {
v1_6.append(String.format("%02x", ((byte)v0_7[v4_2])));
}
v0_1 = v1_6.toString();
return v0_1 == null ? null : v0_1.substring(0, 16);
}
catch(NoSuchAlgorithmException v0_6) {
v0_6.printStackTrace();
return null;
}
}
}
else if(v1 == 4) {
if(argBytes1.length != 0) {
try {
MessageDigest v1_3 = MessageDigest.getInstance("SHA-256");
v1_3.update(argBytes1);
byte[] v0_5 = v1_3.digest();
StringBuilder v1_4 = new StringBuilder();
int v3_1 = v0_5.length;
int v4_1;
for(v4_1 = 0; v4_1 < v3_1; ++v4_1) {
v1_4.append(String.format("%02x", ((byte)v0_5[v4_1])));
}
v0_1 = v1_4.toString();
return v0_1 == null ? null : v0_1.substring(0, 16);
}
catch(NoSuchAlgorithmException v0_4) {
v0_4.printStackTrace();
return null;
}
}
}
else if(v1 == 5 && argBytes1.length != 0) {
try {
MessageDigest v1_1 = MessageDigest.getInstance("SHA-384");
v1_1.update(argBytes1);
byte[] v0_3 = v1_1.digest();
StringBuilder v1_2 = new StringBuilder();
int v3 = v0_3.length;
int v4;
for(v4 = 0; v4 < v3; ++v4) {
v1_2.append(String.format("%02x", ((byte)v0_3[v4])));
}
v0_1 = v1_2.toString();
return v0_1 == null ? null : v0_1.substring(0, 16);
}
catch(NoSuchAlgorithmException v0_2) {
v0_2.printStackTrace();
return null;
}
}
return null;
}flag生成
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
30int ind4;
for(ind4 = 0; ind4 < 8; ++ind4) {
byte[] clips_i_bytes = a.hex2Bytes(clips[ind4]);
int ind5 = 0;
int rndKey1 = 0;
while(ind5 < clips_i_bytes.length) {
rndKey1 = rndKey1 << 1 | (clips_i_bytes[ind5] & 0x80) >>> 7;
clips_i_bytes[ind5] = (byte)(clips_i_bytes[ind5] & 0x7F);
++ind5;
}
if((rndKey1 >>> 3 & 1) != 0) {
int ind6;
for(ind6 = 0; ind6 < clips_i_bytes.length / 2; ++ind6) {
byte v11 = clips_i_bytes[ind6];
clips_i_bytes[ind6] = clips_i_bytes[clips_i_bytes.length - 1 - ind6]; // 把clips_i_bytes倒序一下
clips_i_bytes[clips_i_bytes.length - 1 - ind6] = v11;
}
}
flag_mat[rndKey1 & 7] = new String(clips_i_bytes);
}
StringBuilder finalFlag = new StringBuilder();
while(v3 < 8) {
finalFlag.append(flag_mat[v3]);
++v3;
}
v0_1.setText(finalFlag.toString());爆破
爆破的形式多种多样,最方便的当然是用Frida直接Hook爆破,无奈手机计算性能不够。
在PC上也不行,因为每个clip一共要尝试268435456个可能。
于是我找了个12核的工作站来跑,每秒大概可以尝试262144个可能,十多分钟就能跑出来一个clip。
开8个同时找,也能很快出结果。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
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292import java.math.BigInteger;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
// 爆破第二个clip
public class m1micXctfHaha{
public static byte[][] SpecBytesMat ={ hex2Bytes("2bf07832853b16d0339f1a43900c3481"),
hex2Bytes("35aaae7322e0e37f3ba0fbcb25981aa7"),
hex2Bytes("ccb8cc2b84ac701819b738255f518c2d"),
hex2Bytes("663b77d67c17a0a8d4f12acf35aba2b4"),
hex2Bytes("9f3a3227ce8bd38475cf317347874d11"),
hex2Bytes("c08ba12183ecabc899c3957675e28cd9"),
hex2Bytes("6d330eef823b5c741e37f978488890b1"),
hex2Bytes("9e99c54e9dcea758cecf0d4cc18a0c22")};
public static int[] rndKeys = {4,10,15,1,14,5,11,8};
public static String[] clips_i_hash_mat={ "fc7466e55fbf37b1", "78b0be39e63b6837", "c2f9c805d0442203", "c11a61bb60d79dab", "869e650ee55bd9f6", "f2dda5fc021fe2bf", "305044db48fe6174", "d6659b5e2d1059f8"};
public static String charset3 = "01234567";
public static String charset4 = "0123456789abcdef";
public static void main(String[] args) {
for(int c0=0;c0<8;c0++){
for(int c1=0;c1<16;c1++){
for(int c2=0;c2<8;c2++){
String clip_now = "" + charset3.charAt(c0)+charset4.charAt(c1)+charset3.charAt(c2);
System.out.println(clip_now);
for(int c3=0;c3<16;c3++){
for(int c4=0;c4<8;c4++){
for(int c5=0;c5<16;c5++){
for(int c6=0;c6<8;c6++){
for(int c7=0;c7<16;c7++){
String clip = ""+charset3.charAt(c0)+charset4.charAt(c1)+charset3.charAt(c2)+charset4.charAt(c3)+charset3.charAt(c4)+charset4.charAt(c5)+charset3.charAt(c6)+charset4.charAt(c7);
// String clip = "00010567";
byte[] clip_bytes = hex2Bytes(clip);
String clip_i_hash = hash_int_string_add0_0_16(10, clip_bytes);
// if(clip_i_hash==null){
// continue;
// }
// System.out.println("clip: ", clip);
// System.out.println('clip_i_hash: ', clip_i_hash);
// return ;
if(clip_i_hash.equals("78b0be39e63b6837")){
System.out.println("+++++++\nclip1: " + clip + "rndKeyi=" + 10);
System.out.println("clip_i_hash: " + clip_i_hash);
return ;
}
}
}
}
}
}
}
}
}
}
public static String hash_int_string_add0_0_16(int argRndKey, byte[] argBytes) {
String v0_1;
byte[] argBytes1 = argBytes;
if((argRndKey >>> 3 & 1) == 1) {
switch(argRndKey & 7) {
case 0: {
v0_1 = SHA512_int_String_add0(argBytes1, SpecBytesMat[0]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 1: {
v0_1 = SHA512_int_String_add0(argBytes1, SpecBytesMat[1]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 2: {
v0_1 = SHA512_int_String_add0(argBytes1, SpecBytesMat[2]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 3: {
v0_1 = SHA512_int_String_add0(argBytes1, SpecBytesMat[3]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 4: {
v0_1 = SHA512_int_String_add0(argBytes1, SpecBytesMat[4]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 5: {
v0_1 = SHA512_int_String_add0(argBytes1, SpecBytesMat[5]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 6: {
v0_1 = SHA512_int_String_add0(argBytes1, SpecBytesMat[6]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
case 7: {
v0_1 = SHA512_int_String_add0(argBytes1, SpecBytesMat[7]);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
default: {
return null;
}
}
}
int v1 = argRndKey & 7;
if(v1 == 0) {
if(argBytes1.length != 0) {
try {
MessageDigest v1_11 = MessageDigest.getInstance("MD2");
v1_11.update(argBytes1);
byte[] v0_13 = v1_11.digest();
char[] v1_12 = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
char[] v2_1 = new char[v0_13.length * 2];
int v3_5 = v0_13.length;
int v4_5 = 0;
int v5_1 = 0;
while(v4_5 < v3_5) {
int v6_1 = v0_13[v4_5];
int v8_1 = v5_1 + 1;
v2_1[v5_1] = v1_12[v6_1 >>> 4 & 15];
v5_1 = v8_1 + 1;
v2_1[v8_1] = v1_12[v6_1 & 15];
++v4_5;
}
v0_1 = new String(v2_1);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
catch(NoSuchAlgorithmException v0_12) {
v0_12.printStackTrace();
return null;
}
}
}
else if(v1 == 1) {
if(argBytes1.length != 0) {
try {
MessageDigest v1_9 = MessageDigest.getInstance("MD5");
v1_9.update(argBytes1);
byte[] v0_11 = v1_9.digest();
char[] v1_10 = new char[]{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
char[] v2 = new char[v0_11.length * 2];
int v3_4 = v0_11.length;
int v4_4 = 0;
int v5 = 0;
while(v4_4 < v3_4) {
int v6 = v0_11[v4_4];
int v8 = v5 + 1;
v2[v5] = v1_10[v6 >>> 4 & 15];
v5 = v8 + 1;
v2[v8] = v1_10[v6 & 15];
++v4_4;
}
v0_1 = new String(v2);
return v0_1 == null ? null : v0_1.substring(0, 16);
}
catch(NoSuchAlgorithmException v0_10) {
v0_10.printStackTrace();
return null;
}
}
}
else if(v1 == 2) {
if(argBytes1.length != 0) {
try {
MessageDigest v1_7 = MessageDigest.getInstance("SHA-1");
v1_7.update(argBytes1);
byte[] v0_9 = v1_7.digest();
StringBuilder v1_8 = new StringBuilder();
int v3_3 = v0_9.length;
int v4_3;
for(v4_3 = 0; v4_3 < v3_3; ++v4_3) {
v1_8.append(String.format("%02x", ((byte)v0_9[v4_3])));
}
v0_1 = v1_8.toString();
return v0_1 == null ? null : v0_1.substring(0, 16);
}
catch(NoSuchAlgorithmException v0_8) {
v0_8.printStackTrace();
return null;
}
}
}
else if(v1 == 3) {
if(argBytes1.length != 0) {
try {
MessageDigest v1_5 = MessageDigest.getInstance("SHA-224");
v1_5.update(argBytes1);
byte[] v0_7 = v1_5.digest();
StringBuilder v1_6 = new StringBuilder();
int v3_2 = v0_7.length;
int v4_2;
for(v4_2 = 0; v4_2 < v3_2; ++v4_2) {
v1_6.append(String.format("%02x", ((byte)v0_7[v4_2])));
}
v0_1 = v1_6.toString();
return v0_1 == null ? null : v0_1.substring(0, 16);
}
catch(NoSuchAlgorithmException v0_6) {
v0_6.printStackTrace();
return null;
}
}
}
else if(v1 == 4) {
if(argBytes1.length != 0) {
try {
MessageDigest v1_3 = MessageDigest.getInstance("SHA-256");
v1_3.update(argBytes1);
byte[] v0_5 = v1_3.digest();
StringBuilder v1_4 = new StringBuilder();
int v3_1 = v0_5.length;
int v4_1;
for(v4_1 = 0; v4_1 < v3_1; ++v4_1) {
v1_4.append(String.format("%02x", ((byte)v0_5[v4_1])));
}
v0_1 = v1_4.toString();
return v0_1 == null ? null : v0_1.substring(0, 16);
}
catch(NoSuchAlgorithmException v0_4) {
v0_4.printStackTrace();
return null;
}
}
}
else if(v1 == 5 && argBytes1.length != 0) {
try {
MessageDigest v1_1 = MessageDigest.getInstance("SHA-384");
v1_1.update(argBytes1);
byte[] v0_3 = v1_1.digest();
StringBuilder v1_2 = new StringBuilder();
int v3 = v0_3.length;
int v4;
for(v4 = 0; v4 < v3; ++v4) {
v1_2.append(String.format("%02x", ((byte)v0_3[v4])));
}
v0_1 = v1_2.toString();
return v0_1 == null ? null : v0_1.substring(0, 16);
}
catch(NoSuchAlgorithmException v0_2) {
v0_2.printStackTrace();
return null;
}
}
return null;
}
public static String SHA512_int_String_add0(byte[] arg2, byte[] arg3) {
try {
SecretKeySpec v1 = new SecretKeySpec(arg3, "HmacSha512");
Mac v3 = Mac.getInstance("HmacSha512");
v3.init(v1);
v3.update(arg2);
String v2_1; // hash -> BigInteger -> String
for(v2_1 = new BigInteger(1, v3.doFinal()).toString(16); v2_1.length() < 32; v2_1 = "0" + v2_1) { // hash -> BigInteger -> String
}
return v2_1;
}
catch(NoSuchAlgorithmException | InvalidKeyException v2) {
v2.printStackTrace();
return null;
}
}
public static byte[] hex2Bytes(String arg6) {
int lens = arg6.length();
byte[] v1 = new byte[lens / 2];
int ind = 0;
while(ind < lens) {
int v3 = Character.digit(arg6.charAt(ind), 16) << 4;
int v4 = Character.digit(arg6.charAt(ind + 1), 16);
if(v3 >= 0 && v4 >= 0) {
v1[ind / 2] = (byte)(v3 + v4);
ind += 2;
continue;
}
return null;
}
return v1;
}
}
mimic-xctf-studydesk
强网杯 “拟态”比赛 mobile2
当时做完了mobile1比较晚了,就没怎么细看这个题,没想到挺简单的。
flag要求长度为32,也就是32个byte,然后进行了一系列的处理。
引入了一个静态byte数组staticBytes,长度为288。对于其中的每个byte,去flag中查找下标,即ind=flag.index(staticBytes[i])
。
因为flag本身长度为32,所以0<ind<32,所以保存为5bit,总共得到1440bit。
然后分割得到180个byte,记作byteArray
,并将其与目标数组aimBytes
比较。
aimBytes
的生成比较烧脑,但还好它是输入无关的。
把代码copy出来跑一遍,可以发现生成的是圆周率3.1415926……
把pi值转换成string,去掉小数点,每相邻两个数字处理为一个byte,得到aimBytes
。
1 | BigDecimal v2_1 = new BigDecimal("1"); |
解密代码如下:
1 | pi = "314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593344612847564823378678316527120190914564856692346034861045432664821339360726024914127372458700660631558817488152092096282925409171536436789259036" |
卖瓜
中科大信安比赛
猜测后台计算公式如下:
$$
new_weight = old_weight + 6 \times b6 + 9 \times b9
$$
根据运算符的优先级,计算顺序如下:
$$
new_weight = old_weight + (6 \times b6) + (9 \times b9)
$$
PHP 64位系统下,INT最大值是9223372036854775807
,超出这个数字会被解释为float。
这里的乘法应该是用9223372036854775807
作为最大值,但是加法的最大值更大。
或者说一旦构造了一个超过9223372036854775807
的数字,系统里面的数值就被解释为float了,造成最大值变大。
总的来说,我们希望构造参数,使得 $(6 \times b6)$ 和$(9 \times b9)$偶尔溢出为负数的float,然后追加整数构造出20.
构造步骤如下:
1024819115206086201 * 9 = 9223372036854775809 ->(溢出为)-> -9223372036854775808
-9223372036854775808 + 1024819115206086200 * 9 = -8
-8 + 1024819115206086200 * 9 = 9223372036854775792
9223372036854775792 + 1024819115206086201 * 9 = 9223372036854775792 + -9223372036854775808 = -16
-16 + 6 * 6 = 20
bytecode
天津市大学生信息安全大赛
给出一条python的字节码代码,常见题型,人脑模拟逆向。可是我当时把BINARY_AND
看成BINARY_ADD
,然后没及时做出来。(恼
dis – Python字节码反汇编器
1 | Disassembly of main: |
1 | import struct |
hello.apk
“东华杯” 2021年大学生网络安全邀请赛暨第七届上海市大学生网络安全大赛
本题为常见的encode(flag, key)=cipher
的校验模式。
Java层可以得到信息:flag长度为42,key是程序的签名,校验函数是StringFromJNI
,在native层。
1 | public void onClick(View arg7) { |
在native层,主要对flag进行了两次处理,最后与目标数组进行比较。
因为这里使用的是arm64,所以稍微有些陌生,需要阅读汇编代码。
先是对flag进行抑或加密,从下标1开始。抑或密钥来自签名sign,从sign的354下标开始,每次向后移动27。
然后对flag进行循环位移操作,这里使用了一些向量操作函数,但简单来说就是对flag的每个byte向右循环移动3位。
然后把flag与cipher进行比较,cipher的具体值可从ida中导出。
此外,sign的值可以通过安装运行从Log中获取。也可以使用JEB等工具导出certificate
文件,二进制数据即为signatures。
虽然apk中android:testOnly=true
,但是可以通过adb -t install xxx.apk
安装。(感谢@Guohong的提示)
1 | jstring __fastcall Java_com_example_hello_MainActivity_stringFromJNI(JNIEnv *env, __int64 thiz, __int64 input, __int64 sign) |
解密脚本
1 | sign = "308202e4308201cc020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b30090603550406130255533020170d3231303330363134333034385a180f32303531303232373134333034385a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330820122300d06092a864886f70d01010105000382010f003082010a0282010100cbf2b09e4308ebb459e8841e5a7b920497fef2b349e80648f7eb35f48d40a75e7ce7945b8b42d197bec0bf177e6c9899ed707dcc4a726cb14c1a69b0c4a02474806fa73cfb10e10f7b1665021c24762b6edad65ca63cea3c72e0d4e4ca3f98301173eec3254337af1f5a11f779ecbe04d1b74d53f5835e011222155a56f97e00d75374cd93080dfa087cd356a99fe1eebf5d6d5e31846aad5252c3a17a4656e2e210ce1c7aa4d147fb8cf440a50add61bbb2ec299a2e0dab0b4504796ac3a899da553ab1d83576691ab23409d18398014b3b5eaf12e83f4d99aa09e1e4e4cae133530730c1133da2b3dee37b58eb1a5795b221ec5a8830731a41167d295f9e1b0203010001300d06092a864886f70d010105050003820101000e4740235e9cf2be33de3e06d777139cbbc5cf0622285c17da04697b8067318aaf8df0fbb4d3166f293ea15aa2592f06eb6929af063722ac9f30ad85e2c087564931d6ac65fcd5fbc864b3dc9841e039c6e1d5fbc5c2f8adf90a547bc4ebc07d387914db24451c2cc89925359bd3bb0750c7aabf9d743b1893e98bbc8ff74b24fc0b4be2dbaaf1c917bba01496d0617ffc3a4a8b7a6e79a3036298a6ebf57bb00001e43a0b242864eebb0fcec9e323144d4447c878430f18e6e358ad97566fa04d1f07b171c1476c9af5a1eba0bf6616e219c0b9e1299d09fecded24a880397f92e0f99d8951228c7770c184fd77adff943bfc8b6aa524c5f0a6d7686fe35486" |