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中存放加密的字符串,然后解密后进行加载