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

Leave a Reply

Your email address will not be published.