首页
社区
课程
招聘
[旧帖] [原创]跟我编写补丁小工具-第一课 0.00雪花
发表于: 2012-5-26 21:14 5785

[旧帖] [原创]跟我编写补丁小工具-第一课 0.00雪花

2012-5-26 21:14
5785

新人第一次发文章,而且还是教程类....像我这样文笔不好的...写得不好各位莫怪那
在我编写这个程序的时候是分3部分编写,所以我觉得教程还是分3次的好,今天这篇是第一篇,在进入第一部分之前我先把整个程序大概个所以然出来
这就是我们最后要完成的程序,整个工程主要由C语言和少部分汇编完成
好了,现在给大家一个对本程序的整体印象
工具由3部分组合而成,一部分就是上面这幅图,也就是工具主要部分,比如添加补丁地址,设置补丁方式等界面上的东西,除去这些界面上的东西和一些数据的获得,也就剩个生成补丁了,在最后一课大家会看到这个函数的实现,顺便提下..定制补丁那2个选项大家可以暂时忽略掉,我是觉得有点鸡肋了
工具的剩下2部分一个是Loader补丁一个patch补丁(内存补丁和文件补丁),单说补丁的话就算一部分了
如果写过补丁程序的话应该会很清楚,只是补丁都是针对某一个程序,每次都写过的话就麻烦了,所以我们来编写工具靠工具“生产”补丁,而用来生产补丁的补丁就成了“补丁模版”,有个模版稍加改变,就能产生需要的补丁程序,工具“生产”补丁主要就靠“模版”啦

所以要编写整个工具我们就得先从“模版”开始,模版固定了工具也好写了
大概也所以个然了,现在就开始工具的第一个也是最简单的“模版”------Loader

编写这个Loader"模版",我们首先就要考虑怎么个模版法,补丁方法是其次
一个补丁,首先会有针对的文件,然后是补丁的地址和地址的数据等变量,我们的目标就是“模版”到补丁工具能够轻松的修改他!
于是,我们只要为这些数据专门安排一个区段,那么工具不就能迅速的定位了吗?到这相信各位大概清楚了补丁“模版”的实现,下面就是部分代码中:

//需要工具修正的节为.sdata节
#pragma data_seg(".sdata")
DWORD	wTypeOfPatch = 0;		       //我们支持多种补丁方法,所以加个变量指示补丁类型
DWORD	dwPatchNum = 2;		      //补丁数量
//偏移8(指在.sdata节中的偏移)
TCHAR	szFileName[MAX_PATH] = { 0 };
//偏移528
DWORD	dwPatchAddress[16] = { 0}  //////////////////////利用调试寄存器打丁///////////////////////////////////////////////////////
/////////////打此类补丁应在补丁地址第一个地址填上希望中断的地址以确保所有地址数据已解码,以保证补丁正确性///////////
//偏移592
BYTE	byOldData[16] = { 0};		//补丁处旧数据和新数据
//偏移608
BYTE	byNewData[16] = { 0};
#pragma data_seg()
#pragma comment(linker, "/SECTION:.sdata,ERW")//添加写属性提供修正可能

[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

上传的附件:
收藏
免费 6
支持
分享
最新回复 (26)
雪    币: 45
活跃值: (55)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
2
牛B,膜拜!
2012-5-27 10:29
0
雪    币: 49
活跃值: (40)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
3
上一课我们做出了最简单的一个Loader,这次我们就要做一个最复杂的patch了,不单代码比上次多多了,还要混合汇编~~~
“模版”的数据在这咱就不多说了,数据依然是那些个数据,节区依然是那个.sdata,要看的到上一课看哈
在这里我们要实现的patch,也就是文件补丁----给PE文件附加上执行代码
在这个“模版”patch中,我实现了4种补丁类型,分别是:
#define ADD_LAST_SECTION  1   //添加代码到最后一个区段	
#define ADD_NEW_SECTION  2  //添加代码到一个新建的区段
#define ADD_TO_HEADER       3  //添加代码到PE头部
//除了以上3种外还有一种是寻找已存在区段空闲处并插入代码,这种插入PE头的修改更简单,只是比较容易失败,这里就不实现了
//这里再加一种字节补丁,针对一些简单的程序
#define	BYTE_PATCH		 4
由于PE文件对齐后存在的间隙给我们的补丁代码带来了生存空间,我们的目标就是想法设法让我们的补丁代码进入目标程序,让目标程序一运行就自动给我们补丁,我们暂且不管补丁代码如何补丁,现在的目标就是把代码加入PE文件并让他很好的运行起来,所以一开始并不需要补丁的实现,我们可以把补丁编写为弹出一个对话框来作为测试
现在假设存在以下2个变量
Appendcode_Start;                //附加代码段起始处
Appendcode_End;            //附加代码段结束处
于是我们的补丁代码起始地址为&Appencode_Start,大小为(&Appencode_End-&Appencode_Start)
有了这2个变量,我们就可以专注于如何添加代码到PE中了
首先我们实现第一种类型的代码添加---添加到最后一个区段
对于了解PE的朋友们都会知道,PE中的数据是分段存储的,而在节表中记录了这些区段的信息,而最后一个区段比较特殊,如果我们构造的好的话在能够加载的情况下应该可以无限制的添加代码,别的先不说,先来个添加代码后的整体结构大家应该就会比较清楚了(下面的东西修改了几次都对不齐,不知道怎么整,就这样将就吧= = )
//_________________
//|                                |   
//|            PE头         |         第一部分
//|                                |
//|________________|
//|_______节表______|
//|                                |
//|_______对齐______|
//|                                |
//|                                |
//.                                .
//.        各区段          .         第二部分
//.                                .
//|                                |
//|________________|
//.___对齐后间隙______.
//!                                !
//!____附加代码______!        第三部分
//!____最终对齐______!

被补丁后的整个PE文件结构如上,其中第一第二部分是整个文件原始情况
加上第三部分后就是我们的目标文件了至于具体的如何加上去,用代码说话吧!
HANDLE	hFile ;//文件句柄
HANDLE	hMap; //映射文件句柄

//////////////////////////////添加代码到最后一个区块/////////////////////////////////////

BOOL AddToLastSection(PBYTE lpMemory, DWORD dwFileSize)
{
	PIMAGE_NT_HEADERS		lpNtHeaders;
	PIMAGE_SECTION_HEADER	lpSectionHeader;
	PIMAGE_SECTION_HEADER	lpLastSectionHeader;
	DWORD					dwNewFileSize;		//最终文件大小
	DWORD					dwFileAlignSize;	//原文件对齐后大小
	DWORD					dwLastSectionAlignSize; //最后区段内存对齐后大小
	DWORD					dwPatchSize;		//补丁大小
	DWORD					dwFileAlign;		//文件对齐粒度
	DWORD					dwSectionAlign;		//内存对齐粒度
	//DWORD					dwLastSectionSize;
	//DWORD					dwPatchStart;	//指定补丁要复制到的文件偏移起始
	PBYTE					lpNewFile;		//最终文件缓存
	DWORD					dwSectionNum;

	lpNtHeaders		= (PIMAGE_NT_HEADERS)( lpMemory + ((PIMAGE_DOS_HEADER)lpMemory)->e_lfanew );
	lpSectionHeader	= (PIMAGE_SECTION_HEADER)(lpNtHeaders + 1);
	
	dwSectionNum = lpNtHeaders->FileHeader.NumberOfSections ;
	lpLastSectionHeader	= lpSectionHeader + dwSectionNum - 1;
	
	dwFileAlign		= lpNtHeaders->OptionalHeader.FileAlignment;
	dwSectionAlign	= lpNtHeaders->OptionalHeader.SectionAlignment;
	dwFileAlignSize	= Align(dwFileSize, dwFileAlign);	//求原文件对齐大小
	
	dwPatchSize		= ((DWORD)&Appendcode_End ) - ( (DWORD)&Appendcode_Start );	//获得补丁代码大小

	dwNewFileSize	= Align(dwFileAlignSize + dwPatchSize, dwFileAlign); //获得最终文件对齐后大小
	dwLastSectionAlignSize	= Align(lpLastSectionHeader->Misc.VirtualSize + dwPatchSize, dwSectionAlign); //获得内存中最后区段大小

	lpNewFile		= (PBYTE)VirtualAlloc (NULL, dwNewFileSize, MEM_COMMIT, PAGE_READWRITE);
	if ( !lpNewFile )  //分配内存失败
		return FALSE;

	//复制原文件数据
	memset(lpNewFile, 0, dwNewFileSize);
	memcpy(lpNewFile, lpMemory, dwFileSize);
	//复制完毕,关闭映射文件和句柄 (不关闭后面就无法创建新文件,本来想在AddCode关闭,结果试了抛异常什么的还是没用,只能hMap设置成全局变量然后在这关了,实在不雅观啊‘_’)
	UnmapViewOfFile(lpMemory);
	CloseHandle(hMap);
	CloseHandle(hFile);

	//复制补丁代码前先转储补丁数据
	PBYTE	pBuffer		= (PBYTE)(&Patch_Data);   //指向附加代码补丁数据处
	//(*(DWORD*)pBuffer)	= dwPatchNum; 
	memcpy(pBuffer + 4, dwPatchAddress, 16*sizeof(DWORD) );
	pBuffer	+= 4 + 16*sizeof(DWORD);
	memcpy(pBuffer, byOldData, 16);
	memcpy(pBuffer+16, byNewData, 16);

	//复制补丁代码
	memcpy(lpNewFile + dwFileAlignSize, &Appendcode_Start, dwPatchSize); 

	//修正PE头数据
	PIMAGE_NT_HEADERS		lpNewNtHeaders;
	PIMAGE_SECTION_HEADER	lpNewSectionHeader;
	PIMAGE_SECTION_HEADER	lpNewLastSection;
	DWORD*					lpNewEntry;			//指向新入口处
	DWORD					OldEntry;
	
	lpNewNtHeaders		= (PIMAGE_NT_HEADERS)( lpNewFile + ((PIMAGE_DOS_HEADER)lpNewFile)->e_lfanew );
	lpNewSectionHeader	= (PIMAGE_SECTION_HEADER)(lpNewNtHeaders + 1);
	lpNewLastSection	= lpNewSectionHeader + dwSectionNum - 1;

 	
	//给最后区段添加读写执行属性
	lpNewLastSection->Characteristics  |= 0xC0000020;
	
	//修正最后一个区段的偏移量
	lpNewLastSection->SizeOfRawData		= dwNewFileSize - lpNewLastSection->PointerToRawData;  
	lpNewLastSection->Misc.VirtualSize	= Align( GetValidSize(lpNewFile, lpNewLastSection), dwSectionAlign);//Align(lpNewLastSection->Misc.VirtualSize + dwPatchSize, dwSectionAlign) ;
	
	//修正镜像大小
	lpNewNtHeaders->OptionalHeader.SizeOfImage	= Align(lpNewLastSection->VirtualAddress + lpNewLastSection->Misc.VirtualSize, dwSectionAlign);

	//修正入口地址
	OldEntry	= lpNewNtHeaders->OptionalHeader.AddressOfEntryPoint;
	lpNewNtHeaders->OptionalHeader.AddressOfEntryPoint	= OffsetToRVA( (IMAGE_DOS_HEADER *)lpNewFile, dwFileAlignSize) ;
	
	//修正补丁代码跳回OEP的参数
	lpNewEntry			= (DWORD*)(lpNewFile + dwFileAlignSize + dwPatchSize - 5);
	*lpNewEntry			= OldEntry - (lpNewNtHeaders->OptionalHeader.AddressOfEntryPoint + dwPatchSize - 1); 

	//补丁完毕,写回文件
	HANDLE  hNewFile;
	DWORD	dwRead;
	if (INVALID_HANDLE_VALUE == ( hNewFile = CreateFile (szFileName, GENERIC_READ | GENERIC_WRITE , FILE_SHARE_READ , NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_ARCHIVE, NULL) ) )
		return FALSE;

	WriteFile(hNewFile, lpNewFile, dwNewFileSize, &dwRead, NULL);
	CloseHandle(hNewFile);
	return TRUE;
}
//这里再附上调用添加代码函数的主要函数,保证完整性
BOOL AddCode( )
{
	DWORD	dwFileSize;	//文件大小
	PBYTE	lpMemory;	//内存映射指针
	
	if (INVALID_HANDLE_VALUE != ( hFile = CreateFile (szFileName, GENERIC_READ , FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL) ) )
	{
		dwFileSize	= GetFileSize (hFile, NULL);
		if (dwFileSize)
		{
			hMap	= CreateFileMapping (hFile, NULL, PAGE_READONLY, 0, 0, NULL);
			if (hMap)
			{
				lpMemory	= (BYTE *)MapViewOfFile (hMap, FILE_MAP_READ, 0, 0, 0);
				if (lpMemory)
				{
					//使用指定方法打补丁
					switch(dwTypeOfPatch)
					{
						case ADD_LAST_SECTION:
							if (!AddToLastSection(lpMemory, dwFileSize) )
								return FALSE;
							break;

						case ADD_NEW_SECTION:
							if (!AddToNewSection(lpMemory, dwFileSize) )
								return FALSE;
							break;

						case ADD_TO_HEADER:
							if (!AddToHeaderSection(lpMemory, dwFileSize) )
								return FALSE;
							break;

						case BYTE_PATCH:
							if (!BytePatch(lpMemory, dwFileSize))
								return FALSE;
							break;

					}
					
					return TRUE;
				}
				else
					MessageBox (GetActiveWindow() , TEXT("目标文件打开失败"), NULL, MB_OK);
			}
			else
				MessageBox ( GetActiveWindow() , TEXT("目标文件打开失败"), NULL, MB_OK);
		}
	}
	else
		MessageBox (GetActiveWindow() , TEXT("目标文件打开失败"), NULL, MB_OK);
	return FALSE;
}
上面的注释应该写的比较清楚了,这样我们就完成了一个添加代码的方法,第二种方法是给PE增加一个新区段,这种方法需要PE头至少有容纳一个节表(20个字节)的空闲容量,不过由于PE头也可以增加大小(最大0x1000字节),所以这种方法基本也能成功,只是增大了PE头的话就需要调整所有节表的文件偏移,要麻烦一些具体实现就不贴出了,可以去看附件的代码

到这里,整个patch的添加代码部分就实现了,下面就是具体编写实现补丁的代码,这个代码用汇编编写,涉及到自定位,动态加载等技术,在贴出代码前有些东西要提下,由于用mov eax,[esp]的方法获得kernel32.dll内部地址然后向前搜索kernel32.dll基地址的方法虽然稳定,但有局限性,比如用这种方法只能给目标文件实现一次补丁,如果继续添加补丁之前的补丁的mov eax,[esp]就会无效,等于说这种方法只能给目标文件添加一次补丁代码,而且代码量相对来说比较大。
所有我这里用的是从系统PEB结构获得基地址,仅需要以下几行代码即可
assume fs:nothing
mov eax,fs:[30h]
mov eax,[eax+0ch]
mov esi,[eax+1ch]
lodsd
mov eax,[eax+8]
只需要上面几行代码就可以获得kernel32基地址,具体涉及到TEB,PEB,PEB_LDR_DATA结构,这种方法在XP上能很好的工作。但是事实上用这种方法在WIN7下获得的是kernelbase.dll的基址而不是kernel32。还有一种,通过SEH框架最后一个异常处理地址,这个根据WINPE权威指南上说是kernel32.dll中,所以可以从此入手。而经测试后在WIN7中这个地址也换了,已经是位于ntdll.dll中的地址。
嗯,看似无路可走,其实PEB的方法还是可行的,在WIN7下查询kernelbase.dll导出表,发现其中虽然没有了LoadLibrary函数,但是有LoadLibraryEx函数,GetProcAddress函数也有,有这2个函数就够了,我们用PEB的动态加载就能够实行,注意是LoadLibraryEx函数哦!比LoadLibrary多了2个参数,调用时需要注意!
还有一个问题就是如何将前面的C代码和汇编代码结合在一起编译,首先就要导出某个变量的符号供其他代码使用,这个问题存在的原因主要是链接时外部符号无法识别,究其根本就是调用约定和名称修饰,具体可以参考C/C++名称修饰,加密解密的第16章也有讲到这个混合编译时的设置等,可以自行查找
讲了这么多,下面就贴出代码落~~
include		c:\masm32\include\windows.inc

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 导出变量供补丁工具使用
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;导出的变量
PUBLIC	Appendcode_Start	;附加代码起始处	
PUBLIC	Appendcode_End		;附加代码结束处
PUBLIC	Patch_Data		;补丁数据处

		.code
Appendcode_Start LABEL		DWORD

	jmp	_NewEntry

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;重要的函数名,为兼容WIN7 kernelbase.dll,使用LoadLibraryExA函数
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
szLoadLibraryExA db	'LoadLibraryExA',0  
szGetProcAddress db	'GetProcAddress',0

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;SEH错误Handler,用于在错误中回复并跳转到安全位置
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_SEHHandler proc _lpExceptionRecord,_lpSEH,_lpContext,_lpDispatchertext
	pushad
	mov	esi,_lpExceptionRecord
	assume	esi:ptr EXCEPTIONRECORD
	mov	edi,_lpContext
	assume	edi:ptr CONTEXT
	mov	eax,_lpSEH
	push	[eax+0ch]
	pop	[edi].regEbp
	push	[eax+08]
	pop	[edi].regEip
	push	eax
	pop	[edi].regEsp
	assume	edi:nothing,esi:nothing
	popad
	mov	eax,ExceptionContinueExecution
	ret
_SEHHandler endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;用PEB获取基址的方法,WIN7中获得的实际是kernelbase.dll的基地址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_GetKernel32Base proc
	local	@dwRet

	pushad
	
	assume fs:nothing
	mov eax,fs:[30h]	;获取PEB所在地址
	mov eax,[eax+0ch]	;获取PEB_LDR_DATA 结构指针
	mov esi,[eax+1ch]	;获取InInitializationOrderModuleList 链表头
				;第一个LDR_MODULE节点InInitializationOrderModuleList成员的指针
	lodsd			;获取双向链表当前节点后继的指针
	mov eax,[eax+8]		;获取kernel32.dll的基地址(WIN7中是kernelbase.dll基址)
	mov @dwRet,eax
	popad
	
	mov eax,@dwRet
	ret
_GetKernel32Base endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;查找导出表获取指定API地址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_GetApi	proc	_hModule,_lpszApi
	local	@dwReturn,@dwSize
	pushad
	
	call	@F
	@@:
	pop	ebx
	sub	ebx,@B
	
	assume	fs:nothing
	push	ebp
	push	[ebx+offset error]
	push	[ebx+offset _SEHHandler]
	push	fs:[0]
	mov	fs:[0],esp
	
	mov	edi,_lpszApi
	mov	ecx,-1
	xor	eax,eax
	cld
	repnz	scasb
	sub	edi,_lpszApi
	mov	@dwSize,edi

	mov	esi,_hModule
	add	esi,[esi+3ch]
	assume	esi:ptr IMAGE_NT_HEADERS
	mov	esi,[esi].OptionalHeader.DataDirectory.VirtualAddress
	add	esi,_hModule
	assume	esi:ptr IMAGE_EXPORT_DIRECTORY

	mov	ebx,[esi].AddressOfNames
	add	ebx,_hModule
	xor	edx,edx
	.while  edx <	[esi].NumberOfNames
		push	esi
		mov	edi,[ebx]
		add	edi,_hModule
		mov	esi,_lpszApi
		mov	ecx,@dwSize
		cld
		repz	cmpsb      ;搜索指定API的字符串
		.if	!ecx
			pop	esi
			jmp	@F  ;成功
		.endif
		next:
		pop	esi
		inc	edx
		add	ebx,4
	.endw
	jmp	error
	@@:
	sub	ebx,[esi].AddressOfNames         
	sub	ebx,_hModule     ;获得偏移
	shr	ebx,1                   ;由于索引数组是WORD数组,所以右移一位
	add	ebx,[esi].AddressOfNameOrdinals
	add	ebx,_hModule
	movzx	eax,word ptr [ebx]
	shl	eax,2                    ;将取得的索引左移2位获得字节偏移
	add	eax,[esi].AddressOfFunctions     
	add	eax,_hModule

	mov	eax,[eax]       ;取得目标API地址
	add	eax,_hModule
	mov	@dwReturn,eax
	error:
	pop	fs:[0]
	add	esp,0ch
	assume	esi:nothing
	popad
	mov	eax,@dwReturn
	ret
_GetApi endp

;补丁所需要的函数和全局变量
szCreateThread		db	'CreateThread',0
szGetTickCount		db	'GetTickCount',0
szVirtualProtect	db	'VirtualProtect',0
lpGetTickCount		dd	0
StartCount		dd	0

;以下变量需要补丁程序修正
Patch_Data	LABEL		DWORD
;dwTypeOfPatch	dd	0				;指示补丁类型
dwPatchNum	dd	0				;补丁数量	
dwPatchAddress	dd	16 dup(0)			;补丁地址
byOldData	db	16 dup(0)			;补丁处旧数据和新数据
byNewData	db	16 dup(0) 			

;这个线程是实现补丁的部分,循环检测补丁地址的数据并补丁,还加入了一个5分钟的超时检测
_Thread	proc	_lpVirtualProtect
	local	@lpGetTickCount,@temp,@StartCount,@num

	pushad
	call	@F
	@@:
	pop	ebx
	sub	ebx,@B
	mov	edx,dword ptr [ebx+offset lpGetTickCount]
	mov	@lpGetTickCount,edx
	mov	edx,dword ptr [ebx+offset StartCount]
	mov	@StartCount,edx

	mov	ecx,dword ptr [ebx+offset dwPatchNum]
	mov	@num,ecx
	.while	TRUE
		call	@lpGetTickCount
		sub	eax,@StartCount
		cmp	eax,493e0h		;大于五分钟则超时退出线程
		jg	_exit
		;开始检测补丁地址
		lea	esi,dword ptr [ebx+offset dwPatchAddress]  ;指向补丁地址
		lea	edi,dword ptr [ebx+offset byOldData]	   ;补丁处旧数据
		lea	edx,dword ptr [ebx+offset byNewData]	   ;补丁处新数据
		;检测所有补丁处字节
		mov	ecx,dword ptr [ebx+offset dwPatchNum]
		_peek:
			push	ecx
			mov	ecx,dword ptr [esi]	
			xor	eax,eax
			mov	al,byte  ptr [ecx]		;取补丁处数据
			cmp	al,byte  ptr [edi]		;补丁处是否解码
			jne	_mismatch
			
			;更改页面为读写执行,以确保补丁地址处拥有读写执行权限
			pushad
			lea		eax,@temp
			push	eax
			push	40h
			push	100h
			push	ecx
			call	_lpVirtualProtect
			popad		
			mov	al,byte ptr [edx]	;进行补丁
			mov	byte ptr [ecx],al
			dec	@num
			_mismatch:	
			inc	edi
			inc	edx
			add	esi,4
			pop	ecx
			cmp	@num,0
			je	_exit
		loop	_peek
	.endw
	_exit:
	popad
	ret
_Thread	endp


;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;补丁功能部分
;_dwKernelBase:		kernel32.dll基址
;_lpGetProcAddress:	GetProcAddress地址
;_lpLoadLibraryExA	LoadLibraryExA地址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;这里的作用是获得一些必须的函数,然后创建补丁线程
_Patch	proc	_dwKernelBase,_lpGetProcAddress,_lpLoadLibraryExA
		local	@lpVirtualProtect		
		local	@temp

		pushad
		lea	edx,dword ptr [ebx+offset szVirtualProtect]
		push	edx
		push	_dwKernelBase
		call	_lpGetProcAddress
		cmp	eax,0
		je	_exit
		mov	@lpVirtualProtect,eax
		lea	edx,@temp
		push	edx
		push	40h
		push	1000h
		lea	edx,dword ptr [ebx+offset lpGetTickCount]
		push	edx
		call	@lpVirtualProtect   ;确保这个附加代码处全局变量位置可写
		lea	edx,dword ptr [ebx+offset szGetTickCount]
		push	edx
		push	_dwKernelBase
		call	_lpGetProcAddress
		.if	eax
			mov	dword ptr [ebx+offset lpGetTickCount],eax
			call	eax
			mov	dword ptr [ebx+offset StartCount],eax
			lea	edx,dword ptr [ebx+offset szCreateThread]
			push	edx
			push	_dwKernelBase
			call	_lpGetProcAddress
			.if	eax	
				lea		edx,@temp
				push	edx
				push	0
				push	@lpVirtualProtect				;线程参数为VirtualProtect函数的地址
				lea	edx,dword ptr [ebx+offset _Thread]
				push	edx
				push	0
				push	0
				call	eax	;创建监测线程进行补丁
			.endif
		.endif
		_exit:
		popad
		ret
_Patch	endp
;从导入表获得GetProcAddress函数和LoadLibraryExA函数地址
_start	proc
	local	@dwKernel32Base
	local	@lpGetProcAddress,@lpLoadLibraryExA
	
	pushad

	call	_GetKernel32Base
	.if	eax
		mov	@dwKernel32Base,eax
		lea	edx,dword ptr [ebx+offset szGetProcAddress]
		push	edx
		push	eax
		call	_GetApi
		mov	@lpGetProcAddress,eax
	.endif
	.if	@lpGetProcAddress
		lea	edx,dword ptr [ebx+offset szLoadLibraryExA]
		push	edx
		push	@dwKernel32Base
		call	@lpGetProcAddress
		.if	eax
			mov	@lpLoadLibraryExA,eax
			push	eax
			push	@lpGetProcAddress
			push	@dwKernel32Base
			call	_Patch
		.endif
	.endif

	popad
	xor	eax,eax
	ret
_start	endp

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;PE文件新入口
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_NewEntry:
	call	@F
	@@:
	pop	ebx
	sub	ebx,@B
	call	_start
	;ret
	jmpToStart db 0E9h,0F0h,0FFh,0ffh,0ffh	;需要补丁程序修正,放在这个位置只要在&Appencode-5处赋值就可以修正了,比较方便
	ret
Appendcode_End LABEL		DWORD
end
经过一系列代码运行测试后最终才使用上面的补丁代码,然后再在C代码中对汇编代码中需要修正的数据进行修正,测试运行,其实由于是文件补丁,我们还得考虑重复补丁的情况~
所以就在patch中加了个CRC32验证,验证失败就认为可能损坏或已经补丁,详细的可以下载附件。做完这些工作后,我们的patch"模版"就完工了,虽然也不是最终版本,最终的修正都放到最后一篇讲,因为我也是在最后工具写完测试才发现的问题!
第二篇出了,第一篇都没什么人反应啊(沮丧中...),第三篇尽量早点发吧
下面传上patch的代码,整个工程的在第一篇附件中有
Patch.rar
上传的附件:
2012-5-27 11:28
0
雪    币: 49
活跃值: (40)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
4
经过前面2课,我们已经把需要的补丁“模版”完成,到这最后一课我们就要完成最后一步----------编写一个补丁工具了
在编写之前先要说一点需要的东西,可能有些人不知道,因为代码中用到了<windowsx.h>这个头文件
这个头文件主要就是一个消息分流器,里面有很多各种宏定义,比如消息分流,API宏,控件宏等,主要就是为了简化WIN32程序的开发,例如下面这个主窗口过程:
BOOL CALLBACK DialogProc (HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message)
	{
		HANDLE_MSG(hDlg, WM_INITDIALOG, Main_OnInitDialog);
		HANDLE_MSG(hDlg, WM_COMMAND	, Main_OnCommand	);

		case WM_CLOSE:
			EndDialog (hDlg, 0);
			return true;
	}
	return FALSE;
}
中的HANDLE_MSG的作用就是将某个消息分流到某个函数中,同时参数也定义好了,省去了一大堆的case和不同消息的参数不同意义带来的麻烦,至于一些控件宏更是一目了然看名字就知道是干嘛的这就不说了,然后我还定义了几个列表视图的控件宏方便使用,在"ApiMacro.h"头文件中可以找到,然后还用了超类化定义出几个16进制控件,具体方法是根据老罗WIN32汇编程序设计来的,还有个RvaToOffset.h文件,其中定义了一些RVA和文件偏移之间互相转换的函数以及几个小函数,最后还加上了一个指示WIN7界面风格的链接参数,这可是我找了很久才找到的,以前不知道的时候那界面真是难看死了,下面这个代码至少在VS2008中是有效的:
//添加WIN7风格界面
#pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0'\processorArchitecture='x86' publicKeyToken='6595b64144ccf1df' language='*'\"")
加上这个界面就漂亮多了。界面上的东西就不多说了,拉拉控件百度下就OK,下面贴上完成的界面:

界面做好了,下面就来说说如何创建补丁,我们在前面不是弄出了2个补丁"模版"吗?在这里我们就把补丁模版的exe文件作为工具的自定义资源加入,这样工具和“模版”就成为一个整体了,当我们需要创建补丁的时候只需要用FindResource,LoadResource,LockResource这几个API取得资源,然后写入文件,这个补丁"模版"就再次作为EXE出现了,然后我们只需要根据补丁工具的数据对"模版"数据进行修正,一个补丁就这样制造出来了,这里特别为补丁的生成写了一个函数,就贴在下面了:
//创建补丁文件,补丁模版以资源的形式存储在程序中
//参数:szPatchName:创建的补丁文件名     szFileName:目标文件名     lpPatchAddress:补丁地址数组  
//	   lpNewByte:补丁原始数据数组       lpNewByte:补丁新数据数组  dwTypeOfPatch:补丁类型
//	   dwPatchNum:补丁数量				ID:补丁模版的资源ID		  bCRC32:是否加入CRC32文件验证
BOOL CreatePatch(TCHAR szPatchName[ ], TCHAR szFileName[ ], DWORD lpPatchAddress[ ],BYTE lpOldByte[ ],BYTE lpNewByte[ ], DWORD	dwTypeOfPatch, DWORD dwPatchNum, DWORD ID, BOOL bCRC32 )
{
	static	char	secName[8] = ".sdata";
	DWORD	CRC32;
	if (bCRC32)		  //这里是CRC32的验证
	{
		CRC32 = GetCRC32(szFileName );
		if (!CRC32)
		{
			MessageBox(NULL, TEXT("CRC32提取出错"), NULL, 0);
			return FALSE;
		}
	}
	
	DWORD	dwResSize;
	PBYTE	lpResData;
	HGLOBAL	hGlobal;
	HRSRC hRes	= FindResource(hInst, MAKEINTRESOURCE(ID), L"PETYPE" );
	if (hRes)
	{
		dwResSize	= SizeofResource(hInst, hRes);
		hGlobal		= LoadResource(hInst, hRes);
		if (hGlobal )
		{
			lpResData	= (PBYTE)LockResource(hGlobal);
			if (lpResData )
			{

///////////////////////////////////开始写入文件并修正补丁中的参数//////////////////////////////////////////

				HANDLE					hFile, hMap;
				PBYTE					lpMemory;
				PIMAGE_NT_HEADERS		lpNtHeaders;
				PIMAGE_SECTION_HEADER	lpSectionHeader;
				PBYTE					lpSectionData;
				DWORD*					lpCRC32;
				DWORD					dwFileSize, dwRead, dwSectionNum;

				if (INVALID_HANDLE_VALUE != ( hFile = CreateFile (szPatchName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_ARCHIVE, NULL) ) )
				{
					WriteFile(hFile, lpResData, dwResSize, &dwRead, NULL);    //写入文件
					dwFileSize	= GetFileSize (hFile, NULL);
					//修正数据
					if (dwFileSize)
					{
						hMap	= CreateFileMapping (hFile, NULL, PAGE_READWRITE, 0, 0, NULL);
						if (hMap)
						{
							lpMemory	= (BYTE *)MapViewOfFile (hMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
							if (lpMemory)
							{
								lpNtHeaders		= (PIMAGE_NT_HEADERS)(lpMemory + ((PIMAGE_DOS_HEADER)lpMemory)->e_lfanew);
								if (bCRC32)    //写CRC32值
								{
									lpCRC32			= (DWORD*)((PBYTE)(lpNtHeaders)-4);
									*lpCRC32		= CRC32;
								}
								dwSectionNum	= lpNtHeaders->FileHeader.NumberOfSections;
								lpSectionHeader	= (PIMAGE_SECTION_HEADER)(lpNtHeaders + 1);
								
								//查找需修正变量所在区段
								for (DWORD i=0; i < dwSectionNum; i++, lpSectionHeader++)
								{
									if ( !lstrcmpiA( (LPCSTR)lpSectionHeader->Name, secName) )
										break;
								}
								lpSectionData			= lpMemory + RvaToOffset( (PIMAGE_DOS_HEADER)lpMemory, lpSectionHeader->VirtualAddress);
////////////////////////////////////////////////////////////修正变量///////////////////////////////////////////////////////////////////////////
								int x;
								*(DWORD*)lpSectionData	= dwTypeOfPatch;
								*(DWORD*)(lpSectionData+4) = dwPatchNum;
								
								for(x=lstrlen(szFileName); x > 0; x--)
									if(szFileName[x] == TEXT('\\') )
										break;
								
								lstrcpy( (LPWSTR)(lpSectionData+8), &(szFileName[x]) );
								memcpy(lpSectionData+528, lpPatchAddress, ITEM_NUM*sizeof(DWORD));
								memcpy(lpSectionData+592, lpOldByte, ITEM_NUM*sizeof(BYTE));
								memcpy(lpSectionData+608, lpNewByte, ITEM_NUM*sizeof(BYTE));
////////////////////////////////////////////////////////////修正完毕////////////////////////////////////////////////////////////////////////////
								
								UnmapViewOfFile (lpMemory);
								CloseHandle (hMap);
								CloseHandle (hFile);
								return TRUE;
							}

						}

					}

				}
////////////////////////////////////////////////////////////////////////////////////////////////////////////

				return FALSE;
			}
		}
	}
	return FALSE;
}
到这里,我们的补丁工具就基本完成了,真的完成了?还没有!接下来就会出现我在前面提到的某个问题了!
事实上我们前面的代码单独有用,配上这个工具来动态生成用到实际补丁当中就无效了!为什么?且看下文。
现在来测试效果,其实你一测试就会发现,无论怎么生成,补丁都没有效果,我编写的过程就是这样,还不停的调工具程序,完全没有错误,生成的补丁的对应区段数据也正确写入,没有一点问题。
于是只能OD调试补丁程序一点点的查找,最后发现,在根据补丁类型数据进行switch跳转的地方发现,这个switch跳转不见了!仔细一想,原来是被编译器优化掉了这个跳转,因为“模版”中补丁类型的数据等于0,是个常量,所以编译器就直接优化掉跳转而不去管他。
所以我试着不给指示类型的数据初始值,然后继续调试,这次就更麻烦了,不给他初始值他都没在我们的目标区段.sdata占用空间,变量都找不到更别提修正了,于是就别想用初值来。。。于是想着实在没办法就只有用内联汇编实现跳转了,不过这样太难看了于是就问别人,结果还真问到了,一个用于指示链接器优化的代码#pragma optimize("",on|off)
于是只需要在包含跳转的代码函数体前后分别加上
#pragma optimize("",off)
//........函数体
#pragma optimize("",on)
就OK了
了解了这一层次的原因,我们就应该想到,所有用上了这几个数据的地方都可能存在这种问题,于是经过测试发现确实是这样,除了那几个数组数据,别的都存在这情况,不过如果在整个函数上都去掉优化那效率舍弃就太大了,于是我选择了将相关位置视情况改为内联汇编,这样就成功解决了这个问题,解决了这些问题后才是我们最终的“模版”补丁。
于是经过这些修改后,我们的补丁工具终于”出炉“了!

下面再提下那个”鸡肋“吧,各位应该看到界面上有个定制补丁选项吧?这个补丁选项也有个专门的函数CreateDiyPatch(),我本意是想实现一个用汇编编写的给目标文件最后一个区段加入执行代码的补丁(嗯,确实也写好了,在template文件目录下,附件可以找到),然后补丁到底要干什么由用户实现,所以如果点击了这个按钮就会弹出一个编辑器对话框,在里面写入需要的代码,然后由工具隐藏编译链接来实现补丁生成,嗯,虽然说写是写出来了这个功能,而且编译链接的结果也通过一些代码将结果以对话框的形式弹出来显示,但是一看发现masm32的库和头文件有几十MB总之很大,要移到工具里不太现实,需要机器里设置了masm32的环境变量才能正常编译链接,所以这个功能我也没有测试,不知道有没有一些小问题,下面就贴上这个函数的代码吧
BOOL CreateDiyPatch()
{
	static TCHAR	szStr[]= TEXT("创建自定义补丁"); 
	static char		szBuffer[1024];
	static TCHAR	szPatch[MAX_PATH];	
	static TCHAR	szCurrentDirectory[MAX_PATH];
	static TCHAR	szOriDirectory[MAX_PATH];
	HANDLE	hAsmFile, hAsmMap;
	PBYTE	lpMemory;
	DWORD	dwFileSize;
	int x;
	if (DIY_OK == FALSE)
		return FALSE;
	if ( PopFileSaveDlg (g_hWnd, g_szPatchName, szStr) )
	{
		GetCurrentDirectory(sizeof(szOriDirectory), szOriDirectory);
		GetModuleFileName(hInst, szPatch, sizeof(szPatch) );
		for(x=lstrlen(szPatch); x > 0; x--)
			if(szPatch[x] == TEXT('\\') )
				break;
		lstrcpy(szCurrentDirectory, szPatch);
		lstrcpy(&(szCurrentDirectory[x]), L"\\template\\");  //获得当前目录
		lstrcpy( &(szPatch[x]), TEXT("\\template\\patch"));
		if (INVALID_HANDLE_VALUE != ( hAsmFile = CreateFile ( szPatch, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ , NULL, OPEN_EXISTING, FILE_ATTRIBUTE_ARCHIVE, NULL) ) )
		{
			dwFileSize	= GetFileSize (hAsmFile, NULL);
			if (dwFileSize)
			{
				hAsmMap	= CreateFileMapping (hAsmFile, NULL, PAGE_READWRITE, 0, 0, NULL);
				if (hAsmMap)
				{
					lpMemory	= (BYTE *)MapViewOfFile (hAsmMap, FILE_MAP_ALL_ACCESS, 0, 0, 0);
					if (lpMemory)
					{
						memcpy(lpMemory, g_szUserCode, strlen( (char*)g_szUserCode) ); //复制自定义代码
						
						//下面代码用来隐藏控制台,要不编译链接的时候会一闪一闪,很难看
						TCHAR	szConsoleTitle[100];//控制台标题
						HWND	hCon;
						AllocConsole(); //分配产生一个控制台
						GetConsoleTitle(szConsoleTitle, sizeof(szConsoleTitle)); //获得控制台标题
						hCon=FindWindow(NULL, szConsoleTitle);
						ShowWindow(hCon, SW_HIDE);   //隐藏控制台
						
						//设置工作目录并编译连接,结果输出到_1.txt文件
						SetCurrentDirectory(szCurrentDirectory);
						system("ml /c /coff DiyPatch >> _1.txt");
						system("link /subsystem:windows DiyPatch.obj DiyPatch.res >> _1.txt");
						system("del DiyPatch.obj");
						MoveFile(L"DiyPatch.exe", g_szPatchName);
						memcpy(lpMemory, g_szBuffer, dwFileSize );//还原模版代码
						UnmapViewOfFile(lpMemory);
						CloseHandle(hAsmMap);
						CloseHandle(hAsmFile);

						FILE *pFile = fopen("_1.txt", "r+");   //提取_1.txt中存储的结果并显示对话框
						int	ch,i=0;
						if (pFile != NULL)
						{
							while((ch = fgetc(pFile) )!= EOF) 
							{
								if (ch == '\n')
									szBuffer[i++]='\r';
								szBuffer[i++]=ch;
							}
							MessageBoxA(g_hWnd, szBuffer, "生成结果", 0);
							fclose(pFile);
						}
						system("del _1.txt");
						SetCurrentDirectory(szOriDirectory); //还原工作目录
						return TRUE;
					}
					else	UnmapViewOfFile(lpMemory);
				}
				else	CloseHandle(hAsmMap);
			}
			else	CloseHandle(hAsmFile);
		}
	}
	return FALSE;
}

到这里整个补丁工具的三部分就讲完了,代码量大概有2000行,附件上工具的代码,整个所有工程的代码在第一课已经上传,可以在那找,另外顺便把一些用到的PE相关函数做了个DLL,附件有,比如要给PE加上你想要的代码,只要:
	InitPE32(L"Test.exe");
	AddCode(L"testnew2.exe", &APPEND_START, &APPEND_END-&APPEND_START, ADD_LAST_SECTION);//当然加上的补丁代码还是要自己用汇编写地~

三篇文章到此结束,可是完全没有人气啊啊啊伤心,没有人有兴趣么最后求次捧场吧(我想当会员T_T)

patchTool.rar

PE32_K.rar
上传的附件:
2012-5-28 17:47
0
雪    币: 23
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
5
好文章啊!!
2012-5-28 23:17
0
雪    币: 49
活跃值: (40)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
6
终于有个人了,感动ING
2012-5-29 00:10
0
雪    币: 1844
活跃值: (35)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
7
写了这么长,应该顶一下的
2012-5-29 01:23
0
雪    币: 24
活跃值: (27)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
help you help me  ,haha
2012-5-29 16:18
0
雪    币: 77
活跃值: (48)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
mark
2014-1-16 16:53
0
雪    币: 255
活跃值: (367)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
文章写得很好.特别是补丁代码附加到目标PE文件上进行patch,很强大.但最终的产品好像不行!!
我在XP SP3下试用不行:
目标程序加ASProtect V2.X Registered壳,无论哪种补丁方式均不成功.
不知是什么原因??难道是加了壳程序不行吗?无壳的程序没有试(如无壳还有必要用patch吗?)
一个地址一个字节进行patch,录入不方便,16个字节太少,实用性稍差.另外再增加一个编辑功能,对录入错误进行修改就好了.期待说明和进一步完善,真正变得很强大!
2014-1-17 16:46
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
看起来真的很不错,可惜我看不懂~~我还会回来的!!
2014-1-19 09:56
0
雪    币: 110
活跃值: (308)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
12
mark.
2014-1-20 00:50
0
雪    币: 49
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
13
mark ..
2014-1-20 08:38
0
雪    币: 47
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
14
mark
2014-1-20 08:40
0
雪    币: 2155
活跃值: (29)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
15
ASProtect算是一个比较强的可了。。。自带内存保护以及反内存补丁功能,当然无法patch了。。。
2014-1-20 10:50
0
雪    币: 3
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
学习~
2014-1-20 16:18
0
雪    币: 1
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
厉害。。。。。
2014-1-20 16:50
0
雪    币: 217
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
18
谢谢楼主分享。非常不错的文章
2014-1-20 20:48
0
雪    币: 36
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
确实不错呀,支持楼主
2014-1-20 23:15
0
雪    币: 110
活跃值: (308)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
20
刚看了你的代码,很不错。 不过直接用win32 api来写界面,代码好庞大
2014-1-22 23:49
0
雪    币: 37
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
21
必须回复,
2014-1-23 00:00
0
雪    币: 0
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
22
mark...
2014-1-23 10:15
0
雪    币: 37
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
23
非常感谢,这个找了好久了,终于找到个能用的,谢谢
2014-1-26 17:06
0
雪    币: 294
活跃值: (40)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
24
好棒~~~~支持楼主
2014-1-26 17:22
0
雪    币: 786
活跃值: (1621)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
25
汗颜啊  太长了  要是视频教程多好啊  看到文字头痛啊
2014-1-26 19:27
0
游客
登录 | 注册 方可回帖
返回
//