JsSSA与网络请求解析
2024-2-27 15:6:37 Author: www.freebuf.com(查看原文) 阅读量:6 收藏

前言

当想对浏览器某个页面的的内容进行分析时,通常会进行一个网页跳转的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.send0
a("1111")
a = () => {}
a("2222")

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

1709017240_65dd88989b023822b98d1.png!small

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

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

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

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

1709017246_65dd889e82e595256b7a1.png!small

在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来对某个对象进行追踪,获取一个数组。可以通过Show或ShowWithSource获取数组信息。

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等。比如,每个值可以通过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.open和ajax.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: false
func: phi(ajax)[newActiveXObject("Microsoft.XMLHTTP"),XMLHttpRequest0()].send()
 */

文章来源: https://www.freebuf.com/articles/network/392698.html
如有侵权请联系:admin#unsafe.sh