Shellcode提取、加载与解析

admin 2023年2月24日20:02:54评论101 views字数 4150阅读13分50秒阅读模式

       在我刚刚入行时,看见师傅各种shellcode乱飞,看着这些二进制代码,又看看墙上黑客帝国的尼奥海报,内心逐渐向往起来。shellcode使用比较灵活,在免杀和漏洞利用中经常被大家使用到。那么我们是如何去是实现一个shellcode的提取、加载呢,msf的shellcode又是如何实现的呢?

Shellcode提取、加载与解析

0x01 shellcode基础

shellcode为16进制的机器码,下面我们通过一段打开计算器Calc.exe的简单的代码来进一步了解一下什么是shellcode。

#include "stdafx.h"          #include                             int main(int argc, char* argv[])          {            WinExec("calc",1);            return 0;          }

一句特别简单的代码,调用Windows API WinExec 打开计算器,接着我们在OD动态调试看看。

Shellcode提取、加载与解析
push 0x1 ;在x86下,通过压栈来传参,将1压栈,WinExec("calc",1)里的参数1          push OpenCalc.00406030 ;将存放calc字符串的地址压栈,也是传参          call dword ptr ds:[<&KERNEL32.WinExec>] ;调用KERNEL32下的WinExec

引出问题

我们将这三句话下划线标注部分的机器码,用C语言的方式表达就是"x6Ax01x68x30x60x40x00xFFx15x00x50x40x00",也就是我常见的shellcode字符串。如果我们把这串加载在内存,能不能成功运行起来,恐怕是不行的,因为我们不能保证每一个程序的0x406030地址上都存放了calc字符串,也不能保证导入表中0x405000就是WinExec地址。


0x02 编写一个简单的shellcode  

那么问题很多的小明就会问,如果我们直接传calc字符串,写死的WinExec地址,是不是能在当前环境下的主机上运行?那我们就来试试,试试就试试。

首先构造一个calc字符串

xor  ecx, ecx              ; 把ecx置零                   push ecx                   ; 置零后压栈,充当字符串结尾的x00                   push 0x636c6163            ; clac(calc小端)                   mov  eax, esp              ; 再把指向calcx00的栈顶指针保存到eax

获取Kernel32 WinExec 地址 

有两种方法获取地址,一是通过动态调试,二是通过GetProcAddress.

方法一:

Shellcode提取、加载与解析

选中或右键再数据窗口跟随,即可获取 WinExec 地址。

Shellcode提取、加载与解析

方法二:

#include "stdafx.h"#include <windows.h>
typedef int (__cdecl *MYPROC)(LPTSTR);
int main() {  HINSTANCE Kernel32Addr;  MYPROC WinExecAddr;
 Kernel32Addr = GetModuleHandle("kernel32.dll");  printf("KERNEL32 address in memory: 0x%08pn", Kernel32Addr);
 WinExecAddr = (MYPROC)GetProcAddress(Kernel32Addr, "WinExec");
 printf("WinExec address in memory is: 0x%08pn", WinExecAddr );  getchar();    return 0;}
Shellcode提取、加载与解析

构造完整的汇编代码

section .data
section .bss
section .text  global _start
_start:  xor  ecx, ecx              ; 把ecx置零  push ecx                   ; 置零后压栈,充当字符串结尾的x00  push 0x636c6163            ; clac(calc小端)  mov  eax, esp              ; 再把指向calcx00的栈顶指针保存到eax    inc  ecx              ; ecx 置1  push ecx              ; 压栈传第二个参数1  push eax              ; 压栈,eax指向的是calcx00  mov  ebx, 0x7c86250d  ; 把WinExec地址保存到evx  call ebx              ; 执行WinExec

将上面文件保存为xxx.asm,如果windows没有编译环境,可以直接用kali进行编译,命令如下

nasm -f elf32 -o xxx.o xxx.asmld -m elf_i386 -o xxx xxx.o

然后通过 objdump -d xxx 读取文件,并将其通过C格式打印出来,命令如下 

objdump -M intel -d xxx | grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr 't' ' '|sed 's/ $//g'|sed 's/ /\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'

这样就顺利拿到了我们的shellcode

Shellcode提取、加载与解析

加载shellcode  

#include "stdafx.h"#include <windows.h>
unsigned char shellcode[] = "x31xc9x51x68x63x61x6cx63x89xe0x41x51x50xbbx0dx25x86x7cxffxd3x59";

int main(int argc, char* argv[]){  //将shellcode保存到内存中进行加载  void* exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);  memcpy(exec, shellcode, sizeof shellcode);  ((void(*)())exec)();  return 0;}

编译后运行。

Shellcode提取、加载与解析

成功弹出计算器,证实了小明的猜想是可行的。那么问题很多的小明又问了,这种写死的地址只能自己操自己,那有什么用呢?不妨我们来看下msf的shellcode是如何实现在不同机器上实现功能的。


0x03 解析msf的shellcode  

我们从逆向的角度去解析msf的shellcode,首先我们先通过一条msfvenom命令输出与上文实现相同功能的shellcode。

msfvenom -p windows/exec cmd=calc.exe -f c

编译我们的加载器,对其进行逆向分析。

#include "stdafx.h"#include <windows.h>
// msf shellcode unsigned char shellcode[] = "xfcxe8x82x00x00x00x60x89xe5x31xc0x64x8bx50x30""x8bx52x0cx8bx52x14x8bx72x28x0fxb7x4ax26x31xff""xacx3cx61x7cx02x2cx20xc1xcfx0dx01xc7xe2xf2x52""x57x8bx52x10x8bx4ax3cx8bx4cx11x78xe3x48x01xd1""x51x8bx59x20x01xd3x8bx49x18xe3x3ax49x8bx34x8b""x01xd6x31xffxacxc1xcfx0dx01xc7x38xe0x75xf6x03""x7dxf8x3bx7dx24x75xe4x58x8bx58x24x01xd3x66x8b""x0cx4bx8bx58x1cx01xd3x8bx04x8bx01xd0x89x44x24""x24x5bx5bx61x59x5ax51xffxe0x5fx5fx5ax8bx12xeb""x8dx5dx6ax01x8dx85xb2x00x00x00x50x68x31x8bx6f""x87xffxd5xbbxf0xb5xa2x56x68xa6x95xbdx9dxffxd5""x3cx06x7cx0ax80xfbxe0x75x05xbbx47x13x72x6fx6a""x00x53xffxd5x63x61x6cx63x2ex65x78x65x00";
int main(int argc, char* argv[]){  void* exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);  memcpy(exec, shellcode, sizeof shellcode);  ((void(*)())exec)();  return 0;}

作者使用OD调试报错,而改用windbg进行动态调试。

0x401000为main函数(代码段初始地址),是我们加载代码,通过virtualAlloc申请内存空间,并把内存地址放在了eax里面,为0x003A0000,通过memcpy,将存放在.data段的shellcode变量存放在0x003A0000上,然后call。

Shellcode提取、加载与解析

我们跟进去看下,发现已经shellcode已经复制进了内存,我们的shellcode就能够正常运行起来了,我把每一步汇编的作用都注释了起来。

Shellcode提取、加载与解析

Shellcode提取、加载与解析

大致的流程我们可以分解成:

1.通过硬编码偏移0x82获得shellcode末尾的字符串“calc.exe”,然后入栈参数1以及calc.exe,还有winexec API hash 876F8B31

2.循环遍历PEB表获取模块基址

3.解析PE文件,无导出表则跳过,继续2

4.解析导出表,导出表数量为0则跳过,继续2

5.根据导出名称表遍历导出名称来计算hash,并找到对应的函数,也就是WinExec

6.若找不到该函数,则通过链表找到下一个模块信息,继续2

7.找到winExec,执行该函数


里面比较有意思的是使用了一个API hash来查找函数地址,这种技术叫SFHA(Stephen Fewer’s Hash AI),相关技术在17年的DEFCON时有专题讲解。

0x04总结

书山有路勤为径,学海无涯苦作舟,还得是要多多调试下代码,才能深化理解。

原文始发于微信公众号(TERRA星环安全团队):Shellcode提取、加载与解析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年2月24日20:02:54
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Shellcode提取、加载与解析https://cn-sec.com/archives/1572764.html

发表评论

匿名网友 填写信息