crkme08
http://doomo.xyz/crack/crkme08.exe
※前
実行
名前とパスワードを聞かれる。もちろんどちらもわからないので今回は2つの情報をリバースエンジニアリングで見つける必要がある。
解析
「GetDlgItemTextA」にBPを仕掛けて、適当に名前とパスワードを入力して実行する。
上図のアセンブリから分かることは
・正解パスワードは8文字であること
・入力したNameを引数として関数004012A8を呼び出していること
その返り値をEDXに格納していること
・パスワードを引数として関数004012D0を呼び出していること
・EAXとEDXが等しいなら正解処理(0040128F)へ進むこと
004012A8の解析
①EAXに入力した1文字のASCIIコードを足す
②EAXを2乗する
③①~②を入力文字分やる
だいたいこんな感じ。
EAXの値は入力するNameの値によってさまざま。
004012D0の解析
見た感じ、
・パスワードが16進数で使う文字(0~9とA~F)
その数値がそのままEBXに代入される
・それ以外の文字が入ってたらEBXを0xFFFFFFFFにする。
最終的にEBXはEAXに代入されて戻り値になる。
このEAXが関数004012A8で生成された値と同じになればよい。NameがAの時に生成される値は
0x183FB4ACなので、これが正解のパスワード。
crkme07
http://doomo.xyz/crack/crkme07.exe
実行
crkme07.exeを実行すると4つのテキストボックスが出てきて、シリアル番号的なものをを要求される。
解析
OllyDbgでcrkme07.exeを開く。アセンブリが表示されているウィンドウで右クリック->検索->ラベル名(現在のモジュール)をクリックする。
入力したシリアルが正しいかどうかを判定するには必ず入力テキストの情報を取得しなければならないので、そのあたりの処理に着目しよう。
GetWindowTextAが参照されているところでBPをセットして実行してみる。
1文字入力した瞬間にBPにぶつかる。多分半角数字以外のものが入力されていないかのチェックなのでいったんBPを外して適当なシリアルを全部入力する。
入力し終えたらもう一度GetWindowTextAのところにBPをセットして「登録」を押下。
.
.
.
ブレークポイントに引っかからない。
↑もう一度ラベル名を見てみる。テキストを取得する関数にはGetWindowTextのほかに、GetDlgItemIntが見つかった。
テキストを整数値に変換してそれを戻り値とする。今回の入力は数値だけなのでこれが怪しい。ここにBPを設置してもう一度実行してみる。
↑今度はちゃんと引っかかった。
↑まずこのへんの処理から。ループ構造になっている。
GetDlgItem関数で戻ってきた値(入力した整数値)をEBXが示すアドレス(0x40311C~)に格納する。そしてEBXを+2、ECXとEDXを+1してにECX > 4ならもう一度ループする。
ECXを+1するのはループカウンタ(Cのfor文の変数iみたいな)だから。
EDXはGetDlgItemInt関数を呼び出すときの引数として使われている。
004012BC . 52 PUSH EDX ; |ControlID
ControllIDは整数値に変換したいテキストの識別子なのでこれを+1することで次のテキストを取得できるという仕組み。
EBXを足すのはテキストを格納するアドレスをずらすため。
004012C5 . 66:8903 MOV WORD PTR DS:[EBX],AX
↑ここではワード長(32ビット)でデータを格納するので+2している。
無事ループを抜けるとEBXが指定したアドレスに整数1234(=0x04D2)が4つ分入ってる。
0x40311Dに格納されている数値をESIに代入。一つ目のテキストに入力した整数値だ。
その数値と0x66666667を乗算する。
IMUL関数はEAXの乗算の結果をEAXに格納するが、おさまりきらないときは上位ビットがEDXに格納される。
さらにEDXを2ビット分算術右ビットシフト(=÷4する)。
さらに0x1F(=31)ビットシフトしたものを加えた結果が
EDXが0x3E7ならOK。
0x66666667と掛け算して上位9~12ビットが0x03E7ならなんでもいいらしいので逆算してみる(計算過程は略)。1つめのテキストボックスには9990を指定すればいいことが分かった。
0x40311F(2番目のテキスト)に入力する値は0x270F(=9999)以下でなければならないことが分かる。(3番目のテキストも同様。)
このへんはよくわからない。読むのが嫌になるくらい長い。CMP命令も一番下に行くまでなくて、入力した数値から別の数値を作ってる感じ。
この問題を作った方も解説できないとのこと。
なので、こういうときはCMP命令周りを見てみるといいらしい。
004013B0 . 66:3BE8 CMP BP,AX
今、BPには4番目のテキストボックスの数値、AXには謎のメカニズムで生成された謎の数値が入っている(ここでは0x1732)。これが一致すればいいので4番目のテキストに0x1732(=5938)を入力する。
登録済と表示された。今回もいろいろ正解パターンがある。
crkme06
前→
前と比べて正解処理にたどり着くまでの道のりがけっこう長くなっている。
・正解文字列は10文字(004011FD . 83F8 0A CMP EAX,0A)
0040120C . 0FBF06 MOVSX EAX,WORD PTR DS:[ESI]
EAXにESIの先頭2文字(16バイト分)を格納する。ESIには入力データへのポインタが格納されているのでEAXには入力文字列の先頭2文字が格納される。
00401214 . 66:3D 454F CMP AX,4F45
このEAXの値は0x454F(= EO)でなければならない。
(EAXの値は0x4F45だけど、リトルエンディアンなので格納するときに順序が逆になる)
・3文字目以降が数字かを判定して、EDXに7文字目以降の数字の合計を格納。
その後EAXにEDXを転送してEDXを0にする。
0040124A . F7F1 DIV ECX
この命令、単にEAXをECXで除算するだけでなく、商をEAXに、余りをEDXに格納する。その後CMP EDX, 0 となってるのでEAXがちょうど10で割り切れるようにしなければならない。
EAXには8文字目~10文字目の数字の合計が格納されているのでこれらの和が10の倍数になればいいはず。
・最初がEO
・3~7文字目は数字
・8~10文字目は数字で合計が10の倍数
なら何でもいいみたい。
crkme05
前→
00401221 |. E8 28000000 CALL crkme05.0040124E
ここまでは前回と全く同じで10進数の入力を16進数にしてEAXに格納している。
それ以降の命令を見てみると、
①0x4d2(=1234)を足し算(ADD命令)
②0x0D(=13)で割り算(DIV命令)
③0x3DB(=987)を引き算(SUB命令)
④0x64(=100)を掛け算(MUL命令)
この結果EAXが0なら正解という処理になっている。
オペランドが1つしか指定されていない場合、演算結果はEAXに格納される模様。
(x + 1234)/13 - 987 = 0
x = 11597
crkme04 + 04a
http://doomo.xyz/crack/crkme04.exe
いつも通りGetWindowText関数が呼び出された直後の処理を見ていく。
004011FA . 68 2C304000 PUSH crkme04.0040302C
004011FF . E8 20000000 CALL crkme04.00401224
入力した文字列を引数としてアドレス401224に飛んでいる。
0040122C |> AC /LODS BYTE PTR DS:[ESI]
LODS命令はESIレジスタに格納されているアドレスを参照してEAXレジスタに格納する命令だ。
ここでは「BYTE PTR DS」と指定されているので1バイト(入力文字列の先頭1文字分)だけをEAXにロードしている。
ロードしたEAXの値に対して3つの比較を行い、ループを抜けるかの条件分岐をしている。
ループを抜ける条件は
①EAXが0であること
②EAXが30より小さいこと
③EAXが39より大きいこと
なので入力文字列は0x30~0x39(ASCIIコードで0~9)でなければならない。
EAXが0x30~0x39のとき、以下の処理が行われる。
EAXから0x30引いてEBXと交換する。EAXをx0A倍してそれをEBXと足し合わせる。
...
入力文字列が12345678なら
1 + 0*10
2 + 1*10
3 + 12*10
4 + 123*10
.
.
.
という風な感じになる。ただ入力文字列を10進数とみなして16進数に直してるだけだった。この関数を呼び出した直後に
00401204 . 3D F9602E2C CMP EAX,2C2E60F9
とあり、EAXの値は最終的に0x2C2E60F9でなければならない。
EAXを0x2C2E60F9にしたいならこれを10進数に直した741236985を入力として与えればよい。
crkme04a
00401222 |. E8 21000000 CALL crkme04a.00401248
関数0x401248はcrkme04でやったのと同じで入力した数字を16進数にしてEAXに格納する関数だった。
その後上位16ビットをEAXに、下位16ビットEBXに分けて格納して、EAX×EBXが0x99BA4A95なら正解処理に進む。ただしEAX < EBXでなければならない。
0x99BA4A95は10進数で2579122837。この約数をプログラムで求めると、
2579122837 = 42197 × 61121 であることが分かった。
42197 = 0xA4D5
61121 = 0xEEC1
0xA4D5EEC1 = 2765483713
crkme03
http://doomo.xyz/crack/crkme03.exe
01~02aまでにあったGetWindowTextAの代わりにGetDlgItemTextAで入力文字列を取得している。
その直後の処理を見ていく。今回はプログラミング言語風に書いてみる。
ESI = (入力文字列);
ECX = 0;
EDI = "vP12NGgoQa";
while(ECX < 0x0B){
AL = EDI[ECX];
EBX = ECX && 1;
AL += EBX;
if(strcmp(ESI[ECX], AL)){
//不正解処理へ
}
ECX++;
}
//正解処理へ
"vP12NGgoQa"の各文字列のアスキーコードに対して1,3,5,7,9番目の文字に+0(そのまま)2,4,6,8,10番目の文字に+1する処理のようだ。
vP12NGgoQa = 76 50 31 32 4e 47 67 6f 51 61
↓
76 51 31 33 4e 48 67 70 51 62
↓
vQ13NHgpQb
crkme02 + 02a
↑答え書いてあるけど自分なりにメモ。
実行
いつも通りOllyで起動。
①GetWindowTextAを呼び出しているところでブレークポイントを設置する。
②GetWindowTextAの引数(EAX)と8を比べて、一致しなければ不正解処理に飛ぶ。
GetWindowText function (Windows)
If the function succeeds, the return value is the length, in characters, of the copied string,
とあり、文字列の長さを返すので正解パターンは8文字。
③つぎに0040123Aの関数に入る。
ESIレジスタに入力文字列を代入し一文字ずつ比較している。
不正解なら返り値に0、正解なら1が返ってくる。
文字列は
35, 45, 48, 39, 56, 33, 51, 57
=5EH9V3QW
crkme02a.exe
02と同じように検索->ラベル名->GetWindowTextAにブレークポイントを仕掛けて実行すると、
ふざけたことを言われる。
ブレークポイントをセットするとそれを検知してプログラムが終了してしまう。
CALL GetWindowTextA以降の処理は02とあまり変わらないのでそれより上を調べれば仕掛けが分かるかもしれない。
4011EB LEA ESI, DWORD PTR DS:[401233]
アドレス401233はちょうどGetWindowTextA関数の呼び出しが始まるところだ。
GetWindowTextAにブレークポイントがセットされていると
401209 CALL crkme02a.00401233
が実行されない。そうするとGetWindowTextA以降の処理がされずに終了してしまう。
なぜそのようなことができるのかというと、
x86_64アーキテクチャではプログラムにint3(0xCC)という1バイトの命令を埋め込むことでブレークポイントを制御しているからだと思われる。
①LEA ESI, DWORD PTR DS:[401233]
・ESIにアドレス0x401233を代入
②MOV EBX, 36
MOV EBX, 96
・EBXに0x36+0x96(=0xCC)を代入
③CMP BYTE PTR DS:[EAX+ESI], BL ~ CMP EAX, 91
・ESIのアドレスを1ずつずらしながらEBXと比較する、つまりブレークポイントがセットされていないかをチェックしている。
つまり0x401233+0x91までの場所にBPを仕掛けていると終了してしまう。
逆に言えば0x401233より小さいアドレスにBPを仕掛けても大丈夫ということ。
401209 CALL crkme02a.00401233
のところにBPをつけて探ってみる。
GetWindowTextA呼び出しを抜けた直後
00401245 CALL crkme02a.00401280
をF7で見てみると
即値と比較しているわけではなく、0x4012EEを呼び出してDLを加工してそれを入力値と比較しているらしい。動的に実行してDLレジスタを確かめていくとパスワードが分かる。
1文字でも違うと不正解処理に飛んでしまうのでJNZ処理をつぶすか、Zフラグを1に書き換えて処理を進めていく。
DLに格納されている値は順に、
57 4D 46 54 50 56 4F 47
→WMFTPVOG