Shellcode學習之編寫變形的shellcode[原理篇]

X'Con04據說轟轟烈烈的結束了,馬上安焦就毫無保留地把所有的演講用幻燈都放了出來。作爲菜鳥,我也好事地下載了一份來看,首先看到的是Plan9 大蝦的《高級shellcode設計技巧》。這篇文章似乎總結了一下當前Shellcode的各種技巧和設計思路,比較有意思的是當中提到的 ADMMutate,雖然在2001年就有見過這個工具,但是似乎一直都在*nix下而且這麼多年來沒見過升級。閒下來沒有事情的時候,我就想做上這麼一個東西來打發時間,面向的平臺是Win32——至少有一點要與ADMMutate不一樣吧?
切入正題之前先科普並八卦一下。擺弄shellcode似乎是蠻好玩的事情,Phrack從老早老早就幾乎每一期都有相關的文章,從全字母數字的,到 390的,然後到符合Unicode或UTF8的shellcode都有。這些文章大多都給出的是一些路子,甚至是一些可用的指令集而已,像我們這種菜鳥看了不免一頭霧水。不過深入一點就可以發現,需要解決的問題就是如何利用有限的指令,來“修補”出所有可能的指令來——正如這個系列前面所說的,關鍵的是解碼的部分,因爲我們可以想出各種各樣的算法來對起作用的shellcode來編碼,但是解碼的工作卻會有各種各樣的限制(特殊字符啊之類的),這些文章也就是解決了解碼部分的問題。從這個意義上來說,這些技術不單單可以應用在shellcode上面,它很可能最早來自病毒的技術,而且可以同樣的適用於對文件進行加密等應用上。
ADMMutate是將shellcode變形的工具。變形的概念似乎最早出自於病毒方面(具體何時我沒法考證,因爲估計變形病毒出現的時候我還在流着鼻涕擺弄Apple呢),大概的意思就是說生成出來的shellcode總是有不同的形式。這看起來似乎有點不可思議,但是確實是可行的,舉個簡單的例子來說,比如我們要問一個在北京的四川人昨天晚上幹什麼去了,北京人可以說“你丫挺的昨晚上那晃悠去了?”,四川人可以說“龜兒子昨晚黑抓子去老?”,都是可以讓對方瞭解的——這就是同樣意思的不同表達方式;或者北京人可以說“昨晚你丫挺的上那晃悠去了?”,四川人說“昨晚黑龜兒子抓子去老?”,對方也可以理解——這是在前面的基礎上,調換了一下表達的順序,這種調換不影響意思的表達;再或者北京人可以說“你丫挺的昨晚好事不做上那晃悠去了?”,四川人說“昨晚黑你個龜兒子的不落教抓子去老哦?”,添加了很多沒有用的東西,這樣還是可以清晰地表達。這裏說的,對於同樣的意思,不同方言的表達,不同語句順序的表達,插入無關緊要的字句,就是變形的基本方式。換個角度,我們可以認爲是表達的意思不變的情況下,對其中的因素進行變換次序或者插入無關因素,就是一種變形。
那,我們就一個一個的來看基本的變形方法。先是基本的概念,然後纔是實戰嘛。

“你丫挺的” VS “龜兒子” —— 選擇不同的寄存器

同樣的意思,可以有多種不同的表達方式,最簡單的一種就是用不同的方言來替換。同樣的,對於計算機能夠理解的語言,要實現同樣方式的不同表達,最簡單的就是選用不同的寄存器。
早期的計算機上,寄存器的使用似乎不像現在這樣隨便,這從寄存器的名字就可以看出來,eax是累加(accumulate)寄存器,ecx是計數(counter)寄存器,不過後來這種限制就不多了,對內存的操作幾乎所有的寄存器都可以。在這樣的情況下,幾乎所有的寄存器都可以被我們選用來做爲操作所需的那個寄存器,簡單的舉個例子,我們要通過寄存器放一個數入棧,可以:

mov eax, xxxxxxxx
push eax

也可以:

mov ecx, xxxxxxxx
push ecx

兩個是等價的。
那麼,就出來可能的第一個問題,這樣子變換最終生成的機器碼長度是否是一樣的呢?爲了編寫變形shellcode的方便,我們當然希望簡單的對寄存器的選擇變換,最後生成的機器碼長度一樣,因爲這樣我們比較好定位,然後通過直接對機器碼的變換來實現每一次不同的變形。事實上很多指令長度是固定的,比如 push一個寄存器,不管你push的是什麼(正常的寄存器,FS之類的不算),都是一個字節——甚至,我們可以一個小公式來算出來:

pushcode = '"x50' + register;

其中,每一個寄存器的對應值是:

EAX = 0, ECX = 1, EDX = 2, EBX = 3, ESP = 4, EBP = 5, ESI = 6, EDI = 7

直接帶入register就可以算出來。即倘若是eax,那麼對應該是'"x50' + 0,push eax的指令就是0x50。
這樣的公式有什麼用呢?如果我們寫了一段彙編的代碼,裏面某些指令全部用的eax操作的,而且這些指令的位置我們清楚地知道,我們就能通過這樣一類公式來直接計算,同樣的操作替換成其他寄存器(比如ecx)的時候相對應的機器碼應該是什麼,然後用新計算出來的替換掉老的,這不就是一個變形了麼?
這一步可以完全由計算機來做,而不用人手工替換,需要的代價是我們寫出一個類似於彙編編譯器的部分,這是可以實現的。上面的公式也並不是拼湊出來的,而是彙編在編譯的時候遵從的一些約定,限於篇幅,這一類的公式就不一一給出了,如果有興趣的話,可以查看彙編手冊,自己稍微花點時間來列舉一下。
感性化一點的解釋可以是這樣,我們用四川話、北京(普通)話和英語來表示不同的寄存器操作,假設有一個完成某項任務的指令表示爲:

小樣 tonight 在哪兒 eat 白食? [普通話 + 英語]

然後按照公式(如果有的話,嘿嘿)計算出普通話對應的四川話和英語對應的普通話,然後替換掉,就成了:

龜兒 今晚 到哪點 吃 趴活?

替換後還是一個意思,都是問對方晚上那裏蹭飯去了。但是,沒有兩個字重複是不是,這就是替換寄存器的變形。
上面說的最後替換出來的長度都一樣,如果變換寄存器後指令長度不一樣(比如xor xxx, xxx),那就很麻煩,我們留在後面的“插入無關語句”部分去說。
還有需要提醒的是,並不是隨便什麼寄存器都可以拿來隨便用,如果你有loop一類的指令,那就要避免改變ecx的值,如果在棧上有操作,就不要把寄存器替換成esp或者ebp。類似需要注意到的地方還有很多,在你寫的時候,注意一下就可以,不是什麼很重要的問題,所以就不詳細的寫了。

“昨晚你丫” VS “你丫昨晚” ——調整塊的順序

我們說話的時候,如果前後沒有什麼很大的聯繫的話,語序的調換也不會影響表達。像兩個人見面的時候要問“在哪裏發財啊?老婆孩子好啊?火車轉彎燈要不要?”,這三句話前後沒有必然的邏輯關係,所以按句子進行整體的調換也沒有什麼問題,先問對方要不要火車轉彎等也行。
同樣的,如果shellcode中也像這樣有着明顯的功能模塊,調換其前後順序不會產生影響,這是最大粒度上的通過調換順序的變形。
粒度更小的,可以是調換若干單一指令之間的順序,當然前提條件是不能改變表達的意思,例如有一句話是“一邊打電話一邊打架”,換成“一邊打架一邊打電話”也無妨,前提條件就是互相不影響,結果不會給別人造成困擾就行。
還有一種中間粒度的調換,這個例子不好舉,只好用比較形式化的方式來描述一下:比如有兩個操作序列A和B,分別完成兩個功能且互相不干擾(包括寄存器), A的序列是A1/A2/A3/A4……,B的序列是B1/B2/B3/B4……,然後我們像洗牌一樣把這兩個序列洗一下,得出來的結果還是沒有問題的,像序列A1/A2/B1/A3/B2/B3/B4/A4或者是序列A1/B1/B2/B3/B4/A2/A3/A4都沒有關係,只要保證抽取出來的所有關於 A的序列和所有關於B的序列都正常即可。
一個不太恰當的例子是用兩種語言來說兩件事情,普通話對應上面的A,英語對應上面的B。普通話是“火車的 轉彎燈 要不要?”,英語是“I want home”,那麼這樣說是可以的:

火車的 I 轉彎燈 要不要 want home

這樣也可以:

I want 火車的 轉彎燈 home 要不要

因爲他們分別表達了不同的意思,而且互相之間不干擾理解。但是如果相互之間有干擾的話,這種方法就不在適用,比如“火車的 轉彎燈 要不要”和“你裝不裝 寬帶網”,打亂順序組合一下就有可能是“火車的 你 裝不裝 轉彎燈 寬帶網 要不要”,似乎成了問別人裝不裝轉彎燈,意思完全變了。
總之,調換塊的順序就是在不影響表達的情況下調換可能的順序。通常上面三種粒度上的調換是綜合使用的,第一個最簡單,實現其來也最方便,最後一個比較麻煩,而且適用的範圍似乎不是很廣。

“你” vs “你!@#$#!@#$”——插入無關的字句

插入無關的字句作爲變形是最容易理解的,這種方法的基本出發點就是隻要不是喧賓奪主的廢話,基本上都可以被省略掉。從這個意義上來說,我寫的這篇文章也在大量的進行變形,尤其以插入哈哈、嘻嘻之類沒有意義的助詞或者是擬聲詞爲最,可是在你讀起來,最多嘴角向上一翹,並沒有影響你瞭解文章的內容,這就是成功的變形。
插入的字句一般被稱之爲nop-like,從字面上理解就是插入一些類似於NOP的指令。這種指令包括很多,最常見的就是0x90即NOP,這個指令還有另外一個解釋(也許是原本的解釋),那就是xchg eax, eax——這就是一個純粹的沒用的指令,效果類似於文章中的“呵呵”。同樣的,xchg exx, exx基本上都是,不過除了交換兩個eax,交換其他兩個相同的寄存器最後生成出來指令長度都是兩個字節,所以大家不愛用。
除了這種純粹沒用的指令以外,還可以選擇一些對偶指令,讓其執行後沒有任何影響。比如說,inc edx + dec edx一類的,開始對edx進行增一操作,然後馬上又進行edx的減一操作,最後對任何東西幾乎都沒有影響(對標誌位還是可能有影響),這有點類似於一個人說話,開始說了一句“我天下無敵了”然後接上一句“那是不可能的”,別人聽了以後覺得沒有什麼問題,你的正常表述幾乎不受任何影響。
還有一類的無關語句是在其他寄存器上的操作。比如我整個要變形的部分只用到了eax和ecx,那麼中間無論什麼地方插入對ebx的操作,都沒有問題。這個有點像說話的時候,你一直用普通話來表達你所要說的,這時候突然你走火說了兩句英文,好像也沒有多大的關係,至少別人理解你要表達的東西上沒有受到影響。
插入無關的字句理論上最簡單,但在實踐過程中可能存在一個巨大的問題,那就是一些相對跳轉或者是相對調用的指令會受到影響。舉個最簡單的例子而言:

a: xor dword ptr [eax], ebx
loop a

a: xor dword ptr [eax], ebx
nop
loop a

兩個完全一樣功能的代碼,後面一個是前面一個的變形。但是單單的插入一個nop到前面一個去肯定會出錯,因爲前面一個和後面一個loop a兩個編譯出來指令碼相差1,這是不得不考慮的!前面說所的,替換寄存器後指令長度是否一樣也是一個重要問題,就指的這些方面——我們不得不精心的去調整我們需要變形的部分,而這種調整最主要的應該集中到相對跳轉類指令上去,避免它們出問題。

其他,相同語義的不同表達

寫完一本書,可以叫殺青,也可以直接說寫完了準備付梓,這是同一種語義的不同表達方式。一般意義上而言,所有綁定cmd的shellcode都可以看作是同樣語義(目的)的不同實現,當然可以算作一種變形。只不過這種變形似乎代價稍微大了一點,而且不容易自動化生成。
小範圍而言,這種相同語義的不同表達是可以的,前面說的替換寄存器就是簡單的實現。另外的,把jmp替換成jno和jo或者是諸如此類的東西,也是一個不錯的變形方案,這種變形可能需要一個很大的庫來支持,而且相同語義的選擇需要人爲的預先定義。一旦有了一個龐大的庫來支持,剩下的事情就是選擇其中的某些內容來進行組合,工作量就小多了,而且效果是很好的。因爲實現起來比較困難,所以這個使用不是太普遍,簡單提一下就好。

基礎性的東西介紹完畢以後,下面就可以開始實戰操練了

上次花了很多筆墨來說明一個變形的shellcode到底是什麼原理,這一次的實戰篇我們就來做一個簡單的變形shellcode。由於篇幅的限制,這個變形的東西還是比較幼稚,但是它已經能夠逃脫幾乎所有按照shellcode特徵來殺 “毒”的各類軟件,在shellcode不限長度的時候,你甚至可以反覆的使用這個加密的頭部來進行變形。具體的內容,我們在下面慢慢說。
切入正題之前還是按照老傳統先科普一下。shellcode後面一部分是實現真正功能的部分,這一塊我們可以用某種加密的方式來進行編碼,而前面的 decode部分則是解碼的。實現功能部分的編碼本身就帶有變形的意義,比如我用0x99(最常見的)來異或,與我們用0x98或者0xee來異或,結果是不同的,這一點外在的表現就是變形。我們需要的更多是一種能夠變形的解碼部分,而且要求這部分可以相對獨立,這樣子就像一個套子一樣,套在一塊代碼上面就變了一次形,多套幾次也沒關係,最多變成其它的更加讓人不知爲何物的東西。
需要明白的是,我們寫的這個變形shellcode是一次性的(也叫做拋棄型的),因爲shellcode在一次exploit的時候只能用一次,不像病毒一樣還要辛辛苦苦的傳播。所以我們作的東西,只要能夠生成每次不一樣的shellcode就可以,shellcode不用包含自己讓自己變形的部分—— 現在這句話還有點拗口,看到後面自然就會明白的。
那麼,我們的變形之旅還是從一個最基本的decode部分開始:

jmp l
de: pop ebx
xor ecx, ecx
mov cl, 222h
lp: xor byte ptr [ebx], 0x99
inc ebx
loop lp
jmp stt
l: call de
stt:

看過兩期連載的朋友可能都要笑了,又是這個,都看煩了。沒關係,總是從最簡單的開始嘛。我們假設起作用的部分是xor過0x99的,就可以專注於解碼部分了。如果異或的數字不是0x99,那只需要改變第五行的那個操作數即可——這也是一個變形。這一塊解碼部分首先滿足了相對的獨立性,也就是說它不依賴於任何的環境,比如各種寄存器的值或者是棧上的數據,它只是負責將後面的數據按照異或0x99的方式解碼,這樣子,我們把它看作一個“帽子”,並用D來表示,將X()作爲異或0x99的編碼方法,對於任何一個可執行的shellcode(表示爲字符串S),下面就是我們最常看到的形式:

D + X(S)

將帽子再套一層,變成了:

D + X( D + X(S) )

這也是可以執行的,就是作了兩次解碼工作。不過我們平時是見不到這種形式的shellcode,因爲解多少次碼都可以變成解一次碼的方式,而黑客們總是喜歡最簡便的東西。話說回來,雖然不常見到,對於變形來說,這也是很不錯的方法,前提是你的shellcode沒有限制長度。
仔細看這個decode部分,除了跳轉以外,用到了兩個寄存器,ebx和ecx。其中ecx是作爲計數器使用,一旦確定了要用loop,那就不能改變,所以真正可以選擇的還是ebx。考慮到棧的完整性和指令的長度,一般不建議使用esp和ebp,所以好像只有eax/ebx/edx/esi/edi五個寄存器可以選擇使用,而隨機的選擇出一個寄存器後,就要把原始中所有的ebx全部替換成選出來的那個寄存器才行。
這裏的替換不是VB中replace這麼簡單。我們操作的是最後生成的機器碼,寄存器的變換導致字節的變換,不是簡單的replace。在原理篇裏面說過,總可以有某個公式來對應不同寄存器相同指令下的機器碼,在說明這個問題前,先得了解寄存器的順序問題。
寄存器本身沒有高低的級別,然而對應指令的時候,它們有一個潛在的順序。簡單的舉個例子,對
inc 而言,inc eax對應指令爲0x40,inc ecx對應指令爲0x41,inc edx對應指令爲0x42……一直到inc edi對應的指令爲0x47,排列的寄存器順序就是eax/ecx/edx/ebx/esp/ebp/esi/edi。倘若給出一個順序定義如下:

enum Register
{
EAX = 0, ECX = 1, EDX = 2, EBX = 3,
ESP = 4, EBP = 5, ESI = 6, EDI = 7
};

很容易得到inc exx的對應指令是0x40 + Register,同樣的dec、push、pop等都滿足這樣簡單的規律。
回過頭來看那個decode,要做的工作是選擇寄存器然後改變代碼,自然而然的就要去尋找其中的規律,inc的那個已經說了,剩下pop和xor byte ptr[exx], 0xXX就要去動手找一下。在VC中嵌入彙編然後查看代碼後可以清楚地看到,前面說到的五個寄存器,基本上滿足的是如下兩個公式:

pop exx:
0x58 + Register
xor bytr ptr[exx], 0xXX:
[80] [0x30 + Register] [XX]

XOR是一個三字節的指令,前面0x80固定,最後一個是操作數。在實驗的時候你也看到了,對於esp和ebp,這是一個四字節的指令,長度不一樣,也是我們要拋棄的一個原因。
準備工作已經就緒,就從一個程序開始,按照原理篇的幾個部分來做。第一個程序是test0.cpp,我已經寫好了,這個沒有什麼特別的地方,只是告訴你這是最基礎的部分,看看可以熟悉一下解碼部分的最基本寫法,而且在後面我們也可以把每一步生成的decode部分拿過來測試測試。作爲一個基礎,我把它命名成了0,也是C程序員的習慣吧~
程序test1.cpp就是變形的開始。我們先人爲的把decode部分分成了八份,基本上每個指令就是一份——本來這裏有九條指令的,但是xor ecx,ecx和mov cl, 222h其實就是mov ecx, 222h一個指令,不過是我們爲了避免0x00出現而耍的花招而已,大體上還是把他們看作一個指令爲好。這八個部分,真正與選擇寄存器有關的還是2、4和 5,第一步“寄存器的選擇”,焦點就集中在這三個部分上。
選擇寄存器不用說了,初始化一個隨機種子,然後就可以按照獲取的隨機數來選擇一個。根據這個寄存器,按照上面的公式,第二句的pop exx的機器碼就應該是0x58 + Register,第四句的XOR中,第二個字節應該是0x30 + Register。同樣,第五句的inc exx應該成了0x40 + Register。具體的實現在test1.cpp裏面對應了step1函數,函數雖然很短,但程序微長,建議大家還是打開看看。
除了選擇寄存器以外,還有一個函數是combine,這是將分散的頭部寫成一個統一的頭部。這個函數還有一個另外的功用,就是對代碼進行一些可能的調整,交換指令次序一部分也可以在這裏來實現。
不管你相不相信,就這麼簡單的一段程序(test1.cpp),已經是一個變形的頭部了。遺憾的是他的變形能力還非常有限,因爲歸根結底這段代碼裏面只用到了一個寄存器,我們選擇寄存器的組合方式只有區區的五個。如果有更多的寄存器在decode部分出現,同時我們要選出很多個寄存器備用的話,這樣子組合下來的結果就更多,變形的效果更好(相應的會更復雜,所以還是簡單的來做例子比較好)。
原理篇裏面說到的第二種方法是交換指令的順序,在這個地方也可以辦到,看着兩段:

pop ebx
xor ecx, ecx
mov cl, 222h

還是將後面兩句看成一個整體。彈出棧頂的值給寄存器或是賦值給ecx,這兩步沒有絕對的先後次序,也就是說誰在前面並不影響到最終的結果。因而我們可以隨意的調換兩者的位置(雖然是“隨意”,說到底也就兩種方式而已,如果很多條指令可以互換次序的話,情況就麻煩了),對應的實現在函數Step2()中,交換一下字符串的內容,這樣的話不影響到後面的一系列函數。
最麻煩的還是所謂的插入NOP-like指令。
最容易想到變形方法就是這個,然而卻是最難實現的。decode部分不可避免的要有一些相對跳轉和相對調用的指令,一旦其中的某一個指令長度發生了變化,幾乎要影響到所有相對跳轉的地方,因此,要加入NOP-like指令的時候,需要對每一個指令進行考慮。
對於我們上面寫的這個decode而言,我們已經人爲的將其分成了八個部分,之所以這樣做,有一個好處是我們可以在加入NOP-like指令的時候,僅僅是加入每一個部分中去,當作這個部分的一個整體,而不是安插在某兩個部分之間,難於理解不說,同時也難於處理。
以插入NOP 0x90爲例(其他的NOP-like指令我們已經在原理篇裏面討論過了,不是麼~)。如果我們插入到第一句jmp l後面,毫無疑問的,直接影響到的只有自身而已,最後面的一個call de也可以算一個,不過call的地方是一條有意義的指令或者是一個NOP關係不是很大,簡便起見,索性就不修改call de指令。這個修改反映在test3的Step3()函數中,在對第一句加入了NOP-like以後,jmp的操作數應該相應的加上增長的字節數,所以 head1[1]就視情況有所修改。
對head2的插入就更爲複雜。pop exx後面加入NOP-like以後,除了第一個jmp以外,後面的call也受到了影響,同樣的xor ecx,ecx和mov cl加入NOP-like以後也有同樣的影響,這兩個在前面說過是可以交換的,也容易證明交換後對前後指令影響一致,所以可以一同處理,即:加入Nop- like後前面的jmp要多跳一跳指令,後面call的目標也要向挪一個。
後面的修正,可以挨着挨着的做,都是同樣的方法。test3.cpp中還舉了一個inc exx後的插入,這裏就不具體的解釋了,道理和前面差不多,不過插入的不再是NOP,而是《原理篇》裏面提到過的指令對——inc eax和dec eax(0x4048)。Nop-like的指令多種多樣,在網上也有相關的討論,有興趣的話可以去看看cnhonker.com的相關文章。
還有一個misc()函數。這個函數是通過decode部分的本身性質來變形的,例如上面的循環次數,也就是要解碼多少個字節,這個數目可大可小,只要能夠保證所有編碼過的字符都能夠被解到即可。像此類的變形不太能說清楚屬於什麼方面,只能視情況而定,所以放到雜類中了。
到這個時候,差不多一個變形的decode部分已經完成。剩下一件小事情,就是將其作用的shellcode用一個數字來異或,然後將對應的數字填入 decode部分即可,代碼我已經有一個簡單的實現(見光盤中給出的test4.cpp),具體的細節不再做解釋,大家看代碼一下子就可以明白。
光盤裏的東西到此爲止,然而變形的路子並沒有就此結束,還有一些值得討論的,順帶在文章中簡單的提提。對這個過於簡單例子有所不滿的朋友,下面的話是可以進一步做到的,希望您能和我交流一下。
第一是關於起作用的部分,也就是所謂real shellcode部分的編碼方式。例子中給出的是很普通的單字節異或,據我的實驗來看,似乎四個字節一組或者是四個字節一組的異或效果比較好,不過限於篇幅的關係,沒有給出這樣的代碼。四個字節一組的主要思想就是平衡解碼部分的生成難度和變形能力,對於32位機而言,簡單處理情況下四字節(DWORD)剛好是一次性處理的極限;七字節主要考慮的是變形的能力,這種情況下顯然不能一次性異或七個字節,而可能要4-2-1或者2-2-2-1或者其他分次異或的方法,對應的指令集比較分散,只是解碼部分稍微麻煩了一點。當然,其他的編碼方式也可以,只不過寫起來可能還要複雜一些。
第二是解碼部分的編寫。這裏給出的編寫方法顯然太過於複雜,好的辦法是在你編寫的上面套一層像編譯器一樣的東西,這樣需要做的不過就是不斷地加指令,相對位置的調整還有機器碼的生成都可以讓程序自己完成。我寫了個簡單的,有興趣的話可以交換一下,省是省力些,不過不太好用就是。
第三是有關解碼頭部本身的。這個頭部,在前面說過了,可以反覆的加,反覆的用,沒有關係的,代碼實現起來也很方便。變形病毒的話,這個頭部是集成在了 real shellcode裏面,負責在傳播的時候生成新的解碼部分,這裏我們編寫變形的shellcode不需要這麼麻煩(拋棄型的),就單獨把頭部的生成提取出來做成了程序,這也就是前面說的“不用包含自己讓自己變形的部分”。
生成變形的shellcode不是一件很難的事情,只要你能寫出一種編碼的方法,然後寫出解碼的頭部就可以了,然而麻煩的是如何在長度(複雜度)與變形的能力之間尋求到一個平衡點。寫病毒的話,考慮的可能不是這麼多,因爲只要能找到足夠的空間可以隱藏,變形能力越強,對殺毒軟件的考驗越大。寫 shellcode則不然,通常exploit需要的shellcode不能太長,而且ids/ips藉以判定的字串往往還不是shellcode,感覺上只要能寫出一個讓殺毒軟件不認識的shellcode就可以了,從這一點上看,變形shellcode的唯一好處是每次能夠給你一個基本上全新的 shellcode,只要你不公開你的算法,殺毒軟件廠商沒有哪個精力(也許是能力)來分析你的東西
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章