OSED study materials
OSED study resources

Combining a Format String Leak with a Stack Overflow

OSED · Put your knowledge to work in context.

All case studies

Combining a Format String Leak with a Stack Overflow

A 32-bit Windows service, logsvc.exe, has two vulnerabilities. First, a logging function calls fprintf(logfile, user_input); user_input is a 256-byte buffer received over TCP. The formatted output is not written to the network, but the service returns a debug response containing the formatted string if the request includes a magic header X-DEBUG: 1. Second, a different command handler copies 1024 bytes into a 128-byte stack buffer with strcpy. The binary is linked with /GS, /DYNAMICBASE, /NXCOMPAT, and /SAFESEH. You can send multiple requests. The service runs as a low-privilege user on Windows 10 x86. You need to exploit the overflow to execute a payload. You have confirmed the overflow offset is 140 and that you control EIP. The format string buffer is on the stack, and the service returns the formatted output verbatim when the debug header is present.

1. How can you use the format string to leak a stack address and the exe base address? What specific format specifiers and reasoning would you use?

Read the worked answer

Send a format string like %p.%p.%p.%p.%p.%p.%p.%p with the X-DEBUG: 1 header. The response contains the formatted output, so you can read the leaked values. The first few %p values are the format string function's arguments and stack slots; after that, you will see pointers into the stack (e.g., 0x00xxxxxx or 0x0xxxxxxx) and pointers into the exe (0x0040xxxx) and DLLs (0x7xxxxxxx). Identify a stack pointer that is close to your own buffer: the format string itself is on the stack, so one of the leaked values will point to the buffer. Identify an exe pointer: for example, a saved return address into logsvc.exe; subtract its known offset (from static analysis) to compute the exe base. Be careful: ASLR randomizes the exe base, but the low 12 bits are fixed, so the leaked pointer's low bits tell you which function it is. Use multiple requests to confirm stability across process restarts if the service restarts.

2. Once you have the leaks, how do you use them to defeat /GS and DEP in the overflow?

Read the worked answer

The stack leak gives you the address of your buffer on the stack, and also lets you compute the location of the stack cookie. The cookie is stored at a fixed offset from the saved EBP in the vulnerable function; by leaking a stack pointer from the same thread, you can calculate the cookie's address, but you need its value. Use the format string to read the cookie: place the cookie's address at the end of the format string and use %s to read it, or use %x if the cookie is in a known stack slot. Once you know the cookie value, include it in the overflow payload at the correct offset so __security_check_cookie passes. With the exe base known, you can build a ROP chain using gadgets in the exe to call VirtualProtect on the stack buffer, then jump to shellcode. The chain must preserve the cookie and any other checked values. Because you have both leaks, ASLR and /GS are defeated; DEP is defeated by the ROP call to VirtualProtect.

3. What if the debug header is removed and the format string output is no longer returned? How would your approach change?

Read the worked answer

Without the debug output, the format string becomes blind. You can still use %n to write to memory, but you cannot directly read leaks. A blind format string can be used to overwrite a function pointer or a length field, but to bypass ASLR you need an address. Options: (a) use the format string to overwrite the GOT entry of a function with another function's address, but you need to know both addresses; (b) use %n to write a large value to a global variable that controls a length check, creating a second overflow with different constraints; (c) look for another information leak, such as an uninitialized stack variable returned in an error message, or a crash dump. If no leak exists, the format string alone is not enough to reliably defeat ASLR and /GS; you would need to combine it with a partial overwrite or a non-ASLR module. In practice, a blind format string is much harder and often requires a write primitive to modify a pointer that is later used in a way that reveals memory.