Skip to content

Commit

Permalink
updating code for v0.0.2, closes #1 and #2
Browse files Browse the repository at this point in the history
  • Loading branch information
SuperFola committed Jan 19, 2021
1 parent 0aa6027 commit 587500e
Show file tree
Hide file tree
Showing 5 changed files with 41 additions and 22 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Changelog

## Unreleased changes
## v0.0.2
### Added
- converter.py, to encode/decode ascii in base 32 and base 64 flawlessly
- packet.py to encapsulate a lot of dull work
- threaded udp socket server binded on port 53 otherwise we have an ICMP type 3 error (port unreachable, because nothing is binded to it)
- simple chat server, anonymizing the ip addresses, can receive commands to get the messages (/consult), otherwise just add the message to the queue
- error catching on subdomains decoding errors

### Changed
- now using subdomains of a main domain instead of the qname field, in case it's filtered
Expand Down
34 changes: 24 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# [WIP] DoNotSend - hijacking the DNS protocol
# DoNotSend - hijacking the DNS protocol

For now, it works on **Linux only**.

Expand All @@ -13,7 +13,7 @@ Here it's used to send messages and retrieve other messages, instead of asking f
* if it isn't installed alongside scapy:
* libpcap
* venv
* Sometimes the wheel module is needed as well
* Sometimes the `wheel` module is needed as well

```shell
apt install python3-venv
Expand All @@ -36,16 +36,16 @@ apt install libpcap0.8-dev
We can include arbitrary data in the hostname which the server then can interpret and execute/relay.
Thus we put our data in the qname section of the query, encoded using base32, without the padding (we can easily recalculate it).

Currently, it's just a WIP, it sends a single message "hello world" and get responses from the server which are displayed.
The queries sent are TXT DNS queries, otherwise (because we answer with TXT DNS replies) the replies will get lost/deleted when transmitted by peers (yes you read correctly, Google can ask the DNS if it knows `crafted-domain.my_dns.domain.example.com`).

### Running the client

```shell
cd src
# needs to run as root because it is using port 53
python3 client.py "hostname"
```bash
python3 client.py [my_dns.domain.example.com] "message here"
```

If no message is given, `hello world` is sent.

You can also use the `client.sh` version, relying only on `dig`, `base32` and `base64`.

## server

It receives queries and read the wanted "fake" hostname, decode the data put in the hostname as base32.
Expand All @@ -57,9 +57,23 @@ Then it replies through a DNS TXT reply, where the data is encoded as base64 wit
```shell
cd src
# needs to run as root because it is binding port 53
python3 server.py
python3 server.py [interface, for example eth0 on linux] [my_dns.domain.example.com]
```

## Having other big DNS relay your queries and answers

In a few steps I was able to configure my NS provider to set myself up as my own DNS, to get to reply to the weird domains I need to communicate.

For this examples, let's say my server is named `example.com`.

1. In my DNS Zone, I added a `NS` entry for `dns.example.com`, pointing to `dns.example.com.` (mind the final dot)
1. Then I added a `A` entry for `dns.example.com`, pointing to `my server ip here`
1. In the DNS servers configuration, I already had things like `ns1.provider.com`, I added myself as a DNS server: `dns.example.com`, pointing to `my server ip here`
1. For the redirections, I added `dns.example.com` as a type `A`, pointing to `my server ip here`
1. Then, just wait a bit (can be as long as 48 hours) and you're good to go

Now I just have to tell my client scripts to use the domain `dns.example.com` to send messages to it and it works like a charm, even when asking Google about it!

## Documentation

* [DNS packet structure](doc/DNSPacketStructure.md)
Expand Down
2 changes: 1 addition & 1 deletion client.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# send message
stripped_b32=`echo $1 | base32 | tr -d =`
crafted_domain="${stripped_b32}.dns.12f.pl"
answer=`dig @12f.pl $crafted_domain A`
answer=`dig @12f.pl $crafted_domain TXT`
# decode answer
message=`echo $answer | grep -A 1 ";; ANSWER SECTION:" | tail -n 1 | egrep -o "\".+\"" | cut -c 2- | rev | cut -c 2- | rev`
length=$((4 - $(expr length "$message") % 4))
Expand Down
4 changes: 3 additions & 1 deletion src/packet.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ def build_query(layer: dict, domain: str) -> object:
id=randint(0, 2 ** 16 - 1),
rd=0, # no recursion desired
qr=DNSHeaders.QR.Query,
qd=DNSQR(qname=layer["dns"]["qname"], qtype=DNSHeaders.Type.HostAddr),
# requests must be of type TXT otherwise our answers (of type TXT)
# don't get transmitted if recursion occured
qd=DNSQR(qname=layer["dns"]["qname"], qtype=DNSHeaders.Type.Text),
)

return Packet(pkt, domain)
Expand Down
20 changes: 11 additions & 9 deletions src/server.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
#!/usr/bin/env python3

import sys
import binascii
import socket
import sys
import threading

from scapy.layers.dns import DNS, DNSQR, DNSRR
from scapy.layers.inet import IP, UDP
from scapy.sendrecv import send, sniff

from converter import Domain, Content
from converter import Content, Domain
from packet import Packet
from utils import DNSHeaders, DNSAnswer, init_logger, get_ip_from_hostname
from utils import DNSAnswer, DNSHeaders, get_ip_from_hostname, init_logger


def socket_server(ip: str):
Expand All @@ -37,11 +38,14 @@ def dns_responder(self, pkt: IP):
if packet.is_valid_dnsquery():
self.logger.info("got a packet from %s:%i", packet.src, packet.sport)

# TEMP fix
subdomain = packet.subdomain_from_qname.split('.')[0]
self.logger.debug("subdomain: %s", subdomain)
data = Domain.decode(subdomain)
self.logger.debug("decoded: %s", data)

try:
data = Domain.decode(subdomain)
except binascii.Error:
# couldn't decode, drop the packet and do nothing
return

# keep destination
answer = Packet.build_reply(
Expand All @@ -66,9 +70,7 @@ def dns_responder(self, pkt: IP):
self.domain,
)

self.logger.debug("incomming packet type: %s", hex(packet.question.qtype))

self.logger.debug(answer.dns.summary())
self.logger.debug("Answering %s", answer.dns.summary())
send(answer.packet, verbose=0, iface=self.interface)

def run(self):
Expand Down

0 comments on commit 587500e

Please sign in to comment.