2020.1.14 update does not require jailbreak installation
This article is a tutorial on reversing the WeChat binary file to realize the forwarding of short videos in Moments. From the initial assembly code to the final re-signing installation and other operations, it will teach you step by step how to play with WeChat! After you learn it, it will be easy to reverse engineer other WeChat functions.
This article is divided into two parts due to its length. The first part explains the reverse work, that is, how to find the relevant functions and methods to implement. The second part explains how to re-signature installation on non-jailbroken machines and tweak installation on jailbroken machines. Detailed process.
The second part of the text also provides the code for WeChat to automatically grab red envelopes and modify the number of WeChat steps. These can be found step by step by following the routine of this article and will not be repeated here.
Before practicing, you need to prepare a jailbroken mobile phone and then install all the tools listed below. IDA and Reveal are both cracked versions. The genuine version of IDA costs more than 2,000 dollars. It is really worth the money for such an awesome reverse engineering tool. However, if you are not a company that specializes in reverse engineering, there is no need to use the genuine version. The next cracked version of Windows will be OK, I can’t find it on Mac yet. You can use hopper to replace IDA on Mac, which is also a very powerful reverse engineering tool. Without further ado, let’s get started!
Note: The WeChat binary file reversed in this article is version 6.3.28. If it is a different WeChat version, the base address in the binary file is also different.
Reverse environment for MacOS + iPhone5S 9.1 jailbroken machine <br> First use dumpdecrypted to smash the shell of WeChat (if you don’t know how, please read this tutorial written by me), get a WeChat.decrypted file, and first throw this file into IDA for analysis (a binary file of about 60MB, it takes IDA almost 40 minutes to analyze End), use class-dump to export all header files
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
I have a mother-in-law! There are a total of 8,000 header files, and WeChat really has a huge amount of work! Calm down your emotions, collect your thoughts and continue. To get the download link of a short video, find the View that plays the video, and follow the clues to find the URL of the short video. Use Reveal to view the playback window of the short video. You can see that the WCContentItemViewTemplateNewSigh object is the playback window of the short video. Its subViews include WCSightView, SightView, and SightPlayerView. These classes are our entry point. When saving a video to favorite, long-press the video to pop up the option, so there may be gesture-related methods in the WCContentItemViewTemplateNewSight class. Find clues in the header file just exported.
- (void)onLongTouch;
- (void)onLongPressedWCSight:(id)arg1;
- (void)onLongPressedWCSightFullScreenWindow:(id)arg1;
These methods are related to the long press gesture. Find these functions in IDA and view them one by one. onLongPressedWCSight and onLongPressedWCSightFullScreenWindow are relatively simple, onLongTouch is relatively long, and I found that the method Favorites_Add is called internally, because when you long press the video, an option that comes out is Favorites, and I saw this function call
ADRP X8, #selRef_sightVideoPath@PAGE
LDR X1, [X8,#selRef_sightVideoPath@PAGEOFF]
I got the address of the short video here. It can be speculated that this function is related to the collection. Let’s break the point and test it.
(lldb) im li -o -f
[ 0] 0x000000000003c000 /var/mobile/Containers/Bundle/Application/2F1D52EC-C57E-4F95-B715-EF04351232E8/WeChat.app/WeChat(0x000000010003c000)
You can see that the ASLR of WeChat is 0x3c000. Find the base addresses of these three functions in IDA and set breakpoints respectively.
(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
Go back to WeChat and long press the short video to see how the breakpoint is triggered.
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
……
It was found that breakpoint 2 was triggered first, then breakpoint 1 was triggered, and then breakpoints 2 and 1 were triggered once each. Breakpoint 3 has been very quiet. You can exclude the connection between onLongPressedWCSightFullScreenWindow and the collection of short videos. The traces of short videos must be found in the remaining two methods. Find C through V, and find M by following the clues, which has been tried and tested! Use cycript to inject WeChat and get the Controller where the view that plays the short video is located.
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>"
Find the Controller to which WCContentItemViewTemplateNewSight belongs through the responder chain and it is WCTimeLineViewController. No valuable clues were found in the header file of this class, but we noticed that the view where the short video is located belongs to MMTableVIewCell (see the Reveal analysis chart above), which is the most familiar TableView and cell data for every iOS. It is assigned through the proxy method of UITableViewDataSource - tableView:cellForRowAtIndexPath:
Through this method, you can definitely know the shadow of M. Find [WCTimeLineViewController tableView:cellForRowAtIndexPath:]
in IDA and locate the base address 0x10128B6B0:
__text:000000010128B6B0 ADRP X8, #selRef_genNormalCell_indexPath_@PAGE
The function here is the method for generating cells in WCTimeLineViewController. In addition to this method, there are three other methods for generating cells in this class:
- (void)genABTestTipCell:(id)arg1 indexPath:(id)arg2;
- (void)genRedHeartCell:(id)arg1 indexPath:(id)arg2;
- (void)genUploadFailCell:(id)arg1 indexPath:(id)arg2;
From the literal meaning, we can guess that normal should be a method for generating small video cells. Continue to look for clues in IDA
__text:0000000101287CC8 ADRP X8, #selRef_getTimelineDataItemOfIndex_@PAGE
The above method is found in genNormalCell:IndexPath:
method. You can boldly guess that this method is a method to obtain TimeLine (moments) data. The data of the short video must also be obtained through this method, and IDA can see the call in this method. A method called selRef_getTimelineDataItemOfIndex_
, obtaining DataItem seems to be the data source of the cell! Next, use LLDB to set breakpoints to verify the conjecture. You can find the base address corresponding to this method through IDA: 0x101287CE4. First print the ASLR offset of running WeChat.
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)
So the location of our breakpoint is 0x50000+0x101287CE4
(lldb) br s -a 0x50000+0x101287CE4
Breakpoint 1: where = WeChat`___lldb_unnamed_symbol63721$$WeChat + 252, address = 0x00000001012d7ce4
Print the value of x0
(lldb) po $x0
Class name: WCDataItem, addr: 0x15f5f03b0
tid: 12393001887435993280
username: wxid_z8twcz4o18fg12
createtime: 1477360950
commentUsers: (
)
contentObj: <WCContentItem: 0x15f57d000>
Get a WCDataItem object. The value of x0 here is the return value after the execution of selRef_getTimelineDataItemOfIndex_
, and then change the value of x0.
(lldb) register write $x0 0
(lldb) c
At this time, you will find that the content of the small video we want to refresh is all empty.
At this point, we have found the method to obtain the source data of the short video. The question is how do we get this WCDataItem? Continue to look at the calling status of the IDA analysis function:
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
......
The parameter passed in to selRef_getTimelineDataItemOfIndex_
is x2. You can see that x21 passed to x2 is the return value of the function selRef_calcDataItemIndex_
, which is an unsigned long data type. Continuing the analysis, the caller of selRef_getTimelineDataItemOfIndex_
function is the return value of selRef_getService_
in the previous step. After breakpoint analysis, it is found that it is a WCFacade
object. Let’s sort out the calls to selRef_getTimelineDataItemOfIndex_
:
The caller is the return value of selRef_getService_
; the parameter is the return value of selRef_calcDataItemIndex_
<br> Let’s turn our attention to those two functions and use the same principle to analyze how they implement the call.
selRef_getService_
first:MMServiceCenter
object. Looking up, x19 is assigned at the position 0x101287C88. The result is very clear that x19 is the return of [MMServiceCenter defaultCenter]
value.[WCFacade class]
.selRef_calcDataItemIndex_
:WCTimeLineViewController
its caller x0 at the position of 0x101287C58.selRef_section
0x101287C4C, it is found that selRef_section
incoming parameter comes from selRef_section
. or is x3WCTimeLineViewController - (void)genNormalCell:(id) indexPath:(id)
, so the parameter of selRef_calcDataItemIndex_
is [IndexPath section]
.getTimelineDataItemOfIndex:
can pass [[MMServiceCenter defaultCenter] getService:[WCFacade class]]
To obtain, its parameters can be obtained through the following function
[WCTimeLineViewController calcDataItemIndex:[indexPath section]]
Always feel like something is missing? We haven’t gotten the indexPath yet! The next step is to get the indexPath. This is relatively simple, because we are in [WCContentItemViewTemplateNewSight onLongTouch]
, so we can get MMTableViewCell, MMTableView and WCTimeLineViewController in sequence through [self nextResponder]
, and then get the indexPath through [MMTableView indexPathForCell:MMTableViewCell]
.
After doing this, we have obtained the WCDataItem object. The next focus should be on WCDataItem, and finally get the short video we want. Look for clues in the header file of this class. Because the video can only be played after downloading, the path of the video should be obtained here, so pay attention to the attributes or methods related to url and path, and then find the following suspects.
@property(retain, nonatomic) NSString *sourceUrl2;
@property(retain, nonatomic) NSString *sourceUrl;
- (id)descriptionForKeyPaths;
- (id)keyPaths;
Go back to LLDB and print these values with breakpoints to see what's there.
(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
There are no valuable clues, but I noticed that there is a WCContentItem in WCDataItem. It seems that the only way to start is here. Let's take a look at the header file!
@property(retain, nonatomic) NSString *linkUrl;
@property(retain, nonatomic) NSString *linkUrl2;
@property(retain, nonatomic) NSMutableArray *mediaList;
Print it out in 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>
)
There is a WCMediaItem object in the mediaList array. Media is generally used to represent video and audio. I would make a bold guess that this is it! Quickly find the header file and search it.
@property(retain, nonatomic) WCUrl *dataUrl;
- (id)pathForData;
- (id)pathForSightData;
- (id)pathForTempAttachVideoData;
- (id)videoStreamForData;
Among the above attributes and methods, pathForSightData
is the most likely to get the short video path. Continue to verify.
(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
Got the network url and local path of the short video! Here you can use iFunBox or scp to copy this file from the sandbox to see if it is a short video that this cell should play.
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
Open it with QuickTime and find that it is indeed the short video we are looking for. Verify that the URL is correct again. Open the dataUrl printed above in the browser and find that it is also this short video. Analyzing this class we can draw the following conclusions:
At this point, the analysis of the path and acquisition method of the short video has been completed. To achieve forwarding, we need to continue to analyze the release of WeChat Moments.
This section is a detour I took when looking for the short video forwarding function. In the end, I didn't find a way to implement it. However, it also provides some commonly used ideas and methods in reverse engineering. If you don't want to read it, you can skip to the second section.
Open the shooting interface of the short video and inject it with cycript. We need to find out which method is used to publish the short video, and then check which windows are in the current window (because the shooting of the short video is not done in the keyWindow of UIApplication)
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>>"
)
It is found that the current page has a total of 5 windows, among which MMUIWindow is the window where the short video was shot. Print its UI tree structure.
cy# [#0x127796440 recursiveDescription]
The printed result is quite long, so I won’t post it. Find this button which is the button for shooting short videos
| | | | | | <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>>
and then execute
cy# [#0x1277a9d70 setHidden:YES]
I found that the shooting button disappeared, which confirmed my suspicion. To find the response event of the button, you can find it through target
cy# [#0x1277a9d70 allTargets]
[NSSet setWithArray:@[#"<MainFrameSightViewController: 0x1269a4600>"]]]
cy# [#0x1277a9d70 allControlEvents]
193
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:193]
null
I found that the button has no corresponding action, which is strange! UIButton must have a target and action, otherwise the Button cannot respond to events. Let's try other ControlEvents
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchDown]
@["btnPress"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpOutside]
@["btnRelease"]
cy# [#0x1277a9d70 actionsForTarget:#0x1269a4600 forControlEvent:UIControlEventTouchUpInside]
@["btnRelease"]
It turns out that these three ControlEvents have corresponding actions. Let’s take a look at the values of these three enumerations.
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;
It can be seen that UIControlEventTouchDown corresponds to 1, UIControlEventTouchUpInside corresponds to 128, UIControlEventTouchUpOutside corresponds to 64, and the sum of the three is exactly 193! It turns out that when calling [#0x1277a9d70 allControlEvents]
, the returned value should be an enumeration. If there are multiple enumerations, add their values. Isn’t this a bit confusing? I feel the same way! Just now we printed out the actions corresponding to the three ControlEvents and continued with LLDB+IDA for dynamic analysis.
Because we need to find a way to publish short videos, we don't care about the corresponding btnPress
function. We focus on btnRelease
, a method that will be called after the shooting button is released. Find this method in IDA and set the next breakpoint after finding it.
(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
Shoot a short video with your mobile phone and then release it, triggering the breakpoint, which shows that our guess is correct. Continuing the analysis, we found that the code is from the right side of the picture above. After looking at it, there is no way to jump to the video release. However, if you look carefully, there is a block, which is the system's delay block and is located at 0x102093760. Then we follow the breakpoint and jump to the address stored in x16 at 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>
It is found that the parameter x2 passed in is a block. Let’s review the dispatch_after function again.
void dispatch_after(dispatch_time_t when, dispatch_queue_t queue, dispatch_block_t block);
This function has three parameters, namely dispatch_time_t, dispatch_queue_t, and dispatch_block_t. The x2 printed here is the block to be passed in, so we guess that there will be a delay after shooting the short video, and then execute the block just passed in, so x2 must be If there are other method calls, the next step is to know the location of this block.
(lldb) memory read --size 8 --format x 0x16fd49f88
0x16fd49f88: 0x000000019f8fd218 0x00000000c2000000
0x16fd49f98: 0x000000010214777c 0x0000000102fb0e60
0x16fd49fa8: 0x000000015da32600 0x000000015ea1a430
0x16fd49fb8: 0x000000015cf5fee0 0x000000016fd49ff0
0x000000010214777c is the location of the block. Of course, the current ASLR offset of WeChat must be subtracted. The final address in IDA is 0x10209377C. Suddenly, it is discovered that this is the subroutine of btnRelease
sub_10209377C. This subroutine is very simple and has only one method selRef_logicCheckState_
which may be our target. Let’s first see who called this method
(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>
I found that it was still called by the MainFrameSightViewController object. Then selRef_logicCheckState_
must also be in the header file of this class. I searched and found it.
- (void)logicCheckState:(int)arg1;
Look for [MainFrameSightViewController logicCheckState:] in the left window of IDA and find that this method is super complicated and has too much logic, so don’t worry and go through it slowly. We found a switch jump at 0x102094D6C, and the idea was very clear. We only need to find the line where the short video was shot and look down. LLDB will help us look at the line. Set a breakpoint at 0x102094D6C. This breakpoint will be triggered multiple times when shooting short videos. You can disable the breakpoint before shooting, enable the breakpoint before letting go, and print the x8 value at this time.
(lldb) p/x $x8
(unsigned long) $38 = 0x0000000102174e10
x8 is a pointer, and the address it points to is 0x102174e10. Use this address minus the current ASLR offset to find the base address in IDA. It is found to be 0x102094E10. The logical processing line for the completion of shooting is found and goes all the way. After the 0x102094E24 position, jump to 0x1020951C4. This branch has less content and contains three functions.
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
Among them, selRef_finishWriter
and selRef_turnCancelBtnForFinishRecording
need to be focused on. These two methods seem to mean the end of short video recording. The clues are most likely in these two functions. By looking at the caller, we found that these two methods belong to MainFrameSightViewController. Continue to check these two methods in IDA. I found a method called f_switchToSendingPanel
near the end of selRef_finishWriter
at 0x102094248, set a breakpoint, and then shot the video, I found that this method was not triggered. The publishing interface should not be called through this method, so continue to return to selRef_finishWriter
method; call the method selRef_stopRecording
at the location of 0x1020941DC, and the caller who prints it finds that this method belongs to SightFacade
, and continues to look for the implementation of this method in IDA. selRef_stopRecord
is called again at the 0x101F9BED4 position of this method. The caller also finds that this method belongs to SightCaptureLogicF4. It is a bit like peeling an onion and continues to look for the implementation of this method. selRef_finishWriting
is called again at position 0x101A98778 inside this method. Using the same principle, it is found that this method belongs to SightMovieWriter. Three layers have been peeled off, let’s continue:
Two lines are divided at the 0x10261D004 position in SightMovieWriter - (void)finishWriting
A breakpoint is set at this position, and then the breakpoint is triggered after shooting the short video, and the value of x19 is printed.
(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 }>
Therefore, the code will not jump to loc_10261D054 but go to the left. In the code on the left, it is found that a block is enabled. This block is the subroutine sub_10261D0AC and the address is 0x10261D0AC. Find this address and the structure is as shown below:
It can be seen that it is mainly divided into two lines. We set a breakpoint at the end of the first box, which is 0x10261D108. After the breakpoint is triggered after the shooting is completed, the value of x0 is printed as 1. The assembly code here is
__text:000000010261D104 CMP X0, #2
__text:000000010261D108 B.EQ loc_10261D234
B.EQ will jump to loc_10261D234 only when the result of the previous step is 0, but the result here is not 0. Change the value of x0 to 2 so that the result of the previous step is 0.
(lldb) po $x0
1
(lldb) register write $x0 2
(lldb) po $x0
2
At this time, release the breakpoint and wait to jump to the short video publishing interface. The result is that it is stuck in this interface without any response, so I guess the logic to implement the jump should be on the line on the right, and continue to search along the line on the right: It was found that the following method was called at the right position 0x10261D3AC
- (void)finishWritingWithCompletionHandler:(void (^)(void))handler;
This method is a method in AVAssetWriter provided by the system. It is an operation to be done after the video writing is completed. Here, a block is passed in. Because there is only one parameter, the corresponding variable is x2, and the value of x2 is printed.
(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
And find the block position 0x10261D4B0 through the stack memory (the ASLR offset needs to be subtracted)
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
Only two methods are called, one is selRef_stopAmr
to stop amr (an audio format), and the other is selRef_compressAudio
to compress audio. The next operation after the shooting is completed should not be placed in these two methods. I have been looking for it for so long and I still have no clue. This road seems to be dead, don't get into trouble, retreat strategically and look for other entrances.
The joy of going reverse is to experience the joy of success on the way to searching for the truth. You may also go in the wrong direction and get farther and farther away from the truth. Don’t be discouraged, adjust the direction and keep moving forward!
(Since WeChat has been secretly upgraded in the background, the following content is the ASLR of WeChat 6.3.30 version, and the above analysis is based on version 6.3.28)
Notice that when you click the camera button in the upper right corner of the circle of friends, a Sheet will pop up at the bottom. The first one is the Sight short video. Start here and use cycript to see which event the Sight button corresponds to.
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()
The Sheet at the bottom is NewYearActionSheet, and then print the UI tree structure diagram of NewYearActionSheet (it’s too long so I won’t post it). Then find that the UIButton corresponding to Sight is 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:"]
The event bound to the button can be found through actionsForTarget:forControlEvent:
method of UIControl. The trigger method of the Sight button is OnDefaultButtonTapped:
Go back to IDA and find this method in NewYearActionSheet. We continue to analyze only this method selRef_dismissWithClickedButtonIndex_animated
, by printing its caller, we find that it is still NewYearActionSheet. Continue to click in to find the newYearActionSheet_clickedButtonAtIndex
method. It seems that it is not NewYearActionSheet itself. Printing the caller x0, we find that it belongs to the class WCTimeLineViewController. Follow the breakpoint and call the method #selRef_showSightWindowForMomentWithMask_byViewController_scene
at the position 0x1012B78CC. Through observation, we found that the caller of this method is the return value x0 at the position 0x1012B78AC. This is a class SightFacade. I guess this method is in SightFacade. I went to the header file to find it. found this method
- (void)showSightWindowForMomentWithMask:(id)arg1 byViewController:(id)arg2 scene:(int)arg3;
This method should be the method to jump to the small video interface. Print its parameters separately below
(lldb) po $x2
<UIImage: 0x14f046660>, {320, 568}
(lldb) po $x3
<WCTimeLineViewController: 0x14e214800>
(lldb) po $x4
2
(lldb) po $x0
<SightFacade: 0x14f124b40>
Among them, x2, x3, and x4 correspond to three parameters respectively. x0 is the caller. Jump to the inside of this method to see how to implement it. It is found that the short video shooting interface is initialized in this method. First, a MainFrameSightViewController is initialized, then a UINavigationController is created to put the MainFrameSightViewController in it, and then a MMWindowController is initialized to call
- (id)initWithViewController:(id)arg1 windowLevel:(int)arg2;
The method puts the UINavigationController in and completes all UI creation work for the short video shooting interface. After the shooting is completed, enter the publishing interface. At this time, use cycript to find that the current Controller is SightMomentEditViewController. From this, I came up with an idea, wouldn't it be enough to skip the previous shooting interface and directly enter the publishing interface? We create a SightMomentEditViewController ourselves and put it in the UINavigationController, and then put this navigation controller in the MMWindowController. **(I have written the tweak here for verification, and the specific tweak ideas are written later)** The result is that the publishing interface can indeed pop up, but the NavgationBar of the navigation bar covers the original, and the entire interface is transparent , it's ugly, and after the publishing is completed, the entire MMWindowController cannot be destroyed, and it still stays in the publishing interface. This is not the result we want, but we do gain a lot. At least we can directly call the publishing interface, and small videos can be forwarded normally. My personal guess is that the reason why the current interface cannot be destroyed is because MMWindowController created a new window, which is not the same as the keyWindow where TimeLine is located. The button trigger method of SightMomentEditViewController cannot destroy this window, so I have a bold guess. Can't I just display SightMomentEditViewController directly on the current WCTimeLineViewController?
[WCTimelineVC presentViewController:editSightVC animated:YES completion:^{
}];
Wouldn’t it be nice to display it like this? However, by observing the header file of SightMomentEditViewController, combined with the elements on the interface when the short video is released, it is speculated that creating this controller requires at least two attributes, one is the path of the short video, and the other is the thumbnail of the short video. These two key If the attribute is given to SightMomentEditViewController, it should be displayed normally.
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:^{
}];
The short video publishing interface can be displayed normally and all functions can be used normally. The only problem is that the return button has no effect and the SightMomentEditViewController cannot be destroyed. Use cycript to check the actionEvent of the left button and find that its response function is - (void)popSelf;
Clicking the left button to return triggers the pop method, but this controller is not in the navgationController, so it is invalid. We need to redo this method. Write
- (void)popSelf
{
[self dismissViewControllerAnimated:YES completion:^{
}];
}
At this time, click the return button to exit normally. In addition, a method called - (void)sendSightToFriend;
was found in WCContentItemViewTemplateNewSight, which can directly forward short videos to friends. So far, the function of forwarding short videos has been found.
The forwarding of short videos supports 4 functions, forwarding to Moments, forwarding to friends, saving to local photo album, and copying the short video link to the pasteboard. If the short video is not downloaded, long pressing will only show the url link of the short video.
Here we need to hook two classes, namely WCContentItemViewTemplateNewSight and SightMomentEditViewController. Hook the onLongTouch method in WCContentItemViewTemplateNewSight and then add the menu pop-up menu, and add the response methods in turn. The specific code is as follows:
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];
I put the specific tweak file on 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"//安装完成杀掉的进程
The control file does not need to be modified, and then execute the command make package install
to install it on the mobile phone. WeChat will be killed, and then open WeChat again. The function of forwarding short videos has been added.
Install macports (the installation process requires a VPN connection, otherwise the installation will not be successful)
After installing MacPorts, open the terminal and enter sudo port -v selfupdate
to update MacPorts to the latest version, which may take a long time.
After updating MacPorts, install the DPKG file and enter sudo port -f install dpkg
in the terminal.
Download and install iOSOpendev. If the installation fails, you can use Command + L
to check for problems during the installation.
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";
}
Here's a solution: Download the Specifications folder in iOSOpenDevInstallSolve
5. To fix the installation failure problem, open the Specifications folder downloaded in step 4. There should be 8 files in it. If you have multiple xcodes installed, please put them in the corresponding xcodes.
(1) The four files starting with iPhoneOS are placed in the /Application/Xcode/Content/Developer/Platforms/IphoneOS.platform/Developer/Library/Xcode/Specifications folder (if not, please create a Specifications folder yourself)
(2) The other four files starting with iPhone Simulator are placed in the /Application/Xcode/Content/Developer/Platforms/iPhoneSimulator.platform/Developer/Library/Xcode/Specifications folder (if not, please create one as well)
(3) Create a usr folder under the /Application/Xcode/Content/Developer/Platforms/iPhoneSimulator.platform/Developer/ folder, and create a folder named bin under the usr folder.
Note: Sometimes there will be a prompt that the installation failed. Open a new project in Xcode. If there is iOSOpenDev in the option menu of the project, it means the installation is successful. Ignore the installation prompt.
To install the ipa package, you can also use tools such as itool, but ideviceinstaller can see the installation process, which makes it easier for us to find the cause of the error.
execute command
brew install ideviceinstaller
If you are prompted that the brew command cannot be found, it means that Homebrew has not been installed on your Mac.
Common error messages:
ERROR: Could not connect to lockdownd, error code -5
At this time, just reinstall libimobiledevice (because ideviceinstaller relies on many other plug-ins)
Execute the following command:
$ brew uninstall libimobiledevice
$ brew install --HEAD libimobiledevice
Download the iOS App Signer re-signing tool* (save a lot of command line operations, re-sign with one click!)*
(3) Download the hacked WeChat application
Because the AppStore package is encrypted (shelled) and cannot be re-signed, you have to use a shelled one. You can use dumpdecrypted to dump the shell yourself, or you can directly use the PP assistant or itool assistant to download the jailbroken version of the WeChat application that has been shelled. .
(4) Install yololib
yololib can inject dylib into the WeChat binary file, so that your Hook can be effective. After downloading, compile and get yololib
#####(1) Generate static library. iOSOpendev has been installed in the previous step. Now open a new project in Xcode. The iOSOpendev project will appear in the project selection interface. Here we need to select the CaptainHook Tweak project. There is only one newly created project. .mm file, we only need to write all hook methods in this file.
Because non-jailbroken machines cannot install tweak plug-ins to hook original applications like jailbroken machines, CaptainHook uses the Runtime mechanism to implement functions such as class definition and method replacement using macro commands. Here is a brief introduction to how to use it:
CHDeclareClass(WCContentItemViewTemplateNewSight);
CHDeclareClass(ClassName)
indicates which class to hook, and is usually written at the beginning of the operation on this class.
CHDeclareMethod0(id, WCContentItemViewTemplateNewSight, SLSightDataItem){......}
CHDeclareMethod(count, return_type, class_type, name1, arg1, ....)
means creating a new method, count means the number of parameters of this method, return_type means the return type, class_type fills in the class name of this method, name1 means the method name, arg1 represents the first parameter, if there is no parameter, leave it blank, and so on.
CHOptimizedMethod0(self, void, WCContentItemViewTemplateNewSight, onLongTouch){
CHSuper(0, className, Method);//可选
......
}
CHOptimizedMethod(count, optimization, return_type, class_type, name1, type1, arg1)
means hooking the original method (if CHSuper(0, className, Method)
means copying the original method, CHSuper means calling the original method at the current position), count means the number of hook method parameters, optimization generally fills in self, return_type is the method return value type, class_type fills in the class name of the current class, name1 is the method name, arg1 is the parameter, if there are no parameters, fill in arg, and so on.
CHConstructor
{
@autoreleasepool
{
CHLoadLateClass(WCContentItemViewTemplateNewSight);
CHHook(0, WCContentItemViewTemplateNewSight, onLongTouch);
}
}
This is the entry function of CaptainHook. All hooked classes must be declared to be loaded here, and the methods in the class must be declared hooked here.
Then you can write code into the classes and methods. The code is too long so I won’t post it. I put it on github with MMPlugin.
This project includes the functions of forwarding short videos, automatically grabbing red envelopes, and modifying WeChat exercise steps. The functions of automatically grabbing red envelopes and modifying WeChat exercise steps can be turned off manually.
Note: If you use system classes, remember to import the corresponding class library (such as UIKit) and header files, otherwise an error will be reported during compilation.
After the compilation is successful, you can find the compiled static library in the Products folder.
Find it in finder and copy it for later use.
The materials you should have at this point include:
Find the WeChat binary file from the original WeChat app and copy it for later use. Delete the Watch folder in weChat.app and the WeChatShareExtensionNew.appex in the PlugIns folder .
Execute the following command to inject MMPlugin.dylib into the WeChat binary file. The command is as follows:
LeonLei-MBP:WeChat gaoshilei$ ./yololib WeChat MMPlugin.dylib
When executing this command, make sure that yololib, WeChat, and WeChat.app are in the same directory.
After completion, copy MMPlugin.dylib and WeChat to the original WeChat.app, overwriting the original WeChat file.
Open iOS App Signer and select various parameters as shown below:
What I chose here is an enterprise-level certificate. Personal developer certificates are also available. Be sure to choose the production environment. After selecting, click start. Wait a moment and a re-signed ipa package will be generated.
Connect to your mobile phone and execute the following command to check whether ideviceinstaller is connected to the mobile phone:
LeonLei-MBP:WeChat gaoshilei$ ideviceinfo
If a lot of mobile phone information is printed out, it means that the connection is successful and you can install the ipa package. If it is unsuccessful, please make adjustments according to the error prompts. Execute the following command to install:
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
After the installation is complete, open WeChat on your phone to try the new features we added! If a certain link is stuck, an error will be reported. Please modify it according to the error message. Please see the renderings: