Assembly 在64位AMD上使用32位调用约定返回EFAULT的execve syscall

Assembly 在64位AMD上使用32位调用约定返回EFAULT的execve syscall,assembly,x86-64,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 ar

我在汇编中玩手动系统调用。我以前能够使它正确启动,但在删除空值后,我无法让系统调用执行
/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

尽管如此,系统调用不执行程序,并返回-14,这将转换为
EFAULT
(segfault)。我不确定我忽略了什么,任何帮助都将不胜感激


因此,敏锐的读者可能已经注意到,上述代码在64位系统上使用32位系统调用约定(使用
%ebx
int$0x80
和friends)。这是一个错误,因为32位约定仅支持执行32位代码。在为64位系统编写的代码中,系统调用使用
%rdi
%rsi
%rdx
%r10
%r8
%r9
以及
syscall
指令。以下是64位系统(无空)的更正代码:

但是,64位系统支持32位系统调用约定(因此可以运行32位可执行文件),我还成功地在该系统上使用32位调用约定执行了
execve
d其他命令。事实上,我为x86_64系统检查的大多数“外壳代码”都使用32位约定。因此,我的问题仍然存在:为什么32位调用约定在上述代码中不起作用?

execve()调用具有原型

int execve(const char *filename,
           char *const argv[],
           char *const envp[]);
请注意,通常不会传递
NULL
指针,但如果
envp[]
数组为空,则应传递指向NULL的指针,该指针用作环境变量的结尾。同样,
argv[]
也不能是空指针。它必须至少包含作为程序名的
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位)

我不理解你的堆栈操作。推、推、推、系统调用就足够了。我不明白为什么要直接操作堆栈指针

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

你回答了自己的问题:

32位约定仅支持执行32位代码

您也可以从64位代码调用它,这只是一个意外,内核不会检查。它希望调用源自32位代码,并且zero将带有参数的32位寄存器扩展到64位,因为接口仅使用32位寄存器。这是在(请记住,64位模式下的32位操作会自动清除寄存器的前32位):

因此,不能使用它来传递不适合32位的参数。请注意,
rcx
值为
0x7fffffe968
,因此将被截断为
0xfffffe968
,并最终导致
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"

据我所知,
argv
(或32位代码中的
rcx
)指向一个
char*
数组(注意代码后面的gdb输出)。我操纵堆栈指针以防止碰撞
/bin/date
字符串(在
jmp
/
调用之后由
rsp
指向)。但我几乎肯定忽略了一些东西;明天早上我会再看一遍。@BK康拉德:够了。我也很累,9个小时后会更仔细地看。
char *filename = "/bin/date";
char *argv [2] = {filename, NULL};
char *envp [1] = {NULL};

execve (filename, argv, envp);
char *filename = "/bin/date";

execve (filename, NULL);   // note missing third parameter, and malformed argv[]
/*
 * 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
.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"