首页
论坛
课程
招聘
[讨论]对office加密文档的快速破译问题的研究
2008-11-1 17:27 132349

[讨论]对office加密文档的快速破译问题的研究

2008-11-1 17:27
132349
破解OFFICE文档(word和excel)的两种方法有:
1、使用黑客字典或暴力穷举方式来猜测用户的口令字;
2、采用全查表方式直接获取加密OFFICE文档的密钥,然后通过密钥去解密文档;方法2与方法1的区别是只能获取到文档内容,而不能获取到文档的加密密码。
    这两种方法都需要知道office系统软件的加密机制,有兴趣的朋友可以去参考这篇文章:http://www.team509.com/download/MS_Word_encrypt.pdf。并且此文章对于方法1已经描述得很清楚了,在这里就不重复了。下面就重点讨论方法2:

大家可以去看另外一个牛人写的帖子:
【原创】Word Password Recovery Master 2.0.0.4 程序分析
http://bbs.pediy.com/showthread.php?p=369640&highlight=word#post369640
对此有一个大概的了解之后,继续下面的细节方面的讨论。

    方法2简单的说就是让客户端提取OFFICE文档中的密钥流,发送给服务器,利用服务器的计算资源快速求解出解密密钥,再将密钥传回客户端,在本地解密OFFICE文件,从而得到文件内容。
    而密钥流是通过RC4算法加密过的密钥。1个40bit密钥,根据RC4算法,就可以产生出相应的密钥流(乱数),如果能做到这样,那么就可以事先建立起2^40个对应关系,记为T 表;有了这个密钥流,再根据T 表反向查表,即可快速得到40比特密钥(输入),有了密钥即可对密报进行立即解密。
    所以问题的关键就在两个:
    1、获取到这个密钥流。根据 乱数 = 密文 xor 底码 的数学公式,如果底码为0,那么乱数就=密文。所以我们只要找出OFFICE文档中一段固定的5字节或全0处就行了。
    2、建立全查表。这样就定义出了一个函数关系f,即 5字节key(输入)A  映射到  5字节ks(输出)B。这个全查表有2^40条记录。

关于全查表,目前还存在一些细节方面的问题,有请jeffcjh和各位感兴趣的朋友来帮忙解答,谢谢:

1、在没有压缩和其它任何优化处理的情况下,产生的全查表大概占多大的磁盘空间呢?
2、因为我还没有生成这个全查表,所以不清楚B5字节key(输入)A  映射到  5字节ks(输出)B的关系,按照您说的意思,5字节ks(输出)B存在大量的碰撞,也就是说,同一个5字节ks(输出)B,会有2^24个key(输入)A 值的对应?在2^16个文件中,每个文件中的只需要存放A值就够了,因为这些A值所对应的B值都是一样的?如果这样的话,不会存在根据客户端传回的B值而查询不到A值的情况吧。
3、这样说的话,前期的主要准备工作就不是对B值进行排序,而是把相同的B值划分到同一个文件中了(有什么效率比较高的方法吗)?那么查询速度的瓶颈就在于查询这个有2^24个A的文件上了?

注:由于此贴是从【求助】关于微软公布的office二进制文档格式 http://bbs.pediy.com/showthread.php?t=72128 转过来的,可能有些描述不是很清楚。有不明白的地方可以先去那个帖子看一下。

[2022冬季班]《安卓高级研修班(网课)》月薪三万班招生中~

收藏
点赞0
打赏
分享
最新回复 (257)
雪    币: 58
活跃值: 活跃值 (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jeffcjh 活跃值 2008-11-1 19:30
2
0
1、全查表需要占用5TB的硬盘空间。5TB是这样计算出来的:
   5TB = (2^40) * 5,
   其中 2^40 表示记录总数,5 表示每个记录要占用5字节。

   如果采用差分保存技术,可以将5TB的需求降到2TB左右。

2、你的理解是对的,每个文件中的2^24个A(输入)按照映射关系都对应同一个输出B。根据5字节的B值来查表,对应文件中的2^24个A值都是“嫌疑”对象(可能的密钥),到底哪一个是“真凶”(加密该office文件的正确密钥),验证依据就是48字节随机数中后32字节的md5依赖关系。

3、将2^40个 B--A  函数对应关系摊分到2^16个文件中,每个文件保存2^24个对应关系,在每个文件内做快速排序,这样总的排序时间为
     T = (2^16) * O( (2^24) * lg(2^24) ),
T 表达式中的前者为文件数,后者是每个文件内部快速排序的时间复杂度。

而对原始的2^40个记录进行快速排序的时间为 O( (2^40) * lg(2^40) ),
显然T大约是此值的1/2左右,所以分割后总效率上要高一点。

需要说明的是,这些讨论的技术都是基于自己来实施的假设。
如果你想利用那几个网站来进行破译,我们根本就不需考虑这些啦(网站帮我们实现了这些功能)。
雪    币: 201
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiaoxinxx 活跃值 2008-11-2 21:38
3
0
1、会不会存在根据客户端传回的B值而查询不到A值的情况?
2、有没有一些技巧方法能够方面快速的生成文件,并按照5字节ks(输出)B值来划分到同一个文件中呢?
雪    币: 58
活跃值: 活跃值 (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jeffcjh 活跃值 2008-11-3 19:11
4
0
1、基本不存在这种情况,即可能性极小(采用彩虹链时失败概率小于千分之一),采用现在讨论的全查表方法的成功率为100%;

2、这个完全可以。根据Word加密机制,对2^40个输入A,依次计算其输出B时,根据情况分类存入到2^16个文件中去即可。但是,要注意的是,计算全部2^40个输入的输出值,这个任务在你的单机上是难以完成的,大约需要几个月的时间,所以真正实施时必须采用并行技术技术,多台机器同时做才行。
雪    币: 201
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiaoxinxx 活跃值 2008-11-3 23:42
5
0
thank!!!
雪    币: 58
活跃值: 活跃值 (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jeffcjh 活跃值 2008-11-4 09:45
6
0
互相学习,共同提高。
雪    币: 200
活跃值: 活跃值 (16)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Xusually 活跃值 2008-11-6 21:14
7
0
用过那个工具,旧版现在已经不好用了好像
雪    币: 201
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiaoxinxx 活跃值 2008-11-7 11:13
8
0
"给定40比特(5字节)密钥 key 后,经由md5得到128字节的rc4密钥,再通过rc4加密算法(注:包括两个阶段)生产出5字节的伪随机数 ks (密钥流)。"

碰到个问题,rc4加密机制是将明文和密钥流进行异或就可以产生密文,我得到128的rc4密钥之后,是不是通过这个密钥再来加密“40比特(5字节)”,然后产生ks?

我的做法是这样的:
循环输入40比特key,加上固定的填充后生成一个64字节的数,再进行一轮标准的md5 hash后产生128位的结果(这个过程应该就是您说的“给定40比特(5字节)密钥 key 后,经由md5得到128字节的rc4密钥”)。后面的过程(再通过rc4加密算法(注:包括两个阶段)生产出5字节的伪随机数 ks (密钥流))我就不太明白了。
雪    币: 58
活跃值: 活跃值 (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jeffcjh 活跃值 2008-11-7 23:40
9
0
“给定40比特(5字节)密钥 key 后,经由md5得到128比特的rc4密钥”这句话你的理解对了。

接下来就是根据这个128比特的密钥,按照RC4算法的两个阶段(密钥调度阶段KS和密钥流产生阶段)产生密钥流ks。本来密钥流可以无限长,但具体到我们的问题,其实只需要5字节的ks就够了。以后我们就是利用5字节ks来反向查表得到40bit密钥(输入)的。

注意:“有了128比特rc4密钥之后,不是通过这个密钥再来加密“40比特(5字节)”,而是按照RC算法产生ks,用ks再来加密明文的。这正是序列密码的一个基本特点,密钥流与明文通常是独立的。
雪    币: 201
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiaoxinxx 活跃值 2008-11-8 00:13
10
0
我就是不太明白怎样按rc4算法来产生ks,能不能根据你提供的rc4算法来详细说明一下,比如已经生成了128位的密钥(简称为C),那么怎样使用你改写的那个rc4算法来生成ks?
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
飞烟逝 活跃值 2008-11-8 01:05
11
0
这个要好好看一下才懂哦
雪    币: 58
活跃值: 活跃值 (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jeffcjh 活跃值 2008-11-8 23:02
12
0
我提供的RC4代码有四个函数:
1. void RC4_KSA(UCHAR *rc4key, int KL, RC4_STATE *state);
2. void RC4_KSA_128(UCHAR *rc4key, RC4_STATE *state);
3. UCHAR RC4_PRGA(RC4_STATE *state);
4. void RC4(UCHAR *rc4key, int keylen, UCHAR *keystream, int kslen);
其中前2个函数负责密钥初始化(前者为密钥长度为KL字节的,后者专门为密钥长度为16字节的),函数3每次生成1字节密钥流ks,函数4其实是对前面三个函数的封装。

现在你经过MD5已得到了128比特(即16字节)的密钥K[],那么调用函数4:
RC4(K, 16, ks, 5);
即可得到5字节的ks。

不过这5字节是密钥流的最初5字节,在具体利用时可能不是这个位置的5字节,那些网站取的位置比较靠后,并且还不是连续的位置(有间隔),这些都是小问题,根据实际需要调用函数4生产足够长度的密钥流,再提取对应位置的数据即可。
雪    币: 400
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
neteagle 活跃值 2008-11-9 18:36
13
0
这种方法理论和实践的产品有人做过的
雪    币: 58
活跃值: 活跃值 (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jeffcjh 活跃值 2008-11-9 19:43
14
0
当然有人做过啦,只是好像要花钱买的。

你觉得你能做吗?否则请不要这样子说话,有本事发表一点技术性的建议。OK?
雪    币: 201
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiaoxinxx 活跃值 2008-11-10 11:58
15
0
这几天在调试程序来生成全查表,按照您说的,B值最多就是2的16次方个,但是我的程序输入不同A,都会产生一个不同的B值,比如输入2^20个A,就会输出2^20个不同的B值,也就是说B值都没有碰撞。
雪    币: 58
活跃值: 活跃值 (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jeffcjh 活跃值 2008-11-10 17:56
16
0
输入A总共有2^40个,得到的输出B有2^40个,但这2^40个输出B是可能存在碰撞的(即不同的A对应到相同的B)。

你所说的输入2^20个A,就会输出2^20个不同的B值,也就是说B值都没有碰撞,这在实际中是可以存在的,并不矛盾。因为以上所说的是可能存在碰撞,但不一定碰撞。实际上这是一个概率问题,可以算出碰撞的可能性有多大。如果你把2^40个输入的输出都计算出来了,我感觉几乎一定会有碰撞的,还早呢,朋友。但我不知道你是否有这么大的计算能力(把所有2^40个都算一遍),你可以通过已计算2^20个结果来估算一下总时间。
雪    币: 201
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiaoxinxx 活跃值 2008-11-10 18:29
17
0
这是生成128比特的代码,jeffcjh帮看看对不对,thanks!!!:

ArrayByteA = array [0..4] of byte;
Array16Byte = array [0..15] of byte;

function MakeKey(byteA: ArrayByteA):Array16Byte;
var
  mdContext: MD5Context;
  pwarray: array [0..63] of byte;
  byteB: Array16Byte;
  block: DWord;
begin
  block := 0;
  Fillchar(pwarray,sizeof(pwarray),0);        //pwarray数组清零
  CopyMemory(@pwarray, @byteA, 5);     //拷过去40位(5个字节)
  //put block number in byte 6...9
  pwarray[5] := byte(block and $FF);
  pwarray[6] := byte((block shr 8) and $FF);
  pwarray[7] := byte((block shr 16) and $FF);
  pwarray[8] := byte((block shr 24) and $FF);
  pwarray[9] := $80;
  pwarray[56] := $48;

  //对pwarrey计算一轮标准的MD5 hash begin
  MD5Init(mdContext);
  MD5Update (mdContext, @pwarray, 64);
  MD5StoreDigest(mdContext);
  //对pwarrey计算一轮标准的MD5 hash end

  CopyMemory(@byteB, @mdContext.Digest, 16);
  result := byteB;
end;

生成byteB之后,就调用您的rc4函数来生成ks.
雪    币: 200
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
terrywolf 活跃值 2008-11-10 18:43
18
0
好好学习,天天向上
雪    币: 58
活跃值: 活跃值 (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jeffcjh 活跃值 2008-11-10 19:14
19
0
你的总体代码正确,但有一个细节我认为存在问题。

我认为你会C代码,我参考你的变量名,将这一块的正确C代码告知如下:
        unsigned char byteA[9];        // byteA[]的前5字节赋值为输入A
        unsigned char byteB[16];
        unsigned char ks[512];         // 存储生产的密钥流(乱数), 具体长度自己确定

        blockcnt = 0;             // 加密块编号,初值为0x00000000

        // byteA[]的第5至8字节赋值为块编号
        *(unsigned int *)(byteA + 5) = blockcnt;

        // 对9字节(不是64字节)作标准的MD5 !!!
        MD5Data((unsigned char*)byteA, 9, byteB);

        // 以16字节byteB[]做密钥, 根据RC4算法产生512字节乱数
        RC4(byteB, 16, ks, 0x200);

说明:
可见这一块其实是很简单的,你应能明白。
我认为存在的问题就在对数组byteA[]9字节之后的填充上。我用C语言写的代码就是对byteA的9字节作标准的MD5得到16字节散列值byteB。由于对你的上述byteA填充不是很清楚,所以提出这个疑问供参考。

另外C语言数组的下标从0开始,VB是从0还是从1开始,这个边界小问题敬请注意。
雪    币: 201
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiaoxinxx 活跃值 2008-11-10 19:16
20
0
按照您原来的说法,B值总共就只有2^16个,而现在我输入2^20个A值,就产生了2^20个不同的B值,这显然就已经不正确了啊。
雪    币: 58
活跃值: 活跃值 (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jeffcjh 活跃值 2008-11-10 19:20
21
0
不好意思,忘了对代码中的MD5进行说明,其C原型如下
unsigned char * MD5(const unsigned char *data, const unsigned int len, unsigned char *digest);
[in]参数data为数据首地址,
[in]参数len为数据长度,
[in, out]参数digest为最终输出的16字节md5散列值.
为方便使用,该函数实际上封装了标准MD5的三个函数,即函数实现为:
unsigned char * MD5(const unsigned char *data, const unsigned int len, unsigned char *digest)
{
    MD5_CTX ctx;

    MD5Init(&ctx);
    MD5Update(&ctx, data, len);
    MD5Final(digest, &ctx);

    return digest;
}
供参考,可以与你的VB版本的MD5进行比较使用。
雪    币: 201
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiaoxinxx 活跃值 2008-11-10 19:24
22
0
md5的算法应该都是通用的。那估计就是我的生成128比特的代码有问题了。能否贴出这部分代码让我参考一下,到底是哪儿出错?生成对照表这个程序困扰我一个星期了。比较郁闷。
雪    币: 58
活跃值: 活跃值 (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jeffcjh 活跃值 2008-11-10 19:25
23
0
你理解错了,输入A共有2^40个,相应的输出B值也有2^40个啊。我说的是这2^40个输出B是存储到2^16个文件中去。对于输出B,到底存到2^16个文件中的哪一个中去呢?这要你自己定原则了,比如可以考虑按照B的最低2字节来分类,B的最低2字节相同的就存到同一个文件中,这样自然总共会产生2^16个结果文件。
雪    币: 58
活跃值: 活跃值 (28)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
jeffcjh 活跃值 2008-11-10 19:28
24
0
那我觉得可能就是你对byteA[]9字节后的填充不对,就不用去填充,而是直接对9字节进行md5运算,具体代码我上面已经贴出来了啊。
雪    币: 201
活跃值: 活跃值 (15)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
xiaoxinxx 活跃值 2008-11-10 19:29
25
0
ok。我马上试一试看看。
游客
登录 | 注册 方可回帖
返回