CVE-2023-39453
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.
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
ImageGear - https://www.accusoft.com/products/imagegear-collection/
9.8 - CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
CWE-416 - Use After Free
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.
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
---------
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
2023-08-28 - Vendor Disclosure
2023-09-20 - Vendor Patch Release
2023-09-25 - Public Release
Discovered by Emmanuel Tacheau of Cisco Talos.