Site icon Заметки разработчика

Динамическая модификация кода — самомодифицирующиеся программы

динамическая модификация кода программы

Вероятно, многие считают, что самомодифицирующийся код (динамическая модификация кода программы) нужен только вирусам и троянам для того, чтобы затруднить распознавание антивирусными программами. Однако это далеко не так, и, как показывает практика, вынуждает нас этим заниматься сама великая и могучая корпорация — Microsoft.

Почему? — спросите Вы. Да очень просто. Каждая новая операционная система делает доступным для разработчиков ряд новых технологий. И это здорово! Проблема в том, что, несмотря на свое величие, даже Microsoft не в состоянии тщательно тестировать все свои продукты. Таким образом, часть того, что работало раньше, в некий момент перестает работать. Есть, правда, и случаи, когда это делалось Microsoft’ом намеренно (например, была заблокирована возможность запускать неподписанные драйвера под x64). Но это — тема для отдельного разговора.

Что же делать программным пакетам, если в новой версии операционной системы или Service Pack все перестало работать? Бывают случаи, что остаётся только одно — модифицировать код компонентов Microsoft на лету!

Для драйверов ядра с этим есть определенные проблемы. Я имею в виду Patch-Guard под 64-битными системами. Я собираюсь написать про него отдельно в следующих статьях. Ну, а что касается приложений пользовательского режима — возможность такая пока есть и, надеюсь, будет доступна и в дальнейшем.

Я говорю о паре Win32 функций, жизненно необходимых для таких операций:

BOOL WriteProcessMemory(HANDLE  hProcess,
                        LPCVOID lpBaseAddress,
                        LPVOID  lpBuffer,
                        DWORD   nSize,
                        LPDWORD lpNumberOfBytesWritten);

BOOL VirtualProtect(LPVOID lpAddress,
                    SIZE_T dwSize,
                    DWORD  flNewProtect,
                    PDWORD lpflOldProtect);

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

Во всем, что я уже успел перечислить, немаловажно и то, что, во-первых, изменение c использованием такого метода кода, например, Kernel32.dll, происходит только для текущего процесса и не будет влиять на работу других программ, а во-вторых, то, что все известные мне антивирусы относятся к этому весьма лояльно!

Если дела обстоят таким образом, что же нас останавливает от написания самомодифицирующегося кода? Да разве что вопрос — зачем? :). В статье про асинхронный DCOM я приведу конкретный пример, когда это необходимо. Сейчас же давайте попробуем освоить метод до конца.

Реализация метода

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

int BuggyFunction(volatile int a)
{
   return a + 0x12345678;  // А должно быть "return a - 0x12345678;"
}

Таким образом, нам нужно:

     1. Найти адрес инструкции сравнения переменных a и b (add)
     2. Заменить ее на sub

Программа, которую я предлагаю вашему вниманию, и выполняет эти действия. Это простое консольное приложение для Windows. Входная точка программы — функция main — вычисляет адрес функции BuggyFunction, находит смещение инструкции add и выполняет модификацию кода:

   void main()
   {
      // Вызываем оригинальную функцию и печатаем результат
      printf("Before modification: %08h\n", BuggyFunction(0x123));

      const int ms = 256;     // Функция BuggyFunction не может быть длиннее
      LPBYTE pCode = (LPBYTE)GetFunctionAddress(&BuggyFunction);
      for (int i = 0; i < ms; i++, pCode++)
      {
         if (*(DWORD *)pCode == 0x12345678)
            break;
      }

      if (i >= ms)
      {
         printf("Хмм. Инструкция не найдена\n");
         return;
      }

      ...
      // Открываем процесс
      // Разрешаем запись в секцию кода используя VirtualProtect();
      DWORD cb;
      BYTE  bSub = 0x50;
      WriteProcessMemory(hProcess, pCode, &bSub, sizeof(bSub), &cb);
      ...
      // Восстанавливаем доступ
      // Освобождаем ресурсы

      // Вызываем модифицированную функцию и печатаем результат
      printf("After modification: %i\n", BuggyFunction(0x123));
   }

Предлагаю скачать код проекта по ссылке внизу страницы и убедиться, что, как я и утверждаю, программа при исполнении напечатает

      Before modification: 1234579Bh
      After modification: EDCBAAABh

Что и требовалось доказать. Спасибо за внимание.

selfmodify.zip

Exit mobile version