首页
社区
课程
招聘
[原创]Windows系统程序设计之插入DLL和挂接API
2006-6-12 03:47 29366

[原创]Windows系统程序设计之插入DLL和挂接API

2006-6-12 03:47
29366
                   Windows系统程序设计之插入DLL和挂接API
【作者】北极星2003
【来源】看雪技术论坛(bbs.pediy.com)
【时间】2006年6月12日
【说明】关于本文的所有信息都可以在附件中下载

        首先简要介绍一下内容要点。
        在WINDOWS中,每个进程都有自己的私有地址空间,当用指针访问内存的时候只能访问到自己进程的地址空间,而无法进入其他进程的地址空间。而当自己的进程需要对其他进程进行一系列操作的时候,一个比较好的选择就是把这些操作封装在一个DLL中,然后把DLL插入到目标进程的私有地址空间中。这样,如果DLL中使用指针的话,就可以访问目标进程的地址空间。
        在程序中通常有两种方法来实现插入DLL:(1)使用远程线程(2)HOOK

        挂接API的目标是当系统调用API函数的时候能够执行我们的自定义函数,需要注意的是自定义函数的参数、返回类型、调用方式都必须和原API函数完全相同。这样我们就可以很便捷的取得或者修改参数来实现相应的目标,而无需考虑该API的内部实现。
        通常有两种方法可以实现挂接API:(1)修改IAT(2)API入口的覆盖。

        需要说明的是对知识点的简要概括,至于具体信息,无论在书本或者网络上都有很多,不再赘述。
       
        使用远程线程方法实现插入DLL可以参考我以前的文章《QQ盗号的核心技术(简单版)》http://bbs.pediy.com/showthread.php?threadid=14203

        这里主要讲解两个软件的制作。
(1)        IPPack――IP封包截获工具
原理:使用挂钩(HOOK)插入DLL,覆盖API入口地址挂接API
(2)        Watch IAT ――查看IAT信息
关于修改IAT来实现挂接API已经很普遍了,我也懒的再重复。这个工具软件是我很早以前写的,用来查看可执行文件的IAT信息,但有一个难点,由于IAT信息是在加载时候动态产生的,那么什么时候是最佳时机呢??

实例一 : IPPack ―― IP封包截获工具


1、设计目标
                把基本的DLL插入技术和HOOK API技术相联系,应用到实际。另外,在该软件中还设计进程间通信、线程同步、DLL实现等多种Windows程序设计中的基本技术。(经常有朋友说这个软件不能截获数据包,这里需要说明的是这个软件仅仅是一个技术实例,并不是工具软件。对目标进程进行测试的时候,最好先用PE分析工具查看一下,确认导入表中有send,recv,sendto,recvfrom这四个函数。)

2、设计思路
        第一步:把DLL插入到目标进程地址空间
  

        第二步:挂接API,取得相关的参数信息并传送到IPPack.exe
                        如果不熟悉管道通信机制,可以参考《Windows系统程序设计之进程间通信》
http://bbs.pediy.com/showthread.php?s=&threadid=26252  

3、难点
        插入DLL和挂接API的应用比较多,基本上没什么难度。
       
4、详细设计
(1)当启动IPPack.exe进行初始化时,启动命名管道线程服务
UINT ServerThread ( LPVOID lpParameter )
{
	……
	while ( true )
	{
		ConnectNamedPipe ( CurPipeInst.hPipe, &OverLapStruct ) ;
		WaitForSingleObject ( CurPipeInst.hEvent, INFINITE ) ;
		if ( !GetOverlappedResult ( CurPipeInst.hPipe, &OverLapStruct, &dwByte, true ) )
			break ;
		……
		// 从管道中读取数据并显示到界面
		……
		// 断开客户端的连接,以便等待下一客户的到来
		DisconnectNamedPipe ( CurPipeInst.hPipe ) ;
	}
	
	return 0 ;
}


(2)显示进程列表,需要提升本进程的权限
void CIPPackDlg::LookUpProcessPriviege ()
{
	TOKEN_PRIVILEGES tkp; 
	HANDLE hToken; 
	if (!OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken))
		return ; 
	LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tkp.Privileges[0].Luid);	tkp.PrivilegeCount = 1; 
	tkp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; 
	AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0); 
}


(3)更新进程列表
void CSPDlg::UpdateProcess ()
{
	DWORD	cbNeededProcess, cbNeededModule;
	HMODULE	hMod ;
	CString TempStr ;
	EnumProcesses ( dwProcessId, sizeof(dwProcessId), &cbNeededProcess ) ;
	for ( unsigned i = 0; i < ( cbNeededProcess/sizeof(DWORD) ); i++ )
	{
		HANDLE hProcess = OpenProcess( PROCESS_QUERY_INFORMATION |	\
                                   PROCESS_VM_READ,	FALSE, dwProcessId[i] );
		if ( hProcess != INVALID_HANDLE_VALUE )
		{
			EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeededModule ) ;
			GetModuleBaseName ( hProcess, hMod, szProcessName[i].GetBuffer(512), 512 ) ;

			TempStr.Format ( "%d", dwProcessId[i] ) ;
			m_ListCon.InsertItem ( i, TempStr, 0 ) ;
			m_ListCon.SetItemText ( i, 1, szProcessName[i] ) ;			
			CloseHandle ( hProcess ) ;
		}
	}
}


(4)取得目标进程的主线程ID
dwTarProcessId		= dwProcessId[nCurIndex] ;
szTarProcessName	= szProcessName[nCurIndex] ;

HANDLE hThreadSnap = CreateToolhelp32Snapshot ( TH32CS_SNAPTHREAD, 0 ) ;
if ( hThreadSnap == INVALID_HANDLE_VALUE ) 
	return ; 

THREADENTRY32 te32 = { sizeof(THREADENTRY32) } ;
if ( Thread32First ( hThreadSnap, &te32 ) )
{
	do{
		if ( te32.th32OwnerProcessID == dwTarProcessId )
		{
			dwTarThreadId = te32.th32ThreadID ; 
			break ;
		}
	}while ( Thread32Next ( hThreadSnap, &te32 ) ) ;
}


(5)封装HOOK API入口地址覆盖到CFEAHook类,FEAHook.h文件如下:
#ifndef _FEAHook_H
#define _FEAHook_H

class CFEAHook {
public:
	BOOL	isSuccess ;						// 标识是否成功
	PSTR	pModuleName, pFunName ;			// 模块名、函数名
	LPVOID	pOldFunEntry, pNewFunEntry ;	// 初始函数地址、HOOK后的函数地址
	BYTE	bOldByte[5], bNewByte[5] ;		// 原始字节、目标字节

public:
	CFEAHook () {	isSuccess	= false ;	}
	~CFEAHook() {	UnHook() ;		}
	void Hook ( PSTR szModuleName, PSTR szFunName, FARPROC pFun )
	{	
		HMODULE	hMod = GetModuleHandle ( szModuleName ) ;
		if ( hMod != NULL )
		{
			isSuccess		= true ;
			pModuleName		= szModuleName ;
			pFunName		= szFunName ;
			pNewFunEntry	= (LPVOID)pFun ;
			pOldFunEntry	= (LPVOID)GetProcAddress ( hMod, pFunName ) ;
			bNewByte[0]		= 0xE9 ;
			*((PDWORD)(&(bNewByte[1])))	= (DWORD)pNewFunEntry - (DWORD)pOldFunEntry - 5 ; 

			DWORD   dwProtect, dwWriteByte, dwReadByte ; 
			VirtualProtect ( (LPVOID)pOldFunEntry, 5, PAGE_READWRITE, &dwProtect );
			ReadProcessMemory	( GetCurrentProcess(), (LPVOID)pOldFunEntry, bOldByte, 5, &dwReadByte ) ;		
			WriteProcessMemory	( GetCurrentProcess(), (LPVOID)pOldFunEntry, bNewByte, 5, &dwWriteByte ) ;
			VirtualProtect ( (LPVOID)pOldFunEntry, 5, dwProtect, NULL ) ;
		}
	}
	void ReHook ()
	{
		DWORD	dwProtect, dwWriteByte ;
		VirtualProtect ( pOldFunEntry, 5, PAGE_READWRITE, &dwProtect );
		WriteProcessMemory ( GetCurrentProcess(), pOldFunEntry, bNewByte, 5, &dwWriteByte ) ;
		VirtualProtect ( pOldFunEntry, 5, dwProtect, NULL ) ;
	}
	void UnHook ()
	{
		DWORD	dwProtect, dwWriteByte ;
		VirtualProtect ( pOldFunEntry, 5, PAGE_READWRITE, &dwProtect );
		WriteProcessMemory ( GetCurrentProcess(), pOldFunEntry, bOldByte, 5, &dwWriteByte ) ;
		VirtualProtect ( pOldFunEntry, 5, dwProtect, NULL ) ;
	}
} ;

#endif

(6)HOOK四个函数
CFEAHook	HOOK_send ;
CFEAHook	HOOK_recv ;
CFEAHook	HOOK_sendto ;
CFEAHook	HOOK_recvfrom ;
HOOK_send.Hook		( "wsock32.dll", "send",		(FARPROC)MY_send ) ;
HOOK_recv.Hook		( "wsock32.dll", "recv",		(FARPROC)MY_recv ) ;
HOOK_sendto.Hook		( "wsock32.dll", "sendto",		(FARPROC)MY_sendto ) ;
HOOK_recvfrom.Hook	("wsock32.dll",	"recvfrom",(FARPROC)MY_recvfrom ) ;


实例二:Watch IAT ―― 查看IAT信息


1、        设计目标
查看可执行文件的IAT信息。这个软件虽然与修改IAT的HOOK API没有直接的联系,但我觉得非常有必要放到这个部分,至于必要性请看下文。

2、        存在的难点
由于IAT信息是在可执行文件初始化时经过PE加载器改写的,而不是原本的磁盘文件中的数据。问题就在这里,什么时候才是对IAT进行操作的最佳时机?PE加载器对IAT部分的改写是在什么时候完成的?如果仅仅创建挂起的进程,此时的加载器是否写入IAT信息?

3、问题的分析与设计
利用调试API实现对远程进程的创建、入口点的控制。
其实,可以通过先到达程序入口点,同时让程序挂起。这就可以利用调试API来实现,先创建目标进程(即可执行的目标文件,要查看IAT信息必须要让PE文件执行,从而才能使加载器改写功能的实现),该进程需带有DEBUG_ONLY_THIS_PROCES标志,这样该进程就具有可被本线程调试的权限,然后在主线程中建立一个调试循环体用于对目标进程执行流程的控制。当内核在完成进程装载和初始化之后会在被调试进程的上下文环境中自己调用一次int 3的(原本我在创建该进程的时候还带有CREATE_SUSPENDED标志,然后在入口点写入__int 3断点(0xcc),这样就等于做了重复工作,感谢drwch的指点),利用WaitForDebugEvent实现对目标进程EXCEPTION_DEBUG_EVENT异常的捕捉,这样程序就中断在程序入口点,同时可以读取所需的IAT信息。
这里还有一个关键之处在于目标进程的DEBUG_ONLY_THIS_PROCESS
标志。该目标进程与常见它的母体有个令人比较讨厌的特性,由于目标进程是DEBUG子程式,如果母程式存在,子程式就不会结束。原本想利用DebugActiveProcessStop来实现子程式与母程式的分离,但该函数对于平台的要求比较特殊,最终决定用开辟线程的方式来解决这个问题。线程一旦结束,该子程式自然也就结束了。

3、        详细设计(关键代码如下)
DWORD WINAPI GetIATInfo ( LPVOID lpParameter )
{
	……
	STARTUPINFO				stStartUp;
	PROCESS_INFORMATION		stProcInfo;
	GetStartupInfo ( &stStartUp ) ;
	if( !CreateProcess(BaseInfo.szFileName,NULL,NULL,NULL,FALSE, \
		DEBUG_ONLY_THIS_PROCESS,NULL,NULL,&stStartUp,&stProcInfo) )   
	{
		SetEvent ( hEvent ) ;
		return dwErrorCode ;
	}
	else
	{		
		DEBUG_EVENT devent ;
		BOOL bIsContinue = true ;
		while ( bIsContinue )
		{
			if ( WaitForDebugEvent( &devent, 100 ) )
			{
				switch ( devent.dwDebugEventCode )
				{
				case EXCEPTION_DEBUG_EVENT:
					// 取IAT信息
					for ( UINT uIATEntryIndex = 0; DllItemInfo[uIATEntryIndex].dwIATEntry; uIATEntryIndex++ )
					{
						DWORD dwIATEntry = DllItemInfo[uIATEntryIndex].dwIATEntry + BaseInfo.dwImageBase ;
						DWORD dwIAT = 0, dwNumberOfByte = 0 ;
						UINT count = 0 ;
						
						while ( ReadProcessMemory(stProcInfo.hProcess,(LPVOID)(dwIATEntry),&dwIAT,0X4,&dwNumberOfByte) )
						{
							if ( dwIAT ) 
							{
								DllItemInfo[uIATEntryIndex].FunctionItemInfo[count].dwFunctionEntry = dwIAT ;
								DllItemInfo[uIATEntryIndex].FunctionItemInfo[count++].dwImageRVa = dwIATEntry-BaseInfo.dwImageBase;
								DllItemInfo[uIATEntryIndex].dwIATSize += 4 ;
								dwIAT = 0 ;
								dwIATEntry += 4 ;
							}	
							else
								break ;
						}		
					}
	
					bIsContinue = false ;
					break ;
				} //switch
				
				ContinueDebugEvent ( devent.dwProcessId, devent.dwThreadId, DBG_CONTINUE ) ;
			} // if
		} // while		
	} // else


	SetEvent ( hEvent ) ;
	return 0; 
}

4、        心得体会
这个软件写的比较久了,回忆了下。加深对加载过程的了解,熟悉WIN32调试API。
【参考文献】
[1].Windows核心编程 Jeffrey Richter著
【版权声明】必须注明原创于看雪技术论坛(bbs.pediy.com) 及作者,并保持文章的完整性。

[CTF入门培训]顶尖高校博士及硕士团队亲授《30小时教你玩转CTF》,视频+靶场+题目!助力进入CTF世界

上传的附件:
收藏
点赞7
打赏
分享
最新回复 (58)
雪    币: 1852
活跃值: (504)
能力值: (RANK:1010 )
在线值:
发帖
回帖
粉丝
北极星2003 25 2006-6-12 04:09
2
0
搞的太晚,头晕了
光发贴也至少编辑了十次
如有疏忽或者错误之处,请指出
雪    币: 222
活跃值: (10)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
drwch 3 2006-6-12 04:22
3
0
以DEBUG状态创建的进程不用加CREATE_SUSPENDED标志吧,内核在完成进程装载和初始化之后会在被调试进程的上下文环境中自己调用一次int 3的
雪    币: 603
活跃值: (617)
能力值: ( LV12,RANK:660 )
在线值:
发帖
回帖
粉丝
prince 16 2006-6-12 09:45
4
0
北极星辛苦!
雪    币: 1852
活跃值: (504)
能力值: (RANK:1010 )
在线值:
发帖
回帖
粉丝
北极星2003 25 2006-6-12 10:10
5
0
Originally posted by drwch
以DEBUG状态创建的进程不用加CREATE_SUSPENDED标志吧,内核在完成进程装载和初始化之后会在被调试进程的上下文环境中自己调用一次int 3的


多谢指点
我测试了你说的,完全正确
在这个地方,我确实做了重复工作
雪    币: 2506
活跃值: (995)
能力值: (RANK:990 )
在线值:
发帖
回帖
粉丝
CCDebuger 24 2006-6-12 10:25
6
0
呵呵,系列开始继续了?还怕你受刺激,不搞了呢。支持北极星!
雪    币: 184
活跃值: (108)
能力值: ( LV9,RANK:410 )
在线值:
发帖
回帖
粉丝
hnhuqiong 10 2006-6-12 10:30
7
0
严重支持.....期待好文继续
雪    币: 1852
活跃值: (504)
能力值: (RANK:1010 )
在线值:
发帖
回帖
粉丝
北极星2003 25 2006-6-12 10:33
8
0
Originally posted by CCDebuger
呵呵,系列开始继续了?还怕你受刺激,不搞了呢。支持北极星!


这个系列又没有停止过,只不过跟我的初衷不同
原本是想让各位高手出手,但是……
至于我自己嘛,有时间还是会继续的,只不过这样的话,整个流程会拉长。

刚看到的时候是受了点刺激,但是对我的积极性并没有影响

何况,还有你们这些朋友的支持
雪    币: 179
活跃值: (131)
能力值: ( LV12,RANK:290 )
在线值:
发帖
回帖
粉丝
WAKU 7 2006-6-12 11:59
9
0
唉...现在像北极星版版的好人不多了
雪    币: 236
活跃值: (35)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
误入楼台 1 2006-6-12 13:14
10
0
支持这个东西 学习
雪    币: 156
活跃值: (23)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
sbright 2 2006-6-12 13:32
11
0
最初由 北级星 发布
这个软件写的比较久了,回忆了下。加深对加载过程的了解,熟悉WIN32调试API。

大哥如果写文章太累,可以把自己可以发的代码发出来,写个大概说明就好.
其他牛b的大虾们也支持一下啊,多发发你们的程序.强烈支持北极星,来,亲一个.....
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
franke 2006-6-12 19:52
12
0
很详细的资料,可以减少小虾们的入门时间.把附件下了,仔细看看.多谢北极星
雪    币: 122
活跃值: (45)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
figofuture 2006-6-12 20:03
13
0
强烈支持一下啊!!!!!!!
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
scwlf 2006-6-12 22:23
14
0
非常感谢,大大辛苦了
雪    币: 260
活跃值: (81)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
萝卜 1 2006-6-12 23:32
15
0
支持!下载回头看
雪    币: 222
活跃值: (10)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
drwch 3 2006-6-13 00:19
16
0
最初由 北极星2003 发布
多谢指点
我测试了你说的,完全正确
在这个地方,我确实做了重复工作


不必客气,好文是要支持的

顺便问一下有没有办法直接提升每日许可发帖的数量,我想PM一下看雪,但是发现论坛连短消息系统都被关掉了
雪    币: 2506
活跃值: (995)
能力值: (RANK:990 )
在线值:
发帖
回帖
粉丝
CCDebuger 24 2006-6-13 00:35
17
0
drwch 兄可以来篇精华让我们学习一下,最快的方法就是来一篇精华帖,将会没有发帖限制,还可以上传文件,使用FTP
雪    币: 267
活跃值: (235)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
ngaut 6 2006-6-13 03:43
18
0
辛苦了,支持!
雪    币: 226
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
myd 2006-6-13 12:27
19
0
辛苦了,好帖哦!
雪    币: 235
活跃值: (41)
能力值: ( LV9,RANK:170 )
在线值:
发帖
回帖
粉丝
alpsdew 4 2006-6-13 13:36
20
0
向所有写作者致敬!

学习ing...
雪    币: 222
活跃值: (10)
能力值: ( LV8,RANK:130 )
在线值:
发帖
回帖
粉丝
drwch 3 2006-6-13 18:24
21
0
最初由 CCDebuger 发布
drwch 兄可以来篇精华让我们学习一下,最快的方法就是来一篇精华帖,将会没有发帖限制,还可以上传文件,使用FTP


我不知道该写些什么好啊,好像我会的这里基本上都有提到
雪    币: 21
活跃值: (269)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
littlepotato 2006-6-14 01:13
22
0
【时间】2007年6月12日
雪    币: 223
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
you8107 1 2006-6-14 10:42
23
0
雪    币: 1852
活跃值: (504)
能力值: (RANK:1010 )
在线值:
发帖
回帖
粉丝
北极星2003 25 2006-6-14 11:39
24
0
Originally posted by littlepotato
【时间】2007年6月12日


这个错误可以载入史册了
雪    币: 203
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
我爱罗 2006-6-14 13:48
25
0
各位大侠,我是一个菜鸟,不过我对这个很感兴趣,希望各位大侠多多帮助.
游客
登录 | 注册 方可回帖
返回