ニーモニック解析


さてそれでは今回も解析するプログラムを用意して早速取り掛かりましょう。
base64->rar
UmFyIRoHAM+QcwAADQAAAAAAAADM23Qggi8A/gIAAAAMAAACq8fd6wAAcjgdNQ8AIAAAAGNya21l
MDIuZXhlAAHACQgdUREMn83BHrN/CfZNd2TVjEjymaM3h9EdMJBBBYGqbrIjNJpGTix4bkzcNzJN
3chn4BIRQ1iKiwEgg8UEEEF4xJwYEhHjFgpHg8l/gEHjjJOCD+BOepl5mRjsW8zc1YwXi9drMy6r
Kuq9dZll+u/5r3q9fqu7PWXR75Xr9WUGXgZwvhgZI5uUDsNgeD8o0RLPBfUUBejTXK+00K8M2u9M
LPIu/qXxx6ROIihMRLnEmFz56CZOCaSipPJP2KNecwmchLNiQID61cBe3s2SPg+GPCZv/9Y6FljX
geo+F31pXy6AnenmHNX/9aJJjsq7I/Apm8xrlm/YFgRuiHr8HS5WCkFiEufTabHfvAZwy5NP06l8
3fU51B26/yptCRhnGqfvhAMcglvtfziJm4qbJr1KQ6p173AVKiIyy0yxL5uOP2/K/WeosSnFhDZo
pyN1ou+jtHpImA59SKxHzGwKvkF9zCBXVXO8SVFG2dwYLalRkNUeG07VzGtBFfIwX+TrZMd/cbse
eVMDZMG49zsRKOoLh1qOIdqNTalar1WDpa9YpaKx2Tp6Ke+GKBqC/KiieGI1YY3r0VWQpfiqwpZg
qPsJtxxBFvo+jywR2+NrCqHFM2laGOQipv2E6KUsVndspAWeTlP+WemqERdKjDsyRiIe9XcFZ+s8
WARV8gp6xkE87hMMsa+2PzsLc9x2dtif2guP2DnL5VJcNJ/gM64Rjy5qEzkIYsksexnjh4Qjz5dN
4HxC5NTjTZnKTNnXwy7X23uESDUki5ho5GzVEjUbe2iS5s1lUKV8U9OSigYOAFcQOeyEJak026Ln
ro2kwMNaebNqRlXyC6yvF1veHaSbU1ZkebIznPdtqr5BfrIB6uWbWlG4ftbCSl9tc8ErpglDNa7v
8hLXJWxqNW6eU6B8PpELFdmDZwwnAaMZbDJLwQxgiBegWGIB4SwSDGqcH/Q4VG3o0g5pWcVpFZhW
4wcm7Hyx8AIANbStrH7zXgcsWHTVfKQ3Yjkragn0Qq+QR9HEPXsAQAcA
それではサクサクと説明をしていこうと思います。
前回と同じようにして、まず入力した文字列を取得した位置から見ていきましょう。
ここの操作の説明を忘れてしまった人は前回の内容を読み直しながらやってみてください。
慣れてしまえばこれは無意識にやるレベルになっていると思うので、大丈夫かと思います。

さて、前回と同様の操作をできていれば401054というアドレスの行で止まっていると思います。
ああ、書き忘れていましたが、401054というのも10進数のように見えますが、16進数の数値です。
16進数の数値にはこれ以降'h'を末尾に付加していきます。
同様に2進数の数値には末尾に'b'を付加して書き表しますので、覚えてください。
覚えることが多くてイヤになってしまうかもしれませんが、これも慣れで、自然と無意識にできるようになります。
覚えきれないうちは適当なメモ帳にでも覚えるべき用語をメモしておくとより円滑に操作を行えるでしょう。

そういえばプログラムは数値であるだの何だのを実際にやってみると書いていましたが、やってはいても説明はしていませんでしたね。
ディスアセンブルの列の左の列にHexダンプという列があると思います。
これが数値であるプログラムを16進数で表示したものです。
この数値をニーモニックに置き換えて表示されたものがディスアセンブル列、ということなのですね。

さてと、以前の蟠りが消えたところで本題に移りましょう
401054hからF8キーで処理を進めていくのですが、いきなり以前とは違う処理をしていますね。
以前はこの文字列取得関数の直後に正解文字列かどうかの判定を行っていましたが、今回はそうでもないようです。

さて、それでは初のニーモニック解読といきましょう。
関数には一般的に”返り値”と呼ばれるものが存在します。
それは何かというと、処理が成功したか、もしくは処理を失敗したか。
またはその処理が行う結果自体を返すものもあります。
それについては解析以外でも用いられる資料、参考サイトなどで詳しく解説をしているところがありますので、新しい関数が出て来次第、検索などで調べるとより深く理解ができると思います。
けれど引数についてはあまり参考にしすぎない方が良いかもしれません。
順番が逆になっていたり、型というもの自体が存在していませんので。

それでは返り値の説明に戻りますが、この返り値というのはeaxレジスタと呼ばれるレジスタに代入されます。
前回ちょこっと書いたレジスタというものがでてきましたね。
レジスタにはeax、ebx、ecx、edx、esi、edi、esp、ebpなどが存在します。
ollyの右側にあるレジスタという列がそれですね。
一般的に値の操作に用いられるのはeax、ebx、ecx、edxの4つです。
このレジスタとは一体何なのか、というと、数学でよく使われるxやyなどのような変数と同じようなものだと思ってください。
それぞれにはollyで表示されているように、値を格納し、操作することができます。

つまり、関数というのは色々な処理をし、その結果をeaxに代入する、というものなのです。
それではGetDlgItemTextAは一体何の数値をeaxに代入しているのでしょう。
それは、入力された文字列の"サイズ"です。
みなさんも携帯、パソコンを手にしたことがある方は聞いたことがあると思うのですが、(というかこれ読んでる時点でパソコンは使ってるのですがね)半角文字、全角文字というものがあります。
半角数値や半角アルファベットは1Byte、全角文字は2Byteのサイズで表示されています。
ちなみに1Byteは16進数二桁の数値で構成されています。(今は余談)

GetDlgItemTextA関数が実行された直後のeaxには入力された文字列のサイズが格納されています。
つまり、この直後のニーモニックで処理されている内容は文字列のサイズの判定処理になりますね。
それではこの部分のニーモニックを解読していきましょう。
CMP命令は比較の命令です。
ここで覚えて欲しい用語がまた一つ…。
ニーモニックは幾つかの値を設定し、それに色々な処理を行うものですが(例外もあります)、この時の設定される値をオペランドといいます。
このアドレス(401059h)のcmp命令を例にとると、
CMP EAX ,6
[命令名] [第一オペランド] [第二オペランド]
という感じになっています。
まぁこれは呼び方云々というより、意味として理解できてくれれば良いので。
cmp命令の解説に戻りますが、これは比較の命令です。
もちろん、第一オペランドと第二オペランドを比較するのですが、この命令自体はプログラムの動作としては何もしません。
所謂内部処理ってやつですね。
実際はフラグ立てなどをやってくれているのですが、面倒くさい概念を覚えるくらいなら、ここは”比較”とだけ知っていてくれればOKです。

次のアドレスの処理はJNZ SHORT CRKME02.0040107Dとなっていますね。
このSHORTというものは、読む上では必要ありませんので、JNZ CRKME02.0040107Dと脳内変換してください。
Jnz命令はcmp命令で比較した結果を元にアドレスの移動を行うものです。
アドレスとは以前書いたように、処理している位置を示すものなので、処理する位置を移動する、ということです。
そうそう、'.'の前は実行ファイル名の略称になっていますので、これも気にしないでOKです。
つまりこの場合JNZ 0040107Dとだけ考えれば良いわけですね。
ここまで短くなればアドレスの移動だけでも意味がわかってもらえると思いますが、0040107Dhに移動します。
どんなときに?というところを処理しているのがこのJnz命令で、Jnz命令はCmp命令の結果が間違っていたときにアドレス移動を行い、あっていた場合はアドレス移動を行わない、という命令です。
つまりここでは入力した文字列が6ByteであればJnz命令に続く40105Ehからの処理を行い、6Byte以外であれば移動先の0040107Dhからの処理を行う、ということになります。

このアドレス移動をジャンプと呼ぶので、これも覚えて置いてください。
JnzのJはJumpの略なので、簡単に覚えてもらえると思います。
同様にJe命令、Jle命令、Jg命令などJ*もしくはJ**という命令は多分全て、アドレスジャンプの命令です。
それでは6Byte以外だった場合の処理を見てみましょう。

40107Dhからの処理を見ると、4つの引数を渡してMessageBoxAという関数を呼び出していますね。
これはみなさんがよく目にすると思いますが、ダイアログを表示する関数です。
このダイアログにはたくさん種類がありますが、MessageBoxA関数が呼び出すダイアログはきわめて単純なものばかりです。
MessageBoxAに渡される引数は
MessageBoxのStyle(形式)、タイトル、表示する文字、親ウィンドウハンドル となっています。
解析を行う際にコメント部に表示されている引数は各参考書、サイトに載っているAPI定義の引数とは順番が逆になっていますのでご注意ください。
コメント部に表示されたタイトルと表示する文字からして、これは不正解メッセージであることがわかりますね。
形式についてはまだまだ不要なことなので省略させていただきます。
入力した文字列が6Byteでない場合、不正解メッセージが表示されるわけですから、正解の文字列は6Byteということになります。

正解文字列のサイズがわかったところで、サイズだけで判断している、なんてことは滅多に有り得ません。
それでは次なる処理を求めて先ほどの40105Ehに戻ってください。
これが6Byteだったときの処理になりますね。
Call命令がでてきていますね。
これはアドレスを参照する、という命令なのですが、簡単に言ってしまえば指定したアドレスからRetn命令が実行されるまでの処理を実行するものです。
詳しく言うと少し横道がそれすぎてしまいそうなので、今はそう解釈してください。
まずはここのアドレスを無視して次に進んでみることにします。

その次のアドレスではTest命令という命令がでてきていますね…。
ここまで連続して命令だの何だのが連続してでてくると、最初は困惑するものかと思いますが、徐々に慣れますから。(笑
Test命令というのは第一オペランドと第二オペランドの論理積うんたらかんたらといういかにも面倒くさそうな命令なのですが、大概Test命令が用いられるパターンは第一オペランドと第二オペランドが同じです。
なので、オペランドのレジスタが0であるか否かを判断する命令、と思ってくれて不便はないと思います。
それの方が説明にも理解にも易しいかと思いますので。(笑
つまり、TEST EAX,EAXをCmp命令を用いて書き直すと、CMP EAX,0と同等、というわけです。
ではなぜわざわざTest命令とCmp命令を使い分けるのか、といわれると結構面倒な話になりますが、簡単に言えばTest命令の方が若干早いからです。
多分2倍くらいの速度です、はい。
でもそんなのパソコンのスペックにもよりますが、10の-20乗秒くらいだと思うので、気にするほどではないのですけれど、やはりプログラマは好んでこの形式を使います。
(コンパイラなどでもおそらくこのようにコンパイルされるのではないでしょうか)

さて解析の方に戻りますが、Cmp命令と同等の処理を行っていることがわかればあとは次のアドレスですね。
Je命令は、先ほどでてきたJnz命令の真逆の動作をします。
つまり、比較して、一致していたらアドレスジャンプということになりますので、一つ前のアドレスのTest命令の解釈を加えると、eaxが0であればジャンプ、ということになりますね。
そしてジャンプ先は不正解メッセージの表示処理なわけですから、eaxはこの時点で0以外でなければならないわけです。
それの処理を行っているのはどこか、というと先ほど飛ばしたCall命令のところですね。

それではCall先である4010A8hへ移動してください。
Xor命令という初登場の処理がでてきましたね。
これもTest命令に似たような感じで排他的論理和という処理を行う命令なのですが、第一オペランドと第二オペランドが一致する場合は、オペランドの値を0にする、という処理になります。
論理処理については色々と理解しにくいところがありますので、後々詳しい解説をしますので、その時に「だからそう言う処理になったんだ」と知識の補完をしてくれれば大丈夫です。

さて、勘の良い方はここでパスワードを求めなくても正解メッセージを表示する方法に気づいたはずです。
Call命令の直前にeaxは入力した文字列のサイズ、これ以降もRetn命令までXor命令がないのだから、このXor命令さえ消えてしまえば、6Byteの文字列でさえあれば何でも正解になるんじゃないか、と。
つまり、処理の方を書き換えてしまう、という手法ですね。
これは一般的にクラッキングやパッチと呼ばれる手法になります。
では実際にその手法を試して見ましょう。

4010A8hを選択した状態で右クリック→逆アセ修正でそのアドレスの処理を編集することができます。
さて、ではこれを一体どうすれば良いのか?
ここの編集の方法にはいくつものやり方があり、一般的には書き換えるByte数が少ない方が良い、といわれています。
しかし慣れていないうちは大概頼るのが今から示す1番になると思います。
  1. 何もしないようにする
  2. 意味のない処理をするようにする
  3. 強制的にジャンプさせる
3番についてはこのタイミングで使う意味は全くありません。
それならば、また別の手法を取る方が全然楽だからです。
順に方法を示すのも良いですが、まず3番の別の手法、という方から教えましょう。
純粋に401065hの処理を逆にしてしまえば良いのです。
つまりJe命令を反対の意味を持つJnz命令に。
それだけで6Byteの文字であれば何でも正解、ということになります。

それでは味気ないので、1番の解説をしましょう。
先ほど編集したアドレスは右クリック→変更を取り消しをしておいてください。
4010A8hをNop命令に書き換えてしまいましょう。
先ほど出てきた画面で、元々入っていた文字列を消去し、Nopとだけ入力してアセンブルを押せばOKです。
すると元々のアドレスが2行に増え、赤文字でNOP、という表示に変わったと思います。
このNop命令とは、選択肢にもあったように”何もしない命令”です。
まぁこれにも存在する理由があるのですが、気にせずにそう覚えていてください。

次に2番の選択肢をやりますが、その前にNop命令に書き換えた部分を元に戻しておきましょう。
行が増えた場合や減った場合は1回では元に戻らないので、右クリック→変更を取り消し、そして適当なアドレスを左クリックしてからもう一度直すアドレスを左クリックで選択→右クリック→変更を取り消しをしてください。
アドレスの修正などをするとHexダンプの括弧の一部が削れてしまいますが、まぁ我慢してください。(笑
色々手法がある中で一番奇特なパターンが2番になると思います。
それゆえにこれを使う場合は処理に不具合を生じさせないように注意が必要です。
先に言っておきますが、この手法を取る上での責任は自己責任、また、こちらで解説した手順と少しでも手順が異なればプログラムが動作しないなどということは日常茶飯事です。
それのせいか、クラッキング手法の中でも最も素人が苦労し、最も玄人が好むものです。

では書き換える命令を何にするか、それを考えていきましょう。
先に宣言しておきますが、これの書き換えの手法はほぼ無限に存在します。
その内のいくつかを紹介しているだけですので、”これが絶対”などとは決して思わないでください。
このCall命令で呼び出されたアドレスからCall元に返った直後にはTest命令があったのは覚えていますか?
この時点では比較をしようと何をしようと、その後に比較をしてくれるのですから、比較結果は変わりません。
さて、それではXor命令をTest命令に書き換えてみましょう。
無事成功したようで、正解メッセージが表示されたはずです。
このように、Test命令がすぐ後で行われるにも関わらず同じ処理をする、といった無意味な処理に書き換える手法がコレです。

さて、もう一つ紹介をしますが、これも一応分類で言えば2番に相当します。
こういうことをするのは大概玄人か変人なので、参考にするというより、じっくり経験を通してやり方を学ぶと良いと思います。
とはいえアセンブリプログラミングをするようになればすぐに身に付けざるを得ないわけですがね。
今は理解をする必要はないので、書き換え方を羅列するだけにとどめます。
XOR EAX,EBX
OR EAX,EAX
などなど。
Or命令への書き換えはJe→Jnzと似たようなもので、真逆の処理ですね。

さて、プログラムの内容を書き換えるなどという美しくない解決法はやめましょう。
今まで書き換えたアドレスを全て戻してください。
それでは、正当に(?)正解の文字列を探していきます。
確か4010AAhからの解説でしたね。

Mov命令は転送命令、と呼ばれるものです。
でもわかりにくいので、代入命令と考えて何も問題はありません。
つまりMOV EBX,403024というのはEBX=403024ということですね。
それではこの数値は一体何だ、と言われるとアドレスについて詳しく知ってもらう必要がでてきます。
以前レジスタは変数のようなものだ、と言いましたが、どう考えても8個や9個の変数だけで全ての処理を行うのは不可能です。
それにレジスタはプログラムが動作している最中に色々と変化していくものであり、プログラムの起動時は0です。
それでは最初から設定しておきたい数値、文字列を扱うことができません。
そこで登場するのが”アドレスで管理する数値や文字列”です。

…と、うだうだと書くよりも見てもらったほうがずっと早いと思うので、このアドレスとは何なのか、見てみましょう。
右クリック→移動→アドレス で出てきたダイアログに先ほどの403024hを入力してください。
ああ、ollyでは16進数を基本としていますから、'h'は省略して入力してください。
すると、意味のわからないADD BYTE PTR DS:[EAX],ALがたくさん羅列された場所にきましたね。
ジャンプしてきたアドレスはディスアセンブル列がADD BYTE PTR DS:[EAX],ALではないと思います。
これがアドレスに値が格納されている、という状態です。
ディスアセンブルがADD BYTE PTR DS:[EAX],ALになっている行のHexダンプを見てください。
0000hとなっていますね。
これが値を格納されていない状態、ということです。
つまりこの領域においてはディスアセンブル列は全く意味を持たないものになっています。
意味があるのはHexダンプ列に表示されている数値だけ、です。

今現在選択されているアドレスは00403023hもしくは403024hになっていると思いますが、このアドレスは常に変動するものですので、どちらかになっていればOKです。
それでは実行させているcrkme02の方の画面に切り替え、適当な文字を入力して不正解メッセージを表示してください。
この状態でollyに戻り、どこでも良いので適当なアドレスを左クリックしてください。
すると先ほどのアドレスが赤文字になって表示されたと思います。
この状態が丁度、アドレスに入力された文字列を格納した状態です。
GetDlgItemTextA関数はこんなところに入力文字列を格納していたのですね。

でもHexダンプを見てても文字列なんてどこにもないじゃないか、という当然の疑問がでてくると思います。
これもまた難解な問題になってしまうかもしれませんが、パソコンは文字列という概念を持ちません。
変わりに文字に対応する数値として扱っているのです。
この文字に対応した数値のことを文字コードもしくはASCIIコードといいます。
正確にはASCIIコードなんでしょうけれど、面倒なんで文字コードと呼ぶことが多いと思いますが、何卒ご理解を。

これが何に対応している、とか何とかはollyの便利さが全て解決してくれます。
いつも見ている画面のすぐ下には白いスペースがあると思いますが、さらにその下にアドレス、Hexダンプ、ASCIIという3つの列があると思います。
これの403020hの列を見てください。
左から1Byteずつ区切ってありますが、その5Byte目をクリックしてみてください。
灰色で選択されたと思いますが、そのままASCIIの列を見てください。
同じく灰色で選択された文字が、その数値に対応した文字であり、入力した文字の1文字目になっているはずです。

ここで覚えておいて欲しいのは文字列の終点についてです。
この概念は文字列を制限しないプロテクトの場合によく使われるので、重要です。
人間は一文の終点を認識するとき、日本語では'。'(まる)、英語では'.'(ピリオド)を用いますね。
これと同様に、機械も”機械独自の終点”を持っているのです。
例をあげた方が早く理解していただけると思うので、先ほどの入力文字列を示します。
筆者は"012345"と入力しましたが、実際に機械が認識した文字列の値は、
30 31 32 33 34 35 00 012345.
なのです。
つまり、機械は00hを”終点”として認識します。

さて、それでは先ほどと同様の方法で4010AAhに戻りましょう。
アドレスに格納された値、というものを理解して欲しくてこのような説明を長々としてきましたが、実は今回においては4010AAhのコメント列に入力した文字列は表示されているんです。(笑
まぁ、気にせずに進みますが、次の行からは比較とジャンプの嵐になっていますね。
全てジャンプ先は同じなので、先に確認しておきましょう。
ジャンプ先は4010D3hのRetn命令ですので、どれか一つでも違っていればCall元に戻される、というわけですね。
では全てあっていた場合はどうなるのか、というと、004010D2hのINC EAX…これだけのようですね。
Inc命令は第一オペランドの数値に1を加算する、という命令です。
つまりこの時点ではeaxはXor命令で0にされているので、Retn命令が実行される時点で、どれか一つでも違っていればeaxは0、全て合っていればeaxは1になっているわけです。

それなら話は早い、全て一致していれば良いのです。
4010AAhでebxには入力した文字列の先頭アドレスが格納されているので、入力する文字列の1Byte目が63hから良いわけです。
これの文字コードと文字の変換については日本でバイナリエディタと呼ばれているツールを使うのが一番です。
Stirlingというツールが一番有名なので、これを使ってみましょう。
前回同様、ダウンロードについての解説はしませんので、検索などをしてダウンロードしておいてください。
筆者が使っているバージョンは1.31ですが、多少違うくらいなら操作性は変わらないでしょう。

ツールバーの白い紙のようなボタンを押してみてください。
すると先ほどのollyの下半分の画面のようなウィンドウがでてきましたね。
まぁ、見た目もそのままなので、直感でわかると思いますが、この状態で63hと入力してください。
'c'と出てきましたね。
これで1Byte目が'c'であることが判明しました。

ここでいくつかあるCmp命令の第一オペランドに注目します。
Cmp命令の全体を見ると、
BYTE PTR DS:[EBX+]←これは一致していますね。
では、EBX+いくつか、というのは何を示しているのか、それを説明しましょう。
1Byte目は関係は大して気になりませんでしたが、"BYTE PTR DS:"というものの意味が気になってきます。
これは括弧内の示すアドレスに格納された先頭アドレスから1Byteのみを対象にしますよ、というものです。
他にはWORD(2Byte)、DWORD(4Byte)などが存在します。
まぁこれは第二オペランドから判断できる場合が多いので、大して気にする必要はないです。
対象となるアドレスの先頭アドレスを保管しているのがEBXなので、+いくつかをされれば、その分だけ先頭アドレスから対象となるアドレスがズレるわけです。

ここで勘違いしないで欲しいのですが、BYTE PTR DS:[EBX+1]とあっても、実際にebxに1が加算されるわけではありません。
なので、4010B4hからのCmp命令は対象アドレスを1ずつずらしながら比較している、ということになります。
つまり、1文字ずつ正解文字コードと比較しているわけですね。

それがわかれば安心、あとはハイスピードで比較している文字コードをStirlingに打ち込んでいきます。
63 72 30 32 6D 65 cr02me
上記のようになれば完成です。
つまり正解の文字列は"cr02me"ということですね。

随分と長くなってしまいましたが、今回はアドレス格納の概念というすごく重要なところを扱っていたので、致し方ないことかと…。
しかしこれでもう、数をこなせば何でもできるようになったかと思います。
とはいえ解説していない関数やニーモニックもたくさんあるので、先は長いですね。


Copyright © 2008 Rapidsy. All Rights Reserved.