******************************************************************************* * * * 【引言】 * * 本篇文章所含內容,皆是以業餘電子佈告欄星之故鄉站內信管 Smiley Wang * * 所翻譯並公佈的 PS 的病毒雜誌中所轉載的。請體諒他人用心並尊重智慧財產權。 * * 本文章將隨新的文章公佈而持續更新。 * * * * 【索引】 * * 01. 病毒程式最佳化 * * 02. 多形病毒 * * * ******************************************************************************* =============================================================================== * 01. 病毒程式最佳化 * 發信人: yifeng@NTUCC_BBS (寶寶), 信區: Virus 標 題: P/S 文摘: 病毒程式最佳化 發信站: 台大電算中心 BBS (Wed Aug 3 20:45:20 1994) 轉信站: NTUCC_BBS (local) 本文章譯自某病毒雜誌, 希望各位將之定位於「研究病毒」上, 不要任意散播病毒。 這篇文章其實也沒有什麼, 只是一些組合語言的寫作技巧, 希望對您有些幫助。 如果文章之中有謬誤之處, 敬請不吝指正, 謝謝. 譯者 Smiley Wang 1994.08.03. ------------------------------------- 病毒程式碼最佳化, 初學者指南 ------------------------------------- Written by Dark Angel ------------------------------------- 程式碼最佳化 撰寫病毒, 頭一個要考量的就是程式碼大小。一隻肥胖的病毒除了跑起來比較慢之外, 還占用較大的磁碟空間。 在開始最佳化程式碼之前, 請保留目前程式碼的備份. 因為我們並沒有把握這次寫的一 定比原來還好. 程式碼最佳化有兩種途徑: 「結構調整」和「區域調整」。 結構調整是屬於程式流程 方面的調整, 如下例: check_install: mov ax,1234h int 21h cmp bx,1234h ret install_virus: call check_install jz exit_install 調整後: install_virus: mov ax,1234h int 21h cmp bx,1234h jz exit_install 第一段程式碼共浪費四個位元組, 三個在 CALL, 一個在 RET. 雖然四個位元組看起來 沒什麼, 但是聚沙成塔, 集腋成裘。以下是另一個例子: get attributes open file read/only read file close file exit if already infected clear attributes open file read/write get file time/date write new header move file pointer to end of file concatenate virus restore file time/date close file restore attributes exit 調整後: get attributes clear attributes open file read/write read file if infected, exit to close file get file time/date move file pointer to end of file concatenate virus move file pointer to beginning write new header restore file time/date close file restore attributes exit 第二段程式碼增加移動檔案指標程序, 縮減開檔關檔部分, 節省了相當數量位元組。 「區域調整」比「結構調整」簡單的多, 因為它是藉由獨立的精簡程式片斷來調整。 請看以下的例子: 這可能是最常見的例子: mov ax,0 ; this instruction is 3 bytes long mov bp,0 ; mov reg, 0 with any reg = nonsegment register takes 3 bytes 最佳化之後: xor ax,ax ; this takes but 2 bytes xor bp,bp ; mov reg, 0 always takes 2 bytes 或是這樣也可以: sub ax,ax ; also takes 2 bytes sub bp,bp 還有一個容易被忽略的例子: mov bh,5h ; two bytes mov bl,32h ; two bytes ; total: four bytes 最佳化之後: mov bx,532h ; three bytes, save one byte 在控制 FILE HANDLE 時一個相當有用的技巧: mov bx,ax ; 2 bytes 最佳化之後: xchg ax,bx ; 1 byte 另一個控制檔案指標常見的例子: mov ax,4202h ; save one byte from "mov ah,42h / mov al,2" xor dx,dx ; saves one byte from "mov dx,0" xor cx,cx ; same here int 21h 最佳化之後: mov ax,4202h cwd ; equivalent to "xor dx,dx" when ax < 8000h xor cx,cx int 21h 有時候使用 [si] 會比 [bp] 更好: mov ax,[bp] ; 3 bytes mov word ptr cs:[bp],1234h ; 6 bytes add ax,[bp+1] ; 3 bytes - no byte savings will occur mov ax,[si] ; 2 bytes mov word ptr cs:[si],1234h ; 5 bytes add ax,[si+1] ; 3 bytes - this is not smaller 還有一個奇特的現象: inc al ; 2 bytes inc bl ; 2 bytes 最佳化之後: inc ax ; 1 byte inc bx ; 1 byte 結構最佳化也可以減少程式重複片斷. 例如我們通常在磁碟寫入中斷處理程式之後, 加上"jc error"的錯誤處理程序。也可以考慮使用磁碟作業系統的嚴重錯誤處理程序 (INT24)取代這部分程式。 以下這個最佳化的例子, 您覺得如何呢? mov ax, 4300h ; get file attributes mov dx, offset filename int 21h push dx ; save filename push cx ; and attributes on stack inc ax ; ax = 4301h = set file attributes push ax ; save 4301h on stack xor cx,cx ; clear attributes int 21h ...感染後... pop ax ; ax = 4301h pop cx ; cx = original attributes of file pop dx ; dx-> original filename int 21h 最佳化幾乎總是特定處理程序. 透過調整結構和使用精鍊的指令, 一個好的程式設計 師可以有效的替病毒減肥。 藉著對80x86指令集的熟悉, 您可以找出更好的程式寫法 。讓我們共同為製造最小的病毒而努力吧。 =============================================================================== * 02. 多形病毒 * 發信人: yifeng@Palmarama (寶寶), 信區: Virus 標 題: P/S 文摘: 多形病毒 發信站: 台大計中椰林風情站 (Tue Aug 23 18:58:21 1994) 轉信站: Palmarama 本文章譯自某病毒雜誌, 希望各位將之定位於「研究病毒」上, 不要任意散 播病毒。這篇文章其實也沒有什麼, 介紹一些多形病毒加密解密的方法, 希 望對您有些幫助。如果文章之中有謬誤之處, 敬請不吝指正, 謝謝. 譯者 Smiley Wang 1994.08.23. --------------------- 多形病毒進階指南 --------------------- By Dark Angel --------------------- 最近連續出了好幾個病毒加密「引擎」, 使我也有寫一個的念頭. 經過幾個禮拜的努力, 我已經能夠設計了. 其實只要小心的撰寫程式, 多 形病毒加密引擎和複合病毒產生器一樣, 並沒有想像中那麼困難. (譯者注: 就是 DAME ( Dark Angel's Multiple Encryptor ) ) 在談到到病毒多形加密引擎之前, 我們要對現況有些瞭解. 老套的掃 描可執行檔, 是快速且幾乎可以找出所有知名病毒的方法. 但是自從多形 引擎推出後, 情勢整個扭轉了過來. 多形病毒替同樣的病毒製造很多不同 解碼程序, 理論上這種病毒的解碼程序, 沒有一個位元是相同的. 病毒偵 測程式, 無法依賴簡單的病毒檢查碼來判斷「病毒是否已經藏身在電腦中 」. 取而代之的是, 使用一些演算法則誘使病毒暴露行蹤. 建立可靠的演 算法則偵測多形病毒, 比弄一些病毒檢查碼要有用多了. 儘管如此, 一旦 病毒偵測程式無法真正偵測到病毒的存在, 即使只有一隻, 它就會不斷的 繁衍下去. 因為, 病毒的存在只為一個目的, 那就是「活著」. 在寫多形程式之前, 請您先弄一本 80x86 指令集. 如果沒有這本書 , 您在寫多形的程式時將會縛手縛腳. 因為我們要精確的知道哪個指令對 映哪些運算碼(opcode). 這樣才能寫出緊密的多形核心程式. 現在讓我們來看看一段機械碼 "BB1301B900022E8137123483C302E2F6 ", 這是一段真正的多形病毒片斷--解碼程式. ---------------------------------------------------------------- 譯者註: 這段機械碼反組譯於下: 2180:0100 BB1301 MOV BX,0113 2180:0103 B90002 MOV CX,0200 2180:0106 2E CS: 2180:0107 81371234 XOR WORD PTR [BX],3412 2180:010B 83C302 ADD BX,+02 2180:010E E2F6 LOOP 0106 ---------------------------------------------------------------- 它的演算法則是這樣的: 指定一個指標暫存器, 指標內容是需解碼的記憶體位址. 指定一個計數暫存器 ┌→ 解碼 │ 改變指標暫存器, 指向下一個記憶體位址 │ 遞減計數暫存器 └← 重複迴圈直到計數暫存器其值為零 依此法則, 我們可以寫出如下的程式: 解碼範例(一) ------------ mov bx,offset startencrypt ; BX 是指標暫存器 mov cx,viruslength / 2 ; CX 是計數暫存器 decrypt_loop: xor word ptr [bx],12h ; 解碼一個 WORD inc bx ; 下一個記憶體位址 inc bx loop decrypt_loop ; 重複迴圈直到計數值為零 startencrypt: 解碼範例(二) ------------ start: mov bx,viruslength ; BX 是計數暫存器 mov bp,offset start ; BP 是指標暫存器 decrypt_loop: add byte ptr [bp+0Ch],33h ; 解碼一個 BYTE inc bp ; 下一個記憶體位址 dec bx ; 遞減計數暫存器 jnz decrypt_loop ; 重複迴圈直到計數值為零 只要充分發揮我們的想像力, 一個演算法則可以寫出各式各樣的程式 , 比原來的運算碼多了「彈性」. 利用以上的例子, 我們還可以做進一步 的變化. 例如對調指定指標暫存器和計數暫存器的順序. mov cx,viruslength mov bx,offset startencrypt 兩行對調 mov bx,offset startencrypt mov cx,viruslength 我們的目標是希望以最少的程式碼製造出最大的變化出來. 程式的寫作風格於是就變得很重要, 因為新的變化可以很方便的加進去, 或調整順序. 寫多形病毒第一個遭遇的問題是決定變體後如何加密, 假設我們決定 用一個 WORD 的長度, 利用 XOR 來解碼, 以 BX 當指標暫存器, DX 包含 解碼數值, CX 當作計數暫存器. 一旦確定了這幾點, 就要開始計算每個 變數的啟始數值. 例如, 決定以 CX 當作計數暫存器, 以 BYTE 的長度解 碼, 它的啟始值就應該是病毒本身的長度. 如果我們增加變數控制解碼, 解碼長度將會變得更小變化會更多. 請注意某些變數, 特別是指標暫存器 , 在編碼之前我們根本不知道它的值是多少. 所謂多形程式, 當然必須要能夠正確的解碼. 不論怎麼變都必須自動 產生有效的解碼程式. 簡單的多形程式只能編碼 "MOV" 指令, 將數值指 定給某暫存器. 複雜的多形程式可以編碼一系列功能類似的指令, 如: mov ax, 808h 可以換成: mov ax, 303h ; ax = 303h mov bx, 101h ; bx = 101h add ax, bx ; ax = 404h shl ax, 1 ; ax = 808h 如果我們採用暫存器隨機編碼, 計數暫存器應該被排除在外. 因為頭 一個將計數暫存器編碼的話, 可能會導致多形程式損毀, 這一點要特別注 意. 暫存器編碼之後, 接著再對解碼程式編碼. 有必要的話, 連"LOOP"指 令也要編碼. 我們可以使用不同的指令替代它, 如 LOOP,LOOPNZ,JNZ 等 等. 此外還可以改變解碼暫存器或迴圈計數暫存器的值. 現在我們要想辦法把編碼弄的更亂, 在程式之中滲一些無用的指令及 變數, 還有同樣的功能要用不同的手法來寫, 如果您的編碼程式設計的夠 好的話, 還可以利用現在程式的運算碼產生亂數指令, 塞在原本已經夠亂 的程式裡. (譯者註: 「灌水」誰都會吧, 呵...) 解碼程式寫好之後, 再來就是病毒本身的編碼了. 編碼當然必須根據 已經決定的方式編, 這樣解碼程式才能正確的解出來啊. 「編碼」「解碼 」必須配對存在, 多形程式隨機決定解碼方式, 也因而決定了編碼方式. 雖然多形程式內部好幾套編碼解碼程式, 會讓程式體積大一點, 可是它也 同時使程式變得靈活, 變形容易, 是個值得的投資. 最後我們要對指標暫存器加密, 為什麼指標暫存器要擺在最後呢? 因 為在加密上述這些程式之前, 我們無法取得真正的程式進入點. 加密指標 暫存器一般有兩種方法: 其一是單一指令加密: 例如 mov bx,185h 取得啟始值, 是個簡單缺 乏變化性的方法. 其二是輪替加密法: 例如 xor word ptr [bx+185],cx 和 xor word ptr [bx],cx 這兩行交錯加密, 這樣就比較有彈性了. 指標暫存器的啟始值不一定是定值, 我們可以藉著調整解密程式的偏移位 址而得到正確的解密程式. 當然不一定非使用單一指令加密還是輪替加密 不可, 如果混合使用(有時單一、有時輪替), 效果更加! ------------------------------------------------------------------ 翻譯完這篇文章, 我覺得有些疑問, 希望高手能為我澄清. 我們知道病毒的感染基本步驟如下: 1) 找個檔案感染 2) 檢查它是否已經感染過了 3) 如果是,回到 1 4) 感染它 5) 如果感染夠了,離開 6) 否則回到 1 可是在多形病毒之中, 病毒如何判斷檔案中已經有自己的同伴了? 假設有某 個識別碼, 這不是自曝行蹤, 又給予掃描病毒碼的偵毒程式機會?! 疑惑的 Smiley Wang