这里假装iOS上的
Social
框架是个私有框架,不会使用它的任何头文件或module来帮助我们使用发现的API。我们将会使用dlopen
动态加载Social
框架,结合LLDB探索其中的API并加以利用。
加载并探索Social框架
在开始进攻Social之前,最好使用LLDB进行观察做些准备,来找些重要的方法和类。
打开Watermark
项目,并run在模拟器上。可以看到这个项目暂时没有实现分享按钮的回调。
暂停执行并使用LLDB来动态加载
Social
框架:
(lldb) process load /System/Library/Frameworks/Social.framework/Social
Loading "/Systen/Library/Frameworks/Social.framework/Social"...ok
Image 0 loaded.
现在我们有了在我们的可执行文件中访问Social
框架任何代码的权限,到了探索这个框架到底提供了些啥的时候了。但从哪里开始呢?你可以用image lookup -rn
来导出所有这框架里的所有东西,但那样会得到太多内容了。幸运的是,我们有更优雅的方式来寻找比较重要的代码。
当你完全找不到一个框架的切入点时,最好的方式是搜索并尝试启动框架里的view controller:
(lldb) image lookup -rn 'ViewController\ init' Social
嗯,我们会发现Social
框架有以init开头的方法的vc还是太多了。该是试一下别的思路了,看能否找到比较特别的一个方法。
试下这个:
(lldb) image lookup -rn '\+\[.*ViewController\ [a-zA-Z]+' Social
9 matches found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/Social.framework/Social:
Address: Social[0x0000000000016d45] (Social.__TEXT.__text + 86945)
Summary: Social`+[SLComposeViewController extensionIdentifierForActivityType:] Address: Social[0x0000000000017705] (Social.__TEXT.__text + 89441)
Summary: Social`+[SLComposeViewController isAvailableForExtension:] Address: Social[0x00000000000178c0] (Social.__TEXT.__text + 89884)
Summary: Social`+[SLComposeViewController isAvailableForServiceType:] Address: Social[0x0000000000017c3d] (Social.__TEXT.__text + 90777)
Summary: Social`+[SLComposeViewController isAvailableForExtensionIdentifier:] Address: Social[0x00000000000187d6] (Social.__TEXT.__text + 93746)
Summary: Social`+[SLComposeViewController composeViewControllerForExtension:] Address: Social[0x000000000001888a] (Social.__TEXT.__text + 93926)
Summary: Social`+[SLComposeViewController composeViewControllerForServiceType:] Address: Social[0x00000000000188e7] (Social.__TEXT.__text + 94019)
Summary: Social`+[SLComposeViewController composeViewControllerForExtensionIdentifier:] Address: Social[0x000000000002de4c] (Social.__TEXT.__text + 181416)
Summary: Social`+[SLFacebookComposeViewController serviceBundle] Address: Social[0x0000000000044734] (Social.__TEXT.__text + 273808)
Summary: Social`+[SLMicroBlogComposeViewController serviceBundle]
这个稍微有点复杂。它匹配那些不是私有的类方法(即不以下划线开头的方法)。
观察输出,看看有哪些方法看起来比较像UIViewControllers
的初始化方法。
可以看到有3个方法比较吸引我们,这三个方法都以+[SLComposeViewController composeViewController
开头。
这有点尴尬。这三个哪个才是我们应该使用呢(公开)?记住,因为我们把它当成了一个『私有』框架,我们不可以去查看Social
框架的头文件说明。
我们可以尝试执行每一个方法并传nil为参数。我们会发现这3个有一个有点特殊,它会打印一些输出并返回nil而不是某实例:
+[SLComposeViewController composeViewControllerForServiceType:]
ok。我们就继续探索这个方法。
现在面临一个逆向时很普遍的问题:我们到底该传什么参数给这个方法???
我们先来创建一个断点,在它被deallocated的时候保留住它,以供我们慢慢玩耍和研究。
首先,我们需要判断VC是否存在重写过的dealloc
方法:
(lldb) image lookup -rn UIViewController.dealloc
1 match found in /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk//System/Library/Frameworks/UIKit.framework/UIKit:
Address: UIKit[0x00000000001c7594] (UIKit.__TEXT.__text + 1855012)
Summary: UIKit`-[UIViewController dealloc]
很好,只有一个匹配。接下来给它设置一个断点:
(lldb) rb UIViewController.dealloc
并再次尝试运行这个方法:(-i0
表示lldb中不忽略断点)
expression -i0 -O -lobjc -- [SLComposeViewController composeViewControllerForServiceType:nil]
上述命令执行后,app的执行就会停在-[UIViewController dealloc]
的实现上。这是查看调用栈就会发现SLComposeViewController
有一个重写的dealloc
方法。
没关系。你所需要的只是内存中的一个实例,而且这个类还没有将自己从内存中移除。如果你在不同的栈帧上,在Xcode中通过选择顶部的栈帧或通过LLDB命令frame select 0
确保处在栈顶上。
现在我们就可以获取到这个类的内存实例了:
(lldb) po $rdi
<SLComposeViewController: 0x7ffc834089c0>
查看这个实例的所有ivar
:
(lldb) po [0x7ffc834089c0 _ivarDescription]
<SLComposeViewController: 0x7ffc834089c0>:
in SLComposeViewController:
_extension (NSExtension*): nil
_initialText (NSString*): nil
_itemProviders (NSArray*): nil
_extensionItems (NSArray*): nil
_keyboardTopConstraint (NSLayoutConstraint*): nil
_keyboardTrackingView (UIView*): nil
_savedStatusBarStyle (long): 0
_wasPresented (BOOL): NO
(内容太多,此处有省略)
搜索serviceType
:
_serviceType (NSString*): nil
完美!这在提示我们应该传一个NSString
给这个方法:composeViewControllerForServiceType:
现在又面临另外一个逆向遇到的经典问题:这个参数会有哪些取值呢?
这个我们可以到Social
框架的DATA
段里面去探寻。
导出Social
框架的的符号表symbol table
:
(lldb) image dump symtab Social -s address
这个命令会按实现地址排序导出框架的符号表。
既然框架使用SL
作为类前缀并且你要查找一个包含有serviceType
信息的NSString
,很自然的我们要搜索SLServiceType
。于是有了下列惊喜:
[ 4297] 4297 X Data 0x000000000009fe20 0x000000011726ee20 0x0000000000000008 0x000f0000 SLServiceTypeTwitter
[ 4291] 4291 X Data 0x000000000009fe28 0x000000011726ee28 0x0000000000000008 0x000f0000 SLServiceTypeFacebook
[ 4294] 4294 X Data 0x000000000009fe30 0x000000011726ee30 0x0000000000000008 0x000f0000 SLServiceTypeSinaWeibo
[ 4295] 4295 X Data 0x000000000009fe38 0x000000011726ee38 0x0000000000000008 0x000f0000 SLServiceTypeTencentWeibo
[ 4296] 4296 X Data 0x000000000009fe40 0x000000011726ee40 0x0000000000000008 0x000f0000 SLServiceTypeTudou
[ 4299] 4299 X Data 0x000000000009fe48 0x000000011726ee48 0x0000000000000008 0x000f0000 SLServiceTypeYouku
[ 4298] 4298 X Data 0x000000000009fe50 0x000000011726ee50 0x0000000000000008 0x000f0000 SLServiceTypeVimeo
[ 4292] 4292 X Data 0x000000000009fe58 0x000000011726ee58 0x0000000000000008 0x000f0000 SLServiceTypeFlickr
[ 4293] 4293 X Data 0x000000000009fe60 0x000000011726ee60 0x0000000000000010 0x000f0000 SLServiceTypeLinkedIn
至此我们成功的找到了枚举值的值域!
接下来我们选择SLServiceTypeTwitter尝试一下:
(lldb) po SLServiceTypeTwitter
==> com.apple.social.twitter
很好。这个值看起来就是我们要寻找的。再确认下它是一个NSString:
(lldb) po [SLServiceTypeTwitter class]
==> __NSCFConstantString
完美!将这个值传入上述方法再次调用:
(lldb) po [SLComposeViewController composeViewControllerForServiceType:@"com.apple.social.twitter"]
<SLComposeViewController: 0x7ffc83511b90>
接下来就可以查看这个类有哪些方法了:
(lldb) po [0x7ffc83511b90 _shortMethodDescription]
<SLComposeViewController: 0x7ffc83511b90>:
in SLComposeViewController:
Class Methods:
+ (id) composeViewControllerForServiceType:(id)arg1; (0x1171e788a)
+ (id) extensionIdentifierForActivityType:(id)arg1; (0x1171e5d45)
+ (BOOL) isAvailableForExtension:(id)arg1; (0x1171e6705)
+ (id) composeViewControllerForExtension:(id)arg1; (0x1171e77d6)
+ (id) _serviceTypeToExtensionIdentifierMap; (0x1171e585c)
+ (BOOL) _isMultiUserDevice; (0x1171e64c3)
+ (id) _serviceTypeForExtensionIdentifier:(id)arg1; (0x1171e5a39)
+ (BOOL) _isAvailableForService:(id)arg1; (0x1171e63a9)
+ (BOOL) _isAvailableForMediaShareExtension:(id)arg1; (0x1171e64cb)
+ (BOOL) _isServiceType:(id)arg1; (0x1171e5c3d)
+ (id) _extensionIdentifierForServiceType:(id)arg1; (0x1171e59b0)
+ (id) _shareExtensionWithIdentifier:(id)arg1; (0x1171e5ee1)
+ (BOOL) isAvailableForServiceType:(id)arg1; (0x1171e68c0)
+ (BOOL) isAvailableForExtensionIdentifier:(id)arg1; (0x1171e6c3d)
+ (id) composeViewControllerForExtensionIdentifier:(id)arg1; (0x1171e78e7)
Properties:
@property (retain) UIViewController* remoteViewController; (@synthesize remoteViewController = _remoteViewController;)
@property (readonly, nonatomic) NSString* serviceType; (@synthesize serviceType = _serviceType;)
@property (copy, nonatomic) ^block completionHandler; (@synthesize completionHandler = _completionHandler;)
Instance Methods:
- (BOOL) setInitialText:(id)arg1; (0x1171e799c)
- (BOOL) addImage:(id)arg1; (0x1171e7fe2)
- (void) .cxx_destruct; (0x1171ea976)
- (void) dealloc; (0x1171e78f9)
- (^block) completionHandler; (0x1171ea92b)
- (BOOL) shouldAutorotateToInterfaceOrientation:(long)arg1; (0x1171ea837)
- (void) viewWillAppear:(BOOL)arg1; (0x1171ea274)
- (void) viewDidAppear:(BOOL)arg1; (0x1171ea77b)
- (void) viewWillDisappear:(BOOL)arg1; (0x1171ea743)
- (void) viewDidDisappear:(BOOL)arg1; (0x1171ea749)
- (id) remoteViewController; (0x1171ea94f)
- (void) viewDidUnload; (0x1171ea808)
- (BOOL) _useCustomDimmingView; (0x1171ea26c)
- (void) setRemoteViewController:(id)arg1; (0x1171ea965)
- (BOOL) addItemProvider:(id)arg1; (0x1171e9614)
- (BOOL) addExtensionItem:(id)arg1; (0x1171e96c1)
- (void) setCompletionHandler:(^block)arg1; (0x1171ea93e)
- (id) initWithServiceType:(id)arg1; (0x1171e761e)
- (void) completeWithResult:(long)arg1; (0x1171e9af3)
- (id) initWithExtension:(id)arg1 requestedServiceType:(id)arg2; (0x1171e6c4f)
- (BOOL) canAddContent; (0x1171e7988)
- (id) _urlForUntypedAsset:(id)arg1; (0x1171e7aef)
- (BOOL) _addImageAsset:(id)arg1 preview:(id)arg2; (0x1171e7c30)
- (BOOL) supportsImageAsset:(id)arg1; (0x1171e7a01)
- (BOOL) _addImageJPEGData:(id)arg1 preview:(id)arg2; (0x1171e809a)
- (BOOL) supportsVideoAsset:(id)arg1; (0x1171e7a78)
- (BOOL) addURL:(id)arg1 withPreviewImage:(id)arg2; (0x1171e8cf8)
- (BOOL) _addURL:(id)arg1 type:(long)arg2 preview:(id)arg3; (0x1171e8d8c)
- (BOOL) _addVideoData:(id)arg1 preview:(id)arg2; (0x1171e8998)
- (BOOL) _addVideoAsset:(id)arg1 preview:(id)arg2; (0x1171e85fb)
- (void) _handleRemoteViewFailure; (0x1171ea01a)
- (void) didLoadSheetViewController; (0x1171e9d2e)
- (void) remoteController:(id)arg1 didLoadWithError:(id)arg2; (0x1171ea084)
- (void) remoteViewController:(id)arg1 didTerminateWithError:(id)arg2; (0x1171ea1f5)
- (id) initWithExtensionIdentifier:(id)arg1; (0x1171e77c4)
- (BOOL) addImageAsset:(id)arg1; (0x1171e7bd1)
- (BOOL) removeAllImages; (0x1171e839b)
- (BOOL) addURL:(id)arg1; (0x1171e8c99)
- (BOOL) removeAllURLs; (0x1171e91a8)
- (BOOL) addAttachment:(id)arg1; (0x1171e97be)
- (^block) addDownSampledImageDataByProxyWithPreviewImage:(id)arg1; (0x1171e9a84)
- (void) setLongitude:(double)arg1 latitude:(double)arg2 name:(id)arg3; (0x1171e9aed)
- (void) userDidCancel; (0x1171e9d03)
- (void) userDidPost; (0x1171e9d17)
- (void) remoteViewControllerLoadDidTimeout; (0x1171ea008)
- (BOOL) canSendTweet; (0x1171ea8e5)
- (id) serviceType; (0x1171ea91a)
(UIViewController ...)
我们着重看这几个方法:
- (BOOL) setInitialText:(id)arg1; (0x11a80b7aa)
- (BOOL) addImage:(id)arg1; (0x11a80bdf4)
@property (copy, nonatomic) ^block completionHandler; (@synthesize
completionHandler = _completionHandler;)
小试牛刀
分析了辣么多,该是动手操作的时候了。Xcode的Project Navigator
中选择HookingC
目录,File\New\File\Objective-c File
,新建文件命名为P_SLComposeViewController
,选择NSObject
为category
,保存文件。
打开NSObject+P_SLComposeViewController.m
替换一下内容:
#import "NSObject+P_SLComposeViewController.h"
#import <dlfcn.h>
@implementation NSObject (P_SLComposeViewController)
+ (void)load {
dlopen("Social.framework/Social", RTLD_NOW);
}
@end
这里使用了OC的load
类方法(Swift没有这个)来说明,一旦这个类加载到runtime中,就用dlopen把Social
framework加载进来。RTLD_NOW
则代表着直到加载完毕程序才恢复执行。
接着打开NSObject+P_SLComposeViewController.h
并用一下内容替换:
#import <Foundation/Foundation.h>
@interface NSObject (P_SLComposeViewController)
+ (id)composeViewControllerForServiceType:(NSString *)serviceType;
- (BOOL)setInitialText:(id)text;
- (BOOL)addImage:(id)image;
@property (copy, nonatomic) id completionHandler;
@end
最后,在project navigator
中选择NSObject+P_SLComposeViewController.h
,然后在右侧的Target Membershi
的下面,保证HookingC
旁边的Public
是勾选的。
此时构建并启动app,会得到一些warning:在头部文件定义的方法未找到。
打开NSObject+P_SLComposeViewController.m
然后在import下面添加一些编译选项,在@implementation下面一行添加对completionHandler
的说明:
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wincomplete-implementation"
@implementation NSObject (P_SLComposeViewController)
@dynamic completionHandler;
...
@end
#pragma clang diagnostic pop
这样就禁用了关于实现缺失的告警,并告诉编译器completionHandler
实现在其他地方。
OC vc Swift
虽然Swift逐渐成为苹果开发的趋势,但它给私有代码的研究带来了不少麻烦。
回忆上节我们创建了一个头部文件,在其中声明了任何NSObject
都实现了上述方法。这看起来很不妥当,但我们得必须这样做,因为我们在跟Swift打交道。
如果我们的项目仅仅使用了OC,我们可以使用更简洁的方法:创建一个实现了这些方法的协议protocol
:
@protocol P_SLComposeViewControllerProtocol <NSObject>
+ (id)composeViewControllerForServiceType:(NSString *)serviceType;
- (BOOL)setInitialText:(id)text;
- (BOOL)addImage:(id)image;
@property (copy, nonatomic) id completionHandler;
@end
然后你可以这样使用这个协议:
id<P_SLComposeViewControllerProtocol> vc =
[(id<P_SLComposeViewControllerProtocol>)
NSClassFromString(@"SLComposeViewController")
composeViewControllerForServiceType:@"com.apple.social.twitter"];
[vc setInitialText:@"hello world"];
在OC中,我们可以创建并强制转换一个对象的类型,告诉编译器它实现了这个协议,因此有着对应的方法和属性。但在Swift中,存在着对协议实现的运行时检查,Swift运行时发现实际的SLComposeViewController
并没有实现这个协议,然后就会crash掉app。因此就有了上面这个看起来很不妥当的解决方法:让所有NSObject
都实现上述方法。
调用私有的UIViewController
到现在你已经实现了NSObject
category,打开ViewController.swift
并添加下面代码到sharingButtonTapped(_:)
中:
guard let vcClass =
NSClassFromString("SLComposeViewController") else { return }
let vc = vcClass.composeViewController(forServiceType:
"com.apple.social.twitter") as! UIViewController
vc.setInitialText("Yay! Doggie Love!")
if let originalImage = imageView.image {
vc.addImage(originalImage)
}
present(vc, animated: true)
接着打开HookingC.h
并文件末尾添加以下:#import "NSObject+P_SLComposeViewController.h"
。
现在再次构建并运行app。点击顶部右侧的分享按钮。然后观察下发生神马了。
如果你已经在模拟器中添加了一个Twitter账号,就会得到一个没有错误的Twitter分享小窗口;如果你尚未登录Twitter,就会得到类似下面的错误:
恭喜你成功地完成了一次逆向!
后记
伟大的墙让我们免受Twitter等的伤害,何不这里尝试把serviceType
换成新浪微博再试一下?
从上面的分析知道新浪微博的serviceType为com.apple.social.sinaweibo
。ok简单改下重新构建运行,点击分享按钮,但是……crash了!
冷静地分析下crash信息:
fatal error: unexpectedly found nil while unwrapping an Optional value
原来是因为模拟器上的不支持新浪微博!
切换到真机上重新构建运行,点击分享按钮,Suuuuuuuuuuuuuuccess!