3.2.防毒实现方案实编码实现

防毒功能实现需要拦截进程启动行为,在一个进程启动前,先对其进行扫描,判断是否是病毒,如果是病毒则立刻进行阻止。这也就是许多杀毒软件宣传的主动防御功能。

为了实现拦截进程启动功能,我们这里要使用API Hook技术。拦截到进程时,获取到它的文件特征码,并在本地病毒库、黑名单、云病毒库中进行匹配校验,如果检测到是病毒,直接阻止。其程序流程如下图所示:
病毒检测与杀毒技术大揭秘6-LMLPHP
实现拦截进程启动,可以由多种技术手段实现。可以修改SSDT表通过驱动实现,这是较底层的方式;也可以通过在用户层进行全局DLL注入实现。两者各有优劣。驱动方式更加接近系统底层,控制权限更高,但容易对系统稳定性造成影响,并且杀毒软件大多采用驱动方式,这样便容易引发同类软件冲突、与同类软件不兼容等情况。DLL方式工作在用户层,对系统稳定性及性能影响小,并不易有同类软件兼容性问题。本杀毒软件设计中采用DLL注入方式。

大多数杀毒软件的防护功能,不仅仅会拦截进程启动,还会对注册表读写、文件读写等众多操作进行监管。而本设计中,只拦截启动行为,这样做的原因在于:防护的最核心最关键的就在于进程执行这一步,如果这一步被病毒通过,那么病毒可能会读写文件,可能会读写注册表,也可能会格式化磁盘,甚至会强行删除文件,一切都是不可预料的,做为杀毒软件,不可能接管系统所有的操作,也就是说,如果被病毒运行,等于防线已经崩溃了,系统已经被破坏了。而且,接管系统的各种操作,在系统中没有病毒的情况下,也会给系统本身带来很大的性能负担和运行风险。可能会使系统性能降低一半甚至更多,可能会使系统经常性的死机、蓝屏。用一个为了防止未知的风险,而付出如此多的代价是划不来的,用户也不愿意接受的。因此,在本软件中,仅最大化的加强执行防护这一个防毒门槛。带给用户安全的同时,又不影响用户使用电脑。

3.2.1.云安全实现方案的引入

云安全的优势在于:在具有广泛云客户端的基础上,可以及时、实时的获取最新的病毒木马信息,并同时加入到云安全病毒库中。

为了进一步增强防护功能,在本杀毒软件的防毒功能设计中引入了云安全防毒功能。从理论上来讲,云安全的实现,最少是需要一台服务器的,用于架设服务器、存储数据。架设服务器,需要云后台程序,存储数据,需要用安装数据库。

本设计中使用的云安全后台使用ASP编写。数据库可以用MS-SQL或Access。

后台程序实现两个功能。其一,接收用户端杀毒软件传送的数据,数据为用户执行程序时获取的文件特征码,这个行为将用户端视为云的客户端,实现了采集数据的功能;其二,根据客户端请求进行查询并反馈结果。请求中附带的依然是特征码。根据云数据库中的数据,后台程序接收特征码进行校验查询,并反馈查询结果给客户端,这个操作用于防毒功能。即上文所讲的防毒方案中的查询云安全病毒库过程。接收到的数据存储在数据库中,可由维护人员操作,或由云后台根据数据情况按一定规则返回查询结果给客户端软件。
病毒检测与杀毒技术大揭秘6-LMLPHP
数据量和用户量都较小的时候,可以使用Access,服务器可以使用虚拟主机代替。

介绍了云安全的服务端设置,再来看拦截进程启动的编程实现:

系统在启动一个进程时,在用户层是通过调用API函数:CreateProcess实现的。拦截进程启动行为,我们要做的就是Hook这个API。要实现API Hook,并在拦截后执行相应的操作,单纯VB是做不到的。这时需要用VC开发一个DLL文件供VB调用。软件运行时,DLL将被注入到所有系统进程中,接管各进程中的CreateProcess函数。这样,无论是哪个程序调用CreateProcess启动程序,DLL都会将暂停这个启动行为,并将被启动的文件信息发给杀毒软件,由杀毒软件判断安全性,根据判断结果决定是否允许程序启动。如果这时检测到是病毒在启动,杀毒软件会告诉DLL:这是病毒,不能让它启动。病毒的启动行为就被阻止了。

来看代码:

DLL部分:

Dll入口函数:
BOOL APIENTRY DllMain( HANDLE hModule, DWORD  ul_reason_for_call, LPVOID lpReserved)
{
hMod = (HINSTANCE)hModule;
if (ul_reason_for_call==DLL_PROCESS_ATTACH)
{
hProcess=OpenProcess(PROCESS_ALL_ACCESS,0, CurrentProcessId());

获取我们设计的杀毒软件的标题,以便发送消息进行通迅:
hExe = FindWindow(NULL, "杀毒软件Demo");

DLL被加载时对createprocess函数进行Hook:
HookCreateProcess();

}

if (ul_reason_for_call==DLL_PROCESS_DETACH)
{
HookStatus(FALSE);
}
return TRUE;
}

Hookcreateprocess函数:
BOOL HookCreateProcess()
{
ULONG JmpAddr=0;

获取CreateProcessW函数原始地址:
FunAddr=(ULONG)GetProcAddress(LoadLibrary("Kernel32.dll"), "CreateProcessW");

保存CreateProcessW函数原始地址,以备恢复hook时使用:
memcpy(OldCode, (void *)FunAddr, 5);

构造汇编跳转指令:
NewCode[0] = 0xe9;
JmpAddr = (ULONG)CreateProcessWCallBack - FunAddr - 5;

用我们自定义的CreateProcessWCallBack函数取代CreateProcessW函数的地址,这样当CreateProcessW系统函数被调用时,我们自定义的函数可以接管相关的操作:
memcpy(&NewCode[1], &JmpAddr, 4);

启用API Hook,此时,当有启动进程操作时,由CreateProcessWCallBack进行管理:
HookStatus(TRUE);
return TRUE;
}

CreateProcessW的替代函数,拥有与CreateProcessW相同的参数:
BOOL WINAPI CreateProcessWCallBack
(
LPCWSTR lpApplicationName,
LPWSTR lpCommandLine,
LPSECURITY_ATTRIBUTES lpProcessAttributes,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
BOOL bInheritHandles,
DWORD dwCreationFlags,
LPVOID lpEnvironment,
LPCWSTR lpCurrentDirectory,
LPSTARTUPINFOW lpStartupInfo,
LPPROCESS_INFORMATION lpProcessInformation
)
{
BOOL b=FALSE;

获取要启动的进程:
char AppName[256]={0};
WideCharToMultiByte(CP_ACP, 0,(const unsigned short *)lpApplicationName, -1, AppName, 256,NULL, NULL);
char* CopyData="";

构造字符串,内容是:进程和命令行。用于发送给上层杀毒软件:
strcpy(CopyData,AppName);
strcat(CopyData,"$");
COPYDATASTRUCT cds={0};
cds.lpData = CopyData;
cds.cbData = 1024;

取得dll所在进程pid:
DWORD dwProcessId;
dwProcessId=GetCurrentProcessId();

DWORD ret;

向杀毒软件发送消息,消息中包含着正要启动的进程的路径,并等待返回结果:
ret=SendMessage(hExe, WM_COPYDATA, dwProcessId, (LPARAM)&cds);

如果返回值是915,这个值是自定义的,这个值表明杀毒软件已经检验过被启动文件,并且是安全的,可以启动。
if(ret==915)
{

设置拦截状态,暂不启用API Hook。如果此处不做修改,CreateProcessWCallBack会一直有效,形成错误的递规调用,任何程序都启动不了。
HookStatus(FALSE);

启动进程:
b = CreateProcessW
(
lpApplicationName,
lpCommandLine,
lpProcessAttributes,
lpThreadAttributes,
bInheritHandles,
dwCreationFlags,
lpEnvironment,
lpCurrentDirectory,
lpStartupInfo,
lpProcessInformation
);

启动后立刻启用API Hook,继续监控进程启动行为:
HookStatus(TRUE);
return b;
}
else
{

如果返回值不是915,表达在杀毒软件中检测到被启动文件是病毒或是其它恶意文件。直接返回,阻止程序启动。这时软件中也有相应的提示及操作。
return FALSE;
}
return FALSE;
}

设置API Hook启用状态状态:
BOOL HookStatus(BOOL Status)
{
BOOL ret=FALSE;
if (Status)
{

将CreateProcessW入口地址写为我们自己定义的函数,使API Hook启用:
ret = WriteProcessMemory(hProcess, (void *)FunAddr, NewCode, 5, 0);
if (ret) return TRUE;
}
else
{

将CreateProcessW入口地址写为原始函数CreateProcessW的地址,API Hook停止
ret = WriteProcessMemory(hProcess, (void *)FunAddr, OldCode, 5, 0);
if (ret) return TRUE;
}
return FALSE;
}

LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam)
{
return(CallNextHookEx(hHook,nCode,wParam,lParam)
);
}

这是输出函数,用于在应用程序中调用,关闭API HOOK:
BOOL WINAPI ActiveDefenseOFF()
{
return(UnhookWindowsHookEx(hHook));
}

这是输出函数,用于在应用程序中调用,启用API HOOK:
BOOL WINAPI ActiveDefenseON()
{
设置系统钩子,向各活动进程注入此DLL
hHook = SetWindowsHookEx(WH_GETMESSAGE,(HOOKPROC)HookProc, hMod, 0);
if (hHook)
{
return TRUE;
}
else
{
return FALSE;
}
}

在此DLL中,使用SetWindowsHookEx函数向所有进程注入此DLL,DLL初注入后,接管各进程中的CreateProcessW系统函数,此函数可以控制进程启动操作,当检测到进程启动后,暂停启动行为,获取进程的完整路径,并传递给上层执行文件,也就是我们的杀毒软件主程序。传递到主程序后,主程序中对传来路径的文件进行扫描,此时DLL中还在等待上层主程序的处理结果,当在主程序中扫描完成后,反馈信息给DLL:如果是病毒,会中止此进程的启动行为,否则让程序完成启动。这样就完成了对病毒的主动防御。

在主程序中,为了与DLL呼应,完成相应的功能,需要两方面的操作,一方面接管窗口消息,以实现DLL发送消息的响应;另一方面,完成文件扫描并向DLL回应消息。

编码如下:

用SetWindowLong函数,将把程序的消息权交给SubClassMessage函数,这样就接管了程序的消息管理:
SetWindowLong hWnd, -4, AddressOf SubClassMessage

这样我们就可以在SubClassMessage函数中获取DLL发来的数据。如下:

Public Function SubClassMessage(ByVal lHwnd As Long, ByVal lMessage As Long, ByVal lParentPID As Long, ByVal lParam As Long) As Long
...
Dim sTemp As String
Dim uCDS As COPYDATASTRUCT

判断是否是DLL传来的消息,上文中DLL发送消息时的参数WM_COPYDATA值等于&H4A
If lMessage = &H4A Then

CopyMemory uCDS, ByVal lParam, Len(uCDS)
sTemp = Space(uCDS.cbData)
CopyMemory ByVal sTemp, ByVal uCDS.lpData, uCDS.cbData

获取拦截到的正要启动的程序
Dim sFile As String
sFile = Left(sTemp, InStr(1, sTemp, "$") - 1)
sTemp = Right(sTemp, Len(sTemp) - InStr(1, sTemp, "$"))

end if
End function

获取到要启动的程序后,便可使上前面介绍过的扫描病毒代码对文件进行检测。并根据扫描结果向DLL返回消息。编码如下:

Dim sScanResult As String
sScanResult = ScanFile(sFile)
If sScanResult ="" then
SubClassMessage =915
else
SubClassMessage = 0
End if

同样是调用前面讲过的ScanFile函数对文件进行扫描,如果返回为空说明没有检测到病毒,文件是安全的,则给SubClassMessage赋值915。这句代码代码的意思是:SubClassMessage函数是主程序负责消息处理的函数,DLL中将消息发送到主程序后,将由此函数负责处理,此函数获取赋值后,相当于是函数的返回值,也就是DLL中使用SendMessage函数发送消息后要等待的消息返回结果。这里的赋值,会直接传递给DLL。而915这个数字我们在前面已经提过,是自定义的一个数值,如果DLL中接收到此值,表明文件没有扫描到病毒,文件是安全的。反之返回0的含意与此类似,只是功能相反。

这是防毒功能中使用传统特征码扫描识别的实现,我们前面讲过要在防毒中使用云安全技术。接下来进行相关介绍。

当本地病毒库特征码校验没有扫描到病毒后,时此可以使用云安全扫描,进行二次检测。编码如下:
sCloudAskRet=CloudMatch(“url/”&HashFile(sFile))

我们约定:如果返回值不为空说明是病毒:
If sCloudAskRet <> "" Then
Dim sCloudName As String

此处是获取返回值中对病毒文件描述,返回的数据中用“sCloudName=Right(sCloudAskRet,Len(sCloudAskRet)InStr(1,sCloudAskRet,""))

用与上面同样的方法向DLL回应消息:
SubClassMessage = 0
End If

这里需要说明的是,使用云安全检测,同样是需要提取文件的特征码,并将特征码发送给云安全服务器。在这里我们使用不同于病毒库中的特征码格式,云安全中将文件整体的哈希值做为特征码。这样做是为了存储和查询的方便。上面代码中HashFile函数,用于获取文件的云安全特征码。编码如下:

Function HashFile(ByVal sFilename As String) As String

HashFile = ""
Dim lCtx As Long
Dim lHash As Long
Dim lFile As Long
Dim lRes As Long
Dim lLen As Long
Dim lIdx As Long

Dim bHash() As Byte

If Len(Dir(sFilename)) = 0 Then
Exit Function
End If

lRes=CryptAcquireContext(lCtx,vbNullString,vbNullString, PROV_RSA_FULL, 0)
If lRes <> 0 Then
lRes = CryptCreateHash(lCtx, ALGORITHM, 0, 0, lHash)
If lRes <> 0 Then
lFile = FreeFile
Open sFilename For Binary As #lFile
Const BLOCK_SIZE As Long = 32 * 1024&
ReDim bBlock(1 To BLOCK_SIZE) As Byte
Dim lCount As Long
Dim lBlocks As Long
Dim lLastBlock As Long
lBlocks = LOF(lFile) \ BLOCK_SIZE
lLastBlock = LOF(lFile) - lBlocks * BLOCK_SIZE
For lCount = 1 To lBlocks
Get lFile, , bBlock
lRes = CryptHashData(lHash, bBlock(1), BLOCK_SIZE, 0)
Next
If lLastBlock > 0 And lRes <> 0 Then
ReDim bBlock(1 To lLastBlock) As Byte
Get lFile, , bBlock
lRes = CryptHashData(lHash, bBlock(1), lLastBlock, 0)
End If
Close lFile
If lRes <> 0 Then
lRes = CryptGetHashParam(lHash, HP_HASHSIZE, lLen, 4, 0)
If lRes <> 0 Then
ReDim bHash(0 To lLen - 1)
lRes = CryptGetHashParam(lHash, HP_HASHVAL, bHash(0), lLen, 0)
If lRes <> 0 Then
For lIdx = 0 To UBound(bHash)
HashFile = HashFile & Right("0" & Hex(bHash(lIdx)), 2)
Next
End If
End If
End If
CryptDestroyHash lHash
End If
End If
CryptReleaseContext lCtx, 0
End Function

此函数跟前面讲过的HashFileStream函数代码非常接近,上文中已对HashFileStream函数做过详细讲解,在此不再缀述。

上面的代码中获取了文件的云安全特征码后,使用CloudMatch函数进行云安全校验。CloudMatch函数中将连接云安全服务器,向云后台传递云安全特征码,通过调用云安全后台程序判断文件是否是病毒,编码代码如下:

Public Function CloudMatch(sUrl As String) As String

Dim lInternetOpenUrl As Long
Dim lInternetOpen As Long
Dim sTemp As String * 1024
Dim lInternetReadFile As Long
Dim lSize As Long
Dim sContent As String
sContent = vbNullString

lInternetOpen = InternetOpen("Cloud", 1, vbNullString, vbNullString, 0)

If lInternetOpen Then

连接云后台,sUrl变量中存储的是云后台程序的网络地址:
lInternetOpenUrl = InternetOpenUrl(lInternetOpen, sUrl, vbNullString, 0, INTERNET_FLAG_NO_CACHE_WRITE, 0)

If lInternetOpenUrl Then
Do

读取云后台反馈的内容:
lInternetReadFile = InternetReadFile(lInternetOpenUrl, sTemp, 1024, lSize)
sContent = sContent & Mid(sTemp, 1, lSize)
Loop While (lSize <> 0)
End If

如果云安全后台检测到病毒,返回的内容格式为:Cloud$(云安全标识)+病毒名称/病毒描述

If UCase(Left(sContent, 6)) = UCase("Cloud$") Then
    CloudMatch = Right(sContent, Len(sContent) - 6)
Else
    CloudMatch = ""
End If

End Function

3.2.2.云安全服务端编程

上面的代码中执行云安全查询时,是这样调用的:“sCloudAskRet = CloudMatch("http://xxx.com/ask.asp?Hash=" & HashFile(sFile))”

不难看出,“w2sft”此地址便是云安全的后台程序,在本例中,后台程序由asp写成,负责处理杀毒软件发起的查询,并返回云安全扫描结果。编码如下:

<%

取得杀毒软件中传递的文件特征码:

dim sHash
sHash=trim(request("Hash"))

dim conn
dim connstr
dim rs
dim sql

连接数据库,云安全的特征码存储在数据库,数据库可以使用MsSql、MySql等各种数据库,甚至是Access,为了演示编码方便,这里我们采用最简单的Access数据库:

set conn=server.createobject(“ADODB.CONNECTION”)
onnstr=“DBQ=”+server.mappath(“cloud.mdb”)+";DefaultDir=;DRIVER={Microsoft Access Driver (*.mdb)};"
conn.open connstr
set rs=createobject(“adodb.recordset”)

进行SQL查询,病毒库中是否有传特来的特征码:

sql="select * from Cloud where Hash='" & sHash & "'"
rs.open sql,conn,1,3

if rs.recordcount=1 then
response.write "Cloud$云安全检测到病毒"
end if

rs.close
set rs=nothing
set conn=nothing
%>

到此,杀毒和防毒两个最核心功能的基本讲解已完成。也许有读者会疑惑、会意犹未尽:为什么这么简单?是的,确实很简单,到此,本书使用较少量的代码已经完成了这两个最重要功能的介绍和编码实现演示,之所以使用能由如此少量的代码完成,原因在于省略掉了其它部分代码量非常大的会影响功能实现直观性的编码,只保留了相关的核心代码。这是一种模块化的功能演示编码,也只有这样,才能明了的完成这两个功能的讲解。其它相关部分,如黑白名单的使用、设置选项的关联、与界面的互交代码等等会单独进行讲解。本书后面章节中讲述其它功能时,也会采用类似的方法。

10-07 18:40