记一次逆向 Android 的经历

戳上面的蓝字关注我们哦!

作者:王不哈 链接:https://www.jianshu.com/p/d0732c9319b2 声明:本文是 王不哈 原创,转发等请联系原作者授权。

0. 起因

因工作或生活上的某些原因不得不使用某应用,暂且记为A应用把。可 A 应用设计得实在不人性化,一个操作通常需要点击若干次屏幕,点击一次还要 lodaing ,程序员说:不能忍。

于是开始着手改善软件体验

1. 初步计划

初步分析 A 应用实际上是一个 HTTP 客户端,前端后台之间完全通过 HTTP 协议传输数据。可使用 Fiddler 工具抓取数据包分析。

分析发现之前所有那些繁杂的操作(例如签到打卡(虚构)),其实只需要发送一个 HTTP 请求。于是,完全可以使用一段代码,伪装成 A 应用向后端发请求,完成相应的操作。甚至可以将应用内常用的操作全部提取出来,这样在上班的时候突然想起还没签到打卡,直接跑一段程序就 OK,甚至都不需要打开手机。简直美滋滋,我这样想着。

以签到打卡操作为例 实际应用中并不存在签到打卡操作

2. 分析请求

使用 Fiddler 抓取请求如下:

POST http://api.*****.com/v1/****/****/what
// 请求头
headers = {
    "Accept-Encoding": "gzip",
    "Accept-Language": "zh_CN",
    "User-Agent": "Dalvik/2.1.0 (Linux; U; Android 7.0; MI 5 MIUI/8.1.25)",
    "Content-Type": "application/x-www-form-urlencoded",
    "Welove-UA": "[Device:MI5][OSV:7.0][CV:Android4.0.2][WWAN:0][zh_CN][platform:tencent][WSP:2]"
}
// 请求体(表单)
form = {
    "access_token": "562********358-2****************6",
    "app_key": "a*************4",
    "timestamp":"1522393966",
    "sig":"rTRa2PTiGiwkNVQUnSB0n2l6KrA=",
}

使用 Postman 原样发送请求,操作成功。但一旦更改请求的参数,服务端便会返回:

{
    "result": 160,
    "error_msg": "sig签名错误"
}

操作失败! 回头看一眼请求体中的sig字段,这个值rTRa2PTiGiwkNVQUnSB0n2l6KrA=一看就是一个用于校验的字符串,A应用在构造完请之后,根据URL和请求参数生成一个sig字段,并附加到请求的参数里面,后台接收到请求之后,通过sig字段来校验请求的合法性。这个设计一定程度上阻碍了我们伪装成A应用发请求。

所以我们修改了请求体中的数据之后,必然导致后台校验sig失败。

如何能愉快的玩耍?关键在于窥探A应用如何生成sig字段。

3. Hack It

思路: (1)反编译应用,静态分析代码,找出生成sig的规则; (2)若静态分析又困难,尝试动态调试(运行时打印日志等)。

3.1 反编译得到 smali

(1)下载最新版本的 Apktook (2)获取A应用安装包,命名为t.apk (3)使用java -jar apktool.jar d t.apk 反编译应用,得到文件夹t,里面便是A应用的全部 文件夹t的目录结构如下:

其中smali开头的文件夹里面,是反编译之后的smali代码(类似汇编代码)。 可是 smali 代码不便于阅读,能不能直接看到 Java 源码呢?

3.2 反编译得到 Java 源码

(1)使用电脑上的压缩软件直接打开t.apk, (2)解压出压缩包.dex后缀的所有文件, (3)使用 dex2jar 工具将 dex 文件转化为 jar 文件 (4)使用 jd-gui 工具打开jar文件,即可查看源码

注:使用 Apktool 反编译之后的文件夹t,可使用 Apktool 回编译成apk文件,经签名之后,可再次安装到Android设备上运行。

3.3 定位关键代码

使用 jd-gui 打开 dex2jar 转化之后的 jar 文件,场面大概是这个样子:

反编译的目的是找到 A 应用生成 sig 的规则,可即使我们得到了他的代码,如何能在混淆之后的浩如烟海的代码之中,找到生成 sig 的那几行呢?

在 jd-gui 中搜索 字符串 "sig",经过层层删选,锁定了某个名为 a 类中的 a 方法:

其中关键的是,判断 paramMap中是否包含key为sig的值,若没有,就调用e.a()生成一个,于是,sig的生成规则,就看e.a()这个方法了,

点开一看,事情似乎异常明朗了: (1)e.a()方法掉用了重载方法来生成 sig (2)在重载的a方法里面,采用 HmacSHA1 加密算法,密钥为8b5bd1f, (3)加密之后的内容在通过 Base64 编码,得到最后的 sig。

可加密的内容是什么呢? 加密的内容是paramString1.doFinal方法的参数,即paramArrayOfByte,追踪一下这个参数,看到b(paramString1,paramString2,paramMap).getBytes() 于是又进入 b 方法:

这个方法做的事情似乎复杂了很多,大致是: (1)将paramMap中的数据按key排序,并用&连接成一个字符串, (2)经某种处理之后将 paramString1 和 paramString2 和第一步中的字符串连接,并返回。 由前面的参数跟踪分析可知 paramString2 值是"POST",由此大胆猜测,paramString1是请求地址,paramMap是请求体。

如何验证?

3.4 动态调试代码,彻底搞清楚 sig 的生成规则

思路:在 smali 代码找到 3.3 中关键代码对应的部分,在关键的地方加上打印 log 的代码,然后回编译成 apk,重新运行程序进行操作,便可以在日志看到我们感兴趣的内容。

这里我们 log 一下 b 方法的返回值。

回到 3.1 中得到的 smali 代码,找到 a(String,String,Map) 方法

.method public static a(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Ljava/lang/String;
    .locals 1
//...省略了部分代码
//...
//这里调用了 b 方法
    invoke-static {p0, p1, p2}, Lcom/xxxx/xxxx/k/e;->b(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Ljava/lang/String;
//方法的返回值赋给 v0 寄存器
    move-result-object v0
    invoke-virtual {v0}, Ljava/lang/String;->getBytes()[B
    move-result-object v0
    invoke-static {p0, p1, v0}, Lcom/xxxx/xxxx/k/e;->a(Ljava/lang/String;Ljava/lang/String;[B)Ljava/lang/String;
    move-result-object v0
    return-object v0
.end method

所以我们在这里加上一段代码打印出 v0 寄存器的值就 ok 了,代码如下。

//这里调用了 b 方法
invoke-static {p0, p1, p2}, Lcom/xxxx/xxxx/k/e;->b(Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)Ljava/lang/String;
//方法的返回值赋给 v0 寄存器
move-result-object v0
const-string v1, "I got sig"
//打印 v0
invoke-static {v1, v0}, Landroid/util/Log;->i(Ljava/lang/String;Ljava/lang/String;)I

然后回编译:

 java -jar apktool.jar b t

然后签名:

"jarsigner.exe" -keystore your_key_store  -signedjar t_signed.apk t\dist\t.apk username

然后手机连接电脑,安装签名之后的应用:

adb install t_signed.apk 

然后监控手机端日志:

adb logcat | grep "I got sig"

手机运行应用,可以看到有日志输出:

由此得到了 b 方法的返回值,

POST&http%3A%2F%2Fapi.xxxxxxxxx.com%2Fv1%2Fapp%2Fstartup&app_key%3Dac5f34563a4344c4%26imei%3D861945033465836%26mac%3D02%253A00%253A00%253A00%253A00%253A00

解码之后发现和之前的猜想一致:该值由请求方式、请求地址、请求参数拼接而成。

4. sig 的生成规则是什么?

现在可以梳理一下 sig 的生成规则了。 (1)获得请求方式 method, (2)获得请求地址请求 url, (3)获得请求参数表 param, (4)param 按 key 排序,并使用key=value的形式,用&拼接得到字符串paramStr, (5)将method,url,paramStr进行 url 编码,并用&拼接,得到字符串unsig, (6)使用HmacSHA1算法,密钥8b5bd1f,对 unsig 加密,得到字节数组dsig, (7)Base64编码 dsig,得到字符串 sig

5. 为任意请求生成 sig

又能愉快的玩耍了,抄起 Python 写一个为任意请求生成 sig 的方法,便于后续使用:

from hashlib import sha1
import hmac
import base64

from urllib import parse

def sig_gen(method, url, param):
    result = method + '&'
    result = result + parse.quote(url) + '&'

    param_keys = param.keys()
    param_keys = sorted(param_keys)
    param_str = ''
    for i, key in enumerate(param_keys):
        param_str = param_str + key + '=' + str(param[key])
        if i < len(param_keys) - 1:
            param_str = param_str + '&'

    result = result + parse.quote(param_str)
    result = result.replace('/','%2F')
    return sig(result)


def sig(raw):
    sign = hmac.new(b'8b5b********d1f',raw.encode('utf-8'),sha1).digest()
    return base64.b64encode(sign).decode('utf-8')

原文发布于微信公众号 - 刘望舒(liuwangshuAndroid)

原文发表时间:2018-04-04

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏欧阳大哥的轮子

iOS的MVC框架之控制层的构建(上)

在我前面的两篇文章里面分别对MVC框架中的M层的定义和构建方法进行了深入的介绍和探讨。这篇文章则是想深入的介绍一下我们应该如何去构建控制层。控制层是联系视图层和...

1062
来自专栏章鱼的慢慢技术路

《算法图解》第五章笔记与课后练习_散列函数与散列表

1825
来自专栏mini188

聊聊从web session的共享到可扩展缓存设计

先从web session的共享说起 许多系统需要提供7*24小时服务,这类系统肯定需要考虑灾备问题,单台服务器如果宕机可能无法立马恢复使用,这必定影响到服务。...

2076
来自专栏微信公众号:Java团长

Spring框架简介

随着软件结构的日益庞大,软件模块化趋势出现,软件开发也需要多人合作,随即分工出现。如何划分模块,如何定义接口方便分工成为软件工程设计中越来越关注的问题。良好的模...

1202
来自专栏为数不多的Android技巧

如何安全地打印日志

如何打印日志?这不是很简单,直接使用android.util.Log这个类不就行了?然而,日志属于非常敏感的信息;逆向工程师在逆向你的程序的时候,本来需要捕捉你...

1523
来自专栏信安之路

2017-NSCTF-PWN

这次比赛值得吐槽的地方很多,但是我要忍住,先讲正经的。 这次总结比赛的两道pwn,都是栈溢出类型的,比较简单。先放上题目链接:http://pan.baidu....

810
来自专栏*坤的Blog

Java分层概念(转)

6754
来自专栏星回的实验室

JavaScript中的沙箱机制探秘

最近有需求要研究下开放给用户的自动化工具,于是就顺便整理了下沙箱的相关问题。Sandbox,中文称沙箱或者沙盘,在计算机安全中是个经常出现的名词。Sandbox...

2482
来自专栏Golang语言社区

说说JSON和JSONP,也许你会豁然开朗-转

今天在写底层通信框架的时候,遇到了跨域的问题;随便给不知道的童鞋们分享下基础知识。 前言   由于Sencha Touch 2这种开发模式的特性,基本...

3956
来自专栏数据小魔方

RCurl中这么多get函数,是不是一直傻傻分不清!!!

你想知道R语言中的RCurl包中一共有几个get开头的函数嘛,今天我特意数了一下,大约有十四五个那么多(保守估计)! 所以如果对这个包了解不太深入的话,遇到复杂...

4315

扫码关注云+社区