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

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

谢谢

4

2 回答 2

78

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

让我们测试它,使用-z execstack启用 read-implies-exec (进程范围,尽管名称中有“堆栈”),以便我们可以在.dataor.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[]将可执行代码放入一页只读数据中。至少对于不想修改自身的 shellcode。

于 2013-03-29T14:03:38.067 回答
21

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

但是您和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 保护,可防止在数据段或堆栈中执行代码。所以我们应该明确指定编译器来禁用这些。

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

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

$ ./launcher
 y0u sp34k 1337 ? $ 

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

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