1.8 运用C编写ShellCode代码
在笔者前几篇文章中,我们使用汇编语言并通过自定位的方法实现了一个简单的MessageBox
弹窗功能,但由于汇编语言过于繁琐在编写效率上不仅要考验开发者的底层功底,还需要写出更多的指令集,这对于普通人来说是非常困难的,当然除了通过汇编来实现ShellCode
的编写以外,使用C同样可以实现编写,在多数情况下读者可以直接使用C开发,只有某些环境下对ShellCode条件有极为苛刻的长度限制时才会考虑使用汇编。
相较于汇编语言,使用C编写Shellcode
可以更加方便、高效,特别是对于需要大量计算的操作。在编写Shellcode时,读者需要注意以下几点:
- 1.使用纯C语言进行编写:在编写Shellcode时,需要避免使用C++标准库或其他外部依赖库,因为这些库往往会增加代码的长度和复杂度。
- 2.关闭编译器优化:在编写Shellcode时,需要关闭编译器的优化功能,因为优化可能会改变代码的执行顺序,导致Shellcode无法正常工作。
- 3.避免使用全局变量和静态变量:在Shellcode中,全局变量和静态变量往往会导致代码长度过长,并且这些变量的地址也可能与Shellcode中其他代码的地址产生冲突。
- 4.使用裸指针和裸内存管理:为了减小Shellcode的长度和复杂度,需要使用裸指针和裸内存管理,这可以减少代码中不必要的辅助函数调用。
- 5.不能使用全局变量,或者用static修饰的变量,在Shellcode中要自定义入口函数,所有的字符串都要用字符串数组的方式代替。
首先读者应自行新建一个开发项目,并将编译模式调整为Release
模式,这是因为Debug
模式下的代码在转换成汇编后首先都是一个JMP
指令,然后再跳到我们的功能代码处,但JMP
指令是地址相关的 ,所以在转换成ShellCode
时就会出错。此外在读者新建项目文件时请最好使用*.c
结尾而不要使用*.cpp
结尾。
当读者新建文件以后,接下来请修改配置属性,将运行库修改为多线程(MT)
并关闭安全检查机制,如下图所示;
接着在连接器部分,新增一个EntryMain
入口点,默认的Main
入口点显然时不能使用的,如下图所示;
与前几章中的内容原理一致,首先我们需要得到kernel32.dll
模块的基址,这段代码我们依然采用汇编实现,这里需要注意__declspec(naked)
的含义,该声明是微软编译器提供的一个扩展,它用于指示编译器不要为函数自动生成函数头和尾,并将函数转化为裸函数。这种函数不会自动生成函数前缀和后缀的代码,也不会创建任何本地变量或保护寄存器。
在使用__declspec(naked)
声明的函数中,开发者需要自己手动管理堆栈和调用函数的传递参数,然后在函数体中使用汇编指令实现所需的功能。使用__declspec(naked)
声明的函数可以有效地减小生成的代码大小,因为不需要在函数前后添加额外的代码,而且可以精确控制函数内部的代码。
注意:使用
__declspec(naked)
声明的函数需要开发者对汇编语言有一定的了解,否则容易出现错误。在使用时,需要非常小心,确保在函数内部正确地管理堆栈和传递参数,以确保函数能够正常工作。
// ---------------------------------------------- |
当我们能够拿到kernel32.dll
的模块基址时,则接下来就是通过该基址得到Kernel32的模块导出表,并获取该导出表内的GetProcessAddress
函数的基址,至于为什么需要这么做,在读者前面的文章中有详细的分析,这里就不再重复叙述。
// ---------------------------------------------- |
接着我们需要编写主代码逻辑,主代码逻辑中使用GetProcAddress
和LoadLibraryW
来加载user32.dll
并调用其中的MessageBoxW
函数弹出一个消息框的示例。
下面是代码的详细实现流程:
- 1.定义函数指针类型FN_GetProcAddress,用于存储GetProcAddress函数的地址,该函数用于在加载的DLL中查找导出函数的地址。
- 2.通过getProcAddress函数获取kernel32.dll中的GetProcAddress函数地址,并将其转换为FN_GetProcAddress类型的函数指针fn_GetProcAddress。
- 3.定义函数指针类型FN_LoadLibraryW,用于存储LoadLibraryW函数的地址,该函数用于加载指定的DLL文件。
- 4.定义名为xyLoadLibraryW的字符数组,存储字符串”LoadLibraryW”。
- 5.使用fn_GetProcAddress函数指针获取kernel32.dll中的LoadLibraryW函数的地址,并将其转换为FN_LoadLibraryW类型的函数指针fn_LoadLibraryW。
- 6.定义函数指针类型FN_MessageBoxW,用于存储MessageBoxW函数的地址,该函数用于弹出消息框。
- 7.定义名为xy_MessageBoxW的字符数组,存储字符串”MessageBoxW”。
- 8.定义名为xy_user32的字符数组,存储字符串”user32.dll”。
- 9.使用fn_LoadLibraryW函数指针加载user32.dll,并使用fn_GetProcAddress函数指针获取其中的MessageBoxW函数地址,并将其转换为FN_MessageBoxW类型的函数指针fn_MessageBoxW。
- 10.定义名为MsgBox和Title的wchar_t数组,用于存储消息框的文本内容和标题。
- 11.使用fn_MessageBoxW函数指针弹出一个消息框,显示MsgBox中的文本内容,并使用Title中的文本作为标题。
|
至此读者需要手动编译上述代码,当编译通过之后,请打开WinHex
工具,并定位到ShellCode
的开头位置,如下图所示则是我们需要提取的指令集;
选中这片区域,并右键点击编辑按钮,找到复制,C源码格式,此时读者即可得到一个完整的源代码格式;
至此读者只需要一个注入器用于测试代码的完善性,此处是简单实现的一个注入器,代码中shellcode
是我们上图中提取出来的片段,读者需要修改targetPid
为任意一个32位应用程序,并运行注入即可;
|
如果一切顺利,则读者可看到这段ShellCode已经在特定进程内实现运行了,并输出了如下图所示的弹窗提示;