- 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:

.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:

.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:

Full source and sample exe

Number of downloads: 2933