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.