Null-Byte-Constrained Overflow in an FTP Command
A 32-bit Windows FTP server, ftpd.exe, copies the argument of the USER command into a 128-byte stack buffer with strcpy. The command is read with recv into a 1024-byte buffer, so you can send long strings, but strcpy stops at the first null byte. You have a crash with EIP=0x41414141 at offset 132. The binary is linked with /GS, /NXCOMPAT, /DYNAMICBASE, and /SAFESEH. It loads a third-party legacy.dll that was built without /DYNAMICBASE; dumpbin shows its preferred base is 0x10000000. The exe itself is ASLR-enabled. The function containing the overflow has a local function pointer at ebp-0x10 that is called after the strcpy and before the stack cookie check. DEP is enforced. You need to execute a payload on the server.
1. Explain how the null-byte constraint affects a traditional ROP chain and how the presence of `legacy.dll` changes your approach.
Read the worked answer
strcpy terminates at the first 0x00, so you cannot place any address containing a null byte into the overflow. The exe is at 0x00400000, so its gadget addresses contain a null high byte and are unusable. legacy.dll is loaded at a fixed 0x10000000 and is not ASLR-enabled; its gadget addresses look like 0x1000xxxx, which contain no null bytes. That makes it possible to write a full ROP chain into the buffer using gadgets from legacy.dll. You still need to avoid 0x20, 0x0A, and 0x0D if the FTP command parser treats them specially, but the null-byte problem is solved. The chain must stay within the 1024-byte command buffer and must not include any accidental null bytes from data values.
2. Design the exploitation flow, addressing /GS, DEP, and the pre-epilogue function pointer.
Read the worked answer
Do not try to return through the function epilogue: the cookie is corrupted and /GS would abort. Instead, overwrite the local function pointer at ebp-0x10 with the address of a stack-pivot gadget in legacy.dll (e.g., xchg eax, esp; ret or pop esp; ret). After strcpy, the program calls that pointer, so you gain control before __security_check_cookie. Pivot ESP to the start of your ROP chain, which you place in the same buffer. The chain should call VirtualProtect (via the exe's IAT or by resolving it with LoadLibraryA/GetProcAddress from legacy.dll if imported) to mark the stack executable, then jump to the shellcode also in the buffer. Because legacy.dll is fixed, all gadget addresses are known and null-free. DEP is bypassed by the VirtualProtect call; /GS is bypassed by diverting execution before the epilogue. Test the pivot offset carefully: the function pointer is at a fixed offset from the buffer, and the ROP chain must not be overwritten by later stack operations.
3. What if `legacy.dll` were also ASLR-enabled? How would that change your plan?
Read the worked answer
If legacy.dll were ASLR-enabled, you would lose the fixed, null-free gadget addresses. You would then need an information leak to discover the base of at least one module. Possible sources: an error message that echoes a pointer, a format string in another command, or a partial overwrite of the saved function pointer. A partial overwrite could redirect the function pointer to another location within legacy.dll using only the low two bytes, because the original pointer already contains the correct randomized high bytes. That gives one gadget but not a full ROP chain. To build a chain, you would need to leak a stack address and a module base, then write addresses that may contain null bytes by using an encoder or by writing them with a separate write primitive. Without any leak, exploitation would be unreliable; you would look for a non-ASLR module or a different vulnerability.