Πηγαίος κώδικας στο GitHub
Μια υπογραφή καρφώνει ένα μόνο σημείο στον χώρο των κωδικοποιήσεων
Ένας δεκαεξαδικός κανόνας YARA είναι ένα συντακτικό κατηγόρημα πάνω σε έναν buffer από bytes. Αυτό που προσπαθεί να εντοπίσει είναι ένα σημασιολογικό αντικείμενο, για παράδειγμα μια συνάρτηση που επιστρέφει 7. Ανάμεσα στα δύο επίπεδα βρίσκεται η καθοριστική ιδιότητα της x86-64: μια κωδικοποίηση μεταβλητού μήκους, στην οποία η αντιστοίχιση ακολουθιών bytes σε αρχιτεκτονικά αποτελέσματα είναι μαζικά πολλά-προς-ένα. Το να τεθεί ο RAX σε 7 και να γίνει επιστροφή έχει, με συντηρητική εκτίμηση, περισσότερες από δώδεκα έγκυρες κωδικοποιήσεις.
Μια υπογραφή καρφώνει ένα σημείο σε αυτόν τον χώρο. Η μόνη δουλειά ενός mutator είναι να μετακινηθεί σε άλλο. Γι' αυτό η διαισθητική υπόθεση —προσθέτεις ένα NOP, αλλάζουν το hash και το μέγεθος, και ο κανόνας ταυτότητας παρακάμπτεται— μετρά στο λάθος επίπεδο. Το ερώτημα δεν είναι ποτέ αν μετακινήθηκαν κάποια bytes. Είναι αν το ακριβές σημείο που καρφώνει ο κανόνας εμφανίζεται ακόμη κάπου στον buffer, και αν ο κανόνας καρφώνει ένα σημείο ή μια οικογένεια.
Πρόκειται για εργαστηριακή ανάλυση σε x86-64 για εξουσιοδοτημένη αξιολόγηση ανιχνεύσεων, με το Aether 1.0.0 και το Zydis v4.1.1 ως αναπαραγώγιμη υλοποίηση αναφοράς. Δεν διεκδικεί αυθαίρετη σημασιολογία .text, γενικής χρήσης επανεγγραφή δυαδικών αρχείων ούτε παράκαμψη οποιουδήποτε προϊόντος ασφαλείας.
Έξι bytes, ένα NOP, και ο κανόνας εξακολουθεί να ταιριάζει
Η συνάρτηση δοκιμής είναι έξι bytes: B8 07 00 00 00 C3, δηλαδή mov eax, 7; ret. Ο εκπαιδευτικός κανόνας ταιριάζει ακριβώς σε αυτά τα bytes, συνεχόμενα. Είναι κανόνας μόνο για το εργαστήριο, όχι υπογραφή παραγωγής· η αξία του είναι ότι κάνει ορατό το επίπεδο ταυτότητας.
Η πρώτη καταγραφή SAFE με seed 42 έδωσε 90 B8 07 00 00 00 C3: ένα NOP ενός byte μπροστά από το αμετάβλητο πρωτότυπο. Το hash άλλαξε, το μέγεθος άλλαξε, η τιμή επιστροφής παραμένει ίδια και ο κανόνας εξακολουθεί να ταιριάζει, επειδή η «βελόνα» των έξι bytes είναι άθικτη ένα byte πιο πέρα.
Ο λόγος είναι μηχανικός. Το YARA ανάγει το μοτίβο σε σύντομα atoms, συνήθως υποσυμβολοσειρές έως τεσσάρων bytes όπως 07 00 00 00, τα τοποθετεί σε ένα αυτόματο Aho-Corasick και εκτελεί τον πλήρη έλεγχο μόνο όπου βρεθεί atom. Το αυτόματο είναι ανεξάρτητο θέσης, οπότε μια μετατόπιση κατά ένα byte δεν αλλάζει τίποτα. Το πόρισμα: η πραγματική ισχύς ενός κανόνα ταυτότητας είναι η ποιότητα του σπανιότερου atom του, όχι το μήκος του. Η χρήση μπαλαντέρ στο immediate, όπως B8 ?? ?? ?? ?? C3, αναγκάζει το YARA να αγκυρωθεί στα atoms ενός byte B8 και C3, που κατακλύζουν κάθε πραγματικό .text και μετατρέπουν τον κανόνα σε αργό έλεγχο χαμηλής ακρίβειας.
Επομένως, το συμπέρασμα ότι η προσθήκη γεμίσματος παρέκαμψε την υπογραφή δεν είναι αποδεκτό χωρίς δοκιμή του ίδιου του κανόνα. Αυτό το πείραμα μέτρησε padding, όχι κατάρρευση του κανόνα.
{ B8 07 00 00 00 C3 } // contiguous identity Μία αλλαγή κωδικοποίησης, και ο κανόνας αστοχεί
Το seed 5 ακολουθεί διαφορετική διαδρομή μέσα από την ίδια πολιτική SAFE και παράγει 48 C7 C0 07 00 00 00 C3. Η αρχική συμβολοσειρά δεν εμφανίζεται πλέον, επειδή η εντολή άλλαξε κλάση κωδικοποίησης: ένα πρόθεμα REX του οποίου το bit W επιλέγει τελεστέο 64 bit, η ομάδα opcode MOV C7 /0, ένα ModR/M άμεσου καταχωρητή που επιλέγει τον RAX, το ίδιο immediate 7 και ret. Το αποτέλεσμα είναι ουσιαστικά mov rax, 7; ret.
Η διαπίστωση έχει σαφές όριο. Το B8 επεκτείνει με μηδενικά το immediate 32 bit στον RAX, ενώ το REX.W + C7 /0 το επεκτείνει με πρόσημο. Για το θετικό immediate 7 και οι δύο μορφές δίνουν την ίδια τιμή 64 bit. Ωστόσο, το πεδίο pure_rax του Aether είναι oracle τιμής επιστροφής 32 bit. Με ένα immediate όπως 0x80000000 οι δύο μορφές θα διέφεραν στα ανώτερα 32 bit, και το oracle δεν θα μπορούσε να το δει. Η σημασιολογική διαπίστωση περιορίζεται σκόπιμα σε αυτή τη μορφή με θετικό immediate και στο δηλωμένο πεδίο εξόδου 32 bit.
Ούτε το seed μόνο του καθορίζει την έξοδο· μια δημοσιευμένη καταγραφή πρέπει να αναφέρει νέα διεργασία, το πρώτο morph με seed και το ακριβές build. Με αυτή την πειθαρχία, η σύγκριση είναι καθαρή:
- Πρωτότυπο B8 … C3: κανόνας ταυτότητας HIT, pure_rax 7
- Seed 42 (90 + πρωτότυπο): κανόνας ταυτότητας HIT, pure_rax 7 → 7 OK
- Seed 5 (48 C7 C0 … C3): κανόνας ταυτότητας MISS, pure_rax 7 → 7 OK
Τι κάνει το αποτέλεσμα αξιόπιστο
Αυτό δεν είναι επιχείρημα ότι η ανίχνευση είναι αδύνατη. Ένας κανόνας που αναγνωρίζει και τις δύο οικογένειες κωδικοποίησης μπορεί να ταιριάζει και στις δύο. Είναι επιχείρημα ότι ένα μεμονωμένο θραύσμα ταυτότητας είναι ελλιπές.
Το Aether αποκωδικοποιεί κάθε εντολή με το Zydis και την κατατάσσει ως Control, Memory, Identity, PureRax ή άλλη κλάση· μόνο οι ταξινομημένες εντολές εισέρχονται σε έναν σημασιολογικά οριοθετημένο κατάλογο αντικαταστάσεων, και το ret δεν διαφοροποιείται ποτέ ως καθαρό. Τα bytes που το Zydis δεν μπορεί να αποκωδικοποιήσει διατηρούνται ως αδιαφανή, ώστε μια λανθασμένη αποκωδικοποίηση να μικραίνει την επιφάνεια μετασχηματισμού αντί να επινοεί επανεγγραφή. Κάθε έξοδος αποκωδικοποιείται ξανά και ελέγχεται με καθαρή σύγκριση επιστροφής 32 bit, με προσομοιωτή ανεξάρτητο από τον host με σταθερά ζεύγη πολλαπλών εισόδων και, σε x86-64 ή στο εργαστήριο Docker amd64, με εγγενή εκτέλεση. Στις λειτουργίες Lab ή Industry μια καθαρή απόκλιση οδηγεί σε αποτυχία κλειστού τύπου.
Το benchmark του εργαστηρίου του συγγραφέα σε Apple Silicon χρησιμοποίησε 5.096 εγγραφές corpus (1.000 συνθετικές, 4.096 από ELF). Καταγράφει 10.192 επιτυχή morphs, 5.020 καθαρούς ελέγχους και 4.862 εγγενείς ελέγχους χωρίς καμία παραβίαση, καθώς και 94,89% μοναδικές εξόδους κατά SHA-256. Στον εστιασμένο πίνακα 128 seeds, ο εργαστηριακός κανόνας ταυτότητας ταίριαξε 30 φορές (23,44%) και αστόχησε 98 φορές (76,56%), χωρίς καμία καθαρή παραβίαση. Πρόκειται για δεδομένα καταγραφής για το συγκεκριμένο build και host, όχι για μόνιμα νούμερα του προϊόντος.
Τα επίπεδα που πρέπει να συμφωνούν
Το δίδαγμα δεν είναι να χρησιμοποιούμε περισσότερες υπογραφές, ούτε ότι το morphing κερδίζει. Είναι να δοκιμάζουμε τους κανόνες στο επίπεδο που πραγματικά παρατηρούν. Το παράδειγμα των έξι bytes διασχίζει πέντε επίπεδα, και η αντιμετώπιση οποιουδήποτε από αυτά ως ολόκληρου του συστήματος είναι ο τρόπος με τον οποίο ένα πειστικό dump bytes γίνεται ψευδής ισχυρισμός. Μια δομική επαναποκωδικοποίηση δεν μπορεί να αποδείξει την πλήρη σημασιολογία της μηχανής, ένα επιτυχημένο καθαρό oracle δεν μπορεί να αποδείξει ότι ένα EDR δεν σήμανε συναγερμό, και ένα MISS του YARA δεν μπορεί να αποδείξει ότι μια κωδικοποίηση απουσιάζει από κάθε οικογένεια ανίχνευσης.
- Αποκωδικοποίηση: ποια bytes και ποιο γράφημα ροής ελέγχου αποκωδικοποιήσαμε πραγματικά;
- Ανάλυση: επιτρέπει το τρέχον μοντέλο αυτόν τον μετασχηματισμό;
- Lowering: δείχνουν ακόμη τα άλματα στα προβλεπόμενα λογικά μπλοκ;
- Oracle: τηρήθηκε το δηλωμένο συμβόλαιο εξόδου σε αυτό το πεδίο;
- Ανίχνευση: ποιο παρατηρήσιμο άλλαξε, και ποιος αισθητήρας το βλέπει ακόμη;
Μετατρέποντας το πείραμα σε CI ανίχνευσης
Το χρήσιμο μοτίβο: δημιουργήστε έγκυρα σημασιολογικά δίδυμα μέσα σε ένα στενά δηλωμένο πεδίο, εκτελέστε τον κανόνα και το oracle, κρατήστε τα τεκμήρια και στη συνέχεια διορθώστε τον αισθητήρα που απέτυχε. Τρέξτε το job σε τουλάχιστον τρία επίπεδα: το σημασιολογικό oracle απορρίπτει μεταλλάξεις που παραβιάζουν το δηλωμένο συμβόλαιο, η ταυτότητα καταγράφεται ως ακριβή bytes, μέγεθος και hash, και ο κατονομαζόμενος ανιχνευτής εκτελείται με τον ακριβή κανόνα του. Μια παρατήρηση από ClamAV ή EDR είναι ξεχωριστή τέταρτη μέτρηση, όχι υποκατάστατο του αποτελέσματος YARA.
Κρατήστε τον ακριβή πηγαίο κώδικα του εργαστηριακού κανόνα YARA, την αναφορά benchmark σε JSON, τις αποκωδικοποιημένες λίστες πριν και μετά, καθώς και την έκδοση και την ημερομηνία του ανιχνευτή. Κατονομάστε το oracle αντί να γράφετε «επικυρώθηκε»: το Apple Silicon μπορεί να τρέξει την προσομοίωση αλλά όχι να εκτελέσει εγγενώς x86-64, οπότε χρησιμοποιήστε το εργαστήριο Docker amd64 όταν απαιτείται εκτέλεση σε υλικό.
Όταν ένας κανόνας αποτυγχάνει, η διόρθωση πρέπει να ακολουθεί το επίπεδο. Ένας κανόνας συνεχόμενων bytes μπορεί να χρειάζεται εναλλακτικές ανά οικογένεια κωδικοποίησης, πλαίσιο ή δεύτερο σήμα· ένας κανόνας που καλύπτει τόσο το B8 ?? … C3 όσο και το 48 C7 C0 ?? … C3 μπορεί να αναγνωρίσει και τις δύο μορφές. Ένας ανιχνευτής μεγέθους ή εντροπίας μπορεί να βλέπει ήδη τη μέση διόγκωση περίπου 2,19× και να χρειάζεται μόνο βαθμονόμηση. Ένας αισθητήρας συμπεριφοράς πρέπει να δοκιμάζεται σε επιφάνειες εκτέλεσης, δέσμευσης μνήμης, χρονισμού και μνήμης, αντί να συνάγεται από ένα στατικό hash.
Key Takeaways
Ένα NOP δεν έσπασε τον κανόνα· μία ισοδύναμη κωδικοποίηση τον έσπασε. Το πείραμα δείχνει μια αδυναμία σε έναν συνεχόμενο κανόνα ταυτότητας, όχι μια καθολική αποτυχία της ανίχνευσης, και ισχύει μόνο μέσα στο δηλωμένο πεδίο του: μια μορφή με θετικό immediate, ένα oracle επιστροφής 32 bit, ένα συγκεκριμένο build και ένας συγκεκριμένος κανόνας.
Η σωστή απάντηση δεν είναι να υποσχόμαστε άτρωτα συστήματα, αλλά να κάνουμε τις παραδοχές ενός κανόνα εκτελέσιμες και καλυμμένες από regression tests. Το Aether προορίζεται για εξουσιοδοτημένη εργαστηριακή χρήση υπό την Aether Research License. Το πλήρες άρθρο στο Medium περιέχει κάθε μέτρηση και κάθε επιφύλαξη, και τα έγγραφα SCOPE, SCORECARD, LIBRARY και SECURITY στο αποθετήριο πρέπει να διαβαστούν πριν οποιοδήποτε αποτέλεσμα θεωρηθεί ισχυρισμός για χρήση σε παραγωγή.
Christian Schnapka
Macstab Engineering
Ο Christian Schnapka αναπτύσσει τα ανοιχτά εργαλεία μηχανικής πίσω από τη Macstab. Δημοσιεύει τις εκτενείς εκδοχές αυτών των ερευνών στο Medium, με κάθε μέτρηση, κάθε επιφύλαξη και κάθε αρνητικό αποτέλεσμα.