通用SHELLCODE编写
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从代码区复制