BMZCTF2020 re & crypto writeup

题目链接

RE

re1

64位程序,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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4[30]; // [rsp+0h] [rbp-90h]
int v5; // [rsp+78h] [rbp-18h]
int m; // [rsp+7Ch] [rbp-14h]
int l; // [rsp+80h] [rbp-10h]
int k; // [rsp+84h] [rbp-Ch]
int j; // [rsp+88h] [rbp-8h]
int i; // [rsp+8Ch] [rbp-4h]

puts("Hello,Reverser,Lets play a game (T_T)");
for ( i = 0; i <= 27; ++i )
v4[i] = getchar();
getchar();
for ( j = 0; j <= 27; ++j )
{
v4[j] ^= 0x1A2B3Cu;
encrypt_jump(0x1A2B3Cu);
}
v5 = 0x1A2B0C;
for ( k = 0; k <= 27; ++k )
{
v4[k] %= v5;
encrypt_jump(v5);
}
for ( l = 0; l <= 27; ++l )
{
v4[l] ^= 0x4D5E6Fu;
encrypt_jump(0x4D5E6Fu);
}
for ( m = 0; m <= 27; ++m )
{
if ( bytes_0318912x[m] != v4[m] )
{
puts("Sorry~");
return 0;
}
}
puts("Congratulations!");
return 0;
}

v4数组是flag的值,让bytes_0318912x[m] == v4[m]时候跳转到Congratulations处,encrypt_ jump()对v4没有影响,直接逆算法。注意在这里:

1
2
3
4
5
for ( k = 0; k <= 27; ++k )
{
v4[k] %= v5;
encrypt_jump(v5);
}

按理说模运算并不容易复原,但尝试后发现这里只有倍数k=1。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bytes_0318912x = [0x4D5E21,0x4D5E2B,0x4D5E3E,0x4D5E20,
0x4D5E54,0x4D5E1D,0x4D5E0A,0x4D5E35,
0x4D5E1C,0x4D5E33,0x4D5E01,0x4D5E38,
0x4D5E0D,0x4D5E22,0x4D5E32,0x4D5E22,
0x4D5E37,0x4D5E2C,0x4D5E6C,0x4D5E38,
0x4D5E6E,0x4D5E2C,0x4D5E38,0x4D5E1C,
0x4D5E28,0x4D5E6F,0x4D5E6E,0x4D5E5A]

def attack():
flag = ''
for i in range(0, 28):
bytes_0318912x[i] ^= 0x4D5E6F
bytes_0318912x[i] += 0x1A2B0C
bytes_0318912x[i] ^= 0x1A2B3C
flag += chr(bytes_0318912x[i])
return flag

print(attack())

# flag{BMZCTF_ReUeXs3_1s_Co01}

re2

64位程序,IDA查看伪代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4[30]; // [rsp+0h] [rbp-80h]
int v5; // [rsp+78h] [rbp-8h]
int i; // [rsp+7Ch] [rbp-4h]

puts(&s);
v5 = 0;
for ( i = 0; i <= 26; ++i )
v4[i] = getchar();
getchar();
if ( (unsigned int)Auth(v4, argv) == 1 )
puts("Congratulations!~");
else
puts("tryyyyy again");
return 0;

当输入字符串v4满足Auth(v4, argv) == 1时,打印出”Congratulations!~”,v4为flag值。进入Auth函数,a1=v4。

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
signed __int64 __fastcall Auth(__int64 a1)
{
int v2[12]; // [rsp+8h] [rbp-C0h]
int v3[12]; // [rsp+38h] [rbp-90h]
int v4[12]; // [rsp+68h] [rbp-60h]
__int64 v5; // [rsp+98h] [rbp-30h]
__int64 v6; // [rsp+A0h] [rbp-28h]
__int64 v7; // [rsp+A8h] [rbp-20h]
int j; // [rsp+B0h] [rbp-18h]
int v9; // [rsp+B4h] [rbp-14h]
int v10; // [rsp+B8h] [rbp-10h]
int i; // [rsp+BCh] [rbp-Ch]
int v12; // [rsp+C0h] [rbp-8h]
int v13; // [rsp+C4h] [rbp-4h]

v13 = 0;
v12 = 8;
while ( v13 <= 8 )
v4[v13++] = *(_DWORD *)(4LL * v12-- + a1) ^ 0xCE2;
for ( i = 9; i <= 17; ++i )
v3[i - 9] = *(_DWORD *)(4LL * i + a1) ^ 0xFFF;
v10 = 18;
v9 = 26;
while ( v10 <= 26 )
v2[v10++ - 18] = *(_DWORD *)(4LL * v9-- + a1) ^ 0x4EA;
v7 = SboxExchangeA(&SboxA, v4);
v6 = SboxExchangeB(&SboxB, v3);
v5 = SboxExchangeC(&SboxC, v2);
for ( j = 0; j <= 8; ++j )
{
if ( bytes_0x0001[j] != *(_DWORD *)(4LL * j + v7) )
return 0LL;
if ( bytes_0x0002[j] != *(_DWORD *)(4LL * j + v6) )
return 0LL;
if ( *((_DWORD *)&bytes_0x0003 + j) != *(_DWORD *)(4LL * j + v5) )
return 0LL;
}
return 1LL;
}

发现最终要使Auth()返回 1,那么就要满足bytes_0x0001[j] = v7[j],bytes_0x0002[j]=v6[j],bytes_0x0003[j]=v5[j]。bytes_0x0001,bytes_0x0002,bytes_0x0003可以直接查看,那么需要知道v5,v6,v7的值。v5,v6,v7由v2,v3,v4经过S盒得出。如果推出v2,v3,v4的值,根据前面的循环,可以拼凑出a1的值。3个S盒:

1
2
3
4
5
6
7
8
_DWORD *__fastcall SboxExchangeA(__int64 a1, __int64 a2)
{
signed int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; i <= 8; ++i )
final_2323[i] = *(_DWORD *)(4LL * *(signed int *)(4LL * i + a1) + a2);
return final_2323;
}
1
2
3
4
5
6
7
8
_DWORD *__fastcall SboxExchangeB(__int64 a1, __int64 a2)
{
signed int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; i <= 8; ++i )
final_2332[i] = *(_DWORD *)(4LL * *(signed int *)(4LL * i + a1) + a2);
return final_2332;
}
1
2
3
4
5
6
7
8
_DWORD *__fastcall SboxExchangeC(__int64 a1, __int64 a2)
{
signed int i; // [rsp+1Ch] [rbp-4h]

for ( i = 0; i <= 8; ++i )
final_2341[i] = *(_DWORD *)(4LL * *(signed int *)(4LL * i + a1) + a2);
return final_2341;
}

发现3个S盒只是简单的异或运算,直接异或回去就好。

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
bytes_0x0001 = [0x0C8E, 0x0C85, 0x0C87, 0x0C99, 0x0CA4, 0x0CD1, 0x0C83, 0x0C8E, 0x0C84]
bytes_0x0002 = [0x0F9A, 0x0F8B, 0x0FA0, 0x0FCF, 0x0F8D, 0x0FA0, 0x0FB9, 0x0F9E, 0x0FA0]
bytes_0x0003 = [0x48F,0x499,0x48F,0x497,0x4DD,0x4B5,0x49C,0x482,0x4B8]
sboxa = [7,0,0,0,5,0,0,0,2,0,0,0,4,0,0,0,3,0,0,0,1,0,0,0,6,0,0,0,0,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
sboxb = [2,0,0,0,6,0,0,0,0,0,0,0,7,0,0,0,4,0,0,0,5,0,0,0,1,0,0,0,3,0,0,0,8,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]
sboxc = [3,0,0,0,1,0,0,0,6,0,0,0,0,0,0,0,8,0,0,0,5,0,0,0,2,0,0,0,7,0,0,0,4,0,0,0]

def SboxExchangeA():
final_2323 = ['0'] * 9
for i in range(0, 9):
final_2323[i] = bytes_0x0001[sboxa[4*i]]
return final_2323

def SboxExchangeB():
final_2323 = ['0'] * 9
for i in range(0, 9):
final_2323[i] = bytes_0x0002[sboxb[4*i]]
return final_2323

def SboxExchangeC():
final_2341 = ['0'] * 9
for i in range(0, 9):
final_2341[i] = bytes_0x0003[sboxc[4*i]]
return final_2341

def attack():
v4 = SboxExchangeA()
v3 = SboxExchangeB()
v2 = SboxExchangeC()
a1 = ['*'] * 27
for i in range(0, 9):
a1[8-i] = v4[i] ^ 0xCE2
for i in range(9, 18):
a1[i] = v3[i - 9] ^ 0xFFF
for i in range(18, 27):
a1[44 - i] = v2[i - 18] ^ 0x4EA
flag = ''
for i in a1:
flag += chr(i)
return flag

print(attack())

# flag{Fe3l_Fear_t0_7he_Revs}

re3

apk文件解压之后dex2jar转为jar文件,jd-gui打开。可以看到有两个Activity:MainActivity.class和FlagActivity.class

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
// MainActivity.class
package com.ctfgame.bmzctf;

import android.content.Context;
import android.content.Intent;
import android.content.pm.Signature;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class MainActivity extends AppCompatActivity {
public int Globalhit = 999999;

private boolean checkSignature() {
try {
Signature signature = (getPackageManager().getPackageInfo(getPackageName(), 64)).signatures[0];
getMD5String(signature.toByteArray());
boolean bool = getMD5String(signature.toByteArray()).equals("E915DDE06E7090754E9ACD1DA29C052A");
return bool;
} catch (Exception exception) {
exception.printStackTrace();
return false;
}
}

private String getMD5String(byte[] paramArrayOfbyte) {
}

protected void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
if (!checkSignature())
System.exit(1);
setContentView(2131361823);
((Button)findViewById(2131165265)).setOnClickListener(new Flag_listenser());
}

private class Flag_listenser implements View.OnClickListener {
private Flag_listenser() {}

public void onClick(View param1View) {
MainActivity mainActivity = MainActivity.this;
mainActivity.Globalhit--;
MainActivity.this.checkSignature();
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(");
stringBuilder.append(Integer.toString(MainActivity.this.Globalhit));
stringBuilder.append(");
String str = stringBuilder.toString();
((TextView)MainActivity.this.findViewById(2131165419)).setText(str);
if (MainActivity.this.Globalhit == 0) {
Intent intent = new Intent((Context)MainActivity.this, FlagActivity.class);
MainActivity.this.startActivity(intent);
}
}
}
}

Globalhit = 999999,用listener来监听点击事件,每点击一次Globalhit减1,当Globalhit = 0时利用Intent传参调用startActivity()来启动FlagActivity。

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
// FlagActivity.class
package com.ctfgame.bmzctf;

import android.os.Bundle;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;

public class FlagActivity extends AppCompatActivity {
public int[] encF = new int[] {
4, 5, 30, 27, 9, 55, 56, 53, 43, 15,
70, 90, 85, 14, 25, 9, 78, 54, 83, 96,
25, 23, 19, 115, 25, 49, 77, 75, 29, 28,
4, 122, 96, 110, 56, 16 };

private int[] wocaozheshisha(int[] paramArrayOfint) {
for (int i = 0; i <= 34; i++)
paramArrayOfint[i + 1] = paramArrayOfint[i] ^ paramArrayOfint[i + 1];
return paramArrayOfint;
}

protected void onCreate(Bundle paramBundle) {
super.onCreate(paramBundle);
setContentView(2131361822);
byte[] arrayOfByte = "bmz".getBytes();
wocaozheshisha(wocaozheshisha(wocaozheshisha(this.encF)));
int i = 0;
while (true) {
int[] arrayOfInt = this.encF;
if (i <= arrayOfInt.length - 1) {
arrayOfInt[i] = arrayOfByte[i % 3] ^ arrayOfInt[i];
i++;
continue;
}
String str = "";
for (i = 0; i <= this.encF.length - 1; i++) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(str);
stringBuilder.append((char)this.encF[i]);
str = stringBuilder.toString();
}
((TextView)findViewById(2131165420)).setText(str);
return;
}
}
}

最后打印出的是encF中的字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
encF = [4, 5, 30, 27, 9, 55, 56, 53, 43, 15, 70, 90, 85, 14, 25, 9, 78, 54, 83, 96, 25, 23, 19, 115, 25, 49, 77, 75, 29, 28, 4, 122, 96, 110, 56, 16 ]

def wocaozheshisha(paramArrayOfint):
for i in range(0, 35):
paramArrayOfint[i + 1] = paramArrayOfint[i] ^ paramArrayOfint[i + 1];
return paramArrayOfint

def attack():
new_encF = wocaozheshisha(wocaozheshisha(wocaozheshisha(encF)))
arrayOfInt = len(new_encF)
arrayOfByte = "bmz"
for i in range(0, arrayOfInt):
new_encF[i] = ord(arrayOfByte[i % 3]) ^ new_encF[i]
flag = ''
for j in new_encF:
flag += chr(j)
return flag

print(attack())

# flag{Every0ne-0f-BMZCTF-1s-3he-Best}

Crypto

Crypto_xor

hint: [‘\x00’,’\x00’,’\x00’] at start of xored is the best hint you get.

1
2
3
4
5
6
xored = ['\x00', '\x00', '\x00', '\x18', 'C', '_', '\x05', 'E', 'V', 'T', 'F', 'U', 'R', 'B', '_', 'U', 'G', '_', 'V', '\x17', 'V', 'S', '@', '\x03', '[', 'C', '\x02', '\x07', 'C', 'Q', 'S', 'M', '\x02', 'P', 'M', '_', 'S', '\x12', 'V', '\x07', 'B', 'V', 'Q', '\x15', 'S', 'T', '\x11', '_', '\x05', 'A', 'P', '\x02', '\x17', 'R', 'Q', 'L', '\x04', 'P', 'E', 'W', 'P', 'L', '\x04', '\x07', '\x15', 'T', 'V', 'L', '\x1b']
s1 = ""
s2 = ""
a_list = [chr(ord(a) ^ ord(b)) for a,b in zip(s1, s2)]
print(a_list)
print("".join(a_list))

提示说3个\x00是关键提示,我寻思不就是相同异或为0吗…虽然s1s2是可打印字符串,但只知道异或的值构造flag好像又有点说不过去。flag开头固定字符长度为3的只有’ctf’,len(xored) = 69是3的倍数,只好猜想s1或s2中是’ctf’重复23次。(总之有点迷)

1
2
3
4
5
6
s1 = ['c', 't', 'f'] * 23
s2 = ['\x00', '\x00', '\x00', '\x18', 'C', '_', '\x05', 'E', 'V', 'T', 'F', 'U', 'R', 'B', '_', 'U', 'G', '_', 'V', '\x17', 'V', 'S', '@', '\x03', '[', 'C', '\x02', '\x07', 'C', 'Q', 'S', 'M', '\x02', 'P', 'M', '_', 'S', '\x12', 'V', '\x07', 'B', 'V', 'Q', '\x15', 'S', 'T', '\x11', '_', '\x05', 'A', 'P', '\x02', '\x17', 'R', 'Q', 'L', '\x04', 'P', 'E', 'W', 'P', 'L', '\x04', '\x07', '\x15', 'T', 'V', 'L', '\x1b']
a_list = [chr(ord(a) ^ ord(b)) for a,b in zip(s1, s2)]
print("".join(a_list))

# ctf{79f107231696395c004e87dd7709d3990f0d602a57e9f56ac428b31138bda258}

Crypto_easy_crypto

变过形的RSA。题目代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from Crypto.Util.number import *
from secret import flag

def keygen(nbit):
while True:
p, q, r = [getPrime(nbit) for _ in range(3)]
if isPrime(p + q + r):
pubkey = (p * q * r, p + q + r)
privkey = (p, q, r)
return pubkey, privkey

def encrypt(msg, pubkey):
enc = pow(bytes_to_long(msg.encode('utf-8')), 0x10001, pubkey[0] * pubkey[1])
return enc

nbit = 512
pubkey, _ = keygen(nbit)
print('pubkey =', pubkey)

enc = encrypt(flag, pubkey)
print('enc =', enc)

# pubkey = (763929224901239050077647342425144363318006219360210465113458622937882119766705458273788947718911994090187932947694326848868575500906975653763043489419853300731695950407389203582333794360159494405111004208058198680365172060235321945875374635278292661761319773173838900295933943437761251440819434675631450032782188481758975272071015244168751016874668742536151359822585793537758876078350987062972237271763834743266722655055439868971805843593929839097963319788083763, 27750416681837900468631425875797804482827864226099175414717825894823303299159277202974070903630276868248755642608884903122768656427165401563920443482151217)
# enc = 7579294603035135817234501131256282324240132424272000903035801073847222917306092792610406876451698121956232047031639448767323475088170357863653435096746640609325525035329328626562143049656124241263576649974820533800161432666946327226418036826146160288273175521864687122720836795596635067761146519940441817765732160382018434465776749372209181212817247187474685246156193381822044772746654690126746715728166527274933634750443478682812664022009444348448955720226579468781927776133336045525149203384137859868153077820708167146052429014790660921586958660759949399826568136755816563468739111123761288318276379025018708883521

加密过程$\mathsf{\scriptsize m^{e}\equiv c(mod\;pqr(p+q+r))}$因为$\mathsf{\scriptsize p,q,r,p+q+r}$两两互质,因此等价于4个同余式:

注意到$\mathsf{\scriptsize m^{e}\equiv c(mod\;p+q+r)}$中$\mathsf{\scriptsize p+q+r}$已知,由于$\mathsf{\scriptsize p+q+r}$为素数,则有$\mathsf{\scriptsize (e,p+q+r)=1}$,因此存在$\mathsf{\scriptsize d}$使得$\mathsf{\scriptsize ed\equiv 1(mod\;p+q+r-1)}$。计算$\mathsf{\scriptsize d}$用来解密:$\mathsf{\scriptsize m\equiv c^{d}(mod\;p+q+r)}$

1
2
3
4
5
6
7
8
9
10
11
12
13
import gmpy2
from Crypto.Util.number import *

b = 27750416681837900468631425875797804482827864226099175414717825894823303299159277202974070903630276868248755642608884903122768656427165401563920443482151217
phi = b - 1
e = 0x10001
d = gmpy2.invert(e, phi)
enc = 7579294603035135817234501131256282324240132424272000903035801073847222917306092792610406876451698121956232047031639448767323475088170357863653435096746640609325525035329328626562143049656124241263576649974820533800161432666946327226418036826146160288273175521864687122720836795596635067761146519940441817765732160382018434465776749372209181212817247187474685246156193381822044772746654690126746715728166527274933634750443478682812664022009444348448955720226579468781927776133336045525149203384137859868153077820708167146052429014790660921586958660759949399826568136755816563468739111123761288318276379025018708883521
# d = 4700510789097495050159107964161182653522012310205333571551682030889932250850163056444682562540239918129139820080278793804505162808763952008195078247331441
m = pow(enc, d, b)
print(long_to_bytes(m))

# BMZCTF{9d8ef46w59c4-4s5w-8d4a-w9d5-5dq78e9d}
-------------end ♥-------------