次のことを行う C 関数を作成することは可能ですか?
- ヒープに大量のメモリを割り当てます
- そこにマシンコードを書き込みます
- それらのマシン命令を実行します
もちろん、スタックの状態をそれらのマシン命令を手動で実行する前の状態に復元する必要がありますが、そもそもこれが実現可能かどうかを知りたいです。
次のことを行う 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 とコンパイラのペア、およびハードウェアによって異なります)。
| | サブルーチン return addr | | | パラメータ ... | | | 自動変数 |
ここでの基本的なトリックは、コードのアドレスをリターン アドレスにこっそりと入れることです。ルーチンが戻ると、基本的にその戻りアドレスにジャンプします。偽装できれば、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(引数)
必要な適切な事前または事後コール スタック操作ステップを追加する必要があります。
ただし、生成されたコード内で正しい規則に従っていることを確認してください。