Lua based DnsCat traffic parser

For the ones who don’t know DnsCat it’s an awsome tool and even has metasploit shellcodes. Here’s a little quote from the website:

dnscat is designed in the spirit of netcat, allowing two hosts over the Internet to talk to each other. The major difference between dnscat and netcat, however, is that dnscat routes all traffic through the local (or a chosen) DNS server. This has several major advantages:

  • Bypasses pretty much all network firewalls
  • Bypasses many local firewalls
  • Doesn’t pass through the typical gateway/proxy and therefore is stealthy

Which brings us to my original quest of finding a nice protocol to experiment with Wireshark dissector prototyping using LUA. I decided to try and make a dissector for DnsCat traffic. This post won’t contain the dissector, instead it contains a stand alone LUA DnsCat traffic parser(main dissector logic). Reason for this is that I’m not entirely happy with the dissector as it is right now,  I want to try and improve some things on it before publishing it. To be able to run this parser you’ll need to install an additional LUA library though, since LUA doesn’t have native support for bitwise operators. You can get the library from the following website:

http://bitop.luajit.org/

The installation is pretty straightforward, if you are on ubuntu you could also just do the following:

sudo apt-get install liblua5.1-bitop0

Ones you have that in place you should be able to run the parser without any problems. You can get the parser from pastebin or at the end of this post. If you are really impatient you can throw together a quick dissector yourself and just reuse this code for the dissecting part. I’ve also included some references at the end of this post that I’ve used while developing the parser.

Code


#!/usr/bin/env lua
--[[
 Author: DiabloHorn https://diablohorn.wordpress.com
 DnsCat traffic decoder, can be easily converted to a wireshark dissector :)
 This could be written a lot more efficient, but hey it's one of my first serious LUA scripts.
]]--
-- testdata
local testdata = {"dnscat.21.fgnlglmh.7718fc12.0.dgqt.directdns","dnscat.31.hihuznqd.2cedf05e.1.636174202f6574632f69737375650a.tvtm.directdns", "dnscat.29.gnwbuhls.699c9c36.3.sryi.directdns", "dnscat.21.gnwbuhls.699c9c2f.1.ffgchfgohehfcadbdacodadecodbcaemfefdcafmgocafmgmakak.rypw.directdns"}

-- requires the bitopt library to be installed
local bit = require("bit")

-- start base parser

-- split the request into an array of subdomain
function getsubs(data)
 -- empty table to hold the subs
 local subs = {}
 for sub in data:gmatch("[^%.]+") do
 table.insert(subs,sub)
 end
 return subs
end

-- decode the flags to human readable strings
function decodeflags(data)
 -- protocol flags
 local FLAG_STREAM = 0x00000001
-- deprecated
 local FLAG_SYN = 0x00000002
 local FLAG_ACK = 0x00000004
-- end of deprecated
 local FLAG_RST = 0x00000008
 local FLAG_HEX = 0x00000010
 local FLAG_SESSION = 0x00000020
 local FLAG_IDENTIFIER = 0x00000040

 -- convert string to number
 local hFlags = tonumber(data,16)
 --setup the flags table
 local Flags = {} -- st=nil,sy=nil,ac=nil,rs=nil,he=nil,se=nil,id=nil
 -- let's see which are set
 if bit.band(hFlags,FLAG_STREAM) ~= 0 then
 table.insert(Flags,"stream")
 end
-- deprecated
 if bit.band(hFlags,FLAG_SYN) ~= 0 then
 table.insert(Flags,"syn")
 end
 if bit.band(hFlags,FLAG_ACK) ~= 0 then
 table.insert(Flags,"ack")
 end
-- end of deprecated
 if bit.band(hFlags,FLAG_RST) ~= 0 then
 table.insert(Flags,"rst")
 end
 if bit.band(hFlags,FLAG_HEX) ~= 0 then
 table.insert(Flags,"hex")
 end
 if bit.band(hFlags,FLAG_SESSION) ~= 0 then
 table.insert(Flags,"session")
 end
 if bit.band(hFlags,FLAG_IDENTIFIER) ~= 0 then
 table.insert(Flags,"identifier")
 end
 return Flags
end

-- decode the error code to something human readable
-- overcomplicated...but hey I wanted to use the bitopt lib again
function decoderr(data)
 local ERR_SUCCESS = 0x00000000
 local ERR_BUSY = 0x00000001
 local ERR_INVSTATE = 0x00000002
 local ERR_FIN = 0x00000003
 local ERR_BADSEQ = 0x00000004
 local ERR_NOTIMPLEMENTED = 0x00000005
 local ERR_TEST = 0xFFFFFFFF
 local err = {}
 local herr = tonumber(data,16)

 if bit.tobit(ERR_SUCCESS) == bit.tobit(herr) then
 table.insert(err,"success")
 end

 if bit.tobit(ERR_BUSY) == bit.tobit(herr) then
 table.insert(err,"busy")
 end

 if bit.tobit(ERR_INVSTATE) == bit.tobit(herr) then
 table.insert(err,"invalidstate")
 end

 if bit.tobit(ERR_FIN) == bit.tobit(herr) then
 table.insert(err,"confin")
 end

 if bit.tobit(ERR_BADSEQ) == bit.tobit(herr) then
 table.insert(err,"badseqnum")
 end

 if bit.tobit(ERR_NOTIMPLEMENTED) == bit.tobit(herr) then
 table.insert(err,"notimplemented")
 end

 if bit.tobit(ERR_TEST) == bit.tobit(herr) then
 table.insert(err,"contest")
 end
 return err
end

-- decode netbios data to ascii
function decodenetbios(data)
 local ldata = data:upper()
 local dec = ""
 for sub in ldata:gmatch("%u%u") do
 -- perform operation in decimal and convert final value from hex XX to decimal
 --local decnum = tonumber(((sub:byte(1)-65) .. (sub:byte(2)-65)),16)
 -- Thanks to Animal for making me realize the concat has to be with hexnumbers
 local decnum = tonumber((bit.tohex(sub:byte(1)-65,1) .. bit.tohex(sub:byte(2)-65,1)),16)
 print(bit.tohex(sub:byte(1)-65,1))
 print(decnum,sub)
 if decnum > 31 and decnum < 127 then
 dec = dec .. string.char(decnum)
 else
 dec = dec .. "."
 end
 decnum = 0
 sub = ""
 end
 return dec
end

-- decode hex data to ascii
function decodehex(data)
 local dec = ""
 for sub in data:gmatch("%x%x") do
 decnum = tonumber(sub,16)
 if decnum > 31 and decnum < 127 then
 dec = dec .. string.char(decnum)
 else
 dec = dec .. "."
 end
 end
 return dec
end

-- main flow implementation
-- lacks implementation of syn/ack since it's deprecated
function parseDC(data)
 local finalparsed = {}
 local x = getsubs(data)

 finalparsed["signature"] = x[1]
 table.remove(x,1)
 finalparsed["flags"] = table.concat(decodeflags(x[1]),",")
 table.remove(x,1)

 if finalparsed["flags"]:find("identifier") ~= nil then
 finalparsed["identifier"] = x[1]
 table.remove(x,1)
 if finalparsed["flags"]:find("session") ~= nil then
 finalparsed["session"] = x[1]
 table.remove(x,1)
 end
 elseif finalparsed["flags"]:find("session") ~= nil then
 finalparsed["session"] = x[1]
 table.remove(x,1)
 end

 if finalparsed["flags"]:find("stream") ~= nil then
 finalparsed["seqnum"] = x[1]
 table.remove(x,1)
 end

 if finalparsed["flags"]:find("rst") ~= nil then
 finalparsed["err"] = table.concat(decoderr(x[1]),",")
 table.remove(x,1)
 finalparsed["garbage"] = x[1]
 finalparsed["domain"] = x[2]
 else
 finalparsed["count"] = x[1]
 table.remove(x,1)
 -- if you wonder the character # == len()
 finalparsed["garbage"] = x[#x-1]
 finalparsed["domain"] = x[#x]
 table.remove(x,(#x))
 table.remove(x,(#x))
 -- so we either got data or we don't
 finalparsed["asciidata"] = ""
 while #x > 0 do
 if finalparsed["flags"]:find("hex") == nil then
 finalparsed["asciidata"] = finalparsed["asciidata"] .. decodenetbios(x[1])
 else
 finalparsed["asciidata"] = finalparsed["asciidata"] .. decodehex(x[1])
 end
 table.remove(x,1)
 end
 end -- end of rst check

 return finalparsed
end -- end of parseDC function

-- end of base parser

for i,v in ipairs(testdata) do
 local fp = parseDC(v)
 print("TestData: " .. v)
 print("Signature: " .. tostring(fp.signature))
 print("Flags: " .. tostring(fp.flags))
 print("Identifier: " .. tostring(fp.identifier))
 print("Session: " .. tostring(fp.session))
 print("Seqnum: " .. tostring(fp.seqnum))
 print("Count: " .. tostring(fp.count))
 print("Error: " .. tostring(fp.err))
 print("Data: " .. tostring(fp.asciidata))
 print("Garbage: " .. tostring(fp.garbage))
 print("Domain: " .. tostring(fp.domain))
end

References

http://www.skullsecurity.org/wiki/index.php/Dnscat
http://www.wowpedia.org/Pattern_matching
http://bitop.luajit.org/api.html
http://lua-users.org/wiki/
http://www.lua.org/manual/5.1/

One thought on “Lua based DnsCat traffic parser”

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: