├── .github └── workflows │ └── objective-c-xcode.yml ├── README.md ├── cfg.data ├── process_inject.entitlements ├── process_inject.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcuserdata │ │ └── artemis.xcuserdatad │ │ └── UserInterfaceState.xcuserstate ├── xcshareddata │ └── xcschemes │ │ └── process_inject.xcscheme └── xcuserdata │ ├── artemis.xcuserdatad │ └── xcschemes │ │ └── xcschememanagement.plist │ └── voidm.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── process_inject └── main.m ├── test_bin └── bin_dev.cpp └── test_lib ├── inject.c └── inject.m /.github/workflows/objective-c-xcode.yml: -------------------------------------------------------------------------------- 1 | name: Builder 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | 8 | env: 9 | CMAKE_VERSION: 3.5 10 | LLVM_VERSION: 15.0.6 11 | 12 | jobs: 13 | 14 | macos: 15 | runs-on: macos-latest 16 | # needs: delete_latest_release 17 | steps: 18 | 19 | - name: checkout master 20 | uses: actions/checkout@master 21 | 22 | - name: delete latest release 23 | uses: dev-drprasad/delete-tag-and-release@v0.2.1 24 | with: 25 | delete_release: true 26 | tag_name: latest 27 | env: 28 | GITHUB_TOKEN: ${{ secrets.TOKEN }} 29 | 30 | - name: Set up Xcode 16 31 | uses: maxim-lobanov/setup-xcode@v1 32 | with: 33 | xcode-version: '16.2.0' # 指定 Xcode 16 的版本 34 | 35 | - name: Check Xcode version 36 | run: xcodebuild -version 37 | 38 | - name: compile macos 39 | run: | 40 | # xcodebuild -showBuildSettings 41 | xcodebuild -scheme process_inject -derivedDataPath ./build -configuration Release CODE_SIGNING_ALLOWED=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -quiet -showBuildTimingSummary 42 | BUILT_PRODUCTS_DIR="./build/Build/Products/Release/" 43 | ls -la "$BUILT_PRODUCTS_DIR" 44 | 45 | echo "-----------Checking entitlements before signing:-----------" 46 | codesign -d --entitlements - "$BUILT_PRODUCTS_DIR/process_inject" 47 | 48 | sudo codesign -f -s - --all-architectures --deep --entitlements "process_inject.entitlements" "$BUILT_PRODUCTS_DIR/process_inject" 49 | 50 | echo "-----------Checking entitlements after signing:-----------" 51 | codesign -d --entitlements - "$BUILT_PRODUCTS_DIR/process_inject" 52 | 53 | tar -czvf process_inject.tar.gz -C "$BUILT_PRODUCTS_DIR" process_inject 54 | 55 | 56 | 57 | - name: update release 58 | uses: ncipollo/release-action@v1 59 | with: 60 | token: ${{ secrets.TOKEN }} 61 | tag: latest 62 | body: | 63 | A macOS dylib project based on the Dobby Hook framework, aimed at enhancing and extending the functionality of target software. 64 | 65 | ## Latest Commit 66 | ${{ github.event.head_commit.message }} 67 | artifacts: "process_inject.tar.gz" 68 | allowUpdates: true 69 | replacesArtifacts: true 70 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # macOS Process Injection Tool [SIP OFF] 2 | 3 | 4 | ## Usage 5 | 6 | ```bash 7 | process_inject [flags] 8 | ``` 9 | 10 | ### Parameters: 11 | 12 | - ``: Can be one of: 13 | - Process ID of the target process (for direct injection) 14 | - Process name (when using -name flag) 15 | - Application bundle path (when using -spawn flag) 16 | - ``: The full path to the dynamic library (dylib) to inject. 17 | - `[flags]`: Optional flags for different injection modes. 18 | 19 | ### Flags: 20 | 21 | - `-name`: Use the process name instead of process ID to identify the target (for existing processes). 22 | - `-spawn`: Spawn a new process from the specified application bundle and inject the dylib. 23 | 24 | ### Examples: 25 | 26 | 1. Inject into an already running process by PID: 27 | 28 | ```bash 29 | process_inject 1234 "/path/to/library.dylib" 30 | ``` 31 | 32 | 2. Inject into an already running process by name: 33 | 34 | ```bash 35 | process_inject "Safari" "/path/to/library.dylib" -name 36 | ``` 37 | 38 | 3. Spawn a new process and inject (spawn mode): 39 | 40 | ```bash 41 | process_inject "/Applications/Calculator.app/Contents/MacOS/Calculator" "/path/to/library.dylib" -spawn 42 | ``` 43 | 44 | 45 | **Note:** Injecting into root processes requires root privileges 46 | 47 | > sudo codesign -f -s - --all-architectures --deep --entitlements "process_inject.entitlements" process_inject 48 | 49 | 50 | ## References 51 | 52 | Thanks to these projects for their inspiring idea and code! 53 | 54 | - <[https://github.com/notahacker8/MacInject](https://github.com/notahacker8/MacInject)> 55 | -------------------------------------------------------------------------------- /cfg.data: -------------------------------------------------------------------------------- 1 | /Applications/Hopper Disassembler v4.app/Contents/MacOS/Hopper Disassembler v4|libdylib_dobby_hook.dylib|-spawn 2 | -------------------------------------------------------------------------------- /process_inject.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.debugger 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /process_inject.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 77; 7 | objects = { 8 | 9 | /* Begin PBXCopyFilesBuildPhase section */ 10 | B50A42E12CA80ABD00CB6706 /* CopyFiles */ = { 11 | isa = PBXCopyFilesBuildPhase; 12 | buildActionMask = 2147483647; 13 | dstPath = /usr/share/man/man1/; 14 | dstSubfolderSpec = 0; 15 | files = ( 16 | ); 17 | runOnlyForDeploymentPostprocessing = 1; 18 | }; 19 | /* End PBXCopyFilesBuildPhase section */ 20 | 21 | /* Begin PBXFileReference section */ 22 | B50A42E32CA80ABD00CB6706 /* process_inject */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = process_inject; sourceTree = BUILT_PRODUCTS_DIR; }; 23 | B51252BF2CB942F000708BA6 /* process_inject.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = process_inject.entitlements; sourceTree = ""; }; 24 | B54947A72CAC3AAE0010B3AD /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; 25 | B54947A92CAC3B2B0010B3AD /* CoreFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreFoundation.framework; path = System/Library/Frameworks/CoreFoundation.framework; sourceTree = SDKROOT; }; 26 | /* End PBXFileReference section */ 27 | 28 | /* Begin PBXFileSystemSynchronizedRootGroup section */ 29 | B50A42E52CA80ABD00CB6706 /* process_inject */ = { 30 | isa = PBXFileSystemSynchronizedRootGroup; 31 | path = process_inject; 32 | sourceTree = ""; 33 | }; 34 | /* End PBXFileSystemSynchronizedRootGroup section */ 35 | 36 | /* Begin PBXFrameworksBuildPhase section */ 37 | B50A42E02CA80ABD00CB6706 /* Frameworks */ = { 38 | isa = PBXFrameworksBuildPhase; 39 | buildActionMask = 2147483647; 40 | files = ( 41 | ); 42 | runOnlyForDeploymentPostprocessing = 0; 43 | }; 44 | /* End PBXFrameworksBuildPhase section */ 45 | 46 | /* Begin PBXGroup section */ 47 | B50A42DA2CA80ABD00CB6706 = { 48 | isa = PBXGroup; 49 | children = ( 50 | B51252BF2CB942F000708BA6 /* process_inject.entitlements */, 51 | B50A42E52CA80ABD00CB6706 /* process_inject */, 52 | B54947A62CAC3AAE0010B3AD /* Frameworks */, 53 | B50A42E42CA80ABD00CB6706 /* Products */, 54 | ); 55 | sourceTree = ""; 56 | }; 57 | B50A42E42CA80ABD00CB6706 /* Products */ = { 58 | isa = PBXGroup; 59 | children = ( 60 | B50A42E32CA80ABD00CB6706 /* process_inject */, 61 | ); 62 | name = Products; 63 | sourceTree = ""; 64 | }; 65 | B54947A62CAC3AAE0010B3AD /* Frameworks */ = { 66 | isa = PBXGroup; 67 | children = ( 68 | B54947A92CAC3B2B0010B3AD /* CoreFoundation.framework */, 69 | B54947A72CAC3AAE0010B3AD /* Security.framework */, 70 | ); 71 | name = Frameworks; 72 | sourceTree = ""; 73 | }; 74 | /* End PBXGroup section */ 75 | 76 | /* Begin PBXNativeTarget section */ 77 | B50A42E22CA80ABD00CB6706 /* process_inject */ = { 78 | isa = PBXNativeTarget; 79 | buildConfigurationList = B50A42EA2CA80ABD00CB6706 /* Build configuration list for PBXNativeTarget "process_inject" */; 80 | buildPhases = ( 81 | B50A42DF2CA80ABD00CB6706 /* Sources */, 82 | B50A42E02CA80ABD00CB6706 /* Frameworks */, 83 | B50A42E12CA80ABD00CB6706 /* CopyFiles */, 84 | ); 85 | buildRules = ( 86 | ); 87 | dependencies = ( 88 | ); 89 | fileSystemSynchronizedGroups = ( 90 | B50A42E52CA80ABD00CB6706 /* process_inject */, 91 | ); 92 | name = process_inject; 93 | packageProductDependencies = ( 94 | ); 95 | productName = process_inject; 96 | productReference = B50A42E32CA80ABD00CB6706 /* process_inject */; 97 | productType = "com.apple.product-type.tool"; 98 | }; 99 | /* End PBXNativeTarget section */ 100 | 101 | /* Begin PBXProject section */ 102 | B50A42DB2CA80ABD00CB6706 /* Project object */ = { 103 | isa = PBXProject; 104 | attributes = { 105 | BuildIndependentTargetsInParallel = 1; 106 | LastUpgradeCheck = 1600; 107 | TargetAttributes = { 108 | B50A42E22CA80ABD00CB6706 = { 109 | CreatedOnToolsVersion = 16.0; 110 | }; 111 | }; 112 | }; 113 | buildConfigurationList = B50A42DE2CA80ABD00CB6706 /* Build configuration list for PBXProject "process_inject" */; 114 | developmentRegion = en; 115 | hasScannedForEncodings = 0; 116 | knownRegions = ( 117 | en, 118 | Base, 119 | ); 120 | mainGroup = B50A42DA2CA80ABD00CB6706; 121 | minimizedProjectReferenceProxies = 1; 122 | preferredProjectObjectVersion = 77; 123 | productRefGroup = B50A42E42CA80ABD00CB6706 /* Products */; 124 | projectDirPath = ""; 125 | projectRoot = ""; 126 | targets = ( 127 | B50A42E22CA80ABD00CB6706 /* process_inject */, 128 | ); 129 | }; 130 | /* End PBXProject section */ 131 | 132 | /* Begin PBXSourcesBuildPhase section */ 133 | B50A42DF2CA80ABD00CB6706 /* Sources */ = { 134 | isa = PBXSourcesBuildPhase; 135 | buildActionMask = 2147483647; 136 | files = ( 137 | ); 138 | runOnlyForDeploymentPostprocessing = 0; 139 | }; 140 | /* End PBXSourcesBuildPhase section */ 141 | 142 | /* Begin XCBuildConfiguration section */ 143 | B50A42E82CA80ABD00CB6706 /* Debug */ = { 144 | isa = XCBuildConfiguration; 145 | buildSettings = { 146 | ALWAYS_SEARCH_USER_PATHS = NO; 147 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 148 | CLANG_ANALYZER_NONNULL = YES; 149 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 150 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 151 | CLANG_ENABLE_MODULES = YES; 152 | CLANG_ENABLE_OBJC_ARC = YES; 153 | CLANG_ENABLE_OBJC_WEAK = YES; 154 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 155 | CLANG_WARN_BOOL_CONVERSION = YES; 156 | CLANG_WARN_COMMA = YES; 157 | CLANG_WARN_CONSTANT_CONVERSION = YES; 158 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 159 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 160 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 161 | CLANG_WARN_EMPTY_BODY = YES; 162 | CLANG_WARN_ENUM_CONVERSION = YES; 163 | CLANG_WARN_INFINITE_RECURSION = YES; 164 | CLANG_WARN_INT_CONVERSION = YES; 165 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 166 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 167 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 168 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 169 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 170 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 171 | CLANG_WARN_STRICT_PROTOTYPES = YES; 172 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 173 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 174 | CLANG_WARN_UNREACHABLE_CODE = YES; 175 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 176 | COPY_PHASE_STRIP = NO; 177 | DEBUG_INFORMATION_FORMAT = dwarf; 178 | ENABLE_STRICT_OBJC_MSGSEND = YES; 179 | ENABLE_TESTABILITY = YES; 180 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 181 | GCC_C_LANGUAGE_STANDARD = gnu17; 182 | GCC_DYNAMIC_NO_PIC = NO; 183 | GCC_NO_COMMON_BLOCKS = YES; 184 | GCC_OPTIMIZATION_LEVEL = 0; 185 | GCC_PREPROCESSOR_DEFINITIONS = ( 186 | "DEBUG=1", 187 | "$(inherited)", 188 | ); 189 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 190 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 191 | GCC_WARN_UNDECLARED_SELECTOR = YES; 192 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 193 | GCC_WARN_UNUSED_FUNCTION = YES; 194 | GCC_WARN_UNUSED_VARIABLE = YES; 195 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 196 | MACOSX_DEPLOYMENT_TARGET = 15.0; 197 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 198 | MTL_FAST_MATH = YES; 199 | ONLY_ACTIVE_ARCH = YES; 200 | SDKROOT = macosx; 201 | }; 202 | name = Debug; 203 | }; 204 | B50A42E92CA80ABD00CB6706 /* Release */ = { 205 | isa = XCBuildConfiguration; 206 | buildSettings = { 207 | ALWAYS_SEARCH_USER_PATHS = NO; 208 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 209 | CLANG_ANALYZER_NONNULL = YES; 210 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 211 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 212 | CLANG_ENABLE_MODULES = YES; 213 | CLANG_ENABLE_OBJC_ARC = YES; 214 | CLANG_ENABLE_OBJC_WEAK = YES; 215 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 216 | CLANG_WARN_BOOL_CONVERSION = YES; 217 | CLANG_WARN_COMMA = YES; 218 | CLANG_WARN_CONSTANT_CONVERSION = YES; 219 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 220 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 221 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 222 | CLANG_WARN_EMPTY_BODY = YES; 223 | CLANG_WARN_ENUM_CONVERSION = YES; 224 | CLANG_WARN_INFINITE_RECURSION = YES; 225 | CLANG_WARN_INT_CONVERSION = YES; 226 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 227 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 228 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 229 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 230 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 231 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 232 | CLANG_WARN_STRICT_PROTOTYPES = YES; 233 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 234 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 235 | CLANG_WARN_UNREACHABLE_CODE = YES; 236 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 237 | COPY_PHASE_STRIP = NO; 238 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 239 | ENABLE_NS_ASSERTIONS = NO; 240 | ENABLE_STRICT_OBJC_MSGSEND = YES; 241 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 242 | GCC_C_LANGUAGE_STANDARD = gnu17; 243 | GCC_NO_COMMON_BLOCKS = YES; 244 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 245 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 246 | GCC_WARN_UNDECLARED_SELECTOR = YES; 247 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 248 | GCC_WARN_UNUSED_FUNCTION = YES; 249 | GCC_WARN_UNUSED_VARIABLE = YES; 250 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 251 | MACOSX_DEPLOYMENT_TARGET = 15.0; 252 | MTL_ENABLE_DEBUG_INFO = NO; 253 | MTL_FAST_MATH = YES; 254 | SDKROOT = macosx; 255 | }; 256 | name = Release; 257 | }; 258 | B50A42EB2CA80ABD00CB6706 /* Debug */ = { 259 | isa = XCBuildConfiguration; 260 | buildSettings = { 261 | CODE_SIGN_ENTITLEMENTS = process_inject.entitlements; 262 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 263 | CODE_SIGN_STYLE = Automatic; 264 | DEVELOPMENT_TEAM = PF34J7Z7XR; 265 | ENABLE_HARDENED_RUNTIME = YES; 266 | ONLY_ACTIVE_ARCH = NO; 267 | PRODUCT_BUNDLE_IDENTIFIER = "com.voidm.process-inject"; 268 | PRODUCT_NAME = "$(TARGET_NAME)"; 269 | }; 270 | name = Debug; 271 | }; 272 | B50A42EC2CA80ABD00CB6706 /* Release */ = { 273 | isa = XCBuildConfiguration; 274 | buildSettings = { 275 | CODE_SIGN_ENTITLEMENTS = process_inject.entitlements; 276 | "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; 277 | CODE_SIGN_STYLE = Automatic; 278 | DEVELOPMENT_TEAM = PF34J7Z7XR; 279 | ENABLE_HARDENED_RUNTIME = YES; 280 | GCC_OPTIMIZATION_LEVEL = 0; 281 | PRODUCT_BUNDLE_IDENTIFIER = "com.voidm.process-inject"; 282 | PRODUCT_NAME = "$(TARGET_NAME)"; 283 | }; 284 | name = Release; 285 | }; 286 | /* End XCBuildConfiguration section */ 287 | 288 | /* Begin XCConfigurationList section */ 289 | B50A42DE2CA80ABD00CB6706 /* Build configuration list for PBXProject "process_inject" */ = { 290 | isa = XCConfigurationList; 291 | buildConfigurations = ( 292 | B50A42E82CA80ABD00CB6706 /* Debug */, 293 | B50A42E92CA80ABD00CB6706 /* Release */, 294 | ); 295 | defaultConfigurationIsVisible = 0; 296 | defaultConfigurationName = Release; 297 | }; 298 | B50A42EA2CA80ABD00CB6706 /* Build configuration list for PBXNativeTarget "process_inject" */ = { 299 | isa = XCConfigurationList; 300 | buildConfigurations = ( 301 | B50A42EB2CA80ABD00CB6706 /* Debug */, 302 | B50A42EC2CA80ABD00CB6706 /* Release */, 303 | ); 304 | defaultConfigurationIsVisible = 0; 305 | defaultConfigurationName = Release; 306 | }; 307 | /* End XCConfigurationList section */ 308 | }; 309 | rootObject = B50A42DB2CA80ABD00CB6706 /* Project object */; 310 | } 311 | -------------------------------------------------------------------------------- /process_inject.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /process_inject.xcodeproj/project.xcworkspace/xcuserdata/artemis.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marlkiller/process_inject/97cc36c1ce6647c64fd9da65521300ddd6da1e55/process_inject.xcodeproj/project.xcworkspace/xcuserdata/artemis.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /process_inject.xcodeproj/xcshareddata/xcschemes/process_inject.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 9 | 10 | 16 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 44 | 46 | 52 | 53 | 54 | 55 | 58 | 59 | 62 | 63 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /process_inject.xcodeproj/xcuserdata/artemis.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | process_inject.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | B50A42E22CA80ABD00CB6706 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /process_inject.xcodeproj/xcuserdata/voidm.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | process_inject.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | SuppressBuildableAutocreation 14 | 15 | B50A42E22CA80ABD00CB6706 16 | 17 | primary 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /process_inject/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // process_inject 4 | // 5 | // Created by voidm on 2024/9/28. 6 | // 7 | 8 | #import 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #define CONFIG_FILE "cfg.data" 21 | 22 | // === Unified log macros with color === 23 | #define COLOR_RESET "\x1b[0m" 24 | #define COLOR_GREEN "\x1b[32m" 25 | #define COLOR_YELLOW "\x1b[33m" 26 | #define COLOR_RED "\x1b[31m" 27 | #define COLOR_BLUE "\x1b[34m" 28 | 29 | #define LOG_INFO(fmt, ...) printf(COLOR_BLUE "[*] " fmt COLOR_RESET "\n", ##__VA_ARGS__) 30 | #define LOG_OK(fmt, ...) printf(COLOR_GREEN "[+] " fmt COLOR_RESET "\n", ##__VA_ARGS__) 31 | #define LOG_WARN(fmt, ...) printf(COLOR_YELLOW "[!] " fmt COLOR_RESET "\n", ##__VA_ARGS__) 32 | #define LOG_ERR(fmt, ...) fprintf(stderr, COLOR_RED "[×] " fmt COLOR_RESET "\n", ##__VA_ARGS__) 33 | #define LOG_FATAL(fmt, ...) do { LOG_ERR(fmt, ##__VA_ARGS__); exit(EXIT_FAILURE); } while (0) 34 | 35 | extern char **environ; 36 | 37 | #ifdef __x86_64__ 38 | 39 | ///Shellcode for the mach thread. 40 | static const unsigned char mach_thread_code[] = 41 | { 42 | // 0xCC, 43 | 0x55, // 000000011F9B8001 push rbp 44 | 0x48, 0x89, 0xe5, // 000000011F9B8002 mov rbp, rsp 45 | 0x48, 0x89, 0xef, // 000000011F9B8005 mov rdi, rbp 46 | 0xff, 0xd0, // 000000011F9B8008 call rax ; _pthread_create_from_mach_thread 47 | 0x48, 0xc7, 0xc0, 0x09, 0x03, 0x00, 0x00, // 000000011F9B800A mov rax, 309h ; 777 48 | 0xe9, 0xfb, 0xff, 0xff, 0xff // 000000011F9B8011 jmp loc_11F9B8011 49 | }; 50 | 51 | ///Shellcode for the posix thread. 52 | static const unsigned char posix_thread_code[] = 53 | { 54 | // 0xCC, 55 | 0x55, // 00000001210EE001 push rbp 56 | 0x48, 0x89, 0xe5, // 00000001210EE002 mov rbp, rsp 57 | 0x48, 0x8b, 0x07, // 00000001210EE005 mov rax, [rdi] ; dlopen 58 | 0x48, 0x8b, 0x7f, 0xf8, // 00000001210EE008 mov rdi, [rdi-8] ; xxx.dylib 59 | 0xbe, 0x01, 0x00, 0x00, 0x00, // 00000001210EE00C mov esi, 1 60 | 0xff, 0xd0, // 00000001210EE011 call rax ; dlopen 61 | 0xc9, // 00000001210EE013 leave 62 | 0xc3 // 00000001210EE014 retn 63 | }; 64 | 65 | #define PTR_SIZE sizeof(void*) 66 | #define STACK_SIZE 1024 67 | #define MACH_CODE_SIZE sizeof(mach_thread_code) 68 | #define POSIX_CODE_SIZE sizeof(posix_thread_code) 69 | #else 70 | 71 | //#define ARM_THREAD_STATE64 6 72 | typedef struct 73 | { 74 | __uint64_t __x[29]; /* General purpose registers x0-x28 */ 75 | __uint64_t __fp; /* Frame pointer x29 */ 76 | __uint64_t __lr; /* Link register x30 */ 77 | __uint64_t __sp; /* Stack pointer x31 */ 78 | __uint64_t __pc; /* Program counter */ 79 | __uint32_t __cpsr; /* Current program status register */ 80 | __uint32_t __pad; /* Same size for 32-bit or 64-bit clients */ 81 | } 82 | __arm_thread_state64_t; 83 | //#define ARM_THREAD_STATE64_COUNT ((mach_msg_type_number_t) \ 84 | // (sizeof (__arm_thread_state64_t)/sizeof(uint32_t))) 85 | 86 | ///Shellcode for the mach thread. 87 | unsigned char mach_thread_code[] = 88 | { 89 | // "\x20\x8e\x38\xd4" // brk 90 | "\x80\x00\x3f\xd6" // 0x121858004: blr x4 ;pthread_create_from_mach_thread 91 | "\x00\x00\x00\x14" // 0x121858008: b 0x121858008 92 | }; 93 | #define MACH_CODE_SIZE sizeof(mach_thread_code) 94 | #define STACK_SIZE 1024 95 | #endif 96 | 97 | 98 | ///The function we will call through the mach thread. 99 | int pthread_create_from_mach_thread(pthread_t *thread, 100 | const pthread_attr_t *attr, 101 | void *(*start_routine)(void *), 102 | void *arg); 103 | 104 | 105 | #define kr(value) if (value != KERN_SUCCESS)\ 106 | {\ 107 | LOG_ERR("Mach error: %s (line %d)", mach_error_string(value), __LINE__);\ 108 | exit(value);\ 109 | } 110 | 111 | 112 | bool is_dylib_loaded2(const task_t task, 113 | const char* dylib_path) 114 | { 115 | bool image_exists = false; 116 | mach_msg_type_number_t size = 0; 117 | 118 | mach_msg_type_number_t dataCnt = 0; 119 | vm_offset_t readData = 0; 120 | 121 | struct task_dyld_info dyld_info; 122 | mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; 123 | task_info(task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count); 124 | size = sizeof(struct dyld_all_image_infos); 125 | mach_vm_read(task, dyld_info.all_image_info_addr, size, &readData, &dataCnt); 126 | unsigned char* data = (unsigned char*)readData; 127 | struct dyld_all_image_infos* infos = (struct dyld_all_image_infos*)data; 128 | size = sizeof(struct dyld_image_info)*(infos -> infoArrayCount); 129 | mach_vm_read(task, (mach_vm_address_t)infos -> infoArray, size, &readData, &dataCnt); 130 | unsigned char* info_buf = (unsigned char*)readData; 131 | struct dyld_image_info* info = (struct dyld_image_info*)info_buf; 132 | 133 | for (int i = 0 ; i < (infos -> infoArrayCount) ; i++) 134 | { 135 | size = PATH_MAX; 136 | mach_vm_read(task, (mach_vm_address_t)info[i].imageFilePath, size, &readData, &dataCnt); 137 | unsigned char* foundpath = (unsigned char*)readData; 138 | if (foundpath) 139 | { 140 | // printf("Checking dylib: %s\n", foundpath); 141 | if (strcmp((const char*)(foundpath), dylib_path) == 0) 142 | { 143 | LOG_INFO("Dylib already loaded: %s", foundpath); 144 | image_exists = true; 145 | break; 146 | } 147 | } 148 | } 149 | return image_exists; 150 | } 151 | 152 | #ifdef __x86_64__ 153 | 154 | int inject_dylib_x86(pid_t pid, const char *lib){ 155 | //Function addresses. 156 | const static void* pthread_create_from_mach_thread_address = 157 | (const void*)pthread_create_from_mach_thread; 158 | 159 | const static void* dlopen_address = (const void*)dlopen; 160 | 161 | vm_size_t path_length = strlen(lib); 162 | 163 | //Obtain the task port. 164 | task_t task; 165 | kr(task_for_pid(mach_task_self_, pid, &task)); 166 | 167 | if (is_dylib_loaded2(task,lib)) { 168 | return -1; 169 | } 170 | 171 | 172 | mach_vm_address_t mach_code_mem = 0; 173 | mach_vm_address_t posix_code_mem = 0; 174 | mach_vm_address_t stack_mem = 0; 175 | mach_vm_address_t path_mem = 0; 176 | mach_vm_address_t posix_param_mem = 0; 177 | 178 | kr(mach_vm_allocate(task, &mach_code_mem, MACH_CODE_SIZE, VM_FLAGS_ANYWHERE)); 179 | kr(mach_vm_allocate(task, &posix_code_mem, MACH_CODE_SIZE, VM_FLAGS_ANYWHERE)); 180 | //Allocate the path variable and the stack. 181 | kr(mach_vm_allocate(task, &stack_mem, STACK_SIZE, VM_FLAGS_ANYWHERE)); 182 | kr(mach_vm_allocate(task, &path_mem, path_length, VM_FLAGS_ANYWHERE)); 183 | //Allocate the pthread parameter array. 184 | kr(mach_vm_allocate(task, &posix_param_mem, (PTR_SIZE * 2), VM_FLAGS_ANYWHERE)); 185 | 186 | //Write the path into memory. 187 | kr(mach_vm_write(task, path_mem, (vm_offset_t)lib, (int)path_length)); 188 | //Write the parameter array contents into memory. This array will be the pthread's parameter. 189 | //The address of dlopen() is the first parameter. 190 | kr(mach_vm_write(task, posix_param_mem, (vm_offset_t)&dlopen_address, PTR_SIZE)); 191 | //The pointer to the dylib path is the second parameter. 192 | kr(mach_vm_write(task, (posix_param_mem - PTR_SIZE), (vm_offset_t)&path_mem, PTR_SIZE)); 193 | //Write to both instructions, and mark them as readable, writable, and executable. 194 | //Do it for the mach thread instruction. 195 | kr(mach_vm_write(task, mach_code_mem, (vm_offset_t)&mach_thread_code, MACH_CODE_SIZE)); 196 | //Do it for the pthread instruction. 197 | kr(mach_vm_write(task, posix_code_mem, (vm_offset_t)&posix_thread_code, POSIX_CODE_SIZE)); 198 | 199 | kr(mach_vm_protect(task, mach_code_mem, MACH_CODE_SIZE, FALSE, VM_PROT_ALL)); 200 | kr(mach_vm_protect(task, posix_code_mem, POSIX_CODE_SIZE, FALSE, VM_PROT_ALL)); 201 | 202 | //The state and state count for launching the thread and reading its registers. 203 | mach_msg_type_number_t state_count = x86_THREAD_STATE64_COUNT; 204 | mach_msg_type_number_t state = x86_THREAD_STATE64; 205 | 206 | //Set all the registers to 0 so we can avoid setting extra registers to 0. 207 | x86_thread_state64_t regs; 208 | bzero(®s, sizeof(regs)); 209 | 210 | //Set the mach thread instruction pointer. 211 | regs.__rip = (__uint64_t)mach_code_mem; 212 | 213 | //Since the stack "grows" downwards, this is a usable stack pointer. 214 | regs.__rsp = (__uint64_t)(stack_mem + STACK_SIZE); 215 | 216 | //Set the function address, the 3rd parameter, and the 4th parameter. 217 | regs.__rax = (__uint64_t)pthread_create_from_mach_thread_address; 218 | regs.__rdx = (__uint64_t)posix_code_mem; 219 | regs.__rcx = (__uint64_t)posix_param_mem; 220 | 221 | 222 | 223 | //Initialize the thread. 224 | thread_act_t thread; 225 | kr(thread_create_running(task, state, (thread_state_t)(®s), state_count, &thread)); 226 | 227 | LOG_INFO("Monitoring register values for PID %d", pid); 228 | 229 | //Repeat check if a certain register has a certain value. 230 | for (;;) 231 | { 232 | mach_msg_type_number_t sc = state_count; 233 | kr(thread_get_state(thread, state, (thread_state_t)(®s), &sc)); 234 | if (regs.__rax == 777) 235 | { 236 | LOG_OK("Detected completion signal in RAX register"); 237 | break; 238 | } 239 | // TODO Sleep will cause the program to crash. 240 | } 241 | 242 | LOG_INFO("Cleaning up injection thread for PID %d", pid); 243 | kr(thread_suspend(thread)); 244 | kr(thread_terminate(thread)); 245 | 246 | kr(mach_vm_deallocate(task, stack_mem, STACK_SIZE)); 247 | kr(mach_vm_deallocate(task, mach_code_mem, MACH_CODE_SIZE)); 248 | 249 | LOG_OK("Successfully injected '%s' into PID %d", lib, pid); 250 | return 0; 251 | } 252 | #else 253 | int inject_dylib_arm(pid_t pid, const char *lib){ 254 | task_t task; 255 | kr(task_for_pid(mach_task_self_, pid, &task)); 256 | 257 | if (is_dylib_loaded2(task,lib)) { 258 | return -1; 259 | } 260 | 261 | mach_vm_address_t remote_mach_code = 0; 262 | mach_vm_address_t remote_stack = 0; 263 | mach_vm_address_t remote_pthread_mem = 0; 264 | mach_vm_address_t remote_path = 0; 265 | 266 | kr(mach_vm_allocate(task, &remote_mach_code, MACH_CODE_SIZE, VM_FLAGS_ANYWHERE)); 267 | kr(mach_vm_allocate(task, &remote_stack, STACK_SIZE, VM_FLAGS_ANYWHERE)); 268 | kr(mach_vm_allocate(task, &remote_pthread_mem, 8, VM_FLAGS_ANYWHERE)); 269 | kr(mach_vm_allocate(task, &remote_path, strlen(lib), VM_FLAGS_ANYWHERE)); 270 | 271 | kr(mach_vm_write(task, remote_path, (vm_address_t)lib, (int)strlen(lib))); 272 | kr(mach_vm_write(task, remote_mach_code, (vm_address_t)mach_thread_code, MACH_CODE_SIZE)); 273 | kr(mach_vm_protect(task, remote_mach_code, MACH_CODE_SIZE, FALSE, VM_PROT_READ|VM_PROT_EXECUTE)); 274 | 275 | __arm_thread_state64_t regs; 276 | bzero(®s, sizeof(regs)); 277 | regs.__pc = remote_mach_code; 278 | regs.__sp = remote_stack + STACK_SIZE; 279 | 280 | 281 | // pthread_create_from_mach_thread 282 | const static void* pthread_create_from_mach_thread_address = 283 | (const void*)pthread_create_from_mach_thread; 284 | regs.__x[4] = (vm_address_t)pthread_create_from_mach_thread_address; 285 | regs.__x[0] = remote_pthread_mem; 286 | regs.__x[1] = 0; 287 | regs.__x[2] = (vm_address_t)dlopen; 288 | regs.__x[3] = remote_path; 289 | 290 | 291 | thread_act_t remote_thread; 292 | kr(thread_create_running(task, ARM_THREAD_STATE64, (thread_state_t)®s, ARM_THREAD_STATE64_COUNT, &remote_thread)); 293 | LOG_INFO("Monitoring thread execution for PID %d", pid); 294 | 295 | 296 | sleep(1); 297 | 298 | LOG_INFO("Terminating injection thread for PID %d", pid); 299 | kr(thread_terminate(remote_thread)); 300 | 301 | kr(mach_vm_deallocate(task, remote_stack, STACK_SIZE)); 302 | kr(mach_vm_deallocate(task, remote_mach_code, MACH_CODE_SIZE)); 303 | kr(mach_vm_deallocate(task, remote_path, strlen(lib))); 304 | kr(mach_vm_deallocate(task, remote_pthread_mem, 8)); 305 | 306 | LOG_OK("Successfully injected '%s' into PID %d", lib, pid); 307 | return 0; 308 | } 309 | #endif 310 | 311 | int inject_dylib(pid_t pid, const char *lib) { 312 | 313 | #ifdef __x86_64__ 314 | inject_dylib_x86(pid, lib); 315 | #else 316 | inject_dylib_arm(pid, lib); 317 | #endif 318 | return (0); 319 | } 320 | 321 | 322 | ///Get the process ID of a process by its name. 323 | pid_t find_pid_by_name2(const char* process_name) 324 | { 325 | static pid_t pids[4096]; 326 | int retpid = -1; 327 | const int count = proc_listpids(PROC_ALL_PIDS, 0, pids, sizeof(pids)); 328 | const int proc_count = count/sizeof(pid_t); 329 | LOG_INFO("Scanning %d processes for target '%s'", proc_count, process_name); 330 | for (int i = 0; i < proc_count; i++) 331 | { 332 | struct proc_bsdinfo proc; 333 | const int st = proc_pidinfo(pids[i], PROC_PIDTBSDINFO, 0, &proc, PROC_PIDTBSDINFO_SIZE); 334 | if (st == PROC_PIDTBSDINFO_SIZE) 335 | { 336 | // printf("Checking process: %s (PID: %d)\n", proc.pbi_name, proc.pbi_pid); 337 | 338 | if (strcmp(process_name, proc.pbi_name) == 0) 339 | { 340 | LOG_OK("Found target process: %s (PID: %d)", proc.pbi_name, proc.pbi_pid); 341 | retpid = pids[i]; 342 | break; 343 | } 344 | } 345 | } 346 | return retpid; 347 | } 348 | 349 | pid_t find_pid_by_name(const char *process_name) { 350 | int mib[4] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; 351 | size_t size; 352 | if (sysctl(mib, 4, NULL, &size, NULL, 0) == -1) { 353 | LOG_ERR("sysctl operation failed"); 354 | return -1; 355 | } 356 | 357 | struct kinfo_proc *process_list = malloc(size); 358 | if (sysctl(mib, 4, process_list, &size, NULL, 0) == -1) { 359 | LOG_ERR("sysctl operation failed"); 360 | free(process_list); 361 | return -1; 362 | } 363 | 364 | size_t process_count = size / sizeof(struct kinfo_proc); 365 | pid_t pid = -1; 366 | 367 | LOG_INFO("Scanning %zu processes for target '%s'", process_count, process_name); 368 | 369 | for (size_t i = 0; i < process_count; i++) { 370 | char path[PROC_PIDPATHINFO_MAXSIZE]; 371 | proc_pidpath(process_list[i].kp_proc.p_pid, path, sizeof(path)); 372 | // printf("Checking process: %s (PID: %d)\n", path, process_list[i].kp_proc.p_pid); 373 | if (strstr(path, process_name) != NULL) { 374 | pid = process_list[i].kp_proc.p_pid; 375 | LOG_OK("Found target process: '%s' (PID: %d)", path, pid); 376 | break; 377 | } 378 | } 379 | 380 | if (pid == -1) { 381 | LOG_WARN("Process '%s' not found", process_name); 382 | } 383 | 384 | free(process_list); 385 | return pid; 386 | } 387 | 388 | 389 | 390 | 391 | int is_valid_dylib(const char *path) { 392 | struct stat buffer; 393 | return (stat(path, &buffer) == 0 && S_ISREG(buffer.st_mode)); 394 | } 395 | 396 | 397 | 398 | bool is_dylib_loaded(pid_t pid, const char *dylib_path) { 399 | char command[256]; 400 | snprintf(command, sizeof(command), "lsof -p %d | grep '.dylib'", pid); 401 | 402 | FILE *fp = popen(command, "r"); 403 | if (fp == NULL) { 404 | perror("popen"); 405 | return false; 406 | } 407 | 408 | char buffer[256]; 409 | while (fgets(buffer, sizeof(buffer), fp) != NULL) { 410 | // printf("dylib: %s", buffer); 411 | if (strstr(buffer, dylib_path) != NULL) { 412 | LOG_INFO("Dylib already loaded: %s", buffer); 413 | pclose(fp); 414 | return true; 415 | } 416 | } 417 | 418 | pclose(fp); 419 | return false; 420 | } 421 | 422 | char* convertToAbsolutePath(const char *relativePath) { 423 | char *absolutePath = realpath(relativePath, NULL); 424 | if (!absolutePath) { 425 | perror("Error resolving absolute path"); 426 | exit(EXIT_FAILURE); 427 | } 428 | return absolutePath; 429 | } 430 | 431 | 432 | // TODO: It seems that the timing is not very accurate ??? 433 | kern_return_t wait_for_dyld(pid_t pid) { 434 | kill(pid, SIGCONT); 435 | task_t task; 436 | kr(task_for_pid(mach_task_self(), pid, &task)); 437 | 438 | task_dyld_info_data_t dyld_info; 439 | mach_msg_type_number_t count = TASK_DYLD_INFO_COUNT; 440 | 441 | int retries = 0; 442 | int MAX_RETRIES = 1000; 443 | while (retries < MAX_RETRIES) { 444 | kr(task_info(task, TASK_DYLD_INFO, (task_info_t)&dyld_info, &count)); 445 | 446 | if (dyld_info.all_image_info_addr != 0) { 447 | LOG_OK("dyld images are loaded. Address: %p", (void*)dyld_info.all_image_info_addr); 448 | return KERN_SUCCESS; 449 | } 450 | 451 | retries++; 452 | LOG_WARN("dyld images are not loaded yet. Retrying... (%d/%d)", retries, MAX_RETRIES); 453 | usleep(1000); 454 | } 455 | 456 | LOG_ERR("Failed to load dyld images after %d retries", MAX_RETRIES); 457 | return KERN_FAILURE; 458 | } 459 | void get_executable_directory(char *buffer, size_t size) { 460 | char path[PATH_MAX]; 461 | uint32_t path_size = sizeof(path); 462 | 463 | // Get the full path of the executable 464 | if (_NSGetExecutablePath(path, &path_size) == 0) { 465 | // Get the directory of the executable 466 | strncpy(buffer, dirname(path), size); 467 | } else { 468 | LOG_FATAL("Failed to get executable path"); 469 | exit(EXIT_FAILURE); 470 | } 471 | } 472 | // Function to load configuration from the file 473 | // eg:/Applications/Hopper Disassembler v4.app/Contents/MacOS/Hopper Disassembler v4|./libdylib_dobby_hook.dylib|-spawn 474 | int load_config(const char *filename, char *arg1, char *arg2, char *arg3) { 475 | FILE *file = fopen(filename, "r"); 476 | if (!file) { 477 | LOG_ERR("Failed to open configuration file: %s", filename); 478 | return -1; 479 | } 480 | 481 | char line[1024]; 482 | if (fgets(line, sizeof(line), file)) { 483 | // Remove trailing newline 484 | line[strcspn(line, "\n")] = 0; 485 | 486 | // Split the line into up to 3 parts using '|' 487 | char *token = strtok(line, "|"); 488 | if (token) strncpy(arg1, token, 255); 489 | token = strtok(NULL, "|"); 490 | if (token) { 491 | // Convert the second argument (dylib path) to an absolute path if it's relative 492 | if (token[0] != '/') { 493 | char absolute_path[PATH_MAX] = {0}; 494 | get_executable_directory(absolute_path, sizeof(absolute_path)); 495 | strncat(absolute_path, "/", sizeof(absolute_path) - strlen(absolute_path) - 1); 496 | strncat(absolute_path, token, sizeof(absolute_path) - strlen(absolute_path) - 1); 497 | strncpy(arg2, absolute_path, 255); 498 | } else { 499 | strncpy(arg2, token, 255); 500 | } 501 | } 502 | token = strtok(NULL, "|"); 503 | if (token) strncpy(arg3, token, 255); 504 | } else { 505 | LOG_ERR("Configuration file is empty or invalid"); 506 | fclose(file); 507 | return -1; 508 | } 509 | 510 | fclose(file); 511 | return 0; 512 | } 513 | 514 | int main(int argc, const char * argv[]) { 515 | @autoreleasepool { 516 | char config_path[PATH_MAX] = {0}; 517 | char config_arg1[256] = {0}; 518 | char config_arg2[256] = {0}; 519 | char config_arg3[256] = {0}; 520 | get_executable_directory(config_path, sizeof(config_path)); 521 | strncat(config_path, "/" CONFIG_FILE, sizeof(config_path) - strlen(config_path) - 1); 522 | 523 | // Check if configuration file exists and load it 524 | if (access(config_path, F_OK) == 0) { 525 | LOG_INFO("Configuration file '%s' found. Loading...", CONFIG_FILE); 526 | if (load_config(config_path, config_arg1, config_arg2, config_arg3) == 0) { 527 | LOG_INFO("Loaded configuration: %s | %s | %s", config_arg1, config_arg2, config_arg3); 528 | // Override command-line arguments with config values 529 | argv[1] = config_arg1; 530 | argv[2] = config_arg2; 531 | if (strlen(config_arg3) > 0) { 532 | argv[3] = config_arg3; 533 | argc = 4; // Update argument count 534 | } else { 535 | argc = 3; 536 | } 537 | } else { 538 | LOG_FATAL("Failed to load configuration file"); 539 | return EXIT_FAILURE; 540 | } 541 | } 542 | 543 | if (argc < 2) { 544 | fprintf(stderr, "\n" 545 | "process_inject: a macOS dylib injector by marlkiller @ GitHub\n\n" 546 | "Usage: %s [flags]\n\n" 547 | "Modes:\n" 548 | " - Inject into running process by PID\n" 549 | " -name - Inject into running process by name\n" 550 | " -spawn - Spawn new process and inject\n\n" 551 | "Arguments:\n" 552 | " - Process ID or process name (with -name flag)\n" 553 | " - Full path to executable (with -spawn flag)\n" 554 | " - Full path to dynamic library to inject\n\n" 555 | "Options:\n" 556 | " -name - Treat as process name instead of PID\n" 557 | " -spawn - Launch new process in suspended state and inject\n\n" 558 | "Examples:\n" 559 | " %s 1234 /path/to/library.dylib\n" 560 | " %s Safari /path/to/library.dylib -name\n" 561 | " %s /Applications/Calculator.app/Contents/MacOS/Calculator /path/to/library.dylib -spawn\n\n" 562 | "Note: Injecting into root processes requires root privileges\n", 563 | argv[0], argv[0], argv[0], argv[0]); 564 | return EXIT_FAILURE; 565 | } 566 | 567 | const char *process = argv[1]; 568 | const char *dylib_path = argv[2]; 569 | pid_t pid = atoi(process); 570 | 571 | if (dylib_path[0] != '/') { 572 | // Convert to absolute path if it's relative 573 | char *absoluteDylibPath = convertToAbsolutePath(dylib_path); 574 | dylib_path = absoluteDylibPath; // Update to the absolute path 575 | } 576 | 577 | if (!is_valid_dylib(dylib_path)) { 578 | LOG_FATAL("Invalid dylib path: %s", dylib_path); 579 | return EXIT_FAILURE; 580 | } 581 | if (argc > 3) 582 | { 583 | if (strcmp(argv[3], "-name") == 0) 584 | { 585 | pid = find_pid_by_name(process); 586 | } 587 | if (strcmp(argv[3], "-spawn") == 0) 588 | { 589 | posix_spawnattr_t attr; 590 | posix_spawnattr_init(&attr); 591 | short flags = POSIX_SPAWN_START_SUSPENDED; 592 | posix_spawnattr_setflags(&attr, flags); 593 | // File actions for redirecting output 594 | posix_spawn_file_actions_t file_actions; 595 | posix_spawn_file_actions_init(&file_actions); 596 | // posix_spawn_file_actions_addopen(&file_actions, STDOUT_FILENO, "/dev/null", O_WRONLY, 0); 597 | // posix_spawn_file_actions_addopen(&file_actions, STDERR_FILENO, "/dev/null", O_WRONLY, 0); 598 | int spawn_result = posix_spawn(&pid, process, &file_actions, &attr, (char *const[]){(char *)process, NULL}, environ); 599 | // int spawn_result = posix_spawn(&pid, process, NULL, NULL, (char *const[]){(char *)process, NULL}, environ); 600 | posix_spawnattr_destroy(&attr); 601 | if (spawn_result != 0) { 602 | LOG_FATAL("Failed to spawn process: %s", strerror(spawn_result)); 603 | return EXIT_FAILURE; 604 | } 605 | LOG_OK("Launched target process (PID: %d)", pid); 606 | // wait_for_dyld(pid); 607 | kill(pid, SIGCONT); 608 | } 609 | } 610 | else 611 | { 612 | pid = atoi(process); 613 | } 614 | 615 | if (pid <= 0) { 616 | LOG_FATAL("Invalid PID or process not found: %s", process); 617 | return EXIT_FAILURE; 618 | } 619 | 620 | LOG_INFO("Injection target: PID %d", pid); 621 | LOG_INFO("Dylib path: %s", dylib_path); 622 | inject_dylib(pid, dylib_path); 623 | // sleep(5); 624 | LOG_INFO("Injection completed. Press any key to exit..."); 625 | getchar(); 626 | } 627 | return 0; 628 | } 629 | -------------------------------------------------------------------------------- /test_bin/bin_dev.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | #if defined(__arm64__) 14 | #include 15 | #endif 16 | 17 | // clang++ -o bin_dev bin_dev.cpp -lpthread -arch x86_64 -arch arm64 18 | // clang++ -o bin_dev_x86_64 bin_dev.cpp -lpthread -arch x86_64 19 | // clang++ -o bin_dev_arm64 bin_dev.cpp -lpthread -arch arm64 20 | // Thread ID: 259, Name: , NameFlag: 0, User Time: 0s 2455μs, System Time: 0s 2972μs, CPU Usage: 0, Policy: 1, Run State: 3, Flags: 1, Suspend Count: 0, Sleep Time: 0 21 | // Thread ID: 2563, Name: , NameFlag: 0, User Time: 0s 3254μs, System Time: 0s 4622μs, CPU Usage: 0, Policy: 1, Run State: 1, Flags: 0, Suspend Count: 0, Sleep Time: 0 22 | // Thread ID: 3591, Name: , NameFlag: 3, User Time: 1s 276351μs, System Time: 0s 2132μs, CPU Usage: 0, Policy: 1, Run State: 3, Flags: 1, Suspend Count: 20, Sleep Time: 0 23 | // Thread ID: 2819, Name: , NameFlag: 0, User Time: 1s 269062μs, System Time: 0s 8449μs, CPU Usage: 0, Policy: 1, Run State: 3, Flags: 1, Suspend Count: 20, Sleep Time: 0 24 | void printThreads() { 25 | mach_msg_type_number_t threadCount; 26 | thread_act_t *threads; 27 | 28 | // 获取当前进程的线程 29 | if (task_threads(mach_task_self(), &threads, &threadCount) != KERN_SUCCESS) { 30 | std::cerr << "Failed to get threads." << std::endl; 31 | return; 32 | } 33 | std::cerr << "-----------printThreads-----------" << std::endl; 34 | 35 | thread_act_t threadsToTerminate[threadCount]; 36 | int terminateCount = 0; 37 | 38 | for (mach_msg_type_number_t i = 0; i < threadCount; i++) { 39 | thread_info_data_t threadInfo; 40 | mach_msg_type_number_t threadInfoCount = THREAD_INFO_MAX; 41 | 42 | if (thread_info(threads[i], THREAD_BASIC_INFO, (thread_info_t)&threadInfo, &threadInfoCount) == KERN_SUCCESS) { 43 | thread_basic_info_t basicInfo = (thread_basic_info_t)&threadInfo; 44 | 45 | char threadName[256] = {0}; 46 | pthread_t threadId = pthread_from_mach_thread_np(threads[i]); 47 | // On success, these functions return 0; on error, they return a nonzero error number. 48 | int nameFlag = pthread_getname_np(threadId, threadName, sizeof(threadName)); 49 | std::cout << "THREAD_BASIC_INFO Thread ID: " << threads[i] 50 | << ", Name: " << threadName 51 | << ", NameFlag: " << nameFlag 52 | << ", User Time: " << basicInfo->user_time.seconds << "s " << basicInfo->user_time.microseconds << "μs" 53 | << ", System Time: " << basicInfo->system_time.seconds << "s " << basicInfo->system_time.microseconds << "μs" 54 | << ", CPU Usage: " << basicInfo->cpu_usage 55 | << ", Policy: " << basicInfo->policy 56 | << ", Run State: " << basicInfo->run_state 57 | << ", Flags: " << basicInfo->flags 58 | << ", Suspend Count: " << basicInfo->suspend_count 59 | << ", Sleep Time: " << basicInfo->sleep_time 60 | << std::endl; 61 | 62 | // if (nameFlag!=0){ 63 | // threadsToTerminate[terminateCount++] = threads[i]; 64 | // #if defined(__arm64__) || defined(__aarch64__) 65 | // if (i+1 < threadCount) { 66 | // threadsToTerminate[terminateCount++] = threads[i+1]; 67 | // } 68 | // #endif 69 | // } 70 | } 71 | 72 | 73 | // #if defined(__arm64__) 74 | // mach_msg_type_number_t stateCount = ARM_THREAD_STATE64_COUNT; 75 | // thread_state_flavor_t flavor = ARM_THREAD_STATE64; 76 | // arm_thread_state64_t threadState; 77 | // #else 78 | // mach_msg_type_number_t stateCount = x86_THREAD_STATE64_COUNT; 79 | // thread_state_flavor_t flavor = x86_THREAD_STATE64; 80 | // x86_thread_state64_t threadState; 81 | // #endif 82 | 83 | 84 | // if (thread_get_state(threads[i], flavor, (thread_state_t)&threadState, &stateCount) == KERN_SUCCESS) { 85 | // #if defined(__arm64__) 86 | // printf( 87 | // "Thread ID: %u, PC: 0x%llx, SP: 0x%llx, PSR: 0x%x, X0: 0x%llx," 88 | // // " X1: 0x%llx, X2: 0x%llx, X3: 0x%llx, X4: 0x%llx, X5: 0x%llx, X6: 0x%llx, X7: 0x%llx, X8: 0x%llx, X9: 0x%llx, X10: 0x%llx, X11: 0x%llx, X12: 0x%llx, X13: 0x%llx, X14: 0x%llx, X15: 0x%llx, X16: 0x%llx, X17: 0x%llx, X18: 0x%llx, X19: 0x%llx, X20: 0x%llx, X21: 0x%llx, X22: 0x%llx, X23: 0x%llx, X24: 0x%llx, X25: 0x%llx, X26: 0x%llx, X27: 0x%llx, X28: 0x%llx, FP: 0x%llx, LR: 0x%llx" 89 | // "\n", 90 | // threads[i], 91 | // threadState.__pc, 92 | // threadState.__sp, 93 | // threadState.__cpsr, 94 | // threadState.__x[0] 95 | // // ,threadState.__x[1], threadState.__x[2], threadState.__x[3], 96 | // // threadState.__x[4], threadState.__x[5], threadState.__x[6], threadState.__x[7], 97 | // // threadState.__x[8], threadState.__x[9], threadState.__x[10], threadState.__x[11], 98 | // // threadState.__x[12], threadState.__x[13], threadState.__x[14], threadState.__x[15], 99 | // // threadState.__x[16], threadState.__x[17], threadState.__x[18], threadState.__x[19], 100 | // // threadState.__x[20], threadState.__x[21], threadState.__x[22], threadState.__x[23], 101 | // // threadState.__x[24], threadState.__x[25], threadState.__x[26], threadState.__x[27], 102 | // // threadState.__x[28], threadState.__fp, threadState.__lr 103 | // ); 104 | 105 | // if (threadState.__x[0] == 0xd13) 106 | // { 107 | // threadsToTerminate[terminateCount++] = threads[i]; 108 | // } 109 | // #else 110 | // // 打印 x86_64 架构下的寄存器, 0xd13 111 | // printf("Thread ID: %u, RIP: 0x%llx, RSP: 0x%llx, RFLAGS: 0x%llx, RAX: 0x%llx, " 112 | // // "RBX: 0x%llx, RCX: 0x%llx, RDX: 0x%llx, RSI: 0x%llx, RDI: 0x%llx, RBP: 0x%llx, R8: 0x%llx, R9: 0x%llx, R10: 0x%llx, R11: 0x%llx, R12: 0x%llx, R13: 0x%llx, R14: 0x%llx, R15: 0x%llx" 113 | // "\n", 114 | // threads[i], 115 | // threadState.__rip, threadState.__rsp, threadState.__rflags,threadState.__rax 116 | // //, threadState.__rbx, threadState.__rcx, threadState.__rdx, 117 | // // threadState.__rsi, threadState.__rdi, threadState.__rbp, 118 | // // threadState.__r8, threadState.__r9, threadState.__r10, threadState.__r11, 119 | // // threadState.__r12, threadState.__r13, threadState.__r14, threadState.__r15 120 | // ); 121 | 122 | // if (threadState.__rax == 0xd13) 123 | // { 124 | // threadsToTerminate[terminateCount++] = threads[i]; 125 | // } 126 | 127 | // #endif 128 | 129 | // } 130 | 131 | // // 获取 THREAD_IDENTIFIER_INFO 132 | // thread_identifier_info_data_t threadIdInfo; 133 | // mach_msg_type_number_t idInfoCount = THREAD_IDENTIFIER_INFO_COUNT; 134 | 135 | // if (thread_info(threads[i], THREAD_IDENTIFIER_INFO, (thread_info_t)&threadIdInfo, &idInfoCount) == KERN_SUCCESS) { 136 | // std::cout << "THREAD_IDENTIFIER_INFO Thread ID: " << threads[i] 137 | // << ", thread_handle: " << threadIdInfo.thread_handle 138 | // << ", dispatch_qaddr: " << threadIdInfo.dispatch_qaddr 139 | // << std::endl; 140 | // } 141 | 142 | 143 | // // 获取 THREAD_EXTENDED_INFO 144 | // thread_extended_info_data_t threadExtendedInfo; 145 | // mach_msg_type_number_t extendedInfoCount = THREAD_EXTENDED_INFO_COUNT; 146 | 147 | // if (thread_info(threads[i], THREAD_EXTENDED_INFO, (thread_info_t)&threadExtendedInfo, &extendedInfoCount) == KERN_SUCCESS) { 148 | // std::cout << "THREAD_EXTENDED_INFO Thread ID: " << threads[i] 149 | // << ", User Time: " << threadExtendedInfo.pth_user_time << " ns" 150 | // << ", System Time: " << threadExtendedInfo.pth_system_time << " ns" 151 | // << ", CPU Usage: " << threadExtendedInfo.pth_cpu_usage 152 | // << ", Policy: " << threadExtendedInfo.pth_policy 153 | // << ", Run State: " << threadExtendedInfo.pth_run_state 154 | // << ", Flags: " << threadExtendedInfo.pth_flags 155 | // << ", Sleep Time: " << threadExtendedInfo.pth_sleep_time << " s" 156 | // << ", Current Priority: " << threadExtendedInfo.pth_curpri 157 | // << ", Priority: " << threadExtendedInfo.pth_priority 158 | // << ", Max Priority: " << threadExtendedInfo.pth_maxpriority 159 | // << ", Thread Name: " << threadExtendedInfo.pth_name 160 | // << std::endl; 161 | // } 162 | } 163 | 164 | for (int i = 0; i < terminateCount; i++) { 165 | std::cerr << "kill thread: " << threadsToTerminate[i] << std::endl; 166 | if (thread_suspend(threadsToTerminate[i]) != KERN_SUCCESS) { 167 | std::cerr << "Failed to suspend thread." << std::endl; 168 | } 169 | if (thread_terminate(threadsToTerminate[i]) != KERN_SUCCESS) { 170 | std::cerr << "Failed to terminate thread." << std::endl; 171 | } 172 | } 173 | 174 | // 释放线程数组 175 | vm_deallocate(mach_task_self(), (vm_address_t)threads, threadCount * sizeof(thread_act_t)); 176 | } 177 | 178 | void* threadFunction(void* arg) { 179 | char name[256]; 180 | snprintf(name, sizeof(name), "WorkerThread-%ld", (long)arg); 181 | // pthread_setname_np( name); 182 | 183 | while (true) { 184 | printThreads(); 185 | std::this_thread::sleep_for(std::chrono::seconds(2)); // 每 2 秒刷新一次 186 | } 187 | return nullptr; 188 | } 189 | 190 | int main() { 191 | pthread_t thread; 192 | pthread_create(&thread, NULL, threadFunction, NULL); 193 | // 主线程可以做其他事情,或者等待子线程结束 194 | pthread_join(thread, NULL); 195 | return 0; 196 | } -------------------------------------------------------------------------------- /test_lib/inject.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | 5 | // # 编译ARM版本 6 | // clang -target arm64-apple-macos11 -dynamiclib -o inject_arm.dylib inject.c 7 | 8 | // # 编译x86_64版本 9 | // clang -target x86_64-apple-macos11 -dynamiclib -o inject_x86.dylib inject.c 10 | 11 | // # 使用lipo创建通用二进制 12 | // lipo -create inject_arm.dylib inject_x86.dylib -output inject.dylib 13 | 14 | __attribute__((constructor)) 15 | static void customConstructor(int argc, const char **argv) 16 | { 17 | printf(">>>>>>>> Hello from dylib!\n"); 18 | syslog(LOG_ERR, ">>>>>>>> Dylib injection successful in %s\n", argv[0]); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /test_lib/inject.m: -------------------------------------------------------------------------------- 1 | // # 编译ARM版本 2 | // clang -target arm64-apple-macos11 -dynamiclib -o inject_arm.dylib inject.m -framework Foundation 3 | 4 | // # 编译x86_64版本 5 | // clang -target x86_64-apple-macos11 -dynamiclib -o inject_x86.dylib inject.m -framework Foundation 6 | 7 | // # 使用lipo创建通用二进制 8 | // lipo -create inject_arm.dylib inject_x86.dylib -output inject.dylib -framework Foundation 9 | 10 | #import 11 | #import 12 | #import 13 | #import 14 | #import 15 | 16 | 17 | 18 | 19 | __attribute__((constructor)) 20 | static void customConstructor(void) { 21 | NSLog(@">>>>>> OC dylib loaded"); 22 | } 23 | 24 | @implementation dylib_dobby_hook 25 | @end 26 | --------------------------------------------------------------------------------