Move all win32 logic to wine executable

Actions like reading the registry and serial number were being done in python even though the final decryption was done in wine. This commit moves all windows logic except architecture detection into the exe ran under wine to simplify the architecture.
This commit is contained in:
melvyn2 2022-06-20 23:15:09 -07:00 committed by Florian Bach
parent 61a03fe988
commit 0cb13b9d38
3 changed files with 120 additions and 521 deletions

View File

@ -5,307 +5,7 @@
import sys, binascii
def unfuck(user):
# Wine uses a pretty nonstandard encoding in their registry file.
# I haven't found any existing Python implementation for that,
# so I looked at the C code and wrote my own.
# This implementation doesn't support multi-byte UTF-8 chars,
# but a standard-conforming Wine registry won't contain
# these anyways, so who cares.
hex_char_list = [48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 65, 66, 67, 68, 69, 70, 97, 98, 99, 100, 101, 102]
# Remove the quotation marks at beginning and end:
user = user.strip()[1:-1]
user_new = bytearray()
i = 0
while i < len(user):
# Convert string of len 1 to a byte
char = user[i][0].encode("latin-1")[0]
if sys.version_info[0] == 2:
char = ord(char)
if char == ord('\\'):
# Get next char:
i += 1
char = user[i][0].encode("latin-1")[0]
if sys.version_info[0] == 2:
char = ord(char)
if (char == ord('a')):
user_new.append(0x07)
elif (char == ord('b')):
user_new.append(0x08)
elif (char == ord('e')):
user_new.append(0x1b)
elif (char == ord('f')):
user_new.append(0x0c)
elif (char == ord('n')):
user_new.append(0x0a)
elif (char == ord('r')):
user_new.append(0x0d)
elif (char == ord('t')):
user_new.append(0x09)
elif (char == ord('v')):
user_new.append(0x0b)
elif (char == ord('x')):
# Get next char
i += 1
char = user[i][0].encode("latin-1")[0]
if sys.version_info[0] == 2:
char = ord(char)
if char not in hex_char_list:
user_new.append(ord('x'))
# This seems to be fallback code.
# Subtract 1 so the next char (the one that's not a hex char)
# is handled normally.
i -= 1
else:
ival = "" + chr(char)
# Read up to 3 more chars
next = user[i + 1][0].encode("latin-1")[0]
if sys.version_info[0] == 2:
next = ord(next)
if next in hex_char_list:
ival += chr(next)
i += 1
next = user[i + 1][0].encode("latin-1")[0]
if sys.version_info[0] == 2:
next = ord(next)
if next in hex_char_list:
ival += chr(next)
i += 1
next = user[i + 1][0].encode("latin-1")[0]
if sys.version_info[0] == 2:
next = ord(next)
if next in hex_char_list:
ival += chr(next)
i += 1
# ival now contains "00e9". Convert to int ...
ival = int(ival, 16)
# then drop everything except the lowest byte
ival = ival & 0xFF
# then add it to the array
user_new.append(ival)
elif (char >= ord('0') and char <= ord('9') ):
octal = char - ord('0')
# Read up to 2 more chars
next = user[i + 1][0].encode("latin-1")[0]
if sys.version_info[0] == 2:
next = ord(next)
if next >= ord('0') and next <= ord('9'):
octal = (octal * 8) + (next - ord('0'))
i += 1
next = user[i + 1][0].encode("latin-1")[0]
if sys.version_info[0] == 2:
next = ord(next)
if next >= ord('0') and next <= ord('9'):
octal = (octal * 8) + (next - ord('0'))
i += 1
else:
if (char < 0x80):
user_new.append(char)
else:
print("Multi-Byte UTF-8, not supported")
print("This should never happen in a standard-conform Wine registry ...")
return False
# Parse next char
i += 1
return user_new
def GetMasterKey(path_to_wine_prefix):
import os
if os.name == 'nt':
print("Hey! This is for Linux!")
return
verbose_logging = False
try:
import calibre_plugins.deacsm.prefs as prefs
deacsmprefs = prefs.DeACSM_Prefs()
verbose_logging = deacsmprefs["detailed_logging"]
except:
pass
import cpuid
import struct
try:
# Linux / Wine code just assumes that the system drive is C:\
serial_file = open(os.path.join(path_to_wine_prefix, "drive_c", ".windows-serial"), "r")
serial = serial_file.read()
serial_file.close()
serial = int(serial, 16)
except:
# If this file is not present, Wine will usually use a default serial number of "0".
# There are some edge cases where Wine uses a different serial number even when that
# .windows-serial file is not present.
serial = 0
if (verbose_logging):
print("Serial: {}".format(serial))
cpu = cpuid.CPUID()
_, b, c, d = cpu(0)
vendor = struct.pack("III", b, d, c)
if (verbose_logging):
print("Vendor: {}".format(vendor))
signature, _, _, _ = cpu(1)
signature = struct.pack('>I', signature)[1:]
if (verbose_logging):
print("Signature: {}".format(binascii.hexlify(signature)))
# Search for the username in the registry:
user = None
# Linux - loop through the Wine registry file to find the "username" attribute
try:
registry_file = open(os.path.join(path_to_wine_prefix, "user.reg"))
waiting_for_username = False
while True:
line = registry_file.readline()
if not line:
break
if waiting_for_username:
if (not line.lower().startswith("\"username\"=")):
continue
# If we end up here, we have the username.
user = line.split('=', 1)[1].strip()
user = unfuck(user)
break
else:
if (line.startswith("[Software\\\\Adobe\\\\Adept\\\\Device]")):
waiting_for_username = True
if (line.startswith("[Volatile Environment]")):
waiting_for_username = True
registry_file.close()
except:
# There was an error hunting through the registry.
raise
pass
if (user is None):
print("Error while determining username ...")
exit()
# Comes as bytearray
if sys.version_info[0] == 3:
user = bytes(user)
else:
user = str(user)
if verbose_logging:
print("Username: {}".format(user))
# Find the value we want to decrypt from the registry. loop through the Wine registry file to find the "key" attribute
try:
registry_file = open(os.path.join(path_to_wine_prefix, "user.reg"))
waiting_for_key = False
key_line = None
while True:
line = registry_file.readline()
if not line:
break
if waiting_for_key:
if (not line.lower().startswith("\"key\"=")):
continue
# If we end up here, we have the key.
key_line = line
while (key_line.strip().endswith('\\')):
key_line = key_line.strip()[:-1] + registry_file.readline()
# Now parse ...
key_line = key_line.split(':', 1)[1]
key_line = key_line.replace('\t', '').replace('\r', '').replace('\n', '').replace(' ', '').replace(',', '')
key_line = binascii.unhexlify(key_line)
else:
if (line.startswith("[Software\\\\Adobe\\\\Adept\\\\Device]")):
waiting_for_key = True
registry_file.close()
except:
# There was an error hunting through the registry.
raise
pass
if key_line is None:
print("No ADE activation found ...")
return None
if verbose_logging:
print("Encrypted key: {}".format(binascii.hexlify(key_line)))
# These should all be "bytes" (Py3) or "str" (Py2)
# print(type(vendor))
# print(type(signature))
# print(type(user))
entropy = struct.pack('>I12s3s13s', serial, vendor, signature, user)
if verbose_logging:
print("Entropy: {}".format(binascii.hexlify(entropy)))
# We would now call CryptUnprotectData to decrypt the stuff,
# but unfortunately there's no working Linux implementation
# for that.
#
# The plan was to handle everything in Python so we don't have
# to interact with Wine - that's why we're doing all the registry
# handling ourselves.
# Unfortunately, that doesn't work for the actual decryption.
#
# This means we have to call a Windows binary through
# Wine just for this one single decryption call ...
success, data = CryptUnprotectDataExecuteWine(path_to_wine_prefix, key_line, entropy)
if (success):
keykey = data
if verbose_logging:
print("Key: {}".format(binascii.hexlify(keykey)))
return keykey
else:
print("Error number: {}".format(data))
if data == 13: # WINError ERROR_INVALID_DATA
print("Could not decrypt data with the given key. Did the entropy change?")
return None
def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
def GetMasterKey(wineprefix):
import subprocess, os, re
verbose_logging = False
@ -319,7 +19,7 @@ def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
print("Asking WINE to decrypt encrypted key for us ...")
if wineprefix == "" or not os.path.exists(wineprefix):
print("Wineprefix not found!!")
print("Wineprefix not found!")
return None
@ -334,27 +34,21 @@ def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
if not line:
break
stuff = re.match(r'#arch=(win32|win64)', line)
if (stuff):
winearch = stuff.groups()[0]
archkey = re.match(r'#arch=(win32|win64)', line)
if (archkey):
winearch = archkey.groups()[0]
break
regfile.close()
except:
pass
# Execute!
env_dict = os.environ
env_dict["PYTHONPATH"] = ""
env_dict["WINEPREFIX"] = wineprefix
#env_dict["WINEDEBUG"] = "-all,+crypt"
env_dict["WINEDEBUG"] = "+err,+fixme"
# Use environment variables to get the input data to the application.
env_dict["X_DECRYPT_DATA"] = binascii.hexlify(data).decode("utf-8")
env_dict["X_DECRYPT_ENTROPY"] = binascii.hexlify(entropy).decode("utf-8")
try:
from calibre.utils.config import config_dir
pluginsdir = os.path.join(config_dir,"plugins")
@ -366,37 +60,26 @@ def CryptUnprotectDataExecuteWine(wineprefix, data, entropy):
# calls decrypt_win32.exe or decrypt_win64.exe
proc = subprocess.Popen(["wine", "decrypt_" + winearch + ".exe"], shell=False, cwd=moddir, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
prog_output, prog_stderr = proc.communicate()
stdout = prog_output.decode("utf-8")
stderr = prog_stderr.decode("utf-8")
prog_stdout, prog_stderr = proc.communicate()
if stdout.startswith("PROGOUTPUT:0:"):
key_string = stdout.split(':')[2]
if verbose_logging:
print("Stderr log:\n{}".format(prog_stderr.decode("utf-8")))
print("Stdout log: {}".format(prog_stdout.decode("utf-8")))
print("Exit code: {}".format(proc.returncode))
if proc.returncode == 0:
if verbose_logging:
print("Successfully got encryption key from WINE: {}".format(key_string))
print("Successfully got encryption key from WINE: {}".format(prog_stdout.decode("utf-8")))
else:
print("Successfully got encryption key from WINE.")
master_key = binascii.unhexlify(key_string)
return True, master_key
master_key = binascii.unhexlify(prog_stdout)
return master_key
else:
print("Huh. That didn't work. ")
try:
err = int(stdout.split(':')[1])
if err == -4:
err = int(stdout.split(':')[2])
new_serial = int(stdout.split(':')[3])
if verbose_logging:
print("New serial: {}".format(new_serial))
except:
err = None
print("Failed to extract encryption key from WINE.")
print("Exit code: {}".format(proc.returncode))
if verbose_logging:
# print("Stderr log:\n{}".format(stderr))
print("Program output: {}".format(stdout))
return False, err
return None
if __name__ == "__main__":

View File

@ -5,10 +5,10 @@ clean:
rm decrypt_win32.exe decrypt_win64.exe 2>/dev/null || /bin/true
decrypt_win32.exe: main.c Makefile
i686-w64-mingw32-gcc main.c -Os -o decrypt_win32.exe -lcrypt32
i686-w64-mingw32-gcc main.c -Os -o decrypt_win32.exe -lcrypt32 -lwsock32
i686-w64-mingw32-strip decrypt_win32.exe
decrypt_win64.exe: main.c Makefile
x86_64-w64-mingw32-gcc main.c -Os -o decrypt_win64.exe -lcrypt32
x86_64-w64-mingw32-gcc main.c -Os -o decrypt_win64.exe -lcrypt32 -lwsock32
x86_64-w64-mingw32-strip decrypt_win64.exe

View File

@ -1,196 +1,112 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <wincrypt.h>
#include <dpapi.h>
#include <fileapi.h>
#include <direct.h>
#include <cpuid.h>
#include <intsafe.h>
#ifdef DEBUG
#undef DEBUG
#endif
union CPUIDVendor {
unsigned int reg[3];
char vendor[13];
};
int char2int(char input) {
if (input >= '0' && input <= '9')
return input - '0';
if (input >= 'A' && input <= 'F')
return input - 'A' + 10;
if (input >= 'a' && input <= 'f')
return input - 'a' + 10;
fputs("PROGOUTPUT:-3", stdout);
exit(-3);
}
void hex2bin(const char * src, char * dst) {
while (*src && src[1]) {
*(dst++) = char2int(*src) * 16 + char2int(src[1]);
src += 2;
}
}
#ifdef DEBUG
void hexDump (
const char * desc,
const void * addr,
const int len,
int perLine
) {
// Silently ignore silly per-line values.
if (perLine < 4 || perLine > 64) perLine = 16;
int i;
unsigned char buff[perLine+1];
const unsigned char * pc = (const unsigned char *)addr;
// Output description if given.
if (desc != NULL) fprintf (stderr, "%s:\n", desc);
// Length checks.
if (len == 0) {
fprintf(stderr, " ZERO LENGTH\n");
return;
}
if (len < 0) {
fprintf(stderr, " NEGATIVE LENGTH: %d\n", len);
return;
}
// Process every byte in the data.
for (i = 0; i < len; i++) {
// Multiple of perLine means new or first line (with line offset).
if ((i % perLine) == 0) {
// Only print previous-line ASCII buffer for lines beyond first.
if (i != 0) fprintf (stderr, " %s\n", buff);
// Output the offset of current line.
fprintf (stderr, " %04x ", i);
}
// Now the hex code for the specific character.
fprintf (stderr, " %02x", pc[i]);
// And buffer a printable ASCII character for later.
if ((pc[i] < 0x20) || (pc[i] > 0x7e)) // isprint() may be better.
buff[i % perLine] = '.';
else
buff[i % perLine] = pc[i];
buff[(i % perLine) + 1] = '\0';
}
// Pad out last line if not exactly perLine characters.
while ((i % perLine) != 0) {
fprintf (stderr, " ");
i++;
}
// And print the final ASCII buffer.
fprintf (stderr, " %s\n", buff);
}
#endif
int get_serial() {
DWORD serial = 0;
int retval = GetVolumeInformation("c:\\\\", NULL, 0, &serial, NULL, NULL, NULL, 0);
if (retval == 0) {
fprintf(stderr, "Error with GetVolumeInformation: %d\n", GetLastError());
return 0;
}
return serial;
}
struct EncEntropy {
unsigned int serial;
char vendor[12];
char signature[3];
char user[13];
};
int main() {
char * var_data = "X_DECRYPT_DATA";
char * var_entropy = "X_DECRYPT_ENTROPY";
char * data_hex = getenv(var_data);
char * entropy_hex = getenv(var_entropy);
if (data_hex == NULL || entropy_hex == NULL) {
fputs("PROGOUTPUT:-1", stdout);
exit(-1);
// Get disk serial
DWORD serial;
if (GetVolumeInformation("c:\\\\", NULL, 0, &serial, NULL, NULL, NULL, 0) == 0) {
DWORD err = GetLastError();
fprintf(stderr, "Error with GetVolumeInformation: %ld\n", err);
return err;
}
DWORD be_serial = htonl(serial);
fprintf(stderr, "Disk serial (hex): %08lx\n", serial);
char * data_bytes = malloc((strlen(data_hex) / 2));
char * entropy_bytes = malloc((strlen(entropy_hex) / 2));
if (data_bytes == NULL || entropy_bytes == NULL) {
fputs("PROGOUTPUT:-2", stdout);
exit(-2);
unsigned int eax, ebx, ecx, edx;
// Get CPUID vendor string
union CPUIDVendor cpu_vendor;
if (__get_cpuid(0, &eax, &cpu_vendor.reg[0], &cpu_vendor.reg[2], &cpu_vendor.reg[1]) == 0) {
fprintf(stderr, "Error: cpuid(0) not supported");
return 1;
}
hex2bin(data_hex, data_bytes);
hex2bin(entropy_hex, entropy_bytes);
#ifdef DEBUG
hexDump("data", data_bytes, strlen(data_hex)/2, 16);
hexDump("entropy", entropy_bytes, strlen(entropy_hex)/2, 16);
#endif
DATA_BLOB input_data;
DATA_BLOB entropy_data;
DATA_BLOB output_data;
input_data.pbData = data_bytes;
input_data.cbData = strlen(data_hex)/2;
entropy_data.pbData = entropy_bytes;
entropy_data.cbData = strlen(entropy_hex)/2;
int ret = CryptUnprotectData(
&input_data,
NULL,
&entropy_data,
NULL,
NULL,
0,
&output_data);
if (ret) {
if (output_data.cbData != 16) {
printf("PROGOUTPUT:-5:%d", output_data.cbData);
exit(-5);
cpu_vendor.vendor[12] = '\0';
fprintf(stderr, "CPUID Vendor: %s\n", cpu_vendor.vendor);
// Get CPUID "signature" (eax of CPUID(1))
unsigned int signature;
if (__get_cpuid(1, &signature, &ebx, &ecx, &edx) == 0) {
fprintf(stderr, "Error: cpuid(1) not supported");
return 1;
}
// Success! Return decrypted data
fputs("PROGOUTPUT:0:", stdout);
unsigned int be_signature = htonl(signature);
fprintf(stderr, "CPUID Signature (hex): %08x\n", signature);
// Get windows user
#define USERBUFSIZE 512
TCHAR user[USERBUFSIZE];
memset(&user, 0, sizeof(user)); // GetUserName only sets bytes as needed for length of username, but we need null bytes to fill the rest
DWORD bufsize = USERBUFSIZE ;
if (GetUserName(user, &bufsize) == 0) {
DWORD err = GetLastError();
fprintf(stderr, "Error with GetUserName: %ld\n", err);
return err;
}
fprintf(stderr, "Username: %s\n", user);
// Get Encrypted adobe key
#define KEYBUFSIZE 180 // As measured
BYTE key[KEYBUFSIZE];
DWORD regkeysize = KEYBUFSIZE;
LSTATUS retval = RegGetValue(HKEY_CURRENT_USER, "Software\\Adobe\\Adept\\Device", "key", RRF_RT_REG_BINARY, NULL, &key, &regkeysize);
if (retval != ERROR_SUCCESS) {
fprintf(stderr, "Error with RegGetValue: %ld\n", retval);
fprintf(stderr, "regkeysize: %ld\n", regkeysize);
return retval;
}
fprintf(stderr, "Encrypted key (hex): ");
for (size_t i = 0; i < KEYBUFSIZE; i++ )
{
fprintf(stderr, "%02x", key[i]);
}
fprintf(stderr, "\n");
// Assemble "entropy" (passphrase) for key
struct EncEntropy entropy;
memcpy(&entropy.serial, &be_serial, sizeof(entropy.serial));
memcpy(&entropy.vendor, &cpu_vendor.vendor, sizeof(entropy.vendor));
memcpy(&entropy.signature, ((char*)(&be_signature))+1, sizeof(entropy.signature)); // Only the last 3 bytes are needed, hence the +1 ptr
memcpy(&entropy.user, &user, sizeof(entropy.user));
// Print entropy byte by byte in hex
fprintf(stderr, "Entropy: ");
for (size_t i = 0; i < sizeof(entropy); i++ )
{
fprintf(stderr, "%02x", ((unsigned char*)&entropy)[i]);
}
fprintf(stderr, "\n");
// Run decryption API
DATA_BLOB ciphertext_data, entropy_data, plaintext_data;
ciphertext_data.pbData = key;
ciphertext_data.cbData = sizeof(key);
entropy_data.pbData = (BYTE*)(&entropy);
entropy_data.cbData = sizeof(entropy);
if (CryptUnprotectData(&ciphertext_data, NULL, &entropy_data, NULL, NULL, 0, &plaintext_data) != TRUE) {
DWORD err = GetLastError();
fprintf(stderr, "Error with CryptUnprotectData: %ld\n", err);
return err;
}
fprintf(stderr, "Decrypted key length: %lu\n", plaintext_data.cbData);
// Print decrypted key to stdout
for (int i = 0; i < 16; i++) {
printf("%02x", output_data.pbData[i]);
printf("%02x", plaintext_data.pbData[i]);
}
exit(0);
}
else {
// Apparently Wine has issues with the volume serial code sometimes
// so the code on the Linux side detects the wrong serial number.
// Thus, if the decryption fails, we read the serial number that Wine
// (and ADE) sees back to the Linux side for another attempt.
int err = GetLastError();
printf("PROGOUTPUT:-4:%d:%08x", err, get_serial());
exit(-4);
}
}