• 标 题:超星PDG转换(10千字)
  • 作 者:heXer
  • 时 间:2002-10-26 17:02:53
  • 链 接:http://bbs.pediy.com


======================================
“超星读书卡”持卡用户可以通过Internet下载超星数字图书馆中的数字图书。

令很多用户困扰的是在一台机器上下载的文件,在另一台机器上无法直接阅读。

虽然可以通过获取离线注册码的方式解决,但总感觉不尽方便,能否妥善解决?

也许我们注意到在线阅读时是不分那台机器的,可以想象其PDG文件在从网上传

到机器内时是不具备机器识别特征的,这些文件是下载到本地时在本地加密后存

盘的。

因此我们有可能在分析出其本地加密算法,然后推演出其逆算法,将其还原

成原始文件,那么就可以实现自由的离线阅读了。

经过算法分析,证明此方法是可行的,并做出这个demo版的小工具:

1. 此工具暂没有批量转换功能,一次只能转换一个选定文件

2. 我只做了不全面的简单测试,不一定适用于所有的PDG文件

3. 我用的是SSReader3.6及以前版下载的PDG文件,通过测试

4. 现在SSReader3.7的加密方式已经改变,肯定不支持,有待以后分析

======================================

以下论述是我跟踪分析的大致结果,表述不是很精确,有些是猜测
所有分析是基于3.6版的,新版的格式可能会增加内容或有所改变,
我的分析是针对于两种主要类型(格式)的,其实也是目前绝大部分
图书所采用的类型,一种是我们在线阅览的那种pdgtype=02h
另一种是下载存储到本地硬盘文件的那种pdgtype=10h
因此以下描述的结构只能保证对这两种文件有效,其它类型的还需
进一步的分析验证.

==========================================================================================
1. PDG文件的大致结构:

PDG2_file STRUCT
    HH_header<>
    optional header<>
    PDG_data<>
PDG2_file ENDS

HH_header STRUCT
    dw    4848h            ;超星文件特征标志
    db    02h            ;PDG_VERSION=2
    db    00h            ;我见到的PDG2这里都是0,就算默认吧
    dd    ?            ;不知道,对我们似乎没有价值
    dd    offset_optional_header    ;optional_header在文件中的偏移地址,应该等于0Ch吧
HH_header ENDS

optional_header STRUCT
    db    80h,00h,00h        ;没什么好说的,算是标志吧
    db    pdgtype            ;文档类型,我只分析02h和10h两种类型
    dw    x_pix            ;扫描图像的横向参数
    dw    y_pix            ;扫描图像的纵向参数
    db    01h,00h,00h,00h        ;按默认算吧
    dd    offset_PDG_data        ;扫描图像数据在文件中的偏移地址,大概都是8Ch吧
    dd    size_PDG_data        ;扫描图像数据的字节数
    dd    8 dup (?)        ;作用可能不大,把它们添上0不影响页面的显示
    key_data<>
    db    1Ch dup (?)        ;作用可能不大,把它们添上0不影响页面的显示
optional_header ENDS

key_data STRUCT
    db    1Eh dup (?)        ;这里有时是一个有关超星公司字符信息,有时为空(全0)
    dw    ?            ;我不清楚什么含义
    db    8 dup (?)        ;可能有几处默认是0
    dd    SS_user_key        ;重要的数据,可用于解码还原x_pix和y_pix
    dd    ?            ;
key_data ENDS
==========================================================================================
2. 我所想象的图书阅览管理过程(基于简单的分析,未必准确,仅供参考)
    超星图书服务器上所存储的图书是扫描生成的(废话,大家都知道),是按pdgtype=02h存储的,
文件结构与上述结构相符,单其扫描数据PDG_data是经过加密的,加密过程如下:
    30h字节长的key_data经过md5运算得到128位即16字节的数据,此16字节的数据作为密钥,采用
一种分组加密算法以16字节为单位进行加密运算,直到数据结束,此分组加密算法我根据手头的不多
的密码学算法比较,我没有识别出来,我暂且称之为encode_sub,我将它放在后面了,大家有兴趣的可
以帮忙看一下,有知道可以的告诉我,万分感谢.
    当我们在线阅览时,ssreader.exe中的相对应decode_sub将数据解码后,就可以显示出来了.

    在下载到本地硬盘时,文件存储成pdgtype=10h类型的了,PDG_data扫描数据是用解码后的原始
数据存储的,但它把optional_header中部分数据加密了,加密过程如下:
    跟据每台机器的硬盘C:分区的卷序列号和空间大小对应此机器的SS机器码,由SS机器码可以算
出一个word值,暂时称为SS_w,这段算法我没去分析,对我们用处也不大.在下载存盘时还需要一个随
机word值RANDOM_w,RANDOM_w实际就作为SS_user_key的高16位,由SS_w和RANDOM_w再算出一个word
值作为SS_user_key的低16位,因其算法可逆,所以可以用SS_user_key还原出原来的SS_w,这也是原程
序用来检测当前硬盘的SS_w和下载文件的SS_w是否一致的方法,不一致则提示你用户不对不是无法阅
读.
    接下来用SS_user_key(用SS_w和RANDOM_w算法稍变也可以)算出两个word值,分别去减x_pix和
y_pix,最后把pdgtype添上10h.这样与我们有用的处理就算结束了,存盘后就可以了.
==========================================================================================
3. 我们需要做的工作
    我们只要把pdgtype=10h的文件转变成pdgtype=02h的文件,就可以实现不受硬盘的限制脱机自由
阅读了.过程大致这样吧,大家也应该可以想到了,我用语言描述很费尽的,因此这里用汇编代码描述吧:

下面这段算法在pdg2.dll可以找到,我贴的稍有调整,并去掉了SS_w的检测

          mov ebp,SS_user_key
          shr eax, 10h
          xor edx, edx
          mov esi,1FFh
          div esi
          mov ecx, edx
          mov eax,ebp
          shr eax,10h
          xor edx, edx
          mov esi,0DBh
          div esi
          imul ecx, edx
          lea edx, dword ptr [ecx-000050EEh]
          mov ebx, ebp
          sub ebx, edx
          and ebx, 0000FFFFh
          shr ebp, 10h
          mov eax, ebx
          xor edx, edx
          mov ecx, 000003FBh
          mov edi, 00000083h
          div ecx
          imul ebx, ebp
          mov eax, ebp
          mov ecx, edx
          xor edx, edx
          div edi
          mov eax, edx
          sub eax, ebx
          sub eax, ecx
          sub ecx, ebx
          sub ecx, edx
      add x_pix,ax        ;还原x_pix
      add y_pix,cx        ;还原y_pix

为了看起来干净利索,我们把key_data区域都清0,不这样也没问题,不过有几处可能不要乱数
      lea edi,key_data
      mov ecx,30h
      xor eax,eax
          rep stosb
然后对30h字节长度的key_data进行一次md5,md5我就不贴在这里了,太费篇幅了,大家都可以
找到,我们下面要用到这128位(16字节)的结果作为分组加密算法的密钥,实际上我们的key_data
已经是清0的了,结果也是固定不变的了,也可以直接用现成的结果.我假设结果存在key_128处.

为转成pdgtype=02h文件,PDG_data要用encode_sub加密

                mov    edi, size_PDG_data        ;数据长度
                cmp    edi, 10h
                jl      data_encode_end
                mov    ebx, key_128
                mov    esi, offset PDG_data
                shr    edi, 4
@@:            push    ebx                ;密钥指针
                push    esi                ;数据指针,加密结果也存储在这里
                call    sub_encode            ;分组算法,每次加密10h字节
                add    esp, 8
                add    esi, 10h
                dec    edi
                jnz    @B
data_encode_end:                    ;处理结束

;下面是完整的分组加密算法,在********.exe中可以找到
;其中注释是我分析时加的,不尽准确,本想删去,后来一想算了,献丑也罢,只当交流了
;开始看时感觉上有点象RC5族算法,但越看越不象,资料有限,还是确认不了,望高手指教

encode_sub      proc near
var_2C          = dword ptr -2Ch
var_28          = dword ptr -28h
var_24          = dword ptr -24h
var_20          = dword ptr -20h
var_1C          = dword ptr -1Ch
var_18          = dword ptr -18h
var_14          = dword ptr -14h
var_10          = dword ptr -10h
var_C          = dword ptr -0Ch
var_8          = dword ptr -8
var_4          = dword ptr -4
arg_0          = dword ptr  8            ;pData
arg_4          = dword ptr  0Ch        ;pKey
                push    ebp
                mov    ebp, esp
                add    esp, 0FFFFFFD4h
                mov    eax, [ebp+arg_0]    ;pData
                mov    edx, [eax]        ;A
                mov    [ebp+var_4], edx    ;sa=A
                mov    edx, [eax+4]        ;B
                mov    [ebp+var_8], edx    ;sb=B
                mov    edx, [eax+8]        ;C
                mov    [ebp+var_C], edx    ;sc=C
                mov    edx, [eax+0Ch]        ;D
                mov    [ebp+var_10], edx    ;sd=D
                mov    eax, [ebp+arg_4]    ;pKey
                mov    edx, [eax]        ;ka
                mov    [ebp+var_14], edx    ;ka
                mov    edx, [eax+4]        ;kb
                mov    [ebp+var_18], edx    ;kb
                mov    edx, [eax+8]        ;kc
                mov    [ebp+var_1C], edx    ;kc
                mov    edx, [eax+0Ch]        ;kd
                mov    [ebp+var_20], edx    ;kd
                xor    edx, edx        ;c1
                mov    [ebp+var_24], edx    ;c1
                mov    [ebp+var_28], 9E3779B9h    ;c0
                mov    [ebp+var_2C], 10h    ;r
@@:            mov    ecx, [ebp+var_28]    ;c0
                add    [ebp+var_24], ecx    ;c1+c0
                mov    eax, [ebp+var_8]    ;sb
                shl    eax, 4            ;sb<<4
                add    eax, [ebp+var_14]    ;sb<<4+ka
                mov    edx, [ebp+var_8]    ;sb
                add    edx, [ebp+var_24]    ;sb+c1
                xor    eax, edx        ;(sb<<4+ka) xor (sb+c1)
                mov    ecx, [ebp+var_8]    ;sb
                shr    ecx, 5            ;sb>>5
                add    ecx, [ebp+var_18]    ;sb>>5+kb
                xor    eax, ecx        ;(sb<<4+ka) xor (sb+c1) xor (sb>>5+kb)
                add    [ebp+var_4], eax    ;sa=sa+[(sb<<4+ka) xor (sb+c1) xor (sb>>5+kb)]
                mov    eax, [ebp+var_C]    ;sc
                shl    eax, 4            ;sc<<4
                add    eax, [ebp+var_1C]    ;sc<<4+kc
                mov    edx, [ebp+var_C]    ;sc
                add    edx, [ebp+var_24]    ;sc+c1
                xor    eax, edx        ;(sc<<4+kc) xor (sc+c1)
                mov    ecx, [ebp+var_C]    ;sc
                shr    ecx, 5            ;sc>>5
                add    ecx, [ebp+var_20]    ;sc>>5+kd
                xor    eax, ecx        ;(sc<<4+kc) xor (sc+c1) xor (sc>>5+kd)
                add    [ebp+var_8], eax    ;sb=sb+[(sc<<4+kc) xor (sc+c1) xor (sc>>5+kd)]
                mov    eax, [ebp+var_10]    ;sd
                shl    eax, 4            ;sd<<4
                add    eax, [ebp+var_14]    ;sd<<4+ka
                mov    edx, [ebp+var_10]    ;sd
                add    edx, [ebp+var_24]    ;sd+c1
                xor    eax, edx        ;(sd<<4+ka) xor (sd+c1)
                mov    ecx, [ebp+var_10]    ;sd
                shr    ecx, 5            ;sd>>5
                add    ecx, [ebp+var_20]    ;sd>>5+kd
                xor    eax, ecx        ;(sd<<4+ka) xor (sd+c1) xor (sd>>5+kd)
                add    [ebp+var_C], eax    ;sc=sc+[(sd<<4+ka) xor (sd+c1) xor (sd>>5+kd)]
                mov    eax, [ebp+var_4]    ;sa
                shl    eax, 4            ;sa<<4
                add    eax, [ebp+var_1C]    ;sa<<4+kc
                mov    edx, [ebp+var_4]    ;sa
                add    edx, [ebp+var_24]    ;sa+c1
                xor    eax, edx        ;(sa<<4+kc) xor (sa+c1)
                mov    ecx, [ebp+var_4]    ;sa
                shr    ecx, 5            ;sa>>5
                add    ecx, [ebp+var_18]    ;sa>>5+kb
                xor    eax, ecx        ;(sa<<4+kc) xor (sa+c1) xor (sa>>5+kb)
                add    [ebp+var_10], eax    ;sd=sd+[(sa<<4+kc) xor (sa+c1) xor (sa>>5+kb)]
                mov    eax, [ebp+var_2C]    ;r
                dec    eax            ;r-1
                mov    [ebp+var_2C], eax    ;r=r-1
                jnz    @B
                mov    eax, [ebp+arg_0]    ;pData
                mov    edx, [ebp+var_4]    ;sa
                mov    [eax], edx        ;A=sa
                mov    edx, [ebp+var_8]    ;sb
                mov    [eax+4], edx        ;B=sb
                mov    edx, [ebp+var_C]    ;sc
                mov    [eax+8], edx        ;C=sc
                mov    edx, [ebp+var_10]    ;sd
                mov    [eax+0Ch], edx        ;D=sd
                mov    esp, ebp
                pop    ebp
                retn
encode_sub      endp
==========================================================================================
后记:文章我是最懒得写的,写起来真是辛苦,花了数个小时,总感觉表述的不是很满意,无奈已经尽力了
    分析水平有限,错误之处在所难免,唯恐误人子弟,读者朋友们见谅了,希望大家能提出自己的主见.
==========================================================================================
          heXer/iPB
          2002.10.26