Je comprends le concept de Polymorphic et Metamorphic code mais j'ai récemment lu la page Wikipedia sur les deux (pour quelle raison que je ne l'avais pas fait auparavant!). Maintenant, je veux vraiment essayer d'écrire du code métamorphique pour moi.
Je suis un maître d'aucune langue, un baroudeur de beaucoup. Je connais PHP, MySQL, c/c ++, Java, script Bash, Visual Basic 6, VBScripting, Perl, JavaScript.
Quelqu'un peut-il fournir un exemple de code métamorphique dans l'une de ces langues? Je voudrais voir un exemple de travail, même lorsque la sortie du programme est juste "Hello World", pour comprendre par exemple comment cela se produit (j'ai du mal à théoriser comment ces techniques peuvent être obtenues par la seule pensée mentale). N'importe quelle langue ferait vraiment l'affaire, ce ne sont que des langues préférées.
De plus, la recherche sur Internet n'a renvoyé qu'un nombre limité d'exemples en c/c ++ (même pas des exemples de travail complets, des extraits de code plus partiels), parce que les autres langues que j'ai suggérées ne sont pas assez bas pour avoir le pouvoir/flexibilité requise pour créer du code métamorphique?
Vous trouverez ci-dessous un exemple de ce que je pense classer comme du code métamorphique écrit en C.Je crains de ne pas avoir beaucoup d'expérience dans l'écriture de code C portable, il peut donc nécessiter des modifications pour être compilé sur d'autres plateformes (je ' m en utilisant une ancienne version de Borland sous Windows). En outre, il repose sur la plate-forme cible x86 car il implique une certaine génération de code machine. En théorie, il devrait cependant être compilé sur n'importe quel système d'exploitation x86.
Comment ça marche
Chaque fois que le programme est exécuté, il génère une copie modifiée aléatoirement de lui-même, avec un nom de fichier différent. Il imprime également une liste des décalages qui ont été modifiés afin que vous puissiez voir qu'il fait réellement quelque chose.
Le processus de modification est très simpliste. Le code source est simplement interprété avec des séquences d'instructions d'assemblage qui ne font rien. Lorsque le programme est exécuté, il trouve ces séquences et les remplace aléatoirement par un code différent (ce qui évidemment ne fait rien non plus).
Le codage en dur d'une liste de décalages n'est évidemment pas réaliste pour quelque chose que d'autres personnes doivent pouvoir compiler, de sorte que les séquences sont générées d'une manière qui les rend faciles à identifier dans une recherche à travers le code objet, espérons-le sans correspondre à aucun faux positif .
Chaque séquence commence par une opération Push sur un certain registre, un ensemble d'instructions qui modifient ce registre, puis une opération pop pour restaurer le registre à sa valeur initiale. Pour simplifier les choses, dans la source d'origine, toutes les séquences ne sont que Push EAX
, Huit NOP
s et POP EAX
. Dans toutes les générations suivantes de l'application, cependant, les séquences seront entièrement aléatoires.
Explication du code
J'ai divisé le code en plusieurs parties afin que je puisse essayer de l'expliquer étape par étape. Si vous voulez le compiler vous-même, il vous suffira de réunir toutes les parties.
Tout d'abord, certains assez standard comprennent:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
Ensuite, nous avons défini pour divers opcodes x86. Celles-ci seront généralement combinées avec d'autres valeurs pour générer une instruction complète. Par exemple, le Push
define (0x50
) En lui-même est Push EAX
, Mais vous pouvez dériver les valeurs des autres registres en ajoutant un décalage compris entre 0 et 7. Identique chose pour POP
et MOV
.
#define Push 0x50
#define POP 0x58
#define MOV 0xB8
#define NOP 0x90
Les six derniers sont les octets de préfixe de plusieurs opcodes à deux octets. Le deuxième octet code les opérandes et sera expliqué plus en détail ultérieurement.
#define ADD 0x01
#define AND 0x21
#define XOR 0x31
#define OR 0x09
#define SBB 0x19
#define SUB 0x29
const unsigned char prefixes[] = { ADD,AND,XOR,OR,SBB,SUB,0 };
JUNK
est une macro qui insère notre séquence d'opérations indésirables partout où nous voulons dans le code. Comme je l'ai expliqué précédemment, il s'agit tout d'abord d'écrire Push EAX
, NOP
et POP EAX
. JUNKLEN
est le nombre de NOP
s dans cette séquence - pas la longueur totale de la séquence.
Et au cas où vous ne seriez pas au courant, __emit__
Est une pseudo-fonction qui injecte des valeurs littérales directement dans le code objet. Je soupçonne que cela peut être quelque chose que vous devez porter si vous utilisez un compilateur différent.
#define JUNK __emit__(Push,NOP,NOP,NOP,NOP,NOP,NOP,NOP,NOP,POP)
#define JUNKLEN 8
Quelques variables globales où notre code sera chargé. Les variables globales sont mauvaises, mais je ne suis pas un bon codeur.
unsigned char *code;
int codelen;
Ensuite, nous avons une fonction simple qui lira notre code objet en mémoire. Je ne libère jamais cette mémoire parce que je m'en fiche.
Remarquez les appels de macro JUNK
insérés à des points aléatoires. Vous en verrez beaucoup plus dans le code. Vous pouvez les insérer presque n'importe où, mais si vous utilisez un vrai compilateur C (par opposition à C++), il se plaindra si vous essayez de les mettre avant ou entre les déclarations de variables.
void readcode(const char *filename) {
FILE *fp = fopen(filename, "rb"); JUNK;
fseek(fp, 0L, SEEK_END); JUNK;
codelen = ftell(fp);
code = malloc(codelen); JUNK;
fseek(fp, 0L, SEEK_SET);
fread(code, codelen, 1, fp); JUNK;
}
Une autre fonction simple pour réécrire l'application une fois qu'elle a été modifiée. Pour le nouveau nom de fichier, nous remplaçons simplement le dernier caractère du nom de fichier d'origine par un chiffre qui est incrémenté à chaque fois. Aucune tentative n'est faite pour vérifier si le fichier existe déjà et que nous n'écrasons pas un élément crucial du système d'exploitation.
void writecode(const char *filename) {
FILE *fp;
int lastoffset = strlen(filename)-1;
char lastchar = filename[lastoffset];
char *newfilename = strdup(filename); JUNK;
lastchar = '0'+(isdigit(lastchar)?(lastchar-'0'+1)%10:0);
newfilename[lastoffset] = lastchar;
fp = fopen(newfilename, "wb"); JUNK;
fwrite(code, codelen, 1, fp); JUNK;
fclose(fp);
free(newfilename);
}
Cette fonction suivante écrit une instruction aléatoire pour notre séquence indésirable. Le paramètre reg représente le registre avec lequel nous travaillons - ce qui sera poussé et sauté à chaque extrémité de la séquence. Le offset est le décalage dans le code où l'instruction sera écrite. Et espace donne le nombre d'octets qu'il nous reste dans notre séquence.
Selon l'espace dont nous disposons, nous pouvons être limités à quelles instructions nous pouvons écrire, sinon nous choisissons au hasard s'il s'agit d'un NOP
, MOV
ou de l'une des autres. NOP
n'est qu'un octet. MOV est de cinq octets: notre opcode MOV (avec le paramètre reg ajouté), et 4 octets aléatoires représentant le nombre déplacé dans le registre.
Pour les séquences de deux octets, la première n'est qu'un de nos préfixes choisis au hasard. Le second est un octet dans la plage 0xC0
À 0xFF
Où les 3 bits les moins significatifs représentent le registre principal - c'est-à-dire qui doit être réglé sur la valeur de notre reg
paramètre.
int writeinstruction(unsigned reg, int offset, int space) {
if (space < 2) {
code[offset] = NOP; JUNK;
return 1;
}
else if (space < 5 || Rand()%2 == 0) {
code[offset] = prefixes[Rand()%6]; JUNK;
code[offset+1] = 0xC0 + Rand()%8*8 + reg; JUNK;
return 2;
}
else {
code[offset] = MOV+reg; JUNK;
*(short*)(code+offset+1) = Rand();
*(short*)(code+offset+3) = Rand(); JUNK;
return 5;
}
}
Nous avons maintenant la fonction équivalente pour relire l'une de ces instructions. En supposant que nous ayons déjà identifié les reg
des opérations Push
et POP
à chaque extrémité de la séquence, cette fonction peut tenter de valider si l'instruction à la donnée offset
est une de nos opérations indésirables et que le registre primaire correspond à la donnée reg
= paramètre.
S'il trouve une correspondance valide, il renvoie la longueur de l'instruction, sinon il renvoie zéro.
int readinstruction(unsigned reg, int offset) {
unsigned c1 = code[offset];
if (c1 == NOP)
return 1; JUNK;
if (c1 == MOV+reg)
return 5; JUNK;
if (strchr(prefixes,c1)) {
unsigned c2 = code[offset+1]; JUNK;
if (c2 >= 0xC0 && c2 <= 0xFF && (c2&7) == reg)
return 2; JUNK;
} JUNK;
return 0;
}
Cette fonction suivante est la boucle principale recherchée et remplace les séquences indésirables. Il commence par rechercher un opcode Push
suivi d'un opcode POP
sur le même registre huit octets plus tard (ou autre chose JUNKLEN
a été défini à).
void replacejunk(void) {
int i, j, inc, space;
srand(time(NULL)); JUNK;
for (i = 0; i < codelen-JUNKLEN-2; i++) {
unsigned start = code[i];
unsigned end = code[i+JUNKLEN+1];
unsigned reg = start-Push;
if (start < Push || start >= Push+8) continue; JUNK;
if (end != POP+reg) continue; JUNK;
Si le registre s'avère être ESP
, nous pouvons le sauter en toute sécurité car nous n'utiliserons jamais ESP
dans notre code généré (les opérations de pile sur ESP
nécessitent une attention particulière qui n'est pas ça ne vaut pas la peine).
if (reg == 4) continue; /* register 4 is ESP */
Une fois que nous avons trouvé une combinaison probable de Push et POP
, nous essayons de lire les instructions entre les deux. Si nous réussissons à faire correspondre la longueur d'octets que nous attendons, nous considérons qu'une correspondance peut être remplacée.
j = 0; JUNK;
while (inc = readinstruction(reg,i+1+j)) j += inc;
if (j != JUNKLEN) continue; JUNK;
Nous choisissons ensuite l'un des 7 registres au hasard (comme expliqué précédemment, nous ne considérons pas ESP
), et écrivons les opérations Push
et POP
pour ce registre à chaque extrémité de la séquence.
reg = Rand()%7; JUNK;
reg += (reg >= 4);
code[i] = Push+reg; JUNK;
code[i+JUNKLEN+1] = POP+reg; JUNK;
Ensuite, tout ce que nous devons faire est de remplir l'espace intermédiaire en utilisant notre fonction writeinstruction
.
space = JUNKLEN;
j = 0; JUNK;
while (space) {
inc = writeinstruction(reg,i+1+j,space); JUNK;
j += inc;
space -= inc; JUNK;
}
Et voici où nous affichons le décalage que nous venons de corriger.
printf("%d\n",i); JUNK;
}
}
Enfin, nous avons la fonction principale. Cela appelle simplement les fonctions décrites précédemment. Nous lisons le code, remplaçons les fichiers indésirables, puis l'écrivons à nouveau. L'argument argv[0]
Contient le nom de fichier de l'application.
int main(int argc, char* argv[]) {
readcode(argv[0]); JUNK;
replacejunk(); JUNK;
writecode(argv[0]); JUNK;
return 0;
}
Et c'est tout ce qu'il y a à faire.
Quelques notes finales
Lors de l'exécution de ce code, vous devez évidemment vous assurer que l'utilisateur dispose des autorisations appropriées pour écrire un fichier au même emplacement que le code d'origine. Ensuite, une fois le nouveau fichier généré, vous devrez généralement le renommer si vous êtes sur un système où l'extension de fichier est importante, ou définir ses attributs d'exécution si cela est nécessaire.
Enfin, je pense que vous voudrez peut-être exécuter le code généré via un débogueur plutôt que de simplement l'exécuter directement et en espérant le meilleur. J'ai constaté que si je copiais le fichier généré sur l'exécutable d'origine, le débogueur était heureux de me laisser le parcourir tout en affichant le code source d'origine. Ensuite, chaque fois que vous arrivez à un point du code qui dit [~ # ~] junk [~ # ~], vous pouvez passer dans la vue Assembly et regarder le code qui a été généré.
Quoi qu'il en soit, j'espère que mes explications ont été raisonnablement claires, et c'est le genre d'exemple que vous cherchiez. Si vous avez des questions, n'hésitez pas à demander dans les commentaires.
Mise à jour bonus
En prime, je pensais que j'inclurais également un exemple de code métamorphique dans un langage de script. C'est assez différent de l'exemple C, car dans ce cas, nous devons muter le code source, plutôt que l'exécutable binaire, ce qui est un peu plus facile je pense.
Pour cet exemple, j'ai largement utilisé la fonction goto
de php. Chaque ligne commence par une étiquette et se termine par un goto
pointant vers l'étiquette de la ligne suivante. De cette façon, chaque ligne est essentiellement autonome, et nous pouvons les mélanger avec plaisir et continuer à faire fonctionner le programme exactement comme avant.
Les conditions et les structures de boucle sont un peu plus compliquées, mais elles doivent simplement être réécrites sous la forme d'une condition qui passe à l'une des deux étiquettes différentes. J'ai inclus des marqueurs de commentaires dans le code où les boucles seraient pour essayer de le rendre plus facile à suivre.
Exemple de code sur ideone.com
Tout ce que fait le code est l'écho de la copie mélangée de lui-même, vous pouvez donc facilement le tester sur ideone simplement en coupant et en collant la sortie dans le champ source et en l'exécutant à nouveau.
Si vous vouliez qu'il mute encore plus, il serait assez facile de faire quelque chose comme remplacer toutes les étiquettes et variables par un ensemble différent de chaînes aléatoires à chaque exécution du code. Mais j'ai pensé qu'il valait mieux essayer de garder les choses aussi simples que possible. Ces exemples sont juste destinés à démontrer le concept - nous n'essayons pas réellement d'éviter la détection. :)
Les exemples de code métamorphique disponibles publiquement sont limités par plusieurs facteurs:
1) Expertise: Le codage métamorphique est une technique extrêmement avancée en programmation informatique. Le nombre de programmeurs capables de coder du code métamorphique cohérent et propre adapté à l'échantillonnage est très faible.
2) Incitations financières: Le codage métamorphique a une utilisation limitée dans les applications commerciales. Pour cette raison, le nombre de programmeurs qui ont des compétences suffisantes pour créer du code métamorphique n'ont aucune exposition/incitation professionnelle à créer/apprendre des techniques de codage métamorphique.
3) Légitimité: Le codage métamorphique a de grandes applications dans la création de virus puissants. Par conséquent, tout professionnel responsable qui a créé du code métamorphique aurait des problèmes éthiques à distribuer librement des échantillons, car un pirate informatique peut être en mesure d'utiliser le code pour améliorer une attaque malveillante. À l'inverse, tout pirate qui était suffisamment compétent pour créer du code métamorphique n'aurait aucune raison de faire connaître ses compétences, si l'une de ses attaques était découverte, car il figurerait alors sur une très courte liste de suspects en fonction de ses compétences.
4) Secret: Enfin, et probablement la raison la plus réaliste pour laquelle le code métamorphique est si difficile à trouver est parce que tout programmeur qui démontre des compétences en programmation métamorphique, et est non appréhendé par les autorités pour cybercriminalité, est susceptible d'être recruté par une agence de sécurité gouvernementale, une entreprise de sécurité privée ou une société antivirus et les recherches/connaissances ultérieures du programmeur sont ensuite soumises à un accord de non-divulgation pour maintenir un avantage concurrentiel.
Pourquoi seulement C/C++
exemples?
Vous mentionnez trouver seulement C/C++
exemples de code de programmation poly/métamorphique et déduit que seuls les langages proches du matériel peuvent être poly/métamorphiques. Cela est vrai pour les définitions les plus strictes du code poly/métamorphique. Les langages interprétés peuvent avoir un comportement poly/métamorphique mais s'appuyer sur un interprète statiquement conforme pour s'exécuter, donc une grande partie de la "signature d'exécution" n'est pas modifiable. Seuls les langages de bas niveau compilés offrent la flexibilité de calcul d'avoir une "signature d'exécution" hautement mutable.
Voici du "polymorphe" PHP code que j'ai écrit. PHP étant un langage interprété et non un langage compilé, le vrai polymorphisme est impossible).
Code PHP:
<?php
// Programs functional Execution Section
system("echo Hello World!!\\n");
// mutable malicious payload goes here (if you were devious)
// Programs Polymorphic Engine Section
recombinate();
?>
<?php
function recombinate() {
$file = __FILE__; //assigns file path to $file using magic constant
$contents = file_get_contents($file); //gets file contents as string
$fileLines = explode("\n", $contents); //splits into file lines as string array
$varLine = $fileLines[2]; //extracts third file line as string
$charArr = str_split($varLine); //splits third line into char array
$augStr = augmentStr($charArr); //recursively augments char array
$newLine = implode("",$augStr); //rebuilds char array into string
$fileLines[2] = $newLine; //inserts string back into array
$newContents = implode("\n",$fileLines); //rebuilds array into single string
file_put_contents($file,$newContents); //writes out augmented file
sleep(1); //let the CPU rest
$pid = pcntl_fork(); //forks process
if($pid) { //if in parent:
exit(0); //exit parent process
} //WARNING: creates 'Zombie' child process
else { //else in child process
system("Nohup php -f " .$file . " 2> /dev/null"); //executes augmented file
exit(0); //exits exit child process
}
}
function augmentStr($inArr) {
if (mt_Rand(0,6) < 5) { //determines mutability
/*$startIndex & $endIndex define mutable parts of file line as Xs
* system("echo XXXXX ... XXXXX\\n");
* 01234567890123 -7654321
*/
$startIndex = 13;
$endIndex = count($inArr)-7;
$targetIndex = mt_Rand($startIndex,$endIndex); //choose mutable index
$inArr[$targetIndex] = getSafeChar(mt_Rand(0,62)); //mutate index
$inArr = augmentStr($inArr); //recurse
}
return $inArr;
}
function getSafeChar($inNum) { //cannot use escaped characters
$outChar; //must be a standard PHP char
if ($inNum >= 0 && $inNum <= 9 ) { $outChar = chr($inNum + 48); }
else if ($inNum >= 10 && $inNum <= 35) { $outChar = chr($inNum + 55); }
else if ($inNum >= 36 && $inNum <= 61) { $outChar = chr($inNum + 61); }
else if ($inNum == 62) { $outChar = " "; }
else { $outChar = " "; }
return $outChar;
}
?>
AVERTISSEMENT: Crée un processus zombie , sachez comment tuer un processus zombie avant d'exécuter du code
Techniques de recherche d'informations:
Cet article contient des informations plus spécifiques que Wikipedia. Cet article ne contient cependant pas de vrai code source. Si vous souhaitez mon avis, bien qu'il soit très peu probable qu'il trouve un exemple de code source, vous pourrez peut-être trouver suffisamment de documentation académique pour créer votre propre code métamorphique. Considérez ceci pour commencer ( google scholar ):
Lorsque vous lisez des articles/articles universitaires, assurez-vous de consulter les sources à la fin du document car ces sources peuvent également contenir des informations précieuses.
Bonne chance dans votre quête de connaissances !
Cette réponse n'est pas terminée, je continuerai à la développer au fil du temps, jusqu'à ce que la réponse à cette question soit complète
Exemple de script - PHP
J'ai fait ma propre copie du script PHP fourni par James Holderness, afin que je puisse voir par moi-même par une démonstration comment un script métamorphique pourrait fonctionner. Une écriture complète du code est ici; http://null.53bits.co.uk/index.php?page=php-goto-replicator
Simplement, après avoir initialement exécuté le script, il se copie dans un nouveau fichier avec un nom de fichier aléatoire, avec les lignes de code dans un nouvel ordre aléatoire, il lance alors un nouveau processus qui exécute la nouvelle copie du fichier de script et l'original copie quitte. Il y a maintenant une nouvelle copie du script en cours d'exécution, qui est une copie du fichier d'origine mais avec un nom de fichier aléatoire et les lignes de code sont dans un ordre différent. Il s'agit d'un processus perpétuel; réorganiser et répliquer, puis exécuter une nouvelle instance (processus) tuant la précédente.
Je visais à étendre la réponse PHP de James Holderness) en un exemple de code de réplication et de morphing fonctionnel.
Ceci est le code PHP PHP que j'ai trouvé;
<?php goto a01;
a01: $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; goto a02;
a02: $randomString = __DIR__."/"; goto a03;
a03: $i = 0; goto a04;
a04: if ($i < 10) goto a05; else goto a07;
a05: $randomString .= $characters[Rand(0, strlen($characters) - 1)]; goto a06;
a06: $i++; goto a04;
a07: $randomString .= ".php"; goto a08;
a08: $ARGS=Array("-f",$randomString); goto a09;
a09: $handle_out = fopen("$randomString", "w"); goto l01;
l01: $filename = __FILE__; goto l02;
l02: $contents = file_get_contents($filename); goto l03;
l03: $lines = explode("\n",$contents); goto l04;
l04: $collection = array(); goto l05;
l05: $pattern = '%^[^:]+:.*goto [^;]+;$%'; goto l06;
l06: $i = 0; goto l07;
l07: if ($i < count($lines)-1) goto l08; else goto l23;
l08: $line = $lines[$i]; goto l09;
l09: $line = trim($line); goto l10;
l10: if (substr($line,0,2) != '//') goto l11; else goto l22;
l11: if (preg_match($pattern, $line) === 1) goto l12; else goto l13;
l12: $collection[] = $line; goto l22;
l13: shuffle($collection); goto l14;
l14: $j = 0; goto l15;
l15: if ($j < count($collection)) goto l16; else goto l19;
l16: echo $collection[$j]."\n"; goto l17;
l17: fwrite($handle_out, $collection[$j]."\n"); goto l18;
l18: $j++; goto l15;
l19: $collection = array(); goto l20;
l20: fwrite($handle_out, $line."\n"); goto l21;
l21: echo $line."\n"; goto l22;
l22: $i++; goto l07;
l23: fclose($handle_out); goto f01;
f01: $pid = pcntl_fork(); goto f02;
f02: if ($pid == -1) goto f03; else goto f04;
f03: die("Could not fork a new child\n"); goto f03;
f04: if ($pid) goto f05; else goto f06;
f05: exit(0); goto f05;
f06: $sid = posix_setsid(); goto f07;
f07: if ($sid < 0) goto f08; else goto f09;
f08: die("Child posix_setsid error\n"); goto f08;
f09: sleep(10); goto f10;
f10: pcntl_exec(PHP_BINARY, $ARGS);
l24: exit(0); goto l24;
?>