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:
231
src/main/java/common/WinActivation.java
Normal file
231
src/main/java/common/WinActivation.java
Normal 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();
|
||||
}
|
||||
}
|
||||
308
src/main/java/common/WinDevice.java
Normal file
308
src/main/java/common/WinDevice.java
Normal 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";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user