├── 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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------