【逆向】ShellCode分析基础
1、shellcode经常与漏洞利用一起使用,或者被病毒等恶意代码用于进程注入。所以它们总是在一个程序中被植入运行。
2、shellcode是一段与位置无关的代码。因为它在内存中的位置是随机不可控的,所以在编写shellcode时应该避开对内存地址进行硬编码。
3、shellcode中包含数据和代码。所以在以位置无关的方式访问数据时,需要有一个基础地址(基址)加上或减去偏移的方式来访问。shellcode通常使用当前指令指针(EIP)作为基础地址来使用。为了在代码执行时获取EIP的值,可以根据call/pop指令执行原理来变相获取EIP值。
4、当一个call指令执行时,处理器会将下一条指令地址(函数返回地址)保存到栈上,shellcode可以在一个call指令后面立刻执行pop指令,从栈上取出指令地址作为基础地址来使用。通过基础地址就可以对代码中的数据进行自由访问了。
5、shellcode因为不能硬编码内存地址,所以它在通过API与系统进行交互时,必须自己动态加载获取需要的模块和API地址。为了完成这个任务,shellcode经常使用LoadLibraryA和GetProcess函数。如果shellcode有这两个函数的访问权限,它就可以加载任意模块到系统中并获取导出函数地址。这两个函数都是从Kernel32.dll中导出的,所以shellcode必须在内存中找到Kernel32.dll,并解析Kernel32.dll的PE文件,搜索并获取以上函数的地址。
6、想要在内存中找到Kernel32.dll基址我们需要用到下图中的一些数据结构。
1 typedef struct _PEB_LDR_DATA 2 { 3 ULONG Length; // +0x00 4 BOOLEAN Initialized; // +0x04 5 PVOID SsHandle; // +0x08 6 LIST_ENTRY InLoadOrderModuleList; // +0x0c 模块加载顺序 7 LIST_ENTRY InMemoryOrderModuleList; // +0x14 模块在内存中的顺序 8 LIST_ENTRY InInitializationOrderModuleList; // +0x1c 模块初始化时的顺序 9 } PEB_LDR_DATA,*PPEB_LDR_DATA; 10 // 该结构体包含了三个双向链表(_LIST_ENTRY),它们分别指向了_LDR_DATA_TABLE_ENTRY结构体。
1 typedef struct _LIST_ENTRY 2 { 3 struct _LIST_ENTRY *Flink; // +0x00 4 struct _LIST_ENTRY *Blink; // +0x04 5 } LIST_ENTRY, *PLIST_ENTRY, *RESTRICTED_POINTER PRLIST_ENTRY; 6 // 这个双向链表指向了进程中加载的模块,结构中的每个指针,都指向了一个LDR_DATA_TABLE_ENTRY结构体。
1 typedef struct _LDR_DATA_TABLE_ENTRY 2 { 3 LIST_ENTRY InLoadOrderLinks; 4 LIST_ENTRY InMemoryOrderLinks; 5 LIST_ENTRY InInitializationOrderLinks; 6 PVOID DllBase; // 镜像基址 7 PVOID EntryPoint; // 入口点 8 ULONG SizeOfImage; // 镜像大小 9 UNICODE_STRING FullDllName; // 模块全路径字符串 10 UNICODE_STRING BaseDllName; // 模块名称字符串 11 ULONG Flags; 12 WORD LoadCount; 13 WORD TlsIndex; 14 union 15 { 16 LIST_ENTRY HashLinks; 17 struct 18 { 19 PVOID SectionPointer; 20 ULONG CheckSum; 21 }; 22 }; 23 union 24 { 25 ULONG TimeDateStamp; 26 PVOID LoadedImports; 27 }; 28 _ACTIVATION_CONTEXT * EntryPointActivationContext; 29 PVOID PatchInformation; 30 LIST_ENTRY ForwarderLinks; 31 LIST_ENTRY ServiceTagLinks; 32 LIST_ENTRY StaticLinks; 33 } LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; 34 // 每一个被加载的模块都对应一个该结构体,结构体中保存了该模块的一些信息。
Windows XP Professional Service Pack 3 (x86) (5.1, Build 2600)
1 lkd> dt -b _LDR_DATA_TABLE_ENTRY 2 nt!_LDR_DATA_TABLE_ENTRY 3 +0x000 InLoadOrderLinks // 加载时的顺序 4 +0x000 Flink // 5 +0x004 Blink // 6 +0x008 InMemoryOrderLinks // 内存中的顺序 7 +0x000 Flink // 8 +0x004 Blink // 9 +0x010 InInitializationOrderLinks // 初始化的顺序 10 +0x000 Flink // 11 +0x004 Blink // 12 +0x018 DllBase // 镜像基址 13 +0x01c EntryPoint // 入口点 14 +0x020 SizeOfImage // 镜像大小 15 +0x024 FullDllName // 模块路径 16 +0x000 Length // 17 +0x002 MaximumLength // 18 +0x004 Buffer // 19 +0x02c BaseDllName // 模块名称 20 +0x000 Length // 21 +0x002 MaximumLength // 22 +0x004 Buffer // 23 +0x034 Flags // 24 +0x038 LoadCount // 25 +0x03a TlsIndex // 26 +0x03c HashLinks // 27 +0x000 Flink // 28 +0x004 Blink // 29 +0x03c SectionPointer // 30 +0x040 CheckSum // 31 +0x044 TimeDateStamp // 32 +0x044 LoadedImports // 33 +0x048 EntryPointActivationContext // 34 +0x04c PatchInformation //
当通过PEB_LDR_DATA结构体中的三个双向链表遍历模块时,它们总是指向下一个或上一个LDR_DATA_TABLE_ENTRY结构体的同样位置。
例如:
PEB_LDR_DATA->InLoadOrderModuleList遍历时,指针指向下一个LDR_DATA_TABLE_ENTRY->InLoadOrderModuleList位置。
PEB_LDR_DATA->InMemoryOrderModuleList遍历时,指针指向下一个LDR_DATA_TABLE_ENTRY->InMemoryOrderModuleList位置。
PEB_LDR_DATA->InInitializationOrderModuleList遍历时,指针指向下一个LDR_DATA_TABLE_ENTRY->InInitializationOrderModuleList位置。
示例代码:
1 00404830 mov eax,dword ptr fs:[0x30] // PEB = FS:[0x30] 2 00404836 mov eax,dword ptr ds:[eax+0xC] // PEB_LDR_DATA = [PEB+0xC] 3 00404839 mov eax,dword ptr ds:[eax+0xC] // InLoadOrderModuleList 使用模块加载顺序遍历 4 0040483C mov eax,dword ptr ds:[eax+0x18] // eax = 00400000(LDR_DATA_TABLE_ENTRY.DllBase) 第一个DLL模块基址
1 00404830 mov eax,dword ptr fs:[0x30] // PEB = FS:[0x30] 2 00404836 mov eax,dword ptr ds:[eax+0xC] // PEB_LDR_DATA = [PEB+0xC] 3 00404839 mov eax,dword ptr ds:[eax+0x14] // InMemoryOrderModuleList 使用模块在内存中的顺序遍历 4 0040483C mov eax,dword ptr ds:[eax+0x10] // eax = 00400000(LDR_DATA_TABLE_ENTRY.DllBase) 第一个DLL模块基址
1 00404830 mov eax,dword ptr fs:[0x30] // PEB = FS:[0x30] 2 00404836 mov eax,dword ptr ds:[eax+0xC] // PEB_LDR_DATA = [PEB+0xC] 3 00404839 mov eax,dword ptr ds:[eax+0x1C] // InMemoryOrderModuleList 使用模块初始化时的顺序遍历 4 0040483C mov eax,dword ptr ds:[eax+0x8] // eax = 776C0000(LDR_DATA_TABLE_ENTRY.DllBase) 第一个DLL模块基址
7、从内存中获取Kernel32.dll基址后,我们就可以通过解析PE文件导出表来获取导出函数调用地址了,关于解析导出表获取导出函数的内容之前文章已经讲过,这里不再赘述。详情可以查阅这篇文章:https://www.cnblogs.com/SunsetR/p/11234093.html