Tydzień temu zaproponowałem mały konkurs, polegający na zmuszenia .NET’a do zrobienia rzeczy, której teoretycznie nie powinno dać się zrobić. Niestety trochę “przedobrzyłem” i jednym z dostępnych rozwiązań rozwiązań było użycie Reflection o co mnie w pytaniu nie chodziło. Świat się jednak nie wali, a jest nawet lepiej. Wiem, że są osoby, które o Reflection nie słyszały i dziwią się, że takie rzeczy w .NET można robić.
Tak więc zobaczmy jakie rozwiązania są dostępne:

Rozwiązania

Podstawowe czyli z użyciem Reflection:

    public class NaughtyByReflectionPlugin : IPlugin
    {       
        public void Execute(PluginData data)
        {
            var memebrs = data.GetType().GetMember(“ConstString”, BindingFlags.Instance | BindingFlags.NonPublic);
            var member = (FieldInfo)memebrs[0];
            member.SetValueDirect(__makeref(data), “Hacking .NET”);
        }       
    }

Takie (lub podobne – oparte na Reflection) rozwiązania nadesłali: Przemysław Mynarski, Marek Grabarz, Jarosław Dubrownik, Krystian Kulig, Paweł Szczygielski, Łukasz Jezior, Adam Rafałko oraz Bo i wiero. Jak już wspominałem nie wszyscy są świadomi Reflection, tak więc dobrze, że i takie rozwiązanie się pojawiło. Dodatkowo duży plus dla Marka za sprawienie, że teraz wiem o słowie kluczowym __makeref – choć i da się bez tego. Widać człowiek uczy się cały czas.
Jaki jest minus takiego rozwiązania? A no to, że w bardzo prosty sposób możemy się przed nim zabezpieczyć. Wystarczy, że nasz program będzie miał zabrane prawa do Reflection i już nie poszalejemy.

    [ReflectionPermission(SecurityAction.Deny, Unrestricted = true)]
    class Program
    {

No więc, co możemy zrobić? Da się to obejść?

Pointery. A co to takiego?

Da się obejść powyższe ograniczenie za pomocą pointerów, ale trzeba posiłkować się kodem unsafe, więc nie wiem czy takie rozwiązanie przeszłoby przy ładowaniu z osobnego assembly. Nie miałem chwili tego sprawdzić. Zobaczmy jednak kod:

public void Execute(PluginData data)
{
    string toWrite = “Hacking .Net”;
    string constdata = String.Intern(“Const data”);
    unsafe
    {
        GCHandle handleConst = GCHandle.Alloc(constdata, GCHandleType.Pinned);
        GCHandle hackString = GCHandle.Alloc(toWrite, GCHandleType.Pinned);
        byte* dst = (byte*)handleConst.AddrOfPinnedObject().ToPointer();
        byte* src = (byte*)hackString.AddrOfPinnedObject().ToPointer();
        //*(dst – 4) = (byte)toWrite.Length;
        for (int i = 0; i < toWrite.Length << 1; i++)
            *dst++ = *src++;
        *dst = 0;
    }
}

Takie rozwiązanie jako alternatywne nadesłał Adam Rafałko. Nie do końca ono spełniało, założenie bo Adam (chyba celowo ;)) użył krótszego stringu niż Hacking .NET i jego kod ucinał kawałek napisu. Aby wyświetlał się cały napis konieczna jest zmiana, którą już ja dodałem – zmiana długości łańcucha w pamięci (zakomentowana linia – którą trzeba by napisać prawidłowo). Dodatkowo plus za sprawienie, że wiem iż jest funkcja String.Intern, choć jeszcze nie wiem co ona daje 🙂

StructLayout

Każdy kto używał P/Invoke zetknął się z takowym atrybutem. Używało się, przy dodawaniu typów nieobecnych w .NET do framework’a aby można z nich było skorzystać z kodu C#. Standardowe użycie takiego parametru wyglądało mniej więcej tak:

    [StructLayout(LayoutKind.Explicit)]
    public struct _PROCESSOR_INFO_UNION
    {
        [FieldOffset(0)]
        internal uint dwOemId;
        [FieldOffset(0)]
        internal ushort wProcessorArchitecture;
        [FieldOffset(2)]
        internal ushort wReserved;
    }

Niby nic podejrzanego, ale co się stanie gdy nasz plugin napiszemy tak?

    [StructLayout(LayoutKind.Explicit)]
    class NaughtyByStructLayoutPlugin : IPlugin
    {
        internal class PluginHack
        {
            public string Text;
        }
        [FieldOffset(0)]
        private PluginData _original;
        [FieldOffset(0)]
        private PluginHack _hack;
        public void Execute(PluginData data)
        {
            _original = data;
            _hack.Text = “Hacking .NET”;
        }
    }

Przeanalizujemy ten kod. Atrybut StructLayout jest wymagany, aby móc ustawiać przesunięcia danych pól w obiekcie. Następnie ustalamy przesunięcia. Widzimy ten atrybut FieldOffset z ustawionym takim samym przesunięciem wynoszącym 0 dla obu obiektów? Wszystko jasne? Dzięki takiemu potraktowaniu obiekt typu PluginData oraz obiekt typu PluginHack będą okupować ten sam fragment pamięci. A dzięki temu, że Text jest publiczny możemy go sobie jawnie przeładować czym tylko chcemy.
O takie właśnie rozwiązanie chodziło. Adrian Ciura oraz Rafał Jasica przesłali właśnie takie rozwiązanie. Brawo!
Jest jeszcze jedno rozwiązanie, choć bardziej z kategorii humorystycznych 🙂

public void Execute(PluginData data)
{
    int cursorPosY = Console.CursorTop;
    new Thread(p =>
    {
        Thread.Sleep(100); // time may vary
        Console.SetCursorPosition(0, cursorPosY);
        Console.WriteLine(“Hacking .NET”);
        Console.SetCursorPosition(0, cursorPosY + 2);
    }).Start();
}

Interesujące? 🙂 Takie rozwiązanie nadesłał także Bo.
Niby takie małe hakowanie, ale czy da się napsuć coś więcej? Pisanie bezpośrednio do pamięci czy też dostawanie się do wewnętrznej pamięci za pomocą innych obiektów może nam już napsuć w kodzie dość mocno. No bo możemy sobie wskazać adres łańcucha na adres np. 0xbaadfeed :). Pytanie, czy za pomocą tych technik można napsuć coś w kodzie metod? Może komuś się uda? Nie wiem jak wy, ale ja uwielbiam analizować rzeczy trochę niżej niż to co mamy dostępne od ręki.
Post był inspirowany wpisem Is .NET Type-Safe? napisanym przez p. Wiktora Zychlę wykładowcę na UWr oraz byłego MVP w kategorii C#.
PS. Co do nagród to muszę jeszcze pomyśleć. Nie spodziewałem się tylko zgłoszeń 🙂