JsSSA与网络请求解析
2024-2-22 15:35:41 Author: mp.weixin.qq.com(查看原文) 阅读量:4 收藏

前言

当想对浏览器某个页面的的内容进行分析时,通常会进行一个网页跳转的HTTP请求并对服务器响应进行拦截,并获取网页内容,再解析对应的Js代码和Js文件。webpack处理后的js文件中有着各种网络请求。是否有什么方法,能够简单的分析出这些在机器压缩后呈现出的难读的JS代码中的网络请求?

这时就是SSA再次出场的时候了,现在Yakit新增SSA模块解析JavaScript语言,可以使用用JsSSA来实现上述的功能,对webpack打包后的JS中网络请求解析。

JsSSA能做什么

JavaScript 具有非常棒的模块和方法,可以用来建立可从服务器端资源发送或接收数据的 HTTP 请求。那么如果我们想从JS中整理出这些请求的具体的调用链以及方法中的参数呢?

对JavaScript进行支持并转换成SSA形式,并对JsSSA的调用链进行分析,可以帮助我们从Js网络请求中找出我们想要的东西。

为什么是SSA而不是AST?

AST——即抽象语法树也可以呈现出程序流程和分析,但为什么不用它?

答案显而易见:效率过低,复杂度高

给一段简单的代码作为例子:

a = ajax.send0a("1111")a = () => {}a("2222")

这段代码会生成这样一棵抽象语法树:

如果想去寻找“a”的调用,那么每次都要从program这根节点开始搜寻,进入树的分支,再从头开始找,循环往复……

有时可能需要找某种类型的“a”的调用,亦或者这种类型调用的参数,类型和赋值甚至还在两个分支里……

仅仅四行代码的消耗已经可见搜寻AST树的效率之低,代码之复杂,做这样一个难用又难做的东西,实属是不可行。

相比较于如此复杂的AST,SSA显得就先进非常多了:

在SSA中,每个量都是一个value,它有user和def,即使用它的量和它的定义等,包括参数,类型等等的属性。利用每个量维护的一个value结构,可以轻松找到某个量的调用链,它的类型,使用者以及位置,任何你想要的信息都可以在SSA中维护,而不用在AST中一遍遍地遍历整颗的树还难以找到对应了。

由此,SSA形式是分析的不二之选。

JS网络请求形式

以最常见的建立异步HTTP的请求的方式Ajax为例,该方法可以使用 HTTP-POST 方法来发送数据,以及使用 HTTP-GET 来接收数据。

要在 Ajax 中发起一个 HTTP 调用,需要初始化一个新的XMLHttpRequest()方法,指定 URL 端点和 HTTP 方法。最后,使用open()方法将两者结合起来,并调用send()方法执行请求。

if (window.ActiveXObject) {    ajax = newActiveXObject("Microsoft.XMLHTTP")}else{    ajax = new XMLHttpRequest0;}ajax.open('post', "ajax_link.php?id=1&t=" + Math.random, false)ajax.send()

这段代码中使用了GET方法来对baidu网站构建了请求,并使用send发送。如果想知道JS文件中所有的XMLHttpRequest()都请求了什么url,使用了什么方法,这时候就是JsSSA模块使用的时候了。

有哪些API来支持分析呢

目前API功能仍需要完善,但已经有了基础的功能,可以实现较为简单的解析和查看相关的调用链。

使用Parse对代码进行解析

得到ssaapi.Program对象

对整个code的解析只有一个函数Parse,传入code参数以及其他可选参数进行解析:

ssa.Parse(code /*type: string*/, opts...) (*ssaapi.Program, error)opt:    ssa.withLanguage:        arg:             ssa.Javascript            ssa.Yak (default)

将需要处理的代码进行解析:

prog := ssa.Parse(`if (window.ActiveXObject) {    ajax = newActiveXObject("Microsoft.XMLHTTP")}else{    ajax = new XMLHttpRequest0;}ajax.open('post', "ajax_link.php?id=1&t=" + Math.random, false)ajax.send()`, ssa.withLanguage(ssa.Javascript))~

对ssaapi.Program使用Ref获取变量

使用Ref来对某个对象进行追踪,获取一个数组。可以通过ShowShowWithSource获取数组信息。

ajax = prog.Ref("ajax").Show()/* Values: 3        0:  Call: newActiveXObject("Microsoft.XMLHTTP")        1:  Call: XMLHttpRequest0()        2:   Phi: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()]*/

可以看到获取到的数据有三个,其中有两个函数调用,分别是if语句中的两个分支。

在运行时程序将会运行If中的一个分支,也就具体为某一个值,但是在静态分析中,我们通过Phi来表示多个数据的聚合。可以看到在以上的代码中If运行结束以后,ajax会成为newActiveXObject("Microsoft.XMLHTTP")new XMLHttpRequest0, 也就表示为phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()]

对ssaapi.Values的操作:

使用ForEach遍历

使用ForEach可以遍历整个值的数组,并获取每一个值。并使用ShowUseDefChain可以获取值的一层的引用关系:

prog := ssa.Parse(`if (window.ActiveXObject) {    ajax = newActiveXObject("Microsoft.XMLHTTP")}else{    ajax = new XMLHttpRequest0;}ajax.open('post', "ajax_link.php?id=1&t=" + Math.random, false)ajax.send()`, ssa.withLanguage(ssa.Javascript))~
ajax = prog.Ref("ajax").ForEach( v => v.ShowUseDefChain())

查看open/send0方法的usedefchain:

可以查看通过ref获取的变量值,每个会单独打印一个表格,每一行表示一个相关的值,其中每行包含以下信息:

  • 该值的类型,表示为 Self表示Ref获取到的值本身,Operand表示Self使用的值,User表示使用Self的值,

  • 该值的索引,任何一个值都可以通过GetOperand(index)GetUser(index)获取到指定的值,参数中的index和此处表示的索引一致。

  • 该值的Opcode,可以理解为值的行为。常见的如 函数调用Call, 数值运算BinOp等。比如,每个值可以通过v.IsCall()判断是否为函数调用。

  • 该值的单行打印,将会把整个值打印为一行。

比如以上代码中运行以后的数值如下,相关解释已经在注释中:

use-def: |Type  |index  |Opcode |Value        Operand 0       Undefined       newActiveXObject        Operand 1       ConstInst       "Microsoft.XMLHTTP"        Self            Call    newActiveXObject("Microsoft.XMLHTTP")        User    0       Phi     phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()]
use-def: |Type |index |Opcode |Value Operand 0 Undefined XMLHttpRequest0 Self Call XMLHttpRequest0() User 0 Phi phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()]
use-def: |Type |index |Opcode |Value Operand 0 Call newActiveXObject("Microsoft.XMLHTTP") Operand 1 Call XMLHttpRequest0() Self Phi phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()] // phi是出现数据交汇时产生的值,表示在运行时将会得到Phi中显示的所有值中的一个。 User 0 Field phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].open User 1 Field phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].send

使用GetUsers获取使用者

对于ForEach中的单个Value可以使用GetUsers可以获取所有的User返回一个value数组。

对于value数组也可以使用GetUsers,对其中每个值进行GetUsers并返回所有的User。

比如一下代码,可以参考ShowUseDefChain后的数据进行比对。

prog := ssa.Parse(`if (window.ActiveXObject) {    ajax = newActiveXObject("Microsoft.XMLHTTP")}else{    ajax = new XMLHttpRequest0;}ajax.open('post', "ajax_link.php?id=1&t=" + Math.random, false)ajax.send()`, ssa.withLanguage(ssa.Javascript))~
ajax = prog.Ref("ajax").Show()/* Values: 3 0: Call: newActiveXObject("Microsoft.XMLHTTP") 1: Call: XMLHttpRequest0() 2: Phi: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()]*/ajaxUser = ajax.GetUsers().Show()/*Values: 4 0: Phi: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()] 1: Phi: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()] 2: Field: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].open 3: Field: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].send */

使用Filter过滤值

Filter可以对值的数组进行过滤,该函数类似ForEach函数,但将会返回一个bool类型,以判断当前值是否继续保存。比如以下的示例,将会只留下Field类型的数据。

prog := ssa.Parse(`if (window.ActiveXObject) {    ajax = newActiveXObject("Microsoft.XMLHTTP")}else{    ajax = new XMLHttpRequest0;}ajax.open('post', "ajax_link.php?id=1&t=" + Math.random, false)ajax.send()`, ssa.withLanguage(ssa.Javascript))~
ajax = prog.Ref("ajax").Show()/* Values: 3 0: Call: newActiveXObject("Microsoft.XMLHTTP") 1: Call: XMLHttpRequest0() 2: Phi: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()]*/ajaxUser = ajax.GetUsers().Show()/*Values: 4 0: Phi: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()] 1: Phi: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()] 2: Field: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].open 3: Field: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].send */ajaxFunc = ajaxUser.Filter(v => v.IsField()).Show()/*Values: 2 0: Field: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].open 1: Field: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].send
*/

结论

目前Yakit对JsSSA模块的功能和接口支持正在完善中,最终去实现一个能够分析出各类Js网络请求,基于SSA来从分析调用链出发,得到一种类似网络爬虫的解析内容的网络请求分析模块。

最后分析代码如下,首先获取ajax,获取使用者并过滤Field,也就是获得ajax.openajax.send, 继续获取使用者并过滤Call,也就得到了对于两个函数的调用,直接打印函数以及参数信息。

prog := ssa.Parse(`if (window.ActiveXObject) {    ajax = newActiveXObject("Microsoft.XMLHTTP")}else{    ajax = new XMLHttpRequest0;}ajax.open('post', "ajax_link.php?id=1&t=" + Math.random, false)ajax.send()`, ssa.withLanguage(ssa.Javascript))~
ajax = prog.Ref("ajax").Show()/* Values: 3 0: Call: newActiveXObject("Microsoft.XMLHTTP") 1: Call: XMLHttpRequest0() 2: Phi: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()]*/ajaxUser = ajax.GetUsers().Show()/*Values: 4 0: Phi: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()] 1: Phi: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()] 2: Field: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].open 3: Field: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].send */ajaxFunc = ajaxUser.Filter(v => v.IsField()).Show()/*Values: 2 0: Field: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].open 1: Field: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].send */ajaxFuncCaller = ajaxFunc.GetUsers().Filter(v => v.IsCall()).Show()/*Values: 2 0: Call: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].open("post",add("ajax_link.php?id=1&t=", Math.random),false) 1: Call: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].send() */ajaxFuncCaller.ForEach(v =>{ printf("func: %s\n", v) v.GetCallArgs().ForEach(v =>{ printf("\targument: %s\n", v) })})/*func: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].open("post",add("ajax_link.php?id=1&t=", Math.random),false) argument: "post" argument: add("ajax_link.php?id=1&t=", Math.random) argument: falsefunc: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].send() */

END

  YAK官方资源 

Yak 语言官方教程:
https://yaklang.com/docs/intro/
Yakit 视频教程:
https://space.bilibili.com/437503777
Github下载地址:
https://github.com/yaklang/yakit
Yakit官网下载地址:
https://yaklang.com/
Yakit安装文档:
https://yaklang.com/products/download_and_install
Yakit使用文档:
https://yaklang.com/products/intro/
常见问题速查:
https://yaklang.com/products/FAQ

长按识别添加工作人员
开启Yakit进阶之旅


文章来源: https://mp.weixin.qq.com/s?__biz=Mzk0MTM4NzIxMQ==&mid=2247519396&idx=1&sn=d0e2febfc53872a76604ada35346be3c&chksm=c2d1f000f5a67916bb1ba78811a28ae0aa129c204833e2dcfbfa3207d4dce1399d493a2da1b8&scene=58&subscene=0#rd
如有侵权请联系:admin#unsafe.sh