Summary
In October 2021, Adobe released a security update for vulnerabilities in Adobe Acrobat and Reader. Among these vulnerabilities is an out-of-bounds read (CVE-2021-40729) that was discovered by Zscaler’s ThreatLabz. In this blog, we present our analysis of this vulnerability in the Adobe Acrobat Pro DC Solid Framework. Adobe uses the Solid Framework for the conversion of PDF files to Microsoft Office files. Foxit’s PDF Editor is also impacted by this vulnerability since it also uses the Solid Framework for the conversion of PDF files to other file formats.
Vulnerability Description
CVE-2021-40729 is an out-of-bounds read vulnerability that could potentially lead to disclosure of sensitive memory. An attacker could leverage this vulnerability to bypass mitigations such as ASLR. Exploitation of this issue requires user interaction in that a victim must open a malicious PDF file.
Known Affected Software Configurations
Acrobat DC Continuous 21.007.20095 and earlier versions in Windows
Acrobat DC Continuous 21.007.20096 and earlier versions in macOS
Acrobat 2020 Classic 2020 20.004.30015 and earlier versions in Windows & macOS
Acrobat 2017 Classic 2017 17.011.30202 and earlier versions in Windows & macOS
Foxit PDF Editor 11.2.0.53415 and all previous 11.x versions, 10.1.6.37749 and earlier
Proof of Concept
The vulnerability can be triggered by opening a specially crafted PDF file and exporting it to a Microsoft Word document. Zscaler ThreatLabz created a PoC file that will cause the following crash.
(1734.15d4): Access violation - code c0000005 (first chance) First chance exceptions are reported before any exception handling. This exception may be expected and handled. eax=a7acaff8 ebx=a7fc4fa0 ecx=00000001 edx=80000090 esi=809f8220 edi=04afbb7c eip=80a82943 esp=04afbb00 ebp=04afbb18 iopl=0 nv up ei ng nz ac po cy cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00210293 PDFLibTool!CPdfColorSpace::SetCurrentColorIndirect+0xb3: 80a82943 f20f100cc8 movsd xmm1,mmword ptr [eax+ecx*8] ds:002b:a7acb000=???????????????? 0:000> dc eax a7acaff8 6f23f199 3ff0cd9a ???????? ???????? ..#o...????????? a7acb008 ???????? ???????? ???????? ???????? ???????????????? a7acb018 ???????? ???????? ???????? ???????? ???????????????? a7acb028 ???????? ???????? ???????? ???????? ???????????????? a7acb038 ???????? ???????? ???????? ???????? ???????????????? a7acb048 ???????? ???????? ???????? ???????? ???????????????? a7acb058 ???????? ???????? ???????? ???????? ???????????????? a7acb068 ???????? ???????? ???????? ???????? ???????????????? 0:000> !heap -p -a a7acaff8 address a7acaff8 found in _DPH_HEAP_ROOT @ 9501000 in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize) a7993bc8: a7acaff8 8 - a7aca000 2000 672ba8b0 verifier!AVrfDebugPageHeapAllocate+0x00000240 7714f10e ntdll!RtlDebugAllocateHeap+0x00000039 770b70f0 ntdll!RtlpAllocateHeap+0x000000f0 770b6e3c ntdll!RtlpAllocateHeapInternal+0x0000104c 770b5dde ntdll!RtlAllocateHeap+0x0000003e 75840166 ucrtbase!_malloc_base+0x00000026 80d11770 PDFLibTool!CxManagedImage::Set+0x000c27b0 80a09dcd PDFLibTool!SolidWatermark::CSolidImageWatermarkOptBase::getWatermarkFileName+0x0000095d 80b1e9ed PDFLibTool!CPdfPageContentProcessor::ProcessGeneralCommand+0x00001d6d 80b1c398 PDFLibTool!CPdfPageContentProcessor::ProcessCommandStreamMultiThread+0x000005a8 80b1bdce PDFLibTool!CPdfPageContentProcessor::ProcessCommandStream+0x0000002e 848d7fad SecurePdfSDK!CLayoutPage::PerTextContent+0x0000004d 848ffbf4 SecurePdfSDK!CPdfOCRPageRenderTools::IsPageScanned+0x00000174 83f89e31 PdfFlt+0x00009e31 83fa634d PdfFlt+0x0002634d 8453a8dd PdfFlt+0x005ba8dd 80b17800 PDFLibTool!CPdfDocumentImplEx::RenderPDFDocument+0x00000410 8453c8b8 PdfFlt+0x005bc8b8 84541c4b PdfFlt+0x005c1c4b 0:000> kb # ChildEBP RetAddr Args to Child WARNING: Stack unwind information not available. Following frames may be wrong. 00 04afbb18 80b225ed 04afbb7c a7acaff8 00000000 PDFLibTool!CPdfColorSpace::SetCurrentColorIndirect+0xb3 01 04afbbf8 80b1ea41 00000001 a7acaff8 00000000 PDFLibTool!CPdfPageContentProcessor::SetCurrentColor+0x1cd 02 04afbd98 80b1c398 04afc1b8 80b1c398 04afbe9c PDFLibTool!CPdfPageContentProcessor::ProcessGeneralCommand+0x1dc1 03 04afc0cc 80b1bdce a38defb8 a329efe8 04afe918 PDFLibTool!CPdfPageContentProcessor::ProcessCommandStreamMultiThread+0x5a8 04 04afc0e4 848d7fad a38defb8 88a9b2b0 a329efe8 PDFLibTool!CPdfPageContentProcessor::ProcessCommandStream+0x2e 05 04afc114 848ffbf4 88a9b7a8 6c818f20 83f87840 SecurePdfSDK!CLayoutPage::PerTextContent+0x4d 06 04afc40c 83f89e31 6a550cf0 a329efe8 04afc474 SecurePdfSDK!CPdfOCRPageRenderTools::IsPageScanned+0x174 07 04afc8d8 83fa634d 6c818f20 4e1b0412 83fa62c0 PdfFlt+0x9e31 08 04afc92c 8453a8dd 6c818f20 4e1b0156 84539dd0 PdfFlt+0x2634d 09 04afcc68 80b17800 6babbb4c 04afe918 6fdf4f40 PdfFlt+0x5ba8dd 0a 04afcd90 8453c8b8 95236fe0 4e1b00de 04afe918 PDFLibTool!CPdfDocumentImplEx::RenderPDFDocument+0x410 0b 04afcde0 84541c4b 95236fe0 04afe990 73f2aff0 PdfFlt+0x5bc8b8 0c 04afce00 84541d1a 4e1b030a 04afe990 6a550cc0 PdfFlt+0x5c1c4b 0d 00000000 00000000 00000000 00000000 00000000 PdfFlt+0x5c1d1a
Test Environment
Adobe Acrobat Pro DC, Product version: 21.7.20095.60881
PDFLibTool.dll, Product version: 10.0.12082.1
Solid Framework (x86), Product version: 10.0.12082.1
Technical Analysis
The out-of-bounds read vulnerability CVE-2021-40729 exists in Adobe Acrobat Pro DC’s third-party library Solid Framework, which is located in the directory C:\Program Files (x86)\Adobe\Acrobat DC\Acrobat\plug_ins\SaveAsNonPDF\Solid. Figure 1 shows a comparison between a properly structured PDF file with a minimized PoC file that triggers this vulnerability.
Figure 1. Comparison between a proper PDF file and the minimized PoC file that triggers CVE-2021-40729
Figure 2 shows the single modified byte that triggers the vulnerability located in obj 4.
Figure 2. Hexdump of the obj 4 buffer including the modified byte that triggers the vulnerability
The structure of the minimized PoC file is shown below in Figure 3.
Figure 3. The structure of the minimized PoC file
In Figure 3, the obj 2 uses obj 6 as its resource and obj 4 as its content. obj 6 references obj 8 as its colorspace. obj 8 is an ICCBased family color space. ICCBased color spaces can have 1, 3, or 4 components (defined by the N dictionary value). So they often use Gray (1 component), RGB (3 components), or CMYK (4 components). obj 25 contains the ICC profile in its stream. The parameter “/N 3” indicates the number of color components in the color space described by the ICC profile data. In this PoC PDF file, it uses RGB color spaces. Therefore, when the Solid Framework processes the SC (set color) operator, three operands are required.
The description of the SC operator is listed in Figure 4.
Figure 4. The SC (set color) operator definition
The stream data in obj 4 has been compressed using the deflate algorithm. (For more information on deflate, please refer to https://tools.ietf.org/html/rfc1951). Zlib is a C library that implements the deflate algorithm. Adobe Acrobat also uses Zlib to decompress the deflate-compressed data, we can use the Python script shown below to decompress the data in obj 4.
Figure 5. Python Zlib decompression code
The uncompressed stream content in obj 4 contains a stream of graphics operators. Many of these streams contain invalid operators and abnormal operands including “1.050196108SC” as highlighted in Figure 6.
Figure 6. Uncompressed stream content in obj 4 in the minimized PoC file
Normal stream data for the SC operator is shown in Figure 7.
Figure 7. Uncompressed stream content in obj 4 in a normal PDF file
The parsing and processing of these abnormal operands for the SC operator are directly related to this vulnerability. In the section of “Proof of Concept”, the methods CPdfPageContentProcessor::ProcessCommandStreamMultiThread and CPdfPageContentProcessor::ProcessGeneralCommand in the stack backtrace stand out.
The method CPdfPageContentProcessor::ProcessCommandStreamMultiThread(std::streambuf &) is responsible for processing the command stream of graphic operators and operands. First, it calls the function sub_101288B0() that starts a thread to parse the stream content. The thread calls the function sub_1012AB70(). Finally, in the function sub_1012AB70() it parses each command from the stream content. The call flow graph is shown in Figure 8.
Figure 8. The control flows for parsing the PDF stream content
The following breakpoint in Figure 9 can be set in order to analyze the memory layout for each command.
Figure 9. Breakpoint to analyze the memory layout of the CPdfCmdContext structure
After the method CPdfCommandStreamBase::GetNextCmd(CPdfCmdContext &) is called, a breakpoint can be set on the following instruction to analyze the memory layout of the CPdfCmdContext structure.
bu PDFLibTool!CPdfPageContentProcessor::~CPdfPageContentProcessor+0x75e ".echo get cmd: ; dd ebp-0x3c L1; .echo Vector<CPdfLexem>; db poi(ebp-0x38) L(poi(ebp-0x34)-poi(ebp-0x38)); gc; "
Example output when this breakpoint is hit is shown in Figure 10 with the std::Vector<CPdfLexem> graphic operator objects. Each CPdfLexem object consists of 0x28 bytes that store the operator and its operands.
Figure 10. Memory dump for a CPdfCmdContext structure
A conditional breakpoint can be set after the method CPdfCommandStreamBase::GetNextCmd(CPdfCmdContext &) finishes parsing the stream content “0.5968767 m.5285492” followed by the stream content “1.050196108SC” to further understand how the invalid data is processed.
bu PDFLibTool!CPdfPageContentProcessor::~CPdfPageContentProcessor+0x75e ".echo get cmd: ; dd ebp-0x3c L1; .echo Vector<CPdfLexem>; db poi(ebp-0x38) L(poi(ebp-0x34)-poi(ebp-0x38)); .if(poi(poi(ebp-0x38)+0x20)=0x2a47d227) { .echo 0.5968767 m.5285492 hit; } .else {gc;}"
In Figure 11, the memory layout is shown with the members of a CPdfCmdContext structure.
Figure 11. The members of a CPdfCmdContext structure
After the call to CPdfCommandStreamBase::GetNextCmd(CPdfCmdContext &) the memory layout of std::Vector<CPdfLexem> object contains the stream content “1.050196108SC” as seen in Figure 12. This std::Vector<CPdfLexem> object includes two CPdfLexem objects: the first comes from a single operand, and the second comes from the operator SC.
Figure 12. Memory layout of std::Vector<CPdfLexem> for the stream content “1.050196108SC”.
The memory layout of the type double 1.050196108 is correct (0x3ff0cd9a6f23f199) as shown in Figure 13 using an online conversion tool.
Figure 13. The 1.050196108 double value converted to a hex value
The method CPdfPageContentProcessor::ProcessCommandStreamMultiThread(std::streambuf &), obtains all commands via parsing the stream content for graphic operators and operands. Then, it calls CPdfPageContentProcessor::ProcessGeneralCommand(GrStateCommands const &,char const *,std::vector<CPdfLexem> &,int) to process these commands one by one in a loop.
Thereby, another conditional breakpoint can be set on the method call CPdfPageContentProcessor::ProcessGeneralCommand(GrStateCommands const &,char const *,std::vector<CPdfLexem> &,int).
bu PDFLibTool!CPdfPageContentProcessor::ProcessGeneralCommand ".if(poi(poi(esp+4))=0x23) {.printf \"stream 0x23 \\n\"; .printf \"CPdfPageContentProcessor::ProcessGeneralCommand hit\\n\"; dps esp L7; db poi(esp+8); dd poi(esp+4) L1; }; .else{ gc;}"
This function takes 4 parameters. The 1st parameter stores the command number, the 2nd parameter stores the graphic operator, the 3rd parameter is a reference to a std::vector<CPdfLexem> object, and the 4th parameter is the number of operands in this graphic operator.
The following output shows when this breakpoint is hit the second time.
stream 0x23 CPdfPageContentProcessor::ProcessGeneralCommand hit 04efb85c 84fac398 PDFLibTool!CPdfPageContentProcessor::ProcessCommandStreamMultiThread+0x5a8 04efb860 04efb954 ;1st parameter 04efb864 aaab4fdc ;2nd parameter 04efb868 04efb958 ;3rd parameter 04efb86c 00000001 ;4th parameter 04efb870 39045405 04efb874 50caaff8 aaab4fdc 53 43 00 85 ff ff ff ff-b4 fd c5 a4 40 11 f6 84 SC..........@... aaab4fec 02 00 00 00 0f 00 00 00-00 00 00 00 00 00 00 00 ................ aaab4ffc 00 00 00 00 ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ....???????????? aaab500c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? aaab501c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? aaab502c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? aaab503c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? aaab504c ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 04efb954 00000023 eax=1103d938 ebx=04efbc70 ecx=04efbc70 edx=55000054 esi=881ec9c0 edi=564c6fb8 eip=84facc80 esp=04efb85c ebp=04efbb84 iopl=0 nv up ei pl zr na pe cy cs=0023 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00200247 PDFLibTool!CPdfPageContentProcessor::ProcessGeneralCommand: 84facc80 53 push ebx 0:000> db poi(04efb958) aaab4fb0 04 00 00 00 00 f0 1a 85-ff ff ff ff b4 fd c5 a4 ................ aaab4fc0 40 11 f6 84 00 00 00 00-0f 00 00 00 00 00 00 00 @............... aaab4fd0 99 f1 23 6f 9a cd f0 3f-07 00 00 00 53 43 00 85 ..#o...?....SC.. aaab4fe0 ff ff ff ff b4 fd c5 a4-40 11 f6 84 02 00 00 00 ........@....... aaab4ff0 0f 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................ aaab5000 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? aaab5010 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? aaab5020 ?? ?? ?? ?? ?? ?? ?? ??-?? ?? ?? ?? ?? ?? ?? ?? ???????????????? 0:000> !heap -p -a aaab4fb0 address aaab4fb0 found in _DPH_HEAP_ROOT @ 87a1000 in busy allocation ( DPH_HEAP_BLOCK: UserAddr UserSize - VirtAddr VirtSize) aa9929f4: aaab4fb0 50 - aaab4000 2000 707ba8b0 verifier!AVrfDebugPageHeapAllocate+0x00000240 7714f10e ntdll!RtlDebugAllocateHeap+0x00000039 770b70f0 ntdll!RtlpAllocateHeap+0x000000f0 770b6e3c ntdll!RtlpAllocateHeapInternal+0x0000104c 770b5dde ntdll!RtlAllocateHeap+0x0000003e 75840166 ucrtbase!_malloc_base+0x00000026 851a1770 PDFLibTool!CxManagedImage::Set+0x000c27b0 84f1df85 PDFLibTool!UnicodeHelper::unicodeStringToPdfUnicodeString+0x00002b25 84f9cb50 PDFLibTool!CPdfCommandStreamBase::GetNextCmd+0x00000240 84faabbe PDFLibTool!CPdfPageContentProcessor::~CPdfPageContentProcessor+0x0000075e 84fa8b08 PDFLibTool!CPdfDocumentImplEx::SetupPageData+0x00000728 75854f9f ucrtbase!thread_start<unsigned int (__stdcall*)(void *),1>+0x0000003f 76f8fa29 KERNEL32!BaseThreadInitThunk+0x00000019 770d7a9e ntdll!__RtlUserThreadStart+0x0000002f 770d7a6e ntdll!_RtlUserThreadStart+0x0000001b 00000000 +0x00000000
In this example, the command number is 0x23. This causes the code to enter the following switch case shown in Figure 14 in the function CPdfPageContentProcessor::ProcessGeneralCommand.
Figure 14. The switch case for command 0x23 in CPdfPageContentProcessor::ProcessGeneralCommand
This switch code block reads 8 bytes at the offset 0x20 in the std::vector<CPdfLexem> object. The value of the 8 bytes represents the type double 1.050196108. Then it creates a heap buffer with a size of 8 bytes. Next, it copies these 8 bytes from the std::vector<CPdfLexem> object to the newly created heap buffer. Next, it makes an indirect call to the method CPdfPageContentProcessor::SetCurrentColor(int,double *,char const *,int). When it enters into this function CPdfPageContentProcessor::SetCurrentColor, the second parameter is a pointer to a double with the value 1.050196108 as shown in Figure 15.
Figure 15. The parameters of CPdfPageContentProcessor::SetCurrentColor
In the method CPdfPageContentProcessor::SetCurrentColor, the code calls the method CPdfColorSpace::SetCurrentColorIndirect(CGSColor &,double const *,int,std::string const &). This first parameter is a CGSColor object. The second parameter comes from the 2nd parameter of CPdfPageContentProcessor::SetCurrentColor. The register ECX is the this pointer for a CPdfColorSpace object whose size is 0x60 bytes. The 4 bytes at offset 0x08 in the CPdfColorSpace object represents the number of color components in the color space described by the ICC profile data. In Figure 16, the 4 bytes at offset 0x08 in the CPdfColorSpace object is equal to 0x3. As previously described in Figure 3 and Figure 4, “/N 3” indicates the number of color components in the color space, which explains why the value at offset 0x8 in the CPdfColorSpace object is 0x3.
Figure 16. Inspect the parameters of CPdfColorSpace::SetCurrentColorIndirect
The function CPdfPageContentProcessor::SetCurrentColor calls the method CPdfColorSpace::SetCurrentColorIndirect. This method tries to read 3 required color components from its second parameter in a loop. However, in the PoC the second parameter stores only one color component. When it tries to read the second color component from the heap buffer, it causes an out-of-bounds read crash as shown in Figure 17.
Figure 17. Read color components from a heap buffer
This concludes the analysis of this out-of-bounds read vulnerability. In short, due to abnormal SC operands in obj 4 when Adobe Acrobat parses “1.050196108SC”, it converts this stream content to one double type as the only operand of the SC operator. One double type takes 8 bytes. obj 25 declared that in color spaces it required 3 color components (i.e., three operands). Therefore, it tries to read 3 double type values in the heap buffer when it handles the graphic operator SC. This heap buffer only consists of 8 bytes, which subsequently causes an out-of-bounds read crash.
Mitigation
All users of Adobe Acrobat and Reader are encouraged to upgrade to the latest version of this software. Zscaler’s Advanced Threat Protection and Advanced Cloud Sandbox can protect customers against this vulnerability.
References
https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-40729
https://helpx.adobe.com/security/products/acrobat/apsb21-104.html
https://www.foxit.com/support/security-bulletins.html
https://blog.idrsolutions.com/2011/04/understanding-the-pdf-file-format-%E2%80%93-iccbased-colorspaces/
https://gregstoll.com/~gregstoll/floattohex/