Process Anomaly Detection in Java: Comprehensive Behavioral Monitoring and Threat Detection

Process anomaly detection involves monitoring system processes, their behavior, and resource usage to identify suspicious activities, malware, and security threats in real-time.


Setup and Dependencies

1. Maven Dependencies
<!-- pom.xml -->
<properties>
<oshi.version>6.4.6</oshi.version>
<opencsv.version>5.8</opencsv.version>
<smile.version>3.0.1</smile.version>
<spring-boot.version>3.1.0</spring-boot.version>
</properties>
<dependencies>
<!-- System Monitoring -->
<dependency>
<groupId>com.github.oshi</groupId>
<artifactId>oshi-core</artifactId>
<version>${oshi.version}</version>
</dependency>
<!-- Machine Learning -->
<dependency>
<groupId>com.github.haifengl</groupId>
<artifactId>smile-core</artifactId>
<version>${smile.version}</version>
</dependency>
<!-- CSV Processing -->
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>${opencsv.version}</version>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>${spring-boot.version}</version>
</dependency>
<!-- JSON Processing -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.15.2</version>
</dependency>
</dependencies>
2. Configuration Properties
# application.yml
process:
monitoring:
enabled: true
scan-interval: 5000  # 5 seconds
history-size: 1000
anomaly-threshold: 0.85
max-cpu-usage: 80.0
max-memory-usage: 90.0
max-file-handles: 10000
detection:
rules:
- name: "high_cpu_usage"
threshold: 90.0
duration: 30000  # 30 seconds
- name: "suspicious_process"
patterns: ["cryptominer", "malware", "backdoor"]
- name: "unusual_process_tree"
max-depth: 5
alert-on-orphan: true
logging:
level:
com.example.security.process: DEBUG

Core Process Monitoring

1. Process Monitor Service
package com.example.security.process;
import oshi.SystemInfo;
import oshi.hardware.HardwareAbstractionLayer;
import oshi.software.os.OSProcess;
import oshi.software.os.OperatingSystem;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.stream.Collectors;
@Service
public class ProcessMonitorService {
private static final Logger logger = LoggerFactory.getLogger(ProcessMonitorService.class);
private final SystemInfo systemInfo;
private final HardwareAbstractionLayer hardware;
private final OperatingSystem operatingSystem;
private final Map<Integer, ProcessStats> processHistory;
private final List<ProcessAnomaly> detectedAnomalies;
private final AnomalyDetectionEngine detectionEngine;
public ProcessMonitorService(AnomalyDetectionEngine detectionEngine) {
this.systemInfo = new SystemInfo();
this.hardware = systemInfo.getHardware();
this.operatingSystem = systemInfo.getOperatingSystem();
this.processHistory = new ConcurrentHashMap<>();
this.detectedAnomalies = new CopyOnWriteArrayList<>();
this.detectionEngine = detectionEngine;
}
@Scheduled(fixedRateString = "${process.monitoring.scan-interval:5000}")
public void monitorProcesses() {
try {
List<OSProcess> processes = Arrays.stream(operatingSystem.getProcesses(
OperatingSystem.ProcessFiltering.ALL_PROCESSES,
OperatingSystem.ProcessSorting.CPU_DESC,
100
)).collect(Collectors.toList());
// Update process statistics
updateProcessStats(processes);
// Detect anomalies
detectAnomalies(processes);
// Clean up old entries
cleanupOldEntries();
} catch (Exception e) {
logger.error("Process monitoring failed", e);
}
}
public ProcessSnapshot getCurrentSnapshot() {
List<OSProcess> processes = Arrays.stream(operatingSystem.getProcesses(
OperatingSystem.ProcessFiltering.ALL_PROCESSES,
OperatingSystem.ProcessSorting.CPU_DESC,
50
)).collect(Collectors.toList());
return new ProcessSnapshot(processes, System.currentTimeMillis());
}
public List<ProcessStats> getProcessHistory(int pid) {
return processHistory.entrySet().stream()
.filter(entry -> entry.getKey() == pid)
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}
public List<ProcessAnomaly> getDetectedAnomalies() {
return new ArrayList<>(detectedAnomalies);
}
public SystemHealth getSystemHealth() {
SystemHealth health = new SystemHealth();
health.setTimestamp(System.currentTimeMillis());
health.setCpuUsage(hardware.getProcessor().getSystemCpuLoad(1000));
health.setMemoryUsage(getMemoryUsage());
health.setProcessCount(operatingSystem.getProcessCount());
health.setThreadCount(operatingSystem.getThreadCount());
return health;
}
private void updateProcessStats(List<OSProcess> processes) {
long currentTime = System.currentTimeMillis();
for (OSProcess process : processes) {
int pid = process.getProcessID();
ProcessStats stats = processHistory.computeIfAbsent(pid, k -> new ProcessStats());
stats.setPid(pid);
stats.setName(process.getName());
stats.setCommandLine(process.getCommandLine());
stats.setCpuUsage(process.getProcessCpuLoadCumulative());
stats.setMemoryUsage(process.getResidentSetSize());
stats.setThreadCount(process.getThreadCount());
stats.setOpenFiles(process.getOpenFiles());
stats.setUser(process.getUser());
stats.setStartTime(process.getStartTime());
stats.setTimestamp(currentTime);
// Add to history
stats.addToHistory(stats.copy());
}
}
private void detectAnomalies(List<OSProcess> processes) {
for (OSProcess process : processes) {
int pid = process.getProcessID();
ProcessStats stats = processHistory.get(pid);
if (stats != null) {
// Check for various types of anomalies
checkResourceAnomalies(stats, process);
checkBehavioralAnomalies(stats);
checkSuspiciousPatterns(stats);
}
}
}
private void checkResourceAnomalies(ProcessStats stats, OSProcess process) {
// CPU usage anomaly
if (process.getProcessCpuLoadCumulative() > 90.0) {
ProcessAnomaly anomaly = new ProcessAnomaly();
anomaly.setPid(stats.getPid());
anomaly.setProcessName(stats.getName());
anomaly.setAnomalyType(AnomalyType.HIGH_CPU_USAGE);
anomaly.setSeverity(Severity.HIGH);
anomaly.setDescription(String.format("Process %s (PID: %d) using %.2f%% CPU", 
stats.getName(), stats.getPid(), process.getProcessCpuLoadCumulative()));
anomaly.setTimestamp(System.currentTimeMillis());
anomaly.setMetrics(Map.of("cpu_usage", process.getProcessCpuLoadCumulative()));
detectedAnomalies.add(anomaly);
logger.warn("High CPU usage detected: {}", anomaly.getDescription());
}
// Memory usage anomaly
long maxMemory = Runtime.getRuntime().maxMemory();
if (process.getResidentSetSize() > maxMemory * 0.8) {
ProcessAnomaly anomaly = new ProcessAnomaly();
anomaly.setPid(stats.getPid());
anomaly.setProcessName(stats.getName());
anomaly.setAnomalyType(AnomalyType.HIGH_MEMORY_USAGE);
anomaly.setSeverity(Severity.HIGH);
anomaly.setDescription(String.format("Process %s (PID: %d) using %d bytes memory", 
stats.getName(), stats.getPid(), process.getResidentSetSize()));
anomaly.setTimestamp(System.currentTimeMillis());
anomaly.setMetrics(Map.of("memory_usage", process.getResidentSetSize()));
detectedAnomalies.add(anomaly);
logger.warn("High memory usage detected: {}", anomaly.getDescription());
}
// File handle anomaly
if (process.getOpenFiles() > 10000) {
ProcessAnomaly anomaly = new ProcessAnomaly();
anomaly.setPid(stats.getPid());
anomaly.setProcessName(stats.getName());
anomaly.setAnomalyType(AnomalyType.HIGH_FILE_HANDLES);
anomaly.setSeverity(Severity.MEDIUM);
anomaly.setDescription(String.format("Process %s (PID: %d) has %d open files", 
stats.getName(), stats.getPid(), process.getOpenFiles()));
anomaly.setTimestamp(System.currentTimeMillis());
anomaly.setMetrics(Map.of("open_files", process.getOpenFiles()));
detectedAnomalies.add(anomaly);
logger.warn("High file handle count detected: {}", anomaly.getDescription());
}
}
private void checkBehavioralAnomalies(ProcessStats stats) {
// Use machine learning to detect behavioral anomalies
double anomalyScore = detectionEngine.calculateAnomalyScore(stats);
if (anomalyScore > 0.85) {
ProcessAnomaly anomaly = new ProcessAnomaly();
anomaly.setPid(stats.getPid());
anomaly.setProcessName(stats.getName());
anomaly.setAnomalyType(AnomalyType.BEHAVIORAL_ANOMALY);
anomaly.setSeverity(Severity.HIGH);
anomaly.setDescription(String.format("Behavioral anomaly detected in process %s (PID: %d). Score: %.2f", 
stats.getName(), stats.getPid(), anomalyScore));
anomaly.setTimestamp(System.currentTimeMillis());
anomaly.setMetrics(Map.of("anomaly_score", anomalyScore));
detectedAnomalies.add(anomaly);
logger.warn("Behavioral anomaly detected: {}", anomaly.getDescription());
}
}
private void checkSuspiciousPatterns(ProcessStats stats) {
// Check for suspicious process names or command lines
List<String> suspiciousPatterns = Arrays.asList(
"minerd", "cpuminer", "xmrig", "ccminer", "backdoor", 
"reverse_shell", "keylogger", "ransomware"
);
for (String pattern : suspiciousPatterns) {
if (stats.getName().toLowerCase().contains(pattern) || 
(stats.getCommandLine() != null && stats.getCommandLine().toLowerCase().contains(pattern))) {
ProcessAnomaly anomaly = new ProcessAnomaly();
anomaly.setPid(stats.getPid());
anomaly.setProcessName(stats.getName());
anomaly.setAnomalyType(AnomalyType.SUSPICIOUS_PROCESS);
anomaly.setSeverity(Severity.CRITICAL);
anomaly.setDescription(String.format("Suspicious process detected: %s (PID: %d) matches pattern: %s", 
stats.getName(), stats.getPid(), pattern));
anomaly.setTimestamp(System.currentTimeMillis());
anomaly.setMetrics(Map.of("suspicious_pattern", pattern));
detectedAnomalies.add(anomaly);
logger.error("Suspicious process detected: {}", anomaly.getDescription());
break;
}
}
}
private void cleanupOldEntries() {
long cutoffTime = System.currentTimeMillis() - (5 * 60 * 1000); // 5 minutes
processHistory.entrySet().removeIf(entry -> 
entry.getValue().getTimestamp() < cutoffTime
);
// Keep only recent anomalies
detectedAnomalies.removeIf(anomaly -> 
anomaly.getTimestamp() < (System.currentTimeMillis() - (30 * 60 * 1000)) // 30 minutes
);
}
private double getMemoryUsage() {
long totalMemory = hardware.getMemory().getTotal();
long availableMemory = hardware.getMemory().getAvailable();
return ((double) (totalMemory - availableMemory) / totalMemory) * 100;
}
// DTO classes
public static class ProcessStats {
private int pid;
private String name;
private String commandLine;
private double cpuUsage;
private long memoryUsage;
private int threadCount;
private int openFiles;
private String user;
private long startTime;
private long timestamp;
private List<ProcessStats> history = new ArrayList<>();
public ProcessStats copy() {
ProcessStats copy = new ProcessStats();
copy.pid = this.pid;
copy.name = this.name;
copy.commandLine = this.commandLine;
copy.cpuUsage = this.cpuUsage;
copy.memoryUsage = this.memoryUsage;
copy.threadCount = this.threadCount;
copy.openFiles = this.openFiles;
copy.user = this.user;
copy.startTime = this.startTime;
copy.timestamp = this.timestamp;
return copy;
}
public void addToHistory(ProcessStats stats) {
if (history.size() > 100) { // Keep last 100 entries
history.remove(0);
}
history.add(stats);
}
// Getters and setters
public int getPid() { return pid; }
public void setPid(int pid) { this.pid = pid; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getCommandLine() { return commandLine; }
public void setCommandLine(String commandLine) { this.commandLine = commandLine; }
public double getCpuUsage() { return cpuUsage; }
public void setCpuUsage(double cpuUsage) { this.cpuUsage = cpuUsage; }
public long getMemoryUsage() { return memoryUsage; }
public void setMemoryUsage(long memoryUsage) { this.memoryUsage = memoryUsage; }
public int getThreadCount() { return threadCount; }
public void setThreadCount(int threadCount) { this.threadCount = threadCount; }
public int getOpenFiles() { return openFiles; }
public void setOpenFiles(int openFiles) { this.openFiles = openFiles; }
public String getUser() { return user; }
public void setUser(String user) { this.user = user; }
public long getStartTime() { return startTime; }
public void setStartTime(long startTime) { this.startTime = startTime; }
public long getTimestamp() { return timestamp; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
public List<ProcessStats> getHistory() { return history; }
public void setHistory(List<ProcessStats> history) { this.history = history; }
}
public static class ProcessSnapshot {
private List<OSProcess> processes;
private long timestamp;
public ProcessSnapshot(List<OSProcess> processes, long timestamp) {
this.processes = processes;
this.timestamp = timestamp;
}
// Getters
public List<OSProcess> getProcesses() { return processes; }
public long getTimestamp() { return timestamp; }
}
public static class SystemHealth {
private long timestamp;
private double cpuUsage;
private double memoryUsage;
private int processCount;
private int threadCount;
// Getters and setters
public long getTimestamp() { return timestamp; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
public double getCpuUsage() { return cpuUsage; }
public void setCpuUsage(double cpuUsage) { this.cpuUsage = cpuUsage; }
public double getMemoryUsage() { return memoryUsage; }
public void setMemoryUsage(double memoryUsage) { this.memoryUsage = memoryUsage; }
public int getProcessCount() { return processCount; }
public void setProcessCount(int processCount) { this.processCount = processCount; }
public int getThreadCount() { return threadCount; }
public void setThreadCount(int threadCount) { this.threadCount = threadCount; }
}
}
2. Anomaly Detection Engine
package com.example.security.process;
import smile.anomaly.*;
import smile.data.DataFrame;
import smile.data.type.StructType;
import smile.data.vector.DoubleVector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Service
public class AnomalyDetectionEngine {
private static final Logger logger = LoggerFactory.getLogger(AnomalyDetectionEngine.class);
private final Map<Integer, ProcessBehaviorProfile> processProfiles;
private LOF<double[]> lofDetector;
private boolean isTrained = false;
public AnomalyDetectionEngine() {
this.processProfiles = new ConcurrentHashMap<>();
initializeDetector();
}
public double calculateAnomalyScore(ProcessMonitorService.ProcessStats stats) {
try {
if (!isTrained) {
return 0.0; // Not enough data yet
}
double[] features = extractFeatures(stats);
return lofDetector.score(features);
} catch (Exception e) {
logger.error("Anomaly score calculation failed for PID: {}", stats.getPid(), e);
return 0.0;
}
}
public void updateModel(ProcessMonitorService.ProcessStats stats) {
try {
// Update process profile
ProcessBehaviorProfile profile = processProfiles.computeIfAbsent(
stats.getPid(), k -> new ProcessBehaviorProfile(stats.getPid(), stats.getName()));
profile.update(stats);
// Train model if we have enough data
if (processProfiles.size() >= 10) {
trainModel();
}
} catch (Exception e) {
logger.error("Model update failed for PID: {}", stats.getPid(), e);
}
}
public BehavioralAnomaly detectBehavioralChange(ProcessMonitorService.ProcessStats currentStats) {
ProcessBehaviorProfile profile = processProfiles.get(currentStats.getPid());
if (profile == null) {
return null;
}
BehavioralAnomaly anomaly = new BehavioralAnomaly();
anomaly.setPid(currentStats.getPid());
anomaly.setProcessName(currentStats.getName());
anomaly.setTimestamp(System.currentTimeMillis());
// Check CPU usage pattern
double cpuDeviation = profile.checkCpuDeviation(currentStats.getCpuUsage());
if (cpuDeviation > 2.0) { // 2 standard deviations
anomaly.getDeviations().put("cpu_usage", cpuDeviation);
}
// Check memory usage pattern
double memoryDeviation = profile.checkMemoryDeviation(currentStats.getMemoryUsage());
if (memoryDeviation > 2.0) {
anomaly.getDeviations().put("memory_usage", memoryDeviation);
}
// Check thread count pattern
double threadDeviation = profile.checkThreadDeviation(currentStats.getThreadCount());
if (threadDeviation > 2.0) {
anomaly.getDeviations().put("thread_count", threadDeviation);
}
if (!anomaly.getDeviations().isEmpty()) {
anomaly.setOverallScore(calculateOverallAnomalyScore(anomaly.getDeviations()));
return anomaly;
}
return null;
}
private void initializeDetector() {
try {
// Initialize with default parameters
this.lofDetector = LOF.fit(new double[0][0], 20); // k=20 neighbors
} catch (Exception e) {
logger.error("LOF detector initialization failed", e);
}
}
private void trainModel() {
try {
List<double[]> trainingData = new ArrayList<>();
for (ProcessBehaviorProfile profile : processProfiles.values()) {
if (profile.getStatsHistory().size() >= 5) {
double[] features = profile.getAverageFeatures();
trainingData.add(features);
}
}
if (trainingData.size() >= 10) {
double[][] data = trainingData.toArray(new double[0][0]);
this.lofDetector = LOF.fit(data, 20);
this.isTrained = true;
logger.info("Anomaly detection model trained with {} samples", trainingData.size());
}
} catch (Exception e) {
logger.error("Model training failed", e);
}
}
private double[] extractFeatures(ProcessMonitorService.ProcessStats stats) {
return new double[] {
stats.getCpuUsage(),
Math.log(stats.getMemoryUsage() + 1), // Log transform for memory
stats.getThreadCount(),
stats.getOpenFiles(),
normalizeTimestamp(stats.getTimestamp())
};
}
private double normalizeTimestamp(long timestamp) {
// Normalize timestamp to 0-1 range based on current time
long currentTime = System.currentTimeMillis();
long dayInMillis = 24 * 60 * 60 * 1000L;
return (double) (timestamp % dayInMillis) / dayInMillis;
}
private double calculateOverallAnomalyScore(Map<String, Double> deviations) {
return deviations.values().stream()
.mapToDouble(Double::doubleValue)
.average()
.orElse(0.0);
}
public static class ProcessBehaviorProfile {
private final int pid;
private final String processName;
private final List<ProcessMonitorService.ProcessStats> statsHistory;
private final double[] cpuStats = new double[2]; // mean, stdDev
private final double[] memoryStats = new double[2];
private final double[] threadStats = new double[2];
public ProcessBehaviorProfile(int pid, String processName) {
this.pid = pid;
this.processName = processName;
this.statsHistory = new ArrayList<>();
}
public void update(ProcessMonitorService.ProcessStats stats) {
statsHistory.add(stats);
// Keep only recent history
if (statsHistory.size() > 100) {
statsHistory.remove(0);
}
// Update statistics
updateStatistics();
}
public double checkCpuDeviation(double currentCpu) {
if (statsHistory.size() < 5) return 0.0;
return Math.abs(currentCpu - cpuStats[0]) / (cpuStats[1] + 0.001); // Avoid division by zero
}
public double checkMemoryDeviation(double currentMemory) {
if (statsHistory.size() < 5) return 0.0;
return Math.abs(Math.log(currentMemory + 1) - memoryStats[0]) / (memoryStats[1] + 0.001);
}
public double checkThreadDeviation(int currentThreads) {
if (statsHistory.size() < 5) return 0.0;
return Math.abs(currentThreads - threadStats[0]) / (threadStats[1] + 0.001);
}
public double[] getAverageFeatures() {
if (statsHistory.isEmpty()) {
return new double[5];
}
double avgCpu = statsHistory.stream()
.mapToDouble(ProcessMonitorService.ProcessStats::getCpuUsage)
.average().orElse(0.0);
double avgMemory = statsHistory.stream()
.mapToDouble(stats -> Math.log(stats.getMemoryUsage() + 1))
.average().orElse(0.0);
double avgThreads = statsHistory.stream()
.mapToInt(ProcessMonitorService.ProcessStats::getThreadCount)
.average().orElse(0.0);
double avgFiles = statsHistory.stream()
.mapToInt(ProcessMonitorService.ProcessStats::getOpenFiles)
.average().orElse(0.0);
return new double[] {avgCpu, avgMemory, avgThreads, avgFiles, 0.5}; // Default timestamp
}
private void updateStatistics() {
if (statsHistory.size() >= 5) {
// CPU statistics
double[] cpuValues = statsHistory.stream()
.mapToDouble(ProcessMonitorService.ProcessStats::getCpuUsage)
.toArray();
cpuStats[0] = calculateMean(cpuValues);
cpuStats[1] = calculateStdDev(cpuValues, cpuStats[0]);
// Memory statistics (log transformed)
double[] memoryValues = statsHistory.stream()
.mapToDouble(stats -> Math.log(stats.getMemoryUsage() + 1))
.toArray();
memoryStats[0] = calculateMean(memoryValues);
memoryStats[1] = calculateStdDev(memoryValues, memoryStats[0]);
// Thread statistics
double[] threadValues = statsHistory.stream()
.mapToInt(ProcessMonitorService.ProcessStats::getThreadCount)
.asDoubleStream().toArray();
threadStats[0] = calculateMean(threadValues);
threadStats[1] = calculateStdDev(threadValues, threadStats[0]);
}
}
private double calculateMean(double[] values) {
return Arrays.stream(values).average().orElse(0.0);
}
private double calculateStdDev(double[] values, double mean) {
double variance = Arrays.stream(values)
.map(v -> Math.pow(v - mean, 2))
.average().orElse(0.0);
return Math.sqrt(variance);
}
// Getters
public int getPid() { return pid; }
public String getProcessName() { return processName; }
public List<ProcessMonitorService.ProcessStats> getStatsHistory() { return statsHistory; }
}
public static class BehavioralAnomaly {
private int pid;
private String processName;
private long timestamp;
private double overallScore;
private Map<String, Double> deviations = new HashMap<>();
// Getters and setters
public int getPid() { return pid; }
public void setPid(int pid) { this.pid = pid; }
public String getProcessName() { return processName; }
public void setProcessName(String processName) { this.processName = processName; }
public long getTimestamp() { return timestamp; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
public double getOverallScore() { return overallScore; }
public void setOverallScore(double overallScore) { this.overallScore = overallScore; }
public Map<String, Double> getDeviations() { return deviations; }
public void setDeviations(Map<String, Double> deviations) { this.deviations = deviations; }
}
}
3. Process Anomaly Types and Models
package com.example.security.process;
import java.util.Map;
public class ProcessAnomaly {
public enum AnomalyType {
HIGH_CPU_USAGE,
HIGH_MEMORY_USAGE,
HIGH_FILE_HANDLES,
SUSPICIOUS_PROCESS,
BEHAVIORAL_ANOMALY,
UNUSUAL_PROCESS_TREE,
PROCESS_INJECTION,
PRIVILEGE_ESCALATION
}
public enum Severity {
LOW,
MEDIUM,
HIGH,
CRITICAL
}
private int pid;
private String processName;
private AnomalyType anomalyType;
private Severity severity;
private String description;
private long timestamp;
private Map<String, Object> metrics;
private String recommendation;
// Getters and setters
public int getPid() { return pid; }
public void setPid(int pid) { this.pid = pid; }
public String getProcessName() { return processName; }
public void setProcessName(String processName) { this.processName = processName; }
public AnomalyType getAnomalyType() { return anomalyType; }
public void setAnomalyType(AnomalyType anomalyType) { this.anomalyType = anomalyType; }
public Severity getSeverity() { return severity; }
public void setSeverity(Severity severity) { this.severity = severity; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public long getTimestamp() { return timestamp; }
public void setTimestamp(long timestamp) { this.timestamp = timestamp; }
public Map<String, Object> getMetrics() { return metrics; }
public void setMetrics(Map<String, Object> metrics) { this.metrics = metrics; }
public String getRecommendation() { return recommendation; }
public void setRecommendation(String recommendation) { this.recommendation = recommendation; }
}
4. Alerting and Notification Service
package com.example.security.process;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.HashMap;
import java.util.Map;
@Service
public class ProcessAlertService {
private static final Logger logger = LoggerFactory.getLogger(ProcessAlertService.class);
private final JavaMailSender mailSender;
private final RestTemplate restTemplate;
@Value("${security.alerts.email.recipient:[email protected]}")
private String alertEmailRecipient;
@Value("${security.alerts.slack.webhook:}")
private String slackWebhookUrl;
public ProcessAlertService(JavaMailSender mailSender, RestTemplate restTemplate) {
this.mailSender = mailSender;
this.restTemplate = restTemplate;
}
public void sendAlert(ProcessAnomaly anomaly) {
switch (anomaly.getSeverity()) {
case CRITICAL:
sendCriticalAlert(anomaly);
break;
case HIGH:
sendHighAlert(anomaly);
break;
case MEDIUM:
sendMediumAlert(anomaly);
break;
case LOW:
sendLowAlert(anomaly);
break;
}
logAnomaly(anomaly);
}
private void sendCriticalAlert(ProcessAnomaly anomaly) {
String subject = "🚨 CRITICAL Process Anomaly Detected";
String message = buildAlertMessage(anomaly);
sendEmailAlert(subject, message);
sendSlackAlert(subject, message);
sendPagerDutyAlert(anomaly);
logger.error("CRITICAL process anomaly: {}", anomaly.getDescription());
}
private void sendHighAlert(ProcessAnomaly anomaly) {
String subject = "⚠️ HIGH Process Anomaly Detected";
String message = buildAlertMessage(anomaly);
sendEmailAlert(subject, message);
sendSlackAlert(subject, message);
logger.warn("HIGH process anomaly: {}", anomaly.getDescription());
}
private void sendMediumAlert(ProcessAnomaly anomaly) {
String subject = "🔔 Medium Process Anomaly Detected";
String message = buildAlertMessage(anomaly);
sendSlackAlert(subject, message);
logger.info("MEDIUM process anomaly: {}", anomaly.getDescription());
}
private void sendLowAlert(ProcessAnomaly anomaly) {
// Just log low severity anomalies
logger.info("LOW process anomaly: {}", anomaly.getDescription());
}
private void sendEmailAlert(String subject, String message) {
try {
SimpleMailMessage mailMessage = new SimpleMailMessage();
mailMessage.setTo(alertEmailRecipient);
mailMessage.setSubject("[Process Security] " + subject);
mailMessage.setText(message);
mailSender.send(mailMessage);
logger.info("Process alert email sent: {}", subject);
} catch (Exception e) {
logger.error("Failed to send process alert email", e);
}
}
private void sendSlackAlert(String subject, String message) {
if (slackWebhookUrl == null || slackWebhookUrl.isEmpty()) {
return;
}
try {
Map<String, Object> slackMessage = new HashMap<>();
slackMessage.put("text", "🔍 *Process Anomaly Alert*\n*" + subject + "*\n" + message);
restTemplate.postForEntity(slackWebhookUrl, slackMessage, String.class);
logger.info("Process alert sent to Slack: {}", subject);
} catch (Exception e) {
logger.error("Failed to send process alert to Slack", e);
}
}
private void sendPagerDutyAlert(ProcessAnomaly anomaly) {
// Implementation for PagerDuty integration
// This would require additional PagerDuty dependencies and configuration
logger.info("PagerDuty alert would be sent for critical anomaly: {}", anomaly.getDescription());
}
private String buildAlertMessage(ProcessAnomaly anomaly) {
StringBuilder message = new StringBuilder();
message.append("Process Anomaly Details:\n");
message.append("------------------------\n");
message.append("Process: ").append(anomaly.getProcessName()).append(" (PID: ").append(anomaly.getPid()).append(")\n");
message.append("Type: ").append(anomaly.getAnomalyType()).append("\n");
message.append("Severity: ").append(anomaly.getSeverity()).append("\n");
message.append("Description: ").append(anomaly.getDescription()).append("\n");
message.append("Timestamp: ").append(new java.util.Date(anomaly.getTimestamp())).append("\n");
if (anomaly.getMetrics() != null && !anomaly.getMetrics().isEmpty()) {
message.append("Metrics:\n");
anomaly.getMetrics().forEach((key, value) -> 
message.append("  - ").append(key).append(": ").append(value).append("\n"));
}
if (anomaly.getRecommendation() != null) {
message.append("Recommendation: ").append(anomaly.getRecommendation()).append("\n");
}
return message.toString();
}
private void logAnomaly(ProcessAnomaly anomaly) {
// Structured logging for SIEM integration
Map<String, Object> logData = new HashMap<>();
logData.put("event_type", "process_anomaly");
logData.put("pid", anomaly.getPid());
logData.put("process_name", anomaly.getProcessName());
logData.put("anomaly_type", anomaly.getAnomalyType());
logData.put("severity", anomaly.getSeverity());
logData.put("description", anomaly.getDescription());
logData.put("timestamp", anomaly.getTimestamp());
logData.put("metrics", anomaly.getMetrics());
logger.info("Process anomaly detected: {}", logData);
}
}
5. Security Dashboard Controller
package com.example.security.process;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/api/security/process")
@PreAuthorize("hasRole('SECURITY_TEAM')")
public class ProcessSecurityController {
private final ProcessMonitorService processMonitor;
private final ProcessAlertService alertService;
private final AnomalyDetectionEngine detectionEngine;
public ProcessSecurityController(ProcessMonitorService processMonitor,
ProcessAlertService alertService,
AnomalyDetectionEngine detectionEngine) {
this.processMonitor = processMonitor;
this.alertService = alertService;
this.detectionEngine = detectionEngine;
}
@GetMapping("/snapshot")
public ProcessMonitorService.ProcessSnapshot getCurrentSnapshot() {
return processMonitor.getCurrentSnapshot();
}
@GetMapping("/health")
public ProcessMonitorService.SystemHealth getSystemHealth() {
return processMonitor.getSystemHealth();
}
@GetMapping("/anomalies")
public List<ProcessAnomaly> getDetectedAnomalies() {
return processMonitor.getDetectedAnomalies();
}
@GetMapping("/history/{pid}")
public List<ProcessMonitorService.ProcessStats> getProcessHistory(@PathVariable int pid) {
return processMonitor.getProcessHistory(pid);
}
@PostMapping("/scan/trigger")
public String triggerManualScan() {
processMonitor.monitorProcesses();
return "Manual process scan triggered";
}
@GetMapping("/suspicious")
public List<ProcessAnomaly> getSuspiciousProcesses() {
return processMonitor.getDetectedAnomalies().stream()
.filter(anomaly -> anomaly.getSeverity() == ProcessAnomaly.Severity.CRITICAL ||
anomaly.getSeverity() == ProcessAnomaly.Severity.HIGH)
.collect(java.util.stream.Collectors.toList());
}
@PostMapping("/analyze/{pid}")
public AnomalyDetectionEngine.BehavioralAnomaly analyzeProcessBehavior(@PathVariable int pid) {
List<ProcessMonitorService.ProcessStats> history = processMonitor.getProcessHistory(pid);
if (history.isEmpty()) {
return null;
}
ProcessMonitorService.ProcessStats latest = history.get(history.size() - 1);
return detectionEngine.detectBehavioralChange(latest);
}
}

Advanced Detection Rules

1. Process Tree Analysis
package com.example.security.process;
import oshi.software.os.OSProcess;
import oshi.software.os.OperatingSystem;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class ProcessTreeAnalyzer {
public ProcessTree analyzeProcessTree(int rootPid, OperatingSystem os) {
ProcessTree tree = new ProcessTree();
tree.setRootPid(rootPid);
Map<Integer, ProcessTreeNode> nodes = new HashMap<>();
buildTree(rootPid, nodes, os, 0);
tree.setNodes(new ArrayList<>(nodes.values()));
analyzeTreeStructure(tree);
return tree;
}
public List<ProcessTreeAnomaly> detectTreeAnomalies(ProcessTree tree) {
List<ProcessTreeAnomaly> anomalies = new ArrayList<>();
// Check for orphan processes
detectOrphanProcesses(tree, anomalies);
// Check for unusual parent-child relationships
detectUnusualRelationships(tree, anomalies);
// Check for process injection patterns
detectProcessInjection(tree, anomalies);
return anomalies;
}
private void buildTree(int pid, Map<Integer, ProcessTreeNode> nodes, 
OperatingSystem os, int depth) {
if (depth > 10) return; // Prevent infinite recursion
OSProcess process = os.getProcess(pid);
if (process == null) return;
ProcessTreeNode node = new ProcessTreeNode();
node.setPid(pid);
node.setName(process.getName());
node.setCommandLine(process.getCommandLine());
node.setDepth(depth);
node.setParentPid(process.getParentProcessID());
nodes.put(pid, node);
// Recursively build tree for children
int[] childPids = os.getChildProcesses(pid, OperatingSystem.ProcessFiltering.ALL_PROCESSES, 0);
for (int childPid : childPids) {
if (!nodes.containsKey(childPid)) {
buildTree(childPid, nodes, os, depth + 1);
}
}
}
private void detectOrphanProcesses(ProcessTree tree, List<ProcessTreeAnomaly> anomalies) {
for (ProcessTreeNode node : tree.getNodes()) {
if (node.getParentPid() != 0 && !tree.getNodes().stream()
.anyMatch(n -> n.getPid() == node.getParentPid())) {
ProcessTreeAnomaly anomaly = new ProcessTreeAnomaly();
anomaly.setType(ProcessTreeAnomaly.Type.ORPHAN_PROCESS);
anomaly.setPid(node.getPid());
anomaly.setProcessName(node.getName());
anomaly.setDescription("Orphan process detected: " + node.getName() + " (PID: " + node.getPid() + ")");
anomaly.setSeverity(ProcessAnomaly.Severity.HIGH);
anomalies.add(anomaly);
}
}
}
private void detectUnusualRelationships(ProcessTree tree, List<ProcessTreeAnomaly> anomalies) {
// Implementation for detecting unusual parent-child relationships
// e.g., browser spawning command shell, system process spawning user process
}
private void detectProcessInjection(ProcessTree tree, List<ProcessTreeAnomaly> anomalies) {
// Implementation for detecting process injection patterns
}
private void analyzeTreeStructure(ProcessTree tree) {
tree.setMaxDepth(tree.getNodes().stream()
.mapToInt(ProcessTreeNode::getDepth)
.max().orElse(0));
tree.setTotalProcesses(tree.getNodes().size());
}
public static class ProcessTree {
private int rootPid;
private List<ProcessTreeNode> nodes;
private int maxDepth;
private int totalProcesses;
// Getters and setters
public int getRootPid() { return rootPid; }
public void setRootPid(int rootPid) { this.rootPid = rootPid; }
public List<ProcessTreeNode> getNodes() { return nodes; }
public void setNodes(List<ProcessTreeNode> nodes) { this.nodes = nodes; }
public int getMaxDepth() { return maxDepth; }
public void setMaxDepth(int maxDepth) { this.maxDepth = maxDepth; }
public int getTotalProcesses() { return totalProcesses; }
public void setTotalProcesses(int totalProcesses) { this.totalProcesses = totalProcesses; }
}
public static class ProcessTreeNode {
private int pid;
private String name;
private String commandLine;
private int depth;
private int parentPid;
// Getters and setters
public int getPid() { return pid; }
public void setPid(int pid) { this.pid = pid; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public String getCommandLine() { return commandLine; }
public void setCommandLine(String commandLine) { this.commandLine = commandLine; }
public int getDepth() { return depth; }
public void setDepth(int depth) { this.depth = depth; }
public int getParentPid() { return parentPid; }
public void setParentPid(int parentPid) { this.parentPid = parentPid; }
}
public static class ProcessTreeAnomaly {
public enum Type {
ORPHAN_PROCESS,
UNUSUAL_PARENT_CHILD,
PROCESS_INJECTION,
PRIVILEGE_ESCALATION
}
private Type type;
private int pid;
private String processName;
private String description;
private ProcessAnomaly.Severity severity;
// Getters and setters
public Type getType() { return type; }
public void setType(Type type) { this.type = type; }
public int getPid() { return pid; }
public void setPid(int pid) { this.pid = pid; }
public String getProcessName() { return processName; }
public void setProcessName(String processName) { this.processName = processName; }
public String getDescription() { return description; }
public void setDescription(String description) { this.description = description; }
public ProcessAnomaly.Severity getSeverity() { return severity; }
public void setSeverity(ProcessAnomaly.Severity severity) { this.severity = severity; }
}
}

Best Practices

  1. Monitoring Strategy:
  • Continuous real-time monitoring
  • Behavioral baseline establishment
  • Multi-layered detection approach
  • Resource usage optimization
  1. Detection Techniques:
  • Statistical anomaly detection
  • Machine learning-based detection
  • Signature-based detection
  • Behavioral pattern analysis
  1. Alert Management:
  • Severity-based alerting
  • Alert correlation
  • False positive reduction
  • Automated response actions
  1. Performance Considerations:
  • Efficient resource usage
  • Scalable architecture
  • Minimal performance impact
  • Optimized data collection
# Example usage for testing
curl -X GET http://localhost:8080/api/security/process/snapshot
curl -X GET http://localhost:8080/api/security/process/anomalies
curl -X POST http://localhost:8080/api/security/process/scan/trigger

Conclusion

Process Anomaly Detection in Java provides:

  • Real-time process monitoring and behavioral analysis
  • Machine learning-based anomaly detection
  • Multi-layered detection strategies
  • Comprehensive alerting and notification system
  • Process tree analysis for advanced threat detection

By implementing the patterns and techniques shown above, you can establish robust process monitoring, detect suspicious activities in real-time, identify advanced threats through behavioral analysis, and maintain comprehensive visibility into your system's process ecosystem for enhanced security posture.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper