Skip to content

An analysis of INC ransomware

An analysis of INC ransomwareDiagram of the recorded run: the sample starts five worker threads, they rewrite virtual-machine files with pwrite64, each file is renamed to .INC, and a ransom note is left behind.An analysis of INC ransomwareLinux/ESXi encryptor, Rust build, recorded from the guest kernelsamplepid 1362tid 1363tid 1364tid 1365tid 1366tid 13675 worker threadspwrite64 ×36.vmx.vmdk.vswprename ×12.vmx.INC.vmdk.INC.vswp.INCINC-README.txt

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.

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).

The Summary tab of the VM eval-dm. VM configuration: Compute (2 vCPU, 2 GiB RAM, default CPU model and acceleration), Storage and display (8 GiB disk, base image Debian 13 Minimal, auto display), Network and transport (routed, route chosen per run, ring 1024 KiB per vCPU, waits up to 5 s when full). Device profile Realistic desktop: machine Lenovo ThinkCentre M75q Gen 2 Mini PC, UEFI firmware, Intel e1000e network card, guest network 192.168.1.0/24, clock RTC UTC, and the list of what is still visible to the guest.
The Summary of the VM used for the run, with its complete configuration.
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.

Run History for eval-dm: two runs, the INC sample run 18787cdc started 14:41:08.726 with arguments --dir /vmfs/volumes --mode fast, Finished, Exit 0, and the decoy setup script /tmp/decoys.sh run 942a4126 started 14:41:08.207. The Details panel shows the recorded process tree: process inc-86d04474, pid 1362, 436 calls, exit 0, and the route InetSim.
Run History with the decoy setup run and the INC run.

Times in the app are in the host’s time zone, UTC+7.

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).

Triage, Process tab, process tree: pid 1362 with five threads, TIDs 1363 to 1367, every row /tmp/inc-86d04474 --dir /vmfs/volumes --mode fast. The first row is first seen at 14:41:08.745 and the threads between 14:41:08.746 and 14:41:08.748; last seen times run to 14:41:08.780.
The process tree: one process and five worker threads.

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.

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.

Triage, Files tab, Per-file history of web01.vswp, also seen as web01.vswp.INC: open, write, read, write x3, rename. Calls: openat, pwrite64 of 83 bytes at offset 0x40000, pread64 of 262,227 bytes at offset 0, write of 262,144 bytes at offset 0, two more pwrite64 calls of 83 bytes at 0x40000, then rename. Below: the SHA-256 the guest took of the file after the final write and the notices that earlier bytes are unavailable and the transferred content contains capture gaps or exceeds the bounded preview.
The per-file history of web01.vswp, from open to rename.
  1. openat. The worker opens the file.
  2. pwrite64 of 83 bytes at offset 0x40000. 0x40000 is 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 string INC, 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 .vswp trailer begins \xd62"\xe3O, the flat disk’s begins .\xc8ZY), which fits per-file values. I did not decode them.
  3. pread64 of 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.
  4. write of 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 is write, not pwrite64, because the file position is still 0; the earlier pread64 did not move it.
  5. pwrite64 of 83 bytes at 0x40000, 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.
  6. rename from web01.vswp to web01.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.

Syscall Trace filtered with syscall == pwrite64 || syscall == rename: 48 matching of 436 saved calls, all in File and filesystem. pwrite64 rows from threads 1364, 1367, 1365, 1366 and 1363 each return 83 with a captured buffer; rename rows show oldname /vmfs/volumes/datastore1/web01/web01.vswp. The Details panel shows the pwrite64 arguments and the 83 captured bytes.
The trace filtered to pwrite64 and rename: every trailer write returns 83, interleaved across five threads.

--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.

Triage, Files tab, Per-file history of web01-flat.vmdk, also seen as web01-flat.vmdk.INC: pwrite64 of 83 bytes at 0x100000, pread64 of 1,003,520 bytes at offset 0, write of 1,003,520 bytes at offset 0 fourteen milliseconds later, two more pwrite64 calls of 83 bytes at 0x100000 and the rename.
The per-file history of web01-flat.vmdk: 1,003,520 of its 1,048,576 bytes are read and rewritten.

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).

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.

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.

Antinomie Triage summary for the INC run: YARA 0 rules matched, IDS 0, Sigma 1 rule matched; MITRE ATT&CK T1036 Masquerading, T1486 Data Encrypted for Impact and T1622 Debugger Evasion; behaviour and files counts: 436 syscalls, 6 processes, 3 suspicious observations, 28 opened-file operations, 25 read-file operations and 16 changed-file groups; network evidence for the InetSim route with 0 capture frames, 0 destination IPs, 0 observed hostnames, 0 HTTP/1 requests and 0 DNS questions.
The Triage summary of the run.

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
Triage, Process tab, Suspicious activity: three rows, evasion.debugger_check (low, 14:41:08.746), impact.ransom_note (medium, 14:41:08.747) and impact.mass_rewrite (high, 14:41:08.762). The Details panel for impact.mass_rewrite shows the process inc-86d04474 (1362 / 1363), the summary, source MITRE ATT&CK T1486 and detection DET0215, and confidence high.
Suspicious activity, with the details of the mass-rewrite finding.

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.

Triage Summary, Main activity graph: phases impact, severity marks at 14:41:08.746 (Evasion), 14:41:08.747 and 14:41:08.762 (Impact), with 1 high, 1 medium and 1 low finding; below it the Indicators card with 0 of 16 matched and the Observed activity list.
The activity graph of the run: evasion, then the notes, then the mass rewrite.

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.

Triage, Files tab, Renamed / moved: twelve rename rows between 14:41:08.750 and 14:41:08.777 for web01 and db01 files under /vmfs/volumes/datastore1, ending with the two -flat.vmdk files.
The twelve renames, from 14:41:08.750 to 14:41:08.777.

The static report of the same file adds what the run cannot, and a few of its results explain observations above.

Triage, Properties: executable verified, 870,920 bytes (850.5 KiB); file type ELF Dynamic X86_64; magic ELF 64-bit LSB shared object, x86-64, dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, stripped; SHA-256, SHA-1 and MD5.
Triage › Properties for the sample.

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 .INC rename.
  • execute syscall (6 locations), encode data using XOR (21 locations) and contain obfuscated stackstrings: the evasion measures above.
  • compiled with rust.
Static analysis, Capa Matrix, Table: 26 capabilities from capa with capa-rules v9.4.0. The rows include write file on Linux (7 locations), encode data using XOR (21 locations), contain obfuscated stackstrings, create process on Linux, encrypt data using Curve25519, hash data using sha256 via x86 extensions, execute syscall, create thread and set thread name on Linux.
The capa capabilities of the sample.

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.

Static analysis, Strings, filtered with value contains motd: three rows including Error: '' while setting motd!, and a string containing /etc/motd, vim-cmd and vmsvc/getallvms.
Strings that mention motd, with the vim-cmd text next to /etc/motd.
Static analysis, File Info, Summary: state Partial, path /tmp/inc-86d04474, hashes, specimen source fetched from guest, ELF, X86_64, 64-bit, dynamic, little endian; categories Metadata, Structures, Strings (partial, 10,372 rows), Regex matches, Entropy, Hardening, Rules, Identification, Capabilities (26 rows), Disassembly (150,085 rows) and Decompilation (timeout).
Static analysis File Info and the status of each check.

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.

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.

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.

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: pwrite64 at the end-of-file offset, pread64 of the file size plus the trailer, write of the file size, rename to X.INC.
  • The same short file created in many directories within milliseconds (INC-README.txt in four) is the cheapest early signal, since the notes come before the first rename.
  • Execution from /tmp of 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/volumes do more than detection.
  • 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, --motd and the other modes were not tested.
  • A hostile sample’s view of the VM. See Anti-analysis.
  1. Use a dedicated machine you can rebuild, and read Safe use.
  2. Create a Debian 13 Minimal VM with the Realistic desktop device profile, and take a snapshot so you can roll the disk back.
  3. 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, .vswp and .nvram.
  4. Download the sample from MalwareBazaar by its SHA-256, upload it and run it as root with --dir /vmfs/volumes --mode fast on the InetSim route, with a 300 s limit.
  5. Reopen the run from Run History. Start with Triage › Files › Per-file history of one .vswp file, then the Syscall Trace filter syscall == pwrite64 || syscall == rename.
  6. Roll the VM back to the snapshot.