1
mirror of https://github.com/thepeacockproject/Peacock synced 2024-11-29 09:15:11 +01:00
Peacock/patcher/AOBScanner.cs
Reece Dunham 6245e91624 Initial commit
Co-authored-by: Tino Roivanen <tino.roivanen98@gmail.com>
Co-authored-by: Govert de Gans <grappigegovert@hotmail.com>
Co-authored-by: Gray Olson <gray@grayolson.com>
Co-authored-by: Alexandre Sanchez <alex73630@gmail.com>
Co-authored-by: Anthony Fuller <24512050+anthonyfuller@users.noreply.github.com>
Co-authored-by: atampy25 <24306974+atampy25@users.noreply.github.com>
Co-authored-by: David <davidstulemeijer@gmail.com>
Co-authored-by: c0derMo <c0dermo@users.noreply.github.com>
Co-authored-by: Jeevat Singh <jeevatt.singh@gmail.com>
Signed-off-by: Reece Dunham <me@rdil.rocks>
2022-10-19 21:33:45 -04:00

477 lines
19 KiB
C#

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
namespace HitmanPatcher
{
internal static class AOBScanner
{
public static bool TryGetHitmanVersionByScanning(Process process,
IntPtr hProcess, out HitmanVersion result)
{
Stopwatch bench = Stopwatch.StartNew();
IntPtr baseAddress = process.MainModule.BaseAddress;
byte[] exeData = new byte[process.MainModule.ModuleMemorySize];
Pinvoke.ReadProcessMemory(hProcess, baseAddress, exeData,
(UIntPtr) exeData.Length,
out _); // fuck it, just read the whole thing
Task<IEnumerable<Patch[]>> getCertpinPatches =
Task.Factory.ContinueWhenAll(new Task<Patch[]>[]
{
findCertpin_nearjump(exeData),
findCertpin_shortjump(exeData),
},
tasks =>
tasks.Select(task => task.Result)
.Where(x => x != null));
Task<IEnumerable<Patch[]>> getAuthheadPatches =
Task.Factory.ContinueWhenAll(new Task<Patch[]>[]
{
findAuthhead3_30(exeData),
findAuthhead2_72(exeData),
findAuthhead1_15(exeData),
},
tasks =>
tasks.Select(task => task.Result)
.Where(x => x != null));
Task<IEnumerable<Patch[]>> getConfigdomainPatches =
Task.Factory.ContinueWhenAll(new Task<Patch[]>[]
{
findConfigdomain(exeData),
},
tasks =>
tasks.Select(task => task.Result)
.Where(x => x != null));
Task<IEnumerable<Patch[]>> getProtocolPatches =
Task.Factory.ContinueWhenAll(new Task<Patch[]>[]
{
findProtocol3_30(exeData),
},
tasks =>
tasks.Select(task => task.Result)
.Where(x => x != null));
Task<IEnumerable<Patch[]>> getDynresForceofflinePatches =
Task.Factory.ContinueWhenAll(new Task<Patch[]>[]
{
findDynresForceoffline(exeData),
},
tasks =>
tasks.Select(task => task.Result)
.Where(x => x != null));
Task<IEnumerable<Patch[]>>[] alltasks =
{
getCertpinPatches, getAuthheadPatches, getConfigdomainPatches,
getProtocolPatches, getDynresForceofflinePatches
};
// ReSharper disable once CoVariantArrayConversion
Task.WaitAll(alltasks);
bench.Stop();
Console.WriteLine(bench.Elapsed.ToString());
// error out if any task does not have exactly 1 result
if (alltasks.Any(task => task.Result.Count() != 1))
{
result = null;
return false;
}
#if DEBUG
Note("CertPin", getCertpinPatches.Result.First()[0]);
Note("AuthHeader1", getAuthheadPatches.Result.First()[0]);
Note("AuthHeader2", getAuthheadPatches.Result.First()[1]);
Note("ConfigDomain", getConfigdomainPatches.Result.First()[0]);
Note("Protocol", getProtocolPatches.Result.First()[0]);
Note("DynamicResources", getDynresForceofflinePatches.Result.First()[0]);
#endif
result = new HitmanVersion()
{
certpin = getCertpinPatches.Result.First(),
authheader = getAuthheadPatches.Result.First(),
configdomain = getConfigdomainPatches.Result.First(),
protocol = getProtocolPatches.Result.First(),
dynres_noforceoffline =
getDynresForceofflinePatches.Result.First()
};
return true;
}
#region Utilities
#if DEBUG
private static void Note(string name, Patch patch)
{
MainForm.GetInstance().log($"{name}: {patch.offset:X} {BitConverter.ToString(patch.original).Replace("-", string.Empty)} {BitConverter.ToString(patch.patch).Replace("-", string.Empty)}");
}
#endif
#endregion
#region certpin
private static Task<Patch[]> findCertpin_nearjump(byte[] data)
{
return Task.Factory.ContinueWhenAll(new[]
{
Task.Factory.StartNew(() => findPattern(data, 0xe,
"? ? 9afdffffc747302f000000c7471803000000")),
Task.Factory.StartNew(() => findPattern(data, 0xc,
"? ? 9afdffffc747302f000000c7471803000000")), // 1.15
Task.Factory.StartNew(() => findPattern(data, 0xd,
"? ? 6ffdffffc747302f000000c7471804000000"))
}, tasks =>
{
IEnumerable<int> offsets =
tasks.SelectMany(task => task.Result);
if (offsets.Count() != 1)
return null;
return new[]
{
new Patch(offsets.First(), "0F85", "90E9",
MemProtection.PAGE_EXECUTE_READ)
};
});
}
private static Task<Patch[]> findCertpin_shortjump(byte[] data)
{
return Task.Factory.ContinueWhenAll(new[]
{
Task.Factory.StartNew(() =>
findPattern(data, 0x3, "? 0ec746302f000000c7461803000000")),
Task.Factory.StartNew(() =>
findPattern(data, 0x2,
"? 0ec746302f000000c7461803000000")), // v2.13
}, tasks =>
{
IEnumerable<int> offsets =
tasks.SelectMany(task => task.Result);
if (offsets.Count() != 1)
return null;
return new[]
{
new Patch(offsets.First(), "75", "EB",
MemProtection.PAGE_EXECUTE_READ)
};
});
}
#endregion
#region authheader
private static Task<Patch[]> findAuthhead3_30(byte[] data)
{
return Task.Factory.ContinueWhenAll(new[]
{
Task.Factory.StartNew(() =>
findPattern(data, 0xd, "0f85b50000004883f90675e8")),
Task.Factory.StartNew(() =>
findPattern(data, 0xd, "9090909090904883f90675e8")),
Task.Factory.StartNew(() =>
findPattern(data, 0x3, "0f84b800000084db0f85b0000000")),
Task.Factory.StartNew(() =>
findPattern(data, 0x3, "90909090909084db0f85b0000000"))
}, tasks =>
{
// concat results for non-patched and patched
IEnumerable<int> offsetspart1 =
tasks.Take(2).SelectMany(task => task.Result);
IEnumerable<int> offsetspart2 = tasks.Skip(2).Take(2)
.SelectMany(task => task.Result);
if (offsetspart1.Count() != 1 || offsetspart2.Count() != 1)
return null;
return new[]
{
new Patch(offsetspart1.First(), "0F85B5000000",
"909090909090", MemProtection.PAGE_EXECUTE_READ),
new Patch(offsetspart2.First(), "0F84B8000000",
"909090909090", MemProtection.PAGE_EXECUTE_READ)
};
});
}
private static Task<Patch[]> findAuthhead2_72(byte[] data)
{
return Task.Factory.ContinueWhenAll(new[]
{
Task.Factory.StartNew(() =>
findPattern(data, 0x8, "? 18488d15ff02cd00")),
Task.Factory.StartNew(() =>
findPattern(data, 0x8, "? 18488d155fd6cc00")),
Task.Factory.StartNew(() =>
findPattern(data, 0x7, "? 18488d152018cc00")), // v2.13
Task.Factory.StartNew(() =>
findPattern(data, 0xc, "0f84860000004584e4")),
Task.Factory.StartNew(() =>
findPattern(data, 0xc, "9090909090904584e4")),
Task.Factory.StartNew(() =>
findPattern(data, 0xb, "0f84830000004584e4")), // v2.13
Task.Factory.StartNew(() =>
findPattern(data, 0xb, "9090909090904584e4")), // v2.13
}, tasks =>
{
// concat results for non-patched and patched
IEnumerable<int> offsetspart1 =
tasks.Take(3).SelectMany(task => task.Result);
IEnumerable<int> offsetspart2 = tasks.Skip(3).Take(4)
.SelectMany(task => task.Result);
if (offsetspart1.Count() != 1 || offsetspart2.Count() != 1)
return null;
return new[]
{
new Patch(offsetspart1.First(), "75", "EB",
MemProtection.PAGE_EXECUTE_READ),
new Patch(offsetspart2.First(), "0F8486000000",
"909090909090", MemProtection.PAGE_EXECUTE_READ)
};
});
}
private static Task<Patch[]> findAuthhead1_15(byte[] data)
{
return Task.Factory.ContinueWhenAll(new[]
{
Task.Factory.StartNew(() =>
findPattern(data, 0x5, "0f84b3000000498bcf")),
Task.Factory.StartNew(() =>
findPattern(data, 0x5, "909090909090498bcf")),
Task.Factory.StartNew(() =>
findPattern(data, 0x5, "0f84a30000004584ed")),
Task.Factory.StartNew(() =>
findPattern(data, 0x5, "9090909090904584ed"))
}, tasks =>
{
// concat results for non-patched and patched
IEnumerable<int> offsetspart1 =
tasks.Take(2).SelectMany(task => task.Result);
IEnumerable<int> offsetspart2 = tasks.Skip(2).Take(2)
.SelectMany(task => task.Result);
if (offsetspart1.Count() != 1 || offsetspart2.Count() != 1)
return null;
return new[]
{
new Patch(offsetspart1.First(), "0F84B3000000",
"909090909090", MemProtection.PAGE_EXECUTE_READ),
new Patch(offsetspart2.First(), "0F84A3000000",
"909090909090", MemProtection.PAGE_EXECUTE_READ)
};
});
}
#endregion
#region configdomain
private static Task<Patch[]> findConfigdomain(byte[] data)
{
return Task.Factory.ContinueWhenAll(new[]
{
Task.Factory.StartNew(() => findPattern(data, 0xe,
"488905 ? ? ? 03488d0d ? ? ? 034883c4205b48ff25 ? ? ? 01"))
.ContinueWith(task =>
task.Result.Select(addr =>
addr + 14 +
BitConverter.ToInt32(data, addr + 10))
.ToArray()),
Task.Factory.StartNew(() => findPattern(data, 0xd,
"83f06e69d093010001e8 ? ? 0400488d05 ? ? ? 01ba000100004c8d05 ? ? ? 01488905 ? ? ? 02488d0d ? ? ? 02"))
.ContinueWith(task =>
task.Result.Select(addr =>
addr + 47 +
BitConverter.ToInt32(data, addr + 43))
.ToArray()),
Task.Factory.StartNew(() => findPattern(data, 0xd,
"83f16e69d193010001488d0d ? ? ? 02e8 ? ? 0400488d05 ? ? ? 01ba000100004c8d05 ? ? ? 01488905 ? ? ? 02488d0d ? ? ? 02"))
.ContinueWith(task =>
task.Result.Select(addr =>
addr + 54 +
BitConverter.ToInt32(data, addr + 50))
.ToArray()), // 2.13: big boy
Task.Factory.StartNew(() => findPattern(data, 0x0,
"4883ec28baa71ace87488d0d ? ? ? 02e8 ? ? 0200488d05 ? ? ? 01ba000100004c8d05 ? ? ? 01488905 ? ? ? 02488d0d ? ? ? 02"))
.ContinueWith(task =>
task.Result.Select(addr =>
addr + 54 +
BitConverter.ToInt32(data, addr + 50))
.ToArray()) // 1.15: big boy as well
}, tasks =>
{
IEnumerable<int> offsets =
tasks.SelectMany(task => task.Result);
if (offsets.Count() != 1)
return null;
return new[]
{
new Patch(offsets.First(), "", "",
MemProtection.PAGE_READWRITE, "configdomain")
};
});
}
#endregion
#region protocol
private static Task<Patch[]> findProtocol3_30(byte[] data)
{
return Task.Factory.ContinueWhenAll(new[]
{
Task.Factory.StartNew(() =>
findPattern(data, 0x0, "? 747470733a2f2f7b307d00")),
Task.Factory.StartNew(() =>
findPattern(data, 0x8, "? 747470733a2f2f7b307d00"))
}, tasks =>
{
IEnumerable<int> offsets =
tasks.SelectMany(task => task.Result);
if (offsets.Count() != 1)
return null;
return new[]
{
new Patch(offsets.First(), "68", "61",
MemProtection.PAGE_READONLY)
};
});
}
#endregion
#region dynres_forceoffline
private static Task<Patch[]> findDynresForceoffline(byte[] data)
{
return Task.Factory.ContinueWhenAll(new[]
{
Task.Factory.StartNew(() => findPattern(data, 0x1,
"83f17269c193010001488d0d ? ? ? 0383f06569d093010001e8 ? ? 0400488d05 ? ? ? 01c705 ? ? ? 0301000000"))
.ContinueWith(task =>
task.Result.Select(addr =>
addr + 47 +
BitConverter.ToInt32(data, addr + 39))
.ToArray()),
Task.Factory.StartNew(() => findPattern(data, 0xb,
"83f17269c193010001488d0d ? ? ? 0283f06569d093010001e8 ? ? 0400488d05 ? ? ? 01c705 ? ? ? 0201000000")) // 2.72
.ContinueWith(task =>
task.Result.Select(addr =>
addr + 47 +
BitConverter.ToInt32(data, addr + 39))
.ToArray()),
Task.Factory.StartNew(() => findPattern(data, 0x7,
"83f17269c193010001488d0d ? ? ? 0283f06569d093010001e8 ? ? 0400488d05 ? ? ? 01c705 ? ? ? 0201000000")) // 2.13
.ContinueWith(task =>
task.Result.Select(addr =>
addr + 47 +
BitConverter.ToInt32(data, addr + 39))
.ToArray()),
Task.Factory.StartNew(() => findPattern(data, 0x4,
"ba2734ff12488d0d ? ? ? 02e8 ? ? 0200488d05 ? ? ? 01c705 ? ? ? 0201000000")) // 1.15
.ContinueWith(task =>
task.Result.Select(addr =>
addr + 34 +
BitConverter.ToInt32(data, addr + 26))
.ToArray())
}, tasks =>
{
IEnumerable<int> offsets =
tasks.SelectMany(task => task.Result);
if (offsets.Count() != 1)
return null;
return new[]
{
new Patch(offsets.First(), "01", "00",
MemProtection.PAGE_EXECUTE_READWRITE)
};
});
}
#endregion
private static int[] findPattern(byte[] data, byte alignment,
string pattern)
{
List<int> results = new List<int>();
int offset = 0;
// convert string pattern to byte array
List<byte> bytepatternlist = new List<byte>(pattern.Length);
List<int> wildcardslist = new List<int>(pattern.Length);
pattern = pattern.Replace(" ", ""); // remove spaces
for (int i = 0; i < pattern.Length; i += 2)
{
string twochars = pattern.Substring(i, 2);
if (twochars.StartsWith("?"))
{
if (wildcardslist.Count ==
0) // pattern starts with wildcard(s)
{
alignment += 1;
offset -= 1;
i -= 1; // step forward 1 less because it's one '?' instead of two hex chars
}
else
{
wildcardslist.Add(1);
for (int j = wildcardslist.Count - 2;
wildcardslist[j] > 0;
j--)
{
wildcardslist[j] += 1;
}
bytepatternlist.Add(0x00); // dummy value
i -= 1; // step forward 1 less because it's one '?' instead of two hex chars
}
}
else
{
wildcardslist.Add(0);
bytepatternlist.Add(Convert.ToByte(twochars, 16));
}
}
byte[] bytepattern = bytepatternlist.ToArray();
int[] wildcards = wildcardslist.ToArray();
int patternLen = bytepattern.Length;
int searchEnd = data.Length - patternLen;
// int[] matched = new int[patternLen];
byte firstbyte = bytepattern[0];
// search data for byte array
for (int i = alignment; i < searchEnd; i += 16)
{
if (data[i] == firstbyte)
{
var k = 1;
k += wildcards[k];
while (data[i + k] == bytepattern[k])
{
k += 1;
if (k == patternLen)
{
results.Add(i + offset);
break;
}
k += wildcards[k];
}
}
}
return results.ToArray();
}
}
}