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:
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 ? ).