2022CTF培训windows&linux&安卓平台调试机制原理
Posted _sky123_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了2022CTF培训windows&linux&安卓平台调试机制原理相关的知识,希望对你有一定的参考价值。
windows平台调试机制原理
手动编写一个简易调试器
创建待调试进程
使用 CreateProcess 函数创建待调试进程,创建时指定 dwCreationFlags 参数为 DEBUG_ONLY_THIS_PROCESS 将会告诉操作系统我们需要让当前调用者(线程)接管所有子进程的调试事件,包括进程创建、进程退出、线程创建、线程退出以及最重要的运行时异常等等。
STARTUPINFO si = 0 ;
PROCESS_INFORMATION pi = 0 ;
void CreateDebuggee()
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
ZeroMemory(&pi, sizeof(pi));
CreateProcess(ProcessNameToDebug, NULL, NULL, NULL, FALSE,
DEBUG_ONLY_THIS_PROCESS, NULL, NULL, &si, &pi
);
/*
specifying DEBUG_ONLY_THIS_PROCESS as the sixth parameter (dwCreationFlags).
With this flag, we are asking the Windows OS to communicate this thread for all debugging events,
including process creation/termination, thread creation/termination, runtime exceptions, and so on.
*/
Debugger Loop
Debugger Loop(调试器循环)是所有调试器最核心的部分,它主要围绕 WaitForDebugEvent API 进行工作,WaitForDebugEvent 接受两个参数,一个参数为指向 DEBUG_EVENT 的指针,另一个参数为 timeout(我们一般指定为 INFINITE),只需要包含 Windows 相关头文件即可使用这个 API,它的结构如下所示:
BOOL WaitForDebugEvent(DEBUG_EVENT* lpDebugEvent, DWORD dwMilliseconds);
其中,DEBUG_EVENT 结构包括调试中产生的所有事件的信息,它的定义如下:
typedef struct _DEBUG_EVENT
DWORD dwDebugEventCode;
DWORD dwProcessId; // 进程 pid
DWORD dwThreadId; // 线程 tid
union
EXCEPTION_DEBUG_INFO Exception;
CREATE_THREAD_DEBUG_INFO CreateThread;
CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
EXIT_THREAD_DEBUG_INFO ExitThread;
EXIT_PROCESS_DEBUG_INFO ExitProcess;
LOAD_DLL_DEBUG_INFO LoadDll;
UNLOAD_DLL_DEBUG_INFO UnloadDll;
OUTPUT_DEBUG_STRING_INFO DebugString;
RIP_INFO RipInfo;
u;
DEBUG_EVENT, *LPDEBUG_EVENT;
一旦 WaitForDebugEvent 函数返回(它是阻塞的,没有消息的情况下它是不会返回的),我们就需要去使用自己定义的函数对异常进行处理,处理完毕之后,我们需要调用 ContinueDebugEvent API,告诉操作系统我们准备好接受下一个 DebugEvent,我们编写一个简易的 DebugLoop 如下:
for (;;)
if (!WaitForDebugEvent(&debug_event, INFINITE))
return 0;
DWORD dbg_status;
ProcessDebugEvent(&debug_event, &dbg_status); // User-defined function, not API
ContinueDebugEvent(
debug_event.dwProcessId,
debug_event.dwThreadId,
dbg_status
);
其中,我们调用 ContinueDebugEvent 传递的 dbg_status 由我们自己的 ProcessDebugEvent 函数进行设置,dwProcessId 和 dwThreadId 指定进程以及线程,这个参数我们可以直接沿用 WaitForDebugEvent 返回的结果。
处理调试事件
Windows 中主要有 9 种调试事件,在异常事件的类别下有多达 20 种不同的异常事件类型。
结合之前我们讨论过的 DEBUG_EVENT 结构体的定义,WaitForDebugEvent 在成功返回之后会将 DEBUG_EVENT 结构体中的数据填充好,dwDebugEventCode 指定的是调试事件的类型,根据类型的不同,联合类型 u 包括事件的具体信息,我们应该只使用 union 中相应类型的信息(例如如果发生的调试事件类型为 OUTPUT_DEBUG_STRING_EVENT,则我们应该使用 union 中的 OUTPUT_DEBUG_STRING_INFO。
编写自己的 ProcessDebugEvent 函数,这个函数的基本框架如下:
void ProcessDebugEvent(DEBUG_EVENT* dbg_event, DWORD* dbg_status)
switch (dbg_event->dwDebugEventCode)
case EXIT_THREAD_DEBUG_EVENT:
printf("The thread %d exited with code: %d\\n",
dbg_event->dwThreadId,
dbg_event->u.ExitThread.dwExitCode
);
*dbg_status = DBG_CONTINUE;
break;
case CREATE_THREAD_DEBUG_EVENT:
printf("Thread 0x%x (Id: %d) created at: 0x%x\\n",
dbg_event->u.CreateThread.hThread,
dbg_event->dwThreadId,
dbg_event->u.CreateThread.lpStartAddress
);
*dbg_status = DBG_CONTINUE;
break;
上述代码将会处理两种调试消息:
- EXIT_THREAD_DEBUG_EVENT,有线程退出
- CREATE_THREAD_DEBUG_EVENT,有线程创建
运行程序,我们会发现我们的调试器正常接管到了子进程新线程创建的消息,退出子程序,我们能够接管到子进程线程退出的消息。
实现断点
我们通过 INT3 异常实现一个断点,在调试器中我们编写如下函数:
BYTE Bp(LPVOID BpAddr)
BYTE INT3 = 0xcc;
BYTE ORG = 0;
ReadProcessMemory(pi.hProcess, BpAddr, &ORG, sizeof(BYTE), NULL);
WriteProcessMemory(pi.hProcess, BpAddr, &INT3, sizeof(BYTE), NULL);
FlushInstructionCache(pi.hProcess, BpAddr, 1);
return ORG;
这个函数将会对当前正在调试的进程的 BpAddr 位置设置一个 INT3 断点,注意,在使用 WriteProcessMemory 函数进行内存写入操作之后,如果写入的是目标程序的代码,则需要进行 FlushInstructionCache 操作来刷新指令缓存。
随后我们需要在自己的 ProcessDebugEvent 函数中实现对 INT3 异常的接管:
case EXCEPTION_DEBUG_EVENT: // 异常消息
EXCEPTION_DEBUG_INFO& exception = dbg_event->u.Exception;
PVOID& addr = exception.ExceptionRecord.ExceptionAddress;
switch (exception.ExceptionRecord.ExceptionCode)
case STATUS_BREAKPOINT: // Same value as EXCEPTION_BREAKPOINT
// 在此处响应
break;
default:
如果我们需要继续运行程序,则需要首先恢复未设置断点的状态,然后将 Eip 减一(因为调试器接到的线程上下文中,Eip 的值是发生异常的位置 +1 的值),并通知操作系统继续运行程序。
用来恢复断点的函数如下:
void Recover(LPVOID Addr, BYTE data)
WriteProcessMemory(pi.hProcess, Addr, &data, sizeof(BYTE), NULL);
FlushInstructionCache(pi.hProcess, Addr, 1);
第二个参数使用我们 Bp 函数返回的值即可。
使用调试器完成 CTF VM 题的爆破
观察程序
main 函数如下,输入的数据对应的字符串的指针作为数据的一部分传给 VM 函数,执行完 VM 函数后将 输入的字符串 input 与 dst 比较。
int __cdecl main(int argc, const char **argv, const char **envp)
int v3; // ecx
int data[162]; // [esp+0h] [ebp-2CCh] BYREF
char input[32]; // [esp+288h] [ebp-44h] BYREF
unsigned __int8 op_code[32]; // [esp+2A8h] [ebp-24h] BYREF
puts("Input your flag: \\n");
memset(input, 0, sizeof(input));
scanf("%s", input);
*(_DWORD *)op_code = 0x3040500; // [0, 5, 4, 3, 15, 5, 14, 5, 12, 2, 3, 1, 8, 6, 5, 1, 9, 8, 5, 10, 3, 1, 8, 7, 1, 5, 8, 2, 1, 5, 11, 13]
*(_DWORD *)&op_code[4] = 0x50E050F;
*(_DWORD *)&op_code[8] = 16974348;
*(_DWORD *)&op_code[12] = 17106440;
*(_DWORD *)&op_code[16] = 168101897;
*(_DWORD *)&op_code[20] = 117965059;
*(_DWORD *)&op_code[24] = 34080001;
*(_DWORD *)&op_code[28] = 218825985;
data[0] = (int)input;
data[1] = 0;
data[2] = 22;
data[3] = 23;
data[4] = 204;
data[5] = 1;
data[6] = -25;
data[7] = 22;
data[8] = 23;
data[9] = 204;
data[10] = 1;
data[11] = -25;
data[12] = 22;
data[13] = 23;
data[14] = 204;
data[15] = 1;
data[16] = -25;
data[17] = 22;
data[18] = 23;
data[19] = 204;
data[20] = 1;
data[21] = -25;
data[22] = 22;
data[23] = 23;
data[24] = 204;
data[25] = 1;
data[26] = -25;
data[27] = 22;
data[28] = 23;
data[29] = 204;
data[30] = 1;
data[31] = -25;
data[32] = 22;
data[33] = 23;
data[34] = 204;
data[35] = 1;
data[36] = -25;
data[37] = 22;
data[38] = 23;
data[39] = 204;
data[40] = 1;
data[41] = -25;
data[42] = 22;
data[43] = 23;
data[44] = 204;
data[45] = 1;
data[46] = -25;
data[47] = 22;
data[48] = 23;
data[49] = 204;
data[50] = 1;
data[51] = -25;
data[52] = 22;
data[53] = 23;
data[54] = 204;
data[55] = 1;
data[56] = -25;
data[57] = 22;
data[58] = 23;
data[59] = 204;
data[60] = 1;
data[61] = -25;
data[62] = 22;
data[63] = 23;
data[64] = 204;
data[65] = 1;
data[66] = -25;
data[67] = 22;
data[68] = 23;
data[69] = 204;
data[70] = 1;
data[71] = -25;
data[72] = 22;
data[73] = 23;
data[74] = 204;
data[75] = 1;
data[76] = -25;
data[77] = 22;
data[78] = 23;
data[79] = 204;
data[80] = 1;
data[81] = -25;
data[82] = 22;
data[83] = 23;
data[84] = 204;
data[85] = 1;
data[86] = -25;
data[87] = 22;
data[88] = 23;
data[89] = 204;
data[90] = 1;
data[91] = -25;
data[92] = 22;
data[93] = 23;
data[94] = 204;
data[95] = 1;
data[96] = -25;
data[97] = 22;
data[98] = 23;
data[99] = 204;
data[100] = 1;
data[101] = -25;
data[102] = 22;
data[103] = 23;
data[104] = 204;
data[105] = 1;
data[106] = -25;
data[107] = 22;
data[108] = 23;
data[109] = 204;
data[110] = 1;
data[111] = -25;
data[112] = 22;
data[113] = 23;
data[114] = 204;
data[115] = 1;
data[116] = -25;
data[117] = 22;
data[118] = 23;
data[119] = 204;
data[120] = 1;
data[121] = -25;
data[122] = 22;
data[123] = 23;
data[124] = 204;
data[125] = 1;
data[126] = -25;
data[127] = 22;
data[128] = 23;
data[129] = 204;
data[130] = 1;
data[131] = -25;
data[132] = 22;
data[133] = 23;
data[134] = 204;
data[135] = 1;
data[136] = -25;
data[137] = 22;
data[138] = 23;
data[139] = 204;
data[140] = 1;
data[141] = -25;
data[142] = 22;
data[143] = 23;
data[144] = 204;
data[145] = 1;
data[146] = -25;
data[147] = 22;
data[148] = 23;
data[149] = 204;
data[150] = 1;
data[151] = -25;
data[152] = 22;
data[153] = 23;
data[154] = 204;
data[155] = 1;
data[156] = -25;
data[157] = 22;
data[158] = 23;
data[159] = 204;
data[160] = 1以上是关于2022CTF培训windows&linux&安卓平台调试机制原理的主要内容,如果未能解决你的问题,请参考以下文章