Page 1 of 1

MASM - Enumerating the Windows Registry

#1 GunnerInc  Icon User is offline

  • "Hurry up and wait"
  • member icon




Reputation: 858
  • View blog
  • Posts: 2,282
  • Joined: 28-March 11

Post icon  Posted 09 June 2012 - 08:17 PM

This is part 1 of 2 or 3. We will be building the base for the other tutorials. I will show how to use:
  • RegCloseKey
  • RegEnumKeyExA
  • RegEnumValueA
  • RegOpenKeyExA
  • RegQueryInfoKeyA

The Windows Registry is where programs, the OS, drivers, and other items store settings and data for later use. Personally for my programs I use the good old fashioned ini files.

The Registry is a dangerous place! One small mistake and you could render the OS non bootable. We won't have that issue here as we are only going to read from the Registy. When I first started the Tutorial, it was very complex so I started over and made it very simple. Wasn't good for what I wanted to show, so I settled on somewhat complex.

We will use a TreeView and ListView control to display all keys, subkeys, and values under HKEY_CURRENT_USER\Software. You can modify it for any purpose/keys.

*** Note ***
I do not enumerate all keys and subkeys when loading. When the program starts, we only enumerate the subkeys under HKCU\Software and set a flag if the key has subkeys, we then fill these children in when the TVITEM is expanded.

The "splitter" code is nothing fancy, modfiy it at your will. There is a mix of invokes and push/calls, I started with push/calls and go tired of typing so switch to invokes for intellisense.

This will be a bit long, so get comfy!
We will be using the following structures:
TVITEM
TV_INSERTSTRUCT
TVHITTESTINFO
LVITEM and LVCOLUMN


The "Magic" happens in our WM_NOTIFY handler of our WindowProc. We will handle 4 messages from the Treeview:
TVN_SELCHANGED
This is where we will handle displaying the values of selected node from the Registry.

TVN_ITEMEXPANDING
This is where we will fill all children of node if it has not already been filled.

TVN_GETDISPINFO
This is where we will change the icon to either an open folder or closed folder depending on action.

NM_CLICK
This is where we check to see if the item expand button was clicked and select Treeview item.

TVN_SELCHANGED
First thing we have to do when we get this notification is get the handle of selected item and its parent.
ebx will contain our treeview handle

    invoke  SendMessage, ebx, TVM_GETNEXTITEM, TVGN_CARET, NULL
    push    eax
    invoke  SendMessage, ebx, TVM_GETNEXTITEM, TVGN_PARENT, eax
    test    eax, eax
    jz      Done
        
    cmp     eax, hRoot
    jne     ProcessChild

If the handle does not equal hRoot, a child was selected; if so, then a parent node was selected and we get the text of the item:
    mov     tvi._mask, TVIF_TEXT
    mov     tvi.pszText, offset lpszBuffer
    mov     tvi.cchTextMax, sizeof lpszBuffer
    pop     tvi.hItem
    invoke  SendMessage, ebx, TVM_GETITEM, 0, addr tvi
    jmp     ShowValues

No furthure processing is required since the Registry path will be HKEY_CURRENT_USER\Software\TextOfParent so we will go onto displaying the values.

If a child was selected, we have to build the registry path from the child all the way back to the parent. For example, if we have a node like so:
SomeParent
    Child1
    Child2
    Parent2
        Child3

and Child3 was selected, we have to scan backwards till we reach the root of the treevew. So after our call to BuildRegPath, the path will be HKCU\Software\SomeParent\Parent2\Child3 then we call ShowInfo.

TVN_ITEMEXPANDING
As I mentioned earlier, we don't enumerate all subkeys under HKCU\Software only the toplevel keys. When we add a toplevel key to the treeview its lParam will be 0. When we get TVN_ITEMEXPANDING, we check the lParam of selected item, if it is 0 then we enumerate the registry children and set lParam of the item to 1. Same as TVN_SELCHANGED, if it is a child we have to build a reg path before we enumerate:
    invoke  SendMessage, hLVValues, LVM_DELETEALLITEMS, 0, 0
    invoke  SendMessage, ebx, TVM_GETNEXTITEM, TVGN_CARET, NULL
    push    eax
    invoke  SendMessage, ebx, TVM_GETNEXTITEM, TVGN_PARENT, eax
    test    eax, eax
    jz      Done
        
    cmp     eax, hRoot
    jne     ProcessChild
    
    ; parent selected
    mov     tvi._mask, TVIF_TEXT
    mov     tvi.pszText, offset lpszBuffer
    mov     tvi.cchTextMax, sizeof lpszBuffer
    pop     tvi.hItem
    invoke  SendMessage, ebx, TVM_GETITEM, 0, addr tvi
    jmp     ShowValues
           
ProcessChild:
    ; child selected
    mov     tvi._mask, TVIF_TEXT
    mov     tvi.pszText, offset lpszBuffer
    mov     tvi.cchTextMax, MAX_PATH    
    pop     tvi.hItem
    invoke  SendMessage, ebx, TVM_GETITEM, 0, addr tvi
    
    invoke  BuildRegPath, tvi.hItem, offset lpszBuffer

ShowValues:
    invoke  RegOpenKeyEx, HKEY_CURRENT_USER, offset szSoftware, NULL, KEY_QUERY_VALUE or KEY_ENUMERATE_SUB_KEYS, addr hKey
    invoke  RegOpenKeyEx, hKey, offset lpszBuffer, NULL, KEY_QUERY_VALUE or KEY_ENUMERATE_SUB_KEYS, addr hSubKey
	test    eax, eax
	jnz     Done
	
    invoke  ShowInfo, hSubKey
    
    invoke  RegCloseKey, hSubKey
    invoke  RegCloseKey, hKey    
    jmp     Done


TVN_GETDISPINFO
First we get the state of current item:
    mov     eax, (NMTVDISPINFO ptr [edi]).item.hItem
    mov     tvi.hItem, eax
    mov     tvi._mask, TVIF_STATE
    invoke  SendMessage, ebx, TVM_GETITEM, 0, addr tvi

Then we check to see if the TVIS_EXPANDED bit is set
    mov     eax, tvi.state
    test    eax, TVIS_EXPANDED 
    jnz     @F


If the ZERO flag is not zero, then the item is expanded and we fill in 2 members of the NMTVDISPINFO structure - item.iSelectedImage/iImage with the index of our open folder icon. Otherwise the closed folder icon
    mov     (NMTVDISPINFO ptr [edi]).item.iSelectedImage, ICON_FOLDER_IDX
    mov     (NMTVDISPINFO ptr [edi]).item.iImage, ICON_FOLDER_IDX
    jmp     Done
@@:    
    mov     (NMTVDISPINFO ptr [edi]).item.iSelectedImage, ICON_FOLDER_OPEN_IDX
    mov     (NMTVDISPINFO ptr [edi]).item.iImage, ICON_FOLDER_OPEN_IDX
    jmp     Done


NM_CLICK
    ; see if the item button was clicked
    invoke  GetCursorPos, addr pt1
    invoke  ScreenToClient, ebx, addr pt1
    m2m     tvhti.pt.x, pt1.x
    m2m     tvhti.pt.y, pt1. y
    invoke  SendMessage, ebx, TVM_HITTEST, 0, addr tvhti
    mov     eax, tvhti.flags
    cmp     eax, TVHT_ONITEMBUTTON
    jne     Done
    
    ; it was, now select item
    invoke  SendMessage, ebx, TVM_SELECTITEM,TVGN_CARET, tvhti.hItem
    jmp     Done

All we do here, is get the position of the cursor, and check if the expand button was clicked. If it was we select the item so we can "fire" the TVN_SELCHANGED code. If we didn't handle this message, then things will get screwed up when we use TVM_GETNEXTITEM/TVGN_CARET. To see what happens, just comment out these lines:
    cmp     dword ptr(NMHDR ptr[edi]).code, NM_CLICK 
    je      _NM_CLICK 


The functions EnumChildren and ShowInfo both require a handle to a reg key to work on. This will be the currently selected Treeview item and as mentioned, if it is not a parent key, then we use BuildRegPath to get the reg path. Then we must open 2 keys: HKCU\Software and with that handle, we call RegOpenKeyEx and the path returned from BuildRegPath.

Now for the code that does the work:
ShowInfo will enumerate the values in open reg key.
Spoiler

I will use the register EDI for 0, since it is a smaller opcode to push a register vs. a number.

At the beginning of the procedure, I turn of painting/drawing of the listview to speed up inserting items. We do this with a SendMessage and a wParam value of FALSE
    push    edi
    push    edi
    push    WM_SETREDRAW
    push    hLVValues
    call    SendMessage

Now for this piece of code, I need to know 2 things about the registry key:
  • Total values in key
  • Size of the longest data component among the key's values

Since we don't need any other info, we can pass NULL to most of the Parameters of RegQueryInfoKey
    push    edi                             ; lpftLastWriteTime - N/A
    push    edi                             ; lpcbSecurityDescriptor - N/A
    lea     eax, lpcMaxValueLen             ; 
    push    eax                             ; longest data length
    push    edi                             ; lpcMaxValueNameLen - N/A
    lea     ecx, lpcValues                  ;
    push    ecx                             ; total values in key
    push    edi                             ; lpcMaxClassLen - N/A
    push    edi                             ; lpcMaxSubKeyLen - N/A
    push    edi                             ; lpcSubKeys - N/A
    push    edi                             ; lpReserved - NULL
    push    edi                             ; lpcClass - N/A
    push    edi                             ; lpClass - N/A
    push    hRegKey                         ; handle of open reg key to query info from
    call    RegQueryInfoKey                 ;

Once the call returns, we check the value of lpcValues and if it is zero, we add a "default" value to the listview.

We need a buffer to hold the enumerated data:
    push    lpcMaxValueLen                  ; create a buffer to hold value data
    push    HEAP_ZERO_MEMORY                ; size == length of longest value
    push    hHeap                           ;
    call    HeapAlloc                       ; 
    xchg    eax, esi                        ; move pointer to esi

Now we can use RegEnumValueEx to enumerate all values in the key. We call it in a loop until it returns ERROR_NO_MORE_ITEMS. When you first call RegEnumValueEx, you need to pass 0 in the dwIndex param and increment it each loop iteration.
   
GetNextValue:
    mov     [esi], edi                      ; zero buffer 
    mov     lpcValueName, MAX_VALNAME + 1

    push    lpcMaxValueLen
    pop     lpcbData 
    
    lea     eax, lpcbData                   ; how big our buffer is
    push    eax                             ;
    push    esi                             ; pointer to our buffer to hold current enum'd values data
    lea     ecx, lpType                     ; var to hold values data type
    push    ecx                             ;
    push    NULL                            ; lpReserved - NULL
    lea     edx, lpcValueName               ; how big our valuename buffer is
    push    edx                             ;
    lea     eax, lpValueName                ; buffer to hold value name
    push    eax                             ;
    push    ebx                             ; dwIndex
    push    hRegKey                         ; handle of open reg key to enumerate values
    call    RegEnumValue                    ;
    cmp     eax, ERROR_NO_MORE_ITEMS        ; any more values?
    je      NoMoreValues                    ; nope, exit loop
    
    cmp     lpcValueName, edi               ; does key have values?
    jnz     AddValue                        ; yes, add it


When we are done adding values to the listview, we turn back on painting/drawing, we do this with a SendMessage and a wParam value of TRUE:
    ; turn back on listview drawing
    push    0
    push    TRUE
    push    WM_SETREDRAW
    push    hLVValues
    call    SendMessage


AddLVItem adds a registry value to listview - Name, Type, and Data
Spoiler

First thing we do is fill in some members of the LVITEM structure with passed data. When we insert an item into column 0 we use LVM_INSERTITEM and we will use the flags LVIF_TEXT and LVIF_IMAGE to tell the listview we want to set the icon and text of the item.
    mov     lvi.imask, LVIF_TEXT or LVIF_IMAGE
    push    dwItem
    pop     lvi.iItem
    mov     lvi.iSubItem, 0
    push    pszName
    pop     lvi.pszText

Next we see what data type was passed and fill in lvi.iImage accordingly:
    mov     eax, dwType
    cmp     eax, REG_SZ
    je      StringData
    cmp     eax, REG_MULTI_SZ
    je      StringData
    cmp     eax, REG_EXPAND_SZ
    jne     OtherData
    
StringData:
    mov     lvi.iImage, ICON_STRING_IDX
    jmp     AddIt
    
OtherData:
    mov     lvi.iImage, ICON_DATA_IDX

Once that is all done, we insert the item into the listview:
AddIt:
    lea     eax, lvi                        ;
    push    eax                             ;
    push    0                               ;
    push    LVM_INSERTITEM                  ; 
    push    hLVValues                       ;
    call    SendMessage                     ; add icon and value name

Now to "add" subitems to the listview item we just inserted, we use LVM_SETITEM. We set lvi.iSubitem to the index of the subitem to set. When we first insert an item, lvi.iSubitem is zero so we just increase the value for each subitem:
    inc     lvi.iSubItem

For the data type column, we check to see the data type that was grabbed from the registry and use that number as an index into a table of string pointers for the data type:
    lea     esi, RegTypesTable
    .if dwType > RegTypesSize
        mov     lvi.pszText, offset szUnknown
    .else
        mov     ecx, dwType
        mov     eax, [esi + 4 * ecx]
        mov     lvi.pszText, eax 
    .endif
    invoke  SendMessage, hLVValues, LVM_SETITEM, 0, addr lvi

Now there is one more column to set - data
    inc     lvi.iSubItem

If it is one of the string types, we just use the data passed:
    .if dwType == REG_SZ || dwType == REG_MULTI_SZ || dwType == REG_EXPAND_SZ 
        push    lpData
        pop     lvi.pszText  
        invoke  SendMessage, hLVValues, LVM_SETITEM, 0, addr lvi

If it is one of the DWORD types we create a temp buffer to hold our formated number then convert to hex, convert the dword to string and multicat it all together to get a nicely formated string like this:
Attached Image
    .elseif dwType == REG_DWORD || dwType == REG_DWORD_BIG_ENDIAN
        invoke  HeapAlloc, hHeap, HEAP_ZERO_MEMORY, 32
        mov     TempBuf, eax
        mov     ebx, lpData
        mov     ebx, [ebx]
        invoke  dw2hex, ebx, addr HexNum
        invoke  dwtoa, ebx, addr Num
        invoke  szMultiCat, 5, TempBuf, offset szHex, addr HexNum, offset szParenLeft, addr Num, offset szParenRight
        push    TempBuf
        pop     lvi.pszText
        invoke  SendMessage, hLVValues, LVM_SETITEM, 0, addr lvi
        invoke  HeapFree, hHeap, 0, TempBuf

If the data type is anything other than the above types, we will treat it as binary and convert it to hex.
ConvertToHex is code modified from the MASM32 library - bin2hex to format the string as I need it to be.
First we need a buffer to hold our hex string. We take the value in dwDataLen and shift left by 2 to multiply the value by 4 so our buffer is large enough, then we call ConvertToHex to give us a string like this:
Attached Image
    .else
        mov     eax, dwDataLen
        shl     eax, 2
        invoke  HeapAlloc, hHeap, HEAP_ZERO_MEMORY, eax
        mov     TempBuf, eax
        invoke  ConvertToHex, lpData, dwDataLen, TempBuf
        push    TempBuf
        pop     lvi.pszText
        invoke  SendMessage, hLVValues, LVM_SETITEM, 0, addr lvi
        invoke  HeapFree, hHeap, 0, TempBuf

EnumChildren is called anytime we expand a parent node and it need to be filled
Spoiler

EnumChildren takes 2 parameters: handle to an open registry key to enumerate and handle to its treeview parent.
We use RegEnumKeyEx in a loop until it returns ERROR_NO_MORE_ITEMS. As with RegEnumValue we pass 0 for dwIndex the first time we call it. We are only interested in the currently enumerated key name.
    lea     esi, lpName
    xor     ebx, ebx
    xor     edi, edi
GetNextKey:
    mov     [esi], edi
    mov     lpcName, sizeof lpName
    invoke  RegEnumKeyEx, hKey, ebx, esi, addr lpcName, edi, edi, edi, edi
    cmp     eax, ERROR_NO_MORE_ITEMS
    je      Done
    
    invoke  RegOpenKeyEx, hKey, esi, edi, KEY_QUERY_VALUE, addr hSubKey
    invoke  RegQueryInfoKey, hSubKey, edi, edi, edi, addr lpcSubkeys, edi, edi, edi, edi, edi, edi, edi
    invoke  RegCloseKey, hSubKey

    invoke  AddKeyToTV, hParentNode, esi
    .if lpcSubkeys > edi
        ; notify TV item has children so we 
        ; get the expand button
        mov     tvi._mask, TVIF_CHILDREN or TVIF_PARAM
        mov     tvi.lParam, 0
        mov     tvi.hItem, eax
        mov     tvi.cChildren, 1
        invoke  SendMessage, hTVKeys, TVM_SETITEM, edi, addr tvi
    .endif
    inc     ebx
    jmp     GetNextKey
Done:
    ret

Once we get a key name from RegEnumKeyEx, we open it with RegOpenKeyEx and get the number of subkeys with RegQueryInfoEx. We add the reg key to the Treeview with AddkeyToTV which returns a handle of the inserted item. Now we check to see if there were subkeys, and if so, we set the tvi.cChildren member to 1 so we get the button to expand the item.

Many Registry API functions have a samDesired parameter. This is the desired access right to the key. The calls WILL fail if you request a right that your user account does not have. I see folks using KEY_ALL_ACCESS, WHY?!?!? Some also use KEY_READ for anything having to do with enumerating, reading, querying, etc.. the same with KEY_WRITE. Well I was taught long ago when Windows was a bit more tempermental, to use only the access rights that you need. If I just need to query a value, I will use KEY_QUERY_VALUE. Enumerate keys, then it is KEY_ENUMERATE_SUB_KEYS, if I need to do more, I will OR them together. By using ONLY what you require, you reduce the chances of bugs popping up.

PHEW!!! That was a bit long, but worth it I hope!
This is what all of that put together looks like:

Attached Image

Full source and sample exe
Attached File  Registry.zip (17.82K)
Number of downloads: 331

Is This A Good Question/Topic? 0
  • +

Replies To: MASM - Enumerating the Windows Registry

#2 danzar  Icon User is offline

  • D.I.C Head
  • member icon

Reputation: 8
  • View blog
  • Posts: 108
  • Joined: 10-December 08

Posted 10 June 2012 - 11:00 AM

Very Nice, Thank you.
Was This Post Helpful? 0
  • +
  • -

Page 1 of 1