吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 9156|回复: 73
收起左侧

[.NET逆向] 通dnSpy的内存搜索去除Spire.XLS的PDF水印

  [复制链接]
千人千面 发表于 2022-9-7 15:26
本帖最后由 千人千面 于 2022-9-22 14:22 编辑

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


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

image.png

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

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

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

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

我们在代码上单击SaveToFile进入详细代码
forum.png
image.png

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

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

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

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

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

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

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

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

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

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

感谢大佬@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 // 验证平台兼容性
        }

        /// <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;
        }


    }
image.png

免费评分

参与人数 17威望 +1 吾爱币 +34 热心值 +15 收起 理由
pandawakak + 1 + 1 谢谢@Thanks!
美的随想 + 1 我很赞同!
timeni + 1 + 1 用心讨论,共获提升!
yyhf + 1 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
h07799486 + 1 + 1 谢谢@Thanks!
jiaoshudong + 1 + 1 鼓励转贴优秀软件安全工具和文档!
MaxMadcc + 1 + 1 我很赞同!
pufa0721 + 1 + 1 我很赞同!
allspark + 1 + 1 用心讨论,共获提升!
hxd97244 + 1 + 1 用心讨论,共获提升!
bsdn321321 + 1 谢谢@Thanks!
5omggx + 1 用心讨论,共获提升!
笙若 + 1 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Tonyha7 + 1 用心讨论,共获提升!
Hmily + 1 + 20 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
Jormungand911 + 1 + 1 我很赞同!非常有用
1052481067 + 1 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

cdj68765 发表于 2022-9-7 17:50
本帖最后由 cdj68765 于 2022-9-9 23:14 编辑

给楼主两端代码,是之前研究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并不适用,请注意

免费评分

参与人数 4吾爱币 +3 热心值 +4 收起 理由
hughhugh + 1 + 1 谢谢@Thanks!
千人千面 + 1 + 1 用心讨论,共获提升!
5omggx + 1 用心讨论,共获提升!
Hmily + 1 + 1 用心讨论,共获提升!

查看全部评分

cdj68765 发表于 2022-9-14 16:29
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")

关于交互问题,说实话这个没办法,这个是作者他本身的问题,我们作为使用者是无能为力的
真要用的话,我想也只能开两个线程交互通信了,摊手

免费评分

参与人数 1吾爱币 +2 热心值 +1 收起 理由
zhanglei1371 + 2 + 1 感谢解答!

查看全部评分

zhanglei1371 发表于 2022-9-14 08:44
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是相同的。这种共存问题如何解决呢?
谢谢!

附件:
cdj68765 发表于 2023-1-8 14:00
本帖最后由 cdj68765 于 2023-1-8 21:20 编辑
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<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()[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 < offset; k++)
                                            {
                                                originalOpcodes[k] = *(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[0];
            sheet2.SaveToImage("ExceltoImage.jpg");
        }
    }
}


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

SpireHook.zip

1.93 KB, 下载次数: 34, 下载积分: 吾爱币 -1 CB

岔路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


@千人千面 最后多出来的图是不是插入这里的,好像丢了。
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-5-7 10:55

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表