#include "DriverControl.h"
#include "../Misc/Utils.h"
#include "../Misc/Trace.hpp"
#include "../Misc/DynImport.h"
#include <3rd_party/VersionApi.h>
namespace blackbone
{
#define DRIVER_SVC_NAME L"BlackBone"
DriverControl::DriverControl()
{
}
DriverControl::~DriverControl()
{
//Unload();
}
DriverControl& DriverControl::Instance()
{
static DriverControl instance;
return instance;
}
///
/// Try to load driver if it isn't loaded
///
/// Path to the driver file
/// Status code
NTSTATUS DriverControl::EnsureLoaded( const std::wstring& path /*= L"" */ )
{
// Already open
if (_hDriver)
return STATUS_SUCCESS;
// Try to open handle to existing driver
_hDriver = CreateFileW(
BLACKBONE_DEVICE_FILE,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL
);
if (_hDriver)
return _loadStatus = STATUS_SUCCESS;
// Start new instance
return Reload( path );
}
///
/// Reload driver
///
/// Path to the driver file
/// Status code
NTSTATUS DriverControl::Reload( std::wstring path /*= L"" */ )
{
Unload();
// Use default path
if (path.empty())
{
const wchar_t* filename = nullptr;
if (IsWindows10OrGreater())
filename = BLACKBONE_FILE_NAME_10;
else if (IsWindows8Point1OrGreater())
filename = BLACKBONE_FILE_NAME_81;
else if (IsWindows8OrGreater())
filename = BLACKBONE_FILE_NAME_8;
else if (IsWindows7OrGreater())
filename = BLACKBONE_FILE_NAME_7;
else
filename = BLACKBONE_FILE_NAME;
path = Utils::GetExeDirectory() + L"\\" + filename;
}
_loadStatus = LoadDriver( DRIVER_SVC_NAME, path );
if (!NT_SUCCESS( _loadStatus ))
{
BLACKBONE_TRACE( L"Failed to load driver %ls. Status 0x%X", path.c_str(), _loadStatus );
return _loadStatus;
}
_hDriver = CreateFileW(
BLACKBONE_DEVICE_FILE,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL
);
if (!_hDriver)
{
_loadStatus = LastNtStatus();
BLACKBONE_TRACE( L"Failed to open driver handle. Status 0x%X", _loadStatus );
return _loadStatus;
}
return _loadStatus;
}
///
/// Unload driver
///
/// Status code
NTSTATUS DriverControl::Unload()
{
_hDriver.reset();
return UnloadDriver( DRIVER_SVC_NAME );
}
///
/// Maps target process memory into current process
///
/// Target PID
/// Pipe name to use for hook data transfer
/// The map sections.
/// Results
/// Status code
NTSTATUS DriverControl::MapMemory( DWORD pid, const std::wstring& pipeName, bool mapSections, MapMemoryResult& result )
{
MAP_MEMORY data = { 0 };
DWORD bytes = 0;
ULONG sizeRequired = 0;
data.pid = pid;
data.mapSections = mapSections;
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
wcscpy_s( data.pipeName, pipeName.c_str() );
BOOL res = DeviceIoControl( _hDriver, IOCTL_BLACKBONE_MAP_MEMORY, &data, sizeof( data ), &sizeRequired, sizeof( sizeRequired ), &bytes, NULL );
if (res != FALSE && bytes == 4)
{
MAP_MEMORY_RESULT* pResult = (MAP_MEMORY_RESULT*)malloc( sizeRequired );
if (DeviceIoControl( _hDriver, IOCTL_BLACKBONE_MAP_MEMORY, &data, sizeof( data ), pResult, sizeRequired, &bytes, NULL ))
{
for (ULONG i = 0; i < pResult->count; i++)
result.regions.emplace( std::make_pair( std::make_pair( pResult->entries[i].originalPtr, pResult->entries[i].size ),
pResult->entries[i].newPtr ) );
result.hostSharedPage = pResult->hostPage;
result.targetSharedPage = pResult->targetPage;
result.targetPipe = (HANDLE)pResult->pipeHandle;
free( pResult );
return STATUS_SUCCESS;
}
}
return LastNtStatus();
}
///
/// Maps single memory region into current process
///
/// Target PID
/// Region base address
/// Region size
/// Mapped region info
/// Status code
NTSTATUS DriverControl::MapMemoryRegion( DWORD pid, ptr_t base, uint32_t size, MapMemoryRegionResult& result )
{
MAP_MEMORY_REGION data = { 0 };
MAP_MEMORY_REGION_RESULT mapResult = { 0 };
DWORD bytes = 0;
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
data.pid = pid;
data.base = base;
data.size = size;
if (DeviceIoControl( _hDriver, IOCTL_BLACKBONE_MAP_REGION, &data, sizeof( data ), &mapResult, sizeof( mapResult ), &bytes, NULL ))
{
result.newPtr = mapResult.newPtr;
result.originalPtr = mapResult.originalPtr;
result.removedPtr = mapResult.removedPtr;
result.removedSize = mapResult.removedSize;
result.size = mapResult.size;
return STATUS_SUCCESS;
}
return LastNtStatus();
}
///
/// Unmap memory of the target process from current
///
/// Target PID
/// Status code
NTSTATUS DriverControl::UnmapMemory( DWORD pid )
{
UNMAP_MEMORY data = { pid };
DWORD bytes = 0;
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
if (DeviceIoControl( _hDriver, IOCTL_BLACKBONE_UNMAP_MEMORY, &data, sizeof( data ), NULL, 0, &bytes, NULL ))
return STATUS_SUCCESS;
return LastNtStatus();
}
///
/// Unmap single memory region
/// If unmapped region size is smaller than the size specified during map, function will return info about
/// 2 regions that emerged after unmap
///
/// Target PID
/// Region base
/// Region size
/// Unampped region info
/// Status code
NTSTATUS DriverControl::UnmapMemoryRegion( DWORD pid, ptr_t base, uint32_t size )
{
UNMAP_MEMORY_REGION data = { 0 };
DWORD bytes = 0;
data.pid = pid;
data.base = base;
data.size = size;
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_UNMAP_REGION, &data, sizeof( data ), NULL, 0, &bytes, NULL ))
return LastNtStatus();
return STATUS_SUCCESS;
}
///
/// Disable DEP for process
/// Has no effect on native x64 processes
///
/// Target PID
/// Status code
NTSTATUS DriverControl::DisableDEP( DWORD pid )
{
DWORD bytes = 0;
DISABLE_DEP disableDep = { pid };
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_DISABLE_DEP, &disableDep, sizeof( disableDep ), nullptr, 0, &bytes, NULL ))
return LastNtStatus();
return STATUS_SUCCESS;
}
///
/// Change process protection flag
///
/// Target PID
/// Process protection policy
/// Prohibit dynamic code
/// Prohibit loading non-microsoft dlls
/// Status code
NTSTATUS DriverControl::ProtectProcess(
DWORD pid,
PolicyOpt protection,
PolicyOpt dynamicCode /*= Policy_Keep*/,
PolicyOpt binarySignature /*= Policy_Keep*/
)
{
DWORD bytes = 0;
SET_PROC_PROTECTION setProt = { pid, protection, dynamicCode, binarySignature };
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_SET_PROTECTION, &setProt, sizeof( setProt ), nullptr, 0, &bytes, NULL ))
return LastNtStatus();
return STATUS_SUCCESS;
}
///
/// Change handle access rights
///
/// Target PID.
/// Handle
/// New access
/// Status code
NTSTATUS DriverControl::PromoteHandle( DWORD pid, HANDLE handle, DWORD access )
{
DWORD bytes = 0;
HANDLE_GRANT_ACCESS grantAccess = { 0 };
grantAccess.pid = pid;
grantAccess.handle = (ULONGLONG)handle;
grantAccess.access = access;
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_GRANT_ACCESS, &grantAccess, sizeof( grantAccess ), nullptr, 0, &bytes, NULL ))
return LastNtStatus();
return STATUS_SUCCESS;
}
///
/// Allocate virtual memory
///
/// Tarhet PID
/// Desired base. If 0 address is chosed by the system
/// Region size
/// Allocation type - MEM_RESERVE/MEM_COMMIT
/// Memory protection
/// Status code
NTSTATUS DriverControl::AllocateMem( DWORD pid, ptr_t& base, ptr_t& size, DWORD type, DWORD protection, bool physical /*= false*/ )
{
DWORD bytes = 0;
ALLOCATE_FREE_MEMORY allocMem = { 0 };
ALLOCATE_FREE_MEMORY_RESULT result = { 0 };
allocMem.pid = pid;
allocMem.base = base;
allocMem.size = size;
allocMem.type = type;
allocMem.protection = protection;
allocMem.allocate = TRUE;
allocMem.physical = physical;
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
if (!DeviceIoControl(
_hDriver, IOCTL_BLACKBONE_ALLOCATE_FREE_MEMORY,
&allocMem, sizeof( allocMem ),
&result, sizeof( result ), &bytes, NULL
))
{
size = base = 0;
return LastNtStatus();
}
base = result.address;
size = result.size;
return STATUS_SUCCESS;
}
///
/// Free virtual memory
///
/// Tarhet PID
/// Desired base. If 0 address is chosed by the system
/// Region size
/// Free type - MEM_RELEASE/MEM_DECOMMIT
/// Status code
NTSTATUS DriverControl::FreeMem( DWORD pid, ptr_t base, ptr_t size, DWORD type )
{
DWORD bytes = 0;
ALLOCATE_FREE_MEMORY freeMem = { 0 };
ALLOCATE_FREE_MEMORY_RESULT result = { 0 };
freeMem.pid = pid;
freeMem.base = base;
freeMem.size = size;
freeMem.type = type;
freeMem.allocate = FALSE;
freeMem.physical = FALSE;
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
if (!DeviceIoControl(
_hDriver, IOCTL_BLACKBONE_ALLOCATE_FREE_MEMORY,
&freeMem, sizeof( freeMem ),
&result, sizeof( result ), &bytes, NULL
))
{
return LastNtStatus();
}
return STATUS_SUCCESS;
}
///
/// Read process memory
///
/// Target PID
/// Target base
/// Data size
/// Buffer address
/// Status code
NTSTATUS DriverControl::ReadMem( DWORD pid, ptr_t base, ptr_t size, PVOID buffer )
{
DWORD bytes = 0;
COPY_MEMORY copyMem = { 0 };
copyMem.pid = pid;
copyMem.targetPtr = base;
copyMem.localbuf = (ULONGLONG)buffer;
copyMem.size = size;
copyMem.write = FALSE;
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_COPY_MEMORY, ©Mem, sizeof( copyMem ), nullptr, 0, &bytes, NULL ))
return LastNtStatus();
return STATUS_SUCCESS;
}
///
/// Write process memory
///
/// Target PID
/// Target base
/// Data size
/// Buffer address
/// Status code
NTSTATUS DriverControl::WriteMem( DWORD pid, ptr_t base, ptr_t size, PVOID buffer )
{
DWORD bytes = 0;
COPY_MEMORY copyMem = { 0 };
copyMem.pid = pid;
copyMem.targetPtr = base;
copyMem.localbuf = (ULONGLONG)buffer;
copyMem.size = size;
copyMem.write = TRUE;
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_COPY_MEMORY, ©Mem, sizeof( copyMem ), nullptr, 0, &bytes, NULL ))
return LastNtStatus();
return STATUS_SUCCESS;
}
///
/// Change memory protection
///
/// Target PID.
/// Regiod base address
/// Region size
/// New protection
/// Status code
NTSTATUS DriverControl::ProtectMem( DWORD pid, ptr_t base, ptr_t size, DWORD protection )
{
DWORD bytes = 0;
PROTECT_MEMORY protectMem = { 0 };
protectMem.pid = pid;
protectMem.base = base;
protectMem.size = size;
protectMem.newProtection = protection;
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_PROTECT_MEMORY, &protectMem, sizeof( protectMem ), nullptr, 0, &bytes, NULL ))
return LastNtStatus();
return STATUS_SUCCESS;
}
///
/// Inject DLL into arbitrary process
///
/// Target PID
/// Full qualified dll path
/// Injection type
/// Init routine RVA
/// Init routine argument
/// Unlink module after injection
/// Erase PE headers after injection
/// Wait for injection
/// Status code
NTSTATUS DriverControl::InjectDll(
DWORD pid,
const std::wstring& path,
InjectType itype,
uint32_t initRVA /*= 0*/,
const std::wstring& initArg /*= L""*/,
bool unlink /*= false*/,
bool erasePE /*= false*/,
bool wait /*= true*/
)
{
DWORD bytes = 0;
INJECT_DLL data = { IT_Thread };
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
wcscpy_s( data.FullDllPath, path.c_str() );
wcscpy_s( data.initArg, initArg.c_str() );
data.type = itype;
data.pid = pid;
data.initRVA = initRVA;
data.wait = wait;
data.unlink = unlink;
data.erasePE = erasePE;
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_INJECT_DLL, &data, sizeof( data ), nullptr, 0, &bytes, NULL ))
return LastNtStatus();
return STATUS_SUCCESS;
}
///
/// Manually map PE image
///
/// Target PID
/// Full qualified image path
/// Mapping flags
/// Init routine RVA
/// Init routine argument
/// Status code
NTSTATUS DriverControl::MmapDll(
DWORD pid,
const std::wstring& path,
KMmapFlags flags,
uint32_t initRVA /*= 0*/,
const std::wstring& initArg /*= L"" */
)
{
DWORD bytes = 0;
INJECT_DLL data = { IT_MMap };
UNICODE_STRING ustr = { 0 };
// Convert path to native format
SAFE_NATIVE_CALL( RtlDosPathNameToNtPathName_U, path.c_str(), &ustr, nullptr, nullptr );
wcscpy_s( data.FullDllPath, ustr.Buffer );
SAFE_CALL( RtlFreeUnicodeString, &ustr );
wcscpy_s( data.initArg, initArg.c_str() );
data.pid = pid;
data.initRVA = initRVA;
data.wait = true;
data.unlink = false;
data.erasePE = false;
data.flags = flags;
data.imageBase = 0;
data.imageSize = 0;
data.asImage = false;
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_INJECT_DLL, &data, sizeof( data ), nullptr, 0, &bytes, NULL ))
return LastNtStatus();
return STATUS_SUCCESS;
}
///
/// Manually map PE image
///
/// Target PID
/// Memory location of the image to map
/// Image size
/// Memory chunk has image layout
/// Mapping flags
/// Init routine RVA
/// Init routine argument
/// Status code
NTSTATUS DriverControl::MmapDll(
DWORD pid,
void* address,
uint32_t size,
bool asImage,
KMmapFlags flags,
uint32_t initRVA /*= 0*/,
const std::wstring& initArg /*= L"" */
)
{
DWORD bytes = 0;
INJECT_DLL data = { IT_MMap };
memset( data.FullDllPath, 0, sizeof( data.FullDllPath ) );
wcscpy_s( data.initArg, initArg.c_str() );
data.pid = pid;
data.initRVA = initRVA;
data.wait = true;
data.unlink = false;
data.erasePE = false;
data.flags = flags;
data.imageBase = (ULONGLONG)address;
data.imageSize = size;
data.asImage = asImage;
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_INJECT_DLL, &data, sizeof( data ), nullptr, 0, &bytes, NULL ))
return LastNtStatus();
return STATUS_SUCCESS;
}
///
/// Manually map another system driver into system space
///
/// Fully quialified path to the drver
/// Status code
NTSTATUS DriverControl::MMapDriver( const std::wstring& path )
{
DWORD bytes = 0;
MMAP_DRIVER data = { { 0 } };
UNICODE_STRING ustr = { 0 };
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
// Convert path to native format
SAFE_NATIVE_CALL( RtlDosPathNameToNtPathName_U, path.c_str(), &ustr, nullptr, nullptr);
wcscpy_s( data.FullPath, ustr.Buffer );
SAFE_CALL( RtlFreeUnicodeString, &ustr);
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_MAP_DRIVER, &data, sizeof( data ), nullptr, 0, &bytes, NULL ))
return LastNtStatus();
return STATUS_SUCCESS;
}
///
/// Make VAD region appear as PAGE_NO_ACESS to NtQueryVirtualMemory
///
/// Target process ID
/// Region base
/// Region size
/// Status code
NTSTATUS DriverControl::ConcealVAD( DWORD pid, ptr_t base, uint32_t size )
{
DWORD bytes = 0;
HIDE_VAD hideVAD = { 0 };
hideVAD.base = base;
hideVAD.size = size;
hideVAD.pid = pid;
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_HIDE_VAD, &hideVAD, sizeof( hideVAD ), nullptr, 0, &bytes, NULL ))
return LastNtStatus();
return STATUS_SUCCESS;
}
///
/// Unlink process handle table from HandleListHead
///
/// Target process ID
/// Status code
NTSTATUS DriverControl::UnlinkHandleTable( DWORD pid )
{
DWORD bytes = 0;
UNLINK_HTABLE unlink = { pid };
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
return STATUS_DEVICE_DOES_NOT_EXIST;
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_UNLINK_HTABLE, &unlink, sizeof( unlink ), nullptr, 0, &bytes, NULL ))
return LastNtStatus();
return STATUS_SUCCESS;
}
///
/// Enumerate committed, accessible, non-guarded memory regions
///
/// Target process ID
/// Found regions
/// Status code
NTSTATUS DriverControl::EnumMemoryRegions( DWORD pid, std::vector& regions )
{
// Not loaded
if (_hDriver == INVALID_HANDLE_VALUE)
{
return STATUS_DEVICE_DOES_NOT_EXIST;
}
DWORD bytes = 0;
ENUM_REGIONS data = { 0 };
DWORD size = sizeof( ENUM_REGIONS_RESULT );
auto result = reinterpret_cast(malloc( size ));
data.pid = pid;
result->count = 0;
DeviceIoControl( _hDriver, IOCTL_BLACKBONE_ENUM_REGIONS, &data, sizeof( data ), result, size, &bytes, NULL );
result->count += 100;
size = static_cast(result->count * sizeof( result->regions[0] ) + sizeof( result->count ));
result = reinterpret_cast(realloc( result, size ));
if (!DeviceIoControl( _hDriver, IOCTL_BLACKBONE_ENUM_REGIONS, &data, sizeof( data ), result, size, &bytes, NULL ))
{
free( result );
return LastNtStatus();
}
regions.resize( static_cast(result->count) );
for (uint32_t i = 0; i < result->count; i++)
{
regions[i].AllocationBase = result->regions[i].AllocationBase;
regions[i].AllocationProtect = result->regions[i].AllocationProtect;
regions[i].BaseAddress = result->regions[i].BaseAddress;
regions[i].Protect = result->regions[i].Protect;
regions[i].RegionSize = result->regions[i].RegionSize;
regions[i].State = result->regions[i].State;
regions[i].Type = result->regions[i].Type;
}
free( result );
return STATUS_SUCCESS;
}
///
/// Load arbitrary driver
///
/// Driver service name
/// Driver file path
/// Status
NTSTATUS DriverControl::LoadDriver( const std::wstring& svcName, const std::wstring& path )
{
UNICODE_STRING Ustr;
// If no file provided, try to start existing service
if (!path.empty() && PrepareDriverRegEntry( svcName, path ) != 0)
return LastNtStatus();
std::wstring regPath = L"\\registry\\machine\\SYSTEM\\CurrentControlSet\\Services\\" + svcName;
SAFE_CALL( RtlInitUnicodeString, &Ustr, regPath.c_str() );
return SAFE_NATIVE_CALL( NtLoadDriver, &Ustr );
}
///
/// Unload arbitrary driver
///
/// Driver service name
/// Status
NTSTATUS DriverControl::UnloadDriver( const std::wstring& svcName )
{
UNICODE_STRING Ustr = { 0 };
std::wstring regPath = L"\\registry\\machine\\SYSTEM\\CurrentControlSet\\Services\\" + svcName;
SAFE_CALL( RtlInitUnicodeString, &Ustr, regPath.c_str() );
// Remove previously loaded instance, if any
NTSTATUS status = SAFE_NATIVE_CALL( NtUnloadDriver, &Ustr );
SHDeleteKeyW( HKEY_LOCAL_MACHINE, (L"SYSTEM\\CurrentControlSet\\Services\\" + svcName).c_str() );
return status;
}
///
/// Fill minimal required driver registry entry
///
/// Driver service name
/// Driver path
/// Status code
LSTATUS DriverControl::PrepareDriverRegEntry( const std::wstring& svcName, const std::wstring& path )
{
HKEY key1, key2;
DWORD dwType = 1;
LSTATUS status = 0;
WCHAR wszLocalPath[MAX_PATH] = { 0 };
swprintf_s( wszLocalPath, ARRAYSIZE( wszLocalPath ), L"\\??\\%s", path.c_str() );
status = RegOpenKeyW( HKEY_LOCAL_MACHINE, L"system\\CurrentControlSet\\Services", &key1 );
if (status)
return status;
status = RegCreateKeyW( key1, svcName.c_str(), &key2 );
if (status)
{
RegCloseKey( key1 );
return status;
}
status = RegSetValueExW(
key2, L"ImagePath", 0, REG_SZ,
reinterpret_cast(wszLocalPath),
static_cast(sizeof( WCHAR )* (wcslen( wszLocalPath ) + 1))
);
if (status)
{
RegCloseKey( key2 );
RegCloseKey( key1 );
return status;
}
status = RegSetValueExW( key2, L"Type", 0, REG_DWORD, reinterpret_cast(&dwType), sizeof( dwType ) );
if (status)
{
RegCloseKey( key2 );
RegCloseKey( key1 );
return status;
}
RegCloseKey( key2 );
RegCloseKey( key1 );
return status;
}
}