forked from dnSpy/dnSpy
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathIVTPatcher.cs
194 lines (168 loc) · 7.07 KB
/
IVTPatcher.cs
1
2
3
4
5
6
7
8
9
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
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/*
Copyright (C) 2014-2019 [email protected]
This file is part of dnSpy
dnSpy is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
dnSpy is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with dnSpy. If not, see <http://www.gnu.org/licenses/>.
*/
// This code patches an assembly's System.Runtime.CompilerServices.InternalsVisibleToAttribute
// so the string passed to the attribute constructor is the name of another assembly.
// If there's no such attribute or if the new string doesn't fit in the old string, this code fails.
// If it happens, use a smaller public key and/or a shorter assembly name and use no spaces
// or don't use PublicKey=xxxx... (since Roslyn C# compiler seems to ignore the public key).
//
// A more generic patcher would rewrite some of the metadata tables but this isn't needed since
// we only use it to patch Roslyn assemblies which contain a ton of IVT attributes.
//
// PERF is the same as copying a file since it just patches the data in memory, nothing is rewritten.
using System;
using dnlib.DotNet;
using dnlib.DotNet.MD;
using dnlib.IO;
namespace MakeEverythingPublic {
enum IVTPatcherResult {
OK,
NoCustomAttributes,
NoIVTs,
IVTBlobTooSmall,
}
struct IVTPatcher {
// Prefer overwriting IVTs with this public key since they're just test assemblies
const string ROSLYN_OPEN_SOURCE_PUBLIC_KEY = "002400000480000094000000060200000024000052534131000400000100010055e0217eb635f69281051f9a823e0c7edd90f28063eb6c7a742a19b4f6139778ee0af438f47aed3b6e9f99838aa8dba689c7a71ddb860c96d923830b57bbd5cd6119406ddb9b002cf1c723bf272d6acbb7129e9d6dd5a5309c94e0ff4b2c884d45a55f475cd7dba59198086f61f5a8c8b5e601c0edbf269733f6f578fc8579c2";
readonly byte[] data;
readonly Metadata md;
readonly byte[] ivtBlob;
public IVTPatcher(byte[] data, Metadata md, byte[] ivtBlob) {
this.data = data;
this.md = md;
this.ivtBlob = ivtBlob;
}
public IVTPatcherResult Patch() {
var rids = md.GetCustomAttributeRidList(Table.Assembly, 1);
if (rids.Count == 0)
return IVTPatcherResult.NoCustomAttributes;
if (FindIVT(rids, out var foundIVT, out uint ivtBlobOffset)) {
Array.Copy(ivtBlob, 0, data, ivtBlobOffset, ivtBlob.Length);
return IVTPatcherResult.OK;
}
if (!foundIVT)
return IVTPatcherResult.NoIVTs;
return IVTPatcherResult.IVTBlobTooSmall;
}
bool FindIVT(RidList rids, out bool foundIVT, out uint ivtBlobDataOffset) {
ivtBlobDataOffset = 0;
foundIVT = false;
uint otherIVTBlobOffset = uint.MaxValue;
var blobStream = md.BlobStream.CreateReader();
var tbl = md.TablesStream.CustomAttributeTable;
uint baseOffset = (uint)tbl.StartOffset;
var columnType = tbl.Columns[1];
var columnValue = tbl.Columns[2];
for (int i = 0; i < rids.Count; i++) {
uint rid = rids[i];
uint offset = baseOffset + (rid - 1) * tbl.RowSize;
uint type = ReadColumn(columnType, offset);
if (!IsIVTCtor(type))
continue;
foundIVT = true;
uint blobOffset = ReadColumn(columnValue, offset);
if (blobOffset + ivtBlob.Length > blobStream.Length)
continue;
blobStream.Position = blobOffset;
if (!blobStream.TryReadCompressedUInt32(out uint len))
continue;
var compressedSize = blobStream.Position - blobOffset;
if (compressedSize + len < ivtBlob.Length)
continue;
if (!ParseIVTBlob(ref blobStream, blobStream.Position + len, out var publicKeyString))
continue;
if (StringComparer.OrdinalIgnoreCase.Equals(publicKeyString, ROSLYN_OPEN_SOURCE_PUBLIC_KEY)) {
ivtBlobDataOffset = (uint)md.BlobStream.StartOffset + blobOffset;
return true;
}
else
otherIVTBlobOffset = (uint)md.BlobStream.StartOffset + blobOffset;
}
if (otherIVTBlobOffset != uint.MaxValue) {
ivtBlobDataOffset = otherIVTBlobOffset;
return true;
}
return false;
}
static bool ParseIVTBlob(ref DataReader reader, uint end, out string? publicKeyString) {
publicKeyString = null;
if ((ulong)reader.Position + 2 > end)
return false;
if (reader.ReadUInt16() != 1)
return false;
if (!reader.TryReadCompressedUInt32(out uint len) || (ulong)reader.Position + len >= end)
return false;
var s = reader.ReadUtf8String((int)len);
const string PublicKeyPattern = "PublicKey=";
int index = s.IndexOf(PublicKeyPattern, StringComparison.OrdinalIgnoreCase);
if (index >= 0)
publicKeyString = s.Substring(index + PublicKeyPattern.Length).Trim();
return true;
}
bool IsIVTCtor(uint codedType) {
if (!CodedToken.CustomAttributeType.Decode(codedType, out MDToken ctor))
return false;
switch (ctor.Table) {
case Table.Method:
uint declTypeDefToken = md.GetOwnerTypeOfMethod(ctor.Rid);
return IsIVT_TypeDef(declTypeDefToken);
case Table.MemberRef:
if (!md.TablesStream.TryReadMemberRefRow(ctor.Rid, out var memberRefRow))
return false;
if (!CodedToken.MemberRefParent.Decode(memberRefRow.Class, out MDToken parentToken))
return false;
switch (parentToken.Table) {
case Table.TypeDef:
return IsIVT_TypeDef(parentToken.Rid);
case Table.TypeRef:
return IsIVT_TypeRef(parentToken.Rid);
case Table.TypeSpec:
default:
return false;
}
default:
return false;
}
}
bool IsIVT_TypeRef(uint typeRefRid) {
if (!md.TablesStream.TryReadTypeRefRow(typeRefRid, out var typeRefRow))
return false;
if (!CodedToken.ResolutionScope.Decode(typeRefRow.ResolutionScope, out MDToken scope) || scope.Table == Table.TypeRef)
return false;
return IsIVTCtor(typeRefRow.Namespace, typeRefRow.Name);
}
bool IsIVT_TypeDef(uint typeDefRid) {
if (!md.TablesStream.TryReadTypeDefRow(typeDefRid, out var typeDefRow))
return false;
if ((typeDefRow.Flags & (uint)TypeAttributes.VisibilityMask) >= (uint)TypeAttributes.NestedPublic)
return false;
return IsIVTCtor(typeDefRow.Namespace, typeDefRow.Name);
}
bool IsIVTCtor(uint @namespace, uint name) =>
md.StringsStream.ReadNoNull(name) == InternalsVisibleToAttribute &&
md.StringsStream.ReadNoNull(@namespace) == System_Runtime_CompilerServices;
static readonly UTF8String System_Runtime_CompilerServices = new UTF8String("System.Runtime.CompilerServices");
static readonly UTF8String InternalsVisibleToAttribute = new UTF8String("InternalsVisibleToAttribute");
uint ReadColumn(ColumnInfo column, uint columnOffset) {
columnOffset += (uint)column.Offset;
switch (column.Size) {
case 1: return data[columnOffset];
case 2: return data[columnOffset++] | ((uint)data[columnOffset] << 8);
case 4: return data[columnOffset++] | ((uint)data[columnOffset++] << 8) | ((uint)data[columnOffset++] << 16) | ((uint)data[columnOffset] << 24);
default: throw new InvalidOperationException();
}
}
}
}