195 lines
7.1 KiB
C#
Raw Permalink Normal View History

2021-09-20 18:20:01 +02:00
/*
Copyright (C) 2014-2019 de4dot@gmail.com
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();
}
}
}
}