逆向系列0x01-在Swift中使用Social框架

这里假装iOS上的Social框架是个私有框架,不会使用它的任何头文件或module来帮助我们使用发现的API。我们将会使用dlopen动态加载Social框架,结合LLDB探索其中的API并加以利用。

加载并探索Social框架

在开始进攻Social之前,最好使用LLDB进行观察做些准备,来找些重要的方法和类。

打开Watermark项目,并run在模拟器上。可以看到这个项目暂时没有实现分享按钮的回调。

Watermak截图

暂停执行并使用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,选择NSObjectcategory,保存文件。
打开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把Socialframework加载进来。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

到现在你已经实现了NSObjectcategory,打开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,就会得到类似下面的错误:

TwitterPopup

恭喜你成功地完成了一次逆向!

后记

伟大的墙让我们免受Twitter等的伤害,何不这里尝试把serviceType换成新浪微博再试一下?

从上面的分析知道新浪微博的serviceType为com.apple.social.sinaweibo。ok简单改下重新构建运行,点击分享按钮,但是……crash了!

冷静地分析下crash信息:
fatal error: unexpectedly found nil while unwrapping an Optional value
原来是因为模拟器上的不支持新浪微博!

Settings

切换到真机上重新构建运行,点击分享按钮,Suuuuuuuuuuuuuuccess!

新浪微博
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容

  • 发现 关注 消息 iOS 第三方库、插件、知名博客总结 作者大灰狼的小绵羊哥哥关注 2017.06.26 09:4...
    肇东周阅读 11,617评论 4 59
  • 我陪了你这么久,两年,精神上我能给予你的,是不是比以前任何一个女人都多。 可是我要走了。
    Joann喵阅读 150评论 0 0
  • 1.手关节炎 四渎穴和三阳络穴,具体痛点。 2.腰痛艾灸:命门,肾俞,具体痛点 大椎穴、肩中俞、肩外俞、肩井,具体...
    欧阳雯秀阅读 505评论 0 2
  • 其实我是有过时间的 只不过都被我浪费了
    七晌阅读 183评论 0 0
  • 认知通常也意味着其他事情—有能力表达知道什么和我们如何得知它。但是事情总是并非如此,我们也许不能用文字表达...
    邓洁儿阅读 258评论 0 1