12

I have WinDbg attached to a process I don't have the source code for. I've set a breakpoint with bm ADVAPI32!_RegOpenKeyExW@20. The output of dv is:

Unable to enumerate locals, HRESULT 0x80004005
Private symbols (symbols.pri) are required for locals.
Type ".hh dbgerr005" for details.

The output of kP is:

0:000> kP
ChildEBP RetAddr  
001ae174 5b73a79c ADVAPI32!_RegOpenKeyExW@20
001ae1cc 5b77bb20 msenv!?ReadSecurityAddinSetting@@YG_NPAGK@Z+0x8a
001ae468 5b781aad msenv!?    QueryStatusCmd@CVSCommandTarget@@QAEJPBVCIcmdGuidCmdId@@PBU_GUID@@KQAU_tagOLECMD@@PAU_tagOLECMDTEXT@@@Z+0x254
001ae49c 5b786073 msenv!?IsCommandVisible@CVSShellMenu@@QAEJPBVCIcmdGuidCmdId@@_N@Z+0xbf
001ae4e4 5b785fd2 msenv!?IsCommandVisible@CSurfaceCommandingSupport@@UAGJABU_COMMANDTABLEID@@_NPAH@Z+0xa0
. . .

What can I do to look at the values of the paramaters passed (particularly the second one: LPCTSTR lpSubKey)? Also, what can I do to set a conditional breakpoint based on the value?

I have the Visual Studio debugger as well as WinDbg. I'm willing to try other tools as well.

5 Answers 5

13

This is doable in WinDbg, but the syntax isn't the greatest. The other answers are probably better if you're just looking to get a result quickly. That said, here's how you'd do it.

RegOpenKeyExW is an stdcall function, and it looks like you're on a 32-bit version of Windows, so when you enter the function the first argument will be at ESP+4, the second at ESP+8, etc. The signature for it is:

LONG WINAPI RegOpenKeyEx(
  _In_        HKEY hKey,
  _In_opt_    LPCTSTR lpSubKey,
  _Reserved_  DWORD ulOptions,
  _In_        REGSAM samDesired,
  _Out_       PHKEY phkResult
);

So at ESP+8 you'll find a pointer to a null-terminated Unicode string (lpSubKey). You can test this out by setting an unconditional breakpoint on RegOpenKeyExW and then using du poi(esp+8) to print the string.

To do the next piece – stopping when the value of lpSubKey is equal to some pattern, you can check out MSDN's documentation, which contains an example of doing exactly that. I don't have access to a Windows machine at the moment, but the following should do what you want (adapted from the MSDN example):

bp advapi32!RegOpenKeyExW "$$<c:\\commands.txt"

And then create c:\commands.txt with the following contents:

.if (poi(esp+8) != 0) { as /mu ${/v:SubKey} poi(esp+8) } .else { ad /q ${/v:SubKey} }
.if ($spat(@"${SubKey}", "Foo*") == 0)  { gc } .else { .echo SubKey }

You can replace "Foo*" in the above with any valid string wildcard.

Breaking this down, the first .if sets up an alias named SubKey equal to the contents of the unicode string at esp+8. The second line matches that string against the pattern "Foo*"; if it doesn't match, then gc is used to continue execution without stopping. If it does match, .echo SubKey prints out the string, and then breaks.

7
  • Great answer. I despise the arcane syntax of WinDbg script, but you really nailed it :)
    – 0xC0000022L
    May 1, 2013 at 22:28
  • That's cool. I take it that without access to the headers, there is no way to predict the parameter types, so if I wanted to make a windbg plugin to automate this, I'd need all the header versions. May 1, 2013 at 23:53
  • Yes, unfortunately that's correct. Inside MS they have access to private symbols, which give them the types of arguments. You can sometimes see this because some of the built-in WinDbg extensions fail when run outside of Microsoft... May 1, 2013 at 23:58
  • 1
    @JustinDearing: the book "Undocumented Windows 2000 Secrets" by Sven B. Schreiber IIRC has implemented a scheme which requires a table of "format strings" and will then be able to give you the parameters. The book exists now as a free of charge PDF file. Also, I'm tempted to undelete my answer, but then I'm unsure about your exact requirements.
    – 0xC0000022L
    May 2, 2013 at 11:20
  • @0xC0000022L a friend of mine brought that book to my attention during our windows internals study group. I have it downloaded. I will look there. The other option I was considering was using Rohit's XML which has all the header info. May 2, 2013 at 12:10
5

I have had hands down the best experiences with PaiMei, or in particular PyDbg for these purposes. The book "Grayhat Python" brought it to my attention and I decided to give it a try.

The script

Here's a somewhat generic skeleton I've been using in my dynamic analysis () efforts. I have adjusted it so it'll hook the Unicode version of the function you are interested in hooking.

However, the answer may not be spot on, since you can achieve breaking conditionally, but you'll have to add the interactive debugger part to the script. If you wanted to vreak instead of continuing, you'd have to tell the debugger to do just that by modifying the line return DBG_CONTINUE.

import sys
import ctypes
import traceback
try:
    from pydbg import *
    from pydbg.defines import *
    from utils import hooking
except:
    print "ERROR: you need pydbg and utils.hooking from PAIMEI."
    sys.exit(-1)

reg = None

class reg_pydbg(pydbg):
    @staticmethod
    def __getlen(mbi, address):
        # What's the maximum number of bytes we can read?
        _maxlen = 64*1024
        absmaxlen = (mbi.BaseAddress + mbi.RegionSize) - address
        if absmaxlen > _maxlen:
            return _maxlen
        return absmaxlen

    def rootkey_const(self, key):
        if 0x80000000 == key:
            return "HKCR"
        elif 0x80000001 == key:
            return "HKCU"
        elif 0x80000002 == key:
            return "HKLM"
        elif 0x80000003 == key:
            return "HKU"
        elif 0x80000004 == key:
            return "HKEY_PERFORMANCE_DATA"
        elif 0x80000050 == key:
            return "HKEY_PERFORMANCE_TEXT"
        elif 0x80000060 == key:
            return "HKEY_PERFORMANCE_NLSTEXT"
        elif 0x80000005 == key:
            return "HKEY_CURRENT_CONFIG"
        elif 0x80000006 == key:
            return "HKEY_DYN_DATA"
        elif 0x80000007 == key:
            return "HKEY_CURRENT_USER_LOCAL_SETTINGS"
        return "0x%08X" % (key)

    def readmem(self, address, len = 0):
        try:
            mbi = self.virtual_query(address)
        except:
            return None, "%08X <invalid ptr>" % (address)

        if mbi.Protect & PAGE_GUARD: # no way to display contents of a guard page
            return None, "%08X <guard page>" % (address)

        if 0 == len: # try to make a good guess then
            len = self.__getlen(mbi, address)

        try:
            explored = self.read_process_memory(address, len)
        except:
            return None, "%08X <ReadProcessMemory failed>" % (address)

        return explored, None

    def readstring(self, address, unicodeHint = False, returnNone = False):
        if 0 == address:
            if returnNone:
                return None
            return "<nullptr>"

        explored, retval = self.readmem(address)

        if not explored:
            if returnNone:
                return None
            return retval

        explored_string = None

        if not unicodeHint:
            explored_string = self.get_ascii_string(explored)

        if not explored_string:
            explored_string = self.get_unicode_string(explored)

        if not explored_string:
            explored_string = self.get_printable_string(explored)

        return explored_string

def exit_RegOpenKeyExW(dbg, args, ret):
    keyname = dbg.readstring(args[1], True)
    print "RegOpenKeyExW(%s, %s, ...) -> %s (%d)" % (dbg.rootkey_const(args[0]), keyname, ctypes.FormatError(ret), ret)
    return DBG_CONTINUE

class reghooks:
    fct2hook = {
        "advapi32.dll" :
            {
            "RegOpenKeyExW"                     : { "args" : 5, "entry" : None, "exit" : exit_RegOpenKeyExW },
            },
    }

    hooked = {}
    hookcont = None
    dbg = None

    def __init__ (self, dbg):
        self.hookcont = hooking.hook_container()
        self.hooked = {}
        self.dbg = dbg
        dbg.set_callback(LOAD_DLL_DEBUG_EVENT, self.handler_loadDLL)

    def hookByDLL(self, dll):
        if not dll.name.lower() in self.hooked:
            for key,value in self.fct2hook.items():
                if key.lower() == dll.name.lower():
                    self.hooked[dll.name.lower()] = 1
                    print "%s at %08x" % (dll.name, dll.base)
                    for func,fctprops in value.items():
                        entry = None; exit = None; args = 0
                        if "entry" in fctprops and None != fctprops["entry"]:
                            print "\tentry hook " + func
                            entry = fctprops["entry"]
                        if "exit" in fctprops and None != fctprops["exit"]:
                            print "\texit hook " + func
                            exit = fctprops["exit"]
                        if "args" in fctprops and None != fctprops["args"]:
                            args = fctprops["args"]
                        if None != entry or None != exit:
                            funcaddr = self.dbg.func_resolve(dll.name, func)
                            self.hookcont.add(self.dbg, funcaddr, args, entry, exit)
        else:
            self.hooked[dll.name.lower()] += 1
        return

    @staticmethod
    def handler_loadDLL(dbg):
        global reg
        dbg.hide_debugger()
        last_dll = dbg.get_system_dll(-1)
        reg.hookByDLL(last_dll)
        return DBG_CONTINUE

def main():
    dbg = reg_pydbg()
    dbg.load("C:\\Windows\\regedit.exe")
    global reg
    reg = reghooks(dbg)
    dbg.run()

if __name__ == "__main__":
    main()

Example output

>C:\Python26\python.exe hookreg.py
advapi32.dll at 75840000
        exit hook RegOpenKeyExW
RegOpenKeyExW(HKCU, Software\Microsoft\Windows\CurrentVersion\Applets\Regedit, ...) -> The operation completed successfully. (0)
RegOpenKeyExW(HKLM, SOFTWARE\Microsoft\Windows NT\CurrentVersion\FontLink\SystemLink, ...) -> The operation completed successfully. (0)
RegOpenKeyExW(HKLM, SOFTWARE\Microsoft\Windows NT\CurrentVersion\LanguagePack\DataStore_V1.0, ...) -> The operation completed successfully. (0)
RegOpenKeyExW(HKLM, SOFTWARE\Microsoft\Windows NT\CurrentVersion\LanguagePack\SurrogateFallback, ...) -> The operation completed successfully. (0)

Explanation

The important parts are:

"RegOpenKeyExW" : { "args" : 5, "entry" : None, "exit" : exit_RegOpenKeyExW },

and the function exit_RegOpenKeyExW. You could also modify the above to hook only upon entry RegOpenKeyExW instead of upon exit or to do both. Depends on what you want to achieve.

I have used this method to decode buffers (SCSI_PASS_THROUGH_DIRECT and SCSI_PASS_THROUGH) sent via DeviceIoControl, so the above is by far not the most complex thing you can do.

On another note, I have also hooked stuff like opening files (or registry keys) and kept lists of the returned handles along with the string form of what they are. This way I could implement quite complex human-readable logging scenarios.

Rationale

The reason stuff like the nested dictionary exists is so that it can be easily extended to hook whatever function from whatever DLL I please. Of course it could be hardcoded as well, but I have had scenarios where I was hooking dozens of functions.

4

With OllyDbg you can do it like this

F:\odbg110>regedit & tasklist /fi "imagename eq rege*"

Image Name                   PID Session Name     Session#    Mem Usage
========================= ====== ================ ======== ============
regedit.exe                 2820 Console                 0      2,512 K

F:\odbg110>ollydbg -P 2820

F:\odbg110>

In OllyDbg hit Alt+g (goto) type advapi32.RegOpenKeyExW and hit Enter. If you are on Windows XP SP3 it should look something like this:

77DD6AAF ADVAPI32.RegOpenKeyExW U>/$  8BFF          MOV     EDI, EDI

Hit Ctrl+F4 (conditional log breakpoint)

In the "condition" edit box enter say (no wild cards needs a valid escaped pattern)

UNICODE [[esp+8]]  ==  "system\\CurrentControlSet\\Services\\Beep" 

In the "explanation" edit box enter any explanation you want like

log RegOpenKeyExW subkey

In the expression edit box enter

[esp+8]

In the "Decode Value" select "pointer to Unicode string"

pause program on condition
log value of expression always
log function arguments on condition 

(all of this can be done without pausing the debuggee) (dynamic break insertions)

Now go to regedit and play with it a little and select the "Beep Service"

OllyDbg will break and will have the function arguments logged too; and you will have all other strings logged too without pause, like below

Log data
Address    Message
77DD6AAF   COND: log reop Subkey = 0007FBC8 "SYSTEM\CurrentControlSet\Services\aswMonFlt"
77DD6AAF   COND: log reop Subkey = 0007FBC8 "SYSTEM\CurrentControlSet\Services\aswRdr"
77DD6AAF   COND: log reop Subkey = 0007FBC8 "SYSTEM\CurrentControlSet\Services\aswRvrt"
77DD6AAF   COND: log reop Subkey = 0007FBC8 "SYSTEM\CurrentControlSet\Services\aswSnx"
77DD6AAF   COND: log reop Subkey = 0007FBC8 "SYSTEM\CurrentControlSet\Services\aswSP"
77DD6AAF   COND: log reop Subkey = 0007FBC8 "SYSTEM\CurrentControlSet\Services\aswTdi"
77DD6AAF   COND: log reop Subkey = 0007FBC8 "SYSTEM\CurrentControlSet\Services\aswVmm"
77DD6AAF   COND: log reop Subkey = 0007FBC8 "SYSTEM\CurrentControlSet\Services\avast! Antivirus"
77DD6AAF   COND: log reop Subkey = 0007FBC8 "SYSTEM\CurrentControlSet\Services\Beep"
77DD6AAF   CALL to RegOpenKeyExW from regedit.01008B23
             hKey = HKEY_LOCAL_MACHINE
             Subkey = "SYSTEM\CurrentControlSet\Services\Beep"
             Reserved = 0
             Access = 2000000
             pHandle = regedit.01019098
77DD6AAF   Conditional breakpoint at ADVAPI32.RegOpenKeyExW
1
3

Download Rohitab's API Monitor and set the API capture filter on just RegOpenKeyEx. Below are just example screenshots of how it works and the information it provides.

From what you described, this should do exactly what you need.

enter image description here

enter image description here

3
  • This does show me the parameter values. However, it does not let me conditionally break based on them. My problem is the app is constantly reading many registry values. I only care when it reads certain ones. May 2, 2013 at 0:07
  • Correct, conditional breakpoints are not currently supported. FWIW, the author mentioned on the site's forums that he plans to add that to an upcoming release.
    – Mick
    May 2, 2013 at 12:34
  • Additionally, API Monitor does allow you to search through the captured API data. While not exactly what you are looking for (conditional breakpoints), a little bit of CTRL+F and F3 will get you there.
    – Mick
    May 2, 2013 at 12:39
2

Although it doesn't support breakpoints, Process Monitor is an excellent tool to monitor registry access. Using Filters you can easily choose which keys you want to include, exclude, highlight etc. It also allows you to view the stack trace of any event.

Process Monitor is an advanced monitoring tool for Windows that shows real-time file system, Registry and process/thread activity. It combines the features of two legacy Sysinternals utilities, Filemon and Regmon, and adds an extensive list of enhancements including rich and non-destructive filtering, comprehensive event properties such session IDs and user names, reliable process information, full thread stacks with integrated symbol support for each operation, simultaneous logging to a file, and much more. Its uniquely powerful features will make Process Monitor a core utility in your system troubleshooting and malware hunting toolkit.

enter image description here enter image description here

1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.