Skip to content

Commit

Permalink
Workaround for thread_set_state limitation, requires disabling AMFI. F…
Browse files Browse the repository at this point in the history
…ixes #32, Fixes #33
  • Loading branch information
LIJI32 committed Jun 15, 2024
1 parent 004a832 commit c72a415
Show file tree
Hide file tree
Showing 9 changed files with 175 additions and 56 deletions.
32 changes: 21 additions & 11 deletions MIP/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -36,29 +36,38 @@ endif
SIGN_IDENTITY ?= CodeSign
CODESIGN_TARGET := codesign -s "$(SIGN_IDENTITY)"

all: build/lsdinjector.dylib build/loader.dylib build/inject local.lsdinjector.plist
all: build/lsdinjector.dylib build/loader.dylib build/inject build/injectd local.lsdinjector.plist

build/lsdinjector.dylib: build/injector/lsd_injector.c.o \
build/injector/injector.o
$(CC) $(LDFLAGS) -arch $(ARCH1) $^ -lbsm -shared -o $@
build/lsdinjector.dylib: build/injector/lsd_injector.c.o build/injector/injectd_client/injectd_client.m.o
$(CC) $(LDFLAGS) -arch $(ARCH1) $^ -lbsm -framework Foundation -shared -o $@
$(CODESIGN_TARGET) $@

build/loader.dylib: $(LOADER_SOURCES)
$(CC) $(CFLAGS) $(SUBSTITUTE_CFLAGS) $(LDFLAGS) -install_name /Library/Apple/System/Library/Frameworks/mip/loader.dylib -framework Foundation -shared -arch $(ARCH1) $(if $(DUAL_ARCH), -arch $(ARCH2)) $^ -o $@
$(CODESIGN_TARGET) $@

build/inject: build/injector/injector.o build/injector/main.c.o | injector/inject.entitlements
build/injectd: build/injector/injector.o build/injector/injectd.m.o | injector/injectd.entitlements
mkdir -p $(dir $@)
$(CC) $(LDFLAGS) $^ -o $@ -arch $(ARCH1)
$(CODESIGN_TARGET) --entitlements injector/inject.entitlements $@
$(CC) $(LDFLAGS) -framework Foundation $^ -o $@ -arch $(ARCH1)
$(CODESIGN_TARGET) --entitlements injector/injectd.entitlements $@

build/inject: build/injector/inject.c.o build/injector/injectd_client/injectd_client.m.o
mkdir -p $(dir $@)
$(CC) $(LDFLAGS) -framework Foundation $^ -o $@ -arch $(ARCH1)
$(CODESIGN_TARGET) $@

build/injector/injector.o: build/injector/inject/inject.c.o build/injector/payloads/injected_$(ARCH1).c.bin build/injector/payloads/injected_$(ARCH2).c.bin
ld -r $(filter %.o,$^) -o $@ -sectcreate __INJ_$(ARCH1) __inj_$(ARCH1) build/injector/payloads/injected_$(ARCH1).c.bin -sectcreate __INJ_$(ARCH2) __inj_$(ARCH2) build/injector/payloads/injected_$(ARCH2).c.bin

build/%.o: %
build/%.c.o: %.c
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -arch $(ARCH1) -c $^ -o $@

build/%.m.o: %.m
mkdir -p $(dir $@)
$(CC) $(CFLAGS) -fobjc-arc -arch $(ARCH1) -c $^ -o $@


build/injector/payloads/injected_i386.c.dylib: injector/payloads/injected_i386.c
mkdir -p $(dir $@)

Expand Down Expand Up @@ -93,12 +102,13 @@ install: all
sudo mkdir -p /Library/Apple/System/Library/Frameworks/mip/Bundles
sudo mkdir -p /usr/local/include/mip
@# We remove the old libraries before copying, overwriting causes codesigning issues.
-@sudo rm -f /Library/Apple/System/Library/Frameworks/mip/lsdinjector.dylib /Library/Apple/System/Library/Frameworks/mip/loader.dylib
sudo cp build/lsdinjector.dylib build/loader.dylib /Library/Apple/System/Library/Frameworks/mip/
-@sudo rm -f /Library/Apple/System/Library/Frameworks/mip/{lsdinjector.dylib,loader.dylib,injectd}
sudo cp build/lsdinjector.dylib build/loader.dylib build/injectd /Library/Apple/System/Library/Frameworks/mip/
sudo cp build/inject /usr/local/bin/
sudo cp loader/loader_public.h /usr/local/include/mip/loader.h
sudo cp local.lsdinjector.plist /Library/LaunchDaemons/
sudo cp local.lsdinjector.plist local.injectd.plist /Library/LaunchDaemons/
sudo defaults write /Library/Preferences/com.apple.security.libraryvalidation.plist DisableLibraryValidation -bool true
-sudo launchctl bootstrap system /Library/LaunchDaemons/local.injectd.plist
if nvram boot-args | grep -v tss_should_crash=0; then \
sudo nvram boot-args="`nvram boot-args | cut -c 11-` tss_should_crash=0"; \
if nvram boot-args | grep -v tss_should_crash=0; then\
Expand Down
33 changes: 7 additions & 26 deletions MIP/injector/main.c → MIP/injector/inject.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
#include <sys/types.h>
#include <signal.h>
#include <stdbool.h>
#include "inject/inject.h"
#include "injectd_client/injectd_client.h"

int main(int argc, const char **argv)
{
Expand Down Expand Up @@ -69,31 +69,12 @@ int main(int argc, const char **argv)
}

fprintf(stderr, "Injecting to process %d\n", pid);

kern_return_t ret = KERN_SUCCESS;

if ((ret = task_for_pid(mach_task_self(), pid, &task))) {
fprintf(stderr, "Failed to get task for pid %d (error %x). Make sure %s is signed correctly or run it as root.\n", pid, ret, argv[0]);
exit(ret);

const char *error = inject_to_pid(pid, argv[2], true);
if (error) {
fprintf(stderr, "Injection failed: %s\n", error);
exit(1);
}

ret = inject_to_task(task, argv[2]);
if (ret) {
fprintf(stderr, "Injection failed with error %x.\n", ret);
exit(ret);
}

/* This interrupts blocking system calls to ensure execution. */
mach_port_t thread;

ret = get_thread_port_for_task(task, &thread);
if (!ret) {
ret = thread_abort(thread);
}
if (ret) {
fprintf(stderr, "Injection succeeded, but the injected library will only run after the main thread wakes up\n");
exit(errno);
}


return 0;
}
File renamed without changes.
80 changes: 80 additions & 0 deletions MIP/injector/injectd.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#import <Foundation/Foundation.h>
#import "injectd_client/MIPProtocol.h"
#import "inject/inject.h"

@interface MIPConnection : NSObject <MIPProtocol>
@end

@implementation MIPConnection

- (void)injectDylib:(const char *)dylib toPID:(pid_t)pid interruptSyscalls:(bool)interrupt withReply:(void (^)(NSString *error))reply
{
kern_return_t ret = KERN_SUCCESS;
mach_port_t task = 0;

if ((ret = task_for_pid(mach_task_self(), pid, &task))) {
reply(@"Failed to obtain task for PID.");
return;
}

ret = inject_to_task(task, dylib);
if (ret) {
reply(@"Injection failed, check Console for details.");
return;
}

if (!interrupt) {
reply(nil);
return;
}

/* This interrupts blocking system calls to ensure execution. */
mach_port_t thread;

ret = get_thread_port_for_task(task, &thread);
if (!ret) {
ret = thread_abort(thread);
}
if (ret) {
reply(@"Injection succeeded, but the injected library will only run after the main thread wakes up.");
return;
}

reply(nil);
}

@end

@interface ServiceDelegate : NSObject <NSXPCListenerDelegate>
@end

@implementation ServiceDelegate

- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)newConnection
{
if (newConnection.effectiveUserIdentifier != 0) {
[newConnection invalidate];
return false;
}

newConnection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MIPProtocol)];
MIPConnection *exportedObject = [[MIPConnection alloc] init];
newConnection.exportedObject = exportedObject;

[newConnection resume];
return true;
}

@end

int main(int argc, const char *argv[])
{
ServiceDelegate *delegate = [ServiceDelegate new];

NSXPCListener *listener = [[NSXPCListener alloc] initWithMachServiceName:@"local.injectd"];
listener.delegate = delegate;

[listener resume];
[[NSRunLoop mainRunLoop] run];
return 0;
}
5 changes: 5 additions & 0 deletions MIP/injector/injectd_client/MIPProtocol.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#import <Foundation/Foundation.h>

@protocol MIPProtocol
- (void)injectDylib:(const char *)dylib toPID:(pid_t)pid interruptSyscalls:(bool)interrupt withReply:(void (^)(NSString *error))reply;
@end
4 changes: 4 additions & 0 deletions MIP/injector/injectd_client/injectd_client.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
#include <stdbool.h>
#include <sys/types.h>

const char *inject_to_pid(pid_t pid, const char *dylib, bool interrupt_syscalls);
54 changes: 54 additions & 0 deletions MIP/injector/injectd_client/injectd_client.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
#import <Foundation/Foundation.h>
#import <pthread.h>
#import "MIPProtocol.h"

const char *inject_to_pid(pid_t pid, const char *dylib, bool interrupt_syscalls)
{
__block NSString *ret = @"Could not connect to injectd";

@autoreleasepool {
static id<MIPProtocol> remoteObject = nil;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;


__block bool shouldRetry = false;
__block bool didRetry = false;

retry:
pthread_mutex_lock(&lock);
if (!remoteObject) {
static NSXPCConnection *connection = nil;
if (connection) {
[connection invalidate];
connection = nil;
}

connection = [[NSXPCConnection alloc] initWithMachServiceName:@"local.injectd" options:0];
connection.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MIPProtocol)];
[connection resume];

remoteObject = [connection synchronousRemoteObjectProxyWithErrorHandler:^(NSError *error) {
NSLog(@"injectd connection error: %@", error.localizedDescription);
ret = error.localizedDescription;
if (!didRetry) {
shouldRetry = true;
}
}];
}

[remoteObject injectDylib:dylib toPID:pid interruptSyscalls:interrupt_syscalls withReply:^(NSString *reply) {
ret = reply;
}];

pthread_mutex_unlock(&lock);

if (shouldRetry) {
shouldRetry = false;
didRetry = true;
remoteObject = nil;
goto retry;
}
}

return ret.UTF8String;
}
23 changes: 4 additions & 19 deletions MIP/injector/lsd_injector.c
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@
#include <sys/syslimits.h>

#include <loader/loader.h>
#import <mach-o/loader.h>
#import <mach-o/dyld.h>
#include "inject/inject.h"
#include <mach-o/loader.h>
#include <mach-o/dyld.h>
#include "injectd_client/injectd_client.h"

// Private APIs
extern mach_port_t xpc_dictionary_copy_mach_send(xpc_object_t, const char *);
Expand Down Expand Up @@ -55,22 +55,7 @@ static void handleClientMessageHook_common(uint64_t command, xpc_object_t dict)
/* While strictly speaking this should be loader's responsibility to create this folder,
this function must run as root, so it is done by the injector. */
create_user_data_folder(pid);

/* Before 10.12, the xpc dict also included a apptaskport key with task port send rights.
In 10.11 this could be considered a vulnerability - if you managed to receive this
xpc message, by setting up a fake launchservicesd daemon or managing to inject code to
the real one, you could completely bypass the need for task_for_pid and get a task port
for rootless/SIP-protected processes, something that wouldn't be possible under SIP
even as root. Not sure if this possible vulnerability is the reason Apple removed this
key, it could be just some code cleanup.
*/
mach_port_t task = xpc_dictionary_copy_mach_send(dict, "apptaskport");

if (!task) {
task_for_pid(mach_task_self(), pid, &task);
}
inject_to_task(task, "/Library/Apple/System/Library/Frameworks/mip/loader.dylib");
mach_port_destroy(mach_task_self(), task);
inject_to_pid(pid, "/Library/Apple/System/Library/Frameworks/mip/loader.dylib", false);
}
}

Expand Down
Binary file added MIP/local.injectd.plist
Binary file not shown.

0 comments on commit c72a415

Please sign in to comment.