计算机病毒常用手段

article/2025/6/10 18:05:03

460500587

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

一、键盘记录技术

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

1、局部钩子

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

(1)安装钩子。

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

HHOOK WINAPI SetWindowsHookEx(

_In_ int idHook, //钩子类型

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

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

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

);

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(....)

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

}

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

);

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 //由安装钩子函数返回得到的句柄

);

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//线程终止时。

DLL_PROCESS_ATTACH//被进程加载;

DLL_PROCESS_DETACH//被进程卸载;

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

DLL_THREAD_DETACH//线程终止时。

参数三lpReserved,一般不用。

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

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

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

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

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

隐式链接:

#include<"Dll库头文件.h"> //加载头文件

#include<"Dll库头文件.h"> //加载头文件

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

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

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

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

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

显式链接:

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

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

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

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

FreeLibrary(hInst); //释放内存

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

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

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

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

FreeLibrary(hInst); //释放内存

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

HMODULE WINAPI LoadLibrary(

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

);

HMODULE WINAPI LoadLibrary(

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

);

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

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

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

LPCWSTR lpProcName //导出函数名

);

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

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

LPCWSTR lpProcName //导出函数名

);

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

最后一个函数如下。

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

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

);

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)

);

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 //内存权限

);

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 //实际写入的字节数

);

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

);

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 //等待线程时间间隔

);

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分配的区域

);

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:[0x30]来得到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;

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;

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;

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[2];

LIST_ENTRY InMemoryOrderLinks;

PVOID Reserved2[2];

PVOID DllBase; //DLL模块地址

PVOID EntryPoint;

PVOID Reserved3;

UNICODE_STRING FullDllName; //DLL路径名

BYTE Reserved4[8];

PVOID Reserved5[3];

union {

ULONG CheckSum;

PVOID Reserved6;

};

ULONG TimeDateStamp;

} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;

typedef struct _LDR_DATA_TABLE_ENTRY {

PVOID Reserved1[2];

LIST_ENTRY InMemoryOrderLinks;

PVOID Reserved2[2];

PVOID DllBase; //DLL模块地址

PVOID EntryPoint;

PVOID Reserved3;

UNICODE_STRING FullDllName; //DLL路径名

BYTE Reserved4[8];

PVOID Reserved5[3];

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;

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[2];

ULONG_PTR UniqueProcessId;

PVOID Reserved3;

} PROCESS_BASIC_INFORMATION;

typedef struct _PROCESS_BASIC_INFORMATION {

PVOID Reserved1;

PPEB PebBaseAddress;

PVOID Reserved2[2];

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)格式一般如下:

[AutoRun]

open=打开的程序

[AutoRun]

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 //盘符名称

);

UINT WINAPI GetDriveType(

LPCTSTR lpRootPathName //盘符名称

);

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

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

BOOL SetFileAttributes(

LPCTSTR lpFileName, //文件详细路径

DWORD dwAttributes //文件属性

);

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 //窗口的名称

);

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

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)

DWORD GetTickCount(void)

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

4、加壳

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

5、加花

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


http://www.hkcw.cn/article/NRjfcrhvPY.shtml

相关文章

原创唐太宗的得力助手 十八学士两大将军都是谁

唐太宗李世民,是中国历史上一位杰出的皇帝,他开创了著名的贞观之治,奠定了唐朝的繁荣基础。在唐太宗的统治时期,他身边聚集了一批才华横溢的文臣武将,其中最为人称道的是十八学士与两大将军。这些人才不仅是唐太宗的得力助手,更是唐朝建立和发展的关键人物。十八学士是唐…

禄来福来 禄来视角的独特创造者

1916年,不伦瑞克的一家照相机厂的工厂,有个人设计了一种新的胶卷照相机,但是在厂内没有人采纳他的设计,因为厂里的人担心这样的照相机无法解决胶卷的平面问题,此外当时生产的照相底板相机的销售也很好。他的名字就是雷因赫海德克。1916年,有几个人可曾想到海德克这个名字…

朱元璋出一上联,朱棣对出绝妙下联,朱元璋听后脸色大变:必谋反

朱元璋有嫔妃无数,但心中挚爱的却只有马皇后一人,朱元璋有儿孙满堂,但眼里却只看得到皇太子朱标以及皇太子朱允炆。所以,马皇后死后,朱元璋不再立后,朱标死后,朱元璋越过二十几个儿子直接把皇位传给了孙子朱允炆。朱标和朱允炆父子天性懦弱善良,与朱元璋的性格大相径庭…

原创假冒金庸的天龙九部,替段正淳和段延庆写前传,你看过吗?

武侠小说红火的那些年,假冒金庸的小说层出不穷。书商们以及一些无良作者,靠着“金庸”两个字欺世盗名,赚得钵满盆满。 当然,一些假冒金庸的小说,其实颇有可读之处,时隔多年再重温,感觉还挺不错的。 我是真游泳的猫,一个看武侠24年的老武侠迷,大家一定要关注我呀。 今天…

【案件】全国首例“呼死你”团伙被摧毁 实施12亿余次恶意呼叫

内容提要 大家知道,“呼叫死”系统最早是用来对付小广告的,而你可能想不到,有人竟把“呼死你”经营成了黑色产业链,最近广东警方就摧毁了两个“呼死你”团伙,嫌疑人就是通过非法软件平台,发出大量骚扰电话或短信,逼的你不得不花钱消灾。这些人疯狂到啥程度?8个月对全国…

洛克王国:它用眼神就能够弄垮对手,即使强大的恩佐都不是它对手

本篇文章由企鹅号小张聊动漫独家发布在腾讯企鹅号,切勿抄袭转载,否则邮箱举报封号为止。(已邮箱举报QQ看点账号动漫二次元、每日趣味录、动漫逍遥君。) 洛克迷们!大家好!随着洛克王国的不断发展!如今越来越多强力的宠物也是陆续登入到洛克王国中,而除了新宠物的登场更加…

原创斗罗大陆:史莱克七怪后代的天赋如何?他们都是谁?

在唐三与小舞一同化解了斗罗大陆上的危机以后,史莱克七怪便陆续成神,一同飞升到了斗罗神界之中。在神界漫长的岁月里,史莱克七怪也是接连拥有了属于自己的后代。那么史莱克七怪都有哪些后代?他们后代的天赋如何?一、唐三与小舞 唐三与小舞一共孕育了两个后代,二人的强大血…

曾经风靡一时的韩国男星,现在都过得如何?其中张根硕最可惜

随着韩剧在中国的流行,韩国的男艺人也变成了大家心目中的男神,前几年的时候,大陆娱乐圈哈韩气息浓厚,韩星层出不穷。回想当年这些韩国男明星只是风靡一时,现在他们都怎么样了呢?提到红了很久的韩国男明星,小编首先想到就是Rain,Rain原名叫郑智薰,1982年的,今年刚好本…

一组成年人秒懂的内涵漫画

展开全文来源:微信公众号“兔姐漫画” 欢迎转发点赞 转载请联系授权 ▼点击查看更多文章责 编 | 任晓蓉 编 辑 | 朱 琛 肖 健(团河南省委) 共青团中央 微信号:gqtzy2014

央视澄清了防辐射服 孕妇必要穿防辐射服吗

关于防辐射服是能防辐射,还是不能防辐射,近年来一直是大家热议的话题。对此央视给防辐射服进行了多次的实验检测和探讨,下面我们就来了解一下央视澄清了防辐射服的相关知识,看看孕妇必要穿防辐射服吗? 防辐射服一、央视澄清了防辐射服 为避免大众,尤其是孕妇们出现不必要…

原创娱乐圈到底有多乱“潜规则”无处不在,就连金马影后也难逃魔爪

文字|咸鱼千万不要加盐 编辑|咸鱼千万不要加盐 娱乐业是一个巨大的利润网络。有些人为了更多的利益,愿意出卖自己的“身体”。他们可能在别人面前看起来很富有,但背后却隐藏着很多肮脏的东西。为了出名,张钰被30多名导演“隐藏”,但最终的结果却是被集体封杀。吴京曾在接受…

网站兼职SEO关键词优化推广靠谱吗?

兼职SEO是指在业余时间从事SEO相关工作,以获取额外收入的人。与全职SEO相比,兼职SEO的工作时间和工作强度更加灵活,更适合那些希望在业余时间赚取额外收入的人。 以下是一些兼职SEO可以考虑的职位: SEO助理:兼职SEO助理需要协助SEO专员进行网站的日常维护和优化,包括分析…

原创高层接连被查,拥有8.8万家门店的沙县小吃问题在哪?

文/纸不语 近日,福建省三明市沙县区纪委监委官网消息显示,沙县区小吃文化旅游发展集团有限公司董事长童友健涉嫌严重职务违法,目前正接受沙县区监委监察调查。 据天眼查APP,童友健同时还担任“沙县小吃集团有限公司”的法人。 2024年5月,该集团副总经理徐振海也曾被通报涉…

原创罗曼回归剧情备受期待,未来有望组建新势力,家族猛将实为卧底?

在WWE支持者心中,罗曼雷恩斯拥有无可替代的地位。自他短暂离场以来,回归的愿望愈发强烈。大家对于这位过去标志性人物怀有深深的眷恋,并期待着他以全新的姿态和故事展示在观众眼前。每一次罗曼的缺席都让粉丝们倍感遗憾,他们热切期盼他重新回到赛场,引领WWE攀登更高的巅峰…

UNICOOL | 这位法国女人,一本现代女性典范教科书

时髦的你们肯定知道最近因vogue在官网上出了一个名为“1980s supermodel’s age defying beauty routine”的视频而大火。这支视频内容就像所有美妆博主流行拍护肤视频那样,可不同的是这个视频的女主角是60岁的超模Joan Severance。此时此刻,想想还在街头跳广场舞或晨起在公园…

印尼十大顶级酒吧(上) | 印尼小众旅行攻略

1. 云吧台(Cloud Lounge)云吧台(Cloud Lounge)位于印尼广场(Plaza Indonesia)的四十九楼,三百六十度无死角俯瞰整个雅加达的夜空,绚丽多彩。在伏特加房(Vodka Room)可以喝到零度的伏特加,号称是印尼第一家! 虽然这里是观看整个城市的最佳酒吧,但是消费却也不比其他…

2024届艺考生必看!统考时间、考试内容、评分标准

河南省艺术类统考时间音乐类专业考试时间: (一)乐理、听写考试时间:2023 年 11 月 25 日 8:30—10:00; (二)视唱、声乐、器乐考试时间:从 2024 年 1 月 3 日开始分批进行,考生应于 2023 年 12 月 20 日 8:00 至 12 月 22 日 18:00 登录河南省教育考试院网站,通过“…

原创花还开科技祛斑:经常看电脑的人,真的容易长斑吗?

林女士今年31岁,一向以皮肤细白光亮被人羡慕。大学毕业后,她在一家公司做文员,电脑成了她工作的好伙伴,回家后,笔记本电脑是她与朋友交流的亲密伙伴。可有一天下班回到家,她发现自己脸颊颧骨处隐隐约约出现了两大黑块,难道是自己年纪轻轻就有了“老年斑”? 实际上像林女…

2019考试必备数学运算-对容斥原理三集合标准题型

对容斥原理三集合标准型题型的考查与学习 容斥原理类型历来都是公务员行测考试数学运算模块考查的一个重要题型。其中两集合标准型已经考查较多,且难度不高,而三集合标准型将成为国联考及省考考察的一个重要的方向。下面我们将重点学习标准型三集合标准型题的原理以及考查方法…

谢祖武一家幸福时光,夫妻恩爱20载,儿子考上戏剧系

谢祖武一家近期的生活状况引起了广泛关注。这位资深演员为了宣传新剧《婚姻结业式》,多次接受媒体采访,分享了一家人的幸福生活。照片中,谢祖武身着棕色皮衣,搭配浅色牛仔裤和大背头,显得非常时尚。虽然已经53岁,但他的皮肤和身材保养得都很好,看起来像一位年轻的小伙子…