几个Android逆向

记录最近做的一些Android逆向题目。

Accelerate-Time

华为内部CTF

进去提示只能在固定的时间才能登录,于是先hook this.hour=4。

image-20210820113557084

跟着走发现需要验证username和password。
逻辑比较长且复杂,跟着Password View的监听函数一步步跟找到校验username和password的逻辑。

image-20210820143926398

关注password的监听事件,在这里可以逆向得到username=Android和password=Greatly。

image-20210820114137957

username和password得到验证之后,关注login按钮的监听事件,但是最终还是进入到上述的校验逻辑中。
接着貌似验证过程就结束了。虽然在onCreate()之后就能发现另外一段校验函数,但是这个函数是如何调用的呢?

image-20210820144400989

关注一开始设置的observer,根据LoginActivity.onCreate.2的构造函数,观察的对象就是传进去的当前的LoginActivity。
进一步的,如果当前的LoginActivity发生了变化,会调用到updateUiWithUser()进行二次验证。

image-20210820144857352

在login按钮的点击事件中,会进入下面的函数。
Result oLoggedInUser是用户名和密码校验的返回结果,这里检查是否返回了success,如果是,则把this._LoginResult(MutableLiveData类)设置success的子类。这样的修改会被观察者模式发觉,因此调用回调函数onChanged并进一步调用showLoginFailed()或者updateUiWithUser()。

image-20210820145309550

仔细观察updateUiWithUser()函数,先构造字符串hour+minute+second计算hash值timeMD5,然后构造字符串flag{+timeMD5+}+username+password
但是需要注意的是,username和password是直接使用的其对应的EditText对象,所以拼接的不是Android+Greatly

1
2
3
4
5
6
7
8
9
10
private final void updateUiWithUser(LoggedInUserView arg3) {
Intrinsics.checkExpressionValueIsNotNull(this.getString(0x7F0D0041), "getString(R.string.welcome)"); // string:welcome "Welcome !"
arg3.getDisplayName();
if(Intrinsics.areEqual(LoginActivityKt.encodeMD5("flag{" + LoginActivityKt.encodeMD5(String.valueOf(this.hour) + String.valueOf(this.minute) + String.valueOf(this.second)) + "}" + ((EditText)this._$_findCachedViewById(id.username)) + ((EditText)this._$_findCachedViewById(id.password))), this.getString(0x7F0D002E))) { // string:code "1a9852e856816224"
Toast.makeText(this.getApplicationContext(), "Congulations, You got the secert code", 1).show();
return;
}

Toast.makeText(this.getApplicationContext(), "Yeah, you are logged in but the code is still hidden under the mist", 1).show();
}

虽然非常奇怪,但是不管是smali代码还是hook LoginActivitykt.encodeMD5()的输入参数,都证明确实直接拼接了EditText对象。

image-20210820150709400 image-20210820150729060

解密的思路非常简单,直接爆破时间,计算24*60*60次。
如果按照flag{MD5(hour+minute+second)}AndroidGreatly来计算,可以得到时间为4:35:23,且hash值满足要求。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import hashlib

aim = "1a9852e856816224"

for h in range(0,25):
for m in range(0,61):
for s in range(0,61):
timebytes = (str(h) + str(m) +str(s)).encode()
timehash = hashlib.md5(timebytes).hexdigest()
thehash = hashlib.md5( ("flag{" + timehash[8:24] + "}AndroidGreatly").encode() ).hexdigest()
if(thehash[8:24] == aim):
print(h,m,s)
print(timehash)
print(thehash)

如果严格按照代码来,则找不出满足要求的时间。
frida爆破代码如下:

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
//覆盖updateUiWithUser(),改为爆破函数,主动调用LoginActiivtyKt.encodeMD5()。
//hook Intrinsics.areEqual(),拦截hash="1a9852e856816224"的情况
//hook LoginAcitivityKt.encodeMD5(),拦截某次hash值为"1a9852e856816224"的情况,同时打印明文字符串,验证确实直接拼接了EditText对象
function main() {
Java.perform(function x(){
console.log("long live frida");
var LoginActivityClass = Java.use("com.flag.reverse.c.ui.login.LoginActivity");
LoginActivityClass.$init.implementation = function(){//hook构造函数,预置时间为4:35:23也过不了
this.$init();
console.log("into LoginActivity() args=");
console.log(this.hour.value, this.minute.value, this.second.value);
this.hour.value = 4;
this.minute.value = 35;
this.second.value = 23;

console.log(this.hour.value, this.minute.value, this.second.value);
}

var LoginActivityKt = Java.use("com.flag.reverse.c.ui.login.LoginActivityKt");
var intrics = Java.use("kotlin.jvm.internal.Intrinsics");
intrics.areEqual.overload("java.lang.Object", "java.lang.Object").implementation = function(s1, s2){
// console.log(s1,s2);
if(s1 == s2){
console.log("into areEqual() args=", s1, s2);
}
return this.areEqual(s1, s2);
}

var stop = false;
LoginActivityKt.encodeMD5.overload("java.lang.String").implementation = function(plain){
var hashstr = this.encodeMD5(plain);
if(hashstr == "1a9852e856816224"){
stop = true;
console.log(plain);
}
return hashstr;
}

LoginActivityClass.updateUiWithUser.overload("com.flag.reverse.c.ui.login.LoggedInUserView").implementation = function(v){
var last_v;
for(var h=0;h<=24;h++){
for(var m=0;m<=60;m++){
console.log(h,m);
for(var s=0;s<=60;s++){
if(!stop){
this.hour.value = h;
this.minute.value = m;
this.second.value = s;
last_v = this.updateUiWithUser(v);
}
else{
console.log(this.hour.value, this.minute.value, this.second.value);
return last_v;
}
}
}
}
}

})
}
setImmediate(main)

Time-Machine

华为内部CTF

这道题做得比较久,中途基本想放弃了,不过还是坚持做下来了,值得给自己点赞。(雾

首先看一下Java层,还好没有太多逻辑需要厘清。
要求先后点击两个按钮并获取点击时间,要求第一个时间戳大于第二个时间戳,显然这是不能实现的。(对应题目的名字Time Machine)
可以通过Frida Hook时间类,或者直接修改smali代码,把大于改成小于即可。为了后续IDA动态调试方便,我选择了后者。

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
public class MainActivity extends AppCompatActivity {
static {
System.loadLibrary("native-lib");
}

@Override // androidx.appcompat.app.AppCompatActivity
protected void onCreate(Bundle arg7) {
super.onCreate(arg7);
this.setContentView(0x7F0A001C); // layout:activity_main
Button startButton = (Button)this.findViewById(0x7F070042); // id:button
Button stopButton = (Button)this.findViewById(0x7F070043); // id:button2
long[] startTime = new long[]{0L};
long[] endTime = new long[]{0L};
startButton.setOnClickListener(new View.OnClickListener() {
@Override // android.view.View$OnClickListener
public void onClick(View arg4) {
startTime[0] = System.currentTimeMillis();
}
});
stopButton.setOnClickListener(new View.OnClickListener() {
@Override // android.view.View$OnClickListener
public void onClick(View arg7) {
endTime[0] = System.currentTimeMillis();
long[] v7 = startTime;
if(v7[0] == 0L) {
Toast.makeText(MainActivity.this, "game not start ", 0).show();
return;
}

long startTime2 = v7[0];
String endTime2 = endTime[0] + " ";
if(startTime2 - endTime[0] > 1L) { //不修改或者调试过不了的条件
Bundle timeString = new Bundle();
timeString.putString("starttime", startTime2 + " ");
timeString.putString("endtime", endTime2);
Intent intent = new Intent();
intent.setClass(MainActivity.this, Main2Activity.class);
intent.putExtras(timeString);
MainActivity.this.startActivity(intent);
}
}
});
}
}

然后就输入flag并进行检查。
要求flag长度为42,格式为flag{xxxxx},flag前三位是e25,第三到第八位的md5值为1E862D87DB3293B81C7D2934577A22FA,用somd5解出来是be952
然后把flag剩余的部分交给check函数传到native层去检查。

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
protected void onCreate(Bundle bundle) {
super.onCreate(bundle);
this.setContentView(0x7F0A001D); // layout:activity_main2
Bundle v3 = this.getIntent().getExtras();
v0.toString();
v3.getString("endtime");
EditText mEditText = (EditText)this.findViewById(0x7F070056); // id:editText
((Button)this.findViewById(0x7F070044)).setOnClickListener(new View.OnClickListener() { // id:button3
@Override // android.view.View$OnClickListener
public void onClick(View arg6) {
String inputFlag = mEditText.getText().toString();
if(inputFlag.length() != 42) {
Toast.makeText(Main2Activity.this, "Wrong!!!", 0).show();
return;
}

if((inputFlag.substring(0, 5).equals("flag{")) && (inputFlag.substring(41).equals("}"))) {
String flagStr = inputFlag.substring(5, 41);
if((flagStr.substring(0, 3).equals("e25")) && (Main2Activity.this.md5(flagStr.substring(3, 8)).equals("1E862D87DB3293B81C7D2934577A22FA"))) {
Toast.makeText(Main2Activity.this, Main2Activity.this.Check(flagStr.substring(8)), 0).show();
return;
}
}
else {
Toast.makeText(Main2Activity.this, "wrong~~", 1).show();
}
}
});
}

IDA分析so文件,本着一直以来的习惯先检查init_arrayJNI_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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __fastcall sub_8ED24CF8(JNIEnv *env, int jthiz, int inputFlag)
{
const char *v4; // r0
int v5; // r0
const char *v6; // r1
int v7; // r4
const char *flag_base64; // [sp+0h] [bp-18h] BYREF
char v10[8]; // [sp+4h] [bp-14h] BYREF

v4 = (*env)->GetStringUTFChars(env, inputFlag, 0);
ooo000(&flag_base64, v4);// 换表base64编码
v5 = check(flag_base64);// 抑或校验
v6 = "tql!!!!";
if ( v5 != 1 )
v6 = "啊这。。";
v7 = ((*env)->NewStringUTF)(env, v6, "啊这。。");
sub_8ED27710(flag_base64 - 12, v10);
return v7;
}

先看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
2
index = base64table.index(base64cipher[i])
int6 = (index & 0x38) | ((index>>3) ^ (index&0x7))
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
int __fastcall ooo000(char *retString, const char *flag)
{
int v4; // r0
size_t v5; // r0
unsigned int v6; // r10
int ind; // r8
unsigned __int8 v8; // r1
const char *v9; // r0
unsigned __int8 v10; // r2
unsigned int v11; // r0
int v12; // r6
unsigned int v13; // r10
unsigned int v14; // r0
int v15; // r3
unsigned int v16; // r5
unsigned int v17; // r0
int v18; // r1
int int6_4[10]; // [sp+8h] [bp-28h]

int6_4[0] = 0;
v4 = sub_8ED28214(retString, &unk_8ED370CE);
sub_8ED24488(v4);
v5 = strlen(flag);
if ( v5 < 3 )
{
v12 = 0;
}
else
{
v6 = 0;
do
{
ind = 0;
v8 = flag[3 * v6];
v9 = &flag[3 * v6];
LOBYTE(int6_4[0]) = v8 >> 2;
v10 = v9[1];
BYTE1(int6_4[0]) = (16 * v8) & 0x30 | (v10 >> 4);
v11 = *(v9 + 2);
BYTE2(int6_4[0]) = (v11 >> 6) & 0xC3 | (4 * (v10 & 0xF));
HIBYTE(int6_4[0]) = v11 & 0x3F;// 相当明显的base64操作
do
{
appendString(retString, base64table[*(int6_4 + ind) ^ (*(int6_4 + ind) >> 3)]);
++ind;
}
while ( ind < 4 );
v5 = strlen(flag);
++v6;
}
while ( v6 < v5 / 3 );
v12 = 3 * v6;
}
v13 = v5 % 3;
if ( v5 % 3 ) // 这里在补上==或=
{
v14 = flag[v12];
v15 = (16 * v14) & 0x30;
LOBYTE(int6_4[0]) = flag[v12] >> 2;
v16 = v15 | (flag[v12 + 1] >> 4);
BYTE1(int6_4[0]) = v15 | (flag[v12 + 1] >> 4);
appendString(retString, base64table[(v14 >> 5) ^ (v14 >> 2)]);
appendString(retString, base64table[v16 ^ (v16 >> 3)]);
if ( (v13 & 3) == 2 )
{
v17 = (4 * flag[v12 + 1]) & 0x3C;
v18 = v17 | (flag[v12 + 2] >> 6);
BYTE2(int6_4[0]) = v17 | (flag[v12 + 2] >> 6);
appendString(retString, base64table[v18 ^ (v17 >> 3)]);
sub_8ED26168(retString, "=", 1);
}
else if ( (v13 & 3) == 1 )
{
sub_8ED26168(retString, "==", 2);
}
}
return _stack_chk_guard - int6_4[2];
}

于是得到一个base64bytes数组,在check函数中对它进行校验。
看起来这里的校验逻辑非常复杂,但是不需要一一厘清,只需要关注有意义的那两行代码就好。
根据经验加一点点合理的推测,就是先使用xor_key数组把刚刚得到的base64bytes数组抑或成bytesAfterXor数组,然后和aimBytes比较是否一致。
aimBytes数组的可以直接提取,xor_key存在于bss段,需要确定生成逻辑。但是因为我是动态调试的,所以直接从内存里面dump出来的。
有了这两个数据的具体值,可以还原base64bytes,再根据base64算法,可以还原出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
int __fastcall check(const char *base64bytes)
{
int v1; // r10
int lens; // r5
int i; // r6
int v5; // r1
int v6; // r0
int v7; // r0
int v8; // lr
int v9; // r3
int v10; // r0
int v12; // [sp+4h] [bp-D4h]
int ind; // [sp+Ch] [bp-CCh]
int v14; // [sp+10h] [bp-C8h]
int v15; // [sp+14h] [bp-C4h]
int aimBytes[40]; // [sp+18h] [bp-C0h] BYREF

qmemcpy(aimBytes, off_8ED37180, sizeof(aimBytes));
lens = strlen(base64bytes);
for ( i = 0; ; i = v15 + 1 )
{
v5 = 517013752;
while ( v5 == 517013752 )
{
v15 = i;
v5 = -1119517027;
if ( i < 256 )
v5 = 1985024802;
}
if ( v5 == -1119517027 )
break;
dword_8ED3B0E8[v15] = v15;
}
sub_8ED24684();
v6 = sub_8ED24978();
v7 = sub_8ED24A18(v6, lens);
v8 = 2031990082;
v9 = 0;
v12 = v7;
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( v8 <= -44937610 )
{
if ( v8 > -1227108057 )
{
if ( v8 == -1227108056 )
{
v10 = 1;
LABEL_27:
v12 = v10;
v8 = 1736361794;
}
else
{
v8 = 1517945422;
if ( bytesAfterXor[v14] != aimBytes[v14] )
v8 = 1454719996;
}
}
else if ( v8 == -1972997478 )
{
v14 = v1;
v8 = -1227108056;
if ( v1 < 40 )
v8 = -96183791;
}
else
{
v8 = 0x791DB542;
bytesAfterXor[ind] = xor_key[4 * ind] ^ base64bytes[ind] ^ ind;
v9 = ind + 1;
}
}
if ( v8 > 1517945421 )
break;
if ( v8 != -44937609 )
{
v10 = 0;
goto LABEL_27;
}
v1 = 0;
v8 = -1972997478;
}
if ( v8 != 1517945422 )
break;
v8 = -1972997478;
v1 = v14 + 1;
}
if ( v8 != 2031990082 )
return v12;
ind = v9;
v8 = -44937609;
if ( v9 < 40 )
v8 = -1516589019;
}
}
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
# flag还原代码
base64Bytes = [0 for i in range(40)]

base64table=[0x5A, 0x4F, 0x36, 0x4B, 0x71, 0x37, 0x39, 0x4C, 0x26,
0x43, 0x50, 0x57, 0x76, 0x4E, 0x6F, 0x70, 0x7A, 0x51,
0x66, 0x67, 0x68, 0x44, 0x52, 0x53, 0x47, 0x40, 0x64,
0x69, 0x2A, 0x6B, 0x41, 0x42, 0x38, 0x72, 0x73, 0x46,
0x65, 0x77, 0x78, 0x6C, 0x6D, 0x2B, 0x2F, 0x75, 0x35,
0x61, 0x5E, 0x32, 0x59, 0x74, 0x54, 0x4A, 0x55, 0x56,
0x45, 0x6E, 0x30, 0x24, 0x48, 0x49, 0x33, 0x34, 0x79, 0x23]

key= [0xF8, 0xA7, 0x9E, 0xA0, 0xC3,
0x66, 0x65, 0x21, 0xD5, 0xE0,
0x40, 0xA5, 0x80, 0x87,
0x9B, 0xD2, 0xCD, 0x30, 0xBB,
0x6A, 0x80, 0x21, 0xD1, 0xD0,
0xE5, 0x7F, 0xB2, 0xE1,
0xE9, 0xC4, 0x73, 0x96, 0x90,
0x82, 0x21, 0x11, 0x47, 0xD2,
0xC8, 0x44]

aimBytes=[0xA8, 0xCE, 0xCE, 0xD7, 0xB1, 0x5A, 0x20, 0x4B, 0xAB, 0xA2, 0x23, 0xFA,
0xFC, 0xF0, 0xDF, 0xA5, 0xB4, 0x77, 0xE6, 0x41, 0xC4, 0x65, 0x84, 0x91,
0x8B, 0x0A, 0xE6, 0xAE, 0xBB, 0xB5, 0x37, 0xFD, 0xC0, 0xCB, 0x72, 0x78,
0x13, 0x91, 0xD3, 0x5E]


def printHexInList(l):
for i in l:
print(bin(i)[2:], end=' ')
print()

for ind in range(0,40):
base64Bytes[ind] = aimBytes[ind] ^ key[ind] ^ ind
# print(bin(base64Bytes[ind]),end=', ')
# print()


def guess3Bytes(int6_4byte):
o_int6_4bytes=[0,0,0,0]
for ind in range(0, len(int6_4byte)):
try:
int6_4byte[ind] = base64table.index(int6_4byte[ind])
except:
int6_4byte[ind] = 0
for i in range(4):
o_int6_4bytes[i] = (int6_4byte[i] & 0x38) | ((int6_4byte[i] ^ (int6_4byte[i] >> 3)) & 0x7 )
o_3bytes = [0,0,0]
o_3bytes[0] = ((o_int6_4bytes[0] << 2) | (o_int6_4bytes[1] >> 4)) & 0xff
o_3bytes[1] = ((o_int6_4bytes[1] << 4) | (o_int6_4bytes[2] >> 2)) & 0xff
o_3bytes[2] = ((o_int6_4bytes[2] << 6) | (o_int6_4bytes[3] )) & 0xff

# printHexInList(int6_4byte)
# printHexInList(o_int6_4bytes)
# printHexInList(o_3bytes)
# print('---------')
return o_3bytes

print("flag{e25be952", end='')
for ind in range(0,40,4):
g = guess3Bytes(base64Bytes[ind: ind+4])
print(chr(g[0])+chr(g[1])+chr(g[2]), end='')
print("}")

一些不足:
没有对反调试代码逻辑进行细致的梳理,想着是能过就好。
xor_key在bss段,在程序运行过程中生成,没有梳理生成算法,动态调试连过去直接dump内存得到的。

mimic-xctf-hahahaha

强网杯 “拟态”比赛 mobile1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
clip0: 5f384050   -> 5fb84050
clip_i_hash: fc7466e55fbf37b1
clip1: 35315f48 -> b531df48
clip_i_hash: 78b0be39e63b6837
clip2: 7d503131 -> fdd0b1b1 (rndKeyi=15
clip_i_hash: c2f9c805d0442203
clip3: 7b484035 -> 7b4840b5
clip_i_hash: c11a61bb60d79dab
clip4: 435f3535 -> c3dfb535
clip_i_hash: 869e650ee55bd9f6
clip5: 50314e33 -> 50b14eb3
clip_i_hash: f2dda5fc021fe2bf
clip6: 3348375f -> b348b7df (rndKeyi=11
clip_i_hash: 305044db48fe6174
clip7: 47414c46 -> c7414c46 (rndKeyi=8
clip_i_hash: d6659b5e2d1059f8

该题先检查输入是否满足要求,然后基于输入数据生成flag。不过检查逻辑都是基于hash的,所以不能解密得到原文。
不过输入的8段数据(8个clip)的检查是相互独立的,所以可以考虑爆破。

接下来分析程序逻辑:

  1. 密钥准备:
    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
    33
    a.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 {
    @Override // 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
    }
    }
  2. 输入格式检查
    先获取输入的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
    53
    clipsi[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;
    }
  3. 格式转换与校验
    既然每个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
    229
    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;
    }

    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;
    }
  4. 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
    30
    int 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());
  5. 爆破
    爆破的形式多种多样,最方便的当然是用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
    292
    import 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;
    }
    }
    Snipaste_2021-00-52-201

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
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
BigDecimal v2_1 = new BigDecimal("1");
BigDecimal v3_1 = new BigDecimal("0");
BigDecimal v4_2 = new BigDecimal("6");
MathContext MathCtx1 = new MathContext(360, RoundingMode.HALF_UP); // (digits, roundingMode)
MathContext MathCtx2 = new MathContext(720, RoundingMode.HALF_UP);
int inds = 0;
while(true) {
v2_1 = a.a(new BigDecimal("2").subtract(a.a(new BigDecimal(4).subtract(v2_1.multiply(v2_1, MathCtx2), MathCtx2), MathCtx2)), MathCtx2);
v4_2 = new BigDecimal("2").multiply(v4_2, MathCtx1);
v10 = new BigDecimal("0.5").multiply(v2_1.multiply(v4_2, MathCtx1), MathCtx1);
if(v10.compareTo(v3_1) == 0) {
break;
}

++inds;
if(inds % 30 == 0) {
StringBuilder v3_2 = b.a.a.a.a.e("running: ");
v3_2.append(inds / 6);
v3_2.append("%");
Log.i("StudyDesk:", v3_2.toString());
}

v3_1 = v10;
}

Log.i("StudyDesk:", "running: 100%");
String aimString = v10.toString().replace(".", "");

解密代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pi = "314159265358979323846264338327950288419716939937510582097494459230781640628620899862803482534211706798214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196442881097566593344612847564823378678316527120190914564856692346034861045432664821339360726024914127372458700660631558817488152092096282925409171536436789259036"
aimBytes = []
for ind in range(0, len(pi), 2):
aimBytes.append(int(pi[ind:ind+2]))

staticBytes = [0x73, 0x6F, 43, 0x72, 0x74, 45, 0x30, 36, 84, 98, 89, 36, 38, 66, 38, 43, 84, 0x79, 50, 101, 101, 43, 100, 87, 69, 0x6F, 51, 66, 89, 49, 69, 51, 101, 51, 53, 0x74, 45, 98, 98, 0x72, 50, 36, 98, 50, 85, 85, 85, 107, 66, 36, 53, 51, 0x6F, 0x72, 89, 89, 66, 50, 33, 66, 0x5F, 66, 101, 0x79, 0x5F, 0x40, 33, 66, 50, 0x40, 85, 85, 45, 43, 36, 50, 0x74, 0x30, 85, 0x73, 0x5F, 0x40, 49, 0x72, 50, 101, 101, 51, 51, 43, 53, 51, 53, 51, 85, 50, 0x40, 0x79, 53, 36, 0x40, 69, 89, 98, 45, 0x6F, 101, 36, 97, 66, 100, 0x30, 0x73, 97, 0x30, 36, 0x6F, 101, 50, 0x5F, 49, 0x30, 0x40, 89, 0x74, 85, 0x30, 85, 0x73, 89, 43, 89, 97, 0x30, 89, 0x72, 97, 100, 38, 50, 0x74, 51, 98, 0x75, 0x5F, 50, 0x74, 0x73, 0x6F, 84, 98, 89, 69, 0x6F, 100, 0x30, 0x6F, 98, 89, 0x72, 0x40, 50, 36, 66, 89, 101, 0x72, 51, 84, 51, 50, 36, 38, 0x40, 0x30, 53, 51, 0x30, 49, 97, 0x74, 89, 101, 85, 97, 66, 84, 97, 45, 43, 100, 89, 45, 0x30, 0x73, 0x30, 0x40, 97, 100, 98, 51, 100, 0x6F, 0x73, 50, 53, 101, 66, 101, 0x6F, 0x75, 50, 45, 0x5F, 51, 82, 50, 89, 87, 101, 50, 89, 0x30, 89, 101, 43, 89, 36, 38, 61, 101, 0x40, 84, 89, 0x5F, 66, 0x74, 49, 0x40, 87, 97, 43, 0x5F, 0x73, 43, 0x30, 89, 45, 84, 89, 33, 89, 107, 53, 85, 0x30, 98, 98, 0x5F, 50, 107, 66, 101, 0x6F, 51, 97, 33, 66, 97, 0x75, 51, 0x74, 51, 97, 0x40, 89, 107, 98, 51, 69, 0x40, 73, 0x5F, 0x30, 85, 0x74, 0x30, 97]

def bit8(x):
return bin(x)[2:].rjust(8,'0')

flagTable = [0 for i in range(100)]
sb_index = 0
for ind in range(0, 180, 5):
binString = bit8(aimBytes[ind])+bit8(aimBytes[ind+1])+bit8(aimBytes[ind+2])+bit8(aimBytes[ind+3])+bit8(aimBytes[ind+4])
for indd in range(0, len(binString), 5):
index = int(binString[indd:indd+5], 2)
flagTable[index] = staticBytes[sb_index]
sb_index += 1

print("\nflag: ", end='')
for i in flagTable:
print(chr(i), end='')

卖瓜

中科大信安比赛

猜测后台计算公式如下:

$$
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

image-20211026132532729

bytecode

天津市大学生信息安全大赛

给出一条python的字节码代码,常见题型,人脑模拟逆向。可是我当时把BINARY_AND看成BINARY_ADD,然后没及时做出来。(恼
dis – Python字节码反汇编器

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
Disassembly of main:
27 0 LOAD_CONST 1 (305419896)
2 LOAD_CONST 2 (2271560481)
4 LOAD_CONST 3 (2427178479)
6 LOAD_CONST 4 (4275878409)
8 BUILD_LIST 4
10 STORE_FAST 0 (key)

28 12 LOAD_CONST 5 (3888592564)
14 LOAD_CONST 6 (3737879155)
16 BUILD_LIST 2
18 LOAD_CONST 7 (4063334467)
20 LOAD_CONST 8 (2214487552)
22 BUILD_LIST 2
24 LOAD_CONST 9 (2420456096)
26 LOAD_CONST 10 (1529806583)
28 BUILD_LIST 2
30 LOAD_CONST 11 (2576007368)
32 LOAD_CONST 12 (2328179940)
34 BUILD_LIST 2
36 LOAD_CONST 13 (1665686107)
38 LOAD_CONST 14 (1748819876)
40 BUILD_LIST 2
42 BUILD_LIST 5
44 STORE_FAST 1 (arr)

29 46 LOAD_GLOBAL 0 (input)
48 LOAD_CONST 15 ('please input your secret key: ')
50 CALL_FUNCTION 1
52 STORE_FAST 2 (flag)

31 54 BUILD_LIST 0
56 STORE_FAST 3 (encry)

32 58 BUILD_LIST 0
60 STORE_FAST 4 (encryted)

33 62 LOAD_GLOBAL 1 (range)
64 LOAD_CONST 16 (0)
66 LOAD_GLOBAL 2 (len)
68 LOAD_FAST 2 (flag)
70 CALL_FUNCTION 1
72 LOAD_CONST 17 (8)
74 CALL_FUNCTION 3
76 GET_ITER
>> 78 FOR_ITER 112 (to 192)
80 STORE_FAST 5 (i)

34 82 LOAD_FAST 3 (encry)
84 LOAD_METHOD 3 (append)
86 LOAD_GLOBAL 4 (struct)
88 LOAD_METHOD 5 (unpack)
90 LOAD_CONST 18 ('<I')
92 LOAD_FAST 2 (flag)
94 LOAD_FAST 5 (i)
96 LOAD_FAST 5 (i)
98 LOAD_CONST 19 (4)
100 BINARY_ADD
102 BUILD_SLICE 2
104 BINARY_SUBSCR
106 LOAD_METHOD 6 (encode)
108 LOAD_CONST 20 ('utf-8')
110 CALL_METHOD 1
112 CALL_METHOD 2
114 LOAD_CONST 16 (0)
116 BINARY_SUBSCR
118 CALL_METHOD 1
120 POP_TOP

35 122 LOAD_FAST 3 (encry)
124 LOAD_METHOD 3 (append)
126 LOAD_GLOBAL 4 (struct)
128 LOAD_METHOD 5 (unpack)
130 LOAD_CONST 18 ('<I')
132 LOAD_FAST 2 (flag)
134 LOAD_FAST 5 (i)
136 LOAD_CONST 19 (4)
138 BINARY_ADD
140 LOAD_FAST 5 (i)
142 LOAD_CONST 17 (8)
144 BINARY_ADD
146 BUILD_SLICE 2
148 BINARY_SUBSCR
150 LOAD_METHOD 6 (encode)
152 LOAD_CONST 20 ('utf-8')
154 CALL_METHOD 1
156 CALL_METHOD 2
158 LOAD_CONST 16 (0)
160 BINARY_SUBSCR
162 CALL_METHOD 1
164 POP_TOP

36 166 LOAD_GLOBAL 7 (encrypt)
168 LOAD_FAST 3 (encry)
170 LOAD_FAST 0 (key)
172 CALL_FUNCTION 2
174 STORE_FAST 6 (encrypted)

37 176 LOAD_FAST 4 (encryted)
178 LOAD_METHOD 3 (append)
180 LOAD_FAST 6 (encrypted)
182 CALL_METHOD 1
184 POP_TOP

38 186 BUILD_LIST 0
188 STORE_FAST 3 (encry)
190 JUMP_ABSOLUTE 78

39 >> 192 LOAD_FAST 4 (encryted)
194 LOAD_FAST 1 (arr)
196 COMPARE_OP 2 (==)
198 POP_JUMP_IF_FALSE 210

40 200 LOAD_GLOBAL 8 (print)
202 LOAD_CONST 21 ('ok,fine~')
204 CALL_FUNCTION 1
206 POP_TOP
208 JUMP_FORWARD 8 (to 218)

42 >> 210 LOAD_GLOBAL 8 (print)
212 LOAD_CONST 22 ('sry~')
214 CALL_FUNCTION 1
216 POP_TOP
>> 218 LOAD_CONST 0 (None)
220 RETURN_VALUE


Disassembly of encrypt:
6 0 LOAD_FAST 0 (v)
2 LOAD_CONST 1 (0)
4 BINARY_SUBSCR
6 STORE_FAST 2 (v0)

7 8 LOAD_FAST 0 (v)
10 LOAD_CONST 2 (1)
12 BINARY_SUBSCR
14 STORE_FAST 3 (v1)

8 16 LOAD_CONST 1 (0)
18 STORE_FAST 4 (x)

9 20 LOAD_CONST 3 (6710886)
22 STORE_FAST 5 (delta)

10 24 LOAD_FAST 1 (k)
26 LOAD_CONST 1 (0)
28 BINARY_SUBSCR
30 STORE_FAST 6 (k0)

11 32 LOAD_FAST 1 (k)
34 LOAD_CONST 2 (1)
36 BINARY_SUBSCR
38 STORE_FAST 7 (k1)

12 40 LOAD_FAST 1 (k)
42 LOAD_CONST 4 (2)
44 BINARY_SUBSCR
46 STORE_FAST 8 (k2)

13 48 LOAD_FAST 1 (k)
50 LOAD_CONST 5 (3)
52 BINARY_SUBSCR
54 STORE_FAST 9 (k3)

14 56 LOAD_GLOBAL 0 (range)
58 LOAD_CONST 6 (32)
60 CALL_FUNCTION 1
62 GET_ITER
>> 64 FOR_ITER 108 (to 174)
66 STORE_FAST 10 (i)

15 68 LOAD_FAST 4 (x)
70 LOAD_FAST 5 (delta)
72 INPLACE_ADD
74 STORE_FAST 4 (x)

16 76 LOAD_FAST 4 (x)
78 LOAD_CONST 7 (4294967295)
80 BINARY_AND
82 STORE_FAST 4 (x)

17 84 LOAD_FAST 2 (v0)
86 LOAD_FAST 3 (v1)
88 LOAD_CONST 8 (4)
90 BINARY_LSHIFT
92 LOAD_FAST 6 (k0)
94 BINARY_ADD
96 LOAD_FAST 3 (v1)
98 LOAD_FAST 4 (x)
100 BINARY_ADD
102 BINARY_XOR
104 LOAD_FAST 3 (v1)
106 LOAD_CONST 9 (5)
108 BINARY_RSHIFT
110 LOAD_FAST 7 (k1)
112 BINARY_ADD
114 BINARY_XOR
116 INPLACE_ADD
118 STORE_FAST 2 (v0)

18 120 LOAD_FAST 2 (v0)
122 LOAD_CONST 7 (4294967295)
124 BINARY_AND
126 STORE_FAST 2 (v0)

19 128 LOAD_FAST 3 (v1)
130 LOAD_FAST 2 (v0)
132 LOAD_CONST 8 (4)
134 BINARY_LSHIFT
136 LOAD_FAST 8 (k2)
138 BINARY_ADD
140 LOAD_FAST 2 (v0)
142 LOAD_FAST 4 (x)
144 BINARY_ADD
146 BINARY_XOR
148 LOAD_FAST 2 (v0)
150 LOAD_CONST 9 (5)
152 BINARY_RSHIFT
154 LOAD_FAST 9 (k3)
156 BINARY_ADD
158 BINARY_XOR
160 INPLACE_ADD
162 STORE_FAST 3 (v1)

20 164 LOAD_FAST 3 (v1)
166 LOAD_CONST 7 (4294967295)
168 BINARY_AND
170 STORE_FAST 3 (v1)
172 JUMP_ABSOLUTE 64

21 >> 174 LOAD_FAST 2 (v0)
176 LOAD_FAST 0 (v)
178 LOAD_CONST 1 (0)
180 STORE_SUBSCR

22 182 LOAD_FAST 3 (v1)
184 LOAD_FAST 0 (v)
186 LOAD_CONST 2 (1)
188 STORE_SUBSCR

23 190 LOAD_FAST 0 (v)
192 RETURN_VALUE
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
import struct

def encrypt(v, k):
v0 = v[0]
v1 = v[1]
x = 0
delta = 0x666666
k0 = k[0]
k1 = k[1]
k2 = k[2]
k3 = k[3]
for i in range(32):
delta = (delta + x) & 0xffffffff
x = delta & 0xffffffff
v0 = v0 + ( ((v1<<4) + k0) ^ (v1 + x) ^ ( (v1>>5) + k1))
v0 = v0 & 0xffffffff
v1 = v1 + ( ((v0<<4) + k2) ^ (v0 + x) ^ ( (v0>>5) + k3))
v1 = v1 & 0xffffffff
v[0]=v0
v[1]=v1
print(v0, v1)
return v

key = [305419896, 2271560481, 2427178479, 4275878409]
arr = [[3888592564, 3737879155], [4063334467, 2214487552], [2420456096, 1529806583], [2576007368, 2328179940], [1665686107, 1748819876]]
flag = input('please input your secret key: ')
print(len(flag))
encry = []
encryted = []

for i in range(0, len(flag), 8):
encry.append(struct.unpack('<I', flag[i: i+4].encode('utf-8'))[0])
encry.append(struct.unpack('<I', flag[i+4: i+8].encode('utf-8'))[0])
encrypted = encrypt(encry, key)
encryted.append(encrypted)
encry = []

if(encryted != arr):
print("sry")
else:
print('ok, fine')


def decrypt():
for arri in arr:
v0 = arri[0]
v1 = arri[1]
for i in range(31, -1, -1):
v1 = (v1 - ( ((v0<<4) + k[2]) ^ (v0 + xx[i]) ^ ( (v0>>5) + k[3]))) & 0xffffffff
v0 = (v0 - ( ((v1<<4) + k[0]) ^ (v1 + xx[i]) ^ ( (v1>>5) + k[1]))) & 0xffffffff
v1 = v1 & 0xffffffff
v0 = v0 & 0xffffffff
# print(v0, v1)
print(struct.pack("<I", v0).decode('utf-8'), end='')
print(struct.pack("<I", v1).decode('utf-8'), end='')

xx = []
x_ = 0
delta = 0x666666
for i in range(32):
x_ = (x_ + delta) & 0xffffffff
xx.append(x_)

k = key
decrypt()

hello.apk

“东华杯” 2021年大学生网络安全邀请赛暨第七届上海市大学生网络安全大赛

本题为常见的encode(flag, key)=cipher的校验模式。
Java层可以得到信息:flag长度为42,key是程序的签名,校验函数是StringFromJNI,在native层。

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
public void onClick(View arg7) {
try {
if(MainActivity.this.input.getText().length() == 42) {
Toast v7_2 = Toast.makeText(
MainActivity.this,
MainActivity.this.stringFromJNI(
MainActivity.this.input.getText().toString(),
new hi().getSignatures(arg7)),
1);
v7_2.setGravity(0, 0, -700);
v7_2.show();
return;
}
Toast v7_3 = Toast.makeText(MainActivity.this, "Hello!", 0);
v7_3.setGravity(0, 0, -700);
v7_3.show();
}
catch(PackageManager.NameNotFoundException v7_1) { v7_1.printStackTrace(); }
catch(NoSuchAlgorithmException v7) { v7.printStackTrace(); }
}
class hi {
private Log Log;
public String getSignatures(View arg3) throws PackageManager.NameNotFoundException, NoSuchAlgorithmException {
MessageDigest.getInstance("MD5");
Signature[] signs = arg3.getContext().getPackageManager().getPackageInfo("com.example.hello", 0x40).signatures;
if(signs.length > 0) {
Signature sign = signs[0]; // 一个程序可能拥有多个签名
Log.i("hello", sign.toCharsString());
return sign.toCharsString();
}
return "this_is_your_gift!";
}
}

在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
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
jstring __fastcall Java_com_example_hello_MainActivity_stringFromJNI(JNIEnv *env, __int64 thiz, __int64 input, __int64 sign)
{
int8x16_t *inputs; // x20
const char *signs; // x0
const char *signs1; // x21
unsigned __int64 ind; // x23
unsigned int ind_p27; // w24
unsigned int v11; // w1
unsigned __int8 v12; // w9
unsigned int v13; // w11
unsigned int v14; // w12
unsigned int v15; // w13
unsigned int v16; // w14
unsigned int v17; // w15
int8x16_t v18; // q0
unsigned int v19; // w16
int8x16_t v20; // q1
unsigned int v21; // w17
__int64 ind2; // x8
const char *retStr1; // x21
jstring v24; // x19
char retStr[16]; // [xsp+0h] [xbp-50h] BYREF
char *v27; // [xsp+10h] [xbp-40h]
__int64 v28; // [xsp+18h] [xbp-38h]

v28 = *(_QWORD *)(_ReadStatusReg(ARM64_SYSREG(3, 3, 13, 0, 2)) + 40);
inputs = (int8x16_t *)(*env)->GetStringUTFChars(env, input, 0LL);
// input本为42个byte的数组,在这里被保存为一个3x(16 byte)的对象,后续通过input[0],input[1],input[2]引用。
// input[2]只有10个byte。
signs = (*env)->GetStringUTFChars(env, sign, 0LL);
if ( inputs->n128_u8[0] ) // 汇编:LDRB W8, [X20],即input[0],后续相同
{
signs1 = signs;
inputs->n128_u8[0] ^= signs[327];
if ( strlen((const char *)inputs) >= 2uLL )
{
ind = 1LL;
ind_p27 = 354;
do
{ // input[ind] = input[ind] ^ (sign[ind_p27] + ind)
inputs->n128_u8[ind] ^= signs1[ind_p27] + (_BYTE)ind;// sign的长度比较迷惑,通过Log获取
++ind;
ind_p27 += 27;
}
while ( strlen((const char *)inputs) > ind );
}
}
// inputs的长度是int8x16 bit,即16char。输入42,刚好最后10单独处理
// 简单来说,就是每个byte交换高3位和低5位的位置,或者说向右循环移动3位
v11 = (inputs[2].n128_u8[0] >> 3) & 0xFFFFE01F | (32 * inputs[2].n128_u8[0]);
v12 = (inputs[2].n128_u8[1] >> 3) & 0x1F | (32 * inputs[2].n128_u8[1]);
v13 = (inputs[2].n128_u8[3] >> 3) & 0xFFFFE01F | (32 * inputs[2].n128_u8[3]);
v14 = (inputs[2].n128_u8[4] >> 3) & 0xFFFFE01F | (32 * inputs[2].n128_u8[4]);
v15 = (inputs[2].n128_u8[5] >> 3) & 0xFFFFE01F | (32 * inputs[2].n128_u8[5]);
v16 = (inputs[2].n128_u8[6] >> 3) & 0xFFFFE01F | (32 * inputs[2].n128_u8[6]);
v17 = (inputs[2].n128_u8[7] >> 3) & 0xFFFFE01F | (32 * inputs[2].n128_u8[7]);
v18 = vorrq_s8(vshrq_n_u8(*inputs, 3uLL), vshlq_n_s8(*inputs, 5uLL));
v19 = (inputs[2].n128_u8[8] >> 3) & 0xFFFFE01F | (32 * inputs[2].n128_u8[8]);
v20 = vorrq_s8(vshrq_n_u8(inputs[1], 3uLL), vshlq_n_s8(inputs[1], 5uLL));
v21 = (inputs[2].n128_u8[9] >> 3) & 0xFFFFE01F | (32 * inputs[2].n128_u8[9]);
inputs[2].n128_u8[2] = (inputs[2].n128_u8[2] >> 3) & 0x1F | (32 * inputs[2].n128_u8[2]);
inputs[2].n128_u8[0] = v11;
inputs[2].n128_u8[1] = v12;
inputs[2].n128_u8[3] = v13;
inputs[2].n128_u8[4] = v14;
inputs[2].n128_u8[5] = v15;
inputs[2].n128_u8[6] = v16;
inputs[2].n128_u8[7] = v17;
inputs[2].n128_u8[8] = v19;
inputs[2].n128_u8[9] = v21;
*inputs = v18;
inputs[1] = v20;
retStr[0] = 26;
ind2 = 0LL;
v27 = 0LL;
retStr1 = &retStr[1];
strcpy(&retStr[1], "Wrong, wrong!");
while ( inputs->n128_u8[ind2] == cipher[ind2] )//比较
{
if ( (unsigned __int64)++ind2 > 41 )
{
std::string::assign((int)retStr, "Hello, hello!", 0xDu);
if ( (retStr[0] & 1) != 0 )
retStr1 = v27;
break;
}
}
v24 = (*env)->NewStringUTF(env, retStr1);
if ( (retStr[0] & 1) != 0 )
operator delete(v27);
return v24;
}

解密脚本

1
2
3
4
5
6
7
8
9
10
11
12
13
sign = "308202e4308201cc020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b30090603550406130255533020170d3231303330363134333034385a180f32303531303232373134333034385a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330820122300d06092a864886f70d01010105000382010f003082010a0282010100cbf2b09e4308ebb459e8841e5a7b920497fef2b349e80648f7eb35f48d40a75e7ce7945b8b42d197bec0bf177e6c9899ed707dcc4a726cb14c1a69b0c4a02474806fa73cfb10e10f7b1665021c24762b6edad65ca63cea3c72e0d4e4ca3f98301173eec3254337af1f5a11f779ecbe04d1b74d53f5835e011222155a56f97e00d75374cd93080dfa087cd356a99fe1eebf5d6d5e31846aad5252c3a17a4656e2e210ce1c7aa4d147fb8cf440a50add61bbb2ec299a2e0dab0b4504796ac3a899da553ab1d83576691ab23409d18398014b3b5eaf12e83f4d99aa09e1e4e4cae133530730c1133da2b3dee37b58eb1a5795b221ec5a8830731a41167d295f9e1b0203010001300d06092a864886f70d010105050003820101000e4740235e9cf2be33de3e06d777139cbbc5cf0622285c17da04697b8067318aaf8df0fbb4d3166f293ea15aa2592f06eb6929af063722ac9f30ad85e2c087564931d6ac65fcd5fbc864b3dc9841e039c6e1d5fbc5c2f8adf90a547bc4ebc07d387914db24451c2cc89925359bd3bb0750c7aabf9d743b1893e98bbc8ff74b24fc0b4be2dbaaf1c917bba01496d0617ffc3a4a8b7a6e79a3036298a6ebf57bb00001e43a0b242864eebb0fcec9e323144d4447c878430f18e6e358ad97566fa04d1f07b171c1476c9af5a1eba0bf6616e219c0b9e1299d09fecded24a880397f92e0f99d8951228c7770c184fd77adff943bfc8b6aa524c5f0a6d7686fe35486"
cipher = [0xca, 0xeb, 0x4a, 0x8a, 0x68, 0xe1, 0xa1, 0xeb, 0xe1, 0xee, 0x6b, 0x84, 0xa2, 0x6d, 0x49, 0xc8, 0x8e, 0xe, 0xcc, 0xe9, 0x45, 0xcf, 0x23, 0xcc, 0xc5, 0x4c, 0xc, 0x85, 0xcf, 0xa9, 0x8c, 0xf6, 0xe6, 0xd6, 0x26, 0x6d, 0xac, 0xc, 0xac, 0x77, 0xe0, 0x64]


# shift byte
for ind in range(len(cipher)):
x = ((cipher[ind] >> 5) & 0x7f) | ((cipher[ind] << 3) & 0x7f)
cipher[ind] = x

ind = 354
for ind1 in range(1, 42):
print(chr( (cipher[ind1] ^ (ord(sign[ind]) + ind1 )) & 0x7f ), end='')
ind = ind + 27

Welcome to my other publishing channels