├── Makefile ├── README.md └── lldb_fix.c /Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS = -shared -fpic -fobjc-link-runtime /usr/lib/libxcselect.dylib 2 | CC = clang 3 | SDK_ROOT := `xcrun -sdk macosx -show-sdk-path` 4 | DS_OUTPUT := "plugin load \"$(PWD)/lldb_fix.dylib\"" 5 | 6 | all: lldb_fix.c 7 | $(CC) $? -isysroot $(SDK_ROOT) -o lldb_fix.dylib $(CFLAGS) 8 | @echo \\nbuild success! 9 | 10 | install: all 11 | /usr/bin/grep -q -F $(DS_OUTPUT) ~/.lldbinit || echo $(DS_OUTPUT) >> ~/.lldbinit 12 | @echo \\nAdded \'$(DS_OUTPUT)\' .lldbinit file...\\n 13 | 14 | clean: 15 | -rm -f lldb_fix.dylib 16 | sed -i -e '/plugin load .*lldb_fix.dylib/d' ~/.lldbinit 17 | @echo Removed \'$(DS_OUTPUT)\' in .lldbinit file 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # lldb_fix 2 | 3 | ## Resolved in Xcode 10.2 4 | 5 | This fix only pertains to Xcode 10.0-10.1, no longer needed in Xcode 10.2 6 | 7 | ## Huh? 8 | This is a fix which patches LLDB v`lldb-1000.11.37.1`, which has a nasty little bug that incorrectly imports the wrong SDK when debugging via a Terminal session. For example, if you were to debug an iOS Simulator application via Terminal, LLDB will import the wrong SDK headers which prevents you from executing code correctly in that process. By default, this version of LLDB will import the MacOSX headers resulting in many scripts breaking in [Facebook's Chisel repository](https://github.com/facebook/chisel) as well as [many of my "Advanced Debugging and Reverse Engineering" scripts of my own found here](https://github.com/DerekSelander/LLDB/) 9 | 10 | To see if you affected, open up a Terminal window and check your LLDB version: 11 | ```none 12 | ~$ lldb 13 | (lldb) version 14 | lldb-1000.11.37.1 15 | Swift-4.2 16 | (lldb) q 17 | Quitting LLDB will detach from one or more processes. Do you really want to proceed: [Y/n] Y 18 | ``` 19 | 20 | If you have the same version (or maybe one coming from Xcode 10.1/10.2) this will happen when debugging any iOS Simulator application: 21 | 22 | ```none 23 | ~$ lldb -n MachOFun #The name of an application on the iOS Simulator that is running 24 | (lldb) po @import UIKit 25 | error: while importing modules: 26 | error: Header search couldn't locate module UIKit 27 | ``` 28 | 29 | This is because LLDB is looking in the MacOS SDK directory for the UIKit headers. This also means many LLDB scripts which rely on this feature will also fail. For example, check out my [search](https://github.com/DerekSelander/LLDB/blob/master/lldb_commands/search.py) command which enumerates for instances of a class when used against this problematic version of LLDB 30 | 31 | ```none 32 | (lldb) search UIViewController # Enumerates the heap for all alive instances of UIViewController 33 | error: 34 | ************************************** 35 | error: error: error: unknown type name 'CFMutableSetRef' 36 | error: unknown type name 'CFMutableSetRef' 37 | error: unknown type name 'CFMutableArrayRef' 38 | error: unknown type name 'CFMutableSetRef' 39 | error: use of undeclared identifier 'CFMutableSetRef' 40 | error: use of undeclared identifier 'CFMutableSetRef' 41 | error: use of undeclared identifier 'CFMutableArrayRef' 42 | error: 'NSClassFromString' has unknown return type; cast the call to its declared return type 43 | ``` 44 | 45 | This is a big problem, since this also cripples a couple chapters in my [Advanced Apple Debugging and Reverse Engineering](https://store.raywenderlich.com/products/advanced-apple-debugging-and-reverse-engineering) :[ 46 | 47 | This means that users would need to use a different version of LLDB or find a way to get around this problem... 48 | 49 | [You can see a tweet thread about this here](https://twitter.com/LOLgrep/status/1055172805535264768) 50 | 51 | ## How 52 | 53 | The TLDR: This code will hunt for the location of a problematic c++ function and attempts to overwrite the pointer in a c++ vtable to code I control, correctly setting the SDK type bassed upon the environment variable **LLDB_SDK** 54 | 55 | If no `LLDB_SDK` environment variable is set, the execution will behave normally in the buggy fashion. 56 | 57 | ### Installation 58 | 59 | 1) clone/copy repo 60 | 2) cd into repo directory 61 | 3) run `make install` 62 | 63 | This will compile a dylib in the current `$PWD` called lldb_fix.dylib and add the following line of code to `~/.lldbinit` 64 | 65 | ``` 66 | plugin load $(PWD)/lldb_fix.dylib 67 | ``` 68 | 69 | LLDB will call this code when it starts executing which performs the lookup of the problematic function and patches it provided you specify a valid `LLDB_SDK` environment variable 70 | 71 | You should make sure the plugin is referenced in ~/.lldbinit with the following command: 72 | 73 | ```none 74 | cat ~/.lldbinit | tail -1 75 | ``` 76 | 77 | ### Uninstall 78 | 79 | `make clean` 80 | 81 | ### Just build it without adding to `~/.lldbinit` 82 | 83 | `make` 84 | 85 | ## Usage 86 | 87 | This code will only spring to life provided you have the `LLDB_SDK` environment variable. LLDB_SDK will expect either **sim**, **mac**, or **ios** 88 | 89 | So if you wanted to attach to an application running on the Simulator called MachOFun, then you can do the following: 90 | 91 | ``` 92 | LLDB_SDK=sim lldb -n MachOFun 93 | ``` 94 | 95 | You'll see this plugin is rather chatty... 96 | 97 | ``` 98 | Found "AddClangModuleCompilationOptionsForSDKType" at: 0x1146c1aac 99 | Found problematic function "AddClangModuleCompilationOptions" at: 0x1145c67fe 100 | Found "PlatformMacOSX" vtable c++ class at: 0x115b87c58 101 | Found problematic function at: PlatformMacOSX`<+0x160> ...patching 102 | Success! 103 | ``` 104 | 105 | Now try a command that imports a module: 106 | ``` 107 | (lldb) po @import UIKit 108 | Caught problematic function, changing "MacOSX" SDK to "iPhoneSimulator" 109 | ``` 110 | 111 | No error is a good thing! Now Chisel's and my commands are fair game: 112 | 113 | ``` 114 | (lldb) search UIViewController 115 | 116 | 117 | 118 | 119 | 120 | ``` 121 | 122 | TADA!!!!!!!!!!!!!!!!!!! 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /lldb_fix.c: -------------------------------------------------------------------------------- 1 | // 2 | // lldb_fix.c 3 | // 4 | // 5 | // Created by Derek Selander on 10/16/18. 6 | // Copyright © 2018 Selander. All rights reserved. 7 | // 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | ////////////////// Declarations //////////////////////////// 18 | 19 | /// Found here https://github.com/llvm-mirror/lldb/blob/818d9df928bde22243bf9603809868869676074a/source/Plugins/Platform/MacOSX/PlatformDarwin.h#L98-L102 20 | typedef enum { 21 | MacOSX = 0, 22 | iPhoneSimulator, 23 | iPhoneOS, 24 | } SDKType; 25 | 26 | /// Will contain pointer to PlatformDarwin::AddClangModuleCompilationOptionsForSDKType 27 | static void* (*AddClangModuleFunc)(void *platform, void * target, void *junk, long sdktype) = NULL; 28 | 29 | static SDKType ResolvedSDK = MacOSX; 30 | 31 | static void dsprintf(const char *format, ...) { 32 | static int quiet_mode = 0; 33 | if (quiet_mode == 0) { 34 | quiet_mode = getenv("DS_QUIET") ? 1 : -1; 35 | } 36 | if (quiet_mode == 1) { return; } 37 | printf("\033[36;1m"); 38 | va_list args; 39 | va_start(args, format); 40 | vprintf(format, args); 41 | va_end( args ); 42 | printf("\033[0m"); 43 | } 44 | 45 | //////////////////////////////////////////////////////////// 46 | 47 | /// Function to intercept "bad" PlatformMacOSX::AddClangModuleCompilationOptions 48 | __attribute__((used)) static void *hellz_yeah(void *platform, void * target, void *junk, SDKType sdktype) { 49 | 50 | dsprintf("Caught problematic function, changing \"MacOSX\" SDK to "); 51 | switch (ResolvedSDK) { 52 | case MacOSX: 53 | dsprintf("\"MacOSX\"\n"); 54 | break; 55 | case iPhoneSimulator: 56 | dsprintf("\"iPhoneSimulator\"\n"); 57 | break; 58 | case iPhoneOS: 59 | dsprintf("\"iPhoneOS\"\n"); 60 | break; 61 | } 62 | 63 | return AddClangModuleFunc(platform, target, junk, ResolvedSDK); 64 | } 65 | 66 | // expects code to be linked to /usr/lib/xcselect.dylib ("xcode-select -p" equivalent) 67 | static char *xcode_path() { 68 | char *input = calloc(0x400, sizeof(char)); 69 | char dunno1, dunno2, dunno3; 70 | void* xcselect_get_developer_dir_path(char *ptr, size_t length, char *a, char*b, char *c); 71 | xcselect_get_developer_dir_path(input, 0x400, &dunno1, &dunno2, &dunno3); 72 | return input; 73 | } 74 | 75 | /// Searches in LLDB.framework for symbols 76 | /// Doesn't use dlopen/dlsym since these functions are non exported :[ 77 | static uintptr_t address_for_function(char *symbol_name) { 78 | static void *handle = NULL; 79 | static int LLDBIndex = 0; 80 | static struct mach_header_64* header; 81 | static struct symtab_command *symtab_cmd = NULL; 82 | static struct segment_command_64 *linkedit_cmd = NULL; 83 | static intptr_t slide; 84 | static long onceToken; 85 | static const char *fullpathToLLDB = NULL; 86 | ///////////////////////////////// 87 | 88 | // a poor man's dispatch_once :] 89 | if (onceToken == 0) { 90 | onceToken = -1; 91 | 92 | // First need to determine the path of Xcode, don't assume it's always /Applications/Xcode.app/ 93 | char path[0x400] = {}; 94 | strcpy(path, xcode_path()); 95 | // This will give me something like /Applications/Xcode.app/Contents/Developer 96 | // replace the ./Developer with the ./SharedFrameworks/LLDB... 97 | char *offset = strstr(path, "/Developer"); 98 | if (!offset) { 99 | dsprintf("unable to parse \"%s\"\n", path); 100 | return 0; 101 | } 102 | strcpy(offset, "/SharedFrameworks/LLDB.framework/Versions/A/LLDB"); 103 | handle = dlopen(path, RTLD_NOW); 104 | if (!handle) { 105 | dsprintf("unable find LLDB.framework at \"%s\"\n", path); 106 | return 0; 107 | } 108 | 109 | // Find the LLDB.framework loaded into the LLDB process 110 | for (int i = 0; i < _dyld_image_count(); i++) { 111 | const char *imagePath = _dyld_get_image_name(i); 112 | if (strstr(imagePath, "SharedFrameworks/LLDB.framework/Versions/A/LLDB") != NULL) { 113 | LLDBIndex = i; 114 | fullpathToLLDB = imagePath; 115 | break; 116 | } 117 | } 118 | 119 | if (!fullpathToLLDB) { 120 | dsprintf("unable find path to LLDB.framework\n"); 121 | return 0; 122 | } 123 | 124 | // Get the Mach-O header's memory address of LLDB.framework 125 | header = (struct mach_header_64*)_dyld_get_image_header(LLDBIndex); 126 | if (header->magic != MH_MAGIC_64) { 127 | dsprintf("LLDB.framework header messed up\n"); 128 | return 0; 129 | } 130 | 131 | slide = _dyld_get_image_vmaddr_slide(LLDBIndex); 132 | uintptr_t cur_pointer = (uintptr_t)header + sizeof(struct mach_header_64); 133 | 134 | // Get the LC_SYMTAB Load Command and linkedit section Load Command 135 | for (int i = 0; i < header->ncmds; i++) { 136 | struct load_command *cur_cmd = (struct load_command *)cur_pointer; 137 | 138 | // LC_SYMTAB 139 | if (cur_cmd->cmd == LC_SYMTAB) { 140 | symtab_cmd = (struct symtab_command *)cur_cmd; 141 | } 142 | 143 | // LC_SEGMENT_64 144 | if (cur_cmd->cmd == LC_SEGMENT_64) { 145 | struct segment_command_64 *segment_cmd = (struct segment_command_64*)cur_cmd; 146 | if (strcmp(segment_cmd->segname, SEG_LINKEDIT) == 0) { 147 | linkedit_cmd = segment_cmd; 148 | } 149 | } 150 | 151 | cur_pointer += cur_cmd->cmdsize; 152 | } 153 | 154 | // Make sure we have the address to both of those Load Commands 155 | if (!symtab_cmd || !linkedit_cmd) { 156 | dsprintf("unable find the appropriate commands to find variables\n"); 157 | return 0; 158 | } 159 | } // 160 | 161 | 162 | // If we got here, we found the location of the symbol table, search for the function 163 | uintptr_t base_pointer = slide + linkedit_cmd->vmaddr - linkedit_cmd->fileoff; 164 | struct nlist_64* nlists = (struct nlist_64 *)(base_pointer + symtab_cmd->symoff); 165 | char* strtab = (char*)(base_pointer + symtab_cmd->stroff); 166 | int numSymbols = symtab_cmd->nsyms; 167 | 168 | for (int i = 0; i < numSymbols; i++) { 169 | struct nlist_64 symbol = nlists[i]; 170 | char *str = &strtab[symbol.n_un.n_strx]; 171 | if (strcmp(str, symbol_name) == 0) { 172 | uintptr_t resolved_address = (((uintptr_t)header) + symbol.n_value); 173 | return resolved_address; 174 | } 175 | } 176 | dsprintf("unable to find symbol \"%s\"\n", symbol_name); 177 | return 0; 178 | } 179 | 180 | 181 | ////////////////////////////////////////////////////////////////////// 182 | // LLDB calls lldb::PluginInitialize(lldb::SBDebugger) on startup // 183 | ////////////////////////////////////////////////////////////////////// 184 | __attribute__((used)) int _ZN4lldb16PluginInitializeENS_10SBDebuggerE(void *dbg) { 185 | 186 | char * override = getenv("LLDB_SDK"); 187 | if (override == NULL) { return 0; } 188 | if (strcmp(override, "ios") == 0) { 189 | ResolvedSDK = iPhoneOS; 190 | } else if (strcmp(override, "sim") == 0) { 191 | ResolvedSDK = iPhoneSimulator; 192 | } else if (strcmp(override, "mac") == 0) { 193 | ResolvedSDK = MacOSX; 194 | } else { 195 | dsprintf("couldn't resolve LLDB_SDK environment variable to a known type, use \"ios\", \"sim\" or \"mac\"\n"); 196 | return 0; 197 | } 198 | 199 | // check for the problematic LLDB version 200 | char *lldb_version = (char*)address_for_function("_LLDBVersionString"); 201 | if (!lldb_version) { return 0; } 202 | if (strcmp(lldb_version, "@(#)PROGRAM:LLDB PROJECT:lldb-1000.11.37.1\n") != 0 && strcmp(lldb_version, "@(#)PROGRAM:LLDB PROJECT:lldb-1000.11.38.2\n") != 0) { 203 | dsprintf("cowardly refusing to patch LLDB.framework, problem version is 1000.11.37.1 (Xcode 10.0) or lldb-1000.11.38.2 (Xcode 10.1), you are running \"%s\"\n", lldb_version); 204 | return 0; 205 | } 206 | 207 | // the address we want to call... AddClangModuleCompilationOptionsForSDKType 208 | //PlatformMacOSX::AddClangModuleCompilationOptions eventually calls this 209 | AddClangModuleFunc = (void*)address_for_function("__ZN14PlatformDarwin42AddClangModuleCompilationOptionsForSDKTypeEPN12lldb_private6TargetERNSt3__16vectorINS3_12basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEENS8_ISA_EEEENS_7SDKTypeE"); 210 | if (!AddClangModuleFunc) { return 0; } 211 | dsprintf("Found \"AddClangModuleCompilationOptionsForSDKType\" at: %p\n", AddClangModuleFunc); 212 | 213 | // the address we want to intercept... which sets rcx to 0, then jmps to above function 214 | uintptr_t buggyFunction = address_for_function("__ZN14PlatformMacOSX32AddClangModuleCompilationOptionsEPN12lldb_private6TargetERNSt3__16vectorINS3_12basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEENS8_ISA_EEEE"); 215 | if (!buggyFunction) { return 0; } 216 | dsprintf("Found problematic function \"AddClangModuleCompilationOptions\" at: %p\n", (void *)buggyFunction); 217 | 218 | // the vtable for PlatformMacOSX, start here and look for the "buggy function" address 219 | uintptr_t platformMacOSXClassBase = address_for_function("__ZTV14PlatformMacOSX"); 220 | if (!platformMacOSXClassBase) { return 0; } 221 | dsprintf("Found \"PlatformMacOSX\" vtable c++ class at: %p\n", (void *)platformMacOSXClassBase); 222 | 223 | // If this class has more than 100 members, screw it, I give up 224 | for (int i = 0; i < 100 * sizeof(void *); i+=sizeof(void*)) { 225 | if (*(long *)(platformMacOSXClassBase + i) == buggyFunction) { 226 | dsprintf("Found problematic function at: PlatformMacOSX`<+0x%x> ...patching\n", i); 227 | *(long *)(platformMacOSXClassBase + i) = (long)&hellz_yeah; 228 | dsprintf("Success!\n"); 229 | } 230 | } 231 | return 1; 232 | } 233 | 234 | --------------------------------------------------------------------------------