execve系统调用在64位AMD上使用32位调用约定返回EFAULT

时间:2012-06-10 01:32:00

标签: assembly x86-64

我正在使用汇编中的手动系统调用。我之前能够正确启动它,但在删除空值后,我无法让系统调用执行/bin/date。这是我用AT& T语法编写的代码。

.global main
main:
    jmp     two

one:
    # zero rax and rdx
    xor     %rax,%rax 
    mov     %rax,%rdx

    # save string location
    mov     (%rsp),%rbx

    # push argv array onto the stack
    add     $16, %rsp
    push    %rax
    push    %rbx
    # assign argv pointer
    mov     %rsp,%rcx

    # execve call
    mov     $0xb, %al 
    int     $0x80

    # exit on failure
    xor     %rax,%rax
    xor     %rbx,%rbx
    movb    $0x1,%al
    int     $0x80

two:
    # get address of the string
    call    one
    .string "/bin/date"

如果我是对的,%rbx应直接指向要启动的程序命名的字符串。 %rcx应该指向一个以null结尾的指针数组,表示程序的argv,而%rdx指向环境,所以我在这里将它保留为null。当然,%rax包含系统调用号(在这种情况下为0x0b)。

(gdb) info registers 
rax            0xb  11
rbx            0x4000a0 4194464
rcx            0x7fffffffe968   140737488349544
rdx            0x0  0

(gdb) x/s $rbx
0x4000a0:    "/bin/date"

(gdb) x/s *$rcx
0x4000a0:    "/bin/date"

尽管如此,系统调用不执行程序,并返回-14,转换为EFAULT(segfault)。我不确定我会忽略什么,任何帮助都会受到赞赏。


因此,有识之士可能已经注意到上述代码在64位系统上使用32位系统调用约定(使用%ebxint $0x80和朋友)。这是一个错误,因为32位约定仅支持执行32位代码。在为64位系统编写的代码中,系统调用使用%rdi%rsi%rdx%r10%r8%r9以及{{ 1}}指令。以下是64位系统的校正代码(nullfree):

syscall

但是,64位系统支持32位系统调用约定 <(因此可以运行32位可执行文件),而且我还使用32位调用约定成功.global main main: jmp two one: # zero rax and rdx xor %rax,%rax mov %rax,%rdx # save string location, note that %rdi is used instead of %rbx pop %rdi # push argv array onto the stack add $16, %rsp push %rax push %rdi # assign argv pointer, using %rsi instead of %rcx mov %rsp,%rsi # execve call, note that the syscall number is different than in 32bit mov $0x3b, %al syscall two: # get address of the string call one .string "/bin/date" d其他命令系统。实际上,我为x86_64系统检查的绝大多数“shellcode”都使用了32位约定。所以,我的问题仍然存在:为什么32位调用约定在上面的代码中不起作用?

2 个答案:

答案 0 :(得分:2)

execve()调用具有原型

int execve(const char *filename,
           char *const argv[],
           char *const envp[]);

请注意,NULL指针通常不会传递,但如果envp[]数组为空,则应将指针传递给NULL,该指针用作环境变量的结尾。同样,argv[]不能是NULL指针。它必须至少包含argv[0]这是程序名称。将filename作为第一个元素通常就足够了。要完全符合shell的作用,请删除路径并仅将最后一个组件传递为argv [0](在示例中为date)。

您的示例的等效C代码是:

char *filename = "/bin/date";
char *argv [2] = {filename, NULL};
char *envp [1] = {NULL};

execve (filename, argv, envp);

也许我错过了它,但看起来你做的相当于(32位和64位)

char *filename = "/bin/date";

execve (filename, NULL);   // note missing third parameter, and malformed argv[]

我不明白你的堆栈操作。它应该足以推,推,推,系统调用。我很困惑为什么你直接操纵堆栈指针。

答案 1 :(得分:1)

  

为什么32位调用约定在上面的代码中不起作用?

你回答了自己的问题:

  

仅支持32位约定以启用执行32位代码。

您只能意外地从64位代码调用它,内核不会检查。它期望调用源自32位代码,并且零扩展32位寄存器,其参数为64位,因为接口仅使用32位寄存器。这是在ia32entry.S中完成的(请记住,64位模式下的32位操作会自动清除寄存器的前32位):

/*
 * Emulated IA32 system calls via int 0x80.
 *
 * Arguments:
 * eax  system call number
 * ebx  arg1
 * ecx  arg2
 * edx  arg3
 * esi  arg4
 * edi  arg5
 * ebp  arg6    (note: not saved in the stack frame, should not be touched)
 *
 * Notes:
 * Uses the same stack frame as the x86-64 version.
 * All registers except eax must be saved (but ptrace may violate that).
 * Arguments are zero extended. For system calls that want sign extension and
 * take long arguments a wrapper is needed. Most calls can just be called
 * directly.
 * Assumes it is only called from user space and entered with interrupts off.
 */
[...]    
ia32_do_call:
    /* 32bit syscall -> 64bit C ABI argument conversion */
    movl %edi,%r8d  /* arg5 */
    movl %ebp,%r9d  /* arg6 */
    xchg %ecx,%esi  /* rsi:arg2, rcx:arg4 */
    movl %ebx,%edi  /* arg1 */
    movl %edx,%edx  /* arg3 (zero extension) */
    call *ia32_sys_call_table(,%rax,8) # xxx: rip relative

因此,您无法使用它来传递不适合32位的参数。请注意,您的rcx值为0x7fffffffe968,因此会被截断为0xffffe968并最终导致EFAULT

rcx当然是从rsp设置的,典型的64位地址空间使堆栈从地址空间的正半部分的顶部向下增长,并且超出了32位范围。为了证明这一点,您可以切换到位于低内存中的堆栈。以下代码工作正常:

.lcomm stack, 4096
.global main
main:
    movl    $stack, %esp
    jmp     two

one:
    # zero rax and rdx
    xor     %rax,%rax
    mov     %rax,%rdx

    # save string location
    mov     (%rsp),%rbx

    # push argv array onto the stack
    add     $16, %rsp
    push    %rax
    push    %rbx
    # assign argv pointer
    mov     %rsp,%rcx

    # execve call
    mov     $0xb, %al
    int     $0x80

    # exit on failure
    xor     %rax,%rax
    xor     %rbx,%rbx
    movb    $0x1,%al
    int     $0x80

two:
    # get address of the string
    call    one
    .string "/bin/date"