问题:
我有来自服务器的日志文件,其中包含引发错误的调用栈,这些文件触发了此日志文件的创建。服务器应用程序是用带有Node.js的Typescript编写的,但gest已转译为javascript,而javascript则被Google Closure编译器混淆了。现在,我的调用栈很难解释,我试图通过使用闭包编译器创建的源映射对js代码进行模糊处理来进行更改,然后再次使用源映射将js调用栈“反转换”为 typescript 调用栈。
我的局限性
我可以访问源映射,源代码(ts和js)以及混淆的代码,但是我无法更改代码本身,因此无法使用当前的调用堆栈。我还可以访问所有选项以及混淆代码的代码/工具,因此也许我将一些所需的信息(如其他映射)存储在文件中(源映射中未提供的信息)。
想法和尝试
最初的尝试是简单地解释源映射,并使用该信息对调用堆栈进行模糊处理(去混淆是最困难的部分),但是在尝试理解cc创建源映射的方式后,我遇到了一些问题:
cc不仅将一个名称映射到另一个名称,因为他多次重复使用某些名称(例如a,f或这类“名称”)。因此,可能存在一个带有某些匿名函数或嵌套函数的函数,其中名称f被使用了几次,但由于范围的原因在每个上下文中都有不同的含义。
下一个想法就是简单地信任调用栈。要理解我的意思,您必须了解(如果我理解正确的话)cc如何创建和管理映射:
return method.call(thisObj, args[0], args[1]);
这行混淆了(我离开了空格以更好地理解索引):
return f.call(d, a[0], a[1]);
现在为该单行创建了多个映射,单个映射如下所示:
export interface MappingItem {
source: string;
generatedLine: number;
generatedColumn: number;
originalLine: number;
originalColumn: number;
name: string | null;
}
此映射实例中唯一重要的信息是列和名称。一些映射包含一个名称,而其他则不包含。不包含名称的名称用于围绕具有名称的名称建立某种范围,以便找出名称/替换名称的起始和终止位置(索引)。
使用上面两个语句的逻辑示例:
Generated │ Original │ Name │ Scope
0 │ 16 │ null │ ━━━┓
15 │ 23 │ method │ x │
16 │ 23 │ call │ x │
21 │ 23 │ null │ ━┓ │
22 │ 35 │ thisObject │ x│ │
23 │ 23 │ null │ ━┛ │
25 │ 44 │ args │ x │
26 │ 44 │ null │ ━┓ │
27 │ 49 │ null │ ?│ │
28 │ 44 │ null │ ━┛ │
29 │ 23 │ null │ ━━┓│
31 │ 53 │ args │ x ││
32 │ 53 │ null │ ━┓││
33 │ 58 │ null │ ?│││
34 │ 53 │ null │ ━┛││
35 │ 23 │ null │ ━━┛│
36 │ 16 │ null │ ━━━┛
使用此调用堆栈,我想从application.js解析所有内容。所有经过编译和混淆的js代码都在其中。休息无关紧要:
at do2 (c:\Users\me\test\js\test.js:14:11)
at do1 (c:\Users\me\test\js\test.js:11:5)
at Server.<anonymous> (c:\Users\me\test\js\test.js:6:5)
at f (c:\Users\me\build\transpiled\obfuscated\application.js:235:18)
at Object.a.safeInvoke (c:\Users\me\build\transpiled\obfuscated\application.js:285:27)
at Server.g.getWrappedListener (c:\Users\me\build\transpiled\obfuscated\application.js:3313:17)
at emitTwo (events.js:106:13)
at Server.emit (events.js:191:7)
at HTTPParser.parserOnIncoming [as onIncoming] (_http_server.js:546:12)
at HTTPParser.parserOnHeadersComplete (_http_common.js:99:23)
现在使用源 map 中的信息,很容易获得原始的行和列,但名称却不然。我试图先尝试从代码中获取任何信息,方法是准备将前一个位置(行和列)推断为下一行的名称。
因此,如果我想解析f,我会查看它的调用位置(285:18),然后在源映射中查找它的名称。但是对于这个过程,我总是需要知道它在哪里被调用。现在,那就是问题所在。因为如果函数将被存储在变量中,或者将是匿名的或类似的东西,我就会遇到问题。
f.call(d, a[0], a[1]);
我还注意到,在这种情况下,某些方法(如call)没有在调用堆栈中列出,这是另一个问题。因此,如果我可以确定自己是否知道调用的位置以及它们是否在调用堆栈中,那么现在我可以至少解析名称。但是我没有像这样的半解法。
我的第二次尝试是使用发现的一个有前途的javascript模块:stacktrace-js
该模块虽然是为浏览器js编写的,但 typescript 的文档/类型很差,尽管它显然是用 typescript 编写的。这也导致几乎不支持本地读取文件,因为它们总是使用xmlhttprequests来调用。该部分有一些解决方法,但是该模块非常复杂(可能是由于代码被转译),以至于还有其他部分不支持我,使用本地文件。要重写/更改它以使其能正确地与nodejs配合使用,实在太多了。
您是否知道使用该模块更干净的方法?我还考虑过使用源代码解析器来获取更多上下文来支持源映射(在那些恶毒的.call方法的情况下)。如果有文档说明我在解析和解释代码时要注意的所有异常,也许我可以编写自己的源代码解析器...
也许我目前监督的另一种方式是...
最佳答案
组合源 map
首先,请确保您具有完整的源 map 。您提到了两种生成源映射的工具,即 typescript 编译器和闭包编译器。封闭编译器是否提供了输入源映射?如果是这样,它将输出提及原始文件的源映射。如果没有,您将获得两倍的工作量。事实发生之后,可以使用source-map package组成源 map 。
正确理解源 map
从您最初的问题很明显,您还不完全了解源 map 。例如,没有name
的条目通常是语言语义。例如:
document.createElement('div')
源映射可以包含
document
和createElement
的映射,还可以包含.
和(
字符的映射。这里没有涉及范围。可视化工具
这里有多种可视化工具可以为您提供帮助。我最喜欢的一些是:
这里的想法是您将源 map 和源加载到工具中,然后单击以查看事物的映射方式。这需要花点时间,但是您应该能够在原始源中找到与堆栈跟踪中的行/列信息相匹配的行和列。
流程自动化
之所以存在https://sentry.io/之类的工具是有原因的。它将自动为您消除混淆。
关于javascript - 从混淆的javascript代码获取混淆的 typescript 调用堆栈,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/45638270/