┌───────────────────────┐ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ │ █ █ █ █ █ █ │ │ █ █ █ █ █▀▀▀▀ │ │ █ █ █ █ ▄ │ │ ▄▄▄▄▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄ ▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄▄▄▄▄ │ │ █ │ La technique de False-disassembly polymorphique │ █ │ ~ S01den └───────────────────█ ──┘ [ Traduction par 0xNinja ] Écrit avec amour par S01den, de l'équipage tmp.out ! mail : S01den@protonmail.com --- Introduction --- Pendant le développement de Lin32.Bakunin[0], je me demandais comment le rendre plus intéressant qu'un simple virus mips qui affiche des messages stupides. Je voulais vraiment emmerder les reversers. Et je me suis rappelé d'une technique de false-disassembly que j'ai utilisé dans un de mes crackmes. Parce que le polymorphisme c'est cool, je voulais comprendre s'il était possible de créer quelquechose d'intéressant en combinant ça au false-disassembly d'une manière ou d'une autre. La réponse est oui, et j'ai appelé (je ne sais pas si c'est une toute nouvelle technique) cette astuce "false-disassembly polymorphique" ou simplement "Faux polymorphisme". --- Comment ça marche ? --- Cette technique est vraiment directe à comprendre et implémenter. Je l'ai découverte dans le fameux papier de Silvio Cesare[1] à propose de techniques d'anti-debug et de revese Linux. Il suffit d'ajouter des octets qui normallement commencent une instruction avant du code assembleur, comme suit : -------------------- fin de page -------------------- hey: hey: xor %rbx, %rbx .ascii "\x48\x31" jmp yo ====> xor %rbx, %rbx jmp yo --------------------------------------------------- Maintenant, si on regarde le code désassemblé ça donne un truc comme ça (vive radare2): -------------------- fin de page -------------------- ;-- hey: 0x00401002 4831db xor rbx, rbx 0x00401005 eb02 jmp 0x401009 || \/ ;-- hey: 0x00401002 48314831 xor qword [rax + 0x31], rcx 0x00401006 dbeb fucomi st(3) 0x00401008 026631 add ah, byte [rsi + 0x31] --------------------------------------------------- Pourquoi le désassembleur a un tel comportement ? En fait, \x48\x31 est censé représenter une opération xor[2], les octets suivants définissent les registres utilisés. Donc ces octets "d'initialisation" se collent aux octets qui suivent, qui sont eux aussi des octets "d'initialisation", et donnent des données incorrectes au lieu des instructions voulues ! Par conséquent, pour exécuter un tel code, il faut sauter les octets précédemment ajoutés. Comme ceci : -------------------- fin de page -------------------- _start: jmp hey+2 hey: .ascii "\x48\x31" xor %rbx, %rbx jmp yo --------------------------------------------------- --- Le c0de en entier --- Imagine, non je rigole, mais imagine quand même qu'on puisse changer de manière aléatoire les octets qui font le faux désassemblage à chaque exécution ou infection, le code désassemblé changera aussi et les reversers vont penser qu'il s'agit de code polymorphique, alors que seuls quelques octets changent... Et maintenant sans plus attendre, le code. ----------- fin de page ----------- # build cmd: as Linux.FakePolymorphism.asm -o fakePoly.o ; ld fakePoly.o -o fakePoly # this code is a fake polymorphic example, feel free to try/use/whatever it! # It grabs itself its code, modify the fake-disassembly bytes and put the result # on the stack. .text .global _start _start: jmp true_start+2 # jump over the fake-disassembly bytes true_start: .ascii "\x48\x31" # fake-disassembly bytes xor %rbx, %rbx jmp get_code+2 # jump over the fake-disassembly bytes get_code: .ascii "\x66\x31" # fake-disassembly bytes call get_rip sub $0x10 ,%rax # 0x10 is the number of bytes between _start abd this instruction movb (%rax,%rbx), %al movb %al, (%rsp,%rbx) inc %rbx cmp $0x54, %rbx # 0x54 is the total size of this code jne get_code+2 # Pseudo RNG thanks to the time stamp counter rdtsc xor $0xdead, %rax mov %ax, 2(%rsp) xor $0xbeef, %rdx mov %ax, 9(%rsp) mov $60, %rax mov $0, %rdi syscall # sys_exit get_rip: mov (%rsp), %rax ret ---------------------------- -- Conclusion -- J'espère que vous avez apprécié ce papier et que vous allez essayer d'implémenter cette technique dans vos crackmes ou virus ! Avec sblip, nous avons écrit un virus polymorphique (Lin64.Eng3ls, allez lire le papier et le code !) qui utilise cette technique pour obfusquer son déchiffreur. Le code du déchiffreur : ------- fin de page ------- pop rcx jmp jmp_over+2 jmp_over: db `\x48\x31` ; false disassembly mov al,0x00 xor rdx, rdx decoder: jmp jmp_over2+2 jmp_over2: db `\xb8\xd9` ; false disassembly mov dl, byte [r12+rdi] cmp rdi, STUB_SIZE-1 jna no_decrypt jmp jmp_over3+2 jmp_over3: db `\x48\x81` ; false disassembly xor dl, al no_decrypt: mov byte [rbx+rdi], dl inc rdi loop decoder ------------------------- Voici des déchiffreurs désassemblés[3] de binaires infectés, avec l'astuce en action : 1. 0x0c003f46 59 pop rcx 0x0c003f47 eb02 jmp 0xc003f4b 0x0c003f49 00d6 add dh, dl 0x0c003f4b b06d mov al, 0x6d 0x0c003f4d 4831d2 xor rdx, rdx 0x0c003f50 eb02 jmp 0xc003f54 0x0c003f52 1aca sbb cl, dl 0x0c003f54 418a143c mov dl, byte [r12 + rdi] 0x0c003f58 4881ff870000. cmp rdi, 0x87 0x0c003f5f 7606 jbe 0xc003f67 0x0c003f61 eb02 jmp 0xc003f65 0x0c003f63 c0d630 rcl dh, 0x30 0x0c003f66 c28814 ret 0x1488 0x0c003f69 3b48ff cmp ecx, dword [rax - 1] 0x0c003f6c c7 invalid 0x0c003f6d e2e1 loop 0xc003f50 2. 0x0c003fe6 59 pop rcx 0x0c003fe7 eb02 jmp 0xc003feb 0x0c003fe9 ce invalid 0x0c003fea 0ab0a34831d2 or dh, byte [rax - 0x2dceb75d] 0x0c003ff0 eb02 jmp 0xc003ff4 0x0c003ff2 39cb cmp ebx, ecx 0x0c003ff4 418a143c mov dl, byte [r12 + rdi] 0x0c003ff8 4881ff870000. cmp rdi, 0x87 0x0c003fff 7606 jbe 0xc004007 0x0c004003 0e invalid 0x0c004004 0a30 or dh, byte [rax] 0x0c004006 c28814 ret 0x1488 0x0c004009 3b48ff cmp ecx, dword [rax - 1] 0x0c00400c c7 invalid 0x0c00400d e2e1 loop 0xc003ff0 Le résultat est très différent du code originel. --- Notes et références --- [0] https://vx-underground.org/papers/VXUG /Exclusive/Bakounin/Writing_virus_in_MIPS_assembly_for_fun.txt [1] http://www.ouah.org/linux-anti-debugging.txt // the silvio's paper [2] https://www.felixcloutier.com/x86/xor [3] Avec radare2