summaryrefslogtreecommitdiff
path: root/src/shared/inject.cpp
blob: 9fea7fc8a4e3e1909876ea7aae67e76a580a2ab5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*
Copyright (C) 2012 Sebastian Herbord. All rights reserved.

This file is part of Mod Organizer.

Mod Organizer is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Mod Organizer is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Mod Organizer.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "inject.h"
/*#if defined UNICODE && !defined _UNICODE
#define _UNICODE 1
#endif*/
#include <wchar.h>
#include <cstdio>
#include <tchar.h>
#include <cstdlib>
#include <exception>
#include <stdexcept>
#include <string>
#include "windows_error.h"
#include "error_report.h"

namespace MOShared {


struct TParameters {
  char dllname[MAX_PATH];
  wchar_t profileName[101];
  char initstr[5];
  int  logLevel;
};


typedef HMODULE (WINAPI *TLoadLibraryType)(LPCTSTR);
typedef FARPROC (WINAPI *TGetProcAddressType)(HMODULE, LPCSTR);



void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel)
{
  // prepare parameters that are to be passed to the injected dll
  TParameters parameters;
  memset(&parameters, '\0', sizeof(TParameters));
  strncpy(parameters.dllname, dllname.c_str(), MAX_PATH - 1);
  wcsncpy(parameters.profileName, profileName.c_str(), 100);
  _snprintf(parameters.initstr, 5, "Init"); //this is the name of thie initialisation function we want to call in the target process

  HMODULE k32mod = ::LoadLibrary(__TEXT("kernel32.dll"));
  TLoadLibraryType loadLibraryFunc = NULL;
  TGetProcAddressType getProcAddressFunc = NULL;
  // ansi binaries
  if (k32mod != NULL) {
    loadLibraryFunc = reinterpret_cast<TLoadLibraryType>(::GetProcAddress(k32mod, "LoadLibraryA"));
    getProcAddressFunc = reinterpret_cast<TGetProcAddressType>(::GetProcAddress(k32mod, "GetProcAddress"));
    if ((loadLibraryFunc == NULL) || (getProcAddressFunc == NULL)) {
      throw windows_error("failed to determine address for required functions");
    }
  } else {
    throw windows_error("kernel32.dll not loaded??");
  }

  // allocate memory in the target process and write the parameter-block there
  LPVOID remoteMem = ::VirtualAllocEx(processHandle, NULL, sizeof(TParameters), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
  if (remoteMem == NULL) {
    throw windows_error("failed to allocate memory in target process");
  }
  SIZE_T written;
  if (!::WriteProcessMemory(processHandle, remoteMem, &parameters, sizeof(TParameters), &written) ||
      (written != sizeof(TParameters))) {
    throw windows_error("failed to write parameters to target process");
  }

  // now for the interesting part: write a stub into the target process that is run before any code of the original binary. This code will load
  // our injected dll into the process and run its Init-function which takes the profile name as its parameter
  // obviously this code is ultra-hacky

  // construct the stub in beautiful assembler with placeholders
  BYTE stubLocal[] = { 0x60,                         // PUSHAD
                       0xB8, 0xBA, 0xAD, 0xF0, 0x0D, // MOV EAX, imm32 (LoadLibrary)
                       0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 (dllname)
                       0xFF, 0xD0,                   // CALL EAX (=LoadLibrary, leaves module handle of our dll in eax)
                       0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 ("Init")
                       0x50,                         // PUSH EAX
                       0xB8, 0xBA, 0xAD, 0xF0, 0x0D, // MOVE EAX, imm32 (GetProcAddress)
                       0xFF, 0xD0,                   // CALL EAX (GetProcAddress)
                       0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 (profile name)
                       0x68, 0xBA, 0xAD, 0xF0, 0x0D, // PUSH imm32 (log level)
                       0xFF, 0xD0,                   // CALL EAX (=InitFunction)
                       0x58,                         // POP EAX (init function is defined cdecl)
                       0x58,                         // POP EAX (init function is defined cdecl)
                       0x61,                         // POPAD
                       0xE9, 0xBA, 0xAD, 0xF0, 0x0D  // JMP near, relative (=original entry point)
                    };

  // reserve memory for the stub
  PBYTE stubRemote = reinterpret_cast<PBYTE>(::VirtualAllocEx(processHandle, NULL, sizeof(stubLocal), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE));
  if (stubRemote == NULL) {
    throw windows_error("failed to allocate memory for stub");
  }
  TParameters *remoteParams = reinterpret_cast<TParameters*>(remoteMem);
  // determine the remote addresses of each of the parameters
//  ULONG remoteDLLName = reinterpret_cast<ULONG>(remoteMem);
//  ULONG remoteInitString = remoteDLLName + MAX_PATH * sizeof(char);

  // dizzy yet? we still have to calculate the entry point as an address relative to our stub
  ULONG entryPoint = 0;

  CONTEXT threadContext;
  threadContext.ContextFlags = CONTEXT_CONTROL;
  if (::GetThreadContext(threadHandle, &threadContext) == 0) {
    throw windows_error("failed to access thread context. Please note that Mod Organizer does not support 64bit binaries!");
  } else {
    entryPoint = threadContext.Eip - (reinterpret_cast<ULONG>(stubRemote) + sizeof(stubLocal));
  }
  // now replace each baadf00d by the correct value. The pointers need to be the pointers in the REMOTE memory of course, that's why we copied them there
  *(PULONG)&stubLocal[2]  = reinterpret_cast<ULONG>(*loadLibraryFunc);
  *(PULONG)&stubLocal[7]  = reinterpret_cast<ULONG>(remoteParams->dllname);
  *(PULONG)&stubLocal[14] = reinterpret_cast<ULONG>(remoteParams->initstr);
  *(PULONG)&stubLocal[20] = reinterpret_cast<ULONG>(*getProcAddressFunc);
  *(PULONG)&stubLocal[27] = reinterpret_cast<ULONG>(remoteParams->profileName);
  *(PULONG)&stubLocal[32] = logLevel;
  *(PULONG)&stubLocal[42] = entryPoint;
  // almost there. copy stub to target process
  if (!::WriteProcessMemory(processHandle, stubRemote, reinterpret_cast<LPCVOID>(stubLocal), sizeof(stubLocal), &written) ||
      (written != sizeof(stubLocal))) {
    throw windows_error("failed to write stub to target process");
  }
  // finally, make the stub the new next thing for the thread to execute
  threadContext.Eip = (ULONG)stubRemote;
  if (::SetThreadContext(threadHandle, &threadContext) == 0) {
    throw windows_error("failed to overwrite thread context");
  }
}

} // namespace MOShared