An analysis of INC ransomware
Summary
Section titled “Summary”INC is a ransomware family with a Rust build for Linux and VMware ESXi. Run on a
Debian guest with the arguments --dir /vmfs/volumes --mode fast, this build
walked the directory tree, dropped a ransom note named INC-README.txt in each
directory, and handed twelve virtual-machine files to five worker threads. Each
worker appended an 83-byte trailer to its file, read the file, wrote the
transformed bytes back over it in place, wrote the trailer again, and renamed
the file to <name>.INC. The whole process, from first to last recorded call,
lasted about 35 ms on files of 1 MiB or less.
The behaviour is what a ransomware encryptor on a hypervisor looks like: it
needs no network, it stays inside the datastore, and it is finished before a
person could react. Triage rated the run high because twelve renames to
one new extension, spread over several threads, is not something a benign
program does. The recording also shows what it does not do here: it makes no
network contact, runs no other program, and does not touch files outside
/vmfs/volumes.
Environment
Section titled “Environment”The sample ran in a Debian 13 Minimal guest on a VM named eval-dm. The VM
summary lists the whole configuration: 2 vCPU, 2 GiB of RAM, an 8 GiB disk,
the network route chosen per run, and the Realistic desktop device profile,
which presents the guest as a Lenovo ThinkCentre M75q Gen 2 with its firmware,
network card and clock set to match. The list at the bottom of the card, “Still
visible”, is the app’s own account of what a determined sample could still use
to recognise the VM (see Anti-analysis).

| Setting | Value |
|---|---|
| Base image | Debian 13 Minimal, revision 20 |
| VM | 2 vCPU, 2 GiB RAM, 8 GiB disk |
| Device profile | Realistic desktop (Lenovo ThinkCentre M75q Gen 2) |
| Network route | InetSim (built-in services) |
| Sample | /tmp/inc-86d04474, ELF x86-64, 870,920 bytes |
| Arguments | --dir /vmfs/volumes --mode fast |
| User | root |
| Time limit | 300 s |
| Static analysis | every check, run on the sample fetched from the guest |
| Recording | system calls with captured buffers, packets, file hashes |
Two preparation choices decide what the recording can show.
The arguments. The sample does nothing useful without them. An earlier
attempt with a malformed command line only printed its usage text. With
--dir it encrypts below that directory, and --mode fast selects the
partial-encryption mode discussed below.
The decoys. The sample looks for virtual-machine files, so the guest needs
some. A benign shell script, decoys.sh, ran first on a route that drops all
traffic and created the files of two machines, web01 and db01, under
/vmfs/volumes/datastore1: a .vmx, .vmdk, -flat.vmdk, .vmsn, .vswp
and .nvram each, twelve files in all. Their content is random-looking bytes. That matters
later: encrypted and random data look the same, so the recording cannot show a
file “turning into noise”. It has to show the rewrite another way.

Times in the app are in the host’s time zone, UTC+7.
Execution and threads
Section titled “Execution and threads”The recording starts with the sample running as pid 1362. Within 3 ms it has
started five more tasks, TIDs 1363 to 1367, and Triage shows them under pid 1362
marked thread, all with the same image and arguments. They are threads of one
process, not child programs: nothing was executed (Triage’s executed-files list
is empty).

What the thread layout tells you depends on what each thread does, and the
file list shows it. The main thread, 1362, opens the ld cache, reads
/proc/self/maps, then creates INC-README.txt in /vmfs/volumes, in
datastore1, in web01 and in db01, walking the tree as it goes. Only then
do the workers open files. The first five files go to five different workers,
one each, and the rest follow on whichever worker is free: thread 1363 renamed
seven of the twelve files, 1367 two, and 1364, 1365 and 1366 one each. The
workers made 113, 43, 33, 32 and 31 calls in total, so the busy one did more
work, which matches a shared queue of files that workers pull from (an
inference; the recording shows the result, not the queue).
For an analyst this is the shape to look for in an encryptor: one thread finds targets and drops notes, a pool does the I/O, and the speed comes from running the files in parallel. It also explains a detection detail. A rule that counts renames per thread sees one or two renames on most workers and stays quiet; the count has to be kept per process (see Triage).
The sample did not use esxcli or any other helper. Its strings contain the
text vim-cmd and vmsvc/getallvms (see Static analysis),
which suggests it can list virtual machines on a real ESXi host, but nothing
like that ran here.
The encryption routine, file by file
Section titled “The encryption routine, file by file”Triage keeps a history for every file the process touched. For one file it
turns hundreds of calls into a sequence you can read. Take
/vmfs/volumes/datastore1/web01/web01.vswp, a 262,144-byte file.

openat. The worker opens the file.pwrite64of 83 bytes at offset0x40000.0x40000is 262,144, the size of the file, so these 83 bytes go after the end: the file grows by 83 bytes. The sample writes a trailer first. A trailer of this kind usually carries per-file metadata, such as key material and a marker saying the file is already processed; Acronis describes an INC footer that ends in the ASCII stringINC, and Rapid7 describes how a similar ransomware uses a trailer to skip files it has already encrypted. The bytes are captured and differ from file to file (the.vswptrailer begins\xd62"\xe3O, the flat disk’s begins.\xc8ZY), which fits per-file values. I did not decode them.pread64of 262,227 bytes at offset 0. 262,227 is 262,144 + 83: the worker reads the whole file, the trailer included, into memory in one call. Reading before writing is what an in-place rewrite needs.writeof 262,144 bytes at offset 0, two milliseconds later. The gap is the work the recording cannot see: transforming the bytes in memory involves no system call. Then the same length goes back over the same range, so the file does not grow, shrink or move. The first bytes read were\x92\xe2\x9e#'and the first bytes written are\xae_\x1b\xa8. The content changed, which is the proof of the rewrite given that the decoys were random to begin with. This call iswrite, notpwrite64, because the file position is still 0; the earlierpread64did not move it.pwrite64of 83 bytes at0x40000, twice more. The trailer is written again at the same place. The recording does not say why. It may be a checkpoint or a rewrite after the final key is known; both are guesses.renamefromweb01.vswptoweb01.vswp.INC. The new name is the victim-visible signal, and it comes last, so a file that is renamed has already been rewritten.
Counting these gives the totals on the trace filter. Twelve files with three
pwrite64 calls each is 36 pwrite64 calls, plus twelve rename calls: the
48 calls that syscall == pwrite64 || syscall == rename keeps out of 436.

Fast mode
Section titled “Fast mode”--mode fast is partial encryption. The recording shows its effect on the
larger file. web01-flat.vmdk is 1,048,576 bytes. Its trailer goes at
0x100000 (the end), and then the worker reads 1,003,520 bytes from offset
0 and writes the same 1,003,520 bytes back: 95.7 % of the file, and 245 blocks
of 4,096 bytes. The last 45,056 bytes are never read or written. The smaller
.vswp, in contrast, was rewritten from the first byte to the last. Two
things follow. The encryptor does not always touch all of a file: a “fast” run
leaves the tail of a large file in its original state. And the cost shows in
the timing: the larger file took 14 ms between read and write, the smaller one
2 ms, so partial encryption buys speed. Other write-ups describe the same
trade, with the share of each file depending on its size or on a percentage
option. For a virtual disk the trade costs the attacker little, because a disk
image with 95 % of its blocks scrambled is as unusable as one with all of
them; the untouched tail is not a recovery path, and this recording does not
claim it is.

The bytes themselves are captured. The recording keeps the buffer of every write (2.6 MiB in all), so the Triage history shows what went into each file, and Reconstruct contents offers to rebuild a file from the captured calls. I did not measure entropy: with random decoys it would say nothing, and the reconstruction is limited by capture gaps (“earlier bytes unavailable” in the history, because the VM’s disk changed after the run started).
The ransom note
Section titled “The ransom note”INC-README.txt is 3,448 bytes. The recording shows four copies, in
/vmfs/volumes, datastore1, web01 and db01, all with the same
SHA-256, and Triage reports each as created, fully written and complete. The
main thread writes them at 14:41:08.747, within 2 ms of the process starting and
before any virtual-machine file is touched. That ordering is worth noting: the
notes are in place before the first file is renamed, so even an interrupted
run would probably leave the victim a message, and a defender who sees the first renames has
already lost the race for the notes. The recording does not show the note’s
text, and I do not reproduce it.
Anti-analysis
Section titled “Anti-analysis”Triage reports one low-tier finding, evasion.debugger_check: “2 checks
(debugger); 0 would show a debugger”. It fired at 14:41:08.746, the first
moment of the run, right after the sample read /proc/self/maps five times.
Two things follow. The sample checks its environment before it does anything
else, which is what code that wants to change course when it is watched would
do (the recording cannot show its intent). None of the checks would have shown
a debugger, and the sample went on. The finding is low because a debugger check alone does not harm a victim;
it matters because it tells you the sample was written with analysis in mind.
The capa results point the same way: the sample can issue system calls
directly, 6 locations, which bypasses libc and any hook placed there, and it
uses XOR encoding and obfuscated stack strings. That may explain why the
strings list holds INC-README.txt and vim-cmd but no esxcli or vmfs
text: some strings are probably assembled at run time (an inference from the
missing strings and the capa matches; I did not decode them).
No monitoring process runs in the guest’s userspace beside the sample, but the
recording is not invisible. A sample with root can still look for signs of the patched
kernel, and one that exploits the guest kernel can blind the recording. The
VM summary lists what the guest can still see, for example the network chip,
the firmware vendor and the pointer name. This sample looked at /proc/self/maps,
did not stop, and finished its work.
What Triage flagged and why
Section titled “What Triage flagged and why”
Three behaviour rules fired, in the order the sample did the things they describe.
| Time | Rule | Tier | What it means here |
|---|---|---|---|
| 14:41:08.746 | evasion.debugger_check |
low | the check described above |
| 14:41:08.747 | impact.ransom_note |
medium | INC-README.txt created in 3 directories |
| 14:41:08.762 | impact.mass_rewrite |
high | 10 files renamed to .INC and/or rewritten in place within 60 s |

Why the high tier is mass_rewrite: a rename to one extension is easy to
explain for one file, not for ten in a few milliseconds, and the rule counts
across all threads of the process. Each finding freezes the count when it
fires, which is why the table says 3 directories and 10 files while the
recording holds 4 notes and 12 renames.

The ATT&CK mapping, with the reasoning:
| Technique | Source | Why it applies |
|---|---|---|
| T1486 Data Encrypted for Impact | impact.mass_rewrite, impact.ransom_note |
files rewritten in place and renamed to a new extension, with a ransom note |
| T1622 Debugger Evasion | evasion.debugger_check |
the sample checked for a debugger at startup |
| T1036 Masquerading | Sigma “Potentially Suspicious Execution From Tmp Folder” | the sample ran from /tmp |
Read the last row with care. The Sigma rule matched because the sample was
uploaded to and run from /tmp, which is where the analyst put it. It says
something about how the sample was run, not necessarily about the family.
YARA and the IDS matched nothing: the rule set used has no INC rule, and with
no traffic there is nothing for the IDS to see. The recording therefore does
not name the family on its own; the name comes from the file name, the
.INC extension and the note.
The capa results map to three other techniques (T1129 Shared Modules, T1027 Obfuscated Files or Information and T1082 System Information Discovery). They describe what the code is capable of, not what it did in the run, and I do not count them as observed.

Static analysis
Section titled “Static analysis”The static report of the same file adds what the run cannot, and a few of its results explain observations above.

The file is a stripped, dynamically linked, position-independent x86-64 ELF, so it carries no symbol names to read. capa found 26 capabilities with capa-rules v9.4.0, and the ones that matter line up with the recording:
- encrypt data using Curve25519 and hash data using sha256 via x86 extensions: the cryptography is public-key, which fits (an inference) a design where each file gets a key that only the operator can unwrap, and which would explain the per-file trailer.
- create thread and set thread name on Linux: the worker pool. The thread names are set by the sample; Triage lists the threads by TID only.
- write file on Linux (7 locations) and move file: the rewrite and the
.INCrename. - execute syscall (6 locations), encode data using XOR (21 locations) and contain obfuscated stackstrings: the evasion measures above.
- compiled with rust.

The strings of the sample are mostly noise, but three hits confirm the rest of
the picture: INC-README.txt, a message about “setting motd” together with
/etc/motd, and vim-cmd with vmsvc/getallvms. The first is the note. The
second suggests that the sample can also put its note in the login message of
a host, and the third that it can enumerate virtual machines on a real ESXi
host. Neither happened in the recording: no program was executed and
/etc/motd was not opened, because the arguments requested neither. Published
analyses of INC describe the options that switch these behaviours on; this
recording does not test them.


The report is partial: strings are cut for one very long string, and decompilation timed out on this 850 KiB binary, so there is no decompiled source behind any statement here.
Network
Section titled “Network”The route was InetSim, which answers common services inside the sandbox. Triage shows 0 capture frames, 0 destination IPs, 0 hostnames, 0 HTTP requests and 0 DNS questions. For ransomware this is unremarkable: this build needs no command-and-control to do its damage. The absence of traffic here is also why the IDS found nothing. It does not show that the family never contacts the network; this build with these arguments did not.
Indicators
Section titled “Indicators”File names are defanged with [.]. The run has no network indicators, and none
of the 16 file indicators matched the threat-intelligence feeds dated 2026-11-01.
| Type | Indicator |
|---|---|
| SHA-256 (sample) | 86d04474459f2b0df01eb59e37e26a0c5eff5c48b359fb662bf0ea513f0f7d13 |
| SHA-1 (sample) | 8b829be0837378b70b3a595ffeb60077b8b53016 |
| MD5 (sample) | 04bafa07c24c7dd08738bee51e43956c |
| ELF build ID | d28fb46dc4d5c6f99eea4005137821f42012aa9a |
| Command line | /tmp/inc-86d04474 --dir /vmfs/volumes --mode fast |
| Renamed files | original name with [.]INC appended |
| Ransom note | INC-README[.]txt, 3,448 bytes, written to every directory it walks |
| File trailer | 83 bytes appended at the old end of file, written three times per file |
| Behaviour | 12 renames to .INC in about 27 ms; 36 pwrite64 calls of 83 bytes |
The sample is available from MalwareBazaar by its SHA-256.
Detection ideas
Section titled “Detection ideas”Everything below follows from observations above. It is a starting point, not a tested rule set.
- Rename bursts to a new extension, counted per process. The twelve renames came from five threads, so a per-thread counter misses them. The built-in rule counts per process.
- A file written past its end, read in one go, then rewritten and renamed
is a stronger sequence than a rename alone:
pwrite64at the end-of-file offset,pread64of the file size plus the trailer,writeof the file size,renametoX.INC. - The same short file created in many directories within milliseconds
(
INC-README.txtin four) is the cheapest early signal, since the notes come before the first rename. - Execution from
/tmpof a binary that takes--dir /vmfs/volumes. - Speed is the main fact for defenders. This run finished in about 35 ms on
decoys of at most 1 MiB. Real virtual disks are far larger and fast mode
covers only part of each, but the lesson holds: a response that starts after
the first rename has little to save. Offline backups and restricting what
may write under
/vmfs/volumesdo more than detection.
What the recording cannot show
Section titled “What the recording cannot show”- The encryption itself. It is computation and makes no system call. The recording shows its inputs, outputs and timing, not the algorithm; capa and the published analyses supply the likely one.
- The pre-run bytes of the decoys. They were written by another run in the same boot, so the history marks the rewritten files as incomplete. That is why recovery from the recording is limited here.
- A family verdict. Without a YARA rule, the name rests on the file name, extension and note.
- Options that were not requested.
--esxi,--motdand the other modes were not tested. - A hostile sample’s view of the VM. See Anti-analysis.
Reproduce it safely
Section titled “Reproduce it safely”- Use a dedicated machine you can rebuild, and read Safe use.
- Create a Debian 13 Minimal VM with the Realistic desktop device profile, and take a snapshot so you can roll the disk back.
- On a route that drops all traffic, run a benign script that creates a few
decoy files under
/vmfs/volumes/datastore1/<name>/with the extensions.vmx,.vmdk,-flat.vmdk,.vmsn,.vswpand.nvram. - Download the sample from MalwareBazaar by its SHA-256, upload it and run it
as root with
--dir /vmfs/volumes --mode faston the InetSim route, with a 300 s limit. - Reopen the run from Run History. Start with
Triage › Files › Per-file history of one
.vswpfile, then the Syscall Trace filtersyscall == pwrite64 || syscall == rename. - Roll the VM back to the snapshot.