前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >记一次逆向 Android 的经历

记一次逆向 Android 的经历

作者头像
用户1269200
发布2018-04-19 10:56:46
1.3K0
发布2018-04-19 10:56:46
举报
文章被收录于专栏:刘望舒
戳上面的蓝字关注我们哦!

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

0. 起因

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

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

1. 初步计划

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

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

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

2. 分析请求

使用 Fiddler 抓取请求如下:

代码语言:javascript
复制
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 原样发送请求,操作成功。但一旦更改请求的参数,服务端便会返回:

代码语言:javascript
复制
{
    "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) 方法

代码语言:javascript
复制
.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 了,代码如下。

代码语言:javascript
复制
//这里调用了 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

然后回编译:

代码语言:javascript
复制
 java -jar apktool.jar b t

然后签名:

代码语言:javascript
复制
"jarsigner.exe" -keystore your_key_store  -signedjar t_signed.apk t\dist\t.apk username

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

代码语言:javascript
复制
adb install t_signed.apk 

然后监控手机端日志:

代码语言:javascript
复制
adb logcat | grep "I got sig"

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

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

代码语言:javascript
复制
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 的方法,便于后续使用:

代码语言:javascript
复制
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')
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2018-04-04,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 刘望舒 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 0. 起因
  • 1. 初步计划
  • 2. 分析请求
  • 3. Hack It
    • 3.1 反编译得到 smali
      • 3.2 反编译得到 Java 源码
        • 3.3 定位关键代码
          • 3.4 动态调试代码,彻底搞清楚 sig 的生成规则
          • 4. sig 的生成规则是什么?
          • 5. 为任意请求生成 sig
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档