1

Here's my shellcode:

    xor rax, rax
    xor rdi, rdi
    xor rsi, rsi
    xor rdx, rdx

    ; getting address of `pathname`
    mov rdi, [something]
    mov al, 59
    mov rdi, rsp
    syscall

    xor rax,rax
    mov al, 60
    xor rdi, rdi
    syscall

_pathname:
    db "/usr/bin/shutdown",0

Simple execve and exit, that's all. The problem is that I can't get the absolute address in order to access pathname. The "solution" to this, that I have found, is rigging the code something like this:

call _end

_start:
    pop rdi ; absolute address of _start
    ; then add bytes to get to address of _end

_end:
    call start
    db "/usr/bin/shutdown", 0

This never made sense to me, and it didn't work. I also tried using FPU instructions, which were supposed to work as well, but they didn't work either.

Anybody have a solution?

OS: Linux
Architecture: amd64

1 Answer 1

3

On x86-64 you have RIP-relative addressing modes, so you should be able to simply do lea rdi, [rel _pathname]. The assembler computes the displacement between the address of the current instruction (or actually the next one) and the label _pathname, and encodes it into the instruction. At runtime this displacement is added to rip and the result placed in rdi.

x86-32 doesn't have relative addressing modes, so in 32-bit mode, your call/pop trick is the standard way to accomplish this, despite being awkward. And it does work in 64-bit mode too. But the address popped by pop rdi isn't the address of _start; rather it's the return address pushed by the call _start instruction, i.e. the address of the byte following the call _start instruction. In this case it is exactly the first byte of your /usr/bin/shutdown string, so in fact you wouldn't have to add anything.

Note that you are passing a null pointer as the argv and envp arguments to execve. This is legal for the kernel, but the shutdown program is probably not expecting this and it may crash upon startup. So you might have to do some more work to construct a minimal argument vector with argv[0] == "shutdown" at least.

Sign up to request clarification or add additional context in comments.

6 Comments

Note that shellcode needs to be free of 00 bytes, so [RIP+rel32] is only usable with negative displacements (or with some large constant added that you subtract from the final address, like lea rdi, [rel foo + 0x01010101] / sub rdi, 0x01010101 as in Avoiding 0xFF bytes in shellcode using CALL to read RIP?) Without trickery, this means your code has to be after the string so you need to jmp over it, and it can't have a terminating 0.
The jmp/call/pop trick is more compact, and the backwards call makes the rel32 negative. (The OP used call _end instead of jmp _end which breaks this trick because there's no call rel8; the forward call will have zeros in the call rel32 encoding.)
@NateEldredge execve("\1", NULL, NULL) = -1 ENOENT (No such file or directory) from strace when using lea rdi, [rel _pathname + 0x69696969] and sub rdi, 0x69696969. Any more wisdom?
@R-Rothrock: It works for me. You did remember to remove the mov rdi, rsp right? Otherwise please post a minimal reproducible example of the code you are testing.
@R-Rothrock: "\1" as the path could be the result of doing mov rdi, rsp while RSP was still pointing to argc (which it does in the Linux process-startup state.) BTW, _start is a very bad choice of label name if you're going to put code ahead of it. If you did global _start, the linker would set the process entry point to _start, so execution would start there, instead of at the top of the text section (ld's default if there's no exported _start symbol.)
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.