ゆとりーなの日記

日記的な事を書いて行くと思はれる

まあポインタってそういうものだよね

私のポインタに対する認識はまだまだ甘かったようです。まあでもポインタが何なのかを突き詰めて考えればそういうことも出来るよな〜って感じですね。今日はC++でポインタを使った黒魔術を紹介しましょう。

ポインタの再確認

まずはポインタがどういうものかを再確認しておきましょう。今回は黒魔術の内容に構造体やクラスのポインタは関わってこないので、組み込み型のポインタのみ扱います。

宣言

ポインタ型の変数はこんな風に宣言します。

int *ptr;
初期化とか代入

ポインタは変数がが保存されている番地を指すものです。無効な番地を示すときはNULL、何かの変数の番地を指すときは&変数名という風に書きます。

int value;
int *null_ptr(NULL);    // 無効なポインタ
int *value_ptr(&value); // valueの番地が入る
ポインタから実体を書き換える

ポインタ変数名の前に*を付けてから値を代入すると、その番地にある変数の中身が代入したものに変えられます。

int value(0);
cout << value << std::endl; // 0と表示
int *value_ptr(&value);
*value_ptr = 1;
cout << value << std::endl; // 1と表示

今日の黒魔術

Cプラーの中にはポインタを使うことがそもそも黒魔術だという人もいると思いますが、真っ当に使う分には全然レベルが低いです。今回は中級クラスの黒魔術的なポインタの使い方を御見せしましょう。

ポインタに数値を代入するとどうなる?

こんな風にしたらどうなるかということです。

unsigned char *num_ptr(reinterpret_cast<unsigned char *>(0x111111);
*num_ptr = 0;

ポインタの原理を考えると0x111111番地の値を0に書き変えるという処理になりますね。通常このようなコードを書いた場合プログラムは落ちるでしょう。大抵の場合アクセス違反が起きるからです。

アクセス違反を起こさず書き換える。

ところがwin32APIには、メモリのアクセス属性を変えられるものが存在します。これで目的のアドレス一帯を書き込み可属性に書き換えてから上の操作を行うと、アクセス違反で落ちることはなくなります。別の意味で落ちることはありそうですが。

何をやろうとしているのか勘のいい人ならもう分かるかもしれない

別の意味で落ちると言うのは、もともと書き込み不可属性の部分には、プログラムコードが入っていたりするので、これを変に書き換えると、プログラムコードが狂ってしまい落ちてしまいそうということです。ということは、プログラムコードを落ちないように正しく書き換えてやれば、動的にプログラムの挙動を書き換えられるということを意味しています。

多重起動を動的にC++コードで潰す

はい。今日の黒魔術はこれです。多重起動を潰すプログラムは前々回に使ったものをそのまま使います。
まずは前々回の逆アセンブラを見てみましょう。

002E1267  |> FF15 38202E00  CALL DWORD PTR DS:[<&KERNEL32.GetLastErr>; [GetLastError
002E126D  |. 3D B7000000    CMP EAX,0B7
002E1272    ^0F85 02FFFFFF  JNZ boot.002E117A
002E1278  |. 6A 00          PUSH 0                                   ; /Style = MB_OK|MB_APPLMODAL
002E127A  |. 68 CC212E00    PUSH OFFSET boot.??_C@_05KKCIMGE@error?$>; |Title = "error"
002E127F  |. 68 D4212E00    PUSH OFFSET boot.??_C@_0BF@PDCHOLH@?$JB?>; |Text = "多重起動は出来ません"
002E1284  |. 6A 00          PUSH 0                                   ; |hOwner = NULL
002E1286  |. FF15 FC202E00  CALL DWORD PTR DS:[<&USER32.MessageBoxA>>; \MessageBoxA
002E128C  |> 53             PUSH EBX                                 ; /hObject
002E128D  |. FF15 08202E00  CALL DWORD PTR DS:[<&KERNEL32.CloseHandl>; \CloseHandle
002E1293  |. 5F             POP EDI
002E1294  |. 5E             POP ESI
002E1295  |. 33C0           XOR EAX,EAX
002E1297  |. 5B             POP EBX
002E1298  |. 8BE5           MOV ESP,EBP
002E129A  |. 5D             POP EBP
002E129B  \. C2 1000        RETN 10

前々回の結論により、多重起動を潰すには3行目を逆アセ修正でJMP boot.002E117Aに書き換えればいいことが分かっています。OllDbgでは、この部分がどこの番地にあるか、この命令がどういうマシン語になるかを見ることができます。番地は一番左の0x002E1272、マシン語は0F85 02FFFFFFになります。一方JMP boot.002E117Aに書き換えた後のマシン語は E9 03FFFFFF 90となります。これをC++コードで書き換えてやります。

先ずはDLLインジェクション

取り敢えず前々回作ったboot.exe起動時に自作コードを埋め込むためにDLLインジェクションを行います。方法は大分前にこのブログでも紹介しましたし、DLLインジェクションってどうやるのってGoogle先生に聞いたらきっと答えてくれるので今回はそのやりかたについては省略します。唯、多重起動判定をするよりも前にDLLを埋め込んでやらなくてはいけないので、少々方法が限られてきます。

書き換えるコード

埋め込むDLLのコードの説明に入ります。プログラムコードがメモリのどこに存在するのかをOllyDbgを使って調べます。メモリーマップでオーナーがbootでセクションが.textとなっている所のアドレスとサイズを見ます。するとそれぞれ1D1000と1000という値が得られます。詰まり1D1000番地から1000バイト分がプログラムコードということになります。この部分をVirtualProtect関数を使って書き込み可能属性に書き換えます。

unsigned long old_protect;
VirtualProtect(reinterpret_cast<void *>(0x1D1000), 0x1000, PAGE_EXECUTE_WRITECOPY, &old_protect);

これで書き換え可能になったので、2E1272番地から6バイト分のマシン語命令を0F85 02FFFFFFからE9 03FFFFFF 90に書き換えます。

unsigned char *func_ptr(reinterpret_cast<unsigned char *>(0x2E1272));
*func_ptr = 0xE9;
++func_ptr;
*func_ptr = 0x03;
++func_ptr;
*func_ptr = 0xFF;
++func_ptr;
*func_ptr = 0xFF;
++func_ptr;
*func_ptr = 0xFF;
++func_ptr;
*func_ptr = 0x90;

書き換えたらそのままでは危険なのでプログラムコードのアクセス属性を元に戻します。

VirtualProtect(reinterpret_cast<void *>(0x1D1000), 0x1000, old_protect, &old_protect);

これでこのコードを埋め込まれたboot.exeは多重起動出来るようになりました。

注意:割と危険な黒魔術なので試すなら慎重に!!