动态注入 dylib到 Mac 应用

最近看了很多书, 学到了不少新姿势, 原本想写出来和大家分享一下, 但是发现在简书上都有类似的资料, 而且质量都还可以, 所以就只好藏拙了.

这次突然之间想到搞搞 Mac 应用, 是因为Mac 上某个下载应用让我很烦恼, 明明网速很好就是因为不是会员把下载速度弄的特别慢, 加上看了糖炒小虾的 tweakQQ, 所以想去逆向一下, 这篇文章只是讲一下前期的准备工作, 也算是对糖炒小虾文章中一些未涉及的点进行补充.

一. 事先准备:

  1. 一些 Objective-C 的 runtime 知识;
  2. 动态注入库 yololib, git地址, 源代码下载下来之后编译之, 得到一个可执行文件 yolokit(可以自己修改工程名换成别的名字)
  3. 一个 Mac demo 工程, 一个 dylib 工程

二. 大致需求
这边会写一个 Mac demo, 里面只有一个简单的类叫 YoloTester, 它的 -description 方法返回的是@"YoloTest", 然后我在 AppDelegate 上面写了 NSLog(@"hello, %@!", [YoloTester new]), 最终输出的应该是@"hello, YoloTest".

我的目标是通过动态注入的方式, 让最终输出的 log 变成@"hello, cracker".

三. 注入尝试
1). 直接重写类YoloTester来覆盖:
1.新建一个 dylib 工程:

dylib_create_1.png

  1. 重写类:
@interface YoloTester : NSObject

@end

@implementation YoloTester

- (NSString *)description
{
    return @"Cracker";
}

@end

为了方便操作, 我们copy 这个 dylib 和 yololib 执行文件到 demo 的 可执行文件目录下. 然后执行 ./yololib YoloTest libDylib.dylib, 然后就会看到输入日志:

2017-03-13 21:40:58.936 yololib[2164:81591] dylib path @executable_path/libDylib.dylib
2017-03-13 21:40:58.937 yololib[2164:81591] dylib path @executable_path/libDylib.dylib
Reading binary: YoloTest

2017-03-13 21:40:58.937 yololib[2164:81591] Thin 64bit binary!
2017-03-13 21:40:58.937 yololib[2164:81591] dylib size wow 56
2017-03-13 21:40:58.937 yololib[2164:81591] mach.ncmds 20
2017-03-13 21:40:58.937 yololib[2164:81591] mach.ncmds 21
2017-03-13 21:40:58.937 yololib[2164:81591] Patching mach_header..
2017-03-13 21:40:58.937 yololib[2164:81591] Attaching dylib..

2017-03-13 21:40:58.937 yololib[2164:81591] size 55
2017-03-13 21:40:58.937 yololib[2164:81591] complete!

这样的输出代表我们成功注入到了可执行文件中, 然后我们执行./YoloTest来验证一下是否真正替换了我们的方法, 结果输出下面的日志:

objc[2221]: Class YoloTester is implemented in both /Users/ryan/Library/Developer/Xcode/DerivedData/YoloTest-fiuymriohppdctfwvyeqikbxfzgo/Build/Products/Debug/./libDylib.dylib (0x101291120) and /Users/ryan/Library/Developer/Xcode/DerivedData/YoloTest-fiuymriohppdctfwvyeqikbxfzgo/Build/Products/Debug/./YoloTest (0x10128c178). One of the two will be used. Which one is undefined.
2017-03-13 21:41:45.883 YoloTest[2221:84530] Hello, YoloTest!

也就是说, 我们注入之后, 得到了2个 YoloTester 类, 具体使用哪一个没有被指定, 所以最终系统应该是按先后顺序执行了非注入的那一个, 导致输出的还是 Hello, YoloTest!

按道理这里我们有2条路可以走, 第一条, 换一种方式注入, 第二条既然没有指定, 我能不能想办法指定它? 因为第二条我没有找到太多的资料, 但是我认为也是可以走通的, 但是我比较担心即使走通了, 会不会把这个类其它的方法全部都不加载进来了, 这样就有点得不偿失了, 所以为了稳妥起见, 我们还是选择既简单有保险的路子.

  1. 写 category:
    第一条路走不同之后, 自然就想到了用 runtime hook 方法的形式, 然后打算写 category, 然后在+initialize 里面写 exchangeMethod, 但是有个问题是, 在 dylib 里面这么写, 编译不给过, 认为你给了一个不存在的类写 category, 即使你@class YoloTest也不会起作用, 继续抛弃.

  2. 新增入口:
    依然是打runtime的主意, 问题是, 在哪加?

我们知道, 一般我们写代码最早大多数情况都是在 main 函数之后执行, 但其实有很多比 main 函数还早执行的, 例如类的+load(Apple 已经不建议这么写了, 用后面的+ initialize)和+initialize就要早于 main函数. 但是上面我们已经论证了 category 是不可行的, 所以这里再介绍一种更早于main 函数的入口----__attribute__((constructor)).

__attribute__((constructor)) void myentry(){
    // do something
}

一个 C 函数, 如果用__attribute__((constructor))修饰之后, 就会在imageLoad 时期就会被执行到(切记不要滥用, 比较影响启动性能), 这个符号会被写在 Mach-O 的 DATA 中生成一条 mod_init_func记录, 如:

mod_init.png

所以我们就在这里加上我们的代码试试看:

NSString * my_description()
{
    return @"Cracker";
}

__attribute__((constructor)) void myentry(){
    Class YoloTester = NSClassFromString(@"YoloTester");
    
    class_replaceMethod(YoloTester, @selector(description), (IMP)my_description, "@:v");
}

上面的代码主要意思是, 把 YoloTesterdescription方法用 C 函数my_description替换掉.
执行./yololib YoloTest libDylib.dylib和'./YoloTest'后输出:

2017-03-13 22:04:54.239 YoloTest[3661:165402] Hello, Cracker!

证明我们搞定了这个简单的小需求, 成功把代码注入到了一个已经编译好的程序上了.

四. 更进一步
在某些情况下, 我们 hook 之后还想拿到原来的实现, 这里有2种方法, 第一种是:
class_replaceMethod会返回一个 IMP, 我们都知道, IMP 可以直接强转为一个函数指针, 所以我们可以这样

IMP ret = class_replaceMethod(YoloTester, @selector(description), (IMP)my_description, "@:v");
NSString *(*func)() = (NSString *(*)())ret; // 如果想在 my_description函数中执行, 可以赋值给 static 变量, 在 my_description 里面判断执行即可.

另一种方法是:
我们都知道 Objective-C 的方法最终都会调用 objc_msgSend, 然后第一个参数是发消息的对象, 第二个是 SEL, 后续的则是各个参数, 所以我们可以先调用class_addMethodmethod_exchangeImplementations, 然后在 my_description中,是这样的:

NSString * my_description(id self, SEL sel)
{
// 不需要返回值用[self performSelector:], 需要返回值用 NSInvocation
}

个人还是觉得第一种更简单一些.

五. 后记
这里对 Mac 应用做了一个简单的注入 dylib 介绍, 里面涉及到 runtime 的东西没有深入展开阐述, 因为网上资源简直不要太多. 后续我还会继续深入了解一下里面的情况, 希望有一些高质量的产出可以和大家分享.

其实一开始想到逆向 Mac 应用, 我脑子里最先冒出来的是直接用 Hopper 改汇编代码, 后面觉得太麻烦, 然后翻到了糖炒小虾的文章觉得这是一个更加"人性化"的方法...不过最近比较迷汇编, 也看了不少书, 不知道有没有同道中人可以一起学习进步的.

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

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的转载 这篇文章完全是基于南峰子老师博客的...
    西木阅读 30,540评论 33 466
  • 如何看待剩女: 一、提倡素质模式多样化 给“剩女”找出路,必须变革择偶模式,即改变传统的“男高女低”单一模式为多元...
    孤独的长跑者阅读 852评论 0 0
  • 故事开始于瓜阴洲一个破败的傅家园子里,这是个曾经声名显赫的大家族,后来迁徙异地,只剩一个七十多岁的老园丁和傅家的一...
    Ltt2683阅读 4,025评论 0 0
  • 樹活一張皮,人活一張臉。有時間人簡單點,或許更快樂,走入贏家發現無論說話做事都是有意義和價值,走著走著,如何在平凡...
    好彩妹阅读 335评论 0 0