IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Contribuez Delphi Discussion :

Injection d'opcodes au runtime


Sujet :

Contribuez Delphi

  1. #1
    Membre averti
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    49
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 49
    Par défaut Injection d'opcodes au runtime
    Bonjour, je propose dans cette petite démo une démonstration du concept d'injection de code au runtime. L'idée est terriblement simple. Le code non compilé mais a éxécuter est placé dans un array de bytes. Quand le code arbitraire doit être éxécuté, il suffit de placer un pointeur de procédure sur l'array de bytes et appeller la procédure pointée.
    Je propose un "proof of concept" dans une simple TForm:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    type
      TProc = Procedure;
      TForm1 = class(TForm)
        procedure FormClick(Sender: TObject);
      private
        Proc: TProc;
        Assembly: Array Of UINT64;
      end;
     
    var
      Form1: TForm1;
     
     
    implementation
     
    {$R *.dfm}
     
    procedure TForm1.FormClick(Sender: TObject);
    var
      a: Integer;
    begin
     
      // prépare un array de bytes, qui représente du code machine x86 (opcodes)
      // pour des raisons de claretée dans la démonstration, une seule instruction est placée par 8 Bytes.
      // l'écriture des opcodes se fait de droite à gauche.
      // les données peuvent facilement venir d'un editeur de texte ou autres sources (un parser+compilo spécifique, fichier externe, etc)
      SetLength(Assembly,3);
      Assembly[0]:= $909090909090C033;   // XOR EAX,EAX
      Assembly[1]:= $909090000000FF05;   // ADD EAX,0x000000FF
      Assembly[2]:= $C390909090909090;   // RETURN
     
      // place un pointeur de procédure sur l'array et execute
      Proc := TProc(@Assembly[0]);
      Proc;
     
      // le résultat doit être passé en assembleur inline, la variable locale "a"
      // n'étant pas accessible dans la sous-procédure que représente l'array d'opcodes
      Asm
        Mov a,eax
      End;
     
      // vérification
      Caption := IntToStr(a);
    end;
     
    {
    applications:
    * Système de protection logiciel: la procédure de vérification peut être décriptée (en utilsant le serial comme clé de décriptage par exemple)
      puis appellée sans apparaitre en clair.Sans le bon serial, on imagine facilement le cauchemard de violation d'accés généré...
    * Scripting: l'array d'opcode est généré au runtime puis appellé et intégré au scritping host sans passer une compilation "conventionelle",
      évaluateur d'expression rapide, etc.
    * de manière plug générale: "injection de code" au runtime.
     
    avantage:
    * dans une application modulaire, le traitement implique que beaucoup de CALL seront générés, ici tout peut être regroupé et optimisé,
      et appellé via un CALL unique
     
    inconvénients:
    * la difficulté technique que représente la génération de l'array d'opcodes (via analyse de texte, création logique 'paramétrique', etc)
    }
     
    end.
    Comme précisé dans les commentaires, celà ouvre tout un champ d'application, particulièrement dans le scripting. En effet, cette manière de faire est équivalente au fait de placer des données brutes dans l'assembleur en ligne (via dd, dq, etc). Sauf qu'ici, les données brutes peuvent êtres "non déterminées" (arbitraires) et éxécutées, à priori sans restriction ( peut être même des opcodes d'instructions privilégiées ? ).

  2. #2
    Expert confirmé

    Avatar de Nono40
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2002
    Messages
    8 640
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 58
    Localisation : France, Loir et Cher (Centre)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : Industrie

    Informations forums :
    Inscription : Mai 2002
    Messages : 8 640
    Par défaut
    Citation Envoyé par az0101 Voir le message
    ( peut être même des opcodes d'instructions privilégiées ? ).
    Tu n'auras pas les droits pour les exécuter dans ce contexte. Car même si tu les injectes de la sorte, au niveau processeur tu resteras dans la tâche en cours avec les droits associés.

    Vu coté processeur que le code soit précompilé ou nom ça reste du code machine.

    D'ailleurs le code dans le 'array of byte' est déjà compilé pour moi. Il est compilé à la main, mais compilé.
    Delphi :
    La F.A.Q. , 877 réponses à vos questions !
    264 sources à consulter/télécharger !

  3. #3
    Membre Expert

    Profil pro
    Leader Technique
    Inscrit en
    Juin 2005
    Messages
    1 756
    Détails du profil
    Informations personnelles :
    Âge : 47
    Localisation : France, Rhône (Rhône Alpes)

    Informations professionnelles :
    Activité : Leader Technique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Juin 2005
    Messages : 1 756
    Par défaut
    Tu fais ça sous quel OS ?

    Parce logiquement, si tu fais ça comme ça, le DEP (Data execution prevention) doit t'envoyer bouller.

    Cette façon de faire est similaire à celle utilisée par les hackers pour exploiter les débordements de buffer : Tu places le code que tu veux exécuter à un emplacement mémoire qui est censé contenir des données (et pas du code), puis tu utilises un artifice pour faire en sorte que les données soient exécutées comme s'il s'agissait de code.

    Dans ton cas tu te sert d'un pointeur de procédure.
    Dans le cas d'un débordement de buffer, le buffer est écrit de telle sorte qu'il écrase l'adresse de retour sur la pile pour qu'a la fin de l'exécution de la procédure, ce soit le code injecté qui soit exécuté au lieu de retourner à l'appelant.

    Pour se protéger de ce genre de vulnérabilité, le DEP a été mis en place pour interdire l'exécution de code dans un segment de données...
    Donc normalement, à partir de Windows XP SP2, ce type d'astuce ne doit pas fonctionner à moins de désactiver le DEP.

    Autrement dit, il ne faut pas passer par un simple tableau déclaré en Delphi.
    Il faut commencer par allouer un bloc mémoire avec VirtualAlloc, en précisant au moins une des constantes PAGE_EXECUTE_xxxx pour le paramètre flProtect, écrire le code machine à cet emplacement, l'exécuter avec ta méthode, puis libérer la mémoire allouée.

  4. #4
    Membre averti
    Profil pro
    Inscrit en
    Mai 2009
    Messages
    49
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Mai 2009
    Messages : 49
    Par défaut
    Oui, vos précisions tombent à point. je vient de réaliser que cette méthode ne marche que si la "prévention contre l'éxécution des données" est appliquée seulement pour les programmes/services windows, autrement si la DEP est en mode parano là démo provoque des violations d'accès.

    ( propriétés sytèmes, performances, paramètres, prévention de l'éxécution des données).

    En effet en mode "paranoiaque", windows ne permet pas mon petit exploit ( qui pour ceux qui connaissent n'en est pas un car de nombreux cracks de protection physique, tel que ceux pour les licences syncrosofts utilisent l'injection de code ( telle que présentée dans cet démo), eux mêmes provoquent des violations d'accès quand la DEP est en mode parano.

    Cependant tant que le réglage DEP est limité au système, cette methode marche.

  5. #5
    Membre éclairé
    Avatar de gb_68
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2006
    Messages
    232
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Haut Rhin (Alsace)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2006
    Messages : 232
    Par défaut
    Bonjour,
    il est aussi possible de redéfinir la protection d'une zone mémoire avec VirtualProtect (-> PAGE_EXECUTE_READWRITE). Je m'étais aussi déjà amusé à générer du code à l'exécution, notamment pour transformer des fonctions membres en fonctions libres.

    A ce propos, il y a un excellent tutoriel de Sébastien Doeraene (mais assez pointu ), avec des exemples d'injections d'opcode à l'exécution.

    Enfin, dans l'unité Classes de Delphi, le même principe est appliqué pour créer une callback WindowProc propre à un objet, via la fonction MakeObjectInstance (© Borland, CodeGear, Embarcadero, ... ?)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    function MakeObjectInstance(Method: TWndMethod): Pointer;
    const
      BlockCode: array[1..2] of Byte = (
        $59,       { POP ECX }
        $E9);      { JMP StdWndProc }
      PageSize = 4096;
    var
      Block: PInstanceBlock;
      Instance: PObjectInstance;
    begin
      if InstFreeList = nil then
      begin
        Block := VirtualAlloc(nil, PageSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
        Block^.Next := InstBlockList;
        Move(BlockCode, Block^.Code, SizeOf(BlockCode));
        Block^.WndProcPtr := Pointer(CalcJmpOffset(@Block^.Code[2], @StdWndProc));
        Instance := @Block^.Instances;
        repeat
          Instance^.Code := $E8;  { CALL NEAR PTR Offset }
          Instance^.Offset := CalcJmpOffset(Instance, @Block^.Code);
          Instance^.Next := InstFreeList;
          InstFreeList := Instance;
          Inc(Longint(Instance), SizeOf(TObjectInstance));
        until Longint(Instance) - Longint(Block) >= SizeOf(TInstanceBlock);
        InstBlockList := Block;
      end;
      Result := InstFreeList;
      Instance := InstFreeList;
      InstFreeList := Instance^.Next;
      Instance^.Method := Method;
    end;
    Donc de base, quasiment toutes les applications Delphi utilise ce principe (un bon "proof of concept" en somme ).

Discussions similaires

  1. Injection de MSIL au runtime
    Par smyley dans le forum Framework .NET
    Réponses: 18
    Dernier message: 25/11/2008, 10h22
  2. Centrino : quel Assembleur ? Opcodes 16-32-64 bits
    Par Arnaudv6 dans le forum Assembleur
    Réponses: 16
    Dernier message: 14/01/2004, 10h42
  3. [LG]runtime error 202
    Par picsou123 dans le forum Langage
    Réponses: 2
    Dernier message: 14/11/2003, 22h53
  4. Runtime VC++ ou MFC
    Par Elodie_nl dans le forum MFC
    Réponses: 9
    Dernier message: 03/12/2002, 17h23
  5. [Kylix] Runtime error 230 avec INDY
    Par Anonymous dans le forum EDI
    Réponses: 2
    Dernier message: 23/03/2002, 11h51

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo