密码学中的HASH算法

xiaoeryu Lv5

HASH,一般翻译做散列、杂凑,或音译为哈希,是把任意长度的输入(又叫做预映射pre-image)通过散列算法变换成固定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,散列值的空间通常远小于输入的空间,不同的输入可能会散列成相同的输出,所以不可能从散列值来确定唯一的输入值。简单的说就是一种将任意长度的消息压缩到某一固定长度的消息摘要的函数。

HASH算法的应用

HASH算法在当前被广泛的应用在信息传输中的错误校验、信息安全等领域,比如简单地对函数的前几个字节进行crc32校验用于inline hook的检测、签名验证等

常用的hash算法:

CRC家族如CRC16、CRC32等

MD4的改进版MD5,更安全,在抗分析和差分方面表现更好

SHA-1、SHA-256等,抗穷举(brute-force)性更好

安全性

MD5、SHA1的破解

2004年8月17日,在美国加州圣芭芭拉召开的国际密码大会上,山东大学的王小云教授首次宣布了她以及她的研究小组的研究成果——对MD5、HAVAL-128、MD4和RIPEMD四个著名密码算法的破译结果。次年二月宣布破解SHA-1密码。

一些研究表明SHA-256在特定的情况下也存在碰撞的可能性

HASH算法之CRC32

CRC检验原理实际上就是在一个p位二进制数据序列之后附加一个r位二进制检验码(序列),从而构成一个总长为$n = p ➕r$位的二进制序列;附加在数据序列之后的这个检验码与数据序列的内容之间存在着某种特定的关系。如果因干扰等原因使数据序列中的某一位或某些位发生错误,这种特定关系就会被破坏。因此,通过检查这一关系,就可以实现对数据正确性的检验。

优点:检错能力极强,开销小,易于用编码器及检测电路实现。从其检错能力来看,它所不能发现的错误的几率仅为*0.0047%*以下。从性能上和开销上考虑,均远远优于奇偶校验及算数和校验等方式

CRC的本质

是模-2除法的余数,采用的除数不同,CRC的类型也就不一样。通常,CRC的除数用生成多项式来表示。最常用的CRC码及生成多项式名称有CRC-12、CRC-16、CRC-32,对应的输出即hash value分别为12位、16位和32位

在Java中使用和hook CRC32

Java类:Java.util.zip.CRC32

  • Java本身提供了这个类,直接引入包名调用即可

  • hook的时候跟之前一样对*update()getValue()*进行hook就可以了

    function hookCRC32(){
        if(Java.available){
            Java.perform(function(){
                var CRC32 = Java.use('java.util.zip.CRC32');
                CRC32.update.overload('[B').implementation = function(bytes){
                    console.log('CRC32->update: ' + JSON.stringify(bytes));
                    var StringClass = Java.use('java.lang.String');
                    var str = StringClass.$new(bytes);
                    console.log('CRC32->update str: ' + str);
                    var result = this.update(bytes);
                    return result;
                }
                CRC32.getValue.implementation = function(){
                    var result = this.getValue();
                    console.log('CRC32->getValue: ' + result);
                    return result;
                }
                })
        }
    }
    setImmediate(hookCRC32);
    
    • 这里以皮皮虾为例测试一下

      frida -U -f com.sup.android.superb -l hash.js --no-pause

在so中识别CRC32

为了提高CRC32的计算效率,往往使用查表法,因此就引入了常量表。该查询表可以使用静态的数据,也可以动态生成,因此只需要使用FindCrypt自动识别即可(下图为FindCrypy中的yara规则);同时CRC32的输出长度与输入数据无关,始终为32位。

例如,我们拿编写的没有加混淆的demo app来反编译看一下

直接ctrl + alt + F使用findcrypt插件定位到常量表

进入常量位置,用交叉引用定位到调用常量位置

接下来这里IDA直接识别出来算法代码了

拿ollvm混淆过的看一下

依然可以定位

交叉引用

跟进去之后看一下它混淆后的流程

  • 还不是特别复杂
  • 在这个位置F5很难看出来它具体有什么功能,当然需要的话也可以hook这个函数

这个函数继续往上交叉引用

  • 进来之后就看到直接定位到JNI函数当中了,有三个参数a3就是我们输入的内容,a1还是JNIENV*

改完之后看一下,这里面是使用哪个函数来处理我们输入的参数的

  • 看到这个函数接受了我们的参数,有一个返回值暂时将它改为返回的指针
  • 继续分析对这个指针接下来的两处处理

第一处是一个赋值操作暂时先不管它,看第二处计算了它的长度

  • 这里分析起来还是比较难看出来什么
  • 还是写frida脚本重放hook
另一种思路

因为,使用插件找到有调用CRC32常量的地方,所以可以猜测这里面一定是有调用CRC32的函数,所以往上找就能直接找到它的JNI函数

  • 可以看到这里JNI函数的混淆还是比刚才的要复杂的

    • 同样也定位到了这个位置,接下来还是编写frida脚本去重放就可以了

HASH算法之MD5

MD5的输出长度是128位,与输入内容无关。也存在一些常量,同样可以使用FindCrypt插件去识别。

SHA

在SHA家族中的SHA1、SHA256、SHA512中,也均有对应的不相同的常量表输出长度分别为160、256、512。这些都是算法的明显特征,这些算法也都有常量表都可以通过插件识别。在识别到之后通过frida编写脚本去查看函数的输入和输出,从而去分析。

总结:

  1. 针对Java层中的常见加解密算法的快速识别和逆向分析,可以重点对Cipher类相关的函数进行hook;
  2. 针对so中的常见加解密算法的快速逆向可以着手从常量表的识别、输入输出特征、控制流特征等结合;其中,动态的重放是一个极为快速高效的办法。
附件

示例代码

  • 标题: 密码学中的HASH算法
  • 作者: xiaoeryu
  • 创建于 : 2024-03-10 15:01:06
  • 更新于 : 2024-03-10 15:18:18
  • 链接: https://github.com/xiaoeryu/2024/03/10/密码学中的HASH算法/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论