diff --git a/README.md b/README.md index e667f93..aa2abd1 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ BrokenType is a set of tools designed to test the robustness and security of fon - **[TTF/OTF loader for Windows GDI](ttf-otf-windows-loader)** - a utility for loading and testing custom fonts with Windows GDI / Uniscribe. - **[TTF/OTF loader for DirectWrite](ttf-otf-dwrite-loader)** - a utility for loading and testing custom fonts with the Microsoft DirectWrite API. - **[TTF loader for Windows FontSub.dll](ttf-fontsub-loader)** - a utility for loading and testing custom fonts with the Font Subsetting library. + - **[Linux PE loader for FontSub.dll](fontsub-dll-on-linux)** - a minimal PE loader for Linux, tailored for fuzzing the Windows subsetter. - **[PDF font embedders](font2pdf)** - Python scripts for generating PDF documents which embed the specified fonts and display all of their glyphs. The description and usage instructions of the utilities can be found in their corresponding READMEs. diff --git a/fontsub-dll-on-linux/Makefile b/fontsub-dll-on-linux/Makefile new file mode 100644 index 0000000..57a6f04 --- /dev/null +++ b/fontsub-dll-on-linux/Makefile @@ -0,0 +1,18 @@ +CXX=g++ +LD=g++ +CXXFLAGS=-m32 -DDEBUG +LDFLAGS=-lpe-parser-library -ldl -m32 +DEPS=fontsub.h +SRCS=loader.cc +OBJS=$(subst .cc,.o,$(SRCS)) + +all: loader + +%.o: %.cc $(DEPS) + $(CXX) -c -o $@ $< $(CXXFLAGS) + +loader: $(OBJS) + $(LD) -o $@ $< $(LDFLAGS) + +clean: + $(RM) loader $(OBJS) diff --git a/fontsub-dll-on-linux/README.md b/fontsub-dll-on-linux/README.md new file mode 100644 index 0000000..1277945 --- /dev/null +++ b/fontsub-dll-on-linux/README.md @@ -0,0 +1,117 @@ +# Linux DLL loader for the Windows Font Subsetting Library (FontSub.dll) + +This project is an equivalent of the [Windows FontSub Harness](https://github.com/googleprojectzero/BrokenType/tree/master/ttf-fontsub-loader), but aimed to run on Linux, by introducing a thin PE loading component. It is capable of mapping the PE sections in the Linux address space, setting the requested memory access rights, redirecting `msvcrt.dll` imports to the corresponding libc functions, providing custom stubs for other imports, and handling basic relocations. As it turns out, the `FontSub.dll` library is simple and self-contained enough that this is sufficient to have it run on Linux. + +The benefit to this approach is that with the harness, all typical Linux fuzzing tools become available: + - You can use [AFL](http://lcamtuf.coredump.cx/afl/), [honggfuzz](https://github.com/google/honggfuzz), or your favourite Linux-based fuzzer. + - You can use custom allocators, either by compiling them in, or injecting them through `LD_PRELOAD` (e.g. AFL's [libdislocator](https://github.com/google/afl/tree/master/libdislocator)). + - You can use performance improvements such as a "Fork Server", which are easier to implement on Linux. + - You can use additional DBI such as [Intel Pin](https://software.intel.com/en-us/articles/pin-a-dynamic-binary-instrumentation-tool) or [DynamoRIO](https://www.dynamorio.org/), which in our experience work most reliably on Linux. + +The program is meant to serve as a proof-of-concept and reference for quickly developing simple Linux DLL loaders for specialized tasks. Another more complex and mature project of this kind is Tavis Ormandy's [loadlibrary](https://github.com/taviso/loadlibrary). + +We used a similar tool for fuzzing a JPEG2000 decoder in VMware Workstation in 2016, see slides 195-208 of the [Windows Metafiles - An Analysis of the EMF Attack Surface & Recent Vulnerabilities](https://j00ru.vexillium.org/slides/2016/metafiles_full.pdf) presentation. + +## Requirements + +The tool uses the [pe-parse](https://github.com/trailofbits/pe-parse) library for a majority of the PE-related work. You also obviously need the Windows `FontSub.dll` system file to execute. + +## Building + +With `pe-parse` installed in the system, simply type `make` in the project directory: + +``` +$ make +g++ -c -o loader.o loader.cc -m32 -DDEBUG +g++ -o loader loader.o -lpe-parser-library -ldl -m32 +$ +``` + +## Usage + +Before using the loader, please note that some recent versions of the FontSub library contain telemetry functions such as `LogCreateFontPackage` and `LogMergeFontPackage`, called at the end of the exported routines, e.g.: + +``` +.text:0000000180001345 loc_180001345: ; CODE XREF: CreateFontPackage+C1 +.text:0000000180001345 ; CreateFontPackage+161 +.text:0000000180001345 movzx ebx, r10w +.text:0000000180001349 mov ecx, ebx ; unsigned int +.text:000000018000134B call LogCreateFontPackage +.text:0000000180001350 mov eax, ebx +``` + +These functions will call unimplemented Win32 API imports and crash the loader. Since their execution is not essential to the correct functioning of the subsetter, you might consider disabling them in your copy of the DLL. + +Then, just pass the path of the library, path of the input TrueType, and optionally the desired image base: + +``` +$ ./loader fontsub.dll times.ttf 0x20000000 +[+] Provided fontsub.dll file successfully parsed. +[*] Attempting to map section .text base 20001000, size 88576 +[+] SUCCESS! +[*] Attempting to map section .data base 20017000, size 1536 +[+] SUCCESS! +[*] Attempting to map section .idata base 20018000, size 2048 +[+] SUCCESS! +[*] Attempting to map section .rsrc base 20019000, size 1024 +[+] SUCCESS! +[*] Attempting to map section .reloc base 2001a000, size 2560 +[+] SUCCESS! +[...] +[*] Resolving the MSVCRT.DLL!memmove import. +[+] Function memmove found in libc at address 0xf7b53690. +[*] Resolving the MSVCRT.DLL!memcmp import. +[+] Function memcmp found in libc at address 0xf7b688e0. +[*] Resolving the MSVCRT.DLL!memcpy import. +[+] Function memcpy found in libc at address 0xf7b53050. +[*] Resolving the MSVCRT.DLL!memset import. +[+] Function memset found in libc at address 0xf7b51e20. +[...] +[*] Setting desired access rights for section .text +[+] SUCCESS! +[*] Setting desired access rights for section .data +[+] SUCCESS! +[*] Setting desired access rights for section .idata +[+] SUCCESS! +[*] Setting desired access rights for section .rsrc +[+] SUCCESS! +[*] Setting desired access rights for section .reloc +[+] SUCCESS! +[+] Located CreateFontPackage at 0x20002500, MergeFontPackage at 0x20002630. +[A] malloc(0x168) ---> 0x573eebb0 +[A] malloc(0x121b) ---> 0x573f3780 +[A] malloc(0x14) ---> 0x573ed7d0 +[A] malloc(0x490) ---> 0x573eed20 +[A] malloc(0xfae) ---> 0x573f49a0 +[A] free(0x573eed20) +[A] free(0x573f49a0) +[...] +[A] free(0xf79a1010) +[+] CreateFontPackage([ 1162804 bytes ], TTFCFP_SUBSET) ---> 0 (344996 bytes) +[A] realloc((nil), 0x543a4) ---> 0x573f7a20 +[A] free(0x573f7a20) +[+] MergeFontPackage(NULL, [ 344996 bytes ], TTFMFP_SUBSET) ---> 0 (344996 bytes) +$ +``` + +There are a lot of debug messages in the above output. If you build the tool without `-DDEBUG`, the output is much cleaner: + +``` +$ ./loader fontsub.dll times.ttf 0x20000000 +[+] CreateFontPackage([ 1162804 bytes ], TTFCFP_SUBSET) ---> 0 (344996 bytes) +[+] MergeFontPackage(NULL, [ 344996 bytes ], TTFMFP_SUBSET) ---> 0 (344996 bytes) +$ +``` + +## Limitations + +The harness currently only supports x86 `FontSub.dll` modules, but adding support for x64 images should be relatively straightforward. + +However, in a more general scenario, there are a number of differences between the Windows and Linux runtime environments, and the MSVC/gcc/clang compilers. They may result in compatbility problems with varying degrees of difficulty to solve. For instance: + - `sizeof(long)` is 4 on 64-bit Windows, but 8 on 64-bit Linux. + - Both systems have [different](https://en.wikipedia.org/wiki/X86_calling_conventions#x86-64_calling_conventions) calling conventions on the x64 architecture. This can be somewhat mitigated by using the `ms_abi` attribute for external function prototypes. + - Accessing the TEB/PEB Windows structures through segment registers like `fs` or `gs` will most likely crash the loader, if some kind of emulation is not developed. They may be directly referenced in code to make use of the [SEH](https://docs.microsoft.com/en-us/windows/win32/debug/structured-exception-handling) mechanism (in built-in functions such as `_SEH_prolog4` and `_SEH_epilog4`), dynamic stack allocations (`alloca()`, to get the stack base address) etc. + - The exception handling architecture is vastly different in Windows/Linux, and so a target which makes extensive use of them may be problematic. Furthermore, standard C++ exceptions are implemented with the `RaiseException` API on Windows, which is not provided by the harness at the moment. + +Last but not least, all external Windows API functions used by the library in question must be emulated or at least stubbed out, which may range from a simple to an almost impossible task. + diff --git a/fontsub-dll-on-linux/fontsub.h b/fontsub-dll-on-linux/fontsub.h new file mode 100644 index 0000000..57ffedb --- /dev/null +++ b/fontsub-dll-on-linux/fontsub.h @@ -0,0 +1,57 @@ +///////////////////////////////////////////////////////////////////////// +// +// Author: Mateusz Jurczyk (mjurczyk@google.com) +// +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#ifndef FONTSUB_H_ +#define FONTSUB_H_ + +// Standard FontSub constants from the Windows SDK (fontsub.h). + +/* for usSubsetFormat */ +#define TTFCFP_SUBSET 0 /* Straight Subset Font - Backward compatibility */ +#define TTFCFP_SUBSET1 1 /* Subset font with full TTO and Kern tables. For later merge */ +#define TTFCFP_DELTA 2 /* Delta font, for merge with a subset1 font */ + +/* for usSubsetPlatform */ +#define TTFCFP_UNICODE_PLATFORMID 0 +#define TTFCFP_APPLE_PLATFORMID 1 +#define TTFCFP_ISO_PLATFORMID 2 +#define TTFCFP_MS_PLATFORMID 3 + +/* for usSubsetEncoding */ +#define TTFCFP_STD_MAC_CHAR_SET 0 /* goes with TTFSUB_APPLE_PLATFORMID */ +#define TTFCFP_SYMBOL_CHAR_SET 0 /* goes with TTFSUB_MS_PLATFORMID */ +#define TTFCFP_UNICODE_CHAR_SET 1 /* goes with TTFSUB_MS_PLATFORMID */ +#define TTFCFP_DONT_CARE 0xFFFF + +/* for usSubsetLanguage */ +#define TTFCFP_LANG_KEEP_ALL 0 + +/* for usFlags */ +#define TTFCFP_FLAGS_SUBSET 0x0001 /* if bit off, don't subset */ +#define TTFCFP_FLAGS_COMPRESS 0x0002 /* if bit off, don't compress */ +#define TTFCFP_FLAGS_TTC 0x0004 /* if bit off, its a TTF */ +#define TTFCFP_FLAGS_GLYPHLIST 0x0008 /* if bit off, list is characters */ + +/* for usModes */ +#define TTFMFP_SUBSET 0 /* copy a Straight Subset Font package to Dest buffer */ +#define TTFMFP_SUBSET1 1 /* Expand a format 1 font into a format 3 font */ +#define TTFMFP_DELTA 2 /* Merge a format 2 with a format 3 font */ + +#endif // FONTSUB_H_ + diff --git a/fontsub-dll-on-linux/loader.cc b/fontsub-dll-on-linux/loader.cc new file mode 100644 index 0000000..b50018c --- /dev/null +++ b/fontsub-dll-on-linux/loader.cc @@ -0,0 +1,578 @@ +///////////////////////////////////////////////////////////////////////// +// +// Author: Mateusz Jurczyk (mjurczyk@google.com) +// +// Copyright 2019 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#if __x86_64__ +#error The loader only works in x86 mode. +#endif // __x86_64__ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +#include "fontsub.h" + +using namespace std; +using namespace peparse; + +#ifdef DEBUG +# define LOG(x) printf x; +#else +# define LOG(x) +#endif // DEBUG + +namespace globals { + +// See https://docs.microsoft.com/en-us/windows/win32/api/fontsub/ for the +// documentation of the prototypes below. + +typedef void *(*CFP_ALLOCPROC)(size_t Arg1); +typedef void *(*CFP_REALLOCPROC)(void *, size_t Arg1); +typedef void (*CFP_FREEPROC)(void *); + +typedef unsigned long (*fnCreateFontPackage)( + const unsigned char *puchSrcBuffer, + const unsigned long ulSrcBufferSize, + unsigned char **ppuchFontPackageBuffer, + unsigned long *pulFontPackageBufferSize, + unsigned long *pulBytesWritten, + const unsigned short usFlag, + const unsigned short usTTCIndex, + const unsigned short usSubsetFormat, + const unsigned short usSubsetLanguage, + const unsigned short usSubsetPlatform, + const unsigned short usSubsetEncoding, + const unsigned short *pusSubsetKeepList, + const unsigned short usSubsetListCount, + CFP_ALLOCPROC lpfnAllocate, + CFP_REALLOCPROC lpfnReAllocate, + CFP_FREEPROC lpfnFree, + void *lpvReserved +); + +typedef unsigned long (*fnMergeFontPackage)( + const unsigned char *puchMergeFontBuffer, + const unsigned long ulMergeFontBufferSize, + const unsigned char *puchFontPackageBuffer, + const unsigned long ulFontPackageBufferSize, + unsigned char **ppuchDestBuffer, + unsigned long *pulDestBufferSize, + unsigned long *pulBytesWritten, + const unsigned short usMode, + CFP_ALLOCPROC lpfnAllocate, + CFP_REALLOCPROC lpfnReAllocate, + CFP_FREEPROC lpfnFree, + void *lpvReserved +); + +fnCreateFontPackage pfnCreateFontPackage; +fnMergeFontPackage pfnMergeFontPackage; + +// The delta between the actual address the library is being loaded to in +// relation to the suggested ImageBase from the PE headers. +size_t base_address_delta; + +} // namespace globals + +static void *CfpAllocproc(size_t Arg1) { + void *ret = malloc(Arg1); + LOG(("[A] malloc(0x%zx) ---> %p\n", Arg1, ret)); + return ret; +} + +static void CfpFreeproc(void *Arg1) { + LOG(("[A] free(%p)\n", Arg1)); + return free(Arg1); +} + +static void *CfpReallocproc(void *Arg1, size_t Arg2) { + void *ret = realloc(Arg1, Arg2); + LOG(("[A] realloc(%p, 0x%zx) ---> %p\n", Arg1, Arg2, ret)); + return ret; +} + +static int MapSection(void *cbd, VA base_address, string& name, + image_section_header hdr, bounded_buffer *data) { + + // Apply potential relocation. + base_address += globals::base_address_delta; + + LOG(("[*] Attempting to map section %-8s base %zx, size %u\n", + name.c_str(), (size_t)base_address, data->bufLen)); + + // Create an initial writable mapping in memory to copy the section's data. + void *mmapped_addr = mmap((void *)base_address, + hdr.Misc.VirtualSize, + PROT_WRITE, + MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, + -1, + 0); + + if (mmapped_addr == (void *)-1) { + perror("MapSection"); + exit(1); + } + + // Copy the physical data there. + memcpy(mmapped_addr, data->buf, data->bufLen); + + LOG(("[+] SUCCESS!\n")); + return 0; +} + +static int SetSectionAccessRights(void *cbd, VA base_address, string& name, + image_section_header hdr, bounded_buffer *data) { + + // Apply potential relocation. + base_address += globals::base_address_delta; + + LOG(("[*] Setting desired access rights for section %-8s\n", name.c_str())); + + // Change the protection to the desired one specified in the section's header. + int prot = PROT_READ; + if ((hdr.Characteristics & IMAGE_SCN_CNT_CODE) || + (hdr.Characteristics & IMAGE_SCN_MEM_EXECUTE)) { + prot |= PROT_EXEC; + } + if (hdr.Characteristics & IMAGE_SCN_MEM_WRITE) { + prot |= PROT_WRITE; + } + + if (mprotect((void *)base_address, hdr.Misc.VirtualSize, prot) == -1) { + perror("SetSectionAccessRights"); + exit(1); + } + + LOG(("[+] SUCCESS!\n")); + return 0; +} + +static int ResolveImport(void *cbd, VA import_addr, const string& module_name, + const string& function_name) { + + // Apply potential relocation. + import_addr += globals::base_address_delta; + + LOG(("[*] Resolving the %s!%s import.\n", module_name.c_str(), function_name.c_str())); + + // Initialize the libc handle, if it hasn't been yet. + static void *libc_handle = NULL; + if (libc_handle == NULL) { + libc_handle = dlopen("libc.so.6", RTLD_NOW); + if (libc_handle == NULL) { + fprintf(stderr, "ResolveImport: Unable to get the base address of libc.\n"); + exit(1); + } + + LOG(("[+] Resolved the libc handle at %p\n", libc_handle)); + } + + // Insert hooks for target-specific functions. + void *hook_func_addr = NULL; + if (function_name == "malloc") { + hook_func_addr = (void *)CfpAllocproc; + } else if (function_name == "realloc") { + hook_func_addr = (void *)CfpReallocproc; + } else if (function_name == "free") { + hook_func_addr = (void *)CfpFreeproc; + } + + if (hook_func_addr != NULL) { + memcpy((void *)import_addr, &hook_func_addr, sizeof(void *)); + return 0; + } + + // In the rest of the function, we only do MSVCRT => libc remapping + // Other imports are filled with a 0xcccccccc marker. + if (module_name != "MSVCRT.DLL") { + memcpy((void *)import_addr, "\xcc\xcc\xcc\xcc", 4); + return 0; + } + + // Locate the corresponding libc function and insert its address in IAT. + void *func_addr = dlsym(libc_handle, function_name.c_str()); + if (func_addr != NULL) { + LOG(("[+] Function %s found in libc at address %p.\n", function_name.c_str(), func_addr)); + + memcpy((void *)import_addr, &func_addr, sizeof(void *)); + } else { + LOG(("[!] Function %s NOT FOUND in libc.\n", function_name.c_str())); + + // The marker for unmatched MSVCRT functions is 0xdddddddd. + memcpy((void *)import_addr, "\xdd\xdd\xdd\xdd", 4); + } + + return 0; +} + +int ResolveRelocation(void *cbd, VA reloc_addr, reloc_type type) { + if (type == HIGHLOW) { + uint32_t *address = (uint32_t *)(reloc_addr + globals::base_address_delta); + *address += globals::base_address_delta; + } else if (type != ABSOLUTE) { + fprintf(stderr, "[-] Unknown relocation type: %d\n", type); + } + + return 0; +} + +static int FindExports(void *cbd, VA func_addr, string& module_name, string& function_name) { + if (function_name == "CreateFontPackage") { + globals::pfnCreateFontPackage = + (globals::fnCreateFontPackage)(func_addr + globals::base_address_delta); + } else if (function_name == "MergeFontPackage") { + globals::pfnMergeFontPackage = + (globals::fnMergeFontPackage)(func_addr + globals::base_address_delta); + } + + return 0; +} + +static bool ReadFileToString(const char *filename, std::string *buffer) { + FILE *f = fopen(filename, "rb"); + if (f == NULL) { + return false; + } + + fseek(f, 0, SEEK_END); + const long size = ftell(f); + fseek(f, 0, SEEK_SET); + + bool success = true; + + char *tmp = (char *)malloc(size); + if (tmp == NULL) { + success = false; + } + + if (success) { + success = (fread(tmp, 1, size, f) == size); + } + + if (success) { + buffer->assign(tmp, size); + } + + free(tmp); + fclose(f); + + return success; +} + +static void GenerateKeepList(std::mt19937& rand_gen, + std::vector *keep_list) { + int list_length; + + switch (rand_gen() & 3) { + case 0: list_length = 1 + (rand_gen() % 10); break; + case 1: list_length = 1 + (rand_gen() % 100); break; + case 2: list_length = 1 + (rand_gen() % 1000); break; + case 3: list_length = 1 + (rand_gen() % 10000); break; + } + + for (int i = 0; i < list_length; i++) { + keep_list->push_back(rand_gen() % (list_length * 2)); + } +} + +unsigned long FontSubCreate(const std::string& input_data, std::mt19937& rand_gen, + unsigned short usFormat, std::string *output_data) { + unsigned char *puchFontPackageBuffer = NULL; + unsigned long ulFontPackageBufferSize = 0; + unsigned long ulBytesWritten = 0; + + unsigned short usFlag = TTFCFP_FLAGS_SUBSET; + unsigned short usTTCIndex = 0; + + if (rand_gen() & 1) { usFlag |= TTFCFP_FLAGS_COMPRESS; } + if (rand_gen() & 1) { usFlag |= TTFCFP_FLAGS_GLYPHLIST; } + if (input_data.size() >= 12 && !memcmp(input_data.data(), "ttcf", 4)) { + int font_count = input_data.data()[11]; + if (font_count == 0) { + font_count = 1; + } + + usFlag |= TTFCFP_FLAGS_TTC; + usTTCIndex = rand_gen() % font_count; + } + + unsigned short usSubsetPlatform = TTFCFP_UNICODE_PLATFORMID; + unsigned short usSubsetEncoding = TTFCFP_DONT_CARE; + + switch (rand_gen() & 3) { + case 0: + usSubsetPlatform = TTFCFP_UNICODE_PLATFORMID; + break; + case 1: + usSubsetPlatform = TTFCFP_APPLE_PLATFORMID; + if (rand_gen() & 1) { + usSubsetEncoding = TTFCFP_STD_MAC_CHAR_SET; + } + break; + case 2: + usSubsetPlatform = TTFCFP_ISO_PLATFORMID; + break; + case 3: + usSubsetPlatform = TTFCFP_MS_PLATFORMID; + switch (rand_gen() & 3) { + case 0: + usSubsetEncoding = TTFCFP_SYMBOL_CHAR_SET; + break; + case 1: + usSubsetEncoding = TTFCFP_UNICODE_CHAR_SET; + break; + } + break; + } + + std::vector keep_list; + GenerateKeepList(rand_gen, &keep_list); + + unsigned long ret = globals::pfnCreateFontPackage( + (unsigned char *)input_data.data(), + input_data.size(), + &puchFontPackageBuffer, + &ulFontPackageBufferSize, + &ulBytesWritten, + usFlag, + usTTCIndex, + usFormat, + /*usSubsetLanguage=*/0, + usSubsetPlatform, + usSubsetEncoding, + &keep_list[0], + keep_list.size(), + CfpAllocproc, + CfpReallocproc, + CfpFreeproc, + NULL); + + if (ret == 0) { + output_data->assign((char *)puchFontPackageBuffer, ulBytesWritten); + CfpFreeproc(puchFontPackageBuffer); + } else { + output_data->clear(); + } + + return ret; +} + +unsigned long FontSubMerge1(const std::string& input_data, unsigned short usMode, + std::string *output_data) { + unsigned char *puchDestBuffer = NULL; + unsigned long ulDestBufferSize = 0; + unsigned long ulBytesWritten = 0; + + unsigned long ret = globals::pfnMergeFontPackage( + /*puchMergeFontBuffer=*/NULL, + /*ulMergeFontBufferSize=*/0, + /*puchFontPackageBuffer=*/(const unsigned char *)input_data.data(), + /*ulFontPackageBufferSize=*/input_data.size(), + /*ppuchDestBuffer=*/&puchDestBuffer, + /*pulDestBufferSize=*/&ulDestBufferSize, + /*pulBytesWritten=*/&ulBytesWritten, + usMode, + CfpAllocproc, + CfpReallocproc, + CfpFreeproc, + NULL); + + if (ret == 0) { + output_data->assign((char *)puchDestBuffer, ulBytesWritten); + CfpFreeproc(puchDestBuffer); + } else { + output_data->clear(); + } + + return ret; +} + +unsigned long FontSubMerge2(const std::string& merge_font, const std::string& font_package, + std::string *output_data) { + unsigned char *puchDestBuffer = NULL; + unsigned long ulDestBufferSize = 0; + unsigned long ulBytesWritten = 0; + + unsigned long ret = globals::pfnMergeFontPackage( + /*puchMergeFontBuffer=*/(const unsigned char *)merge_font.data(), + /*ulMergeFontBufferSize=*/merge_font.size(), + /*puchFontPackageBuffer=*/(const unsigned char *)font_package.data(), + /*ulFontPackageBufferSize=*/font_package.size(), + /*ppuchDestBuffer=*/&puchDestBuffer, + /*pulDestBufferSize=*/&ulDestBufferSize, + /*pulBytesWritten=*/&ulBytesWritten, + /*usMode=*/TTFMFP_DELTA, + CfpAllocproc, + CfpReallocproc, + CfpFreeproc, + NULL); + + if (ret == 0) { + output_data->assign((char *)puchDestBuffer, ulBytesWritten); + CfpFreeproc(puchDestBuffer); + } else { + output_data->clear(); + } + + return ret; +} + +void ProcessSample(const std::string& input_data) { + std::seed_seq seed(input_data.begin(), input_data.end()); + std::mt19937 rand_gen(seed); + + if (rand_gen() & 1) /* TTFCFP_SUBSET case */ { + std::string font_package; + unsigned long ret = FontSubCreate(input_data, rand_gen, TTFCFP_SUBSET, &font_package); + + printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_SUBSET) ---> %lu (%zu bytes)\n", + input_data.size(), ret, font_package.size()); + + if (ret == 0) { + std::string working_font; + ret = FontSubMerge1(font_package, TTFMFP_SUBSET, &working_font); + + printf("[+] MergeFontPackage(NULL, [ %zu bytes ], TTFMFP_SUBSET) ---> %lu (%zu bytes)\n", + font_package.size(), ret, working_font.size()); + } + } else /* TTFCFP_SUBSET1 / TTFCFP_DELTA case */ { + std::string font_package; + unsigned long ret = FontSubCreate(input_data, rand_gen, TTFCFP_SUBSET1, &font_package); + + printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_SUBSET1) ---> %lu (%zu bytes)\n", + input_data.size(), ret, font_package.size()); + + if (ret == 0) { + std::string working_font; + ret = FontSubMerge1(font_package, TTFMFP_SUBSET1, &working_font); + + printf("[+] MergeFontPackage(NULL, [ %zu bytes ], TTFMFP_SUBSET1) ---> %lu (%zu bytes)\n", + font_package.size(), ret, working_font.size()); + + const int merges = 1 + (rand_gen() % 5); + for (int i = 0; i < merges; i++) { + ret = FontSubCreate(input_data, rand_gen, TTFCFP_DELTA, &font_package); + printf("[+] CreateFontPackage([ %zu bytes ], TTFCFP_DELTA) ---> %lu (%zu bytes)\n", + input_data.size(), ret, font_package.size()); + + if (ret == 0) { + std::string new_working_font; + ret = FontSubMerge2(working_font, font_package, &new_working_font); + + printf("[+] MergeFontPackage([ %zu bytes ], [ %zu bytes ], TTFMFP_DELTA) ---> %lu (%zu bytes)\n", + working_font.size(), font_package.size(), ret, new_working_font.size()); + + if (ret == 0) { + working_font = new_working_font; + } + } + } + } + } +} + +int main(int argc, char **argv) { + if (argc < 3 || argc > 4) { + printf("Usage: %s /path/to/fontsub.dll /path/to/font/file [image base]\n", argv[0]); + return 1; + } + + const char *fontsub_dll_path = argv[1]; + const char *input_font_path = argv[2]; + + // Parse the input PE file. + parsed_pe *pe = ParsePEFromFile(fontsub_dll_path); + if (pe == NULL) { + fprintf(stderr, "[-] Unable to parse the %s file.\n", fontsub_dll_path); + return 1; + } + LOG(("[+] Provided fontsub.dll file successfully parsed.\n")); + + // In case of an alternate image base, save information about the address delta. + if (argc == 4) { + size_t new_base_address = 0; + if (sscanf(argv[3], "%zx", &new_base_address) != 1 || + (new_base_address & 0xffff) != 0) { + fprintf(stderr, "[-] Invalid base address %s\n", argv[3]); + DestructParsedPE(pe); + return 1; + } + + size_t orig_base_address = 0; + if (pe->peHeader.nt.OptionalMagic == NT_OPTIONAL_32_MAGIC) { + orig_base_address = pe->peHeader.nt.OptionalHeader.ImageBase; + } else { + orig_base_address = pe->peHeader.nt.OptionalHeader64.ImageBase; + } + + globals::base_address_delta = new_base_address - orig_base_address; + } + + // Map sections to their respective addresses. + IterSec(pe, MapSection, NULL); + + // Resolve imports (redirect MSVCRT to libc, install stubs etc.) in IAT. + IterImpVAString(pe, ResolveImport, NULL); + + // If we're loading the library to non-default image base, resolve relocations. + if (globals::base_address_delta != 0) { + IterRelocs(pe, ResolveRelocation, NULL); + } + + // Set the final access rights for the section memory regions. + IterSec(pe, SetSectionAccessRights, NULL); + + // Find the fontsub!CreateFontPackage and fontsub!MergeFontPackage exported functions. + IterExpVA(pe, FindExports, NULL); + + if (globals::pfnCreateFontPackage == NULL || globals::pfnMergeFontPackage == NULL) { + fprintf(stderr, "[-] Unable to locate the required exported functions.\n"); + DestructParsedPE(pe); + return 1; + } + LOG(("[+] Located CreateFontPackage at %p, MergeFontPackage at %p.\n", + globals::pfnCreateFontPackage, globals::pfnMergeFontPackage)); + + // Read the input font file. + std::string font_data; + if (!ReadFileToString(input_font_path, &font_data)) { + fprintf(stderr, "[-] Unable to read the %s input file\n", input_font_path); + DestructParsedPE(pe); + return 1; + } + + // Process the input data using the loaded DLL file. + ProcessSample(font_data); + + // Destroy the PE object, but don't manually unmap the image from memory. + // Instead, rely on the OS for the cleanup. + DestructParsedPE(pe); + + return 0; +} +