├── .gitignore ├── bfinject.tar ├── bfinject4realz ├── jtool.liberios ├── dylibs ├── iSpy.dylib ├── cycript.dylib ├── simple.dylib ├── bfdecrypt.dylib └── cycript-runner.dylib ├── Makefile ├── lorgnette.h ├── lorgnette-structs.h ├── bfinject ├── LICENSE ├── bfinject4realz.mm ├── README.md └── lorgnette.mm /.gitignore: -------------------------------------------------------------------------------- 1 | *.o 2 | *.dSYM 3 | -------------------------------------------------------------------------------- /bfinject.tar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klmitchell2/bfinject/HEAD/bfinject.tar -------------------------------------------------------------------------------- /bfinject4realz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klmitchell2/bfinject/HEAD/bfinject4realz -------------------------------------------------------------------------------- /jtool.liberios: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klmitchell2/bfinject/HEAD/jtool.liberios -------------------------------------------------------------------------------- /dylibs/iSpy.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klmitchell2/bfinject/HEAD/dylibs/iSpy.dylib -------------------------------------------------------------------------------- /dylibs/cycript.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klmitchell2/bfinject/HEAD/dylibs/cycript.dylib -------------------------------------------------------------------------------- /dylibs/simple.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klmitchell2/bfinject/HEAD/dylibs/simple.dylib -------------------------------------------------------------------------------- /dylibs/bfdecrypt.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klmitchell2/bfinject/HEAD/dylibs/bfdecrypt.dylib -------------------------------------------------------------------------------- /dylibs/cycript-runner.dylib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/klmitchell2/bfinject/HEAD/dylibs/cycript-runner.dylib -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DYLIBS=bfdecrypt.dylib 2 | BINARY_NAME=bfinject4realz 3 | BFINJECT_SRC=bfinject4realz.mm 4 | LORGNETTE_SRC=lorgnette.m 5 | OBJS=$(addsuffix .o,$(basename $(BFINJECT_SRC))) \ 6 | $(addsuffix .o,$(basename $(LORGNETTE_SRC))) 7 | 8 | SDK=$(shell xcodebuild -showsdks| grep iphoneos | awk '{print $$4}') 9 | SDK_PATH=$(shell xcrun --sdk $(SDK) --show-sdk-path) 10 | 11 | CC=$(shell xcrun --sdk $(SDK) --find clang) 12 | CXX=$(shell xcrun --sdk $(SDK) --find clang++) 13 | LD=$(CXX) 14 | INCLUDES=-I $(SDK_PATH)/usr/include 15 | ARCHS=-arch arm64 16 | 17 | IOS_FLAGS=-isysroot $(SDK_PATH) -miphoneos-version-min=11.0 18 | CFLAGS=$(IOS_FLAGS) -g $(ARCHS) $(INCLUDES) -Wdeprecated-declarations 19 | CXXFLAGS=$(IOS_FLAGS) -g $(ARCHS) $(INCLUDES) -Wdeprecated-declarations 20 | 21 | FRAMEWORKS=-framework CoreFoundation -framework IOKit -framework Foundation -framework JavaScriptCore -framework UIKit -framework Security -framework CFNetwork -framework CoreGraphics 22 | LIBS=-lobjc -L$(SDK_PATH)/usr/lib -lz -lsqlite3 -lxml2 -lz -ldl -lSystem #$(SDK_PATH)/usr/lib/libstdc++.tbd 23 | LDFLAGS=$(IOS_FLAGS) $(ARCHS) $(FRAMEWORKS) $(LIBS) -ObjC -all_load 24 | MAKE=$(shell xcrun --sdk $(SDK) --find make) 25 | 26 | DEVELOPERID=$(shell security find-identity -v -p codesigning | grep "iPhone Developer:" |awk '{print $$2}') 27 | 28 | all: $(BINARY_NAME) finish 29 | 30 | $(BINARY_NAME): $(OBJS) 31 | $(LD) $(LDFLAGS) $^ -o $@ 32 | 33 | finish: 34 | tar cf bfinject.tar jtool.liberios bfinject bfinject4realz dylibs/ 35 | 36 | %.o: %.mm $(DEPS) 37 | $(CXX) -c $(CXXFLAGS) $< -o $@ 38 | 39 | %.o: %.c $(DEPS) 40 | $(CC) -c $(CFLAGS) $< -o $@ 41 | 42 | webroot.o: webroot.c 43 | 44 | clean: 45 | rm -f $(OBJS) 2>&1 > /dev/null 46 | rm -f $(BINARY_NAME) 2>&1 > /dev/null 47 | 48 | -------------------------------------------------------------------------------- /lorgnette.h: -------------------------------------------------------------------------------- 1 | // 2 | // lorgnette.h 3 | // liblorgnette 4 | // 5 | // Created by Dmitry Rodionov on 9/24/14. 6 | // Copyright (c) 2014 rodionovd. All rights reserved. 7 | // 8 | #include 9 | #pragma once 10 | /** 11 | * @abstract 12 | * Locate a symbol inside an arbitrary process' address space. 13 | * 14 | * @note 15 | * This function iterates local symbols first and only then it looks for symbols 16 | * in linked libraries. 17 | * 18 | * @param target 19 | * The target process to inspect. 20 | * @param symbol_name 21 | * The name of the symbol to find. This parameter must not be NULL. 22 | * 23 | * @return 24 | * An address of the given symbol within the given process, or 0 (zero) if this symbol 25 | * could not be found. 26 | * 27 | * @b Examples 28 | * 29 | * Find a @p dlopen symbol address within the current task memory space 30 | * @code 31 | * addr = lorgnette_lookup(mach_task_self(), "dlopen"); 32 | * @endcode 33 | * Find a @p glob_var0 symbol address inside a remote process 34 | * @code 35 | * addr = lorgnette_lookup(some_task, "glob_var0"); 36 | * @endcode 37 | */ 38 | vm_address_t lorgnette_lookup(task_t target, const char *symbol_name); 39 | 40 | /** 41 | * @abstract 42 | * Locate a symbol within a particular image inside an alien process. 43 | * 44 | * @param target 45 | * The target process to inspect. 46 | * @param symbol_name 47 | * The name of the symbol to find. This parameter must not be NULL. 48 | * @param image_name 49 | * The name of the host image of the given symbol. This may be NULL. 50 | * The image name should be either a full file path or just a file base name. 51 | * 52 | * @return 53 | * An address of the given symbol within the given process, or 0 (zero) if this symbol 54 | * could not be found [within the given image, if @p image_name is not NULL]. 55 | * 56 | * @see lorgnette_lookup() 57 | */ 58 | vm_address_t lorgnette_lookup_image(task_t target, const char *symbol_name, const char *image_name); 59 | int _image_headers_in_task(task_t task, 60 | const char *suggested_image_name, 61 | vm_address_t *headers, 62 | uint32_t *count, 63 | uint64_t *shared_cache_slide); -------------------------------------------------------------------------------- /lorgnette-structs.h: -------------------------------------------------------------------------------- 1 | // 2 | // lorgnette-structs.h 3 | // liblorgnette 4 | // 5 | // Created by Dmitry Rodionov on 9/26/14. 6 | // Copyright (c) 2014 rodionovd. All rights reserved. 7 | // 8 | 9 | #pragma once 10 | 11 | struct dyld_image_info_32 { 12 | uint32_t imageLoadAddress; 13 | uint32_t imageFilePath; 14 | uint32_t imageFileModDate; 15 | }; 16 | 17 | struct load_command_with_segname { 18 | uint32_t cmd; 19 | uint32_t cmdsize; 20 | uint32_t segname; 21 | }; 22 | 23 | struct dyld_image_info_64 { 24 | uint64_t imageLoadAddress; 25 | uint64_t imageFilePath; 26 | uint64_t imageFileModDate; 27 | }; 28 | 29 | struct dyld_all_image_infos_32 { 30 | uint32_t version; 31 | uint32_t infoArrayCount; 32 | uint32_t infoArray; 33 | uint32_t notification; 34 | bool processDetachedFromSharedRegion; 35 | bool libSystemInitialized; 36 | uint32_t dyldImageLoadAddress; 37 | uint32_t jitInfo; 38 | uint32_t dyldVersion; 39 | uint32_t errorMessage; 40 | uint32_t terminationFlags; 41 | uint32_t coreSymbolicationShmPage; 42 | uint32_t systemOrderFlag; 43 | uint32_t uuidArrayCount; 44 | uint32_t uuidArray; 45 | uint32_t dyldAllImageInfosAddress; 46 | uint32_t initialImageCount; 47 | uint32_t errorKind; 48 | uint32_t errorClientOfDylibPath; 49 | uint32_t errorTargetDylibPath; 50 | uint32_t errorSymbol; 51 | uint32_t sharedCacheSlide; 52 | uint8_t sharedCacheUUID[16]; 53 | uint32_t sharedCacheBaseAddress; 54 | uint64_t infoArrayChangeTimestamp; 55 | uint32_t dyldPath; 56 | uint32_t notifyMachPorts[8]; 57 | uint32_t reserved[5]; 58 | uint32_t compact_dyld_image_info_addr; 59 | uint32_t compact_dyld_image_info_size; 60 | }; 61 | 62 | struct dyld_all_image_infos_64 { 63 | uint32_t version; 64 | uint32_t infoArrayCount; 65 | uint64_t infoArray; 66 | uint64_t notification; 67 | bool processDetachedFromSharedRegion; 68 | bool libSystemInitialized; 69 | uint32_t paddingToMakeTheSizeCorrectOn32bitAndDoesntAffect64b; // NOT PART OF DYLD_ALL_IMAGE_INFOS! 70 | uint64_t dyldImageLoadAddress; 71 | uint64_t jitInfo; 72 | uint64_t dyldVersion; 73 | uint64_t errorMessage; 74 | uint64_t terminationFlags; 75 | uint64_t coreSymbolicationShmPage; 76 | uint64_t systemOrderFlag; 77 | uint64_t uuidArrayCount; 78 | uint64_t uuidArray; 79 | uint64_t dyldAllImageInfosAddress; 80 | uint64_t initialImageCount; 81 | uint64_t errorKind; 82 | uint64_t errorClientOfDylibPath; 83 | uint64_t errorTargetDylibPath; 84 | uint64_t errorSymbol; 85 | uint64_t sharedCacheSlide; 86 | uint8_t sharedCacheUUID[16]; 87 | uint64_t sharedCacheBaseAddress; 88 | uint64_t infoArrayChangeTimestamp; 89 | uint64_t dyldPath; 90 | uint32_t notifyMachPorts[8]; 91 | uint64_t reserved[9]; 92 | uint64_t compact_dyld_image_info_addr; 93 | uint64_t compact_dyld_image_info_size; 94 | }; 95 | -------------------------------------------------------------------------------- /bfinject: -------------------------------------------------------------------------------- 1 | #!/jb/bin/bash 2 | 3 | CYCRIPT_PORT=1337 4 | 5 | function help { 6 | echo "Syntax: $0 [-p PID | -P appname] [-l /path/to/yourdylib | -L feature]" 7 | echo 8 | echo For example: 9 | echo " $0 -P Reddit.app -l /path/to/evil.dylib # Injects evil.dylib into the Reddit app" 10 | echo " or" 11 | echo " $0 -p 1234 -L cycript # Inject Cycript into PID" 12 | echo " or " 13 | echo " $0 -p 4566 -l /path/to/evil.dylib # Injects the .dylib of your choice into PID" 14 | echo 15 | echo "Instead of specifying the PID with -p, bfinject can search for the correct PID based on the app name." 16 | echo "Just enter \"-P identifier\" where \"identifier\" is a string unique to your app, e.g. \"fing.app\"." 17 | echo 18 | echo Available features: 19 | echo " cycript - Inject and run Cycript" 20 | echo " decrypt - Create a decrypted copy of the target app" 21 | echo " test - Inject a simple .dylib to make an entry in the console log" 22 | echo " ispy - Inject iSpy. Browse to http://:31337/" 23 | echo 24 | } 25 | 26 | 27 | # 28 | # check args 29 | # 30 | if [ "$1" != "-p" ] && [ "$1" != "-P" ]; then 31 | help 32 | exit 1 33 | fi 34 | 35 | if [ "$3" != "-l" -a "$3" != "-L" ]; then 36 | help 37 | exit 1 38 | fi 39 | 40 | if [ "$1" == "-p" ]; then 41 | PID=$2 42 | else 43 | count=`ps axwww|grep "$2"|grep container|grep '.app'|grep -v grep |wc -l|sed 's/ //g'` 44 | if [ "$count" != "1" ]; then 45 | echo "[!] \"$2\" was not uniquely found, please check your criteria." 46 | exit 1 47 | fi 48 | PID=`ps awwwx|grep "$2"|grep container|grep '.app'|grep -v grep|sed 's/^\ *//g'|cut -f1 -d\ ` 49 | bad=1 50 | case "$PID" in 51 | ''|*[!0-9]*) bad=1 ;; 52 | *) bad=0 ;; 53 | esac 54 | if [ "$bad" != "0" ]; then 55 | echo "[!] Process not found for string \"$3\"" 56 | exit 1 57 | fi 58 | fi 59 | 60 | declare -a DYLIBS 61 | 62 | if [ "$3" == "-l" ]; then 63 | FEATURE="" 64 | DYLIBS=("$4") 65 | else 66 | FEATURE="$4" 67 | 68 | case "$FEATURE" in 69 | cycript) 70 | DYLIBS=(dylibs/cycript.dylib dylibs/cycript-runner.dylib) 71 | ;; 72 | 73 | decrypt) 74 | DYLIBS=(dylibs/bfdecrypt.dylib) 75 | ;; 76 | 77 | test) 78 | DYLIBS=(dylibs/simple.dylib) 79 | ;; 80 | ispy) 81 | DYLIBS=(dylibs/iSpy.dylib) 82 | ;; 83 | iSpy) 84 | DYLIBS=(dylibs/iSpy.dylib) 85 | ;; 86 | default) 87 | help 88 | exit 1 89 | ;; 90 | esac 91 | fi 92 | 93 | 94 | # 95 | # Be a good netizen and tidy up your litter 96 | # 97 | function clean_up { 98 | if [ -d "$DYLIB_DIR" ] && [ "$DYLIB_DIR" != "/System/Library/Frameworks" ]; then 99 | rm -rf "$DYLIB_DIR" >/dev/null 2>&1 100 | fi 101 | rm -f "$RANDOM_NAME" > /dev/null 2>&1 102 | rm -f /electra/usr/local/bin/bfinject4realz > /dev/null 2>&1 103 | rm -f /electra/usr/local/bin/jtool.liberios > /dev/null 2>&1 104 | } 105 | 106 | 107 | # 108 | # Entitlements for dylib injection and for our injector binary. 109 | # 110 | if [ ! -f entitlements.xml ]; then 111 | cat > entitlements.xml << EOF 112 | 113 | 114 | 115 | 116 | platform-application 117 | 118 | get-task-allow 119 | 120 | task_for_pid-allow 121 | 122 | com.apple.system-task-ports 123 | 124 | 125 | 126 | EOF 127 | fi 128 | 129 | 130 | # 131 | # Detect LiberiOS vs Electra 132 | # 133 | if [ -f /electra/inject_criticald ]; then 134 | # This is Electra >= 1.0.2 135 | echo "[+] Electra detected." 136 | mkdir -p /electra/usr/local/bin 137 | cp jtool.liberios /electra/usr/local/bin/ 138 | chmod +x /electra/usr/local/bin/jtool.liberios 139 | JTOOL=/electra/usr/local/bin/jtool.liberios 140 | cp bfinject4realz /electra/usr/local/bin/ 141 | INJECTOR=/electra/usr/local/bin/bfinject4realz 142 | elif [ -f /bootstrap/inject_criticald ]; then 143 | # This is Electra < 1.0.2 144 | echo "[+] Electra detected." 145 | cp jtool.liberios /bootstrap/usr/local/bin/ 146 | chmod +x /bootstrap/usr/local/bin/jtool.liberios 147 | JTOOL=/bootstrap/usr/local/bin/jtool.liberios 148 | cp bfinject4realz /bootstrap/usr/local/bin/ 149 | INJECTOR=/bootstrap/usr/local/bin/bfinject4realz 150 | elif [ -f /jb/usr/local/bin/jtool ]; then 151 | # This is LiberiOS 152 | echo "[+] Liberios detected" 153 | JTOOL=jtool 154 | INJECTOR=`pwd`/bfinject4realz 155 | else 156 | echo "[!] Unknown jailbreak. Aborting." 157 | exit 1 158 | fi 159 | 160 | 161 | # 162 | # Do the actual injection into the remote process 163 | # 164 | for DYLIB in ${DYLIBS[@]}; do 165 | if [ ! -f "$DYLIB" ]; then 166 | echo "$DYLIB" doesn\'t exist 167 | clean_up 168 | exit 1 169 | fi 170 | 171 | # Use random filenames to avoid cached binaries causing "Killed: 9" messages. 172 | RAND=`dd if=/dev/random bs=1 count=16 2>/dev/null | md5sum` 173 | RANDOM_NAME="${INJECTOR%/*}/`dd if=/dev/random bs=1 count=16 2>/dev/null | md5sum`" 174 | DYLIB_DIR="/System/Library/Frameworks/${RAND}.framework" 175 | DYLIB_PATH="$DYLIB_DIR/$RAND.dylib" 176 | 177 | # We'll give the injector as a random filename 178 | cp "$INJECTOR" "$RANDOM_NAME" 179 | chmod +x "$RANDOM_NAME" 180 | 181 | # 182 | # Find the full path to the target app binary 183 | # 184 | BINARY=`ps -o pid,command $PID|tail -n1|sed 's/^\ *//g'|cut -f2- -d\ ` 185 | if [ "$BINARY" == "COMMAND" ]; then 186 | echo "[!] ERROR: PID $PID not found." 187 | clean_up 188 | exit 1 189 | fi 190 | echo "[+] Injecting into '$BINARY'" 191 | 192 | # 193 | # Get the Team ID that signed the target app's binary. 194 | # We need this so we can re-sign the injected .dylib to fool the kernel 195 | # into assuming the .dylib is part of the injectee bundle. 196 | # This allows is to map the .dylib into the target's process space via dlopen(). 197 | # 198 | echo "[+] Getting Team ID from target application..." 199 | TEAMID=`$JTOOL --ent "$BINARY" 2> /dev/null | grep -A1 'com.apple.developer.team-identifier' | tail -n1 |sed 's/ //g'|cut -f2 -d\>|cut -f1 -d\<` 200 | if [ "$TEAMID" == "" ]; then 201 | echo "[+] WARNING: No Team ID found. Continuing regardless, but expect weird stuff to happen." 202 | fi 203 | 204 | # 205 | # Move the injectee dylib to a sandbox-friendly location 206 | # 207 | mkdir "$DYLIB_DIR" 208 | cp "$DYLIB" "$DYLIB_PATH" 209 | 210 | # 211 | # Thin the binary so that it's not FAT and contains only an arm64 image 212 | echo "[+] Thinning dylib into non-fat arm64 image" 213 | $JTOOL -arch arm64 -e arch "$DYLIB_PATH" >/dev/null 2>&1 214 | if [ "$?" == "0" ]; then 215 | rm -f "$DYLIB_PATH" 216 | DYLIB_PATH="${DYLIB_PATH}.arch_arm64" 217 | else 218 | echo "[!] WARNING: Wasn't able to thin the dylib." 219 | fi 220 | 221 | # 222 | # Sign platform entitlements and Team ID into our dylib 223 | # 224 | echo "[+] Signing injectable .dylib with Team ID $TEAMID and platform entitlements..." 225 | $JTOOL --sign platform --ent entitlements.xml --inplace --teamid "$TEAMID" "$DYLIB_PATH" > /dev/null 2>&1 226 | if [ "$?" != "0" ]; then 227 | echo jtool dylib signing error. barfing. 228 | clean_up 229 | exit 1 230 | fi 231 | 232 | # 233 | # Sign the randomly-renamed injector binary with platform entitlements 234 | # 235 | $JTOOL --sign platform --ent entitlements.xml --inplace "$RANDOM_NAME" >/dev/null 2>&1 236 | if [ "$?" != "0" ]; then 237 | echo jtool "$RANDOM_NAME" signing error. barfing. 238 | clean_up 239 | exit 1 240 | fi 241 | 242 | # 243 | # Inject! 244 | # 245 | "$RANDOM_NAME" "$PID" "$DYLIB_PATH" 246 | done 247 | 248 | # 249 | # EOF 250 | # 251 | echo "[+] So long and thanks for all the fish." 252 | clean_up 253 | exit 0 254 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /bfinject4realz.mm: -------------------------------------------------------------------------------- 1 | /* 2 | bfinject - Inject shared libraries into running App Store apps on iOS 11.x < 11.2 3 | https://github.com/BishopFox/bfinject 4 | 5 | Carl Livitt @ Bishop Fox 6 | */ 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #import 19 | #include 20 | #include 21 | #include "lorgnette.h" 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #define STACK_SIZE ((1024 * 1024) * 512) // 512MB stack 31 | #define ROP_ret "\xc0\x03\x5f\xd6" 32 | #define ALIGNSIZE 8 33 | #define align64(x) ( ((x) + ALIGNSIZE - 1) & ~(ALIGNSIZE - 1) ) 34 | #define UNINITIALIZED 0x11223344 35 | 36 | static mach_port_t task = UNINITIALIZED; 37 | static thread_act_port_array_t threadList = (thread_act_port_array_t)UNINITIALIZED; 38 | static mach_msg_type_number_t threadCount = (mach_msg_type_number_t)UNINITIALIZED; 39 | static thread_t thread = UNINITIALIZED; 40 | static vm_address_t remoteStack = UNINITIALIZED; 41 | static void *gadgetAddress = (void *)UNINITIALIZED; 42 | static uint64_t dlopenAddress = 0; 43 | 44 | static void get_task(int pid); 45 | static char *readmem(uint64_t addr, vm_size_t *len); 46 | static void *find_gadget(const char *gadget, int gadgetLen); 47 | static uint64_t ropcall(int pid, const char *symbol, const char *symbolLib, const char *argMap, uint64_t *arg1, uint64_t *arg2, uint64_t *arg3, uint64_t *arg4); 48 | 49 | 50 | /* 51 | Entry point 52 | */ 53 | int main(int argc, char ** argv) 54 | { 55 | char argMap[128]; 56 | char *pathToAppBinary; 57 | int pid; 58 | uint64_t retval; 59 | kern_return_t kret; 60 | 61 | if(argc == 3) { 62 | pid = atoi(argv[1]); 63 | pathToAppBinary = argv[2]; 64 | } else { 65 | printf("bfinject -=[ https://www.bishopfox.com ]=-\nSyntax: %s \n", argv[0]); 66 | exit(1); 67 | } 68 | 69 | printf("[bfinject4realz] Calling task_for_pid() for PID %d.\n", pid); 70 | get_task(pid); 71 | 72 | printf("[bfinject4realz] Calling thread_create() on PID %d\n", pid); 73 | if((kret = thread_create(task, &thread)) != KERN_SUCCESS) { 74 | printf("[bfinject4realz] ERROR: thread_create() returned %d\n\n", kret); 75 | printf("[bfinject4realz] Failed to create thread in remote process.\n This most likely is caused by \"Tweaks\" being enabled in Electra.\n Please try rebooting and re-jailbreaking with \"Tweaks\" disabled.\n"); 76 | exit(1); 77 | } 78 | 79 | // Find an infinite loop ROP gadget in the remote process 80 | printf("[bfinject4realz] Looking for ROP gadget... ");fflush(stdout); 81 | if(!((gadgetAddress = (void *)(uint64_t)find_gadget(ROP_ret, 4)))) { 82 | printf("[bfinject4realz] WAT: Infinite loop RET gadget not found :(\n"); 83 | return 0; 84 | } 85 | printf("found at 0x%llx\n", (uint64_t)gadgetAddress); 86 | 87 | // Create a new memory section in the remote process. Mark it RW like a stack should be. 88 | kret = vm_allocate(task, &remoteStack, STACK_SIZE, VM_FLAGS_ANYWHERE); 89 | if (kret != KERN_SUCCESS) { 90 | printf("[bfinject4realz] ERROR: Unable to vm_allocate() a new stack in the remote process.\n"); 91 | printf("[bfinject4realz] The error was: %s\n", mach_error_string(kret)); 92 | return 0; 93 | } 94 | kret = vm_protect(task, remoteStack, STACK_SIZE, FALSE, VM_PROT_WRITE|VM_PROT_READ); 95 | if(kret != KERN_SUCCESS) { 96 | printf("[bfinject4realz] ERROR: Unable to vm_protect(VM_PROT_WRITE|VM_PROT_READ) the new stack.\n"); 97 | printf("[bfinject4realz] The error was: %s\n", mach_error_string(kret)); 98 | return 0; 99 | } 100 | printf("[bfinject4realz] Fake stack frame at %p\n", (void *)remoteStack); 101 | 102 | // Call _pthread_set_self(NULL) to avoid crashing in dlopen() later 103 | ropcall(pid, "_pthread_set_self", "libsystem_pthread.dylib", (char *)"u", 0, 0, 0, 0); 104 | 105 | // Call dlopen() to load the requested shared library 106 | snprintf(argMap, 127, "s%luu", strlen(pathToAppBinary)); 107 | if((retval = ropcall(pid, "dlopen", "libdyld.dylib", argMap, (uint64_t *)pathToAppBinary, (uint64_t *)(uint64_t )(RTLD_NOW|RTLD_GLOBAL), 0, 0)) == 0) { 108 | // If dlopen() failed, we call dlerror() to see what went wrong 109 | printf("[bfinject4realz] ERROR: dlopen() failed to load the dylib.returned 0x%llx (%s)\n", (uint64_t)retval, (retval==0)?"FAILURE":"success!"); 110 | 111 | retval = ropcall(pid, "dlerror", "libdyld.dylib", "", 0, 0, 0, 0); 112 | 113 | vm_size_t bytesRead = 4096; 114 | char *buf = readmem(retval, &bytesRead); 115 | printf("[bfinject4realz] dlerror() returned: %s\n", buf); 116 | free(buf); 117 | } else { 118 | printf("[bfinject4realz] Success! Library was loaded at 0x%llx\n", (uint64_t)retval); 119 | /* 120 | // if we're decrypting, try to get the path to the saved IPA file as a user convenience 121 | uint64_t decryptedIPAPathAddress = lorgnette_lookup_image(task, "decryptedIPAPath", "bfdecrypt.dylib"); 122 | if(decryptedIPAPathAddress) { 123 | uint64_t pathLenAddress = lorgnette_lookup_image(task, "pathLen", "bfdecrypt.dylib"); 124 | vm_size_t readLen = 8; 125 | uint64_t *pathLen = (uint64_t *)readmem(pathLenAddress, &readLen); 126 | readLen = (vm_size_t)*pathLen; 127 | char *decryptedIPAPath = readmem(decryptedIPAPathAddress, &readLen); 128 | printf("[bfinject4realz] In a few seconds the IPA will be saved to '%s'\n", decryptedIPAPath); 129 | free(pathLen); 130 | free(decryptedIPAPath); 131 | } 132 | */ 133 | } 134 | 135 | // Clean up the mess 136 | thread_terminate(thread); 137 | vm_deallocate(task, remoteStack, STACK_SIZE); 138 | 139 | exit(0); 140 | } 141 | 142 | 143 | /* 144 | find_gadget traverses each of the remote process' pages marked R-X looking for 145 | the specified ROP gadget. 146 | */ 147 | static void *find_gadget(const char *gadget, int gadgetLen) { 148 | kern_return_t kr; 149 | vm_size_t size = 0; 150 | uint64_t targetFunctionAddress = 0; 151 | 152 | if(dlopenAddress == 0) { 153 | dlopenAddress = lorgnette_lookup_image(task, "dlopen", "libdyld.dylib"); 154 | if(!dlopenAddress) { 155 | printf("[bfinject4realz] ERROR: Could not find a symbol called 'dlopen'\n"); 156 | return 0; 157 | } 158 | } 159 | targetFunctionAddress = dlopenAddress; 160 | size = 65536; 161 | 162 | char *buf = (char *)malloc((size_t)size); 163 | char *origBuf = buf; 164 | if(!buf) { 165 | printf("[bfinject4realz] ERROR: malloc fail\n"); 166 | return NULL; 167 | } 168 | 169 | // read the remote R-X pages into the local buffer 170 | kr = vm_read_overwrite(task, targetFunctionAddress, size, (vm_address_t)buf, &size); 171 | if(kr != KERN_SUCCESS) { 172 | printf("[bfinject4realz] ERROR: vm_read_overwrite fail\n"); 173 | free(origBuf); 174 | return NULL; 175 | } 176 | 177 | // grep for the gadget 178 | while(buf < origBuf + size) { 179 | char *ptr = (char *)memmem((const void *)buf, (size_t)size - (size_t)(buf - origBuf), (const void *)gadget, (size_t)gadgetLen); 180 | 181 | // Make sure the gadget is aligned correctly 182 | if(ptr) { 183 | vm_size_t offset = (vm_size_t)(ptr - origBuf); 184 | vm_address_t gadgetAddrReal = targetFunctionAddress + offset; 185 | if(((uint64_t)gadgetAddrReal % 8) == 0) { 186 | free(origBuf); 187 | return (void *)gadgetAddrReal; 188 | } 189 | else { 190 | // The gadget we found isn't 64-bit aligned, so we can't use it. Keep trying. 191 | buf = ptr + gadgetLen; 192 | } 193 | } 194 | } 195 | free(origBuf); 196 | return NULL; 197 | } 198 | 199 | 200 | static void get_task(int pid) { 201 | // Get port for target app's process 202 | if(task == UNINITIALIZED) { 203 | int kret = task_for_pid(mach_task_self(), pid, &task); 204 | if(kret != KERN_SUCCESS) { 205 | printf("[bfinject4realz] ERROR: task_for_pid() failed with message %s!\n",mach_error_string(kret)); 206 | exit(1); 207 | } 208 | } 209 | } 210 | 211 | 212 | /* 213 | Call any loaded function in the remote process. 214 | We use it to call _pthread_set_self(), dlopen() and, if needed, dlerror(). 215 | Supports up to 4 parameters, but should really use varargs. 216 | */ 217 | static uint64_t ropcall(int pid, const char *symbol, const char *symbolLib, const char *argMap, uint64_t *arg1, uint64_t *arg2, uint64_t *arg3, uint64_t *arg4) { 218 | kern_return_t kret; 219 | arm_thread_state64_t state = {0}; 220 | mach_msg_type_number_t stateCount = ARM_THREAD_STATE64_COUNT; 221 | uint64_t targetFunctionAddress = 0; 222 | 223 | // Find the address of the function to be called in the remote process 224 | //printf("[bfinject4realz] Looking for '%s' in the target process...\n", symbol); 225 | if(strcmp(symbol, "dlopen") == 0) { 226 | if(dlopenAddress == 0) { 227 | dlopenAddress = lorgnette_lookup_image(task, "dlopen", "libdyld.dylib"); 228 | if(!dlopenAddress) { 229 | printf("[bfinject4realz] ERROR: Could not find a symbol called '%s'\n", symbol); 230 | return 0; 231 | } 232 | } 233 | targetFunctionAddress = dlopenAddress; 234 | } else { 235 | targetFunctionAddress = lorgnette_lookup_image(task, symbol, symbolLib); 236 | } 237 | 238 | // Setup a new registers for the call to target function 239 | //printf("[bfinject4realz] Setting CPU registers with function and ROP addresses\n"); 240 | state.__pc = (uint64_t)targetFunctionAddress; // We'll be jumping to here 241 | state.__lr = (uint64_t)gadgetAddress; // Address of the infinite loop RET gadget 242 | state.__sp = (uint64_t)(remoteStack + STACK_SIZE) - (STACK_SIZE / 4); // Put $sp bang in the middle of the fake stack frame 243 | state.__fp = state.__sp; 244 | 245 | // Allocate a STACK_SIZE local buffer that we'll populate before copying it to the remote process 246 | char *localFakeStack = (char *)malloc((size_t)STACK_SIZE); 247 | 248 | // Ok, now we handle the parameters being passed 249 | char *argp = (char *)argMap; 250 | char *stackPtr = localFakeStack; 251 | uint64_t paramLen = 0; 252 | 253 | for(int param = 0; param <= 4; param++) { 254 | if(!(*argp)) 255 | break; 256 | 257 | switch(*argp) { 258 | case 's': // char * string 259 | int numDigits; 260 | char tmpBuf[6]; 261 | 262 | argp++; 263 | numDigits = 0; 264 | while(*argp >= '0' && *argp <= '9') { 265 | if(++numDigits == 6) { 266 | printf("[bfinject4realz] ERROR: String too long, param=%d\n", param); 267 | return 0; 268 | } 269 | tmpBuf[numDigits-1] = *(argp++); 270 | } 271 | tmpBuf[numDigits] = 0; 272 | 273 | paramLen = strtoull(tmpBuf, NULL, 10); 274 | 275 | uint64_t *argPtr; 276 | if(param==0) 277 | argPtr = arg1; 278 | if(param==1) 279 | argPtr = arg2; 280 | if(param==2) 281 | argPtr = arg3; 282 | if(param==3) 283 | argPtr = arg4; 284 | 285 | memcpy(stackPtr, argPtr, paramLen); 286 | 287 | state.__x[param] = (uint64_t)remoteStack + (stackPtr - localFakeStack); 288 | stackPtr += 16; 289 | stackPtr += paramLen; 290 | stackPtr = (char *)align64((uint64_t)stackPtr); 291 | 292 | break; 293 | 294 | case 'u': // uint64_t 295 | state.__x[param] = (param==0)?(uint64_t)arg1:(param==1)?(uint64_t)arg2:(param==2)?(uint64_t)arg3:(uint64_t)arg4; 296 | argp++; 297 | break; 298 | 299 | default: 300 | printf("[bfinject4realz] ERROR: Uknown argument type: '%c'\n", *argp); 301 | return 0; 302 | } 303 | } 304 | 305 | // Copy fake stack buffer over to the new stack frame in the remote process 306 | kret = vm_write(task, remoteStack, (vm_address_t)localFakeStack, STACK_SIZE); 307 | free(localFakeStack); 308 | 309 | if(kret != KERN_SUCCESS) { 310 | printf("[bfinject4realz] ERROR: Unable to copy fake stack to target process. Error: %s\n", mach_error_string(kret)); 311 | return 0; 312 | } 313 | 314 | // start the remote thread with the fake stack and tweaked registers 315 | printf("[bfinject4realz] Calling %s() at %p...\n", symbol, (void *)targetFunctionAddress); 316 | thread_set_state(thread, ARM_THREAD_STATE64, (thread_state_t)&state, ARM_THREAD_STATE64_COUNT); 317 | thread_resume(thread); 318 | 319 | // Wait for the remote thread to RET to the infinite loop gadget... 320 | while(1) { 321 | usleep(250000); 322 | thread_get_state(thread, ARM_THREAD_STATE64, (thread_state_t)&state, &stateCount); 323 | 324 | // are we in the infinite loop gadget yet? 325 | if(state.__pc == (uint64_t)gadgetAddress) { 326 | printf("[bfinject4realz] Returned from '%s'\n", symbol); 327 | 328 | // dlopen is done and we're stuck in our RET loop. restore the universe. 329 | thread_suspend(thread); 330 | 331 | // so long and thanks for all the fish 332 | break; 333 | } 334 | } 335 | 336 | // return value is in x0 337 | return (uint64_t)state.__x[0]; 338 | } 339 | 340 | // Caller must free() the pointer returned by readmem() 341 | static char *readmem(uint64_t addr, vm_size_t *len) { 342 | char *buf = (char *)malloc((size_t)len); 343 | if(buf) 344 | vm_read_overwrite(task, addr, *len, (vm_address_t)buf, len); 345 | return buf; 346 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bfinject 2 | Easy dylib injection for jailbroken 64-bit iOS 11.0 - 11.1.2. Compatible with Electra and LiberiOS jailbreaks. 3 | 4 | bfinject loads arbitrary dylibs into running App Store apps. It has built-in support for decrypting App Store apps, and comes bundled with iSpy and Cycript. 5 | 6 | bfinject is a wrapper that takes care of correctly codesigning your dylibs before injecting them using `bfinject4realz`. It's completely standalone, doesn't require jailbreakd, QiLin, or anything like that. It just works. 7 | 8 | **Note**: bfinject does not work on Electra if "Tweaks" is enabled. Reboot and re-run Electra without tweaks in order to use bfinject. If you see errors with "thread_create", this is the problem. 9 | 10 | **Note**: bfdecrypt is available as a standalone dylib here: https://github.com/BishopFox/bfdecrypt/ 11 | 12 | ## Navigate 13 | * [Electra setup](https://github.com/BishopFox/bfinject/blob/master/README.md#electra-setup) 14 | * [LiberiOS setup](https://github.com/BishopFox/bfinject/blob/master/README.md#liberios-setup) 15 | * [Using bfinject](https://github.com/BishopFox/bfinject/blob/master/README.md#using-bfinject) 16 | * [Testing bfinject](https://github.com/BishopFox/bfinject/blob/master/README.md#a-simple-test) 17 | * [Decrypting App Store apps](https://github.com/BishopFox/bfinject/blob/master/README.md#decrypt-app-store-apps) 18 | * [Cycript](https://github.com/BishopFox/bfinject/blob/master/README.md#cycript) 19 | * [How does it work?](https://github.com/BishopFox/bfinject/blob/master/README.md#how-does-it-work) 20 | * [Known issues](https://github.com/BishopFox/bfinject/blob/master/README.md#known-issues) 21 | * [Credits](https://github.com/BishopFox/bfinject/blob/master/README.md#credits) 22 | 23 | ## Electra Setup 24 | * Jailbreak your iOS 11.0 - 11.1.2 device with Electra >= b7 25 | * Copy the bfinject tarball, https://github.com/BishopFox/bfinject/raw/master/bfinject.tar, onto your jailbroken device. You might need to copy it to your laptop first because Github enforces SSL, but the Electra version of `wget` doesn't support SSL. 26 | ``` 27 | ssh root@your-device-ip # (the password is 'alpine') 28 | mkdir bfinject 29 | cd bfinject 30 | wget http:///bfinject.tar 31 | tar xvf bfinject.tar 32 | ``` 33 | 34 | ## LiberiOS Setup 35 | * Jailbreak your iOS 11.0 - 11.1.2 device with LiberiOS >= 11.0.3 36 | * Copy the bfinject tarball, https://github.com/BishopFox/bfinject/raw/master/bfinject.tar, onto your jailbroken device. You might need to copy it to your laptop first because Github enforces SSL, but the LiberiOS version of `wget` doesn't support SSL. 37 | ``` 38 | ssh root@your-device-ip # (the password is 'alpine') 39 | export PATH=$PATH:/jb/usr/bin:/jb/bin:/jb/sbin:/jb/usr/sbin:/jb/usr/local/bin: 40 | cd /jb 41 | mkdir bfinject 42 | cd bfinject 43 | wget http:///bfinject.tar 44 | tar xvf bfinject.tar 45 | ``` 46 | 47 | ## Using bfinject 48 | * Launch the target app into which you will inject your shared library 49 | * Type `bash bfinject` for help 50 | * NOTE: it's important to precede the command with `bash` or it won't work. Sandbox yadda yadda. 51 | ``` 52 | -bash-3.2# bash bfinject 53 | Syntax: bfinject [-p PID | -P appname] [-l /path/to/yourdylib | -L feature] 54 | 55 | For example: 56 | bfinject -P Reddit.app -l /path/to/evil.dylib # Injects evil.dylib into the Reddit app 57 | or 58 | bfinject -p 1234 -L cycript # Inject Cycript into PID 59 | or 60 | bfinject -p 4566 -l /path/to/evil.dylib # Injects the .dylib of your choice into PID 61 | 62 | Instead of specifying the PID with -p, bfinject can search for the correct PID based on the app name. 63 | Just enter "-P identifier" where "identifier" is a string unique to your app, e.g. "fing.app". 64 | 65 | Available features: 66 | cycript - Inject and run Cycript 67 | decrypt - Create a decrypted copy of the target app 68 | test - Inject a simple .dylib to make an entry in the console log 69 | ispy - Inject iSpy. Browse to http://:31337/ 70 | ``` 71 | 72 | ## A Simple Test 73 | Before doing anything more complex, test that it works. bfinject has built-in self tests. Here's an example using the Reddit app as the target: 74 | 75 | ``` 76 | Cs-iPhone:~ root# bash bfinject -P Reddit -L test 77 | [+] Electra detected. 78 | [+] Injecting into '/var/containers/Bundle/Application/55C94FAA-A282-4FDC-967D-6A012D01087E/Reddit.app/Reddit' 79 | [+] Getting Team ID from target application... 80 | [+] Thinning dylib into non-fat arm64 image 81 | [+] Signing injectable .dylib with Team ID 2TDUX39LX8 and platform entitlements... 82 | [bfinject4realz] Calling task_for_pid() for PID 486. 83 | [bfinject4realz] Calling thread_create() on PID 486 84 | [bfinject4realz] Looking for ROP gadget... found at 0x1019a2ba0 85 | [bfinject4realz] Fake stack frame at 0x12ac5c000 86 | [bfinject4realz] Calling _pthread_set_self() at 0x182bfb814... 87 | [bfinject4realz] Returned from '_pthread_set_self' 88 | [bfinject4realz] Calling dlopen() at 0x1829bb460... 89 | [bfinject4realz] Returned from 'dlopen' 90 | [bfinject4realz] Success! Library was loaded at 0x1c016e1c0 91 | [+] So long and thanks for all the fish. 92 | ``` 93 | 94 | On the device screen you should see this: 95 | 96 | 97 | 98 | If not, something is broken ;) 99 | 100 | ## Decrypt App Store apps 101 | Here's an example decrypting the Reddit app on an Electra-jailbroken iPhone: 102 | 103 | ``` 104 | Cs-iPhone:~ root# bash bfinject -P Reddit -L decrypt 105 | [+] Electra detected. 106 | [+] Injecting into '/var/containers/Bundle/Application/BCEBDD64-6738-45CE-9B3C-C6F933EA0793/Reddit.app/Reddit' 107 | [+] Getting Team ID from target application... 108 | [+] Thinning dylib into non-fat arm64 image 109 | [+] Signing injectable .dylib with Team ID 2TDUX39LX8 and platform entitlements... 110 | [bfinject4realz] Calling task_for_pid() for PID 3218. 111 | [bfinject4realz] Calling thread_create() on PID 3218 112 | [bfinject4realz] Looking for ROP gadget... found at 0x1016a5110 113 | [bfinject4realz] Fake stack frame at 0x10a06c000 114 | [bfinject4realz] Calling _pthread_set_self() at 0x181303814... 115 | [bfinject4realz] Returned from '_pthread_set_self' 116 | [bfinject4realz] Calling dlopen() at 0x1810c3460... 117 | [bfinject4realz] Returned from 'dlopen' 118 | [bfinject4realz] Success! Library was loaded at 0x1c03e1100 119 | [+] So long and thanks for all the fish. 120 | ``` 121 | 122 | You'll see this screen on your device: 123 | 124 | 125 | 126 | Once it's complete, you'll be presented with a UI alert to ask if you want to spawn a service from which you can download your decrypted IPA: 127 | 128 | 129 | 130 | If you tap `Yes`, a service will be spawned on port 31336 of your device. Connect to it and you'll be sent a raw copy of the IPA that can be downloaded with netcat like so: 131 | 132 | ```bash 133 | carl@calisto-3 /tmp $ nc 192.168.1.33 31336 > decrypted.ipa 134 | carl@calisto-3 /tmp $ ls -l decrypted.ipa 135 | -rw-r--r-- 1 carl wheel 14649063 Jan 25 16:57 decrypted.ipa 136 | carl@calisto-3 /tmp $ file decrypted.ipa 137 | decrypted.ipa: iOS App Zip archive data, at least v2.0 to extract 138 | ``` 139 | 140 | Alternatively, check the console log for the device, it will tell you where the decrypted IPA is stored. For example: 141 | 142 | ``` 143 | [dumpdecrypted] Wrote /var/mobile/Containers/Data/Application/6E6A5887-8B58-4FC5-A2F3-7870EDB5E8D1/Documents/decrypted-app.ipa 144 | ``` 145 | 146 | You can also search the filesystem for the IPA like so: 147 | 148 | ``` 149 | find /var/mobile/Containers/Data/Application/ -name decrypted-app.ipa 150 | ``` 151 | 152 | Getting the .ipa off the device can be done with netcat. On your laptop, set up a listener service: 153 | 154 | ``` 155 | ncat -l 0.0.0.0 12345 > decrypted.ipa 156 | ``` 157 | 158 | And on the jailbroken device: 159 | 160 | ``` 161 | cat /path/to/decrypted.ipa > /dev/tcp//12345 162 | ```` 163 | 164 | The .ipa will be a clone of the original .ipa from the App Store, except that the main binary and all its accompanying frameworks and shared libraries will be decrypted. The CRYPTID flag will be 0 in each previously-encrypted file. You can take the .ipa, extract the app, modify it as needed, re-sign it with your own developer cert, and deploy it onto non-jailbroken devices as needed. 165 | 166 | ## Cycript 167 | One of bfinject's features is to incorporate common pentesting tools, like Cycript. More will be added with time. To use Cycript you will need the Cycript command-line client installed on your MacBook (http://www.cycript.org/). Then, once bfinject is installed on your test device, do something like this example in which we inject Cycript into the Reddit app: 168 | 169 | ``` 170 | Cs-iPhone:~ root# bash bfinject -P Reddit -L cycript 171 | [+] Electra detected. 172 | [+] Injecting into '/var/containers/Bundle/Application/55C94FAA-A282-4FDC-967D-6A012D01087E/Reddit.app/Reddit' 173 | [+] Getting Team ID from target application... 174 | [+] Thinning dylib into non-fat arm64 image 175 | [+] Signing injectable .dylib with Team ID 2TDUX39LX8 and platform entitlements... 176 | [bfinject4realz] Calling task_for_pid() for PID 486. 177 | [bfinject4realz] Calling thread_create() on PID 486 178 | [bfinject4realz] Looking for ROP gadget... found at 0x1019a2ba0 179 | [bfinject4realz] Fake stack frame at 0x10ab00000 180 | [bfinject4realz] Calling _pthread_set_self() at 0x182bfb814... 181 | [bfinject4realz] Returned from '_pthread_set_self' 182 | [bfinject4realz] Calling dlopen() at 0x1829bb460... 183 | [bfinject4realz] Returned from 'dlopen' 184 | [bfinject4realz] Success! Library was loaded at 0x1c01786c0 185 | [+] Injecting into '/var/containers/Bundle/Application/55C94FAA-A282-4FDC-967D-6A012D01087E/Reddit.app/Reddit' 186 | [+] Getting Team ID from target application... 187 | [+] Thinning dylib into non-fat arm64 image 188 | [+] Signing injectable .dylib with Team ID 2TDUX39LX8 and platform entitlements... 189 | [bfinject4realz] Calling task_for_pid() for PID 486. 190 | [bfinject4realz] Calling thread_create() on PID 486 191 | [bfinject4realz] Looking for ROP gadget... found at 0x1019a2ba0 192 | [bfinject4realz] Fake stack frame at 0x10ab00000 193 | [bfinject4realz] Calling _pthread_set_self() at 0x182bfb814... 194 | [bfinject4realz] Returned from '_pthread_set_self' 195 | [bfinject4realz] Calling dlopen() at 0x1829bb460... 196 | [bfinject4realz] Returned from 'dlopen' 197 | [bfinject4realz] Success! Library was loaded at 0x1c4179680 198 | [+] So long and thanks for all the fish. 199 | ``` 200 | 201 | Once Cycript has been injected, you'll see the following message on your device: 202 | 203 | 204 | 205 | You can connect to Cycript from your MacBook like this (assuming you installed Cycript into ~/bin/): 206 | 207 | ``` 208 | carl@calisto-3 /tmp $ ~/bin/cycript -r 192.168.1.33:1337 209 | cy# UIApp 210 | #"" 211 | cy# 212 | ``` 213 | 214 | ## How does it work? 215 | At a high level, `bfinject4realz` side-loads a self-signed .dylib into a running Apple-signed App Store app. 216 | 217 | The process is done in two stages. 218 | 219 | ### 1. Sign the dylib to be injected 220 | Codesigning checks on iOS comprise userspace services (amfid) and kernel services (AppleMobileFileIntegrity). Both LiberiOS and Electra patch the userspace amfid process to bypass codesigning checks, but there are still further checks performed by the kernel. 221 | 222 | However, Electra and LiberiOS are KPPless, which means they don't patch anything in the kernel; not a single byte. This is because of Kernel Patch Protection ("KPP"), an Apple security technology that does sophisticated kernel introspection to detect and thwart kernel patches. As a result, kernel codesigning checks are still intact. 223 | 224 | Fortunately for us, it appears that the kernel assumes amfid has already checked the validity of the cryptographic signature attached to a dylib's entitlements. As a result, all we need to do is self-sign two entitlements into a dylib if we want the kernel to accept it: 225 | * The first is the `platform-application` entitlement, which I believe indicates that the dylib is Apple software. 226 | * The second is the Team ID of the signing certificate that was used to sign the code we are injecting into. For example, the Reddit app is signed by Team ID `2TDUX39LX8`. As a result, to inject a dylib into the Reddit app we must sign the dylib with the same Team ID: `2TDUX39LX8`. 227 | 228 | bfinject takes care of all the signing shenanigans for you, which is nice. 229 | 230 | ### 2. Inject the correctly-signed dylib into the target process 231 | * Using `task_for_pid()`, get a mach port for the target process 232 | * Use the port to manipulate threads and non-executable memory segments in the target process 233 | * Note: without kernel patches, it is not possible to modify executable code in a process. 234 | * As a result, we have to use ROP tricks to execute code of our choice. 235 | * Allocate some memory pages in the remote process for a new temporary stack 236 | * Place the string "/path/to/my.dylib" at known location in stack 237 | * Use some tricks to lookup the address of dlopen() in the remote process 238 | * Find a simple RET ROP gadget in an executable page (RET = "\xc0\x03\x5f\xd6" on arm64) 239 | * Create a new thread in the target process 240 | * Set CPU registers for the thread: 241 | * $pc = Address of dlopen() 242 | * $x0 = Parameter 1 of dlopen: address of string "/path/to/dylib" in temporary stack 243 | * $x1 = Parameter 2 of dlopen: the value RTLD_LAZY | RTLD_GLOBAL 244 | * $sp = Middle of the temporary stack 245 | * $fp = Quarter way into the temporary stack 246 | * $lr = Address of ROP gadget 247 | * Resume the thread. The following will happen: 248 | * _pthread_set_self is called in order to setup threading for dlopen() 249 | * dlopen() is called to inject our evil shared library 250 | * dlopen() will RET to the value in the $lr register, which is another RET instruction 251 | * RET will return to RET will return to RET... ad infinitum 252 | * Poll the thread's registers to check for $pc == address of ROP gadget (the RET instruction) 253 | * Once the gadget is hit, terminate the thread, free the memory, job done. 254 | 255 | For a low-level description, see the source. 256 | 257 | ## Known issues 258 | Note that on Electra, the version of `jtool` (@morpheus' code-signing multitool) doesn't support platform binary entitlements, so bfinject supplies `jtool` from LiberiOS and uses that instead. bfinject does not use Electra's `inject_criticald`. 259 | 260 | ## Credits 261 | * Stefan Esser (10n1c) for the original ideas and code behind dumpdecrypted (https://github.com/stefanesser/dumpdecrypted/blob/master/dumpdecrypted.c) 262 | * Dmitry Rodionov for lorgnette (https://github.com/rodionovd/liblorgnette/) 263 | * Jonathan Levin for the LiberiOS jailbreak (http://newosxbook.com/liberios/) 264 | * Ian Beer for the async_wake exploit (https://bugs.chromium.org/p/project-zero/issues/detail?id=1417) 265 | * Apple for a great mobile OS 266 | -------------------------------------------------------------------------------- /lorgnette.mm: -------------------------------------------------------------------------------- 1 | // 2 | // lorgnette.c 3 | // liblorgnette 4 | // 5 | // Created by Dmitry Rodionov on 9/24/14. 6 | // Copyright (c) 2014 rodionovd. All rights reserved. 7 | // 8 | 9 | /** We don't want assert() to be stripped out from release builds. */ 10 | #ifdef NDEBUG 11 | #define RD_REENABLE_NDEBUG NDEBUG 12 | #undef NDEBUG 13 | #endif 14 | #include 15 | #ifdef RD_REENABLE_NDEBUG 16 | #define NDEBUG RD_REENABLE_NDEBUG 17 | #undef RD_REENABLE_NDEBUG 18 | #endif 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | 30 | #include "lorgnette.h" 31 | #include "lorgnette-structs.h" 32 | 33 | #define RDFailOnError(function, label) \ 34 | do { \ 35 | if (err != KERN_SUCCESS) { \ 36 | syslog(LOG_NOTICE, "[%d] %s failed with error: %s [%d]\n", \ 37 | __LINE__-1, function, mach_error_string(err), err); \ 38 | goto label; \ 39 | } \ 40 | } while(0) 41 | 42 | /** This magic Mach-O header flag implies that image was loaded from dyld shared cache */ 43 | #define kImageFromSharedCacheFlag 0x80000000 44 | /** @see _copyin_string() */ 45 | #define kRemoteStringBufferSize 2048 46 | /** Default base addresses for 32- and 64-bit executables */ 47 | #define ki386DefaultBaseAddress 0x1000 48 | #define kx86_64DefaultBaseAddress 0x100000000 49 | 50 | int _image_headers_in_task(task_t, const char *, vm_address_t*, uint32_t*, uint64_t*); 51 | static int _image_headers_from_dyld_info32(task_t, task_dyld_info_data_t, const char*, uint32_t*, 52 | uint64_t*, uint64_t *); 53 | static int _image_headers_from_dyld_info64(task_t, task_dyld_info_data_t, const char*, uint32_t*, 54 | uint64_t*, uint64_t *); 55 | static vm_address_t _scan_remote_image_for_symbol(task_t, vm_address_t, const char *, bool *); 56 | static char *_copyin_string(task_t, vm_address_t); 57 | 58 | 59 | #pragma mark - Lorgnette 60 | 61 | vm_address_t lorgnette_lookup(task_t target, const char *symbol_name) 62 | { 63 | return lorgnette_lookup_image(target, symbol_name, NULL); 64 | } 65 | 66 | vm_address_t lorgnette_lookup_image(task_t target, const char *symbol_name, const char *image_name) 67 | { 68 | assert(symbol_name); 69 | assert(strlen(symbol_name) > 0); 70 | 71 | int err = KERN_SUCCESS; 72 | uint32_t count = 0; 73 | uint64_t shared_cache_slide = 0x0; 74 | err = _image_headers_in_task(target, image_name, NULL, &count, &shared_cache_slide); 75 | if (err != KERN_SUCCESS) { 76 | return 0; 77 | } 78 | 79 | vm_address_t *headers = (vm_address_t *)malloc(sizeof(*headers) * count); 80 | err =_image_headers_in_task(target, image_name, headers, &count, &shared_cache_slide); 81 | if (err != KERN_SUCCESS) { 82 | free(headers); 83 | return 0; 84 | } 85 | vm_address_t result = 0; 86 | bool imageFromSharedCache = 0; 87 | for (uint32_t i = 0; i < count; i++) { 88 | vm_address_t image = headers[i]; 89 | result = _scan_remote_image_for_symbol(target, image, symbol_name, &imageFromSharedCache); 90 | if (result > 0) { 91 | /** Add ASLR slice only for the main image of the target */ 92 | if (i == 0) { 93 | // FIXME: dirty hardcoding 94 | /* Get a relative symbol offset */ 95 | if (result < kx86_64DefaultBaseAddress) { 96 | result -= ki386DefaultBaseAddress; 97 | } else { 98 | result -= kx86_64DefaultBaseAddress; 99 | } 100 | /* The header pointer already have ASLR slice included */ 101 | result += headers[0]; 102 | } else if (!imageFromSharedCache) { 103 | /** 104 | * On some setups dyld shared cache doesn't contain some system libraries. 105 | * In this case we have to append a base_address+ASLR value to the result. 106 | */ 107 | if (headers[i] > kx86_64DefaultBaseAddress && result < kx86_64DefaultBaseAddress) { 108 | /* x86_64 target */ 109 | result += headers[i]; 110 | } 111 | if (headers[i] < kx86_64DefaultBaseAddress && result < ki386DefaultBaseAddress) { 112 | /* i386 target */ 113 | result += headers[i]; 114 | } 115 | } 116 | break; 117 | }; 118 | } 119 | free(headers); 120 | /* Add a slide if our target image was a library from the dyld shared cache */ 121 | if (imageFromSharedCache && result > 0) { 122 | result += shared_cache_slide; 123 | } 124 | 125 | return result; 126 | } 127 | 128 | #pragma mark - All the interesting stuff 129 | /** 130 | * @abstract 131 | * Get a list of load addresses of all mach-o images within the target task. 132 | * @note 133 | * These addresses could belong to a foreign address space. 134 | * 135 | * @param task 136 | * the target process 137 | * @param headers (out) 138 | * the list of length @p count containing addresses of images loaded into the target task 139 | * @param count (out) 140 | * the length of @p headers list 141 | */ 142 | int _image_headers_in_task(task_t task, 143 | const char *suggested_image_name, 144 | vm_address_t *headers, 145 | uint32_t *count, 146 | uint64_t *shared_cache_slide) 147 | { 148 | task_flavor_t flavor = TASK_DYLD_INFO; 149 | task_dyld_info_data_t data; 150 | mach_msg_type_number_t number = TASK_DYLD_INFO_COUNT; 151 | int err = task_info(task, flavor, (task_info_t)&data, &number); 152 | //RDFailOnError("task_info()", fail); 153 | 154 | if (data.all_image_info_format == TASK_DYLD_ALL_IMAGE_INFO_32) { 155 | return _image_headers_from_dyld_info32(task, data, suggested_image_name, count, 156 | (unsigned long long *)headers, shared_cache_slide); 157 | } else { 158 | return _image_headers_from_dyld_info64(task, data, suggested_image_name, count, 159 | (unsigned long long *)headers, shared_cache_slide); 160 | } 161 | 162 | fail: 163 | return KERN_FAILURE; 164 | } 165 | 166 | static 167 | int _image_headers_from_dyld_info64(task_t target, 168 | task_dyld_info_data_t dyld_info, 169 | const char *suggested_image_name, 170 | uint32_t *count, 171 | uint64_t *headers, 172 | uint64_t *shared_cache_slide) 173 | { 174 | assert(count); 175 | assert(shared_cache_slide); 176 | 177 | int err = KERN_FAILURE; 178 | struct dyld_all_image_infos_64 infos; 179 | vm_size_t size = dyld_info.all_image_info_size; 180 | err = vm_read_overwrite(target, dyld_info.all_image_info_addr, size, 181 | (vm_address_t)&infos, &size); 182 | ////RDFailOnError("vm_read_overwrite()", fail); 183 | 184 | *count = infos.infoArrayCount; 185 | *shared_cache_slide = infos.sharedCacheSlide; 186 | 187 | size = sizeof(struct dyld_image_info_64) * (*count); 188 | struct dyld_image_info_64 *array = (struct dyld_image_info_64 *)malloc((size_t)size); 189 | err = vm_read_overwrite(target, (vm_address_t)infos.infoArray, size, 190 | (vm_address_t)array, &size); 191 | ////RDFailOnError("vm_read_overwrite()", fail); 192 | 193 | bool should_find_particular_image = (suggested_image_name != NULL); 194 | if (headers) { 195 | for (uint32_t i = 0; i < *count; i++) { 196 | /// FIXME: Find a real location of the first image path 197 | /* We have to always include the first image in the headers list 198 | * because an image filepath's address is slided with an unknown offset, 199 | * so we can't read the image name directly. */ 200 | if (!should_find_particular_image || i == 0) { 201 | headers[i] = (vm_address_t)array[i].imageLoadAddress; 202 | } else { 203 | char *image_name = _copyin_string(target, array[i].imageFilePath); 204 | bool not_found = ({ 205 | strcmp(suggested_image_name, image_name) && 206 | strcmp(suggested_image_name, basename(image_name)); 207 | }); 208 | free(image_name); 209 | if (not_found) { 210 | headers[i] = 0; 211 | } else { 212 | headers[i] = (vm_address_t)array[i].imageLoadAddress; 213 | break; 214 | } 215 | } 216 | } 217 | } 218 | 219 | free(array); 220 | return KERN_SUCCESS; 221 | 222 | fail: 223 | free(array); 224 | return KERN_FAILURE; 225 | } 226 | 227 | 228 | static 229 | int _image_headers_from_dyld_info32(task_t target, 230 | task_dyld_info_data_t dyld_info, 231 | const char *suggested_image_name, 232 | uint32_t *count, 233 | uint64_t *headers, 234 | uint64_t *shared_cache_slide) 235 | { 236 | assert(count); 237 | assert(shared_cache_slide); 238 | 239 | int err = KERN_FAILURE; 240 | struct dyld_all_image_infos_32 infos; 241 | vm_size_t size = dyld_info.all_image_info_size; 242 | err = vm_read_overwrite(target, dyld_info.all_image_info_addr, size, 243 | (vm_address_t)&infos, &size); 244 | //RDFailOnError("vm_read_overwrite()", fail); 245 | 246 | *count = infos.infoArrayCount; 247 | *shared_cache_slide = infos.sharedCacheSlide; 248 | 249 | size = sizeof(struct dyld_image_info_32) * (*count); 250 | struct dyld_image_info_32 *array = (struct dyld_image_info_32 *)malloc((size_t)size); 251 | err = vm_read_overwrite(target, (vm_address_t)infos.infoArray, size, 252 | (vm_address_t)array, &size); 253 | //RDFailOnError("vm_read_overwrite()", fail); 254 | 255 | bool should_find_particular_image = (suggested_image_name != NULL); 256 | if (headers) { 257 | for (uint32_t i = 0; i < *count; i++) { 258 | /// FIXME: Find a real location of the first image path 259 | /* We have to always include the first image in the headers list 260 | * because an image filepath's address is slided with an unknown offset, 261 | * so we can't read the image name directly. */ 262 | if (!should_find_particular_image || i == 0) { 263 | headers[i] = (vm_address_t)array[i].imageLoadAddress; 264 | } else { 265 | char *image_name = _copyin_string(target, array[i].imageFilePath); 266 | bool not_found = ({ 267 | strcmp(suggested_image_name, image_name) && 268 | strcmp(suggested_image_name, basename(image_name)); 269 | }); 270 | free(image_name); 271 | if (not_found) { 272 | headers[i] = 0; 273 | } else { 274 | headers[i] = (vm_address_t)array[i].imageLoadAddress; 275 | break; 276 | } 277 | } 278 | } 279 | } 280 | 281 | free(array); 282 | return KERN_SUCCESS; 283 | 284 | fail: 285 | free(array); 286 | return KERN_FAILURE; 287 | } 288 | 289 | /** 290 | * 291 | */ 292 | static 293 | vm_address_t _scan_remote_image_for_symbol(task_t task, 294 | vm_address_t remote_header, 295 | const char *symbol_name, 296 | bool *imageFromSharedCache) 297 | { 298 | assert(symbol_name); 299 | assert(imageFromSharedCache); 300 | int err = KERN_FAILURE; 301 | 302 | if (remote_header == 0) { 303 | return 0; 304 | } 305 | 306 | vm_size_t size = sizeof(struct mach_header); 307 | struct mach_header header = {0}; 308 | err = vm_read_overwrite(task, remote_header, size, (vm_address_t)&header, &size); 309 | //RDFailOnError("vm_read_overwrite()", fail); 310 | 311 | bool sixtyfourbit = (header.magic == MH_MAGIC_64); 312 | *imageFromSharedCache = ((header.flags & kImageFromSharedCacheFlag) == kImageFromSharedCacheFlag); 313 | 314 | /* We don't support anything but i386 and x86_64 */ 315 | if (header.magic != MH_MAGIC && header.magic != MH_MAGIC_64) { 316 | syslog(LOG_NOTICE, "liblorgnette ERROR: found image with unsupported architecture" 317 | "at %p, skipping it.\n", (void *)remote_header); 318 | return 0; 319 | } 320 | 321 | /** 322 | * Let's implement some nlist() 323 | */ 324 | vm_address_t symtab_addr = 0; 325 | vm_address_t linkedit_addr = 0; 326 | vm_address_t text_addr = 0; 327 | 328 | size_t mach_header_size = sizeof(struct mach_header); 329 | if (sixtyfourbit) { 330 | mach_header_size = sizeof(struct mach_header_64); 331 | } 332 | vm_address_t command_addr = remote_header + mach_header_size; 333 | struct load_command command = {0}; 334 | size = sizeof(command); 335 | 336 | for (uint32_t i = 0; i < header.ncmds; i++) { 337 | err = vm_read_overwrite(task, command_addr, size, (vm_address_t)&command, &size); 338 | //RDFailOnError("vm_read_overwrite()", fail); 339 | 340 | if (command.cmd == LC_SYMTAB) { 341 | symtab_addr = command_addr; 342 | } else if (command.cmd == LC_SEGMENT || command.cmd == LC_SEGMENT_64) { 343 | /* struct load_command only has two fields (cmd & cmdsize), while its "child" type 344 | * struct segment_command has way more fields including `segname` at index 3, so we just 345 | * pretend that we have a real segment_command and skip first two fields away */ 346 | size_t segname_field_offset = sizeof(command); 347 | vm_address_t segname_addr = command_addr + segname_field_offset; 348 | char *segname = _copyin_string(task, segname_addr); 349 | if (0 == strcmp(SEG_TEXT, segname)) { 350 | text_addr = command_addr; 351 | } else if (0 == strcmp(SEG_LINKEDIT, segname)) { 352 | linkedit_addr = command_addr; 353 | } 354 | free(segname); 355 | } 356 | // go to next load command 357 | command_addr += command.cmdsize; 358 | } 359 | 360 | if (!symtab_addr || !linkedit_addr || !text_addr) { 361 | syslog(LOG_NOTICE, "Invalid Mach-O image header, skipping...\n"); 362 | return 0; 363 | } 364 | 365 | struct symtab_command symtab = {0}; 366 | size = sizeof(struct symtab_command); 367 | err = vm_read_overwrite(task, symtab_addr, size, (vm_address_t)&symtab, &size); 368 | //RDFailOnError("vm_read_overwrite", fail); 369 | 370 | // FIXME: find a way to remove the copypasted code below 371 | // These two snippets share all the logic, but differs in structs and integers 372 | // they use for reading the data from a target process (32- or 64-bit layout). 373 | if (sixtyfourbit) { 374 | struct segment_command_64 linkedit = {0}; 375 | size = sizeof(struct segment_command_64); 376 | err = vm_read_overwrite(task, linkedit_addr, size, 377 | (vm_address_t)&linkedit, &size); 378 | //RDFailOnError("vm_read_overwrite", fail); 379 | struct segment_command_64 text = {0}; 380 | err = vm_read_overwrite(task, text_addr, size, (vm_address_t)&text, &size); 381 | //RDFailOnError("vm_read_overwrite", fail); 382 | 383 | uint64_t file_slide = linkedit.vmaddr - text.vmaddr - linkedit.fileoff; 384 | uint64_t strings = remote_header + symtab.stroff + file_slide; 385 | uint64_t sym_addr = remote_header + symtab.symoff + file_slide; 386 | 387 | for (uint32_t i = 0; i < symtab.nsyms; i++) { 388 | struct nlist_64 sym = {{0}}; 389 | size = sizeof(struct nlist_64); 390 | err = vm_read_overwrite(task, sym_addr, size, (vm_address_t)&sym, &size); 391 | //RDFailOnError("vm_read_overwrite", fail); 392 | sym_addr += size; 393 | 394 | if (!sym.n_value) continue; 395 | 396 | uint64_t symname_addr = strings + sym.n_un.n_strx; 397 | char *symname = _copyin_string(task, symname_addr); 398 | /* Ignore the leading "_" character in a symbol name */ 399 | if (0 == strcmp(symbol_name, symname+1)) { 400 | free(symname); 401 | return (vm_address_t)sym.n_value; 402 | } 403 | free(symname); 404 | } 405 | } else { 406 | struct segment_command linkedit = {0}; 407 | size = sizeof(struct segment_command); 408 | err = vm_read_overwrite(task, linkedit_addr, size, 409 | (vm_address_t)&linkedit, &size); 410 | //RDFailOnError("vm_read_overwrite", fail); 411 | struct segment_command text = {0}; 412 | err = vm_read_overwrite(task, text_addr, size, (vm_address_t)&text, &size); 413 | //RDFailOnError("vm_read_overwrite", fail); 414 | 415 | uint32_t file_slide = linkedit.vmaddr - text.vmaddr - linkedit.fileoff; 416 | uint32_t strings = (uint32_t)remote_header + symtab.stroff + file_slide; 417 | uint32_t sym_addr = (uint32_t)remote_header + symtab.symoff + file_slide; 418 | 419 | for (uint32_t i = 0; i < symtab.nsyms; i++) { 420 | struct nlist sym = {{0}}; 421 | size = sizeof(struct nlist); 422 | err = vm_read_overwrite(task, sym_addr, size, (vm_address_t)&sym, &size); 423 | //RDFailOnError("vm_read_overwrite", fail); 424 | sym_addr += size; 425 | 426 | if (!sym.n_value) continue; 427 | 428 | uint32_t symname_addr = strings + sym.n_un.n_strx; 429 | char *symname = _copyin_string(task, symname_addr); 430 | /* Ignore the leading "_" character in a symbol name */ 431 | if (0 == strcmp(symbol_name, symname+1)) { 432 | free(symname); 433 | return (vm_address_t)sym.n_value; 434 | } 435 | free(symname); 436 | } 437 | } 438 | 439 | fail: 440 | return 0; 441 | } 442 | 443 | /** 444 | * @abstract 445 | * Copy a string from the target task's address space to current address space. 446 | * 447 | * @param task 448 | * The target task. 449 | * @param pointer 450 | * The address of a string to copyin. 451 | * 452 | * @return 453 | * A pointer to a string. It may be NULL. 454 | */ 455 | static char *_copyin_string(task_t task, vm_address_t pointer) 456 | { 457 | assert(pointer > 0); 458 | int err = KERN_FAILURE; 459 | 460 | /* Since calls to vm_read_overwrite() are expensive we'll just use 461 | * a rather big buffer insead of reading char-by-char. 462 | */ 463 | // FIXME: what about the size of this buffer? 464 | // Users can requst symbols with very long names (e.g. C++ mangled method names, etc) 465 | char buf[kRemoteStringBufferSize] = {0}; 466 | vm_size_t sample_size = sizeof(buf); 467 | err = vm_read_overwrite(task, pointer, sample_size, 468 | (vm_address_t)&buf, &sample_size); 469 | assert(err == KERN_SUCCESS); 470 | buf[kRemoteStringBufferSize-1] = '\0'; 471 | 472 | char *result = strdup(buf); 473 | return result; 474 | } --------------------------------------------------------------------------------