user4929293 user4929293 - 1 year ago 107
Linux Question

Ubuntu 16.04 assembly code for shell

.global main
call func
.string "/bin/sh"
push %rsp
pop %rsi
pop %rdi
mov $0x00, %edx
mov $0x3b, %eax

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

Answer Source

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 -g shell.S

#include <asm/unistd.h>         // for __NR_execve                                                                                                                
// #include <sys/syscall.h>     // or include this glibc header for SYS_execve

    mov   $shell, %edi     # filename = shell
                           # argv     = main's argv, already in rsi (not on the stack like in _start)
    xor   %edx, %edx       # envp     = NULL
    mov   $__NR_execve, %eax    # execve(
    ret                    # in case the syscall fails

.section .rodata
.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
    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(
    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 ARGV.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download