从混淆的javascript代码

时间:2017-08-11 15:00:58

标签: javascript node.js google-closure-compiler callstack deobfuscation

问题:

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

我的限制

我可以访问源映射,源代码(ts和js)以及混淆代码,但我无法更改代码本身,因此我坚持使用当前的callstacks。我还可以访问所有选项以及混淆代码的代码/工具,因此我可能会将一些所需信息存储在一个文件中(源图中未显示的信息),如其他映射。

想法和尝试

第一次尝试是简单地解释源映射,并使用该信息反向混淆callstack(反混淆是困难的部分),但是在我试图理解cc创建源映射的方式后,我遇到了一些问题: cc并不只是将一个名称映射到另一个名称,因为他多次重复使用某些名称(例如a,f或这些"名称")。因此,可能存在具有一些匿名函数或嵌套函数的函数,其中名称f被多次使用,但由于范围的原因,在每个上下文中具有不同的含义。

下一个想法只是信任callstack。要理解我的意思,你必须理解(如果我理解正确的话)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            │   ━━━┛

使用此callstack,我想解决来自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)

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

所以,如果我想解决f,我会看看它被调用的地方(285:18),然后在源地图中查找,我会找到它的名字。但是对于这个过程,我始终需要知道它的调用位置。现在就是问题所在。因为如果函数已经存储在变量中,或者是匿名的或类似的东西,我就有问题。

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

我也注意到,在这种情况下调用某些方法并不会在callstack中列出,这是另一个问题。所以现在我可以至少解析名称,如果我能确定我是否知道他们被叫到哪里以及他们是否在callstack中。但我不是这样的半解决方案。

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

这个模块是为浏览器js而制作的,并且打字稿文件/打字很差,虽然它是用打字稿写的。这也导致在本地不支持读取文件,因为它们总是被xmlhttprequests调用。该部分有一些变通方法,但模块非常复杂(可能是由于被转换的代码),还有其他部分也不支持我,使用本地文件。重写/改变它以适当地使用nodejs就太多了....

您是否知道使用该模块更简洁的方法?我还想过使用源代码解析器获得更多上下文来支持源映射(如果是那些恶意的.call方法)。也许我可以编写自己的源代码解析器,如果在解析代码和解释代码时我必须注意的所有异常的文档...... 也许还有另一种方式,我目前正在监督......

1 个答案:

答案 0 :(得分:2)

组合源地图

首先,确保您拥有完全合成的源地图。您提到了两个生成源映射的工具,typescript编译器和闭包编译器。闭包编译器提供了输入源映射吗?如果是这样,它将输出提取原始文件的源地图。如果没有,你将有两倍的工作量。事后可以使用source-map package组成源地图。

正确理解源地图

从原始问题中可以清楚地看出,您并未完全了解源地图。例如,没有name的条目通常是语言语义。例如:

document.createElement('div')

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

可视化工具

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

这里的想法是您在工具中加载源地图和源,然后单击以查看事物的映射方式。它需要一点点,但你应该能够在原始源中找到与堆栈跟踪中的行/列信息匹配的行和列。

自动化流程

https://sentry.io/这样的工具存在是有原因的。它会自动为您调用一个调用堆栈。