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

查看 174|回复 12
作者:千人千面   
根据大佬@cdj68765 提供的思路和方法!的方法,直接跳过了以下的Debug过程,详细请看结尾。
1、最近在用这个Spire.XLS把XLS表格生成PDF,但是免费版的居然也有水印!这我能忍?


image.png (35.09 KB, 下载次数: 0)
下载附件
2022-9-7 15:32 上传

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


image.png (243.43 KB, 下载次数: 0)
下载附件
2022-9-7 15:33 上传



image.png (141 KB, 下载次数: 0)
下载附件
2022-9-7 15:33 上传



image.png (235.56 KB, 下载次数: 0)
下载附件
2022-9-7 15:33 上传

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


image.png (230.64 KB, 下载次数: 0)
下载附件
2022-9-7 15:34 上传

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


image.png (421.97 KB, 下载次数: 0)
下载附件
2022-9-7 15:34 上传

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


forum.png (51.3 KB, 下载次数: 0)
下载附件
2022-9-7 15:35 上传



image.png (478.2 KB, 下载次数: 0)
下载附件
2022-9-7 15:36 上传

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


image.png (75.67 KB, 下载次数: 0)
下载附件
2022-9-7 15:36 上传

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


image.png (114.82 KB, 下载次数: 0)
下载附件
2022-9-7 15:36 上传

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


image.png (315.07 KB, 下载次数: 0)
下载附件
2022-9-7 15:37 上传

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


image.png (250.8 KB, 下载次数: 0)
下载附件
2022-9-7 15:37 上传



image.png (172.69 KB, 下载次数: 0)
下载附件
2022-9-7 15:37 上传

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


image.png (371.63 KB, 下载次数: 0)
下载附件
2022-9-7 15:37 上传

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


image.png (196.89 KB, 下载次数: 0)
下载附件
2022-9-7 15:37 上传

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


image.png (367.03 KB, 下载次数: 0)
下载附件
2022-9-7 15:37 上传

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


image.png (124.25 KB, 下载次数: 0)
下载附件
2022-9-7 15:38 上传

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


image.png (368.8 KB, 下载次数: 0)
下载附件
2022-9-7 15:25 上传

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


image.png (13.84 KB, 下载次数: 0)
下载附件
2022-9-7 15:25 上传


感谢大佬@cdj68765 提供的思路和方法!
2022年9月22日更新了一下方法,C#用户直接写一个拓展方法调用即可
调用方法为:
[Asm] 纯文本查看 复制代码var wb = new Workbook();
wb.Crack();
拓展类为:
[Asm] 纯文本查看 复制代码
    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 // 验证平台兼容性
        }
        ///
        /// 注入激活信息
        ///
        ///
        public static void Crack(this Workbook workbook)
        {
            CrackLicense(workbook);
        }
        ///
        /// 注入激活信息
        ///
        ///
        public static void Crack(this Document document)
        {
            CrackLicense(document);
        }
        ///
        /// 注入激活信息,并返回该类型
        ///
        ///
        ///
        ///
        public static T CrackLicense(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   

给楼主两端代码,是之前研究Spire留下的
针对Word的
[C#] 纯文本查看 复制代码
                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的
[C#] 纯文本查看 复制代码
                        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   


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

说实话,你好会玩,哈哈哈
每次实例化或者载入文档的时候,验证信息都会被重置,因此在载入文档之后,紧跟着        
InternalLicense.SetValue(Xls, TypeLic)
就行了,或者在保存之前赋值一次也行,反正代码上也只在保存的时候才验证。
[C#] 纯文本查看 复制代码        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   


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

您好,之前下载了最新的,用新建的文档测试,确实没有水印了。
不过还有两个问题待解惑:
1.测试发现,若是打开现有的文档,而不是新建,还是有水印;
[Asm] 纯文本查看 复制代码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   


330201818 发表于 2023-1-7 17:41
[C#] 纯文本查看 复制代码                var Xls = new Spire.Xls.Workbook();
                var  ...[/quote]
[mw_shl_code=csharp,true]
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
        {
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern bool FlushInstructionCache(IntPtr hProcess, IntPtr lpBaseAddress, UIntPtr dwSize);
            [DllImport("kernel32.dll", SetLastError = true)]
            public static extern IntPtr GetCurrentProcess();
            [DllImport("kernel32.dll", SetLastError = true)]
            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 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()[0].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[offset];
                                        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
给你一段相对复杂一些的方案吧
使用该方案需要项目属性里开启运行不安全代码,毕竟直接操作了内存不得不这么做
代码使用了类的几个关键点的匹配,所以不能保证以后的版本也能用这个方法来操作
岔路ko   

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

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

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

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

学习一下
您需要登录后才可以回帖 登录 | 立即注册

返回顶部