@windypro-rourou/dsh-logcat 0.3.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -4
- package/lib/index.js +283 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,19 +18,22 @@ DSH Web GUI 的安卓实机调试面板(类似 Android Studio 的 Logcat 视
|
|
|
18
18
|
- 暂停/继续(暂停时缓冲,恢复自动回放)、清空、复制、导出 .txt
|
|
19
19
|
- 窗口化渲染 + 自动滚动(滚动手动上翻时自动停用)
|
|
20
20
|
- 未授权设备提示「请在手机上点击允许 USB 调试」
|
|
21
|
-
- **Agent 工具**(共
|
|
21
|
+
- **Agent 工具**(共 21 个,全部对 agent 开放,前置提示中已明示可调用):
|
|
22
22
|
- 设备:`logcat_devices`(列出设备)、`device_info`(型号/版本/SDK/分辨率/内存/电量)、`device_stats`(CPU/内存/电量实时采样)
|
|
23
23
|
- 执行:`adb_exec`(shell)、`adb_install`(本地 APK 装真机)、`adb_pull`(拉文件)
|
|
24
24
|
- 输入:`input_tap` / `input_swipe` / `input_text`(真机 UI 自动化)、`ui_dump`(界面层级 XML)
|
|
25
25
|
- 日志:`logcat_recent`(按包名/级别/关键词过滤)、`logcat_crash`(崩溃/ANR 自动捕获 + 上下文)、
|
|
26
26
|
`logcat_set_package`(锁定当前测试包名)
|
|
27
27
|
- 逆向/内存:`proc_list`(进程列表)、`proc_maps`(内存映射 + so 模块基址)、`proc_status`(进程状态/内存摘要)、
|
|
28
|
-
`mem_dump`(指定地址读内存 hex
|
|
28
|
+
`proc_smaps`(smaps 明细,Pss Top 区域)、`mem_dump`(指定地址读内存 hex)、
|
|
29
|
+
`mem_search`(内存搜 hex 模式/字符串)、`frida_server`(frida-server 部署/启停)、
|
|
30
|
+
`frida_script`(hook/trace/scan/bypass/dump 常用脚本模板生成)
|
|
29
31
|
- **实机调试工作流**:构建安卓应用时,agent 的通告会动态列出当前已连接设备(serial + 型号)与当前测试包名,
|
|
30
32
|
可先向用户确认后:`adb_install` 部署 APK → `adb_exec` 启动 → `logcat_set_package` 锁包 →
|
|
31
33
|
`logcat_recent` / `logcat_crash` 看崩溃 → `ui_dump` + `input_*` 做界面自动化 → `adb_pull` 取证,闭环真机调试。
|
|
32
|
-
- **逆向工作流**:`proc_list` 定位进程 → `proc_maps`
|
|
33
|
-
`
|
|
34
|
+
- **逆向工作流**:`proc_list` 定位进程 → `proc_maps` 拿模块基址 → `mem_dump` 读目标地址 / `mem_search` 搜特征模式 →
|
|
35
|
+
`proc_smaps` 看内存占用明细 → `frida_script` 生成脚本 + `frida_server` 起 frida 做动态插桩。
|
|
36
|
+
读其他应用内存/maps 需要 root 或 debuggable 应用(run-as),工具会给出明确提示。
|
|
34
37
|
- **附加能力**:`POST /api/dsh-logcat/exec` 执行 shell、`POST /api/dsh-logcat/package` 设置包名、
|
|
35
38
|
`GET /api/dsh-logcat/screenshot` 截屏、`POST /api/dsh-logcat/install-adb` 一键装 adb。
|
|
36
39
|
|
package/lib/index.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
import { spawn, execFile } from 'node:child_process'
|
|
18
18
|
import { existsSync } from 'node:fs'
|
|
19
19
|
import { homedir } from 'node:os'
|
|
20
|
-
import { join } from 'node:path'
|
|
20
|
+
import { dirname, join } from 'node:path'
|
|
21
21
|
import { createInterface } from 'node:readline'
|
|
22
22
|
import { WebSocket, WebSocketServer } from 'ws'
|
|
23
23
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
@@ -41,7 +41,7 @@ export const LOGCAT_GUIDANCE =
|
|
|
41
41
|
'设备与状态:logcat_devices(列出设备)、device_info(型号/版本/SDK/分辨率/内存/电量)、device_stats(CPU/内存/电量采样);' +
|
|
42
42
|
'部署与执行:adb_install(本地 APK 装真机)、adb_exec(任意 adb shell 命令)、adb_pull(拉文件)、input_tap/input_swipe/input_text(模拟点击/滑动/输入)、ui_dump(界面层级 XML);' +
|
|
43
43
|
'日志与崩溃:logcat_recent(按级别/关键词/包名读日志)、logcat_crash(崩溃/ANR 捕获+上下文)、logcat_set_package(锁定当前测试包名);' +
|
|
44
|
-
'逆向与内存:proc_list(进程列表)、proc_maps(内存映射+so 模块基址)、proc_status(进程状态/内存摘要)、mem_dump(指定地址读内存 hex)、frida_server(frida-server
|
|
44
|
+
'逆向与内存:proc_list(进程列表)、proc_maps(内存映射+so 模块基址)、proc_status(进程状态/内存摘要)、proc_smaps(内存占用明细 Top 区域)、mem_dump(指定地址读内存 hex)、mem_search(内存搜 hex 模式/字符串)、frida_server(frida-server 部署/启停)、frida_script(hook/trace/scan/bypass/dump 常用脚本模板)。' +
|
|
45
45
|
'工作流建议:构建安卓应用 → 确认设备在线后 adb_install 部署、adb_exec 启动、logcat_set_package 锁包、logcat_recent/logcat_crash 看崩溃、ui_dump+input_* 做界面自动化;' +
|
|
46
46
|
'逆向分析 → proc_list 找进程、proc_maps 拿模块基址、mem_dump 读内存、frida_server 起 frida。' +
|
|
47
47
|
'限制:需设备开启 USB 调试并授权本机;读其他应用内存/maps 需要 root 或 debuggable 应用(run-as);logcat 输出可能含敏感信息;' +
|
|
@@ -1520,6 +1520,284 @@ function fridaServerTool(engine) {
|
|
|
1520
1520
|
})
|
|
1521
1521
|
}
|
|
1522
1522
|
|
|
1523
|
+
/** The mem_search agent tool: search process memory for a byte pattern. */
|
|
1524
|
+
function memSearchTool(engine) {
|
|
1525
|
+
return defineTool({
|
|
1526
|
+
name: 'mem_search',
|
|
1527
|
+
description: 'Search a process\'s memory for a byte pattern — plain ASCII string or hex bytes ("hex:48656c6c6f") — and return byte offsets. ' +
|
|
1528
|
+
'Requires root (or a debuggable app via run-as). Scans with grep -aob over /proc/<pid>/mem; very large processes can be slow.',
|
|
1529
|
+
parameters: {
|
|
1530
|
+
serial: { type: 'string', description: 'Device serial from logcat_devices (optional; defaults to the first attached device).' },
|
|
1531
|
+
pid: { type: 'integer', description: 'Process id (from proc_list).' },
|
|
1532
|
+
pattern: { type: 'string', description: 'Pattern to search: ASCII string ("hello") or hex bytes ("hex:48656c6c6f").' },
|
|
1533
|
+
maxResults: { type: 'integer', description: 'Max matches to return (default 20).' },
|
|
1534
|
+
},
|
|
1535
|
+
output: {
|
|
1536
|
+
schema: {
|
|
1537
|
+
type: 'object',
|
|
1538
|
+
additionalProperties: false,
|
|
1539
|
+
properties: {
|
|
1540
|
+
pid: { type: 'integer', required: true },
|
|
1541
|
+
pattern: { type: 'string' },
|
|
1542
|
+
offsets: { type: 'array', required: true, items: { type: 'integer' } },
|
|
1543
|
+
truncated: { type: 'boolean' },
|
|
1544
|
+
error: { type: 'string' },
|
|
1545
|
+
},
|
|
1546
|
+
},
|
|
1547
|
+
render: (_args, value) => {
|
|
1548
|
+
if (value?.error) return [{ type: 'text', text: value.error }]
|
|
1549
|
+
const offs = value?.offsets ?? []
|
|
1550
|
+
if (offs.length === 0) return [{ type: 'text', text: `(no match for "${value?.pattern ?? ''}" in pid ${value?.pid ?? '?'})` }]
|
|
1551
|
+
const hex = offs.map((o) => '0x' + o.toString(16)).join('\n')
|
|
1552
|
+
return [{ type: 'text', text: `pattern "${value.pattern}" in pid ${value.pid}:\n${hex}${value.truncated ? '\n(truncated)' : ''}` }]
|
|
1553
|
+
},
|
|
1554
|
+
},
|
|
1555
|
+
async execute(args) {
|
|
1556
|
+
const devices = engine.deviceList()
|
|
1557
|
+
const serial = typeof args.serial === 'string' && args.serial !== '' ? args.serial : devices[0]?.serial ?? ''
|
|
1558
|
+
const pid = Number(args.pid)
|
|
1559
|
+
const pattern = typeof args.pattern === 'string' ? args.pattern.trim() : ''
|
|
1560
|
+
const max = Math.min(Math.max(Number(args.maxResults ?? 20) || 20, 1), 200)
|
|
1561
|
+
if (serial === '' || engine.adb === null || !Number.isInteger(pid) || pid <= 0) return { pid: 0, offsets: [], error: 'a valid pid is required' }
|
|
1562
|
+
if (pattern === '') return { pid, offsets: [], error: 'pattern is required (string or hex:...)' }
|
|
1563
|
+
// Normalize to a byte sequence emitted by printf '\xNN\xNN...'
|
|
1564
|
+
const bytes = pattern.startsWith('hex:')
|
|
1565
|
+
? pattern.slice(4).replace(/\s+/g, '').match(/[0-9a-f]{2}/gi)?.map((b) => b.toLowerCase()) ?? []
|
|
1566
|
+
: [...Buffer.from(pattern, 'utf8')].map((b) => b.toString(16).padStart(2, '0'))
|
|
1567
|
+
if (bytes.length === 0) return { pid, offsets: [], error: 'could not parse pattern into bytes' }
|
|
1568
|
+
const printfArg = "\\x" + bytes.join("\\x")
|
|
1569
|
+
const sh = `grep -aob -F "$(printf '${printfArg}')" /proc/${pid}/mem 2>&1 | head -${max}`
|
|
1570
|
+
const result = await runAdbFull(engine.adb, ['-s', serial, 'shell', sh], 30000)
|
|
1571
|
+
const out = result.stdout ?? ''
|
|
1572
|
+
if (!result.ok || /Permission denied|No such file|No such process/.test(out)) {
|
|
1573
|
+
return { pid, offsets: [], error: `cannot search pid ${pid} memory (need root / debuggable app); output: ${out.slice(0, 200)}` }
|
|
1574
|
+
}
|
|
1575
|
+
const offsets = []
|
|
1576
|
+
for (const line of out.split(/\r?\n/)) {
|
|
1577
|
+
const m = /^(\d+):/.exec(line)
|
|
1578
|
+
if (m !== null) offsets.push(Number.parseInt(m[1], 10))
|
|
1579
|
+
}
|
|
1580
|
+
return { pid, pattern, offsets, truncated: offsets.length >= max }
|
|
1581
|
+
},
|
|
1582
|
+
})
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
/** The proc_smaps agent tool: per-region memory detail (top Pss regions). */
|
|
1586
|
+
function procSmapsTool(engine) {
|
|
1587
|
+
return defineTool({
|
|
1588
|
+
name: 'proc_smaps',
|
|
1589
|
+
description: 'Read /proc/<pid>/smaps and return the top memory regions by Pss (size/rss/pss per mapping) — detailed memory footprint analysis. ' +
|
|
1590
|
+
'Requires root or matching permissions.',
|
|
1591
|
+
parameters: {
|
|
1592
|
+
serial: { type: 'string', description: 'Device serial from logcat_devices (optional; defaults to the first attached device).' },
|
|
1593
|
+
pid: { type: 'integer', description: 'Process id (from proc_list).' },
|
|
1594
|
+
top: { type: 'integer', description: 'Number of top regions to return (default 10, max 50).' },
|
|
1595
|
+
},
|
|
1596
|
+
output: {
|
|
1597
|
+
schema: {
|
|
1598
|
+
type: 'object',
|
|
1599
|
+
additionalProperties: false,
|
|
1600
|
+
properties: {
|
|
1601
|
+
pid: { type: 'integer', required: true },
|
|
1602
|
+
regions: {
|
|
1603
|
+
type: 'array',
|
|
1604
|
+
required: true,
|
|
1605
|
+
items: {
|
|
1606
|
+
type: 'object',
|
|
1607
|
+
additionalProperties: false,
|
|
1608
|
+
properties: {
|
|
1609
|
+
start: { type: 'string', required: true },
|
|
1610
|
+
end: { type: 'string' },
|
|
1611
|
+
perms: { type: 'string' },
|
|
1612
|
+
path: { type: 'string' },
|
|
1613
|
+
sizeKb: { type: 'integer' },
|
|
1614
|
+
rssKb: { type: 'integer' },
|
|
1615
|
+
pssKb: { type: 'integer' },
|
|
1616
|
+
},
|
|
1617
|
+
},
|
|
1618
|
+
},
|
|
1619
|
+
error: { type: 'string' },
|
|
1620
|
+
},
|
|
1621
|
+
},
|
|
1622
|
+
render: (_args, value) => {
|
|
1623
|
+
if (value?.error) return [{ type: 'text', text: value.error }]
|
|
1624
|
+
const regs = value?.regions ?? []
|
|
1625
|
+
if (regs.length === 0) return [{ type: 'text', text: `(pid ${value?.pid ?? '?'}: no regions)` }]
|
|
1626
|
+
const lines = regs.map((r) => `${r.start}${r.end ? '-' + r.end : ''} ${(r.perms ?? '').padEnd(4)} pss=${r.pssKb ?? '?'}kB rss=${r.rssKb ?? '?'}kB ${r.path ?? ''}`)
|
|
1627
|
+
return [{ type: 'text', text: lines.join('\n') }]
|
|
1628
|
+
},
|
|
1629
|
+
},
|
|
1630
|
+
async execute(args) {
|
|
1631
|
+
const devices = engine.deviceList()
|
|
1632
|
+
const serial = typeof args.serial === 'string' && args.serial !== '' ? args.serial : devices[0]?.serial ?? ''
|
|
1633
|
+
const pid = Number(args.pid)
|
|
1634
|
+
const top = Math.min(Math.max(Number(args.top ?? 10) || 10, 1), 50)
|
|
1635
|
+
if (serial === '' || engine.adb === null || !Number.isInteger(pid) || pid <= 0) return { pid: 0, regions: [], error: 'a valid pid is required' }
|
|
1636
|
+
const result = await runAdbFull(engine.adb, ['-s', serial, 'shell', `cat /proc/${pid}/smaps 2>&1; true`], 30000)
|
|
1637
|
+
const text = result.stdout ?? ''
|
|
1638
|
+
if (!result.ok || /Permission denied|No such file|No such process/.test(text) || text.trim() === '') {
|
|
1639
|
+
return { pid, regions: [], error: `cannot read /proc/${pid}/smaps (need root or matching perms); output: ${text.slice(0, 200)}` }
|
|
1640
|
+
}
|
|
1641
|
+
const regions = []
|
|
1642
|
+
let current = null
|
|
1643
|
+
for (const line of text.split(/\r?\n/)) {
|
|
1644
|
+
const header = /^([0-9a-f]+)-([0-9a-f]+)\s+([rwxps-]{4})\s+\S+\s+\S+\s+\d+\s*(.*)$/.exec(line)
|
|
1645
|
+
if (header !== null) {
|
|
1646
|
+
current = { start: header[1], end: header[2], perms: header[3], path: header[4] ?? '', sizeKb: 0, rssKb: 0, pssKb: 0 }
|
|
1647
|
+
regions.push(current)
|
|
1648
|
+
continue
|
|
1649
|
+
}
|
|
1650
|
+
if (current === null) continue
|
|
1651
|
+
const field = /^(Size|Rss|Pss):\s+(\d+) kB$/.exec(line.trim())
|
|
1652
|
+
if (field !== null) {
|
|
1653
|
+
if (field[1] === 'Size') current.sizeKb = Number.parseInt(field[2], 10)
|
|
1654
|
+
else if (field[1] === 'Rss') current.rssKb = Number.parseInt(field[2], 10)
|
|
1655
|
+
else current.pssKb = Number.parseInt(field[2], 10)
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
const topRegions = regions
|
|
1659
|
+
.filter((r) => r.pssKb > 0)
|
|
1660
|
+
.sort((a, b) => b.pssKb - a.pssKb)
|
|
1661
|
+
.slice(0, top)
|
|
1662
|
+
.map(({ start, end, perms, path, sizeKb, rssKb, pssKb }) => ({ start, end, perms, path, sizeKb, rssKb, pssKb }))
|
|
1663
|
+
return { pid, regions: topRegions }
|
|
1664
|
+
},
|
|
1665
|
+
})
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
/** frida script templates (reverse engineering quick starts). */
|
|
1669
|
+
const FRIDA_TEMPLATES = {
|
|
1670
|
+
hook_method: (target, method) => `// hook_method: log args + return of a Java method
|
|
1671
|
+
// usage: frida -U -f <package> -l hook.js (or attach to a running app)
|
|
1672
|
+
Java.perform(function () {
|
|
1673
|
+
var clsName = "${target ?? 'com.example.Class'}";
|
|
1674
|
+
var methodName = "${method ?? 'method'}";
|
|
1675
|
+
var cls = Java.use(clsName);
|
|
1676
|
+
cls[methodName].overloads.forEach(function (ov) {
|
|
1677
|
+
ov.implementation = function () {
|
|
1678
|
+
var args = Array.prototype.slice.call(arguments).map(String).join(", ");
|
|
1679
|
+
console.log("[" + clsName + "." + methodName + "] called(" + args + ")");
|
|
1680
|
+
var ret = ov.apply(this, arguments);
|
|
1681
|
+
console.log("[" + clsName + "." + methodName + "] => " + ret);
|
|
1682
|
+
return ret;
|
|
1683
|
+
};
|
|
1684
|
+
});
|
|
1685
|
+
console.log("[*] hooked " + clsName + "." + methodName);
|
|
1686
|
+
});`,
|
|
1687
|
+
trace_native: (target) => `// trace_native: trace a native export, target "libfoo.so!func"
|
|
1688
|
+
var spec = "${target ?? 'libfoo.so!func'}";
|
|
1689
|
+
var sep = spec.indexOf("!");
|
|
1690
|
+
if (sep < 0) { console.log("usage: lib.so!func"); throw 0; }
|
|
1691
|
+
var moduleName = spec.slice(0, sep);
|
|
1692
|
+
var funcName = spec.slice(sep + 1);
|
|
1693
|
+
var base = Module.findBaseAddress(moduleName);
|
|
1694
|
+
if (!base) { console.log("[!] module not loaded: " + moduleName); throw 0; }
|
|
1695
|
+
var addr = Module.findExportByName(moduleName, funcName);
|
|
1696
|
+
if (!addr) { console.log("[!] export not found: " + funcName); throw 0; }
|
|
1697
|
+
Interceptor.attach(addr, {
|
|
1698
|
+
onEnter: function (args) {
|
|
1699
|
+
var a = [];
|
|
1700
|
+
for (var i = 0; i < 8; i++) a.push(args[i]);
|
|
1701
|
+
console.log("[+] " + funcName + "(" + a.join(", ") + ")");
|
|
1702
|
+
},
|
|
1703
|
+
onLeave: function (retval) { console.log("[+] " + funcName + " => " + retval); }
|
|
1704
|
+
});
|
|
1705
|
+
console.log("[*] tracing " + spec + " @ " + addr);`,
|
|
1706
|
+
scan_memory: (target) => `// scan_memory: scan all modules for a hex pattern ("hex:48 65 6c 6c 6f" or raw hex)
|
|
1707
|
+
var pattern = "${target ?? 'hex:48 65 6c 6c 6f'}";
|
|
1708
|
+
if (pattern.indexOf("hex:") === 0) pattern = pattern.slice(4);
|
|
1709
|
+
var bytes = pattern.split(/\\s+/).filter(Boolean).map(function (b) { return parseInt(b, 16); });
|
|
1710
|
+
if (bytes.some(function (b) { return isNaN(b); })) { console.log("[!] bad hex pattern"); throw 0; }
|
|
1711
|
+
Process.enumerateModules().forEach(function (m) {
|
|
1712
|
+
try {
|
|
1713
|
+
Memory.scan(m.base, m.size, bytes, {
|
|
1714
|
+
onMatch: function (addr, size) {
|
|
1715
|
+
console.log("found @ " + addr + " in " + m.name + " (+0x" + addr.sub(m.base).toString(16) + ")");
|
|
1716
|
+
},
|
|
1717
|
+
onComplete: function () {}
|
|
1718
|
+
});
|
|
1719
|
+
} catch (e) {}
|
|
1720
|
+
});
|
|
1721
|
+
console.log("[*] scanning " + Process.enumerateModules().length + " modules");`,
|
|
1722
|
+
bypass_debug: () => `// bypass_debug: basic anti-debug / anti-frida helpers
|
|
1723
|
+
// 1) observe ptrace (self-attach tricks), 2) hide common frida markers
|
|
1724
|
+
var ptrace = Module.findExportByName(null, "ptrace");
|
|
1725
|
+
if (ptrace) {
|
|
1726
|
+
Interceptor.attach(ptrace, {
|
|
1727
|
+
onEnter: function (args) { console.log("[*] ptrace called, request=" + args[0]); },
|
|
1728
|
+
onLeave: function (retval) { if (retval.toInt32() === -1) console.log("[*] ptrace denied (anti-debug active)"); }
|
|
1729
|
+
});
|
|
1730
|
+
}
|
|
1731
|
+
try {
|
|
1732
|
+
Java.perform(function () {
|
|
1733
|
+
// neutralize common "is frida running" checks by spamming hooks is out of scope;
|
|
1734
|
+
// this template focuses on ptrace observation.
|
|
1735
|
+
});
|
|
1736
|
+
} catch (e) {}
|
|
1737
|
+
console.log("[*] basic anti-debug helpers installed (ptrace observer)");`,
|
|
1738
|
+
dump_class: (target) => `// dump_class: list methods and fields of a Java class
|
|
1739
|
+
Java.perform(function () {
|
|
1740
|
+
var clsName = "${target ?? 'com.example.Class'}";
|
|
1741
|
+
var cls = Java.use(clsName);
|
|
1742
|
+
console.log("[*] " + clsName + " methods:");
|
|
1743
|
+
cls.class.getDeclaredMethods().forEach(function (m) { console.log(" " + m.toString()); });
|
|
1744
|
+
console.log("[*] fields:");
|
|
1745
|
+
cls.class.getDeclaredFields().forEach(function (f) { console.log(" " + f.toString()); });
|
|
1746
|
+
});`,
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
/** The frida_script agent tool: generate ready-to-use frida scripts. */
|
|
1750
|
+
function fridaScriptTool(engine) {
|
|
1751
|
+
return defineTool({
|
|
1752
|
+
name: 'frida_script',
|
|
1753
|
+
description: 'Generate a ready-to-use frida JavaScript script from a built-in template (hook_method / trace_native / scan_memory / bypass_debug / dump_class) ' +
|
|
1754
|
+
'and optionally write it to a local file, then use with frida_server + "frida -U -f <pkg> -l <script>".',
|
|
1755
|
+
parameters: {
|
|
1756
|
+
template: { type: 'string', enum: ['hook_method', 'trace_native', 'scan_memory', 'bypass_debug', 'dump_class'], description: 'Which template to generate.' },
|
|
1757
|
+
target: { type: 'string', description: 'Template-specific target: class name (hook_method/dump_class), "lib.so!func" (trace_native), hex pattern (scan_memory).' },
|
|
1758
|
+
method: { type: 'string', description: 'Method name for hook_method (optional; hooks the given method of the target class).' },
|
|
1759
|
+
output: { type: 'string', description: 'Optional local file path to write the script to, e.g. "F:/tools/hook.js".' },
|
|
1760
|
+
},
|
|
1761
|
+
output: {
|
|
1762
|
+
schema: {
|
|
1763
|
+
type: 'object',
|
|
1764
|
+
additionalProperties: false,
|
|
1765
|
+
properties: {
|
|
1766
|
+
script: { type: 'string', required: true },
|
|
1767
|
+
wroteTo: { type: 'string' },
|
|
1768
|
+
error: { type: 'string' },
|
|
1769
|
+
},
|
|
1770
|
+
},
|
|
1771
|
+
render: (_args, value) => {
|
|
1772
|
+
if (value?.error) return [{ type: 'text', text: value.error }]
|
|
1773
|
+
return [{ type: 'text', text: value?.script ?? '' }]
|
|
1774
|
+
},
|
|
1775
|
+
},
|
|
1776
|
+
async execute(args) {
|
|
1777
|
+
const template = typeof args.template === 'string' ? args.template : ''
|
|
1778
|
+
const target = typeof args.target === 'string' ? args.target.trim() : ''
|
|
1779
|
+
const method = typeof args.method === 'string' ? args.method.trim() : ''
|
|
1780
|
+
const generator = FRIDA_TEMPLATES[template]
|
|
1781
|
+
if (generator === undefined) {
|
|
1782
|
+
return { script: '', error: `unknown template: ${template}; pick one of ${Object.keys(FRIDA_TEMPLATES).join(' / ')}` }
|
|
1783
|
+
}
|
|
1784
|
+
const script = generator(target || undefined, method || undefined)
|
|
1785
|
+
const output = typeof args.output === 'string' ? args.output.trim() : ''
|
|
1786
|
+
if (output !== '') {
|
|
1787
|
+
try {
|
|
1788
|
+
const { writeFileSync, mkdirSync } = await import('node:fs')
|
|
1789
|
+
mkdirSync(dirname(output), { recursive: true })
|
|
1790
|
+
writeFileSync(output, script, 'utf8')
|
|
1791
|
+
return { script, wroteTo: output }
|
|
1792
|
+
} catch (error) {
|
|
1793
|
+
return { script, error: `failed to write ${output}: ${error instanceof Error ? error.message : String(error)}` }
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
return { script }
|
|
1797
|
+
},
|
|
1798
|
+
})
|
|
1799
|
+
}
|
|
1800
|
+
|
|
1523
1801
|
/** ------------------------------------------------------------------ */
|
|
1524
1802
|
|
|
1525
1803
|
/** Mount the adb engine, routes, tool, and announcement. */
|
|
@@ -1603,6 +1881,9 @@ export function apply(ctx, config) {
|
|
|
1603
1881
|
procStatusTool(engine),
|
|
1604
1882
|
memDumpTool(engine),
|
|
1605
1883
|
fridaServerTool(engine),
|
|
1884
|
+
memSearchTool(engine),
|
|
1885
|
+
procSmapsTool(engine),
|
|
1886
|
+
fridaScriptTool(engine),
|
|
1606
1887
|
].map((tool) => ctx.tools.register(tool))
|
|
1607
1888
|
return () => { for (const dispose of disposers) dispose() }
|
|
1608
1889
|
}, 'dsh-logcat: tools')
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@windypro-rourou/dsh-logcat",
|
|
3
3
|
"description": "Android Logcat viewer for the dsh web GUI: auto-connects to any adb device in debug mode, live logcat stream with level/keyword filters, pause/clear/export, plus agent tools (logcat_recent). Hot-pluggable — mounted via ~/.dsh/cordis.patch.yml + a profile node_modules copy, no dsh source changes.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.1",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"packageManager": "pnpm@11.22.0",
|
|
7
7
|
"engines": {
|