Как исправить dll в visual studio


Download Article


Download Article

Most people users will not need to edit DLL files. However, if you are a computer programmer, you may want to edit DLL files to reverse engineer a program, extract icons, hack software, or just see how a program works under the hood. Microsoft Visual Studio allows can open and edit certain sections of a DLL file. If you need to edit the code in a DLL file, you will need to use a decompiler application to extract the code from the file. This wikiHow article teaches you how to open and edit DLL files in Visual Studio.

  1. Image titled Run a HTML File in Visual Studio Code Step 1

    Yes, you can open and edit certain DLL resources within Visual Studio. You can use Visual Studio to add, extract or delete certain graphical or textual resources, but you cannot edit the functions of a DLL file.

  2. Advertisement

  1. Image titled Edit DLL Files in Visual Studio Step 2

    Not in most cases. Most DLL files do not contain any source code that can be edited. The code contained within most DLL files has already been compiled into binary or machine code.

    • If the DLL file contains any intermediate language (IL), you can view the IL code using the IL Disassembler tool, which is installed alongside Visual Studio. You can also use CIL Disassembler to view common intermediate language within a DLL file. CIL Disassembler does not come with Visual Studio, but it is free to download from the Microsoft Store.
    • If a DLL file is not functioning correctly and you need to edit it, you can do so by editing the original source code that was used to create the DLL file. This will be on a separate code file. You can use the original source code to create a new DLL file, which can be used to replace the DLL file that isn’t working.
  1. Image titled What Software Opens Dll Files for Free Step 5

    You can try to reconstruct it using a decompiler program. A decompiler program can take the machine language within a DLL file and convert it into a high-level language, such as C++ or C#.

    • Warning: Any code you extract from a DLL file using a decompiler will be a reconstruction. There is no guarantee that it will match the original source code. If you attempt to replace a DLL file using code extracted from a decompiler program, you will likely need to do lots of testing to make sure all programs that depend on the DLL file still function correctly.
  2. Advertisement

  1. Image titled Edit DLL Files in Visual Studio Step 4

    You can easily open a DLL file in Visual Studio the same way you would any other file. To do so, click File in the menu bar at the top, followed by Open. Then click File and select the DLL file you want to open and click Open. This will open the DLL file in a new Resource Editor window. Alternatively, you can use the following steps to open a DLL file:[1]

    • Make sure you have Visual Studio installed.
    • Press Win + E to open File Explorer.
    • Navigate to the folder containing the DLL file you want to open.
    • Right-click the folder and click Open with Visual Studio.
    • Expand the folder in the Solution Explorer window to the right.
    • Double-click the DLL file you want to open.
  1. Image titled Edit DLL Files in Visual Studio Step 5

    You can easily add a new resource to a DLL file in Visual Studio. These resources include keyboard shortcut accelerators, bitmap images, cursors, icons, dialogue boxes, menus, HTML files, audio (.wav) files, string tables, and the version number. Use the following steps to add a new resource to a DLL file.

    • Open a DLL file in Visual Studio.
    • Right-click the folder you want to add the resource to in the Resource Editor window.
    • Click Add Resource.
    • Double-click the resource type you want to add or click Import to import an existing file.
    • Click File
    • Click Save [filename].dll to overwrite the file or click Save [filename].dll as to save a copy.
  2. Advertisement

  1. Image titled Edit DLL Files in Visual Studio Step 6

    You can easily do so in Visual Studio. Warning: Editing or deleting contents from a DLL file may cause the programs that depend on that file to not function properly. Be sure to save a backup copy of the DLL file you are editing. Use the following steps to delete a resource from a DLL file:

    • Open a DLL file in Visual Studio.
    • Expand the folder that contains the resource you want to delete in the Resource Editor window.
    • Right-click the resource you want to delete and click Delete.
    • Click File
    • Click Save [filename].dll to overwrite the file or click Save [filename].dll as to save a copy.
  1. Image titled Edit DLL Files in Visual Studio Step 7

    You can easily use Visual Studio to create new DLL files. You can do so using your own source code, or source code extracted from a decompiler program. Use the following steps to create a new DLL file in Visual Studio:[2]

    • Open Visual Studio.
    • Click Create New Project.
    • Type class in the search bar at the top.
    • Scroll down and select Class Library (.NET Framework) and click Next.
    • Type a name for your project and click Create.
    • Enter the code for your DLL file.
    • Click Build in the menu bar at the top.
    • Click Build Solution to create a new DLL file.
    • Check the Output window at the bottom to see where the DLL file was created.
  2. Advertisement

  1. Image titled Edit DLL Files in Visual Studio Step 8

    You can do so using the Developer Command Prompt. The IL Disassembler tool allows you to view intermediate language (IL) and assembly language in a DLL file. The IL Disassembler tool is installed alongside Visual Studio. Use the following steps to open a DLL file in IL Disassembler:

    • Click the Windows Start menu.
    • Type Developer Command Prompt.
    • Click Developer Command Prompt for VS 2019 (or whichever version of Visual Studio you are using.)
    • Type ILDASM.EXE and press Enter.
    • Drag and drop the DLL file into the IL Disassembler window.
    • Expand the filename below “MANIFEST’ in the IL Disassembler window.
    • Click a class or function to view the assembly code.
  1. Image titled Edit DLL Files in Visual Studio Step 9

    You will need to use a separate decompiler program outside of Visual Studio. These programs can convert machine code into a high-level language, like C++ or C#. Warning: The code these programs extract is a reconstruction and not guaranteed to match the original source code. Use the following steps to decompile a DLL file:[3]

    • Download and install a Decompiler program. Popular programs include Reflector, dotPeek, and JustDecompile.
    • Open your decompiler program.
    • Drag and drop the DLL file into the Decompiler program window.
    • Expand the program or filename in the panel to the right.
    • Expand the program name next to the bracket icons.
    • Click the function or class name to view the code.
    • Copy the code into a Visual Studio file, or click File followed by Export to Project.
  2. Advertisement

  1. Image titled Open the Command Prompt in Windows Step 4

    Some DLL files need to be registered in order to function. You can register a DLL file using the Run program. Use the following steps to register a DLL file in Windows 10:[4]

    • Press Win + R to open Run.
    • Type regsvr32 next to “Open.”
    • Drag and drop the DLL file into the “Open” text box to add the path to the DLL file.
    • Put quotations (” “) around the path to the DLL file.
    • Click Ok.

Ask a Question

200 characters left

Include your email address to get a message when this question is answered.

Submit

Advertisement

Thanks for submitting a tip for review!

About This Article

Thanks to all authors for creating a page that has been read 65,369 times.

Is this article up to date?

Как восстановить DLL, не найденную в Visual Studio

Одним из самых популярных инструментов кодирования является Visual Studio, но многие пользователи сообщают о получении сообщения об ошибке «DLL не найдена в Visual Studio» при его использовании.

Что касается Visual Studio, замена отсутствующих библиотек DLL — это довольно простой процесс, и в сегодняшнем руководстве мы научим вас, как это сделать лучше всего.

Где находится DLL в Visual Studio?

Используйте окно «Модули» в Visual Studio, чтобы увидеть библиотеки DLL, от которых зависит ваш проект. Выберите Windows в разделе «Отладка», чтобы получить к нему доступ. Наконец, выберите Модули.

Кроме того, вы можете использовать Ctrl + другой + U ярлык.

Как я могу исправить Visual Studio, если файлы DLL отсутствуют?

Есть несколько возможных обходных путей, которые вы, возможно, захотите изучить, прежде чем мы начнем:

  • Перемещение файлов DLL – Перемещение файлов DLL в ту же папку, что и исполняемый файл, — это самый быстрый способ решить любую проблему с DLL. Вы можете автоматизировать этот процесс, создав событие после сборки.
  • Используйте библиотеки DLL из другого места – В зависимости от вашего проекта использование DLL из другого пути может помочь решить различные проблемы. Предлагаемый путь C:Program Files (x86)Microsoft Visual Studio 14.0VCredistdebug_nonredistx64Microsoft.VC140.DebugCRT
  • Проверьте настройки антивируса – Cylance Protect и другое антивирусное программное обеспечение могут вызывать проблемы с библиотеками DLL. Настройте белый список антивируса, чтобы файлы DLL не были заблокированы.
  • Удалите файл suo — Закройте Visual Studio и удалите файл . файл суо. Файл находится рядом с . sln-файл. Запустите Visual Studio и сохраните все файлы.
  • Используйте правильную версию. СЕТЬ – Убедитесь, что вы ориентируетесь на правильную версию. СЕТЬ. Использование неправильного может вызвать проблемы.

1. Используйте специальный фиксаж DLL

Хотя вы всегда можете загрузить необходимые файлы, если DLL не включена в Visual Studio, это может занять много времени, особенно если вам требуется много файлов.

Когда дело доходит до сторонних DLL, могут возникнуть проблемы, если файл DLL не полностью совместим с Windows. Fortect может помочь в этой ситуации.

2. Установите переменные среды для проектов

  1. В Visual Studio перейдите в «Проекты» и выберите Предложения.
  2. Затем перейдите в «Свойства конфигурации» и выберите «Среда отладки». Наконец, выберите Объединить среду.

  3. Теперь найдите значение PATH и измените его на: PATH=%PATH%;$(TargetDir)DLLS
  4. Сохранить изменения.

Попробуйте и это; некоторые пользователи утверждали, что им помогло использование $(ExecutablePath) вместо $(TargetDir).

3. Изменить настройки сборки

  1. На правой панели щелкните правой кнопкой мыши решение и выберите Предложения.

  2. Теперь нажмите Конфигурация.
  3. Проверить строить коробки для проектов. Если они уже отмечены, снимите их и сохраните изменения. После этого проверьте их снова.

  4. Необязательно: некоторые пользователи предлагают перезапустить Visual Studio после снятия флажков «Сборка».
  5. Необязательно: повторите шаг 3 для обоих Выпуск и Отлаживать режимы.

Как видите, проблема с DLL, не найденной в Visual Studio, решается просто. Все, что вам нужно сделать, это правильно настроить свои проекты.

Какие еще методы вы использовали для решения этой проблемы? Расскажите нам в разделе комментариев ниже.

Похожие темы



Время на прочтение
14 мин

Количество просмотров 4K

В этой статье я хочу поделиться практическими методами отладки, модификации и устранения багов в 32-х и 64-х разрядных приложениях под ОС Windows, разработанных на языке C/C++, исходные коды которых по тем или иным причинам не стали достоянием общественности.

Этот пробел отчасти можно попытаться устранить, например, с помощью плагина Hex-Rays для IDA Pro, и зачастую удаётся довольно качественно восстановить нужный участок исходного кода, обнаружив в нём проблемное место. Но после этого всегда возникает вопрос – что с этим исправленным кодом делать дальше, как и где его можно использовать? На данном этапе мне всегда хотелось взять этот отдельно декомпилированный фрагмент программы, поменять в нём что-нибудь и затем каким-то чудесным образом «поместить обратно» в программу.

Далее будет описан один из возможных способов реализации этой идеи на практике.

Иногда возникает необходимость исправить какой-нибудь баг в сторонней программе, добавить к ней отсутствующий функционал, изменить существующий, либо модифицировать логику некоторых процессов, особенно если нет возможности обратиться за помощью к разработчикам. Описание процедуры выявления «интересных» мест выходит за рамки данной статьи. В некоторых случаях это может быть банальная ошибка в программе, которую удаётся вылечить несложным патчем непосредственно в теле EXE или DLL файла. Но если же предстоит исправлять алгоритмы поведения некоторых функций, либо добавлять новые, обычным «байтовым» патчем здесь уже не обойтись. Устранение же некоторых на первый взгляд простых багов может в итоге вылиться в «перепахивание» доброй половины программы. Также приходится потрудиться, чтобы заставить правильно работать приложения, созданные, например, под Win XP в новых ОС Windows.

Освоив и давно применяя на практике некоторые методы модификации программного кода, самым удобным и простым на мой взгляд способом оказалась его модификация в ран-тайме с помощь метода DLL-инъекции. На эту тему существует огромное количество материала и в плане реализации самого метода DLL-инъекции данная статья не претендует ни на новизну, ни на оригинальность, но в большей степени призвана систематизировать практические наработки в области отладки, модификации, поиска и устранения багов в чужом коде с его помощью.

Описываемый здесь способ вынесения некоторых функций сторонних приложений в свою собственную программу на C/C++ позволяет отлаживать, модифицировать и при необходимости более детально изучать их работу с помощью отладчика на высокоуровневом языке, вместо того, чтобы «дебажить» их в дизассемблированном коде.

Прилагаемый к данной статье пример с помощью минимальных изменений можно использовать в своих целях в качестве готового проекта для Visual Studio (VS).

В VS при создании 32-х разрядных приложений платформа называется «x86», а 64-x разрядных «x64». Поэтому чтобы не путаться с названиями я буду для обозначения разрядности приложений использовать термины «Win32» и «Win64».

Инструментарий.

  • IDA Pro (файлы примеров в проекте для v7.5).

  • CFF Explorer или любой другой инструмент редактирования импорта.

  • Visual Studio С/C++ (проект примера для Platform Toolset: Visual Studio 2022 (v143)).

Практика внедрения.

Для примера рассмотрим тестовую программу SimpleCalc – простое оконное приложение (проект для VS прилагается), которое производит сложение и умножение двух чисел.
В программу намеренно внесён «баг», приводящий к тому, что для операций «2+2» и «2*2» результат получается равным 5. Заголовок диалогового окна данного приложения – «Simple Buggy Calc ( 2+2=5; 2*2=5 )».

Следовательно, нам будет необходимо модифицировать его работу таким образом, чтобы в результате выполнения уникальных операций 2+2 и 2*2 получались правильные значения, а текст заголовка окна выглядел бы, как «Simple Calc».

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

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

Займёмся описанием класса Adder (имя этому классу можно задать любое).

Для работы с классами в IDA Pro существует плагин ClassInformer. В некоторых случаях он может помочь восстановить иерархию классов и имена виртуальных таблиц, чтобы получить близкие к оригиналу имена классов.

Место создания класса Adder обнаруживается в методе DialogProc, откуда можно определить его размер (для Win32 версии):

INT_PTR CALLBACK DialogProc( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
  ...

  pAdder = operator new( 0x10 );          // Размер структуры для класса Adder
  if( pAdder )
  {
    sub_401870( pAdder, 0x10 );           // Метод инициализации, созданный компилятором
    pAdder = sub_401160( pAdder, hDlg );  // Вызов конструктора Adder::Adder()
  }
  else
  {
    pAdder = 0;
  }
  _pAdder = pAdder;

  ...

Псевдокод конструктора в IDA Pro:

void* __thiscall sub_401160( void* this, HWND hDlg )
{
  *(_DWORD*)this = &Adder::`vftable';
  *((_DWORD*)this + 2) = GetDlgItem( hDlg, 1001 );
  *((_DWORD*)this + 3) = GetDlgItem( hDlg, 1002 );
  *((_DWORD*)this + 1) = GetDlgItem( hDlg, 1003 );
  return this;
}

Теперь создим в IDA Pro структуру такого же размера с именем класса. В нашем случае структура для класса и интерфейса будет выглядеть так:

IAdder          struc ; (sizeof=0x4)
  Add             dd ?      ; Адрес метода, выполняющего операцию сложения.
IAdder          ends

Adder           struc ; (sizeof=0x10)
  pVftable        dd ?      ; IAdder* - указатель на таблицу методов интерфейса IAdder
  _hRes           dd ?      ; HWND - идентификатор окна суммы
  _hAdd1          dd ?      ; HWND - идентификатор окна ввода для первого слагаемого
  _hAdd2          dd ?      ; HWND - идентификатор окна ввода для второго слагаемого
Adder           ends

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

INT_PTR CALLBACK DialogProc( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
  ...
  pAdder = (Adder*)operator new( sizeof( Adder ) );
  if( pAdder )
  {
    Adder:: __autoclassinit2( pAdder, sizeof( Adder ) );  // Метод инициализации, созданный компилятором
    pAdder = Adder::Adder( pAdder, hDlg );                // Конструктор
  }
  else
  {
    pAdder = 0;
  }
  _pAdder = pAdder;
  
  ...

 А конструктор будет теперь выглядеть так:

Adder* __thiscall Adder::Adder( Adder* this, HWND hDlg )
{
  this->pVftable = (IAdder*)&Adder::`vftable';      // Адрес таблицы методов интерфейса IAdder
  this->_hAdd1 = GetDlgItem( hDlg, IDC_ADD_EDIT1 );
  this->_hAdd2 = GetDlgItem( hDlg, IDC_ADD_EDIT2 );
  this->_hRes = GetDlgItem( hDlg, IDC_ADD_RES );
  return this;
}

Т.к. вызов реализованного метода Add интерфейса IAdder в программе происходит через таблицу адресов, найдём его в этой таблице Adder::`vftable’.

После причёсывания декомпилированного псевдокода в IDA Pro метод Adder::Add будет выглядеть примерно так:

// 0x4011B0
void __thiscall Adder::Add( Adder* this )
{
  int res;
  int add2;
  int add1;
  char* pBuf;

  pBuf = (char*)operator_new( 64 );

  GetWindowTextA( this->_hAdd1, pBuf, 64 );
  add1 = itol( pBuf );

  GetWindowTextA( this->_hAdd2, pBuf, 64 );
  add2 = itol( pBuf );

  // Этот «баг» нам необходимо будет исправить в своей DLL.
  //
  if( add1 == 2 && add2 == 2 )
    res = 5;
  else
    res = add2 + add1;

  sprintf_s( pBuf, 0x40, "%ld", res );
  SetWindowTextA( this->_hRes, pBuf );

  DeleteBuf( this, pBuf )
}

 Здесь для нас важно запомнить адрес этого метода – 0x4011B0.

Теперь перейдём к поиску и декомпиляции метода, производящего операцию умножения.
Его вызов также обнаруживается в методе DialogProc и после декомпиляции (для Win32 версии) будет выглядеть так:

// 0x401150
void __usercall Mult( HWND hMul1@<eax>, HWND hMul2@<ebx> )
{
  Mult_( hMul1, hMul2 );
}

 А код метода Mult_() так:

void __stdcall Mult_( HWND* hMul1, HWND* hMul2 )
{
  int res;
  int mul2;
  int mul1;
  char buf[64];

  GetWindowTextA( hMul1, buf, sizeof( buf ) );
  mul1 = itol( buf );

  GetWindowTextA( hMul2, buf, sizeof( buf ) );
  mul2 = itol( buf );

  // Этот «баг» нам необходимо будет исправить в своей DLL.
  //
  if( mul1 == 2 && mul2 == 2 )
    res = 5;
  else
    res = mul2 * mul1;

  sprintf_s( buf, sizeof( buf ), "%ld", res );

  SetWindowTextA( g_hMulRes, buf );
}

В программе SimpleCalc метод Mult() намеренно был создан, как псевдо __usercall с передачей параметров через регистры EAX и EBX. Компилятор от MS для Win32 использует соглашения о вызовах __cdecl,  __stdcall или __thiscall, но некоторые другие компиляторы могут использовать иные соглашения и примером такого поведения является метод Mult().

Конечно, в данном конкретном случае можно было бы ограничиться исправлением только __stdcall метода Mult_(), но для полноты картины опишем способ с нестандартным для MS соглашением о вызовах.

Для того, чтобы поместить значения регистров в стек для вызова «обычного» метода __stdcall  Mult_() из метода Mult(), воспользуемся атрибутом naked. Этот атрибут указывает компилятору не создавать пролог и эпилог внутри метода, чтобы не испортить значения регистров EAX и EBX (в нашем случае) до помещения их в стек:

// void __usercall Mult( HWND hMul1@<eax>, HWND hMul2@<ebx> )
__declspec( naked )
void Mult()
{
  __asm
  {
    push	ebx
    push	eax
    call	Mult_

    ret
  }
}
PATCH( 0x401150, Mult )

Можно было бы написать код метода Mult() и на чистом ассемблере, а для Win64 это единственный способ, так как в MSVC для него не поддерживается атрибут naked. Но для Win64, как правило, используется соглашение о вызове __fastcall и другие, по крайней мере среди тех приложений, которые мне приходилось «лечить», не попадались.

В методе Mult_(), реализующем операцию умножения, мы обнаруживаем использование глобальной переменной:

// 0x41B36C
HWND g_hMulRes;

Адреса метода Mult() и глобальной переменной g_hMulRes нам понадобятся, поэтому их необходимо будет запомнить.

Способ изменения заголовка диалогового окна будет описан чуть позже.

Создание инжектируемой DLL.

Итак, мы обнаружили и декомпилировали интересующие нас методы и теперь перейдём к созданию собственной DLL, которую позже внедрим в тестовую программу SimpleCalc. Проект такой DLL (Win32 и Win64) для VS прилагается.

Основная идея метода DLL-Injection заключается в том, что на этапе загрузки приложения, но ещё до передачи ему управления, загрузчик ОС, среди прочих, загрузит в общее адресное пространство и нашу DLL (которую мы добавим в секцию импорта EXE файла) и вызовет из неё метод:

BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason, LPVOID lpReserved )

со значением переменной ul_reason равным DLL_PROCESS_ATTACH.

Для упрощения описания не будем рассматривать здесь частные случаи типа отложенной загрузки, LoadLibrary или TLS-коллбэков.

На данном этапе мы должны модифицировать начальную часть перехватываемых нами методов из EXE файла по адресам, полученным из IDA Pro, разместив там команды безусловных переходов на адреса наших переписанных в DLL методов.

Для Win32 команда перехода, содержащая адрес непосредственно в своём операнде (а нам нужна именно такая, чтобы не портить ни стек, ни регистры) состоит из пяти байт, первый – 0xE9, а остальные четыре знаковое смещение относительно адреса следующей команды.
Для Win64 эта команда состоит из шести байт: 0xFF, 0x25, 0x00, 0x00, 0x00, 0x00 и следом за ними восемь байт адрес перехода – итого четырнадцать байт.

Здесь необходимо следить за тем, чтобы длина команды перехода была не больше длины самого модифицируемого метода! Как, например, для «заглушек», содержащих единственную однобайтовую команду ret.

Установкой адреса перехода занимается метод Patch(). Так как адресное пространство EXE модуля, в котором расположен исполняемый код недоступно для записи, сначала мы модифицируем права доступа к этой памяти с помощью функции VirtualProtect, затем записываем в начало перехватываемого метода нужные байты – команду перехода на наш метод в DLL и в конце восстанавливаем прежние права с помощью той же функции VirtualProtect.

При расчёте адреса перехода существует одна тонкость, заключающаяся в том, что IDA Pro по умолчанию дизассемблирует EXE файл по стандартному адресу загрузки – 0x00400000 (либо 0x140000000 для Win64). Но иногда встречаются EXE файлы, у которых в заголовке в параметре IMAGE_NT_HEADER.OptionalHeader.DllCharacteristics установлен флаг «DLL Can Move» и в этом случае ОС вправе загрузить его по любому другому адресу.

В свойствах проекта SimpleCalc в VS – за этот флаг отвечает пункт «Linker -> Advanced -> Randomize Base Address», который намеренно установлен в «Yes» для тестирования данного поведения.

Следовательно, в нашей DLL мы должны учитывать адрес, по которому загружен EXE файл, и затем использовать его в методе Patch() для расчёта смещения в команде перехода относительно адреса взятого из IDA Pro.

При работе с большими проектами, в которых используется множество классов, исходный код DLL в VS обрастает большим количеством отдельных файлов. И следить за тем, перехвачен ли тот или иной метод в DllMain с помощью вызова Patch() становится неудобным. Поэтому впоследствии был использован способ для объявления перехвата непосредственно возле нужного метода с помощью вспомогательной статической структуры. При этом оказалось возможным отказаться от использования DllMain, как места для объявления всех перехватов.

В дополнение к этому был создан вспомогательный класс CPatch.

При загрузке DLL ещё до попадания в точку входа (DllMain) инициализируются все статические объекты и таким образом, через вызов метода Patch( exeAddr, dllAdd ) происходит замена первых байт из оригинального метода в EXE на команду перехода в переписанный нами метод в DLL.

Класс CPatch содержит в себе контейнер std::map<ADDR, PATCH_DATA>, ключом в котором является адрес оригинального метода из EXE, а значением – структура PATCH_DATA, в которой хранятся оригинальные байты, заменённые на команду перехода и байты самой команды перехода. При каждом вызове метода Patch( exeAddr, dllAddr ) в этот контейнер добавляется новый элемент с соответствующим ключом – адресом оригинального метода из EXE и структурой PATCH_DATA, описанной выше.

В классе CPatch также содержатся методы Patch( exeAddr ) и Unpatch( exeAddr ), смысл использования которых состоит в следующем. Иногда бывает необходимо «попасть» в какой-нибудь метод из EXE, чтобы проконтролировать передаваемые в него аргументы, сохранив их значения, скажем, в лог файл, но весь метод для этого переписывать и декомпилировать в наши планы пока не входит.

Приведём пример использования этих методов для изменения заголовка диалогового окна программы SimpleCalc.

«Пропатчим» оконную процедуру диалогового окна DialogProc таким образом, что при попадании в наш переписанный метод DialogProc в DLL мы каждый раз будем восстанавливать оригинальные байты в EXE методе с помощью _patch->Unpatch(), вызывать оригинальный метод и после этого проверять значение аргумента uMsg. Если оно будет равным WM_INITDIALOG, для нас это может означает, что можем менять его заголовок. При всех других значениях аргумента uMsg, перед выходом из нашего метода DialogProc мы снова будем меняем начальные байты оригинального метода EXE на команду перехода на наш метод в DLL с помощью _patch->Patch().

// 0x4012C0
INT_PTR CALLBACK DialogProc( HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
  _patch->Unpatch( 0x4012C0 );

  auto res = ( ( INT_PTR( CALLBACK* )( HWND, UINT, WPARAM, LPARAM ) )A( 0x4012C0 ) )( hDlg, uMsg, wParam, lParam );

  if( uMsg == WM_INITDIALOG )
  {
    ::SetWindowText( hDlg, "Simple Calc" );
  }
  else
  {
    _patch->Patch( 0x4012C0 );
  }

  return res;
}
PATCH( 0x4012C0, DialogProc )

Проверка работоспособности инжектируемой DLL.

После создания DLL, в которой описан класс Adder, метод Mul() и DialogProc() необходимо дополнительно объявить в ней один экспортируемый метод с любым именем. В нашем случае им будет пустой метод Setup(). В свойствах проекта в VS помещаем созданную DLL в ту же директорию, что и EXE файл.

Теперь настало время добавить в секцию импорта программы SimpleCalc созданную нами DLL и единственный экспортируемый из неё метод Setup(). Для этого можно воспользоваться утилитой CFF Explorer. Открываем в ней EXE файл SimpleCalc, в левой части окна выбираем пункт «Import Adder», в открывшемся окне нажимаем кнопку «Add» и выбираем наш DLL файл. После этого в окне «Exported Functions» должен отобразиться наш единственный экспортируемый метод Setup(), выделяем его мышкой и нажимаем кнопку «Import By Name» после чего он отобразится в соседнем справа окне «Imported Functions»:

Затем нажимаем кнопку «Rebuild Import Table» и перезаписываем исходный EXE файл через меню “File -> Save” или сохраняем его с новым именем “File -> Save As”.

Часто возникает соблазн внедрить свой код в чужой файл без какой бы то нибыло его модификации. Обычно любой EXE файл импортирует несколько системных DLL из которых, к тому же, используются далеко не все методы. При этом можно было бы создать свою DLL с тем же именем, скажем, kernel32.dll. Описать в ней прокси переходы для всех импортируемых из этой DLL методов, добавить свои и в итоге поместить её в тот же каталог рядом с EXE файлом. Но MS об этом тоже подумала и создала список так называемых Known DLLs, куда входят все системные, чтобы они не могли загружаться из того же каталога, что и EXE.

Существует способ обойти это ограничение, но в мои планы не входило создание вирусов, программ-шпионов, или подключение к уже запущенным процессам, поэтому модификация секции импорта EXE файла выглядит в данном случае абсолютно приемлемой и совершенно несложной операцией.

Теперь в VS в свойствах DLL проекта в разделе «Debugging» в пункте «Command» прописываем путь к модифицированному EXE файлу, ставим точки останова на начало методов Add, Mult, DialogProc и стартуем отладку. Сначала мы должны попасть в метод DialogProc, а при нажатии в окне программы на кнопку «=» – в соответствующий метод вычисления суммы или произведения. Убеждаемся, что патч программы был произведен успешно.

Вызов методов EXE файла из DLL.

Как правило, из метода, который мы переписали в своей DLL, вызывается несколько других, которые на данном этапе нас не интересуют, либо мы планируем перейти к их разбору позже.

Предположим, у нас есть метод sub_4037B0, из которого вызывается другой, по адресу 0x404800. Тогда, определив из дизассемблера соглашение о вызове этого метода и тип аргументов, можно будет вызвать его таким образом:

int sub_4037B0()
{
  HWND hDlg;

  ...

  // int __cdecl sub_404800( HWND hDlg );
  int res = ((int(__cdecl*)(HWND))0x404800)( hDlg );

  ...
}

Если же он вызывается из множества мест нашей DLL, но декомпилировать его пока в наши планы не входит, можно реализовать его таким образом:

int __cdecl sub_404800( HWND hDlg )
{
  return ((int(__cdecl*)(HWND))0x404800)( hDlg );
}

Для случая, если метод является членом класса, его вызов будет выглядеть так:

int ClassA::sub_4037B0()
{
  HWND hDlg;

  ...

  // int __thiscall ClassA::sub_404800( HWND hDlg );
  int res = ((int(__thiscall*)( ClassA*, HWND ))0x404800)( this, hDlg );

  ...
}

или, опять же:

int ClassA::sub_404800(( HWND hDlg )
{
  return ((int(__thiscall*)(ClassA*, HWND))0x404800)( this, hDlg );
}

Способ вызова методов с нестандартными для MSVC соглашениями о вызовах был описан выше на примере с __usercall Mult().

Доступ к данным EXE файла из DLL.

В случае, если метод сторонней программы, перехват которого производится, был написан с использованием классов и все данные, используемые в этом методе – члены этого (или другого) класса, то доступ к ним в своём переписанном методе сводится лишь к правильному описанию самого класса. Если все члены класса расположены в его описании строго на своих местах, то и доступ к ним будет осуществляться автоматически по их имени (как в классе Adder). Их значения также будут отображаться в отладчике VS через указатель this.
Но в случае использования глобальных переменных ситуация усложняется. Доступ к таким переменным можно получить лишь по их адресу в памяти основной программы, взятому из дизассемблера. В MSVC, к сожалению, невозможно объявить переменную по абсолютному адресу (даже без выделения для неё памяти), как это делается во многих Embedded системах, поэтому приходится объявлять глобальные переменные для указателей, например, таким образом:

#define _ptr1 ((int*)0x40BEF4)

а для переменных, используемых по значению, таким:

#define _val1 (*(int*)0x40BEF8)

Но, во-первых, при этом возникает огромное неудобство при отладке программы. Дело в том, что VS не отображает в отладчике значения макросов, объявленных с помощью директивы препроцессора #define. А во-вторых, что ещё более печально, для указателя, объявленного таким образом, не удаётся получить его адрес:

int** pptr1 = &_ptr1; // error C2101: '&' on constant

Можно объявить и инициализировать указатель так:

int* _ptr1 = (int*)0x40BEF4;

И это будет работать для отображения его значения в отладчике и для операции разадресации «*_ptr1», но как только мы попытаемся получить адрес этого указателя, им окажется адрес нашей локальной переменной _ptr1 в адресном пространстве DLL.

Применение оператора «placement new» тоже не даёт нужного результата, т.к. непосредственно переменную с нужным именем разместить по конкретному адресу не удаётся. Так, например, конструкция типа:

int* _ptr1 = new((int*)0x40BEF4) int(5);

действительно размещает объект типа int со значением 5 по адресу 0x40BEF4, но не переменную _ptr1 ! А она опять же находится в адресном пространстве DLL, что для нашей задачи равносильно определению:

int* _ptr1 = (int*)0x40BEF4;

Поэтому в качестве универсального метода пришлось прибегнуть к такой страшноватой конструкции, помещая переменную в структуру:

#define VAR(name,type,addr) 
  typedef struct { type var; } name##_; 
  auto name = (name##_*)(addr);

Здесь мы сначала объявляем новый тип структуры, который содержит в себе единственный элемент – нашу переменную с именем var. Затем создаём указатель на эту структуру с именем нашей переменной и инициализируем его адресом этой переменной из EXE файла.

Объявление глобальной переменной g_hMulRes в этом случае будет выглядеть так:

VAR( g_hMulRes, HWND, 0x41B36C )

 а обращение к ней так:

g_hMulRes->var

Несколько «ugly», но зато с этой переменной можно производить операции взятия адреса, разадресации и, что немаловажно, её значение будет отображаться в отладчике.

Буду весьма признателен, если кто-нибудь подсказажет более изящный способ решения этого вопроса.

Управление памятью.

При сборке EXE файла использовалась та или иная версия библиотеки C/C++ (например, CRT), в которой были определены операторы и методы для управления памятью «new», «delete», «malloc», «free» и другие. И эта версия может и скорее всего будет не совпадать с версией библиотеки используемой для линковки с DLL.

Если в каком-либо из переписанных из EXE в DLL методов применяется один из этих операторов, например «new» для выделения памяти, а для её освобождения будет соответственно использоваться оператор «delete», но уже в недрах EXE модуля, то при работе программы может возникнуть исключение. Поэтому, если в переписанном методе в DLL необходимо использовать отдельный оператор «new» или «delete», предпочтительнее вызывать его по соответствующему адресу из EXE модуля, определив его предварительно в дизассемблере.

Пример такого поведения содержится в проекте DLL в методе Adder::Add() для создания массива символов.

Ведение логов.

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

Для реализации такого примера в конструкторе метода Patch тестовой DLL создаётся и открывается на запись файл SimpleCalc.log, а затем в этот файл записываются производимые пользователем операции.

Заключение.

Описанный здесь способ неоднократно помог мне в самых различных ситуациях. С его помощью были исправлены баги и недочёты в десятках программных продуктах для моей частной и профессиональной деятельности и я по сей день прибегаю к его использованию. Он одинаково хорошо применим для работы с 32-х и 64-х разрядными приложениями и апробирован на различных версиях ОС Windows, так что его смело можно рекомендовать к практическому применению при минимальных трудозатратах на внедрение.

Спасибо за проявленный интерес!

Приложение.

Архивный файл с проектом программы SimpleCalc и тестовой DLL для VS.

Содержание

  • Способ 1: Самостоятельная загрузка и установка DLL
  • Способ 2: Установка Microsoft Visual Studio 2017
  • Вопросы и ответы

Запуск программы невозможен, так как на компьютере отсутствует ucrtbased.dll
Файл ucrtbased.dll относится к среде разработки Microsoft Visual Studio. Ошибки вида «Запуск программы невозможен, так как на компьютере отсутствует ucrtbased.dll» возникают из-за неправильно установленной Вижуал Студио или повреждения соответствующей библиотеки в системной папке. Сбой характерен для большинства актуальных версий Windows.

Если у вас не самый быстрый интернет или вы не хотите устанавливать Microsoft Visual Studio, можете сами скачать нужную библиотеку и установить ее в соответствующий вашей системе каталог, а затем перезагрузить компьютер.

Скачать ucrtbased.dll

Самостоятельная установка библиотеки ucrtbased.dll в системную директорию

Расположение этого каталога зависит от версии Windows, которая установлена на вашем ПК, так что изучите этот материал перед манипуляциями.

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

Способ 2: Установка Microsoft Visual Studio 2017

Один из самых простых методов восстановления ucrtbased.dll в системе – установка среды Майкрософт Вижуал Студио 2017. Для этого подойдет и бесплатный вариант, который называется Visual Studio Community 2017.

  1. Скачайте веб-установщик указанного пакета с официального сайта. Обратите внимание, что для совершения загрузки вам понадобится либо войти в свой аккаунт Microsoft, либо создать новый!

    Скачать Visual Studio Community 2017

  2. Скачать пакет установки Visual Studio

  3. Запустите инсталлятор. Примите лицензионное соглашение нажатием кнопки «Продолжить».
  4. Начало установки Visual Studio

  5. Подождите, пока утилита подгрузит устанавливаемые компоненты. Затем выберите желаемую директорию для установки и нажимайте «Установить».
  6. Приступить к установке Visual Studio

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

Закрыть программу поcле установки Visual Studio

Вместе с установленной средой в системе появится и библиотека ucrtbased.dll, что автоматически исправит проблемы с запуском софта, который требует этот файл.

Еще статьи по данной теме:

Помогла ли Вам статья?

  • Remove From My Forums
  • Question

  • I didn’t have this problem in Visual Studio 2015 and 2017. I always put my DLL files in one of the Executable Directories (Project>Properties>VC++ Directories>Executable Directories) and it worked fine.

    Now I’m using Visual Studio 2019 and whenever I try to run my project through Visual Studio, I get an error saying “The code execution cannot proceed because ______.dll was not found.”, even though the DLL file is in one of the Executable Directories.

    Is there any way to fix this? I don’t want to put DLL files in the same directory as the .exe, because I want to have a project template that takes care of everything, so I don’t have to manually copy DLL files every time I start a new project.

Добавить комментарий