WriteUp runme.exe(Reversing)
前回(https://zenn.dev/kbaur/articles/daff26eac71d31)の記事で
次はCrypto書こうと言っときながらまたReversingですが・・・
SECCON2018のrunme(reversing)を解いてみました。
まずは runme.exe を実行してみますがエラーダイアログが出て終わり。
解析(Ghidraで逆アセンブル)
ということで、Ghidraで解析してみました。
でメインウインドウにて真ん中の逆アセンブリ結果のところから、
まずエントリポイントを探します。
エントリポイントの探し方は以下記事が参考になりました。
runme.exeの場合は 左ペインにある .text をダブルクリックした一番上に出てくるので
わかりやすいですね。
undefined __stdcall entry(void) とあり entry と書かれているので、
そこからもわかりやすいです。
さて、エントリポイントの逆アセンブリ結果ですが、コピペしたものが以下です。
//
// .text
// ram:00401000-ram:004019ff
//
**************************************************************
* FUNCTION *
**************************************************************
undefined __stdcall entry(void)
undefined AL:1 <RETURN>
undefined4 Stack[-0x8]:4 local_8 XREF[1]: 0040100a(W)
entry XREF[4]: Entry Point(*), 004000a8(*),
004000ac(*), 00400184(*)
00401000 55 PUSH EBP
00401001 89 e5 MOV EBP,ESP
00401003 56 PUSH ESI
00401004 ff 15 68 CALL dword ptr [->KERNEL32.DLL::GetCommandLineA] = 0000307e
30 40 00
0040100a 89 45 fc MOV dword ptr [EBP + local_8],EAX
0040100d 50 PUSH EAX
0040100e 6a 22 PUSH 0x22
00401010 e8 1f 00 CALL FUN_00401034 undefined FUN_00401034(char para
00 00
00401015 6a 40 PUSH 0x40
00401017 68 27 20 PUSH s_Congratz_00402027 = "Congratz"
40 00
0040101c 68 30 20 PUSH s_You_know_the_flag!_00402030 = "You know the flag!"
40 00
00401021 6a 00 PUSH 0x0
00401023 ff 15 98 CALL dword ptr [->USER32.DLL::MessageBoxA] = 000030a0
30 40 00
00401029 ff 15 64 CALL dword ptr [->KERNEL32.DLL::ExitProcess] = 00003070
30 40 00
機械語なのでちんぷんかんぶんですね。
右ペインのC言語相当の部分を追うだけでもこの問題は十分解ける
(さらにいえば Linux で runme.exe をstrings コマンドで読み込むだけでも十分だったり・・・)
のですが、勉強のために解釈してみました。
まず、PUSHですがこれはスタックに情報を置くものです。
EBPとESPはレジスタ(CPU内にあるメインメモリよりも高速アクセス可能な領域)に関するもので
EBPはスタックの底のアドレス、ESPはスタックの最初のアドレスが入っているとのこと。
で、4行目のCALLは関数を呼ぶもので、
Windows API の GetCommandLineA を呼び出しています。
次のMOVは情報を代入するもので、EAX(レジスタ内の領域の一つ)に
GetCommandLineAの結果を格納しています。
そして、8行目のCALLで FUN_00401034 で
6,7行目のPUSHでスタックに積んでおいた 0x22 と GetCommandLineAの結果 の情報を
引数として実行しています。
スタックは後入れ先出し、LIFO(Last In First Out)なので
0x22 が第一引数、GetCommandLineAの結果が第二引数ですね。
参考情報
右ペイン、C言語相当のコード部分をコピペしたものが以下です。
void entry(void)
{
LPSTR pCVar1;
UINT uExitCode;
pCVar1 = GetCommandLineA();
uExitCode = 0x22;
FUN_00401034('\"',pCVar1);
MessageBoxA((HWND)0x0,"You know the flag!","Congratz",0x40);
/* WARNING: Subroutine does not return */
ExitProcess(uExitCode);
}
機械語で書かれている通り、4行目のCALLと5行目のMOV部分として
pCVar1という変数に GetCommandLineA() の結果を代入という処理で書かれており、
6~8行目のPUSH,CALLの処理が
FUN_00401034という関数を '"',pCVar1 の引数とともに呼び出しという形で書かれてますね。
それでは FUN_00401034 部分です。
まず逆アセンブル結果のコピペから。
**************************************************************
* FUNCTION *
**************************************************************
undefined __cdecl FUN_00401034(char param_1, char * para
undefined AL:1 <RETURN>
char Stack[0x4]:1 param_1 XREF[1]: 00401038(R)
char * Stack[0x8]:4 param_2 XREF[2]: 0040103c(R),
0040104f(R)
FUN_00401034 XREF[1]: entry:00401010(c)
00401034 55 PUSH EBP
00401035 89 e5 MOV EBP,ESP
00401037 56 PUSH ESI
00401038 0f b6 4d 08 MOVZX ECX,byte ptr [EBP + param_1]
0040103c 8b 55 0c MOV EDX,dword ptr [EBP + param_2]
0040103f 0f b6 12 MOVZX EDX,byte ptr [EDX]
00401042 39 d1 CMP ECX,EDX
00401044 0f 85 71 JNZ LAB_004018bb
08 00 00
0040104a b9 01 00 MOV ECX,0x1
00 00
0040104f 8b 55 0c MOV EDX,dword ptr [EBP + param_2]
00401052 42 INC EDX
00401053 52 PUSH EDX
00401054 6a 43 PUSH 0x43
00401056 e8 05 00 CALL FUN_00401060 undefined FUN_00401060(char para
00 00
0040105b 5e POP ESI
0040105c 89 ec MOV ESP,EBP
0040105e 5d POP EBP
0040105f c3 RET
LAB_004018bb XREF[50]: 00401044(j),
FUN_00401060:00401070(j),
FUN_0040108c:0040109c(j),
FUN_004010b8:004010c8(j),
FUN_004010e4:004010f4(j),
FUN_00401110:00401120(j),
FUN_0040113c:0040114c(j),
FUN_00401168:00401178(j),
FUN_00401194:004011a4(j),
FUN_004011c0:004011d0(j),
FUN_004011ec:004011fc(j),
FUN_00401218:00401228(j),
FUN_00401244:00401254(j),
FUN_00401270:00401280(j),
FUN_0040129c:004012ac(j),
FUN_004012c8:004012d8(j),
FUN_004012f4:00401304(j),
FUN_00401320:00401330(j),
FUN_0040134c:0040135c(j),
FUN_00401378:00401388(j), [more]
004018bb 6a 40 PUSH 0x40
004018bd 68 00 20 PUSH s_Failed_00402000 = "Failed"
40 00
004018c2 68 07 20 PUSH s_The_environment_is_not_correct._00402007 = "The environment is not correc
40 00
004018c7 6a 00 PUSH 0x0
004018c9 ff 15 98 CALL dword ptr [->USER32.DLL::MessageBoxA] = 000030a0
30 40 00
004018cf ff 15 64 CALL dword ptr [->KERNEL32.DLL::ExitProcess] = 00003070
30 40 00
4~7行目ですが、
MOVとMOVZXは値代入で、param1とparam2をそれぞれ ECX,EDX に入れています。
MOVZXでは byte ptr とあるので1byteだけ代入、つまり一文字だけ入れています。
つまり、まずは '"' と GetCommandLineA の一文字目を比べているということですね。
7行目の CMP ECX,EDX で ECX,EDX を比較して、NGなら
次の JNZ LAB_004018bb を呼び出すとなっています。
もしOKなら JNZ は飛ばして次の MOV ECX,0x1 へとなります。
その後 MOV EDX,dword ptr [EBP + param_2],INC EDX,PUSH EDX で
param_2(GetCommandLineA結果)の先頭アドレスを EDX に入れ、
EDX のアドレスを一つインクリメント、
つまり二文字目のアドレスをPUSHでスタックに格納しています。
次に 0x43 ASCIIコードで'C'をスタックに格納し CALL FUN_00401060 を
'C',GetCommandLineAの結果の二文字目のアドレスを引数として関数呼び出し、
ということをずっと続けているということがわかりました。
なおNGの時の LAB_004018bb では MessageBoxA を
0x0,"The environment is not correct.","Failed",0x40 の引数とともに実施して
ExitProcessで終了しています。
ここまでの処理のC言語解釈を見てみると
void __cdecl FUN_00401034(char param_1,char *param_2)
{
UINT unaff_ESI;
if (param_1 == *param_2) {
FUN_00401060('C',param_2 + 1);
return;
}
MessageBoxA((HWND)0x0,"The environment is not correct.","Failed",0x40);
/* WARNING: Subroutine does not return */
ExitProcess(unaff_ESI);
}
というように機械語の内容がC言語相当に解釈されていました。
結果
関数を追い続けて、GetCommandLineAの結果と何を比較しているかを確認した結果は
以下となりました。
"C:\Temp\SECCON2018Online.exe" SECCON{Runn1n6_P47h}
それではこれを実行してみます・・・
congratzがでました!
感想
今回Ghidraでの2回目の解析で少しインターフェースがわかりました。
機械語も少しだけわかりました・・・すこしだけ・・・
次は機械語が少しわかったところでgdbかwindbgでデバッグやってみたいところです。
Discussion