Ces classes étendent leurs classes respectives, mais chargent leurs données à partir des informations stockées par ADE dans le Registre Windows. Elles n'effectuent aucun appel réseau si les entrées de registre requises ne peuvent pas être trouvées. Lors de l'enregistrement des données (sauvegarde), les méthodes non surchargées sont utilisées, ce qui génère des fichiers compatibles avec "libgourou" dans le répertoire de sortie.

This commit is contained in:
2025-10-24 17:16:42 -07:00
parent 72bbebf9b2
commit 2de7bb80f5
2 changed files with 539 additions and 0 deletions

View File

@@ -0,0 +1,231 @@
package common;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.jna.Library;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.WinDef;
import org.w3c.dom.Document;
import javax.xml.parsers.ParserConfigurationException;
/**
* An extension of the Activation class that reads activation data from the
* Windows registry instead of from the file system.
*/
public class WinActivation extends Activation
{
public interface ManualKernel32 extends Library
{
ManualKernel32 INSTANCE = Native.load( "kernel32", ManualKernel32.class);
WinDef.UINT GetSystemDirectoryW( Memory lpBuffer, int uSize);
}
private final Logger LOGGER = Logger.getLogger( WinActivation.class.getName() );
private String section;
private String licenseURL = null;
/**
* Constructor which loads existing activation info from the registry.
*
* @param activationFile the name of the activation file. This class reads its data
* from the Windows registry, so this file is only used when exporting windows data.
* @param httpUtils a Network helper object that makes http requests. Injected
* here, so we can create mock objects for unit testing
**/
public WinActivation( Path activationFile, HttpUtils httpUtils, Level logLevel )
{
super( activationFile, httpUtils, logLevel );
parseActivationRegistry();
LOGGER.setLevel( logLevel );
}
private String readNonBlank( BufferedReader in )
throws IOException
{
String line;
do
{
line = in.readLine();
}
while (line != null && line.isBlank());
if (null == line)
{
return null;
}
return line.trim();
}
private String appendElement( String keyNode, String line, BufferedReader reader )
throws IOException
{
String keyName = null;
String hkey = "";
while (null != line)
{
// if the line starts with HKEY start a new node and call this recursively
if (line.startsWith( "HKEY" ))
{
if (null != keyNode && !line.startsWith( keyNode ))
{
break;
}
// a new element
hkey = line;
line = line.substring( keyNode.length() + 1 );
if (line.contains( "\\" ))
{
// // if the line contains a slash, were going to need multiple elements.
// while (line.contains( "\\" ))
// {
// keyNode = line.substring( 0, line.indexOf( '\\' ) );
// line = line.substring( keyNode.length() + 1 ).trim();
// }
// if (!line.isBlank())
// {
//
// }
line = readNonBlank( reader );
line = appendElement( keyNode, line, reader );
}
else
{
if (null != keyName)
{
// save off this name as the parent property.
if (!keyName.equalsIgnoreCase( section ))
{
section = keyName;
if ("activationToken".equalsIgnoreCase( section ))
{
hasActivationToken = true;
}
}
}
line = readNonBlank( reader );
line = appendElement( hkey, line, reader );
}
}
// if the line starts with "(Default)", change the parent node name
else if (line.startsWith( "(Default)" ))
{
if (line.contains( "REG_SZ" ))
{
keyName = line.substring( line.lastIndexOf( " " ) + 1 );
line = readNonBlank( reader );
}
}
else if (line.contains( "REG_" ))
{
// has to be a name/value pair
String key = line.substring( 0, line.indexOf( ' ' ) );
String value = line.substring( line.lastIndexOf( ' ' ) + 1 ).trim();
if (line.contains( "REG_SZ" ))
{
// value is a string, add a text node.
if ("value".equals( key ))
{
// special case; operatorURL goes in a special list
if ("operatorURL".equalsIgnoreCase( keyName )
&& "operatorURLList".equalsIgnoreCase( section ))
{
operatorURLList.add( value );
}
// special case: license info pairs are stored together
else if ("licenseServiceInfo".equalsIgnoreCase( section ))
{
if ("licenseURL".equalsIgnoreCase( keyName ))
{
licenseURL = value;
}
else if (null != licenseURL)
{
licenseServiceCertificates.put( licenseURL, value );
licenseURL = null;
}
}
// loan token info is not stored in the Activation object
else if (!"loanToken".equalsIgnoreCase( section ))
{
properties.put( keyName, value );
}
}
else
{
properties.put( key, value );
}
}
line = readNonBlank( reader );
}
else
{
break;
}
}
return line;
}
public Document parseActivationRegistry()
throws GourouException
{
try
{
Process process = Runtime.getRuntime().exec(
"reg query HKEY_CURRENT_USER\\Software\\Adobe\\Adept\\Activation /s /v *" );
BufferedReader reader = new BufferedReader( new InputStreamReader( process.getInputStream() ) );
String line = readNonBlank( reader );
if (null == line)
{
return null;
}
String keyNode = line.substring( 0, line.lastIndexOf( '\\' ) );
appendElement( keyNode, line, reader );
process.waitFor();
}
catch (Exception e)
{
e.printStackTrace();
}
return null;
}
/**
* Allows the WinDevice class to update the private license key with a decrypted value
* @param plk
*/
public void setPrivateLicenseKey( String plk )
{
properties.put( "privateLicenseKey", plk );
}
/**
* Creates "activation.xml", "device.xml", and "devicesalt" from the values derived from windows.
* Simply calls the "update*File" methods on the Activation and Device objects.
* @param dirPath The output directory where the files will be written.
*/
public void updateFromWindows( Path dirPath, WinActivation activation, WinDevice device )
throws ParserConfigurationException, IOException, NoSuchAlgorithmException
{
activation.updateActivationFile(); // Write the Windows registry values to libgourou compatible files.
device.updateDeviceFiles( dirPath.resolve( "device.xml" ),
dirPath.resolve( "devicesalt" ) ); // writes a new device.xml file
}
@Override
public Path getActivationDirPath()
{
return Paths.get( "").toAbsolutePath();
}
}

View File

@@ -0,0 +1,308 @@
package common;
import com.sun.jna.Memory;
import com.sun.jna.platform.win32.Advapi32Util;
import com.sun.jna.platform.win32.Crypt32Util;
import com.sun.jna.platform.win32.WinDef;
import com.sun.jna.ptr.IntByReference;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.math.BigInteger;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Base64;
import java.util.Locale;
import java.util.logging.Level;
import static com.sun.jna.platform.win32.Kernel32.INSTANCE;
import static com.sun.jna.platform.win32.WinDef.MAX_PATH;
public class WinDevice extends Device
{
private final WinActivation activation;
private String windowsUsername;
private String decryptedPrivateKey;
public String getDecryptedPrivateKey()
{
return decryptedPrivateKey;
}
/**
* Main Device constructor.
*
* @ param deviceFile Path of device.xml
* @ param deviceKeyFile Path of devicesalt
* @ param hobbesVersion Used to create the Device file if it doesn't exist
* @ param randomSerial If the Device file is being created, use a random serial number instead of a derived one.
*/
public WinDevice( WinActivation activation, Level logLevel )
throws IOException, InterruptedException
{
LOGGER.setLevel( logLevel );
this.activation = activation;
parsePrivateKey();
fingerprint = activation.getDeviceFingerprint();
deviceClass = "Desktop";
InetAddress localHost = InetAddress.getLocalHost();
deviceName = localHost.getHostName();
deviceType = "standalone";
}
private static String toHex( byte[] data )
{
final String HEXES = "0123456789abcdef";
final StringBuilder hex = new StringBuilder( 2 * data.length );
for (final byte b : data)
{
hex.append( HEXES.charAt( (b & 0xF0) >> 4 ) ).append( HEXES.charAt( (b & 0x0F) ) );
}
return hex.toString();
}
public static byte[] hexStringToByteArray( String hexString )
{
if (hexString == null)
{
throw new IllegalArgumentException( "Invalid hex string: " + hexString );
}
if (hexString.startsWith( "0x" ) || hexString.startsWith( "0X" ))
{
hexString = hexString.substring( 2 );
}
if (hexString.length() % 2 != 0)
{
throw new IllegalArgumentException( "Hex string must have an even number of characters." );
}
int hexLen = hexString.length();
int byteLen = hexLen / 2;
byte[] data = new byte[byteLen];
for (int i = 0; i < hexLen; i += 2)
{
data[i / 2] = (byte) ((Character.digit( hexString.charAt( i ), 16 ) << 4)
+ Character.digit( hexString.charAt( i + 1 ), 16 ));
}
return data;
}
private String readNonBlank( BufferedReader in )
throws IOException
{
String line;
do
{
line = in.readLine();
}
while (line != null && line.isBlank());
if (null == line)
{
return null;
}
return line.trim();
}
private String constructEntropy()
{
long serial = getVolumeSerialNumber();
byte[] buffer = new byte[ 4 ];
for (int i = 3; i >= 0; i--)
{
buffer[i] = (byte) (serial & 0xFF);
serial >>= Byte.SIZE;
}
String serialHex = toHex( buffer );
StringBuilder sb = new StringBuilder( serialHex );
String vendor = getWMICInfo( "Manufacturer" );
vendor = toHex( vendor.getBytes() );
sb.append( vendor );
String signature = getWMICInfo( "ProcessorId" );
sb.append( signature.toLowerCase().substring( 10 ) );
sb.append( toHex( windowsUsername.getBytes( StandardCharsets.UTF_8 ) ) );
while (sb.length() < 64)
sb.append( '0' );
return sb.toString();
}
public String getWMICInfo( String key )
{
try
{
String line;
Process p = Runtime.getRuntime().exec( "wmic cpu get " + key );
BufferedReader input = new BufferedReader( new InputStreamReader( p.getInputStream() ) );
while ((line = input.readLine()) != null)
{
if (line.isBlank())
{
continue;
}
if (!line.toLowerCase().contains( key.toLowerCase() ))
{
return line.trim();
}
// System.out.println( line );
}
input.close();
}
catch (Exception err)
{
err.printStackTrace();
}
return "";
}
public long getVolumeSerialNumber()
{
Memory buffer = new Memory( MAX_PATH * 2); // Allocate space for Unicode (2 bytes per character)
// Call the GetSystemDirectoryW function (Unicode version)
// This function retrieves the path of the system directory, e.g., C:\Windows\System32
WinDef.UINT size = WinActivation.ManualKernel32.INSTANCE.GetSystemDirectoryW( buffer, MAX_PATH * 2 );
String rootPathName = buffer.getWideString( 0 );
rootPathName = rootPathName.substring( 0, rootPathName.indexOf( '\\' ) + 1 );
// Define buffers for the output
char[] volumeNameBuffer = new char[256];
char[] fileSystemNameBuffer = new char[256];
IntByReference volumeSerialNumber = new IntByReference();
IntByReference maximumComponentLength = new IntByReference();
IntByReference fileSystemFlags = new IntByReference();
if ( INSTANCE.GetVolumeInformation(
rootPathName,
volumeNameBuffer,
volumeNameBuffer.length,
volumeSerialNumber,
maximumComponentLength,
fileSystemFlags,
fileSystemNameBuffer,
fileSystemNameBuffer.length
))
{
return Integer.toUnsignedLong( volumeSerialNumber.getValue());
}
else
return -1;
}
protected String parsePrivateKey()
throws IOException, InterruptedException
{
Process process = Runtime.getRuntime().exec(
"reg query HKEY_CURRENT_USER\\Software\\Adobe\\Adept\\Device /s /v *" );
BufferedReader reader = new BufferedReader( new InputStreamReader( process.getInputStream() ) );
windowsUsername = Advapi32Util.getUserName();
String line = readNonBlank( reader );
if (null == line)
{
return null;
}
while (!line.contains( "key" ))
{
line = readNonBlank( reader );
if (null == line)
{
return null; // TODO: throw an exception instead
}
if (line.startsWith( "username" ))
{
windowsUsername = line.substring( (line.lastIndexOf( ' ' )) ).trim();
}
}
String key = line.substring( line.lastIndexOf( ' ' ) ).trim();
byte[] entropy;
byte[] protectedData = hexStringToByteArray( key );
try
{
entropy = hexStringToByteArray( constructEntropy() );
deviceKey = Crypt32Util.cryptUnprotectData( protectedData, entropy, 0, null );
// winDecrypt( protectedData, entropy ); // equivalent of devicesalt?
String privatekeystring = activation.getPrivateLicenseKey();
byte[] privateKey = Base64.getDecoder().decode( privatekeystring );
byte[] iv = new byte[16];
byte[] decrypted = CryptoUtils.aesDecrypt( deviceKey, iv, privateKey );
byte[] copy = new byte[decrypted.length - 26];
System.arraycopy( decrypted, 26, copy, 0, decrypted.length - 26 );
byte[] plk = buildPkcs8KeyFromPkcs1Key( copy );
String plkString = Base64.getEncoder().encodeToString( plk );
activation.setPrivateLicenseKey( plkString );
decryptedPrivateKey = Base64.getEncoder().encodeToString( copy );
// System.out.println( decryptedPrivateKey );
return decryptedPrivateKey;
}
catch (Exception e)
{
System.out.println( e.getMessage() );
}
finally
{
process.waitFor();
}
return null; // TODO: Throw exception instead.
}
private static byte[] buildPkcs8KeyFromPkcs1Key(byte[] innerKey)
{
var result = new byte[innerKey.length + 26];
System.arraycopy(Base64.getDecoder().decode("MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKY="), 0, result, 0, 26);
System.arraycopy( BigInteger.valueOf( result.length - 4).toByteArray(), 0, result, 2, 2);
System.arraycopy(BigInteger.valueOf(innerKey.length).toByteArray(), 0, result, 24, 2);
System.arraycopy(innerKey, 0, result, 26, innerKey.length);
return result;
}
@Override
protected void createDeviceKeyFile( Path deviceKeyFile )
{
try
{
Files.write( deviceKeyFile, deviceKey );
}
catch (IOException ioException)
{
throw new DeviceException( "Unable to write device key file", ioException );
}
}
// @Override
// public String getHobbes()
// {
// return AdeptProcessor.HOBBES_DEFAULT_VERSION;
// }
@Override
public String getClientOS()
{
return System.getProperty( "os.name" ) + " " + System.getProperty( "os.version" );
}
@Override
public String getClientLocale()
{
return Locale.getDefault().toString();
}
@Override
public String getDeviceClass()
{
return "Desktop";
}
@Override
public String getDeviceType()
{
return "standalone";
}
}