Repository: googleprojectzero/BrokenType Branch: master Commit: cf49a52b8e35 Files: 33 Total size: 157.2 KB Directory structure: gitextract_fs1xo8nh/ ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── font2pdf/ │ ├── README.md │ ├── ttfotf_to_pdf.py │ └── type1_to_pdf.py ├── fontsub-dll-on-linux/ │ ├── Makefile │ ├── README.md │ ├── fontsub.h │ └── loader.cc ├── truetype-generator/ │ ├── README.md │ └── truetype_generate.py ├── ttf-fontsub-loader/ │ ├── README.md │ └── ttf-fontsub-loader.cpp ├── ttf-otf-dwrite-loader/ │ ├── README.md │ ├── config.h │ └── ttf-otf-dwrite-loader.cpp ├── ttf-otf-mutator/ │ ├── Makefile.linux │ ├── Makefile.mingw │ ├── README.md │ ├── common.h │ ├── main.cpp │ ├── mutator.cpp │ ├── mutator.h │ ├── random.cpp │ ├── random.h │ ├── sfnt_font.cpp │ ├── sfnt_font.h │ ├── sfnt_mutator.cpp │ └── sfnt_mutator.h └── ttf-otf-windows-loader/ ├── README.md ├── config.h └── ttf-otf-windows-loader.cpp ================================================ FILE CONTENTS ================================================ ================================================ FILE: CONTRIBUTING.md ================================================ # How to Contribute We'd love to accept your patches and contributions to this project. There are just a few small guidelines you need to follow. ## Contributor License Agreement Contributions to this project must be accompanied by a Contributor License Agreement. You (or your employer) retain the copyright to your contribution; this simply gives us permission to use and redistribute your contributions as part of the project. Head over to to see your current agreements on file or to sign a new one. You generally only need to submit a CLA once, so if you've already submitted one (even if it was for a different project), you probably don't need to do it again. ## Code reviews All submissions, including submissions by project members, require review. We use GitHub pull requests for this purpose. Consult [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more information on using pull requests. ## Community Guidelines This project follows [Google's Open Source Community Guidelines](https://opensource.google.com/conduct/). ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ # BrokenType BrokenType is a set of tools designed to test the robustness and security of font rasterization software, especially codebases prone to memory corruption issues (written in C/C++ and similar languages). It consists of the following components: - **[TrueType program generator](truetype-generator)** - a Python script for generating random, but valid [TrueType programs](https://docs.microsoft.com/en-us/typography/opentype/spec/ttinst). - **[TTF/OTF mutator](ttf-otf-mutator)** - a semi-"smart" binary font file mutator written in C++. - **[TTF/OTF loader for Windows GDI](ttf-otf-windows-loader)** - a utility for loading and testing custom fonts with Windows GDI / Uniscribe. - **[TTF/OTF loader for DirectWrite](ttf-otf-dwrite-loader)** - a utility for loading and testing custom fonts with the Microsoft DirectWrite API. - **[TTF loader for Windows FontSub.dll](ttf-fontsub-loader)** - a utility for loading and testing custom fonts with the Font Subsetting library. - **[Linux PE loader for FontSub.dll](fontsub-dll-on-linux)** - a minimal PE loader for Linux, tailored for fuzzing the Windows subsetter. - **[PDF font embedders](font2pdf)** - Python scripts for generating PDF documents which embed the specified fonts and display all of their glyphs. The description and usage instructions of the utilities can be found in their corresponding READMEs. The programs and scripts were successfully used in 2015-2019 to discover and report [20](https://bugs.chromium.org/p/project-zero/issues/list?can=1&q=status:fixed%20finder:mjurczyk%20product:kernel%20methodology:mutation-fuzzing%20font&colspec=ID%20Status%20Restrict%20Reported%20Vendor%20Product%20Finder%20Summary&cells=ids) vulnerabilities in the font rasterization code present in the Windows kernel (`win32k.sys` and `atmfd.dll` drivers), [19](https://bugs.chromium.org/p/project-zero/issues/list?can=1&q=status:fixed%20finder:mjurczyk%20uniscribe&colspec=ID%20Status%20Restrict%20Reported%20Vendor%20Product%20Finder%20Summary&cells=ids) security flaws in the user-mode Microsoft Uniscribe library, as well as [9](https://bugs.chromium.org/p/project-zero/issues/list?colspec=ID%20Status%20Restrict%20Reported%20Vendor%20Product%20Finder%20Summary&cells=ids&q=status%3Afixed%20finder%3Amjurczyk%20fontsub&can=1) bugs in the `FontSub.dll` library and several issues in DirectWrite. The fuzzing efforts were discussed in the following Google Project Zero blog posts: - [A year of Windows kernel font fuzzing #1: the results](https://googleprojectzero.blogspot.com/2016/06/a-year-of-windows-kernel-font-fuzzing-1_27.html) (June 2016) - [A year of Windows kernel font fuzzing #2: the techniques](https://googleprojectzero.blogspot.com/2016/07/a-year-of-windows-kernel-font-fuzzing-2.html) (July 2016) - [Notes on Windows Uniscribe Fuzzing](https://googleprojectzero.blogspot.com/2017/04/notes-on-windows-uniscribe-fuzzing.html) (April 2017) and the ["Reverse engineering and exploiting font rasterizers"](https://j00ru.vexillium.org/talks/44con-reverse-engineering-and-exploiting-font-rasterizers/) talk given in September 2015 at the 44CON conference in London. The two most notable issues found by the tool were [CVE-2015-2426](https://bugs.chromium.org/p/project-zero/issues/detail?id=369) and [CVE-2015-2455](https://bugs.chromium.org/p/project-zero/issues/detail?id=368) - an OTF bug collision with an exploit found in the Hacking Team leak, and a TTF bug collision with KeenTeam's exploit for pwn2own 2015. ## Disclaimer This is not an official Google product. ================================================ FILE: font2pdf/README.md ================================================ # PDF font embedder The two simple Python scripts convert standard TrueType/OpenType/Type1 font files to PDF documents, which include the font data in verbatim and display all of their glyphs. The tools are designed to facilitate the testing (e.g. fuzzing) of font rasterization engines used by PDF readers. They were tested on Adobe Acrobat Reader DC, Foxit Reader and Google Chrome. The `ttfotf_to_pdf.py` script should be used for TTF/OTF files, while `type1_to_pdf.py` works with Type 1 PostScript fonts (`.pfb` extension). Both of them require the [fontTools](https://github.com/fonttools/fonttools) package to be installed in the system to work correctly, in order to extract the number of glyphs in the font. ## Usage Just pass the path of the font as the first argument and path of the output PDF as the second: ``` c:\font2pdf>python ttfotf_to_pdf.py C:\Windows\Fonts\times.ttf test.pdf Glyphs in font: 4635 Generated pages: 2 c:\font2pdf> ``` and enjoy the generated document: ![PDF with a custom font embedded](../images/font2pdf.png) ================================================ FILE: font2pdf/ttfotf_to_pdf.py ================================================ # Author: Mateusz Jurczyk (mjurczyk@google.com) # # Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import math import os import struct import sys from fontTools.ttLib import TTFont # Configuration constants. GLYPHS_PER_SEGMENT = 50 SEGMENTS_PER_PAGE = 75 GLYPHS_PER_PAGE = SEGMENTS_PER_PAGE * GLYPHS_PER_SEGMENT PAGE_OBJECT_IDX_START = 100 PAGE_CONTENTS_IDX_START = 200 def main(argv): if len(argv) != 3: print "Usage: %s " % argv[0] sys.exit(1) # Obtain the number of glyphs in the font, and calculate the number of necessary pages. font = TTFont(argv[1]) glyph_count = len(font.getGlyphSet()) print "Glyphs in font: %d" % glyph_count page_count = int(math.ceil(float(glyph_count) / GLYPHS_PER_PAGE)) print "Generated pages: %d" % page_count # Craft the PDF header. pdf_data = "%PDF-1.1\n" pdf_data += "\n" pdf_data += "1 0 obj\n" pdf_data += "<< /Pages 2 0 R >>\n" pdf_data += "endobj\n" pdf_data += "\n" pdf_data += "2 0 obj\n" pdf_data += "<<\n" pdf_data += " /Type /Pages\n" pdf_data += " /Count %d\n" % page_count pdf_data += " /Kids [ %s ]\n" % " ".join(map(lambda x:" %d 0 R" % (PAGE_OBJECT_IDX_START + x), (i for i in xrange(page_count)))) pdf_data += ">>\n" pdf_data += "endobj\n" pdf_data += "\n" # Construct the page descriptor objects. for i in xrange(page_count): pdf_data += "%d 0 obj\n" % (PAGE_OBJECT_IDX_START + i) pdf_data += "<<\n" pdf_data += " /Type /Page\n" pdf_data += " /MediaBox [0 0 612 792]\n" pdf_data += " /Contents %d 0 R\n" % (PAGE_CONTENTS_IDX_START + i) pdf_data += " /Parent 2 0 R\n" pdf_data += " /Resources <<\n" pdf_data += " /Font <<\n" pdf_data += " /CustomFont <<\n" pdf_data += " /Type /Font\n" pdf_data += " /Subtype /Type0\n" pdf_data += " /BaseFont /TestFont\n" pdf_data += " /Encoding /Identity-H\n" pdf_data += " /DescendantFonts [4 0 R]\n" pdf_data += " >>\n" pdf_data += " >>\n" pdf_data += " >>\n" pdf_data += ">>\n" pdf_data += "endobj\n" # Construct the font body object. with open(argv[1], "rb") as f: font_data = f.read() pdf_data += "3 0 obj\n" pdf_data += "<>stream\n" % len(font_data) pdf_data += font_data pdf_data += "\nendstream\n" pdf_data += "endobj\n" # Construct the page contents objects. for i in xrange(page_count): pdf_data += "%d 0 obj\n" % (PAGE_CONTENTS_IDX_START + i) pdf_data += "<< >>\n" pdf_data += "stream\n" pdf_data += "BT\n" for j in xrange(SEGMENTS_PER_PAGE): if i * GLYPHS_PER_PAGE + j * GLYPHS_PER_SEGMENT >= glyph_count: break pdf_data += " /CustomFont 12 Tf\n" if j == 0: pdf_data += " 10 775 Td\n" else: pdf_data += " 0 -10 Td\n" pdf_data += " <%s> Tj\n" % "".join(map(lambda x: struct.pack(">H", x), (x for x in xrange(i * GLYPHS_PER_PAGE + j * GLYPHS_PER_SEGMENT, i * GLYPHS_PER_PAGE + (j + 1) * GLYPHS_PER_SEGMENT)))).encode("hex") pdf_data += "ET\n" pdf_data += "endstream\n" pdf_data += "endobj\n" # Construct the descendant font object. pdf_data += "4 0 obj\n" pdf_data += "<<\n" pdf_data += " /FontDescriptor <<\n" pdf_data += " /Type /FontDescriptor\n" pdf_data += " /FontName /TestFont\n" pdf_data += " /Flags 5\n" pdf_data += " /FontBBox[0 0 10 10]\n" pdf_data += " /FontFile3 3 0 R\n" pdf_data += " >>\n" pdf_data += " /Type /Font\n" pdf_data += " /Subtype /OpenType\n" pdf_data += " /BaseFont /TestFont\n" pdf_data += " /Widths [1000]\n" pdf_data += ">>\n" pdf_data += "endobj\n" # Construct the trailer. pdf_data += "trailer\n" pdf_data += "<<\n" pdf_data += " /Root 1 0 R\n" pdf_data += ">>\n" # Write the output to disk. with open(argv[2], "w+b") as f: f.write(pdf_data) if __name__ == "__main__": main(sys.argv) ================================================ FILE: font2pdf/type1_to_pdf.py ================================================ # Author: Mateusz Jurczyk (mjurczyk@google.com) # # Copyright 2019 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import math import os import struct import sys from fontTools.t1Lib import T1Font # Configuration constants. GLYPHS_PER_SEGMENT = 50 def main(argv): if len(argv) != 3: print "Usage: %s <.pfb font file> " % argv[0] sys.exit(1) # Load the number of glyphs in the font. font = T1Font(argv[1]) glyph_count = len(font.getGlyphSet()) print "Glyphs in font: %d" % glyph_count # Craft the PDF header. pdf_data = "%PDF-1.1\n" pdf_data += "\n" pdf_data += "1 0 obj\n" pdf_data += "<< /Pages 2 0 R >>\n" pdf_data += "endobj\n" pdf_data += "\n" pdf_data += "2 0 obj\n" pdf_data += "<<\n" pdf_data += " /Type /Pages\n" pdf_data += " /Count 1\n" pdf_data += " /Kids [ 3 0 R ]\n" pdf_data += ">>\n" pdf_data += "endobj\n" pdf_data += "\n" pdf_data += "3 0 obj\n" pdf_data += "<<\n" pdf_data += " /Type /Page\n" pdf_data += " /MediaBox [0 0 612 792]\n" pdf_data += " /Contents 5 0 R\n" pdf_data += " /Parent 2 0 R\n" pdf_data += " /Resources <<\n" pdf_data += " /Font <<\n" pdf_data += " /CustomFont <<\n" pdf_data += " /Type /Font\n" pdf_data += " /Subtype /Type0\n" pdf_data += " /BaseFont /TestFont\n" pdf_data += " /Encoding /Identity-H\n" pdf_data += " /DescendantFonts [6 0 R]\n" pdf_data += " >>\n" pdf_data += " >>\n" pdf_data += " >>\n" pdf_data += ">>\n" pdf_data += "endobj\n" # Construct the font body object. with open(argv[1], "rb") as f: font_data = f.read() pdf_data += "4 0 obj\n" pdf_data += "<>stream\n" % len(font_data) pdf_data += font_data pdf_data += "\nendstream\n" pdf_data += "endobj\n" # Construct the page contents object. pdf_data += "5 0 obj\n" pdf_data += "<< >>\n" pdf_data += "stream\n" pdf_data += "BT\n" segment_count = int(math.ceil(float(glyph_count) / GLYPHS_PER_SEGMENT)) for i in xrange(segment_count): pdf_data += " /CustomFont 12 Tf\n" if i == 0: pdf_data += " 10 780 Td\n" else: pdf_data += " 0 -10 Td\n" pdf_data += " <%s> Tj\n" % "".join(map(lambda x: struct.pack(">H", x), (x for x in xrange(i * GLYPHS_PER_SEGMENT, (i + 1) * GLYPHS_PER_SEGMENT)))).encode("hex") pdf_data += "ET\n" pdf_data += "endstream\n" pdf_data += "endobj\n" # Construct the descendant font object. pdf_data += "6 0 obj\n" pdf_data += "<<\n" pdf_data += " /FontDescriptor <<\n" pdf_data += " /Type /FontDescriptor\n" pdf_data += " /FontName /TestFont\n" pdf_data += " /Flags 5\n" pdf_data += " /FontBBox[0 0 10 10]\n" pdf_data += " /FontFile3 4 0 R\n" pdf_data += " >>\n" pdf_data += " /Type /Font\n" pdf_data += " /Subtype /Type1\n" pdf_data += " /BaseFont /TestFont\n" pdf_data += ">>\n" pdf_data += "endobj\n" # Construct the trailer. pdf_data += "trailer\n" pdf_data += "<<\n" pdf_data += " /Root 1 0 R\n" pdf_data += ">>\n" # Write the output to disk. with open(argv[2], "w+b") as f: f.write(pdf_data) if __name__ == "__main__": main(sys.argv) ================================================ FILE: fontsub-dll-on-linux/Makefile ================================================ CXX=g++ LD=g++ CXXFLAGS=-m32 -DDEBUG LDFLAGS=-lpe-parser-library -ldl -m32 DEPS=fontsub.h SRCS=loader.cc OBJS=$(subst .cc,.o,$(SRCS)) all: loader %.o: %.cc $(DEPS) $(CXX) -c -o $@ $< $(CXXFLAGS) loader: $(OBJS) $(LD) -o $@ $< $(LDFLAGS) clean: $(RM) loader $(OBJS) ================================================ FILE: fontsub-dll-on-linux/README.md ================================================ # Linux DLL loader for the Windows Font Subsetting Library (FontSub.dll) This project is an equivalent of the [Windows FontSub Harness](https://github.com/googleprojectzero/BrokenType/tree/master/ttf-fontsub-loader), but aimed to run on Linux, by introducing a thin PE loading component. It is capable of mapping the PE sections in the Linux address space, setting the requested memory access rights, redirecting `msvcrt.dll` imports to the corresponding libc functions, providing custom stubs for other imports, and handling basic relocations. As it turns out, the `FontSub.dll` library is simple and self-contained enough that this is sufficient to have it run on Linux. The benefit to this approach is that with the harness, all typical Linux fuzzing tools become available: - You can use [AFL](http://lcamtuf.coredump.cx/afl/), [honggfuzz](https://github.com/google/honggfuzz), or your favourite Linux-based fuzzer. - You can use custom allocators, either by compiling them in, or injecting them through `LD_PRELOAD` (e.g. AFL's [libdislocator](https://github.com/google/afl/tree/master/libdislocator)). - You can use performance improvements such as a "Fork Server", which are easier to implement on Linux. - You can use additional DBI such as [Intel Pin](https://software.intel.com/en-us/articles/pin-a-dynamic-binary-instrumentation-tool) or [DynamoRIO](https://www.dynamorio.org/), which in our experience work most reliably on Linux. The program is meant to serve as a proof-of-concept and reference for quickly developing simple Linux DLL loaders for specialized tasks. Another more complex and mature project of this kind is Tavis Ormandy's [loadlibrary](https://github.com/taviso/loadlibrary). We used a similar tool for fuzzing a JPEG2000 decoder in VMware Workstation in 2016, see slides 195-208 of the [Windows Metafiles - An Analysis of the EMF Attack Surface & Recent Vulnerabilities](https://j00ru.vexillium.org/slides/2016/metafiles_full.pdf) presentation. ## Requirements The tool uses the [pe-parse](https://github.com/trailofbits/pe-parse) library for a majority of the PE-related work. You also obviously need the Windows `FontSub.dll` system file to execute. ## Building With `pe-parse` installed in the system, simply type `make` in the project directory: ``` $ make g++ -c -o loader.o loader.cc -m32 -DDEBUG g++ -o loader loader.o -lpe-parser-library -ldl -m32 $ ``` ## Usage Before using the loader, please note that some recent versions of the FontSub library contain telemetry functions such as `LogCreateFontPackage` and `LogMergeFontPackage`, called at the end of the exported routines, e.g.: ``` .text:0000000180001345 loc_180001345: ; CODE XREF: CreateFontPackage+C1 .text:0000000180001345 ; CreateFontPackage+161 .text:0000000180001345 movzx ebx, r10w .text:0000000180001349 mov ecx, ebx ; unsigned int .text:000000018000134B call LogCreateFontPackage .text:0000000180001350 mov eax, ebx ``` These functions will call unimplemented Win32 API imports and crash the loader. Since their execution is not essential to the correct functioning of the subsetter, you might consider disabling them in your copy of the DLL. Then, just pass the path of the library, path of the input TrueType, and optionally the desired image base: ``` $ ./loader fontsub.dll times.ttf 0x20000000 [+] Provided fontsub.dll file successfully parsed. [*] Attempting to map section .text base 20001000, size 88576 [+] SUCCESS! [*] Attempting to map section .data base 20017000, size 1536 [+] SUCCESS! [*] Attempting to map section .idata base 20018000, size 2048 [+] SUCCESS! [*] Attempting to map section .rsrc base 20019000, size 1024 [+] SUCCESS! [*] Attempting to map section .reloc base 2001a000, size 2560 [+] SUCCESS! [...] [*] Resolving the MSVCRT.DLL!memmove import. [+] Function memmove found in libc at address 0xf7b53690. [*] Resolving the MSVCRT.DLL!memcmp import. [+] Function memcmp found in libc at address 0xf7b688e0. [*] Resolving the MSVCRT.DLL!memcpy import. [+] Function memcpy found in libc at address 0xf7b53050. [*] Resolving the MSVCRT.DLL!memset import. [+] Function memset found in libc at address 0xf7b51e20. [...] [*] Setting desired access rights for section .text [+] SUCCESS! [*] Setting desired access rights for section .data [+] SUCCESS! [*] Setting desired access rights for section .idata [+] SUCCESS! [*] Setting desired access rights for section .rsrc [+] SUCCESS! [*] Setting desired access rights for section .reloc [+] SUCCESS! [+] Located CreateFontPackage at 0x20002500, MergeFontPackage at 0x20002630. [A] malloc(0x168) ---> 0x573eebb0 [A] malloc(0x121b) ---> 0x573f3780 [A] malloc(0x14) ---> 0x573ed7d0 [A] malloc(0x490) ---> 0x573eed20 [A] malloc(0xfae) ---> 0x573f49a0 [A] free(0x573eed20) [A] free(0x573f49a0) [...] [A] free(0xf79a1010) [+] CreateFontPackage([ 1162804 bytes ], TTFCFP_SUBSET) ---> 0 (344996 bytes) [A] realloc((nil), 0x543a4) ---> 0x573f7a20 [A] free(0x573f7a20) [+] MergeFontPackage(NULL, [ 344996 bytes ], TTFMFP_SUBSET) ---> 0 (344996 bytes) $ ``` There are a lot of debug messages in the above output. If you build the tool without `-DDEBUG`, the output is much cleaner: ``` $ ./loader fontsub.dll times.ttf 0x20000000 [+] CreateFontPackage([ 1162804 bytes ], TTFCFP_SUBSET) ---> 0 (344996 bytes) [+] MergeFontPackage(NULL, [ 344996 bytes ], TTFMFP_SUBSET) ---> 0 (344996 bytes) $ ``` ## Limitations The harness currently only supports x86 `FontSub.dll` modules, but adding support for x64 images should be relatively straightforward. However, in a more general scenario, there are a number of differences between the Windows and Linux runtime environments, and the MSVC/gcc/clang compilers. They may result in compatbility problems with varying degrees of difficulty to solve. For instance: - `sizeof(long)` is 4 on 64-bit Windows, but 8 on 64-bit Linux. - Both systems have [different](https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions) calling conventions on the x64 architecture. This can be somewhat mitigated by using the `ms_abi` attribute for external function prototypes. - Accessing the TEB/PEB Windows structures through segment registers like `fs` or `gs` will most likely crash the loader, if some kind of emulation is not developed. They may be directly referenced in code to make use of the [SEH](https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling) mechanism (in built-in functions such as `_SEH_prolog4` and `_SEH_epilog4`), dynamic stack allocations (`alloca()`, to get the stack base address) etc. - The exception handling architecture is vastly different in Windows/Linux, and so a target which makes extensive use of them may be problematic. Furthermore, standard C++ exceptions are implemented with the `RaiseException` API on Windows, which is not provided by the harness at the moment. Last but not least, all external Windows API functions used by the library in question must be emulated or at least stubbed out, which may range from a simple to an almost impossible task. ================================================ FILE: fontsub-dll-on-linux/fontsub.h ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #ifndef FONTSUB_H_ #define FONTSUB_H_ // Standard FontSub constants from the Windows SDK (fontsub.h). /* for usSubsetFormat */ #define TTFCFP_SUBSET 0 /* Straight Subset Font - Backward compatibility */ #define TTFCFP_SUBSET1 1 /* Subset font with full TTO and Kern tables. For later merge */ #define TTFCFP_DELTA 2 /* Delta font, for merge with a subset1 font */ /* for usSubsetPlatform */ #define TTFCFP_UNICODE_PLATFORMID 0 #define TTFCFP_APPLE_PLATFORMID 1 #define TTFCFP_ISO_PLATFORMID 2 #define TTFCFP_MS_PLATFORMID 3 /* for usSubsetEncoding */ #define TTFCFP_STD_MAC_CHAR_SET 0 /* goes with TTFSUB_APPLE_PLATFORMID */ #define TTFCFP_SYMBOL_CHAR_SET 0 /* goes with TTFSUB_MS_PLATFORMID */ #define TTFCFP_UNICODE_CHAR_SET 1 /* goes with TTFSUB_MS_PLATFORMID */ #define TTFCFP_DONT_CARE 0xFFFF /* for usSubsetLanguage */ #define TTFCFP_LANG_KEEP_ALL 0 /* for usFlags */ #define TTFCFP_FLAGS_SUBSET 0x0001 /* if bit off, don't subset */ #define TTFCFP_FLAGS_COMPRESS 0x0002 /* if bit off, don't compress */ #define TTFCFP_FLAGS_TTC 0x0004 /* if bit off, its a TTF */ #define TTFCFP_FLAGS_GLYPHLIST 0x0008 /* if bit off, list is characters */ /* for usModes */ #define TTFMFP_SUBSET 0 /* copy a Straight Subset Font package to Dest buffer */ #define TTFMFP_SUBSET1 1 /* Expand a format 1 font into a format 3 font */ #define TTFMFP_DELTA 2 /* Merge a format 2 with a format 3 font */ #endif // FONTSUB_H_ ================================================ FILE: fontsub-dll-on-linux/loader.cc ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #if __x86_64__ #error The loader only works in x86 mode. #endif // __x86_64__ #include #include #include #include #include #include #include #include #include #include #include #include "fontsub.h" using namespace std; using namespace peparse; #ifdef DEBUG # define LOG(x) printf x; #else # define LOG(x) #endif // DEBUG namespace globals { // See https://docs.microsoft.com/en-us/windows/win32/api/fontsub/ for the // documentation of the prototypes below. typedef void *(*CFP_ALLOCPROC)(size_t Arg1); typedef void *(*CFP_REALLOCPROC)(void *, size_t Arg1); typedef void (*CFP_FREEPROC)(void *); typedef unsigned long (*fnCreateFontPackage)( const unsigned char *puchSrcBuffer, const unsigned long ulSrcBufferSize, unsigned char **ppuchFontPackageBuffer, unsigned long *pulFontPackageBufferSize, unsigned long *pulBytesWritten, const unsigned short usFlag, const unsigned short usTTCIndex, const unsigned short usSubsetFormat, const unsigned short usSubsetLanguage, const unsigned short usSubsetPlatform, const unsigned short usSubsetEncoding, const unsigned short *pusSubsetKeepList, const unsigned short usSubsetListCount, CFP_ALLOCPROC lpfnAllocate, CFP_REALLOCPROC lpfnReAllocate, CFP_FREEPROC lpfnFree, void *lpvReserved ); typedef unsigned long (*fnMergeFontPackage)( const unsigned char *puchMergeFontBuffer, const unsigned long ulMergeFontBufferSize, const unsigned char *puchFontPackageBuffer, const unsigned long ulFontPackageBufferSize, unsigned char **ppuchDestBuffer, unsigned long *pulDestBufferSize, unsigned long *pulBytesWritten, const unsigned short usMode, CFP_ALLOCPROC lpfnAllocate, CFP_REALLOCPROC lpfnReAllocate, CFP_FREEPROC lpfnFree, void *lpvReserved ); fnCreateFontPackage pfnCreateFontPackage; fnMergeFontPackage pfnMergeFontPackage; // The delta between the actual address the library is being loaded to in // relation to the suggested ImageBase from the PE headers. size_t base_address_delta; } // namespace globals static void *CfpAllocproc(size_t Arg1) { void *ret = malloc(Arg1); LOG(("[A] malloc(0x%zx) ---> %p\n", Arg1, ret)); return ret; } static void CfpFreeproc(void *Arg1) { LOG(("[A] free(%p)\n", Arg1)); return free(Arg1); } static void *CfpReallocproc(void *Arg1, size_t Arg2) { void *ret = realloc(Arg1, Arg2); LOG(("[A] realloc(%p, 0x%zx) ---> %p\n", Arg1, Arg2, ret)); return ret; } static int MapSection(void *cbd, VA base_address, string& name, image_section_header hdr, bounded_buffer *data) { // Apply potential relocation. base_address += globals::base_address_delta; LOG(("[*] Attempting to map section %-8s base %zx, size %u\n", name.c_str(), (size_t)base_address, data->bufLen)); // Create an initial writable mapping in memory to copy the section's data. void *mmapped_addr = mmap((void *)base_address, hdr.Misc.VirtualSize, PROT_WRITE, MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); if (mmapped_addr == (void *)-1) { perror("MapSection"); exit(1); } // Copy the physical data there. memcpy(mmapped_addr, data->buf, data->bufLen); LOG(("[+] SUCCESS!\n")); return 0; } static int SetSectionAccessRights(void *cbd, VA base_address, string& name, image_section_header hdr, bounded_buffer *data) { // Apply potential relocation. base_address += globals::base_address_delta; LOG(("[*] Setting desired access rights for section %-8s\n", name.c_str())); // Change the protection to the desired one specified in the section's header. int prot = PROT_READ; if ((hdr.Characteristics & IMAGE_SCN_CNT_CODE) || (hdr.Characteristics & IMAGE_SCN_MEM_EXECUTE)) { prot |= PROT_EXEC; } if (hdr.Characteristics & IMAGE_SCN_MEM_WRITE) { prot |= PROT_WRITE; } if (mprotect((void *)base_address, hdr.Misc.VirtualSize, prot) == -1) { perror("SetSectionAccessRights"); exit(1); } LOG(("[+] SUCCESS!\n")); return 0; } static int ResolveImport(void *cbd, VA import_addr, const string& module_name, const string& function_name) { // Apply potential relocation. import_addr += globals::base_address_delta; LOG(("[*] Resolving the %s!%s import.\n", module_name.c_str(), function_name.c_str())); // Initialize the libc handle, if it hasn't been yet. static void *libc_handle = NULL; if (libc_handle == NULL) { libc_handle = dlopen("libc.so.6", RTLD_NOW); if (libc_handle == NULL) { fprintf(stderr, "ResolveImport: Unable to get the base address of libc.\n"); exit(1); } LOG(("[+] Resolved the libc handle at %p\n", libc_handle)); } // Insert hooks for target-specific functions. void *hook_func_addr = NULL; if (function_name == "malloc") { hook_func_addr = (void *)CfpAllocproc; } else if (function_name == "realloc") { hook_func_addr = (void *)CfpReallocproc; } else if (function_name == "free") { hook_func_addr = (void *)CfpFreeproc; } if (hook_func_addr != NULL) { memcpy((void *)import_addr, &hook_func_addr, sizeof(void *)); return 0; } // In the rest of the function, we only do MSVCRT => libc remapping // Other imports are filled with a 0xcccccccc marker. if (module_name != "MSVCRT.DLL") { memcpy((void *)import_addr, "\xcc\xcc\xcc\xcc", 4); return 0; } // Locate the corresponding libc function and insert its address in IAT. void *func_addr = dlsym(libc_handle, function_name.c_str()); if (func_addr != NULL) { LOG(("[+] Function %s found in libc at address %p.\n", function_name.c_str(), func_addr)); memcpy((void *)import_addr, &func_addr, sizeof(void *)); } else { LOG(("[!] Function %s NOT FOUND in libc.\n", function_name.c_str())); // The marker for unmatched MSVCRT functions is 0xdddddddd. memcpy((void *)import_addr, "\xdd\xdd\xdd\xdd", 4); } return 0; } int ResolveRelocation(void *cbd, VA reloc_addr, reloc_type type) { if (type == HIGHLOW) { uint32_t *address = (uint32_t *)(reloc_addr + globals::base_address_delta); *address += globals::base_address_delta; } else if (type != ABSOLUTE) { fprintf(stderr, "[-] Unknown relocation type: %d\n", type); } return 0; } static int FindExports(void *cbd, VA func_addr, string& module_name, string& function_name) { if (function_name == "CreateFontPackage") { globals::pfnCreateFontPackage = (globals::fnCreateFontPackage)(func_addr + globals::base_address_delta); } else if (function_name == "MergeFontPackage") { globals::pfnMergeFontPackage = (globals::fnMergeFontPackage)(func_addr + globals::base_address_delta); } return 0; } static bool ReadFileToString(const char *filename, std::string *buffer) { FILE *f = fopen(filename, "rb"); if (f == NULL) { return false; } fseek(f, 0, SEEK_END); const long size = ftell(f); fseek(f, 0, SEEK_SET); bool success = true; char *tmp = (char *)malloc(size); if (tmp == NULL) { success = false; } if (success) { success = (fread(tmp, 1, size, f) == size); } if (success) { buffer->assign(tmp, size); } free(tmp); fclose(f); return success; } static void GenerateKeepList(std::mt19937& rand_gen, std::vector *keep_list) { int list_length; switch (rand_gen() & 3) { case 0: list_length = 1 + (rand_gen() % 10); break; case 1: list_length = 1 + (rand_gen() % 100); break; case 2: list_length = 1 + (rand_gen() % 1000); break; case 3: list_length = 1 + (rand_gen() % 10000); break; } for (int i = 0; i < list_length; i++) { keep_list->push_back(rand_gen() % (list_length * 2)); } } unsigned long FontSubCreate(const std::string& input_data, std::mt19937& rand_gen, unsigned short usFormat, std::string *output_data) { unsigned char *puchFontPackageBuffer = NULL; unsigned long ulFontPackageBufferSize = 0; unsigned long ulBytesWritten = 0; unsigned short usFlag = TTFCFP_FLAGS_SUBSET; unsigned short usTTCIndex = 0; if (rand_gen() & 1) { usFlag |= TTFCFP_FLAGS_COMPRESS; } if (rand_gen() & 1) { usFlag |= TTFCFP_FLAGS_GLYPHLIST; } if (input_data.size() >= 12 && !memcmp(input_data.data(), "ttcf", 4)) { int font_count = input_data.data()[11]; if (font_count == 0) { font_count = 1; } usFlag |= TTFCFP_FLAGS_TTC; usTTCIndex = rand_gen() % font_count; } unsigned short usSubsetPlatform = TTFCFP_UNICODE_PLATFORMID; unsigned short usSubsetEncoding = TTFCFP_DONT_CARE; switch (rand_gen() & 3) { case 0: usSubsetPlatform = TTFCFP_UNICODE_PLATFORMID; break; case 1: usSubsetPlatform = TTFCFP_APPLE_PLATFORMID; if (rand_gen() & 1) { usSubsetEncoding = TTFCFP_STD_MAC_CHAR_SET; } break; case 2: usSubsetPlatform = TTFCFP_ISO_PLATFORMID; break; case 3: usSubsetPlatform = TTFCFP_MS_PLATFORMID; switch (rand_gen() & 3) { case 0: usSubsetEncoding = TTFCFP_SYMBOL_CHAR_SET; break; case 1: usSubsetEncoding = TTFCFP_UNICODE_CHAR_SET; break; } break; } std::vector keep_list; GenerateKeepList(rand_gen, &keep_list); unsigned long ret = globals::pfnCreateFontPackage( (unsigned char *)input_data.data(), input_data.size(), &puchFontPackageBuffer, &ulFontPackageBufferSize, &ulBytesWritten, usFlag, usTTCIndex, usFormat, /*usSubsetLanguage=*/0, usSubsetPlatform, usSubsetEncoding, &keep_list[0], keep_list.size(), CfpAllocproc, CfpReallocproc, CfpFreeproc, NULL); if (ret == 0) { output_data->assign((char *)puchFontPackageBuffer, ulBytesWritten); CfpFreeproc(puchFontPackageBuffer); } else { output_data->clear(); } return ret; } unsigned long FontSubMerge1(const std::string& input_data, unsigned short usMode, std::string *output_data) { unsigned char *puchDestBuffer = NULL; unsigned long ulDestBufferSize = 0; unsigned long ulBytesWritten = 0; unsigned long ret = globals::pfnMergeFontPackage( /*puchMergeFontBuffer=*/NULL, /*ulMergeFontBufferSize=*/0, /*puchFontPackageBuffer=*/(const unsigned char *)input_data.data(), /*ulFontPackageBufferSize=*/input_data.size(), /*ppuchDestBuffer=*/&puchDestBuffer, /*pulDestBufferSize=*/&ulDestBufferSize, /*pulBytesWritten=*/&ulBytesWritten, usMode, CfpAllocproc, CfpReallocproc, CfpFreeproc, NULL); if (ret == 0) { output_data->assign((char *)puchDestBuffer, ulBytesWritten); CfpFreeproc(puchDestBuffer); } else { output_data->clear(); } return ret; } unsigned long FontSubMerge2(const std::string& merge_font, const std::string& font_package, std::string *output_data) { unsigned char *puchDestBuffer = NULL; unsigned long ulDestBufferSize = 0; unsigned long ulBytesWritten = 0; unsigned long ret = globals::pfnMergeFontPackage( /*puchMergeFontBuffer=*/(const unsigned char *)merge_font.data(), /*ulMergeFontBufferSize=*/merge_font.size(), /*puchFontPackageBuffer=*/(const unsigned char *)font_package.data(), /*ulFontPackageBufferSize=*/font_package.size(), /*ppuchDestBuffer=*/&puchDestBuffer, /*pulDestBufferSize=*/&ulDestBufferSize, /*pulBytesWritten=*/&ulBytesWritten, /*usMode=*/TTFMFP_DELTA, CfpAllocproc, CfpReallocproc, CfpFreeproc, NULL); if (ret == 0) { output_data->assign((char *)puchDestBuffer, ulBytesWritten); CfpFreeproc(puchDestBuffer); } else { output_data->clear(); } return ret; } void ProcessSample(const std::string& input_data) { std::seed_seq seed(input_data.begin(), input_data.end()); std::mt19937 rand_gen(seed); if (rand_gen() & 1) /* TTFCFP_SUBSET case */ { std::string font_package; unsigned long ret = FontSubCreate(input_data, rand_gen, TTFCFP_SUBSET, &font_package); printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_SUBSET) ---> %lu (%zu bytes)\n", input_data.size(), ret, font_package.size()); if (ret == 0) { std::string working_font; ret = FontSubMerge1(font_package, TTFMFP_SUBSET, &working_font); printf("[+] MergeFontPackage(NULL, [ %zu bytes ], TTFMFP_SUBSET) ---> %lu (%zu bytes)\n", font_package.size(), ret, working_font.size()); } } else /* TTFCFP_SUBSET1 / TTFCFP_DELTA case */ { std::string font_package; unsigned long ret = FontSubCreate(input_data, rand_gen, TTFCFP_SUBSET1, &font_package); printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_SUBSET1) ---> %lu (%zu bytes)\n", input_data.size(), ret, font_package.size()); if (ret == 0) { std::string working_font; ret = FontSubMerge1(font_package, TTFMFP_SUBSET1, &working_font); printf("[+] MergeFontPackage(NULL, [ %zu bytes ], TTFMFP_SUBSET1) ---> %lu (%zu bytes)\n", font_package.size(), ret, working_font.size()); const int merges = 1 + (rand_gen() % 5); for (int i = 0; i < merges; i++) { ret = FontSubCreate(input_data, rand_gen, TTFCFP_DELTA, &font_package); printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_DELTA) ---> %lu (%zu bytes)\n", input_data.size(), ret, font_package.size()); if (ret == 0) { std::string new_working_font; ret = FontSubMerge2(working_font, font_package, &new_working_font); printf("[+] MergeFontPackage([ %zu bytes ], [ %zu bytes ], TTFMFP_DELTA) ---> %lu (%zu bytes)\n", working_font.size(), font_package.size(), ret, new_working_font.size()); if (ret == 0) { working_font = new_working_font; } } } } } } int main(int argc, char **argv) { if (argc < 3 || argc > 4) { printf("Usage: %s /path/to/fontsub.dll /path/to/font/file [image base]\n", argv[0]); return 1; } const char *fontsub_dll_path = argv[1]; const char *input_font_path = argv[2]; // Parse the input PE file. parsed_pe *pe = ParsePEFromFile(fontsub_dll_path); if (pe == NULL) { fprintf(stderr, "[-] Unable to parse the %s file.\n", fontsub_dll_path); return 1; } LOG(("[+] Provided fontsub.dll file successfully parsed.\n")); // In case of an alternate image base, save information about the address delta. if (argc == 4) { size_t new_base_address = 0; if (sscanf(argv[3], "%zx", &new_base_address) != 1 || (new_base_address & 0xffff) != 0) { fprintf(stderr, "[-] Invalid base address %s\n", argv[3]); DestructParsedPE(pe); return 1; } size_t orig_base_address = 0; if (pe->peHeader.nt.OptionalMagic == NT_OPTIONAL_32_MAGIC) { orig_base_address = pe->peHeader.nt.OptionalHeader.ImageBase; } else { orig_base_address = pe->peHeader.nt.OptionalHeader64.ImageBase; } globals::base_address_delta = new_base_address - orig_base_address; } // Map sections to their respective addresses. IterSec(pe, MapSection, NULL); // Resolve imports (redirect MSVCRT to libc, install stubs etc.) in IAT. IterImpVAString(pe, ResolveImport, NULL); // If we're loading the library to non-default image base, resolve relocations. if (globals::base_address_delta != 0) { IterRelocs(pe, ResolveRelocation, NULL); } // Set the final access rights for the section memory regions. IterSec(pe, SetSectionAccessRights, NULL); // Find the fontsub!CreateFontPackage and fontsub!MergeFontPackage exported functions. IterExpVA(pe, FindExports, NULL); if (globals::pfnCreateFontPackage == NULL || globals::pfnMergeFontPackage == NULL) { fprintf(stderr, "[-] Unable to locate the required exported functions.\n"); DestructParsedPE(pe); return 1; } LOG(("[+] Located CreateFontPackage at %p, MergeFontPackage at %p.\n", globals::pfnCreateFontPackage, globals::pfnMergeFontPackage)); // Read the input font file. std::string font_data; if (!ReadFileToString(input_font_path, &font_data)) { fprintf(stderr, "[-] Unable to read the %s input file\n", input_font_path); DestructParsedPE(pe); return 1; } // Process the input data using the loaded DLL file. ProcessSample(font_data); // Destroy the PE object, but don't manually unmap the image from memory. // Instead, rely on the OS for the cleanup. DestructParsedPE(pe); return 0; } ================================================ FILE: truetype-generator/README.md ================================================ # TrueType program generator This Python script generates random streams of TrueType instructions, and embeds them into `.ttf` files converted to the XML format (`.ttx`) by [fonttools](https://github.com/fonttools/fonttools). It is useful in identifying complex vulnerabilities in TrueType *virtual machines*, which are triggered through a series of consecutive non-standard instructions and thus can't be otherwise detected through small modifications in legitimate TrueType programs. The random instructions are inserted into the `prep` table (i.e. the *[control value program](https://docs.microsoft.com/en-us/typography/opentype/spec/prep)*), and into the programs associated with all glyphs present in the font (the `glyf` table). A few values in other tables (mostly `cvt` and `maxp`) are also modified, in order to unify some limits declared by the font and make sure that the TrueType virtual machine doesn't bail out early. ## Usage Suppose we want to run the tool against an existing file on disk, `arial.ttf`. First, we must [install fonttools](https://github.com/fonttools/fonttools#installation) to be able to use the `ttx` compiler/decompiler: ``` C:\truetype-generator>pip --no-cache-dir install fonttools Collecting fonttools Downloading https://files.pythonhosted.org/packages/9c/0d/d562e2491c579dffb6e0d6a7fa082ad5bc2fede46380d71e91e146a10bd7/fonttools-3.28.0-py2.py3-none-any.whl (609kB) 100% |UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU| 614kB 1.0MB/s Installing collected packages: fonttools Successfully installed fonttools-3.28.0 ``` Then, we can convert `arial.ttf` to `arial.ttx`: ``` C:\truetype-generator>ttx arial.ttf Dumping "arial.ttf" to "arial.ttx"... Dumping 'GlyphOrder' table... Dumping 'head' table... Dumping 'hhea' table... [...] Dumping 'JSTF' table... Dumping 'meta' table... Dumping 'DSIG' table... C:\truetype-generator> ``` Now, we can run the script to create a new, mutated font. We recommend using 100000 as the total number of inserted TrueType instructions: ``` C:\truetype-generator>python truetype_generate.py arial.ttx output.ttx 100000 C:\truetype-generator> ``` At this point, the `output.ttx` file should contain long streams of TrueType programs, e.g.: ``` [...] PUSH[ ] 31597 SCVTCI[ ] PUSH[ ] 1 SZP1[ ] SFVTPV[ ] PUSH[ ] 20232 NROUND[01] POP[ ] [...] ``` The last step is to "compile" the XML file back to binary form, like so: ``` C:\truetype-generator>ttx output.ttx Compiling "output.ttx" to "output.ttf"... Parsing 'GlyphOrder' table... Parsing 'head' table... Parsing 'hhea' table... [...] Parsing 'JSTF' table... Parsing 'meta' table... Parsing 'DSIG' table... C:\truetype-generator> ``` The `output.ttf` font is now ready to be used for testing, for example with the [font loader for Windows](../ttf-otf-windows-loader). When writing this tutorial, the resulting file took the following form: ![TrueType font with random hint programs](../images/generator.png) ================================================ FILE: truetype-generator/truetype_generate.py ================================================ # Author: Mateusz Jurczyk (mjurczyk@google.com) # # Copyright 2018 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os import random import sys import xml.etree.ElementTree as ET class TTProgram: def __init__(self, twilight_points, contours_in_glyph, points_in_glyph): # Specifies the number of points in the Twilight Zone (Z0). self._twilight_points = twilight_points # Specifies the number of contours making up the glyph. self._contours_in_glyph = contours_in_glyph # Specifies the number of points existing in the glyph. self._points_in_glyph = points_in_glyph # Current state of zone pointers. self._zp = [1, 1, 1] def _Imm_ContourIdx(self, zone_idx): """Returns a random contour index for Zone 1 (the Twilight Zone should never contain contours). """ # Once in a while, ignore the constraints and return something possibly invalid. if random.randint(0, 1000) == 0: if self._contours_in_glyph == 0: return self._Imm_SRandom16() else: return random.randint(0, 2 * self._contours_in_glyph) # Otherwise, obey the rules and return a valid contour index. assert(self._zp[zone_ptr] != 0) assert(self._contours_in_glyph != 0) return random.randint(0, self._contours_in_glyph - 1) def _Imm_PointIdx(self, zone_ptr): """Returns a random point index for the specified zone. """ # Once in a while, ignore the constraints and return something possibly invalid. if random.randint(0, 1000) == 0: if (self._twilight_points == 0) and (self._points_in_glyph == 0): return self._Imm_SRandom16() else: return random.randint(0, max(self._twilight_points, self._points_in_glyph) - 1) # Otherwise, obey the rules and return a valid point index. if self._zp[zone_ptr] == 0: assert(self._twilight_points != 0) point_idx = random.randint(0, self._twilight_points - 1) else: assert(self._points_in_glyph != 0) point_idx = random.randint(0, self._points_in_glyph - 1) return point_idx def _Imm_SRandom16(self): return random.randint(-32768, 32767) def _Imm_URandom16(self): return random.randint(0, 32767) # Stack instructions. # Only used as a part of other instructions; not arbitrarily inserted into the generated program. def _Instr_PUSH(self, values): return ["PUSH[ ]\n" + " ".join(map(lambda x: str(x), values))] def _Instr_POP(self): return ["POP[ ]"] def _Instr_CLEAR(self): return ["CLEAR[ ]"] # Storage Area. # Not implemented, as we don't expect that fuzzing these instructions would yield any interesting results. def _Instr_RS(self): return [] def _Instr_WS(self): return [] # Control Value Table. def _Instr_WCVTP(self): return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_SRandom16()]) + # TODO: Change the second Random16() to RandomF26Dot6(). ["WCVTP[ ]"]) def _Instr_WCVTF(self): return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_SRandom16()]) + # TODO: Change the second Random16() to RandomF26Dot6(). ["WCVTF[ ]"]) def _Instr_RCVT(self): return (self._Instr_PUSH([self._Imm_URandom16()]) + ["RCVT[ ]"] + self._Instr_POP()) # Graphics State. def _Instr_SVTCA(self): return ["SVTCA[" + str(random.randint(0, 1)) + "]"] def _Instr_SPVTCA(self): return ["SPVTCA[" + str(random.randint(0, 1)) + "]"] def _Instr_SFVTCA(self): return ["SFVTCA[" + str(random.randint(0, 1)) + "]"] def _Instr_SPVTL(self): try: return (self._Instr_PUSH([self._Imm_PointIdx(1), self._Imm_PointIdx(2)]) + ["SPVTL[" + str(random.randint(0, 1)) + "]"]) except: return [] def _Instr_SFVTL(self): try: return (self._Instr_PUSH([self._Imm_PointIdx(1), self._Imm_PointIdx(2)]) + ["SFVTL[" + str(random.randint(0, 1)) + "]"]) except: return [] def _Instr_SFVTPV(self): return ["SFVTPV[ ]"] def _Instr_SDPVTL(self): try: return (self._Instr_PUSH([self._Imm_PointIdx(1), self._Imm_PointIdx(2)]) + ["SDPVTL[" + str(random.randint(0, 1)) + "]"]) except: return [] def _Instr_SPVFS(self): opt = random.randint(0, 2) if opt == 0: return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_URandom16()]) + ["SPVFS[ ]"]) elif opt == 1: return (self._Instr_PUSH([0x4000, 0]) + ["SPVFS[ ]"]) elif opt == 2: return (self._Instr_PUSH([0, 0x4000]) + ["SPVFS[ ]"]) def _Instr_SFVFS(self): opt = random.randint(0, 2) if opt == 0: return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_URandom16()]) + ["SFVFS[ ]"]) elif opt == 1: return (self._Instr_PUSH([0x4000, 0]) + ["SFVFS[ ]"]) elif opt == 2: return (self._Instr_PUSH([0, 0x4000]) + ["SFVFS[ ]"]) def _Instr_GPV(self): # Not implemented, as we don't expect that fuzzing this instruction would yield any results. return [] def _Instr_GFV(self): # Not implemented, as we don't expect that fuzzing this instruction would yield any results. return [] def _Instr_SRP0(self, zone_ptr): return (self._Instr_PUSH([self._Imm_PointIdx(zone_ptr)]) + ["SRP0[ ]"]) def _Instr_SRP1(self, zone_ptr): return (self._Instr_PUSH([self._Imm_PointIdx(zone_ptr)]) + ["SRP1[ ]"]) def _Instr_SRP2(self, zone_ptr): return (self._Instr_PUSH([self._Imm_PointIdx(zone_ptr)]) + ["SRP2[ ]"]) def _Instr_SZP0(self): zone_ptr = random.randint(0, 1) self._zp[0] = zone_ptr return (self._Instr_PUSH([zone_ptr]) + ["SZP0[ ]"]) def _Instr_SZP1(self): zone_ptr = random.randint(0, 1) self._zp[1] = zone_ptr return (self._Instr_PUSH([zone_ptr]) + ["SZP1[ ]"]) def _Instr_SZP2(self, zone_ptr = None): if zone_ptr == None: zone_ptr = random.randint(0, 1) self._zp[2] = zone_ptr return (self._Instr_PUSH([zone_ptr]) + ["SZP2[ ]"]) def _Instr_SZPS(self): zone_ptr = random.randint(0, 1) self._zp = [zone_ptr] * 3 return (self._Instr_PUSH([zone_ptr]) + ["SZPS[ ]"]) def _Instr_RTHG(self): return ["RTHG[ ]"] def _Instr_RTG(self): return ["RTG[ ]"] def _Instr_RTDG(self): return ["RTDG[ ]"] def _Instr_RDTG(self): return ["RDTG[ ]"] def _Instr_RUTG(self): return ["RUTG[ ]"] def _Instr_ROFF(self): return ["ROFF[ ]"] def _Instr_SROUND(self): return (self._Instr_PUSH([self._Imm_URandom16()]) + ["SROUND[ ]"]) def _Instr_S45ROUND(self): return (self._Instr_PUSH([self._Imm_URandom16()]) + ["S45ROUND[ ]"]) def _Instr_SLOOP(self): # Not implemented yet, since it is not clear how to do it correctly. return [] def _Instr_SMD(self): return (self._Instr_PUSH([self._Imm_URandom16()]) + # TODO: Change Random16() to RandomF26Dot6(). ["SMD[ ]"]) def _Instr_INSTCTRL(self): return (self._Instr_PUSH([self._Imm_SRandom16(), 3]) + ["INSTCTRL[ ]"]) def _Instr_SCANCTRL(self): return (self._Instr_PUSH([self._Imm_SRandom16()]) + ["SCANCTRL[ ]"]) def _Instr_SCANTYPE(self): return (self._Instr_PUSH([self._Imm_SRandom16()]) + ["SCANTYPE[ ]"]) def _Instr_SCVTCI(self): return (self._Instr_PUSH([self._Imm_URandom16()]) + # TODO: Change Random16() to RandomF26Dot6(). ["SCVTCI[ ]"]) def _Instr_SSWCI(self): return (self._Instr_PUSH([self._Imm_URandom16()]) + # TODO: Change Random16() to RandomF26Dot6(). ["SSWCI[ ]"]) def _Instr_SSW(self): return (self._Instr_PUSH([self._Imm_URandom16()]) + # TODO: Change Random16() to RandomF26Dot6(). ["SSW[ ]"]) def _Instr_FLIPON(self): return ["FLIPON[ ]"] def _Instr_FLIPOFF(self): return ["FLIPOFF[ ]"] def _Instr_SANGW(self): return (self._Instr_PUSH([self._Imm_URandom16()]) + ["SANGW[ ]"]) def _Instr_SDB(self): return (self._Instr_PUSH([self._Imm_URandom16()]) + ["SDB[ ]"]) def _Instr_SDS(self): return (self._Instr_PUSH([self._Imm_URandom16()]) + ["SDS[ ]"]) # Managing outlines. def _Instr_FLIPPT(self): try: return (self._Instr_PUSH([self._Imm_PointIdx(0)]) + ["FLIPPT[ ]"]) except: return [] def _Instr_FLIPRGON(self): try: x = self._Imm_PointIdx(0) y = self._Imm_PointIdx(0) if x > y: x, y = y, x return (self._Instr_PUSH([x, y]) + ["FLIPRGON[ ]"]) except: return [] def _Instr_FLIPRGOFF(self): try: x = self._Imm_PointIdx(0) y = self._Imm_PointIdx(0) if x > y: x, y = y, x return (self._Instr_PUSH([x, y]) + ["FLIPRGOFF[ ]"]) except: return [] def _Instr_SHP(self): try: op = [] flag = random.randint(0, 1) if flag == 0: op += self._Instr_SRP2(1) else: op += self._Instr_SRP1(0) return (op + self._Instr_PUSH([self._Imm_PointIdx(2)]) + ["SHP[" + str(flag) + "]"]) except: return [] def _Instr_SHC(self): try: flag = random.randint(0, 1) if flag == 0: op += self._Instr_SRP2(1) else: op += self._Instr_SRP1(0) return (op + self._Instr_PUSH([self._Imm_ContourIdx(2)]) + ["SHC[" + str(flag) + "]"]) except: return [] def _Instr_SHZ(self): try: flag = random.randint(0, 1) if flag == 0: op += self._Instr_SRP2(1) else: op += self._Instr_SRP1(0) return (op + self._Instr_PUSH([random.randint(0, 1)]) + ["SHZ[" + str(flag) + "]"]) except: return [] def _Instr_SHPIX(self): try: return (self._Instr_PUSH([self._Imm_PointIdx(2), self._Imm_URandom16()]) + # TODO: Change Random16() to RandomF26Dot6(). ["SHPIX[ ]"]) except: return [] def _Instr_MSIRP(self): try: return (self._Instr_SRP0(0) + self._Instr_PUSH([self._Imm_PointIdx(1), self._Imm_URandom16()]) + # TODO: Change Random16() to RandomF26Dot6(). ["MSIRP[" + str(random.randint(0, 1)) + "]"]) except: return [] def _Instr_MDAP(self): try: return (self._Instr_PUSH([self._Imm_PointIdx(0)]) + # TODO: Change Random16() to RandomF26Dot6(). ["MDAP[" + str(random.randint(0, 1)) + "]"]) except: return [] def _Instr_MIAP(self): try: return (self._Instr_PUSH([self._Imm_PointIdx(0), self._Imm_URandom16()]) + ["MIAP[" + str(random.randint(0, 1)) + "]"]) except: return [] def _Instr_MDRP(self): try: return (self._Instr_SRP0(0) + self._Instr_PUSH([self._Imm_PointIdx(1)]) + ["MDRP[" + str(random.randint(0, 1)) + str(random.randint(0, 1)) + str(random.randint(0, 1)) + str(random.randint(0, 1)) + str(random.randint(0, 1)) + "]"]) except: return [] def _Instr_MIRP(self): try: return (self._Instr_SRP0(0) + self._Instr_PUSH([self._Imm_PointIdx(1), self._Imm_URandom16()]) + ["MIRP[" + str(random.randint(0, 1)) + str(random.randint(0, 1)) + str(random.randint(0, 1)) + str(random.randint(0, 1)) + str(random.randint(0, 1)) + "]"]) except: return [] def _Instr_ALIGNRP(self): try: return (self._Instr_SRP0(0) + self._Instr_PUSH([self._Imm_PointIdx(1)]) + ["ALIGNRP[ ]"]) except: return [] def _Instr_ISECT(self): try: return (self._Instr_PUSH([self._Imm_PointIdx(2), self._Imm_PointIdx(1), self._Imm_PointIdx(1), self._Imm_PointIdx(0), self._Imm_PointIdx(0)]) + ["ISECT[ ]"]) except: return [] def _Instr_ALIGNPTS(self): try: return (self._Instr_PUSH([self._Imm_PointIdx(0), self._Imm_PointIdx(1)]) + ["ALIGNPTS[ ]"]) except: return [] def _Instr_IP(self): try: return (self._Instr_SRP1(0) + self._Instr_SRP2(1) + self._Instr_PUSH([self._Imm_PointIdx(2)]) + ["IP[ ]"]) except: return [] def _Instr_UTP(self): try: return (self._Instr_PUSH([self._Imm_PointIdx(0)]) + ["UTP[ ]"]) except: return [] def _Instr_IUP(self): if self._points_in_glyph == 0: return [] return ["IUP[" + str(random.randint(0, 1)) + "]"] # Managing exceptions. def _Instr_DELTAP1(self): try: return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_PointIdx(0), 1]) + ["DELTAP1[ ]"]) except: return [] def _Instr_DELTAP2(self): try: return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_PointIdx(0), 1]) + ["DELTAP2[ ]"]) except: return [] def _Instr_DELTAP3(self): try: return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_PointIdx(0), 1]) + ["DELTAP3[ ]"]) except: return [] def _Instr_DELTAC1(self): return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_URandom16(), 1]) + ["DELTAC1[ ]"]) def _Instr_DELTAC2(self): return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_URandom16(), 1]) + ["DELTAC2[ ]"]) def _Instr_DELTAC3(self): return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_URandom16(), 1]) + ["DELTAC3[ ]"]) # Compensating for the engine characteristics. def _Instr_ROUND(self): return (self._Instr_PUSH([self._Imm_URandom16()]) + ["ROUND[" + str(random.randint(0, 1)) + str(random.randint(0, 1)) + "]"] + self._Instr_POP()) def _Instr_NROUND(self): return (self._Instr_PUSH([self._Imm_URandom16()]) + ["NROUND[" + str(random.randint(0, 1)) + str(random.randint(0, 1)) + "]"] + self._Instr_POP()) def GenerateInstruction(self): instructions = [ # Storage Area. self._Instr_RS, self._Instr_WS, # Control Value Table. self._Instr_WCVTP, self._Instr_WCVTF, self._Instr_RCVT, # Graphics State. self._Instr_SVTCA, self._Instr_SPVTCA, self._Instr_SFVTCA, self._Instr_SPVTL, self._Instr_SFVTL, self._Instr_SFVTPV, self._Instr_SDPVTL, self._Instr_SPVFS, self._Instr_SFVFS, self._Instr_GPV, self._Instr_GFV, #self._Instr_SRP0, # #self._Instr_SRP1, # Reference point setting is only performed directly in instructions which use them. #self._Instr_SRP2, # self._Instr_SZP0, self._Instr_SZP1, self._Instr_SZP2, self._Instr_SZPS, self._Instr_RTHG, self._Instr_RTG, self._Instr_RTDG, self._Instr_RDTG, self._Instr_RUTG, self._Instr_ROFF, self._Instr_SROUND, self._Instr_S45ROUND, self._Instr_SLOOP, self._Instr_SMD, self._Instr_SCANCTRL, self._Instr_SCANTYPE, self._Instr_SCVTCI, self._Instr_SSWCI, self._Instr_SSW, self._Instr_FLIPON, self._Instr_FLIPOFF, self._Instr_SANGW, self._Instr_SDB, self._Instr_SDS, # Managing Outlines. self._Instr_FLIPPT, self._Instr_FLIPRGON, self._Instr_FLIPRGOFF, self._Instr_SHP, self._Instr_SHC, self._Instr_SHZ, self._Instr_SHPIX, self._Instr_MSIRP, self._Instr_MDAP, self._Instr_MIAP, self._Instr_MDRP, self._Instr_MIRP, self._Instr_ALIGNRP, self._Instr_ISECT, self._Instr_ALIGNPTS, self._Instr_IP, self._Instr_UTP, self._Instr_IUP, # Managing Exceptions. self._Instr_DELTAP1, self._Instr_DELTAP2, self._Instr_DELTAP3, self._Instr_DELTAC1, self._Instr_DELTAC2, self._Instr_DELTAC3, # Compensating For The Engine Characteristics. self._Instr_ROUND, self._Instr_NROUND, ] return random.choice(instructions)() def GenerateProgram(self, instructions_limit): pgm = self._Instr_FLIPRGON() while len(pgm) < instructions_limit: pgm += self.GenerateInstruction() return "\n".join(pgm) class TTXParser: # Specifies the number of instructions inserted into the "prep" TTF table. PREP_INSTRUCTIONS = 10000 # Specifies the maximum number of instructions per glyph. MAX_INSTRUCTIONS_PER_GLYPH = 10000 def __init__(self, num_instructions): # Specifies the total number of TrueType instructions which should be inserted into the font. self._num_instructions = num_instructions # Specifies the chosen number of twilight points in the font. self._twilight_points = 0 # Specifies the total number of glyphs defined in the font, as extracted from the "maxp" table. self._num_glyphs = 0 # Specifies the number of contours defined in the currently processed glyph. self._contours_in_glyph = 0 # Specifies the number of points defined in the currently processed glyph. self._points_in_glyph = 0 def _Handler_ttFont(self, path, node): """Makes sure that the CVT table exists in the font, as it is required for the generated instruction stream, and fonttools recalculate maxp.maxStorage based on the size of the table. """ if node.find("cvt") == None: ET.SubElement(node, "cvt") def _Handler_maxp(self, path, node): """Sets the limits specified in the "maxp" TTF table to desired values. """ num_glyphs_node = node.find("numGlyphs") assert(num_glyphs_node != None) self._num_glyphs = int(num_glyphs_node.attrib["value"]) max_points = None for item in node: if item.tag == "maxStorage": item.attrib["value"] = str(2 ** 15) elif item.tag == "maxStackElements": item.attrib["value"] = "8" elif item.tag == "maxSizeOfInstructions": item.attrib["value"] = str(2 ** 16 - 1) elif item.tag == "maxPoints": max_points = int(item.attrib["value"]) elif item.tag == "maxTwilightPoints": assert(max_points != None) if random.randint(0, 1) == 0: self._twilight_points = max(max_points * 2, 100) else: self._twilight_points = 0 item.attrib["value"] = str(self._twilight_points) elif item.tag == "maxZones": item.attrib["value"] = "2" def _Handler_cvt(self, path, node): """Fills the CVT table with 32768 random values. """ # First, iterate through the "cv" elements in order to locate the largest existing index. max_index = -1 for item in node.findall("cv"): max_index = max(max_index, int(item.attrib["index"])) for idx in range(max_index + 1, 32767 + 1): ET.SubElement(node, "cv", {"index" : str(idx), "value" : str(random.randint(-32768, 32767))}) def _Handler_TTGlyph(self, path, node): """Resets the number of contours and points in the glyph (because there is a new one), and inserts a child tag if one is not already present, in order to have TrueType instructions executed for all glyphs in the font. """ self._contours_in_glyph = 0 self._points_in_glyph = 0 # Only add the instructions for glyphs which have at least one contour. if (node.find("contour") != None) and (node.find("instructions") == None): instructions_node = ET.SubElement(node, "instructions") ET.SubElement(instructions_node, "assembly") def _Handler_contour(self, path, node): """ Accounts for a new contour in the glyph by incrementing the _contours_in_glyph field. """ self._contours_in_glyph += 1 def _Handler_pt(self, path, node): """Accounts for a new point in the glyph by incrementing the _points_in_glyph field. """ self._points_in_glyph += 1 def _Handler_assembly(self, path, node): """Generates a new TTF program for the node. """ if "fpgm" in path: # We want the "fpgm" (Font Program) section empty, as it should only contain instruction/function definitions. node.text = "" else: program = TTProgram(self._twilight_points, self._contours_in_glyph, self._points_in_glyph) if "prep" in path: # Insert a constant number of initialization instructions into the "prep" table. node.text = program.GenerateProgram(self.PREP_INSTRUCTIONS) else: # Generate a regular TrueType program with length depending on the number of glyphs in font. node.text = program.GenerateProgram(min(self._num_instructions // self._num_glyphs, self.MAX_INSTRUCTIONS_PER_GLYPH)) def TraverseNode(self, path, node): handlers = {"ttFont" : self._Handler_ttFont, # global TTF font tag. "maxp" : self._Handler_maxp, # "maxp" TTF table. "cvt" : self._Handler_cvt, # "cvt " TTF table. "TTGlyph" : self._Handler_TTGlyph, # Glyph definition. "contour" : self._Handler_contour, # Contour definition in glyph. "pt" : self._Handler_pt, # Point definition in glyph. "assembly" : self._Handler_assembly, # TTF instructions ("fpgm" TTF table, "prep" TTF table or glyph hinting program) } # Run the tag handler, if one exists. if node.tag in handlers: handlers[node.tag](path, node) # Traverse the XML nodes recursively. for child in node: self.TraverseNode(path + [node.tag], child) def main(argv): if len(argv) != 4: print "Usage: %s /path/to/input.ttx /path/to/output.ttx " % argv[0] sys.exit(1) tree = ET.parse(argv[1]) parser = TTXParser(int(argv[3])) parser.TraverseNode([], tree.getroot()) tree.write(argv[2], encoding='utf-8', xml_declaration = True) if __name__ == "__main__": main(sys.argv) ================================================ FILE: ttf-fontsub-loader/README.md ================================================ # Font loader for the Windows Font Subsetting Library (FontSub.dll) The loader is designed to load font files using the [FontSub](https://docs.microsoft.com/en-us/windows/win32/api/fontsub/) library, and test its robustness against malformed input. The library is used to subset fonts while printing documents via Direct2D, which is the case e.g. in Microsoft Edge. Both the [CreateFontPackage](https://docs.microsoft.com/en-us/windows/win32/api/fontsub/nf-fontsub-createfontpackage) and [MergeFontPackage](https://docs.microsoft.com/en-us/windows/win32/api/fontsub/nf-fontsub-mergefontpackage) API functions are tested by the harness. ## Building The application can be compiled with Microsoft Visual Studio after importing `ttf-fontsub-loader.cpp` into a new project. ## Usage Using the tool is as simple as passing the path of the tested TTF font in the first argument, for instance: ``` c:\ttf-fontsub-loader>ttf-fontsub-loader.exe C:\Windows\Fonts\arial.ttf [+] CreateFontPackage([ 1027192 bytes ], TTFCFP_SUBSET1) ---> 0 (390020 bytes) [+] MergeFontPackage(NULL, [ 390020 bytes ], TTFMFP_SUBSET1) ---> 0 (505796 bytes) [+] CreateFontPackage([ 1027192 bytes ], TTFCFP_DELTA) ---> 0 (1416 bytes) [+] MergeFontPackage([ 505796 bytes ], [ 1416 bytes ], TTFMFP_DELTA) ---> 0 (505796 bytes) [+] CreateFontPackage([ 1027192 bytes ], TTFCFP_DELTA) ---> 0 (1416 bytes) [+] MergeFontPackage([ 505796 bytes ], [ 1416 bytes ], TTFMFP_DELTA) ---> 0 (505796 bytes) c:\ttf-fontsub-loader> ``` More runtime information is printed if the program is compiled in Debug mode. During fuzzing, we recommend enabling [Page Heap](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/gflags-and-pageheap) for the loader process, for better detection of out-of-bounds and uninitialized memory bugs. An example of a subsetted Courier New Italic font is shown below: ![Subsetted Courier New Italic font](../images/loader-fontsub.png) ================================================ FILE: ttf-fontsub-loader/ttf-fontsub-loader.cpp ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #define _CRT_SECURE_NO_WARNINGS #include #include #include #include #include #include #include #include #include #include #pragma comment(lib, "fontsub.lib") #ifdef _DEBUG # define LOG(x) printf x; #else # define LOG(x) #endif // DEBUG static void *CfpAllocproc(size_t Arg1) { void *ret = malloc(Arg1); LOG(("[+] malloc(0x%zx) ---> %p\n", Arg1, ret)); return ret; } static void CfpFreeproc(void *Arg1) { LOG(("[+] free(%p)\n", Arg1)); return free(Arg1); } static void *CfpReallocproc(void *Arg1, size_t Arg2) { void *ret = realloc(Arg1, Arg2); LOG(("[+] realloc(%p, 0x%zx) ---> %p\n", Arg1, Arg2, ret)); return ret; } static bool ReadFileToString(const char *filename, std::string *buffer) { FILE *f = fopen(filename, "rb"); if (f == NULL) { return false; } fseek(f, 0, SEEK_END); const long size = ftell(f); fseek(f, 0, SEEK_SET); bool success = true; char *tmp = (char *)malloc(size); if (tmp == NULL) { success = false; } if (success) { success = (fread(tmp, 1, size, f) == size); } if (success) { buffer->assign(tmp, size); } free(tmp); fclose(f); return success; } void GenerateKeepList(std::mt19937& rand_gen, std::vector *keep_list) { int list_length; switch (rand_gen() & 3) { case 0: list_length = 1 + (rand_gen() % 10); break; case 1: list_length = 1 + (rand_gen() % 100); break; case 2: list_length = 1 + (rand_gen() % 1000); break; case 3: list_length = 1 + (rand_gen() % 10000); break; } for (int i = 0; i < list_length; i++) { keep_list->push_back(rand_gen() % (list_length * 2)); } } unsigned long FontSubCreate(const std::string& input_data, std::mt19937& rand_gen, unsigned short usFormat, std::string *output_data) { unsigned char *puchFontPackageBuffer = NULL; unsigned long ulFontPackageBufferSize = 0; unsigned long ulBytesWritten = 0; unsigned short usFlag = TTFCFP_FLAGS_SUBSET; unsigned short usTTCIndex = 0; if (rand_gen() & 1) { usFlag |= TTFCFP_FLAGS_COMPRESS; } if (rand_gen() & 1) { usFlag |= TTFCFP_FLAGS_GLYPHLIST; } if (input_data.size() >= 12 && !memcmp(input_data.data(), "ttcf", 4)) { int font_count = input_data.data()[11]; if (font_count == 0) { font_count = 1; } usFlag |= TTFCFP_FLAGS_TTC; usTTCIndex = rand_gen() % font_count; } unsigned short usSubsetPlatform = TTFCFP_UNICODE_PLATFORMID; unsigned short usSubsetEncoding = TTFCFP_DONT_CARE; switch (rand_gen() & 3) { case 0: usSubsetPlatform = TTFCFP_UNICODE_PLATFORMID; break; case 1: usSubsetPlatform = TTFCFP_APPLE_PLATFORMID; if (rand_gen() & 1) { usSubsetEncoding = TTFCFP_STD_MAC_CHAR_SET; } break; case 2: usSubsetPlatform = TTFCFP_ISO_PLATFORMID; break; case 3: usSubsetPlatform = TTFCFP_MS_PLATFORMID; switch (rand_gen() & 3) { case 0: usSubsetEncoding = TTFCFP_SYMBOL_CHAR_SET; break; case 1: usSubsetEncoding = TTFCFP_UNICODE_CHAR_SET; break; } break; } std::vector keep_list; GenerateKeepList(rand_gen, &keep_list); unsigned long ret = CreateFontPackage( (unsigned char *)input_data.data(), input_data.size(), &puchFontPackageBuffer, &ulFontPackageBufferSize, &ulBytesWritten, usFlag, usTTCIndex, usFormat, /*usSubsetLanguage=*/0, usSubsetPlatform, usSubsetEncoding, &keep_list[0], keep_list.size(), CfpAllocproc, CfpReallocproc, CfpFreeproc, NULL); if (ret == 0) { LOG(("[+] CreateFontPackage returned buffer %p, buffer size 0x%lx, bytes written 0x%lx\n", puchFontPackageBuffer, ulFontPackageBufferSize, ulBytesWritten)); output_data->assign((char *)puchFontPackageBuffer, ulBytesWritten); CfpFreeproc(puchFontPackageBuffer); } else { output_data->clear(); } return ret; } unsigned long FontSubMerge1(const std::string& input_data, unsigned short usMode, std::string *output_data) { unsigned char *puchDestBuffer = NULL; unsigned long ulDestBufferSize = 0; unsigned long ulBytesWritten = 0; unsigned long ret = MergeFontPackage( /*puchMergeFontBuffer=*/NULL, /*ulMergeFontBufferSize=*/0, /*puchFontPackageBuffer=*/(const unsigned char *)input_data.data(), /*ulFontPackageBufferSize=*/input_data.size(), /*ppuchDestBuffer=*/&puchDestBuffer, /*pulDestBufferSize=*/&ulDestBufferSize, /*pulBytesWritten=*/&ulBytesWritten, usMode, CfpAllocproc, CfpReallocproc, CfpFreeproc, NULL); if (ret == 0) { LOG(("[+] MergeFontPackage returned buffer %p, buffer size 0x%lx, bytes written 0x%lx\n", puchDestBuffer, ulDestBufferSize, ulBytesWritten)); output_data->assign((char *)puchDestBuffer, ulBytesWritten); CfpFreeproc(puchDestBuffer); } else { output_data->clear(); } return ret; } unsigned long FontSubMerge2(const std::string& merge_font, const std::string& font_package, std::string *output_data) { unsigned char *puchDestBuffer = NULL; unsigned long ulDestBufferSize = 0; unsigned long ulBytesWritten = 0; unsigned long ret = MergeFontPackage( /*puchMergeFontBuffer=*/(const unsigned char *)merge_font.data(), /*ulMergeFontBufferSize=*/merge_font.size(), /*puchFontPackageBuffer=*/(const unsigned char *)font_package.data(), /*ulFontPackageBufferSize=*/font_package.size(), /*ppuchDestBuffer=*/&puchDestBuffer, /*pulDestBufferSize=*/&ulDestBufferSize, /*pulBytesWritten=*/&ulBytesWritten, /*usMode=*/TTFMFP_DELTA, CfpAllocproc, CfpReallocproc, CfpFreeproc, NULL); if (ret == 0) { LOG(("[+] MergeFontPackage returned buffer %p, buffer size 0x%lx, bytes written 0x%lx\n", puchDestBuffer, ulDestBufferSize, ulBytesWritten)); output_data->assign((char *)puchDestBuffer, ulBytesWritten); CfpFreeproc(puchDestBuffer); } else { output_data->clear(); } return ret; } void ProcessSample(const std::string& input_data) { std::seed_seq seed(input_data.begin(), input_data.end()); std::mt19937 rand_gen(seed); if (rand_gen() & 1) /* TTFCFP_SUBSET case */ { std::string font_package; unsigned long ret = FontSubCreate(input_data, rand_gen, TTFCFP_SUBSET, &font_package); printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_SUBSET) ---> %lu (%zu bytes)\n", input_data.size(), ret, font_package.size()); if (ret == 0) { std::string working_font; ret = FontSubMerge1(font_package, TTFMFP_SUBSET, &working_font); printf("[+] MergeFontPackage(NULL, [ %zu bytes ], TTFMFP_SUBSET) ---> %lu (%zu bytes)\n", font_package.size(), ret, working_font.size()); } } else /* TTFCFP_SUBSET1 / TTFCFP_DELTA case */ { std::string font_package; unsigned long ret = FontSubCreate(input_data, rand_gen, TTFCFP_SUBSET1, &font_package); printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_SUBSET1) ---> %lu (%zu bytes)\n", input_data.size(), ret, font_package.size()); if (ret == 0) { std::string working_font; ret = FontSubMerge1(font_package, TTFMFP_SUBSET1, &working_font); printf("[+] MergeFontPackage(NULL, [ %zu bytes ], TTFMFP_SUBSET1) ---> %lu (%zu bytes)\n", font_package.size(), ret, working_font.size()); const int merges = 1 + (rand_gen() % 5); for (int i = 0; i < merges; i++) { ret = FontSubCreate(input_data, rand_gen, TTFCFP_DELTA, &font_package); printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_DELTA) ---> %lu (%zu bytes)\n", input_data.size(), ret, font_package.size()); if (ret == 0) { std::string new_working_font; ret = FontSubMerge2(working_font, font_package, &new_working_font); printf("[+] MergeFontPackage([ %zu bytes ], [ %zu bytes ], TTFMFP_DELTA) ---> %lu (%zu bytes)\n", working_font.size(), font_package.size(), ret, new_working_font.size()); if (ret == 0) { working_font = new_working_font; } } } } } } int main(int argc, char **argv) { if (argc != 2) { printf("Usage: %s \n", argv[0]); return 1; } const char *input_font_path = argv[1]; std::string font_data; if (!ReadFileToString(input_font_path, &font_data)) { printf("[-] Unable to read the %s input file\n", input_font_path); return 1; } ProcessSample(font_data); return 0; } ================================================ FILE: ttf-otf-dwrite-loader/README.md ================================================ # Font loader for Microsoft DirectWrite The loader is designed to load font files using the [DirectWrite](https://docs.microsoft.com/en-us/windows/win32/directwrite/direct-write-portal) interface (the engine used by all major Windows web browsers at the time of this writing), and test its rasterization code against malformed input. The purpose of the program is to stress-test as much font-handling code as possible, and to execute it for all glyphs found in the font file instead of a limited charset such as just the ASCII characters. The font-related DirectWrite calls made by the loader are listed below: - [IDWriteFactory::CreateFontFileReference](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefactory-createfontfilereference) - [IDWriteFactory::CreateFontFace](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefactory-createfontface) - [IDWriteFontFile::Analyze](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefontfile-analyze) - [IDWriteFontFace::GetMetrics](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefontface-getmetrics) - [IDWriteFontFace::GetDesignGlyphMetrics](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefontface-getdesignglyphmetrics) - [IDWriteFontFace::GetGlyphIndices](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefontface-getglyphindices) - [IDWriteFontFace1::GetUnicodeRanges](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_1/nf-dwrite_1-idwritefontface1-getunicoderanges) - [IDWriteBitmapRenderTarget::DrawGlyphRun](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritebitmaprendertarget-drawglyphrun) ## Building The application can be compiled with Microsoft Visual Studio after importing `ttf-otf-dwrite-loader.cpp` and `config.h` into a new project. ## Usage Using the tool is as simple as passing the path of the tested TTF/OTF font in the first argument, for example: ``` c:\ttf-otf-dwrite-loader>ttf-otf-dwrite-loader.exe C:\Windows\Fonts\arial.ttf [+] Input font is supported, with file type 2, face type 1 and number of faces 1 [+] Loaded 140 unicode ranges from the input font [+] Font processing completed [+] Font displayed without errors. c:\ttf-otf-dwrite-loader> ``` In addition to the standard output, you should also observe the font's glyphs being drawn in the upper left corner of the screen: ![Font glyphs displayed on the screen as rasterized by DirectWrite](../images/loader-dwrite.png) During fuzzing, we recommend enabling [Page Heap](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/gflags-and-pageheap) for the loader process, for better detection of out-of-bounds and uninitialized memory bugs. ================================================ FILE: ttf-otf-dwrite-loader/config.h ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #ifndef CONFIG_H_ #define CONFIG_H_ // Dimensions of the bitmap where the font glyphs are rendered on. #define RENDER_TARGET_WIDTH 800 #define RENDER_TARGET_HEIGHT 600 // Number of glyphs displayed at once. #define DISPLAYED_GLYPHS_COUNT 10 #endif // CONFIG_H_ ================================================ FILE: ttf-otf-dwrite-loader/ttf-otf-dwrite-loader.cpp ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2019 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #pragma comment(lib, "d2d1.lib") #pragma comment(lib, "dwrite.lib") #ifndef IFR #define IFR(expr) do {hr = (expr); _ASSERT(SUCCEEDED(hr)); if (FAILED(hr)) return(hr);} while(0) #endif FLOAT ConvertPointSizeToDIP(FLOAT points) { return (points / 72.0f) * 96.0f; } int wmain(int argc, wchar_t *argv[], wchar_t *envp[]) { HRESULT hr = S_OK; IDWriteFactory *pDWriteFactory; IDWriteGdiInterop *pGdiInterop; IDWriteBitmapRenderTarget *pBitmapRenderTarget; IDWriteRenderingParams *pRenderingParams; if (argc != 2) { wprintf(L"Usage: %s \n", argv[0]); return EXIT_FAILURE; } // Initialize DirectWrite. IFR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), reinterpret_cast(&pDWriteFactory))); IFR(pDWriteFactory->GetGdiInterop(&pGdiInterop)); IFR(pGdiInterop->CreateBitmapRenderTarget(GetDC(NULL), RENDER_TARGET_WIDTH, RENDER_TARGET_HEIGHT, &pBitmapRenderTarget)); IFR(pDWriteFactory->CreateRenderingParams(&pRenderingParams)); LPCWSTR szFontPath = argv[1]; BOOL bSuccess = TRUE; IDWriteFontFile *pFontFile = NULL; do { hr = pDWriteFactory->CreateFontFileReference(szFontPath, NULL, &pFontFile); if (FAILED(hr)) { wprintf(L"[-] IDWriteFactory::CreateFontFileReference failed with code %x\n", hr); bSuccess = FALSE; break; } BOOL isSupportedFontType = FALSE; DWRITE_FONT_FILE_TYPE fontFileType; DWRITE_FONT_FACE_TYPE fontFaceType; UINT32 numberOfFaces; hr = pFontFile->Analyze(&isSupportedFontType, &fontFileType, &fontFaceType, &numberOfFaces); if (FAILED(hr)) { wprintf(L"[-] IDWriteFontFile::Analyze failed with code %x\n", hr); bSuccess = FALSE; break; } if (!isSupportedFontType) { wprintf(L"[-] The input font with file type %d and face type %d isn't supported by DirectWrite\n", fontFileType, fontFaceType); bSuccess = FALSE; break; } wprintf(L"[+] Input font is supported, with file type %d, face type %d and number of faces %d\n", fontFileType, fontFaceType, numberOfFaces); for (UINT32 face_it = 0; face_it < numberOfFaces; face_it++) { IDWriteFontFile *pFontFiles[] = { pFontFile }; IDWriteFontFace *pFontFace = NULL; hr = pDWriteFactory->CreateFontFace(fontFaceType, 1, pFontFiles, face_it, DWRITE_FONT_SIMULATIONS_NONE, &pFontFace); if (FAILED(hr)) { wprintf(L"[-] IDWriteFactory::CreateFontFace(faceIndex=%u) failed with code %x\n", face_it, hr); bSuccess = FALSE; // Don't bail out at this point and instead continue processing other font faces. continue; } IDWriteFontFace1 *pFontFace1 = static_cast(pFontFace); DWRITE_FONT_METRICS fontMetrics; pFontFace->GetMetrics(&fontMetrics); UINT32 actualRangeCount; hr = pFontFace1->GetUnicodeRanges(0, NULL, &actualRangeCount); if (SUCCEEDED(hr)) { pFontFace->Release(); continue; } else if (hr != E_NOT_SUFFICIENT_BUFFER) { wprintf(L"[-] IDWriteFontFace1::GetUnicodeRanges(faceIndex=%u) failed with code %x\n", face_it, hr); bSuccess = FALSE; pFontFace->Release(); continue; } DWRITE_UNICODE_RANGE *pRanges = new DWRITE_UNICODE_RANGE[actualRangeCount]; hr = pFontFace1->GetUnicodeRanges(actualRangeCount, pRanges, &actualRangeCount); if (FAILED(hr)) { wprintf(L"[-] IDWriteFontFace1::GetUnicodeRanges(faceIndex=%u) failed second time with code %d\n", face_it, hr); bSuccess = FALSE; delete[] pRanges; pFontFace->Release(); continue; } wprintf(L"[+] Loaded %u unicode ranges from the input font\n", actualRangeCount); BitBlt(pBitmapRenderTarget->GetMemoryDC(), 0, 0, RENDER_TARGET_WIDTH, RENDER_TARGET_HEIGHT, NULL, 0, 0, WHITENESS); UINT32 code_points[DISPLAYED_GLYPHS_COUNT]; UINT16 glyph_indices[DISPLAYED_GLYPHS_COUNT]; DWRITE_GLYPH_METRICS glyph_metrics[DISPLAYED_GLYPHS_COUNT]; DWORD char_count = 0; for (UINT32 range_it = 0; range_it < actualRangeCount; range_it++) { for (UINT32 ch = pRanges[range_it].first; ch <= pRanges[range_it].last; ch++) { code_points[char_count++] = ch; if (char_count >= DISPLAYED_GLYPHS_COUNT || (range_it == actualRangeCount - 1 && ch == pRanges[range_it].last && char_count > 0)) { hr = pFontFace1->GetGlyphIndices(code_points, char_count, glyph_indices); if (SUCCEEDED(hr)) { hr = pFontFace->GetDesignGlyphMetrics(glyph_indices, char_count, glyph_metrics, /*isSideways=*/FALSE); if (FAILED(hr)) { wprintf(L"[+] IDWriteFontFace::GetDesignGlyphMetrics([%x .. %x]) failed with code %x\n", code_points[0], code_points[char_count - 1], hr); bSuccess = FALSE; } CONST FLOAT point_sizes[] = { 8.0f, 9.0f, 10.0f, 11.0f, 12.0f, 14.0f, 16.0f, 18.0f, 20.0f, 22.0f, 24.0f, 26.0f, 28.0f, 36.0f, 48.0f, 72.0f }; DWRITE_GLYPH_RUN glyphRun = { pFontFace, ConvertPointSizeToDIP(12.0f), char_count, glyph_indices, NULL, NULL, FALSE, 0 }; for (FLOAT point_size : point_sizes) { glyphRun.fontEmSize = ConvertPointSizeToDIP(point_size); CONST COLORREF color_ref = RGB(point_size * 2, point_size * 2, point_size * 2); hr = pBitmapRenderTarget->DrawGlyphRun(point_size, point_size, DWRITE_MEASURING_MODE_NATURAL, &glyphRun, pRenderingParams, color_ref); if (FAILED(hr)) { bSuccess = FALSE; } } } else { wprintf(L"[-] IDWriteFontFace1::GetGlyphIndices failed with code %x\n", hr); } char_count = 0; BitBlt(GetDC(NULL), 0, 0, RENDER_TARGET_WIDTH, RENDER_TARGET_HEIGHT, pBitmapRenderTarget->GetMemoryDC(), 0, 0, SRCCOPY | NOMIRRORBITMAP); } } } wprintf(L"[+] Font processing completed\n"); delete[] pRanges; pFontFace->Release(); } } while (0); if (bSuccess) { wprintf(L"[+] Font displayed without errors\n"); } if (pFontFile != NULL) { pFontFile->Release(); } pRenderingParams->Release(); pBitmapRenderTarget->Release(); pGdiInterop->Release(); pDWriteFactory->Release(); return 0; } ================================================ FILE: ttf-otf-mutator/Makefile.linux ================================================ SRC = main.cpp mutator.cpp random.cpp sfnt_font.cpp sfnt_mutator.cpp DEPS = common.h mutator.h random.h sfnt_font.h sfnt_mutator.h OBJ = $(SRC:.cpp=.o) OUT = mutator CFLAGS = -Wno-multichar GCC = g++ $(OUT): $(OBJ) $(GCC) $(CFLAGS) $(OBJ) -o $(OUT) %.o: %.cpp $(GCC) $(CFLAGS) $< -c -o $@ clean: rm $(OBJ) $(OUT) ================================================ FILE: ttf-otf-mutator/Makefile.mingw ================================================ SRC = main.cpp mutator.cpp random.cpp sfnt_font.cpp sfnt_mutator.cpp DEPS = common.h mutator.h random.h sfnt_font.h sfnt_mutator.h OBJ = $(SRC:.cpp=.o) OUT = mutator.exe CFLAGS = -Wno-multichar GCC = g++ SHELL = cmd $(OUT): $(OBJ) $(GCC) $(CFLAGS) $(OBJ) -o $(OUT) %.o: %.cpp $(GCC) $(CFLAGS) $< -c -o $@ clean: del $(OBJ) $(OUT) ================================================ FILE: ttf-otf-mutator/README.md ================================================ # TTF/OTF mutator The mutator inserts random binary modifications into many of the supported TrueType and OpenType [SFNT tables](https://docs.microsoft.com/en-us/typography/opentype/spec/otff#font-tables), using several mutation algorithms and per-table mutation ratios. It preserves the basic structure of the files and fixes up the checksums, in order to pass basic sanity checks and consistently reach the deeper levels of font rasterization code. The inner workings of the tool were discussed in detail in the [A year of Windows kernel font fuzzing #2](https://googleprojectzero.blogspot.com/2016/07/a-year-of-windows-kernel-font-fuzzing-2.html) blog post, but in summary, the supported mutation algorithms are _bitflipping_, _byteflipping_, _chunkspew_, _special ints_ and _binary arithmetic_. For each combination of `(table, algorithm)`, we estimated a mutation ratio such that when 10 tables are mutated in a font, it can be successfully loaded in Windows approximately 50% of the times. This left us with the following numbers: **Table**|**Bitflipping**|**Byteflipping**|**Chunkspew**|**Special Ints**|**Binary arithmetic** :-----:|:-----:|:-----:|:-----:|:-----:|:-----: `hmtx`|0.1|0.8|0.8|0.8|0.8 `maxp`|0.009766|0.078125|0.125|0.056641|0.0625 `OS/2`|0.1|0.2|0.4|0.2|0.4 `post`|0.004|0.06|0.2|0.15|0.03 `cvt`|0.1|0.1|0.1|0.1|0.1 `fpgm`|0.01|0.01|0.01|0.01|0.01 `glyf`|0.00008|0.00064|0.008|0.00064|0.00064 `prep`|0.01|0.01|0.01|0.01|0.01 `gasp`|0.1|0.1|0.1|0.1|0.1 `CFF`|0.00005|0.0001|0.001|0.0002|0.0001 `EBDT`|0.01|0.08|0.2|0.08|0.08 `EBLC`|0.001|0.001|0.001|0.001|0.001 `EBSC`|0.01|0.01|0.01|0.01|0.01 `GDEF`|0.01|0.01|0.01|0.01|0.01 `GPOS`|0.001|0.008|0.01|0.008|0.008 `GSUB`|0.01|0.08|0.01|0.08|0.08 `hdmx`|0.01|0.01|0.01|0.01|0.01 `kern`|0.01|0.01|0.01|0.01|0.01 `LTSH`|0.01|0.01|0.01|0.01|0.01 `VDMX`|0.01|0.01|0.01|0.01|0.01 `vhea`|0.1|0.1|0.1|0.1|0.1 `vmtx`|0.1|0.1|0.1|0.1|0.1 `mort`|0.01|0.01|0.01|0.01|0.01 ## Building The program can be compiled with `g++` using `Makefile.linux` on Linux, with `mingw-g++` using `Makefile.mingw` on Windows, or with Microsoft Visual Studio, after having imported all of the source files into a new project. ## Usage The usage of the tool is very simple: ``` c:\ttf-otf-mutator>mutator Usage: mutator c:\ttf-otf-mutator> ``` For example, to create a mutated `output.ttf` file based on `C:\Windows\Fonts\arial.ttf`, we can run the following command: ``` c:\ttf-otf-mutator>mutator C:\Windows\Fonts\arial.ttf output.ttf [+] Ignoring table "DSIG" [+] Mutating table "GDEF" of size 820 [+] Mutating table "GPOS" of size 73976 [+] Mutating table "GSUB" of size 27738 [+] Mutating table "JSTF" of size 30 [+] Mutating table "LTSH" of size 4241 [...] [+] Ignoring table "loca" [+] Mutating table "maxp" of size 32 [+] Ignoring table "meta" [+] Ignoring table "name" [+] Mutating table "post" of size 32 [+] Mutating table "prep" of size 3119 [+] Font successfully mutated and saved in "output.ttf". c:\ttf-otf-mutator> ``` When opened in the default Windows Font Viewer, an example output font may look as follows: ![Mutated TrueType font](../images/mutator.png) ================================================ FILE: ttf-otf-mutator/common.h ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #ifndef COMMON_H_ #define COMMON_H_ #if defined(clang) || defined(__GNUC__) #define SWAP32(value) __builtin_bswap32(value) #define SWAP16(value) __builtin_bswap16(value) #elif defined(_MSC_VER) #define SWAP32(value) _byteswap_ulong(value) #define SWAP16(value) _byteswap_ushort(value) #endif #endif // COMMON_H_ ================================================ FILE: ttf-otf-mutator/main.cpp ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #if defined(_MSC_VER) #define _CRT_SECURE_NO_WARNINGS #endif // defined(_MSC_VER) #include #include #include #include "common.h" #include "sfnt_font.h" #include "sfnt_mutator.h" static bool WriteStringToFile(const char *filename, const std::string& data) { FILE *f = fopen(filename, "w+b"); if (f == NULL) { return false; } if (fwrite(data.data(), sizeof(uint8_t), data.size(), f) != data.size()) { fclose(f); return false; } fclose(f); return true; } int main(int argc, char **argv) { if (argc != 3) { fprintf(stderr, "Usage: %s \n", argv[0]); return 1; } const char *input_file_path = argv[1]; const char *output_file_path = argv[2]; SfntFont font; if (!font.LoadFromFile(input_file_path)) { fprintf(stderr, "[-] Unable to load the \"%s\" file as a TTF/OTF font.\n", input_file_path); return 1; } SfntStrategies strategies; InitSfntMutationStrategies(&strategies); MutateSfntFile(&strategies, &font); std::string output_data; if (!font.SaveToString(&output_data)) { fprintf(stderr, "[-] Unable to save a SFNT font to a string.\n"); return 1; } if (!WriteStringToFile(output_file_path, output_data)) { fprintf(stderr, "[-] Unable to save the output font to \"%s\".\n", output_file_path); return 1; } printf("[+] Font successfully mutated and saved in \"%s\".\n", output_file_path); return 0; } ================================================ FILE: ttf-otf-mutator/mutator.cpp ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "mutator.h" #include #include #include #include #include #include "random.h" namespace globals { } // globals void Mutator::MutateString(const std::vector& strategies, std::string *buffer, unsigned int *changed_bytes) { std::vector strats = strategies; // Randomly choose the strategies we are going to use for mutation. const unsigned int mutations_no = (Random32() % strats.size()) + 1; random_shuffle(strats.begin(), strats.end()); strats.resize(mutations_no); // Choose the mutation ratios from available ranges. std::vector mutation_ratios; for (unsigned int i = 0; i < strats.size(); i++) { const double min_ratio = strats[i].min_mutation_ratio; const double max_ratio = strats[i].max_mutation_ratio; const double ratio = min_ratio + fmod(RandomFloat(), max_ratio - min_ratio + 1e-10); mutation_ratios.push_back(ratio); } // Choose how much each mutation will affect the result. std::vector division; for (unsigned int i = 0; i < strats.size() - 1; i++) { division.push_back(RandomFloat()); } division.push_back(0.0f); division.push_back(1.0f); sort(division.begin(), division.end()); for (unsigned int i = 1; i < division.size(); i++) { mutation_ratios[i - 1] *= (division[i] - division[i - 1]); } // Invoke each mutation strategy over the string. *changed_bytes = 0; for (unsigned int i = 0; i < strats.size(); i++) { unsigned int locally_changed_bytes; MutateString(strats[i].type, mutation_ratios[i], buffer, &locally_changed_bytes); *changed_bytes += locally_changed_bytes; } } void Mutator::MutateString(MutationType type, double ratio, std::string *buffer, unsigned int *changed_bytes) { switch (type) { case MUTATION_BITFLIPPING: Bitflipping(buffer, ratio, changed_bytes); break; case MUTATION_BYTEFLIPPING: Byteflipping(buffer, ratio, changed_bytes); break; case MUTATION_CHUNKSPEW: Chunkspew(buffer, ratio, changed_bytes); break; case MUTATION_SPECIAL_INTS: SpecialInts(buffer, ratio, changed_bytes); break; case MUTATION_ADD_SUB_BINARY: AddSubBinary(buffer, ratio, changed_bytes); break; default: break; } } void Mutator::Bitflipping(std::string *buffer, double ratio, unsigned int *changed_bytes) { uint32_t bytes_to_mutate = ((double)buffer->size() * ratio) * 8; *changed_bytes = bytes_to_mutate; while (bytes_to_mutate-- > 0) { uint32_t offset = (Random32() % buffer->size()); const unsigned int bit = (Random32() & 7); buffer->at(offset) ^= (1 << bit); } } void Mutator::Byteflipping(std::string *buffer, double ratio, unsigned int *changed_bytes) { uint32_t bytes_to_mutate = ((double)buffer->size() * ratio); *changed_bytes = bytes_to_mutate; while (bytes_to_mutate-- > 0) { const uint32_t offset = (Random32() % buffer->size()); buffer->at(offset) = (Random32() & 0xff); } } void Mutator::Chunkspew(std::string *buffer, double ratio, unsigned int *changed_bytes) { const unsigned int kMaximumChunkSpew = 64; const uint32_t bytes_to_mutate = ((double)buffer->size() * ratio); uint32_t mutated_bytes = 0; uint8_t chunk_buffer[kMaximumChunkSpew]; while (mutated_bytes < bytes_to_mutate) { const uint32_t mutation_size = (Random32() % MIN3(kMaximumChunkSpew, buffer->size() / 2, bytes_to_mutate - mutated_bytes + 1)); const uint32_t source_offset = (Random32() % (buffer->size() - mutation_size)); const uint32_t dest_offset = (Random32() % (buffer->size() - mutation_size)); memcpy(chunk_buffer, &buffer->data()[source_offset], mutation_size); buffer->replace(dest_offset, mutation_size, reinterpret_cast(chunk_buffer), mutation_size); mutated_bytes += mutation_size; } *changed_bytes = mutated_bytes; } void Mutator::SpecialInts(std::string *buffer, double ratio, unsigned int *changed_bytes) { const struct { size_t size; const char *data; } kSpecialBinaryInts[] = { {1, "\x00"}, {1, "\x7f"}, {1, "\x80"}, {1, "\xff"}, {2, "\x00\x00"}, {2, "\x7f\xff"}, {2, "\xff\xff"}, {2, "\x80\x00"}, {2, "\x40\x00"}, {2, "\xc0\x00"}, {4, "\x00\x00\x00\x00"}, {4, "\x7f\xff\xff\xff"}, {4, "\x80\x00\x00\x00"}, {4, "\x40\x00\x00\x00"}, {4, "\xc0\x00\x00\x00"}, {4, "\xff\xff\xff\xff"} }; const uint32_t bytes_to_mutate = ((double)buffer->size() * ratio); uint32_t mutated_bytes = 0; while (mutated_bytes < bytes_to_mutate) { const unsigned int int_idx = (Random32() % (sizeof(kSpecialBinaryInts) / sizeof(kSpecialBinaryInts[0]))); const unsigned int int_size = kSpecialBinaryInts[int_idx].size; uint32_t offset = (Random32() % (buffer->size() - int_size + 1)); buffer->replace(offset, int_size, kSpecialBinaryInts[int_idx].data, int_size); mutated_bytes += int_size; } *changed_bytes = mutated_bytes; } void Mutator::AddSubBinary(std::string *buffer, double ratio, unsigned int *changed_bytes) { const uint32_t bytes_to_mutate = ((double)buffer->size() * ratio); uint32_t mutated_bytes = 0; while (mutated_bytes < bytes_to_mutate) { const uint32_t spec = Random32(); const bool big_endian = ((spec & 1) == 1); const bool addition = ((spec & 2) == 2); unsigned int unit_width; uint32_t max_value; // Decide on the unit width. switch ((spec >> 2) % 3) { case 0: // Byte arithmetic. unit_width = 1; max_value = UCHAR_MAX; break; case 1: // Word arithmetic. unit_width = 2; max_value = USHRT_MAX; break; case 2: // Dword arithmetic. unit_width = 4; max_value = UINT_MAX; break; } // Choose offset. if (buffer->size() < unit_width) { continue; } const uint32_t offset = (Random32() % (buffer->size() - unit_width + 1)); // Read the value. uint32_t value = 0; if (big_endian) { for (unsigned int i = 0; i < unit_width; ++i) { value |= (buffer->at(offset + i) << (8 * ((unit_width - 1) - i))); } } else { for (unsigned int i = 0; i < unit_width; ++i) { value |= (buffer->at(offset + i) << (8 * i)); } } // Choose an operand. uint32_t op; switch (unit_width) { case 1: op = 1 + (Random32() % UCHAR_MAX); break; case 2: case 4: if (value > UCHAR_MAX && (Random32() & 1) == 1) { op = 1 + (Random32() % USHRT_MAX); } else { op = 1 + (Random32() % UCHAR_MAX); } break; } // Perform the arithmetic. if (addition) { if (max_value - value < op) { value = max_value; } else { value += op; } } else { if (value < op) { value = 0; } else { value -= op; } } // Write the data back to buffer and count byte diffs. unsigned int byte_delta = 0; if (big_endian) { for (unsigned int i = 0; i < unit_width; ++i) { uint8_t byte = (value >> (8 * ((unit_width - 1) - i))); if (buffer->at(offset + i) != byte) { buffer->at(offset + i) = byte; byte_delta++; } } } else { for (unsigned int i = 0; i < unit_width; ++i) { uint8_t byte = (value >> (8 * i)); if (buffer->at(offset + i) != byte) { buffer->at(offset + i) = byte; byte_delta++; } } } // Update the mutation progress made so far. mutated_bytes += byte_delta; } *changed_bytes = mutated_bytes; } ================================================ FILE: ttf-otf-mutator/mutator.h ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #ifndef MUTATOR_H_ #define MUTATOR_H_ #include #include #define MIN3(a, b, c) ((((a) < (b) ? (a) : (b)) < (c)) ? ((a) < (b) ? (a) : (b)) : (c)) enum MutationType { MUTATION_BITFLIPPING, MUTATION_BYTEFLIPPING, MUTATION_CHUNKSPEW, MUTATION_SPECIAL_INTS, MUTATION_ADD_SUB_BINARY }; struct MutationStrategy { enum MutationType type; double min_mutation_ratio; double max_mutation_ratio; }; class Mutator { public: static void MutateString(const std::vector& strategies, std::string *buffer, unsigned int *changed_bytes); static void MutateString(MutationType type, double ratio, std::string *buffer, unsigned int *changed_bytes); private: static void Bitflipping(std::string *buffer, double ratio, unsigned int *changed_bytes); static void Byteflipping(std::string *buffer, double ratio, unsigned int *changed_bytes); static void Chunkspew(std::string *buffer, double ratio, unsigned int *changed_bytes); static void SpecialInts(std::string *buffer, double ratio, unsigned int *changed_bytes); static void AddSubBinary(std::string *buffer, double ratio, unsigned int *changed_bytes); }; #endif // MUTATOR_H_ ================================================ FILE: ttf-otf-mutator/random.cpp ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "random.h" #include #include #include #include #include namespace globals { std::mt19937 generator; std::uniform_int_distribution int_distrib(0, UINT_MAX); std::uniform_real_distribution real_distrib(0.0, 1.0); } // namespace globals static void EnsureSeeded() { static bool seeded = false; if (!seeded) { std::random_device rd; globals::generator.seed(rd()); seeded = true; } } uint32_t Random32() { EnsureSeeded(); return globals::int_distrib(globals::generator); } double RandomFloat() { EnsureSeeded(); return globals::real_distrib(globals::generator); } ================================================ FILE: ttf-otf-mutator/random.h ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #ifndef RANDOM_H_ #define RANDOM_H_ #include uint32_t Random32(); double RandomFloat(); #endif // RANDOM_H_ ================================================ FILE: ttf-otf-mutator/sfnt_font.cpp ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #if defined(_MSC_VER) #define _CRT_SECURE_NO_WARNINGS #endif // defined(_MSC_VER) #include "sfnt_font.h" #include #include "common.h" SfntFont::SfntFont(const SfntFont& font) { sfnt_version_ = font.sfnt_version_; sfnt_tables_ = font.sfnt_tables_; } bool SfntFont::LoadFromFile(const char *file_path) { // Open the file. FILE *f = fopen(file_path, "rb"); if (f == NULL) { goto error; } // Read the file header, but only save the SFNT version; other elements will // be recalculated while writing the output file. SfntHeader hdr; uint16_t num_tables; if (fread(&hdr, sizeof(SfntHeader), 1, f) != 1) { goto error; } sfnt_version_ = hdr.version; num_tables = SWAP16(hdr.num_tables); sfnt_tables_.resize(num_tables); // Read the table headers. for (uint32_t i = 0; i < num_tables; i++) { if (fseek(f, sizeof(SfntHeader) + i * sizeof(SfntTableHeader), SEEK_SET) != 0) { goto error; } SfntTableHeader table_hdr; if (fread(&table_hdr, sizeof(SfntTableHeader), 1, f) != 1) { goto error; } uint32_t table_offset = SWAP32(table_hdr.offset); uint32_t table_length = SWAP32(table_hdr.length); void *buffer = malloc(table_length); if (buffer == NULL) { goto error; } if (fseek(f, table_offset, SEEK_SET) != 0) { free(buffer); goto error; } if (fread(buffer, sizeof(uint8_t), table_length, f) != table_length) { free(buffer); goto error; } sfnt_tables_[i].tag = SWAP32(table_hdr.tag); sfnt_tables_[i].data.assign((char *)buffer, table_length); free(buffer); } // Close the file. fclose(f); f = NULL; return true; error: if (f != NULL) { fclose(f); } sfnt_version_ = 0; sfnt_tables_.clear(); return false; } bool SfntFont::SaveToFile(const char *file_path) { // Save the font to a string. std::string font_contents; if (!SaveToString(&font_contents)) { return false; } // Open the file. FILE *f = fopen(file_path, "w+b"); if (f == NULL) { return false; } if (fwrite(font_contents.data(), sizeof(uint8_t), font_contents.size(), f) != font_contents.size()) { fclose(f); return false; } // Close the file. fclose(f); return true; } bool SfntFont::SaveToString(std::string *buffer) { buffer->clear(); // Craft and write the file header. SfntHeader hdr; hdr.version = sfnt_version_; hdr.num_tables = SWAP16(sfnt_tables_.size()); hdr.search_range = 1; hdr.entry_selector = 0; while (2 * hdr.search_range <= sfnt_tables_.size()) { hdr.search_range *= 2; hdr.entry_selector++; } hdr.search_range = SWAP16(16 * hdr.search_range); hdr.entry_selector = SWAP16(hdr.entry_selector); hdr.range_shift = SWAP16((sfnt_tables_.size() * 16) - SWAP16(hdr.search_range)); buffer->append((char *)&hdr, sizeof(SfntHeader)); // Craft and write the table headers. uint32_t curr_offset = sizeof(SfntHeader) + sfnt_tables_.size() * sizeof(SfntTableHeader); std::vector table_offsets; for (unsigned int i = 0; i < sfnt_tables_.size(); i++) { SfntTableHeader table_hdr; table_hdr.tag = SWAP32(sfnt_tables_[i].tag); table_hdr.checksum = SWAP32(CalculateChecksum(sfnt_tables_[i].data)); table_hdr.offset = SWAP32(curr_offset); table_hdr.length = SWAP32(sfnt_tables_[i].data.size()); // Account for the padding when calculating table data offset. curr_offset += ((sfnt_tables_[i].data.size() + 3) & (-4)); buffer->append((char *)&table_hdr, sizeof(SfntTableHeader)); } // Write the actual data segments to the file. for (unsigned int i = 0; i < sfnt_tables_.size(); i++) { buffer->append(sfnt_tables_[i].data.data(), sfnt_tables_[i].data.size()); // Insert padding bytes if necessary. if (sfnt_tables_[i].data.size() & 3) { buffer->append(4 - (sfnt_tables_[i].data.size() & 3), '\0'); } } return true; } uint32_t SfntFont::CalculateChecksum(const std::string& data) { uint32_t sum = 0, i; for (i = 0; i + 3 < data.size(); i += 4) { sum += SWAP32(*(uint32_t *)&data[i]); } if (i != data.size()) { char buf[4] = {0}; for (uint32_t j = 0; i < data.size(); i++, j++) { buf[j] = data[i]; } sum += SWAP32(*(uint32_t *)&buf[0]); } return sum; } ================================================ FILE: ttf-otf-mutator/sfnt_font.h ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #ifndef SFNT_FONT_H_ #define SFNT_FONT_H_ #include #include #include #pragma pack(push, 1) struct SfntHeader { uint32_t version; uint16_t num_tables; uint16_t search_range; uint16_t entry_selector; uint16_t range_shift; }; struct SfntTableHeader { uint32_t tag; uint32_t checksum; uint32_t offset; uint32_t length; }; #pragma pack(pop) struct SfntTable { uint32_t tag; std::string data; }; class SfntFont { public: SfntFont() : sfnt_version_(0) { } SfntFont(const SfntFont& font); ~SfntFont() { } bool LoadFromFile(const char *file_path); bool SaveToFile(const char *file_path); bool SaveToString(std::string *buffer); uint32_t sfnt_version_; std::vector sfnt_tables_; private: uint32_t CalculateChecksum(const std::string& data); }; #endif // SFNT_FONT_H_ ================================================ FILE: ttf-otf-mutator/sfnt_mutator.cpp ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include "sfnt_mutator.h" #include #include "common.h" #include "mutator.h" #include "sfnt_font.h" void InitSfntMutationStrategies(SfntStrategies *strategies) { MutationStrategy strat; #define ADD_MUTATION_STRATEGY(tag, mutation_type, mutation_ratio) {\ strat.type = mutation_type;\ strat.min_mutation_ratio = 0.0;\ strat.max_mutation_ratio = 2.0 * mutation_ratio;\ (*strategies)[tag].push_back(strat);\ } ADD_MUTATION_STRATEGY('hmtx', MUTATION_BITFLIPPING, 0.1); ADD_MUTATION_STRATEGY('hmtx', MUTATION_BYTEFLIPPING, 0.8); ADD_MUTATION_STRATEGY('hmtx', MUTATION_CHUNKSPEW, 0.8); ADD_MUTATION_STRATEGY('hmtx', MUTATION_SPECIAL_INTS, 0.8); ADD_MUTATION_STRATEGY('hmtx', MUTATION_ADD_SUB_BINARY, 0.8); ADD_MUTATION_STRATEGY('maxp', MUTATION_BITFLIPPING, 0.009766); ADD_MUTATION_STRATEGY('maxp', MUTATION_BYTEFLIPPING, 0.078125); ADD_MUTATION_STRATEGY('maxp', MUTATION_CHUNKSPEW, 0.125); ADD_MUTATION_STRATEGY('maxp', MUTATION_SPECIAL_INTS, 0.056641); ADD_MUTATION_STRATEGY('maxp', MUTATION_ADD_SUB_BINARY, 0.0625); ADD_MUTATION_STRATEGY('OS/2', MUTATION_BITFLIPPING, 0.1); ADD_MUTATION_STRATEGY('OS/2', MUTATION_BYTEFLIPPING, 0.2); ADD_MUTATION_STRATEGY('OS/2', MUTATION_CHUNKSPEW, 0.4); ADD_MUTATION_STRATEGY('OS/2', MUTATION_SPECIAL_INTS, 0.2); ADD_MUTATION_STRATEGY('OS/2', MUTATION_ADD_SUB_BINARY, 0.4); ADD_MUTATION_STRATEGY('post', MUTATION_BITFLIPPING, 0.004); ADD_MUTATION_STRATEGY('post', MUTATION_BYTEFLIPPING, 0.06); ADD_MUTATION_STRATEGY('post', MUTATION_CHUNKSPEW, 0.2); ADD_MUTATION_STRATEGY('post', MUTATION_SPECIAL_INTS, 0.15); ADD_MUTATION_STRATEGY('post', MUTATION_ADD_SUB_BINARY, 0.03); ADD_MUTATION_STRATEGY('cvt ', MUTATION_BITFLIPPING, 0.1); ADD_MUTATION_STRATEGY('cvt ', MUTATION_BYTEFLIPPING, 0.1); ADD_MUTATION_STRATEGY('cvt ', MUTATION_CHUNKSPEW, 0.1); ADD_MUTATION_STRATEGY('cvt ', MUTATION_SPECIAL_INTS, 0.1); ADD_MUTATION_STRATEGY('cvt ', MUTATION_ADD_SUB_BINARY, 0.1); ADD_MUTATION_STRATEGY('fpgm', MUTATION_BITFLIPPING, 0.1); ADD_MUTATION_STRATEGY('fpgm', MUTATION_BYTEFLIPPING, 0.1); ADD_MUTATION_STRATEGY('fpgm', MUTATION_CHUNKSPEW, 0.1); ADD_MUTATION_STRATEGY('fpgm', MUTATION_SPECIAL_INTS, 0.1); ADD_MUTATION_STRATEGY('fpgm', MUTATION_ADD_SUB_BINARY, 0.1); ADD_MUTATION_STRATEGY('glyf', MUTATION_BITFLIPPING, 0.00008); ADD_MUTATION_STRATEGY('glyf', MUTATION_BYTEFLIPPING, 0.00064); ADD_MUTATION_STRATEGY('glyf', MUTATION_CHUNKSPEW, 0.008); ADD_MUTATION_STRATEGY('glyf', MUTATION_SPECIAL_INTS, 0.00064); ADD_MUTATION_STRATEGY('glyf', MUTATION_ADD_SUB_BINARY, 0.00064); ADD_MUTATION_STRATEGY('prep', MUTATION_BITFLIPPING, 0.01); ADD_MUTATION_STRATEGY('prep', MUTATION_BYTEFLIPPING, 0.01); ADD_MUTATION_STRATEGY('prep', MUTATION_CHUNKSPEW, 0.01); ADD_MUTATION_STRATEGY('prep', MUTATION_SPECIAL_INTS, 0.01); ADD_MUTATION_STRATEGY('prep', MUTATION_ADD_SUB_BINARY, 0.01); ADD_MUTATION_STRATEGY('gasp', MUTATION_BITFLIPPING, 0.1); ADD_MUTATION_STRATEGY('gasp', MUTATION_BYTEFLIPPING, 0.1); ADD_MUTATION_STRATEGY('gasp', MUTATION_CHUNKSPEW, 0.1); ADD_MUTATION_STRATEGY('gasp', MUTATION_SPECIAL_INTS, 0.1); ADD_MUTATION_STRATEGY('gasp', MUTATION_ADD_SUB_BINARY, 0.1); ADD_MUTATION_STRATEGY('CFF ', MUTATION_BITFLIPPING, 0.00005); ADD_MUTATION_STRATEGY('CFF ', MUTATION_BYTEFLIPPING, 0.0001); ADD_MUTATION_STRATEGY('CFF ', MUTATION_CHUNKSPEW, 0.001); ADD_MUTATION_STRATEGY('CFF ', MUTATION_SPECIAL_INTS, 0.0002); ADD_MUTATION_STRATEGY('CFF ', MUTATION_ADD_SUB_BINARY, 0.0001); ADD_MUTATION_STRATEGY('EBDT', MUTATION_BITFLIPPING, 0.01); ADD_MUTATION_STRATEGY('EBDT', MUTATION_BYTEFLIPPING, 0.08); ADD_MUTATION_STRATEGY('EBDT', MUTATION_CHUNKSPEW, 0.2); ADD_MUTATION_STRATEGY('EBDT', MUTATION_SPECIAL_INTS, 0.08); ADD_MUTATION_STRATEGY('EBDT', MUTATION_ADD_SUB_BINARY, 0.08); ADD_MUTATION_STRATEGY('EBLC', MUTATION_BITFLIPPING, 0.001); ADD_MUTATION_STRATEGY('EBLC', MUTATION_BYTEFLIPPING, 0.001); ADD_MUTATION_STRATEGY('EBLC', MUTATION_CHUNKSPEW, 0.001); ADD_MUTATION_STRATEGY('EBLC', MUTATION_SPECIAL_INTS, 0.001); ADD_MUTATION_STRATEGY('EBLC', MUTATION_ADD_SUB_BINARY, 0.001); ADD_MUTATION_STRATEGY('EBSC', MUTATION_BITFLIPPING, 0.01); ADD_MUTATION_STRATEGY('EBSC', MUTATION_BYTEFLIPPING, 0.01); ADD_MUTATION_STRATEGY('EBSC', MUTATION_CHUNKSPEW, 0.01); ADD_MUTATION_STRATEGY('EBSC', MUTATION_SPECIAL_INTS, 0.01); ADD_MUTATION_STRATEGY('EBSC', MUTATION_ADD_SUB_BINARY, 0.01); ADD_MUTATION_STRATEGY('BASE', MUTATION_BITFLIPPING, 0.01); ADD_MUTATION_STRATEGY('BASE', MUTATION_BYTEFLIPPING, 0.01); ADD_MUTATION_STRATEGY('BASE', MUTATION_CHUNKSPEW, 0.01); ADD_MUTATION_STRATEGY('BASE', MUTATION_SPECIAL_INTS, 0.01); ADD_MUTATION_STRATEGY('BASE', MUTATION_ADD_SUB_BINARY, 0.01); ADD_MUTATION_STRATEGY('GDEF', MUTATION_BITFLIPPING, 0.01); ADD_MUTATION_STRATEGY('GDEF', MUTATION_BYTEFLIPPING, 0.01); ADD_MUTATION_STRATEGY('GDEF', MUTATION_CHUNKSPEW, 0.01); ADD_MUTATION_STRATEGY('GDEF', MUTATION_SPECIAL_INTS, 0.01); ADD_MUTATION_STRATEGY('GDEF', MUTATION_ADD_SUB_BINARY, 0.01); ADD_MUTATION_STRATEGY('GPOS', MUTATION_BITFLIPPING, 0.001); ADD_MUTATION_STRATEGY('GPOS', MUTATION_BYTEFLIPPING, 0.008); ADD_MUTATION_STRATEGY('GPOS', MUTATION_CHUNKSPEW, 0.01); ADD_MUTATION_STRATEGY('GPOS', MUTATION_SPECIAL_INTS, 0.008); ADD_MUTATION_STRATEGY('GPOS', MUTATION_ADD_SUB_BINARY, 0.008); ADD_MUTATION_STRATEGY('GSUB', MUTATION_BITFLIPPING, 0.01); ADD_MUTATION_STRATEGY('GSUB', MUTATION_BYTEFLIPPING, 0.08); ADD_MUTATION_STRATEGY('GSUB', MUTATION_CHUNKSPEW, 0.01); ADD_MUTATION_STRATEGY('GSUB', MUTATION_SPECIAL_INTS, 0.08); ADD_MUTATION_STRATEGY('GSUB', MUTATION_ADD_SUB_BINARY, 0.08); ADD_MUTATION_STRATEGY('JSTF', MUTATION_BITFLIPPING, 0.01); ADD_MUTATION_STRATEGY('JSTF', MUTATION_BYTEFLIPPING, 0.01); ADD_MUTATION_STRATEGY('JSTF', MUTATION_CHUNKSPEW, 0.01); ADD_MUTATION_STRATEGY('JSTF', MUTATION_SPECIAL_INTS, 0.01); ADD_MUTATION_STRATEGY('JSTF', MUTATION_ADD_SUB_BINARY, 0.01); ADD_MUTATION_STRATEGY('hdmx', MUTATION_BITFLIPPING, 0.01); ADD_MUTATION_STRATEGY('hdmx', MUTATION_BYTEFLIPPING, 0.01); ADD_MUTATION_STRATEGY('hdmx', MUTATION_CHUNKSPEW, 0.01); ADD_MUTATION_STRATEGY('hdmx', MUTATION_SPECIAL_INTS, 0.01); ADD_MUTATION_STRATEGY('hdmx', MUTATION_ADD_SUB_BINARY, 0.01); ADD_MUTATION_STRATEGY('kern', MUTATION_BITFLIPPING, 0.01); ADD_MUTATION_STRATEGY('kern', MUTATION_BYTEFLIPPING, 0.01); ADD_MUTATION_STRATEGY('kern', MUTATION_CHUNKSPEW, 0.01); ADD_MUTATION_STRATEGY('kern', MUTATION_SPECIAL_INTS, 0.01); ADD_MUTATION_STRATEGY('kern', MUTATION_ADD_SUB_BINARY, 0.01); ADD_MUTATION_STRATEGY('LTSH', MUTATION_BITFLIPPING, 0.01); ADD_MUTATION_STRATEGY('LTSH', MUTATION_BYTEFLIPPING, 0.01); ADD_MUTATION_STRATEGY('LTSH', MUTATION_CHUNKSPEW, 0.01); ADD_MUTATION_STRATEGY('LTSH', MUTATION_SPECIAL_INTS, 0.01); ADD_MUTATION_STRATEGY('LTSH', MUTATION_ADD_SUB_BINARY, 0.01); ADD_MUTATION_STRATEGY('VDMX', MUTATION_BITFLIPPING, 0.01); ADD_MUTATION_STRATEGY('VDMX', MUTATION_BYTEFLIPPING, 0.01); ADD_MUTATION_STRATEGY('VDMX', MUTATION_CHUNKSPEW, 0.01); ADD_MUTATION_STRATEGY('VDMX', MUTATION_SPECIAL_INTS, 0.01); ADD_MUTATION_STRATEGY('VDMX', MUTATION_ADD_SUB_BINARY, 0.01); ADD_MUTATION_STRATEGY('vhea', MUTATION_BITFLIPPING, 0.1); ADD_MUTATION_STRATEGY('vhea', MUTATION_BYTEFLIPPING, 0.1); ADD_MUTATION_STRATEGY('vhea', MUTATION_CHUNKSPEW, 0.1); ADD_MUTATION_STRATEGY('vhea', MUTATION_SPECIAL_INTS, 0.1); ADD_MUTATION_STRATEGY('vhea', MUTATION_ADD_SUB_BINARY, 0.1); ADD_MUTATION_STRATEGY('vmtx', MUTATION_BITFLIPPING, 0.1); ADD_MUTATION_STRATEGY('vmtx', MUTATION_BYTEFLIPPING, 0.1); ADD_MUTATION_STRATEGY('vmtx', MUTATION_CHUNKSPEW, 0.1); ADD_MUTATION_STRATEGY('vmtx', MUTATION_SPECIAL_INTS, 0.1); ADD_MUTATION_STRATEGY('vmtx', MUTATION_ADD_SUB_BINARY, 0.1); ADD_MUTATION_STRATEGY('mort', MUTATION_BITFLIPPING, 0.01); ADD_MUTATION_STRATEGY('mort', MUTATION_BYTEFLIPPING, 0.01); ADD_MUTATION_STRATEGY('mort', MUTATION_CHUNKSPEW, 0.01); ADD_MUTATION_STRATEGY('mort', MUTATION_SPECIAL_INTS, 0.01); ADD_MUTATION_STRATEGY('mort', MUTATION_ADD_SUB_BINARY, 0.01); } void MutateSfntFile(SfntStrategies *strategies, SfntFont *font) { for (unsigned int i = 0; i < font->sfnt_tables_.size(); i++) { uint32_t tag = SWAP32(font->sfnt_tables_[i].tag); char tag_name[5]; memcpy(&tag_name[0], &tag, sizeof(uint32_t)); tag_name[4] = '\0'; if (strategies->find(font->sfnt_tables_[i].tag) != strategies->end()) { printf("[+] Mutating table \"%s\" of size %d\n", tag_name, (int)font->sfnt_tables_[i].data.size()); unsigned int changed_bytes; Mutator::MutateString((*strategies)[font->sfnt_tables_[i].tag], &font->sfnt_tables_[i].data, &changed_bytes); } else { printf("[+] Ignoring table \"%s\"\n", tag_name); } } } ================================================ FILE: ttf-otf-mutator/sfnt_mutator.h ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #ifndef SFNT_MUTATOR_H_ #define SFNT_MUTATOR_H_ #include #include #include #include "mutator.h" #include "sfnt_font.h" typedef std::map > SfntStrategies; void InitSfntMutationStrategies(SfntStrategies *strategies); void MutateSfntFile(SfntStrategies *strategies, SfntFont *font); #endif // SFNT_MUTATOR_H_ ================================================ FILE: ttf-otf-windows-loader/README.md ================================================ # Font loader for Windows The loader is designed to temporarily install a specific font in Windows, and test the built-in rasterization code present in the operating system against the (potentially malformed) file. The purpose of the program is to stress-test as much font-handling code as possible, and to execute it for all glyphs found in the font file instead of a limited charset such as just the ASCII characters. The font-related GDI calls made by the loader are listed below: - [AddFontResourceW](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-addfontresourcew) - [RemoveFontResourceW](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-removefontresourcew) - [GetFontResourceInfoW](http://www.undocprint.org/winspool/getfontresourceinfo) - [CreateFontIndirectW](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-createfontindirectw) - [GetKerningPairs](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-getkerningpairsa) - [GetFontUnicodeRanges](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-getfontunicoderanges) - [GetGlyphOutline](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-getglyphoutlinea) - [DrawTextW](https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-drawtextw) Furthermore, the program also invokes a number of Uniscribe API functions: - [ScriptCacheGetHeight](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptcachegetheight) - [ScriptGetFontProperties](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetfontproperties) - [ScriptGetGlyphABCWidth](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetglyphabcwidth) - [ScriptGetCMap](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetcmap) - [ScriptGetFontScriptTags](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetfontscripttags) - [ScriptGetFontLanguageTags](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetfontlanguagetags) - [ScriptGetFontFeatureTags](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetfontfeaturetags) - [ScriptGetFontAlternateGlyphs](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetfontalternateglyphs) - [ScriptSubstituteSingleGlyph](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptsubstitutesingleglyph) ## Building The application can be compiled with Microsoft Visual Studio after importing `ttf-otf-windows-loader.cpp` and `config.h` into a new project. ## Usage Using the tool is as simple as passing the path of the tested TTF/OTF font in the first argument, for example: ``` c:\ttf-otf-windows-loader>ttf-otf-windows-loader.exe C:\Windows\Fonts\arial.ttf [+] Extracted 1 logfonts. [+] Installed 1 fonts. [+] Starting to test font 1 / 1, variation 1 / 5 [+] Getting kerning pairs [+] Getting unicode ranges [+] Getting glyph outlines and drawing them on screen [+] Testing the Uniscribe user-mode library [+] Starting to test font 1 / 1, variation 2 / 5 [+] Getting kerning pairs [+] Getting unicode ranges [+] Getting glyph outlines and drawing them on screen [+] Testing the Uniscribe user-mode library [+] Starting to test font 1 / 1, variation 3 / 5 [+] Getting kerning pairs [+] Getting unicode ranges [+] Getting glyph outlines and drawing them on screen [+] Testing the Uniscribe user-mode library [+] Starting to test font 1 / 1, variation 4 / 5 [+] Getting kerning pairs [+] Getting unicode ranges [+] Getting glyph outlines and drawing them on screen [+] Testing the Uniscribe user-mode library [+] Starting to test font 1 / 1, variation 5 / 5 [+] Getting kerning pairs [+] Getting unicode ranges [+] Getting glyph outlines and drawing them on screen [+] Testing the Uniscribe user-mode library c:\ttf-otf-windows-loader> ``` In addition to the standard output, you should also observe the font's glyphs being drawn in the upper left corner of the screen: ![Font glyphs displayed on the screen with the DrawText API call](../images/loader.png) When fuzzing fonts in Windows 7 and 8.1, we recommend enabling the [Special Pool](https://docs.microsoft.com/en-us/windows-hardware/drivers/devtest/special-pool) mechanism for the `win32k.sys` and `atmfd.dll` kernel modules. On Windows 10, it is a good idea to enable [Page Heap](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/gflags-and-pageheap) for the `fontdrvhost.exe` process, as font processing was moved to user space in the latest version of the system. ================================================ FILE: ttf-otf-windows-loader/config.h ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #ifndef CONFIG_H_ #define CONFIG_H_ // Harness build options (tested font-related functionality) #define HARNESS_TEST_KERNING_PAIRS 1 #define HARNESS_TEST_DRAWTEXT 1 #define HARNESS_TEST_GLYPH_OUTLINE 1 #define HARNESS_TEST_UNISCRIBE 1 // Number of glyphs displayed at once by a single DrawText() call. #define DISPLAYED_GLYPHS_COUNT (10) // Maximum number of alternate glyphs requested through the // usp10!ScriptGetFontAlternateGlyphs function. #define MAX_ALTERNATE_GLYPHS (10) // Maximum number of tags requested through the usp10!ScriptGetFontScriptTags, // usp10!ScriptGetFontLanguageTags and usp10!ScriptGetFontFeatureTags functions. #define UNISCRIBE_MAX_TAGS (16) #endif // CONFIG_H_ ================================================ FILE: ttf-otf-windows-loader/ttf-otf-windows-loader.cpp ================================================ ///////////////////////////////////////////////////////////////////////// // // Author: Mateusz Jurczyk (mjurczyk@google.com) // // Copyright 2018 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // #include #include #include #include #include "config.h" #pragma comment(lib, "Usp10.lib") #ifndef ARRAY_SIZE #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) #endif // // Undocumented definitions required to use the gdi32!GetFontResourceInfoW function. // typedef BOOL(WINAPI *PGFRI)(LPCWSTR, LPDWORD, LPVOID, DWORD); #define QFR_LOGFONT (2) static BOOL GetLogfonts(LPCWSTR szFontPath, LPLOGFONTW *lpLogfonts, LPDWORD lpdwFonts) { // Unload all instances of fonts with this path, in case there are any leftovers in the system. while (RemoveFontResourceW(szFontPath)) {} // Load the font file into the system temporarily. DWORD dwFontsLoaded = AddFontResourceW(szFontPath); if (dwFontsLoaded == 0) { wprintf(L"[-] AddFontResourceW() failed.\n"); return FALSE; } *lpdwFonts = dwFontsLoaded; *lpLogfonts = (LPLOGFONTW)HeapAlloc(GetProcessHeap(), 0, dwFontsLoaded * sizeof(LOGFONTW)); if (*lpLogfonts == NULL) { wprintf(L"[-] HeapAlloc(%u) failed.\n", dwFontsLoaded * sizeof(LOGFONTW)); RemoveFontResourceW(szFontPath); return FALSE; } // Resolve the GDI32!GetFontResourceInfoW symbol. HINSTANCE hGdi32 = GetModuleHandleA("gdi32.dll"); PGFRI GetFontResourceInfo = (PGFRI)GetProcAddress(hGdi32, "GetFontResourceInfoW"); DWORD cbBuffer = dwFontsLoaded * sizeof(LOGFONTW); if (!GetFontResourceInfo(szFontPath, &cbBuffer, *lpLogfonts, QFR_LOGFONT)) { wprintf(L"[-] GetFontResourceInfoW() failed.\n"); HeapFree(GetProcessHeap(), 0, *lpLogfonts); RemoveFontResourceW(szFontPath); return FALSE; } // Unload the font. RemoveFontResourceW(szFontPath); return TRUE; } static VOID TestUniscribe(HDC hdc, LPGLYPHSET lpGlyphset) { SCRIPT_CACHE sc = NULL; // Get font height. long tmHeight; ScriptCacheGetHeight(hdc, &sc, &tmHeight); // Get font properties. SCRIPT_FONTPROPERTIES fp; ScriptGetFontProperties(hdc, &sc, &fp); // Perform some operations (mostly in batches) over each supported glyph. WCHAR szTextBuffer[DISPLAYED_GLYPHS_COUNT]; DWORD dwTextCount = 0; WORD *wOutGlyphs = (WORD *)HeapAlloc(GetProcessHeap(), 0, DISPLAYED_GLYPHS_COUNT * sizeof(WORD)); for (DWORD i = 0; i < lpGlyphset->cRanges; i++) { for (LONG j = lpGlyphset->ranges[i].wcLow; j < lpGlyphset->ranges[i].wcLow + lpGlyphset->ranges[i].cGlyphs; j++) { szTextBuffer[dwTextCount++] = (WCHAR)j; // Test particular characters. ABC abc; ScriptGetGlyphABCWidth(hdc, &sc, (WORD)j, &abc); // Test characters in batches where possible. if (dwTextCount >= DISPLAYED_GLYPHS_COUNT) { // Test the usp10!ScriptGetCMap function. ScriptGetCMap(hdc, &sc, szTextBuffer, dwTextCount, 0, wOutGlyphs); dwTextCount = 0; } } } if (dwTextCount > 0) { // Test the usp10!ScriptGetCMap function. ScriptGetCMap(hdc, &sc, szTextBuffer, dwTextCount, 0, wOutGlyphs); } // Call some script/lang/feature-related APIs over each glyph. OPENTYPE_TAG *ScriptTags = (OPENTYPE_TAG *)HeapAlloc(GetProcessHeap(), 0, UNISCRIBE_MAX_TAGS * sizeof(OPENTYPE_TAG)); OPENTYPE_TAG *LangTags = (OPENTYPE_TAG *)HeapAlloc(GetProcessHeap(), 0, UNISCRIBE_MAX_TAGS * sizeof(OPENTYPE_TAG)); OPENTYPE_TAG *FeatureTags = (OPENTYPE_TAG *)HeapAlloc(GetProcessHeap(), 0, UNISCRIBE_MAX_TAGS * sizeof(OPENTYPE_TAG)); int cScriptTags = 0, cLangTags = 0, cFeatureTags = 0; WORD *AlternateGlyphs = (WORD *)HeapAlloc(GetProcessHeap(), 0, MAX_ALTERNATE_GLYPHS * sizeof(WORD)); int cAlternates; // Retrieve a list of available scripts in the font. if (ScriptGetFontScriptTags(hdc, &sc, NULL, UNISCRIBE_MAX_TAGS, ScriptTags, &cScriptTags) == 0) { for (int ScriptTag = 0; ScriptTag < cScriptTags; ScriptTag++) { // Retrieve a list of language tags for the specified script tag. if (ScriptGetFontLanguageTags(hdc, &sc, NULL, ScriptTags[ScriptTag], UNISCRIBE_MAX_TAGS, LangTags, &cLangTags) == 0) { for (int LangTag = 0; LangTag < cLangTags; LangTag++) { // Retrieve a list of typographic features for the defined writing system. if (ScriptGetFontFeatureTags(hdc, &sc, NULL, ScriptTags[ScriptTag], LangTags[LangTag], UNISCRIBE_MAX_TAGS, FeatureTags, &cFeatureTags) == 0) { for (int FeatureTag = 0; FeatureTag < cFeatureTags; FeatureTag++) { // Iterate through all glyphs in the font. for (DWORD i = 0; i < lpGlyphset->cRanges; i++) { for (LONG j = lpGlyphset->ranges[i].wcLow; j < lpGlyphset->ranges[i].wcLow + lpGlyphset->ranges[i].cGlyphs; j++) { // Test usp10!ScriptGetFontAlternateGlyphs. if (ScriptGetFontAlternateGlyphs(hdc, &sc, NULL, ScriptTags[ScriptTag], LangTags[LangTag], FeatureTags[FeatureTag], (WORD)j, MAX_ALTERNATE_GLYPHS, AlternateGlyphs, &cAlternates) == 0) { for (int alt_glyph_id = 1; alt_glyph_id < cAlternates; alt_glyph_id++) { WORD wOutGlyphId; // Test usp10!ScriptSubstituteSingleGlyph. ScriptSubstituteSingleGlyph(hdc, &sc, NULL, ScriptTags[ScriptTag], LangTags[LangTag], FeatureTags[FeatureTag], alt_glyph_id, (WORD)j, &wOutGlyphId); } } } } } } } } } } ScriptFreeCache(&sc); HeapFree(GetProcessHeap(), 0, wOutGlyphs); HeapFree(GetProcessHeap(), 0, ScriptTags); HeapFree(GetProcessHeap(), 0, LangTags); HeapFree(GetProcessHeap(), 0, FeatureTags); HeapFree(GetProcessHeap(), 0, AlternateGlyphs); } int wmain(int argc, wchar_t *argv[], wchar_t *envp[]) { if (argc != 2) { wprintf(L"Usage: %s \n", argv[0]); return EXIT_FAILURE; } LPCWSTR szFontPath = argv[1]; // Get screen coordinates. RECT screen_rect = { 0 }; screen_rect.left = 0; screen_rect.top = 0; screen_rect.right = GetSystemMetrics(SM_CXFULLSCREEN); screen_rect.bottom = GetSystemMetrics(SM_CYFULLSCREEN); // Reset the PRNG state. srand(0); do { // Get the logfont structures. LPLOGFONTW lpLogfonts = NULL; DWORD dwFonts = 0; if (GetLogfonts(szFontPath, &lpLogfonts, &dwFonts)) { wprintf(L"[+] Extracted %u logfonts.\n", dwFonts); } else { break; } // Load the font in the system from memory. int cFonts = AddFontResourceW(szFontPath); if (cFonts > 0) { wprintf(L"[+] Installed %d fonts.\n", cFonts); } else { wprintf(L"[-] AddFontResourceW() failed.\n"); break; } HDC hDC = GetDC(NULL); SetGraphicsMode(hDC, GM_ADVANCED); SetMapMode(hDC, MM_TEXT); // Display all fonts from the input file. BOOL success = TRUE; for (DWORD font_it = 0; success && font_it < dwFonts; font_it++) { // Display the font in several different point sizes. CONST LONG point_sizes[] = { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 }; for (unsigned int variation_it = 0; success && variation_it < ARRAY_SIZE(point_sizes) + 1; variation_it++) { wprintf(L"[+] Starting to test font %u / %d, variation %u / %d\n", font_it + 1, dwFonts, variation_it + 1, ARRAY_SIZE(point_sizes) + 1); HFONT hFont = NULL; if (variation_it == 0) { hFont = CreateFontIndirectW(&lpLogfonts[font_it]); } else { LOGFONTW lf; RtlCopyMemory(&lf, &lpLogfonts[font_it], sizeof(LOGFONTW)); lf.lfHeight = -MulDiv(point_sizes[variation_it - 1], GetDeviceCaps(hDC, LOGPIXELSY), 72); lf.lfWeight = 0; lf.lfItalic = (rand() & 1); lf.lfUnderline = (rand() & 1); lf.lfStrikeOut = (rand() & 1); lf.lfQuality = (rand() % 6); hFont = CreateFontIndirectW(&lf); } if (hFont == NULL) { wprintf(L"[!] CreateFontIndirectW() failed.\n"); success = FALSE; DeleteFont(hFont); break; } // Select the font for the Device Context. SelectFont(hDC, hFont); #if HARNESS_TEST_KERNING_PAIRS wprintf(L"[+] Getting kerning pairs.\n"); // Get the font's kerning pairs. DWORD nNumPairs = GetKerningPairs(hDC, 0, NULL); if (nNumPairs != 0) { LPKERNINGPAIR lpkrnpairs = (LPKERNINGPAIR)HeapAlloc(GetProcessHeap(), 0, nNumPairs * sizeof(KERNINGPAIR)); if (GetKerningPairs(hDC, nNumPairs, lpkrnpairs) == 0) { wprintf(L"[!] GetKerningPairs() failed.\n"); } HeapFree(GetProcessHeap(), 0, lpkrnpairs); } #endif // HARNESS_TEST_KERNING_PAIRS wprintf(L"[+] Getting unicode ranges.\n"); // Get Unicode ranges available in the font. DWORD dwGlyphsetSize = GetFontUnicodeRanges(hDC, NULL); if (dwGlyphsetSize == 0) { wprintf(L"[!] GetFontUnicodeRanges() failed.\n"); success = FALSE; DeleteFont(hFont); break; } LPGLYPHSET lpGlyphset = (LPGLYPHSET)HeapAlloc(GetProcessHeap(), 0, dwGlyphsetSize); if (GetFontUnicodeRanges(hDC, lpGlyphset) == 0) { wprintf(L"[!] GetFontUnicodeRanges() failed.\n"); success = FALSE; HeapFree(GetProcessHeap(), 0, lpGlyphset); DeleteFont(hFont); break; } #if HARNESS_TEST_DRAWTEXT WCHAR szTextBuffer[DISPLAYED_GLYPHS_COUNT + 1]; DWORD dwTextCount = 0; #endif // HARNESS_TEST_DRAWTEXT wprintf(L"[+] Getting glyph outlines and drawing them on screen.\n"); for (DWORD i = 0; i < lpGlyphset->cRanges; i++) { for (LONG j = lpGlyphset->ranges[i].wcLow; j < lpGlyphset->ranges[i].wcLow + lpGlyphset->ranges[i].cGlyphs; j++) { #if HARNESS_TEST_GLYPH_OUTLINE // Get the glyph outline in all available formats. CONST UINT glyph_formats[] = { GGO_BEZIER, GGO_BITMAP, GGO_GRAY2_BITMAP, GGO_GRAY4_BITMAP, GGO_GRAY8_BITMAP, GGO_NATIVE }; for (UINT format : glyph_formats) { GLYPHMETRICS gm; MAT2 mat2 = { 0, 1, 0, 0, 0, 0, 0, 1 }; DWORD cbBuffer = GetGlyphOutline(hDC, j, format, &gm, 0, NULL, &mat2); if (cbBuffer != GDI_ERROR) { LPVOID lpvBuffer = HeapAlloc(GetProcessHeap(), 0, cbBuffer); if (GetGlyphOutline(hDC, j, format, &gm, cbBuffer, lpvBuffer, &mat2) == GDI_ERROR) { wprintf(L"[!] GetGlyphOutline() failed for glyph %u.\n", j); } HeapFree(GetProcessHeap(), 0, lpvBuffer); } } #endif // HARNESS_TEST_GLYPH_OUTLINE #if HARNESS_TEST_DRAWTEXT // Insert the glyph into current string to be displayed. szTextBuffer[dwTextCount++] = (WCHAR)j; if (dwTextCount >= DISPLAYED_GLYPHS_COUNT) { szTextBuffer[DISPLAYED_GLYPHS_COUNT] = L'\0'; DrawTextW(hDC, szTextBuffer, -1, &screen_rect, DT_WORDBREAK | DT_NOCLIP); dwTextCount = 0; } #endif // HARNESS_TEST_DRAWTEXT } } #if HARNESS_TEST_DRAWTEXT if (dwTextCount > 0) { szTextBuffer[dwTextCount] = L'\0'; DrawTextW(hDC, szTextBuffer, -1, &screen_rect, DT_WORDBREAK | DT_NOCLIP); } #endif // HARNESS_TEST_DRAWTEXT #if HARNESS_TEST_UNISCRIBE wprintf(L"[+] Testing the Uniscribe user-mode library.\n"); // Test some user-mode Windows Uniscribe functionality. TestUniscribe(hDC, lpGlyphset); #endif // HARNESS_TEST_UNISCRIBE HeapFree(GetProcessHeap(), 0, lpGlyphset); DeleteFont(hFont); } } // Remove the font from the system. ReleaseDC(NULL, hDC); RemoveFontResourceW(szFontPath); } while (0); return 0; }