int
is the opcode for triggering a software interrupt. Software interrupts are numbered (from 0 to 255) and handled by the kernel. On Linux systems, interrupt 128 (0x80) is the conventional entry point for system calls. The kernel expects the system call arguments in the registers; in particular, the %eax register identifies which system call we are talking about.
- Set %ebx to 0
- Compute %ebx+23 and store the result in %eax (the opcode is
lea
as "load effective address" but not memory access is involved; this is just a devious way of making an addition).
- System call. %eax contains 23, which means that the system call is
setuid
. That system call uses one argument (the target UID), to be found in %ebx, which conveniently contains 0 at that point (it was set in the first instruction). Note: upon return, registers are unmodified, except for %eax which contains the returned value of the system call, normally 0 (if the call was a success).
- Push %ebx on the stack (which is still 0).
- Push $0x68732f6e on the stack.
- Push $0x69622f2f on the stack. Since the stack grows "down" and since the x86 processors use little endian encoding, the effect of instructions 4 to 6 is that %esp (the stack pointer) now points at a sequence of twelve bytes, of values 2f 2f 62 69 6e 2f 73 68 00 00 00 00 (in hexadecimal). That's the encoding of the "//bin/sh" string (with a terminating zero, and three extra zeros afterwards).
- Move %esp to %ebx. Now %ebx contains a pointer to the "//bin/sh" string which was built above.
- Push %eax on the stack (%eax is 0 at that point, it is the returned status from
setuid
).
- Push %ebx on the stack (pointer to "//bin/sh"). Instructions 8 and 9 build on the stack an array of two pointers, the first being the pointer to "//bin/sh" and the second a NULL pointer. That array is what the
execve
system call will use as second argument.
- Move %esp to %ecx. Now %ecx points to the array built with instructions 8 and 9.
- Sign-extend %eax into %edx:%eax.
cltd
is the AT&T syntax for what the Intel documentations call cdq
. Since %eax is zero at that point, this sets %edx to zero too.
- Set %al (the least significant byte of %eax) to 11. Since %eax was zero, the whole value of %eax is now 11.
- System call. The value of %eax (11) identifies the system call as
execve
. execve
expects three arguments, in %ebx (pointer to a string naming the file to execute), %ecx (pointer to an array of pointers to strings, which are the program arguments, the first one being a copy of the program name, to be used by the invoked program itself) and %edx (pointer to an array of pointers to strings, which are the environment variables; Linux tolerates that value to be NULL, for an empty environment), respectively.
So the code first calls setuid(0)
, then calls execve("//bin/sh", x, 0)
where x
points to an array of two pointers, first one being a pointer to "//bin/sh", while the other is NULL.
This code is quite convoluted because it wants to avoid zeros: when assembled into binary opcodes, the sequence of instruction uses only non-zero bytes. For instance, if the 12th instruction had been movl $0xb,%eax
(setting the whole of %eax to 11), then the binary representation of that opcode would have contained three bytes of value 0. The lack of zero makes that sequence usable as the contents of a zero-terminated C string. This is meant for attacking buggy programs through buffer overflows, of course.