Engineer CTF 2022 Writeup

この大会は2022/3/5 18:30(JST)~2022/3/6 18:30(JST)に開催されました。
今回もチームで参戦。結果は 6979点で302チーム中15位でした。
自分で解けた問題をWriteupとして書いておきます。

Sanity Check (easypeasy)

問題にフラグが書いてあった。

CTF{Welcome}

Join our discord (easypeasy)

Discordに入り、#you-should-come-hereチャネルのメッセージを見ると、フラグが書いてあった。

CTF{Welcome_to_ectf22}

whatisthis (Misc)

zipを解凍し、展開されたpngファイルを見たが、pngの形式にはなっていない。

$ file whatisthis.png 
whatisthis.png: POSIX tar archive
$ mv whatisthis.png whatisthis.tar
$ tar xf whatisthis.tar

中身を見てみると、各ディレクトリに以下のような構成になっている。

・json
・layer.tar
・VERSION

各layer.tarを解凍・展開してみていく。
a4ddd19c83968edda65f7205efc664cab87fe1522d7bec7252384aa259f37b39\layer.tarを展開すると、a4ddd19c83968edda65f7205efc664cab87fe1522d7bec7252384aa259f37b39\layer\etc\m\m\nothing.txtにフラグが書いてある。

CTF{D0cker_ent3r5_the_p4rty}

Not really random (Misc)

2022/3/1 00:00:00のUNIXTIMEからブルートフォースで同じ乱数が出るseedを探し、フラグを出力する。

import random
import time
import hashlib

seed = 1646060400

while True:
    random.seed(seed, version=2)
    rand = random.random()
    if rand == 0.33567959567961436:
        break
    seed += 1

while True:
    rand = random.random()
    has = hashlib.sha256(str(rand).encode()).hexdigest()
    flag = f"CTF{{{has}}}"
    if "7a2" in has:
        print(flag)
        break
    else:
        print(f"Bad random value: {rand}")

実行結果は以下の通り。

Bad random value: 0.8913897703358419
Bad random value: 0.3032054069265432
Bad random value: 0.6860829464688437
Bad random value: 0.2658087107328536
Bad random value: 0.8903005048882441
Bad random value: 0.914630909612433
Bad random value: 0.9688578899818961
Bad random value: 0.7925090397955323
Bad random value: 0.10136501216336935
Bad random value: 0.568451491382639
Bad random value: 0.16898065821921437
Bad random value: 0.5541712073794856
Bad random value: 0.029926361216790154
Bad random value: 0.18218590474521223
Bad random value: 0.49713845657579536
Bad random value: 0.7631162105077507
Bad random value: 0.7386939443532723
Bad random value: 0.5815609491717452
Bad random value: 0.5905894610211082
Bad random value: 0.09018146469820387
CTF{a13a806d175841731b24a01e9af240bc81750967542550a4b3bb77a29a9d291b}
CTF{a13a806d175841731b24a01e9af240bc81750967542550a4b3bb77a29a9d291b}

Packman 1 (RE-pwn)

$ strings chall | grep CTF
CTF{ee_lo_p3ll3t_kh4o}
CTF{ee_lo_p3ll3t_kh4o}

Lost Password (RE-pwn)

$ strings main | grep CTF
CTF{HaC

Ghidraでデコンパイルする。

undefined8 main(int param_1,long param_2)

{
  int iVar1;
  long in_FS_OFFSET;
  int local_3c;
  int local_38 [4];
  undefined4 local_28;
  undefined4 local_24;
  undefined4 local_20;
  undefined4 local_1c;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_38[0] = 0x9e;
  local_38[1] = 0x86;
  local_38[2] = 0xa5;
  local_38[3] = 0xb2;
  local_28 = 0xa0;
  local_24 = 0xb4;
  local_20 = 0xa1;
  local_1c = 0xd0;
  if (param_1 == 2) {
    printf("Checking %s\n",*(undefined8 *)(param_2 + 8));
    iVar1 = strcmp(*(char **)(param_2 + 8),"");
    if (iVar1 == 0) {
      for (local_3c = 0; local_3c < 8; local_3c = local_3c + 1) {
        putchar(local_38[local_3c] + -0x53);
      }
    }
    else {
      puts("WRONG!");
    }
  }
  else {
    puts("What you doin?? Need Argument");
  }
  if (local_10 == *(long *)(in_FS_OFFSET + 0x28)) {
    return 0;
  }
                    /* WARNING: Subroutine does not return */
  __stack_chk_fail();
}

引数に"mingw.exe"を指定すれば良さそう。

$ ./main mingw.exe
Checking mingw.exe
K3R_MaN}
CTF{HaCK3R_MaN}

You're too blind to see (Web)

HTMLソースを見ると、スクリプトにこう書いてある。

    function generateHash(plainText) {
        var md = forge.md.sha256.create();
        md.start();
        md.update(plainText, "utf8");
        var hashText = md.digest().toHex();
        return hashText;
    }

        :

    function checkLogDetails() {
        var username = document.getElementById("Uname").value;
        var password = document.getElementById("Pass").value;
        if (username == "Rick Astley" && generateHash(password) == "1b638a7a9a56a4485ebd95816d1d8abf0576cdbf39854c6dd5cb47c3c53f48be") {
            redirect_site = 'redirect'+ password + '.html';
            window.open(redirect_site, '_blank');
        } else {
            window.open('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
        }
    }

以下を入力できればよい。

username: Rick Astley
passwordのsha256: 1b638a7a9a56a4485ebd95816d1d8abf0576cdbf39854c6dd5cb47c3c53f48be

またコメントにこう書いてある。

<!-- bmV2ZXJnb25uYWxldHlvdWRvd24wMTIyQGdtYWlsLmNvbSBrbm93cyB0aGUgcGFzc3dvcmQ -->
$ echo bmV2ZXJnb25uYWxldHlvdWRvd24wMTIyQGdtYWlsLmNvbSBrbm93cyB0aGUgcGFzc3dvcmQ= | base64 -d
nevergonnaletyoudown0122@gmail.com knows the password

nevergonnaletyoudown0122@gmail.comにメールで連絡をしたら、以下のメッセージが返ってきた。

never_gonna_say_goodbye
$ echo -n never_gonna_say_goodbye | sha256sum
1b638a7a9a56a4485ebd95816d1d8abf0576cdbf39854c6dd5cb47c3c53f48be  -

ハッシュが一致した。以下の情報でログインしてみると、フラグが表示された。

username: Rick Astley
password: never_gonna_say_goodbye
CTF{W3r3_N0_57r4N63r5_70_10V3}

Hack the Auth (Web)

HTMLソースを見ると、スクリプトのこう書いてある。

        var userAuthentication = function() {
            var username = document.getElementsByName('username')[0].value;
            var password = document.getElementsByName('password')[0].value;
            var hash = sha256(password);
            if(hash === "dfbec338b51c5643ba481625e1075236d3a9a07fbd6393763f253e99024958a4" && username === "admin") {
                window.location = "./q1?username=admin&password=" + password;
            } else {
                alert("Invalid username or password");
            }
        };

以下を入力できればよい。

username: admin
passwordのsha256: dfbec338b51c5643ba481625e1075236d3a9a07fbd6393763f253e99024958a4

CrackStationでクラックする。

Thisismypassword

以下の情報でログインすることができる。

username: admin
password: Thisismypassword

ログインすると、このようなメッセージが表示される。

Congrats! You got the admin password!

クッキーのHack-the-Authキーにフラグが設定されていた。

CTF%7BSHA256_AND_WEB%7D
CTF{SHA256_AND_WEB}

Favourite Website (Web)

このページを12893422回目に閲覧したときにフラグが表示されるようだ。リロードすると、回数が増えていく。
Cookieを見ると、_gata~_gatoまでbase64文字列が設定されている。これを見ていくと、_gatjに回数を含むデータが含まれていた。

$ echo c3RpbGwgY2Fubm90IHNvbHZlIHRoaXMgdGFzaz90aW1lcz0wMDAwMDAwMSZpZD0xNzhiNzI2Mi1kNTIzLTQyMDktYjY0OS0yMTIyZDBmMzFmZDk3 | base64 -d
still cannot solve this task?times=00000001&id=178b7262-d523-4209-b649-2122d0f31fd97

timesに12893422を設定する。

$ echo -n "still cannot solve this task?times=12893422&id=178b7262-d523-4209-b649-2122d0f31fd97" | base64
c3RpbGwgY2Fubm90IHNvbHZlIHRoaXMgdGFzaz90aW1lcz0xMjg5MzQyMiZpZD0xNzhiNzI2Mi1k
NTIzLTQyMDktYjY0OS0yMTIyZDBmMzFmZDk3

Cookieの_gatjに以下を設定する。

c3RpbGwgY2Fubm90IHNvbHZlIHRoaXMgdGFzaz90aW1lcz0xMjg5MzQyMiZpZD0xNzhiNzI2Mi1kNTIzLTQyMDktYjY0OS0yMTIyZDBmMzFmZDk3

リロードしてみると、フラグが表示された。

Congrats! You got the flag: CTF{C@@kie$_@re_the_be$t}
CTF{C@@kie$_@re_the_be$t}

THE CAT KNOWS THE CULPRIT (Forensics)

$ exiftool clue.jfif 
ExifTool Version Number         : 10.80
File Name                       : clue.jfif
Directory                       : .
File Size                       : 113 kB
File Modification Date/Time     : 2022:03:05 18:47:32+09:00
File Access Date/Time           : 2022:03:05 18:47:43+09:00
File Inode Change Date/Time     : 2022:03:05 18:47:32+09:00
File Permissions                : rwxrwxrwx
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : None
X Resolution                    : 1
Y Resolution                    : 1
Comment                         : CTF{It_1s_D0UG_JUdY}
Image Width                     : 1400
Image Height                    : 1400
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:4:4 (1 1)
Image Size                      : 1400x1400
Megapixels                      : 2.0

Commentにフラグが設定されていた。

CTF{It_1s_D0UG_JUdY}

Father's Day Dilemma (Forensics)

index.htmlを見ると、スペースによる不自然なインデントがある。問題には、以下のことが書いてある。

・数が好きなドットとダッシュの2人の子供がいた。
・彼らは8より大きい数字しか好きではなかった。
・ドットは数字の10を見つけるために使っていた。
・ダッシュは数字の12を見つけるために使っていた。

このことから、

タグごとにインデントスペースが8バイトより大きい部分を見て、インデントスペースが10の箇所を"."、12の箇所を"-"にして置き換えると、モールス信号になると推測できる。置き換えると、以下のようになる。

.--. .--- -.-. --- --- .-..

デコードする。

PJCOOL
CTF{PJCOOL}

I can do this all day. (Forensics)

ファイルがたくさんあるので、grepで検索する。

$ grep -rl CTF ./icandothisallday/
./icandothisallday/Pip_the_Troll.txt
$ cat ./icandothisallday/Pip_the_Troll.txt
Okay. Okay. Ready.CTF{d3cry471ng_nu3l34r_c0d35}		strings * | grep <word> would've been faster 
CTF{d3cry471ng_nu3l34r_c0d35}

Heavens and Earth (Forensics)

jpgの後ろにzipがくっついている。zipを切り出し解凍すると、2つのpngファイルが展開される。各ピクセルでXORをしてみる。

from PIL import Image

img1 = Image.open('earth.png').convert('RGB')
img2 = Image.open('heavens.png').convert('RGB')
w, h = img1.size

output_img = Image.new('RGB', (w, h), (255, 255, 255))

for y in range(0, h):
    for x in range(0, w):
        r1, g1, b1 = img1.getpixel((x, y))
        r2, g2, b2 = img2.getpixel((x, y))
        output_img.putpixel((x, y), (r1 ^ r2, g1 ^ g2, b1 ^ b2))

output_img.save('flag.png')

f:id:satou-y:20220314074634p:plain
実行した結果、文字が現れる。

J__B0NWBCU13L

Rail Fence Cipherと推測し、https://www.dcode.fr/rail-fence-cipherで復号する。

J   _   _   B
 0 N W B C U
  1   3   L
J01N_W3B_CLUB
CTF{J01N_W3B_CLUB}

Save Our Souls! (Forensics)

wavの先頭4バイトが壊れているので、"RIFF"に修正する。Audacityで開き、スペクトログラムを見ると、メッセージが現れた。
f:id:satou-y:20220314074808p:plain

WE-NEED-REINFORCEMENTS
CTF{WE-NEED-REINFORCEMENTS}

TVA's Trap (Crypto)

改行やスペースを除くとbase64文字列になるので、8回デコードする。

#!/usr/bin/env/ python3
import base64

with open('8-6-4.txt', 'r') as f:
    data = f.read()

data = data.replace('\n', '').replace(' ', '')

for _ in range(8):
    data = base64.b64decode(data)

flag = data.decode()
print(flag)
CTF{w3c-ctf-z0z2_3ry9t0}

Shakespeare and Encoding (Crypto)

2進数コードをデコードし、ASCIIコードで-1方向にシフトする。

#!/usr/bin/env python3
codes = '01000100 01010101 01000111 01111100 01011010 00110001 01010110 00101110 01110101 01110000 01110000 00101110 01000011 01110011 01110110 00110010 01110110 01010100 01111110'
codes = codes.split(' ')

msg = ''
for code in codes:
    msg += chr(int(code, 2) - 1)
print(msg)
CTF{Y0U-too-Bru1uS}

Locked Out (Crypto)

QRコードを読み取ると、base64文字列になっていた。

aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xRDBtTkp0YThkRFRDY2VCREkycG9YS3AyYlJhMEcya0Ivdmlldz91c3A9c2hhcmluZw==
$ echo aHR0cHM6Ly9kcml2ZS5nb29nbGUuY29tL2ZpbGUvZC8xRDBtTkp0YThkRFRDY2VCREkycG9YS3AyYlJhMEcya0Ivdmlldz91c3A9c2hhcmluZw== | base64 -d
https://drive.google.com/file/d/1D0mNJta8dDTCceBDI2poXKp2bRa0G2kB/view?usp=sharing

base64デコードしたらGoogleドライブのURLになったので、アクセスしてファイルをダウンロードする。
そのファイルには2進数で8桁ごとにスペースで区切られているデータが書いてあるので、デコードする。

#!/usr/bin/env python3
with open('USPatent4405829.txt', 'r') as f:
    codes = f.read().split(' ')

msg = ''
for code in codes:
    msg += chr(int(code, 2))
print(msg)

実行結果は以下の通り。

c = 7424699238646955201370695213431987671142812230143306867560482933056266299380926684689129315498363439046922143979269386180477235672715428337919573885741855
n = 14783703403657671882600600446061886156235531325852194800287001788765221084107631153330658325830443132164971084137462046607458019775851952933254941568056899
e = 0x10001

RSA暗号のパラメータになったので、nをfactordbで素因数分解してみる。

p = 121588253559534573498320028934517990374721243335397811413129137253981502291629
q = 121588253559534573498320028934517990374721243335397811413129137253981502291631

あとはそのまま通常通り復号する。

#!/usr/bin/env python3
from Crypto.Util.number import *

c = 7424699238646955201370695213431987671142812230143306867560482933056266299380926684689129315498363439046922143979269386180477235672715428337919573885741855
n = 14783703403657671882600600446061886156235531325852194800287001788765221084107631153330658325830443132164971084137462046607458019775851952933254941568056899
e = 0x10001

p = 121588253559534573498320028934517990374721243335397811413129137253981502291629
q = 121588253559534573498320028934517990374721243335397811413129137253981502291631
assert p * q == n

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode()
print(flag)
CTF{r0n_4D1_130N4rd}

climb the HILL (Crypto)

Hill暗号。文字列のテーブルは英小文字、数字、{}_の順番にして復号する。

#!/usr/bin/sage
import string

def decrypt(K, ct, alphabet):
    ct = [[alphabet.index(c)] for c in ct]
    C = matrix(Zmod(n), ct)
    P = K.inverse() * C
    pt = ''.join([alphabet[P[i][0]] for i in range(P.nrows())])
    return pt

alphabet = string.ascii_lowercase + string.digits + '{}_'
ciphertext = 'z3{ryu0kdhhxwuph__oin4}{h5cn78y6a{obebxbh932j'

n = len(alphabet)
K = matrix(Zmod(n), [[6, 24, 11], [12, 13, 10], [20, 17, 15]])

flag = ''
for i in range(0, len(ciphertext), 3):
    ct = ciphertext[i:i+3]
    pt = decrypt(K, ct, alphabet)
    flag += pt
print(flag)
{y0u_h4ve_cl1m6ed_the_h1ll_here_1s_y0ur_fl4g}
CTF{y0u_h4ve_cl1m6ed_the_h1ll_here_1s_y0ur_fl4g}