以下の内容はhttps://daisuke20240310.hatenablog.com/entry/picoctf2025reより取得しました。


picoCTF 2025:Reverse Engineeringの全7問をやってみた

前回 から、picoCTF 2025 にリアルタイムで参戦しています。

picoCTF 2025 が 3/7 から始まっていて、昨日(3/17)に終了しました。今回は、リアルタイムで参戦できました。終了するまでは、解法や、フラグを公開することは禁止されていましたので、順番にその内容を公開していきます。

今回は、Reverse Engineering です。

それでは、やっていきます。

はじめに

「セキュリティ」の記事一覧です。良かったら参考にしてください。

セキュリティの記事一覧
・第1回:Ghidraで始めるリバースエンジニアリング(環境構築編)
・第2回:Ghidraで始めるリバースエンジニアリング(使い方編)
・第3回:VirtualBoxにParrotOS(OVA)をインストールする
・第4回:tcpdumpを理解して出力を正しく見れるようにする
・第5回:nginx(エンジンエックス)を理解する
・第6回:Python+Flask(WSGI+Werkzeug+Jinja2)を動かしてみる
・第7回:Python+FlaskのファイルをCython化してみる
・第8回:shadowファイルを理解してパスワードを解読してみる
・第9回:安全なWebアプリケーションの作り方(徳丸本)の環境構築
・第10回:Vue.jsの2.xと3.xをVue CLIを使って動かしてみる(ビルドも行う)
・第11回:Vue.jsのソースコードを確認する(ビルド後のソースも見てみる)
・第12回:徳丸本:OWASP ZAPの自動脆弱性スキャンをやってみる
・第13回:徳丸本:セッション管理を理解してセッションID漏洩で成りすましを試す
・第14回:OWASP ZAPの自動スキャン結果の分析と対策:パストラバーサル
・第15回:OWASP ZAPの自動スキャン結果の分析と対策:クロスサイトスクリプティング(XSS)
・第16回:OWASP ZAPの自動スキャン結果の分析と対策:SQLインジェクション
・第17回:OWASP ZAPの自動スキャン結果の分析と対策:オープンリダイレクト
・第18回:OWASP ZAPの自動スキャン結果の分析と対策:リスク中すべて
・第19回:CTF初心者向けのCpawCTFをやってみた
・第20回:hashcatの使い方(GPU実行時間の見積りとパスワード付きZIPファイル)
・第21回:Scapyの環境構築とネットワークプログラミング
・第22回:CpawCTF2にチャレンジします(クリア状況は随時更新します)
・第23回:K&Rのmalloc関数とfree関数を理解する
・第24回:C言語、アセンブラでシェルを起動するプログラムを作る(ARM64)
・第25回:機械語でシェルを起動するプログラムを作る(ARM64)
・第26回:入門セキュリhttps://github.com/SECCON/SECCON2017_online_CTF.gitティコンテスト(CTFを解きながら学ぶ実践技術)を読んだ
・第27回:x86-64 ELF(Linux)のアセンブラをGDBでデバッグしながら理解する(GDBコマンド、関連ツールもまとめておく)
・第28回:入門セキュリティコンテスト(CTFを解きながら学ぶ実践技術)のPwnable問題をやってみる
・第29回:実行ファイルのセキュリティ機構を調べるツール「checksec」のまとめ
・第30回:setodaNote CTF Exhibitionにチャレンジします(クリア状況は随時更新します)
・第31回:常設CTFのksnctfにチャレンジします(クリア状況は随時更新します)
・第32回:セキュリティコンテストチャレンジブックの「Part2 pwn」を読んだ
・第33回:セキュリティコンテストチャレンジブックの「付録」を読んでx86とx64のシェルコードを作った
・第34回:TryHackMeを始めてみたけどハードルが高かった話
・第35回:picoCTFを始めてみた(Beginner picoMini 2022:全13問完了)
・第36回:picoCTF 2024:Binary Exploitationの全10問をやってみた(Hardの1問は後日やります)
・第37回:picoCTF 2024:Reverse Engineeringの全7問をやってみた(Windowsプログラムの3問は後日やります)
・第38回:picoCTF 2024:General Skillsの全10問をやってみた
・第39回:picoCTF 2024:Web Exploitationの全6問をやってみた(最後の2問は解けず)
・第40回:picoCTF 2024:Forensicsの全8問をやってみた(最後の2問は解けず)
・第41回:picoCTF 2024:Cryptographyの全5問をやってみた(最後の2問は手つかず)
・第42回:picoCTF 2023:General Skillsの全6問をやってみた
・第43回:picoCTF 2023:Reverse Engineeringの全9問をやってみた
・第44回:picoCTF 2023:Binary Exploitationの全7問をやってみた(最後の1問は後日やります)
・第45回:書籍「セキュリティコンテストのためのCTF問題集」を読んだ
・第46回:書籍「詳解セキュリティコンテスト」のReversingを読んだ
・第47回:書籍「詳解セキュリティコンテスト」のPwnableのシェルコードを読んだ
・第48回:書籍「バイナリファイル解析 実践ガイド」を読んだ
・第49回:書籍「詳解セキュリティコンテスト」Pwnableのスタックベースエクスプロイトを読んだ
・第50回:書籍「詳解セキュリティコンテスト」Pwnableの共有ライブラリと関数呼び出しを読んだ
・第51回:picoCTF 2025:General Skillsの全5問をやってみた
・第52回:picoCTF 2025:Reverse Engineeringの全7問をやってみた ← 今回

picoCTF の公式サイトは以下です。

picoctf.org

3/7 から 3/17 までの 10日間で開催されています。

picoCTF 2025
picoCTF 2025

今回は、Reverse Engineering をやっていきます。

picoCTF 2025:Reverse Engineering

ポイントの低い順にやっていきます。

Flag Hunters(75 points)

Pythonファイル(lyric-reader.py)をダウンロードできます。また、netcat で接続する問題のようです。

Flag Hunters(75 points)
Flag Hunters(75 points)

まず、Pythonスクリプトです。結構長いですね。ところどころ文字化けしてます。全角の ' が良くないようです。

import re
import time

# Read in flag from file
flag = open('flag.txt', 'r').read()

secret_intro = \
'''Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether窶冱 ours to conquer, '''\
+ flag + '\n'


song_flag_hunters = secret_intro +\
'''

[REFRAIN]
We窶决e flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We窶决e chasing that victory, and we窶冤l never quit.
CROWD (Singalong here!);
RETURN

[VERSE1]
Command line wizards, we窶决e starting it right,
Spawning shells in the terminal, hacking all night.
Scripts and searches, grep through the void,
Every keystroke, we're a cypher's envoy.
Brute force the lock or craft that regex,
Flag on the horizon, what challenge is next?

REFRAIN;

Echoes in memory, packets in trace,
Digging through the remnants to uncover with haste.
Hex and headers, carving out clues,
Resurrect the hidden, it's forensics we choose.
Disk dumps and packet dumps, follow the trail,
Buried deep in the noise, but we will prevail.

REFRAIN;

Binary sorcerers, let窶冱 tear it apart,
Disassemble the code to reveal the dark heart.
From opcode to logic, tracing each line,
Emulate and break it, this key will be mine.
Debugging the maze, and I see through the deceit,
Patch it up right, and watch the lock release.

REFRAIN;

Ciphertext tumbling, breaking the spin,
Feistel or AES, we窶决e destined to win.
Frequency, padding, primes on the run,
Vigenティre, RSA, cracking them for fun.
Shift the letters, matrices fall,
Decrypt that flag and hear the ether call.

REFRAIN;

SQL injection, XSS flow,
Map the backend out, let the database show.
Inspecting each cookie, fiddler in the fight,
Capturing requests, push the payload just right.
HTML's secrets, backdoors unlocked,
In the world wide labyrinth, we窶决e never lost.

REFRAIN;

Stack's overflowing, breaking the chain,
ROP gadget wizardry, ride it to fame.
Heap spray in silence, memory's plight,
Race the condition, crash it just right.
Shellcode ready, smashing the frame,
Control the instruction, flags call my name.

REFRAIN;

END;
'''

MAX_LINES = 100

def reader(song, startLabel):
  lip = 0
  start = 0
  refrain = 0
  refrain_return = 0
  finished = False

  # Get list of lyric lines
  song_lines = song.splitlines()
  
  # Find startLabel, refrain and refrain return
  for i in range(0, len(song_lines)):
    if song_lines[i] == startLabel:
      start = i + 1
    elif song_lines[i] == '[REFRAIN]':
      refrain = i + 1
    elif song_lines[i] == 'RETURN':
      refrain_return = i

  # Print lyrics
  line_count = 0
  lip = start
  while not finished and line_count < MAX_LINES:
    line_count += 1
    for line in song_lines[lip].split(';'):
      if line == '' and song_lines[lip] != '':
        continue
      if line == 'REFRAIN':
        song_lines[refrain_return] = 'RETURN ' + str(lip + 1)
        lip = refrain
      elif re.match(r"CROWD.*", line):
        crowd = input('Crowd: ')
        song_lines[lip] = 'Crowd: ' + crowd
        lip += 1
      elif re.match(r"RETURN [0-9]+", line):
        lip = int(line.split()[1])
      elif line == 'END':
        finished = True
      else:
        print(line, flush=True)
        time.sleep(0.5)
        lip += 1

reader(song_flag_hunters, '[VERSE1]')

早速やってみます。最初の Crowd: のところで、1回だけ入力しただけで、あとは、そのまま終了しました。この入力から考えなければなりませんね。

$ nc verbal-sleep.picoctf.net 50331
Command line wizards, we’re starting it right,
Spawning shells in the terminal, hacking all night.
Scripts and searches, grep through the void,
Every keystroke, we're a cypher's envoy.
Brute force the lock or craft that regex,
Flag on the horizon, what challenge is next?

We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: 2

Echoes in memory, packets in trace,
Digging through the remnants to uncover with haste.
Hex and headers, carving out clues,
Resurrect the hidden, it's forensics we choose.
Disk dumps and packet dumps, follow the trail,
Buried deep in the noise, but we will prevail.

We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: 2

Binary sorcerers, let’s tear it apart,
Disassemble the code to reveal the dark heart.
From opcode to logic, tracing each line,
Emulate and break it, this key will be mine.
Debugging the maze, and I see through the deceit,
Patch it up right, and watch the lock release.

We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: 2

Ciphertext tumbling, breaking the spin,
Feistel or AES, we’re destined to win.
Frequency, padding, primes on the run,
Vigenère, RSA, cracking them for fun.
Shift the letters, matrices fall,
Decrypt that flag and hear the ether call.

We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: 2

SQL injection, XSS flow,
Map the backend out, let the database show.
Inspecting each cookie, fiddler in the fight,
Capturing requests, push the payload just right.
HTML's secrets, backdoors unlocked,
In the world wide labyrinth, we’re never lost.

We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: 2

Stack's overflowing, breaking the chain,
ROP gadget wizardry, ride it to fame.
Heap spray in silence, memory's plight,
Race the condition, crash it just right.
Shellcode ready, smashing the frame,
Control the instruction, flags call my name.

We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: 2

ダウンロードした Pythonスクリプトをデバッガで動かしてみると、最初の for文の結果は、start=15、refrain=7、refrain_return=12 でした。つまり、最初は 15行目から([VERSE1] の次の行から)始まります。その後、REFRAIN; の行まで進み、そこで、RETURN の行を書き換えて、[REFRAIN] の行(7行目)に飛びます。その後、CROWD (Singalong here!); で、最初の入力を行い、RETURN の行に進み、先ほど書き換えた値の行に飛びます。

だいたいの流れは分かりましたが、どうすればいいでしょうか。0行目に飛ばして、フラグが表示されれば、ゴールです。各行は、セミコロンで分割してるので、入力するときに、;RETURN 0 とすれば良さそうです。

とりあえず、やってみます。無事にフラグが表示されました。

$ nc verbal-sleep.picoctf.net 50331
Command line wizards, we’re starting it right,
Spawning shells in the terminal, hacking all night.
Scripts and searches, grep through the void,
Every keystroke, we're a cypher's envoy.
Brute force the lock or craft that regex,
Flag on the horizon, what challenge is next?

We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd: ;RETURN 0

Echoes in memory, packets in trace,
Digging through the remnants to uncover with haste.
Hex and headers, carving out clues,
Resurrect the hidden, it's forensics we choose.
Disk dumps and packet dumps, follow the trail,
Buried deep in the noise, but we will prevail.

We’re flag hunters in the ether, lighting up the grid,
No puzzle too dark, no challenge too hid.
With every exploit we trigger, every byte we decrypt,
We’re chasing that victory, and we’ll never quit.
Crowd:
Pico warriors rising, puzzles laid bare,
Solving each challenge with precision and flair.
With unity and skill, flags we deliver,
The ether’s ours to conquer, picoCTF{70637h3r_f0r3v3r_6c145c84}

Binary Instrumentation 1(200 points)

1つの ZIPファイル(bininst1.zip)がダウンロードできますが、Windows がウィルスと検知してしまいます。仕方ないので、ParrotOS で wget を使ってダウンロードしました。

Binary Instrumentation 1(200 points)
Binary Instrumentation 1(200 points)

Windows の実行ファイルでした。Windows は後回しにします。

Tap into Hash(200 points)

1つの暗号化されたファイル(enc_flag)と、Pythonスクリプト(block_chain.py)がダウンロードできます。

Tap into Hash(200 points)
Tap into Hash(200 points)

暗号化ファイルはテキストファイルでした。

Key: b'\xa9\xcco`\xfa\xf9\xb5\xc0\xda\xf6*\xb3\xbe\xa9t\x0fi\xae\x13\x01q-\xae\x9ap\xb7\xa45\x1e{\xaa\xb4'
Encrypted Blockchain: b'\xf7Y\x8db\x8bS\xb2\x80q\xf2\xa0\x87\xd6(\xfc\xe6\xf2\\\x82`\x8c\\\xb4\xd4v\xf0\xf2\xd1\xde/\xfa\xb0\xfb]\xdfg\x8bV\xe2\xd1$\xa5\xa6\xd9\x8c+\xa8\xe7\xa6X\x82d\xda\x01\xb1\x85u\xa4\xa3\xd3\xda}\xff\xbc\xeeZ\x8am\x8d\x01\xb1\x84$\xa1\xf4\x85\x8c,\xfa\xe7\xf0S\x8f4\x8f\x02\xb1\x82w\xf1\xf6\x85\xd7/\xff\xb3\xa6]\xdf`\x8b\x00\xe3\xd1"\xf2\xf6\xd8\xda|\xfd\xb7\xf3Z\xd83\xdc\\\xbe\xd6!\xa2\xae\xd8\x8c{\xfa\xb0\xf2G\x8ae\x8a\x01\xe3\xd7q\xf4\xa3\xd9\x8aq\xfd\xbd\xfb\x08\x8ag\x8dQ\xe3\xd0"\xa4\xf2\x84\xdeq\xac\xb5\xf4\x0e\xca<\xdd\x0b\xc5\xe6U\xbc\xf5\x8d\x81*\xa6\xdb\xf09\xe8=\xe8\r\xd4\xd0G\xf6\xe6\x82\xb6\x16\x95\xd1\xa9\'\x8a\'\x8a]\xe5\xfaL\xb6\xd4\x9b\x83\x03\x97\xfe\x81!\xe5a\x87\\\xbf\xd4*\xa2\xf6\x9c\xdex\xf4\xe5\xf6R\x8em\x87W\xb1\x80 \xf5\xf4\xd6\x8a{\xfd\xe7\xf3\x08\x89d\xdfP\xb2\x82u\xf4\xa0\x80\xc3y\xfd\xb2\xa5[\x83m\x8dQ\xe5\x84+\xfe\xa5\x84\xd7p\xf4\xb6\xf1S\x89m\xddQ\xb0\xd7&\xf2\xf2\xd3\x8b,\xf9\xb5\xfa\x08\xd8f\x8c\\\xe5\xd7"\xf3\xf6\xd4\x8c|\xac\xe5\xa7\x08\x8ag\x8d\x02\xbe\xd1$\xa2\xa3\x80\x8ad\xfd\xb4\xa6]\xdc0\xd8W\xb1\x85q\xa5\xf6\x80\xdbq\xa9\xb5\xf0_\xdee\x8e\x00\xbe\xd3r\xf4\xa2\xd4\x88y\xf4\xb0\xa5_\xdc4\xda\x05\xb7\x80r\xf1\xa3\x84\x8ap\xfb\xb2\xfa\x08\x8c`\x8eR\xe3\x81q\xfe\xae\x84\x88x\xcf\x86'

Pythonスクリプトは以下です。VSCode でデバッグしながら見ていきます。

コマンドライン引数が必要なので、適当な文字列("aaa")を入力して進めていきます。ざっと見ましたが、だいぶ難しいです。

import time
import base64
import hashlib
import sys
import secrets


class Block:
    def __init__(self, index, previous_hash, timestamp, encoded_transactions, nonce):
        self.index = index
        self.previous_hash = previous_hash
        self.timestamp = timestamp
        self.encoded_transactions = encoded_transactions
        self.nonce = nonce

    def calculate_hash(self):
        block_string = f"{self.index}{self.previous_hash}{self.timestamp}{self.encoded_transactions}{self.nonce}"
        return hashlib.sha256(block_string.encode()).hexdigest()


def proof_of_work(previous_block, encoded_transactions):
    index = previous_block.index + 1
    timestamp = int(time.time())
    nonce = 0

    block = Block(index, previous_block.calculate_hash(),
                  timestamp, encoded_transactions, nonce)

    while not is_valid_proof(block):
        nonce += 1
        block.nonce = nonce

    return block


def is_valid_proof(block):
    guess_hash = block.calculate_hash()
    return guess_hash[:2] == "00"


def decode_transactions(encoded_transactions):
    return base64.b64decode(encoded_transactions).decode('utf-8')


def get_all_blocks(blockchain):
    return blockchain


def blockchain_to_string(blockchain):
    block_strings = [f"{block.calculate_hash()}" for block in blockchain]
    return '-'.join(block_strings)


def encrypt(plaintext, inner_txt, key):
    midpoint = len(plaintext) // 2

    first_part = plaintext[:midpoint]
    second_part = plaintext[midpoint:]
    modified_plaintext = first_part + inner_txt + second_part
    block_size = 16
    plaintext = pad(modified_plaintext, block_size)
    key_hash = hashlib.sha256(key).digest()

    ciphertext = b''

    for i in range(0, len(plaintext), block_size):
        block = plaintext[i:i + block_size]
        cipher_block = xor_bytes(block, key_hash)
        ciphertext += cipher_block

    return ciphertext


def pad(data, block_size):
    padding_length = block_size - len(data) % block_size
    padding = bytes([padding_length] * padding_length)
    return data.encode() + padding


def xor_bytes(a, b):
    return bytes(x ^ y for x, y in zip(a, b))


def generate_random_string(length):
    return secrets.token_hex(length // 2)


random_string = generate_random_string(64)


def main(token):
    key = bytes.fromhex(random_string)

    print("Key:", key)

    genesis_block = Block(0, "0", int(time.time()), "EncodedGenesisBlock", 0)
    blockchain = [genesis_block]

    for i in range(1, 5):
        encoded_transactions = base64.b64encode(
            f"Transaction_{i}".encode()).decode('utf-8')
        new_block = proof_of_work(blockchain[-1], encoded_transactions)
        blockchain.append(new_block)

    all_blocks = get_all_blocks(blockchain)

    blockchain_string = blockchain_to_string(all_blocks)
    encrypted_blockchain = encrypt(blockchain_string, token, key)

    print("Encrypted Blockchain:", encrypted_blockchain)


if __name__ == "__main__":
    text = sys.argv[1]
    main(text)

encrypt関数がポイントな気がします。encrypt関数の引数は、そこまでに作った 5個のブロックを - で繋いだ文字列と、コマンドライン引数で指定した文字列と、ランダムな文字列です。

encrypt関数を詳細に見ます。midpoint は、常に 162 になるようです。plaintext(324文字)を、前半分を first_part に入れて、後半分を second_part に入れます。コマンドライン引数で指定した文字列を真ん中に入れて、これらを連結します。パディングして 16の倍数にします。ランダムな文字列を使って、sha256 で、32byte のダイジェストを作ります。for文では、16byteずつ取り出して、ダイジェストと XOR を取ったものを ciphertext として連結します。

これ以上、遡っていくのは難しいので、まずは、これを逆算するような Pythonスクリプトを作っていきます。

import hashlib
import sys

def xor_bytes(a, b):
    return bytes(x ^ y for x, y in zip(a, b))

key = b'\xa9\xcco`\xfa\xf9\xb5\xc0\xda\xf6*\xb3\xbe\xa9t\x0fi\xae\x13\x01q-\xae\x9ap\xb7\xa45\x1e{\xaa\xb4'
ciphertext = b'\xf7Y\x8db\x8bS\xb2\x80q\xf2\xa0\x87\xd6(\xfc\xe6\xf2\\\x82`\x8c\\\xb4\xd4v\xf0\xf2\xd1\xde/\xfa\xb0\xfb]\xdfg\x8bV\xe2\xd1$\xa5\xa6\xd9\x8c+\xa8\xe7\xa6X\x82d\xda\x01\xb1\x85u\xa4\xa3\xd3\xda}\xff\xbc\xeeZ\x8am\x8d\x01\xb1\x84$\xa1\xf4\x85\x8c,\xfa\xe7\xf0S\x8f4\x8f\x02\xb1\x82w\xf1\xf6\x85\xd7/\xff\xb3\xa6]\xdf`\x8b\x00\xe3\xd1"\xf2\xf6\xd8\xda|\xfd\xb7\xf3Z\xd83\xdc\\\xbe\xd6!\xa2\xae\xd8\x8c{\xfa\xb0\xf2G\x8ae\x8a\x01\xe3\xd7q\xf4\xa3\xd9\x8aq\xfd\xbd\xfb\x08\x8ag\x8dQ\xe3\xd0"\xa4\xf2\x84\xdeq\xac\xb5\xf4\x0e\xca<\xdd\x0b\xc5\xe6U\xbc\xf5\x8d\x81*\xa6\xdb\xf09\xe8=\xe8\r\xd4\xd0G\xf6\xe6\x82\xb6\x16\x95\xd1\xa9\'\x8a\'\x8a]\xe5\xfaL\xb6\xd4\x9b\x83\x03\x97\xfe\x81!\xe5a\x87\\\xbf\xd4*\xa2\xf6\x9c\xdex\xf4\xe5\xf6R\x8em\x87W\xb1\x80 \xf5\xf4\xd6\x8a{\xfd\xe7\xf3\x08\x89d\xdfP\xb2\x82u\xf4\xa0\x80\xc3y\xfd\xb2\xa5[\x83m\x8dQ\xe5\x84+\xfe\xa5\x84\xd7p\xf4\xb6\xf1S\x89m\xddQ\xb0\xd7&\xf2\xf2\xd3\x8b,\xf9\xb5\xfa\x08\xd8f\x8c\\\xe5\xd7"\xf3\xf6\xd4\x8c|\xac\xe5\xa7\x08\x8ag\x8d\x02\xbe\xd1$\xa2\xa3\x80\x8ad\xfd\xb4\xa6]\xdc0\xd8W\xb1\x85q\xa5\xf6\x80\xdbq\xa9\xb5\xf0_\xdee\x8e\x00\xbe\xd3r\xf4\xa2\xd4\x88y\xf4\xb0\xa5_\xdc4\xda\x05\xb7\x80r\xf1\xa3\x84\x8ap\xfb\xb2\xfa\x08\x8c`\x8eR\xe3\x81q\xfe\xae\x84\x88x\xcf\x86'

print( f"len(key)={len(key)}, len(ciphertext)={len(ciphertext)}" ) # 32 384 (162 + 60 + 162)

key_hash = hashlib.sha256(key).digest()
print( f"key_hash={key_hash}, len(key_hash)={len(key_hash)}" )

block_size = 16
plaintext = b''

for i in range(0, len(ciphertext), block_size):
    block = ciphertext[i:i + block_size]
    plain_block = xor_bytes(block, key_hash)
    plaintext += plain_block

print( f"plaintext={plaintext}" )

とりあえず、実行してみます。

plaintext の途中にフラグのようなものが見えます。これを提出するとクリアでした。

$ python block_chain_decode.py
len(key)=32, len(ciphertext)=384
key_hash=b'\xc3j\xbaU\xbed\x86\xb2\x13\xc7\x97\xe1\xeeI\xcd\x84\xdb\xfb\xaf2\x10k>\x8b\xb3\xfe\xcb3`a\xaa\x82', len(key_hash)=32
plaintext=b'43775742b57f8a1b1685282fe7e00f7487e252dc7b18bbece281de77fc424428-0083e767fcdbe7c395a1f70d6ad9f27e7e55dec15a9450300bfb88d2e99b2741-004eeeb348d8098b0235eb1cee08a17dpicoCTF{block_3SRhViRbT1qcX_XUjM0r49cH_qCzmJZzBK_4989f9ea}019a5848937232c7d20c0b31a440f37a-006f19835c6892e99922938c56e55e2ee419bb328ce14a5b5aadb023f8c7e4ad-00e7fef377bbaa58d135d00d8aa355f094f5fada12a64ed9669b6506e3b99ef1\x02\x02'

Chronohack(200 points)

1つの Pythonスクリプト(token_generator.py)をダウンロードできます。あと、サーバを起動して進めるようです。

Chronohack(200 points)
Chronohack(200 points)

Pythonスクリプトは以下です。

まず、20文字の文字列を取得して、トークンとします。ユーザから入力を得て、トークンと一致したらフラグが表示されるようです。普通に考えたら無理ですね。

import random
import time

def get_random(length):
    alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    random.seed(int(time.time() * 1000))  # seeding with current time 
    s = ""
    for i in range(length):
        s += random.choice(alphabet)
    return s

def flag():
    with open('/flag.txt', 'r') as picoCTF:
        content = picoCTF.read()
        print(content)


def main():
    print("Welcome to the token generation challenge!")
    print("Can you guess the token?")
    token_length = 20  # the token length
    token = get_random(token_length) 

    try:
        n=0
        while n < 50:
            user_guess = input("\nEnter your guess for the token (or exit):").strip()
            n+=1
            if user_guess == "exit":
                print("Exiting the program...")
                break
            
            if user_guess == token:
                print("Congratulations! You found the correct token.")
                flag()
                break
            else:
                print("Sorry, your token does not match. Try again!")
            if n == 50:
                print("\nYou exhausted your attempts, Bye!")
    except KeyboardInterrupt:
        print("\nKeyboard interrupt detected. Exiting the program...")

if __name__ == "__main__":
    main()

乱数シードが、time.time() なので、現在時刻のようです。これは予測できるということでしょうか。同じ乱数シードで、50回のチャレンジが出来るので、なんとかなりそうです。

Python と pwntools を使って実装しました。

import random
import time
from pwn import *

def get_random(length, seed):
    alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
    random.seed(seed)  # seeding with current time 
    s = ""
    for i in range(length):
        s += random.choice(alphabet)
    return s

def main(delay):
    
    adrs = "verbal-sleep.picoctf.net"
    port = 57465
    #adrs = "localhost"
    #port = 4000
    
    proc = remote( adrs, port )
    
    now = time.time()
    
    try:
        n = 0
        while n < 50:
            
            ret = proc.recvuntil( ':' )
            
            seed = int((now) * 1000) + n + delay
            #seed = int((now) * 1000)
            
            info( f"recv ret={ret}', seed={seed}" )
            
            ss = get_random( 20, seed )
            
            proc.sendline( ss.encode("utf-8") )
            ret = proc.recvline()
            info( f"send ss={ss}, recv ret={ret}" )
            if 'Congratulations' in ret.decode("utf-8"):
                info( proc.recvline() )
                info( proc.recvline() )
                info( proc.recvline() )
                info( proc.recvline() )
            
            n += 1
    
    except KeyboardInterrupt:
        print("\nKeyboard interrupt detected. Exiting the program...")
    except EOFError:
        print( f"********* ret={ret} *********" )

if __name__ == "__main__":
    for ii in range(1000):
        main( ii * 30 )

以下は、成功したところです。かなり苦労して、なんとか当てることが出来ました。実は何度も当ててたのですが、標準出力にフラグを表示できてなかったりなど、まだ pwntools を使いこなせてない感じでした。

$ python token_generator_pwntools.py > 20250311_2.log 2>&1 &

$ tail -f 20250311_2.log
(途中省略)
[*] recv ret=b'\nEnter your guess for the token (or exit):'', seed=1741701063299
[*] send ss=QvkOjQzT1o4vdLkOBOcL, recv ret=b'Sorry, your token does not match. Try again!\n'
[*] recv ret=b'\nEnter your guess for the token (or exit):'', seed=1741701063300
[*] send ss=4MDrmzpg2YJWNCisQgN0, recv ret=b'Sorry, your token does not match. Try again!\n'
[*] recv ret=b'\nEnter your guess for the token (or exit):'', seed=1741701063301
[*] send ss=x9QriVKHlbbIBJ9TMm9t, recv ret=b'Sorry, your token does not match. Try again!\n'
[*] recv ret=b'\nEnter your guess for the token (or exit):'', seed=1741701063302
[*] send ss=wTECACj6qoJqnmLulH8N, recv ret=b'Sorry, your token does not match. Try again!\n'
[*] recv ret=b'\nEnter your guess for the token (or exit):'', seed=1741701063303
[*] send ss=j5xEiw8db900Toy3EjC1, recv ret=b'Sorry, your token does not match. Try again!\n'
[*] recv ret=b'\nEnter your guess for the token (or exit):'', seed=1741701063304
[*] send ss=jt4XtryCEHxhxzohjbCO, recv ret=b'Sorry, your token does not match. Try again!\n'
[*] recv ret=b'\nEnter your guess for the token (or exit):'', seed=1741701063305
[*] send ss=vq1qnqESQihBi9L8eEmN, recv ret=b'Sorry, your token does not match. Try again!\n'
[*] recv ret=b'\nEnter your guess for the token (or exit):'', seed=1741701063306
[*] send ss=bfQDEvV8nbbxmVEp21Oa, recv ret=b'Sorry, your token does not match. Try again!\n'
[*] recv ret=b'\nEnter your guess for the token (or exit):'', seed=1741701063307
[*] send ss=6T40hEAGtddkQ7bSjXzF, recv ret=b'Sorry, your token does not match. Try again!\n'
[*] recv ret=b'\nEnter your guess for the token (or exit):'', seed=1741701063308
[*] send ss=K7gBgnZpJipAOCQoG4FO, recv ret=b'Sorry, your token does not match. Try again!\n'
[*] recv ret=b'\nEnter your guess for the token (or exit):'', seed=1741701063309
[*] send ss=MgdeQQt5UnxZPIVv8bWZ, recv ret=b'Sorry, your token does not match. Try again!\n'
[*] recv ret=b'\nEnter your guess for the token (or exit):'', seed=1741701063310
[*] send ss=XzZcLl3W5OCtbmaeAnfx, recv ret=b'Congratulations! You found the correct token.\n'
/home/user/20240819/lib/python3.11/site-packages/pwnlib/log.py:396: BytesWarning: Bytes is not text; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  self._log(logging.INFO, message, args, kwargs, 'info')
[*] picoCTF{UseSecure#$_Random@j3n3r@T0rsde389b79}
********* ret=b'Congratulations! You found the correct token.\n' *********
[x] Opening connection to verbal-sleep.picoctf.net on port 64908
[x] Opening connection to verbal-sleep.picoctf.net on port 64908: Trying 3.138.217.147
[+] Opening connection to verbal-sleep.picoctf.net on port 64908: Done
[*] recv ret=b'Welcome to the token generation challenge!\nCan you guess the token?\n\nEnter your guess for the token (or exit):'', seed=1741701074622
[*] send ss=iRxlltvft4QWUWdw11IL, recv ret=b'Sorry, your token does not match. Try again!\n'
[*] recv ret=b'\nEnter your guess for the token (or exit):'', seed=1741701074623
[*] send ss=FMiKR1WRoC6zEh7XkDu0, recv ret=b'Sorry, your token does not match. Try again!\n'

Quantum Scrambler(200 points)

Pythonスクリプト(quantum_scrambler.py)をダウンロードできます。サーバに接続して進める問題のようです。

Quantum Scrambler(200 points)
Quantum Scrambler(200 points)

Pythonスクリプトは以下です。

まず、フラグを読み出し、1文字ずつ、数値に変換して、16進数文字列(例:"0x70")にして、それをリストにして、hex_flagリストに追加(例:[['0x70'], ['0x69'], ...])します。その hex_flagリストを引数にして、scramble関数を呼び出します。そこでは、hex_flagリストの中身をいろいろ移動してるようです。

import sys

def exit():
  sys.exit(0)

def scramble(L):
  A = L
  i = 2
  while (i < len(A)):
    A[i-2] += A.pop(i-1)
    A[i-1].append(A[:i-2])
    i += 1
    
  return L

def get_flag():
  flag = open('flag.txt', 'r').read()
  flag = flag.strip()
  hex_flag = []
  for c in flag:
    hex_flag.append([str(hex(ord(c)))])

  return hex_flag

def main():
  flag = get_flag()
  cypher = scramble(flag)
  print(cypher)

if __name__ == '__main__':
  main()

サーバに接続してみます。すると、大量の cipher が表示されます。あまりに多いので最初の方だけ貼ります。

$ nc verbal-sleep.picoctf.net 63779
[['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79'], ['0x74', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b']], '0x68'], ['0x6f', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79']], '0x6e'], ['0x5f', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79'], ['0x74', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b']], '0x68']], '0x69'], ['0x73', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79'], ['0x74', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b']], '0x68'], ['0x6f', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79']], '0x6e']], '0x5f'], ['0x77', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79'], ['0x74', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b']], '0x68'], ['0x6f', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79']], '0x6e'], ['0x5f', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79'], ['0x74', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b']], '0x68']], '0x69']], '0x65'], ['0x69', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79'], ['0x74', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b']], '0x68'], ['0x6f', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79']], '0x6e'], ['0x5f', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79'], ['0x74', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b']], '0x68']], '0x69'], ['0x73', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79'], ['0x74', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b']], '0x68'], ['0x6f', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79']], '0x6e']], '0x5f']], '0x72'], ['0x64', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x69'], ['0x63', [], '0x6f']], '0x7b'], ['0x70', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54']], '0x79'], ['0x74', [['0x70', '0x69'], ['0x63', [], '0x6f'], ['0x43', [['0x70', '0x69']], '0x54'], ['0x46', [['0x70', '0x

結局、どうすればいいのでしょうか。この大きな cipher を復号して、フラグを抽出するということだと思います。では、逆算するような Pythonスクリプトを作ってみます。

その前に、ちょっとした Pythonスクリプトを作りました。ただし、お題の cipher が大きいので、そこは省略して貼りました。仮のフラグを用意して、i と len(L) がどのように推移していくのかを見えるようにしました。

import sys
import copy

cipher = [['0x70', '0x69'], ['0x63', [], '0x6f'], ...]

def scramble(L):
  A = L
  i = 2
  print( f"i={i}, len(L)={len(L)}" )
  while (i < len(A)):
    A[i-2] += A.pop(i-1)
    A[i-1].append(A[:i-2])
    i += 1
    print( f"i={i}, len(L)={len(L)}" )
    
  return L

def main():
    
    print( f"len(cipher)={len(cipher)}" )
    
    flag = [['0x70'], ['0x69'], ['0x63'], ['0x6f'], ['0x43'], ['0x54'], ['0x46'], ['0x7b'], ['0x64'], ['0x65'], ['0x61'], ['0x64'], ['0x62'], ['0x65'], ['0x65'], ['0x66'], ['0x7d'], ['0x70'], ['0x69'], ['0x63'], ['0x6f'], ['0x43'], ['0x54'], ['0x46'], ['0x7b'], ['0x64'], ['0x65'], ['0x61'], ['0x64'], ['0x62'], ['0x65'], ['0x65'], ['0x66'], ['0x7d']]
    
    print( f"len(flag)={len(flag)}" )
    scramble( copy.deepcopy(flag) )
    
    flag.pop( 0 )
    print( f"len(flag)={len(flag)}" )
    scramble( copy.deepcopy(flag) )
    
    flag.pop( 0 )
    print( f"len(flag)={len(flag)}" )
    scramble( copy.deepcopy(flag) )
    
    flag.pop( 0 )
    print( f"len(flag)={len(flag)}" )
    scramble( copy.deepcopy(flag) )

if __name__ == '__main__':
  main()

実行します。お題の cipher は、要素数が 17 のようです。scramble関数では、L は、要素数が 1つずつ減っていきます。一方、i は 2 から始まって、1つずつ増えていきます。cipher の要素数が 17 になるパターンは 2つあります。2通り試せばいいですね。

$ python quantum_scrambler_pre.py
len(cipher)=17
len(flag)=34
i=2, len(L)=34
i=3, len(L)=33
i=4, len(L)=32
i=5, len(L)=31
i=6, len(L)=30
i=7, len(L)=29
i=8, len(L)=28
i=9, len(L)=27
i=10, len(L)=26
i=11, len(L)=25
i=12, len(L)=24
i=13, len(L)=23
i=14, len(L)=22
i=15, len(L)=21
i=16, len(L)=20
i=17, len(L)=19
i=18, len(L)=18
len(flag)=33
i=2, len(L)=33
i=3, len(L)=32
i=4, len(L)=31
i=5, len(L)=30
i=6, len(L)=29
i=7, len(L)=28
i=8, len(L)=27
i=9, len(L)=26
i=10, len(L)=25
i=11, len(L)=24
i=12, len(L)=23
i=13, len(L)=22
i=14, len(L)=21
i=15, len(L)=20
i=16, len(L)=19
i=17, len(L)=18
i=18, len(L)=17
len(flag)=32
i=2, len(L)=32
i=3, len(L)=31
i=4, len(L)=30
i=5, len(L)=29
i=6, len(L)=28
i=7, len(L)=27
i=8, len(L)=26
i=9, len(L)=25
i=10, len(L)=24
i=11, len(L)=23
i=12, len(L)=22
i=13, len(L)=21
i=14, len(L)=20
i=15, len(L)=19
i=16, len(L)=18
i=17, len(L)=17
len(flag)=31
i=2, len(L)=31
i=3, len(L)=30
i=4, len(L)=29
i=5, len(L)=28
i=6, len(L)=27
i=7, len(L)=26
i=8, len(L)=25
i=9, len(L)=24
i=10, len(L)=23
i=11, len(L)=22
i=12, len(L)=21
i=13, len(L)=20
i=14, len(L)=19
i=15, len(L)=18
i=16, len(L)=17
i=17, len(L)=16

逆に操作する Pythonスクリプトを実装しました。

import sys

cipher = [['0x70', '0x69'], ['0x63', [], '0x6f'], ...]

def main():
    
    global cipher
    
    print( f"len(cipher)={len(cipher)}" ) # whileループを抜けるときは、i と len(A) は同じ値?のはず
    
    # i=18, len(L)=17 → i=17, len(L)=17
    #ii = 17
    ii = 16
    
    while( ii >= 2 ):
        
        # A[16].append(A[:15]):A[:15] を A[16] の末尾に追加 → A[16] の末尾の要素を削除する
        print( f"ii={ii}, len(cipher[ii-1])={len(cipher[ii-1])}" )
        cipher[ii - 1].pop( -1 )
        
        # A[15] += A.pop(16): → A[15] の末尾の要素を抽出して、A の末尾に追加する
        cipher.insert( ii - 1, [cipher[ii - 2].pop(-1)] )
        
        ii -= 1
        
        print( f"ii={ii}, len(cipher)={len(cipher)}" )
    
    print( cipher )
    
    ss = ""
    for cc in cipher:
        ss += chr( int(cc[0], 16) )
    
    print( ss )

if __name__ == '__main__':
  main()

実行します。フラグが表示されました。

$ python quantum_scrambler_decrypt.py
len(cipher)=17
ii=16, len(cipher[ii-1])=2
ii=15, len(cipher)=18
ii=15, len(cipher[ii-1])=2
ii=14, len(cipher)=19
ii=14, len(cipher[ii-1])=2
ii=13, len(cipher)=20
ii=13, len(cipher[ii-1])=2
ii=12, len(cipher)=21
ii=12, len(cipher[ii-1])=2
ii=11, len(cipher)=22
ii=11, len(cipher[ii-1])=2
ii=10, len(cipher)=23
ii=10, len(cipher[ii-1])=2
ii=9, len(cipher)=24
ii=9, len(cipher[ii-1])=2
ii=8, len(cipher)=25
ii=8, len(cipher[ii-1])=2
ii=7, len(cipher)=26
ii=7, len(cipher[ii-1])=2
ii=6, len(cipher)=27
ii=6, len(cipher[ii-1])=2
ii=5, len(cipher)=28
ii=5, len(cipher[ii-1])=2
ii=4, len(cipher)=29
ii=4, len(cipher[ii-1])=2
ii=3, len(cipher)=30
ii=3, len(cipher[ii-1])=2
ii=2, len(cipher)=31
ii=2, len(cipher[ii-1])=2
ii=1, len(cipher)=32
[['0x70'], ['0x69'], ['0x63'], ['0x6f'], ['0x43'], ['0x54'], ['0x46'], ['0x7b'], ['0x70'], ['0x79'], ['0x74'], ['0x68'], ['0x6f'], ['0x6e'], ['0x5f'], ['0x69'], ['0x73'], ['0x5f'], ['0x77'], ['0x65'], ['0x69'], ['0x72'], ['0x64'], ['0x66'], ['0x39'], ['0x64'], ['0x65'], ['0x34'], ['0x34'], ['0x39'], ['0x61'], ['0x7d']]
picoCTF{python_is_weirdf9de449a}

Binary Instrumentation 2(300 points)

1つの ZIPファイル(bininst2.zip)がダウンロードできます。今度は、ウィルスとは検知されませんでした。と思ったら、解凍したらウィルスと検知しました。

Binary Instrumentation 2(300 points)
Binary Instrumentation 2(300 points)

確認できませんでしたが、Windows の実行ファイルだと思います。Windows は後回しにします。

perplexed(400 points)

1つの実行ファイル(perplexed)がダウンロードできます。問題文には何も書かれていません。

perplexed(400 points)
perplexed(400 points)

とりあえず、表層解析します。

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

$ ~/bin/checksec --file=perplexed
RELRO          STACK CANARY     NX          PIE     RPATH     RUNPATH     Symbols     FORTIFY  Fortified  Fortifiable  FILE
Partial RELRO  No canary found  NX enabled  No PIE  No RPATH  No RUNPATH  39 Symbols  No       0          2            perplexed

$ pwn checksec --file=perplexed
[*] '/home/user/svn/experiment/picoCTF/picoCTF2025_ReverseEngineering/perplexed'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No

実行してみます。パスワードを入力させるプログラムのようです。

$ ./perplexed
Enter the password: xxxxxxxx
Wrong :(

Ghidra で見ていきます。

main関数は、ユーザにパスワードを入力させて、check関数を実行して、その戻り値が 1以外なら成功ということのようです。

bool main(void)
{
  bool bVar1;
  undefined8 local_118;
  undefined8 local_110;
  undefined8 local_108;
  undefined8 local_100;
  undefined8 local_f8;
  undefined8 local_f0;
  undefined8 local_e8;
  undefined8 local_e0;
  undefined8 local_d8;
  undefined8 local_d0;
  undefined8 local_c8;
  undefined8 local_c0;
  undefined8 local_b8;
  undefined8 local_b0;
  undefined8 local_a8;
  undefined8 local_a0;
  undefined8 local_98;
  undefined8 local_90;
  undefined8 local_88;
  undefined8 local_80;
  undefined8 local_78;
  undefined8 local_70;
  undefined8 local_68;
  undefined8 local_60;
  undefined8 local_58;
  undefined8 local_50;
  undefined8 local_48;
  undefined8 local_40;
  undefined8 local_38;
  undefined8 local_30;
  undefined8 local_28;
  undefined8 local_20;
  int local_c;
  
  local_118 = 0;
  local_110 = 0;
  local_108 = 0;
  local_100 = 0;
  local_f8 = 0;
  local_f0 = 0;
  local_e8 = 0;
  local_e0 = 0;
  local_d8 = 0;
  local_d0 = 0;
  local_c8 = 0;
  local_c0 = 0;
  local_b8 = 0;
  local_b0 = 0;
  local_a8 = 0;
  local_a0 = 0;
  local_98 = 0;
  local_90 = 0;
  local_88 = 0;
  local_80 = 0;
  local_78 = 0;
  local_70 = 0;
  local_68 = 0;
  local_60 = 0;
  local_58 = 0;
  local_50 = 0;
  local_48 = 0;
  local_40 = 0;
  local_38 = 0;
  local_30 = 0;
  local_28 = 0;
  local_20 = 0;
  printf("Enter the password: ");
  fgets((char *)&local_118,0x100,stdin);
  local_c = check(&local_118);
  bVar1 = local_c != 1;
  if (bVar1) {
    puts("Correct!! :D");
  }
  else {
    puts("Wrong :(");
  }
  return !bVar1;
}

次は、check関数を見てみます。パスワード長は 27文字でなければなりません。実際は改行が含まれるようなので、26文字でした。

ちょっと分かりにくいですが、スタックの rbp - 0x50 から、23byte のデータがあります。ざっくり言うと、この 23byte のデータと、パスワードのデータをビット単位で比較して、XOR して、0 になるビットであれば、それが正しいパスワードということになります。ただし、23byte のデータで使うビット位置とパスワードのビット位置は異なる動きをするようなので、そこの見極めが難しそうです。

undefined8 check(char *param_1)
{
  size_t sVar1;
  undefined8 uVar2;
  size_t sVar3;
  undefined8 local_58;
  undefined7 local_50;
  undefined uStack_49;
  undefined7 uStack_48;
  uint local_34;
  uint local_30;
  undefined4 local_2c;
  int local_28;
  uint local_24;
  int local_20;
  int local_1c;
  
  sVar1 = strlen(param_1);
  if (sVar1 == 0x1b) {
    local_58 = 0x617b2375f81ea7e1;
    local_50 = 0x69df5b5afc9db9;
    uStack_49 = 0xd2;
    uStack_48 = 0xf467edf4ed1bfe;
    local_1c = 0;
    local_20 = 0;
    local_2c = 0;
    for (local_24 = 0; local_24 < 0x17; local_24 = local_24 + 1) {
      for (local_28 = 0; local_28 < 8; local_28 = local_28 + 1) {
        if (local_20 == 0) {
          local_20 = 1;
        }
        local_30 = 1 << (7U - (char)local_28 & 0x1f);
        local_34 = 1 << (7U - (char)local_20 & 0x1f);
        if (0 < (int)((int)param_1[local_1c] & local_34) !=
            0 < (int)((int)*(char *)((long)&local_58 + (long)(int)local_24) & local_30)) {
          return 1;
        }
        local_20 = local_20 + 1;
        if (local_20 == 8) {
          local_20 = 0;
          local_1c = local_1c + 1;
        }
        sVar3 = (size_t)local_1c;
        sVar1 = strlen(param_1);
        if (sVar3 == sVar1) {
          return 0;
        }
      }
    }
    uVar2 = 0;
  }
  else {
    uVar2 = 1;
  }
  return uVar2;
}

スタックを可視化します。

アドレス サイズ 内容
rbp
rbp - 0x14 4 local_1c
rbp - 0x18 4 local_20(1→2→...→7→1→...)
rbp - 0x1C 4 local_24
rbp - 0x20 4 local_28
rbp - 0x24 4 空き
rbp - 0x28 4 local_30(0x80→0x40→...→0x00→0x80→...)
rbp - 0x2C 4 local_34(0x40→0x20→...→0x00→0x40→...)
rbp - 0x40 8 fe, 1b, ed, f4, ed, 67, f4, 00(0x00f467edf4ed1bfe)
rbp - 0x48 8 b9, 9d, fc, 5a, 5b, df, 69, d2(0xd269df5b5afc9db9)
rbp - 0x50 8 e1, a7, 1e, f8, 75, 23, 7b, 61(0x617b2375f81ea7e1)
rbp - 0x58 8 passwordのアドレス

GDB で表示したアセンブラも貼っておきます。

pwndbg> disassemble 
Dump of assembler code for function check:
   0x0000000000401156 <+0>:  push   rbp
   0x0000000000401157 <+1>:  mov    rbp,rsp
   0x000000000040115a <+4>:  push   rbx
   0x000000000040115b <+5>:  sub    rsp,0x58
   0x000000000040115f <+9>:  mov    QWORD PTR [rbp-0x58],rdi
   0x0000000000401163 <+13>: mov    rax,QWORD PTR [rbp-0x58]
   0x0000000000401167 <+17>: mov    rdi,rax
   0x000000000040116a <+20>: call   0x401040 <strlen@plt>
   0x000000000040116f <+25>: cmp    rax,0x1b
   0x0000000000401173 <+29>: je     0x40117f <check+41>
   0x0000000000401175 <+31>: mov    eax,0x1
   0x000000000040117a <+36>: jmp    0x40129f <check+329>
   0x000000000040117f <+41>: movabs rax,0x617b2375f81ea7e1
   0x0000000000401189 <+51>: movabs rdx,0xd269df5b5afc9db9
   0x0000000000401193 <+61>: mov    QWORD PTR [rbp-0x50],rax
   0x0000000000401197 <+65>: mov    QWORD PTR [rbp-0x48],rdx
   0x000000000040119b <+69>: movabs rax,0xf467edf4ed1bfed2
   0x00000000004011a5 <+79>: mov    QWORD PTR [rbp-0x41],rax
   0x00000000004011a9 <+83>: mov    DWORD PTR [rbp-0x14],0x0
   0x00000000004011b0 <+90>: mov    DWORD PTR [rbp-0x18],0x0
   0x00000000004011b7 <+97>: mov    DWORD PTR [rbp-0x24],0x0
   0x00000000004011be <+104>:    mov    DWORD PTR [rbp-0x1c],0x0
   0x00000000004011c5 <+111>:    jmp    0x40128e <check+312>
   0x00000000004011ca <+116>:    mov    DWORD PTR [rbp-0x20],0x0
   0x00000000004011d1 <+123>:    jmp    0x401280 <check+298>
   0x00000000004011d6 <+128>:    cmp    DWORD PTR [rbp-0x18],0x0
   0x00000000004011da <+132>:    jne    0x4011e0 <check+138>
   0x00000000004011dc <+134>:    add    DWORD PTR [rbp-0x18],0x1
   0x00000000004011e0 <+138>:    mov    eax,0x7
   0x00000000004011e5 <+143>:    sub    eax,DWORD PTR [rbp-0x20]
   0x00000000004011e8 <+146>:    mov    edx,0x1
   0x00000000004011ed <+151>:    mov    ecx,eax
   0x00000000004011ef <+153>:    shl    edx,cl
   0x00000000004011f1 <+155>:    mov    eax,edx
   0x00000000004011f3 <+157>:    mov    DWORD PTR [rbp-0x28],eax
   0x00000000004011f6 <+160>:    mov    eax,0x7
   0x00000000004011fb <+165>:    sub    eax,DWORD PTR [rbp-0x18]
   0x00000000004011fe <+168>:    mov    edx,0x1
   0x0000000000401203 <+173>:    mov    ecx,eax
   0x0000000000401205 <+175>:    shl    edx,cl
   0x0000000000401207 <+177>:    mov    eax,edx
   0x0000000000401209 <+179>:    mov    DWORD PTR [rbp-0x2c],eax
   0x000000000040120c <+182>:    mov    eax,DWORD PTR [rbp-0x1c]
   0x000000000040120f <+185>:    cdqe
   0x0000000000401211 <+187>:    movzx  eax,BYTE PTR [rbp+rax*1-0x50]
   0x0000000000401216 <+192>:    movsx  eax,al
   0x0000000000401219 <+195>:    and    eax,DWORD PTR [rbp-0x28]
   0x000000000040121c <+198>:    test   eax,eax
   0x000000000040121e <+200>:    setg   cl
   0x0000000000401221 <+203>:    mov    eax,DWORD PTR [rbp-0x14]
   0x0000000000401224 <+206>:    movsxd rdx,eax
   0x0000000000401227 <+209>:    mov    rax,QWORD PTR [rbp-0x58]
   0x000000000040122b <+213>:    add    rax,rdx
   0x000000000040122e <+216>:    movzx  eax,BYTE PTR [rax]
   0x0000000000401231 <+219>:    movsx  eax,al
   0x0000000000401234 <+222>:    and    eax,DWORD PTR [rbp-0x2c]
   0x0000000000401237 <+225>:    test   eax,eax
   0x0000000000401239 <+227>:    setg   al
   0x000000000040123c <+230>:    xor    eax,ecx
   0x000000000040123e <+232>:    test   al,al
=> 0x0000000000401240 <+234>:   je     0x401249 <check+243>
   0x0000000000401242 <+236>:    mov    eax,0x1
   0x0000000000401247 <+241>:    jmp    0x40129f <check+329>
   0x0000000000401249 <+243>:    add    DWORD PTR [rbp-0x18],0x1
   0x000000000040124d <+247>:    cmp    DWORD PTR [rbp-0x18],0x8
   0x0000000000401251 <+251>:    jne    0x40125e <check+264>
   0x0000000000401253 <+253>:    mov    DWORD PTR [rbp-0x18],0x0
   0x000000000040125a <+260>:    add    DWORD PTR [rbp-0x14],0x1
   0x000000000040125e <+264>:    mov    eax,DWORD PTR [rbp-0x14]
   0x0000000000401261 <+267>:    movsxd rbx,eax
   0x0000000000401264 <+270>:    mov    rax,QWORD PTR [rbp-0x58]
   0x0000000000401268 <+274>:    mov    rdi,rax
   0x000000000040126b <+277>:    call   0x401040 <strlen@plt>
   0x0000000000401270 <+282>:    cmp    rbx,rax
   0x0000000000401273 <+285>:    jne    0x40127c <check+294>
   0x0000000000401275 <+287>:    mov    eax,0x0
   0x000000000040127a <+292>:    jmp    0x40129f <check+329>
   0x000000000040127c <+294>:    add    DWORD PTR [rbp-0x20],0x1
   0x0000000000401280 <+298>:    cmp    DWORD PTR [rbp-0x20],0x7
   0x0000000000401284 <+302>:    jle    0x4011d6 <check+128>
   0x000000000040128a <+308>:    add    DWORD PTR [rbp-0x1c],0x1
   0x000000000040128e <+312>:    mov    eax,DWORD PTR [rbp-0x1c]
   0x0000000000401291 <+315>:    cmp    eax,0x16
   0x0000000000401294 <+318>:    jbe    0x4011ca <check+116>
   0x000000000040129a <+324>:    mov    eax,0x0
   0x000000000040129f <+329>:    mov    rbx,QWORD PTR [rbp-0x8]
   0x00000000004012a3 <+333>:    leave
   0x00000000004012a4 <+334>:    ret
End of assembler dump.

同じ動きをする Pythonスクリプトを作ります。条件を満たすビットを論理和で重ねていけば、求められているパスワードが作られていく、みたいな感じにしてみます。

def main():
    
    password = "abcdefghijklmnopqrstuvwxyz"
    
    lst = [ 0xe1, 0xa7, 0x1e, 0xf8, 0x75, 0x23, 0x7b, 0x61, 0xb9, 0x9d, 0xfc, 0x5a, 0x5b, 0xdf, 0x69, 0xd2, 0xfe, 0x1b, 0xed, 0xf4, 0xed, 0x67, 0xf4, 0x00, 0x00, 0x00, 0x00, 0x00 ]
    ret = [ 0 ] * 27
    
    l_1c = 0
    l_20 = 0
    for l_24 in range( 23 ):
        for l_28 in range( 8 ):
            
            if l_20 == 0:
                l_20 = 1
            
            l_30 = 1 << ( (7 - l_28) & 0x1F )
            l_34 = 1 << ( (7 - l_20) & 0x1F )
            
            #if (0 < ord(password[l_1c]) & l_34) != (0 < (lst[l_24] & l_30)):
            #    print( "failure" )
            
            if (lst[l_24] & l_30) != 0:
                ret[l_1c] |= l_34
            
            print( f"ii={l_24:2d}, jj={l_28}: l_30=0x{l_30:02X}, l_34=0x{l_34:02X}, l_20={l_20}, l_1c={l_1c}" )
            
            l_20 += 1
            if l_20 == 8:
                l_20 = 0
                l_1c += 1
            
            if l_1c == 27:
                print( "success" )
    
    ret = [ chr(ii) for ii in ret ]
    ret = "".join( ret )
    print( ret )

if __name__ == "__main__":
    main()

実行してみます。出ました!

$ python perplexed.py
ii= 0, jj=0: l_30=0x80, l_34=0x40, l_20=1, l_1c=0
ii= 0, jj=1: l_30=0x40, l_34=0x20, l_20=2, l_1c=0
ii= 0, jj=2: l_30=0x20, l_34=0x10, l_20=3, l_1c=0
ii= 0, jj=3: l_30=0x10, l_34=0x08, l_20=4, l_1c=0
ii= 0, jj=4: l_30=0x08, l_34=0x04, l_20=5, l_1c=0
ii= 0, jj=5: l_30=0x04, l_34=0x02, l_20=6, l_1c=0
ii= 0, jj=6: l_30=0x02, l_34=0x01, l_20=7, l_1c=0
ii= 0, jj=7: l_30=0x01, l_34=0x40, l_20=1, l_1c=1
ii= 1, jj=0: l_30=0x80, l_34=0x20, l_20=2, l_1c=1
ii= 1, jj=1: l_30=0x40, l_34=0x10, l_20=3, l_1c=1
ii= 1, jj=2: l_30=0x20, l_34=0x08, l_20=4, l_1c=1
ii= 1, jj=3: l_30=0x10, l_34=0x04, l_20=5, l_1c=1
ii= 1, jj=4: l_30=0x08, l_34=0x02, l_20=6, l_1c=1
ii= 1, jj=5: l_30=0x04, l_34=0x01, l_20=7, l_1c=1
ii= 1, jj=6: l_30=0x02, l_34=0x40, l_20=1, l_1c=2
ii= 1, jj=7: l_30=0x01, l_34=0x20, l_20=2, l_1c=2
ii= 2, jj=0: l_30=0x80, l_34=0x10, l_20=3, l_1c=2
ii= 2, jj=1: l_30=0x40, l_34=0x08, l_20=4, l_1c=2
ii= 2, jj=2: l_30=0x20, l_34=0x04, l_20=5, l_1c=2
ii= 2, jj=3: l_30=0x10, l_34=0x02, l_20=6, l_1c=2
ii= 2, jj=4: l_30=0x08, l_34=0x01, l_20=7, l_1c=2
ii= 2, jj=5: l_30=0x04, l_34=0x40, l_20=1, l_1c=3
ii= 2, jj=6: l_30=0x02, l_34=0x20, l_20=2, l_1c=3
ii= 2, jj=7: l_30=0x01, l_34=0x10, l_20=3, l_1c=3
ii= 3, jj=0: l_30=0x80, l_34=0x08, l_20=4, l_1c=3
ii= 3, jj=1: l_30=0x40, l_34=0x04, l_20=5, l_1c=3
ii= 3, jj=2: l_30=0x20, l_34=0x02, l_20=6, l_1c=3
ii= 3, jj=3: l_30=0x10, l_34=0x01, l_20=7, l_1c=3
ii= 3, jj=4: l_30=0x08, l_34=0x40, l_20=1, l_1c=4
ii= 3, jj=5: l_30=0x04, l_34=0x20, l_20=2, l_1c=4
ii= 3, jj=6: l_30=0x02, l_34=0x10, l_20=3, l_1c=4
ii= 3, jj=7: l_30=0x01, l_34=0x08, l_20=4, l_1c=4
ii= 4, jj=0: l_30=0x80, l_34=0x04, l_20=5, l_1c=4
ii= 4, jj=1: l_30=0x40, l_34=0x02, l_20=6, l_1c=4
ii= 4, jj=2: l_30=0x20, l_34=0x01, l_20=7, l_1c=4
ii= 4, jj=3: l_30=0x10, l_34=0x40, l_20=1, l_1c=5
ii= 4, jj=4: l_30=0x08, l_34=0x20, l_20=2, l_1c=5
ii= 4, jj=5: l_30=0x04, l_34=0x10, l_20=3, l_1c=5
ii= 4, jj=6: l_30=0x02, l_34=0x08, l_20=4, l_1c=5
ii= 4, jj=7: l_30=0x01, l_34=0x04, l_20=5, l_1c=5
ii= 5, jj=0: l_30=0x80, l_34=0x02, l_20=6, l_1c=5
ii= 5, jj=1: l_30=0x40, l_34=0x01, l_20=7, l_1c=5
ii= 5, jj=2: l_30=0x20, l_34=0x40, l_20=1, l_1c=6
ii= 5, jj=3: l_30=0x10, l_34=0x20, l_20=2, l_1c=6
ii= 5, jj=4: l_30=0x08, l_34=0x10, l_20=3, l_1c=6
ii= 5, jj=5: l_30=0x04, l_34=0x08, l_20=4, l_1c=6
ii= 5, jj=6: l_30=0x02, l_34=0x04, l_20=5, l_1c=6
ii= 5, jj=7: l_30=0x01, l_34=0x02, l_20=6, l_1c=6
ii= 6, jj=0: l_30=0x80, l_34=0x01, l_20=7, l_1c=6
ii= 6, jj=1: l_30=0x40, l_34=0x40, l_20=1, l_1c=7
ii= 6, jj=2: l_30=0x20, l_34=0x20, l_20=2, l_1c=7
ii= 6, jj=3: l_30=0x10, l_34=0x10, l_20=3, l_1c=7
ii= 6, jj=4: l_30=0x08, l_34=0x08, l_20=4, l_1c=7
ii= 6, jj=5: l_30=0x04, l_34=0x04, l_20=5, l_1c=7
ii= 6, jj=6: l_30=0x02, l_34=0x02, l_20=6, l_1c=7
ii= 6, jj=7: l_30=0x01, l_34=0x01, l_20=7, l_1c=7
ii= 7, jj=0: l_30=0x80, l_34=0x40, l_20=1, l_1c=8
ii= 7, jj=1: l_30=0x40, l_34=0x20, l_20=2, l_1c=8
ii= 7, jj=2: l_30=0x20, l_34=0x10, l_20=3, l_1c=8
ii= 7, jj=3: l_30=0x10, l_34=0x08, l_20=4, l_1c=8
ii= 7, jj=4: l_30=0x08, l_34=0x04, l_20=5, l_1c=8
ii= 7, jj=5: l_30=0x04, l_34=0x02, l_20=6, l_1c=8
ii= 7, jj=6: l_30=0x02, l_34=0x01, l_20=7, l_1c=8
ii= 7, jj=7: l_30=0x01, l_34=0x40, l_20=1, l_1c=9
ii= 8, jj=0: l_30=0x80, l_34=0x20, l_20=2, l_1c=9
ii= 8, jj=1: l_30=0x40, l_34=0x10, l_20=3, l_1c=9
ii= 8, jj=2: l_30=0x20, l_34=0x08, l_20=4, l_1c=9
ii= 8, jj=3: l_30=0x10, l_34=0x04, l_20=5, l_1c=9
ii= 8, jj=4: l_30=0x08, l_34=0x02, l_20=6, l_1c=9
ii= 8, jj=5: l_30=0x04, l_34=0x01, l_20=7, l_1c=9
ii= 8, jj=6: l_30=0x02, l_34=0x40, l_20=1, l_1c=10
ii= 8, jj=7: l_30=0x01, l_34=0x20, l_20=2, l_1c=10
ii= 9, jj=0: l_30=0x80, l_34=0x10, l_20=3, l_1c=10
ii= 9, jj=1: l_30=0x40, l_34=0x08, l_20=4, l_1c=10
ii= 9, jj=2: l_30=0x20, l_34=0x04, l_20=5, l_1c=10
ii= 9, jj=3: l_30=0x10, l_34=0x02, l_20=6, l_1c=10
ii= 9, jj=4: l_30=0x08, l_34=0x01, l_20=7, l_1c=10
ii= 9, jj=5: l_30=0x04, l_34=0x40, l_20=1, l_1c=11
ii= 9, jj=6: l_30=0x02, l_34=0x20, l_20=2, l_1c=11
ii= 9, jj=7: l_30=0x01, l_34=0x10, l_20=3, l_1c=11
ii=10, jj=0: l_30=0x80, l_34=0x08, l_20=4, l_1c=11
ii=10, jj=1: l_30=0x40, l_34=0x04, l_20=5, l_1c=11
ii=10, jj=2: l_30=0x20, l_34=0x02, l_20=6, l_1c=11
ii=10, jj=3: l_30=0x10, l_34=0x01, l_20=7, l_1c=11
ii=10, jj=4: l_30=0x08, l_34=0x40, l_20=1, l_1c=12
ii=10, jj=5: l_30=0x04, l_34=0x20, l_20=2, l_1c=12
ii=10, jj=6: l_30=0x02, l_34=0x10, l_20=3, l_1c=12
ii=10, jj=7: l_30=0x01, l_34=0x08, l_20=4, l_1c=12
ii=11, jj=0: l_30=0x80, l_34=0x04, l_20=5, l_1c=12
ii=11, jj=1: l_30=0x40, l_34=0x02, l_20=6, l_1c=12
ii=11, jj=2: l_30=0x20, l_34=0x01, l_20=7, l_1c=12
ii=11, jj=3: l_30=0x10, l_34=0x40, l_20=1, l_1c=13
ii=11, jj=4: l_30=0x08, l_34=0x20, l_20=2, l_1c=13
ii=11, jj=5: l_30=0x04, l_34=0x10, l_20=3, l_1c=13
ii=11, jj=6: l_30=0x02, l_34=0x08, l_20=4, l_1c=13
ii=11, jj=7: l_30=0x01, l_34=0x04, l_20=5, l_1c=13
ii=12, jj=0: l_30=0x80, l_34=0x02, l_20=6, l_1c=13
ii=12, jj=1: l_30=0x40, l_34=0x01, l_20=7, l_1c=13
ii=12, jj=2: l_30=0x20, l_34=0x40, l_20=1, l_1c=14
ii=12, jj=3: l_30=0x10, l_34=0x20, l_20=2, l_1c=14
ii=12, jj=4: l_30=0x08, l_34=0x10, l_20=3, l_1c=14
ii=12, jj=5: l_30=0x04, l_34=0x08, l_20=4, l_1c=14
ii=12, jj=6: l_30=0x02, l_34=0x04, l_20=5, l_1c=14
ii=12, jj=7: l_30=0x01, l_34=0x02, l_20=6, l_1c=14
ii=13, jj=0: l_30=0x80, l_34=0x01, l_20=7, l_1c=14
ii=13, jj=1: l_30=0x40, l_34=0x40, l_20=1, l_1c=15
ii=13, jj=2: l_30=0x20, l_34=0x20, l_20=2, l_1c=15
ii=13, jj=3: l_30=0x10, l_34=0x10, l_20=3, l_1c=15
ii=13, jj=4: l_30=0x08, l_34=0x08, l_20=4, l_1c=15
ii=13, jj=5: l_30=0x04, l_34=0x04, l_20=5, l_1c=15
ii=13, jj=6: l_30=0x02, l_34=0x02, l_20=6, l_1c=15
ii=13, jj=7: l_30=0x01, l_34=0x01, l_20=7, l_1c=15
ii=14, jj=0: l_30=0x80, l_34=0x40, l_20=1, l_1c=16
ii=14, jj=1: l_30=0x40, l_34=0x20, l_20=2, l_1c=16
ii=14, jj=2: l_30=0x20, l_34=0x10, l_20=3, l_1c=16
ii=14, jj=3: l_30=0x10, l_34=0x08, l_20=4, l_1c=16
ii=14, jj=4: l_30=0x08, l_34=0x04, l_20=5, l_1c=16
ii=14, jj=5: l_30=0x04, l_34=0x02, l_20=6, l_1c=16
ii=14, jj=6: l_30=0x02, l_34=0x01, l_20=7, l_1c=16
ii=14, jj=7: l_30=0x01, l_34=0x40, l_20=1, l_1c=17
ii=15, jj=0: l_30=0x80, l_34=0x20, l_20=2, l_1c=17
ii=15, jj=1: l_30=0x40, l_34=0x10, l_20=3, l_1c=17
ii=15, jj=2: l_30=0x20, l_34=0x08, l_20=4, l_1c=17
ii=15, jj=3: l_30=0x10, l_34=0x04, l_20=5, l_1c=17
ii=15, jj=4: l_30=0x08, l_34=0x02, l_20=6, l_1c=17
ii=15, jj=5: l_30=0x04, l_34=0x01, l_20=7, l_1c=17
ii=15, jj=6: l_30=0x02, l_34=0x40, l_20=1, l_1c=18
ii=15, jj=7: l_30=0x01, l_34=0x20, l_20=2, l_1c=18
ii=16, jj=0: l_30=0x80, l_34=0x10, l_20=3, l_1c=18
ii=16, jj=1: l_30=0x40, l_34=0x08, l_20=4, l_1c=18
ii=16, jj=2: l_30=0x20, l_34=0x04, l_20=5, l_1c=18
ii=16, jj=3: l_30=0x10, l_34=0x02, l_20=6, l_1c=18
ii=16, jj=4: l_30=0x08, l_34=0x01, l_20=7, l_1c=18
ii=16, jj=5: l_30=0x04, l_34=0x40, l_20=1, l_1c=19
ii=16, jj=6: l_30=0x02, l_34=0x20, l_20=2, l_1c=19
ii=16, jj=7: l_30=0x01, l_34=0x10, l_20=3, l_1c=19
ii=17, jj=0: l_30=0x80, l_34=0x08, l_20=4, l_1c=19
ii=17, jj=1: l_30=0x40, l_34=0x04, l_20=5, l_1c=19
ii=17, jj=2: l_30=0x20, l_34=0x02, l_20=6, l_1c=19
ii=17, jj=3: l_30=0x10, l_34=0x01, l_20=7, l_1c=19
ii=17, jj=4: l_30=0x08, l_34=0x40, l_20=1, l_1c=20
ii=17, jj=5: l_30=0x04, l_34=0x20, l_20=2, l_1c=20
ii=17, jj=6: l_30=0x02, l_34=0x10, l_20=3, l_1c=20
ii=17, jj=7: l_30=0x01, l_34=0x08, l_20=4, l_1c=20
ii=18, jj=0: l_30=0x80, l_34=0x04, l_20=5, l_1c=20
ii=18, jj=1: l_30=0x40, l_34=0x02, l_20=6, l_1c=20
ii=18, jj=2: l_30=0x20, l_34=0x01, l_20=7, l_1c=20
ii=18, jj=3: l_30=0x10, l_34=0x40, l_20=1, l_1c=21
ii=18, jj=4: l_30=0x08, l_34=0x20, l_20=2, l_1c=21
ii=18, jj=5: l_30=0x04, l_34=0x10, l_20=3, l_1c=21
ii=18, jj=6: l_30=0x02, l_34=0x08, l_20=4, l_1c=21
ii=18, jj=7: l_30=0x01, l_34=0x04, l_20=5, l_1c=21
ii=19, jj=0: l_30=0x80, l_34=0x02, l_20=6, l_1c=21
ii=19, jj=1: l_30=0x40, l_34=0x01, l_20=7, l_1c=21
ii=19, jj=2: l_30=0x20, l_34=0x40, l_20=1, l_1c=22
ii=19, jj=3: l_30=0x10, l_34=0x20, l_20=2, l_1c=22
ii=19, jj=4: l_30=0x08, l_34=0x10, l_20=3, l_1c=22
ii=19, jj=5: l_30=0x04, l_34=0x08, l_20=4, l_1c=22
ii=19, jj=6: l_30=0x02, l_34=0x04, l_20=5, l_1c=22
ii=19, jj=7: l_30=0x01, l_34=0x02, l_20=6, l_1c=22
ii=20, jj=0: l_30=0x80, l_34=0x01, l_20=7, l_1c=22
ii=20, jj=1: l_30=0x40, l_34=0x40, l_20=1, l_1c=23
ii=20, jj=2: l_30=0x20, l_34=0x20, l_20=2, l_1c=23
ii=20, jj=3: l_30=0x10, l_34=0x10, l_20=3, l_1c=23
ii=20, jj=4: l_30=0x08, l_34=0x08, l_20=4, l_1c=23
ii=20, jj=5: l_30=0x04, l_34=0x04, l_20=5, l_1c=23
ii=20, jj=6: l_30=0x02, l_34=0x02, l_20=6, l_1c=23
ii=20, jj=7: l_30=0x01, l_34=0x01, l_20=7, l_1c=23
ii=21, jj=0: l_30=0x80, l_34=0x40, l_20=1, l_1c=24
ii=21, jj=1: l_30=0x40, l_34=0x20, l_20=2, l_1c=24
ii=21, jj=2: l_30=0x20, l_34=0x10, l_20=3, l_1c=24
ii=21, jj=3: l_30=0x10, l_34=0x08, l_20=4, l_1c=24
ii=21, jj=4: l_30=0x08, l_34=0x04, l_20=5, l_1c=24
ii=21, jj=5: l_30=0x04, l_34=0x02, l_20=6, l_1c=24
ii=21, jj=6: l_30=0x02, l_34=0x01, l_20=7, l_1c=24
ii=21, jj=7: l_30=0x01, l_34=0x40, l_20=1, l_1c=25
ii=22, jj=0: l_30=0x80, l_34=0x20, l_20=2, l_1c=25
ii=22, jj=1: l_30=0x40, l_34=0x10, l_20=3, l_1c=25
ii=22, jj=2: l_30=0x20, l_34=0x08, l_20=4, l_1c=25
ii=22, jj=3: l_30=0x10, l_34=0x04, l_20=5, l_1c=25
ii=22, jj=4: l_30=0x08, l_34=0x02, l_20=6, l_1c=25
ii=22, jj=5: l_30=0x04, l_34=0x01, l_20=7, l_1c=25
ii=22, jj=6: l_30=0x02, l_34=0x40, l_20=1, l_1c=26
ii=22, jj=7: l_30=0x01, l_34=0x20, l_20=2, l_1c=26
picoCTF{0n3_bi7_4t_a_7im3}

おわりに

今回、picoCTF 2025 にリアルタイムで参戦しています。今回は、Reverse Engineering の全7問のうち、Windowsプログラムの 2問を除いて、全部解けました!(Windowsプログラムもいずれ勉強します。。)

次回は、Binary Exploitation をやっていきます。

最後になりましたが、エンジニアグループのランキングに参加中です。

気楽にポチッとよろしくお願いいたします🙇

今回は以上です!

最後までお読みいただき、ありがとうございました。




以上の内容はhttps://daisuke20240310.hatenablog.com/entry/picoctf2025reより取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14