《黑客攻防-系统实战》--shellcode

《黑客攻防-系统实战》--shellcode

shellcode

  shellcode 是一组可注入的指令,可以在被攻击得到程序内运行,因为shellcode要直接操作寄存器和程序函数,所以通常用汇编语言编写并被翻译为十六进制操作码,因此不能用高级语言编写shellcode, 即使细微的差别有可能导致shellcode无法准确执行,这些导致编写shellcode难度的原因

  shellcode最初的作用是漏洞利用程序的特殊部分,破解漏洞就是预先把shellcode注入到缓冲区,然后欺骗目标程序执行它

 

理解系统调用

  写shellcode的目的就是想让目标程序以不同于设计者预期的方式运行,而操纵程序的方式之一就是强制它产生系统调用,通过系统调用可以获取一些特权操作,访问系统内核,

  调用系统调用方法:1)使用C库包装(libc) 2)使用汇编指令(把适当的参数加载到寄存器,然后调用软中断)执行系统调用

 

系统调用的过程

  linux环境程序通过int 0x80软中断来执行系统调用,程序执行int  0x80时,CPU切换到内核模式并执行相应的系统调用,使用fastcall约定, 提高寄存器的使用率

复制代码
 calling convention)即C调用约定按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的。
      另外,在函数名修饰约定方面也有所不同。 _cdecl是C和C++ f( *p) –>> _f@
复制代码

调用过程:

  1)系统调用编号载入EAX

  2)把系统调用参数压入栈中(注意:最多支持6个参数,分别保存在:EBX,ECX,EDX,ESI,EDI,EBP,  超过六个,通过第一个指定数据结构传递)

  3)执行int 0x80指令

  4)CPU切换到内核模式

  5)执行系统调用

举个例子:

  

复制代码
1 #include <stdio.h>2 3 int main()4 {5     exit(0);6     return 0;7 }
复制代码

exit() -> _exit()

反汇编看一下:

 

 

 call   *%gs:0x10     ;系统调用从这里进入,会调用到int80

mov 0x4(%esp), %ebx; 退出把系统调用参数加载到ebx, 系统调用之前压栈

编写shellcode

注意:

  shellcode大小:由于较小的shellcode 可以注入更多的缓冲区,可以用来攻击更多的程序,所以要使得shellcode尽量保持简单,紧凑

  当攻击问题程序的时候,需要把shellcode复制到缓存区,另外还要加上调用shellcode的指令, 所以必须要小

分析程序:

  前面exit系统调用分析主要完成三个动作:

  1)把0放到EBX

  2)把1放到EAX

  3)执行int  0x80 指令来产生系统调用

1. hello.c

复制代码
 1 section .data                           ;section declaration 2 msg     db      "Hello, world!",0xA     ;our dear string 3 len     equ     $ - msg                 ;length of our dear string 4 section .text                           ;section declaration 5                        ;we must export the entry point to the ELF linker or 6    global _start       ;loader. They conventionally recognize _start as their 7                        ;entry point. Use ld -e foo to override the default. 8 _start: 9 ;write our string to stdout10        mov     eax,4   ;system call number (sys_write)11        mov     ebx,1   ;first argument: file handle (stdout)12        mov     ecx,msg ;second argument: pointer to message to write13        mov     edx,len ;third argument: message length14        int     0x80    ;call kernel15 ;and exit16        mov     eax,1   ;system call number (sys_exit)17        xor     ebx,ebx ;first syscall argument: exit code18        int     0x80    ;call kernel
复制代码

2. objdump -d hello

复制代码
 1 foobar:     fiobjdump -d foobarle format elf32-i386 2  3  4 Disassembly of section .text: 5  6 08049000 <.text>: 7  8049000:    b8 04 00 00 00           mov    $0x4,%eax 8  8049005:    bb 01 00 00 00           mov    $0x1,%ebx 9  804900a:    b9 00 a0 04 08           mov    $0x804a000,%ecx10  804900f:    ba 0e 00 00 00           mov    $0xe,%edx11  8049014:    cd 80                    int    $0x8012  8049016:    b8 01 00 00 00           mov    $0x1,%eax13  804901b:    31 db                    xor    %ebx,%ebx14  804901d:    cd 80                    int    $0x80
复制代码

 

3. 把操作码写到字符串数组中执行

char shellcode[] = "\xb8\x04\x00\x00\x00"

        "\xbb\x01\x00\x00\x00"

        "\xb9\x00\xa0\x04\x08"

        "\xba\x0e\x00\x00\x00"

        "\xcd\x80"

        "\xb8\x01\x00\x00\x00"

        "\x31\xdb"

        "\xcd\x80"

 

gcc -o wack wack.c -m32 -z execstack

 

跳板技术(转https://zhuanlan.zhihu.com/p/88459547

程序每次运行后在内存中的指令地址都是变化的,所以shellcode入口地址也是动态的,所以为了能够动态找到shellcode的位置,引入了跳板技术。

如图所示,左边表示存储返回地址的栈帧填充为shellcode入口地址,这种方式下次运行时入口地址将发生变化导致失败,右边表示跳板技术后,通过esp来定位shellcode,这种方式可保证下次运行exp依然有效。

 

 

跳板技术是用来动态跳转shellcode的,shellcode代码需要从函数返回后esp的栈顶位置开始,然后函数返回到JMP ESP指令处,指令执行后跳到esp位置进入shellcode入口。

注: 对于不同的返回指令的不同,函数返回后esp的指向也有所不同。一般执行ret指令后esp+4,此时shellcode放在存放返回地址的栈帧的下一位置。若是ret N指令,执行后esp+4+N,则shellcode需要放在计算出的对应位置处才行。JMP ESP指令的地址要已知,在xp中JMP ESP可以通过加载kernel32.dll、user32.dll、mfc32.dll等这些经常被加载到内存中的库中寻找,一般地址都是确定的。利用C实现查找代码如下:

复制代码
 1 # include <stdio.h> 2 #include <windows.h> 3   4 main() 5 { 6     HINSTANCE hLib; 7     hLib = LoadLibrary("user32.dll"); 8     if(!hLib) 9     {10         printf("Load dll error!\n");11         exit(0);12     }13  14     byte* ptr = (byte*) hLib;15     int address;16     int position;17     bool done_flag = false;18  19     for(position=0; !done_flag; position++)20     {21         try22         {23             if(ptr[position] == 0xFF && ptr[position+1] == 0xE4)24             {25              // jmp esp 的机器码 为 0xFFE426                 address = (int)ptr + position;27                 printf("Find OPcode at 0x%08lX\n", address);28             }29         }30         catch(...)31         {32             address = (int)ptr + position;33             printf("End of 0x%08lX\n", address);34             done_flag = true;35         }36     }37 }
复制代码

 

抬高栈顶保护shellcode 

如果shellcode放在返回地址栈帧之前,那么在函数返回后栈顶位置会在shellcode下方,虽然出栈后的数据不被清空,但是却会受入栈操作的影响,所以shellcode中若存在push操作,很有可能破坏shellcode结构:

 

所以要在shellcode开头适当先抬高栈顶让shellcode在栈顶下方,这样push就不会影响shellcode。

抬高栈顶可以用sub esp, N,N大于shellcode长度即可。


关键词:新闻动态