RoboDBG
Loading...
Searching...
No Matches
debugger.cpp
1#include "debugger.h"
2
3namespace RoboDBG {
4
5
7 this->verbose = false;
8}
9
10Debugger::Debugger( bool verbose) {
11 this->verbose = verbose;
12}
13
15 PROCESS_BASIC_INFORMATION pbi = {};
16 ULONG retLen;
17
18 NTSTATUS status = NtQueryInformationProcess(
19 hProcessGlobal,
20 ProcessBasicInformation,
21 &pbi,
22 sizeof(pbi),&retLen
23 );
24
25 if (status != 0) {
26 std::cerr << "[-] NtQueryInformationProcess failed\n";
27 return false;
28 }
29
30 // PEB is at this address in remote process
31 BYTE beingDebugged = 0;
32 SIZE_T bytesWritten = 0;
33
34 // BeingDebugged is at offset 0x2 of the PEB
35 LPVOID pebDebugFlagAddr = (LPBYTE)pbi.PebBaseAddress + 2;
36
37 // Write zero to the flag
38 if (!WriteProcessMemory(hProcessGlobal, pebDebugFlagAddr, &beingDebugged, sizeof(beingDebugged), &bytesWritten)) {
39 std::cerr << "[-] WriteProcessMemory failed: " << GetLastError() << "\n";
40 return false;
41 }
42
43 return true;
44}
45
46uintptr_t Debugger::ASLR(LPVOID address) {
47 return baseImageBase + reinterpret_cast<uintptr_t>(address);
48}
49
50uintptr_t Debugger::ASLR(uintptr_t address) {
51 return baseImageBase + address;
52}
53
54// Attach to an existing process
55int Debugger::attach(std::string exeName) {
56 DWORD pid = Util::findProcessId(exeName);
57 if (pid == 0) {
58 std::cerr << "Process not found: " << exeName << std::endl;
59 return -1;
60 }
61 std::cout << "Attach to " << pid << std::endl;
62 if (!DebugActiveProcess(pid)) {
63 std::cerr << "Failed to attach to process: " << GetLastError() << std::endl;
64 return -1;
65 }
66
67 hProcessGlobal = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
68 if (hProcessGlobal == NULL) {
69 std::cerr << "Failed to open process: " << GetLastError() << std::endl;
70 return -1;
71 }
72 debuggedPid = pid;
73 onAttach( );
74 return 0;
75}
76
78 // Detach the debugger
79 if (!DebugActiveProcessStop(debuggedPid)) {
80 std::cerr << "Failed to detach debugger. Error: " << GetLastError() << std::endl;
81 return 1;
82 }
83 return 0;
84}
85
86// Start a new process under debug control
87int Debugger::start(std::string exeName) {
88 STARTUPINFO si = { sizeof(si) };
89 PROCESS_INFORMATION pi;
90
91 if (!CreateProcessA(
92 NULL,
93 const_cast<LPSTR>(exeName.c_str()),
94 NULL,
95 NULL,
96 FALSE,
97 DEBUG_ONLY_THIS_PROCESS,
98 NULL,
99 NULL,
100 &si,
101 &pi)) {
102 std::cerr << "CreateProcess failed: " << GetLastError() << std::endl;
103 return -1;
104 }
105
106 hProcessGlobal = pi.hProcess;
107 hThreadGlobal = pi.hThread;
108 return 0;
109}
110
111int Debugger::start(std::string exeName, const std::vector<std::string>& args) {
112 STARTUPINFO si = { sizeof(si) };
113 PROCESS_INFORMATION pi{};
114
115 // Build the full command line
116 std::string cmdLine = "\"" + exeName + "\""; // quote exe path in case it has spaces
117 for (const auto& arg : args) {
118 cmdLine += " " + arg;
119 }
120
121 // CreateProcess requires a modifiable LPSTR
122 std::vector<char> cmdLineMutable(cmdLine.begin(), cmdLine.end());
123 cmdLineMutable.push_back('\0');
124
125 if (!CreateProcessA(
126 nullptr,
127 cmdLineMutable.data(),
128 nullptr,
129 nullptr,
130 FALSE,
131 DEBUG_ONLY_THIS_PROCESS,
132 nullptr,
133 nullptr,
134 &si,
135 &pi
136 )) {
137 std::cerr << "CreateProcess failed: " << GetLastError() << std::endl;
138 return -1;
139 }
140
141 hProcessGlobal = pi.hProcess;
142 hThreadGlobal = pi.hThread;
143 return 0;
144}
145
146
147
148void Debugger::enableSingleStep(HANDLE hThread) {
149 CONTEXT ctx = {};
150 ctx.ContextFlags = CONTEXT_CONTROL;
151
152 if (GetThreadContext(hThread, &ctx)) {
153 ctx.EFlags |= 0x100; // Trap Flag
154 SetThreadContext(hThread, &ctx);
155 }
156}
157
158void Debugger::decrementIP(HANDLE hThread) {
159 if (SuspendThread(hThread) == (DWORD)-1) {
160 std::cerr << "[-] SuspendThread failed: " << GetLastError() << "\n";
161 }
162 CONTEXT ctx = {};
163 ctx.ContextFlags = CONTEXT_CONTROL;
164 if (GetThreadContext(hThread, &ctx)) {
165 #ifdef _WIN64
166 ctx.Rip--;
167 #else
168 ctx.Eip--;
169 #endif
170 SetThreadContext(hThread, &ctx);
171 }
172 // Always try to resume
173 if (ResumeThread(hThread) == (DWORD)-1) {
174 std::cerr << "[-] ResumeThread failed: " << GetLastError() << "\n";
175 }
176}
177
178
179
180void Debugger::printIP(HANDLE hThread) {
181 CONTEXT ctx = {};
182 ctx.ContextFlags = CONTEXT_CONTROL;
183 if (GetThreadContext(hThread, &ctx)) {
184 #ifdef _WIN64
185 std::cout << "[!] RIP = 0x" << std::hex << ctx.Rip << std::endl;
186 #else
187 std::cout << "[!] EIP = 0x" << std::hex << ctx.Eip << std::endl;
188 #endif
189 }
190}
191
192//actualize a thread list. e.g. after attaching to an existing running application;
194 threads.clear(); // Clear existing thread list
195
196 DWORD processId = GetProcessId(hProcessGlobal);
197 if (processId == 0) {
198 std::cerr << "Failed to get process ID from handle. Error: " << GetLastError() << std::endl;
199 return;
200 }
201
202 HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
203 if (snapshot == INVALID_HANDLE_VALUE) {
204 std::cerr << "Failed to create thread snapshot. Error: " << GetLastError() << std::endl;
205 return;
206 }
207
208 THREADENTRY32 te32 = {};
209 te32.dwSize = sizeof(te32);
210
211 if (Thread32First(snapshot, &te32)) {
212 do {
213 if (te32.th32OwnerProcessID == processId) {
214 HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, te32.th32ThreadID);
215 if (!hThread) {
216 std::cerr << "Failed to open thread " << te32.th32ThreadID << ". Error: " << GetLastError() << std::endl;
217 continue;
218 }
219
220 // Optionally get thread start address (Windows internal APIs, see note below)
221 // For now we fill with nullptr placeholders
222 thread_t t;
223 t.hThread = hThread;
224 t.threadId = te32.th32ThreadID;
225 t.threadBase = nullptr; // Not easily available without NtQueryInformationThread
226 t.startAddress = nullptr; // Optional: can use NtQueryInformationThread with ThreadQuerySetWin32StartAddress
227
228 threads.push_back(t);
229 }
230 } while (Thread32Next(snapshot, &te32));
231 }
232
233 CloseHandle(snapshot);
234}
235
237 bool stepping = false;
238
239 DEBUG_EVENT dbgEvent;
240 while (true) {
241 if (!WaitForDebugEvent(&dbgEvent, INFINITE)) break;
242
243 DWORD cont = DBG_CONTINUE;
244
245 switch (dbgEvent.dwDebugEventCode) {
246 case CREATE_PROCESS_DEBUG_EVENT: {
247 baseImageBase = reinterpret_cast<uintptr_t>(dbgEvent.u.CreateProcessInfo.lpBaseOfImage);
248 DWORD_PTR entryPoint = Util::getEntryPoint(hProcessGlobal, reinterpret_cast<LPVOID>(baseImageBase));
249 onStart(baseImageBase,static_cast<uintptr_t>(entryPoint));
250 break;
251 }
252
253 case EXIT_PROCESS_DEBUG_EVENT: {
254 DWORD exitCode = dbgEvent.u.ExitProcess.dwExitCode;
255 DWORD pid = dbgEvent.dwProcessId;
256 onEnd(exitCode, pid);
257 return 0;
258 }
259 case CREATE_THREAD_DEBUG_EVENT: {
260 //std::cout << "[*] Thread created. TID=" << dbgEvent.dwThreadId
261 //<< " TEB=0x" << std::hex << (DWORD_PTR)threadBase
262 // << " Start Address=0x" << std::hex << (DWORD_PTR)threadStartAddr << "\n";
263
264 LPVOID threadBase = dbgEvent.u.CreateThread.lpThreadLocalBase;
265 LPVOID threadStartAddr = dbgEvent.u.CreateThread.lpStartAddress;
266
267 HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dbgEvent.dwThreadId);
268 if (!hThread) {
269 std::cerr << "[-] Failed to open thread: " << GetLastError() << "\n";
270 break;
271 }
272 onThreadCreate( hThread, dbgEvent.dwThreadId, reinterpret_cast<uintptr_t>(threadBase), reinterpret_cast<uintptr_t>(threadStartAddr));
273
274 // Store the thread info
275 thread_t newThread = {
276 .hThread = hThread,
277 .threadId = dbgEvent.dwThreadId,
278 .threadBase = threadBase,
279 .startAddress = threadStartAddr
280 };
281 threads.push_back(newThread);
282
283 break;
284 }
285
286 case EXIT_THREAD_DEBUG_EVENT: {
287 //std::cout << "[*] Thread exited. TID=" << dbgEvent.dwThreadId << "\n";
288 onThreadExit( dbgEvent.dwThreadId );
289 break;
290 }
291
292 case LOAD_DLL_DEBUG_EVENT: {
293 LPVOID base = dbgEvent.u.LoadDll.lpBaseOfDll;
294 auto name = Util::getDllName(hProcessGlobal, dbgEvent.u.LoadDll.lpImageName, dbgEvent.u.LoadDll.fUnicode);
295 DWORD_PTR entryPoint = Util::getEntryPoint(hProcessGlobal, base);
296 onDLLLoad(reinterpret_cast<uintptr_t>(base), name, static_cast<uintptr_t>(entryPoint));
297
298 //std::cout << "[*] DLL loaded at 0x" << std::hex << (DWORD_PTR)base
299 //<< " Name: " << name << "\n";
300 break;
301 }
302
303 case UNLOAD_DLL_DEBUG_EVENT: {
304 LPVOID base = dbgEvent.u.UnloadDll.lpBaseOfDll;
305 auto name = Util::getDllName(hProcessGlobal, dbgEvent.u.LoadDll.lpImageName, dbgEvent.u.LoadDll.fUnicode);
306 onDLLUnload(reinterpret_cast<uintptr_t>(base), name);
307 //std::cout << "[*] DLL unloaded from 0x" << std::hex << (DWORD_PTR)base << "\n";
308 break;
309 }
310
311 case EXCEPTION_DEBUG_EVENT: {
312 DWORD code = dbgEvent.u.Exception.ExceptionRecord.ExceptionCode;
313 LPVOID addr = dbgEvent.u.Exception.ExceptionRecord.ExceptionAddress;
314 HANDLE hThread = OpenThread(THREAD_ALL_ACCESS, FALSE, dbgEvent.dwThreadId);
315 if (!hThread) break;
316
317 if (code == EXCEPTION_BREAKPOINT) {
318 if(this->verbose) std::cout << "[~] Breakpoint Hit" << std::endl;
319 auto it = breakpoints.find(addr);
320 if (it != breakpoints.end()) {
321 //std::cout << "[*] Loop Breakpoint hit at: 0x" << std::hex << (DWORD_PTR)addr << "\n";
322 restoreBreakpoint(addr);
323 decrementIP(hThread);
324 BreakpointAction bpType = onBreakpoint(reinterpret_cast<uintptr_t>(addr),hThread);
325 if (bpType != BREAK ) {
326 enableSingleStep(hThread);
327 stepping = true;
328 if(bpType == RESTORE )
329 {
330 bpAddrToRestore = addr;
331 lastBpType = bpType;
332 needToRestoreBreakpoint = true;
333 }
334 } else { //BP_DO_NOP
335 stepping = false;
336 }
337 }
338 } else if (code == EXCEPTION_SINGLE_STEP && stepping) {
339 if(needToRestoreBreakpoint) //just hit a breakpoint. Now restore.
340 {
341 if(restoreHwBP)
342 {
343 if(this->verbose) std::cout << "[~] Restoring hardware breakpoint" << std::endl;
344 setHardwareBreakpoint(hwBPToRestore);
345 restoreHwBP = false;
346 } else {
347 setBreakpoint(bpAddrToRestore); //TODO
348 }
349 needToRestoreBreakpoint = false;
350
351 }
352
353 if(lastBpType == SINGLE_STEP ) {
354 BreakpointAction bpType = onBreakpoint(reinterpret_cast<uintptr_t>(addr),hThread);
355 if (bpType != BREAK) {
356 bpAddrToRestore = addr;
357 needToRestoreBreakpoint = true;
358
359 lastBpType = bpType;
360 enableSingleStep(hThread);
361 stepping = true;
362 }
363 } else {
364 stepping = false;
365 }
366 } else if (code == EXCEPTION_SINGLE_STEP ) {
367 if(this->verbose) std::cout << "[~] Single step: 0x" << std::hex << (DWORD_PTR)addr << "\n";
368 DRReg reg = isHardwareBreakpointAt( addr );
369 if(reg != DRReg::NOP) {
370 BreakpointAction bpType = onHardwareBreakpoint(reinterpret_cast<uintptr_t>(addr), hThread, reg);
371 if(bpType == BREAK) { //Software Breakpoint
373 } else if(bpType == SINGLE_STEP)
374 {
375 enableSingleStep(hThread);
376 stepping = true;
378 } else if(bpType == RESTORE)
379 {
380 hwBp_t bp = getBreakpointByReg( reg );
381 needToRestoreBreakpoint = true;
382 restoreHwBP = true;
383 hwBPToRestore = bp;
384 bpAddrToRestore = addr;
385 hwBPThreadToRestore = hThread;
386 lastBpType = bpType;
387
388 //getHardwareBreakpoints();
389 clearHardwareBreakpointOnThread(hwBPThreadToRestore, reg);
390 enableSingleStep(hThread);
391 stepping = true;
392 }
393 } else {
394 onUnknownException(reinterpret_cast<uintptr_t>(addr), code);
395
396 }
397 } else if ( code == EXCEPTION_ACCESS_VIOLATION ) {
398 ULONG_PTR rawAddr = dbgEvent.u.Exception.ExceptionRecord.ExceptionInformation[1];
399 LPVOID faultingAddress = reinterpret_cast<LPVOID>(rawAddr);
400 ULONG_PTR accessType = dbgEvent.u.Exception.ExceptionRecord.ExceptionInformation[0];
401 //MemoryRegion_t mr = getPageByAddress( faultingAddress);
402 //changeMemoryProtection(mr, PAGE_EXECUTE_READWRITE);
403 onAccessViolation( static_cast<uintptr_t>(rawAddr), reinterpret_cast<uintptr_t>(faultingAddress), static_cast<long>(accessType));
404 //enableSingleStep(hThread);
405 //stepping = true;
406
407 } else {
408 onUnknownException( reinterpret_cast<uintptr_t>(addr), code );
409 }
410
411 CloseHandle(hThread);
412 break;
413 }
414
415 case OUTPUT_DEBUG_STRING_EVENT: {
416 OUTPUT_DEBUG_STRING_INFO& info = dbgEvent.u.DebugString;
417 SIZE_T bytesRead;
418
419 if (!info.fUnicode) {
420 // ANSI string
421 char buffer[1024] = {0};
422 ReadProcessMemory(
423 hProcessGlobal,
424 info.lpDebugStringData,
425 buffer,
426 std::min<DWORD>(info.nDebugStringLength, sizeof(buffer) - 1),
427 &bytesRead
428 );
429
430 std::string msg(buffer);
431 onDebugString(msg);
432 } else {
433 // Unicode string
434 wchar_t wbuffer[1024] = {0};
435 ReadProcessMemory(
436 hProcessGlobal,
437 info.lpDebugStringData,
438 wbuffer,
439 std::min<DWORD>(info.nDebugStringLength * sizeof(wchar_t), sizeof(wbuffer) - sizeof(wchar_t)),
440 &bytesRead
441 );
442
443 // Convert wchar_t* to std::string using WideCharToMultiByte (UTF-8)
444 int size_needed = WideCharToMultiByte(CP_UTF8, 0, wbuffer, -1, nullptr, 0, nullptr, nullptr);
445 std::string msg(size_needed, 0);
446 WideCharToMultiByte(CP_UTF8, 0, wbuffer, -1, &msg[0], size_needed, nullptr, nullptr);
447
448 onDebugString(msg);
449 }
450
451 break;
452 }
453
454 case RIP_EVENT: {
455 const RIP_INFO& rip = dbgEvent.u.RipInfo;
456 onRIPError( rip );
457 break;
458 }
459
460 default: {
461 onUnknownDebugEvent( dbgEvent.dwDebugEventCode );
462 break;
463 }
464 }
465 ContinueDebugEvent(dbgEvent.dwProcessId, dbgEvent.dwThreadId, cont);
466 }
467
468 CloseHandle(hProcessGlobal);
469 CloseHandle(hThreadGlobal);
470 return 0;
471}
472
473}
int start(std::string exeName)
Starts a process under debugging.
Definition debugger.cpp:87
virtual void onAttach()
Called after successfully attaching to an already running process.
DRReg isHardwareBreakpointAt(LPVOID address)
Checks if a hardware breakpoint exists at an address.
void enableSingleStep(HANDLE hThread)
Enables trap flag (single-step) for a thread.
Definition debugger.cpp:148
int loop()
Main debugger message loop.
Definition debugger.cpp:236
int attach(std::string exeName)
Attaches to a running process by name.
Definition debugger.cpp:55
bool hideDebugger()
Attempts to hide the debugger from basic anti-debug checks.
Definition debugger.cpp:14
uintptr_t ASLR(LPVOID address)
Applies the module ASLR slide to an LPVOID.
Definition debugger.cpp:46
bool clearHardwareBreakpoint(DRReg reg)
Clears a DRx slot across threads.
virtual void onAccessViolation(uintptr_t address, uintptr_t faultingAddress, long accessType)
Called on access violation (AV).
virtual void onThreadExit(DWORD threadID)
Called when a thread exits.
virtual void onEnd(DWORD exitCode, DWORD pid)
Called when the debuggee exits.
virtual void onDLLUnload(uintptr_t address, std::string dllName)
Called when a DLL is unloaded.
virtual void onUnknownException(uintptr_t addr, DWORD code)
Called on unknown exception.
virtual void onStart(uintptr_t imageBase, uintptr_t entryPoint)
Called when a new debuggee process is started.
virtual void onThreadCreate(HANDLE hThread, DWORD threadId, uintptr_t threadBase, uintptr_t startAddress)
Called when a thread is created in the debuggee.
int detach()
Detaches from the current debuggee.
Definition debugger.cpp:77
void setBreakpoint(LPVOID address)
Sets a software INT3 breakpoint at an address.
bool clearHardwareBreakpointOnThread(HANDLE hThread, DRReg reg)
Clears a DRx slot on a single thread.
hwBp_t getBreakpointByReg(DRReg reg)
Gets the breakpoint definition bound to a DRx register.
void decrementIP(HANDLE hThread)
Moves the instruction pointer one instruction backward (post-breakpoint fixup).
Definition debugger.cpp:158
virtual void onRIPError(const RIP_INFO &rip)
Called on RIP error (native debug port issues).
virtual bool onDLLLoad(uintptr_t address, std::string dllName, uintptr_t entryPoint)
Called when a DLL is loaded.
void restoreBreakpoint(LPVOID address)
Restores the original byte at a software breakpoint address.
virtual BreakpointAction onBreakpoint(uintptr_t address, HANDLE hThread)
Called on software breakpoint (INT3).
virtual void onDebugString(std::string dbgString)
Called when OutputDebugString is emitted by the debuggee.
bool setHardwareBreakpoint(hwBp_t bp)
Sets a hardware breakpoint for all existing (and future) threads where applicable.
void printIP(HANDLE hThread)
Prints the current instruction pointer (IP/EIP/RIP) of a thread.
Definition debugger.cpp:180
virtual BreakpointAction onHardwareBreakpoint(uintptr_t address, HANDLE hThread, DRReg reg)
Called on hardware breakpoint hit.
virtual void onUnknownDebugEvent(DWORD code)
Called on unhandled/unknown debug events.
Debugger()
Constructs a Debugger with default settings.
Definition debugger.cpp:6
uintptr_t baseImageBase
Typical image base (with ASLR).
Definition debugger.h:196
void actualizeThreadList()
Refreshes the internal thread list by querying the target process.
Definition debugger.cpp:193
Main Debugger file.
BreakpointAction
Specifies the action to take when a breakpoint is hit.
Definition debugger.h:37
@ SINGLE_STEP
Perform a single-step execution after hitting the breakpoint.
Definition debugger.h:40
@ RESTORE
Restore the original instruction at the breakpoint.
Definition debugger.h:39
@ BREAK
Stop execution at the breakpoint.
Definition debugger.h:38
DRReg
Hardware debug registers used for breakpoints.
Definition debugger.h:57
@ NOP
No register assigned.
Definition debugger.h:58
DWORD_PTR getEntryPoint(HANDLE hProcess, LPVOID baseAddress)
Gets the entry point address of a loaded module in a remote process.
Definition util.cpp:190
DWORD findProcessId(const std::string &processName)
Finds the process ID of a process by name.
Definition util.cpp:122
std::string getDllName(HANDLE hProcess, LPVOID lpImageName, BOOL isUnicode)
Retrieves the name of a DLL from a remote process.
Definition util.cpp:55
Hardware breakpoint configuration.
Definition debugger.h:91
Represents a thread in a debugged process.
Definition debugger.h:80
LPVOID threadBase
Base address of the thread.
Definition debugger.h:83
HANDLE hThread
Thread handle.
Definition debugger.h:81
DWORD threadId
Thread ID.
Definition debugger.h:82
LPVOID startAddress
Start address of the thread.
Definition debugger.h:84