前言
当想对浏览器某个页面的的内容进行分析时,通常会进行一个网页跳转的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")
这段代码会生成这样一棵抽象语法树:
如果想去寻找“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来支持分析呢
使用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
等。比如,每个值可以通过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.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()
*/
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