Advisories

Microsoft Jet database Record::IsNull Memory Corruption

CVE ID

CVE-2019-1250

Tested Versions

  • Windows 10 version 1903 and below
  • Windows 7

The Microsoft Jet Database Engine (also Microsoft JET Engine or simply Jet) is a database engine on which several Microsoft products have been built. JET stands for Joint Engine Technology. Microsoft Access and Visual Basic have used Jet as their underlying database engine.

The vulnerable DLL msrd3x40.dll is a component in versions from Windows 7 to Windows 10. The vulnerability described here can be triggered with a specially-crafted MDB file, and could lead to code execution.

Vulnerability Details

Version information for the vulnerable module is as follows:

0:000> lmvm msrd3x40
start    end        module name
67dc0000 67e1b000   msrd3x40   (pdb symbols)    C:\sym\MSRD3X40.pdb\5AD2E09CC1DF47C083473BA07C43F2B41\MSRD3X40.pdb
    Loaded symbol image file: C:\Windows\SysWOW64\msrd3x40.dll
    Image path: C:\Windows\SysWOW64\msrd3x40.dll
    Image name: msrd3x40.dll
    Timestamp:        Thu May  2 07:28:03 2019 (5CCA2B83)
    CheckSum:         000637F1
    ImageSize:        0005B000
    File version:     4.0.9801.16
    Product version:  4.0.9801.16
    File flags:       0 (Mask 3F)
    File OS:          40004 NT Win32
    File type:        2.0 Dll
    File date:        00000000.00000000
    Translations:     0000.04b0
    Information from resource tables:
        CompanyName:      Microsoft Corporation
        ProductName:      Microsoft (R) Jet
        InternalName:     MSRD3X40
        OriginalFilename: MSRD3X40.DLL
        ProductVersion:   4.00.9801.16
        FileVersion:      4.00.9801.16
        FileDescription:  Microsoft (R) Red ISAM
        LegalCopyright:   Copyright (C) Microsoft Corp. 1991-1999

Crash context:

0:000> r
eax=00000001 ebx=053e9b18 ecx=053ef31c edx=053ef31c esi=0c491000 edi=00000000
eip=67dcfc41 esp=0057da78 ebp=00000001 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010202
msrd3x40!Record::IsNull+0x11:
67dcfc41 8a0e            mov     cl,byte ptr [esi]          ds:002b:0c491000=??
0:000> kb
ChildEBP RetAddr  Args to Child
0057da98 67dd08f5 00000001 0057dd84 053ef140 msrd3x40!Record::IsNull+0x11
0057daac 67dd5755 053ef1a0 0057dd84 053b83d8 msrd3x40!ColumnFixedLong::Retrieve+0x15
0057dae0 67dd5443 00000001 0057db4c 00000200 msrd3x40!Cursor::RetrieveFromRecord+0x115
0057db00 67de85f0 00000001 0057db4c 00000200 msrd3x40!Cursor::RetrieveColumn+0x33
0057dd4c 67de8421 0f000000 67f0243c 0057dd84 msrd3x40!Instance::ReadSysObjRecord+0x190
0057dd68 67ddd162 67f0243c 67f02450 0057dd84 msrd3x40!Instance::ReadObjectRecord+0x41
0057dd94 67f3b7ed 053f2b28 053aa3c8 67f0243c msrd3x40!ErrIsamGetObjidFromName+0x32
0057ddb0 67f34468 053869b8 000000ff 67f0243c msjet40!ErrDispGetObjidFromName+0x3d
0057de64 67f1bd4a 053869b8 000000ff 67f0243c msjet40!ErrSecValidateAccess+0x48
0057e0d8 67f1f6cd 053869b8 053e0c34 0057e160 msjet40!ErrOpenDatabase+0x53a
0057e0f4 680a310b 053869b8 053e0c34 0057e160 msjet40!JetOpenDatabase+0x4d
0057e110 6809e4aa 053869b8 053e0c34 0057e160 msjetoledb40!CJOLT::JOLTJetOpenDatabase+0x1b
0057e564 6809ecc7 053df0b8 053df0bc 0057e9e0 msjetoledb40!CDataSource::JETOpenDatabase+0x12a
0057e9c8 6809c9e2 0057e9e0 00000000 053df028 msjetoledb40!CDataSource::OpenSessionAndDatabase+0x187
0057e9e0 6809f8c7 053df028 00000001 053df828 msjetoledb40!CDataSource::AttemptInitialize+0xb2
0057ea10 6809e1d0 00000001 0057ea2c e6cc3d28 msjetoledb40!CDataSource::UtilInitialize+0x167
0057ea3c 682d62a3 053df08c ed4ab232 07377c8c msjetoledb40!CImpIDBInitialize::Initialize+0x70

Through analysis we found that if we set the version field to 0 and then Encryption::Encrypt() function will not be called and will bring the program to a different branch:

The program will call msjet40!ErrDispGetObjidFromName, from which the function msrd3x40!ErrIsamGetObjidFromName will also be called:

eax=70dbd130 ebx=0071ef20 ecx=07a10f28 edx=0027e0f8 esi=000000ff edi=000000ff
eip=6e82b7eb esp=0027e0b8 ebp=00000000 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
msjet40!ErrDispGetObjidFromName+0x3b:
6e82b7eb ffd0            call    eax {msrd3x40!ErrIsamGetObjidFromName (70dbd130)}
0:000> kb
ChildEBP RetAddr  Args to Child
0027e0cc 6e824468 0071ef20 000000ff 6e7f243c msjet40!ErrDispGetObjidFromName+0x3b
0027e180 6e80bd4a 0071ef20 000000ff 6e7f243c msjet40!ErrSecValidateAccess+0x48
0027e3f4 6e80f6cd 0071ef20 07f0bfd4 0027e47c msjet40!ErrOpenDatabase+0x53a
0027e410 6f0e310b 0071ef20 07f0bfd4 0027e47c msjet40!JetOpenDatabase+0x4d

In msrd3x40!ErrIsamGetObjidFromName, there is a call to ReadObjectRecord, which will read record object:

0:000> r
eax=6e4f243c ebx=0096ef20 ecx=0701dfb0 edx=0031e160 esi=0701dfb0 edi=07614e90
eip=7104d15d esp=0031e0f4 ebp=00000000 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
msrd3x40!ErrIsamGetObjidFromName+0x2d:
7104d15d e87eb20000      call    msrd3x40!Instance::ReadObjectRecord (710583e0)
0:000> kb4
ChildEBP RetAddr  Args to Child
0031e118 6e52b7ed 07f72f28 0701dfb0 6e4f243c msrd3x40!ErrIsamGetObjidFromName+0x2d
0031e134 6e524468 0096ef20 000000ff 6e4f243c msjet40!ErrDispGetObjidFromName+0x3d
0031e1e8 6e50bd4a 0096ef20 000000ff 6e4f243c msjet40!ErrSecValidateAccess+0x48
0031e45c 6e50f6cd 0096ef20 07f86fd4 0031e4e4 msjet40!ErrOpenDatabase+0x53a
0:000> du poi(esp)
6e4f243c  "Databases"
0:000> du poi(esp+4)
6e4f2450  "MSysDb"

There are two parameters which represent strings, in our case, one is Databases, the other is MSysDb. If the string is not “Tables”, Cursor::RetrieveColumn will be called:

0:000> r
eax=71031e80 ebx=0031e108 ecx=07614e90 edx=0031ded0 esi=0000f3d4 edi=00000000
eip=710585ed esp=0031de8c ebp=0701dfb0 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
msrd3x40!Instance::ReadSysObjRecord+0x18d:
710585ed ff506c          call    dword ptr [eax+6Ch]  ds:002b:71031eec={msrd3x40!Cursor::RetrieveColumn (71045410)}
0:000> dd esp
0031de8c  00000001 0031ded0 00000200 00000000

The program will read the first column and will call ColumnVariableLong::Retrieve:

0:000> r
eax=71031988 ebx=0705c888 ecx=0cc59f30 edx=07614ef0 esi=07614e90 edi=0031e108
eip=71045752 esp=0031de38 ebp=00000001 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
msrd3x40!Cursor::RetrieveFromRecord+0x112:
71045752 ff5018          call    dword ptr [eax+18h]  ds:002b:710319a0={msrd3x40!ColumnVariableLong::Retrieve (71040f10)}

When calling ColumnVariableLong::Retrieve function, the first parameter is struct tagColParams, and this parameter will finally cause the crash. The pointer to tagColParams comes from ESI+0x60, where ESI points to a Cursor object:

address 07614e90 found in
_DPH_HEAP_ROOT @ 481000
in busy allocation (  DPH_HEAP_BLOCK:    UserAddr    UserSize -    VirtAddr    VirtSize)
                             7650820:     7614e90         16c -     7614000        2000
      msrd3x40!Cursor::`vftable'
70598e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
77020fe6 ntdll!RtlDebugAllocateHeap+0x00000030
76fdab8e ntdll!RtlpAllocateHeap+0x000000c4
76f83461 ntdll!RtlAllocateHeap+0x0000023a
71075ca7 msrd3x40!malloc+0x00000049

The key paramter:

0:000> dd edx
07614ef0  10e6efec 0031ded0 00000200 07614e90
07614f00  00000000 c0c0c0c0 c0c0c0c0 00000001
07614f10  c0c0c000 c0c0c0c0 c0c0c0c0 07354c30
07614f20  0cc2ffd0 00000600 ffffffff 0cc86005
07614f30  000007fb 00000000 0cc82200 0cc86000
07614f40  00000001 c0c0c0c0 00000000 00000000
07614f50  c0c0c0c0 00000000 00000000 00000800
07614f60  0cc86000 00000000 07614f74 00000004
0:000> dd poi(edx)
10e6efec  0ccb9000 00000000 00000000 00000000
10e6effc  d0d0d0d0 ???????? ???????? ????????
10e6f00c  ???????? ???????? ???????? ????????
10e6f01c  ???????? ???????? ???????? ????????
10e6f02c  ???????? ???????? ???????? ????????
10e6f03c  ???????? ???????? ???????? ????????
10e6f04c  ???????? ???????? ???????? ????????
10e6f05c  ???????? ???????? ???????? ????????

From the crash context, ESI is related to ECX:

ECX points to a TableMover Object:

0:000> r
eax=71031988 ebx=0cc59f30 ecx=10e6efec edx=07614ef0 esi=07614ef0 edi=0031e108
eip=7103fc30 esp=0031de1c ebp=00000001 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
msrd3x40!Record::IsNull:
7103fc30 83ec20          sub     esp,20h

    address 10e6efec found in
    _DPH_HEAP_ROOT @ 481000
    in busy allocation (  DPH_HEAP_BLOCK:    UserAddr    UserSize -    VirtAddr    VirtSize)
                                 cb813dc:    10e6efb8          44 -    10e6e000        2000
          msrd3x40!TableMover::`vftable'
    70598e89 verifier!AVrfDebugPageHeapAllocate+0x00000229
    77020fe6 ntdll!RtlDebugAllocateHeap+0x00000030
    76fdab8e ntdll!RtlpAllocateHeap+0x000000c4
    76f83461 ntdll!RtlAllocateHeap+0x0000023a
    71075ca7 msrd3x40!malloc+0x00000049
    7107553f msrd3x40!operator new+0x0000001d

Vendor Response

The vendor has acknowledged the issue and released an update to address it. This issue has been independently found and reported by other researchers – see the vendor advisory for details.

The vendor advisory can be found here: https://portal.msrc.microsoft.com/en-US/security-guidance/advisory/CVE-2019-1250.

Timeline

  • 2019-09-09 Reported to vendor
  • 2019-09-10 Vendor patched

Credit

Discovered by CodeBreaker and Meysam Firouzi

Want to participate in such cutting-edge research?

We are hiring!

Find Out More