.global main
main:
call func
.string "/bin/sh"
func:
push %rsp
pop %rsi
pop %rdi
mov $0x00, %edx
mov $0x3b, %eax
syscall
I wrote assembly lagunage like above for execute /bin/sh
I compiled it but when I try to execute program, /bin/sh: 0: Can't open ????
this error is occur.
It doesn't execute /bin/sh. I want to know why I can't execute /bin/sh
I'm using Ubuntu 16.04 and x64 Architectures
Your code is needlessly hard to follow because of using push/pop in a weird way, but running your program under strace -f ./a.out
to trace the system calls shows:
... 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 +++
So on my system, execve
returns with an error, but the program exits successfully. IDK how you're getting /bin/sh: 0: Can't open ????
. Your question doesn't contain enough information to reproduce your results. But maybe when you tried, the stack happened to contain different garbage. I built it with gcc -g foo.S
.
After main
fails to return, execution falls through into whatever CRT function follows main
, which does end with a RET instruction. It must also zero eax, since it will be -EFAULT
after the SYSCALL.
Anyway, your asm is equivalent to this non-useful code:
int main(void) {
const char *p = "/bin/sh";
execve(p, &p, NULL);
}
note that push %rsp; pop %rsi
is equivalent to mov %rsp, %rsi
. So RSI holds a pointer to the stack memory where CALL wrote the "return address".
One more POP after that dereferences the stack pointer and loads the pointer to the string into RDI.
Using CALL to push the address of a string is really nasty. IDK why you'd do that. Just use MOV like a normal person.
How to do this correctly
# 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"
This works, and doesn't do anything weird.
Or, in a way that works as a position-independent flat binary, and doesn't use main()'s argv, since you added that request:
#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"
Your RSI=RSP idea is not bad, but you forgot to add a terminating NULL pointer to the end of the argv[]
array.
Outside of main
which gets envp[]
as an arg, we don't have an already-constructed envp[]
accessible anywhere convenient, so just pass NULL. On Linux, that's equivalent to passing a valid pointer to an empty NULL-terminated array, i.e. a pointer to an 8-byte 0
in memory.
C code:
#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 bit X86 assembly code:
// 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"
you can invoke the shell as:
./shell
or
./shell -c "echo hello;echo world"
With this code all parameters of the command "shell" will be passed to /bin/bash unchanged.
Also the process name will change and become "bash" (in ps -ef)
And what is more important (which I didn't see anywhere else)
is that with this code the environment is preserved. (no pun intended)
If you don't need the args and you don't want the env preserved, uncomment the comments and comment the previous lines.