IPB

Добро пожаловать, гость ( Вход | Регистрация )

> Работа с отладчиком
Siberian GRemlin
Jun 14 2018, 11:14
Сообщение #1


Advanced Member
***

Группа: CTPAX-X
Сообщений: 538
Регистрация: 4-February 08
Пользователь №: 2
Спасибо сказали: 223 раз(а)



Дайте кто-нибудь совет новичку, кратко, чтобы не читать целую книгу.

Как найти в отладчике функцию в exe игры, которая отвечает за расшифровку файла? Игра полностью читает файл в память, в котором предположительно лежит ключ и сами зашифрованные данные, либо ключ как-то генерируется из имени файла или его размера, ибо для каждого файла ключ свой. Проблема в том, что файл сначала был сжат zlib'ом, затем зашифрован, также используется выравнивание до размера кратного 16. Возможно, файлы сжимаются блоками и уже их выравнивали, а затем всё шифровали, так как чем больше размер файла тем больше разница со сжатым мной — в паке есть и зашифрованные/сжатые файлы и чистые, но использует только зашифрованные.

Судя, по основному архиву игры, шифруется обычным цикличным xor. В архиве к каждому файлу прилагался свой ключ в 20 байт.

Хотелось бы овладеть навыком находить ключи и сами алгоритмы шифрования.
User is offlineProfile CardPM
Go to the top of the page
+Quote Post
 
Reply to this topicStart new topic
Ответов
-=CHE@TER=-
Mar 13 2026, 23:01
Сообщение #2


Walter Sullivan
***

Группа: Root Admin
Сообщений: 1,428
Регистрация: 4-February 08
Пользователь №: 3
Спасибо сказали: 327 раз(а)



Добавлю ещё кое-чего. Сначала код, потому объясню как им пользоваться (код из моей программы перевода The Neverhood):
CODE
static void MemWrite(void *p, void *b, DWORD l) {
DWORD dx;
  if (p && b && l && VirtualProtect(p, l, PAGE_READWRITE, &dx)) {
    CopyMemory(p, b, l);
    VirtualProtect(p, l, dx, &dx);
    FlushInstructionCache(GetCurrentProcess(), p, l);
  }
}

#define HOOK_NONE 0x00
#define HOOK_CALL 0xE8
#define HOOK_JUMP 0xE9

static void HookAddr(void *hd, void *hs, DWORD ht) {
BYTE *d, *s;
  d = hd;
  s = hs;
  // need to add jump / call instruction
  if (ht != HOOK_NONE) {
    MemWrite(d, &ht, 1);
    // recalc addr
    s -= (((SIZE_T) d) + 5);
    d++; // skip call / jump instruction
  }
  MemWrite(d, &s, 4);
}
В общем, я в сообщении выше давал код wrapper/proxy/обёртки .DLL, но бывают ситуации, когда нужно не просто экспортируемые функции библиотеки на свои заменить, но и что-то ещё перехватить.
Например, есть функция куда кривые/косые/неверные входные аргументы попадают. Проверить и поправить их перед тем как отдать функции места нет или нужно внутри функции ещё что-то сделать. Тогда приходится в DLL-обёртке перехватывать ещё и такие функции.
Стратегий перехвата, как и с прерываниями в DOS когда-то, всего три:
1. Функция-перехватчик вызывается вместо оригинальной (полная замена).
2. Функция-перехватчик вызывается после оригинала (сначала вызываем оригинальную функцию, потом смотрим что делать с результатом).
3. Функция-перехватчик вызывается до оригинала (сначала подготавливаем входящие параметры и/или среду необходимым образом, затем уже вызываем оригинальную функцию).
Предположим, есть у нас такой код в дизассемблере (точка в начале означает виртуальный адрес):
CODE
.40100E: E8 6D 00 00 00 call .401080
Помним, про то что у длинного JUMP, как и у CALL, после байта операции идёт не адрес вызова, а смещение относительно конца текущей команды - насколько перейти. В данном случае:
0x40100E (текущий адрес) + 5 (размер текущей инструкции) + 0x0000006D (смещение) из чего получаем:
0x40100E + 5 + 0x0000006D = 0x401080 (поэтому там такой адрес и стоит напротив CALL).

В общем, нашли такую функцию, которую хотим перехватить, и её адрес. Теперь как для каждой из трёх стратегий нам нужно будет поступить:

В случае замены функции всё просто:
CODE
HookAddr((BYTE *) 0x40100E, (void *) hook_MyFunc, HOOK_CALL);
Где hook_MyFunc() - это функция прототип которой полностью соответствует прототипу перехваченной функции! Т.е. тип вызова (cdecl/stdcall/thiscall/и т.п.), количество аргументов и их типы. Иначе либо стек разрушите, либо получите что попало вместо результата работы.

В случае вызова функции до/после из оригинальной - точно также хучите вызов, но в коде hook_MyFunc() либо вызываете оригинал функции в начале, либо в конце, перед выходом и возвращением результата. Обратите внимание, что для вызова оригинальной функции должен быть объявлен прототип. Вот пример перехвата функции SetLastError() из WinAPI:
CODE
// hooked function prototype
typedef void (WINAPI *LPSETLASTERROR)(DWORD dwErrCode);

// hook for replaced function
void WINAPI hook_SetLastError(DWORD dwErrCode) {
LPSETLASTERROR orig_SetLastError;
  // for example overwrite access denied error
  if (dwErrCode == ERROR_ACCESS_DENIED) {
    dwErrCode = ERROR_SUCCESS;
  }
  // call original routine
  orig_SetLastError = (LPSETLASTERROR) 0x401080;
  orig_SetLastError(dwErrCode);
}

// somewhere in the code hook original function call
HookAddr((BYTE *) 0x40100E, (void *) hook_SetLastError, HOOK_CALL);


Далее. Иогда функция, которую нужно заменить, вызывается в куче разных мест и заменять через HOOK_CALL каждое такое место - удовольствие ниже среднего. В таком случае хучим уже адрес начала функции через HOOK_JUMP:
CODE
HookAddr((BYTE *) 0x401080, (void *) hook_SetLastError, HOOK_JUMP);

Обратите внимание, что вместо 0x40100E, там теперь стоит адрес 0x401080 и вместо HOOK_CALL стоит HOOK_JUMP. Иными словами мы в начало кода функции вкорячиваем безусловный переход на нашу функцию. Хочу отметить, что при таком способе вызов оригинальной функции будет невозможен, потому что мы только что уничтожили её начало! Поэтому этот способ подходит только при полной замене функции, которая много вызывается в разных местах.

Наконец, HOOK_NONE используется когда нужно в массив адресов функций (какая-то структура) просто вписать адрес нужной нам функции-перехватчика без каких-либо изменений.

Последнее, что хочу заметить, так это то что функции вполне себе нормально хучить в DLL-обёртке из DllMain() при fdwReason == DLL_PROCESS_ATTACH. Однако хучить функции и вызывать код - две большие разницы! Помните, что в DllMain() можно делать очень небольшой набор вещей, иначе получите ту же проблему, что и в старых 3DMark (ссылка).
Если нужно вызвать код, который что-либо инициализирует, то перехватываете какую-нибудь функцию в WinMain() или сам вызова WinMain() из CRTStartup() и оттуда уже вызываете код всяких библиотек, создаёте окна и всё такое прочее.
Если есть какие-то ресурсы, которые нужно непременно корректно освободить перед закрытием программы, то перехватываете какую-либо функцию, которая вызывается при закрытии приложения, и там уже делаете необходимые действия не забывая до или после вызвать перехваченный оригинал.
User is offlineProfile CardPM
Go to the top of the page
+Quote Post

Сообщения в этой теме


Reply to this topicStart new topic
1 чел. читают эту тему (гостей: 1, скрытых пользователей: 0)
Пользователей: 0 -

 



Упрощённая версия Сейчас: 1st May 2026 - 13:05