什么是WINDOWS的资源这里就不提了。写过WIN程序的人应该都晓得了。如果没写过,那么。。。建议去写哈。
关于加密资源节,也是加壳过程中的一个可选项而已。加和不加都不太对破解造成影响。但是如果
你的程序中,有一些重要数据在资源节里面而你又不想让其他人通过资源浏览器参看到,那么这个
时候加密资源节就有必要了。在这里节,我们首先了解一下资源节在PE文件中的结构。其次,编写
一个简单的资源浏览器用于熟悉资源节的结构。最后,完成一个加密与解密资源节的小程序。
资源节可以说在PE结构中最复杂的一个结构了。整体是一个2叉树结构。
总共有3层。第一层是类型,第二层是名称,第三层是语言,在语言后面就是一个指向资源数据的位置的结构了。
下面将资源节的结构如图:
资源节结构图:
附件中查看 呵呵。。。
资源节处于数据目录的第三项可以通过微软预定义的宏IMAGE_DIRECTORY_ENTRY_RESOURCE来定位资源
节的数据目录。通过VirtualAddress转换文件偏移 + 文件映射地址就得到了资源节所在文件的位置
节内容的起始是一个被称为资源目录表的结构,如下:
IMAGE_RESOURCE_DIRECTORY STRUCT
Characteristics dd ?
TimeDateStamp dd ?
MajorVersion dw ?
MinorVersion dw ?
NumberOfNamedEntries dw ?
NumberOfIdEntries dw ?
IMAGE_RESOURCE_DIRECTORY ENDS
Characteristics:资源标志,保留,设置为0
TimeDataStamp:时间戳,由资源编译器设定
MajorVersion:主版本号,由用户设定
MinorVersion:次版本号,由用户设定
以下这两项是我们说要关心的
NumberOfNamedEntries:通过字符串来定义的资源
NumberOfIdEntries:用过序号来定义的资源
这两项的和为引入资源的总数,在这个结构后面是一组由IMAGE_RESOURCE_DIRECTORY_ENTRY组成的数组
具体个数为NumberOfNamedEntries + NumberOfIdEntries
以下为结构的说明:
IMAGE_RESOURCE_DIRECTORY_ENTRY STRUCT
union
rName RECORD NameIsString:1,NameOffset:31
Name1 dd ?
Id dw ?
ends
union
OffsetToData dd ?
rDirectory RECORD DataIsDirectory:1,OffsetToDirectory:31
ends
IMAGE_RESOURCE_DIRECTORY_ENTRY ENDS
这个结构在不同的位置是不同的意义。
在第一层的为类型,Name1代表的是一个类型,在资源类型方面有16个基本类型
"RT_CURSOR"
"RT_BITMAP"
"RT_ICON"
"RT_MENU"
"RT_DIALOG"
"RT_STRING"
"RT_FONTDIR"
"RT_FONT"
"RT_ACCELERATOR"
"RT_RCDATA"
"DIFFERENCE"
"RT_GROUP_CURSOR"
"RT_GROUP_ICON"
"RT_VERSION"
也有更多的类型,这方面的资料很少。我也没有能收集全,RT_RCDATA类型表示为自定义的。
这里为标准的类型值。
在第二层时:如果Name的2进制最高位时候1,那么它表示一个以名字引出的资源,此时Name的低2位表示一个从资源节开头到它的偏移
使用Name的低2位值 + 资源节开头的地址 指向一个IMAGE_RESOURCE_DIRECTORY_STRING结构。资源的名字使用UNICODE编码。
IMAGE_RESOURCE_DIRECTORY_STRING STRUCT
Length1 dw ?
NameString db ?
IMAGE_RESOURCE_DIRECTORY_STRING ENDS
Length1表示有几个字母
NameString为以双0结尾的UNICODE字符串。
在第三层时,Name表示资源所用语言的代码。
OffsetToData:这个字段在不同层都表示一个到资源节开头的偏移。
不同的是,如果OffsetToData的2进制最高位是1,表示还有下面的其他层(语言层或者名字层,类型层总是存在)。
如果是0,则表示一个到IMAGE_RESOURCE_DATA_ENTRY的偏移
IMAGE_RESOURCE_DATA_ENTRY STRUCT
OffsetToData dd ?
Size1 dd ?
CodePage dd ?
Reserved dd ?
IMAGE_RESOURCE_DATA_ENTRY ENDS
OffsetTOData表示到资源数据的偏移,这个偏移是到加载地址(ImageBase)的偏移。
Size1为数据的长度
CodePage:一般为0
Reserved:一般为0
接下来看段代码可能会更清晰一些。
ResViewEntry proc uses ebx ecx edx edi esi pEntry : LPVOID, dwLevel : DWORD LOCAL szOutFileName[MAX_PATH] : BYTE ;; level = 0,1,2 mov esi, pEntry assume esi : ptr IMAGE_RESOURCE_DIRECTORY ;; 计算有几个资源在当前目录下 mov cx, word ptr [esi].NumberOfNamedEntries add cx, word ptr [esi].NumberOfIdEntries movzx ecx, cx ;; 这里存在着ecx个IMAGE_RESOURCE_DIRECTORY_ENTRY add esi, sizeof IMAGE_RESOURCE_DIRECTORY mov edi, esi assume edi : ptr IMAGE_RESOURCE_DIRECTORY_ENTRY ResDirEntryLoop: ;; 打印资源层 invoke JudegeResourceEntryLevel, edi, dwLevel mov eax, dword ptr [edi].OffsetToData test eax, FIRSTBIT jz FoundTheDataEntry and eax, 0000FFFFh add eax, g_pResSection mov edx, dwLevel inc edx invoke ResViewEntry, eax, edx jmp ContinueResDirEntryLoop FoundTheDataEntry: ;; 取出资源文件 add eax, g_pResSection assume eax : ptr IMAGE_RESOURCE_DATA_ENTRY mov edx, dword ptr [eax].Size1 mov eax, dword ptr [eax].OffsetToData ;; 转化RVA到偏移 invoke RVA2Offset, g_pMem, eax push eax push ecx push edx invoke wsprintf, addr szOutFileName, offset g_szOutOrdFormat, eax pop edx pop ecx pop eax add eax, g_pMem invoke OutputToFile, addr szOutFileName, eax, edx ContinueResDirEntryLoop: add edi, sizeof IMAGE_RESOURCE_DIRECTORY_ENTRY dec ecx test ecx, ecx jnz ResDirEntryLoop assume esi : nothing assume edi : nothing ret ResViewEntry endp JudegeResourceEntryLevel proc uses ebx ecx edx edi esi pResEntry : LPVOID, dwLevel : DWORD LOCAL szTmpBuf[MAX_PATH] : BYTE ;; init local variable mov ecx, MAX_PATH lea edi, szTmpBuf xor eax, eax cld rep stosb mov edi, pResEntry assume edi : ptr IMAGE_RESOURCE_DIRECTORY_ENTRY mov eax, dwLevel cmp eax, 0 jnz JudgeLevelOne ;; 显示类型 mov eax, dword ptr [edi].Name1 invoke ShowResourceType, eax jmp ExitJudgeLevel JudgeLevelOne: cmp eax, 1 jnz JudgeLevelTwo ;; 显示名称 mov eax, dword ptr [edi].Name1 ;; 最高位是0时表示使用ID test eax, FIRSTBIT jz UseID and eax, 0000FFFFh add eax, g_pResSection assume eax : ptr IMAGE_RESOURCE_DIR_STRING_U lea esi, [eax].NameString lea edi, szTmpBuf ;; 拷贝字符串到局部变量 mov cx, word ptr [eax].Length1 movzx ecx, cx cld rep movsw invoke crt_printf, offset g_szOutFormat, offset g_szResNameByStr invoke crt_wprintf, addr szTmpBuf invoke crt_printf, offset g_szOutFormat, offset g_szEndLine jmp ExitJudgeLevel UseID: invoke crt_printf, offset g_szResNameById, eax jmp ExitJudgeLevel JudgeLevelTwo: ;; 第三次是语言,这里输出语言定义的常量 mov eax, dword ptr [edi].Name1 invoke crt_printf, offset g_szResLang, eax ExitJudgeLevel: assume eax : nothing assume edi : nothing ret JudegeResourceEntryLevel endp
接下来就改讲解一下加解密资源节了,其实与列出资源节的算法一样。这里使用的加密算法也很简单只是简单的XOR了一哈。
不过要注意的是。有些资源是不可以加密的。例如ICON还有寻找ICON偏移的ICON_GROUP还有版本信息等,还有其他的,总之在PE加载器在代码段之前加载的资源是不可以加密,否则程序不能运行。这里的加密程序不加密ICON ICON_GROUP VERSION这三种类型,如果你遇到加密资源后程序无法运行那可以就是加了不该加的资源了。呵呵,自行修改下代码吧。加密在JudegeResourceType处添加过滤,解密在JudgeDecryptResType处修改,代码很简单一看就明白
下面列出加解密代码
首先是加密的
EncryptResSec proc uses ebx ecx edx edi esi pEntry : LPVOID, dwLevel : DWORD ;; level = 0,1,2 mov esi, pEntry assume esi : ptr IMAGE_RESOURCE_DIRECTORY ;; 计算有几个资源在当前目录下 mov cx, word ptr [esi].NumberOfNamedEntries add cx, word ptr [esi].NumberOfIdEntries movzx ecx, cx ;; 这里存在着ecx个IMAGE_RESOURCE_DIRECTORY_ENTRY add esi, sizeof IMAGE_RESOURCE_DIRECTORY mov edi, esi assume edi : ptr IMAGE_RESOURCE_DIRECTORY_ENTRY ResDirEntryLoop: ;; 加密资源层 ;; 清除加密标志,如果是0级别则清除 cmp dwLevel, 0 jnz SkipClearEncryptFlag push 0 pop g_dwEncrypt SkipClearEncryptFlag: invoke JudegeResourceType, edi, dwLevel mov eax, dword ptr [edi].OffsetToData test eax, FIRSTBIT jz FoundTheDataEntry and eax, 0000FFFFh add eax, g_pResSection mov edx, dwLevel inc edx invoke EncryptResSec, eax, edx jmp ContinueResDirEntryLoop FoundTheDataEntry: ;; 加密资源文件 mov ebx, g_dwEncrypt test ebx, ebx jz ContinueResDirEntryLoop add eax, g_pResSection assume eax : ptr IMAGE_RESOURCE_DATA_ENTRY mov edx, dword ptr [eax].Size1 mov eax, dword ptr [eax].OffsetToData ;; 转化RVA到偏移 invoke RVA2Offset, g_pMem, eax add eax, g_pMem ;; 加密资源 EncryptResourceData: push edi push esi push ecx mov edi, eax mov esi, edi mov ecx, edx cld EnResourceLoop: lodsb xor al, CRYPT_KEY stosb loop EnResourceLoop pop ecx pop esi pop edi ContinueResDirEntryLoop: add edi, sizeof IMAGE_RESOURCE_DIRECTORY_ENTRY dec ecx test ecx, ecx jnz ResDirEntryLoop assume esi : nothing assume edi : nothing ret EncryptResSec endp JudegeResourceType proc uses eax edi pResEntry : LPVOID, dwLevel : DWORD ;; 不加密ICON,ICON_GROUP,还有版本信息 ;; 在PE加载器加载时要读取这些资源 ;; 否则程序会崩溃 mov edi, pResEntry assume edi : ptr IMAGE_RESOURCE_DIRECTORY_ENTRY mov eax, dwLevel ;; 不是类型层,直接退出 cmp eax, 0 jnz ExitJudgeLevel ;; 显示类型 mov eax, dword ptr [edi].Name1 cmp eax, RT_ICON jnz CmpICON_GROUP jmp ExitJudgeLevel CmpICON_GROUP: cmp eax, RT_GROUP_ICON jnz CmpVERSION jmp ExitJudgeLevel CmpVERSION: cmp eax, RT_VERSION jnz EncryptData ExitJudgeLevel: assume eax : nothing assume edi : nothing ret EncryptData: push 1 pop g_dwEncrypt jmp ExitJudgeLevel JudegeResourceType endp
CryptResource proc pFileName : LPSTR LOCAL hFile : HANDLE LOCAL hMap : HANDLE LOCAL pMem : LPVOID LOCAL dwAppendSize : DWORD LOCAL dwNTHeaderAddr : DWORD LOCAL dwImageBase : DWORD LOCAL dwOrigEntryAddr : DWORD LOCAL dwResSecVA : DWORD ;; init data xor eax, eax mov g_bError, al mov eax, offset EndDecryptResource - offset DecryptResource mov g_dwDecryptResourceSize, eax ;; get the append size invoke CountAppendSize, pFileName .IF eax == 0 jmp GetFileSizeFailed .ENDIF mov dwAppendSize, eax ;; open file invoke CreateFile, pFileName,\ GENERIC_WRITE + GENERIC_READ,\ FILE_SHARE_WRITE + FILE_SHARE_READ,\ NULL,\ OPEN_EXISTING,\ FILE_ATTRIBUTE_NORMAL,\ 0 .IF eax == INVALID_HANDLE_VALUE jmp OpenFileFailed .ENDIF mov hFile, eax ; ;; get file size ; invoke GetFileSize, eax, NULL ; .IF eax == 0 ; jmp GetFileSizeFailed ; .ENDIF mov eax, dwAppendSize xchg eax, ecx ;; create memory map xor ebx, ebx invoke CreateFileMapping, hFile, ebx, PAGE_READWRITE, ebx, ecx, ebx .IF eax == 0 invoke CloseHandle, hFile jmp CreateMapFailed .ENDIF mov hMap, eax ;; map file to memory xor ebx, ebx invoke MapViewOfFile, hMap, FILE_MAP_WRITE + FILE_MAP_READ + FILE_MAP_COPY, ebx, ebx, ebx .IF eax == 0 invoke CloseHandle, hMap invoke CloseHandle, hFile jmp MapFileFailed .ENDIF mov pMem, eax mov g_pMem, eax ;; check it's PE file or not ? xchg eax, esi assume esi : ptr IMAGE_DOS_HEADER .IF [esi].e_magic != 'ZM' invoke UnmapViewOfFile, pMem invoke CloseHandle, hMap invoke CloseHandle, hFile jmp InvalidPE .ENDIF add esi, [esi].e_lfanew assume esi : ptr IMAGE_NT_HEADERS .IF word ptr [esi].Signature != 'EP' invoke UnmapViewOfFile, pMem invoke CloseHandle, hMap invoke CloseHandle, hFile jmp InvalidPE .ENDIF ;; store basic PE orig information mov dwNTHeaderAddr, esi assume esi : ptr IMAGE_NT_HEADERS mov eax, dword ptr [esi].OptionalHeader.ImageBase mov dwImageBase, eax mov eax, dword ptr [esi].OptionalHeader.AddressOfEntryPoint add eax, dwImageBase mov dwOrigEntryAddr, eax ;; 判断是否存在资源节 mov eax, dword ptr [esi].OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE * sizeof IMAGE_DATA_DIRECTORY].VirtualAddress .IF eax == 0 jmp NoResourceSec .ENDIF mov ebx, eax add ebx, dwImageBase mov dwResSecVA, ebx ; ebx 为资源节在内存中的地址 ;; 转变为FVA invoke RVA2Offset, pMem, eax add eax, pMem mov g_pResSection, eax ;; 加密资源节 invoke EncryptResSec, eax, 0 ;; 升级数据 lea eax, DecryptRes_dwOrigEntryAddress push dwOrigEntryAddr pop dword ptr [eax] lea eax, DecryptRes_ResVA push dwResSecVA pop dword ptr [eax] lea eax, DecryptRes_ImageBase push dwImageBase pop dword ptr [eax] ;; 添加解密节 invoke AddSection, pMem, NULL, g_dwDecryptResourceSize mov esi, eax assume esi : ptr IMAGE_SECTION_HEADER ;; 更新入口点与SizeOfImage mov edi, dwNTHeaderAddr assume edi : ptr IMAGE_NT_HEADERS ;; 更改资源节属性 mov eax, dword ptr [edi].OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_RESOURCE * sizeof IMAGE_DATA_DIRECTORY].VirtualAddress invoke FindSectionTable, pMem, eax assume eax : ptr IMAGE_SECTION_HEADER push 0E00000E0h ; 可读可写可执行 pop dword ptr [eax].Characteristics push dword ptr [esi].VirtualAddress pop dword ptr [edi].OptionalHeader.AddressOfEntryPoint mov eax, dword ptr [esi].VirtualAddress add eax, dword ptr [esi].Misc.VirtualSize mov ebx, dword ptr [edi].OptionalHeader.SectionAlignment invoke PEAlign, eax, ebx push eax pop dword ptr [edi].OptionalHeader.SizeOfImage ;; 将解密节写入 mov edi, dword ptr [esi].PointerToRawData add edi, pMem lea esi, DecryptResource mov ecx, g_dwDecryptResourceSize cld rep movsb LogicShellExit: ;; close handle & write it invoke UnmapViewOfFile, pMem invoke CloseHandle, hMap invoke CloseHandle, hFile .IF g_bError == 0 ;; show success message invoke crt_printf, offset g_szOutFormat, offset g_szDone .ENDIF assume eax : nothing assume esi : nothing assume edi : nothing ret ;; ----- Show error message ----- OpenFileFailed: lea eax, g_szOpenFileFailed jmp ShowErr GetFileSizeFailed: lea eax, g_szGetFileSizeFailed jmp ShowErr CreateMapFailed: lea eax, g_szCreateMapFailed jmp ShowErr MapFileFailed: lea eax, g_szMapFileFailed jmp ShowErr InvalidPE: lea eax, g_szInvalidPE jmp ShowErr NoResourceSec: lea eax, g_szNoResTbl jmp ShowErr ShowErr: invoke crt_printf, offset g_szOutFormat, eax mov al, 1 mov g_bError, al jmp LogicShellExit ;; ----- 解密资源节 ----- DecryptResource: DecryptResourceStackSize equ 10h DecryptResource_Eip equ -04h DecruptResource_dwFlags equ -08h push ebp mov ebp, esp sub esp, DecryptResourceStackSize ;; 清0堆栈 mov edi, esp mov ecx, DecryptResourceStackSize cld xor eax, eax rep stosb call GetEip GetEip: pop eax sub eax, offset GetEip - offset DecryptResource mov dword ptr [ebp+DecryptResource_Eip], eax add eax, offset DecryptRes_ResVA - offset DecryptResource push 0 ; Level push dword ptr [eax] ; ResVA push dword ptr [eax] ; pEntry call RecDecryptRes ;; 读取原入口点 mov eax, offset DecryptRes_dwOrigEntryAddress - offset DecryptResource add eax, dword ptr [ebp+DecryptResource_Eip] mov eax, dword ptr [eax] mov esp, ebp pop ebp jmp eax ;; ----- 解密资源节数据 ----- DecryptRes_dwOrigEntryAddress dd 0 DecryptRes_ResVA dd 0 DecryptRes_bDecrypt dd 0 DecryptRes_ImageBase dd 0 ;; ----- 递归解密资源节的函数 ----- RecDecryptRes: RecDecryptResArg_Entry equ 08h RecDecryptResArg_ResVA equ 0ch ResDecryptRes_dwLevel equ 10h ResDecryptResStackSize equ 04h ResDecryptRes_Eip equ -04h push ebp mov ebp, esp sub esp, ResDecryptResStackSize push ebx push ecx push edx push edi push esi ;; 获取eip call GetRecDecryptResEip GetRecDecryptResEip: pop ebx sub ebx, offset GetRecDecryptResEip - offset RecDecryptRes mov dword ptr [ebp+ResDecryptRes_Eip], ebx mov esi, dword ptr [ebp+RecDecryptResArg_Entry] assume esi : ptr IMAGE_RESOURCE_DIRECTORY ;; 计算有几个资源在当前目录下 mov cx, word ptr [esi].NumberOfNamedEntries add cx, word ptr [esi].NumberOfIdEntries movzx ecx, cx ;; 这里存在着ecx个IMAGE_RESOURCE_DIRECTORY_ENTRY add esi, sizeof IMAGE_RESOURCE_DIRECTORY mov edi, esi assume edi : ptr IMAGE_RESOURCE_DIRECTORY_ENTRY ResDeDirEntryLoop: ;; 解密资源层 ;; 是0级别则清除 cmp dword ptr [ebp+ResDecryptRes_dwLevel], 0 jnz SkipClearDecryptFlag push 0 mov eax, dword ptr [ebp+ResDecryptRes_Eip] sub eax, offset RecDecryptRes - offset DecryptRes_bDecrypt pop dword ptr [eax] SkipClearDecryptFlag: push dword ptr [ebp+ResDecryptRes_dwLevel] push edi call JudgeDecryptResType mov eax, dword ptr [edi].OffsetToData test eax, FIRSTBIT jz FoundTheDeDataEntry and eax, 0000FFFFh add eax, dword ptr [ebp+RecDecryptResArg_ResVA] mov edx, dword ptr [ebp+ResDecryptRes_dwLevel] inc edx push edx push dword ptr [ebp+RecDecryptResArg_ResVA] push eax call RecDecryptRes jmp ContinueResDeDirEntryLoop FoundTheDeDataEntry: ;; 取出资源文件 mov ebx, dword ptr [ebp+ResDecryptRes_Eip] sub ebx, offset RecDecryptRes - offset DecryptRes_bDecrypt mov ebx, dword ptr [ebx] test ebx, ebx jz ContinueResDeDirEntryLoop ;; ebx 不为0则解密 add eax, dword ptr [ebp+RecDecryptResArg_ResVA] assume eax : ptr IMAGE_RESOURCE_DATA_ENTRY mov edx, dword ptr [eax].Size1 mov eax, dword ptr [eax].OffsetToData mov ebx, dword ptr [ebp+ResDecryptRes_Eip] sub ebx, offset RecDecryptRes - offset DecryptRes_ImageBase mov ebx, dword ptr [ebx] add eax, ebx ;; 解密资源 push edi push esi push ecx mov edi, eax mov esi, edi mov ecx, edx cld DeResourceLoop: lodsb xor al, CRYPT_KEY stosb loop DeResourceLoop mov esi, dword ptr [ebp+ResDecryptRes_Eip] sub esi, offset RecDecryptRes - offset DecryptRes_bDecrypt push 0 pop dword ptr [esi] pop ecx pop esi pop edi ContinueResDeDirEntryLoop: add edi, sizeof IMAGE_RESOURCE_DIRECTORY_ENTRY dec ecx test ecx, ecx jnz ResDeDirEntryLoop assume esi : nothing assume edi : nothing pop esi pop edi pop edx pop ecx pop ebx mov esp, ebp pop ebp retn 0ch ;; ----- 判断资源类型 ----- JudgeDecryptResType: JudgeDecryptResArg_Entry equ 08h JudgeDecryptResArg_dwLevel equ 0ch push ebp mov ebp, esp push eax push edi push ebx push esi ;; 获取EIP call GetJudgeDecryptEip GetJudgeDecryptEip: pop esi sub esi, offset GetJudgeDecryptEip - offset JudgeDecryptResType ;; esi 为函数头地址 ;; 这里不加密第一个ICON与第一个ICON GROUP ;; 在PE加载器加载时要读取这些资源 ;; 否则程序会崩溃 ;; 清除加密标志 ;xor eax, eax mov ebx, esi sub ebx, offset JudgeDecryptResType - offset DecryptRes_bDecrypt ;mov dword ptr [ebx], eax ;; ebx 为解密标志 ;; edx 为解密标志的地址 ;; 用两个寄存器来代替变量 mov edi, dword ptr [ebp+JudgeDecryptResArg_Entry] assume edi : ptr IMAGE_RESOURCE_DIRECTORY_ENTRY mov eax, dword ptr [ebp+JudgeDecryptResArg_dwLevel] cmp eax, 0 jnz ExitJudgeLevel ;; 显示类型 mov eax, dword ptr [edi].Name1 cmp eax, RT_ICON jnz CmpICON_GROUP jmp ExitJudgeLevel CmpICON_GROUP: cmp eax, RT_GROUP_ICON jnz CmpVERSION jmp ExitJudgeLevel CmpVERSION: cmp eax, RT_VERSION jnz DecryptData ExitJudgeLevel: assume eax : nothing assume edi : nothing pop esi pop ebx pop edi pop eax mov esp, ebp pop ebp retn 08h DecryptData: push 1 pop dword ptr [ebx] jmp ExitJudgeLevel EndDecryptResource: CryptResource endp
...
那个资源节结构图是用EDraw画的,原文件也一并上传了。画的难看了点,见谅。。。