https://github.com/xorbkpt/iec-61850-ids-cyber-engine-demo
TuM Institute for Advanced Study, Advanced Computation, Cryptography: Post Doc Bucket: 8
https://github.com/xorbkpt/iec-61850-ids-cyber-engine-demo
cybersecurity goose-sv iec61850 intrusion-detection secure-coding
Last synced: 7 months ago
JSON representation
TuM Institute for Advanced Study, Advanced Computation, Cryptography: Post Doc Bucket: 8
- Host: GitHub
- URL: https://github.com/xorbkpt/iec-61850-ids-cyber-engine-demo
- Owner: XORBKPT
- License: gpl-3.0
- Created: 2025-03-13T22:51:30.000Z (7 months ago)
- Default Branch: main
- Last Pushed: 2025-03-15T21:12:28.000Z (7 months ago)
- Last Synced: 2025-03-16T11:23:45.079Z (7 months ago)
- Topics: cybersecurity, goose-sv, iec61850, intrusion-detection, secure-coding
- Language: C++
- Homepage:
- Size: 55.7 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
A cybersecurity standalone IDS (Intrusion Detection System) application for the Advantec MIC-7700, with comments for students to practice detecting intrusions on IEC 61850 Goose SV messaging (5ms latency) in lab setups. The Advantec MIC-7700 is assumed to be running a real time hardended Linux-based OS; dependencies are outlined below, this is a demo prototype only not intended for production, for educational use only.
---
### `IEC 61850 IDS_Cyber_Demo.cpp`
```cpp
// IEC 61850 IDS_Cyber_Demo.cpp
// Cybersecurity monitoring program for Advantec MIC-7700 to process Goose SV data from 50 fibre ports#include // Standard I/O operations
#include // Dynamic arrays for risk results
#include // String handling
#include // Multi-threading support
#include // Thread synchronization
#include // Thread signaling
#include // Message queuing
#include // Efficient lookup for heartbeat and rate data
#include // Whitelist of authorized MACs
#include // High-precision timing
#include // C-style string operations
#include // For int64_t (ensures portability)
#include // Socket programming (POSIX)
#include // Internet address structures
#include // POSIX system calls (e.g., close)
#include // HMAC computation (OpenSSL)
#include // Cryptographic functions
#include // OpenSSL error handling
#include // Terminal-based UI
#include // Signal handling// Constants for security and performance
#define HMAC_KEY "secret_hmac_key_32_bytes_long!!" // Hardcoded HMAC key (WARNING: Use secure key management in production)
#define MAX_RATE_PER_SOURCE 100 // Max messages per second per source to prevent DDoS
#define BUFFER_SIZE 1024 // Buffer size for receiving messages
#define PORT_BASE 1000 // Base port (1000-1049 for 50 ports)
#define NUM_PORTS 50 // Number of fibre ports// Structure for Goose SV data
struct GooseData {
float voltage; // Voltage value (e.g., 430V)
float current; // Current value (e.g., 2000A)
// Add more fields as per IEC 61850 Goose SV spec if needed, grid codes TC57 when they (eventually) arrive <80)
};// Secure message structure
struct SecureMessage {
std::string mac_address; // Source MAC address
std::string data; // Plaintext Goose SV data
unsigned char hmac[32]; // HMAC-SHA256 (32 bytes)
int64_t timestamp; // Timestamp in milliseconds
GooseData goose_data; // Parsed Goose SV data
};// Risk levels for threat categorization
enum class RiskLevel { LOW, MEDIUM, HIGH, CRITICAL };// Risk result structure for display
struct RiskResult {
std::string source; // Port identifier (e.g., "Port 1000")
RiskLevel risk_level; // Assessed risk level
std::string details; // Details including MAC and issue
};// Global variables with thread safety
std::mutex queue_mtx; // Protects message queue
std::mutex display_mtx; // Protects display updates
std::condition_variable queue_cv; // Signals new messages
std::queue message_queue; // Incoming message queue
std::unordered_map last_heartbeat; // Tracks heartbeats
std::unordered_map message_count_per_sec; // Tracks message rates
std::unordered_set whitelist = {"00:11:22:33:44:55"}; // Authorized MACs
std::vector risk_results(NUM_PORTS); // Risk results per port
volatile sig_atomic_t running = 1; // Shutdown flag// Signal handler for Ctrl+C or termination
void signal_handler(int sig) {
running = 0; // Gracefully stop the program
}// Compute HMAC-SHA256 for message integrity
void computeHMAC(const std::string& data, unsigned char* hmac_output, unsigned int& hmac_len) {
HMAC(EVP_sha256(), (const unsigned char*)HMAC_KEY, strlen(HMAC_KEY),
(const unsigned char*)data.c_str(), data.size(), hmac_output, &hmac_len);
}// Verify HMAC to ensure authenticity and integrity
bool verifyHMAC(const std::string& data, const unsigned char* received_hmac, unsigned int hmac_len) {
unsigned char computed_hmac[32];
unsigned int computed_len;
computeHMAC(data, computed_hmac, computed_len);
return (computed_len == hmac_len && memcmp(computed_hmac, received_hmac, hmac_len) == 0);
}// Check if MAC is authorized
bool isAuthorized(const std::string& mac) {
return whitelist.find(mac) != whitelist.end();
}// Validate timestamp to prevent replay attacks (1-second tolerance)
bool validateTimestamp(int64_t received_time) {
auto now = std::chrono::duration_cast(
std::chrono::system_clock::now().time_since_epoch()).count();
return std::abs(now - received_time) < 1000;
}// Enforce rate limiting to prevent DDoS
bool checkRateLimit(const std::string& mac) {
auto now = std::chrono::duration_cast(
std::chrono::system_clock::now().time_since_epoch()).count();
static int64_t last_reset = now;
if (now > last_reset) {
message_count_per_sec.clear();
last_reset = now;
}
int& count = message_count_per_sec[mac];
return ++count <= MAX_RATE_PER_SOURCE;
}// Validate Goose SV data ranges
bool checkRanges(const GooseData& data) {
return (data.voltage >= 220 && data.voltage <= 240) &&
(data.current >= 0 && data.current <= 100);
}// Placeholder for sensor data reading (to be implemented by engineer)
GooseData read_sensor_data() {
// TODO: Replace with actual sensor reading logic for MIC-7700
return {230.0f, 50.0f}; // Simulated values
}// Process message and assess risks
RiskResult processMessage(const SecureMessage& msg, int port_idx) {
RiskResult result;
result.source = "Port " + std::to_string(port_idx + PORT_BASE);
result.risk_level = RiskLevel::LOW;
result.details = "MAC: " + msg.mac_address + " - No issues detected";if (!isAuthorized(msg.mac_address)) {
result.risk_level = RiskLevel::CRITICAL;
result.details = "MAC: " + msg.mac_address + " - Unauthorized sender";
return result;
}
if (!verifyHMAC(msg.data, msg.hmac, 32)) {
result.risk_level = RiskLevel::HIGH;
result.details = "MAC: " + msg.mac_address + " - HMAC verification failed";
return result;
}
if (!checkRateLimit(msg.mac_address)) {
result.risk_level = RiskLevel::HIGH;
result.details = "MAC: " + msg.mac_address + " - Rate limit exceeded";
return result;
}
if (!validateTimestamp(msg.timestamp)) {
result.risk_level = RiskLevel::MEDIUM;
result.details = "MAC: " + msg.mac_address + " - Invalid timestamp";
return result;
}
if (!checkRanges(msg.goose_data)) {
result.risk_level = RiskLevel::HIGH;
result.details = "MAC: " + msg.mac_address + " - Fake values detected";
return result;
}
return result;
}// Handle client connections
void handleClient(int client_socket, int port_idx) {
char buffer[BUFFER_SIZE];
while (running) {
int bytes_received = recv(client_socket, buffer, BUFFER_SIZE - 1, 0);
if (bytes_received <= 0) {
std::lock_guard lock(display_mtx);
risk_results[port_idx] = {"Port " + std::to_string(port_idx + PORT_BASE),
RiskLevel::HIGH, "Connection lost"};
break;
}// Use exact length to handle binary data with possible nulls
SecureMessage msg;
msg.mac_address = "00:11:22:33:44:55"; // TODO: Extract from actual message
msg.data = std::string(buffer, bytes_received);
computeHMAC(msg.data, msg.hmac, 32);
msg.timestamp = std::chrono::duration_cast(
std::chrono::system_clock::now().time_since_epoch()).count();
msg.goose_data = read_sensor_data(); // Replace with real parsing if needed{
std::lock_guard lock(queue_mtx);
message_queue.push(msg);
}
queue_cv.notify_one();
}
close(client_socket);
}// Update ncurses display
void updateDisplay() {
std::lock_guard lock(display_mtx);
clear();
printw("Current Risk Levels:\n");
printw("Port | Risk Level | Details\n");
printw("-----------|------------|---------\n");
for (const auto& result : risk_results) {
const char* level_str = (result.risk_level == RiskLevel::LOW) ? "LOW" :
(result.risk_level == RiskLevel::MEDIUM) ? "MEDIUM" :
(result.risk_level == RiskLevel::HIGH) ? "HIGH" : "CRITICAL";
printw("%-10s | %-10s | %s\n", result.source.c_str(), level_str, result.details.c_str());
}
refresh();
}int main() {
// Register signal handlers
signal(SIGINT, signal_handler);
signal(SIGTERM, signal_handler);// Initialize ncurses
initscr();
cbreak();
noecho();
keypad(stdscr, TRUE);// Initialize OpenSSL
OpenSSL_add_all_algorithms();
ERR_load_crypto_strings();// Initialize risk results
for (int i = 0; i < NUM_PORTS; ++i) {
risk_results[i] = {"Port " + std::to_string(PORT_BASE + i), RiskLevel::LOW, "Initializing"};
}// Set up server sockets
std::vector server_sockets(NUM_PORTS);
for (int i = 0; i < NUM_PORTS; ++i) {
int sock = socket(AF_INET, SOCK_STREAM, 0);
if (sock < 0) {
endwin();
std::cerr << "Error: Socket creation failed for port " << (PORT_BASE + i) << std::endl;
return 1;
}
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); // Allow port reuse
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(PORT_BASE + i);
if (bind(sock, (struct sockaddr*)&addr, sizeof(addr)) < 0 || listen(sock, 5) < 0) {
endwin();
std::cerr << "Error: Bind/listen failed for port " << (PORT_BASE + i) << std::endl;
close(sock);
return 1;
}
server_sockets[i] = sock;
}// Start accept threads
std::vector threads;
for (int i = 0; i < NUM_PORTS; ++i) {
threads.emplace_back([i, &server_sockets]() {
while (running) {
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_sock = accept(server_sockets[i], (struct sockaddr*)&client_addr, &client_len);
if (client_sock < 0) continue;
std::thread client_thread(handleClient, client_sock, i);
client_thread.detach();
}
});
}// Main loop to process messages
while (running) {
std::unique_lock lock(queue_mtx);
queue_cv.wait(lock, [] { return !message_queue.empty() || !running; });
if (!running) break;SecureMessage msg = message_queue.front();
message_queue.pop();
lock.unlock();int port_idx = std::stoi(msg.source.substr(5)) - PORT_BASE; // Note: Adjusted below
risk_results[port_idx] = processMessage(msg, port_idx); // Pass port_idx explicitly
updateDisplay();
}// Cleanup
for (int sock : server_sockets) close(sock);
for (auto& t : threads) if (t.joinable()) t.join();
endwin();
EVP_cleanup();
ERR_free_strings();
return 0;
}
```---
### Key Changes and Fixes from v0.1
1. **Added Missing Include**: Added `#include ` for OpenSSL error handling functions.
2. **Corrected Message Parsing**: Changed `msg.data = buffer` to `msg.data = std::string(buffer, bytes_received)` to handle binary data correctly.
3. **Improved ProcessMessage**: Modified `processMessage` to take `port_idx` as a parameter and set `source` as the port string, including the MAC address in `details` for traceability.
4. **Fixed Port Indexing**: Ensured `port_idx` is passed correctly to `processMessage` in `handleClient`.
5. **Added Sensor Placeholder**: Included a `read_sensor_data()` function as a clear placeholder for the engineer to implement.
6. **Enhanced Comments**: Added detailed comments explaining each section and noting areas for future improvement (e.g., secure key management, real sensor data).
7. **Socket Reuse**: Added `SO_REUSEADDR` to prevent "Address already in use" errors during restarts.---
### Build and Run Instructions
Assuming the Advantec MIC-7700 runs a Debian-based Linux OS (e.g., Ubuntu), follow these steps to build and run the demo code:
#### Step 1: Prep the Environment
Install the necessary dependencies on the MIC-7700. Connect to the device via SSH or better an air-gapped from the terminal and run:```bash
sudo apt update
sudo apt install -y g++ libssl-dev libncurses5-dev
```- `g++`: C++ compiler
- `libssl-dev`: OpenSSL libraries for HMAC and cryptography
- `libncurses5-dev`: ncurses library for the terminal UI#### Step 2: Save the Code
Copy the code above into a file named `IEC 61850 IDS_Cyber_Demo.cpp` on the MIC-7700. You can use a text editor like `nano`:```bash
nano IEC 61850 IDS_Cyber_Demo.cpp
```Paste the code, then save and exit (`Ctrl+O`, `Enter`, `Ctrl+X`).
#### Step 3: Create a Makefile
Create a `Makefile` to simplify building. In the same directory, run:```bash
nano Makefile
```Paste the following:
```makefile
CXX = g++
CXXFLAGS = -std=c++11 -Wall -Wextra
LIBS = -pthread -lssl -lcrypto -lncursestransformer_monitor: trIEC 61850 IDS_Cyber_Demo.cpp
$(CXX) $(CXXFLAGS) -o IEC 61850 IDS_Cyber_Demo IEC 61850 IDS_Cyber_Demo.cpp $(LIBS)clean:
rm -f IEC 61850 IDS_Cyber_Demo
```Save and exit.
#### Step 4: Build the Program
Compile the code using the Makefile:```bash
make
```This creates an executable named `IEC 61850 IDS_Cyber_Demo`. If there are errors; check compiler output for missing dependencies or typos.
#### Step 5: Run the Program
Execute the program:```bash
./IEC 61850 IDS_Cyber_Demo
```The program will:
- Listen on ports 1000-1049.
- Display a terminal UI showing risk levels for each port.
- Process incoming messages (simulated in this version).
- Exit via `Ctrl+C`.#### Step 6: Verify Operation
- Ensure no "port in use" errors occur (thanks to `SO_REUSEADDR`).
- Check the ncurses display for port statuses (initially "Initializing").
- Test with a client sending data to ports 1000-1049 if available, or implement sensor logic as noted below.---
### Notes for the Engineer- **System Integration**: The `read_sensor_data()` function is a placeholder. Replace it with actual energy sensor reading logic specific to the MIC-7700's hardware (using I2C or GPIO libraries or interfaces depending on protocols for exmaple UMG-512 Pro).
- **Goose SV Parsing**: The current code simulates Goose SV data. Implement parsing logic in `handleClient` to extract real Goose SV data from `msg.data`.
- **Security**: The HMAC key is hardcoded for simplicity. In production, use a secure key management system (e.g., a file with restricted permissions or implement hardware security e.g. Infineon).
- **MAC Addresses**: The MAC is hardcoded as "00:11:22:33:44:55". Modify `handleClient` to extract the real MAC from incoming messages or network packets.
- **Firewall**: If the MIC-7700 has a firewall, ensure ports 1000-1049 are open for incoming connections (e.g., `sudo ufw allow 1000:1049/tcp`).---
### Troubleshooting
- **Build Errors**: If headers or libraries are missing, verify the `libssl-dev` and `libncurses5-dev` packages are installed correctly.
- **Runtime Errors**: Check if ports are in use (`netstat -tuln | grep 1000`) and free them if necessary.
- **Display Issues**: Ensure the terminal supports ncurses (most Linux terminals do, just saying).This completes the demo of an IDS IEC 61850 standalone cybersecurity monitor for Goose SV for students of the energy sectors or industry.