Sec Hotspot 首页  排行榜  收藏本站  技术博客  RSS
统计信息
已收录文章数量:15677 篇
已收录公众号数量:89 个
本站文章为爬虫采集,如有侵权请告知
已收录微信公众号
网信中国 区块链大本营 白说区块链 区块链投资家 区块链官微 区块链铅笔Blockchain HACK学习呀 二道情报贩子 合天智汇 小白帽学习之路 小米安全中心 弥天安全实验室 SAINTSEC SecPulse安全脉搏 TideSec安全团队 360安全卫士 游侠安全网 计算机与网络安全 安全祖师爷 安全学习那些事 腾讯安全联合实验室 黑客技术与网络安全 安全圈 腾讯御见威胁情报中心 Python开发者 Python之禅 编程派 Python那些事 Python程序员 安全威胁情报 吾爱破解论坛 行长叠报 安在 i春秋 嘶吼专业版 E安全 MottoIN 网信防务 网安杂谈 数说安全 互联网安全内参 漏洞战争 安全分析与研究 邑安全 ChaMd5安全团队 天融信阿尔法实验室 安全牛 SecWiki 安全学术圈 信安之路 漏洞感知 浅黑科技 Secquan圈子社区 奇安信集团 奇安信 CERT 国舜股份 雷神众测 盘古实验室 美团安全应急响应中心 瓜子安全应急响应中心 顺丰安全应急响应中心 蚂蚁金服安全响应中心 携程安全应急响应中心 滴滴安全应急响应中心 字节跳动安全中心 百度安全应急响应中心 腾讯安全应急响应中心 网易安全应急响应中心 OPPO安全应急响应中心 京东安全应急响应中心 Bypass CNNVD安全动态 安恒应急响应中心 天融信每日安全简报 奇安信威胁情报中心 看雪学院 黑白之道 水滴安全实验室 安全客 木星安全实验室 云鼎实验室 绿盟科技安全预警 白帽汇 深信服千里目安全实验室 腾讯玄武实验室 长亭安全课堂 FreeBuf 绿盟科技 nmask
Frida配合BurpSuite的Brida插件自动解密取证
本文来自公众号:看雪学院   2020.11.21 17:58:07


本文为看雪论坛文章

看雪论坛作者 ID:无造



本文为 看雪安卓高研2w班(8月班)优秀学员 作品。


下面先让我们来看看学员的学习心得吧!


学员感想


这是2W班8月份的练习题第一题。题目要求是:
参考文档:https://bbs.pediy.com/thread-260422.htm

1. 使用frida来搜集图片证据,越多越好;并配置RPC自动传到电脑上保存;
2. 配置RPC,在电脑上实时显示解密的视频信息元数据;
3. 配置最新版brida
(https://github.com/federicodotta/Brida),抓到视频信息元数据的包、通过brida调用app的解密算法来全自动解密。

本题加密流程已经由大佬给出,也就是在大佬的基础上进行取证。对Brida的使用花费了多一点的时间。

另外当时做这题的时候,自己对frida的操作还是停留在根据内存分布去处理数据,而10月课程学习后,其实有很多更简便兼容性也更强的方式。

这里的代码都是对应于7月份老版本的HOOK。新版本需要重新找hook点。

ps. 题目附件请点击“阅读原文”下载。




解题过程



参考文档中已经给出了APP的加密流程和解密代码。这里就不再分析APP的加密,只是打印出解密后的数据。




下载图片



直接定位图片解密函数,保存返回值。

首先尝试直接保存到手机


var class_decimg = Java.use("com.ilulutv.fulao2.other.g.b");class_decimg.b.overload('[B', '[B', 'java.lang.String').implementation = function(bArr, bArr2, str){    var result = this.b(bArr, bArr2, str);    imgResult = result;    console.log("imgResult.length->", imgResult.length);    var fileName = "/sdcard/fulao2/"+imgResult.length+".jpg";    try{        var buffer = Memory.alloc(imgResult.length);        for(var i=0;i<imgResult.length;i++){            var ival = imgResult[i];            if(ival < 0) {                ival += 256;            }            // console.log("buffer:",buffer.add(i),ival);            buffer.add(i).writeU8(ival);//imgResult[i]        }        console.log("buffer:", hexdump(buffer));        var fd = new File(fileName, "ab+");        if (fd && fd != null) {            var dex_buffer = ptr(buffer).readByteArray(imgResult.length);            fd.write(dex_buffer);            fd.flush();            fd.close();            console.log("[dump img]:", fileName);        }    }    catch(error){        console.log("write_file_log error:",error);    }    return result;}

class_decimg.a-> [object Object]class_decimg.a-> 56439buffer:            0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  F  0123456789ABCDEF95e7f148  ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 01  ......JFIF......95e7f158  00 01 00 00 ff fe 00 3b 43 52 45 41 54 4f 52 3a  .......;CREATOR:95e7f168  20 67 64 2d 6a 70 65 67 20 76 31 2e 30 20 28 75   gd-jpeg v1.0 (u95e7f178  73 69 6e 67 20 49 4a 47 20 4a 50 45 47 20 76 36  sing IJG JPEG v695e7f188  32 29 2c 20 71 75 61 6c 69 74 79 20 3d 20 39 30  2), quality = 9095e7f198  0a ff db 00 43 00 03 02 02 03 02 02 03 03 03 03  ....C...........95e7f1a8  04 03 03 04 05 08 05 05 04 04 05 0a 07 07 06 08  ................95e7f1b8  0c 0a 0c 0c 0b 0a 0b 0b 0d 0e 12 10 0d 0e 11 0e  ................95e7f1c8  0b 0b 10 16 10 11 13 14 15 15 15 0c 0f 17 18 16  ................95e7f1d8  14 18 12 14 15 14 ff db 00 43 01 03 04 04 05 04  .........C......95e7f1e8  05 09 05 05 09 14 0d 0b 0d 14 14 14 14 14 14 14  ................95e7f1f8  14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14  ................95e7f208  14 14 14 14 14 14 14 14 14 14 14 14 14 14 14 14  ................95e7f218  14 14 14 14 14 14 14 14 14 14 14 ff c0 00 11 08  ................95e7f228  01 ca 03 1b 03 01 22 00 02 11 01 03 11 01 ff c4  ......".........95e7f238  00 1f 00 00 01 05 01 01 01 01 01 01 00 00 00 00  ................[dump img]: /sdcard/fulao2/56439.jpg

可以保存但是速度极慢,因为不知道怎么取出result的地址,所以就新建buffer重写了一遍在保存。

配置rpc下载


var class_decimg = Java.use("com.ilulutv.fulao2.other.g.b");class_decimg.b.overload('[B', '[B', 'java.lang.String').implementation = function(bArr, bArr2, str){    var result = this.b(bArr, bArr2, str);    imgBase64Arr[imginx++] = gson.$new().toJson(result);    return result;} rpc.exports = {    getimgarr : function(i){        var result = '';        if(i in imgBase64Arr){            try{                result = imgBase64Arr[i];                console.log("imgBase64Arr[i] result->", result.length);            }            catch(error){                console.log(error);            }        }else{            console.log("i in imgBase64Arr false");        }        return result;    },    getimginx : function(){        return imginx;    },    clearimgarr: function(start,len){        for(var i = 0; i< len;i++){            // imgBase64Arr[start+i] = "clear";            delete  imgBase64Arr[start+i];        }    }}

def downImg(imgDataStr):    try:        ms = re.findall("(-?\d+)", imgDataStr)        intArr = []        for m in ms:            ival = int(m)            if ival < 0:                ival += 256            intArr.append(ival)         bs = bytes(intArr)        m = hashlib.md5()        m.update(bs)        downname = m.hexdigest()        with open("download/"+downname+".jpg", 'ab') as f:            f.write(bs)        print("download sucess->"+ downname+".jpg")    except Exception as e:        print("downImg Error->") imginx = 0while True:    try:        imgArrValue = script.exports.getimginx()        while imgArrValue > imginx:            imgobj = script.exports.getimgarr(imginx)            downImg(imgobj)            script.exports.clearimgarr(imginx, 1)            imginx += 1        print(imgArrValue)        time.sleep(3)    except Exception as ex:        print("Error:", ex)        break

这样下载图片速度不会影响app的正常运行了,只是批量下载需要滑动触发图片加载。

大量读取的话,还是用原作者写好的分页查询图片,然后依次下载脱离app运行方便很多,这里就下载一点测试。

显示解密的视频信息元数据


由于解密的函数com.ilulutv.fulao2.other.g.b.a大多接口都会调用。这里我只想显示m3u8的数据,所以打印了堆栈,向上找了一些定位到了c.c.a.h.a.c.a。

public void a(String str, int i2) {    switch (i2) {        case 82000:            VideoInfoGson videoInfoGson = (VideoInfoGson) a(str, (Class<?>) VideoInfoGson.class);            a(videoInfoGson.getStatus().getCode(), videoInfoGson.getStatus().getMessage(), videoInfoGson, this);            return;        case 82001:            com.ilulutv.fulao2.other.b.t = str;            g.m(str);            if (str.isEmpty()) {                Crashlytics.logException(new Exception("BC" + str));            }            MenuTitleGson menuTitleGson = (MenuTitleGson) a(str, (Class<?>) MenuTitleGson.class);            if (g.v0()) {                g.e(menuTitleGson.getResponse().getMenus().getUncoverX().size());            } else {                g.e(0);            }            if (g.u0()) {                g.d(menuTitleGson.getResponse().getMenus().getCoverX().size());            } else {                g.d(0);            }            a(menuTitleGson.getStatus().getCode(), menuTitleGson.getStatus().getMessage(), menuTitleGson, this);            return;        case 82002:            LikeGson likeGson = (LikeGson) a(str, (Class<?>) LikeGson.class);            a(likeGson.getStatus().getCode(), likeGson.getStatus().getMessage(), likeGson, this);            return;        case 82003:            ReportVideoGson reportVideoGson = (ReportVideoGson) a(str, (Class<?>) ReportVideoGson.class);            a(reportVideoGson.getStatus().getCode(), reportVideoGson.getStatus().getMessage(), reportVideoGson, this);            return;        case 82004:            VideoListGson videoListGson = (VideoListGson) a(str, (Class<?>) VideoListGson.class);            a(videoListGson.getStatus().getCode(), videoListGson.getStatus().getMessage(), videoListGson, this);            return;        case 82005:            c(str);            return;        case 82006:            AdGson2 adGson2 = (AdGson2) a(str, (Class<?>) AdGson2.class);            a(adGson2.getStatus().getCode(), adGson2.getStatus().getMessage(), adGson2, this);            return;        case 82007:            ActorInfoGson actorInfoGson = (ActorInfoGson) a(str, (Class<?>) ActorInfoGson.class);            a(actorInfoGson.getStatus().getCode(), actorInfoGson.getStatus().getMessage(), actorInfoGson, this);            return;        case 82008:            a(str);            return;        case 82009:            VideoLikeListGson videoLikeListGson = (VideoLikeListGson) a(str, (Class<?>) VideoLikeListGson.class);            a(videoLikeListGson.getStatus().getCode(), videoLikeListGson.getStatus().getMessage(), videoLikeListGson, this);            return;        case 82010:            e(str);            return;        case 82011:            d(str);            return;        case 82012:            CurrentOrderGson currentOrderGson = (CurrentOrderGson) a(str, (Class<?>) CurrentOrderGson.class);            a(currentOrderGson.getStatus().getCode(), currentOrderGson.getStatus().getMessage(), currentOrderGson, this);            return;        case 82013:            HistoryOrdersGson historyOrdersGson = (HistoryOrdersGson) a(str, (Class<?>) HistoryOrdersGson.class);            a(historyOrdersGson.getStatus().getCode(), historyOrdersGson.getStatus().getMessage(), historyOrdersGson, this);            return;        case 82014:            g(str);            return;        case 82015:            f(str);            return;        case 82016:            UserInfoGson userInfoGson = (UserInfoGson) a(str, (Class<?>) UserInfoGson.class);            a(userInfoGson.getStatus().getCode(), userInfoGson.getStatus().getMessage(), userInfoGson, this);            return;        case 82017:            AddUserSourceGson addUserSourceGson = (AddUserSourceGson) a(str, (Class<?>) AddUserSourceGson.class);            a(addUserSourceGson.getStatus().getCode(), addUserSourceGson.getStatus().getMessage(), addUserSourceGson, this);            return;        case 82018:            a((Object) str);            return;        case 82019:            WatchHistoryListsGson watchHistoryListsGson = (WatchHistoryListsGson) a(str, (Class<?>) WatchHistoryListsGson.class);            a(watchHistoryListsGson.getStatus().getCode(), watchHistoryListsGson.getStatus().getMessage(), watchHistoryListsGson, this);            return;        case 82020:            b(str);            return;        case 82021:            NoticeListGson noticeListGson = (NoticeListGson) a(str, (Class<?>) NoticeListGson.class);            a(noticeListGson.getStatus().getCode(), noticeListGson.getStatus().getMessage(), noticeListGson, this);            return;        case 82022:            NoticeListGsonTwo noticeListGsonTwo = (NoticeListGsonTwo) a(str, (Class<?>) NoticeListGsonTwo.class);            a(noticeListGsonTwo.getStatus().getCode(), noticeListGsonTwo.getStatus().getMessage(), noticeListGsonTwo, this);            return;        default:            return;    }}

这个函数应该是不同类型http请求解密后呈现数据的方法。筛选82018就是m3u8解密的数据。

var BaseCheckStatusRetrofit = Java.use('c.c.a.h.a.c');BaseCheckStatusRetrofit.a.overload('java.lang.String', 'int').implementation = function(str, i2){    this.a(str, i2);    if(i2 == 82018){        send("BaseCheckStatusRetrofit.a->"+str)    }}

def on_message(message, data):    if message['type'] == 'send':        if "BaseCheckStatusRetrofit.a->" in message['payload']:            print('[*] start load m3u8->')            ms = re.findall("([^$]+?)#EXTINF[^$]+-(\d+).ts",message['payload'].replace("BaseCheckStatusRetrofit.a->",""))            if len(ms) > 0:                print("m3u8 Header->\n", ms[0][0])                print("m3u8 ts Length->", ms[0][1])        else:            print('[*] '+message['payload'])

这边就直接使用send打印出数据了。


ffmpeg下载视频


这里app本身需要代理才能播放视频,但是ffmpeg下载确没有什么限制,可能是其他接口被干掉导致播放不了。
ffmpeg.exe -protocol_whitelist "file,http,crypto,tcp,https,tls" -i test.m3u8 -c copy test.mp4
根据作者给出的命令下载正常播放,确定解密的视频数据正确。





Brida自动解密



安装配置


BurpSuite安装Brida从BApp Store直接安装比较方便,这里只有最新版,其他版本就是用jar包在Extensions中添加。
配置碰到一些小坑,因为要求是最新版0.4版本,网上教程大多数是0.3版本的教程。所以直接看官方文档才能避免这些问题。

  • 0.4版本主要更新是新增了Custonm Plugins选项卡,有4种类型的内部插件使用也比较方便。
  • 还有一点就是RPC导出的函数没必要hex转来转去,直接使用字符串就可以了。如果是老版本代码去掉hexToString,stringToHex的调用即可。
  • Win10我配置后console.log获取不到打印,不知道什么原因,然后kali正常,后面测试也都是在kali上。
  • 最开始写代码最好在Debug export中测试正常后再使用插件调用


自动解密


1.首先使用Brida的IConetxtMenu添加右键菜单,在Repeater请求后右键选择添加的菜单解密。

2.接下来使用IHttpListener自动修改Repeater的返回值

3.都顺利后可以自动解密Proxy中所有带X-VTag的返回数据,配置和上一步差不多,只 是不替换resphone,单纯打印
这样解密的日志可以直接在console中查看,这里是复制出来的数据,将其他类型的数据也一起打印出来了。
调用app解密函数代码就比较简单了:

contextcustom1: function (tm, content) {        // console.log("Brida start :--->", tm, content);        // return "success";        var result;        Java.perform(function () {            try {                var key = "fe34dd6bbd3020c2fb69abe73b5b973c";                var aes = Java.use("com.ilulutv.fulao2.other.g.b");                var md5result = aes.e(tm);                // console.log("Brida : md5result--->",md5result);                var iv = md5result.substring(8,24);                result = aes.a(key, iv, content);                 console.log("Brida : encrypt after--->",result.length);             } catch (error) {                console.log("[!]Exception:" + error.message);            }        });        return result;    },



- End -


看雪ID:无造

https://bbs.pediy.com/user-home-571058.htm

*本文由看雪论坛 无造 原创,转载请注明来自看雪社区。


好消息!!现在 看雪《安卓高级研修班》线下班 & 网课(12月班)开始同步招生 啦!以前没报上高研班的小伙伴赶快抓紧机会报名,升职加薪唾手可得!!



推荐文章++++

* 一个自定义classloader的函数抽取壳样本

* RC4、Base64魔改看雪CTF-变形金刚学习笔记

* 一个易上手的函数抽取样本还原

* 编写单机游戏连连看辅助的全过程

* frida跟踪应用中所有运行在解释模式的java函数






公众号ID:ikanxue
官方微博:看雪安全
商务合作:wsc@kanxue.com



求分享

求点赞

求在看


“阅读原文 一起 来充电吧!