简体   繁体   English

Linux Shellcode “你好,世界!”

[英]Linux Shellcode “Hello, World!”

I have the following working NASM code:我有以下工作 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

which prints "Hello, World!\\n" to the screen.它将“Hello, World!\\n”打印到屏幕上。 I also have the following C wrapper which contains the previous NASM object code:我还有以下 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)();
}

However when I run the code, it seems like the assembler code isn't executed, but the program exits fine.但是,当我运行代码时,似乎没有执行汇编代码,但程序退出正常。 Any ideas?有任何想法吗?

Thanks谢谢

When you inject this shellcode, you don't know what is at message :当你注入这个 shellcode 时,你不知道message是什么:

mov ecx, message

in the injected process, it can be anything but it will not be "Hello world!\\r\\n" since it is in the data section while you are dumping only the text section.在注入的过程中,它可以是任何东西,但不会是"Hello world!\\r\\n"因为它在数据部分,而您只转储文本部分。 You can see that your shellcode doesn't have "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";

This is common problem in shellcode development, the way to work around it is this way:这是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

Now dump the text section:现在转储文本部分:

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

$

The lines I marked are our "Hello, World!\\r\\n" string:我标记的行是我们的"Hello, World!\\r\\n"字符串:

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

$ 

So our C wrapper will be:所以我们的 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;
}

Lets test it, using -z execstack to enable read-implies-exec ( process-wide, despite "stack" in the name ) so we can executed code in the .data or .rodata sections:让我们测试一下, 使用-z execstack来启用 read-implies-exec ( 进程范围内,尽管名称中有“stack” ),以便我们可以执行.data.rodata部分中的代码:

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

It works.有用。 ( -m32 is necessary, too, on 64-bit systems. The int $0x80 32-bit ABI doesn't work with 64-bit addresses like .rodata in a PIE executable. Also, the machine code was assembled for 32-bit. It happens that the same sequence of bytes would decode to equivalent instructions in 64-bit mode but that's not always the case.) -m32在 64 位系统上也是必需的.rodata int $0x80 32 位 ABI 不适用于 PIE 可执行文件中的.rodata等 64 位地址。此外,机器代码是为 32 位汇编的. 碰巧相同的字节序列会在 64 位模式下解码为等效的指令,但情况并非总是如此。)

Modern GNU ld puts .rodata in a separate segment from .text , so it can be non-executable.现代 GNU ld.rodata.text放在一个单独的段中,因此它可以是不可执行的。 It used to be sufficient to use const char code[] to put executable code in a page of read-only data.过去使用const char code[]将可执行代码放在只读数据页中就足够了。 At least for shellcode that doesn't want to modify itself.至少对于不想修改自己的 shellcode。

As BSH mentioned, your shellcode does not contain the message bytes.正如BSH 所提到的,您的 shellcode 不包含消息字节。 Jumping to the MESSAGE label and calling the GOBACK routine just before defining the msg byte was a good move as the address of msg would be on the top of the stack as return address which could be popped to ecx , where the address of msg is stored.在定义msg字节之前跳转到MESSAGE标签并调用GOBACK例程是一个很好的举措,因为 msg 的地址将作为返回地址位于堆栈顶部,该地址可以弹出到ecx ,其中存储了 msg 的地址.

But both yours and BSH 's code has a slight limitation.但是你的和BSH的代码都有一个轻微的限制。 It contains NULL bytes ( \\x00 ) which would be considered as end of string when dereferenced by the function pointer.它包含NULL bytes ( \\x00 ) ,当被函数指针取消引用时,这些NULL bytes ( \\x00 )将被视为字符串的结尾。

There is a smart way around this.有一个聪明的方法可以解决这个问题。 The values you store into eax, ebx and edx are small enough to be directly written into the lower nibbles of the respective registers in one go by accessing al, bl and dl respectively.您存储到eax, ebx and edx中的值足够小,可以通过分别访问al, bl and dl一次性直接写入相应寄存器的低半字节。 The upper nibble may contain junk value so it can be xored.上半字节可能包含垃圾值,因此可以进行异或运算。

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


becomes变成

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


Unlike the prior instruction set, the new instruction set does not contain any NULL byte.与之前的指令集不同,新的指令集不包含任何 NULL 字节。

So, the final program looks like this :所以,最终的程序是这样的:

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

Assembling and linking :组装和链接:

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

Now extract the shellcode from the hello binary :现在从 hello 二进制文件中提取 shellcode:

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

output:输出:

\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

Now we can have our driver program to launch the shellcode.现在我们可以让我们的驱动程序来启动 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;
}

There are certain security features in modern compilers like NX protection which prevents execution of code in data segment or stack.现代编译器中有某些安全功能,例如NX 保护,可防止在数据段或堆栈中执行代码。 So we should explicitly specify the compiler to disable these.所以我们应该明确指定编译器来禁用这些。

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

Now the launcher can be invoked to launch the shellcode.现在可以调用启动launcher来启动 shellcode。

$ ./launcher
 y0u sp34k 1337 ? $ 

For more complex shellcodes, there would be another hurdle.对于更复杂的 shellcode,还有另一个障碍。 Modern Linux kernels haveASLR or Address Space Layout Randomization You may need to disable this before your inject the shellcode, especially when it is through buffer overflows.现代 Linux 内核具有ASLRAddress Space Layout Randomization在注入 shellcode 之前,您可能需要禁用它,尤其是当它通过缓冲区溢出时。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM