Shellcode are the basic building blocks of performing exploits that spawn a shell.
What is a shellcode?
Shellcode in layman terms is any injected code that can be used to spawn a command shell. The kernel part of any operating system interacts with the registers and uses various system calls to functionality. Shellcode is inherently written in low level assembly langugae following certain rules like no hardcoded address, and no nulls "0x00" etc. The assembler "assembles" and "links" this machine code and produces a hexcode output, which can be then be executed as an executable.
What if the execute instruction (.text section) of such an executable is formatted say, as a string and then cast as a function pointer in some high level language such as C. What happens when the function gets called? If this function is then called it executes the string (machine code). What if a simple hello world prog in C contains the formatted execuatble disguised as a string, and spawns a TCP shell listening on a port enabling a backdoor to an attacker? The possibilities are endless, and shellcode exploits are still common and widely used against vulnerable systems.
Adding long descriptive comments in code to help people understand code easily, and also me in case I forget useful stuff. shell-storm
Refer to my Exploits repo for some binary exploitation links and guides Eg: Protostar aka now Exploit education's Phoenix VM
There are certain key differences between Shellcode and StandAlone assembly programs. Due to these, the assembly program that performs perfectly well when assembled and run as an executable, fails when converted as a shellcode. Shellcode has to adhere to the following constraints.
- Position independent code
The most crucial feature to understand that your code must be position independent. In layman terms, what this means is you cannot have any hardcoded address values in your assembly code. Only .text section of your assembly code is valid, so the variables in data section will not be considered in the shellcode. Hence any strings or hardcoded variables have to be placed on the stack. There are two techniques, primarily to achieve this.
-
JMP-CALL-POP
This technique primarily involves placing the address of the string/variable in just after a call instruction. When CALL is executed , as we know it places the next instruction onto the stack( When RET is called this is set to EIP ) After the address of the string is pushed , it could then be used to refer to the string variable
- STACK-TECHNIQUE
We can also push the variable/string onto the stack (in reverse ), and then use the string directly from the stack.
-
JMP-CALL-POP
- Global entry point not defined
The program starts top down, and _start ceases to hold meaning. The program starts from the first instruction in the text section
- Cannot have NULL and certain bad characters
Depending upon the context that the shellcode is placed onto, there are certain characters which cannot be in the shellcode, as the host application might treat them as bad chars and trim the shellcode off. These characters could be 0xa, 0xd, but the most common one is the null which is 0x00.
- Size constraints
Since the shellcode is often delivered as a part of a payload, it has to be optimized to be as small as possible. Also, if the intent is to bypass anti-virus softwares,certain shellcode must have a few useless operations to beat pattern detection tools.
The assembly code has been written for x86-64 architecture Intel, and are shellsafe, i.e do not contain a null or 0x00. In Linux , IA-32 and x86-64 we gdb (GNU debugger) is a great way to debug assembly code, and it certainly helps me a lot to use the text interface of the gnu (gnu -q exec.s -tui). For editing assembly code, it helps to use vim which is very powerful.
-
execShell.asm
An execShell is a shellcode which when run, spawns a shell. In this case we spawn a bourne again shell, popularly known as bash shell. This will involve a system calls to spawn execute a program. You can look systemcalls in the unistd.h or unistd file depending upon your distro. Otherwise, it is handy to bookmark this link for easy reference. The method used here is relative addressing mode which is supported only in x64 architectures, and is a very handy tool.
Syscall List x86-64 We are interested in the syscall number no 0x59. The procedure for calling a syscall in x86-64 is simply syscall. In IA-32 architectures we need to us int $0x80. -
jcpexecShell.asm
This shellcode follows the JMP-CALL-POP technique discussed above. The code contains hints for each place. We must place them in the said order and I already asked this question in stackoverflow, you can find it here. JMP-CALL-POP order
-
stackexecShell.asm
This shellcode follows the stack technique, and pushes the shell string onto the stack and accessing it from there. It is easier to handle longer strings with JMP-CALL-POP technique howevever.
-
tcpBindShell.asm
A tcp bind shell is inheritently which spawns an execshell and binds it to a TCP socket. Now what does bind mean? It means that the input/output of the spawned shell are replaced by the input output stream of the TCP socket. Envision a simple TCP chat server, however the client sends chat messages to the server's side spawned shell. The client will have the same user level access on the shell as the user spawning the shell. The victim (the server here) executes the shellcode, and inadvertently spawns a TCP shell on listening on the port 4444 (this port has a history) which could be in practicality any open port. The attacker then connects to the port over the network and then gains access to the shell. The stack technique has been used to make this code shellsafe.
There is one additional feature that has been implemented as part of the code is a passcode that will be passed and verified before the access to the shell is given. In practice this has just the utility of demonstrating that additional operations may be performed before spawning the shell. Also, it provides a basic security feature.
-
tcpReverseShell.asm
A reverse shell spawns a shell over TCP but instead of the victim listening, the victim connects to a listening service and spawns a shell and replaces whatever it receives from the server onto the shell.This too has a password protected reverse shell. Inherently a reverse shell are harder to monitor as they may be spawned from any port and difficult to block and avoid. Also reverse shells are not services that must be continuously running to support this attack.
Shellcode can be generated as follows. Since the assembly is written for Linux x86-64 arch, we will use the NASM assembler. If nasm is not installed on the system, install it from from the respective Linux repo.
nasm -felf64 -o myFile.o myFile.asmWe add -N to be able to rewrite the stack. This operation is useful where we are writing on to the stack memory. Examples are operations such as below. We are moving a value stored in eax to the address value represented by rsp incremented by 1.
mov dword [rsp+1], eax
ld -o myFile.o myFile.s -N
Now we need to generate the hex machine code that can be placed inside a Cfile. To do this we will use a linux utility called objCopy. There are some buggy converters on shellstorm, so make it a habit to do the below for getting the shellcode.
objcopy -j.text -O binary execShell execShell.bin hexdump -v -e '"\\""x" 1/1 "%02x" ""' execShell.binOnce this shell is copied we can inject it to the buffer to be used by the C code for injection as under. The C file for using the assembly as shell is as below.
#include<stdio.h> #include<string.h> /* Machine code to be injected for the exploit This will be executed and we will see how */ unsigned char code[] = "< shellcode String >";main() {
printf("Shellcode Length: %d\n", (int)strlen(code)); /* Method of shellcode execution (Execv) Shellcode will be injected into the code as a function */ //<--1---> <----2-------->> int (*ret)() = (int(*)())code; //<----------3--------------> /* 1.Pointer to a function with no parameter(indicated by '()') that returns int 2.Casts the above unsigned character array as a pointer to a function 3. ret() is then called from inside main. -> gcc with -fno-stack-protect -z execstack -o <filename>.o <filename>.c */ ret();
}
In Windows, the chosen machine is IA-32 and with ASLR disabled. The primary motive of the excercise to learn windows shellcode was to demonstrate buffer overflow attacks. For this reason, the machine they have been tested out on is a Win XP, 32-bit SP3. Buffer overflow attacks demonstrated are basic, and in present day scenario impractical on 64-bit machines.(64-bit address space is too large for bruteforcing to work with ASLR). WinXp SP3, with ASLR disabled is easy ot target with applications that do not guard against buffer overflow. Most of the payload generation has been done on metasploit and then injected into the application directly.
For windows the assembler being used is MASM32. It can be downloaded from here.