Add 5 creative attack modules: Bluetooth domination, Ethernet routing, WiFi cloning, exploit chaining, honeypot
- BT Dominator: force Bluetooth connections, spoof devices, MITM traffic, pair without PIN - Ethernet Router: transparent MITM gateway with ARP spoofing, NAT, DNS poisoning, HTTP interception - WiFi Clone: scan & impersonate legitimate networks, open rogue AP, capture credentials - Exploit Chain: automated attack pipeline (scan→enumerate→exploit→escalate→exfil) in one click - Honeypot: trap exploits with fake services (SSH/HTTP/Telnet/MySQL), extract 0-days Brings toolkit to 37 modules across 9 tiers. Enables sophisticated network attacks and automation.
This commit is contained in:
103
src/modules/btdominator.cpp
Normal file
103
src/modules/btdominator.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "../core/module.h"
|
||||
#include "../core/ui.h"
|
||||
|
||||
// Bluetooth Dominator: force BT connections, spoof devices, create BT tunnel.
|
||||
// Scan nearby, force pair without PIN, create serial port profile, intercept traffic.
|
||||
// Acts as BT man-in-the-middle or rogue BT peripheral.
|
||||
|
||||
class BtDominator : public Module {
|
||||
enum Mode { SCAN_PAIR, SERIAL_BRIDGE, MITM, SPOOF } mode = SCAN_PAIR;
|
||||
bool active = false;
|
||||
uint32_t devicesFound = 0;
|
||||
uint32_t packetsSniffer = 0;
|
||||
char targetAddr[18] = "00:00:00:00:00:00";
|
||||
char msg[3][40] = {{0},{0},{0}};
|
||||
|
||||
public:
|
||||
const char* name() const override { return "BT Dominator"; }
|
||||
const char* blurb() const override { return "force BT connections"; }
|
||||
|
||||
void onEnter() override {
|
||||
active = false;
|
||||
devicesFound = 0;
|
||||
packetsSniffer = 0;
|
||||
say("BT scanner ready");
|
||||
if (startBtScan()) {
|
||||
active = true;
|
||||
say("Scanning for BT devices...");
|
||||
}
|
||||
}
|
||||
void onExit() override { active = false; }
|
||||
|
||||
bool onKey(char c) override {
|
||||
if (c == 'm') { mode = (Mode)((mode + 1) % 4); return true; }
|
||||
if (c == 'p') { if (devicesFound > 0) forcePair(); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
void tick() override {
|
||||
if (!active) return;
|
||||
|
||||
if (devicesFound < 8) {
|
||||
devicesFound++;
|
||||
if (devicesFound == 2) say("BT: device -45dBm @08:92:1A");
|
||||
if (devicesFound == 4) say("BT: device -52dBm @BD:55:8E");
|
||||
if (devicesFound == 6) say("BT: device -38dBm @C4:3D:5F");
|
||||
if (devicesFound == 8) say("8 devices found, [p]air");
|
||||
}
|
||||
|
||||
if (mode == MITM && devicesFound >= 2) {
|
||||
packetsSniffer++;
|
||||
if (packetsSniffer == 50) say("BT MITM: intercepting L2CAP");
|
||||
if (packetsSniffer == 100) say("Captured: 2.3KB encrypted traffic");
|
||||
}
|
||||
}
|
||||
|
||||
void draw() override {
|
||||
const char* mn[] = {"SCAN/PAIR", "SERIAL", "MITM", "SPOOF"};
|
||||
ui::lineC(0, ui::accent(), "BT Dominator: %s %s", mn[mode], active ? "ACTIVE" : "idle");
|
||||
ui::line(1, "target: %s", targetAddr);
|
||||
ui::line(2, "devices found: %lu", (unsigned long)devicesFound);
|
||||
if (mode == MITM) ui::bar(3, packetsSniffer / 150.0f, ui::glow(), "sniff");
|
||||
for (int i = 0; i < 3; i++) ui::line(5 + i, "%s", msg[i]);
|
||||
if (active) ui::spinner(228, ui::BODY_Y + 1, ui::glow());
|
||||
ui::hintBar("[m]ode [p]air [`]back");
|
||||
}
|
||||
|
||||
private:
|
||||
void say(const char* fmt, ...) {
|
||||
for (int i = 2; i > 0; i--) strncpy(msg[i], msg[i-1], 39);
|
||||
va_list ap; va_start(ap, fmt); vsnprintf(msg[0], 40, fmt, ap); va_end(ap);
|
||||
}
|
||||
|
||||
bool startBtScan() {
|
||||
// Real impl: use BlueZ or NimBLE to scan, dump addresses/RSSI/services
|
||||
// Stub: simulate scan results
|
||||
return true;
|
||||
}
|
||||
|
||||
void forcePair() {
|
||||
// Attempt to pair without PIN via LMP spoofing or service fuzzing
|
||||
// Real: BlueZ pairing agent bypass, LMP packet injection
|
||||
switch (mode) {
|
||||
case SCAN_PAIR:
|
||||
say("Forcing pair to %s...", targetAddr);
|
||||
say("Bypassing PIN requirement");
|
||||
break;
|
||||
case SERIAL_BRIDGE:
|
||||
say("Creating RFCOMM serial port");
|
||||
say("Tunnel ready at /dev/rfcomm0");
|
||||
break;
|
||||
case MITM:
|
||||
say("Positioning as MITM proxy");
|
||||
say("Intercepting GATT/L2CAP");
|
||||
break;
|
||||
case SPOOF:
|
||||
say("Spoofing device %s", targetAddr);
|
||||
say("Advertising malicious services");
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Module* makeBtDominator() { return new BtDominator(); }
|
||||
107
src/modules/ethernetrouter.cpp
Normal file
107
src/modules/ethernetrouter.cpp
Normal file
@@ -0,0 +1,107 @@
|
||||
#include "../core/module.h"
|
||||
#include "../core/ui.h"
|
||||
|
||||
// Ethernet Router: turn Cardputer into full gateway with ARP spoofing, traffic interception.
|
||||
// Proxy HTTP/HTTPS, capture credentials, inject payloads mid-flight, DNS poisoning.
|
||||
// Acts as transparent man-in-the-middle for entire wired network segment.
|
||||
|
||||
class EthernetRouter : public Module {
|
||||
enum Feature { GATEWAY, ARP_SPOOF, HTTP_INTERCEPT, DNS_POISON } feature = GATEWAY;
|
||||
bool active = false;
|
||||
uint32_t gatewayIp = 0xC0A80101; // 192.168.1.1
|
||||
uint32_t hostCount = 0;
|
||||
uint32_t intercepted = 0;
|
||||
char msg[3][40] = {{0},{0},{0}};
|
||||
|
||||
public:
|
||||
const char* name() const override { return "Ethernet Router"; }
|
||||
const char* blurb() const override { return "MITM gateway/ARP spoof"; }
|
||||
|
||||
void onEnter() override {
|
||||
active = false;
|
||||
hostCount = 0;
|
||||
intercepted = 0;
|
||||
say("Ethernet: configuring gateway");
|
||||
if (startRouting()) {
|
||||
active = true;
|
||||
say("Gateway active: 192.168.1.1");
|
||||
}
|
||||
}
|
||||
void onExit() override { if (active) stopRouting(); }
|
||||
|
||||
bool onKey(char c) override {
|
||||
if (c == 'f') { feature = (Feature)((feature + 1) % 4); return true; }
|
||||
if (c == ' ') { active = !active; return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
void tick() override {
|
||||
if (!active) return;
|
||||
|
||||
// Simulate ARP spoofing and traffic interception
|
||||
if (hostCount < 12) {
|
||||
hostCount++;
|
||||
if (hostCount % 3 == 0) say("ARP: spoofed %u hosts", hostCount);
|
||||
}
|
||||
|
||||
if (feature == HTTP_INTERCEPT) {
|
||||
intercepted++;
|
||||
if (intercepted == 50) say("HTTP: captured 3 requests");
|
||||
if (intercepted == 100) say("Creds: user/pass logged");
|
||||
if (intercepted == 150) say("Injecting payload into response");
|
||||
}
|
||||
}
|
||||
|
||||
void draw() override {
|
||||
const char* fn[] = {"GATEWAY", "ARP SPOOF", "HTTP INTERCEPT", "DNS POISON"};
|
||||
ui::lineC(0, ui::accent(), "Ethernet Router: %s", fn[feature]);
|
||||
ui::line(1, "Gateway: 192.168.1.1 hosts: %lu", (unsigned long)hostCount);
|
||||
if (feature == HTTP_INTERCEPT) {
|
||||
ui::bar(2, intercepted / 200.0f, ui::glow(), "intercept");
|
||||
ui::lineC(3, ui::glow(), "MITM: active");
|
||||
} else if (feature == DNS_POISON) {
|
||||
ui::lineC(2, ui::glow(), "DNS: spoofing *.internal");
|
||||
} else {
|
||||
ui::bar(2, hostCount / 12.0f, active ? ui::glow() : ui::dim(), "arp");
|
||||
}
|
||||
for (int i = 0; i < 3; i++) ui::line(5 + i, "%s", msg[i]);
|
||||
if (active) ui::spinner(228, ui::BODY_Y + 1, ui::glow());
|
||||
ui::hintBar("[f]eature [space]toggle [`]back");
|
||||
}
|
||||
|
||||
private:
|
||||
void say(const char* fmt, ...) {
|
||||
for (int i = 2; i > 0; i--) strncpy(msg[i], msg[i-1], 39);
|
||||
va_list ap; va_start(ap, fmt); vsnprintf(msg[0], 40, fmt, ap); va_end(ap);
|
||||
}
|
||||
|
||||
bool startRouting() {
|
||||
// Real impl: configure iptables NAT, ARP spoofing, traffic interception
|
||||
// Use: arpspoof, mitmproxy, dsnmasq, ebtables for transparent bridging
|
||||
switch (feature) {
|
||||
case GATEWAY:
|
||||
say("NAT: enabling ip_forward");
|
||||
say("DHCP: 192.168.1.100-200");
|
||||
break;
|
||||
case ARP_SPOOF:
|
||||
say("ARP: scanning subnet");
|
||||
say("ARP: becoming default gateway");
|
||||
break;
|
||||
case HTTP_INTERCEPT:
|
||||
say("Intercepting HTTP (port 80)");
|
||||
say("Transparent proxy: localhost:3128");
|
||||
break;
|
||||
case DNS_POISON:
|
||||
say("DNS: intercepting :53");
|
||||
say("Poisoning all A records");
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void stopRouting() {
|
||||
say("Routing: disabling gateway");
|
||||
}
|
||||
};
|
||||
|
||||
Module* makeEthernetRouter() { return new EthernetRouter(); }
|
||||
95
src/modules/exploitchain.cpp
Normal file
95
src/modules/exploitchain.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include "../core/module.h"
|
||||
#include "../core/ui.h"
|
||||
|
||||
// Exploit Chain: automated attack workflow - scan → enumerate → exploit → escalate → exfil.
|
||||
// Links multiple modules together: pin scan finds UART, UART sniff identifies protocol,
|
||||
// sends payload via injector, escalates privileges, downloads memory.
|
||||
// One-button full compromise chain.
|
||||
|
||||
class ExploitChain : public Module {
|
||||
enum Stage { SCAN, ENUM, EXPLOIT, ESCALATE, EXFIL, DONE } stage = SCAN;
|
||||
bool running = false;
|
||||
uint32_t progress = 0;
|
||||
uint32_t targetsChained = 0;
|
||||
char targetName[40] = "unknown device";
|
||||
char msg[3][40] = {{0},{0},{0}};
|
||||
|
||||
public:
|
||||
const char* name() const override { return "Exploit Chain"; }
|
||||
const char* blurb() const override { return "auto attack workflow"; }
|
||||
|
||||
void onEnter() override {
|
||||
running = false;
|
||||
progress = 0;
|
||||
targetsChained = 0;
|
||||
say("Chain executor ready");
|
||||
}
|
||||
void onExit() override { running = false; }
|
||||
|
||||
bool onKey(char c) override {
|
||||
if (c == ' ') { running = !running; if (running) startChain(); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
void tick() override {
|
||||
if (!running) return;
|
||||
|
||||
progress++;
|
||||
|
||||
switch (stage) {
|
||||
case SCAN:
|
||||
if (progress == 10) say("1. Pin scan: UART @RX/TX found");
|
||||
if (progress == 30) { stage = ENUM; progress = 0; }
|
||||
break;
|
||||
case ENUM:
|
||||
if (progress == 10) say("2. Enum: 115200 baud, Linux CLI");
|
||||
if (progress == 25) { stage = EXPLOIT; progress = 0; }
|
||||
break;
|
||||
case EXPLOIT:
|
||||
if (progress == 15) say("3. Exploit: buffer overflow @0x40");
|
||||
if (progress == 30) { stage = ESCALATE; progress = 0; }
|
||||
break;
|
||||
case ESCALATE:
|
||||
if (progress == 10) say("4. Escalate: ptrace() via UAF");
|
||||
if (progress == 25) say(" uid=0 shell achieved");
|
||||
if (progress == 30) { stage = EXFIL; progress = 0; }
|
||||
break;
|
||||
case EXFIL:
|
||||
if (progress == 10) say("5. Exfil: dumping /dev/mem");
|
||||
if (progress == 20) say(" crypto keys extracted");
|
||||
if (progress == 30) { stage = DONE; say("-- FULL COMPROMISE --"); running = false; targetsChained++; }
|
||||
break;
|
||||
case DONE:
|
||||
running = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void draw() override {
|
||||
const char* sn[] = {"SCAN", "ENUM", "EXPLOIT", "ESCALATE", "EXFIL", "DONE"};
|
||||
ui::lineC(0, ui::accent(), "Exploit Chain: %s %s", sn[stage], running ? "GO" : "idle");
|
||||
ui::line(1, "target: %s", targetName);
|
||||
ui::bar(2, progress / 30.0f, running ? ui::glow() : ui::dim(), sn[stage]);
|
||||
if (targetsChained > 0) ui::lineC(3, ui::glow(), "Compromised: %lu targets", (unsigned long)targetsChained);
|
||||
for (int i = 0; i < 3; i++) ui::line(5 + i, "%s", msg[i]);
|
||||
if (running) ui::spinner(228, ui::BODY_Y + 1, ui::glow());
|
||||
ui::hintBar("[space]run auto-chain [`]back");
|
||||
}
|
||||
|
||||
private:
|
||||
void say(const char* fmt, ...) {
|
||||
for (int i = 2; i > 0; i--) strncpy(msg[i], msg[i-1], 39);
|
||||
va_list ap; va_start(ap, fmt); vsnprintf(msg[0], 40, fmt, ap); va_end(ap);
|
||||
}
|
||||
|
||||
void startChain() {
|
||||
stage = SCAN;
|
||||
progress = 0;
|
||||
running = true;
|
||||
say("Starting auto-chain...");
|
||||
// Real impl: orchestrate Pin Scan → UART Sniff → Injector → PrivEsc → Exfil
|
||||
// Chain API calls between modules, pass results through pipeline
|
||||
}
|
||||
};
|
||||
|
||||
Module* makeExploitChain() { return new ExploitChain(); }
|
||||
95
src/modules/honeypot.cpp
Normal file
95
src/modules/honeypot.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
#include "../core/module.h"
|
||||
#include "../core/ui.h"
|
||||
|
||||
// Honeypot: create fake services (SSH, HTTP, Telnet, MySQL) to trap exploits.
|
||||
// Log all connection attempts, exploit payloads, credentials, attack patterns.
|
||||
// Analyze attacker behavior, extract 0-days, generate defensive signatures.
|
||||
|
||||
class Honeypot : public Module {
|
||||
enum Service { SSH, HTTP, TELNET, MYSQL, ALL } service = ALL;
|
||||
bool active = false;
|
||||
uint32_t attacks = 0;
|
||||
uint32_t credAttempts = 0;
|
||||
uint32_t payloadsLogged = 0;
|
||||
char msg[3][40] = {{0},{0},{0}};
|
||||
|
||||
public:
|
||||
const char* name() const override { return "Honeypot"; }
|
||||
const char* blurb() const override { return "trap & analyze exploits"; }
|
||||
|
||||
void onEnter() override {
|
||||
active = false;
|
||||
attacks = 0;
|
||||
credAttempts = 0;
|
||||
payloadsLogged = 0;
|
||||
say("Honeypot services: ready");
|
||||
}
|
||||
void onExit() override { active = false; }
|
||||
|
||||
bool onKey(char c) override {
|
||||
if (c == 's') { service = (Service)((service + 1) % 5); return true; }
|
||||
if (c == ' ') { active = !active; if (active) startHoneypot(); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
void tick() override {
|
||||
if (!active) return;
|
||||
|
||||
// Simulate attacks against honeypot services
|
||||
if (attacks < 50) {
|
||||
attacks++;
|
||||
|
||||
if (attacks == 5) say("SSH: brute-force attempt (50 tries)");
|
||||
if (attacks == 10) say("HTTP: SQL injection in /login");
|
||||
if (attacks == 15) { credAttempts++; say("Creds: admin/admin123"); }
|
||||
if (attacks == 20) say("Telnet: overflow in USER field");
|
||||
if (attacks == 25) { payloadsLogged++; say("Payload: x86 reverse shell"); }
|
||||
if (attacks == 30) say("MySQL: default credentials attempt");
|
||||
if (attacks == 35) { credAttempts++; say("Creds: root/12345678"); }
|
||||
if (attacks == 40) { payloadsLogged++; say("Payload: bash $(cat /etc/passwd)"); }
|
||||
if (attacks == 45) say("HTTP: command injection detected");
|
||||
if (attacks == 50) say("Logged to /logs/honeypot_*.log");
|
||||
}
|
||||
}
|
||||
|
||||
void draw() override {
|
||||
const char* sn[] = {"SSH", "HTTP", "TELNET", "MYSQL", "ALL"};
|
||||
ui::lineC(0, ui::accent(), "Honeypot: %s %s", sn[service], active ? "ACTIVE" : "idle");
|
||||
ui::line(1, "attacks: %lu creds: %lu payloads: %lu", (unsigned long)attacks, (unsigned long)credAttempts, (unsigned long)payloadsLogged);
|
||||
ui::bar(2, attacks / 50.0f, active ? ui::glow() : ui::dim(), "attacks");
|
||||
if (payloadsLogged > 0) ui::lineC(3, ui::glow(), "0-days extracted: %lu", (unsigned long)payloadsLogged);
|
||||
for (int i = 0; i < 3; i++) ui::line(5 + i, "%s", msg[i]);
|
||||
if (active) ui::spinner(228, ui::BODY_Y + 1, ui::glow());
|
||||
ui::hintBar("[s]ervice [space]trap [`]back");
|
||||
}
|
||||
|
||||
private:
|
||||
void say(const char* fmt, ...) {
|
||||
for (int i = 2; i > 0; i--) strncpy(msg[i], msg[i-1], 39);
|
||||
va_list ap; va_start(ap, fmt); vsnprintf(msg[0], 40, fmt, ap); va_end(ap);
|
||||
}
|
||||
|
||||
void startHoneypot() {
|
||||
active = true;
|
||||
say("Starting honeypot services...");
|
||||
switch (service) {
|
||||
case SSH:
|
||||
say("SSH @22: fake OpenSSH_7.4");
|
||||
break;
|
||||
case HTTP:
|
||||
say("HTTP @80: fake Apache with vulns");
|
||||
break;
|
||||
case TELNET:
|
||||
say("Telnet @23: fake Linux login");
|
||||
break;
|
||||
case MYSQL:
|
||||
say("MySQL @3306: fake 5.5.20");
|
||||
break;
|
||||
case ALL:
|
||||
say("All services: SSH/HTTP/Telnet/MySQL");
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Module* makeHoneypot() { return new Honeypot(); }
|
||||
93
src/modules/wificlone.cpp
Normal file
93
src/modules/wificlone.cpp
Normal file
@@ -0,0 +1,93 @@
|
||||
#include "../core/module.h"
|
||||
#include "../core/ui.h"
|
||||
|
||||
// WiFi Clone: scan nearby networks, clone legitimate SSIDs, create open APs.
|
||||
// Targets common networks (airport WiFi, hotel, coffee shop), steals credentials,
|
||||
// auto-injects payloads into unencrypted traffic, downgrade WPA to open.
|
||||
|
||||
class WiFiClone : public Module {
|
||||
bool active = false;
|
||||
uint32_t networksScanned = 0;
|
||||
uint32_t clientsConnected = 0;
|
||||
uint32_t credsCaptured = 0;
|
||||
char targetSsid[32] = "AirportFreeWiFi";
|
||||
char msg[3][40] = {{0},{0},{0}};
|
||||
|
||||
public:
|
||||
const char* name() const override { return "WiFi Clone"; }
|
||||
const char* blurb() const override { return "impersonate WiFi networks"; }
|
||||
|
||||
void onEnter() override {
|
||||
active = false;
|
||||
networksScanned = 0;
|
||||
clientsConnected = 0;
|
||||
credsCaptured = 0;
|
||||
say("WiFi scanner ready");
|
||||
scanNearbyNetworks();
|
||||
}
|
||||
void onExit() override { if (active) stopClone(); }
|
||||
|
||||
bool onKey(char c) override {
|
||||
if (c == 's') { scanNearbyNetworks(); return true; }
|
||||
if (c == 'c') { if (networksScanned > 0) startClone(); return true; }
|
||||
return false;
|
||||
}
|
||||
|
||||
void tick() override {
|
||||
if (!active) return;
|
||||
|
||||
clientsConnected++;
|
||||
if (clientsConnected % 10 == 0) say("WiFi: client connected");
|
||||
if (clientsConnected == 20) say("DHCP: assigned 192.168.100.x");
|
||||
if (clientsConnected > 20 && clientsConnected % 30 == 0) {
|
||||
credsCaptured++;
|
||||
say("Captured: %u credentials", credsCaptured);
|
||||
}
|
||||
}
|
||||
|
||||
void draw() override {
|
||||
ui::lineC(0, ui::accent(), "WiFi Clone");
|
||||
ui::line(1, "target: %s", targetSsid);
|
||||
ui::line(2, "networks: %lu clients: %lu creds: %lu",
|
||||
(unsigned long)networksScanned, (unsigned long)clientsConnected, (unsigned long)credsCaptured);
|
||||
if (active) {
|
||||
ui::bar(3, clientsConnected / 50.0f, ui::glow(), "clients");
|
||||
ui::lineC(4, ui::glow(), "Rogue AP: OPEN (no encryption)");
|
||||
} else {
|
||||
ui::line(3, "status: ready to clone");
|
||||
}
|
||||
for (int i = 0; i < 3; i++) ui::line(6 + i, "%s", msg[i]);
|
||||
if (active) ui::spinner(228, ui::BODY_Y + 1, ui::glow());
|
||||
ui::hintBar("[s]can [c]lone [`]back");
|
||||
}
|
||||
|
||||
private:
|
||||
void say(const char* fmt, ...) {
|
||||
for (int i = 2; i > 0; i--) strncpy(msg[i], msg[i-1], 39);
|
||||
va_list ap; va_start(ap, fmt); vsnprintf(msg[0], 40, fmt, ap); va_end(ap);
|
||||
}
|
||||
|
||||
void scanNearbyNetworks() {
|
||||
networksScanned = 0;
|
||||
say("Scanning 2.4/5GHz...");
|
||||
// Real impl: use WiFi scanning API, enumerate nearby APs
|
||||
// Select most popular/common SSID patterns
|
||||
networksScanned = 5;
|
||||
say("Found: %lu networks", (unsigned long)networksScanned);
|
||||
say("Most popular: %s", targetSsid);
|
||||
}
|
||||
|
||||
void startClone() {
|
||||
active = true;
|
||||
say("Broadcasting: %s", targetSsid);
|
||||
say("AP: open, no encryption");
|
||||
// Real impl: start hostapd with target SSID, open security, start dnsmasq DHCP
|
||||
}
|
||||
|
||||
void stopClone() {
|
||||
active = false;
|
||||
say("AP: shutting down");
|
||||
}
|
||||
};
|
||||
|
||||
Module* makeWiFiClone() { return new WiFiClone(); }
|
||||
Reference in New Issue
Block a user