├── Lynx ├── Lynx.sln ├── Lynx │ ├── Icons-Land-Medical-Equipment-Syringe-Full.ico │ ├── Lynx.vcxproj │ ├── Lynx.vcxproj.filters │ ├── Lynx.vcxproj.user │ ├── Resource.rc │ ├── console.cpp │ ├── console.h │ ├── gui.cpp │ ├── gui.h │ ├── helper.cpp │ ├── helper.h │ ├── injector.cpp │ ├── injector.h │ ├── main.cpp │ └── resource.h └── TestDLL │ ├── TestDLL.vcxproj │ ├── TestDLL.vcxproj.filters │ └── main.cpp └── README.md /Lynx/Lynx.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Lynx", "Lynx\Lynx.vcxproj", "{67E59F8D-BF37-4F87-8DEA-C9F619BE17BE}" 7 | ProjectSection(ProjectDependencies) = postProject 8 | {8738CC4F-FFC5-4DD4-9810-7D31E2C5ADB4} = {8738CC4F-FFC5-4DD4-9810-7D31E2C5ADB4} 9 | EndProjectSection 10 | EndProject 11 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestDLL", "TestDLL\TestDLL.vcxproj", "{8738CC4F-FFC5-4DD4-9810-7D31E2C5ADB4}" 12 | EndProject 13 | Global 14 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 15 | Debug|x64 = Debug|x64 16 | Debug|x86 = Debug|x86 17 | Release|x64 = Release|x64 18 | Release|x86 = Release|x86 19 | EndGlobalSection 20 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 21 | {67E59F8D-BF37-4F87-8DEA-C9F619BE17BE}.Debug|x64.ActiveCfg = Debug|x64 22 | {67E59F8D-BF37-4F87-8DEA-C9F619BE17BE}.Debug|x64.Build.0 = Debug|x64 23 | {67E59F8D-BF37-4F87-8DEA-C9F619BE17BE}.Debug|x86.ActiveCfg = Debug|Win32 24 | {67E59F8D-BF37-4F87-8DEA-C9F619BE17BE}.Debug|x86.Build.0 = Debug|Win32 25 | {67E59F8D-BF37-4F87-8DEA-C9F619BE17BE}.Release|x64.ActiveCfg = Release|x64 26 | {67E59F8D-BF37-4F87-8DEA-C9F619BE17BE}.Release|x64.Build.0 = Release|x64 27 | {67E59F8D-BF37-4F87-8DEA-C9F619BE17BE}.Release|x86.ActiveCfg = Release|Win32 28 | {67E59F8D-BF37-4F87-8DEA-C9F619BE17BE}.Release|x86.Build.0 = Release|Win32 29 | {8738CC4F-FFC5-4DD4-9810-7D31E2C5ADB4}.Debug|x64.ActiveCfg = Debug|x64 30 | {8738CC4F-FFC5-4DD4-9810-7D31E2C5ADB4}.Debug|x64.Build.0 = Debug|x64 31 | {8738CC4F-FFC5-4DD4-9810-7D31E2C5ADB4}.Debug|x86.ActiveCfg = Release|Win32 32 | {8738CC4F-FFC5-4DD4-9810-7D31E2C5ADB4}.Debug|x86.Build.0 = Release|Win32 33 | {8738CC4F-FFC5-4DD4-9810-7D31E2C5ADB4}.Release|x64.ActiveCfg = Release|x64 34 | {8738CC4F-FFC5-4DD4-9810-7D31E2C5ADB4}.Release|x64.Build.0 = Release|x64 35 | {8738CC4F-FFC5-4DD4-9810-7D31E2C5ADB4}.Release|x86.ActiveCfg = Release|Win32 36 | {8738CC4F-FFC5-4DD4-9810-7D31E2C5ADB4}.Release|x86.Build.0 = Release|Win32 37 | EndGlobalSection 38 | GlobalSection(SolutionProperties) = preSolution 39 | HideSolutionNode = FALSE 40 | EndGlobalSection 41 | EndGlobal 42 | -------------------------------------------------------------------------------- /Lynx/Lynx/Icons-Land-Medical-Equipment-Syringe-Full.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NtRaiseHardError/Lynx/8d46067be9ea36ff0644d07b99e004baa9f68207/Lynx/Lynx/Icons-Land-Medical-Equipment-Syringe-Full.ico -------------------------------------------------------------------------------- /Lynx/Lynx/Lynx.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {67E59F8D-BF37-4F87-8DEA-C9F619BE17BE} 23 | Win32Proj 24 | Lynx 25 | 8.1 26 | 27 | 28 | 29 | Application 30 | true 31 | v140 32 | Unicode 33 | 34 | 35 | Application 36 | false 37 | v140_xp 38 | true 39 | Unicode 40 | 41 | 42 | Application 43 | true 44 | v140 45 | Unicode 46 | 47 | 48 | Application 49 | false 50 | v140 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | true 74 | 75 | 76 | true 77 | 78 | 79 | false 80 | 81 | 82 | false 83 | 84 | 85 | 86 | 87 | 88 | Level3 89 | Disabled 90 | WIN32;_DEBUG;_WINDOWS;%(PreprocessorDefinitions) 91 | 92 | 93 | Windows 94 | true 95 | 96 | 97 | 98 | 99 | 100 | 101 | Level3 102 | Disabled 103 | _DEBUG;_WINDOWS;%(PreprocessorDefinitions) 104 | 105 | 106 | Windows 107 | true 108 | 109 | 110 | 111 | 112 | Level3 113 | 114 | 115 | MaxSpeed 116 | true 117 | true 118 | WIN32;NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 119 | MultiThreaded 120 | 121 | 122 | Windows 123 | true 124 | true 125 | false 126 | 127 | 128 | 129 | 130 | Level3 131 | 132 | 133 | MaxSpeed 134 | true 135 | true 136 | NDEBUG;_WINDOWS;%(PreprocessorDefinitions) 137 | 138 | 139 | Windows 140 | true 141 | true 142 | true 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /Lynx/Lynx/Lynx.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | 35 | 36 | Header Files 37 | 38 | 39 | Header Files 40 | 41 | 42 | Header Files 43 | 44 | 45 | Header Files 46 | 47 | 48 | Header Files 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | Resource Files 57 | 58 | 59 | -------------------------------------------------------------------------------- /Lynx/Lynx/Lynx.vcxproj.user: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | WindowsLocalDebugger 7 | 8 | 9 | 10 | 11 | WindowsLocalDebugger 12 | 13 | 14 | -p explorer.exe 15 | WindowsLocalDebugger 16 | 17 | -------------------------------------------------------------------------------- /Lynx/Lynx/Resource.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NtRaiseHardError/Lynx/8d46067be9ea36ff0644d07b99e004baa9f68207/Lynx/Lynx/Resource.rc -------------------------------------------------------------------------------- /Lynx/Lynx/console.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "console.h" 8 | #include "helper.h" 9 | #include "injector.h" 10 | 11 | #define CONSOLE_WINDOW_TITLE L"Lynx DLL Injector v1.0" 12 | 13 | struct _params { 14 | std::wstring lpProcessName; 15 | std::wstring lpUpdatePayload; 16 | DWORD dwOptions; 17 | }; 18 | 19 | VOID CreateConsole() { 20 | ::AllocConsole(); 21 | ::AttachConsole(::GetCurrentProcessId()); 22 | ::SetConsoleTitle(CONSOLE_WINDOW_TITLE); 23 | freopen("CON", "w", stdout); 24 | freopen("CON", "w", stderr); 25 | } 26 | 27 | VOID DestroyConsole() { 28 | ::FreeConsole(); 29 | } 30 | 31 | VOID PauseConsole() { 32 | WCHAR szTmp[2]; 33 | DWORD dwRead = 0; 34 | 35 | //::WriteConsole 36 | std::wcerr << L"Press Enter to continue..."; 37 | ::ReadConsole(::GetStdHandle(STD_INPUT_HANDLE), szTmp, 1, &dwRead, NULL); 38 | } 39 | 40 | /* 41 | * Console syntax: argv[0] -p | -u -o [--obfuscate] 42 | */ 43 | VOID PrintUsage(LPCWSTR self) { 44 | std::wcerr << self << L" -p | -u [--obfuscate]\n"; 45 | PauseConsole(); 46 | ExitProcess(1); 47 | } 48 | 49 | VOID ExecutePayload(Injector *i) { 50 | // check if module has DLL payload 51 | Debug(L"Checking for existing payload...\n"); 52 | if (i->HasPayload()) { 53 | // load DLL payload 54 | Debug(L"Loading payload...\n"); 55 | if (i->LoadFromResource()) { 56 | // inject into target process 57 | Debug(L"Injecting payload...\n"); 58 | if (i->InjectPayload()) { 59 | // execute DLL 60 | Debug(L"Executing payload...\n"); 61 | INT nExitCode = i->ExecuteDll(true, true); 62 | Debug(L"Thread returned with exit code: %d\n", nExitCode); 63 | } else 64 | Debug(L"Failed to execute payload\n"); 65 | } else 66 | Debug(L"Failed to locate payload\n"); 67 | } else 68 | Debug(L"Failed to locate payload: %lu\n", GetLastError()); 69 | 70 | PauseConsole(); 71 | } 72 | 73 | VOID UpdatePayload(Injector *i, std::wstring lpFileName) { 74 | // create new file 75 | 76 | // read file 77 | 78 | // load from disk 79 | 80 | // update file resources 81 | 82 | // clean up 83 | } 84 | 85 | int ConsoleMain(int argc, wchar_t *argv[]) { 86 | // get a console window 87 | CreateConsole(); 88 | 89 | // check correct number of parameters (3) 90 | if (argc < 3) 91 | PrintUsage(argv[0]); 92 | 93 | struct _params *p = new struct _params; 94 | 95 | // parse command line 96 | for (int i = 0; i < argc; i++) { 97 | // target process name 98 | if (!wcsicmp(argv[i], L"-p")) 99 | p->lpProcessName = std::wstring(argv[i + 1]); 100 | 101 | // update DLL payload resource 102 | else if (!wcsicmp(argv[i], L"-u")) 103 | p->lpUpdatePayload = std::wstring(argv[i + 1]); 104 | 105 | // obfuscate updated DLL payload 106 | else if (!wcsicmp(argv[i], L"--obfuscate")) 107 | p->dwOptions |= OPTIONS_OBFUSCATE; 108 | } 109 | 110 | // reject if both are either empty or not empty 111 | if ((p->lpProcessName.empty() && p->lpUpdatePayload.empty()) || 112 | !p->lpProcessName.empty() && !p->lpUpdatePayload.empty()) 113 | PrintUsage(argv[0]); 114 | 115 | 116 | // if target process 117 | if (!p->lpProcessName.empty()) { 118 | Debug(L"You've selected to inject into process: %s\n", p->lpProcessName.c_str()); 119 | // create new injector object 120 | Injector *i = new Injector(p->lpProcessName); 121 | ExecutePayload(i); 122 | delete i; 123 | 124 | // else if update payload 125 | } else if (!p->lpUpdatePayload.empty()) { 126 | Debug(L"You've selected to update your payload with %s\n", p->lpUpdatePayload.c_str()); 127 | Injector *i = new Injector(p->lpUpdatePayload, p->dwOptions); 128 | UpdatePayload(i, p->lpUpdatePayload); 129 | delete i; 130 | } 131 | 132 | // clean up 133 | delete p; 134 | 135 | // free console window 136 | DestroyConsole(); 137 | 138 | return 0; 139 | } -------------------------------------------------------------------------------- /Lynx/Lynx/console.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef __CONSOLE_H__ 3 | #define __CONSOLE_H__ 4 | 5 | int ConsoleMain(int argc, wchar_t *argv[]); 6 | 7 | #endif // !__CONSOLE_H__ 8 | -------------------------------------------------------------------------------- /Lynx/Lynx/gui.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "gui.h" 7 | #include "helper.h" 8 | #include "injector.h" 9 | #include "resource.h" 10 | 11 | #pragma comment(lib, "ComCtl32.lib") 12 | 13 | #define RED RGB(0xFF, 0, 0) 14 | #define GREEN RGB(0, 0xFF, 0) 15 | 16 | void OutputString(HWND hDlg, LPCWSTR fmt, ...) { 17 | va_list args; 18 | va_start(args, fmt); 19 | 20 | WCHAR szOutput[MAX_PATH]; 21 | vswprintf(szOutput, fmt, args); 22 | ::SetDlgItemText(hDlg, IDC_STATIC, szOutput); 23 | 24 | va_end(args); 25 | } 26 | 27 | void UpdateProgressBar(HWND hDlg, int nValue, bool bError) { 28 | if (bError) 29 | ::SendMessage(::GetDlgItem(hDlg, IDC_PROGRESS1), PBM_SETBARCOLOR, 0, static_cast(RED)); 30 | else 31 | ::SendMessage(::GetDlgItem(hDlg, IDC_PROGRESS1), PBM_SETBARCOLOR, 0, static_cast(GREEN)); 32 | 33 | // -1 means keep same value 34 | if (nValue >= 0) 35 | ::SendMessage(::GetDlgItem(hDlg, IDC_PROGRESS1), PBM_SETPOS, static_cast(nValue), 0); 36 | } 37 | 38 | bool SaveFile(HWND hDlg, std::wstring& szSaveFileName) { 39 | LPOPENFILENAME lpOfn = new OPENFILENAME; 40 | WCHAR szFileName[MAX_PATH] = L""; 41 | 42 | ::ZeroMemory(lpOfn, sizeof(OPENFILENAME)); 43 | 44 | lpOfn->lStructSize = sizeof(OPENFILENAME); 45 | lpOfn->hwndOwner = hDlg; 46 | lpOfn->lpstrFilter = L"Executable Files (*.exe)\0*.txt\0All Files (*.*)\0*.*\0"; 47 | lpOfn->lpstrFile = szFileName; 48 | lpOfn->nMaxFile = MAX_PATH; 49 | lpOfn->Flags = OFN_EXPLORER | OFN_HIDEREADONLY | OFN_OVERWRITEPROMPT | OFN_CREATEPROMPT; 50 | lpOfn->lpstrDefExt = L"exe"; 51 | 52 | if (!::GetSaveFileName(lpOfn)) 53 | return false; 54 | 55 | szSaveFileName = std::wstring(szFileName); 56 | 57 | delete lpOfn; 58 | 59 | return true; 60 | } 61 | 62 | bool OpenFile(HWND hDlg, std::wstring& szOpenFileName) { 63 | LPOPENFILENAME lpOfn = new OPENFILENAME; 64 | WCHAR szFileName[MAX_PATH] = L""; 65 | 66 | ZeroMemory(lpOfn, sizeof(OPENFILENAME)); 67 | 68 | lpOfn->lStructSize = sizeof(OPENFILENAME); 69 | lpOfn->hwndOwner = hDlg; 70 | lpOfn->lpstrFile = szFileName; 71 | lpOfn->lpstrFile[0] = '\0'; 72 | lpOfn->nMaxFile = MAX_PATH; 73 | lpOfn->lpstrFilter = L"DLL Files (*.dll)\0*.txt\0All Files (*.*)\0*.*\0"; 74 | lpOfn->nFilterIndex = 1; 75 | lpOfn->lpstrFileTitle = NULL; 76 | lpOfn->nMaxFileTitle = 0; 77 | lpOfn->lpstrInitialDir = NULL; 78 | lpOfn->Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST; 79 | 80 | if (!::GetOpenFileName(lpOfn)) 81 | return false; 82 | 83 | szOpenFileName = std::wstring(szFileName); 84 | 85 | delete lpOfn; 86 | 87 | return true; 88 | } 89 | 90 | bool GetProcesses(HWND hDlg) { 91 | // clear list entries 92 | ::SendMessage(::GetDlgItem(hDlg, IDC_LIST1), LB_RESETCONTENT, 0, 0); 93 | 94 | PROCESSENTRY32 pe32; 95 | pe32.dwSize = sizeof(PROCESSENTRY32); 96 | 97 | HANDLE hSnapshot = ::CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); 98 | 99 | if (::Process32First(hSnapshot, &pe32)) { 100 | while (::Process32Next(hSnapshot, &pe32)) { 101 | BOOL bWow64 = FALSE; 102 | HANDLE hProcess = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, pe32.th32ProcessID); 103 | if (::IsWow64Process(hProcess, &bWow64) && bWow64 == TRUE) 104 | ::SendMessage(::GetDlgItem(hDlg, IDC_LIST1), LB_ADDSTRING, 0, reinterpret_cast(pe32.szExeFile)); 105 | ::CloseHandle(hProcess); 106 | } 107 | } else 108 | return ::CloseHandle(hSnapshot), false; 109 | 110 | ::CloseHandle(hSnapshot); 111 | 112 | return true; 113 | } 114 | 115 | bool UpdatePayload(HWND hDlg) { 116 | OutputString(hDlg, L"Feature is currently unavailable"); 117 | 118 | return false; 119 | } 120 | 121 | std::wstring GetProcessName(HWND hDlg) { 122 | // get item index from process list 123 | int nIndex = ::SendMessage(::GetDlgItem(hDlg, IDC_LIST1), LB_GETCURSEL, 0, 0); 124 | int nTextLen = ::SendMessage(::GetDlgItem(hDlg, IDC_LIST1), LB_GETTEXTLEN, static_cast(nIndex), 0); 125 | 126 | LPWSTR szText = new WCHAR[nTextLen + 1]; 127 | ZeroMemory(szText, (nTextLen + 1) * sizeof(WCHAR)); 128 | ::SendMessage(::GetDlgItem(hDlg, IDC_LIST1), LB_GETTEXT, static_cast(nIndex), reinterpret_cast(szText)); 129 | std::wstring szProcessName(szText); 130 | 131 | delete[] szText; 132 | 133 | return szProcessName; 134 | } 135 | 136 | bool InjectPayload(HWND hDlg, std::wstring& szProcessName) { 137 | Injector *i = new Injector(szProcessName); 138 | // check if module has DLL payload 139 | OutputString(hDlg, L"Checking for existing payload...\n"); 140 | if (i->HasPayload()) { 141 | UpdateProgressBar(hDlg, 33, false); 142 | // load DLL payload 143 | OutputString(hDlg, L"Loading payload...\n"); 144 | if (i->LoadFromResource()) { 145 | UpdateProgressBar(hDlg, 66, false); 146 | // inject into target process 147 | OutputString(hDlg, L"Injecting payload...\n"); 148 | if (i->InjectPayload()) { 149 | UpdateProgressBar(hDlg, 100, false); 150 | // execute DLL 151 | OutputString(hDlg, L"Executing payload...\n"); 152 | INT nExitCode = i->ExecuteDll(false, true); 153 | // OutputString(hDlg, L"Thread returned with exit code: %d\n", nExitCode); 154 | } else { 155 | OutputString(hDlg, L"Failed to execute payload: %lu\n", GetLastError()); 156 | UpdateProgressBar(hDlg, -1, true); 157 | } 158 | } else { 159 | OutputString(hDlg, L"Failed to locate payload:%lu\n", GetLastError()); 160 | UpdateProgressBar(hDlg, -1, true); 161 | } 162 | } else { 163 | OutputString(hDlg, L"Failed to locate payload: %lu\n", GetLastError()); 164 | UpdateProgressBar(hDlg, -1, true); 165 | } 166 | 167 | return true; 168 | } 169 | 170 | INT_PTR CALLBACK DialogProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { 171 | switch (uMsg) { 172 | case WM_INITDIALOG: 173 | GetProcesses(hDlg); 174 | OutputString(hDlg, L"Ready."); 175 | return TRUE; 176 | case WM_COMMAND: 177 | switch (LOWORD(wParam)) { 178 | case IDC_REFRESH: 179 | GetProcesses(hDlg); 180 | ::EnableWindow(::GetDlgItem(hDlg, IDC_INJECT), FALSE); 181 | return TRUE; 182 | case IDC_UPDATE: 183 | UpdatePayload(hDlg); 184 | return TRUE; 185 | case IDC_ABOUT: 186 | 187 | return TRUE; 188 | case IDC_INJECT: 189 | InjectPayload(hDlg, GetProcessName(hDlg)); 190 | return TRUE; 191 | case IDC_LIST1: 192 | switch (HIWORD(wParam)) { 193 | case LBN_SELCHANGE: 194 | ::EnableWindow(::GetDlgItem(hDlg, IDC_INJECT), TRUE); 195 | break; 196 | case LBN_SELCANCEL: 197 | ::EnableWindow(::GetDlgItem(hDlg, IDC_INJECT), FALSE); 198 | break; 199 | } 200 | return TRUE; 201 | } 202 | break; 203 | 204 | case WM_CLOSE: 205 | ::DestroyWindow(hDlg); 206 | return TRUE; 207 | 208 | case WM_DESTROY: 209 | ::PostQuitMessage(0); 210 | return TRUE; 211 | } 212 | 213 | return FALSE; 214 | } 215 | 216 | int GuiMain(HINSTANCE hInstance) { 217 | HWND hDlg = ::CreateDialogParam(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), 0, DialogProc, 0); 218 | ::ShowWindow(hDlg, SW_SHOW); 219 | 220 | MSG msg; 221 | BOOL ret; 222 | while ((ret = ::GetMessage(&msg, 0, 0, 0)) != 0) { 223 | if (ret == -1) 224 | return -1; 225 | 226 | if (!IsDialogMessage(hDlg, &msg)) { 227 | ::TranslateMessage(&msg); 228 | ::DispatchMessage(&msg); 229 | } 230 | } 231 | 232 | return 0; 233 | } 234 | -------------------------------------------------------------------------------- /Lynx/Lynx/gui.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef __GUI_H__ 3 | #define __GUI_H__ 4 | 5 | int GuiMain(HINSTANCE hInstance); 6 | 7 | #endif // !__GUI_H__ 8 | -------------------------------------------------------------------------------- /Lynx/Lynx/helper.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "helper.h" 5 | 6 | //VOID Debug(LPCTSTR fmt, ...) { 7 | // va_list args; 8 | // va_start(args, fmt); 9 | // 10 | //#ifdef _UNICODE 11 | // vwprintf(fmt, args); 12 | //#else 13 | // vprintf(fmt, args); 14 | //#endif // _UNICODE 15 | // 16 | // va_end(args); 17 | //} 18 | 19 | VOID DebugW(LPCWSTR fmt, ...) { 20 | va_list args; 21 | va_start(args, fmt); 22 | 23 | DWORD dwWrite = 0; 24 | WCHAR szOutput[MAX_PATH]; 25 | vswprintf(szOutput, fmt, args); 26 | ::WriteConsole(::GetStdHandle(STD_OUTPUT_HANDLE), szOutput, wcslen(szOutput), &dwWrite, NULL); 27 | 28 | va_end(args); 29 | } 30 | 31 | VOID DebugA(LPCSTR fmt, ...) { 32 | va_list args; 33 | va_start(args, fmt); 34 | 35 | vprintf(fmt, args); 36 | 37 | va_end(args); 38 | } -------------------------------------------------------------------------------- /Lynx/Lynx/helper.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef __HELPER_H__ 3 | #define __HELPER_H__ 4 | 5 | #ifdef _UNICODE 6 | #define Debug DebugW 7 | #else 8 | #define Debug DebugA 9 | #endif // _UNICODE 10 | 11 | 12 | //VOID Debug(LPCTSTR fmt, ...); 13 | VOID DebugW(LPCWSTR fmt, ...); 14 | VOID DebugA(LPCSTR fmt, ...); 15 | 16 | #endif // !__HELPER_H__ 17 | -------------------------------------------------------------------------------- /Lynx/Lynx/injector.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "helper.h" 5 | #include "injector.h" 6 | #include "resource.h" 7 | 8 | bool Injector::GetProcess() { 9 | PROCESSENTRY32 pe32; 10 | pe32.dwSize = sizeof(PROCESSENTRY32); 11 | 12 | HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); 13 | 14 | if (::Process32First(hSnapshot, &pe32)) { 15 | while (::Process32Next(hSnapshot, &pe32)) { 16 | if (wcsicmp(pe32.szExeFile, this->szProcessName.c_str()) == 0) { 17 | HANDLE hProcess = ::OpenProcess(PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_VM_OPERATION | PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION, FALSE, pe32.th32ProcessID); 18 | ::CloseHandle(hSnapshot); 19 | this->payload->hProcess = hProcess; 20 | return true; 21 | } 22 | } 23 | } else 24 | return ::CloseHandle(hSnapshot), false; 25 | 26 | return false; 27 | } 28 | 29 | bool Injector::MemoryMapPayload(LPVOID lpPayload) { 30 | // get DOS header 31 | PIMAGE_DOS_HEADER pidh = reinterpret_cast(lpPayload); 32 | // get NT headers 33 | PIMAGE_NT_HEADERS pinh = reinterpret_cast(reinterpret_cast(lpPayload) + pidh->e_lfanew); 34 | 35 | HANDLE hMapping = ::CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, 0, pinh->OptionalHeader.SizeOfImage, NULL); 36 | if (hMapping) { 37 | LPVOID lpMapping = ::MapViewOfFile(hMapping, FILE_MAP_WRITE, 0, 0, 0); 38 | if (lpMapping) { 39 | // map payload to memory 40 | // copy headers 41 | ::CopyMemory(lpMapping, lpPayload, pinh->OptionalHeader.SizeOfHeaders); 42 | // copy sections 43 | for (int i = 0; i < pinh->FileHeader.NumberOfSections; i++) { 44 | PIMAGE_SECTION_HEADER pish = reinterpret_cast(reinterpret_cast(lpPayload) + pidh->e_lfanew + sizeof(IMAGE_NT_HEADERS) + sizeof(IMAGE_SECTION_HEADER) * i); 45 | ::CopyMemory(reinterpret_cast(reinterpret_cast(lpMapping) + pish->VirtualAddress), reinterpret_cast(reinterpret_cast(lpPayload) + pish->PointerToRawData), pish->SizeOfRawData); 46 | } 47 | this->vPayloadData = std::vector(reinterpret_cast(lpMapping), reinterpret_cast(lpMapping) + pinh->OptionalHeader.SizeOfImage); 48 | ::UnmapViewOfFile(lpMapping); 49 | ::CloseHandle(hMapping); 50 | return true; 51 | } 52 | ::CloseHandle(hMapping); 53 | } 54 | 55 | return false; 56 | } 57 | 58 | /* 59 | * Walk the relocation table and fix the location 60 | * of data with the delta offset 61 | * https://stackoverflow.com/questions/34086866/loading-an-executable-into-current-processs-memory-then-executing-it 62 | */ 63 | bool Injector::BaseRelocate(LPVOID lpBaseAddress, PIMAGE_NT_HEADERS pinh, DWORD dwDelta) { 64 | IMAGE_BASE_RELOCATION *r = reinterpret_cast(reinterpret_cast(lpBaseAddress) + pinh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress); //The address of the first I_B_R struct 65 | IMAGE_BASE_RELOCATION *r_end = reinterpret_cast(reinterpret_cast(r) + pinh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size - sizeof(IMAGE_BASE_RELOCATION)); //The addr of the last 66 | for (; r < r_end; r = reinterpret_cast(reinterpret_cast(r) + r->SizeOfBlock)) { 67 | WORD *reloc_item = reinterpret_cast(r + 1); 68 | DWORD num_items = (r->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); 69 | 70 | for (DWORD i = 0; i < num_items; ++i, ++reloc_item) { 71 | switch (*reloc_item >> 12) { 72 | case IMAGE_REL_BASED_ABSOLUTE: 73 | break; 74 | case IMAGE_REL_BASED_HIGHLOW: 75 | *(DWORD_PTR *)(reinterpret_cast(lpBaseAddress) + r->VirtualAddress + (*reloc_item & 0xFFF)) += dwDelta; 76 | break; 77 | default: 78 | return false; 79 | } 80 | } 81 | } 82 | 83 | return true; 84 | } 85 | 86 | /* 87 | * Walk the import table and fix the addresses 88 | */ 89 | bool Injector::RebuildImportTable(LPVOID lpBaseAddress, PIMAGE_NT_HEADERS pinh) { 90 | // parse import table if size != 0 91 | if (pinh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].Size) { 92 | // https://stackoverflow.com/questions/34086866/loading-an-executable-into-current-processs-memory-then-executing-it 93 | PIMAGE_IMPORT_DESCRIPTOR pImportDescriptor = reinterpret_cast(reinterpret_cast(lpBaseAddress) + pinh->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); 94 | 95 | // Walk until you reached an empty IMAGE_IMPORT_DESCRIPTOR 96 | while (pImportDescriptor->Name != NULL) { 97 | // get the name of each DLL 98 | LPSTR lpLibrary = reinterpret_cast(reinterpret_cast(lpBaseAddress) + pImportDescriptor->Name); 99 | 100 | HMODULE hLibModule = ::LoadLibraryA(lpLibrary); 101 | 102 | PIMAGE_THUNK_DATA nameRef = reinterpret_cast(reinterpret_cast(lpBaseAddress) + pImportDescriptor->Characteristics); 103 | PIMAGE_THUNK_DATA symbolRef = reinterpret_cast(reinterpret_cast(lpBaseAddress) + pImportDescriptor->FirstThunk); 104 | PIMAGE_THUNK_DATA lpThunk = reinterpret_cast(reinterpret_cast(lpBaseAddress) + pImportDescriptor->FirstThunk); 105 | for (; nameRef->u1.AddressOfData; nameRef++, symbolRef++, lpThunk++) { 106 | // fix addresses 107 | // check if import by ordinal 108 | if (nameRef->u1.AddressOfData & IMAGE_ORDINAL_FLAG) 109 | *(FARPROC *)lpThunk = ::GetProcAddress(hLibModule, MAKEINTRESOURCEA(nameRef->u1.AddressOfData)); 110 | else { 111 | PIMAGE_IMPORT_BY_NAME thunkData = reinterpret_cast(reinterpret_cast(lpBaseAddress) + nameRef->u1.AddressOfData); 112 | *(FARPROC *)lpThunk = ::GetProcAddress(hLibModule, reinterpret_cast(&thunkData->Name)); 113 | } 114 | } 115 | ::FreeLibrary(hLibModule); 116 | // advance to next IMAGE_IMPORT_DESCRIPTOR 117 | pImportDescriptor++; 118 | } 119 | } 120 | 121 | return true; 122 | } 123 | 124 | //bool Injector::ParseExportTable(HANDLE hProcess) { 125 | // return false; 126 | //} 127 | 128 | Injector::Injector(std::wstring szProcessName) { 129 | this->bUpdate = false; 130 | this->szProcessName = std::wstring(szProcessName); 131 | this->payload = new struct _payload; 132 | } 133 | 134 | Injector::Injector(std::wstring szFileName, DWORD dwOptions) { 135 | this->bUpdate = true; 136 | this->szFileName = std::wstring(szFileName); 137 | this->dwOptions = dwOptions; 138 | } 139 | 140 | Injector::~Injector() { 141 | ::CloseHandle(this->payload->hProcess); 142 | delete this->payload; 143 | } 144 | 145 | BOOL CALLBACK EnumResNameProc(HMODULE hModule, LPCWSTR lpszType, LPWSTR lpszName, LONG_PTR lParam) { 146 | // TODO: CHECK RESOURCE NAME FOR DLL PAYLOAD 147 | HRSRC *h = reinterpret_cast(lParam); 148 | HRSRC hRsrc = ::FindResource(hModule, lpszName, lpszType); 149 | if (!hRsrc) return TRUE; 150 | // if found, stop enumerating 151 | else { 152 | *h = hRsrc; 153 | return FALSE; 154 | } 155 | 156 | return TRUE; 157 | } 158 | 159 | bool Injector::HasPayload() { 160 | // get own module 161 | HMODULE hModule = ::GetModuleHandle(NULL); 162 | if (!hModule) return false; 163 | 164 | // enumerate resources and select raw data 165 | // result variable 166 | HRSRC hRsrc = NULL; 167 | if (!::EnumResourceNames(hModule, L"PAYLOAD", EnumResNameProc, reinterpret_cast(&hRsrc)) && GetLastError() != ERROR_RESOURCE_ENUM_USER_STOP) 168 | return false; // fail if no PAYLOAD resources are found 169 | 170 | if (!hRsrc) return false; 171 | 172 | this->payload->hResPayload = hRsrc; 173 | 174 | return true; 175 | } 176 | 177 | bool Injector::LoadFromResource() { 178 | // get resource size 179 | DWORD dwSize = ::SizeofResource(::GetModuleHandle(NULL), this->payload->hResPayload); 180 | // load resource 181 | HGLOBAL hResData = ::LoadResource(NULL, this->payload->hResPayload); 182 | if (hResData) { 183 | // get pointer to data 184 | LPVOID lpPayload = ::LockResource(hResData); 185 | if (lpPayload) { 186 | // save to vector 187 | if (MemoryMapPayload(lpPayload)) 188 | return true; 189 | } 190 | } 191 | 192 | return false; 193 | } 194 | 195 | bool Injector::InjectPayload() { 196 | if (!GetProcess()) 197 | return Debug(L"Could not find process: %lu\n", GetLastError()), false; 198 | 199 | // get DOS header 200 | PIMAGE_DOS_HEADER pidh = reinterpret_cast(this->vPayloadData.data()); 201 | // get NT headers 202 | PIMAGE_NT_HEADERS pinh = reinterpret_cast(reinterpret_cast(this->vPayloadData.data()) + pidh->e_lfanew); 203 | 204 | // check valid PE file 205 | if (pidh->e_magic != IMAGE_DOS_SIGNATURE || pinh->Signature != IMAGE_NT_SIGNATURE) 206 | return Debug(L"Signature error\n"), false; 207 | 208 | // allocate space in target process 209 | this->payload->lpAddress = ::VirtualAllocEx(this->payload->hProcess, NULL, pinh->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); 210 | if (!this->payload->lpAddress) 211 | return Debug(L"Failed to allocate space: %lu\n", GetLastError()), false; 212 | 213 | // fix the payload locally first before writing to target process 214 | // fix import table 215 | if (!RebuildImportTable(reinterpret_cast(this->vPayloadData.data()), pinh)) 216 | return Debug(L"Failed to parse import table: %lu\n", GetLastError()), false; 217 | 218 | // base relocate 219 | // get delta offset of image bases 220 | DWORD dwDelta = reinterpret_cast(this->payload->lpAddress) - pinh->OptionalHeader.ImageBase; 221 | if (!BaseRelocate(reinterpret_cast(this->vPayloadData.data()), pinh, dwDelta)) 222 | return Debug(L"Failed to relocate base: %lu\n", GetLastError()), false; 223 | 224 | // copy to process 225 | //if (!CopyHeadersAndSections(this->payload->hProcess, pidh, pinh)) 226 | if (!::WriteProcessMemory(this->payload->hProcess, this->payload->lpAddress, this->vPayloadData.data(), pinh->OptionalHeader.SizeOfImage, NULL)) 227 | return Debug(L"Failed write payload: %lu\n", GetLastError()), false; 228 | 229 | this->payload->dwEntryPoint = reinterpret_cast(this->payload->lpAddress) + pinh->OptionalHeader.AddressOfEntryPoint; 230 | 231 | return true; 232 | } 233 | 234 | INT Injector::ExecuteDll(bool bWait, bool bDetach) { 235 | // get DOS header 236 | PIMAGE_DOS_HEADER pidh = reinterpret_cast(this->vPayloadData.data()); 237 | // get NT headers 238 | PIMAGE_NT_HEADERS pinh = reinterpret_cast(reinterpret_cast(this->vPayloadData.data()) + pidh->e_lfanew); 239 | 240 | // walk and execute TLS 241 | 242 | DWORD dwExitCode = -1; 243 | // get entry point 244 | if (this->payload->dwEntryPoint) { 245 | // execute entry point 246 | HANDLE hThread = ::CreateRemoteThread(this->payload->hProcess, NULL, 0, reinterpret_cast(this->payload->dwEntryPoint), reinterpret_cast(this->payload->lpAddress), 0, NULL); 247 | if (!hThread) 248 | return Debug(L"Failed to start payload: %lu\n", GetLastError()), -1; 249 | 250 | if (bWait) { 251 | Debug(L"Waiting for payload thread...\n"); 252 | ::WaitForSingleObject(hThread, INFINITE); 253 | if (!::GetExitCodeThread(hThread, &dwExitCode)) 254 | dwExitCode = -1; 255 | } 256 | 257 | if (bDetach) 258 | ::VirtualFreeEx(this->payload->hProcess, this->payload->lpAddress, pinh->OptionalHeader.SizeOfImage, MEM_RELEASE); 259 | } 260 | 261 | return dwExitCode; 262 | } 263 | 264 | bool Injector::LoadFromDisk() { 265 | return false; 266 | } 267 | 268 | bool Injector::UpdatePayload() { 269 | return false; 270 | } 271 | -------------------------------------------------------------------------------- /Lynx/Lynx/injector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifndef __INJECTOR_H__ 3 | #define __INJECTOR_H__ 4 | 5 | #include 6 | #include 7 | 8 | // options 9 | #define OPTIONS_OBFUSCATE 0x01 10 | 11 | struct _payload { 12 | HRSRC hResPayload; 13 | HANDLE hProcess; 14 | LPVOID lpAddress; 15 | DWORD dwEntryPoint; 16 | }; 17 | 18 | class Injector { 19 | private: 20 | bool bUpdate = false; 21 | std::wstring szProcessName; 22 | std::wstring szFileName; 23 | DWORD dwOptions = 0; 24 | std::vector vPayloadData; 25 | struct _payload *payload = nullptr; 26 | 27 | bool GetProcess(); 28 | bool MemoryMapPayload(LPVOID lpPayload); 29 | bool BaseRelocate(LPVOID lpBaseAddress, PIMAGE_NT_HEADERS pinh, DWORD dwDelta); 30 | bool RebuildImportTable(LPVOID lpBaseAddress, PIMAGE_NT_HEADERS pinh); 31 | //bool ParseExportTable(HANDLE hProcess); 32 | 33 | public: 34 | Injector(std::wstring szProcessName); 35 | Injector(std::wstring szFileName, DWORD dwOptions); 36 | ~Injector(); 37 | 38 | // execute payload 39 | bool HasPayload(); 40 | bool LoadFromResource(); 41 | bool InjectPayload(); 42 | INT ExecuteDll(bool bWait, bool bDetach); 43 | 44 | // update payload 45 | bool LoadFromDisk(); 46 | bool UpdatePayload(); 47 | }; 48 | 49 | #endif // !__INJECTOR_H__ 50 | -------------------------------------------------------------------------------- /Lynx/Lynx/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "console.h" 4 | #include "gui.h" 5 | 6 | __declspec(dllexport) int func(int a, int b) { 7 | return a - b; 8 | } 9 | 10 | int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nShow) { 11 | int argc = 0; 12 | LPWSTR *argv = ::CommandLineToArgvW(::GetCommandLine(), &argc); 13 | 14 | if (argc < 2) 15 | return GuiMain(hInstance); 16 | else 17 | return ConsoleMain(argc, argv); 18 | } -------------------------------------------------------------------------------- /Lynx/Lynx/resource.h: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/NtRaiseHardError/Lynx/8d46067be9ea36ff0644d07b99e004baa9f68207/Lynx/Lynx/resource.h -------------------------------------------------------------------------------- /Lynx/TestDLL/TestDLL.vcxproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | Win32 7 | 8 | 9 | Release 10 | Win32 11 | 12 | 13 | Debug 14 | x64 15 | 16 | 17 | Release 18 | x64 19 | 20 | 21 | 22 | {8738CC4F-FFC5-4DD4-9810-7D31E2C5ADB4} 23 | Win32Proj 24 | TestDLL 25 | 8.1 26 | 27 | 28 | 29 | DynamicLibrary 30 | true 31 | v140 32 | Unicode 33 | 34 | 35 | DynamicLibrary 36 | false 37 | v140_xp 38 | true 39 | Unicode 40 | 41 | 42 | DynamicLibrary 43 | true 44 | v140 45 | Unicode 46 | 47 | 48 | DynamicLibrary 49 | false 50 | v140 51 | true 52 | Unicode 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | true 74 | 75 | 76 | true 77 | 78 | 79 | false 80 | 81 | 82 | false 83 | 84 | 85 | 86 | 87 | 88 | Level3 89 | Disabled 90 | WIN32;_DEBUG;_WINDOWS;_USRDLL;TESTDLL_EXPORTS;%(PreprocessorDefinitions) 91 | 92 | 93 | Windows 94 | true 95 | 96 | 97 | 98 | 99 | 100 | 101 | Level3 102 | Disabled 103 | _DEBUG;_WINDOWS;_USRDLL;TESTDLL_EXPORTS;%(PreprocessorDefinitions) 104 | 105 | 106 | Windows 107 | true 108 | 109 | 110 | 111 | 112 | Level3 113 | 114 | 115 | MaxSpeed 116 | true 117 | true 118 | WIN32;NDEBUG;_WINDOWS;_USRDLL;TESTDLL_EXPORTS;%(PreprocessorDefinitions) 119 | MultiThreaded 120 | 121 | 122 | Windows 123 | true 124 | true 125 | false 126 | MyMain 127 | 128 | 129 | 130 | 131 | Level3 132 | 133 | 134 | MaxSpeed 135 | true 136 | true 137 | NDEBUG;_WINDOWS;_USRDLL;TESTDLL_EXPORTS;%(PreprocessorDefinitions) 138 | 139 | 140 | Windows 141 | true 142 | true 143 | true 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /Lynx/TestDLL/TestDLL.vcxproj.filters: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | -------------------------------------------------------------------------------- /Lynx/TestDLL/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | BOOL APIENTRY MyMain(LPVOID lpParameter) { 5 | DWORD dwImageBase = reinterpret_cast(lpParameter); 6 | 7 | //WCHAR szOutput[MAX_PATH]; 8 | //wsprintf(szOutput, L"Module base: 0x%08x", dwImageBase); 9 | ::MessageBox(NULL, TEXT("This is the test DLL!"), TEXT("Test DLL"), MB_OK); 10 | 11 | return TRUE; 12 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lynx 2 | 3 | Dodgy reflective DLL injector PoC for 32-bit Windows. 4 | 5 | ![GUI](https://i.imgur.com/7zM8Uzj.jpg) 6 | 7 | ![Demo](https://i.imgur.com/ZWj85Zk.jpg) 8 | 9 | ## How to Compile 10 | 11 | Use MSVC++ because MinGW doesn't support `wWinMain`, though you can port it over if you wish. 12 | 13 | ## TODO List 14 | 15 | 1. Implement GUI 16 | 2. Add module to update the payload dynamically (without need to recompile) 17 | 3. Implement obfuscation option for the payload --------------------------------------------------------------------------------