Harekazeで参加。2450点で32位。
以下、自分が解いた or ある程度取り組んだ問題について。
- [RE 300] Backdoor Pi
- [Fun 150] Old School
- [Crypto 800] Bad computations
- [RE 400] Smart Heater (part 1)(解けてない)
- [RE 700] BlaBlaMan(解けてない)
[RE 300] Backdoor Pi
ルート以下を丸ごとZIPで固めたものが渡される。
怪しいファイルを探すところから始めろということか・・・(forensicかよ)。
/bin/back
が「/bin
にある」かつ「更新日時が新しい」といかにも怪しいため、file
してみると、
$ file /bin/back /bin/back: python 2.7 byte-compiled
デコンパイルしてみると、
import sys import os import time from flask import Flask from flask import request from flask import abort import hashlib def check_creds(user, pincode): if len(pincode) <= 8 and pincode.isdigit(): val = '{}:{}'.format(user, pincode) key = hashlib.sha256(val).hexdigest() if key == '34c05015de48ef10309963543b4a347b5d3d20bbe2ed462cf226b1cc8fff222e': return 'Congr4ts, you found the b@ckd00r. The fl4g is simply : {}:{}'.format(user, pincode) return abort(404) app = Flask(__name__) @app.route('/') def hello(): return '<h1>HOME</h1>' @app.route('/backdoor') def backdoor(): user = request.args.get('user') pincode = request.args.get('pincode') return check_creds(user, pincode) if __name__ == '__main__': app.run(threaded=True, host='0.0.0.0', port=3333)
pincodeは約108通りなので、userさえ分かれば全探索で解ける。
pi
(/home
に唯一あったディレクトリ)やadmin
を試してみたがヒットしない。
ユーザ一覧といえば、/etc/passwd
。
$ cat /etc/passwd root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/bin/sh bin:x:2:2:bin:/bin:/bin/sh sys:x:3:3:sys:/dev:/bin/sh sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/bin/sh man:x:6:12:man:/var/cache/man:/bin/sh lp:x:7:7:lp:/var/spool/lpd:/bin/sh mail:x:8:8:mail:/var/mail:/bin/sh news:x:9:9:news:/var/spool/news:/bin/sh uucp:x:10:10:uucp:/var/spool/uucp:/bin/sh proxy:x:13:13:proxy:/bin:/bin/sh www-data:x:33:33:www-data:/var/www:/bin/sh backup:x:34:34:backup:/var/backups:/bin/sh list:x:38:38:Mailing List Manager:/var/list:/bin/sh irc:x:39:39:ircd:/var/run/ircd:/bin/sh gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/bin/sh nobody:x:65534:65534:nobody:/nonexistent:/bin/sh libuuid:x:100:101::/var/lib/libuuid:/bin/sh pi:x:1000:1000:,,,:/home/pi:/bin/bash sshd:x:101:65534::/var/run/sshd:/usr/sbin/nologin ntp:x:102:104::/home/ntp:/bin/false statd:x:103:65534::/var/lib/nfs:/bin/false messagebus:x:104:106::/var/run/dbus:/bin/false usbmux:x:105:46:usbmux daemon,,,:/home/usbmux:/bin/false lightdm:x:106:109:Light Display Manager:/var/lib/lightdm:/bin/false avahi:x:107:110:Avahi mDNS daemon,,,:/var/run/avahi-daemon:/bin/false b4ckd00r_us3r:x:1001:1004::/home/b4ckd00r_us3r:/bin/bash
b4ckd00r_us3r
がものすごく怪しい。
$ python solve_backdoor_pi.py b4ckd00r_us3r Congr4ts, you found the b@ckd00r. The fl4g is simply : b4ckd00r_us3r:12171337
ということで、KLCTF{b4ckd00r_us3r:12171337}
。
[Fun 150] Old School
なんか渡される。
$ file old_school.bin old_school.bin: NES ROM image (iNES): 2x16k PRG, 1x8k CHR [V-mirror]
NESが分からないので調べてみると、Nintendo Entertainment Systemの略で、いわば海外版のファミコンらしい。
ROM配布サイト的なものが多数見つかったが、違法な臭いがプンプンする。著作権とか著作権とか著作権とか。
さて、VirtuaNESなるエミュレータが見つかったので、それで起動。
パックマンだった。
とりあえず1面をクリアしてみると、
出ましたASCII。WELCOME
だそう。
2面もクリアしてやろうと思ったが、なんとこの数字のオブジェクトも壁扱いで、一部のドットが食えない。
正攻法では解けないみたい(CTFだし当然)なので、メモリを眺めていると、
ROMの領域に、2面に対応していそうな部分を発見。
周囲のアドレスにもそれっぽい(各面に対応していそうな)部分があったが、上記画像に77
がないように、一部欠損している。
アドレス順に並べると、次のようになる。
FLAG AT WELCOE YOU WILL FIND THE THE LEVEL EIGH? /NVxy9
(?
は多分T
だが、484
となっている)
8面にフラグがあるっぽいので、楽して8面に飛ぼうと試行錯誤するも、うまく行かない。
しかし、その過程において、0x0060の値がx座標、0x0062の値がy座標であることが分かる。
仕方ないので、必要に応じてそれらの値を書き換えることで、8面まで地道にクリアしていくことに。
結局、各面のメッセージは以下の通り。
1. 2. WELCOME 3. YOU WILL 4. FIND THE 5. FLAG AT 6. THE LEVEL 7. EIGH? 8. /nXNuVxy9
パッと見、/nXNuVxy9
は短縮URLとかのあれっぽい。
ということで、まずは https://goo.gl/nXNuVxy9 にアクセスしてみたが、存在しない。
次に、「海外といえばPastebinだよねー」と
https://pastebin.com/raw/nXNuVxy9 にアクセスしてみると、見事にヒット。
S0xGQ1RGe1czXzRMTF9MMFYzX1IzVFIwfQ==
はい。
$ echo S0xGQ1RGe1czXzRMTF9MMFYzX1IzVFIwfQ== | base64 -d KLFCTF{W3_4LL_L0V3_R3TR0}
しかし、これをそのまま提出してもWA。
問題文の
Concat answer to KLCTF prefix
を参考に幾つか試してみたところ、KLCTFW3_4LL_L0V3_R3TR0
が正解だった。マジ何がしたいのか分からないんすけど・・・。
全チーム中3番目に解けて喜んでいたが、この問題が全問題中最低点数という悲しい事実。
ちなみに - その1
普通なら「SCORE」や「HIGH SCORE」と表示される部分が「PASTE」や「BIN」となっていて、一応それがPastebinを示唆していたっぽい。
答えが分かってからそれがヒントだったと分かるやつ。
guessingはクソ。
ちなみに - その2
メモリ上で484
となっていた部分は、表示上も484
となっていた。
T
は84
なので、おそらくゴミがくっ付いただけで、この面のメッセージは予想通りEIGHT
。
[Crypto 800] Bad computations
最高点の問題のくせに、多くのチームが通していたため挑戦。
渡されたcrypt.py
の識別子を分かりやすくrename&一部関数の計算量を落とすと、
from random import choice from sys import argv from base64 import b64encode from math import sqrt b = 22 def gen_prime_list(first, last): prime_list = [] for a in range(first, last + 1): for i in range(2, a): if (a % i) == 0: break else: prime_list.append(a) return prime_list def sieve(n): is_prime = [True for _ in range(n)] for i in range(2, int(sqrt(n)) + 1): if is_prime[i]: for j in range(i * i, n, i): is_prime[j] = False prime_list = [i for i in range(2, n) if is_prime[i]] return prime_list def xgcd(a, b): if a == 0: return (b, 0, 1) else: g, y, x = xgcd(b % a, a) return (g, x - (b // a) * y, y) def modinv(a, m): g, x, y = xgcd(a, m) if g != 1: raise Exception('Oops! Error!') else: return x % m def L(u, n): return (u - 1) // n if __name__ == '__main__': print("Key cryptor v1.0") if len(argv) != 2: print("Start script like: python crypt.py <YourOwnPasswordString>") if (not str(argv[1]).startswith("KLCTF{")) or (not str(argv[1]).endswith("}")): print("Error! Password must starts with KLCTF") exit() p = choice(gen_prime_list(100, 1000)) q = choice(gen_prime_list(200, 1000)) print("Waiting for encryption...") n = p * q g = None for i in range(n + 1, n * n): if ((i % p) == 0) or ((i % q) == 0) or ((i % n) == 0): continue g = i break if g is None: print("Error! Can't find g!") exit() lamb = (p - 1) * (q - 1) mu = modinv(L(pow(g, lamb, n * n), n), n) % n rc = sieve(n - 1) if len(rc) == 0: print("Error! Candidates for r not found!") exit() if p in rc: rc.remove(p) if q in rc: rc.remove(q) r = choice(rc) password = [ord(x) for x in argv[1][6:-1]] dcew = (pow(g, b, (n * n)) * pow(r, n, (n * n))) % (n * n) for i in range(len(password)): password[i] = (((pow(g, password[i], (n * n)) * pow(r, n, (n * n))) % (n * n)) * dcew) % (n * n) password[i] = (L(pow(password[i], lamb, (n * n)), n) * mu) % n password = b64encode(bytearray(password)) print(str(password)[2:-1])
passwordをいじっている部分の詳細はよく分からないが、「入力長と出力(b64encode()
する前)長は同じ」、「入力のi文字目は出力のi文字目にしか影響しない」ことは容易に分かる。
さらに、ちょっと実験してみると、a
はw
に、b
はx
に、_
はu
にと、変換により+22
されることが分かる(変換式に現れるbの値と同じなので、つまりはそういう式なのだろう)。
また、問題文には以下のようにある。
The answer for this task is the password the encrypted version of which: hnd/goJ/e4h1foWDhYOFiIZ+f3l1e4R5iI+Gin+FhA==
よって、次のように解ける。
from base64 import b64decode tmp = b64decode('hnd/goJ/e4h1foWDhYOFiIZ+f3l1e4R5iI+Gin+FhA=='); print(bytearray([c - 22 for c in tmp]))
$ python solve_crypt.py bytearray(b'paillier_homomorphic_encryption')
ということで、KLCTF{paillier_homomorphic_encryption}
。
paillier homomorphic encryption
でググってみると、Paillier cryptosystem - Wikipediaが出てきたので、これを実装したものなのかな??
何にせよ、これのどこが800点・・・?
[RE 400] Smart Heater (part 1)(解けてない)
何回かサーバが落ちてた。その上、アクセスできても問題ファイルが見つからず。
そんな中、http://94.130.149.88/backup/backup.tar
を見つけてくれたst98さんに感謝(プロ!)。
解凍すると、cli
、index.php
、mainServer
の3ファイル。
解析すると、次のことが分かる。
mainServer
は./pass.conf
から読み込んだ文字列を/dev/shm/shared_memory
に書き込むcli
はポート2323でlistenして、接続を受けるとパスワードを要求する。このときのパスワードは/dev/shm/shared_memory
から読み込んだ文字列。そして、このパスワードがフラグ
しかし、./pass.conf
または/dev/shm/shared_memory
の値を手に入れる方法が分からず、フラグ獲得には至らなかった。
にしても、問題ファイルを探させる必要はあったのか・・・?
[RE 700] BlaBlaMan(解けてない)
PE32+が渡される。同じものがリモートで動いている。
ローカルで実行してみると、
Talk to me: 541ECF0D483B4B570A1EFF6102C88E970F0FDFA8F17FED6B2E342815FE912314
のように64桁のランダムな16進数が表示され、入力待ちになる。
少し時間が経つか、適当な値(表示されている16進数でも)を入力すると、何の表示もなく終了する。
解析すると、次のことが分かる。
seed.txt
から読み込んだ数値と_time64()
の返り値とを掛け算し、それでsrand()
している(多分、seedを予想する解法を防ぐため)- 入力として64桁の16進数を期待している
data.txt
には16進数が4つ書かれている- 表示されている16進数と入力の16進数を先頭から16桁ずつ、4回に分けて処理している。各処理は以下の通り
ans := 表示されている16進数(から抜き出した16桁) x := 入力(から抜き出した16桁) a := data.txtの1つ目の値 b := data.txtの2つ目の値 c := data.txtの3つ目の値 d := data.txtの4つ目の値 tmp := ax^3 - bx^2 + cx - d (mod 2^64) if (tmp !≡ ans (mod 2^64)) { tmpを表示して終了 }
- 適当な値を入力した際に何も表示されなかったのは、
data.txt
がなかったから - 4処理分一致すると、
flag.txt
の内容が表示される
ということで、表示されたtmpを基にa、b、c、dの値をあらかじめ求めておけば、あとはそれを使って合同方程式を解くことでフラグが得られる。
そして、各値は以下のように求まる。
a = 0x23A79C805D1296DD b = 0x88A4495017D18AAA c = 0x91358369274ACD98 d = 0xFB3491CCD0195483
上の合同方程式の解は存在しない場合もあるので、4処理分全てに解があるような16進数が得られなかった場合は再接続の必要がある。
と、ここまでは分かったのだが、肝心の合同方程式の解き方が分からず、フラグ獲得には至らなかった。
あと一歩だったのにー! 悔しい。
追記
BlaBlaManのsolverです.参考にして下さい.https://t.co/xqkjQq4gu5
— ふるかわ(furukawa) (@_N4NU_) 2017年10月10日
SMTソルバで解けるらしい。
ふるかわさん、ありがとうございます!