crackmes.oneというサイトではユーザーがアップロードしたリバーシングの問題が解ける。


X0rb0y's X0rb0y

https://crackmes.one/crackme/639d12b333c5d43ab4ecefe6


qualityが4.7と高いからやってみる。


x64dbgで解析しようとするとなんか落ちる?

IDAでちゃんとコードを読んでみる。

間違えるとWrong Length!と表示される。

この文字列で検索する。

readで入力を受け取る。

strlenで文字数が20h(32)か確認。

最初の11文字が"securinets{"か確認。

次の9文字が


Securinets{_Q3eM3tT1t6}}56789012





ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー

5か月たった今、ずっと放置していたこの問題を解こうとしてみた。

x64dbgで開いてみると普通に開いた。

5か月前はなぜか開かなかったらしいが、x64dbgが新しくなったから開けるようになったのか?

x32dbgで開かずにx64dbgの方にD&Dして起動しようとしてたとか?

とりあえずcmdで起動すると文字列を要求されている感じになる。

適当な単語を入れると"Wrong Length!"と怒られる。

もう一度起動して、F9押しまくって入力のところまで進め、一時停止ボタンを押した。

そしてなんとなくappleと入力してユーザ・コード(Alt+F9)まで実行した。

するとcall JMP readを抜けたところに出た。

あとはF8を押しまくっていると4017C9でmov dword ptr ss:[esp],keygen.4050ACで"Wrong Length!"をスタックに入れてputsを呼び出しているところにたどり着いた。

その周りを見るとCongrats!と表示する部分も見えた。

この部分にうまくたどり着けるようにすればいいらしい。

401672から下に読んでみる。



mov dword ptr ss:[esp+74],eax
read関数から得た終端文字を含めた文字数を[esp+74]に代入
mov eax,dword ptr ss:[esp+74]
sub eax,1
終端文字を含めない文字数をeaxに代入

mov byte ptr ss:[esp+eax+40],0
これは何をしているんだろう。
esp=61FEA0, eax=5より
esp+eax+40=61FEE5
[61FEE5]は下の画像を見てみると0から0のまま変わっていない?
代わりに[61FEE7]がAから0になってる??
これ、本当に理解できなかったんですけど、x64dbgのダンプ画面と右下のスタック画面を見比べると理解できました。
スタック画面では値がリトルエンディアンになっているんですね。
だから[61FEE7]だと思っていたところは[61FEE5]なのでちゃんと[61FEE5]に0が代入されてますね。
あとメモリのアドレスの数え方をいつも忘れるんですよね。
左に書かれているアドレスは右にある値の一番最初のアドレスを指しているのか、それとも一つ前のアドレスを指しているのか。
今回しっかり考えたおかげで左に表記されているアドレスは右の値の最初のアドレスだということを理解できました。
とりあえず次の行の
401682 lea eax,dword ptr ss:[esp+40]
を実行すると"apple"がeaxに代入されました。
ということは[esp+40]に”apple”という文字列が保存されているアドレスが入ってるっぽいですね。
read関数により代入されていたのでしょうか。
read関数が呼び出される前を見てみると
[esp+4]に[esp+40]をコピーしてます。
esp+40を引数として文字列を受け取っていそうな感じですね。
長くなりましたが
40167D mov byte ptr ss:[esp+eax+40],0
は"apple"の文字列の後ろに0を入れる、すなわち終端文字として0を入れただけでした。

lea eax,dword ptr ss:[esp+40]
mov dword ptr ss:[esp],eax
call <JMP.&strlen>
"apple"という文字列が書かれているアドレスを[esp]に入れて引数としてstrlen関数を呼んだ。






cmp eax,20
jne keygen.4017C9
入力された文字列の文字数が0x20=32でなければ”Wrong Length!”と表示する。
jneはZFが0のときjmpするやつですね。
jump not equalだと思ったら、jump if not equalとifが入るんですね。

mov dword ptr ss:[esp+8],B
lea eax,dword ptr ss:[esp+40]
mov dword ptr ss:[esp+4],eax
lea eax,dword ptr ss:[esp+30]
mov dword ptr ss:[esp],eax
call <JMP.&strncpy>

ひたすらスタックにコピーしてるのはstrncpyの引数っぽいですね。
0xB=11文字だけ文字をコピーするっぽいです。

mov dword ptr ss:[esp+4],keygen.405064
lea eax,dword ptr ss:[esp+30]
mov dword ptr ss:[esp],eax
call <JMP.&strcmp>
test eax,eax
jne keygen.4017BB


keygen.405064には"Securinets{"という文字列へのアドレスが入っていました。
先ほどのstrncpyで[esp+30]に入力された文字列の11文字がコピーされて、それと比べて一致しなかったら"Wrong Key!"に飛ばされます。

004016CF
lea eax,dword ptr ss:[esp+40]
add eax,B
mov dword ptr ss:[esp+8],9
mov dword ptr ss:[esp+4],eax
lea eax,dword ptr ss:[esp+20]
mov dword ptr ss:[esp],eax
call <JMP.&strncpy>

[esp+4]=入力した文字列の11文字目以降
[esp]=[esp+20]
[esp+8]=9
入力した文字列から9文字[esp+20]にコピー。


004016EE | mov dword ptr ss:[esp+7C],1             
004016F6 | mov dword ptr ss:[esp+78],0             
004016FE | jmp keygen.401725                       
00401700 | lea edx,dword ptr ss:[esp+20]           
00401704 | mov eax,dword ptr ss:[esp+78]           
00401708 | add eax,edx                             
0040170A | movzx eax,byte ptr ds:[eax]             
0040170D | movsx eax,al                            
00401710 | sub eax,30                             
00401713 | cmp eax,9                               
00401716 | jbe keygen.401720                       
00401718 | mov dword ptr ss:[esp+7C],0             
00401720 | add dword ptr ss:[esp+78],1             
00401725 | lea eax,dword ptr ss:[esp+20]           
00401729 | mov dword ptr ss:[esp],eax              
0040172C | call <JMP.&strlen>                      
00401731 | mov edx,eax                             
00401733 | mov eax,dword ptr ss:[esp+78]           
00401737 | cmp edx,eax                             
00401739 | ja keygen.401700                        
0040173B | cmp dword ptr ss:[esp+7C],0             
00401740 | je keygen.4017BB                        | go "Wrong Key!"

[esp+7C]=1
[esp+78]=0
上から読んでjmpしてstrlenまでで入力した文字列から11文字目以降が何文字か確認している。
その後おそらくstrlenの戻り値が入ったedxと[esp+78]を比較している。
[esp+78]は1が代入されていた。
jaでedxが1より大きければjmpするらしい。
(ja: jump if above. cmp edx,eaxのあとのjaはif edx>eax, jmp)
401700にjmp後、
edx=[esp+20]のアドレス
eax=[esp+78]
eax+=edx
movzはコピーして足りないビットをゼロで埋めるやつ。
movsxはコピーして足りないビットを同じ符号で埋める。
eaxにデータセグメントの[esp+78]をコピー。
eaxに下位8ビットのalを符号を維持してコピー。
eaxから0x30=48を引いてeaxが9以下だったら401720にjmp.
(jbe: jump if below or equal)
そうでなければ[esp+7C]=0
[esp+78]に1を足して[esp+20]の文字数を求める。
その文字数が[esp+78]より大きければこれを繰り返す。
そうでなければ[esp+7C]が0かどうか比較して、0ならWrong Keyへ。
[esp+7C]は0じゃダメで、eaxが9以下ではないと[esp+7C]に0が代入されます。
そのためeaxが9以下になるようにしないといけないみたいです。
どんどんさかのぼってみると、eaxから0x30が引かれていて、0x30は"0"のことだから文字列の数字を数値に変換しています。
add edx, eaxで入力した文字列の11文字以降から9文字分が入ったアドレスに対して[esp+78]を足していて、あとを見ると[esp+78]はadd 1されて1ずつ増えています。
その後[esp+78]が文字数を超えるとja keygen.401700のループが止まる。
[esp+7C]が0になるのは入力した文字列の11文字以降の9文字が0~9の文字以外、すなわち数字以外のとき。
長くなりましたが入力した文字列の11文字以降の9文字が数字がどうかただ判定してるだけでしたね。
理解するのにすごい時間がかかってしまった。


00401742 | lea eax,dword ptr ss:[esp+20]           
00401746 | mov dword ptr ss:[esp],eax              
00401749 | call keygen.40158D               
0040174E | mov dword ptr ss:[esp+70],eax           
00401752 | cmp dword ptr ss:[esp+70],F9B9B765      
0040175A | jne keygen.4017BB               

9文字を関数 keygen.40158Dに渡して、その戻り値がF9B9B765でなかったら”Wrong Key!”に飛ばすみたいです。
call 40158Dの中身をみてみます。

今気づいたんですけど、x64dbgの逆アセンブリをメモ帳にコピペするときれいに表示されるんですね。
bloggerだと右側のレジスタの中身を表示してくれる部分の|がずれてちゃうんですよね。
なんでなんだろう。
スペースの設定がおかしいとか?

0040158D | push ebp                               
0040158E | mov ebp,esp                             
00401590 | sub esp,10                              
00401593 | mov dword ptr ss:[ebp-C],1000193        
0040159A | mov dword ptr ss:[ebp-10],811C9DC5      
004015A1 | mov eax,dword ptr ss:[ebp-10]           
004015A4 | mov dword ptr ss:[ebp-4],eax            
004015A7 | mov eax,dword ptr ss:[ebp+8]            
004015AA | mov dword ptr ss:[ebp-8],eax            
004015AD | jmp keygen.4015C9            

関数といえばpush ebp, mov ebp,espですよね。
リバースエンジニアリングバイブルの本も最初は関数のアセンブリの説明でした。
でもこの部分が本当に理解できなくて本に書き込みながら頑張って理解しようとした記憶があります。
おかげで今では何となくはわかるようになりました。
まあ関数の呼び出し規約のページは読み飛ばしちゃったんですけどね。

スタックをまとめると、
[ebp-10]=811C9DC5
[ebp-C]=1000193
[ebp-8]=[ebp+8]=入力した文字列9文字へのアドレス
[ebp-4]=[ebp-10]=811C9DC5
[ebp]=もとのebp
[ebp+4]=リターンアドレス
[ebp+8]=入力した文字列9文字へのアドレス

次に4015C9にジャンプします。

004015AF | mov eax,dword ptr ss:[ebp-8]            
004015B2 | movzx eax,byte ptr ds:[eax]             
004015B5 | movsx eax,al                            
004015B8 | xor dword ptr ss:[ebp-4],eax         
004015BB | mov eax,dword ptr ss:[ebp-4]            
004015BE | imul eax,dword ptr ss:[ebp-C]           
004015C2 | mov dword ptr ss:[ebp-4],eax            
004015C5 | add dword ptr ss:[ebp-8],1              
004015C9 | mov eax,dword ptr ss:[ebp-8]            
004015CC | movzx eax,byte ptr ds:[eax]             
004015CF | test al,al                              
004015D1 | jne keygen.4015AF   

9文字から一文字だけ文字列のまま取り出して811C9DC5とxorする。
その値と1000193を掛ける。
次の文字を取り出して終端文字の0でなければ繰り返す。
なんか計算めんどくさそう。
                
004015D3 | mov eax,dword ptr ss:[ebp-4]    
004015D6 | leave                         
004015D7 | ret          

xorと掛け算をした値を戻り値として渡してもどる。
この値がF9B9B765になればいいということですね。
よくわからないので実際の計算を見てみます。
9文字は”123456789”としてみます。
xor 811C9DC5,31("1")
=811C 9DF4
imul 811C9DF4,1000193
=81 1D69 340C A71C
eaxは32ビットだから4バイト分の 340C A71C

xor 340CA71C,32("2")
=340CA72E
imul 340CA72E,1000193
=34 0CF9 1DEB 2D6A
=1DEB 2D6A

あと繰り返し。

(811C9DC5 xor "n")*1000193を繰り返してF9B9B765になる。。
逆の計算にして割り算してxorすればいいと思ったけれど32ビットで切り捨てるから割り算ができない。

どうやってやるんや。
考えても逆算はできなさそう。
むりやり当てはめるのか?
やり方がわからなかったのでここであきらめた。
てかまだ何もしてないじゃん・・・

コメントを見るとz3というpythonのライブラリを使うらしい。
これで総当たりするらしい。
solutionを軽く見るとこれはFNVハッシュ関数らしい。
しかもこの9文字の後も暗号化がまだ残っていたらしい。
いやもうこれむりやん。
もっと勉強してまたいつか思い出したら解きなおそう。
ちなみに5か月前はqualityが4.7だったのに今は5.0に上がっていた。
みんなこれ解けるんだなー。
difficulty 3.6でこの難しさかー。
もっと簡単なやつ今度やってみよ。


0040175C | lea eax,dword ptr ss:[esp+40]           
00401760 | add eax,14                              
00401763 | mov dword ptr ss:[esp+8],C              
0040176B | mov dword ptr ss:[esp+4],eax            
0040176F | lea eax,dword ptr ss:[esp+10]           
00401773 | mov dword ptr ss:[esp],eax              
00401776 | call <JMP.&strncpy>                     
0040177B | mov dword ptr ss:[esp+4],6              
00401783 | lea eax,dword ptr ss:[esp+10]           
00401787 | mov dword ptr ss:[esp],eax              
0040178A | call keygen.401460                      
0040178F | mov dword ptr ss:[esp+4],keygen.405070  | 405070:"_Q3eM3tT1t6}}"
00401797 | mov dword ptr ss:[esp],eax              
0040179A | call <JMP.&strcmp>                      
0040179F | test eax,eax                            
004017A1 | jne keygen.4017BB                       
004017A3 | mov dword ptr ss:[esp],keygen.405080    | 405080:"Congrats! You cracked this shit!"
004017AA | call <JMP.&puts>                        
004017AF | mov dword ptr ss:[esp],0                
004017B6 | call <JMP.&exit>                        
004017BB | mov dword ptr ss:[esp],keygen.4050A1    | 4050A1:"Wrong Key!"
004017C2 | call <JMP.&puts>                        
004017C7 | jmp keygen.4017D5                       
004017C9 | mov dword ptr ss:[esp],keygen.4050AC    | 4050AC:"Wrong Length!"
004017D0 | call <JMP.&puts>                        
004017D5 | mov eax,0                               
004017DA | leave                                   
004017DB | ret                                     


Securinets{  11文字
_Q3eM3tT1t6}  12文字
合計23文字 あと9文字
フラグは32文字

Securinets{123456789_Q3eM3tT1t6}





ーーーーーーーーーーーーーーーーーーーーーーーーーー
自分が今少し興味があること
・ブラウザの仕組み(Sessionとか)
・ネットワークのVPNとか
・リバースエンジニアリング(コードパッチング、リーバスエンジニアリングバイブルの14、15章を理解する!!)
・Excelのマクロ、VBA
(・言語生成AI)

生成AIは大量のデータとGPUが必要みたいだからなんか無理そう。
chatGPTでいいじゃんって気持ちになってしまった。
でも言葉に制限のないAIを作るには自分で作るしかなさそう。
でも調整されてなかったらめっちゃ攻撃的なAIになるかもしれないのか。
まあまた強い興味が出たら勉強してみよう。

それで今この中で興味が強いのはリバースエンジニアリング。
なんかつまらないから音楽聞きながらずっと放置してたcrackmeでもやろうかなと思ってやってみたら少し興味を取り戻した。
crackmeをやってたら、リバースエンジニアリングバイブルの著者が第8章シリアルキーの抽出法で書いていたことを思い出した。
”keygen解析がリバースエンジニアリングの基礎だという人がいるがそれに私は反対だ。リバースエンジニアリングのスキルが高くなれば自然にkeygenができるようになるが、keygenでトレーニングしてもリバースエンジニアリングがうまくならない。”
こんな感じのことを書いていた。
なるほど確かにそうかもしれない。
でもじゃあどうすればいいのだろう。
著者は第15章のコードパッチングにおいて自分がどんなことにリバースエンジニアリングを使ったか書いている。
デジタル署名の自動化、電卓起動時に16進数のラジオボタンをオンにしたこと、テレビチューナーのアプリケーションにDLLを組み込んでリモコンから自作関数を実行したこと。
じゃあkeygen以外でリバースエンジニアリングするには、身近なことで便利にしたいことを見つけるしかないのだろうか。
でも今のところ別にそんなものはない。
でもリバースエンジニアリングはパソコンの深いところに潜っているみたいでちょっと楽しい。
じゃあやりたいことがないけどリバースエンジニアリングに触れるにはkeygenすなわちcrackmeをやるしかなくね?
そんなことを思った。
あと14,15章の記憶がほとんどない。
多分最初に軽く読んだはずだけれど、2週目3週目ではDLLインジェクションとかで躓いてたどり着かなかった気がする。
今度こそ理解できるだろうか。
興味が残っているうちに14,15章を学びなおす!!