记录黑客技术中优秀的内容,传播黑客文化,分享黑客技术精华

Frida配合BurpSuite的Brida插件自动解密取证

2020-11-21 19:28

本文为看雪论坛文章

看雪论坛作者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



求分享

求点赞

求在看


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

知识来源: https://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458374225&idx=2&sn=a0b023bd8a57f2831545af3a33a68a88

阅读:5271 | 评论:0 | 标签:自动 解密

想收藏或者和大家分享这篇好文章→复制链接地址

“Frida配合BurpSuite的Brida插件自动解密取证”共有0条留言

发表评论

姓名:

邮箱:

网址:

验证码:

公告

❤人人都能成为掌握黑客技术的英雄❤

ADS

标签云