┌───────────────────────┐
                                                       ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄       │
                                                       │ █   █ █ █ █   █       │
                                                       │ █   █ █ █ █▀▀▀▀       │
                                                       │ █   █   █ █     ▄     │
                                                       │                 ▄▄▄▄▄ │
                                                       │                 █   █ │
                                                       │                 █   █ │
                                                       │                 █▄▄▄█ │
                                                       │                 ▄   ▄ │
                                                       │                 █   █ │
                                                       │                 █   █ │
                                                       │                 █▄▄▄█ │
                                                       │                 ▄▄▄▄▄ │
Introducing SHELF Loading                              │                   █   │
The Nexus between Static and Position Independent Code │                   █   │
~ @ulexec and @Anonymous_                              └───────────────────█ ──┘

1. Introduction

Over the last several years there have been several enhancements in Linux 
offensive tooling in terms of sophistication and complexity. Linux malware has
become increasingly more popular, given the higher number of public reports
documenting Linux threats. These include government-backed Linux implants such 
as APT28's VPNFilter, Drovorub or Winnti wide range of Linux Malware.

However, this increase in popularity does not seem to have had much of an impact
in the totality of the sophistication of the current Linux threat landscape just
yet. It's a fairly young ecosystem, where cybercriminals have not been able to 
identify reliable targets for monetization apart from Cryptocurrency Mining, 
DDoS, and more recently, Ransomware operations.

In today's Linux threat landscape, even the smallest refinement or introduction
of complexity often results in AV evasion, and therefore Linux malware authors 
do not tend to invest unnecessary resources to sophisticate their implants. 
There are various reasons why this phenomenon occurs, and it is subject to 
ambiguity. The Linux ecosystem, in contrast to other popular spheres such as 
Windows and MacOS, is more dynamic and diverse, stemming from the many flavors 
of ELF files for different architectures, the fact that ELF binaries can be 
valid in many different forms, and that the visibility of Linux threats is often
quite poor.

Due to these issues, AV vendors face a completely different set of challenges 
detecting these threats. Often times this disproportionate detection failure of
simple/unsophisticated threats leaves an implicit impression that Linux malware
is by nature not complex. This statement couldn't be further from the truth, and
those familiar with the ELF file format know that there is quite a lot of room 
for innovation with ELF files that other file formats are not able to provide 
due to their lack of flexibility, even if we have not seen it abused as much 
over the years.

In this article we are going to discuss a technique that achieves an uncommon 
functionality of file formats, which generically converts full executables to 
shellcode in a way that demonstrates, yet again, another example that ELF 
binaries can be manipulated to achieve offensive innovation that is hard or 
impossible to replicate in other file formats.


2. A Primer On ELF Reflective Loading

In order to understand the technique, we must first give a contextual background
on previously known ELF techniques upon which this one is based, with a 
comparison of the benefits and tradeoffs.

Most ELF packers, or any application implementing any form of ELF binary 
loading, are primarily based on what's known as User-Land-Exec.

User-Land-Exec is a method first documented by @thegrugq, in which an ELF binary
can be loaded without using any of the execve family of system calls, and hence 
its name.

For the sake of simplicity, the steps to implement an ordinary User-Land-Exec 
with support of ET_EXEC and ET_DYN ELF binaries is illustrated in the following
diagram, showcasing an implementation of the UPX packer for ELF binaries:



As we can observe, this technique has the following requirements (by @thegrugq):

  1. Clean out the address space
  2. If the binary is dynamically linked, load the dynamic linker.
  3. Load the binary.
  4. Initialize the stack.
  5. Determine the entry point (i.e. the dynamic linker or the main executable).
  6. Transfer execution to the entry point.

On a more technical level, we come up with the following requirements:
 
  1. Setup the stack of the embedded executable with its correspondent Auxiliary
     Vector.
  2. Parse PHDR's and identify if there is a PT_INTERP segment, denoting that 
     the file is a dynamically linked executable.
  3. LOAD interpreter if PT_INTERP is present.
  4. LOAD target embedded executable.
  5. Pivot to mapped e_entry of target executable or interpreter accordingly, 
     depending if the target executable is a dynamically linked binary.

For a more in-depth explanation, we suggest reading @thegrugq's comprehensive 
paper on the matter [9]. 

One of the capabilities of conventional User-Land-Exec are the evasion of an 
execve footprint as previously mentioned, in contrast with other techniques 
such as memfd_create/execveat, which are also widely used to load end execute
a target ELF file. Since the loader maps and loads the target executable, the
embedded executable has the flexibility of having a non-conventional structure.
This has the side benefit of being useful for evasion and anti-forensics 
purposes.

On the other hand, since there are a lot of critical artifacts involved in the
loading process, it can be easy to recognize by reverse-engineers, as well as 
being somewhat fragile due to the fact that the technique is heavily dependent
on these components. For this reason, writing User-Land-Exec based loaders have
been somewhat tedious. As more features get added to the ELF file format, this 
technique has been inclined to mature over time and thereby increasing its 
complexity.

The new technique that we will be covering in this paper relies on implementing
a generic User-Land-Exec loader with a reduced set of constraints supporting a 
hybrid PIE and statically linked ELF binaries that to our knowledge have yet to
be reported. 

We believe this technique represents a drastic improvement of previous versions
of User-Land-Exec loaders, since based on the lack of technical implementation 
constraints and the nature of this new hybrid static/PIE ELF flavor, the extent
of capabilities it can provide is wider and more evasive than with previous 
User-Land-Exec variants.


3. Internals Of Static PIE Executable Generation

3.1 Background

In July of 2017 H. J. Lu patched a bug entry in GCC bugzilla named ‘Support 
creating static PIE'. This patch mentioned the implementation of a statically 
based PIE in his branch at glibc hjl/pie/static, in which Lu documented that by
supplying –static and –pie flags to the linker along with PIE versions of crt*.o
as input, static PIE ELF executables could be generated. It is important to 
note, that at the time of this patch, generation of fully statically linked PIE
binaries was not possible.[1]

In August, Lu submitted a second patch[2] to the GCC driver, for adding the 
–static flag to support static PIE files that he was able to demonstrate in his
previous patch. The patch was accepted in trunk[3], and this feature was 
released in GCC v8.

Moreover, in December of 2017 a commit was made in glibc[4] adding the option 
–enable-static-pie. This patch made it possible to embed the needed parts of 
ld.so to produce standalone static PIE executables.

The major change in glibc to allow static PIE was the addition of the function
_dl_relocate_static_pie which gets called by __libc_start_main. This function is
used to locate the run-time load address, read the dynamic segment, and perform 
dynamic relocations before initialization, then transfer control flow of 
execution to the subject application.

In order to know which flags and compilation/linking stages were needed in order
to generate static PIE executables, we passed the flag –static-pie –v to GCC. 
However, we soon realized by doing this that the linker generated a plethora of
flags and calls to internal wrappers. As an example, the linking phase is 
handled by the tool /usr/lib/gcc/x86_64-linux-gnu/9/collect2 and GCC itself is 
wrapped by /usr/lib/gcc/x86_64-linux-gnu/9/cc1. Nevertheless, we managed to 
remove the irrelevant flags and we ended up with the following steps:



These steps are in fact the same provided by Lu, supplying the linker with input
files compiled with –fpie, and –static, -pie, -z text, --no-dynamic-linker. 
In particular, the most relevant artifacts for static PIE creation are rcrt1.o,
libc.a, and our own supplied input file, test.o. The rcrt1.o object contains the
_start code which has the code required to correctly load the application before
executing its entry point by calling the correspondent libc startup code 
contained in __libc_start_main:



As previously mentioned, __libc_start_main will call the new added function 
_dl_relocate_static_pie (defined at elf/dl-reloc-static-pie.c file of glibc 
source). The primary steps performed by this function are commented in the 
source:



With the help of these features, GCC is capable of generating static executables
which can be loaded at any arbitrary address. 

We can observe that _dl_relocate_static_pie will handle the needed dynamic 
relocations. One noticeable difference of rcrt1.o from conventional crt1.o is 
that all contained code is position independent. Inspecting what the generated 
binaries look like we see the following:



At first glance they seem to be common dynamically linked PIE executables, based
on the ET_DYN executable type retrieved from the ELF header. However, upon 
closer examination of the segments, we will observe the nonexistent PT_INTERP 
segment usually denoting the path to the interpreter in dynamically linked 
executables and the existence of a PT_TLS segment, usually contained only in 
statically linked executables.



If we check what the dynamic linker identifies the subject executable as, we 
will see it identifies the file type correctly:



In order to load this file, all we would need to do is map all the PT_LOAD 
segments to memory, set up the process stack with the correspondent Auxiliary
Vector entries, and then pivot to the mapped executable's entry point. We do 
not need to be concerned about mapping the RTLD since we don't have any external
dependencies or link time address restrictions.

As we can observe, we have four loadable segments commonly seen in SCOP ELF 
binaries. However, for the sake of easier deployment, it will be crucial if we 
could merge all those segments into one as is usually done with ELF disk 
injection into a foreign executable. We can do just this by using the –N linker
flag to merge data and text within a single segment.

3.2. Non-compatibility of GCC's -N and static-pie flags

If we pass –static-pie and –N flags together to GCC we see that it generates the
following executable:



The first thing we noticed about the type of generated ELF when using 
–static-pie alone was that it had a type of ET_DYN, and now together with –N it 
results in an ET_EXEC.

In addition, if we take a closer look at the segment's virtual addresses, we see
that the generated binary is not a Position Independent Executable. This is due 
to the fact that the virtual addresses appear to be absolute addresses and not 
relative ones. To understand why our program is not being linked as expected, we
inspected the linker script that was being used.

As we are using the ld linker from binutils, we took a look on how ld selected 
the linker script; this is done in the ld/ldmain.c code at line 345:



The ldfile_open_default_command_file is in fact an indirect call to an 
architecture independent function generated at compile time that contains a set
of internal linker scripts to be selected depending upon the flags passed to ld.
Because we are using the x86_64 architecture, the generated source will be 
ld/elf_x86_64.c, and the function which is called to select the script is 
gldelf_x86_64_get_script, which is simply a set of if-else-if statements to 
select the internal linker script. The –N option sets the config.text_read_only
variable to false, which forces the selection function to use an internal script
which does not produce PIC as can be seen below:



This way of selecting the default script makes the –static-pie and –N flags 
non-compatible, because the forced test of selecting the script based on –N is 
parsed before –static-pie.

3.3. Circumvention via custom Linker Script

The incompatibility between –N, -static, and –pie flags led us to a dead end, 
and we were forced to think of different ways to overcome this barrier. What we
attempted was to provide a custom script to drive the linker. As we essentially
needed to merge the behavior of two separate linker scripts, our approach was to
choose one of the scripts and adapt it to generate the desired outcome with 
features of the remaining script.

We chose the default script of –static-pie over the one used with –N because in
our case it was easier to modify as opposed to changing the –N default script to
support PIE generation.

To accomplish this goal, we would need to change the definition of the segments,
which are controlled by the PHDRS [5] field in the linker script. If the command
is not used the linker will provide program headers generated by default – 
However, if we neglect this in the linker script, the linker will not create any
additional program headers and will strictly follow the guidelines defined in 
the subject linker script.

Taking into account the details discussed above, we added a PHDRS command to the
default linker script, starting with all the original segments which are created
by default when using –static-pie:



After this we need to know how each section maps to each segment – and for this 
we can use readelf as shown below:



With knowledge of the mappings, we just needed to change the section output 
definition in the linker script which adds the appropriate segment name at the
end of each function definition, as shown in the following example:



Here, the .tdata and .tbss sections are being assigned to the segments that get
mapped in the same order that we saw in the output of the readelf –l command. 
Eventually, we ended up having a working script precisely changing all mapped 
sections which were mapped in data to the text segment:



If we compile our subject test file with this linker script, we see the 
following generated executable:



We now have a static-pie with just one loadable segment. The same approach can 
be repeated to remove other irrelevant segments, keeping only critical segments
necessary for the execution of the binary. As an example, the following is a 
static-pie executable instance with minimal program headers needed to run:



The following is the final output of our desired ELF structure – having only one
PT_LOAD segment generated by a linker script with the PHDRS command configured 
as in the screenshot below:




4. SHELF Loading

This generated ELF flavor gives us some interesting capabilities that other ELF
types are not able to provide. For the sake of simplicity, we have labelled this
type of ELF binary as SHELF, and we will be referencing it throughout the rest 
of this paper. The following is an updated diagram of the loading stages needed
for SHELF loading:



As we can see in the diagram above, the process of loading SHELF files is highly
reduced in complexity compared to conventional ELF loading schemes. 

To illustrate the reduced set of constraints to load these types of files, a 
snippet of a minimalistic SHELF User-Land-Exec approach is as follows:



By using this approach, a subject SHELF file would look as follows in memory and
on disk:



As we can observe, the ELF header and Program Headers are missing from the 
process image. This is a feature that this flavor of ELF enables us to implement
and is discussed in the following section.

4.1 Anti-Forensic Capabilities

This new approach to User-Land-Exec has also two optional stages useful for 
anti-forensic purposes. Since the dl_relocate_static_pie function will obtain 
all of the required fields for relocation from the Auxiliary Vector, this leaves
us room to play with how the subject SHELF file structure may look in memory and
on disk.

Removing the ELF header will directly impact reconstruction capabilities, 
because most Linux-based scanners will scan process memory for existing ELF 
images by first identifying ELF headers. The ELF header will be parsed and will
contain further information on where to locate the Program Header Table and 
consequently the rest of the mapped artifacts of the file.

Removal of the ELF header is trivial since this artifact is not really needed by
the loader – all required information in the subject file will be retrieved from
the Auxiliary Vector as previously mentioned.

An additional artifact that can be hidden is the Program Header Table. This is a
slightly different case when compared with the ELF Header. The Auxiliary Vector 
needs to locate the Program Header Table in order for the RTLD to successfully 
load the file by applying the needed runtime relocations. Regardless, there are
many approaches to obfuscating the PHT. The simplest approach is to remove the
original Program Header Table location, and relocate it somewhere in the file 
that is only known by the Auxiliary Vector.



We can precompute the location of each of the Auxiliary Vector entries and 
define each entry as a macro in an include file, tailoring our loader to every
subject SHELF file at compile-time. The following is an example of how these 
macros can be generated:



As we can observe, we have parsed the subject SHELF file for its e_entry and 
e_phnum fields, creating corresponding macros to hold those values. We also have
to choose a random base image to load the file. Finally, we locate the PHT and 
convert it to an array, then remove it from its original location. Applying 
these modifications allows us to completely remove the ELF header and change the
default location of the subject SHELF file PHT both on disk and in memory(!)

Without successful retrieval of the Program Header Table, reconstruction 
capabilities may be strictly limited and further heuristics will have to be 
applied for successful process image reconstruction.

An additional approach to make the reconstruction of the Program Header Table 
much harder is by instrumenting the way glibc implements the resolution of the
Auxiliary Vector fields.

4.2 Obscuring SHELF features by PT_TLS patching

Even after modifying the default location of the Program Header Table by 
choosing a new arbitrary location when crafting the Auxiliary Vector, the 
Program Header Table would still reside in memory and could be found with some
effort. To obscure ourselves even further we can cover how the startup code 
reads the Auxiliary Vector fields.

The code that does this is in elf/dl_support.c in the function _dl_aux_init. In
abstract, the code iterates over all the auxv_t entries, and each of these 
entries initialize internal variables from glibc:



The only reason the Auxiliary Vector is required is to initialize internal _dl_*
variables. Knowing this, we can bypass the creation of the Auxiliary Vector 
entirely and do the same job that _dl_aux_init would do before passing control
of execution to the subject SHELF file.

The only entries which are critical are AT_PHDR, AT_PHNUM, and AT_RANDOM. 
Therefore, we only need to patch the respective _dl_* variables that depend on
these fields. As an example of how to retrieve these values, we can use the 
following one-liner to generate an include file with precomputed macros holding
the offset to every dl_* variable:



With the offset to these variables located, we only need to patch them in the
same way the original startup code would do so using the Auxiliary Vector. As a
way to illustrate this technique, the following code will initialize the 
addresses of the Program Headers to new_address, and the number of program 
headers to the correct number:



At this point we have a working program without supplying the Auxiliary Vector.
Because the subject binary is statically linked, and the code that will load the
SHELF file is our loader, we can neglect every other segment in the Auxiliary 
Vector's AT_PHDR and AT_PHNUM or dl_phdr and dl_phnum respectively. There is an
exception, which is the PT_TLS segment which is the interface in which Thread 
Local Storage is implemented in the ELF file format.

The following code which resides in csu/libc-tls.c on function __libc_setup_tls
show the type of information that gets retrieved from the PT_TLS segment:



In the code snippet above, we can see that TLS initialization relies on the 
presence of the PT_TLS segment. We have several approaches that can obfuscate 
this artifact, such as patching the __libc_setup_tls function to just return and
then initialize the TLS with our own code. Here, we'll choose to implement a 
quick patch to glibc instead as a PoC.

To avoid the need of the PT_TLS Program Header we have added a global variable 
to hold the values from PT_TLS and set the values inside __libc_setup_tls, 
reading from our global variable instead of the subject SHELF file Program 
Header Table. With this small change we finally strip all the program headers:



Using the following script to generate _phdr.h:



We can apply our patches in the following way after including _phdr.h:



Applying the methodology shown above, we gain a high level of evasiveness by 
loading and executing our SHELF file without an ELF header, Program Header 
Table, and Auxiliary Vector – just as shellcode gets loaded. The following 
diagram illustrates how straightforward the loading process of SHELF files is:




5. Conclusion

We have covered the internals of Reflective Loading of ELF files, explaining 
previous implementations of User-Land-Exec, along with its benefits and 
drawbacks. We then explained the latest patches in the GCC code base that 
implemented support for static-pie binaries, discussing our desired outcome, 
and the approaches we followed to achieve the generation of static-pie ELF files
with one single PT_LOAD segment. Finally, we discussed the anti-forensic 
features that SHELF loading can provide, which we think to be a considerable 
enhancement when compared with previous versions of ELF Reflective Loading.

We think this could be the next generation of ELF Reflective Loading, and it may
benefit readers to understand the extent of offensive capabilities that the ELF
file format can provide. If you would like access to the source code, contact 
@sblip or @ulexec.


6. References

[1] (support static pie) 
    https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81498 
[2] (first patch gcc)
    https://gcc.gnu.org/ml/gcc-patches/2017-08/msg00638.html
[3] (gcc patch)
    https://gcc.gnu.org/viewcvs/gcc?view=revision&revision=252034
[4] (glibc --enable-static-pie)
    https://sourceware.org/git/?p=glibc.git;a=commit; \
      h=9d7a3741c9e59eba87fb3ca6b9f979befce07826 
[5] (ldscript doc)
    https://sourceware.org/binutils/docs/ld/PHDRS.html#PHDRS 
[6] https://sourceware.org/binutils/docs/ld/
      Output-Section-Phdr.html#Output-Section-Phdr
[7] https://www.akkadia.org/drepper/tls.pdf
[8] (why ld doesn't allow -static -pie -N)
    https://sourceware.org/git \
      /gitweb.cgi?p=binutils-gdb.git;a=blob;f=ld/ldmain.c; \
      h=c4af10f4e9121949b1b66df6428e95e66ce3eed4;hb=HEAD#l345 
[9] (grugq ul_exec paper)
    https://grugq.github.io/docs/ul_exec.txt 
[10] (ELF UPX internals)
     https://ulexec.github.io/ulexec.github.io/article \
       /2017/11/17/UnPacking_a_Linux_Tsunami_Sample.html