-
-
[原创]超简单的C++编写Windows平台ShellCode的框架
-
发表于:
2020-4-12 16:01
12859
-
[原创]超简单的C++编写Windows平台ShellCode的框架
功能
可以向写普通C++代码一样写ShellCode,写完直接编译即可以生成相应的代码,直接引用即可。(
字符串还是要拆开写的)
所有的API都可以像上面这样直接调用,程序会自动生成相应的库的代码
编译后,程序会自动生成Code.h头文件,直接引用即可.
首先是API这个宏:
支持X86和X64。
来看雪很长时间了,在这里学到了不少东西,本人菜鸡一只,一直在潜水。
周末整理了一下笔记,整理出这个ShellCode编写框架,取名EasyShellCode,不敢独享,分享给大家,希望有用。
#define API(DLLNAME, FUNNAME) ((decltype(&FUNNAME))WinApi::ForceCalc<WinApi::ELFNoCaseHash(#DLLNAME ".dll"), WinApi::CalcHash(#FUNNAME)>())
这里用到了 C++11的decltype(下面还会用到C++14的constexpr,这也是要求高版本编译器的原因)如果不用高版本编译器,函数类型的声明就要自己写.
整体思想就是通过计算字符串的hash值去找到对应的模块.其中WinApi::ForceCalc是一个模板函数,定义如下:
template<DWORD dwDllName, DWORD dwFunName>
__forceinline PVOID ForceCalc()
{
return GetApiAddr(dwDllName, dwFunName);
}
这个函数看起来没什么用, 但是把它去掉就能发现问题了. 为什么呢? 得说完hash计算方法后再说.
下面继续看
ELFNoCaseHash 函数,因为Dll的名字是不区分大小写的,所以这里用了一个不区分大小写的hash生成函数
constexpr UINT ELFNoCaseHash(LPCSTR str, UINT nLen = UINT_MAX)
{
if (nullptr == str) {
return -1;
}
UINT hash = 0;
UINT test = 0;
while (*str && nLen) {
hash = (hash << 4) + MiniToUpper((*str++));
if ((test = hash & 0xF0000000) != 0) {
hash = ((hash ^ (test >> 24)) & (~test));
}
nLen--;
}
return hash;
}
精髓就是constexpr, 这个关键字会让编译器尽可能在编译器计算值,因为字符串是个常量,所以在编译器计算hash是可能的,这也就导致了编译到最后代码中的不是一个字符串常量,而是一个hash整数.
再来说为什么要有WinApi::ForceCalc,简单来说就是
constexpr编译器不保证真的在编译器计算出值(有的是因为根本计算不出来值,有的是因为它偷懒了)。怎么办呢? 就是通过这个函数模板让它强制计算(它不计算就不知道怎么生成模板函数),也就是名字的由来。
剩下的技巧就没什么了,就是一个根据hash值找对应函数的方法了. 主要靠
GetApiAddr函数实现.
这个函数先去PEB中查找已经加载的模块(请查看GetModuleHandleFromPeb 函数实现), 如果找到了,那么直接去模块中找函数地址. 反之如果没找到,那么就去加载它(请查看 GetModuleHandleFromDllNum 函数实现).具体去看下代码吧,就没什么技巧了.
项目支持多文件,需要添加文件.
如之前所说,Main.cpp中的
EasyShellCodeMain就是ShellCode的入口函数,如果用到其他文件,直接添加即可.
但要保证AAAAAStart.cpp是第一个,ZZZZZEnd.cpp是最后一个(一般只要保证文件名是大写字母开头就行)。
template<DWORD dwDllName, DWORD dwFunName>
__forceinline PVOID ForceCalc()
{
return GetApiAddr(dwDllName, dwFunName);
}
这个函数看起来没什么用, 但是把它去掉就能发现问题了. 为什么呢? 得说完hash计算方法后再说.
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 2020-4-12 16:02
被独—行编辑
,原因: