mirror of
https://github.com/thepeacockproject/Peacock
synced 2024-11-29 09:15:11 +01:00
6245e91624
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>
477 lines
19 KiB
C#
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();
|
|
}
|
|
}
|
|
}
|