3

问题:反混淆获取调用堆栈打字稿从模糊JavaScript代码

我有记录从一个服务器上的文件,其中包含从抛出的错误,引发该日志文件的创建调用堆栈。服务器应用程序使用nodejs编写在typescript中,但是将gest转换为javascript,并且javascript代码使用google闭包编译器进行混淆处理。现在我的调用堆很难解释,我试图通过去混淆js代码来改变,使用闭包编译器创建的源映射,然后再次使用源映射,将js调用堆“untranspile”到打字稿调用堆。

我的局限性

我访问源的地图,源代码(TS和js)和混淆代码,但我不能更改代码本身,因此即时通讯坚持与当前调用堆栈。我也可以访问所有选项以及混淆代码的代码/工具,因此我可能会将某些需要的信息存储在文件中(信息未显示在源映射中),就像其他映射一样。

思路和尝试

第一次尝试是简单地interprete源的地图和与该信息反混淆调用堆栈(deobfuscating是最困难的部分),但试图了解CC创建源的方式后,我地图我有一些问题: cc不只是将一个名称映射到另一个名称,因为他多次重复使用某些名称(如a,f或这些“名称”)。因此,可能会有一些函数带有一些匿名函数或嵌套函数,其中名称f被多次使用,但由于范围而在每个上下文中具有不同的含义。

下一个想法是简单地相信调用堆栈。明白我的意思是,你必须了解(如果我了解,正确的),并抄送如何创建和管理映射:

   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; 
} 

在此MAPP唯一重要信息ing实例是列和名称。一些映射包含其他名称不是。那些不包含名字的名字被用来为那些有名字的人建立一些范围,以便找出名字/替换名字开始和结束的地方(索引)。

使用上述两种说法这个逻辑的一个例子:

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和具有不良的打字稿文档/分型,但它显然是写在打字稿。这也会导致在本地读取文件时不支持,因为它们总是被xmlhttprequests调用。这个部分有一些解决方法,但是模块非常复杂(可能是由于被编译的代码),还有其他部分也不支持我,使用本地文件。它只是太重写/改变它与nodejs正常工作....

你知道一个更干净的方式与模块做?我还想过使用源代码解析器来获取更多的上下文以支持源地图(在那些恶意的.call方法的情况下)。也许我可以写我自己的源代码解析器,如果有一个文档的所有例外,我不得不注意当解析代码和解释它... 也许有另一种方式,我目前正在监督...

+0

错误如此难以重现,您无法加载未混淆的版本,在那里生成并调试错误?这将是一个简单的方法,至少这一次。 – ASDFGerte

+0

现在你的问题看起来像寻找建议,这是脱离主题。让我们来更具体一点。你能用'.ts'文件中的几行代码重现问题吗?你使用哪个tsc标志?你使用哪个cc标志?输出文件是什么样的?最后,你没有得到什么预期的行为。 – styfle

回答

2

组合源地图

首先,确保您有完整的源地图。您提到了两个生成源映射的工具,即打字稿编译器和封闭编译器。封闭编译器是否提供了输入源地图?如果是这样,它会输出一个提到原始文件的源地图。如果没有,你会有两倍的工作为你裁减。在事实之后可以使用source-map package来组成源地图。

正确理解源地图

这是从你原来的问题,你不完全了解源地图清晰。例如,没有name的条目通常是语言语义。例如:

document.createElement('div') 

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

可视化工具

有很多可视化工具可以帮助您。我的一些最喜欢的是:

这里的想法是,你加载源地图和资源的工具,然后单击四周,看看事情如何映射。它需要一点点拨动,但是您应该能够在原始来源中找到与堆栈跟踪中的行/列信息匹配的行和列。

自动化进程

工具等存在的一个原因https://sentry.io/。它会自动为您解除调用堆栈。

+0

当你说闭包编译器可以提供源地图作为输入时,你的意思是这个函数是内置在cc中的吗?或者更像是一个插件或自行编写的代码?因为我无法找到,如何输入文件,以便像你提到的那样整合它们(https://github.com/google/closure-compiler/wiki/Source-Maps在这里查看)。 –

+0

它是内置的。请参阅'--source_map_input'标志。最好的文档是使用'--help'标志来查看它。 –

+0

我的理解是,有了这个标志,CC确实可以读取源地图,但不会将其集成到新的源地图中,而只是将其用于CC生成的自己的错误输出 –