web-dev-qa-db-ja.com

Linuxシェルコード「Hello、World!」

次の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)();
}

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

ありがとう

27
user1408643

このシェルコードを注入すると、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    8048083 <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   8048065 <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;
}

テストしてみましょう:

$ gcc test.c -o test
$ ./test 
Hello wolrd!
$ 

できます。

69
user1129665

[〜#〜] bsh [〜#〜] のように、シェルコードにはメッセージバイトが含まれていません。 MESSAGEラベルにジャンプし、GOBACKバイトを定義する直前にmsgルーチンを呼び出すことは、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カーネルには [〜#〜] aslr [〜#〜] またはAddress Space Layout Randomizationがあります。シェルコードを挿入する前に、特にバッファオーバーフローが発生している場合は、これを無効にする必要があります。

root@localhost:~# echo 0 > /proc/sys/kernel/randomize_va_space 
20