最简单的ShellCode生成

admin 2021年9月8日10:27:56评论902 views字数 5691阅读18分58秒阅读模式
最简单的ShellCode生成

本文为看雪论坛优秀文章

看雪论坛作者ID:wx_墨雪妖莲



ShellCode初级版:


什么是ShellCode?

shellcode实际上是一段可以独立执行的代码,常指一段二进制数据,这段二进制数据独立存在,不像常规PE数据那样加载运行。它没有导入表导出表之类的结构,这使得shellcode极为灵活,同时也加大了它的编写难度。一段健壮的shellcode应具备很好的兼容性。因此,要编写出一个完整的ShellCode需要以下几个重要的技术要点:


1、GetPC(用于准确定位ShellCode所定义的数据)


在x86中即为获取当前代码执行的EIP(也就是程序执行到了哪一行代码)。 

目前GetPC技术一共有以下几种:

(1)CALL方式


利用

E8 00000000 #CALL xxxx 58 #POP EAX


获取当前EIP的值。在这里,CALL的作用等同于将下一条指令的地址压入栈后,在jmp到函数地址上。利用这个特性,我们可以结合pop将压入的地址进行弹出,从而获取到当前EIP。

 

但是虽然这种方法直观简便,但是注意很多程序在读取你所编写ShellCode时是自带00截断的,说的形象点,就像读取字符串的长度strlen一样,遇到00就会返回前面的字符串长度,后面的字符就不计数了。因此很有可能在读取Shell Code时会因为00截断导致后面的ShellCode无法正常加载并运行。


(2)FSTEVV方式

这个方式主要是因为x86:当一个进程正在使用FPU(浮点单元)执行浮点运算时,这些寄存器需要有操作系统在上下文切换时保存。

 

重点关注这个结构体中的fpu_instruction_pointer:

struct FpuSaveState{uint32 control_word; // 控制码uint32 status_word; // 状态码uint32 tag_word; // 标志位uint32 fpu_instruction_pointer; // *指向上条FPU指令*uint16 fpu_instruction_selector; uint16 fpu_opcode;uint32 fpu_operand_pointer;uint16 fpu_operand_selector;uint16 reserved;
}


利用这个内容,我们可以得到上一条FPU指令所在的位置。大致情况如下:通过汇编指令FNSTENV(将FPU的状态值保存在内存中),在进行pop就可以得到当前EIP的值了:

D9 EE #FLDZD9 74 24 F4 #FNSTENV[ESP-0CH]5B #POP EBX


2、模拟导入表,获取函数地址


作为一段可以独立运行的代码,那必然没有线程的dll和函数可以提供给你直接调用。因此,在这里,我们就要用到Windows的强大设计:PEB(进程环境块),可能讲到这里,有些人不是对齐特别的了解,在这里我从网上扣了2张神图提供给大家方便学习:


最简单的ShellCode生成


最简单的ShellCode生成


最简单的ShellCode生成


大致代码原理如下:

xor eax,eaxmov eax,fs:[0x30] #获取PEB地址mov eax,[eax+0xC] #获取PEB_LDR_DATA的结构体指针mov esi,[eax+0x1C] #通过PEB_LDR_DATA获取到结构体成员:模块链表的头部地址mov eax,[esi] #获取第一个加载的模块信息(根据环境的不同而不同)mov eax,[eax] #获取第二个加载的模块信息(根据环境的不同而不同),我这里是kernel32.dllmov eax,[eax+0x8] #获取kernel32.dll的模块基址mov kernelBase,eax 将基址保存到定义的变量进行接收


获取到了指定的模块基址后,我们就可以通过其来获取我们想要的函数地址了,在这里,需要对PE结构有一个基本的了解。

 

怎么获取函数地址呢?若是能够使用函数的话,当然可以直接使用GetProcAddress进行获取,但现在我们写的是ShellCode,必然不可能通过这种方法进行获取,这里我们就要用到PE结构中的导出表了:(在这里我就不用汇编表示了,我用C的结构体进行表示更加明了)

// 获取DOS头IMAGE_DOS_HEADER* DosHeader = (IMAGE_DOS_HEADER*)kernelBase;// 获取NT头IMAGE_NT_HEADERS* NtHeader = (IMAGE_NT_HEADERS*)(DosHeader->e_lfanew + kernelBase);// 获取扩展头IMAGE_OPTIONAL_HEADER OPHeader = NtHeader->OptionalHeader;// 获取导出表IMAGE_EXPORT_DIRECTORY* ExportList= (IMAGE_EXPORT_DIRECTORY*)(OPHeader.DataDirectory[0].VirtualAddress + kernelBase);// 获取函数地址表DWORD* FuncAddList = (DWORD*)(ExportList->AddressOfFunctions+kernelBase);// 获取函数名称表DWORD* NameAddList = (DWORD*)(ExportList->AddressOfNames+kernelBase);// 获取函数序号表SHORT* OriList = (SHORT*)(ExportList->AddressOfNameOrdinals + kernelBase);


获取到了导出表后,就可以通过遍历导出表中的名称表,序号表,以及函数地址表就可以进行获取函数地址了(这里我找的是GetProcAddress):

// 循环遍历,获取LoadLibrary函数地址以及GetProceAddress地址for (int i = 0; i < ExportList->NumberOfNames; i++) {if (g_strcmp((char*)(NameAddList[i] + kernelBase), g_GetProcAddress) == TRUE) { My_GetProcAddress = (MyGetProcAddress)((FuncAddList[OriList[i]]+kernelBase)); } }


当然这里的g_strcmp函数和GetProcAddress的字符串也需要自己进行定义:

 

注:字符串之所以这样定义,并定义在函数内,是因为ShellCode加载的位置不确定,如果直接定义字符串或者全局变量,很有可能会导致数据的不可靠性。而这样定义,在汇编层就会单个字节的进行局部变量的赋值。


char g_GetProcAddress[] = { 'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0 };

inline BOOL g_strcmp(char* str1, char* str2){// 判断对比字符的长度,防止访问越界int length = 0;for (length;; length++) {if (str1[length] == 0 || str2[length] == 0)break; }// 进行对比for (int i = 0;i<length; i++) {if (str1[i] != str2[i])return FALSE;else if (str1[i] == str2[i] && str2[i + 1] == 0&&str1[i+1]==0)return TRUE; }};


做到了以上代码,我们就可以通过kernel32.dll找到GetProcAddress和LoadLibrary的函数地址,进行后续的操作。
 
例:通过获取到的GetProcAddress地址和LoadLibrary的函数地址,我们就可以直接调用它去获取其他的函数地址。
 
比如实现一个弹框:MessageBoxA(在user32.dll中)

// 定义函数指针typedef HMODULE(WINAPI* MyLoadLibrary)(_In_ LPCSTR lpFileName);typedef FARPROC(WINAPI* MyGetProcAddress)(_In_ HMODULE hModule, _In_ LPCSTR lpProcName);typedef int(WINAPI* MyMessageBox)(_In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType);// 通过遍历导出表进行获取函数地址(这里我就不详细写了,具体情况就是上面的代码修改一下)HMODULE hModule = NULL;hModule=My_LoadLibrary(g_user32);My_MessageBox=(MyMessageBox)My_GetProcAddress(hModule, g_MessageBox);My_MessageBox(0, 0, 0, 0);


3、生成ShellCode


上述写的代码是用C语言结合嵌入汇编进行编写的,现在我们要做的就是将程序进行生成(Release版本我用的是),然后用x32dbg进行调试,找到程序main函数的起点和终点,进行二进制代码复制,这段代码就是我们的ShellCode:

 
操作如下:选中代码,右键二进制->编辑->复制数据->选中C样式ShellCode字符串->复制即可


最简单的ShellCode生成


4、调试生成的ShellCode


对于Shell Code的调试,我们可以直接运用以下代码(这里的原理我就不多说了,就是将arr的首地址当成代码进行执行):
 
不过要注意的是:#pragma comment(linker, "/section:.data,RWE")要加上,不然你的数据区就是不可执行的,自然就不会运行你编写的ShellCode了。


#include<Windows.h>#include<iostream>// 使数据段可读可写可执行#pragma comment(linker, "/section:.data,RWE")// 生成的ShellCodechar arr[] = { "x55x8BxECx81xECx2Cx01x00x00x53x33xC0xC7x45xD8x4Cx6Fx61x64x56x57""xC7x45xDCx4Cx69x62x72xC7x45xE0x61x72x79x41x88x45xE4xC7x45xC8x47""x65x74x50xC7x45xCCx72x6Fx63x41xC7x45xD0x64x64x72x65x66xC7x45xD4""x73x73x88x45xD6xC7x45xF4x75x73x65x72xC7x45xF8x33x32x2Ex64x66xC7""x45xFCx6Cx6Cx88x45xFExC7x45xE8x4Dx65x73x73xC7x45xECx61x67x65x42""xC7x45xF0x6Fx78x41x00x33xC0x64xA1x30x00x00x00x8Bx40x0Cx8Bx70x1C""x8Bx06x8Bx00x8Bx40x08x89x45xC4x8Bx5DxC4x8DxBDxD4xFExFFxFFx6Ax38""x59x8Dx73x18x03x73x3CxF3xA5x8Bx85x34xFFxFFxFFx33xFFx33xF6x8Bx4C""x18x1Cx8Bx54x18x20x03xCBx89x4DxBCx03xD3x8Bx4Cx18x24x8Bx44x18x18""x03xCBx89x55xB4x89x4DxC0x89x45xB8x85xC0x74x2Ex8Dx45xC8x50x8Bx04""xB2x03xC3x50xE8x40x00x00x00x59x59x83xF8x01x75x0Fx8Bx45xC0x8Bx4D""xBCx0FxBFx04x70x8Bx3Cx81x03xFBx8Bx55xB4x46x3Bx75xB8x72xD2x8Dx45""xD8x50x53xFFxD7x8Dx4DxF4x51xFFxD0x8Dx4DxE8x51x50xFFxD7x33xC9x51""x51x51x51xFFxD0x5Fx5Ex5BxC9xC3x55x8BxECx53x8Bx5Dx0Cx33xD2x56x8B""x75x08x57x8BxFAx38x16x74x11x8BxCBx8BxC6x2BxCEx38x14x01x74x06x47""x40x38x10x75xF5x85xFFx7Ex30x8BxC6x2BxC3x89x45x08x8Dx0Cx1Ax8Ax04""x08x3Ax01x75x1Dx80x7Cx1Ax01x00x75x07x80x7Cx32x01x00x74x0Ax42x3B""xD7x7Dx0Cx8Bx45x08xEBxDEx33xC0x40xEBx02x33xC0x5Fx5Ex5Bx5DxC3" };int main(){ _asm { lea eax,arr push eax jmp eax }}


运行效果如下:(当然你也可以添加弹框的参数,用上述的char数组进行添加),或者自己写一些有趣的小玩意。


最简单的ShellCode生成



end





最简单的ShellCode生成


看雪ID:wx_墨雪妖莲

https://bbs.pediy.com/user-home-916882.htm

  *本文由看雪论坛 wx_墨雪妖莲 原创,转载请注明来自看雪社区。



最简单的ShellCode生成
《安卓高级研修班》2021年秋季班火热招生中!



# 往期推荐





最简单的ShellCode生成
公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]



最简单的ShellCode生成

球分享

最简单的ShellCode生成

球点赞

最简单的ShellCode生成

球在看



最简单的ShellCode生成

点击“阅读原文”,了解更多!

本文始发于微信公众号(看雪学院):最简单的ShellCode生成

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年9月8日10:27:56
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   最简单的ShellCode生成http://cn-sec.com/archives/407475.html

发表评论

匿名网友 填写信息