[polarisctf] ez-nc
Table of Contents
- Challenge Summary
- Program Profile and Attack Surface
- Pre-exploitation Constraints
- Primitive Extraction
- Route Selection
- Attack Chain Breakdown
- Pitfalls and Stability
- Pattern Transfer
- exp Summary:
[!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

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
- Which protection mechanisms currently have a real impact on exploitation?
- What are the limits on input length, number of interactions, character set, alignment, stack balance, etc.?
- Which constraints must be solved first, and which are only implementation details?
At present, only string filtering protection and the input length limit have been found, so conventional long input is not suitable:
%1$p.%2$p.%3$p.%4$p..... Instead, a new form using a for loop to constructf"%{i}$pis adopted.
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
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:
Successful hit:

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:
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:
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
It was discovered that the flag was directly hardcoded in the .rodata data section of the ELF file.

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
- At which step did the first version of the exploit fail earliest?
- At the time, did the error look like an offset issue, a stack balance issue, or an environment issue?
- Why was this error not easy to realize at first?
Root Cause Identification
- What was the final confirmed root cause?
- Was it a local/remote difference, a symbol calculation error, gadget contamination, or an interaction timing issue?
- Which debugging evidence really helped you pinpoint the issue?
Stable Version Fixes
- Compared with the initial version, what were the most critical fixes in the final version?
- Which fixes merely made it “work,” and which truly made it “stable”?
- If you encounter a similar pitfall again in the future, what should you check first?
Pattern Transfer
Pattern Recognition
- What is the most critical recognition signal in this challenge?
- Which local phenomenon is most worth becoming immediately alert to next time when solving a challenge?
Reuse Through Transfer
- Next time you see what kind of structure, you should prioritize thinking of this route?
- What kind of reusable exploitation template can be distilled from this challenge?
Pattern Recognition and Transfer
Recognition Signals
- What structural signal in this challenge is most worth remembering?
- Is it a certain input model, a certain heap state, a certain leak method, or a certain convergence pattern?
- When these signals appear together, why should this route be prioritized?
Transfer Pattern
- What kind of reusable pattern can this challenge ultimately be abstracted into?
- If it is changed into a similar challenge with different details, which parts can be reused directly and which parts must be rebuilt?
- Next time you encounter a similar challenge, which three points should be verified first?
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()