2

https://www.exploit-db.com/exploits/46907

global _start
section .text
_start:
    xor rsi,rsi
    push rsi
    mov rdi,0x68732f2f6e69622f
    push rdi
    push rsp
    pop rdi
    push 59
    pop rax
    cdq
    syscall

One instruction is push %rsp, which is followed by popping it in rdi. I am confused. According to the documentation, rdi in the execve syscall should contain the path name to the executable. What does it have to do with rsp here? Other shellcode don't manipulate rsp.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
user2370139
  • 1,297
  • 1
  • 11
  • 13
  • you're pushing the params to that api onto the stack. FILO – Dean Van Greunen Nov 12 '21 at 15:02
  • 2
    Constructing data on the stack, and then copying the stack pointer to another reg, is totally normal. Lots of shellcode manipulates RSP. `push rsp` / `pop rdi` is just a 2-byte version of `mov rdi, rsp`, and it comes after pushing a 0 and 8 bytes of ASCII data. Single-step the asm with a debugger and look at memory pointed-to by RDI when `syscall` executes, and work backwards to understand how you got there. – Peter Cordes Nov 12 '21 at 15:36

1 Answers1

2

Constructing data on the stack, and then copying the stack pointer to another reg, is totally normal. Lots of shellcode manipulates RSP.

push rsp / pop rdi is just a 2-byte version of mov rdi, rsp, and it comes after pushing a 0 and 8 bytes of ASCII data. (Which could have been written much more clearly in NASM syntax as mov rdi, '/bin//sh').

Single-step the asm with a debugger and look at memory pointed-to by RDI when syscall executes, and work backwards to understand how you got there.


It's weird that they golfed their mov rdi,rsp down to 2 bytes with push/pop but used a REX prefix on their xor-zeroing instruction. xor esi,esi is equivalent. NASM will optimize that source into xor esi,esi, but your link shows the disassembly.

Also, push 59 / pop rax is the standard 3-byte way to construct a small constant (__NR_execve) in a register without depending on any other register values. They could have done lea eax, [rsi+59] which is also 3 bytes and also avoids any 0 bytes. (5-byte mov eax, 59 would include an imm32 with three zero bytes, which is why most shellcode has to avoid it.)

cdq just sets RDX=0 (envp=NULL), since EAX is signed positive at that point. Another code-golfy way to save bytes vs. xor edx,edx. And they apparently knew about taking advantage of implicit zeroing of the full 64-bit reg when writing a 32-bit reg (EDX) in this case, so it's even more strange they used 64-bit xor-zeroing for RSI. Maybe they didn't know much about x86-64 and didn't even realize that cqo would explicitly use 64-bit operand-size, sign-extending RAX into RDX:RAX, and had intended to use 64-bit everywhere because they thought they needed to.

ecm
  • 2,583
  • 4
  • 21
  • 29
Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
  • I seen thank you for the details. So this implies that when pushing a register, the most significant bytes are pushed first, this way 2f (/) is the last one pushed on the stack, thus esp points to it, which is also consistent with endianness since the least significant byte will be at the lowest adress (stack grows towards lower adresses) – user2370139 Nov 12 '21 at 16:00
  • @user2370139: x86 is little-endian. A qword store puts the least-significant byte at the lowest address. This has nothing to do with the direction of stack-growth, that's just a coincidence. A `push` changes RSP and does a qword store to stack memory, just like any other store. All 8 bytes are written at the same time; an aligned qword store is [guaranteed to be atomic on x86](https://stackoverflow.com/questions/36624881/why-is-integer-assignment-on-a-naturally-aligned-variable-atomic-on-x86/36685056#36685056). – Peter Cordes Nov 12 '21 at 16:12
  • 1
    @Peter Cordes: Note that if you assemble eg `mov eax, "abcd"` then NASM and the MASM style assemblers will disagree on the order of the string bytes. NASM makes it so that `db "abcd"` and a store from `eax` after my `mov` result in the same data (61h, then 62h, then 63h, then 64h, little-endian dword 6463_6261h). (This also matches the order used by the strings in leaf 0 of `cpuid`.) MASM reverses the letter order for the `mov` so that "abcd" turns into 6162_6364h; the first (leftmost) letter is used as the first (leftmost, most significant) 8 bits of the number. – ecm Nov 12 '21 at 20:30
  • 1
    @ecm: Right, MASM (and GAS `.intel_syntax`) both suck at convenient use of ASCII strings as immediates. (There's a canonical Q&A about MASM vs. NASM vs. GAS for this: [When using the MOV mnemonic to load/copy a string to a memory register in MASM, are the characters stored in reverse order?](https://stackoverflow.com/a/57439043)). Before following the link in the question, I assumed this was hand-written NASM source (in which case what I said 100% applies: write it the simple way since you are writing in NASM syntax), but it may just have been generated automatically from machine code. – Peter Cordes Nov 13 '21 at 01:52