Isaiah大牛提及了1下,能不能自己构造一套调试的框架呢?不依赖于操作系统的提供的机制。比如调试事件。
于是偶发1个讨论帖子,看看有无大牛对这些东西感兴趣,指点1下
我们的目的.一些加壳软件使用自调试,比如arm的copymem2,我们不能方便的调试子进程,因为调试端口已经被占用,OllyDbg不能attach,我们希望实现1个自己的调试器注入进去,实现windbg类似的入侵调试功能,我的想法的制作1个开源debuger计划,欢迎大家帮助开发完善这个小工具,这个小工具并不打算替代OllyDbg,仅仅是在某些OllyDbg一时无法使用的时候作为临时debuger使用
SoftICE是完全自己实现了调试过程
OllyDbg则是利用Platform SDK和最新的DbgHelp.dll提供的API作为引擎写的debugger,也即是的debugger的Host端
其实CPU为Debug提供了强力的支持,比如设单步跟踪的标志,还有Drx机制
我们来看看
OllyDbg使用了下面的一系列API
================
GetThreadContext
SetThreadContext
DebugActiveProcess
ReadProcessMemory
WriteProcessMemory
CreateProcess with DEBUG_PROCESS flag
WaitForDebugEvent
================
其中一些API不是调试特有的,我们可以继续使用
我们需要修正的是DebugActiveProcess和WaitForDebugEvent
简单起见,我们只模拟attach过程
一个调试器,当DebugActiveProcess上一个进程以后,被调试进程主线程被挂起,这种状况将持续到我们的程序调用WaitForDebugEvent为止
背景知识: user-mode debuger工作流程
<1>debuger创建一个新进程,或attach一个正在运行的进程。我们称这个进程为B。
<2>debuger等待进程B产生debug事件
<3>进程B产生debug事件,发送消息给debuger,进程挂起,等待debuger指令。
<3>debuger处理debug事件,发送消息给进程B。
<4>进程B接受debuger发送的消息,进程复苏。
<5>循环2-4
消息传递是通过lpc port来进行的,流程如下所示:
debuger <--> kernel <--> process B
上面所说的消息结构如下:
typedef struct _DEBUG_MESSAGE
{
PORT_MESSAGE PORT_MSG;
DEBUG_EVENT DebugEvent;
}DEBUG_MESSAGE, *PDEBUG_MESSAGE;
typedef struct _PORT_MESSAGE
{
USHORT DataSize;//数据长度
USHORT MessageSize;//总长度
USHORT MessageType;
USHORT DataInfoOffset;
CLIENT_ID ClientId;
ULONG MessageId;
ULONG SectionSize;
//UCHAR Data[];
}PORT_MESSAGE, *PPORT_MESSAGE;
在\Microsoft SDK\samples\winbase\Debug目录下有几个简单的user-mode debuger的源代码,
大家可以参考一下。
我们不做进一步深入,只说说我们需要的东西,那就是DEBUG_EVENT
OllyDbg关键部分是对于DEBUG_EVENT的处理
背景知识:
EXCEPTION_DEBUG_EVENT:产生调试例外
CRATE_THREAD_DEBUG_EVENT:新的线程产生
CREATE_PROCESS_DEBUG_EVENT:新的进程产生。注:在DEBUG_ONLY_THIS_PROCESS时只有一次,
在DEBUG_PROCESS时如果该程序启动了子进程就可能有多次。
EXIT_THREAD_DEBUG_EVENT:一个线程运行中止
EXIT_PROCESS_DEBUG_EVENT:一个进程中止。注:在DEBUG_ONLY_THIS_PROCESS时只有一次,
在DEBUG_PROCESS可能有多次。
LOAD_DLL_DEBUG_EVENT:一个DLL模块被载入。
UNLOAD_DLL_DEBUG_EVENT:一个DLL模块被卸载。
本讨论帖子不求完美,尽可能多保存1点相关undocu资料
还是简单起先,我们只处理EXCEPTION_DEBUG_EVENT事件
typedef struct _EXCEPTION_DEBUG_INFO {
EXCEPTION_RECORD ExceptionRecord;
DWORD dwFirstChance;
} EXCEPTION_DEBUG_INFO, *LPEXCEPTION_DEBUG_INFO;
typedef struct _EXCEPTION_RECORD {
DWORD ExceptionCode;
DWORD ExceptionFlags;
struct _EXCEPTION_RECORD *ExceptionRecord;
PVOID ExceptionAddress;
DWORD NumberParameters;
ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
} EXCEPTION_RECORD, *PEXCEPTION_RECORD;
产生中断的地址ExceptionAddress和产生中断的信息代码ExceptionCode
这2个是我们必须模拟的,继续简单化处理,我们只管2个
EXCEPTION_BREAKPOINT:断点中断信息代码
EXCEPTION_SINGLE_STEP:单步中断信息代码
我们现在来看看中断,中断,我们先说int3,最简单,向指定地址写入CC
我们需要接管异常处理流程,那么我们的入侵调试器需要有1个dll
用于注入被调试进程,用来接管线程异常,从而我们能够处理异常
操作系统为每个线程分配一个TEB结构的数据块,并用FS指向它:
NT_TIB STRUCT
+0 ExceptionList dd ?
StackBase dd ?
StackLimit dd ?
SubSystemTib dd ?
union
FiberData dd ?
Version dd ?
ends
ArbitraryUserPointer dd ?
Self dd ?
NT_TIB ENDS
EXCEPTION_REGISTRATION_RECORD struct
+0 prev dd ?
+4 handler dd ?
EXCEPTION_REGISTRATION_RECORD ends
FS指向NT_TIB结构,FS:[0]就指向了EXCEPTION_REGISTRATION_RECORD结构
EXCEPTION_RECORD结构包含了发生异常的详细信息,这些信息独立于CPU
EXCEPTION_RECORD STRUCT
+0 ExceptionCode DWORD ? ;异常发生的原因代码
+4 ExceptionFlags DWORD ? ;异常处理操作标志
+8 pExceptionRecord DWORD ?
+0Ch ExceptionAddress DWORD ? ;异常发生的地址
+10h NumberParameters DWORD ?
+14h ExceptionInformation DWORD EXCEPTION_MAXIMUM_PARAMETERS dup(?)
EXCEPTION_RECORD ENDS
异常发生时,操作系统向引起异常的线程的堆栈压入3个结构:
EXCEPTION_RECORD,CONTEXT,EXCEPTION_POINTERS
EXCEPTION_POINTERS STRUCT
+0 pExceptionRecord DWORD ?
+4 ContextRecord DWORD ?
EXCEPTION_POINTERS ENDS
即是说:EXCEPTION_POINTERS包含指向EXCEPTION_RECORD,CONTEXT这两个结构的指针
CONTEXT结构包含了特定处理器的寄存器数据
CONTEXT STRUCT
ContextFlags DWORD ?
//调试寄存器
+4 iDr0 DWORD ?
+8 iDr1 DWORD ?
+0C iDr2 DWORD ?
+10 iDr3 DWORD ?
+14 iDr6 DWORD ?
+18 iDr7 DWORD ?
//浮点寄存器
FloatSave FLOATING_SAVE_AREA <>
//段寄存器
+8C regGs DWORD ?
+90 regFs DWORD ?
+94 regEs DWORD ?
+98 regDs DWORD ?
//通用寄存器
+9C regEdi DWORD ?
+A0 regEsi DWORD ?
+A4 regEbx DWORD ?
+A8 regEdx DWORD ?
+AC regEcx DWORD ?
+B0 regEax DWORD ?
//控制寄存器
+B4 regEbp DWORD ?
+B8 regEip DWORD ?
+BC regCs DWORD ?
+C0 regFlag DWORD ?
+C4 regEsp DWORD ?
+C8 regSs DWORD ?
ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION dup(?)
CONTEXT ENDS
我们最好直接更改线程的异常处理链,把我们的处理过程放在第一个位置
当然我们可以使用SetUnhandledExceptionFilter,但是那样我们的异常处理过程
被安排到了最后,但是作为做实验,使用SetUnhandledExceptionFilter可以
方便的安装处理过程
整理1下我们一共需要的工作--最简单的
1.1个dll,用来注入被调试进程,安装ExceptionFilter
2.主debuger,和dll通讯,接受异常分析并且处理
3.主exe,完成以下api工作,他们不是调试特有api,可以工作
GetThreadContext
SetThreadContext
ReadProcessMemory
WriteProcessMemory
4.关键功能,异常处理部分,偶打算完成int3,drx,内存断点
这里需要感谢大牛们对OllyDbg各种功能的完整分析
内存断点其实就是改变内存页属性,使之发生异常
从本质上来说,断点就是发生异常
5.界面设计,我的想法还是参照OllyDbg,一个反汇编窗口+1个寄存器窗口+1个内存查看窗口
其实堆栈查看和内存查看窗口差不多,不过是自动定位esp的地址显示的那片内存
不过偶基本不会做界面,汗..
代码部分偶还没有来得及写,其实主要是不太会写
希望大牛参与帮助 哈哈
欢迎大家参与讨论 哈哈
贴个测试代码
1.异常程序
// dbgtest.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include "windows.h"
int main(int argc, char* argv[])
{
MessageBox(0,"int3","dbgtest",MB_OK);
_asm int 3
MessageBox(0,"ok","dbgtest",MB_OK);
return 0;
}
这个程序直接运行是会异常的,
我们需要注入1个DbgDll,执行如下过程
DWORD WINAPI InjectDbgDll(IN LPINJECT_DATA lpData)
{
SetUnhandledExceptionFilter(UnhandledExceptionFilter);
return 0;
}
LONG WINAPI UnhandledExceptionFilter(IN struct _EXCEPTION_POINTERS* ExceptionInfo)
{
return -1;
}
什么也不处理,直接返回,这样这个程序就可以处理异常.运行下去了
不过这个模式不适合int3断点,int3断点需要还原代码,eip-1
但是内存异常和drx这样处理应该可以
[课程]FART 脱壳王!加量不加价!FART作者讲授!