Shellcode的原理与编写
简介
什么是shellcode
ShellCode是不依赖环境,放到任何地方都能够执行的机器码
编写ShellCode的方式有两种,分别是用编程语言编写或者用ShellCode生成器自动生成
ShellCode生成器生成的shellcode功能比较单一,常见的ShellCode生成器有shell storm、Msfvenom等
而用编程语言写的shellcode会更加突显灵活性,可以自己添加或修改功能
shellcode的原理
将shellcode注入缓冲区,然后欺骗目标程序执行它。而将shellcode注入缓冲区最常用的方法是利用目标系统上的缓冲区溢出漏洞。
环境搭建
1.修改程序入口点
为什么要修改程序入口点?VS新建一个32位的控制台程序,如下代码所示
int main()
{
return 0;
}然后把生成的程序拖入IDA中打开,在左边的函数窗口可以发现除了main函数以外还有一些其他函数,这是因为没有修改函数入口点所导致的

在VS中的项目属性找到链接器的高级选项进行修改函数入口点

在配置选项将Debug修改成Release, 解决方案配置也修改成Release


将重新生成的程序拖入IDA中, 可以发现少了很多的函数

2.关闭缓冲区安全检查
项目属性的C++代码生成处禁用安全检查
若没禁用安全检查,那么ShellCode则无法执行

3.设置项目兼容XP系统
在项目属性的C++代码生成处更改成MT(Release版本修改成MT,Debug版本则修改成MTD)

4.关闭生成清单

5.关闭生成调试信息

6.启用最大优化
选择最大优化,这样生成的shellcode会小些

探索PEB
什么是PEB
TEB:全称Thread Environment Block,中文名为线程环境块。系统在此TEB中保存频繁使用的线程相关的数据,一般存储在fs:[0]
PEB:全称Process Environment Block,中文名为进程环境块。存放进程信息,每个进程都有自己的PEB信息。通常存储在fs:[0x30]。这里主要通过PEB来获取kernel32.dll的基址
获取kernel32的基址
1.TEB->PEB
在TEB结构搜寻PEB,如下图所示TEB的结构代码, peb结构在30h的位置
在汇编里fs:[0]代表TEB的地址, 所以PEB的地址是fs:[30]

2.PEB->LDR
如下代码是PEB结构, LDR处于0C位置
LDR: 包含加载模块的信息
3.LDR->InLoadOrderModuleList
LDR指向PEB\_LDR\_DATA结构, 要注意下图三个用蓝色标明的成员,分别是InMemoryOrderModuleList、InLoadOrderModuleList、InInitializationOrderModuleLists,这三者分别代表模块在不同状态下的排列顺序
也就是说这三个链表存放着所有模块,只是存放顺序不一样,这里选择跟进InLoadOrderModuleList
InLoadOrderModuleList:按模块加载的顺序
InMemoryOrderModuleList :按内存排列的顺序
InInitializationOrderModuleLists:模块初始化的装载顺序

4.遍历InLoadOrderModuleList链表
依照InLoadOrderModuleList的模块排列顺序,第一个模块是teb.exe,也就是可执行文件本身;第二个模块是ntdll.dll;第三个模块是kernel32.dll
不管是在XP系统还是win7及以上的系统,前三个模块的排列顺序都是不变的,所以只需找到链表的第三个位置就是kernel32.dll
5.获取模块基址
如下图所示,链表是指向LDR_DATA_TABLE_ENTRY结构的指针,只需关注三个成员DLLBase、FullDllName、BaseDllName,而DLLBase代表模块基址
DLLBase:模块的基址
FullDllName:包含路径的模块名
BaseDllName:不包含路径的模块名

编写汇编
从运行结果可以发现,通过peb获取的kernel模块基址和LoadLibrary函数获取的模块基址是一致的

生成函数地址的规律
要先编写一个完整的ShellCode框架,首先要了解在VS中生成函数地址的规律,这样才能确定生成Shellcode的字节
单文件的函数生成规律
如下代码所示,A函数编写在B函数的前面,以这种形式编译的程序,生成的代码时A函数的地址会排在B函数的前面,也就是说在单文件里的函数生成规律与其编写代码时定义的函数排列位置有关
将生成的程序拖入IDA中查看可以发现,A函数的地址排在B函数前面

多文件的函数生成规律
测试代码如下,分别有一个头文件header.h和三个cpp文件A.cpp、B.cpp、test.cpp
把生成的程序放到IDA查看,发现A函数的地址在B函数地址的前面,那么这个是由什么决定的呢

多文件的函数生成规律与cpp文件生成代码的顺序有关
如下图所示,A.cpp排在B.cpp前面, 那么A.cpp里的函数就会排在B.cpp里的函数前面
若你把A.cpp的名字改成C.cpp, 那么B.cpp里的函数就会排在C.cpp里的函数前面, 也就是说cpp文件的第一个首字母或者数字会决定函数的生成规律, 字母按照AZ, 数字按照09

当然除了更改cpp文件名字以外还有其他方法可以更改函数生成规律, 找到C程序项目所在目录, 打开后缀名为vcxproj的文件

更改如下图所示的排列顺序也可以影响函数的生成规律

ShellCode编写原则
1.不能使用常量字符串
只要出现了常量字符串,程序会把字符串存放在常量区段,所以要对字符串进行“打散”处理
2.不能直接调用系统函数
如下代码所示,直接调用了系统api函数的代码不能作为ShellCode
如下代码所示,可以通过GetProcAddress和LoadLibraryA这两个函数实现动态调用系统api函数
但是这样不也是调用了系统函数吗,没关系,后续会讲到通过kernel32.dll模块来获取GetProcAddress函数地址,以此来实现动态调用所有的api函数
3.不能有全局变量
vs会将全局变量编译在其他区段中 结果就是一个绝对的地址, 也不能使用static来声明变量,因为其效果和全局变量一样
简单的ShellCode实例

完整的ShellCode实例
ShellCode生成代码
header.h
api.h
0_Entry.cpp
1_ShellCodeStart.cpp
2_work.cpp
3_ShellCodeEnd.cpp
ShellCode加载代码
运行结果
执行ShellCode生成程序后生成了bin文件

将bin文件拖入到ShellCode加载程序中, 成功执行ShellCode代码中的弹框


参考链接
https://www.bilibili.com/video/BV1y4411k7ch?p=3&vd_source=a6caf742912abf241ffbcb3c11933841
https://xz.aliyun.com/t/10478#toc-8
https://www.cnblogs.com/thresh/p/12609659.html
总结
1、编写ShellCode前需要先了解PE结构、汇编和win32编程相关的知识,这里推荐大家去看滴水的逆向课程,主讲老师是海东老师,BiliBili上能直接搜到
2、这里推荐一款用于查找api函数所在哪个dll的工具:DllExportFinder

最后更新于