goo blog サービス終了のお知らせ 

Sim's blog

電子工作はじめてみました

ICFP2007 (28) 主催者の種明かし

2007-10-10 23:10:41 | ICFPプログラミングコンテスト
ICFP2007 (27) 画像修復(5) 完成の続きです

主催者の種明かしレポートが公表されているそうです(ここ)。

このテクニカルレポートに書かれている謎はほとんど解明できたと思います。優勝チームはgoogleチームで、なんと二連覇だそうです。

まだ残っているのは以下です。
- sunの正しい修復方法。私はダンプリストと逆アセンブルリストを見ながら手で直しました。
- bioMul_adaptationの修復。草の乱数の初期値はbioMul_adaptationのあたりを修復すればいいという方針はほとんど合っていたみたいですが、動かすところまではできていません。
- ドキュメントには、audio hintという話が書いてありますが見つけていません。

ドキュメントには3903バイトという信じられないような短いprefixが載っています。私の現在のprefixは6312バイトなので半分くらいです。

現在のprefixは、compressorのパクリで、自分の後ろにバックアップのコピーがいて、その後に次の書き換え場所までのオフセットとデータ長さ、データの三つ組みがひたすら並んでいるというコードになっています。(?P)(?P)(?P)とすると自分の後ろにある数字を3つ取り出せます。まだ実装できていませんが、考えているのはquoteを使うことでデータや数値を圧縮することと、MSBが必ず1なのは冗長なのでMSB抜きにする(浮動小数点と同じ考え方)ことの2つくらいです。あとはパッチを当てる修正箇所の最適化くらいでしょうか。

別のアイデアは登場するデータはICFPの4通りしかないので、例えばIだけについて圧縮して次にCについて書き換え箇所を圧縮してと4回繰り返す方法です。データを載せなくていいぶん縮みそうな気もします。数値のエンコード方法を工夫すればなんとかなるかもしれません。4種類の文字があるので4進にするのもあるかもしれません。

DNA中の部分列をコピーすることで縮むかと思いましたが、patchは結構細切れなのであまりうまくいっていません。

主催者が使ったツールを作るのに使われたプログラミング言語が書かれていたりしてなかなか楽しめるドキュメントでした。

ICFP2007 (27) 画像修復(5) 完成

2007-10-07 02:01:40 | ICFPプログラミングコンテスト
ICFP2007 (26) 画像修復(4)の続きです

ついに完成しました。最後に残っていたのはふきだしの中の文字μでした。文字の形はcharInfo_Tempus-Bold-Huge_Mに格納されていますが、このデータは暗号化されていました。
暗号化の鍵はstickyの画像に書かれているno1@Ax3でした。

試しにprefixを作ってみました。長さは9565バイトです。うまく作ればもっと短くなりそうですが、とりあえずはこんな感じです。大きそうなのがballoonの座標データが2kくらい、cloudの修復も2kくらいです。prefixは1つの代入文で、全てパッチを当てています。サブルーチンコールとかはscenarioの中に埋め込まれているので、これもパッチで作っています。
cow-tailへのパッチは暗号化されたまま行っています。RC4は乱数をxorするだけなので、原文と乱数が分かっていれば暗号化されたままでも修正できます。
cloudを反転するコードは難しそうなので、差分だけをパッチあてしています。

ふーっ、長かったー。でも、かなり楽しませてもらいました。

完成した画像です。prefix 9565 bytes risk 9565


ICFP2007 (26) 画像修復(4)

2007-10-02 00:17:52 | ICFPプログラミングコンテスト
ICFP2007 (25) Steganographyの続きです。

草の乱数の初期値が分かりました。
地面の草を描くdrawGrassPatchは第一パラメータの59文字の文字列を乱数seedとして受け取ります。drawGrassPatchの一番最初の処理は文字配列seedとint24配列biomorphPerturbを要素毎に加えています。biomorphPerturbの初期値は0です。biomorphPerturbを書き換えると草が生える場所が変わります。通常のコードでbiomorphPerturbを変更するコードはpayloadBioMorphという関数にしかありません。payloadBioMorphは2つのパラメータindexとvalueをとって(名前は適当に決めました)、biomorphPerturb[index] = valueという処理を行います。
payloadBioMorphはpayloadBioMorph_adaptationから呼ばれています。そしてpayloadBiomorphはmainで一番最初に呼ばれているadaptationであるbiomorph_adaptationで呼ばれています。
さて、このbiomorph_adaptationですが壊れています。少なくともbiomorph_adaptationを修復する必要があります。関連して中で呼ばれているbioMul_adaptationも修復する必要があります。このあたりの話はhelp-initial-condに書かれています。似た記法でbioMulを書いてみると次のようになるはずです。
bioMul Zero     y = Zero
bioMul (Succ x) y = bioAdd (bioMul x y) y

C風に書くと次のような再帰関数になると思います。
int bioMul(int x, int y)
{
    if(x == 0) return 0;
    else bioMul(x - 1, y) + y;
}

今のところ、biomorph_adaptationの完全な修復はできていません。数値の形式が何種類かあるみたいで、どのadaptationにどの形式を渡せばいいのかがよく分かっていません。少なくとも、mkほにゃの形式、bioほにゃの形式、intBoxの形式があります。
biomorph_adaptationの中で一番最初に呼ばれているのはenableBioMorph_adaptationです。中身はfalseになっていますが、trueに変える必要があります。このadaptationはtrueの時にパラメータを逐次実行する機能があるようです。トレースすると実行の様子が変わります。初期値はfalseになっています。enableBioMorph_adaptationのパラメータが上にでてきたpayloadBioMorph_adaptationです。3回呼ばれています。雰囲気的に以下のようなパラメータで呼び出されているっぽいコードになっています。
payloadBioMorph_adaptation 1 * 3  -4 * 4
payloadBioMorph_adaptation 3 * 3   1 * 7
payloadBioMorph_adaptation 7 * 3   2 * 2

このパラメータがpayloadBioMorphに渡されるのだとすると、biomorphPerturbの3番目に-16、9番目に7、21番目に4が書かれるはずです。試しに書いてみたところビンゴで、草が正しい位置に表示されました。
どうせdrawGrassPatchのseedに足されるのでseedを足された値に書き換えてやってもうまくいきます。書き換えた初期値です。

El gasto ziempre se vi mas verde del otro lado de la cerca.

もしかするとbioMul_adaptationの修復だけで十分なのかもしれません。問題の切り分けができていないというか、biomorph_adaptationの他の部分も壊れているのかどうか、よく分かりません。

残っているballoon(ふきだし)ですが、2つのことが分かりました。

1つ目です。グラデーションを描くdrawGradientCornerNWの位置とサイズが分かりました。balloonを取り囲む最小の長方形にすれば、うまくいきました。
左上の座標が(198, 324)でパラメータが(101, 169)です。実際は102x170なのですが、drawGradientCornerNWに渡すときは1ずつ小さい値にしないといけないようです。

2つ目です。分かったというか、無理やり作りました。ターゲット画像からballoonの枠のドットの座標を全部拾い出して、線画で描くときの移動量をプログラムで探索してpolygonのパラメータを作りました。探索プログラムはgreedyなので、最適とは限りませんが、全ての開始地点について探索を行った中で最も線分の数が少なくなるものです。
探索アルゴリズムは基準点から別の点まで線を引いたときに途中の点を全て通る線の中で一番遠くまで引けたものを採用して、基準点を更新していきます。とりあえず一番遠くまで引けなくても、先まで考えると遠くまで引けるかもしれないといった探索は一切行っていません(というのがgreedyな所以ですね)。どうせ塗りつぶすので、途中の内側に点を作ると、もしかすると短縮できるかもしれませんが、考慮していません。
balloonのpolygonデータ
72,
55, 59,
 -7, -1,  -6, 2,   -4,  5,  -2, 2,    0,12,   5, 8,   2, 6,   5, 6,  -3, 5,  -2,  1,
 -4,  7,  -2, 1,  -16, 17,  -4, 3,  -14,12,   0, 5,   1, 2,   2, 5,   5, 5,   5,  1,
  2,  1,   4, 0,    0,  1,  -2, 1,   -1, 3,  -3,-2,  -4,-1,  -4,-3,  -7,-7,  -1, -3,
  0, -9,   7,-6,   36,-36,   1,-3,   -5,-8,  -4,-5,  -1,-3,   0,-2,  -3,-3,  -1, -3,
  0,-10,   7,-8,    1,  0,   5,-5,    1, 0,   1,-1,  -2,-5,  -2,-6,  -1,-9,   2,-11,
  3, -6,   3,-4,    5, -5,   8,-3,    8,-2,   8, 3,   2, 0,   5, 3,   6, 4,   5,  4,
  0,  1,   4, 5,    2,  6,  -2,13,   -4, 5,  -1, 3,  -4, 4,  -6, 3,  -4, 3,  -11  2,
-14,  0,
0, 0,

というわけで最新の修復画像です。残りはμだけです。ターゲット画像とは548ピクセル異なっています。


μですが、たぶんfontTable_Tempus-Bold-HugeというフォントのMです。ただ、この文字を描こうとするとDNAが途中で止まってしまいます。壊れているみたいです。charInfo_Tempus-Bold-Huge_Mがμの描画情報みたいです。
最後の最後まで楽しませてくれます。

ICFP2007 (25) Steganography

2007-09-30 02:13:19 | ICFPプログラミングコンテスト
ICFP2007 (24) 画像修復(3)の続きです。

修復ガイドのpage 3にはsteganographyのことが書かれていました。steganographyは、画像に秘密を埋め込む技術ということのようです。暗号と違うのは鍵に相当するデータがないので隠し方自体が鍵になっていることです。


二つの絵のうち左側が右側に隠されていると書かれています。左側の絵は右側の絵のLSBとして埋め込まれていました。

各RGB値についてLSBが1のとき0xff、0のとき0x00に変換した画像です。


なんか目がチカチカしますが、埋め込まれているのが分かると思います。

他にもないか探してみました。修復ガイドのpage 112です。


同じようにLSBだけ取り出してみました。


ETのいた辺りに何か文字があるのが見えます。拡大してみます。


cow-tailの暗号鍵の9546がこんなところに隠されていました。
ページ番号の112ですが、海外では緊急通報用の電話番号だそうです。日本の110番みたいなものでしょうか。

LSBではありませんが、黄色の文字を読んでいくとprefixになっているものもあります。過去10年間のICFPの紹介です。

[contest-1998] IIP


[contset-1999] IFFCPICPF


[contest-2000] FIICPIIC


[contest-2001] IPPPI


[contest-2002] CIIC


つなげるとhillsEnabled(0x03346a)をFからPに変えるprefixになっています。
IIPIFFCPICPFFIICPIICIPPPICIIC
(?IFPFCC)F; #0P;

prefixが埋め込まれているのは2003までですが、この紹介自体は2007年まであります。ついでなので載せておきます。
[contest-2003]


[contest-2004]


[contest-2005]


[contest-2006]


[contest-2007]


最多優勝はOCaml(1999, 2000, 2002)とHaskell(2001, 2004, 2005)の3回です。さすがにFunctional Programmingの学会ということでしょうか。

ICFP2007 (24) 画像修復(3)

2007-09-27 00:21:34 | ICFPプログラミングコンテスト
ICFP2007 (23) 画像修復(2)の続きです。

最新の画像です。


差分です。3594ピクセル異なっています。


(17) balloon ふきだし

- 修復方法が不明なのでとりあえずskip

(18) 文字列

- 0x4d6606にgoto 0x4d60fbを書く
- 0x4d6316から"s morphed!xff"を書く

文字列を書くのに微妙にメモリが足りません。直前にドイツ風の"Endo hat gemorpht"を表示するコードがあるので、色をセットし終わったら、そちらに飛ばしてやります。

ここまでで、scenarioに沿った修正はおわりです。足りない部分を追加します。どこにどう追加するとprefixが短くなるのかはよく分かっていません

コードを追加するのに使える隙間は以下です。
- sky-day-bodies
- grassの後からflowerbedまで
- chick (ここまでがwindmillより前)
- lambda-id (ここまでがwhaleより前)
- crater
- 文字列からreturnまで

追加するものは以下です。
- sun(太陽)
- clouds(雲) windmillより前
- chick(ひよこ) windmillより後
- 鯨の噴水 whaleより前
- 鯨のcup whaleより後
- balloon(ふきだし)

(19) sunの修復

- [0x206dce + 855] = I
- [0x207192 + 15] = I
- [0x207192 + 47] = C
- [0x207269 + 87] = I
- [0x207269 + 95] = C
- [0x207269 + 176] = C
- [0x207336 + 7] = I
- [0x207336 + 81] = I
- [0x207336 + 104] = C
- [0x2073cc + 49] = C
- 太陽の座標を(480, 20)にする
- 太陽の色はcolorSoftYellow()
- [0x641878 + 48] = 530 (spirograph5 x)
- [0x6418c4 + 48] = 70 (spirograph5 y)
- [0x641a0a + 48] = 1 (spirograph5 magnify)
- [0x641f81 + 48] = 530 (spirograph6 x)
- [0x641fcd + 48] = 70 (spirograph6 y)
- [0x642113 + 48] = 1 (spirograph6 magnify)
- [0x642694 + 48] = 530 (spirograph7 x)
- [0x6426e0 + 48, 24, 70] (spirograph7 y)
- [0x642826 + 48, 24, 1] (spirograph6 magnify)
- spirograph描画後、適当なreturnへjumpする

前半はsunの修復です。これはダンプリストを見ながら手動でおかしな所を直してみました。修正しては逆アセンブルしなおしを繰り返しました。10箇所修正したところでcheckIntegrityもpassになりました。
後半はspirographのパラメータ修正です。これは修復ガイドのpage 180878の中央のspirographです。太陽の中央にはこのspirographを縮小したものが描かれています。page 180878はmainの一部に埋め込まれています。
太陽の色はcolorSoftYellow()です。この色はflowerbedで使われている色と一緒です。sun()ではYellowなので色を修正する必要があります。sunの直前はimpDoc1なので壊しても構わないので、ここが利用できます。

sun自体はsky-day-bodiesの中で呼ばれています。sky-day-bodiesはおおまかにこんな感じになっています。
void sky-day-bodies()
{
    if(weather == 2){
        if(weather == 2) lightningBolt();
        clouds();
    }
    if(weather == 3) sun();
}

一番最初のifを3と比較することにすればcloudsとsunが呼ばれることになります。weatherの初期値は0なのでweather = 3にするとよいことになります。

(20) clouds

- duolcの逆順をcloudにコピーする
- cloud先頭のcloudy = PをFにする
- [0x5c9164] = 25 (雲1 y座標)
- [0x5c963d] = 10 (雲2 サイズ)
- [0x5c94fa] = 55 (雲2 y座標)
- [0x5c99dd] = 20 (雲2 サイズ)
- [0x5c989a] = 30 (雲3 y座標)

cloudとduolcを連結すると回文になっています。cloudは壊れているのですが、duolcから復元できます。回文に関するヒントはpage 1230321です(修復したpage 1230321)。ページ番号も回文になっています。
cloudのパラメータは雲の大きさです。デフォルトは15になっています。

(21) chick (ひよこ)

- 座標を(171, 410)にする

windmillより後に描画しないといけません。パラメータはcolorDuckYellowとcolorDuckOrangeです。scenarioの中ではchick呼び出しの前後に隙間はないのですが、サブルーチンコールでスタックに戻り番地を積む所を飛ばしたい番地に修正することで呼び出し順序を変えることができます。どのように直すのが最適かはまだ分かっていません。

(22) 噴水

- 座標を(430, 200)にする
- compressorのtableの7番目をturnClockwiseにする([0x29bdda + 26] = FFFFF)
- compressorのtableの9番目をturnCounterClockwiseにする([0x29be1a + 26] = CCCCC)
- 描画しおわったら適当なreturnへjumpする

噴水はprintGeneTableのpage 15以降に隠されていました(右下)。このままでは上下さかさまなのでcompressorのturnClockwiseとturnCounterClockwiseのRNAを入れ替えてやります。
whaleより前に描画する必要があります。

(23) cup (暫定)

- waterの座標を(420, 265)にする
- water(), addbmpのRNA, 完全にopaqueなufo-cup(), clip, 通常のufo-cup()の順に呼び出す
- ufo-cupのpolygonはx座標とy座標を両方とも符号を反転させる
- fillのためのmoveToも座標の符号を反転させる
- ufo-cupの座標は(477, 315)

ターゲットの鯨はcupの中の水に浮いている絵になっています。waterをufo-cupの逆さまでclipした後、半透明なufo-cupを上書きすることで絵が完成します。ufo-cupはx方向にもy方向にも反転しています。一応x座標とy座標を両方とも符号反転することで描画しています。時計回りに2回方向を変えることでも実現できるはずですが、まだ試していません。DNAの中では、現在の描画位置をいくつRNAを出力したかを元に追跡しています。DNAの知らない所で向きを変えたり座標を変えたりすると、追跡している座標と実際の座標が矛盾して、それ以降の描画がずれてしまいます。これはとても危険なので安全策として座標反転をしています。後でダミーのmoveをすることで座標のつじつまを合わせることも可能なはずなので、RNAを直接挿入した方がprefixが短くなるはずです。
ufoの中にはufo-cupと同じpolygonが2つも含まれています。それもwaterを呼び出した直後です。ufoの中身をいじくってやることで今回の絵がうまくつくれそうです。
waterは通常は描画されませんがweaterが1または2のときにだけ描画されるようになっています。waterの存在はweatherをいじくって遊んでいるときに気づきました。というかufoの中に隠されていたということになります。

weather = 2にした画像です。雨や雷、ufo-cupに波模様が見えます。


(24) balloon (ふきだし)

balloonの修正方法は分かっていません。少なくとも単一の白ではなくグラデーションがかかっています。グラデーションはdrawGradientCornerNWを使って描かれています。drawGradientCornerNW自体は四角を書くだけなのでふきだしの形でclipしてやる必要があります。グラデーションのパラメータwとhと描画位置のx, yが未知です。またclip用のふきだしの形も分かりません。力技でpolygonデータを作るしかないのかもしれません。もしかするとどこかにヒントになる図形が隠されているかもしれません。
μの描画方法も分かりません。λはフォントを変えてLを書いています。他の文字に変えると表示されるのかもしれません。
もう残っているのはballoonだけです。

以上で修正箇所のまとめをおわります。宿題として残っているのは以下です。
- 草を描く乱数の修正値biomorphPerturbを決めるbiomorph_adaptationの修正
- 座標を符号反転させないufo-cupの描画方法とufoの修正方法
- balloonのグラデーションパラメータと形状
- 追加描画部分はどこに置くのがよいか考える
- prefixの作成と圧縮(というかアセンブラ作らなきゃ)
- ヒントの探索。例えばcow-tailの暗号化鍵9546

その他に思いつくことはサブルーチンコールの戻り番地の修正でfar jumpを作ることができるので、これを利用できないか考えることです。

ずいぶん長いこと、しつこくやっていましたが、ようやっと終わりが見えてきたという感じです。10月になるとICFPの学会が開催されて上位入賞者の発表があります。もしかするとネタバレもあるかもしれません。どうせなら、その前に片付けておきたいところです。

ICFP2007 (23) 画像修復(2)

2007-09-25 03:15:15 | ICFPプログラミングコンテスト
ICFP2007 (22) 画像修復(1)の続きです。

(13) river 左下の川

- 0x71c897をmkGoldfishR_adaptationにする (a)
- 0x71c9ffをmkGoldfishL_adaptationにする (b)
- 0x71cb67をmkGoldfishR_adaptationにする (c)
- 0x71cc3fを18にする (d)
- 0x71cd17をmkGoldfishL_adaptationにする (e)
- 0x71cec7をmkGoldfishL_adaptationにする (f)
- 0x71cf0fをmkGoldfishR_adaptationにする (g)

川を修正する必要はありませんが川の中の魚を修正します。魚はdrawGoldenFish_adaptationで描かれています。この中のgoldenFish_adaptationを修正することになります。adaptationについては修復ガイドのpage 9に書かれています。各adaptationについてはFuunDocに書かれています。

goldenFish_adaptationを逆アセンブルした結果です。段付けは手動です。パラメータで一段深くしています。最初の方はmkBeforeAbove(mkEmp(0x14, 0x14), mkBeforeAbove(...))のように解釈できます。

goldenFish_adaptation 71c66f 0008d0

71c66f 0008a0
    71c687 activateAdaptationTree
    71c69f mkBeforeAbove_adaptation
    71c6b7 0000c0
        71c6cf activateAdaptationTree
        71c6e7 mkEmp_adaptation
        71c6ff 000030
            71c717 intBox
            71c72f 000014
        71c747 000030
            71c75f intBox
            71c777 000014
    71c78f 000780
        71c7a7 activateAdaptationTree
        71c7bf mkBeforeAbove_adaptation
        71c7d7 000390
            71c7ef activateAdaptationTree
            71c807 mkAbove_adaptation
            71c81f 0001e0
                71c837 activateAdaptationTree
                71c84f mkBeforeAbove_adaptation
                71c867 000030
                    71c87f activateAdaptationTree
                    71c897 mkGoldfishL_adaptation ← (a) mkGoldfishR_adaptation
                71c8af 000150
                    71c8c7 activateAdaptationTree
                    71c8df mkBeforeAbove_adaptation
                    71c8f7 0000c0
                        71c90f activateAdaptationTree
                        71c927 mkEmp_adaptation
                        71c93f 000030
                            71c957 intBox
                            71c96f 7ffffe
                        71c987 000030
                            71c99f intBox
                            71c9b7 7fffff
                    71c9cf 000030
                        71c9e7 activateAdaptationTree
                        71c9ff mkGoldfishR_adaptation ← (b) mkGoldfishL_adaptation
            71ca17 000150
                71ca2f activateAdaptationTree
                71ca47 mkBeforeAbove_adaptation
                71ca5f 0000c0
                    71ca77 activateAdaptationTree
                    71ca8f mkEmp_adaptation
                    71caa7 000030
                        71cabf intBox
                        71cad7 00002d
                    71caef 000030
                        71cb07 intBox
                        71cb1f 000018
                71cb37 000030
                    71cb4f activateAdaptationTree
                    71cb67 mkGoldfishL_adaptation ← (c) mkGoldfishR_adaptation
        71cb7f 000390
            71cb97 activateAdaptationTree
            71cbaf mkBeforeAbove_adaptation
            71cbc7 0000c0
                71cbdf activateAdaptationTree
                71cbf7 mkEmp_adaptation
                71cc0f 000030
                    71cc27 intBox
                    71cc3f 000000 ← (d) 0x12
                71cc57 000030
                    71cc6f intBox
                    71cc87 000018
            71cc9f 000270
                71ccb7 activateAdaptationTree
                71cccf mkAbove_adaptation
                71cce7 000030
                    71ccff activateAdaptationTree
                    71cd17 mkGoldfishR_adaptation ← (e) mkGoldfishL_adaptation
                71cd2f 0001e0
                    71cd47 activateAdaptationTree
                    71cd5f mkBeforeAbove_adaptation
                    71cd77 0000c0
                        71cd8f activateAdaptationTree
                        71cda7 mkEmp_adaptation
                        71cdbf 000030
                            71cdd7 intBox
                            71cdef 000019
                        71ce07 000030
                            71ce1f intBox
                            71ce37 00001d
                    71ce4f 0000c0
                        71ce67 activateAdaptationTree
                        71ce7f threeFish_adaptation
                        71ce97 000030
                            71ceaf activateAdaptationTree
                            71cec7 mkGoldfishR_adaptation ← (f) mkGoldfishL_adaptation
                        71cedf 000030
                            71cef7 activateAdaptationTree
                            71cf0f emptyBox_adaptation ← (g) mkGoldfishR_adaptation
71cf27 00c662

(14) whale 鯨

- if文の条件を反転する (0x266585にIPPを書く)
- 座標を(410, 200)にする

噴水はwhaleより前に描く必要があります。またcupはwhaleより後に描く必要があります。噴水とcupについては後で記します。
whaleのパラメータは2つのboolです。1番目が表情でPで泣き顔、Fで笑顔です。2番目は使用されていません。if文の条件を反転することで笑顔にしています。ifを書き換えるかわりに呼び出し時の第1パラメータを書き換える手も考えられます。

(15) crater

- 不要なのでskipする

(16) bmu endoさん

- enableBiomorphをPにする (enableBiomorphはGZ+0x033963)
- weather判定と変なRNAをskip (0x0dafadにgoto 0x0db0fbを書く)
- 変なrnaとendocowをskip (0x0df476にgoto 0x0df78cを書く)
- cow-spot-middleの誤り訂正 (correctErrors(0x0d985b, 0x000b34, 0x0da3a7))
- cow-tailの復号 (crypt("9546", 0x4aa77d, 0x00145c))
- cow-tailの色修正 addOpaqueを7個、addTransparentを3個追加
- cow-tailのintegrity checkをskip

enableBiomorphはPにしなくても、ifをskipするだけでもいいです。次でweather判定をskipしているので統合した方がprefixが短くなります(0x0daee5にgoto 0x0db0fbを書く)。
cow-tailは復号しただけだと不透明です。0.3だけ透明にします。cow-tailの最初でbucketをemptyにするRNAコードがあるので、これをつぶしてやります。そうするとintegrity checkが通らなくなるので、integrity checkもskipします。今はintegrity checkの所に透明化用のRNA出力を上書きしています。integrity checkはそのままでcow-tailの途中に飛び込む手もありますが、透明化コードを置く場所に困ります。既存のコードで再利用できそうな部分を探すとよいかもしれません。このあたりはもうちょっと考えた方がよさそうです。

暗号化については修復ガイドのpage 4405829に書かれています(このページの復号結果はここです)。cow-tailの暗号鍵9546は力技で発見しました。どこかにヒントがありそうですが見つかっていません。完全数というわけでもなく謎です。もしかすると特徴のない最小の数かもしれません(笑)。
誤り訂正符号については修復ガイドのpage 84に書かれています(このページは暗号化されていて鍵は42でした)。

bmuはおおまかに以下のような流れになっています。
void bmu(void)
{
    if(enableBioMorph == P){
        if(weather == 1 || weather == 2){
            cowを描く
            endocow();
        } else if(ducksShown == P){
            superDuck();
        } else if(ocamlrules == P){
            ocaml();
        } else {
            mlephant();
        }
    } else {
        endo();
    }
}

各変数の初期値は
enableBioMorph(0x033963) = F
weather(0x03394b) = 0
ducksShown(0x033964) = F
ocamlrules(0x0c9228) = P
です。ducksShownはscenarioの中でmotherDuckWithChicksを呼び出す直前でPが代入されています。値を変えると色々な画像が見れます。

endocowにしたときの画像です。


superDuckです。


ocamlです。


mlephantです。


endoです。


長くなってきたので続きます。

ICFP2007 (22) 画像修復(1)

2007-09-24 16:13:44 | ICFPプログラミングコンテスト
ずいぶん間があいてしまいましたが、ICFP2007 (21) 暗号解読(3)の続きです。

ICFP 2007のコンテストの課題は、与えられた画像を修復するパッチ(prefix)を開発することでした。最低でもDNAを解釈してRNAを作るプログラムと、RNAから画像に変換するプログラムを作る必要があります。色々なヒントがDNAの中に隠されていました。

まだ残されているとは思いますが、見つけるのがつらくなってきたので、画像修復をはじめました。今回は現状報告です。

現状の画像です。ターゲットの画像との差分は5799pixelです。prefixは作っていないので長さはわかりません。


ターゲット画像との差分です。


prefixを作るのに主に以下のようなDNAを使います。
- 代入の変更 (!offset)!skip; #0 value;というコードはoffset番地の内容をvalueに書き換える代入文です。skipはvalueのサイズです。このvalueを書き換えると代入する値を書き換えることになります。
- short jump (!skip); ;というコードはプログラムカウンタをskipバイト進めます。
- far jump !gz(!offset(!size))というDNAは他の関数へのjumpになります。gzはGreen Zoneまでのバイト数、offsetは飛び先のGZからのオフセット、sizeは飛び先のサイズ(Red ZoneにロードされたときのGreen Zoneまでのバイト数)です。

mainの中で呼ばれているscenarioという関数が画像表示を行うメイン処理になります。このscenarioの流れに沿って修復箇所を挙げていきます。

(1) sky 空

- night-or-dayをFにする (night_or_dayはGZ+0x50f)。

Pだと夜空になります。night-or-dayをPにするprefixが修復ガイドのpage 1(help_intro)に載っていました。

(9/25 追記 skyから呼び出されるsky-day-bodiesではweatherの値によってcloudsやsunを呼び出すようです。うまくするとprefixの量を削減できそうです。)

(2) surfaceTransform 山

- hillsEnabledをPにする (hillsEnabledはGZ+0x3346a)。
- surfaceTransform()先頭にあるvirusコードをskipします(short jumpを書き込む)。
- 山1のy座標 242 → 218
- 山1のparabolaの第1パラメータ -28 → -21
- 山1のparabolaの第2パラメータ 3348 → 1848
- 山2のy座標 232 → 235
- 山2のparabolaの第1パラメータ -21 → -28
- 山2のparabolaの第2パラメータ 1848 → 3348
- 山3のsineの第2パラメータ 65 → 60

山パラメータの探索には苦労しました。山自体はdrawFunctionBaseという関数で描画しています。drawFunctionBaseのパラメータは関数へのコールバックです。関数は一つ前のy座標との差分を返します。つまり微分されています。山1と山2のコールバック関数はf(x) = a0*x+b0 + sine(a*x+b)*cです。山3はf(x) = sine(a*x+b)*cだけです。実際に描かれるのはf(x)を積分した関数です。関数の仕様はImpDocに書かれています。結果の値は固定小数点になっていて下位12bitが小数部になっています。
山3のパラメータは試行錯誤だけですく見つかりました。山1と山2のパラメータは全然見つかりませんでした。色々試行錯誤しましたが、結局画像を拡大して関数の値を手動で取り出して、結果が一致するようなパラメータを探索するプログラムを書きました。結果としては山1と山2のパラメータが入れ替わっただけでした。探索自体は山1についてしか行っていません。山1のパラメータが分かった時点で山2のパラメータと入れ替わっていることに気づいたからです。描画位置(y座標)だけは自分で調整しました。
virusはDNA全体をscanしてRNAのemptyを書き換えるものです。virusについては修復ガイドのpage 100に書かれています。というか、これ自体は読めませんがvirusの実例が3つもはいっています。

(3) drawGrassPatch 草(川辺を除く)

草は謎が解明しきれていないので力技で描いています。
川沿いの草は別の場所で書いています。drawGrassPatchでは地面の草を描いています。
草単体の描画はgrass1~grass4の4つのルーチンがありますが、drawGrassPatchではgrass1~grass3の3種類のうちのどれかしか描かれません(バグ?)。
草の位置と種類は乱数で決まります。乱数はcryptでも使っているRC4です(initFastRandomとfastRandom)。草の種類を決めるときは乱数の下位2bitをとっているので0~3の値をとりますが、drawGrassPatchの中では1ならgrass1、2ならgrass2、3ならgrass3、4ならgrass4を呼んでいます。乱数が0になったときは何も描かれません。従ってgrass4が呼ばれることはありません。
乱数シードは"El pasto siempre se ve mas verde del otro lado de la cerca."という文字列になっています。さらにdrawGrassPatchの先頭でbioMorphPerturbという配列の内容が要素ごとに加えられてinitFastRandomに渡されています。bioMorphPerturbの中身は最初は0になっています。草の位置と種類の修正には乱数シード(鍵)を見つけるか、bioMorphPerturbに正しい値をセットしてやる必要があります。
怪しいと思っているがbiomorph_adaptationです。これはmainでscenarioが呼ばれる前に呼び出されているadaptationです。adaptationについては修復ガイドのpage 9 (help-adaptive-genes)に、各adaptationの仕様はFuunDocに書かれています。また、help-initial-condにもadaptationのことが書かれていますが、たぶんこれがbiomorph_adaptationのことです。一番最初に呼び出されているadaptationなのでinitial conditionという話に合致しています。調べてみるとbioMul_adaptationは中身がなく乗算するかわりに0を返すだけになっていたりします。このあたりを解明して修復できれば上のbiomorph_perturbという配列の初期化を行ってくれるのではないかと想像していますが、まだできていません。

画像エディタで拡大して一つずつ場所を合わせた草の座標一覧です。何かの影になっていて一部しか見えていないものについては草の種類が間違っているかもしれません。
	grass(3, 444, 332); // 草の種類、x座標、y座標
	grass(1, 496, 402);
	grass(1, 490, 419);
	grass(1, 510, 541);
	grass(1, 470, 554);
	grass(3, 476, 532);
	grass(1, 460, 484);
	grass(1, 426, 373);
	grass(2, 416, 468);
	grass(2, 416, 527);
	grass(1, 386, 365);
	grass(2, 362, 378);
	grass(1, 356, 398);
	grass(2, 346, 355);
	grass(3, 318, 345);
	grass(1, 364, 450);
	grass(2, 338, 546);
	grass(1, 348, 567);
	grass(3, 304, 491);
	grass(2, 286, 531);
	grass(3, 304, 521);
	grass(2, 210, 464);
	grass(2, 222, 479);
	grass(3, 230, 492);
	grass(1, 242, 520);
	grass(1, 118, 381);
	grass(1, 138, 422);
	grass(3, 150, 466);
	grass(3, 222, 396);
	grass(3, 246, 429);
	grass(1,  58, 376);
	grass(2, 134, 323);
	grass(2, 138, 374);
	grass(2, 162, 506);
	grass(1, 114, 563);
	grass(3, 123, 548);
	grass(3,  96, 430);

(4) flowerbed 右下の花

- 花1 (0, 0) <---> 花2 (34, 0)
- 花3 (17, 24) <---> 花4 (58, 12)

座標を入れ替えています。
色はflowerのパラメータなのでパラメータを入れ替えてもいいかもしれません。
このflowerbedの直前にはdead space(実行されないコード)があります。

(5) cargobox 右下の箱

- 8番目のmagentaをyellowに変更 (0x21f12b + 30番地をCFにする)
- 12番目のgreenをblueに変更 (0x21f1ab + 31番地をPにする)

色を修正します。cargoboxはcompressorを使って描画しているのでRNA tableを書き換えます。
色のRNAコードはどれも似ているので書き換える箇所は少ないです。
compressorについては修復ガイドのpage 123456 (help-compression-rna)とpage 1024に書かれています。

(6) motherDuckWithChicks 親子連れのあひる

- 直前のif文でスキップしないようにする (0x4cf3e8番地にIPPを書く)
- 親あひるの描画パラメータ修正 (0x2c932e番地にCを書く)

親あひるを描くmotherDuckという関数はgene tableのintegrity checkにひっかかる(壊れている)関数です。RNAの表示を逐次実行しながらターゲットと比較していくと親あひるの右足がずれているのが分かりました。足はdrawPolygonを使って描画しています。drawPolygonは移動量のx, yをセットにした配列をパラメータとして受け取ります。このパラメータを修正しました。一番最初のx移動量が0になっているのを2にしています。
polygonデータのデータ構造については修復ガイドのpage 8 (help-encodings)に書かれています。polygonデータの最後の2要素は移動量の総和になっているので壊れているデータが分かります。
ちなみに0を2に書き換えるとcheck integrityも通るようになります。

(7) appletree 右のりんごの木

- appletreeからpeartreeへfar jumpする

もしくはappletreeの呼び出しをpeartreeへの呼び出しに書き換えてもいいかもしれません。呼び出しは4箇所あります。appletreeとpeartreeの違いはappleとpearだけなので、appleからpearへfar jumpしてもいいです。どれにするかはprefixのサイズが小さくなるものを選ぶことになるはずです。

(8) chick 右側のひよこ

- skipする
windmill(風車)より後に描かないといけないのでここのコードはskipします。

(9) weeds 左側の枯れた木

- seedを8128にする。

weeds自体はlsystem-weedという関数で描画されています。lsystem-weedでは乱数を使って成長する方向を決めています。seedはrandomInt関数で発生する乱数のシードです。L-Systemについては修復ガイドのpage 5 (help-lsystems)とpage 180878に書かれています。
一番左の木だけを描くプログラムを作って、seedを変えながらターゲットとの差分が最小になるパラメータを探しました。
この8128という数字は4番目の完全数です。修復ガイドのpage 8128 (help-beautiful-numbers)には完全数のことが色々書いてありました。6, 28, 496の最初の3つの完全数も載っていました。実はページ番号自身も4番目の完全数でした。page 8128はlazinessという鍵で暗号化されていました。このページはgoodVibrationsに123を書いても表示されます。
暗号化されているので何か重要な情報が含まれているのではないかと思っていましたが、実は隠していたのはページ番号だったようです。結局、暗号化されているという事実とページ番号だけ分かれば実際に暗号解読する必要はなかったということです。

(10) vmu 中央のcaravan

- vmuCodeを0x1fにする
- vmuRegCodeを"Out_of_Band_II"にする
- 座標を(267, 210)にする

vmuについてはhelp-vmuに書かれています。vmuRegCodeはvmu-codeに書かれていました。vmu-codeは暗号化されていてgiveMeAPresentに暗号鍵OPEを書くことで表示できました。このprefixはalien-lifeformsに描かれています。のりたんさんの情報によるとこのOPEというのは博士の異常な愛情という映画に出てきた暗号キーだそうです。
vmuCodeはvmuを解析して見つけました。0x1f(= 31)だとcaravan、0x33(= 51)だとufo-with-smoke、それ以外だとhelp-vmuが描かれるよいになっています。どこかにヒントがあったのかもしれませんが分かっていません。

(11) windmill 風車

- drawPolylinePolarとmoveToPolarのパラメータ中の角度を全て+5する(いっぱいある)

9/25 追記 ↑のかわりに、polarAngleIncr(0x0c91e0)に5を入れておくだけでいいです。もしくはsetGlobalPolarRotationをパラメータを5にして呼び出してもいいです。setGlobalPolarRotationはpolarAngleIncrにパラメータをセットするだけの関数です。
moveToPolarでは、角度に必ずpolarAngleIncrを加えてから処理をするので、5をセットしておくだけで全ての角度が+5されることになります。polarAngleIncrの初期値は0なので3文字書き込むだけでwindmillが完成することになります。
night-or-dayとpolarAngleIncrの書き換えだけで差分は98904になります。次の76文字のprefixはこの二つを書き換えます。
IIPIPICCCCCIIICICCCPIICICIIPIPIIIICICCIICCIIICIICCPIICCCCIICIPPPPIPPCPFCFIIC
(!3a3e)P(!c8cd0)III; #0F#1CIC;

riskは989116(= 98904*10+76)なのでScoreboardでは25位相当になります。(ここまで追記)

windmillは、羽根の部分の角度だけが違っています。羽根の部分drawPolylinePolarを使って描いています。drawPolylinePolarが受け取るpolygonはdrawPolylineと違って、最初が要素数、2番目以降が(角度、移動量)が並んでいます。角度は相対ではなく絶対なので全ての要素を書き換えてやる必要があります。drawPolylineは5回呼ばれています。順に左上の羽根、右上の羽根、右下の羽根、左下の羽根、軸です。またfillする関係でmoveToPolarで塗りつぶし開始位置に移動していますが、このパラメータの角度も+5してやります。
windmillはcloudyがPのときに変な色になります。cloudyはcloudを呼ぶと(未登場)Pになるのでcloud中の該当箇所をつぶしておく必要があります。他に影響を受けるのは右下の花と、下の文字です。黄色はcolorGermanyYellowという関数の色ですが、この配色はドイツの国旗からきているようです。

cloudy = Pにした画像


windmillより前に描画しなければいけないのは、clouds(雲)です。逆にwindmillより後に描画しなければいけなのはchick(ひよこ)です。

(12) lambda-id 左側の文字

- 不要なのでskipする

長くなってきたので、続きます。

ICFP2007 (21) 暗号解読(3)

2007-09-13 00:43:58 | ICFPプログラミングコンテスト
ICFP2007 (20) 暗号解読(2)の続きです。

caravanの復号方法が分かりました。これはvmuという関数を読んでいて分かったのですが、caravanはvmuRegCodeを鍵として暗号化されていました。
以前張った画像ですが、このOut_of_Band_IIが鍵でした。最後が読みづらかったのですが、何通りか試してみたところI(アイ)でした。(書きながら思ったのが、vmu-codeは復号できるのだからソースを読めばよかったんですね。)



caravanは修復する画像の真ん中あたりにある車(?)です。画像修復には、鍵による復号は必須ではなくて、各部品は暗号化されていないので自分で並べてやってもいいです。ただし、その分prefixが長くなるので鍵で復号した方がいいです。vmuという関数はvmuMode=1fのときにvmuRegCodeを使ってcaravanを復号して表示するようになっています。



help-activating-genesですが、cryptを使って暗号化しているわけではなく、シーザー暗号でした。ICFPを各々FPICと2文字ずらすと復号できました。page 23になります。



サブルーチンコールの仕組みとかが書かれています。最後に書かれているのはadaptationの話です。dnaの中にはインタプリタみたいなのがあって、コード化されたプログラムを逐次実行するみたいなことができます。具体的にはint24がひたすら並んでいて、各々がコマンドだったりパラメータだったりします。FuunDocに書かれているのがadaptationの各コマンドの概要です。正確に言うとインタプリタとも違うような気もしますが、直接実行されるわけではないところがインタプリタ風です。FuunDoc自体はHaskell風に書いてあります。

最後はcow-tailです。鍵は"9546"でした。javaのプログラムは遅かったので結局Cで書き直してなんとか見つかりました。解読の実行時間は3964.95秒(1時間6分)でした。



プログラムは長くなるので貼りませんが、おおまかに次のような流れです。

- 先頭を2バイト分復号してみる
- 正しく復号できていたら次に進む。だめなら次の鍵にする
- 最後の方まで復号していって、最後の固定パターンが正しく復号できているか調べる。
- 正しく復号されていたら出力しておわり。だめなら次の鍵にして最初に戻る

プログラムの先頭の10文字とプログラムの最後の75文字の平文が既知です。1文字が2ビットなので先頭は2バイトと2ビットしか分かっていないことになります。20ビットくらいだと、偽鍵が大量に発生します。つまり、正しくない鍵でも20ビットくらいならたまたま復号できてしまうこともあります。最初は偽鍵が嫌だったので最後の固定パターンを使って鍵探索していましたが、最後まで復号するのは時間がかかります。しかたないので最初のパターンでふるいにかけたものについてだけ最後まで復号するというように方針を変更しました。

これ以上鍵が長いとさすがにPCでの探索もつらいものがあります。アルゴリズムもそんなに複雑ではないのでFPGAを使って並列探索とかするとよいかもしれません。ブロックRAMを使うとすると24並列とか平気でできそうです。これはおもしろそうなので今後の課題です。

ICFP2007 (20) 暗号解読(2)

2007-09-10 20:38:20 | ICFPプログラミングコンテスト
ICFP2007 (19) 誤り訂正符号の続きです。

DNAの暗号解読は遅くてたまらないのでjavaでcrypt互換ルーチンを作っていました。
mainの中で配列に初期値として入っている値はhelp-beautiful-numbersを復号する本当の鍵とgoodVibrationsだけずれています。goodVibrationsを0から順に増やして鍵探索を行いました。正しく復号できれば、正解ですが、復号できたかどうかはcheckIntegrityを使って判定しています。

探索部分はこんな感じです。
void crackGoodVibrations(){
    int i, gb;
    byte[] ks = {
        (byte)0xd8, (byte)0xc6, (byte)0xee, (byte)0xce,
        (byte)0xda, (byte)0xca, (byte)0xe7, (byte)0xe7
   };
    boolean found = false;

    for(gb = 0; gb <256; gb++){
結果、123が出力されました(16進だと7b)。さっそくgoodVibrationsに123を入れて動かしてみるとhelp-beautiful-numbersが見れました。カタログのpage 8128になります。このページはページ番号を指定しなくてもgoodVibrationsに正しい値を入れておくだけで自動的に見れます。
本当の鍵はlazinessということになります。



完全数のことなんかが書いてあります。博士の愛した数式に出てきました。ラマヌジャンの名前もあります。1729は自然数の3乗の和として2通りに表せる最小の数だそうです(1729 = 12^3+1^3 = 10^3+9^3)。苦労したわりにはあまり役に立つ情報ではないような気もします。わざわざ赤字で書いてあるところはパスワード候補ということでチェックです。

暗号化されている関数で残っているのはcaravan、cow-tail、help-activating-genesです。今まで分かっている暗号化の鍵では復号できませんでした(含む今回の赤字)。関数の終わりはどれも同じパターンであることを利用すれば解読できそうです。もしくは先頭のパターンも分かっている(RNA出力)ので、そこを利用することもできます。とりあえず解読プログラムをのろのろ書き始めました。
関数以外にもデータの方にまだ暗号化されているものが残されているかもしれません。

javaのbyteは-128~127なのでcastしないと0xd6とかが代入できません。なんか面倒です。javaでもprintfが使えるようになったのは便利です。Eclipseには高級なデバッグ機能があるのに、あいかわらずprintfデバッグをしています。

ICFP2007 (19) 誤り訂正符号

2007-09-06 21:52:57 | ICFPプログラミングコンテスト
CFP2007 (18) 暗号解読の続きです。

鍵が分かったので復号してみました。


復号に使ったprefixです。
IIPIFFCPICFPPICIICIPCCCPIICIPPPCCFCFCFIICIIPIIPIPCICIICCIIICPIICIPCICCCCIICCCIII
CCICIICCCPIICIICIPPCPCCCFFCFFCFFFCFFCCCCCCCCICFFCCFFFFCCFFCCFFCFCCCCFICCCFCFFCFI
CCFCCFFCFICFFFFFFFFICIPPPIICIPCCCCICIICICICCPIIPIPICCCIIIIICICIICCCCIIICPIIPIPIC
CCIIIIICICPIICIPCICIIIICCIIIIICCICCCIICPIICIICIPPPIPPCPCFFFCFCCFCFFFCCFFCFFCCFIC
CFCCCCCCCCCCFCFCCCCCCCCICFCFCCCFCFCCFFFCCFCFCCCFICCCCCCFCCFFCCCCCCCCCCCCCICIIC

helpScreen = 84;
crypt("42", help-error-correcting-codes, 0x006ed8);
startup();

8-4ハミング符号なんて書いてあります。correctErrorsという関数を調べてみました。impdocには

void correctErros(int24 data, int24 ecc, int24 size)
Correct errors in a DNA region using the Hamming bits in another DNA region.

と書かれています。dataとeccは同じ長さで各々2文字づつ処理します。dataから取り出した2文字とeccから取り出した2文字を連結して下の表を引いた結果の2文字をdataに書き戻します。??になっているのは、誤り訂正能力を超えていて何に戻せばいいのか分からないところです。IIIIからPPPPまでで4^4 = 256通りあります。
IIII→II	IIIC→II	IIIF→II	IIIP→??	IICI→II	IICC→??	IICF→??	IICP→IF
IIFI→II	IIFC→??	IIFF→??	IIFP→CI	IIPI→??	IIPC→FI	IIPF→IC	IIPP→??
ICII→II	ICIC→??	ICIF→??	ICIP→FC	ICCI→??	ICCC→CC	ICCF→IC	ICCP→??
ICFI→??	ICFC→IP	ICFF→IC	ICFP→??	ICPI→IC	ICPC→??	ICPF→IC	ICPP→IC
IFII→II	IFIC→??	IFIF→??	IFIP→IF	IFCI→??	IFCC→IF	IFCF→IF	IFCP→IF
IFFI→??	IFFC→IP	IFFF→FF	IFFP→??	IFPI→CF	IFPC→??	IFPF→??	IFPP→IF
IPII→??	IPIC→IP	IPIF→CP	IPIP→??	IPCI→FP	IPCC→??	IPCF→??	IPCP→IF
IPFI→IP	IPFC→IP	IPFF→??	IPFP→IP	IPPI→??	IPPC→IP	IPPF→IC	IPPP→??
CIII→II	CIIC→??	CIIF→??	CIIP→CI	CICI→??	CICC→CC	CICF→PI	CICP→??
CIFI→??	CIFC→CI	CIFF→CI	CIFP→CI	CIPI→CF	CIPC→??	CIPF→??	CIPP→CI
CCII→??	CCIC→CC	CCIF→CP	CCIP→??	CCCI→CC	CCCC→CC	CCCF→??	CCCP→CC
CCFI→PC	CCFC→??	CCFF→??	CCFP→CI	CCPI→??	CCPC→CC	CCPF→IC	CCPP→??
CFII→??	CFIC→PF	CFIF→CP	CFIP→??	CFCI→CF	CFCC→??	CFCF→??	CFCP→IF
CFFI→CF	CFFC→??	CFFF→??	CFFP→CI	CFPI→CF	CFPC→CF	CFPF→CF	CFPP→??
CPII→CP	CPIC→??	CPIF→CP	CPIP→CP	CPCI→??	CPCC→CC	CPCF→CP	CPCP→??
CPFI→??	CPFC→IP	CPFF→CP	CPFP→??	CPPI→CF	CPPC→??	CPPF→??	CPPP→PP
FIII→II	FIIC→??	FIIF→??	FIIP→FC	FICI→??	FICC→FI	FICF→PI	FICP→??
FIFI→??	FIFC→FI	FIFF→FF	FIFP→??	FIPI→FI	FIPC→FI	FIPF→??	FIPP→FI
FCII→??	FCIC→FC	FCIF→FC	FCIP→FC	FCCI→FP	FCCC→??	FCCF→??	FCCP→FC
FCFI→PC	FCFC→??	FCFF→??	FCFP→FC	FCPI→??	FCPC→FI	FCPF→IC	FCPP→??
FFII→??	FFIC→PF	FFIF→FF	FFIP→??	FFCI→FP	FFCC→??	FFCF→??	FFCP→IF
FFFI→FF	FFFC→??	FFFF→FF	FFFP→FF	FFPI→??	FFPC→FI	FFPF→FF	FFPP→??
FPII→FP	FPIC→??	FPIF→??	FPIP→FC	FPCI→FP	FPCC→FP	FPCF→FP	FPCP→??
FPFI→??	FPFC→IP	FPFF→FF	FPFP→??	FPPI→FP	FPPC→??	FPPF→??	FPPP→PP
PIII→??	PIIC→PF	PIIF→PI	PIIP→??	PICI→PI	PICC→??	PICF→PI	PICP→PI
PIFI→PC	PIFC→??	PIFF→??	PIFP→CI	PIPI→??	PIPC→FI	PIPF→PI	PIPP→??
PCII→PC	PCIC→??	PCIF→??	PCIP→FC	PCCI→??	PCCC→CC	PCCF→PI	PCCP→??
PCFI→PC	PCFC→PC	PCFF→PC	PCFP→??	PCPI→PC	PCPC→??	PCPF→??	PCPP→PP
PFII→PF	PFIC→PF	PFIF→??	PFIP→PF	PFCI→??	PFCC→PF	PFCF→PI	PFCP→??
PFFI→??	PFFC→PF	PFFF→FF	PFFP→??	PFPI→CF	PFPC→??	PFPF→??	PFPP→PP
PPII→??	PPIC→PF	PPIF→CP	PPIP→??	PPCI→FP	PPCC→??	PPCF→??	PPCP→PP
PPFI→PC	PPFC→??	PPFF→??	PPFP→PP	PPPI→??	PPPC→PP	PPPF→PP	PPPP→PP

cow-spot-middleをcow-spot-middle-eccを使って誤り訂正できました。integrity checkはpassになりました。

色々な仕掛けがしてあります。

ICFP2007 (18) 暗号解読

2007-09-05 22:58:57 | ICFPプログラミングコンテスト
ICFP2007 (17) help-palindromesの修復の続きです。

crackKeyAndPrintという関数にpurchase codeを渡してやると暗号解読して鍵を表示してくれます。purchase codeはint24の0(III・・・IIP)を暗号化したものなので、端から順に試していけば鍵が分かるという仕組みです。purchase codeは3つ分かっています。

一つ目はhelp-error-correcting-codes_purchase_codeを解読したものです。


やたら時間がかかります。たった2桁を解読するのに56122457回(5612万2457回)の繰り返しで157.562秒かかりました。

2つ目はvmu-code_purchase_codeです。これの鍵はICFP2007 (10) gene tableの修復で見つけたOPEです。答えは分かっていますが実行してみます。


これは恐ろしく時間がかかりました。繰り返しが1965822242回(19億6582万2242回)で実行時間は5379.296秒です。ほぼ1時間半です。

最後のhelp-beautiful-numbers_purchase_codeはICFP2007 (9) mainの逆アセンブルの結果からすると8桁なので調べるのは無謀っぽいのでやっていません。暗号アルゴリズムがRC4と分かっているのでgoodVibrationsを00-ffと変える方が速そうです。

ところで、crackKeyAndPrint関数の最初では背景と同じ黒い文字で「Harry Potter and the Orion Slave Girls. Out *now* on Arcturus III.」というメッセージを出力しています。これもまた新たな謎です。どこかに使う鍵なのかもしれません。ハリーポッターシリーズの最新作のタイトルでしょうか。Orion Slave Girlsというのはエンタープライズのオリオンシンジゲートと何か関係があったりするのでしょうか?

crackKeyAndPrintを動かすためのprefixです。
// パラメータをスタックに積む
// (!36f1c(!18)!6f5c6e)
IIPIPIICCCIIICCCCICCICCIIIIIPIIPIPIIICCPIICIPICCCICCIIICCCICICCCCICCPIICIIC
// #1#0
IPPCPIPPPIIC
// サブルーチンコール(init)
// !352f(!23ca0e(!a0e)!4ec185)
IPCCCCICIICICICCPIIPIPICCCIIIIICICIICCCCIIICPIIPIPICCCIIIIICICPIIC
IPCICIIIICCIIIIICCICCCIICPIICIIC
// #0#1
IPPPIPPCP
// CIICICCIIICICIICIICCICCP crackKeyAndPrint番地
// ICCICIIIICCICIIIIIIIIIIP 同サイズ
// CCCCIIIIICCCIICIICIIICIP terminate番地
// ICICIIICCIIIIIIIIIIIIIIP 同サイズ
FCCFCFFCCCFCFCCFCCFFCFFIC
CFFCFCCCCFFCFCCCCCCCCCCIC
FFFFCCCCCFFFCCFCCFCCCFCIC
CFCFCCCFFCCCCCCCCCCCCCCIC
IIC

スタックにパラメータを積んでからinitをcallしています。initの戻り番地としてcrackKeyAndPrint、crackKeyAndPrintの戻り番地としてterminateを積んでいます。戻り番地をどんどん積んでおくことでサブルーチンの連続callを一回のcallで済ませることができます。

ICFP2007 (17) help-palindromesの修復

2007-09-02 23:30:34 | ICFPプログラミングコンテスト
ICFP2007 (16) トレースRNAのエンコードの続きです。

mainを逆アセンブルしてその存在が分かったpage 1230321です。元の画像は文字がどんどんフェードアウトして先が読めなくなっていました。調べてみると意外と簡単に文字が読めました。



元の画像です。


drawStringは文字の色を外部の関数にまかせます。事前にcharColorCallbackに関数へのポインタをセットしておきます。help-palindromesではfadingColorsという関数をセットしていました。これをcolorWhiteに換えただけです。
実際にcharColorCallbackに設定する命令はGZ+6571dfです。テンプレート中に設定する関数の番地があります。GZ+657210が関数の番地、657229が関数のサイズです。

palindromeというのは回文のことです。「竹やぶ焼けた」みたくさかさまから読んでも同じになる文です。最後のパラグラフが意味深です。誤り訂正をするための冗長部分として回文が利用できるみたいなことが書いてあります。壊れているcloudとduolcを連結すると回文になっているということみたいです。ちょうどサイズも同じです。
9/3 追記 cloudの記述を変更

サイズが同じというとcow-middle-spotとcow-middle-spot-ecc、sunとsunflowerも同じサイズです。

ICFP2007 (16) トレースRNAのエンコード

2007-09-01 23:04:33 | ICFPプログラミングコンテスト
ICFP2007 (15) Fuun Security Featuresの続きです

ICFP 2007関連が増えてきたのでカテゴリーを新設しました。

8/28に関数の最初でCから始まるユニークな無効RNAを出力していることに気づきました。関連する話は以前載せたpage 2181889 (help-undocumented-rna)にも書いてあります。



このトレース用RNAのエンコードが分かりました。RNAなので全部で7文字、先頭はCに固定です。残りの6文字はCを0、Iを1、Pを2とした3進数になっています。関数はアドレス順に通し番号がついています。以下のようになっています。
0ca6fa 00127e CCCCCCC(0) endo-left-arm
0cb990 000876 CICCCCC(1) cow-right-horn
0cc21e 00ae91 CPCCCCC(2) contest-2005
0d70c7 0021ce CCICCCC(3) smoke
0d92ad 000596 CIICCCC(4) colorDuckBrown
・・・
6f9e01 000ba3 CCPICCI(258) sky-day-bodies

途中で番号が飛んでいることがあります。原因は暗号化されていたり壊れているときか、データのときです。欠番は9個ありました。実際は番号はついていませんが前後の関係から決めてかっこの中に記しました。
0da3a7 000b34 (6)   cow-spot-middle-ecc    (データ?)
0e5d10 007e2d (10)  help-beautiful-numbers (暗号化)
20742f 0006e0 (25)  sunflower              (データ?)
213563 0080f4 (28)  vmu-code               (暗号化)
2bc2cb 00c0eb (80)  help-activating-genes  (暗号化?)
42ccf3 006ed8 (119) help-error-correcting-codes (暗号化)
4aa77d 00145c (144) cow-tail               (暗号化?)
5706a4 001365 (185) caravan                (暗号化?)
61181e 001962 (218) duolc                  (データ?)

gene tableのintegrity checkでfailと表示されるものでもちゃんと番号がついているものもあります。cloud(212), cow-spot-middle(5), help-virus(219), motherDuck(82), sun(24), surfaceTransform(240)です。これらは暗号化されていなくて、先頭は壊れていなくて、途中から壊れているものと思われます。

gene tableに載っていない関数も3つありました。gene tableをアドレスでソースすると隙間が空いていて、関数番号もついていて、最後にchecksumもついています。
453945 000338 CCCCPIC(135)  startup() ???
58ccac 00131f CPCCIPC(191)  drawInt() ???
58faf2 00018a CPICIPC(194)  colorYellow() ???

この表のサイズはchecksumの分も含まれているので実際の関数のサイズは24を引いた値になります。一番上のものは一番最初に実行される関数です。次はprintGeneTableでページ番号を書いています。最後もprintGeneTable内で使われています。charColorCallBackに設定しているので色関係です。impdocに載っていてgene tableには載っていないcolorYellowだと思われます。

ICFP2007 (15) Fuun Security Features

2007-08-30 23:43:13 | ICFPプログラミングコンテスト
ICFP2007 (14)の続きです。

以前も貼りましたがカタログのpage 4405829です。



help-fuun-securityという関数で表示されています。中身を見てみると何のひねりもなく暗号化された文字列を表示しているだけでした。コードから文字列を抽出しました。
Shhaf pbagnva n terng qrny bs cebcevrgnel grpuabybtl. Gb cebgrpg gur Vagryyrpghny Cebcregl 
evtugf bs ShhaGrpu naq vgf fgbpxubyqref, pregnva cnegf bs n Shha'f QAN ner rapelcgrq hfvat 
bar bs gjb pelcgbtencuvp grpuabybtvrf. Guvf ranoyrf fhpu cnegf gb or npprffrq bayl ol gubfr 
phfgbzref jub unir bognvarq gur arprffnel npprff pbqrf.

Zrgubq N jnf hfrq va gur cnfg, ohg vg unf erpragyl orra oebxra. Vaqrrq, gur rapelcgvba fpurzr 
jnf erirnyrq gb or fb synjrq gung vg pna or oebxra znahnyyl. Vg vf fgvyy va hfr sbe fbzr cnegf bs 
gur flfgrz, ohg ShhaGrpu vf zvtengvat njnl sebz vg. Jr jvyy abg qvfphff vg shegure.

Zrgubq O, n ShhaGrpu genqr frperg, vf fvtavsvpnagyl zber cbjreshy. Vg erdhverf gur phfgbzre 
gb rapbqr n fcrpvny frdhrapr bs punenpgref, pnyyrq n "xrl", vagb uvf Shha'f QAN ng n fcrpvsvp 
ybpngvba. Vs gur xrl vf cerfrag, gur pbeerfcbaqvat shapgvbanyvgl orpbzrf ninvynoyr. Bgurejvfr, 
Shha orunivbhe znl enatr sebz na reebe zrffntr gb ivbyrag qrngu.

Vs lbh unir ybfg n xrl, erpbirel bs gur xrl vf abg trarenyyl cbffvoyr qhr gb gur pbzcyrkvgl bs gur 
pelcgbtencuvp grpuabybtl. (Vg vf n tbbq vqrn gb jevgr qbja xrlf fbzrjurer, znlor rira ba n lryybj 
cvrpr bs cncre nggnpurq gb lbhe pbzchgvat qrivpr.) Ubjrire, vs lbhe xrl vf fhssvpvragyl fubeg, lbh 
znl or noyr gb erpbire vg hfvat gur Shha'f xrl penpxre. Gb or noyr gb hfr gur penpxre, lbh zhfg 
fgvyy unir lbhe chepunfr pbqr (n 24-npvq frdhrapr). Cyrnfr npgvingr gur penpxre trar, cnffvat vg 
gur chepunfr pbqr, naq nsgre fbzr crevbq bs gvzr vg znl cevag bhg lbhe xrl. Or cercnerq gb jnvg n 
ybat gvzr. Va gur Fybj Mbar, penpxvat n 2-punenpgre xrl znl gnxr zvahgrf, juvyr n 3-punenpgre 
xrl znl gnxr ubhef.

*** Qrgnvyrq qrfpevcgvba bs Zrgubq O (fhowrpg gb AQN) ***

1. Znxr n yvfg bs ahzoref, 0 gb 255.

2. Frg gur pbhagre sbb gb mreb.

3. Yrg one pbhag sebz 0 gb 255 naq rnpu gvzr, nqq gur one'gu ryrzrag bs gur yvfg naq gur one
'gu punenpgre bs gur xrl (jenccvat nebhaq gb gur fgneg bs gur xrl vs arprffnel) gb sbb, naq fjnc 
gur sbb'gu naq one'gu ryrzragf bs gur yvfg.

4. Erfrg sbb naq one gb mreb.

5. Sbe nyy gur qngn lbh jnag gb rapelcg be qrpelcg:

     N. Vapernfr sbb, nqq gb one gur sbb'gu ryrzrag bs gur yvfg, gura fjnc gur sbb'gu naq one'gu 
ryrzragf.

     O. Nqq gubfr gjb ryrzragf, fcyvg gur erfhyg vagb sbhe tebhcf, naq KBE gurz jvgu gur npvqf 
(V = 0, P = 1, S = 2, C = 3).

Nyy nevguzrgvp fubhyq or qbar jvgu 8 fvtavsvpnag npvqf.

暗号化はA→N、B→O・・・のように文字を置き換えているだけです。復号結果です。
Fuuns contain a great deal of proprietary technology. To protect the Intellectual Property 
rights of FuunTech and its stockholders, certain parts of a Fuun's DNA are encrypted using 
one of two cryptographic technologies. This enables such parts to be accessed only by those 
customers who have obtained the necessary access codes.

Method A was used in the past, but it has recently been broken. Indeed, the encryption scheme 
was revealed to be so flawed that it can be broken manually. It is still in use for some parts of 
the system, but FuunTech is migrating away from it. We will not discuss it further.

Method B, a FuunTech trade secret, is significantly more powerful. It requires the customer 
to encode a special sequence of characters, called a "key", into his Fuun's DNA at a specific 
location. If the key is present, the corresponding functionality becomes available. Otherwise, 
Fuun behaviour may range from an error message to violent death.

If you have lost a key, recovery of the key is not generally possible due to the complexity of the 
cryptographic technology. (It is a good idea to write down keys somewhere, maybe even on a yellow 
piece of paper attached to your computing device.) However, if your key is sufficiently short, you 
may be able to recover it using the Fuun's key cracker. To be able to use the cracker, you must 
still have your purchase code (a 24-acid sequence). Please activate the cracker gene, passing it 
the purchase code, and after some period of time it may print out your key. Be prepared to wait a 
long time. In the Slow Zone, cracking a 2-character key may take minutes, while a 3-character 
key may take hours.

*** Detailed description of Method B (subject to NDA) ***

1. Make a list of numbers, 0 to 255.

2. Set the counter foo to zero.

3. Let bar count from 0 to 255 and each time, add the bar'th element of the list and the bar
'th character of the key (wrapping around to the start of the key if necessary) to foo, and swap 
the foo'th and bar'th elements of the list.

4. Reset foo and bar to zero.

5. For all the data you want to encrypt or decrypt:

     A. Increase foo, add to bar the foo'th element of the list, then swap the foo'th and bar'th 
elements.

     B. Add those two elements, split the result into four groups, and XOR them with the acids 
(I = 0, C = 1, F = 2, P = 3).

All arithmetic should be done with 8 significant acids.

purchase codeをcrackerに渡すと鍵を表示してくれると書いてあります。impdoc2に載っているcrackKeyのことです。purchase codeは3つ分かっています(8/24)。
- int24 help-beautiful-numbers_purchase_code
- int24 help-error-correcting-codes_purchase_code
- int24 vmu-code_purchase_code

このドキュメントに載っている暗号アルゴリズムはRC4そのものです。SSLやWEPなんかで使われています(もしかすると過去形?)。

コンピュータの横に張っておくなんてことも書いてあります。stickyの画像のことなんでしょうね。


ICFP2007 (14)

2007-08-28 22:05:47 | ICFPプログラミングコンテスト
ICFP2007 (13) gene tableのpage 15の続きです。

helpシリーズで見忘れていた画像がありました。


その他、気づいたことです。

関数の最初はIIIから始まっています。これはRNA出力コマンドですが、Cから始まる無効なRNAを出力します。このRNAは関数にユニークなようです。また、関数からリターンするときも無効RNA、CFPICFPを出力します。つまりRNAを見れば、どの関数に入ったとか抜けたとかがトレースできるようになっているみたいです。まだ関数とトレース用RNAの対応表は作っていません。

関数の直後にはint24のchecksumが格納されています。これはcheckIntegrityを解析した結果わかったことです。
bool checkIntegrity(int size, int offset){
	int sum1;	//[BZ]
	int sum2;	//[BZ+18]

	sum2 = checksum(offset, size);
	offset += size;
	sum1 = [GZ+offset];
	rc = F;
	if(sum1 == sum2) rc = P;
	return;
}

ちなみにchecksumを求める計算はIを0、Cを1、Fを2、Pを3とみなして、sum = rol(sum, 2) ^ cという操作を関数の最初から最後まで行います。sumの初期値は1です。下のencode( )はICFPを0123に対応させています。rcは戻り値です。
int24 checksum(int24 offset, int24 size)
{
	int24 end = 0;
	int24 sum = 1;
	base t[3];

	if(size > 2500) size = 2500;
	end = offset + size;
	while(end != offset){
		sum = rol(sum, 2) ^ encode([GZ+offset]);
		offset++;
	}
	rc = sum;
}

9/1 追記 sizeの判定が逆でした。最大でも2500バイトしかチェックサムを求めないようです。
関数の最後は固定パターンになっています。
CPPPPPPIICIICIICIICIICIICIICIICIICIICCCFIICIICIFFCPICCFPICICFCPICIICIICIII
何か意味があるのでしょうか?

関数の戻り値のためのスタックは呼び出し側が確保します。通常のパラメータの前に置くので第0パラメータになります。
通常実行時のスタックのレイアウトは以下のようになっています。
[BZ]    ローカル変数
[BZ+??] リターンアドレス(int24が2つ)
[BZ+??] パラメータn
[BZ+??] パラメータn-1
...
[BZ+??] パラメータ1
[BZ+??] 戻り値

戻り値やローカル変数は関数によってはないこともあります。関数を呼び出すときは、パラメータをスタックに積んでから呼び出すので、ローカル変数の前にパラメータが置かれます。このとき、ローカル変数がスタック先頭ではなくなるのでコードが読みずらくなります。

本来の目的であるはずの画像の修復はどうでもよくなってきていて、中身を覗いて遊んでいます。