2020.1.14更新無需越獄安裝
此文為逆向微信二進位文件,實現朋友圈小影片轉發的教程,從最開始的彙編程式碼入手到最後重簽名安裝等操作,手把手教你玩轉微信!學會之後再去逆向微信其他功能易如反掌。
本篇文章由於篇幅太長分成了兩篇,上篇講解的是逆向工作,也就是怎麼找到相關的函數和方法實現,下篇講解的是怎麼在非越獄機重簽名安裝和越獄機tweak安裝的詳細過程。
正文的第二部分也提供了微信自動搶紅包、修改微信步數的程式碼,這些都可以照葫蘆畫瓢按照本文的套路一步步逆向找到,這裡就不再贅述。
在實踐之前,需要準備好一部越獄的手機,然後將下文列出的所有工具安裝好。 IDA跟Reveal都是破解版,IDA的正版要2000多刀,對於這麼牛逼的逆向工具確實物有所值,不過不是專門研究逆向的公司也沒必要用正版的,下個Windows的破解版就好,Mac上暫時沒找到。 Mac上可以用hopper取代IDA,也是一款很屌的逆向工具。廢話不多說,正式開始吧!
注意:本文逆向的微信的二進位檔案為6.3.28版本,如果是不同的微信版本,二進位檔案中的基底位址也不相同
逆向環境為MacOS + iPhone5S 9.1越獄機<br>先用dumpdecrypted給微信砸殼(不會的請我寫的看這篇教程),獲得一個WeChat.decrypted文件,先把這個文件扔到IDA中分析(60MB左右的二進位文件,IDA差不多40分鐘才能分析完),用class-dump導出所有頭文件
LeonLei-MBP:~ gaoshilei$ class-dump -S -s -H /Users/gaoshilei/Desktop/reverse/binary_for_class-dump/WeChat.decrypted -o /Users/gaoshilei/Desktop/reverse/binary_for_class-dump/class-Header/WeChat
我滴個親娘!一共有8000個頭文件,微信果然工程量浩大!穩定一下情緒,理一理思路繼續搞。要取得小視頻的下載鏈接,找到播放視頻的View,順藤摸瓜就能找到小視頻的URL。用Reveal查看小影片的播放視窗可以看出來WCContentItemViewTemplateNewSigh這個物件是小影片的播放窗口,它的subView有WCSightView,SightView、SightPlayerView,這幾個類別就是我們的切入點。 儲存影片到favorite的時候是長按影片彈出選項的,那麼在WCContentItemViewTemplateNewSight這個類別裡面可能有手勢相關的方法,去剛才匯出的頭檔中找線索。
- (void)onLongTouch;
- (void)onLongPressedWCSight:(id)arg1;
- (void)onLongPressedWCSightFullScreenWindow:(id)arg1;
這幾個方法跟長按手勢相關,再去IDA找到這些函數,逐一查看。 onLongPressedWCSight和onLongPressedWCSightFullScreenWindow都比較簡單,onLongTouch比較長,而且發現了內部調用了方法Favorites_Add,因為長按視頻的時候出來一個選項就是Favorites,並且我看到這個函數調用
ADRP X8, #selRef_sightVideoPath@PAGE
LDR X1, [X8,#selRef_sightVideoPath@PAGEOFF]
這裡拿到了小影片的地址,可以推測這個函數跟收藏有關,下面打斷點測試。
(lldb) im li -o -f
[ 0] 0x000000000003c000 /var/mobile/Containers/Bundle/Application/2F1D52EC-C57E-4F95-B715-EF04351232E8/WeChat.app/WeChat(0x000000010003c000)
可以看到WeChat的ASLR為0x3c000,在IDA查找到這三個函數的基底位址,分別下斷點
(lldb) br s -a 0x1020D3A10+0x3c000
Breakpoint 1: where = WeChat`___lldb_unnamed_symbol110094$$WeChat + 28, address = 0x000000010210fa10
(lldb) br s -a 0x1020D3370+0x3c000
Breakpoint 2: where = WeChat`___lldb_unnamed_symbol110091$$WeChat + 8, address = 0x000000010210f370
(lldb) br s -a 0x1020D33E4+0x3c000
Breakpoint 3: where = WeChat`___lldb_unnamed_symbol110092$$WeChat + 12, address = 0x000000010210f3e4
回到微信裡面長按小視頻,看斷點觸發狀況
Process 3721 stopped
* thread #1: tid = 0x658fc, 0x000000010210f370 WeChat`___lldb_unnamed_symbol110091$$WeChat + 8, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
frame #0: 0x000000010210f370 WeChat`___lldb_unnamed_symbol110091$$WeChat + 8
WeChat`___lldb_unnamed_symbol110091$$WeChat:
-> 0x10210f370 <+8>: add x29, sp, #16 ; =16
0x10210f374 <+12>: mov x19, x0
0x10210f378 <+16>: adrp x8, 4968
0x10210f37c <+20>: ldr x0, [x8, #744]
(lldb) c
Process 3721 resuming
Process 3721 stopped
* thread #1: tid = 0x658fc, 0x000000010210fa10 WeChat`___lldb_unnamed_symbol110094$$WeChat + 28, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
frame #0: 0x000000010210fa10 WeChat`___lldb_unnamed_symbol110094$$WeChat + 28
WeChat`___lldb_unnamed_symbol110094$$WeChat:
-> 0x10210fa10 <+28>: add x29, sp, #96 ; =96
0x10210fa14 <+32>: sub sp, sp, #96 ; =96
0x10210fa18 <+36>: mov x19, x0
0x10210fa1c <+40>: adrp x8, 4863
……
發現斷點2先觸發,接著觸發斷點1,後面斷點2和1又各觸發了1次,斷點3一直很安靜。可以排除onLongPressedWCSightFullScreenWindow與收藏小影片的連結。小影片的蹤影就要在剩下的兩個方法中尋找了。透過V找到C,順藤摸瓜找到M屢試不爽!用cycript注入WeChat,拿到播放小影片的view所在的Controller。
cy# [#0x138c18030 nextResponder]
#"<WCTimeLineCellView: 0x138c34620; frame = (0 0; 319 249); tag = 1048577; layer = <CALayer: 0x138362ba0>>"
cy# [#0x138c34620 nextResponder]
#"<UITableViewCellContentView: 0x138223c70; frame = (0 0; 320 256); gestureRecognizers = <NSArray: 0x1384ec480>; layer = <CALayer: 0x138081dc0>>"
cy# [#0x138223c70 nextResponder]
#"<MMTableViewCell: 0x138c9f930; baseClass = UITableViewCell; frame = (0 307; 320 256); autoresize = W; layer = <CALayer: 0x1382dcd10>>"
cy# [#0x138c9f930 nextResponder]
#"<UITableViewWrapperView: 0x137b57800; frame = (0 0; 320 504); gestureRecognizers = <NSArray: 0x1383db660>; layer = <CALayer: 0x138af20c0>; contentOffset: {0, 0}; contentSize: {320, 504}>"
cy# [#0x137b57800 nextResponder]
#"<MMTableView: 0x137b8ae00; baseClass = UITableView; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x138adb590>; layer = <CALayer: 0x138956890>; contentOffset: {0, 99.5}; contentSize: {320, 3193}>"
cy# [#0x137b8ae00 nextResponder]
#"<UIView: 0x138ade5c0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x138ac9990>>"
cy# [#0x138ade5c0 nextResponder]
#"<WCTimeLineViewController: 0x1379eb000>"
透過響應者鏈條找到WCContentItemViewTemplateNewSight所屬的Controller為WCTimeLineViewController。在這個類別的頭檔中並沒有發現有價值的線索,不過我們注意到小影片所在的view是屬於MMTableVIewCell的(見上圖Reveal分析圖),這是每一個iOS最熟悉的TableView,cell的數據是透過UITableViewDataSource的代理方法- tableView:cellForRowAtIndexPath:
賦值的,透過這個方法一定能知道M的影子。在IDA中找到[WCTimeLineViewController tableView:cellForRowAtIndexPath:]
,定位到基底位址0x10128B6B0位置:
__text:000000010128B6B0 ADRP X8, #selRef_genNormalCell_indexPath_@PAGE
這裡的函式是WCTimeLineViewController中產生cell的方法,除了這個方法在這個類別中還有另外三個產生cell的方法:
- (void)genABTestTipCell:(id)arg1 indexPath:(id)arg2;
- (void)genRedHeartCell:(id)arg1 indexPath:(id)arg2;
- (void)genUploadFailCell:(id)arg1 indexPath:(id)arg2;
透過字面意思可以猜測normal這個應該是生成小視訊cell的方法。繼續在IDA中尋找線索
__text:0000000101287CC8 ADRP X8, #selRef_getTimelineDataItemOfIndex_@PAGE
在genNormalCell:IndexPath:
方法中發現上面這個方法,可以大膽猜想這個方法是獲取TimeLine(朋友圈)數據的方法,那小視頻的數據肯定也是透過這個方法獲取的,並且IDA可以看到這個方法中調用一個叫做selRef_getTimelineDataItemOfIndex_
的方法,取得DataItem看起來像cell的資料來源啊!接下來用LLDB下斷點驗證猜想。 透過IDA可以找到這個方法對應的基底位址為:0x101287CE4,先列印正在運行WeChat的ASLR偏移
LeonLei-MBP:~ gaoshilei$ lldb
(lldb) process connect connect://localhost:1234
(lldb) im li -o -f
[0] 0x0000000000050000 /var/mobile/Containers/Bundle/Application/2DCE8F30-9B6B-4652-901C-37EB1FF2A40D/WeChat.app/WeChat(0x0000000100050000)
所以我們下斷點的位置是0x50000+0x101287CE4
(lldb) br s -a 0x50000+0x101287CE4
Breakpoint 1: where = WeChat`___lldb_unnamed_symbol63721$$WeChat + 252, address = 0x00000001012d7ce4
列印x0的值
(lldb) po $x0
Class name: WCDataItem, addr: 0x15f5f03b0
tid: 12393001887435993280
username: wxid_z8twcz4o18fg12
createtime: 1477360950
commentUsers: (
)
contentObj: <WCContentItem: 0x15f57d000>
得到一個WCDataItem的對象,這裡x0的值就是selRef_getTimelineDataItemOfIndex_
執行完的回傳值,然後把x0的值改掉
(lldb) register write $x0 0
(lldb) c
此時會發現我們要刷新的那條小影片內容全部為空
到這裡已經找到了小影片的來源資料取得方法,問題是我們要怎麼拿到這個WCDataItem呢?繼續看IDA分析函數的呼叫狀況:
WCTimeLineViewController - (void)genNormalCell:(id) indexPath:(id)
__text:0000000101287BCC STP X28, X27, [SP,#var_60]!
__text:0000000101287BD0 STP X26, X25, [SP,#0x60+var_50]
__text:0000000101287BD4 STP X24, X23, [SP,#0x60+var_40]
__text:0000000101287BD8 STP X22, X21, [SP,#0x60+var_30]
__text:0000000101287BDC STP X20, X19, [SP,#0x60+var_20]
__text:0000000101287BE0 STP X29, X30, [SP,#0x60+var_10]
__text:0000000101287BE4 ADD X29, SP, #0x60+var_10
__text:0000000101287BE8 SUB SP, SP, #0x80
__text:0000000101287BEC MOV X19, X3
__text:0000000101287BF0 MOV X22, X0
__text:0000000101287BF4 MOV W25, #0x100000
__text:0000000101287BF8 MOVK W25, #1
__text:0000000101287BFC MOV X0, X2
__text:0000000101287C00 BL _objc_retain
__text:0000000101287C04 MOV X28, X0
__text:0000000101287C08 MOV X0, X19
__text:0000000101287C0C BL _objc_retain
__text:0000000101287C10 MOV X20, X0
__text:0000000101287C14 STR X20, [SP,#0xE0+var_98]
__text:0000000101287C18 ADRP X8, #selRef_row@PAGE
__text:0000000101287C1C LDR X1, [X8,#selRef_row@PAGEOFF]
__text:0000000101287C20 BL _objc_msgSend
__text:0000000101287C24 MOV X26, X0
__text:0000000101287C28 ADRP X8, #selRef_section@PAGE
__text:0000000101287C2C LDR X19, [X8,#selRef_section@PAGEOFF]
__text:0000000101287C30 MOV X0, X20
__text:0000000101287C34 MOV X1, X19
__text:0000000101287C38 BL _objc_msgSend
__text:0000000101287C3C STR X0, [SP,#0xE0+var_A8]
__text:0000000101287C40 MOV X0, X20
__text:0000000101287C44 MOV X1, X19
__text:0000000101287C48 BL _objc_msgSend
__text:0000000101287C4C MOV X2, X0
__text:0000000101287C50 ADRP X8, #selRef_calcDataItemIndex_@PAGE
__text:0000000101287C54 LDR X1, [X8,#selRef_calcDataItemIndex_@PAGEOFF]
__text:0000000101287C58 MOV X0, X22
__text:0000000101287C5C BL _objc_msgSend
__text:0000000101287C60 MOV X21, X0
__text:0000000101287C64 STR X21, [SP,#0xE0+var_C0]
__text:0000000101287C68 ADRP X8, #classRef_MMServiceCenter@PAGE
__text:0000000101287C6C LDR X0, [X8,#classRef_MMServiceCenter@PAGEOFF]
__text:0000000101287C70 ADRP X8, #selRef_defaultCenter@PAGE
__text:0000000101287C74 LDR X1, [X8,#selRef_defaultCenter@PAGEOFF]
__text:0000000101287C78 STR X1, [SP,#0xE0+var_B8]
__text:0000000101287C7C BL _objc_msgSend
__text:0000000101287C80 MOV X29, X29
__text:0000000101287C84 BL _objc_retainAutoreleasedReturnValue
__text:0000000101287C88 MOV X19, X0
__text:0000000101287C8C ADRP X8, #classRef_WCFacade@PAGE
__text:0000000101287C90 LDR X0, [X8,#classRef_WCFacade@PAGEOFF]
__text:0000000101287C94 ADRP X8, #selRef_class@PAGE
__text:0000000101287C98 LDR X1, [X8,#selRef_class@PAGEOFF]
__text:0000000101287C9C STR X1, [SP,#0xE0+var_B0]
__text:0000000101287CA0 BL _objc_msgSend
__text:0000000101287CA4 MOV X2, X0
__text:0000000101287CA8 ADRP X8, #selRef_getService_@PAGE
__text:0000000101287CAC LDR X1, [X8,#selRef_getService_@PAGEOFF]
__text:0000000101287CB0 STR X1, [SP,#0xE0+var_A0]
__text:0000000101287CB4 MOV X0, X19
__text:0000000101287CB8 BL _objc_msgSend
__text:0000000101287CBC MOV X29, X29
__text:0000000101287CC0 BL _objc_retainAutoreleasedReturnValue
__text:0000000101287CC4 MOV X20, X0
__text:0000000101287CC8 ADRP X8, #selRef_getTimelineDataItemOfIndex_@PAGE
__text:0000000101287CCC LDR X1, [X8,#selRef_getTimelineDataItemOfIndex_@PAGEOFF]
__text:0000000101287CD0 STR X1, [SP,#0xE0+var_C8]
__text:0000000101287CD4 MOV X2, X21
__text:0000000101287CD8 BL _objc_msgSend
__text:0000000101287CDC MOV X29, X29
__text:0000000101287CE0 BL _objc_retainAutoreleasedReturnValue
__text:0000000101287CE4 MOV X21, X0
__text:0000000101287CE8 MOV X0, X20
......
selRef_getTimelineDataItemOfIndex_
傳入的參數是x2,可以看到傳值給x2的x21是函數selRef_calcDataItemIndex_
的回傳值,是一個unsigned long資料型別。繼續分析, selRef_getTimelineDataItemOfIndex_
函數的呼叫者是上一個步驟selRef_getService_
的回傳值,經過斷點分析發現是一個WCFacade
物件。整理一下selRef_getTimelineDataItemOfIndex_
的呼叫:
呼叫者是selRef_getService_
的回傳值;參數是selRef_calcDataItemIndex_
的回傳值<br>下面把目光轉向那兩個函數,用相同的原理分析它們各自怎麼實現調用
selRef_getService_
:MMServiceCenter
對象,往上找x19是在0x101287C88這個位置賦值的,結果很清晰x19是[MMServiceCenter defaultCenter]
的返回值。[WCFacade class]
的回傳值。selRef_calcDataItemIndex_
:WCTimeLineViewController
。selRef_section
函數的回傳值x0賦值的,在0x101287C30位置可以發現selRef_section
函數的呼叫者是x20賦值的,如下圖所示,最終找到selRef_section函數的呼叫者是x20賦值的,如下圖所示,最終找到的selRef_section
者是x3WCTimeLineViewController - (void)genNormalCell:(id) indexPath:(id)
的第二個參數indexPath,,所以selRef_calcDataItemIndex_
的參數是[IndexPath section]
。getTimelineDataItemOfIndex:
的呼叫者可以透過 [[MMServiceCenter defaultCenter] getService:[WCFacade class]]
來獲得,它的參數可以透過下面的函數取得
[WCTimeLineViewController calcDataItemIndex:[indexPath section]]
總感覺還少點什麼? indexPath我們還沒拿到!下一步就是拿到indexPath,這個就比較簡單了,因為我們位於[WCContentItemViewTemplateNewSight onLongTouch]
中,所以可以透過[self nextResponder]
依序拿到MMTableViewCell、MMTableView和WCTimeLineViewController,再透過[MMTableView indexPathForCell:MMTableViewCell]
拿到indexMM
做完這些,已經拿到WCDataItem對象,接下來的重點要放在WCDataItem上,最後要取得我們想要的小影片。到這個類別的頭檔中找線索,因為影片是下載完成後才能播放的,所以這裡應該拿到了影片的路徑,所以要注意url和path相關的屬性或方法,然後找到下面這幾個嫌疑對象
@property(retain, nonatomic) NSString *sourceUrl2;
@property(retain, nonatomic) NSString *sourceUrl;
- (id)descriptionForKeyPaths;
- (id)keyPaths;
回到LLDB中,用斷點列印這些值,看看有什麼。
(lldb) po [$x0 keyPaths]
<__NSArrayI 0x15f74e9d0>(
tid,
username,
createtime,
commentUsers,
contentObj
)
(lldb) po [$x0 descriptionForKeyPaths]
Class name: WCDataItem, addr: 0x15f5f03b0
tid: 12393001887435993280
username: wxid_z8twcz4o18fg12
createtime: 1477360950
commentUsers: (
)
contentObj: <WCContentItem: 0x15f57d000>
(lldb) po [$x0 sourceUrl]
nil
(lldb) po [$x0 sourceUrl2]
nil
沒有什麼有價值的線索,但注意到WCDataItem裡面有一個WCContentItem,看來只能從這兒入手了,去看一下頭檔吧!
@property(retain, nonatomic) NSString *linkUrl;
@property(retain, nonatomic) NSString *linkUrl2;
@property(retain, nonatomic) NSMutableArray *mediaList;
在LLDB列印出來
(lldb) po [[$x0 valueForKey:@"contentObj"] linkUrl]
https://support.weixin.qq.com/cgi-bin/mmsupport-bin/readtemplate?t=page/common_page__upgrade&v=1
(lldb) po [[$x0 valueForKey:@"contentObj"] linkUrl2]
nil
(lldb) po [[$x0 valueForKey:@"contentObj"] mediaList]
<__NSArrayM 0x15f985e10>(
<WCMediaItem: 0x15dfebdf0>
)
mediaList陣列裡面有一個WCMediaItem對象,Media一般用來表示視訊和音頻,大膽猜測就是它了!趕緊找到頭檔搜尋一遍。
@property(retain, nonatomic) WCUrl *dataUrl;
- (id)pathForData;
- (id)pathForSightData;
- (id)pathForTempAttachVideoData;
- (id)videoStreamForData;
上面這些屬性和方法中pathForSightData
是最有可能拿到小視訊路徑的,繼續驗證
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] dataUrl]
type[1], url[http://vweixinf.tc.qq.com/102/20202/snsvideodownload?filekey=30270201010420301e020166040253480410d14adcddf086f4e131d11a5b1cca1bdf0203039fa00400&bizid=1023&hy=SH&fileparam=302c0201010425302302040fde55e20204580ebd3602024eea02031e8d7d02030f42400204d970370a0201000400], enckey[0], encIdx[-1], token[]
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] pathForData]
/var/mobile/Containers/Data/Application/7C3A6322-1F57-49A0-ACDE-6EF0ED74D137/Library/WechatPrivate/6f696a1b596ce2499419d844f90418aa/wc/media/5/53/8fb0cdd77208de5b56169fb3458b45
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] pathForSightData]
/var/mobile/Containers/Data/Application/7C3A6322-1F57-49A0-ACDE-6EF0ED74D137/Library/WechatPrivate/6f696a1b596ce2499419d844f90418aa/wc/media/5/53/8fb0cdd77208de5b56169fb3458b45.mp4
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] pathForAttachVideoData]
nil
(lldb) po [[[[$x0 valueForKey:@"contentObj"] mediaList] lastObject] videoStreamForData]
nil
拿到小影片的網路url和本地路徑了!這裡可以用iFunBox或是scp從沙盒拷貝這個檔案看看是不是這個cell應該播放的小影片。
LeonLei-MBP:~ gaoshilei$ scp [email protected]:/var/mobile/Containers/Data/Application/7C3A6322-1F57-49A0-ACDE-6EF0ED74D137/Library/WechatPrivate/6f696a1b596ce2499419d844f90418aa/wc/media/5/53/8fb0cdd77208de5b56169fb3458b45.mp4 Desktop/
8fb0cdd77208de5b56169fb3458b45.mp4 100% 232KB 231.9KB/s 00:00
用QuickTime開啟發現果然是我們要找的小影片。再驗證一下url是否正確,把上面印好的dataUrl在瀏覽器中打開,發現也是這個小影片。分析這個類別可以得到下面的結論:
至此小影片的路徑和取得方式分析已經完成,要實現轉發還要繼續分析微信的朋友圈發布。
這節是我在找小視訊轉送功能時走的彎路,扒到最後並沒有找到實現方法,不過也提供了一些逆向中常用的思路和方法,不想看的可以跳到第二節。
打開小影片的拍攝介面,用cycript注入,我們要找到發布小影片的方法是哪一個,然後看看目前的視窗有哪些window(因為小影片的拍攝並不是在UIApplication的keyWindow中進行的)
cy# [UIApp windows].toString()
(
"<iConsoleWindow: 0x125f75e20; baseClass = UIWindow; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x125f77b70>; layer = <UIWindowLayer: 0x125df4810>>",
"<SvrErrorTipWindow: 0x127414d40; baseClass = UIWindow; frame = (0 64; 320 45); hidden = YES; gestureRecognizers = <NSArray: 0x12740d930>; layer = <UIWindowLayer: 0x1274030b0>>",
"<MMUIWindow: 0x127796440; baseClass = UIWindow; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x1278083c0>; layer = <UIWindowLayer: 0x127796750>>",
"<UITextEffectsWindow: 0x1270e0d40; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x1270b4ba0>>",
"<NewYearActionSheet: 0x127797e10; baseClass = UIWindow; frame = (0 0; 320 568); hidden = YES; userInteractionEnabled = NO; layer = <UIWindowLayer: 0x1277d5490>>"
)
發現目前頁面總共有5個window,其中MMUIWindow是小影片拍攝所在的window,列印它的UI樹狀結構
cy# [#0x127796440 recursiveDescription]
列印結果比較長,不貼了。找到這個按鈕是拍攝小影片的按鈕
| | | | | | <UIButton: 0x1277a9d70; frame = (89.5 368.827; 141 141); opaque = NO; gestureRecognizers = <NSArray: 0x1277aaeb0>; layer = <CALayer: 0x1277a9600>>
| | | | | | | <UIView: 0x1277aa0a0; frame = (0 0; 141 141); userInteractionEnabled = NO; tag = 252707333; layer = <CALayer: 0x1277aa210>>
| | | | | | | | <UIImageView: 0x1277aa2e0; frame = (0 0; 141 141); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x1277aa490>>
然後執行
cy# [#0x1277a9d70 setHidden:YES]
發現拍攝的按鈕消失了,驗證了我的猜想。尋找按鈕的響應事件,可以透過target來尋找
cy# [#0x1277a9d70 allTargets]
[NSSet setWithArray:@[#"<MainFrameSightViewController: 0x1269a4600>"]]]
cy# [#0x1277a9d70 allControlEvents]
193
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:193]
null
發現按鈕沒有對應的action,就很奇怪了! UIButton必須要有target和action,不然這個Button不能回應事件。我們試試其他的ControlEvent
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchDown]
@["btnPress"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpOutside]
@["btnRelease"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpInside]
@["btnRelease"]
結果發現這三個ContrlEvent有對應的action,我們再來看看這三個列舉的值
typedef enum UIControlEvents : NSUInteger {
UIControlEventTouchDown = 1 << 0,
UIControlEventTouchDownRepeat = 1 << 1,
UIControlEventTouchDragInside = 1 << 2,
UIControlEventTouchDragOutside = 1 << 3,
UIControlEventTouchDragEnter = 1 << 4,
UIControlEventTouchDragExit = 1 << 5,
UIControlEventTouchUpInside = 1 << 6,
UIControlEventTouchUpOutside = 1 << 7,
UIControlEventTouchCancel = 1 << 8,
......
} UIControlEvents;
可以看出來UIControlEventTouchDown對應1,UIControlEventTouchUpInside對應128,UIControlEventTouchUpOutside對應64,三人相加剛好193!原來呼叫[#0x1277a9d70 allControlEvents]
的時候回傳的應該是枚舉,有多個枚舉則把它們的值相加,是不是略坑?我也是這樣覺得的!剛才我們把三種ControlEvent對應的action都印出來了,繼續LLDB+IDA做動態分析。
因為要找到小影片發布的方法,所以對應的btnPress
函數我們並不關心,把重點放在btnRelease
上面,拍攝按鈕放開後就會呼叫的方法。在IDA中找到這個方法找到之後下個斷點
(lldb) br s -a 0xac000+0x10209369C
Breakpoint 4: where = WeChat`___lldb_unnamed_symbol108894$$WeChat + 32, address = 0x000000010213f69c
Process 3813 stopped
* thread #1: tid = 0xf1ef0, 0x000000010213f69c WeChat`___lldb_unnamed_symbol108894$$WeChat + 32, queue = 'com.apple.main-thread', stop reason = breakpoint 4.1
frame #0: 0x000000010213f69c WeChat`___lldb_unnamed_symbol108894$$WeChat + 32
WeChat`___lldb_unnamed_symbol108894$$WeChat:
-> 0x10213f69c <+32>: bl 0x1028d0b60 ; symbol stub for: objc_msgSend
0x10213f6a0 <+36>: cmp w0, #2 ; =2
0x10213f6a4 <+40>: b.ne 0x10213f6dc ; <+96>
0x10213f6a8 <+44>: adrp x8, 5489
用手機拍攝小影片然後鬆開,觸發了斷點,說明我們的猜想是正確的。繼續分析發現程式碼是從上圖的右邊走的,看了一下沒有什麼方法是跳到發布視頻的,不過仔細看一下有一個block,是系統的延時block,位置在0x102093760。然後我們跟著斷點進去,在0x1028255A0跳到x16所存的位址
(lldb) si
Process 3873 stopped
* thread #1: tid = 0xf62c4, 0x00000001028d9598 WeChat`dispatch_after, queue = 'com.apple.main-thread', stop reason = instruction step into
frame #0: 0x00000001028d9598 WeChat`dispatch_after
WeChat`dispatch_after:
-> 0x1028d9598 <+0>: adrp x16, 1655
0x1028d959c <+4>: ldr x16, [x16, #1056]
0x1028d95a0 <+8>: br x16
WeChat`dispatch_apply:
0x1028d95a4 <+0>: adrp x16, 1655
(lldb) po $x2
<__NSStackBlock__: 0x16fd49f88>
發現傳入的參數x2是一個block,我們再回顧dispatch_after函數
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
這個函數有三個參數,分別是dispatch_time_t、dispatch_queue_t、dispatch_block_t,那麼這裡打印的x2就是要傳入的block,所以我們猜測拍攝完小視頻會有一個延時,然後執行剛才傳入的block,所以x2中肯定有其他方法調用,下一步就是要知道這個block的位置。
(lldb) memory read --size 8 --format x 0x16fd49f88
0x16fd49f88: 0x000000019f8fd218 0x00000000c2000000
0x16fd49f98: 0x000000010214777c 0x0000000102fb0e60
0x16fd49fa8: 0x000000015da32600 0x000000015ea1a430
0x16fd49fb8: 0x000000015cf5fee0 0x000000016fd49ff0
0x000000010214777c就是block所在的位置,當然要減掉目前WeChat的ASLR偏移,最終在IDA中的位址為0x10209377C,突然發現這就是btnRelease
的子程式sub_10209377C。這個子程式非常簡單,只有一個方法selRef_logicCheckState_
有可能是我們的目標。先看看這個方法是誰呼叫的
(lldb) br s -a 0xb4000+0x1020937BC
......
Process 3873 stopped
* thread #1: tid = 0xf62c4, 0x00000001021477bc WeChat`___lldb_unnamed_symbol108895$$WeChat + 64, queue = 'com.apple.main-thread', stop reason = breakpoint 3.1
frame #0: 0x00000001021477bc WeChat`___lldb_unnamed_symbol108895$$WeChat + 64
WeChat`___lldb_unnamed_symbol108895$$WeChat:
-> 0x1021477bc <+64>: adrp x8, 5489
0x1021477c0 <+68>: ldr x1, [x8, #1552]
0x1021477c4 <+72>: orr w2, wzr, #0x1
0x1021477c8 <+76>: ldp x29, x30, [sp, #16]
(lldb) po $x0
<MainFrameSightViewController: 0x15d1f0c00>
發現還是MainFrameSightViewController這個物件呼叫的,那selRef_logicCheckState_
一定也在這個類別的頭檔中,尋找一下果然發現了
- (void)logicCheckState:(int)arg1;
在IDA左側視窗中尋找[MainFrameSightViewController logicCheckState:],發現這個方法超級複雜,邏輯太多了,不急著慢慢捋。 在0x102094D6C位置我們發現一個switch jump,思路就很清晰了,我們只要找到小視頻拍攝完成的這條線往下看就行了,LLDB來幫忙看看走的那條線。在0x102094D6C位置下個斷點,這個斷點在拍攝小視頻的時候會多次觸發,可以在拍攝之前把斷點dis掉,拍攝鬆手之前再啟用斷點,打印此時的x8值
(lldb) p/x $x8
(unsigned long) $38 = 0x0000000102174e10
x8是一個指針,它指向的位址是0x102174e10,用這個位址減去目前ASLR的偏移就可以找到在IDA中的基底位址,發現是0x102094E10,拍攝完成的邏輯處理這條線找到了,一直走到0x102094E24位置之後跳到0x1020951C4,這個分支的內容較少,裡面有三個函數
loc_1020951C4
ADRP X8, #selRef_hideTips@PAGE
LDR X1, [X8,#selRef_hideTips@PAGEOFF]
MOV X0, X19
BL _objc_msgSend
ADRP X8, #selRef_finishWriter@PAGE
LDR X1, [X8,#selRef_finishWriter@PAGEOFF]
MOV X0, X19
BL _objc_msgSend
ADRP X8, #selRef_turnCancelBtnForFinishRecording@PAGE
LDR X1, [X8,#selRef_turnCancelBtnForFinishRecording@PAGEOFF]
MOV X0, X19
BL _objc_msgSend
B loc_102095288
其中selRef_finishWriter
和selRef_turnCancelBtnForFinishRecording
需要重點關注,這兩個方法看起來都是小錄影結束的意思,線索極有可能就在這兩個函數中。透過查看呼叫者發現這兩個方法都屬於MainFrameSightViewController,繼續在IDA中查看這兩個方法。在selRef_finishWriter
中靠近末尾0x102094248的位置發現一個方法名叫做f_switchToSendingPanel
,下個斷點,然後拍攝視頻,發現這個方法並沒有被觸發。應該不是透過這個方法呼叫發佈介面的,繼續回到selRef_finishWriter
方法;在0x1020941DC的位置呼叫方法selRef_stopRecording
,列印它的呼叫者發現這個方法屬於SightFacade
,繼續在IDA中尋找這個方法的實作。在這個方法的0x101F9BED4位置又調用了selRef_stopRecord
,同樣打印調用者發現這個方法屬於SightCaptureLogicF4,有點像剝洋蔥,繼續在尋找這個方法的實現。在這個方法內部0x101A98778位置又呼叫了selRef_finishWriting
,同樣的原理找到這個方法是屬於SightMovieWriter。已經剝了3層了,繼續往下:
在SightMovieWriter - (void)finishWriting
中的0x10261D004位置分了兩條線,這個位置下個斷點,然後拍攝完小視頻觸發斷點,打印x19的值
(lldb) po $x19
<OS_dispatch_queue: CAPTURE.CALLBACK[0x13610bcd0] = { xrefcnt = 0x4, refcnt = 0x4, suspend_cnt = 0x0, locked = 1, target = com.apple.root.default-qos.overcommit[0x1a0aa3700], width = 0x0, running = 0x0, barrier = 1 }>
所以程式碼不會跳到loc_10261D054而是走的左側,在左側的程式碼中發現啟用了一個block,這個block是子程式sub_10261D0AC,位址為0x10261D0AC,找到這個位址,結構如下圖所示:
可以看出來主要分兩條線,我們在第一個方框的末尾也就是0x10261D108位置下個斷點,等拍攝完畢觸發斷點之後打印x0的值為1,這裡的彙編代碼為
__text:000000010261D104 CMP X0, #2
__text:000000010261D108 B.EQ loc_10261D234
B.EQ是在上一步的結果為0才會跳到loc_10261D234,但是這裡的結果是不為0的,將x0的值改為2讓上一步的結果為0
(lldb) po $x0
1
(lldb) register write $x0 2
(lldb) po $x0
2
此時放開斷點,等待跳到小視訊發佈介面,結果是一直卡在這個介面沒有任何反應,所以猜測實現跳躍的邏輯應該在右邊的那條線,繼續順著右邊的線尋找:在右側0x10261D3AC位置發現呼叫了下面的這個方法
- (void)finishWritingWithCompletionHandler:(void (^)(void))handler;
這個方法是系統提供的AVAssetWriter裡面的方法,在視訊寫入完成之後要做的操作,這個裡面是要傳入一個block的,因為只有一個參數所以對應的變數是x2,列印x2的值
(lldb) po $x2
<__NSStackBlock__: 0x16e086c78>
(lldb) memory read --size 8 --format x 0x16e086c78
0x16e086c78: 0x00000001a0aa5218 0x00000000c2000000
0x16e086c88: 0x00000001026d94b0 0x0000000102fc98c0
0x16e086c98: 0x0000000136229fd0 0x000000016e086d00
0x16e086ca8: 0x00000001997f5318 0xfffffffec9e882ff
並且透過堆疊記憶體找到block位置為0x10261D4B0(需要減去ASLR的偏移)
sub_10261D4B0
var_20= -0x20
var_10= -0x10
STP X20, X19, [SP,#var_20]!
STP X29, X30, [SP,#0x20+var_10]
ADD X29, SP, #0x20+var_10
MOV X19, X0
LDR X0, [X19,#0x20]
ADRP X8, #selRef_stopAmr@PAGE
LDR X1, [X8,#selRef_stopAmr@PAGEOFF]
BL _objc_msgSend
LDR X0, [X19,#0x20]
ADRP X8, #selRef_compressAudio@PAGE
LDR X1, [X8,#selRef_compressAudio@PAGEOFF]
LDP X29, X30, [SP,#0x20+var_10]
LDP X20, X19, [SP+0x20+var_20],#0x20
B _objc_msgSend
; End of function sub_10261D4B0
只呼叫了兩個方法,一個是selRef_stopAmr
停止amr(一種音頻格式),另一個是selRef_compressAudio
壓縮音頻,拍攝完成的下一步操作應該不會放在這兩個方法裡面,找了這麼久也沒有頭緒,這個路看來走不通了,不要鑽牛角尖,策略性撤退尋找其他入口。
逆向的樂趣就是一直尋找真相的路上,能體會到成功的樂趣,也有可能方向錯了離真相反而越來越遠,不要氣餒調整方向繼續前進!
(由於微信在後台偷偷升級了,以下的內容都是微信6.3.30版本的ASLR,上面的分析基於6.3.28版本)
注意到在點擊朋友圈右上角的相機按鈕底部會彈出一個Sheet,第一個就是Sight小視頻,從這裡入手,用cycript查看Sight按鈕對應的事件是哪個
iPhone-5S:~ root# cycript -p "WeChat"
cy# [UIApp windows].toString()
`(
"<iConsoleWindow: 0x14d6ccc00; baseClass = UIWindow; frame = (0 0; 320 568); autoresize = W+H; gestureRecognizers = <NSArray: 0x14d7df110>; layer = <UIWindowLayer: 0x14d7d6f60>>",
"<SvrErrorTipWindow: 0x14eaa5800; baseClass = UIWindow; frame = (0 0; 320 45); hidden = YES; gestureRecognizers = <NSArray: 0x14e9e8950>; layer = <UIWindowLayer: 0x14e9e6510>>",
"<UITextEffectsWindow: 0x14ec38ba0; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x14ec39360>>",
"<UITextEffectsWindow: 0x14e9c67a0; frame = (0 0; 320 568); layer = <UIWindowLayer: 0x14d683ff0>>",
"<UIRemoteKeyboardWindow: 0x14f226e40; frame = (0 0; 320 568); opaque = NO; autoresize = W+H; layer = <UIWindowLayer: 0x14d6f4de0>>",
"<NewYearActionSheet: 0x14f1704a0; baseClass = UIWindow; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x14ef9bf90>; layer = <UIWindowLayer: 0x14ef61a20>>"
)`
cy# [#0x14f1704a0 recursiveDescription].toString()
底部的Sheet是NewYearActionSheet,然後列印NewYearActionSheet的UI樹狀結構圖(比較長不貼了)。然後找到Sight對應的UIButton是0x14f36d470
cy# [#0x14f36d470 allTargets]
[NSSet setWithArray:@[#"<NewYearActionSheet: 0x14f1704a0; baseClass = UIWindow; frame = (0 0; 320 568); gestureRecognizers = <NSArray: 0x14ef9bf90>; layer = <UIWindowLayer: 0x14ef61a20>>"]]]
cy# [#0x14f36d470 allControlEvents]
64
cy# [#0x14f36d470 actionsForTarget:#0x14f1704a0 forControlEvent:64]
@["OnDefaultButtonTapped:"]
透過UIControl的actionsForTarget:forControlEvent:
方法可以找到按鈕綁定的事件,Sight按鈕的觸發方法為OnDefaultButtonTapped:
,回到IDA中在NewYearActionSheet中找到這個方法們繼續往下分析只有這個方法selRef_dismissWithClickedButtonIndex_animated
,透過列印它的呼叫者發現還是NewYearActionSheet,繼續點進去找到newYearActionSheet_clickedButtonAtIndex
方法,看樣子不是NewYearActionSheet自己的,列印呼叫者x0發現它屬於類別WCTimeLineViewController。跟著斷點走下去在0x1012B78CC位置呼叫了方法#selRef_showSightWindowForMomentWithMask_byViewController_scene
透過觀察發現這個方法的呼叫者是0x1012B78AC這個位置的回傳值x0,這是一個類別SightFacade,猜測檔案這個方法果然在SightFacade發現這個方法
- (void)showSightWindowForMomentWithMask:(id)arg1 byViewController:(id)arg2 scene:(int)arg3;
這個方法應該就是跳到小視訊介面的方法了。下面分別列印它的參數
(lldb) po $x2
<UIImage: 0x14f046660>, {320, 568}
(lldb) po $x3
<WCTimeLineViewController: 0x14e214800>
(lldb) po $x4
2
(lldb) po $x0
<SightFacade: 0x14f124b40>
其中x2、x3、x4分別對應三個參數,x0是呼叫者,跳到這個方法內部查看怎麼實現的。發現在這個方法中進行了小影片拍攝介面的初始化工作,先初始化一個MainFrameSightViewController,再建立一個UINavigationController將MainFrameSightViewController放進去,接下來初始化一個MMWindowController調用
- (id)initWithViewController:(id)arg1 windowLevel:(int)arg2;
方法將UINavigationController放了進去,完成小影片拍攝介面的所有UI創建工作。 拍攝完成後進入發布介面,此時用cycript找到目前的Controller是SightMomentEditViewController,由此萌生一個想法,跳過前面的拍攝介面直接進入發布介面不就可以了嗎?我們自己建立一個SightMomentEditViewController然後放到UINavigationController裡面,然後再將這個導航控制器放到MMWindowController裡面。 **(這裡我已經寫好tweak進行了驗證,具體的tweak思路編寫在後文有)**結果是的確可以彈出發布的界面,但是導航欄的NavgationBar遮住了原來的,整個界面是透明的,很難看,而且發布完成之後無法銷毀整個MMWindowController,還是停留在發布介面。我們要的結果不是這個,不過確實有很大的收穫,最起碼可以直接呼叫發佈介面了,小影片也能正常轉發。我個人猜測,目前介面不能被銷毀的原因是因為MMWindowController新建了一個window,它跟TimeLine所在的keyWindow不是同一個,SightMomentEditViewController的按鈕觸發的方法是沒有辦法銷毀這個window的,所以有一個大膽的猜想,我直接在目前的WCTimeLineViewController上把SightMomentEditViewController展示出來不就可以了嗎?
[WCTimelineVC presentViewController:editSightVC animated:YES completion:^{
}];
像這樣展示豈不妙哉?不過透過觀察SightMomentEditViewController的頭文件,結合小視頻發佈時介面上的元素,推測創建這個控制器至少需要兩個屬性,一個是小視頻的路徑,另一個是小視頻的縮圖,將這兩個關鍵屬性給了SightMomentEditViewController那麼應該就可以正常展示了
SightMomentEditViewController *editSightVC = [[%c(SightMomentEditViewController) alloc] init];
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
UIImage *image = [[self valueForKey:@"_sightView"] getImage];
[editSightVC setRealMoviePath:localPath];
[editSightVC setMoviePath:localPath];
[editSightVC setRealThumbImage:image];
[editSightVC setThumbImage:image];
[WCTimelineVC presentViewController:editSightVC animated:YES completion:^{
}];
小影片的發布介面可以正常顯示且所有功能都可以正常使用,唯一的問題是返回按鈕沒有效果,並不能銷毀SightMomentEditViewController。用cycript查看左側按鈕的actionEvent找到它的回應函數是- (void)popSelf;
,點擊左側返回觸發的是pop方法,但是這個控制器並不在navgationController裡面,所以無效,我們要對這個方法進行重寫
- (void)popSelf
{
[self dismissViewControllerAnimated:YES completion:^{
}];
}
此時再點擊返回按鈕就可以正常退出了,此外,在WCContentItemViewTemplateNewSight中發現了一個方法叫做- (void)sendSightToFriend;
,可以直接將小視頻轉發給好友,至此小視頻轉發的功能已經找到了。
小影片的轉寄支援4個功能,轉寄至朋友圈、轉寄至好友、儲存到本地相簿、拷貝小影片連結到貼片。如果小影片沒有下載長按只會有小影片的url連結。
這裡要hook兩個類,分別是WCContentItemViewTemplateNewSight和SightMomentEditViewController,在WCContentItemViewTemplateNewSight中hook住onLongTouch方法然後添加menu彈出菜單,依次添加響應的方法,具體的代碼如下:
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
UISaveVideoAtPathToSavedPhotosAlbum(localPath, nil, nil, nil);
}
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
UISaveVideoAtPathToSavedPhotosAlbum(localPath, nil, nil, nil);
SightMomentEditViewController *editSightVC = [[%c(SightMomentEditViewController) alloc] init];
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
UIImage *image = [[self valueForKey:@"_sightView"] getImage];
[editSightVC setRealMoviePath:localPath];
[editSightVC setMoviePath:localPath];
[editSightVC setRealThumbImage:image];
[editSightVC setThumbImage:image];
[WCTimelineVC presentViewController:editSightVC animated:YES completion:^{
}];
[self sendSightToFriend];
UIMenuController *menuController = [UIMenuController sharedMenuController];
if (menuController.isMenuVisible) return;//防止出现menu闪屏的情况
[self becomeFirstResponder];
NSString *localPath = [[self iOSREMediaItemFromSight] pathForSightData];
BOOL isExist =[[NSFileManager defaultManager] fileExistsAtPath:localPath];
UIMenuItem *retweetMenuItem = [[UIMenuItem alloc] initWithTitle:@"朋友圈" action:@selector(SLRetweetSight)];
UIMenuItem *saveToDiskMenuItem = [[UIMenuItem alloc] initWithTitle:@"保存到相册" action:@selector(SLSightSaveToDisk)];
UIMenuItem *sendToFriendsMenuItem = [[UIMenuItem alloc] initWithTitle:@"好友" action:@selector(SLSightSendToFriends)];
UIMenuItem *copyURLMenuItem = [[UIMenuItem alloc] initWithTitle:@"复制链接" action:@selector(SLSightCopyUrl)];
if(isExist){
[menuController setMenuItems:@[retweetMenuItem,sendToFriendsMenuItem,saveToDiskMenuItem,copyURLMenuItem]];
}else{
[menuController setMenuItems:@[copyURLMenuItem]];
}
[menuController setTargetRect:CGRectZero inView:self];
[menuController setMenuVisible:YES animated:YES];
具體的tweak文件我放在了github上WCSightRetweet
@interface WCUrl : NSObject
@property(retain, nonatomic) NSString *url;
@end
@interface WCContentItem : NSObject
@property(retain, nonatomic) NSMutableArray *mediaList;
@end
@interface WCDataItem : NSObject
@property(retain, nonatomic) WCContentItem *contentObj;
@end
@interface WCMediaItem : NSObject
@property(retain, nonatomic) WCUrl *dataUrl;
- (id)pathForSightData;
@end
@interface MMServiceCenter : NSObject
+ (id)defaultCenter;
- (id)getService:(Class)arg1;
@end
@interface WCFacade : NSObject
- (id)getTimelineDataItemOfIndex:(long long)arg1;
@end
@interface WCSightView : UIView
- (id)getImage;
@end
@interface WCContentItemViewTemplateNewSight : UIView{
WCSightView *_sightView;
}
- (WCMediaItem *)iOSREMediaItemFromSight;
- (void)iOSREOnSaveToDisk;
- (void)iOSREOnCopyURL;
- (void)sendSightToFriend;
@end
@interface SightMomentEditViewController : UIViewController
@property(retain, nonatomic) NSString *moviePath;
@property(retain, nonatomic) NSString *realMoviePath;
@property(retain, nonatomic) UIImage *thumbImage;
@property(retain, nonatomic) UIImage *realThumbImage;
- (void)makeInputController;
@end
@interface MMWindowController : NSObject
- (id)initWithViewController:(id)arg1 windowLevel:(int)arg2;
- (void)showWindowAnimated:(_Bool)arg1;
@end
@interface WCTimeLineViewController : UIViewController
- (long long)calcDataItemIndex:(long long)arg1;
@end
@interface MMTableViewCell : UIView
@end
@interface MMTableView : UIView
- (id)indexPathForCell:(id)cell;
@end
THEOS_DEVICE_IP = 192.168.0.115//手机所在的IP
include $(THEOS)/makefiles/common.mk
ARCHS = arm64//支持的CPU架构
TWEAK_NAME = WCTimelineSightRetweet
WCTimelineSightRetweet_FILES = Tweak.xm
WCTimelineSightRetweet_FRAMEWORKS = UIKit CoreGraphics//导入系统的framework
include $(THEOS_MAKE_PATH)/tweak.mk
after-install::
install.exec "killall -9 WeChat"//安装完成杀掉的进程
control檔案不需要做修改,然後執行指令make package install
安裝到手機,微信會被殺掉,然後再次開啟微信轉發小影片的功能已經加上了。
安裝macports (安裝過程需要連接VPN,否則無法安裝成功)
安裝MacPorts後開啟終端,輸入sudo port -v selfupdate
更新MacPorts到最新版本,時間可能比較長。
更新完MacPorts後安裝DPKG文件,在終端機輸入sudo port -f install dpkg
下載安裝iOSOpendev 如果安裝失敗,可以透過Command + L
查看安裝中出現的問題。
PackageKit: Install Failed: Error Domain=PKInstallErrorDomain Code=112 "运行软件包“iOSOpenDev-1.6-2.pkg”的脚本时出错。" UserInfo={NSFilePath=./postinstall, NSURL=file://localhost/Users/ice/Downloads/iOSOpenDev-1.6-2.pkg#iodsetup.pkg, PKInstallPackageIdentifier=com.iosopendev.iosopendev162.iod-setup.pkg, NSLocalizedDescription=运行软件包“iOSOpenDev-1.6-2.pkg”的脚本时出错。} {
NSFilePath = "./postinstall";
NSLocalizedDescription = "U8fd0U884cU8f6fU4ef6U5305U201ciOSOpenDev-1.6-2.pkgU201dU7684U811aU672cU65f6U51faU9519U3002";
NSURL = "file://localhost/Users/ice/Downloads/iOSOpenDev-1.6-2.pkg#iodsetup.pkg";
PKInstallPackageIdentifier = "com.iosopendev.iosopendev162.iod-setup.pkg";
}
這裡有一個解決方案:下載iOSOpenDevInstallSolve中的Specifications資料夾
5. 修復安裝失敗問題打開步驟4下載的Specifications資料夾,裡面應該有8個檔案,如果你有安裝多個xcode注意放到對應的xcode裡面。
(1)iPhoneOS開頭的四個檔案放到/應用程式/Xcode/Content/Developer/Platforms/IphoneOS.platform/Developer/Library/Xcode/Specifications資料夾下(如果沒有,請自己建立一個Specifications資料夾)
(2)iPhone Simulator 開頭的另外四個檔案放入/應用程式/Xcode/Content/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications資料夾下(如果沒有,請同樣建立一個)
(3)在/應用程式/Xcode/Content/Developer/Platforms/iPhoneSimulator.platform/Developer/資料夾下建立usr資料夾,usr資料夾下再建立一個名為bin的資料夾。
注意:有時候會提示安裝失敗,開啟Xcode新建工程,如果在工程的選項選單中有iOSOpenDev就表示安裝成功了,不用管那個安裝提示。
安裝ipa包用的,也可以透過itool之類的工具,不過ideviceinstaller可以看到安裝過程的過程,方便我們找到出錯原因。
執行命令
brew install ideviceinstaller
如果提示brew指令找不到,那就是你的Mac還沒安裝Homebrew
常見的報錯訊息:
ERROR: Could not connect to lockdownd, error code -5
這時候只要重新安裝libimobiledevice就可以了(因為ideviceinstaller依賴很多其他外掛)
執行下面的命令:
$ brew uninstall libimobiledevice
$ brew install --HEAD libimobiledevice
下載iOS App Signer重簽名工具*(省去很多命令列操作,一鍵重簽名!)*
(3) 下載砸殼的微信應用
因為AppStore的包是被加密(有殼),無法進行重簽名,所以要用砸殼的,可以用dumpdecrypted自己砸殼,也可以直接利用PP助手或者itool助手下載越獄版已經砸過殼的微信應用。
(4) 安裝 yololib
yololib可以將dylib注入進WeChat二進位檔中,這樣才能是你的Hook有作用,下載之後編譯得到yololib
#####(1)產生靜態庫在上一步已經安裝好iOSOpendev,此時開啟Xcode新建項目,在選擇工程介面會出現iOSOpendev的工程,這裡我們要選擇CaptainHook Tweak專案新建好的工程只有一個.mm文件,我們只需要把所有hook方法寫在這個文件中即可。
因為非越獄機不能像越獄機一樣可以安裝tweak插件對原來的應用進行hook,CaptainHook使用的Runtime機制實現,利用巨集命令封裝類別定義、方法替換等功能,簡單介紹它的使用方法:
CHDeclareClass(WCContentItemViewTemplateNewSight);
CHDeclareClass(ClassName)
表示要hook哪個類,一般寫在這個類別運算的最前面。
CHDeclareMethod0(id, WCContentItemViewTemplateNewSight, SLSightDataItem){......}
CHDeclareMethod(count, return_type, class_type, name1, arg1, ....)
表示新建一個方法,count表示這個方法的參數個數,return_type表示回傳類型,class_type填入這個方法所在的類別名,name1表示方法名, arg1表示第一個參數,如果沒有參數則不填,以此類推。
CHOptimizedMethod0(self, void, WCContentItemViewTemplateNewSight, onLongTouch){
CHSuper(0, className, Method);//可选
......
}
CHOptimizedMethod(count, optimization, return_type, class_type, name1, type1, arg1)
表示hook原來的方法(如果不加CHSuper(0, className, Method)
表示複寫原來的方法,CHSuper表示在目前位置呼叫原來的方法實作),count表示hook的方法參數個數,optimization一般填self,return_type即方法傳回值類型,class_type填目前類別的類別名,name1是方法名,arg1是參數,如果沒有參數不同填入arg,以此類推。
CHConstructor
{
@autoreleasepool
{
CHLoadLateClass(WCContentItemViewTemplateNewSight);
CHHook(0, WCContentItemViewTemplateNewSight, onLongTouch);
}
}
這是CaptainHook的入口函數,所有被hook的類別必須在這裡聲明加載,類別裡面的方法要在這裡聲明hook。
然後就可以在類別和方法中寫程式碼了,程式碼太長不貼了,我放在了github上面MMPlugin
這個項目包含了小影片轉送、自動搶紅包、修改微信運動步數功能,自動搶紅包和修改微信運動步數功能可以手動關閉。
注意:如果用到了系統的類別記住要導入對應的類別庫(比方說UIKit)和頭檔否則編譯的時候會報錯。
編譯成功之後就可以在Products資料夾中找到編譯好的靜態函式庫了
在finder中找到它,拷貝出來待用。
進行到這裡目前應有的材料有:
從原來的微信app中找到WeChat二進位檔案拷貝出來待用,刪除weChat.app中的Watch資料夾、PlugIns資料夾中的WeChatShareExtensionNew.appex 。
執行下面的命令將MMPlugin.dylib注入到WeChat二進位檔案中,命令如下:
LeonLei-MBP:WeChat gaoshilei$ ./yololib WeChat MMPlugin.dylib
執行這個指令時要確保yololib、WeChat、WeChat.app處於同一目錄下。
完成後將MMPlugin.dylib和WeChat拷貝到原來的WeChat.app中,覆蓋掉原來的WeChat檔案。
開啟iOS App Signer依照下圖選擇好各項參數:
我這裡選的是企業級證書,個人開發者證書也是可以的,一定要選擇生產環境的,選好之後點擊start,稍等片刻一個經過重簽名的ipa包就生成了。
連接你的手機執行下面的命令查看ideviceinstaller是否連接上手機:
LeonLei-MBP:WeChat gaoshilei$ ideviceinfo
如果列印一大堆手機的訊息表示連線成功可以安裝ipa包,如果不成功請根據錯誤提示進行調整。執行下面的命令進行安裝:
LeonLei-MBP:WeChat gaoshilei$ ideviceinstaller -i WeChat.ipa
WARNING: could not locate iTunesMetadata.plist in archive !
WARNING: could not locate Payload/WeChat.app/SC_Info/WeChat.sinf in archive !
Copying ' WeChat.ipa ' to device... DONE.
Installing ' com.xxxxxxxxxxxx '
- CreatingStagingDirectory (5%)
- ExtractingPackage (15%)
- InspectingPackage (20%)
- TakingInstallLock (20%)
- PreflightingApplication (30%)
- InstallingEmbeddedProfile (30%)
- VerifyingApplication (40%)
- CreatingContainer (50%)
- InstallingApplication (60%)
- PostflightingApplication (70%)
- SandboxingApplication (80%)
- GeneratingApplicationMap (90%)
- Complete
安裝完成,在手機上開啟微信試試我們新增的功能吧!如果某個環節卡住會報錯,請依照報錯訊息進行修改。請看效果圖: