2021-09-20 18:20:01 +02:00

370 lines
10 KiB
C#

/*
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/>.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using dnlib.IO;
using dnlib.PE;
namespace dnSpy.Debugger.DotNet.CorDebug.AntiAntiDebug {
sealed class ECallManager {
/// <summary>
/// true if we found the CLR module (mscorwks/clr.dll/coreclr.dll)
/// </summary>
public bool FoundClrModule { get; }
readonly Dictionary<string, ECFunc[]> classToFuncsDict = new Dictionary<string, ECFunc[]>(StringComparer.Ordinal);
ulong clrDllBaseAddress;
/// <summary>
/// Constructor that can be used to test this class on some random clr file
/// </summary>
/// <param name="filename"></param>
public ECallManager(string filename) {
FoundClrModule = true;
Initialize(filename);
}
public ECallManager(int pid, string clrPath) {
using (var process = Process.GetProcessById(pid)) {
FoundClrModule = false;
foreach (ProcessModule? mod in process.Modules) {
if (StringComparer.OrdinalIgnoreCase.Equals(clrPath, mod!.FileName)) {
FoundClrModule = true;
Initialize(mod.FileName);
clrDllBaseAddress = (ulong)mod.BaseAddress.ToInt64();
break;
}
}
}
Debug.Assert(FoundClrModule, $"Couldn't find {clrPath}");
}
void Initialize(string filename) {
try {
using (var reader = new ECallListReader(filename)) {
foreach (var ecc in reader.List) {
var fname = ecc.FullName;
bool b = classToFuncsDict.ContainsKey(fname);
Debug.Assert(!b);
if (!b)
classToFuncsDict[fname] = ecc.Functions;
}
}
}
catch {
}
}
public bool FindFunc(string classFullName, string methodName, out ulong methodAddr) {
methodAddr = 0;
if (!classToFuncsDict.TryGetValue(classFullName, out var funcs))
return false;
foreach (var func in funcs) {
if (func.Name == methodName) {
methodAddr = clrDllBaseAddress + func.FunctionRVA;
return true;
}
}
return false;
}
}
enum FCFuncFlag : ushort {
EndOfArray = 0x01,
HasSignature = 0x02,
Unreferenced = 0x04, // Suppress unused fcall check
QCall = 0x08, // QCall - mscorlib.dll to mscorwks.dll transition implemented as PInvoke
}
[DebuggerDisplay("{FullName}")]
readonly struct ECClass {
public readonly string Namespace;
public readonly string Name;
public readonly ECFunc[] Functions;
public string FullName => string.IsNullOrEmpty(Namespace) ? Name : Namespace + "." + Name;
public ECClass(string ns, string name, ECFunc[] funcs) {
Namespace = ns;
Name = name;
Functions = funcs;
}
}
// Unfortunately this enum is different from sscli20's CorInfoIntrinsics
enum CorInfoIntrinsics : byte {
Illegal = byte.MaxValue,
}
enum DynamicID : byte {
FastAllocateString,
CtorCharArrayManaged,
CtorCharArrayStartLengthManaged,
CtorCharCountManaged,
CtorCharPtrManaged,
CtorCharPtrStartLengthManaged,
InternalGetCurrentThread,
InvalidDynamicFCallId = byte.MaxValue,
}
[DebuggerDisplay("{FunctionRVA} {Name}")]
readonly struct ECFunc {
public readonly uint RecordRVA;
public readonly uint Flags;
public readonly uint FunctionRVA;
public readonly string Name;
public readonly uint MethodSigRVA;
public bool HasSignature => MethodSigRVA != 0;
public bool IsUnreferenced => (Flags & (uint)FCFuncFlag.Unreferenced) != 0;
public bool IsQCall => (Flags & (uint)FCFuncFlag.QCall) != 0;
public CorInfoIntrinsics IntrinsicID => (CorInfoIntrinsics)(Flags >> 16);
public DynamicID DynamicID => (DynamicID)(Flags >> 24);
public ECFunc(uint recRva, uint flags, uint methRva, string name, uint sigRva) {
RecordRVA = recRva;
Flags = flags;
FunctionRVA = methRva;
Name = name;
MethodSigRVA = sigRva;
}
}
struct ECallListReader : IDisposable {
readonly PEImage peImage;
DataReader reader;
readonly bool is32bit;
readonly uint ptrSize;
readonly uint endRva;
readonly List<ECClass> list;
TableFormat? tableFormat;
public List<ECClass> List => list;
enum TableFormat {
// .NET 2.0 to ???
V1,
// .NET 3.x and later
V2,
}
public ECallListReader(string filename) {
peImage = new PEImage(filename);
reader = peImage.CreateReader();
is32bit = peImage.ImageNTHeaders.OptionalHeader.Magic == 0x010B;
ptrSize = is32bit ? 4U : 8;
var last = peImage.ImageSectionHeaders[peImage.ImageSectionHeaders.Count - 1];
endRva = (uint)last.VirtualAddress + last.VirtualSize;
list = new List<ECClass>();
tableFormat = null;
Read();
}
ulong ReadPtr(long pos) {
reader.Position = (uint)pos;
return is32bit ? reader.ReadUInt32() : reader.ReadUInt64();
}
uint? ReadRva(long pos) {
ulong ptr = ReadPtr(pos);
ulong b = peImage.ImageNTHeaders.OptionalHeader.ImageBase;
if (ptr == 0)
return 0;
if (ptr < b)
return null;
ptr -= b;
return ptr >= endRva ? (uint?)null : (uint)ptr;
}
ImageSectionHeader? FindSection(string name) {
foreach (var sect in peImage.ImageSectionHeaders) {
if (sect.DisplayName == name)
return sect;
}
return null;
}
void Read() {
// Refs: coreclr/src/vm/{ecalllist.h,mscorlib.cpp,ecall.h,ecall.cpp}
long pos = 0;
long end = reader.Length - (3 * ptrSize - 1);
List<ECClass> eccList = new List<ECClass>();
for (; pos <= end; pos += ptrSize) {
tableFormat = null;
var ecc = ReadECClass(pos, true);
if (ecc is null)
continue;
for (long pos2 = pos; pos2 <= end; pos2 += 3 * ptrSize) {
ecc = ReadECClass(pos2, false);
if (ecc is null)
break;
eccList.Add(ecc.Value);
}
if (eccList.Count >= 20)
break;
eccList.Clear();
}
list.AddRange(eccList);
}
ECClass? ReadECClass(long pos, bool first) {
if (pos + ptrSize * 3 > reader.Length)
return null;
var name = ReadAsciizIdPtr(pos);
if (name is null)
return null;
var ns = ReadAsciizIdPtr(pos + ptrSize);
if (ns is null)
return null;
var funcs = ReadECFuncs(ReadRva(pos + ptrSize * 2), first);
if (funcs is null)
return null;
return new ECClass(ns, name, funcs);
}
ECFunc[]? ReadECFuncs(uint? rva, bool first) {
if (rva is null || rva.Value == 0)
return null;
var funcs = new List<ECFunc>();
var pos = (long)peImage.ToFileOffset((RVA)rva.Value);
if (tableFormat is null)
InitializeTableFormat(pos);
if (tableFormat is null)
return null;
var tblSize = tableFormat == TableFormat.V1 ? 5 * ptrSize : 3 * ptrSize;
for (;;) {
if (pos + ptrSize > reader.Length)
return null;
ulong flags = ReadPtr(pos);
if ((flags & (ulong)FCFuncFlag.EndOfArray) != 0)
break;
bool hasSig = (flags & (ulong)FCFuncFlag.HasSignature) != 0;
uint size = tblSize + (hasSig ? ptrSize : 0);
if (pos + size > reader.Length)
return null;
uint? methRva;
string? name;
if (tableFormat == TableFormat.V1) {
methRva = ReadRva(pos + ptrSize * 1);
ulong nullPtr1 = ReadPtr(pos + ptrSize * 2);
ulong nullPtr2 = ReadPtr(pos + ptrSize * 3);
name = ReadAsciizIdPtr(pos + ptrSize * 4);
if (nullPtr1 != 0 || nullPtr2 != 0)
return null;
}
else {
Debug.Assert(tableFormat == TableFormat.V2);
methRva = ReadRva(pos + ptrSize * 1);
name = ReadAsciizIdPtr(pos + ptrSize * 2);
}
if (name is null || methRva is null)
return null;
if (methRva.Value != 0 && !IsCodeRva(methRva.Value))
return null;
uint sigRva = 0;
if (hasSig) {
var srva = ReadRva(pos + tblSize);
if (srva is null || srva.Value == 0)
return null;
sigRva = srva.Value;
}
uint recRva = (uint)peImage.ToRVA((FileOffset)pos);
funcs.Add(new ECFunc(recRva, (uint)flags, methRva.Value, name, sigRva));
pos += size;
}
// A zero length array is allowed (eg. clr.dll 4.6.96.0) so we can't return null if we find one
return funcs.ToArray();
}
void InitializeTableFormat(long pos) {
if (pos + ptrSize > reader.Length)
return;
ulong flags = ReadPtr(pos);
if ((flags & (ulong)FCFuncFlag.EndOfArray) != 0)
return;
bool hasSig = (flags & (ulong)FCFuncFlag.HasSignature) != 0;
if (pos + ptrSize * (5 + (hasSig ? 1 : 0)) < reader.Length) {
uint? methRva = ReadRva(pos + ptrSize * 1);
ulong nullPtr1 = ReadPtr(pos + ptrSize * 2);
ulong nullPtr2 = ReadPtr(pos + ptrSize * 3);
var name = ReadAsciizIdPtr(pos + ptrSize * 4);
if (nullPtr1 == 0 && nullPtr2 == 0 && name is not null && methRva is not null && (methRva.Value == 0 || IsCodeRva(methRva.Value))) {
tableFormat = TableFormat.V1;
return;
}
}
if (pos + ptrSize * (3 + (hasSig ? 1 : 0)) < reader.Length) {
uint? methRva = ReadRva(pos + ptrSize * 1);
var name = ReadAsciizIdPtr(pos + ptrSize * 2);
if (name is not null && methRva is not null && (methRva.Value == 0 || IsCodeRva(methRva.Value))) {
tableFormat = TableFormat.V2;
return;
}
}
}
bool IsCodeRva(uint rva) {
if (rva == 0)
return false;
var textSect = FindSection(".text");
if (textSect is null)
return false;
return (uint)textSect.VirtualAddress <= rva && rva < (uint)textSect.VirtualAddress + Math.Max(textSect.VirtualSize, textSect.SizeOfRawData);
}
string? ReadAsciizIdPtr(long pos) => ReadAsciizId(ReadRva(pos));
string? ReadAsciizId(uint? rva) {
if (rva is null || rva.Value == 0)
return null;
reader.Position = (uint)peImage.ToFileOffset((RVA)rva.Value);
var bytes = reader.TryReadBytesUntil(0);
const int MIN_ID_LEN = 2;
const int MAX_ID_LEN = 256;
if (bytes is null || bytes.Length < MIN_ID_LEN || bytes.Length > MAX_ID_LEN)
return null;
foreach (var b in bytes) {
var ch = (char)b;
if (!(('a' <= ch && ch <= 'z') || ('A' <= ch && ch <= 'Z') || ('0' <= ch && ch <= '9') || ch == '_' || ch == '.'))
return null;
}
var s = Encoding.ASCII.GetString(bytes);
if (char.IsNumber(s[0]))
return null;
return s;
}
public void Dispose() => peImage?.Dispose();
}
}