Shellcode中ret调用和call调用函数区别

ShellShellcode中ret调用和call调用函数区别

在编写shellcode时,我们通常会使用call或者ret来调用执行某些系统函数或者我们自己编写的函数代码。下面以一个调用calc.exe的shellcode来说明一下它们的使用方式

执行calc.exe的C语言代码:

    1 LoadLibrary("kernel32.dll");
    2 WinExec("calc.exe", 5);
    3 ExitProcess(0);

对应的汇编代码为:

     1     .386
     2     .model flat,stdcall
     3     option casemap:none
     4 
     5 include     windows.inc
     6 include     user32.inc
     7 includelib  user32.lib
     8 include     kernel32.inc
     9 includelib  kernel32.lib
    10 
    11     .code
    12 start:
    13     ;push ebp
    14     ;mov ebp,esp
    15 
    16     ;LoadLibrary("kernel32.dll");
    17     ;xor eax,eax
    18     ;push eax
    19     ;mov eax,6C6C642Eh   ;".dll"
    20     ;push eax
    21     ;mov eax,32336C65h   ;"el32"
    22     ;push eax
    23     ;mov eax,6E72656Bh   ;"kern"
    24     ;push eax
    25     ;mov eax,esp
    26     ;push eax            ;Arg1 = "kernel32.dll"
    27     ;mov eax,7C801D7Bh   ;kernel32.LoadLibrary
    28     ;call eax
    29 
    30     ;WinExec("calc.exe", 5);
    31     xor eax,eax
    32     push eax
    33     mov eax,6578652Eh   ;".exe"
    34     push eax
    35     mov eax,636C6163h   ;"calc"
    36     push eax
    37     mov eax,esp
    38     push 5              ;Arg2 = SW_SHOW
    39     push eax            ;Arg1 = "calc.exe"
    40     mov eax,7C8623ADh   ;kernel32.WinExec
    41     call eax
    42 
    43     ;ExitProcess(0);
    44     xor eax,eax
    45     push eax            ;Arg1 = 0
    46     mov eax,7C81CAFAh   ;kernel32.ExitProcess
    47     call eax
    48 
    49     ;mov esp,ebp
    50     ;pop ebp
    51 end start

我们重点关注调用winexec函数打开calc.exe的汇编代码:

    30     ;WinExec("calc.exe", 5);
    31     xor eax,eax
    32     push eax
    33     mov eax,6578652Eh   ;".exe"
    34     push eax
    35     mov eax,636C6163h   ;"calc"
    36     push eax
    37     mov eax,esp
    38     push 5              ;Arg2 = SW_SHOW
    39     push eax            ;Arg1 = "calc.exe"
    40     mov eax,7C8623ADh   ;kernel32.WinExec
    41     call eax

首先将calc.exe压入栈中,然后将参数5压入栈,最后使用call调用kernal32.Exec函数。这种方式跟一般函数调用过程的汇编代码很类似。不赘述,下面看一下使用ret指令调用的版本:

    30     ;WinExec("calc.exe", 5);
    31     xor eax,eax
    32     push eax
    33     mov eax,6578652Eh   ;".exe"
    34     push eax
    35     mov eax,636C6163h   ;"calc"
    36     push eax
    37     mov eax,esp
    38     push 5              ;Arg2 = SW_SHOW
    39     push eax            ;Arg1 = "calc.exe"
    40     push eax            ;push a radom ret addr
    41     push 7C8623ADh      ;addr of kernel32.WinExec
    42     ret

这部分代码和上面代码很类似,但是没有直接使用call指令,而是先使用使用ret指令弹出push到栈顶winExec函数地址,再执行。但是在压入函数参数时,我们多执行了一次 push eax。这条指令的作用是什么呢?
作用是压入一个函数返回地址,这样在ret执行EinExec的时候,栈的分布就跟call调用函数时一致,当然返回地址是不对的。
那么为什么我们要多执行一次push eax ?首先我们来看看ret 和call指令在执行时分别做了什么。

1. ret

  • ret指令用栈中的数据,修改IP的内容,从而实现近转移;
  • CPU执行ret指令时,进行下面的两步操作:

    1. (IP) = ((ss)*32 +(sp))
    2. (sp) = (sp)+4

    • CPU 执行ret指令相当于执行 pop IP

2. call

  • CPU执行call指令时,进行两步操作:
    1.

    (sp) = (sp)-4
    ((ss)*32 +(sp)) = (IP)

    2.

    (IP) = (IP)+16位位移。
  • CPU执行call指令相当于执行

    1. push IP
    2. jmp near ptr XX

    我们可以发现,在使用call指令时,会自动将函数返回地址压入栈中,再去执行所调用函数代码,但是使用ret时,我们并没有将返回地址压栈,所以在将函数地址push并调用ret指令之前,要先push一个字长的数据作为返回地址(虚假返回地址占位).这样在调用函数汇编代码时,当前栈帧和使用call指令才是一致的。

你可能感兴趣的:(学习笔记)