#include <stdio.h> const void *ptrprintf = printf; #pragma section(".exre", execute, read) __declspec(allocate(".exre")) int main[] = { 0x646C6890, 0x20680021, 0x68726F57, 0x2C6F6C6C, 0x48000068, 0x24448D65, 0x15FF5002, &ptrprintf, 0xC314C483 };
まえがき
それで、私はこの記事を見つけることから始めました。 それに触発されて、私は窓でそれをする方法を考え始めました。
その記事では、画面出力はsyscallを使用して実装されましたが、Windowsではprintf関数しか使用できません。 間違っているかもしれませんが、他に何も見つかりませんでした。
勇気を出し、ビジュアルスタジオを手に入れて、試してみました。 コンパイル設定のエントリポイントを置換するのにそれほど時間がかかった理由はわかりませんが、後で判明したように、Visual Studioコンパイラは、mainが配列であり関数ではない場合でも警告をスローしません。
私が直面しなければならなかった問題の主なリスト:
1)配列はデータセクションにあり、実行できません
2)Windowsにはsyscallがなく、printfを使用して出力を実装する必要があります
ここで関数呼び出しが悪い理由を説明しましょう。 通常、呼び出しアドレスは、私が間違っていない場合、シンボルテーブルからコンパイラによって置き換えられます。 しかし、通常の配列があり、自分でアドレスを書き込む必要があります。
「実行可能データ」の問題の解決策
予想どおり、最初に遭遇した問題は、単純な配列がデータセクションに格納されており、コードとして実行できないことでした。 しかし、stackoverflowとmsdnを少し掘り下げた後、私はまだ道を見つけました。 Visual Studioコンパイラはセクションプリプロセッサディレクティブをサポートしており、実行許可を持つセクションに表示されるように変数を宣言できます。
これが事実であるかどうかを確認した後、私はこれが機能し
#pragma section(".exre", execute, read) __declspec(allocate(".exre")) char main[] = { 0xC3 };
アセンブラーのビット
配列を実行できるようになったので、実行するコードを作成する必要がありました。
「Hello、World」というメッセージをアセンブラーコードに保存することにしました。 私はアセンブラーをかなり理解していないとすぐに言わなければならないので、スリッパで強く走らないようにお願いしますが、批判は大歓迎です。 stackoverfowに対するこの回答は、余分な機能を引き起こすことなく挿入できるアセンブラコードを理解するのに役立ちました
メモ帳++を使用し、プラグイン->コンバーター-> "ASCII-> HEX"関数を使用して、文字コードを取得しました。
Hello World!
48656C6C6F2C20576F726C6421
次に、4バイトを分割して、逆順でスタックに配置する必要があります。リトルエンディアンに変換することを忘れないでください。
分割、反転。
終端にゼロを追加します。
最後から4バイトの16進数で除算します。
リトルエンディアンを有効にして順序を逆にします
48656C6C6F2C20576F726C642100
最後から4バイトの16進数で除算します。
00004865 6C6C6F2C 20576F72 6C642100
リトルエンディアンを有効にして順序を逆にします
0x0021646C 0x726F5720 0x2C6F6C6C 0x65480000
printfを直接呼び出して、このアドレスを後で配列に保存しようとした方法で、ポイントをわずかに逃しました。 printfへのポインターを保存しただけでした。 後でそれがなぜ見られるでしょう。
#include <stdio.h> const void *ptrprintf = printf; void main() { __asm { push 0x0021646C ; "ld!\0" push 0x726F5720 ; " Wor" push 0x2C6F6C6C ; "llo," push 0x65480000 ; "\0\0He" lea eax, [esp+2] ; eax -> "Hello, World!" push eax ; call ptrprintf ; printf add esp, 20 ; } }
逆アセンブラーをコンパイルして監視します。
00A8B001 68 6C 64 21 00 push 21646Ch 00A8B006 68 20 57 6F 72 push 726F5720h 00A8B00B 68 6C 6C 6F 2C push 2C6F6C6Ch 00A8B010 68 00 00 48 65 push 65480000h 00A8B015 8D 44 24 02 lea eax,[esp+2] 00A8B019 50 push eax 00A8B01A FF 15 00 90 A8 00 call dword ptr [ptrprintf (0A89000h)] 00A8B020 83 C4 14 add esp,14h 00A8B023 C3 ret
ここから、コードのバイトを取得する必要があります。
アセンブラコードを手動で削除しないようにするには、メモ帳++で正規表現を使用できます。
コードバイトの後のシーケンスの正規表現:
行の先頭は、メモ帳++ TextFxのプラグインを使用して削除できます。
TextFX-> "TextFxツール"-> "行番号または最初の単語を削除"、すべての行を強調表示します。
その後、ほぼ完成した配列のコードシーケンスが作成されます。
{2} *。*
行の先頭は、メモ帳++ TextFxのプラグインを使用して削除できます。
TextFX-> "TextFxツール"-> "行番号または最初の単語を削除"、すべての行を強調表示します。
その後、ほぼ完成した配列のコードシーケンスが作成されます。
68 6C 64 21 00 68 20 57 6F 72 68 6C 6C 6F 2C 68 00 00 48 65 8D 44 24 02 50 FF 15 00 90 A8 00; FF 15の後、次の4バイトは呼び出された関数のアドレスでなければなりません。 83 C4 14 C3
「既知の」アドレスで関数を呼び出す
コンパイラだけがこれを知っている場合、完成したシーケンスで関数テーブルのアドレスを残す方法を長い間考えていました。 そして、何人かの使い慣れたプログラマーに尋ねて実験したところ、呼び出された関数のアドレスは、変数ポインターから関数へのアドレスを取得する操作を使用して取得できることに気付きました。 私がやった。
#include <stdio.h> const void *ptrprintf = printf; void main() { void *funccall = &ptrprintf; __asm { call ptrprintf } }
ご覧のとおり、ポインターにはまったく同じ着信アドレスが含まれています。 必要なもの。
すべてをまとめる
したがって、アセンブラコードのバイトシーケンスがあり、その中に、コンパイラがprintfを呼び出す必要があるアドレスに変換する式を残す必要があります。 4バイトのアドレスがあります(32ビットプラットフォーム用のコードを記述しているため)。つまり、配列には4バイトの値が含まれている必要があります。したがって、バイトFF 15の後にアドレスを配置する次の要素があります。
単純な置換を使用して、目的のシーケンスを取得します。
以前に取得したアセンブラコードのバイトシーケンスを使用します。 FF 15の後の4バイトという事実に基づいて、1つの値を作成してフォーマットする必要があります。 そして、不足しているバイトをコード0x90のnop操作で置き換えます。
繰り返しになりますが、リトルエンディアンで4バイトの値を作成しましょう。 列をラップするには、メモ帳++で複数行選択を使用し、alt + shiftを組み合わせて使用すると非常に便利です。
90 68 6C 64 21 00 68 20 57 6F 72 68 6C 6C 6F 2C 68 00 00 48 65 8D 44 24 02 50 FF 15 00 90 A8 00; printfを呼び出すアドレス 83 C4 14 C3
繰り返しになりますが、リトルエンディアンで4バイトの値を作成しましょう。 列をラップするには、メモ帳++で複数行選択を使用し、alt + shiftを組み合わせて使用すると非常に便利です。
646C6890 20680021 68726F57 2C6F6C6C 48000068 24448D65 15FF5002 00000000; printfを呼び出すためのアドレス、それは式に置き換えられます C314C483
これで、4バイトの数字のシーケンスとprintf関数を呼び出すアドレスができました。そして、最終的にメイン配列にデータを追加できます。
#include <stdio.h> const void *ptrprintf = printf; #pragma section(".exre", execute, read) __declspec(allocate(".exre")) int main[] = { 0x646C6890, 0x20680021, 0x68726F57, 0x2C6F6C6C, 0x48000068, 0x24448D65, 0x15FF5002, &ptrprintf, 0xC314C483 };
Visual Studioデバッガーでブレークポイントを呼び出すには、配列の最初の要素を0x646C68 CCに置き換える必要があります
私たちは始め、見ます。
できた!
おわりに
誰かがその記事を「最小のもの」だと思っていたらおIびします。 プロセスをできるだけ詳細に説明し、明白なことは省略しようとしました。 私はそのような小さな研究の私自身の経験を共有したかったです。 記事が誰かにとって興味深いものであり、おそらく役に立つものであるならば、私は喜んでいるでしょう。
ここにすべてのリンクを残します。
記事「メインは通常機能」
msdnの説明セクション
StackOverflowのアセンブリコードの説明
念のため、Visual Studio 2013のプロジェクトに7zアーカイブへのリンクを残します
また、printf呼び出しをさらに減らし、別の関数呼び出しコードを使用することが可能であったことも除外しませんが、この質問を調査することはできませんでした。
フィードバックとコメントをお待ちしています。