129 lines
3.1 KiB
PHP
129 lines
3.1 KiB
PHP
<?php
|
|
|
|
namespace App\Classes\Protocol\DNS;
|
|
|
|
use Illuminate\Support\Collection;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
final class Query
|
|
{
|
|
private const LOGKEY = 'PDQ';
|
|
|
|
private string $buf;
|
|
private int $class;
|
|
private string $dns;
|
|
private int $id;
|
|
private int $type;
|
|
|
|
private int $arcount;
|
|
private int $qdcount;
|
|
|
|
private RR $additional;
|
|
|
|
private Collection $labels;
|
|
|
|
// https://github.com/guyinatuxedo/dns-fuzzer/blob/master/dns.md
|
|
private const header = [ // Struct of a DNS query
|
|
'id' => [0x00,'n',1], // ID
|
|
'header' => [0x01,'n',1], // Header
|
|
'qdcount' => [0x02,'n',1], // Entries in the question
|
|
'ancount' => [0x03,'n',1], // Resource Records in the answer
|
|
'nscount' => [0x04,'n',1], // Server Resource Records in the answer
|
|
'arcount' => [0x05,'n',1], // Resource Records in the addition records section
|
|
];
|
|
|
|
public function __construct(string $buf)
|
|
{
|
|
$this->buf = $buf;
|
|
$rx_ptr = 0;
|
|
|
|
// DNS Query header
|
|
$header = unpack(self::unpackheader(self::header),$buf);
|
|
$rx_ptr += $this->header_len();
|
|
|
|
$this->id = $header['id'];
|
|
$this->qdcount = $header['qdcount'];
|
|
$this->arcount = $header['arcount'];
|
|
$this->header = $header['header'];
|
|
|
|
// Get the domain elements
|
|
$this->labels = collect();
|
|
|
|
while (($len=ord(substr($this->buf,$rx_ptr++,1))) !== 0x00) {
|
|
$this->labels->push(substr($this->buf,$rx_ptr,$len));
|
|
$rx_ptr += $len;
|
|
}
|
|
|
|
// Get the query type/class
|
|
try {
|
|
$result = unpack('ntype/nclass',substr($this->buf,$rx_ptr,4));
|
|
|
|
} catch (\Exception $e) {
|
|
Log::error(sprintf('%s:! Unpack failed: Buffer: [%s] (%d), RXPTR [%d]',self::LOGKEY,hex_dump($this->buf),strlen($this->buf),$rx_ptr));
|
|
|
|
return;
|
|
}
|
|
|
|
$rx_ptr += 4;
|
|
$this->type = $result['type'];
|
|
$this->class = $result['class'];
|
|
|
|
$this->dns = substr($this->buf,$this->header_len(),$rx_ptr-$this->header_len());
|
|
|
|
// Do we have additional records
|
|
if ($this->arcount) {
|
|
// Additional records, EDNS: https://datatracker.ietf.org/doc/html/rfc6891
|
|
if (($haystack = strstr(substr($this->buf,$rx_ptr+1+10),"\x00",true)) !== FALSE) {
|
|
Log::error(sprintf('%s:! DNS additional record format error?',self::LOGKEY));
|
|
// @todo catch this
|
|
}
|
|
|
|
$this->additional = new RR(substr($this->buf,$rx_ptr,(strlen($haystack) === 0) ? NULL : strlen($haystack)));
|
|
$rx_ptr += $this->additional->length;
|
|
}
|
|
|
|
if (strlen($this->buf) !== $rx_ptr) {
|
|
dd(['query remaining'=>strlen($this->buf)-$rx_ptr,'hex'=>hex_dump(substr($this->buf,$rx_ptr))]);
|
|
}
|
|
}
|
|
|
|
public function __get($key)
|
|
{
|
|
switch ($key) {
|
|
case 'class':
|
|
case 'dns':
|
|
case 'id':
|
|
case 'labels':
|
|
case 'qdcount':
|
|
case 'arcount':
|
|
case 'header':
|
|
case 'type':
|
|
return $this->{$key};
|
|
|
|
case 'domain':
|
|
return $this->labels->join('.');
|
|
}
|
|
}
|
|
|
|
public static function header_len()
|
|
{
|
|
return collect(self::header)->sum(function($item) { return $item[2]*2; });
|
|
}
|
|
|
|
/**
|
|
* Unpack our configured DNS header
|
|
*
|
|
* @param array $pack
|
|
* @return string
|
|
*/
|
|
protected static function unpackheader(array $pack): string
|
|
{
|
|
return join('/',
|
|
collect($pack)
|
|
->sortBy(function($k,$v) {return $k[0];})
|
|
->transform(function($k,$v) {return $k[1].$v;})
|
|
->values()
|
|
->toArray()
|
|
);
|
|
}
|
|
} |