Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions EDR_telem_linux.json
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,25 @@
"Sysmon":"No",
"Uptycs":"Via EnablingTelemetry"
},
{
"Telemetry Feature Category":"Anonymous File Activity",
"Sub-Category":"Memory-Backed File Creation",
"Auditd":"Pending Response",
"BitDefender":"Pending Response",
"Cortex XDR":"Pending Response",
"Carbon Black Cloud":"Pending Response",
"C-Prot":"Pending Response",
"CrowdStrike":"Pending Response",
"ESET Inspect":"Pending Response",
"Elastic":"Yes",
"Harfanglab":"Pending Response",
"LimaCharlie":"Pending Response",
"MDE":"Pending Response",
"Qualys":"Pending Response",
"SentinelOne":"Pending Response",
"Sysmon":"Pending Response",
"Uptycs":"Pending Response"
},
{
"Telemetry Feature Category":"Service Activity",
"Sub-Category":"Service Creation",
Expand Down Expand Up @@ -568,5 +587,62 @@
"SentinelOne":"No",
"Sysmon":"Yes",
"Uptycs":"Yes"
},
{
"Telemetry Feature Category":"File Metadata",
"Sub-Category":"File Entropy",
"Auditd":"Pending Response",
"BitDefender":"Pending Response",
"Cortex XDR":"Pending Response",
"Carbon Black Cloud":"Pending Response",
"C-Prot":"Pending Response",
"CrowdStrike":"Pending Response",
"ESET Inspect":"Pending Response",
"Elastic":"Via EnablingTelemetry",
"Harfanglab":"Pending Response",
"LimaCharlie":"Pending Response",
"MDE":"Pending Response",
"Qualys":"Pending Response",
"SentinelOne":"Pending Response",
"Sysmon":"Pending Response",
"Uptycs":"Pending Response"
},
{
"Telemetry Feature Category":null,
"Sub-Category":"File Header Bytes",
"Auditd":"Pending Response",
"BitDefender":"Pending Response",
"Cortex XDR":"Pending Response",
"Carbon Black Cloud":"Pending Response",
"C-Prot":"Pending Response",
"CrowdStrike":"Pending Response",
"ESET Inspect":"Pending Response",
"Elastic":"Via EnablingTelemetry",
"Harfanglab":"Pending Response",
"LimaCharlie":"Pending Response",
"MDE":"Pending Response",
"Qualys":"Pending Response",
"SentinelOne":"Pending Response",
"Sysmon":"Pending Response",
"Uptycs":"Pending Response"
},
{
"Telemetry Feature Category":null,
"Sub-Category":"File Size",
"Auditd":"Pending Response",
"BitDefender":"Pending Response",
"Cortex XDR":"Pending Response",
"Carbon Black Cloud":"Pending Response",
"C-Prot":"Pending Response",
"CrowdStrike":"Pending Response",
"ESET Inspect":"Pending Response",
"Elastic":"Yes",
"Harfanglab":"Pending Response",
"LimaCharlie":"Pending Response",
"MDE":"Pending Response",
"Qualys":"Pending Response",
"SentinelOne":"Pending Response",
"Sysmon":"Pending Response",
"Uptycs":"Pending Response"
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ The script includes the following functionalities:
11. **User Account Events**: Create, modify, and delete user accounts using libuser
12. **eBPF Events**: Utilize pamspy for credential dumping using eBPF
13. **File Operations**: Create, modify, and delete files.
14. **Memory-Backed File Creation**: Exercise `memfd_create()` with a representative set of flag combinations and stage an in-memory ELF for execution from `/proc/self/fd/<fd>`.
15. **File Metadata**: Drop a small set of files with known magic-byte headers and varying entropy (random, repeating ASCII, ELF/ZIP/XZ/GZIP/PDF stubs) and then modify/rename them, so EDRs that collect `file.Ext.entropy` and `file.Ext.header_bytes` have deterministic input.


## Usage
Expand Down Expand Up @@ -62,6 +64,8 @@ This command will run the `FileCreated`, `DnsQuery`, and `NetworkConnect` events
- `NetworkListen`
- `NetworkRawSocket`
- `eBPFProgram`
- `MemfdCreate`
- `FileMetadata`

## Disclaimers

Expand Down
80 changes: 80 additions & 0 deletions Tools/Telemetry-Generator/Linux/complex/file_metadata.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import os
import secrets
import tempfile
from typing import Optional

# Sample magic-byte headers so EDRs can populate file.Ext.header_bytes /
# file.Ext.entropy with deterministic, recognisable inputs.
_FIXTURES = [
# (file_name, header_bytes, body_bytes, expected_extension)
("telemgen_high_entropy.bin", b"", None, "bin"), # random body, no header
("telemgen_low_entropy.txt", b"", b"A" * 8192, "txt"), # repeating ASCII
("telemgen_elf_stub.elf", b"\x7fELF\x02\x01\x01\x00", None, "elf"),
("telemgen_zip_stub.zip", b"PK\x03\x04", None, "zip"),
("telemgen_xz_stub.xz", b"\xfd7zXZ\x00", None, "xz"),
("telemgen_gzip_stub.gz", b"\x1f\x8b\x08", None, "gz"),
("telemgen_pdf_stub.pdf", b"%PDF-1.7\n", None, "pdf"),
]


def _write_fixture(out_dir: str, name: str, header: bytes, body: Optional[bytes]) -> str:
path = os.path.join(out_dir, name)
with open(path, "wb") as fh:
if header:
fh.write(header)
if body is None:
# Pad with random bytes so EDR-side entropy calculation has signal
fh.write(secrets.token_bytes(4096))
else:
fh.write(body)
return path


def _modify_fixture(path: str) -> None:
"""Append data to the file to trigger a file.modification event with a
fresh entropy/header sample on EDRs that recompute on every change."""
with open(path, "ab") as fh:
fh.write(b"\n# telemgen modification\n")
fh.write(secrets.token_bytes(512))


def _rename_fixture(path: str) -> str:
"""Rename to a path with a different extension so EDRs that key entropy
collection on extension still emit the field."""
base, ext = os.path.splitext(path)
new_path = f"{base}.renamed{ext}"
os.rename(path, new_path)
return new_path


def run_file_metadata():
"""Entry point invoked by lnx_telem_gen.py.

Generates a small set of files with known headers and varying entropy
so EDRs that collect file.Ext.header_bytes and file.Ext.entropy on file
creation/modification events have something to populate.
"""
out_dir = tempfile.mkdtemp(prefix="telemgen-file-metadata-")
print(f"[+] Writing file-metadata fixtures into {out_dir}")
written = []
for name, header, body, _ in _FIXTURES:
try:
path = _write_fixture(out_dir, name, header, body)
print(f"[+] created {path} (header={header!r}, size={os.path.getsize(path)})")
written.append(path)
except Exception as exc:
print(f"[!] failed to create {name}: {exc}")

for path in written:
try:
_modify_fixture(path)
new_path = _rename_fixture(path)
print(f"[+] modified+renamed {path} -> {new_path}")
except Exception as exc:
print(f"[!] post-creation activity failed for {path}: {exc}")

return f"file metadata fixtures written under {out_dir}"


if __name__ == "__main__":
run_file_metadata()
133 changes: 133 additions & 0 deletions Tools/Telemetry-Generator/Linux/complex/memfd_create_exec.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import ctypes
import ctypes.util
import os
import shutil
import time

# memfd_create flags from <sys/mman.h> / <linux/memfd.h>
MFD_CLOEXEC = 0x0001
MFD_ALLOW_SEALING = 0x0002
MFD_HUGETLB = 0x0004
MFD_NOEXEC_SEAL = 0x0008
MFD_EXEC = 0x0010


def _libc():
libc_path = ctypes.util.find_library("c") or "libc.so.6"
libc = ctypes.CDLL(libc_path, use_errno=True)
# int memfd_create(const char *name, unsigned int flags);
libc.memfd_create.argtypes = [ctypes.c_char_p, ctypes.c_uint]
libc.memfd_create.restype = ctypes.c_int
return libc


def _memfd_create(libc, name: bytes, flags: int) -> int:
fd = libc.memfd_create(name, flags)
if fd < 0:
errno = ctypes.get_errno()
raise OSError(errno, os.strerror(errno), name.decode("utf-8", "replace"))
return fd


def _flags_to_str(flags: int) -> str:
parts = []
for value, label in (
(MFD_CLOEXEC, "MFD_CLOEXEC"),
(MFD_ALLOW_SEALING, "MFD_ALLOW_SEALING"),
(MFD_HUGETLB, "MFD_HUGETLB"),
(MFD_NOEXEC_SEAL, "MFD_NOEXEC_SEAL"),
(MFD_EXEC, "MFD_EXEC"),
):
if flags & value:
parts.append(label)
return "|".join(parts) if parts else "0"


def _create_variants(libc):
"""Exercise memfd_create with several flag combinations to surface
process.Ext.memfd.flag.* fields in EDR telemetry."""
variants = [
(b"telemgen-payload-default", 0),
(b"telemgen-payload-cloexec", MFD_CLOEXEC),
(b"telemgen-payload-sealing", MFD_CLOEXEC | MFD_ALLOW_SEALING),
(b"telemgen-payload-noexec", MFD_CLOEXEC | MFD_NOEXEC_SEAL),
(b"telemgen-payload-exec", MFD_CLOEXEC | MFD_EXEC),
]
fds = []
for name, flags in variants:
try:
fd = _memfd_create(libc, name, flags)
except OSError as e:
print(f"[!] memfd_create({name!r}, {_flags_to_str(flags)}) failed: {e}")
continue
print(f"[+] memfd_create({name.decode()}, {_flags_to_str(flags)}) -> fd={fd}")
fds.append((fd, name.decode()))
return fds


def _copy_into_fd(src_path: str, dst_fd: int) -> None:
with open(src_path, "rb") as src:
while True:
chunk = src.read(64 * 1024)
if not chunk:
break
os.write(dst_fd, chunk)


def _exec_from_memfd(libc):
"""Stage /bin/true (or /bin/sh) into an exec-flagged memfd and exec it
from /proc/self/fd/<fd>. Confirms memfd-backed execution telemetry."""
src = shutil.which("true") or "/bin/true"
if not os.path.isfile(src):
print(f"[!] Skipping memfd-backed exec: {src} not present")
return

name = b"telemgen-loader"
flags = MFD_CLOEXEC | MFD_EXEC
try:
fd = _memfd_create(libc, name, flags)
except OSError as e:
# Older kernels (pre-6.3) reject MFD_EXEC. Fall back to plain CLOEXEC.
try:
fd = _memfd_create(libc, name, MFD_CLOEXEC)
except OSError as e2:
print(f"[!] memfd_create for exec failed: {e2}")
return

try:
_copy_into_fd(src, fd)
proc_path = f"/proc/self/fd/{fd}"
print(f"[+] Forking child to execve {proc_path} (memfd-backed exec of {src})")
pid = os.fork()
if pid == 0:
try:
os.execv(proc_path, ["telemgen-loader"])
except Exception as e:
print(f"[child] execv failed: {e}")
os._exit(127)
else:
_, status = os.waitpid(pid, 0)
print(f"[+] memfd-backed child exited with status {status}")
finally:
try:
os.close(fd)
except OSError:
pass


def run_memfd_create():
"""Entry point invoked by lnx_telem_gen.py."""
libc = _libc()
fds = _create_variants(libc)
time.sleep(0.5)
_exec_from_memfd(libc)
for fd, _ in fds:
try:
os.close(fd)
except OSError:
pass
return "memfd_create variants emitted"


if __name__ == "__main__":
run_memfd_create()
6 changes: 5 additions & 1 deletion Tools/Telemetry-Generator/Linux/lnx_telem_gen.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from complex.scheduled_task import run_task
from complex.process_hijack_demo import start_hijacking
from complex.eBPF_exec import run_pamspy
from complex.memfd_create_exec import run_memfd_create
from complex.file_metadata import run_file_metadata
from prettytable import PrettyTable

scheduler = sched.scheduler(time.time, time.sleep)
Expand Down Expand Up @@ -351,7 +353,9 @@ def raw_access_read():
'NetworkListen': NetworkSocketManager.network_listen,
'NetworkRawSocket': NetworkSocketManager.network_raw_socket,
'eBPFProgram': run_pamspy,
'ProcessAccess': start_hijacking
'ProcessAccess': start_hijacking,
'MemfdCreate': run_memfd_create,
'FileMetadata': run_file_metadata
}

def log_to_csv(function_name, output, error=None):
Expand Down
6 changes: 5 additions & 1 deletion Tools/compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,14 +94,18 @@
"Raw Access Read": 1,
"Process Access": 1,
"Process Tampering": 1,
"Memory-Backed File Creation": 1,
"Service Creation": 1,
"Service Modification": 0.7,
"Service Deletion": 0.6,
"Agent Start": 0.2,
"Agent Stop": 0.8,
"MD5": 1,
"SHA": 1,
"Fuzzy Hash": 1
"Fuzzy Hash": 1,
"File Entropy": 0.5,
"File Header Bytes": 0.5,
"File Size": 0.2
}

# macOS-specific categories
Expand Down
Loading