【问题标题】:Linux 64-bit shellcodeLinux 64 位 shellcode
【发布时间】:2015-01-05 13:39:26
【问题描述】:

我正在尝试在我的 64 位 Ubuntu 上编写我的第一个“Hello world”shellcode,但它不起作用。

我有文件 hello.asm

; 64-bit "Hello World!" in Linux NASM

global _start            ; global entry point export for ld

section .text
_start:

    ; sys_write(stdout, message, length)

    mov    rax, 1        ; sys_write
    mov    rdi, 1        ; stdout
    mov    rsi, message    ; message address
    mov    rdx, length    ; message string length
    syscall

    ; sys_exit(return_code)

    mov    rax, 60        ; sys_exit
    mov    rdi, 0        ; return 0 (success)
    syscall

section .data
    message: db 'Hello, world!',0x0A    ; message and newline
    length:    equ    $-message        ; NASM definition pseudo-instruction

我使用了这个命令:

nasm -felf64 hello.asm -o hello.o
ld -o hello hello.o
objdump -d hello

我将来自 objdump 的 shellcode 放入我的 C 程序中:

char code[] = "\xb8\x01\x00\x00\x00\xbf\x01\x00\x00\x00\x48\xbe\xd8\x00\x60\x00\x00\x00\x00\x00\xba\x0e\x00\x00\x00\x0f\x05\xb8\x3c\x00\x00\x00\xbf\x00\x00\x00\x00\x0f\x05";

int main(int argc, char **argv)
{
    int (*func)();
    func = (int (*)()) code;
    (int)(*func)();
    return 0;
}

并在 gcc 中编译它,但运行后出现“分段错误(核心转储)”。

我不知道我做错了什么。汇编代码似乎可以工作,因为当我运行 ./hello 时,它会打印“Hello world”。

【问题讨论】:

  • .data 部分(您的代码结束的地方)默认情况下不可执行。此外,您应该确保您的代码与位置无关。
  • 在 x86_64 上,您确定这是有效的:length: equ $-message

标签: assembly nasm shellcode


【解决方案1】:

您可以简单地使用nasm -felf64 编译并使用ld 链接。不需要gcc。我相信您偶然发现的问题是代码中的x86 样式equ $-message 计算。对于 x86_64,这是无效的。这是一个简单的例子:

section .data
    string1 db  0xa, "  Hello StackOverflow!!!", 0xa, 0xa, 0

section .text
    global _start

    _start:
        ; calculate the length of string
        mov     rdi, string1        ; string1 to destination index
        xor     rcx, rcx            ; zero rcx
        not     rcx                 ; set rcx = -1
        xor     al,al               ; zero the al register (initialize to NUL)
        cld                         ; clear the direction flag
        repnz   scasb               ; get the string length (dec rcx through NUL)
        not     rcx                 ; rev all bits of negative results in absolute value
        dec     rcx                 ; -1 to skip the null-terminator, ecx contains length
        mov     rdx, rcx            ; put length in rdx
        ; write string to stdout
        mov     rsi, string1        ; string1 to source index
        mov     rax, 1              ; set write to command
        mov     rdi,rax             ; set destination index to rax (stdout)
        syscall                     ; call kernel

        ; exit
        xor     rdi,rdi             ; zero rdi (rdi hold return value)
        mov     rax, 0x3c           ; set syscall number to 60 (0x3c hex)
        syscall                     ; call kernel

编译/输出

$ nasm -felf64 -o hello-stack_64.o hello-stack_64.asm
$ ld -o hello-stack_64 hello-stack_64.o
$ ./hello-stack_64

  Hello StackOverflow!!!

【讨论】:

  • 问题是:(a)不可执行的.data部分; (b) mov rsi, message 中的绝对寻址。 equ $-message 也适用于 x64。
  • 你能解释一下string1中的0xa是什么意思吗?
  • 当然0xa 只是十六进制10 这是'\n'(换行符)字符:) 的ASCII 值(这是在字符串中添加换行符的方式)
【解决方案2】:

这里有两层问题。

1) char data[] = ... 将被放入数据段,不可执行。您需要 const char data[] = ... 以便将其放在文本段中。 (从技术上讲,它将在 只读数据 段中,可以标记为不可执行,但据我所知目前没有人这样做。)

2) 这是我反汇编data的内容得到的:

   0:   b8 01 00 00 00          mov    eax, 0x1
   5:   bf 01 00 00 00          mov    edi, 0x1
   a:   48 be d8 00 60 00 00    movabs rsi, 0x6000d8
  11:   00 00 00 
  14:   ba 0e 00 00 00          mov    edx, 0xe
  19:   0f 05                   syscall 
  1b:   b8 3c 00 00 00          mov    eax, 0x3c
  20:   bf 00 00 00 00          mov    edi, 0x0
  25:   0f 05                   syscall 

这就是我尝试运行它时得到的结果:

$ strace ./a.out
execve("./a.out", ["./a.out"], [/* 38 vars */]) = 0
write(1, 0x6000d8, 14)                  = -1 EFAULT (Bad address)
_exit(0)                                = ?
+++ exited with 0 +++

您可以看到这与您想要的相差不远,但它显然不起作用,绝对地址0x6000d8 处没有任何内容。问题是您只将 code 放入data,而不是字符串。您需要将字符串紧跟在代码之后,并使用 PC 相对寻址来获取它。 nighter 的回答显示了这是如何完成的(在data 中可能不会出现\x00 字节的附加约束条件下,如果您实际上在这里利用典型的缓冲区溢出而不是仅仅使用机器语言,这将是必要的)。

【讨论】:

    【解决方案3】:

    如果您想将空字节注入缓冲区,最好删除空字节,但主要问题可能是数据段中有字符串?

    我像这样重写它并让它工作。

        global _start
        _start:
        jmp short string
    
        code:
        pop rsi
        xor rax, rax
        mov al, 1
        mov rdi, rax
        mov rdx, rdi
        add rdx, 14
        syscall
    
        xor rax, rax
        add rax, 60
        xor rdi, rdi
        syscall
    
        string:
        call code
        db  'Hello, world!',0x0A
    

    $ nasm -felf64 hello.asm -o hello.o
    $ ld -s -o hello hello.o
    $ for i in $(objdump -d hello |grep "^ " |cut -f2); do echo -n '\x'$i; done; echo
    \xeb\x1e\x5e\x48\x31\xc0\xb0\x01\x48\x89\xc7\x48\x89\xfa\x48\x83\xc2\x0e\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xdd\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x20\x77\x6f\x72\x6c\x64\x21\x0a
    

    char code[] = "\xeb\x1e\x5e\x48\x31\xc0\xb0\x01\x48\x89\xc7\x4\x89\xfa  \x48\x83\xc2\x0e\x0f\x05\x48\x31\xc0\x48\x83\xc0\x3c\x48\x31\xff\x0f\x05\xe8\xdd\xff\xff\xff\x48\x65\x6c\x6c\x6f\x2c\x20\x77\x6f\x72\x6c\x64\x21\x0a";
    
    int main(int argc, char **argv)
    {
        int (*func)();
        func = (int (*)()) code;
        (int)(*func)();
         return 0;
    }
    

    $ gcc -fno-stack-protector -z execstack -o code code.c    
    $./code
    Hello, world!
    

    【讨论】:

    • 这几乎是正确的,但并不完全正确。它应该是const char code[](那么你就不需要-fno-stack-protector -zexecstack);并且 add rdx, 14 应该是 add rdx, 12 以不在字符串末尾写两个。
    • 由于代码不在栈中,所以不需要-fno-stack-protector,只需要-z execstack。请注意,由于-z execstack 而具有可执行数据部分是一种Linux 特定的行为,详细描述了here。它在 FreeBSD 上不起作用,即 -z execstack 使 .data 在该操作系统上不可执行。
    【解决方案4】:

    希望回答这个问题还为时不晚:)

    您必须使用此代码编译您的 C 代码(禁用堆栈保护并使其可执行):

    gcc -fno-stack-protector -z execstack -o hello hello.c
    

    祝你好运

    【讨论】:

    • 代码不在堆栈上,而是在数据段中,因此您的答案不适用。
    • @HristoIliev 我已经多次编写并执行了我的 shellcode。这是因为堆栈保护,与数据段无关。请先测试您的想法,然后发表评论..
    • 不,不是因为堆栈保护。这是由于code 符号所在的不可执行的.data 部分。 -z execstackGNU_STACK 段标记为可执行,这具有同时使数据段可执行的副作用。没有-fno-stack-protector,代码可以正常工作。此外,使code[] const 将其从.data 部分移动到.rodata 部分,后者无论如何都在可执行段中,因此不需要-z execstack
    • 更多关于为什么可执行堆栈也使数据段可执行的技术解释请参见here。请注意,此行为特定于 Linux 内核,并且不适用于例如FreeBSD 具有可执行堆栈不会影响默认内存保护策略,.data 仍然不可执行。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-05
    • 1970-01-01
    • 2022-06-24
    • 1970-01-01
    • 2011-11-19
    • 2012-07-17
    • 1970-01-01
    相关资源
    最近更新 更多