深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例


No.0

挖掘前的准备

在模拟器中安装该app后,首先运行app熟悉页面及其功能,下图为登录页面

图片[1]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

No.1

登录接口

抓包分析

我们先使用Charles对该app抓取http请求数据包

图片[2]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛
图片[3]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

可以看到该登录接口请求包中的”Encrypt”参数和相应包都是进行了加密处理,多次更改登录信息发现参数变化,我们来分析下该接口的加密处理。

No.2

脱壳处理

我们使用jadx工具对该apk包进行反编译分析,可以看到该包做了混淆处理,无法有效阅读代码,并且里面的”qihoo.util”可以初步判断使用的360加固

图片[4]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

我们也可以使用查壳工具来判断出使用的360加固

图片[5]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

接下来我们就需要去对其进行脱壳处理得到原代码,这里使用frida-dexdump搭配fri

图片[6]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

然后在命令行窗口上使用frida-dexdum得到程序脱壳后的dex文件

frida-dexdump -FU

图片[7]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

可以看到嘟嘟牛在线有四个dex文件,这些为脱壳后的dex文件

图片[8]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

同样使用BlackDex32在模拟器上对嘟嘟牛在线进行脱壳处理得到脱壳后文件

图片[9]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛
图片[10]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

No.3

反编译分析

我们使用jadx-gui对脱壳后的dex文件进行反编译,如下图所示

图片[11]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

搜索文本”Encrypt”作为关键字字符串进行查询,由于该字符串是在登录接口http报文中涵盖的字符串,所以可以判断出源代码位于com.dodonew.online.http.JsonRequest类中存在addRequestMap方法中有该字符串

图片[12]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

跟进该函数进一步分析逻辑关系

图片[13]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

该函数中值得注意的是定义了一个encrypt字符串变量,这就是生成的Encrypt字符串,入口点定位正确

String time = System.currentTimeMillis() + DeviceConfig.f7371b;
        if (addMap == null) {
            addMap = new HashMap();
        }
        addMap.put(“timeStamp”, time);
String encrypt = RequestUtil.encodeDesMap(RequestUtil.paraMap(addMap, Config.BASE_APPEND, “sign”), this.desKey, this.desIV);
        JSONObject obj = new JSONObject();

先获取系统时间戳,将其添加进addmap中,随后调用RequestUtil.paraMap将addMap和Config.BASE_APPEND作为参数。可以看到BASE_APPEND为一固定值字符串 “sdlkjsdljf0j2fsjk”

图片[14]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

我们跟进RequestUtil.paraMap函数进一步分析

图片[15]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

该函数先将addMap中的键提取出来,然后定义一个List集合用来存放键值信息,使用sort()对该list进行排序。将排序后的键值按序保存在builder中,最后添加 “sdlkjsdljf0j2fsjk”这一字符串,对builder进行计算md5值保存在sign这一字符串中。

No.4

还原加密

上面初步分析了对字符串计算md5值,接下来我们来分析判断对哪些值进行md5计算。

下面编写一个Frida脚本记录java类的操作,获取com.dodonew.online.util.utils类的对象,然后返回其计算md5值后的字符串信息

function main() {

    Java.perform(function () {

        var Utils = Java.use(“com.dodonew.online.util.Utils”);

        Utils[“md5”].implementation = function (string) {

            console.log(‘md5 is called’ + ‘, ‘ + ‘string: ‘ + string);

            var ret = this.md5(string);

            console.log(‘md5 ret value is ‘ + ret);

            return ret;

        };

    });

}

setImmediate(main)

将其保存为test.js文件,使用frida命令运行启动脚本

frida -U -l test.js -f com.dodonew.online

图片[16]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

等程序运行后,输入手机号码和密码信息点击登录,可看到脚本中返回需要计算md5的参数和计算后的md5值

图片[17]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

返回信息如下:

[SM-G988N::com.dodonew.online ]-> md5 is called, string: equtype=ANDROID&loginImei=Android351564715931416&timeStamp=1691317099396&userPwd=12345&username=18148411953&key=sdlkjsdljf0j2fsjk

md5 ret value is
7b9841d0d0c5d23c9271200e9f950b32

我们使用在线工具计算字符串md5值正确,对照一致,符合标准md5值算法

图片[18]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

我们继续来看下面的代码,encodeDesMap函数还有this.deskey和this.desIVl两个参数,猜测使用了des算法

String encrypt = RequestUtil.encodeDesMap(RequestUtil.paraMap(addMap, Config.BASE_APPEND, “sign”), this.desKey, this.desIV);

跟进该函数,先调用了DesSecurity函数对deskey和desIV进行操作,再调用了encrypt64函数对加密的数据进行Base64编码

图片[19]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

跟进DesSecurity函数,调用InitCipher函数对deskey和desIV进行操作

图片[20]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

InitCipher函数中首先对deskey进行md5加密,然后传进去进行DES加密,使用的加密模式是CBC填充方式PKCS5Padding

图片[21]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

我们先找出deskey和desIV参数,下面代码为hook代码

function main() {

    Java.perform(function () {

        var RequestUtil = Java.use(“com.dodonew.online.http.RequestUtil”);

        RequestUtil[“encodeDesMap”].overload(‘java.lang.String’, ‘java.lang.String’, ‘java.lang.String’).implementation = function (data, desKey, desIV) {

            console.log(‘encodeDesMap is called’ + ‘, ‘ + ‘data: ‘ + data + ‘, ‘ + ‘desKey: ‘ + desKey + ‘, ‘ + ‘desIV: ‘ + desIV);

            var ret = this.encodeDesMap(data, desKey, desIV);

            console.log(‘encodeDesMap ret value is ‘ + ret);

            return ret;

        };

    });

}

setImmediate(main)

使用frida运行该脚本得到deskey和desiv这两个值65102933和32028092

 encodeDesMap is called, data: {“equtype”:”ANDROID”,”loginImei”:”Android351564715931416″,”sign”:”4AC813019A9C77EF6498B3221DCCC17F”,”timeStamp”:”1691322190860″,”userPwd”:”12345″,”username”:”18200411953″}, desKey: 65102933, desIV: 32028092

encodeDesMap ret value is NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii7mUQQsI1NkwwCI4HVgshTMq
i6557JDHstrEdSW11iu/vvUpskEuxayIZPFIFNCu4Botd21NW6//bTU+cPCqMHADoYHmZWF4QshO
SBlYcRIP9PkfH1EZE0gjBqIGmjhZrzr1C7RPND6PYF8q2udN4T9bznMt6Qn9mAf719HDgepvcBCm
m09qHaE=

图片[22]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

了解了整个加密过程,我们编写程序来还原下加密代码

from pyDes import CBC, PAD_PKCS5, des

from hashlib import md5

import base64

def get_md5_mes(mes):

    new_md5 = md5()

    new_md5.update(mes.encode(encoding=’utf-8′))

    return new_md5.hexdigest()

def des_encrypt(data, desKey, desIV):

    key = desKey[:8]  

    ds = des(key, CBC, desIV, pad=None)

    en = ds.encrypt(data.encode(), padmode = PAD_PKCS5)

    return base64.b64encode(en).decode()



if __name__ == ‘__main__’:

    desIV = ‘32028092’

    desKey = bytes.fromhex(get_md5_mes(‘65102933’))

    data = ‘{“equtype”:”ANDROID”,”loginImei”:”Android351564715931416″,”sign”:”4AC813019A9C77EF6498B3221DCCC17F”,”timeStamp”:”1691322190860″,”userPwd”:”12345″,”username”:”18200411953″}’

    print(des_encrypt(data, desKey, desIV))

图片[23]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

可以看到得出的结果刚好符合,还原成功。

No.5

模拟请求

我们来梳理下整个加密流程

首先使用md5计算请求签名(equtype、loginImei、timeStamp、userPwd、username、key)保存为sign

对签名及其他字符串进行des加密

取deskey前8字节作为加密密钥
取desIV使用CBC模式PAD_PKCS5进行加密
将机密后的字符串进行Base64编码

编写的模拟请求如下:

from pyDes import CBC, PAD_PKCS5, des

from hashlib import md5

import requests

import base64

import time


def get_md5_mes(mes):#获取字符串的md5值

    new_md5 = md5()

    new_md5.update(mes.encode(encoding=’utf-8′))

    return new_md5.hexdigest()

def des_encrypt(data, desKey, desIV):#DES加密

    key = desKey[:8]  

    ds = des(key, CBC, desIV, pad=None)

    en = ds.encrypt(data.encode(), padmode=PAD_PKCS5)

    return base64.b64encode(en).decode()

def get_timeStamp():#获取时间戳

    return str(int(time.time() * 1000))

def get_sign():#获取请求签名

    s = ‘equtype=ANDROID&loginImei=Androidnull&timeStamp=’ + timeStamp + ‘&userPwd=admin&username=18148411953&key=sdlkjsdljf0j2fsjk’

    return get_md5_mes(s).upper()

def get_Encrypt():#获取加密后的请求参数

    s = ‘{“equtype”:”ANDROID”,”loginImei”:”Androidnull”,”sign”:”‘ + get_sign() + ‘”,”timeStamp”:”‘ + timeStamp + ‘”,”userPwd”:”admin”,”username”:”18148411953″}’

    return des_encrypt(s, desKey, desIV)

def login():#构造http request请求

    url = “http://api.dodovip.com/api/user/login”

    header = {

    “Host”: “api.dodovip.com”,

    “Cache-Control”: “public, max-age=0”,

    ‘Content-Type’: ‘application/json; charset=utf-8’,

    ‘User-Agent’: “Dalvik/2.1.0 (Linux; U; Android 11; M2012K11AC Build/RQ3A.211001.001)”,

    }

    data = {

        ‘Encrypt’: get_Encrypt()

    }

    res = requests.post(url, headers=header, json=data)

    print(res.text)

if __name__ == ‘__main__’:

    desIV = ‘32028092’

    # 需转换成 byte 的 hex 值 用 hexstr 来创建 bytes 对象

    desKey = bytes.fromhex(get_md5_mes(‘65102933’))

    timeStamp = get_timeStamp()

    login()

可以看到模拟请求的响应报文和app登录接口响应报文中内容一样

图片[24]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

返回数据还是加密的

2v+DC2gq7RuAC8PE5GZz5wH3/y9ZVcWhFwhDY9L19g9iEd075+Q7xwewvfIN0g0ec/NaaF43/S0=

DES算法是对称加密算法,可以在JsonRequest类中可以看到有个response方法

图片[25]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

注意下面这行代码,其中用到了this.deskey和this.desIV这两个参数

str = RequestUtil.decodeDesJson(str, this.desKey, this.desIV);

下面我们来具体看看decodeDesJson方法用到的数据,使用frida编写简单的hook代码

function main() {

    Java.perform(function () {

        var RequestUtil = Java.use(“com.dodonew.online.http.RequestUtil”);

        RequestUtil[“decodeDesJson”].implementation = function (json, desKey, desIV) {

            console.log(‘decodeDesJson is called’ + ‘, ‘ + ‘json: ‘ + json + ‘, ‘ + ‘desKey: ‘ + desKey + ‘, ‘ + ‘desIV: ‘ + desIV);

            var ret = this.decodeDesJson(json, desKey, desIV);

            console.log(‘decodeDesJson ret value is ‘ + ret);

            return ret;

        };

    });

}

setImmediate(main)

运行该脚本,在登录接口中输入手机号码和密码点击登录,可以看到返回的加密信息和解密后的JSON字符串

图片[26]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

返回信息如下:

{“code”:-1,”message”:”账号或密码错误”,”data”:{}}

至此将该app登录接口模拟请求成功,由于该接口未做防撞库,可使用字典对手机号和密码进行爆破

No.6

注册接口

注册界面如下,有手机号码、登录密码和验证码三个输入参数

图片[27]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

抓包分析

我们先使用Charles对该app抓取获取验证码http请求数据包,这里使用的测试用例为

电话号码:181****1953
登录密码:admin

图片[28]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

抓取的请求和响应报文如下:

POST /api/user/regCode HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1.2; SM-G988N Build/NRD90M)
Host: api.dodovip.com
Accept-Encoding: gzip
Content-Length: 151
Connection: keep-alive

{“Encrypt”:”VcoXz6dO\/thZ4\/m4PTyZPX+0J3iLNxRy+URRTGi9\/LI0AUSg\/R6SaZXENfK9uOm8nXrss2dC5Nbi\neItA\/j2wmRE33fd4nCDaFtCJ3Wda8vxfwA8AuGaqLkCt5dJ3bQhU\n”}


respond:
2v+DC2gq7Rs5bcSNXbyEx5NU077kz30XWVKcFfbDbqfwNfIfy/H7WfFaOoMFRieB4vJKmUSdrviL
ucFs2ZbC+89cZLVR2q86OhBJqXwkedVPzhVeu2r3xpKnonbCMovs

下面为注册时的http请求及其响应报文,测试用例为

电话号码:181****1953
登录密码:admin
验证码:123456

图片[29]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

抓取的请求和响应报文如下:

POST /api/user/activateAccount HTTP/1.1
Content-Type: application/json; charset=utf-8
User-Agent: Dalvik/2.1.0 (Linux; U; Android 7.1.2; SM-G988N Build/NRD90M)
Host: api.dodovip.com
Accept-Encoding: gzip
Content-Length: 285
Connection: keep-alive

{“Encrypt”:”NIszaqFPos1vd0pFqKlB42Np5itPxaNH\/\/FDsRnlBfgL4lcVxjXii7mUQQsI1NkwwCI4HVgshTOW\nqpHE8q4N8iEDukVF0C9p4xJVE7Km+zijcNlqK038YX5CRUp5A8oy+UVl7otgB3TAD65sXwkMhCwS\nwY0iPkIY7Cr\/lMsVHISUXF6xAPs+xApDcTW804\/ICXm\/gfrzyecRtMgjtowLUWI6I2Nll12kOQdw\n4dO6FM1wbqY2T72Z\/wh\/4e653Twn\n”}

respond:
2v+DC2gq7RsNlVI9b1yN862HwQjTZO7NoQVzr8hRlcBfVp8rq9IPMznCRmxEVw9jsS/SHTe8nzCD
gX+C9o0F2w==

No.7

反编译分析

在jadx中搜索关键字符串”activateAccount”,可查询到RegisterActivity类的register函数

图片[30]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

值得注意的是onClick这个函数,这个函数使用switch分支来判断获取的按钮id来执行相应的逻辑,btn_get_code按钮为获取验证码,btn_register按钮为注册账号,btn_found_pwd为忘记密码

图片[31]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

首先来看获取验证码btn_get_code这个分支代码,先使用checkMobile函数检查电话号是否符合要求,若符合则调用getCode函数向指定电话发送验证码信息

case C0689R.C0688id.btn_get_code /*2131558565*/:
                String mobile = this.etMobile.getText() + DeviceConfig.f7371b.trim();
                if (checkMobile(mobile)) {
                    getCode(mobile);
                    return;
1
                }
                return;

注册功能btn_register代码中通过checkInput函数判断用户输入的phone、pwd、code这三个参数,若都符合则调用register函数进行注册

case C0689R.C0688id.btn_register /*2131558653*/:
                String phone = this.etMobile.getText() + DeviceConfig.f7371b.trim();
                String pwd = this.etPwd.getText() + DeviceConfig.f7371b.trim();
                String code = this.etCode.getText() + DeviceConfig.f7371b.trim();
                if (checkInput(phone, pwd, code)) {
                    register(phone, pwd, code);
                    return;
                }
                return;

我们跟进checkInput函数,首先依次检查电话号码、密码和验证码是否为空,若为空则提示错误

图片[32]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

继续跟进register函数,构造para参数属性(moblie、pwd、code、config.equtype、application.devid),然后调用requestNetwork函数通过”user/activateAccount”路由接口进行请求。

图片[33]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

继续跟进requestNetwork函数

图片[34]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

创建JsonRequest对象,当符合指定路由的时候设置用户和手机号码参数调用注册组件,然后调用addRequestMap函数和DodonewOnlineApplication.addRequest函数

his.request.addRequestMap(para);
        DodonewOnlineApplication.addRequest(this.request, this);

这里调用的addRequestMap函数和登录接口不同,重载只有一个参数,将useDes设置为真,调用paraMap函数

图片[35]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

继续跟进paraMap函数,构造addMap2参数(timeStamp、userid、imei),然后调用RequestUtil.encodeDesMap函数

图片[36]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

下面getCode函数为获取验证码功能,与注册功能不同的是para构造参数只有mobile这一参数

图片[37]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

前面以及对“注册“和”获取验证码“这两个按钮事件进行了分析,接下来我们分析”找回密码”按钮事件,这里直接开启了FindPasswordActivitystartActivity(new Intent(this,FindPasswordActivity.class));跟进FindPasswordActivity类,里面的click函数为重要逻辑代码

startActivity(new Intent(this,FindPasswordActivity.class));
跟进FindPasswordActivity类,里面的click函数为重要逻辑代码
switch (this.position) {
            case TTransportException.f7557b /*1*/:
                this.mobile = this.step1.getMobile();
                if (Utils.isMobileNO(this.mobile)) {
                    this.isAgain = false;
                    sendMobileCode(this.mobile);
                    return;
                }
           showToast("\u8bf7\u8f93\u5165\u6b63\u786e\u7684\u624b\u673a\u53f7.");
                return;
                //验证手机号并发送验证码
            case TTransportException.f7558c /*2*/:
                String code = this.step2.getCode();
                if (TextUtils.isEmpty(code)) {
                    showToast("\u8bf7\u8f93\u5165\u9a8c\u8bc1\u7801.");
                    return;
                } else {
                    verificationCode(this.mobile, code);
                    return;
                }
                //验证判断信息
            case TTransportException.f7559d /*3*/:
                String newPwd = this.step3.getNewPwd();
                if (TextUtils.isEmpty(newPwd)) {
                    showToast("\u8bf7\u8f93\u5165\u65b0\u5bc6\u7801");
                    return;
                } else {
                    modifyNewPwd(this.mobile, newPwd, this.phoneCode.getToken());
                    return;
                }
                //修改密码

接下来就和登录接口流程是一样的

No.8

还原加密

注册功能

编写js脚本获取输入处理的参数以及经过encodeDesMap函数后的加密数据encodeDesMap is called, data: {“equtype”:”ANDROID”,”loginImei”:”Android351564715931416″,”mobileCode”:”123456″,”phone”:”18148411953″,”sign”:”DF249AC067B2097E07BDFD4329B95D4B”,”timeStamp”:”1691735134838″,”userPwd”:”admin”}, desKey: 65102933, desIV: 32028092encodeDesMap ret value is NIszaqFPos1vd0pFqKlB42Np5itPxaNH//FDsRnlBfgL4lcVxjXii7mUQQsI1NkwwCI4HVgshTOWqpHE8q4N8iEDukVF0C9p4xJVE7Km+zijcNlqK038YX5CRUp5A8oy+UVl7otgB3QAcgbw2mrGvsUikMJr1V+9/Lvs7Bgj8JWrl6vLlvqcknHWaYN4O+pJBSKxY83p40f/SNvHAwGsZvNA0f436GxGDHv7+o8Z/gt9MsZTK5oyQJy3Ogma7mbW2

该app的所有http请求加密过程都是同一个加密过程,根据编写加密脚本来验证加密数据,执行结果如下,完全符合

图片[38]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

找回密码

获取验证码时只对手机号码、时间戳和固定的字符串计算md5值

图片[39]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

判断验证时对验证码、手机号码、时间戳和固定的字符串计算MD5值

No.9

模拟请求

模拟请求代码和上面登录接口代码一样,只需修改进行计算md5值的字符串和进行DES加密的字符串即可,这里只对返回信息进行Hook解密

图片[40]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

获取验证码返回信息

{“code”:1,”message”:”验证码已发送”,”data”:{“message”:”验证码已发送”,”code”:1}}

图片[41]-深度分析移动应用安全漏洞:加密机制解构与模拟攻击实例-山海云端论坛

注册返回信息{“code”:0,”message”:”验证码错误或者已过期”,”data”:{}}

© 版权声明
THE END
喜欢就支持一下吧
点赞5 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容