{"id":235,"date":"2021-07-28T18:54:29","date_gmt":"2021-07-28T16:54:29","guid":{"rendered":"https:\/\/theredwindows.net\/?p=235"},"modified":"2022-07-07T19:32:49","modified_gmt":"2022-07-07T17:32:49","slug":"reflexion-en-powershell","status":"publish","type":"post","link":"https:\/\/theredwindows.net\/index.php\/2021\/07\/28\/reflexion-en-powershell\/","title":{"rendered":"R\u00e9flexion en PowerShell"},"content":{"rendered":"\n<p>Lorsqu\u2019un programme \u00e0 la capacit\u00e9 d\u2019acc\u00e9der \u00e0 ses propres m\u00e9tadonn\u00e9es, de magnifiques perspectives s\u2019offrent alors \u00e0 nous. Modifier son comportement en <code>RunTime<\/code> ou ajouter des types s\u2019inscrivent dans ce que l\u2019on appelle la r\u00e9flexion et .NET le permet, particuli\u00e8rement gr\u00e2ce au C#. Puisque PowerShell est enti\u00e8rement construit sur ce dernier, nombres de possibilit\u00e9s s\u2019ouvrent alors. Dans cet article, nous \u00e9tudierons les mani\u00e8res d\u2019utiliser la r\u00e9flexion en PowerShell qui est l\u2019une des cl\u00e9s du PInvoke.<\/p>\n\n\n\n<p>Pour des questions de lisibilit\u00e9, je d\u00e9finis &#8220;m\u00e9thode&#8221; de 2 mani\u00e8res diff\u00e9rentes puisque je risque de souvent l\u2019utiliser:<\/p>\n\n\n\n<ul><li>m\u00e9thode: qui d\u00e9signe un ensemble de d\u00e9marches raisonn\u00e9es pour atteindre un but (merci le Robert).<\/li><li><strong>m\u00e9thode<\/strong>: fonction s\u2019appliquant sur et depuis un type sous-jacent (un type \u00e9tant une classe ou un objet .NET).<\/li><\/ul>\n\n\n\n<h2>L\u2019architecture .NET<\/h2>\n\n\n\n<p>Avant de commencer, j\u2019ai besoin de vous expliquer certains concepts et d\u2019\u00e9tablir un ensemble de d\u00e9finition afin de ne pas vous perdre. Le framework .NET est compos\u00e9 de plusieurs \u00e9l\u00e9ments, chacun permettant d\u2019achever une t\u00e2che particuli\u00e8re. L\u2019ensemble de ces composants cr\u00e9\u00e9 une sorte de structure pour .NET. Tout en haut, nous avons le code dans sa repr\u00e9sentation la plus brute\u00a0: C#, F#, ou encore VB.NET. Lorsque de tels programmes sont compil\u00e9s, le code est transform\u00e9 en CIL\/IL pour Common Intermediate Language (aussi connu sous le nom de MSIL pour Microsoft Intermediate Language). Ce dernier ressemble d\u2019une certaine fa\u00e7on \u00e0 l\u2019assembleur sans toutefois int\u00e9grer des appels directement au syst\u00e8me. Voila un exemple de code en MSIL:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">.class private auto ansi beforefieldinit SharpSample.Program\n\textends [mscorlib]System.Object\n{\n\t\/\/ Methods\n\t.method private hidebysig static \n\t\tvoid Main (\n\t\t\tstring[] args\n\t\t) cil managed \n\t{\n\t\t\/\/ Method begins at RVA 0x2050\n\t\t\/\/ Code size 113 (0x71)\n\t\t.maxstack 4\n\t\t.entrypoint\n\t\t.locals init (\n\t\t\t[0] class [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.LdapConnection connection,\n\t\t\t[1] class [System]System.Net.NetworkCredential cred,\n\t\t\t[2] string 'filter',\n\t\t\t[3] string[] attributesToReturn,\n\t\t\t[4] class [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.SearchRequest searchRequest,\n\t\t\t[5] class [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.SearchResponse searchResponse,\n\t\t\t[6] class [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.SearchResultAttributeCollection attributes\n\t\t)\n\n\t\tIL_0000: nop\n\t\tIL_0001: ldstr \"testlab.local\"\n\t\tIL_0006: newobj instance void [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.LdapConnection::.ctor(string)\n\t\tIL_000b: stloc.0\n\t\tIL_000c: ldstr \"admin\"\n\t\tIL_0011: ldstr \"passw0rd1!\"\n\t\tIL_0016: ldstr \"testlab.local\"\n\t\tIL_001b: newobj instance void [System]System.Net.NetworkCredential::.ctor(string, string, string)\n\t\tIL_0020: stloc.1\n\t\tIL_0021: ldloc.0\n\t\tIL_0022: ldloc.1\n\t\tIL_0023: callvirt instance void [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.DirectoryConnection::set_Credential(class [System]System.Net.NetworkCredential)\n\t\tIL_0028: nop\n\t\tIL_0029: ldstr \"cn=WS01\"\n\t\tIL_002e: stloc.2\n\t\tIL_002f: ldc.i4.1\n\t\tIL_0030: newarr [mscorlib]System.String\n\t\tIL_0035: dup\n\t\tIL_0036: ldc.i4.0\n\t\tIL_0037: ldstr \"msDS-SupportedEncryptionTypes\"\n\t\tIL_003c: stelem.ref\n\t\tIL_003d: stloc.3\n\t\tIL_003e: ldstr \"\"\n\t\tIL_0043: ldloc.2\n\t\tIL_0044: ldc.i4.2\n\t\tIL_0045: ldloc.3\n\t\tIL_0046: newobj instance void [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.SearchRequest::.ctor(string, string, valuetype [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.SearchScope, string[])\n\t\tIL_004b: stloc.s 4\n\t\tIL_004d: ldloc.0\n\t\tIL_004e: ldloc.s 4\n\t\tIL_0050: callvirt instance class [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.DirectoryResponse [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.DirectoryConnection::SendRequest(class [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.DirectoryRequest)\n\t\tIL_0055: castclass [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.SearchResponse\n\t\tIL_005a: stloc.s 5\n\t\tIL_005c: ldloc.s 5\n\t\tIL_005e: callvirt instance class [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.SearchResultEntryCollection [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.SearchResponse::get_Entries()\n\t\tIL_0063: ldc.i4.0\n\t\tIL_0064: callvirt instance class [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.SearchResultEntry [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.SearchResultEntryCollection::get_Item(int32)\n\t\tIL_0069: callvirt instance class [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.SearchResultAttributeCollection [System.DirectoryServices.Protocols]System.DirectoryServices.Protocols.SearchResultEntry::get_Attributes()\n\t\tIL_006e: stloc.s 6\n\t\tIL_0070: ret\n\t} \/\/ end of method Program::Main\n\n\t.method public hidebysig specialname rtspecialname \n\t\tinstance void .ctor () cil managed \n\t{\n\t\t\/\/ Method begins at RVA 0x20cd\n\t\t\/\/ Code size 8 (0x8)\n\t\t.maxstack 8\n\n\t\tIL_0000: ldarg.0\n\t\tIL_0001: call instance void [mscorlib]System.Object::.ctor()\n\t\tIL_0006: nop\n\t\tIL_0007: ret\n\t} \/\/ end of method Program::.ctor\n\n} \/\/ end of class SharpSample.Program<\/pre>\n\n\n\n<p>Qui en C# est:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"csharp\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">using System.Net;\nusing System.DirectoryServices.Protocols;\n\nnamespace SharpSample\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            LdapConnection connection = new LdapConnection(\"testlab.local\");\n            NetworkCredential cred = new NetworkCredential(\"admin\", \"passw0rd1!\", \"testlab.local\");\n            connection.Credential = cred;\n            string filter = \"cn=\" + \"WS01\";\n            string[] attributesToReturn = new string[] { \"msDS-SupportedEncryptionTypes\" };\n            SearchRequest searchRequest = new SearchRequest(\"\", filter, SearchScope.Subtree, attributesToReturn);\n\n            SearchResponse searchResponse = (SearchResponse)connection.SendRequest(searchRequest);\n            SearchResultAttributeCollection attributes = searchResponse.Entries[0].Attributes;\n        }\n    }\n}\n<\/pre>\n\n\n\n<p>Pour cela, le code est pass\u00e9 par le compilateur au CLI pour Common Language Infrastructure, qui est l\u2019un des deux c\u0153urs de .NET. Ce dernier se divise en plusieurs axes. Nous avons le CTS pour Common Type System qui permet d\u2019\u00e9tablir une mani\u00e8re commune aux diff\u00e9rents langages compatible .NET, de d\u00e9finir leurs types. Ainsi, CTS d\u00e9finit les types valeurs et les types r\u00e9f\u00e9rences. Les premiers sont des objets repr\u00e9sent\u00e9s par leur valeur (comme les entiers ou les cha\u00eenes de caract\u00e8res), contrairement aux seconds qui agissent similairement aux pointeurs en C\/C++. CTS regroupe \u00e9galement ces types dans plusieurs cat\u00e9gories. Par exemple, les classes, les structures, ou bien les \u00e9num\u00e9rations. Chacune de ces cat\u00e9gories poss\u00e8de une syntaxe propre et un ensemble de propri\u00e9t\u00e9s propres qui sont \u00e9galement d\u00e9finies. On peut retrouver ces types primitifs dans l\u2019espace de nom commun \u00e0 tout langage du framework .NET <code>System<\/code> (situ\u00e9 dans la dll c\u0153ur de .NET <code>mscorlib.dll<\/code>). Pourtant, qu\u2019importe le langage, il existera des points communs entre ces types primitifs. Ces points communs sont d\u00e9finis gr\u00e2ce \u00e0 CLS pour Common Language Specification et doivent \u00eatre appliqu\u00e9 par CLI pour pouvoir obtenir une &#8220;inter-op\u00e9rabilit\u00e9&#8221; parmi les langages compatibles. Ce code, construit \u00e0 partir de la CLI, est appel\u00e9 code manag\u00e9 et est transform\u00e9 en code machine lors de son ex\u00e9cution par le CLR pour Common Language Runtime et son compilateur JIT (Just In Time). En C#, si le code n&#8217;est pas directement compil\u00e9 en langage Machine, cela permet plusieurs choses. En premier lieu, les binaires g\u00e9n\u00e9r\u00e9s sont multi-syst\u00e8mes d&#8217;exploitation. En second lieu, cela permet l&#8217;int\u00e9gration de code dynamique gr\u00e2ce au compilateur JIT. Enfin, la construction du langage se rapproche beaucoup de Java, qui a \u00e9norm\u00e9ment inspir\u00e9 C#, ce qui rend la transition entre les deux langages plus ais\u00e9e.<\/p>\n\n\n\n<p>Par ailleurs, les types sont des objets et non simplement un espace en m\u00e9moire&nbsp;!<\/p>\n\n\n\n<h2>La R\u00e9flexion en C#<\/h2>\n\n\n\n<p>Pour commencer, il est n\u00e9cessaire de pr\u00e9ciser que les assemblys contiennent des modules, qui contiennent des types qui contiennent des membres. Chacun de ces blocs intervient dans la r\u00e9flexion. Commen\u00e7ons par les assemblys. Les assemblys (ou assemblage en fran\u00e7ais, oui c\u2019est moche) sont fondamentales pour .NET et sont le minimum \u00e0 d\u00e9ployer (penser \u00e0 la place de la cellule dans la th\u00e9orie cellulaire, pour les aficionados de biologie: la cellule est l\u2019unit\u00e9 de base du vivant, pourtant elle poss\u00e8de des composants). Ce sont usuellement des fichiers exe, dll (mais pas que) et contiennent des collections de modules (et par cons\u00e9quent de types \u2026) et de ressources. Elles poss\u00e8dent un certain nombre de propri\u00e9t\u00e9s qui sont retenues dans un manifeste qui exhibe, le nom de l\u2019assembly, la version de l\u2019assembly, une liste des r\u00e9f\u00e9rences \u00e0 d\u2019autres assemblys notamment. La plupart des dlls de r\u00e9f\u00e9rence sont situ\u00e9es dans le GAC pour Global Assembly Cache qui est par d\u00e9faut en <code>C:\\Windows\\Assembly<\/code> (pour .NET de 1.0 \u00e0 3.5) ou en<code> C:\\Windows\\Microsoft.NET\\Assembly<\/code> (\u00e0 partir de .NET 4.0). Le but n\u2019\u00e9tant pas l\u2019exhaustivit\u00e9, vous pouvez trouver plus de d\u00e9tails <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.microsoft.com\/fr-fr\/dotnet\/standard\/assembly\/\" data-type=\"URL\" data-id=\"https:\/\/docs.microsoft.com\/fr-fr\/dotnet\/standard\/assembly\/\" target=\"_blank\">ici<\/a>. Un module, quant \u00e0 lui, est simplement une assembly (structurellement parlant) mais qui ne contient pas de manifeste. Ainsi, il expose les m\u00e9tadonn\u00e9es de type, et le code compil\u00e9. Fait amusant, puisque toutes les assemblys .NET doivent supporter CTS et CLS, il est possible de joindre \u00e0 une assembly plusieurs modules qui ne proviennent pas du m\u00eame langage ! Concernant les types et les membres. Ils ont bri\u00e8vement \u00e9t\u00e9 introduits plus haut. Cependant, il faut apporter quelques pr\u00e9cisions. Un type peut repr\u00e9senter une classe et peut donc contenir des &#8220;sous-types&#8221;. Par cons\u00e9quent on peut \u00e9galement utiliser le vocabulaire des classes pour les types. Un champ <code>Field<\/code> est une valeur nomm\u00e9e dans une classe. Une propri\u00e9t\u00e9 <code>Property<\/code> est un type particulier de m\u00e9thode qui permet d\u2019interagir avec un champ. Les constructeurs <code>Constructor<\/code> sont des <strong>m\u00e9thodes<\/strong> sp\u00e9cifiques qui aident \u00e0 initialiser les classes. On d\u00e9signe par membre tout ce qui vient d\u2019\u00eatre mentionn\u00e9&#8230;<\/p>\n\n\n\n<p>Pour manipuler ces blocs, on utilise plusieurs espaces de noms et classes. Le premier est <code>System.Reflection<\/code> qui contient deux classes tr\u00e8s importantes:<code> Assembly<\/code> et <code>Module<\/code>. <code>System.Type<\/code> qui permet, comme son nom l\u2019indique, de manipuler les types (et repr\u00e9sente par ailleurs les types du CTS). Pour interagir dynamiquement avec les assemblys\/types, on utilise <code>System.Reflection.Emit<\/code>. De mani\u00e8re g\u00e9n\u00e9rale, la r\u00e9flexion s\u2019articule autour de 2 \u00e9tapes, on obtient d\u2019abord le type au travers d\u2019une assembly, puis on acc\u00e8de \u00e0 ses membres.<\/p>\n\n\n\n<h2>Interagir avec les types et les m\u00e9thodes<\/h2>\n\n\n\n<p>Pour obtenir le type d\u2019un objet, rien de plus simple. En PowerShell il n\u2019existe pas d\u2019op\u00e9rateur <code data-enlighter-language=\"csharp\" class=\"EnlighterJSRAW\">typeof<\/code>, cependant l\u2019utilisation de la <strong>m\u00e9thode<\/strong> <code>.GetType()<\/code> permet d\u2019obtenir le m\u00eame r\u00e9sultat; mais on peut \u00e9galement, dans les cas o\u00f9 cela est possible, acc\u00e9der au type gr\u00e2ce &#8220;au chemin de sa classe&#8221; (e.g <code data-enlighter-language=\"powershell\" class=\"EnlighterJSRAW\">[System.DirectoryServices.DirectoryEntry]<\/code>). Il faut \u00e9galement mentionner qu\u2019il peut \u00eatre utilis\u00e9 pour obtenir un type particulier contenu dans une assembly sp\u00e9cifique. Pour cela, il faut obtenir un objet de type <code>RuntimeAssembly<\/code> issu de l\u2019assembly en question. C\u2019est dans ce but que l\u2019on utilise la propri\u00e9t\u00e9 <code>.Assembly<\/code><code data-enlighter-language=\"powershell\" class=\"EnlighterJSRAW\"><\/code> auquel on applique la <strong>m\u00e9thode<\/strong> <code>.GetType()<\/code> avec en param\u00e8tre une cha\u00eene de caract\u00e8re d\u00e9crivant le type. Un exemple issu du bypass de l\u2019article sur le contournement de protection PowerShell:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"powershell\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">[Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')<\/pre>\n\n\n\n<p>Ou pour faire \u00e9cho \u00e0 l\u2019exemple ci-dessus:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"powershell\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">[System.DirectoryServices.ActiveDirectoryRights].Assembly.GetType('System.DirectoryServices.DirectoryEntry')<\/pre>\n\n\n\n<p>Cette m\u00e9thode est tr\u00e8s pratique puisqu\u2019elle permet d\u2019acc\u00e9der aux types non expos\u00e9s (ou priv\u00e9s) lors de leur d\u00e9claration \u00e0 l\u2019image du premier exemple. Gr\u00e2ce \u00e0 ce type obtenu, on peut alors \u00e9num\u00e9rer ses membres gr\u00e2ce \u00e0 l\u2019utilisation de la cmdlet <code data-enlighter-language=\"prolog\" class=\"EnlighterJSRAW\">Get-Member<\/code> (ou la <strong>m\u00e9thode<\/strong> <code>.GetMembers()<\/code> attention beaucoup d\u2019informations seront retourn\u00e9es). La cmdlet s\u2019organise autour d\u2019un nombre assez faible de param\u00e8tres. Le flag <code>MemberType<\/code> indique le type de membre qu\u2019il faut retourner, par exemple les propri\u00e9t\u00e9s (<code>Property<\/code>) ou les <strong>m\u00e9thodes<\/strong> (<code>Method<\/code>). Pour obtenir l\u2019acc\u00e8s \u00e0 un membre en particulier on utilise la <strong>m\u00e9thode<\/strong> <code>.GetMember()<\/code> qui prend en param\u00e8tre le nom du membre, et \u00e9ventuellement les caract\u00e9ristiques de sa d\u00e9finition, les <a rel=\"noreferrer noopener\" href=\"https:\/\/docs.microsoft.com\/fr-fr\/dotnet\/api\/system.reflection.bindingflags?view=net-5.0\" data-type=\"URL\" data-id=\"https:\/\/docs.microsoft.com\/fr-fr\/dotnet\/api\/system.reflection.bindingflags?view=net-5.0\" target=\"_blank\"><code>BindingFlags<\/code><\/a>. Il existe des m\u00e9thodes similaires pour les <code>Fields<\/code> les <code>Constructors<\/code>\u2026 Pour lire\/modifier ces membres, on peut utiliser les <strong>m\u00e9thodes<\/strong> <code>.GetValue()<\/code> et <code>.SetValue()<\/code>. La premi\u00e8re prend en param\u00e8tre un objet ou une liste d\u2019objets qui seront retourn\u00e9es, la seconde prend en param\u00e8tre un objet ou une liste d\u2019objet qui seront la nouvelle valeur. On peut alors combiner ces actions pour modifier le comportement de certaines fonctions accessibles, comme l\u2019alphabet d\u2019encodage de base64 (exemple issu de at-ps)\u00a0:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"powershell\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">[Byte[]] $DataToEncode = 0..255\n\n# donn\u00e9s encod\u00e9 avant la modification\n$EncodedData1 = [Convert]::ToBase64String($DataToEncode)\n\n# On obtient en premier lieu un acc\u00e8s au champ base64Table que l\u2019on peut trouver dans DnSpy\n\n$Base64TableField = [Convert].GetField('base64Table', [Reflection.BindingFlags] 'NonPublic, Static')\n$OriginalBase64Alphabet = $Base64TableField.GetValue($null)\n\n# Ce qui nous permet de changer les deux premiers caract\u00e8res\n\n$OriginalBase64Alphabet[0] = [Char] 'B'                                                                                                                                                                          $OriginalBase64Alphabet[1] = [Char] 'A'\n\n# On peut utiliser la variable Base64TableField pour lui appliquer la m\u00e9thode SetValue() avec notre nouvel alphabet\n\n$Base64TableField.SetValue($null, $OriginalBase64Alphabet)\n# On encode et on peut alors comparer\n\n$EncodedData2 = [Convert]::ToBase64String($DataToEncode)\n\n$EncodedData1                                                                                                                                                                                                    $EncodedData2<\/pre>\n\n\n\n<p>L\u2019instanciation d\u2019un type est universelle et n\u00e9cessaire (<em>a priori<\/em> pour les amateurs de philosophie). En PowerShell on dispose principalement de 3 mani\u00e8res de proc\u00e9der. La premi\u00e8re est sp\u00e9cifique \u00e0 PowerShell, c\u2019est la cmdlet <code data-enlighter-language=\"powershell\" class=\"EnlighterJSRAW\">New-Object<\/code>. Un param\u00e8tre <code>ArgumentList<\/code> permet d\u2019ajouter des informations au type cr\u00e9\u00e9. Seulement des m\u00e9thodes plus \u00e9l\u00e9mentaires existent. La premi\u00e8re provient de la classe <code>System.Activator<\/code> avec la <strong>m\u00e9thode<\/strong> <code>::CreateInstance()<\/code> qui prend en param\u00e8tre le type \u00e0 instancier, puis une liste d\u2019objets \u00e9ventuelle qui sera compl\u00e9t\u00e9e par la liste en question dans le param\u00e8tre suivant. La derni\u00e8re m\u00e9thode utilise les constructeurs avec la <strong>m\u00e9thode<\/strong> <code>.GetConstructor()<\/code> qui prend en param\u00e8tre une liste de types qui repr\u00e9sente l\u2019ordre des types des arguments fournis. Le r\u00e9sultat de cette <strong>m\u00e9thode<\/strong> (peut \u00eatre plac\u00e9 dans une variable pour plus de lisibilit\u00e9 de code) est pass\u00e9 \u00e0 la <strong>m\u00e9thode<\/strong> <code>.Invoke()<\/code> \u2013 qui s\u2019applique \u00e0 la classe <code>System.Reflection.ConstructorInfo<\/code> \u2013 qui prend en param\u00e8tre une liste qui contient les arguments fournis. Voil\u00e0 un exemple pour illustrer mon propos, avec la classe <code>System.DirectoryServices.DirectoryEntry<\/code>:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"powershell\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">New-Object System.DirectoryServices.DirectoryEntry -ArgumentList \"LDAP:\/\/CN=AdminSDHolder,CN=System,DC=contoso,DC=local\"\n\n[System.Activator]::CreateInstance([System.DirectoryServices.DirectoryEntry], [Object[]], @(\"LDAP:\/\/CN=AdminSDHolder,CN=System,DC=contoso,DC=local\"))\n\n[System.DirectoryServices.DirectoryEntry].GetConstructor([Type[]] @([String])).Invoke([Object[]] @(\"LDAP:\/\/CN=AdminSDHolder,CN=System,DC=contoso,DC=local\"))<\/pre>\n\n\n\n<p>Il est important de comprendre que <code data-enlighter-language=\"powershell\" class=\"EnlighterJSRAW\">New-Object<\/code> ne permet pas d\u2019acc\u00e9der aux types priv\u00e9s tout comme la m\u00e9thode naturelle pour PowerShell (e.g <code data-enlighter-language=\"powershell\" class=\"EnlighterJSRAW\">[System.DirectoryServices.Directory]<\/code>). Pourtant les types priv\u00e9s peuvent tout de m\u00eame \u00eatre instanci\u00e9s gr\u00e2ce aux 2 autres m\u00e9thodes vues plus haut\u00a0<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"powershell\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">$Type = [Ref].Assembly.GetType('System.Management.Automation.AmsiUtils')\n$Instance = [Activator]::CreateInstance($Type)<\/pre>\n\n\n\n<p>De plus, il est souvent plus simple d\u2019utiliser la troisi\u00e8me m\u00e9thode lorsque nous souhaitons d\u00e9clarer des attributs. <\/p>\n\n\n\n<p>D\u00e9sormais en possession de types, il faut maintenant d\u00e9terminer les moyens d\u2019ex\u00e9cuter des <strong>m\u00e9thodes<\/strong>. En utilisant la r\u00e9flexion, il est possible, \u00e9videmment, d\u2019acc\u00e9der aux <strong>m\u00e9thodes<\/strong> d\u2019un type en utilisant <code>.GetMethod()<\/code>. Elle prend plusieurs arguments, en premier le nom de la <strong>m\u00e9thode<\/strong>, puis des <code>BindingFlags<\/code>, puis un binder (tr\u00e8s souvent nul), ensuite un tableau de type repr\u00e9sentant les types des arguments, et enfin un tableau de <code>ParameterModifier<\/code> (tr\u00e8s souvent nul \u00e9galement). <code>.GetMethod()<\/code> retourne un <code>System.Reflection.MethodInfo<\/code> qui nous permet d\u2019appeler <code>.Invoke()<\/code> qui prend en param\u00e8tre l\u2019objet sur lequel on applique la <strong>m\u00e9thode<\/strong>, puis la liste des arguments. Voici un exemple issu de at-ps pour convertir un entier en hexad\u00e9cimal:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"powershell\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">$IntToConvert = 1094795585 \n$ToStringMethod = [Int32].GetMethod('ToString',[Reflection.BindingFlags] 'Public, Instance', $null, [Type[]] @([String]), $null) \n$ToStringMethod.Invoke($IntToConvert, [Object[]] @('X8'))<\/pre>\n\n\n\n<p>Au travers de cette section, vous avez pu apercevoir l\u2019importance des espaces de noms pr\u00e9sent\u00e9s dans la seconde section qui sont tr\u00e8s utilis\u00e9s.<\/p>\n\n\n\n<h2>Interagir avec les Assembly<\/h2>\n\n\n\n<p>Tr\u00e8s souvent lorsque l\u2019on utilise PowerShell, on souhaite utiliser du code C# qui n\u2019est pas natif. Une fa\u00e7on de proc\u00e9der tr\u00e8s connu dans ces situations est d\u2019utiliser la cmdlet <code data-enlighter-language=\"powershell\" class=\"EnlighterJSRAW\">Add-Type<\/code> qui compile le code qui lui est fournit et utilise les fonctionnalit\u00e9s, que nous allons explorer dans cette partie, pour l\u2019ajouter \u00e0 notre session. Comme nous pouvons le voir sur cette capture d&#8217;\u00e9cran (faite avec <code>procmon64.exe<\/code>), l\u2019appel de <code data-enlighter-language=\"powershell\" class=\"EnlighterJSRAW\">Add-Type<\/code> provoque la cr\u00e9ation d\u2019un dossier d\u2019un fichier temporaire et la cr\u00e9ation d\u2019un processus <code>csc.exe<\/code>. <\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" width=\"1024\" height=\"77\" src=\"https:\/\/theredwindows.net\/wp-content\/uploads\/2021\/07\/AddType-1024x77.png\" alt=\"\" class=\"wp-image-240\" srcset=\"https:\/\/theredwindows.net\/wp-content\/uploads\/2021\/07\/AddType-1024x77.png 1024w, https:\/\/theredwindows.net\/wp-content\/uploads\/2021\/07\/AddType-300x23.png 300w, https:\/\/theredwindows.net\/wp-content\/uploads\/2021\/07\/AddType-768x58.png 768w, https:\/\/theredwindows.net\/wp-content\/uploads\/2021\/07\/AddType-400x30.png 400w, https:\/\/theredwindows.net\/wp-content\/uploads\/2021\/07\/AddType-800x60.png 800w, https:\/\/theredwindows.net\/wp-content\/uploads\/2021\/07\/AddType.png 1261w\" sizes=\"(max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>L\u2019ensemble de ces artefacts rend l\u2019op\u00e9ration facilement d\u00e9tectable. Pourtant l\u2019espace de nom <code>System.Reflection<\/code> poss\u00e8de de tr\u00e8s puissantes capacit\u00e9s qui nous aiderons \u00e0 contourner ce probl\u00e8me. Charger un assembly est une t\u00e2che tr\u00e8s ais\u00e9e. Pour cela, deux possibilit\u00e9s. Si l\u2019assembly se trouve sur le disque, on utilise la commande suivante: <code data-enlighter-language=\"powershell\" class=\"EnlighterJSRAW\">[System.Reflection.Assembly]::LoadFile(\"Chemin Complet vers le fichier\")<\/code>. Si l\u2019assembly n\u2019est pas sur le disque, il est possible de la charger depuis un tableau de bytes qui le repr\u00e9sente. Pour obtenir ce dernier vous pouvez, sur votre machine, utiliser la <strong>m\u00e9thode<\/strong> <code>.ReadAllBytes()<\/code> qui prend en param\u00e8tre le chemin complet vers l\u2019assembly, de la classe <code>System.IO.File<\/code>: <code data-enlighter-language=\"powershell\" class=\"EnlighterJSRAW\">$assemblyBytes = [System.IO.File]::ReadAllBytes(\"Chemin Complet vers le fichier\")<\/code>. Une possibilit\u00e9 autre est d\u2019exhiber l\u2019assembly sur un serveur web et d\u2019utiliser la <strong>m\u00e9thode<\/strong> <code>.DownloadData()<\/code> qui prend en argument l\u2019URL o\u00f9 se situe le binaire: <code data-enlighter-language=\"powershell\" class=\"EnlighterJSRAW\">$assemblyBytes = (New-Object System.Net.WebClient).DownloadData(\"http:\/\/contoso.com\/assembly.exe\")<\/code>. Une fois en possession du tableau de bytes, on peut utiliser la m\u00e9thode <code>::Load()<\/code>: <code data-enlighter-language=\"powershell\" class=\"EnlighterJSRAW\">$assembly = [System.Reflection.Assembly]::Load($assemblyBytes)<\/code><\/p>\n\n\n\n<p>S\u2019il s\u2019ag\u00eet d\u2019un ex\u00e9cutable .NET, on peut alors invoquer la <strong>m\u00e9thode<\/strong> <code>Main()<\/code> en utilisant la propri\u00e9t\u00e9 <code>.EntryPoint<\/code> et lui appliquer la <strong>m\u00e9thode<\/strong> <code>.Invoke()<\/code> ; \u00e0 l\u2019image de tout \u00e0 l\u2019heure avec les param\u00e8tres suivants <code data-enlighter-language=\"powershell\" class=\"EnlighterJSRAW\">$null, [Object[]] @(@(,([String[]] @())))<\/code> o\u00f9 le dernier tableau contient les arguments \u00e0 passer. Si l\u2019assembly n\u2019est pas un ex\u00e9cutable, <code>.EntryPoint<\/code> retourne <code data-enlighter-language=\"powershell\" class=\"EnlighterJSRAW\">$null<\/code>. Sinon il faut conna\u00eetre les espaces de noms\/classes\/m\u00e9thodes de l\u2019assembly charg\u00e9e pour les appeler de la m\u00eame mani\u00e8re que les types habituels. Petit exemple qui charge <code>Rubeus.exe<\/code>:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"powershell\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">$assembly = [System.Reflection.Assembly]::LoadFile(\"D:\\tools\\Rubeus\\Rubeus\\bin\\debug\\Rubeus.exe\")\n$assembly.EntryPoint.Invoke($null, [Object[]] @(@(,([String[]] @(\"triage\")))))<\/pre>\n\n\n\n<p>Attention tout de m\u00eame, les <strong>m\u00e9thodes<\/strong> de chargements d\u2019assembly sont support\u00e9es par l\u2019AMSI \u00e0 partir de .NET 4.8, vos outils offensifs pr\u00e9f\u00e9r\u00e9s seront s\u00fbrement d\u00e9tect\u00e9s&nbsp;!<\/p>\n\n\n\n<p>Une mani\u00e8re de contourner ce probl\u00e8me est d\u2019ajouter dynamiquement du code \u00e0 notre session. Pour cela il faut dans un premier temps cr\u00e9er un module en m\u00e9moire. Ainsi, nous devons ajouter \u00e0 notre domaine d\u2019ex\u00e9cution \u2013 c\u2019est \u00e0 dire l\u2019ensemble des informations de l\u2019environnement dans lequel l\u2019application est ex\u00e9cut\u00e9e \u2013, une assembly dynamique. Pour en cr\u00e9er une, nous utilisons la classe <code>System.Reflection.AssemblyName<\/code> avec en param\u00e8tre le nom de l\u2019assembly. Puis on ajoute l\u2019assembly au domaine en utilisant la m\u00e9thode <code>.DefineDynamicAssembly()<\/code> qui prend en argument la classe instanci\u00e9e pr\u00e9c\u00e9demment, et le droit d\u2019acc\u00e8s \u00e0 l\u2019assembly qui sera issu de l\u2019\u00e9num\u00e9ration <code>AssemblyBuilderAccess<\/code> de la classe <code>System.Reflection.Emit<\/code>. La plupart du temps, il faut choisir le flag <code>Run<\/code>. A partir de l\u00e0, si vous avez bien suivis, vous savez qu\u2019il faut ajouter un module \u00e0 notre assembly dynamique. Ce faisant, on utilise la <strong>m\u00e9thode<\/strong> <code>.DefineDynamicModule()<\/code> qui prend en param\u00e8tre le nom du module.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"powershell\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">$DynAssembly = New-Object System.Reflection.AssemblyName('InMemoryAssembly')\n$AssemblyBuilder = [AppDomain]::CurrentDomain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run)\n$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('InMemoryModule', $False)<\/pre>\n\n\n\n<p>On peut alors d\u00e9finir de nouveaux types, comme les classes avec la <strong>m\u00e9thode<\/strong> <code>.DefineType()<\/code> qui prend en param\u00e8tre le nom du type, puis ses attributs de type contenu dans l\u2019\u00e9num\u00e9ration <code>System.Reflection.TypeAttributes<\/code>, le type parents et une liste de types des interfaces impl\u00e9ment\u00e9es. Une fois la <strong>m\u00e9thode<\/strong> cr\u00e9\u00e9e on peut lui ajouter des instructions. Pour cela il faut directement lui fournir du code MSIL. Dans un premier temps nous obtenons un objet <code>System.Reflection.Emit.ILGenerator<\/code> gr\u00e2ce \u00e0 la m\u00e9thode <code>.GetILGenerator()<\/code>. Puis on adjoint les instructions MSIL avec la <strong>m\u00e9thode<\/strong> .Emit() qui prend en param\u00e8tre l\u2019instruction MSIL de la structure <code>System.Reflection.Emit.OpCode<\/code> et l\u2019\u00e9ventuel param\u00e8tre de cette instruction. Pour appeler une m\u00e9thode, il est n\u00e9cessaire d\u2019obtenir un objet <code>.MethodeInfo()<\/code> qui repr\u00e9sente la <strong>m\u00e9thode<\/strong> donn\u00e9e avec l\u2019instruction <code>Call<\/code>. Pour ajouter un point d\u2019entr\u00e9 \u00e0 notre assembly, on utilise <code>.SetEntryPoint()<\/code> avec en param\u00e8tre la variable contenant le type <code>MethodBuilder<\/code> qui nous a permis de construire cette derni\u00e8re. Enfin, on cr\u00e9er le type avec la <strong>m\u00e9thode<\/strong> <code>.CreateType()<\/code>. La cr\u00e9ation de code dynamique est permise par l\u2019utilisation d\u2019un compilateur JIT qui transforme le bytecode IL en code machine. Voici un exemple pour un programme qui affiche <code>Hello, world!<\/code> (merci SpecterOps)\u00a0:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"powershell\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">$Domain = [AppDomain]::CurrentDomain\n$DynAssembly = New-Object System.Reflection.AssemblyName('HelloWorld')\n$AssemblyBuilder = $Domain.DefineDynamicAssembly($DynAssembly, [Reflection.Emit.AssemblyBuilderAccess]::Run)\n$ModuleBuilder = $AssemblyBuilder.DefineDynamicModule('HelloWorld.exe')\n$TypeBuilder = $ModuleBuilder.DefineType('MyClass', [Reflection.TypeAttributes]::Public)\n$MethodBuilder = $TypeBuilder.DefineMethod('Main', [Reflection.MethodAttributes] 'Public, Static', [Void], @([String[]]))\n$Generator = $MethodBuilder.GetILGenerator()\n$WriteLineMethod = [Console].GetMethod('WriteLine', [Type[]] @([String]))\n# Recreate the MSIL from the disassembly listing.\n$Generator.Emit([Reflection.Emit.OpCodes]::Ldstr, 'Hello, world!')\n$Generator.Emit([Reflection.Emit.OpCodes]::Call, $WriteLineMethod)\n$Generator.Emit([Reflection.Emit.OpCodes]::Ret)\n$AssemblyBuilder.SetEntryPoint($MethodBuilder)\n$TypeBuilder.CreateType()\n[MyClass]::Main(@())<\/pre>\n\n\n\n<p>Et un autre provenant de PSReflect qui ajoute une<strong> m\u00e9thode<\/strong> <code>::GetSize()<\/code> \u00e0 une structure:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"powershell\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">$SizeMethod = $StructBuilder.DefineMethod('GetSize', 'Public, Static', [Int], [Type[]] @())\n$ILGenerator = $SizeMethod.GetILGenerator()\n$ILGenerator.Emit([Reflection.Emit.OpCodes]::Ldtoken, $StructBuilder)\n$ILGenerator.Emit([Reflection.Emit.OpCodes]::Call, [Type].GetMethod('GetTypeFromHandle'))\n$ILGenerator.Emit([Reflection.Emit.OpCodes]::Call, [Runtime.InteropServices.Marshal].GetMethod('SizeOf', [Type[]] @([Type])))\n$ILGenerator.Emit([Reflection.Emit.OpCodes]::Ret)<\/pre>\n\n\n\n<p>La possibilit\u00e9 d\u2019\u00e9mettre du code dynamiquement est tr\u00e8s utile pour faire du Pinvoke. Il est assez rare d\u2019utiliser cette fa\u00e7on de proc\u00e9der pour ajouter du code dynamiquement, mais il faut toujours garder en m\u00e9moire qu\u2019elle existe, pour ajouter de courtes <strong>m\u00e9thodes<\/strong> \u00e0 des objets personnalis\u00e9s. Pour des assemblys plus cons\u00e9quente, il va de soi que l\u2019on pr\u00e9f\u00e9rera utiliser un chargement d\u2019assembly, d\u2019autant plus que programmer en CIL est tr\u00e8s inconfortable. <\/p>\n\n\n\n<p>Les assemblys, modules classes et autres <strong>m\u00e9thodes<\/strong> peuvent parfois n\u00e9cessiter l\u2019ajout d\u2019attributs qui augmentent le contenu des m\u00e9tadonn\u00e9es. Il est possible de fournir \u00e0 du code dynamique de tels objets. Pour cela, on commence par obtenir un constructeur de l\u2019attribut en question. Puis il faut \u00e9tablir l\u2019ensemble des champs\/propri\u00e9t\u00e9s qu\u2019il doit contenir (si ces donn\u00e9es vous sont inconnues, il vous reste <code>.GetProperties()<\/code> et <code>.GetFields()<\/code> ou la documentation de Microsoft de l\u2019assembly en question, ultimement utiliser DnSpy\/IlSpy). Pour cela on utilise les <strong>m\u00e9thodes<\/strong> <code>.GetProperty()<\/code> et <code>.GetField()<\/code> avec en argument le nom de la propri\u00e9t\u00e9\/champ. On construit alors un tableau contenant ces objets, et un autre tableau contenant leurs valeurs. On utilise alors la classe <code>System.Reflection.Emit.CustromAttributeBuilder<\/code> avec en param\u00e8tre le constructeur, puis les \u00e9ventuels param\u00e8tres, ensuite le tableau pr\u00e9c\u00e9demment construit et enfin les valeurs associ\u00e9s.On applique finalement la <strong>m\u00e9thode<\/strong> <code>.SetCustomAttribute()<\/code> avec en param\u00e8tre l\u2019objet cr\u00e9\u00e9. L\u2019exemple le plus parlant sera dans l\u2019article qui suit celui-ci, sur le PInvoke.<\/p>\n\n\n\n<p>Notre \u00e9tude de la r\u00e9flexion touche \u00e0 sa fin, j\u2019esp\u00e8re que cet article vous aura plu&nbsp;!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Lorsqu\u2019un programme \u00e0 la capacit\u00e9 d\u2019acc\u00e9der \u00e0 ses propres m\u00e9tadonn\u00e9es, de magnifiques perspectives s\u2019offrent alors \u00e0 nous. Modifier son comportement en RunTime ou ajouter des types s\u2019inscrivent dans ce que l\u2019on appelle la r\u00e9flexion et .NET le permet, particuli\u00e8rement gr\u00e2ce au C#. Puisque PowerShell est enti\u00e8rement construit sur ce dernier, nombres de possibilit\u00e9s s\u2019ouvrent alors. [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":[],"categories":[2],"tags":[],"_links":{"self":[{"href":"https:\/\/theredwindows.net\/index.php\/wp-json\/wp\/v2\/posts\/235"}],"collection":[{"href":"https:\/\/theredwindows.net\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/theredwindows.net\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/theredwindows.net\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/theredwindows.net\/index.php\/wp-json\/wp\/v2\/comments?post=235"}],"version-history":[{"count":9,"href":"https:\/\/theredwindows.net\/index.php\/wp-json\/wp\/v2\/posts\/235\/revisions"}],"predecessor-version":[{"id":309,"href":"https:\/\/theredwindows.net\/index.php\/wp-json\/wp\/v2\/posts\/235\/revisions\/309"}],"wp:attachment":[{"href":"https:\/\/theredwindows.net\/index.php\/wp-json\/wp\/v2\/media?parent=235"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/theredwindows.net\/index.php\/wp-json\/wp\/v2\/categories?post=235"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/theredwindows.net\/index.php\/wp-json\/wp\/v2\/tags?post=235"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}