【猿人学】 第二次js对抗第三题算法还原

699 阅读12分钟

概要:

第三题,简单的指令。守道笃行,心逸神宁。

image.png 题目提示:jsvmp,指令。

image.png

token加密原理:

使用sm3杂凑(信息摘要)算法的一种变体。并在字符串转换成bytes时对字节进行偏移。

字节偏移

image.png
顾名思义,string convert to bytes。 python的一种实现:
strtobytes=lambda x : [ord(i) for i n x] image.png
sm3中的strtobytes:
image.png
明显可以看出sm3中的strtobytes处理后的结果只有偶数,可能进行了某种偏移,比如: strtobytes=lambda x : [ord(i)-ord(i)%2 for i in x] image.png
或者如果strtobytes对每个字符的处理都是独立的且字符取值有明显的范围,我们可以让它处理一个包含所有可能字符的字符串,返回的结果便是字符与byte的映射。 由于这里sm3算法的参数是 时间戳+页数 的字符串,那么字符的范围就是 0-9:

image.png
以下便是字符串与byte的映射。

var strMap={
    0: 48
    1: 48
    2: 50
    3: 50
    4: 52
    5: 52
    6: 54
    7: 54
    8: 56
    9: 56
}

sm3杂凑算法:

想要算法还原就必须清楚sm3的实现原理以及加密过程。这份文档可以很清楚的领着你一步一步去实现sm3算法。
sm3杂凑算法实现原理

处理流程:

image.png

整个流程c语言实现如下:

 
char* sm3(char*msg,int len){
    //消息填充,生成长度 length%512==0 的填充字符串。
    FillMsg *fm= fill(msg,len);
    printf("fill:");
    for(int i=0;i<64;i++){
        printf("%x",(unsigned char)*(fm->msg+i));

    }
    printf("\n");
    //分组,每组消息大小为 512bit。
    int groupCount=fm->bitSize/512;
    
    word *iv_0,*iv_1 ,*block;
    iv_0=(word*) malloc(sizeof(word)*8);
    block=(word*) malloc(sizeof(word)*16);
    memcpy(iv_0,IV,sizeof(word)*8);
    //迭代对每一组消息进行扩展压缩
    for(int i=0;i<groupCount;i++){
        memcpy(block,(fm->msg+(i*(sizeof(word)*16))),sizeof(word)*16);
        for(int j=0;j<16;j++){
            *(block+j)= exchange(*(block+j));
        }
        iv_1= compress(iv_0,block);
        iv_0=iv_1;
    }
    //返回16进制字符串形式杂凑值
    return toHex(iv_1,8);
}
 

处理步骤:

  1. 消息填充

image.png
将原始消息末尾添加1bit并置1,然后补0直到消息长度 length%512=448。最后在消息末尾添加64位用来表示消息的真实长度(高位补0)。 例如:
原始消息 “abc” 其字节数组可表示为 “61 62 63”,经过消息扩展后变成61626380000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000018
2. 迭代压缩

image.png

先将消息分组,然后对每一组消息进行扩展与压缩。


word *compress(word* v,word* b){
    word ss1,ss2,tt1,tt2,A,B,C,D,E,F,G,H,*array,*res;
    A=*(v);
    B=*(v+1);
    C=*(v+2);
    D=*(v+3);
    E=*(v+4);
    F=*(v+5);
    G=*(v+6);
    H=*(v+7);
    //对消息进行扩展
    array= expand(b);
    printf("expand:\n");
    for(int i=0;i<132;i++){
        if(i%8==0&&i>0) printf("\n");
        printf("%08x ",*(array+i));

    }
    printf("\ncompress:\n");
    printf("    %08c,%08c,%08c,%08c,%08c,%08c,%08c,%08c\n",'A','B','C','D','E','F','G','H');
    printf("%02d  %08x %08x %08x %08x %08x %08x %08x %08x \n",-1,A,B,C,D,E,F,G,H);
    //对扩展后的消息进行压缩
    for(int j=0;j<64;j++){
        ss1=rot_left((rot_left(A,12)+E+rot_left(T_j(j),j)),7);
        ss2=ss1^rot_left(A,12);
        tt1= FF_j(A,B,C,j)+D+ss2+*(array+68+j);
        tt2= GG_j(E,F,G,j)+H+ss1+*(array+j);
        D=C;
        C= rot_left(B,9);
        B=A;
        A=tt1;
        H=G;
        G= rot_left(F,19);
        F=E;
        E= P_0(tt2);
        printf("%02d  %08x %08x %08x %08x %08x %08x %08x %08x \n",j,A ,B,C,D,E,F,G,H);
    }
    res=(word*) malloc(sizeof(word)*8);
    *(res)=A;
    *(res+1)=B ;
    *(res+2)=C ;
    *(res+3)=D ;
    *(res+4)=E ;
    *(res+5)=F ;
    *(res+6)=G ;
    *(res+7)=H;
    printf("%02d  %08x %08x %08x %08x %08x %08x %08x %08x \n",-1,*(v)^A,B,C,D,E,F,G,H);
    return  _xor(res,v); ;
}

  1. 获取杂凑值

image.png
其实就是迭代压缩完成之后 ABCDEFGH八个寄存器的值拼到一起的。
具体的细节以及一些常量和方法的声明可在样例代码以及官方文档中找到。
这里会介绍一些此次算法还原过程中会使用到的常量变量及方法:

  • IV初始值 7380166f 4914b2b9 172442d7 da8a0600 a96f30bc 163138aa e38dee4d b0fb0e4e (在扩展和压缩的过程中会用到)
  • 常量T Tj(x) (x<=15 && x>=0 ?0x79cc4519:0x7a879d8a)(在扩展压缩过程中会用到)
  • 中间变量SS1,SS2,TT1,TT2(在压缩过程中会用到)

如果想实现sm3算法的变体,可以更改IV初始值,常量T或者更改填充甚至压缩扩展中的处理逻辑。

实现源码:

sm3-c sm3杂凑算法

尝试还原

js加密源码
image.png 将js源码经过虚拟化后基本上全是跳转,其主要执行逻辑集中在 o==110 这个case的return中,通过断点调试可以发现j==59的case下面会执行参数加密,以及发起请求的动作。算是一个执行入口。通过调试还可以发现加密使用sm3开头的方法执行过程如下:
strtobytes->sum->hexstr

image.png

image.png

image.png

sm3处理逻辑由这个方法完成

image.png
再细心点你会发现:

image.png

image.png 加密主要使用window.SM3.prototype.sum 其实到这里已经有很多方法可以hook加密逻辑进行抓取了(模拟浏览器环境,魔改浏览器,写脚本一次跑完各个时间段时间戳与页数组合的token)。
继续往下走深挖出算法的意义不大,如果感兴趣的话可以尝试。

前提:

已知晓并理解甚至可以手动实现sm3的加密方法。
这里使用最直接的打日志方法在加密各个阶段的运算以及处理逻辑。
主要的观察范围是o==110 case中的操作。 打日志是一点一点加上去的是比较费时的需要耐心,静下心来去做。

细节:

image.png

image.png 调用SM3 方法会返回一个对象,包含了一个reg大小的为8的数组,这里刚好与sm3初始IV数组类似,但值不同。猜测这里是一个魔改点。

image.png

image.png
这里发现对常量T的值也进行了替换。

image.png sum调用后首先会调用strtobytes然后对生成的字节数组进行扩充。这不明显是sm3处理中对消息进行填充的步骤。不过这里strtobytes稍微有一些不对。

image.png 也就是上面提到的转换为bytes全为偶数,通过分析发现这里对字节进行偏移。还原方法如上所提到。

var strMap={
    0: 48
    1: 48
    2: 50
    3: 50
    4: 52
    5: 52
    6: 54
    7: 54
    8: 56
    9: 56
}

填充使用的是方法window.SM3.property._fill

image.png 注:这里算法对数据的操作结果都保留在数组chunk数组中。这里可以看出strtobytes对abc偏移为[96,98,98]

迭代压缩中使用的扩展方法是window.SM3.property._expand

image.png 如下是正常sm3算法对填充后消息的扩展 image.png 可以看出在扩展时并未对原始的sm3扩展逻辑进行魔改。那么魔改的地方就只有压缩部分了。

小总结:

到目前为止一共发现3个魔改点:

  1. strtobytes
  2. IV初始值
  3. 常量T的值

分析sm3迭代压缩这就需要打日志去分析算法内部执行的操作了。
完整版js源码
下载文件到本地直接对链接 download.python-spider.com/match2023/c… 进行自动响应替换掉原始js,便可看到加密过程中执行的日志。

(console.log("hook start rote left "),_hook1=r << a,console.log("rote left res:"+_hook1),_hook1): 
                        16 == j ? r == a : 
                        103 == j ? null :
                        6 == j ? i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n, 0, i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n)) : 
                        94 == j ? r != a : 
                        80 == j ?(console.log("hook start non ") ,_hook2=~i(l, r, a, n),console.log("non res"+_hook2), _hook2): 
                        2 == j ? r <= a : 
                        59 == j ? (
 
                                    we = i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n, true), 
                                    // console.log([l,C,i(l, C, -3)]),
                                    Fe = i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), 
                                    // console.log("res:"),
                                    // console.log(Fe),
                                    we instanceof i.constructor ?   (
                                      console.log("bool:true:"),
                                      console.log(we ),console.log(Fe),
                                        i.apply.call(we, n[0].n, Fe) 
                                      ): 
                                        (   
                                          console.log("bool:false:"),
                                          console.log(we[1]),console.log(Fe),(
                                            g!=undefined&&g.r!=undefined?(console.log(g.r),console.log(" ss1:"+g.ss1+" ss2:"+g.ss2+" tt1:"+g.tt1+" tt2:"+g.tt2+" ssr:"+g.ssr)):console.log('[]')
                                          ),
                                            be = we[0][we[1]], we[0] instanceof i.g ? 
                                                i.apply.call(be, n.n, Fe) : 
                                                (
                                                _wwqq=i.apply.call(be, we[0], Fe),
                                                console.log(_wwqq),
                                                _wwqq
                                                )
                                        )
                                ) : 
                        52 == j ? r !== a : 
                        68 == j ? (console.log("hook / start"),_hook3=r / a,console.log("res:"+_hook3),_hook3) : 
                        75 == j ? r ?(console.log("hook sub1 start:"+r) ,_hook4=--a[0][a[1]],console.log("res1:"+_hook4),_hook4 ):(console.log("hook sub2 start:"+r),_hook5= a[0][a[1]]--,console.log(_hook5) ): 
                        46 == j ? (61 == (ce = l[C++]) && (Ce = l[C++]), 53 == ce && (m = i(l, C, -2), C = m.i, Ce = m.n), 70 == ce && (Ce = parseFloat(i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n))), Ce) : 
                        69 == j ? (console.log("hook /= start"),_hook6=r[0][r[1]] / a,console.log("res:"+r[0][r[1]] ),r[0][r[1]] =_hook6) : 
                        85 == j ? !i(l, r, a, n) : 
                        111 == j ? r in a : 
                        87 == j ? undefined : 
                        120 == j ? (n.a = true, i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n)) : 
                        62 == j ? (console.log("hook rote right start"),_hook8=r >> a,console.log("res:"+_hook8),_hook8) : 
                        93 == j ? 
                        r instanceof a 
                        : 
                        81 == j ? (n.t && n.t[n.t.length - 1]).s = true : 
                        110 == j ? ( console.log("hook mod start"),_hook6=r % a,console.log("res:"+_hook6),_hook6 ): 
                        78 == j ? (n.m = true, je = i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), n.m = false, g[je] = n.u, i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n)) : 
                        95 == j ? n.n : 
                        38 == j ? r || i(l, a, u, n) : 
                        121 == j ? (console.log("hook |= start"),_hook6=r[0][r[1]] | a,console.log("res:"+_hook6),r[0][r[1]]=_hook6 ): 
                        60 == j ? i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n, 0, i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), C++, (m = i(l, C, -3), C = m.i, m.n)) : 
                        63 == j ? (console.log("hook * start"),_hook6=r * a,console.log("res:"+_hook6),_hook6) : 
                        19 == j ? (console.log("hook -= start "),_hook6=r[0][r[1]] - a,console.log("res:"+_hook6),r[0][r[1]]=_hook6) : 
                        92 == j ? r && i(l, a, u, n) : 
                        1 == j ? (ce = i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), n.m = !i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), he = i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), n.m = false, [ce, he, pe = i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n)]) : 
                        30 == j ? (console.log("hook & start args:"+r+","+a),_hook6=r & a,console.log("res:"+_hook6),_hook6) : 
                        24 == j ? +i(l, r, a, n) : 
                        101 == j ? r >= a : 
                        91 == j ? i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n, 0, C++, (m = i(l, C, -3), C = m.i, m.n)) : 
                        57 == j ? i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n).pop() : 
                        77 == j ? !
                        ((ue = i(l, r, a, n, true))[0] 
                        instanceof i.g
                        ) 
                        && delete ue[0][ue[1]] : 
                        119 == j ? r ? ++a[0][a[1]] : a[0][a[1]]++ : 
                        116 == j ? i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n) : 
                        102 == j ? -i(l, r, a, n) : 
                        37 == j ? (console.log("hook | start args:"+r+","+a),_hook6=r | a,console.log("res:"+_hook6),_hook6) : 
                        10 == j ? (console.log("hook %= start args:"+r[0][r[1]]+","+a),_hook6=r[0][r[1]] % a,console.log("res:"+_hook6),r[0][r[1]]=_hook6) : 
                        72 == j ? (xe = i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), G = i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), n.m = !xe, Ee = i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), n.m = false, t ? [G, Ee] : G[Ee]) : 
                        3 == j ? (n.m = true, Re = i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), n.m = false, Se = i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), g[Re] = Se, n.e ? [g, Re] : void 0) : 
                        45 == j ? !!l[C++] : 
                        36 == j ? r < a :
                        74 == j ? new RegExp(i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n)) : 
                        97 == j ? (console.log("hook >>>= start "),_hook6=r[0][r[1]] >>> a,console.log("res:"+_hook6),r[0][r[1]]=_hook6 ) : 
                        125 == j ? 
                            typeof i(l, r, a, n) 
                            : 
                        43 == j ? r > a : 
                        15 == j ? (console.log("hook ^ start args:"+r+","+a),_hook6=r ^ a ,console.log("res:"+_hook6),_hook6): 
                        126 == j ? i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n) : 
                        21 == j ? i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n, 0, i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n, true), i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n)) : 
                        96 == j ? i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n, 0, i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n), i(l, C++, (m = i(l, C, -3), C = m.i, m.n), n, true)) : 
                        8 == j ? (console.log("hook += start"),r[0][r[1]] += a ,console.log("res:"+r[0][r[1]])): 
                        113 == j ?(console.log("hook - start"), _hook6=r - a,console.log("res:"+_hook6),_hook6 ): 
                        58 == j ?(console.log("hook + args:"+r+","+a),_hook6= r + a ,console.log("res:"+_hook6),_hook6): 
                        104 == j ? (n.t && n.t[n.t.length - 1]).v = true : 
                        26 == j ? r === a : 
                        86 == j ? (console.log("hook = start"),r[0][r[1]] = a) : 
                        64 == j ? (console.log("right:"+r+">>>"+a),_qqqq=r >>> a,console.log("res:"+_qqqq),_qqqq) :  void 0 ;

image.png

image.png 这里使用Fiddler做自动响应。
由于上述已经分析到加密核心处理是window.SM3.property.sum方法,因此我们只需要分析这个方法内部的执行逻辑就行。 image.png 可以看到已经有日志出来了。此刻执行sum ,因为将大部分执行的方法都打印了,并且压缩前扩展并未被魔改,这里直接搜索_expand 查找压缩开始的地方进行分析。 由于压缩需要使用到8个寄存器(reg或者叫 [A,B,C,D,E,F,G,H]),那我们就查找在_expand方法下面reg数组作为参数第一次出现的地方。

注意:

方法名下面打印的数组是执行参数  
紧接着下面的第一个res 前缀的日志信息后面的是结果
两者中间所有日志则是方法内部执行逻辑或者调用逻辑

image.png 上面大小132的数组刚好是扩展完的结果,这与前面分析的对应上了。 先看看正常compress的执行逻辑

word *compress(word* v,word* b){
    word ss1,ss2,tt1,tt2,A,B,C,D,E,F,G,H,*array,*res;
    A=*(v);
    B=*(v+1);
    C=*(v+2);
    D=*(v+3);
    E=*(v+4);
    F=*(v+5);
    G=*(v+6);
    H=*(v+7);
    array= expand(b);
    printf("expand:\n");
    for(int i=0;i<132;i++){
        if(i%8==0&&i>0) printf("\n");
        printf("%08x ",*(array+i));

    }
    printf("\ncompress:\n");
    printf("    %08c,%08c,%08c,%08c,%08c,%08c,%08c,%08c\n",'A','B','C','D','E','F','G','H');
    printf("%02d  %08x %08x %08x %08x %08x %08x %08x %08x \n",-1,A,B,C,D,E,F,G,H);
    for(int j=0;j<64;j++){
        ss1=rot_left((rot_left(A,12)+E+rot_left(T_j(j),j)),7);
        ss2=ss1^rot_left(A,12);
        tt1= FF_j(A,B,C,j)+D+ss2+*(array+68+j);
        tt2= GG_j(E,F,G,j)+H+ss1+*(array+j);
        D=C;
        C= rot_left(B,9);
        B=A;
        A=tt1;
        H=G;
        G= rot_left(F,19);
        F=E;
        E= P_0(tt2);
        printf("%02d  %08x %08x %08x %08x %08x %08x %08x %08x \n",j,A ,B,C,D,E,F,G,H);
    }
    res=(word*) malloc(sizeof(word)*8);
    *(res)=A;
    *(res+1)=B ;
    *(res+2)=C ;
    *(res+3)=D ;
    *(res+4)=E ;
    *(res+5)=F ;
    *(res+6)=G ;
    *(res+7)=H;
    printf("%02d  %08x %08x %08x %08x %08x %08x %08x %08x \n",-1,*(v)^A,B,C,D,E,F,G,H);
    return  _xor(res,v); ;

扩展完之后直接进入循环进行压缩。
正常执行逻辑:

取寄存器A的值循环左移12 取常量T(j)(第一次循环 j=0)循环左移j位,两者之和加上寄存器E的值循环左移7位得到中间量ss1的值

sum的执行逻辑

image.png

image.png 可以看到确实取寄存器A的值循环左移12, 取常量_T(0)左移0,但接下来并未取寄存器E的值 2701930684 并且多了一个中间量 ssr(正常sm3只有 ss1,ss2,tt1,tt2四个中间变量)。

image.png image.png

image.png 注意这里可以看到是将前两次循环左移结果相加再与ssr的值相与得到407977229 再将407977229循环左移7得到结果赋给ss1。那么在压缩算法内第一个魔改点就是ss1赋值这里
原先是:
ss1=rot_left((rot_left(A,12)+E+rot_left(T_j(j),j)),7);
目前分析是:
ss1=rot_left((rot_left(A,12)+rot_left(T_j(j),j)+E)&ssr,7)
对于ssr中间量的值经全文查找并未发现改变,猜测ssr中间量的值是一个常量 ssr=4244635647
继续往下看,分析ss2的赋值

image.png
ss2的赋值就是寄存器A循环左移12,未发生改变。
ss2=ss1^rot_left(A,12);
继续往下看分析tt1,tt2的赋值

image.png 对于tt1 原始的赋值逻辑是:
tt1= FF_j(A,B,C,j)+D+ss2+*(array+68+j);
与上图比对发现同样是将ABC三个寄存器及当前循环次数j带入到_ff方法中并将计算结果与寄存器D的值相加,再加上ss2与扩展数组中下标为68+j的值并与数字4294967290相与。 可以看出对对tt1的赋值逻辑变为:
tt1=FF_j(A,B,C,j)+D+ss2+*(array+68+j))&4294967290;

image.png 与tt1的分析相同,可发现tt2的赋值变为
tt2=GG_j(E,F,G,j)+H+ss1+*(array+j))&4289724415
剩下寄存器值的交换
原始逻辑为:

        D=C;
        C= rot_left(B,9);
        B=A;
        A=tt1;
        H=G;
        G= rot_left(F,19);
        F=E;
        E= P_0(tt2);

image.png

image.png

image.png

image.png

image.png

image.png

image.png

不难发现这里对寄存器E的赋值进行了更改,变为了 (rot_left(tt2,9)^tt2)^(rot_left(tt2,17)) 因此寄存器值的交换由原来变成了:

    ss1=rot_left((rot_left(A,12)+E+rot_left(T_j(j),j)),7);  
    //(rot_left(A,12)+E+rot_left(T_j(j),j));  
    ss1=(unsigned long) ((long long) rot_left(A,12)+rot_left(T_j(j),j)+E)&ssr;  
    ss1=rot_left(ss1,7);  
    //ss1=rot_left(((rot_left(A,12)^rot_left(T_j(j),j))+E),7);  
    ss2=ss1^rot_left(A,12);  
    tt1=(long) ((long long)FF_j(A,B,C,j)+D+ss2+*(array+68+j))&4294967290;  
    tt2=(long) ((long long )GG_j(E,F,G,j)+H+ss1+*(array+j))&4289724415;  
    D=C;  
    C= rot_left(B,9);  
    B=A;  
    A=tt1;  
    H=G;  
    G= rot_left(F,19);  
    F=E;  
    E=(rot_left(tt2,9)^tt2)^(rot_left(tt2,17));  
    //E= P_0(tt2);

剩下就是从头开始循环。
根据以上发现的有变化的地方修改 sm3算法

word *compress(word* v,word* b){  
    word ssr,ss1,ss2,tt1,tt2,A,B,C,D,E,F,G,H,*array,*res ;  
    A=*(v);  
    B=*(v+1);  
    C=*(v+2);  
    D=*(v+3);  
    E=*(v+4);  
    F=*(v+5);  
    G=*(v+6);  
    H=*(v+7);  
    array= expand(b);  
    printf("expand:\n");  
    for(int i=0;i<132;i++){  
    if(i%8==0&&i>0) printf("\n");  
        printf("%09u ",((unsigned int)*(array+i)));  

    }  
    printf("\ncompress:\n");  
    printf(" %08c,%08c,%08c,%08c,%08c,%08c,%08c,%08c\n",'A','B','C','D','E','F','G','H');  
    printf("%02d %08x %08x %08x %08x %08x %08x %08x %08x \n",-1,A,B,C,D,E,F,G,H);  
    ssr=4244635647;  
    for(int j=0;j<64;j++){ 
        //主要变化在这里
        ss1=rot_left((rot_left(A,12)+E+rot_left(T_j(j),j)),7);  
        //(rot_left(A,12)+E+rot_left(T_j(j),j));  
        ss1=(unsigned long) ((long long) rot_left(A,12)+rot_left(T_j(j),j)+E)&ssr;  
        ss1=rot_left(ss1,7);  
        //ss1=rot_left(((rot_left(A,12)^rot_left(T_j(j),j))+E),7);  
        ss2=ss1^rot_left(A,12);  
        tt1=(long) ((long long)FF_j(A,B,C,j)+D+ss2+*(array+68+j))&4294967290;  
        tt2=(long) ((long long )GG_j(E,F,G,j)+H+ss1+*(array+j))&4289724415;  
        D=C;  
        C= rot_left(B,9);  
        B=A;  
        A=tt1;  
        H=G;  
        G= rot_left(F,19);  
        F=E;  
        E=(rot_left(tt2,9)^tt2)^(rot_left(tt2,17));  
        //E= P_0(tt2);  
        printf("%02d %08u %08u %08u %08u %08u %08u %08u %08u \n",j,A ,B,C,D,E,F,G,H);  
    }  
    res=(word*) malloc(sizeof(word)*8);  
    *(res)=A;  
    *(res+1)=B ;  
    *(res+2)=C ;  
    *(res+3)=D ;  
    *(res+4)=E ;  
    *(res+5)=F ;  
    *(res+6)=G ;  
    *(res+7)=H;  
    printf("%02d %08x %08x %08x %08x %08x %08x %08x %08x \n",-1,*(v)^A,B,C,D,E,F,G,H);  
    return _xor(res,v);  
}

试着跑一下程序看看加密结果还有没有什么不同:

image.png

由于strtobytes时发生偏移"abc"->"`bb"

image.png

可以发现两者结果相同,至此算法还原结束。

总结:

此次算法还原中共发现四处魔改:

  1. strtobytes时发生字节偏移。
  2. IV初始值发生变化。
  3. 常量T的值发生变化。
  4. 压缩函数内部增加中间量ssr并且内部执行逻辑发生更改。

如题所述:守道独行,心逸神宁。既然决定做,就不要想太多,静下心来相信自己的判断,即使分析的有问题那也是宝贵的经验。

如上算法还原源码