shellcode生成
作者:休闲 来源:焦点 浏览: 【大 中 小】 发布时间:2026-10-17 06:19:50 评论数:
拿Metasploit生成一个payload,丢给杀软,十有八九被抬走。真正的shellcode生成,核心就一句话:在字节层面跟杀软和操作系统玩猫鼠游戏。
我见过最野的写法,是把shellcode拆成三明治——开头一段正常代码,中间夹着真正的恶意载荷,尾巴再补一段正常代码。杀软扫描的时候,特征码匹配的是开头和结尾,中间那段被当成了数据。这招现在不太灵了,但思路还在:shellcode生成不是写代码,是写布局。
真正的难点在编码。你写一段C代码,编译完是机器码,但机器码里有太多空字节(\x00)和固定特征。空字节在漏洞利用里是致命的,因为字符串函数遇到\x00就截断。所以得编码,把坏字节全滤掉。最常见的做法是异或编码,拿一个单字节密钥把整个载荷挨个异或一遍。解密的时候,shellcode自己先跑一段解码循环,把真实载荷还原出来。
这里有个细节很多人忽略:解码器的位置。放在载荷前面,意味着解码器本身也是shellcode的一部分,它也得过杀软。所以解码器得写短,还得写得不像恶意代码。我见过有人用FPU指令做解码器,因为浮点指令的字节序列跟常见恶意特征完全对不上,能骗过不少静态扫描。
再往深了说,编码方式决定了你的shellcode能在什么环境下跑。比如x86架构下,你可以用Alpha混合编码,把任意字节转成可打印字符,专门用来对付那些只允许字母数字输入的漏洞场景。这种编码生成的shellcode全是大写小写字母加数字,看着像普通文本,实际上跑起来就是一段完整的攻击载荷。
但编码只是第一步,真正让shellcode活下来的是动态解析。Windows下你没法硬编码API地址,因为不同版本的系统,kernel32.dll加载基址不一样。所以shellcode得自己找。经典手法是FS段寄存器指向TEB,TEB里找PEB,PEB里找装载模块链表,遍历模块找kernel32,再从导出表里翻GetProcAddress的地址。这套流程写下来大概几十个字节,但每一步都讲究——比如遍历模块的时候,你得判断模块名是不是大写,因为内核里存的模块名全是大写。
实际操作里,我习惯先写一个能弹计算器的极简shellcode,大概30字节,跑通了再往上加功能。加功能的过程就是不断跟编码器较劲的过程。比如你想调用WinExec,参数得压栈,但栈上不能有坏字节,所以得用sub esp, 0xXX来腾空间,而不是直接push。这些细节没法靠理论推出来,全是试出来的。
杀软也在进化。现在的主流杀软不看静态特征,直接上沙箱动态跑。你shellcode一执行,解码器一跑,真实载荷落地,沙箱全给录下来。所以现在流行的是分离式shellcode——真正的恶意逻辑不在shellcode里,shellcode只负责下载下一阶段payload。这样就算沙箱跑完了第一段,也看不到真实意图。
说到底,shellcode生成是个体力活。你得反复调试,看哪条指令编译出来字节太长,哪条指令换一种写法能省两个字节。有时候为了省一个字节,得把整个解码逻辑重写一遍。这种抠字节的功夫,跟写业务代码完全两个世界,但那种把一个载荷从500字节压到200字节的满足感,写十年业务代码都体会不到。
