Meituan iOS reverse engineering analysis

Posted by shah on Sat, 01 Jan 2022 20:41:15 +0100

Tech: meituan iOS reverse engineering analysis

by ChenQi

Tech: reverse engineering analysis of meituan Android , nine months ago, I conducted a simple unpacking analysis on meituan Android App, mainly to understand the implementation of meituan MRN engineering structure, and observe the module design division and code volume proportion of each business line. The logic implementation of the source code is not specifically analyzed.

Recently, QIN made a more detailed reverse analysis of meituan iOS App.

[reprint] original address: https://chenqi.app/Meituan-iOS-Reverse-Engineering/ Original author: Chen Qi

static analysis

Meituan App iOS installation package
Version No. 11.6 two hundred and one
Released on January 1, 2021 eighteen

Fiddler

For routine operation, first, Fiddler is used to capture packets by HTTP(S) network proxy, and no obvious clues are obtained. It is speculated that meituan App uses the Native layer to realize the network communication function and is responsible for common processing such as encryption and decryption, compression and decompression. It belongs to a common technical scheme, and so does Ctrip CRN. The subsequent analysis confirmed the conjecture.

Cydia

iPhone jailbreak and install Frida cyrun cyscript in Cydia App, which will be used later.

frida-ios-dump

Pull a decrypted IPA from a jailbroken device

Usage

  • Install frida on device
  • sudo pip install -r requirements.txt --upgrade
  • Run usbmuxd/iproxy SSH forwarding over USB (Default 2222 -> 22). e.g. iproxy 2222 22
  • Run ./dump.py Display name or Bundle identifier

For SSH/SCP make sure you have your public key added to the target device's ~/.ssh/authorized_keys file.

After the App is submitted to the App Store, it is officially encrypted by Apple to generate an IPA file for users to download. The direct decompression of the file is ciphertext, which is difficult to analyze. Therefore, you need to use frida to smash the shell through the prison break mobile phone environment to obtain the decrypted original IPA file (IPA = ZIP).

iproxy 2222 22 # Turn on USB port forwarding and keep the process alive. Do not turn it off.
./dump.py com.meituan.imeituan # Start to smash the shell and export the original IPA file to the local computer.
unzip ./Meituan.ipa # Unzip to/ Payload/imeituan.app/

class-dump

class-dump is a command-line utility for examining the Objective-C segment of Mach-O files. It generates declarations for the classes, categories and protocols.

Restore the Objective-C class declaration header file from the Mach-O binary file extracted from the original IPA.

class-dump -s -S -a -A --arch arm64 -H -o ./headers/imeituan/ ./Payload/imeituan.app/imeituan

MRNBundle.h

Use VSCode to open the above header file project generated using class dump. It is known that meituan air tickets, hotels and other businesses are implemented using MRN(Meituan React Native) technology, so search RN keywords directly.

Start with mrnbundle H start.

//
//     Generated by class-dump 3.5 (64 bit).
//
//     class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard.
//
 
#import "SAKDomainObject.h"
 
@class METDIOBundle, NSArray, NSDictionary, NSMutableArray, NSString;
 
@interface MRNBundle : SAKDomainObject
{
    _Bool _manualStopLoading;   // 8 = 0x8
    _Bool _confused;    // 9 = 0x9
    _Bool _isRAMBundle; // 10 = 0xa
    NSString *_installPath; // 16 = 0x10
    NSString *_bizName; // 24 = 0x18
    NSString *_moduleName;  // 32 = 0x20
    NSString *_version; // 40 = 0x28
    NSArray *_dependencies; // 48 = 0x30
    NSString *_name;    // 56 = 0x38
    NSString *_timestamp;   // 64 = 0x40
    unsigned long long _bundleType; // 72 = 0x48
    NSMutableArray *_lazyLoadJavaScriptPaths;   // 80 = 0x50
    NSDictionary *_fonts;   // 88 = 0x58
    NSDictionary *_hashSum; // 96 = 0x60
    NSDictionary *_metaInfo;    // 104 = 0x68
    NSString *_RNVersion;   // 112 = 0x70
    unsigned long long _format; // 120 = 0x78
    NSString *_status;  // 128 = 0x80
    long long _size;    // 136 = 0x88
    NSArray *_dependentBundles; // 144 = 0x90
    NSString *_indexPath;   // 152 = 0x98
    NSArray *_filePaths;    // 160 = 0xa0
    METDIOBundle *_dioInstance; // 168 = 0xa8
}
 
+ (id)JSONKeyPathsByPropertyKey;    // IMP=0x00000001000f3778
+ (id)bundleFromDIOBundle:(id)arg1; // IMP=0x00000001000f17d8
+ (id)bundleFromPath:(id)arg1;  // IMP=0x00000001000f1238
+ (id)bundleTypeJSONTransformer;    // IMP=0x00000001000f3bc0
+ (id)domainWithJSONDictionary:(id)arg1;    // IMP=0x00000001000f36d8
- (void).cxx_destruct;  // IMP=0x00000001000f6b74
@property(readonly, nonatomic) NSString *RNVersion; // @synthesize RNVersion=_RNVersion;
@property(retain, nonatomic) NSString *bizName; // @synthesize bizName=_bizName;
@property(nonatomic) unsigned long long bundleType; // @synthesize bundleType=_bundleType;
@property(nonatomic) _Bool confused; // @synthesize confused=_confused;
- (id)dataOfFileAtPath:(id)arg1;    // IMP=0x0000000101730518
@property(retain, nonatomic) NSArray *dependencies; // @synthesize dependencies=_dependencies;
@property(retain, nonatomic) NSArray *dependentBundles; // @synthesize dependentBundles=_dependentBundles;
@property(retain, nonatomic) METDIOBundle *dioInstance; // @synthesize dioInstance=_dioInstance;
- (id)fileName; // IMP=0x00000001000e53ac
@property(retain, nonatomic) NSArray *filePaths; // @synthesize filePaths=_filePaths;
- (void)fillModuleName; // IMP=0x00000001000f4cb8
@property(retain, nonatomic) NSDictionary *fonts; // @synthesize fonts=_fonts;
@property(nonatomic) unsigned long long format; // @synthesize format=_format;
- (_Bool)greaterThan:(id)arg1;  // IMP=0x0000000101730400
@property(retain, nonatomic) NSDictionary *hashSum; // @synthesize hashSum=_hashSum;
@property(retain, nonatomic) NSString *indexPath; // @synthesize indexPath=_indexPath;
@property(retain, nonatomic) NSString *installPath; // @synthesize installPath=_installPath;
- (_Bool)isEntry;   // IMP=0x00000001000fcc9c
- (_Bool)isEqual:(id)arg1;  // IMP=0x00000001017302a8
@property(nonatomic) _Bool isRAMBundle; // @synthesize isRAMBundle=_isRAMBundle;
@property(retain, nonatomic) NSMutableArray *lazyLoadJavaScriptPaths; // @synthesize lazyLoadJavaScriptPaths=_lazyLoadJavaScriptPaths;
@property(nonatomic) _Bool manualStopLoading; // @synthesize manualStopLoading=_manualStopLoading;
@property(retain, nonatomic) NSDictionary *metaInfo; // @synthesize metaInfo=_metaInfo;
@property(retain, nonatomic) NSString *moduleName; // @synthesize moduleName=_moduleName;
@property(retain, nonatomic) NSString *name; // @synthesize name=_name;
@property(nonatomic) long long size; // @synthesize size=_size;
@property(retain, nonatomic) NSString *status; // @synthesize status=_status;
@property(retain, nonatomic) NSString *timestamp; // @synthesize timestamp=_timestamp;
@property(retain, nonatomic) NSString *version; // @synthesize version=_version;
- (id)sm_requestConfigPathWithComponent:(id)arg1;   // IMP=0x0000000104664680
- (id)sm_requestConfigWithComponent:(id)arg1;   // IMP=0x0000000104664770
- (_Bool)verifyDependencyIntegral;  // IMP=0x0000000101730218
 
@end

dynamic analysis

Next is the hardest part of brain burning.

frida-trace

The Frida trace is used to track the running process of MRNBundle.

frida-trace -U -f com.meituan.imeituan -m '*[MRNBundle bundle*]'
# -U USB connection
# -f application process BundleID
# -m ObjC class method, supporting fuzzy matching

The Frida trace JavaScript hook function description is omitted.

Edit file:__ handlers__/MRNBundle/bundleFromPath_.js

Modification method: onEnter()

replace

log(`+[MRNBundle bundleFromPath:${args[2]}]`);

become

log(`+[MRNBundle bundleFromPath:${new ObjC.Object(args[2])}]`);

About new objc See the official website documentation for the usage of object.

Re execute the above Frida trace command, observe the log output, and find the path of the Bundle folder when the App is running.

Use the scp command to export the phone folder to the PC folder.

scp -P 2222 root@127.0.0.1:/var/mobile/Containers/Data/Application/8F33CFCF-0B24-4D9F-96B5-10BC0C8809D3/Documents/com.cipstorage.archiver/public/MRN/MRNBundlesV2Dio/* ./

DIO file format

As shown in the figure, it is obvious that each DIO file is an MRN Bundle. However, its format is not ZIP, and direct decompression is invalid. It seems that meituan App has taken security reinforcement measures, so it is necessary to continue to analyze its relevant ObjC code clues.

MTDIOBundle.h

Look back at the mrnbundle H declare the file and find the suspect point.

METDIOBundle *_dioInstance; // 168 = 0xa8

Mtdiobundle h. Keep an eye on_ filePaths and fileData are two properties and methods.

//
//     Generated by class-dump 3.5 (64 bit).
//
//     class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard.
//
 
#import "NSObject.h"
 
@class NSArray, NSString;
 
@interface METDIOBundle : NSObject
{
    NSArray *_filePaths;    // 8 = 0x8
    struct _Dio_Reader_context *_context;   // 16 = 0x10
    NSString *_bundlePath;  // 24 = 0x18
    unsigned long long _loganType;  // 32 = 0x20
}
 
- (void).cxx_destruct;  // IMP=0x00000001000f6df0
- (void)_fetchFilePaths;    // IMP=0x00000001000f26e8
@property(copy, nonatomic) NSString *bundlePath; // @synthesize bundlePath=_bundlePath;
- (id)contentsOfDirectory:(id)arg1; // IMP=0x00000001013e89d0
@property(nonatomic) struct _Dio_Reader_context *context; // @synthesize context=_context;
- (void)dealloc;    // IMP=0x00000001000f6ce0
- (void)extracFile:(id)arg1 to:(id)arg2 withCompletionHandler:(CDUnknownBlockType)arg3; // IMP=0x00000001013e8668
- (id)fileData:(id)arg1 error:(id *)arg2;   // IMP=0x00000001000f28a8
- (_Bool)fileExists:(id)arg1;   // IMP=0x00000001013e82c0
- (unsigned long long)fileLength:(id)arg1;  // IMP=0x00000001013e849c
@property(readonly, nonatomic) NSArray *filePaths; // @synthesize filePaths=_filePaths;
- (id)initWithPath:(id)arg1;    // IMP=0x00000001013e8234
- (id)initWithPath:(id)arg1 loganType:(id)arg2; // IMP=0x00000001000f1acc
- (_Bool)isDirectory:(id)arg1;  // IMP=0x00000001013e8784
@property(nonatomic) unsigned long long loganType; // @synthesize loganType=_loganType;
 
@end

Use cyrun to attach to the App runtime process, locate the memory mediobundle class instance, view the relevant information of these two elements, and finally get to the bottom of the matter.

  • _ filePaths describes the file list information in the package, including file name and offset value.
  • fileData holds file data.

Next, use writeToFile() to write the decrypted data into the local storage of the mobile phone, and then use the scp command to send the file back to the computer.

rn-hotel-mainlist

After a tedious search, find the hotel list page and take out the index JS, continue to patiently view a large number of compiled confused code and find a line of code.

return (u = t.NativeModules.MRNNetwork).request.apply(u, arguments)

The native module hosts the network protocol request and verifies the guess that Fiddler is used to analyze the fruitless guess at the beginning of the article.

We had to continue to analyze mrnnetwork h .

MRNNetwork.h

//
//     Generated by class-dump 3.5 (64 bit).
//
//     class-dump is Copyright (C) 1997-1998, 2000-2001, 2004-2013 by Steve Nygard.
//
 
#import "NSObject.h"
 
@interface MRNNetwork : NSObject
{
}
 
+ (id)bin2URL:(id)arg1; // IMP=0x0000000101762e7c
+ (void)invokeRequestFinishedCallback:(CDUnknownBlockType)arg1 config:(id)arg2 data:(id)arg3 response:(id)arg4 error:(id)arg5;  // IMP=0x0000000101760f68
+ (void)sendMapiRequestWithParams:(id)arg1 client:(id)arg2 onComplete:(CDUnknownBlockType)arg3; // IMP=0x0000000101761f6c
+ (void)sendRequestWithParams:(id)arg1 customCATInfo:(id)arg2 onComplete:(CDUnknownBlockType)arg3;  // IMP=0x00000001017614f4
 
@end

Mrnbundle H process is similar, continue to sacrifice Frida trace.

frida-trace -U -f com.meituan.imeituan -m '*[MRNNetwork *]'

Run the command and trigger the hotel search to the list page function to view the method call.

Edit file:

__handlers__/MRNNetwork/sendRequestWithParams_customCATI_19c1af7a.js

Modification method: onEnter()

Add code, print out, request parameters sent by RN to Native:

var request = new ObjC.Object(args[2])
log(request)

Hopper Disassembly

The macOS and Linux Disassembler

Hopper Disassembler, the reverse engineering tool that lets you disassemble, decompile and debug your applications.

Similar to Fiddler's authorization method, it can be used for free, but it is automatically closed every 30 minutes.

After startup, the imeituan Drag the app / imeituan file and a configuration prompt box will pop up. Press the default configuration and click OK to start decompilation.

Wait for the run to complete. Focus on analyzing the MRNNetwork sendRequestWithParams method and locate the NVTask shouldRequestInTunnel method. Once again, Frida trace dynamically tracks runtime data.

Finally understand the complete contract of the hotel list page network request.

Request Header key fields

Here, the repeated disassembly analysis process using Hopper and IDA and the clear request fields are omitted, and only the key and complex field meanings are listed.

pragma-unionid

give an example:

85aa34cea4e143c0a3fd2b0e4cef47e0a161103622439265119

From: [[nvnetworkconfigurator] unionid]

M-SHARK-TRACEID

give an example:

10285aa34cea4e143c0a3fd2b0e4cef47e0a161103622439265119182OfP1611745980663.913086PSoc6U

Meaning of split data:

fieldmeaningsource
10AppID[[NVNetworkConfigurator configurator] appId]
2Fixed value
85aa34cea4e143c0a3fd2b0e4cef47e0a161103622439265119unionid[[NVNetworkConfigurator configurator] unionId]
182OfPRandom six bit string[choose(NVTask)[0] randomStringWithLength: 6]]
1611745980663.913086time stamp[[NSDate date] timeIntervalSince1970 ] * 1000
PSoc6URandom six bit string[choose(NVTask)[0] randomStringWithLength: 6]]

Value range of generated random characters:

abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789

pragma-os

give an example:

"MApi 1.1 (mtscope 11.6.201 appstore; iPhone 12.4.5 iPhone7,2; a0d1)"

App version and mobile phone system related information (measured, fixed content can be transmitted to the server).

siua

give an example:

i2.0GkeXEeqXpKCVy7ROAkB5ZWtw2Q1caL1Q2GR1Y1dU5lTYlGGoHZEKEUHfX7vRU5SlGthNwvbaDtSg71rhKMHR07j8qQBh/s3W4r+N4os8t9HIfx8lY1xoCF2j2QTnYM0v8XKyVH/MEvzv9i87QE8lLANNworNB/VH0kCmQSk3of9CEea+LsSGR+JwIpJoosBcD8XUAIhRUIuhq8jLBzN7tIbxLhYsVyfpAY8IQjnQWh07e2VS3NdGz2v69wRU9AWqs+rsImhYfjOAhuYy5CB81YB+PQUNfc9wGuFdqEptqk7cQhyb6a0XmTypeWKLw0stkS99cF4XmbyBvAGKlAXDE5FKzCvFGb397oFRjxjTz/ys8v9rBEnvk0kM7mEt+BKauZ0AliMcK2j5pWGeXo0Ocu8KgDSgG8Uy5C+1lthHbrQwu4rUla5y1M1kAUjctkRVva7z7E02N3cROWt1BCeXW7AwcSfla/9jdVcPD1vOVrxmUQS9ain+2WCoUHHrh9BwxzZPDNahloMGp7oqlwoNDN3C9gIXMIMgYm3ExGqzOaCZOvIY7qrWmebovLNJqFJNgBZX45vNTkVen+xZZfch+E2BgsXW4modtafUEuBi+5rUP+88B69Iuzt/8Cmhvckm/oYvbPON57dDT8bts9fj79SsKp1VY/Su2zPvMVFblyY=

This field is optional and can not be transferred. The generation algorithm is to use the mobile phone system and device related information to construct the device fingerprint, generate the string, and then use two-layer encryption and base64 encoding.

The complete generation algorithm is located at:

-[SAKGuardDataProcessor collectFingerprintData]

mtgsig

give an example:

{
  "a0": "1.5",
  "a1": "6f50fb51-23ee-4173-ab33-6ee69ed0ef29",
  "a2": "ecf6a150692c92eec8ff25f3e2c1c10e6d39efc0",
  "a3": 2,
  "a4": 1611745980,
  "a5": "AGjHF6YqMr1g4dT4Hm9AvW0wE6eB8KhybiTSAylXNvMZHUlxKbMZVJWtGrf0Nz+sSNDTtnKCFhiRRIy7igvGcdrLCVhtt1x0SB2vNe3DOY2SnuIrHyUkDEdkT44VYuzOMrVAGwLdTNAstTXtuSafhGI9pDP3wTNyH14GMwOznMOtP+12qxkfC4hmmR1inIdWvQ+IrGzlg/KbwWKbS34N2z15ppwt7s+IJeUdYA==",
  "a6": 0,
  "d1": "150bddee5b271abe814c8fa42fe33bfe117f58f1"
}

The measured field must be transmitted, otherwise it will be intercepted by WAF(Web Application Firewall). And the generated content must be correct, otherwise it will also be intercepted.

Its generation logic lies in the following methods:

-[libCoreExtension signatureRequestForRequest:]

Finally, the following methods are called for encryption:

-[libCoreExtension yFkjiQTANujkdkct:]  

The method name is obviously generated randomly, so it is uncertain whether it will change in the future. So remember to analyze from the entry method. Signaturerequesforrequest

yFkjiQTANujkdkct input analysis

Request method name + Space + request URL + Space + request URL parameter
(Parameter press key Sort)

give an example:

GET /coresearch/v1/selectList/fast __reqTraceID=351BE837-69C0-43B5-9CB0-9CFFFE3873EC&cate=20&cateId=20&childAges=&ci=10&cityId=10&client=iphone&cn_pt=RN&endDay=20210126&gps_cityid=-1&isPrefetch=1&keyword=&language=en&msid=BC6CC6CF-6680-4694-B0FA-17A2F889B61F1611640848323419&numberOfAdults=1&numberOfChildren=0&osversion=12.4.5&platform_business=meituan&roomCount=1&sort=smart&source=mt&sourceType=hotel&startDay=20210126&token=RBquN9MXBHXbsmM1twAwnl2O9QEAAAAAhgwAADGp1bzWy18zLs9BlW1vN4ll-P9cm36JAHLiHzqEBpvt7u9J5SWqV8ujseh3bfzWUQ&userid=3140000433&utm_campaign=AgroupBgroupD200Ghomepage_category3_20__a1__c__e123148H0&utm_content=000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A161103622439265119&utm_medium=iphone&utm_source=AppStore&utm_term=11.6.201&uuid=000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A161103622439265119&version_name=11.6.201

It seems that the hotel list query request only uses the GET method. When the request mode is POST, other parameters need to be added, which are omitted without in-depth analysis.

yFkjiQTANujkdkct return value analysis

That is, the above mtgsig JSON format can be directly sent in the Request Header.

Because its data is generated based on URL parameters, mtgsig values are different when URL parameters are different. Therefore, this field cannot be simulated and fixed, and it needs to be calculated dynamically by calling the above algorithm.

URL key field

Example of full URL:

https://apihotel.meituan.com/coresearch/HotelSearch?utm_campaign=AgroupBgroupD200Ghomepage_category3_20__a1__c__e123148H0&utm_content=000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A161103622439265119&version_name=11.6.201&__reqTraceID=955C9000-9F1D-4366-A6E2-EF8CF5C875BE&utm_source=AppStore&flagshipFilter=1&newcate=1&category=5&remoteCenter=&utm_term=11.6.201&roomCount=1&startDay=20210127&uuid=000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A161103622439265119&sort=smart&limit=30&cate=20&client=iphone&wifiList=%255B%257B%2522isConnected%2522%253Atrue%252C%2522strength%2522%253A0%252C%2522name%2522%253A%2522wifi%2522%252C%2522address%2522%253A%252292%253A74%253A87%253Aa8%253A4d%253A01%2522%257D%255D&platform_business=meituan&numberOfChildren=0&childAges=&attr_28=129&osversion=12.4.5&propagateData=&tipBoothId=94001296&queryRewrite=rewrite&q=&gps_cityid=-1&cityId=10&utm_medium=iphone&cn_pt=RN&offset=0&numberOfAdults=1&accommodationType=1&remoteJumpEnabled=true&inputKeyword=&sourceType=hotelSearch&steParam=&ci=10&cateId=20&msid=D210226D-D207-483A-A4C8-01699E25EDD81611745886186903&token=RBquN9MXBHXbsmM1twAwnl2O9QEAAAAAhgwAADGp1bzWy18zLs9BlW1vN4ll-P9cm36JAHLiHzqEBpvt7u9J5SWqV8ujseh3bfzWUQ&areaName=%E5%85%A8%E5%9F%8E&searchKeywordSource=&language=en&hotel_queryid=000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A1611036224392651191611745914.847&userLocationType=0&endDay=20210127&latlng=&userid=3140000433

Convert to JSON format for easy reading:

{
  "utm_campaign": "AgroupBgroupD200Ghomepage_category3_20__a1__c__e123148H0",
  "utm_content": "000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A161103622439265119",
  "version_name": "11.6.201",
  "__reqTraceID": "955C9000-9F1D-4366-A6E2-EF8CF5C875BE",
  "utm_source": "AppStore",
  "flagshipFilter": "1",
  "newcate": "1",
  "category": "5",
  "remoteCenter": "",
  "utm_term": "11.6.201",
  "roomCount": "1",
  "startDay": "20210127",
  "uuid": "000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A161103622439265119",
  "sort": "smart",
  "limit": "30",
  "cate": "20",
  "client": "iphone",
  "wifiList": "%5B%7B%22isConnected%22%3Atrue%2C%22strength%22%3A0%2C%22name%22%3A%22wifi%22%2C%22address%22%3A%2292%3A74%3A87%3Aa8%3A4d%3A01%22%7D%5D",
  "platform_business": "meituan",
  "numberOfChildren": "0",
  "childAges": "",
  "attr_28": "129",
  "osversion": "12.4.5",
  "propagateData": "",
  "tipBoothId": "94001296",
  "queryRewrite": "rewrite",
  "q": "",
  "gps_cityid": "-1",
  "cityId": "10",
  "utm_medium": "iphone",
  "cn_pt": "RN",
  "offset": "0",
  "numberOfAdults": "1",
  "accommodationType": "1",
  "remoteJumpEnabled": "true",
  "inputKeyword": "",
  "sourceType": "hotelSearch",
  "steParam": "",
  "ci": "10",
  "cateId": "20",
  "msid": "D210226D-D207-483A-A4C8-01699E25EDD81611745886186903",
  "token": "RBquN9MXBHXbsmM1twAwnl2O9QEAAAAAhgwAADGp1bzWy18zLs9BlW1vN4ll-P9cm36JAHLiHzqEBpvt7u9J5SWqV8ujseh3bfzWUQ",
  "areaName": "the whole city",
  "searchKeywordSource": "",
  "language": "en",
  "hotel_queryid": "000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A1611036224392651191611745914.847",
  "userLocationType": "0",
  "endDay": "20210127",
  "latlng": "",
  "userid": "3140000433"
}

[[SAKEnvironment environment] commonParameter]

The method calculates the following parameters:

{
    "userid" : "31410000433",
    //userid, no need to explain
    "ci" : 10,
    //Curing, no explanation
    "language" : "en",
    //Curing, no explanation
    "utm_campaign" : "AgroupBgroupD200H0",
    //Curing, no explanation
    "utm_medium" : "iphone",
    //Curing, no explanation
    "utm_source" : "AppStore",
    //Curing, no explanation
    "utm_term" : "11.6.201",
    //Curing, no explanation
    "version_name" : "11.6.201",
    //Curing, no explanation

    "__reqTraceID" : "70184915-D47F-4F1C-8597-14FC276C3ADB",
    //Each time it changes, it is a uuid.
    "msid" : "BC6CC6CF-6680-4694-B0FA-17A2F889B61F1611566966630356",
    //The first half part is SessionId, which is presumably generated when the APP is started or distributed by the server, and then solidified and unchanged. The second half is the timestamp 161156696630356
    "utm_content" : "000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A161103622439265119",
    //Same as uuid below
    "uuid" : "000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A161103622439265119",
    //0000000000000 + unionId mentioned above
}

[MRNBaseModel addUserParams:]

The method calculates the following parameters:

token and userID, presumably user credentials.

cy# [[choose(SAKBaseModel)[0] user] token]
@"RBquN9MXBHXbsmM1twAwnl2O9QEAAAAAhgwAADGp1bzWy18zLs9BlW1vN4ll-P9cm36JAHLiHzqEBpvt7u9J5SWqV8ujseh3bfzWUQ"
 
cy# [[choose(SAKBaseModel)[0] user] userID]
@3140000433

wifiList

The measured optional field, which can not be transmitted, represents the Wi Fi information currently connected to the user's mobile phone.

give an example:

[
  {
    "isConnected":true,
    "strength":0,
    "name":"wifi",
    "address":"12:34:56:78:90:12"
  }
]

hotel_queryid

give an example:

000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A1611036224392651191611745914.847

Format:

0000000000000 + unionId + time stamp

Other non key parameters are omitted and can be roughly divided into two types:

  • Hotel query criteria parameters
  • Non critical fields that can be filled

Making crawler scripts

It can be seen from the above analysis that the generation methods of MTG SIG and siua are extremely complex, so a new method is adopted. Continue to use frida, a powerful tool, to directly call the corresponding encryption function in the App from the outside for calculation.

  • Create a new folder and execute npm init -y to initialize an empty project.
  • Execute npm install --save frida to install dependencies.
  • Implement the following code and keep it in a separate file. Test. Is used here JS as an example.
module.exports = (async (frida) => {
  const probeRealm = async (device, target, realm) => {
    return device.attach({ target, realm })
    .then(function (session) {
      console.log('attached:', session)
      return session.createScript(`
        var core = ObjC.classes.libCoreExtension.alloc()
        var SAKGuardCore = ObjC.classes.SAKGuardCore
 
        rpc.exports = {
          //-[libCoreExtension yFkjiQTANujkdkct:]
          mtgsig: function (data) {
            data = ObjC.classes.NSString.stringWithString_(data); // Convert to NSString object
 
            return core.yFkjiQTANujkdkct_(data.dataUsingEncoding_(4)).toString() // Convert to NSData object, pass in encryption method, convert return value to string and return
          },
          siua: function (data) {
            // send(data)
 
            var a = ObjC.classes.NSData.alloc().initWithBase64EncodedString_options_(data, 0) // Get the native content by solving Base64
 
            var b = ObjC.classes.NSString.alloc()
 
            var c = b.initWithData_encoding_(a, 4) // Press utf8 to decode and get NSString
 
            var p = Memory.allocUtf8String(c.toString())
 
            return SAKGuardCore.packSiuaData_len_(p, a.length()).toString()
          }
        };
      `)
    })
    .then(async function (script) {
      console.log('script created:', script)
 
      script.message.connect(message => {
        console.log('[*] Message:', message);
      });
 
      await script.load()
 
      console.log('script loaded:', script)
 
      return script.exports
    })
  }
 
  const findMeiTuanApp = async (device) => {
    const applications = await device.enumerateApplications();
 
    const app = applications.find((app) => app.identifier === 'com.meituan.imeituan')
 
    if (!app) {
      throw new Error("Can not find Meituan ...")
    }
 
    return probeRealm(device, app.pid, 'native') // Attach to specified appid
  }
 
  return await frida
    .getUsbDevice()       // Find USB devices connected to this computer
    .then(findMeiTuanApp) // Find meituan's App
})(require('frida'))
  • Create call entry file
require('./test.js').then(async ({mtgsig, siua}) => {
    // 1. Generate the URL to request
    // 2. Sort the parameters in the URL by key
    // 3. Generate a string like ` ${METHOD} ${URL_PATH} ${SORTED_QUERY} '
    // var sig = await mtgsig(something) / / obtain the data after signing the request URL and use the data as the Request Header mtgsig field.
    // Finally, after sending the request, you should be able to get the data returned by the server.
}, (e) => {
    console.error('Init Fail!', e)
})
  • Run the file to see the effect. Done!

The End

The hotel prices returned by the server (or background interface) are different for users with different identities (not logged in, logged in, new users, old users, different levels).

Therefore, the idea of making a hotel list page crawler from the front end is blocked.

Meituan first controls the security strategy from the link of login account. Similarly, there are air travel.

The following figure shows the hotel list data and prices obtained by "logged in user (left)" and "logged in user (right)", which can clearly see the difference between the two.

The figure below shows the room type policy data and prices specific to a hotel, but there are also differences.

Topics: iOS