質問
次のことを行うC関数を作成することは可能ですか?
- ヒープに大量のメモリを割り当てる
- その中にマシンコードを書き込みます
- これらのマシンの命令を実行します
もちろん、スタックの状態を、それらのマシン命令を手動で実行する前の状態に復元する必要がありますが、これが最初に実行可能かどうかを知りたいです。
解決
確かに可能です。さまざまな理由から、私たちは過去30〜40年間、それを可能な限り困難にするために多くの努力を費やしてきましたが、それは可能です。現在、ほとんどのシステムには、データスペースの実行を保護しようとするハードウェアとソフトウェアのメカニズムがあります。
ただし、基本はかなり簡単です。コードの一部を構築し、それを手動またはコンパイラを介して4でアセンブルします。次に、コードスペースのフラグメントが必要なので、プログラムにコードを挿入します
unsigned int prgm[] = { 0x0F, 0xAB, 0x9A ... }; // Random numbers, just as an example
ヒープを使用したいので、スペースをmallocする必要があります
void * myspace ;
if((myspace= malloc(sizeof(prgm))) != NULL) {
memcpy(myspace, pgrm, sizeof(pgrm));
} else { // allocation error
}
今、必要なのは、コードチャンクでもあるデータチャンクを指すように、プログラムカウンターを取得する方法です。ここに、ちょっとした巧妙さが必要です。プログラムカウンターの設定は大したことではありません。これは、基礎となるマシンのJUMP命令にすぎません。しかし、それを行う方法は?
最も簡単な方法の1つは、意図的にスタックをいじることです。概念的には、スタックは次のようになります(詳細は、OSとコンパイラのペア、およびハードウェアに依存します):
| subroutine return addr | | parameters ... | | automatic variables |
ここでの基本的な秘trickは、コードのアドレスをこっそりと戻りアドレスに入れることです。ルーチンが戻ると、基本的にそのリターンaddrfessにジャンプします。偽造できる場合は、PCは好きな場所に設定されます。
したがって、必要なのはルーチンです。これを" goThere()"と呼びましょう
void goThere(void * addr){
int a ; // observe above; this is the first space
// on the stack following the parameters
int * pa; // so we use it's address
pa = (&a - (sizeof(int)+(2*sizeof(void*))) ; // so use the address
// but back up by the size of an int, the pointer on the
// stack, and the return address
// Now 'pa' points to the routine's return add on the stack.
*pa = addr; // sneak the address of the new code into return addr
return ; // and return, tricking it into "returning"
// to the address of your special code block
}
動作しますか?まあ、多分、ハードウェアとOSに依存します。最新のOSのほとんどは、ヒープが(メモリマッピングなどを介して)PCから保護されます。これは、セキュリティ上の目的で役立ちます。そのような完全な制御を できないようにするためです。
他のヒント
これはこの質問と非常に似ています:)
vc ++からヒープに保存された呼び出しコードを読む。 posixでは、 mprotect
が適切であると思われます( man mprotect
を調べてください):
char *mem = malloc(sizeof(code));
mprotect(mem, sizeof(code), PROT_READ|PROT_WRITE|PROT_EXEC);
memcpy(mem, code, sizeof(code));
// now arrange some code to jump to mem. But read the notes here on casting
// from void* to a function pointer:
// http://www.opengroup.org/onlinepubs/009695399/functions/dlsym.html
しかし、それは言う:
PROT_EXECがPROT_READと異なる効果があるかどうかは、アーキテクチャおよびカーネルのバージョンに依存します。一部のハードウェアアーキテクチャ(i386など)では、PROT_WRITEはPROT_READを意味します。
それで、オペレーティングシステム上で動作するかどうかを最初に確認してください。
RE:手動でスタックを復元する
生成するマシンコード内でプラットフォーム/コンパイラが使用する呼び出し規約に従う場合、手動でスタックを復元する必要はありません。コンパイラは、あなたがそうするときにあなたのためにそれをします
* pfunc(args)
必要な適切な事前または事後呼び出しスタック操作ステップを追加する必要があります。
ただし、生成されたコード内で正しい規則に従うようにしてください。