原文地址:
http://blog.csdn.net/linyt/article/details/48738757
背景
前面介紹了ret2libc和ret2plt,這兩個攻擊技術的相通點是函數參數是通過壓棧來傳遞,這也是i386架構的調用約定。然而隨着64位服務器的普及,以及後來越來越廣泛,以致幾乎所有服務器都升級到64位的硬件上。
x86_64天生具有免疫力
根據X86_64 ABI的調用約定,函數間傳遞參數不再以壓棧的方式,而是以寄存器方式傳遞參數,前面6個參數依次以rdi, rsi, rdx, rcx, r8和r9寄存來傳遞。
在Linux系統,64位架構只使用48位的虛擬地址空間,也即每個地址高16位全部爲0,因此在64位系統上,地址已天然零化。
前面介紹的攻擊方法似乎一夜之間沒有用武之地,很快安全人員在ret2plt攻擊方法基礎上,做了一個升級片本的攻擊方法,稱爲ROP(Return-oriented programming)方法。
何謂ROP
顧名思義ROP,就是面向返回語句的編程方法,它借用libc代碼段裏面的多個retq前的一段指令拼湊成一段有效的邏輯,從而達到攻擊的目標。
爲什麼是retq,因爲retq指令返到哪裏執行,由棧上的內容決定,而這是攻擊者很容易控制的地址。
那參數如何控制,就是利用retq執行前的pop reg指令,將棧上的內容彈到指令的寄存器上,來達到預期
一段retq指令未必能完全到想攻擊目標的前提條件,那可在棧上控制retq指令跳到另一段retq指令表,如果它還達不到目標,再跳到另一段retq,直到攻擊目標實現。
在ret2plt攻擊方法,我們使用PPR(pop, pop, ret)指令序列,實現順序執行多個strcpy函數調用,其實這就是一種最簡單的ROP用法。ROP更是ret2plt的升級版
ROP方法技巧性很強,那它能完全勝任所有攻擊嗎?返回語句前的指令是否會因爲功能單一,而無法實施預期的攻擊目標呢?業界大牛已經過充分研究並證明ROP方法是圖靈完備的,換句話說, ROP可以借用libc的指令實現任何邏輯功能。
攻擊實例
在這裏省去對準EIP以及漏洞代碼分析,直奔主題,如何構造ROP指令順列來實現攻擊邏輯。
簡單起來,攻擊目標爲實現system(“echo success”) 這個函數調用。
首先,調用system的參數爲”echo success”字符串的地址,而字符串是棧注入的內容,那它的地址應該是rsp + offset。而函數調用時,第一個參數是放到rdi寄存裏面。 所以需要從libc裏面,在retq或者call *reg指令前找到rdi = rsp + offset邏輯等價的指令序順,發現有如下的兩條指令:
0x7ffff7a610a3 lea 0x120(%rsp),%rdi
0x7ffff7a610ab call %rax
- 1
- 2
- 1
- 2
這樣,可以將”echo success”字符串安排在rsp + 0x120的位置。但往下一條指令,要指令call %rax,因此需要在指令這個指令段前控制rax的值必須爲system函數的地址。
然後,想將system地址放到rax相當容易,只需要在retq指令令,找到pop rax指令即可,從libc裏面查找,發現如下:
0x7ffff7a3b076: pop %rax
0x7ffff7a3b077: pop %rbx
0x7ffff7a3b078: pop %rbp
0x7ffff7a3b0f9: retq
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
於是,構建如下的執行順序:
pop %rax <— 這裏彈出system函數地址
pop %rbx
pop %rbp
retq <— 這裏從棧中跳到下段指令
lea 0x120(esp), %rdi <– 需要安排好”echo success”位置,使得此時的rsp + 0x120剛好是字符串地址
call *%rax <– 調用system,參數剛好。
通過gdb查看system函數的地址:
(gdb) p system
$4 = {<text variable, no debug info>} 0x7ffff7a61310 <system>
- 1
- 2
- 1
- 2
於是棧注入內容就很容易:
0x7fffff7a3b076 + address of system + dummy1 + dummy2 + 0x7ffff7a610a3 +dummy(0x120) + “echo success”
攻擊圖示
上面的攻擊實例中,指令的執行過程和棧注入內存佈局如下圖所示。
ROP可以爲所欲爲
上面提到已有研究員稱ROP攻擊借用的多個代碼片段串起來的程序邏加是圖靈完備的,也即這個程序包含順序執行語句(這個當然是廢話),還有分支語句,甚至有循環語句。
稍有反編譯或者逆向工程經驗,或者對C語言生成的彙編結構熟悉都知道,retq指令是函數的返回指令,在此之前的指令是彈棧指令(如pop rax, pop rbx等),怎麼可以出現分支指僅(bne等),甚至循環指令呢?
是的,這個是事實,但不是事實的全部。如果將glibc進行逆向工程,會發現retq指令前向全是清一色的pop指令,但是事實上攻擊者總是不按常規出牌。
X86指令集是CISR指令集,密集度很高,一條指令中的一部分,也可能是一條新指令。
攻擊者就是利用這一點,不按常規出牌。retq指令就只有一個字節,是C3。通過編寫工具,對glibc進行掃描,把C3的指令內容找到,然後向前解碼各種可能的指令,形成一個指令表。這些指令表會是非常豐富,有運算指令,轉跳指令,以及訪存指令。利用它們可以形成圖靈完備的計算邏輯。
關於ROP 的攻擊的具體實現:
具體實施ROP 攻擊的時候,由於找到所有合適的 gadgets 非常困難,花費的時間也很多。所有某些情況下,會 找到gadgets 去remove 掉 DEP , 使堆棧有執行權限,進而方便實施 buffer overflow 攻擊。 這個方法會簡化攻擊難度。