Logo




Subscribe:
RSS 2.0 | Atom 1.0
Categories:

Sign In


[Giagnocavo]Michael::Write()

# Friday, January 28, 2005
Inline x86 ASM in C#

One thing I had done before and decided to try again was inline (embedded? inline isn't the right term exactly) ASM with C#. Remember, the CLR JITs your IL code down to native code when it runs. There's no interpreter or likewise going on -- your C# code is x86 when it runs (on an x86 platform). However, when writing in C#, it's rather hard to get out to x86 directly. Probably the easiest way would be to use Managed C++ and an inline asm section there. But, if you want to keep it all in C# (say, you want something extra hard to decompile), you can achieve that.

[I must note, the more I learn of internals, the more I learn I need to learn more. Thus hopefully, some true expert will read this and give me more insight.]

The most straightforward way that occurred to me was to use a delegate. As far as I know, C# won't issue calli and ldftn IL opcodes for us in any way we can neatly control. There will be ldftn when a delegate is created, but we can't set that value directly. So instead, we'll create a delegate and modify it. Delegates have a private field named “_methodPtr”. This, as far as I can tell, points to the code to be executed by the delegate. It's important that our delegate is accurate regarding the number of parameters, and the return value.

We will store our x86 in a byte array. Then, we'll pin the array, and stick the address of the first element inside the delegate. When we call the delegate, everything will be set.

As far as I can tell, methods in the CLR use the fastcall convention, so the first two parameters will be in EDX and ECX. The return value is expected in EAX. My demo is going to be simple, performing a ROR (ROtate Right) by 1 on the parameter and returning that. 3 lines of ASM.

Compile with /unsafe obviously, else I'd be writing to secure@microsoft.com. I'm not sure how terribly useful this is, but it seemed cool to me. At the very minimum, it serves to tell people to STFU when they claim that C# / .NET can't do pointers, or raw code, or whatever.

using System;
using System.Reflection;

class Program
{
    public delegate uint Ret1ArgDelegate(uint arg1);
    static uint PlaceHolder1(uint arg1) { return 0; }
    
    public static byte[] asmBytes = new byte[]
        {        
0x89,0xD0, // MOV EAX,EDX
0xD1,0xC8, // ROR EAX,1
0xC3       // RET
        };
        
    unsafe static void Main(string[] args)
    {
        fixed(byte* startAddress = &asmBytes[0]) // Take the address of our x86 code
        {
            // Get the FieldInfo for "_methodPtr"
            Type delType = typeof(Delegate);
            FieldInfo _methodPtr = delType.GetField("_methodPtr", BindingFlags.NonPublic | BindingFlags.Instance);

            // Set our delegate to our x86 code
            Ret1ArgDelegate del = new Ret1ArgDelegate(PlaceHolder1);
            _methodPtr.SetValue(del, (IntPtr)startAddress);

            // Enjoy
            uint n = (uint)0xFFFFFFFC;
            n = del(n);
            Console.WriteLine("{0:x}", n);
        }
    }
}

Code | IL
Friday, January 28, 2005 7:15:12 PM UTC  #    Comments [14]  |  Trackback Tracked by:
"http://coppohq.biz/www-cuconnect-colorado-edu.html" (http://coppohq.biz/www-cuc... [Pingback]


Saturday, January 29, 2005 1:22:22 AM UTC
Of course when CLR 2.0 comes out and you can run on x86 or IA64 machines, doing this will cause you a boatload of pain :-)
Shawn
Saturday, January 29, 2005 4:18:08 PM UTC
I wonder, could you use this as an attack vector into an application that uses delegates or does the framework prevent that from occuring?
Saturday, January 29, 2005 4:49:23 PM UTC
The thing is, AFAIK, you need full trust to modify private fields via reflection. So, if you are already running as full trust, you can do any number of things. In fact, a cute way is screwing up the stack and getting your methods run that way. But really, after you're running as full trust, there's so many attacks, its irrelevant. Now... if you did this inside VERIFIED, UNTRUSTED, code... that'd be something to report.

In fact, quite some time ago, I did stumble on something like that, but MS took care of it. Maybe in 5 years I can publish details :P.
Saturday, January 29, 2005 6:12:31 PM UTC
Thanks that makes sense.

The bad thing is .Net is ending up being just like the registry and running full admin where users are concerned. At least now it's down to the app level though. However I would bet good money users will still click on "trust this source" etc and run it under full privileges and get hacked. .Net is a step in the right direction for security the issue is getting people to use it.

For example do this test at any place you work if they don't produce software as their main revenue source and you'd be surprised the results you get.

Write a web control that has a bunch of yes buttons on it but the only close button is the little x up in the right hand corner. Then open up a generic e-mail account and spam everybody in your work place from this unknown to them e-mail account saying something like "if you love insertYourWorkplacenamehere then click here to take a survey". Send them to the page with the ActiveX control in it have it pop up a bunch of stuff with really obvious text on it like "would you like to install slammer" etc. and only leave them a yes button. If they progress through every screen blindly clicking yes to all the obvious malicious text in the dialogs have it send you an e-mail from them when it's done. In our company at least 50% percent of employees will run stuff like this blindly clicking OK or Yes.

I think .Net is going to end up the same way people will click yes to running it fully trusted like they already do with unknown applets and ActiveX controls and then when they get a virus or there machine gets loaded up with horse p0rn they will blame MSFT for not protecting them.

You can lead users to water but you can't make them drink.

Down the road if MSFT ever lets you publish what the security risk was I would love to read what it was you found and how you found it.
Saturday, January 29, 2005 9:39:31 PM UTC
Yes, but the problem isn't with .NET, it's with the underlying operating system. Current versions of Windows are simply not made for people to run arbitrary code. That's a big shift.

My hopes are definately in Longhorn. I WANT to be able to click on random email attachments, knowning nothing can happen. I WANT my family to be able to view "Happy Bunnies.exe". I'm hoping that by the time my first child is born and gets her first computer (say, 4 or 5 years), that I won't have to drill in basic "operation safety" instructions.

I'm hoping that MS can achieve this with a new OS, one that has code security as a basic concept throughout...
Monday, April 18, 2005 9:40:26 AM UTC
In .NET 2.0 you can avoid the horrible trick of hacking inside delegate implementation by using Marshal.GetDelegateForFunctionPointer:
http://msdn2.microsoft.com/library/System.Runtime.InteropServices.Marshal.GetDelegateForFunctionPointer.aspx
Michael Entin
Monday, April 18, 2005 2:42:05 PM UTC
That's pretty cool. Whidbey has a bunch of nice new features :).
Friday, September 09, 2005 10:30:14 AM UTC
dude, its pretty cool to manaully override another window's window proc :) thats what I had to do... :P


thanks dude
Thursday, April 19, 2007 5:29:15 AM UTC
Looks like it still does a managed to unmanaged transition. Might as well be using dll interop at that point.

Damnit Microsoft, we need to be able to make changes to and ship our own version of the JIT!
John
Sunday, July 27, 2008 5:54:01 PM UTC
Just a little post to say that this article has inspired NetAsm a .NET library that enables true inline of assembler x86 without using interop. You can go on codeplex : http://www.codeplex.com/netasm
Thursday, October 16, 2008 7:22:57 PM UTC
How did you figure out that
0x89,0xD0, // MOV EAX,EDX
0xD1,0xC8, // ROR EAX,1
0xC3 // RET

0xC3 = RET and 0x89 = MOV. I'm not understaning how you got this from the asm code.
Monday, October 05, 2009 6:16:44 AM UTC
This still works, by the way.

You do have to add this code though, as modern versions of windows don't allow you to execute code in data regions of memory (which the array in the code above is). Instead, call VirtualAlloc() to allocate an executable region, then put the assembly there and set the delegate to call it.

IntPtr p = VirtualAlloc(IntPtr.Zero, new UIntPtr(4096), AllocationType.COMMIT | AllocationType.RESERVE,
MemoryProtection.EXECUTE_READWRITE);

byte* ex = (byte*)p.ToPointer();
ex[0] = 0x89;
ex[1] = 0xd0;
ex[2] = 0xd1;
ex[3] = 0xc8;
ex[4] = 0xc3;

// Set our delegate to our x86 code
Ret1ArgDelegate del = new Ret1ArgDelegate(PlaceHolder1);
_methodPtr.SetValue(del, p);

Pretty neat.
benjcooley
Sunday, May 09, 2010 11:10:03 PM UTC
thanks a lot guys, now i can start to build a opcode builder class to build my assembler applications in c# (not entire apps ofcourse morelike decision critical procedures)
However i need to figure out how to pass the variables to the correct registers (but i read this page again (poor english you see))
Monday, November 01, 2010 6:51:28 AM UTC
Nice article, I'm trying to modify the function to accept 2 parameter but failed to read the 2nd param. Can you share some light in this?

kayX
OpenID
Please login with either your OpenID above, or your details below.
Name
E-mail
Home page

Comment (HTML not allowed)  

Enter the code shown (prevents robots):

Live Comment Preview