程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> 更多編程語言 >> 匯編語言 >> 用程序修改PE使其可顯示一個消息框

用程序修改PE使其可顯示一個消息框

編輯:匯編語言

該程序可從命令行提取一文件名,或出現對話框要求選擇一文件,該文件必須是有效的PE可執行文件,如果該程序中曾使用動態鏈接庫USER32.dll中的函數MessageBoxA,則向該程序中新添加一段指令,用來顯示一個消息框,若未曾使用此函數,則不再修改此文件。由於本人對PE格式文件認識膚淺,程序可能不夠完善,僅供初學者參考,高手看看,可提些改進意見!

本程序在運行時要修改代碼段,編譯方法可參考“系列11:把數據寫到代碼段”。

本程序不增加源程序大小,不適合對手工處理的PE文件操作。

----------------------------------------------------------------------
;文件名:19.asm
此文用老羅的代碼著色器處理,在此向老羅表示感謝!
.386
.model flat,stdcall
Option CaseMap:none
include windows.inc
include kernel32.inc
include user32.inc
include comdlg32.inc
includelib kernel32.lib
includelib user32.lib
includelib comdlg32.lib
    .data
ofn       OPENFILENAME <0>           ;打開文件對話框要用到該結構
FileName    db 256 dup(0)
Caption     db 'GetCommandLine',0
FilterString  db '可執行文件(*.exe)',0,'*.exe',0,0 ;文件過濾器字符串
DialogTitle   db '請選擇要打開的文件', 0
MessageTitle  db '你選擇的文件',0
NoFileError   db '程序沒有選擇文件,不能繼續進行!',0
ErrorTitle   db '警告',0
ErrorPEFormat  db '無效的PE格式文件',0
RightPEFormat  db '有效的PE格式文件',0
szDll      db 'USER32.dll',0
szFunction   db 'MessageBoxA',0
ErrorMessage  db '所選文件中不曾使用 MessageBoxA 函數!',0
CanNotModify  db '原代碼段內不足以放下新增部分!',0
OkModify    db '所選程序已經被成功修改!',0
    .data?
hFile      dd ?
hMap      dd ?
pMapAddr    dd ?
ptext      dd ?
prdata     dd ?
MsgBoxA     dd ?
GetFileNameFromCommandLine  PROTO  :LPSTR
ModifyFile PROTO :LPSTR
    .code
start:
    call GetFileName  ;取文件名
    .if eax==NULL    ;無文件名
    invoke MessageBox,NULL,addr NoFileError,addr ErrorTitle,MB_ICONEXCLAMATION
    .else
    call WinMain  ;修改文件
    .endif
    invoke ExitProcess,NULL
WinMain proc
    invoke CreateFile, \         ;打開文件,該函數具有多種功能
    addr FileName,\            ;指向要打開的文件名字符串
    GENERIC_READ or GENERIC_WRITE ,\   ;打開的文件具有讀寫的權限
    FILE_SHARE_READ or FILE_SHARE_WRITE,\ ;別人也可讀寫此文件
    NULL, \                ;95下不用
    OPEN_EXISTING, \           ;要打開的文件必須存在
    FILE_ATTRIBUTE_NORMAL,\        ;文件的屬性
    NULL                 ;95下必須是NULL
    .if eax!=INVALID_HANDLE_VALUE     ;判斷文件是否已正常打開
    mov hFile, eax           ;保存文件句柄
    invoke CreateFileMapping, \ ;Creat File mapping Object
    hFile, \          ;Identifies the file from which to create a mapping object
    NULL, \           ;ignored
    PAGE_READWRITE, \      ;access
    0, \             ;high-order 32 bits of the maximum size
    0, \             ;low-order 32 bits of the maximum size
    NULL             ;the mapping object is created without a name
    .if eax!=NULL        ;
    mov hMap,eax       ;the return value is a handle to the file-mapping object
    invoke MapViewOfFile,hMap,FILE_MAP_WRITE,0,0,NULL ;映射文件到內存
    .if eax!=NULL
    mov pMapAddr,eax         ;保存返回的內存塊首地址
    invoke ModifyFile,pMapAddr    ;修改內存塊內容
    invoke UnmapViewOfFile,pMapAddr ;解除文件映射
    .endif
    invoke CloseHandle,hMap    ;關閉內存映射文件
    .endif
    invoke CloseHandle, hFile     ;關閉文件
    .endif
    ret
WinMain endp
;獲取要處理的文件名
; 返回:若eax=NULL則表示沒提供要處理的文件名
;    否則eax指向文件名地址
GetFileName Proc
    invoke GetFileNameFromCommandLine,addr FileName
    .if eax==NULL
    call GetFileNameFromDialog
    .endif
    ret
GetFileName endp
;返回時若eax=NULL則表示無法從命令行提取被處理的文件
GetFileNameFromCommandLine proc uses esi edi ,lpString:LPSTR
    mov edi,lpString
    invoke GetCommandLine  ;取命令行參數
    mov esi,eax
    xor eax,eax
    cld
G1:
    lodsb
    cmp al,'"'   ;程序名項是以雙引號開始嗎?
    jnz G3
G2:
    lodsb
    cmp al,'"'   ;遇到雙引號則表示程序項名的結束
    jnz G2
G3:
    lodsb
    cmp al,' '   ;程序項名後總有一個空格
    jnz G3
G4:
    lodsb
    cmp al,0    ;無參數,轉結束
    jz G7
    cmp al,' '   ;跳過無效字符,可能是空格,也可能是制表符
    jz G4
    cmp al,9
    jz G4
    cmp al,'"'   ;文件名項也是以雙引號開始嗎?
    jnz G5
    stosb
G4p:
    lodsb
    stosb
    cmp al,'"'   ;遇到雙引號則表示文件名項的結束!
    jnz G4p
    jmp G6
G5:
    stosb
    lodsb
    cmp al,0     
    jz G6
    cmp al,' '   ;若文件名項沒用雙引號,則空格、制表或0表示文件名項的結束
    jz G6
    cmp al,9
    jnz G5
G6:
    xor al,al    ;以Ascii碼0作為結束
    stosb
    mov eax,lpString
G7:
    ret
GetFileNameFromCommandLine endp
;顯示打開對話框要求用戶選擇一個要處理的文件
GetFileNameFromDialog  proc
    mov ofn.lStructSize,sizeof ofn      ;結構的大小
    mov ofn.lpstrFilter,offset FilterString  ;文件過濾器
    mov ofn.lpstrFile,offset FileName     ;文件名的存放位置
    mov ofn.nMaxFile,256           ;文件名的最大長度
    mov ofn.Flags,OFN_FILEMUSTEXIST or OFN_HIDEREADONLY or OFN_LONGNAMES
    mov ofn.lpstrTitle,offset DialogTitle   ;“打開”對話框的標題
    invoke GetOpenFileName,addr ofn      ;顯示打開對話框
    .if eax!=NULL
    lea eax,FileName
    .endif
    ret
GetFileNameFromDialog  endp
;修改內存塊的內容,就相當於修改文件的內容
ModifyFile proc uses ebx esi edi,lpBufferAddress:LPSTR
    mov edi,lpBufferAddress   ;取內存塊地址
    call CheckPEValid
    .if eax==NULL
    invoke MessageBox,NULL,addr ErrorPEFormat,addr ErrorTitle,MB_ICONEXCLAMATION
    .else
    ;若是有效的PE,則edi中有PE頭的地址
    invoke MessageBox,NULL,addr RightPEFormat,addr MessageTitle,MB_ICONINFORMATION
    call IniData    ;設置.text及.rdata的位置
    call HaveMessageBox
    .if eax!=0
    call AddCode
    .if eax==0
    invoke MessageBox,NULL,addr CanNotModify,addr ErrorTitle,MB_ICONEXCLAMATION
    .else
    invoke MessageBox,NULL,addr OkModify,addr MessageTitle,MB_ICONINFORMATION
    .endif
    .else
    invoke MessageBox,NULL,addr ErrorMessage,addr ErrorTitle,MB_ICONEXCLAMATION
    .endif
    .endif    
    ret
ModifyFile endp
;檢查PE文件標記以確定其有效性
;若有效,eax指向PE頭
; 否則eax=0
CheckPEValid  proc
    xor eax,eax
    assume edi:ptr IMAGE_DOS_HEADER
    .if [edi].e_magic==IMAGE_DOS_SIGNATURE   ;是否有DOS頭標記
    add edi,[edi].e_lfanew
    assume edi:ptr IMAGE_NT_HEADERS
    .if [edi].Signature==IMAGE_NT_SIGNATURE ;是否有PE頭標記
    mov eax,edi             ;eax指向PE頭
    .endif
    .endif
    ret
CheckPEValid  endp
;檢查程序中是否使用MessageBoxA函數
;返回:沒用,eax=NULL
;    用,eax含存放函數入口的單元的地址
HaveMessageBox proc
    mov esi,prdata           ;esi指向section table中的 .rdata
    assume esi:ptr IMAGE_SECTION_HEADER
    mov eax,[esi].PointerToRawData   ; .rdata在文件中的絕對偏移位置
    mov ebx,[esi].VirtualAddress    ; .rdata在內存中的VRA
    sub eax,ebx
    add eax,pMapAddr  ;加上文件在內存中的起始位置
    mov ecx,eax
    assume edi:ptr IMAGE_NT_HEADERS
    add eax,[edi].OptionalHeader.DataDirectory.VirtualAddress \
    +sizeof IMAGE_DATA_DIRECTORY
    mov esi,eax   ;esi指向.rdata section
    assume esi:ptr IMAGE_IMPORT_DESCRIPTOR
Have1:
    mov eax,[esi].Name1
    or eax,eax
    jz end2               ;結束,轉!
    add eax,ecx             ;對應到文件內存塊中的地址
    push ecx
    invoke lstrcmp,eax,addr szDll    ;看是否引用USER32.dll庫
    pop ecx
    or eax,eax
    jz Have3              ;有引用,轉!
    add esi,sizeof IMAGE_IMPORT_DESCRIPTOR
    jmp Have1              ;繼續測試下一個
Have3:
    call CheckFunction
end2:    ;結束,沒有USER32.Dll庫
    ret
HaveMessageBox endp
;esi指向IMAGE_IMPORT_DESCRIPTOR 結構
;若有MessageBoxA函數,則eax含有(存放函數入口的)單元地址
; 否則eax=0
CheckFunction  proc
    assume esi:ptr IMAGE_IMPORT_DESCRIPTOR
    mov edx,[esi].FirstThunk    ;指向(存放函數入口的)單元地址
    mov ebx,[esi].OriginalFirstThunk
    add ebx,ecx           ;ebx指向內存塊中的IMAGE_THUNK_DATA結構
Check1:
    mov eax,[ebx]
    or eax,eax
    jz Check3
    add eax,ecx           ;eax指向內存塊中的函數字符串
    push ecx
    push edx
    invoke lstrcmp,eax,addr szFunction ;是MessageBoxA嗎?
    pop edx
    pop ecx
    or eax,eax
    jz Check2
    add ebx,4
    add edx,4
    jmp Check1
Check2:
    mov eax,edx
Check3:
    ret
CheckFunction  endp
;EDI指向 PE Header
IniData proc
    assume edi:ptr IMAGE_NT_HEADERS
    mov ebx,[edi].OptionalHeader.NumberOfRvaAndSizes
    lea esi,[edi+8*ebx].OptionalHeader.DataDirectory.VirtualAddress
;此時esi指向section table
IniData1:
    mov eax,DWORD ptr [esi]
    cmp eax,7865742eh    ;是 .text嗎?
    jnz IniData2
    mov ptext,esi
    jmp IniData3
IniData2:
    cmp eax,6164722eh    ;是 .rdata嗎?
    jz IniData4
IniData3:
    add esi,sizeof IMAGE_SECTION_HEADER
    jmp IniData1
IniData4:
    mov prdata,esi
    ret
IniData endp
;將代碼加到原程序中!
AddCode proc
    mov esi,ptext
    assume esi:ptr IMAGE_SECTION_HEADER
    call ModifyCode         ;修改新增的指令部分
    assume edi:ptr IMAGE_NT_HEADERS
    mov eax,[esi].Misc.VirtualSize ;取原指令的長度
    mov ebx,[esi].PointerToRawData ;指令在文件中的絕對位置
    add ebx,eax           ;指向原指令後
    add ebx,pMapAddr        ;內存中的該位置
    add eax,FirstRunLen       ;加上新指令的長度
    cmp eax,[esi].SizeOfRawData ;和調整後的Section長度相比
    ja AddCode1           ;放不下新加的指令,結束
    push eax
    mov eax,[edi].OptionalHeader.BaseOfCode       ;原代碼開始處
    add eax,[esi].Misc.VirtualSize           ;指向新的入口
    mov [edi].OptionalHeader.AddressOfEntryPoint,eax  ;修改指令入口指向我們的程序
    pop eax
    mov [esi].Misc.VirtualSize,eax ;設置新的代碼段長度
    mov ecx,FirstRunLen       ;新指令的長度
    cld
    mov esi,offset FirstRun     ;指向新怎指令
    mov edi,ebx           ;設置文件內存塊中的目標地址
    rep movsb            ;將新增指令加到原指令後
    ret
AddCode1:
    xor eax,eax   ;沒能修改原程序,返回eax=0
    ret
AddCode endp
;eax含入口地址
ModifyCode proc
    assume edi:ptr IMAGE_NT_HEADERS
    add eax,[edi].OptionalHeader.ImageBase     ;調整eax的值 
    mov MustModify1,eax               ;設置MessageBoxA的入口地址
    mov eax,[edi].OptionalHeader.AddressOfEntryPoint;原程序的入口地址VRA
    mov edx,[edi].OptionalHeader.BaseOfCode     ;計算新加程序後第一個字節的VRA
    add edx,[esi].Misc.VirtualSize
    add edx,FirstRunLen
    sub eax,edx                   ;段內直接尋址
    mov MustModify2,eax               ;修改此值以轉向原指令處繼續執行
    ret
ModifyCode endp
;這是加到其他程序中要首先執行的部分
FirstRun proc
    push MB_ICONINFORMATION     ;消息框中的圖標
    call FirstRun1         ;消息框的標題
    db '憐香歡迎您',0
FirstRun1:
    call FirstRun2         ;消息框的文字內容
    db '這是程序被修改後首先顯示的信息!',0
FirstRun2:
    push NULL
MustModify1 equ this dword+2    ;要修改此處以使其可正常執行MessageBoxA函數
    call MsgBoxA
MustModify2 equ this dword+1    ;要修改此處以使其可轉到原程序去執行
    jmp start
FirstRun  endp
FirstRunLen equ $-FirstRun     ;計算新加代碼的長度
    end start
------------------------------------------------------------------------------

後記:

為編本程序,把MM都給氣哭啦,好幾天都不理我,說我心理沒有她,難呀,男人!

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved