吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 13247|回复: 76
收起左侧

[.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] 纯文本查看 复制代码
1
2
var wb = new Workbook();
wb.Crack();

拓展类为:
[Asm] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
    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 感谢发布原创作品,吾爱破解论坛因你更精彩!
风随心起 + 1 + 1 我很赞同!非常有用
1052481067 + 1 + 1 用心讨论,共获提升!

查看全部评分

本帖被以下淘专辑推荐:

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

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

给楼主两端代码,是之前研究Spire留下的
针对Word的
[C#] 纯文本查看 复制代码
1
2
3
4
5
6
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#] 纯文本查看 复制代码
1
2
3
4
5
6
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#] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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] 纯文本查看 复制代码
1
2
3
4
5
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#] 纯文本查看 复制代码
001
002
003
004
005
006
007
008
009
010
011
012
013
014
015
016
017
018
019
020
021
022
023
024
025
026
027
028
029
030
031
032
033
034
035
036
037
038
039
040
041
042
043
044
045
046
047
048
049
050
051
052
053
054
055
056
057
058
059
060
061
062
063
064
065
066
067
068
069
070
071
072
073
074
075
076
077
078
079
080
081
082
083
084
085
086
087
088
089
090
091
092
093
094
095
096
097
098
099
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
                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, 下载次数: 55, 下载积分: 吾爱币 -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, 2025-4-24 12:23

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

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