Я новичок в этой теме недавно научился оффсеты статичные находить. По этому без умных слов пожалуйста! Подробно объясните как находить сигнатуры, и да я буду находить через Cheat Engine, по этому желательно примеры на нем если конечно они будут.
Смотрите видео ниже, чтобы узнать, как установить наш сайт в качестве веб-приложения на домашнем экране.
Примечание: Эта возможность может быть недоступна в некоторых браузерах.
Спасибо за ответ хоть он и не самый информативный получился..Вопрос из разряда "как реверсить игры". Искать сиги (то бишь искать функции) — почти самая главная часть в реверсе. Так что это тебе в другой раздел.
Так тут не на что давать ответ — вопроса нет, по сути. Ты сформулируй, что ты конкретно хочешь искать. Просто сигнатуры — ну ищи функцию в IDA\x64dbg, да делай сигу, лол.Спасибо за ответ хоть он и не самый информативный получился..
Мне оба и функцию и данные и как с ними желательно работать в дополнении т.к у меня External и пишу на путоне.Так тут не на что давать ответ — вопроса нет, по сути. Ты сформулируй, что ты конкретно хочешь искать. Просто сигнатуры — ну ищи функцию в IDA\x64dbg, да делай сигу, лол.
Но тебе же нужно функцию нужную или данные какие-то найти. Какие? Всё по-разному ищется.
Если ты не можешь ответить на такой казалось бы простой вопрос для человека который знаком с реверсом то не надо отвечать.Сигу НА ЧТО? Нет никакого волшебного способа. Пойми, что тебе нужно и реверси, ищи. Сделать сигу — дело двух секунд. Хоть вручную по байтикам, хоть плагинами.
С функциями ты никак работать не сможешь из экстернала — ребилди себе.
И вообще, я так вижу, ты даже не понимаешь, что такое сигнатура вообще.
Ну говорю же, ты чего-то не понимаешь. Сига — это просто набор байтов. Как она поменяется при обновлении? Ну как разрабы захотят, так и будет, как код свой изменят.как понять какие места сигны меняются после обновления
Ты у нас на питоне пишешь, лол. Может просто за тебя весь чит написать?как реализовать поиск сигны в коде
Так бы сразу и без мелочи этой.Я тебе несколько раз ответил, что сигу не надо "находить", ты продолжаешь бессмысленные вопросы задавать
Ну говорю же, ты чего-то не понимаешь. Сига — это просто набор байтов. Как она поменяется при обновлении? Ну как разрабы захотят, так и будет, как код свой изменят.
Создавать можешь как тебе угодно.Пожалуйста, авторизуйтесь для просмотра ссылки.,Пожалуйста, авторизуйтесь для просмотра ссылки.. Хочешь — вообще вручную.
Вот тебе ассемблер. Красное — мусор (заменяй на ?)
48 83 EC 28 — sub rsp,28
E8 17050000 — call AyuGram.exe+4FE54A0
48 83 C4 28 — add rsp,28
E9 7AFEFFFF — jmp AyuGram.exe+4FE4E0C
Вот тебе готовая сига:
48 83 EC ? E8 ? ? ? ? 48 83 C4 ? E9
Ты у нас на питоне пишешь, лол. Может просто за тебя весь чит написать?
Доволен? Достаточно подробно?
def find_pattern(self,
pattern: str,
start_address: int = None,
end_address: int = None,
max_matches: int = 10) -> List[int]:
"""
Поиск сигнатуры в памяти процесса.
Args:
pattern: строка сигнатуры в формате "AA BB ?? DD"
start_address: начальный адрес поиска
end_address: конечный адрес поиска
max_matches: максимальное количество соответствий
Returns:
List[int]: список найденных адресов
"""
if not self.process_handle:
logger.error("Нет подключения к процессу")
return []
try:
# Преобразование строковой сигнатуры в байтовый шаблон
byte_pattern, non_wildcard_count = self.pattern_to_bytes(pattern)
pattern_length = len(byte_pattern)
if non_wildcard_count == 0:
logger.error("Сигнатура не содержит определенных байтов")
return []
# Определение диапазона адресов для поиска
if start_address is None:
# Начинаем с базового адреса процесса
proc = psutil.Process(self.process_id)
memory_maps = proc.memory_maps(grouped=False)
if not memory_maps:
logger.error("Не удалось получить карту памяти процесса")
return []
start_address = int(memory_maps[0].addr.split('-')[0], 16)
if end_address is None:
# Ограничиваем 4 ГБ для 32-бит и 16 ГБ для 64-бит
max_address = 0x400000000 if self.is_64bit else 0x100000000
end_address = min(start_address + max_address, 0x7FFFFFFFFFFFFFFF if self.is_64bit else 0xFFFFFFFF)
logger.info(f"Поиск сигнатуры '{pattern}' в диапазоне 0x{start_address:X} - 0x{end_address:X}")
# Будем читать память блоками
block_size = 4096
results = []
current_address = start_address
while current_address < end_address and len(results) < max_matches:
# Пытаемся прочитать блок памяти
buffer = ctypes.create_string_buffer(block_size)
bytes_read = ctypes.c_size_t(0)
result = ctypes.windll.kernel32.ReadProcessMemory(
self.process_handle,
ctypes.c_void_p(current_address),
buffer,
ctypes.c_size_t(block_size),
ctypes.byref(bytes_read)
)
if not result or bytes_read.value == 0:
# Не удалось прочитать блок, переходим к следующему
current_address += block_size
continue
# Проверяем совпадения в прочитанном блоке
data = buffer.raw[:bytes_read.value]
for i in range(len(data) - pattern_length + 1):
match = True
for j in range(pattern_length):
if byte_pattern[j] is not None and data[i + j] != byte_pattern[j]:
match = False
break
if match:
match_address = current_address + i
results.append(match_address)
logger.info(f"Найдено совпадение по адресу 0x{match_address:X}")
if len(results) >= max_matches:
break
current_address += block_size
logger.info(f"Поиск завершен. Найдено совпадений: {len(results)}")
return results
except ValueError as e:
logger.error(f"Ошибка в формате сигнатуры: {str(e)}")
return []
except Exception as e:
logger.error(f"Ошибка при поиске сигнатуры: {str(e)}")
return []
в чит енджине это AOB(array of bytes) скан называется, погугли на эту тему и документацию по чит енджину почитай по синтаксисуЯ новичок в этой теме недавно научился оффсеты статичные находить. По этому без умных слов пожалуйста! Подробно объясните как находить сигнатуры, и да я буду находить через Cheat Engine, по этому желательно примеры на нем если конечно они будут.
Хорошо я +- понял. Но осталась маленькая проблема - поиск в коде, в коде у меня постоянна происходит ошибка 299, запускал от имени администратора, выдавал все права PROCESS_ALL_ACCESS = 0x1F0FFF PROCESS_VM_READ = 0x0010 PROCESS_QUERY_INFORMATION = 0x0400 PROCESS_VM_OPERATION = 0x0008
Но все ровно ошибка 299
ну во-первых вопросики на конце не нужны(вопросик = ИГНОР ПРИ ПОИСКЕ. зачем игнорить конец если можно просто его не писать в сигу)Проблему решил теперь осталась проблема в уникальности, вот щас делаю поиск по сигу находиться 10 совпадений но не один не имеет верный адрес.
Сига правильная (надеюсь):Пожалуйста, авторизуйтесь для просмотра ссылки.
ниче не понял, скриншоты в студию, да и код тоже; если у тебя два разных места подходят под шаблон В ПРЕДЕЛАХ НУЖНОГО ТЕБЕ МОДУЛЯ то это да, проблема(ну или если они полностью одинаковы по интересующему тебя функционалу то поебать впринципе), надо либо на другое место сигу делать или удлинять или еще что-нибудь предпринимать; но ты мб просто не там ищешь? мб ты просто во всем процессе ищешь а не в конкретном модуле?Проверил адрес который выдает код на Memory View и они полностью идентичны но находяться в разных местах это что значит мне надо расширить сигну в верх и вниз?
CC не трогай это паддинг для выравнивания функций до 16-байтной границы(ну т.е. чтобы у функции ноль на конце в адресе был в шестнадцатиричной системе(ну или другими словами последние четыре бита в ноль стояли))(ну например если функция размером 6 байт, то у нее 10 байт CC'шек будет на конце, если она 30 размером то 2 CC будет и т.д. - если функция поменяет свой размер то количество CC изменится так что лучше их в сиги не брать это нестабильно. само CC кодирует инструкцию int3 которая ну +- крашнет кароче(если не подключен дебаггер). это сделано для того чтобы если вдруг какойто алкаш случайно начнет по какой-либо причине исполнять там инструкции в паддинге(т.е. за пределами функции) то он бы получил краш(точнее исключение) и пошел бы разбираться что не так. так-то конечно там любые байтики могли бы быть в паддинге но обычно CC компиляторы высирают чтобы крашило если это запустят случайно)И если да то что делать с пустыми CC int 3
ноп это тоже паддинг обычноИли 90 (NOP)
def pattern_to_bytes(self, pattern: str) -> Tuple[List[Optional[int]], int]:
"""
Преобразует строковую сигнатуру в список байтов с возможными wildcard (None).
Args:
pattern: строка сигнатуры в формате "AA BB ?? DD"
Returns:
Tuple[List[Optional[int]], int]: список байтов и количество определенных байтов
Raises:
ValueError: если формат сигнатуры некорректен
"""
if not pattern:
raise ValueError("Пустая сигнатура")
# Разбиваем строку на отдельные байтовые значения
pattern_parts = pattern.split()
byte_pattern = []
non_wildcard_count = 0
for part in pattern_parts:
if part == "??" or part == "**":
# Wildcard байт
byte_pattern.append(None)
else:
try:
# Преобразуем hex-строку в числовое значение
byte_value = int(part, 16)
if byte_value < 0 or byte_value > 255:
raise ValueError(f"Значение байта {part} вне диапазона 0-255")
byte_pattern.append(byte_value)
non_wildcard_count += 1
except ValueError:
raise ValueError(f"Некорректный формат байта: {part}")
return byte_pattern, non_wildcard_count
def get_memory_regions(self) -> List[Tuple[int, int]]:
"""
Получает карту памяти процесса (все доступные регионы).
Returns:
List[Tuple[int, int]]: список пар (начальный адрес, конечный адрес)
"""
regions = []
current_address = 0
# Структура для VirtualQueryEx
class MEMORY_BASIC_INFORMATION(ctypes.Structure):
_fields_ = [
("BaseAddress", ctypes.c_void_p),
("AllocationBase", ctypes.c_void_p),
("AllocationProtect", ctypes.c_ulong),
("RegionSize", ctypes.c_size_t),
("State", ctypes.c_ulong),
("Protect", ctypes.c_ulong),
("Type", ctypes.c_ulong)
]
mbi = MEMORY_BASIC_INFORMATION()
# Константы
MEM_COMMIT = 0x1000
# Цикл по всей памяти процесса
while True:
result = ctypes.windll.kernel32.VirtualQueryEx(
self.process_handle,
ctypes.c_void_p(current_address),
ctypes.byref(mbi),
ctypes.sizeof(mbi)
)
if result == 0:
break
# Проверяем, является ли регион доступным для чтения
if mbi.State == MEM_COMMIT and mbi.Protect != 0x01: # PAGE_NOACCESS
regions.append((current_address, current_address + mbi.RegionSize))
# Переходим к следующему региону
current_address = current_address + mbi.RegionSize
# Проверка на переполнение для 32-битных систем
if current_address < 0:
break
logger.info(f"Найдено {len(regions)} доступных регионов памяти")
return regions
def get_executable_memory_regions(self) -> List[Tuple[int, int]]:
"""
Получает список исполняемых регионов памяти в процессе.
Returns:
List[Tuple[int, int]]: список пар (начальный адрес, конечный адрес)
"""
regions = []
current_address = 0
# Структура для VirtualQueryEx
class MEMORY_BASIC_INFORMATION(ctypes.Structure):
_fields_ = [
("BaseAddress", ctypes.c_void_p),
("AllocationBase", ctypes.c_void_p),
("AllocationProtect", ctypes.c_ulong),
("RegionSize", ctypes.c_size_t),
("State", ctypes.c_ulong),
("Protect", ctypes.c_ulong),
("Type", ctypes.c_ulong)
]
mbi = MEMORY_BASIC_INFORMATION()
# Константы для проверки доступа
MEM_COMMIT = 0x1000
PAGE_EXECUTE = 0x10
PAGE_EXECUTE_READ = 0x20
PAGE_EXECUTE_READWRITE = 0x40
PAGE_EXECUTE_WRITECOPY = 0x80
# Цикл по всей памяти процесса
while True:
result = ctypes.windll.kernel32.VirtualQueryEx(
self.process_handle,
ctypes.c_void_p(current_address),
ctypes.byref(mbi),
ctypes.sizeof(mbi)
)
if result == 0:
break
# Проверяем, является ли регион исполняемым
if (mbi.State == MEM_COMMIT and
(mbi.Protect & (PAGE_EXECUTE | PAGE_EXECUTE_READ |
PAGE_EXECUTE_READWRITE | PAGE_EXECUTE_WRITECOPY))):
regions.append((current_address, current_address + mbi.RegionSize))
# Переходим к следующему региону
current_address = current_address + mbi.RegionSize
# Проверка на переполнение для 32-битных систем
if current_address < 0:
break
logger.info(f"Найдено {len(regions)} исполняемых регионов памяти")
return regions
def get_module_info(self, module_name: str) -> Tuple[int, int]:
"""
Получает базовый адрес и размер указанного модуля.
Args:
module_name: имя модуля (например, "game.exe" или "kernel32.dll")
Returns:
Tuple[int, int]: (базовый адрес, размер) или (None, None) если модуль не найден
"""
# Для совместимости с 32-битными и 64-битными процессами
if hasattr(ctypes.windll, 'kernel32'):
k32 = ctypes.windll.kernel32
else:
return None, None
# Структура для хранения информации о модуле
class MODULEINFO(ctypes.Structure):
_fields_ = [
("lpBaseOfDll", ctypes.c_void_p),
("SizeOfImage", ctypes.c_ulong),
("EntryPoint", ctypes.c_void_p)
]
# Получаем список модулей
hModules = (ctypes.c_void_p * 1024)()
cbNeeded = ctypes.c_ulong()
# Получаем список загруженных модулей
if not k32.K32EnumProcessModules(
self.process_handle,
ctypes.byref(hModules),
ctypes.sizeof(hModules),
ctypes.byref(cbNeeded)
):
logger.error(f"Не удалось получить список модулей, ошибка: {k32.GetLastError()}")
return None, None
# Количество полученных модулей
count = min(cbNeeded.value // ctypes.sizeof(ctypes.c_void_p), 1024)
# Ищем нужный модуль
for i in range(count):
module_handle = hModules[i]
# Получаем имя модуля
module_name_buffer = ctypes.create_string_buffer(260) # MAX_PATH
if k32.K32GetModuleBaseNameA(
self.process_handle,
module_handle,
module_name_buffer,
ctypes.sizeof(module_name_buffer)
) == 0:
continue
current_module_name = module_name_buffer.value.decode('ascii', errors='ignore').lower()
# Сравниваем с искомым именем
if current_module_name == module_name.lower():
# Получаем информацию о модуле
module_info = MODULEINFO()
if k32.K32GetModuleInformation(
self.process_handle,
module_handle,
ctypes.byref(module_info),
ctypes.sizeof(module_info)
):
return int(module_info.lpBaseOfDll), module_info.SizeOfImage
logger.warning(f"Модуль {module_name} не найден")
return None, None
def find_pattern(self,
pattern: str,
module_name: str = None,
start_address: int = None,
end_address: int = None,
max_matches: int = 10,
debug_mode: bool = False) -> List[int]:
"""
Поиск сигнатуры в памяти процесса.
Args:
pattern: строка сигнатуры в формате "AA BB ?? DD"
module_name: имя модуля для поиска (например, "game.exe")
start_address: начальный адрес поиска (если не указан модуль)
end_address: конечный адрес поиска (если не указан модуль)
max_matches: максимальное количество соответствий
debug_mode: включает дополнительный вывод для отладки
Returns:
List[int]: список найденных адресов
"""
if not self.process_handle:
logger.error("Нет подключения к процессу")
return []
try:
# Преобразование строковой сигнатуры в байтовый шаблон
byte_pattern, non_wildcard_count = self.pattern_to_bytes(pattern)
pattern_length = len(byte_pattern)
if non_wildcard_count == 0:
logger.error("Сигнатура не содержит определенных байтов")
return []
# Увеличиваем размер блока для предотвращения "разрыва" паттерна между блоками
block_size = 65536 # 64KB
# Размер перекрытия между блоками равен длине паттерна - 1
overlap = pattern_length - 1
results = []
# Определяем области памяти для сканирования
memory_regions = []
# Если указано имя модуля, ищем только в нём
if module_name:
module_base, module_size = self.get_module_info(module_name)
if not module_base or not module_size:
logger.error(f"Модуль {module_name} не найден")
return []
# По умолчанию сканируем весь модуль
memory_regions = [(module_base, module_base + module_size)]
logger.info(f"Поиск в модуле {module_name}: 0x{module_base:X} - 0x{module_base + module_size:X}")
# Если заданы конкретные адреса, используем их
elif start_address is not None and end_address is not None:
memory_regions = [(start_address, end_address)]
logger.info(f"Используем заданный диапазон: 0x{start_address:X} - 0x{end_address:X}")
# Иначе получаем все доступные регионы памяти
else:
# Используем только исполняемые регионы для повышения эффективности
memory_regions = self.get_executable_memory_regions()
if not memory_regions:
logger.error("Не удалось получить карту памяти процесса")
return []
logger.info(f"Поиск по всем исполняемым регионам памяти: {len(memory_regions)} регионов")
# Константа ERROR_PARTIAL_COPY для обработки
ERROR_PARTIAL_COPY = 299
regions_scanned = 0
for region_index, (region_start, region_end) in enumerate(memory_regions):
logger.info(f"Сканирование региона [{region_index}]: 0x{region_start:X} - 0x{region_end:X}")
current_address = region_start
while current_address < region_end and len(results) < max_matches:
# Корректируем размер блока, если мы близки к концу региона
actual_block_size = min(block_size, region_end - current_address)
if actual_block_size <= 0:
break
# Пытаемся прочитать блок памяти
buffer = ctypes.create_string_buffer(actual_block_size)
bytes_read = ctypes.c_size_t(0)
result = ctypes.windll.kernel32.ReadProcessMemory(
self.process_handle,
ctypes.c_void_p(current_address),
buffer,
ctypes.c_size_t(actual_block_size),
ctypes.byref(bytes_read)
)
error_code = 0
if not result:
error_code = ctypes.windll.kernel32.GetLastError()
# Если ошибка отличается от ERROR_PARTIAL_COPY, то пропускаем этот блок
if error_code != ERROR_PARTIAL_COPY:
logger.debug(
f"Не удалось прочитать блок по адресу 0x{current_address:X}, ошибка: {error_code}")
# Переходим к следующему блоку
current_address += actual_block_size
continue
else:
# При частичном чтении, продолжаем с тем, что прочитали
logger.debug(
f"Частичное чтение по адресу 0x{current_address:X}, прочитано {bytes_read.value} байт")
# Если прочитали 0 байт, пропускаем этот блок
if bytes_read.value == 0:
current_address += actual_block_size
continue
# Проверяем совпадения в прочитанном блоке
data = buffer.raw[:bytes_read.value]
# В режиме отладки выводим информацию о прочитанном блоке
if debug_mode:
logger.debug(f"Прочитано {bytes_read.value} байт по адресу 0x{current_address:X}")
if bytes_read.value > 0:
logger.debug(f"Первые 16 байт блока: "
f"{' '.join([f'{data[i]:02X}' for i in range(min(16, bytes_read.value))])}")
# Поиск совпадений только если размер данных достаточен
if len(data) >= pattern_length:
for i in range(len(data) - pattern_length + 1):
match = True
for j in range(pattern_length):
if byte_pattern[j] is not None and data[i + j] != byte_pattern[j]:
match = False
break
if match:
match_address = current_address + i
results.append(match_address)
# Вывод контекста найденного совпадения
context_start = max(0, i - 8)
context_end = min(len(data), i + pattern_length + 8)
context_bytes = data[context_start:context_end]
hex_context = ' '.join([f'{b:02X}' for b in context_bytes])
logger.info(f"Найдено совпадение по адресу 0x{match_address:X}")
logger.info(f"Контекст: [...{hex_context}...]")
# Визуально выделяем найденный паттерн в контексте
pattern_start_offset = i - context_start
pattern_end_offset = pattern_start_offset + pattern_length
pattern_visual = ' '.join(
['^^' if (context_start + k >= i and context_start + k < i + pattern_length)
else ' ' for k in range(len(context_bytes))])
logger.info(f"Позиция: [...{pattern_visual}...]")
if len(results) >= max_matches:
break
# Адаптивное смещение указателя
if error_code == ERROR_PARTIAL_COPY:
# Если было частичное чтение, смещаемся на количество прочитанных байт
current_address += bytes_read.value
else:
# Иначе используем стандартную логику сдвига с перекрытием
current_address += (
actual_block_size - overlap) if actual_block_size > overlap else actual_block_size
regions_scanned += 1
# Периодически сообщаем о прогрессе
if regions_scanned % 10 == 0:
logger.info(f"Просканировано {regions_scanned}/{len(memory_regions)} регионов...")
logger.info(f"Поиск завершен. Найдено совпадений: {len(results)}")
return results
except ValueError as e:
logger.error(f"Ошибка в формате сигнатуры: {str(e)}")
return []
except Exception as e:
logger.error(f"Ошибка при поиске сигнатуры: {str(e)}")
import traceback
logger.error(traceback.format_exc())
return []
Проект предоставляет различный материал, относящийся к сфере киберспорта, программирования, ПО для игр, а также позволяет его участникам общаться на многие другие темы. Почта для жалоб: admin@yougame.biz