DEV Community

Cover image for Safely Handling Malware Samples for API Testing
Ionx Solutions
Ionx Solutions

Posted on • Originally published at blog.ionxsolutions.com

Safely Handling Malware Samples for API Testing

This is a developer's guide to safely submitting malware samples to Verisys Antivirus API.

Malware samples can be a useful tool when testing an antivirus API - but handling live malware is genuinely dangerous. This guide covers everything you need to know: from safe test files for most developers, to a purpose-built, hardened environment for those who need the real thing.

Who This Guide is For

If you're building a file upload feature and want to verify that your integration with Verisys Antivirus API works correctly before going to production, you've come to the right place. But before we go any further, we need to split this audience in two.

  1. The vast majority of developers testing an antivirus integration don't need live malware at all. The EICAR test file - a purpose-built, universally recognised test vector - is all you need to validate that your API calls are correctly wired up, that malware detections are returned and handled properly, and that your application behaves as expected when a threat is found. If that's you, jump straight to Start here: the EICAR test file.

  2. A small number of developers (such as those working in dedicated security roles, or researchers building specialised tooling) may legitimately need to work with real samples. The rest of this guide is written for you. But be warned: this is not a casual undertaking - here be dragons! Handling live malware without proper precautions can compromise your machine, your network, and potentially your colleagues' machines too.

NOTE: This guide is specifically about safely uploading malware samples to an antivirus API for detection testing. It is not a guide to malware analysis or reverse engineering. You will not be executing (or "detonating"), debugging, or decompiling anything. That distinction meaningfully reduces the risk profile, as we'll explain below.

Start Here: The EICAR Test File

The EICAR Anti-Malware Test File is the industry-standard way to test antivirus integrations without handling real malware. It was created in the 90s as a collaboration between antivirus vendors, and virtually every antivirus engine recognises it as a test threat and returns a detection.

It's a completely harmless plain text file containing exactly this string:

X5O!P%@AP[4\PZX54(P^)7CC)7}$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!$H+H*
Enter fullscreen mode Exit fullscreen mode

Despite containing no actual malicious code, every major antivirus engine flags it. It's entirely harmless - your machine is not at risk. But it lets you exercise the full detection path in your integration with confidence.

You can download it directly from EICAR's website, or you can simply create the file yourself by copying the string above into a new file and save it with any extension (.com, .txt, or anything else).

To scan it with Verisys Antivirus API:

curl https://eu1.api.av.ionxsolutions.com/v1/malware/scan/file \
    --header 'X-API-Key: YOUR_API_KEY' \
    --form file=@/path/to/eicar.com
Enter fullscreen mode Exit fullscreen mode

You should receive a response something like this:

{
  "id": "275d2ead-abb2-4764-a4d4-0797d1a2193d",
  "scan_type": "malware",
  "status": "threat",
  "content_length": 68,
  "content_type": "text/plain",
  "signals": ["Virus:EICAR_Test_File"],
  "metadata": {
    "hash_sha1": "3395856ce81f2b7382dee72602f798b642f14140",
    "hash_sha256": "275a021bbfb6489e54d471899f7db9d1663fc695ec2fe2a2c4538aabf651fd0f"
  }
}
Enter fullscreen mode Exit fullscreen mode

A status of threat and the signal Virus:EICAR_Test_File tells you everything is working as intended. If you see this, your integration is correctly passing files to the API and receiving threat detections back. That's all you need to scan real malware too.

NOTE: Don't have an API key yet? Start a free trial of Verisys Antivirus API - no credit card required. You'll be scanning files in no time!

You can also scan the EICAR file by URL, without downloading it at all:

curl https://eu1.api.av.ionxsolutions.com/v1/malware/scan/url \
    --header 'X-API-Key: YOUR_API_KEY' \
    --header 'Content-Type: application/json' \
    --data '{"file_url": "https://secure.eicar.org/eicar.com.txt"}'
Enter fullscreen mode Exit fullscreen mode

If you're satisfied that EICAR meets your needs, you're done - and your machine is safer for it. The rest of this guide covers the small number of scenarios where real malware samples are necessary - only proceed if you have a clear, justified need to work with real malware samples.

Why You Might Need Real Malware Samples

For the vast majority of file upload integrations, EICAR is sufficient. But there are legitimate reasons a developer might need to work with real samples:

  • Detection rate validation: verifying that the API correctly detects a specific malware family or strain relevant to your threat model.
  • Edge case testing: testing API behaviour with unusual file types, packed executables, or polyglot files that embed malicious payloads.
  • QA pipeline stress testing: building a comprehensive test corpus that exercises multiple threat categories - ransomware, trojans, infostealers, and so on - to validate detection across the board.
  • Security research tooling: building infrastructure that processes, classifies, or routes malware samples as part of a broader security product.

If any of these apply to you, continue reading - but proceed cautiously.

The Dangers of Handling Live Malware

Let's be direct about what you're dealing with. Unlike the EICAR file, real malware is not inert - it is specifically designed to execute, propagate, and cause harm. Even if your intention is only to upload a file to an API for scanning, the risks are real:

  • Accidental execution: Double-clicking, previewing, or opening a file in the wrong application can trigger execution. Modern operating systems have many automatic file-handling behaviours - thumbnail generation, indexing, preview pane rendering - that can invoke malicious payloads without any deliberate action on your part.

  • Network propagation: Some malware families - worms in particular - are designed to scan the local network and spread to reachable hosts automatically upon execution. If your machine is connected to a shared network, a single accidental execution can cascade rapidly.

  • Lateral movement: Many modern malware strains include persistence mechanisms, credential harvesting, and lateral movement capabilities. If a sample executes on a machine with access to shared drives, password managers, or cloud sync folders, the damage can extend far beyond the original machine.

  • Legal and policy risk: Depending on your jurisdiction, possessing certain malware samples may carry legal implications. Always ensure you're operating within the law and within your organisation's acceptable use policies.

  • Human error: This is, honestly, the most common risk. Experienced security researchers have accidentally executed malware despite knowing better. Fatigue, distraction, and familiarity all contribute. A robust environment doesn't just protect against malware - it protects against you!

The Upload-Only Advantage

Here's the key insight that most general malware handling guides miss: if your goal is only to upload a sample to an API, your risk profile is dramatically lower than a malware analyst's.

A malware analyst needs to execute samples, observe runtime behaviour, capture network traffic, and interact with live processes. That requires a fundamentally different (and far more complex) containment strategy.

You don't need any of that. Your workflow is:

  1. Download the sample (remember that by convention, samples are compressed and password-protected)
  2. Extract the sample in an isolated environment
  3. Upload the file to the API via curl
  4. Observe the response
  5. Delete the sample

You never execute anything. This means the primary risk is accidental execution, which a combination of isolation, careful tooling choices, and good habits can mitigate effectively.

Architecture Overview

The environment we'll build has two components: a dedicated physical host running Linux, and a sandbox virtual machine running inside it. The key design principle is a clean separation of responsibilities.

System Architecture

The host is responsible for downloading compressed, password-protected malware archives from the internet. It stores them in /malware-archives, a bind-mounted directory with noexec and nosuid flags. The host never extracts or uploads these encrypted archives - it only ever downloads them to disk - so the risk of accidental execution on the host is greatly reduced.

The sandbox VM is responsible for extracting archives and uploading the resulting files (typically binaries) to Verisys Antivirus API. It can see the host's /malware-archives as a read-only virtiofs mount - it can read archives, but cannot modify or add to them. Extracted binaries are stored in /malware-binaries, a tmpfs RAM disk inside the sandbox with noexec flags. Crucially, the sandbox VM's network access is permanently and significantly locked down: it can make outbound TCP/443 connections to exactly one IP address - your chosen Verisys Antivirus API endpoint - and nothing else. It cannot reach the internet, your local network, or any other host.

This separation ensures that the two critical risk factors - extracted malware binaries and internet access - never coexist in the same environment. The host retains internet access, but never encounters the extracted binaries; the sandbox VM holds the malware binaries but has no meaningful network connectivity.

The sandbox VM is also intended to be ephemeral; that is, temporary in nature. Every time the malware upload workflow is required, the sandbox VM is restored from a clean-state snapshot, and is then discarded afterwards. This means that malware binaries will only live for as long as needed, and even then, they will only exist on a RAM disk inside a disposable virtual machine.

What You'll Need

To follow this guide, you will need:

  • A dedicated physical host, separate from your day-to-day machine. An old laptop or desktop works fine. Running a Linux host OS means that most Windows-targeted malware will not execute even if it somehow escapes its VM - it simply has no runtime to attach to
  • At least 4GB RAM on the host (we'll allocate just 2GB for the sandbox VM OS)
  • At least 40GB disk on the host (we'll allocate 16GB for the sandbox VM disk)
  • Hardware virtualisation support (Intel VT-x or AMD-V) - verified during host setup

Setting Up the Host

Use a minimal, up-to-date Linux distribution for your host - Ubuntu Server LTS or Debian are good choices, and indeed we used Ubuntu Server 24.04 LTS here for this guide. There is no need to install a desktop environment, as a console is enough for our needs - this also helps reduce the attack surface. During installation you will be prompted to enter a username for a non-root account - in this guide we've used ops as that user.

Once the host OS is installed, install KVM/QEMU and the management tools you'll need:

sudo apt update && sudo apt install -y qemu-kvm libvirt-daemon-system \
    libvirt-clients bridge-utils virtinst dnsmasq
Enter fullscreen mode Exit fullscreen mode

Importantly, verify that the host hardware supports virtualisation:

# Verify hardware virtualisation support (output must be > 0)
egrep -c '(vmx|svm)' /proc/cpuinfo
Enter fullscreen mode Exit fullscreen mode

Download an ISO for the sandbox VM's guest OS - just like our host, we'll stick with Ubuntu Server LTS:

# Create the directory for ISOs
sudo mkdir -p /var/lib/libvirt/images/iso

# Download Ubuntu Server LTS
sudo wget https://releases.ubuntu.com/24.04/ubuntu-24.04.4-live-server-amd64.iso \
    -O /var/lib/libvirt/images/iso/ubuntu-24.04-server.iso
Enter fullscreen mode Exit fullscreen mode

Creating the Sandbox Virtual Network

The sandbox VM needs a dedicated virtual network, isolated from your main network. libvirt will NAT the VM's traffic through the host - in a later step, we'll use iptables to lock down exactly what the VM can actually reach.

Create a network definition file for a libvirt network bridge named virbr-sandbox:

cat > ~/sandbox-net.xml << 'EOF'
<network>
  <name>sandbox-net</name>
  <forward mode='nat'/>
  <bridge name='virbr-sandbox' stp='on' delay='0'/>
  <ip address='192.168.100.1' netmask='255.255.255.0'>
    <dhcp>
      <host mac='52:54:00:10:24:24' name='sandbox' ip='192.168.100.10'/>
      <range start='192.168.100.100' end='192.168.100.150'/>
    </dhcp>
  </ip>
</network>
EOF
Enter fullscreen mode Exit fullscreen mode

The DHCP entry with a fixed MAC address (52:54:00:10:24:24) ensures the sandbox VM always gets the same IP (192.168.100.10). We'll use this same MAC when creating the sandbox VM, and the fixed IP is what we'll later target with iptables rules to lock down network access.

Define, start, and persist the virtual network:

# Define the network from the XML file
virsh net-define ~/sandbox-net.xml

# Start the network
virsh net-start sandbox-net

# Mark it to autostart with libvirtd
virsh net-autostart sandbox-net

# Confirm it is active
virsh net-list --all
Enter fullscreen mode Exit fullscreen mode

Setting Up Shared Directories

The host needs a directory to hold compressed, encrypted malware archives, and the sandbox VM needs a read-only view of that same directory. We use bind mounts to create both - one for host use, one specifically for sharing into the VM.

# The backing directory - this is where archives actually live on the host disk
# Our non-root user is `ops` - replace with your own non-root user if required
sudo mkdir -p /var/lib/libvirt/shared/malware-archives
sudo chown ops:ops /var/lib/libvirt/shared/malware-archives
sudo chmod 0755 /var/lib/libvirt/shared/malware-archives

# A hardened bind mount for host usage - noexec, nosuid, nodev
sudo mkdir -p /malware-archives
sudo mount --bind -o noexec,nodev,nosuid \
    /var/lib/libvirt/shared/malware-archives /malware-archives

# A separate read-only bind mount to share into the sandbox VM via virtiofs
sudo mkdir -p /var/lib/libvirt/shared/malware-archives-ro
sudo mount --bind -o ro,nodev,nosuid,noexec \
    /var/lib/libvirt/shared/malware-archives \
    /var/lib/libvirt/shared/malware-archives-ro
Enter fullscreen mode Exit fullscreen mode

Make both mounts persistent by adding them to /etc/fstab:

echo "/var/lib/libvirt/shared/malware-archives /malware-archives \
    none bind,noexec,nodev,nosuid 0 0" | sudo tee -a /etc/fstab

echo "/var/lib/libvirt/shared/malware-archives \
    /var/lib/libvirt/shared/malware-archives-ro \
    none bind,ro,nodev,nosuid,noexec 0 0" | sudo tee -a /etc/fstab
Enter fullscreen mode Exit fullscreen mode

Verify both mounts are in place:

mount | grep malware
Enter fullscreen mode Exit fullscreen mode

You should see output something like this:

/dev/mapper/ubuntu--vg-ubuntu--lv on /malware-archives type ext4 (rw,nosuid,nodev,noexec,relatime)
/dev/mapper/ubuntu--vg-ubuntu--lv on /var/lib/libvirt/shared/malware-archives-ro type ext4 (ro,nosuid,nodev,noexec,relatime)
Enter fullscreen mode Exit fullscreen mode

The reason for two separate mounts (rather than sharing the backing directory directly) is defence in depth: the host uses /malware-archives for its own operations; the sandbox VM receives only the dedicated read-only path. libvirt's virtiofs shares a directory from the host into the guest - by pointing it at the read-only bind mount rather than the live backing directory, we get kernel-level enforcement of the read-only constraint inside the VM, not just filesystem permissions.

Creating the Sandbox VM

With the network and shared directories in place, we are now ready to create the sandbox VM. We pass the read-only malware-archives directory in at creation time using a virtiofs filesystem share:

# Create the VM disk image
sudo qemu-img create -f qcow2 /var/lib/libvirt/images/sandbox.qcow2 16G

# Create the VM and boot the Ubuntu installer
sudo virt-install \
    --connect qemu:///system \
    --name sandbox \
    --vcpus 4 \
    --memory 2048 \
    --network network=sandbox-net,mac=52:54:00:10:24:24,model=virtio \
    --graphics=none \
    --disk path=/var/lib/libvirt/images/sandbox.qcow2,format=qcow2,bus=virtio \
    --filesystem source=/var/lib/libvirt/shared/malware-archives-ro,target=malware-archives,accessmode=passthrough,driver.type=virtiofs \
    --location /var/lib/libvirt/images/iso/ubuntu-24.04-server.iso,kernel=casper/vmlinuz,initrd=casper/initrd \
    --os-variant ubuntu24.04 \
    --console pty,target_type=serial \
    --memorybacking access.mode=shared,source.type=memfd \
    --extra-args="console=ttyS0,115200n8"
Enter fullscreen mode Exit fullscreen mode

A few flags worth noting:

  • --network mac=52:54:00:10:24:24: matches the fixed DHCP entry in the network definition, ensuring the VM always gets IP 192.168.100.10.
  • --filesystem driver.type=virtiofs: virtiofs is a high-performance virtio-based filesystem protocol for sharing host directories into guests. It requires --memorybacking access.mode=shared,source.type=memfd, which enables the shared memory backend virtiofs needs.
  • --graphics=none: no VNC or SPICE surface is created; there is no clipboard or drag-and-drop channel to act as an escape vector.
  • --location: if using a different Linux distribution other than Ubuntu Server LTS, you will need to change this value accordingly

Follow the on-screen prompts to install Ubuntu Server in the guest VM. Accept defaults where possible - a minimal installation is fine. As with the host OS, during installation you will be prompted to enter a username for a non-root account - in this guide we've used ops as that user, just as we did for the host OS.

Configuring the Sandbox VM

Once the guest OS is installed, the VM should reboot into the fresh system. If you're no longer still in a console prompt in the sandbox, reconnect from the host:

# Connect to the sandbox VM console
sudo virsh --connect qemu:///system console sandbox
Enter fullscreen mode Exit fullscreen mode

Install the few tools you'll need for working with malware archives:

sudo apt install -y unzip p7zip-full jq
Enter fullscreen mode Exit fullscreen mode

unzip and p7zip-full handle the compressed archives that malware repositories distribute samples in. jq is useful for pretty-printing the JSON responses from the Verisys API.

Next, configure the mount points for the shared malware archives and the extraction RAM disk:

# Mount point for the read-only virtiofs share from the host
sudo mkdir -p /malware-archives
echo "malware-archives  /malware-archives  virtiofs  ro,nodev,nosuid,noexec,_netdev 0 0" \
    | sudo tee -a /etc/fstab

# Mount point for a 1GB noexec tmpfs RAM disk - extracted binaries live here
sudo mkdir -p /malware-binaries
echo "malware-binaries  /malware-binaries  tmpfs  rw,noexec,nodev,nosuid,size=1G 0 0" \
    | sudo tee -a /etc/fstab
Enter fullscreen mode Exit fullscreen mode

The _netdev flag on the virtiofs mount tells the init system to wait for the virtio network backend to be ready before mounting - without it, the mount may fail on boot. The tmpfs size of 1GB is a sensible ceiling for a working extraction area, but do adjust it if you expect to need more space (and you have the RAM, of course!).

Reboot the sandbox VM to apply the fstab entries:

sudo reboot now
Enter fullscreen mode Exit fullscreen mode

Once it's back up, verify both mounts are present:

mount | grep malware
Enter fullscreen mode Exit fullscreen mode

You should see:

malware-binaries on /malware-binaries type tmpfs (rw,nosuid,nodev,noexec,relatime,size=1048576k,inode64)
malware-archives on /malware-archives type virtiofs (ro,nosuid,nodev,noexec,relatime,_netdev)
Enter fullscreen mode Exit fullscreen mode

With both mounts correctly in place, shut the VM down cleanly and take a snapshot - this is the clean baseline you'll revert to at the start of every malware upload session:

# Still connected to the console of the sandbox VM, shut it down
sudo shutdown -P now
Enter fullscreen mode Exit fullscreen mode

Then once you're back at the host prompt:

virsh snapshot-create-as sandbox --name "clean"
Enter fullscreen mode Exit fullscreen mode

This snapshot captures the configured, clean state of the VM - mounts, installed tools, but no malware. Every session starts by reverting to this snapshot, which guarantees a completely clean environment regardless of what happened in the previous session.

To start a new sandbox VM session:

virsh shutdown sandbox
virsh snapshot-revert sandbox --snapshotname "clean"
virsh start sandbox

# Connect to the VM console
sudo virsh --connect qemu:///system console sandbox
Enter fullscreen mode Exit fullscreen mode

Locking Down the Network

The last piece of the setup - and arguably the most important - is restricting what the sandbox VM can do with its network connection. At this point, the VM still has full internet access via the NAT network, so we need to reduce that to a single outbound destination.

First, save a backup of your current iptables rules so you can restore them if anything goes wrong:

sudo iptables-save | tee ~/iptables_without_sandbox.conf
Enter fullscreen mode Exit fullscreen mode

Resolve the IP address of your chosen Verisys Antivirus API regional endpoint. We'll use eu1 here - swap it for gb1, us1, or ap1 depending on your region:

dig +short eu1.api.av.ionxsolutions.com
Enter fullscreen mode Exit fullscreen mode

This gives you the endpoint IP - 5.75.138.88 for eu1 at time of writing. Note it down, as you'll use it in the rules below.

Region Endpoint
EU (Germany) eu1.api.av.ionxsolutions.com
UK gb1.api.av.ionxsolutions.com
US us1.api.av.ionxsolutions.com
Asia Pacific (Singapore) ap1.api.av.ionxsolutions.com

Now create a dedicated iptables chain for the sandbox and populate it with rules:

# Create the chain (or flush it if it already exists from a previous run)
sudo iptables -N SANDBOX_LOCKDOWN 2>/dev/null || iptables -F SANDBOX_LOCKDOWN

# Allow return traffic for connections the VM has already established
sudo iptables -A SANDBOX_LOCKDOWN -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT

# Allow outbound HTTPS to the Verisys Antivirus API endpoint only
# Replace 5.75.138.88 with the IP you resolved above if using a different region
sudo iptables -A SANDBOX_LOCKDOWN -p tcp -d 5.75.138.88 --dport 443 -j ACCEPT

# Log anything that doesn't match the above rules, then drop it
sudo iptables -A SANDBOX_LOCKDOWN -j LOG --log-prefix "SANDBOX DROP: "
sudo iptables -A SANDBOX_LOCKDOWN -j DROP

# Attach the chain to the FORWARD table for traffic originating from the sandbox bridge
# The -C check avoids inserting a duplicate rule if this is re-run
sudo iptables -C FORWARD -i virbr-sandbox -j SANDBOX_LOCKDOWN 2>/dev/null || \
    sudo iptables -I FORWARD 1 -i virbr-sandbox -j SANDBOX_LOCKDOWN
Enter fullscreen mode Exit fullscreen mode

Persist the rules across reboots:

sudo netfilter-persistent save
Enter fullscreen mode Exit fullscreen mode

The chain is attached using -i virbr-sandbox - matching on the bridge interface name rather than a source IP range. This means the rules apply to all traffic entering the host's FORWARD path from the sandbox bridge, regardless of what IP the guest happens to have. It's slightly more robust than source IP matching.

The LOG rule before the final DROP means that any traffic the VM attempts to send that isn't to the Verisys endpoint will appear in your system logs (/var/log/syslog or via journalctl -k), prefixed with SANDBOX DROP:. This is useful for diagnosing unexpected behaviour.

NOTE: While we try hard not to, the IP addresses of Verisys API endpoints could change over time. If you need to update the rules to change the IP, update the SANDBOX_LOCKDOWN chain: flush it with sudo iptables -F SANDBOX_LOCKDOWN, re-add the rules above with the new IP, and then run sudo netfilter-persistent save to persist the changes.

Sourcing Malware Samples

With your environment ready, you now need malware samples to work with. The following are well-regarded, legitimate sources used by security researchers worldwide, and all provide samples in password-protected archives to prevent accidental execution. Remember that downloads will happen only on the host - the sandbox VM's network is locked down and can't reach them. Do not extract downloaded malware sample archives on the host system under any circumstances.

WARNING: Downloading malware samples is a deliberate, professional act. Only download what you actually need. Keep /malware-archives tidy - delete archives you're no longer working with. The directory is noexec, nosuid, and nodev, so archives cannot execute directly on the host; but good housekeeping is still important. And for extraction and upload, always, always only work within your isolated sandbox environment as described above.

MalwareBazaar

MalwareBazaar is run by abuse.ch, a Swiss non-profit security research organisation. It maintains a large, searchable database of malware samples contributed by the security community, browsable by malware family, file type, tag, and signature. Samples are distributed as password-protected ZIP archives (password: infected). It's one of the most reputable and widely used sources in the industry.

Use the MalwareBazaar API to download a sample by its SHA-256 hash - find this on the sample's page on the MalwareBazaar website:

As well as a website, MalwareBazaar also provides an API for programmatic access, which is useful if you're building a test corpus. Note that you'll need to create an account before you can use the API (it's free to use under their free use principles) For example, to download a sample by its SHA-256 hash (such hashes can be found on the website):

# On the host
hash="<YOUR_SHA256_HERE>"
wget -O /malware-archives/${hash}.zip \
    "https://mb-api.abuse.ch/api/v1/" \
    --header "Auth-Key: YOUR_API_AUTH_KEY_HERE" \
    --post-data="query=get_file&sha256_hash=${hash}"
Enter fullscreen mode Exit fullscreen mode

TheZoo

TheZoo is a curated GitHub repository of live malware samples, explicitly intended for security researchers. Samples are organised by family name and stored as password-protected ZIP archives (password: infected).

# On the host, download a sample directly from the GitHub repository
family="<MALWARE_FAMILY_NAME>"
wget -O /malware-archives/${family}.zip \
    https://github.com/ytisf/theZoo/raw/refs/heads/master/malware/Binaries/${family}/${family}.zip
Enter fullscreen mode Exit fullscreen mode

vx-underground

vx-underground maintains one of the largest freely available malware archives on the internet, covering a wide range of families and historical strains. Samples are stored as password-protected 7z archives (password: infected).

To obtain a sample's URL for download, navigate through the website to your chosen malware binary, and copy the download link. You can then use wget or curl to download the sample (note that VX Underground download links are only valid for 1 hour):

# On the host
wget -O /malware-archives/sample.zip "https://<vx-underground-sample-url>"
Enter fullscreen mode Exit fullscreen mode

End-to-End Workflow

Now everything is setup, let's walk through a complete workflow example from start to finish. We'll download a real malware sample (Nivdort, a Windows data-stealing trojan dating back to 2016) from the TheZoo repository, scan it with Verisys Antivirus API, and interpret the result.

Step 1 - Download the sample archive on the host:
Download a sample of the Nivdort trojan:

# On the host
wget -O /malware-archives/Nivdort.zip \
    https://github.com/ytisf/theZoo/raw/refs/heads/master/malware/Binaries/Nivdort/Nivdort.zip
Enter fullscreen mode Exit fullscreen mode

Step 2 - Start a clean instance of the sandbox VM:
Start a nice, clean sandbox instance to work in:

# On the host - revert to the clean snapshot and start afresh
virsh shutdown sandbox 2>/dev/null
virsh snapshot-revert sandbox --snapshotname "clean"
virsh start sandbox

# Connect to the VM console and log in when prompted
sudo virsh --connect qemu:///system console sandbox
Enter fullscreen mode Exit fullscreen mode

Step 3 - Extract the archive inside the sandbox VM:
The archive is visible inside the sandbox VM at /malware-archives/Nivdort.zip via the read-only virtiofs mount. Extract it to the noexec RAM disk, entering password infected when prompted:

# Inside the sandbox VM
unzip /malware-archives/Nivdort.zip -d /malware-binaries
Enter fullscreen mode Exit fullscreen mode

Note what's happening here: the archive comes in to the sandbox VM through a read-only mount (the sandbox can't modify or add to /malware-archives), and the extracted binary lands in a tmpfs RAM disk with noexec set at the mount level - the kernel will refuse to execute binaries directly from /malware-binaries, regardless of the file's permission bits.


Step 4 - Upload to Verisys Antivirus API:
Now we'll upload the sample (change endpoint eu1.api.av.ionxsolutions.com if you're using a different one):

# Inside the sandbox VM
curl https://eu1.api.av.ionxsolutions.com/v1/malware/scan/file \
    --header 'X-API-Key: YOUR_API_KEY' \
    --form file=@/malware-binaries/sample.exe | jq
Enter fullscreen mode Exit fullscreen mode

This is the only outbound network connection the VM is permitted to make - TCP/443 to 5.75.138.88. Any other network traffic from the VM is logged and dropped by the SANDBOX_LOCKDOWN chain.

NOTE: Don't have an API key yet? Start a free trial of Verisys Antivirus API - no credit card required. You'll be scanning malware in minutes!

Interpreting the API Response

Verisys Antivirus API returns a consistent JSON structure for every scan. For a detected threat, like the Nivdort sample above, you'll see something like this:

{
  "id": "9f9590f6-1be7-4764-8e2e-233133aa4f13",
  "scan_type": "malware",
  "status": "threat",
  "content_length": 892416,
  "content_type": "application/vnd.microsoft.portable-executable",
  "signals": [
    "TrojanSpy:Win32/Nivdort.DU"
  ],
  "metadata": {
    "hash_sha1": "5b3e04f8208d3de912413efce27372255d6b3fe9",
    "hash_sha256": "eea059174127860154f4dce1a7d8995a9a5056febf73819d63ddadb522ed6c8f"
  }
}
Enter fullscreen mode Exit fullscreen mode

And for a clean file you'll see something like this:

{
  "id": "8f1c4b2d-3a9e-4f7c-b6d1-2e5a8c0f9b3e",
  "scan_type": "malware",
  "status": "clean",
  "content_length": 12288,
  "content_type": "application/pdf",
  "signals": [],
  "metadata": {
    "hash_sha1": "adc83b19e793491b1c6ea0fd8b46cd9f32e592fc",
    "hash_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
  }
}
Enter fullscreen mode Exit fullscreen mode

Key fields to pay attention to:

Field Description
status clean, threat (or error if something went wrong)
signals Names of identified threats (empty array if clean)
content_type The actual detected file type, determined by binary signature analysis - not the filename or client-supplied Content-Type header
metadata.hash_sha1 SHA-1 hash of the scanned file
metadata.hash_sha256 SHA-256 hash of the scanned file - useful for cross-referencing against threat intelligence databases like MalwareBazaar

The content_type field is worth highlighting: Verisys Antivirus API detects the real file type from the binary signature, independently of the file extension. A Windows executable renamed to invoice.pdf is still identified as a Windows binary - a critical defence against file type spoofing in production upload pipelines.

SHA-1/SHA-256 hashes (metadata.hash_sha*) are particularly useful when working with known samples, as they provide a unique, verifiable file signature that can be cross-referenced against threat intelligence databases.

Closing Thoughts

The environment described in this guide is deliberately designed around a clean separation of concerns: the host handles internet access and storage of password-protected archives; the sandbox handles extraction and upload to Verisys Antivirus API. Neither environment does both. This means the two most dangerous moments in the workflow - open internet access and live, extracted binaries - never coincide in the same place.

The key protections working together are:

  • A dedicated physical host running Linux, so Windows-targeted malware has no runtime to attach to, even if it somehow broke out of the sandbox onto the host.
  • noexec bind mounts on the host, so archives can't execute even if somehow triggered.
  • A read-only virtiofs share into the sandbox VM, so the sandbox can read archives but cannot modify the source directory.
  • A noexec tmpfs RAM disk for extracted binaries inside the sandbox VM, enforced at the mount level by the kernel.
  • A permanent iptables lockdown allowing only TCP/443 to one specific Verisys API IP - the sandbox VM cannot reach the internet, your local network, or any C2 (Command and Control) infrastructure.
  • Snapshot-based session management, so every sandbox session begins from a guaranteed clean state.

For the majority of developers, the answer remains the same as it was at the beginning of this guide: use EICAR, validate your integration, and ship with confidence. But for those who need more, this architecture provides a secure, tightly controlled environment for safely acquiring and processing real-world malware samples.

Learn more about Verisys Antivirus API, our language-agnostic antivirus REST API that makes it simple to add malware scanning to any application.

Top comments (0)