0x00 Preface
---
Previously, in "Windows Shellcode Study Notes - Generating Shellcode via VisualStudio", we introduced a method using C++ (without inline assembly) to dynamically obtain API addresses and make calls, disassemble to extract shellcode, and open-sourced the test code.
During the subsequent process of extracting shellcode, some bugs were discovered in the previously open-sourced code. Therefore, this article focuses on fixing these bugs in the test code and discusses considerations for developing shellcode using C++.
Download link for the test code containing bugs:
An open-source project
0x01 Introduction
---
Simple shellcode extraction process:
- Develop code using C++
- Modify VisualStudio compilation configuration
- Generate exe
- Open the generated exe in IDA to obtain machine code
Since API addresses are obtained and called dynamically, to ensure shellcode compatibility, fixed addresses must not appear in the code, and the use of global variables should be minimized. If the code contains sub-functions, depending on the calling method, attention must also be paid to the arrangement order between functions (the entry function should be placed first).
0x02 Bug Fix
---
Configure three compilation options: release, disable optimization, disable /GS
Compile the code, then use IDA to extract machine code as shellcode
During actual debugging, bugs were found in the code:
1. Global variables should be properly handled in the code
Using global variables in the code
FARPROC(WINAPI* GetProcAddressAPI)(HMODULE, LPCSTR); |
After compilation, these become fixed addresses, making the shellcode incompatible across different environments
The simplest and most direct approach is to avoid global variables in shellcode whenever possible
2. Function declaration method needs modification
After modifying global variables, the following code needs to be changed:
MESSAGEBOXA_INITIALIZE MeassageboxA_MyOwn = reinterpret_cast(GetProcAddressAPI(LoadLibraryWAPI(struser32), MeassageboxA_api)); |
Need to completely replace with typedef function declaration style
3. Function call order
If using the following method to load shellcode:
(*(int(*)()) sc)(); |
The definition of the entry function should be at the very beginning of this shellcode (independent of the order of function declarations)
Note:
If the shellcode contains sub-functions, ensure each function is placed in a contiguous address range, with the entry function positioned at the very front. This way, after extracting the machine code, you can directly load the entry function to execute the shellcode.
In summary, provide the new complete code:
#include |
0x03 Shellcode Extraction
---
After compiling the above code into an exe and opening it with IDA, check the Function Window to find the starting addresses of each subfunction
As shown in the figure

It can be seen that each function is stored in a continuous address range, and the shellcode starting function is located at the very beginning
Double-click the first function shell_code(void) to enter the IDA text view, where you can see that the specific location of the shell_code(void) function in the exe file is 00000400
As shown in the figure

Check the location of the main function in the exe file, which is 00000A00
As shown in the figure

Based on the structure of the C code, it can be inferred that the offset range 00000400-00000A00 in the exe file is the machine code we need
Use a hex editor to extract the machine code and save it to a file; the content in the file is the shellcode we need.
Of course, the function of manually extracting machine code and saving it to a file can be automated by a program. The complete code is as follows:
#include |
Note:
Open the file in "wb" mode to write binary data
If using "w" mode, the 0A character will be replaced with 0D0A during writing, causing issues with the shellcode
0x04 Shellcode Testing
---
Use the following code to read the shellcode saved in the file, load it, and test its functionality:
#include |