【问题标题】:Linux Shellcode "Hello, World!"Linux Shellcode“你好,世界!”
【发布时间】:2013-03-13 15:49:59
【问题描述】:

我有以下可用的 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”打印到屏幕上。我还有以下 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)();
}

但是,当我运行代码时,似乎没有执行汇编代码,但程序退出正常。有什么想法吗?

谢谢

【问题讨论】:

    标签: c linux assembly nasm shellcode


    【解决方案1】:

    当你注入这个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    (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;
    }
    

    让我们测试一下,using -z execstack 以启用 read-implies-exec (process-wide, despite "stack" in the name),以便我们可以执行 .data.rodata 部分中的代码:

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

    它有效。 (-m32 在 64 位系统上也是必需的。int $0x80 32 位 ABI 不适用于 PIE 可执行文件中的 .rodata 等 64 位地址。此外,机器代码是为 32 -bit。在 64 位模式下,相同的字节序列会解码为等效指令,但情况并非总是如此。)

    现代 GNU ld.rodata.text 放在一个单独的段中,因此它可以是不可执行的。过去使用const char code[] 将可执行代码放入一页只读数据就足够了。至少对于不想修改自身的 shellcode。

    【讨论】:

    • 不知道为什么你没有得到任何支持,但这是一个很好的答案。感谢您的帮助。
    • @REALFREE 如果您使用的函数需要空终止字符串,如strcpy 等字符串函数,则空字节将是一个问题,它不会读取整个shellcode,字符串。否则没关系。
    【解决方案2】:

    正如BSH 提到的,您的shellcode 不包含消息字节。跳转到 MESSAGE 标签并在定义 msg 字节之前调用 GOBACK 例程是一个很好的举措,因为 msg 的地址将作为返回地址位于堆栈顶部,可以弹出到 ecx , msg 的地址存放的地方。

    但是你和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
    


    与之前的指令集不同,新的指令集不包含任何 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 二进制文件中提取 shellcode:

    $ 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
    

    现在我们可以让我们的驱动程序来启动 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 之前禁用此功能,尤其是当它通过缓冲区溢出时。

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

    【讨论】:

      猜你喜欢
      • 2021-11-19
      • 1970-01-01
      • 2015-01-13
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多