好奇心の足跡

飽きっぽくすぐ他のことをしてしまうので、忘れないため・形にして頭に残すための備忘録。

picoCTF2021 [Reverse Engineering] writeup

2021年3月16日~3月30日(日本時間では3月17日~3月31日)に開催された中高生向けのCTF大会、picoCTFの[Reversing]分野のwriteupです。
その他のジャンルについてはこちらを参照

tech.kusuwada.com

Transformation

I wonder what this really is... enc ''.join([chr((ord(flag[i]) << 8) + ord(flag[i + 1])) for i in range(0, len(flag), 2)])

encファイルが配布されます。
開いてみると

灩捯䍔䙻ㄶ形楴獟楮獴㌴摟潦弸弰摤捤㤷慽

わーお。全然わからん。問題文のスクリプトの逆をするコードを書いて動かしてみます。

enc = '灩捯䍔䙻ㄶ形楴獟楮獴㌴摟潦弸弰摤捤㤷慽'
for c in enc:
    print(chr(int('{:016b}'.format(ord(c))[:8],2)), end='')
    print(chr(int('{:016b}'.format(ord(c))[8:],2)), end='')

実行結果

picoCTF{16_bits_inst34d_of_8_0ddcd97a}

keygenme-py

keygenme-trial.py

keygenme-trial.pyが配布されます。

keygenme-trial.py (長いので折りたたみ)

#============================================================================#
#============================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 = "MORTON"
bUsername_trial = b"MORTON"

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_nv3JrW2AMPOanzoOatT8tWrZSH9V2-H_-sY8RFTh1Vr9guvCw3iIUq7eVs4IhR2u6bI_HkJm6u5VK99vYIKXfthUYjQRMNmo6uMwqdK8ZYm3wH3Z2BlFNmGxMSXKmFhDQujSexuuMg5ZFIb5VhyZeUY4R9KubbghdUIlZ2hBeCpxef_ioNjM7VUKJEyJgxiAtPdMKSgT8y-FJsikUsw9Scbe8nH-bC856u_mqk6AKQnORLmxfsJykFMFm-wOh-unFnUvg9HiT6lYXCrFnXNEFEnq5djwM9H4iRXYwyO4XdHEqVcodEyE3HoPFOh24R9ATElafkxty16jECSabI7k608v6sk2Pxd-EAI0XEtjlVE5Qz-qdnoTIXEXXbwK8Bpw33JwRGJs-WduRmF2G5qVVAGONjaAc9CPXhawUkf2_sNMlnq6lqn7sBb2K7BiqW2Efc88pzfsOYvGQC94CBBhIUQTePRcZ_bUbsDLR8PrWlNQ28Vt6P45URpPDtxkUGQtixiQi9QlPThCFJs28XmApvRQQfJHdje45t2ELeVgn5YMi_FHQ98qgMudwXHXprigHAMmcB2rFPWWhAQn4sIrdL2In424RWO0qOhd9IJrU7DldcrmOMyvpHV87HzVgsRnI97Hn-jiVi_FemDWAsDGFwjaGbrxWUfr01ienDjlrXc-NC2x1Tt6jTHiFb2LbUqzGyjJ-m-5eCuqr1zh8_oCypnBB79XuKMGlngql0NQVEReZ9pDZd3Iax0TJVM55luvuCpjolarAJXz28Ynt5et1DHfnAQ1gQ4laW3ZtRL6TTp1vRpfllOURLpAxNrY03a7cMS5iIS7nXMgllh-uV9S1O3Ww4z_nPSh3q-1yKUilHfuXwdazGjBvfvwDSD3ZgvzgDmvQ-eCNV4I-oEHlAjqAiuwMvClU3Pr-mmBg257A8LHQlJJ7Rl6oYpW-B43esP6sQ2G66ofHbx4ZyUhqH5WC6jjoSuxyRFs6Z6wiNDztgkSdqbSc7iTxtF8oHBqPI_boBwr7YskpWmgwNFhWPzxGwyhMI2H38WGx1klLhNKsa4Ep9vcTuj_ltCL_QZkIgfgklf3QYGWBNLjIPBPkv1c0GqYMPkhnc4Libqdr8LXZJug2ry1s-H16d2U7_cjo9d8usNG2nNhUQvJ8d5I58r3IlioEUWjkUfWy4t3Aw8UN43iZ62MyzE7yzNIYMIwIxgB9qXmrFkh3pFnoZKlPHh7rjVBlbsIgPl64Ewi70P2AdkuDe7BXQ6kX4rdWm3i18ePWIX8n0WYz4eyGz41eRXU2hjyJeIz4Wi0rcjx39LS9BXWq4qHYnT3WNGzONy1ArEiNNiXYk61CyJiaCmFX2BZ5NL85O0ePD-bf7cn7KIWvTGqUoXIP-j1FAYvJy47myTITkurZ7YwEHIAk2sejWMltN-JeI6Jqu8nFKTl1h1sbNpCvAnJmSFczKN9fTJONndpK_PpPbUIilA1UHeqtsQfhNpyx1KK6xeFYyCa0V2sLDnHIHWREXMgcB99TUYSV0_DpbIvwQIaINt7cTDQWWvD7tyFXWltw7VqGAUCgtWYAOIoXj9ZxyOCwuIBHXGD8h8r_kBWx8GlN2Mn70ALuVpPWfR_Y55SHGy3KEUtgig92ms5758hdTQjbvb9wk9byAXvAwpltWQG86XlCIIZSM2vpDqpQ6l-8j_BBir_oXjjuaHQKEw3A3ISpLTP9ZKqcRuDAyeiNNNO_HuPK8hxNFzy7Zvu46tH3waLaPW_v_OivUOx7rP8YxocSv6ON_lwR4sRj3mvAk_WPbXpGQaRaNL813pp5aPzOkUAuod25H6esLw0Io7nCirZ3ZhPKfhtY0bakpTl2o5JJGe_2eEm_D6afBnhnkpEaJOsJhkPj7BXBZH9VJJlBWsHOV8WM3sgy1dBQjCP4C0ThSBhEUwq1mPIFKsvffKJgianCnk0fdmpFOLDJci9i5LDAB-sra6RM4y-HhV2gt3PSvyB_H0WDcdQhSZHVz6U_yNMucIW8-4YwdRwec6HGrNneiyIlacHlKO15PCwWBogndUApfLYrxKHHbxIU1sDybMlIEgBBu9_3gOStqXZu4zBDR93P-qYGz-0jrEHq3YVN9olBLx9yoOVI2AZ9m0PJwpKHYc2uZsciFy__dckefZ79zycUzeSKuKbhaZozQOvdLKwmtcB8y5n3_nwwN6-FQu9APoLqsZoaq3wT5QM6ax-_FS9XwzhqEunognjINxAXu660db73L710noV3CBT76aDY3hiV7fnYhQTfysI4SHQtnAzHXMbxYmNfbMnOALTAkQt7Yaiy_NGAr7jmNOAQwhHIW8eaOsCfhRPl7XjdM7pX2N4EM5cFAEphKWQ62f_3RhnyvaTDNmDXkde-_2FjfBMGirouVJL2rSA-icdD1pfkCKO7U0An5V7AUKuYDTZXTXE4DFWUIq4K36jFmHJ9fwllHoe1_A22RuP4DHm4o7TYEJIeo5UvaVc9Lfd6edWIn8KqDzhTz6TZLWlSbK_RnZlpCv3rdibpHzE-epOv1y5pDv08R8jWnVzHv0c3DgfJzWknRd-S5hIIOd5FEyCAx4NpOqYJjpD109ijZLzDMxuvml8QJmX2tplAp-5xGGiVAFPLse1Vo95f5Wsh8KXwnbNjFQSYf8AgskPp_2spP7rBpVvDrn6HJp7TvQY6mNZMXgLOWy8WHynKESedYkD7iQSHwZ5iMvJTVLDHjYbll0PZyI-Tsy7Q9ZP4ysdzkL5TwGwHa53UhUsiLQmhpkht4dKg3kqq-pA-f5PwKOWKCsKwtUyGFvz9gL7yDNNQwNpSaN2vBy5U8ulVA8SooM9SwmelNHOKvF2rmy6nOQ8mzt-vnnKqCTnc_4VgNakpw86IQVsw7VGQEGTNLD8ULL8bFkSlfICvDQKSLOPONExox17G4YuXFRpDCGoK3KLbM4oKQCi9QWPOL0jhovl0zu1j-XIY7IA2SX4C_Ie9MV27amQi7mwFHYpj3081isJOZd_cUxszijjqOFI71IBeHwSHj5wOnWRUHVLk-aOCT_rKKpae6mPyL0uJsIS1s0B62scEcn0wFa90pjuARSPhc35VCA9TFDcq6W0tAi4aNNzT-vgv4NkqfR6hh_jqs0irQZj7jtUh4OGbkaZljMovt7UYBKd9L4m5gmh1w672_Oif5pDU3YXMdc4FSp39f1wWDvHz0Uw7grUTlvLL3ge6f-Elxe2c0n56nWxs4qo4rqGY9c5BuIFxly_6iymzec33Yo8r0_L5VIRIoWl6lcZq-uIRdTl8mzbVDEoU7r1P6mvg3xUfKfF-Q-XnlfV23XNLSuIF3JF78DkjkNxdOP9dM4dCHbexa5yKeNqAan6Rq7LCnlxTGqP_lxpJO4GvU6npINZ1qjp9ydUveK-Ya7o6rfJqN1GbITvUdaMOFq00OfOsKdZXn7gB_I6el_O-YKJcx3W2X_im81FwT_8P-zW_jEdO-lmztiEyVEvUOFJJfAm-K_G4-HEYzROMPrgof708fnnxDaWGiUi0AtJeEk4sHvf2PTiE0517FcKadlELd_4R-eLKk9Wwu24faJ_R0Ej8NinIbeqCUTb0LkIKa5ChJbL4nXs9f019rUFewPDJhpVLy4nJywzVou9_7-DCMxUT3ld1PjfumXvAbYejzN4Ubue-yfBwPFe_U2YoLM-CyCPNXCbP8WKzCzzNwCcLf8OcQ6gbsyhi0Ul9urHnqWK93qVmPZLEVG9QBEmKPtN6FNWtNIomFiLnl6C9dwbSCWxUJWU_dP7tmVzundLgQhiZhgQz2T7ffSf1aNGSR5hklFayb-MyfOJm46EPx1hVVgqIcdxg7Ko4gt6rpjwAYRQ==
"""



# Enter main loop
ui_flow()

if jump_into_full:
    exec(full_version_code)

めっちゃ長い。とにかく動かしてみます。

$ python keygenme-trial.py 

===============================================
Welcome to the Arcane Calculator, MORTON!

This is the trial version of Arcane Calculator.
The full version may be purchased in person near
the galactic center of the Milky Way galaxy. 
Available while supplies last!
=====================================================


___Arcane Calculator___

Menu:
(a) Estimate Astral Projection Mana Burn
(b) [LOCKED] Estimate Astral Slingshot Approach Vector
(c) Enter License Key
(d) Exit Arcane Calculator
What would you like to do, MORTON (a/b/c/d)? 

4つのメニューから選べるみたい。cのライセンスキー入力部分のコードはこちら。

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")

check_keyの処理。

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

keyは入力したライセンス、username_trialはglobalに定義されてるMORTONで固定なので、keyが計算できそう。

import hashlib

bUsername_trial = b"MORTON"
user_key = 'picoCTF{1n_7h3_|<3y_of_'
arry = [4,5,3,6,2,7,1,8]

for i in arry:
    user_key += hashlib.sha256(bUsername_trial).hexdigest()[i]
user_key += '}'
print(user_key)

実行結果

$ python solve.py 
picoCTF{1n_7h3_|<3y_of_75fc1081}

念の為、enter_licenseに突っ込んで正しいか見てもらう。

$ python keygenme-trial.py 

===============================================
Welcome to the Arcane Calculator, MORTON!

This is the trial version of Arcane Calculator.
The full version may be purchased in person near
the galactic center of the Milky Way galaxy. 
Available while supplies last!
=====================================================


___Arcane Calculator___

Menu:
(a) Estimate Astral Projection Mana Burn
(b) [LOCKED] Estimate Astral Slingshot Approach Vector
(c) Enter License Key
(d) Exit Arcane Calculator
What would you like to do, MORTON (a/b/c/d)? c

Enter your license key: picoCTF{1n_7h3_|<3y_of_75fc1081}

Full version written to 'keygenme.py'.

Exiting trial version...

===================================================

Welcome to the Arcane Calculator, tron!

===================================================

よさそう!ということで、これがflagでした。

crackme-py

crackme.py

crackme.pyが配布されます。

# 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-MFE0g4dd`_cgN"

# 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()

最初にmainからcallされるようになっている choose_greatest() は、ただ入力値の大きい方を返すだけのプログラムなのでダミー。その上にある decode_secret()bezos_cc_secretをdecodeするための関数呼び出しを一行追加すればOK。

#元の関数の最後を下記のように変更
#choose_greatest()
decode_secret(bezos_cc_secret)

実行結果

$ python crackme.py 
picoCTF{1|\/|_4_p34|\|ut_8c551048}

ARMssembly 0

What integer does this program print with arguments 4004594377 and 4110761777? File: chall.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb})

chall.Sが配布されます。アセンブリだ。

…ちょっと読むのが面倒だったので、ヒントの "Simple compare" から、入力値のどちらか大きい値がflagに違いないと推測。

value1 = 4004594377
value2 = 4110761777

print(hex(value1)[2:])
print(hex(value2)[2:])

実行結果

$ python solvee.py 
eeb142c9
f5053f31

下の値をflag formatに合わせたやつが通りました。(全然想定解ではないけど、解けてる人が多かったのでそういうことかな、と…。アセンブリ読めない。)

speeds and feeds

There is something on my shop network running at mercury.picoctf.net:59953, but I can't tell what it is. Can you?

指定されたホストに接続してみます。

$ nc mercury.picoctf.net 59953
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
...

こんなのがばーっっとでてきます。全然わからん。
ヒントを見てみます。

What language does a CNC machine use?

CNC machine というのがキーワードらしい。

コンピュータ数値制御 - Wikipedia

これっぽい。Gコードとな。
下記に、日本語の基本的なGコード紹介サイト。

Gコードの基礎知識と使用時の注意点【機能がわかる一覧表付き】 | CAD/CAMコラム | CAD/CAMに関する資料 | 株式会社フアクト

これ自分でplotする関数角のめっちゃ大変そう…。と思ってぐぐってみると、plotしてくれるサイトがいくつかありました!今回は下記を利用。めっちゃ良い。

ncviewer.com

GCode Fileに降ってきたやつを貼り付けると、flagが描かれました!

f:id:kusuwada:20210331120402p:plain

Shop

Best Stuff - Cheap Stuff, Buy Buy Buy... Store Instance: source. The shop is open for business at nc mercury.picoctf.net 3952.

sourceが配布されます。

$ file source 
source: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, Go BuildID=Wq2z6hkBrrAovu6w8dMb/chyUPt3_NgRB5zVfMpf8/99tYvdYHy3xNdHp4wuxA/ClEGFX9e3WU6qjzPxg9K, with debug_info, not stripped

実行ファイルだ。
まずは指定されたところにつないでみます。きっとここで使われているやつが配布されているに違いない。

$ nc mercury.picoctf.net 3952
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: 

なるほど。最初は40 coinsしかもって無くて、Flagを買いたいんだけどお金が足りないと。何とかしてお金を稼がないといけないわけだ。
コードを解析する前に適当にチートしてたら通った。

Choose an option: 
1
How many do you want to buy?
-10000000  
You have 150000040 coins
    Item        Price   Count
(0) Quiet Quiches   10  12
(1) Average Apple   15  10000008
(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 57 99 49 49 56 98 98 102 125]

りんごを負の数購入すると、計算がバグっているようでお金が増えた。あとは普通にflagを買うと、10進数のflagが降ってきた。
CyberChefでFrom DecimalしたらFlagがでました🙌

ARMssembly 1

For what argument does this program print win with variables 83, 0 and 3? File: chall_1.S Flag format: picoCTF{XXXXXXXX} -> (hex, lowercase, no 0x, and 32 bits. ex. 5614267 would be picoCTF{0055aabb})

chall_1.Sが配布されます。

 .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, 83
    str w0, [sp, 16]
    str wzr, [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

また結構長い。
これに問題文の入力を入れるとどういう出力になるか、という問題っぽい。

このあたりを読みながら解読。
白目むきながら解いたので、何処か違っているところあったらご指摘ください。

 .arch armv8-a
    .file   "chall_1.c"
    .text
    .align  2
    .global func
    .type   func, %function
func:
    sub sp, sp, #32                                               w0 = 8, wzr = 3, 
    str w0, [sp, 12]        # [sp+12]のアドレスに、 w0を書き込む     8
    mov w0, 83              # w0 <- 83                            w0 = 83
    str w0, [sp, 16]        # [sp + 16] のアドレスに、w0を書き込む   83
    str wzr, [sp, 20]       # [sp + 20] のアドレスに、wzrを書き込む  3
    mov w0, 3               # w0 <- 3                             w0 = 3
    str w0, [sp, 24]        # [sp + 24] <- w0                     [sp + 24] = 3
    ldr w0, [sp, 20]        # w0 = [sp+20] のアドレスの値 (== wzr) w0 = 3
    ldr w1, [sp, 16]        # w1 = [sp+16] のアドレスの値(== 83)  w1 = 83
    lsl w0, w1, w0          # w0 = w1 << w0                       w0 = 83 << 3 = 664
    str w0, [sp, 28]        # [sp + 28] <- w0                     w0
    ldr w1, [sp, 28]        # w1 = [sp + 28] (==w0)               w1 = 664
    ldr w0, [sp, 24]        # w0 = [sp + 24] (==3)                w0 = 3
    sdiv    w0, w1, w0      # w0 = w1 // w0                       w0 = w1 // 3 = 83
    str w0, [sp, 28]        # [sp + 28] <- w0                     w0
    ldr w1, [sp, 28]        # w1 = [sp + 28] (=w0)                w1 = w0 = 83
    ldr w0, [sp, 12]        # w0 = [sp + 12]                      w0 = 8 
    sub w0, w1, w0          # w0 = w1 // w0                       w0 = 83 // 8  = 0x1b
    str w0, [sp, 28]        # sp + 28 = w0                        
    ldr w0, [sp, 28]        # w0 = sp + 28
    add sp, sp, 32          # 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]!  # x29, x30 を stack pointer から48上にpush
    add x29, sp, 0            # x29 = sp + 0
    str w0, [x29, 28]         # [x29+28]のアドレスに、 w0を書き込む   [x29+28] <- 83
    str x1, [x29, 16]         # [x29+16]のアドレスに、 x1を書き込む   [x29+16] <- 0
    ldr x0, [x29, 16]         # x0 = [x29+16]のアドレスの値 (== x1)  [x29+16] <- 0
    add x0, x0, 8             # x0 = x0 + 8                         x0 = 8
    ldr x0, [x0]              # x0 = x0                             x0 = 8
    bl  atoi                  
    str w0, [x29, 44]         # [x29+44] に w0 を書き込む            [x29+44] <- 8
    ldr w0, [x29, 44]         # w0 = [x29+44] のアドレスの値         w0 <- 8
    bl  func                  # func呼び出し
    cmp w0, 0                 # cmp w0 0
    bne .L4                   # w0 != 0 -> you lose
    adrp    x0, .LC0          # w0 == 0 -> you win
    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

ということで、答えは picoCTF{0000001b} となりました。
ちなみに ARMssembly 2 は終了間際に同じように解読してガチャガチャフラグ候補を突っ込んでみたけど通らなかった…。