- UID
- 66114
注册时间2010-4-1
阅读权限30
最后登录1970-1-1
龙战于野
TA的每日心情 | 慵懒 2019-3-12 17:25 |
---|
签到天数: 3 天 [LV.2]偶尔看看I
|
发表于 2010-5-7 19:11:34
|
显示全部楼层
标 题: 【成果6.1】软件保护壳技术专题 - 添加新节
作 者: 玩命
时 间: 2008-06-15,18:09:52
链 接: http://bbs.pediy.com/showthread.php?t=66612
壳要附加到软件本身,有很多方式进行。在这里使用最常用的一种方式,添加一个新节。这里我们先用文字的形式描述一下添加新 节的算法。然后给出一段代码并在注释中详细给出没条指令的解释。在这之前,首先给出一些名词的解释,以便刚接触的朋友可以熟悉。熟悉的朋友可以直接跳过。
名词解释:
Offset
相对偏移
常指在文件中到文件头的距离。
RVA
相当内存偏移
常指在内存中到内存加载起始地址的距离。
VA
虚拟地址
常指在内存中确切的地址也就是RVA+内存加载起始地址。
Alignment
对齐粒度
当作"最小单位"就可以了例如:对齐粒度为200h。可以这样理解,一辆装货的车上面装的全是箱子,这箱子的大小是200h。如果最后一箱子没有装满,但是它 仍然占着一个箱子。
添加新节相关的PE头属性:
位于IMAGE_NT_HEADERS结构中的属性:
ImageBase(4字节)
SizeOfImage(4字节)
NumberOfSections(2字节)
AddressOfEntryPoint(4字节)
SectionAlignment(4字节)
FileAlignment(4字节)
位于IMAGE_SECTION_HEADER结构的属性:
最后节表VirtualSize(4字节)
最后节表的VirtualAddress(4字节)
最后节表的SizeOfRawData(4字节)
最后节表的PointerToRawData(4字节)
最后节表的Characteristics(4字节)
添加新节算法描述:
1.建立文件映射
2.判断是否是PE文件
3.移动到最后一个节表
4.添加新节节表
5.设置新节的VirtualAddress,VirtualSize,SizeOfRawData,PointerToRawData,Characteristics等属性
6.将新节的内容写入文件
7.增加NumberOfSections属性
8.设置SizeOfImage,AddressOfEntryPoint属性
9.将内存映射回文件
注:代码中讲解的部分用红色标出
首先是建立文件映射,也可以直接读写文件,不过这样做操作起来会方便一些。
代码:
CryptFile proc szFname : LPSTR
LOCAL hFile : HANDLE
LOCAL hMap : HANDLE
LOCAL pMem : LPVOID
LOCAL dwOrigFileSize : DWORD
LOCAL dwNTHeaderAddr : DWORD
;; init data
xor eax, eax
mov g_bError, al
mov eax, offset EndNewSection - offset NewSection
mov g_dwNewSectionSize, eax
;; open file
invoke CreateFile, szFname,\
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
invoke GetFileSize, hFile, NULL
.IF eax == 0
invoke CloseHandle, hFile
jmp GetFileSizeFailed
.ENDIF
mov dwOrigFileSize, eax
;; 这里的dwOrigFileSize 是原始的文件大小
;; 因为你要添加新节,所以要多上那么一点
;; 点尺寸.这个尺寸就是APPEND_SIZE,如果
;; 这个尺寸也许会让你最后添加的程序后有
;; 一些多余的数据。也可以没有,不过有一
;; 点点麻烦。这就要计算添加后的
;; SizeOfImage了。等到以后讲解吧。
add eax, APPEND_SIZE
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
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
;; 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
mov dwNTHeaderAddr, esi
;; 在建立映射后
;; 这段代码将映射后文件的指针存放在局部变量pMem.
;; 而指定的NT结构头指针存放到esi寄存器处。
;; 现在我们调用添加节函数添加一个新节,AddSection是
;; 我们这节的主要函数,将在下面讲解。
;; 增加一个新节
;; pMem:文件映射内存指针
;; g_szNewSectionName:新节的节名,这里是(.new)
;; g_dwNewSectionSize:新节的长度,这里是offset EndNewSection - offset NewSection
invoke AddSection, pMem, offset g_szNewSectionName, g_dwNewSectionSize
push eax
mov esi, dwNTHeaderAddr
assume esi : ptr IMAGE_NT_HEADERS
;; 下面做的就是设置新节的中的原代码入口点,就是真正的入口地址.
mov ebx, dword ptr [esi].OptionalHeader.AddressOfEntryPoint
add ebx, dword ptr [esi].OptionalHeader.ImageBase
;; OrigAddressOfEntry这个变量在CryptFile底部的NewSection节中
mov eax, offset OrigAddressOfEntry
mov dword ptr [eax], ebx
;; 更新入口点,将新节的入口点设置到NT头结构的AddressOfEntryPoint
;; 哪个节的VirusAddress被设置到AddressOfEntryPoint处,哪个节将会被先执行
;; 这也是最通常的入口点技术
pop eax
assume eax : ptr IMAGE_SECTION_HEADER
push dword ptr [eax].VirtualAddress
pop dword ptr [esi].OptionalHeader.AddressOfEntryPoint
;; 下面的代码利用新节节表将新节的代码写入文件
mov esi, offset NewSection
mov edi, dword ptr [eax].PointerToRawData
add edi, pMem
mov ecx, g_dwNewSectionSize
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 MessageBox, NULL, offset g_szDone, offset g_szDoneCap, MB_ICONINFORMATION
.ENDIF
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
ShowErr:
invoke MessageBox, NULL, eax, offset g_szErr, MB_ICONERROR
mov al, 1
mov g_bError, al
jmp LogicShellExit
;; ----- 新节代码 -----
NewSection:
;; 在这里获取地址
;; call指令会将下条指令的地址压入堆栈
;; 注意此指令的OPCODE为EB00000000
;; 病毒与Shellcode等常用此指令定位
;; 杀毒软件的启发式搜索常将此特征作为查找特征
;; 聪明的读者可以自己修改定位代码来躲过
;; 这类的查杀
call GetEip
GetEip:
;; eax中有保存着当前的地址,标号为GetEip
pop eax
add eax, offset OrigAddressOfEntry - offset GetEip
;; 两个偏移的差就是这两个地址之间的距离,它的距离 + 起始地址
;; 就为OrigAddressOfEntry的地址
;; 最后将OrigAddressOfEntry保存的值,也就是原来的入口节的地址
;; 送回eax中。
mov eax, dword ptr [eax]
;; 跳到原入口点地址
jmp eax
;; ----- 新节数据 -----
OrigAddressOfEntry dd ?
EndNewSection:
CryptFile endp
上面的新节代码没有任何作用,只是直接跳入到原入口的地址。
下面的代码就是这次主题的主要的代码.
代码:
AddSection proc uses ebx ecx edx esi edi, pMem : LPVOID,
pSectionName : LPVOID,
dwSectionSize : DWORD
;; add a new section
;; ret: eax = new section table file offset
LOCAL dwNTHeader : LPVOID
LOCAL dwLastSecTbl : LPVOID
LOCAL dwFileAlig : DWORD
LOCAL dwSecAlig : DWORD
;; move to section table
mov esi, pMem
;; 将映射地址传给esi,再将esi设置为PE头的地址
;; 这里这个3ch是DOS头中e_lfanew的偏移。e_lfanew保存着
;; 从MZ头到PE头的偏移,所以这样的相加使esi指向PE头
;; 也可以使用下面同等指令替换
;; assume esi : ptr IMAGE_DOS_HEADER
;; add esi, dword ptr [esi].e_lfanew
add esi, dword ptr [esi+3ch]
mov dwNTHeader, esi
assume esi : ptr IMAGE_NT_HEADERS
;; update the number of section
mov cx, word ptr [esi].FileHeader.NumberOfSections
movzx ecx, cx
;; 增加节的数目
inc word ptr [esi].FileHeader.NumberOfSections
push dword ptr [esi].OptionalHeader.FileAlignment
pop dwFileAlig
push dword ptr [esi].OptionalHeader.SectionAlignment
pop dwSecAlig
;; move esi point to section table
;; 在NT头结构下面跟着的就是N个节表街头.加上NT头结构的长度就为第一个节表
add esi, sizeof IMAGE_NT_HEADERS
;; store the last section table
;; 在这里保存最后一个节表的偏移,因为一会在计算新节的RVA和offset要应用。
mov eax, sizeof IMAGE_SECTION_HEADER
mov ebx, ecx
imul ebx
;; esi 为新节节表的偏移
;; 到这里可以判断下SizeOfHeader大小以便检测是否有空间加入新节,这里没有做判断
;; 一般没有加过壳的程序都会有空间加入,这个也是按照File Alignment对齐的所以就会有
;; 剩余的空隙加入
add esi, eax ; esi = the end of orig last section fva
push esi
;; 这里保存了原最后节表的偏移,为了设置新节的地址做准备
sub esi, sizeof IMAGE_SECTION_HEADER ; esi = the orig last section fva
mov dwLastSecTbl, esi
pop esi
;; set new section table
;; 设置新节节表名
assume esi : ptr IMAGE_SECTION_HEADER
;; set section name
push esi
lea edi, [esi].Name1
mov esi, pSectionName
CopySectionNameLoop:
lodsb
test al, al
jz EndCopySectionNameLoop
stosb
jmp CopySectionNameLoop
EndCopySectionNameLoop:
pop esi
;; set section characteristics
;; 设置设置节的属性,一般关心的有三个属性,读写执行
;; 0E00000E0h为设置可读可写可执行三个属性的或运算的值
;; 有些杀毒软件的启发式搜索也会检查入口点节是否存在写权限
;; 来判断是否被病毒感染,有些多态病毒需要自解密自身所有需要
;; 写权限,在壳被恶意程序利用后,现在的杀毒软件有可能报告为
;; Heru.XXX或者XCrypt之类,都是启发式惹的祸,过此类检测也是有
;; 方法的,例如启动后将的代码写到栈中执行.现在的病毒做的越来越像壳
;; 壳整的越来越像病毒,两者只有目的上的区别了。
push 0E00000E0h
pop dword ptr [esi].Characteristics
;; set section virtualsize
;; 这里设置节的真实大小,VirtualSize表示新节的真实大小,没有经过对齐后的
push dwSectionSize
pop dword ptr [esi].Misc.VirtualSize
;; set section sizeofrawdata
;; 节的SizeOfRawData为此节在文件中经过文件对齐后的大小
;; PEAlign函数用于计算节对齐后的大小,将在附件的代码中给出
invoke PEAlign, dwSectionSize, dwFileAlig
mov dword ptr [esi].SizeOfRawData, eax
;; set section virtualaddress
;; 设置新节的内存偏移和文件偏移需要上一节的一个信息
;; 新节的内存偏移 = 上一节的内存偏移 + 上一节经过节对齐后的长度
;; 新节的文件偏移 = 上一节的文件偏移 + 上一节进过文件对齐后的长度
mov eax, dwLastSecTbl ; eax = orig last section table fva
assume eax : ptr IMAGE_SECTION_HEADER
mov ecx, dword ptr [eax].VirtualAddress
add ecx, dword ptr [eax].Misc.VirtualSize ; ecx = new section rva
mov ebx, dword ptr [eax].PointerToRawData
add ebx, dword ptr [eax].SizeOfRawData ; ebx = new section fva
invoke PEAlign, ecx, dwSecAlig
mov dword ptr [esi].VirtualAddress, eax
;; set section pointertorawdata
invoke PEAlign, ebx, dwFileAlig
mov dword ptr [esi].PointerToRawData, eax
;; update the sizeofimage
;; SizeofImage表示从文件到内存映射文件的内容通过节对齐的大小
;; 这个值等于当前最后一节的内存偏移 + 当前最后一节的经过节对齐的大小
;; 大家可以思考一下。这个值很有用,可以利用此值做一些特殊的感染来躲过启发式
;; 搜索。呵呵...
mov eax, dword ptr [esi].VirtualAddress
add eax, dword ptr [esi].Misc.VirtualSize
invoke PEAlign, eax, dwSecAlig
mov edx, dwNTHeader
assume edx : ptr IMAGE_NT_HEADERS
mov dword ptr [edx].OptionalHeader.SizeOfImage, eax
push dword ptr [esi].PointerToRawData
pop edi
add edi, pMem
;; clear the new sec
;; 在这里做一下清0工作ZeroMemory
mov ecx, dwSectionSize
xor eax, eax
cld
rep stosb
;; 此函数的返回值,新节节表的文件偏移
mov eax, esi
assume esi : nothing
assume eax : nothing
assume edx : nothing
ret
AddSection endp
添加新节为最基本的感染方式,感染这个词用在这里虽然有些不太恰当,但是我实在想不到用什么更形象词来描述此等行为。感染算法在病毒领域有很多的讨论,感染技术很多配合入口点技术来一起讨论。如果真要详细讨论此技术怕是要在开一个专题讨论才够。
在附件中的代码由于要修改代码节自身,所以添加新节的程序在连接时要用到
/SECTION:.text,ERW的选项.如果在RadAsm中配置连接选项时要改为/SECTION:.text|ERW才可以正常连接。
附件程序的使用为在AddSection同目录下放置一名为target.exe程序直接运行AddSection后即可.可使用ollydbg调试target.exe查看.
平常很少发帖,在专题文章的质量和可读性上希望大家多多提要求。。。 |
|