< BACK TO ARTICLES
Article/ x86-windows-shellcoding

x86 Windows Shellcoding

learn how to develop custom shellcode for windows in x86 assembly language.

Binary Exploitation 💥🧠advancedFebruary 8, 20267 min readshwet
shellcodingexploit development

In this tutorial we are gonna understand how to develop shellcode for windows in x86 assembly language.

Features

process independent code

Null Bytes Free

resolve functions at runtime






Basics

for shellcoding to be process independent, shellcode needs to resolve functions at runtime. so that shellcode not need to be depend on which which function main exe import. shellcode will auto resolve the functions at runtime.

so here is the way...

we can use LoadLibraryA to load the DLL, Then use GetModuleHandleA to get the base address of the loaded Dll, And at last use GetProcAddress to resolve the Symbols.






syntax for LoadLibraryA

cpp
HMODULE LoadLibraryA( [in] LPCSTR lpLibFileName ); // from kernel32.dll


syntax for GetModuleHandleA

cpp
HMODULE GetModuleHandleA( [in, optional] LPCSTR lpModuleName ); // from kernel32.dll


syntax for GetProcAddress

cpp
FARPROC GetProcAddress( [in] HMODULE hModule, [in] LPCSTR lpProcName ); // from kernel32.dll






Problem...

All three important functions need kernel32.dll to be imported already. but kernel32.dll is the basics dll and already loaded but the problem is when exe run the address for each function not always same, it changes on every run. so we have to manually find the address for it.

Procedure for solution

  • find Base Address of kernel32.dll

  • resolve the export funtion by kernel32.dll

  • use can then use LoadLibraryA to load other dll

  • then we can use GetProcAddress to resolve functions within them.

Finding kernel32.dll

Methods for finding kernel32.dll base address

Methods

portability

utilising PEB

very common and portable

utilising SEH

less portable not work on modern versions of windows

Top Stack Method

less portable not work on modern version of windows

Using PEB method

image

Understanding fs register and TEB

what actually fs register is ?

fs register is a special type of segment register.

  • it provides a base address

  • when we do ...

x86asm
mov eax, fs:[0x30] ; cpu internally does actuall_address = fs_base + 0x30 eax = *(actuall_address)

what does windows put in fs ?

on 32-bit windows

windows sets fs base = address of TEB

what is TEB ?

TEB stands for thread environment block. it is a small data structure windows provide to every thread.

every thread gets its own TEB

it contains...

  • SEH chain

  • Stack info

  • Thread Id

  • Pointer to PEB

  • etc...

for more Contents of The TEB on Windows go to Win32 Thread Information Block - Wikipedia

Come Back to Topic

To Find kernel32.dll...

we have to.

  1. from fs:[0x18] reg get the TEB address

  2. at offset 0x30 from the start of the TEB its pointer to PEB

  3. from start of PEB at offset 0x00c there is a pointer to _PEB_LDR_DATA this structure holds information about loaded modules (DLLs).

  4. in _PEB_LDR_DATA struct at offsets +0x00c, +0x014, +0x01c there is three double linked list

-> InLoadOrderModuleList

  • shows the previous and next module in Load Order.

-> InMemoryOrderModuleList

  • shows the previous and next module in memory Placement order

-> InInitializationOrderModuleList

  • shows the previous and next module in initialisation order.

what is LIST_ENTRY ?

cpp
struct _LIST_ENTRY { Flink; // Forward link (next) Blink; // Backward link (previous) }

so

  • flink -> next element

  • blink -> previous element

what actually double linked list means here ?

when we see in windbg

InInitializationOrderModuleList : LISTENTRY

  • this is the HEAD of the list.

  • It is not a module

  • it is just a anchor.

MEMORY DIAGRAM

head is at address 1000

ModuleA LIST_ENTRY at 2000

ModuleB LIST_ENTRY at 3000

ModuleC LIST_ENTRY at 4000

Windows Connect Them like...

x86asm
HEAD (1000) Flink = 2000 → first module Blink = 4000 → last module A (2000) Flink = 3000 Blink = 1000 B (3000) Flink = 4000 Blink = 2000 C (4000) Flink = 1000 Blink = 3000

image

so now focus on InInitializationOrderlinks

from the start of the HEAD if we minus 0x10 we will reach to the _LDR_DATA_TABLE_ENTRY structure

  • InInitializationOrderLinksis part of _LDR_DATA_TABLE_ENTRY

x86asm
0:002> dt _LDR_DATA_TABLE_ENTRY (0x04011658 - 0x10) ntdll!_LDR_DATA_TABLE_ENTRY +0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x4011ab0 - 0x4011728 ] +0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x4011ab8 - 0x4011730 ] +0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x4011d88 - 0x776c9abc ] +0x018 DllBase : 0x775c0000 Void +0x01c EntryPoint : (null) +0x020 SizeOfImage : 0x17a000 +0x024 FullDllName : _UNICODE_STRING "C:\Windows\SYSTEM32\ntdll.dll" +0x02c BaseDllName : _UNICODE_STRING "ntdll.dll" +0x034 FlagGroup : [4] "???" +0x034 Flags : 0xa2c4 ...
  • from above we can see at offset +0x018 there is DLLBase for the module

  • and at offset 0x02c there is BaseDllName for the name of dll which is a nested structure of _UNICODE_STRING type.

  • here is the syntax for _UNICODE_STRING type structure

cpp
typedef struct _UNICODE_STRING { USHORT Length; USHORT MaximumLength; PWSTR Buffer; } UNICODE_STRING, *PUNICODE_STRING;

so therefore actual Buffer start from +0x04 from start of the _UNICODE_STRING

therefore DLL name starts at offsets 0x30 from the beginning of the _LDR_DATA_TABLE_ENTRY_

so after these we can parse the InInitilisationOrderModuleList using flink and blink and use the

BaseDLLName field to match to our target module. if we will found we can take its BaseAddress.

Share Article

Last updated: February 17, 2026
More Articles