.NET CLR Hosting + Console woes menu

User Tag List

Results 1 to 6 of 6
  1. #1
    Shenlok's Avatar Active Member
    Reputation
    15
    Join Date
    Jul 2008
    Posts
    42
    Thanks G/R
    6/2
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)

    .NET CLR Hosting + Console woes

    Hi! Firstly, apologies if this seems like an awfully dumb question, I know those are way too common around here, but I've been trying to figure this one out for myself for a couple days now to no avail. Anyway, here goes!

    I've decided to go back to dabbling with WoW memory editing/botting/reversing, whatever you want to call it, and am trying my hand at some injected C# fun. I've succeeded in the sense that I have a C# Winforms app that injects a native bootstrapper DLL, which in turn loads the CLR into WoW.exe, and executes a test method in a test assembly. This all works fine, my test method allocates a console, prints to it, frees the console, and returns a random int. I get the return code, everything is great. However, if I try to repeat this action after doing it the first time, WoW craps itself and crashes.

    The first time round looks fine, the console appears, the message appears, the console disappears after calling FreeConsole(). The second time, WoW crashes on the call to Console.WriteLine();

    I'm not sure what the problem is, my best guess would be maybe something handle related? Has anyone had any joy (or pain) in using a console window for output while injected (from managed or unmanaged code)? If I get rid of the console related stuff and just return the random int everything works fine.

    For reference, here is my code:

    The .NET assembly + function that gets executed
    Code:
    namespace TestAssemblyInjected
    {
        public class Class1
        {
            private static Random rand;
            public static int Test(String argument)
            {
                var res = AttachConsole(-1); // -1 == ATTACH_PARENT_PROCESS
                if (res == 0)
                    AllocConsole();
                Console.WriteLine("Hello");
                Console.ReadLine();
                FreeConsole();
    
                if (rand == null)
                    rand = new Random();
    
                return rand.Next(0,1000);
            }
    
            [DllImport("kernel32.dll", SetLastError = true)]
            internal static extern int AllocConsole();
    
            [DllImport("kernel32.dll", SetLastError = true)]
            internal static extern int AttachConsole(int HANDLE);
    
            [DllImport("kernel32.dll", SetLastError = true)]
            internal static extern int FreeConsole();
        }
    }
    The CLR bootstrapper DLL
    Code:
    #include "DOTNET Bootstrap DLL.h"
    #include <metahost.h>
    #include <strsafe.h>
    #include <iostream>
    #include <fstream>
    #include <io.h>
    #include <fcntl.h>
    
    #pragma comment(lib, "mscoree.lib")
    
    // What is this black magic? I don't think anyone knows. I sure don't.
    #import "mscorlib.tlb" raw_interfaces_only \
    	high_property_prefixes("_get","_put","_putref") \
    	rename("ReportEvent", "InteropServices_ReportEvent")
    
    DOTNETBOOTSTRAPDLL_API int __cdecl bootstrap(wchar_t* assembly_path, wchar_t* type_name, wchar_t* method_name)
    {
    	HRESULT hr;
    	ICLRMetaHost *pMetaHost = nullptr;
    	ICLRRuntimeInfo *pRuntimeInfo = nullptr;
    	ICLRRuntimeHost *pClrRuntimeHost = nullptr;
    	DWORD dw = 1;
    	BOOL result;
    
    	hr = CLRCreateInstance(CLSID_CLRMetaHost, IID_PPV_ARGS(&pMetaHost));
    	if (SUCCEEDED(hr))
    	{
    		hr = pMetaHost->GetRuntime(L"v4.0.30319", IID_PPV_ARGS(&pRuntimeInfo));
    		if (SUCCEEDED(hr))
    		{
    			hr = pRuntimeInfo->GetInterface(CLSID_CLRRuntimeHost, IID_PPV_ARGS(&pClrRuntimeHost));
    			if (SUCCEEDED(hr))
    			{
    				hr = pClrRuntimeHost->Start();
    				if (FAILED(hr)) {
    					ErrorExit(TEXT("Start"));
    				}	
    				hr = pClrRuntimeHost->ExecuteInDefaultAppDomain(
    					assembly_path,
    					type_name,
    					method_name,
    					L"HERRO PREESE",
    					&dw
    					);
    				if (FAILED(hr)) {
    					ErrorExit(TEXT("ExecuteInDefaultAppDomain"));
    				}				
    				hr = pClrRuntimeHost->Release();
    			} else {
    				ErrorExit(TEXT("GetInterface"));
    			}
    			hr = pRuntimeInfo->Release();
    		} else {
    			ErrorExit(TEXT("GetRuntime"));
    		}
    		hr = pMetaHost->Release();
    	} else {
    		ErrorExit(TEXT("CLRCreateInstance"));
    	}
    
    	return dw;
    }
    
    void ErrorExit(LPTSTR lpszFunction) 
    { 
    	// Retrieve the system error message for the last-error code
    
    	LPVOID lpMsgBuf;
    	LPVOID lpDisplayBuf;
    	DWORD dw = GetLastError(); 
    
    	FormatMessage(
    		FORMAT_MESSAGE_ALLOCATE_BUFFER | 
    		FORMAT_MESSAGE_FROM_SYSTEM |
    		FORMAT_MESSAGE_IGNORE_INSERTS,
    		NULL,
    		dw,
    		MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
    		(LPTSTR) &lpMsgBuf,
    		0, NULL );
    
    	// Display the error message and exit the process
    
    	lpDisplayBuf = (LPVOID)LocalAlloc(LMEM_ZEROINIT, 
    		(lstrlen((LPCTSTR)lpMsgBuf) + lstrlen((LPCTSTR)lpszFunction) + 40) * sizeof(TCHAR)); 
    	StringCchPrintf((LPTSTR)lpDisplayBuf, 
    		LocalSize(lpDisplayBuf) / sizeof(TCHAR),
    		TEXT("%s failed with error %d: %s"), 
    		lpszFunction, dw, lpMsgBuf); 
    	MessageBox(NULL, (LPCTSTR)lpDisplayBuf, TEXT("Error"), MB_OK); 
    
    	LocalFree(lpMsgBuf);
    	LocalFree(lpDisplayBuf);
    }
    And the relevant bits from my WinForms C# app that injects and calls the bootstrapper
    Code:
    //Called upon clicking a 'Find Wow' button
            private void FindWoW(object sender, EventArgs e)
            {
                var wowProcs = ApplicationFinder.FromProcessName("Wow");
                Process wowProc;
                if (!wowProcs.Any())
                {
                    // launch wow!
                    wowProc = Process.Start("D:\\Games\\World of Warcraft\\Wow.exe");
                } else
                {
                    wowProc = wowProcs.First();
                }
                Thread.Sleep(100);
                this.wow = new MemorySharp(wowProc);
                l_PID.Text = wow.Pid.ToString();
            }
    
            // Called upon clicking an 'Inject' button
            private void DoInject(object sender, EventArgs e)
            {
                string bootstrapperPath = "bootstrapper.dll";
                string testAssemblyPath = "TestAssemblyInjected.dll";
                if (File.Exists(bootstrapperPath))
                {
                    var bsf = new FileInfo(bootstrapperPath);
                    var taf = new FileInfo(testAssemblyPath);
                    bootstrapperPath = bsf.FullName;
                    testAssemblyPath = taf.FullName;
                    using (bootstrapper = wow.Modules.Inject(bootstrapperPath))
                    {
                        string test = "Hello!";
                        var ret = bootstrapper["bootstrap"].Execute(CallingConventions.Cdecl, AsUTF8(testAssemblyPath),
                                                                    AsUTF8("TestAssemblyInjected.Class1"), AsUTF8("Test"));
                        l_ret.Text = ret.ToString();
                    }
                } else
                {
                    l_ret.Text = "Bootstrapper not found.";
                }
                
            }
            
            // C++ Win32 API wants strings in UTF8, C# uses UTF16, let's make it happen
            private static string AsUTF8(string s)
            {
                var utf16 = new UnicodeEncoding();
                byte[] encodedBytes = utf16.GetBytes(s);
                return Encoding.UTF8.GetString(encodedBytes);
            }
    Update:
    So since I forgot to actually mention the specific error WoW is throwing, here it is...
    Exception: 0xC0000005 (ACCESS_VIOLATION) at 0023:0C0D137F

    The instruction at "0x0C0D137F" referenced memory at "0x0C0D137F". The memory could not be "executed".

    I've also wrapped my console stuff in a try/catch block, and SOMETIMES instead of WoW crashing it will catch an exception:

    The handle is invalid.
    @ WriteLine()


    So I guess something isn't being cleaned up properly the first time around, or my STDOUT etc handles aren't being correctly set the second time around. The fact that it only sometimes crashes is quite confusing though.
    Last edited by Shenlok; 10-19-2013 at 09:17 AM. Reason: Updated info

    .NET CLR Hosting + Console woes
  2. #2
    Valediction's Avatar Active Member
    Reputation
    37
    Join Date
    Jul 2012
    Posts
    48
    Thanks G/R
    8/4
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    If you haven't already, take a look at this:

    http://www.ownedcore.com/forums/worl...ml#post1915992 (.Net Managed Assembly Removal)

    PS. It doesn't contain anything related to the console, it just shows an effective way to reload assemblies after a (single) DLL+CLR injection.

  3. #3
    Shenlok's Avatar Active Member
    Reputation
    15
    Join Date
    Jul 2008
    Posts
    42
    Thanks G/R
    6/2
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by Valediction View Post
    If you haven't already, take a look at this:

    http://www.ownedcore.com/forums/worl...ml#post1915992 (.Net Managed Assembly Removal)

    PS. It doesn't contain anything related to the console, it just shows an effective way to reload assemblies after a (single) DLL+CLR injection.
    Thanks! I'll take a look. At first glance it's quite similar to how miceiken does his assembly loading in their IceFlake project, guess I found the source of it! Still would like to figure out why exactly I'm having this issue, for curiosity's sake. Maybe the default AppDomain just isn't meant to be used like this.

  4. #4
    Shenlok's Avatar Active Member
    Reputation
    15
    Join Date
    Jul 2008
    Posts
    42
    Thanks G/R
    6/2
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    So I implemented a DomainManager class as kindly shared by Apoc here (.Net Managed Assembly Removal), so I can now have my assembly be loaded/executed multiple times without having to restart WoW. Awesome!

    I did run into an issue with my console usage, where I would call FreeConsole() after exiting my logic loop as cleanup, and then suddenly I'd get WoW crashing on me. Tracked it down to a Console.WriteLine() in GreyMagic's SafeMemoryHandle.ReleaseHandle(), presumably the Console's handle was invalidated by my FreeConsole() call and this was throwing an exception. Commenting it out fixed the issue, just thought I'd document my experience here in case it proves useful to someone.

    I'd still like to know why exactly my previous approach (sans DomainManager making separate AppDomains for each execution of my code) was going so wrong. It's good to leave a problem with it fixed and also knowing WHY that fix did its job
    Last edited by Shenlok; 10-19-2013 at 03:06 PM.

  5. #5
    _Mike's Avatar Contributor
    Reputation
    310
    Join Date
    Apr 2008
    Posts
    531
    Thanks G/R
    0/2
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Your problem is because System.Console caches the handles the first time they are used, and when you destroy the console those handles become invalid.
    You can use the following code after each call to AllocConsole to force a refresh
    Code:
    var _in = typeof(System.Console).GetField("_in", BindingFlags.Static | BindingFlags.NonPublic);
    var _out = typeof(System.Console).GetField("_out", BindingFlags.Static | BindingFlags.NonPublic);
    var _err = typeof(System.Console).GetField("_error", BindingFlags.Static | BindingFlags.NonPublic);
    _in.SetValue(null, null);
    _out.SetValue(null, null);
    _err.SetValue(null, null);
    But I haven't looked at how FieldInfo.SetValue() works internally so you might have resource leaks if the TextReader/TextWriters aren't deallocated properly.

    Edit:
    Actually it would probably be better to do this just before you call FreeConsole() instead of after AllocConsole(). Then any following writes will just silently fail instead of crashing the application.
    Last edited by _Mike; 10-20-2013 at 03:59 AM.

  6. #6
    Shenlok's Avatar Active Member
    Reputation
    15
    Join Date
    Jul 2008
    Posts
    42
    Thanks G/R
    6/2
    Trade Feedback
    0 (0%)
    Mentioned
    0 Post(s)
    Tagged
    0 Thread(s)
    Originally Posted by _Mike View Post
    Your problem is because System.Console caches the handles the first time they are used, and when you destroy the console those handles become invalid.
    You can use the following code after each call to AllocConsole to force a refresh
    Code:
    var _in = typeof(System.Console).GetField("_in", BindingFlags.Static | BindingFlags.NonPublic);
    var _out = typeof(System.Console).GetField("_out", BindingFlags.Static | BindingFlags.NonPublic);
    var _err = typeof(System.Console).GetField("_error", BindingFlags.Static | BindingFlags.NonPublic);
    _in.SetValue(null, null);
    _out.SetValue(null, null);
    _err.SetValue(null, null);
    But I haven't looked at how FieldInfo.SetValue() works internally so you might have resource leaks if the TextReader/TextWriters aren't deallocated properly.

    Edit:
    Actually it would probably be better to do this just before you call FreeConsole() instead of after AllocConsole(). Then any following writes will just silently fail instead of crashing the application.
    Ahhh right, I thought it might be something like that. Thanks very much for the explanation!

Similar Threads

  1. Replies: 1
    Last Post: 01-19-2012, 03:14 AM
  2. [C#] CLR hosting using C# and BlackMagic (ASM)
    By JuJuBoSc in forum WoW Memory Editing
    Replies: 9
    Last Post: 05-10-2011, 08:44 AM
  3. [General] CLR hosting without the dll?
    By !@^^@! in forum WoW Memory Editing
    Replies: 34
    Last Post: 05-05-2011, 11:50 AM
  4. [.NET 4 hosting] Loading config, exception handling
    By Kryso in forum WoW Memory Editing
    Replies: 1
    Last Post: 11-03-2010, 12:37 PM
All times are GMT -5. The time now is 05:17 PM. Powered by vBulletin® Version 4.2.3
Copyright © 2024 vBulletin Solutions, Inc. All rights reserved. User Alert System provided by Advanced User Tagging (Pro) - vBulletin Mods & Addons Copyright © 2024 DragonByte Technologies Ltd.
Digital Point modules: Sphinx-based search