GameDesign 板


LINE

从原始码了解 Pokémon Go INSIDE http://www.inside.com.tw/2016/08/24/source-code-of-pokemon-go 原文为 《Unbundling Pokémon Go》 https://applidium.com/en/news/unbundling_pokemon_go/ 译者简志伟,为软体开发者,译文刊载於 Medium http://goo.gl/PqFI9 INSIDE 获授权转载。 ---------- 最近 Pokémon Go 实在太红了,加上自己是技术控,看到这篇文 ”Unbundling Poké mon Go”:在讲如何用逆向工程得到 App 的原始码,并分析其运作机制,在此翻译分享 给大家。 本翻译文已取得 Adrien Couque 的同意,全文如下: 最近不知从哪儿冒出来,Pokémon Go 在一个礼拜内席卷了全世界,我们从里面发现一些 有趣的东西。 虽然,这个 App 目前只在三个国家公开下载 (美国、澳洲和纽西兰),但它仍然让 Twitter 和 Facebook 相形失色。它打败了 Candy Crush 成为美国最成功的手机游戏, 不仅证明了对开发者带来收益,在地商家也注意 Pokémon Go 会为他们带来客源,任天 堂公司的市值因而增加 90%。 这个游戏在这麽短的时间就成为家喻户晓的话题,激励着我们想去看看它内部的构造。这 篇文以 Pokémon Go App 为例子,说明如何透过逆向工程取得 Android App 的程式码, 同时分析其网路连线请求来得知更多的资讯。 准备 APK 要做逆向工程之前你必须先有 APK 档案,而取得 Pokémon Go 的 APK 档案并不困难, 这里就不详述。请注意安装来源不明的 APK 会有很大的安全风险,但其实 Google Play 会对 App 做一些分析以降低风险,因此一般人最好还是透过 Google Play 下载安装 App 。但对於逆向工程来说,最喜欢这些恶意的 APK,因为很有趣。在这里,我们是针对 7/7 释出的 Pokémon Go 0.29.0 版本进行分析。 先讲一下,做了逆向工程後,我们仍然会看不到一些东西: 任何跟 build code 有关的东西。 任何跟测试和持续整合 (continuous integration) 的东西。 其他特殊版本 (例如 debug 版本):在开发当中,你可能会有某些特殊的功能但不会放在 最终的产品里,因为我们是要分析 Pokémon Go 正式释出的版本,所以特殊功能应该不 会被放在这里面。 後端服务的程式码。很多人可能想知道演算法是如何决定神奇宝贝出现的地点。但这个演 算法是放在後端,我们只能知道如何跟後端传送资料,无法知道内部演算法的逻辑。 APK 的内容 我们来看一下 APK 的内部构造。事实上,APK 只是一个 zip 压缩挡,其包含: http://inside.com.tw/wp-content/uploads/-000//1/1*-x5yUG5lS3zMadIbzErXHQ.png
这里描述一下每个档案 (绿色) 和档案夹 (红色) 的功用: Manifest 就是 Android Manifest,它就像 App 的身分证,里面提供名字、图示、版本 、权限、硬体限制和其他元件等资讯。当系统在安装或升级 App 时也会需要它。 程式被编译後都放在 classes.dex,你可以有一个以上的 classes.dex。 lib 档案夹装着的是函式库。 res 和 assets 档案夹装着的是静态资源档案。 resources.arsc 是 Android 的特殊档案,由编译 R.java 产生的,它是用来连结程式和 静态资源。 META-INF 档案夹装着的是中介资料 (metadata), 但我们在这里不需要。 以上就是当你解压缩 APK 後会看到的东西。 我们开始来看第一个档案:classes.dex。 反编译程式码 dex 是 Dalvik Executable 的缩写 (Dalvik 是 Android 系统里的旧版虚拟器,现在新 的叫 ART,全名是 Android Runtime,但档案的副档名仍用 dex)。这是 Android 系统专 用的档案格式,而且不容易读取其内容。有两个方式可以做到:第一种使用 smali 反组 译工具将 dex 档案内容转成可易於阅读的 bytecode,第二种使用 dex2jar 将内容转成 传统的 Java 档案。 我们打算使用第二种方法将 dex 转成 jar 档 (jar 是一种压缩档,其包含所有的 .class 档案)。接下来我们需要反编译工具再将 .class 档案转换成 Java 程式码。有 很多现成的反编译工具,有各自个优缺点,我们使用 Jadx,你可以使用你惯用的,甚至 可以找到线上版的反编译器。 我们现在有的大部份易於阅读的 Java 程式码,受限於反编译器的限制,仍然有一部分的 程式码无法被看见。事实上,还有一个反编译器 Procyon,可能可以有更好的输出结果。 有一点很重要:我们得到的程式码并不是当初开发者所写的原始码,就像使用 Google 翻 译将英文翻成法文後,再翻回英文,你会得到另一串新的英文。原因是当要翻成法文时, 根据英文的内容会针对单字或片语决定最佳的对应词或句,再次翻回英文时,根据法文的 内容会再做一次决定最佳的对应词或句的运算,这来回的过程各自独立,结果就会产生差 异。这和程式码的逆向工程的结果很像:我们反编译出来的程式码,其运作的行为会跟原 始码一样,但程式码内容不会完全跟原始码一样,差异可能有函数名称、变数名称和注解 。 幸运的是,我们可以清楚得知 app 里所用到的函式库: Android support libraries : support-v4, appcompat and support-annotations Various parts of the Play Services Jackson (JSON parser) : core, annotations and databind Gson (JSON parser) Otto (event bus) Dagger (dependency injection) RxJava / RxAndroid (reactive programming) Apache Commons IO (utilities for I/O) AdMob, now declared as firebase-ads (ads, analytics) Upsight (analytics) Crittercism, now known as Apteligent (monitoring and crash reporting) Unity classes.jar (interaction between the Android framework and Unity) Lunar Mobile Console (Unity logger for Android) Voxelbusters’s Cross Platform Native Plugins (mainly used for sharing from Unity) Google VR SDK 如果你是 Android 开发者的话,可能会觉得奇怪:为什麽有两个 JSON parser?一个做 reactive programming(译注:作者 Ray Shih 对 reactive programming 的见解),一 个做 event bus?这其实是 transitive dependencies:函式库会有相依性才能运作,但 写程式有时候只会呼叫到其中几个函式库,你可以到这里了解我们如何分析 transitive dependencies。 清理掉一些没有呼叫的函式库後,得到一份更简洁的清单: Gson Crittercism Upsight Admob/firebase-ads Google VR SDK, Unity and associated 另外有种相依性则是由外到内,一层层包裹起来,像是 Upsight 里头包了大量的函式库 ,列出清单和函式数目:RxAndroid (4k), Dagger (~200), Commons IO (1k), Jackson (10k), Otto (~50), various Play Services (12k), 自己开发的函式 (3k)。 +--- com.upsight.android:all:4.1.3 | +--- io.reactivex:rxandroid:1.0.1 | | \--- io.reactivex:rxjava:1.0.13 | +--- com.upsight.android:analytics:4.1.3 | | +--- io.reactivex:rxandroid:1.0.1 (*) | | +--- com.google.dagger:dagger:2.0.2 | | | \--- javax.inject:javax.inject:1 | | +--- com.upsight.android:core:4.1.3 | | | +--- io.reactivex:rxandroid:1.0.1 (*) | | | +--- com.google.dagger:dagger:2.0.2 (*) | | | +--- commons-io:commons-io:2.4 | | | +--- com.fasterxml.jackson.core:jackson-databind:2.6.3 | | | | +--- com.fasterxml.jackson.core:jackson-annotations:2.6.0 | | | | \--- com.fasterxml.jackson.core:jackson-core:2.6.3 | | | \--- com.squareup:otto:1.3.8 | | +--- commons-io:commons-io:2.4 | | +--- com.fasterxml.jackson.core:jackson-databind:2.6.3 (*) | | \--- com.squareup:otto:1.3.8 | +--- com.google.dagger:dagger:2.0.2 (*) | +--- com.upsight.android:google-advertising-id:4.1.3 | | +--- io.reactivex:rxandroid:1.0.1 (*) | | +--- com.upsight.android:analytics:4.1.3 (*) | | +--- com.google.dagger:dagger:2.0.2 (*) | | +--- com.android.support:support-v4:23.2.1 (*) | | +--- com.google.android.gms:play-services-ads:8.4.0 -> 9.2.0 (*) | | +--- com.upsight.android:core:4.1.3 (*) | | +--- com.upsight.android:marketing:4.1.3 | | | +--- io.reactivex:rxandroid:1.0.1 (*) | | | +--- com.upsight.android:analytics:4.1.3 (*) | | | +--- com.google.dagger:dagger:2.0.2 (*) | | | +--- com.upsight.android:core:4.1.3 (*) | | | +--- commons-io:commons-io:2.4 | | | +--- com.fasterxml.jackson.core:jackson-databind:2.6.3 (*) | | | \--- com.squareup:otto:1.3.8 | | +--- commons-io:commons-io:2.4 | | +--- com.fasterxml.jackson.core:jackson-databind:2.6.3 (*) | | \--- com.squareup:otto:1.3.8 | +--- com.upsight.android:google-push-services:4.1.3 | | +--- io.reactivex:rxandroid:1.0.1 (*) | | +--- com.upsight.android:analytics:4.1.3 (*) | | +--- com.google.dagger:dagger:2.0.2 (*) | | +--- com.android.support:support-v4:23.2.1 (*) | | +--- com.google.android.gms:play-services-gcm:8.4.0 -> 9.2.0 (*) | | +--- com.upsight.android:core:4.1.3 (*) | | +--- com.upsight.android:marketing:4.1.3 (*) | | +--- commons-io:commons-io:2.4 | | +--- com.fasterxml.jackson.core:jackson-databind:2.6.3 (*) | | \--- com.squareup:otto:1.3.8 | +--- com.upsight.android:managed-variables:4.1.3 | | +--- io.reactivex:rxandroid:1.0.1 (*) | | +--- com.upsight.android:analytics:4.1.3 (*) | | +--- com.google.dagger:dagger:2.0.2 (*) | | +--- com.upsight.android:core:4.1.3 (*) | | +--- commons-io:commons-io:2.4 | | +--- com.fasterxml.jackson.core:jackson-databind:2.6.3 (*) | | \--- com.squareup:otto:1.3.8 | +--- com.upsight.android:marketing:4.1.3 (*) | +--- com.upsight.android:core:4.1.3 (*) | +--- commons-io:commons-io:2.4 | +--- com.fasterxml.jackson.core:jackson-databind:2.6.3 (*) | \--- com.squareup:otto:1.3.8 这表示你有数以千计的函式要分析。 虽然函式库很多,但去掉了分析用工具、监测工具、当机回报和广告,最主要的剩下 Pok émon Go 用的游戏引擎 Unity。这就是为什麽你打开 app 会有一个 Niantic 的标志, 为的是让用户稍待片刻让 Unity 引擎启动,然後再出现一个进度条,显示引擎读取静态 档的状态。你所有的互动操作都是在 Unity 的执行环境里,所以不会看到任何 Android 原生的介面。 另一个受到注意的是:VR SDK。在 Pokémon Go Beta 的阶段,有人用跟我们一样的方法 发现 Cardboard/VR 等字眼在程式码里,在正式版的 app 使用声明里也提到 Cardboard 。但从我的分析来看,未来并不会有 VR 或 Cardboard 的相应功能。从我们的专业来看 ,VR SDK 这个函式库只是用来串接 Android framework 和 Unity,但如果真的要和 Cardboard 整合,就必须让 Android framework 和 Unity 可以交互沟通,因此必须引 用大量的开源程式才能做到。但我们从现在的程式码中并没有看到。 到这里,我们花了很多时间在清理程式,但还没有一个真正能执行专案,因为还需要 resources 和 assets,让我们继续往下看。 静态资源档 要得到 resources 和 assets 比原始码还简单。事实上,assets 会原封不动地被打包进 App,几乎所有的 assets 都用在 Unity,所以我们暂且先不管它们。Resources 比较有 趣,它们包括了 icons、layouts 和 wording。Resources 的内容会在 build 後变得不 易於阅读或编辑,例如 xml layouts 档案会转为二进位格式,9-patches 档案则失去判 读缩放的依据。 好消息是有个工具叫 apktool,它可以帮助我们将 Manifest 和 resources 档案转会成 易於阅读的格式内容,并且产生一个可执行的 Android 专案。一开始我们没有用是因为 apktool 会将 classes.dex 转成 smali 档案,而不是我们要的 Java 程式码。 现在有了反编译的 resources 和 Manifest,另外也有 assets,再加上早些将程式码先 清理乾净,我们可以开始建立和执行一个完整的 Android 专案了。 编译和执行 为了产生 APK,我们要编译的 Java 程式码前,需要建立一个 Android 专案和 build 的 指令。如果你还记得的话,因为这些东西并不在 APK 里,所以我们得自己来,靠的是: Gradle。 其中有一件有趣的事情就是 「最低 Android 版本需求」。App 在 Google Play 上的最 低需求是 Android KitKat(Android 4.4, API level 19),但在函式库的分析中, Google VR SDK 最高需求也只有到 API level 16(JellyBeans, or Android 4.1),我们 不清楚为什麽在 Google Play 的声明要高於实际 API 需求 3 个版本。这麽做一开始就 排除了 20% 的 Android 使用者 (根据 Google’s latest numbers ),也许是故意的, 也或许是失误。 不过目前最重要的是,我们已经有一个可以执行在手机上的专案了。如果你想要安装这个 逆向工程版的 App,建议在你的 build.gradle 和 Manifest components/permissions 里面先改掉 application id,避免和官方版的发生冲突,以确保官方版随时可以更新。 安装成功後,你会发现你卡在登入画面。第一个登入选项是用 Google Sign-In。但是当 你点击它时,它会进行验证 App 签署的凭证,显然的是我们并没有凭证,所以跳出错误 讯息: GoogleAuthException: INVALID_AUDIENCE 为了避开这个限制,我们得花很大的力气才有办法,所以最简单的做法是直接到 Google Developer Console 申请一个新的 App,这样逆向工程版 App 就可以有自己的凭证了, 登入成功後取得 token,但还是不能跟後端做资料交换。 第二个登入选项是透过 Pokémon Trainer Club 申请帐号。但因为太多人申请,伺服器 似乎已经关闭,等它恢复後,我们会再试看看逆向工程版 App 是否可以登入。 分析程式码 这里开始我们会简短看一下程式码。虽说这篇文是在讲述逆向工程的概论,但这部分我们 会着重在 Pokémon Go App,而且每支 App 的分析可能都不太一样。 我们稍早看到大部份的程式码都执行在 Unity 引擎中,因为 Unity 是跨平台的,所以这 些程式码可以执行在 iOS 和 Android 上。但有些则是基於 Android 原生的功能,例如 : Sign-in / Registration (inside the package com.nianticlabs.nia.account) In-App purchases (inside com.nianticlabs.nia.iap) Interaction with Location, Network and Sensors (inside com.nianticlabs.nia.location/ network/sensors) Communication via Bluetooth with the Pokémon Go Plus (inside com.nianticproject.holoholo.sfida) 第一眼看到最有趣的是 location/network/sensors 程式码 (如果你假造你的位置或速度 ,第一时间知道出现的位置和种类,然後可以抓到更多神奇宝贝的话…) 跟 Pokémon Go Plus 沟通,应该就是当你的手机放在背包或口袋的时候,能通知你附近 出现神奇宝贝。这部分程式码可以和网路请求的分析做结合,让 App 只通知你所感兴趣 的神奇宝贝,例如你还没蒐集到的那只。 稍微看一下与 Pokémon Go Plus 沟通的程式码: boolean notifyCancelDowser(); boolean notifyError(); boolean notifyFoundDowser(); boolean notifyNoPokeball(); boolean notifyPokeballShakeAndBroken(String str); boolean notifyPokemonCaught(); boolean notifyProximityDowser(String str); boolean notifyReachedPokestop(String str); boolean notifyReadyForThrowPokeball(String str); boolean notifyRewardItems(String str); boolean notifySpawnedLegendaryPokemon(String str); boolean notifySpawnedPokemon(String str); boolean notifySpawnedUncaughtPokemon(String str); boolean notifyStartDowser(); 这是非常有价值的资料!你可以打造你自己的装置: http://inside.com.tw/wp-content/uploads/-000//1/0*dkO68AHbbFyrlUn5. 截取网路连线 做逆向工程不代表就要大费周章地去拆解程式码,你可以从 App 如何和外界事物互动, 这个方法适用於任何软体。 App 基本上都会与萤幕连动,来做显示或触控的互动,另外还有:档案系统、感测器、网 路等。 这里我们最感兴趣的是网路请求。如我们稍早提到的,游戏最重要的逻辑运算都在伺服器 上头,App 需要与伺服器做资料交换才可以运作,如果能撷取这些传输的资料,我们也许 可以不用再透过 App 就可以和伺服器沟通。 实际上,Pokémon Go 在处理网路请求时,用了一个叫 Optimistic Models 的方法。 Optimistic Models 让使用者在 app 上做一个动作後,不需要等待伺服器的回应,就直 接往下一动作继续操作,让使用者感觉很流畅。如果後来伺服器报错,它才会跳出警示。 所以你可以看到当你在传送神奇宝贝的时候,并没有显示任何等待提示。目前 App 在这 个机制上还没有运作得很流畅,主要是因为伺服器满载,相信接下来几个礼拜会改善。 所以,我们如何撷取网路请求?最简单的方式是在 App 和伺服器中间架一个 proxy。可 是如果资料被 HTTPS 加密,你只能看到无关紧要的 metadata。 有一种方式叫 Man-in-the-Middle 攻击。这种方式是你用 proxy 来骗 App 你是 Server ,然後骗 Server 你是 App。当你收到 App 的请求,用你的 app-side key 先解密,再 用 server-side key 加密送到 Server 取得回应,再用 server-side key 解密,再用 app-side key 加密送回 App。这样你就可以取得完整的资料,而且 App 和 Server 并 不会知道你的存在。 显然,如果故事就这样结束,那所有在网路上的资料都会被看光光。事实上,这些加解密 用的 key 是需要被第三方验证过的,就是 Certificate Authorities。你的手机或浏览 器只会信任验证过的 key,否则回跳出警告讯息。因为手机是我们自己的,我们可以把 key 先装在手机上,来撷取资料。 有现成的工具可以帮我们完成 proxy 的设置,像 mitmproxy 和 Charles。Charles 要付 费,但有使用介面可以导引我们做设定。下图是 App 启动时所截取到的网路请求: http://inside.com.tw/wp-content/uploads/-000//1/0*EMChuPcYEqdpYnjO. 从这里面可以学到很多东西,来看看头几个请求: https://android.clients.google.com/c2dm/register3 : 注册 push notifications https://stats.unity3d.com/HWStatsUpdate.cgi : 可能是一个跟 Unity 有关的分析事 件 https://bootstrap.upsight-api.com/config/v1/a9cc12f87adc420baf964f187672ecb4/ : Upsight 的第一个分析事件 https://appload.ingest.crittercism.com/v0/appload : Crittercism 的第一个分析事 件 https://pgorelease.nianticlabs.com/plfe/rpc : 底下会详述这项 https://play.googleapis.com/log : 跟 Play Services 後端沟通 http://lh4.ggpht.com/LakctgAXpXwe-3PMCWws8rCoVn1_TmyfAiWjWXm6VtsRjRl5v53n1JrWBumWmldzsBFxIUdRLXgsMewLjuyN: 这是一个对 Picasa 的请求,就是 PokéStop 的图片 https://e.crashlytics.com : 跟 Crashlytics 沟通,但看起来是失败 https://www.google.com/loc/m/api : GPS 位置 我们可以看到 App 很频繁地跟 https://pgorelease.nianticlabs.com/plfe/ 做沟通, 而且一个 226 的数字接在 URL 後面,我猜这是为了做 Load balancing:也就是第一个 请求会被指定到某台伺服器去,接下来在同个 session 的所有请求都会导向一样的伺服 器。 最後,「rpc」这个接在 URL 最後的东西代表 App 是透过 Remote Procedure Call 跟 Server 做沟通,因此所有的请求才都发到同一个 URL,这跟用 REST 方式不一样。 看看请求的内容,既不像 JSON,也不是 XML,而且也没有压缩或加密过:所以我们可以 清楚看到 UUIDs 和 “pm0015” 等字串,这可能是使用 protocol buffers (或是 flat buffers) 做序列化後的格式。Charles 会帮忙整理乾净,也可以使用 protocol buffers 的 command line,所以从: 整理成: 1: 53 2: 6032429073588813826 3: "pgorelease.nianticlabs.com/plfe/226" 7 { 1: "nr\026\335Z\206\241\317\257\275\224\'\353X\326\320_}\220 \316~\227\361\3670\'@\205\315t\221\233-C\367\211\r<j8y\024 \224\312v\342\2269~\304\202/\036\247\276\361\266,\033s\027\006\f^" 2: 1468599616357 3: "$\002\304\337.\034\270\361\214D\251nz\273fM" } 100 { } 100 { } 这是请求 pgorelease.nianticlabs.com/plfe/rpc 返回的内容,其中有一个新的请求端 点:pgorelease.nianticlabs.com/plfe/226,是给之後的所有请求使用。 还可以看到很多 “\xxx”,这是 「octal escaping」。使用解码器,内容从: nr\026\335Z\206\241\317\257\275\224\'\353X\326\320_}\220 \316~\227\361\3670\'@\205\315t\221\233-C\367\211\r<j8y\024 \224\312v\342\2269~\304\202/\036\247\276\361\266,\033s\027\006\f^ 变成: nr5Z617754\'3X60_}06~7170\'@55t13-C71\r<j8y42v269~42/7616,s\f^ 从结果推测,这像是出现在附近的神奇宝贝的物件列表,每个物件有自己的 UUID 和属性 (例如 pm0015 代表 pokémon 015 号: Beedrill),其他可能是座标、战斗力和统计数 据。我们可以从请求 https://storage.googleapis.com/cloud_assets_pgorelease/bundles/android/pm0126 来证明这个假设,因为这个请求可以得到 pm0126 相关的 assets。 继续看其他的请求的返回内容。例如,底下这应该是玩家的相关资讯: 100 { 1: 1 2 { 1: 1467925951134 2: "REDACTED: player name" 7: "\000\001\003\004\a" 8 { 8: 1 } 9: 250 10: 350 11 { } 12 { } 13 { } 14 { 1: "POKECOIN" } 14 { 1: "STARDUST" 2: 500 } } } 数字 1467925951134 是 Unix timestamp,指的是 07/07/2016 21:12,这应该是玩家的 注册时间。在请求和返回的内容中,到处都可以看到 timestamp,有的精确度到 millisecond,有的到 nanosecond。 再深入些,我们可以看到很多成对的数字,像:0x40486ddc40000000, 0x4002d99520000000。这应该是座标,但不是被编码成十六进制,而是 IEEE 754 doubles。这对十六进制的值转成数字是: http://inside.com.tw/wp-content/uploads/-000//1/0*uGNGyda8yod38n9w. 是我们办公室的座标!我们将可以拿到的所有座标,猜想它的意义,都标记在地图上: the position of the user (黄色), points of interests / PokéStops (红色) and possible spawn points (绿色) 到目前为止,我们会读取网路交换的资料、序列化的格式,还会分辨一些 id、 timestamps 和 GPS 座标,其他的留给有兴趣的人研究。 结论:如何避免被逆向工程 看到这里,身为开发者也许会觉得没办法防止被别人做逆向工程分析,其实是有的。 模糊你的 Java 程式码是第一步:使用 Proguard。它会把所有的 package、fields 和 methods 的名字以乱数取代,让分析更困难。如果你想要对这种 App 做分析,从 framework classes 开始。Proguard 不只用在模糊程式码,也可以移除没用到的 resources 和 methods。Proguard 很好用,我想 Pokémon Go 未来应该会用。 还有一种方式是减少 Java 程式码,将部分功能改写成 native libraries,这会增加分 析的难度,但对开发很不方便,而且有太多的 Java 与 native 串接,会导致效能下降。 我们能截取网路请求是因为 App 没有使用 Certificate pinning。使用 basic Android classes 或 OkHttp 是很平常的,而且很容易。但就像模糊程式码,它并不能抵挡偏激的 攻击者 (因为凭证也可以被逆向工程),但可以拖延他们一些时间。 最後,本文是相当基本的分析,我们没有揭露任何游戏的秘密,公开作弊的方法让游戏产 生不公平。但对开发者来说,你必须谨慎防范专业级的骇客。 这里条列一下我们的发现: 程式码没有模糊化,这会很容易进行逆向工程分析。 我们可以重建可执行的专案 库的依赖管理可以更好 未来没有 VR 或 Cardboard 版本的迹象 可能可以降低 Android 版本需求 我们可以读取跟位置 / 网路 / 感测器和 Pokémon Go Plus 相关的程式码 容易撷取网路请求,因为缺少 certificate pinning 网路请求是透过 protobuffers-RPC 完成 你可以找到我们的逆向工程版程式码:Github https://github.com/applidium/PokemonGo_Android_RE 与我们联络: Twitter https://twitter.com/applidium --



※ 发信站: 批踢踢实业坊(ptt.cc), 来自: 123.192.242.163
※ 文章网址: https://webptt.com/cn.aspx?n=bbs/GameDesign/M.1472563165.A.B9D.html
1F:推 kudoropu: 推 08/30 22:10
2F:推 catsummery: 请问能在pokemon板也发一篇吗?或者转载? 08/30 23:45
3F:→ catsummery: 啊还是不要好了,怕踩到板规C 08/30 23:46
4F:推 allensheng: 那里连闲聊文都水桶了 转这种大概也是水桶还桶更久 08/30 23:49
5F:推 f9968106: 有看有推 09/02 08:23
6F:推 kelen8861325: 好文 推 09/07 08:54
7F:→ y3k: 居然没用Proguard XD 09/07 19:16
8F:→ y3k: Unity好像有人写扰乱用的unityasset?@@ 09/07 19:27
9F:推 balaking: 专业好文 09/15 17:42







like.gif 您可能会有兴趣的文章
icon.png[问题/行为] 猫晚上进房间会不会有憋尿问题
icon.pngRe: [闲聊] 选了错误的女孩成为魔法少女 XDDDDDDDDDD
icon.png[正妹] 瑞典 一张
icon.png[心得] EMS高领长版毛衣.墨小楼MC1002
icon.png[分享] 丹龙隔热纸GE55+33+22
icon.png[问题] 清洗洗衣机
icon.png[寻物] 窗台下的空间
icon.png[闲聊] 双极の女神1 木魔爵
icon.png[售车] 新竹 1997 march 1297cc 白色 四门
icon.png[讨论] 能从照片感受到摄影者心情吗
icon.png[狂贺] 贺贺贺贺 贺!岛村卯月!总选举NO.1
icon.png[难过] 羡慕白皮肤的女生
icon.png阅读文章
icon.png[黑特]
icon.png[问题] SBK S1安装於安全帽位置
icon.png[分享] 旧woo100绝版开箱!!
icon.pngRe: [无言] 关於小包卫生纸
icon.png[开箱] E5-2683V3 RX480Strix 快睿C1 简单测试
icon.png[心得] 苍の海贼龙 地狱 执行者16PT
icon.png[售车] 1999年Virage iO 1.8EXi
icon.png[心得] 挑战33 LV10 狮子座pt solo
icon.png[闲聊] 手把手教你不被桶之新手主购教学
icon.png[分享] Civic Type R 量产版官方照无预警流出
icon.png[售车] Golf 4 2.0 银色 自排
icon.png[出售] Graco提篮汽座(有底座)2000元诚可议
icon.png[问题] 请问补牙材质掉了还能再补吗?(台中半年内
icon.png[问题] 44th 单曲 生写竟然都给重复的啊啊!
icon.png[心得] 华南红卡/icash 核卡
icon.png[问题] 拔牙矫正这样正常吗
icon.png[赠送] 老莫高业 初业 102年版
icon.png[情报] 三大行动支付 本季掀战火
icon.png[宝宝] 博客来Amos水蜡笔5/1特价五折
icon.pngRe: [心得] 新鲜人一些面试分享
icon.png[心得] 苍の海贼龙 地狱 麒麟25PT
icon.pngRe: [闲聊] (君の名は。雷慎入) 君名二创漫画翻译
icon.pngRe: [闲聊] OGN中场影片:失踪人口局 (英文字幕)
icon.png[问题] 台湾大哥大4G讯号差
icon.png[出售] [全国]全新千寻侘草LED灯, 水草

请输入看板名称,例如:WOW站内搜寻

TOP