首页
社区
课程
招聘
[原创]病毒木马常用手段之Debug Blocker
发表于: 2022-6-14 00:01 15590

[原创]病毒木马常用手段之Debug Blocker

2022-6-14 00:01
15590

在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层层嵌套,并结合多种反调试技术,给逆向分析人员带来极大的挑战。最后,革命尚未成功,同志仍需努力!!

#include <windows.h>
#include <tchar.h>
#include <stdio.h>
 
 
#define DEF_MUTEX_NAME      L"ReverseCore:DebugMe4"
 
 
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);
    }
}
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
 
 
#define DEF_MUTEX_NAME      L"ReverseCore:DebugMe4"
 
 
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 被寒江独钓_编辑 ,原因:
收藏
免费 12
支持
分享
最新回复 (2)
雪    币: 619
活跃值: (1593)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
2
说明:在调试途中虚拟机出了点问题,所以换了一个OD调试,这也是图中出现两个OD的原因
2022-6-14 00:02
0
雪    币: 23
活跃值: (73)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
受益匪浅
2022-8-2 18:04
0
游客
登录 | 注册 方可回帖
返回
//