枚举注册表搜索病毒痕迹的实现思路

作者:灰狐
来源:灰狐's Blog

注:本文已发表在<黑客防线>2008年第1期,转载请注明出处.

在第11期的《黑客防线》中,我那篇关于algsrvs病毒专杀的文章中有点不太完美的地方,就是没有把清理注册表那一部分写完整,一个原因就是我在那里说的因为当时我已经把病毒清理干净了,没有记录下每一个相应键值的位置;还有一个原因就是就算我记录下来了,也是一件相当麻烦的事情,因为病毒在注册表中写入的信息相当地多,大概有几十处,这样一个一个删还不把人累死?

当然也不是没有办法,我今天就用一种比较简单的思路来进行演示:首先完整枚举所有项,检查其键值是否被病毒修改过,然后进入子项中递归调用此函数完成遍历注册表的功能。

为了程序的通用性,今天我就不用VCL库中的Registry类了,改用Windows操作系统提供的正宗API,这样大家就可以把它完美地嵌入到自己的程序中了。

首先来讲一点基础知识,使没有编程操作过注册表的菜友们也能顺利上手。

注册表有五大子键,这我就不废话了,今天我们只拿其中比较重要的一个HKEY_LOCAL_MACHINE来演示,需要遍历注册表的话只需要把函数简单修改下调用五次就没问题咯。我们在注册表中某个地方点击右键,选择“新建”,可以看到有以下几种数据类型:字符串值、二进制值、DWORD值、多字符串值、可扩充字符串值。最常用的也是注册表中子项的默认值就是字符串值,它的内部名称为REG_SZ。

关于注册表编程的资料通常都很零散,所以通常我们最好的参考书就是MSDN了,在MSDN的索引里输入Reg就可以显示出所有以Reg开头的函数,前面那些基本就是跟操作注册表有关的,RegOpenKeyEx,RegCloseKey,RegDeleteValue、RegEnumKeyEx、RegEnumValue、RegQueryValueEx、RegSetValueEx这几个是我们重点需要的。

首先我们来看一下删除病毒键值的方法,为避免误删除,我们先自己建立一个子项:HKEY_LOCAL_MACHINE\SOFTWARE\grayfox,选中它后在右边的空白区域点右键,“新建”——“字符串值”——“test”,双击“test”,在“数值数据”里输入“test1,test2,test3”。我们假设test是启动项,test1、test2是本来的启动程序,病毒又向其中添加了一个test3,我们的目的就是把使其还原为test1,test2。这里要注意的是我们是不能直接使用RegDeleteKey将其删除的,如果里面只有test3这个值的话是可以的,但如果还有其他值的话需要使用RegSetValueEx重新设置。

现在我们来使用VC 6.0建立一个控制台程序测试效果,代码如下:

#include <stdio.h>

#include <windows.h>

void main()

{

HKEY hKey = NULL;

DWORD rc;

rc=::RegOpenKeyEx(HKEY_LOCAL_MACHINE,"software\\grayfox",0,

KEY_ALL_ACCESS,&hKey);

if( rc == ERROR_SUCCESS )

{

unsigned char buffer[MAX_PATH]="\0";

DWORD type = REG_SZ;

DWORD size = sizeof(buffer);

if(RegQueryValueEx(hKey,"test",NULL,&type,(LPBYTE)buffer,&size)==ERROR_SUCCESS)

{ //先读取原内容保存到buffer中

printf("%s\n",buffer);

char *lstr;

lstr = strstr((char *)buffer,"test3"); //检查是否有test3这个值

if(lstr != NULL) //如果有则进入处理

{

printf("Find!\n");

if(strchr((char *)buffer,',') == NULL) //检查是否有逗号

{ //无逗号说明只有这一个值,可直接删除

RegDeleteValue(hKey,"test");

}

else

{ //有逗号不能直接删除,需重新设置值

int result = (int)lstr – (int)buffer; //找到病毒名字符串所在位置

buffer[result – 1] = '\0'; //从这里将后面截断,因为病毒通常都是将自己加入后在最后面

printf("New Buffer:%s\n",buffer);

sprintf((char *)buffer,"%s",buffer);

RegSetValueEx(hKey,"test",0,REG_SZ,buffer,sizeof(buffer));

}

}

}

}

else

{

printf("Can't Open The Key!\n");

}

RegCloseKey(hKey);

}

代码其实不长,函数也不多,很容易看懂,太多的注释反而会影响整体思路。下面的代码都侧重于搜索,而对搜索到符合条件的值如何处理没有给出实际代码,所以上面这段代码请务必领会。

我们运行下看看,果然被修改成了test1,test2,我们再把这两个值改成test3,然后重新运行程序,可以看到由于只有一个值,这个键值被直接删除了,hoho~~命中目标!

当然,这段代码其实并不完善,因为假如里面刚好有个值为abctest3de的话,不是被直接KILL就是该被改成abc了,完全不符合我们原来的设想了,这个问题就交给大家发挥自己的聪明才智来解决了。

下面我们来演示枚举子项和枚举键值。请看代码:

#include <windows.h>

#include <stdio.h>

#define SUBKEYS 1 //决定是枚举子项还是键值

void main()

{

HKEY hKey = NULL,h = NULL;

char str[MAX_PATH];

DWORD num = sizeof(str),index = 0,rc;

#if SUBKEYS

rc=::RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE",0,KEY_ALL_ACCESS,&hKey);

#else

rc=::RegOpenKeyEx(HKEY_LOCAL_MACHINE,"SOFTWARE\\Microsoft\\Windows\\CurrentVersion",0,KEY_ALL_ACCESS,&hKey);

#endif

if(rc == ERROR_SUCCESS)

{

#if SUBKEYS

while( RegEnumKeyEx(hKey,index,str,&num,NULL,NULL,NULL,NULL)==0 )

#else

while( RegEnumValue(hKey,index,str,&num,NULL,NULL,NULL,NULL)==0 )

#endif

{

printf("%s\n",str);

index++;

num = MAX_PATH;

}

printf("\n\nNumberOfindex = %d\n\n",index);

}

else

{

printf("Can't Open The Key!\n");

}

RegCloseKey(h);

RegCloseKey(hKey);

}

在上面这段代码中我使用了条件编译指令,分别演示用来枚举子项还是键值,默认是枚举HKEY_LOCAL_MACHINE\SOFTWARE下所有的子项,如果改为#define SUBKEYS 0就是枚举HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion下所有的键值,测试结果分别如下图1,图2:

图1

图2

嘿嘿,非常完美地实现了我们的功能哦,当然这只是演示,但要想应用到实际中其实也不难,如果你还记得那段经典的遍历文件的代码的话这个就应该不在话下了,呵呵,就是利用递归来实现的。我写了一段演示的代码,献丑了:

#include <windows.h>

#include <stdio.h>

#define MAINKEY HKEY_LOCAL_MACHINE //主键

int ResultCount = 0; //全局变量用来记录符合条件的值数

//下面这段代码是处理键值的,第一个参数是使用API从注册表中读出来的值

//第二个参数是你要查找的值,比如病毒名,由于目的主要是演示,写的很简单

bool StealReg(char KeyValue[MAX_PATH],char Virus[MAX_PATH])

{ //这里面要实现的具体功能请大家根据实际情况自行扩充

if(strcmp(KeyValue,Virus) == 0)

{

return true;

}

return false;

}

//这个函数是关键,唯一的参数是SubKey,比如“software\\Microsoft”

void EnumReg(char SubKey[MAX_PATH])

{

char temp[MAX_PATH];

HKEY hKey = NULL;

char str[MAX_PATH]; //存放读取出来的值

DWORD num = sizeof(str),index = 0,rc;

rc = ::RegOpenKeyEx(MAINKEY,SubKey,0,KEY_ALL_ACCESS,&hKey);

if(rc == ERROR_SUCCESS)

{

while( RegEnumValue(hKey,index,str,&num,NULL,NULL,NULL,NULL)==0 )

{ //首先遍历值,进行处理

printf("\t%s\n",str);

if(StealReg(str,"F:\\BCB\\Program Files\\Common Files\\Borland Shared\\BDE\\"))

{ //上面第二个参数就是你要查找的值,注意目录之间要用两个\\

ResultCount++; //记录数加一

}

index++;

num = MAX_PATH;

}

index = 0;

while( RegEnumKeyEx(hKey,index,str,&num,NULL,NULL,NULL,NULL)==0 )

{ //然后遍历子项后进行递归

printf("%s\n",str);

strcpy(temp,SubKey); //这样做是必须的,不然会通常情况下都会出现异常

strcat(temp,"\\");

strcat(temp,str);

EnumReg(temp); //递归

index++;

num = MAX_PATH;

}

}

else

{

printf("Can't Open The Key!\n");

}

RegCloseKey(hKey);

}

void main()

{

EnumReg("software");

printf("\n\n符合条件的值共有:%d 条!\n\n",ResultCount);

}

好了,这上面的代码都是可以直接保存为文本文件编译运行的,但由于注册表实在太庞大了,所以要在里面找到全部的东西耗时实在有点长,如果要更加完善的话就需要大家的一起努力了。

相关日志

发表评论