iOSHook系统C函数(一):使用动态库

Cocoa Touch Framework

我们将使用 Cocoa Touch Framework来Hook iOS 中系统的C函数,首先我们先新创建一个工程HooKDemo,在AppDelegate.h文件中,在 application(_:didFinishLaunchingWithOptions:)方法后面,我们写下如下代码:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
   // Override point for customization after application launch.
   char *str = getenv("HOME");
   NSString *envStr = [[NSString alloc] initWithUTF8String:str];

   NSLog(@"HOME env: %@", envStr);

   return YES;
}

这段代码是用来获取系统的环境变量 HOME的值,我们运行工程,运行结果如下

2020-11-20 HooKDemo[76436:7339554] HOME env: /Users/ritamashin/Library/Developer/CoreSimulator/Devices/928FDC1A-5119-406D-872E-6B605E24ED40/data/Containers/Data/Application/0F44D2B0-C3F5-42A3-8661-96DEC5578ECD

该值是我们所运行的模拟器中的 HOME环境变量值。如果我们想HOOK getEnv函数,我们需要创建一个依赖于HooKDemo的Framework,来改变getEnv函数的实现

Xcode中,我们通过 File -> New -> Target,并且选择 Cocoa Touch Framework,选择 HookingC为项目名,语言选择Objective-C

frameWork.png

HookingC项目中,我们新建一个c语言文件,并命名为 getenvhook

hook c.png

我们在 getenvhook.c文件中,添加以下头文件

 #import 
 #import 
 #import 
 #import 
 #import 
  • dlfcn.h中,我们主要用了两个函数 dlopendlsym
  • assert.h中,我们主要来测试真实的 getEnv函数被真正加载。
  • stdio.h我们主要用里面的 printf函数。
  • string.h我们主要用里面的strcmp函数来比较两个C字符串。

我们在 getenvhook.c文件中,重写getenv方法

char * getenv(const char *name) {
    return  "YAY!";
}

然后我们运行该工程,我们可以看到如下输出

HOME env: YAY!

我们可以得知,已经成功替换了系统的 getenv方法,但这肯定不是我们期望的,如果我们在 getenv函数里面增加如下代码会发生什么呢?

char  * getenv(const char *name) {
  return getenv(name);
  return "YAY"!
}

这样的话,会造成递归循环调用,导致栈溢出,我们现在已经替换了系统的getenv函数,我们需要找到系统的getenv函数,然后在调用它,我们重新启动工程,然后使工程进入lldb模式,在镜像文件中查找getenv方法

(lldb) image lookup -s getenv
1 symbols match 'getenv' in /Users/ritamashin/Library/Developer/Xcode/DerivedData/HooKDemo-gosjkkcckyouwldjttqgdrtoaovl/Build/Products/Debug-iphonesimulator/HooKDemo.app/Frameworks/HOOKingC.framework/HOOKingC:
        Address: HOOKingC[0x0000000000000f60] (HOOKingC.__TEXT.__text + 0)
        Summary: HOOKingC`getenv at getenvhook.c:12
1 symbols match 'getenv' in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/lib/system/libsystem_c.dylib:
        Address: libsystem_c.dylib[0x000000000005a167] (libsystem_c.dylib.__TEXT.__text + 364823)
        Summary: libsystem_c.dylib`getenv
1 symbols match 'getenv' in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/PrivateFrameworks/AppleAccount.framework/AppleAccount:

我们一共找到3个getenv实现,一个在HOOKingC.framework中,一个在lib system_c.dylib中。它的全路径为 /usr/lib/system/libsystem_c.dylib

现在我们知道了这个函数是从哪里加载的了,下面我们将使用dlopen函数,去加载该函数。其方法签名如下

extern void * dlopen(const char * __path, int __mode); 
  • dlopen函数第一个参数为 一个全路径,第二个参数为一个整数来决定了 dlopen函数以怎样的模式去加载,如果加载成功,dlopen将会返回一个void *类型的数据,否则将会返回NULL

dlopen函数加载完之后,我们使用dlsym去得到 getenv函数的引用,dlsym函数的函数签名如下:

extern void * dlsym(void * __handle, const char * __symbol);
  • dlsym的第一个参数为 dlopen函数得到的方法引用,第二个参数为方法的名字。如果成功,将返回第二个参数 符号名内存地址。如果失败了,则返回NULL
    我们将我们自定义的getEnv函数,进行改进一下。
char * getenv(const char *name) {
    void *handle = dlopen("/usr/lib/system/libsystem_c.dylib", RTLD_NOW);
    assert(handle);

    void *real_getenv = dlsym(handle, "getenv");

    printf("Real getenv: %p\n Fake getenv: %p\n", real_getenv, getenv);

    return  "YAY!";
}
  • 1,我们使用 RTLD_NOW模式使用 dlopen函数的意思是:让该函数立即执行,不使用懒加载的方式。
  • 2,我们使用assert函数,确保 handle不为NULL
  • 3,我们使用 dlsym函数,来加载get_env的真实地址值。
    我们再次运行工程,会得到类似的以下输出:
Real getenv: 0x7fff5232b167
 Fake getenv: 0x107469de0
 HooKDemo[86264:7770900] HOME env: YAY!

现在我们已经知道了getenv的函数签名,接下来,我们将getenv函数进行如下调整:

char * getenv(const char *name) {
  static void *handle;      // 1 
  static char * (*real_getenv)(const char *); // 2
  
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{  // 3 
    handle = dlopen("/usr/lib/system/libsystem_c.dylib",
                    RTLD_NOW); 
    assert(handle);
    real_getenv = dlsym(handle, "getenv");
  });
  
  if (strcmp(name, "HOME") == 0) { // 4
    return "/WOOT";
  }
  
  return real_getenv(name); // 5
}.
  • 1,创建一个static变量 handle,因为是静态的,在函数执行完之后也不会被释放,在getenv函数之外,我们也可以获取该值。
  • 2,我们根据getenv函数的方法签名,定义了一个静态的real_getenv函数指针。我们将会把它赋值到真正的getenv方法实现中去。
  • 3,使用dispatch_once函数,保证只执行一次。
  • 4,使用 strcmp函数,判断当前变量是否为HOME,来更改其输出
  • 5, 如果不是HOME变量,则调用默认的getenv函数。

AppDelegate中,我们分别获取HOMEPATH环境变量

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.
    char *str = getenv("HOME");
    NSString *envStr = [[NSString alloc] initWithUTF8String:str];

    NSLog(@"HOME env: %@", envStr);
    char *pathchar = getenv("PATH");
    NSString *pathStr = [[NSString alloc] initWithUTF8String:pathchar];
    NSLog(@"HOME env: %@", pathStr);
    return YES;
}

其运行结果如下:

2020-11-21 14:38:03.325656+0800 HooKDemo[86696:7792671] HOME env: /WOOT
2020-11-21 14:38:03.325885+0800 HooKDemo[86696:7792671] HOME env: /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/bin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/bin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/sbin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/sbin:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Library/Developer/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/usr/local/bin

这样我们就成功的将HOME环境变量的值给替换掉,非HOME环境变量还是调用默认的getenv函数。

如果我们想要调用 UIKit框架中的 getenv函数,以我们现在的方式是不可以的,这是因为,此时UIKit已经加载到内存中,我们自定义的getenv函数不会得到调用。

为了能够调用UIKit中的getenv函数,我们需要掌握 符号表的重定向知识,然后去改变在__DATA.__la_symbol_ptr段中的getenv的地址。

备注:dlopen:打开动态库,dlsym:获得符号名。

总结

本篇文章,我们主要探讨了使用 iOS中的frameworkHOOK系统中的C函数,通过 dlopen函数得到系统中真实的函数地址,使用dlsym函数和方法签名对真实的地址进行符号绑定。从而达到调用默认函数的目的。

最后附上本文代码示例 HookDemo

你可能感兴趣的:(iOSHook系统C函数(一):使用动态库)