-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathProgram.cs
More file actions
1306 lines (1118 loc) · 58.5 KB
/
Program.cs
File metadata and controls
1306 lines (1118 loc) · 58.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
namespace MemoryValueScanner
{
class Program
{
[DllImport("kernel32.dll")]
static extern IntPtr OpenProcess(int dwDesiredAccess, bool bInheritHandle, int dwProcessId);
[DllImport("kernel32.dll")]
static extern bool ReadProcessMemory(IntPtr hProcess, IntPtr lpBaseAddress,
byte[] lpBuffer, int dwSize, out int lpNumberOfBytesRead);
[DllImport("kernel32.dll")]
static extern int VirtualQueryEx(IntPtr hProcess, IntPtr lpAddress,
out MEMORY_BASIC_INFORMATION lpBuffer, uint dwLength);
const int PROCESS_VM_READ = 0x0010;
const int PROCESS_QUERY_INFORMATION = 0x0400;
[StructLayout(LayoutKind.Sequential)]
public struct MEMORY_BASIC_INFORMATION
{
public IntPtr BaseAddress;
public IntPtr AllocationBase;
public uint AllocationProtect;
public IntPtr RegionSize;
public uint State;
public uint Protect;
public uint Type;
}
const uint MEM_COMMIT = 0x1000;
const uint PAGE_READWRITE = 0x04;
const uint PAGE_READONLY = 0x02;
public class MemoryRegion
{
public string Name { get; set; } = string.Empty;
public IntPtr BaseAddress { get; set; }
public IntPtr Size { get; set; }
}
public class SearchValue
{
public string Type { get; set; } = string.Empty;
public string Value { get; set; } = string.Empty;
}
static void Main(string[] args)
{
Console.Write("Escolha o modo (buscar/mostrar/pointer): ");
string mode = Console.ReadLine()?.Trim().ToLower() ?? "";
Console.Write("Digite o nome do processo (sem .exe): ");
string processName = Console.ReadLine() ?? "";
var processes = Process.GetProcessesByName(processName);
if (processes.Length == 0)
{
Console.WriteLine($"Processo '{processName}' não encontrado.");
Console.WriteLine("Dica: Certifique-se de que o processo está rodando e digite apenas o nome (sem .exe)");
return;
}
Process proc = processes[0];
IntPtr baseAddress;
int moduleSize;
try
{
baseAddress = proc.MainModule?.BaseAddress ?? IntPtr.Zero;
moduleSize = proc.MainModule?.ModuleMemorySize ?? 0;
Console.WriteLine($"Processo encontrado: {proc.ProcessName} (PID: {proc.Id})");
}
catch (Exception ex)
{
Console.WriteLine($"Erro ao acessar informações do processo: {ex.Message}");
Console.WriteLine("Soluções:");
Console.WriteLine("1. Execute este programa como Administrador");
Console.WriteLine("2. Certifique-se de que o processo não está protegido");
Console.WriteLine("3. Desative temporariamente o antivírus se necessário");
return;
}
if (baseAddress == IntPtr.Zero || moduleSize == 0)
{
Console.WriteLine("Não foi possível obter informações do módulo principal.");
return;
}
Console.WriteLine($"Endereço base do módulo: 0x{baseAddress.ToString("X")}");
Console.WriteLine($"Tamanho do módulo: {FormatBytes(moduleSize)}");
IntPtr hProcess = OpenProcess(PROCESS_VM_READ | PROCESS_QUERY_INFORMATION, false, proc.Id);
if (hProcess == IntPtr.Zero)
{
Console.WriteLine("Falha ao abrir o processo para leitura de memória.");
Console.WriteLine("Soluções:");
Console.WriteLine("1. Execute este programa como Administrador");
Console.WriteLine("2. Certifique-se de que o processo não está protegido por antivírus");
Console.WriteLine("3. Tente com um processo diferente para testar");
return;
}
if (mode == "mostrar")
{
// Coleta valores para highlight
var searchValues = CollectSearchValues();
if (searchValues.Count > 0)
{
Console.WriteLine($"\n>>> Destacando {searchValues.Count} valor(es) com 'X' na primeira coluna <<<");
foreach (var sv in searchValues)
{
Console.WriteLine($" - {sv.Type}: '{sv.Value}'");
}
}
else
{
Console.WriteLine("\n>>> Nenhum valor será destacado <<<");
}
Console.WriteLine("\n=== CONFIGURAÇÃO DO ENDEREÇO INICIAL ===");
Console.WriteLine("1. Usar endereço base do módulo principal");
Console.WriteLine("2. Especificar endereço inicial personalizado");
Console.Write("Escolha uma opção (1-2): ");
string addressChoice = Console.ReadLine()?.Trim() ?? "1";
IntPtr startAddr = baseAddress; // Padrão: endereço base do módulo principal
if (addressChoice == "2")
{
Console.Write("Digite o endereço inicial (hex, ex: 0x01138DDC): ");
string addrInput = Console.ReadLine()?.Trim() ?? "";
if (!addrInput.StartsWith("0x")) addrInput = "0x" + addrInput;
try
{
startAddr = (IntPtr)Convert.ToInt64(addrInput, 16);
Console.WriteLine($"Endereço inicial definido: 0x{startAddr.ToString("X")}");
}
catch (Exception ex)
{
Console.WriteLine($"Erro ao processar endereço: {ex.Message}");
Console.WriteLine("Usando endereço base do módulo principal.");
startAddr = baseAddress;
}
}
else
{
Console.WriteLine($"Usando endereço base do módulo principal: 0x{baseAddress.ToString("X")}");
}
Console.Write("Digite o tamanho do bloco (número): ");
long blockNumber = long.Parse(Console.ReadLine() ?? "1024");
Console.Write("Digite a unidade (bytes/kb/mb): ");
string unit = (Console.ReadLine() ?? "bytes").ToLower().Trim();
long blockSize = unit switch
{
"bytes" or "b" => blockNumber,
"kb" or "k" => blockNumber * 1024,
"mb" or "m" => blockNumber * 1024 * 1024,
_ => blockNumber
};
Console.WriteLine($"Tamanho do bloco: {FormatBytes(blockSize)}");
// Usa sempre o módulo principal como área de busca
long totalSectionSize = moduleSize;
IntPtr sectionEndAddr = (IntPtr)(baseAddress.ToInt64() + totalSectionSize);
Console.WriteLine($"\n=== ÁREA DE BUSCA ===");
Console.WriteLine($"Endereço inicial: 0x{startAddr.ToString("X")}");
Console.WriteLine($"Endereço final do módulo: 0x{sectionEndAddr.ToString("X")}");
Console.WriteLine($"Tamanho do módulo: {FormatBytes(totalSectionSize)}");
// Ajusta o tamanho se o endereço inicial for diferente do base
if (startAddr.ToInt64() != baseAddress.ToInt64())
{
long offsetFromBase = startAddr.ToInt64() - baseAddress.ToInt64();
if (offsetFromBase > 0 && offsetFromBase < moduleSize)
{
totalSectionSize = moduleSize - offsetFromBase;
Console.WriteLine($"Área efetiva de busca: {FormatBytes(totalSectionSize)} (restante do módulo)");
}
else if (offsetFromBase >= moduleSize)
{
Console.WriteLine("Aviso: Endereço inicial está fora do módulo principal!");
Console.Write("Digite o tamanho da área para escanear (em bytes, ex: 1048576): ");
totalSectionSize = long.Parse(Console.ReadLine() ?? "1048576");
}
}
// Navegação por blocos
IntPtr currentBlockStart = startAddr;
long processedBytes = 0;
while (processedBytes < totalSectionSize)
{
long remainingBytes = totalSectionSize - processedBytes;
long currentBlockSize = Math.Min(blockSize, remainingBytes);
IntPtr currentBlockEnd = (IntPtr)(currentBlockStart.ToInt64() + currentBlockSize);
Console.WriteLine($"\n=== BLOCO ATUAL ===");
Console.WriteLine($"Endereço inicial do bloco: 0x{currentBlockStart.ToString("X")}");
Console.WriteLine($"Endereço final do bloco: 0x{currentBlockEnd.ToString("X")}");
Console.WriteLine($"Tamanho do bloco: {FormatBytes(currentBlockSize)}");
Console.WriteLine($"Progresso: {FormatBytes(processedBytes)} / {FormatBytes(totalSectionSize)} " +
$"({(processedBytes * 100.0 / totalSectionSize):F1}%)");
Console.WriteLine("\nH | Endereço | byte | int16 | int32 | string(16)");
Console.WriteLine(new string('-', 65));
for (long i = 0; i < currentBlockSize; i++)
{
IntPtr addr = currentBlockStart + (int)i;
byte b = ReadByte(hProcess, addr);
short s = ReadInt16(hProcess, addr);
int n = ReadInt32(hProcess, addr);
string str = ReadString(hProcess, addr, 16);
// Só mostra se pelo menos um dos valores for diferente de zero
if (b != 0 || s != 0 || n != 0 || !string.IsNullOrEmpty(str.Trim()))
{
// Verifica se deve ser destacado
bool highlighted = IsHighlighted(hProcess, addr, searchValues);
string highlightMark = highlighted ? "X" : " ";
Console.WriteLine($"{highlightMark} | 0x{addr.ToString("X"),-12} | {b,4} | {s,5} | {n,8} | {str,-16}");
}
}
processedBytes += currentBlockSize;
currentBlockStart = currentBlockEnd;
// Verifica se ainda há mais blocos
if (processedBytes < totalSectionSize)
{
Console.WriteLine($"\nAinda restam {FormatBytes(totalSectionSize - processedBytes)} para escanear.");
Console.Write("Deseja continuar para o próximo bloco? (s/n): ");
string continueChoice = (Console.ReadLine() ?? "n").ToLower().Trim();
if (continueChoice != "s" && continueChoice != "sim" && continueChoice != "y" && continueChoice != "yes")
{
Console.WriteLine("Escaneamento interrompido pelo usuário.");
break;
}
}
else
{
Console.WriteLine("\n=== ESCANEAMENTO COMPLETO ===");
Console.WriteLine("Toda a seção foi escaneada!");
}
}
return;
}
// --- MODO POINTER SCAN ---
if (mode == "pointer")
{
PerformPointerScan(hProcess, proc, baseAddress, moduleSize);
return;
}
// --- MODO BUSCAR (original) ---
Console.Write("Digite o tipo de dado (byte, int16, int32, string): ");
string dataType = (Console.ReadLine() ?? "").ToLower();
Console.Write("Digite o(s) valor(es) esperado(s), separados por vírgula: ");
string expectedValuesInput = Console.ReadLine() ?? "";
string[] expectedValues = expectedValuesInput
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
int dataSize = GetDataSize(dataType, expectedValues);
if (dataSize == 0)
{
Console.WriteLine("Tipo de dado inválido.");
return;
}
// Configuração do endereço inicial da busca
Console.WriteLine("\n=== CONFIGURAÇÃO DA ÁREA DE BUSCA ===");
Console.WriteLine("1. Buscar em todo o módulo principal");
Console.WriteLine("2. Especificar endereço inicial personalizado");
Console.Write("Escolha uma opção (1-2): ");
string searchChoice = Console.ReadLine()?.Trim() ?? "1";
IntPtr searchStartAddress = baseAddress;
int searchSize = moduleSize;
if (searchChoice == "2")
{
Console.Write("Digite o endereço inicial (hex, ex: 0x01138DDC): ");
string startAddrInput = Console.ReadLine()?.Trim() ?? "";
if (!startAddrInput.StartsWith("0x")) startAddrInput = "0x" + startAddrInput;
try
{
searchStartAddress = (IntPtr)Convert.ToInt64(startAddrInput, 16);
Console.WriteLine($"Endereço inicial definido: 0x{searchStartAddress.ToString("X")}");
Console.Write("Digite o tamanho da área para buscar (em bytes, ex: 1048576 para 1MB): ");
string sizeInput = Console.ReadLine()?.Trim() ?? "";
if (int.TryParse(sizeInput, out int customSize))
{
searchSize = customSize;
}
else
{
Console.WriteLine("Tamanho inválido, usando tamanho padrão restante do módulo.");
searchSize = (int)Math.Max(0, (baseAddress.ToInt64() + moduleSize) - searchStartAddress.ToInt64());
}
}
catch (Exception ex)
{
Console.WriteLine($"Erro ao processar endereço: {ex.Message}");
Console.WriteLine("Usando configuração padrão do módulo principal.");
searchStartAddress = baseAddress;
searchSize = moduleSize;
}
}
Console.WriteLine($"Área de busca: 0x{searchStartAddress.ToString("X")} até 0x{(searchStartAddress.ToInt64() + searchSize).ToString("X")}");
Console.WriteLine($"Tamanho da área: {FormatBytes(searchSize)}");
Console.WriteLine("Iniciando varredura...");
Dictionary<string, List<IntPtr>> valueToAddresses = new Dictionary<string, List<IntPtr>>();
for (int offset = 0; offset <= searchSize - dataSize; offset++)
{
IntPtr currentAddress = searchStartAddress + offset;
string? matchedValue = null;
switch (dataType)
{
case "byte":
byte val8 = ReadByte(hProcess, currentAddress);
matchedValue = expectedValues.FirstOrDefault(v => v == val8.ToString());
break;
case "int16":
short val16 = ReadInt16(hProcess, currentAddress);
matchedValue = expectedValues.FirstOrDefault(v => v == val16.ToString());
break;
case "int32":
int val32 = ReadInt32(hProcess, currentAddress);
matchedValue = expectedValues.FirstOrDefault(v => v == val32.ToString());
break;
case "string":
string str = ReadString(hProcess, currentAddress, expectedValues.Max(s => s.Length));
matchedValue = expectedValues.FirstOrDefault(v => v == str);
break;
}
if (matchedValue != null)
{
if (!valueToAddresses.ContainsKey(matchedValue))
valueToAddresses[matchedValue] = new List<IntPtr>();
valueToAddresses[matchedValue].Add(currentAddress);
}
if (offset % 100000 == 0)
{
Console.SetCursorPosition(0, Console.CursorTop);
Console.Write($"Progresso: {FormatBytes(offset)} de {FormatBytes(searchSize)}");
Console.Write($" | Endereço atual: 0x{currentAddress.ToString("X")}");
Console.Write($" | Valores encontrados: {valueToAddresses.Sum(kv => kv.Value.Count)}");
Console.Write($" | Valores únicos: {valueToAddresses.Count}");
Console.WriteLine();
}
}
Console.WriteLine($"\nVarredura finalizada. Total de valores encontrados: {valueToAddresses.Sum(kv => kv.Value.Count)}");
Console.WriteLine("\nResumo dos valores encontrados:");
foreach (var kvp in valueToAddresses)
{
string value = kvp.Key;
int count = kvp.Value.Count;
Console.WriteLine($"Valor '{value}': {count} endereço(s) encontrado(s)");
}
// cria a pasta output se não existir
if (!System.IO.Directory.Exists("output"))
{
System.IO.Directory.CreateDirectory("output");
}
// salva os endereços encontrados em um arquivo, o nome do arquivo é o nome do processo_valor.txt
foreach (var kvp in valueToAddresses)
{
string value = kvp.Key;
var addresses = kvp.Value;
string fileName = $"output/{processName}_{value}.txt";
using (var writer = new System.IO.StreamWriter(fileName))
{
foreach (var addr in addresses)
{
writer.WriteLine($"0x{addr.ToString("X")}");
}
}
Console.WriteLine($"Endereços para o valor '{value}' salvos em: {fileName}");
}
const int proximityThreshold = 4;
var keys = valueToAddresses.Keys.ToList();
// imprime os 10 primeiros endereços encontrados de valor
Console.WriteLine("\nEndereços encontrados para os primeiros 10 valores:");
foreach (var kvp in valueToAddresses.Take(10))
{
string value = kvp.Key;
var addresses = kvp.Value;
Console.WriteLine($"\nValor '{value}':");
for (int i = 0; i < Math.Min(10, addresses.Count); i++)
{
Console.WriteLine($" - 0x{addresses[i].ToString("X")}");
}
}
Console.WriteLine("\nAnalisando proximidade entre valores diferentes:");
for (int i = 0; i < keys.Count - 1; i++)
{
string val1 = keys[i];
for (int j = i + 1; j < keys.Count; j++)
{
string val2 = keys[j];
bool headerShown = false;
foreach (var addr1 in valueToAddresses[val1])
{
foreach (var addr2 in valueToAddresses[val2])
{
long distance = Math.Abs(addr1.ToInt64() - addr2.ToInt64());
if (distance <= proximityThreshold)
{
if (!headerShown)
{
Console.WriteLine($"\nValor '{val1}' em relação ao valor '{val2}':");
headerShown = true;
}
Console.WriteLine($" - 0x{addr1.ToString("X")} está a {distance} bytes de 0x{addr2.ToString("X")}");
}
}
}
}
}
// Nova tabela detalhada dos endereços encontrados
DisplayDetailedAddressTable(hProcess, valueToAddresses);
}
static void DisplayDetailedAddressTable(IntPtr hProcess, Dictionary<string, List<IntPtr>> valueToAddresses)
{
Console.WriteLine("\n=== TABELA DETALHADA DOS ENDEREÇOS ENCONTRADOS ===");
Console.WriteLine("Mostrando os primeiros 128 bytes a partir de cada endereço encontrado:\n");
// Coleta todos os endereços únicos de todos os valores
var allAddresses = new HashSet<IntPtr>();
foreach (var kvp in valueToAddresses)
{
foreach (var addr in kvp.Value)
{
allAddresses.Add(addr);
}
}
// Ordena os endereços
var sortedAddresses = allAddresses.OrderBy(addr => addr.ToInt64()).ToList();
Console.WriteLine("Endereço | Offset | byte | int16 | int32 | float | string(16) | Valor Original");
Console.WriteLine(new string('-', 100));
foreach (var baseAddr in sortedAddresses.Take(20)) // Limita a 20 endereços para não sobrecarregar
{
// Encontra qual valor original foi encontrado neste endereço
string originalValue = "";
foreach (var kvp in valueToAddresses)
{
if (kvp.Value.Contains(baseAddr))
{
originalValue += $"{kvp.Key} ";
}
}
// Mostra dados dos primeiros 128 bytes a partir do endereço
for (int offset = 0; offset < 128; offset += 4) // Incrementa de 4 em 4 para não ficar muito verboso
{
IntPtr currentAddr = baseAddr + offset;
try
{
byte byteVal = ReadByte(hProcess, currentAddr);
short int16Val = ReadInt16(hProcess, currentAddr);
int int32Val = ReadInt32(hProcess, currentAddr);
float floatVal = ReadFloat(hProcess, currentAddr);
string stringVal = ReadString(hProcess, currentAddr, 16);
// Só mostra se pelo menos um valor não for zero
if (byteVal != 0 || int16Val != 0 || int32Val != 0 || !string.IsNullOrEmpty(stringVal.Trim()))
{
string originalValueColumn = offset == 0 ? originalValue : "";
Console.WriteLine($"0x{currentAddr.ToString("X"),-12} | +{offset,3} | {byteVal,4} | {int16Val,5} | {int32Val,8} | {floatVal,8:F2} | {stringVal,-15} | {originalValueColumn}");
}
}
catch
{
// Ignora erros de leitura de memória
continue;
}
}
// Linha separadora entre diferentes endereços base
if (baseAddr != sortedAddresses.Last())
{
Console.WriteLine(new string('-', 100));
}
}
if (sortedAddresses.Count > 20)
{
Console.WriteLine($"\n... e mais {sortedAddresses.Count - 20} endereços. Mostrando apenas os primeiros 20 para legibilidade.");
}
}
static int GetDataSize(string dataType, string[] expectedValues)
{
return dataType switch
{
"byte" => 1,
"int16" => 2,
"int32" => 4,
"string" => expectedValues.Max(s => s.Length),
_ => 0,
};
}
static string FormatBytes(long bytes)
{
const long KB = 1024;
const long MB = KB * 1024;
if (bytes > MB)
return $"{(bytes / (double)MB):F2} MB";
else if (bytes > KB)
return $"{(bytes / (double)KB):F2} KB";
else
return $"{bytes} bytes";
}
static byte ReadByte(IntPtr processHandle, IntPtr address)
{
byte[] buffer = new byte[1];
ReadProcessMemory(processHandle, address, buffer, buffer.Length, out int _);
return buffer[0];
}
static short ReadInt16(IntPtr processHandle, IntPtr address)
{
byte[] buffer = new byte[2];
ReadProcessMemory(processHandle, address, buffer, buffer.Length, out int _);
return BitConverter.ToInt16(buffer, 0);
}
static int ReadInt32(IntPtr processHandle, IntPtr address)
{
byte[] buffer = new byte[4];
ReadProcessMemory(processHandle, address, buffer, buffer.Length, out int _);
return BitConverter.ToInt32(buffer, 0);
}
static float ReadFloat(IntPtr processHandle, IntPtr address)
{
byte[] buffer = new byte[4];
ReadProcessMemory(processHandle, address, buffer, buffer.Length, out int _);
return BitConverter.ToSingle(buffer, 0);
}
static string ReadString(IntPtr processHandle, IntPtr address, int length)
{
byte[] buffer = new byte[length];
ReadProcessMemory(processHandle, address, buffer, buffer.Length, out int _);
return Encoding.ASCII.GetString(buffer).TrimEnd('\0');
}
static List<MemoryRegion> GetMemoryRegions(IntPtr hProcess, Process proc)
{
var regions = new List<MemoryRegion>();
// Adiciona o módulo principal (seção .text, .data, etc.)
if (proc.MainModule != null)
{
regions.Add(new MemoryRegion
{
Name = "Módulo Principal (.exe)",
BaseAddress = proc.MainModule.BaseAddress,
Size = (IntPtr)proc.MainModule.ModuleMemorySize
});
}
// Adiciona outros módulos carregados (DLLs)
foreach (ProcessModule module in proc.Modules)
{
if (proc.MainModule != null && module.BaseAddress != proc.MainModule.BaseAddress)
{
regions.Add(new MemoryRegion
{
Name = $"DLL: {module.ModuleName}",
BaseAddress = module.BaseAddress,
Size = (IntPtr)module.ModuleMemorySize
});
}
}
// Escaneia a memória para encontrar regiões de dados (heap)
IntPtr address = IntPtr.Zero;
MEMORY_BASIC_INFORMATION mbi;
while (VirtualQueryEx(hProcess, address, out mbi, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION))) != 0)
{
// Procura por regiões de dados gravável (heap, stack)
if (mbi.State == MEM_COMMIT &&
(mbi.Protect == PAGE_READWRITE || mbi.Protect == PAGE_READONLY) &&
mbi.RegionSize.ToInt64() > 1024) // Apenas regiões maiores que 1KB
{
bool isAlreadyAdded = regions.Any(r =>
mbi.BaseAddress.ToInt64() >= r.BaseAddress.ToInt64() &&
mbi.BaseAddress.ToInt64() < r.BaseAddress.ToInt64() + r.Size.ToInt64());
if (!isAlreadyAdded)
{
string regionName = "Região de Dados";
if (mbi.Protect == PAGE_READWRITE)
regionName = "Heap/Stack (R/W)";
else if (mbi.Protect == PAGE_READONLY)
regionName = "Dados Somente Leitura";
regions.Add(new MemoryRegion
{
Name = regionName,
BaseAddress = mbi.BaseAddress,
Size = mbi.RegionSize
});
}
}
long nextAddress = mbi.BaseAddress.ToInt64() + mbi.RegionSize.ToInt64();
if (nextAddress <= address.ToInt64())
break;
address = (IntPtr)nextAddress;
}
return regions.OrderBy(r => r.BaseAddress.ToInt64()).ToList();
}
static List<SearchValue> CollectSearchValues()
{
var searchValues = new List<SearchValue>();
Console.WriteLine("\n=== CONFIGURAR VALORES PARA HIGHLIGHT ===");
Console.WriteLine("Digite os valores que deseja destacar na visualização.");
while (true)
{
Console.WriteLine("\nMenu de Valores para Highlight:");
Console.WriteLine("1. Adicionar valor byte (0-255)");
Console.WriteLine("2. Adicionar valor int16 (-32768 a 32767)");
Console.WriteLine("3. Adicionar valor int32");
Console.WriteLine("4. Adicionar string");
Console.WriteLine("5. Listar valores adicionados");
Console.WriteLine("6. Remover último valor");
Console.WriteLine("7. Continuar com a visualização");
Console.Write("Escolha uma opção (1-7): ");
string choice = Console.ReadLine()?.Trim() ?? "";
switch (choice)
{
case "1":
Console.Write("Digite o valor byte (0-255): ");
string byteValue = Console.ReadLine()?.Trim() ?? "";
if (byte.TryParse(byteValue, out byte b) && b >= 0 && b <= 255)
{
searchValues.Add(new SearchValue { Type = "byte", Value = byteValue });
Console.WriteLine($"Valor byte '{byteValue}' adicionado!");
}
else
{
Console.WriteLine("Valor inválido para byte. Deve ser entre 0 e 255.");
}
break;
case "2":
Console.Write("Digite o valor int16 (-32768 a 32767): ");
string int16Value = Console.ReadLine()?.Trim() ?? "";
if (short.TryParse(int16Value, out short s))
{
searchValues.Add(new SearchValue { Type = "int16", Value = int16Value });
Console.WriteLine($"Valor int16 '{int16Value}' adicionado!");
}
else
{
Console.WriteLine("Valor inválido para int16.");
}
break;
case "3":
Console.Write("Digite o valor int32: ");
string int32Value = Console.ReadLine()?.Trim() ?? "";
if (int.TryParse(int32Value, out int i))
{
searchValues.Add(new SearchValue { Type = "int32", Value = int32Value });
Console.WriteLine($"Valor int32 '{int32Value}' adicionado!");
}
else
{
Console.WriteLine("Valor inválido para int32.");
}
break;
case "4":
Console.Write("Digite a string: ");
string stringValue = Console.ReadLine() ?? "";
if (!string.IsNullOrEmpty(stringValue))
{
searchValues.Add(new SearchValue { Type = "string", Value = stringValue });
Console.WriteLine($"String '{stringValue}' adicionada!");
}
else
{
Console.WriteLine("String não pode estar vazia.");
}
break;
case "5":
Console.WriteLine("\nValores adicionados para highlight:");
if (searchValues.Count == 0)
{
Console.WriteLine("Nenhum valor adicionado ainda.");
}
else
{
for (int j = 0; j < searchValues.Count; j++)
{
Console.WriteLine($"{j + 1}. {searchValues[j].Type}: '{searchValues[j].Value}'");
}
}
break;
case "6":
if (searchValues.Count > 0)
{
var removed = searchValues[searchValues.Count - 1];
searchValues.RemoveAt(searchValues.Count - 1);
Console.WriteLine($"Removido: {removed.Type} '{removed.Value}'");
}
else
{
Console.WriteLine("Nenhum valor para remover.");
}
break;
case "7":
Console.WriteLine($"Continuando com {searchValues.Count} valor(es) para highlight.");
return searchValues;
default:
Console.WriteLine("Opção inválida. Tente novamente.");
break;
}
}
}
static bool IsHighlighted(IntPtr hProcess, IntPtr address, List<SearchValue> searchValues)
{
foreach (var searchValue in searchValues)
{
switch (searchValue.Type)
{
case "byte":
byte byteVal = ReadByte(hProcess, address);
if (byteVal.ToString() == searchValue.Value)
return true;
break;
case "int16":
short int16Val = ReadInt16(hProcess, address);
if (int16Val.ToString() == searchValue.Value)
return true;
break;
case "int32":
int int32Val = ReadInt32(hProcess, address);
if (int32Val.ToString() == searchValue.Value)
return true;
break;
case "string":
string stringVal = ReadString(hProcess, address, searchValue.Value.Length);
if (stringVal == searchValue.Value)
return true;
break;
}
}
return false;
}
static void PerformPointerScan(IntPtr hProcess, Process proc, IntPtr baseAddress, int moduleSize)
{
Console.WriteLine("\n=== POINTER SCAN AVANÇADO PARA INVENTÁRIO ===");
Console.WriteLine("Este modo usa scanning diferencial para encontrar endereços precisos.");
Console.WriteLine("Primeiro vamos fazer dois scans para isolar o endereço correto!\n");
Console.Write("Digite o tipo de dado do item (int32 é comum para quantidades): ");
string dataType = (Console.ReadLine() ?? "int32").ToLower();
// SCAN 1: Valor inicial
Console.WriteLine("\n=== SCAN 1: VALOR INICIAL ===");
Console.Write("Digite o valor ATUAL do item no jogo: ");
string initialValue = Console.ReadLine() ?? "";
Console.WriteLine($"Fazendo primeiro scan para o valor '{initialValue}'...");
var initialAddresses = FindValueAddresses(hProcess, baseAddress, moduleSize, dataType, initialValue);
if (initialAddresses.Count == 0)
{
Console.WriteLine("Nenhum endereço encontrado para o valor inicial.");
Console.WriteLine("Dicas:");
Console.WriteLine("- Certifique-se de que digitou o valor correto");
Console.WriteLine("- Tente com tipo de dado diferente (byte, int16, float)");
return;
}
Console.WriteLine($"✅ Primeiro scan: {initialAddresses.Count} endereços encontrados");
// SCAN 2: Valor alterado
Console.WriteLine("\n=== SCAN 2: VALOR ALTERADO ===");
Console.WriteLine("Agora vá no jogo e ALTERE a quantidade do item!");
Console.WriteLine("Por exemplo:");
Console.WriteLine("- Use/consuma alguns itens");
Console.WriteLine("- Compre/venda itens");
Console.WriteLine("- Mova itens no inventário");
Console.WriteLine("\nApós alterar o valor no jogo, pressione ENTER para continuar...");
Console.ReadLine();
Console.Write("Digite o NOVO valor do item após a alteração: ");
string newValue = Console.ReadLine() ?? "";
Console.WriteLine($"Fazendo segundo scan para o valor '{newValue}'...");
var newAddresses = FindValueAddresses(hProcess, baseAddress, moduleSize, dataType, newValue);
if (newAddresses.Count == 0)
{
Console.WriteLine("Nenhum endereço encontrado para o novo valor.");
Console.WriteLine("O valor pode ter mudado novamente ou o tipo de dado pode estar incorreto.");
return;
}
Console.WriteLine($"✅ Segundo scan: {newAddresses.Count} endereços encontrados");
// ANÁLISE DIFERENCIAL
Console.WriteLine("\n=== ANÁLISE DIFERENCIAL ===");
Console.WriteLine("Comparando os dois scans para encontrar endereços que mudaram...");
var matchingAddresses = FindMatchingChangedAddresses(hProcess, initialAddresses, newAddresses, dataType, initialValue, newValue);
if (matchingAddresses.Count == 0)
{
Console.WriteLine("❌ Nenhum endereço corresponde exatamente às mudanças esperadas.");
Console.WriteLine("Possíveis causas:");
Console.WriteLine("- O valor mudou novamente durante o scan");
Console.WriteLine("- O tipo de dado está incorreto");
Console.WriteLine("- O item está armazenado de forma diferente");
// Oferece scan de proximidade como alternativa
Console.WriteLine("\n=== ANÁLISE DE PROXIMIDADE (ALTERNATIVA) ===");
var nearbyAddresses = FindNearbyAddresses(initialAddresses, newAddresses, 32);
if (nearbyAddresses.Count > 0)
{
Console.WriteLine($"Encontrados {nearbyAddresses.Count} pares de endereços próximos:");
for (int i = 0; i < Math.Min(10, nearbyAddresses.Count); i++)
{
var (oldAddr, newAddr) = nearbyAddresses[i];
long distance = Math.Abs(oldAddr.ToInt64() - newAddr.ToInt64());
Console.WriteLine($" {i + 1}. 0x{oldAddr:X} → 0x{newAddr:X} (distância: {distance} bytes)");
}
matchingAddresses = nearbyAddresses.Select(pair => pair.Item2).ToList();
}
else
{
return;
}
}
else
{
Console.WriteLine($"🎯 SUCESSO! Encontrados {matchingAddresses.Count} endereços que mudaram corretamente:");
for (int i = 0; i < Math.Min(10, matchingAddresses.Count); i++)
{
Console.WriteLine($" {i + 1}. 0x{matchingAddresses[i].ToString("X")} (era '{initialValue}', agora '{newValue}')");
}
}
// FASE FINAL: POINTER SCAN NOS ENDEREÇOS FILTRADOS
Console.WriteLine("\n=== FASE 3: POINTER SCAN DOS ENDEREÇOS FILTRADOS ===");
Console.Write("Profundidade máxima do scan (1-5, recomendado 3): ");
int maxDepth = int.TryParse(Console.ReadLine(), out int depth) ? Math.Min(depth, 5) : 3;
Console.Write("Offset máximo para buscar ponteiros (hex, ex: 1000): ");
string offsetInput = Console.ReadLine() ?? "1000";
if (!offsetInput.StartsWith("0x")) offsetInput = "0x" + offsetInput;
long maxOffset = Convert.ToInt64(offsetInput, 16);
var pointerChains = new List<PointerChain>();
Console.WriteLine($"\nRealizando pointer scan nos {Math.Min(5, matchingAddresses.Count)} melhores endereços...");
foreach (var targetAddr in matchingAddresses.Take(5))
{
Console.WriteLine($"Buscando ponteiros para 0x{targetAddr.ToString("X")}...");
var chains = FindPointerChains(hProcess, baseAddress, moduleSize, targetAddr, maxDepth, maxOffset);
pointerChains.AddRange(chains);
}
DisplayPointerResults(pointerChains, proc, newValue, dataType, maxDepth, maxOffset);
}
static List<IntPtr> FindMatchingChangedAddresses(IntPtr hProcess, List<IntPtr> oldAddresses, List<IntPtr> newAddresses, string dataType, string oldValue, string newValue)
{
var matchingAddresses = new List<IntPtr>();
// Para cada endereço do primeiro scan, verifica se o valor realmente mudou para o esperado
foreach (var oldAddr in oldAddresses)
{
try
{
// Lê o valor atual no endereço do primeiro scan
string currentValue = ReadCurrentValue(hProcess, oldAddr, dataType);
// Se o valor mudou para o valor esperado, este é um candidato válido
if (currentValue == newValue)
{
matchingAddresses.Add(oldAddr);
}
}
catch
{
// Ignora erros de leitura
}
}
return matchingAddresses;
}
static List<(IntPtr, IntPtr)> FindNearbyAddresses(List<IntPtr> oldAddresses, List<IntPtr> newAddresses, int maxDistance)
{
var nearbyPairs = new List<(IntPtr, IntPtr)>();
foreach (var oldAddr in oldAddresses)
{
foreach (var newAddr in newAddresses)
{
long distance = Math.Abs(oldAddr.ToInt64() - newAddr.ToInt64());
if (distance <= maxDistance)
{
nearbyPairs.Add((oldAddr, newAddr));
}
}
}
return nearbyPairs.OrderBy(pair => Math.Abs(pair.Item1.ToInt64() - pair.Item2.ToInt64())).ToList();
}
static string ReadCurrentValue(IntPtr hProcess, IntPtr address, string dataType)
{
switch (dataType.ToLower())
{
case "byte":
return ReadByte(hProcess, address).ToString();
case "int16":
return ReadInt16(hProcess, address).ToString();
case "int32":
return ReadInt32(hProcess, address).ToString();
case "float":