freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

hook方式实现App加密数据明文抓取
2023-03-30 15:39:48
所属地 四川省

0x00 前言

在做移动安全的app渗透或者说移动app的漏洞挖掘时,往往会碰到一种情况:好不容易绕过了app的反抓包机制,通过burp抓到了app传输的数据包,这时想对这部分数据做一些爆破、篡改之类的测试,却发现关键数据进行了加密处理,那么这时就不得不首先解决一下数据解密截取的问题。

0x01 环境搭建

(一)抓包环境

首先是抓包环境,需要针对app的反抓包机制做一些绕过,这不是本篇文章讨论重点,因此在另一篇文章中做介绍。

(二)frida框架

其次是我们做hook操作,需要依赖一些hook框架,来帮助我们更好的完成操作,这里我选用的是frida框架:简单安装教程如下:

1)首先安装python3环境,之后使用pip工具安装frida框架:

pip3 install frida(默认安装最新版)
当然可以指定版本安装:
pip3 install frida==14.2.18 frida-tools==9.2.4
!!注意frida与frida-tools的版本对应关系,可以去github查找

2)再安装frida-server到手机(如果是模拟器注意安装包的选取),下载后解压传输安装赋权启用即可

frida-server14.2.18

图片.png

adb push xxxfdserverxxx /data/local/tmp/fs14218
cd /data/local/tmp
chmod 777 fs14218
./fs14218
//启动

之后另起一个cmd窗口,输入命令查看手机上的信息:

frida-ps -Uai

如果能看到如下一类信息,即安装成功。

图片.png

(三).反编译工具 jadx-gui

直接下载打开即可食用

jadx-gui下载地址

0x03 apk逆向&&明文抓取

(零)思路介绍

首先配置好代理,抓包看一下:

图片.png
可以看到,app传输的数据都经过了一些未知的复杂加密处理,sign字段看过去类似是经过摘要算法得到的签名值之类,那么这里如果我们想要对data、timestamp等字段做一个测试,显然我们是不能直接修改的(可以更改一下看看效果)

既然这里不是一些简单的base64编码之类的处理,不能采取直接解码,又一眼看不出这里的加密方式(密码算法逆向),那么此时我们的思路还有什么呢?

可以想象一下数据的传输过程:

明文数据-->app调用加密算法进行加密--->app调用摘要算法计算消息摘要--->app发送请求,传输加密数据---->........

一眼看过去,可操作的攻击思路是不是就清晰了?

1.可以对加密算法进行逆向,完全掌控加密过程

2.对明文数据在进行加密之前进行截取,并做更改

这篇文章就介绍第二种攻击思路:

对app进行反编译,找到明文传输路径,通过hook方式在加密操作之前,对app的明文数据进行篡改操作,这样的篡改操作在加密与摘要之前,在app的验证机制看来完全合法。接下来实操尝试一下:

(一)反编译及寻找可疑方法调用栈

打开jadx-gui,将得到的app安装包丢进去反编译一下(有壳的先砸壳处理一下):

图片.png
如何寻找可疑方法呢?这里介绍一种最常用的简单方法:

回头看一下我们抓到的包:

图片.png

可以看到一些关键字,那么我们就直接在反编译的代码中去检索这些关键字,大概率就可以找到关键位置(如果源码做了混淆,就得采用一些其他办法,这里不做讨论):搜索一下sign关键字:
图片.png

有所发现,一一进行查看即可,有经验的话,可能可以猜到一些特征:sign是网络请求中的字段等等...随意联想。

经过检查,找到最具可疑性的目标:

图片.png

出现了请求包中出现的sign、_ver字段,以及可疑的DATA,继续跟进一下:

图片.png

原来,DATA就是我们的data,hahah~~~

回头追踪sign字段:发现与e.c.a.i.c.a()方法相关,跟过去看一下

图片.png

图片.png

阅读代码,找到源头,原来sign值就是将传入的数据+一定的密钥通过encrypt()方法处理得到的,那么这里就可以作为我们的切入点,hook验证一下看看我们的猜测对不对,数据是不是真的经过了这里:

脚本如下:

//原理:APP应用程序在处理数据、提交数据时,通常会将数据存放于集合中,而HashMap又是其中较为常用的。因此,可以通过Hook HashMap的put方法来定位关键代码所在的位置。
// 使用Java.perform包装代码块以确保在正确的线程和类加载器上下文中执行
Java.perform(function () {
    // 获取 java.util.HashMap 类引用
    var HashMap = Java.use('java.util.HashMap');

    // Hook HashMap 的 put 方法
    HashMap.put.implementation = function (key, value) {
        // 将 Java String 转换为 JavaScript 字符串
        var keyStr = key ? key.toString() : '';

        // // 如果 key 是 "data" 或 "sign"
        if (keyStr === 'data' || keyStr === 'sign') {
            // 打印 key 和 value
            console.log('Key:', keyStr);
            console.log('Value:', value);

            // 打印调用栈
            console.log('Call Stack:', Java.use('android.util.Log').getStackTraceString(Java.use('java.lang.Exception').$new()));
        }
        // console.log('Key:', keyStr);
        // console.log('Value:', value);
        // 调用原始的 put 方法实现,并返回结果
        return this.put.call(this, key, value);
    };
});

使用frida注入脚本:

首先在手机开启frida服务器:

./fs14218

之后执行命令,注入脚本:

frida -U -f 包名 -l   hook脚本 --no-pause
如果此时app在前台运行,可以选择-UF模式
frida -UF -l hook脚本

图片.png

刷新一下app试试:

图片.png

果然,看到了相应的数据,证明我们的猜测无误,同时脚本也打印出了调用栈。

根据分析调用栈,寻找源头:(找与app包名相关的东西):找到:

e.c.a.i.d.a()方法,追踪到e.c.a.i.c.a()方法,和前面我们的分析吻合。那么这里我们就找到了精准位置

图片.png

将该方法复制为一段frida代码片段,hook一下看看:

let c = Java.use("e.c.a.i.c");
c["a"].overload('java.lang.String').implementation = function (str) {
    console.log(`c.a is called: str=${str}`);
    let result = this["a"](str);
    console.log(`c.a result=${result}`);
    return result;
};

改成简单的hook脚本:1.js

Java.perform(function () {
      let c = Java.use("e.c.a.i.c");
      c["a"].overload('java.lang.String').implementation = function (str) {
      console.log(`c.a is called: str=${str}`);
      let result = this["a"](str);
      console.log(`c.a result=${result}`);
      return result;
    };
});

注入一下看看:方法如上

图片.png
截取到了想要的数据。切入点无误,开始操作,抓取明文:

(二)编写脚本,抓取明文(被动调用)

前面我们找到了精准位置如下:

图片.png

因此这里就编写对应的hook脚本与python脚本,提取控制转发明文即可达成我们的目的:

首先简单的只打印看一下:

Java.perform(function () {
  var c = Java.use("e.c.a.i.c");
  var EncryptUtil = Java.use("com.qq.lib.EncryptUtil");
  c.a.overload('java.lang.String').implementation  = function(str){
    console.log("\n请求加密前明文:\n",str);
    return this.a(str);
  }


  EncryptUtil.decrypt.implementation = function(str,str2){
    console.log("\n响应解密后明文:\n",this.decrypt(str,str2));
    return this.decrypt(str,str2);
  }
});

图片.png

看到,明文已经抓取成功了,接下来就是需要丰富一下脚本功能,把请求与响应转发到我们的burp工具去,方便我们进行改包测试(这里我利用python来实现转发请求,调用hook脚本等功能)

python脚本如下(构建镜像服务器):

import argparse
from threading import Thread
from http.server import HTTPServer, BaseHTTPRequestHandler
import sys
import requests
import frida

# 定义端口号
ECHO_PORT = 28080
BURP_PORT = 8080


# 创建一个请求处理器类,继承自 BaseHTTPRequestHandler
class RequestHandler(BaseHTTPRequestHandler):
    # 定义处理请求的方法
    def do_REQUEST(self):
        content_length = int(self.headers.get('content-length', 0))
        self.send_response(200)
        self.end_headers()
        self.wfile.write(self.rfile.read(content_length))

    # 处理响应的方法与处理请求的方法相同
    do_RESPONSE = do_REQUEST


def echo_server_thread():
    print('start echo server at port {}'.format(ECHO_PORT))
    server = HTTPServer(('', ECHO_PORT), RequestHandler)
    server.serve_forever()


t = Thread(target=echo_server_thread)
t.daemon = True
t.start()

# 添加命令行参数解析
parser = argparse.ArgumentParser(description="Frida Python script with command line arguments.")
parser.add_argument("process_name", help="The process name you want to attach to.")
parser.add_argument("js_file", help="The path to the Frida JS script.")
args = parser.parse_args()

# 通过 USB 设备附加到指定进程
session = frida.get_usb_device().attach(args.process_name)

# 加载 Frida JS 脚本
with open(args.js_file, "r", encoding='utf-8') as f:
    js_code = f.read()
script = session.create_script(js_code)


# 定义处理来自 Frida 脚本的消息的函数
def on_message(message, data):
    if message['type'] == 'send':
        payload = message['payload']
        _type, data = payload['type'], payload['data']
        # print(message)
        if _type == 'REQ':
            data = str(data)
            r = requests.request('REQUEST', 'http://127.0.0.1:{}/'.format(ECHO_PORT),
                                 proxies={'http': 'http://127.0.0.1:{}'.format(BURP_PORT)},
                                 data=data.encode('utf-8'))

            script.post({'type': 'NEW_REQ', 'payload': r.text})

        elif _type == 'RESP':
            r = requests.request('RESPONSE', 'http://127.0.0.1:{}/'.format(ECHO_PORT),
                                 proxies={'http': 'http://127.0.0.1:{}'.format(BURP_PORT)},
                                 data=data.encode('utf-8'))

            script.post({'type': 'NEW_RESP', 'payload': r.text})


# 为 Frida 脚本设置消息处理函数
script.on('message', on_message)
script.load()

# 使主线程保持运行,等待用户输入
sys.stdin.read()

hook脚本如下:

// 使用 Java.perform 函数确保 Frida 在 Java 虚拟机中运行此代码
Java.perform(function () {
  // 获取 "e.c.a.i.c" 类
  var c = Java.use("e.c.a.i.c");
  // 获取 "com.qq.lib.EncryptUtil" 类
  var EncryptUtil = Java.use("com.qq.lib.EncryptUtil");
  // 定义一个变量来存储从 Python 接收到的新字符串
  var newStr;

  // Hook "e.c.a.i.c" 类中的 "a" 方法,参数类型为 "java.lang.String"
  c.a.overload('java.lang.String').implementation = function (str) {
    // 将原始参数转换为 JSON 字符串并将其发送给 Python
    send({ type: 'REQ', data: JSON.stringify(str) });

    // 等待来自 Python 的新参数
    var newArgs = recv('NEW_REQ', function (data) {
      // 将从 Python 接收到的 JSON 字符串转换为 JavaScript 对象
      newStr = JSON.parse(data.payload);
    });
    // 等待 recv 函数处理完数据
    newArgs.wait();

    // 使用新参数调用原始方法并返回结果
    return this.a(newStr);
  }

  // 定义一个变量来存储从 Python 接收到的新明文
  var newPlaintext;

  // Hook "com.qq.lib.EncryptUtil" 类中的 "decrypt" 方法
  EncryptUtil.decrypt.implementation = function (str, str2) {
    // 使用原始参数调用原始解密方法并将结果存储在 "plaintext" 变量中
    var plaintext = this.decrypt(str, str2);

    // 将解密后的明文转换为 JSON 字符串并将其发送给 Python
    send({ type: 'RESP', data: JSON.stringify(plaintext) });

    // 等待来自 Python 的新明文
    var newResult = recv('NEW_RESP', function (data) {
      // 将从 Python 接收到的 JSON 字符串转换为 JavaScript 对象
      newPlaintext = JSON.parse(data.payload);
    });
    // 等待 recv 函数处理完数据
    newResult.wait();

    // 返回新的明文
    return newPlaintext;
  }
});

记得在burp监听相应的端口:测试效果

启动python脚本:python3 main.py org.tdyoa.mcdfmv(包名为org.tdyoa.mcdfmv) hook.js

图片.png

burp上接收到python转发过来的响应包,我们进行修改试试:以用户名为例:

原用户名:

图片.png

改包到新用户名:xiaoheilaoshi123

图片.png

已经成功篡改,可以开始愉快的测试啦!!!

(三)进阶-主动调用

用了前面的方法,有的小伙伴肯定就要说了,每次测试,我都要去点app,操作手机,好麻烦啊,算了算了。别急,咱们这就解决一下

前面我们知道,我们是通过反编译找到关键函数点,然后用frida进行hook打印寻找调用栈,那么不妨拓展一下省力思路:

能不能找到关键方法,然后脚本直接调用方法,进行hook,这样我们就只需要把app开着放在一边,直接去运行脚本,就可以完成操作?

显然是可以的(就以同一个app中data字段的加密解密方法为例):

编写一下脚本如下(找到该方法相关的类名、密钥等信息,利用python提供的frida、flask库实现frida的功能):

#rpchook.py
import frida
import sys
from flask import Flask, request, jsonify

app = Flask(__name__)

device = frida.get_usb_device()
session = device.attach("org.tdyoa.mcdfmv")  # 使用你的APP包名替换


@app.route('/decrypt', methods=['POST'])
def decrypt():
    encrypted_data = request.json['encrypted_data']
    decrypt_key = request.json['decrypt_key']

    script = session.create_script("""
        Java.perform(function() {
            var EncryptUtil = Java.use('com.qq.lib.EncryptUtil');
            var encrypted_data = '%s';
            var decrypt_key = '%s';

            var decrypted_data = EncryptUtil.decrypt(encrypted_data, decrypt_key);
            send(decrypted_data);
        });
    """ % (encrypted_data, decrypt_key))

    decrypted_data = None

    def on_message(message, data):
        nonlocal decrypted_data
        if message['type'] == 'send':
            decrypted_data = message['payload']

    script.on('message', on_message)
    script.load()
    script.unload()

    return jsonify({'decrypted_data': decrypted_data})


if __name__ == '__main__':
    app.run(host='0.0.0.0', port=5000)
#testjiemi.py
import requests

url = "http://localhost:5000/decrypt"
headers = {"Content-Type": "application/json"}
data = {
    "encrypted_data": "8039C68F5AAE16B87FED41779E66ADC704ACC42028F9D422F97BFAB7D3288972139060B21A850DA4DB0107F6D2165076DCB064BA6C4796B61C4E2AF0F3B02947F41F6D7E6454F6FD06117A714093FDD71A46A017A490E9E51B5D73A403E34F5BFF83A1F2963935B12B04E960BD31540F8773AFB6A6B916FE66531B0D8891B1A0F1B9A0EA4DE7976FC01E3DE4DACEB8BA07D2086F845D2191A06A20626B9C1906B1A7AC02275C9DB8AF88E2D5254F9C7588104E4668C1229365850FA0BB6C2B55CB2DE8D3186C6F5A6DD5E3C1EE4EB8D50E02C7DA6C388D4A0D94C31D180B882E2C05CD15D13F226BD0966BE78FC2942FD23862E92D2A5AC3CF3C818D13C20D37680C3E4B7F42101C84EB795289945107EBBB539B27704A083D3D55B2815FD848F359C9ACE6986E1B751F1041CD9B1DBCF35591DAA4C74974F6A9CD57B4082C15A0FB8FD77A5F9F17B7F80F008D5D18D5D0C4D7D842E5BA5BABDDCB52C7A1E4860ED439F4A97FFBDDFE6BDF7BA82CCA479113A1A2FF59A91E0A4C0304CCB1CF0B64D7307F98C810088D49CB4AF9D06C674B3F8C38DAF82D5D1ABE3D3F08CD29763C7233B55960512D3D16FD4412537872EC6BE1E219B145B6F0116BDCC48C811DCE60CAF080198067138DB7BFC7D3C216B9FACF3D5DCF5734E38A6C99EEF23327CF188089820183E4F739022CB64ABF433A61F1B71F4C00B926E81D52486BF70FFA9A20EAB9381C63143A414391DD9D4A1B49701027F7F7BDCF4A6A108B8212FC463063B6DC42C086292F7A6F9B675805E4064327F8FE0ECDCF8FBCC407858A540FC4D47AA9028CC00C8DB9EB41BB6266187EEEBABB5138C1E83EE25C88A83EB20B0EAD4A7B09D45B9D4DEB44D1887C017B10F985021B2231A5F69053468EC0A45B6411561780D85C12E6E59597846FC4014F2ACB28E17175F952A64EAB5E3C1AA520A3821D6340627EA83448F1505B29D51097577A37F32B2F68D3B3BFC2018B8F5AADC41E20D1D9DA4383D4AEC7BBC6F86AE3602EAE99774EF695532B52E05824E025250F43A70137F56B6B6EBE7B8604FF785039D1F38E1CC10D49DA238F11C50B9BBE9E30FE785A5BFC49E28D3A3025A207A82BEC973E3760915A827428E3BA11B9433D95640EB3FA51394A02D543622F1B815A47C918453A2E6A917B00730F89E28DE31DF9184DAFFEC5737789122AA5A35A7ACA32A289031626CCC59251F890A213C893DBC0B17CAA3EDA18A9B365CA3DD3AC1E49445C4B67F63106BEED0A3346F50E18B7FAAE0600908F15E494C28140E2437282481C0687D31D938514D93CA4FF32F274823A4075982517DB18C8F9ED081452ECA3E35BBA8DE95E77612E3DD8449389B67DE4D631605079B609024CE7F03AB83F7BAAD7FF9DE5DCF6255FF78219FF724F0DBEFA29B4506375FC6383828638AB77B19FA0A0057EF8A84CD09CFB28C239CD11042D52D370FD0FC6C2F284BF4287A5735802FBEDF666601EED5EB9029E1C2448E6393E44536FEC7D178E8BD3843DA4E0B6137C590AD23310F52CD3B8F5AF93081EB3C5D01003AC9D272FECC40C5808EBFC9533421C84C8C5B1C372BF9146107F6B7946B8ECC3D0773AA1211F953B2773E18C06C57949163BADA100A4BC0A3E6A7555914DA70C2537C1EB709C7C54F8BE3C8D2617381BCDEDB9790B1E673EC7B73BBE9AA2B45139EEFC17AC6416215D5B02219968B4DF9311AD8EF3992910932A3B7B72CA9BA5AC66BDDF45DB272D469D08607E4B12201C07BF0EB8FBDA7F41D343ADB8217E289D730EECCA4D84027C9A5AD4619C9326B452AEC88F2C6B1F0A004C7B45594CA5888C022E4975C9E7D337519336D0607AD32BCEE34557DE7374EECC55717AC09139155D7932B19189C48AF9D90C157805B2B97145BE7348637DBA0F3C6A7F91EAEE489FB309CC60041F83EAD4845AC1F2F4EFE06326575602A4715D6E4A296459D5FFB466A5089EC45FEC0158302388A96D725228855A3D85CAC933DBCE2153209069EEF2DE1DA4780A7616492295D81397D581589E13FEAE061D57CCC6D427131E8A7FDB9AEB454F2F7555E598C056A46662E9E0BF96A1B9AA5E09423E6653710A16177AB658F2E14BA9C83639BB64DFF4E4F1CA21143FD2E01B62CAF69D6BC559BAD2B76749919AE6F1648C0E9608B39746A13CFA42F47C9AC52A2198438CFA4283D2B6CFF961D69C40F6DF36DCE6F8DCED811E0197427BDD7F135050A49D004C262D1AECA4417F25A823D95FA921C491853783F56914ABDA14384163640DDC7FA36C2C302A1EF396DF846C3CC8A130583B7C6826771948D60029AA14CCE816D84B113B1E418A2361AA0A4E64BFD5F6DC1EDF9DCC548BC6F7A329D3FABDE9CF1AA6692DF2F67A53D5E951804F769BE31CEA7681F63C8AADCD629460564D31F43843125A35B7CE4EDF8A6D79E15363CF74F955AD6029DD6C0B1EBF8031AC52B502DA90CACF9AEB5FEAD5A19D85216C12F03160CB33F015E461CD2AA6CCFCD68865E53930B544C5364E00A7821B8DE76116D0BD4A3EE231801C0B44C12CC11376BE0437962FD43C677D70D9C39AC2A479550A5A8DFD1C5678964FE3C207AE124AD3D0B406573A72DB2CBDA5421647C3D625D1C29A58395421A186A7BD0B48AA8CD1FD5CCF73DEE9719ACC7837A47B9B0CC436ACAB936872F72F1E65C36FA02CB0B218F1B1D37B552713627F4FE400882185DA042CBD7EB79E90AF628692141704D3B72948536CF62C427477370B65B18D43C9B2D02D6E376F0ABB59916B6C7FBFC3CBD7018BB7F4BE1F91D381A1E059B1D0953FC23DF55A648D50AD4BC44903BCD68E8D6E61090C8DF4A06532DEC45FF9F69CED82B4909563AF336AA339F1684A6AE7D6DBBCD32A897A99049BEBFF8847357DE40A437BE4E49DCF41D2DE7F9804CECE81B7E19F291480F1D0279506020D7960F9563C731F5C23BEC2FE3FC754A8F7D4F6E9C5703A149703ACFCC67068083739EF1E646977FF53F7C11333BEFA0EF7C824D697AA62774BAB7B616ECD2B99A4D3F3D56D01C09F374869B2577E4BACC91539DDE51EB484EEDBF5DFF84E302510E9AB39FB9269511CB8D7788EB0567C5A897F7FDA5D815201DB4CD0C5DEA6836A6C5BC62424640A9869B743D499A865C65D1EF9F9651722D38002DADC41025C1C63B6E3E1022F085A92882E1DC4F23FC9A9A2BF2609E6DF54AFB36F6F136909C7",
    "decrypt_key": "PT0dPVxYXglbDARZXwlfXwwMDApYDgReWV4JXl4ICAtfWAxcJD4WCD8SFRYCFQ=="
}

response = requests.post(url, json=data, headers=headers)
print(response.json())

运行效果如下:

图片.png

好啦,本篇文章讨论就到这里啦,小伙伴们开始愉快的玩耍吧!!!

# 移动安全
本文为 独立观点,未经允许不得转载,授权请联系FreeBuf客服小蜜蜂,微信:freebee2022
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
相关推荐
  • 0 文章数
  • 0 关注者
文章目录