您好,欢迎来到花生壳b2b外贸网信息发布平台!
18951535724
  • 计算机病毒常用手段

       2026-08-10 网络整理佚名1930
    核心提示:第2个参数lpfn则会设置回调函数的地址,也就是接下来第2步设置的钩子回调函数的地址。 _In_ HHOOK hhk //由安装钩子函数返回得到的句柄 与局部钩子不同的是

    460500587

    本文介绍的常用手段及技术,从源代码方面透彻地分析种种神秘的功能。

    一、键盘记录技术

    木马病毒的盗号功能往往利用键盘记录技术进行,主流技术就是利用HOOK,也就是钩子(Windows消息处理机制的监视点),链式结构,进行账号密码的截获。截获方式分为全局消息钩子(截获系统中所有进程的按键消息),局部消息钩子(只能记录特定程序当前线程的按键消息)。

    1、局部钩子

    首先先来了解一下什么是局部消息钩子,局部消息钩子使用过程分为以下三个步骤:

    (1)安装钩子。

    SetWindowsHookEx是用来安装钩子的函数,其函数原型如下。

    HHOOK WINAPI SetWindowsHookEx(

    _In_ int idHook, //钩子类型

    _In_ HOOKPROC lpfn, //回调函数地址

    _In_ HINSTANCE hMod, //钩子指向的模板句柄

    _In_ DWORD dwThreadId //安装钩子的线程ID

    );

    第1个参数idHook用于设置钩子函数截获的消息类型,主要使用有WH_CALLWNDPROC(使用SendMessage发送消息前安装钩子)、WH_CALLWNDPROCRET(使用SendMessage发送消息后安装钩子)、WH_GETMESSAGE(调用PeekMessage或者SendMessage后安装钩子)、WH_KEYBOARD(截获WM_KEYUP或WM_KEYDOWN时安装钩子)、WM_MOUSE(截获鼠标消息时安装钩子)。

    第2个参数lpfn则会设置回调函数的地址,也就是接下来第2步设置的钩子回调函数的地址。

    第3个参数hMod在局部消息钩子中设置为NULL。

    第4个参数dwThreadId,指定需要安装钩子函数的线程ID。这里可以先使用CreateToolhelp32Snapshot函数快照得出所有线程ID的快照,随后使用Process32First以及Process32Next遍历线程得到指定程序(可以通过程序名,“××.exe”)的线程ID。也可以通过FindWindowEx找到程序的窗口句柄,然后通过GetWindo的线程ID。也可以通过FindWindowEx找到程序的窗口句柄,然后通过GetWindowThreadProcessId的返回值得到线程ID。目前很多程序,例如QQ、YY等聊天工具均用GUI绘图制得,无法得到其窗口句柄,因此第2种方法失效。而第一种方法,广泛流行的并不是针对单一程序进行盗号,因此通常情况下会使用全局钩子。

    (2)设置钩子函数

    LRESULT CALLBACK HOOKxxx //名称自定义

    int nCode, //钩子目的代码

    WPARAM wParam, //发送或接受消息的参数

    LPARAM lParam //发送或接受消息的参数

    ... //对于各种消息的处理代码,如将接收到的消息通过CreateFile、WriteFile保存在某个位置等,如监测键盘大小写等。

    return CallNextHookEx(....)

    //钩子为链式结构,故需要一个回调函数,将信息从钩子间相互传递

    接着详细查看一下CallNextHookEx回调函数的作用。

    LRESULT WINAPI CallNextHookEx(

    _In_opt_ HHOOK hhk, //由安装钩子函数返回得到的句柄

    _In_ int nCode, //同HOOKxxx (因为保存了要传递给下一个钩子的全部信息,所以下面的参数均与自身钩子函数一致)

    _In_ WPARAM wParam, //同HOOKxxx

    _In_ LPARAM lParam //同HOOKxxx

    );

    (3)卸载钩子

    钩子使用完毕后如果不卸载,则会占用大量的系统资源,使得系统运行缓慢也提高了被用户发现的可能性,因此需要卸载钩子。

    BOOL WINAPI UnhookWindowsHookEx(

    _In_ HHOOK hhk //由安装钩子函数返回得到的句柄

    );

    2、全局钩子

    与局部钩子不同的是,全局钩子使用DLL文件加载函数。分析全局钩子之前,先来了解下什么是DLL。

    DLL(Dynamic Link Library)即动态链接库,因为每个程序的进程都有自己的内存空间,要监控键盘的所有按键信息,记录键盘的程序就需要加载进入其他程序的内存空间,然后记录这个程序使用键盘的情况,而DLL文件是可以动态地加载进其他程序的内存空间,从而调用钩子函数进行键盘记录,故需要将以上3个步骤的函数放入DLL文件内。

    先来看看DllMain函数参数的含义,参数一hModule即DLL自身的实例句柄,指向DLL文件被映射进进程空间的地址。

    参数二ul_reason_for_call是函数调用的原因,可以设置为以下4个值之一。

    DLL_PROCESS_ATTACH//被进程加载;

    DLL_PROCESS_DETACH//被进程卸载;

    DLL_THREAD_ATTACH//进程创建新进程时;

    DLL_THREAD_DETACH//线程终止时。

    参数三lpReserved,一般不用。

    若编译方式为C++(默认均为.cpp),则需要接着进行外链声明,即:

    extern "C" _declspec(dllexport)函数类型 函数名称(参数);

    若编译方式为C语言,则不需要extern声明。_declspec(dllexport)为导出函数标志。

    加载DLL的方式分为动态链接和静态链接。

    隐式链接:

    #include //加载头文件

    在主函数(WinMain)调用DLL文件的时候,需要前置声明加载该dll的LIB库(静态库,包含在DLL中创建的函数等)文件。

    #pragma comment(lib,"DLL文件名.lib");

    然后就可以正常使用DLL文件中的导出函数了。

    隐式链接的特点为:使用方式简单,但被多次调用时,因内存无法释放使内存开销大,从而导致系统运行缓慢。

    显式链接:

    HINSTANCE hInst = LoadLibrary("DLL文件名.dll"); //映射内存,得到句柄

    typedef 类型 (*xxx)(); //定义函数指针

    ××× 变量名 = (×××)GetProcAddress(hInst,"DLL导出函数名");

    ..... //键盘记录功能代码

    FreeLibrary(hInst); //释放内存

    显式链接比隐式链接复杂,但被多次调用时的内存开销小,这也是键盘记录程序作为首选的原因。下面接着分析上面函数:

    HMODULE WINAPI LoadLibrary(

    _In_ LPCTSTR lpFileName //加载DLL文件进入内存

    );

    该函数仅有一个参数lpFileName,若DLL文件和调用DLL的cpp原文件在同一目录下,则参数可以直接为“Dll名称.dll”;若不在同一个目录下,则需要写清楚要加载的DLL文件的路径。

    FARPROC GetProcAddress( //函数返回指定导出的DLL函数的地址

    HMODULE hModule, //加载进内存的DLL句柄

    LPCWSTR lpProcName //导出函数名

    );

    因为该函数导出的是地址信息,所以可以使用相关指针进行接收。

    最后一个函数如下。

    BOOL FreeLibrary( //释放加载的DLL(类似C/C++语言中malloc/new与free/delete的对应关系)

    HMODULE hLibModule //当前加载的DLL句柄

    );

    二、DLL注入

    DLL注入又称注入。大多数为了提高自身隐蔽性、目标程序的针对性、进程检测软件的躲避等能力,均采用这种方式。其中远程则是基于线程之间的,即通过在目标程序下创建线程来运行功能。

    先来了解DLL注入需要用到的函数,然后再通过其使用步骤来认识它。

    HANDLE WINAPI OpenProcess( //得到目标进程句柄

    DWORD dwDesiredAccess, //权限设置

    BOOL bInheritHandle, //句柄继承

    DWORD dwProcessId //目标PID(进程ID)

    );

    参数一dwDesiredAccess权限通常设置为PROCESS_ALL_ACCESS,即享有全部权限。参数二bInheritHamdle则设置为FALSE,即无句柄继承。参数三dwProcessId则为目标进程的PID。调用该函数得到句柄,这是假定为hPrcoess。

    LPVOID VirtualAllocEx( //为目标进程申请一段内存空间

    HANDLE hProcess, //目标进程句柄

    LPVOID lpAddress, //申请内存的起始地址

    DWORD dwSize, //内存大小,以字节为单位

    DWORD flAllocationType, //内存类型

    DWORD flProtect //内存权限

    );

    参数一wProcess为OpenProcess得到的进程句柄。参数二lpAddress一般设置为NULL,也就是由系统决定申请内存的起始地址。参数三dwsize分配的大小应为页内存的整数倍。参数四flAllocation Type一般选择为MEM_COMMIT,为特定的页面区域分配内存中或磁盘的页面文件中的物理存储空间。参数五flProtecf为PAGE_READWRITE,即可以读写该区域。

    BOOL WriteProcessMemory( //将信息写入内存

    HANDLE hProcess, //目标进程句柄

    LPVOID lpBaseAddress, //目标进程内存空间的首地址

    LPVOID lpBuffer, //写入数据的缓冲区,包含写入内存的首地址

    DWORD nSize, //写入的字节数

    LPDWORD lpNumberOfBytesWritten //实际写入的字节数

    );

    参数二lpBaseAddress即为VirtualAllocEx函数申请到的内存空间。为了下一步使用远程线程注入DLL,这步就应该将DLL文件的具体路径、文件名通过上述函数写入目标进程的内存空间,因此VirtualAllocEx的参数三dwSize以及本函数的参数四nSize则可以是包含DLL文件的具体路径的字符串长度。lpBuffer则设置为DLL文件的具体路径。

    HANDLE CreateRemoteThreadEx(

    HANDLE hProcess, //目标进程句柄

    LPSECURITY_ATTRIBUTES lpThreadAttributes, //定义新线程的安全描述符

    SIZE_T dwStackSize, //堆栈初始大小

    LPTHREAD_START_ROUTINE lpStartAddress, //线程函数的起始地址

    LPVOID lpParameter, //指针传参

    DWORD dwCreationFlags, //线程运行状态

    LPPROC_THREAD_ATTRIBUTE_LIST lpAttributeList, //新线程附加参数

    LPDWORD lpThreadId //返回线程ID

    );

    参数二lpThreadAttributes、参数七lpAttributeList以及参数八lpThreadId通常设置为NULL,分别表示无安全描述符、无附加参数、无线程ID返回。参数三dwStackSize若设置为0,则将以默认大小运行程序。参数四lpStartAddress则设置为导出函数在目标程序内存中的地址,LoadLibrary函数位于Kernel32.dll中,几乎所有进程启动均会加载kernel32.dll模块,并且在任何进程中该函数地址相同,可以使用GetModuleHandle函数从Kernel32.dll中获得DLL句柄,然后放入GetProcAddress,进而获得类型为FARPROC的LoadLibrary函数加载的地址,随后转换其类型为LPTHREAD_START_ROUTINE即可。而用于传参的指针则是由VirtualAllocEx函数返回得到,即用于传递内存分配的信息。参数dwCreationFlags设置为0,表示线程被创建后立刻运行。

    HMODULE GetModuleHandle( //得到DLL文件模块句柄

    LPCTSTR lpModuleName //DLL模块名

    );

    DWORD WaitForSingleObject( //用于等待线程完成

    HANDLE hHandle, //句柄对象

    DWORD dwMilliseconds //等待线程时间间隔

    );

    参数一hHamdle设置为由CreateRemoteThreadEx返回的线程句柄,而参数二dwMiUiseconcls则设置为INFINITE,意为无限等待。

    BOOL VirtualFreeEx( //释放使用VirtualAllocEx分配的内存

    HANDLE hProcess, //使用OpenProcess返回的目标进程句柄

    LPVOID lpAddress, //指向VirtualAllocEx分配的内存地址

    DWORD dwSize, //释放的区域大小,这里必须设置为0,与下一个参数对应

    DWORD dwFreeType //自由操作类型,设置为MEM_RELEASE,指向由

    VirtualAllocEx分配的区域

    );

    DLL注入具体分为以下几个步骤。

    (1)通过目标程序的PID(进程ID),使用OpenProcess函数得到进程句柄。

    (2)使用VirtualAllocEx及得到的句柄,在目标进程下创建一段内存空间。

    (3)使用WriteProcessMemory将DLL文件的路径信息写入内存。

    (4)使用GetModuleHandle以及GetProcAddress得到LoadLibrary函数的地址(为了使用LoadLibrary加载恶意DLL文件,需要得到LoadLibrary函数在进程中的地址)。

    (5)使用CreateRemoteThreadEx启动远程线程,加载恶意DLL文件。

    (6)使用WaitForSingleObject等待线程运行完毕退出。

    (7)使用VirtualFreeEx释放申请的内存。

    (8)使用CloseHandle关闭句柄。

    这样代码就加载进了正常程序内,并且进程监视等软件无法检测到运行的存在。当然,更加巧妙的隐藏自身的方式也可以将代码直接写入目标进程,那么下列步骤中的WriteProcessMemory就应将代码写入内存。随后利用步骤(4)得到代码内使用的API函数位于系统DLL文件在该进程内存空间的位置,那么此步骤应在目标进程空间内实现。为了便于得到写入代码的大小和地址,则往往将其封装入结构体,再使用sizeof(结构体)以及取地址符&(结构体)就可得到。

    同时也可以通过“隐藏”DLL模块,进而隐藏DLL文件名称以及路径来躲避检测工具的扫描。这就需要知道DLL模块在内存中具体加载的位置,然后才能对其进行修改并隐藏。因为DLL被用于注入目标程序的内存空间,所以在PEB中就会保存该DLL模块的信息,那么所谓“隐藏”,也就是对PEB中DLL模块的信息进行修改。

    首先来看一下32位系统下的TEB结构。在WinDbg中打开任意可执行程序,随后在0:000 >后输入!teb,显示结果如图1所示。

    图1 TEB显示结果

    从图1中可以得出TEB及PEB的地址(TEB:7ffdf000h;PEB:7ffdb000h)。随后输入dt _teb 7ffdf000来查看TEB结构信息,如图2所示。

    图2 TEB结构

    这里可以看出PEB结构位于TEB偏移0x30的地址上。而TEB结构的信息则存放于FS寄存器中,那么便可以通过汇编指令mov eax,fs:来得到PEB的地址。

    下面来查看PEB结构,输入dt _peb 7ffdb000得到PEB结构信息,如图3所示。

    图3 PEB结构

    其中有个重要的参数,相对PEB偏移地址为0x0c的Ldr结构,即struct _PEB_LDR_DATA *Ldr,其中Ldr为指向PEB_LDR_DATA结构的指针。

    MSDN中PEB结构如下。

    typedef struct _PEB

    UCHAR InheritedAddressSpace;

    UCHAR ReadImageFileExecOptions;

    UCHAR BeingDebugged;

    UCHAR SpareBool;

    PVOID Mutant;

    PVOID ImageBaseAddress;

    PPEB_LDR_DATA Ldr;

    }PEB,*PPEB;

    现在再使用WinDbg查看结构。输入dt _PEB_LDR_DATA 0x00351ea0查看_PEB_LDR_DATA结构(见图4)。

    图4 PEB_LDR_DATA结构

    查阅MSDN。

    typedef struct _PEB_LDR_DATA {

    ULONG Length; //+0x00

    BOOLEAN Initialized; //+0x04

    PVOID SsHandle; //+0x08

    LIST_ENTRY InLoadOrderModuleList; //+0x0c双向链表中包含进程中加载模块的节点

    LIST_ENTRY InMemoryOrderModuleList; //+0x14

    LIST_ENTRY InInitializationOrderModuleList; //+0x1c

    } PEB_LDR_DATA, *PPEB_LDR_DATA;

    这里的重点是参数三InMemoryOrderModuleList及参数四InLoadOrderModuleList,首先来看看它的类型_LIST_ENTRY。

    typedef struct _LIST_ENTRY {

    struct _LIST_ENTRY *Flink; //指向下一个链表节点的Blink指针

    struct _LIST_ENTRY *Blink; //指向上一个链表节点的Flink指针

    } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY;

    该结构体包含两个指针,作为链接模块节点的工具,由此可以看出PEB中保存模板信息是以链表的形式构成的,并且参数中的每一项指向名为_LDR_DATA_TABLE_ENTRY的结构体:

    typedef struct _LDR_DATA_TABLE_ENTRY {

    PVOID Reserved1;

    LIST_ENTRY InMemoryOrderLinks;

    PVOID Reserved2;

    PVOID DllBase; //DLL模块地址

    PVOID EntryPoint;

    PVOID Reserved3;

    UNICODE_STRING FullDllName; //DLL路径名

    BYTE Reserved4;

    PVOID Reserved5;

    union {

    ULONG CheckSum;

    PVOID Reserved6;

    };

    ULONG TimeDateStamp;

    } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

    很明显,该结构体存放了程序以及加载的DLL模块的地址DllBase和它们的路径信息,也就是UNICODE_STRING类型的参数FullDllName。

    typedef struct _UNICODE_STRING {

    USHORT Length; //长度

    USHORT MaximumLength; //最大长度

    PWSTR Buffer; //缓冲区

    } UNICODE_STRING;

    隐藏DLL的方式就是:

    (1)将保存注入的DLL文件模块的信息链进行脱链操作。

    (2)(可选)将信息链中保存的DLL路径及文件名清除。

    具体实现方法:

    (1)定位到程序Ldr的地址,以Ldr→InLoadOrderModuleList.Flink作为遍历模块的入口点。

    (2)遍历模块找到注入的DLL模块。

    (3)对该模块进行脱链、清除信息等操作。

    步骤一,通过使用如下汇编代码。

    当然也可以使用:

    接着使用pPEB→Ldr→InLoadOrderModuleList.Flink得到模块链表遍历入口点。

    步骤二,使用一个循环以pLdr→InLoadOrderModuleList.Flink作为链表头指针进行模块遍历,通过判断DLL模块地址,搜寻注入DLL文件的模块所在节点。

    步骤三,脱链操作也就是将所在节点的上下相邻节点的Flink、Blink指针指向进行修改,指针的指向将跳过该注入DLL模块的节点,那么该节点就从链表中脱离出来,也就实现了隐藏DLL操作,而清除信息则是使用memset等函数对参数FullDllName进行清零处理。

    首先使用CONTAINING_RECORD函数来获得LDR_DATA_TABLE_ENTRY结构的地址。

    Flink指向的是LDR_DATA_TABLE_ENTRY结构中的InMemoryOrderLinks成员,因此需要得到一个指向LDR_DATA_TABLE_ENTRY结构的指针,即使用CONTAINING_RECORD函数。

    在这里的用法则是PLDR_DATA_TABLE_ENTRY pLdrDataEntry = CONTAINING_RECORD(pLdr→InLoadOrderModuleList.Flink, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks),随后便可以使用pLdrDataEntry→FullDllName.buffer得到DLL文件信息,最后使用memset进行清零。

    而在64位系统中,使用WinDbg查看同一程序的情况则不同于32位系统,如图5、图6所示。

    图5 64位TEB结构

    图6 32位TEB结构

    PEB地址偏移并不是之前的0x30了,而是0x60,继续查看EB结构,如图7所示。

    图7 64位PEB结构

    Ldr的偏移地址同样也发生了改变,偏移地址改为0x18。

    那么对应的FS寄存器中的值也需要进行改变。接下来,介绍另一种方式即使用API函数进行获取PEB地址的操作。

    参数一指定为目标进程句柄,参数二ProcessInformationClass可取下列值。

    这里,显然取ProcessBasicInformation,其中保存了PEB结构信息。

    typedef struct _PROCESS_BASIC_INFORMATION {

    PVOID Reserved1;

    PPEB PebBaseAddress;

    PVOID Reserved2;

    ULONG_PTR UniqueProcessId;

    PVOID Reserved3;

    } PROCESS_BASIC_INFORMATION;

    该结构体的参数二即为PEB结构的基地址,也正是我们需要得到的信息。

    函数中参数三ProcessInformation用来接收PEB信息的缓冲区(以PROCESS_BASIC_INFORMATION类型声明缓冲区接收信息),参数四ProcessInformationLength表示信息的大小(sizeof即可)。该函数并未被微软公开,因此需要使用GetProcAddress和LoadLibrary从Ntdll.dll中获取该函数地址。调用该函数以后,便得到了PEB结构的地址。

    三、autorun.inf

    U盘在2007年大规模爆发,并且危害很大。2011年,随着微软发布名为KB967940的补丁后,U盘在XP系统下主动传播功能近乎失效,如今在Windows 7/8甚至10普及的今天,U盘更是失去了效果,但其原理却是值得一探究竟的。

    病毒主要利用了autorun.inf的特性——随U盘打开而自动运行的功能。以记事本的形式打开该文件,则文件如图8所示。

    图8 autorun.inf(1)

    autorun.inf文件(见图9)格式一般如下:

    open=打开的程序

    该程序往往是隐藏在U盘中的病毒文件。下面的shell等同样也起到打开文件的作用。

    图9 autorun.inf(2)

    就本例来说,内置该文件的U盘被双击打开以后,会自动运行coink.exe。该则会将自身复制进系统目录,然后添加到自启项,实现等功能。该通过监控USB接口信息,一旦检测到有U盘插入,则复制自身进入U盘并创建改写U盘的autorun.inf文件,然后通过隐藏自身来实现感染U盘的目的,进而继续进行传播。接下来具体认识它的这些功能是如何实现的。

    一般会创建三个文件,在系统根目录下和U盘中创建自身,即可执行病毒(exe、com等文件);在U盘里创建或替换autorun.inf文件。

    病毒检测U盘的手段并不神秘,不过是使用了GetDriveType函数而已。

    UINT WINAPI GetDriveType(

    LPCTSTR lpRootPathName //盘符名称

    );

    参数显然为盘符的名称了,例如C:。函数的返回值若为DRIVE_REMOVABLE,则表示是移动存储设备,如U盘等;若为DRIVE_FIXED,则为固定硬盘;若为DRIVE_CDROM,则为光驱。然后将这个函数放入循环,每隔一段时间检测全部磁盘就实现了检测U盘的功能。

    紧接着就是隐藏自身文件的功能,代码如下所示。

    BOOL SetFileAttributes(

    LPCTSTR lpFileName, //文件详细路径

    DWORD dwAttributes //文件属性

    );

    文件路径也就是autorun.inf以及病毒程序的具体路径,文件属性则设置为FILE_ATTRIBUTE_HIDDEN,那么文件就被隐藏了。

    四、劫持

    下面介绍病毒的各种劫持方式。

    1、浏览器劫持

    (1)hosts劫持

    hosts(见图10)文件路径一般为C:WindowsSystem32driversetc,可以用记事本打开,其作用是加快域名访问速度,也就是当用户输入某网址并访问时,若hosts中存有该网址与对应IP则会通过该IP解析并访问该网站。因此,病毒也会通过修改对应网站的IP,将用户输入的网址劫持至指定IP的网站,这是hosts劫持。

    图10 hosts文件

    (2)BHO劫持

    BHO(Browser Helper Object,浏览器辅助对象)是浏览器与程序员之间开放交互接口的业界标准。程序员通过这个接口可以编写代码控制浏览器的行为,别有用心的病毒编写者则会用此劫持浏览器,如篡改IE主页、弹广告等。

    BHO在注册表中的位置是HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindowsCurrentVersionExplorerBrowser Helper Objects,如图11所示。其下的项名即为对应的BHO。

    图11 BHO注册表

    在HKEY_LOCAL_MACHINESOFTWAREClassesCLSID下,可以找到BHO对应的注册项。其中CLSID属于GUID(Globally Unique Identifier),也称作UUID(Universally Unique Identifier),它是全局唯一标识符,作为COM类的标识符。

    其中InprocServer32下键值数据所表示的是BHO加载的DLL文件(见图12)。病毒则会将自身添加进BHO且加载恶意DLL来劫持浏览器。

    图12 加载DLL

    2、映像/镜像劫持

    映像劫持(Image File Execution Options, IFEO)是早期病毒对付杀毒软件的一大手段,其实也只是使用完全权限,在注册表HKEY_LOCAL_MACHINESOFTWAREMicrosoftWindows NTCurrentVersionImage File Execution Options下新建一个Debugger项,项的名称为劫持的软件名称,如cmd.exe。随后将该项的键值改为一个不存在的文件,比如asdadasdasdasd.exe,如图13所示。

    图13 IFEO

    每当打开cmd.exe时,系统会先访问该注册表下的Debugger键值所指向的文件,即asdadasdasdasd.exe,而该文件不存在时cmd.exe自然就无法被打开,同理作用于杀毒软件,这就是病毒对抗杀毒软件的原理。

    3、DLL劫持

    在加载DLL文件时,先会搜索程序所在的目录,若没有则会搜索系统目录。DLL劫持正是利用系统的这个机制,在程序目录下创建一个与系统DLL名称、导出函数相同(为了维持程序正常运行),但是内含病毒代码的DLL文件。当应用程序加载该DLL时,病毒代码也就神不知鬼不觉地运行了。

    五、反虚拟机技术

    鉴于许多安全人员通过虚拟机搭建系统作为分析的强有力手段,往往在自身代码中植入检测虚拟机的代码。

    1、检测虚拟机的一般手段

    检测虚拟机的一般手段主要是搜索相关VM虚拟机的字符串,如搜索虚拟机服务进程使用CreateToolhelp32Snapshot、Process32First、Process32Next三个函数遍历进程,来搜索虚拟机的相关进程。

    CreateToolhelp32Snapshot函数将对用户选择的类型进行快照,本例就是快速读取进程列表。参数一dwFlags有如下选择。

    TH32CS_INHERIT:快照句柄可以被继承。

    TH32CS_SNAPALL:所有的进程、线程、由th32ProcessID指定的进程模块。

    TH32CS_SNAPHEAPLIST:由th32ProcessID指定进程中的堆。

    TH32CS_SNAPMODULE:由th32ProcessID指定进程模块。

    TH32CS_SNAPMODULE32:由th32ProcessID指定64位程序进程中的32位进程模块。

    TH32CS_SNAPPROCESS:系统全部进程信息。

    TH32CS_SNAPTHREAD:系统全部线程信息。

    本例选择TH32CS_SNAPPROCESS,而参数二则设置为0,表示无指定进程。

    参数二指向了PROCESSENTRY32结构体。

    检测虚拟机具体流程则为:

    随后建立循环,在使用Process32First(hProcessSnap,&pe32),Process32Next(hProcessSn ap,&pe32)中间查找虚拟机进程,如使用strcmp(pe32.szExeFile,"VMwareUser.exe")语句。

    2、通过内存来检测虚拟机

    虚拟机为避免与物理主机冲突,它在内存映射方面与物理主机存在差异,因此可以通过检测内存的方式来检测虚拟机,如检测IDT(Interrupt Deor Table,中断描述符表)的地址,其中VMware虚拟机中IDT地址为0xFF××××××,而物理主机中IDT地址不会高于0xD0××××××,因此可以通过执行SIDT指令检测IDTR(Interrupt Deer Table Register,中断描述符表寄存器),IDTR用于存放IDT地址,然后判断返回LowIDTbase的第一个字节是否高于0XD0,以此来检测虚拟机环境。

    同理可以检测LDT(Local Deor Table,本地描述符表)与GDT(Global Deor Table,全局描述符表),当LDT位于0x0000时,为物理主机;当GDT位于0xFF××××××时,为虚拟主机。

    3、其他方法

    另外,还可以通过搜索“VMware”字符串来检测虚拟机的注册表项,进而检测虚拟机是否存在。此外也可以从物理硬件层面进行检测,如检测网卡的MAC地址、BIOS等。

    六、反调试技术

    侦测虚拟机的同时,也存在被调试器分析的风险,因此病毒会在自身代码中实现反调试技术,常见的反调试技术有以下几种。

    1、查询PEB

    使用NtQueryInformationProcess函数,并且将其参数二设置为ProcessDebugPort(0x7),返回0表明程序没有被调试,调试状态下将返回调试的端口号。

    以上三个函数均采用了检测PEB结构中的BeingDebugged参数信息,同样也可以通过汇编代码获取该信息。

    最后检测dbg的值,若dbg的值为0,则表明非调试环境。同样在PEB结构中被改变的还有一些Heap(堆)标志,如ProcessHeap、NTGlobalFlag。

    2、扫描进程

    程序可以使用CreateToolhelp32Snapshot、Process32First、Process32Next三个函数持续对进程进行扫描,查找如“ida.exe”“OllyDbg.exe”之类的调试软件,也可以直接使用FindWindow函数进行窗口查找。

    HWND FindWindow(

    LPCTSTR lpClassName, //窗口的类

    LPCTSTR lpWindowName //窗口的名称

    );

    参数一可以为NULL,参数二中只要包含调试程序名称的部分字符串即可,如FindWindow(NULL,"ollydbg ");即可。

    3、时间间隔检测

    这里介绍RDTSC指令。

    RDTSC指令返回的是自开机以来的CPU的周期数,返回的是一个64位的值EDXL:EAX(高32位在EDX,低32位在EAX),那么使用方法是如下。

    int time_first, time_last, time_sub

    _asm{

    rdtsc

    mov time_first, eax

    ...... //部分程序代码

    _asm{

    rdtsc

    mov time_last, eax

    time_sub = time_last - time_first

    通过比较time_sub的差值是否大于正常运行时间,从而判断程序是否位于调试环境,与之相似的函数有:

    DWORD GetTickCount(void)

    返回自计算机启动到调用该函数的时间差,以毫秒为单位。可以使用类似上文的方法,计算时间差,判断调试环境。

    4、加壳

    是运用加密算法将程序压缩,修改程序入口点(Original Entry Point, OEP),加密程序源码,在内存中加载时进行解码,从而阻止调试软件对程序代码在用户层面的修改。

    5、加花

    加“花”是指加入,由一些组成的干扰调试人员进行分析的代码,程序能够正常运行,但会造成反出错。

     
    举报收藏 0打赏 0评论 0
    更多>相关评论
    暂时没有评论,来说点什么吧
    更多>同类百科知识
    推荐图文
    推荐百科知识