自定义ShellCode
郑重声明:文中所涉及的技术、思路和工具仅供以安全为目的的学习交流使用,如果您不同意请关闭该页面!任何人不得将其用于非法用途以及盈利等目的,否则后果自行承担!
终于写完了,应该挺详细的,奥利给
环境配置讲解
当前项目使用的vs2019,其他vs编译器都可以
清除多余的函数
首先创建一个项目,然后什么配置都不修改使用
release
生成如下代码
int main() |
然后我们把生成的exe文件放到ida里面去可以看到下图,明明我们什么函数都没有加为什么会多出这么多函数呢?
其实这些函数都是vs编译器自动帮我加进去的,我们的代码段加上这些函数就组成了一个PE文件
位置:项目->配置属性->高级->入口点中添加
MyMian
字段,这个字段可以随意修改
接着我们把原先的代码替换为如下代码,然后重新生成
int MyMain() |
可以看到多余的函数都不见了
禁用安全检查
上面是不是还有一个多余函数,该函数是用于安全检查用的,我们一样可以关闭它。
位置:项目->配置属性->
C/C++
->代码生成->安全检查,点击禁用安全检查
然后重新生成后就能看到只剩下一个字段了
设置兼容XP系统
虽然
win7
都淘汰了但是还是有不少
哔哔哔哔
(自带消音)还用着
windwos XP
,但是
v142
已经不支持
xp
编程了,所以我们只能下载
v141_xp
来使用
位置:项目->配置属性->常规->平台工作集,选用
Visual Studio 2017 - Windows XP (v141_xp)
(需要自己下载该项目包
接着修改运行库
位置:项目->配置属性->
C/C++
->代码生成->运行库,改成
MT
格式
关闭资源段
当我们把以上调试好的exe文件放入到PEiD中的时候可以看到还多出了资源段和只读数据段
位置:项目->配置属性->链接器->清单文件->生成清单,改成 否
重新生成后的文件放入PEiD中可以看到资源段已经消失了
关闭只读数据段
位置:项目->配置属性->链接器->调试->生成调试信息,改为 否
vs编译器会自动把没用用到的参数优化了,所以我们要关闭优化,会更直观。
位置:项目->配置属性->
C/C++
->优化,修改为
已禁用
MyMian
字段
C/C++
->代码生成->安全检查,点击禁用安全检查
Visual Studio 2017 - Windows XP (v141_xp)
C/C++
->优化,修改为
已禁用
C/C++
->代码生成->运行库,改成
MT
格式
C/C++
->语言->符合模式,改成
否
格式
代码中的注意事项
杜绝双引号
vs开放平台中,双引号字符串会被编译到只读数据段,以引用绝对地址的方式使用,
shellcode
是为了便于所有机器上使用的,所有要
避免一切绝对地址的使用
,下面的函数调用会报错,之所以这样写是为了直观理解,实际需要用动态调用。
禁止使用的格式
//1.字符串和指针形式 |
应该写成的格式
//1.字符串的形式 |
函数动态调用
如果不是用该方法,在我们修改了main函数的入口后,调用include中的函数就好花式报错,但是你自己写的函数就不会
先贴一个调用
Windows API
中
CreateFileA
函数的例子,后面再详细讲解
|
首先如果使用自定义的函数入口点需要添加
#pragma comment(linker,"/entry:MyMain")
,
MyMain
的值为你的入口函数名
接着我们需要获取DLL中函数的地址,使用
GetProcAddress(LoadLibraryA("kernel32.dll"), "CreateFileA");
这段代码的意思就是获取
kernel32.dll
动态链接库中的
CreateFileA
函数的相对地址。
然后找到 CreateFileA 函数的声明,然后复制过来
接着就是使用 typedef 定义复制过来的函数,以及声明调用等
如果使用 printf 函数也是一样的,代码如下
|
函数动态调用优化
由上文可以知道我们需要动态调用函数或者API,但是有两个地方违背了我们核心的原理。
首先我们解决第一个痛点:
获取kernel32.dll基址
|
可以看到返回的结果是一模一样的
__declspec(naked)
是用来告诉编译器函数代码的汇编语言为自己的所写,不需要编译器添加任何汇编代码
上面的汇编使用的是 MASM 版本, NASM 版本代码如下,根据系统使用不同的代码
__declspec(naked)DWORD getKernel32() |
上面注释中说的入口点,不同程序不同的入口点如下
获取kernel32.dll中的函数
有了 kernel32.dll 基址后,我们可以通过基址来获取到里面需要用到API函数
依旧直接贴代码
|
看下图的结果,说明代码没错
上面代码的流程,首先获取
kernel32.dll
的基址,然后利用
typedef
来声明动态调用,接着调用写好的
_GetProcAddress
函数来把地址负责给声明好的
fn_GetProcAddress
中,接着我们直接使用
fn_GetProcAddress
函数就和调用windows api一样的。
_GetProcAddress
函数的写法涉及到PE结构编写,后面有时间会单独写一篇相关的文章
禁止使用全局变量
因为全局变量在使用vs平台进行编译的时候会加载到PE结构中的特定区段中,类似于使用了绝对地址,这样是我们写shellcode的时候需要避免的。
备注:
static
关键字也是一样的,需要避免
确保加载所需要的动态链接库
在上面的
printf
函数中我们也是使用动态调用的方式来实现的,所以在使用非自己写的函数的时候都必须确保加载了所需要的动态链接库!!!!!
编写我们第一个shellcode
我们普通的代码写法
#include <windows.h> |
shellcode写法
|
效果如下图
接下来我们来提取进程中的shellcode段,首先用PEID来看看文件的偏移点
我们用16进制进制来看看shellcode代码段,如图所示从200-5D7都是我们shellcode
我们把这段代码复制下来另存为一个文件
然后我们用shellcode加载器加载他,看下是否成功
函数生成规律
单文件函数生成规律
先说结论:
首先验证我们结论,我们看下面两张图
第二张图我们把 b 函数放到 MyMain 函数上面,其他的不变
可以看到我们的结论是完全正确的!!
多文件函数生成规律
首先直接来结论:
文件生成顺序和头文件引用顺序无关,只和工程项目
vcxproj
结尾的文件相关
如果有多个文件,并且多个文件里面有多个函数,那么函数生成的顺序第一优先级为
vcxproj
书写的顺序相关,第二优先级只和
cpp
文件中书写函数的顺序相关,举个例子
vcxproj
文件中顺序为
a.cpp
dome.cpp
b.cpp
,并且
a.cpp
文件中有两个函数
a
和
c
,
b.cpp
文件只有
b
函数,
dome.cpp
文件只有
MyMain
函数,那么函数排列的顺序就是这样的
a函数->c函数->MyMain函数->b函数 |
首先我们来验证第一条结论
用IDA来查看
接着我们来修改
vcxproj
文件中文件中的位置,可以发现论证成立
接着我们来验证第二个结论,可见结论完全正确
编写Shellcode
首先vs文件创建排序是按数字其次才是字母排序,编译顺序也是如此(0-9-a-z)
首先我们创建一侧项目,然后按上面的要求来配置我们的文件,最后我们可以得到一个入下图所示的解决方案
我们只需要把代码放到
z.end.cpp
和
a.start.cpp
之间即可
首先我们贴
0.main.cpp
的代码,由于该函数不需要加载在shellcode中,所以我们可以按照正常的写法来写,然后获取
ShellcodeEnd
函数还有
ShellcodeStart
函数使他们相减即可
|
接着我们来定义
api.h
头文件,这个文件都存放着我们所有的动态调用的api类,定义好动态调用的类后我们把它用
typedef
来集合起来,看不懂的可以看我原来搬运的一篇文章
|
接着是
statement.h
头文件,这个文件声明了我们所有自定义的函数
|
接着是
a.start.cpp
文件,首先需要定一个
ShellcodeStart
函数,这个函数是我们所有代码的入口,一个
jmp
的汇编命令,该命令跳转到的我们真正的起始函数中,接下来就是获取基址,以及一些动态调用的赋值
|
b.code.cpp
这个文件是我们所有的函数使用,如果还想加函数使用之类的都可以写在这边,然后在
a.start.cpp
中的
ShellcodeEntry
函数调用即可
|
最后是
z.end.cpp
这里面只要写一个结束的函数就行
|
生成代码,可以发现顺序完全一致