2020.1.14 アップデートではジェイルブレイクのインストールは必要ありません
この記事は、WeChat バイナリ ファイルを反転して Moments での短いビデオの転送を実現するチュートリアルであり、最初のアセンブリ コードから最終的な再署名インストールやその他の操作まで、WeChat で遊ぶ方法を段階的に説明します。これを学べば、他の WeChat 機能をリバース エンジニアリングするのが簡単になります。
この記事は長いため 2 つの部分に分かれています。最初の部分では、逆の作業、つまり、関連する機能と実装方法を見つける方法について説明します。2番目の部分では、ジェイルブレイクされていないマシンでインストールを再署名する方法について説明します。ジェイルブレイクされたマシンでのインストールを調整する詳細なプロセス。
テキストの 2 番目の部分では、WeChat が自動的に赤い封筒を取得し、WeChat のステップ数を変更するためのコードも提供します。これらは、この記事の手順に従うことで段階的に確認できるため、ここでは繰り返しません。
練習する前に、脱獄した携帯電話を準備し、以下にリストされているすべてのツールをインストールする必要があります。 IDA と Reveal は両方ともクラック版です。IDA の正規版は 2,000 ドル以上します。しかし、リバース エンジニアリングを専門とする会社でなければ、その価値はありません。正規版を使用する必要があります。Windows の次のクラック版は問題ありませんが、Mac ではまだ見つかりません。 Hopper を使用して Mac 上の IDA を置き換えることができます。これは非常に強力なリバース エンジニアリング ツールでもあります。さっそく始めましょう!
注: この記事で反転した WeChat バイナリ ファイルはバージョン 6.3.28 です。WeChat バージョンが異なる場合、バイナリ ファイル内のベース アドレスも異なります。
MacOS + iPhone5S 9.1 ジェイルブレイク済みマシンの逆環境<br>まず、dumpdecrypted を使用して WeChat のシェルを破壊し (方法がわからない場合は、私が書いたこのチュートリアルを読んでください)、WeChat.decrypted ファイルを取得し、最初にこのファイルを分析のために IDA にスローします (約 60 MB のバイナリ ファイル) IDA による分析には約 40 分かかります。終了)、クラスダンプを使用してすべてのヘッダー ファイルをエクスポートします
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
義母がいるよ!ヘッダー ファイルは合計 8,000 個あり、WeChat には本当に膨大な量の作業が必要です。感情を落ち着かせ、考えをまとめて続行します。短いビデオのダウンロード リンクを取得するには、ビデオを再生するビューを見つけ、手がかりに従って短いビデオの URL を見つけます。 Reveal を使用してショート ビデオの再生ウィンドウを表示します。WCContentItemViewTemplateNewSigh オブジェクトが、WCSightView、SightView、および SightPlayerView のサブビューを含むことがわかります。 ビデオをお気に入りに保存するときに、ビデオを長押しするとオプションがポップアップするため、エクスポートされたばかりのヘッダー ファイルで手がかりを見つけるための WCContentItemViewTemplateNewSight クラスにジェスチャ関連のメソッドが存在する可能性があります。
- (void)onLongTouch;
- (void)onLongPressedWCSight:(id)arg1;
- (void)onLongPressedWCSightFullScreenWindow:(id)arg1;
これらのメソッドは、長押しジェスチャに関連しています。IDA でこれらの機能を見つけて、1 つずつ表示します。 onLongPressedWCSight と onLongPressedWCSightFullScreenWindow は比較的単純で、onLongTouch は比較的長く、ビデオを長押しすると出てくるオプションが [お気に入り] であるため、メソッド Favourites_Add が内部で呼び出されていることがわかりました。この関数呼び出しを確認しました。
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 でこれら 3 つの関数のベース アドレスを見つけて、それぞれブレークポイントを設定します。
(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
WeChat に戻り、短いビデオを長押しして、ブレークポイントがどのようにトリガーされるかを確認します。
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 と短いビデオのコレクションの間の接続を除外できます。残りの 2 つの方法では、短いビデオの痕跡を見つける必要があります。 C から V を見つけ、ヒントをたどって M を見つけてください。これは試行されています。 cycript を使用して WeChat を挿入し、短いビデオを再生するビューが配置されているコントローラーを取得します。
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 が属するコントローラーを見つけます。それは WCTimeLineViewController です。このクラスのヘッダー ファイルには貴重な手がかりは見つかりませんでしたが、短いビデオが配置されているビューが MMTableVIewCell (上記の Reveal 分析チャートを参照) に属していることに気付きました。これは、すべての iOS で最もよく知られている TableView およびセル データです。 UITableViewDataSource - tableView:cellForRowAtIndexPath:
このメソッドを通じて、M の影を確実に知ることができます。 IDA で[WCTimeLineViewController tableView:cellForRowAtIndexPath:]
を見つけ、ベース アドレス 0x10128B6B0 を見つけます。
__text:000000010128B6B0 ADRP X8, #selRef_genNormalCell_indexPath_@PAGE
ここでの関数は、WCTimeLineViewController でセルを生成するメソッドです。このメソッドに加えて、このクラスにはセルを生成するメソッドが 3 つあります。
- (void)genABTestTipCell:(id)arg1 indexPath:(id)arg2;
- (void)genRedHeartCell:(id)arg1 indexPath:(id)arg2;
- (void)genUploadFailCell:(id)arg1 indexPath:(id)arg2;
文字通りの意味から、Normal は小さなビデオ セルを生成する方法であると推測できます。 IDA で手がかりを探し続ける
__text:0000000101287CC8 ADRP X8, #selRef_getTimelineDataItemOfIndex_@PAGE
上記のメソッドはgenNormalCell:IndexPath:
メソッド内にあり、このメソッドは TimeLine (瞬間) データを取得するメソッドであると推測できます。また、IDA はこのメソッドを通じて取得する必要があります。このメソッド内でselRef_getTimelineDataItemOfIndex_
というメソッドを呼び出し、DataItem を取得するのがセルのデータソースのようです。次に、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>
ここでの 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_
の戻り値であり、符号なしの Long データ型であることがわかります。分析を続けると、 selRef_getTimelineDataItemOfIndex_
関数の呼び出し元は、前のステップのselRef_getService_
の戻り値であることがわかります。ブレークポイント分析の後、それがWCFacade
オブジェクトであることがわかります。 selRef_getTimelineDataItemOfIndex_
の呼び出しを整理してみましょう。
呼び出し元はselRef_getService_
の戻り値、パラメータはselRef_calcDataItemIndex_
の戻り値です。<br>これら 2 つの関数に注目し、同じ原理を使用して呼び出しをどのように実装するかを分析してみましょう。
selRef_getService_
を見てみましょう。MMServiceCenter
に割り当てられていることがわかります。 [MMServiceCenter defaultCenter]
の値が返されます。[WCFacade class]
の戻り値であることがわかります。selRef_calcDataItemIndex_
を見つけます。WCTimeLineViewController
。selRef_section
0x101287C4C では、受信selRef_section
がselRef_section
からのものであるか、または x3 であることがわかります。WCTimeLineViewController - (void)genNormalCell:(id) indexPath:(id)
あるため、 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]
。
これを実行した後、WCDataItem オブジェクトを取得しました。次に、WCDataItem に焦点を当て、最後に必要な短いビデオを取得します。このクラスのヘッダー ファイルで手がかりを探します。ビデオはダウンロード後にのみ再生できるため、ここでビデオのパスを取得する必要があります。そのため、URL とパスに関連する属性またはメソッドに注目して、以下を見つけます。容疑者。
@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 オブジェクトがあり、メディアは通常、ビデオとオーディオを表すために使用されます。ヘッダー ファイルをすばやく見つけて検索します。
@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 を使用してこのファイルをサンドボックスからコピーし、このセルで再生すべき短いビデオかどうかを確認できます。
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 を開くと、それがこの短いビデオであることがわかります。このクラスを分析すると、次の結論を導き出すことができます。
この時点で、ショートビデオの経路と取得方法の分析は完了しました。転送を実現するには、WeChat モーメントのリリースを引き続き分析する必要があります。
このセクションは、短いビデオの転送機能を探していたときに行った寄り道ですが、最終的にはそれを実装する方法が見つかりませんでした。ただし、リバース エンジニアリングでよく使用されるアイデアと方法もいくつか紹介します。読みたくない場合は、2 番目のセクションに進んでください。
ショート ビデオの撮影インターフェイスを開き、cycript を使用してショート ビデオを挿入します。ショート ビデオの公開にどのメソッドが使用されているかを確認し、現在のウィンドウにどのウィンドウがあるかを確認する必要があります (ショート ビデオの撮影は行われないため)。 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 つのウィンドウがあり、そのうち MMUIWindow はその 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
ボタンには対応するアクションがないことがわかりましたが、これは奇妙です。 UIButton にはターゲットとアクションが必要です。そうでない場合、ボタンはイベントに応答できません。他の ControlEvent も試してみましょう
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchDown]
@["btnPress"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpOutside]
@["btnRelease"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpInside]
@["btnRelease"]
これら 3 つの ControlEvent には対応するアクションがあることがわかります。これら 3 つの列挙体の値を見てみましょう。
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 に対応し、3 つの合計はちょうど 193 であることがわかります。 [#0x1277a9d70 allControlEvents]
を呼び出すとき、返される値は列挙型である必要があることがわかりました。複数の列挙型がある場合、これは少し混乱しませんか。私も同じように感じます!先ほど、3 つの ControlEvent に対応するアクションを出力し、動的分析のために 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
携帯電話で短いビデオを撮影して放すと、ブレークポイントがトリガーされ、推測が正しいことがわかります。分析を続けると、コードは上の図の右側にあることがわかりました。これを確認すると、ビデオ リリースにジャンプする方法がありません。システムの遅延ブロックで、0x102093760 にあります。次に、ブレークポイントに従い、x16 に格納されているアドレス 0x1028255A0 にジャンプします。
(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 がブロックであることがわかります。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 という 3 つのパラメータがあります。ここで出力される x2 は、渡されるブロックです。そのため、短いビデオの撮影後に遅延が発生すると推測し、渡されたばかりのブロックを実行します。他のメソッド呼び出しがある場合、次のステップはこのブロックの位置を知ることです。
(lldb) memory read --size 8 --format x 0x16fd49f88
0x16fd49f88: 0x000000019f8fd218 0x00000000c2000000
0x16fd49f98: 0x000000010214777c 0x0000000102fb0e60
0x16fd49fa8: 0x000000015da32600 0x000000015ea1a430
0x16fd49fb8: 0x000000015cf5fee0 0x000000016fd49ff0
0x000000010214777c はブロックの位置です。もちろん、WeChat の現在の ASLR オフセットを差し引く必要があります。IDA の最終アドレスはbtnRelease
であることがわかります。このサブルーチンは非常に単純で、ターゲットとなるメソッドselRef_logicCheckState_
が 1 つだけあります。まずこのメソッドを呼び出した人を見てみましょう
(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 の左側のウィンドウで [MainFrameSightViewControllerlogicCheckState:] を探すと、このメソッドは非常に複雑でロジックが多すぎることがわかります。そのため、心配せずにゆっくりと実行してください。 0x102094D6C でスイッチ ジャンプが見つかりました。そのアイデアは非常に明確でした。必要なのは、短いビデオが撮影されたラインを見つけて、そのラインを確認するのに役立ちます。 0x102094D6C にブレークポイントを設定します。このブレークポイントは、短いビデオを撮影するときに複数回トリガーされます。撮影前にブレークポイントを無効にし、放す前にブレークポイントを有効にして、この時点で x8 値を出力できます。
(lldb) p/x $x8
(unsigned long) $38 = 0x0000000102174e10
x8 はポインタであり、それが指すアドレスは 0x102174e10 です。このアドレスから現在の ASLR オフセットを引いた値が 0x102094E10 であることがわかり、撮影を完了するための論理処理ラインが見つかります。 0x102094E24 の位置の後は、0x1020951C4 にジャンプします。このブランチには内容が少なく、3 つの関数が含まれています。
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
に注目する必要があります。この 2 つのメソッドは、おそらくこの 2 つの関数に短いビデオ録画の終了を意味すると思われます。呼び出し元を確認すると、これら 2 つのメソッドが MainFrameSightViewController に属していることがわかりました。引き続き IDA でこれら 2 つのメソッドを確認します。 selRef_finishWriter
の終わり近く、0x102094248 でf_switchToSendingPanel
というメソッドを見つけ、ブレークポイントを設定してビデオを撮影したところ、このメソッドがトリガーされていないことがわかりました。パブリッシング インターフェイスは、このメソッドを通じて呼び出すべきではないため、引き続きselRef_finishWriter
メソッドに戻り、 0x1020941DC の場所でメソッドselRef_stopRecording
呼び出します。これを出力する呼び出し元は、このメソッドがSightFacade
に属していることを認識し、このメソッドを IDA に実装します。 selRef_stopRecord
は、このメソッドの 0x101F9BED4 の位置で再度呼び出されます。呼び出し側は、このメソッドが SightCaptureLogicF4 に属していることを確認し、このメソッドの実装を探し続けます。同じ原理を使用して、このメソッド内の 0x101A98778 の位置でselRef_finishWriting
が再度呼び出されます。このメソッドは SightMovieWriter に属していることがわかります。 3 つの層が剥がれました。続けてみましょう。
SightMovieWriter - (void)finishWriting
この位置にブレークポイントが設定され、ショートビデオの撮影後にブレークポイントがトリガーされ、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 にジャンプせず、左側にジャンプします。左側のコードでは、このブロックがサブルーチン sub_10261D0AC であり、そのアドレスが 0x10261D0AC であることがわかります。以下に示すように:
主に 2 行に分かれていることがわかります。最初のボックスの最後にブレークポイント (0x10261D108) を設定しています。撮影後にブレークポイントがトリガーされた後、x0 の値が 1 として出力されます。は
__text:000000010261D104 CMP X0, #2
__text:000000010261D108 B.EQ loc_10261D234
B.EQ は、前のステップの結果が 0 の場合にのみ loc_10261D234 にジャンプしますが、ここでの結果は 0 ではありません。前のステップの結果が 0 になるように、x0 の値を 2 に変更します。
(lldb) po $x0
1
(lldb) register write $x0 2
(lldb) po $x0
2
このとき、ブレークポイントを解除してショートビデオ公開インターフェイスにジャンプするのを待ちます。結果として、何も応答せずにこのインターフェイスでスタックしているため、ジャンプを実現するロジックは右側の行にあるはずです。右側の行に沿って検索を続けます。右側の 0x10261D3AC で次のメソッドが呼び出されていることがわかりました。
- (void)finishWritingWithCompletionHandler:(void (^)(void))handler;
このメソッドは、システムが提供する AVAssetWriter 内のメソッドで、ビデオの書き込みが完了した後に実行される操作です。ここでは、パラメーターが 1 つしかないため、対応する変数は 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
そして、スタック メモリからブロック位置 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
呼び出されるメソッドは 2 つだけです。1 つは amr (オーディオ形式) を停止するためのselRef_stopAmr
で、もう 1 つはオーディオを圧縮するためのselRef_compressAudio
です。私はこれを長い間探していました。この道はまだ行き止まりのようです。トラブルに巻き込まれないように、戦略的に撤退し、他の入り口を探してください。
逆行の楽しみは、真実を見つける途中で成功の喜びを体験できることです。また、間違った方向に進み、真実からどんどん遠ざかってしまうこともあります。落胆しないで、方向を調整してください。そして前進し続けてください!
(WeChat はバックグラウンドで密かにアップグレードされているため、以下の内容は WeChat バージョン 6.3.30 の ASLR であり、上記の分析はバージョン 6.3.28 に基づいています)
友達のサークルの右上隅にあるカメラ ボタンをクリックすると、下部にシートが表示されることに注意してください。最初のビデオは Sight です。ここから開始して、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がNew YearActionSheetで、New YearActionSheetの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 に戻って、このメソッドselRef_dismissWithClickedButtonIndex_animated
呼び出し元を出力すると、それが New YearActionSheet であることがわかります。さらにクリックしてnewYearActionSheet_clickedButtonAtIndex
メソッドを見つけます。呼び出し元 x0 を出力すると、それが WCTimeLineViewController クラスに属していることがわかります。ブレークポイントに従って、0x1012B78CC の位置にあるメソッド#selRef_showSightWindowForMomentWithMask_byViewController_scene
を呼び出します。観察により、このメソッドの呼び出し元は 0x1012B78AC のクラスであることがわかりました。ヘッダーファイルに移動してこのメソッドを見つけました。
- (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 はそれぞれ 3 つのパラメーターに対応します。x0 はこのメソッドの内部にジャンプして、その実装方法を確認します。このメソッドでは、まず MainFrameSightViewController が初期化され、次に MainFrameSightViewController を配置するための UINavigationController が作成され、次に MMWindowController が呼び出されるように初期化されていることがわかります。
- (id)initWithViewController:(id)arg1 windowLevel:(int)arg2;
このメソッドは UINavigationController を配置し、ショートビデオ撮影インターフェイスのすべての UI 作成作業を完了します。 撮影が完了したら、公開インターフェースに入ります。このとき、現在のコントローラーが SightMomentEditViewController であることを確認します。このことから、前の撮影インターフェースをスキップして、直接公開インターフェースに入るのは十分ではないでしょうか。 ? SightMomentEditViewController を自分で作成して UINavigationController に配置し、このナビゲーション コントローラーを MMWindowController に配置します。 **(検証のためにここに微調整を書きました。具体的な微調整のアイデアは後で書きます)** その結果、パブリッシュ インターフェイスは実際にポップアップ表示されますが、ナビゲーション バーの NavigationBar が元のインターフェイスと全体をカバーしてしまいます。インターフェイスは透過的で、醜く、公開が完了した後、MMWindowController 全体を破棄することはできず、公開インターフェイスに残ったままになります。これは私たちが望んでいた結果ではありませんが、少なくともパブリッシング インターフェイスを直接呼び出すことができ、小さなビデオを正常に転送できるようになりました。私の個人的な推測は、現在のインターフェイスを破棄できない理由は、MMWindowController が TimeLine が配置されている keyWindow と同じではない新しいウィンドウを作成したためであると考えています。大胆な推測ですが、SightMomentEditViewController を現在の WCTimeLineViewController に直接表示することはできないでしょうか。
[WCTimelineVC presentViewController:editSightVC animated:YES completion:^{
}];
こんな風に展示しても素敵ではないでしょうか?ただし、SightMomentEditViewController のヘッダー ファイルと、ショート ビデオがリリースされたときのインターフェイスの要素を観察すると、このコントローラーの作成には少なくとも 2 つの属性が必要であると推測されます。1 つはショート ビデオのパスで、もう 1 つはショートビデオのサムネイル この2つをキー 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:^{
}];
}
このとき、戻るボタンをクリックすれば正常に終了します。 また、 - (void)sendSightToFriend;
には、短い動画を直接転送できるメソッドが見つかりました。 。
ショート ビデオの転送は、モーメントへの転送、友達への転送、ローカル フォト アルバムへの保存、ショート ビデオ リンクのペーストボードへのコピーの 4 つの機能をサポートします。ショートビデオがダウンロードされていない場合は、長押しするとショートビデオの URL リンクのみが表示されます。
ここでは、WCContentItemViewTemplateNewSight と SightMomentEditViewController という 2 つのクラスをフックする必要があります。WCContentItemViewTemplateNewSight で onLongTouch メソッドをフックし、メニュー ポップアップ メニューを追加し、次に応答メソッドを追加します。
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];
特定の調整ファイルを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"//安装完成杀掉的进程
制御ファイルを変更する必要はありません。make make package install
コマンドを実行すると、WeChat が強制終了され、再度 WeChat を開くことができます。短いビデオを転送する機能が追加されました。
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 の [仕様] フォルダーをダウンロードします。
5. インストールの失敗の問題を解決するには、手順 4 でダウンロードした [仕様] フォルダーを開きます。その中には 8 つのファイルがあるはずです。複数の xcode がインストールされている場合は、対応する xcode に入れてください。
(1) iPhoneOS で始まる 4 つのファイルは、/Application/Xcode/Content/Developer/Platforms/IphoneOS.platform/Developer/Library/Xcode/仕様フォルダに配置されます(ない場合は、仕様フォルダを自分で作成してください)
(2) iPhone Simulator で始まる他の 4 つのファイルは、/Application/Xcode/Content/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/仕様フォルダに配置されます(ない場合は、同様に作成してください)
(3) /Application/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 アプリ署名者再署名ツールをダウンロード* (多くのコマンドライン操作を節約し、ワンクリックで再署名します!)*
(3) ハッキングされたWeChatアプリケーションをダウンロードする
AppStore パッケージは暗号化 (シェル化) されており、再署名できないため、dumpdecrypted を使用して自分でシェルをダンプするか、PP アシスタントまたは itool アシスタントを直接使用してジェイルブレイク バージョンをダウンロードすることができます。シェル化された WeChat アプリケーションの。
(4) yololibをインストールする
yololib は WeChat バイナリ ファイルに dylib を挿入できるため、ダウンロード後にコンパイルして yololib を取得できます。
#####(1) 静的ライブラリを生成します。iOSOpendev は前の手順でインストールされています。ここで、iOSOpendev プロジェクトがプロジェクト選択インターフェイスに表示されます。新しく作成されたプロジェクトは 1 つだけです。このファイルにすべてのフック メソッドを記述するだけです。
脱獄していないマシンは、脱獄したマシンのように元のアプリケーションをフックする調整プラグインをインストールできないため、CaptainHook はランタイム メカニズムを使用して、マクロ コマンドを使用してクラス定義やメソッド置換などの機能を実装します。その使用方法を簡単に紹介します。
CHDeclareClass(WCContentItemViewTemplateNewSight);
CHDeclareClass(ClassName)
フックするクラスを示し、通常はこのクラスの操作の開始時に書き込まれます。
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)
元のメソッドをフックすることを意味します ( CHSuper(0, className, Method)
元のメソッドをコピーすることを意味し、CHSuper は現在の位置で元のメソッドを呼び出すことを意味します。count はフック メソッドのパラメータの数を意味します。最適化は通常 self を入力します。return_type はメソッドの戻り値の型で、class_type はメソッドのクラス名を入力します。現在のクラス、name1 はメソッド名、arg1 はパラメータです。パラメータがない場合は、arg を入力します。
CHConstructor
{
@autoreleasepool
{
CHLoadLateClass(WCContentItemViewTemplateNewSight);
CHHook(0, WCContentItemViewTemplateNewSight, onLongTouch);
}
}
これは、CaptainHook のエントリ関数です。ロードするには、フックされたすべてのクラスをここで宣言する必要があります。また、クラス内のメソッドもここでフックするように宣言する必要があります。
次に、クラスとメソッドにコードを記述します。コードは長すぎるため、MMPlugin を使用して github に投稿します。
このプロジェクトには、短いビデオの転送、赤い封筒の自動取得、WeChat 演習ステップの変更の機能が含まれています。赤い封筒の自動取得と WeChat 演習ステップの変更機能は手動でオフにすることができます。
注: システム クラスを使用する場合は、対応するクラス ライブラリ (UIKit など) とヘッダー ファイルを忘れずにインポートしてください。インポートしないと、コンパイル中にエラーが報告されます。
コンピレーションが成功した後、製品フォルダにコンパイルされた静的ライブラリを見つけることができます。
Finderで見つけて、後で使用するためにコピーします。
この時点で持っておくべき材料は次のとおりです。
元のwechatアプリからWeChatバイナリファイルを見つけて、後で使用するためにそれをコピーします。
次のコマンドを実行して、mmplugin.dylibをWeChatバイナリファイルに挿入します。
LeonLei-MBP:WeChat gaoshilei$ ./yololib WeChat MMPlugin.dylib
このコマンドを実行するときは、Yololib、Wechat、およびWechat.Appが同じディレクトリにあることを確認してください。
完了後、mmplugin.dylibとwechatを元のwechat.appにコピーして、元のwechatファイルを上書きします。
iOSアプリの署名者を開き、以下に示すようにさまざまなパラメーターを選択します。
ここで私が選んだのは、エンタープライズレベルの証明書です。
携帯電話に接続し、次のコマンドを実行して、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
インストールが完了したら、携帯電話でWeChatを開いて、追加した新しい機能を試してみてください!特定のリンクが立ち往生している場合、エラーが報告されます。レンダリングをご覧ください: