bplinux
bplinux

Reputation: 23

shellcode is executed in two loops instead of one

i am succesfully build up my shellcode.

[BITS 64]

segment .text
    global _start

_start: jmp call

back:   lea rsi, [rsp]
    mov rdi, [rsi]
    xor rax, rax
    push rax
    lea rdx, [rsp]
    mov al, 0x3b
    syscall
call:   call back
    db "/bin/sh",0

But there is something mysterious about the code execution. I still can not figure it out whats the issue and maybe someone is able to help.

When i am debugging the code with radare2 the whole process of setting the arguments and syscalling is executed twice. I have no idea whats wrong with it. The registers, rip and so on looks good. In the end, the shell pops after the second loop. But for execution on stack it is a pain in the ass, because my /bin/sh screwed up in the second loop.

The first syscall returns -14 (BAD ADRESS)

I tried already some code variations and RTFM. Help

Thank you in advance :)

Upvotes: 1

Views: 135

Answers (1)

Margaret Bloom
Margaret Bloom

Reputation: 44046

Well, the argv array is malformed. You set that parameter to an address on the stack but you didn't put a null pointer to terminate it.

This is the (lower portion of) stack when the first instruction of _start is executed:

...
0
argN
...
arg0
argc <-- rsp

Note that these are the arguments of your program. Also note that <-- means "points to" (or: contains the address of).

When back is called the stack is:

...
0
argN
...
arg0
count
ptr to shell path <-- rsp

After the leas (by the way, lea with such a trivial addressing mode is just a mov) and the xor, the stack is:

...
0
argN
...
arg0
count
ptr to shell path <-- rsp, rsi
                  rdi = ptr to shell path

Then just before the syscall we have:

...
0
argN
...
arg0
count
ptr to shell path <-- rsi
                  rdi = ptr to shell path
0                 <-- rsp, rdx
            

Then execve will read the executable path from rdi (which checks), the envp from rdx (which checks), and the argv from rsi (which fails).
The last one fails because execve will read up the stack from the path of the shell to the first 0, but count is not a valid pointer (it is probably 1).

Having the system call failed, the execution falls through to call back, the steps are repeated but this time the 0 from the previous iteration will correctly terminate argv:

...
0
argN
...
arg0
count
ptr to shell path
0
ptr to shell path <-- rsi
                  rdi = ptr to shell path
0                 <-- rsp, rdx

You can:

  • Do nothing and leave it this way as a tricky way to invoke the shell (note that some value of count could be interpreted as a valid pointer, giving the shell an argument).
  • Change the count into a zero. You can move xor eax, eax up and use mov [rsp+8], rax. This works in other contexts as long as you can write at [rsp+8].
  • Push a zero yourself. Either pop the return address in a register and then push it after pushing a zero or use an xchg or similar.
  • Pass argv and envp as NULL (i.e. zero out rsi and rdx). On Linux this has the same effect as what you are already doing (passing an array with a single null pointer).
  • If you want to pass your program argument to the shell, you can double pop and then push the return address or pop the return address and use a mov to overwrite count.

Upvotes: 4

Related Questions