安全矩阵

 找回密码
 立即注册
搜索
查看: 979|回复: 0

最简单的ShellCode生成

[复制链接]

221

主题

233

帖子

792

积分

高级会员

Rank: 4

积分
792
发表于 2021-6-29 18:18:44 | 显示全部楼层 |阅读模式
最简单的ShellCode生成原创 wx_墨雪妖莲 [url=]看雪学院[/url] 前天

本文为看雪论坛优秀文章
看雪论坛作者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:

  1. struct FpuSaveState
  2. {
  3. uint32 control_word;    // 控制码
  4. uint32 status_word;        // 状态码
  5. uint32 tag_word;        // 标志位
  6. uint32 fpu_instruction_pointer;        // *指向上条FPU指令*
  7. uint16 fpu_instruction_selector;   
  8. uint16 fpu_opcode;
  9. uint32 fpu_operand_pointer;
  10. uint16 fpu_operand_selector;
  11. uint16 reserved;

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

           

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

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

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







大致代码原理如下:
  1. xor eax,eaxmov eax,fs:[0x30]        #获取PEB地址
复制代码
获取到了指定的模块基址后,我们就可以通过其来获取我们想要的函数地址了,在这里,需要对PE结构有一个基本的了解。

怎么获取函数地址呢?若是能够使用函数的话,当然可以直接使用GetProcAddress进行获取,但现在我们写的是ShellCode,必然不可能通过这种方法进行获取,这里我们就要用到PE结构中的导出表了:(在这里我就不用汇编表示了,我用C的结构体进行表示更加明了)
  1. // 获取DOS头
  2. IMAGE_DOS_HEADER* DosHeader = (IMAGE_DOS_HEADER*)kernelBase;
  3. // 获取NT头
  4. IMAGE_NT_HEADERS* NtHeader = (IMAGE_NT_HEADERS*)(DosHeader->e_lfanew + kernelBase);
  5. // 获取扩展头
  6. IMAGE_OPTIONAL_HEADER OPHeader = NtHeader->OptionalHeader;
  7. // 获取导出表
  8. IMAGE_EXPORT_DIRECTORY* ExportList= (IMAGE_EXPORT_DIRECTORY*)(OPHeader.DataDirectory[0].VirtualAddress + kernelBase);
  9. // 获取函数地址表
  10. DWORD* FuncAddList = (DWORD*)(ExportList->AddressOfFunctions+kernelBase);
  11. // 获取函数名称表
  12. DWORD* NameAddList = (DWORD*)(ExportList->AddressOfNames+kernelBase);
  13. // 获取函数序号表
  14. SHORT* OriList = (SHORT*)(ExportList->AddressOfNameOrdinals + kernelBase);

复制代码
获取到了导出表后,就可以通过遍历导出表中的名称表,序号表,以及函数地址表就可以进行获取函数地址了(这里我找的是GetProcAddress):
  1. // 循环遍历,获取LoadLibrary函数地址以及GetProceAddress地址
  2. for (int i = 0; i < ExportList->NumberOfNames; i++)
  3.     {
  4. if (g_strcmp((char*)(NameAddList[i] + kernelBase), g_GetProcAddress) == TRUE)
  5.         {
  6.             My_GetProcAddress = (MyGetProcAddress)((FuncAddList[OriList[i]]+kernelBase));
  7.         }
  8.     }
复制代码

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

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


           

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


  1. inline BOOL g_strcmp(char* str1, char* str2)
  2. {
  3. // 判断对比字符的长度,防止访问越界
  4. int length = 0;
  5. for (length;; length++)
  6.     {
  7. if (str1[length] == 0 || str2[length] == 0)
  8. break;
  9.     }
  10. // 进行对比
  11. for (int i = 0;i<length; i++)
  12.     {
  13. if (str1[i] != str2[i])
  14. return FALSE;
  15. else if (str1[i] == str2[i] && str2[i + 1] == 0&&str1[i+1]==0)
  16. return TRUE;
  17.     }
  18. };

复制代码
做到了以上代码,我们就可以通过kernel32.dll找到GetProcAddress和LoadLibrary的函数地址,进行后续的操作。

例:通过获取到的GetProcAddress地址和LoadLibrary的函数地址,我们就可以直接调用它去获取其他的函数地址。

比如实现一个弹框:MessageBoxA(在user32.dll中)
  1. // 定义函数指针
  2. typedef HMODULE(WINAPI* MyLoadLibrary)(_In_ LPCSTR lpFileName);
  3. typedef FARPROC(WINAPI* MyGetProcAddress)(_In_ HMODULE hModule, _In_ LPCSTR lpProcName);
  4. typedef int(WINAPI* MyMessageBox)(_In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType);
  5. // 通过遍历导出表进行获取函数地址(这里我就不详细写了,具体情况就是上面的代码修改一下)
  6. HMODULE hModule = NULL;
  7. hModule=My_LoadLibrary(g_user32);
  8. My_MessageBox=(MyMessageBox)My_GetProcAddress(hModule, g_MessageBox);
  9. My_MessageBox(0, 0, 0, 0);
复制代码

3、生成ShellCode

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

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



4、调试生成的ShellCode

对于Shell Code的调试,我们可以直接运用以下代码(这里的原理我就不多说了,就是将arr的首地址当成代码进行执行):

不过要注意的是:#pragma comment(linker, "/section:.data,RWE")要加上,不然你的数据区就是不可执行的,自然就不会运行你编写的ShellCode了。

  1. #include<Windows.h>
  2. #include<iostream>
  3. // 使数据段可读可写可执行
  4. #pragma comment(linker, "/section:.data,RWE")
  5. // 生成的ShellCode
  6. char arr[] = { "\x55\x8B\xEC\x81\xEC\x2C\x01\x00\x00\x53\x33\xC0\xC7\x45\xD8\x4C\x6F\x61\x64\x56\x57"\
  7. "\xC7\x45\xDC\x4C\x69\x62\x72\xC7\x45\xE0\x61\x72\x79\x41\x88\x45\xE4\xC7\x45\xC8\x47"\
  8. "\x65\x74\x50\xC7\x45\xCC\x72\x6F\x63\x41\xC7\x45\xD0\x64\x64\x72\x65\x66\xC7\x45\xD4"\
  9. "\x73\x73\x88\x45\xD6\xC7\x45\xF4\x75\x73\x65\x72\xC7\x45\xF8\x33\x32\x2E\x64\x66\xC7"\
  10. "\x45\xFC\x6C\x6C\x88\x45\xFE\xC7\x45\xE8\x4D\x65\x73\x73\xC7\x45\xEC\x61\x67\x65\x42"\
  11. "\xC7\x45\xF0\x6F\x78\x41\x00\x33\xC0\x64\xA1\x30\x00\x00\x00\x8B\x40\x0C\x8B\x70\x1C"\
  12. "\x8B\x06\x8B\x00\x8B\x40\x08\x89\x45\xC4\x8B\x5D\xC4\x8D\xBD\xD4\xFE\xFF\xFF\x6A\x38"\
  13. "\x59\x8D\x73\x18\x03\x73\x3C\xF3\xA5\x8B\x85\x34\xFF\xFF\xFF\x33\xFF\x33\xF6\x8B\x4C"\
  14. "\x18\x1C\x8B\x54\x18\x20\x03\xCB\x89\x4D\xBC\x03\xD3\x8B\x4C\x18\x24\x8B\x44\x18\x18"\
  15. "\x03\xCB\x89\x55\xB4\x89\x4D\xC0\x89\x45\xB8\x85\xC0\x74\x2E\x8D\x45\xC8\x50\x8B\x04"\
  16. "\xB2\x03\xC3\x50\xE8\x40\x00\x00\x00\x59\x59\x83\xF8\x01\x75\x0F\x8B\x45\xC0\x8B\x4D"\
  17. "\xBC\x0F\xBF\x04\x70\x8B\x3C\x81\x03\xFB\x8B\x55\xB4\x46\x3B\x75\xB8\x72\xD2\x8D\x45"\
  18. "\xD8\x50\x53\xFF\xD7\x8D\x4D\xF4\x51\xFF\xD0\x8D\x4D\xE8\x51\x50\xFF\xD7\x33\xC9\x51"\
  19. "\x51\x51\x51\xFF\xD0\x5F\x5E\x5B\xC9\xC3\x55\x8B\xEC\x53\x8B\x5D\x0C\x33\xD2\x56\x8B"\
  20. "\x75\x08\x57\x8B\xFA\x38\x16\x74\x11\x8B\xCB\x8B\xC6\x2B\xCE\x38\x14\x01\x74\x06\x47"\
  21. "\x40\x38\x10\x75\xF5\x85\xFF\x7E\x30\x8B\xC6\x2B\xC3\x89\x45\x08\x8D\x0C\x1A\x8A\x04"\
  22. "\x08\x3A\x01\x75\x1D\x80\x7C\x1A\x01\x00\x75\x07\x80\x7C\x32\x01\x00\x74\x0A\x42\x3B"\
  23. "\xD7\x7D\x0C\x8B\x45\x08\xEB\xDE\x33\xC0\x40\xEB\x02\x33\xC0\x5F\x5E\x5B\x5D\xC3" };
  24. int main()
  25. {
  26.     _asm {
  27.         lea eax,arr
  28.         push eax
  29.         jmp eax
  30.     }
  31. }
复制代码
​ 运行效果如下:(当然你也可以添加弹框的参数,用上述的char数组进行添加),或者自己写一些有趣的小玩意。



回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-11-29 06:31 , Processed in 0.014678 second(s), 18 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表