RSS Security

❌ About FreshRSS
There are new articles available, click to refresh the page.
Today — 5 August 2021Research - Companies

Threat Source newsletter (Aug. 5, 2021)

  Newsletter compiled by Jon Munshaw.Good afternoon, Talos readers.   We hope everyone is enjoying BlackHat and/or DEFCON this week, regardless of if you're attending virtually or in person. In case you missed any of our talks from BlackHat, you can check them out here, along...

[[ This is only the beginning! Please visit the blog for the complete entry ]]

Analysis of a Heap Buffer-Overflow Vulnerability in Microsoft Windows Address Book

5 August 2021 at 12:26

By Eneko Cruz Elejalde


This post analyzes a heap-buffer overflow in Microsoft Windows Address Book. Microsoft released an advisory for this vulnerability for the 2021 February patch Tuesday. This post will go into detail about what Microsoft Windows Address Book is, the vulnerability itself, and the steps to craft a proof-of-concept exploit that crashes the vulnerable application.

Windows Address Book

Windows Address Book is a part of the Microsoft Windows operating system and is a service that provides users with a centralized list of contacts that can be accessed and modified by both Microsoft and third party applications. The Windows Address Book maintains a local database and interface for finding and editing information about contacts, and can query network directory servers using Lightweight Directory Access Protocol (LDAP). The Windows Address Book was introduced in 1996 and was later replaced by Windows Contacts in Windows Vista and subsequently by the People App in Windows 10.

The Windows Address Book provides an API that enables other applications to directly use its database and user interface services to enable services to access and modify contact information. While Microsoft has replaced the application providing the Address Book functionality, newer replacements make use of old functionality and ensure backwards compatibility. The Windows Address Book functionality is present in several Windows Libraries that are used by Windows 10 applications, including Outlook and Windows Mail. In this way, modern applications make use of the Windows Address Book and can even import address books from older versions of Windows.


A heap-buffer overflow vulnerability exists within the SecurityCheckPropArrayBuffer() function within wab32.dll when processing nested properties of a contact. The network-based attack vector involves enticing a user to open a crafted .wab file containing a malicious composite property in a WAB record.


The vulnerability analysis that follows is based on Windows Address Book Contacts DLL (wab32.dll) version 10.0.19041.388 running on Windows 10 x64.

The Windows Address Book Contacts DLL (i.e. wab32.dll) provides access to the Address Book API and it is used by multiple applications to interact with the Windows Address Book. The Contacts DLL handles operations related to contact and identity management. Among others, the Contacts DLL is able to import an address book (i.e, a WAB file) exported from an earlier version of the Windows Address Book.

Earlier versions of the Windows Address Book maintained a database of identities and contacts in the form of a .wab file. While current versions of Windows do not use a .wab file by default anymore, they allow importing a WAB file from an earlier installation of the Windows Address Book.

There are multiple ways of importing a WAB file into the Windows Address Book, but it was observed that applications rely on the Windows Contacts Import Tool (i.e, C:\Program Files\Windows Mail\wabmig.exe) to import an address book. The Import Tool loads wab32.dll to handle loading a WAB file, extracting relevant contacts, and importing them into the Windows Address Book.

WAB File Format

The WAB file format (commonly known as Windows Address Book or Outlook Address Book) is an undocumented and proprietary file format that contains personal identities. Identities may in turn contain contacts, and each contact might contain one or more properties.

Although the format is undocumented, the file-format has been partially reverse-engineered by a third party. The following structures were obtained from a combination of a publicly available third-party application and the disassembly of wab32.dll. Consequently, there may be inaccuracies in structure definitions, field names, and field types.

The WAB file has the following structure:

Offset      Length (bytes)    Field                   Description
---------   --------------    --------------------    -------------------
0x0         16                Magic Number            Sixteen magic bytes
0x10        4                 Count 1                 Unknown Integer
0x14        4                 Count 2                 Unknown Integer
0x18        16                Table Descriptor 1      Table descriptor
0x28        16                Table Descriptor 2      Table descriptor
0x38        16                Table Descriptor 3      Table descriptor
0x48        16                Table Descriptor 4      Table descriptor
0x58        16                Table Descriptor 5      Table descriptor
0x68        16                Table Descriptor 6      Table descriptor

All multi-byte fields are represented in little-endian byte order unless otherwise specified. All string fields are in Unicode, encoded in the UTF16-LE format.

The Magic Number field contains the following sixteen bytes: 9c cb cb 8d 13 75 d2 11 91 58 00 c0 4f 79 56 a4. While some sources list the sequence of bytes 81 32 84 C1 85 05 D0 11 B2 90 00 AA 00 3C F6 76 as a valid magic number for a WAB file, it was found experimentally that replacing the sequence of bytes prevents the Windows Address Book from processing the file.

Each of the six Table Descriptor fields numbered 1 through 6 has the following structure:

Offset    Length    Field    Description
-------   --------  -------  -------------------
0x0       4         Type     Type of table descriptor
0x4       4         Size     Size of the record described
0x8       4         Offset   Offset of the record described relative to the beginning of file
0xC       4         Count    Number of records present at offset

The following are examples of some known types of table descriptor:

  • Text Record (Type: 0x84d0): A record containing a Unicode string.
  • Index Record (Type: 0xFA0): A record that may contain several descriptors to WAB records.

Each text record has the following structure:

Offset   Length (bytes)    Field          Description
------   --------------    ------------   -------------------
0x0      N                 Content        Text content of the record; a null terminated UNICODE string
0x0+N    0x4               RecordId       A record identifier for the text record 

Similarly, each index record has the following structure

Offset      Length (bytes)    Field        Description
---------   --------------    ----------   -------------------
0x0         4                 RecordId     A record identifier for the index record
0x4         4                 Offset       Offset of the record relative to the beginning of the file

Each entry in the index record (i.e, each index record structure in succession) has an offset that points to a WAB record.

WAB Records

A WAB record is used to describe a contact. It contains fields such as email addresses and phone numbers stored in properties, which may be of various types such as string, integer, GUID, and timestamp. Each WAB record has the following structure:

Offset      Length   Field              Description
---------   ------   ---------------    -------------------
0x0         4        Unknown1           Unknown field
0x4         4        Unknown2           Unknown field
0x8         4        RecordId           A record identifier for the WAB record
0xC         4        PropertyCount      The number of properties contained in RecordProperties
0x10        4        Unknown3           Unknown field 
0x14        4        Unknown4           Unknown field 
0x18        4        Unknown5           Unknown field 
0x1C        4        DataLen            The length of the RecordProperties field (M)
0x20        M        RecordProperties   Succession of subproperties belonging to the WAB record

The following fields are relevant:

  • The RecordProperties field is a succession of record property structures.
  • The PropertyCount field indicates the number of properties within the RecordProperties field.

Record properties can be either simple or composite.

Simple Properties

Simple properties have the following structure:

Offset      Length (bytes)    Field       Description
---------   --------------    ---------   -------------------
0x0         0x2               Tag         A property tag describing the type of the contents
0x2         0x2               Unknown     Unknown field
0x4         0x4               Size        Size in bytes of Value member (X)
0x8         X                 Value       Property value or content

Tags of simple properties are smaller than 0x1000, and include the following:

Tag Name        Tag Value    Length      Description
---------       -----------  ---------   -------------------
PtypInteger16   0x00000002   2           A 16-bit integer
PtypInteger32   0x00000003   4           A 32-bit integer
PtypFloating32  0x00000004   4           A 32-bit floating point number
PtypFloating64  0x00000005   8           A 64-bit floating point number
PtypBoolean     0x0000000B   2           Boolean, restricted to 1 or 0
PtypString8     0x0000001E   Variable    A string of multibyte characters in externally specified
                                         encoding with terminating null character (single 0 byte)
PtypBinary      0x00000102   Variable    A COUNT field followed by that many bytes
PtypString      0x0000001F   Variable    A string of Unicode characters in UTF-16LE format encoding
                                         with terminating null character (0x0000).
PtypGuid        0x00000048   16          A GUID with Data1, Data2, and Data3 filds in little-endian
PtypTime        0x00000040   8           A 64-bit integer representing the number of 100-nanosecond
                                         intervals since January 1, 1601
PtypErrorCode   0x0000000A   4           A 32-bit integer encoding error information

Note the following:

  • The aforementioned list is not exhaustive. For more property tag definitions, see this.
  • The value of PtypBinary is prefixed by a COUNT field, which counts 16-bit words.
  • In addition to the above, the following properties also exist; their usage in WAB is unknown.
    • PtypEmbeddedTable (0x0000000D): The property value is a Component Object Model (COM) object.
    • PtypNull (0x00000001): None: This property is a placeholder.
    • PtypUnspecified (0x00000000): Any: this property type value matches any type;

Composite Properties

Composite properties have the following structure:

Offset  Length     Field             Description
------  ---------  ----------------- -------------------
0x0     0x2        Tag               A property tag describing the type of the contents
0x2     0x2        Unknown           Unknown field
0x4     0x4        NestedPropCount   Number of nested properties contained in the current WAB property
0x8     0x4        Size              Size in bytes of Value member (X)
0xC     X          Value             Property value or content

Tags of composite properties are greater than or equal to 0x1000, and include the following:

Tag Name                Tag Value
---------               ----------
PtypMultipleInteger16   0x00001002
PtypMultipleInteger32   0x00001003
PtypMultipleString8     0x0000101E
PtypMultipleBinary      0x00001102
PtypMultipleString      0x0000101F
PtypMultipleGuid        0x00001048
PtypMultipleTime        0x00001040

The Value field of each composite property contains NestedPropCount number of Simple properties of the corresponding type.

In case of fixed-sized properties (PtypMultipleInteger16, PtypMultipleInteger32, PtypMultipleGuid, and PtypMultipleTime), the Value field of a composite property contains NestedPropCount number of the Value field of the corresponding Simple property.

For example, in a PtypMultipleInteger32 structure with NestedPropCount of 4:

  • The Size is always 16.
  • The Value contains four 32-bit integers.

In case of variable-sized properties (PtypMultipleString8, PtypMultipleBinary, and PtypMultipleString), the Value field of the composite property contains NestedPropCount number of Size and Value fields of the corresponding Simple property.

For example, in a PtypMultipleString structure with NestedPropCount of 2 containing the strings “foo” and “bar” in Unicode:

  • The Size is 14 00 00 00.
  • The Value field contains a concatenation of the following two byte-strings:
    • “foo” encoded with a four-byte length: 06 00 00 00 66 00 6f 00 6f 00.
    • “bar” encoded with a four-byte length: 06 00 00 00 62 00 61 00 72 00.

Technical Details

The vulnerability in question occurs when a malformed Windows Address Book in the form of a WAB file is imported. When a user attempts to import a WAB file into the Windows Address Book, the method WABObjectInternal::Import() is called, which in turn calls ImportWABFile(). For each contact inside the WAB file, ImportWABFile() performs the following nested calls: ImportContact(), CWABStorage::ReadRecord(), ReadRecordWithoutLocking(), and finally HrGetPropArrayFromFileRecord(). This latter function receives a pointer to a file as an argument and reads the contact header and extracts PropertyCount and DataLen. The function HrGetPropArrayFromFileRecord() in turn calls SecurityCheckPropArrayBuffer() to perform security checks upon the imported file and HrGetPropArrayFromBuffer() to read the contact properties into a property array.

The function HrGetPropArrayFromBuffer() relies heavily on the correctness of the checks performed by SecurityCheckPropArrayBuffer(). However, the function fails to implement security checks upon certain property types. Specifically, SecurityCheckPropArrayBuffer() may skip checking the contents of nested properties where the property tag is unknown, while the function HrGetPropArrayFromBuffer() continues to process all nested properties regardless of the security check. As a result, it is possible to trick the function HrGetPropArrayFromBuffer() into parsing an unchecked contact property. As a result of parsing such a property, the function HrGetPropArrayFromBuffer() can be tricked into overflowing a heap buffer.

Code Analysis

The following code blocks show the affected parts of methods relevant to this vulnerability. Code snippets are demarcated by reference markers denoted by [N]. Lines not relevant to this vulnerability are replaced by a [Truncated] marker.

The following is the pseudocode of the function HrGetPropArrayFromFileRecord:


if ( !(unsigned int)SecurityCheckPropArrayBuffer(wab_buffer_full, HIDWORD(uBytes[1]), wab_buffer[3]) )

    result = 0x8004011b;        // Error
    goto LABEL_25;              // Return prematurely

  result = HrGetPropArrayFromBuffer(wab_buffer_full, HIDWORD(uBytes[1]), wab_buffer[3], 0, a7);

At [1] the function SecurityCheckPropArrayBuffer() is called to perform a series of security checks upon the buffer received and the properties contained within. If the check is positive, then the input is trusted and processed by calling HrGetPropArrayFromBuffer() at [3]. Otherwise, the function returns with an error at [2].

The following is the pseudocode of the function SecurityCheckPropArrayBuffer():

    __int64 __fastcall SecurityCheckPropArrayBuffer(unsigned __int8 *buffer_ptr, unsigned int buffer_length, int header_dword_3)
      unsigned int security_check_result; // ebx
      unsigned int remaining_buffer_bytes; // edi
      int l_header_dword_3; // er15
      unsigned __int8 *ptr_to_buffer; // r9
      int current_property_tag; // ecx
      __int64 c_dword_2; // r8
      unsigned int v9; // edi
      int VA; // ecx
      int VB; // ecx
      int VC; // ecx
      int VD; // ecx
      int VE; // ecx
      int VF; // ecx
      int VG; // ecx
      int VH; // ecx
      signed __int64 res; // rax
      _DWORD *ptr_to_dword_1; // rbp
      unsigned __int8 *ptr_to_dword_0; // r14
      unsigned int dword_2; // eax
      unsigned int v22; // edi
      int v23; // esi
      int v24; // ecx
      unsigned __int8 *c_ptr_to_property_value; // [rsp+60h] [rbp+8h]
      unsigned int v27; // [rsp+68h] [rbp+10h]
      unsigned int copy_dword_2; // [rsp+70h] [rbp+18h]

      security_check_result = 0;
      remaining_buffer_bytes = buffer_length;
      l_header_dword_3 = header_dword_3;
      ptr_to_buffer = buffer_ptr;
      if ( header_dword_3 )                      
        while ( remaining_buffer_bytes > 4 )        


          if ( *(_DWORD *)ptr_to_buffer & 0x1000 )  


            current_property_tag = *(unsigned __int16 *)ptr_to_buffer;
            if ( current_property_tag == 0x1102 ||                    
                 (unsigned int)(current_property_tag - 0x101E) <= 1 ) 

              ptr_to_dword_1 = ptr_to_buffer + 4;                     
              ptr_to_dword_0 = ptr_to_buffer;
              if ( remaining_buffer_bytes < 0xC )                     
                return security_check_result;                         
              dword_2 = *((_DWORD *)ptr_to_buffer + 2);
              v22 = remaining_buffer_bytes - 0xC;
              if ( dword_2 > v22 )                                     
                return security_check_result;                         
              ptr_to_buffer += 12;
              copy_dword_2 = dword_2;
              remaining_buffer_bytes = v22 - dword_2;
              c_ptr_to_property_value = ptr_to_buffer;                
              v23 = 0;                                                
              if ( *ptr_to_dword_1 > 0u )
                while ( (unsigned int)SecurityCheckSingleValue(
                                        *(_DWORD *)ptr_to_dword_0,
                                        ©_dword_2) )
                  if ( (unsigned int)++v23 >= *ptr_to_dword_1 )       
                    ptr_to_buffer = c_ptr_to_property_value;
                    goto LABEL_33;
                return security_check_result;

              if ( remaining_buffer_bytes < 0xC )
                return security_check_result;
              c_dword_2 = *((unsigned int *)ptr_to_buffer + 2);       
              v9 = remaining_buffer_bytes - 12;
              if ( (unsigned int)c_dword_2 > v9 )                     
                return security_check_result;                         
              remaining_buffer_bytes = v9 - c_dword_2;
              VA = current_property_tag - 0x1002;                     
              if ( VA )
                VB = VA - 1;
                if ( VB && (VC = VB - 1) != 0 )
                  VD = VC - 1;
                  if ( VD && (VE = VD - 1) != 0 && (VF = VE - 1) != 0 && (VG = VF - 13) != 0 && (VH = VG - 44) != 0 )
                    res = VH == 8 ? 16i64 : 0i64;
                    res = 8i64;
                  res = 4i64;
                res = 2i64;
              if ( (unsigned int)c_dword_2 / *((_DWORD *)ptr_to_buffer + 1) != res ) 
                return security_check_result;                                        

              ptr_to_buffer += c_dword_2 + 12;


            if ( remaining_buffer_bytes < 4 )       
              return security_check_result;
            v24 = *(_DWORD *)ptr_to_buffer;         
            c_ptr_to_property_value = ptr_to_buffer + 4;// new exe: v13 = buffer_ptr + 4;
            v27 = remaining_buffer_bytes - 4;       
            if ( !(unsigned int)SecurityCheckSingleValue(v24, &c_ptr_to_property_value, &v27) )
              return security_check_result;
            remaining_buffer_bytes = v27;
            ptr_to_buffer = c_ptr_to_property_value;
          if ( !--l_header_dword_3 )
      if ( !l_header_dword_3 )
        security_check_result = 1;
      return security_check_result;

At [4] the tag of the property being processed is checked. The checks performed depend on whether the property processed in each iteration is a simple or a composite property. For simple properties (i.e, properties with tag lower than 0x1000), execution continues at [8]. The following checks are done for simple properties:

  1. If the remaining number of bytes in the buffer is fewer than 4, the function returns with an error.
  2. A pointer to the property value is obtained and SecurityCheckSingleValue() is called to perform a security check upon the simple property and its value. SecurityCheckSingleValue() performs a security check and increments the pointer to point at the next property in the buffer, so that SecurityCheckPropArrayBuffer() can check the next property on the next iteration.
  3. The number of total properties is decremented and compared to zero. If equal to zero, then the function returns successfully. If different, the next iteration of the loop checks the next property.

Similarly, for composite properties (i.e, properties with tag equal or higher than 0x1000) execution continues at [5] and the following is done.

For variable length composite properties (if the property tag is equal to 0x1102 (PtypMultipleBinary) or equal or smaller than 0x101f (PtypMultipleString)), the code at [6] does the following:

  1. The number of bytes left to read in the buffer is compared with 0xC to avoid overrunning the buffer.
  2. The Size field of the property is compared to the remaining buffer length to avoid overrunning the buffer.
  3. For each nested property, the function SecurityCheckSingleValue() is called. It:
    1. Performs a security check on the nested property.
    2. Advances the pointer to the buffer held by the caller, in order to point to the next nested property.
  4. The loop runs until the number of total properties in the contact (decremented in each iteration) is zero.

For fixed-length composite properties (if the property tag in question is different from 0x1102 (PtypMultipleBinary) and larger than 0x101f (PtypMultipleString)), the following happens starting at [7]:

  1. The number of bytes left to read in the buffer is compared with 0xC to avoid overrunning the buffer.
  2. The Size is compared to the remaining buffer length to avoid overrunning the buffer.
  3. The size of each nested property, which depends only on the property tag, is calculated from the parent property tag.
  4. The Size is divided by NestedPropCount to obtain the size of each nested property.
  5. The function returns with an error if the calculated subproperty size is different from the property size deduced from parent property tag.
  6. The buffer pointer is incremented by the size of the parent property value to point to the next property.

Unknown or non-processable property types are assigned the nested property size 0x0.

It was observed that if the calculated property size is zero, the buffer pointer is advanced by the size of the property value, as described by the header. The buffer is advanced regardless of the property size and by advancing the buffer, the security check permits the value of the parent property (which may include subproperties) to stay unchecked. For the security check to pass the result of the division performed on Step 4 for fixed-length composite properties must be zero. Therefore for an unknown or non-processable property to pass the security check, NestedPropCount must be larger than Size. Note that since the size of any property in bytes is at least two, NestedPropCount must always be no larger than half of Size, and therefore, the aforementioned division must never be zero in benign cases.

After the checks have concluded, the function returns zero for a failed check and one for a passed check.

Subsequently, the function HrGetPropArrayFromFileRecord() calls HrGetPropArrayFromBuffer(), which aims to collect the properties into an array of _SPropValue structs and return it to the caller. The _SPropValue array has a length equal of the number of properties (as given by the contact header) and is allocated in the heap through a call to LocalAlloc(). The number of properties is multiplied by sizeof(_SPropValue) to yield the total buffer size. The following fragment shows the allocation taking place:

    if ( !property_array_r )
        ret = -2147024809;
        goto LABEL_71;
    *property_array_r = 0i64;
    header_dword_3_1 = set_to_zero + header_dword_3;


    if ( (unsigned int)header_dword_3_1 < header_dword_3       
      || (unsigned int)header_dword_3_1 > 0xAAAAAAA            
      || (v10 = (unsigned int)header_dword_3_1,                               
          property_array = (struct _SPropValue *)LocalAlloc(
                                                   0x18 * header_dword_3_1),
                                                   // sizeof(_SPropValue) * n_properties_in_binary
        (*property_array_r = property_array) == 0i64) )
        ret = 0x8007000E;
        goto LABEL_71;

An allocation of sizeof(_SPropValue) * n_properties_in_binary can be observed at [9]. Immediately after, each of the property structures are initialized and their property tag member is set to 1. After initialization, the buffer, on which security checks have already been performed, is processed property by property, advancing the property a pointer to the next property with the property header and value sizes provided by the property in question.

If the property processed by the specific loop iteration is a simple property, the following code is executed:

    if ( !_bittest((const signed int *)¤t_property_tag, 0xCu) )
      if ( v16 < 4 )
      dword_1 = wab_ulong_buffer_full[1];
      ptr_to_dword_2 = (char *)(wab_ulong_buffer_full + 2);
      v38 = v16 - 4;
      if ( (unsigned int)dword_1 > v38 )
      current_property_tag = (unsigned __int16)current_property_tag;
      if ( (unsigned __int16)current_property_tag > 0xBu )


        v39 = current_property_tag - 0x1E;
        if ( !v39 )
          goto LABEL_79;
        v40 = v39 - 1;
        if ( !v40 )
          goto LABEL_79;
        v41 = v40 - 0x21;
        if ( !v41 )
          goto LABEL_56;
        v42 = v41 - 8;
        if ( v42 )
          if ( v42 != 0xBA )
            goto LABEL_56;
          v43 = dword_1;
          (*property_array_r)[p_idx].Value.bin.lpb = (LPBYTE)LocalAlloc(0x40u, dword_1);
          if ( !(*property_array_r)[p_idx].Value.bin.lpb )
          (*property_array_r)[p_idx].Value.l = dword_1;
          v44 = *(&(*property_array_r)[p_idx] + 1);

          v43 = dword_1;
          (*property_array_r)[p_idx].Value.cur.int64 = (LONGLONG)LocalAlloc(0x40u, dword_1);
          v44 = (*property_array_r)[p_idx].Value.dbl;
          if ( v44 == 0.0 )
        memcpy_0(*(void **)&v44, ptr_to_dword_2, v43);
        wab_ulong_buffer_full = (ULONG *)&ptr_to_dword_2[v43];

        memcpy_0(&(*property_array_r)[v15].Value, ptr_to_dword_2, dword_1);
        wab_ulong_buffer_full = (ULONG *)&ptr_to_dword_2[dword_1];
      remaining_bytes_to_process = v38 - dword_1;
      goto NEXT_PROPERTY;


        processed_property_count = (unsigned int)(processed_property_count_1 + 1);
        processed_property_count_1 = processed_property_count;
        if ( (unsigned int)processed_property_count >= c_header_dword_3 )
          return 0;

At [10] the property tag is extracted and compared with several constants. If the property tag is 0x1e (PtypString8), 0x1f (PtypString), or 0x48 (PtypGuid), then execution continues at [11]. If the property tag is 0x40 (PtypTime) or is not recognized, execution continues at [12]. The memcpy call at [12] is prone to a heap overflow.

Conversely, if the property being processed in the specific loop iteration is not a simple property, the following code is executed. Notably, when the following code is executed, the pointer DWORD* wab_ulong_buffer_full points to the property tag of the property being processed. Regardless of which composite property is being processed, before the property tag is identified the buffer is advanced to point to the beginning of the property value, which is at the 4th 32-bit integer.


    if ( v16 < 4 )
    c_dword_1 = wab_ulong_buffer_full[1];
    v19 = v16 - 4;
    if ( v19 < 4 )
    dword_2 = wab_ulong_buffer_full[2];
    wab_ulong_buffer_full += 3;                     

    remaining_bytes_to_process = v19 - 4;


    if ( (unsigned __int16)current_property_tag >= 0x1002u )
    if ( (unsigned __int16)current_property_tag <= 0x1007u || (unsigned __int16)current_property_tag == 0x1014 )
        goto LABEL_80;
    if ( (unsigned __int16)current_property_tag == 0x101E )
    if ( (unsigned __int16)current_property_tag == 0x101F )
    if ( ((unsigned __int16)current_property_tag - 0x1040) & 0xFFFFFFF7 )
        if ( (unsigned __int16)current_property_tag == 0x1102 )


        (*property_array_r)[p_idx].Value.bin.lpb = (LPBYTE)LocalAlloc(0x40u, dword_2);
        if ( !(*property_array_r)[p_idx].Value.bin.lpb )
        (*property_array_r)[p_idx].Value.l = c_dword_1;
        if ( (unsigned int)dword_2 > remaining_bytes_to_process )
        memcpy_0((*property_array_r)[p_idx].Value.bin.lpb, wab_ulong_buffer_full, dword_2);
        wab_ulong_buffer_full = (ULONG *)((char *)wab_ulong_buffer_full + dword_2);
        remaining_bytes_to_process -= dword_2;

        processed_property_count = (unsigned int)(processed_property_count_1 + 1);
        processed_property_count_1 = processed_property_count;
        if ( (unsigned int)processed_property_count >= c_header_dword_3 )
        return 0;

After the buffer has been advanced at [13], the property tag is compared with several constants starting at [14]. Finally, the code fragment at [15] attempts to process a composite property (i.e. >= 0x1000) with a tag not contemplated by the previous constants.

Although the processing logic of each type of property is irrelevant, an interesting fact is that if the property tag is not recognized, the buffer pointer has still been advanced to the end of the end of its header, and it’s never retracted. This happens if all of the following conditions are met:

  • The property tag is larger or equal than 0x1002.
  • The property tag is larger than 0x1007.
  • The property tag is different from 0x1014.
  • The property tag is different from 0x101e.
  • The property tag is different from 0x101f.
  • The property tag is different from 0x1102.
  • The result of subtracting 0x1040 from the property tag, and performing a bitwise AND of the result with 0xFFFFFFF7 is nonzero.

Interestingly, if all of the above conditions are met, the property header of the composite property is skipped, and the next loop iteration will interpret its property body as a different property.

Therefore, it is possible to overflow the _SPropValue array allocated in the heap by HrGetPropArrayFromBuffer() by using the following observations:

  • A specially crafted composite unknown or non-processable property can be made to bypass security checks if NestedPropCount is larger than Size.
  • HrGetPropArrayFromBuffer() can be made to interpret the Value of a specially crafted property as a separate property.


In order to create a malicious WAB file from a benign WAB file, export a valid WAB file from an instance of the Windows Address Book. It is noted that Outlook Express on Windows XP includes the ability to export contacts as a WAB file.

The benign WAB file can be modified to make it malicious by altering a contact inside it to have the following characteristics:

  • A nested property containing the following:
  • A tag of an unknown or unprocessable type, for example the tag 0x1058, with the following conditions:
    • Must be larger or equal than 0x1002.
    • Must be larger than 0x1007.
    • Must be different from 0x1014, 0x101e, 0x101f, and 0x1102.
    • The result of subtracting 0x1040 from the property tag, and performing a bitwise AND of the result with 0xFFFFFFF7 is non-zero.
    • Must be different from 0x1002, 0x1003, 0x1004, 0x1005, 0x1006, 0x1007, 0x1014, 0x1040, and 0x1048.
    • NestedPropCount is larger than Size.
    • The Value of the composite property is empty.
    • A malicious simple property containing the following:
    • A property tag different from 0x1e, 0x1f, 0x40 and 0x48. For example, the tag 0x0.
    • The Size value is larger than 0x18 x NestedPropCount in order to overflow the _SPropValue array buffer.
    • An unspecified number of trailing bytes, that will overflow the _SPropValue array buffer.

Finally, when an attacker tricks an unsuspecting user into importing the specially crafted WAB file, the vulnerability is triggered and code execution could be achieved. Failed exploitation attempts will most likely result in a crash of the Windows Address Book Import Tool.

Due to the presence of ASLR and a lack of a scripting engine, we were unable to obtain arbitrary code execution in Windows 10 from this vulnerability.


Hopefully you enjoyed this dive into CVE-2021-24083, and if you did, go ahead and check out our other blog post on a use-after-free vulnerability in Adobe Acrobat Reader DC. If you haven’t already, make sure to follow us on Twitter to keep up to date with our work. Happy hacking!

The post Analysis of a Heap Buffer-Overflow Vulnerability in Microsoft Windows Address Book appeared first on Exodus Intelligence.

Interview with an NVISO Intern – Writing Custom Beacon Object Files

5 August 2021 at 05:48

During the first months of this year, Sander joined our ‘Software Security & Assessments’ team as an intern and worked on writing Custom Beacon Object Files for the Cobalt Strike C2 framework.

Below you can find out how it all went!

Who are you?

My name is Sander, @cerbersec on twitter. I’m a student information management & cybersecurity at Thomas More and I’ve had the opportunity to do an 8-week internship in NVISO’s Red Team.

How did you discover NVISO?

NVISO co-organizes the Cyber Security Challenge Belgium and there I noticed they had open internship positions for IoT projects. I’ve always been passionate about offensive security and red teaming. Needless to say, I was pleasantly surprised when NVISO offered an internship position in a red team where I’d be working on research for new tooling.

What was it like to be an intern at NVISO?

With the ongoing COVID-19 pandemic, the internship would be fully remote. This meant that meeting colleagues would be slightly more difficult, and the entire internship required a high degree of autonomy. Regardless, I felt very welcome from the start. I had a kick-off meeting with my mentors where I could ask any questions I had and received a nudge in the right direction to get me started.

The overall atmosphere was very pleasant and light-hearted, they encouraged me to ask questions, but also made sure I did my own research first. I learned a lot just from sharing and discussing potentially interesting topics and techniques we came across, not all of them necessarily pertaining to my internship topic. Working with like-minded people really motivated me to push myself.

As my project developed over time, I got the opportunity to present my work during lunch to colleagues from different teams and even from different countries! NVISO regularly organizes Brown Bag sessions during lunch for anybody to attend. During these sessions an NVISO expert presents their insights into a currently hot topic. As an intern, I also got the opportunity to present my own project during lunch. My presentation was met with great feedback and opened up a discussion on the presented techniques, their strengths and possible shortcomings.

On my final day I had a chance to see the office and meet my mentors and colleagues in person. NVISO has a shared open space and after a tour of the office and some quick work, we went for lunch together, which gave me a chance to get to know my mentors in real life and share my experience as an intern. To finish the day, I demoed my project and discussed my findings.

What was your internship project?

When I first met my mentors Jonas and Jean-François, they came up with a variety of topics that would directly contribute to the daily operations of the different red teams. This allowed me to choose topics I’d like to work on which would be in line with my skills, experience, and interests. Little did I know I picked one of the toughest of them all.

I’ve always had a secret love for coding and recently ventured into malware analysis, which turned out to be a great stepping stone towards writing malware. For 8 weeks I would be working on custom process injection techniques and investigate alternative methods to exfiltrate C2 traffic since Domain Fronting is no longer a viable option (at least not “legitimately” via Azure).

My main task consisted of writing custom Beacon Object Files (BOFs) for the Cobalt Strike C2 framework, to perform process injection and bypass Endpoint Detection and Response (EDR) and Anti-Virus (AV) products by using direct syscalls. As my secondary task, I researched different protocols and techniques like DNS over HTTPS (DoH), Domain Borrowing and Software as a Service (SaaS) for exfiltrating C2 traffic.

Working with the Windows Native API was definitely a challenge at first, as there is very limited documentation available, debugging code that is not fully compiled into an executable is a challenge on its own, and use of undocumented functions requires a lot of trial and error to get right. I wrote most of my code in C, which took some time to relearn as I only had limited experience with the language before I started. During development it quickly became apparent that relying on documentation only wasn’t going to get me the results I needed, so I had to break out a debugger and dive into the assembly instructions to figure out where things were going wrong.

As a result, I was able to provide BOFs making use of various injection techniques and ported the recently disclosed Domain Borrowing technique to CobaltStrike (

What is your conclusion?

Overall, I had a great time and got to know some very cool people. I got to explore the highly technical concepts behind malware and at the same time translate this into an easily understandable format so a less technical audience could still understand my work. I’m excited to keep developing these skills and hopefully return for a second internship very soon.

Yesterday — 4 August 2021Research - Companies

Beers with Talos, Ep. #108: Kaseya it ain't so

Beers with Talos (BWT) Podcast episode No. 108 is now available. Download this episode and subscribe to Beers with Talos:Apple Podcasts Google PodcastsSpotify  StitcherIf iTunes and Google Play aren't your thing, click here. Who needed a summer vacation anyway? The whole Beers...

[[ This is only the beginning! Please visit the blog for the complete entry ]]

Vulnerability Spotlight: Use-after-free vulnerability in tinyobjloader

Lilith >_> of Cisco Talos discovered this vulnerability. Blog by Jon Munshaw. Cisco Talos recently discovered that a specific function of tinyobjloader does not properly validate array indexes. An adversary could trick a user into opening a specially crafted file, causing an index...

[[ This is only the beginning! Please visit the blog for the complete entry ]]

Hotcobalt – New Cobalt Strike DoS Vulnerability That Lets You Halt Operations

4 August 2021 at 12:58

Executive Summary

  • Versions 4.2 and 4.3 of Cobalt Strike’s server contain multiple Denial of Service vulnerabilities (CVE-2021-36798).
  • The vulnerabilities can render existing Beacons unable to communicate with their C2 server, prevent new beacons from being installed, and have the potential to interfere with ongoing operations.
  • We have released a new Python library to help generically parse Beacon communication in order to help the research security community.


Cobalt Strike is one of the most popular attack frameworks designed for Red Team operations. At the same time, many APTs and malicious actors also use it.

SentinelOne has seen numerous attacks involving Cobalt Strike Beacons across our customer base. SentinelOne detects Cobalt Strike Beacon and we are constantly rolling out new ways to detect modifications or novel ways to load Beacon in memory.

Given its rampant adoption by red teams and attackers alike, we wanted to better understand the operational security of Cobalt Strike. This led us to discover the vulnerabilities reported in CVE-2021-36798 and which we describe below.

Beacon Communications

To understand the vulnerabilities we found, we will briefly cover how Cobalt Strike Beacon communication works.

The first time the Cobalt Strike server runs, it creates randomly generated RSA keys, private and public, stored in a file named “.Cobalt Strike.beacon_keys”. Every Beacon stager has the public key embedded in it.

We can get the Beacon’s public RSA key by parsing its configuration

When a Beacon stager runs, it gathers information about the computer it is running on (CPU architecture, keyboard layout, internal IP, etc.), encrypts that info using the public key, and sends it to the server in an HTTP GET request. We will refer to that part as “Beacon registration”.

After the Beacon has registered with the server, the attacker can interact with the Beacon. From this point, the Beacon works by receiving and replying to “tasks”. Tasks can, for example, be used to get a process list, run a command, conduct lateral movement, and many other things of interest to the attacker.

Receiving tasks generally happens over HTTP GET requests and the Beacon replies with the task data over HTTP POST requests. Tasks are encrypted using an AES key sent by the Beacon in the registration request. The entire communication flow is explained in the official documentation, but the outline above should suffice for what follows.

One of the most famous features of Cobalt Strike is its Malleable C2. In short, this feature lets the attacker encode (“transform” in Cobalt’s language) all the beacon’s HTTP communications. The entire process described above is wrapped in the chosen Malleable profile’s transformation steps, which are also embedded in the stager itself.

Below is an example of a popular Malleable C2 profile that masquerades traffic as a normal request for the jquery code (source):

An example of a popular Malleable C2 profile


First, it should be noted that there was already one known vulnerability in Cobalt Strike that was previously reported. A great write-up written by nccgroup is worth reading for a more in-depth understanding of Beacon’s communication internals. In practice, that vulnerability allowed for remote code execution on the server.

We’re not interested in remote code execution vulnerability here as it would be overkill for our purposes. Considering that the server’s code is written in Java and isn’t very large, it wasn’t too hard to find bugs there.

For example, in the Screenshot and Keylogger task replies, there’s an interesting behavior when reading the reply’s data:

public void process_beacon_callback_decrypted(final String beaconID, final byte[] responseBytes) {
// Sanity checks here
try {
final DataInputStream responeBytesStream = new DataInputStream(new ByteArrayInputStream(responseBytes));
cmd = responeBytesStream.readInt();
if (cmd == 0) {...}
else if (cmd == 3) {
	final DataParser dp = new DataParser(CommonUtils.readAll(responeBytesStream));
	final byte[] scData = dp.readCountedBytes();    // Bug #1 here
	final int scDesktop = dp.readInt();
	final String scTitle = this.getCharsets().process(beaconID, dp.readCountedBytes());
	final String process6 = this.getCharsets().process(beaconID, dp.readCountedBytes());
	if (scData.length == 0) {
		output(BeaconOutput.Error(beaconID, "screenshot from desktop " + scDesktop + " is empty"));
	output(BeaconOutput.OutputB(beaconID, "received screenshot of " + scTitle + " from " + process6 + " (" + CommonUtils.formatSize(scData.length) + ")"));

In this example, we see the parsing of a screenshot task reply. To read the screenshot’s data, it calls the function readCountedBytes, which reads an integer from the first four bytes of the data and treats it as the screenshot’s size without any sanity checks.

Then, before reading the screenshot’s data, it allocates a buffer big enough to hold it:

byte[] array = new byte[ReplySize];

By manipulating the screenshot’s size we can make the server allocate an arbitrary size of memory, the size of which is totally controllable by us. However, in order to trigger this piece of code, we need to be able to talk to the server like a Beacon.

By combining all the knowledge of Beacon communication flow with our configuration parser, we have all we need to fake a Beacon.

We’ve published a POC python script that does just that: it parses a Beacon’s configuration and uses the information stored in it to register a new random Beacon on the server. After registering the Beacon, it’s pretty trivial to use the primitive found above to iteratively send fake task replies that squeeze every bit of available memory from the C2’s web server thread:

size = 1000000000
while True:
        if size 

This leads to the crashing of the server’s web thread that handles HTTP stagers and Beacon communication:

Crashing the server's web thread

This would allow an attacker to cause memory exhaustion in the Cobalt Strike server  (the “Teamserver”) making the server unresponsive until it's restarted. This means that live Beacons cannot communicate to their C2 until the operators restart the server.

Restarting, however, won’t be enough to defend against this vulnerability as it is possible to repeatedly target the server until it is patched or the Beacon’s configuration is changed.

Either of these will make the existing live Beacons obsolete as they’ll be unable to communicate with the server until they’re updated with the new configuration. Therefore, this vulnerability has the potential to severely interfere with ongoing operations.

Although used every day for malicious attacks, Cobalt Strike is ultimately a legitimate product, so we have disclosed these issues responsibly to HelpSystems and they have fixed the vulnerabilities in the last release.


On our Cobalt Strike parser repository, we’ve added new modules and code examples that implement:

  • Parsing of a Beacon’s embedded Malleable profile instructions
  • Parsing of a Beacon’s configuration directly from an active C2 (like the popular nmap script)
  • Basic code for communicating with a C2 as a fake Beacon

Other than registering a fake Beacon with the server, the code we are releasing makes it easier to parse captured Beacon communications in a generic way.

Let’s take, for example, a case of a captured unencrypted Beacon communication from malware-traffic-analysis and decode it using the new communication module:

from urllib import parse
from pcaper import PcapParser
from parse_beacon_config import *
from comm import *

conf = cobaltstrikeConfig(r"beacon.bin").parse_config()
pparser = PcapParser()
reqs = pparser.read_pcap({'input': r"2019-07-25-Hancitor-style-Amadey-with-Pony-and-Cobalt-Strike.pcap"})

t = Transform(conf['HttpPost_Metadata'])
for req in reqs:
	if conf['HttpPostUri'] in req.uri:
		params = {k: v[0] for k, v in parse.parse_qs(parse.urlsplit(req.uri).query).items()}		
		print('\n\nFound beacon reply:\n', t.decode(req.body, req.headers, params)[1])


Found beacon reply:
 ♠r↓ (platform: 500 version: 6.1 name: HIDDENROAD-PC domain: WORKGROUP)
Scanner module is complete

Found beacon reply:
 ☺►[*] Wrote hijack DLL to 'C:\Users\SARAH~1.RUT\AppData\Local\Temp\745f.dll'
[+] Privileged file copy success! C:\Windows\System32\sysprep\CRYPTBASE.dll
[+] C:\Windows\System32\sysprep\sysprep.exe ran and exited.
[*] Cleanup successful

It parses the Malleable C2 instructions embedded in the Beacon’s configuration and uses it to decode Beacon replies from the captured HTTP requests.

There’s a lot that can be done with this new communication library and it will be interesting to see what other researchers from the community will do with it.


Research into attack frameworks like Cobalt Strike and Cellebrite is still a niche area. We hope that this research and the tools we have released help to further encourage research into the robustness of attack frameworks and expand the range of available options when facing their consistent abuse.

Disclosure Timeline

We would like to thank HelpSystems for their approach to our disclosure and for remediating the vulnerabilities.

04/20/2021 - Initial contact with HelpSystems for issue disclosure.
04/22/2021 - Issue details disclosed to HelpSystems.
04/23/2021 - HelpSystems confirmed the issue and asked for an extension until August 3rd.
04/28/2021 - SentinelOne accepted the extension.
07/18/2021 - Submitted CVE request to MITRE.
07/19/2021 - CVE-2021-36798 was assigned and reserved for the specified issue.
08/02/2021 - SentinelOne shared the publication date and post for review.
08/02/2021 - HelpSystems reviewed and confirmed the post for publication.
08/04/2021 - HelpSystems released Cobalt Strike 4.4, which contains a fix for CVE-2021-36798.

All issues found by SentinelOne are disclosed to the relevant third party according to our Responsible Disclosure Policy for Third Parties.

The post Hotcobalt – New Cobalt Strike DoS Vulnerability That Lets You Halt Operations appeared first on SentinelLabs.

Before yesterdayResearch - Companies

Updates to the Cisco Talos Email Status Portal

Cisco Talos is rolling out several changes to the Email Status Portal that adds new features and makes the Portal even easier to use.  The Talos Email Status Portal allows users to view mail samples submitted and their statuses, analyze graphical displays of submission metrics, administer...

[[ This is only the beginning! Please visit the blog for the complete entry ]]

Preventing Physical Security Attacks Against Your Business

2 August 2021 at 21:41

How easy is it to breach your physical security controls? In this article, Versprite shares real red teaming cases where our physical security team hacked into security cameras, cloned badges, walked past guards, and exploited office cybersecurity.

The post Preventing Physical Security Attacks Against Your Business appeared first on VerSprite.