黑客攻防:緩沖區(qū)溢出攻擊與堆棧保護(hù)
大家好,我是小風(fēng)哥。在上一篇文章《??進(jìn)程切換的本質(zhì)是什么???》中舉了一個(gè)示例,也就是這段代碼:
#include <stdio.h>
#include <stdlib.h>
void funcC() {
printf("jump to funcC !!!\n") ;
exit(-1) ;
}
void funcB() {
long *p = NULL ;
p = (long*)&p ;
*(p+2) = (long)funcC ;
}
void funcA() {
funcB();
}
int main() {
funcA() ;
return 0 ;
}
有同學(xué)在微信群里問不能在自己的機(jī)器上復(fù)現(xiàn),并給出了編譯后的機(jī)器指令:
00000000004005ee <funcB>:
4005ee: 55 push %rbp
4005ef: 48 89 e5 mov %rsp,%rbp
4005f2: 48 83 ec 10 sub $0x10,%rsp
4005f6: 64 48 8b 04 25 28 00 mov %fs:0x28,%rax
4005fd: 00 00
4005ff: 48 89 45 f8 mov %rax,-0x8(%rbp)
400603: 31 c0 xor %eax,%eax
400605: 48 c7 45 f0 00 00 00 movq $0x0,-0x10(%rbp)
40060c: 00
40060d: 48 8d 45 f0 lea -0x10(%rbp),%rax
400611: 48 89 45 f0 mov %rax,-0x10(%rbp)
400615: 48 8b 45 f0 mov -0x10(%rbp),%rax
400619: 48 83 c0 10 add $0x10,%rax
40061d: ba d6 05 40 00 mov $0x4005d6,%edx
400622: 48 89 10 mov %rdx,(%rax)
400625: 90 nop
400626: 48 8b 45 f8 mov -0x8(%rbp),%rax
40062a: 64 48 33 04 25 28 00 xor %fs:0x28,%rax
400631: 00 00
400633: 74 05 je 40063a <funcB+0x4c>
400635: e8 66 fe ff ff callq 4004a0 <__stack_chk_fail@plt>
40063a: c9 leaveq
40063b: c3 retq
仔細(xì)看這段代碼,有這樣一段可疑的指令:
mov %fs:0x28,%rax
mov %rax,-0x8(%rbp)
這兩行指令將fs:[0x28] (段尋址的方式)處的值push到了調(diào)用棧上(%rbp偏移8字節(jié)),并在函數(shù)即將返回的時(shí)候又檢查了一遍該值有沒有被修改:
mov -0x8(%rbp),%rax
xor %fs:0x28,%rax
接下來如果保存到棧上的值不等于fs:[0x28]處的值(xor指令進(jìn)行比較)那么跳轉(zhuǎn)到__stack_chk_fail函數(shù),我們的疑問是為什么要有這么一遍檢查呢?本質(zhì)上我們?cè)陂_頭給出的代碼相對(duì)于緩沖區(qū)溢出攻擊,做法是修改上一個(gè)棧幀的返回地址,將其修改為某個(gè)特定地址(黑客希望跳轉(zhuǎn)到的地方);
在開頭的這段代碼中本來funcA函數(shù)調(diào)用完funcB后需要返回funcA,但在我們的“精心”設(shè)計(jì)下調(diào)用完funcB后卻跳轉(zhuǎn)到了funcC,那么我們有沒有辦法防范這種攻擊呢?
答案是肯定的,這種方法要追溯到很久很久以前。在上世紀(jì)初,煤礦開采是一項(xiàng)很危險(xiǎn)的工作,因?yàn)槊旱V中的有毒氣體通常極難被人類察覺,這給礦工的生命帶來很大的威脅,而金絲雀對(duì)毒氣非常敏感,這樣礦工可以利用金絲雀來監(jiān)控礦區(qū),從而提早發(fā)現(xiàn)險(xiǎn)情。
這里也是一樣的道理,我們可以在棧區(qū)中放置一個(gè)“金絲雀”(fs:[0x28]處的值):
當(dāng)函數(shù)返回時(shí)我們會(huì)再次拿fs:[0x28]處的值與棧上的“金絲雀”進(jìn)行對(duì)比,一旦發(fā)現(xiàn)這兩個(gè)值不同我們就可以認(rèn)為當(dāng)前的棧已經(jīng)被破壞了,由于棧上的數(shù)據(jù)已然不可信,因此我們必須及早撤離礦區(qū),也就是調(diào)用__stack_chk_fail函數(shù)提前終止進(jìn)程。
而金絲雀也就是fs:[0x28]是隨機(jī)產(chǎn)生的(每次程序運(yùn)行時(shí)都不一樣),因此攻擊者很難提前知道該值是多少。
當(dāng)然我們也可以看到,添加堆棧保護(hù)功能需要增加額外的機(jī)器指令,這些也會(huì)稍稍對(duì)性能產(chǎn)生影響,代價(jià)就是需要額外多執(zhí)行一部分機(jī)器指令。
這就是編譯器的堆棧保護(hù)功能,當(dāng)然這個(gè)功能也是可以去掉的,編譯時(shí)添加-fno-stack-protector編譯選項(xiàng),這樣即可關(guān)閉堆棧保護(hù)功能,生成的代碼就可以復(fù)現(xiàn)上一篇文章《??進(jìn)程切換的本質(zhì)是什么???》中提到的效果了。
怎么樣,想成為黑客還是沒那么容易吧,就好比只有真正理解法律才能鉆空子一樣,只有真正理解計(jì)算機(jī)的工作原理才能hack它,當(dāng)然,想成為頂尖黑客只有對(duì)計(jì)算機(jī)的理解還不夠,你還需要有想象力。