28

私は次の動作するNASMコードを持っています:

global _start

section .text

_start:
    mov eax, 0x4
    mov ebx, 0x1
    mov ecx, message
    mov edx, 0xF
    int 0x80

    mov eax, 0x1
    mov ebx, 0x0
    int 0x80

section .data
    message: db "Hello, World!", 0dh, 0ah

画面に「Hello、World!\n」と出力されます。以前のNASMオブジェクトコードを含む次のCラッパーもあります。

char code[] =
"\xb8\x04\x00\x00\x00"
"\xbb\x01\x00\x00\x00"
"\xb9\x00\x00\x00\x00"
"\xba\x0f\x00\x00\x00"
"\xcd\x80\xb8\x01\x00"
"\x00\x00\xbb\x00\x00"
"\x00\x00\xcd\x80";

int main(void)
{
    (*(void(*)())code)();
}

ただし、コードを実行すると、アセンブラコードが実行されていないように見えますが、プログラムは正常に終了します。何か案は?

ありがとう

4

2 に答える 2

78

このシェルコードを挿入すると、何があるかわかりませんmessage:

mov ecx, message

"Hello world!\r\n"挿入されたプロセスでは、テキストセクションのみをダンプしているときにデータセクションにあるため、何でもかまいません。シェルコードに次のものが含まれていないことがわかります"Hello world!\r\n"

"\xb8\x04\x00\x00\x00"
"\xbb\x01\x00\x00\x00"
"\xb9\x00\x00\x00\x00"
"\xba\x0f\x00\x00\x00"
"\xcd\x80\xb8\x01\x00"
"\x00\x00\xbb\x00\x00"
"\x00\x00\xcd\x80";

これは、シェルコード開発でよくある問題です。これを回避する方法は次のとおりです。

global _start

section .text

_start:
    jmp MESSAGE      ; 1) lets jump to MESSAGE

GOBACK:
    mov eax, 0x4
    mov ebx, 0x1
    pop ecx          ; 3) we are poping into `ecx`, now we have the
                     ; address of "Hello, World!\r\n" 
    mov edx, 0xF
    int 0x80

    mov eax, 0x1
    mov ebx, 0x0
    int 0x80

MESSAGE:
    call GOBACK       ; 2) we are going back, since we used `call`, that means
                      ; the return address, which is in this case the address 
                      ; of "Hello, World!\r\n", is pushed into the stack.
    db "Hello, World!", 0dh, 0ah

section .data

次に、テキスト セクションをダンプします。

$ nasm -f elf shellcode.asm
$ ld shellcode.o -o shellcode
$ ./shellcode 
Hello, World!
$ objdump -d shellcode

shellcode:     file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
 8048060:   e9 1e 00 00 00   jmp    8048083 <MESSAGE>

08048065 <GOBACK>:
 8048065:   b8 04 00 00 00   mov    $0x4,%eax
 804806a:   bb 01 00 00 00   mov    $0x1,%ebx
 804806f:   59               pop    %ecx
 8048070:   ba 0f 00 00 00   mov    $0xf,%edx
 8048075:   cd 80            int    $0x80
 8048077:   b8 01 00 00 00   mov    $0x1,%eax
 804807c:   bb 00 00 00 00   mov    $0x0,%ebx
 8048081:   cd 80            int    $0x80

08048083 <MESSAGE>:
 8048083:   e8 dd ff ff ff   call   8048065 <GOBACK>
 8048088:   48               dec    %eax                    <-+
 8048089:   65               gs                               |
 804808a:   6c               insb   (%dx),%es:(%edi)          |
 804808b:   6c               insb   (%dx),%es:(%edi)          |
 804808c:   6f               outsl  %ds:(%esi),(%dx)          |
 804808d:   2c 20            sub    $0x20,%al                 |
 804808f:   57               push   %edi                      |
 8048090:   6f               outsl  %ds:(%esi),(%dx)          |
 8048091:   72 6c            jb     80480ff <MESSAGE+0x7c>    |
 8048093:   64               fs                               |
 8048094:   21               .byte 0x21                       |
 8048095:   0d               .byte 0xd                        |
 8048096:   0a               .byte 0xa                      <-+

$

私がマークした行は私たちの"Hello, World!\r\n"文字列です:

$ printf "\x48\x65\x6c\x6c\x6f\x2c\x20\x57\x6f\x72\x6c\x64\x21\x0d\x0a"
Hello, World!

$ 

したがって、C ラッパーは次のようになります。

char code[] = 

    "\xe9\x1e\x00\x00\x00"  //          jmp    (relative) <MESSAGE>
    "\xb8\x04\x00\x00\x00"  //          mov    $0x4,%eax
    "\xbb\x01\x00\x00\x00"  //          mov    $0x1,%ebx
    "\x59"                  //          pop    %ecx
    "\xba\x0f\x00\x00\x00"  //          mov    $0xf,%edx
    "\xcd\x80"              //          int    $0x80
    "\xb8\x01\x00\x00\x00"  //          mov    $0x1,%eax
    "\xbb\x00\x00\x00\x00"  //          mov    $0x0,%ebx
    "\xcd\x80"              //          int    $0x80
    "\xe8\xdd\xff\xff\xff"  //          call   (relative) <GOBACK>
    "Hello wolrd!\r\n";     // OR       "\x48\x65\x6c\x6c\x6f\x2c\x20\x57"
                            //          "\x6f\x72\x6c\x64\x21\x0d\x0a"


int main(int argc, char **argv)
{
    (*(void(*)())code)();

    return 0;
}

またはセクションでコードを実行できるように、 read-implies-exec (名前に「スタック」が含まれているにもかかわらず、プロセス全体)を有効に-z execstackてテストします。.data.rodata

$ gcc -m32 test.c -z execstack -o test
$ ./test 
Hello wolrd!

できます。(-m32は 64 ビット システムでも必要ですint $0x80。32 ビット ABI は、PIE 実行可能ファイルのように 64 ビット アドレスでは機能しません.rodata。また、マシン コードは 32 ビット用にアセンブルされています。同じシーケンスが発生します。のバイトは、64 ビット モードで同等の命令にデコードされますが、常にそうであるとは限りません。)

最新の GNUld.rodataとは別のセグメントを.text配置するため、実行できない場合があります。const char code[]以前は、読み取り専用データのページに実行可能コードを配置するのに十分でした。少なくとも、それ自体を変更したくないシェルコードの場合。

于 2013-03-29T14:03:38.067 に答える
21

BSHが述べたように、シェルコードにはメッセージ バイトが含まれていません。MESSAGEラベルにジャンプし、バイトをGOBACK定義する直前にルーチンを呼び出すmsgことは、msg のアドレスが格納されている にポップできる戻りアドレスとしてスタックの一番上にあるため、良い動きでしたecx

しかし、あなたのコードとBSHのコードの両方には、わずかな制限があります。NULL bytes ( \x00 )関数ポインターによって逆参照されたときに文字列の末尾と見なされるものを含みます。

これを回避する賢い方法があります。格納する値eax, ebx and edxは、それぞれアクセスすることによって、それぞれのレジスタの下位ニブルに一度に直接書き込むのに十分なほど小さいal, bl and dlです。上部のニブルにはジャンク値が含まれている可能性があるため、xored にすることができます。

b8 04 00 00 00 ------ mov $0x4,%eax


になる

b0 04          ------ mov $0x4,%al
31 c0          ------ xor    %eax,%eax


以前の命令セットとは異なり、新しい命令セットには NULL バイトが含まれていません。

したがって、最終的なプログラムは次のようになります。

global _start

section .text

_start:
jmp message

proc:
    xor eax, eax
    mov al, 0x04
    xor ebx, ebx
    mov bl, 0x01
    pop ecx
    xor edx, edx
    mov dl, 0x16
    int 0x80

    xor eax, eax
    mov al, 0x01
    xor ebx, ebx
    mov bl, 0x01   ; return 1
    int 0x80

message:
    call proc
    msg db " y0u sp34k 1337 ? "

section .data

組み立てとリンク :

$ nasm -f elf hello.asm -o hello.o
$ ld -s -m elf_i386 hello.o -o hello
$ ./hello
 y0u sp34k 1337 ? $ 

hello バイナリからシェルコードを抽出します。

$ for i in `objdump -d hello | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\\x$i" ; done

出力:

\xeb\x19\x31\xc0\xb0\x04\x31\xdb\xb3\x01\x59\x31\xd2\xb2\x12\xcd\x80\x31\xc0\xb0\x01\x31\xdb\xb3\x01\xcd\x80\xe8\xe2\xff\xff\xff\x20\x79\x30\x75\x20\x73\x70\x33\x34\x6b\x20\x31\x33\x33\x37\x20\x3f\x20

これで、シェルコードを起動するためのドライバー プログラムを作成できます。

#include <stdio.h>

char shellcode[] = "\xeb\x19\x31\xc0\xb0\x04\x31\xdb"
                   "\xb3\x01\x59\x31\xd2\xb2\x12\xcd"
                   "\x80\x31\xc0\xb0\x01\x31\xdb\xb3"
                   "\x01\xcd\x80\xe8\xe2\xff\xff\xff"
                   "\x20\x79\x30\x75\x20\x73\x70\x33"
                   "\x34\x6b\x20\x31\x33\x33\x37\x20"
                   "\x3f\x20";


int main(int argc, char **argv) {
    (*(void(*)())shellcode)();
    return 0;
}

最新のコンパイラには、データ セグメントまたはスタックでのコードの実行を防止するNX 保護などの特定のセキュリティ機能があります。したがって、これらを無効にするコンパイラを明示的に指定する必要があります。

$ gcc -g -Wall -fno-stack-protector -z execstack launcher.c -o launcher

これで、launcherを呼び出してシェルコードを起動できます。

$ ./launcher
 y0u sp34k 1337 ? $ 

より複雑なシェルコードの場合、別のハードルがあります。最新の Linux カーネルにはASLRAddress Space Layout Randomization があります。特にバッファ オーバーフローによる場合は、シェルコードを挿入する前にこれを無効にする必要がある場合があります。

root@localhost:~# echo 0 > /proc/sys/kernel/randomize_va_space 
于 2014-06-26T19:19:55.180 に答える