从上下了一个程序unixc.rar,在readme.txt中,作者说:
该程序模拟UNIX中save与resume函数,并介绍在VC中如何使用汇编进行机器级的操作.
主函数很简单首先引入两个外部函数,
extern"C"表示按传统C命名习惯.函数save将程序指针保存在(*s)中并返回0,为什么有
if(save(&sp)){...}
if后的语句看起来永远都不会被执行,但是运行结果表明它被执行了.这个问题同UNIX中处理机调度函数(switc
h)的那个if语句(第一句)一样.
程序执行完save(&sp)后得到因为条件为假而执行else语句,却在判断之前将程序指针保存在sp中了.
else语句中的resume(&sp),该函数很狡猾将堆栈中的返回地址改变了,改到了sp所指出,即将程序指针改到了执
行条件判断前.resume返回1,条件满足,执行if语句.
save函数堆栈:
eip ebp+8
s ebp+4
ebp ebp+0
resume函数堆栈与save的相同.
新建一个win32的工程,将unixc.cpp和unix.obj加入过程即可.
unix.obj是用masm6.11生成的:ml /c /coff unix.asm,生成coff格式的obj而不是omf格式.
本文主要通过反汇编来说明,如何通过保存返回地址使得即执行程序的if模块还执行
程序的else模块。
--- E:\程序_规范\UnixC\UnixC.cpp ----------------------------------------------
1:
2: #include<stdio.h>
3: extern "C" int save(long * s);
4: extern "C" int resume(long * s);
5: void main()
6: {
00401030 push ebp
保存栈底指针
ESP = 0012FF80
EBP = 0012FFC0
0012FF80 C0 FF 12 ... 堆栈内为ebp的值
00401031 mov ebp,esp //把当前的栈顶当作栈底
ESP = 0012FF80
EBP = 0012FF80
00401033 sub esp,44h //预留44h的栈空间
ESP = 0012FF3C 栈顶指向
EBP = 0012FF80 此时的栈底
00401036 push ebx //把要用到的寄存器值进栈
ESP = 0012FF38
EBP = 0012FF80
012FF39 F0 FD 7F 瘕. //为ebx
0012FF3C 02 00 00 ...
00401037 push esi
00401038 push edi
ESP = 0012FF30
EBP = 0012FF80
0012FF30 00 00 00 ... 为edi
0012FF33 00 00 00 ... 为esi
0012FF36 00 00 00 ...
0012FF39 F0 FD 7F 瘕.
0012FF3C 02 00 00 ...
00401039 lea edi,[ebp-44h]
EDI = 0012FF3C
0040103C mov ecx,11h //计数器 11h*4=44h
00401041 mov eax,0CCCCCCCCh
00401046 rep stos dword ptr [edi] //将这44个字节全部初始化为cc 即int 3
7: long spp;
0012FF7C CC CC CC 烫.
&sp = 0x0012ff7c
8: printf("Both the code in the if and the code in the else will be exe!!!\n");
00401048 push offset string "Both the code in the if and the "... (00422050)
0040104D call printf (004010c0)
00401052 add esp,4
9: if(save(&spp))
00401055 lea eax,[ebp-4]
//将spp的地址付给eax
eax = 0x0012ff7c
00401058 push eax
ESP = 0012FF2C
EBP = 0012FF80
012FF2C 7C FF 12 |.. //eax入栈
0012FF2F 00 00 00 ...
0012FF32 00 00 00 ...
0012FF35 00 00 00 ...
0012FF38 00 F0 FD .瘕
0012FF3B 7F CC CC .烫
0012FF3E CC CC CC 烫.
00401059 call @ILT+5(_main+176080) (0040100a)
ESP = 0012FF28
EBP = 0012FF80
012FF26 12 00 5E ..^ 子程序返回时,下一条要执行的语句的地址
0012FF29 10 40 00 .@.
0012FF2C 7C FF 12 |..
0012FF2F 00 00 00 ...
0012FF32 00 00 00 ...
0040105E add esp,4 将调用_save时的参数弹出栈
ESP = 0012FF30
EBP = 0012FF80
00401061 test eax,eax
//返回至为零,因为我们置eax为零
00401063 je main+44h (00401074)
10: {
11: printf("return form the if\n");
00401065 push offset string "return form the if\n" (00422038)
0040106A call printf (004010c0)
0040106F add esp,4
12: return;
00401072 jmp main+5Dh (0040108d)
13: }
14: else
15: {
16: printf("return form the else\n");
00401074 push offset string "return form the else\n" (0042201c)
00401079 call printf (004010c0)
0040107E add esp,4
17: resume(&spp);
00401081 lea ecx,[ebp-4]
把spp的地址付给ecx,作为函数调用的参数
ECX = 0012FF7C
00401084 push ecx
入栈
ESP = 0012FF2C
EBP = 0012FF80
0012FF2C 7C FF 12 |.. spp的地址
0012FF2F 00 00 00 ...
00401085 call @ILT+0(_main+176098) (00401005)
0040108A add esp,4
18: }
19: return;
20: }
0040108D pop edi
0040108E pop esi
0040108F pop ebx
00401090 add esp,44h
00401093 cmp ebp,esp
00401095 call __chkesp (00401140)
0040109A mov esp,ebp
0040109C pop ebp
0040109D ret
子函数_save
_save:
ESP = 0012FF28
EBP = 0012FF80
012FF26 12 00 5E ..^ _save返回时,下一条要执行的语句的地址
0012FF29 10 40 00 .@.
0012FF2C 7C FF 12 |..
0012FF2F 00 00 00 ...
0012FF32 00 00 00 ...
0042B000 push ebp
ESP = 0012FF24
EBP = 0012FF80
0012FF24 80 FF 12 €.. ebp
0012FF27 00 5E 10 .^. _save返回时,下一条要执行的语句的地址
0012FF2A 40 00 7C @.|
0012FF2D FF 12 00 ...
0012FF30 00 00 00 ...
0042B001 mov ebp,esp
ESP = 0012FF24
EBP = 0012FF24
0042B003 mov esi,dword ptr [ebp+8]
ESI = 0012FF7C spp的地址给esi
0042B006 mov eax,dword ptr [ebp+4]
EAX = 0040105E _save返回时,下一条要执行的语句的地址给eax
0042B009 mov dword ptr [esi],eax
在spp的内存地址中保留_save返回时下一条要执行的语句的地址 0040105e
012FF7C 5E 10 40 ^.@
0012FF7F 00 C0 FF ...
0042B00B pop ebp
ESP = 0012FF28 ESP= ESP+4
EBP = 0012FF80
012FF28 5E 10 40 ^.@返回时下一条要执行的语句的地址
0012FF2B 00 7C FF .|.
0012FF2E 12 00 00 ...
0012FF31 00 00 00 ...
0042B00C mov eax,0 //郅返回结果为0
0042B011 ret 回主函数
子函数_resume
_resume:
0042B012 push ebp
0042B013 mov ebp,esp
ESP = 0012FF24
EBP = 0012FF24
012FF24 80 FF 12 €..
0012FF27 00 8A 10 ... //函数调用返回后,下一条要执行的语句的地址0040108a
0012FF2A 40 00 7C @.|
0012FF2D FF 12 00 ...
这时,我么要注意,ebp+4就是函数调用返回后,下一条要执行的语句的地址0040108a
我们想把函数的返回地址改变,改为执行_save后的第一条指令,还指令的地址放在ebp+8中
0042B015 mov esi,dword ptr [ebp+8]
取出_save后的第一条地址存放的地址为spp的地址 0012ff7c
0042B018 mov eax,dword ptr [esi]
将esi中的值给eax eax =0x0040105e 这就是改为执行_save后的第一条指令,而不是按正常执行_resume后的第一条指令
0042B01A mov dword ptr [ebp+4],eax
修改_resume函数返回后执行的第一条指令的地址
012FF24 80 FF 12 €..
012FF27 00 5E 10 .^. //函数调用返回后,下一条要执行的语句的地址由0040108a改为0040105e
0012FF2A 40 00 7C @.|
0012FF2D FF 12 00 ...
0042B01D pop ebp
0042B01E mov eax,1
//付值为1,返回,但程序会跳转到_save后的第一条指令,这样else 和if 都执行了
0042B023 ret
总的来说,就是通过一个变量来保存返回断点的下一条指令的地址,然后用该地址
替换程序正常返回地址,从而达到目的。