前言
这两天分析了下Yoda Cryptor 1.2的源码和大家分享下
本来想分析 Yoda's Protect 1.02的,看了一下,代码是C语言的,不过那写界面的代码比用来实现主要功能的代码还要多,看得我晕了,就分析了Yoda Cryptor 1.2的ASM源码,比VC的那个简洁多了
我主要分析了CryptStuff.ASM和PER.ASM这两个文件,主要功能是在CryptFile这个函数。
分析过程可能会有错误,大家作为参考下就行了。
有一些变量的定义在其它的两个文件大家可以在附件中下载原始的源码对着来看 CryptStuff.ASM
;------ MACROS -----
PUPO
MACRO pSrc, pDest
PUSH pSrc
POP pDest
ENDM
;------ DEFINITIONS -------
DEPACKER_CODE_SIZE
equ (
offset DepackerCodeEnd -
offset DepackerCode)
CHECKSUM_SKIP_SIZE
equ 5
; (don't include the saved checksum itself in the checksum calculation)
TLS_BACKUP_ADDR
equ (
offset TlsBackupLabel -
offset DepackerCode)
CHECKSUM_ADDR
equ (
OFFSET ChecksumLabel -
OFFSET DepackerCode)
CRYPT_LOADER_SIZE_DB
EQU (
OFFSET LOADER_CRYPT_END -
OFFSET LOADER_CRYPT_START)
CRYPT_OEP_JUMP_SIZE
equ (
OFFSET OEP_JUMP_CODE_END -
OFFSET OEP_JUMP_CODE_START)
IT_SIZE
equ 060h
MAX_SECTION_NUM
equ 20
MAX_IID_NUM
equ 30
OEP_JUMP_ENCRYPT_NUM
equ ('y')
LOADER_CRC_CHECK_SIZE
equ (
OFFSET OEP_JUMP_CODE_START -
OFFSET DepackerCode)
VAR_PER_SIZE
EQU 030h
SEC_PER_SIZE
EQU 030h
;------- CONST --------
.const
szDone
db "File encrypted successfully !" ,0
szDoneCap
db ":)" ,0
szFileErr
db "File access error :(" ,0
szNoPEErr
db "Invalid PE file !" ,0
szNoMemErr
db "Not enough memory :(" ,0
szFsizeErr
db "Files with a filesize of 0 aren't allowed !" ,0
szNoRoom4SectionErr
db "There's no room for a new section :(" ,0
szSecNumErr
db "Too many sections !" ,0
szIIDErr
DB "Too much ImageImportDescriptors !" ,0
ALIGN_CORRECTION
dd 01000h
; this big value is e.g. needed for WATCOM compiled files
DEPACKER_SECTION_NAME
dd ('Cy')
szKernel
db "KeRnEl32.dLl" ,0
szLoadLibrary
db "LoadLibraryA" ,0
szGetProcAddress
db "GetProcAddress" ,0
;------- DATA ---------
.data
pMap
dd 0
dwBytesRead
dd 0
dwBytesWritten
dd 0
pMem
dd 0
dwFsize
dd 0
dwOutPutSize
dd 0
dwNewFileEnd
dd 0
dwNTHeaderAddr
dd 0
dwSectionNum
dd 0
dwNewSectionRO
dd 0
dwOrgITRVA
dd 0
hFile
dd 0
;------- CODE ---------
.code
CryptFile
PROC szFname : LPSTR, hDlg : HWND,dwProtFlags :
DWORD
assume
fs : nothing
CALL InitRandom
;这里初始化随机数种子主要利用了 GetTickCount
;----- MAP THE FILE -----
;;下面主要是分配内存空间并把程序读取到内存中,并且检测PE文件的有效性
;**********************************************************************************************************
invoke CreateFile,szFname,GENERIC_WRITE + GENERIC_READ,FILE_SHARE_WRITE + FILE_SHARE_READ,\
NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0
cmp eax ,INVALID_HANDLE_VALUE
jz FileErr
mov hFile,
eax
invoke GetFileSize,hFile,0
;获取文件大小
.IF eax == 0
push hFile
call CloseHandle
jmp FsizeErr
.ENDIF
mov dwFsize,
eax
mov eax ,dwFsize
add eax ,IT_SIZE
;IT_SIZE 输入表大小
add eax ,DEPACKER_CODE_SIZE
;DEPACKER_CODE_SIZE 解码代码的大小
add eax ,ALIGN_CORRECTION
mov dwOutPutSize,
eax ;把上面的加起来得到要输出的文件大小
push eax
push GMEM_FIXED + GMEM_ZEROINIT
call GlobalAlloc
;分配内存空间
.IF eax == NULL
push hFile
call CloseHandle
jmp MemErr
.ENDIF
mov pMem,
eax
invoke ReadFile,hFile,pMem,dwFsize,
offset dwBytesRead,NULL
;把文件的内容读到内存中
; ----- check the PE Signature and get some needed values -----
mov edi ,pMem
.IF word ptr [
edi ] != 'ZM'
;检验 IMAGE_DOS_SIGNATURE
push pMem
call GlobalFree
push hFile
call CloseHandle
jmp PEErr
.ENDIF
add edi ,[
edi +3Ch]
.IF word ptr [
edi ] != 'EP'
;检验IMAGE_NT_SIGNATURE
push pMem
call GlobalFree
push hFile
call CloseHandle
jmp PEErr
.ENDIF
;;下面是保存程序的一些重要信息
;*****************************************************************************************************************
mov dwNTHeaderAddr,
edi
assume
edi :
ptr IMAGE_NT_HEADERS
push [
edi ].OptionalHeader.DataDirectory[SIZEOF IMAGE_DATA_DIRECTORY].VirtualAddress
;获取程序的输入表RVA
pop dwOrgITRVA
push word ptr [
edi ].FileHeader.NumberOfSections
;获取程序的区段数
pop word ptr dwSectionNum
.IF dwSectionNum > MAX_SECTION_NUM
;区段数不能大于20
JMP SecNumErr
.ENDIF
push [
edi ].OptionalHeader.AddressOfEntryPoint
;保存程序OEP
pop dwOrgEntryPoint
push [
edi ].OptionalHeader.ImageBase
;保存程序镜像基址
pop dwImageBase
;----- DELETE Bound Import & IAT DIRECTORIES -----
;**********************************************************************************
;下面销毁 IMAGE_BOUND_IMPORT_DESCRIPTOR
XOR EAX ,
EAX
MOV ECX , 4
LEA EDI , [
EDI ].OptionalHeader.DataDirectory[11 *SIZEOF IMAGE_DATA_DIRECTORY].VirtualAddress
;获取 IMAGE_BOUND_IMPORT_DESCRIPTOR RVA
assume
edi : nothing
DirDelLoop:
STOSD
LOOP DirDelLoop
;用 0 填充掉 IMAGE_BOUND_IMPORT_DESCRIPTOR
;*******************************************************************
;加密DLL 和 API 名字 并且销毁 IID
PUSH dwOrgITRVA
PUSH pMem
CALL RVA2Offset
;RVA2Offset 把RVA转成文件的偏移,这里得到原程序输入表的偏移
PUSH EAX
PUSH pMem
CALL ProcessOrgIT
;加密原程序输入表
OR EAX ,
EAX
.IF ZERO?
;是否加密成功
PUSH pMem
CALL GlobalFree
PUSH hFile
CALL CloseHandle
JMP IIDErr
.ENDIF
;;增加一个新节******************************************************
push pMem
call AddSection
.IF eax == 0
push pMem
call GlobalFree
push hFile
call CloseHandle
jmp NoRoom4SectionErr
.ENDIF
;创建壳自己的输入表**************************************88
xchg eax ,
esi ;ESI 指向新节的节头
assume
esi :
ptr IMAGE_SECTION_HEADER
mov eax ,[
esi ].PointerToRawData
MOV dwNewSectionRO,
EAX
add eax ,pMem
push [
esi ].VirtualAddress
push eax
call AssembleIT
;开始创建壳的输入表
;处理Tls表。把Tls复制到壳中*************************
push [
esi ].VirtualAddress
push pMem
call ProcessTlsTable
;加密节表**************************************
pushad
; generate PER
;这个PER是什么意思我也不知道,这里保留原注释
;下面是产生加密和解密函数,同时会生成花指令。是动态生成的。每一次加壳生成的加解密函数都不相同
;作者在文件中注释为此多态(PS:原来多态就是这样子的)。
;生成的函数用来后面的加密节代码,和解密节代码、
PUSH SEC_PER_SIZE
PUSH OFFSET SecDecryptBuff
;解密函数保存在这
PUSH OFFSET SecEncryptBuff
;加密函数保存在这
CALL MakePER
;生成加解官函数。
; encrypt !
mov eax ,pMem
mov ebx ,0
call CryptPE
;加密各个节
popad ;***************************************************************
; ----- UPDATE PE HEADER -----
mov edi ,dwNTHeaderAddr
assume
edi :
ptr IMAGE_NT_HEADERS
; edi -> pointer to PE header
push [
esi ].VirtualAddress
;壳段的RVA(刚开始的地方保存的就是壳的输入表)压栈
pop [
edi ].OptionalHeader.DataDirectory[SIZEOF IMAGE_DATA_DIRECTORY].VirtualAddress
;把PE头的的输入表指向壳的输入表
; EntryPoint...
mov eax ,[
esi ].VirtualAddress
add eax ,IT_SIZE
;壳段RVA + 输入表的大小,后面的就是壳的代码
mov [
edi ].OptionalHeader.AddressOfEntryPoint,
eax ;OEP修改为壳的代码
; SizeOfImage ...
mov eax ,[
esi ].VirtualAddress
;基址
add eax ,[
esi ].Misc.VirtualSize
;加上虚拟大小得到程序在内存的映像大小
mov [
edi ].OptionalHeader.SizeOfImage,
eax ;修改SizeOfImage
; save protection flags...
push dwProtFlags
pop PROTECTION_FLAGS
;保存加壳标志
assume
esi : nothing
assume
edi : nothing
; ----- CALCULATE THE NEW EOF -----
;计算文件加壳后大小
mov eax ,dwNewSectionRO
add eax ,IT_SIZE
add eax ,DEPACKER_CODE_SIZE
mov dwNewFileEnd,
eax
; ----- COPY LOADER CODE TO FILE MEMORY & DO CHECKSUM STUFF ------
;复制壳代码到新节中
mov edi ,dwNewSectionRO
add edi ,IT_SIZE
add edi ,pMem
mov esi ,
offset DepackerCode
mov ecx ,DEPACKER_CODE_SIZE
rep movsb
;----- ENCRYPT OEP JUMP CODE -----
;下面的代码把跳到要OEP的这段代码加密
MOV EDI , pMem
ADD EDI , dwNewSectionRO
ADD EDI , IT_SIZE
ADD EDI , (
OFFSET OEP_JUMP_CODE_START -
OFFSET DepackerCode)
MOV ESI ,
EDI ;ESI 指向JUMP_CODE_START
MOV ECX , CRYPT_OEP_JUMP_SIZE
XOR EBX ,
EBX
OepJumpEncryptLoop:
;开始循环加密
LODSB
ROR AL , 2
ADD AL ,
BL
XOR AL , OEP_JUMP_ENCRYPT_NUM
STOSB
INC EBX
LOOP OepJumpEncryptLoop
;----- ENCRYPT LOADER -----
; generate PER
PUSH VAR_PER_SIZE
MOV EAX , pMem
ADD EAX , dwNewSectionRO
ADD EAX , IT_SIZE
ADD EAX , (
OFFSET VarDecryptBuff -
OFFSET DepackerCode)
PUSH EAX ;得到VarDecryptBuff的VA
PUSH OFFSET VarEncryptBuff
CALL MakePER
;产生加解密函数
; encryption !
MOV EDI , pMem
ADD EDI , dwNewSectionRO
ADD EDI , IT_SIZE
ADD EDI , (
OFFSET LOADER_CRYPT_START -
OFFSET DepackerCode)
;EDI = LOADER_CRYPT_START的地址
MOV ECX , CRYPT_LOADER_SIZE_DB
;大小
MOV ESI ,
EDI
@@VarEncryptionLoop:
;循环加密
LODSB
VarEncryptBuff
DB VAR_PER_SIZE DUP (0)
;MakePER产生的加密函数
STOSB
LOOP @@VarEncryptionLoop
;----- CALCULATE CHECKSUM -----
;计算文件校验和
mov eax ,pMem
mov ecx ,dwNewFileEnd
sub ecx ,CHECKSUM_SKIP_SIZE
call GetChecksum
mov dwOrgChecksum,
eax ;保存文件校验和
;----- PASTE CHECKSUM ------
MOV EAX , pMem
ADD EAX , IT_SIZE
ADD EAX , dwNewSectionRO
ADD EAX , CHECKSUM_ADDR
MOV EDX , dwOrgChecksum
MOV DWORD PTR [
EAX ],
EDX ;保存校验和到壳段
; ----- WRITE FILE MEMORY TO DISK -----
;将加壳后的程序写到文件
invoke SetFilePointer,hFile,0,NULL,FILE_BEGIN
invoke WriteFile,hFile,pMem,dwOutPutSize,
offset dwBytesWritten,NULL
; ------ FORCE CALCULATED FILE SIZE ------
;这是干什么的?????
invoke SetFilePointer,hFile,dwNewFileEnd,NULL,FILE_BEGIN
invoke SetEndOfFile,hFile
invoke MessageBox,hDlg,
offset szDone,
offset szDoneCap,MB_ICONINFORMATION
; ----- CLEAN UP -----
push pMem
call GlobalFree
push hFile
call CloseHandle
@@Exit:
ret
;加密完成*******************************************************************************************************************
;**************************************************************************************************************************** ;----- ERROR MESSAGES -----
MemErr:
mov eax ,
offset szNoMemErr
jmp ShowErr
PEErr:
mov eax ,
offset szNoPEErr
jmp ShowErr
FileErr:
mov eax ,
offset szFileErr
jmp ShowErr
NoRoom4SectionErr:
mov eax ,
offset szNoRoom4SectionErr
jmp ShowErr
FsizeErr:
mov eax ,
offset szFsizeErr
jmp ShowErr
SecNumErr:
mov eax ,
offset szSecNumErr
jmp ShowErr
IIDErr:
MOV EAX ,
OFFSET szIIDErr
JMP ShowErr
ShowErr:
invoke MessageBox,hDlg,
eax ,
offset szErr,MB_ICONERROR
jmp @@Exit
CryptFile
ENDP
;--------- functions -----------------
; esi = CryptStart
; ecx = CryptSize
EncryptSec:
;加密函数
mov edi ,
esi
SecEncryptLoop:
LODSB
SecEncryptBuff
DB SEC_PER_SIZE DUP (0)
;MakePER 函数生成的加密函数代码放在这里,进行加密
STOSB ;把加密后的代码写回去
LOOP SecEncryptLoop
RET ;*********************************************************************************************************
;添加一个区段函数
; 返回值:
; 0 - 空间不足
; 1 - 添加节失败,可能文件已经加壳
; 否则: 返回一个指向新添加节的 IMAGE_SECTION_HEADER 的指针
;********************************************************************************************************
AddSection
PROC USES edi esi ebx ecx edx , pMem_ : LPVOID
LOCAL dwSecNum :
DWORD
mov edi ,pMem_
add DWORD PTR edi ,[
edi +03Ch]
assume
edi :
ptr IMAGE_NT_HEADERS
; edi指向PE头
;检查是否有足够的空间来添加新节
xor eax ,
eax
mov ax ,[
edi ].FileHeader.NumberOfSections
;区段数
mov dwSecNum,
eax
mov ecx ,SIZEOF IMAGE_SECTION_HEADER
imul eax ,
ecx ; eax = 所有Section Header 的大小的和
add eax ,SIZEOF IMAGE_SECTION_HEADER
; 加上一个区段的大小
mov ecx ,
edi ; ecx = PE头
sub ecx ,pMem_
; 减去pMem 得到DOS头大小
add ecx ,
eax ; 加上新的总 SectionHeader 的大小
add ecx ,0F8h
; 加上PE头的大小得 ecx = sizeof (DOS头 + Section Header +PE Header)
.IF ecx > [
edi ].OptionalHeader.SizeOfHeaders
;如果ecx < SizeOfHeaders 则有足够空间
xor eax ,
eax
jmp @@ExitProc_AS
.ENDIF
; create a new section
mov esi ,
edi
add esi ,0F8h
;esi加上PE头大小,此时 esi 指向 节表
assume
esi :
ptr IMAGE_SECTION_HEADER
mov edx ,dwSecNum
sub edx ,1
;区段数减去1,下面的repeat循环用到的计数器
;下面循环把区段添加一个可写属性
.REPEAT
mov eax ,[
esi ].Characteristics
;得到区段的属性
or eax ,080000000h
;or 080000000,添加可写属性
mov [
esi ].Characteristics,
eax ;更新区段属性
add esi ,SIZEOF IMAGE_SECTION_HEADER
;指向下一个区段
dec edx
.UNTIL edx == 0
;循环,循环结束里,ESI指向最后一个区段
; 开始添加区段
mov edx ,
esi ;EDX指向最后一个区段
add edx ,SIZEOF IMAGE_SECTION_HEADER
; EDX指向新的区段
assume
edx :
ptr IMAGE_SECTION_HEADER
; VirtualAddress...
mov eax ,[
esi ].VirtualAddress
;最后一个区段的RVA
add eax ,[
esi ].Misc.VirtualSize
;加上大小得到未对齐时区段末尾
push 01000h
;对齐大小
push eax ;
call PEAlign
;对齐,得到对齐后区段末尾,就是新节的开始
mov [
edx ].VirtualAddress,
eax ;新节的开始
; VirtualSize..
mov [
edx ].Misc.VirtualSize,02000h
;新节的大小为2000h
; RawSize..
mov eax ,IT_SIZE
;新的开始加上壳的输入表大小
add eax ,DEPACKER_CODE_SIZE
;加上壳代码的大小得到新区段的大小
mov [
edx ].SizeOfRawData,
eax ;写入SizeOfRawData
; Section name
lea eax ,[
edx ].Name1
;写入区段名
push DEPACKER_SECTION_NAME
pop [
eax ]
MOV DWORD PTR [
EAX +4],0
; Characteristics
mov [
edx ].Characteristics,0E00000E0h
;写入新区段属性
; RawOffset
mov eax ,[
esi ].PointerToRawData
;最后一个区段的偏移
add eax ,[
esi ].SizeOfRawData
;加上大小,指向还没对齐的段末尾
push 0200h
push eax
call PEAlign
;对齐,对到新节的偏移
mov [
edx ].PointerToRawData,
eax ;写入新节偏移
mov eax ,
edx ; eax =新节的偏移,作为返回值
; update the PE header
inc [
edi ].FileHeader.NumberOfSections
;区段数加1
assume
edx : nothing
assume
esi : nothing
assume
edi : nothing
@@ExitProc_AS:
ret
AddSection
ENDP ;***************************************************************************************8
;函数功能:创建壳自己的输入表,里面包含壳要用到的函数,LoadLibraryA,GetProcAddressA
;参数: pAdress4IT 壳输入表的 地址,
; dwNewSectionVA 壳段的VA
;反回值: 无
;***************************************************************************************8
AssembleIT
PROC USES ebx ecx edx esi edi , pAddress4IT : LPVOID, dwNewSectionVA :
DWORD
mov esi ,pAddress4IT
; esi -> base of the new IT
; Zero the memory for the new IT
mov eax ,pAddress4IT
mov ecx ,IT_SIZE
ZeroMem:
;用 0 初始化
mov byte ptr [
eax ],0
inc eax
loop ZeroMem
; build a new,nice ImportTable :)
mov ebx ,
esi
mov eax ,SIZEOF IMAGE_IMPORT_DESCRIPTOR
xor edx ,
edx
mov ecx ,2
;壳的输入表中包含 2 个 IID
mul ecx
add ebx ,
eax ;ebx 指向IID 末尾
assume
esi :
ptr IMAGE_IMPORT_DESCRIPTOR
mov eax ,
ebx
sub eax ,
esi ;eax 等于 2 个IID的大小 ,最后一个 IID 是全 0,代码 IID结束
add eax ,dwNewSectionVA
;eax 等于 IID 末尾的RVA
mov [
esi ].Name1,
eax ;笫一个 IID 的DLL 名字指向了 IID末尾
push esi
mov esi ,
offset szKernel
;szKernel 指向 "Kernel32.dll"
mov edi ,
ebx
.REPEAT ;把 "Kernel32.dll" 填充到IID 末尾
lodsb
stosb
.UNTIL byte ptr [
esi ] == 0
pop esi
mov ebx ,
edi
inc ebx ;在"Kernel32.dll" 末尾空出一字节,是IMAGE_THUNK_DATA数组
mov eax ,
ebx
sub eax ,
esi
add eax ,dwNewSectionVA
;RVA,这里保存一个IMAGE_THUNK_DATA
mov [
esi ].FirstThunk,
eax
mov edx ,
ebx
add edx ,10
;edx = IMAGE_IMPORT_BY_NAME
mov eax ,
edx
sub eax ,
esi
add eax ,dwNewSectionVA
;IMAGE_IMPORT_BY_NAME VA
mov [
ebx ],
eax
add edx ,2
;Hint
push esi
mov esi ,
offset szLoadLibrary
;"LoadLibraryA"
mov edi ,
edx
.REPEAT ;"LoadLibratyA" 填充到 edx.Name
lodsb
stosb
.UNTIL byte ptr [
esi ] == 0
pop esi
mov edx ,
edi
add ebx ,4
;下一个 IMAGE_THUNK_DATA
mov eax ,
edx ;"GetProcAddress"
sub eax ,
esi
add eax ,dwNewSectionVA
;VA
mov [
ebx ],
eax
add edx ,2
;Hint
mov esi ,
offset szGetProcAddress
mov edi ,
edx
.REPEAT ;填充"GetProcAddress"到edx.name
lodsb
stosb
.UNTIL byte ptr [
esi ] == 0
assume
esi : nothing
ret
AssembleIT
ENDP ;****************************************************************************************
;处理Tls表。把原程序的Tls复制到壳的代码中。并把数据上录的Tls地址修正到壳的Tls地址
;参数: pFIleMem 文件的基址
; CryptSectionVA 壳节基址VA
;返回值: 无
;****************************************************************************************
ProcessTlsTable
PROC USES edi ebx esi ecx , pFileMem : LPVOID, CryptSectionVA :
DWORD
LOCAL pTlsDirAddr : LPVOID
mov edi ,pFileMem
add edi ,[
edi +03Ch]
;EDI = PE头
assume
edi :
ptr IMAGE_NT_HEADERS
lea ebx ,[
edi ].OptionalHeader.DataDirectory[SIZEOF IMAGE_DATA_DIRECTORY * 9].VirtualAddress
;ebx = IMAGE_TLS_DIRECTORY32
mov pTlsDirAddr,
ebx
mov ebx ,[
ebx ]
;ebx 指向 Tls数据起始地址
assume
edi : nothing
cmp ebx ,0
; Tls 是否存在
jz ExitTlsFixProc
; get a RAW pointer to the tls table
push ebx
push pFileMem
call RVA2Offset
;计算 Tls 的偏移
cmp eax ,0
jz ExitTlsFixProc
mov esi ,pFileMem
add esi ,
eax ; esi 指向 Tls 的地址
;把 Tls 复制到 壳的代码中保存
mov edi ,
offset TlsBackup
mov ecx ,sizeof IMAGE_TLS_DIRECTORY32
rep movsb
;把Tls的地址指向壳的Tls
mov eax ,CryptSectionVA
add eax ,IT_SIZE
add eax ,TLS_BACKUP_ADDR
;基址加上输入表大小,再加上壳Tls基于解压代码的偏移得到Tls的地址
mov esi ,pTlsDirAddr
mov [
esi ],
eax ;修正地址
ExitTlsFixProc:
ret
ProcessTlsTable
ENDP
;*********************************************************************************************************************
;;处理加密输入表,函数会把 DLL的名字,API的名字加密保存真情为,并销毁原来的 IID
;参数
; pFileImage 基址
; pITBaseR0 输入表在文件中的偏移
;函数返回值
; 1:成功
; 0:IID结构太大
;*********************************************************************************************************************
ProcessOrgIT
PROC USES edi esi edx , pFileImage : LPVOID, pITBaseRO : LPVOID
LOCAL dwIIDNum :
DWORD
; clear the IIDInfo array
XOR EAX ,
EAX
MOV EDI ,
OFFSET IIDInfo
MOV ECX , SIZEOF IIDInfo
ClearArrayLoop:
STOSB
LOOP ClearArrayLoop
;用 0 初始化 IIDInfo数组
; get a random number ;产生一个随机数,后面会用来填充掉 IID
INVOKE GetTickCount
XOR EAX , (
"yoda" )
MOV EDX ,
EAX ; EDX -> stupid number :)
; start
MOV dwIIDNum, 0
MOV EDI ,pITBaseRO
ADD EDI ,pFileImage
ASSUME
EDI :
PTR IMAGE_IMPORT_DESCRIPTOR
;EDI指向笫一个 IID 结构
MOV ESI ,
OFFSET IIDInfo
ASSUME
ESI :
PTR sItInfo
;ESI 指向 一个sItInfo数组,主要用来保存IID的一些重要信心
.WHILE [
EDI ].Name1
;循环加密 DLL 名字
; too much IID's ?
INC dwIIDNum
.IF dwIIDNum == (MAX_IID_NUM)
XOR EAX ,
EAX
JMP POIT_Exit
.ENDIF
; save IID Infos ;保存 IID的重要信息
PUPO <[
EDI ].Name1>, <[
ESI ].DllNameRVA>
PUPO <[
EDI ].OriginalFirstThunk>, <[
ESI ].OrgFirstThunk>
PUPO <[
EDI ].FirstThunk>, <[
ESI ].FirstThunk>
;-> get dll pointer
PUSH [
EDI ].Name1
PUSH pFileImage
CALL RVA2Offset
ADD EAX , pFileImage
;得到 DLL 名字的地址
;-> crypt string
CALL EnDeCryptString
;加密 DLL 名字
;--- CRYPT API name strings ---
PUSH ESI
MOV ESI , [
EDI ].OriginalFirstThunk
.IF !
ESI ;如果 OriginalFirstThunk 不为空,则使用 OriginalFirstThunk。否则使用 FirstThunk
MOV ESI , [
EDI ].FirstThunk
.ENDIF
PUSH ESI
PUSH pFileImage
CALL RVA2Offset
MOV ESI ,
EAX
ADD ESI , pFileImage
;得到 IMAGE_THUNK_DATA
.WHILE DWORD PTR [
ESI ]
;得到 IMAGE_IMPORT_BY_NAME
MOV EAX , [
ESI ]
TEST EAX ,IMAGE_ORDINAL_FLAG32
;判断 Hint高位是否为1,为1则函数以序号引出,跳过此函数
JNZ SkipApiString
PUSH EAX
PUSH pFileImage
CALL RVA2Offset
OR EAX ,
EAX
JZ SkipApiString
ADD EAX , pFileImage
ADD EAX , 2
;跳过 Hint
CALL EnDeCryptString
;加密 API 名字
SkipApiString:
ADD ESI , 4
;指向下一个IMAGE_THUNK_DATA
.ENDW ;循环加密API名字
POP ESI
; 用随机数填充 IID
MOV [
EDI ].Name1,
EDX
MOV [
EDI ].OriginalFirstThunk,
EDX
MOV [
EDI ].FirstThunk,
EDX
MOV [
EDI ].TimeDateStamp,
EDX
MOV [
EDI ].ForwarderChain,
EDX
;EDI 指向下一个 IID,ESI指向下一个 sItInfo
ADD EDI ,SIZEOF IMAGE_IMPORT_DESCRIPTOR
ADD ESI ,SIZEOF sItInfo
.ENDW ;循环
ASSUME
ESI : NOTHING
ASSUME
EDI : NOTHING
XOR EAX ,
EAX
INC EAX ;返回 1代表加密成功
POIT_Exit:
RET
ProcessOrgIT
ENDP
;********************************************************************************************
;对齐函数
PEAlign
PROC USES ecx edx , dwTarNum :
DWORD , dwAlignTo :
DWORD
mov ecx ,dwAlignTo
mov eax ,dwTarNum
xor edx ,
edx
div ecx
cmp edx ,0
jz AlreadyAligned
inc eax
AlreadyAligned:
mul ecx
ret
PEAlign
ENDP
;******************************************************************************************** ;***********************************************************************************************
; 把一个RVA值转成在文件的偏移值
; Base - 映射基址
; dwITRVA - 要转换的RVA
; 失败返回0,成功返回转换后的偏移
;**********************************************************************************************
RVA2Offset
PROC USES ebx ecx edx , Base :
DWORD ,dwITRVA :
DWORD
mov eax ,Base
add eax ,[
eax +03Ch]
;eax指向PE头
invoke ImageRvaToSection,
eax ,Base,dwITRVA
;得到RVA所在区段的Section Header
test eax ,
eax ;是否成功
jz @@ExitProc
xchg eax ,
ebx
assume
ebx :
ptr IMAGE_SECTION_HEADER
mov eax ,dwITRVA
sub eax ,[
ebx ].VirtualAddress
;RVA - 区段虚拟地址
add eax ,[
ebx ].PointerToRawData
;加上区段在文件的偏移,得到RVA在文件的偏移
assume
ebx : nothing
@@ExitProc:
ret
RVA2Offset
ENDP
;**********************************************************************************************************************
;下面是壳的代码
DepackerCode:
pushad
; get base ebp
call CallMe
;重定位
CallMe:
pop ebp
sub ebp ,
offset CallMe
;----- DECRYPT LOADER VARIABLES -----
MOV ECX , CRYPT_LOADER_SIZE_DB
LEA EDI , [
EBP +
OFFSET LOADER_CRYPT_START]
MOV ESI ,
EDI
VarDecryptionLoop:
LODSB
VarDecryptBuff
DB VAR_PER_SIZE DUP (0)
;MakePER函数产生的解密函数。对下面的代码进行解密
STOSB
LOOP VarDecryptionLoop
LOADER_CRYPT_START:
;------ DETECT WinNT ------
MOV EAX , [
ESP +020h]
;判断是不是NT?什么原理?
INC EAX
JS NoNT
MOV DWORD PTR [
EBP +bNT], 1
NoNT:
;------ Get CRC OF LOADER CODE ------
LEA EAX , [
EBP +
OFFSET DepackerCode]
;计算校验和
MOV ECX , LOADER_CRC_CHECK_SIZE
CALL GetChecksum
MOV [
EBP +dwLoaderCRC],
EAX ;保存校验和
;----- SI Check 1 -----
MOV EAX , [
ebp +PROTECTION_FLAGS]
AND EAX , CHECK_SI_FLAG
jz SkipSICheck
; install SEH frame
LEA ESI ,[
EBP +SEH]
ASSUME
ESI :
PTR sSEH
LEA EAX , [
EBP +
OFFSET SICheck1_SP]
mov [
ESI ].SaveEip,
EAX
ASSUME
ESI : NOTHING
MOV EDI ,
EBP
LEA EAX , [
EBP +
OFFSET SehHandler1]
;取SehHandler1的偏移
XOR EBX ,
EBX
push EAX ;安装SEH
push FS :[
EBX ]
mov FS :[
EBX ],
ESP
; 0 - SI not found
; 1 - SI found
mov ebp , 04243484Bh
;?????
mov ax , 04h
JMP SM1
DB 0FFh
;花指令
SM1:
INT 3
;异常。跳到SehHandler1
SICheck1_SP:
;SaveEip
MOV EBP ,
EDI
; uninstall SEH frame
XOR EBX ,
EBX ;移除SEH
POP FS :[
EBX ]
ADD ESP , 4
;
.IF AL != 4
; exit
JMP SM2
DB 0E9h
;花指令
SM2:
popad
ret
.ENDIF
SkipSICheck:
;----- GET BASE API ADDRESSES -----
; find the ImageImportDescriptor and grab dll addresses
mov eax ,[
ebp +dwImageBase]
;获取镜像基址
add eax ,[
eax +03Ch]
;EAX = PE Header
add eax ,080h
;EAX = DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtuallAddress
mov ecx ,[
eax ]
;ECX = IID RVA
add ecx ,[
ebp +dwImageBase]
;IID VA
add ecx ,16
;IID.FirstThunk
mov eax ,
dword ptr [
ecx ]
;EAX = IMAGE_THUNK_DATA RVA
add eax ,[
ebp +dwImageBase]
;Va
mov ebx ,
dword ptr [
eax ]
;ebx 指向LoadLibraryA的地址
mov [
ebp +_LoadLibrary],
ebx ;保存LoadLIbraryA地址
add eax ,4
;下一个IMAGE_THUNK_DATA_VA
mov ebx ,
dword ptr [
eax ]
;GetProcAddressA地址
mov [
ebp +_GetProcAddress],
ebx ;保存
;**********************************************************************************
;获取需要用的函数地址
; get kernel base
lea eax ,[
ebp +
offset szKernel32]
;"Kernel32.dll"
push eax
call [
ebp +_LoadLibrary]
;"加载Kernel32.dll"
mov esi ,
eax ; esi = Kernel32 handle
MOV [
EBP +dwKernelBase],
EAX ;保存 Kernel32 handle
;-> GetModuleHandle
lea eax ,[
ebp +szGetModuleHandle]
call DoGetProcAddr
;获取 GetModuleHandle 地址
mov [
ebp +_GetModuleHandle],
eax ;保存
;-> VirtualProtect
lea eax ,[
ebp +szVirtualProtect]
call DoGetProcAddr
;获取VirtualProtect 地址
mov [
ebp +_VirtualProtect],
eax
;-> GetModuleFileName
lea eax ,[
ebp +szGetModuleFileName]
call DoGetProcAddr
;获取GetModuleFileName 地址
mov [
ebp +_GetModuleFileName],
eax
;-> CreateFile
lea eax ,[
ebp +szCreateFile]
call DoGetProcAddr
;获取CreateFileA 地址
mov [
ebp +_CreateFile],
eax
;-> GlobalAlloc
lea eax ,[
ebp +szGlobalAlloc]
call DoGetProcAddr
;获取GlobalAlloc 地址
mov [
ebp +_GlobalAlloc],
eax
;-> GlobalFree
lea eax ,[
ebp +szGlobalFree]
call DoGetProcAddr
;获取GlobalFree 地址
mov [
ebp +_GlobalFree],
eax
;-> ReadFile
lea eax ,[
ebp +szReadFile]
call DoGetProcAddr
;获取ReadFileA 地址
mov [
ebp +_ReadFile],
eax
;-> GetFileSize
lea eax ,[
ebp +szGetFileSize]
call DoGetProcAddr
;获取GetFileSize 地址
mov [
ebp +_GetFileSize],
eax
;-> CloseHandle
lea eax ,[
ebp +szCloseHandle]
call DoGetProcAddr
;获取CloseHandle 地址
mov [
ebp +_CloseHandle],
eax
; FUNNY JUMP :)
LEA EAX , [
EBP +
OFFSET LoaderContinue1]
PUSH EAX ;变形 JMP 到 LoaderContinue1
RET
;******************************************************************************
; it's in an own function to keep a the loader code small
; eax = address of API string
; esi = target dll base
DoGetProcAddr:
push eax
push esi
call [
ebp +_GetProcAddress]
ret
LoaderContinue1:
;----- ANTI DUMP -----
test [
ebp +PROTECTION_FLAGS],ANTI_DUMP_FLAG
;是否要 anti dump
jz LetDumpable
push fs :[30h]
;PEB
pop eax
TEST EAX ,
EAX
JS fuapfdw_is9x
; detected Win 9x
fuapfdw_isNT:
MOV EAX , [
EAX +0Ch]
;_PEB_LDR_DATA
MOV EAX , [
EAX +0Ch]
;LDR_MODULE
MOV DWORD PTR [
EAX +20h], 1000h
; 修改进程映像大小 为 1000h
JMP fuapfdw_finished
fuapfdw_is9x:
PUSH 0
CALL [
ebp +_GetModuleHandle]
;获取自身句柄
TEST EDX ,
EDX
JNS fuapfdw_finished
; Most probably incompatible!!!
CMP DWORD PTR [
EDX +8], -1
;??????
JNE fuapfdw_finished
; Most probably incompatible!!!
MOV EDX , [
EDX +4]
; get address of internaly used
; PE header
MOV DWORD PTR [
EDX +50h], 1000h
; increase size variable
fuapfdw_finished:
LetDumpable:
;---- GET HEADER WRITE ACCESS -----
mov edi ,[
ebp +dwImageBase]
add edi ,[
edi +03Ch]
;PE头
assume
edi :
ptr IMAGE_NT_HEADERS
; edi -> pointer to PE header
mov esi ,[
ebp +dwImageBase]
mov ecx ,[
edi ].OptionalHeader.SizeOfHeaders
;ECX = SizeOfHeaders
assume
edi : nothing
; 修改文件头的属性为PAGE_READWRITE
lea eax ,[
ebp +Buff]
;保存现在的页属性
push eax
push PAGE_READWRITE
push ecx ;大小为SizeOfHeaders
push [
ebp +dwImageBase]
call [
ebp +_VirtualProtect]
;是否要进行CRC校验
test [
ebp +PROTECTION_FLAGS],CHECK_HEADER_CRC
jz DontCheckCRC
;获取自身的文件名
push MAX_PATH
lea edi ,[
ebp +Buff]
;临时保存文件名
push edi
push 0
call [
ebp +_GetModuleFileName]
;以只读方式打开自身
push 0
push FILE_ATTRIBUTE_NORMAL
push OPEN_EXISTING
push NULL
push FILE_SHARE_READ
push GENERIC_READ
push edi
call [
ebp +_CreateFile]
.IF eax == INVALID_HANDLE_VALUE
xor eax ,
eax
jmp SkipChecksumCalc
.ENDIF
mov edi ,
eax
;获取文件大小
push NULL
push edi
call [
ebp +_GetFileSize]
sub eax ,CHECKSUM_SKIP_SIZE
xchg eax ,
esi ;ESI = 文件大小
;申请内存空间
push esi
push GMEM_FIXED+GMEM_ZEROINIT
call [
ebp +_GlobalAlloc]
.IF eax == NULL
jmp SkipChecksumCalcAndCleanUp
.ENDIF
xchg eax ,
ebx ; EBX = 申请到的内存空间
;把文件读到申请的内存空间中
push NULL
lea eax ,[
ebp +Buff]
;参数NumberOfBytesToRead
push eax
push esi ;大小
push ebx ;申请到的内存空间
push edi ;文件句柄
call [
ebp +_ReadFile]
; 计算校验和
mov eax ,
ebx
mov ecx ,
esi
PUSH EBX ; [ESP] -> hMem
PUSH EDI ; EDI = hFile
CALL GetChecksum
mov [
ebp +dwCalcedCRC],
eax ;保存校验和
POP EDI
POP EBX
; the calculated CRC will be compared at the start of the InitIT function >:-)
LEA EAX , [
EBP +
OFFSET AfterCRCCalcContinue]
PUSH EAX ;变形JMP
RET
;-> Start of GetChecksum
GetChecksum:
;计算代码校验和
; eax = file image base
; ecx = filesize
mov edi ,
eax ; edi -> data pointer
xor eax ,
eax ; eax -> current bytes
xor ebx ,
ebx ; ebx -> current checksum
xor edx ,
edx ; edx -> Position (zero based)
; start calculation
CheckSumLoop:
mov al ,
byte ptr [
edi ]
mul edx
add ebx ,
eax
inc edx
inc edi
loop CheckSumLoop
xchg eax ,
ebx ; eax -> checksum
RET ;返回值 EAX =校验和
;-> End of GetChecksum
AfterCRCCalcContinue:
; clean up
PUSH EBX
call [
ebp +_GlobalFree]
;释放空间
xchg esi ,
eax
SkipChecksumCalcAndCleanUp:
push eax
push edi
call [
ebp +_CloseHandle]
;关闭文件句柄
pop eax
SkipChecksumCalc:
DontCheckCRC:
;解密
mov eax ,[
ebp +dwImageBase]
mov ebx ,1
CALL CryptPE
LEA EAX , [
EBP +
OFFSET AfterDeCryptionContinue]
PUSH EAX ;JMP
RET
;*****************************************************
; eax = pointer to file memory
; ebx: 0 - 加密文件中的代码
; 1 - 解密内存中的代码
CryptPE:
mov edi ,
eax
add edi ,[
edi +3Ch]
;EDI = IMAGE_NT_HEADERS
assume
edi :
ptr IMAGE_NT_HEADERS
mov esi ,
edi
add esi ,0F8h
;ESI = IMAGE_SECTION_HEADER
assume
esi :
ptr IMAGE_SECTION_HEADER
xor edx ,
edx
.REPEAT
;跳过下面这些区段,不进行加密
.IF dword ptr [
esi ].Name1 == ('crsr')
;.rsrc 资源
jmp @@LoopEnd
.ENDIF
.IF dword ptr [
esi ].Name1 == ('rsr.')
;.rsr 资源
jmp @@LoopEnd
.ENDIF
.IF dword ptr [
esi ].Name1 == ('oler')
;.relo 重定位
jmp @@LoopEnd
.ENDIF
.IF dword ptr [
esi ].Name1 == ('ler.')
;.rel 重定位
jmp @@LoopEnd
.ENDIF
.IF dword ptr [
esi ].Name1 == ('Cy')
;.yC 自己的段
jmp @@LoopEnd
.ENDIF
.IF dword ptr [
esi ].Name1 == ('ade.')
;.eda ??
jmp @@LoopEnd
.ENDIF
;跳过空数据段
.IF [
esi ].PointerToRawData == 0 || [
esi ].SizeOfRawData == 0
jmp @@LoopEnd
.ENDIF
;-> en-/decrypt it
pushad
mov ecx ,[
esi ].SizeOfRawData
.IF ebx == 0
; 加密文件中的代码?
mov esi ,[
esi ].PointerToRawData
ADD ESI ,
EAX ;加上基址
CALL EncryptSec
.ELSE
mov esi ,[
esi ].VirtualAddress
add esi ,
eax
CALL DecryptSec
.ENDIF
JMP SecDecryptContinue1
; esi = CryptStart
; ecx = CryptSize
DecryptSec:
mov edi ,
esi
SecDecryptLoop:
LODSB
SecDecryptBuff
DB SEC_PER_SIZE DUP (0)
;Make PER 函数生成的解密代码放在这里,进行解密
STOSB ;解密后的代码重新写回去
LOOP SecDecryptLoop
RET
SecDecryptContinue1:
popad
@@LoopEnd:
add esi ,SIZEOF IMAGE_SECTION_HEADER
;ESI 指向下一个节
INC EDX
.UNTIL dx == [
edi ].FileHeader.NumberOfSections
;循环
assume
esi : nothing
assume
edi : nothing
ret
;**********************************************************************
AfterDeCryptionContinue:
;------ PREPARE THE OEP JUMP EXCEPTION :) ------
MOV EBX , [
EBP +dwImageBase]
ADD EBX , [
EBP +dwOrgEntryPoint]
ROR EBX , 7
MOV [
ESP +010h],
EBX
LEA EBX , [
EBP +
OFFSET SehHandler_OEP_Jump]
MOV [
ESP +01Ch],
EBX
;
;下面代码将TLS索引清0
;***************************************************
mov edi ,[
ebp +dwImageBase]
add edi ,
dword ptr [
edi +03Ch]
;EDI = PE 头
assume
edi :
ptr IMAGE_NT_HEADERS
mov ebx ,[
edi ].OptionalHeader.DataDirectory[SIZEOF IMAGE_DATA_DIRECTORY * 9].VirtualAddress
;Tls TABLE
assume
edi : nothing
cmp ebx ,0
; 存在TLS?
jz SkipTlsFix
add ebx ,[
ebp +dwImageBase]
; ebx -> pointer to tls table
assume
ebx :
ptr IMAGE_TLS_DIRECTORY32
mov eax ,[
ebx ].AddressOfIndex
mov dword ptr [
eax ],0
;TLS索引置0
assume
ebx : nothing
;****************************************************
SkipTlsFix:
;校验和是否相等
mov eax ,[
ebp +dwCalcedCRC]
.IF eax != 0
.IF eax != [
ebp +dwOrgChecksum]
jmp SkipInitIt
;不相等就跳过初始化输入表
.ENDIF
.ENDIF
;----- INIT IMPORT TABLE -----
; 0 - 失败
; 1 - 成功
LEA ESI , [
EBP +
OFFSET IIDInfo]
; ESI -> pointer to the current IID
ASSUME
ESI :
PTR sItInfo
;------ PREPARE API REDIRECTION ------
TEST [
EBP +PROTECTION_FLAGS], API_REDIRECT_FLAG
.IF !ZERO?
PUSH ESI
LEA EDI , [
EBP +
OFFSET Buff]
ASSUME
EDI :
PTR sReThunkInfo
XOR ECX ,
ECX
;下面的循环,获取API函数的个数
.WHILE [
ESI ].FirstThunk
MOV EDX , [
ESI ].FirstThunk
ADD EDX , [
EBP +dwImageBase]
.WHILE DWORD PTR [
EDX ]
;获取一个IID的函数的个娄
INC ECX
ADD EDX , 4
.ENDW
ADD ESI , SIZEOF sItInfo
;指向下一个IID
.ENDW
; 申请内存
XOR EDX ,
EDX
MOV EAX , SIZEOF sApiStub
MUL ECX
PUSH EAX
PUSH GMEM_FIXED
CALL [
EBP +_GlobalAlloc]
.IF !
EAX ; fatal exit
ADD ESP , 4
POPAD
RET
.ENDIF
MOV [
EDI ].ApiStubMemAddr,
EAX ;EAX = 申请到的内存地址
MOV [
EDI ].pNextStub,
EAX
ASSUME
EDI : NOTHING
POP ESI
.ENDIF
; start with the real routine
;解密DLL名字
.WHILE [
esi ].FirstThunk != 0
mov ebx ,[
esi ].DllNameRVA
add ebx ,[
ebp +dwImageBase]
MOV EAX ,
EBX
CALL EnDeCryptString
LEA EAX , [
EBP +InitITContinue1]
; goto InitITContinue1
PUSH EAX
RET ;JMP InitITContinue1,加载DLL
;*************************************************************************
; 参数;eax = 要解密的字符串的VA
EnDeCryptString:
PUSH ESI
PUSH EDI
MOV ESI ,
EAX
MOV EDI ,
EAX
DllCryptLoop:
;
LODSB
ROR AL ,4
STOSB
CMP BYTE PTR [
EDI ],0
JNZ DllCryptLoop
POP EDI
POP ESI
RET
;************************************************************************
InitITContinue1:
push ebx
call [
ebp +_LoadLibrary]
;加载DLL
test eax ,
eax
jz SkipInitIt
;下面要销毁DLL名字
PUSH EAX
test [
ebp +PROTECTION_FLAGS],DESTROY_IMPORT_FLAG
jz DontKillDllName
LEA EAX , [
EBP +
OFFSET DontKillDllName]
PUSH EAX ;KillString的返回地址
MOV EAX ,
EBX
JMP KillString
;销毁DLL名字
DontKillDllName:
POP EBX ;EBX = DLL 的句柄
; process the (Original-)FirstThunk members
mov ecx ,[
esi ].OrgFirstThunk
.IF ecx == 0
mov ecx ,[
esi ].FirstThunk
.ENDIF
add ecx ,[
ebp +dwImageBase]
;ECX指向IMAGE_THUNK_DATA
mov edx ,[
esi ].FirstThunk
add edx ,[
ebp +dwImageBase]
; edx - IAT ,IMAGE_IMPORT_BY_NAME
.WHILE dword ptr [
ecx ] != 0
test dword ptr [
ecx ],IMAGE_ORDINAL_FLAG32
;判断是否由序号引出
jnz @@OrdinalImp
; process a name import
mov dword ptr eax ,[
ecx ]
;EAX = IMAGE_IMPORT_BY_NAME
add eax ,2
;跳过Hint
add eax ,[
ebp +dwImageBase]
;EAX = API NAME
PUSH EAX
CALL EnDeCryptString
;解密APINAME
POP EAX
mov edi ,
eax ; 保存API名字指针,后面会用来销毁API名字
push edx
push ecx ;保存IMAGE_THUNK_DATA 指针
push eax
push ebx
call [
ebp +_GetProcAddress]
;获取函数地址
.IF eax == NULL
pop ecx
pop edx
jmp SkipInitIt
.ENDIF
pop ecx
pop edx
;销毁API名字,这段很像 Acprotect中销毁字符串的代码
PUSHAD
test [
ebp +PROTECTION_FLAGS],DESTROY_IMPORT_FLAG
JZ DontKillApiName
LEA EAX , [
EBP +
OFFSET DontKillApiName]
;保存返回地址
PUSH EAX
MOV EAX ,
EDI
JMP KillString
;销毁
DontKillApiName:
POPAD
;-> paste API address
mov dword ptr [
edx ],
eax ;API地址到填充IAT
jmp @@NextThunkPlease
@@OrdinalImp:
; process an ordinal import
push edx
push ecx ; save the thunk pointers
mov dword ptr eax ,[
ecx ]
;取得函数序号
sub eax ,080000000h
;去掉高位
push eax
push ebx
call [
ebp +_GetProcAddress]
;获取API函数地址
test eax ,
eax
jz SkipInitIt
pop ecx
pop edx
mov dword ptr [
edx ],
eax ;写到IAT
;下面是IAT 重定向的处理
@@NextThunkPlease:
; eax = Current Api address
; ebx = dll base
; edx = non-org thunk pointer
TEST [
EBP +PROTECTION_FLAGS], API_REDIRECT_FLAG
.IF !ZERO?
.IF [
EBP +bNT]
;如果是NT下的非系统DLL则跳过
.IF EBX < 070000000h ||
EBX > 077FFFFFFh
JMP SkipThunkRed
.ENDIF
.ELSE
.IF EBX < 080000000h
;98下的非系统DLL同样跳过
JMP SkipThunkRed
.ENDIF
.ENDIF
PUSH EDI
PUSH ESI
LEA EDI , [
EBP +Buff]
ASSUME
EDI :
PTR sReThunkInfo
MOV ESI , [
EDI ].pNextStub
MOV [
EDX ],
ESI ; make the thunk point to stub mem
SUB EAX ,
ESI
SUB EAX , 5
; eax - esi -5 得到 JMP xxxxx后面的偏移
MOV BYTE PTR [
ESI ], 0E9h
;写入E9--JMP
MOV DWORD PTR [
ESI +1],
EAX ;将函数地址写进去
ADD [
EDI ].pNextStub, SIZEOF sApiStub
;指向一个sApiStub
ASSUME
EDI : NOTHING
POP ESI
POP EDI
SkipThunkRed:
.ENDIF
add ecx ,4
;下一个IMAGE_THUNK_DATA
add edx ,4
;下一个IMAGE-IMPORT_BY_NAME
.ENDW
add esi ,SIZEOF sItInfo
; ESI = 下一个IID
.ENDW
assume
esi :nothing
xor eax ,
eax
inc eax
SkipInitIt:
.IF eax !=
TRUE
; exit
popad
ret
.ENDIF
;----- ERASE PE HEADER ------
test [
ebp +PROTECTION_FLAGS],ERASE_HEADER_FLAG
jz SkipEraseHeader
;下面是销毁PE头。好猥琐!
mov edi ,[
ebp +dwImageBase]
add edi ,[
edi +03Ch]
;PE头
assume
edi :
ptr IMAGE_NT_HEADERS
mov ecx ,[
edi ].OptionalHeader.SizeOfHeaders
;大小
mov esi ,[
ebp +dwImageBase]
;ESI= 镜像基址
assume
edi : nothing
ZeroMemLoop:
;用0填充掉
mov byte ptr [
esi ],0
inc esi
loop ZeroMemLoop
SkipEraseHeader:
;------ CHECK AGAIN LOADER CRC & COMPARE ------
LEA EAX , [
EBP +DepackerCode]
MOV ECX , LOADER_CRC_CHECK_SIZE
JMP SM10
DB 0E9h
;花指令
SM10:
CALL GetChecksum
;校验和
JMP SM11
DB 0C7h
;花指令
SM11:
MOV EBX , [
EBP +dwLoaderCRC]
XOR EAX ,
EBX ;检验校验和是否相等
.IF !ZERO?
;不等返回
JMP SM12
DB 02Ch
SM12:
POPAD
JMP SM13
DB 0E8h
SM13:
RET
.ENDIF
;解密 OEP_JUMP_CODE
LEA EDI , [
EBP +
OFFSET OEP_JUMP_CODE_START]
MOV ESI ,
EDI
MOV ECX , CRYPT_OEP_JUMP_SIZE
XOR EBX ,
EBX
OepJumpDecryptLoop:
;下面是解密算法
LODSB
XOR AL , OEP_JUMP_ENCRYPT_NUM
SUB AL ,
BL
ROL AL , 2
STOSB
INC EBX
LOOP OepJumpDecryptLoop
;跳到OEP
OEP_JUMP_CODE_START:
;----- CHECK FOR DEBUG API's -----
LEA EAX , [
EBP +
OFFSET szIsDebuggerPresent]
PUSH EAX
PUSH [
EBP +dwKernelBase]
CALL [
EBP +_GetProcAddress]
;获取 IsDebuggerPresent 函数地址
OR EAX ,
EAX ; 95系统下不支持这个函数
.IF !ZERO?
CALL EAX ;调用 IsDebugPresent检测调试器
OR EAX ,
EAX
.IF !ZERO?
;存在调试器,直接返回
POPAD
RET
.ENDIF
.ENDIF
;------ SECOND SI CHECK ------
; doesn't work on NT
; install SEH frame
TEST [
EBP +PROTECTION_FLAGS], CHECK_SI_FLAG
JZ SkipSICheck2
LEA ESI ,[
EBP +SEH]
ASSUME
ESI :
PTR sSEH
LEA EAX , [
EBP +
OFFSET SICheck2_SP]
MOV [
ESI ].SaveEip,
EAX
ASSUME
ESI : NOTHING
XOR EBX ,
EBX
LEA EAX , [
EBP +
OFFSET SehHandler2]
PUSH EAX
PUSH FS :[
EBX ]
MOV FS :[
EBX ],
ESP
MOV EDI ,
EBP
MOV EAX , 4400h
JMP SM4
DB 0C7h
SM4:
INT 68h
SICheck2_SP:
XOR EBX ,
EBX
POP FS :[
EBX ]
ADD ESP , 4
.IF DI == 01297h ||
DI == 01277h ||
DI == 01330h
JMP SM5
DB 0FFh
SM5:
POPAD
JMP SM6
DB 0E8h
SM6:
RET
.ENDIF
SkipSICheck2:
LEA EAX , [
EBP +
OFFSET OepJumpCodeCont]
PUSH EAX
RET
;*************************************************************************************
;在这个异常处理会跳到OEP去
SehHandler_OEP_Jump
PROC C pExcept:
DWORD ,pFrame:
DWORD ,pContext:
DWORD ,pDispatch:
DWORD
PUSH EDI
MOV EAX ,pContext
ASSUME
EAX :
PTR CONTEXT
; 恢复原来的SEH链
MOV EDI , [
EAX ].regEsp
PUSH [
EDI ]
XOR EDI ,
EDI
POP FS :[
EDI ]
;恢复原来的SEH链
ADD [
EAX ].regEsp, 8
MOV EDI , [
EAX ].regEbx
; EDI = 加密的 OEP
ROL EDI , 7
;解密得到真正的OEP
MOV [
EAX ].regEip,
EDI ;设置EIP到OEP
mov EAX ,ExceptionContinueExecution
;继续执行,会跳到OEP
ASSUME
EAX : NOTHING
POP EDI
RET
SehHandler_OEP_Jump
ENDP
;*******************************************************************************************
OepJumpCodeCont:
;用0填充掉DepackerCode 到 SehHandler_OEP_Jump的代码
XOR AL ,
AL
LEA EDI , [
EBP +
OFFSET DepackerCode]
MOV ECX , (
OFFSET SehHandler_OEP_Jump -
OFFSET DepackerCode)
LoaderZeroLoop:
STOSB
LOOP LoaderZeroLoop
;用0填充掉从LOADER_CRYPT_END到OEP_JUMP_CODE_END的代码
LEA EDI , [
EBP +
OFFSET OEP_JUMP_CODE_END]
MOV ECX , (
OFFSET LOADER_CRYPT_END -
OFFSET OEP_JUMP_CODE_END)
LoaderVarZeroLoop:
STOSB
LOOP LoaderVarZeroLoop
POPAD ; RESTORE STARTUP REGS
; After this POPAD:
; EAX - OEP Seh handler
; EBX - OEP (rored)
;安装SEH,在SehHandler中跳到OEP---
PUSH EAX
XOR EAX ,
EAX
PUSH FS :[
EAX ]
MOV FS :[
EAX ],
ESP
JMP SM3
DB 087H
;花指令
SM3:
; 跳到这里执行,因为下面的代码已经用0填充,所以会异常。中到
OEP_JUMP_CODE_END:
; EAX = ASCII string address
KillString:
.WHILE byte ptr [
eax ] != 0
mov byte ptr [
eax ],0
inc eax
.ENDW
ret
SehHandler1
PROC C pExcept:
DWORD ,pFrame:
DWORD ,pContext:
DWORD ,pDispatch:
DWORD
PUSH EDI
MOV EAX ,pContext
ASSUME
EAX :
PTR CONTEXT
MOV EDI , [
EAX ].regEdi
push [
EDI +SEH.SaveEip]
;SaveEip压栈
pop [
eax ].regEip
;弹出到regEip。更改EIP的地址,程序会转到SaveEip去执行
MOV [
eax ].regEbp,
EDI ;还原EBP
MOV [
EAX ].regEax, 4
; SI NOT detected !
mov EAX ,ExceptionContinueExecution
;返回到SaveEip去
ASSUME
EAX : NOTHING
POP EDI
RET
SehHandler1
ENDP
SehHandler2
PROC C pExcept:
DWORD ,pFrame:
DWORD ,pContext:
DWORD ,pDispatch:
DWORD
PUSH EDI
MOV EAX ,pContext
ASSUME
EAX :
PTR CONTEXT
MOV EDI , [
EAX ].regEdi
push [
EDI +SEH.SaveEip]
pop [
eax ].regEip
MOV [
eax ].regEbp,
EDI
MOV [
EAX ].regEdi, 0
; SI NOT detected !
mov EAX ,ExceptionContinueExecution
ASSUME
EAX : NOTHING
POP EDI
RET
SehHandler2
ENDP
;----- LOADER STRUCTS -----
sItInfo STRUCT
DllNameRVA
dd ?
FirstThunk
dd ?
OrgFirstThunk
dd ?
sItInfo
ENDS
sSEH STRUCT
OrgEsp
dd ?
OrgEbp
dd ?
SaveEip
dd ?
sSEH
ENDS
sReThunkInfo STRUCT
ApiStubMemAddr
DD ?
pNextStub
DD ?
sReThunkInfo
ENDS
sApiStub STRUCT
; UNUSED !
JumpOpc
DB ?
JumpAddr
DD ?
sApiStub
ENDS
;----- LOADER VARIABLES -----
dwImageBase
dd 0
dwOrgEntryPoint
dd 0
PROTECTION_FLAGS
dd 0
dwCalcedCRC
dd 0
dwLoaderCRC
DD 0
bNT
DD 0
IIDInfo
db (SIZEOF sItInfo * MAX_IID_NUM) dup (0)
SEH sSEH <0>
_LoadLibrary
dd 0
_GetProcAddress
dd 0
; some API stuff
szKernel32
db "Kernel32.dll" ,0
dwKernelBase
dd 0
szGetModuleHandle
db "GetModuleHandleA" ,0
_GetModuleHandle
dd 0
szVirtualProtect
db "VirtualProtect" ,0
_VirtualProtect
dd 0
szGetModuleFileName
db "GetModuleFileNameA" ,0
_GetModuleFileName
dd 0
szCreateFile
db "CreateFileA" ,0
_CreateFile
dd 0
szGlobalAlloc
db "GlobalAlloc" ,0
_GlobalAlloc
dd 0
szGlobalFree
db "GlobalFree" ,0
_GlobalFree
dd 0
szReadFile
db "ReadFile" ,0
_ReadFile
dd 0
szGetFileSize
db "GetFileSize" ,0
_GetFileSize
dd 0
szCloseHandle
db "CloseHandle" ,0
_CloseHandle
dd 0
szIsDebuggerPresent
db "IsDebuggerPresent" ,0
LOADER_CRYPT_END:
; This variables won't be crypted:
TlsBackupLabel:
TlsBackup IMAGE_TLS_DIRECTORY32 <0>
ChecksumLabel:
dwOrgChecksum
dd 0
Buff
db 0
; buffer for some stuff, its size: 2000h(VS) - DEPACKER_CODE_SIZE
;----- END OF PE LOADER CODE -----
DepackerCodeEnd:
PER.ASM
; -> Polymorphic En-/Decryption routine generator for per byte encryption <-
; by yoda
;---- STRUCTs ----
sPERTable STRUCT
dwSize
DD ?
dwEncrypt
DD ?
dwDecrypt
DD ?
RandNumType
DD ?
sPERTable
ENDS
; RandNumType:
; 0 - no random num needed
; 1 - 3th byte must be a random number
; 2 - 2nd byte must be a random number
;----- EQUs -----
PERItems
EQU 14
;----- CONST ----
.CONST
; all opcodes are in reverse order
;下面保存的是加密和解密的成对代码表
PERTable
DD 1
DD 090h
; NOP
DD 090h
; NOP
DD 0
DD 1
DD 0F9h
; STC
DD 0F9h
; STC
DD 0
DD 1
DD 0F8h
; CLC
DD 0F8h
; CLC
DD 0
DD 2
DD 0C0FEh
; INC AL
DD 0C8FEh
; DEC AL
DD 0
DD 2
DD 00004
; ADD AL, 0
DD 0002Ch
; SUB AL, 0
DD 2
DD 2
DD 0002Ch
; SUB AL, 0
DD 00004
; ADD AL, 0
DD 2
DD 2
DD 0C102h
; ADD AL, CL
DD 0C12Ah
; SUB AL, CL
DD 0
DD 2
DD 0C12Ah
; SUB AL, CL
DD 0C102h
; ADD AL, CL
DD 0
DD 2
DD 00034h
; XOR AL, 0
DD 00034h
; XOR AL, 0
DD 2
DD 3
DD 000C8C0h
; ROR AL, 0
DD 000C0C0h
; ROL AL, 0
DD 1
DD 3
DD 000C0C0h
; ROL AL, 0
DD 000C8C0h
; ROR AL, 0
DD 1
DD 3
DD 0E801EBh
; Self modifing
DD 0E801EBh
; Self modifing
DD 0
DD 3
DD 0E901EBh
; Self modifing
DD 0E901EBh
; Self modifing
DD 0
DD 3
DD 0C201EBh
; Self modifing
DD 0C201EBh
; Self modifing
DD 0
.DATA
dwRandVal
DD 0
.CODE
; srand should only called one time !!!
;初始化随机数种子。此函数只会被调用 一次
InitRandom
PROC
; manage the random generator
CALL GetTickCount
PUSH EAX
CALL srand
RET
InitRandom
ENDP ;*******************************************************************************
;函数功能: 得用上面的加解密表产生随机的加密和解密代码
;参数: pEncryptBuff 加密代码
; pDecryptBuff 解码代码,和加密代码对应
; dwSize 要填充的缓冲区的大小
; 返回值 没有
;*******************************************************************************
MakePER
PROC pEncryptBuff : LPVOID, pDecryptBuff : LPVOID, dwSize :
DWORD
LOCAL dwCurRandNum :
DWORD
; prepare some things
MOV EDI , pEncryptBuff
; EDI -> EncryptBuffer
MOV ESI , pDecryptBuff
; ESI -> DecryptBuffer
ADD ESI , dwSize
; ESI will be filled from down to top
; generate !
.REPEAT
; get a random PER Item
PUSH PERItems
CALL rand
;产生一个不大于14的随机数
MOV EBX , SIZEOF sPERTable
;
XOR EDX ,
EDX
MUL EBX
ADD EAX ,
OFFSET PERTable
XCHG EAX ,
EDX ;EDX指向一组加解密指令
ASSUME
EDX :
PTR sPERTable
; is this item too big
MOV EBX , [
EDX ].dwSize
CMP EBX , dwSize
;比较有没有足够的空间来保存代码
JG Retry
;把表中的加密指令数据复制到加密代码区
MOV ECX , [
EDX ].dwSize
MOV EAX , [
EDX ].dwEncrypt
MOV ECX , [
EDX ].dwSize
.WHILE ECX != 0
MOV BYTE PTR [
EDI ],
AL ;把多态指令的一个字节填充到缓冲区
ADD EDI , 1
ROR EAX , 8
;右移8位,AL指向下一字节
DEC ECX
.ENDW
; generate the random num
MOV EAX , [
EDX ].RandNumType
.IF EAX == 1 ||
EAX == 2
;判断指令对中是否需要填充随机数
MOV EBX ,
EDI
SUB EBX , 1
;EBX指向[EDX].Encrypt的最后一个字节
PUSH 0F8h
CALL rand
;产生一个小于0F8h的随机数
INC EAX ; 加1避免产生的随机数为0!
MOV dwCurRandNum,
EAX ;保存现在的随机数,解密的时候要用到
MOV BYTE PTR [
EBX ],
AL ;填写随机数到[EDX].Encrypt的最后一个字节
.ENDIF
; update variables/pointers
MOV EAX , [
EDX ].dwSize
SUB dwSize,
EAX ;重新计算dwSize大小
;-> decryption buffer
MOV ECX , [
EDX ].dwSize
MOV EAX , [
EDX ].dwDecrypt
SUB ECX , 1
.WHILE ECX != 0
;使EAX指向[EDI].dwDecrypt末尾
ROR EAX , 8
DEC ECX
.ENDW
MOV ECX , [
EDX ].dwSize
.WHILE ECX != 0
SUB ESI , 1
;ESI指向pDecryptBuff的首地址
MOV BYTE PTR [
ESI ],
AL ;填充AL到pDEcryptBuff
ROL EAX , 8
;这里用左移EAX取得下一字节
DEC ECX
.ENDW
; generate the random num
MOV EAX , [
EDX ].RandNumType
.IF EAX == 1
MOV EBX ,
ESI
ADD EBX , 2
;加2指向最后一个字节
MOV EAX , dwCurRandNum
MOV BYTE PTR [
EBX ],
AL ;填充随机数到最后一个字节
.ELSEIF EAX == 2
MOV EBX ,
ESI
ADD EBX , 1
;加1指向最后一个字节
MOV EAX , dwCurRandNum
MOV BYTE PTR [
EBX ],
AL ;填充随机数到最后一个字节
.ENDIF
ASSUME
EDX : NOTHING
Retry:
.UNTIL dwSize == 0
;循环
RET
MakePER
ENDP ;随机数产生函数*****************************************************************
rand
PROC USES edx ebx , dwRange :
DWORD
MOV EAX , dwRandVal
; save new random number
ADD EAX , 0567h
ROL EAX , 1
MOV dwRandVal,
EAX
; get new random number
XOR EDX ,
EDX
MOV ECX , 32
BitLoop:
SHR EAX , 1
.IF CARRY?
XOR EAX , 013245769h
.ENDIF
LOOP BitLoop
; force dwRange
XOR EDX ,
EDX
MOV EBX , dwRange
DIV EBX
XCHG EAX ,
EDX
RET
rand
ENDP
;随机种子函数*********************************
srand
PROC dwRandNum :
DWORD
MOV EAX , dwRandNum
MOV dwRandVal,
EAX
RET
srand
ENDP yoda' cryptor 分析总结
这个壳机基本上实现了加密壳要实现的东西。总结其特点如下
1.用到了多态(作者说是多态),动态的生成加解密函数
2.API重定向,不过也仅仅是重定向。在重定向到的代码中直接跳到原系统函数了。很容易还原
3.兼容性比较好,各种可能性都考虑到了
4.我分析的这个版本没有处理重定位,不能对DLL加壳
5.作了一些手脚来反调试器和防止脱壳,例如调用IsDebuggerPresent.壳代码执行完后擦除PE头。
修改LDR_MODULE的进程映像大小来anti dump。用SEH异常来跳到OEP,加花指令等等 最后我注后的这些文件不能编译成功,老提示windows.inc里面有错误。可能是我分析 的时候不小心修改了哪 里造成的错误。我也懒得找了。谁找到的可以告诉我<0_O>,编译可以直接下载原来没有注释的文件来编译。
By Demoscene
2010.05.18
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
上传的附件: