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
来源:oschina
链接:https://my.oschina.net/u/4328287/blog/3451087