用户操作
[留言]  [发消息]  [加为好友] 
订阅我的博客
XML聚合    FeedSky
订阅到鲜果
订阅到Google
订阅到抓虾
yanjingtu2008的公告
欢迎光临偶的编程世界!<br> 请大家多多指教!
文章分类
CSDN
lingll的专栏
linux de
都市夜猫的专栏(RSS)
存档

转载  [转载]利用远程线程技术制造隐身程序 收藏

原作者: 王欣/ Navyseals

前段时间每晚睡觉前总要耍几把魔兽,这段时间居然怀旧般的耍起了星际。提到星际,又想起了本科时站在MTY等当时国内一流高手椅子后面观战的热闹场面,当初我们班的星际高手ww0830和zj0832也是那个时候成长起来的。糟糕,扯远了,不过魔兽、星际里面的隐形兵种大家都还是非常熟悉把。呵呵,我们今天就要来学习亲手打造操作系统里面的隐形兵种。
我们知道系统中每个进程都有自己独立的内存空间,其他进程不得随意访问,但是并不是说无法访问。API函数CreateRemoteThread就可以在其他进程中创建新的线程,就好像把一个寄生体注入到了其他生物中一样。这个寄生体将会与他的宿主“同生共死”并且拥有宿主的所有权限。由于我们的程序代码寄生在其他进程内部,所以一般人使用任务管理器是看不见这个“寄生”进程的。下面我们来看看如何具体的实现这一过程,在下文的例子中,我所使用的编译器是Delphi,但由于几乎主要讲解的函数都是WindowsAPI函数,所以下面的东西跟编译器没有什么关系,使用VC或者VB一样可以使用下面提到的函数。
第一步,提升本进程的系统权限。
因为我们要操作的是系统中的其他进程,没有足够的系统权限是无法读取甚至写入其他进程的内存地址的。提升进程权限可能用到以下的函数:

1.函数OpenProcessToken(
HANDLE ProcessHandle, // 进程的句柄
DWORD DesiredAccess, // 对进程的访问描述
PHANDLE TokenHandle // 打开进程令牌的句柄指针
);
这个函数的作用是打开进程令牌。

2.函数LookupPrivilegeValue(
LPCTSTR lpSystemName, //系统名称
LPCTSTR lpName, // 特权名称
PLUID lpLuid // 本地系统唯一的ID号
);
这个函数将会返回一个本地系统内独一无二的ID,来用于系统权限的更改,它的第1个参数是系统名,nil表示本系统。第2个参数是特权的名字。第3个参数用来接收函数返回的ID。

3.函数AdjustTokenPrivileges(
HANDLE TokenHandle, //更改权限的令牌环句柄
BOOL DisableAllPrivileges, //是否修改所有权限的标志位
PTOKEN_PRIVILEGES NewState, //新的系统权限信息
DWORD BufferLength, //上一个参数的长度
PTOKEN_PRIVILEGES PreviousState, // 返回更改系统特权以前的权限
PDWORD ReturnLength //上一个参数的长度
);
这个函数用于更改进程的系统权限 ,第1个参数是要更改权限的令牌环句柄。第2个参数如果为true表示更改所有的系统权限 ,false表示更改部分。第3个参数是要更改的系统特权的值。第4个参数是第3个参数的大小。第5个参数返回更改系统特权以前的权限,我们不需要就设为nil。第6个参数是第5个参数的大小。
把上面的东西合并起来写成一个函数,我们在其他代码中间直接调用PromoteDebugPrivilege就可以提升本进程的系统权限了。代码如下:
======================================================================
function PromoteDebugPrivilege(const PromoteEnabled: Boolean): Boolean;
var
hToken: THandle;
TokenPriv: TOKEN_PRIVILEGES;
Length: DWORD;
begin
Result := False;
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, hToken)) then
begin
TokenPriv.PrivilegeCount := 1;
LookupPrivilegeValue(nil, 'SeDebugPrivilege', TokenPriv.Privileges[0].Luid);
if PromoteEnabled then
TokenPriv.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED
else
TokenPriv.Privileges[0].Attributes := 0;
Length := 0;
AdjustTokenPrivileges(hToken, False, TokenPriv, SizeOf(TokenPriv), nil, Length);
Result := GetLastError = ERROR_SUCCESS;
CloseHandle(hToken);
end;
end;

第二步,进入宿主的内存空间
在拥有了进入宿主程序内存的权限之后,我们所要做的是在其内存空间加入一些新的程序代码,或者是让其载入一个Dll文件里面的函数并运行起来。加入新的代码可以省掉一个dll文件,而加载dll文件可以安装一些系统级的钩子,如果我们的隐形程序是一个截获密码的程序,加载dll就是一个很好的选择。
Kernel32.dll中的函数LoadLibraryW可以加载dll,它只需要dll文件的文件路径就可以完成操作,我们可以很容易的在程序代码中实现取出一个文件路径的操作。但是,我们希望在宿主程序中加载,而我们取出的dll文件路径并不存在于宿主程序的内存空间里面,所以我们需要把dll的文件路径写入宿主的内存空间。这些操作可能用到以下的函数:
1.函数OpenProcess(
DWORD dwDesiredAccess, //访问标志
BOOL bInheritHandle, //继承句柄标志
DWORD dwProcessId // 进程Id
);
这个函数用于修改我们宿主进程的一些属性,这些属性放在第一个参数里面,比如PROCESS_VM_OPERATION就是允许远程VM操作,即允许VirtualProtectEx和WriteProcessMemory函数操作本进程内存空间。PROCESS_CREATE_THREAD 就是允许远程创建线程。PROCESS_VM_WRITE就是允许远程VM写,即允许 WriteProcessMemory函数访问本进程的内存空间。第二个参数是一个标志参数,用来确定返回的句柄是否可以被新的进程继承。我们的程序中设为False。第三个参数需要操作的进程Id,也就是我们的宿主进程的Id。
2.函数VirtualAllocEx(
HANDLE hProcess, //要进行操作的进程句柄,当然是我们的宿主了
LPVOID lpAddress, //分配空间的起始地址
DWORD dwSize, //分配空间的大小
DWORD flAllocationType, // 分配空间的类型
DWORD flProtect // 访问保护类型
);
我们使用 VirtualAllocEx函数在宿主进程中开辟一块内存空间,用于存放dll的文件名。VirtualAllocEx的第1个参数是要操作的进程,第2个是起始地址,第3个是长度,第4,5个是操作参数。其中MEM_COMMIT表示本函数分配的是物理内存或者是内存的页面文件,PAGE_READWRITE表示分配的区域内允许读写。
3.函数WriteProcessMemory (
HANDLE hProcess, //所要操作进程的句柄
LPVOID lpBaseAddress, //开始进行些操作的起始地址
LPVOID lpBuffer, //要写入数据的缓冲区指针
DWORD nSize, // 要写的bytes数
LPDWORD lpNumberOfBytesWritten // 实际写入的bytes数
);
前面在宿主内存中创建好空间后,现在往里面写入dll的名称,而我们的WriteProcessMemory函数就可以胜任这一项工作。WriteProcessMemory函数的第一个参数 是需要往其内存里面写入dd的进程句柄,第二个参数是 “要进行写操作”的目标内存起始地址,第三个参数是 “需要被写入的数据”的地址,第四个参数是准备要写入的长度,第五个参数是实际操作中写的长度,这个参数是被函数输出的。到这里我们就已经能成功把dll的路径名称写进了宿主的内存空间。

第三步,在宿主中启动新的线程!
刚才我们已经在宿主程序中创建了一个用于存放一个dll文件路径的缓冲区,现在我们就要让这个dll在宿主的内存空间中运行起来。我们是用LoadLibraryW函数来加载的,而使用LoadLibraryW,又需要知道LoadLibraryW函数的入口地址。所以在加载dll之前,我们要用GetProcAddress来得到LoadLibraryW的入口地址。我们来看看这几个函数的使用方法:
1.GetProcAddress(
HMODULE hModule, //dll模块的句柄
LPCSTR lpProcName // 函数名称
);
我们用这个函数主要想得到kernel32.dll中的函数LoadLibraryW的入口地址,所以
GetProcAddress(GetModuleHandle('Kernel32'), 'LoadLibraryW')就可以了,当然有些细节得符合程序编译器的要求,VC下使用就要改成
GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryW")的形式。

2.CreateRemoteThread (
HANDLE hProcess, //要进行操作的进程句柄,也就是我们的宿主句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程安全属性的指针
DWORD dwStackSize, //初始化堆(stack)的大小
LPTHREAD_START_ROUTINE lpStartAddress,//新建线程函数的指针,或叫做地址
LPVOID lpParameter, //新建线程函数的参数
DWORD dwCreationFlags, //标志位
LPDWORD lpThreadId //线程返回值
);
这个函数就是本文的点睛之笔了,我们之前所做所有的一切,都是在为它做准备工作,它的功能就是在其他任何进程中创建新的线程,让其他的程序或进程附加执行我们的代码。
CreateRemoteThread函数的第一个参数是要操作的宿主进程句柄;第二个参数为线程安全参数的指针,这里设为nil;第三个参数为初始化堆(stack)的大小,这里设0;第四个参数为新建线程函数的指针或叫做地址或叫入口;第五个参数为新建线程函数的参数,这里就是我们的dll路径名称;第六个参数是标志位,这里设0;第七个参数是线程返回值。
到这里,我们基本上把如何编写一个能够寄生于其他进程之中运行的API函数都依次讲完了,如果读者有兴趣亲手试一下的话,在光盘中可以找到已经编好的程序完整代码和详细的程序注释。当然这个寄生于进程中的dll文件还需要自己编写,不过我写的内容是弹出一个“Helloworld”,而寄生的宿主是我们常用的QQ。我们打开Windows的任务管理器,看到只有QQ在运行,而我们看不到其他的东西。


而使用Windows优化大师的进程管理工具,可以看到我们的testdll已经寄生在QQ内部并且开始运行了。
后记:
为了方便读者试验这个程序我把QQ作为我们的宿主程序,因为关掉QQ就可以中止我们的“隐形程序”。如果我们把系统的Kernel32或者Explorer作为宿主,那就没有人随便关掉它们了。我们看到图2中使用专门的进程管理程序是可以看见我们的dll在QQ中运行的,但是如果我们dll的名字不叫testdll而叫做什么QQAPI.dll或者 Wsock32.dll之类的,估计能引起注意的可能就很小了。
另外,曾经出名一时的“中国黑客”病毒的“三线程”技术,就使用了本文所涉及的线程注入技术。三线程的主要思路就是让程序创建3个线程,第一个线程同时监视注册表和第2个线程的运行状况,第2个线程是寄生在系统进程中的“隐形”线程,它主要负责监视第一个线程的运行状况,第3个线程是功能实现线程。他的自我保护机制就是前2个线程相互监视,一旦发现异常就设法重新启动对方。如果我们的宿主程序是Explorer.exe和Taskmgr.exe的话,那么用任务管理器来关闭程序就是不可能完成的任务了,因为只要taskmgr在运行,寄生在它体内的“隐形”线程就会发挥其作用。网上也有不少“三线程”技术的实现代码,读者可以自行找来看看,用来巩固编程技能,但是我的初衷只是想和大家交流和学习技术,希望读者不要根据本文编出一些无聊的程序来做一些破坏性的事情。 

发表于 @ 2006年11月05日 19:31:00 | 评论( loading... ) | 编辑| 举报| 收藏

新一篇:[转载]在2000下获取进程的全路径

  • 发表评论
  • 评论内容:
  •  
Copyright © yanjingtu2008
Powered by CSDN Blog