从混淆的 javascript 代码中获取去混淆的打字稿调用堆栈

Get deobfuscated typescript callstack from obfuscated javascript code

问题:

我有来自服务器的日志文件,其中包含触发创建此日志文件的抛出错误的调用堆栈。服务器应用程序是用 nodejs 的打字稿编写的,但 gest 被转换为 javascript,并且 javascript 代码被 google 闭包编译器混淆了。现在我的调用堆栈很难解释,我试图通过使用闭包编译器创建的源映射对 js 代码进行反混淆来改变它,然后再次使用源映射,"untranspile" js 调用堆栈到 typescript 调用堆栈.

我的限制

我可以访问源映射、源代码(ts 和 js)和混淆代码,但我无法更改代码本身,所以我只能使用当前的调用堆栈。我还可以访问所有选项和混淆代码的 code/tool,所以也许我将一些需要的信息存储在文件中(源映射中未显示的信息),如附加映射。

想法与尝试

第一次尝试是简单地解释源映射并使用该信息对调用堆栈进行去混淆处理(去混淆是困难的部分),但是在尝试理解 cc 创建源映射的方式后我遇到了一些问题: cc 不只是将一个名称映射到另一个名称,因为他多次重复使用某些名称(如 a、f 或此类 "names")。所以可能有一个函数有一些匿名函数或嵌套函数,其中名称 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;
}

此映射实例中唯一重要的信息是列和名称。有些映射包含名称,有些则不包含。那些不包含名称的用于围绕有名称的那些构建某种范围,以便找出 name/replaced 名称开始和结束的位置(索引)。

使用上述两个语句的此逻辑示例:

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            │   ━━━┛

使用此调用堆栈,我想解决 applications.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)

现在使用 sourcemap 中的信息很容易获得原始行和列,但名称不是。我尝试先尝试在没有代码信息的情况下,通过准备好前一个位置(行和列)来推断下一行的名称。

因此,如果我想解析 f,我会查看它被调用的位置 (285:18),然后在源映射中查找它,在那里我会找到它的名称。但是对于这个过程,我总是需要知道它在哪里被调用。现在这就是问题所在。因为如果函数存储在一个变量中,或者是匿名的或类似的东西,我就有问题了。

f.call(d, a[0], a[1]);

我还注意到,在此上下文中调用的某些方法没有在调用堆栈中列出,这是另一个问题。所以现在我至少可以解析名称,如果我可以确定我是否知道它们在哪里被调用以及它们是否在调用堆栈中。但是我没有这样的一半解决方案。

我的第二次尝试是使用我发现的一个很有前途的 javascript 模块:stacktrace-js

虽然这个模块是为浏览器 js 制作的,但打字稿很差 documentation/typings,尽管它显然是用打字稿写的。这也导致几乎不支持在本地读取文件,因为它们总是通过 xmlhttprequests 被调用。该部分有一些解决方法,但该模块非常复杂(可能是由于被转换的代码)以至于其他部分也不支持我使用本地文件。 rewrite/change 它无法正确使用 nodejs ....

你知道用模块做这件事的更干净的方法吗?我还考虑过使用源代码解析器来获取更多上下文以支持源映射(以防那些恶意的 .call 方法)。也许我可以编写自己的源代码解析器,如果有所有异常的文档,我在解析代码和解释它时必须注意...... 也许还有另一种方法,我目前正在监督...

合成源地图

首先,确保你有一个完整的源映射。您提到了两个生成源映射的工具,打字稿编译器和闭包编译器。闭包编译器是否提供了输入源映射?如果是这样,它将输出一个提到原始文件的源映射。否则,您将面临双倍的工作量。事后可以使用 source-map package 编写源映射。

正确理解 Source Maps

从您最初的问题可以清楚地看出您没有完全理解源地图。例如,没有 name 的条目通常是语言语义。例如:

document.createElement('div')

源映射可以包含 documentcreateElement 的映射,也可以包含 .( 字符的映射。这里不涉及范围

可视化工具

这里有多种可视化工具可以提供帮助。我最喜欢的一些是:

这里的想法是在工具中加载源映射和源,然后四处单击以查看映射方式。这需要花点功夫,但您应该能够在原始来源中找到与堆栈跟踪中的 line/col 信息相匹配的行和列。

自动化流程

https://sentry.io/ 等工具的存在是有原因的。它会自动为你去混淆调用堆栈。