0x00 免责声明

本文仅用于技术讨论与学习,利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,本平台和发布者不为此承担任何责任。

0x01 注意事项

1.数据都用堆栈交互

2.尽量避免0x00的产生,影响shellcode的执行

3.注意堆栈平衡

4.不能有常量数据(如常量字符串)

5.避免使用全局变量

6.必须是Release版

7.不能直接调用系统API

0x02 测试代码

#include<windows.h>

int main(){  MessageBoxA(0, "test", 0, 0);  return 0;}

0x03 环境配置

直接这样编译会发现编译器会给你添加很多代码。如下:


编译器正常设置编译完的程序会默认带有一些安全设置,就比checkEsp,检查堆栈是否平衡等

1>自定义入口点

2>编译一些设置参照表

0x04 相应要求解释

1>不能用全局变量

因为我们编写shellcode时,使用的全局变量是自己的进程里面的全局变量,当加载到别的进程里,地址就会发生变化,因为一个进程启动会把字符串放置在一个特定的Section中(如.rdata或.data),而这个特定的节只有当前的进程知道如何寻找,其他的进程不知道如何寻找

所以通过这个点我们知道,我们需要的相关变量都需要是在堆栈中进行存储,比如要使用字符串的话,那么就需要使用字符数组来进行解决

char szA[] = "test"; 改为  char szA[] = {'t','e','s','t',0};

2>不能直接调用系统API

在 C/C++ 中,调用函数很容易。我们指定#include <>以使用特定的标头并通过其名称调用函数,在编译后它会把相应的函数地址写入导入表中,当我们移动shellcode到其他进程后会找不到相应的函数地址。

在 shellcode 中,我们不能这样做,因为不知道包含我们所需函数的 DLL 是否已加载到内存中,我们也不知道所需函数的地址。由于ASLR(地址空间布局随机化),DLL 不会每次都加载到相同的地址。此外,DLL 可能会随着每个新的 Windows 更新而更改,因此我们不能依赖 DLL 中的特定偏移量。

我们必须将 DLL 加载到内存中,并直接从 shellcode 中找到所需的函数。幸运的是,Windows API提供了两个有用的函数:LoadLibrary和GetProcAddress,我们可以使用它们来查找函数的地址。

解决方案:通过FS:[0x30] 找到PEB,然后通过PEB里的LDR链表 [PEB+0x0C]找到 kernel32.dll 的地址,然后我们遍历它的 IAT表,找到 LoadLibrary 和 GetProcAddress 函数。

3>避免NULL字节

NULL 字节的值为 0x00。在 C/C++ 代码中,NULL 字节被认为是字符串的终止符。因此,shellcode 中这些字节的存在可能会干扰目标应用程序的功能,并且我们的 shellcode 可能无法正确复制到内存中。

即使这种情况不是强制性的,也存在使用strcpy () 函数的常见情况,例如缓冲区溢出。此函数将逐字节复制字符串,并在遇到 NULL 字节时停止。因此,如果 shellcode 包含一个 NULL 字节,strcpy 函数将在该字节处停止,并且 shellcode 将不完整,正如您可以猜到的那样,它将无法正常工作。

此外,在某些特定情况下,shellcode 必须避免使用字符,例如 \r 或 \n,甚至只能使用字母数字字符。

所以这里如果使用到的mov eax,0都需要使用xor eax,eax来进行替代,这样避免NULL字节的出现

0x05 通用shellcode实现

步骤如下:

● 获取 kernel32.dll 基地址

● 查找GetProcAddress函数的地址

● 使用GetProcAddress查找LoadLibrary函数的地址

● 使用LoadLibrary加载 DLL(例如kernel32.dll)

● 使用GetProcAddress查找函数的地址(例如MessageBox)

● 指定函数参数

● 调用函数

1>获取kernel32.dll

获取kernel32.dll就是通过PEB_LDR_DATA的结构体,因为它其中有三个类似的双向链表,其中都存储着相关加载模块的信息,通过遍历就可以进行获取指定模块的基址,模块大小等信息

xor ecx, ecx              ; 绕过空字节的发生
mov eax, fs:[ecx + 0x30]  ; 拿到PEB结构体
mov eax, [eax + 0xc]      ; 拿到_PEB_LDR_DATA结构体
mov esi, [eax + 0x14]     ; 获取LDR结构体中的InMemoryOrderModuleList链表
lodsd                     ; EAX = ds:[esi],也就是InMemoryOrderModuleList指向的下一个LDR_MODULE的链表
xchg eax, esi             ; EAX = ESI, ESI = EAX
lodsd                     ; EAX = Third(kernel32)
mov ebx, [eax + 0x10]     ; EBX = Base address

2>获取导出表的位置

  • 查找 GetProcAddress 函数的地址

接着导出函数序号表中进行寻找,这里通过上面获得的ecx来继续寻找,这里说下为什么*2,原因就是一个序号是占两个字节的,*4的话就是函数地址是占4个字节

3>找到LoadLibrary函数地址

加载 user32.dll 库

4>这里之后就非常的轻松了,因为已经拿到了user32.dll的模块,并且GetProcAddress也拿到了,就可以直接通过GetProcAddress 获取我们自己想要的函数地址。

0x06 测试成果

shellcode自己用winhex从代码区复制