在Debug Blocker中,调试进程与被调试进程它们之间是一种父子关系
文章中提到的父进程和子进程对应调试器和被调试进程
Debug Blocker是一种高级的反调试技术,而且这种技术常常被木马病毒利用
Debug Blocker是进程以调试方式运行自身或者其他可执行文件的技术。目的是增加逆向分析难度
1.父子关系
根据进程当时所扮演的不同角色(调试与被调试的关系),会执行不同的代码分支,并做出不同的动作
2.子进程不能再被其他调试器调试
在Windows中,一个进程无法同时被多个调试器调试,换言之,此时无法用OD进行调试(后面会讲如何调试)
3.父进程会影响子进程的执行
在Dbug Blocker技术中,调试器用来操纵被调试进程的执行分支,对子进程进行修改等操作,而且是一个连续的过程,换言之,在缺少调试进程的前提下,单独调试子进程无法正常运行
4.骨肉相连(这不是一道菜)
如果强制终止调试进程,那么子进程也会被终止,这也是Debug Blocker技术中非常高明的一点(仔细体会)
5.父进程处理子进程的异常
调试器和被调试者关系中,被调试进程发生的所有异常都由调试器处理,子进程故意触发异常(如内存访问异常),如果没有得到处理,程序将崩溃。子进程发生异常时,控制权转移到父进程,此时父进程修改被调试进程的执行分支,也可以对被调试进程进行加解密操作,或者修改寄存器、栈等特定值
基于以上特点,Debug Blocker 是一种比较高级的反调试手段。而且必须对调试进程进行分析调试,确定调试进程是如何处理异常的(执行逻辑),这样才能准确获取被调试进程的代码
进入main函数之后,先是调用CreateMutexW创建互斥体,然后根据返回值判断是以父进程运行还是以子进程运行,如果返回值是0xB7(0xB7说明已经有一个互斥体),就以子进程运行,此时程序是第一次运行,所以会执行父进程
进入父进程函数之后,先调用GetMouduleHandleW和GetModuleFileNameW获取到文件路径,然后调用CreateProcessW通过传入DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS参数以调试方式创建子进程
一般使用了Debug Blocker技术的程序,其核心代码基本上在子进程运行,所以我们重新运行程序,在判断CreateMutexW返回值的时候我们修改ZF标志位强制跳转到子进程函数。
可以看到将要执行的指令是LEA EAX,EAX ,很明显是一条非法指令,(而且后面的指令不是正常的指令),会触发异常,从而将程序执行流程转移到父进程,父进程可能对此处的代码执行解密操作或者修改EIP为其他地址。
这是第一次异常,我们自己一共预设了两个异常
以调试方式创建子进程后,会调用WaiteForDebugEvent等待子进程发生Debug事件,(此处会涉及到Debug_EVENT结构体和调试相关的知识,不清楚的同学请自行补课)
然后根据WaiteForDebugEvent返回的信息进行三次判断,第一次判断是否属于调试事件(EXCEPTION_DEBUG_EVENT),第二次判断发生异常的原因是否属于执行无效指令(EXCEPTION_ILLEGAL_INSTRUCTION),第三次判断发生异常的地址是否是我们提前预设好的地址(0x0040103F),如果这三个条件都成立,那么继续往下执行ReadProcessMemory,否则继续等待,直到条件成立
通过在第三次判断的下一条指令下断点,我们直接进入到ReadProcessMemory这一步,通过ReadProcessMemory读取加密的代码到一个Buffer中,然后进行异或解密
解密后的代码(大家仔细看解密后的代码)
然后调用WriteProcessMemory将解密后的代码写入到子进程
再调用GetThreadContext获取子进程的线程环境块
修改子进程的EIP后,调用SetContextThread将修改后的值设置到子进程
执行ContinueDebugEvent使子进程继续运行起来
子程序继续运行后还会触发第二个异常
第二次触发异常时,子进程仍然会把控制权交给父进程,通过一系列的判断之后,确定触发异常的地址是我们预设好的地址401048
然后将触发异常的地址处的内容(原来是8DC0,也就是LEA EAX,EAX)修改两个字节,修改为681C
接着调用ContinueDebugEvent使子进程继续执行
可以看到子进程顺利执行
如果我们要对一个使用了Debug Blocker技术的程序逆行分析,可以使用OD的编辑功能(Ctrl+E),直接修改401041-101054处的代码即可
上面这种情况适合要修改的代码量和代码逻辑简单的情况下使用,一旦要修改的代码比较多,情况比较复杂,而且分布在程序各个地方,就不方便了。
接下来就介绍一种适合复杂情况下使用的调试方法
记住 hProcess ThreadId ProcessId 这三个值
使用OD的编辑功能(Ctrl+E)在407EDF处写入2个字节的无限循环代码(EBFE),然后在4012D4-4012E4地址处写入汇编代码,实际上就是调用WriteProcessMemory给子进程写一个无限循环,如下图
接着手动调用(将之前记下来的进程ID 和线程ID当作参数传进去)ContinueDebugEvent使子进程继续运行(这里注意要使用寄存器传参,否则可能会出错)
调用DebugActiveProcessStop()函数将被调试进程从调试器中分离出来
直接F9运行
然后F12暂停
将401041处的无限循环代码还原为6A00
将401048处的代码修改为681C
Debug Blocker 技术调试起来非常繁琐,即使是简单的示例程序应用了此技术后调试起来也非常麻烦,其实原理并不复杂,主要是理清楚它们之间的关系,一般都是子进程故意触发异常,然后由父进程处理该异常,在日常分析中许多病毒木马会将Debug Blocker层层嵌套,并结合多种反调试技术,给逆向分析人员带来极大的挑战。最后,革命尚未成功,同志仍需努力!!
void DoParentProcess();
void DoChildProcess();
void _tmain(
int
argc, TCHAR
*
argv[])
{
HANDLE hMutex
=
NULL;
if
( !(hMutex
=
CreateMutex(NULL, FALSE, DEF_MUTEX_NAME)) )
{
printf(
"CreateMutex() failed! [%d]\n"
, GetLastError());
return
;
}
/
/
check mutex
if
( ERROR_ALREADY_EXISTS !
=
GetLastError() )
DoParentProcess();
else
DoChildProcess();
}
void DoChildProcess()
{
/
/
8D
C0 (
"LEA EAX, EAX"
) 富档 救登绰 疙飞绢
/
/
疙飞绢 辨捞 (
0x17
)
__asm
{
nop
nop
}
MessageBox(NULL, L
"ChildProcess"
, L
"DebugMe4"
, MB_OK);
}
void DoParentProcess()
{
TCHAR szPath[MAX_PATH]
=
{
0
,};
STARTUPINFO si
=
{sizeof(STARTUPINFO),};
PROCESS_INFORMATION pi
=
{
0
,};
DEBUG_EVENT de
=
{
0
,};
CONTEXT ctx
=
{
0
,};
BYTE pBuf[
0x20
]
=
{
0
,};
DWORD dwExcpAddr
=
0
, dwExcpCode
=
0
;
const DWORD DECODING_SIZE
=
0x14
;
const DWORD DECODING_KEY
=
0x7F
;
const DWORD EXCP_ADDR_1
=
0x0040103F
;
const DWORD EXCP_ADDR_2
=
0x00401048
;
/
/
create debug process
GetModuleFileName(
GetModuleHandle(NULL),
szPath,
MAX_PATH);
if
( !CreateProcess(
NULL,
szPath,
NULL, NULL,
FALSE,
DEBUG_PROCESS | DEBUG_ONLY_THIS_PROCESS,
NULL, NULL,
&si,
&pi) )
{
printf(
"CreateProcess() failed! [%d]\n"
, GetLastError());
return
;
}
printf(
"Parent Process\n"
);
/
/
debug loop
while
( TRUE )
{
ZeroMemory(&de, sizeof(DEBUG_EVENT));
if
( !WaitForDebugEvent(&de, INFINITE) )
{
printf(
"WaitForDebugEvent() failed! [%d]\n"
, GetLastError());
break
;
}
if
( de.dwDebugEventCode
=
=
EXCEPTION_DEBUG_EVENT )
{
dwExcpAddr
=
(DWORD)de.u.Exception.ExceptionRecord.ExceptionAddress;
dwExcpCode
=
de.u.Exception.ExceptionRecord.ExceptionCode;
if
( dwExcpCode
=
=
EXCEPTION_ILLEGAL_INSTRUCTION )
{
if
( dwExcpAddr
=
=
EXCP_ADDR_1 )
{
/
/
decoding
ReadProcessMemory(
pi.hProcess,
(LPCVOID)(dwExcpAddr
+
2
),
pBuf,
DECODING_SIZE,
NULL);
for
(DWORD i
=
0
; i < DECODING_SIZE; i
+
+
)
pBuf[i] ^
=
DECODING_KEY;
WriteProcessMemory(
pi.hProcess,
(LPVOID)(dwExcpAddr
+
2
),
pBuf,
DECODING_SIZE,
NULL);
/
/
change EIP
ctx.ContextFlags
=
CONTEXT_FULL;
GetThreadContext(pi.hThread, &ctx);
ctx.Eip
+
=
2
;
SetThreadContext(pi.hThread, &ctx);
}
else
if
( dwExcpAddr
=
=
EXCP_ADDR_2 )
{
pBuf[
0
]
=
0x68
;
pBuf[
1
]
=
0x1C
;
WriteProcessMemory(
pi.hProcess,
(LPVOID)dwExcpAddr,
pBuf,
2
,
NULL);
}
}
}
else
if
( de.dwDebugEventCode
=
=
EXIT_PROCESS_DEBUG_EVENT )
{
break
;
}
ContinueDebugEvent(de.dwProcessId, de.dwThreadId, DBG_CONTINUE);
}
}
void DoParentProcess();
void DoChildProcess();
void _tmain(
int
argc, TCHAR
*
argv[])
{
HANDLE hMutex
=
NULL;
if
( !(hMutex
=
CreateMutex(NULL, FALSE, DEF_MUTEX_NAME)) )
{
printf(
"CreateMutex() failed! [%d]\n"
, GetLastError());
return
;
}
/
/
check mutex
if
( ERROR_ALREADY_EXISTS !
=
GetLastError() )
DoParentProcess();
else
DoChildProcess();
}
void DoChildProcess()
{
/
/
8D
C0 (
"LEA EAX, EAX"
) 富档 救登绰 疙飞绢
/
/
疙飞绢 辨捞 (
0x17
)
__asm
{
nop
nop
}
MessageBox(NULL, L
"ChildProcess"
, L
"DebugMe4"
, MB_OK);
}
void DoParentProcess()
{
TCHAR szPath[MAX_PATH]
=
{
0
,};
STARTUPINFO si
=
{sizeof(STARTUPINFO),};
PROCESS_INFORMATION pi
=
{
0
,};
DEBUG_EVENT de
=
{
0
,};
CONTEXT ctx
=
{
0
,};
BYTE pBuf[
0x20
]
=
{
0
,};
DWORD dwExcpAddr
=
0
, dwExcpCode
=
0
;
const DWORD DECODING_SIZE
=
0x14
;
const DWORD DECODING_KEY
=
0x7F
;
const DWORD EXCP_ADDR_1
=
0x0040103F
;
const DWORD EXCP_ADDR_2
=
0x00401048
;
/
/
create debug process
GetModuleFileName(
GetModuleHandle(NULL),
szPath,
MAX_PATH);
if
( !CreateProcess(
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2022-6-14 22:52
被寒江独钓_编辑
,原因: