ネイティブの機械語コードを呼び出そうとしています。ここに私がこれまでに持っているものがあります(バスエラーが発生します)。
char prog[] = {'\xc3'}; // x86 ret instruction
int main()
{
typedef double (*dfunc)();
dfunc d = (dfunc)(&prog[0]);
(*d)();
return 0;
}
関数を正しく呼び出し、ret命令に到達します。ただし、ret命令を実行しようとすると、SIGBUSエラーが発生します。それは、実行が許可されていないページでコードを実行しているからでしょうか?
だから私はここで間違っているのですか?
最初の問題の1つは、progデータが保存されている場所が実行可能でないことです。
少なくともLinuxでは、結果のバイナリはグローバル変数の内容を "data"セグメント または here に配置します。これは ほとんどの通常の場合 。
2番目の問題は、呼び出しているコードが何らかの方法で無効であることです。 呼び出し規約 と呼ばれるCのメソッドを呼び出すための特定の手順があります(たとえば、「cdecl」を使用する場合があります)。呼び出された関数が「ret」だけでは不十分な場合があります。また、スタックのクリーンアップなども必要になる場合があります。そうしないと、プログラムは予期せず動作します。最初の問題を乗り越えると、これが問題になることがあります。
Progが存在するページを実行可能にするには、memprotectを呼び出す必要があります。次のコードはこの呼び出しを行い、progでテキストを実行できます。
#include <unistd.h>
#include <stdio.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/mman.h>
char prog[] = {
0x55, // Push %rbp
0x48, 0x89, 0xe5, // mov %rsp,%rbp
0xf2, 0x0f, 0x10, 0x05, 0x00, 0x00, 0x00,
//movsd 0x0(%rip),%xmm0 # c <x+0xc>
0x00,
0x5d, // pop %rbp
0xc3, // retq
};
int main()
{
long pagesize = sysconf(_SC_PAGE_SIZE);
long page_no = (long)prog/pagesize;
int res = mprotect((void*)(page_no*pagesize), (long)page_no+sizeof(prog), PROT_EXEC|PROT_READ|PROT_WRITE);
if(res)
{
fprintf(stderr, "mprotect error:%d\n", res);
return 1;
}
typedef double (*dfunc)(void);
dfunc d = (dfunc)(&prog[0]);
double x = (*d)();
printf("x=%f\n", x);
fflush(stdout);
return 0;
}
すでに述べたように、prog[]
は実行可能ですが、適切な方法は、JITコンパイラを作成している場合を除き、リンカスクリプトを使用するか、コンパイラが許可、例:
const char prog[] __attribute__((section(".text"))) = {...}
実質的にすべてのCコンパイラは、コードに通常のアセンブリ言語を埋め込むことでこれを可能にします。もちろん、これはCに対する非標準の拡張機能ですが、コンパイラの作成者はそれがしばしば必要であることを認識しています。非標準の拡張機能として、コンパイラのマニュアルを読み、その方法を確認する必要がありますが、 GCC "asm"拡張機能 はかなり標準的なアプローチです。
void DoCheck(uint32_t dwSomeValue)
{
uint32_t dwRes;
// Assumes dwSomeValue is not zero.
asm ("bsfl %1,%0"
: "=r" (dwRes)
: "r" (dwSomeValue)
: "cc");
assert(dwRes > 3);
}
アセンブラーでスタックを破棄するのは簡単なので、コンパイラーは、アセンブラーの一部として使用するレジスタを識別することもできます。コンパイラは、その後、その関数の残りがこれらのレジスタを回避することを保証できます。
自分でアセンブラコードを記述している場合、そのアセンブラをバイトの配列として設定する正当な理由はありません。これは単なるコードの匂いではありません。アセンブラをCに埋め込む正しい方法である「asm」拡張機能を知らない場合にのみ発生する可能性のある真のエラーだと思います。
本質的に、これはウイルス作成者への開かれた招待であったため、厳しく制限されています。しかし、ストレートCのネイティブマシンコードで割り当ててバッファリングし、セットアップすることができます-それは問題ありません。問題はそれを呼んでいます。バッファのアドレスを使用して関数ポインタを設定して呼び出すことはできますが、動作する可能性は非常に低く、何らかの方法で目的の動作を実行できるようになった場合、コンパイラの次のバージョンで中断する可能性が高くなります。したがって、最善の方法は、インラインアセンブリを少し使用して、リターンを設定し、自動生成されたコードにジャンプすることです。しかし、システムがこれに対して保護する場合、Rudiが回答で説明したように、保護を回避する方法を見つける必要があります(ただし、特定のシステムに非常に固有です)。
明らかなエラーの1つは、\xc3
は、あなたが返すと主張するdouble
を返していません。
コンパイラがプロセスメモリの読み取り専用セクションに配列を格納できるようにすることで、クラッシュを解消できます(コンパイル時に既知の場合)。たとえば、配列const
を宣言します。
例:
const char prog[] = {'\xc3'}; // x86 ret instruction
int main()
{
typedef double (*dfunc)();
dfunc d = (dfunc)(&prog[0]);
(*d)();
return 0;
}
または、スタック保護を無効にしてコードをコンパイルすることもできますgcc -z execstack
。
関連する質問: