2013-03-23 125 views
23

我有以下工作NASM代码:Linux Shellcode“Hello,World!”

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 

它打印 “!您好,世界\ N” 到屏幕上。我也有以下的C包装包含以前NASM对象代码:

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

然而,当我运行的代码,它似乎像汇编代码不被执行,但是在程序退出的罚款。有任何想法吗?

感谢

回答

63

当你注入这个shellcode的,你不知道什么是message

mov ecx, message 
在注射过程中

,它可以是任何东西,但它不会是"Hello world!\r\n",因为它是在数据部分,而您仅转储文本部分。你可以看到你的shellcode没有"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"; 

这是shellcode的发展普遍存在的问题,解决它的方法是这样的:

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! 
$ 

它的工作原理。

+2

不知道为什么你没有得到任何upvotes,但这是一个很好的答案。谢谢您的帮助。 – 2013-05-03 13:48:31

+0

空字节应该被移除以执行shellcode tho – REALFREE 2013-10-14 04:14:25

+2

@REALFREE如果您使用函数需要空终止字符串(如字符串函数为“strcpy”),则空字节将会成为问题,它将不会读取整个shellcode字符串。否则就没关系。 – 2013-10-15 13:38:21

18

由于提到BSH,您的shellcode不包含消息字节。跳到MESSAGE标签,只是定义msg字节之前调用GOBACK程序是一个很好的举措作为味精的地址将是对堆栈返回地址的顶部可能被弹出到ecx,其中味精的地址存储。

但无论你和BSH的代码有轻微的限制。 它包含NULL bytes (\x00)当由所述函数指针解除引用这将被认为是字符串的结尾。

周围有这样一个聪明的办法。您存储到eax, ebx and edx的值是足够小,分别访问al, bl and dl直接写入一气呵成各寄存器的下半字节。 高半字节可能包含垃圾值,因此可以进行着色。

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


变得

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


不同于现有的指令集,所述新指令集不包含任何空字节。

所以,最后的方案是这样的:

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 

组装和链接:

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

输出:

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

现在从打招呼二进制提取的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 

现在我们可以让我们的驱动程序启动shellcode。

#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 protection这防止在数据段或堆栈代码执行某些安全功能。所以我们应该明确地指定编译器来禁用它们。

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

现在可以调用launcher来启动shellcode。

$ ./launcher 
y0u sp34k 1337 ? $ 

对于更复杂的shellcode,会有另一个障碍。现代Linux内核有ASLRAddress Space Layout Randomization 您可能需要在注入shellcode之前禁用它,尤其是在通过缓冲区溢出时。

[email protected]:~# echo 0 > /proc/sys/kernel/randomize_va_space