release: Mark VIII 1.0

This commit is contained in:
c4ch3c4d3
2026-08-11 20:24:24 -07:00
commit 3e1805dab8
86 changed files with 20678 additions and 0 deletions
+115
View File
@@ -0,0 +1,115 @@
param(
[string]$PagerHost = "172.16.52.1",
[string]$User = "root",
[string]$Password = "",
[string]$SshKey = "",
[string]$BuildDir = "",
[switch]$NoPortalRefresh
)
$ErrorActionPreference = "Stop"
$Root = Split-Path -Parent $PSScriptRoot
$PayloadKey = "pager-webui"
$PayloadCategory = "remote_access"
$PayloadDir = Join-Path $Root "payload\user\$PayloadCategory\$PayloadKey"
if (-not (Test-Path $PayloadDir)) { throw "Payload dir not found: $PayloadDir" }
if (-not $BuildDir) { $BuildDir = Join-Path $Root "build" }
$OutDir = Join-Path $BuildDir $PayloadKey
New-Item -ItemType Directory -Force -Path $OutDir | Out-Null
# --- 1. Stage payload tree -------------------------------------------------
$Stage = Join-Path $OutDir "stage"
if (Test-Path $Stage) { Remove-Item -Recurse -Force $Stage }
New-Item -ItemType Directory -Force -Path (Join-Path $Stage "user\$PayloadCategory") | Out-Null
Copy-Item -Recurse $PayloadDir (Join-Path $Stage "user\$PayloadCategory\$PayloadKey")
# --- 2. Build zip (portal format: payload-<b64>.zip) -----------------------
$b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($PayloadKey)).TrimEnd('=').Replace('+','-').Replace('/','_')
$ZipName = "payload-$b64.zip"
$ZipPath = Join-Path $OutDir $ZipName
if (Test-Path $ZipPath) { Remove-Item -Force $ZipPath }
Add-Type -AssemblyName System.IO.Compression
$zip = New-Object System.IO.Compression.ZipArchive([IO.File]::Open($ZipPath, 'Create'), [IO.Compression.ZipArchiveMode]::Create)
try {
Get-ChildItem -Recurse -File $Stage | Where-Object {
$_.FullName -notmatch '[\\/]__pycache__[\\/]' -and $_.Extension -ne '.pyc'
} | ForEach-Object {
$rel = $_.FullName.Substring($Stage.Length + 1).Replace('\', '/')
$entry = $zip.CreateEntry($rel, [IO.Compression.CompressionLevel]::Optimal)
$es = $entry.Open()
$bytes = [IO.File]::ReadAllBytes($_.FullName)
if ($rel -match '(^|/)(payload\.sh|pagerwebui\.init)$') {
$bytes = [byte[]]($bytes | Where-Object { $_ -ne 13 })
}
$es.Write($bytes, 0, $bytes.Length)
$es.Close()
}
} finally { $zip.Dispose() }
# --- 3. Manifest with generated fields ------------------------------------
$hash = (Get-FileHash -Algorithm SHA256 $ZipPath).Hash.ToLower()
$manifest = Get-Content -Raw (Join-Path $PayloadDir "_hak5_manifest.json") | ConvertFrom-Json
$manifest.time = [int64]([DateTimeOffset]::UtcNow.ToUnixTimeSeconds())
$manifest.last_hash = $hash
$manifest.zip = $ZipName
$manifest = $manifest | ConvertTo-Json
Set-Content -Path (Join-Path $OutDir "_hak5_manifest.json") -Value $manifest -Encoding ascii
Write-Host "Built: $ZipPath"
# --- 4. Credentials / transport -------------------------------------------
if ($SshKey) {
$sshBase = "$User@$PagerHost"
$scp = "scp -i `"$SshKey`""
$ssh = "ssh -i `"$SshKey`""
} elseif (Get-Command sshpass -ErrorAction SilentlyContinue) {
if (-not $Password) { $Password = Read-Host -AsSecureString "Pager root password"; $Password = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Password)) }
$sshBase = "$User@$PagerHost"
$scp = "sshpass -p `"$Password`" scp"
$ssh = "sshpass -p `"$Password`" ssh"
} else {
Write-Host "`nNo sshpass or -SshKey found. Run these manually (password prompts appear):"
Write-Host " scp `"$ZipPath`" $User@${PagerHost}:/tmp/"
Write-Host " ssh $User@$PagerHost `"cd /root/payloads && unzip -q -o /tmp/$ZipName && chmod +x user/general/$PayloadKey/payload.sh && rm -f /tmp/$ZipName`""
Write-Host "Then re-run this script with -SshKey, or install sshpass."
exit 1
}
# --- 5. Upload + extract on device ----------------------------------------
& cmd /c "$scp `"$ZipPath`" `"$(Join-Path $OutDir '_hak5_manifest.json')`" ${sshBase}:/tmp/" | Out-Null
if ($LASTEXITCODE -ne 0) { throw "SCP failed" }
$remotePayloadDir = "user/$PayloadCategory/$PayloadKey"
$legacyPayloadDir = "user/general/$PayloadKey"
$remoteCmd = "cd /root/payloads && rm -rf $remotePayloadDir $legacyPayloadDir && unzip -q -o /tmp/$ZipName && cp /tmp/_hak5_manifest.json $remotePayloadDir/_hak5_manifest.json && chmod +x $remotePayloadDir/payload.sh && chmod -R 755 $remotePayloadDir/www && rm -f /tmp/$ZipName /tmp/_hak5_manifest.json && if [ -x /etc/init.d/pagerwebui ] && /etc/init.d/pagerwebui running >/dev/null 2>&1; then /etc/init.d/pagerwebui restart; fi && echo EXTRACT_OK"
& cmd /c "$ssh $sshBase `"$remoteCmd`""
if ($LASTEXITCODE -ne 0) { throw "Remote extraction failed" }
Write-Host "Installed to /root/payloads/$remotePayloadDir/"
# --- 6. Portal refresh (best-effort) --------------------------------------
if (-not $NoPortalRefresh) {
if (-not $Password) {
Write-Host "Skipping portal refresh (no password supplied). Run the payload from the on-device menu to verify."
} else {
# Base64 the remote command so cmd/ssh quoting cannot mangle the JSON body.
$loginCmd = "curl -s -X POST http://127.0.0.1:1471/api/login -d '{""username"":""root"",""password"":""$Password""}'"
$b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($loginCmd))
$tokenJson = & cmd /c "$ssh $sshBase `"echo $b64 | base64 -d | sh`""
$tokenJson = [string]$tokenJson -replace '\x1b\[[0-9;?]*[A-Za-z]', ''
$token = ($tokenJson | ConvertFrom-Json).token
if ($token) {
$refreshCmd = "curl -fsS -X POST http://127.0.0.1:1471/api/payloads/portal/refresh -H 'Authorization: Bearer $token'"
$b64 = [Convert]::ToBase64String([Text.Encoding]::UTF8.GetBytes($refreshCmd))
& cmd /c "$ssh $sshBase `"echo $b64 | base64 -d | sh`"" | Out-Null
if ($LASTEXITCODE -eq 0) {
Write-Host "Portal refreshed. The payload should appear in the on-device Payloads menu / Virtual Pager portal."
} else {
Write-Warning "Portal refresh failed; the local payload installation is still complete."
}
} else {
Write-Host "Login to portal refresh failed; the payload is installed as a directory - run it from the menu."
}
}
}
Write-Host "Deploy complete. Run payload.sh from the Pager menu, then browse http://${PagerHost}:8080/"
+19
View File
@@ -0,0 +1,19 @@
param(
[string]$PagerHost = "172.16.52.1",
[int]$Port = 8000,
[switch]$Tunnel
)
$ErrorActionPreference = "Stop"
$Root = Split-Path -Parent $PSScriptRoot
$Python = "$env:LOCALAPPDATA\Programs\Python\Python311\python.exe"
if (-not (Test-Path $Python)) { throw "Python 3.11 not found. Install with: winget install Python.Python.3.11" }
if ($Tunnel) {
Write-Host "Opening SSH tunnel 1471 -> 127.0.0.1:1471 (for direct terminal WS). Close the ssh window to stop it."
Start-Process ssh -ArgumentList "-N","-L","1471:127.0.0.1:1471","root@$PagerHost" -WindowStyle Minimized
}
Write-Host "Dev server: http://127.0.0.1:$Port (API proxied to http://$PagerHost:8080)"
Write-Host "Requires the backend deployed: scripts\deploy.ps1"
& $Python "$PSScriptRoot\dev_proxy.py" --pager-host $PagerHost --port $Port --www "$Root\payload\user\remote_access\pager-webui\www"
+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env python3
"""Local dev proxy: serves www/ statically and proxies /api/* to the Pager."""
import argparse
import http.server
import urllib.request
import urllib.error
def main():
ap = argparse.ArgumentParser()
ap.add_argument('--pager-host', default='172.16.52.1')
ap.add_argument('--pager-port', type=int, default=8080)
ap.add_argument('--port', type=int, default=8000)
ap.add_argument('--www', default='payload/user/remote_access/pager-webui/www')
args = ap.parse_args()
api_target = 'http://%s:%d' % (args.pager_host, args.pager_port)
class Proxy(http.server.SimpleHTTPRequestHandler):
def __init__(self, *a, **kw):
super().__init__(*a, directory=args.www, **kw)
def log_message(self, fmt, *a):
pass
def _serve_dev_config(self):
body = ('window.PAGER_CONFIG = { apiBase: "", wsBase: "", '
'terminalWs: "ws://%s:1471/api/terminal/openWs", '
'pagerScreenWs: "ws://%s:1471/api/pager/display/screen.ws", '
'pagerKeysWs: "ws://%s:1471/api/pager/input/keys.ws" };\n'
% (args.pager_host, args.pager_host, args.pager_host)).encode()
self.send_response(200)
self.send_header('Content-Type', 'text/javascript')
self.send_header('Content-Length', str(len(body)))
self.send_header('Cache-Control', 'no-cache')
self.end_headers()
self.wfile.write(body)
return True
def _proxy(self, method):
body = None
if self.headers.get('Content-Length'):
body = self.rfile.read(int(self.headers['Content-Length']))
req = urllib.request.Request(api_target + self.path, data=body, method=method)
if self.headers.get('Cookie'):
req.add_header('Cookie', self.headers['Cookie'])
req.add_header('Content-Type', self.headers.get('Content-Type', 'application/json'))
try:
with urllib.request.urlopen(req, timeout=25) as r:
data = r.read()
self.send_response(r.status)
self.send_header('Content-Type', r.headers.get('Content-Type', 'application/octet-stream'))
self.send_header('Content-Length', str(len(data)))
for sc in r.headers.get_all('Set-Cookie', []):
self.send_header('Set-Cookie', sc)
self.end_headers()
self.wfile.write(data)
except urllib.error.HTTPError as e:
data = e.read()
self.send_response(e.code)
self.send_header('Content-Type', e.headers.get('Content-Type', 'application/json'))
self.send_header('Content-Length', str(len(data)))
for sc in e.headers.get_all('Set-Cookie', []):
self.send_header('Set-Cookie', sc)
self.end_headers()
self.wfile.write(data)
def do_GET(self):
if self.path == '/js/config.js':
return self._serve_dev_config()
if self.path.startswith('/api/'):
return self._proxy('GET')
return super().do_GET()
def do_POST(self):
return self._proxy('POST') if self.path.startswith('/api/') else self.send_error(404)
def do_DELETE(self):
return self._proxy('DELETE') if self.path.startswith('/api/') else self.send_error(404)
http.server.ThreadingHTTPServer(('127.0.0.1', args.port), Proxy).serve_forever()
if __name__ == '__main__':
main()