BMZCTF2020 re & crypto writeup

题目链接

RE

re1

64位程序,IDA查看。

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没有影响,直接逆算法。注意在这里:

for ( k = 0; k <= 27; ++k )
{
   v4[k] %= v5;
   encrypt_jump(v5);
}

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

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查看伪代码。

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。

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盒:

_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;
}
_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;
}
_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盒只是简单的异或运算,直接异或回去就好。

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

// 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。

// 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中的字符。

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.

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次。(总之有点迷)

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。题目代码:

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

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

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

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}