壳要附加到软件本身,有很多方式进行。在这里使用最常用的一种方式,添加一个新节。这里我们先用文字的形式描述一下添加新 节的算法。然后给出一段代码并在注释中详细给出没条指令的解释。在这之前,首先给出一些名词的解释,以便刚接触的朋友可以熟悉。熟悉的朋友可以直接跳过。
名词解释:
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
[COLOR=Red] ;; 这里的dwOrigFileSize 是原始的文件大小
;; 因为你要添加新节,所以要多上那么一点
;; 点尺寸.这个尺寸就是APPEND_SIZE,如果
;; 这个尺寸也许会让你最后添加的程序后有
;; 一些多余的数据。也可以没有,不过有一
;; 点点麻烦。这就要计算添加后的
;; SizeOfImage了。等到以后讲解吧。[/COLOR]
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
[COLOR=Red] ;; 在建立映射后
;; 这段代码将映射后文件的指针存放在局部变量pMem.
;; 而指定的NT结构头指针存放到esi寄存器处。
;; 现在我们调用添加节函数添加一个新节,AddSection是
;; 我们这节的主要函数,将在下面讲解。
;; 增加一个新节
;; pMem:文件映射内存指针
;; g_szNewSectionName:新节的节名,这里是(.new)
;; g_dwNewSectionSize:新节的长度,这里是offset EndNewSection - offset NewSection[/COLOR]
invoke AddSection, pMem, offset g_szNewSectionName, g_dwNewSectionSize
push eax
mov esi, dwNTHeaderAddr
assume esi : ptr IMAGE_NT_HEADERS
[COLOR=Red] ;; 下面做的就是设置新节的中的原代码入口点,就是真正的入口地址[/COLOR].
mov ebx, dword ptr [esi].OptionalHeader.AddressOfEntryPoint
add ebx, dword ptr [esi].OptionalHeader.ImageBase
[COLOR=Red] ;; OrigAddressOfEntry这个变量在CryptFile底部的NewSection节中[/COLOR]
mov eax, offset OrigAddressOfEntry
mov dword ptr [eax], ebx
[COLOR=Red] ;; 更新入口点,将新节的入口点设置到NT头结构的AddressOfEntryPoint
;; 哪个节的VirusAddress被设置到AddressOfEntryPoint处,哪个节将会被先执行
;; 这也是最通常的入口点技术[/COLOR]
pop eax
assume eax : ptr IMAGE_SECTION_HEADER
push dword ptr [eax].VirtualAddress
pop dword ptr [esi].OptionalHeader.AddressOfEntryPoint
[COLOR=Red] ;; 下面的代码利用新节节表将新节的代码写入文件[/COLOR]
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:
[COLOR=Red] ;; 在这里获取地址
;; call指令会将下条指令的地址压入堆栈
;; 注意此指令的OPCODE为EB00000000
;; 病毒与Shellcode等常用此指令定位
;; 杀毒软件的启发式搜索常将此特征作为查找特征
;; 聪明的读者可以自己修改定位代码来躲过
;; 这类的查杀[/COLOR]
call GetEip
GetEip:
[COLOR=Red] ;; eax中有保存着当前的地址,标号为GetEip[/COLOR]
pop eax
add eax, offset OrigAddressOfEntry - offset GetEip
[COLOR=Red] ;; 两个偏移的差就是这两个地址之间的距离,它的距离 + 起始地址
;; 就为OrigAddressOfEntry的地址
;; 最后将OrigAddressOfEntry保存的值,也就是原来的入口节的地址
;; 送回eax中。[/COLOR]
mov eax, dword ptr [eax]
[COLOR=Red] ;; 跳到原入口点地址[/COLOR]
jmp eax
;; ----- 新节数据 -----
OrigAddressOfEntry dd ?
EndNewSection:
CryptFile endp
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课