SekaiCTF 2022 writeup

Writer:b1uef0x / Webページ建造途中

概要

2問解いた。Webが解けなかった。

目次

Sanity Check (Misc)

Welcome to SekaiCTF! Flags will be in format SEKAI\{[\x20-\x7e]+\}.

Join our Discord for admin support and challenge updates. Flag is in #announcement channel.

SEKAI{w31c0m3_t0_th3_w0r1d!}

Time Capsule (Cryptography)

I have encrypted a secret message with this super secure algorithm and put it into a Time Capsule. Maybe nobody can reveal my secret without a time machine...

Author: sahuang

暗号化を行うchall.pyと暗号データflag.encが配布される。

import time
import os
import random

from SECRET import flag

def encrypt_stage_one(message, key):
    u = [s for s in sorted(zip(key, range(len(key))))]
    res = ''

    for i in u:
        for j in range(i[1], len(message), len(key)):
            res += message[j]

    return res

def encrypt_stage_two(message):
    now = str(time.time()).encode('utf-8')
    now = now + "".join("0" for _ in range(len(now), 18)).encode('utf-8')
    
    random.seed(now)
    key = [random.randrange(256) for _ in message]
    
    return [m ^ k for (m,k) in zip(message + now, key + [0x42]*len(now))]


# I am generating many random numbers here to make my message secure
rand_nums = []
while len(rand_nums) != 8:
    tmp = int.from_bytes(os.urandom(1), "big")
    if tmp not in rand_nums:
        rand_nums.append(tmp)

for _ in range(42):
    # Answer to the Ultimate Question of Life, the Universe, and Everything...
    flag = encrypt_stage_one(flag, rand_nums)

# print(flag)

# Another layer of randomness based on time. Unbreakable.
res = encrypt_stage_two(flag.encode('utf-8'))

with open("flag.enc", "wb") as f:
    f.write(bytes(res))
f.close()

暗号化は二段階で行われており、逆算するにあたってまずencrypt_stage_twoから見ていく。

def encrypt_stage_two(message):
    now = str(time.time()).encode('utf-8')
    now = now + "".join("0" for _ in range(len(now), 18)).encode('utf-8')
    
    random.seed(now)
    key = [random.randrange(256) for _ in message]
    
    return [m ^ k for (m,k) in zip(message + now, key + [0x42]*len(now))]

encrypt_stage_twoでは現在時刻をseedにrandom関数を初期化して、messageの長さ分の乱数を生成してkeyとしてmessageとXORをとっている。

よく見るとmessage+現在時刻nowと足したデータにXORをかけており、また現在時刻nowにかかっているXOR演算はすべて0x42となっていることから、現在時刻を復元することができる。

flag.encの末尾はstvupvsusrlqzvtuwrで、各文字に0x42とXORすれば1647241710.3846750が復元される。この値でrandom関数を初期化してencrypt_stage_twoを逆算できる。

続いてencrypt_stage_oneを見る。

def encrypt_stage_one(message, key):
    u = [s for s in sorted(zip(key, range(len(key))))]
    res = ''

    for i in u:
        for j in range(i[1], len(message), len(key)):
            res += message[j]

    return res

入力されるkeyは1byteの異なる乱数が8個入った配列になっており、乱数はわからない。しかしやっていることは8文字の組ごとに順序を並び替えているだけである。総当りで解くことができる。

各ステージの逆関数を書いて、encrypt_stage_oneを総当りするsolverを書いた。SEKAIから始まる文字列が見つける。

import time
import os
import random
import itertools

def encrypt_stage_one_rev(message, key):
    u = [s for s in sorted(zip(key, range(len(key))))]
    resl = [0] * len(message)

    k = 0
    for i in u:
        for j in range(i[1], len(message), len(key)):
            resl[j] = message[k]
            k += 1

    return "".join(resl)


def encrypt_stage_two_rev(message0,now):

    message = message0[:len(message0)-len(now)]
    
    random.seed(now)
    key = [random.randrange(256) for _ in message]

    
    return [m ^ k for (m,k) in zip(message0, key + [0x42]*len(now))]


with open("flag.enc", "rb") as f:
    enc = f.read()
f.close()

now0 = b'1647241710.3846750'
res = encrypt_stage_two_rev(bytes(enc),now0)
res = res[:len(res)-len(now0)]

l = [0,1,2,3,4,5,6,7]
for v in itertools.permutations(l):
    flag = bytes(res).decode('utf-8')
    for _ in range(42):
        flag = encrypt_stage_one_rev(flag, v)
    if flag[:5] == "SEKAI" :
        print(flag)

SEKAI{T1m3_15_pr3C10u5_s0_Enj0y_ur_L1F5!!!}

Matrix Lab 1 (Reverse)

Welcome to the first lab of Course ML10001 from Sekai University! The Lab 1 assignment should be pretty easy...

Author: sahuang

Matrix_Lab_1.classが配布される。jadを使って逆コンパイルをしてみたところ、Parsing Matrix_Lab_1.class...JavaClassFileParseException: Invalid tag value 0x12と失敗してしまった。

というわけでGhidraで逆コンパイルしてみた。

main関数void main_java.lang.String[]_void(void)

{
  PrintStream objectRef;
  String objectRef_00;
  int iVar1;
  String objectRef_01;
  int iVar2;
  String objectRef_02;
  boolean bVar3;
  Scanner objectRef_03;
  AssertionError objectRef_04;
  
  objectRef_03 = new Scanner(System.in);
  objectRef = System.out;
  objectRef.print("Enter the flag: ");
  objectRef_00 = objectRef_03.next();
  iVar1 = objectRef_00.length();
  if (iVar1 != 0x2b) {
    objectRef = System.out;
    objectRef.println("Oops, wrong flag!");
    return;
  }
  objectRef_01 = objectRef_00.substring(0,Sekai.length);
  iVar1 = Sekai.length;
  objectRef_02 = objectRef_00;
  iVar2 = objectRef_00.length();
  objectRef_02 = objectRef_02.substring(iVar1,iVar2 + -1);
  iVar1 = objectRef_00.length();
  objectRef_00 = objectRef_00.substring(iVar1 + -1);
  bVar3 = objectRef_01.equals("SEKAI{");
  if ((bVar3 != false) && (bVar3 = objectRef_00.equals("}"), bVar3 != false)) {
    if ((Sekai.$assertionsDisabled == false) &&
       (iVar1 = objectRef_02.length(), iVar1 != Sekai.length * Sekai.length)) {
      objectRef_04 = new AssertionError();
                    /* WARNING: Do nothing block with infinite loop */
      athrowOp(objectRef_04);
      do {
      } while( true );
    }
    bVar3 = Sekai.solve(objectRef_02);
    if (bVar3 != false) {
      objectRef = System.out;
      objectRef.println("Congratulations, you got the flag!");
      return;
    }
    objectRef = System.out;
    objectRef.println("Oops, wrong flag!");
    return;
  }
  objectRef = System.out;
  objectRef.println("Oops, wrong flag!");
  return;
}
solve関数int solve_java.lang.String_boolean(void param1)

{
  undefined2 uVar1;
  char[] pcVar2;
  char[] pcVar3;
  String pSVar4;
  String pSVar5;
  String pSVar6;
  boolean bVar7;
  int iVar8;
  int iVar9;
  String objectRef;
  
  pcVar2 = param1.toCharArray();
  pcVar2 = Sekai.transform(pcVar2,Sekai.length);
  iVar8 = 0;
  while( true ) {
    if (Sekai.length / 2 < iVar8) break;
    iVar9 = 0;
    while( true ) {
      if (Sekai.length + iVar8 * -2 + -1 <= iVar9) break;
      uVar1 = (undefined2)(((int[])pcVar2[iVar8 * 2])[0] + (iVar8 + iVar9) * 2);
      (undefined2)(((int[])pcVar2[iVar8 * 2])[0] + (iVar8 + iVar9) * 2) =
           (undefined2)(((int[])pcVar2[(((Sekai.length + -1) - iVar8) - iVar9) * 2])[0] + iVar8 * 2)
      ;
      (undefined2)(((int[])pcVar2[(((Sekai.length + -1) - iVar8) - iVar9) * 2])[0] + iVar8 * 2) =
           (undefined2)
           (((int[])pcVar2[((Sekai.length + -1) - iVar8) * 2])[0] +
           (((Sekai.length + -1) - iVar8) - iVar9) * 2);
      (undefined2)
      (((int[])pcVar2[((Sekai.length + -1) - iVar8) * 2])[0] +
      (((Sekai.length + -1) - iVar8) - iVar9) * 2) =
           (undefined2)(((int[])pcVar2[(iVar8 + iVar9) * 2])[0] + ((Sekai.length + -1) - iVar8) * 2)
      ;
      (undefined2)(((int[])pcVar2[(iVar8 + iVar9) * 2])[0] + ((Sekai.length + -1) - iVar8) * 2) =
           uVar1;
      iVar9 = iVar9 + 1;
    }
    iVar8 = iVar8 + 1;
  }
  objectRef = "oz]{R]3l]]B#50es6O4tL23Etr3c10_F4TD2";
  pcVar3 = Sekai.getArray(pcVar2,0,5);
  pSVar4 = Sekai.encrypt(pcVar3,2);
  pcVar3 = Sekai.getArray(pcVar2,1,4);
  pSVar5 = Sekai.encrypt(pcVar3,1);
  pcVar2 = Sekai.getArray(pcVar2,2,3);
  pSVar6 = Sekai.encrypt(pcVar2,0);
  pSVar4 = makeConcatWithConstants(pSVar4,pSVar5,pSVar6);
  bVar7 = objectRef.equals(pSVar4);
  return (int)bVar7;
}
transform関数char[] transform_char[]_int_char[][](int param1,int param2)

{
  char[] pcVar1;
  int iVar2;
  
  pcVar1 = multianewarrayOp(char[][],param2,param2);
  iVar2 = 0;
  while( true ) {
    if (param2 * param2 <= iVar2) break;
    (undefined2)(((int[])pcVar1[(iVar2 / param2) * 2])[0] + (iVar2 % param2) * 2) =
         (undefined2)(param1 + iVar2 * 2);
    iVar2 = iVar2 + 1;
  }
  return pcVar1;
}
getArray関数char[] getArray_char[][]_int_int_char[](int param1,int param2,int param3)

{
  char[] pcVar1;
  int iVar2;
  int iVar3;
  
  pcVar1 = new char[Sekai.length * 2];
  iVar2 = 0;
  iVar3 = 0;
  while( true ) {
    if (Sekai.length <= iVar3) break;
    pcVar1[iVar2] = ((char[])(((int[])(param1 + param2 * 4))[0] + iVar3 * 2))[0];
    iVar2 = iVar2 + 1;
    iVar3 = iVar3 + 1;
  }
  iVar3 = 0;
  while( true ) {
    if (Sekai.length <= iVar3) break;
    pcVar1[iVar2] =
         ((char[])(((int[])(param1 + param3 * 4))[0] + ((Sekai.length + -1) - iVar3) * 2))[0];
    iVar2 = iVar2 + 1;
    iVar3 = iVar3 + 1;
  }
  return pcVar1;
}
encryptString encrypt_char[]_int_java.lang.String(int param1,short param_2)

{
  char[] pcVar1;
  String pSVar2;
  int iVar3;
  int iVar4;
  int iVar5;
  
  pcVar1 = new char[Sekai.length * 2];
  iVar3 = Sekai.length;
  iVar5 = 0;
  iVar4 = Sekai.length;
  while( true ) {
    iVar3 = iVar3 + -1;
    if (Sekai.length * 2 <= iVar5) break;
    pcVar1[iVar5] = ((char[])(param1 + iVar3 * 2))[0];
    pcVar1[iVar5 + 1] = ((char[])(param1 + iVar4 * 2))[0];
    iVar5 = iVar5 + 2;
    iVar4 = iVar4 + 1;
  }
  iVar5 = 0;
  while( true ) {
    if (Sekai.length * 2 <= iVar5) break;
    pcVar1[iVar5] = pcVar1[iVar5] ^ param_2;
    iVar5 = iVar5 + 1;
  }
  pSVar2 = String.valueOf(pcVar1);
  return pSVar2;
}

処理を簡単に述べると、main関数は入力されたflagのSEKAI{....}の{}の中身を取り出してsolve関数に渡す。

solve関数では文字列をtransformで2次元配列(6*6)に変換して、適当にシャッフルする。

getArray関数で2次元配列(6*6)からの1次元配列(12)を3つ取り出し、それぞれにencrypt関数を使って文字列に変換する。

変換された文字列3つを1つにつなげ、oz]{R]3l]]B#50es6O4tL23Etr3c10_F4TD2と一致するかを判定する。

したがってやることは、各関数を丁寧に読んで逆関数を作ってoz]{R]3l]]B#50es6O4tL23Etr3c10_F4TD2からflag文字列を復元することになる。

頑張ってJavaScriptで書いた。

Sekai = {}
Sekai.length = 6


Sekai.antitransform = function(pcVar1,param2)
{
  var output = "";
  var iVar2 = 0;
  while( true ) {
    if (param2 * param2 <= iVar2) break;
    output += String.fromCharCode(pcVar1[Math.floor(iVar2 / param2)][iVar2 % param2]);
    iVar2 = iVar2 + 1;
  }
  return output;
}


Sekai.setArray = function(param1,param2,param3,pcVar1){
  var iVar2;
  var iVar3;
  
  iVar2 = 0;
  iVar3 = 0;
  while( true ) {
    if(Sekai.length <= iVar3) break;
    param1[param2][iVar3] = pcVar1[iVar2];
    iVar2 = iVar2 + 1;
    iVar3 = iVar3 + 1;
  }
  
  iVar3 = 0;
  while( true ) {
    if (Sekai.length <= iVar3) break;
    param1[param3][Sekai.length -1 - iVar3] = pcVar1[iVar2];
    iVar2 = iVar2 + 1;
    iVar3 = iVar3 + 1;
  }
  return param1;
}


Sekai.decrypt = function(Sparam1,param_2){
  var  param1 = [];
  for(var _i=0; _i<Sekai.length * 2;_i++)param1[_i] = Sparam1.charCodeAt(_i)
  
  var iVar5 = 0;
  while( true ) {
    if (Sekai.length * 2 <= iVar5) break;
    param1[iVar5] = param1[iVar5] ^ param_2;
    iVar5 = iVar5 + 1;
  }
  
  var  pcVar1 = [];
  for(var _i=0; _i<Sekai.length * 2;_i++)pcVar1[_i] = 0
  iVar3 = Sekai.length;
  iVar5 = 0;
  iVar4 = Sekai.length;
  while( true ) {
    iVar3 = iVar3 + -1;
    if (Sekai.length * 2 <= iVar5) break;
    pcVar1[iVar3] = param1[iVar5];
    pcVar1[iVar4] = param1[iVar5+1];
    iVar5 = iVar5 + 2;
    iVar4 = iVar4 + 1;
  }
  return pcVar1;
}


Sekai.desolve = function(param1)
{
  pcVar2 = [];
  for(var _i=0; _i<Sekai.length;_i++) {
    pcVar2[_i] = []
    for(var _j=0; _j<Sekai.length;_j++)pcVar2[_i][_j] = 0;
  }
  
  var dec;
  var objectRef = ["oz]{R]3l]]B#","50es6O4tL23E","tr3c10_F4TD2"];
  dec = Sekai.decrypt(objectRef[2],0);
  pcVar2 = Sekai.setArray(pcVar2,2,3,dec);
  dec = Sekai.decrypt(objectRef[1],1);
  pcVar2 = Sekai.setArray(pcVar2,1,4,dec);
  dec = Sekai.decrypt(objectRef[0],2);
  pcVar2 = Sekai.setArray(pcVar2,0,5,dec);
  
  var iVar8 = Sekai.length / 2;
  var iVar9,uVar1;
  while( true ) {
    if (iVar8<0) break;
    iVar9 = Sekai.length + iVar8 * -2 -2;
    while( true ) {
      if (iVar9<0) break;
      uVar1 = pcVar2[iVar8 + iVar9][Sekai.length -1 - iVar8];
      pcVar2[iVar8 + iVar9][Sekai.length -1 - iVar8] = pcVar2[Sekai.length -1 - iVar8][Sekai.length -1 - iVar8 - iVar9];
      pcVar2[Sekai.length -1 - iVar8][Sekai.length -1 - iVar8 - iVar9] = pcVar2[Sekai.length -1 - iVar8 - iVar9][iVar8]
      pcVar2[Sekai.length -1 - iVar8 - iVar9][iVar8] = pcVar2[iVar8][iVar8 + iVar9];
      pcVar2[iVar8][iVar8 + iVar9] = uVar1;
      iVar9 = iVar9 - 1;
    }
    iVar8 = iVar8 - 1;
  }
  return Sekai.antitransform(pcVar2,Sekai.length);
}


document.writeln("Sekai{"+Sekai.desolve(0)+"}");

Sekai{m4tr1x_d3cryP710N_15_Fun_M4T3_@2D2D!}