Mono.Cecil del 2–inject af funktionskald

by mslot 18. december 2011 18:49

Så fik jeg tid til at skrive lidt om Mono.Cecil igen.  Sidst skrev jeg hvordan man lavede en reference til en anden assembly, og skrev den “modificerede” assembly, med den nye reference, ned på disken. Dog er der ikke meget ved kun at lave referencer til andre assemblies, dette bliver lidt kedelig i længden. Jeg vil i dette indlæg forklare hvordan man kan kalde metoderne i den nylige referencerede assembly.

For at kunne lave et sådanne hack generelt, hvor injecter metode kald ind i andre assemblies, kræves der at man har lidt styr på de mere lavtstående dele af C#, nemlig IL kode. Dog er det minimalt hvor meget der bliver brugt i dette indlæg, så det er lige til overkomme, men vil man lave noget mere avanceret så vil jeg anbefale at man nærstudere MSIL lidt mere. Hvis du vil læse mere om MSIL opkoderne som bliver brugt længere nede, før du læser videre, så kan du tage et kig på

  1. call
  2. ldstr

Disse to opkoder bliver injectet via Mono.Cecils ILProcessor.  Jeg har ikke kunne finde noget dokumentation på hvordan man bruger ILProcessoren, da Mono.Cecils dokumentation er skrevet til en ældre version end den seneste version af Mono.Cecil. Så den eneste dokumentation er her. Hvilket vil sige er lidt af en by i Rusland, hvis man gerne vil have lidt inspiration til hvad det kan bruges til Smiley. Dog kan jeg gå så langt og sige at man nemt kan få skabt sig et overblik via førnævnte FAQ, og lidt tid i tænkeboksen.

Jeg vil bygge lidt videre på min sidste artikel, hvor jeg har udvidet koden lidt,

            Mono.Cecil.TypeDefinition type = lib.MainModule.Types.Where(x => x.Name == "Writer").Single();
            Mono.Cecil.MethodDefinition method = type.Methods.Where(x => x.Name == "WriteLine").Single();

            Mono.Cecil.MethodReference reference = source.MainModule.Import(method);

            Mono.Cecil.TypeDefinition globalType = source.MainModule.Types.Where(x => x.Name == "Global").Single();
            Mono.Cecil.MethodDefinition applicaionErrorMethod = globalType.Methods.Where(x => x.Name == "Application_Error").Single();

            Mono.Cecil.Cil.ILProcessor processor = applicaionErrorMethod.Body.GetILProcessor();
            Mono.Cecil.Cil.Instruction call = processor.Create(Mono.Cecil.Cil.OpCodes.Call, reference);
            Mono.Cecil.Cil.Instruction str = processor.Create(Mono.Cecil.Cil.OpCodes.Ldstr,"h");

            processor.InsertBefore(applicaionErrorMethod.Body.Instructions[0], str);
            processor.InsertAfter(applicaionErrorMethod.Body.Instructions[0], call);

Koden er faktisk lige til, dog har jeg brugt lang tid på at skrive den, da jeg ikke synes at der er meget dokumentation omkring på nettet, som fortæller hvordan man injecter et funktionskald. De fleste af de små snippets/spørgsmål/svar som flyder omkring på nettet, beskriver hvordan man gør dette med de ældre versioner af Mono.Cecil, som gør brug af en CilWorker til at injecte med. Denne metode er blevet byttet ud med førnævnte IlProcessor. Dette er faktisk den største forskel. Dog ligger der ikke et samlet eksempel på nettet på hvordan man laver et inject til en ekstern defineret metode. Jeg vil faktisk våge og påstå at min er det eneste fulde eksempel!! Hvilket jeg synes er lidt ærgeligt, da det er lidt sjovt at sidde og nørkle med.

Det der tog længst tid for mig at regne ud, var hvordan man brugte den injectede assembly reference. For man skulle ved første øjekast tro at man “bare” importerede den fundne metode og efterfølgende brugte den fundne metode i selve ILProcessoren:

            ...
            Mono.Cecil.MethodDefinition method = type.Methods.Where(x => x.Name == "WriteLine").Single();

            source.MainModule.Import(method);
            ...
            Mono.Cecil.Cil.ILProcessor processor = applicaionErrorMethod.Body.GetILProcessor();
            Mono.Cecil.Cil.Instruction call = processor.Create(Mono.Cecil.Cil.OpCodes.Call, method);
            Mono.Cecil.Cil.Instruction str = processor.Create(Mono.Cecil.Cil.OpCodes.Ldstr,"h");

            processor.InsertBefore(applicaionErrorMethod.Body.Instructions[0], str);
            processor.InsertAfter(applicaionErrorMethod.Body.Instructions[0], call);

Dog skal man bruge den returnerede “Mono.Cecil.MethodReference reference = …” videre i forløbet, ellers ved Mono.Cecil ikke hvad for assembly metoden referer til. Det jeg gjorde i første artikel, var altså bare en side af injectningen, hvor man fandt og tilknyttede referencen til den assembly som man vil modificere. Dog skal man bruge den returnerede reference, for at kompileren ikke brokker sig. Dejligt med compiletime tjek!

Det der sker i koden er at man først finder den metode som man vil injecte

            Mono.Cecil.TypeDefinition type = lib.MainModule.Types.Where(x => x.Name == "Writer").Single();
            Mono.Cecil.MethodDefinition method = type.Methods.Where(x => x.Name == "WriteLine").Single();

            Mono.Cecil.MethodReference reference = source.MainModule.Import(method);

Her finder jeg altså metoden “WriteLine” i den statiske klasse “Writer”. Denne MetodeDefinition bruger jeg til at  Importere hele den tilhørende assembly, og den returnerede MethodReference, gemmer jeg. Denne reference skal jeg bruge når jeg rent faktisk skal injecten.

Injecten har jeg besluttet mig for at lave i web applikationens Global.Application_Error metode. At finde denne metode er simpel

            Mono.Cecil.TypeDefinition globalType = source.MainModule.Types.Where(x => x.Name == "Global").Single();
            Mono.Cecil.MethodDefinition applicaionErrorMethod = globalType.Methods.Where(x => x.Name == "Application_Error").Single();

og dejlig letlæselig også takket være Linq.

Jeg har nu

  1. importeret den assembly som indeholder den metode som jeg vil kalde
  2. fundet metoden som injecten skal ske i

Nu kan jeg begynde at injecte:

            Mono.Cecil.Cil.ILProcessor processor = applicaionErrorMethod.Body.GetILProcessor();
            Mono.Cecil.Cil.Instruction call = processor.Create(Mono.Cecil.Cil.OpCodes.Call, reference);
            Mono.Cecil.Cil.Instruction str = processor.Create(Mono.Cecil.Cil.OpCodes.Ldstr,"h");

            processor.InsertBefore(applicaionErrorMethod.Body.Instructions[0], str);
            processor.InsertAfter(applicaionErrorMethod.Body.Instructions[0], call);

For at injecte fx kald i eksisterende kode, skal man først have fat i den kontekst som man vil injecte i. Vi vil gerne injecte noget kode Application_Error metoden, derfor skal vi have fat i kroppen af denne metode. Kroppen har Mono.Cecil pænt pakket ind til os, og de har endda gjort det nemt for os at injecte kode ind med ILProcessor’en. Jeg frygtede, før jeg gik igang, at man selv skulle til at flytte rundt på IL opkoder, og derved nemt ødelægge det eksisterende, men dette behøves man ikke med ILProcessoren. Den hjælper os nemlig. Både med at injecte, men også med at oprette MSIL instruktioner. Med Create metoden laver man instruktioner som skal injectes, og med InsertBefore, og InsertAfter, injecter man. Dette gør at koden er meget nemmere at læse. Man kan derfor fokusere mere på at skrive god og letlæseligt kode, og mindre på at forstå, og huske hvad MSIL kode der skal konstrueres.

Selve oprettelsen af det IL kode der skal injectes sker altså gennem ILProcessorens Create metode.

            Mono.Cecil.Cil.Instruction call = processor.Create(Mono.Cecil.Cil.OpCodes.Call, reference);
            Mono.Cecil.Cil.Instruction str = processor.Create(Mono.Cecil.Cil.OpCodes.Ldstr,"h");

Læg mærke til at jeg bruger WriteLine metodens reference, og ikke selve dens MethodDefinition. Jeg opretter to instruktioner. En som sørger for at kalde metoden, og en som sørger for at lægge metodens paramteren på stakken. For at injecte kaldet indsætter jeg disse to instruktioner i Application_Error’ens krop. Jeg vil gerne indsætte kaldet først i metoden

            processor.InsertBefore(applicaionErrorMethod.Body.Instructions[0], str);
            processor.InsertAfter(applicaionErrorMethod.Body.Instructions[0], call);

Her smider jeg strengen, “h”, på stakken og laver et fald til min WriteLine metode.

Rækkefølgen af indsættelsen er ikke hel ligegyldig. Hvis du ikke er inde i hvordan metodekald sker i de “mere primitive” sprog, så skal man, før man kalder metoden, smide alle metodens parametre på stakken, og derefter kalde metoden. Call koden sørger derefter for at tage de argumenter som den skal bruge fra stakken og oprette en ny stak, som den bruger til at udføre sin handling. Efter endt kald rykkes dens  kaldstak ned, og en eventuel returværdi smides på kalderens stak, som kalderen kan vælge at bruge i dens videre forløb.

Tilsidst skal vi have gemt den modificerede assembly

            source.MainModule.Write("WebApplicationModified.dll");

Der er nu gemt en modificeret assembly med Writer.WriteLine(“h”) kaldet i Application_Error metoden. Du kan se dette ved at inspicere den gemte dll med ILSpy. For at sikre sig at man ikke ødelagt og lavet en korrupt dll, kan man køre PEVerify på den. PEVerify tjekker om alt er som det skal være i den nyoprettede assembly. Har man injectet noget galt og usammenhængede MSIL kode, skal PEVerify nok fortælle dig det. Dette kunne fx være at man havde glemt at smide en parameter på stakken før man kalder en given metode.

Det var alt for nu.

Tags:

.NET | C# | Mono.Cecil

Hvordan henter man XML’en ud i en string fra XmlTextWriter?

by mslot 14. december 2011 15:36
Svaret er meget simpelt:
   1:  using (System.IO.StringWriter stringWriter = new System.IO.StringWriter())
   2:              {
   3:                  using (XmlTextWriter doc = new XmlTextWriter(stringWriter))
   4:                  {
   5:                      string s = stringWriter.ToString();
   6:                  }
   7:              }

Man giver bare en StringWriter til XmlTextWriter’en. Derefter kan man kalde ToString på StringWriter’en, og man har nu en string repræsentation af det xml som er blevet skrevet med XmlTextWriter’en.

Tags:

.NET | C# | XML

Mono.Cecil

by mslot 8. december 2011 23:12

Mono.Cecil er smart. Det er et bibliotek som kan bruges til manipulere managed dll filer efter de er kompileret. Det er en meget god værktøj hvis du fx står med en dll, som du ikke kan debugge, da du ikke source filen, eller måske ikke kan installere windbg på serveren. Her kan man hente dll’en ned, og smide trace information ind de steder som skal kigges efter i sømmene.

Jeg er lige begyndt at kigge på Mono.Cecil, men jeg er allerede benovet over dens styrke. Jeg har fx lavet en lille WriterLibrary, som jeg gerne vil bruge i en anden dll, kaldet WebApplication.dll. Min lille tankespil går ud på at WebApplication.dll er min, ikke source kode defineret, ikke windbg-able, applikation, som jeg gerne vil trace. WriterLibrary er min “store” debug og trace bibliotek som jeg gerne vil bruge til at kigge WebApplication efter i sømmene.

Der er ikke meget dokumentation omkring Mono.Cecil på nettet, dog er det meget simpelt at komme igang med. For at komme igang vil jeg springe lige ud i det. Jeg har lavet et konsol program som ser sådan her ud

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CecilTest
{
    class Program
    {
        static void Main(string[] args)
        {
            Mono.Cecil.AssemblyDefinition source =
                Mono.Cecil.AssemblyDefinition.ReadAssembly(@"C:sti\til\\WebApplication.dll");

            Mono.Cecil.AssemblyDefinition lib =
                Mono.Cecil.AssemblyDefinition.ReadAssembly(@"C:\sti\til\WriterLibrary.dll");

            source.MainModule.Import(lib.MainModule.Types[1]);
            source.MainModule.Write("WebApplicationModified.dll");
        }
    }
}

For at alt dette virker skal vi også have fat i Mono.Cecil, men dette er let. Man henter Mono.Cecil med NuGet. Åbn Package Manager Console og kør “Install-Package Mono.Cecil”. Kommandoen henter de dll’er der skal til for man kan gøre brug af Mono.Cecil. Min WriterLibrary er her blot en simpel class library som ser sådan her ud

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace WriterLibrary
{
    public class Writer
    {
        public void WriteLine(string s)
        {
            Console.WriteLine("wrote:" + s);
        }
    }
}

Når man laver en Import på en hvilken som helst type fra WriterLibrary til WebApplication dll’en, så sørger Mono.Cecil også for at lave en reference til WriterLibrary dll’en. Du kan selv tjekke efter ved at inspicere WebApplication dll’en før konsol applikationen er kørt, og efter. Da man skal betale for Reflector, vil jeg råde alle til at bruge IlSpy. Google den, og hent programmet. Den kan ikke alt det som Reflector kan, men den er gratis, og man kan kigge på dll’er. Meget praktisk Smiley

Ved at inspicere WebApplication dll’en efter konsol applikationen er kørt, kan man se at referencen er tilføjet. Meget smart. Man kan nu bruge WriterLibrary til at lave trace på WebApplication. Jeg har endnu ikke lavet noget injection, men det er noget jeg vil skrive om efterfølgende, da jeg selv lige skal lære at bruge Mono.Cecil. Min vision med dette eksempel er at jeg vil prøve at injecte WriteLine funktionskaldet ind i WebApplication.dll’ens Global.Application_Error, da jeg ved at WebApplication gør brug af en Global.asax.

Jeg vil opdatere denne artikel med links efterfølgende når jeg skriver mere om hvordan man injecter funktionskald.

Håber at der er nogen der kan bruge mit lille eksempel til noget.

Tags:

C# | Mono.Cecil | .NET

ASP.NET Panel og jquery

by mslot 28. november 2011 19:05

En panel i asp.net bliver expandet til en div. Vil du have fat i denne div via id’et, kan du ikke bare tilgå den ved at skrive

$('#PanelId')

da en id’et på panelet bliver sammensat af flere forskellige faktorer, fx. dens parent osv. For at bruge overstående er man derfor nød til at kende navngivningsreglerne, eller se på kildekoden som IIS’en smider tilbage i hovedet på browseren, dog kan man med jquery gøre noget meget smart. Man kan nemlig søge DOM’en efter div id’er indeholdende en tekst, meget lig SQL’s LIKE statement.

Har vi defineret en panel sådan her:

<asp:Panel ID="panelInfoBox" Style="border: 1px solid black;" runat="server"></asp:Panel>

Kan vi få fat i fx teksten, ved at bruge lidt jquery magi:

$("div[id$=panelInfoBox]").text("hello world");

Her sætter vi teksten i panelet. Dette er meget nemmere at læse, og virker hvis man omrokerer hele html’en.

Tags:

indspark | jquery | asp.net

Googlemaps

by mslot 24. november 2011 14:33

Lat og lon til googlemaps, skal separeres med et punktum, og ikke et komma. Dette har nok højest sandsynlig noget at gøre med hvordan et decimal tal repræsenteres i Danmark i forhold til “resten” af verden (bare rolig der er andre lande som også bruger komma). Læs mere om decimal tal her. Er du interesseret i at læse mere om radix punktets historie, så kan det gøres her.

Tags:

googlemaps | indspark

Blog

by mslot 19. november 2011 19:16

Jeg har i kort tid haft mit eget hjemmesyede system anvendt her på domænet, men jeg har dog besluttet mig for at skifte, da jeg ikke gad lave al funktionaliteten selv. Dette kan ses som dovnskab, dog har jeg valgt at fokusere på at bruge min fritid på at udgive indlæg, og ikke udvikle (selvom jeg ææælsker at udvikle). Jeg har valgt blogengine.net, da den har alt det jeg skal bruge:

  1. Man kan udgive via Livewriter
  2. Den understøtter reCaptcha
  3. Både Livewriter og blogengine.net understøtte hilight af kode
  4. Man kan sammenkæde artikler

På den tekniske side kan der nævnes at selve motoren er baseret på .NET 4.0 som man kan udvide via extensions. Det synes Martin Slot fanma er najs!!

Jeg vil via denne blog udgive artikler omkring debugging, C# og T-SQL. Mit mål er at udgive 5 små indspark, og 1 dybdegående artikel om måneden.

Jeg vil holde sproget på dansk.

Tags:

Om

Martin Slot. C#. Javascript og T-SQL.

Indlæg