在编写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指令在执行时分别做了什么。
CPU执行ret指令时,进行下面的两步操作:
1. (IP) = ((ss)*32 +(sp))
2. (sp) = (sp)+4
(sp) = (sp)-4
((ss)*32 +(sp)) = (IP)
(IP) = (IP)+16位位移。
1. push IP
2. jmp near ptr XX