├── cfg.data
├── process_inject.xcodeproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcuserdata
│ │ └── artemis.xcuserdatad
│ │ └── UserInterfaceState.xcuserstate
├── xcuserdata
│ ├── voidm.xcuserdatad
│ │ └── xcschemes
│ │ │ └── xcschememanagement.plist
│ └── artemis.xcuserdatad
│ │ └── xcschemes
│ │ └── xcschememanagement.plist
├── xcshareddata
│ └── xcschemes
│ │ └── process_inject.xcscheme
└── project.pbxproj
├── process_inject.entitlements
├── test_lib
├── inject.c
└── inject.m
├── README.md
├── .github
└── workflows
│ └── objective-c-xcode.yml
├── test_bin
└── bin_dev.cpp
└── process_inject
└── main.m
/cfg.data:
--------------------------------------------------------------------------------
1 | /Applications/Hopper Disassembler v4.app/Contents/MacOS/Hopper Disassembler v4|libdylib_dobby_hook.dylib|-spawn
2 |
--------------------------------------------------------------------------------
/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/HEAD/process_inject.xcodeproj/project.xcworkspace/xcuserdata/artemis.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/process_inject.entitlements:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | com.apple.security.app-sandbox
6 |
7 | com.apple.security.cs.debugger
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 | xcodebuild -project process_inject.xcodeproj -scheme process_inject -configuration Release -arch arm64 -arch x86_64 ONLY_ACTIVE_ARCH=NO BUILD_LIBRARY_FOR_DISTRIBUTION=YES CODE_SIGNING_ALLOWED=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -derivedDataPath ./build
43 | BUILT_PRODUCTS_DIR="./build/Build/Products/Release/"
44 | ls -la "$BUILT_PRODUCTS_DIR"
45 |
46 | echo "-----------Checking entitlements before signing:-----------"
47 | codesign -d --entitlements - "$BUILT_PRODUCTS_DIR/process_inject"
48 |
49 | sudo codesign -f -s - --all-architectures --deep --entitlements "process_inject.entitlements" "$BUILT_PRODUCTS_DIR/process_inject"
50 |
51 | echo "-----------Checking entitlements after signing:-----------"
52 | codesign -d --entitlements - "$BUILT_PRODUCTS_DIR/process_inject"
53 |
54 | tar -czvf process_inject.tar.gz -C "$BUILT_PRODUCTS_DIR" process_inject
55 |
56 |
57 |
58 | - name: update release
59 | uses: ncipollo/release-action@v1
60 | with:
61 | token: ${{ secrets.TOKEN }}
62 | tag: latest
63 | body: |
64 | A macOS dylib project based on the Dobby Hook framework, aimed at enhancing and extending the functionality of target software.
65 |
66 | ## Latest Commit
67 | ${{ github.event.head_commit.message }}
68 | artifacts: "process_inject.tar.gz"
69 | allowUpdates: true
70 | replacesArtifacts: true
71 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------