先週定山渓で行われたCode 2015に参加してきました。
全体的なはなし
一応スタッフの立場だったのですが、ほとんど仕事をせずにおり、やったことと言えば名札を作ったくらいでした。すいません...
それでもイベントを開催できて無事終えることができたので良かったです。
Code自体いろいろ楽しかったのですが、今回はその中でも特にイベント中にあった出し物について自分の採った戦法とコードをさらしてみようと思います。*1
出し物について
お題と回答については@tututen師匠のブログを見てください。
出し物全体を通して
お題がQRコードを使ったものなのでコードを書く前に手持ちのiPhoneでQRコードを読み取ってみて当たりをつけてからコードを書く様にしました。
言語は自分が一番使い慣れているVB.NETにしました。
ただ、最初に自分の使い慣れている言語を選んでしまったためにQRコードを読み取るためのライブラリ選定にかなり苦労しました。
いくつかのライブラリを試してみて最終的に選んだのはZXingの.NET向けポートであるZXing.Netにしました。
初級編(Q1)について
これは簡単でした。見た瞬間にASCIIコードだとわかります。
これはコードで書かなくても回答可能かと思います。
回答に使ったコードはこんな感じです。
''' <summary> ''' 初級者編の問題を解くクラスです。 ''' </summary> Public Class Q1 Private Sub New() 'Private Constructor End Sub ''' <summary> ''' 答えを取得します。 ''' </summary> ''' <returns>答えの文字列</returns> Public Shared Function GetAnswer() As String 'ZXing.NETを使いQRコードを読み込む Dim Reader As ZXing.BarcodeReader = New ZXing.BarcodeReader() Dim Bitmap = New System.Drawing.Bitmap("[お題のQRコードのあるフォルダ]\01-01.jpg") Dim Result = Reader.Decode(Bitmap) Return Result.Text.Split(" "c) _ .Select( Function(v) '読み込んだ数字部分をASCII文字に変換 Return System.Text.Encoding.ASCII.GetString(New Byte() {Convert.ToByte(v)}) End Function ).Aggregate(Of String)( "", Function(s1, s2) '各文字を連結して返す Return s1 + s2 End Function ) End Function End Class
回答はこんな感じで出てきます。
[CodeJP2015]
中級編(Q2)について
ここが一番苦労しました。
とりあえずBASE64エンコードされてるだろと当たりをつけたものの、デコードしてもそれらしい答えを得ることができず他の暗号化やデータの圧縮方式が無いか暫く探していました。
調べてるなかで、どこの記事かは忘れたのですがCTFの記事でBASE64デコードしたデータをさらにzipかtarで解凍するというのを見つけ、そこからもう一度デコードしてみようという発想を得て答えにたどり着くことができました。
回答のコードは以下。
ただ、実際に答えを探している時はこの様に整形はしておらず、コード片をちょっと使っては修正、さらに使って修正といった感じで汚いコード片の集まりで対処してました。
特にデコードする回数なんかは実際に試してみないとわからないので都度回数を変えたりしてました。
また、ここでQRコードを読み取る際に特定のバーコードが読み取れない現象に遭遇し、Reader.AutoRotate = True
を設定しないといけないという罠もあり、ライブラリの使い方を理解するのにも苦労させられた感じです。
''' <summary> ''' 中級者編の問題を解くクラスです。 ''' </summary> Public Class Q2 Private Sub New() 'Private Constructor End Sub ''' <summary> ''' 答えを取得します。 ''' </summary> ''' <returns>答えの文字列</returns> Public Shared Function GetAnswer() As String 'PNGファイルを読み込んでテキスト文字列を取得する Dim Reader As ZXing.BarcodeReader = New ZXing.BarcodeReader() Reader.AutoRotate = True Dim Base64Text = "" Dim Files = System.IO.Directory.GetFiles("[お題のQRコードのあるフォルダ]", "*.png") Array.Sort(Files) For Each f In Files Dim Bitmap = New System.Drawing.Bitmap(f) Dim Result = Reader.Decode(Bitmap) Base64Text += Result.Text Next '4回デコードする Dim Bytes = New Byte() {} For i = 1 To 4 Bytes = Convert.FromBase64String(Base64Text) Base64Text = System.Text.Encoding.ASCII.GetString(Bytes) Next Return Base64Text End Function End Class
回答は以下
Mt. Moiwa Ropeway,Sapporo Art Park,[Jozankei Hot Springs],Hoheikyo Dam,Marukoma Hot Spring Hotel
上級編(Q3)について
こちらは難しいというよりはひたすら面倒だったという印象です。
あと宴会で結構お酒を飲んでたので上級編に到達した時点くらいで眠さが限界に近付いてたのもあり半分寝ながらの作業でした...次からコードを書くときは飲みすぎない様にします...
中級編で複数回デコードする発想を得たのでとりあえずそれっぽい答えを得るまでBASE64デコードし、Zipファイルをゲットするまではあっさりできました。
ここでいくつかのQRコードがReader.Options.PureBarcode = True
にしないと読み取れない事象に遭遇しましたが、こちらは@Emudomeさんに対処法を教えていただきました。
続けてZipファイルを解凍した後ですが、hint.txt
の! fizzbuzz
については割とすぐ意図はつかめました。
ただ、これがFizz(3の倍数)
、Buzz(5の倍数)
、FizzBuzz(15の倍数)
の内のNot (Fizz or Buzz or FizzBuzz)
、Not (Fizz or Buzz)
、Not (FizzBuzz)
のどれに該当するのかを確かめるのに手間取りました。
あと深夜4時ごろにヒントという名の煽りを見せられた時には心が折れかけました。*2
それでもなんとか翌朝の締め切り直前に答えを得ることができ正解することができました。
回答に使ったコードの全体は以下となります。 こちらも整理済みです。
''' <summary> ''' 上級者編の問題を解くクラスです。 ''' </summary> Public Class Q3 Private Sub New() 'Private Constructor End Sub ''' <summary> ''' 第一の答えを取得します。 ''' </summary> Public Shared Function Get1stAnswer() As String Dim Reader As ZXing.BarcodeReader = New ZXing.BarcodeReader() Reader.AutoRotate = True Reader.Options.PureBarcode = True Dim Words = "" Dim Files = System.IO.Directory.GetFiles("[お題のQRコードのあるフォルダ]", "*.png") 'Get 1st BASE64 words For Each f In Files Dim Bitmap = New System.Drawing.Bitmap(f) Dim Result = Reader.Decode(Bitmap) Words += Result.Text Next '3回デコードする Dim ByteArray = New Byte() {} For i = 1 To 3 ByteArray = Convert.FromBase64String(Base64Text) Base64Text = System.Text.Encoding.ASCII.GetString(ByteArray) Next Return Base64Text End Function ''' <summary> ''' 第一の答えからZipファイルを生成します。 ''' </summary> ''' <param name="fileName">生成するZipファイル名</param> Public Shared Sub GetZipFileFromAnswer1(fileName As String) Dim Base64Text = Get1stAnswer() ' Base64Text = Base64Text.Substring(23) Dim Bytes = System.Convert.FromBase64String(Base64Text) Using W = New System.IO.BinaryWriter(New System.IO.FileStream(fileName, IO.FileMode.Append)) W.Write(Bytes) End Using End Sub ''' <summary> ''' 第二の答え(最終回答)を取得します。 ''' </summary> Public Shared Function Get2ndAnswer() As Dictionary(Of String, String) Dim Reader As ZXing.BarcodeReader = New ZXing.BarcodeReader() Reader.AutoRotate = True Reader.Options.PureBarcode = True Dim Files = System.IO.Directory.GetFiles("[第一の答えを解凍した後のQRコードのあるフォルダ]", "*.png") Array.Sort(Files) Dim LineNo = 1 Dim FizzBuzzText = "" Dim NotFizzBuzzText = "" For Each file In Files Dim Bitmap = New System.Drawing.Bitmap(file) Dim DecodeResult = Reader.Decode(Bitmap) If (LineNo Mod 3 = 0) Or (LineNo Mod 5 = 0) Then 'FizzBuzzのパターンにマッチする番号の画像のテキスト FizzBuzzText += DecodeResult.Text Else 'FizzBuzzのパターンにマッチしない番号の画像のテキスト NotFizzBuzzText += DecodeResult.Text End If LineNo += 1 Next ' Dim Bytes = New Byte() {} 'FizzBuzzのパターンにマッチする場合、11回デコードする For i = 1 To 11 Bytes = Convert.FromBase64String(FizzBuzzText) FizzBuzzText = System.Text.Encoding.ASCII.GetString(Bytes) Next 'FizzBuzzのパターンにマッチしない場合、12回デコードする For i = 1 To 12 Bytes = Convert.FromBase64String(NotFizzBuzzText) NotFizzBuzzText = System.Text.Encoding.ASCII.GetString(Bytes) Next Dim Result = New Dictionary(Of String, String) Result.Add("FizzBuzz", FizzBuzzText) Result.Add("! FizzBuzz", NotFizzBuzzText) Return Result End Function End Class
最終回答は以下
FizzBuzz : [https://goo.gl/xGFW4m] ! FizzBuzz : [Eat.Drink.Sleep.]
とりあえずこんな感じです。