picoCTF 2022 Writeup

はじめに

メンバーはshiratakiさんと、ny_aさんです。僕の勝手なイメージでshiratakiさんはpwn系、ny_aさんはweb、crypto系だと思っていたのでいいチームバランスだと思っていました。
僕がやるつもりだったのはrevです。

チームはsokusekiです。即席結成なので、このチーム名にしました。(決して煽りではありません。)

合計で13100点を獲得し、80位です。個人では(横取りしてしまったのもありますが)、4600点を獲得しました。3人同じくらいの点数を取っているのでいい具合に助け合えたかなと思っています。(もうこいつとは組まんと思われていなければ幸いです。)

客観的に見て、revは去年に比べて簡単になったと思います。forensicsは去年ほどguessが多くなかった?ように思います。うーん。

良い結果を残せたと思います。では、自身が通した+手伝った問題のwriteupを書きます。

メンバーのWriteup(追記する予定)

Reversing

file-run1(100pt)

$ strings run | grep pico

flag : picoCTF{U51N6_Y0Ur_F1r57_F113_e5559d46}

file-run2(100pt)

$ strings run | grep pico

flag : picoCTF{F1r57_4rgum3n7_96f2195f}

何がしたいんだ

GDB Test Drive

$ chmod +x gdbme

$ gdb -q gdbme

$ start
$ disas
main + 99はsleep関数を呼んでいるよう。なので、ここにbreak pointを貼るみたいですね

$ b *(main+99)
$ r
$ jump *(main+104)

flag : picoCTF{d3bugg3r_dr1v3_72bd8355}

patchme.py(100pt)

pythonのファイルとエンコードされたflagが配布される。
プログラムを実行するとパスワードを求めてきた。

プログラムを見ると改行されて見づらくなった比較文字列がある。(下は改行をなくしたもの

if( user_pw == "ak98-=90adfjhgj321sleuth9000"):

これを入力するとflagを得る。

flag : picoCTF{p47ch1ng_l1f3_h4ck_f01eabfa}

Safe Opener(100pt)

今度はjavaのファイル。プログラム内部にはbase64という言葉とbase64 encodeされてそうな文字列がある。

String encodedkey = "cGwzYXMzX2wzdF9tM18xbnQwX3RoM19zYWYz";

cyber chefで復元。問題分はpicoCTF{password}となっているので、この復元した文字列を中に入れたら良い。

flag : picoCTF{pl3as3_l3t_m3_1nt0_th3_saf3}

unpackme.py(100pt)

pythonのプログラム。payloadがdecryptされて、最後のexecで実行されるよう。

つまり、そのdecrypt結果を見れば内容が分かる。

# 上にはコードがある

plain = f.decrypt(payload) print(plain) # exec(plain.decode())

結果が

b"\npw = input('What\\'s the password? ')\n\nif pw == 'batteryhorse':\n  print('picoCTF{175_chr157m45_cd82f94c}')\nelse:\n  print('That password is incorrect.')\n\n"

flag : picoCTF{175_chr157m45_cd82f94c}

bloat.py(200pt)

pythonファイルとencodeされたflag.txt

今回は難読化と言っていいか分からないけどprintableな文字列をaという配列に代入し、添字を用いて文字列を表現していた。

見やすくなるよう、適宜コード間に改行を挟む。

  • arg132はflag.txt.encを読み出す
  • arg232は入力を受け取る -> arg432に代入する
  • arg432をarg133で比較する

arg133の比較対象を見ればいい。適当に手元のshellでpythonを起動。

a = "string.pritableな文字列たち"
a[71]+a[64]+a[79]+a[79]+a[88]+a[66]+a[71]+a[64]+a[77]+a[66]+a[68]

結果はhappychanceだったので、これを入力する。

flag : picoCTF{d30bfu5c4710n_f7w_161a4f09}

Fresh Java(200pt)

javajavaだけど、classになっている。こういう系はbytecode-viewerで見ます。

見る。ここには入力をけつから見ていくコードがありその比較対象はflagだということが分かる。一文字づつ打ってみたけどなんか弾かれたので、このコードをコピペしてパースを行った。

べちゃってコード貼っちゃいます。

  • KeygenMe.classの中身
import java.util.Scanner;

public class KeygenMe {
   public static void main(String[] var0) {
      Scanner var1 = new Scanner(System.in);
      System.out.println("Enter key:");
      String var2 = var1.nextLine();
      if (var2.length() != 34) {
         System.out.println("Invalid key");
      } else if (var2.charAt(33) != '}') {
         System.out.println("Invalid key");
      } else if (var2.charAt(32) != 'e') {
         System.out.println("Invalid key");
      } else if (var2.charAt(31) != 'b') {
         System.out.println("Invalid key");
      } else if (var2.charAt(30) != '6') {
         System.out.println("Invalid key");
      } else if (var2.charAt(29) != 'a') {
         System.out.println("Invalid key");
      } else if (var2.charAt(28) != '2') {
         System.out.println("Invalid key");
      } else if (var2.charAt(27) != '3') {
         System.out.println("Invalid key");
      } else if (var2.charAt(26) != '3') {
         System.out.println("Invalid key");
      } else if (var2.charAt(25) != '9') {
         System.out.println("Invalid key");
      } else if (var2.charAt(24) != '_') {
         System.out.println("Invalid key");
      } else if (var2.charAt(23) != 'd') {
         System.out.println("Invalid key");
      } else if (var2.charAt(22) != '3') {
         System.out.println("Invalid key");
      } else if (var2.charAt(21) != 'r') {
         System.out.println("Invalid key");
      } else if (var2.charAt(20) != '1') {
         System.out.println("Invalid key");
      } else if (var2.charAt(19) != 'u') {
         System.out.println("Invalid key");
      } else if (var2.charAt(18) != 'q') {
         System.out.println("Invalid key");
      } else if (var2.charAt(17) != '3') {
         System.out.println("Invalid key");
      } else if (var2.charAt(16) != 'r') {
         System.out.println("Invalid key");
      } else if (var2.charAt(15) != '_') {
         System.out.println("Invalid key");
      } else if (var2.charAt(14) != 'g') {
         System.out.println("Invalid key");
      } else if (var2.charAt(13) != 'n') {
         System.out.println("Invalid key");
      } else if (var2.charAt(12) != '1') {
         System.out.println("Invalid key");
      } else if (var2.charAt(11) != 'l') {
         System.out.println("Invalid key");
      } else if (var2.charAt(10) != '0') {
         System.out.println("Invalid key");
      } else if (var2.charAt(9) != '0') {
         System.out.println("Invalid key");
      } else if (var2.charAt(8) != '7') {
         System.out.println("Invalid key");
      } else if (var2.charAt(7) != '{') {
         System.out.println("Invalid key");
      } else if (var2.charAt(6) != 'F') {
         System.out.println("Invalid key");
      } else if (var2.charAt(5) != 'T') {
         System.out.println("Invalid key");
      } else if (var2.charAt(4) != 'C') {
         System.out.println("Invalid key");
      } else if (var2.charAt(3) != 'o') {
         System.out.println("Invalid key");
      } else if (var2.charAt(2) != 'c') {
         System.out.println("Invalid key");
      } else if (var2.charAt(1) != 'i') {
         System.out.println("Invalid key");
      } else if (var2.charAt(0) != 'p') {
         System.out.println("Invalid key");
      } else {
         System.out.println("Valid key");
      }
   }
  • parseのコード
import re

with open('コピペしたファイル', 'r') as f:
    code = f.read()

flag_dat = re.findall(r"\'.\'", code)
for i in range(len(flag_dat)-1, -1, -1):
    print(flag_dat[i][1], end = "")

flag : picoCTF{700l1ng_r3qu1r3d_9332a6be}

Bbbbloat(300pt)

elfファイル
実行すると数を求めてくる。Ghidraで解析

entry部の最初の関数 FUN_00101307がmain。
関数を見ると、直で比較している値がある -> 0x86187

hexで書かれているけど、入力はちゃんと10進数で

flag : picoCTF{cu7_7h3_bl047_695036e3}

unpackme(300pt)

elfファイルだけど、名前の通りupxでパックされているみたい。

$ upx -d unpackme-upx

実行すると、また数を求めてくる。Bbbbloatと同じ。
値は0xb83cb

flag : picoCTF{up><_m3_f7w_e510a27f}

Keygenme(400pt)

main関数の挙動

  • 入力を受け取る(0x25)
  • checker渡す
  • その結果でinvalidかvalidが出力される

checkerの挙動

  • 比較対象の生成
  • まずは、長さの比較(0x24)
  • 次に、一文字づつ比較対象と比較

以上から、まず入力は0x24でないとお話にならないことが分かります。

一文字づつ比較するということから、最近解いていたこの問題を思い出しました。

https://tsalvia.hatenablog.com/entry/2021/04/08/110000#Easy-as-GDB---160-points

なので、gdbを用いた総当りを試そうと思いました(コードは適宜調整)。
しかし、入力の受け取り方が上記の問題と異なるためデータの入力がうまく行きませんでした。なので、コードを用いた総当りは断念しました。

時間を置いて考え直しました。
まず、上記のコードではifが通った回数(そこにbreak pointを貼る)を数えることで適切な入力データを導きます。しかし、その手前でbreak pointを貼れば比較する瞬間・比較対象を見れるのではないかと思いました。

細かいアドレスの調整・gdbとghidraの差の計算はすっとばして、僕は次のようにしました。

$ gdb -q ./keygenme
$ b *0x555555555411
$ run
ここで、入力を手動で入れる : flagの一部+"A" * ( 0x24 - len( flagの一部 ) )
そして、break pointで止まります。
$ n <- これを複数回

すると、次のような部分まで進みます

f:id:Bigdrea6:20220317204410p:plain

RAXが比較対象、RDXが入力データです。

ここで、flagをメモリつつ(flagの一部を更新して、入力データを新しくする)
$ c
$ run

を繰り返します。

RAXにはときおりノイズが入ります。早とちりしてそれをflagに含めないようにします。

flag : picoCTF{br1ng_y0ur_0wn_k3y_abb48a6c}

Wizardlike(500pt)

サポート強。迷路風なゲームです。

静的解析すると10階層であることが分かります。また、マップは文字列として直に埋め込まれています。

~チームメイトパート~
discordで、gdbで現在地(マップ上でのx、y座標と階層)をいじる方法を教えてくれました。
詳しいことはチームメイトのwriteupで書いてくれるのではと思っています。

マップ外に自身を配置することで、flagの一部をゲットしていました。

~自分のパートに戻ります~
教えを受けたので、他のマップを見てみますがかなり時間がかかりました。そこで、次のような解法をすることにしました。

  1. 階層の移動をgdbで行う
  2. 一部のマップとghidraから取ってきたマップを比較
  3. 全体のマップを作り上げる

はい。10回地道にやるだけです。

flag : picoCTF{ur_4_w1z4rd_1496299E}

Forensics

Enhance!(100pt)

ちゃんとsvgsvgファイルが配られる。

$ strings drawing.flag.svg

分割されたflagがある。

flag : picoCTF{3nh4nc3d_24374675}

File types(100pt)

サポート強。自分でやったのだけ書きます。

discordにて

7069636f4354467b66316c656e406d335f6d406e3170756c407431306e5f
6630725f3062326375723137795f37396230316332367d0a

なにこれは

ふむ。この問題ではmd5の話もあったので、md5かなと思ってbit数を調べるが違う。
脳死でlong_to_bytesです。

flag : picoCTF{f1len@m3_m@n1pul@t10n_f0r_0b2cur17y_79b01c26}

Lookey here(100pt)

$ strings anthem.flag.txt | grep pico

flag : picoCTF{gr3p_15_@w3s0m3_58f5c024}

Packets Primer(100pt)

wiresharkで見ます。

4番目のパケットにflagがあります。

flag : picoCTF{p4ck37_5h4rk_b9d5375}

Redaction gone wrong(100pt)

pdfのpdfファイルが配られます。黒で隠された部分をコピペしてみると、見えます。

flag : picoCTF{C4n_Y0u_S33_m3_fully}

Sleuthkit Intro(100pt)

disk imageにmmlsをします。
$ mmls disk.img

ncすると、求めてくるのはLength in sectors
mmlsで出てきた202752を打ちます。

flag : picoCTF{mm15_f7w!}

Sleuthkit Apprentice(200pt)

今回はflag.txtを修復する系。自分はFTK imagerが使い慣れているので、それを使いました。

3つ目のLinuxの中を覗く

root -> root -> my_folder -> flag.uni.txt

flag : picoCTF{by73_5urf3r_adac6cb4}

St3go(300pt)

pngpngファイルが配られます。

$ zsteg -a pico.flag.png

flag : picoCTF{7h3r3_15_n0_5p00n_a1062667}

Torrent Analyze(400pt)

pcapのファイルが渡されます。
ヒントでbt-dhtとか見れるようにしとけよと言われます。

wiresharkで見ててもよく分からんので、jsonに吐き出してエディタで見てました。
bt-dhtの中にもいくつか情報があるみたいで、info_hashが一番目を引きました。

(引きましたといっても結構時間溶かした後にです。)

info_hashは7つほどあり、その中でも重複して呼ばれるhashがありました。

"e2467cbf021192c241367b892230dc1e05c0580e"

これを調べると、以下のURLにたどりつきます。
ubuntu-19.10-desktop-amd64.iso at Linuxtracker

このファイルが答えです。

flag : picoCTF{ubuntu-19.10-desktop-amd64.iso}

Web

Roboto Sans(200pt)

robotとあるので、脳死robots.txtをurlに貼り付けます。

robots.txtは次のような内容でした。

User-agent *
Disallow: /cgi-bin/
Think you have seen your flag or want to keep looking.

ZmxhZzEudHh0;anMvbXlmaW
anMvbXlmaWxlLnR4dA==
svssshjweuiwl;oiho.bsvdaslejg
Disallow: /wp-admin/

真ん中のbase64 encodeされた文字列を復号すると、
js/myfile.txtとなり、ここに移動するとflagを得ます。

flag : picoCTF{Who_D03sN7_L1k5_90B0T5_22ce1f22}

Crypto

credstuff

手伝った。

分からんと、エンコードされたflagが送られてきたのでシーザーっぽいですと

合ってたようです。

Substitution1(100pt)

単一換字暗号って言うんだっけ、メンバーが解けてそうだったんだけどflagが通らないと言っていたので改めてやってみるとちょっとミスがあったみたいだった。

flag : picoCTF{FR3QU3NCY_4774CK5_4R3_C001_7AA384BC}

Very Smooth(300pt)

これは解いたと言っていいのか分からない。メンバーがsmoothなrsaです。と言っていたので調べてみると次がヒット。

blog.csdn.net

コードを拝借。答えでた。

flag : picoCTF{7c8625a1}

Sum-O-Primes

手伝った。p*qとp+qが与えられているということで、何かできないかなぁと調べていると次がヒット。

math.stackexchange.com

手元で計算してみても正しい計算方法だったので、pythonでやってみるけどコードが動かない。ほっちっちにして、このURLだけ共有していたらメンバーが解いてくれた。

bcってやつを使うといいらしい。ほえー