科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网网络频道Windows下生成Shell的一种新方法

Windows下生成Shell的一种新方法

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

Shell实际上就是一小段可执行程序,有代码段、数据段和堆栈。只不过这段程序在内存中的位置只有在程序执行时才能确定,而在编译时并不能知道,这就给我们的编程带来了不少麻烦,数据不好定位与赋值。

作者:中国IT实验室 2007年8月23日

关键字: shell 数据段 ebp 赋值 char 函数名 define ASM datasize 新方法

  • 评论
  • 分享微博
  • 分享邮件

  Shell实际上就是一小段可执行程序,有代码段、数据段和堆栈。只不过这段程序在内存中的位置只有在程序执行时才能确定,而在编译时并不能知道,这就给我们的编程带来了不少麻烦,数据不好定位与赋值。UNIX下的jmp、call定位在我的VC上好像不太行;而数据的赋值一般用mov byte ptr[ebp-x],'?'来进行,既麻烦、又浪费间。另外要得到shell的代码,还得用VC中的disassembly命令。那么有没有好一点的办法呢?先看一下下面的程序:   

  #include

  #include

  #include

  void main()

  {

  char *buff,*data,shell[500];

  long off;

  int i

  __asm

  {

  mov eax,offset begin

  mov buff,eax

  mov off,offset end

  sub off,eax

  }

  for(i=0;i  shell=buff;

  

  for(i=0;i<4;i++) //写入偏移值(代码段的大小)

  {char ch=(char)(off>>(8*i));

  shell[4+i]=ch;

  }   

  //初始化数据段

  data=shell+off;

  data[0]=0x01;

  ....

  

  //输出shell

  FILE *fp;

  fp=fopen("shell","w");

  fwrite(shell,off+?,1,fp);

  fclose(fp);

  return;

  

  //shell

  begin://shell开始

  __asm{

  mov ebp,esp

  add ebp,0x11111111 //偏移值

  …

  }

  end://shell结束

  ;

  }

  首先应该注意到,程序没有执行完就return了,后面不被执行的地方就是我们要的shell代码,程序一开始就用一个buff指针指向这里。然后就把buff中的内容移到一个shell数组中,这就是我们要的shell代码段了。off是shell代码段的大小,data=shell+off,即data指向shell代码段之后,这就中数据段了,可以直接用data[x]=?对数据段进行初始化(是不是很方便?)。最后输出shell数组中的内容,就是完整的shell了,输出的格式可以自己定义,这里是以十六进制格式直接输出到一个文件中。   

  上面是shell数据的初始化与shell的输出。shell中数据段是如何定位的。大家看看不被执行的那一段汇编程序,mov ebp,esp,使ebp指向shell的代码段(运行时)开始处。add ebp,0x11111111,使ebp的指针下移(0x11111111并不是最终值,这个0x11111111会前面的shell[4+i]=?处中被off覆盖,4是0x11111111在shell中的偏移量),指向数据段(运行时)起始处。这下好了前面写入data[x]中的数据,我们可以在shell中用[ebp+x]来访问(也就是说shell与data形成了一一应的关系)。这里直接用off覆盖可能会产生0,我们可以把off与0xffffffff进行异或再覆盖,当然在shell中也要异或一次。最后我们把上面的程序稍作改动,并定义成宏。并把一些常用的操作也定义成宏,如下:   

  /*********shell.h*************/

  /***程序框架的宏************/

  #define SHELLDATA \

  void main()\

  {\

  char *BUFF,*DATA,SHELL[50000];\

  int CODESIZE,DATASIZE=0;\

  int I;\

  __asm mov eax,offset begin\

  __asm mov BUFF,eax\

  __asm mov CODESIZE,offset end\

  __asm {sub CODESIZE,eax }\

  for(I=0;I  SHELL=BUFF;\

  for(I=0;I<4;I++)\

  { char ch=(char)(CODESIZE>>(8*I));\

  SHELL[7+I]=(char)0xff-ch;\

  }\

  DATA=SHELL+CODESIZE;   

  

  #define SHELLCODE\

  return;\

  begin:\

  __asm mov ebp,0xffffffff\

  __asm xor ebp,0x11111111\

  __asm add ebp,esp   

  

  #define SHELLEND\

  end:\

  ;\

  }

  

  

  /***赋值用的宏************/

  #define STRING(ID,STR)\

  strncpy(&DATA[ID],STR,strlen(STR)+1);\

  if(DATASIZE  DATASIZE=ID+(int)strlen(STR)+1;   

  

  #define CHAR(ID,VAULE)\

  DATA[ID]=VAULE;\

  if(DATASIZE  DATASIZE=ID+1;   

  

  #define INT(ID,VAULE)\

  for(I=0;I<4;I++)\

  { char ch=(char)(VAULE>>(8*I));\

  DATA[ID+I]=(char)ch;\

  }\

  if(DATASIZE  DATASIZE=ID+4;

  

  /***为使写SHELL方便,而定义的一些宏***********/

  

  #define D(x) [ebp+x]

  #define P(x) push x

  #define L(x) }__asm lea edx,x __asm {push edx

  

  #define INVOKE0(f) {call dword ptr f}

  #define INVOKE1(f,p1) {p1}{call dword ptr f}

  #define INVOKE2(f,p1,p2) {p2}{p1}{call dword ptr f}

  #define INVOKE3(f,p1,p2,p3) {p3}{p2}{p1}{call dword ptr f}

  #define INVOKE4(f,p1,p2,p3,p4) {p4}{p3}{p2}{p1}{call dword ptr f}

  

  /***函数**********************/

  void OUTPUT(char *shell,int num)

  {

  FILE *fp;

  fp=fopen("shell","w");

  

  //Hex输出

  //fwrite(shell,num,1,fp);

  

  //数组形式输出

  for(int i=0;i  {fprintf(fp,"0x%x,",(unsigned char)shell);

  if((i+1)%10==0)

  fprintf(fp,"\n");

  }

  

  fclose(fp);

  }

  

  这里的SHELLDATA等是原来的程序主框架,是必须的;CHAR、STRING等宏是为了初始化数据时方便而写的的,第一个参数是一个整数,指明该参数在数据段中的位置。INVOKE4(f,p1,p2,p3,p4)中的4指的是函数中参数的个数是4个,f指的是函数名的地址如:可为[ebp+4],p1,p2是参数必须用L(地址入栈),P(直接入栈)来调用。例如:MessageBox函数的入口地址在[ebp+4]中,正文在[ebp+8]中,标题在[ebp+24]中,那么可写为

  xor eax,eax

  INVOKE4([ebp+4],P(eax),L([ebp+8]),L([ebp+24]),P(eax) )

  

  到此,我们的程序就应该成如下格式:

  #include

  #include

  #include

  #include "shell.h"

  SHELLDATA

  //定义数据

  STRING(0,"abcdefg")

  INT(8,25)

  CHAR(12,'A')

  ……

  //输出shell

  OUTPUT(SHELL, CODESIZE+DATASIZE);

  SHELLCODE

  __asm{

  //shell汇编代码

  ...

  }

  SHELLEND   

  接下来就是要找函数的API地址了,我们可以的LoadLibrary和GetProcAddress的入中地址放在DATA[0],DATA[4]中,以后可以直接使用call [ebp],call [ebp+4](CALL [ebp]会出现0,可用mov eax,ebp,call[eax]代替)。   

  要找地址的函数名放在DATA数组中,可从DATA[8]开始。函数名在找到地址后就没有用了,就把找到的地址放在函数名的位置(把函数名覆盖),这样有一个好处:可以直接根据函数名的位置访问函数。   

  数据段中的0要进行处理,我们可以在数据初始化之后,输出之前进行加密(用C在程序中实现),在SHELL的开头进行解密。这样得到的SHELL中的字符是加过密的,运行时自动解密,不要再另行处理了,十分方便。   

    • 评论
    • 分享微博
    • 分享邮件
          邮件订阅

          如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

          重磅专题
          往期文章
          最新文章