Shellcode Development

aayush malla
7 min readOct 12, 2020

Here in this lab, we will learn to write our own shellcode so that we can write the shellcode for specific requirements.

Here we are using virtual machine from the SEED labs (Ubuntu 16 32 bit).

In our buffer overflow attack we learned how to use malicious into victim’s program’s memory and how to trigger the code. Today we will learn how to write our own malicious code.

For writing our own malicious code we have to use assembly language.The assembly code for launching a shell is known as shellcode. The core part of a shellcode is to use execve () system call to execute /bin/sh.

To use the system call we need to set 4 registers as follows:

  • eax : must contain 11, which is the system ca ll number for execve ().
  • ebx: must contain the address of the command string (e.g . “ /b i n/ sh “ ).
  • ecx: must contain the address of the argument array ; in our case, the first element of the array points to the “ /bin/ sh “ string, while the second element is O (which marks the end of the array) .
  • edx : must contain the address of the environment variables that we want to pass to the new program . We can set it to 0, as we do not need to pass any environment variable.

There are several challenges in writing shellcode, one is to ensure that there is no zero in the binary, and the other is to find out the address of the data used in the command. The first challenge is not very difficult to solve, and there are several ways to solve it. The solutions to the second challenge led to two typical approaches to write shellcode. In one approach, data are pushed into the stack during the execution, so their addresses can be obtained from the stack pointer. In the second approach, data are stored in the code region, right after a call instruction. When the call instruction is executed, the address of the data is treated as the return address, and is pushed into the stack.

FIRST CHALLENGE:

Eliminating zeros from the code

Shellcode is widely used in buffer-overflow attacks. In many cases, the vulnerabilities are caused by string copy, such as the strcpy() function. For these string copy functions, zero is considered as the end of the string. Therefore, if we have a zero in the middle of a shellcode, string copy will not be able to copy anything after the zero from this shellcode to the target buffer, so the attack will not be able to succeed. Although not all the vulnerabilities have issues with zeros, it becomes a requirement for shellcode not to have any zero in the machine code; otherwise, the application of a shellcode will be limited.

There are multiple to eliminate zeros from the code .Some of the hints are given below:

  • If we want to assign zero to eax, we can use “mov eax, 0”, but doing so, we will get a zero in the machine code. A typical way to solve this problem is to use “xor eax, eax”.
  • If we want to store 0x00000099 to eax. We cannot just use mov eax, 0x99, because the second operand is actually 0x00000099, which contains three zeros. To solve this problem, we can first set eax to zero, and then assign a one-byte number 0x99 to the al register, which is the least significant 8 bits of the eax register.
  • Another way is to use shift. In the following code, first 0x237A7978 is assigned to ebx. The ASCII values for x, y, z, and # are 0x78, 0x79, 0x7a, 0x23, respectively. Because most Intel CPUs use the small-Endian byte order, the least significant byte is the one stored at the lower address (i.e., the character x), so the number presented by xyz# is actually 0x237A7978. You can see this when you dissemble the code using objdump.

SECOND CHALLENGE

  1. Using Stack

Here is the assembly code to create a program to run shell. In this code we have used stack to push the commands.

At first we have pushed 0 (using xor eax, eax, push eax) in the stack and

push “//sh”

push “/bin”

Here push instruction takes 32 bit So in case of sh we have to use redundant / to make it 32 bit.

So as explained above ebx must contain the address of the strinf command /bin/sh

so lets put the starting address /bin into ebx using command.

mov ebx, esp

After pushing the command in stack lets construct the argument array argv[]. Here in this case we have no command line variables so argv[0] points to the /bin/sh and argv[1] indicates end of the command. Now as discussed below ecx must contain the address of the argument array so let place the address of command line array into ecx using

mov ecx,esp

Now as we have no environment variable let make edx null using cmd xor edx,edx

Now lets invovke execve

xor eax,eax

mov la,0xb

int 0x80 — it is basically call to the kernel

Now after writing the program to run the shell and generate the shellcode we have to first convert our code file to object file and then to executable binary to extract the machine code(shellcode) from the file.

Command to change the program file to object file

nasm -f elf32 mysh.s -o mysh.o

Command to make the object file executable

ld mysh.o -o mysh

Now lets check whether our program run new shell or not.For this lets print current shell process before and after running the executable file.

fig : proof of getting a new shell

Now during the attack, we only need the machine code of the shellcode, not a standalone executable file, which contains data other than the actual machine code. Technically, only the machine code is called shellcode. Therefore, we need to extract the machine code from the executable file or the object file. There are various ways to do that. One way is to use the objdump command to disassemble the executable or object file.

fig : Using objdump to extract machine code

In the above printout, the highlighted numbers are machine code. You can also use the xxd command to print out the content of the binary file, and you should be able to find out the shellcode’s machine code from the printout.

fig : Using xxd to exctract machine code

In actual attacks, we need to include the shellcode in our attacking code, such as a Python or C program. We usually store the machine code in an array, but converting the machine code printed above to the array assignment in Python and C programs is quite tedious if done manually, especially if we need to perform this process many times in the lab. SEED lab have provided following Python code to help this process. Just copy whatever you get from the xxd command (only the shellcode part) and paste it to the following code, between the lines marked by “””. The code can be downloaded from the lab’s website.

Python program to convert the machine code into an array

Now after running this python file we will get the shellcode in array form.

fig: shellcode in array form

2. Using Code Section

As we can see from the above program to generate shellcode the way how it solves the data address problem is that it dynamically constructs all the necessary data structures on the stack, so their addresses can be obtained from the stack pointer esp. There is another approach to solve the same problem, i.e., getting the address of all the necessary data structures. In this approach, data are stored in the code region, and its address is obtained via the function call mechanism.

Program to run shell using data stored in code region.

Above is the code to run the shellcode were we have stored in code region. The code above first jumps to the instruction at location two, which does another jump (to location one), but this time, it uses the call instruction. This instruction is for function call, i.e., before it jumps to the target location, it keeps a record of the address of the next instruction as the return address, so when the function returns, it can return to the instruction right after the call instruction. In this example, the “instruction” right after the call instruction (Line ➋) is not actually an instruction; it stores a string. However, this does not matter, the call instruction will push its address (i.e., the string’s address) into the stack, in the return address field of the function frame. When we get into the function, i.e., after jumping to location one, the top of the stack is where the return address is stored. Therefore, the pop ebx instruction in Line ➊ actually get the address of the string on Line ➋, and save it to the ebx register. That is how the address of the string is obtained.

And the remaining flow and logic is explained as comment in above code.

Now we can generate the shellcode from the above code in similar way as in previous process.

First change the code into object file

nasm -f elf32 mysh2.s -o mysh2.o

Change the object file into executable

ld — omagic mysh2.o -o mysh2

Now we can get machine code using xxd and then convert it into array form using the python program provided.

Hence by this we can create a simple shellcode to run /bin/sh shell.We will be learning to pass command line arguments and environment variable in next article.

References:

--

--

aayush malla

Data Engineer, Cybersecurity enthusiast , PLSQL, Data Analyst