逆向工程软件私有 API 的教程:破解你的沙发
已发表: 2022-03-11旅行是我的热情所在,我是 Couchsurfing 的忠实粉丝。 Couchsurfing 是一个全球旅行者社区,您可以在其中找到住宿地点或与其他旅行者分享自己的家。 最重要的是,Couchsurfing 可以帮助您在与当地人互动的同时享受真正的旅行体验。 我参与 Couchsurfing 社区已有 3 年多了。 起初我参加了聚会,然后我终于能够接待人了。 这是一次多么奇妙的旅程! 我遇到了来自世界各地的许多不可思议的人,并结交了很多朋友。 这整个经历真正改变了我的生活。
我自己接待过很多旅行者,比我实际上网的次数多得多。 当我住在法国里维埃拉的主要旅游目的地之一时,我收到了大量的沙发请求(旺季每天最多 10 个)。 作为一名自由后端开发人员,我立即注意到 couchsurfing.com 网站的问题在于它并没有真正正确地处理这种“高负载”情况。 没有关于您的沙发可用性的信息 - 当您收到新的沙发请求时,您无法确定您当时是否已经在接待某人。 您接受和待处理的请求应该有一个可视化的表示,这样您就可以更好地管理它们。 此外,如果您可以公开您的沙发可用性,您可以避免不必要的沙发请求。 为了更好地理解我的想法,请查看 Airbnb 日历。
许多公司因不听取用户的意见而臭名昭著。 了解 Couchsurfing 的历史,我不能指望他们很快就会实现这个功能。 自从该网站成为一家营利性公司后,社区就恶化了。 为了更好地理解我在说什么,我建议阅读以下两篇文章:
- http://www.nithincoca.com/2013/03/27/the-rise-and-fall-of-couchsurfing/
- http://mechanicalbrain.wordpress.com/2013/03/04/couchsurfing-a-sad-end-to-a-great-idea/
我知道很多社区成员会很高兴拥有此功能。 所以,我决定制作一个应用程序来解决这个问题。 事实证明,没有可用的公共 Couchsurfing API。 以下是我从他们的支持团队收到的回复:
“不幸的是,我们必须通知您,我们的 API 实际上并未公开,目前也没有公开的计划。”
闯入我的沙发
是时候使用我最喜欢的一些软件逆向工程技术闯入 Couchsurfing.com。 我假设他们的移动应用程序必须使用某种 API 来查询后端。 因此,我不得不拦截来自移动应用程序到后端的 HTTP 请求。 为此,我在本地网络中设置了一个代理,并将我的 iPhone 连接到它以拦截 HTTP 请求。 通过这种方式,我能够找到他们私有 API 的访问点并找出他们的 JSON 有效负载格式。
最后,我创建了一个网站,旨在帮助人们管理他们的沙发请求,并向冲浪者展示沙发可用性日历。 我在社区论坛上发布了一个链接(我认为这些论坛也很细分,在那里很难找到信息)。 接待大多是积极的,尽管有些人不喜欢该网站需要 couchsurfing.com 凭据的想法,这确实是一个信任问题。
该网站的工作方式如下:您使用您的 couchsurfing.com 凭据登录该网站,单击几下后,您将获得可以嵌入到您的 couchsurfing.com 个人资料中的 html 代码,瞧——您有一个自动更新的日历你的个人资料。 下面是日历的截图,这里是关于我如何制作它的文章:
- https://github.com/nderkach/couchsurfing-python
我为 Couchsurfing 创建了一个很棒的功能,我自然而然地认为他们会欣赏我的工作——甚至可能会在他们的开发团队中为我提供一个职位。 我已经向jobs(at)couchsurfing.com发送了一封电子邮件,其中包含指向该网站的链接、我的简历和参考资料。 我的一位沙发冲浪客人留下的感谢信:
几天后,他们跟进了我的逆向工程工作。 在回复中很明显,他们唯一关心的是他们自己的安全,所以他们要求我删除我写的关于 API 的博客文章,最后是网站。 我立即删除了这些帖子,因为我的意图不是违反使用条款和获取用户凭据,而是帮助沙发冲浪社区。 我的印象是我被视为罪犯,公司只关注我的网站需要用户凭据这一事实。
我提议免费给他们我的应用程序。 他们可以在他们的环境中托管它并通过 Facebook 身份验证连接它。 毕竟,这是一个很棒的功能,社区需要它。 这是我收到的最终解决方案:
“假期结束后,我们正在重新开始这里的事情,并希望跟进。
我们已经就您的应用程序进行了一些内部讨论,以及我们如何既尊重它所展示的创造力和主动性,同时又不会在 Couchsurfing 用户将凭据输入第三方站点时潜在地损害他们的隐私和安全性。
日历清楚地填补了我们网站上的一个功能漏洞,该功能是我们正在开展的一个更大项目的一部分。
但是收集用户名和密码的问题仍然存在。 我们无法想出一个简单的方法来设置它,以便我们可以在我们这边托管或支持它,而不允许您访问该数据或让您的网站被视为我们的工作产品。
当前可用的 API 很快就会被一个需要访问它的应用程序进行身份验证/授权的版本所取代。”
今天,当我在写这个逆向工程软件教程时(事件发生一年后),日历功能仍然没有在 Couchsurfing 上实现。
回归纯真 - 再次入侵我的沙发
几周前,我受到启发写了一篇关于逆向工程私有 API 的技术的文章。 自然地,我决定总结一下我之前写的关于这个主题的文章,并添加更多细节。 当我开始写这篇新文章时,我想用最新的 API 展示逆向工程过程,并将另一个存根用于 API hacking。 根据我以前的经验,以及 Couchsurfing 最近宣布推出全新的 Wesbite 和移动应用程序 http://blog.couchsurfing.com/the-future-of-couchsurfing-is-on-the-way/ 的事实,我已经决定再次破解他们的 API。
为什么我要做这个逆向工程过程? 嗯,首先,逆向工程软件通常很有趣。 我特别喜欢它的一点是,它不仅涉及您的技术技能,还涉及您的直觉。 有时,弄清楚事情的最佳方法是做出有根据的猜测 - 与蛮力相比,它会为您节省大量时间。 最近,我听到一家公司的故事,该公司不得不使用专有 API 并且很少或根本没有文档。 几天来,他们一直在努力以未知格式解密 API 响应有效负载,然后有人决定在 url 末尾尝试?decode=true
并且他们有一个正确的 JSON。 有时,如果幸运的话,您需要做的就是美化 JSON 响应。
我编写本教程的另一个原因是,一些公司需要很长时间才能采用用户要求的特定功能。 您可以利用他们的私有 API 的力量并自己构建它,而不是等待它被实现。
因此,使用新的 couchsurfing.com API,我从类似的方法开始,并安装了他们最新的 iOS 应用程序。
首先,您需要在 LAN 中设置代理,通过执行中间人攻击 (MITM) 来伪造从应用程序到 API 的 HTTP 请求。
对于未加密的连接,攻击非常简单——客户端连接到代理,然后您将传入的请求来回中继到目标服务器。 如有必要,您可以修改有效负载。 在公共 WLAN 中,通过冒充 WiFi 路由器来伪装执行此操作相当容易。
对于加密连接,有一个小的区别:所有请求都是端到端加密的。 攻击者不可能解密消息,除非他以某种方式访问了私钥(当然在这些交互过程中不会发送)。 话虽如此,即使 API 通信通道是安全的,端点——尤其是客户端——也不是那么安全。
必须满足以下条件才能使 SSL 正常工作:
- 服务器的证书必须使用受信任的证书颁发机构 (CA) 进行签名
- 服务器的公用名,在证书中,必须与服务器的域名匹配
为了克服 MITM 攻击中的加密,我们的代理需要充当 CA(证书颁发机构)并即时生成证书。 例如,如果客户端尝试连接到 www.google.com,代理会动态地为 www.google.com 创建证书并对其进行签名。 现在,客户端认为代理实际上是 www.google.com
为了实现用于对私有 API 进行逆向工程的嗅探代理,我将使用名为 mitmproxy 的工具。 您可以使用任何其他透明 HTTPS 代理。 Charles 是另一个具有良好 GUI 的示例。 为了完成这项工作,我们需要设置以下内容:
将手机的WiFi连接默认网关配置为代理(这样代理在中间,所有的数据包都通过) 在手机上安装代理的证书(这样客户端的信任库中有代理的公钥)
检查代理的有关安装证书的文档。 以下是 mitmproxy 的说明。 这是 iOS 的证书 PEM 文件。
要监控截获的 HTTP 请求,您只需启动 mitmproxy 并从您的手机连接到它(默认端口为 8080)。
在您的移动浏览器中打开一个网站。 此时你应该可以看到 mitmproxy 中的流量了。
一旦您确保一切都按计划进行,就可以开始探索您选择的私有 API。 基本上,此时您可以打开应用程序,使用它并了解 API 端点和请求结构。
对于如何对软件 API 进行逆向工程,没有严格的算法——大多数时候你依靠你的直觉并做出假设。
我的方法是复制 API 调用并使用不同的选项。 一个好的开始是重播您在 mitmproxy 中捕获的请求,并查看它是否有效(按“r”重播请求)。 第一步是确定哪些标题是强制性的。 使用 mitmproxy 处理 headers 非常方便:按 'e' 进入编辑模式,然后按 'h' 修改 headers。 使用他们使用的快捷方式,vim 上瘾者会感到宾至如归。 您也可以使用 Postman 等浏览器扩展来测试 API,但它们往往会添加不必要的标头,因此我建议坚持使用 mitmproxy 或 curl。
我制作了一个脚本来读取 mitmproxy 转储文件并生成一个 curl 字符串 - https://gist.github.com/nderkach/bdb31b04fb1e69fa5346
让我们从登录时发送的请求开始。
POST https://hapi.couchsurfing.com/api/v2/sessions ← 200 application/json
我注意到的第一件事是每个请求都包含一个强制性的标头X-CS-Url-Signature
,它每次都不同。 我还尝试在一段时间后重播请求以检查服务器上是否有时间戳检查,但没有。 接下来要做的是弄清楚这个签名是如何计算的。
在这一点上,我决定对二进制文件进行逆向工程并找出算法。 很自然地,有了为 iPhone 开发的经验并拥有一部 iPhone,我决定从 iPhone ipa(iPhone 应用交付)开始。 原来是解密一个,我需要一部越狱的手机。 停止! 锤子时间。
然后,我记得他们也有一个 Android 应用程序。 我有点犹豫要不要尝试这种方法,因为我对 Android 或 Java 一无所知。 然后我认为这将是一个学习新东西的好机会。 事实证明,通过反编译 java 字节码比高度优化的 iphone 机器码更容易获得人类可读的准源代码。
Apk(Android 应用交付)基本上是一个 zip 文件。 您可以使用任何 zip 提取器来解压缩其内容。 你会发现一个名为 classes.dex 的文件,它是一个 Dalvik 字节码。 Dalvik 是一个用于在 Android 上运行翻译后的 Java 字节码的虚拟机。

为了将 .dex 文件反编译为 .java 源代码,我使用了名为 dex2jar 的工具。 该工具的输出是一个 jar 文件,您可以使用各种工具对其进行反编译。 您甚至可以在 Eclipse 或 IntelliJ IDEA 中打开一个 jar,它会为您完成所有工作。 这些工具中的大多数都会产生类似的结果。 我们并不关心是否可以将它编译回来运行它,我们只是用它来分析源代码。
这是我尝试过的工具列表:
- FernFlower(现在是 IntelliJ IDEA 的一部分)
- 病死率
- 京东图形界面
- 喀拉喀托
- 南河四
CFR 和 FernFlower 对我来说效果最好。 JD-GUI 无法反编译代码的一些关键部分,并且毫无用处,而其他的质量几乎相同。 幸运的是,Java 代码代码似乎没有被混淆,但是有像 ProGuard http://developer.android.com/tools/help/proguard.html 这样的工具可以帮助您对代码进行反混淆。
Java 反编译并不是这个逆向工程教程的真正范围——关于这个主题的文章很多,所以让我们假设你成功地反编译和反混淆了你的 Java 代码。
我在以下要点中结合了用于计算 X-CS-Url-Signature 的所有相关代码:https://gist.github.com/nderkach/d11540e9af322f1c1c74
首先,我搜索了在RetrofitHttpClient
中找到的X-CS-Url-Signature
的提及。 一个特别的调用似乎很有趣——对EncUtils
模块。 深入研究,我意识到他们正在使用 HMAC SHA1。 HMAC 是一种消息认证代码,它使用加密函数(在本例中为 SHA1)来计算消息的哈希值。 它用于确保完整性(即防止中间人修改请求)和身份验证。
我们需要两件事来计算X-CS-Url-Signature
:私钥和编码消息(可能是 HTTP 请求负载和 URL 的一些变体)。
final String a2 = EncUtils.a(EncUtils.a(a, s)); final ArrayList<Header> list = new ArrayList<Header>(request.getHeaders()); list.add(new Header("X-CS-Url-Signature", a2));
在代码中a
是一条消息, s
是用于计算标头a2
的密钥(对EncUtils
的双重调用仅计算 HMAC SHA1 十六进制摘要)。
找到密钥不是问题——它以纯文本形式存储在ApiModule
中,用于初始化 RetrofitHttpClient 的第二个参数。
RetrofitHttpClient a(OkHttpClient okHttpClient) { return new RetrofitHttpClient(okHttpClient, "v3#!R3v44y3ZsJykkb$E@CG#XreXeGCh"); }
如果我们查看对EncUtils
的调用,我们可以看到上面的字符串文字被逐字用作计算 HMAC 的键,除非定义了this.b
在后一种情况下, this.b
被附加一个点。
String s; if (this.b == null) { s = this.a; } else { s = this.a + "." + this.b; }
现在,仅通过查看代码,我就不清楚this.b
的初始化位置和方式(我唯一能发现的是它是在带有签名this.a(String b)
的方法中调用的,但我在代码中的任何地方都找不到对它的调用)。
public void a(final String b) { this.b = b; }
我鼓励你反编译它并找出你自己:)
弄清楚消息非常简单——在代码中,您可以看到它是 url 路径的串联,即/api/v2/sessions
和带有 JSON 有效负载的字符串(如果有)。
final byte[] b = this.b(request.getUrl()); byte[] a; if (request.getBody() != null && request.getBody() instanceof JsonTypedOutput) { System.out.println("body"); // this.a(x, y) concatenates byte arrays a = this.a(b, ((JsonTypedOutput)request.getBody()).a); } else { a = b; }
仅通过查看代码,很难弄清楚 HMAC 计算的确切算法。 所以,我决定用调试符号重新构建应用程序,以弄清楚应用程序是如何工作的。 我使用了一个名为 apktool https://code.google.com/p/android-apktool/ 的工具来使用 smali https://code.google.com/p/smali/ 分解 Dalvik 字节码。 我遵循了 https://code.google.com/p/android-apktool/wiki/SmaliDebugging 上的指南
构建 apk 后,您需要对其进行签名并将其安装在您的设备上。 由于我没有Android设备,我使用了Android SDK附带的模拟器。 通过一些勺子喂食,您可以这样做:
jarsigner -verbose -keystore ~/.android/debug.keystore -storepass android -keypass android <path_to_your_built_apk> androiddebugkey jarsigner -verify -verbose -certs <path_to_your_built_apk> zipalign -v 4 <path_to_your_built_apk> <path_to_your_output_signed_apk>
我使用了一个内置的 Android 模拟器,它带有 sdk 和一个启用了 HAXM 的 Atom x86 虚拟映像,以确保它运行顺利。
tools/emulator -avd mydroid -no-boot-anim -cpu-delay 0
这是关于如何设置虚拟图像的一个很好的指南:http://jolicode.com/blog/speed-up-your-android-emulator
确保您看到行HAX 正在工作并且模拟器在模拟器启动时以快速 virt 模式运行,以确保您启用了 HAXM。
然后,我将 apk 安装到模拟器中并运行该应用程序。 按照 apktool 指南,我利用 IntelliJ IDEA 远程调试器连接到模拟器并设置了一些行断点:
玩了一会儿这个应用程序,我发现用于初始化RetrofitHttpClient
的私钥用于计算登录请求签名的 HMAC。 在对登录 POST 的响应中,您会收到一个用户 ID 和 accessToken ( X-Access-Token
)。 访问令牌用于授权以下所有请求。 所有登录后请求的 HMAC 的构造方式与登录请求相同,只是密钥是通过将.<user_id>
附加到原始私钥来组成的。
获得授权后,应用程序会发送以下请求:
POST https://hapi.couchsurfing.com/api/v2/users/1003669205/registerDevice ← 200 application/json
正如我能够根据经验推断的那样,此请求对于身份验证是可选的。 如果您弄清楚它的用途,则可以加分!
通过身份验证后,您可以发送请求以获取您(或任何其他人的用户配置文件),如下所示:
GET https://hapi.couchsurfing.com/api/v2/users/1003669205 ← 200 application/json
我没有详细介绍,但我注意到配置文件是使用 PUT 请求更新的。 只是为了好玩,我尝试使用相同的请求更新另一个配置文件 - 它未经授权,因此显然实现了安全基础。
我编写了一个简单的 Python 脚本来使用您的 couchsurfing.com 凭据登录并获取您的用户资料:https://gist.github.com/nderkach/899281d7e6dd0d497533。 这是 API 的 Python 包装器:https://github.com/nderkach/couchsurfing-python,其中包含 pypi 存储库中可用的包(pip install couchsurfing)。
下一步
我不确定这次我要对 API 做什么。 不再允许用户配置文件中的 HTML 代码,所以我将不得不想出一种不同的方法来解决旧问题。 如果有需求,我将继续开发和增强 python API 包装器,并假设 couchsurfing.com 不会引起太多问题。 我没有过多地探索API,只是针对一些基本漏洞进行了测试。 它似乎足够安全,但如果您可以访问无法通过网站获得的数据,将会很有趣。 无论哪种方式,现在您都可以使用我的逆向软件工程为 Windows Phone、Pebble 或您的智能沙发构建替代客户端。
总结一个问题
我想公开一个讨论 - 为什么不发布您的 API 并将其公开? 即使我没有设法破解 API,仍然可以抓取网站。 它会更慢且更难维护,但他们肯定更喜欢消费者使用 API 而不是网络爬虫。 API 的可用性将允许第三方开发人员改进公司的产品,并围绕它构建增值服务。 可以说维护公共 API 比维护私有 API 更昂贵。 但话又说回来,在您的产品之上构建社区服务的优势将超过 API 维护成本。
是否可以完全阻止第三方客户端使用私有 API? 我不这么认为。 使用 SSL pinning 可以防止使用前面描述的简单透明代理技术嗅探 API 请求。 最后,即使你混淆了二进制文件,有一些资源和时间的积极黑客也总是能够对应用程序二进制文件进行逆向工程并获得私钥/证书。 我认为客户端端点是安全的假设本质上是错误的。 API 客户端是一个弱点。
通过保持 API 的私密性,公司基本上向用户传达了不信任的信息。 当然,您可以尝试进一步保护您的私有 API。 但是,您不是更愿意为 API 实现基本安全性以防止恶意使用吗? 而是将资源集中在改进软件以提供更好的用户体验上?
沙发冲浪,漂亮,请在上面加糖,打开 API。