Tech:美团iOS逆向工程分析

by ChenQi

《Tech:美团Android逆向工程分析》,九个月前曾经对美团Android App做过一次简单拆包分析,主要是了解美团MRN工程结构的实现,以及观察各业务线的模块设计划分和代码量占比情况。并未具体分析源码逻辑实现。

最近QIN同学对美团iOS App做了一次更加详细的逆向分析。

[转载] 原地址:https://chenqi.app/Meituan-iOS-Reverse-Engineering/ 原作者:ChenQi

静态分析

美团App iOS 安装包
版本号 11.6.201
发布时间 2021.01.18

Fiddler

常规操作,首先使用Fiddler进行HTTP(S)网络代理抓包,没有获得任何明显的线索。猜测美团App使用了Native层实现网络通讯功能,负责加解密,压缩解压缩等常见处理,属于常见技术方案,携程CRN亦是如此。后续分析印证了该猜测。

Cydia

iPhone越狱,并在Cydia App中安装 Frida Cyrun Cycript 这几个包,后续会用到。

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.

App提交至App Store后,经Apple官方加密,生成供用户下载的IPA文件。该文件直接解压缩是密文,难以分析。所以需要通过越狱手机环境,使用frida砸壳,获得解密后的原始IPA文件(IPA = ZIP)。

iproxy 2222 22 # 开启USB端口转发,保持进程存在,勿关闭。
./dump.py com.meituan.imeituan # 开始砸壳,将原始IPA文件导出至电脑本地。
unzip ./美团.ipa # 解压缩至 ./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.

从原始IPA解压后的 Mach-O 二进制文件,还原出 Objective-C 类声明头文件。

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

MRNBundle.h

使用VSCode打开上述使用 class-dump 生成的头文件工程。已知美团机票酒店等业务使用 MRN(Meituan React Native) 技术实现,所以直接搜索 RN 关键字。

先从 MRNBundle.h 下手。

//
//     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 = 0xaNSString *_installPath; // 16 = 0x10NSString *_bizName; // 24 = 0x18NSString *_moduleName;  // 32 = 0x20NSString *_version; // 40 = 0x28NSArray *_dependencies; // 48 = 0x30NSString *_name;    // 56 = 0x38NSString *_timestamp;   // 64 = 0x40unsigned long long _bundleType; // 72 = 0x48NSMutableArray *_lazyLoadJavaScriptPaths;   // 80 = 0x50NSDictionary *_fonts;   // 88 = 0x58NSDictionary *_hashSum; // 96 = 0x60NSDictionary *_metaInfo;    // 104 = 0x68NSString *_RNVersion;   // 112 = 0x70unsigned long long _format; // 120 = 0x78NSString *_status;  // 128 = 0x80long long _size;    // 136 = 0x88NSArray *_dependentBundles; // 144 = 0x90NSString *_indexPath;   // 152 = 0x98NSArray *_filePaths;    // 160 = 0xa0METDIOBundle *_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

动态分析

接下来是最辛苦烧脑的部分。

frida-trace

祭出 frida-trace 跟踪 MRNBundle 的运行过程。

frida-trace -U -f com.meituan.imeituan -m '*[MRNBundle bundle*]'
# -U USB连接
# -f 应用进程 BundleID
# -m ObjC 类方法,支持模糊匹配

frida-trace JavaScript Hook 功能介绍省略。

编辑文件:__handlers__/MRNBundle/bundleFromPath_.js

修改方法:onEnter()

替换

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

变成

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

关于 new ObjC.Object 的用法,详见官网文档。

重新执行上述frida-trace命令,观察日志输出,找到App运行时Bundle文件夹路径。

使用scp命令,将手机文件夹导出至电脑文件夹。

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文件格式

如图所示,很明显每个DIO文件就是一个MRN Bundle。但是其格式并非ZIP,直接解压缩无效。看来美团App进行了安全加固相关措施,因此需要继续分析与其相关的ObjC代码线索。

MTDIOBundle.h

回看上述 MRNBundle.h 声明文件,找到嫌疑点。

METDIOBundle *_dioInstance; // 168 = 0xa8

顺藤摸瓜 MTDIOBundle.h,盯住 _filePathsfileData 这两个属性和方法。

//
//     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 = 0x8struct _Dio_Reader_context *_context;   // 16 = 0x10NSString *_bundlePath;  // 24 = 0x18unsigned 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

使用 cyrun 附加到App运行时进程,定位内存 METDIOBundle 类实例,查看这两个元素的相关信息,终于水落石出。

  • _filePaths 描述包中文件列表信息,包括文件名,偏移值。
  • fileData 持有文件数据。

接下来使用 writeToFile() 将解密数据写入手机本地存储,再使用 scp 命令将文件传回电脑。

rn-hotel-mainlist

经过一番枯燥繁琐的检索,找到酒店列表页,取出其中 index.js ,继续耐心查看大量编译混淆后的代码,找到一行代码。

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

原生模块托管了网络协议请求,验证了文章开头使用Fiddler分析无果的猜测。

只好继续分析 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 流程类似,继续祭出 frida-trace

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

运行命令,并触发酒店搜索至列表页功能, 查看方法调用。

编辑文件:

__handlers__/MRNNetwork/sendRequestWithParams_customCATI_19c1af7a.js

修改方法:onEnter()

增加代码,打印输出,RN发送给Native的请求参数:

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

Hopper 反汇编

The macOS and Linux Disassembler

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

与Fiddler授权方式类似,可以免费使用,但每隔30分钟自动关闭一次。

启动后,将 imeituan.app/imeituan 文件拖入, 弹出配置提示框,按默认配置,点 OK 开始反编译。

等待运行完成,重点分析 MRNNetwork sendRequestWithParams 方法,定位到 NVTask shouldRequestInTunnel 方法。再次祭出 frida-trace 动态跟踪运行时数据。

终于看懂了酒店列表页网络请求的完整契约。

Request Header 关键字段

这里省略使用Hopper和IDA反复进行反汇编分析过程,以及一目了然的请求字段部分,仅罗列关键复杂的字段含义。

pragma-unionid

举例:

85aa34cea4e143c0a3fd2b0e4cef47e0a161103622439265119

源于:[[NVNetworkConfigurator configurator] unionId]

M-SHARK-TRACEID

举例:

10285aa34cea4e143c0a3fd2b0e4cef47e0a161103622439265119182OfP1611745980663.913086PSoc6U

拆分各段数据含义:

字段 含义 来源
10 AppID [[NVNetworkConfigurator configurator] appId]
2 固定值
85aa34cea4e143c0a3fd2b0e4cef47e0a161103622439265119 unionid [[NVNetworkConfigurator configurator] unionId]
182OfP 随机六位字符串 [choose(NVTask)[0] randomStringWithLength: 6]]
1611745980663.913086 时间戳 [[NSDate date] timeIntervalSince1970 ] * 1000
PSoc6U 随机六位字符串 [choose(NVTask)[0] randomStringWithLength: 6]]

生成随机字符的取值范围:

abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789

pragma-os

举例:

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

App版本和手机系统相关信息(实测可以向服务端传入固定内容)。

siua

举例:

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

实测该字段可选,可以不传。其生成算法是,使用手机系统和设备相关信息,构造设备指纹,生成字符串,再通过两层加密,base64编码形式。

完整的生成算法位于:

-[SAKGuardDataProcessor collectFingerprintData]

mtgsig

举例:

{"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"
}

实测该字段必传,否则会被 WAF(Web Application Firewall) 拦截。以及生成内容必须正确,否则也会被拦截。

其生成逻辑位于以下方法:

-[libCoreExtension signatureRequestForRequest:]

最终调用以下方法进行加密:

-[libCoreExtension yFkjiQTANujkdkct:]

该方法名显然是随机生成,因此不确定未来是否变化。所以记住要从入口方法进行分析。即:signatureRequestForRequest

yFkjiQTANujkdkct 入参分析

请求方法名 + 空格 + 请求URL + 空格 + 请求URL参数
(参数按key进行排序)

举例:

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

酒店列表查询请求似乎只用到了GET方式。当请求方式为POST时,还需添加其他参数,未深入分析,省略。

yFkjiQTANujkdkct 返回值分析

即上述 mtgsig JSON格式,可直接置于 Request Header 中发送。

由于其数据基于URL参数生成,因此当URL参数不同,mtgsig 值不同。所以该字段不能模拟固定,需要调用上述算法动态计算得出。

URL 关键字段

完整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

转为JSON格式,方便阅读:

{"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": "全城","searchKeywordSource": "","language": "en","hotel_queryid": "000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A1611036224392651191611745914.847","userLocationType": "0","endDay": "20210127","latlng": "","userid": "3140000433"
}

[[SAKEnvironment environment] commonParameter]

该方法计算得出以下参数:

{"userid" : "31410000433",//userid,无须解释"ci" : 10,//固化,无须解释"language" : "en",//固化,无须解释"utm_campaign" : "AgroupBgroupD200H0",//固化,无须解释"utm_medium" : "iphone",//固化,无须解释"utm_source" : "AppStore",//固化,无须解释"utm_term" : "11.6.201",//固化,无须解释"version_name" : "11.6.201",//固化,无须解释"__reqTraceID" : "70184915-D47F-4F1C-8597-14FC276C3ADB",//每次均变化,是一个uuid。"msid" : "BC6CC6CF-6680-4694-B0FA-17A2F889B61F1611566966630356",//前半部分是SessionId,推测是APP启动时生成,或服务端下发,之后固化不变。后半部分是时间戳1611566966630356"utm_content" : "000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A161103622439265119",//同下面的uuid"uuid" : "000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A161103622439265119",//0000000000000+上面提及的unionId
}

[MRNBaseModel addUserParams:]

该方法计算得出以下参数:

token 和 userID,推测应为用户凭证。

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

wifiList

实测可选字段,可以不传,代表用户手机当前连接的Wi-Fi信息。

举例:

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

hotel_queryid

举例:

000000000000085AA34CEA4E143C0A3FD2B0E4CEF47E0A1611036224392651191611745914.847

格式:

0000000000000 + unionId + 时间戳

其他非关键参数省略,大致分为两种:

  • 酒店查询条件参数
  • 可以固化填充的非关键字段

制作爬虫脚本

上述分析可见,mtgsigsiua 的生成方式极其复杂,因此采用一种新方式实现。继续借助 frida 这一强大的工具,实现从外部直接调用 App 中相应的加密函数进行计算。

  • 新建文件夹,执行 npm init -y 初始化一个空项目。
  • 执行 npm install --save frida ,安装依赖项。
  • 实现以下代码,保持至单独文件。此处以 test.js 为例。
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.SAKGuardCorerpc.exports = {//-[libCoreExtension yFkjiQTANujkdkct:]mtgsig: function (data) {data = ObjC.classes.NSString.stringWithString_(data); // 转换成 NSString 对象return core.yFkjiQTANujkdkct_(data.dataUsingEncoding_(4)).toString() // 转换为 NSData 对象,并传入 加密方法, 将返回值 转换为字符串后返回},siua: function (data) {// send(data)var a = ObjC.classes.NSData.alloc().initWithBase64EncodedString_options_(data, 0) // 解 Base64 得到原生内容var b = ObjC.classes.NSString.alloc()var c = b.initWithData_encoding_(a, 4) // 按 utf8, 解码, 得到 NSStringvar 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 美团 ...")}return probeRealm(device, app.pid, 'native') // 附加到 指定 appid}return await frida.getUsbDevice()       // 查找连接到本机的 USB 设备.then(findMeiTuanApp) // 查找 美团 的App
})(require('frida'))
  • 创建调用入口文件
require('./test.js').then(async ({mtgsig, siua}) => {// 1. 生成要请求的URL// 2. 将URL中的参数, 按key进行排序// 3. 生成 `${METHOD} ${URL_PATH} ${SORTED_QUERY}`   这样的字符串// var sig = await mtgsig(something)  //获得请求URL签名后的数据, 并将该数据做为Request Header mtgsig 字段。// 最后发送请求, 应该就能拿到服务端返回的数据。
}, (e) => {console.error('Init Fail!', e)
})
  • 运行该文件,查看效果。Done!

The End

不同身份的用户(未登录,已登录,新用户,老用户,等级不同),服务端(或后台接口)返回的酒店价格均有所不同。

因此从前端入手制作酒店列表页爬虫的思路受阻。

美团先从登录账号这一环节控制安全防范策略。与之类似的还有航旅纵横。

下图为“未登录用户(左)”和“已登录用户(右)”获取到的酒店列表数据和价格,可以明显看出两者的差别。

下图为具体到某一酒店的房型政策数据和价格,也存在差异。

美团iOS逆向工程分析相关推荐

  1. 美团 iOS 工程 zsource 命令背后的那些事儿

    zsource 命令是什么? 美团 App 在 2015 年就已经基于 CocoaPods 完成了组件化的工作.在组件化的改造过程中,为了能够加速整体工程的构建速度,我们对需要集成进美团 App 的组 ...

  2. iOS逆向工程- 工具详解

    前言 一.逆向工程的要求 具备丰富的 iOS 开发经验 最好能非常熟悉 iOS 设备的硬件构成,iOS 系统的运行原理. 拿到任意一个 App 之后能够大致推断出它的项目规模和使用的技术,比如它的MV ...

  3. iOS逆向工程-工具篇

    对于初开始学习iOS逆向工程的人来说,实现一个tweak可以算是入门逆向工程了.当然了,可能你现在还不知道tweak是什么.简单来说,你可以把一个tweak当作某一个app的一个插件(类似于浏览器广告 ...

  4. 【网络安全】一款针对Flutter的逆向工程分析工具

    关于reFlutter reFlutter是一款功能强大的逆向工程分析工具,该工具主要针对的是Flutter应用程序.该框架使用了已编译且重新封装的Flutter库来帮助广大研究人员对Flutter应 ...

  5. 初涉iOS逆向工程:免越狱修改微信(外观篇)

    点击上方"iOS开发",选择"置顶公众号" 关键时刻,第一时间送达! 美国学者埃德加·戴尔(Edgar Dale)1946年提出了"学习金字塔&quo ...

  6. iOS逆向工程-静态分析

    最近在学习IOS逆向工程,查看网络上的资料也不是太多,边学边总结一下. 首先学习资料: 念茜(大神)的博客: http://nianxi.net <ios应用逆向工程 分析与实战> --- ...

  7. iOS - 逆向工程

    公司最近搞得物联网项目,很注重安全,对于我这种架构师,当然考虑到APP使用上的安全.分享一篇逆向基础文章. 1.ios逆向工程指的是在软件层面上进行逆向分析的一个过程.如果想要达到对ios软件较强的逆 ...

  8. 技术干货 | iOS逆向工程

    前言 1.ios逆向工程指的是在软件层面上进行逆向分析的一个过程.如果想要达到对ios软件较强的逆向分析能力,最好能非常熟悉ios设备的硬件构成.ios系统的运行原理,还要具备丰富的ios开发经验,比 ...

  9. 安全技术大系iOS取证分析

    <安全技术大系iOS取证分析> 基本信息 作者: (美)莫里西(Morrissey,S.) [作译者介绍] 译者: 郭永健 韩晟 钟琳 出版社:电子工业出版社 ISBN:978712117 ...

  10. block在美团iOS的实践

    说到block,相信大部分iOS开发者都会想到retain cycle或是__block修饰的变量. 但是本文将忽略这些老生常谈的讨论,而是将重点放在美团iOS在实践中对block的应用,希望能对同行 ...

最新文章

  1. Windows 7的CMD中 Telnet 无法执行的解决办法
  2. ChIP-seq基本分析流程
  3. 关于ES、PES、PS以及TS码流
  4. mysql补充(3)优化sql语句查询常用的30种方法
  5. 成功解决WARNING:tensorflow:From :read_data_sets (from tensorflow.contrib.learn.python.learn.
  6. MySQL 高可用架构 之 MHA (Centos 7.5 MySQL 5.7.18 MHA 0.58)
  7. JSP连接数据库 - MySQL
  8. UNIX再学习 -- 标准I/O
  9. 【流媒体服务器的搭建】2. 源码编译安装ffmpeg
  10. java并发编程之美-阅读记录2
  11. tomcat的日志文件权限与启动用户的权限不一致
  12. MPEG中面向沉浸式视觉体验的标准化活动
  13. AJAX实现图片文件上传
  14. 创建用户的种类与区分
  15. Web前端开发初学者十问集锦(4)
  16. milantgh php安全,PHP漏洞全解(一)-PHP网站的安全性问题
  17. ubuntu服务器系统进入安全模式,ubuntu bios 无法进入安全模式
  18. 2018携程实习生大数据分析笔试练习题
  19. [POJ3537]Crosses and Crosses
  20. python龙旋风图形代码_python-如何在数据库中使龙卷风请求成为原...

热门文章

  1. Selenium+超级鹰进行识别滑动操作
  2. 推荐6个实用的Vue模板
  3. MarkDown的下载、安装和基础使用
  4. 时序数据获取 | Python实现时间序列数据集获取
  5. 简书的css排版,浅谈前端(WEB)排版
  6. 第十四篇 积分器和积分运算电路
  7. emf(Enhanced MetaFile)图片格式——在印刷工业中应用于Windows操作系统的文件格式
  8. 电子商务B2C之未来-刘爽
  9. unity c#斗地主算法计算牌型
  10. Hadoop原理及架构