分享对某火APP加密算法和签名算法的还原

0收藏

0点赞

浏览量:781

2022-04-07

举报

1. 开篇

最近遇到一个强度还是挺高的壳,跟大家分享一下如何带壳进行调试,纯粹的技术交流,请勿利用此文章做非法用途!

2. 准备环境

frida 12.11.18
jeb-3.24
jadx-gui-1.3.3-1
xcube
android 7.0(真机)
这里说下为什么一定要用真机,这个壳有模拟器检测,再者就是这个app根本就没编译x86指令的so,所以根本无法在x86模拟器下运行起来。下面直接进入正题。

3. 初探

挂上http代理,尝试拦截流量,客户端直接报ssl证书错误,很明显有ssl pinning之类的验证。jadx加载apk,manifest解析失败,再拉入jeb分析。

 

是否有人遇见过com.vdog.VDogApplication这个壳,知道的还请赐教一下这到底是什么壳哈。粗略看了一下壳的java部分代码:
壳还原了elf的文件头,这个没啥新意,手动还原一下so的前四个字节为.ELF就行了,

 

之后把libvdog-x86.so拉入ida反汇编。结果这个so被混淆得相当美妙,尝试了几个去混淆的脚本都无能为力,我就暂时不想分析这个壳了。那接下来怎么搞,我想了一个办法就是先把dex dump出来,然后利用frida去动态注入调试。

然后接下来就跟预想的一样,有检测frida的代码,一旦spwan或者attach上去,app的进程立马就自动中止了。

1

2

[FRD AL00::com.**.*****]-> Process terminated

[FRD AL00::com.**.*****]->

接着用xcube来加载脚本发现可行。好吧,那先用FDex2把dex dump出来,一共7个dex被dump出来。

4. 正式开始

首先第一步先要突破http抓包的问题,用fiddler抓包,看到https的握手请求User-Agent: okhttp/4.9.1,确定用的是okhttp库。试了几个ssl unpinning的脚本无果,自己分析了一下okhttp/4.9.1库,发现网上给出的脚本有个问题,CertificatePinner.check$okhttp的overload的第二个参数不对,正好这个方法也没有重载,直接就把overload去了,这边给出okhttp 4.9.1可用的ssl unpinning脚本:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

// okhttp4

try {

    var CertificatePinner = Java.use('okhttp3.CertificatePinner');

 

    CertificatePinner.check.overload('java.lang.String''java.util.List').implementation =function(str) {

        writeFile('! Intercepted okhttp4 in [check()]: ' + str);

        return;

    };

 

    try {//.overload('java.lang.String''kotlin.jvm.functions.Function0')

        CertificatePinner.check$okhttp.implementation = function(str, _) {

            writeFile('! Intercepted okhttp4 in [check$okhttp]: ' + str);

            return;

        };

    } catch (ex) {

        writeFile("is this Okhttp3 ?!");

    }

    writeFile('* Setup okhttp4 pinning')

} catch (err) {

    writeFile('* Unable to hook into okhttp4 pinner')

    writeFile(err);

}

测试后成功抓包,下面是登录接口的请求body:
可以看到登录接口的密码是加密的, 还有sign字段。首先寻找密码加密的关键代码如下:
再跟入:

使用rsa对密码进行加密,这个unname方法就是一个绝妙的注入点,编写获取public key的脚本:

1

2

3

4

5

6

7

8

9

// 密码加密:输出RSA的pubkey

var this3 = Java.use("com.fw.x5app.encrypt.this3");

this3.unname.implementation = function(str, str2){

    writeFile('unname is called');

    writeFile("pubkey:" + str2);

    var ret = this.unname(str, str2);

    writeFile('unname ret value is ' + ret);

    return ret;

};

用java还原算法:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

try {

    String password = "qed";

    String publicKey = "MIGfMA0GCSqGSIb3D********qGWVMv5z6FwIDAQAB";

    byte[] decoded = Base64.decode(publicKey, Base64.DEFAULT);

    RSAPublicKey pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));

 

    Cipher instance = Cipher.getInstance("RSA/ECB/PKCS1Padding");

    instance.init(ENCRYPT_MODE, pubKey);

    String pwdenc =Base64.encodeToString(instance.doFinal(password.getBytes(StandardCharsets.UTF_8)), Base64.DEFAULT);

    Log.e(TAG, pwdenc);

 

} catch (Exception e) {

    e.printStackTrace();

}

好了,到此密码加密的算法已经搞定。接下来来看sign算法,在dex中搜索sign找到关键位置:

再跟进SignUtil类里面:
可以看到关键的方法handle在native-lib里面, 通过hookget方法可以获取传入验签的参数和sign结果:

1

2

3

4

5

6

7

8

9

10

11

12

13

var SignUtil = Java.use("com.fw.x5app.encrypt.SignUtil");

SignUtil.get.implementation = function(str, str2, str3, str4){

    writeFile('get is called');

 

    writeFile("str:" + str);

    writeFile("str2:" + str2);

    writeFile("str3:" + str3);

    writeFile("str4:" + str4);

 

    var ret = this.get(str, str2, str3, str4);

    writeFile('get ret value is ' + ret);

    return ret;

};

将libnative-lib.so加载到ida分析,分析结果如下:
sub_45A40函数:
上图已经对关键的方法和变量做了注释,逻辑已经很清晰了,sign=sha256(_requestData+_requestDataList+_token+_clientTime+salt_key),为了验证拼接得对不对,可以把temp_str输出,寻找一出合适的hook点:
这处就可以进行hook来输出,对应的asm代码如下:
这边要注意一下这个temp_str的类型是std::string,要想办法转换成cstring才能打印输出,我们可以利用上面分析出来的string_to_cstr函数来转换:
分析完了,我们来写hook代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

var libnative_addr = Module.findBaseAddress("libnative-lib.so")

writeFile("libnative_addr is: " + libnative_addr)

 

// 内部一个std::string转cstring方法

var str_to_c = new NativeFunction(libnative_addr.add(0x45A85), "pointer", ["pointer"]);

 

// 输出签名字符串

try{

    var addr_45266 = libnative_addr.add(0x45267);

    writeFile("addr_45266: " + addr_45266);

 

    Interceptor.attach(addr_45266, {

        onEnter: function (args) {

            writeFile("ohwawawa");

            var ret = str_to_c(this.context.r2);

            writeFile("addr_45266 OnEnter sign string:" + Memory.readCString(ret));

        },

        onLeave: function (retval) {

             //console.log("retval is :", retval)

        }

    });

} catch(err) {

    writeFile("[!!!!!!!!!!!!] " + err);

}

这样一旦执行签名函数, 签名所有的参数、签名字符串拼接和返回结果全部都能完美输出。这个算法就比较简单不再写代码还原了。

5. 后记

app本身的防护手段还是不错的,做了很多校验。还有这个vdog的壳代码量还是比较惊人的,内部大量复杂的加解密操作和校验的操作,而且还做了复杂的混淆,以后有时间了可以来研究一下这家伙是如何对frida进行防护的。我们巧妙使用xcube来逃过壳对frida的spwan和attach检测,还有一个注意的点就是能看到我写的frida脚本都没法使用console.log输出,都是直接用写文件的方式,这是因为用console.log根本无法输出任何内容, 我猜测这个也是壳做的屏蔽, 极有可能直接hook了底层的log函数,可谓恶心至极。还有像这种体量的app代码量还是很惊人的,而且被拆分成非常多的dex,在分析时必定会在几个dex各种交叉分析,这时候需要的是耐心。收工!




(来源:看雪)

(原文链接:https://bbs.pediy.com/thread-272202.htm

发表评论

点击排行

钓鱼邮件-如何快速成为钓鱼达人

一、前言在大型企业边界安全做的越来越好的情况下,不管是 APT 攻击还是红蓝对抗演练,钓鱼邮件攻击使用的...

【渗透实战系列】| 1 -一次对跨境赌博类APP的渗透实战(getshell并获得全部数据)

本次渗透实战主要知识点:1.app抓包,寻找后台地址2.上传绕过,上传shell3.回shell地址的分析4.中国蚁剑工...

HTTPS - 如何抓包并破解 HTTPS 加密数据?

HTTPS 在握手过程中,密钥规格变更协议发送之后所有的数据都已经加密了,有些细节也就看不到了,如果常规的...

华为防火墙实战配置教程,太全了

防火墙是位于内部网和外部网之间的屏障,它按照系统管理员预先定义好的规则来控制数据包的进出。防火墙是系...

无线电安全攻防之GPS定位劫持

一、需要硬件设备HackRFHackRF 连接数据线外部时钟模块(TCXO 时钟模块)天线(淘宝套餐中的 700MHz-2700MH...

记一次Fastadmin后台getshell的渗透记录

1.信息搜集先来看看目标站点的各种信息后端PHP,前端使用layui,路由URL规则看起来像ThinkPHP,那自然想到...

ADCS系列之ESC1、ESC8复现

对原理感兴趣的可以去https://www.specterops.io/assets/resources/Certified_Pre-Owned.pdf看原文,这里只...

【干货分享】利用MSF上线断网主机的思路分享

潇湘信安 Author 3had0w潇湘信安一个不会编程、挖SRC、代码审计的安全爱好者,主要分享一些安全经验、...

扫描二维码下载APP