MOV RAX, 0x68732f6e69622f
CDQ
PUSH RAX
PUSH RSP
POP RDI
PUSH RDX
PUSH 0x632d
PUSH RSP
POP RSI
PUSH RDX
CALL FUN_0000001e
INSB RDI, DX
JNC FUN_0000001e
FUN_0000001e:
PUSH RSI
PUSH RDI
PUSH RSP
POP RSI
PUSH 0x3b
POP RAX
SYSCALL
These are the assembly instructions for the msfvenom payload:
msfvenom -p linux/x64/exec CMD='ls'
I understand how the execve systemcall is prepared, and /bin/sh -c passed as an argument. But how is this code executing the ls command ?
I tried executing this shellcode using c function pointer, and it executed ls as expected.
Single-step it in a debugger and watch the values on the stack change.
0x0068732f6e69622fis'/bin/sh'with a terminating 0 byte.push rsp/pop rdiis a 2-byte way to domov rdi, rsp.insb/jncdon't actually run, those are just data.insb's machine code is just0x6c; it's a "string" instruction that implicitly uses[rdi]post-incremented. IDK what asm syntax this is for, but it's not NASM. To get NASM to assemble it, I just removed the operands so it was plaininsb.If you assemble this and look at the machine code, you can see the bytes:
ASCII
6C 73 00is"ls"zero-terminated. That's the string whose address was pushed bycall.A normal person would write that as
db 0x6c, 0x73, 0ordb "ls", 0instead of "disassembling" those bytes into instruction mnemonics.Note that this "shellcode" is not free from
0bytes: a forwardcallwill have zeros in the high 3 bytes of therel32(the "return" address that is pushed, is the address of the ASCII bytes that follow it). Also,push 0x632dis a qword push of a sign-extended imm32 whose high 2 bytes are 0, since there's nopush wordsize override.And
jaewith rel8=0 of course has a zero byte.Two ways to make this work in NUL-free shellcode involve putting the
call+ 0-terminated string at the end of the payload, so it's calling backwards.jmp rel8to thecall, then jump backward to near where you came from with the string address on the stack. The call/pop trick is widely used in shellcode examples. Or in x86-64 code,lea rdi, [mystring + 0x11111111]/sub rdi, 0x11111111, but that takes more bytes of machine code.Other options include creating it from immediates like
mov eax, 'xxls';shr eax, 16/push rax, ormov eax, 'ls' ^ 0x11111111/xor eax, 0x11111111/push rax. (The same technique can work for the-cpart, or you might be able to cram parts of them into one immediate.) Or have the string bytes there in the payload with a non-zero placeholder, andmov byte [reg+whatever], alto store zero bytes where they need to go, from some register that you zeroed withxor eax, eaxor whatever. Or AH would already be zero if you materialized an 8-bit constant withpush 1/pop rax(3 bytes).msfvenom can make shellcode that voids zero bytes if you ask it to, so you can see what strategy it picks.