はじめに
2023年6月3日の14:00から丸24時間にわたって開催された,SECCON Beginners CTF 2023に参加しました。(常設でない)CTFの大会への参加はこれが2回目ですので,まだまだ圧倒的に(特にweb系の)知識が足りていないという実感はしているのですが,何事も一旦ある程度仕上げてから実戦に行くというのはどうも性分に合わず,どれだけ解けるんだろうかという感じで参加してみました。
- はじめに
- 本題
- 解けた問題たち
- Welcome(welcome)06/03 14:00:35
- CoughingFox2(crypto)06/03 14:20:09
- Conquer(crypto)06/03 14:37:31
- Half(reversing)06/03 14:49:43
- Three(reversing)06/03 15:11:14
- Forbidden(web)06/03 15:22:51
- YARO(misc)06/03 16:29:37
- poem(pwnable)06/03 17:38:28
- polyglot4b(misc)06/03 18:54:25
- ABC304(competitive programming)06/03 21:00:00-23:00:00
- aiwaf(web)06/04 00:10:45
- sleep(life)06/04 02:00:00-10:00:00
- Choice(crypto)06/04 13:02:31
- 解けなかった問題たち
- 解けた問題たち
- おわりに
本題
解けた問題たち
Welcome(welcome)06/03 14:00:35
はい。大抵のコンテストでここに置かれる問題は,Welcomeとは名ばかりじゃないかみたいなのではなく,本当に望まれた客のように温かく出迎えてくれる問題が出るらしいです。FLAGの形式はctf4b{[\x20-\x7e]+}
という正規表現で最初に伝えられていますが,こんな感じのを探せばいいのねという目安になるのでとてもありがたいです。
CoughingFox2(crypto)06/03 14:20:09
さて,まずは自分が一番解けそうなcryptoから解いていくことにします。
main.py
をみると
for i in range(len(flag)-1): c = ((flag[i] + flag[i+1]) ** 2 + i) cipher.append(c) random.shuffle(cipher)
という記述が見えます。どうやらflag
の隣り合う文字を足して2乗してindexを足したものを配列にし,それをランダムにシャッフルしているようです。ですから,前から1つずつ決めていくことを考え,末尾2つの文字の計算結果がもしcipher
にある数になったら,それで確定させていくという方針で問題なさそうです。以上より,以下のコードを実行してFLAGを得ることができました。
#include <bits/stdc++.h> using namespace std; int main() { vector<int> cipher = {4396, 22819, 47998, 47995, 40007, 9235, 21625, 25006, 4397, 51534, 46680, 44129, 38055, 18513, 24368, 38451, 46240, 20758, 37257, 40830, 25293, 38845, 22503, 44535, 22210, 39632, 38046, 43687, 48413, 47525, 23718, 51567, 23115, 42461, 26272, 28933, 23726, 48845, 21924, 46225, 20488, 27579, 21636}; string flag = "c"; set<int> s; for (int v : cipher) s.insert(v); int idx = 0; while (s.size() > 0) { for (int i = 0x20; i <= 0x7e; i++) { int b = (flag.back() + i) * (flag.back() + i) + idx; if (s.count(b)) { flag += i; s.erase(b); break; } } idx++; } cout << flag << endl; return 0; }
Conquer(crypto)06/03 14:37:31
1問目が比較的さくっと解けたので,この調子で2問目も首尾よく解きたいところです。
problem.py
を見ると
key = getrandbits(length) cipher = flag ^ key for i in range(32): key = ROL(key, pow(cipher, 3, length)) cipher ^= key
という記述が見え,cipher
とkey
は与えられていますから,操作を逆にたどることができればflag
を復元することができそうです。XORは2回繰り返すと元に戻るのでこの部分は造作もないことです。あとは関数ROL
の逆の操作をできればよいです。ROL
を見ると
def ROL(bits, N): for _ in range(N): bits = ((bits << 1) & (2**length - 1)) | (bits >> (length - 1)) return bits
となっており,これはbits
を長さlengthで左にN
だけ回転させる操作ですから,右にN
回戻してやる関数をこんな感じで書いてあげます。
def ROLr(bits, N): for _ in range(N): bits = (bits >> 1) | ((bits & 1) << (length - 1)) return bits
あとはlength
がflag.bit_length()
なので少し困りますが,key
が十進数で132桁の数なのでを計算すると438.49と分かりますから,適当に439を放り込んだのち,print(ROLr(ROL(key, 10), 10))
があっていることを確認して,以下のコードを実行するとFLAGを得ることができました。
from Crypto.Util.number import * def ROLr(bits, N): for _ in range(N): bits = (bits >> 1) | ((bits & 1) << (length - 1)) return bits length = 439 key = 364765105385226228888267246885507128079813677318333502635464281930855331056070734926401965510936356014326979260977790597194503012948 cipher = 92499232109251162138344223189844914420326826743556872876639400853892198641955596900058352490329330224967987380962193017044830636379 for i in range(32): cipher ^= key key = ROLr(key, pow(cipher, 3, length)) flag = cipher ^ key print(long_to_bytes(flag))
Half(reversing)06/03 14:49:43
次に,何とかなりそうなreversingに手を付けてみます(本当はcryptoのChoiceをちょっと見たんですが,すぐに解ける気がしなかったので移行しました)。IDAは知っているのでまずhalf
をIDAで開いてみます。すると,2つの文字列がはっきり見えましたから,これをつなげてFLAGを得ることができました。FLAGの文字列を見て気づきましたが,これなら単にstrings half
でよかったですね。
Three(reversing)06/03 15:11:14
同様に,これもIDAでthree
を開いてみます。すると,なにやらvalidate_flag
なる関数があるのが見えます。そうするとflag_0
,flag_1
,flag_2
を呼んでいて,それぞれc4c_ub__dt_r_1_4}
,tb4y_1tu04tesifg
,f{n0ae0n_e4ept13
だと分かります。いかにもパタトクカシーーの3つ版をやってくださいと言っているので,それをするとFLAGを得ることができました。
Forbidden(web)06/03 15:22:51
webは結構苦手な(というよりも単純に知識そのものがない)んですがさすがにbeginnerくらいは解けるんじゃないかと思って手を付けてみることにします。与えられたサイトはhttps://forbidden.beginners.seccon.games/でhttps://forbidden.beginners.seccon.games/flagにアクセスすればよさそうなんですが,はじかれてしまいます。app/index.js
を見ると
const block = (req, res, next) => { if (req.path.includes('/flag')) { return res.send(403, 'Forbidden :('); } next(); }
とあって,パスの中に/flag
という文字列がそっくりそのままあると駄目みたいです。なので,どうにかして/flag
という文字列を含めずに/flag
にアクセスしたいです。うろ覚えの知識でパーセントエンコーディングみたいなやつだったっけなみたいなことをいろいろやっていたんですが,/FLAG
と大文字にすると通過して,FLAGを得ることができました。どうやらケースセンシティブではないみたいです。学びを得ました。
YARO(misc)06/03 16:29:37
miscって何だろうと思い調べてみると,miscellaneousの略らしいことが分かりました。発音が案外非自明な/mìsəléiniəs/で,mixと同じ語源らしいです。部立てでいうと雑というやつでしょうか。さて,server.py
を見ると,rule
を入力させたのち
compiled = yara.compile(source=rule) for root, d, f in os.walk('.'): for p in f: file = os.path.join(root, p) matches = compiled.match(file, timeout=60) if matches: print(f'Found: {file}, matched{matches}') else: print(f'Not found: {file}')
とyara.compile
なる関数でコンパイルしたルールをファイルたちに適用させているようです。試しにnc yaro.beginners.seccon.games 5003
をしてrule_example.yar
にある
rule shebang { strings: $shebang = /^#!(\/[^\/ ]*)+\/?/ condition: $shebang } rule maybe_python_executable { strings: $ident = /python(2|3)\r*\n/ condition: shebang and $ident }
を食わせてみると
Found: ./redir.sh, matched: [shebang] Found: ./server.py, matched: [shebang, maybe_python_executable] Not found: ./flag.txt Not found: ./requestments.txt
を吐いてくれました。なんとなく読んでみると,rule
の後が規則名的なやつで,strings
で正規表現が使えて,condition
でその条件になった時にmatchした旨を知らせてくれる感じかなと思い,以下を食わせてみます
rule r { strings: $c = "c" condition: $c }
すると案の定
Found: ./redir.sh, matched: [r] Found: ./server.py, matched: [r] Found: ./flag.txt, matched: [r] Found: ./requestments.txt, matched: [r]
を吐いてくれて,flag.txtに文字cが含まれていることが分かりました。あとはこのルールをctf4b{
に0x20~0x7eまでの文字を1文字づつ追加していって,どのルールにmatchするかを見てやれば,flag.txtの長さ-6回のクエリで特定することができます。せっかく正規表現が使えるのであんまり効率的ではないですが,解けるものは解けるので以下のコードを書きました。
#include <bits/stdc++.h> using namespace std; int main() { string now = "ctf4b{"; string out; for (int i = 0x20; i <= 0x7e; i++) { out += "rule "; out += "r" + char(i); out += " {\n"; out += " strings:\n"; out += " $"; out += "s" + char(i); out += " = \"" + now; out += (char)i; out += "\"\n"; out += " condition:\n"; out += " $"; out += "s" + char(i) + "\n"; out += "}\n"; } cout << out << endl; return 0; }
と,この出力結果を食わせるとSomething wrong
と吐いてきて何ですかとなったんですが,出力結果を見れば当然でスペースだのバックスラッシュだのが普通に通るわけがないです。そのため,(どうせ英大小文字と数字とアンダーバーと}なので)範囲を絞って,i
をcharではなく数として出力することにしました。改善したコードが以下です。
#include <bits/stdc++.h> using namespace std; typedef long long ll; ll toInt(string s, ll b = 10) { assert(b > 1); ll res = 0; for (char c : s) { res *= b; res += c - '0'; } return res; } string toStr(ll x, ll b = 10) { assert(b > 1 && x >= 0); if (x == 0) return "0"; string res; while (x) { res += (x % b) + '0'; x /= b; } reverse(res.begin(), res.end()); return res; } int main() { string now = "ctf4b{"; string out; for (int i = 48; i <= 125; i++) { if (i == 92) continue; out += "rule "; out += "r" + toStr(i); out += " {\n"; out += " strings:\n"; out += " $"; out += "s" + toStr(i); out += " = \"" + now; out += (char)i; out += "\"\n"; out += " condition:\n"; out += " $"; out += "s" + toStr(i) + "\n"; out += "}\n"; } cout << out << endl; return 0; }
これを実行で出力結果を食わせながら,吐かせたmatchに対応する文字をnow
の末尾に追加していけば,いつか}が現れるので,これでFLAGを得ることができました。
poem(pwnable)06/03 17:38:28
pwnableは一番「ハッキング」をしているという気がしてできると楽しいので手を付けてみます。
src.c
を見ると
scanf("%d", &n); if (n < 5) { printf("%s\n", poem[n]); }
とあり,いかにもやばそうなコードです。グローバルにchar *flag
とchar *poem[]
が順に置かれていて,n
が負の時をはじいていないので,このあたりを利用してflag
の中身をprintさせればよさそうです。poem[n]
は*(poem + n)
のことですから,(たぶんできる人は一発で計算できるんでしょうけど,私にはそのような能力がないので-1,-2…を順に入力することにより)-4を入力すると,FLAGを得ることができました。
polyglot4b(misc)06/03 18:54:25
polyは多数(polynomialとかpolymerとかのpolyでしょうか)という意味で,glotは舌(これはあまり聞いたことなかったですがglossaryと同じ語源らしいです)という意味なので,全体でmultilingualみたいな意味らしいです。舌で言語というのはmother tongueとかに思いを馳せて納得しました。
さて,これはプログラミングの文脈では複数の言語でコンパイルできるようなもののことを指すらしく,これはエイプリルフールコンテストとかで何回かやったことのある形式です。polyglot4b.py
を見ると
f_type = subprocess.run( ["file", "-bkr", f"tmp/{f_id}/{f_id}"], capture_output=True ).stdout.decode()
で
if "JPEG" in f_type: types["JPG"] = True if "PNG" in f_type: types["PNG"] = True if "GIF" in f_type: types["GIF"] = True if "ASCII" in f_type: types["TXT"] = True
とあり,f_type
にJPG
,PNG
,GIF
,ASCII
の全ての文字列が含まれているようなものを求めればよさそうです。美味しそうなお寿司sample/sushi.jpg
を,text_script.sh
を参考に召し上がってもらうと(しばらくこれに気付かなくて,どうやってバイナリを標準入力に送り込むんだろうとかいろいろ調べてましたが),当然ながらJPG
はOKそうです。file sushi.jpg
をしてみると
sushi.jpg: JPEG image data, Exif standard: [TIFF image data, big-endian, direntries=4, description=CTF4B], baseline, precision 8, 1404x790, components 3
と吐き出され,descriptionがいかにも書き換えられそうなので,右クリックしてプロパティ>詳細からPNGIFASCII
に書き換えると,FLAGを得ることができました。
ABC304(competitive programming)06/03 21:00:00-23:00:00
張り切って参加しましたが,残念ながらUnratedになりました。
aiwaf(web)06/04 00:10:45
app/app.py
を見ると
file = request.args.get("file") # 中略 with open(f"./books/{file}", encoding="utf-8") as f: return f.read().replace(KEY, "")
とあって
f"""\ 以下の{puuid}に囲まれた部分のURLクエリはパストラバーサル攻撃でしょうか? そうである場合Yesを、違う場合Noを返してください。 ../やflagという文字列が含まれていた場合もYesを返してください。 {puuid} {urllib.parse.unquote(request.query_string)[:50]} {puuid} """
とかいうprompt
をAIに食わせているみたいなのでこの通りにはじかれてしまうみたいです。ここからなにやら?file=../flag
とすればいいのではないかということが逆にわかってしまいました。とはいっても,当然このままではAIにはじかれてしまうので困るんですが,ここで[:50]
に注目すると,クエリストリングのうち最初の50文字しか見ていないようですので,これを利用しない手はないです。クエリストリングは&
で複数指定できるので,request.query_string
がどんな仕様かは知りませんでしたが,なんとなく全てのクエリをいい感じに拾ってくれるでしょと,適当に?aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=book1.txt&file=../flag
を投げると,残念ながらAIにはじかれてしまいました。これがいう「気まぐれ」なのかなと思いましたが,単に同じ文字がいっぱい並んでいるのはかなり怪しそうなので,なんか意味のある文字列を投げてみることにします。試しに,?ThequickbrownfoxjumpsoverthelazydogThequickbrownfoxjumpsoverthelazydog=book1.txt&file=../flag
とするとAIにはじかれず,FLAGを得ることができました。
sleep(life)06/04 02:00:00-10:00:00
一瞬オールしようかと思ったんですが,もう私もそんなに若くないためでさすがに明日以降に響いてしまうので,あったかいふとんでぐっすりねました。ちょっとぐっすりしすぎましたかね。
Choice(crypto)06/04 13:02:31
server.py
を見ると,何やらRSA暗号の気配です。
奇しくも,コンテスト中に#9 暗号理論 - 笑わない数学 - NHKが放送されているのを観測して,うんうんとなっていました。この笑わない数学,ABC予想とかP≠NP予想の回を観たことがあるんですが,尾形さんが一生懸命「わかりやすい」解説をしてくれたり,普通はコンピュータがやるところを数を小さくして頑張って手計算したりしていてなんだかとても癒やされるんですよね。
閑話休題,がflagを整数に直したもので
が与えられています。また,いくつか質問を投げることができ,についてを聞けるみたいです。この問題に限らず,ある自然数をとし,1つめの式の両辺を乗すると
すなわち
であり,オイラーの定理よりですから
のとき
となり,flagを復元できます。そのため,を求めないと解けそうにありません。乗法性より
ですから,対称式に思いを馳せて,を求めれば良さそうです。まとめると
- を求める
- を求める
- を求める
- を求める
という感じです。と,ここまでは順調だったのですが,ここから先が大変でした。
まず,式をこねこねしてとかとかなるとかを考えて,
を満たすような感じにすればよさそうなんじゃないかとか,迷宮にはまり込んでしまいました。
こんなのを1時間くらいやっていたんですが,ここでやっぱり対称式って対称なまま変形するのがもっともだよなと思っていると,そういえばって対称式を使って漸化式が作れたような気がする,というおぼろげな記憶が脳内に浮かび上がってきました。さっそく検索すると, 対称式について覚えておくべき7つの公式 | 高校数学の美しい物語が出てきて
が成立するとあります。これは無からでも,次数を揃えたいという思惑の下で導けなくはない式です。これが分かってしまえばもう解けたも同然で,はまさにクエリで聞くことのできるものですから,より大きい連続4整数なら何でもいいですがなんとなくとしてみます。よって,求めるは
で計算できます。途中で,あれでの割り算をしないといけないのにがそもそも分からなくて困ったなと一瞬考えてしまったんですが,1次不定方程式を考えてあげて拡張ユークリッドの互除法を使うことで対数時間でできますから,十分高速です。またpythonならpow(fs[2], -1, n)
とするだけで逆元を求めてくれます。あとは先ほど確認した流れの通りに計算すればが求まり,以下のコードを実行することでFLAGを得ることができました。なお途中で,3次方程式くらいpythonなら簡単に解けるようになっているだろうと思って解いてもらって,になっていることを念のため確認しています。
import sympy from Crypto.Util.number import * n = 18594550144547301333494330440727776321888361219176721067949296945328255249954628307014771206354387992788849094369885364310718228616250534865474996610051308966556663513723774648204138691124155758903715670449495386324341982424372348006314484048087221986823132438653329070617438308229162206886018749114388232554770882124576797234528168647721468061130240000009961736239289406130443086805586878797823927326251348506576180968621212384821330459417776031528751245144779238339037181035019403383704825809754775457158685619237158318168078973375749838072557762835300894929117815659839771328470141036945068417476122304376457803421537844804263578952666410904251530063550310671358146130161038676245076225153104653328579612568130178527397102201450325377763663195186232431356635649083119956424152442105158042604930244205655650846058478007779641925441638181268347095773707292578033077539161414302600982934164451130259061692713617965808525775706316991584688941677794868509981835117478617441665410620748661142428791580699137883153900909645948151807614712106654970231563776077145134141743451153674521494759420742831287853816783005018002982334781293226295634494939193866208956755695275599019914093070515911865097181250631279419933015363070006317691956094799311246182093778097496044243467400529695870747862884609047516877188834217674267413565462195834950315899190217647731281742246014695811019365437664168577053650189544051756367280332782926906642571915503305100402747283029037973541060616787881611915489676815297815488300693048486195522622840700659546607863989212042671317168336110056993734546348545229039485955806005503511573079093279143714008546998039690324458538616639264673123707871575906337313756795608368817932155368265068857021593264223477917381523546491114471418028659951483819379243259563026852723985354642576294216010525128965283026914124732795606749900735076833 e = 65537 c = 10342344907508164765757749919736793352421298410257707305337740282726570614361245233079594385172204228032038008591858829473590298640501612048679712881374460561447979225128106405099192546793981982904899317923409634997879975551981774052544506672989053390029245957379799097945980399159472017099743251162202689417924297496568358594561467023936207423370478335087967546820774935321601454985234148742581683153878329113809706037195132918847881648102665634946452769752004738678048613117527355401794136229705473310192028404361793214991751048535468030619280111134646927837387112430640912403183494723762710533123467822560454338674871509088078617215104155538321276306593153365689313507111715593208178750474531554078490621196839045999551808039533099200652245563619075253478968019566598334595510426814578251026928765863898664774174937080085258312573331457953954032167115597294784688185718000498198837840388823869869039770901112808401481842477934082132389734049421864722446630519821963832522757259715527389788819253085960619598701608415614363559776562905252352608574063449143712540440762622807964305913114301955104121946995740939383395500652249689345874020609138786867786314805547809787147762332634373065475892940322875352016448887691612736784481963472093559208443302511301148129686988612648036587744123151478358079878342235074480902690963990591450559650164661940324949539482132123667109842243030386135018643698795724632719766772255136235129735804849262376248978066582798887621824292957037633913779910202133655441441749570894499609343385415177847125861379981445766435166827219863591917023963573145138447219164203029541433395486348895940466848629638668409529745367441778934635514256250606203623457958839091652623581718481597181728520014497453438570461342930454656943186232641965098703860007216291599015775321140513837342980975049323871697499863284637432909825862229383 s = 2120634407522122525612964053723418142035008003255455641592096603456441760283345528336036431616648322472299059244408007538824000258439838164923407796210909174392749039689427657509527024321339368766677838230545546152048159591282948054638843277903718675625417096955477229483557817363652777867299775888794629564948319225630717435163745607264899467164669119381163336081234217611079146087415453718433920809714724927290349782340431924401594517556836279285779579582540079396131786945740751425128174277504540352669303736847346259677401249241222962966084053096514192634047880439503138321146600760173621421765552658327328684465349942072734704198471541667702290485272759550855086436727812318199946306573660114360541345535141316329963524548671128433222439611653145584650375144692530823811014234892066102319037508553519874861693312123282704274237023375383998670981458631283698478829787764623014405274064787856756691607537071194586111473847051717089009845180552903955935294535346554523933181032790185310551237189202850698277680542460128073462138788009846800208251823875295010557977414826644268230493594558427653290107215989409804313567576233643717506604654708332004251361738146594932146752494896835692533733480315459560645163269962949101443720683719 fs = [ 9359501005480405779372710337021936432612280671999663121224008343684348547062055177732575429571794635366077719318279834954613701534622751238411344449380669105077531877492942070856872279817136197142861700179442219371669126481511240632262705733433514342048453103073776142104815251824802509052607030561199840896866840234190295328295071231067056995746769744952363086416926470167916718606675390833209135853608631016989358594065690707870761954959783723219414643651172946269076072727031959547501822021793451357307859318270202198806772584767104453437719658844554943176777046964738575128934475064455572542801176476060521343465210886599953149885653692704433416467322527924111144976023813154469929275528381427197666463014913600448150125670235969761685182083061041847170959501412003917934341875224344684063243853622120540530675666598206325571740762222262689985152862807988045374878430937031848130118163297680268887558349756031144224057777767363183480137128750575028773423037507635053500750427053993047430867719891670245254246030747587351697020291635978869762883182042084728870367171535999481778276168605605627466082643591407444298025631658821546366741569412129151723129916728795926503518207681440158950132550575299202240407329039470252795448264146280977136469129359592430786937233861797372045960656375898291038315061240184705330087413201385956924464540349611021531688442713914031566600692897512723282096807836752654010495771370935008984221411218404334390601255614187442675078566740033922743719170914045162896416663168129814954678581230684423885143878178315621955870924698851000742520193911200283266106015560581673170509210197718829917947854680468156578468043278711147295435775370243293453279569233248801876968595066170260601567491128414704770453662410686762584303949993600217793466502532386367813904699613846666069071902049236038106387022805976189783705043012419, 10543425029998132962181500239493477861434578008345153350076833599680238184134128502365629893017040533525777456862353943669497449965084013148167504381540855246240916973944529792826775815219705558931360087355735197764498370877908813276650067243560783170100985032612066805786953951208642312755193876191561681736022170157502785810011066568062259816140273925882721530770934660349547754468301482859330295537400797044228214431306185273230114164504117809623842785562905293483747226759634631046858910537736277692829302358325694636755495593467765357277994806806954900548425500983636169370397165815620807979938938436946191224814114625956294640356878027243641122075872592164275606307281570243093818251295379378426901642313076870540070792970205048387020545394398980694295743299016565837262055580203045998183944535034630718565935072832468965371994049537621095149404747388004718635460955835870138335805009138523211692986251994345499218161809507840485173571934933629260253315541403121466257691295916643281963927610148975027283054797279105512202801655858859508459492105349733390939524018010999268733889268671921118513901587904832702198450370539909070313024854940912125557826279196717929032054776798565825576056585792610159269169391389321365630285828349232513094326846736397706499620861417381690014318845799995647881167399603222175399914960948931556288257936225509799362791821095120383864607774422289019511811524098135411756721865164940159897796828119973521339313211641524307992390265301932872552871536422229261592414235729117424432211618129194657690061438493413463616775559354809573530679422684761923320597829693720614054427406676323665515988773387518144500557685713365051219609284360039980520489127571885668983615066083949961109942541483497374543413485758053303489011738166913583093421042667529143940374683492397875497703924259048139817563467332521743750980925106284, 17263368669868749324631629945924347623452814484804302695429615347766269208862055297251399481097496792607151512106384491115241941375832475148707656805179243577507970470652709184322446635844049937991230875434035141953016706954228802899847598405025212267094783507558745889061000602591061766582039802059434295608552192068042663673562397201985948268771296604897029607599970689036605039319115148528468359269388337219168500817459741027589115043253888471423952720076508385933588034898911577107384535441193568468476975351858154045765051345142381213353006683333295463304178723362769210233546893363988082100423460763855727080604248831902904297897542025435948803206754953154500392986392412973550838111807278326817414863490354597800755348007337443942619195803813395558390996349460984837021559339130392386082430838890197823199906463316762475156291666396031434899152482396673523764845223537047318671493919007934112940175533401725440299557820435604420081803515371790890441032862974529733509700179668080253801470645869201682627121511054508901598322990557971956103642490695583017813326577872987591430557112912489765434609265022192018172908195507022860251884541930215759195624016558527280848165519643677329693845129594537062386008112662703622092333746546420077428433176510538990842933428604316798106327801988971335921456282268180599571651175054200763367906616535895913387522973634858641925315521847464788108792508191115661325958738583768416457819280466154461428023951937851388587990733129452457848684788025460620190795543640320890017452640015752678886643019251839144875505059385749623568573418763579233817052968087418241049258695510976736239291218195078969448908213646145897424552026715957589546814605389799595482038262986011682626413957838958384755088390444975643847329537966565065429785844482073955764737030619944419743134943434856457455894840818686198670489448321435, 15091158770353110043094147522710570656498060900577234783719960661476672213697965858395924321690239802223669957673840049012195861303541015451082025592180466401896956679630831713031141827569465607596311308630428997254725169731800048355447961491668940525226096674750983134223182212437800372699803621354936647258231529529281828003810952286404174218110802340184002914225308404961937168897194937469213367287107660479709208506916017238474429701395134282791082831827783056732057535942116908661911928902610424319945781838790909401599531305991109702143502430103861407601409258543539415045115799797187712471074026771189214660040735881151930741462166600472229023438106233998120307358804854706216709376335569903990851567957766916387968411869312597430312948603351203989493756750820699247766886652207686065092336310468326315427409339851585415164011608912927435587856979441322297242948071445924906209636105837161522237729109955565195220970734365514423235411853299517498004103826811569005725914939499097247248097858459449933088629506881451716696235813488682840729502319099179641634540722744028886222418673838887569156580825230061741495146349700272489224011808351894920923254137237816026168506551369327581568567719163503866307440457516712789500401400949339992400025363958076010251967987719321668268298435199974871351817734751235368997770801280316927026188279532637066645931611700914351878698535718166529701752900030469555069094722317519156980331980351355861263264891237410126466464240531942156683758712051076659223566976110286606089276659749820938958597271010051758844652424665593341213335404115655654669839273458022064436288499747389239943577573848276347238235412653832222539883374416636808950791222770271734462051102590838427622616773279643846933789045855541355688390188173503622483037461817175594969970808909889934531322480613970041063352421240982089876119756075136 ] t = (fs[3] + s*fs[1] - n*fs[0]) * pow(fs[2], -1, n) % n x = sympy.Symbol('x') pqrs = sympy.solve(x**3 - t * (x**2) + s * x - n) print(pqrs[0]*pqrs[1]*pqrs[2] == n) phi = int((pqrs[0]-1)*(pqrs[1]-1)*(pqrs[2]-1)) d = pow(e, -1, phi) m = pow(c, d, n) print(long_to_bytes(m))
以上で,コンテスト終了となりました。
解けなかった問題たち
rewriter2(pwnable)
src.c
を見ると
char buf[BUF_SIZE]; __show_stack(buf); printf("What's your name? "); read(0, buf, READ_SIZE); printf("Hello, %s\n", buf); __show_stack(buf); printf("How old are you? "); read(0, buf, READ_SIZE); puts("Thank you!"); __show_stack(buf);
となっており,BUF_SIZE
が0x20しかないところに0x100も読み取っていますから,このあたりを上手く利用しそうです。下の方を見ると
void win() { puts("Congratulations!"); system("/bin/sh"); }
という怪しげな関数が定義されており,これを呼び出すことが目標となりそうです。また,__show_stack(buf)
で親切にもメモリの状況を教えてくれているので適当にAAAAA
とBBBBBBB
を食わせてとりあえず実行してみます。
[Addr] | [Value] ====================+=================== 0x00007ffcef0c29a0 | 0x0000000000000000 <- buf 0x00007ffcef0c29a8 | 0x0000000000000000 0x00007ffcef0c29b0 | 0x0000000000000000 0x00007ffcef0c29b8 | 0x0000000000000000 0x00007ffcef0c29c0 | 0x0000000000000000 0x00007ffcef0c29c8 | xxxxx hidden xxxxx <- canary 0x00007ffcef0c29d0 | 0x0000000000000001 <- saved rbp 0x00007ffcef0c29d8 | 0x00007fae042c5d90 <- saved ret addr 0x00007ffcef0c29e0 | 0x0000000000000000 0x00007ffcef0c29e8 | 0x00000000004011f6 What's your name? AAAAA Hello, AAAAA [Addr] | [Value] ====================+=================== 0x00007ffcef0c29a0 | 0x00000a4141414141 <- buf 0x00007ffcef0c29a8 | 0x0000000000000000 0x00007ffcef0c29b0 | 0x0000000000000000 0x00007ffcef0c29b8 | 0x0000000000000000 0x00007ffcef0c29c0 | 0x0000000000000000 0x00007ffcef0c29c8 | xxxxx hidden xxxxx <- canary 0x00007ffcef0c29d0 | 0x0000000000000001 <- saved rbp 0x00007ffcef0c29d8 | 0x00007fae042c5d90 <- saved ret addr 0x00007ffcef0c29e0 | 0x0000000000000000 0x00007ffcef0c29e8 | 0x00000000004011f6 How old are you? BBBBBBB Thank you! [Addr] | [Value] ====================+=================== 0x00007ffcef0c29a0 | 0x0a42424242424242 <- buf 0x00007ffcef0c29a8 | 0x0000000000000000 0x00007ffcef0c29b0 | 0x0000000000000000 0x00007ffcef0c29b8 | 0x0000000000000000 0x00007ffcef0c29c0 | 0x0000000000000000 0x00007ffcef0c29c8 | xxxxx hidden xxxxx <- canary 0x00007ffcef0c29d0 | 0x0000000000000001 <- saved rbp 0x00007ffcef0c29d8 | 0x00007fae042c5d90 <- saved ret addr 0x00007ffcef0c29e0 | 0x0000000000000000 0x00007ffcef0c29e8 | 0x00000000004011f6
どうやら入力内容はbuf
の右上の方から埋まっていくみたいです。ここで,IDAを開いてwin
とmain
のアドレスの差を調べると0xCCでしたので,適当になんとなくこんな感じかなというものを書いてみて
f = open("in.txt", 'wb') up = [0x00, 0x00, 0x7F, 0xAE, 0x04, 0x2C, 0x5E, 0x5C] b = b'' for i in range(0x200): k = i % 100 if 0x0c <= k and k <= 0x13: b += up[7-(k-0x04)].to_bytes(1, 'big') else: b += k.to_bytes(1, 'big') f.write(b) f.close()
cat in.txt | ./rewriter2.elf
をしました。そうすると,以下を吐き出しました。
[Addr] | [Value] ====================+=================== 0x00007fff127136d0 | 0x0000000000000000 <- buf 0x00007fff127136d8 | 0x0000000000000000 0x00007fff127136e0 | 0x0000000000000000 0x00007fff127136e8 | 0x0000000000000000 0x00007fff127136f0 | 0x0000000000000000 0x00007fff127136f8 | xxxxx hidden xxxxx <- canary 0x00007fff12713700 | 0x0000000000000001 <- saved rbp 0x00007fff12713708 | 0x00007f3b0032cd90 <- saved ret addr 0x00007fff12713710 | 0x0000000000000000 0x00007fff12713718 | 0x00000000004011f6 What's your name? Hello, [Addr] | [Value] ====================+=================== 0x00007fff127136d0 | 0x0706050403020100 <- buf 0x00007fff127136d8 | 0x042c5e5c0b0a0908 0x00007fff127136e0 | 0x1716151400007fae 0x00007fff127136e8 | 0x1f1e1d1c1b1a1918 0x00007fff127136f0 | 0x2726252423222120 0x00007fff127136f8 | xxxxx hidden xxxxx <- canary 0x00007fff12713700 | 0x3736353433323130 <- saved rbp 0x00007fff12713708 | 0x3f3e3d3c3b3a3938 <- saved ret addr 0x00007fff12713710 | 0x4746454443424140 0x00007fff12713718 | 0x4f4e4d4c4b4a4948 How old are you? Thank you! [Addr] | [Value] ====================+=================== 0x00007fff127136d0 | 0x3f3e3d3c3b3a3938 <- buf 0x00007fff127136d8 | 0x4746454443424140 0x00007fff127136e0 | 0x4f4e4d4c4b4a4948 0x00007fff127136e8 | 0x5756555453525150 0x00007fff127136f0 | 0x5f5e5d5c5b5a5958 0x00007fff127136f8 | xxxxx hidden xxxxx <- canary 0x00007fff12713700 | 0x0b0a090807060504 <- saved rbp 0x00007fff12713708 | 0x00007fae042c5e5c <- saved ret addr 0x00007fff12713710 | 0x1b1a191817161514 0x00007fff12713718 | 0x232221201f1e1d1c *** stack smashing detected ***: terminated Aborted
目論見通りret addr
は0x00007fae042c5e5c
になりましたが,なにやら怪しいことをしているのを検知されてしまったようです。canary
というところが隠されているようで,なんだこれはと英和辞書を引くとカナリアの他に《俗》密告者とあって,この意味ですかねとなりました。そういえばカナリアのピー子ちゃんはストーブのガス管がはずれていたのを知らせていたっけなどと考えていました。どうやらこのcanary
を書き換えてしまうと密告されてしまうようです。しかしながらcanary
の内容は隠されてますし,ret addr
まで行くにはここを通る必要がありそうだしということでここで行き詰ってしまいました。
phisher2(web)
サンプルになっている
curl -X POST -H "Content-Type: application/json" -d '{"text":"https://phisher2.beginners.seccon.games/foobar"}' https://phisher2.beginners.seccon.games
を投げると
{"input_url":"https://phisher2.beginners.seccon.games/foobar","message":"admin: Very good web site. Thanks for sharing!","ocr_url":"https://phisher2.beginners.seccon.games/foobar"}
を投げつけられました。a.py
を見るとtext = request.json["text"]
で"https://phisher2.beginners.seccon.games/foobar"
を取っていて,これをadmin.py
のinput_text
に投げ込んでいるのが見えました。その後いろいろあってrequests.get(f"{input_url}?flag={FLAG}")
をしているのが見えたので,このクエリストリングの内容を見ることができればよさそうです。ということで,curl
についていろいろ調べると--trace
とか-v
とかでいろいろ見れるみたいなのでこれらを試したり,Wiresharkで通信を見てみたりしたんですが,よく分からずとなってしまいました。
poker(reversing)
とりあえずpoker
を実行すると1か2の入力を求められるのでしばらく遊んでいると
================ | Score : 0 | ================ [?] Enter 1 or 2: 1 [-] Player 2 wins! Your score is reseted... ================ | Score : 0 | ================ [?] Enter 1 or 2: 2 [+] Player 2 wins! You got score! ================ | Score : 1 | ================ [?] Enter 1 or 2: 2 [+] Player 2 wins! You got score! ================ | Score : 2 | ================ [?] Enter 1 or 2: 1 [-] Player 2 wins! Your score is reseted... ================ | Score : 0 | ================ [?] Enter 1 or 2: 2 [-] Player 1 wins! Your score is reseted... ================ | Score : 0 | ================ [?] Enter 1 or 2: 1 [+] Player 1 wins! You got score! ================ | Score : 1 | ================ [?] Enter 1 or 2: 2 [+] It's a tie! Your score is reseted... ================ | Score : 0 | ================ [?] Enter 1 or 2: 1 [-] Player 2 wins! Your score is reseted... ================ | Score : 0 | ================
のようになっており,どうやら1か2かを選んで,勝つとscore
が1増えて負けると0になるようです。
poker
をIDAで開いて,main関数をそれらしい名前に変えると
score = 0; sub_21C3(); for ( i = 0; i <= 98; ++i ) { showScore_2222(score); inputNum = getInput_2179(); score = sub_1FB7(score, inputNum); if ( score > 99 ) { sub_11A0(); return 0LL; } }
となっていました。どうやらscore
が100以上にならないと勝てないらしく,何らかの方法で次の勝者を予測する必要がありそうです。問題文からメモリを見ることのできるものが必要そうなので調べると,gdb
というものでできるみたいです。sub_1FB7
でrsp+いくつかのところで乱数を使って勝者を決めているみたいなので,さっそくgdb poker.elf
で走らせて,x/100 $rsp
でメモリを見たんですが,1が勝つ/2が勝つ/引き分けのパターンがよくわからなかったです。HIDWORD(v6)
とHIDWORD(v7)
を比較しているみたいなんですが,どう考えても違うでしょうみたいなものが多く,結局法則性を見出すことはできなかったです。ここで,入力を受け取ってから乱数で勝者を生成していることに気付いたので,これ結局分かったところで無理なのではないかと,何も手立てがなくなってしまいました。
switchable_cat(crypto)
problem.py
を見るとLFSR
なるクラスが定義されており,ざっと見るとからに行くときに,1つだけ右にビットシフトして,の下9bitを見ての偶奇によっていくらかのbitを選んでそれらのXORを128bitめに補うという感じみたいです。何ということはない操作ですが,問題はこれをord("🐈")*ord("🐈")*ord("🐈") * 8
回繰り返すことができないということです。これを実際に計算するとですから到底現実的な時間では終わりません。そこで何か周期性があるんだろうかとρ法のようなことを2000000回くらいまわしてみたんですがループしてくれず,ダブリングみたいなことができるんだろうかと思ったんですがいい方法が思いつかず,解くことができませんでした。
おわりに
解けた問題の内訳は
- crypto 3問
- pwnable 1問
- misc 2問
- web 2問
- reversing 2問
- welcome 1問
で合計829pt,788チーム中100位ちょうどでした。取りたかったcryptoをしっかり解くことができて,十分戦えたという達成感が残り大変良かったです。