Window中的shellcode编写框架(入门篇)
shellcode
- 定义
是一段可注入的指令(opcode),可以在被攻击的程序内运行。
- 特点
短小精悍,灵活多变,独立存在,无需任何文件格式的包装,因为shellcode直接操作寄存器和函数,所以opcode必须是16进制形式。因此也不能用高级语言编写shellcode。在内存中运行,无需运行在固定的宿主进程上。
- shellcode的利用原理
将shellcode注入缓冲区,然后欺骗目标程序执行它。而将shellcode注入缓冲区最常用的方法是利用目标系统上的缓冲区溢出漏洞。
shellcode生成方法
- 编程语言编写:汇编语言,c语言
- shellcode生成器
shellcode编写原则
- 杜绝双引号字符串的直接使用
关闭vs自动优化没有使用到的变量
自定义函数入口
- 动态获取函数的地址
getproaddress 从dll中获取函数的地址
参数1:调用dll的句柄,参数2:函数名
bug:error c2760: 语法错误: 意外的令牌“标识符”,预期的令牌为“类型说明符”
打开项目工程-> 属性 -> c/c++ --> 语言 -> 符合模式 修改成否即可 如果这样设置将无法使用c函数。
这个比较关键,否则使用printf就直接崩溃或者是编译报错
最佳方案是:修改平台工具集
- 通过获得kernel32基址来获取getprocaddres基址
- 避免全局变量的使用
因为vs会将全局变量编译在其他区段中 结果就是一个绝对的地址不能使用static定义变量(变量放到内部函数使用)
- 确保已加载使用api的动态链接库
编写shellcode前的准备
- 修改程序入口点:链接器-高级。作用:去除自动生成的多余的exe代码
- 关闭缓冲区安全检查,属性->c/c++ ->代码生成->安全检查禁用
- 设置工程兼容window xp :代码生成 ->运行库 选择 debug mtd release mt
- 清除资源:链接器->调试->清单文件
- 关闭调试功能
如下:
属性->常规->平台工具集 选择xp版本
c/c++->代码生成->运行库选择mt
安全检查禁用
链接器->高级->入口点修改为entrymain
函数动态链接调用
在编写shellcode时,所有用到的函数都需要动态调用,通过loadlibrary函数加载动态链接库,getproaddress获取动态链接库中函数的地址。所以获取到getproaddress和loadlibrary地址时非常重要的。通过getproaddress和loadlibrary,可以获取到已加载的动态链接库的地址。
动态获取kernel32.dll基址和getproaddress 地址
在正常情况下,我们不知道loadlibrarya的地址,所以不能直接使用该函数。getproaddress是动态链接库kernel32.dll中的函数。每个进程内部加载都会加载kernel32.dll,获取到加载kernel32.dll地址就可以获取到getproaddress函数的地址。通过汇编内嵌获取到kernel32的基址。获取到getproaddress地址后,就可以获取到所有需要的函数的地址。
pe文件运行时加载的链接库
代码如下:
#include <windows.h> #include <stdio.h> //内嵌汇编获取kernel32的地址 __declspec(naked) dword getkernel32() { __asm { mov eax,fs:[30h] mov eax,[eax+0ch] mov eax,[eax+14h] mov eax,[eax] mov eax,[eax] mov eax,[eax+10h] ret } } //通过kernel32基址获取getprocaddress的地址 farproc _getprocaddress(hmodule hmodulebase) { pimage_dos_header lpdosheader = (pimage_dos_header)hmodulebase; pimage_nt_headers32 lpntheader = (pimage_nt_headers)((dword)hmodulebase + lpdosheader->e_lfanew); if (!lpntheader->optionalheader.datadirectory[image_directory_entry_export].size){ return null; } if (!lpntheader->optionalheader.datadirectory[image_directory_entry_export].virtualaddress) { return null; } pimage_export_directory lpexports = (pimage_export_directory)((dword)hmodulebase + (dword)lpntheader->optionalheader.datadirectory[image_directory_entry_export].virtualaddress); pdword lpdwfunname = (pdword)((dword)hmodulebase + (dword)lpexports->addressofnames); pword lpword = (pword)((dword)hmodulebase + (dword)lpexports->addressofnameordinals); pdword lpdwfunaddr = (pdword)((dword)hmodulebase + (dword)lpexports->addressoffunctions); dword dwloop = 0; farproc pret = null; for (; dwloop <= lpexports->numberofnames - 1; dwloop++) { char* pfunname = (char*)(lpdwfunname[dwloop] + (dword)hmodulebase); if (pfunname[0] == 'g'&& pfunname[1] == 'e'&& pfunname[2] == 't'&& pfunname[3] == 'p'&& pfunname[4] == 'r'&& pfunname[5] == 'o'&& pfunname[6] == 'c'&& pfunname[7] == 'a'&& pfunname[8] == 'd'&& pfunname[9] == 'd'&& pfunname[10] == 'r'&& pfunname[11] == 'e'&& pfunname[12] == 's'&& pfunname[13] == 's') { pret = (farproc)(lpdwfunaddr[lpword[dwloop]] + (dword)hmodulebase); break; } } return pret; } int main() { //kernel32.dll 基址的动态获取 hmodule hloadlibrary = loadlibrarya("kernel32.dll"); //使用内嵌汇编来获取基址 hmodule _hloadlibrary = (hmodule)getkernel32(); //效果是一样的 printf("loadlibrarya动态获取的地址: 0x%x\n", hloadlibrary); printf("内嵌汇编获取的地址: 0x%x\n", _hloadlibrary); //声明定义,先转到到原函数定义,然后重新定义 typedef farproc(winapi *fn_getprocaddress)( _in_ hmodule hmodule, _in_ lpcstr lpprocname ); fn_getprocaddress fn_getprocaddress; fn_getprocaddress = (fn_getprocaddress)_getprocaddress(_hloadlibrary); printf("动态获取getprocaddress地址: 0x%x\n",fn_getprocaddress); printf("内置函数获取: 0x%x\n",getprocaddress); }
可以看到,动态获取的地址和系统函数获取的是一致的
shellcode框架(一)
引用前面kernel32.dll和getprocaddress的获取地址。对于每一个要引用的函数,通过查看定义来声明定义函数,通过getprocaddress动态获取地址。
具体代码如下:
原c代码:
#include <windows.h> #include <stdio.h> #include <windows.h> int entrymain() { createfilea(“1.txt”, generic_write, 0, null, create_always, 0, null); messageboxa(null, “hello world”, tip, mb_ok); return 0; }
shellcode编写:
#include <windows.h> #include <stdio.h> farproc getprocaddress(hmodule hmodulebase); dword getkernel32(); int entrymain() { //声明定义getprocaddress typedef farproc(winapi *fn_getprocaddress)( _in_ hmodule hmodule, _in_ lpcstr lpprocname ); //获取getprocaddress真实地址 fn_getprocaddress fn_getprocaddress = (fn_getprocaddress)getprocaddress((hmodule)getkernel32()); //声明定义createfilea typedef handle(winapi *fn_createfilea)( __in lpcstr lpfilename, __in dword dwdesiredaccess, __in dword dwsharemode, __in_opt lpsecurity_attributes lpsecurityattributes, __in dword dwcreationdisposition, __in dword dwflagsandattributes, __in_opt handle htemplatefile ); //将来的替换,地址全部动态获取 //fn_createfilea fn_createfilea = (fn_createfilea)getprocaddress(loadlibrary("kernel32.dll"), "createfilea"); //带引号的字符串打散处理 char xycreatefile[] = { 'c','r','e','a','t','e','f','i','l','e','a',0 }; //动态获取createfile的地址 fn_createfilea fn_createfilea = (fn_createfilea)fn_getprocaddress((hmodule)getkernel32(), xycreatefile); char xynewfile[] = { '1','.','t','x','t','\0'}; fn_createfilea(xynewfile, generic_write, 0, null, create_always, 0, null); //定义loadlibrarya typedef hmodule(winapi *fn_loadlibrarya)( __in lpcstr lplibfilename ); char xyloadlibrarya[] = { 'l','o','a','d','l','i','b','r','a','r','y','a',0}; //动态获取loadlibrarya的地址 fn_loadlibrarya fn_loadlibrarya = (fn_loadlibrarya)fn_getprocaddress((hmodule)getkernel32(), xyloadlibrarya); //定义messageboxa typedef int (winapi *fn_messageboxa)( __in_opt hwnd hwnd, __in_opt lpcstr lptext, __in_opt lpcstr lpcaption, __in uint utype); //原来的:messageboxa(null, "hello world", "tip", mb_ok); char xy_user32[] = { 'u','s','e','r','3','2','.','d','l','l',0 }; char xy_messageboxa[] = { 'm','e','s','s','a','g','e','b','o','x','a',0 }; fn_messageboxa fn_messageboxa = (fn_messageboxa)fn_getprocaddress(fn_loadlibrarya(xy_user32), xy_messageboxa); char xy_hello[] = { 'h','e','l','l','o',' ','w','o','r','l','d',0 }; char xy_tip[] = { 't','i','p' }; fn_messageboxa(null, xy_hello, xy_tip, mb_ok); return 0; } //内嵌汇编获取kernel32的地址 __declspec(naked) dword getkernel32() { __asm { mov eax, fs:[30h] mov eax, [eax + 0ch] mov eax, [eax + 14h] mov eax, [eax] mov eax, [eax] mov eax, [eax + 10h] ret } } //获取getprocaddress的地址 farproc getprocaddress(hmodule hmodulebase) { pimage_dos_header lpdosheader = (pimage_dos_header)hmodulebase; pimage_nt_headers32 lpntheader = (pimage_nt_headers)((dword)hmodulebase + lpdosheader->e_lfanew); if (!lpntheader->optionalheader.datadirectory[image_directory_entry_export].size) { return null; } if (!lpntheader->optionalheader.datadirectory[image_directory_entry_export].virtualaddress) { return null; } pimage_export_directory lpexports = (pimage_export_directory)((dword)hmodulebase + (dword)lpntheader->optionalheader.datadirectory[image_directory_entry_export].virtualaddress); pdword lpdwfunname = (pdword)((dword)hmodulebase + (dword)lpexports->addressofnames); pword lpword = (pword)((dword)hmodulebase + (dword)lpexports->addressofnameordinals); pdword lpdwfunaddr = (pdword)((dword)hmodulebase + (dword)lpexports->addressoffunctions); dword dwloop = 0; farproc pret = null; for (; dwloop <= lpexports->numberofnames - 1; dwloop++) { char* pfunname = (char*)(lpdwfunname[dwloop] + (dword)hmodulebase); if (pfunname[0] == 'g'&& pfunname[1] == 'e'&& pfunname[2] == 't'&& pfunname[3] == 'p'&& pfunname[4] == 'r'&& pfunname[5] == 'o'&& pfunname[6] == 'c'&& pfunname[7] == 'a'&& pfunname[8] == 'd'&& pfunname[9] == 'd'&& pfunname[10] == 'r'&& pfunname[11] == 'e'&& pfunname[12] == 's'&& pfunname[13] == 's') { pret = (farproc)(lpdwfunaddr[lpword[dwloop]] + (dword)hmodulebase); break; } } return pret; }
结果:运行exe文件创建了1.txt文件,并出现弹框。
函数生成的位置规律
- 单文件函数生成的位置规律
规律:单文件函数的生成规律,与函数实现的先后顺序有关,而与函数的定义顺序无关。
例如:
#include<windows.h> #include<stdio.h> int funca(int a, int b) { puts("aaaa"); return a + b; } int funcb(int a, int b) { puts("bbb"); return a + b; } int main() { funca(1, 2); funcb(2, 3); return 0; }
结果:在ida中看到生成的exe文件中的函数顺序为funca,funcb,main,与函数实现的先后顺序有关。通过函数的位置,可以得到两个函数之间的空间大小。
2.多文件函数生成的位置规律
规律:与包含文件的位置无关,与实际调用的顺序有关
//a.h #include<stdio.h> void a() { puts("aaa"); } //b.h #include<stdio.h> void b() { puts("bbb"); } //main #include"a.h" #include"b.h" int main() {
a(); b(); return 0; }
结果:
工程下的后缀名为vcxproj文件
修改顺序
可以看到生成的顺序发生了改变
shellcode框架(二)
shellcode代码执行过程
shellcodestart-> shellcodeentry-> shellcodeend
工程文件:
api.h文件:存放定义的的函数
header.h文件:存放定义的功能函数
0.entry.cpp文件:shellcode的入口点
a.start.cpp文件:shellcode执行(实现逻辑功能)
b.work.cpp文件:存放具体功能实现
z.end.cpp文件:shellcode结束
a.start.cpp文件和b.work.cpp文件分别管理逻辑功能和具体实现,更方便管理。
生成的文件顺序:
代码:
api.h
#pragma once #include<windows.h> //声明定义getprocaddress typedef farproc(winapi *fn_getprocaddress)( _in_ hmodule hmodule, _in_ lpcstr lpprocname ); //定义loadlibrarya typedef hmodule(winapi *fn_loadlibrarya)( __in lpcstr lplibfilename ); //定义messageboxa typedef int (winapi *fn_messageboxa)( __in_opt hwnd hwnd, __in_opt lpcstr lptext, __in_opt lpcstr lpcaption, __in uint utype); //定义createfilea typedef handle(winapi *fn_createfilea)( __in lpcstr lpfilename, __in dword dwdesiredaccess, __in dword dwsharemode, __in_opt lpsecurity_attributes lpsecurityattributes, __in dword dwcreationdisposition, __in dword dwflagsandattributes, __in_opt handle htemplatefile ); //定义一个指针 typedef struct _functions { fn_getprocaddress fn_getprocaddress; fn_loadlibrarya fn_loadlibrarya; fn_messageboxa fn_messageboxa; fn_createfilea fn_createfilea; }functions,*pfunctions;
header.h
#pragma once #include<windows.h> #include<stdio.h> #include"api.h" void shellcodestart(); void shellcodeentry(); void shellcodeend(); void createshellcode(); void initfunctions(pfunctions pfn); void createconfigfile(pfunctions pfn);
0.entry.cpp
#include "header.h" int entrymain() { createshellcode(); return 0; } void createshellcode() { hmodule hmsvcrt = loadlibrarya("msvcrt.dll"); //定义printf typedef int (__crtdecl *fn_printf)( _in_z_ _printf_format_string_ char const* const _format, ...); fn_printf fn_printf = (fn_printf)getprocaddress(hmsvcrt,"printf"); handle hbin = createfilea("sh.bin", generic_all, 0, null, create_always, 0, null); if (hbin == invalid_handle_value) { //这里不能使用printf函数,因为修改了函数入口,找不到printf的地址,所有需要动态调用printf函数 //printf("create file error:%d\n", getlasterror()); fn_printf("create file error:%d\n", getlasterror()); return ; } dword dwsize = (dword)shellcodeend - (dword)shellcodestart; dword dwwrite; writefile(hbin, shellcodestart, dwsize, &dwwrite, null); closehandle(hbin); }
a.start.cpp文件
#include "header.h" #include "api.h" __declspec(naked) void shellcodestart() { __asm { jmp shellcodeentry } } //内嵌汇编获取kernel32的地址 __declspec(naked) dword getkernel32() { __asm { mov eax, fs:[30h]; test eax, eax; js finished; mov eax, [eax + 0ch]; mov eax, [eax + 14h]; mov eax, [eax]; mov eax, [eax] mov eax, [eax + 10h] finished: ret } } //获取getprocaddress的地址 farproc getprocaddress(hmodule hmodulebase) { pimage_dos_header lpdosheader = (pimage_dos_header)hmodulebase; pimage_nt_headers32 lpntheader = (pimage_nt_headers)((dword)hmodulebase + lpdosheader->e_lfanew); if (!lpntheader->optionalheader.datadirectory[image_directory_entry_export].size) { return null; } if (!lpntheader->optionalheader.datadirectory[image_directory_entry_export].virtualaddress) { return null; } pimage_export_directory lpexports = (pimage_export_directory)((dword)hmodulebase + (dword)lpntheader->optionalheader.datadirectory[image_directory_entry_export].virtualaddress); pdword lpdwfunname = (pdword)((dword)hmodulebase + (dword)lpexports->addressofnames); pword lpword = (pword)((dword)hmodulebase + (dword)lpexports->addressofnameordinals); pdword lpdwfunaddr = (pdword)((dword)hmodulebase + (dword)lpexports->addressoffunctions); dword dwloop = 0; farproc pret = null; for (; dwloop <= lpexports->numberofnames - 1; dwloop++) { char* pfunname = (char*)(lpdwfunname[dwloop] + (dword)hmodulebase); if (pfunname[0] == 'g'&& pfunname[1] == 'e'&& pfunname[2] == 't'&& pfunname[3] == 'p'&& pfunname[4] == 'r'&& pfunname[5] == 'o'&& pfunname[6] == 'c'&& pfunname[7] == 'a'&& pfunname[8] == 'd'&& pfunname[9] == 'd'&& pfunname[10] == 'r'&& pfunname[11] == 'e'&& pfunname[12] == 's'&& pfunname[13] == 's') { pret = (farproc)(lpdwfunaddr[lpword[dwloop]] + (dword)hmodulebase); break; } } return pret; } //动态调用地址 void initfunctions(pfunctions pfn) { //获取getprocaddress真实地址 pfn->fn_getprocaddress = (fn_getprocaddress)getprocaddress((hmodule)getkernel32()); //动态获取loadlibrarya的地址 char xyloadlibrarya[] = { 'l','o','a','d','l','i','b','r','a','r','y','a',0 }; pfn->fn_loadlibrarya = (fn_loadlibrarya)pfn->fn_getprocaddress((hmodule)getkernel32(), xyloadlibrarya); //动态获取messageboxa的地址 char xy_user32[] = { 'u','s','e','r','3','2','.','d','l','l',0 }; char xy_messageboxa[] = { 'm','e','s','s','a','g','e','b','o','x','a',0 }; pfn->fn_messageboxa = (fn_messageboxa)pfn->fn_getprocaddress(pfn->fn_loadlibrarya(xy_user32), xy_messageboxa); //动态获取createfile的地址 char xycreatefile[] = { 'c','r','e','a','t','f','i','l','e','a',0 }; pfn->fn_createfilea = (fn_createfilea)pfn->fn_getprocaddress((hmodule)getkernel32(), xycreatefile); } //入口点 void shellcodeentry() { char xy_hello[] = { 'h','e','l','l','o',' ','w','o','r','l','d',0 }; char xy_tip[] = { 't','i','p' }; functions fn; initfunctions(&fn); createconfigfile(&fn); }
b.work.cpp
#include"api.h" #include<stdio.h> //存放功能 void createconfigfile(pfunctions pfn) { char xynewfile[] = { '1','.','t','x','t','\0' }; pfn->fn_createfilea(xynewfile, generic_write, 0, null, create_always, 0, null); }
z.end.cpp
#include "header.h" void shellcodeend() { }
shellcode加载器
用来运行提取出来的shellcode代码,保存在sh.bin文件,将sb.bin文件拖入加载器,即可执行。
#include<stdio.h> #include<windows.h> int main(int argc,char* argv[]) { //打开文件 handle hfile = createfilea(argv[1], generic_read, 0, null, open_always, 0, null); if (hfile == invalid_handle_value) { printf("oen file error:%d\n", getlasterror); return -1; } dword dwsize; dwsize = getfilesize(hfile, null); lpvoid lpaddress = virtualalloc(null, dwsize, mem_commit, page_execute_readwrite); //内存分配是否成功 if (lpaddress ==null) { printf("virtualalloc error:%d\n", getlasterror); closehandle(hfile); return -1; } dword dwread; readfile(hfile,lpaddress,dwsize,&dwread, 0); //内嵌汇编 __asm { call lpaddress } _flushall(); system("pause"); return 0; }
上一篇: 【Oracle学习笔记】序列
下一篇: 关键词匹配优化(第0篇)—— 问题和思路