某Vape LiteV3.4.2破解版的账户窃取分析

在论坛中发现了该样本,帖子使用了Codex分析,但某绒的人工分析结果是无恶意行为,于是对其进行分析

原贴分析行为:

image-20260821172817101

加载器分析

静态分析

Loader.exe

image-20260721160604271

新鲜日期的64位程序,没有保护

根据Codex的分析,该文件中有个a.class

我们使用IDA分析该文件,定位到了主函数中,启动了一个线程,调用了DefineClass,但是这些指向的buf没有找到类文件的特征,甚至没有找到指向内容,于是决定动态调试

image-20260821172947041

dump class文件

搜索字符串找到对应引用位置,下断点运行

单步跟到框处函数处

image-20260821173231701

该函数的返回值是明显的CLASS文件魔数特征,发现这里有一个循环,反复调用了该函数

在该循环条件终止时,定位到类文件对应内存空间位置

将这一块内存都dump出来

得到这样的文件:

image-20260821173356021

按照魔术头分隔,用asm修复一下

得到7个类文件

image-20260721182323279

类文件分析

静态分析

a.class中有一个不能反编译的方法,让我们详细看看它:

image-20260721161607590

由于是ZKM25,翻了翻没有什么现有的反混淆器

于是顶着indy混淆手动翻一翻,毕竟INVOKESPECIAL这种用不了indy

image-20260721162236076

观察到Socket创建

image-20260721162322346

压缩文件的输出流,不过用的是字节流直接输出,意味着不落地

说明可能是创建压缩文件通过Socket传输到外部

动态调试

反射调用a函数

把提取的jar作为库导入idea项目

创建一个default包的类,在这里调用a.a

由于ZKM混淆器的renamer使得存在命名重复签名不同的方法,从而不能直接调用,只能使用反射调用

public static void main(String[] args) throws Exception {
    System.out.println(a.class);
    Method[] methods = a.class.getDeclaredMethods();
    for (Method method : methods) {
        method.setAccessible(true);
        if (method.getName().equals("a") && Modifier.isStatic(method.getModifiers()) && method.getParameters()[0].getType() == long.class) {
            System.out.println("ssss");
            
            method.invoke(null, 15186841654917L);
            
            System.out.println("hello");
        }
    }
}

这样可以定位到唯一的方法,从而调用指定方法。

ZKM混淆添加的这个方法参数是暴力测出来的,代码如下:

while (true) {
    try {
        invoke = method.invoke(null, sbs[sb1]);
    } catch (InvocationTargetException e) {
        System.out.println(sb1++);
        continue;
    }
    System.out.println(sbs[sb1]);
}

如果传入的参数不正确,该方法不能正常运行,而是抛出一个异常

这里知道是从Server.onMessage方法中调用了该方法,所以用正则表达式(-?\d+)L:?提取onMessage方法中所有的long常量,约300多个,最终测得只有15186841654917L不会抛出异常

这并不安全,我们运行了一个未知的函数

断点调试

a类中的操作两个静态字段的静态函数下断点,发现f类是indy保护类,同样,对其中解密函数下断点,不需要断下字符串解密函数,因为在clinit中会有大量的字符串解密,而且很多不可读,在执行完之后,对String类的init方法,签名为char[],或使用条件断点功能,设置断点生效时机

image-20260721163638998

经过几次断下后,执行结束

逐个查看f中的静态字段,发现b中有东西

image-20260721165258887

疑似调用了currentTimeMillis,我们知道这个是System的静态方法,获取当前时间戳

这意味着可能判断了时间是否过期/未到,重新检查字节码

image-20260721165416782

发现了个疑似时间戳的常量,左边的分支直接执行到return

image-20260721165529222

约为2026年8月5日,我们将系统时间调到该日期之后,重新执行代码

对Socket的构造方法下断点

image-20260821170619325

导到本地,本地用python开一个服务器

python -m http.server 45678

调试过程中,发现解密了Fileexists函数

image-20260821170756417

于是在对应函数中下断点,这里解密过的字符串不会再次解密了,如果是重要函数一定要下断点,不然不会知道被调用了

image-20260821170834421

发现判断C:\Disable是否存在,可能是某个标志位,不过我这里作为一个普通用户是不存在的

继续调试,注意到字符串解密:

image-20260821170916997

调用exits方法判断了是否存在:

image-20260821171003766

getenv拼接出了hmcl.json

image-20260821171145744

继续用exists判断:

image-20260821171220555

hook到了获取用户名

image-20260821171316616

为了获取hmcl的accounts.json文件

image-20260821171601746

注意到调用了putNextEntry,结合静态分析中看到的ZipOutputStream构造方法,这里把accounts.json存到压缩包中

image-20260821171651386

我们可以更快一步,直接对write方法下断点

同样方法发现了readAllBytes的调用

image-20260821171900233

此处write方法断住,发现了accounts.json被装入压缩包

image-20260821171943275

同样,对Feather的窃取:

image-20260821172048658

对Lunar的窃取:

image-20260821172106534

完成了对以上信息的寻找和窃取后,调用toByteArray方法转为字节流

image-20260821172154482

解密了一个神秘字符串:

image-20260821172210359

随后将这些数据发送给了服务器,数据包拦截:

image-20260821172501476

用异或解密,密钥为xiaojie1337,发现了我的accounts.json文件

image-20260821172556995

随后调用了close方法结束该函数的作用,调试结束

总结

这是一个有窃取HMCL、Feather、Lunar等账户信息行为的破解外挂,生效日期为2026年8月5日

另外,也许某绒病毒分析师可以尝试拥抱AI,我们都会有更美好的未来