PwnMe CTF 2023 Qualifications : "8 bits" Writeup

この大会は2023/5/6 0:00(JST)~2023/5/7 0:00(JST)に開催されました。
今回もチームで参戦。結果は522点で501チーム中125位でした。
自分で解けた問題をWriteupとして書いておきます。

Discord Rules (Other)

Discordに入り、#règlesチャネルのルールを見ると、フラグが書いてあった。

PWNME{G0od_LucK_4_PwnME_V2_CTF!!}

C Stands For C (Reverse)

Ghidraでデコンパイルする。

void main(void)

{
  int iVar1;
  char *__s1;
  long in_FS_OFFSET;
  char local_58 [72];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts("Hi, please provide the password:");
  fgets(local_58,0x40,stdin);
  __s1 = (char *)caesarCipher(local_58,0x40);
  iVar1 = strcmp(__s1,"JQHGY{Qbs_x1x_S0o_f00E_b3l3???y65zx03}\n");
  if (iVar1 == 0) {
    puts("Welcome to the shop.");
  }
  else {
    puts("Who are you? What is your purpose here?");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

以下の文字列をシーザー暗号として復号すればよい。

JQHGY{Qbs_x1x_S0o_f00E_b3l3???y65zx03}

https://www.geocachingtoolbox.com/index.php?lang=en&page=caesarCipherで復号する。

Rotation 20:
PWNME{Why_d1d_Y0u_l00K_h3r3???e65fd03}
PWNME{Why_d1d_Y0u_l00K_h3r3???e65fd03}

Xoxor (Reverse)

Ghidraでデコンパイルする。

void FUN_00101268(void)

{
  int iVar1;
  size_t sVar2;
  size_t sVar3;
  char *__s2;
  long in_FS_OFFSET;
  char local_118 [264];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  sVar2 = strlen("aezx$K+`mcwL<+_3/0S^84B^V8}~8\\TXWmnmFP_@T^RTJ");
  sVar3 = strlen("1245a0eP2475cr0Fpsg0grs02g0Mg4g02LOLg5gs2g0g7");
  __s2 = (char *)FUN_001011e9("aezx$K+`mcwL<+_3/0S^84B^V8}~8\\TXWmnmFP_@T^RTJ",
                              "1245a0eP2475cr0Fpsg0grs02g0Mg4g02LOLg5gs2g0g7",sVar2 & 0xffffffff,
                              sVar3 & 0xffffffff);
  puts(
      "Hello! In order to access the shopping panel, please insert the password and do not cheat thi s time:"
      );
  fgets(local_118,0xff,stdin);
  sVar2 = strlen(local_118);
  local_118[(int)sVar2 + -1] = '\0';
  iVar1 = strcmp(local_118,__s2);
  if (iVar1 == 0) {
    puts("Welcome, you now have access to the shopping panel.");
  }
  else {
    puts("Please excuse us, only authorized persons can access this panel.");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

void * FUN_001011e9(long param_1,long param_2,int param_3,int param_4)

{
  void *pvVar1;
  int local_14;
  
  pvVar1 = malloc((long)param_3);
  for (local_14 = 0; local_14 < param_3; local_14 = local_14 + 1) {
    *(byte *)((long)pvVar1 + (long)local_14) =
         *(byte *)(param_1 + local_14) ^ *(byte *)(param_2 + local_14 % param_4);
  }
  return pvVar1;
}

2つの文字列のXORを取ればよい。

>>> from Crypto.Util.strxor import *
>>> s1 = b"aezx$K+`mcwL<+_3/0S^84B^V8}~8\\TXWmnmFP_@T^RTJ"
>>> s2 = b"1245a0eP2475cr0Fpsg0grs02g0Mg4g02LOLg5gs2g0g7"
>>> strxor(s1, s2)
b'PWNME{N0_W@y_You_C4n_F1nd_M3_h3he!!!!e83f9b3}'
PWNME{N0_W@y_You_C4n_F1nd_M3_h3he!!!!e83f9b3}

Tree Viewer (Web)

Sourceは以下のようになっている。

<?php
$parsed = isset($_POST['input']) ? $_POST['input'] : "/home/";

preg_match_all('/[;|]/m', $parsed, $illegals, PREG_SET_ORDER, 0);
if($illegals){
    echo "Illegals chars found";
    $parsed = "/home/";
}

if(isset($_GET['source'])){
    highlight_file(__FILE__);
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tree Viewer</title>
</head>
<body>
    <a href="/?source">Source code</a>
    <hr/>
    <form action="/" method="post">
        <label for="input">Directory to check</label>
    <input type="text" placeholder="Directory to see" id="input" name="input" value="<?= $parsed ?>">
    </form>

    <h3>Content of <?= $parsed ?>: <?= shell_exec('ls '.$parsed); ?></h3>
    
</body>
</html>

";"や"|"は使用できないので、以下を入力し、flag.txtの内容を読み取る。

&& cat /home/flag.txt

結果は以下の通りで、フラグが読み取れた。

Content of && cat /home/flag.txt: index.php Flag: PWNME{U53R_1NpU75_1n_5h3lL_3x3c_37}
PWNME{U53R_1NpU75_1n_5h3lL_3x3c_37}

kNOCk kNOCk (Forensic)

"http"でフィルタリングする。MalPack.debを受信しているので、エクスポートする。
MalPack.debを解凍すると、simplescript.shが展開される。このファイルには以下のように書かれていて、フラグが得られる。

#!/bin/bash

echo "PWNME{P4ck4g3_1s_g00d_ID}"
PWNME{P4ck4g3_1s_g00d_ID}

Just a XOR (Crypto)

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

・MESSAGE: original-message.txtの内容
・SECRET: 0以上0x2600以下のランダム整数を文字化したものの16個の配列
・ret = encrypt(MESSAGE)
 ・MESSAGEとSECRET(繰り返し)のXORの数値を文字列化したものの配列を返却
・retを","区切りで出力

平文で分かっている部分があるので、16バイトごとのブロックに分けて、SECRETを割り出す。あとは割り出したSECRETで復号すればよい。

#!/ujsr/bin/env python3
with open('intercepted-original-mesage.txt', 'r') as f:
    pt_masked = f.read()

with open('message-encrypted.txt', 'r') as f:
    ct = list(map(int, f.read().split(',')))

SECRET = [0] * 16
for i in range(len(pt_masked)):
    if pt_masked[i] != '*':
        SECRET[i % 16] = ord(pt_masked[i]) ^ ct[i]

pt = ''
for i in range(len(ct)):
    pt += chr(ct[i] ^ SECRET[i % 16])
print(pt)

復号結果は以下の通り。

So, I can see you know how XOR works.. Congratulation :) Here is your flag: PWNME{1t_W4s_r34aLy_Ju3s7_A_x0R} ! Good luck for the next challenges
PWNME{1t_W4s_r34aLy_Ju3s7_A_x0R}

Gib me Initials of Victor (Crypto)

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

・KEY: 16バイトランダム文字列
・iv: 16バイトランダム文字列
・encrypted: FLAGをパディングし、AES-CBC暗号化
・signature: ivとKEYの逆順のXORをhexエンコードしたもの
・ciphertext: ivの16進数表記の4バイト目以降 + encryptedの16進数表記 + signature
・ciphertextを出力

ivがわかれば、signatureからKEYを算出することができる。ivの16進数表記の先頭4バイトが不明なので、ブルートフォースする。

#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad
from Crypto.Util.strxor import strxor

with open('output.txt', 'r') as f:
    output = eval(f.read())

ciphertext = output['ciphertext']
iv_tail = bytes.fromhex(ciphertext[:28])
encrypted = bytes.fromhex(ciphertext[28:-32])
signature = bytes.fromhex(ciphertext[-32:])

found = False
for iv0 in range(256):
    for iv1 in range(256):
        iv = bytes([iv0, iv1]) + iv_tail
        KEY = strxor(iv, signature)[::-1]
        cipher = AES.new(KEY, AES.MODE_CBC, iv)
        FLAG = cipher.decrypt(encrypted)
        if FLAG.startswith(b'PWNME{'):
            found = True
            FLAG = unpad(FLAG, 16).decode()
            print(FLAG)
            break
    if found:
        break
PWNME{1t_1s_d4ng3r0us_wh3n_t0_m4ny_1nf0_4r3_g1v3n}

Scream Like Viking (Crypto)

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

・e = 17
・p: 512ビット素数
・q: 512ビット素数
・N = p * q
・以下繰り返し
 ・cmd: 入力
 ・cmdが"Encrypt"の場合
  ・m: 数値入力
  ・c = encrypt(m)
   ・m1: mを文字列化し、パディング後、数値化したもの
   ・c = pow(m1, e, N)
   ・cを返却
  ・cを表示
 ・cmdが"Flag"の場合
  ・c = encrypt_flag()
   ・m1: flagをパディング後、数値化したもの
   ・c = pow(m1, e, N)
   ・cを返却
  ・cを表示
  ・終了

まずNを求めたい。
mに0x0101...01(01を49個)の10進数を指定すると、パディングした後の数値は0x01...01(01を50個)となる。
mに0x0202...02(02を48個)の10進数を指定すると、パディングした後の数値は0x02...02(02を50個)となる。
mに0x0404...04(04を46個)の10進数を指定すると、パディングした後の数値は0x04...04(04を50個)となる。
mに0x1010...10(10を34個)の10進数を指定すると、パディングした後の数値は0x10...10(10を50個)となる。
上記のそれぞれの暗号化したものをc0, c1, c2, c3とすると、以下の式が成り立つ。

c0 = pow(m0, e, N)
c1 = pow(m1, e, N) = pow(m0 * 2, e, N) = pow(m0, e, N) * pow(2, e, N) % N
c2 = pow(m2, e, N) = pow(m0 * 4, e, N) = pow(m0, e, N) * pow(4, e, N) % N
c3 = pow(m3, e, N) = pow(m0 * 16, e, N) = pow(m0, e, N) * pow(16, e, N) % N

c1 ** 2 = pow(m0, e, N) ** 2 * pow(4, e, N) % N = c2 * c0
c2 ** 2 = pow(m0, e, N) ** 2 * pow(16, e, N) % N = c3 * c0

(c1 ** 2 - c0 * c2) % N = 0
(c2 ** 2 - c0 * c3) % N = 0

このことから以下を計算し、Nを割り出す。

GCD(c1 ** 2 - c0 * c2, c2 ** 2 - c0 * c3)

あとは17個のフラグのNとcの情報を集め、Hastad's Broadcast Attackで復号する。

#!/usr/bin/env python3
import socket
from Crypto.Util.number import *
from sympy.ntheory.modular import crt
from gmpy2 import iroot

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

e = 17
ms = []
ms.append(int('01' * (50 - 1), 16))
ms.append(int('02' * (50 - 2), 16))
ms.append(int('04' * (50 - 4), 16))
ms.append(int('10' * (50 - 16), 16))

ns = []
cs = []
for i in range(e):
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('13.37.17.31', 50826))

    try_rsa_enc = []
    for m in ms:
        data = recvuntil(s, b'> ')
        print(data + 'Encrypt')
        s.sendall(b'Encrypt\n')
        data = recvuntil(s, b'> ')
        print(data + str(m))
        s.sendall(str(m).encode() + b'\n')
        data = recvuntil(s, b'\n').rstrip()
        print(data)
        try_rsa_enc.append(int(data))

    diff1 = (try_rsa_enc[1]) ** 2 - try_rsa_enc[0] * try_rsa_enc[2]
    diff2 = (try_rsa_enc[2]) ** 2 - try_rsa_enc[0] * try_rsa_enc[3]
    N = GCD(diff1, diff2)
    for j in range(128, 1, -1):
        if N % j == 0:
            N = N // j
    ns.append(N)

    data = recvuntil(s, b'> ')
    print(data + 'Flag')
    s.sendall(b'Flag\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    c = int(data.split(' ')[-1])
    cs.append(c)

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

me, _ = crt(ns, cs)
m, success = iroot(me, e)
assert success
flag = long_to_bytes(m).decode()
print(flag)

実行結果は以下の通り。

        :
        :
Enter your option (Encrypt or Flag) > Encrypt
Enter your integer to encrypt > 39556523867752888934680134375046137780785934326428056262724561221517273621581513398125694750002111388417634374582529
29633761006210799671079900862868953846620965510698716181289191451019372254407607821971507525514615344143220143236842405676330342078065560317144573874568182068936309264043154036785015344167898045818780966096895947373587419825710039031260032556421169844324882703080060884164900140948076666108490483898383157035

Enter your option (Encrypt or Flag) > Encrypt
Enter your integer to encrypt > 309035342716819444802188549805047951412390111925219189552535634543103700168605573422856990234391495222012768551426
128083558475595433415966339646450338708213951915470947601427400794294574289697675308358729608310777818618335423067966360123927121562309003166598342100173291500854368152675807228383806204106364006051875366526471632927753790741727626397147873607563634083318236457492180334993861720119042978695226755613171841602

Enter your option (Encrypt or Flag) > Encrypt
Enter your integer to encrypt > 9431010214746687158269914239656004376598819333655370774918690019015615849871996259242461860180404517273338884
2030173969955998081047276642914807292555882299524834822202648251388504491525242385512647350712654076305667498660162407487840741496752398157554477563259579760584158655906716712867185096760496244032932687904717683507453805445886480728088846392943252050219859343817907066007948513007010821736453651569663353629

Enter your option (Encrypt or Flag) > Encrypt
Enter your integer to encrypt > 476144336329835556597907330103803653588825205650034196385248139641888305197355024
36867698018653225660646796911813096513896552187458954348668042407946732181664035935437205393530090513662633219424058272867059565745299990599147729704345702304350390563126649556411649086879518321648929149041447462918839877819645544933595122529548434389253294655502120212642876476616254917502272772746426078006

Enter your option (Encrypt or Flag) > Flag
Flag cipher for you: 118178640531120002813489649187374749396638007537617761680519449467109891593676428105297803624604678778592660363014634306482278073291516748497375553282217093906961754170402445206627547551241543806364181800665131288734507203106000004949400678755865538501950878684696369241559600655381680349235109535121459317355

Enter your option (Encrypt or Flag) > Encrypt
Enter your integer to encrypt > 39556523867752888934680134375046137780785934326428056262724561221517273621581513398125694750002111388417634374582529
24988348200700366323117331723978733262721034978344586088372494326201123568560953516130828633042178543713642534675295499438192580118342624158503119444269878625514653717174867128212085236268025572978484482171050294699385917331799042490755772864997744492751621263236745806598516963660294653279374429712734244518

Enter your option (Encrypt or Flag) > Encrypt
Enter your integer to encrypt > 309035342716819444802188549805047951412390111925219189552535634543103700168605573422856990234391495222012768551426
64394564090285413077281557852248232558899594226504285011793151738285962134497528830441168301671047784664825529783442285883501449276313100251860086916081438650640789887493753001570711468406048883106582871302353293801375621518446347638484703292748265446044394663116529045892937120454887377096415160485307705185

Enter your option (Encrypt or Flag) > Encrypt
Enter your integer to encrypt > 9431010214746687158269914239656004376598819333655370774918690019015615849871996259242461860180404517273338884
64420705452562595765855601654000387350221282257377887299060920348705624231173202829434728146419025689231709002098310921491910890742357255443517035631942672802851761459446697054291992336714690905853946998470646456310593356104339713520143262501950149881425736889256304366005014728208256404814923236934188623511

Enter your option (Encrypt or Flag) > Encrypt
Enter your integer to encrypt > 476144336329835556597907330103803653588825205650034196385248139641888305197355024
10229535008043355337839552369745908652789428345920492252742367878711614571772336463643779901077048866699194393451649320412010939361176277448954629624176370164966569369369995979886953650786979385453526928436091310187924223208984516862802315958447416917418338253129225116143711690533304096251317775639628492354

Enter your option (Encrypt or Flag) > Flag
Flag cipher for you: 72797884866378036732723267345532859660533267204876200124160446622417470035264689159495398403165727505001687803682459676198823846141415978216417430108587649011166743614922712595421623389546905713366101335904154156553764428130057629805301964988887571187976406622888492362055556293392891767112707540525469119418

PWNME{h4st4rd_br0adc4st_d3str0y_s1mple_p4dd1ng}
PWNME{h4st4rd_br0adc4st_d3str0y_s1mple_p4dd1ng}