13

Linux64ビットマシンで16進バイトコードを実行できる単純なCメソッドが必要です。これが私が持っているCプログラムです:

char code[] = "\x48\x31\xc0";
#include <stdio.h>
int main(int argc, char **argv)
{
        int (*func) ();
        func = (int (*)()) code;
        (int)(*func)();
        printf("%s\n","DONE");
}

私が実行しようとしているコード("\x48\x31\xc0")この単純なアセンブリプログラムを作成して取得したコード(実際には何も実行されないはずです)

.text
.globl _start
_start:
        xorq %rax, %rax

次に、それをコンパイルしてobjdumpして、バイトコードを取得します。

ただし、Cプログラムを実行すると、セグメンテーション違反が発生します。何か案は?

4

7 に答える 7

24

マシンコードは実行可能ページにある必要があります。あなたchar code[]はexec権限なしで読み取り+書き込みデータセクションにいるので、そこからコードを実行することはできません。

mmap実行可能ページを次のように割り当てる簡単な例を次に示します。

#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

int main ()
{
  char code[] = {
    0x8D, 0x04, 0x37,           //  lea eax,[rdi+rsi]
    0xC3                        //  ret
  };

  int (*sum) (int, int) = NULL;

  // allocate executable buffer                                             
  sum = mmap (0, sizeof(code), PROT_READ|PROT_WRITE|PROT_EXEC,
              MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

  // copy code to buffer
  memcpy (sum, code, sizeof(code));
  // doesn't actually flush cache on x86, but ensure memcpy isn't
  // optimized away as a dead store.
  __builtin___clear_cache (sum, sum + sizeof(sum));  // GNU C

  // run code
  int a = 2;
  int b = 3;
  int c = sum (a, b);

  printf ("%d + %d = %d\n", a, b, c);
}

の詳細については、この質問に関する別の回答を__builtin___clear_cache参照してください。

于 2012-04-01T12:43:56.347 に答える
9

最近のLinuxカーネルバージョン(5.4より前の場合もあります)までは、gcc -z execstack-を使用してコンパイルするだけで、読み取り専用データ( )や読み取り/書き込みデータ()を含むすべてのページが実行可能になります。.rodata.datachar code[] = "..."

現在-z execstackは実際のスタックにのみ適用されるため、現在は非定数ローカル配列に対してのみ機能します。 つまり、に移動char code[] = ...mainます。


カーネルの変更については、「。data」セクションに対するLinuxのデフォルトの動作、および古い動作のプロジェクトにアセンブリファイルが含まれている場合のmmapからの予期しないexec権限を参照してください:そのプログラムに対してLinuxのREAD_IMPLIES_EXECプロセスを有効にします。(Linux 5.4では、そのQ&Aは、本当に古いバイナリのようREAD_IMPLIES_EXECに、欠落しているものだけを取得することを示しています。最新のGCCは、実行可能ファイルにメタデータを設定し、Linux 5.4は、スタック自体のみを実行可能にするものとして処理します。その前のある時点で、結果として。)PT_GNU_STACK-z execstackPT_GNU_STACK = RWXPT_GNU_STACK = RWXREAD_IMPLIES_EXEC

もう1つのオプションは、実行時にシステムコールを実行して実行可能ページにコピーするか、ページのアクセス許可を変更することです。これは、ローカル配列を使用してGCCにコードを実行可能スタックメモリにコピーさせるよりもさらに複雑です。

(最新のカーネルで有効にする簡単な方法があるかどうかはわかりませんREAD_IMPLIES_EXEC。ELFバイナリにGNUスタック属性がまったくない場合は、32ビットコードでは有効になりますが、64ビットでは有効になりません。)

さらに別のオプションは、__attribute__((section(".text"))) const char code[] = ...;
実用的な例です:https ://godbolt.org/z/draGeh 。
配列を書き込み可能にする必要がある場合、たとえば、文字列にゼロを挿入するシェルコードの場合は、とリンクすることができますld -N。ただし、おそらく-zexecstackとローカル配列を使用するのが最適です。


質問の2つの問題:

  • noexecの読み取り+書き込み.dataセクションに配置される配列を使用したため、ページのexec権限。
  • マシンコードはret命令で終わらないので、たとえ実行されたとしても、実行は戻るのではなく、メモリ内の次にあるものに分類されます。

そしてところで、REXプレフィックスは完全に冗長です。 "\x31\xc0" xor eax,eax とまったく同じ効果がありxor rax,raxます。


実行権限を取得するには、マシンコードを含むページが必要です。x86-64ページテーブルには、従来の386ページテーブルとは異なり、読み取り権限とは別に実行するための個別のビットがあります。

静的配列をread+execメモリに配置する最も簡単な方法は、を使用してコンパイルすることでしたgcc -z execstack(スタックおよび他のセクションを実行可能にするために使用され、現在はスタックのみです)。

最近(2018年または2019年)まで、標準ツールチェーン(binutils ld)はセクション.rodataをと同じELFセグメント.textに配置するため、両方に読み取り+実行権限がありました。したがってconst char code[] = "...";、execstackを使用せずに、手動で指定したバイトをデータとして実行するには、を使用するだけで十分でした。

しかし、私のArch LinuxシステムではGNU ld (GNU Binutils) 2.31.1、それはもはや当てはまりません。 readelf -aは、セクションがと.rodataでELFセグメントに入り、読み取り権限しか持っていないことを示しています。 Read + Execでセグメントに入り、Read + Writeでセグメントに入ります(およびと一緒に)。(ELFファイル形式のセクションとセグメントの違いは何ですか.eh_frame_hdr.eh_frame.text.data.got.got.plt

この変更は、retまたはjmp reg命令のバイトで終わる「ガジェット」として有用なバイトのシーケンスを使用できる実行可能ページに読み取り専用データを持たないことにより、ROPおよびSpectre攻撃をより困難にすることだと思います。

// TODO: use char code[] = {...} inside main, with -z execstack, for current Linux

// Broken on recent Linux, used to work without execstack.
#include <stdio.h>

// can be non-const if you use gcc -z execstack.  static is also optional
static const char code[] = {
  0x8D, 0x04, 0x37,           //  lea eax,[rdi+rsi]       // retval = a+b;                    
  0xC3                        //  ret                                         
};

static const char ret0_code[] = "\x31\xc0\xc3";   // xor eax,eax ;  ret
                     // the compiler will append a 0 byte to terminate the C string,
                     // but that's fine.  It's after the ret.

int main () {
  // void* cast is easier to type than a cast to function pointer,
  // and in C can be assigned to any other pointer type.  (not C++)

  int (*sum) (int, int) = (void*)code;
  int (*ret0)(void) = (void*)ret0_code;

  // run code                                                                   
  int c = sum (2, 3);
  return ret0();
}

古いLinuxシステムの場合:(グローバル/静的アレイgcc -O3 shellcode.c && ./a.outで動作するため)const

5.5より前のLinux(またはそれくらい)gcc -O3 -z execstack shellcode.c && ./a.out-zexecstackマシンコードがどこに保存されているかに関係なく動作します)。おもしろい事実:gccは-zexecstackスペースなしで許可しますが、clangは。のみを受け入れますclang -z execstack

.rdataこれらは、の代わりに読み取り専用データが入力されるWindowsでも機能します.rodata

コンパイラによって生成されたmainものは次のようになります(from objdump -drwC -Mintel)。 内部で実行し、ブレークポイントを設定できますgdbcoderet0_code

(I actually used   gcc -no-pie -O3 -zexecstack shellcode.c  hence the addresses near 401000
0000000000401020 <main>:
  401020:       48 83 ec 08             sub    rsp,0x8           # stack aligned by 16 before a call
  401024:       be 03 00 00 00          mov    esi,0x3
  401029:       bf 02 00 00 00          mov    edi,0x2           # 2 args
  40102e:       e8 d5 0f 00 00          call   402008 <code>     # note the target address in the next page
  401033:       48 83 c4 08             add    rsp,0x8
  401037:       e9 c8 0f 00 00          jmp    402004 <ret0_code>    # optimized tailcall

または、システムコールを使用してページの権限を変更します

でコンパイルする代わりにgcc -zexecstack、を使用mmap(PROT_EXEC)して新しい実行可能ページを割り当てたりmprotect(PROT_EXEC)、既存のページを実行可能ファイルに変更したりできます。(静的データを保持するページを含みます。)もちろん、通常は少なくともPROT_READ、場合PROT_WRITEによっては必要です。

静的配列で使用mprotectするということは、既知の場所からコードを実行していることを意味し、ブレークポイントを設定しやすくなる可能性があります。

Windowsでは、VirtualAllocまたはVirtualProtectを使用できます。

データがコードとして実行されることをコンパイラーに通知する

通常、GCCのようなコンパイラは、データとコードが分離していると想定します。これはタイプベースの厳密なエイリアシングに似ていますが、使用してもchar*、バッファに格納してそのバッファを関数ポインタとして呼び出すことは明確に定義されていません。

__builtin___clear_cache(buf, buf + len)GNU Cでは、マシンコードバイトをバッファに書き込んだ後にも使用する必要があります。これは、オプティマイザが関数ポインタの逆参照をそのアドレスからのバイトの読み取りとして処理しないためです。デッドストアの削除は、ストアがデータとして読み取られていないことをコンパイラが証明した場合に、マシンコードバイトのストアをバッファに削除できます。 https://codegolf.stackexchange.com/questions/160100/the-repetitive-byte-counter/160236#160236およびhttps://godbolt.org/g/pGXn3Bには、gccが実際にこの最適化を行う例があります。 「知っている」malloc

(また、I-cacheがD-cacheとコヒーレントでない非x86アーキテクチャでは、実際に必要なキャッシュ同期を実行します。x86では、純粋にコンパイル時の最適化ブロッカーであり、命令自体には拡張されません。)

Re:3つのアンダースコアが付いた奇妙な名前:これは通常の__builtin_nameパターンですが、nameです__clear_cache

@AntoineMathysの回答に対する私の編集はこれを追加しました。

mmap(MAP_ANONYMOUS)実際には、GCC/clangは彼らが知っている方法を「知りません」malloc。したがって、実際には、オプティマイザは、バッファへのmemcpyが、。がなくても、関数ポインタを介した非インライン関数呼び出しによってデータとして読み取られる可能性があると想定します__builtin___clear_cache()。(関数型をとして宣言した場合を除きます__attribute__((const))。)

I-cacheがデータキャッシュとコヒーレントであるx86では、呼び出しが正確である前に、ストアをasmで発生させます。他のISAでは、__builtin___clear_cache()実際に特別な命令を発行するだけでなく、正しいコンパイル時の順序を保証します。

パフォーマンスのコストがかからず、仮想の将来のコンパイラがコードを壊さないようにするため、コードをバッファにコピーするときに含めることをお勧めします。(たとえば、mmap(MAP_ANONYMOUS)mallocのように、他に何もポインタを持たない、新しく割り当てられた匿名メモリを提供することを理解している場合。)


__attribute__((const))sum()現在のGCCでは、オプティマイザーが純粋関数(グローバルメモリではなく引数のみを読み取る)であることを通知するために使用することで、GCCに不要な最適化を実際に実行させることができました。GCCはsum()、結果をデータとして読み取ることができないことを認識memcpyします。

呼び出し後に別のバッファを同じバッファに入れると、GCCは呼び出しmemcpyの2番目のストアだけにデッドストア除去を実行します。これにより、最初の呼び出しの前にストアがなくなるため、バイトが実行され、segfaultingされます。00 00 add [rax], al

// demo of a problem on x86 when not using __builtin___clear_cache
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>

int main ()
{
  char code[] = {
    0x8D, 0x04, 0x37,           //  lea eax,[rdi+rsi]
    0xC3                        //  ret                                         
  };

  __attribute__((const)) int (*sum) (int, int) = NULL;

  // copy code to executable buffer                                             
  sum = mmap (0,sizeof(code),PROT_READ|PROT_WRITE|PROT_EXEC,
              MAP_PRIVATE|MAP_ANON,-1,0);
  memcpy (sum, code, sizeof(code));
  //__builtin___clear_cache(sum, sum + sizeof(code));

  int c = sum (2, 3);
  //printf ("%d + %d = %d\n", a, b, c);

  memcpy(sum, (char[]){0x31, 0xc0, 0xc3, 0}, 4);  // xor-zero eax, ret, padding for a dword store
  //__builtin___clear_cache(sum, sum + 4);
  return sum(2,3);
}

GCC9.2 -O3を使用してGodboltコンパイラエクスプローラでコンパイル

main:
        push    rbx
        xor     r9d, r9d
        mov     r8d, -1
        mov     ecx, 34
        mov     edx, 7
        mov     esi, 4
        xor     edi, edi
        sub     rsp, 16
        call    mmap
        mov     esi, 3
        mov     edi, 2
        mov     rbx, rax
        call    rax                  # call before store
        mov     DWORD PTR [rbx], 12828721    #  0xC3C031 = xor-zero eax, ret
        add     rsp, 16
        pop     rbx
        ret                      # no 2nd call, CSEd away because const and same args

異なる引数を渡すと別の引数が得call regられますが、2回の呼び出しでもCSEを実行でき__builtin___clear_cachesum(2,3)ます。 __attribute__((const))関数のマシンコードへの変更を尊重しません。しないでください。ただし、関数を1回JITしてから何度も呼び出す場合は、安全です。

__clear_cache最初の結果のコメントを外す

        mov     DWORD PTR [rax], -1019804531    # lea; ret
        call    rax
        mov     DWORD PTR [rbx], 12828721       # xor-zero; ret
       ... still CSE and use the RAX return value

最初の店は__clear_cachesum(2,3)電話のためにそこにあります。(最初のsum(2,3)呼び出しを削除すると、デッドストアの削除が全体で発生し__clear_cacheます。)

によって返されるバッファへの副作用mmapが重要であると想定されているため、2番目のストアがあり、それが最終的な値にmainなります。

プログラムを実行するGodboltの./a.outオプションは、依然として常に失敗しているようです(終了ステータス255)。多分それはJITingをサンドボックス化しますか?それは私のデスクトップで動作し、__clear_cacheなしでクラッシュします。


mprotect既存のC変数を保持するページ。

単一の既存のページに読み取り+書き込み+実行権限を与えることもできます。これは、でコンパイルする代わりになります-z execstack

__clear_cache最適化するストアがないため、読み取り専用のC変数を保持するページは必要ありません。(スタック上の)ローカルバッファを初期化するためにも必要です。それ以外の場合、GCCは、非インライン関数呼び出しが確実にポインターを持たないこのプライベートバッファーの初期化子を最適化します。(エスケープ分析)。を介して指定しない限り、バッファが関数のマシンコードを保持する可能性は考慮されません__builtin___clear_cache

#include <stdio.h>
#include <sys/mman.h>
#include <stdint.h>

// can be non-const if you want, we're using mprotect
static const char code[] = {
  0x8D, 0x04, 0x37,           //  lea eax,[rdi+rsi]       // retval = a+b;                    
  0xC3                        //  ret                                         
};

static const char ret0_code[] = "\x31\xc0\xc3";

int main () {
  // void* cast is easier to type than a cast to function pointer,
  // and in C can be assigned to any other pointer type.  (not C++)
  int (*sum) (int, int) = (void*)code;
  int (*ret0)(void) = (void*)ret0_code;

   // hard-coding x86's 4k page size for simplicity.
   // also assume that `code` doesn't span a page boundary and that ret0_code is in the same page.
  uintptr_t page = (uintptr_t)code & -4095ULL;                  // round down
  mprotect((void*)page, 4096, PROT_READ|PROT_EXEC|PROT_WRITE);  // +write in case the page holds any writeable C vars that would crash later code.

  // run code                                                                   
  int c = sum (2, 3);
  return ret0();
}

この例で使用PROT_READ|PROT_EXEC|PROT_WRITEしたので、変数がどこにあるかに関係なく機能します。それがスタック上のローカルであり、省略した場合、スタックがリターンアドレスをプッシュしようとしたときにのみ読み取り専用にした後、失敗しますPROT_WRITEcall

また、PROT_WRITE自己修正するシェルコードをテストできます。たとえば、ゼロを独自のマシンコード、または回避していた他のバイトに編集できます。

$ gcc -O3 shellcode.c           # without -z execstack
$ ./a.out 
$ echo $?
0
$ strace ./a.out
...
mprotect(0x55605aa3f000, 4096, PROT_READ|PROT_WRITE|PROT_EXEC) = 0
exit_group(0)                           = ?
+++ exited with 0 +++

コメントアウトするとmprotect、GNU Binutilsの最近のバージョンでセグメンテーション違反ld発生し、読み取り専用の定数データが​​セクションと同じELFセグメントに配置されなくなります.text

のようなことをした場合は、その後、ストアが最適化されていないことを確認ret0_code[2] = 0xc3;する必要があり__builtin___clear_cache(ret0_code+2, ret0_code+2)ますが、静的配列を変更しない場合は、その後は必要ありませんmprotect。C(with)で書き込まれたバイトを実行したいので、mmap+または手動ストアの後に必要です。memcpymemcpy

于 2019-04-28T19:20:04.123 に答える
5

アセンブリがコードセグメントに適切に配置されるように、特別なコンパイラ指令を介してアセンブリをインラインで含める必要があります。たとえば、このガイドを参照してください:http ://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html

于 2012-03-31T23:57:38.620 に答える
5

マシンコードは大丈夫かもしれませんが、CPUオブジェクトです。

最新のCPUは、メモリをセグメントで管理します。通常の操作では、オペレーティングシステムは新しいプログラムをプログラムテキストセグメントにロードし、データセグメントにスタックを設定します。オペレーティングシステムは、CPUにデータセグメントでコードを実行しないように指示します。コードはcode[]、データセグメント内にあります。したがって、セグメンテーション違反。

于 2012-03-31T23:57:54.737 に答える
2

これには多少の努力が必要です。

変数は、実行可能ファイルのセクションにcode格納されます。.data

$ readelf -p .data exploit

String dump of section '.data':
  [    10]  H1À

H1À変数の値です。

.dataセクションは実行可能ではありません

$ readelf -S exploit
There are 30 section headers, starting at offset 0x1150:
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
[...]
  [24] .data             PROGBITS         0000000000601010  00001010
       0000000000000014  0000000000000000  WA       0     0     8

私が精通しているすべての64ビットプロセッサは、ページテーブルでネイティブに実行不可能なページをサポートします。ほとんどの新しい32ビットプロセッサ(PAEをサポートするプロセッサ)は、オペレーティングシステムがハードウェアの実行不可能なページをエミュレートするために、ページテーブルに十分な追加スペースを提供します。.data実行可能とマークされたセクションを取得するには、古いOSまたは古いプロセッサのいずれかを実行する必要があります。

これらは実行可能ファイル内の単なるフラグであるため、他のメカニズムを介してフラグを設定できるはずですがX、その方法がわかりません。また、OSでは、書き込み可能で実行可能なページを作成できない場合もあります。

于 2012-04-01T00:03:22.333 に答える
1

ページを呼び出す前に、ページの実行可能ファイルを設定する必要がある場合があります。MS-Windowsでは、VirtualProtect関数を参照してください。

URL: http: //msdn.microsoft.com/en-us/library/windows/desktop/aa366898%28v=vs.85%29.aspx

于 2012-12-05T12:06:05.693 に答える
-1

申し訳ありませんが、上記の複雑な例を追うことができませんでした。そこで、Cから16進コードを実行するための洗練されたソリューションを作成しました。基本的に、asmおよび.wordキーワードを使用して、命令を16進形式で配置できます。以下の例を参照してください。

asm volatile(".rept 1024\n"
             CNOP
           ".endr\n");

ここで、CNOPは次のよ​​うに定義されています。#define ".word 0x00010001 \ n"

基本的に、c.nop命令は私の現在のアセンブラではサポートされていませんでした。それで、私は適切な構文CNOPでの16進数に相当するものとして定義し、c.nop私が知っていたasm内で使用しました。 .rept <NUM> .endr基本的に、命令をNUM回繰り返します。

このソリューションは機能し、検証されています。

于 2021-07-29T13:27:25.123 に答える