├── 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 ├── images ├── font2pdf.png ├── generator.png ├── loader-dwrite.png ├── loader-fontsub.png ├── loader.png └── mutator.png ├── 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 /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | 25 | ## Community Guidelines 26 | 27 | This project follows [Google's Open Source Community 28 | Guidelines](https://opensource.google.com/conduct/). -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BrokenType 2 | 3 | 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: 4 | 5 | - **[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). 6 | - **[TTF/OTF mutator](ttf-otf-mutator)** - a semi-"smart" binary font file mutator written in C++. 7 | - **[TTF/OTF loader for Windows GDI](ttf-otf-windows-loader)** - a utility for loading and testing custom fonts with Windows GDI / Uniscribe. 8 | - **[TTF/OTF loader for DirectWrite](ttf-otf-dwrite-loader)** - a utility for loading and testing custom fonts with the Microsoft DirectWrite API. 9 | - **[TTF loader for Windows FontSub.dll](ttf-fontsub-loader)** - a utility for loading and testing custom fonts with the Font Subsetting library. 10 | - **[Linux PE loader for FontSub.dll](fontsub-dll-on-linux)** - a minimal PE loader for Linux, tailored for fuzzing the Windows subsetter. 11 | - **[PDF font embedders](font2pdf)** - Python scripts for generating PDF documents which embed the specified fonts and display all of their glyphs. 12 | 13 | The description and usage instructions of the utilities can be found in their corresponding READMEs. 14 | 15 | 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: 16 | 17 | - [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) 18 | - [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) 19 | - [Notes on Windows Uniscribe Fuzzing](https://googleprojectzero.blogspot.com/2017/04/notes-on-windows-uniscribe-fuzzing.html) (April 2017) 20 | 21 | 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. 22 | 23 | ## Disclaimer 24 | 25 | This is not an official Google product. 26 | -------------------------------------------------------------------------------- /font2pdf/README.md: -------------------------------------------------------------------------------- 1 | # PDF font embedder 2 | 3 | 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. 4 | 5 | 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. 6 | 7 | ## Usage 8 | 9 | Just pass the path of the font as the first argument and path of the output PDF as the second: 10 | 11 | ``` 12 | c:\font2pdf>python ttfotf_to_pdf.py C:\Windows\Fonts\times.ttf test.pdf 13 | Glyphs in font: 4635 14 | Generated pages: 2 15 | 16 | c:\font2pdf> 17 | ``` 18 | 19 | and enjoy the generated document: 20 | 21 | ![PDF with a custom font embedded](../images/font2pdf.png) 22 | 23 | -------------------------------------------------------------------------------- /font2pdf/ttfotf_to_pdf.py: -------------------------------------------------------------------------------- 1 | # Author: Mateusz Jurczyk (mjurczyk@google.com) 2 | # 3 | # Copyright 2019 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import math 18 | import os 19 | import struct 20 | import sys 21 | from fontTools.ttLib import TTFont 22 | 23 | # Configuration constants. 24 | GLYPHS_PER_SEGMENT = 50 25 | SEGMENTS_PER_PAGE = 75 26 | GLYPHS_PER_PAGE = SEGMENTS_PER_PAGE * GLYPHS_PER_SEGMENT 27 | PAGE_OBJECT_IDX_START = 100 28 | PAGE_CONTENTS_IDX_START = 200 29 | 30 | def main(argv): 31 | if len(argv) != 3: 32 | print "Usage: %s " % argv[0] 33 | sys.exit(1) 34 | 35 | # Obtain the number of glyphs in the font, and calculate the number of necessary pages. 36 | font = TTFont(argv[1]) 37 | glyph_count = len(font.getGlyphSet()) 38 | print "Glyphs in font: %d" % glyph_count 39 | 40 | page_count = int(math.ceil(float(glyph_count) / GLYPHS_PER_PAGE)) 41 | print "Generated pages: %d" % page_count 42 | 43 | # Craft the PDF header. 44 | pdf_data = "%PDF-1.1\n" 45 | pdf_data += "\n" 46 | pdf_data += "1 0 obj\n" 47 | pdf_data += "<< /Pages 2 0 R >>\n" 48 | pdf_data += "endobj\n" 49 | pdf_data += "\n" 50 | pdf_data += "2 0 obj\n" 51 | pdf_data += "<<\n" 52 | pdf_data += " /Type /Pages\n" 53 | pdf_data += " /Count %d\n" % page_count 54 | pdf_data += " /Kids [ %s ]\n" % " ".join(map(lambda x:" %d 0 R" % (PAGE_OBJECT_IDX_START + x), (i for i in xrange(page_count)))) 55 | pdf_data += ">>\n" 56 | pdf_data += "endobj\n" 57 | pdf_data += "\n" 58 | 59 | # Construct the page descriptor objects. 60 | for i in xrange(page_count): 61 | pdf_data += "%d 0 obj\n" % (PAGE_OBJECT_IDX_START + i) 62 | pdf_data += "<<\n" 63 | pdf_data += " /Type /Page\n" 64 | pdf_data += " /MediaBox [0 0 612 792]\n" 65 | pdf_data += " /Contents %d 0 R\n" % (PAGE_CONTENTS_IDX_START + i) 66 | pdf_data += " /Parent 2 0 R\n" 67 | pdf_data += " /Resources <<\n" 68 | pdf_data += " /Font <<\n" 69 | pdf_data += " /CustomFont <<\n" 70 | pdf_data += " /Type /Font\n" 71 | pdf_data += " /Subtype /Type0\n" 72 | pdf_data += " /BaseFont /TestFont\n" 73 | pdf_data += " /Encoding /Identity-H\n" 74 | pdf_data += " /DescendantFonts [4 0 R]\n" 75 | pdf_data += " >>\n" 76 | pdf_data += " >>\n" 77 | pdf_data += " >>\n" 78 | pdf_data += ">>\n" 79 | pdf_data += "endobj\n" 80 | 81 | # Construct the font body object. 82 | with open(argv[1], "rb") as f: 83 | font_data = f.read() 84 | 85 | pdf_data += "3 0 obj\n" 86 | pdf_data += "<>stream\n" % len(font_data) 87 | pdf_data += font_data 88 | pdf_data += "\nendstream\n" 89 | pdf_data += "endobj\n" 90 | 91 | # Construct the page contents objects. 92 | for i in xrange(page_count): 93 | pdf_data += "%d 0 obj\n" % (PAGE_CONTENTS_IDX_START + i) 94 | pdf_data += "<< >>\n" 95 | pdf_data += "stream\n" 96 | pdf_data += "BT\n" 97 | 98 | for j in xrange(SEGMENTS_PER_PAGE): 99 | if i * GLYPHS_PER_PAGE + j * GLYPHS_PER_SEGMENT >= glyph_count: 100 | break 101 | 102 | pdf_data += " /CustomFont 12 Tf\n" 103 | if j == 0: 104 | pdf_data += " 10 775 Td\n" 105 | else: 106 | pdf_data += " 0 -10 Td\n" 107 | 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") 108 | 109 | pdf_data += "ET\n" 110 | pdf_data += "endstream\n" 111 | pdf_data += "endobj\n" 112 | 113 | # Construct the descendant font object. 114 | pdf_data += "4 0 obj\n" 115 | pdf_data += "<<\n" 116 | pdf_data += " /FontDescriptor <<\n" 117 | pdf_data += " /Type /FontDescriptor\n" 118 | pdf_data += " /FontName /TestFont\n" 119 | pdf_data += " /Flags 5\n" 120 | pdf_data += " /FontBBox[0 0 10 10]\n" 121 | pdf_data += " /FontFile3 3 0 R\n" 122 | pdf_data += " >>\n" 123 | pdf_data += " /Type /Font\n" 124 | pdf_data += " /Subtype /OpenType\n" 125 | pdf_data += " /BaseFont /TestFont\n" 126 | pdf_data += " /Widths [1000]\n" 127 | pdf_data += ">>\n" 128 | pdf_data += "endobj\n" 129 | 130 | # Construct the trailer. 131 | pdf_data += "trailer\n" 132 | pdf_data += "<<\n" 133 | pdf_data += " /Root 1 0 R\n" 134 | pdf_data += ">>\n" 135 | 136 | # Write the output to disk. 137 | with open(argv[2], "w+b") as f: 138 | f.write(pdf_data) 139 | 140 | if __name__ == "__main__": 141 | main(sys.argv) 142 | -------------------------------------------------------------------------------- /font2pdf/type1_to_pdf.py: -------------------------------------------------------------------------------- 1 | # Author: Mateusz Jurczyk (mjurczyk@google.com) 2 | # 3 | # Copyright 2019 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import math 18 | import os 19 | import struct 20 | import sys 21 | from fontTools.t1Lib import T1Font 22 | 23 | # Configuration constants. 24 | GLYPHS_PER_SEGMENT = 50 25 | 26 | def main(argv): 27 | if len(argv) != 3: 28 | print "Usage: %s <.pfb font file> " % argv[0] 29 | sys.exit(1) 30 | 31 | # Load the number of glyphs in the font. 32 | font = T1Font(argv[1]) 33 | glyph_count = len(font.getGlyphSet()) 34 | print "Glyphs in font: %d" % glyph_count 35 | 36 | # Craft the PDF header. 37 | pdf_data = "%PDF-1.1\n" 38 | pdf_data += "\n" 39 | pdf_data += "1 0 obj\n" 40 | pdf_data += "<< /Pages 2 0 R >>\n" 41 | pdf_data += "endobj\n" 42 | pdf_data += "\n" 43 | pdf_data += "2 0 obj\n" 44 | pdf_data += "<<\n" 45 | pdf_data += " /Type /Pages\n" 46 | pdf_data += " /Count 1\n" 47 | pdf_data += " /Kids [ 3 0 R ]\n" 48 | pdf_data += ">>\n" 49 | pdf_data += "endobj\n" 50 | pdf_data += "\n" 51 | pdf_data += "3 0 obj\n" 52 | pdf_data += "<<\n" 53 | pdf_data += " /Type /Page\n" 54 | pdf_data += " /MediaBox [0 0 612 792]\n" 55 | pdf_data += " /Contents 5 0 R\n" 56 | pdf_data += " /Parent 2 0 R\n" 57 | pdf_data += " /Resources <<\n" 58 | pdf_data += " /Font <<\n" 59 | pdf_data += " /CustomFont <<\n" 60 | pdf_data += " /Type /Font\n" 61 | pdf_data += " /Subtype /Type0\n" 62 | pdf_data += " /BaseFont /TestFont\n" 63 | pdf_data += " /Encoding /Identity-H\n" 64 | pdf_data += " /DescendantFonts [6 0 R]\n" 65 | pdf_data += " >>\n" 66 | pdf_data += " >>\n" 67 | pdf_data += " >>\n" 68 | pdf_data += ">>\n" 69 | pdf_data += "endobj\n" 70 | 71 | # Construct the font body object. 72 | with open(argv[1], "rb") as f: 73 | font_data = f.read() 74 | 75 | pdf_data += "4 0 obj\n" 76 | pdf_data += "<>stream\n" % len(font_data) 77 | pdf_data += font_data 78 | pdf_data += "\nendstream\n" 79 | pdf_data += "endobj\n" 80 | 81 | # Construct the page contents object. 82 | pdf_data += "5 0 obj\n" 83 | pdf_data += "<< >>\n" 84 | pdf_data += "stream\n" 85 | pdf_data += "BT\n" 86 | 87 | segment_count = int(math.ceil(float(glyph_count) / GLYPHS_PER_SEGMENT)) 88 | for i in xrange(segment_count): 89 | pdf_data += " /CustomFont 12 Tf\n" 90 | if i == 0: 91 | pdf_data += " 10 780 Td\n" 92 | else: 93 | pdf_data += " 0 -10 Td\n" 94 | 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") 95 | 96 | pdf_data += "ET\n" 97 | pdf_data += "endstream\n" 98 | pdf_data += "endobj\n" 99 | 100 | # Construct the descendant font object. 101 | pdf_data += "6 0 obj\n" 102 | pdf_data += "<<\n" 103 | pdf_data += " /FontDescriptor <<\n" 104 | pdf_data += " /Type /FontDescriptor\n" 105 | pdf_data += " /FontName /TestFont\n" 106 | pdf_data += " /Flags 5\n" 107 | pdf_data += " /FontBBox[0 0 10 10]\n" 108 | pdf_data += " /FontFile3 4 0 R\n" 109 | pdf_data += " >>\n" 110 | pdf_data += " /Type /Font\n" 111 | pdf_data += " /Subtype /Type1\n" 112 | pdf_data += " /BaseFont /TestFont\n" 113 | pdf_data += ">>\n" 114 | pdf_data += "endobj\n" 115 | 116 | # Construct the trailer. 117 | pdf_data += "trailer\n" 118 | pdf_data += "<<\n" 119 | pdf_data += " /Root 1 0 R\n" 120 | pdf_data += ">>\n" 121 | 122 | # Write the output to disk. 123 | with open(argv[2], "w+b") as f: 124 | f.write(pdf_data) 125 | 126 | if __name__ == "__main__": 127 | main(sys.argv) 128 | -------------------------------------------------------------------------------- /fontsub-dll-on-linux/Makefile: -------------------------------------------------------------------------------- 1 | CXX=g++ 2 | LD=g++ 3 | CXXFLAGS=-m32 -DDEBUG 4 | LDFLAGS=-lpe-parser-library -ldl -m32 5 | DEPS=fontsub.h 6 | SRCS=loader.cc 7 | OBJS=$(subst .cc,.o,$(SRCS)) 8 | 9 | all: loader 10 | 11 | %.o: %.cc $(DEPS) 12 | $(CXX) -c -o $@ $< $(CXXFLAGS) 13 | 14 | loader: $(OBJS) 15 | $(LD) -o $@ $< $(LDFLAGS) 16 | 17 | clean: 18 | $(RM) loader $(OBJS) 19 | -------------------------------------------------------------------------------- /fontsub-dll-on-linux/README.md: -------------------------------------------------------------------------------- 1 | # Linux DLL loader for the Windows Font Subsetting Library (FontSub.dll) 2 | 3 | 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. 4 | 5 | The benefit to this approach is that with the harness, all typical Linux fuzzing tools become available: 6 | - You can use [AFL](http://lcamtuf.coredump.cx/afl/), [honggfuzz](https://github.com/google/honggfuzz), or your favourite Linux-based fuzzer. 7 | - 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)). 8 | - You can use performance improvements such as a "Fork Server", which are easier to implement on Linux. 9 | - 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. 10 | 11 | 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). 12 | 13 | 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. 14 | 15 | ## Requirements 16 | 17 | 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. 18 | 19 | ## Building 20 | 21 | With `pe-parse` installed in the system, simply type `make` in the project directory: 22 | 23 | ``` 24 | $ make 25 | g++ -c -o loader.o loader.cc -m32 -DDEBUG 26 | g++ -o loader loader.o -lpe-parser-library -ldl -m32 27 | $ 28 | ``` 29 | 30 | ## Usage 31 | 32 | 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.: 33 | 34 | ``` 35 | .text:0000000180001345 loc_180001345: ; CODE XREF: CreateFontPackage+C1 36 | .text:0000000180001345 ; CreateFontPackage+161 37 | .text:0000000180001345 movzx ebx, r10w 38 | .text:0000000180001349 mov ecx, ebx ; unsigned int 39 | .text:000000018000134B call LogCreateFontPackage 40 | .text:0000000180001350 mov eax, ebx 41 | ``` 42 | 43 | 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. 44 | 45 | Then, just pass the path of the library, path of the input TrueType, and optionally the desired image base: 46 | 47 | ``` 48 | $ ./loader fontsub.dll times.ttf 0x20000000 49 | [+] Provided fontsub.dll file successfully parsed. 50 | [*] Attempting to map section .text base 20001000, size 88576 51 | [+] SUCCESS! 52 | [*] Attempting to map section .data base 20017000, size 1536 53 | [+] SUCCESS! 54 | [*] Attempting to map section .idata base 20018000, size 2048 55 | [+] SUCCESS! 56 | [*] Attempting to map section .rsrc base 20019000, size 1024 57 | [+] SUCCESS! 58 | [*] Attempting to map section .reloc base 2001a000, size 2560 59 | [+] SUCCESS! 60 | [...] 61 | [*] Resolving the MSVCRT.DLL!memmove import. 62 | [+] Function memmove found in libc at address 0xf7b53690. 63 | [*] Resolving the MSVCRT.DLL!memcmp import. 64 | [+] Function memcmp found in libc at address 0xf7b688e0. 65 | [*] Resolving the MSVCRT.DLL!memcpy import. 66 | [+] Function memcpy found in libc at address 0xf7b53050. 67 | [*] Resolving the MSVCRT.DLL!memset import. 68 | [+] Function memset found in libc at address 0xf7b51e20. 69 | [...] 70 | [*] Setting desired access rights for section .text 71 | [+] SUCCESS! 72 | [*] Setting desired access rights for section .data 73 | [+] SUCCESS! 74 | [*] Setting desired access rights for section .idata 75 | [+] SUCCESS! 76 | [*] Setting desired access rights for section .rsrc 77 | [+] SUCCESS! 78 | [*] Setting desired access rights for section .reloc 79 | [+] SUCCESS! 80 | [+] Located CreateFontPackage at 0x20002500, MergeFontPackage at 0x20002630. 81 | [A] malloc(0x168) ---> 0x573eebb0 82 | [A] malloc(0x121b) ---> 0x573f3780 83 | [A] malloc(0x14) ---> 0x573ed7d0 84 | [A] malloc(0x490) ---> 0x573eed20 85 | [A] malloc(0xfae) ---> 0x573f49a0 86 | [A] free(0x573eed20) 87 | [A] free(0x573f49a0) 88 | [...] 89 | [A] free(0xf79a1010) 90 | [+] CreateFontPackage([ 1162804 bytes ], TTFCFP_SUBSET) ---> 0 (344996 bytes) 91 | [A] realloc((nil), 0x543a4) ---> 0x573f7a20 92 | [A] free(0x573f7a20) 93 | [+] MergeFontPackage(NULL, [ 344996 bytes ], TTFMFP_SUBSET) ---> 0 (344996 bytes) 94 | $ 95 | ``` 96 | 97 | There are a lot of debug messages in the above output. If you build the tool without `-DDEBUG`, the output is much cleaner: 98 | 99 | ``` 100 | $ ./loader fontsub.dll times.ttf 0x20000000 101 | [+] CreateFontPackage([ 1162804 bytes ], TTFCFP_SUBSET) ---> 0 (344996 bytes) 102 | [+] MergeFontPackage(NULL, [ 344996 bytes ], TTFMFP_SUBSET) ---> 0 (344996 bytes) 103 | $ 104 | ``` 105 | 106 | ## Limitations 107 | 108 | The harness currently only supports x86 `FontSub.dll` modules, but adding support for x64 images should be relatively straightforward. 109 | 110 | 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: 111 | - `sizeof(long)` is 4 on 64-bit Windows, but 8 on 64-bit Linux. 112 | - 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. 113 | - 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. 114 | - 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. 115 | 116 | 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. 117 | 118 | -------------------------------------------------------------------------------- /fontsub-dll-on-linux/fontsub.h: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2019 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #ifndef FONTSUB_H_ 21 | #define FONTSUB_H_ 22 | 23 | // Standard FontSub constants from the Windows SDK (fontsub.h). 24 | 25 | /* for usSubsetFormat */ 26 | #define TTFCFP_SUBSET 0 /* Straight Subset Font - Backward compatibility */ 27 | #define TTFCFP_SUBSET1 1 /* Subset font with full TTO and Kern tables. For later merge */ 28 | #define TTFCFP_DELTA 2 /* Delta font, for merge with a subset1 font */ 29 | 30 | /* for usSubsetPlatform */ 31 | #define TTFCFP_UNICODE_PLATFORMID 0 32 | #define TTFCFP_APPLE_PLATFORMID 1 33 | #define TTFCFP_ISO_PLATFORMID 2 34 | #define TTFCFP_MS_PLATFORMID 3 35 | 36 | /* for usSubsetEncoding */ 37 | #define TTFCFP_STD_MAC_CHAR_SET 0 /* goes with TTFSUB_APPLE_PLATFORMID */ 38 | #define TTFCFP_SYMBOL_CHAR_SET 0 /* goes with TTFSUB_MS_PLATFORMID */ 39 | #define TTFCFP_UNICODE_CHAR_SET 1 /* goes with TTFSUB_MS_PLATFORMID */ 40 | #define TTFCFP_DONT_CARE 0xFFFF 41 | 42 | /* for usSubsetLanguage */ 43 | #define TTFCFP_LANG_KEEP_ALL 0 44 | 45 | /* for usFlags */ 46 | #define TTFCFP_FLAGS_SUBSET 0x0001 /* if bit off, don't subset */ 47 | #define TTFCFP_FLAGS_COMPRESS 0x0002 /* if bit off, don't compress */ 48 | #define TTFCFP_FLAGS_TTC 0x0004 /* if bit off, its a TTF */ 49 | #define TTFCFP_FLAGS_GLYPHLIST 0x0008 /* if bit off, list is characters */ 50 | 51 | /* for usModes */ 52 | #define TTFMFP_SUBSET 0 /* copy a Straight Subset Font package to Dest buffer */ 53 | #define TTFMFP_SUBSET1 1 /* Expand a format 1 font into a format 3 font */ 54 | #define TTFMFP_DELTA 2 /* Merge a format 2 with a format 3 font */ 55 | 56 | #endif // FONTSUB_H_ 57 | 58 | -------------------------------------------------------------------------------- /fontsub-dll-on-linux/loader.cc: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2019 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #if __x86_64__ 21 | #error The loader only works in x86 mode. 22 | #endif // __x86_64__ 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include 34 | #include 35 | 36 | #include 37 | 38 | #include "fontsub.h" 39 | 40 | using namespace std; 41 | using namespace peparse; 42 | 43 | #ifdef DEBUG 44 | # define LOG(x) printf x; 45 | #else 46 | # define LOG(x) 47 | #endif // DEBUG 48 | 49 | namespace globals { 50 | 51 | // See https://docs.microsoft.com/en-us/windows/win32/api/fontsub/ for the 52 | // documentation of the prototypes below. 53 | 54 | typedef void *(*CFP_ALLOCPROC)(size_t Arg1); 55 | typedef void *(*CFP_REALLOCPROC)(void *, size_t Arg1); 56 | typedef void (*CFP_FREEPROC)(void *); 57 | 58 | typedef unsigned long (*fnCreateFontPackage)( 59 | const unsigned char *puchSrcBuffer, 60 | const unsigned long ulSrcBufferSize, 61 | unsigned char **ppuchFontPackageBuffer, 62 | unsigned long *pulFontPackageBufferSize, 63 | unsigned long *pulBytesWritten, 64 | const unsigned short usFlag, 65 | const unsigned short usTTCIndex, 66 | const unsigned short usSubsetFormat, 67 | const unsigned short usSubsetLanguage, 68 | const unsigned short usSubsetPlatform, 69 | const unsigned short usSubsetEncoding, 70 | const unsigned short *pusSubsetKeepList, 71 | const unsigned short usSubsetListCount, 72 | CFP_ALLOCPROC lpfnAllocate, 73 | CFP_REALLOCPROC lpfnReAllocate, 74 | CFP_FREEPROC lpfnFree, 75 | void *lpvReserved 76 | ); 77 | 78 | typedef unsigned long (*fnMergeFontPackage)( 79 | const unsigned char *puchMergeFontBuffer, 80 | const unsigned long ulMergeFontBufferSize, 81 | const unsigned char *puchFontPackageBuffer, 82 | const unsigned long ulFontPackageBufferSize, 83 | unsigned char **ppuchDestBuffer, 84 | unsigned long *pulDestBufferSize, 85 | unsigned long *pulBytesWritten, 86 | const unsigned short usMode, 87 | CFP_ALLOCPROC lpfnAllocate, 88 | CFP_REALLOCPROC lpfnReAllocate, 89 | CFP_FREEPROC lpfnFree, 90 | void *lpvReserved 91 | ); 92 | 93 | fnCreateFontPackage pfnCreateFontPackage; 94 | fnMergeFontPackage pfnMergeFontPackage; 95 | 96 | // The delta between the actual address the library is being loaded to in 97 | // relation to the suggested ImageBase from the PE headers. 98 | size_t base_address_delta; 99 | 100 | } // namespace globals 101 | 102 | static void *CfpAllocproc(size_t Arg1) { 103 | void *ret = malloc(Arg1); 104 | LOG(("[A] malloc(0x%zx) ---> %p\n", Arg1, ret)); 105 | return ret; 106 | } 107 | 108 | static void CfpFreeproc(void *Arg1) { 109 | LOG(("[A] free(%p)\n", Arg1)); 110 | return free(Arg1); 111 | } 112 | 113 | static void *CfpReallocproc(void *Arg1, size_t Arg2) { 114 | void *ret = realloc(Arg1, Arg2); 115 | LOG(("[A] realloc(%p, 0x%zx) ---> %p\n", Arg1, Arg2, ret)); 116 | return ret; 117 | } 118 | 119 | static int MapSection(void *cbd, VA base_address, string& name, 120 | image_section_header hdr, bounded_buffer *data) { 121 | 122 | // Apply potential relocation. 123 | base_address += globals::base_address_delta; 124 | 125 | LOG(("[*] Attempting to map section %-8s base %zx, size %u\n", 126 | name.c_str(), (size_t)base_address, data->bufLen)); 127 | 128 | // Create an initial writable mapping in memory to copy the section's data. 129 | void *mmapped_addr = mmap((void *)base_address, 130 | hdr.Misc.VirtualSize, 131 | PROT_WRITE, 132 | MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, 133 | -1, 134 | 0); 135 | 136 | if (mmapped_addr == (void *)-1) { 137 | perror("MapSection"); 138 | exit(1); 139 | } 140 | 141 | // Copy the physical data there. 142 | memcpy(mmapped_addr, data->buf, data->bufLen); 143 | 144 | LOG(("[+] SUCCESS!\n")); 145 | return 0; 146 | } 147 | 148 | static int SetSectionAccessRights(void *cbd, VA base_address, string& name, 149 | image_section_header hdr, bounded_buffer *data) { 150 | 151 | // Apply potential relocation. 152 | base_address += globals::base_address_delta; 153 | 154 | LOG(("[*] Setting desired access rights for section %-8s\n", name.c_str())); 155 | 156 | // Change the protection to the desired one specified in the section's header. 157 | int prot = PROT_READ; 158 | if ((hdr.Characteristics & IMAGE_SCN_CNT_CODE) || 159 | (hdr.Characteristics & IMAGE_SCN_MEM_EXECUTE)) { 160 | prot |= PROT_EXEC; 161 | } 162 | if (hdr.Characteristics & IMAGE_SCN_MEM_WRITE) { 163 | prot |= PROT_WRITE; 164 | } 165 | 166 | if (mprotect((void *)base_address, hdr.Misc.VirtualSize, prot) == -1) { 167 | perror("SetSectionAccessRights"); 168 | exit(1); 169 | } 170 | 171 | LOG(("[+] SUCCESS!\n")); 172 | return 0; 173 | } 174 | 175 | static int ResolveImport(void *cbd, VA import_addr, const string& module_name, 176 | const string& function_name) { 177 | 178 | // Apply potential relocation. 179 | import_addr += globals::base_address_delta; 180 | 181 | LOG(("[*] Resolving the %s!%s import.\n", module_name.c_str(), function_name.c_str())); 182 | 183 | // Initialize the libc handle, if it hasn't been yet. 184 | static void *libc_handle = NULL; 185 | if (libc_handle == NULL) { 186 | libc_handle = dlopen("libc.so.6", RTLD_NOW); 187 | if (libc_handle == NULL) { 188 | fprintf(stderr, "ResolveImport: Unable to get the base address of libc.\n"); 189 | exit(1); 190 | } 191 | 192 | LOG(("[+] Resolved the libc handle at %p\n", libc_handle)); 193 | } 194 | 195 | // Insert hooks for target-specific functions. 196 | void *hook_func_addr = NULL; 197 | if (function_name == "malloc") { 198 | hook_func_addr = (void *)CfpAllocproc; 199 | } else if (function_name == "realloc") { 200 | hook_func_addr = (void *)CfpReallocproc; 201 | } else if (function_name == "free") { 202 | hook_func_addr = (void *)CfpFreeproc; 203 | } 204 | 205 | if (hook_func_addr != NULL) { 206 | memcpy((void *)import_addr, &hook_func_addr, sizeof(void *)); 207 | return 0; 208 | } 209 | 210 | // In the rest of the function, we only do MSVCRT => libc remapping 211 | // Other imports are filled with a 0xcccccccc marker. 212 | if (module_name != "MSVCRT.DLL") { 213 | memcpy((void *)import_addr, "\xcc\xcc\xcc\xcc", 4); 214 | return 0; 215 | } 216 | 217 | // Locate the corresponding libc function and insert its address in IAT. 218 | void *func_addr = dlsym(libc_handle, function_name.c_str()); 219 | if (func_addr != NULL) { 220 | LOG(("[+] Function %s found in libc at address %p.\n", function_name.c_str(), func_addr)); 221 | 222 | memcpy((void *)import_addr, &func_addr, sizeof(void *)); 223 | } else { 224 | LOG(("[!] Function %s NOT FOUND in libc.\n", function_name.c_str())); 225 | 226 | // The marker for unmatched MSVCRT functions is 0xdddddddd. 227 | memcpy((void *)import_addr, "\xdd\xdd\xdd\xdd", 4); 228 | } 229 | 230 | return 0; 231 | } 232 | 233 | int ResolveRelocation(void *cbd, VA reloc_addr, reloc_type type) { 234 | if (type == HIGHLOW) { 235 | uint32_t *address = (uint32_t *)(reloc_addr + globals::base_address_delta); 236 | *address += globals::base_address_delta; 237 | } else if (type != ABSOLUTE) { 238 | fprintf(stderr, "[-] Unknown relocation type: %d\n", type); 239 | } 240 | 241 | return 0; 242 | } 243 | 244 | static int FindExports(void *cbd, VA func_addr, string& module_name, string& function_name) { 245 | if (function_name == "CreateFontPackage") { 246 | globals::pfnCreateFontPackage = 247 | (globals::fnCreateFontPackage)(func_addr + globals::base_address_delta); 248 | } else if (function_name == "MergeFontPackage") { 249 | globals::pfnMergeFontPackage = 250 | (globals::fnMergeFontPackage)(func_addr + globals::base_address_delta); 251 | } 252 | 253 | return 0; 254 | } 255 | 256 | static bool ReadFileToString(const char *filename, std::string *buffer) { 257 | FILE *f = fopen(filename, "rb"); 258 | if (f == NULL) { 259 | return false; 260 | } 261 | 262 | fseek(f, 0, SEEK_END); 263 | const long size = ftell(f); 264 | fseek(f, 0, SEEK_SET); 265 | 266 | bool success = true; 267 | 268 | char *tmp = (char *)malloc(size); 269 | if (tmp == NULL) { 270 | success = false; 271 | } 272 | 273 | if (success) { 274 | success = (fread(tmp, 1, size, f) == size); 275 | } 276 | 277 | if (success) { 278 | buffer->assign(tmp, size); 279 | } 280 | 281 | free(tmp); 282 | fclose(f); 283 | 284 | return success; 285 | } 286 | 287 | static void GenerateKeepList(std::mt19937& rand_gen, 288 | std::vector *keep_list) { 289 | int list_length; 290 | 291 | switch (rand_gen() & 3) { 292 | case 0: list_length = 1 + (rand_gen() % 10); break; 293 | case 1: list_length = 1 + (rand_gen() % 100); break; 294 | case 2: list_length = 1 + (rand_gen() % 1000); break; 295 | case 3: list_length = 1 + (rand_gen() % 10000); break; 296 | } 297 | 298 | for (int i = 0; i < list_length; i++) { 299 | keep_list->push_back(rand_gen() % (list_length * 2)); 300 | } 301 | } 302 | 303 | unsigned long FontSubCreate(const std::string& input_data, std::mt19937& rand_gen, 304 | unsigned short usFormat, std::string *output_data) { 305 | unsigned char *puchFontPackageBuffer = NULL; 306 | unsigned long ulFontPackageBufferSize = 0; 307 | unsigned long ulBytesWritten = 0; 308 | 309 | unsigned short usFlag = TTFCFP_FLAGS_SUBSET; 310 | unsigned short usTTCIndex = 0; 311 | 312 | if (rand_gen() & 1) { usFlag |= TTFCFP_FLAGS_COMPRESS; } 313 | if (rand_gen() & 1) { usFlag |= TTFCFP_FLAGS_GLYPHLIST; } 314 | if (input_data.size() >= 12 && !memcmp(input_data.data(), "ttcf", 4)) { 315 | int font_count = input_data.data()[11]; 316 | if (font_count == 0) { 317 | font_count = 1; 318 | } 319 | 320 | usFlag |= TTFCFP_FLAGS_TTC; 321 | usTTCIndex = rand_gen() % font_count; 322 | } 323 | 324 | unsigned short usSubsetPlatform = TTFCFP_UNICODE_PLATFORMID; 325 | unsigned short usSubsetEncoding = TTFCFP_DONT_CARE; 326 | 327 | switch (rand_gen() & 3) { 328 | case 0: 329 | usSubsetPlatform = TTFCFP_UNICODE_PLATFORMID; 330 | break; 331 | case 1: 332 | usSubsetPlatform = TTFCFP_APPLE_PLATFORMID; 333 | if (rand_gen() & 1) { 334 | usSubsetEncoding = TTFCFP_STD_MAC_CHAR_SET; 335 | } 336 | break; 337 | case 2: 338 | usSubsetPlatform = TTFCFP_ISO_PLATFORMID; 339 | break; 340 | case 3: 341 | usSubsetPlatform = TTFCFP_MS_PLATFORMID; 342 | switch (rand_gen() & 3) { 343 | case 0: 344 | usSubsetEncoding = TTFCFP_SYMBOL_CHAR_SET; 345 | break; 346 | case 1: 347 | usSubsetEncoding = TTFCFP_UNICODE_CHAR_SET; 348 | break; 349 | } 350 | break; 351 | } 352 | 353 | std::vector keep_list; 354 | GenerateKeepList(rand_gen, &keep_list); 355 | 356 | unsigned long ret = globals::pfnCreateFontPackage( 357 | (unsigned char *)input_data.data(), 358 | input_data.size(), 359 | &puchFontPackageBuffer, 360 | &ulFontPackageBufferSize, 361 | &ulBytesWritten, 362 | usFlag, 363 | usTTCIndex, 364 | usFormat, 365 | /*usSubsetLanguage=*/0, 366 | usSubsetPlatform, 367 | usSubsetEncoding, 368 | &keep_list[0], 369 | keep_list.size(), 370 | CfpAllocproc, 371 | CfpReallocproc, 372 | CfpFreeproc, 373 | NULL); 374 | 375 | if (ret == 0) { 376 | output_data->assign((char *)puchFontPackageBuffer, ulBytesWritten); 377 | CfpFreeproc(puchFontPackageBuffer); 378 | } else { 379 | output_data->clear(); 380 | } 381 | 382 | return ret; 383 | } 384 | 385 | unsigned long FontSubMerge1(const std::string& input_data, unsigned short usMode, 386 | std::string *output_data) { 387 | unsigned char *puchDestBuffer = NULL; 388 | unsigned long ulDestBufferSize = 0; 389 | unsigned long ulBytesWritten = 0; 390 | 391 | unsigned long ret = globals::pfnMergeFontPackage( 392 | /*puchMergeFontBuffer=*/NULL, 393 | /*ulMergeFontBufferSize=*/0, 394 | /*puchFontPackageBuffer=*/(const unsigned char *)input_data.data(), 395 | /*ulFontPackageBufferSize=*/input_data.size(), 396 | /*ppuchDestBuffer=*/&puchDestBuffer, 397 | /*pulDestBufferSize=*/&ulDestBufferSize, 398 | /*pulBytesWritten=*/&ulBytesWritten, 399 | usMode, 400 | CfpAllocproc, 401 | CfpReallocproc, 402 | CfpFreeproc, 403 | NULL); 404 | 405 | if (ret == 0) { 406 | output_data->assign((char *)puchDestBuffer, ulBytesWritten); 407 | CfpFreeproc(puchDestBuffer); 408 | } else { 409 | output_data->clear(); 410 | } 411 | 412 | return ret; 413 | } 414 | 415 | unsigned long FontSubMerge2(const std::string& merge_font, const std::string& font_package, 416 | std::string *output_data) { 417 | unsigned char *puchDestBuffer = NULL; 418 | unsigned long ulDestBufferSize = 0; 419 | unsigned long ulBytesWritten = 0; 420 | 421 | unsigned long ret = globals::pfnMergeFontPackage( 422 | /*puchMergeFontBuffer=*/(const unsigned char *)merge_font.data(), 423 | /*ulMergeFontBufferSize=*/merge_font.size(), 424 | /*puchFontPackageBuffer=*/(const unsigned char *)font_package.data(), 425 | /*ulFontPackageBufferSize=*/font_package.size(), 426 | /*ppuchDestBuffer=*/&puchDestBuffer, 427 | /*pulDestBufferSize=*/&ulDestBufferSize, 428 | /*pulBytesWritten=*/&ulBytesWritten, 429 | /*usMode=*/TTFMFP_DELTA, 430 | CfpAllocproc, 431 | CfpReallocproc, 432 | CfpFreeproc, 433 | NULL); 434 | 435 | if (ret == 0) { 436 | output_data->assign((char *)puchDestBuffer, ulBytesWritten); 437 | CfpFreeproc(puchDestBuffer); 438 | } else { 439 | output_data->clear(); 440 | } 441 | 442 | return ret; 443 | } 444 | 445 | void ProcessSample(const std::string& input_data) { 446 | std::seed_seq seed(input_data.begin(), input_data.end()); 447 | std::mt19937 rand_gen(seed); 448 | 449 | if (rand_gen() & 1) /* TTFCFP_SUBSET case */ { 450 | std::string font_package; 451 | unsigned long ret = FontSubCreate(input_data, rand_gen, TTFCFP_SUBSET, &font_package); 452 | 453 | printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_SUBSET) ---> %lu (%zu bytes)\n", 454 | input_data.size(), ret, font_package.size()); 455 | 456 | if (ret == 0) { 457 | std::string working_font; 458 | ret = FontSubMerge1(font_package, TTFMFP_SUBSET, &working_font); 459 | 460 | printf("[+] MergeFontPackage(NULL, [ %zu bytes ], TTFMFP_SUBSET) ---> %lu (%zu bytes)\n", 461 | font_package.size(), ret, working_font.size()); 462 | } 463 | } else /* TTFCFP_SUBSET1 / TTFCFP_DELTA case */ { 464 | std::string font_package; 465 | unsigned long ret = FontSubCreate(input_data, rand_gen, TTFCFP_SUBSET1, &font_package); 466 | 467 | printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_SUBSET1) ---> %lu (%zu bytes)\n", 468 | input_data.size(), ret, font_package.size()); 469 | 470 | if (ret == 0) { 471 | std::string working_font; 472 | ret = FontSubMerge1(font_package, TTFMFP_SUBSET1, &working_font); 473 | 474 | printf("[+] MergeFontPackage(NULL, [ %zu bytes ], TTFMFP_SUBSET1) ---> %lu (%zu bytes)\n", 475 | font_package.size(), ret, working_font.size()); 476 | 477 | const int merges = 1 + (rand_gen() % 5); 478 | for (int i = 0; i < merges; i++) { 479 | ret = FontSubCreate(input_data, rand_gen, TTFCFP_DELTA, &font_package); 480 | printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_DELTA) ---> %lu (%zu bytes)\n", 481 | input_data.size(), ret, font_package.size()); 482 | 483 | if (ret == 0) { 484 | std::string new_working_font; 485 | ret = FontSubMerge2(working_font, font_package, &new_working_font); 486 | 487 | printf("[+] MergeFontPackage([ %zu bytes ], [ %zu bytes ], TTFMFP_DELTA) ---> %lu (%zu bytes)\n", 488 | working_font.size(), font_package.size(), ret, new_working_font.size()); 489 | 490 | if (ret == 0) { 491 | working_font = new_working_font; 492 | } 493 | } 494 | } 495 | } 496 | } 497 | } 498 | 499 | int main(int argc, char **argv) { 500 | if (argc < 3 || argc > 4) { 501 | printf("Usage: %s /path/to/fontsub.dll /path/to/font/file [image base]\n", argv[0]); 502 | return 1; 503 | } 504 | 505 | const char *fontsub_dll_path = argv[1]; 506 | const char *input_font_path = argv[2]; 507 | 508 | // Parse the input PE file. 509 | parsed_pe *pe = ParsePEFromFile(fontsub_dll_path); 510 | if (pe == NULL) { 511 | fprintf(stderr, "[-] Unable to parse the %s file.\n", fontsub_dll_path); 512 | return 1; 513 | } 514 | LOG(("[+] Provided fontsub.dll file successfully parsed.\n")); 515 | 516 | // In case of an alternate image base, save information about the address delta. 517 | if (argc == 4) { 518 | size_t new_base_address = 0; 519 | if (sscanf(argv[3], "%zx", &new_base_address) != 1 || 520 | (new_base_address & 0xffff) != 0) { 521 | fprintf(stderr, "[-] Invalid base address %s\n", argv[3]); 522 | DestructParsedPE(pe); 523 | return 1; 524 | } 525 | 526 | size_t orig_base_address = 0; 527 | if (pe->peHeader.nt.OptionalMagic == NT_OPTIONAL_32_MAGIC) { 528 | orig_base_address = pe->peHeader.nt.OptionalHeader.ImageBase; 529 | } else { 530 | orig_base_address = pe->peHeader.nt.OptionalHeader64.ImageBase; 531 | } 532 | 533 | globals::base_address_delta = new_base_address - orig_base_address; 534 | } 535 | 536 | // Map sections to their respective addresses. 537 | IterSec(pe, MapSection, NULL); 538 | 539 | // Resolve imports (redirect MSVCRT to libc, install stubs etc.) in IAT. 540 | IterImpVAString(pe, ResolveImport, NULL); 541 | 542 | // If we're loading the library to non-default image base, resolve relocations. 543 | if (globals::base_address_delta != 0) { 544 | IterRelocs(pe, ResolveRelocation, NULL); 545 | } 546 | 547 | // Set the final access rights for the section memory regions. 548 | IterSec(pe, SetSectionAccessRights, NULL); 549 | 550 | // Find the fontsub!CreateFontPackage and fontsub!MergeFontPackage exported functions. 551 | IterExpVA(pe, FindExports, NULL); 552 | 553 | if (globals::pfnCreateFontPackage == NULL || globals::pfnMergeFontPackage == NULL) { 554 | fprintf(stderr, "[-] Unable to locate the required exported functions.\n"); 555 | DestructParsedPE(pe); 556 | return 1; 557 | } 558 | LOG(("[+] Located CreateFontPackage at %p, MergeFontPackage at %p.\n", 559 | globals::pfnCreateFontPackage, globals::pfnMergeFontPackage)); 560 | 561 | // Read the input font file. 562 | std::string font_data; 563 | if (!ReadFileToString(input_font_path, &font_data)) { 564 | fprintf(stderr, "[-] Unable to read the %s input file\n", input_font_path); 565 | DestructParsedPE(pe); 566 | return 1; 567 | } 568 | 569 | // Process the input data using the loaded DLL file. 570 | ProcessSample(font_data); 571 | 572 | // Destroy the PE object, but don't manually unmap the image from memory. 573 | // Instead, rely on the OS for the cleanup. 574 | DestructParsedPE(pe); 575 | 576 | return 0; 577 | } 578 | 579 | -------------------------------------------------------------------------------- /images/font2pdf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleprojectzero/BrokenType/cf49a52b8e35b7d684fc8bc6b2ea8b923c177c2e/images/font2pdf.png -------------------------------------------------------------------------------- /images/generator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleprojectzero/BrokenType/cf49a52b8e35b7d684fc8bc6b2ea8b923c177c2e/images/generator.png -------------------------------------------------------------------------------- /images/loader-dwrite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleprojectzero/BrokenType/cf49a52b8e35b7d684fc8bc6b2ea8b923c177c2e/images/loader-dwrite.png -------------------------------------------------------------------------------- /images/loader-fontsub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleprojectzero/BrokenType/cf49a52b8e35b7d684fc8bc6b2ea8b923c177c2e/images/loader-fontsub.png -------------------------------------------------------------------------------- /images/loader.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleprojectzero/BrokenType/cf49a52b8e35b7d684fc8bc6b2ea8b923c177c2e/images/loader.png -------------------------------------------------------------------------------- /images/mutator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googleprojectzero/BrokenType/cf49a52b8e35b7d684fc8bc6b2ea8b923c177c2e/images/mutator.png -------------------------------------------------------------------------------- /truetype-generator/README.md: -------------------------------------------------------------------------------- 1 | # TrueType program generator 2 | 3 | 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. 4 | 5 | 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. 6 | 7 | ## Usage 8 | 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: 9 | 10 | ``` 11 | C:\truetype-generator>pip --no-cache-dir install fonttools 12 | Collecting fonttools 13 | Downloading https://files.pythonhosted.org/packages/9c/0d/d562e2491c579dffb6e0d6a7fa082ad5bc2fede46380d71e91e146a10bd7/fonttools-3.28.0-py2.py3-none-any.whl (609kB) 14 | 100% |UUUUUUUUUUUUUUUUUUUUUUUUUUUUUUUU| 614kB 1.0MB/s 15 | Installing collected packages: fonttools 16 | Successfully installed fonttools-3.28.0 17 | ``` 18 | 19 | Then, we can convert `arial.ttf` to `arial.ttx`: 20 | 21 | ``` 22 | C:\truetype-generator>ttx arial.ttf 23 | Dumping "arial.ttf" to "arial.ttx"... 24 | Dumping 'GlyphOrder' table... 25 | Dumping 'head' table... 26 | Dumping 'hhea' table... 27 | [...] 28 | Dumping 'JSTF' table... 29 | Dumping 'meta' table... 30 | Dumping 'DSIG' table... 31 | 32 | C:\truetype-generator> 33 | ``` 34 | 35 | Now, we can run the script to create a new, mutated font. We recommend using 100000 as the total number of inserted TrueType instructions: 36 | 37 | ``` 38 | C:\truetype-generator>python truetype_generate.py arial.ttx output.ttx 100000 39 | 40 | C:\truetype-generator> 41 | ``` 42 | 43 | At this point, the `output.ttx` file should contain long streams of TrueType programs, e.g.: 44 | 45 | ``` 46 | [...] 47 | 48 | PUSH[ ] 49 | 31597 50 | SCVTCI[ ] 51 | PUSH[ ] 52 | 1 53 | SZP1[ ] 54 | SFVTPV[ ] 55 | PUSH[ ] 56 | 20232 57 | NROUND[01] 58 | POP[ ] 59 | [...] 60 | ``` 61 | 62 | The last step is to "compile" the XML file back to binary form, like so: 63 | 64 | ``` 65 | C:\truetype-generator>ttx output.ttx 66 | Compiling "output.ttx" to "output.ttf"... 67 | Parsing 'GlyphOrder' table... 68 | Parsing 'head' table... 69 | Parsing 'hhea' table... 70 | [...] 71 | Parsing 'JSTF' table... 72 | Parsing 'meta' table... 73 | Parsing 'DSIG' table... 74 | 75 | C:\truetype-generator> 76 | ``` 77 | 78 | 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: 79 | 80 | ![TrueType font with random hint programs](../images/generator.png) 81 | -------------------------------------------------------------------------------- /truetype-generator/truetype_generate.py: -------------------------------------------------------------------------------- 1 | # Author: Mateusz Jurczyk (mjurczyk@google.com) 2 | # 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # https://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import os 18 | import random 19 | import sys 20 | import xml.etree.ElementTree as ET 21 | 22 | class TTProgram: 23 | def __init__(self, twilight_points, contours_in_glyph, points_in_glyph): 24 | # Specifies the number of points in the Twilight Zone (Z0). 25 | self._twilight_points = twilight_points 26 | 27 | # Specifies the number of contours making up the glyph. 28 | self._contours_in_glyph = contours_in_glyph 29 | 30 | # Specifies the number of points existing in the glyph. 31 | self._points_in_glyph = points_in_glyph 32 | 33 | # Current state of zone pointers. 34 | self._zp = [1, 1, 1] 35 | 36 | def _Imm_ContourIdx(self, zone_idx): 37 | """Returns a random contour index for Zone 1 (the Twilight Zone should never contain contours). 38 | """ 39 | # Once in a while, ignore the constraints and return something possibly invalid. 40 | if random.randint(0, 1000) == 0: 41 | if self._contours_in_glyph == 0: 42 | return self._Imm_SRandom16() 43 | else: 44 | return random.randint(0, 2 * self._contours_in_glyph) 45 | 46 | # Otherwise, obey the rules and return a valid contour index. 47 | assert(self._zp[zone_ptr] != 0) 48 | assert(self._contours_in_glyph != 0) 49 | return random.randint(0, self._contours_in_glyph - 1) 50 | 51 | def _Imm_PointIdx(self, zone_ptr): 52 | """Returns a random point index for the specified zone. 53 | """ 54 | 55 | # Once in a while, ignore the constraints and return something possibly invalid. 56 | if random.randint(0, 1000) == 0: 57 | if (self._twilight_points == 0) and (self._points_in_glyph == 0): 58 | return self._Imm_SRandom16() 59 | else: 60 | return random.randint(0, max(self._twilight_points, self._points_in_glyph) - 1) 61 | 62 | # Otherwise, obey the rules and return a valid point index. 63 | if self._zp[zone_ptr] == 0: 64 | assert(self._twilight_points != 0) 65 | point_idx = random.randint(0, self._twilight_points - 1) 66 | else: 67 | assert(self._points_in_glyph != 0) 68 | point_idx = random.randint(0, self._points_in_glyph - 1) 69 | 70 | return point_idx 71 | 72 | def _Imm_SRandom16(self): 73 | return random.randint(-32768, 32767) 74 | 75 | def _Imm_URandom16(self): 76 | return random.randint(0, 32767) 77 | 78 | # Stack instructions. 79 | # Only used as a part of other instructions; not arbitrarily inserted into the generated program. 80 | def _Instr_PUSH(self, values): 81 | return ["PUSH[ ]\n" + " ".join(map(lambda x: str(x), values))] 82 | 83 | def _Instr_POP(self): 84 | return ["POP[ ]"] 85 | 86 | def _Instr_CLEAR(self): 87 | return ["CLEAR[ ]"] 88 | 89 | # Storage Area. 90 | # Not implemented, as we don't expect that fuzzing these instructions would yield any interesting results. 91 | def _Instr_RS(self): 92 | return [] 93 | 94 | def _Instr_WS(self): 95 | return [] 96 | 97 | # Control Value Table. 98 | def _Instr_WCVTP(self): 99 | return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_SRandom16()]) + # TODO: Change the second Random16() to RandomF26Dot6(). 100 | ["WCVTP[ ]"]) 101 | 102 | def _Instr_WCVTF(self): 103 | return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_SRandom16()]) + # TODO: Change the second Random16() to RandomF26Dot6(). 104 | ["WCVTF[ ]"]) 105 | 106 | def _Instr_RCVT(self): 107 | return (self._Instr_PUSH([self._Imm_URandom16()]) + 108 | ["RCVT[ ]"] + 109 | self._Instr_POP()) 110 | 111 | # Graphics State. 112 | def _Instr_SVTCA(self): 113 | return ["SVTCA[" + str(random.randint(0, 1)) + "]"] 114 | 115 | def _Instr_SPVTCA(self): 116 | return ["SPVTCA[" + str(random.randint(0, 1)) + "]"] 117 | 118 | def _Instr_SFVTCA(self): 119 | return ["SFVTCA[" + str(random.randint(0, 1)) + "]"] 120 | 121 | def _Instr_SPVTL(self): 122 | try: 123 | return (self._Instr_PUSH([self._Imm_PointIdx(1), self._Imm_PointIdx(2)]) + 124 | ["SPVTL[" + str(random.randint(0, 1)) + "]"]) 125 | except: 126 | return [] 127 | 128 | def _Instr_SFVTL(self): 129 | try: 130 | return (self._Instr_PUSH([self._Imm_PointIdx(1), self._Imm_PointIdx(2)]) + 131 | ["SFVTL[" + str(random.randint(0, 1)) + "]"]) 132 | except: 133 | return [] 134 | 135 | def _Instr_SFVTPV(self): 136 | return ["SFVTPV[ ]"] 137 | 138 | def _Instr_SDPVTL(self): 139 | try: 140 | return (self._Instr_PUSH([self._Imm_PointIdx(1), self._Imm_PointIdx(2)]) + 141 | ["SDPVTL[" + str(random.randint(0, 1)) + "]"]) 142 | except: 143 | return [] 144 | 145 | def _Instr_SPVFS(self): 146 | opt = random.randint(0, 2) 147 | if opt == 0: 148 | return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_URandom16()]) + 149 | ["SPVFS[ ]"]) 150 | elif opt == 1: 151 | return (self._Instr_PUSH([0x4000, 0]) + 152 | ["SPVFS[ ]"]) 153 | elif opt == 2: 154 | return (self._Instr_PUSH([0, 0x4000]) + 155 | ["SPVFS[ ]"]) 156 | 157 | def _Instr_SFVFS(self): 158 | opt = random.randint(0, 2) 159 | if opt == 0: 160 | return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_URandom16()]) + 161 | ["SFVFS[ ]"]) 162 | elif opt == 1: 163 | return (self._Instr_PUSH([0x4000, 0]) + 164 | ["SFVFS[ ]"]) 165 | elif opt == 2: 166 | return (self._Instr_PUSH([0, 0x4000]) + 167 | ["SFVFS[ ]"]) 168 | 169 | def _Instr_GPV(self): 170 | # Not implemented, as we don't expect that fuzzing this instruction would yield any results. 171 | return [] 172 | 173 | def _Instr_GFV(self): 174 | # Not implemented, as we don't expect that fuzzing this instruction would yield any results. 175 | return [] 176 | 177 | def _Instr_SRP0(self, zone_ptr): 178 | return (self._Instr_PUSH([self._Imm_PointIdx(zone_ptr)]) + 179 | ["SRP0[ ]"]) 180 | 181 | def _Instr_SRP1(self, zone_ptr): 182 | return (self._Instr_PUSH([self._Imm_PointIdx(zone_ptr)]) + 183 | ["SRP1[ ]"]) 184 | 185 | def _Instr_SRP2(self, zone_ptr): 186 | return (self._Instr_PUSH([self._Imm_PointIdx(zone_ptr)]) + 187 | ["SRP2[ ]"]) 188 | 189 | def _Instr_SZP0(self): 190 | zone_ptr = random.randint(0, 1) 191 | self._zp[0] = zone_ptr 192 | return (self._Instr_PUSH([zone_ptr]) + 193 | ["SZP0[ ]"]) 194 | 195 | def _Instr_SZP1(self): 196 | zone_ptr = random.randint(0, 1) 197 | self._zp[1] = zone_ptr 198 | return (self._Instr_PUSH([zone_ptr]) + 199 | ["SZP1[ ]"]) 200 | 201 | def _Instr_SZP2(self, zone_ptr = None): 202 | if zone_ptr == None: 203 | zone_ptr = random.randint(0, 1) 204 | self._zp[2] = zone_ptr 205 | return (self._Instr_PUSH([zone_ptr]) + 206 | ["SZP2[ ]"]) 207 | 208 | def _Instr_SZPS(self): 209 | zone_ptr = random.randint(0, 1) 210 | self._zp = [zone_ptr] * 3 211 | return (self._Instr_PUSH([zone_ptr]) + 212 | ["SZPS[ ]"]) 213 | 214 | def _Instr_RTHG(self): 215 | return ["RTHG[ ]"] 216 | 217 | def _Instr_RTG(self): 218 | return ["RTG[ ]"] 219 | 220 | def _Instr_RTDG(self): 221 | return ["RTDG[ ]"] 222 | 223 | def _Instr_RDTG(self): 224 | return ["RDTG[ ]"] 225 | 226 | def _Instr_RUTG(self): 227 | return ["RUTG[ ]"] 228 | 229 | def _Instr_ROFF(self): 230 | return ["ROFF[ ]"] 231 | 232 | def _Instr_SROUND(self): 233 | return (self._Instr_PUSH([self._Imm_URandom16()]) + 234 | ["SROUND[ ]"]) 235 | 236 | def _Instr_S45ROUND(self): 237 | return (self._Instr_PUSH([self._Imm_URandom16()]) + 238 | ["S45ROUND[ ]"]) 239 | 240 | def _Instr_SLOOP(self): 241 | # Not implemented yet, since it is not clear how to do it correctly. 242 | return [] 243 | 244 | def _Instr_SMD(self): 245 | return (self._Instr_PUSH([self._Imm_URandom16()]) + # TODO: Change Random16() to RandomF26Dot6(). 246 | ["SMD[ ]"]) 247 | 248 | def _Instr_INSTCTRL(self): 249 | return (self._Instr_PUSH([self._Imm_SRandom16(), 3]) + 250 | ["INSTCTRL[ ]"]) 251 | 252 | def _Instr_SCANCTRL(self): 253 | return (self._Instr_PUSH([self._Imm_SRandom16()]) + 254 | ["SCANCTRL[ ]"]) 255 | 256 | def _Instr_SCANTYPE(self): 257 | return (self._Instr_PUSH([self._Imm_SRandom16()]) + 258 | ["SCANTYPE[ ]"]) 259 | 260 | def _Instr_SCVTCI(self): 261 | return (self._Instr_PUSH([self._Imm_URandom16()]) + # TODO: Change Random16() to RandomF26Dot6(). 262 | ["SCVTCI[ ]"]) 263 | 264 | def _Instr_SSWCI(self): 265 | return (self._Instr_PUSH([self._Imm_URandom16()]) + # TODO: Change Random16() to RandomF26Dot6(). 266 | ["SSWCI[ ]"]) 267 | 268 | def _Instr_SSW(self): 269 | return (self._Instr_PUSH([self._Imm_URandom16()]) + # TODO: Change Random16() to RandomF26Dot6(). 270 | ["SSW[ ]"]) 271 | 272 | def _Instr_FLIPON(self): 273 | return ["FLIPON[ ]"] 274 | 275 | def _Instr_FLIPOFF(self): 276 | return ["FLIPOFF[ ]"] 277 | 278 | def _Instr_SANGW(self): 279 | return (self._Instr_PUSH([self._Imm_URandom16()]) + 280 | ["SANGW[ ]"]) 281 | 282 | def _Instr_SDB(self): 283 | return (self._Instr_PUSH([self._Imm_URandom16()]) + 284 | ["SDB[ ]"]) 285 | 286 | def _Instr_SDS(self): 287 | return (self._Instr_PUSH([self._Imm_URandom16()]) + 288 | ["SDS[ ]"]) 289 | 290 | # Managing outlines. 291 | def _Instr_FLIPPT(self): 292 | try: 293 | return (self._Instr_PUSH([self._Imm_PointIdx(0)]) + 294 | ["FLIPPT[ ]"]) 295 | except: 296 | return [] 297 | 298 | def _Instr_FLIPRGON(self): 299 | try: 300 | x = self._Imm_PointIdx(0) 301 | y = self._Imm_PointIdx(0) 302 | if x > y: 303 | x, y = y, x 304 | 305 | return (self._Instr_PUSH([x, y]) + 306 | ["FLIPRGON[ ]"]) 307 | except: 308 | return [] 309 | 310 | def _Instr_FLIPRGOFF(self): 311 | try: 312 | x = self._Imm_PointIdx(0) 313 | y = self._Imm_PointIdx(0) 314 | if x > y: 315 | x, y = y, x 316 | 317 | return (self._Instr_PUSH([x, y]) + 318 | ["FLIPRGOFF[ ]"]) 319 | except: 320 | return [] 321 | 322 | def _Instr_SHP(self): 323 | try: 324 | op = [] 325 | 326 | flag = random.randint(0, 1) 327 | if flag == 0: 328 | op += self._Instr_SRP2(1) 329 | else: 330 | op += self._Instr_SRP1(0) 331 | 332 | return (op + self._Instr_PUSH([self._Imm_PointIdx(2)]) + 333 | ["SHP[" + str(flag) + "]"]) 334 | except: 335 | return [] 336 | 337 | def _Instr_SHC(self): 338 | try: 339 | flag = random.randint(0, 1) 340 | if flag == 0: 341 | op += self._Instr_SRP2(1) 342 | else: 343 | op += self._Instr_SRP1(0) 344 | 345 | return (op + self._Instr_PUSH([self._Imm_ContourIdx(2)]) + 346 | ["SHC[" + str(flag) + "]"]) 347 | except: 348 | return [] 349 | 350 | def _Instr_SHZ(self): 351 | try: 352 | flag = random.randint(0, 1) 353 | if flag == 0: 354 | op += self._Instr_SRP2(1) 355 | else: 356 | op += self._Instr_SRP1(0) 357 | 358 | return (op + self._Instr_PUSH([random.randint(0, 1)]) + 359 | ["SHZ[" + str(flag) + "]"]) 360 | except: 361 | return [] 362 | 363 | def _Instr_SHPIX(self): 364 | try: 365 | return (self._Instr_PUSH([self._Imm_PointIdx(2), self._Imm_URandom16()]) + # TODO: Change Random16() to RandomF26Dot6(). 366 | ["SHPIX[ ]"]) 367 | except: 368 | return [] 369 | 370 | def _Instr_MSIRP(self): 371 | try: 372 | return (self._Instr_SRP0(0) + 373 | self._Instr_PUSH([self._Imm_PointIdx(1), self._Imm_URandom16()]) + # TODO: Change Random16() to RandomF26Dot6(). 374 | ["MSIRP[" + str(random.randint(0, 1)) + "]"]) 375 | except: 376 | return [] 377 | 378 | def _Instr_MDAP(self): 379 | try: 380 | return (self._Instr_PUSH([self._Imm_PointIdx(0)]) + # TODO: Change Random16() to RandomF26Dot6(). 381 | ["MDAP[" + str(random.randint(0, 1)) + "]"]) 382 | except: 383 | return [] 384 | 385 | def _Instr_MIAP(self): 386 | try: 387 | return (self._Instr_PUSH([self._Imm_PointIdx(0), self._Imm_URandom16()]) + 388 | ["MIAP[" + str(random.randint(0, 1)) + "]"]) 389 | except: 390 | return [] 391 | 392 | def _Instr_MDRP(self): 393 | try: 394 | return (self._Instr_SRP0(0) + 395 | self._Instr_PUSH([self._Imm_PointIdx(1)]) + 396 | ["MDRP[" + str(random.randint(0, 1)) + 397 | str(random.randint(0, 1)) + 398 | str(random.randint(0, 1)) + 399 | str(random.randint(0, 1)) + 400 | str(random.randint(0, 1)) + 401 | "]"]) 402 | except: 403 | return [] 404 | 405 | def _Instr_MIRP(self): 406 | try: 407 | return (self._Instr_SRP0(0) + 408 | self._Instr_PUSH([self._Imm_PointIdx(1), self._Imm_URandom16()]) + 409 | ["MIRP[" + str(random.randint(0, 1)) + 410 | str(random.randint(0, 1)) + 411 | str(random.randint(0, 1)) + 412 | str(random.randint(0, 1)) + 413 | str(random.randint(0, 1)) + 414 | "]"]) 415 | except: 416 | return [] 417 | 418 | def _Instr_ALIGNRP(self): 419 | try: 420 | return (self._Instr_SRP0(0) + 421 | self._Instr_PUSH([self._Imm_PointIdx(1)]) + 422 | ["ALIGNRP[ ]"]) 423 | except: 424 | return [] 425 | 426 | def _Instr_ISECT(self): 427 | try: 428 | return (self._Instr_PUSH([self._Imm_PointIdx(2), 429 | self._Imm_PointIdx(1), self._Imm_PointIdx(1), 430 | self._Imm_PointIdx(0), self._Imm_PointIdx(0)]) + 431 | ["ISECT[ ]"]) 432 | except: 433 | return [] 434 | 435 | def _Instr_ALIGNPTS(self): 436 | try: 437 | return (self._Instr_PUSH([self._Imm_PointIdx(0), self._Imm_PointIdx(1)]) + 438 | ["ALIGNPTS[ ]"]) 439 | except: 440 | return [] 441 | 442 | def _Instr_IP(self): 443 | try: 444 | return (self._Instr_SRP1(0) + self._Instr_SRP2(1) + 445 | self._Instr_PUSH([self._Imm_PointIdx(2)]) + 446 | ["IP[ ]"]) 447 | except: 448 | return [] 449 | 450 | def _Instr_UTP(self): 451 | try: 452 | return (self._Instr_PUSH([self._Imm_PointIdx(0)]) + 453 | ["UTP[ ]"]) 454 | except: 455 | return [] 456 | 457 | def _Instr_IUP(self): 458 | if self._points_in_glyph == 0: 459 | return [] 460 | return ["IUP[" + str(random.randint(0, 1)) + "]"] 461 | 462 | # Managing exceptions. 463 | def _Instr_DELTAP1(self): 464 | try: 465 | return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_PointIdx(0), 1]) + 466 | ["DELTAP1[ ]"]) 467 | except: 468 | return [] 469 | 470 | def _Instr_DELTAP2(self): 471 | try: 472 | return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_PointIdx(0), 1]) + 473 | ["DELTAP2[ ]"]) 474 | except: 475 | return [] 476 | 477 | def _Instr_DELTAP3(self): 478 | try: 479 | return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_PointIdx(0), 1]) + 480 | ["DELTAP3[ ]"]) 481 | except: 482 | return [] 483 | 484 | def _Instr_DELTAC1(self): 485 | return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_URandom16(), 1]) + 486 | ["DELTAC1[ ]"]) 487 | 488 | def _Instr_DELTAC2(self): 489 | return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_URandom16(), 1]) + 490 | ["DELTAC2[ ]"]) 491 | 492 | def _Instr_DELTAC3(self): 493 | return (self._Instr_PUSH([self._Imm_URandom16(), self._Imm_URandom16(), 1]) + 494 | ["DELTAC3[ ]"]) 495 | 496 | # Compensating for the engine characteristics. 497 | def _Instr_ROUND(self): 498 | return (self._Instr_PUSH([self._Imm_URandom16()]) + 499 | ["ROUND[" + str(random.randint(0, 1)) + str(random.randint(0, 1)) + "]"] + 500 | self._Instr_POP()) 501 | 502 | def _Instr_NROUND(self): 503 | return (self._Instr_PUSH([self._Imm_URandom16()]) + 504 | ["NROUND[" + str(random.randint(0, 1)) + str(random.randint(0, 1)) + "]"] + 505 | self._Instr_POP()) 506 | 507 | def GenerateInstruction(self): 508 | instructions = [ 509 | # Storage Area. 510 | self._Instr_RS, 511 | self._Instr_WS, 512 | 513 | # Control Value Table. 514 | self._Instr_WCVTP, 515 | self._Instr_WCVTF, 516 | self._Instr_RCVT, 517 | 518 | # Graphics State. 519 | self._Instr_SVTCA, 520 | self._Instr_SPVTCA, 521 | self._Instr_SFVTCA, 522 | self._Instr_SPVTL, 523 | self._Instr_SFVTL, 524 | self._Instr_SFVTPV, 525 | self._Instr_SDPVTL, 526 | self._Instr_SPVFS, 527 | self._Instr_SFVFS, 528 | self._Instr_GPV, 529 | self._Instr_GFV, 530 | #self._Instr_SRP0, # 531 | #self._Instr_SRP1, # Reference point setting is only performed directly in instructions which use them. 532 | #self._Instr_SRP2, # 533 | self._Instr_SZP0, 534 | self._Instr_SZP1, 535 | self._Instr_SZP2, 536 | self._Instr_SZPS, 537 | self._Instr_RTHG, 538 | self._Instr_RTG, 539 | self._Instr_RTDG, 540 | self._Instr_RDTG, 541 | self._Instr_RUTG, 542 | self._Instr_ROFF, 543 | self._Instr_SROUND, 544 | self._Instr_S45ROUND, 545 | self._Instr_SLOOP, 546 | self._Instr_SMD, 547 | self._Instr_SCANCTRL, 548 | self._Instr_SCANTYPE, 549 | self._Instr_SCVTCI, 550 | self._Instr_SSWCI, 551 | self._Instr_SSW, 552 | self._Instr_FLIPON, 553 | self._Instr_FLIPOFF, 554 | self._Instr_SANGW, 555 | self._Instr_SDB, 556 | self._Instr_SDS, 557 | 558 | # Managing Outlines. 559 | self._Instr_FLIPPT, 560 | self._Instr_FLIPRGON, 561 | self._Instr_FLIPRGOFF, 562 | self._Instr_SHP, 563 | self._Instr_SHC, 564 | self._Instr_SHZ, 565 | self._Instr_SHPIX, 566 | self._Instr_MSIRP, 567 | self._Instr_MDAP, 568 | self._Instr_MIAP, 569 | self._Instr_MDRP, 570 | self._Instr_MIRP, 571 | self._Instr_ALIGNRP, 572 | self._Instr_ISECT, 573 | self._Instr_ALIGNPTS, 574 | self._Instr_IP, 575 | self._Instr_UTP, 576 | self._Instr_IUP, 577 | 578 | # Managing Exceptions. 579 | self._Instr_DELTAP1, 580 | self._Instr_DELTAP2, 581 | self._Instr_DELTAP3, 582 | self._Instr_DELTAC1, 583 | self._Instr_DELTAC2, 584 | self._Instr_DELTAC3, 585 | 586 | # Compensating For The Engine Characteristics. 587 | self._Instr_ROUND, 588 | self._Instr_NROUND, 589 | ] 590 | 591 | return random.choice(instructions)() 592 | 593 | def GenerateProgram(self, instructions_limit): 594 | pgm = self._Instr_FLIPRGON() 595 | while len(pgm) < instructions_limit: 596 | pgm += self.GenerateInstruction() 597 | 598 | return "\n".join(pgm) 599 | 600 | class TTXParser: 601 | 602 | # Specifies the number of instructions inserted into the "prep" TTF table. 603 | PREP_INSTRUCTIONS = 10000 604 | 605 | # Specifies the maximum number of instructions per glyph. 606 | MAX_INSTRUCTIONS_PER_GLYPH = 10000 607 | 608 | def __init__(self, num_instructions): 609 | # Specifies the total number of TrueType instructions which should be inserted into the font. 610 | self._num_instructions = num_instructions 611 | 612 | # Specifies the chosen number of twilight points in the font. 613 | self._twilight_points = 0 614 | 615 | # Specifies the total number of glyphs defined in the font, as extracted from the "maxp" table. 616 | self._num_glyphs = 0 617 | 618 | # Specifies the number of contours defined in the currently processed glyph. 619 | self._contours_in_glyph = 0 620 | 621 | # Specifies the number of points defined in the currently processed glyph. 622 | self._points_in_glyph = 0 623 | 624 | def _Handler_ttFont(self, path, node): 625 | """Makes sure that the CVT table exists in the font, as it is required for the generated instruction 626 | stream, and fonttools recalculate maxp.maxStorage based on the size of the table. 627 | """ 628 | if node.find("cvt") == None: 629 | ET.SubElement(node, "cvt") 630 | 631 | def _Handler_maxp(self, path, node): 632 | """Sets the limits specified in the "maxp" TTF table to desired values. 633 | """ 634 | num_glyphs_node = node.find("numGlyphs") 635 | assert(num_glyphs_node != None) 636 | 637 | self._num_glyphs = int(num_glyphs_node.attrib["value"]) 638 | 639 | max_points = None 640 | for item in node: 641 | if item.tag == "maxStorage": 642 | item.attrib["value"] = str(2 ** 15) 643 | elif item.tag == "maxStackElements": 644 | item.attrib["value"] = "8" 645 | elif item.tag == "maxSizeOfInstructions": 646 | item.attrib["value"] = str(2 ** 16 - 1) 647 | elif item.tag == "maxPoints": 648 | max_points = int(item.attrib["value"]) 649 | elif item.tag == "maxTwilightPoints": 650 | assert(max_points != None) 651 | if random.randint(0, 1) == 0: 652 | self._twilight_points = max(max_points * 2, 100) 653 | else: 654 | self._twilight_points = 0 655 | item.attrib["value"] = str(self._twilight_points) 656 | elif item.tag == "maxZones": 657 | item.attrib["value"] = "2" 658 | 659 | def _Handler_cvt(self, path, node): 660 | """Fills the CVT table with 32768 random values. 661 | """ 662 | 663 | # First, iterate through the "cv" elements in order to locate the largest existing index. 664 | max_index = -1 665 | for item in node.findall("cv"): 666 | max_index = max(max_index, int(item.attrib["index"])) 667 | 668 | for idx in range(max_index + 1, 32767 + 1): 669 | ET.SubElement(node, "cv", {"index" : str(idx), "value" : str(random.randint(-32768, 32767))}) 670 | 671 | def _Handler_TTGlyph(self, path, node): 672 | """Resets the number of contours and points in the glyph (because there is a new one), and inserts a child 673 | tag if one is not already present, in order to have TrueType instructions executed for all 674 | glyphs in the font. 675 | """ 676 | self._contours_in_glyph = 0 677 | self._points_in_glyph = 0 678 | 679 | # Only add the instructions for glyphs which have at least one contour. 680 | if (node.find("contour") != None) and (node.find("instructions") == None): 681 | instructions_node = ET.SubElement(node, "instructions") 682 | ET.SubElement(instructions_node, "assembly") 683 | 684 | def _Handler_contour(self, path, node): 685 | """ Accounts for a new contour in the glyph by incrementing the _contours_in_glyph field. 686 | """ 687 | self._contours_in_glyph += 1 688 | 689 | def _Handler_pt(self, path, node): 690 | """Accounts for a new point in the glyph by incrementing the _points_in_glyph field. 691 | """ 692 | self._points_in_glyph += 1 693 | 694 | def _Handler_assembly(self, path, node): 695 | """Generates a new TTF program for the node. 696 | """ 697 | 698 | if "fpgm" in path: 699 | # We want the "fpgm" (Font Program) section empty, as it should only contain instruction/function definitions. 700 | node.text = "" 701 | else: 702 | program = TTProgram(self._twilight_points, self._contours_in_glyph, self._points_in_glyph) 703 | 704 | if "prep" in path: 705 | # Insert a constant number of initialization instructions into the "prep" table. 706 | node.text = program.GenerateProgram(self.PREP_INSTRUCTIONS) 707 | else: 708 | # Generate a regular TrueType program with length depending on the number of glyphs in font. 709 | node.text = program.GenerateProgram(min(self._num_instructions // self._num_glyphs, self.MAX_INSTRUCTIONS_PER_GLYPH)) 710 | 711 | def TraverseNode(self, path, node): 712 | handlers = {"ttFont" : self._Handler_ttFont, # global TTF font tag. 713 | "maxp" : self._Handler_maxp, # "maxp" TTF table. 714 | "cvt" : self._Handler_cvt, # "cvt " TTF table. 715 | "TTGlyph" : self._Handler_TTGlyph, # Glyph definition. 716 | "contour" : self._Handler_contour, # Contour definition in glyph. 717 | "pt" : self._Handler_pt, # Point definition in glyph. 718 | "assembly" : self._Handler_assembly, # TTF instructions ("fpgm" TTF table, "prep" TTF table or glyph hinting program) 719 | } 720 | 721 | # Run the tag handler, if one exists. 722 | if node.tag in handlers: 723 | handlers[node.tag](path, node) 724 | 725 | # Traverse the XML nodes recursively. 726 | for child in node: 727 | self.TraverseNode(path + [node.tag], child) 728 | 729 | def main(argv): 730 | if len(argv) != 4: 731 | print "Usage: %s /path/to/input.ttx /path/to/output.ttx " % argv[0] 732 | sys.exit(1) 733 | 734 | tree = ET.parse(argv[1]) 735 | 736 | parser = TTXParser(int(argv[3])) 737 | parser.TraverseNode([], tree.getroot()) 738 | 739 | tree.write(argv[2], encoding='utf-8', xml_declaration = True) 740 | 741 | if __name__ == "__main__": 742 | main(sys.argv) 743 | -------------------------------------------------------------------------------- /ttf-fontsub-loader/README.md: -------------------------------------------------------------------------------- 1 | # Font loader for the Windows Font Subsetting Library (FontSub.dll) 2 | 3 | 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. 4 | 5 | ## Building 6 | 7 | The application can be compiled with Microsoft Visual Studio after importing `ttf-fontsub-loader.cpp` into a new project. 8 | 9 | ## Usage 10 | 11 | Using the tool is as simple as passing the path of the tested TTF font in the first argument, for instance: 12 | 13 | ``` 14 | c:\ttf-fontsub-loader>ttf-fontsub-loader.exe C:\Windows\Fonts\arial.ttf 15 | [+] CreateFontPackage([ 1027192 bytes ], TTFCFP_SUBSET1) ---> 0 (390020 bytes) 16 | [+] MergeFontPackage(NULL, [ 390020 bytes ], TTFMFP_SUBSET1) ---> 0 (505796 bytes) 17 | [+] CreateFontPackage([ 1027192 bytes ], TTFCFP_DELTA) ---> 0 (1416 bytes) 18 | [+] MergeFontPackage([ 505796 bytes ], [ 1416 bytes ], TTFMFP_DELTA) ---> 0 (505796 bytes) 19 | [+] CreateFontPackage([ 1027192 bytes ], TTFCFP_DELTA) ---> 0 (1416 bytes) 20 | [+] MergeFontPackage([ 505796 bytes ], [ 1416 bytes ], TTFMFP_DELTA) ---> 0 (505796 bytes) 21 | 22 | c:\ttf-fontsub-loader> 23 | ``` 24 | 25 | 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. 26 | 27 | An example of a subsetted Courier New Italic font is shown below: 28 | 29 | ![Subsetted Courier New Italic font](../images/loader-fontsub.png) 30 | 31 | -------------------------------------------------------------------------------- /ttf-fontsub-loader/ttf-fontsub-loader.cpp: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2019 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #define _CRT_SECURE_NO_WARNINGS 21 | #include 22 | #include 23 | 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #pragma comment(lib, "fontsub.lib") 34 | 35 | #ifdef _DEBUG 36 | # define LOG(x) printf x; 37 | #else 38 | # define LOG(x) 39 | #endif // DEBUG 40 | 41 | static void *CfpAllocproc(size_t Arg1) { 42 | void *ret = malloc(Arg1); 43 | LOG(("[+] malloc(0x%zx) ---> %p\n", Arg1, ret)); 44 | return ret; 45 | } 46 | 47 | static void CfpFreeproc(void *Arg1) { 48 | LOG(("[+] free(%p)\n", Arg1)); 49 | return free(Arg1); 50 | } 51 | 52 | static void *CfpReallocproc(void *Arg1, size_t Arg2) { 53 | void *ret = realloc(Arg1, Arg2); 54 | LOG(("[+] realloc(%p, 0x%zx) ---> %p\n", Arg1, Arg2, ret)); 55 | return ret; 56 | } 57 | 58 | static bool ReadFileToString(const char *filename, std::string *buffer) { 59 | FILE *f = fopen(filename, "rb"); 60 | if (f == NULL) { 61 | return false; 62 | } 63 | 64 | fseek(f, 0, SEEK_END); 65 | const long size = ftell(f); 66 | fseek(f, 0, SEEK_SET); 67 | 68 | bool success = true; 69 | 70 | char *tmp = (char *)malloc(size); 71 | if (tmp == NULL) { 72 | success = false; 73 | } 74 | 75 | if (success) { 76 | success = (fread(tmp, 1, size, f) == size); 77 | } 78 | 79 | if (success) { 80 | buffer->assign(tmp, size); 81 | } 82 | 83 | free(tmp); 84 | fclose(f); 85 | return success; 86 | } 87 | 88 | void GenerateKeepList(std::mt19937& rand_gen, std::vector *keep_list) { 89 | int list_length; 90 | 91 | switch (rand_gen() & 3) { 92 | case 0: list_length = 1 + (rand_gen() % 10); break; 93 | case 1: list_length = 1 + (rand_gen() % 100); break; 94 | case 2: list_length = 1 + (rand_gen() % 1000); break; 95 | case 3: list_length = 1 + (rand_gen() % 10000); break; 96 | } 97 | 98 | for (int i = 0; i < list_length; i++) { 99 | keep_list->push_back(rand_gen() % (list_length * 2)); 100 | } 101 | } 102 | 103 | unsigned long FontSubCreate(const std::string& input_data, std::mt19937& rand_gen, unsigned short usFormat, std::string *output_data) { 104 | unsigned char *puchFontPackageBuffer = NULL; 105 | unsigned long ulFontPackageBufferSize = 0; 106 | unsigned long ulBytesWritten = 0; 107 | 108 | unsigned short usFlag = TTFCFP_FLAGS_SUBSET; 109 | unsigned short usTTCIndex = 0; 110 | 111 | if (rand_gen() & 1) { usFlag |= TTFCFP_FLAGS_COMPRESS; } 112 | if (rand_gen() & 1) { usFlag |= TTFCFP_FLAGS_GLYPHLIST; } 113 | if (input_data.size() >= 12 && !memcmp(input_data.data(), "ttcf", 4)) { 114 | int font_count = input_data.data()[11]; 115 | if (font_count == 0) { 116 | font_count = 1; 117 | } 118 | 119 | usFlag |= TTFCFP_FLAGS_TTC; 120 | usTTCIndex = rand_gen() % font_count; 121 | } 122 | 123 | unsigned short usSubsetPlatform = TTFCFP_UNICODE_PLATFORMID; 124 | unsigned short usSubsetEncoding = TTFCFP_DONT_CARE; 125 | 126 | switch (rand_gen() & 3) { 127 | case 0: 128 | usSubsetPlatform = TTFCFP_UNICODE_PLATFORMID; 129 | break; 130 | 131 | case 1: 132 | usSubsetPlatform = TTFCFP_APPLE_PLATFORMID; 133 | if (rand_gen() & 1) { 134 | usSubsetEncoding = TTFCFP_STD_MAC_CHAR_SET; 135 | } 136 | break; 137 | 138 | case 2: 139 | usSubsetPlatform = TTFCFP_ISO_PLATFORMID; 140 | break; 141 | 142 | case 3: 143 | usSubsetPlatform = TTFCFP_MS_PLATFORMID; 144 | 145 | switch (rand_gen() & 3) { 146 | case 0: 147 | usSubsetEncoding = TTFCFP_SYMBOL_CHAR_SET; 148 | break; 149 | 150 | case 1: 151 | usSubsetEncoding = TTFCFP_UNICODE_CHAR_SET; 152 | break; 153 | } 154 | break; 155 | } 156 | 157 | std::vector keep_list; 158 | GenerateKeepList(rand_gen, &keep_list); 159 | 160 | unsigned long ret = CreateFontPackage( 161 | (unsigned char *)input_data.data(), 162 | input_data.size(), 163 | &puchFontPackageBuffer, 164 | &ulFontPackageBufferSize, 165 | &ulBytesWritten, 166 | usFlag, 167 | usTTCIndex, 168 | usFormat, 169 | /*usSubsetLanguage=*/0, 170 | usSubsetPlatform, 171 | usSubsetEncoding, 172 | &keep_list[0], 173 | keep_list.size(), 174 | CfpAllocproc, 175 | CfpReallocproc, 176 | CfpFreeproc, 177 | NULL); 178 | 179 | if (ret == 0) { 180 | LOG(("[+] CreateFontPackage returned buffer %p, buffer size 0x%lx, bytes written 0x%lx\n", 181 | puchFontPackageBuffer, ulFontPackageBufferSize, ulBytesWritten)); 182 | 183 | output_data->assign((char *)puchFontPackageBuffer, ulBytesWritten); 184 | CfpFreeproc(puchFontPackageBuffer); 185 | } else { 186 | output_data->clear(); 187 | } 188 | 189 | return ret; 190 | } 191 | 192 | unsigned long FontSubMerge1(const std::string& input_data, unsigned short usMode, std::string *output_data) { 193 | unsigned char *puchDestBuffer = NULL; 194 | unsigned long ulDestBufferSize = 0; 195 | unsigned long ulBytesWritten = 0; 196 | 197 | unsigned long ret = MergeFontPackage( 198 | /*puchMergeFontBuffer=*/NULL, 199 | /*ulMergeFontBufferSize=*/0, 200 | /*puchFontPackageBuffer=*/(const unsigned char *)input_data.data(), 201 | /*ulFontPackageBufferSize=*/input_data.size(), 202 | /*ppuchDestBuffer=*/&puchDestBuffer, 203 | /*pulDestBufferSize=*/&ulDestBufferSize, 204 | /*pulBytesWritten=*/&ulBytesWritten, 205 | usMode, 206 | CfpAllocproc, 207 | CfpReallocproc, 208 | CfpFreeproc, 209 | NULL); 210 | 211 | if (ret == 0) { 212 | LOG(("[+] MergeFontPackage returned buffer %p, buffer size 0x%lx, bytes written 0x%lx\n", 213 | puchDestBuffer, ulDestBufferSize, ulBytesWritten)); 214 | 215 | output_data->assign((char *)puchDestBuffer, ulBytesWritten); 216 | CfpFreeproc(puchDestBuffer); 217 | } else { 218 | output_data->clear(); 219 | } 220 | 221 | return ret; 222 | } 223 | 224 | unsigned long FontSubMerge2(const std::string& merge_font, const std::string& font_package, std::string *output_data) { 225 | unsigned char *puchDestBuffer = NULL; 226 | unsigned long ulDestBufferSize = 0; 227 | unsigned long ulBytesWritten = 0; 228 | 229 | unsigned long ret = MergeFontPackage( 230 | /*puchMergeFontBuffer=*/(const unsigned char *)merge_font.data(), 231 | /*ulMergeFontBufferSize=*/merge_font.size(), 232 | /*puchFontPackageBuffer=*/(const unsigned char *)font_package.data(), 233 | /*ulFontPackageBufferSize=*/font_package.size(), 234 | /*ppuchDestBuffer=*/&puchDestBuffer, 235 | /*pulDestBufferSize=*/&ulDestBufferSize, 236 | /*pulBytesWritten=*/&ulBytesWritten, 237 | /*usMode=*/TTFMFP_DELTA, 238 | CfpAllocproc, 239 | CfpReallocproc, 240 | CfpFreeproc, 241 | NULL); 242 | 243 | if (ret == 0) { 244 | LOG(("[+] MergeFontPackage returned buffer %p, buffer size 0x%lx, bytes written 0x%lx\n", 245 | puchDestBuffer, ulDestBufferSize, ulBytesWritten)); 246 | 247 | output_data->assign((char *)puchDestBuffer, ulBytesWritten); 248 | CfpFreeproc(puchDestBuffer); 249 | } else { 250 | output_data->clear(); 251 | } 252 | 253 | return ret; 254 | } 255 | 256 | void ProcessSample(const std::string& input_data) { 257 | std::seed_seq seed(input_data.begin(), input_data.end()); 258 | std::mt19937 rand_gen(seed); 259 | 260 | if (rand_gen() & 1) /* TTFCFP_SUBSET case */ { 261 | std::string font_package; 262 | unsigned long ret = FontSubCreate(input_data, rand_gen, TTFCFP_SUBSET, &font_package); 263 | 264 | printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_SUBSET) ---> %lu (%zu bytes)\n", 265 | input_data.size(), ret, font_package.size()); 266 | 267 | if (ret == 0) { 268 | std::string working_font; 269 | ret = FontSubMerge1(font_package, TTFMFP_SUBSET, &working_font); 270 | 271 | printf("[+] MergeFontPackage(NULL, [ %zu bytes ], TTFMFP_SUBSET) ---> %lu (%zu bytes)\n", 272 | font_package.size(), ret, working_font.size()); 273 | } 274 | } else /* TTFCFP_SUBSET1 / TTFCFP_DELTA case */ { 275 | std::string font_package; 276 | unsigned long ret = FontSubCreate(input_data, rand_gen, TTFCFP_SUBSET1, &font_package); 277 | 278 | printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_SUBSET1) ---> %lu (%zu bytes)\n", 279 | input_data.size(), ret, font_package.size()); 280 | 281 | if (ret == 0) { 282 | std::string working_font; 283 | ret = FontSubMerge1(font_package, TTFMFP_SUBSET1, &working_font); 284 | 285 | printf("[+] MergeFontPackage(NULL, [ %zu bytes ], TTFMFP_SUBSET1) ---> %lu (%zu bytes)\n", 286 | font_package.size(), ret, working_font.size()); 287 | 288 | const int merges = 1 + (rand_gen() % 5); 289 | for (int i = 0; i < merges; i++) { 290 | ret = FontSubCreate(input_data, rand_gen, TTFCFP_DELTA, &font_package); 291 | printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_DELTA) ---> %lu (%zu bytes)\n", 292 | input_data.size(), ret, font_package.size()); 293 | 294 | if (ret == 0) { 295 | std::string new_working_font; 296 | ret = FontSubMerge2(working_font, font_package, &new_working_font); 297 | 298 | printf("[+] MergeFontPackage([ %zu bytes ], [ %zu bytes ], TTFMFP_DELTA) ---> %lu (%zu bytes)\n", 299 | working_font.size(), font_package.size(), ret, new_working_font.size()); 300 | 301 | if (ret == 0) { 302 | working_font = new_working_font; 303 | } 304 | } 305 | } 306 | } 307 | } 308 | } 309 | 310 | int main(int argc, char **argv) { 311 | if (argc != 2) { 312 | printf("Usage: %s \n", argv[0]); 313 | return 1; 314 | } 315 | 316 | const char *input_font_path = argv[1]; 317 | 318 | std::string font_data; 319 | if (!ReadFileToString(input_font_path, &font_data)) { 320 | printf("[-] Unable to read the %s input file\n", input_font_path); 321 | return 1; 322 | } 323 | 324 | ProcessSample(font_data); 325 | 326 | return 0; 327 | } 328 | 329 | -------------------------------------------------------------------------------- /ttf-otf-dwrite-loader/README.md: -------------------------------------------------------------------------------- 1 | # Font loader for Microsoft DirectWrite 2 | 3 | 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. 4 | 5 | The font-related DirectWrite calls made by the loader are listed below: 6 | 7 | - [IDWriteFactory::CreateFontFileReference](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefactory-createfontfilereference) 8 | - [IDWriteFactory::CreateFontFace](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefactory-createfontface) 9 | - [IDWriteFontFile::Analyze](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefontfile-analyze) 10 | - [IDWriteFontFace::GetMetrics](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefontface-getmetrics) 11 | - [IDWriteFontFace::GetDesignGlyphMetrics](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefontface-getdesignglyphmetrics) 12 | - [IDWriteFontFace::GetGlyphIndices](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritefontface-getglyphindices) 13 | - [IDWriteFontFace1::GetUnicodeRanges](https://docs.microsoft.com/en-us/windows/win32/api/dwrite_1/nf-dwrite_1-idwritefontface1-getunicoderanges) 14 | - [IDWriteBitmapRenderTarget::DrawGlyphRun](https://docs.microsoft.com/en-us/windows/win32/api/dwrite/nf-dwrite-idwritebitmaprendertarget-drawglyphrun) 15 | 16 | ## Building 17 | 18 | The application can be compiled with Microsoft Visual Studio after importing `ttf-otf-dwrite-loader.cpp` and `config.h` into a new project. 19 | 20 | ## Usage 21 | 22 | Using the tool is as simple as passing the path of the tested TTF/OTF font in the first argument, for example: 23 | 24 | ``` 25 | c:\ttf-otf-dwrite-loader>ttf-otf-dwrite-loader.exe C:\Windows\Fonts\arial.ttf 26 | [+] Input font is supported, with file type 2, face type 1 and number of faces 1 27 | [+] Loaded 140 unicode ranges from the input font 28 | [+] Font processing completed 29 | [+] Font displayed without errors. 30 | 31 | c:\ttf-otf-dwrite-loader> 32 | ``` 33 | 34 | In addition to the standard output, you should also observe the font's glyphs being drawn in the upper left corner of the screen: 35 | 36 | ![Font glyphs displayed on the screen as rasterized by DirectWrite](../images/loader-dwrite.png) 37 | 38 | 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. 39 | -------------------------------------------------------------------------------- /ttf-otf-dwrite-loader/config.h: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2019 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #ifndef CONFIG_H_ 21 | #define CONFIG_H_ 22 | 23 | // Dimensions of the bitmap where the font glyphs are rendered on. 24 | #define RENDER_TARGET_WIDTH 800 25 | #define RENDER_TARGET_HEIGHT 600 26 | 27 | // Number of glyphs displayed at once. 28 | #define DISPLAYED_GLYPHS_COUNT 10 29 | 30 | #endif // CONFIG_H_ 31 | -------------------------------------------------------------------------------- /ttf-otf-dwrite-loader/ttf-otf-dwrite-loader.cpp: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2019 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | 33 | #include "config.h" 34 | 35 | #pragma comment(lib, "d2d1.lib") 36 | #pragma comment(lib, "dwrite.lib") 37 | 38 | #ifndef IFR 39 | #define IFR(expr) do {hr = (expr); _ASSERT(SUCCEEDED(hr)); if (FAILED(hr)) return(hr);} while(0) 40 | #endif 41 | 42 | FLOAT ConvertPointSizeToDIP(FLOAT points) { 43 | return (points / 72.0f) * 96.0f; 44 | } 45 | 46 | int wmain(int argc, wchar_t *argv[], wchar_t *envp[]) { 47 | HRESULT hr = S_OK; 48 | IDWriteFactory *pDWriteFactory; 49 | IDWriteGdiInterop *pGdiInterop; 50 | IDWriteBitmapRenderTarget *pBitmapRenderTarget; 51 | IDWriteRenderingParams *pRenderingParams; 52 | 53 | if (argc != 2) { 54 | wprintf(L"Usage: %s \n", argv[0]); 55 | return EXIT_FAILURE; 56 | } 57 | 58 | // Initialize DirectWrite. 59 | IFR(DWriteCreateFactory(DWRITE_FACTORY_TYPE_SHARED, __uuidof(IDWriteFactory), 60 | reinterpret_cast(&pDWriteFactory))); 61 | IFR(pDWriteFactory->GetGdiInterop(&pGdiInterop)); 62 | IFR(pGdiInterop->CreateBitmapRenderTarget(GetDC(NULL), RENDER_TARGET_WIDTH, RENDER_TARGET_HEIGHT, &pBitmapRenderTarget)); 63 | IFR(pDWriteFactory->CreateRenderingParams(&pRenderingParams)); 64 | 65 | LPCWSTR szFontPath = argv[1]; 66 | BOOL bSuccess = TRUE; 67 | IDWriteFontFile *pFontFile = NULL; 68 | do { 69 | hr = pDWriteFactory->CreateFontFileReference(szFontPath, NULL, &pFontFile); 70 | if (FAILED(hr)) { 71 | wprintf(L"[-] IDWriteFactory::CreateFontFileReference failed with code %x\n", hr); 72 | bSuccess = FALSE; 73 | break; 74 | } 75 | 76 | BOOL isSupportedFontType = FALSE; 77 | DWRITE_FONT_FILE_TYPE fontFileType; 78 | DWRITE_FONT_FACE_TYPE fontFaceType; 79 | UINT32 numberOfFaces; 80 | hr = pFontFile->Analyze(&isSupportedFontType, &fontFileType, &fontFaceType, &numberOfFaces); 81 | if (FAILED(hr)) { 82 | wprintf(L"[-] IDWriteFontFile::Analyze failed with code %x\n", hr); 83 | bSuccess = FALSE; 84 | break; 85 | } 86 | 87 | if (!isSupportedFontType) { 88 | wprintf(L"[-] The input font with file type %d and face type %d isn't supported by DirectWrite\n", fontFileType, fontFaceType); 89 | bSuccess = FALSE; 90 | break; 91 | } 92 | 93 | wprintf(L"[+] Input font is supported, with file type %d, face type %d and number of faces %d\n", 94 | fontFileType, fontFaceType, numberOfFaces); 95 | 96 | for (UINT32 face_it = 0; face_it < numberOfFaces; face_it++) { 97 | IDWriteFontFile *pFontFiles[] = { pFontFile }; 98 | IDWriteFontFace *pFontFace = NULL; 99 | 100 | hr = pDWriteFactory->CreateFontFace(fontFaceType, 1, pFontFiles, face_it, DWRITE_FONT_SIMULATIONS_NONE, &pFontFace); 101 | if (FAILED(hr)) { 102 | wprintf(L"[-] IDWriteFactory::CreateFontFace(faceIndex=%u) failed with code %x\n", face_it, hr); 103 | bSuccess = FALSE; 104 | 105 | // Don't bail out at this point and instead continue processing other font faces. 106 | continue; 107 | } 108 | 109 | IDWriteFontFace1 *pFontFace1 = static_cast(pFontFace); 110 | 111 | DWRITE_FONT_METRICS fontMetrics; 112 | pFontFace->GetMetrics(&fontMetrics); 113 | 114 | UINT32 actualRangeCount; 115 | hr = pFontFace1->GetUnicodeRanges(0, NULL, &actualRangeCount); 116 | if (SUCCEEDED(hr)) { 117 | pFontFace->Release(); 118 | continue; 119 | } else if (hr != E_NOT_SUFFICIENT_BUFFER) { 120 | wprintf(L"[-] IDWriteFontFace1::GetUnicodeRanges(faceIndex=%u) failed with code %x\n", face_it, hr); 121 | bSuccess = FALSE; 122 | pFontFace->Release(); 123 | continue; 124 | } 125 | 126 | DWRITE_UNICODE_RANGE *pRanges = new DWRITE_UNICODE_RANGE[actualRangeCount]; 127 | 128 | hr = pFontFace1->GetUnicodeRanges(actualRangeCount, pRanges, &actualRangeCount); 129 | if (FAILED(hr)) { 130 | wprintf(L"[-] IDWriteFontFace1::GetUnicodeRanges(faceIndex=%u) failed second time with code %d\n", face_it, hr); 131 | bSuccess = FALSE; 132 | 133 | delete[] pRanges; 134 | pFontFace->Release(); 135 | continue; 136 | } 137 | 138 | wprintf(L"[+] Loaded %u unicode ranges from the input font\n", actualRangeCount); 139 | 140 | BitBlt(pBitmapRenderTarget->GetMemoryDC(), 0, 0, RENDER_TARGET_WIDTH, RENDER_TARGET_HEIGHT, NULL, 0, 0, WHITENESS); 141 | 142 | UINT32 code_points[DISPLAYED_GLYPHS_COUNT]; 143 | UINT16 glyph_indices[DISPLAYED_GLYPHS_COUNT]; 144 | DWRITE_GLYPH_METRICS glyph_metrics[DISPLAYED_GLYPHS_COUNT]; 145 | DWORD char_count = 0; 146 | for (UINT32 range_it = 0; range_it < actualRangeCount; range_it++) { 147 | for (UINT32 ch = pRanges[range_it].first; ch <= pRanges[range_it].last; ch++) { 148 | code_points[char_count++] = ch; 149 | 150 | if (char_count >= DISPLAYED_GLYPHS_COUNT || 151 | (range_it == actualRangeCount - 1 && ch == pRanges[range_it].last && char_count > 0)) { 152 | 153 | hr = pFontFace1->GetGlyphIndices(code_points, char_count, glyph_indices); 154 | if (SUCCEEDED(hr)) { 155 | hr = pFontFace->GetDesignGlyphMetrics(glyph_indices, char_count, glyph_metrics, /*isSideways=*/FALSE); 156 | if (FAILED(hr)) { 157 | wprintf(L"[+] IDWriteFontFace::GetDesignGlyphMetrics([%x .. %x]) failed with code %x\n", code_points[0], code_points[char_count - 1], hr); 158 | bSuccess = FALSE; 159 | } 160 | 161 | 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 }; 162 | DWRITE_GLYPH_RUN glyphRun = { pFontFace, ConvertPointSizeToDIP(12.0f), char_count, glyph_indices, NULL, NULL, FALSE, 0 }; 163 | 164 | for (FLOAT point_size : point_sizes) { 165 | glyphRun.fontEmSize = ConvertPointSizeToDIP(point_size); 166 | CONST COLORREF color_ref = RGB(point_size * 2, point_size * 2, point_size * 2); 167 | hr = pBitmapRenderTarget->DrawGlyphRun(point_size, point_size, DWRITE_MEASURING_MODE_NATURAL, &glyphRun, pRenderingParams, color_ref); 168 | 169 | if (FAILED(hr)) { 170 | bSuccess = FALSE; 171 | } 172 | } 173 | } else { 174 | wprintf(L"[-] IDWriteFontFace1::GetGlyphIndices failed with code %x\n", hr); 175 | } 176 | 177 | char_count = 0; 178 | 179 | BitBlt(GetDC(NULL), 0, 0, RENDER_TARGET_WIDTH, RENDER_TARGET_HEIGHT, pBitmapRenderTarget->GetMemoryDC(), 0, 0, SRCCOPY | NOMIRRORBITMAP); 180 | } 181 | } 182 | } 183 | 184 | wprintf(L"[+] Font processing completed\n"); 185 | 186 | delete[] pRanges; 187 | pFontFace->Release(); 188 | } 189 | } while (0); 190 | 191 | if (bSuccess) { 192 | wprintf(L"[+] Font displayed without errors\n"); 193 | } 194 | 195 | if (pFontFile != NULL) { 196 | pFontFile->Release(); 197 | } 198 | 199 | pRenderingParams->Release(); 200 | pBitmapRenderTarget->Release(); 201 | pGdiInterop->Release(); 202 | pDWriteFactory->Release(); 203 | 204 | return 0; 205 | } 206 | 207 | -------------------------------------------------------------------------------- /ttf-otf-mutator/Makefile.linux: -------------------------------------------------------------------------------- 1 | SRC = main.cpp mutator.cpp random.cpp sfnt_font.cpp sfnt_mutator.cpp 2 | DEPS = common.h mutator.h random.h sfnt_font.h sfnt_mutator.h 3 | OBJ = $(SRC:.cpp=.o) 4 | OUT = mutator 5 | CFLAGS = -Wno-multichar 6 | GCC = g++ 7 | 8 | $(OUT): $(OBJ) 9 | $(GCC) $(CFLAGS) $(OBJ) -o $(OUT) 10 | 11 | %.o: %.cpp 12 | $(GCC) $(CFLAGS) $< -c -o $@ 13 | 14 | clean: 15 | rm $(OBJ) $(OUT) 16 | 17 | -------------------------------------------------------------------------------- /ttf-otf-mutator/Makefile.mingw: -------------------------------------------------------------------------------- 1 | SRC = main.cpp mutator.cpp random.cpp sfnt_font.cpp sfnt_mutator.cpp 2 | DEPS = common.h mutator.h random.h sfnt_font.h sfnt_mutator.h 3 | OBJ = $(SRC:.cpp=.o) 4 | OUT = mutator.exe 5 | CFLAGS = -Wno-multichar 6 | GCC = g++ 7 | SHELL = cmd 8 | 9 | $(OUT): $(OBJ) 10 | $(GCC) $(CFLAGS) $(OBJ) -o $(OUT) 11 | 12 | %.o: %.cpp 13 | $(GCC) $(CFLAGS) $< -c -o $@ 14 | 15 | clean: 16 | del $(OBJ) $(OUT) 17 | -------------------------------------------------------------------------------- /ttf-otf-mutator/README.md: -------------------------------------------------------------------------------- 1 | # TTF/OTF mutator 2 | 3 | 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. 4 | 5 | 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: 6 | 7 | **Table**|**Bitflipping**|**Byteflipping**|**Chunkspew**|**Special Ints**|**Binary arithmetic** 8 | :-----:|:-----:|:-----:|:-----:|:-----:|:-----: 9 | `hmtx`|0.1|0.8|0.8|0.8|0.8 10 | `maxp`|0.009766|0.078125|0.125|0.056641|0.0625 11 | `OS/2`|0.1|0.2|0.4|0.2|0.4 12 | `post`|0.004|0.06|0.2|0.15|0.03 13 | `cvt`|0.1|0.1|0.1|0.1|0.1 14 | `fpgm`|0.01|0.01|0.01|0.01|0.01 15 | `glyf`|0.00008|0.00064|0.008|0.00064|0.00064 16 | `prep`|0.01|0.01|0.01|0.01|0.01 17 | `gasp`|0.1|0.1|0.1|0.1|0.1 18 | `CFF`|0.00005|0.0001|0.001|0.0002|0.0001 19 | `EBDT`|0.01|0.08|0.2|0.08|0.08 20 | `EBLC`|0.001|0.001|0.001|0.001|0.001 21 | `EBSC`|0.01|0.01|0.01|0.01|0.01 22 | `GDEF`|0.01|0.01|0.01|0.01|0.01 23 | `GPOS`|0.001|0.008|0.01|0.008|0.008 24 | `GSUB`|0.01|0.08|0.01|0.08|0.08 25 | `hdmx`|0.01|0.01|0.01|0.01|0.01 26 | `kern`|0.01|0.01|0.01|0.01|0.01 27 | `LTSH`|0.01|0.01|0.01|0.01|0.01 28 | `VDMX`|0.01|0.01|0.01|0.01|0.01 29 | `vhea`|0.1|0.1|0.1|0.1|0.1 30 | `vmtx`|0.1|0.1|0.1|0.1|0.1 31 | `mort`|0.01|0.01|0.01|0.01|0.01 32 | 33 | ## Building 34 | 35 | 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. 36 | 37 | ## Usage 38 | 39 | The usage of the tool is very simple: 40 | 41 | ``` 42 | c:\ttf-otf-mutator>mutator 43 | Usage: mutator 44 | 45 | c:\ttf-otf-mutator> 46 | ``` 47 | 48 | For example, to create a mutated `output.ttf` file based on `C:\Windows\Fonts\arial.ttf`, we can run the following command: 49 | 50 | ``` 51 | c:\ttf-otf-mutator>mutator C:\Windows\Fonts\arial.ttf output.ttf 52 | [+] Ignoring table "DSIG" 53 | [+] Mutating table "GDEF" of size 820 54 | [+] Mutating table "GPOS" of size 73976 55 | [+] Mutating table "GSUB" of size 27738 56 | [+] Mutating table "JSTF" of size 30 57 | [+] Mutating table "LTSH" of size 4241 58 | [...] 59 | [+] Ignoring table "loca" 60 | [+] Mutating table "maxp" of size 32 61 | [+] Ignoring table "meta" 62 | [+] Ignoring table "name" 63 | [+] Mutating table "post" of size 32 64 | [+] Mutating table "prep" of size 3119 65 | [+] Font successfully mutated and saved in "output.ttf". 66 | 67 | c:\ttf-otf-mutator> 68 | ``` 69 | 70 | When opened in the default Windows Font Viewer, an example output font may look as follows: 71 | 72 | ![Mutated TrueType font](../images/mutator.png) 73 | -------------------------------------------------------------------------------- /ttf-otf-mutator/common.h: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2018 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #ifndef COMMON_H_ 21 | #define COMMON_H_ 22 | 23 | #if defined(clang) || defined(__GNUC__) 24 | #define SWAP32(value) __builtin_bswap32(value) 25 | #define SWAP16(value) __builtin_bswap16(value) 26 | #elif defined(_MSC_VER) 27 | #define SWAP32(value) _byteswap_ulong(value) 28 | #define SWAP16(value) _byteswap_ushort(value) 29 | #endif 30 | 31 | #endif // COMMON_H_ 32 | 33 | -------------------------------------------------------------------------------- /ttf-otf-mutator/main.cpp: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2018 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #if defined(_MSC_VER) 21 | #define _CRT_SECURE_NO_WARNINGS 22 | #endif // defined(_MSC_VER) 23 | 24 | #include 25 | #include 26 | #include 27 | 28 | #include "common.h" 29 | #include "sfnt_font.h" 30 | #include "sfnt_mutator.h" 31 | 32 | static bool WriteStringToFile(const char *filename, const std::string& data) { 33 | FILE *f = fopen(filename, "w+b"); 34 | if (f == NULL) { 35 | return false; 36 | } 37 | 38 | if (fwrite(data.data(), sizeof(uint8_t), data.size(), f) != data.size()) { 39 | fclose(f); 40 | return false; 41 | } 42 | 43 | fclose(f); 44 | return true; 45 | } 46 | 47 | int main(int argc, char **argv) { 48 | if (argc != 3) { 49 | fprintf(stderr, "Usage: %s \n", argv[0]); 50 | return 1; 51 | } 52 | 53 | const char *input_file_path = argv[1]; 54 | const char *output_file_path = argv[2]; 55 | 56 | SfntFont font; 57 | if (!font.LoadFromFile(input_file_path)) { 58 | fprintf(stderr, "[-] Unable to load the \"%s\" file as a TTF/OTF font.\n", input_file_path); 59 | return 1; 60 | } 61 | 62 | SfntStrategies strategies; 63 | InitSfntMutationStrategies(&strategies); 64 | MutateSfntFile(&strategies, &font); 65 | 66 | std::string output_data; 67 | if (!font.SaveToString(&output_data)) { 68 | fprintf(stderr, "[-] Unable to save a SFNT font to a string.\n"); 69 | return 1; 70 | } 71 | 72 | if (!WriteStringToFile(output_file_path, output_data)) { 73 | fprintf(stderr, "[-] Unable to save the output font to \"%s\".\n", output_file_path); 74 | return 1; 75 | } 76 | 77 | printf("[+] Font successfully mutated and saved in \"%s\".\n", output_file_path); 78 | 79 | return 0; 80 | } 81 | -------------------------------------------------------------------------------- /ttf-otf-mutator/mutator.cpp: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2018 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #include "mutator.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "random.h" 29 | 30 | namespace globals { 31 | 32 | } // globals 33 | 34 | void Mutator::MutateString(const std::vector& strategies, std::string *buffer, unsigned int *changed_bytes) { 35 | std::vector strats = strategies; 36 | 37 | // Randomly choose the strategies we are going to use for mutation. 38 | const unsigned int mutations_no = (Random32() % strats.size()) + 1; 39 | 40 | random_shuffle(strats.begin(), strats.end()); 41 | strats.resize(mutations_no); 42 | 43 | // Choose the mutation ratios from available ranges. 44 | std::vector mutation_ratios; 45 | for (unsigned int i = 0; i < strats.size(); i++) { 46 | const double min_ratio = strats[i].min_mutation_ratio; 47 | const double max_ratio = strats[i].max_mutation_ratio; 48 | const double ratio = min_ratio + fmod(RandomFloat(), max_ratio - min_ratio + 1e-10); 49 | mutation_ratios.push_back(ratio); 50 | } 51 | 52 | // Choose how much each mutation will affect the result. 53 | std::vector division; 54 | for (unsigned int i = 0; i < strats.size() - 1; i++) { 55 | division.push_back(RandomFloat()); 56 | } 57 | division.push_back(0.0f); 58 | division.push_back(1.0f); 59 | sort(division.begin(), division.end()); 60 | 61 | for (unsigned int i = 1; i < division.size(); i++) { 62 | mutation_ratios[i - 1] *= (division[i] - division[i - 1]); 63 | } 64 | 65 | // Invoke each mutation strategy over the string. 66 | *changed_bytes = 0; 67 | for (unsigned int i = 0; i < strats.size(); i++) { 68 | unsigned int locally_changed_bytes; 69 | MutateString(strats[i].type, mutation_ratios[i], buffer, &locally_changed_bytes); 70 | *changed_bytes += locally_changed_bytes; 71 | } 72 | } 73 | 74 | void Mutator::MutateString(MutationType type, double ratio, std::string *buffer, unsigned int *changed_bytes) { 75 | switch (type) { 76 | case MUTATION_BITFLIPPING: 77 | Bitflipping(buffer, ratio, changed_bytes); 78 | break; 79 | 80 | case MUTATION_BYTEFLIPPING: 81 | Byteflipping(buffer, ratio, changed_bytes); 82 | break; 83 | 84 | case MUTATION_CHUNKSPEW: 85 | Chunkspew(buffer, ratio, changed_bytes); 86 | break; 87 | 88 | case MUTATION_SPECIAL_INTS: 89 | SpecialInts(buffer, ratio, changed_bytes); 90 | break; 91 | 92 | case MUTATION_ADD_SUB_BINARY: 93 | AddSubBinary(buffer, ratio, changed_bytes); 94 | break; 95 | 96 | default: 97 | break; 98 | } 99 | } 100 | 101 | void Mutator::Bitflipping(std::string *buffer, double ratio, unsigned int *changed_bytes) { 102 | uint32_t bytes_to_mutate = ((double)buffer->size() * ratio) * 8; 103 | *changed_bytes = bytes_to_mutate; 104 | 105 | while (bytes_to_mutate-- > 0) { 106 | uint32_t offset = (Random32() % buffer->size()); 107 | const unsigned int bit = (Random32() & 7); 108 | buffer->at(offset) ^= (1 << bit); 109 | } 110 | } 111 | 112 | void Mutator::Byteflipping(std::string *buffer, double ratio, unsigned int *changed_bytes) { 113 | uint32_t bytes_to_mutate = ((double)buffer->size() * ratio); 114 | *changed_bytes = bytes_to_mutate; 115 | 116 | while (bytes_to_mutate-- > 0) { 117 | const uint32_t offset = (Random32() % buffer->size()); 118 | buffer->at(offset) = (Random32() & 0xff); 119 | } 120 | } 121 | 122 | void Mutator::Chunkspew(std::string *buffer, double ratio, unsigned int *changed_bytes) { 123 | const unsigned int kMaximumChunkSpew = 64; 124 | const uint32_t bytes_to_mutate = ((double)buffer->size() * ratio); 125 | uint32_t mutated_bytes = 0; 126 | 127 | uint8_t chunk_buffer[kMaximumChunkSpew]; 128 | while (mutated_bytes < bytes_to_mutate) { 129 | const uint32_t mutation_size = (Random32() % MIN3(kMaximumChunkSpew, buffer->size() / 2, bytes_to_mutate - mutated_bytes + 1)); 130 | const uint32_t source_offset = (Random32() % (buffer->size() - mutation_size)); 131 | const uint32_t dest_offset = (Random32() % (buffer->size() - mutation_size)); 132 | 133 | memcpy(chunk_buffer, &buffer->data()[source_offset], mutation_size); 134 | buffer->replace(dest_offset, mutation_size, reinterpret_cast(chunk_buffer), mutation_size); 135 | mutated_bytes += mutation_size; 136 | } 137 | 138 | *changed_bytes = mutated_bytes; 139 | } 140 | 141 | void Mutator::SpecialInts(std::string *buffer, double ratio, unsigned int *changed_bytes) { 142 | const struct { 143 | size_t size; 144 | const char *data; 145 | } kSpecialBinaryInts[] = { 146 | {1, "\x00"}, {1, "\x7f"}, {1, "\x80"}, {1, "\xff"}, 147 | {2, "\x00\x00"}, {2, "\x7f\xff"}, {2, "\xff\xff"}, {2, "\x80\x00"}, 148 | {2, "\x40\x00"}, {2, "\xc0\x00"}, 149 | {4, "\x00\x00\x00\x00"}, {4, "\x7f\xff\xff\xff"}, {4, "\x80\x00\x00\x00"}, 150 | {4, "\x40\x00\x00\x00"}, {4, "\xc0\x00\x00\x00"}, {4, "\xff\xff\xff\xff"} 151 | }; 152 | const uint32_t bytes_to_mutate = ((double)buffer->size() * ratio); 153 | uint32_t mutated_bytes = 0; 154 | 155 | while (mutated_bytes < bytes_to_mutate) { 156 | const unsigned int int_idx = (Random32() % (sizeof(kSpecialBinaryInts) / sizeof(kSpecialBinaryInts[0]))); 157 | const unsigned int int_size = kSpecialBinaryInts[int_idx].size; 158 | uint32_t offset = (Random32() % (buffer->size() - int_size + 1)); 159 | buffer->replace(offset, int_size, kSpecialBinaryInts[int_idx].data, int_size); 160 | mutated_bytes += int_size; 161 | } 162 | 163 | *changed_bytes = mutated_bytes; 164 | } 165 | 166 | void Mutator::AddSubBinary(std::string *buffer, double ratio, unsigned int *changed_bytes) { 167 | const uint32_t bytes_to_mutate = ((double)buffer->size() * ratio); 168 | uint32_t mutated_bytes = 0; 169 | 170 | while (mutated_bytes < bytes_to_mutate) { 171 | const uint32_t spec = Random32(); 172 | const bool big_endian = ((spec & 1) == 1); 173 | const bool addition = ((spec & 2) == 2); 174 | unsigned int unit_width; 175 | uint32_t max_value; 176 | 177 | // Decide on the unit width. 178 | switch ((spec >> 2) % 3) { 179 | case 0: // Byte arithmetic. 180 | unit_width = 1; 181 | max_value = UCHAR_MAX; 182 | break; 183 | case 1: // Word arithmetic. 184 | unit_width = 2; 185 | max_value = USHRT_MAX; 186 | break; 187 | case 2: // Dword arithmetic. 188 | unit_width = 4; 189 | max_value = UINT_MAX; 190 | break; 191 | } 192 | 193 | // Choose offset. 194 | if (buffer->size() < unit_width) { 195 | continue; 196 | } 197 | const uint32_t offset = (Random32() % (buffer->size() - unit_width + 1)); 198 | 199 | // Read the value. 200 | uint32_t value = 0; 201 | if (big_endian) { 202 | for (unsigned int i = 0; i < unit_width; ++i) { 203 | value |= (buffer->at(offset + i) << (8 * ((unit_width - 1) - i))); 204 | } 205 | } else { 206 | for (unsigned int i = 0; i < unit_width; ++i) { 207 | value |= (buffer->at(offset + i) << (8 * i)); 208 | } 209 | } 210 | 211 | // Choose an operand. 212 | uint32_t op; 213 | switch (unit_width) { 214 | case 1: 215 | op = 1 + (Random32() % UCHAR_MAX); 216 | break; 217 | case 2: 218 | case 4: 219 | if (value > UCHAR_MAX && (Random32() & 1) == 1) { 220 | op = 1 + (Random32() % USHRT_MAX); 221 | } else { 222 | op = 1 + (Random32() % UCHAR_MAX); 223 | } 224 | break; 225 | } 226 | 227 | // Perform the arithmetic. 228 | if (addition) { 229 | if (max_value - value < op) { 230 | value = max_value; 231 | } else { 232 | value += op; 233 | } 234 | } else { 235 | if (value < op) { 236 | value = 0; 237 | } else { 238 | value -= op; 239 | } 240 | } 241 | 242 | // Write the data back to buffer and count byte diffs. 243 | unsigned int byte_delta = 0; 244 | if (big_endian) { 245 | for (unsigned int i = 0; i < unit_width; ++i) { 246 | uint8_t byte = (value >> (8 * ((unit_width - 1) - i))); 247 | if (buffer->at(offset + i) != byte) { 248 | buffer->at(offset + i) = byte; 249 | byte_delta++; 250 | } 251 | } 252 | } else { 253 | for (unsigned int i = 0; i < unit_width; ++i) { 254 | uint8_t byte = (value >> (8 * i)); 255 | if (buffer->at(offset + i) != byte) { 256 | buffer->at(offset + i) = byte; 257 | byte_delta++; 258 | } 259 | } 260 | } 261 | 262 | // Update the mutation progress made so far. 263 | mutated_bytes += byte_delta; 264 | } 265 | 266 | *changed_bytes = mutated_bytes; 267 | } 268 | 269 | -------------------------------------------------------------------------------- /ttf-otf-mutator/mutator.h: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2018 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #ifndef MUTATOR_H_ 21 | #define MUTATOR_H_ 22 | 23 | #include 24 | #include 25 | 26 | #define MIN3(a, b, c) ((((a) < (b) ? (a) : (b)) < (c)) ? ((a) < (b) ? (a) : (b)) : (c)) 27 | 28 | enum MutationType { 29 | MUTATION_BITFLIPPING, 30 | MUTATION_BYTEFLIPPING, 31 | MUTATION_CHUNKSPEW, 32 | MUTATION_SPECIAL_INTS, 33 | MUTATION_ADD_SUB_BINARY 34 | }; 35 | 36 | struct MutationStrategy { 37 | enum MutationType type; 38 | double min_mutation_ratio; 39 | double max_mutation_ratio; 40 | }; 41 | 42 | class Mutator { 43 | public: 44 | static void MutateString(const std::vector& strategies, std::string *buffer, unsigned int *changed_bytes); 45 | static void MutateString(MutationType type, double ratio, std::string *buffer, unsigned int *changed_bytes); 46 | 47 | private: 48 | static void Bitflipping(std::string *buffer, double ratio, unsigned int *changed_bytes); 49 | static void Byteflipping(std::string *buffer, double ratio, unsigned int *changed_bytes); 50 | static void Chunkspew(std::string *buffer, double ratio, unsigned int *changed_bytes); 51 | static void SpecialInts(std::string *buffer, double ratio, unsigned int *changed_bytes); 52 | static void AddSubBinary(std::string *buffer, double ratio, unsigned int *changed_bytes); 53 | }; 54 | 55 | #endif // MUTATOR_H_ 56 | -------------------------------------------------------------------------------- /ttf-otf-mutator/random.cpp: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2018 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #include "random.h" 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | namespace globals { 29 | std::mt19937 generator; 30 | std::uniform_int_distribution int_distrib(0, UINT_MAX); 31 | std::uniform_real_distribution real_distrib(0.0, 1.0); 32 | } // namespace globals 33 | 34 | static void EnsureSeeded() { 35 | static bool seeded = false; 36 | if (!seeded) { 37 | std::random_device rd; 38 | globals::generator.seed(rd()); 39 | 40 | seeded = true; 41 | } 42 | } 43 | 44 | uint32_t Random32() { 45 | EnsureSeeded(); 46 | return globals::int_distrib(globals::generator); 47 | } 48 | 49 | double RandomFloat() { 50 | EnsureSeeded(); 51 | return globals::real_distrib(globals::generator); 52 | } 53 | -------------------------------------------------------------------------------- /ttf-otf-mutator/random.h: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2018 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #ifndef RANDOM_H_ 21 | #define RANDOM_H_ 22 | 23 | #include 24 | 25 | uint32_t Random32(); 26 | double RandomFloat(); 27 | 28 | #endif // RANDOM_H_ 29 | -------------------------------------------------------------------------------- /ttf-otf-mutator/sfnt_font.cpp: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2018 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #if defined(_MSC_VER) 21 | #define _CRT_SECURE_NO_WARNINGS 22 | #endif // defined(_MSC_VER) 23 | 24 | #include "sfnt_font.h" 25 | 26 | #include 27 | 28 | #include "common.h" 29 | 30 | SfntFont::SfntFont(const SfntFont& font) { 31 | sfnt_version_ = font.sfnt_version_; 32 | sfnt_tables_ = font.sfnt_tables_; 33 | } 34 | 35 | bool SfntFont::LoadFromFile(const char *file_path) { 36 | // Open the file. 37 | FILE *f = fopen(file_path, "rb"); 38 | if (f == NULL) { 39 | goto error; 40 | } 41 | 42 | // Read the file header, but only save the SFNT version; other elements will 43 | // be recalculated while writing the output file. 44 | SfntHeader hdr; 45 | uint16_t num_tables; 46 | if (fread(&hdr, sizeof(SfntHeader), 1, f) != 1) { 47 | goto error; 48 | } 49 | 50 | sfnt_version_ = hdr.version; 51 | 52 | num_tables = SWAP16(hdr.num_tables); 53 | sfnt_tables_.resize(num_tables); 54 | 55 | // Read the table headers. 56 | for (uint32_t i = 0; i < num_tables; i++) { 57 | if (fseek(f, sizeof(SfntHeader) + i * sizeof(SfntTableHeader), SEEK_SET) != 0) { 58 | goto error; 59 | } 60 | 61 | SfntTableHeader table_hdr; 62 | if (fread(&table_hdr, sizeof(SfntTableHeader), 1, f) != 1) { 63 | goto error; 64 | } 65 | 66 | uint32_t table_offset = SWAP32(table_hdr.offset); 67 | uint32_t table_length = SWAP32(table_hdr.length); 68 | 69 | void *buffer = malloc(table_length); 70 | if (buffer == NULL) { 71 | goto error; 72 | } 73 | 74 | if (fseek(f, table_offset, SEEK_SET) != 0) { 75 | free(buffer); 76 | goto error; 77 | } 78 | 79 | if (fread(buffer, sizeof(uint8_t), table_length, f) != table_length) { 80 | free(buffer); 81 | goto error; 82 | } 83 | 84 | sfnt_tables_[i].tag = SWAP32(table_hdr.tag); 85 | sfnt_tables_[i].data.assign((char *)buffer, table_length); 86 | 87 | free(buffer); 88 | } 89 | 90 | // Close the file. 91 | fclose(f); 92 | f = NULL; 93 | 94 | return true; 95 | 96 | error: 97 | if (f != NULL) { 98 | fclose(f); 99 | } 100 | sfnt_version_ = 0; 101 | sfnt_tables_.clear(); 102 | return false; 103 | } 104 | 105 | bool SfntFont::SaveToFile(const char *file_path) { 106 | // Save the font to a string. 107 | std::string font_contents; 108 | if (!SaveToString(&font_contents)) { 109 | return false; 110 | } 111 | 112 | // Open the file. 113 | FILE *f = fopen(file_path, "w+b"); 114 | if (f == NULL) { 115 | return false; 116 | } 117 | 118 | if (fwrite(font_contents.data(), sizeof(uint8_t), font_contents.size(), f) != font_contents.size()) { 119 | fclose(f); 120 | return false; 121 | } 122 | 123 | // Close the file. 124 | fclose(f); 125 | 126 | return true; 127 | } 128 | 129 | bool SfntFont::SaveToString(std::string *buffer) { 130 | buffer->clear(); 131 | 132 | // Craft and write the file header. 133 | SfntHeader hdr; 134 | hdr.version = sfnt_version_; 135 | hdr.num_tables = SWAP16(sfnt_tables_.size()); 136 | hdr.search_range = 1; 137 | hdr.entry_selector = 0; 138 | while (2 * hdr.search_range <= sfnt_tables_.size()) { 139 | hdr.search_range *= 2; 140 | hdr.entry_selector++; 141 | } 142 | hdr.search_range = SWAP16(16 * hdr.search_range); 143 | hdr.entry_selector = SWAP16(hdr.entry_selector); 144 | hdr.range_shift = SWAP16((sfnt_tables_.size() * 16) - SWAP16(hdr.search_range)); 145 | 146 | buffer->append((char *)&hdr, sizeof(SfntHeader)); 147 | 148 | // Craft and write the table headers. 149 | uint32_t curr_offset = sizeof(SfntHeader) + sfnt_tables_.size() * sizeof(SfntTableHeader); 150 | std::vector table_offsets; 151 | for (unsigned int i = 0; i < sfnt_tables_.size(); i++) { 152 | SfntTableHeader table_hdr; 153 | table_hdr.tag = SWAP32(sfnt_tables_[i].tag); 154 | table_hdr.checksum = SWAP32(CalculateChecksum(sfnt_tables_[i].data)); 155 | table_hdr.offset = SWAP32(curr_offset); 156 | table_hdr.length = SWAP32(sfnt_tables_[i].data.size()); 157 | 158 | // Account for the padding when calculating table data offset. 159 | curr_offset += ((sfnt_tables_[i].data.size() + 3) & (-4)); 160 | 161 | buffer->append((char *)&table_hdr, sizeof(SfntTableHeader)); 162 | } 163 | 164 | // Write the actual data segments to the file. 165 | for (unsigned int i = 0; i < sfnt_tables_.size(); i++) { 166 | buffer->append(sfnt_tables_[i].data.data(), sfnt_tables_[i].data.size()); 167 | 168 | // Insert padding bytes if necessary. 169 | if (sfnt_tables_[i].data.size() & 3) { 170 | buffer->append(4 - (sfnt_tables_[i].data.size() & 3), '\0'); 171 | } 172 | } 173 | return true; 174 | } 175 | 176 | uint32_t SfntFont::CalculateChecksum(const std::string& data) { 177 | uint32_t sum = 0, i; 178 | 179 | for (i = 0; i + 3 < data.size(); i += 4) { 180 | sum += SWAP32(*(uint32_t *)&data[i]); 181 | } 182 | 183 | if (i != data.size()) { 184 | char buf[4] = {0}; 185 | for (uint32_t j = 0; i < data.size(); i++, j++) { 186 | buf[j] = data[i]; 187 | } 188 | sum += SWAP32(*(uint32_t *)&buf[0]); 189 | } 190 | 191 | return sum; 192 | } 193 | 194 | -------------------------------------------------------------------------------- /ttf-otf-mutator/sfnt_font.h: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2018 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #ifndef SFNT_FONT_H_ 21 | #define SFNT_FONT_H_ 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | #pragma pack(push, 1) 29 | struct SfntHeader { 30 | uint32_t version; 31 | uint16_t num_tables; 32 | uint16_t search_range; 33 | uint16_t entry_selector; 34 | uint16_t range_shift; 35 | }; 36 | 37 | struct SfntTableHeader { 38 | uint32_t tag; 39 | uint32_t checksum; 40 | uint32_t offset; 41 | uint32_t length; 42 | }; 43 | #pragma pack(pop) 44 | 45 | struct SfntTable { 46 | uint32_t tag; 47 | std::string data; 48 | }; 49 | 50 | class SfntFont { 51 | public: 52 | SfntFont() : sfnt_version_(0) { } 53 | SfntFont(const SfntFont& font); 54 | ~SfntFont() { } 55 | 56 | bool LoadFromFile(const char *file_path); 57 | bool SaveToFile(const char *file_path); 58 | bool SaveToString(std::string *buffer); 59 | 60 | uint32_t sfnt_version_; 61 | std::vector sfnt_tables_; 62 | 63 | private: 64 | uint32_t CalculateChecksum(const std::string& data); 65 | }; 66 | 67 | #endif // SFNT_FONT_H_ 68 | 69 | -------------------------------------------------------------------------------- /ttf-otf-mutator/sfnt_mutator.cpp: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2018 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #include "sfnt_mutator.h" 21 | 22 | #include 23 | 24 | #include "common.h" 25 | #include "mutator.h" 26 | #include "sfnt_font.h" 27 | 28 | void InitSfntMutationStrategies(SfntStrategies *strategies) { 29 | MutationStrategy strat; 30 | 31 | #define ADD_MUTATION_STRATEGY(tag, mutation_type, mutation_ratio) {\ 32 | strat.type = mutation_type;\ 33 | strat.min_mutation_ratio = 0.0;\ 34 | strat.max_mutation_ratio = 2.0 * mutation_ratio;\ 35 | (*strategies)[tag].push_back(strat);\ 36 | } 37 | 38 | ADD_MUTATION_STRATEGY('hmtx', MUTATION_BITFLIPPING, 0.1); 39 | ADD_MUTATION_STRATEGY('hmtx', MUTATION_BYTEFLIPPING, 0.8); 40 | ADD_MUTATION_STRATEGY('hmtx', MUTATION_CHUNKSPEW, 0.8); 41 | ADD_MUTATION_STRATEGY('hmtx', MUTATION_SPECIAL_INTS, 0.8); 42 | ADD_MUTATION_STRATEGY('hmtx', MUTATION_ADD_SUB_BINARY, 0.8); 43 | 44 | ADD_MUTATION_STRATEGY('maxp', MUTATION_BITFLIPPING, 0.009766); 45 | ADD_MUTATION_STRATEGY('maxp', MUTATION_BYTEFLIPPING, 0.078125); 46 | ADD_MUTATION_STRATEGY('maxp', MUTATION_CHUNKSPEW, 0.125); 47 | ADD_MUTATION_STRATEGY('maxp', MUTATION_SPECIAL_INTS, 0.056641); 48 | ADD_MUTATION_STRATEGY('maxp', MUTATION_ADD_SUB_BINARY, 0.0625); 49 | 50 | ADD_MUTATION_STRATEGY('OS/2', MUTATION_BITFLIPPING, 0.1); 51 | ADD_MUTATION_STRATEGY('OS/2', MUTATION_BYTEFLIPPING, 0.2); 52 | ADD_MUTATION_STRATEGY('OS/2', MUTATION_CHUNKSPEW, 0.4); 53 | ADD_MUTATION_STRATEGY('OS/2', MUTATION_SPECIAL_INTS, 0.2); 54 | ADD_MUTATION_STRATEGY('OS/2', MUTATION_ADD_SUB_BINARY, 0.4); 55 | 56 | ADD_MUTATION_STRATEGY('post', MUTATION_BITFLIPPING, 0.004); 57 | ADD_MUTATION_STRATEGY('post', MUTATION_BYTEFLIPPING, 0.06); 58 | ADD_MUTATION_STRATEGY('post', MUTATION_CHUNKSPEW, 0.2); 59 | ADD_MUTATION_STRATEGY('post', MUTATION_SPECIAL_INTS, 0.15); 60 | ADD_MUTATION_STRATEGY('post', MUTATION_ADD_SUB_BINARY, 0.03); 61 | 62 | ADD_MUTATION_STRATEGY('cvt ', MUTATION_BITFLIPPING, 0.1); 63 | ADD_MUTATION_STRATEGY('cvt ', MUTATION_BYTEFLIPPING, 0.1); 64 | ADD_MUTATION_STRATEGY('cvt ', MUTATION_CHUNKSPEW, 0.1); 65 | ADD_MUTATION_STRATEGY('cvt ', MUTATION_SPECIAL_INTS, 0.1); 66 | ADD_MUTATION_STRATEGY('cvt ', MUTATION_ADD_SUB_BINARY, 0.1); 67 | 68 | ADD_MUTATION_STRATEGY('fpgm', MUTATION_BITFLIPPING, 0.1); 69 | ADD_MUTATION_STRATEGY('fpgm', MUTATION_BYTEFLIPPING, 0.1); 70 | ADD_MUTATION_STRATEGY('fpgm', MUTATION_CHUNKSPEW, 0.1); 71 | ADD_MUTATION_STRATEGY('fpgm', MUTATION_SPECIAL_INTS, 0.1); 72 | ADD_MUTATION_STRATEGY('fpgm', MUTATION_ADD_SUB_BINARY, 0.1); 73 | 74 | ADD_MUTATION_STRATEGY('glyf', MUTATION_BITFLIPPING, 0.00008); 75 | ADD_MUTATION_STRATEGY('glyf', MUTATION_BYTEFLIPPING, 0.00064); 76 | ADD_MUTATION_STRATEGY('glyf', MUTATION_CHUNKSPEW, 0.008); 77 | ADD_MUTATION_STRATEGY('glyf', MUTATION_SPECIAL_INTS, 0.00064); 78 | ADD_MUTATION_STRATEGY('glyf', MUTATION_ADD_SUB_BINARY, 0.00064); 79 | 80 | ADD_MUTATION_STRATEGY('prep', MUTATION_BITFLIPPING, 0.01); 81 | ADD_MUTATION_STRATEGY('prep', MUTATION_BYTEFLIPPING, 0.01); 82 | ADD_MUTATION_STRATEGY('prep', MUTATION_CHUNKSPEW, 0.01); 83 | ADD_MUTATION_STRATEGY('prep', MUTATION_SPECIAL_INTS, 0.01); 84 | ADD_MUTATION_STRATEGY('prep', MUTATION_ADD_SUB_BINARY, 0.01); 85 | 86 | ADD_MUTATION_STRATEGY('gasp', MUTATION_BITFLIPPING, 0.1); 87 | ADD_MUTATION_STRATEGY('gasp', MUTATION_BYTEFLIPPING, 0.1); 88 | ADD_MUTATION_STRATEGY('gasp', MUTATION_CHUNKSPEW, 0.1); 89 | ADD_MUTATION_STRATEGY('gasp', MUTATION_SPECIAL_INTS, 0.1); 90 | ADD_MUTATION_STRATEGY('gasp', MUTATION_ADD_SUB_BINARY, 0.1); 91 | 92 | ADD_MUTATION_STRATEGY('CFF ', MUTATION_BITFLIPPING, 0.00005); 93 | ADD_MUTATION_STRATEGY('CFF ', MUTATION_BYTEFLIPPING, 0.0001); 94 | ADD_MUTATION_STRATEGY('CFF ', MUTATION_CHUNKSPEW, 0.001); 95 | ADD_MUTATION_STRATEGY('CFF ', MUTATION_SPECIAL_INTS, 0.0002); 96 | ADD_MUTATION_STRATEGY('CFF ', MUTATION_ADD_SUB_BINARY, 0.0001); 97 | 98 | ADD_MUTATION_STRATEGY('EBDT', MUTATION_BITFLIPPING, 0.01); 99 | ADD_MUTATION_STRATEGY('EBDT', MUTATION_BYTEFLIPPING, 0.08); 100 | ADD_MUTATION_STRATEGY('EBDT', MUTATION_CHUNKSPEW, 0.2); 101 | ADD_MUTATION_STRATEGY('EBDT', MUTATION_SPECIAL_INTS, 0.08); 102 | ADD_MUTATION_STRATEGY('EBDT', MUTATION_ADD_SUB_BINARY, 0.08); 103 | 104 | ADD_MUTATION_STRATEGY('EBLC', MUTATION_BITFLIPPING, 0.001); 105 | ADD_MUTATION_STRATEGY('EBLC', MUTATION_BYTEFLIPPING, 0.001); 106 | ADD_MUTATION_STRATEGY('EBLC', MUTATION_CHUNKSPEW, 0.001); 107 | ADD_MUTATION_STRATEGY('EBLC', MUTATION_SPECIAL_INTS, 0.001); 108 | ADD_MUTATION_STRATEGY('EBLC', MUTATION_ADD_SUB_BINARY, 0.001); 109 | 110 | ADD_MUTATION_STRATEGY('EBSC', MUTATION_BITFLIPPING, 0.01); 111 | ADD_MUTATION_STRATEGY('EBSC', MUTATION_BYTEFLIPPING, 0.01); 112 | ADD_MUTATION_STRATEGY('EBSC', MUTATION_CHUNKSPEW, 0.01); 113 | ADD_MUTATION_STRATEGY('EBSC', MUTATION_SPECIAL_INTS, 0.01); 114 | ADD_MUTATION_STRATEGY('EBSC', MUTATION_ADD_SUB_BINARY, 0.01); 115 | 116 | ADD_MUTATION_STRATEGY('BASE', MUTATION_BITFLIPPING, 0.01); 117 | ADD_MUTATION_STRATEGY('BASE', MUTATION_BYTEFLIPPING, 0.01); 118 | ADD_MUTATION_STRATEGY('BASE', MUTATION_CHUNKSPEW, 0.01); 119 | ADD_MUTATION_STRATEGY('BASE', MUTATION_SPECIAL_INTS, 0.01); 120 | ADD_MUTATION_STRATEGY('BASE', MUTATION_ADD_SUB_BINARY, 0.01); 121 | 122 | ADD_MUTATION_STRATEGY('GDEF', MUTATION_BITFLIPPING, 0.01); 123 | ADD_MUTATION_STRATEGY('GDEF', MUTATION_BYTEFLIPPING, 0.01); 124 | ADD_MUTATION_STRATEGY('GDEF', MUTATION_CHUNKSPEW, 0.01); 125 | ADD_MUTATION_STRATEGY('GDEF', MUTATION_SPECIAL_INTS, 0.01); 126 | ADD_MUTATION_STRATEGY('GDEF', MUTATION_ADD_SUB_BINARY, 0.01); 127 | 128 | ADD_MUTATION_STRATEGY('GPOS', MUTATION_BITFLIPPING, 0.001); 129 | ADD_MUTATION_STRATEGY('GPOS', MUTATION_BYTEFLIPPING, 0.008); 130 | ADD_MUTATION_STRATEGY('GPOS', MUTATION_CHUNKSPEW, 0.01); 131 | ADD_MUTATION_STRATEGY('GPOS', MUTATION_SPECIAL_INTS, 0.008); 132 | ADD_MUTATION_STRATEGY('GPOS', MUTATION_ADD_SUB_BINARY, 0.008); 133 | 134 | ADD_MUTATION_STRATEGY('GSUB', MUTATION_BITFLIPPING, 0.01); 135 | ADD_MUTATION_STRATEGY('GSUB', MUTATION_BYTEFLIPPING, 0.08); 136 | ADD_MUTATION_STRATEGY('GSUB', MUTATION_CHUNKSPEW, 0.01); 137 | ADD_MUTATION_STRATEGY('GSUB', MUTATION_SPECIAL_INTS, 0.08); 138 | ADD_MUTATION_STRATEGY('GSUB', MUTATION_ADD_SUB_BINARY, 0.08); 139 | 140 | ADD_MUTATION_STRATEGY('JSTF', MUTATION_BITFLIPPING, 0.01); 141 | ADD_MUTATION_STRATEGY('JSTF', MUTATION_BYTEFLIPPING, 0.01); 142 | ADD_MUTATION_STRATEGY('JSTF', MUTATION_CHUNKSPEW, 0.01); 143 | ADD_MUTATION_STRATEGY('JSTF', MUTATION_SPECIAL_INTS, 0.01); 144 | ADD_MUTATION_STRATEGY('JSTF', MUTATION_ADD_SUB_BINARY, 0.01); 145 | 146 | ADD_MUTATION_STRATEGY('hdmx', MUTATION_BITFLIPPING, 0.01); 147 | ADD_MUTATION_STRATEGY('hdmx', MUTATION_BYTEFLIPPING, 0.01); 148 | ADD_MUTATION_STRATEGY('hdmx', MUTATION_CHUNKSPEW, 0.01); 149 | ADD_MUTATION_STRATEGY('hdmx', MUTATION_SPECIAL_INTS, 0.01); 150 | ADD_MUTATION_STRATEGY('hdmx', MUTATION_ADD_SUB_BINARY, 0.01); 151 | 152 | ADD_MUTATION_STRATEGY('kern', MUTATION_BITFLIPPING, 0.01); 153 | ADD_MUTATION_STRATEGY('kern', MUTATION_BYTEFLIPPING, 0.01); 154 | ADD_MUTATION_STRATEGY('kern', MUTATION_CHUNKSPEW, 0.01); 155 | ADD_MUTATION_STRATEGY('kern', MUTATION_SPECIAL_INTS, 0.01); 156 | ADD_MUTATION_STRATEGY('kern', MUTATION_ADD_SUB_BINARY, 0.01); 157 | 158 | ADD_MUTATION_STRATEGY('LTSH', MUTATION_BITFLIPPING, 0.01); 159 | ADD_MUTATION_STRATEGY('LTSH', MUTATION_BYTEFLIPPING, 0.01); 160 | ADD_MUTATION_STRATEGY('LTSH', MUTATION_CHUNKSPEW, 0.01); 161 | ADD_MUTATION_STRATEGY('LTSH', MUTATION_SPECIAL_INTS, 0.01); 162 | ADD_MUTATION_STRATEGY('LTSH', MUTATION_ADD_SUB_BINARY, 0.01); 163 | 164 | ADD_MUTATION_STRATEGY('VDMX', MUTATION_BITFLIPPING, 0.01); 165 | ADD_MUTATION_STRATEGY('VDMX', MUTATION_BYTEFLIPPING, 0.01); 166 | ADD_MUTATION_STRATEGY('VDMX', MUTATION_CHUNKSPEW, 0.01); 167 | ADD_MUTATION_STRATEGY('VDMX', MUTATION_SPECIAL_INTS, 0.01); 168 | ADD_MUTATION_STRATEGY('VDMX', MUTATION_ADD_SUB_BINARY, 0.01); 169 | 170 | ADD_MUTATION_STRATEGY('vhea', MUTATION_BITFLIPPING, 0.1); 171 | ADD_MUTATION_STRATEGY('vhea', MUTATION_BYTEFLIPPING, 0.1); 172 | ADD_MUTATION_STRATEGY('vhea', MUTATION_CHUNKSPEW, 0.1); 173 | ADD_MUTATION_STRATEGY('vhea', MUTATION_SPECIAL_INTS, 0.1); 174 | ADD_MUTATION_STRATEGY('vhea', MUTATION_ADD_SUB_BINARY, 0.1); 175 | 176 | ADD_MUTATION_STRATEGY('vmtx', MUTATION_BITFLIPPING, 0.1); 177 | ADD_MUTATION_STRATEGY('vmtx', MUTATION_BYTEFLIPPING, 0.1); 178 | ADD_MUTATION_STRATEGY('vmtx', MUTATION_CHUNKSPEW, 0.1); 179 | ADD_MUTATION_STRATEGY('vmtx', MUTATION_SPECIAL_INTS, 0.1); 180 | ADD_MUTATION_STRATEGY('vmtx', MUTATION_ADD_SUB_BINARY, 0.1); 181 | 182 | ADD_MUTATION_STRATEGY('mort', MUTATION_BITFLIPPING, 0.01); 183 | ADD_MUTATION_STRATEGY('mort', MUTATION_BYTEFLIPPING, 0.01); 184 | ADD_MUTATION_STRATEGY('mort', MUTATION_CHUNKSPEW, 0.01); 185 | ADD_MUTATION_STRATEGY('mort', MUTATION_SPECIAL_INTS, 0.01); 186 | ADD_MUTATION_STRATEGY('mort', MUTATION_ADD_SUB_BINARY, 0.01); 187 | } 188 | 189 | void MutateSfntFile(SfntStrategies *strategies, SfntFont *font) { 190 | for (unsigned int i = 0; i < font->sfnt_tables_.size(); i++) { 191 | uint32_t tag = SWAP32(font->sfnt_tables_[i].tag); 192 | 193 | char tag_name[5]; 194 | memcpy(&tag_name[0], &tag, sizeof(uint32_t)); 195 | tag_name[4] = '\0'; 196 | 197 | if (strategies->find(font->sfnt_tables_[i].tag) != strategies->end()) { 198 | printf("[+] Mutating table \"%s\" of size %d\n", tag_name, (int)font->sfnt_tables_[i].data.size()); 199 | 200 | unsigned int changed_bytes; 201 | Mutator::MutateString((*strategies)[font->sfnt_tables_[i].tag], 202 | &font->sfnt_tables_[i].data, &changed_bytes); 203 | } else { 204 | printf("[+] Ignoring table \"%s\"\n", tag_name); 205 | } 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /ttf-otf-mutator/sfnt_mutator.h: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2018 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #ifndef SFNT_MUTATOR_H_ 21 | #define SFNT_MUTATOR_H_ 22 | 23 | #include 24 | #include 25 | #include 26 | 27 | #include "mutator.h" 28 | #include "sfnt_font.h" 29 | 30 | typedef std::map > SfntStrategies; 31 | 32 | void InitSfntMutationStrategies(SfntStrategies *strategies); 33 | void MutateSfntFile(SfntStrategies *strategies, SfntFont *font); 34 | 35 | #endif // SFNT_MUTATOR_H_ 36 | 37 | -------------------------------------------------------------------------------- /ttf-otf-windows-loader/README.md: -------------------------------------------------------------------------------- 1 | # Font loader for Windows 2 | 3 | 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. 4 | 5 | The font-related GDI calls made by the loader are listed below: 6 | 7 | - [AddFontResourceW](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-addfontresourcew) 8 | - [RemoveFontResourceW](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-removefontresourcew) 9 | - [GetFontResourceInfoW](http://www.undocprint.org/winspool/getfontresourceinfo) 10 | - [CreateFontIndirectW](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-createfontindirectw) 11 | - [GetKerningPairs](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-getkerningpairsa) 12 | - [GetFontUnicodeRanges](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-getfontunicoderanges) 13 | - [GetGlyphOutline](https://docs.microsoft.com/en-us/windows/desktop/api/wingdi/nf-wingdi-getglyphoutlinea) 14 | - [DrawTextW](https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-drawtextw) 15 | 16 | Furthermore, the program also invokes a number of Uniscribe API functions: 17 | 18 | - [ScriptCacheGetHeight](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptcachegetheight) 19 | - [ScriptGetFontProperties](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetfontproperties) 20 | - [ScriptGetGlyphABCWidth](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetglyphabcwidth) 21 | - [ScriptGetCMap](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetcmap) 22 | - [ScriptGetFontScriptTags](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetfontscripttags) 23 | - [ScriptGetFontLanguageTags](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetfontlanguagetags) 24 | - [ScriptGetFontFeatureTags](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetfontfeaturetags) 25 | - [ScriptGetFontAlternateGlyphs](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptgetfontalternateglyphs) 26 | - [ScriptSubstituteSingleGlyph](https://docs.microsoft.com/en-us/windows/desktop/api/usp10/nf-usp10-scriptsubstitutesingleglyph) 27 | 28 | ## Building 29 | 30 | The application can be compiled with Microsoft Visual Studio after importing `ttf-otf-windows-loader.cpp` and `config.h` into a new project. 31 | 32 | ## Usage 33 | 34 | Using the tool is as simple as passing the path of the tested TTF/OTF font in the first argument, for example: 35 | 36 | ``` 37 | c:\ttf-otf-windows-loader>ttf-otf-windows-loader.exe C:\Windows\Fonts\arial.ttf 38 | [+] Extracted 1 logfonts. 39 | [+] Installed 1 fonts. 40 | [+] Starting to test font 1 / 1, variation 1 / 5 41 | [+] Getting kerning pairs 42 | [+] Getting unicode ranges 43 | [+] Getting glyph outlines and drawing them on screen 44 | [+] Testing the Uniscribe user-mode library 45 | [+] Starting to test font 1 / 1, variation 2 / 5 46 | [+] Getting kerning pairs 47 | [+] Getting unicode ranges 48 | [+] Getting glyph outlines and drawing them on screen 49 | [+] Testing the Uniscribe user-mode library 50 | [+] Starting to test font 1 / 1, variation 3 / 5 51 | [+] Getting kerning pairs 52 | [+] Getting unicode ranges 53 | [+] Getting glyph outlines and drawing them on screen 54 | [+] Testing the Uniscribe user-mode library 55 | [+] Starting to test font 1 / 1, variation 4 / 5 56 | [+] Getting kerning pairs 57 | [+] Getting unicode ranges 58 | [+] Getting glyph outlines and drawing them on screen 59 | [+] Testing the Uniscribe user-mode library 60 | [+] Starting to test font 1 / 1, variation 5 / 5 61 | [+] Getting kerning pairs 62 | [+] Getting unicode ranges 63 | [+] Getting glyph outlines and drawing them on screen 64 | [+] Testing the Uniscribe user-mode library 65 | 66 | c:\ttf-otf-windows-loader> 67 | ``` 68 | 69 | In addition to the standard output, you should also observe the font's glyphs being drawn in the upper left corner of the screen: 70 | 71 | ![Font glyphs displayed on the screen with the DrawText API call](../images/loader.png) 72 | 73 | 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. 74 | -------------------------------------------------------------------------------- /ttf-otf-windows-loader/config.h: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2018 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #ifndef CONFIG_H_ 21 | #define CONFIG_H_ 22 | 23 | // Harness build options (tested font-related functionality) 24 | #define HARNESS_TEST_KERNING_PAIRS 1 25 | #define HARNESS_TEST_DRAWTEXT 1 26 | #define HARNESS_TEST_GLYPH_OUTLINE 1 27 | #define HARNESS_TEST_UNISCRIBE 1 28 | 29 | // Number of glyphs displayed at once by a single DrawText() call. 30 | #define DISPLAYED_GLYPHS_COUNT (10) 31 | 32 | // Maximum number of alternate glyphs requested through the 33 | // usp10!ScriptGetFontAlternateGlyphs function. 34 | #define MAX_ALTERNATE_GLYPHS (10) 35 | 36 | // Maximum number of tags requested through the usp10!ScriptGetFontScriptTags, 37 | // usp10!ScriptGetFontLanguageTags and usp10!ScriptGetFontFeatureTags functions. 38 | #define UNISCRIBE_MAX_TAGS (16) 39 | 40 | #endif // CONFIG_H_ 41 | -------------------------------------------------------------------------------- /ttf-otf-windows-loader/ttf-otf-windows-loader.cpp: -------------------------------------------------------------------------------- 1 | ///////////////////////////////////////////////////////////////////////// 2 | // 3 | // Author: Mateusz Jurczyk (mjurczyk@google.com) 4 | // 5 | // Copyright 2018 Google LLC 6 | // 7 | // Licensed under the Apache License, Version 2.0 (the "License"); 8 | // you may not use this file except in compliance with the License. 9 | // You may obtain a copy of the License at 10 | // 11 | // https://www.apache.org/licenses/LICENSE-2.0 12 | // 13 | // Unless required by applicable law or agreed to in writing, software 14 | // distributed under the License is distributed on an "AS IS" BASIS, 15 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 | // See the License for the specific language governing permissions and 17 | // limitations under the License. 18 | // 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #include "config.h" 27 | 28 | #pragma comment(lib, "Usp10.lib") 29 | 30 | #ifndef ARRAY_SIZE 31 | #define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) 32 | #endif 33 | 34 | // 35 | // Undocumented definitions required to use the gdi32!GetFontResourceInfoW function. 36 | // 37 | typedef BOOL(WINAPI *PGFRI)(LPCWSTR, LPDWORD, LPVOID, DWORD); 38 | #define QFR_LOGFONT (2) 39 | 40 | static BOOL GetLogfonts(LPCWSTR szFontPath, LPLOGFONTW *lpLogfonts, LPDWORD lpdwFonts) { 41 | // Unload all instances of fonts with this path, in case there are any leftovers in the system. 42 | while (RemoveFontResourceW(szFontPath)) {} 43 | 44 | // Load the font file into the system temporarily. 45 | DWORD dwFontsLoaded = AddFontResourceW(szFontPath); 46 | if (dwFontsLoaded == 0) { 47 | wprintf(L"[-] AddFontResourceW() failed.\n"); 48 | return FALSE; 49 | } 50 | 51 | *lpdwFonts = dwFontsLoaded; 52 | *lpLogfonts = (LPLOGFONTW)HeapAlloc(GetProcessHeap(), 0, dwFontsLoaded * sizeof(LOGFONTW)); 53 | if (*lpLogfonts == NULL) { 54 | wprintf(L"[-] HeapAlloc(%u) failed.\n", dwFontsLoaded * sizeof(LOGFONTW)); 55 | RemoveFontResourceW(szFontPath); 56 | return FALSE; 57 | } 58 | 59 | // Resolve the GDI32!GetFontResourceInfoW symbol. 60 | HINSTANCE hGdi32 = GetModuleHandleA("gdi32.dll"); 61 | PGFRI GetFontResourceInfo = (PGFRI)GetProcAddress(hGdi32, "GetFontResourceInfoW"); 62 | 63 | DWORD cbBuffer = dwFontsLoaded * sizeof(LOGFONTW); 64 | if (!GetFontResourceInfo(szFontPath, &cbBuffer, *lpLogfonts, QFR_LOGFONT)) { 65 | wprintf(L"[-] GetFontResourceInfoW() failed.\n"); 66 | HeapFree(GetProcessHeap(), 0, *lpLogfonts); 67 | RemoveFontResourceW(szFontPath); 68 | return FALSE; 69 | } 70 | 71 | // Unload the font. 72 | RemoveFontResourceW(szFontPath); 73 | return TRUE; 74 | } 75 | 76 | static VOID TestUniscribe(HDC hdc, LPGLYPHSET lpGlyphset) { 77 | SCRIPT_CACHE sc = NULL; 78 | 79 | // Get font height. 80 | long tmHeight; 81 | ScriptCacheGetHeight(hdc, &sc, &tmHeight); 82 | 83 | // Get font properties. 84 | SCRIPT_FONTPROPERTIES fp; 85 | ScriptGetFontProperties(hdc, &sc, &fp); 86 | 87 | // Perform some operations (mostly in batches) over each supported glyph. 88 | WCHAR szTextBuffer[DISPLAYED_GLYPHS_COUNT]; 89 | DWORD dwTextCount = 0; 90 | WORD *wOutGlyphs = (WORD *)HeapAlloc(GetProcessHeap(), 0, DISPLAYED_GLYPHS_COUNT * sizeof(WORD)); 91 | 92 | for (DWORD i = 0; i < lpGlyphset->cRanges; i++) { 93 | for (LONG j = lpGlyphset->ranges[i].wcLow; j < lpGlyphset->ranges[i].wcLow + lpGlyphset->ranges[i].cGlyphs; j++) { 94 | szTextBuffer[dwTextCount++] = (WCHAR)j; 95 | 96 | // Test particular characters. 97 | ABC abc; 98 | ScriptGetGlyphABCWidth(hdc, &sc, (WORD)j, &abc); 99 | 100 | // Test characters in batches where possible. 101 | if (dwTextCount >= DISPLAYED_GLYPHS_COUNT) { 102 | // Test the usp10!ScriptGetCMap function. 103 | ScriptGetCMap(hdc, &sc, szTextBuffer, dwTextCount, 0, wOutGlyphs); 104 | 105 | dwTextCount = 0; 106 | } 107 | } 108 | } 109 | 110 | if (dwTextCount > 0) { 111 | // Test the usp10!ScriptGetCMap function. 112 | ScriptGetCMap(hdc, &sc, szTextBuffer, dwTextCount, 0, wOutGlyphs); 113 | } 114 | 115 | // Call some script/lang/feature-related APIs over each glyph. 116 | OPENTYPE_TAG *ScriptTags = (OPENTYPE_TAG *)HeapAlloc(GetProcessHeap(), 0, UNISCRIBE_MAX_TAGS * sizeof(OPENTYPE_TAG)); 117 | OPENTYPE_TAG *LangTags = (OPENTYPE_TAG *)HeapAlloc(GetProcessHeap(), 0, UNISCRIBE_MAX_TAGS * sizeof(OPENTYPE_TAG)); 118 | OPENTYPE_TAG *FeatureTags = (OPENTYPE_TAG *)HeapAlloc(GetProcessHeap(), 0, UNISCRIBE_MAX_TAGS * sizeof(OPENTYPE_TAG)); 119 | int cScriptTags = 0, cLangTags = 0, cFeatureTags = 0; 120 | 121 | WORD *AlternateGlyphs = (WORD *)HeapAlloc(GetProcessHeap(), 0, MAX_ALTERNATE_GLYPHS * sizeof(WORD)); 122 | int cAlternates; 123 | 124 | // Retrieve a list of available scripts in the font. 125 | if (ScriptGetFontScriptTags(hdc, &sc, NULL, UNISCRIBE_MAX_TAGS, ScriptTags, &cScriptTags) == 0) { 126 | for (int ScriptTag = 0; ScriptTag < cScriptTags; ScriptTag++) { 127 | 128 | // Retrieve a list of language tags for the specified script tag. 129 | if (ScriptGetFontLanguageTags(hdc, &sc, NULL, ScriptTags[ScriptTag], UNISCRIBE_MAX_TAGS, LangTags, &cLangTags) == 0) { 130 | for (int LangTag = 0; LangTag < cLangTags; LangTag++) { 131 | 132 | // Retrieve a list of typographic features for the defined writing system. 133 | if (ScriptGetFontFeatureTags(hdc, &sc, NULL, ScriptTags[ScriptTag], LangTags[LangTag], UNISCRIBE_MAX_TAGS, FeatureTags, &cFeatureTags) == 0) { 134 | for (int FeatureTag = 0; FeatureTag < cFeatureTags; FeatureTag++) { 135 | 136 | // Iterate through all glyphs in the font. 137 | for (DWORD i = 0; i < lpGlyphset->cRanges; i++) { 138 | for (LONG j = lpGlyphset->ranges[i].wcLow; j < lpGlyphset->ranges[i].wcLow + lpGlyphset->ranges[i].cGlyphs; j++) { 139 | 140 | // Test usp10!ScriptGetFontAlternateGlyphs. 141 | if (ScriptGetFontAlternateGlyphs(hdc, &sc, NULL, ScriptTags[ScriptTag], LangTags[LangTag], FeatureTags[FeatureTag], 142 | (WORD)j, MAX_ALTERNATE_GLYPHS, AlternateGlyphs, &cAlternates) == 0) { 143 | 144 | for (int alt_glyph_id = 1; alt_glyph_id < cAlternates; alt_glyph_id++) { 145 | WORD wOutGlyphId; 146 | 147 | // Test usp10!ScriptSubstituteSingleGlyph. 148 | ScriptSubstituteSingleGlyph(hdc, &sc, NULL, ScriptTags[ScriptTag], LangTags[LangTag], FeatureTags[FeatureTag], 149 | alt_glyph_id, (WORD)j, &wOutGlyphId); 150 | } 151 | } 152 | } 153 | } 154 | } 155 | } 156 | } 157 | } 158 | } 159 | } 160 | 161 | ScriptFreeCache(&sc); 162 | 163 | HeapFree(GetProcessHeap(), 0, wOutGlyphs); 164 | HeapFree(GetProcessHeap(), 0, ScriptTags); 165 | HeapFree(GetProcessHeap(), 0, LangTags); 166 | HeapFree(GetProcessHeap(), 0, FeatureTags); 167 | HeapFree(GetProcessHeap(), 0, AlternateGlyphs); 168 | } 169 | 170 | int wmain(int argc, wchar_t *argv[], wchar_t *envp[]) { 171 | if (argc != 2) { 172 | wprintf(L"Usage: %s \n", argv[0]); 173 | return EXIT_FAILURE; 174 | } 175 | 176 | LPCWSTR szFontPath = argv[1]; 177 | 178 | // Get screen coordinates. 179 | RECT screen_rect = { 0 }; 180 | screen_rect.left = 0; 181 | screen_rect.top = 0; 182 | screen_rect.right = GetSystemMetrics(SM_CXFULLSCREEN); 183 | screen_rect.bottom = GetSystemMetrics(SM_CYFULLSCREEN); 184 | 185 | // Reset the PRNG state. 186 | srand(0); 187 | 188 | do { 189 | // Get the logfont structures. 190 | LPLOGFONTW lpLogfonts = NULL; 191 | DWORD dwFonts = 0; 192 | 193 | if (GetLogfonts(szFontPath, &lpLogfonts, &dwFonts)) { 194 | wprintf(L"[+] Extracted %u logfonts.\n", dwFonts); 195 | } else { 196 | break; 197 | } 198 | 199 | // Load the font in the system from memory. 200 | int cFonts = AddFontResourceW(szFontPath); 201 | if (cFonts > 0) { 202 | wprintf(L"[+] Installed %d fonts.\n", cFonts); 203 | } else { 204 | wprintf(L"[-] AddFontResourceW() failed.\n"); 205 | break; 206 | } 207 | 208 | HDC hDC = GetDC(NULL); 209 | SetGraphicsMode(hDC, GM_ADVANCED); 210 | SetMapMode(hDC, MM_TEXT); 211 | 212 | // Display all fonts from the input file. 213 | BOOL success = TRUE; 214 | for (DWORD font_it = 0; success && font_it < dwFonts; font_it++) { 215 | // Display the font in several different point sizes. 216 | CONST LONG point_sizes[] = { 8, 9, 10, 11, 12, 14, 16, 18, 20, 22, 24, 26, 28, 36, 48, 72 }; 217 | 218 | for (unsigned int variation_it = 0; success && variation_it < ARRAY_SIZE(point_sizes) + 1; variation_it++) { 219 | wprintf(L"[+] Starting to test font %u / %d, variation %u / %d\n", 220 | font_it + 1, dwFonts, variation_it + 1, ARRAY_SIZE(point_sizes) + 1); 221 | 222 | HFONT hFont = NULL; 223 | if (variation_it == 0) { 224 | hFont = CreateFontIndirectW(&lpLogfonts[font_it]); 225 | } else { 226 | LOGFONTW lf; 227 | RtlCopyMemory(&lf, &lpLogfonts[font_it], sizeof(LOGFONTW)); 228 | 229 | lf.lfHeight = -MulDiv(point_sizes[variation_it - 1], GetDeviceCaps(hDC, LOGPIXELSY), 72); 230 | lf.lfWeight = 0; 231 | lf.lfItalic = (rand() & 1); 232 | lf.lfUnderline = (rand() & 1); 233 | lf.lfStrikeOut = (rand() & 1); 234 | lf.lfQuality = (rand() % 6); 235 | 236 | hFont = CreateFontIndirectW(&lf); 237 | } 238 | 239 | if (hFont == NULL) { 240 | wprintf(L"[!] CreateFontIndirectW() failed.\n"); 241 | success = FALSE; 242 | DeleteFont(hFont); 243 | break; 244 | } 245 | 246 | // Select the font for the Device Context. 247 | SelectFont(hDC, hFont); 248 | 249 | #if HARNESS_TEST_KERNING_PAIRS 250 | wprintf(L"[+] Getting kerning pairs.\n"); 251 | 252 | // Get the font's kerning pairs. 253 | DWORD nNumPairs = GetKerningPairs(hDC, 0, NULL); 254 | if (nNumPairs != 0) { 255 | LPKERNINGPAIR lpkrnpairs = (LPKERNINGPAIR)HeapAlloc(GetProcessHeap(), 0, nNumPairs * sizeof(KERNINGPAIR)); 256 | 257 | if (GetKerningPairs(hDC, nNumPairs, lpkrnpairs) == 0) { 258 | wprintf(L"[!] GetKerningPairs() failed.\n"); 259 | } 260 | 261 | HeapFree(GetProcessHeap(), 0, lpkrnpairs); 262 | } 263 | #endif // HARNESS_TEST_KERNING_PAIRS 264 | 265 | wprintf(L"[+] Getting unicode ranges.\n"); 266 | 267 | // Get Unicode ranges available in the font. 268 | DWORD dwGlyphsetSize = GetFontUnicodeRanges(hDC, NULL); 269 | if (dwGlyphsetSize == 0) { 270 | wprintf(L"[!] GetFontUnicodeRanges() failed.\n"); 271 | success = FALSE; 272 | DeleteFont(hFont); 273 | break; 274 | } 275 | 276 | LPGLYPHSET lpGlyphset = (LPGLYPHSET)HeapAlloc(GetProcessHeap(), 0, dwGlyphsetSize); 277 | 278 | if (GetFontUnicodeRanges(hDC, lpGlyphset) == 0) { 279 | wprintf(L"[!] GetFontUnicodeRanges() failed.\n"); 280 | success = FALSE; 281 | HeapFree(GetProcessHeap(), 0, lpGlyphset); 282 | DeleteFont(hFont); 283 | break; 284 | } 285 | 286 | #if HARNESS_TEST_DRAWTEXT 287 | WCHAR szTextBuffer[DISPLAYED_GLYPHS_COUNT + 1]; 288 | DWORD dwTextCount = 0; 289 | #endif // HARNESS_TEST_DRAWTEXT 290 | 291 | wprintf(L"[+] Getting glyph outlines and drawing them on screen.\n"); 292 | 293 | for (DWORD i = 0; i < lpGlyphset->cRanges; i++) { 294 | for (LONG j = lpGlyphset->ranges[i].wcLow; j < lpGlyphset->ranges[i].wcLow + lpGlyphset->ranges[i].cGlyphs; j++) { 295 | #if HARNESS_TEST_GLYPH_OUTLINE 296 | // Get the glyph outline in all available formats. 297 | CONST UINT glyph_formats[] = { GGO_BEZIER, GGO_BITMAP, GGO_GRAY2_BITMAP, GGO_GRAY4_BITMAP, GGO_GRAY8_BITMAP, GGO_NATIVE }; 298 | for (UINT format : glyph_formats) { 299 | GLYPHMETRICS gm; 300 | MAT2 mat2 = { 0, 1, 0, 0, 0, 0, 0, 1 }; 301 | 302 | DWORD cbBuffer = GetGlyphOutline(hDC, j, format, &gm, 0, NULL, &mat2); 303 | 304 | if (cbBuffer != GDI_ERROR) { 305 | LPVOID lpvBuffer = HeapAlloc(GetProcessHeap(), 0, cbBuffer); 306 | 307 | if (GetGlyphOutline(hDC, j, format, &gm, cbBuffer, lpvBuffer, &mat2) == GDI_ERROR) { 308 | wprintf(L"[!] GetGlyphOutline() failed for glyph %u.\n", j); 309 | } 310 | 311 | HeapFree(GetProcessHeap(), 0, lpvBuffer); 312 | } 313 | } 314 | #endif // HARNESS_TEST_GLYPH_OUTLINE 315 | 316 | #if HARNESS_TEST_DRAWTEXT 317 | // Insert the glyph into current string to be displayed. 318 | szTextBuffer[dwTextCount++] = (WCHAR)j; 319 | if (dwTextCount >= DISPLAYED_GLYPHS_COUNT) { 320 | szTextBuffer[DISPLAYED_GLYPHS_COUNT] = L'\0'; 321 | DrawTextW(hDC, szTextBuffer, -1, &screen_rect, DT_WORDBREAK | DT_NOCLIP); 322 | dwTextCount = 0; 323 | } 324 | #endif // HARNESS_TEST_DRAWTEXT 325 | } 326 | } 327 | 328 | #if HARNESS_TEST_DRAWTEXT 329 | if (dwTextCount > 0) { 330 | szTextBuffer[dwTextCount] = L'\0'; 331 | DrawTextW(hDC, szTextBuffer, -1, &screen_rect, DT_WORDBREAK | DT_NOCLIP); 332 | } 333 | #endif // HARNESS_TEST_DRAWTEXT 334 | 335 | #if HARNESS_TEST_UNISCRIBE 336 | wprintf(L"[+] Testing the Uniscribe user-mode library.\n"); 337 | 338 | // Test some user-mode Windows Uniscribe functionality. 339 | TestUniscribe(hDC, lpGlyphset); 340 | #endif // HARNESS_TEST_UNISCRIBE 341 | 342 | HeapFree(GetProcessHeap(), 0, lpGlyphset); 343 | DeleteFont(hFont); 344 | } 345 | } 346 | 347 | // Remove the font from the system. 348 | ReleaseDC(NULL, hDC); 349 | RemoveFontResourceW(szFontPath); 350 | } while (0); 351 | 352 | return 0; 353 | } 354 | 355 | --------------------------------------------------------------------------------