Talos Vulnerability Report

TALOS-2023-1830

Accusoft ImageGear tif_parse_sub_IFD use-after-free vulnerability

September 25, 2023
CVE Number

CVE-2023-39453

SUMMARY

A use-after-free vulnerability exists in the tif_parse_sub_IFD functionality of Accusoft ImageGear 20.1. A specially crafted malformed file can lead to arbitrary code execution. An attacker can deliver this file to trigger this vulnerability.

CONFIRMED VULNERABLE VERSIONS

The versions below were either tested or verified to be vulnerable by Talos or confirmed to be vulnerable by the vendor.

Accusoft ImageGear 20.1

PRODUCT URLS

ImageGear - https://www.accusoft.com/products/imagegear-collection/

CVSSv3 SCORE

9.8 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

CWE

CWE-416 - Use After Free

DETAILS

The ImageGear library is a document-imaging developer toolkit that offers image conversion, creation, editing, annotation and more. It supports more than 100 formats such as DICOM, PDF, Microsoft Office and others.

Loading a malformed raw file leads to the following:

(878.19c8): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=0000f0f0 ebx=00000093 ecx=1000001f edx=1000001f esi=72f10660 edi=72f11008
eip=73bd7d30 esp=0019f30c ebp=0019f31c iopl=0         nv up ei ng nz na pe cy
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010287
igCore20d!IG_mpi_page_set+0x10bc10:
73bd7d30 66837ffa63      cmp     word ptr [edi-6],63h     ds:002b:72f11002=????

Looking at the call stack gives us interesting details. There is without any doubt some recurrent call.

0:000> kb
 # ChildEBP RetAddr      Args to Child              
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0019f31c 73bd7d4d     1000001f 72f10660 3371c5f0 igCore20d!IG_mpi_page_set+0x10bc10
01 0019f33c 73bd7d4d     1000001f 1dae59f8 633cbe90 igCore20d!IG_mpi_page_set+0x10bc2d
02 0019f35c 73bd7d4d     1000001f 3371c170 2c2dc5f0 igCore20d!IG_mpi_page_set+0x10bc2d
03 0019f37c 73bd7d4d     1000001f 633cb9f8 26176e90 igCore20d!IG_mpi_page_set+0x10bc2d
04 0019f39c 73bd7d4d     1000001f 2c2dc170 23b685f0 igCore20d!IG_mpi_page_set+0x10bc2d
05 0019f3bc 73bd7d4d     1000001f 261769f8 21471e90 igCore20d!IG_mpi_page_set+0x10bc2d
06 0019f3dc 73bd7d4d     1000001f 23b68170 65e425f0 igCore20d!IG_mpi_page_set+0x10bc2d
07 0019f3fc 73bd7d4d     1000001f 214719f8 63748e90 igCore20d!IG_mpi_page_set+0x10bc2d
08 0019f41c 73bd7d4d     1000001f 65e42170 1959a5f0 igCore20d!IG_mpi_page_set+0x10bc2d
09 0019f43c 73bd7d4d     1000001f 637489f8 19195e90 igCore20d!IG_mpi_page_set+0x10bc2d
0a 0019f45c 73bd7d4d     1000001f 1959a170 29ae85f0 igCore20d!IG_mpi_page_set+0x10bc2d
0b 0019f47c 73bd7d4d     1000001f 191959f8 50f2de90 igCore20d!IG_mpi_page_set+0x10bc2d
0c 0019f49c 73bd7d4d     1000001f 29ae8170 189f45f0 igCore20d!IG_mpi_page_set+0x10bc2d
0d 0019f4bc 73bd7d4d     1000001f 50f2d9f8 1ec68e90 igCore20d!IG_mpi_page_set+0x10bc2d
0e 0019f4dc 73bd7d4d     1000001f 189f4170 0e81c5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0f 0019f4fc 73bd7d4d     1000001f 1ec689f8 726ade90 igCore20d!IG_mpi_page_set+0x10bc2d
10 0019f51c 73bd7d4d     1000001f 0e81c170 6fb665f0 igCore20d!IG_mpi_page_set+0x10bc2d
11 0019f53c 73bd7d4d     1000001f 726ad9f8 6d350e90 igCore20d!IG_mpi_page_set+0x10bc2d
12 0019f55c 73bd7d4d     1000001f 6fb66170 685a45f0 igCore20d!IG_mpi_page_set+0x10bc2d
13 0019f57c 73bd7d4d     1000001f 6d3509f8 65e1ce90 igCore20d!IG_mpi_page_set+0x10bc2d
14 0019f59c 73bd7d4d     1000001f 685a4170 1b0f85f0 igCore20d!IG_mpi_page_set+0x10bc2d
15 0019f5bc 73bd7d4d     1000001f 65e1c9f8 5f916e90 igCore20d!IG_mpi_page_set+0x10bc2d
16 0019f5dc 73bd7d4d     1000001f 1b0f8170 3853e5f0 igCore20d!IG_mpi_page_set+0x10bc2d
17 0019f5fc 73bd7d4d     1000001f 5f9169f8 1c759e90 igCore20d!IG_mpi_page_set+0x10bc2d
18 0019f61c 73bd7d4d     1000001f 3853e170 584315f0 igCore20d!IG_mpi_page_set+0x10bc2d
19 0019f63c 73bd7d4d     1000001f 1c7599f8 0d327e90 igCore20d!IG_mpi_page_set+0x10bc2d
1a 0019f65c 73bd7d4d     1000001f 58431170 40a965f0 igCore20d!IG_mpi_page_set+0x10bc2d
1b 0019f67c 73bd7d4d     1000001f 0d3279f8 0b8ace90 igCore20d!IG_mpi_page_set+0x10bc2d
1c 0019f69c 73bd7d4d     1000001f 40a96170 523a45f0 igCore20d!IG_mpi_page_set+0x10bc2d
1d 0019f6bc 73bd7d4d     1000001f 0b8ac9f8 30e09e90 igCore20d!IG_mpi_page_set+0x10bc2d
1e 0019f6dc 73bd7d4d     1000001f 523a4170 54a395f0 igCore20d!IG_mpi_page_set+0x10bc2d
1f 0019f6fc 73bd7d4d     1000001f 30e099f8 2d518e90 igCore20d!IG_mpi_page_set+0x10bc2d
20 0019f71c 73bd7d4d     1000001f 54a39170 442bc5f0 igCore20d!IG_mpi_page_set+0x10bc2d
21 0019f73c 73bd7d4d     1000001f 2d5189f8 699c9e90 igCore20d!IG_mpi_page_set+0x10bc2d
22 0019f75c 73bd7d4d     1000001f 442bc170 0bdf75f0 igCore20d!IG_mpi_page_set+0x10bc2d
23 0019f77c 73bd7d4d     1000001f 699c99f8 25fabe90 igCore20d!IG_mpi_page_set+0x10bc2d
24 0019f79c 73bd7d4d     1000001f 0bdf7170 4419d5f0 igCore20d!IG_mpi_page_set+0x10bc2d
25 0019f7bc 73bd7d4d     1000001f 25fab9f8 48d8ee90 igCore20d!IG_mpi_page_set+0x10bc2d
26 0019f7dc 73bd7d4d     1000001f 4419d170 50f8a5f0 igCore20d!IG_mpi_page_set+0x10bc2d
27 0019f7fc 73bd7d4d     1000001f 48d8e9f8 0a804e90 igCore20d!IG_mpi_page_set+0x10bc2d
28 0019f81c 73bd7d4d     1000001f 50f8a170 0d3dc5f0 igCore20d!IG_mpi_page_set+0x10bc2d
29 0019f83c 73bd7d4d     1000001f 0a8049f8 454e0e90 igCore20d!IG_mpi_page_set+0x10bc2d
2a 0019f85c 73bd7d4d     1000001f 0d3dc170 113be5f0 igCore20d!IG_mpi_page_set+0x10bc2d
2b 0019f87c 73bd7d4d     1000001f 454e09f8 14332e90 igCore20d!IG_mpi_page_set+0x10bc2d
2c 0019f89c 73bd7d4d     1000001f 113be170 0e7b25f0 igCore20d!IG_mpi_page_set+0x10bc2d
2d 0019f8bc 73bd7d4d     1000001f 143329f8 15f90e90 igCore20d!IG_mpi_page_set+0x10bc2d
2e 0019f8dc 73bd7d4d     1000001f 0e7b2170 0e7925f0 igCore20d!IG_mpi_page_set+0x10bc2d
2f 0019f8fc 73bd7d4d     1000001f 15f909f8 09d9ee90 igCore20d!IG_mpi_page_set+0x10bc2d
30 0019f91c 73bd7d4d     1000001f 0e792170 0dbf05f0 igCore20d!IG_mpi_page_set+0x10bc2d
31 0019f93c 73bd7d4d     1000001f 09d9e9f8 699f5e90 igCore20d!IG_mpi_page_set+0x10bc2d
32 0019f95c 73bd7d4d     1000001f 0dbf0170 09102fd8 igCore20d!IG_mpi_page_set+0x10bc2d
33 0019f97c 73bd7d4d     1000001f 699f59f8 1000001f igCore20d!IG_mpi_page_set+0x10bc2d
34 0019f99c 73bddf36     1000001f 09102c78 0019fc3c igCore20d!IG_mpi_page_set+0x10bc2d
35 0019f9b4 73b66459     1000001f 0019f9d8 00000000 igCore20d!IG_mpi_page_set+0x111e16
36 0019fabc 73aa2005     0019fc3c 0019faf8 00000100 igCore20d!IG_mpi_page_set+0x9a339
37 0019fbfc 73ae072a     0019fc3c 00000000 0019fc38 igCore20d!IG_image_savelist_get+0x1575
38 0019fe68 73ae0239     00000000 052d0fd0 00000001 igCore20d!IG_mpi_page_set+0x1460a
39 0019fe88 73a75bc7     00000000 052d0fd0 00000001 igCore20d!IG_mpi_page_set+0x14119
3a 0019fea8 00402399     052d0fd0 0019febc 7562fb80 igCore20d!IG_load_file+0x47
3b 0019fec0 004026c0     052d0fd0 0019fef8 05236f50 Fuzzme!fuzzme+0x19
3c 0019ff28 00408407     00000005 05230f78 05236f50 Fuzzme!fuzzme+0x340
3d 0019ff70 756300c9     003d3000 756300b0 0019ffdc Fuzzme!fuzzme+0x6087
3e 0019ff80 77887b4e     003d3000 4733f043 00000000 KERNEL32!BaseThreadInitThunk+0x19
3f 0019ffdc 77887b1e     ffffffff 778a8c7d 00000000 ntdll!__RtlUserThreadStart+0x2f
40 0019ffec 00000000     0040848f 003d3000 00000000 ntdll!_RtlUserThreadStart+0x1b

The crash is happening in the following function I named free_IFD_record at 73bd7d30 :

73bd7d00  void __stdcall free_IFD_record(int32_t heap_ptr, struct IFD_Record* IFD_Record)
73bd7d00  55                 push    ebp {__saved_ebp}
73bd7d01  8bec               mov     ebp, esp {__saved_ebp}
73bd7d03  51                 push    ecx {var_8}
73bd7d04  56                 push    esi {__saved_esi}
73bd7d05  8b750c             mov     esi, dword [ebp+0xc {IFD_Record}]
73bd7d08  85f6               test    esi, esi
73bd7d0a  0f8487000000       je      0x73bd7d97

73bd7d10  8b4d08             mov     ecx, dword [ebp+0x8 {heap_ptr}]
73bd7d13  53                 push    ebx {__saved_ebx}  {0x0}
73bd7d14  bb01000000         mov     ebx, 0x1
73bd7d19  8bc3               mov     eax, ebx  {0x1}
73bd7d1b  895dfc             mov     dword [ebp-0x4 {sav_counter}], ebx  {0x1}
73bd7d1e  663b06             cmp     ax, word [esi {IFD_Record::num_entries_into_ifd.w}]
73bd7d21  7762               ja      0x73bd7d85

73bd7d23  57                 push    edi {__saved_edi}
73bd7d24  8dbed0020000       lea     edi, [esi+0x2d0] {IFD_Record::tif_entries[0].allocated_buffer}
73bd7d2a  8d9b00000000       lea     ebx, [ebx]  {0x1}

73bd7d30  66837ffa63         cmp     word [edi-0x6], 0x63
73bd7d35  7525               jne     0x73bd7d5c

We can see at 73bd7d24, the edi register is dereferenced from the esi register pointing to an IFD_Record. The esi register points to heap clearly marked as freed.

0:000> dd esi
72f10660  f0f0f0f0 f0f0f0f0 f0f0f0f0 f0f0f0f0
72f10670  f0f0f0f0 f0f0f0f0 f0f0f0f0 f0f0f0f0

We can look into heap metadata to confirm this:

0:000> dt _DPH_BLOCK_INFORMATION esi-20
ntdll!_DPH_BLOCK_INFORMATION
   +0x000 StartStamp       : 0xabcdaaa9
   +0x004 Heap             : 0x84d81000 Void
   +0x008 RequestedSize    : 0x2c8
   +0x00c ActualSize       : 0x2f0
   +0x010 FreeQueue        : _LIST_ENTRY [ 0x2 - 0x0 ]
   +0x010 FreePushList     : _SINGLE_LIST_ENTRY
   +0x010 TraceIndex       : 2
   +0x018 StackTrace       : 0x03f13254 Void
   +0x01c EndStamp         : 0xdcbaaaa9

We can conclude the following from metadata : - the heap chunk size was 0x2c8 - the free happened in the stacktrace pointed to by 0x03f13254, confirmed below :

    0:000> dds 0x03f13254
    03f13254  00000000
    03f13258  0000f808
    03f1325c  00200000
    03f13260  7466c366 verifier!AVrfpDphNormalHeapFree+0xb6
    03f13264  7466ab23 verifier!AVrfDebugPageHeapFree+0xe3
    03f13268  778ffae6 ntdll!RtlDebugFreeHeap+0x3e
    03f1326c  77863db6 ntdll!RtlpFreeHeap+0xd6
    03f13270  778a7aed ntdll!RtlpFreeHeapInternal+0x783
    03f13274  77863c86 ntdll!RtlFreeHeap+0x46
    03f13278  73c81f3f igCore20d!IG_GUI_page_title_set+0x3e46f
    03f1327c  73ac6dbc igCore20d!AF_memm_alloc+0x7bc
    03f13280  73bd7d96 igCore20d!IG_mpi_page_set+0x10bc76
    03f13284  73bd7d4d igCore20d!IG_mpi_page_set+0x10bc2d
    03f13288  73bd7d4d igCore20d!IG_mpi_page_set+0x10bc2d
    03f1328c  73bd7d4d igCore20d!IG_mpi_page_set+0x10bc2d

At 03f13280, the address to return to is 73bd7d96, which is still in the function free_IFD_record.

    73bd7d85  68de090000         push    0x9de {__saved_edi}
    73bd7d8a  685828d173         push    data_73d12858 {var_18}  {"..\..\..\..\Common\Formats\tifre…"}
    73bd7d8f  56                 push    esi {var_1c_2}
    73bd7d90  51                 push    ecx {var_20_2}
    73bd7d91  e86aefeeff         call    free_a_ptr  // call to free here
    73bd7d96  5b                 pop     ebx {__saved_ebx}  {0x0}

which is also confirming the callstack data. For more information on parsing metadata and understanding the values, the reader can refer to Microsoft’s website https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/-heap.

The function free_IFD_record pseudo code is:

73bd7d00  void __stdcall free_IFD_record(int32_t heap_ptr, struct IFD_Record* IFD_Record)
73bd7d00  {
73bd7d03      int32_t ecx;
73bd7d03      int32_t var_8 = ecx;
73bd7d05      struct IFD_Record* l_IFD_Record = IFD_Record;
73bd7d0a      if (l_IFD_Record != 0)
73bd7d08      {
73bd7d10          int32_t l_heap_ptr = heap_ptr;
73bd7d1b          int32_t sav_counter = 1;
73bd7d21          if (1 <= l_IFD_Record->num_entries_into_ifd)
73bd7d1e          {
73bd7d24              int32_t* edi_1 = &l_IFD_Record->tif_entries[0].allocated_buffer;
73bd7d2a              int32_t counter = 1;
73bd7d82              do
73bd7d82              {
73bd7d35                  if (*(int16_t*)((char*)edi_1 - 6) == 0x63)
73bd7d30                  {
73bd7d37                      int32_t ebx_1 = *(int32_t*)edi_1;
73bd7d39                      int32_t l_index_IDF_Record = 0;
73bd7d3e                      if (edi_1[-1] > 0)
73bd7d3b                      {
73bd7d54                          do
73bd7d54                          {
73bd7d42                              if (ebx_1 != 0)
73bd7d40                              {
73bd7d48                                  free_IFD_record(l_heap_ptr, *(int32_t*)(ebx_1 + (l_index_IDF_Record << 2)));
73bd7d4d                                  l_heap_ptr = heap_ptr;
73bd7d4d                              }
73bd7d50                              l_index_IDF_Record = (l_index_IDF_Record + 1);
73bd7d50                          } while (l_index_IDF_Record < edi_1[-1]);
73bd7d51                      }
73bd7d56                      l_IFD_Record = IFD_Record;
73bd7d59                      counter = sav_counter;
73bd7d59                  }
73bd7d5c                  void* l_ptr_to_free = *(int32_t*)edi_1;
73bd7d60                  if (l_ptr_to_free != 0)
73bd7d5e                  {
73bd7d6e                      free_a_ptr(l_heap_ptr, l_ptr_to_free, "..\..\..\..\Common\Formats\tifre…", 0x9dc);
73bd7d62                  }
73bd7d76                  l_heap_ptr = heap_ptr;
73bd7d79                  counter = (counter + 1);
73bd7d7a                  edi_1 = &edi_1[3];
73bd7d7d                  sav_counter = counter;
73bd7d7d              } while (counter <= ((uint32_t)l_IFD_Record->num_entries_into_ifd));
73bd7d73          }
73bd7d91          free_a_ptr(l_heap_ptr, l_IFD_Record, "..\..\..\..\Common\Formats\tifre…", 0x9de);  // call to free here
73bd7d85      }
73bd7d08  }

The register esi was freed at 73bd7d91.

The callstack indicates clearly that the recursion is happening from 73bd7d48 in free_IFD_record calling itself. We can see the use-after-free happening on the function free_IFD_record while performing a recursive call to itself and crashing because the pointer was already freed.

We confirmed this is a use-after-free; we need to understand why. Investigating use-after-free is sometimes not so trivial, but this is where the time travel feature, if possible, is really cool feature. We use a hardware breakpoint with the recorded trace on the heap chunk, especially against the byte changing value from allocated to free.
Based on documentation from Microsoft we can see that metadata switches the value 0xABCDAAAA to 0xABCDAAA9 when freeing a light page heap block, and 0xABCDBBBB to 0xABCDBBA for a full page heap block.

This leads us to the free happening. Going backward leads us to the allocation routine for this chunk. Note all addresses changed as this are from a recorded trace now:

0:000> kb
 # ChildEBP RetAddr      Args to Child              
00 0019dd9c 749aa966     051e1000 183a0104 283eed38 verifier!AVrfpDphWritePageHeapBlockInformation+0x46
01 0019dde0 778ff28e     051e0000 01000002 000002c8 verifier!AVrfDebugPageHeapAllocate+0x2f6
02 0019de50 77867150     000002c8 4a3ffdcd 051e0000 ntdll!RtlDebugAllocateHeap+0x39
03 0019dffc 77866eac     000002c8 000002d0 00000000 ntdll!RtlpAllocateHeap+0xf0
04 0019e098 77865e4e     00000000 00000000 000002c8 ntdll!RtlpAllocateHeapInternal+0x104c
05 0019e0b0 746a1fa6     051e0000 00000000 000002c8 ntdll!RtlAllocateHeap+0x3e
WARNING: Stack unwind information not available. Following frames may be wrong.
06 0019e0d0 744e661d     000002c8 0019fc3c 00000000 igCore20d!IG_GUI_page_title_set+0x3e4d6
07 0019e0e4 745f8083     1000001f 000002c8 74732858 igCore20d!AF_memm_alloc+0x1d
08 0019e110 745f7fb8     0019fc3c 1000001f 74795718 igCore20d!IG_mpi_page_set+0x10bf63
...

At 0019e0d0, the return address 744e661d corresponds to a return from _malloc.

744e6610  e8dba3faff         call    OS_sync_cs_enter
744e6615  ff750c             push    dword [ebp+0xc {size}] {__saved_esi_1}
744e6618  e840b91b00         call    _malloc
744e661d  8bd8               mov     ebx, eax

The first argument is the size of the heap chunk here: 000002c8. At 0019e0e4 we can see a return to 745f8083 corresponding to a return from AF_memm_alloc.

745f807e  e87de5eeff         call    AF_memm_alloc
745f8083  8906               mov     dword [esi], eax
745f8085  85c0               test    eax, eax
745f8087  7531               jne     0x745f80ba

These addresses belong to the following pseudo-code function: tiff_parse_ifd. This is a very large function with the goal to process TIF directory entries and store values of tags into memory.

745f8000  int32_t __stdcall tiff_parse_ifd(struct mys_table_func* mys_func, int32_t heap_ptr, int32_t* (& table_index_tiff_tag)[0xad], 
745f8000      int32_t nb_tiff_idf_entries, struct IFD_Record** IFD_Record, int32_t* current_pos, struct IFD_Record* size_of_ifd_record)
....
745f8000  {
....
745f8041      if (l_num_entries_in_directory > size_of_ifd_record)  
745f803e      {
745f804b          return 0;
745f804b      }
745f8051      int32_t var_30_1;
745f8051      int32_t var_2c_1;
745f8051      int32_t var_24;
745f8051      struct TIF_TAG_data* var_20_2;
745f8051      void* var_1c_3;
745f8051      struct IFD_Record* l_buffer;
745f8051      if (l_read_size == 2)
745f804e      {
745f806e          int32_t var_20_3;
745f806e          __builtin_strncpy(var_20_3, "X(sty\n", 8);
745f807e          l_buffer = AF_memm_alloc(heap_ptr, ((l_num_entries_in_directory * 0xc) + 0x2c8), "..\..\..\..\Common\Formats\tifre…", 0xa79);
745f8083          *(int32_t*)data_for_directory = l_buffer;
745f8087          if (l_buffer == 0)
745f8085          {
745f8089              var_1c_3 = l_buffer;
745f8098              var_20_2 = ((((uint32_t)IFD_Record) * 0xc) + 0x2c8);
745f8099              var_24 = heap_ptr;
745f809c              int32_t var_28_2 = 0;
745f809e              var_2c_1 = 0xfffffc18;
745f80a3              var_30_1 = 0xa7c;
745f80a3          }
745f804e      }
745f8053      else
745f8053      {
745f8053          var_1c_3 = nullptr;
745f8055          var_20_2 = l_read_size;
745f8056          var_24 = 2;
745f8058          int32_t var_28 = 0;
745f805a          var_2c_1 = 0xfffff7fc;
745f805f          var_30_1 = 0xa75;
745f805f      }
745f8087      var_18;
745f8087      int32_t eax_7;
745f8087      int32_t* esp_1;
745f8087      if ((l_read_size != 2 || (l_read_size == 2 && l_buffer == 0)))
745f8085      {
745f80a8          int32_t var_34;
745f80a8          __builtin_strncpy(var_34, "X(st", 4);
745f80ad          eax_7 = AF_err_record_set("..\..\..\..\Common\Formats\tifre…", var_30_1, var_2c_1, 0, var_24, var_20_2, var_1c_3);
745f80ad          esp_1 = &var_18;
745f80ad      }
745f80b4      if (((l_read_size == 2 && l_buffer != 0) || ((l_read_size != 2 || (l_read_size == 2 && l_buffer == 0)) && eax_7 == 0)))
745f80b2      {
745f80be          struct IFD_Record* p_ifd_record = *(int32_t*)data_for_directory;
745f80ce          int32_t var_20_4 = 0;
745f80d1          OS_memset(p_ifd_record, 0, ((((uint32_t)IFD_Record) * 0xc) + 0x2c8));
745f80d1          int32_t* esp_2 = &var_18;
745f80da          p_ifd_record->num_entries_into_ifd = IFD_Record;
745f80e2          void* l_index;
745f80e2          if (table_index_tiff_tag != 0)
745f80e0          {
745f80e4              l_index = nullptr;
745f80e9              if (nb_tiff_idf_entries > 0)
745f80e6              {
745f80f1                  // esi = ifd_record ptr
745f80ee                  struct tif_entries_rec* edx_2 = &p_ifd_record->gtable[0].field_2;
745f8108                  do 
745f8108                  {
745f80f5                      *(int32_t*)((char*)edx_2 + -2) = *(int692_t*)table_index_tiff_tag[l_index];
745f80f9                      int16_t eax_13 = *(int16_t*)(&*(int692_t*)table_index_tiff_tag[l_index] + 2);
745f80fe                      l_index = ((char*)l_index + 1);
745f80ff                      edx_2->field_0 = eax_13;
745f8102                      edx_2 = &edx_2[1];
745f8102                  } while (l_index < nb_tiff_idf_entries);
745f8105              }
745f80e6          }
745f810d          l_index = IFD_Record;
   ....
745f831d  }

tiff_parse_ifd creates a heap chunk, allocated at 745f807e l_buffer, stored into data_for_directory at 745f8083. The size allocation from the formula: (l_num_entries_in_directory * 0xc) + 0x2c8) means in our case l_num_entries_in_directory should be somehow null.

Stored as a IFD_Record at 745f80be with the variable named p_ifd_record, the structure IFD_Record is described below and contains an interesting field named tif_entries type TIF_TAG_data:

struct IFD_Record __packed
{
    int32_t num_entries_into_ifd;
    int32_t offset_??;
    int32_t field_8;
    struct tif_entries_rec gtable[0xac];
    int32_t field_2bc;
    int32_t field_2c0;
    int16_t field_2c4;
    int16_t field_2c6;
    struct TIF_TAG_data tif_entries[xxx];
    void* field_2ec;
};

TIF_TAG_data structure:

struct TIF_TAG_data __packed
{
    int16_t tag_id;
    int16_t tag_type;
    int32_t tag_count;
    void* allocated_buffer;
};

This is the allocated_buffer mentioned already at 73bd7d24 through the esi register pointer while freeing data. In this case, edi register was pointing to allocated_buffer. The objective of tiff_parse_ifd is to create records of TIF_TAG_data against each tag found in the file. While tracking down allocated_buffer, values lead us to a routine named tif_parse_sub_IFD with the following pseudo-code:

746178b0  int32_t __stdcall tif_parse_sub_IFD(struct mys_table_func* mys_func, int32_t uniq_tag, int32_t shift_offset, 
746178b0      int32_t table_index_tiff_tag, void* nb_tiff_idf_entries, struct IFD_Record** ppIFD_Record, int32_t arg7, void* size_idf_record, 
746178b0      int32_t offset_IFD)
746178b0  {
746178ba      int32_t var_c = 0;
746178c1      struct IFD_Record* ptr_next_IFD_Record = nullptr;
746178c8      int32_t l_sav_pos_in_file = IO_tell(mys_func);
746178dc      struct TIF_TAG_data* tag_TIFF_TAG_SUBIFD = lookup_tags(*(int32_t*)ppIFD_Record, 0, TIFF_TAG_SUBIFD);
746178ef      if ((tag_TIFF_TAG_SUBIFD != 0 && tag_TIFF_TAG_SUBIFD->allocated_buffer != 0))
746178eb      {
7461790c          char* table_of_IFD_records = AF_memm_alloc(uniq_tag, (tag_TIFF_TAG_SUBIFD->tag_count << 2), "..\..\..\..\Common\Formats\tifre…", 0xb80);
74617915          int32_t l_error_status;
74617915          if (table_of_IFD_records == 0)
74617913          {
74617930              l_error_status = AF_err_record_set("..\..\..\..\Common\Formats\tifre…", 0xb83, 0xfffffc18, table_of_IFD_records, uniq_tag, (tag_TIFF_TAG_SUBIFD->tag_count << 2), table_of_IFD_records);
74617937              var_c = l_error_status;
74617937          }
7461793c          if ((table_of_IFD_records != 0 || (table_of_IFD_records == 0 && l_error_status == 0)))
7461793a          {
74617942              int32_t value_index = 0;
74617947              if (tag_TIFF_TAG_SUBIFD->tag_count > 0)
74617944              {
746179a3                  do
746179a3                  {
74617953                      ppIFD_Record = nullptr;
7461795d                      int32_t next_offset_IFD = (*(int32_t*)(tag_TIFF_TAG_SUBIFD->allocated_buffer + (value_index << 2)) + shift_offset);
74617963                      if (next_offset_IFD != offset_IFD)
74617960                      {
7461796b                          IO_seek(mys_func, next_offset_IFD, SEEK_SET);
7461797e                          ptr_next_IFD_Record = nullptr;
74617996                          var_c = tiff_IFD_Records(mys_func, uniq_tag, shift_offset, table_index_tiff_tag, nb_tiff_idf_entries, &ptr_next_IFD_Record, &ppIFD_Record, size_idf_record);
74617970                      }
7461799c                      *(int32_t*)(table_of_IFD_records + (value_index << 2)) = ptr_next_IFD_Record;
7461799f                      value_index = (value_index + 1);
7461799f                  } while (value_index < tag_TIFF_TAG_SUBIFD->tag_count);
746179a0              }
746179a8              if (*(int32_t*)table_of_IFD_records != 0)
746179a5              {
746179cf                  free_a_ptr(uniq_tag, tag_TIFF_TAG_SUBIFD->allocated_buffer, "..\..\..\..\Common\Formats\tifre…", 0xbab);
746179d9                  tag_TIFF_TAG_SUBIFD->allocated_buffer = subifd_data;
746179dc                  tag_TIFF_TAG_SUBIFD->tag_type = 0x63;
746179dc              }
746179b8              else
746179b8              {
746179b8                  free_a_ptr(uniq_tag, table_of_IFD_records, "..\..\..\..\Common\Formats\tifre…", 0xba5);
746179aa              }
746179aa          }
74617913      }
746179ea      IO_seek(mys_func, l_sav_pos_in_file, SEEK_SET);
746179f6      return var_c;
746179f6  }

At 746178dc, tag_TIFF_TAG_SUBIFD is a TIF_TAG_data type returned from the function lookup_tags. The pointer tag_TIFF_TAG_SUBIFD->allocated_buffer (at 7461795d) contains all values read from the file under the TIFF_TAG_SUBIFD. The TIFF_TAG_SUBIFD is a specific tiff tag used to indicate a number of child directory IFD. Each value is an offset into a TIFF file toward a TIFF IFD directory. For more details about SUBIFD, readers may look for “Adobe PageMaker ® 6.0 TIFF Technical Notes”. The tag_TIFF_TAG_SUBIFD->allocated_buffer contains all offsets for all child directories.

At 7461790c, if a tag SUBIFD exists, an allocation is made through a call to AF_memm_alloc. 7461790c gives back a pointer to what I named table_of_IFD_records based on the number of children identified by the tag_count value. At 74617963, if the offset next_offset_IFD is valid (understand it iss different from the current offset of the directory) it will go to the computed offset through the call to IO_seek, and then it will call the function tiff_IFD_Records.
The resulting content of next_offset_IFD is directly read from values from the TIFF_TAG_SUBIFD data regarding each child and may be totally controlled.

The tiff_IFD_Records function’s purpose is to parse a directory and look for all tags to create an IFD_Record. The following pseuco-code indicates how it works :

74617f60  int32_t __stdcall tiff_IFD_Records(struct mys_table_func* offset, int32_t heap_ptr, int32_t shift_offset, 
74617f60      int32_t const* (& table_index_tiff_tag)[0xad], int32_t nb_tiff_idf_entries_set_0xac, struct IFD_Record** ppIDF_Record, 
74617f60      int32_t* ppint32_new_position, int32_t size_idf_record)
74617f60  {
74617f63      int32_t arg1;
74617f63      int32_t num_entries = arg1;
74617f66      struct mys_table_func* l_mys_table_func = offset;
74617f6b      num_entries = 0;
74617f72      int32_t l_current_pos = IO_tell(l_mys_table_func);
74617f7b      offset = l_current_pos;
74617f7e      IO_seek(l_mys_table_func, l_current_pos, SEEK_SET);
74617f88      int32_t read_length = IO_word_read(l_mys_table_func, &num_entries);
74617f96      int32_t status;
74617f96      if (read_length == 2)
74617f93      {
74617f9e          IO_seek(l_mys_table_func, offset, SEEK_SET);
74617fb3          status = tiff_parse_ifd(l_mys_table_func, heap_ptr, table_index_tiff_tag, nb_tiff_idf_entries_set_0xac, ppIDF_Record, ppint32_new_position, size_idf_record);
74617fb3      }
74617fba      if ((read_length != 2 || (read_length == 2 && status == 0)))
74617fb8      {
74617fbc          struct IFD_Record* p_Idf_record = *(int32_t*)ppIDF_Record;
74617fbe          int32_t l_shift_offset = shift_offset;
74617fc3          if (p_Idf_record != 0)
74617fc1          {
74617fcc              status = read_data_of_ifd(l_mys_table_func, heap_ptr, l_shift_offset, p_Idf_record, ppint32_new_position);
74617fd3              if (status == 0)
74617fd1              {
74617fd5                  l_shift_offset = shift_offset;
74617fd5              }
74617fc1          }
74617fd3          if (((p_Idf_record != 0 && status == 0) || p_Idf_record == 0))
74617fc1          {
74617fec              status = tif_parse_sub_IFD(l_mys_table_func, heap_ptr, l_shift_offset, table_index_tiff_tag, nb_tiff_idf_entries_set_0xac, ppIDF_Record, ppint32_new_position, size_idf_record, offset);
74617fd8          }
74617fc1      }
74617ff7      return status;
74617ff7  }

It is not a very complex function once reversed, but readers may notice the call to tif_parse_sub_IFD (74617fec). This is normal, because the program is parsing a TIF directory and all tags. This may make things more complex while recursing tif tag directories. The main goal here is to create a pointer indentied asppIDF_Record, which is the same as the pointer identified by ptr_next_IFD_Record into tif_parse_sub_IFD routine.

Then tif_parse_sub_IFD routine will store the new pointer ptr_next_IFD_Record created by tiff_IFD_Records into the table and continue for each entry of the TIFF_TAG_SUBIFD through a do-while loop between addresses 746179a3 and 7461799f. At 746179d9, once all values from the SUBIFD tag are processed, the table table_of_IFD_records is stored into tag_TIFF_TAG_SUBIFD->allocated_buffer corresponding to its own data somehow.

At 74617963 if next_offset_IFD is equal to offset_IFD, it does not enter into the if condition missing the zero init of ptr_next_IFD_Record to nullptr (7461799c). Bulding a file containing a SUBIFD tag with invalid values equal to offset inside the IFD header may enable storing twice or more of the same pointer ptr_next_IFD_Record into the allocated_buffer. The vunerability is to allow unmodified the variable pointer ptr_next_IFD_Record while parsing the TIFF_TAG_SUBIFD. The null setting should be done outside the if condition check.

In  free_IFD_record, edi_1 is given the values of pointers stored into allocated_buffer. Storing the same pointer several times enables the function free_IFD_record to be called several times with the same arguments, causing a use-after-free and potentially leading to code execution.

Crash Information

0:000> !analyze -v
*******************************************************************************
*                                                                             *
*                        Exception Analysis                                   *
*                                                                             *
*******************************************************************************


KEY_VALUES_STRING: 1

    Key  : AV.Fault
    Value: Read

    Key  : Analysis.CPU.mSec
    Value: 2359

    Key  : Analysis.Elapsed.mSec
    Value: 9983

    Key  : Analysis.IO.Other.Mb
    Value: 11

    Key  : Analysis.IO.Read.Mb
    Value: 0

    Key  : Analysis.IO.Write.Mb
    Value: 19

    Key  : Analysis.Init.CPU.mSec
    Value: 390

    Key  : Analysis.Memory.CommitPeak.Mb
    Value: 59

    Key  : Failure.Bucket
    Value: INVALID_POINTER_READ_AVRF_c0000005_igCore20d.dll!Unknown

    Key  : Failure.Hash
    Value: {531558f4-782e-fbb6-c788-19ac288c476e}

    Key  : Timeline.OS.Boot.DeltaSec
    Value: 199452

    Key  : Timeline.Process.Start.DeltaSec
    Value: 2132856930

    Key  : WER.OS.Branch
    Value: vb_release

    Key  : WER.OS.Version
    Value: 10.0.19041.1

    Key  : WER.Process.Version
    Value: 1.0.1.1


NTGLOBALFLAG:  2100000

APPLICATION_VERIFIER_FLAGS:  0

APPLICATION_VERIFIER_LOADED: 1

EXCEPTION_RECORD:  (.exr -1)
ExceptionAddress: 72dd7d30 (igCore20d!IG_mpi_page_set+0x0010bc10)
   ExceptionCode: c0000005 (Access violation)
  ExceptionFlags: 00000000
NumberParameters: 2
   Parameter[0]: 00000000
   Parameter[1]: 736be002
Attempt to read from address 736be002

FAULTING_THREAD:  00001a64

PROCESS_NAME:  Fuzzme.exe

READ_ADDRESS:  736be002 

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

EXCEPTION_CODE_STR:  c0000005

EXCEPTION_PARAMETER1:  00000000

EXCEPTION_PARAMETER2:  736be002

STACK_TEXT:  
WARNING: Stack unwind information not available. Following frames may be wrong.
0019f2dc 72dd7d4d     1000001f 736bd288 36fe35f0 igCore20d!IG_mpi_page_set+0x10bc10
0019f2fc 72dd7d4d     1000001f 1279c9f8 1ff64e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f31c 72dd7d4d     1000001f 36fe3170 30dac5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f33c 72dd7d4d     1000001f 1ff649f8 2d692e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f35c 72dd7d4d     1000001f 30dac170 699025f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f37c 72dd7d4d     1000001f 2d6929f8 636aee90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f39c 72dd7d4d     1000001f 69902170 227ff5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f3bc 72dd7d4d     1000001f 636ae9f8 1ff29e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f3dc 72dd7d4d     1000001f 227ff170 0bbb85f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f3fc 72dd7d4d     1000001f 1ff299f8 466f3e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f41c 72dd7d4d     1000001f 0bbb8170 194525f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f43c 72dd7d4d     1000001f 466f39f8 1903be90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f45c 72dd7d4d     1000001f 19452170 276595f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f47c 72dd7d4d     1000001f 1903b9f8 0d174e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f49c 72dd7d4d     1000001f 27659170 19d8e5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f4bc 72dd7d4d     1000001f 0d1749f8 20014e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f4dc 72dd7d4d     1000001f 19d8e170 097615f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f4fc 72dd7d4d     1000001f 200149f8 7164be90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f51c 72dd7d4d     1000001f 09761170 4798d5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f53c 72dd7d4d     1000001f 7164b9f8 6d302e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f55c 72dd7d4d     1000001f 4798d170 671e85f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f57c 72dd7d4d     1000001f 6d3029f8 65b96e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f59c 72dd7d4d     1000001f 671e8170 29c1b5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f5bc 72dd7d4d     1000001f 65b969f8 4d98ae90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f5dc 72dd7d4d     1000001f 29c1b170 536d45f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f5fc 72dd7d4d     1000001f 4d98a9f8 48e4ce90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f61c 72dd7d4d     1000001f 536d4170 3950d5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f63c 72dd7d4d     1000001f 48e4c9f8 2ea36e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f65c 72dd7d4d     1000001f 3950d170 4561c5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f67c 72dd7d4d     1000001f 2ea369f8 64b28e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f69c 72dd7d4d     1000001f 4561c170 5d44e5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f6bc 72dd7d4d     1000001f 64b289f8 50ee4e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f6dc 72dd7d4d     1000001f 5d44e170 2e7ad5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f6fc 72dd7d4d     1000001f 50ee49f8 0b3d0e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f71c 72dd7d4d     1000001f 2e7ad170 698c85f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f73c 72dd7d4d     1000001f 0b3d09f8 29977e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f75c 72dd7d4d     1000001f 698c8170 10cc65f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f77c 72dd7d4d     1000001f 299779f8 25f5de90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f79c 72dd7d4d     1000001f 10cc6170 5e84a5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f7bc 72dd7d4d     1000001f 25f5d9f8 66ed2e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f7dc 72dd7d4d     1000001f 5e84a170 4ebce5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f7fc 72dd7d4d     1000001f 66ed29f8 1e917e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f81c 72dd7d4d     1000001f 4ebce170 5477d5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f83c 72dd7d4d     1000001f 1e9179f8 0af56e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f85c 72dd7d4d     1000001f 5477d170 1455a5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f87c 72dd7d4d     1000001f 0af569f8 6226ae90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f89c 72dd7d4d     1000001f 1455a170 0e80d5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f8bc 72dd7d4d     1000001f 6226a9f8 14c9ae90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f8dc 72dd7d4d     1000001f 0e80d170 09c615f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f8fc 72dd7d4d     1000001f 14c9a9f8 08a46e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f91c 72dd7d4d     1000001f 09c61170 0e06e5f0 igCore20d!IG_mpi_page_set+0x10bc2d
0019f93c 72dd7d4d     1000001f 08a469f8 68421e90 igCore20d!IG_mpi_page_set+0x10bc2d
0019f95c 72dd7d4d     1000001f 0e06e170 083c6fd8 igCore20d!IG_mpi_page_set+0x10bc2d
0019f97c 72dd7d4d     1000001f 684219f8 1000001f igCore20d!IG_mpi_page_set+0x10bc2d
0019f99c 72dddf36     1000001f 083c6c78 0019fc3c igCore20d!IG_mpi_page_set+0x10bc2d
0019f9b4 72d66459     1000001f 0019f9d8 00000000 igCore20d!IG_mpi_page_set+0x111e16
0019fabc 72ca2005     0019fc3c 0019faf8 00000100 igCore20d!IG_mpi_page_set+0x9a339
0019fbfc 72ce072a     0019fc3c 00000000 0019fc38 igCore20d!IG_image_savelist_get+0x1575
0019fe68 72ce0239     00000000 052f0fd0 00000001 igCore20d!IG_mpi_page_set+0x1460a
0019fe88 72c75bc7     00000000 052f0fd0 00000001 igCore20d!IG_mpi_page_set+0x14119
0019fea8 00402399     052f0fd0 0019febc 7562fb80 igCore20d!IG_load_file+0x47
0019fec0 004026c0     052f0fd0 0019fef8 05256f50 Fuzzme!fuzzme+0x19
0019ff28 00408407     00000005 05250f78 05256f50 Fuzzme!fuzzme+0x340
0019ff70 756300c9     003bf000 756300b0 0019ffdc Fuzzme!fuzzme+0x6087
0019ff80 77887b4e     003bf000 ef59fe74 00000000 KERNEL32!BaseThreadInitThunk+0x19
0019ffdc 77887b1e     ffffffff 778a8c83 00000000 ntdll!__RtlUserThreadStart+0x2f
0019ffec 00000000     0040848f 003bf000 00000000 ntdll!_RtlUserThreadStart+0x1b


STACK_COMMAND:  ~0s ; .cxr ; kb

SYMBOL_NAME:  igCore20d+10bc10

MODULE_NAME: igCore20d

IMAGE_NAME:  igCore20d.dll

FAILURE_BUCKET_ID:  INVALID_POINTER_READ_AVRF_c0000005_igCore20d.dll!Unknown

OS_VERSION:  10.0.19041.1

BUILDLAB_STR:  vb_release

OSPLATFORM_TYPE:  x86

OSNAME:  Windows 10

IMAGE_VERSION:  20.1.0.117

FAILURE_ID_HASH:  {531558f4-782e-fbb6-c788-19ac288c476e}

Followup:     MachineOwner
---------
VENDOR RESPONSE

Release notes from the vendor can be found here:

https://help.accusoft.com/ImageGear/v20.3/Windows/DLL/webframe.html#release-notes.html

https://help.accusoft.com/ImageGear/v20.3/Linux/webframe.html#release-notes.html

TIMELINE

2023-08-28 - Vendor Disclosure
2023-09-20 - Vendor Patch Release
2023-09-25 - Public Release

Credit

Discovered by Emmanuel Tacheau of Cisco Talos.