分享
 
 
 

为什么C语言的strcpy函数有漏洞?

王朝c/c++·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

为什么C语言的strcpy函数有漏洞?

来源:ChinaITLab 收集整理

前言:研究了几天DOS下的溢出原理,最后明白了其实原理都很简单关键是要懂得为什么C语言的strcpy函数有漏洞,为什么对这个函数的不正常使用会造成溢出。

一节:介绍strcpy函数能看到这篇文章的人可能都知道问题很多是出在它的身上吧呵呵。

先看一看在标准的C语言的string.h中对这个函数的申明char *_Cdecl stpcpy (char *dest, const char *src);对于代码看下面的:(这是微软对这个函数的说明)

(%VC%/vc7/crt/src/intel/strcat.asm)

;***

;char *strcpy(dst, src) - copy one string over another

;Purpose:

; Copies the string src into the spot specified by

; dest; assumes enough room.

;

; Algorithm:

; char * strcpy (char * dst, char * src)

; {

; char * cp = dst;

; while( *cp++ = *src++ ); /* Copy src over dst */

; return( dst );

; }

;Entry:

; char * dst - string over which "src" is to be copied

; const char * src - string to be copied over "dst"

;

;Exit:

; The address of "dst" in EAX

;

;Uses:

; EAX, ECX

;

;Exceptions:

;**********************************************************************

本来想去掉一些注解,不过觉得还是留着好哈:)

从上面我们可以看到这样的代码有问题有:

1.没有检查输入的两个指针是否有效。

2.没有检查两个字符串是否以NULL结尾。

3.没有检查目标指针的空间是否大于等于原字符串的空间。

好了现在我们知道了对于调用string.h中的这个函数,和我们自已写一个如下的程序没有本质上的区别那么我们就来研究它就可以了.就叫它c4.exe吧.

main(){j();}

j()

{

char a[]={a,b,};

char b[1];

char *c=a;

char *d=b;

while(*d++=*c++);

printf("%sn",b);

}

二节:调试我们的c4.exe

所用工具W32dasm,debug,tcc,tc

第一步我们用TC2编绎生成可执行文件c4.exe.

第二步用TCC -B生成这段C代码的汇编源代码.

第三步用W32dasm和debug对c4.exe进行静态和动态调试

先分析由TCC生成的c4.asm代码如下:

先说明一下由于这是一个完整的包括了MAIN函数的C程序,程序刚开始时数据段和堆栈段还有代码都不在一起但是当,执行到我们的J函数时堆栈和数段就在一起了这要特别注意.

ifndef ??version

?debug macro

endm

endif

?debug S "c4.c"

_TEXT segment byte public CODE

DGROUP group _DATA,_BSS

assume cs:_TEXT,ds:DGROUP,ss:DGROUP

_TEXT ends

_DATA segment word public DATA

d@ label byte

d@w label word

_DATA ends

_BSS segment word public BSS

b@ label byte

b@w label word

?debug C E930A68D2E0463342E63

_BSS ends

_TEXT segment byte public CODE

; ?debug L 1

_main proc near

; ?debug L 3

call near ptr _j //这儿执行我们的J函数

@1:

; ?debug L 4

ret

_main endp

_TEXT ends

_DATA segment word public DATA //最先在数据段中定义我们的源串ab结尾符

db 97

db 98

db 0

_DATA ends

_TEXT segment byte public CODE

; ?debug L 6

_j proc near

push bp //J函数入口

mov bp,sp

sub sp,6

push si

push di

push ss

lea ax,word ptr [bp-6]

push ax

push ds

mov ax,offset DGROUP:d@ //特别注意这是得到源串在数据段中的偏移

push ax //所有SCOPY@以上的代码的作用是在堆栈中分配源串加目的串那么多个空间

mov cx,3 //cx=3指定要拷贝的字符数

call far ptr SCOPY@ //执行了另一个函数作用是把数据段中的源串拷到栈中

; ?debug L 10

lea si,word ptr [bp-6]

; ?debug L 11

lea di,word ptr [bp-2]

; ?debug L 12

jmp short @3

@5:

@3:

; ?debug L 12

mov bx,si

inc si

mov al,byte ptr [bx]

mov bx,di

inc di

mov byte ptr [bx],al

or al,al

jne @5

@4:

; ?debug L 13

lea ax,word ptr [bp-2]

push ax

mov ax,offset DGROUP:s@ //得到printf函数的打印格式参数

push ax

call near ptr _printf

pop cx

pop cx

@2:

; ?debug L 14

pop di

pop si

mov sp,bp

pop bp

ret

_j endp

_TEXT ends

?debug C E9

_DATA segment word public DATA

s@ label byte

db 37 //%

db 115 //s

db 10 //换行符:)

db 0

_DATA ends

extrn SCOPY@:far

_TEXT segment byte public CODE

extrn _printf:near

_TEXT ends

public _main

public _j

end

三节:分析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,};

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后,就是一个循环了一次一个字节的把源串中的字母放到目的串中

下面的代码是最重要的了,问题也就出在这儿:!!!!!!

jmp 0220

0220 mov bx,si (把SI的地址给BX)

inc si (SI地址加1)

mov al,[bx] (把BX寄存器中记录的内存地址中的数据给al,第一次就是取出a)

mov bx,di (把dI的地址给BX)

inc di (dI地址加1)

mov [bx],al (把AL中的字符给BX指向的地址)

or al,al

jne 0220 (不为0则跳)

------------又来看看栈的情况--------BP=FFE6,SP=SP=FFDC

FFDC |DI

FFDD |

FFDE |SI

FFDF |

FFE0 |a

FFE1 |b

FFE2 |0

FFE3 |

FFE4 |a (第一次执行DI=FFE4,FFE4中的值成了a)

FFE5 |

从上面的代码我想你已经看出问题来了,是否拷完代码的判断条件只是看有没有遇到

而并没有去比较源串的大小是否比目的串大!

FFE0 |a

FFE1 |b

FFE2 |0

FFE3 |

FFE4 |a (第一次执行DI=FFE4,FFE4中的值成了a)

FFE5 |b

FFE6 |F4 ---->原始BP (注意在我的例子中FFE6将变成0,

FFE7 |FF 但这个只是保存的上个函数的BP所以程序还没出错)

FFE8 |FD ---->main函返回地址

FFE9 |01

再看后来的代码:

lea ax, [bp-02]

mov ax, 0197

push ax

call 0AF5 //执行打印输出

pop cx

pop cx //上面的几行就是打印出目的串

pop di

pop si //把DI,SI弹出

mov sp, bp

pop bp

ret

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有