Lupinus 发表于 2018-10-9 17:07

从去哪儿看React Native安卓App逆向

本帖最后由 Lupinus 于 2018-10-11 09:45 编辑

#### 去哪儿

App:去哪儿客户端

versionCode="172"

versionName="8.8.5"

去哪儿是采用基于React Native技术的QRN开发,使用QRN Ext做路由转发,QHotDogNetWork做网络请求。


##### 先分析自定义App

如果没有React Native的开发基础,需要搭建一个React Native开发环境,反编译hello world来分析React Native App的特征。关于React Native开发环境的搭建可以参考[这里](https://blog.csdn.net/xtylgra/article/details/79807440)。运气不好可能会遇到比较多的问题,多百度总是可以解决的。

成功运行App以后,打包App,分别用jadx和apktool反编译自己打包的apk,在assets目录下,可以看到index.android.bundle文件,App的入口是MainActivity,其实现是空,继承自Facebook包里的一个类。结合前面的开发环境搭建可以知道,我们实际上写的是JS,综合来看只有index.android.bundle这个文件比较可疑。打开发现是压缩了的js,没关系,[美化](https://tool.lu/js/)一下,还是可以阅读的,稍微看一下,可以发现前面部分是React Native的基础框架,后面部分,跟之前的App.js的代码相似度非常高,基本可以确定这个就是真正的逻辑代码了。

原App.js

```

const instructions = Platform.select({

ios: 'Press Cmd+R to reload,\n' + 'Cmd+D or shake for dev menu',

android:

    'Double tap R on your keyboard to reload,\n' +

    'Shake or press menu button for dev menu',

});

type Props = {};

export default class App extends Component<Props> {

render() {

    return (

      <View style={styles.container}>

      <Text style={styles.welcome}>Welcome to React Native!</Text>

      <Text style={styles.instructions}>To get started, edit App.js</Text>

      <Text style={styles.instructions}>{instructions}</Text>

      </View>

    );

}

}

const styles = StyleSheet.create({

container: {

    flex: 1,

    justifyContent: 'center',

    alignItems: 'center',

    backgroundColor: '#F5FCFF',

},

welcome: {

    fontSize: 20,

    textAlign: 'center',

    margin: 10,

},

instructions: {

    textAlign: 'center',

    color: '#333333',

    marginBottom: 5,

},

});

```



获取的代码:

```

                v = y.Platform.select({

                        ios: "Press Cmd+R to reload,\nCmd+D or shake for dev menu",

                        android: "Double tap R on your keyboard to reload,\nShake or press menu button for dev menu"

                }),

                p = (function(t) {

                        function n() {

                              return (0, o.

                        default)(this, n), (0, u.

                        default)(this, (0, c.

                        default)(n).apply(this, arguments))

                        }

                        return (0, s.

                default)(n, t), (0, l.

                default)(n, [{

                              key: "render",

                              value: function() {

                                        return f.

                              default.createElement(y.View, {

                                                style: h.container

                                        }, f.

                              default.createElement(y.Text, {

                                                style: h.welcome

                                        }, "Welcome to React Native!"), f.

                              default.createElement(y.Text, {

                                                style: h.instructions

                                        }, "To get started, edit App.js"), f.

                              default.createElement(y.Text, {

                                                style: h.instructions

                                        }, v))

                              }

                        }]), n

                })(f.Component);

      e.

default = p;

      var h = y.StyleSheet.create({

                container: {

                        flex: 1,

                        justifyContent: 'center',

                        alignItems: 'center',

                        backgroundColor: '#F5FCFF'

                },

                welcome: {

                        fontSize: 20,

                        textAlign: 'center',

                        margin: 10

                },

                instructions: {

                        textAlign: 'center',

                        color: '#333333',

                        marginBottom: 5

                }

      })

}, 435, );

```



关于React Native的参考:

(https://www.jianshu.com/p/02be425d7b13)

(https://blog.csdn.net/MegatronKings/article/details/51114278)

(https://blog.csdn.net/MegatronKings/article/details/51138499)

(https://blog.csdn.net/MegatronKings/article/details/51195110)

(https://blog.csdn.net/MegatronKings/article/details/51339639)



##### 再看去哪儿

分析去哪儿这种App,虽然知道抓包可能抓不出什么,但是还是可以看看,有什么有用的信息没。这里没有用Charles和fiddler之类的,直接用的Packet Capture,果然request body和response body都是加密的,这里有一个特征,request body的开头都是a1708,稍微记一下。把去哪儿拖到jadx看看,发现基本没有Activity,但是找到了com.mqunar.network.NetRequestManager,写个xposed,hook一下request方法,发现请求还真的是走的这里,跟抓包的结果一摸一样。

在lib文件夹下面,找到libq_lib_rnqp.so,这个是去哪儿的静态资源离线方案,把这个so解压,里面的assets文件夹,全是qp和qpmd5文件,随机打开一个QP文件,可以发现前面是一个类似json字符串的东西,里面:

```

{

      "files": [{

                "url": "https://rn.qunar.com/packages/f_flight_search_rn_android/index.bundle",

                "md5": "lO+fevWF8DT4S4lV1FE/vj4Rpwv3z0I9sM9HN93ZLwwEaw5pE01vW/a94Q==",

                "sl": "0,477450"

      }, {

                "url": "https://rn.qunar.com/packages/f_flight_search_rn_android/index.bundle.meta",

                "md5": "BZRwzup822tj5sA0O/IterLMCgmJmgIVQeSGI2oSGcKdkSNxFF4pxAWf6g==",

                "sl": "477450,21"

      }, {

                "url": "https://s.qunarzz.com/flight_search_rn/fonts/0.0.16/f_flight_search_rn_font.ttf",

                "md5": "PyIDorrKuPUsyRoSnjCkqYWGgUIJ8b8evQ7nOB5rxWtlTAJ7bnkua/n32g==",

                "sl": "477471,10320"

      }, {

                "url": "https://s.qunarzz.com/route_service_rn/bgBody.png",

                "md5": "AhojMss6hxmBo39WS3ikUxI7gKykolUSSzT9DBGn5vRxbU+gNRH2bM9FqQ==",

                "sl": "487791,736"

      }, {

                "url": "https://s.qunarzz.com/flight_search_rn/img/FOTAPopLogo.png",

                "md5": "AWq74ZbLcpb19+FUteBPPuJ2X2PC4GFFzwZtii+p+SguUpP+Im0nviULsg==",

                "sl": "488527,3512"

      }, {

                "url": "https://s.qunarzz.com/flight_search_rn/img/FOTAYixuan.png",

                "md5": "wkRBXC16U7lu1/pgEMQ1inI5H3ZZyGkO/6XME61LRVdCdrfifwiaekSQ/A==",

                "sl": "492039,342"

      }, {

                "url": "https://s.qunarzz.com/flight_search_rn/img/atom_flight_pay_booking.png",

                "md5": "Qp/7XxZ424PMSpQd+n4R5tDcyLbtIj2zASr1neJdb7pehBIobsXYTGQdNw==",

                "sl": "492381,994"

      }, {

                "url": "https://s.qunarzz.com/flight_search_rn/img/atom_flight_down_arrow.png",

                "md5": "Vpx0GnB7CXrcJ03rpiUfz5T9lNWbPANmYhZTSosK9xsnSxQQ5QIKSy8GTQ==",

                "sl": "493375,262913"

      }, {

                "url": "https://s.qunarzz.com/flight_search_rn/img/cry_camel.png",

                "md5": "ne3OZ535RNS34nI9LDf/C1gMTRQtaldSrrHjDQ+pEaOlFZE3qQ2ezJxT9g==",

                "sl": "756288,4551"

      }, {

                "url": "https://s.qunarzz.com/flight_search_rn/img/qunar_icon.jpg",

                "md5": "lW0X/N3EP3Y7t4IXvfq2DdTV5s/vPVx9ftAfn2P9IUK/FODBYY6Wo/vR8Q==",

                "sl": "760839,2602"

      }],

      "timestamp": 1536903580677,

      "hybridid": "f_flight_search_rn_android",

      "version": "64",

      "iOS_vid": "vid_80019999",

      "android_vid": "vid_60001194,com.mqunar.react_41",

      "pid": "10010",

      "rnpackage": true,

      "platform": "android"

}

```

这里就是去哪儿的资源文件了,不用猜也知道了,https://rn.qunar.com/packages/f_flight_search_rn_android/index.bundle 贴到浏览器,下载打开,可以发现,这个就是真正的js代码了。但是它跟我们自己导出的js不一样,去哪儿对这个优化了,所有模块共用同一套RN框架的js代码,所以每个模块的就只有真正的逻辑代码了。这个QHotDogNetWork请求的使用说明:

```

import { QHotDogNetWork } from 'qunar-react-native';

//如果需要使用APP中配置的 hotdog 地址,则 requestParam 中不设置 url 属性

var requestParam = {

    serviceType:'',//网络请求type,serviceType和url不能同时为空

    url:'',          //网络请求url(默认为APP中设置的hotdog 地址),serviceType和url不能同时为空

    param:{},      //网络请求参数

    useCache:true,   //是否可以使用cache, true或者false

    cacheKey:'',   //cacheKey,如果useCache为true则cacheKey不能为空

    timeout: 300   //如果 300ms 内没有返回,则会触发 failCallback 回调

    successCallback:(response)=>{},    //网络请求成功的回调

    cacheCallback:(response)=>{},      //网络请求从cache返回的回调

    failCallback:()=>{},               //网络请求失败的回调

}

//发起网络请求,返回该网络请求的requestID
var requestID = QHotDogNetWork.postRequest(requestParam);
//取消网络请求

QHotDogNetWork.cancelNetWorkTask(requestID);

```

跟这里是不是一样?


https://imgchr.com/i/iYF84H
##### 后记

去哪儿的代码逻辑找到了,但是加密的问题并没有解决,因为它不会把加密放在js里面呐!libgoblin_3_1_7.so有个字符串是a1708,这个你还记得吗?哥布林这个so里面有xposed和Cydia Substrate的检测,还有ollvm混淆,加密的函数是int __fastcall sub_123D0(unsigned int a1, int a2, int a3, int a4, _DWORD *a5, int *a6)。

由于上传图片太麻烦了,就没什么图,都在这里了:链接:https://pan.baidu.com/s/1manoCV4e3By7Hf5-_hgb5A
提取码:8olp

yyspawn 发表于 2018-10-10 07:11

河北网友 发表于 2018-10-9 18:00

谢谢分享

ench4nt3r 发表于 2018-10-9 18:18

666,前排膜拜

丿颠覆灬虎哥 发表于 2018-10-9 18:35

谢谢分享

_pan 发表于 2018-10-9 18:57

先收藏了,再细细的看

tigerxiao 发表于 2018-10-10 06:32

谢谢分享

km852753951 发表于 2018-10-10 13:53

学习一下 谢谢分享

Shutd0wn 发表于 2018-10-10 15:12

现在app都是kolin,js了,本以为java就可以了,哎

Lupinus 发表于 2018-10-11 09:43

Shutd0wn 发表于 2018-10-10 15:12
现在app都是kolin,js了,本以为java就可以了,哎

含有kotlin的代码反编译出来,也不是那么舒服,js的代码看起来就更难受了,宁愿看各种混淆都不想看js的各种闭包操作
页: [1] 2
查看完整版本: 从去哪儿看React Native安卓App逆向