三节:分析W32Dasm得来的静态汇编代码,也就是程序最终的代码同时我们一步步来分析
这时堆栈的情况.
**写到这儿可能大家一定认识都是些看到就头大的代码吧,没事我先分析一下
这些代码就执行来说可以分为三个部分:
1.从01FE到020B是根据C代码中的定义在堆栈中分配空间例子中分了6个字节,定义多少分多少也没有毛病
2远跳到0000:1395是把数据段中的源串放到堆栈中由于放入个数在cx中所以这儿也没有毛病
3在堆栈中把源串拷到目的串所在的内存单元中问题就在这儿了!
:0001.01FA E80100 call 01FE //执行我们的j函数
:0001.01FD C3 ret
:0001.01FE 55 push bp
:0001.01FF 8BEC mov bp, sp
:0001.0201 83EC06 sub sp, 0006
:0001.0204 56 push si
:0001.0205 57 push di
:0001.0206 16 push ss
:0001.0207 8D46FA lea ax, [bp-06]
:0001.020A 50 push ax
:0001.020B 1E push ds
:0001.020C B89401 mov ax, 0194
:0001.020F 50 push ax
:0001.0210 B90300 mov cx, 0003
:0001.0213 9A95130000 call 0000:1395 //这儿先跳到1395去执行了由于它是在0000所以是远跳
:0001.0218 8D76FA lea si, [bp-06]
:0001.021B 8D7EFE lea di, [bp-02]
:0001.021E EB00 jmp 0220
:0001.0220 8BDE mov bx, si
:0001.0222 46 inc si
:0001.0223 8A07 mov al , [bx]
:0001.0225 8BDF mov bx, di
:0001.0227 47 inc di
:0001.0228 8807 mov [bx], al
:0001.022A 0AC0 or al , al
:0001.022C 75F2 jne 0220
:0001.022E 8D46FE lea ax, [bp-02]
:0001.0231 50 push ax
:0001.0232 B89701 mov ax, 0197
:0001.0235 50 push ax
:0001.0236 E8BC08 call 0AF5 //执行打印输出
:0001.0239 59 pop cx
:0001.023A 59 pop cx
:0001.023B 5F pop di
:0001.023C 5E pop si
:0001.023D 8BE5 mov sp, bp
:0001.023F 5D pop bp
:0001.0240 C3 ret
//下面的就是我们的SCOPY@
0001.1395 55 push bp
:0001.1396 8BEC mov bp, sp
:0001.1398 56 push si
:0001.1399 57 push di
:0001.139A 1E push ds
:0001.139B C57606 lds si, [bp+06]
:0001.139E C47E0A les di, [bp+0A]
:0001.13A1 FC cld
:0001.13A2 D1E9 shr cx, 01
:0001.13A4 F3 repz
:0001.13A5 A5 movsw
:0001.13A6 13C9 adc cx, cx
:0001.13A8 F3 repz
:0001.13A9 A4 movsb
:0001.13AA 1F pop ds
:0001.13AB 5F pop di
:0001.13AC 5E pop si
:0001.13AD 5D pop bp
:0001.13AE CA0800 retf 0008
我们现在开始DEBUG动态调试:
第一步D:\turboc2>debug c4.exe
-g 01FE 通过W32DASM中的查找我们直接跳到J入口处执行
AX=0000 BX=0566 CX=000E DX=067F SP=FFE8 BP=FFF4 SI=00D8 DI=054B
DS=13DB ES=13DB SS=13DB CS=129F IP=01FE NV UP EI PL ZR NA PE NC
129F:01FE 55 PUSH BP
-t
AX=0000 BX=0566 CX=000E DX=1193 SP=FFE6 BP=FFF4 SI=00D8 DI=054B
DS=13DB ES=13DB SS=13DB CS=129F IP=01FF NV UP EI PL ZR NA PE NC
129F:01FF 8BEC MOV BP,SP
由于上一条指令是CALL O1FE,所以也就有一条POP 01FD,然后又是一个PUSH BP
-d ss:ffe0
13DB:FFE0 FF 01 9F 12 F3 0B F4 FF-FD 01 1D 01 01 00 F2 FF ................
13DB:FFF0 54 05 F6 FF 00 00 43 35-2E 45 58 45 00 00 FB 00 T.....C5.EXE....
现在就来看看栈的情况
mov bp,sp后BP就成了FFE6
低
FFE0 | ->SUB SP,0006(空了六个字节为源目的串在堆栈中分配了空间)
FFE1 |
FFE2 |
FFE3 |
FFE4 |
FFE5 |
FFE6 |F4 ---->当前的栈顶FFE6
FFE7 |FF ---->原BP
FFE8 |FD
FFE9 |01
FFEA |
高
然后把si,di,ss压入堆栈,这时SP就变成了FFDA
再执行lea ax,[bp-06]
push ax
push ds
这是把分配的内存空间的内存地址也放到堆栈中,还有DS
然后又执行
mov ax,0194(mov ax,offset DGROUP:d@) 得到字串在数据段中的偏移
push ax
mov cx,03
好了该执行我们的SCOPY@了
CALL 0000:1395 由于是一个远跳所以CS IP都压堆栈了
再来看看堆栈的情况
内存低地址
FFD0 |18 ---->ip,也就是lea si,[bp-06]所在的CS段中的偏移
FFD1 |02
FFD2 |9F ---->先压CS
FFD3 |12
FFD4 |94 ---->字串在数据段中的偏移压栈
FFD5 |01
FFD6 |DB ---->DS
FFD7 |13
FFD8 |EO ---->为字串分配的空间的地址
FFD9 |FF
FFDA |DB ---->SS
FFDB |13
FFDC |DI ---->这儿把DI,SI压入堆栈的目的是因为过会儿把数
FFDD | 据段中的数据般到堆栈时又要用到它们所以要先保存
FFDE |SI
FFDF |
FFE0 |1 ->SUB SP,0006(空了六个字节为源,目的串在堆栈中分配了空间)
FFE1 | 2
FFE2 | 3
FFE3 | 4
FFE4 | 5
FFE5 | 6
FFE6 |F4 ---->当前的栈顶FFE6
FFE7 |FF 原BP为FFF4,现在BP为FFE6
FFE8 |FD ---->j执行完后返回地址
FFE9 |01
FFEA |
内存高地址
好了到这儿我们的分析算是完成1/3了,现在就来看看到底SCOPY是怎么把数据段中的字串
放到堆栈中去的.
push bp 把以前的BP(FFE6)压栈
mov bp,sp 当前sp=bp=FFCE
push si
push di
push ds 这时sp为FFC8
然后执行
lds si,[bp+06] si就等于ffce+06=ffd4,ffd4中的数据就是字串在数据段中的偏移0194
les di,[bp+0a] di就等于ffc3+0a=ffd8,ffd8中的数据就是堆栈中存放字串的首地址ffe0
这两条指令执行完后si=0194,di=ffe0
内存低地址
下面是栈顶情况:
FFC8 |DB -->ds压栈 <--sp=ffc8
FFC9 |13
FFCA |DI
FFCB |
FFCC |SI
FFCD |
FFCE |E6
FFCF |FF
内存高地址
下面的7行代码就简单了把SI指向的地址中的数据移到DI指的地址中去
cld
shr cx,01 (CX等于3)
repz
movsw
adc cx,cx
repz
movsb
这样是较率比较高的移法了先一次移两个用MOVSW指令,当只一个时用MOVSB
上面的指令执行完后,堆栈中的
FFE0 就分别成了 a
FFE1 b
FFE2 0
好了数据般完了,该还原DS,DI,SI,BP了
pop ds
pop di
pop si
pop bp
这四条指令执行完后sp=FFD0,bp还原成了以前的FFE6
最后是返回指令
retf 8
对这个指令要好好就一下:由于是远跳来执行的所以sp要加4(ffd0+4=ffd4)
再加上代参数8所以还要加8(ffd4+8=ffdc)
这时堆栈的情况就成了:
SP=FFDC,BP=FFE6
FFDC |DI ---->这儿把DI,SI压入堆栈的目的是因为过会儿把数
FFDD | 据段中的数据般到堆栈时又要用到它们所以要先保存
FFDE |SI
FFDF |
FFE0 |1 a ->SUB SP,0006(空了六个字节为源,目的串在堆栈中分配了空间)
FFE1 | 2 b
FFE2 | 3 0(注意源字串已经正确的放在了堆栈中!)
FFE3 | 4
FFE4 | 5
FFE5 | 6
FFE6 |F4 ---->当前的栈顶FFE6
FFE7 |FF 原BP为FFF4,现在BP为FFE6
FFE8 |FD ---->j执行完后返回地址
FFE9 |01
好了该总结一下了:
从上面我们就可以看出上面这些都是没有毛病的.
为什么要分配六个字节的空间?
先来看看我在C程序中是怎么定义的:
char a[]={'a','b','\0'};
char b[1];
其实C中分配空间的规则是很简单的,就是每一个串的长度一定要是双数
如果为单就加1
象上面的: 源为3+1=4
目的1+1=2,源+目的=4+2=6
个人认为这个地方对于要正确的溢出是很重要的,因为有的**里面说
多一个字节可以了但真的是这样吗?不一定吧,象我例子中就是这样的!
不多说了看代码吧:
lea si,[bp-06] 这时BP=FFE6,FFE6-06=FFE0
lea di,[bp-02] 同样DI就等于FFE4
设好了SI,DI后,就是一个循环了一次一个字节的把源串中的字母放到目的串中
下面的代码是最重要的了,问题也就出在这儿:!!!!!! |