This is a quick lab to get familiar with the process of writing and compiling shellcode in C and is merely a personal conspectus of the paper From a C project, through assembly, to shellcode by hasherezade for vxunderground - go check it out for a deep dive on all the subtleties involved in this process, that will not be covered in these notes.
For the sake of this lab, we are going to turn a simple C program (that is provided by hasherezade in the aforementioned paper) that pops a message box, to shellcode and execute it by manually injecting it into an RWX memory location inside notepad.
Code samples used throughout this lab are written by hasherezade, unless stated otherwise.
Overview
Below is a quick overview of how writing and compiling shellcode in C works:
Shellcode is written in C
C code is compiled to a list of assembly instructions
Assembly instructions are cleaned up and external dependencies removed
This lab is based on Visual Studio 2019 Community Edition.
Program and shellcode in this lab targets x64 architecture.
1. Preparing Dev Environment
First of, let's start the Developer Command Prompt for VS 2019, which will set up our dev environment required for compiling and linking the C code used in this lab:
/FA - Create a listing file containing assembler code for the provided C code
/GS- - Turn off detection of some buffer overruns
Below shows how we compile the c-shellcode.cpp into c-shellcode.asm:
3. Massaging Assembly Listing
Now that our C code has been convered to assembly in c-shellcode.asm, we need to clean up the file a bit, so we can link it to an .exe without errors and to avoid the shellcode from crashing. Specifically, we need to:
Remove dependencies from external libraries
Align stack
Fix a simple syntax issue
3.1 Remove Exteranal Libraries
First off, we need to comment out or remove instructions to link this module with libraries libcmt and oldnames:
3.2 Fix Stack Alignment
Add procedure AlignRSP right at the top of the first _TEXT segment in our c-shellcode.asm:
; https://github.com/mattifestation/PIC_Bindshell/blob/master/PIC_Bindshell/AdjustStack.asm; AlignRSP is a simple call stub that ensures that the stack is 16-byte aligned prior; to calling the entry point of the payload. This is necessary because 64-bit functions; in Windows assume that they were called with 16-byte stack alignment. When amd64; shellcode is executed, you can't be assured that you stack is 16-byte aligned. For example,; if your shellcode lands with 8-byte stack alignment, any call to a Win32 function will likely; crash upon calling any ASM instruction that utilizes XMM registers (which require 16-byte); alignment.AlignRSP PROC push rsi ; Preserve RSI since we're stomping on it mov rsi, rsp ; Save the value of RSP so it can be restored and rsp, 0FFFFFFFFFFFFFFF0h ; Align RSP to 16 bytessub rsp, 020h ; Allocate homing space for ExecutePayload call main ; Call the entry point of the payload mov rsp, rsi ; Restore the original value of RSP pop rsi ; Restore RSI ret ; Return to callerAlignRSP ENDP
Below shows how it should look like in the c-shellcode.asm:
3.3 Remove PDATA and XDATA Segments
Remove or comment out PDATA and XDATA segments as shown below:
3.4 Fix Syntax Issues
We need to change line mov rax, QWORD PTR gs:96 to mov rax, QWORD PTR gs:[96]:
4. Linking to an EXE
We are now ready to link the assembly listings inside c-shellcode.asm to get an executable c-shellcode.exe:
We can now check that if c-shellcode.exe does what it was meant to - pops a message box:
6. Copying Out Shellcode
Once we have the c-shellcode.exe binary, we can extract the shellcode and execute it using any code injection technique, but for the sake of this lab, we will copy it out as a list of hex values and simply paste them into an RWX memory slot inside a notepad.exe.
Let's copy out the shellcode from the .text section, which in our case starts at 0x200 into the raw file:
If you are wondering how we found the shellcode location, look at the .text section - you can extract if from there too:
7. Testing Shellcode
Once the shellcode is copied, let's paste it to an RWX memory area (you can set any memory location to have permissions RWX with xdbg64) inside notepad, set RIP to that location and resume code execution in that location. If we did all the previous steps correctly, we should see our shellcode execute and pop the message box: