国庆PE总复习(七)完结篇

2024华为数通HCIP-datacom最新题库(变题更新④) HCIP-Datacom考试真题 阅读详情
今天是国庆长假的最后一天,对看雪的几位朋友的承诺顿就在今天对现了,这是我做人原格问题,说实话要想对现一个承诺,真要要做很我,付出才会有回报,是这我一直相信的道理 ,虽然前几天感冒了,但为了承诺还是一边用手巾擦鼻涕,一边写着文章,总算是写完了,不管是好是坏,总算是带着一些朋友把PE的相关知识又重新温习了一遍,技术含量肯定没有玩命大哥的高,不过自己也一直在向牛人们学习,希望有一天,也能向他们那样吧!!
我相信每一个牛人都是这样一步一步走上去的,从什么都不会,到技术达人!!依惜记得高中毕业时我还不知道QQ是什么,毕业那会有很多同学说给我QQ号,我没要,因为我根本不知道那个有什么用,无语!! 那时也不会上网,只是听别人说上网像冲浪,其实也没玩过冲浪,所以也不知道冲浪是啥感觉,但几年过去了,不说自己技术有多强,但至少有很多东西,我基本上都掌握了,什么事情都是这样的,只要你想去做,就一定要坚持做下去,不管结局是好是坏,总会有一些意外的收获!相信只要大家努力,总有一天能够和看雪的几位大牛们一样的!!有梦想就一直要大胆去追,这样才活的有意义!!

如果这七天,你都认认真真的所这七篇文章看完了,如果能给你一点小小的收获的话,我倍感兴慰~~~

最后一篇文章,我就作个结尾,讲一个比较简单的应用,相信写过病毒的朋友们一般都会参考过这段代码!!如果还没写过病毒的朋友,一定会觉得很有意思,然后会爱上这个,我以前就是这样的,觉得好好玩的!!!如果不学PE的话,这些东西都很少提到,一般的编程书上也很少讲这方面的知识!!

参考文献《Win32汇编语言程序设计》第二版
在PE文件上添加可执行代码!!!!
本章所涉及到的实例将演示在PE文件上添加一段可执行代码,并且让这段代码在原来的代码之前被执行,经过修改的目标PE文件被运行后,将首先弹出一个带"YES"和"NO"的消息框并提示“一定要运行这个程序吗?”,如果用户选择"YES"的话,原文件被运行,否则程序直接退出。

先讲解一下原理吧!
根据前面对PE文件各个部分进行的分析,可以得到在PE文件中添加代码需要以下几个步骤:
将添加的代码写到目标PE文件中,这段代码既可以插入原代码所处的节的空隙中(由于每个节保存在文件中时是按照FileAlignment的值对齐的,所以节的最后必然会有一些空余的空间),也可以通过添加一个新的节对附在原文件的尾部。
PE文件原来的入口指针必须被保存在添加的代码中,这样,这段代码执行完以后可以转移到原始文件处执行。
PE文件原来的入口指针需要被修改,指向新添加代码的入口地址。
PE文件头中的一些值需要根据情况做相应的修正,以符合修改后PE文件的情况。
另外有一些操作是应该避免的,因为它们是无法实现的,或者实现它们的复杂性远远超过它们带来的好处,这些操作是:
如果节的空隙不足以插入代码的话,应该在文件尾新建一个节而不是去扩大原来的代码节并将它后面的其他节后移,因为程序无法得知整个PE文件中有多少个RVA值会指向这些被移动位置的节,修正所有这些RVA值几乎是不可能的。
如果附加的代码中要用到API函数的话,不要尝试在原始目标文件的导入表添加导入函数名称,因为这样将涉及在目标PE文件的导入表中插入新的模块名和函数名,其结果同样是造成导入表的一些项目被移动位置,修正指向这些项目的RVA同样是很难实现的。
_ProcessPeFile这个源代码中包含了一个_AddCode.asm的源文件,先来看看_AddCode.asm为我们做了些什么?
代码如下:
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Sample code for < Win32ASM Programming 2nd Edition>
; by 罗云彬
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 要被添加到目标文件后面的执行代码
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;
;
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 一些函数的原形定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProtoGetProcAddress  typedef  proto  :dword,:dword
_ProtoLoadLibrary  typedef  proto  :dword
_ProtoMessageBox  typedef  proto  :dword,:dword,:dword,:dword
_ApiGetProcAddress  typedef  ptr  _ProtoGetProcAddress
_ApiLoadLibrary    typedef  ptr  _ProtoLoadLibrary
_ApiMessageBox    typedef  ptr  _ProtoMessageBox
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;
;
APPEND_CODE  equ  this byte                            ;定义这段代码的开头位置
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 被添加到目标文件中的代码从这里开始
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include    _GetKernel.asm                               ;这个源程序我在前面已经讲过,这时太不重提了,不清楚的看以前的讲解吧!
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
hDllKernel32  dd  ?
hDllUser32  dd  ?
_GetProcAddress  _ApiGetProcAddress  ?
_LoadLibrary  _ApiLoadLibrary    ?
_MessageBox  _ApiMessageBox    ?
szLoadLibrary  db  'LoadLibraryA',0
szGetProcAddress db  'GetProcAddress',0
szUser32  db  'user32',0
szMessageBox  db  'MessageBoxA',0
szCaption  db  '问题提示',0
szText    db  '你一定要运行这个程序吗?',0
;********************************************************************
; 新的入口地址
;********************************************************************
_NewEntry:
;********************************************************************
; 重定位并获取一些 API 的入口地址
;********************************************************************
    call  @F
    @@:
    pop  ebx
    sub  ebx,offset @B
;********************************************************************
    invoke  _GetKernelBase,[esp]  ;获取Kernel32.dll基址
    .if  ! eax
      jmp  _ToOldEntry
    .endif
    mov  [ebx+hDllKernel32],eax  ;获取GetProcAddress入口
    lea  eax,[ebx+szGetProcAddress]
    invoke  _GetApi,[ebx+hDllKernel32],eax
    .if  ! eax
      jmp  _ToOldEntry
    .endif
    mov  [ebx+_GetProcAddress],eax
;********************************************************************
    lea  eax,[ebx+szLoadLibrary]  ;获取LoadLibrary入口
    invoke  [ebx+_GetProcAddress],[ebx+hDllKernel32],eax
    mov  [ebx+_LoadLibrary],eax
    lea  eax,[ebx+szUser32]  ;获取User32.dll基址
    invoke  [ebx+_LoadLibrary],eax
    mov  [ebx+hDllUser32],eax
    lea  eax,[ebx+szMessageBox]  ;获取MessageBox入口
    invoke  [ebx+_GetProcAddress],[ebx+hDllUser32],eax
    mov  [ebx+_MessageBox],eax
;********************************************************************
    lea  ecx,[ebx+szText]
    lea  eax,[ebx+szCaption]
    invoke  [ebx+_MessageBox],NULL,ecx,eax,MB_YESNO or MB_ICONQUESTION     
    .if  eax !=  IDYES                                 ;如果选择的不是YES,则直接返回,不会返回到原入口点处
      ret
    .endif
;********************************************************************
; 执行原来的文件                                                      ;这里主要是为了从当前入口点返回到原入口点
;********************************************************************
_ToOldEntry:
    db  0e9h  ;0e9h是jmp xxxxxxxx的机器码
_dwOldEntry:
    dd  ?  ;用来填入原来的入口地址               ;这里用来填入原程序的入口地址
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
APPEND_CODE_END  equ  this byte                             ;定义这段代码的结束

_AddCode.asm中包含要被添加到其他可执行文件中的代码。这段代码是按照能够自身重定位的方式写的,而且必须按照这种格式书写,因为当它被添加到目标PE文件后,对于不同的PE文件所处的位置肯定是不同的,不进行重定位处理必然无法正常运行。
这段附加代码实现的功能和前一篇文章中NoImport例子大致相同,也是首先使用_GetKernel..asm中提供的两个函数来获取Kernel32.dll模块的基址和GetProcAddress函数的入口地址,并由此最后得到MessageBox函数的入口地址以便显示消息框。
在这段程序的最后_ToOldEntry标号处的数据是0e9h是JMP XXXXXXXX的机器码的第一个字节,它与下面的_dwOldEntry标号处的双字一起组成整个JMP指令,这条JMP指令将在主程序中根据具体情况修正。

现在我们来重点不看看_ProcessPeFile这个源程序的代码吧!!
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Sample code for < Win32ASM Programming 2nd Edition>
; by 罗云彬
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; AddCode 例子的功能模块
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
    .const

szErrCreate  db  '创建文件错误!',0dh,0ah,0
szErrNoRoom  db  '程序中没有多余的空间可供加入代码!',0dh,0ah,0
szMySection  db  '.adata',0
szExt    db  '_new.exe',0
szSuccess  db  '在文件后附加代码成功,新文件:',0dh,0ah
    db  '%s',0dh,0ah,0

    .code

include    _AddCode.asm

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 计算按照指定值对齐后的数值
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Align    proc  _dwSize,_dwAlign

    push  edx
    mov  eax,_dwSize
    xor  edx,edx
    div  _dwAlign
    .if  edx
      inc  eax
    .endif
    mul  _dwAlign
    pop  edx
    ret

_Align    endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcessPeFile  proc  _lpFile,_lpPeHead,_dwSize
    local  @szNewFile[MAX_PATH]:byte
    local  @hFile,@dwTemp,@dwEntry,@lpMemory
    local  @dwAddCodeBase,@dwAddCodeFile
    local  @szBuffer[256]:byte

    pushad
;********************************************************************
; (Part 1)准备工作:1-建立新文件,2-打开文件
;********************************************************************
    invoke  lstrcpy,addr @szNewFile,addr szFileName
    invoke  lstrlen,addr @szNewFile
    lea  ecx,@szNewFile
    mov  byte ptr [ecx+eax-4],0
    invoke  lstrcat,addr @szNewFile,addr szExt
    invoke  CopyFile,addr szFileName,addr @szNewFile,FALSE     ;从原始PE文件拷贝一个名为“原始文件名_new.exe”的文件
                                                                           ;这个文件将被添加上可执行代码,原来的文件不会被改动
    invoke  CreateFile,addr @szNewFile,GENERIC_READ or GENERIC_WRITE,FILE_SHARE_READ or \
      FILE_SHARE_WRITE,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_ARCHIVE,NULL
    .if  eax ==  INVALID_HANDLE_VALUE                       ;文件拷贝成功后,打开拷贝生成的新文件,以便进行修改
      invoke  SetWindowText,hWinEdit,addr szErrCreate
      jmp  _Ret
    .endif
    mov  @hFile,eax
;********************************************************************
;(Part 2)进行一些准备工作和检测工作
; esi --> 原PeHead,edi --> 新的PeHead
; edx --> 最后一个节表,ebx --> 新加的节表
;********************************************************************
    mov  esi,_lpPeHead
    assume  esi:ptr IMAGE_NT_HEADERS,edi:ptr IMAGE_NT_HEADERS
    invoke  GlobalAlloc,GPTR,[esi].OptionalHeader.SizeOfHeaders ;分配一个等于目标PE文件的文件头大小的内存块
    mov  @lpMemory,eax
    mov  edi,eax
    invoke  RtlMoveMemory,edi,_lpFile,[esi].OptionalHeader.SizeOfHeaders   ;并使用RltMoveMemory将PE文件头拷贝到这个内存中
    add  edi,esi
    sub  edi,_lpFile
    movzx  eax,[esi].FileHeader.NumberOfSections           ;得到节表的数量
    dec  eax
    mov  ecx,sizeof IMAGE_SECTION_HEADER                 ;节表的长度
    mul  ecx                                             ;节表的数量*节表的长度

    mov  edx,edi
    add  edx,eax
    add  edx,sizeof IMAGE_NT_HEADERS                    ;节表尾部的指针
    mov  ebx,edx
    add  ebx,sizeof IMAGE_SECTION_HEADER                ;节表的最后一项的指针
    assume  ebx:ptr IMAGE_SECTION_HEADER,edx:ptr IMAGE_SECTION_HEADER
;********************************************************************
; (Part 2.1)检查是否有空闲的位置可供插入节表
;********************************************************************
    pushad
    mov  edi,ebx
    xor  eax,eax                                        ;将EAX值设为0
    mov  ecx,IMAGE_SECTION_HEADER
    repz  scasb                                          ;看节表中是否存在一个全零的位置,进行扫描
    popad
    .if  ! ZERO?
;********************************************************************
; (Part 3.1)如果没有新的节表空间的话,则查看现存代码节的最后
; 是否存在足够的全零空间,如果存在则在此处加入代码
;********************************************************************
      xor  eax,eax
      mov  ebx,edi
      add  ebx,sizeof IMAGE_NT_HEADERS
      .while  ax <=  [esi].FileHeader.NumberOfSections  ;扫描现存的节
        mov  ecx,[ebx].SizeOfRawData            ;节在磁盘文件中对齐后的大小
        .if  ecx && ([ebx].Characteristics & IMAGE_SCN_MEM_EXECUTE)  ;节的属性
          sub  ecx,[ebx].Misc.VirtualSize ;节在磁盘中对齐后的大小减去节的实际大上,得到节空隙大小
          .if  ecx > offset APPEND_CODE_END-offset APPEND_CODE   ;看现存的节空隙的大小是否大于加入的代码长度
            or  [ebx].Characteristics,IMAGE_SCN_MEM_READ or IMAGE_SCN_MEM_WRITE
            add  [ebx].Misc.VirtualSize,offset APPEND_CODE_END-offset APPEND_CODE
            jmp  @F                 ;如果存在一个节的空隙大于加入到的代码长度,则跳到加入代码处
          .endif
        .endif
        add  ebx,IMAGE_SECTION_HEADER           ;指向下一个节
        inc  ax
      .endw
      invoke  CloseHandle,@hFile
      invoke  DeleteFile,addr @szNewFile                    
      invoke  SetWindowText,hWinEdit,addr szErrNoRoom
      jmp  _Ret
      @@:
;********************************************************************
; 将新增代码加入代码节的空隙中
;********************************************************************
      mov  eax,[ebx].VirtualAddress                  ;节装载到内存中的偏移地址
      add  eax,[ebx].Misc.VirtualSize                ;加上节的实际大小
      mov  @dwAddCodeBase,eax                        ;新添代码在内存中的位置
      mov  eax,[ebx].PointerToRawData                ;在文件中的偏移
      add  eax,[ebx].Misc.VirtualSize                ;加上节的实际大小
      mov  @dwAddCodeFile,eax                        ;新添加代码在文件中的位置             
      invoke  SetFilePointer,@hFile,@dwAddCodeFile,NULL,FILE_BEGIN        ;定位到加入代码地址处
      mov  ecx,offset APPEND_CODE_END-offset APPEND_CODE
      invoke  WriteFile,@hFile,offset APPEND_CODE,ecx,addr @dwTemp,NULL   ;将需加入的代码写入空隙处
    .else
;********************************************************************
; (Part 3.2)如果有新的节表空间的话,加入一个新的节
;********************************************************************
      inc  [edi].FileHeader.NumberOfSections                  ;增加一个新的节,节的数量加1
      mov  eax,[edx].PointerToRawData                   
      add  eax,[edx].SizeOfRawData
      mov  [ebx].PointerToRawData,eax
      mov  ecx,offset APPEND_CODE_END-offset APPEND_CODE
      invoke  _Align,ecx,[esi].OptionalHeader.FileAlignment      ;将新添加后的节按指定值对齐
      mov  [ebx].SizeOfRawData,eax
      invoke  _Align,ecx,[esi].OptionalHeader.SectionAlignment   ;将新添加后的节按指定的值对值
      add  [edi].OptionalHeader.SizeOfCode,eax  ;修正SizeOfCode
      add  [edi].OptionalHeader.SizeOfImage,eax  ;修正SizeOfImage
      invoke  _Align,[edx].Misc.VirtualSize,[esi].OptionalHeader.SectionAlignment
      add  eax,[edx].VirtualAddress
      mov  [ebx].VirtualAddress,eax                             ;给新添加的节的各个字段赋值
      mov  [ebx].Misc.VirtualSize,offset APPEND_CODE_END-offset APPEND_CODE
      mov  [ebx].Characteristics,IMAGE_SCN_CNT_CODE\
        or IMAGE_SCN_MEM_EXECUTE or IMAGE_SCN_MEM_READ or IMAGE_SCN_MEM_WRITE
      invoke  lstrcpy,addr [ebx].Name1,addr szMySection          
;********************************************************************
; 将新增代码作为一个新的节写到文件尾部
;********************************************************************
      invoke  SetFilePointer,@hFile,[ebx].PointerToRawData,NULL,FILE_BEGIN   ;定位到新添加的节的位置
      invoke  WriteFile,@hFile,offset APPEND_CODE,[ebx].Misc.VirtualSize,\   ;将代码写入到新添加的节的位置
        addr @dwTemp,NULL
      mov  eax,[ebx].PointerToRawData
      add  eax,[ebx].SizeOfRawData
      invoke  SetFilePointer,@hFile,eax,NULL,FILE_BEGIN                      ;定位到文件的开始
      invoke  SetEndOfFile,@hFile                                            ;然后得到文件的结尾位置
;********************************************************************
      push  [ebx].VirtualAddress  ;eax = 新加代码的基地址                
      pop  @dwAddCodeBase
      push  [ebx].PointerToRawData
      pop  @dwAddCodeFile
    .endif
;********************************************************************
; (Part 4)修正文件入口指针并写入新的文件头
;********************************************************************
    mov  eax,@dwAddCodeBase
    add  eax,(offset _NewEntry-offset APPEND_CODE)
    mov  [edi].OptionalHeader.AddressOfEntryPoint,eax                          ;将入口地址写入文件头
    invoke  SetFilePointer,@hFile,0,NULL,FILE_BEGIN                               ;定位到文件开始,写入新的文件头
    invoke  WriteFile,@hFile,@lpMemory,[esi].OptionalHeader.SizeOfHeaders,\
      addr @dwTemp,NULL
;********************************************************************
; (Part 5)修正新加代码中的 Jmp oldEntry 指令
;********************************************************************
    push  [esi].OptionalHeader.AddressOfEntryPoint
    pop  @dwEntry
    mov  eax,@dwAddCodeBase
    add  eax,(offset _ToOldEntry-offset APPEND_CODE+5)                         ;定位到原来入口点代码处+5是因为
    sub  @dwEntry,eax                                                          ;JMP XXXXXXXX占用了五个字节
    mov  ecx,@dwAddCodeFile
    add  ecx,(offset _dwOldEntry-offset APPEND_CODE)
    invoke  SetFilePointer,@hFile,ecx,NULL,FILE_BEGIN
    invoke  WriteFile,@hFile,addr @dwEntry,4,addr @dwTemp,NULL                    ;写入跳转到原代码的代码
;********************************************************************
; (Part 6)关闭文件
;********************************************************************
    invoke  GlobalFree,@lpMemory                                                  ;释放内存,关闭文件句柄
    invoke  CloseHandle,@hFile
    invoke  wsprintf,addr @szBuffer,Addr szSuccess,addr @szNewFile
    invoke  SetWindowText,hWinEdit,addr @szBuffer
_Ret:
    assume  esi:nothing
    popad
    ret

_ProcessPeFile  endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
上面的我作了简单的注释,这里我在具体分析一下这段代码,请大家仔细研究一下,很有意思的!!
Part 1从原始PE文件拷贝一个名为“原始文件名_new.exe”的文件,这个文件将被添加上可执行代码,原来的“原始文件名.exe”文件则不会被改动。当文件成功拷贝后,程序将打开拷贝生成的新文件以便进行修改。

Part 2分配一个等于目标PE文件的文件头大小的内存块,并将文件头拷贝到这个内存块中,所有对PE文件头的修改操作都是在这个内存块中完成的,这个内存块的内容最终将被写到“原始文件名_new.exe”文件中。完成拷贝工作以后,程序计算两个指针以备后用:指向节表最后一项指针和指向节表尾部的指针,这两个指针可以从节表的数量和节表的长度计算而来的,节表的数量是从PE文件头中的FileHeader.NumberOfSections字段获取的。
正如本节的开始所述,新增的代码既可以插入原代码所处的节的空隙中,也可以通过添加一个新的节来附在原文件的尾部,为了增加成功的机会,应该对这两种情况都给予考虑,于是在Part2中对节表的尾部进行全零数据的扫描,如果存在一段全零的位置可供放入一个新的节表,那么采取增加新节的办法(Part3.2),否则采用在代码节的空隙中插入的方法(Part3.1)

Part 3.1 中对所有节表进行循环扫描,以便于找到代码节并检测节的空隙是否可以容纳新增的代码,程序首先判断SizeOfRawData是否为0,这个数值为0,说明这个节是包含未初始化数据的节,不能用于插入代码,如果SizeOfRawData大于0的话,则检测Characteristics字段查看当前节是否为代码节(包含IMAGE_SCN_MEM_EXECUTE标志)。
通过检测后,程序计算空隙的大小(SizeOfRawData和Misc.VirtualSize之差)是否大于插入代码的长度,如果空隙足够大的话,则进行插入操作。在插入代码的同时,这个节的属性中必须被加上IMAGE_SCN_MEM_READ和IMAGE_SCN_MEM_WRITE标志,因为附加代码中使用了对被加入部分进行写操作的指令。另外,VirtualSize字段中的实际数据大小也需要被修正。上面的程序只考虑了在代码节中插入的情况,要是代码节中的空隙大小不够,那么程序就退出了。实际上,程序也可以在其他的节中插入代码,只要将节的属性同时也加上IMAGE_SCN_MEM_EXECUTE标志就可以了,要是进一步将程序完成的话,当单个节的空隙大小不够的时候,也可以将附加代码分块插入多个节中,不过这时附加代码中就必须考虑在执行前将代码重新拼装在一起这个步骤了。

Part 3.2在节表中加入一个新的节表项目,节表项中的VirtualSize,VirtualAddress,SizeOfRawData,PointerToRawData,Characteristics和Name1字段需要被设置。其中Name1中的名称被设置为“.adata”;Characteristics字段中的标志被设置为可执行和可读写,其他几个字段值的算法如下(下面的“上一节”指原始PE文件的最后一节):

●  PointerToRawData=(上一节的PointerToRawData)+(上一节的SizeOfRawData)

●  SizeOfRawData=附加代码的长度按FileAlignMent值对齐

●  VirtualAddress=(上一节的VirtualAddress)+(上一节的VirtualSize按SectionAlignMent的对齐值)

●  VirtualSize=附加代码的长度按SectionAlignMent值对齐

其中的对齐算法是用_Align子程序来完成的。在这一部分中,程序还修正了文件头中的SizeOfCode和SizeOfImage的值。如果SizeOfImage的值不被修正的话,Windows将无法装入修改后的PE文件,报的错误为“这不是一个有效的Win32可执行文件”。Part3.2的最后,程序将附加代码写到文件的最后,由于附加代码的长度还没有按FileAlignment的值对齐,所以程序再次使用SetFilePointer函数将文件指针移动到对齐后的位置并用SetFileEnd函数将文件长度扩展到这里。无论是Part3.1还是Part3.2的最后,程序将新增代码在文件中位置和在内存中的位置分别保存在@dwAddCodeFile和@dwAddCodeBase变量中以备后用。

Part4 修改PE文件头中的文件入口地址,并将修改后的整个PE文件头写入到新文件中。

Part 5将原始PE文件的入口地址取出,和附加代码的入口地址计算得出“jmp 原入口地址”这条指令中的二进制码值,并将这个值写到附加代码的对应位置中。JMP指令的编码方式是由一个0e9h字节加上指令执行后的EIP的修正值,也就是说,当JMP指令的下一句指令地址是addr1,而跳转的目标地址是addr2的放在,那么0e9h字节后的双字的值就是addr2-addr1,所以下面的几句就是将指令改成"JMP原入口址"的样子:
push [esi].OptioinalHeader.AddressOfEntryPoint
   pop @dwEntry
   mov eax,@dwAddCodeBase
add eax,(offset _ToOldEntry--offset APPEND_CODE+5)
sub @dwEntry,eax
在指令列执行前,ESI指向PE文件头,@dwAddCodeBase中保存有新增代码被装载到内存后的起始地址,所以由(1)标出的指令执行后,EAX的值是_ToOldEntry后面的5个字节的位置,或者说是JMP XXXXXXXX后一条指令的位置,也就是上面算式中的addr1。@dwEntry中的原始值是可执行文件原来的入口地址,也即addr2,指令(2)执行后,@dwEntry中的值就是addr2-adr1b ,这就是需要填入_dwOldEntry位置的数据。
接下来程序用SetFilePointer函数将文件指针移动到新增代码中的_dwOldEntry位置,并将上面计算出的结果写入文件中。

Part 6进行扫尾工作,如释放内存、关闭文件和显示成功信息等。至此,程序的所有功能就完成了。
写到这里,基本上也对现了承诺,在这七天的时间里陪大家重新温习了一下PE的相关知识,最近在学习内核和驱动方面的知识,希望想交流的朋友能加我的看雪ID号或QQ号,谢谢!
前面有朋友说作个链接,在这里作了一个小链接,以方便大家参考学习,温故而知新!!!
国庆PE总复习(一)(二)       http://bbs.pediy.com/showthread.php?t=121488
国庆PE总复习(三)            http://bbs.pediy.com/showthread.php?t=121595
国庆PE总复习(四)            http://bbs.pediy.com/showthread.php?t=121672
国庆PE总复习(五)            http://bbs.pediy.com/showthread.php?t=121695
国庆PE总复习(六)       http://bbs.pediy.com/showthread.php?t=121748

国庆长假结束了,希望大家在看雪都能找一个属于自己的位置,早日成为看雪上的技术牛人!!  
置换矩阵、转置矩阵以及向量空间、子空间 置换矩阵、转置矩阵以及向量空间、子空间导读置换矩阵 导读 MIT矩阵分析课程之后的的二篇笔记。 阅读详情

相关推荐

STM32+Air724UG基于阿里云物联网平台的OTA远程升级实战指南

本文提供了一套基于STM32微控制器与Air724UG 4G Cat.1通信模组,通过阿里云物联网平台实现OTA远程升级的完整实战指南。文章详细阐述了BootLoader与用户程序的分工、云端配置、固件下载与烧写流程,并分享了关键代码实现与常见问题排查方法,帮助开发者快速为物联网设备部署稳定可靠的无线升级功能。

weixin_28285943的博客 514

在PE文件中添加可执行代码

原理: 将代码添加到目标PE文件中有两种方法,一种是将代码插入到原文件代码的节的空隙中,还有一种办法是在节的末尾新增一个节,当然这两种方法都有局限性,第一个必须得保证节与节之间有足够的空隙插入我们的代码,第二个必须得保证节表末尾有足够的空隙让我们再插入一个节表。 我们既然要插入代码,那肯定就是为了执行。所以我们得让文件先执行我们的代码再去执行原来的代码,因此我们需要将原来的执行入口保存在我们的...

weixin_43575859的博客 1557

QuickFix Java 讲解(一)概述、下载方法,和协议内容

本系列力求手把手教你怎样利用QuickFix Java 搭建自己的 FIX 协议收法平台,以及其中的注意事项。 所有源码的地址(免费): https://github.com/zongzhec/QuickFixPractise 1. 什么是FIX,QuickFix,Quick Java? 1.1. 什么是FIX 官网以及其他翻译自官网的帖子都太官方了,这里我们设想一个情景: 你是一个交易员,现在客户打电话来让你下个单买点股票。你啪啪啪的在你的机器上熟练地输入了下单指令。系统显示:成...

4573

在PE文件上添加执行代码

原理 1,将添加的代码写到目标PE文件中,可以把这个代码插入原代码所处的的section的空隙中,也可以通过添加一个新的section附在原文件的尾部 2,PE文件原来的入口地址必须被保存在添加的代码中,这样,这段代码执行完以后可以转移到原始文件处执行 3,PE文件头中的入口地址需要被修改,指向新添加代码中的入口地址 4,PE文件头中的一些值需要根据情况做相应的修改,以符合修改后的PE文件

liumengcheng的专栏 2902

BGE-M3 文本情感分类实战:预训练模型微调,导出ONNX并测试

本文介绍了如何基于BGE-M3开源模型实现一个简单的二分类情感分析任务,展示了NLP中经典的“预训练 + 微调”范式。首先,加载预训练模型和分词器,并构建分类头整合到模型中。接着,进行数据预处理并构建DataLoader。然后,定义优化器、损失函数和训练循环,进行模型微调。微调过程中,可以选择基于[CLS] token的单特征提取或基于池化策略的多特征融合。最后,通过验证与推理评估模型性能,并导出ONNX模型以适应生产环境。BGE-M3在语义表征任务中表现优异,适合作为文本分类、语义搜索等下游任务的基础模型

大鹏的专栏 1404

从“氛围编程“到“智能体工程“:AI编程范式的认知跃迁与工程实践

摘要: 2026年,ClaudeCode负责人鲍里斯·切尔尼提出用“智能体工程”(Agent Engineering)替代流行的“氛围编程”(Vibe Coding),认为后者过于主观,无法体现AI编程的系统化与工程化本质。智能体工程强调记忆系统、工具编排、任务分解、可观测性等核心技术,将AI编程从“玄学辅助”升级为可度量、可规模化的工程学科。本文通过对比术语差异、构建知识体系,并结合代码实例(如带记忆系统的代码生成Agent和可观测性系统),展示了智能体工程的实际应用。行业案例(如ClaudeCode的并

yyq916的博客 1061

国庆PE总复习合集

【转自看雪】(1-4) (2010-11-19 11:09:37) 转载▼ 标签: 杂谈 分类: PE相关 标 题: 【原创】国庆PE总复习合集 作 者: 熊猫正正 时 间: 2010-10-01,17:44:49 链 接: http://bbs.pediy.com/showthread.php?t=121488 【1】

wxl1986622的专栏 1277

【软件逆向】带壳带反调试找flag教程(VMProtect3.0+X64dbg+ScyllaHide)

【软件逆向】带壳带反调试找flag教程(VMProtect 3.0+X64dbg+ScyllaHide)

zhangjiuding的博客 7510

国庆PE总复习(1-7)合集

本来打算国庆去上海观看世博会最后精彩去的,因为有些事要做,所以就没去了,还是在家呆着吧,给大家重温一下PE文件的知识,国庆七天每天都会更新,我会从最基础的PE理论再结合编程给大家详细讲解,参考书籍主要两本:看雪《加密与解密》第三版,老罗《Windows下32位汇编程序设计》第二版,如果大家看了我的PE文件详解能给大家一点点小的帮助,我想国庆的努力也没白费,呵呵~~其实论坛上,这样的文章也讲了不少,

M-先生 999

AES-GCM加密认证模式:原理、优势与实战应用

在数据安全领域,加密与认证是保障信息机密性和完整性的两大基石。加密确保数据内容不被窃取,而认证则验证数据来源的真实性和传输过程中是否被篡改。传统的加密后认证方案如AES-CBC+HMAC需要串行处理,性能开销较大。GCM(伽罗瓦/计数器模式)作为一种认证加密(AEAD)方案,巧妙地将AES-CTR加密与基于伽罗瓦域的GHASH认证并行结合,实现了单次数据遍历即可同时完成加密和认证,大幅提升了处理效率。其核心价值在于高性能、支持附加认证数据(AAD)以及标准化程度高,广泛应用于TLS协议、无线网络安全和云存储

weixin_28622215的博客 313

用 ABAP 的 AMDP 把计算推到数据库:概念、场景、以及可运行示例

摘要:AMDP(ABAP Managed Database Procedures)是SAP HANA环境下优化ABAP性能的关键技术,通过将数据计算逻辑推送到数据库端执行,减少数据传输和应用层处理开销。其核心价值在于利用HANA的集合运算能力,适用于大数据量聚合、窗口函数等复杂计算场景。AMDP不是通用解决方案,而是针对CDS无法高效处理的特定性能瓶颈的优化手段。典型应用包括财务统计、零售分析等需要高性能数据处理的业务场景。本文通过概念解析、性能逻辑推导和实际代码示例(包括直接调用的AMDP Procedu

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 263

【原创】国庆PE总复习(三)

首先向大家表示道歉,昨天因为有朋友自远方来,玩到了晚上十二点才回来,写完文章一看表都已经五点了,可能让有些等着学习的朋友们等久了,呵呵,今天还好,又有时间和大家一起共同学习了,希望在国庆七天的长假里,朋友们都能我和一起对PE文件有一个更加深切的理解!! 上一篇给大家讲到了输入表,输入表真的很重要,在很多方面都有应用,如免杀,壳的编写,病毒方面输入表都很重要,因此我作为单独的一篇给大家讲解了一

M-先生 811

C#里使用NModbus来创建一个TCP从站服务

摘要:本文介绍了一个用C#实现的Modbus TCP从站服务器,基于.NET Framework 4.6.1开发。代码使用NModbus库创建TcpListener和ModbusSlaveNetwork,支持设置IP地址、端口号和从站ID。服务器实现了数据存储初始化功能(包括线圈和保持寄存器),并提供了启动/停止操作的UI界面和日志记录功能。该解决方案适用于需要调试Modbus TCP协议的场景,特别适合无法升级到更高版本.NET Framework的环境。

大坡3D软件开发 478

【原创】国庆PE总复习(二)

上一篇文章主要给大家介绍了一下PE文件的各个结构的数据结构,以及了解PE结构中的几个重要概念,下面我重点给大家讲解区块表中的四个非常重要的区块:输入表,输出表,重定位表,资源,我会一个一个详解给大家讲解的,请跟随我的进度慢慢消化学习!!如果你对PE中的这四个区块都很了解,请跳过PE总复习二,谢谢~~以免浪费您保贵的时间! 输入表 可执行文件使用来自于其他DLL的代码或数据时,称为输入。当P

M-先生 985

STM32F407中文手册

STM32f407中文手册 本参考手册面向应用开发人员,提供有关使用 STM32F405xx/07xx、STM32F415xx/17xx、 STM32F42xxx 和 STM32F43xxx 微控制器存储器与外设的完整信息。STM32F405xx/07xx、STM32F415xx/17xx、STM32F42xxx 和 STM32F43xxx 构成一个微 控制器系列,各产品具有不同的存储器大小、封装和外设。有关订购信息以及器件的机械与电气特性,请参见数据手册。有关 ARM Cortex™-M4F 内核的信息,请参见《Cortex™-M4F 技术参考手册》。

怀旧版电脑GBC模拟器

再现原版GBC掌机的界面,电脑运行连游戏界面都一样大小,很怀旧,很真实,(英文版)强烈推荐,很小的文件,很难才找到的。

【原创】国庆PE总复习(五)

昨天给大家把《加密与解密》第三版上的PE工具源代码给大家分析了一下,感觉用VC去写PE工具还是不能很好的休现PE的文件内部结构,可能是封装了一些函数的吧,上一篇文章主是为那些没有学过Win32汇编的朋友们准备的,下面我开始重注讲解用Win32汇编进行PE工具的编程,你绝对会对PE文件工具有一个全新的认识,因为用汇编去写PE工具更能体现PE文件的内部组成结构,这是我个人的观点,不代表所有人!! 如

M-先生 1055

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

【原创】国庆PE总复习(四)

昨天感冒了,今天鼻子也不是很舒服,不过不想失约,承诺了就一定要做下去,七篇文章一定会在国庆结束前全部献上,不管评论是好与坏,至少我懂得做为男人既已承诺,就一定要现!! 前面几篇文章中我已经对PE文件的结构作了一个比较详细的讲解,如果大家还有不清楚的,请参考相关资料,谢谢,下面我开始讲解PE文件编程方面的知识~~这样理论结合实际,我想大家会有一个更深切的理解! 首先我想对《加密与解密

M-先生 596

Outlook Express 6 (OE6) for Win7/Win8/Win10

英文版 Outlook Express6.0, 兼容Win7/Win10/WinXP,解压就可用。

国庆PE总复习(六)

前面有一个朋友给我留言说要搞个链接,好的,等我把第七篇文章写完之后,会在最后一篇文章的最后把前面的六篇文章的地址都链接上去,也样第一方便了大家,也同时方便了自己阅读学习,给人方便,就是给自己方便!! 参考文献《Win32汇编语言程序设计》第二版 前面的五篇文章,所讲的都是对PE文件的结构进行了分析,以及对PE工具的编写进行了介绍,但在实际的应用中还未有涉及到很多方面的内容,其实学好PE在很

M-先生 655

Linux wakeup source深度解析:唤醒源机制与功耗排查实战

Linux电源管理是嵌入式开发和驱动开发中的核心难点,系统“睡不下去”或“待机功耗异常”往往与内核里的wakeup source(唤醒源)密切相关。wakeup source作为电源管理子系统中的事件级抽象,记录设备唤醒事件的发生、处理与统计,并通过pm_stay_awake、pm_wakeup_event等接口与suspend流程深度配合。理解其数据结构与激活路径,有助于开发者掌握autosleep、wakelock以及/sys/power/wakeup_count握手机制的原理,从而高效排查功耗问题。本

weixin_34353714的博客 328

2012国庆回来复习 笔记整理

内存分配有三种方式 1静态分配,或者说静态存储区域分配。 就是说内存在程序编译的时候就已经分配好了,这款内存在程序整个运行期间都存在,如全局变量,static变量。 2自动分配,或者说是在栈上分配。 比如在执行函数时,函数内局部变量的存储单元就是在栈上创建的,函数执行结束时,这些存储单元自动被释放。 3堆上分配,或者说是动态内存分配。 那些用malloc和new申请来的内存,就是...

weixin_30852419的博客 84

【逗老师带你考IE】如何快速通过HCIE考试

笔者最近在工作之余,参加了HCIE考试,考试结果还很不错,而且算上准备时间,其实只用了不到50天。 本文献给其他准备HCIE的同学,希望能对大家的备考之路,提供帮助。 先上时间轴 2020年08月28日,参加笔试考试,准备7天 2020年11月10日,参加实验考试,准备21天 2020年12月8日,参加面试考试,准备21天 最终成绩,全部一次通过。 一、作者背景 本人真名李自奇,花名逗老师。各位在百度搜索“逗老师 IT”,应该能看到不少我分享的博客。 我本科毕业与网络工程专业,毕业之后曾经在H3C、京东工

逗老师的博客 1万+

AIX的培训笔记

国庆的时候回公司参加了三天的AIX培训,以前一直是搞LINUX,第一次接触AIX,感觉挺好玩的,至少有一个菜单交互模式,比LINUX的好用得多当时也记了一些笔记的,但是来不及放到电脑上面。到现在都快一个多月了,忘得也快差不多了,...

congbian2246的博客 109

python中turtle画圆填充颜色_Python教程小小turtle绘图教学(一)

Hi!大家好!在之前中秋节/国庆的时候,我们带来了一篇简短的教程:我是如何用Python画出一个月饼的?有小朋友后台留言表示:“月饼画出来了,怎么画其他形状呢?我不会”别急,跟着我们的教程,带你了解如何使用turtle库,画出更多精彩图案!基础图形 - 多边形在绘制出多彩纷呈的图案之前,我们首先需要了解一些基本操作!首先你需要:Visual studiocodepython 3.X (...

weixin_33805938的博客 1万+

Python代码画喜羊羊怎么画_Python教程小小turtle绘图教学(一)

Hi!大家好!在之前中秋节/国庆的时候,我们带来了一篇简短的教程:我是如何用Python画出一个月饼的?有小朋友后台留言表示:“月饼画出来了,怎么画其他形状呢?我不会”别急,跟着我们的教程,带你了解如何使用turtle库,画出更多精彩图案!基础图形 - 多边形在绘制出多彩纷呈的图案之前,我们首先需要了解一些基本操作!首先你需要:Visual studiocodepython 3.X (...

weixin_39584405的博客 3242

python turtle 绘图_Python教程小小turtle绘图教学(一)

Hi!大家好!在之前中秋节/国庆的时候,我们带来了一篇简短的教程:我是如何用Python画出一个月饼的?有小朋友后台留言表示:“月饼画出来了,怎么画其他形状呢?我不会”别急,跟着我们的教程,带你了解如何使用turtle库,画出更多精彩图案!基础图形 - 多边形在绘制出多彩纷呈的图案之前,我们首先需要了解一些基本操作!首先你需要:Visual studiocodepython 3.X (...

weixin_39895096的博客 1317

第一份开发工作回顾

2021年6月毕业后,漂流了近10个月,终于找到了一份实习开发(Go语言)。记录试用期(2022.04 - 2022.10)如何入门互联网行业的。

啦啦啦我是马小哈,你是马大哈~ 668
上一篇: 国庆PE总复习(六)
下一篇: 驱动感染技术扫盲(C描述)
M-先生
博客等级 码龄17年 60粉丝 6原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值