1
0
mirror of https://github.com/veracrypt/VeraCrypt.git synced 2025-11-11 11:08:02 -06:00

Windows: First implementation of non-system volumes decryption.

This commit is contained in:
Mounir IDRASSI
2015-05-17 12:14:58 +02:00
parent 4695920b41
commit f72125ea71
16 changed files with 1557 additions and 116 deletions

View File

@@ -40,6 +40,18 @@ IMPORTANT: Due to this issue, functions in this file must not directly interact
using namespace std;
using namespace VeraCrypt;
#if TC_VOLUME_DATA_OFFSET != 131072
# error TC_VOLUME_DATA_OFFSET != 131072
#endif
#if TC_VOLUME_HEADER_EFFECTIVE_SIZE != 512
# error TC_VOLUME_HEADER_EFFECTIVE_SIZE != 512
#endif
#if TC_TOTAL_VOLUME_HEADERS_SIZE != 262144
# error TC_TOTAL_VOLUME_HEADERS_SIZE != 262144
#endif
#define TC_MAX_NONSYS_INPLACE_ENC_WORK_CHUNK_SIZE (2048 * BYTES_PER_KB)
#define TC_INITIAL_NTFS_CONCEAL_PORTION_SIZE (2 * TC_MAX_VOLUME_SECTOR_SIZE)
#define TC_NTFS_CONCEAL_CONSTANT 0xFF
@@ -312,6 +324,42 @@ BOOL CheckRequirementsForNonSysInPlaceEnc (HWND hwndDlg, const char *devicePath,
return TRUE;
}
BOOL CheckRequirementsForNonSysInPlaceDec (HWND hwndDlg, const char *devicePath, BOOL silent)
{
int partitionNumber = -1, driveNumber = -1;
/* ---------- Checks that do not require admin rights ----------- */
/* Volume type (must be a partition or a dynamic volume) */
if ((sscanf (devicePath, "\\Device\\HarddiskVolume%d", &partitionNumber) != 1
&& sscanf (devicePath, "\\Device\\Harddisk%d\\Partition%d", &driveNumber, &partitionNumber) != 2)
|| partitionNumber == 0)
{
if (!silent)
Error ("INPLACE_ENC_INVALID_PATH", hwndDlg);
return FALSE;
}
/* Admin rights */
if (!IsAdmin())
{
// We rely on the wizard process to call us only when the whole wizard process has been elevated (so UAC
// status can be ignored). In case the IsAdmin() detection somehow fails, we allow the user to continue.
if (!silent)
Warning ("ADMIN_PRIVILEGES_WARN_DEVICES", hwndDlg);
}
/* ---------- Checks that may require admin rights ----------- */
// [Currently none]
return TRUE;
}
int EncryptPartitionInPlaceBegin (volatile FORMAT_VOL_PARAMETERS *volParams, volatile HANDLE *outHandle, WipeAlgorithmId wipeAlgorithm)
{
@@ -606,7 +654,7 @@ int EncryptPartitionInPlaceBegin (volatile FORMAT_VOL_PARAMETERS *volParams, vol
// In the config file, increase the number of partitions where in-place encryption is in progress
SaveNonSysInPlaceEncSettings (1, wipeAlgorithm);
SaveNonSysInPlaceEncSettings (1, wipeAlgorithm, FALSE);
// Add the wizard to the system startup sequence if appropriate
@@ -1033,7 +1081,7 @@ inplace_enc_read:
// Update the configuration files
SaveNonSysInPlaceEncSettings (-1, wipeAlgorithm);
SaveNonSysInPlaceEncSettings (-1, wipeAlgorithm, FALSE);
@@ -1126,6 +1174,513 @@ closing_seq:
return nStatus;
}
int DecryptPartitionInPlace (volatile FORMAT_VOL_PARAMETERS *volParams, volatile BOOL *DiscardUnreadableEncryptedSectors)
{
HANDLE dev = INVALID_HANDLE_VALUE;
PCRYPTO_INFO masterCryptoInfo = NULL, headerCryptoInfo = NULL;
UINT64_STRUCT unitNo;
char *buf = NULL;
byte *tmpSectorBuf = NULL;
char dosDev[TC_MAX_PATH] = {0};
char devName[MAX_PATH] = {0};
WCHAR deviceName[MAX_PATH];
int nStatus = ERR_SUCCESS;
__int64 deviceSize;
uint64 remainingBytes, workChunkStartByteOffset, lastHeaderUpdateDistance = 0, skippedBadSectorCount = 0;
uint32 workChunkSize;
DWORD dwError, dwResult;
BOOL bPause = FALSE, bEncryptedAreaSizeChanged = FALSE;
LARGE_INTEGER offset;
int sectorSize;
int i;
DWORD n;
char *devicePath = volParams->volumePath;
Password *password = volParams->password;
HWND hwndDlg = volParams->hwndDlg;
int pkcs5_prf = volParams->pkcs5;
DISK_GEOMETRY driveGeometry;
buf = (char *) TCalloc (TC_MAX_NONSYS_INPLACE_ENC_WORK_CHUNK_SIZE);
if (!buf)
{
nStatus = ERR_OUTOFMEMORY;
goto closing_seq;
}
headerCryptoInfo = crypto_open();
if (headerCryptoInfo == NULL)
{
nStatus = ERR_OUTOFMEMORY;
goto closing_seq;
}
deviceSize = GetDeviceSize (devicePath);
if (deviceSize < 0)
{
// Cannot determine the size of the partition
nStatus = ERR_OS_ERROR;
goto closing_seq;
}
// The wizard should have dismounted the TC volume if it was mounted, but for extra safety we will check this again.
if (IsMountedVolume (devicePath))
{
int driveLetter = GetMountedVolumeDriveNo (devicePath);
if (driveLetter == -1
|| !UnmountVolume (hwndDlg, driveLetter, TRUE))
{
handleWin32Error (hwndDlg);
AbortProcess ("CANT_DISMOUNT_VOLUME");
}
}
StringCbCopyA ((char *)deviceName, sizeof(deviceName), devicePath);
ToUNICODE ((char *)deviceName, sizeof(deviceName));
if (FakeDosNameForDevice (devicePath, dosDev, sizeof(dosDev), devName, sizeof(devName), FALSE) != 0)
{
nStatus = ERR_OS_ERROR;
goto closing_seq;
}
dev = OpenPartitionVolume (hwndDlg, devName,
TRUE, // Require exclusive access
FALSE, // Do not require shared access
TRUE, // Ask the user to confirm shared access (if exclusive fails)
FALSE, // Do not append alternative instructions how to encrypt the data (to applicable error messages)
FALSE); // Non-silent mode
if (dev == INVALID_HANDLE_VALUE)
{
nStatus = ERR_DONT_REPORT;
goto closing_seq;
}
// This should never be needed, but is still performed for extra safety (without checking the result)
DeviceIoControl (dev,
FSCTL_ALLOW_EXTENDED_DASD_IO,
NULL,
0,
NULL,
0,
&dwResult,
NULL);
if (!DeviceIoControl (dev, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &driveGeometry, sizeof (driveGeometry), &dwResult, NULL))
{
nStatus = ERR_OS_ERROR;
goto closing_seq;
}
sectorSize = driveGeometry.BytesPerSector;
tmpSectorBuf = (byte *) TCalloc (sectorSize);
if (!tmpSectorBuf)
{
nStatus = ERR_OUTOFMEMORY;
goto closing_seq;
}
nStatus = OpenBackupHeader (dev, devicePath, password, pkcs5_prf, &masterCryptoInfo, headerCryptoInfo, deviceSize);
if (nStatus != ERR_SUCCESS)
goto closing_seq;
if (masterCryptoInfo->LegacyVolume)
{
Error ("NONSYS_INPLACE_DECRYPTION_BAD_VOL_FORMAT", hwndDlg);
nStatus = ERR_DONT_REPORT;
goto closing_seq;
}
if (masterCryptoInfo->hiddenVolume)
{
Error ("NONSYS_INPLACE_DECRYPTION_CANT_DECRYPT_HID_VOL", hwndDlg);
nStatus = ERR_DONT_REPORT;
goto closing_seq;
}
if (!bInPlaceEncNonSysResumed
&& masterCryptoInfo->VolumeSize.Value == masterCryptoInfo->EncryptedAreaLength.Value)
{
/* Decryption started (not resumed) */
if ((masterCryptoInfo->HeaderFlags & TC_HEADER_FLAG_NONSYS_INPLACE_ENC) == 0)
{
// The volume has not been encrypted in-place so it may contain a hidden volume.
// Ask the user to confirm it does not.
char *tmpStr[] = {0,
"CONFIRM_VOL_CONTAINS_NO_HIDDEN_VOL",
"VOL_CONTAINS_NO_HIDDEN_VOL",
"VOL_CONTAINS_A_HIDDEN_VOL",
0};
switch (AskMultiChoice ((void **) tmpStr, FALSE, hwndDlg))
{
case 1:
// NOP
break;
case 2:
default:
// Cancel
nStatus = ERR_DONT_REPORT;
goto closing_seq;
}
}
// Update config files and app data
// In the config file, increase the number of partitions where in-place decryption is in progress
SaveNonSysInPlaceEncSettings (1, TC_WIPE_NONE, TRUE);
// Add the wizard to the system startup sequence if appropriate
if (!IsNonInstallMode ())
ManageStartupSeqWiz (FALSE, "/prinplace");
}
bInPlaceEncNonSysResumed = TRUE;
bFirstNonSysInPlaceEncResumeDone = TRUE;
remainingBytes = masterCryptoInfo->EncryptedAreaLength.Value;
lastHeaderUpdateDistance = 0;
ExportProgressStats (masterCryptoInfo->EncryptedAreaLength.Value, masterCryptoInfo->VolumeSize.Value);
SetNonSysInplaceEncUIStatus (NONSYS_INPLACE_ENC_STATUS_DECRYPTING);
/* The in-place decryption core */
while (remainingBytes > 0)
{
workChunkSize = (uint32) min (remainingBytes, TC_MAX_NONSYS_INPLACE_ENC_WORK_CHUNK_SIZE);
if (workChunkSize % ENCRYPTION_DATA_UNIT_SIZE != 0)
{
nStatus = ERR_PARAMETER_INCORRECT;
goto closing_seq;
}
workChunkStartByteOffset = masterCryptoInfo->EncryptedAreaStart.Value;
unitNo.Value = workChunkStartByteOffset / ENCRYPTION_DATA_UNIT_SIZE;
// Read the ciphertext into RAM
offset.QuadPart = workChunkStartByteOffset;
if (SetFilePointerEx (dev, offset, NULL, FILE_BEGIN) == 0)
{
nStatus = ERR_OS_ERROR;
goto closing_seq;
}
if (ReadFile (dev, buf, workChunkSize, &n, NULL) == 0)
{
// Read error
DWORD dwTmpErr = GetLastError ();
if (IsDiskReadError (dwTmpErr) && !bVolTransformThreadCancel)
{
// Physical defect or data corruption
if (!*DiscardUnreadableEncryptedSectors)
{
*DiscardUnreadableEncryptedSectors = (AskWarnYesNo ("DISCARD_UNREADABLE_ENCRYPTED_SECTORS", hwndDlg) == IDYES);
}
if (*DiscardUnreadableEncryptedSectors)
{
// Read the work chunk again, but this time each sector individually and skiping each bad sector
LARGE_INTEGER tmpSectorOffset;
uint64 tmpSectorCount;
uint64 tmpBufOffset = 0;
DWORD tmpNbrReadBytes = 0;
tmpSectorOffset.QuadPart = offset.QuadPart;
for (tmpSectorCount = workChunkSize / sectorSize; tmpSectorCount > 0; --tmpSectorCount)
{
if (SetFilePointerEx (dev, tmpSectorOffset, NULL, FILE_BEGIN) == 0)
{
nStatus = ERR_OS_ERROR;
goto closing_seq;
}
if (ReadFile (dev, tmpSectorBuf, sectorSize, &tmpNbrReadBytes, NULL) == 0
|| tmpNbrReadBytes != (DWORD) sectorSize)
{
// Read error
// Clear the buffer so the content of each unreadable sector is replaced with decrypted all-zero blocks (producing pseudorandom data)
memset (tmpSectorBuf, 0, sectorSize);
skippedBadSectorCount++;
}
memcpy (buf + tmpBufOffset, tmpSectorBuf, sectorSize);
tmpSectorOffset.QuadPart += sectorSize;
tmpBufOffset += sectorSize;
}
}
else
{
SetLastError (dwTmpErr); // Preserve the original error code
nStatus = ERR_OS_ERROR;
goto closing_seq;
}
}
else
{
SetLastError (dwTmpErr); // Preserve the original error code
nStatus = ERR_OS_ERROR;
goto closing_seq;
}
}
// Decrypt the ciphertext in RAM
DecryptDataUnits ((byte *) buf, &unitNo, workChunkSize / ENCRYPTION_DATA_UNIT_SIZE, masterCryptoInfo);
// Conceal initial portion of the filesystem
if (workChunkStartByteOffset - TC_VOLUME_DATA_OFFSET < TC_INITIAL_NTFS_CONCEAL_PORTION_SIZE)
{
// We are decrypting the initial TC_INITIAL_NTFS_CONCEAL_PORTION_SIZE bytes of the filesystem. We will
// conceal this portion to prevent Windows and applications from interfering with the volume.
for (i = 0; i < min (TC_INITIAL_NTFS_CONCEAL_PORTION_SIZE, workChunkStartByteOffset - TC_VOLUME_DATA_OFFSET + workChunkSize); i++)
buf[i] ^= TC_NTFS_CONCEAL_CONSTANT;
}
// Write the plaintext
offset.QuadPart = workChunkStartByteOffset - TC_VOLUME_DATA_OFFSET;
if (SetFilePointerEx (dev, offset, NULL, FILE_BEGIN) == 0)
{
nStatus = ERR_OS_ERROR;
goto closing_seq;
}
if (WriteFile (dev, buf, workChunkSize, &n, NULL) == 0)
{
// Write error
nStatus = ERR_OS_ERROR;
goto closing_seq;
}
masterCryptoInfo->EncryptedAreaStart.Value += workChunkSize;
masterCryptoInfo->EncryptedAreaLength.Value -= workChunkSize;
remainingBytes -= workChunkSize;
lastHeaderUpdateDistance += workChunkSize;
bEncryptedAreaSizeChanged = TRUE;
if (lastHeaderUpdateDistance >= TC_NONSYS_INPLACE_ENC_HEADER_UPDATE_INTERVAL)
{
nStatus = FastVolumeHeaderUpdate (dev, headerCryptoInfo, masterCryptoInfo, deviceSize);
if (nStatus != ERR_SUCCESS)
{
// Possible write error
goto closing_seq;
}
lastHeaderUpdateDistance = 0;
}
ExportProgressStats (masterCryptoInfo->EncryptedAreaLength.Value, masterCryptoInfo->VolumeSize.Value);
if (bVolTransformThreadCancel)
{
bPause = TRUE;
break;
}
}
nStatus = FastVolumeHeaderUpdate (dev, headerCryptoInfo, masterCryptoInfo, deviceSize);
if (nStatus != ERR_SUCCESS)
{
// Possible write error
goto closing_seq;
}
if (!bPause)
{
/* Volume has been fully decrypted. */
// Prevent attempts to update volume header during the closing sequence
bEncryptedAreaSizeChanged = FALSE;
SetNonSysInplaceEncUIStatus (NONSYS_INPLACE_ENC_STATUS_FINALIZING);
/* Undo concealing of the filesystem */
nStatus = ConcealNTFS (dev);
if (nStatus != ERR_SUCCESS)
goto closing_seq;
/* Ovewrite the backup header and the remaining ciphertext with all-zero blocks (the primary header was overwritten with the decrypted data). */
memset (tmpSectorBuf, 0, sectorSize);
for (offset.QuadPart = masterCryptoInfo->VolumeSize.Value;
offset.QuadPart <= deviceSize - sectorSize;
offset.QuadPart += sectorSize)
{
if (SetFilePointerEx (dev, offset, NULL, FILE_BEGIN) == 0)
{
nStatus = ERR_OS_ERROR;
goto closing_seq;
}
if (WriteFile (dev, tmpSectorBuf, sectorSize, &n, NULL) == 0)
{
// Write error
dwError = GetLastError();
SetLastError (dwError);
nStatus = ERR_OS_ERROR;
goto closing_seq;
}
}
/* Update the configuration files */
SaveNonSysInPlaceEncSettings (-1, TC_WIPE_NONE, TRUE);
SetNonSysInplaceEncUIStatus (NONSYS_INPLACE_ENC_STATUS_FINISHED);
nStatus = ERR_SUCCESS;
}
else
{
// The process has been paused by the user or aborted by the wizard (e.g. on app exit)
nStatus = ERR_USER_ABORT;
SetNonSysInplaceEncUIStatus (NONSYS_INPLACE_ENC_STATUS_PAUSED);
}
if (dev != INVALID_HANDLE_VALUE)
{
CloseHandle (dev);
dev = INVALID_HANDLE_VALUE;
}
closing_seq:
dwError = GetLastError();
if (bEncryptedAreaSizeChanged
&& dev != INVALID_HANDLE_VALUE
&& masterCryptoInfo != NULL
&& headerCryptoInfo != NULL
&& deviceSize > 0)
{
// Execution of the core loop may have been interrupted due to an error or user action without updating the header
FastVolumeHeaderUpdate (dev, headerCryptoInfo, masterCryptoInfo, deviceSize);
}
if (dev != INVALID_HANDLE_VALUE)
{
CloseHandle (dev);
dev = INVALID_HANDLE_VALUE;
}
if (masterCryptoInfo != NULL)
{
crypto_close (masterCryptoInfo);
masterCryptoInfo = NULL;
}
if (headerCryptoInfo != NULL)
{
crypto_close (headerCryptoInfo);
headerCryptoInfo = NULL;
}
if (dosDev[0])
RemoveFakeDosName (devicePath, dosDev);
if (buf != NULL)
{
TCfree (buf);
buf = NULL;
}
if (tmpSectorBuf != NULL)
{
TCfree (tmpSectorBuf);
tmpSectorBuf = NULL;
}
if (skippedBadSectorCount > 0)
{
wchar_t msg[30000] = {0};
wchar_t sizeStr[500] = {0};
GetSizeString (skippedBadSectorCount * sectorSize, sizeStr, sizeof(sizeStr));
StringCbPrintfW (msg, sizeof(msg),
GetString ("SKIPPED_BAD_SECTOR_COUNT"),
skippedBadSectorCount,
sizeStr);
WarningDirect (msg, hwndDlg);
}
if (nStatus != ERR_SUCCESS && nStatus != ERR_USER_ABORT)
SetLastError (dwError);
return nStatus;
}
int FastVolumeHeaderUpdate (HANDLE dev, CRYPTO_INFO *headerCryptoInfo, CRYPTO_INFO *masterCryptoInfo, __int64 deviceSize)
{
@@ -1168,6 +1723,13 @@ int FastVolumeHeaderUpdate (HANDLE dev, CRYPTO_INFO *headerCryptoInfo, CRYPTO_IN
mputInt64 (fieldPos, (masterCryptoInfo->EncryptedAreaStart.Value));
mputInt64 (fieldPos, (masterCryptoInfo->EncryptedAreaLength.Value));
// We need to ensure the TC_HEADER_FLAG_NONSYS_INPLACE_ENC flag bit is set, because if volumes created by TC-format
// were decrypted in place, it would be possible to mount them partially encrypted and it wouldn't be possible
// to resume interrupted decryption after the wizard exits.
masterCryptoInfo->HeaderFlags |= TC_HEADER_FLAG_NONSYS_INPLACE_ENC;
fieldPos = (byte *) header + TC_HEADER_OFFSET_FLAGS;
mputLong (fieldPos, (masterCryptoInfo->HeaderFlags));
headerCrc32 = GetCrc32 (header + TC_HEADER_OFFSET_MAGIC, TC_HEADER_OFFSET_HEADER_CRC - TC_HEADER_OFFSET_MAGIC);
fieldPos = (byte *) header + TC_HEADER_OFFSET_HEADER_CRC;
@@ -1419,7 +1981,7 @@ void SetNonSysInplaceEncUIStatus (int nonSysInplaceEncStatus)
}
BOOL SaveNonSysInPlaceEncSettings (int delta, WipeAlgorithmId newWipeAlgorithm)
BOOL SaveNonSysInPlaceEncSettings (int delta, WipeAlgorithmId newWipeAlgorithm, BOOL bDecrypt)
{
int count;
char str[32];
@@ -1435,7 +1997,7 @@ BOOL SaveNonSysInPlaceEncSettings (int delta, WipeAlgorithmId newWipeAlgorithm)
RemoveNonSysInPlaceEncNotifications();
return TRUE;
}
else
else if (!bDecrypt)
{
if (newWipeAlgorithm != TC_WIPE_NONE)
{
@@ -1447,11 +2009,11 @@ BOOL SaveNonSysInPlaceEncSettings (int delta, WipeAlgorithmId newWipeAlgorithm)
{
remove (GetConfigPath (TC_APPD_FILENAME_NONSYS_INPLACE_ENC_WIPE));
}
StringCbPrintfA (str, sizeof(str), "%d", count);
return SaveBufferToFile (str, GetConfigPath (TC_APPD_FILENAME_NONSYS_INPLACE_ENC), strlen(str), FALSE);
}
StringCbPrintfA (str, sizeof(str), "%d", count);
return SaveBufferToFile (str, GetConfigPath (TC_APPD_FILENAME_NONSYS_INPLACE_ENC), strlen(str), FALSE);
}