This article shows you how to inject your 4.5 framework .NET application into a C++ unmanaged host application in a fast and secure manner without any extra tool or library.
Since the War Began
Well, let's begin. Everybody knows how it's much easier to create an app with C# and .NET than develop a native language like C++ or Delphi. But, the main problem of every CLR application is ... security!
I'm telling you after flash swf files, it's the most insecure platform you can develop your app!
Umm ... Security!
Let's be honest, security is a joke! But ... it has levels.
Imagine you have a gold watch and you love it too much! You don't want to share it with anybody else.
- A > You can put it in a glass box and take care of it.
- B > You can put it in a glass and put a lot of laser around it.
- C > You can put it in a metal box and lock it with a unique key that only you have.
- D > you can put it in a glass box and next, put the glass box in a metal box and lock it.
The level of your secuirty methods are different, but eventually it can be defeated. You just determined the level of hardness ... then ... We do the same thing!
Before Start, Know the End!
Let's face it! Hundreds of companies claim that they can secure your software! But ...
As You Know:
People lie! Just to take your money! It never was wrong, right?!
Obfuscator, Crypters, Anti-Debuggers, Anti-Dumpers ...
All of them can be decompiled very easily! There are tools that reverse your app by one click in couple of seconds ... and you do not even need to be an engineer!
Some of these apps you can find:
de4Dot , .Net Generic Unpacker , .NetBreak , DefuscatorNet , DeSpy , .Net Reflector , ILDasm ...
Quote:
If you know the proper recognition of your enemy weapons, the probability of your winning is greater. I don't know who said that but it's logical :D
The Methods
At the moment, we have several methods of injecting a .NET application into a unmanaged C++ app...
- Using BoxedAppSDK's DotNetAppLauncher - You need 8000 USD and it only supports 2.0 framework! Developers had a lot of emails from users wanted 4.+ frameworks but their method requires to embed more than 400MB in C++ EXE file! LOL, it's super f---- up!
- Embedding your .NET app, extract it on a temp folder, launch it! - Hacker just needs task manager! xD
- Using Virtual Applications, Read the .NET from memory. - only supports 2.0 framework too
- Using C++/CLI Framework. - Sorry, the released file is a .NET file!
- Spend a lot of time to develop a Armageddon Packer to end the WAR! - DO IT SOLDIER!
- Using this article, supports WPF and other stuff. It's hard to use and it has some bugs!
- Host the .NET Application by CLRHosting in C++ from Crypted Data from Memory! - Here we go ... It's easy, fast to compile and more safe!
You should know ...
- And, you can use any
PEPacker
, PEProtector
and PECrypter
on your final EXE file. It's .NET! But you can! - It supports WPF too!
- It's 100% virus free! MAGIC EXISTS! <VirusCheck>
Preparing the Project
What you need:
- Visual Studio 2012 or higher (C++ native / .NET C# 4.0 - 4.7)
- Custom Hash to String Convertor "Included in source files"
- Fody/Costura .NET Extension to embed all DLL references as resources in final .NET EXE file. Get it from nuget
- HxD Hash Editor it's free, download it
- VMProtect 3, UPXGUI , MEW , or any packer you want
- CFF Explorer, Detect It Easy and Exeinfo, download them all
Step A. Develop a .NET Application
Step B. Preparing Native Launcher App
- Follow: New > Project > Visual C++ > Win32 > Win32 Console Application and create a new project.
- Add Includes to your main cpp file. (Create RawFiles.h in headers and leave it empty.)
#include <cstdlib>
#include <iostream>
#include <sstream>
#include <cassert>
#include <fstream>
#include "RawFiles.h"
#pragma region Includes and Imports
#include <windows.h>
#include <metahost.h>
#include <vector>
#pragma comment(lib, "mscoree.lib")
#import "mscorlib.tlb" raw_interfaces_only\
high_property_prefixes("_get","_put","_putref")\
rename("ReportEvent", "InteropServices_ReportEvent")
using namespace mscorlib;
#pragma endregion
- Define just one value :
#define RAW_ASSEMBLY_LENGTH 1000
- Replace
"int _tmain(int argc, _TCHAR* argv[])"
with:
int APIENTRY _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _
LPTSTR lpCmdLine, int nCmdShow)
- Goto Linker Properties and change SubSystem to Windows (/SUBSYSTEM:WINDOWS) and Target Machine to MachineX64 (/MACHINE:X64).
Step C. Start to Code the Host!
- Install WPF Mode by adding:
CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);
Tip:
You only need to add CoInitializeEx
when you:
- build a WPF Based Application
- used WPF UserControl in your WinForms
If you didn't, you don't need to add it.
- Install
CLRHost
, MetaHost
and Instances
:
ICLRMetaHost *pMetaHost = NULL; ICLRMetaHostPolicy *pMetaHostPolicy = NULL; ICLRDebugging *pCLRDebugging = NULL; HRESULT hr;
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost,
(LPVOID*)&pMetaHost);
hr = CLRCreateInstance (CLSID_CLRMetaHostPolicy, IID_ICLRMetaHostPolicy,
(LPVOID*)&pMetaHostPolicy);
hr = CLRCreateInstance (CLSID_CLRDebugging, IID_ICLRDebugging,
(LPVOID*)&pCLRDebugging);
- Install .NET Runtime:
Tip: You can set your runtime framework version here GetRuntime(L"RUNTIMEVERSION")
.
For this project, I used "v4.0.30319" version.
ICLRRuntimeInfo* pRuntimeInfo = NULL; hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_ICLRRuntimeInfo, (VOID**)&pRuntimeInfo);
- Start and load
CLRApp
:
BOOL bLoadable;
hr = pRuntimeInfo->IsLoadable(&bLoadable);
ICorRuntimeHost* pRuntimeHost = NULL;
hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost,
IID_ICorRuntimeHost,
(VOID**)&pRuntimeHost);
hr = pRuntimeHost->Start();
- Install
AppDomain
:
IUnknownPtr pAppDomainThunk = NULL;
hr = pRuntimeHost->GetDefaultDomain(&pAppDomainThunk);
_AppDomainPtr pDefaultAppDomain = NULL;
hr = pAppDomainThunk->QueryInterface
(__uuidof(_AppDomain), (VOID**) &pDefaultAppDomain);
_AssemblyPtr pAssembly = NULL;
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].cElements = RAW_ASSEMBLY_LENGTH;
rgsabound[0].lLbound = 0;
SAFEARRAY* pSafeArray = SafeArrayCreate(VT_UI1, 1, rgsabound);
void* pvData = NULL;
hr = SafeArrayAccessData(pSafeArray, &pvData);
- Add, execution, clean up and memory reader:
memcpy(pvData, Raw_Net_Data, RAW_ASSEMBLY_LENGTH);
hr = SafeArrayUnaccessData(pSafeArray);
hr = pDefaultAppDomain->Load_3(pSafeArray, &pAssembly);
_MethodInfoPtr pMethodInfo = NULL;
hr = pAssembly->get_EntryPoint(&pMethodInfo);
VARIANT retVal;
ZeroMemory(&retVal, sizeof(VARIANT));
VARIANT obj;
ZeroMemory(&obj, sizeof(VARIANT));
obj.vt = VT_NULL;
SAFEARRAY *psaStaticMethodArgs = SafeArrayCreateVector(VT_VARIANT, 0, 0);
hr = pMethodInfo->Invoke_3(obj, psaStaticMethodArgs, &retVal);
If you have any problem at compiling, just restart Visual Studio.
Step D. Using the Code!
- Ok , now we have a host that executes our dotnet app from memory, now it's time to inject our application.
- Start HxD and drag and drop your .NET release application.
- Select all of hashes and click on: Edit > Copy As > C.
- Paste clipboard on RawFiles.h file.
- Change the unsigned
char
name to Raw_Net_Data
:
unsigned char Raw_Net_Data[120399] = {
0x4D, 0x5A, 0x90, 0x00, 0x03, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
...
};
- Set
RAW_ASSEMBLY_LENGTH
to Raw_Net_Data
[120399] number:
#define RAW_ASSEMBLY_LENGTH 120399
Now build the application.
As you can see, your app runs perfectly with any problem!
War Begins!
- The Hex array you entered in Visual Studio compiles too fast but the problem is, it compiles to an embeded resource, you can rip it so easily with Exeinfo Ripper.
- Let's start our next solution.
- We're going to create the hex from strings arrays and load it on runtime. Ripper can't recognize the pe hexes located in your resources.
- All code we wrote till now ...
#include <cstdlib>
#include <iostream>
#include <sstream>
#include "stdafx.h"
#include <cassert>
#include <fstream>
#include "RawFiles.h"
#pragma region Includes and Imports
#include <windows.h>
#include <metahost.h>
#include <vector>
#pragma comment(lib, "mscoree.lib")
#import "mscorlib.tlb" raw_interfaces_only\
high_property_prefixes("_get","_put","_putref")\
rename("ReportEvent", "InteropServices_ReportEvent")
using namespace mscorlib;
#pragma endregion
#define RAW_ASSEMBLY_LENGTH 1715200
int _tmain(int argc, _TCHAR* argv[])
{
CoInitializeEx(NULL,COINIT_APARTMENTTHREADED);
ICLRMetaHost *pMetaHost = NULL;
ICLRMetaHostPolicy *pMetaHostPolicy = NULL;
ICLRDebugging *pCLRDebugging = NULL;
HRESULT hr;
hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_ICLRMetaHost,
(LPVOID*)&pMetaHost);
hr = CLRCreateInstance (CLSID_CLRMetaHostPolicy, IID_ICLRMetaHostPolicy,
(LPVOID*)&pMetaHostPolicy);
hr = CLRCreateInstance (CLSID_CLRDebugging, IID_ICLRDebugging,
(LPVOID*)&pCLRDebugging);
ICLRRuntimeInfo* pRuntimeInfo = NULL;
hr = pMetaHost->GetRuntime
(L"v4.0.30319", IID_ICLRRuntimeInfo, (VOID**)&pRuntimeInfo);
BOOL bLoadable;
hr = pRuntimeInfo->IsLoadable(&bLoadable);
ICorRuntimeHost* pRuntimeHost = NULL;
hr = pRuntimeInfo->GetInterface(CLSID_CorRuntimeHost,
IID_ICorRuntimeHost,
(VOID**)&pRuntimeHost);
hr = pRuntimeHost->Start();
IUnknownPtr pAppDomainThunk = NULL;
hr = pRuntimeHost->GetDefaultDomain(&pAppDomainThunk);
_AppDomainPtr pDefaultAppDomain = NULL;
hr = pAppDomainThunk->QueryInterface(__uuidof(_AppDomain), (VOID**) &pDefaultAppDomain);
_AssemblyPtr pAssembly = NULL;
SAFEARRAYBOUND rgsabound[1];
rgsabound[0].cElements = RAW_ASSEMBLY_LENGTH;
rgsabound[0].lLbound = 0;
SAFEARRAY* pSafeArray = SafeArrayCreate(VT_UI1, 1, rgsabound);
void* pvData = NULL;
hr = SafeArrayAccessData(pSafeArray, &pvData);
int startint = 0;
memcpy(pvData, Raw_Net_Data, RAW_ASSEMBLY_LENGTH);
hr = SafeArrayUnaccessData(pSafeArray);
hr = pDefaultAppDomain->Load_3(pSafeArray, &pAssembly);
_MethodInfoPtr pMethodInfo = NULL;
hr = pAssembly->get_EntryPoint(&pMethodInfo);
VARIANT retVal;
ZeroMemory(&retVal, sizeof(VARIANT));
VARIANT obj;
ZeroMemory(&obj, sizeof(VARIANT));
obj.vt = VT_NULL;
SAFEARRAY *psaStaticMethodArgs =
SafeArrayCreateVector(VT_VARIANT, 0, 0);
hr = pMethodInfo->Invoke_3
(obj, psaStaticMethodArgs, &retVal);
return 0;
}
You can copy/paste xD
whatever ...
Step E. Anti-Ripper Solutions
We have four ways to defeat a Ripper:
- Remove random hashes from array and replace it from another lost parts array in runtime before executing CLR.
- It's complex but it's fast.
- It's compressed and gives you less size in final PE File.
- Security in this method is less than other methods.
- Convert all of hashes to Strings Blocks and create a hash array from them at runtime.
- It's heavy, takes more than 10 minutes to compile.
- It executes CLR fast at runtime, don't worry.
- It's very good at secuirty against rippers and debuggers.
- It's not compressed and gives you a high size EXE file but you can compress it by a packer with 5-10% Ratio.
- Convert all of hashes to string, next crypt them with Xor Encryption and decrypt it at runtime.
- It's fast but a little complex.
- It's compressed.
- Awesome Secuirty!
- Create it very simple as before and next crypt resources with a PE Protector like VMProtect 3.0.
- Only expert hackers are able to reverse it.
- It's super compressed.
- it's very simple to use and takes no extra time.
- It's virtual with a perfect security!
Which is better?! :
It really depends on you to choose one of methods.
Here we go, it's the converter of hashes to string
s and a function to build hashes again:
String
arrays are limited then we need to create block chains and link them together. - Data.txt includes
String
Arrays and F_N.txt includes Functions to recreate Hex array. - Paste functions at
//////// Add Functions
Here Area at main code.
int blockchainsize = 10000; int time_in_block = (_countof(rawData))/(blockchainsize); FILE *filex = fopen("C:\\Data.txt", "w");
for( int ax = 0; ax <= time_in_block; ax = ax++ ) {
fprintf(filex, "std::string Raw");
fprintf(filex , "%d", ax);
fprintf(filex, "[");
fprintf(filex, "] = { \n");
for( int i = (blockchainsize*ax); i < (blockchainsize*(ax+1)-1); i = i++ ) {
fprintf(filex,"\"0x%X\"", (unsigned char)rawData[i]);
fprintf(filex, ",");
};
fprintf(filex,"\"0x%X\"", (unsigned char)rawData[(blockchainsize*(ax+1)-1)]);
fprintf(filex, "\n};");
fprintf(filex, "\n");
}
fclose(filex);
std::cout << "file created!" << "\n";
FILE *filex2 = fopen("C:\\F_N.txt", "w");
for( int ax = 0; ax <= time_in_block-1; ax = ax++ ) {
fprintf(filex2, "for (int vx = 0; vx < _countof(Raw");
fprintf(filex2 , "%d", ax);
fprintf(filex2, "); vx++) {\n ");
fprintf(filex2, "char valuex = strtol(Raw");
fprintf(filex2 , "%d", ax);
fprintf(filex2, "[vx");
fprintf(filex2, "].c_str(),NULL,16);\n rawData[startint] = valuex;
\n startint = startint+1; \n }; \n");
}
fclose(filex2);
And here's a simple encrypter/decrypter for using in your project:
std::string encryptDecrypt(std::string toEncrypt) {
char key = 'X';
std::string output = toEncrypt;
for (int i = 0; i < toEncrypt.size(); i++)
output[i] = toEncrypt[i] ^ key;
return output;
}
Step F. Final Step!
- Congratulations!
Quote:
Wubba Lubba Dub Dub ! You Did it ! - R.Sanchez
- Last step is protecting your app using a Simple/Advanced Protector, VMProtect 3.0 (The Best!) or MPRESS from Microsoft.
- I used MPRESS with custom stubs.
Known Issues And Fixes
Conclusion
Hope this article works for you. As I mentioned, there is no full function security, but you can do the best!
- I have to give special thanks to RandomVertex for his Hosting 2.0 Framework CLR from memory.
- If you have any problem or questions, feel free to leave a comment below, I'm glad to help.
What's Next?
I will show you how to protect your C++ with a custom PEPacker, YEAH ! Unknown Protector \m/!
History
- 25th March, 2018: Tips added
- 23rd March, 2018: Article created