Recently I watched "IOS application reverse engineering analysis and practice". When you hold a hammer in your hand, the whole world becomes a nail, so you can't wait to practice. Just recently, I have been following American TV series on a video client. Sometimes I want to cache them for offline viewing. However, due to copyright reasons, many videos cannot be cached. So the main goal of reverse practice today is to cache copyrighted videos.
Some people may ask: how do you know that you can achieve this goal? How can you cache the videos with copyright in Wan'an without providing download address at all? Good question. In fact, before I got the hammer, I was able to download the American TV series with pure physical strength. Use the Charles capture tool to get the episode information (see here for the use of Charles). Even copyrighted videos will have the download "URL" field, and then copy each download address to thunderbolt to download. Fortunately, with this painful experience, I know that today's reverse goal is achievable.
download_url
Using tools
Some assistant software, class dump, cycript, Charles, IDA, theos, jailbroken iPhone.
Analysis and practice process
Download the video client xxxxvideo.ipa to reverse through a helper software (save the process of breaking the shell after downloading the app store). After decompression, copy the binary file and use class dump to export the header file:
One
$ class-dump -H xxxxVideo -o headers
Create a new project with Xcode and import the header file into the project:
Install the video client on the jailbreak mobile phone and enter the video playing interface. You can see that the "cache" button below is gray. It's natural to think of this button as the starting point for analysis.
Next, start to find the class of the button through cycript (if you have installed reveal, the next step can be implemented directly with reveal)
One
Two
Three
Four
Five
Six
Seven
Eight
Nine
$ ssh [email protected]
~ root
PID TT STAT TIME COMMAND
1438 ?? Ss 0:02.19 /var/mobile/Applications/414D20A3-EA72-4AB4-87E4-5A209F648EAB/xxxxVideo.app/xxxxVideo
~ root
Therefore, the view controller class corresponding to the current playback interface is videodetailviewcontroller, but the cache button or the following bar is not found in the header file. However, the following property has attracted my attention:
VideoDetailViewController
One
@property(retain, nonatomic) VideoDetailBarController *videoDetailBarController;
Enter the header file corresponding to this class, and finally find the download button downloadbtn, and it is obvious that clicking this button calls the - (void) downloadwithbutton: (ID) arg1 method. Because the cache button has two states: can cache and can't cache, the judgment of whether can cache should be implemented in this method. Since there is no other member variable or method tag can cache, the judgment basis should exist in an object. At this time, you can notice the datamanager property, which is a data management object, and the management object is video Album.
downloadBtn
- (void)downloadWithButton:(id)arg1
dataManager
videoAlbum
One
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
Eleven
Twelve
Thirteen
Fourteen
Fifteen
Sixteen
Seventeen
Eighteen
Nineteen
Twenty
Twenty-one
#import "BaseViewController.h"
@class AsynImageView, FollowButton, RequestItem, TTTAttributedLabel, UIButton, UILabel, VideoAlbumDataManager;
@interface VideoDetailBarController : BaseViewController
{
FollowButton *_followBtn;
UIButton *_downloadBtn;
}
@property(retain, nonatomic) UIButton *downloadBtn;
@property(retain, nonatomic) FollowButton *followBtn;
@property(retain, nonatomic) VideoAlbumDataManager *dataManager;
- (void)downloadWithButton:(id)arg1;
- (void)updateDownloadBarButtonItem;
- (id)videoAlbum;
- (id)initWithVideoDetailDataManager:(id)arg1;
@end
Entering the videoalbum header file, my dear, a model class is more than 300 lines faster and has more methods than properties, but I found what I wanted in a moment - can be downloaded, which should be the basis for judging whether the video can be cached.
VideoAlbum
canBeDownloaded
One
Two
Three
Four
Five
Six
Seven
@interface VideoAlbum : NSObject
- (BOOL)canBeShared;
- (BOOL)canBeSubscribed;
- (BOOL)canBeDownLoaded;
- (BOOL)canBePlayed;
Verify now, create theos tweet project, and configure other information:
One
Two
Three
Four
Five
Six
Seven
Eight
%hook VideoAlbum
- (BOOL)canBeDownLoaded
{
Return YES;
}
%end
Compile, package and install:
One
Two
$ export THEOS_DEVICE_IP=192.168.1.118
$ make package install
Run the video client again, and the "cache" button returns to normal. Click to open the cache interface, and select the episode to download normally.
This article should be over when we get here. Haven't we had a good time? So let's continue to toss, see the video above the login VIP 30? Non VIP users have 30 seconds of ads in front of each video, and the video in VIP area can only watch the 5 minutes in front. Wasting time is a waste of life, but I can't afford VIP, OK? Let's say goodbye to the advertisement and become a VIP.
登录VIP 30
Because the client will communicate every time it enters the "profile", it should obtain the user information, and use Charles to grab the following information:
One
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
Eleven
Twelve
Thirteen
Fourteen
Fifteen
Sixteen
Seventeen
Eighteen
Nineteen
Twenty
Twenty-one
Twenty-two
Twenty-three
{
"attachment": {
"Status": 0,
"MSG": "OK".
"Jifen": 0,
"Dengji": 0,
"uid": xxxxxxxxx,
"passport": "[email protected]",
"nickname": "TracyYih",
"smallimg": "http://tp3.sinaimg.cn/1342106870/50/5664617611/1",
"mobile": "",
"Email":
"birthday": "",
"Gender": 1,
"Utype": 31,
"token": "1ee4863ec29030730e624afdb401a3e6",
"isVip": "0",
"vipexpire": ""
}
"Message": "success",
"debug": null,
"status": 200
}
Did you see? The isvip field is 0. Search "isvip" in the header file and find two classes: userdatamodel and userinterface. Compare the properties with the JSON format returned by communication, we can see that userdatamodel and JSON data correspond one by one, that is, the user information model class. In the userinterface class, there is a userdatamodel instance as the property, and there are some other methods.
isVip
UserDataModel
UserInterface
UserDataModel
UserInterface
UserDataModel
One
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
Eleven
Twelve
Thirteen
Fourteen
Fifteen
Sixteen
Seventeen
Eighteen
Nineteen
Twenty
Twenty-one
Twenty-two
Twenty-three
Twenty-four
Twenty-five
Twenty-six
Twenty-seven
Twenty-eight
Twenty-nine
Thirty
Thirty-one
Thirty-two
Thirty-three
Thirty-four
Thirty-five
Thirty-six
@interface UserDataModel : NSObject
{
BOOL isVip;
NSString *passport;
NSString *password;
NSString *nickname;
NSString *profileImage;
NSString *mobile;
NSString *email;
NSString *birthday;
NSString *requestToken;
NSString *vipExpire;
NSString *score;
NSString *grade;
NSString *uid;
Int gender;
int loginTpye;
}
@property int loginTpye;
@property int gender;
@property(copy, nonatomic) NSString *uid;
@property(copy) NSString *grade;
@property(copy) NSString *score;
@property(copy) NSString *vipExpire;
@property BOOL isVip;
@property(copy) NSString *requestToken;
@property(copy) NSString *birthday;
@property(copy) NSString *email;
@property(copy) NSString *mobile;
@property(copy) NSString *profileImage;
@property(copy) NSString *nickname;
@property(copy) NSString *password;
@property(copy) NSString *passport;
@end
So we should pay attention to the most basic model (userdatamodel). Whether or not the user information returned by the interface is VIP, we set it as VIP:
UserDataModel
One
Two
Three
Four
Five
Six
Seven
Eight
%hook UserDataModel
- (BOOL)isVip
{
Return YES;
}
%end
Compile, package, install and rerun the video client without any change. What should be the problem. Take a closer look at the userdatamodel class and find that the vipexpire field returned by the interface is an empty string, so it should be related to this field. Besides, we only know that vipexpire is a string (it should be a time), and we don't know its specific format. We can't try it one by one. Go to IDA.
UserDataModel
vipExpire
vipExpire
Or search "isvip". In the isvip method of userdatamodel, it is just a simple storage, and there is no other logical judgment:
UserDataModel
Then look at the getmodelisvip (isvip) method in the userinterface, which is very interesting:
UserInterface
First, obtain the isvip attribute of the datamodel attribute. If it is no, return no directly. Otherwise, enter the following judgment. The corresponding code is as follows:
One
Two
Three
Four
Five
Six
Seven
- (BOOL)getModelIsVip
{
if (self.dataModel.isVip) {
}
Retrun NO;
}
Then we will judge whether the vipexpire field is empty, which is consistent with our previous assumption. The corresponding code is as follows:
vipExpire
One
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
- (BOOL)getModelIsVip
{
if (self.dataModel.isVip) {
NSString *vipExpire = self.dataModel.vipExpire;
if (vipExpire && vipExpire.length) {
}
}
Retrun NO;
}
Determine whether the dateformatter property is empty. If it is empty, create the object.
dateFormatter
One
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
Eleven
Twelve
Thirteen
Fourteen
Fifteen
Sixteen
Seventeen
- (BOOL)getModelIsVip
{
if (self.dataModel.isVip) {
NSString *vipExpire = self.dataModel.vipExpire;
if (vipExpire && vipExpire.length) {
if (!self.dateFormatter) {
self.dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[self.dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
[self.dateFormatter setLocale:locale];
}
}
}
Retrun NO;
}
Compare VIP validity and current time:
One
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
Eleven
Twelve
Thirteen
Fourteen
Fifteen
Sixteen
Seventeen
Eighteen
Nineteen
Twenty
- (BOOL)getModelIsVip
{
if (self.dataModel.isVip) {
NSString *vipExpire = self.dataModel.vipExpire;
if (vipExpire && vipExpire.length) {
if (!self.dateFormatter) {
self.dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[self.dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];
[self.dateFormatter setLocale:locale];
}
NSDate *vipExpireDate = [self.dateFormatter dateFormString:vipExpire];
if ([vipExpireDate compare:[NSDate date]]) {
Return YES;
}
}
}
Retrun NO;
}
Here we basically restore the method of judging whether it is VIP or not. The logic is clear. Now we want to realize VIP identity simply:
One
Two
Three
Four
Five
Six
Seven
Eight
Nine
Ten
Eleven
Twelve
Thirteen
Fourteen
Fifteen
Sixteen
Seventeen
Eighteen
#define kYearInterval 31536000.0
%hook UserDataModel
- (BOOL)isVip
{
Return YES;
}
- (NSString *)vipExpire
{
NSDate *date = [[NSDate date] dateByAddingTimeInterval:kYearInterval];
NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease];
[dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
return [dateFormatter stringFromDate:date];
}
%end
Compile, package, install, and run the client again. Is there any VIP on the big platform? There's no advertisement in the video. Is there any wood? Do you have any VIP videos?
summary
At this point, the whole reverse process is over. It is better to teach people to fish than to teach people to fish, so I described the analysis process in detail rather than the results. In addition, the content of this article is only for personal learning and communication, so even if you know which client I am reversing, do not use the content of this article for the purpose of damaging the interests of the client.
Raspberry PI Bluetooth application and ibeacon base station construction
I program, I'm happy