Skip to content

Commit f11b9cd

Browse files
author
TheLeftExit
committed
Reworked to use native allocations
1 parent 0793833 commit f11b9cd

File tree

12 files changed

+645
-502
lines changed

12 files changed

+645
-502
lines changed

Trickster.sln

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,24 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio Version 16
4-
VisualStudioVersion = 16.0.31702.278
3+
# Visual Studio Version 17
4+
VisualStudioVersion = 17.0.31919.166
55
MinimumVisualStudioVersion = 10.0.40219.1
6-
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Trickster", "Trickster\Trickster.csproj", "{1FF948A3-7E4B-48D9-A551-C8EC0D84A39F}"
6+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Trickster", "Trickster\Trickster.csproj", "{1FF948A3-7E4B-48D9-A551-C8EC0D84A39F}"
77
EndProject
88
Global
99
GlobalSection(SolutionConfigurationPlatforms) = preSolution
10+
Debug|Any CPU = Debug|Any CPU
1011
Debug|x64 = Debug|x64
12+
Release|Any CPU = Release|Any CPU
1113
Release|x64 = Release|x64
1214
EndGlobalSection
1315
GlobalSection(ProjectConfigurationPlatforms) = postSolution
16+
{1FF948A3-7E4B-48D9-A551-C8EC0D84A39F}.Debug|Any CPU.ActiveCfg = Debug|x64
17+
{1FF948A3-7E4B-48D9-A551-C8EC0D84A39F}.Debug|Any CPU.Build.0 = Debug|x64
1418
{1FF948A3-7E4B-48D9-A551-C8EC0D84A39F}.Debug|x64.ActiveCfg = Debug|x64
1519
{1FF948A3-7E4B-48D9-A551-C8EC0D84A39F}.Debug|x64.Build.0 = Debug|x64
20+
{1FF948A3-7E4B-48D9-A551-C8EC0D84A39F}.Release|Any CPU.ActiveCfg = Release|x64
21+
{1FF948A3-7E4B-48D9-A551-C8EC0D84A39F}.Release|Any CPU.Build.0 = Release|x64
1622
{1FF948A3-7E4B-48D9-A551-C8EC0D84A39F}.Release|x64.ActiveCfg = Release|x64
1723
{1FF948A3-7E4B-48D9-A551-C8EC0D84A39F}.Release|x64.Build.0 = Release|x64
1824
EndGlobalSection

Trickster/MainForm.cs

Lines changed: 0 additions & 128 deletions
This file was deleted.

Trickster/Memory/RttiScanner.cs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Runtime.CompilerServices;
4+
using System.Runtime.InteropServices;
5+
using System.Text;
6+
using Windows.Win32;
7+
using Windows.Win32.Foundation;
8+
9+
namespace TheLeftExit.Trickster.Memory {
10+
public unsafe struct RttiScanner : IDisposable {
11+
private nuint _baseAddress;
12+
private nuint _size;
13+
private byte* _pointer;
14+
public RttiScanner(HANDLE handle, nuint mainModuleBaseAddress, nuint mainModuleSize) {
15+
_baseAddress = mainModuleBaseAddress;
16+
_size = mainModuleSize;
17+
_pointer = (byte*)NativeMemory.Alloc(mainModuleSize);
18+
if (!Kernel32.ReadProcessMemory(handle, (void*)mainModuleBaseAddress, _pointer, mainModuleSize))
19+
throw new ApplicationException();
20+
}
21+
22+
public void Dispose() {
23+
NativeMemory.Free(_pointer);
24+
}
25+
26+
public unsafe bool TryRead(ulong address, int count, void* buffer) {
27+
if (address >= _baseAddress && address + (uint)count < _baseAddress + _size) {
28+
void* sourceAddress = _pointer + (address - _baseAddress);
29+
Unsafe.CopyBlock(buffer, sourceAddress, (uint)count);
30+
return true;
31+
}
32+
return false;
33+
}
34+
35+
public bool TryRead<T>(ulong address, out T result) where T : unmanaged {
36+
fixed (void* ptr = &result)
37+
return TryRead(address, sizeof(T), ptr);
38+
}
39+
public bool TryRead<T>(ulong address, Span<T> buffer) where T : unmanaged {
40+
fixed (void* ptr = buffer)
41+
return TryRead(address, buffer.Length * sizeof(T), ptr);
42+
}
43+
44+
private const int BUFFER_SIZE = 60;
45+
46+
public string GetClassName64(ulong address) {
47+
if (!TryRead(address - 0x08, out ulong object_locator)) return null;
48+
if (!TryRead(object_locator + 0x14, out ulong base_offset)) return null;
49+
ulong base_address = object_locator - base_offset;
50+
if (!TryRead(object_locator + 0x0C, out uint type_descriptor_offset)) return null;
51+
ulong class_name = base_address + type_descriptor_offset + 0x10 + 0x04;
52+
byte* buffer = stackalloc byte[BUFFER_SIZE];
53+
buffer[0] = (byte)'?';
54+
if (!TryRead(class_name, BUFFER_SIZE - 1, buffer + 1)) return null;
55+
byte* target = stackalloc byte[BUFFER_SIZE];
56+
uint len = DbgHelp.UnDecorateSymbolName(new PCSTR(buffer), new PSTR(target), BUFFER_SIZE, 0x1800);
57+
return len != 0 ? Encoding.UTF8.GetString(target, (int)len) : null;
58+
}
59+
60+
public string GetClassName32(ulong address) {
61+
if (!TryRead(address - 0x04, out uint object_locator)) return null;
62+
if (!TryRead(object_locator + 0x06, out uint type_descriptor)) return null;
63+
ulong class_name = type_descriptor + 0x0C + 0x03;
64+
byte* buffer = stackalloc byte[BUFFER_SIZE];
65+
buffer[0] = (byte)'?';
66+
if (!TryRead(class_name, BUFFER_SIZE - 1, buffer + 1)) return null;
67+
byte* target = stackalloc byte[BUFFER_SIZE];
68+
uint len = DbgHelp.UnDecorateSymbolName(new PCSTR(buffer), new PSTR(target), BUFFER_SIZE, 0x1000);
69+
return len != 0 ? Encoding.UTF8.GetString(target, (int)len) : null;
70+
}
71+
}
72+
}

Trickster/Memory/Trickster.cs

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
using System;
2+
using System.Diagnostics;
3+
using System.Linq;
4+
5+
using Windows.Win32;
6+
using Windows.Win32.Foundation;
7+
using Windows.Win32.System.Threading;
8+
using Windows.Win32.System.Memory;
9+
using System.Collections.Generic;
10+
using System.Threading.Tasks;
11+
using System.Threading;
12+
using System.Runtime.InteropServices;
13+
14+
namespace TheLeftExit.Trickster.Memory {
15+
public class TricksterException : Exception { }
16+
17+
public record struct TypeInfo(string Name, nuint Address, nuint Offset) {
18+
public override string ToString() {
19+
return $"{Name} - {Offset:X}";
20+
}
21+
}
22+
23+
public unsafe struct MemoryRegionInfo {
24+
public void* BaseAddress;
25+
public nuint Size;
26+
public MemoryRegionInfo(void* baseAddress, nuint size) { BaseAddress = baseAddress; Size = size; }
27+
}
28+
29+
public unsafe struct MemoryRegion {
30+
public void* Pointer;
31+
public void* BaseAddress;
32+
public nuint Size;
33+
public MemoryRegion(void* pointer, void* baseAddress, nuint size) { Pointer = pointer; BaseAddress = baseAddress; Size = size; }
34+
}
35+
36+
public unsafe class Trickster : IDisposable {
37+
private HANDLE _processHandle;
38+
39+
private nuint _mainModuleBaseAddress;
40+
private nuint _mainModuleSize;
41+
private bool _is32Bit;
42+
43+
public TypeInfo[] ScannedTypes;
44+
public MemoryRegion[] Regions;
45+
46+
public Trickster(Process process) {
47+
_processHandle = Kernel32.OpenProcess(PROCESS_ACCESS_RIGHTS.PROCESS_ALL_ACCESS, true, (uint)process.Id);
48+
if (_processHandle.IsNull) throw new TricksterException();
49+
50+
_mainModuleBaseAddress = (nuint)process.MainModule.BaseAddress.ToPointer();
51+
_mainModuleSize = (nuint)process.MainModule.ModuleMemorySize;
52+
53+
BOOL is32Bit; Kernel32.IsWow64Process(_processHandle, &is32Bit); _is32Bit = is32Bit;
54+
}
55+
56+
private TypeInfo[] ScanTypesCore() {
57+
List<TypeInfo> list = new();
58+
59+
using (RttiScanner processMemory = new(_processHandle, _mainModuleBaseAddress, _mainModuleSize)) {
60+
nuint inc = (nuint)(_is32Bit ? 4 : 8);
61+
Func<ulong, string> getClassName = _is32Bit ? processMemory.GetClassName32 : processMemory.GetClassName64;
62+
for (nuint offset = inc; offset < _mainModuleSize; offset += inc) {
63+
nuint address = _mainModuleBaseAddress + offset;
64+
if (getClassName(address) is string className) {
65+
list.Add(new TypeInfo(className, address, offset));
66+
}
67+
}
68+
}
69+
70+
return list.ToArray();
71+
}
72+
73+
private MemoryRegionInfo[] ScanRegionInfoCore() {
74+
ulong stop = _is32Bit ? uint.MaxValue : 0x7ffffffffffffffful;
75+
nuint size = (nuint)sizeof(MEMORY_BASIC_INFORMATION);
76+
77+
List<MemoryRegionInfo> list = new();
78+
79+
MEMORY_BASIC_INFORMATION mbi;
80+
nuint address = 0;
81+
82+
83+
while (address < stop && Kernel32.VirtualQueryEx(_processHandle, (void*)address, &mbi, size) > 0 && address + mbi.RegionSize > address) {
84+
if (mbi.State == VIRTUAL_ALLOCATION_TYPE.MEM_COMMIT &&
85+
!mbi.Protect.HasFlag(PAGE_PROTECTION_FLAGS.PAGE_NOACCESS) &&
86+
!mbi.Protect.HasFlag(PAGE_PROTECTION_FLAGS.PAGE_GUARD) &&
87+
!mbi.Protect.HasFlag(PAGE_PROTECTION_FLAGS.PAGE_NOCACHE))
88+
list.Add(new MemoryRegionInfo(mbi.BaseAddress, mbi.RegionSize));
89+
address += mbi.RegionSize;
90+
}
91+
92+
return list.ToArray();
93+
}
94+
95+
private MemoryRegion[] ReadRegionsCore(MemoryRegionInfo[] infoArray) {
96+
MemoryRegion[] regionArray = new MemoryRegion[infoArray.Length];
97+
for(int i = 0; i < regionArray.Length; i++) {
98+
void* baseAddress = infoArray[i].BaseAddress;
99+
nuint size = infoArray[i].Size;
100+
void* pointer = NativeMemory.Alloc(size);
101+
Kernel32.ReadProcessMemory(_processHandle, baseAddress, pointer, size);
102+
regionArray[i] = new(pointer, baseAddress, size);
103+
}
104+
return regionArray;
105+
}
106+
107+
private void FreeRegionsCore(MemoryRegion[] regionArray) {
108+
for(int i = 0; i < regionArray.Length; i++) {
109+
NativeMemory.Free(regionArray[i].Pointer);
110+
}
111+
}
112+
113+
private ulong[] ScanRegionsCore(MemoryRegion[] regionArray, ulong value) {
114+
List<ulong> list = new();
115+
116+
Parallel.For(0, regionArray.Length, i => {
117+
MemoryRegion region = regionArray[i];
118+
byte* start = (byte*)region.Pointer;
119+
byte* end = start + region.Size;
120+
if (_is32Bit) {
121+
for (byte* a = start; a < end; a += 4)
122+
if (*(uint*)a == value)
123+
lock (list) {
124+
ulong result = (ulong)region.BaseAddress + (ulong)(a - start);
125+
list.Add(result);
126+
}
127+
} else {
128+
for (byte* a = start; a < end; a += 8)
129+
if (*(ulong*)a == value)
130+
lock (list) {
131+
ulong result = (ulong)region.BaseAddress + (ulong)(a - start);
132+
list.Add(result);
133+
}
134+
}
135+
});
136+
137+
return list.ToArray();
138+
}
139+
140+
public void ScanTypes() {
141+
ScannedTypes = ScanTypesCore();
142+
}
143+
144+
public void ReadRegions() {
145+
if(Regions is not null) {
146+
FreeRegionsCore(Regions);
147+
}
148+
149+
MemoryRegionInfo[] scannedRegions = ScanRegionInfoCore();
150+
Regions = ReadRegionsCore(scannedRegions);
151+
}
152+
153+
public ulong[] ScanRegions(ulong value) {
154+
return ScanRegionsCore(Regions, value);
155+
}
156+
157+
public void Dispose() {
158+
if (Regions is not null) {
159+
FreeRegionsCore(Regions);
160+
}
161+
}
162+
}
163+
}

0 commit comments

Comments
 (0)