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

shellcode

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

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

 

理解系统调用

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

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

 

系统调用的过程

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

__fastcall调用的主要特点就是快,因为它是通过寄存器来传送参数的(实际上,它用ECX和EDX传送前两个双字(DWORD)或更小的参数,
      剩下的参数仍旧自右向左压栈传送,被调用的函数在返回前清理传送参数的内存栈),在函数名修饰约定方面,它和前两者均不同。
      __fastcall方式的函数采用寄存器传递参数,VC将函数编译后会在函数名前面加上”@”前缀,在函数名后加上”@”和参数的字节数。 __cdecl (The C
default calling convention)即C调用约定按从右至左的顺序压参数入栈,由调用者把参数弹出栈。对于传送参数的内存栈是由调用者来维护的。
      另外,在函数名修饰约定方面也有所不同。 _cdecl是C和C++程序的缺省调用方式。每一个调用它的函数都包含清空堆栈的代码,所以产生的可执行文件大小
      会比调用_stdcall函数的大 __stdcall是Pascal方式清理C方式压栈,通常用于Win32 Api中,函数采用从右到左的压栈方式,自己在退出时清空堆栈。VC将函数编译后会在函数名前面加上下划线前缀,
      在函数名后加上”@”和参数的字节数。
int f(void *p) –>> _f@4(在外部汇编语言里可以用这个名字引用这个函数) 参考: https://blog.csdn.net/sunriver2000/article/details/84913380

调用过程:

  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 stdout
10        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 write
13        mov     edx,len ;third argument: message length
14        int     0x80    ;call kernel
15 ;and exit
16        mov     eax,1   ;system call number (sys_exit)
17        xor     ebx,ebx ;first syscall argument: exit code
18        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,%ecx
10  804900f:    ba 0e 00 00 00           mov    $0xe,%edx
11  8049014:    cd 80                    int    $0x80
12  8049016:    b8 01 00 00 00           mov    $0x1,%eax
13  804901b:    31 db                    xor    %ebx,%ebx
14  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         try
22         {
23             if(ptr[position] == 0xFF && ptr[position+1] == 0xE4)
24             {
25              // jmp esp 的机器码 为 0xFFE4
26                 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长度即可。

 

posted @ 2020-05-11 23:01  坚持,每天进步一点点  阅读(1809)  评论(0编辑  收藏  举报