.global main
main:
call func
.string "/bin/sh"
func:
push %rsp
pop %rsi
pop %rdi
mov $0x00, %edx
mov $0x3b, %eax
syscall
我编写了如上所述的汇编lagunage for execute / bin / sh我编译了它但是当我尝试执行程序时,/bin/sh: 0: Can't open ????
发生了这个错误。它不执行/ bin / sh。我想知道为什么我不能执行/ bin / sh
我正在使用Ubuntu 16.04和x64架构
由于以奇怪的方式使用push / pop,你的代码很难被遵循,但是在strace -f ./a.out
下运行程序来跟踪系统调用显示:
... dynamic linker and libc init stuff before main() is called ...
execve("/bin/sh", ["/bin/sh", "\211\307\350\t\222\1", "\367", "\367", 0x100000000, "\350\10"], [/* 0 vars */]) = -1 EFAULT (Bad address)
exit_group(0) = ?
+++ exited with 0 +++
所以在我的系统上,execve
返回错误,但程序退出成功。 IDK你怎么得到/bin/sh: 0: Can't open ????
。您的问题未包含足以重现结果的信息。但也许当你尝试时,堆栈碰巧包含不同的垃圾。我用gcc -g foo.S
建造它。
在main
无法返回之后,执行将进入main
之后的任何CRT函数,该函数以RET指令结束。它也必须为零eax,因为在SYSCALL之后它将是-EFAULT
。
无论如何,你的asm相当于这个无用的代码:
int main(void) {
const char *p = "/bin/sh";
execve(p, &p, NULL);
}
请注意,push %rsp; pop %rsi
相当于mov %rsp, %rsi
。所以RSI持有一个指向堆栈内存的指针,其中CALL写了“返回地址”。
之后再一个POP取消引用堆栈指针并将指向字符串的指针加载到RDI中。
使用CALL来推送字符串的地址真的很讨厌。 IDK你为什么那样做。只需像正常人一样使用MOV。
如何正确地做到这一点
# build with gcc -no-pie -g shell.S
#include <asm/unistd.h> // for __NR_execve
// #include <sys/syscall.h> // or include this glibc header for SYS_execve
main: # main(argc, argv, envp)
mov $shell, %edi # filename = shell
# argv = main's argv, already in rsi (not on the stack like in _start)
# leave RDX = envp
# xor %edx, %edx # envp = NULL
mov $__NR_execve, %eax # execve
syscall # execve(shell, argv, envp)
ret # in case the syscall fails
.section .rodata
shell:
.string "/bin/sh"
这有效,并没有做任何奇怪的事情。
或者,以一种与位置无关的平面二进制文件的方式,并且不使用main()的argv,因为您添加了该请求:
#include <asm/unistd.h> // for __NR_execve
// #include <sys/syscall.h> // or include this glibc header for SYS_execve
.globl main
main:
lea shell(%rip), %rdi # filename = shell
xor %edx, %edx # envp = NULL
push %rdx # or push $0
push %rdi
mov %rsp, %rsi # argv = { $shell, NULL } that we just pushed
mov $__NR_execve, %eax # execve(
syscall
ret # in case the syscall fails
shell: # still part of the .text section, and we don't use the absolute address of the label, only for a RIP-relative LEA.
.string "/bin/sh"
你的RSI = RSP的想法还不错,但你忘了在argv[]
数组的末尾添加一个终止NULL指针。
在使用main
作为arg的envp[]
之外,我们没有在任何方便的地方可以访问已经构造的envp[]
,所以只需传递NULL。在Linux上,这相当于将有效指针传递给空的NULL终止数组,即指向内存中8字节0
的指针。
C代码:
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <stdio.h>
#include <unistd.h>
int main(int argc, char **argv, char **env)
{
argv[0] = "/bin/bash";
return execve(argv[0], argv, env);
}
64位X86汇编代码:
// gcc shell.S -Wl,--build-id=none -static -nostdlib -fPIC -o shell
// strip -s shell
#include <asm/unistd.h>
.global _start
.text
_start:
mov (%rsp), %rbx # rbx = argc
mov $__NR_execve, %rax # system call 3b is execve()
lea sh(%rip), %rdi # command to execute
lea 8(%rsp), %rsi # argv
# xor %rsi, %rsi # no args
lea 16(%rsp, %rbx, 8), %rdx # envp !!!
# xor %rdx, %rdx # no envp
syscall # invoke system call
# exit(0)
mov $__NR_exit, %rax # system call 60 is exit
xor %rdi, %rdi # we want return code 0
syscall # invoke operating system to exit
sh:
.string "/bin/bash"
你可以调用shell作为:
./shell
要么
./shell -c "echo hello;echo world"
使用此代码,命令“shell”的所有参数都将传递给/ bin / bash不变。
此过程名称也将更改并变为“bash”(在ps -ef中)
更重要的是(我没有在其他任何地方看到)
是这个代码保留了环境。 (没有双关语)
如果您不需要args并且不希望保留env,请取消注释注释并注释前面的行。