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:
field | meaning | source |
---|---|---|
10 | AppID | [[NVNetworkConfigurator configurator] appId] |
2 | Fixed value | |
85aa34cea4e143c0a3fd2b0e4cef47e0a161103622439265119 | unionid | [[NVNetworkConfigurator configurator] unionId] |
182OfP | Random six bit string | [choose(NVTask)[0] randomStringWithLength: 6]] |
1611745980663.913086 | time stamp | [[NSDate date] timeIntervalSince1970 ] * 1000 |
PSoc6U | Random 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.