BetaMao

awvs v12.0 插件分析

字数统计: 1.3k阅读时长: 4 min
2020/03/07 Share

之前想分析awvs12版,但是Windows下有vmp保护,后来发现linux下的没保护但是没时间弄就扔那里了,直到前几天梦神发了我一篇awvs破解的文章,惊奇的发现作者直接跳过了脱壳这最难的一步,原来官方的demo版没加壳(附下载地址Windowslinux)…迷惑行为,既然提起了就继续分析吧

文件说明

awvs从v11开始,不再使用windows+explorer+Jscript+access,而是使用跨平台+python+nodejs+postgresql架构,前者脚本直接放在文件系统里且使用twofish+zlib加密压缩的方式存储,解密方式及相关浅析可查看之前的文章,后者打开安装目录可以明显看出技术架构:

1
2
3
4
5
6
7
opsrv.exe # 主服务,负责前端与任务下发
xxx.pyd # 编译的python库文件,opsrv用到的python依赖
node && xxx.bin && xxx.node # nodejs运行时及其快照,模块
wvsc # 扫描主程序,为C++编写的内置V8的二进制文件
chromium # 爬虫,由nodejs控制
postgresql && sqlalchemy # 数据库
dir # 其他目录存放的扫描的中间,结果,日志文件

提取脚本

首先我最想要的当然还是它的脚本,它以前的版本是js编写的,现在根据上面分析要么python要么js,而又存在wvsc_blob.bin这个v8特征的快照,感觉新版依然是js编写的,node处理js插件有两种主流方式,要么直接在js外套个壳后直接存储在二进制文件里,要么使用v8编译为机器码再存放,如果是后者就很凉凉,所以先直接在前端下发任务后attach wvsc进程看看内存,搜索if等字符串能明显看到插件的js代码,那么应该是前者!按套路开始静态分析,先ida查看字符串发现:

有10.5插件分析经验就知道这明显是对脚本预处理的模式串,相关函数输入应该就是明文代码了,调试分析该函数发现sub_1400B54B0的第三个参数是脚本名,第二个参数作为输出参数,将存储脚本代码:


经过分析确认了该函数的作用就是通过脚本名获取明文脚本,且上层会通过它循环获取所有脚本及依赖,所以直接hook该函数即可拿到所有脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# coding=utf-8
import os


class DumpScriptsHook(DBG_Hooks):
root_path = r'C:\Users\betamao\Desktop\scripts'

def dbg_process_start(self, pid, tid, ea, name, base, size):
print('{} run in {:x} and the pid is {}'.format(name, base, pid))
self.func_start = base + 0xb54b0
self.func_end = self.func_start + 0x491
add_bpt(self.func_start, 0, BPT_ENABLED | BPT_UPDMEM)
add_bpt(self.func_end, 0, BPT_ENABLED | BPT_UPDMEM)

def dbg_suspend_process(self):
ev_ea = get_event_ea()
if ev_ea == self.func_start: # 函数开始时拿地址
self.current_out = cpu.rdx
script_name_str = cpu.r8
script_name_size = get_dword(script_name_str + 8)
script_addr = get_qword(script_name_str)
self.script_name = get_bytes(script_addr, script_name_size).replace('/', '.')
elif ev_ea == self.func_end: # 函数结束时拿数据并保存到文件里
addr = get_qword(self.current_out + 8)
size = get_dword(self.current_out + 16)
path = os.path.join(self.root_path, self.script_name)
res = savefile(path, 0, addr, size)
if res == 0:
print('save file {} failed'.format(self.script_name))

continue_process()


try:
if dump_hook:
dump_hook.unhook()
except:
pass
finally:
dump_hook = DumpScriptsHook()
dump_hook.hook()
load_and_run_plugin('python', 3)

这样可能由于未触发到某分支而未载入全部插件,也可以用frida勾取该函数,利用postgresql里面的插件数据主动dump,不再赘述。
【注:opsrv为python installer打包的,直接解包就能拿到里面的前端调度代码,不多叙述】

脚本分析

从16年到20年awvs的脚本变化还是很大的,首先v10.5版本应该是ES5版本,而v12.0用的应该是ES6,新版的代码看着明显舒服多了!!!从格式上说,旧版是新建一个ScriptContext对象,在其内部执行插件,而新版是直接使用类似模块一样的封装方式得到函数再直接调用的:

1
(function(scriptArg, scanState){ script content; });

可见一个函数将有两个隐含变量,scriptArg提供要扫描的目标信息,而scanState可用于输出结果。另外在库与依赖上也使用了更优雅的形式:

1
2
/// #include constants.inc;
/// #require 2-XSS-store.script;

这样就符合js语法,并且方便ax.loadModule解析所需依赖。特别的,可以发现dump出的文件里有个legacy.js,看里面的内容让人泪汪汪:

之前花了大量时间去分析的类与函数在这里都有(杀了我就现在👨🔫),于是我们的任务就变成了实现更底层的rt,对此直接搜索FunctionTemplateObjectTemplate就可以拿到所有native类与函数的声明了,至于其实现,简单评估发现没必要逆向,其功能很明显可以直接猜出来,显然实现这几个类工作量少很多。

结尾

我认为插件是核心,有了插件是能自己实现一套扫描器的,即框架部分,在漏扫模块awvs是使用自己内嵌v8来实现运行时的,这样无疑更加高效,不过对于我们这种玩玩闹闹其实用nodejs就好了,因为它提供丰富的库呀~

CATALOG
  1. 1. 文件说明
  2. 2. 提取脚本
  3. 3. 脚本分析
  4. 4. 结尾