4

我在汇编中使用手动系统调用。我之前能够使其正常启动,但在删除空值后,我无法让系统调用执行/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应该指向一个以空结尾的代表程序的指针数组argv,并%rdx指向环境,所以我把它留空了。当然,%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 位系统调用约定(使用%ebx,和朋友)。int $0x80这是一个错误,因为仅支持 32 位约定以启用执行 32 位代码。在为 64 位系统编写的代码中,系统调用使用、%rdi%rsi%rdx%r10以及%r8指令。这是 64 位系统的更正代码(nullfree):%r9syscall

.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"

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

4

2 回答 2

2

execve() 调用有原型

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

请注意,NULL通常不传递指针,但如果envp[]数组为空,则应将指针传递给作为环境变量结尾的 NULL。同样,argv[]不能是 NULL 指针。它必须至少包含argv[0]程序名称。通常放置filename为第一个元素就足够了。为了完全符合外壳的功能,剥离路径并仅将最后一个组件作为 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[]

我不明白你的堆栈操作。推送、推送、推送、系统调用应该足够了。我很困惑为什么你直接操作堆栈指针。

于 2012-06-13T06:12:10.067 回答
1

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

你是在自问自答:

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

您也可以从 64 位代码调用它只是偶然的,内核不会检查。它期望调用源自 32 位代码,并且由于接口仅使用 32 位寄存器,因此零将带有参数的 32 位寄存器扩展为 64 位。这是在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值 is 0x7fffffffe968which 将因此被截断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"
于 2015-04-17T13:40:24.413 回答