恶意代码常用技术解析-注入篇

恶意代码剖析之注入手艺

​ 在许多时刻为了能够对目的历程空间数据举行修改,或者使用目的历程的名称来执行自己的代码,实现危害用户的操作,通常是将一个DLL文件或者ShellCode注入到目的历程中去执行。这里分享四种常用的注入手艺,其中使用DLL注入的方式最为普遍。

全局钩子注入

​ 在Windows中大部份的应用程序都是基于新闻机制的,他们都有一个新闻历程函数,凭据新闻完成差别的功效。Windows操作系统提供的钩子机制就是用来截获和监视这些新闻的。根据钩子的局限差别,它们又可以分为局部钩子和全局钩子,局部钩子是针对某个线程的;而全局钩子则是作用于整个系统的基于新闻的应用。全局钩子需要使用DLL文件,在DLL中实现响应的钩子函数。

  • 要害函数安装钩子程序SetWindowsHookEx()
WINUSERAPI
HHOOK
WINAPI SetWindowsHookExA(
    _In_ int idHook,        // 要安装的钩子的类型例如键盘 鼠标 对话框等
    _In_ HOOKPROC lpfn,     // 一个指向钩子程序的指针
    _In_opt_ HINSTANCE hmod,// 包罗lpfn参数指向的钩子历程的DLL句柄
    _In_ DWORD dwThreadId); // 与钩子程序相关联的线程标识符

乐成返回DLL句柄,失败返回NULL

  • 卸载钩子函数UnhookWindowsHookEx
BOOL UnhookWindowsHookEx(
  HHOOK hhk
);
  • 钩子回调函数
// 示意将当前的钩子传递给钩子链中的下一个钩子
LRESULT
WINAPI CallNextHookEx(
    _In_opt_ HHOOK hhk,
    _In_ int nCode,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam);

远程线程注入

远程线程注入是指一个历程在另一个历程中建立线程的手艺,是一种经典的注入手艺

  • 函数OpenProcess()——打开目的历程
HANDLE
WINAPI OpenProcess(
    _In_ DWORD dwDesiredAccess,  // 接见历程工具
    _In_ BOOL bInheritHandle,    // 若此值为true,则此历程建立的历程将继续该句柄
    _In_ DWORD dwProcessId       // 要打开的内陆历程的PID
    );
// 返回值: 若乐成返回句柄,失败返回NULL
  • 函数VirtualAllocEx()

指定历程的虚拟地址空间内保留、提交或者更改内存的状态

LPVOID
WINAPI
VirtualAllocEx(
    _In_ HANDLE hProcess,      // 历程句柄
    _In_opt_ LPVOID lpAddress, // 指定要分配页面所需的起始指针,为NULL自动分配
    _In_ SIZE_T dwSize,        // 要分配内存的巨细
    _In_ DWORD flAllocationType, // 内存分配的类型:保留、提交和更改
    _In_ DWORD flProtect         // 页面区域的内存保护
    );
// 返回值:函数乐成返回分配的基址,失败返回NULL
  • 函数WriteProcessMemory()——在指定的历程中将数据写入内存区域
BOOL
WINAPI WriteProcessMemory(
    _In_ HANDLE hProcess,   // 要修改的历程句柄
    _In_ LPVOID lpBaseAddress, // 指向指定历程中写入数据的基地址指针
    _In_reads_bytes_(nSize) LPCVOID lpBuffer, // 指向缓冲区的指针
    _In_ SIZE_T nSize,    // 要写入指定历程的字节数
    _Out_opt_ SIZE_T* lpNumberOfBytesWritten  // 指向变量的指针,该变量吸收传输到指定历程的字节数
    );
// 返回值: 函数乐成 != 0;失败返回0 
// 注重:写入区域的内存要可接见,否则操作失败
  • 函数CreateRemoteThread()——实现注入的焦点函数在另一个历程的虚拟地址中建立运行的线程
HANDLE
WINAPI CreateRemoteThread(
    _In_ HANDLE hProcess,   // 要建立线程的历程的句柄
    _In_opt_ LPSECURITY_ATTRIBUTES lpThreadAttributes,
    		// 指向平安形貌符的指针
    _In_ SIZE_T dwStackSize,  // 客栈的初始巨细,若为0则新线程使用可执行文件的默认巨细
    _In_ LPTHREAD_START_ROUTINE lpStartAddress,
    		// 指向由线程执行类型为LPTHREAD_START_ROUTINE的应用程序界说的函数指针,并示意远程历程 中线程的起始地址,该函数必须存在于远程历程中
    _In_opt_ LPVOID lpParameter,  // 要传递给线程函数的变量的指针
    _In_ DWORD dwCreationFlags,   // 控制线程建立的标志
    _Out_opt_ LPDWORD lpThreadId  // 吸收线程标志符变量的指针
    );
// 返回值: 乐成 新线程的句柄,失败:返回NULL

​ 从以上这些函数的作用我们实现的原理就很清晰了,先在指定历程申请一段地址然后将准备好的shellcode或者一个DLL文件写入到这块内存空间中。

​ 注重:对于一些系统服务这样通常会注入失败,由于系统存在SESSION 0隔离的平安机制,需挪用一个加倍底层的ZwCreateThreadEx()来实现。

数据结构与算法(八):排序

APC行列注入

APC(Asynchronus Procedure Call)为异步历程挪用,是指函数在特定线程中被异步执行。在Windows系统中,APC是一种并发机制,用于异步IO或者定时器。

每一个线程都有自己的APC行列,使用QueueUserAPC函数把一个APC函数压入APC行列中。当处于用户模式的APC压入线程APC行列后,该线程并不直接挪用APC函数,除非该线程处于可通知状态,挪用的顺序为先入先出。

  • 函数
WINBASEAPI
DWORD WINAPI QueueUserAPC(
    _In_ PAPCFUNC pfnAPC,  // 指向APC函数的指针
    _In_ HANDLE hThread,   // 线程句柄
    _In_ ULONG_PTR dwData  // 由pfnAPC参数指向的APC函数的单个值
    );
// 返回值 乐成非0; 失败返回0

APC的注入原理是行使当线程被叫醒时APC中的注册函数会执行的机制,并以此去执行DLL加载代码,进而完成DLL注入。为了增添乐成率,可以向目的历程中的所有线程都插入APC

自界说HOOK

  • 自界说HOOK大致可以分为两类
    • inlineHOOK
    • IATHOOK
  • inlineHook是一种通过修改机器码的方式来实现HOOK的手艺

原理:对于一个正常的程序如下图,通过CALL指令来挪用函数。关于CALL指令相当于push 当前函数地址和jmp要执行的指令位置,即 push 0171B7B3 jmp 0171B430,这是我们正常执行00.0171B430这个函数的样子。
恶意代码常用技术解析-注入篇
我们在hook的时刻就是将CALL指令直接改成jmp指令,跳到我们自己编写的函数的位置,执行完成之后跳回函数原来指令的下一条指令0171B7B3,需要注重的是跳转偏移要多盘算5个字节

盘算公式: 跳转偏移 = 目的地址 – jmp所在的地址 – 5

  • 实现方式
  1. 获取函数的现实地址
  2. 修改内存分页属性
  3. 盘算跳转偏移,修改目的地址,还原内存属性
  4. 获取现实地址返回
void OnHook() {   
    //获取函数现实地址    
    HMODULE Module = GetModuleHandleA("kernel32.dll");    
    LPVOID func = GetProcAddress(Module, "OpenProcess");
    //保留5个字节    
    memcpy(g_oldCode, func, 5);
    //修改内存分页属性,由于代码段是不可写的,所有必须先将它的属性酿成可写    
    DWORD dwProtect;    
    VirtualProtect(func, 5, PAGE_EXECUTE_READWRITE, &dwProtect);    
    //盘算跳转偏移
    *(DWORD*)&g_newCode[1] = (DWORD)MyOpenProcess - (DWORD)func - 5;    
    //修改目的地址 
    memcpy(func, g_newCode, 5);    
    //还原内存分页属性    
    VirtualProtect(func, 5, dwProtect, &dwProtect); 
};
  • 用户层的IATHook是通过替换IAT表中函数的原始地址从而实现的Hook

​ 与通俗的InlineHook不一样,IATHook需要充实明白PE文件的结构才气完成,关于相对虚拟地址(RVA)、文件偏移地址(FOA)和加载基址等观点可以自行查阅相关资料。

  • 实现方式
//获取指定dll导出地址表的中函数地址 
DWORD * GetIatAddress(const char * dllName, const char* funName) {    
    // 1. 获取加载基址并转换成DOS头    
    auto DosHeader = (PIMAGE_DOS_HEADER)GetModuleHandle(NULL);
    // 2. 通过 DOS 头的后一个字段 e_lfanew 找到 NT 头的偏移    
    auto NtHeader = (PIMAGE_NT_HEADERS)(DosHeader->e_lfanew + (DWORD)DosHeader);
    // 3. 在数据目录表下标为[1]的地方找到导入表的RVA    
    DWORD ImpRVA = NtHeader->OptionalHeader.DataDirectory[1].VirtualAddress;
    // 4. 获取到导入表结构体,由于程序已经运行了,以是不需要转FOA    
    auto ImpTable = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)DosHeader + ImpRVA);
    // 遍历导入表,以一组全0的结构末端    
    while (ImpTable->Name)  
    {        
        // 获取当前导入表结构形貌的结构体的名称        
        CHAR* Name = (CHAR*)(ImpTable->Name + (DWORD)DosHeader);
        // 忽略巨细写举行对照,查看是否是需要的导入表结构        
        if (!_stricmp(Name, dllName))        
        {            
            // 找到对应的 INT 表以及 IAT 表            
            DWORD* IntTable = (DWORD*)((DWORD)DosHeader + ImpTable->OriginalFirstThunk);            
            DWORD* IatTable = (DWORD*)((DWORD)DosHeader + ImpTable->FirstThunk);            
            // 遍历所有的函数名称,包罗有/没有名称            
            for (int i = 0; IntTable[i] != 0; ++i)            
            {                
                // 比对函数是否存在函数名称表中                
                if ((IntTable[i] & 0x80000000) == 0)                
                {                    
                    // 获取到导入名称结构                    
                    auto Name = (PIMAGE_IMPORT_BY_NAME)((DWORD)DosHeader + IntTable[i]);
                    // 比对函数的名称                    
                    if (!strcmp(funName, Name->Name))                    
                    {                        
                        // 返回函数在IAT中保留的地址                        
                        return &IatTable[i];                    
                    }                
                }            
            }        
        }        
        ImpTable++;    
    }    
    return 0; 
}

总结

​ 钩子手艺总结起来就是通过种种手段来修改代码或者地址从而让程序来执行我们自己编写的代码,在剖析恶意程序时关注一下这些敏感的API函数组合,在查看程序基本信息的时刻就可以大致做出预测。下一篇继续分享常见的启动和隐藏手艺,继续刨析病毒的实现原理。

原创文章,作者:时事新闻,如若转载,请注明出处:https://www.28ru.com/archives/14330.html