如果你不想让其他人使用自己的电脑,你可以选择以下三种方法:

❑ 通过BIOS加密电脑。

❑ 为操作系统的用户设置密码。

❑ 将本机重要的可运行程序都设置为密码运行。

虽然电脑中安装的某些软件,其本身已经具备密码登录功能,如QQ、宽带上网拨号软件等,但大部分软件并不具备密码登录的功能。

这种情况下,就可以选择使用EXE加锁器。EXE加锁器是为EXE程序的运行增加权限的一种技术。如果用户想运行加锁的程序,首先会弹出密码输入框,正确则可以继续使用,错误则退出程序。它本质上是一个补丁,可以改变程序的运行流程;通过在原始PE流程的最开始处加入代码,使得被加锁的程序运行前首先弹出用户登录对话框。如果用户输入信息认证发生错误,则会提前终止程序。

结合捆绑器技术,EXE加锁器还可以辅助完成对文件和文件夹的加密。假设使用第18章开发的EXE捆绑器,将某个待加密的目录完成捆绑操作,被捆绑以后的所有文件和文件夹被存储在一个PE文件中。但是,只要有人执行这个PE文件,所有的文件就会被释放出来。这样,就失去了加密的意义。

最好的解决办法就是,使用本章介绍的EXE加锁器,将捆绑后生成的PE文件再做一次加锁,这样就实现了对文件和文件夹加密的功能。

20.1 基本思路

因为EXE加锁器是一个补丁程序,所以本章将重点介绍补丁程序的代码。本节要开发的补丁程序具备以下三个特性:

❑ 窗口程序无需资源文件

❑ 无需导入表

❑ 无需重定位

弹出的窗口程序需要接收用户的输入,并根据输入信息执行相应的操作判断,是继续执行程序还是退出程序。

弹出的窗口中会提供几个控件,如文本编辑控件、按钮等,这些控件在前几章里都是定义在以.rc为扩展名的资源文件中。但是,本章将其作为补丁程序,需要将控件合并到目标PE中,因为,如果创造一个无需资源文件的窗口程序,就会省去打补丁时合并资源表的操作。关于免重定位和免导入表的技术请参照本书的第6章和第11章。

下面将分三步来开发这个加锁器代码,涉及的程序有:

❑ nores.asm (免资源文件的窗口程序)

❑ login.asm (为窗口程序添加免重定位特性)

❑ patch.asm (最终的补丁程序)

20.2 免资源文件的窗口程序nores.asm

在汇编语言中创建窗口程序,可以使用资源文件,按照资源定义的规则使用脚本语句逐句定义窗口中的控件;也可以不使用资源文件,通过调用函数CreateWindowEx创建每个控件。要实现窗口程序的免资源文件特性,必须选择使用后者。

20.2.1 窗口创建函数CreateWindowEx

该函数可以创建一个具有扩展风格的重叠式窗口、弹出式窗口或子窗口。所有的控件在系统中都被认为是一个子窗口。该函数原型定义如下:

HWND CreateWindowEx(
  DWORD dwExStyle,// 窗口的扩展风格
  LPCTSTR lpClassName,// 窗口所属的类
  LPCTSTR lpWindowName,// 标题栏文字
  DWORD dwStyle,//
  int x,          // 窗口坐标x的值
  int y,             // 窗口坐标y的值
  int nWidth,   // 窗口的宽度
  int nHeight, // 窗口的高度
  HWND hWndParent,// 窗口所属父窗口句柄
  HMENU hMenu,// 窗口菜单句柄
  HANDLE hInstance,// 窗口所处应用程序句柄
  LPVOID lpParam     //
);

函数参数解释如下:

(1)dwExStyle:指定窗口的扩展风格。该参数可以是下列值:

WS_EX_ACCEPTFILES:指定以该风格创建的窗口接受一个拖曳文件。

WS_EX_APPWINDOW:当窗口可见时,将一个顶层窗口放置到任务条上。

WS_EX_CLIENTEDGE:指定窗口有一个带阴影的边界。

WS_EX_CONTEXTHELP:在窗口的标题条包含一个问号标志。

WS_EX_CONTROLPARENT:允许用户使用Tab键在窗口的子窗口间跳转。

WS_EX_DLGMODALFRAME:创建一个带双边的窗口;该窗口可以在dwStyle中指定WS_CAPTION风格来创建一个标题栏。

WS_EX_LEFT:窗口具有左对齐属性,这是默认设置的。

WS_EX_LEFTSCROLLBAR:滚动条在客户区的左部分。该属性只针对部分语言有效,若是其他语言,该风格被忽略并且不作为错误处理。

WS_EX_LTRREADING:窗口文本以LEFT到RIGHT(自左向右)属性的顺序显示。这是默认设置的。

WS_EX_MDICHILD:创建一个MDI子窗口。

WS_EX_NOPATARENTNOTIFY:指明以这个风格创建的窗口在被创建和销毁时不向父窗口发送WM_PARENTNOTFY消息。

WS_EX_OVERLAPPED:WS_EX_CLIENTEDGE和WS_EX_WINDOWEDGE的组合。

WS_EX_PALETTEWINDOW:为WS_EX_WINDOWEDGE、WS_EX_TOOLWINDOW和WS_WX_TOPMOST风格的组合。

WS_EX_RIGHT:窗口具有普通的右对齐属性,该属性只针对特定的语言有效。

WS_EX_RIGHTSCROLLBAR:垂直滚动条在窗口的右边界。这是默认设置的。

WS_EX_RTLREADING:窗口文本自左向右的顺序读出。该属性只针对特定的语言有效。

WS_EX_STATICEDGE:为不接受用户输入的项创建一个三维边界风格。

WS_EX_TOOLWINDOW:创建工具窗口,即窗口是一个游动的工具条。

WS_EX_TOPMOST:指明以该风格创建的窗口应放置在所有非最高层窗口的上面。

WS_EX_TRANSPARENT:指定以这个风格创建的窗口在其下的同属窗口已重画时,该窗口才跟随着重画以便能显示其下的同属窗口,确保该窗口是透明的。

2)IpClassName:指向一个空结束的字符串或整型数。如果lpClassName是一个字符串,它指定了窗口的类名。这个类名可以是任何用函数RegisterClassEx注册的类名,或是任何预定义的控制类名。

3)lpWindowName:指向一个指定窗口名的空结束的字符串指针。如果窗口风格指定了标题条,由lpWindowName指向的窗口标题将显示在标题条上。当使用CreateWindow函数来创建控件(例如按钮、选择框和静态控件)时,可使用lpWindowName来指定控件上要显示的文本。

4)dwStyle:指定创建窗口的风格。

5)x:窗口x坐标的值。

6)y:窗口y坐标的值。

7)nWidth:窗口的宽度。

8)nHeigth:窗口的高度。

9)hWndParent:父窗口句柄。

10)hMenu:附加在窗口上的菜单句柄。

11)hlnstance:与窗口相关联的模块实例的句柄。

12)lpParam:指向一个值的指针,该值传递窗口的WM_CREATE消息。

13)返回值:如果函数执行成功,返回值为新窗口的句柄;如果函数失败,返回值为NULL。若想获得更多错误信息,可以调用GetLastError函数。

20.2.2 创建用户登录窗口的控件

下面分析要创建的EXE加锁器的窗口程序界面所包含的控件,EXE加锁器用户登录的窗口界面如图20-1所示。

图20-1 免资源文件的用户登录窗口界面

如图所示,弹出的用户登录窗口上共有6个控件,包括2个静态文本、2个文本框和2个按钮。这6个控件的相关信息见表20-1。

表20-1 加锁器界面控件表

表中每一个控件都需要使用函数CreateWindowEx创建。

20.2.3 窗口程序源代码

代码清单20-1演示了如何创建一个没有资源文件的窗口程序。窗口程序具备窗口消息处理子程序,可以接收用户输入,接收窗口上控件发出的消息。

代码清单20-1 不需要资源文件的登录窗口程序(chapter20\noRes.asm)

;noRes.asm  一个不需要资源文件的登录窗口示例
;使用 nmake 或下列命令进行编译和链接:
;ml -c -coff noRes.asm
;link -subsystem:windows noRes.obj  

.386
.model flat,stdcall
option casemap:none		;区分大小写

include    C:/masm32/include/windows.inc
include    C:/masm32/include/user32.inc
includelib C:/masm32/lib/user32.lib
include    C:/masm32/include/kernel32.inc 
includelib C:/masm32/lib/kernel32.lib
include    C:/masm32/include/gdi32.inc 
includelib C:/masm32/lib/gdi32.lib

ID_BUTTON1	equ 1
ID_BUTTON2 	equ 2
ID_LABEL1 	equ 3
ID_LABEL2 	equ 4 
ID_EDIT1 	equ 5
ID_EDIT2 	equ 6 

;数据段
.data 

szCaption 		db '欢迎您!',0
szText			db '您是合法用户,请使用该软件!',0
szCaptionMain 	db '系统登录',0
szClassName 	db 'Menu Example',0
szButtonClass 	db 'button', 0 
szEditClass 	db 'edit',0 
szLabelClass 	db 'static', 0 

szButtonText1      db  '登  录',0
szButtonText2      db  '取  消',0
szLabel1           db  '用户名:',0
szLabel2           db  '密   码:',0
lpszUser           db  'admin',0       ;模拟用户名和密码
lpszPass           db  '123456',0

szBuffer           db  256 dup(0)
szBuffer2          db  256 dup(0)

hInstance          dd  ?
hWinMain           dd  ?
hWinEdit           dd  ?
hButton1           dd  ?
hButton2           dd  ?
hLabel1            dd  ?
hLabel2            dd  ?
hEdit1             dd  ?
hEdit2             dd  ?

 ; 代码段
  .code
;----------------
; 退出程序
;----------------
_Quit  proc
       pushad
       invoke DestroyWindow,hWinMain
       invoke PostQuitMessage,NULL
       popad
       ret
_Quit  endp
_Exit proc
       invoke ExitProcess,NULL
_Exit endp
;------------------
; 窗口消息处理子程序
;------------------
_ProcWinMain proc uses ebx edi esi,hWnd,uMsg,wParam,lParam
      local @stPos:POINT

      mov eax,uMsg
      
      .if eax==WM_CREATE
          mov eax,hWnd
          mov hWinMain,eax

          ;标签
          invoke CreateWindowEx,NULL,\
                 addr szLabelClass,addr szLabel1,WS_CHILD or WS_VISIBLE, \
                 10,20,100,30,hWnd,ID_LABEL1,hInstance,NULL
          mov hLabel1,eax

          invoke CreateWindowEx,NULL,\
                 addr szLabelClass,addr szLabel2,WS_CHILD or WS_VISIBLE, \
                 10,50,100,30,hWnd,ID_LABEL2,hInstance,NULL
          mov hLabel2,eax

          ;文本框
          invoke CreateWindowEx,WS_EX_TOPMOST,\
                 addr szEditClass,NULL,WS_CHILD or WS_VISIBLE \
                 or WS_BORDER,\
                 105,19,175,22,hWnd,ID_EDIT1,hInstance,NULL
          mov hEdit1,eax
          invoke CreateWindowEx,WS_EX_TOPMOST,\
                 addr szEditClass,NULL,WS_CHILD or WS_VISIBLE \
                 or WS_BORDER or ES_PASSWORD,\
                 105,49,175,22,hWnd,ID_EDIT2,hInstance,NULL
          mov hEdit2,eax

          ;按钮
          invoke CreateWindowEx,NULL,\
                 addr szButtonClass,addr szButtonText1,WS_CHILD or WS_VISIBLE, \
                 120,100,60,30,hWnd,ID_BUTTON1,hInstance,NULL
          mov hButton1,eax

          invoke CreateWindowEx,NULL,\
                 addr szButtonClass,addr szButtonText2,WS_CHILD or WS_VISIBLE, \
                 200,100,60,30,hWnd,ID_BUTTON2,hInstance,NULL
          mov hButton2,eax
      .elseif eax==WM_COMMAND  ;处理菜单及加速键消息
          mov eax,wParam
          movzx eax,ax
          .if eax==ID_BUTTON1
             invoke GetDlgItemText,hWnd,ID_EDIT1,\
                    addr szBuffer,sizeof szBuffer
             invoke GetDlgItemText,hWnd,ID_EDIT2,\
                    addr szBuffer2,sizeof szBuffer2
             invoke lstrcmp,addr szBuffer,addr lpszUser
             .if eax
                jmp _ret
             .endif
             invoke lstrcmp,addr szBuffer2,addr lpszPass
             .if eax
                jmp _ret
             .endif
             ;invoke MessageBox,NULL,offset szText,offset szCaption,MB_OK
             jmp _ret1
          .elseif eax==ID_BUTTON2
_ret:        call _Exit
_ret1:       call _Quit
          .endif
      .elseif eax==WM_CLOSE
      .else
          invoke DefWindowProc,hWnd,uMsg,wParam,lParam
          ret
      .endif
      
      xor eax,eax
      ret
_ProcWinMain endp


;----------------------
; 主窗口程序
;----------------------
_WinMain  proc

	local @stWndClass:WNDCLASSEX
	local @stMsg:MSG
	local @hAccelerator

	invoke GetModuleHandle,NULL
	mov hInstance,eax

	;注册窗口类
	invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass
	mov @stWndClass.hIcon,NULL
	mov @stWndClass.hIconSm,NULL

	mov @stWndClass.hCursor,NULL
	push hInstance
	pop @stWndClass.hInstance
	mov @stWndClass.cbSize,sizeof WNDCLASSEX
	mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW
	mov @stWndClass.lpfnWndProc,offset _ProcWinMain
	mov @stWndClass.hbrBackground,COLOR_WINDOW
	mov @stWndClass.lpszClassName,offset szClassName
	invoke RegisterClassEx,addr @stWndClass

	;建立并显示窗口
	invoke CreateWindowEx,WS_EX_CLIENTEDGE,\
			offset szClassName,offset szCaptionMain,\
			WS_OVERLAPPED or WS_CAPTION or \
			WS_MINIMIZEBOX,\
			350,280,300,180,\
			NULL,NULL,hInstance,NULL
	mov  hWinMain,eax

	invoke ShowWindow,hWinMain,SW_SHOWNORMAL
	invoke UpdateWindow,hWinMain ;更新客户区,即发送WM_PAINT消息


	;消息循环
	.while TRUE
		invoke GetMessage,addr @stMsg,NULL,0,0
		.break .if eax==0
		invoke TranslateAccelerator,hWinMain,\
					@hAccelerator,addr @stMsg
		.if eax==0
			invoke TranslateMessage,addr @stMsg
			invoke DispatchMessage,addr @stMsg
		.endif
	.endw
	ret
_WinMain endp


start:
    call _WinMain
    invoke MessageBox,NULL,addr szCaptionMain,NULL,MB_OK
    ret
end start

行161~174注册一个窗口类。行171为该窗口类中指定了消息处理子程序为_ProcWinMain。

行176~186建立主窗口,并显示。此时窗口上的6个控件尚未被画出。

行189~199是消息循环。通过调用函数GetMessage从调用线程的消息队列里取一个消息,并将其放于指定的结构变量@stMsg;然后,使用TranslateMessage和DispatchMessage转发和派发消息到消息处理子程序。

消息处理子程序负责接收用户的输入和鼠标按键消息。用户点击“登录”按钮后,便从用户名文本框和密码文本框取回用户输入的信息,并与数据段中事先定义的字符串进行比对;如果一致,则允许用户继续运行程序,否则退出程序。因为现在尚处在测试阶段,所以根据不同的比对结果分别弹出了具有不同显示信息的对话框。

消息处理子程序还有一项很重要的任务,就是负责将窗体上的6个控件依次画出来。该代码在响应消息WM_CREATE的处理流程中,具体见代码行80~115。

测试该程序的重点是要确定程序的转向是否正确。通过调试发现,当提供的用户名和密码正确的时候,程序转向显示信息的代码;不正确的时候则退出程序,测试效果与预先设计的完全一样。接下来就要改造这个源程序,使其符合免重定位特性。

20.3 免重定位的窗口程序login.asm

这是编写补丁程序的第二步,基于上一节已经测试执行正确的nores.asm,修改其中的代码,使其符合免重定位、免导入表特性。修改包括:将数据段转移到代码段、所有涉及的直接寻址均采用免重定位技术。新的程序命名为login.asm,见代码清单20-2。

注意 此时,该程序不符合嵌入补丁框架的结构,且所有的函数调用没有使用动态加载技术,因此还不具备补丁程序特性 。

代码清单20-2 免重定位信息的用户登录窗口(chapter20\login.asm)

;login.asm  该程序演示了一个不需要资源文件的登录窗口示例
;免重定位信息,无数据段
;使用 nmake 或下列命令进行编译和链接:
;ml -c -coff login.asm
;link -subsystem:windows -SECTION:.text,EWR login.obj  

.386
.model flat,stdcall
option casemap:none		;区分大小写

include    C:/masm32/include/windows.inc
include    C:/masm32/include/user32.inc
includelib C:/masm32/lib/user32.lib
include    C:/masm32/include/kernel32.inc 
includelib C:/masm32/lib/kernel32.lib
include    C:/masm32/include/gdi32.inc 
includelib C:/masm32/lib/gdi32.lib

ID_BUTTON1	equ 1
ID_BUTTON2 	equ 2
ID_LABEL1 	equ 3
ID_LABEL2 	equ 4 
ID_EDIT1 	equ 5
ID_EDIT2 	equ 6 

 
 
;代码段
.code
jmp start

szCaption          db  '欢迎您!',0
szText             db  '您是合法用户,请使用该软件!',0
szCaptionMain      db  '系统登录',0
szClassName        db  'Menu Example',0
szButtonClass      db  'button',0
szEditClass        db  'edit',0
szLabelClass       db  'static',0

szButtonText1      db  '登  录',0
szButtonText2      db  '取  消',0
szLabel1           db  '用户名:',0
szLabel2           db  '密   码:',0
lpszUser           db  'admin',0       ;模拟用户名和密码
lpszPass           db  '123456',0

szBuffer           db  256 dup(0)
szBuffer2          db  256 dup(0)

hInstance          dd  ?
hWinMain           dd  ?
hWinEdit           dd  ?
dwRelocBase        dd  ?

;------------------------------------
; 根据kernel32.dll中的一个地址获取它的基地址
;------------------------------------
_getKernelBase  proc _dwKernelRetAddress
   local @dwRet

   pushad

   mov @dwRet,0
   
   mov edi,_dwKernelRetAddress
   and edi,0ffff0000h  ;查找指令所在页的边界,以1000h对齐

   .repeat
     .if word ptr [edi]==IMAGE_DOS_SIGNATURE  ;找到kernel32.dll的dos头
        mov esi,edi
        add esi,[esi+003ch]
        .if word ptr [esi]==IMAGE_NT_SIGNATURE ;找到kernel32.dll的PE头标识
          mov @dwRet,edi
          .break
        .endif
     .endif
     sub edi,010000h
     .break .if edi<070000000h
   .until FALSE
   popad
   mov eax,@dwRet
   ret
_getKernelBase  endp   

;-------------------------------
; 获取指定字符串的API函数的调用地址
; 入口参数:_hModule为动态链接库的基址,_lpApi为API函数名的首址
; 出口参数:eax为函数在虚拟地址空间中的真实地址
;-------------------------------
_getApi proc _hModule,_lpApi
   local @ret
   local @dwLen

   pushad
   mov @ret,0
   ;计算API字符串的长度,含最后的零
   mov edi,_lpApi
   mov ecx,-1
   xor al,al
   cld
   repnz scasb
   mov ecx,edi
   sub ecx,_lpApi
   mov @dwLen,ecx

   ;从pe文件头的数据目录获取导出表地址
   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
   .repeat
     push esi
     mov edi,[ebx]
     add edi,_hModule
     mov esi,_lpApi
     mov ecx,@dwLen
     repz cmpsb
     .if ZERO?
       pop esi
       jmp @F
     .endif
     pop esi
     add ebx,4
     inc edx
   .until edx>=[esi].NumberOfNames
   jmp _ret
@@:
   ;通过API名称索引获取序号索引再获取地址索引
   sub ebx,[esi].AddressOfNames
   sub ebx,_hModule
   shr ebx,1
   add ebx,[esi].AddressOfNameOrdinals
   add ebx,_hModule
   movzx eax,word ptr [ebx]
   shl eax,2
   add eax,[esi].AddressOfFunctions
   add eax,_hModule
   
   ;从地址表得到导出函数的地址
   mov eax,[eax]
   add eax,_hModule
   mov @ret,eax

_ret:
   assume esi:nothing
   popad
   mov eax,@ret
   ret
_getApi endp
;----------------
; 退出程序
;----------------
_Quit  proc
       pushad
       call @F   ; 免去重定位
@@:
       pop ebx
       sub ebx,offset @B   ;求定位基地址ebx       
       invoke DestroyWindow,[ebx+offset hWinMain]
       invoke PostQuitMessage,NULL
       popad
       ret
_Quit  endp

_Exit proc
       invoke ExitProcess,NULL
_Exit endp
;------------------
; 窗口消息处理子程序
;------------------
_ProcWinMain proc uses ebx edi esi,hWnd,uMsg,wParam,lParam
      local @stPos:POINT
      local hLabel1:dword
      local hLabel2:dword
      local hEdit1:dword
      local hEdit2:dword
      local hButton1:dword
      local hButton2:dword

      call @F   ; 免去重定位
   @@:
      pop ebx
      sub ebx,offset @B   ;求定位基地址ebx

      mov eax,uMsg
      
      .if eax==WM_CREATE
          mov eax,hWnd
          mov [ebx+offset hWinMain],eax

          ;标签
          mov eax,offset szLabelClass
          add eax,ebx
          mov ecx,offset szLabel1
          add ecx,ebx
      
          mov edx,[ebx+offset hInstance]

          push ebx
          invoke CreateWindowEx,NULL,\
                 eax,ecx,WS_CHILD or WS_VISIBLE, \
                 10,20,100,30,hWnd,ID_LABEL1,edx,NULL
          mov hLabel1,eax
          pop ebx

          mov eax,offset szLabelClass
          add eax,ebx
          mov ecx,offset szLabel2
          add ecx,ebx
      
          mov edx,[ebx+offset hInstance]

          push ebx
          invoke CreateWindowEx,NULL,\
                 eax,ecx,WS_CHILD or WS_VISIBLE, \
                 10,50,100,30,hWnd,ID_LABEL2,edx,NULL
          mov hLabel2,eax
          pop ebx

          ;文本框
          mov eax,offset szEditClass
          add eax,ebx
     
          mov edx,[ebx+offset hInstance]

          push ebx
          invoke CreateWindowEx,WS_EX_TOPMOST,\
                 eax,NULL,WS_CHILD or WS_VISIBLE \
                 or WS_BORDER,\
                 105,19,175,22,hWnd,ID_EDIT1,edx,NULL
          mov hEdit1,eax
          pop ebx

          mov eax,offset szEditClass
          add eax,ebx
     
          mov edx,[ebx+offset hInstance]
          push ebx
          invoke CreateWindowEx,WS_EX_TOPMOST,\
                 eax,NULL,WS_CHILD or WS_VISIBLE \
                 or WS_BORDER or ES_PASSWORD,\
                 105,49,175,22,hWnd,ID_EDIT2,edx,NULL
          mov hEdit2,eax
          pop ebx

          ;按钮
          mov eax,offset szButtonClass
          add eax,ebx
          mov ecx,offset szButtonText1
          add ecx,ebx
     
          mov edx,[ebx+offset hInstance]
          push ebx
          invoke CreateWindowEx,NULL,\
                 eax,ecx,WS_CHILD or WS_VISIBLE, \
                 120,100,60,30,hWnd,ID_BUTTON1,edx,NULL
          mov hButton1,eax
          pop ebx

          mov eax,offset szButtonClass
          add eax,ebx
          mov ecx,offset szButtonText2
          add ecx,ebx
     
          mov edx,[ebx+offset hInstance]
          push ebx 
          invoke CreateWindowEx,NULL,\
                 eax,ecx,WS_CHILD or WS_VISIBLE, \
                 200,100,60,30,hWnd,ID_BUTTON2,edx,NULL
          mov hButton2,eax
          pop ebx
      .elseif eax==WM_COMMAND  ;处理菜单及加速键消息
          mov eax,wParam
          movzx eax,ax
          .if eax==ID_BUTTON1
             mov eax,offset szBuffer
             add eax,ebx
             push ebx
             invoke GetDlgItemText,hWnd,ID_EDIT1,\
                    eax,sizeof szBuffer
             pop ebx

             mov eax,offset szBuffer2
             add eax,ebx
             push ebx
             invoke GetDlgItemText,hWnd,ID_EDIT2,\
                    eax,sizeof szBuffer2
             pop ebx
             mov eax,offset szBuffer
             add eax,ebx
             mov ecx,offset lpszUser
             add ecx,ebx
             push ebx
             invoke lstrcmp,eax,ecx
             pop ebx
             .if eax
                jmp _ret
             .endif
             mov eax,offset szBuffer2
             add eax,ebx
             mov ecx,offset lpszPass
             add ecx,ebx
             push ebx
             invoke lstrcmp,eax,ecx
             pop ebx
             .if eax
                jmp _ret
             .endif

             ;invoke MessageBox,NULL,offset szText,offset szCaption,MB_OK
             jmp _ret1
          .elseif eax==ID_BUTTON2
_ret:        call _Exit
_ret1:       call _Quit
          .endif
      .elseif eax==WM_CLOSE
      .else
          invoke DefWindowProc,hWnd,uMsg,wParam,lParam
          ret
      .endif
      
      xor eax,eax
      ret
_ProcWinMain endp


;----------------------
; 主窗口程序
;----------------------
_WinMain  proc _base

       local @stWndClass:WNDCLASSEX
       local @stMsg:MSG
       local @hAccelerator

       mov ebx,_base
       push ebx
       invoke GetModuleHandle,NULL
       pop ebx
       mov [ebx+offset hInstance],eax

       push ebx
       ;注册窗口类
       invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass
       mov @stWndClass.hIcon,NULL
       mov @stWndClass.hIconSm,NULL

       mov @stWndClass.hCursor,NULL

       pop ebx

       mov edx,offset _ProcWinMain
       add edx,ebx
       mov ecx,offset szClassName
       add ecx,ebx

       push [ebx+offset hInstance]
       pop @stWndClass.hInstance
       mov @stWndClass.cbSize,sizeof WNDCLASSEX
       mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW
       mov @stWndClass.lpfnWndProc,edx
       mov @stWndClass.hbrBackground,COLOR_WINDOW
       mov @stWndClass.lpszClassName,ecx
       push ebx
       invoke RegisterClassEx,addr @stWndClass
       pop ebx

       mov edx,offset szClassName
       add edx,ebx
       mov ecx,offset szCaptionMain
       add ecx,ebx

       mov eax,offset hInstance
       add eax,ebx
       push ebx
       ;建立并显示窗口
       invoke CreateWindowEx,WS_EX_CLIENTEDGE,\
              edx,ecx,\
              WS_OVERLAPPED or WS_CAPTION or \
              WS_MINIMIZEBOX,\
              350,280,300,180,\
              NULL,NULL,[eax],NULL
       pop ebx
       mov  [ebx+offset hWinMain],eax
       
       mov edx,offset hWinMain
       add edx,ebx

       push ebx
       push edx
       invoke ShowWindow,[edx],SW_SHOWNORMAL
       pop edx
       invoke UpdateWindow,[edx] ;更新客户区,即发送WM_PAINT消息
       pop ebx
   
       ;消息循环
       .while TRUE
          push ebx
          invoke GetMessage,addr @stMsg,NULL,0,0
          pop ebx
          .break .if eax==0
          mov edx,offset hWinMain
          add edx,ebx

          push ebx
          invoke TranslateAccelerator,[edx],\
                 @hAccelerator,addr @stMsg
          pop ebx
          .if eax==0
             invoke TranslateMessage,addr @stMsg
             invoke DispatchMessage,addr @stMsg
          .endif
       .endw
       ret
_WinMain endp


start:
    ;取当前函数的堆栈栈顶值
    mov eax,dword ptr [esp]
    push eax
    call @F   ; 免去重定位
@@:
    pop ebx
    sub ebx,offset @B
    pop eax
    push ebx
    invoke _WinMain,ebx
    pop ebx
    mov eax,offset szCaptionMain
    add eax,ebx
    invoke MessageBox,NULL,eax,NULL,MB_OK
    jmpToStart   db 0E9h,0F0h,0FFh,0FFh,0FFh
    ret
end start

如代码清单所示,在代码行162~165、行187~190、行429~432均使用了重定位技术,定义控件句柄的变量全部变成了函数的局部变量。

使用编译链接执行步骤来测试生成的login.exe程序,测试结果运行正常。

ml -c -coff login.asm

link -subsystem:windows -SECTION:.text,EWR login.obj

注意 因为将数据段中定义的变量全部移到代码段,所以运行前需要在PE文件头部修改.text节的Characteristic属性为0E0000060h,否则会出现错误。

20.4 补丁程序patch.asm

本节是编写补丁程序的最后一步。这一步将在第二步的基础上对login.asm进行修改,主要是完善login.asm的结构,使之符合本书13.3.1节定义的嵌入补丁框架的结构;最后,使用动态加载技术完成所有要调用的外部函数。

20.4.1 获取导入库及函数

为了完成一个免导入表的补丁程序,首先通过查找login.asm源代码,将代码中引入的所有动态链接库及调用的所有外部函数记录到表20-1中。

表20-1 补丁程序用到的导入库及函数

获取以上信息有三种办法:

1)自己从源代码中查找这些函数,通过网络获取函数与动态链接库的关系。

2)删除代码中的include和includelib后编译程序,会出现错误提示,每一行错误提示的最后就是程序中调用的函数名。

3)使用FlexHex找到login.exe的导入表,因为链接程序已经对函数进行了分类,所以从login.exe的字节码中就能获取这些函数,以及它们与动态链接库的关系。

第一种方法太慢;第二种方法会显示许多重名的函数,效率也不高;因此,建议大家采用第三种方法。以下是利用第三种方式获取的login.exe导入表的部分内容:

00000AD0   00 00 00 00 54 00 43 72 65 61 74 65 57 69 6E 64   ....T.CreateWind
00000AE0   6F 77 45 78 41 00 7E 00 44 65 66 57 69 6E 64 6F   owExA.~.DefWindo
00000AF0   77 50 72 6F 63 41 00 00 87 00 44 65 73 74 72 6F   wProcA...Destro
00000B00   79 57 69 6E 64 6F 77 00 8C 00 44 69 73 70 61 74   yWindow..Dispat
00000B10   63 68 4D 65 73 73 61 67 65 41 00 00 F4 00 47 65   chMessageA...Ge
00000B20   74 44 6C 67 49 74 65 6D 54 65 78 74 41 00 19 01   tDlgItemTextA...
00000B30   47 65 74 4D 65 73 73 61 67 65 41 00 9D 01 4D 65   GetMessageA..Me
00000B40   73 73 61 67 65 42 6F 78 41 00 BF 01 50 6F 73 74   ssageBoxA..Post
00000B50   51 75 69 74 4D 65 73 73 61 67 65 00 C8 01 52 65   QuitMessage..Re
00000B60   67 69 73 74 65 72 43 6C 61 73 73 45 78 41 00 00   gisterClassExA..
00000B70   2D 02 53 68 6F 77 57 69 6E 64 6F 77 00 00 3F 02   -.ShowWindow..?.
00000B80   54 72 61 6E 73 6C 61 74 65 41 63 63 65 6C 65 72   TranslateAcceler
00000B90   61 74 6F 72 41 00 42 02 54 72 61 6E 73 6C 61 74   atorA.B.Translat
00000BA0   65 4D 65 73 73 61 67 65 00 00 4E 02 55 70 64 61   eMessage..N.Upda
00000BB0   74 65 57 69 6E 64 6F 77 00 00 75 73 65 72 33 32   teWindow..user32
00000BC0  2E 64 6C 6C 00 00 80 00 45 78 69 74 50 72 6F 63  .dll..€.ExitProc
00000BD0   65 73 73 00 09 01 47 65 74 4D 6F 64 75 6C 65 48   ess...GetModuleH
00000BE0   61 6E 64 6C 65 41 00 00 0B 02 52 74 6C 5A 65 72   andleA....RtlZer
00000BF0   6F 4D 65 6D 6F 72 79 00 B7 02 6C 73 74 72 63 6D   oMemory..lstrcm
00000C00   70 41 00 00 6B 65 72 6E 65 6C 33 32 2E 64 6C 6C   pA..kernel32.dll

如上所示,导入表字符串首先列出被调用的各个函数名,然后才列出这些函数所处的动态链接库的名字。根据这些字节码信息,就很容易得到表20-1的内容。

20.4.2 按照补丁框架修改login.asm

依据嵌入补丁框架编写规则(详见13.3.2节)对原始的login.asm进行修改,这些修改包括:修改函数定义、分解函数调用语句、修改程序结构。首先来看如何修改函数,使其由静态调用变为动态调用。

1.修改函数

按照嵌入补丁框架中对调用外部函数的编写规则,对程序login.asm中调用的每个函数实施改造,步骤如下(以函数GetProcAddress为例):

(1)声明函数

_QLGetProcAddress typedef proto :dword,:dword

(2)引用函数声明

_ApiGetProcAddress   typedef ptr _QLGetProcAddress   ;声明函数引用

(3)定义函数变量(全局变量)

_GetProcAddress   _ApiGetProcAddress   ?     ;定义函数

对函数的声明也不需要去网络或者书籍上查找。找到D:\masm32\include\user32.inc,使用记事本程序打开,选择菜单“编辑”|“查找”;在弹出的对话框中,输入要查找的函数名后,单击“查找下一个”按钮,即可查找到user32.dll中的指定函数的声明。

2.分解函数调用语句

由于函数地址为全局变量,所以在调用时不能采用以下代码:

invoke _GetProcAddrss

这样在生成指令字节码时还是会用到直接寻址,因此,invoke指令的操作数需要重新定位。为了解决这个问题,通常用以下代码来代替:

mov edx,[ebx+offset _GetProcAddress]
call edx

看一个实际的例子:

mov eax,offset szLoadLibraryA
add eax,ebx

push eax
push [ebx+offset hKernel32Base]
mov edx,[ebx+offset _GetProcAddress]
call edx
mov [ebx+offset _LoadLibraryA],eax

上述的代码等价于:

invoke GetProcAddress,hKernel32Base,addr szLoadLibraryA
mov _LoadLibraryA,eax

虽然比原来的代码复杂了些,但由于使用了重定位技术和动态加载技术,所以能获得更好的可移植性。

3.修改程序结构按

照嵌入补丁框架的要求对login.asm的程序结构进行调整,主要涉及以下两个位置:

1)在代码段开始位置加入跳转指令代码,如下所示:

;代码段
    .code
jmp start

szCaption            db  '欢迎您!',0
szText                db  '您是合法用户,请使用该软件!',0
szCaptionMain        db   '系统登录',0
…

2)在返回指令前加入跳转指令代码,如下所示:

jmpToStart    db 0E9h,0F0h,0FFh,0FFh,0FFh
ret
end start

20.4.3 补丁程序主要代码

通过以上几步的修改,补丁程序基本成型,代码清单20-3为补丁程序的主要代码,完整代码请参见随书文件chapter20\patch.asm。

代码清单20-3 EXE加锁器补丁程序主要代码(chapter20\patch.asm)

1    start:
2         ;取当前函数的栈顶值
3         mov eax,dword ptr [esp]
4         push eax
5         call @F    ; 免去重定位
6    @@:
7         pop ebx
8         sub ebx,offset @B
9         pop eax
10        ;获取kernel32.dll的基地址
11        invoke _getKernelBase,eax
12        mov [ebx+offset hKernel32Base],eax
13
14        ;从基地址出发搜索GetProcAddress函数的首址
15        mov eax,offset szGetProcAddress
16        add eax,ebx
17        mov ecx,[ebx+offset hKernel32Base]
18       invoke _getApi,ecx,eax
19   ;为函数引用赋值 GetProcAddress
20        mov [ebx+offset _GetProcAddress],eax
21
22  ;使用GetProcAddress函数的首址,
23  ;传入两个参数调用GetProcAddress函数,获得LoadLibraryA的首址
24        mov eax,offset szLoadLibraryA
25        add eax,ebx
26
27        push eax
28        push [ebx+offset hKernel32Base]
29        mov edx,[ebx+offset _GetProcAddress]
30        call edx
31        mov [ebx+offset _LoadLibraryA],eax
32
33       invoke _getDllBase       ;获取所有用到的DLL的基地址,kernel32除外
34       invoke _getFuns  ;获取所有用到的函数的入口地址,
35                                 ;GetProcAddress和LoadLibraryA除外
36        invoke _WinMain,ebx
37
38        jmpToStart    db 0E9h,0F0h,0FFh,0FFh,0FFh
39        ret
40        end start

以上代码首先调用函数_getKernelBase得到kernel32.dll的基地址;然后,调用函数_getApi获得GetProcAddress的地址;进一步调用刚获取的函数GetProcAddress得到LoadLibraryA的地址。有了这两个地址以后,通过调用函数_getDllBase得到除kernel32.dll外的其他所有链接库的基地址,通过调用函数_getFuns获得本程序中用到的其他所有外部函数的地址。最后,执行主窗口创建函数_WinMain。

20.5 附加补丁运行

因为本实例的补丁代码比较长,共0B2Ch个字节,所以,这里使用第16章介绍的方法将代码添加到目标PE文件新的节中。以下是将patch.exe附加到WinWord的运行结果:

补丁程序:D:\masm32\source\chapter20\patch.exe
目标PE程序:C:\WINWORD.EXE

补丁代码段大小:00000b2c
新增节按照文件对齐粒度对齐以后的大小为:00000c00
目标PE文件头的有效数据长度为:00000312
目标PE文件头有效数据长度对齐后的值为:00000318
目标程序所有节表占用的字节数:000000c8
新文件的PE头实际大小为:00000528
节表后的数据位于文件的偏移:00000600
原文件大小为:00bbd950
加补丁后的新文件的大小为:00bbe800
目标PE的入口地址为:000019f0
新PE文件的入口地址为:00bee000
补丁代码中的E9指令后的操作数修正为:ff412ec5

节中需要修正的文件偏移地址如下:
    节名:.text      原始偏移:00000400      修正后的偏移:00000600
    节名:.data      原始偏移:00aa7000      修正后的偏移:00aa7200
    节名:.tls       原始偏移:00b51600      修正后的偏移:00b51800
    节名:.cdata     原始偏移:00b51800      修正后的偏移:00b51a00
    节名:.rsrc      原始偏移:00b51a00      修正后的偏移:00b51c00

运行界面如图20-2所示。

图20-2 EXE加锁器打补丁

运行界面截图运行以后,会在C盘根目录生成bindB.exe。打开bindB.exe,首先出现的就是权限认证窗口。输入正确的值以后(用户名:admin,密码:123456),才可以运行Word程序。

至此,EXE加锁器设计成功!

---------------------------------------------------

完整的bind.asm源码如下:

;bind.asm  将patch.exe补丁程序插入到指定exe文件新增节中
;使用 nmake 或下列命令进行编译和链接:
;ml -c -coff bind.asm
;rc -r bind.rc
;link -subsystem:windows bind.obj bind.res

.386
.model flat,stdcall
option casemap:none

include    C:/masm32/include/windows.inc
include    C:/masm32/include/user32.inc
include    C:/masm32/include/kernel32.inc
include    C:/masm32/include/gdi32.inc
include    C:/masm32/include/comctl32.inc
include    C:/masm32/include/comdlg32.inc
include    C:/masm32/include/advapi32.inc
include    C:/masm32/include/shell32.inc
include    C:/masm32/include/masm32.inc
include    C:/masm32/include/netapi32.inc
include    C:/masm32/include/winmm.inc
include    C:/masm32/include/ws2_32.inc
include    C:/masm32/include/psapi.inc
include    C:/masm32/include/mpr.inc        ;WNetCancelConnection2
include    C:/masm32/include/iphlpapi.inc   ;SendARP
include    winResult.inc  
includelib C:/masm32/lib/comctl32.lib
includelib C:/masm32/lib/comdlg32.lib
includelib C:/masm32/lib/gdi32.lib
includelib C:/masm32/lib/user32.lib
includelib C:/masm32/lib/kernel32.lib
includelib C:/masm32/lib/advapi32.lib
includelib C:/masm32/lib/shell32.lib
includelib C:/masm32/lib/masm32.lib
includelib C:/masm32/lib/netapi32.lib
includelib C:/masm32/lib/winmm.lib
includelib C:/masm32/lib/ws2_32.lib
includelib C:/masm32/lib/psapi.lib
includelib C:/masm32/lib/mpr.lib
includelib C:/masm32/lib/iphlpapi.lib
includelib winResult.lib 

ICO_MAIN equ 1000
DLG_MAIN equ 1000
IDC_INFO equ 1001
IDM_MAIN equ 2000
IDM_OPEN equ 2001
IDM_EXIT equ 2002
IDM_1    equ 4000
IDM_2    equ 4001
IDM_3    equ 4002
RESULT_MODULE   equ 5000
ID_TEXT1        equ 5001
ID_TEXT2        equ 5002
IDC_MODULETABLE equ 5003
IDC_OK          equ 5004
ID_STATIC       equ 5005
ID_STATIC1      equ 5006
IDC_BROWSE1     equ 5007
IDC_BROWSE2     equ 5008
IDC_THESAME     equ 5009


.data
hInstance   dd ?
hRichEdit   dd ?
hWinMain    dd ?
hWinEdit    dd ?
dwCount     dd ?
dwColorRed  dd ?
hText1      dd ?
hText2      dd ?
hFile       dd ?

dwPatchCodeSize   dd  ?     ;补丁代码大小
dwNewFileSize     dd  ?     ;新文件大小=目标文件大小+补丁代码大小
dwNewPatchCodeSize  dd ?    ;补丁代码按8位对齐后的大小
dwPatchCodeSegStart  dd ?   ;补丁代码所在节在文件中的起始地址
dwSectionCount       dd ?   ;目标文件节的个数
dwSections           dd ?   ;所有节表大小
dwNewHeaders         dd ?   ;新文件头的大小
dwFileAlign          dd ?   ;文件对齐粒度
dwFirstSectionStart  dd ?   ;目标文件第一节距离文件起始的偏移量
dwOff                dd ?   ;新文件比原来多出来的部分
dwValidHeadSize      dd ?   ;目标文件PE头的有效数据长度
dwHeaderSize         dd ?   ;文件头长度
dwBlock1             dd ?   ;原PE头的有效数据长度+补丁代码的有效数据长度
dwPE_SECTIONSize     dd ?   ;PE头+节表大小
dwSectionsLeft       dd ?   ;目标文件所有节数据的大小
dwNewSectionSize     dd ?   ;新增加节对齐后的尺寸
dwNewSectionOff      dd ?   ;新增加节项描述在文件中的偏移
dwDstSizeOfImage     dd ?   ;目标文件内存映像的大小
dwNewSizeOfImage     dd ?   ;新增加的节在内存映像中的大小
dwNewFileAlignSize   dd ?   ;文件对齐后的大小
dwSectionsAlignLeft  dd ?   ;目标文件节在文件中对齐后的大小
dwLastSectionAlignSize  dd ?   ;目标文件最后一节对齐后的最终大小,包含代码
dwLastSectionStart      dd ?   ;目标文件最后一节在文件中的偏移
dwSectionAlign          dd ?   ;节对齐粒度
dwVirtualAddress        dd ?   ;最后一节的起始RVA
dwEIPOff                dd ?   ;新EIP指针和旧EIP指针的距离



dwDstEntryPoint      dd ?   ;旧的入口地址
dwNewEntryPoint      dd ?   ;新的入口地址

lpPatchPE         dd  ?   ;补丁程序的PE标志在文件中的位置,因为从0开始,所以这个位置也是DOS头的大小
lpDstMemory       dd  ?   ;内存中存放新文件数据的起始地址
lpOthers          dd  ?   ;其他数据在文件中的起始位置


hProcessModuleTable dd ?

szFileName           db MAX_PATH dup(?)
szDstFile            db 'c:\bind20.exe',0
;Z:\learnPE\chapter20\list3\patch.exe
szFileNameOpen1      db 'c:\masm32\source\chapter20\patch.exe',MAX_PATH dup(0)
szFileNameOpen2      db 'c:\notepad.exe',MAX_PATH dup(0)

                     ;d:\masm32\source\chapter12\HelloWorld.exe

szResultColName1 db  'PE数据结构相关字段',0
szResultColName2 db  '文件1的值(H)',0
szResultColName3 db  '文件2的值(H)',0
szBuffer         db  256 dup(0),0
bufTemp1         db  200 dup(0),0
bufTemp2         db  200 dup(0),0
szFilter1        db  'Excutable Files',0,'*.exe;*.com',0
                 db  0

.const
szDllEdit   db 'RichEd20.dll',0
szClassEdit db 'RichEdit20A',0
szFont      db '宋体',0
szExtPe     db 'PE File',0,'*.exe;*.dll;*.scr;*.fon;*.drv',0
            db 'All Files(*.*)',0,'*.*',0,0
szErr       db '文件格式错误!',0
szErrFormat db '这个文件不是PE格式的文件!',0
szSuccess   db '恭喜你,程序执行到这里是成功的。',0
szNotFound  db '无法查找',0
szNewSection db 'PEBindQL',0

szCrLf      db 0dh,0ah,0

szOut100       db '补丁代码段大小:%08x',0dh,0ah,0
szOut104       db '空隙一的大小为:%08x',0dh,0ah,0
szOut101       db '目标PE文件头的有效数据长度为:%08x ',0dh,0ah,0
szOut102       db '目标PE文件头有效数据长度对齐后的值为:%08x',0dh,0ah,0
szOut103       db '新文件的PE头所处的位置在新文件偏移:%08x处',0dh,0ah,0
szOut105       db '原文件大小为:%08x   加补丁后的新文件的大小为:%08x',0dh,0ah,0
szOut106       db '目标PE的入口地址为:%08x',0dh,0ah,0
szOut107       db '节中需要修正的文件偏移地址如下:',0dh,0ah,0
szOut108       db '   节名:%s     原始偏移:%08x     修正后的偏移:%08x',0dh,0ah,0
szOut109       db '新文件的PE头实际大小为:%08x',0dh,0ah,0
szOut110       db '节表后的数据位于文件的偏移:%08x',0dh,0ah,0
szOut111       db '目标程序所有节表占用的字节数:%08x',0dh,0ah,0
szOut112       db '补丁代码中的E9指令后的操作数修正为:%08x',0dh,0ah,0
szOut113       db '目标PE头的数据的有效长度为:%08x',0dh,0ah,0
szOut114       db '新增节按照文件对齐粒度对齐以后的大小为:%08x',0dh,0ah,0
szOut115       db '新PE文件的入口地址为:%08x',0dh,0ah,0

szOut121       db 'PE文件大小:%08x   对齐以后的大小:%08x',0dh,0ah,0
szOut122       db '目标文件最后一节在文件中的起始偏移:%08x',0dh,0ah,0
szOut123       db '目标文件最后一节对齐后的大小:%08x',0dh,0ah,0
szOut124       db '新文件大小:%08x',0dh,0ah,0

szOut1      db '补丁程序:%s',0dh,0ah,0
szOut2      db '目标PE程序:%s',0dh,0ah,0
szOutErr    db '代码段长度大于0DA8h,空隙一的空间不足!',0dh,0ah,0
lpszHexArr  db  '0123456789ABCDEF',0

.data?
stLVC         LV_COLUMN <?>
stLVI         LV_ITEM   <?>

.code

;----------------
;初始化窗口程序
;----------------
_init proc
  local @stCf:CHARFORMAT
  
  invoke GetDlgItem,hWinMain,IDC_INFO
  mov hWinEdit,eax
  invoke LoadIcon,hInstance,ICO_MAIN
  invoke SendMessage,hWinMain,WM_SETICON,ICON_BIG,eax       ;为窗口设置图标
  invoke SendMessage,hWinEdit,EM_SETTEXTMODE,TM_PLAINTEXT,0 ;设置编辑控件
  invoke RtlZeroMemory,addr @stCf,sizeof @stCf
  mov @stCf.cbSize,sizeof @stCf
  mov @stCf.yHeight,9*20
  mov @stCf.dwMask,CFM_FACE or CFM_SIZE or CFM_BOLD
  invoke lstrcpy,addr @stCf.szFaceName,addr szFont
  invoke SendMessage,hWinEdit,EM_SETCHARFORMAT,0,addr @stCf
  invoke SendMessage,hWinEdit,EM_EXLIMITTEXT,0,-1
  ret
_init endp

;------------------
; 错误Handler
;------------------
_Handler proc _lpExceptionRecord,_lpSEH,\
              _lpContext,_lpDispathcerContext

  pushad
  mov esi,_lpExceptionRecord
  mov edi,_lpContext
  assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
  mov eax,_lpSEH
  push [eax+0ch]
  pop [edi].regEbp
  push [eax+8]
  pop [edi].regEip
  push eax
  pop [edi].regEsp
  assume esi:nothing,edi:nothing
  popad
  mov eax,ExceptionContinueExecution
  ret
_Handler endp
;---------------------
; 将文件偏移转换为内存偏移量RVA
; lp_FileHead为文件头的起始地址
; _dwOff为给定的文件偏移地址
;---------------------
_OffsetToRVA proc _lpFileHead,_dwOffset
  local @dwReturn
  
  pushad

  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  mov edi,_dwOffset
  mov edx,esi
  add edx,sizeof IMAGE_NT_HEADERS
  assume edx:ptr IMAGE_SECTION_HEADER
  movzx ecx,[esi].FileHeader.NumberOfSections
  ;遍历节表
  .repeat
    mov eax,[edx].PointerToRawData
    add eax,[edx].SizeOfRawData    ;计算该节结束RVA
    .if (edi>=[edx].PointerToRawData)&&(edi<eax)
      mov eax,[edx].PointerToRawData
      sub edi,eax                ;计算RVA在节中的偏移
      mov eax,[edx].VirtualAddress
      add eax,edi                ;加上节在内存中的起始位置
      jmp @F
    .endif
    add edx,sizeof IMAGE_SECTION_HEADER
  .untilcxz
  assume edx:nothing
  assume esi:nothing
  mov eax,-1
@@:
  mov @dwReturn,eax
  popad
  mov eax,@dwReturn
  ret
_OffsetToRVA endp
;---------------------
; 将内存偏移量RVA转换为文件偏移
;---------------------
_RVAToOffset proc _lpFileHead,_dwRVA
  local @dwReturn
  
  pushad
  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  mov edi,_dwRVA
  mov edx,esi
  add edx,sizeof IMAGE_NT_HEADERS
  assume edx:ptr IMAGE_SECTION_HEADER
  movzx ecx,[esi].FileHeader.NumberOfSections
  ;遍历节表
  .repeat
    mov eax,[edx].VirtualAddress
    add eax,[edx].SizeOfRawData  ;计算该节结束RVA
    .if (edi>=[edx].VirtualAddress)&&(edi<eax)
      mov eax,[edx].VirtualAddress
      sub edi,eax                ;计算RVA在节中的偏移
      mov eax,[edx].PointerToRawData
      add eax,edi                ;加上节在文件中的的起始位置
      jmp @F
    .endif
    add edx,sizeof IMAGE_SECTION_HEADER
  .untilcxz
  assume edx:nothing
  assume esi:nothing
  mov eax,-1
@@:
  mov @dwReturn,eax
  popad
  mov eax,@dwReturn
  ret
_RVAToOffset endp

;----------------------------------------
; 获取新节的RVA地址
;----------------------------------------
_getNewSectionRVA  proc _lpFileHead
  local @dwReturn
  
  pushad
  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  mov edi,esi
  add edi,sizeof IMAGE_NT_HEADERS
  assume edi:ptr IMAGE_SECTION_HEADER
  movzx ecx,[esi].FileHeader.NumberOfSections

  
  xor edx,edx
  mov eax,ecx
  dec eax
  mov bx,sizeof IMAGE_SECTION_HEADER
  mul bx
  add edi,eax       ;定位到最后一个节定义处
  assume edi:ptr IMAGE_SECTION_HEADER
  mov eax,[edi].SizeOfRawData
  xor edx,edx
  mov bx,1000h
  div bx
  .if edx!=0
    inc eax
  .endif
  xor edx,edx
  mul bx
  mov ebx,eax

  mov eax,[edi].VirtualAddress
  add eax,ebx

  mov @dwReturn,eax
  popad
  mov eax,@dwReturn
  ret
_getNewSectionRVA endp

;-----------------------------
; 对齐
; 入口:eax----对齐的值
;       ecx----对齐因子
; 出口:eax----对齐以后的值
;-----------------------------
_align       proc
    push edx

    xor edx,edx
    div ecx
    .if edx>0
      inc eax
    .endif
    xor edx,edx
    mul ecx
    pop edx
    ret
_align       endp

;----------------------------------------
; 获取节的个数
;----------------------------------------
_getSectionCount  proc _lpFileHead
  local @dwReturn
  
  pushad
  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  movzx ecx,[esi].FileHeader.NumberOfSections
  mov @dwReturn,ecx
  popad
  mov eax,@dwReturn
  ret
_getSectionCount endp

;----------------------------------------
; 获取文件的对齐粒度
;----------------------------------------
getSectionAlign  proc _lpFileHead
  local @ret
  pushad
  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  mov edx,esi
  add edx,sizeof IMAGE_NT_HEADERS
  assume edx:ptr IMAGE_SECTION_HEADER
  mov ecx,[esi].OptionalHeader.SectionAlignment  
  mov @ret,ecx
  popad
  mov eax,@ret
  ret
getSectionAlign  endp


;--------------------------------------
; 获取目标PE头的数据的有效长度
;--------------------------------------
getValidHeadSize proc _lpFileHead
  local @dwReturn
  local @dwTemp
  
  pushad
  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  mov edx,esi
  add edx,sizeof IMAGE_NT_HEADERS
  assume edx:ptr IMAGE_SECTION_HEADER
  mov eax,[edx].PointerToRawData     ;指向第一个节的起始
  mov @dwTemp,eax

  dec eax
  mov esi,eax
  add esi,_lpFileHead
  mov @dwReturn,0
  .repeat
    mov bl,byte ptr [esi]
    .if bl!=0
      .break
    .endif
    dec esi
    inc @dwReturn
  .until FALSE
  mov eax,@dwTemp
  sub eax,@dwReturn
  add eax,2          ;为有效数据留出两个0字符,假如最后的有效数据为字符串,必须以0结束
  mov @dwReturn,eax

  popad
  mov eax,@dwReturn

  ret
getValidHeadSize endp

;------------------------
; 获取RVA所在节的名称
;------------------------
_getRVASectionName  proc _lpFileHead,_dwRVA
  local @dwReturn
  
  pushad
  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  mov edi,_dwRVA
  mov edx,esi
  add edx,sizeof IMAGE_NT_HEADERS
  assume edx:ptr IMAGE_SECTION_HEADER
  movzx ecx,[esi].FileHeader.NumberOfSections
  ;遍历节表
  .repeat
    mov eax,[edx].VirtualAddress
    add eax,[edx].SizeOfRawData  ;计算该节结束RVA
    .if (edi>=[edx].VirtualAddress)&&(edi<eax)
      mov eax,edx
      jmp @F
    .endif
    add edx,sizeof IMAGE_SECTION_HEADER
  .untilcxz
  assume edx:nothing
  assume esi:nothing
  mov eax,offset szNotFound
@@:
  mov @dwReturn,eax
  popad
  mov eax,@dwReturn
  ret
_getRVASectionName  endp

;------------------------
; 获取RVA所在节的文件起始地址
;------------------------
_getRVASectionStart  proc _lpFileHead,_dwRVA
  local @dwReturn
  
  pushad
  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  mov edi,_dwRVA
  mov edx,esi
  add edx,sizeof IMAGE_NT_HEADERS
  assume edx:ptr IMAGE_SECTION_HEADER
  movzx ecx,[esi].FileHeader.NumberOfSections
  ;遍历节表
  .repeat
    mov eax,[edx].VirtualAddress
    add eax,[edx].SizeOfRawData  ;计算该节结束RVA
    .if (edi>=[edx].VirtualAddress)&&(edi<eax)
      mov eax,[edx].PointerToRawData
      jmp @F
    .endif
    add edx,sizeof IMAGE_SECTION_HEADER
  .untilcxz
  assume edx:nothing
  assume esi:nothing
  mov eax,offset szNotFound
@@:
  mov @dwReturn,eax
  popad
  mov eax,@dwReturn
  ret
_getRVASectionStart  endp


;------------------------
; 获取RVA所在节的原始大小
;------------------------
_getRVASectionSize  proc _lpFileHead,_dwRVA
  local @dwReturn
  
  pushad
  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  mov edi,_dwRVA
  mov edx,esi
  add edx,sizeof IMAGE_NT_HEADERS
  assume edx:ptr IMAGE_SECTION_HEADER
  movzx ecx,[esi].FileHeader.NumberOfSections
  ;遍历节表
  .repeat
    mov eax,[edx].VirtualAddress
    add eax,[edx].SizeOfRawData  ;计算该节结束RVA
    .if (edi>=[edx].VirtualAddress)&&(edi<eax)
      ;invoke _appendInfo,edx
      mov eax,[edx].Misc
      jmp @F
    .endif
    add edx,sizeof IMAGE_SECTION_HEADER
  .untilcxz
  assume edx:nothing
  assume esi:nothing
  mov eax,offset szNotFound
@@:
  mov @dwReturn,eax
  popad
  mov eax,@dwReturn
  ret
_getRVASectionSize  endp

;------------------------
; 获取RVA所在节在文件中对齐以后的大小
;------------------------
_getRVASectionRawSize  proc _lpFileHead,_dwRVA
  local @dwReturn
  
  pushad
  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  mov edi,_dwRVA
  mov edx,esi
  add edx,sizeof IMAGE_NT_HEADERS
  assume edx:ptr IMAGE_SECTION_HEADER
  movzx ecx,[esi].FileHeader.NumberOfSections
  ;遍历节表
  .repeat
    mov eax,[edx].VirtualAddress
    add eax,[edx].SizeOfRawData  ;计算该节结束RVA
    .if (edi>=[edx].VirtualAddress)&&(edi<eax)
      mov eax,[edx].SizeOfRawData
      jmp @F
    .endif
    add edx,sizeof IMAGE_SECTION_HEADER
  .untilcxz
  assume edx:nothing
  assume esi:nothing
  mov eax,offset szNotFound
@@:
  mov @dwReturn,eax
  popad
  mov eax,@dwReturn
  ret
_getRVASectionRawSize  endp

_getRVACount  proc _lpFileHead
  local @ret
  pushad
  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  movzx ecx,[esi].FileHeader.NumberOfSections  
  mov @ret,ecx
  popad
  mov eax,@ret
  ret
_getRVACount endp

;------------------------------------
; 获取最后一节的在文件的偏移
;-------------------------------------
getLastSectionStart proc _lpFileHead
  local @ret
  pushad
  invoke _getRVACount,_lpFileHead
  xor edx,edx
  dec eax
  mov ecx,28h
  mul ecx

  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  add esi,sizeof IMAGE_NT_HEADERS  
  add esi,eax
  assume esi:ptr IMAGE_SECTION_HEADER
  mov eax,[esi].PointerToRawData
  mov @ret,eax
  popad
  mov eax,@ret
  ret
getLastSectionStart endp

getFileAlign  proc _lpFileHead
  local @ret
  pushad
  mov esi,_lpFileHead
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew
  assume esi:ptr IMAGE_NT_HEADERS
  mov edx,esi
  add edx,sizeof IMAGE_NT_HEADERS
  assume edx:ptr IMAGE_SECTION_HEADER
  mov ecx,[esi].OptionalHeader.FileAlignment  
  mov @ret,ecx
  popad
  mov eax,@ret
  ret
getFileAlign  endp

;---------------------
; 往文本框中追加文本
;---------------------
_appendInfo proc _lpsz
  local @stCR:CHARRANGE

  pushad
  invoke GetWindowTextLength,hWinEdit
  mov @stCR.cpMin,eax  ;将插入点移动到最后
  mov @stCR.cpMax,eax
  invoke SendMessage,hWinEdit,EM_EXSETSEL,0,addr @stCR
  invoke SendMessage,hWinEdit,EM_REPLACESEL,FALSE,_lpsz
  popad
  ret
_appendInfo endp

;------------------------------------------
; 打开输入文件
;------------------------------------------
_OpenFile1	proc
		local	@stOF:OPENFILENAME
		local	@stES:EDITSTREAM

                ;如果打开之前还有文件句柄存在,则先关闭再赋值                
                .if hFile
                   invoke CloseHandle,hFile
                   mov hFile,0
                .endif
                ; 显示“打开文件”对话框
		invoke	RtlZeroMemory,addr @stOF,sizeof @stOF
		mov	@stOF.lStructSize,sizeof @stOF
		push	hWinMain
		pop	@stOF.hwndOwner
                push    hInstance
                pop     @stOF.hInstance
		mov	@stOF.lpstrFilter,offset szFilter1
		mov	@stOF.lpstrFile,offset szFileNameOpen1
		mov	@stOF.nMaxFile,MAX_PATH
		mov	@stOF.Flags,OFN_FILEMUSTEXIST or\
                                    OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
		invoke	GetOpenFileName,addr @stOF
		.if	eax
                        invoke SetWindowText,hText1,addr szFileNameOpen1
		.endif
                invoke wsprintf,addr szBuffer,addr szOut1,addr szFileNameOpen1
                invoke _appendInfo,addr szBuffer
		ret

_OpenFile1	endp
;------------------------------------------
; 打开输入文件
;------------------------------------------
_OpenFile2	proc
		local	@stOF:OPENFILENAME
		local	@stES:EDITSTREAM

                ;如果打开之前还有文件句柄存在,则先关闭再赋值                
                .if hFile
                   invoke CloseHandle,hFile
                   mov hFile,0
                .endif
                ; 显示“打开文件”对话框
		invoke	RtlZeroMemory,addr @stOF,sizeof @stOF
		mov	@stOF.lStructSize,sizeof @stOF
		push	hWinMain
		pop	@stOF.hwndOwner
                push    hInstance
                pop     @stOF.hInstance
		mov	@stOF.lpstrFilter,offset szFilter1
		mov	@stOF.lpstrFile,offset szFileNameOpen2
		mov	@stOF.nMaxFile,MAX_PATH
		mov	@stOF.Flags,OFN_FILEMUSTEXIST or\
                                    OFN_HIDEREADONLY or OFN_PATHMUSTEXIST
		invoke	GetOpenFileName,addr @stOF
		.if	eax
                        invoke SetWindowText,hText2,addr szFileNameOpen2
		.endif
                invoke wsprintf,addr szBuffer,addr szOut2,addr szFileNameOpen2
                invoke _appendInfo,addr szBuffer
                invoke _appendInfo,addr szCrLf
		ret

_OpenFile2	endp

;--------------------------
; 将_lpPoint位置处_dwSize个字节转换为16进制的字符串
; bufTemp1处为转换后的字符串
;--------------------------
_Byte2Hex     proc _dwSize
  local @dwSize:dword

  pushad
  mov esi,offset bufTemp2
  mov edi,offset bufTemp1
  mov @dwSize,0
  .repeat
    mov al,byte ptr [esi]

    mov bl,al
    xor edx,edx
    xor eax,eax
    mov al,bl
    mov cx,16
    div cx   ;结果高位在al中,余数在dl中


    xor bx,bx
    mov bl,al
    movzx edi,bx
    mov bl,byte ptr lpszHexArr[edi]
    mov eax,@dwSize
    mov byte ptr bufTemp1[eax],bl


    inc @dwSize

    xor bx,bx
    mov bl,dl
    movzx edi,bx

    ;invoke wsprintf,addr szBuffer,addr szOut2,edx
    ;invoke MessageBox,NULL,addr szBuffer,NULL,MB_OK

    mov bl,byte ptr lpszHexArr[edi]
    mov eax,@dwSize
    mov byte ptr bufTemp1[eax],bl

    inc @dwSize
    mov bl,20h
    mov eax,@dwSize
    mov byte ptr bufTemp1[eax],bl
    inc @dwSize
    inc esi
    dec _dwSize
    .break .if _dwSize==0
   .until FALSE

   mov bl,0
   mov eax,@dwSize
   mov byte ptr bufTemp1[eax],bl

   popad
   ret
_Byte2Hex    endp

_MemCmp  proc _lp1,_lp2,_size
   local @dwResult:dword

   pushad
   mov esi,_lp1
   mov edi,_lp2
   mov ecx,_size
   .repeat
     mov al,byte ptr [esi]
     mov bl,byte ptr [edi]
     .break .if al!=bl
     inc esi
     inc edi
     dec ecx
     .break .if ecx==0
   .until FALSE
   .if ecx!=0
     mov @dwResult,1
   .else 
     mov @dwResult,0
   .endif
   popad
   mov eax,@dwResult
   ret
_MemCmp  endp


;-------------------
; 取代码所在节的大小
; 代码节定位方法:
; 入口地址指向的RVA所在的节
; _lpHeader指向内存中PE文件的起始
; 返回值在eax中
;-------------------
getCodeSegSize proc _lpHeader
   local @dwSize
   pushad
   
   mov esi,_lpHeader
   assume esi:ptr IMAGE_DOS_HEADER
   add esi,[esi].e_lfanew    ;调整ESI指针指向PE文件头
   assume esi:ptr IMAGE_NT_HEADERS
   mov eax,[esi].OptionalHeader.AddressOfEntryPoint

   invoke _getRVASectionSize,_lpHeader,eax
   mov @dwSize,eax   
   popad
   mov eax,@dwSize
   ret
getCodeSegSize endp

;-------------------
; 取补丁代码所在节的大小
; _lpHeader指向内存中PE文件的起始
; 返回值在eax中
;-------------------
getCodeSegStart proc _lpHeader
   local @dwStart
   pushad
   
   mov esi,_lpHeader
   assume esi:ptr IMAGE_DOS_HEADER
   add esi,[esi].e_lfanew    ;调整ESI指针指向PE文件头
   assume esi:ptr IMAGE_NT_HEADERS
   mov eax,[esi].OptionalHeader.AddressOfEntryPoint
   invoke _getRVASectionStart,_lpHeader,eax
   mov @dwStart,eax   
   popad
   mov eax,@dwStart
   ret
getCodeSegStart endp

;-------------------------
; 获取代码入口
;-------------------------
getEntryPoint  proc  _lpFile
   local @ret
   pushad
   mov edi,_lpFile
   assume edi:ptr IMAGE_DOS_HEADER

   add edi,[edi].e_lfanew    ;调整ESI指针指向PE文件头
   assume edi:ptr IMAGE_NT_HEADERS
   ;取源程序装载地址
   add edi,4
   add edi,sizeof IMAGE_FILE_HEADER
   assume edi:ptr IMAGE_OPTIONAL_HEADER32
   mov eax,[edi].AddressOfEntryPoint
   mov @ret,eax
   popad
   mov eax,@ret
   ret
getEntryPoint endp
;--------------
;
;--------------------
writeToFile proc _lpFile,_dwSize
  local @dwWritten
  pushad
  invoke CreateFile,addr szDstFile,GENERIC_WRITE,\
            FILE_SHARE_READ,\
                0,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0
  mov hFile,eax
  invoke WriteFile,hFile,_lpFile,_dwSize,addr @dwWritten,NULL
  invoke CloseHandle,hFile      
  popad
  ret
writeToFile endp

;-------------------------------------
; 改变目标PE节的文件偏移属性
;-------------------------------------
changeRawOffset proc _lpHeader0,_lpHeader
  local @dwSize,@dwSectionSize
  local @ret
  local @dwTemp,@dwTemp1
  pushad

  mov esi,_lpHeader
  assume esi:ptr IMAGE_DOS_HEADER
  add esi,[esi].e_lfanew    ;调整ESI指针指向PE文件头
  assume esi:ptr IMAGE_NT_HEADERS
  ;取节的数量
  add esi,4
  assume esi:ptr IMAGE_FILE_HEADER
  movzx ecx,[esi].NumberOfSections
  mov @dwSectionSize,ecx

  

  mov edi,lpDstMemory
  assume edi:ptr IMAGE_DOS_HEADER
  add edi,[edi].e_lfanew    ;调整ESI指针指向PE文件头
  assume edi:ptr IMAGE_NT_HEADERS
   
  pushad
  invoke _appendInfo,addr szCrLf
  invoke _appendInfo,addr szOut107
  popad

  add edi,sizeof IMAGE_NT_HEADERS   ;edi指向节表位置
  .repeat
     assume edi:ptr IMAGE_SECTION_HEADER
     mov ebx,[edi].PointerToRawData  ;取节在文件中的偏移
     mov @dwTemp,ebx
     add ebx,dwOff      ;修正该值
     mov @dwTemp1,ebx
     mov dword ptr [edi].PointerToRawData,ebx

     ; 显示
     pushad
     mov eax,[edi].VirtualAddress
     inc eax
     invoke _getRVASectionName,_lpHeader,eax
     invoke wsprintf,addr szBuffer,addr szOut108,eax,@dwTemp,@dwTemp1
     invoke _appendInfo,addr szBuffer 
     popad  

     dec @dwSectionSize
     add edi,sizeof IMAGE_SECTION_HEADER
     .break .if @dwSectionSize==0
  .until FALSE
   

  popad 
  ret
changeRawOffset  endp

;--------------------
; 打开PE文件并处理
;--------------------
_openFile proc
  local @stOF:OPENFILENAME
  local @hFile,@dwFileSize,@hMapFile,@lpMemory
  local @hFile1,@dwFileSize1,@hMapFile1,@lpMemory1
  local @bufTemp1[10]:byte
  local @dwTemp:dword,@dwTemp1:dword
  local @dwBuffer,@lpDst,@hDstFile
  

  invoke CreateFile,addr szFileNameOpen1,GENERIC_READ,\
         FILE_SHARE_READ or FILE_SHARE_WRITE,NULL,\
         OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL

  .if eax!=INVALID_HANDLE_VALUE
    mov @hFile,eax
    invoke GetFileSize,eax,NULL
    mov @dwFileSize,eax
    .if eax
      invoke CreateFileMapping,@hFile,\  ;内存映射文件
             NULL,PAGE_READONLY,0,0,NULL
      .if eax
        mov @hMapFile,eax
        invoke MapViewOfFile,eax,\
               FILE_MAP_READ,0,0,0
        .if eax
          mov @lpMemory,eax              ;获得文件在内存的映象起始位置
          assume fs:nothing
          push ebp
          push offset _ErrFormat
          push offset _Handler
          push fs:[0]
          mov fs:[0],esp

          ;检测PE文件是否有效
          mov esi,@lpMemory
          assume esi:ptr IMAGE_DOS_HEADER
          .if [esi].e_magic!=IMAGE_DOS_SIGNATURE  ;判断是否有MZ字样
            jmp _ErrFormat
          .endif
          add esi,[esi].e_lfanew    ;调整ESI指针指向PE文件头
          assume esi:ptr IMAGE_NT_HEADERS
          .if [esi].Signature!=IMAGE_NT_SIGNATURE ;判断是否有PE字样
            jmp _ErrFormat
          .endif
        .endif
      .endif
    .endif
  .endif

  invoke CreateFile,addr szFileNameOpen2,GENERIC_READ,\
         FILE_SHARE_READ or FILE_SHARE_WRITE,NULL,\
         OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL

  .if eax!=INVALID_HANDLE_VALUE
    mov @hFile1,eax
    invoke GetFileSize,eax,NULL
    mov @dwFileSize1,eax
    .if eax
      invoke CreateFileMapping,@hFile1,\  ;内存映射文件
             NULL,PAGE_READONLY,0,0,NULL
      .if eax
        mov @hMapFile1,eax
        invoke MapViewOfFile,eax,\
               FILE_MAP_READ,0,0,0
        .if eax
          mov @lpMemory1,eax              ;获得文件在内存的映象起始位置
          assume fs:nothing
          push ebp
          push offset _ErrFormat1
          push offset _Handler
          push fs:[0]
          mov fs:[0],esp

          ;检测PE文件是否有效
          mov esi,@lpMemory1
          assume esi:ptr IMAGE_DOS_HEADER
          .if [esi].e_magic!=IMAGE_DOS_SIGNATURE  ;判断是否有MZ字样
            jmp _ErrFormat1
          .endif
          add esi,[esi].e_lfanew    ;调整ESI指针指向PE文件头
          assume esi:ptr IMAGE_NT_HEADERS
          .if [esi].Signature!=IMAGE_NT_SIGNATURE ;判断是否有PE字样
            jmp _ErrFormat1
          .endif
        .endif
      .endif
    .endif
  .endif

  ;到此为止,两个内存文件的指针已经获取到了。@lpMemory和@lpMemory1分别指向连个文件头



  ;补丁代码段大小        
  invoke getCodeSegSize,@lpMemory
  mov dwPatchCodeSize,eax 

  invoke wsprintf,addr szBuffer,addr szOut100,eax
  invoke _appendInfo,addr szBuffer 
 

  ;将文件大小按照文件对齐粒度对齐
  
  invoke getFileAlign,@lpMemory1
  mov dwFileAlign,eax
  xchg eax,ecx
  mov eax,@dwFileSize1
  invoke _align
  mov dwNewFileAlignSize,eax

  invoke wsprintf,addr szBuffer,addr szOut121,@dwFileSize1,dwNewFileAlignSize
  invoke _appendInfo,addr szBuffer 

  ;求最后一节在文件中的偏移
  invoke getLastSectionStart,@lpMemory1
  mov dwLastSectionStart,eax

  invoke wsprintf,addr szBuffer,addr szOut122,eax
  invoke _appendInfo,addr szBuffer 

  ;求最后一节大小
  mov eax,dwNewFileAlignSize
  sub eax,dwLastSectionStart
  add eax,dwPatchCodeSize
  ;将该值按照文件对齐粒度对齐
  mov ecx,dwFileAlign
  invoke _align
  mov dwLastSectionAlignSize,eax

  invoke wsprintf,addr szBuffer,addr szOut123,eax
  invoke _appendInfo,addr szBuffer 


  ;求新文件大小
  mov eax,dwLastSectionStart
  add eax,dwLastSectionAlignSize
  mov dwNewFileSize,eax

  invoke wsprintf,addr szBuffer,addr szOut124,eax
  invoke _appendInfo,addr szBuffer 
 

  ;申请内存空间
  invoke GlobalAlloc,GHND,dwNewFileSize
  mov @hDstFile,eax
  invoke GlobalLock,@hDstFile
  mov lpDstMemory,eax   ;将指针给@lpDst

  
  ;将目标文件拷贝到内存区域
  mov ecx,@dwFileSize1   
  invoke MemCopy,@lpMemory1,lpDstMemory,ecx

  ;将补丁代码附加到新的节中
  invoke getCodeSegStart,@lpMemory
  mov dwPatchCodeSegStart,eax

  ;拷贝补丁代码
  mov esi,dwPatchCodeSegStart  
  add esi,@lpMemory

  mov edi,lpDstMemory
  add edi,dwNewFileAlignSize

  mov ecx,dwPatchCodeSize
  invoke MemCopy,esi,edi,ecx

  ;---------------------------到此为止,数据拷贝完毕  

  ;修正

  ;计算SizeOfRawData
  invoke _getRVACount,lpDstMemory
  xor edx,edx
  dec eax
  mov ecx,sizeof IMAGE_SECTION_HEADER
  mul ecx

  mov edi,lpDstMemory
  assume edi:ptr IMAGE_DOS_HEADER
  add edi,[edi].e_lfanew
  add edi,sizeof IMAGE_NT_HEADERS  
  add edi,eax
  assume edi:ptr IMAGE_SECTION_HEADER
  mov eax,dwLastSectionAlignSize
  mov [edi].SizeOfRawData,eax

  ;计算Misc值
  invoke getSectionAlign,@lpMemory1
  mov dwSectionAlign,eax
  xchg eax,ecx
  mov eax,dwLastSectionAlignSize
  invoke _align
  mov [edi].Misc,eax

  ;修改标志
  mov eax,0c0000060h
  mov [edi].Characteristics,eax
  ;计算VirtualAddress

  mov eax,[edi].VirtualAddress  ;取原始RVA值
  mov dwVirtualAddress,eax

  ;修正函数入口地址  
  mov eax,dwNewFileAlignSize
  invoke _OffsetToRVA,lpDstMemory,eax
  mov dwNewEntryPoint,eax
  mov edi,lpDstMemory
  assume edi:ptr IMAGE_DOS_HEADER
  add edi,[edi].e_lfanew    
  assume edi:ptr IMAGE_NT_HEADERS
  mov eax,[edi].OptionalHeader.AddressOfEntryPoint
  mov dwDstEntryPoint,eax
  mov eax,dwNewEntryPoint
  mov [edi].OptionalHeader.AddressOfEntryPoint,eax
  
  mov eax,dwDstEntryPoint
  sub eax,dwNewEntryPoint
  mov dwEIPOff,eax

  ;修正SizeOfImage
  mov eax,dwLastSectionAlignSize
  mov ecx,dwSectionAlign
  invoke _align
  ;获取最后一个节的VirtualAddress
  add eax,dwVirtualAddress
  mov [edi].OptionalHeader.SizeOfImage,eax  
  

  ;修正补丁代码中的E9指令后的操作数  
  mov eax,lpDstMemory
  add eax,dwNewFileAlignSize
  add eax,dwPatchCodeSize

  sub eax,5   ;EAX指向了E9的操作数
  mov edi,eax

  sub eax,lpDstMemory
  add eax,4

  mov ebx,dwDstEntryPoint
  invoke _OffsetToRVA,lpDstMemory,eax
  sub ebx,eax
  mov dword ptr [edi],ebx

  pushad
  invoke wsprintf,addr szBuffer,addr szOut112,ebx
  invoke _appendInfo,addr szBuffer    
  popad
  
  ;将新文件内容写入到c:\bindC.exe
  invoke writeToFile,lpDstMemory,dwNewFileSize
 
  jmp _ErrorExit  ;正常退出

_ErrFormat:
          invoke MessageBox,hWinMain,offset szErrFormat,NULL,MB_OK
_ErrorExit:
          pop fs:[0]
          add esp,0ch
          invoke UnmapViewOfFile,@lpMemory
          invoke CloseHandle,@hMapFile
          invoke CloseHandle,@hFile
          jmp @F
_ErrFormat1:
          invoke MessageBox,hWinMain,offset szErrFormat,NULL,MB_OK
_ErrorExit1:
          pop fs:[0]
          add esp,0ch
          invoke UnmapViewOfFile,@lpMemory1
          invoke CloseHandle,@hMapFile1
          invoke CloseHandle,@hFile1
@@:        
  ret
_openFile endp


;-------------------
;打开对比窗口
;-------------------
_doComp proc
  pushad

  popad
  ret
_doComp endp
;-------------------
; 窗口程序
;-------------------
_ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam
  mov eax,wMsg
  .if eax==WM_CLOSE
    invoke FadeOutClose,hWnd
    invoke EndDialog,hWnd,NULL
  .elseif eax==WM_INITDIALOG  ;初始化
    push hWnd
    pop hWinMain
    call _init
    invoke FadeInOpen,hWnd
  .elseif eax==WM_COMMAND     ;菜单
    mov eax,wParam
    .if eax==IDM_EXIT       ;退出
      invoke FadeOutClose,hWnd
      invoke EndDialog,hWnd,NULL 
    .elseif eax==IDM_OPEN   ;打开文件
        invoke _OpenFile1
    .elseif eax==IDM_1  
        invoke _OpenFile2
    .elseif eax==IDM_2
        ;将内存映射文件复制一份,留出间隙一
        invoke _openFile
    .elseif eax==IDM_3
    .endif
  .else
    mov eax,FALSE
    ret
  .endif
  mov eax,TRUE
  ret
_ProcDlgMain endp

start:
  invoke InitCommonControls
  invoke LoadLibrary,offset szDllEdit
  mov hRichEdit,eax
  invoke GetModuleHandle,NULL
  mov hInstance,eax
  invoke DialogBoxParam,hInstance,\
         DLG_MAIN,NULL,offset _ProcDlgMain,NULL
  invoke FreeLibrary,hRichEdit
  invoke ExitProcess,NULL
end start

测试

打开补丁文件:

打开PE文件:

执行绑定

生成的绑定文件在C盘目录下,这里命名为bind20.exe

运行bind20.exe

输入用户名:admin和密码:123456

点登录即打开记事本程序

20.6 小结

本章实现了一个EXE加锁器。首先,编写了一个无需资源文件的窗口程序,通过在PE中创建一个新节,并通过将代码附加到新节的方法对目标PE实施补丁;当用户想运行目标PE时,需要先输入密码。

本章的重点是无资源文件补丁程序的编写。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐