千人千面 发表于 2022-9-7 15:26

通dnSpy的内存搜索去除Spire.XLS的PDF水印

本帖最后由 千人千面 于 2022-9-22 14:22 编辑

根据大佬@cdj68765 提供的思路和方法!的方法,直接跳过了以下的Debug过程,详细请看结尾。


1、最近在用这个Spire.XLS把XLS表格生成PDF,但是免费版的居然也有水印!这我能忍?



2、不废话,由于是.NET平台写的,我们直接把它拖进DnsPy先给他PY一下看看.常规操作,先搜索下字符串,这里搜索的时候不建议一上来就搜索完整字符串,因为八成没啥结果。下面搜索出来了几个方法,我们挨个点进去看看




从这几个方法的上下文来推断,虽然里面包含了关键字:Evaluation Warning,但是,我可以肯定,和水印上的EvaluationWarning : The document was created with Spire.XLS for .NET没有半毛钱关系,至于我为什么这么肯定,因为我已经NOP掉来调试过了。那么遇到这种情况的时候,字符串搜索不出来什么结果,那么可以肯定的是,字符串八成是被加密了。你以为我就那你没办法了吗!太小看我了,不管你再怎么加密,始终要解密出来,既然要解密出来,那我们怎么知道它啥时候解密,解密的字符串又去哪儿找?当然是去内存里面找,这里写了个简单的demo来生成一个表格并且保存为PDF,经过分析,水印是在保存这一步被添加上去的,我们在保存的这行代码上打个断点让程序跑起来。


现在程序已经断下来了,接下来打开调试工具栏-窗口-内存-内存1


接下来我们让程序跑起来,然后在内存中搜索字符串,可以看见,内存中的确存在该字符串,接下来大家可能会问,我也看见了,问题是他是从哪儿出来的额?
file:///C:/Users/y15/AppData/Local/Temp/msohtmlclip1/01/clip_image016.jpg

我们在代码上单击SaveToFile进入详细代码



往下找到SaveToPdf方法,我们继续深入他


进来以后可以看见,数行代码,我们重点关注黄色的方法调用,可以肯定的是,水印一定是在其中的某个方法中被加上去的,但是我咋知道是那个方法?当然是打断点调试,这里采用二分法打断点,先在中间打一个断点,然后去内存中搜索,逐步缩小范围,直到精确定位到具体方法。


上面没有,这里按F10逐过程进行排查。


当运行到倒数第二行代码的时候,内存中出现了字符串。我们单击该方法进入。发现里面调用了一个method_28的方法,继续深入。。。



代码有点长,我们先在第一行打个断点,然后重新运行程序,在断点处F10逐过程调试


经过一系列操作。。。我们定位到了该方法调用


再次经过一顿操作,最终最终我定位到了字符串解密的地方


难怪搜索不到字符串,它被序列化成数组了。


那么接下来的方法就简单了,我们只需要干掉这个判断就行~


最后保存模块就收工了,生成的PDF已经没有水印了。


感谢大佬@cdj68765 提供的思路和方法!
2022年9月22日更新了一下方法,C#用户直接写一个拓展方法调用即可

调用方法为:
var wb = new Workbook();
wb.Crack();
拓展类为:

    public static class SpireOfficeHelpers
    {
      public static void Print(string path)
      {
            var wb = new Workbook();
            wb.Crack();
            wb.LoadFromFile(path);
            var p = wb.PrintDocument;
#pragma warning disable CA1416 // 验证平台兼容性
            p.Print();
#pragma warning restore CA1416 // 验证平台兼容性
      }

      public static void Print(byte[] bytes)
      {
            MemoryStream memeStream = new(bytes);
            var wb = new Workbook();
            wb.Crack();
            wb.LoadFromStream(memeStream);
            var p = wb.PrintDocument;
#pragma warning disable CA1416 // 验证平台兼容性
            p.Print();
#pragma warning restore CA1416 // 验证平台兼容性
      }

      /// <summary>
      /// 注入激活信息
      /// </summary>
      /// <param name="workbook"></param>
      public static void Crack(this Workbook workbook)
      {
            CrackLicense(workbook);
      }

      /// <summary>
      /// 注入激活信息
      /// </summary>
      /// <param name="document"></param>
      public static void Crack(this Document document)
      {
            CrackLicense(document);
      }

      /// <summary>
      /// 注入激活信息,并返回该类型
      /// </summary>
      /// <typeparam name="T"></typeparam>
      /// <param name="t"></param>
      /// <returns></returns>
      public static T CrackLicense<T>(T t) where T : class
      {
            var InternalLicense = t.GetType().GetProperty("InternalLicense", BindingFlags.NonPublic | BindingFlags.Instance);
            var TypeLic = InternalLicense.PropertyType.Assembly.CreateInstance(InternalLicense.PropertyType.GetTypeInfo().FullName);
            foreach (var item in TypeLic.GetType().GetFields(BindingFlags.NonPublic | BindingFlags.Instance))
            {
                if (item.FieldType.IsArray)
                {
                  item.SetValue(TypeLic, new string[] { "Spire.Spreadsheet", "Spire.DocViewer.Wpf" });
                }
                else if (item.FieldType.IsEnum)
                {
                  item.SetValue(TypeLic, 3);
                }
            }
            InternalLicense.SetValue(t, TypeLic);
            return t;
      }


    }

cdj68765 发表于 2022-9-7 17:50

本帖最后由 cdj68765 于 2022-9-9 23:14 编辑

给楼主两端代码,是之前研究Spire留下的
针对Word的

                Spire.Doc.Document document = new Spire.Doc.Document(Mem);
                var Lic = new Spire.License.InternalLicense();
                Lic.LicenseType = Spire.License.LicenseType.Runtime;
                Lic.AssemblyList = new string[] { "Spire.DocViewer.Wpf" };
                var InternalLicense = document.GetType().GetProperty("InternalLicense", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                InternalLicense.SetValue(document, Lic);

针对Excel的

                        var Xls = new Spire.Xls.Workbook();
                        var Lic = new Spire.License.InternalLicense();
                        Lic.LicenseType = Spire.License.LicenseType.Runtime;
                        Lic.AssemblyList = new string[] { "Spire.Spreadsheet" };
                        var InternalLicense = Xls.GetType().GetProperty("InternalLicense", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
                        InternalLicense.SetValue(Xls, Lic);


这两段代码都可以在不用修改库的情况下,直接使用,原理就是在实例化后,将激活信息通过反射直接赋值到类里面
前面的分析过程和楼主的基本一样,包括字符串那里,后面的方向不太一样。楼主在找到判断是否加水印那里,好像没有继续分析下去
什么条件下能够不进入加水印这一步,而是选择了爆破吧。不过我有理由相信,楼主能够继续跟下去,然后直到找到激活信息那里,一定能找到跟我一样的结论的
PS:此方式仅适用于V10开头的版本,最新的V12并不适用,请注意

cdj68765 发表于 2022-9-14 16:29

zhanglei1371 发表于 2022-9-14 08:44
您好,之前下载了最新的,用新建的文档测试,确实没有水印了。
不过还有两个问题待解惑:
1.测试发现 ...

说实话,你好会玩,哈哈哈
每次实例化或者载入文档的时候,验证信息都会被重置,因此在载入文档之后,紧跟着      
InternalLicense.SetValue(Xls, TypeLic)
就行了,或者在保存之前赋值一次也行,反正代码上也只在保存的时候才验证。
      Dim Xls = New Spire.Xls.Workbook()
      Dim InternalLicense = Xls.GetType().GetProperty("InternalLicense", System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance)
      Dim TypeLic = InternalLicense.PropertyType.Assembly.CreateInstance(InternalLicense.PropertyType.GetTypeInfo().FullName)
      For Each item In TypeLic.GetType().GetFields(System.Reflection.BindingFlags.NonPublic Or System.Reflection.BindingFlags.Instance)
            If item.FieldType.IsArray Then
                item.SetValue(TypeLic, New String() {"Spire.Spreadsheet"})
            ElseIf item.FieldType.IsEnum Then
                item.SetValue(TypeLic, 3)
            End If
      Next item

      InternalLicense.SetValue(Xls, TypeLic)

      Dim worksheets As WorksheetsCollection = Xls.Worksheets
      worksheets.Add("sheetA")
      Dim xlrg As XlsRange = worksheets("sheetA").Range("A1")
      xlrg.Value2 = "这个没有水印!"
      Xls.SaveToFile("textzl.pdf")
      Xls.LoadFromFile("test.xlsx", ExcelVersion.Version2010)

      InternalLicense.SetValue(Xls, TypeLic)

      MsgBox(Xls.ActiveSheet.Range(1, 1).Text)
      Xls.ActiveSheet.Range(1, 19).Text = "这个有水印!"
      Xls.SaveToFile("textzl02.pdf")
关于交互问题,说实话这个没办法,这个是作者他本身的问题,我们作为使用者是无能为力的
真要用的话,我想也只能开两个线程交互通信了,摊手

zhanglei1371 发表于 2022-9-14 08:44

cdj68765 发表于 2022-9-11 11:36
你把你项目里的引用spire dll全都清空了,然后从Nuget重新下载spire试试就可以了。
刚才我试了下,用你 ...

您好,之前下载了最新的,用新建的文档测试,确实没有水印了。
不过还有两个问题待解惑:
1.测试发现,若是打开现有的文档,而不是新建,还是有水印;
Dim XLS As New Workbook
XLS.LoadFromFile("test.xlsx", ExcelVersion.Version2010)
MsgBox(XLS.ActiveSheet.Range(1, 1).Text)
XLS.ActiveSheet.Range(1, 19).Text = "savetime"
XLS.Save()

2.若在同一个工程里,希望同时有xls和doc交互的话,此时就无法初始化,因为二者附带的pdf的dll是相同的。这种共存问题如何解决呢?
谢谢!

附件:https://wwi.lanzoup.com/iMbTu0bnfvne

cdj68765 发表于 2023-1-8 14:00

本帖最后由 cdj68765 于 2023-1-8 21:20 编辑

330201818 发表于 2023-1-7 17:41
                var Xls = new Spire.Xls.Workbook();
                var...

using Spire.Xls;
using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace SpireHook
{
    internal class Program
    {
      internal class Natives
      {
            
            public static extern bool FlushInstructionCache(IntPtr hProcess, IntPtr lpBaseAddress, UIntPtr dwSize);

            
            public static extern IntPtr GetCurrentProcess();

            
            public static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect);

            public enum PageProtection : uint
            {
                PAGE_NOACCESS = 0x01,
                PAGE_READONLY = 0x02,
                PAGE_READWRITE = 0x04,
                PAGE_WRITECOPY = 0x08,
                PAGE_EXECUTE = 0x10,
                PAGE_EXECUTE_READ = 0x20,
                PAGE_EXECUTE_READWRITE = 0x40,
                PAGE_EXECUTE_WRITECOPY = 0x80,
                PAGE_GUARD = 0x100,
                PAGE_NOCACHE = 0x200,
                PAGE_WRITECOMBINE = 0x400
            }
      }

      private static void Main(string[] args)
      {
            uint VirtualProtect(IntPtr address, uint size, uint protectionFlags)
            {
                uint oldProtection;
                if (!Natives.VirtualProtect(address, (UIntPtr)size, protectionFlags, out oldProtection))
                {
                  throw new Win32Exception();
                }
                return oldProtection;
            }
            void FlushInstructionCache(IntPtr address, uint size)
            {
                if (!Natives.FlushInstructionCache(Natives.GetCurrentProcess(), address, (UIntPtr)size))
                {
                  throw new Win32Exception();
                }
            }
            var Xls = new Spire.Xls.Workbook();
            Func<object, object, bool> replacemethod = (a0, a1) => true;
            var replacement = replacemethod.GetMethodInfo();
            foreach (var item in Xls.GetType().Assembly.DefinedTypes)
            {
                if (item.DeclaredFields.Count() == 4 && item.DeclaredMembers.Count() == 11 && item.DeclaredMethods.Count() == 5)
                {
                  if (item.GetMethods(BindingFlags.Static | BindingFlags.NonPublic).Length == 5)
                  {
                        foreach (var item2 in item.GetMethods(BindingFlags.Static | BindingFlags.NonPublic))
                        {
                            if (item2.ReturnParameter.ParameterType.Name == "Boolean")
                            {
                              if (item2.GetParameters().Length == 2)
                              {
                                    if (item2.GetParameters().ParameterType.Name == "Object")
                                    {
                                        RuntimeHelpers.PrepareMethod(item2.MethodHandle);
                                        RuntimeHelpers.PrepareMethod(replacement.MethodHandle);
                                        IntPtr originalSite = item2.MethodHandle.GetFunctionPointer();
                                        IntPtr replacementSite = replacement.MethodHandle.GetFunctionPointer();
                                        var is64 = IntPtr.Size != sizeof(int);
                                        uint offset = (is64 ? 13u : 6u);
                                        byte[] originalOpcodes = new byte;
                                        unsafe
                                        {
                                          //segfault protection
                                          uint oldProtecton = VirtualProtect(originalSite, (uint)originalOpcodes.Length, (uint)Natives.PageProtection.PAGE_EXECUTE_READWRITE);
                                          //get unmanaged function pointer to address of original site
                                          byte* originalSitePointer = (byte*)originalSite.ToPointer();
                                          //copy the original opcodes
                                          for (int k = 0; k < offset; k++)
                                          {
                                                originalOpcodes = *(originalSitePointer + k);
                                          }
                                          if (is64)
                                          {
                                                //mov r11, replacementSite
                                                *originalSitePointer = 0x49;
                                                *(originalSitePointer + 1) = 0xBB;
                                                *((ulong*)(originalSitePointer + 2)) = (ulong)replacementSite.ToInt64(); //sets 8 bytes
                                                                                                                         //jmp r11
                                                *(originalSitePointer + 10) = 0x41;
                                                *(originalSitePointer + 11) = 0xFF;
                                                *(originalSitePointer + 12) = 0xE3;
                                          }
                                          else
                                          {
                                                //push replacementSite
                                                *originalSitePointer = 0x68;
                                                *((uint*)(originalSitePointer + 1)) = (uint)replacementSite.ToInt32(); //sets 4 bytes
                                                                                                                     //ret
                                                *(originalSitePointer + 5) = 0xC3;
                                          }

                                          FlushInstructionCache(originalSite, (uint)originalOpcodes.Length);
                                          VirtualProtect(originalSite, (uint)originalOpcodes.Length, oldProtecton);
                                        }
                                        break;
                                    }
                              }
                            }
                        }
                        break;
                  }
                }
            }
            Xls.LoadFromFile(@"ExceltoImage.xlsx", ExcelVersion.Version2010);
            Worksheet sheet2 = Xls.Worksheets;
            sheet2.SaveToImage("ExceltoImage.jpg");
      }
    }
}

给你一段相对复杂一些的方案吧
使用该方案需要项目属性里开启运行不安全代码,毕竟直接操作了内存不得不这么做
代码使用了类的几个关键点的匹配,所以不能保证以后的版本也能用这个方法来操作

岔路ko 发表于 2022-9-7 15:31

使用webapi调用的时候会出错。

goldli 发表于 2022-9-7 15:43

spire.xls还有50个表的限制呢?

lf1988103 发表于 2022-9-7 16:35

NOP是啥 后面怎么操作的有视频教程吗?

dplxin 发表于 2022-9-7 16:41

只是去掉了水印,一些看不到的限制应该还在吧

Adgerlee 发表于 2022-9-7 17:22

学习一下

xiawan 发表于 2022-9-7 17:28


正需要,支持楼主大人了!

rinima 发表于 2022-9-7 17:53

感谢楼主分享

Hmily 发表于 2022-9-7 18:12

接下来我们让程序跑起来,然后在内存中搜索字符串,可以看见,内存中的确存在该字符串,接下来大家可能会问,我也看见了,问题是他是从哪儿出来的额?
file:///C:/Users/y15/AppData/Local/Temp/msohtmlclip1/01/clip_image016.jpg

@千人千面 最后多出来的图是不是插入这里的,好像丢了。
页: [1] 2 3 4 5 6 7 8
查看完整版本: 通dnSpy的内存搜索去除Spire.XLS的PDF水印