我正在使用汇编中的手动系统调用。我之前能够正确启动它,但在删除空值后,我无法让系统调用执行/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位系统调用约定(使用%ebx
,int $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位调用约定在上面的代码中不起作用?
答案 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"