On-line patching в секретах и советах Крис касперски Хакер, номер #093, стр. 093-080-5 Далее, несмотря на то, что KERNEL32.DLL проецируется на _все_ процессы, при записи внедряемого кода, соответствующие страницы памяти автоматически расщепляются и модификация затронет _только_ хакаемый процесс, никак не воздействия на все остальные (это называется "копированием при записи" — copy-on-write). План наших действий в общих чертах выглядит так: определяем адрес выбранной API-функции в своем процессе, вызываем VirtualAllocEx, выделяя в хакаемом процессе блок памяти, используемый для "сигнальных" целей, запоминаем его адрес и тут же копируем его в shell-код, внедряемый в API-функцию посредством WriteProcessMemory, естественно, сохранив его оригинальное содержимое. Впрочем, о перехвате API-функций, мы уже неоднократно писали, так что не будем повторяться. Рассмотрим усовершенствованный вариант нашего on-line patcher'а. Он перехватывает API-функцию GetVersion, внедряя на ее место shell-код следующего содержания: inc byte ptr [p_p]/ret, где p_p – адрес блока памяти, выделенного VirtualAllocEx. При каждом вызове GetVersion содержимое переменной p_p будет увеличиваться на единицу (оригинальное содержимое функции GetVersion для простоты не сохраняется) и когда оно достигнет двух, наш on-line patcher поймет, что программа распакована и пора приниматься за модификацию. Естественно, чтобы отловить этот момент, приходится непрерывно опрашивать переменную p_p, вызывая ReadProcessMemory в цикле, что не только некрасиво, но еще и непроизводительно. Эстеты могут воспользоваться средствами межпроцессорного взаимодействия (например, семафорами), однако, это усложнит реализацию shell-кода, но вместе с тем улучит качество on-line patcher'а. Фрагмент файла NtExplorer.crack-API.c, демонстрирующего patch через перехват API unsigned char shell[] = {0xFE,0x05,0x56,0x34,0x12,0x00,0xC3}; // INC byte [^^^ address ^^^]; RET // определяем адрес GetVersion h = LoadLibrary("KERNEL32.DLL"); p_f = GetProcAddress(h,"GetVersion"); // внедряем в программу свою переменную p_p = VirtualAllocEx(pi.hProcess, 0, 0x1000, MEM_COMMIT, PAGE_READWRITE); // готовим shell-код - подставляем фактический адрес переменной p_p memcpy(&shell[2],&p_p,4); // внедряем shell-код в программу // здесь цикл необходим для того, чтобы дождаться момента, // когда библиотека KERNEL32.DLL будет загружена while (!WriteProcessMemory(pi.hProcess, p_f, shell, sizeof(shell), &N)); // ждем вызова GetVersion (непрерывный опрос переменной p_p) // первый вызов из распаковщика,второй вызов - из самой программы while(x<2) ReadProcessMemory(pi.hProcess, p_p, &x, sizeof(x), &N); |