qands 发表于 2021-4-3 22:38

aspose.words for .net 21.4,虚拟化后的修改

其实分析不难,改IL指令搞的我心力憔悴,清明出去玩都惦记着.
(求助:dnspy有没有编辑方法时显示IL指令的功能啊....)
等下要麻将,先发结果和几个参数,过会儿再写分析过程.

虚拟化代码存在在一个乱码的资源文件里,像这样.


进入虚拟化那个乱码的字符串".Ounr+YPC1" ,指向这个资源的某个位置,从而读出一些字节.
        \u000F\u2004\u200B\u2008.\u0005\u2005\u200B\u2008().\u0003(\u000F\u2004\u200B\u2008.\u0008\u2005\u200B\u2008(), ".Ounr+YPC1", u);

读出来的bytearr 经过解密 得到解密的bytearr,每4字节组合,得到一个操作指令.这些指令(应该,95%)是源程序的IL指令.暂时称它为指令数组.
指令数组在执行过程中看的到.像这样.解密过程我没看,这个过程也虚拟化了.


这些操作指令的意义是写死在程序里的.是个字典,对应相应的操作.执行过程中也可以看得到

指令数组中的指令在字典中查询并执行的代码在这里
        private void \u0005\u2000()
        {
                this.\u000E\u2002 = this.\u000E\u2001;
                int key = this.\u0008\u2004.\u0003\u2000();
                this.\u000E\u2001 += 4U;
                \u0002\u2007.\u0002\u2000 u0002_u;
                global::\u0002\u2007.\u0005.TryGetValue(key, out u0002_u);
                u0002_u.\u0003(this, this.\u0002(this.\u0008\u2004, u0002_u.\u0002));
        }

每次运行后的结果,保存在这里

比如比较注册的时间的过程,你会在结果集中看到
取出存取注册信息的那个类.
取出注册码过期时间.
执行call得到datetime.now
执行call比较时间大小
返回结果(0或1)
判断是否跳转
...
在这个程序块最后下断,可以很清晰的看到这个过程.按一次F5做一行事情.

当前运行到了IL指令数组的哪个位置,保存在这里


当要跳转的时候 \u000F\u2003的值变成新位置 并在这个程序块返回的程序块中进行判断.这个值不跳转的时候为null
        private void \u0006(bool \u0002)
        {
                uint u000F_u = this.\u000F\u2002;
                for (;;)
                {
                        try
                        {
                                while (!this.\u0005\u2002)
                                {
                                        if (this.\u000F\u2003 != null)
                                        {
                                                this.\u000E\u2001 = this.\u000F\u2003.Value;
                                                this.\u0002((long)((ulong)this.\u000E\u2001));
                                                this.\u000F\u2003 = null;
                                        }
                                        else if (this.\u000E\u2001 >= u000F_u)
                                        {
                                                break;
                                        }
                                        this.\u0005\u2000();
                                }
                        }
                        catch (object u)
                        {
                                this.\u0002(u, 0U);
                                if (\u0002)
                                {
                                        continue;
                                }
                                this.\u0006(true);
                        }
                        break;
                }
        }
好了.现在总结下
1.根据指令数组的长度的不同,你基本可以判断出它在执行哪个虚拟化的程序块.
2.指令数组长度/4就是源程序IL指令的数量,大致可以判断出被虚拟方法的代码长度.如果长度相差太大,直接跳出虚拟化的循环就可以了.不用老点F5
3.我们可以修改\u000F\u2003的值跳转到虚拟方法的任何位置.

根据上2篇文章的经验,判断是否注册的代码很简单,大概长这样
      if (\u000F\u2008\u2004\u2001.\u000F != null && \u000F\u2008\u2004\u2001.\u000F.\u000E != (\u0002\u2009\u2004\u2001)0 && !(\u000F\u2008\u2004\u2001.\u000F.\u0006 < DateTime.Now) && \u0008\u2002\u2005.\u0003() != 4096)
      {
            return (\u0002\u2009\u2004\u2001)1;
      }
      return (\u0002\u2009\u2004\u2001)0;


最终调试结果是,这段判断注册与否的代码被虚拟化后的长度是0x0000013E(它加了很多干扰代码,把指令搞长了)
return 1 那里对应的数组位置是0x88,在虚拟化的跳转处下断,长度是0x13e时第一次跳转发生在数组位置0x6c.
我在这里注入了断代码,当\u000F\u2003==0x6c时,修改成0x88.
然后就成功了.

pjy612 发表于 2023-2-2 11:40

本帖最后由 pjy612 于 2023-2-2 12:14 编辑

dplxin 发表于 2023-2-2 11:05
嗯。。老哥分享下aspose    rsahook的方法学习一下`..`

rsa 是不是只hook 一处就够了
这个是动态hook 因为它里面的 RSA验签是 自己实现的。所以每个库都要针对性筛选关键点然后 hook。

它的验签逻辑是 通过公钥解析 sign 能得到 原始报文的 hash,然后对入参报文进行 hash 。
两个 hash 循环比较 验证。

本来还有办法就是Hook Base64直接Patch它验证用的公钥。
但是 它里面还有个 黑名单 会走相同的验签解析逻辑。
所以 根本判断不出来 它在验证什么。

所以理论上要处理的就是 从旧版本里面 把它 验签解析sign的代码抽出来。
然后 对自己 伪造的 license 生成 sign 和 对应成功的 hash。
然后 Hook 验签逻辑,发现被验证的 sign 是我们生成的时候 返回 对应的 hash。

private static void Postfix_VerifyData(byte[] __0, ref byte[] __result, MethodInfo __originalMethod)
{
    if (StackInAspose())
    {
      if (__result.Length != 127)
      {
            if (RSA_SIGN.SequenceEqual(__0))
            {
                __result = RSA_Hash.ToArray();
            }
      }
    }
}

pjy612 发表于 2023-1-10 16:36

夜泉 发表于 2021-4-3 22:58
关注,第一时间开始看~




最终弄下来
最稳定的还是找个过期的授权模板,然后把 Sign 给 伪造掉 强制返回对应的hash...

其次就是 Hook 系统函数
XmlElement 的 InnerText getter
XmlReader 的 ReadString
判断 当前是读取 SubscriptionExpiry 的时候 给个 2099
然后就是
String 的 IndexOf,有些DLL会重查一下 日期是不是在文件里,所以 这里判断下 如果是 2099啥的 就给他返回个存在。


其他的 比如 Hook Convert.FromBase64String 去替换 RSA的公钥
由于没有上下文会让它自己的黑名单校验出错导致失败。

.NET 的 Hook 感觉还是 HarmonyLib 最好用...
基本那些商用的DLL都能用 HarmonyLib 在不改代码时动态Hook 约等于 运行时构建Aop...
而且能拿到调用链的上下文

夜泉 发表于 2021-4-3 22:58

本帖最后由 夜泉 于 2021-4-4 02:54 编辑

关注,第一时间开始看~


(求助:dnspy有没有编辑方法时显示IL指令的功能啊....)

我都是开另一个程序,每次想要什么IL指令都写上重新编译,然后ildasm查看的。。。
{:301_972:}

对了,为啥不直接修改IL代码然后还原回去?在这里注入,每执行一条IL指令,都要判断一下,感觉也太那啥了吧?
对哦,这个只有解密没有加密,,,通过解密还原加密也太累了

我选择用 过期授权文件。。。

{:301_1007:}

xingkongtianyu 发表于 2021-4-4 08:31

谢谢分享,向大神学习了!

wfcaicai 发表于 2021-4-4 10:27

谢谢分享{:1_921:}

qands 发表于 2021-4-4 13:11

夜泉 发表于 2021-4-3 22:58
关注,第一时间开始看~






这么改的,在这里下断,程序一共也没到这里几次,不影响效率.
而且就算在每次执行都判断次,也不影响效率.这个虚拟机已经把代码搞这样了,还怕效率低?


要还原它以前的代码也是可以的,直接从指令数组得到所有指令,然后对照着字典翻译,然后再把得到的IL指令写到虚化前的方法里.就是这个工作量.....


anye521 发表于 2021-4-4 15:07

感谢&#128591;分享

swhyy 发表于 2021-4-4 17:51

支持楼主原创,楼主厉害

oah1021 发表于 2021-4-5 16:19

感谢大佬的分享

小夫哥 发表于 2021-4-5 16:25

看不懂.要多多学习了....

benny856694 发表于 2021-4-5 20:14

感谢大佬分享
页: [1] 2 3 4
查看完整版本: aspose.words for .net 21.4,虚拟化后的修改