SHELL手动加壳
作者:互联网
SHELL
壳的运行原理:
加壳过的EXE文件是可执行文件,它可以同正常的EXE文件一样执行。用户执行的实际上是外壳程序,这个外壳程序负责把用户原来的程序在内存中解压缩,并把控制权交还给解开后的真正程序,这一切工作都是在内存中运行的,整个过程对用户是透明的。
•编译:将单个的 .c 或 .cpp 编译成中间文件 (.obj),在 VS 下,这个过程由 cl.exe 程序完成。
•链接:将编译出的 .obj 中间文件、系统的启动文件和用到的库文件链接成一个可执行文件 (.exe)。VS 下由 link.exe 程序完成
•装载:将一个可执行文件映射到虚拟地址空间并执行,由操作系统完成。在装载的过程中,完了让程序正常被执行,会有下列几个步骤:
1.判断是否开启重定位,如果开启了,将 PE 文件加载到指定位置,并且修复目标PE 文件的重定位。
2.遍历导入表,加载使用到的所有模块到内存,修复模块相关的信息,并根据导入表中的函数名称,填充所有的 IAT 地址项。
3.查看当前是否存在 TLS 回调函数,如果存在,则传入进程创建事件,依次调用 所有的 TLS 回调函数。
4.以 PE 文件中的 AddressOfEntryPoint 为起始位置,创建线程并运行。
MZ 头:
(0x00) WORD e_magic: 标识当前是一个有效的 DOS 文件,必须为 0x5A4D。
(0x3C) LONG e_lfanew: 保存了一个偏移,用于找到 NT 头部分。文件头:对于 NumberOfSections 字段,它保存的是当前 PE 文件内包含的区段数量,区段的数量必须在 0~96之间,如果超出了这个范围,PE加载器在加载文件时,会提示无效的 PE 文件。
对于 Characteristics 字段,标志位 IMAGE_FILE_RELOCS_STRIPPED 用于标识当前是否包含重定位信息,如果将此字段设置为 0,则标识不包含重定位信息,必须将其加载到扩展头中 ImageBase 指定的加载基址。可用于关闭程序的重定位。
(0x00) WORD Machine: 程序的运行平台,通常为 0x14C 或 0x8664
(0x02) WORD NumberOfSections: 区段的数量,增删区段必须修改
(0x12) WORD Characteristics: 文件属性标识字段,可用于区分 dll/exe 等扩展头:
(0x10) DWORD AddressOfEntryPoint: 保存程序的入口点 RVA 。
(0x1C) DWORD ImageBase: 默认的加载基址,通常是 0x400000\0x10000000。
(0x20) DWORD SectionAlignment: 内存中的对齐力度,通常是 0x1000。
(0x24) DWORD FileAlignment: 文件中的对齐力度,通常是 0x200。
(0x38) DWORD SizeOfImage: 映像文件的大小,可以不被对齐。
(0x46) WORD DllCharacteristics: 属性字段,可用于关闭动态基址。
[0]导出表: 最多有一个,保存所有导出的函数、对象、类等信息。
[1]导入表: 可能有多个,保存所有使用到的模块和函数的信息。
[2]资源表: 三层结构,保存用到的所有资源,例如图标、对话框等。
[5]重定位: 由多个重定位块组成,保存了需要重定位的项所在的地址。
[9]TLS表: 延迟加载表,保存了 TLS 变量和函数的信息。
区段表:PE 文件中的每一个区段都有一个专门的结构体进行描述,这些结构体组合成了一张区段表,保存在扩展头的后面,可以使用 IMAGE_FIRST_SECTION 找到。在进行加壳时,通常都会添加一个新的区段保存壳相关的代码,与之相应的也需要添加一个新的区段结构,除此之外,我们还需要修改文件头中的区段数量以及扩展头中的映像大小等字段。
(0x00) BYTE Name[IMAGE_SIZEOF_SHORT_NAME]: 区段名称,通常具有意义。
(0x08) DWORD VirtualSize: 区段在内存中的大小。
(0x0C) DWORD VirtualAddress: 区段基址在内存中的 RVA。
(0x10) DWORD SizeOfRawData: 区段在文件中占用的大小。
(0x14) DWORD PointerToRawData: 区段基址在文件中的 FOA。
(0x24) DWORD Characteristics: 主要描述了区段的属性,例如是否可执行。导出表:导出表记录了当前模块所有被导出内容的信息,最多只会存在一张,其中保存了一些重要的字段,在这里我们主要需要关注的是其中的三张表,其中序号表和名称表一一对应,序号表中存储的是地址表的下标,只要理清楚了三张表之间的关系,相应的就可以 实现自己的 GetProcAddress 了:
导入表:导入表记录当前程序使用了哪些模块的哪些函数,如果使用了多个模块,相应的也会存在多张导入表,最终会以一个全0 的导入表结构结尾。相对来讲,导入表结构中的 INT并不是必不可少的,即使 INT内没有保存任何的内容,只要确保 IAT中的值是有效的,程序仍然可以执行。
当文件没有加载到内存时,IAT和 INT中保存的通常序号或导入名称结构的RVA,一旦程序被加载到内存中,PE加载器就会为 IAT填充函数的真实地址,填充的过程如下可以使用下面的图进行描述:
重定位表:当程序使用了操作全局变量的语句时时,生成的 OPCODE 中会直接包含有绝对地址,一旦发生重定位,使用这个地址就会产生问题,所以需要通过重定位表保存所有需要重定位的地址,在程序运行时交由 PE 加载器进行修复。重定位地址的修复依赖下面的公式:
重定位后的VA = 重定位前的VA – 默认加载基址 + 实际加载基址
一个 PE 文件中可以有多个重定位块,每一个重定位块由一个重定位结构和一组重定位项组成,用于描述 一个分页内 所有需要重定位的地址 所在的位置:
typedef struct _IMAGE_BASE_RELOCATION {
DWORD VirtualAddress; // 需要重定位的分页 RVA
DWORD SizeOfBlock; // 重定位块的大小
// WORD TypeOffset[1]; // 所在区段的偏移
} IMAGE_BASE_RELOCATION;在 PE文件中,一个地址由三个部分组成,分别是 加载基址、区段RVA以及区段内偏移,其中 加载基址 和 区段的RVA 可能都会有所改变,只要任意一个产生了变化,都需要为它进行重定位。
暴力搜索内存的实现: 几乎所有的 win32 可执行文件(pe 格式文件)运行的时候都会加载 kernel32.dll,可执行文件进入入口点执行后 esp 存放的一般是 Kernel32.DLL 中的某个地址,所以沿着这个地址向上查找就可以找到 kernel32 的基地址。
__asm
{
mov ebx, dword ptr ss : [esp]
and ebx, 0xFFFF0000
cmp_label:
cmp word ptr ds : [ebx], 0x5A4D
je end_label
sub ebx, 0x10000
jmp cmp_label
end_label:
mov eax, ebx
}使用PEbble获取基址的相关结构体
// 查看 PEB 结构体,找到 0x0C 偏移位置的 _PEB_LDR_DATA
0:000> dt _PEB
ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
+0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
+0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
+0x003 IsPackagedProcess : Pos 4, 1 Bit
+0x003 IsAppContainer : Pos 5, 1 Bit
+0x003 IsProtectedProcessLight : Pos 6, 1 Bit
+0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
+0x004 Mutant : Ptr32 Void
+0x008 ImageBaseAddress : Ptr32 Void
+0x00c Ldr : Ptr32 _PEB_LDR_DATA
// 查看 _PEB_LDR_DATA 结构,其中保存了三个双向链表的结构体
0:000> dt _PEB_LDR_DATA
ntdll!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr32 Void
+0x00c InLoadOrderModuleList : _LIST_ENTRY*
+0x014 InMemoryOrderModuleList : _LIST_ENTRY*
+0x01c InInitializationOrderModuleList : _LIST_ENTRY*
+0x024 EntryInProgress : Ptr32 Void
+0x028 ShutdownInProgress : UChar
+0x02c ShutdownThreadId : Ptr32 Void
// 每一个节点都对应了同一个结构体的不同位置
0:000> dt _LDR_DATA_TABLE_ENTRY -b
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x000 Flink : Ptr32
+0x004 Blink : Ptr32
+0x008 InMemoryOrderLinks : _LIST_ENTRY
+0x000 Flink : Ptr32
+0x004 Blink : Ptr32
+0x010 InInitializationOrderLinks : _LIST_ENTRY
+0x000 Flink : Ptr32
+0x004 Blink : Ptr32
+0x018 DllBase : Ptr32
+0x01c EntryPoint : Ptr32
+0x020 SizeOfImage : Uint4B
+0x024 FullDllName : _UNICODE_STRING
+0x000 Length : Uint2B
+0x002 MaximumLength : Uint2B
+0x004 Buffer : Ptr32
+0x02c BaseDllName : _UNICODE_STRING
+0x000 Length : Uint2B
+0x002 MaximumLength : Uint2B
+0x004 Buffer : Ptr32手动遍历 PEB 查找 Kernel32.dll 的基址
// 获取到 PEB 结构体的地址
0:000> !peb
PEB at 00553000
// 查看 PEB 结构体的内容
0:000> dt _PEB 00553000
ntdll!_PEB
+0x000 InheritedAddressSpace : 0 ''
+0x001 ReadImageFileExecOptions : 0 ''
+0x002 BeingDebugged : 0x1 ''
+0x003 BitField : 0x4 ''
+0x003 ImageUsesLargePages : 0y0
+0x003 IsProtectedProcess : 0y0
+0x003 IsImageDynamicallyRelocated : 0y1
+0x003 SkipPatchingUser32Forwarders : 0y0
+0x003 IsPackagedProcess : 0y0
+0x003 IsAppContainer : 0y0
+0x003 IsProtectedProcessLight : 0y0
+0x003 IsLongPathAwareProcess : 0y0
+0x004 Mutant : 0xffffffff Void
+0x008 ImageBaseAddress : 0x00770000 Void
+0x00c Ldr : 0x7746dca0 _PEB_LDR_DATA
// 查看 Ldr 字段内的数据
0:000> dt _PEB_LDR_DATA 0x7746dca0 -b
ntdll!_PEB_LDR_DATA
+0x000 Length : 0x30
+0x004 Initialized : 0x1 ''
+0x008 SsHandle : (null)
// 加载顺序 当前模块 -> ntdll.dll -> kernel32.dll
+0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0xab31f8 - 0xab3998 ]
// mov eax, dword ptr [LDR地址 + 0x0C] 第一个模块结构的地址
+0x000 Flink : 0x00ab31f8
+0x004 Blink : 0x00ab3998
+0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0xab3200 - 0xab39a0 ]
+0x000 Flink : 0x00ab3200
+0x004 Blink : 0x00ab39a0
// 初始化顺序 ntdll.dll -> kernelbase.dll -> kernel32.dll
+0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0xab3100 - 0xab35e8 ]
+0x000 Flink : 0x00ab3100 <------------------
+0x004 Blink : 0x00ab35e8
+0x024 EntryInProgress : (null)
+0x028 ShutdownInProgress : 0 ''
+0x02c ShutdownThreadId : (null)
// 遍历初始化顺序的链表,找到第一项,因为遍历到的节点位于 _LDR_DATA_TABLE_ENTRY
// 偏移为 0x10 的位置,所以需要将地址 -0x10 获取到结构体的首地址
0:000> dt _LDR_DATA_TABLE_ENTRY -b (0x00ab3100-0x10)
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0xab35d8 - 0xab31f8 ]
+0x000 Flink : 0x00ab35d8
+0x004 Blink : 0x00ab31f8
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0xab35e0 - 0xab3200 ]
+0x000 Flink : 0x00ab35e0
+0x004 Blink : 0x00ab3200
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0xab39a8 - 0x7746dcbc ]
+0x000 Flink : 0x00ab39a8 <------------------
+0x004 Blink : 0x7746dcbc
+0x018 DllBase : 0x77350000
+0x01c EntryPoint : (null)
+0x020 SizeOfImage : 0x19a000
+0x024 FullDllName : _UNICODE_STRING "C:\Windows\SYSTEM32\ntdll.dll"
+0x000 Length : 0x3a
+0x002 MaximumLength : 0x3c
+0x004 Buffer : 0x00ab2fd0 "C:\Windows\SYSTEM32\ntdll.dll"
+0x02c BaseDllName : _UNICODE_STRING "ntdll.dll"
+0x000 Length : 0x12
+0x002 MaximumLength : 0x14
+0x004 Buffer : 0x77359270 "ntdll.dll"
// 继续遍历初始化顺序的链表,找到第二项
0:000> dt _LDR_DATA_TABLE_ENTRY -b (0x00ab39a8-0x10)
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0x7746dcac - 0xab35d8 ]
+0x000 Flink : 0x7746dcac
+0x004 Blink : 0x00ab35d8
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0x7746dcb4 - 0xab35e0 ]
+0x000 Flink : 0x7746dcb4
+0x004 Blink : 0x00ab35e0
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0xab35e8 - 0xab3100 ]
+0x000 Flink : 0x00ab35e8 <------------------
+0x004 Blink : 0x00ab3100
+0x018 DllBase : 0x75e80000
+0x01c EntryPoint : 0x75f6cd80
+0x020 SizeOfImage : 0x1fc000
+0x024 FullDllName : _UNICODE_STRING "C:\Windows\System32\KERNELBASE.dll"
+0x000 Length : 0x44
+0x002 MaximumLength : 0x46
+0x004 Buffer : 0x00ab3aa0 "C:\Windows\System32\KERNELBASE.dll"
+0x02c BaseDllName : _UNICODE_STRING "KERNELBASE.dll"
+0x000 Length : 0x1c
+0x002 MaximumLength : 0x1e
+0x004 Buffer : 0x00ab3ac8 "KERNELBASE.dll"
// 初始化顺序的第三个模块就是 kernel32.dll
0:000> dt _LDR_DATA_TABLE_ENTRY -b (0x00ab35e8-0x10)
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY [ 0xab3998 - 0xab30f0 ]
+0x000 Flink : 0x00ab3998
+0x004 Blink : 0x00ab30f0
+0x008 InMemoryOrderLinks : _LIST_ENTRY [ 0xab39a0 - 0xab30f8 ]
+0x000 Flink : 0x00ab39a0
+0x004 Blink : 0x00ab30f8
+0x010 InInitializationOrderLinks : _LIST_ENTRY [ 0x7746dcbc - 0xab39a8 ]
+0x000 Flink : 0x7746dcbc // 当前所在的位置,可以作为基址查找其他的内容
+0x004 Blink : 0x00ab39a8
+0x018 DllBase : 0x74dc0000
+0x01c EntryPoint : 0x74dd5f70
+0x020 SizeOfImage : 0xe0000
+0x024 FullDllName : _UNICODE_STRING "C:\Windows\System32\KERNEL32.DLL"
+0x000 Length : 0x40
+0x002 MaximumLength : 0x42
+0x004 Buffer : 0x00ab36e0 "C:\Windows\System32\KERNEL32.DLL"
+0x02c BaseDllName : _UNICODE_STRING "KERNEL32.DLL"
+0x000 Length : 0x18
+0x002 MaximumLength : 0x1a
+0x004 Buffer : 0x00ab3708 "KERNEL32.DLL"
// 可以通过偏移获取到模块的基址\大小\路径
0:000> dd 0x00ab35e8+0x08 l1
00ab35f0 74dc0000
0:000> dd 0x00ab35e8+0x10 l1
00ab35f8 000e0000
0:000> dd 0x00ab35e8+0x18 l1
00ab3600 00ab36e0
0:000> du 00ab36e0
00ab36e0 "C:\Windows\System32\KERNEL32.DLL"
获取 kernel32.dll 的代码
// 按照加载顺序
mov eax, dword ptr fs : [0x30]
mov eax, dword ptr[eax + 0x0C]
mov eax, dword ptr[eax + 0x0C]
mov eax, dword ptr[eax]
mov eax, dword ptr[eax]
mov eax, dword ptr[eax + 0x18]
// 按照初始化顺序
mov eax, dword ptr fs : [0x30]
mov eax, dword ptr[eax + 0x0C]
mov eax, dword ptr[eax + 0x1C]
mov eax, dword ptr[eax]
mov eax, dword ptr[eax]
mov eax, dword ptr[eax + 0x08]实现自己的 GetProcAddress
DWORD MyGetProcAddress(DWORD Module, LPCSTR FunName)
{
// 获取 Dos 头和 Nt 头
auto DosHeader = (PIMAGE_DOS_HEADER)Module;
auto NtHeader = (PIMAGE_NT_HEADERS)(Module + DosHeader->e_lfanew);
// 获取导出表结构
DWORD ExportRva = NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
auto ExportTable = (PIMAGE_EXPORT_DIRECTORY)(Module + ExportRva);
// 找到导出名称表、序号表、地址表
auto NameTable = (DWORD*)(ExportTable->AddressOfNames + Module);
auto FuncTable = (DWORD*)(ExportTable->AddressOfFunctions + Module);
auto OrdinalTable = (WORD*)(ExportTable->AddressOfNameOrdinals + Module);
// 遍历找名字
for (int i = 0; i < ExportTable->NumberOfNames; ++i)
{
// 获取名字
char* Name = (char*)(NameTable[i] + Module);
if (!strcmp(Name, FunName))
return FuncTable[OrdinalTable[i]] + Module;
}
return -1;
}手动加壳的步骤:
1. 修改文件头中的 NumberOfSection,添加区段数量
2. 添加新的区段表结构体,进行如下设置
2.1 设置区段的名称,注意最大长度为 8 字节
2.2 设置区段的大小,分为 Misc.VirtualSize 和 SizeofRawData
2.3 设置区段的 RVA = 上一个区段的RVA + 上一个区段对齐后的内存大小
2.4 设置区段的 FOA = 上一个区段的FOA + 上一个区段对齐后的文件大小
2.5 修改区段的属性为 0xE00000E0,表示读、写、执行
3. 在 PE 文件内填充新的区段,填充的大小应和 SizeofRawData 相同
4. 修改扩展头中 SizeOfImage 的大小,通常是最后一个区段的内存大小 + 最后一个区段的基址RVA
5. 从壳代码跳转到原始 OEP 位置用LORDPE加区段
更改完毕后,要用010editor增加200个字节。否则大小还是不一样无法打开。
实现功能的代码
#include <windows.h>
// 自己实现一个获取函数地址的函数
DWORD MyGetProcAddress(DWORD ModulBase, LPCSTR FunName)
{
// 1. 获取到 DOS 头
auto DosHeader = (PIMAGE_DOS_HEADER)ModulBase;
// 2. 获取到 NT 头
auto NtHeader = (PIMAGE_NT_HEADERS)(ModulBase + DosHeader->e_lfanew);
// 3. 获取到导出表的RVA
DWORD ExportRva = NtHeader->OptionalHeader.DataDirectory[0].VirtualAddress;
// 4. 获取到导出表结构体
auto ExportTable = (PIMAGE_EXPORT_DIRECTORY)(ExportRva + ModulBase);
// 5. 获取到导出表结构体的三张表
WORD * EOT = (WORD*)(ExportTable->AddressOfNameOrdinals + ModulBase);
DWORD * EAT = (DWORD*)(ExportTable->AddressOfFunctions + ModulBase);
DWORD * ENT = (DWORD*)(ExportTable->AddressOfNames + ModulBase);
// 6. 遍历名称表比对名称
for (WORD i = 0; i < ExportTable->NumberOfNames; ++i)
{
// 7. 区分大小写比对名称
char* name = (char*)(ModulBase + ENT[i]);
if (!strcmp(name, FunName))
{
// 导出表存储的是 EVA 所有需要加基址
return EAT[EOT[i]] + ModulBase;
}
}
return -1;
}
int main()
{
__asm
{
; 跳转到指令区
jmp shellcode
; "user32.dll" - 0x29
__asm _emit('u') __asm _emit('s') __asm _emit('e') __asm _emit('r')
__asm _emit('3') __asm _emit('2') __asm _emit('.') __asm _emit('d')
__asm _emit('l') __asm _emit('l') __asm _emit(0x0)
; "MessageBoxA" - 0x1E
__asm _emit('M') __asm _emit('e') __asm _emit('s') __asm _emit('s')
__asm _emit('a') __asm _emit('g') __asm _emit('e') __asm _emit('B')
__asm _emit('o') __asm _emit('x') __asm _emit('A') __asm _emit(0x0)
; "LoadLibraryA" - 0x12
__asm _emit('L') __asm _emit('o') __asm _emit('a') __asm _emit('d')
__asm _emit('L') __asm _emit('i') __asm _emit('b') __asm _emit('r')
__asm _emit('a') __asm _emit('r') __asm _emit('y') __asm _emit('A')
__asm _emit(0x0)
shellcode:
; 通过 getPC 获取到一个基址
call next_code
next_code:
pop edi
; 获取 kernel32.dll 的基址
mov eax, dword ptr fs : [0x30] ; 获取 PEB
mov eax, dword ptr[eax + 0x0C] ; 获取 LDR
mov eax, dword ptr[eax + 0x1C] ; ntdll
mov eax, dword ptr[eax] ; kernelbase.dll
mov eax, dword ptr[eax] ; kernel32.dll
mov ebx, dword ptr[eax + 0x08] ; base
; 获取到 LoadLibraryA 的地址
lea edx, [edi - 0x12]
push edx
push ebx
call MyGetProcAddress
add esp, 0x08
; 加载 user32.dll
lea edx, [edi - 0x29]
push edx
call eax
; 获取 MessageBoxA 的地址
lea edx, [edi - 0x1E]
push edx
push eax
call MyGetProcAddress
add esp, 0x08
; 调用 MessageBoxA
push 0
push 0
push 0
push 0
call eax
_asm _emit(0xE9) _asm _emit(0x00) _asm _emit(0x00) _asm _emit(0x00)
_asm _emit(0x00)
}
return 0;
}实现功能的shellcode
//弹框
EB 24 75 73 65 72 33 32 2E 64 6C 6C 00 4D 65 73 73 61 67 65 42 6F 78 41 00 4C 6F 61 64 4C 69 62 72 61 72 79 41 00 E8 00 00 00 00 5F 64 A1 30 00 00 00 8B 40 0C 8B 40 1C 8B 00 8B 00 8B 58 08 8D 57 EE 52 53 E8 11 FF FF FF 83 C4 08 8D 57 D7 52 FF D0 8D 57 E2 52 50 E8 FE FE FF FF 83 C4 08 6A 00 6A 00 6A 00 6A 00 FF D0 E9 00 00 00 00
//MyGetProcAddress
55 8B EC 83 EC 0C 53 8B 5D 08 56 57 33 FF 8B 43 3C 8B 44 18 78 03 C3 8B 48 24 8B 50 20 03 CB 89 4D F8 03 D3 8B 48 1C 8B 40 18 03 CB 89 4D F4 89 55 FC 89 45 08 85 C0 74 44 33 F6 0F 1F 44 00 00 8B 04 B2 8B 4D 0C 03 C3 8A 10 3A 11 75 1A 84 D2 74 12 8A 50 01 3A 51 01 75 0E 83 C0 02 83 C1 02 84 D2 75 E4 33 C0 EB 05 1B C0 83 C8 01 85 C0 74 16 8B 55 FC 47 0F B7 F7 3B 75 08 72 C3 5F 5E 83 C8 FF 5B 8B E5 5D C3 8B 45 F8 8B 4D F4 5F 0F B7 04 70 5E 8B 04 81 03 C3 5B 8B E5 5D C3用010Editor打开。代码贴在最后面我们自己新添加的区段内。
用WinDbg打开
然后打补丁导出更改完的EXE文件。加壳完毕。
效果:
原本是输出helloworld:
最后是先弹框,再输出helloworld
标签:__,SHELL,eax,手动,0x000,加壳,区段,emit,asm 来源: https://www.cnblogs.com/ltyandy/p/11364966.html