動態(tài)注入 dylib到 Mac 應用

最近看了很多書, 學到了不少新姿勢, 原本想寫出來和大家分享一下, 但是發(fā)現(xiàn)在簡書上都有類似的資料, 而且質量都還可以, 所以就只好藏拙了.

這次突然之間想到搞搞 Mac 應用, 是因為Mac 上某個下載應用讓我很煩惱, 明明網(wǎng)速很好就是因為不是會員把下載速度弄的特別慢, 加上看了糖炒小蝦的 tweakQQ, 所以想去逆向一下, 這篇文章只是講一下前期的準備工作, 也算是對糖炒小蝦文章中一些未涉及的點進行補充.

一. 事先準備:

  1. 一些 Objective-C 的 runtime 知識;
  2. 動態(tài)注入庫 yololib, git地址, 源代碼下載下來之后編譯之, 得到一個可執(zhí)行文件 yolokit(可以自己修改工程名換成別的名字)
  3. 一個 Mac demo 工程, 一個 dylib 工程

二. 大致需求
這邊會寫一個 Mac demo, 里面只有一個簡單的類叫 YoloTester, 它的 -description 方法返回的是@"YoloTest", 然后我在 AppDelegate 上面寫了 NSLog(@"hello, %@!", [YoloTester new]), 最終輸出的應該是@"hello, YoloTest".

我的目標是通過動態(tài)注入的方式, 讓最終輸出的 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 執(zhí)行文件到 demo 的 可執(zhí)行文件目錄下. 然后執(zhí)行 ./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!

這樣的輸出代表我們成功注入到了可執(zhí)行文件中, 然后我們執(zhí)行./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 類, 具體使用哪一個沒有被指定, 所以最終系統(tǒng)應該是按先后順序執(zhí)行了非注入的那一個, 導致輸出的還是 Hello, YoloTest!

按道理這里我們有2條路可以走, 第一條, 換一種方式注入, 第二條既然沒有指定, 我能不能想辦法指定它? 因為第二條我沒有找到太多的資料, 但是我認為也是可以走通的, 但是我比較擔心即使走通了, 會不會把這個類其它的方法全部都不加載進來了, 這樣就有點得不償失了, 所以為了穩(wěn)妥起見, 我們還是選擇既簡單有保險的路子.

  1. 寫 category:
    第一條路走不同之后, 自然就想到了用 runtime hook 方法的形式, 然后打算寫 category, 然后在+initialize 里面寫 exchangeMethod, 但是有個問題是, 在 dylib 里面這么寫, 編譯不給過, 認為你給了一個不存在的類寫 category, 即使你@class YoloTest也不會起作用, 繼續(xù)拋棄.

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

我們知道, 一般我們寫代碼最早大多數(shù)情況都是在 main 函數(shù)之后執(zhí)行, 但其實有很多比 main 函數(shù)還早執(zhí)行的, 例如類的+load(Apple 已經(jīng)不建議這么寫了, 用后面的+ initialize)和+initialize就要早于 main函數(shù). 但是上面我們已經(jīng)論證了 category 是不可行的, 所以這里再介紹一種更早于main 函數(shù)的入口----__attribute__((constructor)).

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

一個 C 函數(shù), 如果用__attribute__((constructor))修飾之后, 就會在imageLoad 時期就會被執(zhí)行到(切記不要濫用, 比較影響啟動性能), 這個符號會被寫在 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 函數(shù)my_description替換掉.
執(zhí)行./yololib YoloTest libDylib.dylib和'./YoloTest'后輸出:

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

證明我們搞定了這個簡單的小需求, 成功把代碼注入到了一個已經(jīng)編譯好的程序上了.

四. 更進一步
在某些情況下, 我們 hook 之后還想拿到原來的實現(xiàn), 這里有2種方法, 第一種是:
class_replaceMethod會返回一個 IMP, 我們都知道, IMP 可以直接強轉為一個函數(shù)指針, 所以我們可以這樣

IMP ret = class_replaceMethod(YoloTester, @selector(description), (IMP)my_description, "@:v");
NSString *(*func)() = (NSString *(*)())ret; // 如果想在 my_description函數(shù)中執(zhí)行, 可以賦值給 static 變量, 在 my_description 里面判斷執(zhí)行即可.

另一種方法是:
我們都知道 Objective-C 的方法最終都會調用 objc_msgSend, 然后第一個參數(shù)是發(fā)消息的對象, 第二個是 SEL, 后續(xù)的則是各個參數(shù), 所以我們可以先調用class_addMethodmethod_exchangeImplementations, 然后在 my_description中,是這樣的:

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

個人還是覺得第一種更簡單一些.

五. 后記
這里對 Mac 應用做了一個簡單的注入 dylib 介紹, 里面涉及到 runtime 的東西沒有深入展開闡述, 因為網(wǎng)上資源簡直不要太多. 后續(xù)我還會繼續(xù)深入了解一下里面的情況, 希望有一些高質量的產(chǎn)出可以和大家分享.

其實一開始想到逆向 Mac 應用, 我腦子里最先冒出來的是直接用 Hopper 改匯編代碼, 后面覺得太麻煩, 然后翻到了糖炒小蝦的文章覺得這是一個更加"人性化"的方法...不過最近比較迷匯編, 也看了不少書, 不知道有沒有同道中人可以一起學習進步的.

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

  • 轉至元數(shù)據(jù)結尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,030評論 0 9
  • 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的轉載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,872評論 33 466
  • 如何看待剩女: 一、提倡素質模式多樣化 給“剩女”找出路,必須變革擇偶模式,即改變傳統(tǒng)的“男高女低”單一模式為多元...
    孤獨的長跑者閱讀 965評論 0 0
  • 故事開始于瓜陰洲一個破敗的傅家園子里,這是個曾經(jīng)聲名顯赫的大家族,后來遷徙異地,只剩一個七十多歲的老園丁和傅家的一...
    Ltt2683閱讀 4,494評論 0 0
  • 樹活一張皮,人活一張臉。有時間人簡單點,或許更快樂,走入贏家發(fā)現(xiàn)無論說話做事都是有意義和價值,走著走著,如何在平凡...
    好彩妹閱讀 381評論 0 0

友情鏈接更多精彩內(nèi)容