Вопрос Грамотное нахождение сигнатур

Начинающий
Начинающий
Статус
Оффлайн
Регистрация
12 Мар 2025
Сообщения
86
Реакции
8
Я новичок в этой теме недавно научился оффсеты статичные находить. По этому без умных слов пожалуйста! Подробно объясните как находить сигнатуры, и да я буду находить через Cheat Engine, по этому желательно примеры на нем если конечно они будут.
 
Вопрос из разряда "как реверсить игры". Искать сиги (то бишь искать функции) — почти самая главная часть в реверсе. Так что это тебе в другой раздел.
 
Вопрос из разряда "как реверсить игры". Искать сиги (то бишь искать функции) — почти самая главная часть в реверсе. Так что это тебе в другой раздел.
Спасибо за ответ хоть он и не самый информативный получился..
 
Спасибо за ответ хоть он и не самый информативный получился..
Так тут не на что давать ответ — вопроса нет, по сути. Ты сформулируй, что ты конкретно хочешь искать. Просто сигнатуры — ну ищи функцию в IDA\x64dbg, да делай сигу, лол.
Но тебе же нужно функцию нужную или данные какие-то найти. Какие? Всё по-разному ищется.
 
Так тут не на что давать ответ — вопроса нет, по сути. Ты сформулируй, что ты конкретно хочешь искать. Просто сигнатуры — ну ищи функцию в IDA\x64dbg, да делай сигу, лол.
Но тебе же нужно функцию нужную или данные какие-то найти. Какие? Всё по-разному ищется.
Мне оба и функцию и данные и как с ними желательно работать в дополнении т.к у меня External и пишу на путоне.
Как правильно сигну находить и создать , где и как сделать сигу в 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

как реализовать поиск сигны в коде
Ты у нас на питоне пишешь, лол. Может просто за тебя весь чит написать?
Доволен? Достаточно подробно?
 
Я тебе несколько раз ответил, что сигу не надо "находить", ты продолжаешь бессмысленные вопросы задавать

Ну говорю же, ты чего-то не понимаешь. Сига — это просто набор байтов. Как она поменяется при обновлении? Ну как разрабы захотят, так и будет, как код свой изменят.

Создавать можешь как тебе угодно.
Пожалуйста, авторизуйтесь для просмотра ссылки.
,
Пожалуйста, авторизуйтесь для просмотра ссылки.
. Хочешь — вообще вручную.
Вот тебе ассемблер. Красное — мусор (заменяй на ?)

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


Ты у нас на питоне пишешь, лол. Может просто за тебя весь чит написать?
Доволен? Достаточно подробно?
Так бы сразу и без мелочи этой.
 
Ну так вопрос был "как находить сигнатуры", а не как их создавать из уже найденного кода.
Какой вопрос — такой ответ.
 
Ладно смотри. Сигнатуру которую ты привел точно такая же как у меня , я взял dota_camera_distance.

Закинул его в свою программу для поиска сигнатуры но он вывел 0 совпадений

код:

find_signature:
Expand Collapse Copy
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 []
 
Я новичок в этой теме недавно научился оффсеты статичные находить. По этому без умных слов пожалуйста! Подробно объясните как находить сигнатуры, и да я буду находить через Cheat Engine, по этому желательно примеры на нем если конечно они будут.
в чит енджине это AOB(array of bytes) скан называется, погугли на эту тему и документацию по чит енджину почитай по синтаксису
Пожалуйста, авторизуйтесь для просмотра ссылки.
сам сигскан это разновидность pattern matching'а, обычно fuzzy pattern matching всё-таки(это расширение exact pattern matching'а с игнором некоторых символов(wildcard'ов, * или ? обозначаются(обычно вопросики все-таки(где-то один байт обозначают 1 вопросик где-то 2 вопросика, см. документацию по тулзам которые юзаешь))))
если тебе так понятнее то можешь считать pattern matching маленьким подклассом regular expressions(regex(или regexp))
обычно все-таки сигскан это примитивная вещь, без capturing group и подобных вещей, поэтому работают с его результатами руками самостоятельно(ну конечно от инструментов зависит, но не суть).
пример сигскана:
(циферки показывают позиции букв в тексте если что. начиная с 0 индекса)
десятки-0000000000111111111122222222223333-
единицы-0123456789012345678901234567890123-
корпус "Ivan was born in year 1234 in Rome"

десятки--00000000001111111111-
единицы--01234567890123456789-
паттерн "born in year ???? in" (пускай вопросик это игнор одного символа)


матч "born in year 1234 in" на [корпус + 09] (т.е. на десятом по счёту(с единицы) символе от начала места где искали мы нашли совпадение по шаблону, этакому "общему внешнему виду")
чтобы достать 1234 мы берем [(корпус + 09) + 13] (т.е. мы относительно того места где нашли прибавляем 13 байт и в итоге получаем 22-ю позицию которая нас и интересует)) и считываем оттуда 4 буковки и получаем наши 1234 и дальше с ними что хотим то и делаем
если конкретно про читы говорить то сигскан прежде всего используется для поиска инструкций(потому что они как правило радикально не меняются между апдейтами если разработчики игры не будут трогать соответствующий код и не будут обновлять компилятор, менять его настройки и т.д.; инструкции обычно меняют только свои операнды(и прежде всего именно относительные адреса)). т.е. ты делаешь сигу в стиле
"add ecx, ???
call ???
test al, ???"
(т.е. "прибавить хуй знает сколько", потом "вызвать хуй знает что", "умножить побитово на хуй знает что")
и можешь найти например
"add ecx, 123
call SomeFunc
test al, 99"
и потом уже найдя это совпадение, достать что нужно откуда нужно(например SomeFunc)
операнды в разных инструкциях по-разному кодируются, смотри документацию и/или в дизассемблерах некоторых операнды отделены визуально, например в x64dbg пробелами
1744653268284.png

сига соответствующая будет например(два вопросика - 1 байт игнор)
48 89 5C 24 ??
48 89 74 24 ??
55 57 41 56
48 8D AC 24 ?? ?? ?? ??
48 81 EC ?? ?? ?? ??
48 8B 05 ?? ?? ?? ??
48 33 C4
и будет находить места с таким же "общим видом", например то место что на скрине и всякие другие внешне похожие(но отличающиеся мелкими деталями которые вопросиками помечены и игнорятся при поиске):
1744653478055.png

1744653493338.png

найдя совпадение, доходишь от него до нужной инструкции и декодируешь ее и достаешь что надо(зачастую mov,lea,call,jmp и подобные инструкции интерес представляют, они обычно используют относительную адресацию, т.е. [rip + offset], где rip это значение rip(адрес в оперативке откуда процессор инструкции считывает) после декодинга инструкции(т.е. оно будет равно адресу следующей инструкции уже(это адрес текущей плюс ее полный размер))
 
Хорошо я +- понял. Но осталась маленькая проблема - поиск в коде, в коде у меня постоянна происходит ошибка 299, запускал от имени администратора, выдавал все права PROCESS_ALL_ACCESS = 0x1F0FFF PROCESS_VM_READ = 0x0010 PROCESS_QUERY_INFORMATION = 0x0400 PROCESS_VM_OPERATION = 0x0008
Но все ровно ошибка 299
 
Хорошо я +- понял. Но осталась маленькая проблема - поиск в коде, в коде у меня постоянна происходит ошибка 299, запускал от имени администратора, выдавал все права PROCESS_ALL_ACCESS = 0x1F0FFF PROCESS_VM_READ = 0x0010 PROCESS_QUERY_INFORMATION = 0x0400 PROCESS_VM_OPERATION = 0x0008
Но все ровно ошибка 299
Пожалуйста, авторизуйтесь для просмотра ссылки.
ERROR_PARTIAL_COPY
299 (0x12B)
Only part of a ReadProcessMemory or WriteProcessMemory request was completed.
читаешь больше памяти чем "есть" в данном месте на самом деле(за допустимые пределы выходишь частично). логай адрес и размер откуда и сколько ты пытался считать и смотри в тулзах которые показывают распределение виртуальной памяти(процесс хакер, дебаггеры и так далее) есть ли там виртуальная память.
ну вообще конечно сигскан обычно в пределах МОДУЛЯ(exe/dll) выполняется а не по всей памяти, да и даже так инструкции лежат только в пределах исполняемых секций(ну можешь не париться на самом деле и во всём модуле искать для простоты). сначала кароче получи адрес и размер модуля и в этих пределах ищи(инфа о загруженных модулях находится в PEB(process environment block), см. GetModuleHandleA/W, K32GetModuleInformation и т.д.)
 
Проблему решил теперь осталась проблема в уникальности, вот щас делаю поиск по сигу находиться 10 совпадений но не один не имеет верный адрес.
Сига правильная (надеюсь):
Пожалуйста, авторизуйтесь для просмотра ссылки.

так же пробовал: 48 83 EC 28 E8 ?? ?? ?? ?? 48 83 C4 28
но все ровно не находит
 
Последнее редактирование:
Проблему решил теперь осталась проблема в уникальности, вот щас делаю поиск по сигу находиться 10 совпадений но не один не имеет верный адрес.
Сига правильная (надеюсь):
Пожалуйста, авторизуйтесь для просмотра ссылки.
ну во-первых вопросики на конце не нужны(вопросик = ИГНОР ПРИ ПОИСКЕ. зачем игнорить конец если можно просто его не писать в сигу)
во-вторых 48 83 C4 ?? все-таки а не 48 83 ?? ??, там C4 это не операнд(операнд там 0x28 - то, что вычитают) это часть самой инструкции(ну не суть конечно)
а так да, находит то место
1744771677635.png

значит кривой код для поиска у тебя или не там ищешь. дебажь, логай и так далее...
 
Проверил адрес который выдает код на Memory View и они полностью идентичны но находяться в разных местах это что значит мне надо расширить сигну в верх и вниз? И если да то что делать с пустыми CC int 3
 
Или 90 (NOP)
 
Проверил адрес который выдает код на Memory View и они полностью идентичны но находяться в разных местах это что значит мне надо расширить сигну в верх и вниз?
ниче не понял, скриншоты в студию, да и код тоже; если у тебя два разных места подходят под шаблон В ПРЕДЕЛАХ НУЖНОГО ТЕБЕ МОДУЛЯ то это да, проблема(ну или если они полностью одинаковы по интересующему тебя функционалу то поебать впринципе), надо либо на другое место сигу делать или удлинять или еще что-нибудь предпринимать; но ты мб просто не там ищешь? мб ты просто во всем процессе ищешь а не в конкретном модуле?
И если да то что делать с пустыми CC int 3
CC не трогай это паддинг для выравнивания функций до 16-байтной границы(ну т.е. чтобы у функции ноль на конце в адресе был в шестнадцатиричной системе(ну или другими словами последние четыре бита в ноль стояли))(ну например если функция размером 6 байт, то у нее 10 байт CC'шек будет на конце, если она 30 размером то 2 CC будет и т.д. - если функция поменяет свой размер то количество CC изменится так что лучше их в сиги не брать это нестабильно. само CC кодирует инструкцию int3 которая ну +- крашнет кароче(если не подключен дебаггер). это сделано для того чтобы если вдруг какойто алкаш случайно начнет по какой-либо причине исполнять там инструкции в паддинге(т.е. за пределами функции) то он бы получил краш(точнее исключение) и пошел бы разбираться что не так. так-то конечно там любые байтики могли бы быть в паддинге но обычно CC компиляторы высирают чтобы крашило если это запустят случайно)
ноп это тоже паддинг обычно
 
... Да я искал когда дотка была запущена иначе как?
Код у меня длинный очень длинный в принципе там половина для find_pattern
get_module_info, get_executable_memory_regions, get_memory_regions, pattern_to_bytes, find_pattern
Python:
Expand Collapse Copy
    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 []
 
Назад
Сверху Снизу