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&安卓平台调试机制原理的主要内容,如果未能解决你的问题,请参考以下文章

Linu计划书

About 8.7 This Week

CTF Linux 命令执行常规bypass

Linux程序培训课程,带你养成良好的Linux使用习惯

About 8.7 This Week

2020 CTF暑假夏令营培训Day2 密码学Crypto 部分笔记