Executing Rules

Execute IoI rules against your own forensic datasets in five steps.

Testing with published datasets

To reproduce one of the published anti-forensic cases directly, start on the Artifacts & Datasets page, download a per-case reproducibility bundle from the Reproducibility bundle column, then follow the steps below using the included raw artifacts and parser outputs.

The framework operates in three layers:

  • Instantiators — Python scripts that map artifact parser CSV output to CASE/UCO JSON-LD knowledge graphs.
  • Knowledge Graph — named graphs loaded into a SPARQL 1.1 triplestore (Virtuoso), one per artifact source.
  • IoI Rules — SPARQL signatures that query across graphs to surface cross-artifact contradictions.

Prerequisites

Python 3.9+, rdflib and pandas for instantiators and JSON-LD conversion, and a SPARQL 1.1 triplestore. Validation used OpenLink Virtuoso Open-Source Edition running in Docker.

pip install rdflib pandas

Pull the Virtuoso Docker image:

docker pull openlink/virtuoso-opensource-7:latest

Step-by-step

1

Clone the repository

The repository contains all instantiator scripts, JSON-LD templates, ground-truth documents, and IoI SPARQL rules.

git clone https://github.com/ioi-framework/ioi-framework.git
cd ioi-framework
2

Parse your artifacts

If you downloaded one of the published case bundles from Artifacts & Datasets, use the bundled raw artifacts and CSV parser outputs here instead of exporting your own.

Use artifact-specific parsers to produce structured CSV output. The framework was validated with the following tools:

# NTFS $MFT and $UsnJrnl:$J
MFTECmd.exe -f "$MFT" --csv ./output --csvf mft.csv
MFTECmd.exe -f "$J"   --csv ./output --csvf usn.csv

# Windows Event Logs
EvtxECmd.exe -f Security.evtx --csv ./output

# LNK files
LECmd.exe -f target.lnk --csv ./output

# Manual Chrome History export (if you have a copied SQLite History DB)
python3 SCRIPTS/export_chrome_history.py "/path/to/History" ./output/history.json
3

Instantiate CASE/UCO knowledge graphs

Run the instantiator for each artifact. Each script maps parser CSV fields to the CASE/UCO ontology and serializes the result as JSON-LD.

mkdir -p outputs

python instantiators/mft_instantiator.py "<MFT_CSV>" outputs/mft_filled.jsonld
python instantiators/usn_instantiator.py "<USN_CSV>" outputs/usn_filled.jsonld
python3 instantiators/history_instantiator.py ./output/history.json outputs/history_filled.jsonld

Convert every JSON-LD graph required by the rule to N-Triples for bulk loading. For browser-history work outside Autopsy, the flow is: copied Chrome History SQLite DB → SCRIPTS/export_chrome_history.py → history_instantiator.py → JSON-LD/N-Triples:

python3 SCRIPTS/convert_to_ntriples.py outputs/mft_filled.jsonld outputs/mft_case.nt
python3 SCRIPTS/convert_to_ntriples.py outputs/usn_filled.jsonld outputs/usn_case.nt
python3 SCRIPTS/convert_to_ntriples.py outputs/history_filled.jsonld outputs/history_case.nt

Use the rule page to identify the required named graphs. For example, IOI-002 requires mft, usn, and history; IOI-004 requires mft and usn.

Note: For large MFT/USN files, use --chunk-size 5000 to split the output into multiple JSON-LD chunks. Each chunk is then converted to N-Triples separately.

python instantiators/mft_instantiator.py "<MFT_CSV>" outputs/mft_filled.jsonld --chunk-size 5000
# Converts each chunk: outputs/mft_filled_chunk0.jsonld, _chunk1.jsonld, ...
for f in outputs/mft_filled_chunk*.jsonld; do
  python3 SCRIPTS/convert_to_ntriples.py "$f" "${f%.jsonld}.nt"
done
4

Start Virtuoso and verify your setup

Start Virtuoso and wait for it to be ready before loading data:

docker run --name vos -d -e DBA_PASSWORD=dba \
  -p 8890:8890 -p 1111:1111 \
  openlink/virtuoso-opensource-7:latest

# Wait ~10 seconds, then confirm it is ready
docker exec vos isql 1111 dba dba "exec=select 1;"

# Create import/query directories used by the commands below.
# Some Virtuoso images do not include /usr/share/proj by default.
docker exec vos mkdir -p /usr/share/proj /database

You should see 1 returned. If the command fails, wait a few more seconds and retry.

Verify your environment using the AF-004 test graphs (no real data needed). These synthetic graphs are included in the repository and produce a known result:

# Copy test graphs into the container
docker cp CASES/AF-004/test/mft_test.nt vos:/usr/share/proj/mft_test.nt
docker cp CASES/AF-004/test/usn_test.nt vos:/usr/share/proj/usn_test.nt

# Load named graphs and verify counts.
printf "DB.DBA.TTLP_MT(file_to_string_output('/usr/share/proj/mft_test.nt'), '', 'https://ioi-framework.github.io/cases/AF-004/graphs/mft', 512);\nDB.DBA.TTLP_MT(file_to_string_output('/usr/share/proj/usn_test.nt'), '', 'https://ioi-framework.github.io/cases/AF-004/graphs/usn', 512);\nSPARQL SELECT ?g (COUNT(*) AS ?count) WHERE { GRAPH ?g { ?s ?p ?o } FILTER(?g IN (<https://ioi-framework.github.io/cases/AF-004/graphs/mft>, <https://ioi-framework.github.io/cases/AF-004/graphs/usn>)) } GROUP BY ?g;\nEXIT;\n" \
  | docker exec -i vos isql 1111 dba dba

# Run IOI-004 — expect 1 row
docker cp RULES/structural/IOI-004_vss_traces_missing.rq vos:/database/rule.rq
docker exec vos bash -lc "printf 'SPARQL\n'; sed '/^[[:space:]]*#/d' /database/rule.rq; printf '\n;'" \
  | docker exec -i vos isql 1111 dba dba

The piped printf form above is preferred over shell heredocs because it avoids common copy-paste failures where indented or malformed EOF lines leave users stuck at a shell > prompt. A result with 1 row confirms Virtuoso is correctly loaded and rules execute as expected. The row corresponds to bare {GUID} VSS deletion evidence; Apps_{GUID} names are ignored to avoid Windows Search false positives. IOI-004 also requires the full VSS infrastructure triad (tracking.log, IndexerVolumeGuid, _OnDiskSnapshotProp) to be present before correlating to GUID deletions. This flow uses /usr/share/proj because Virtuoso's default DirsAllowed configuration reads from that directory, and TTLP_MT loads the triples directly once the files are there. See CASES/AF-004/ground_truth.md for what this result means forensically.

5

Load your own graphs and execute IoI rules

Copy every N-Triples file required by your selected rule into the container, load each file into the matching named graph IRI, verify nonzero graph counts, then run the rule. Use the artifact list on the rule page to decide which graph files to include; for example, IOI-002 uses mft, usn, and history, while IOI-004 uses mft and usn.

# Load your own graph and execute an IoI rule
#
# IMPORTANT:
# Replace all placeholder values before running.
# Do not run commands with placeholder values unchanged.
#
# Replace:
# CASE_ID   with your case ID
# ARTIFACT  with the artifact graph name, such as mft, usn, history, prefetch, registry
# CATEGORY  with the rule folder name
# RULE_FILE with the rule filename
#
# Your local graph file should already exist.
# It can keep any descriptive local filename; the docker cp command below
# copies it into a stable container filename that matches the graph name.

# Ensure Virtuoso's import and rule directories exist.
docker exec vos mkdir -p /usr/share/proj /database

# Copy the required N-Triples graph into the Virtuoso container.
docker cp outputs/YOUR_LOCAL_FILE.nt vos:/usr/share/proj/ARTIFACT_case.nt

# Start Virtuoso isql inside the vos container.
# Everything between <<'EOF' and EOF is sent directly to isql.
docker exec -i vos isql 1111 dba dba <<'EOF'
SPARQL CLEAR GRAPH <https://ioi-framework.github.io/cases/CASE_ID/graphs/ARTIFACT>;

DB.DBA.TTLP_MT(
  file_to_string_output('/usr/share/proj/ARTIFACT_case.nt'),
  '',
  'https://ioi-framework.github.io/cases/CASE_ID/graphs/ARTIFACT',
  512
);

SPARQL SELECT ?g (COUNT(*) AS ?count)
WHERE {
  GRAPH ?g { ?s ?p ?o }
  FILTER(?g IN (
    <https://ioi-framework.github.io/cases/CASE_ID/graphs/ARTIFACT>
  ))
}
GROUP BY ?g;

EXIT;
EOF

# For rules that require multiple artifact graphs, repeat the copy command
# and add one CLEAR GRAPH and one TTLP_MT load block for each required graph.

# Copy the selected IoI rule into the container.
docker cp RULES/CATEGORY/RULE_FILE.rq vos:/database/rule.rq

# Execute the IoI rule.
# Flattening the rule body to one line reduces noisy continuation prompts from isql.
docker exec vos bash -lc "printf 'SPARQL '; sed '/^[[:space:]]*#/d' /database/rule.rq | tr '\n' ' ' | sed 's/  */ /g'; printf '\n;'" \
  | docker exec -i vos isql 1111 dba dba

Example (AF-002):

# Ensure Virtuoso's import and rule directories exist.
docker exec vos mkdir -p /usr/share/proj /database

# Copy the generated N-Triples files into the Virtuoso container.
# The descriptive local filenames stay on disk, but are copied to stable
# container filenames that match the graph names used below.
docker cp outputs/af002_mft_indexeddb.nt vos:/usr/share/proj/mft_case.nt
docker cp outputs/af002_usn_history_tamper.nt vos:/usr/share/proj/usn_case.nt
docker cp outputs/af002_history_post.nt vos:/usr/share/proj/history_case.nt

# Start Virtuoso isql inside the vos container.
# Everything between <<'EOF' and EOF is sent directly to isql.
docker exec -i vos isql 1111 dba dba <<'EOF'
SPARQL CLEAR GRAPH <https://ioi-framework.github.io/cases/AF-002/graphs/mft>;
SPARQL CLEAR GRAPH <https://ioi-framework.github.io/cases/AF-002/graphs/usn>;
SPARQL CLEAR GRAPH <https://ioi-framework.github.io/cases/AF-002/graphs/history>;

DB.DBA.TTLP_MT(
  file_to_string_output('/usr/share/proj/mft_case.nt'),
  '',
  'https://ioi-framework.github.io/cases/AF-002/graphs/mft',
  512
);

DB.DBA.TTLP_MT(
  file_to_string_output('/usr/share/proj/usn_case.nt'),
  '',
  'https://ioi-framework.github.io/cases/AF-002/graphs/usn',
  512
);

DB.DBA.TTLP_MT(
  file_to_string_output('/usr/share/proj/history_case.nt'),
  '',
  'https://ioi-framework.github.io/cases/AF-002/graphs/history',
  512
);

SPARQL SELECT ?g (COUNT(*) AS ?count)
WHERE {
  GRAPH ?g { ?s ?p ?o }
  FILTER(?g IN (
    <https://ioi-framework.github.io/cases/AF-002/graphs/mft>,
    <https://ioi-framework.github.io/cases/AF-002/graphs/usn>,
    <https://ioi-framework.github.io/cases/AF-002/graphs/history>
  ))
}
GROUP BY ?g;

EXIT;
EOF

# Copy the IoI rule into the container.
docker cp RULES/semantic/IOI-002_chrome_history_missing.rq vos:/database/rule.rq

# Execute the IoI rule.
docker exec vos bash -lc "printf 'SPARQL '; sed '/^[[:space:]]*#/d' /database/rule.rq | tr '\n' ' ' | sed 's/  */ /g'; printf '\n;'" \
  | docker exec -i vos isql 1111 dba dba

A non-empty result set indicates a detected inconsistency. If the graph-count query returns zero for any required graph, fix loading before debugging the rule. Read the corresponding CASES/CASE_ID/ground_truth.md to interpret the result.

Need help?

See the artifacts & datasets page for published reproducibility bundles and downloadable case datasets, the scenarios for ground-truth specifications, and the IoI rules library for all SPARQL signatures. Open an issue on GitHub ↗ if you encounter problems.