Awvs

AWVS 12

下载

下载试用版(2020-05-12:下载链接已失效,需要邮箱重新注册)

demo下载页

准备工作

安装好后,登录并扫描网站,在事件窗口会看到相关信息:

本地路径

所有的文件保存在本地的file:///C:\\ProgramData\\Acunetix Trial\\shared\\scans\\2e0a7e18-0b84-4e2e-a85c-fc3358d06769.zip压缩包下,切换到目录下,解压文件,可以看到三个文件:

时间记录

用sublime打开logfile.csv文件,可以查看到命令:

日志文件

观察上面的命令格式,可以提取命令,简化后的执行命令如下:

"C:\Program Files (x86)\Acunetix Trial\12.0.190927120\wvsc.exe" /scan https://www.baidu.com /log logfile.txt

以及日志部分的关键字提示符:

running in demo mode

去掉 demo 版本的限制

以管理员权限运行x64dbg,打开wvsc.exe程序,然后更改命令为上面的简化命令:

更改命令行

然后运行至EntryPoint,搜索字符串 running in demo mode,在上面的切入点下断点:

切入点

找到当前目标地址值为1,但是需要它为0才能不显示 running in demo mode

目标地址

查找一下 [r13+8c8]的引用:

r13+8c8

观察引用情况,数据段赋值操作有两条

00007FF7FBF5AB4B | 8886 C8080000            | mov byte ptr ds:[rsi+8C8],al            |
00007FF7FC34C670 | C683 C8080000 01         | mov byte ptr ds:[rbx+8C8],1             |

在两个地址下断,观察数据后,第一条 al中的值为0,第二条为赋值为1,因此找到关键点:

关键点

修改代码,将数据值赋值为0后,运行到切入点,发现跳过 running in demo mode

成功

查看扫描结果,是否存在请求信息:

验证

去掉时间限制

修改本地时间,使其与北京时间不同,但请注意不要超过 license_info.json中的时间,不然会有报错!(后期测试加:第一次更换时间,可以使用, 但是我后来重新更换了时间,发现web页面会报错,提示 too many request 显然这是一个暗桩,但我并没有什么方式可以绕过,本地使用 wvsc 进行扫描时,并未报错,那么就有可能是存在另一个校验程序。)

日志

关键字符串:

system time moved back

搜索字符串,找到切入点:

切入点

进入函数下断,重载寻找 al的修改地址:

修改点

手动修改时发现,多次调用该函数进行判断,所以直接修改代码为 xor rax,rax

修改代码

测试结果:

破除时间限制

去除 license 覆写

功能 点上面已经破解的差不多了,接下来需要对 license 进行分析。

license

找到了一些关键字:

"license_info.json"
"license_usage.json"
"license_info.json"
"license_usage.json"
"failed to write License Info File"

在文件夹中搜索其中的字符串,

license_info

接下来,在 failed to write License Info File上方函数下断点。编辑 license_info.json,尝试是否会断下:

疑似

修改函数代码如下:

函数修改

修改 license_info.json内容并刷新页面,显示如下:

覆写

生成 license

打开火绒剑,在不启动扫描的情况下,上一步的页面,看到opsrv.exe进程一直在调用wvsc.exe

判断调用

猜测 lincense 的校验部分由该程序进行,将文件拖入x64dbg,进入到 EntryPoint,发现特征代码如下:

OEP特征

入口OEP属于是 pyinstaller 构建的exe程序的OEP特征, 这里用命令生成一个exe文件用来做对比,命令如下:

pyinstaller get_checksum.py

oep

还有一个特征方式可以鉴别:搜索字符串 pyinstall

特征

针对python打包的exe文件,可以使用 pyinstxtractor + uncompyle6 来进行反编译。 pyinstxtractor.py github下载 ,然后使用如下命令,拆解 exepyc

python pyinstxtractor.py opsrv.exe

如果运行出现如下错误,请去sourceforge下载,两个下载点下载的代码是不一样的,一个存在严格的版本校验,另一个则没有。

报错截图

失败与成功截图对比如下:

对比

在解压后的文件中,搜索 license 结果如下:

licnese

根据名字判断 helpers.licensing.licenses.on_premise.pychelpers.licensing.licenses.pyc 可能性比较大,逐个打开看看。 接下来用 uncompyle6反编译 pyc文件 首先使用命令安装(管理员权限):

pip install uncompyle6

反编译命令如下:

uncompyle6 helpers.licensing.licenses.on_premise.pyc >licenses.on_premise.py

寻找关键函数:

checksum

初步阅读代码,可以发现这是根据 license_data来拼凑一个字符串用来加密生成 checksum 文件,再与 license_data 中的 checksum 对比,如果不一样报错。 license_data 中的信息和 license_info.json 文件中的结构是一样的,估计是读取的这个文件,根据上下文定位判断函数内相关引用和调用来源:

关键引用

查看上面的引用来源:

引用

通过反编译两个 pyc 文件逐步获取加载 license_data 和生成 checksummd5 算法,源代码最终结合的 get_checksum.py 代码如下:

"""
get_checksum (Supports AWVS 12)
Author : Alee
Date   : 28-Mar-2020
"""
import hashlib
import json

class ActivationErrorCodes:
    NO_WARNINGS = 0
    SITE_CHANGED = 1
    CHECKSUM_MISMATCH = 2
    INVALID_EXPIRY_DATES = 4
    EXPIRED = 8
    MAINTENANCE_EXPIRED = 16
    MAINTENANCE_WILL_EXPIRE = 32
    WITHIN_GRACE = 64
    MAC_CHANGED = 128
    KEY_NOT_FOUND = 256
    KEY_NOT_ENABLED = 512
    KEY_VERSION_MISMATCH = 1024
    ENGINE_COUNT_MISMATCH = 2048
    TARGET_COUNT_MISMATCH = 4096
    PRODUCT_CODE_NOT_FOUND = 8192
    SYSTEM_TIME_MOVED_BACK = 16384
    ERP_NOT_REACHED = 32768
    ERP_RESPONSE_ERROR = 65536
    CONCURRENT_REACT = 131072
    ACUMONITOR_FAILED = 16384
    ERROR_LOADING_ACTIVATION_JSON = 524288
    FILE_MISSING_OR_CORRUPT = 1048576

    @classmethod
    def get_keys(cls, code):
        result = []
        for key in cls.__dict__.keys():
            value = getattr(cls, key)
            if key[:1] != '_' and not callable(value) and value & code:
                result.append(key)

        return result


def md5(value: str, salt: str=None) -> str:
    m = hashlib.md5()
    if salt:
        m.update(salt.encode('utf-8'))
    m.update(value.encode('utf8'))
    return m.hexdigest()

def get_license_checksum(license_data):
    activation_error = license_data.get('activation_error', 0)
    if isinstance(activation_error, str):
        activation_error = int(activation_error)
    secret = '25128b0a92d51e6bf4ea7a40b91b33be911144f7'
    canonical_form = ''.join((
     secret,
     license_data.get('license_key', ''),
     license_data.get('product_code', ''),
     '%06d' % (license_data.get('max_targets', 0),),
     '%06d' % (license_data.get('max_engines', 0),),
     license_data.get('bxss_user_id', ''),
     license_data.get('bxss_api_key', ''),
     'true' if license_data.get('offline', False) else 'false',
     '%06d' % (license_data.get('major_version', 0),),
     '%06d' % (license_data.get('minor_version', 0),),
     '%06d' % (license_data.get('build_number', 0),),
     'true' if license_data.get('expired') else 'false',
     'true' if license_data.get('maintenance_expired') else 'false',
     '%06d' % (activation_error,),
     'true' if license_data.get('activated') else 'false',
     'true' if license_data.get('scan') else 'false',
     'true' if license_data.get('update') else 'false',
     'true' if license_data.get('access') else 'false',
     '%d' % (license_data.get('grace', 0),)))
    checksum = md5(canonical_form)
    print('canonical_form:'+canonical_form)
    print('checksum:'+checksum)
    print('license_data.get(\'checksum\'):'+license_data.get('checksum'))

def get_license():
    jsonPath = './license_info.json'
    license_info = json.loads(open(jsonPath, 'rt', encoding='utf-8').read())
    license_info = dict(license_info)
    info = license_info.get('info')
    if info is not None:
        if isinstance(info, dict):
            del license_info['info']
            license_info.update(info)
    license_info['offline'] = license_info.get('is_offline', False)
    license_info['activation_error_codes'] = ActivationErrorCodes.get_keys(int(license_info.get('activation_error', '0')))
    return license_info
license_data = get_license()
get_license_checksum(license_data)

代码存在很大的优化空间。 下面给出 license_info.json 文件的模板,其中 AOPENT为企业版标识,请不要修改。其他的内容,可以根据上方算法进行修改。

{
 "info": {
  "license_key": "TRY-TO-CRAKED-ALEE",
  "company": "REVERSE",
  "name": "ALEE",
  "phone": "666666",
  "email": "crake@crake.com",
  "country": "earth",
  "first_activated": "2020-01-17T10:53:50",
  "expires": "2100-01-31T10:53:50",
  "maintenance_expires": "2100-01-31T10:53:50",
  "max_engines": 999999,
  "max_targets": 999999,
  "product_code": "AOPENT",
  "bxss_user_id": "",
  "bxss_api_key": "",
  "is_offline": false,
  "is_worker": false
 },
 "major_version": 12,
 "minor_version": 0,
 "build_number": 190927120,
 "expired": false,
 "maintenance_expired": false,
 "scan": true,
 "update": true,
 "access": true,
 "activation_error": 0,
 "activated": true,
 "checksum": "17721cb9bc44780a55f52aedde975737",
 "grace": -1,
 "last_reactivated": "2020-01-31T10:53:50"
}

请将 get_checksum.pylicense_info.json 放在同一文件夹下!运行结果如下:

运行结果

验收阶段

结束

验收

AWVS 13

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2020-05-13 新增AWVS13专业版权限绕过方式 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

来源

安装包来源于同事,同事来源与互联网,版本:13.0.200205121

准备工作

调试命令:

"C:\Program Files (x86)\Acunetix\13.0.200205121\wvsc.exe" /scan https://www.baidu.com /profile Default /log C:\Users\Alee\Desktop\logfile.txt

尝试过不加 /profile Default 不会正常进行扫描任务。

去掉license覆写

运行任务,报错信息返回在指定的日志文件中,信息如下:

2020-05-13T10:49:41.218516	INFO	MAIN	8952	command line "C:\Program Files (x86)\Acunetix\13.0.200205121\wvsc.exe" /scan https://www.baidu.com /profile Default /log C:\Users\Alee\Desktop\logfile.txt
2020-05-13T10:55:37.513990	ERROR	activator	8952	Error: License File could not be loaded
2020-05-13T10:55:39.435225	ERROR	MAIN	8952	Licensing Error
2020-05-13T10:55:39.436958	INFO	MAIN	8952	application exiting

在x64dbg中搜搜当前区域字符串引用 License File could not be loadedLicensing Error 总共三处

观察license文件,提示 Licensing Error时,文件就会被重写。

配合搜索当前区域的字符串 failed to write License Info File

定位到 call 0x00007FF79A429B70 就是重写函数。 将定位函数的代码重写为

mov eax,1
ret

一石二鸟

重写函数处理掉以后,就是第一条License File could not be loaded,搜索字符串下断,重新回到Licensing Error提示的上下文片段,发现了一个有趣的地方:

上方的条件call,竟然包含了License File could not be loaded的报错,还包含了很多的报错信息could not decode / decrypt license data 可以推测就是证书校验函数了,重写尝试:

mov eax,1
ret

居然正常运行了,看来把鸡蛋放在一个篮子里,还是挺好的。

license改动

license的算法并未修改,不过需要修改一些版本地址之类的地方,提供一份可用license:

{
 "info": {
  "license_key": "TRY-TO-CRAKED-ALEE",
  "company": "REVERSE",
  "name": "ALEE",
  "phone": "666666",
  "email": "crake@crake.com",
  "country": "earth",
  "first_activated": "2020-05-13T10:53:50",
  "expires": "2100-05-13T10:53:50",
  "maintenance_expires": "2100-05-13T10:53:50",
  "max_engines": 999999,
  "max_targets": 999999,
  "product_code": "AOPENT",
  "bxss_user_id": "",
  "bxss_api_key": "",
  "is_offline": false,
  "is_worker": false
 },
 "major_version": 13,
 "minor_version": 0,
 "build_number": 200205121,
 "expired": false,
 "maintenance_expired": false,
 "scan": true,
 "update": true,
 "access": true,
 "activation_error": 0,
 "activated": true,
 "checksum": "31baf19e58d82c2c9f166371cc593a21",
 "grace": -1,
 "last_reactivated": "2020-05-13T10:53:50"
}

结语

没想到13不仅仅是丰富了漏洞库,优化了扫描界面,就连注册也更加方便了。