最近,VMP 3.5.1 的源码被完整泄露了 等不及看见国产虚拟机了
朋友在读源码的时候发现了 VMP 编译器水印的位置,我只是个调试菜鸡, 写这个文章来记录一下过程
在 cores/processors.cc 的第 2070 行,出现了一个函数 BaseFunction::AddWatermark, 内容如下
这段代码定义了一个名为 BaseFunction::AddWatermark 的方法,该方法将特定的“水印”添加到一个对象或数据中。以下是关于这个函数以及代码中每行的简单解释。
首先,定义了两个指向uint8_t 类型的全局指针,version_watermark 和 owner_watermark, 既版本水印和用户水印。
BaseFunction::AddWatermark 函数接收两个参数: 一个 Watermark 对象指针和一个表示要复制的次数的整数。
然后,创建了一个新的 Watermark 对象 secure_watermark。并创建了一个字符串 value 用于存放从内部 watermark 中提取和处理的数据。
接下来创建了一个内部 watermark 数组 internal_watermarks,它包含了之前定义的全局 watermark 指针。
这里的代码穿过所有 watermark(包括输入的 watermark 和内部的 watermark)。对于每个 watermark,如果它是 NULL,那么就直接跳过当前迭代。
取出内部 watermark,将它的数据作为 32 位和 16 位的块重新解释(也就是把原始的数据直接视作这些数值)。然后,它用一个特殊的算法(对每个字节都执行异或,然后添加到索引上),在 value 中生成新的字符。
最后,它为每个 watermark 创建 copy_count 个复制项,编译每个复制项,并将其添加到 ICommand 中,随后设置创建新块的数据选项标志。
这段代码的主要目的是为了处理和添加 watermark 到数据中。它首先处理传入的 watermark,然后处理定义的内部 watermark。处理的方式包括从中提取数据,以特定方式编辑这些数据,并设置 secure_watermark 或新增 watermark 的值。然后为这些 watermark 创建指定数量的复制项,并添加到命令中。
其中的 secure_watermark.set_value(value); 设定水印内容,之后使用这个实例进行水印的添加 (见上方),我们可以通过获取这个实例的 this 指针来获取水印内容
然而在后期测试的时候,BaseFunction::AddWatermark 实际上是被虚拟机保护了,但 Watermark 类并没有被虚拟机保护,其中的 Watermark::ReadFromIni 函数特征及其鲜明,这意味着我们可以通过 dump 下来的程序,抓住函数地址并直接读取水印内容,见下文
注:必须要用dump后的程序分析,因为VMP似乎只有在运行时才会解密字符串
这里就是我干活的地方了(
掏出带有反反调试器的x64dbg,直接 逆向,启动!()

使用自带的 Scylla 把 VMP 主程序 dump 出来

在 IDA 内搜索特征字符串,找到 Watermark::Compile() 函数


解释后的伪代码 Watermark::Compile() 函数如下
其中的 value_data 就是我们想找的水印字符串的指针,对应汇编为
其中 [r13+0x38] 为从 实例指针(this)+偏移量 中取出 水印字符串指针 赋值给 rax, 既 value_data
进一步追寻 r13 的来源,在函数开头发现了这一行
根据 Windows 平台 x64 下的 fastcall 调用约定,第一个传入参数为 rcx , 既 this 指针,因此我们在 Watermark::Compile 函数开头直接使用 [rcx+0x38] 就可以获取水印字符串指针
注: 关于 x64 调用约定,可参考微软官方文档 d20K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6D9k6h3q4J5L8W2)9J5k6h3#2A6j5%4u0G2M7$3!0X3N6q4)9J5k6h3y4G2L8g2)9J5c8Y4A6Z5i4K6u0V1j5$3&6Q4x3V1k6U0M7s2m8Q4x3V1k6T1N6h3W2D9k6q4)9J5c8Y4R3$3y4q4)9J5k6r3y4S2L8r3I4A6L8X3N6Q4x3X3c8U0L8$3&6$3k6h3&6@1K9h3!0F1i4K6y4r3N6X3W2W2N6#2)9K6c8r3#2K6N6X3y4Q4x3X3b7I4y4K6m8Q4x3U0y4U0j5h3I4D9K9h3&6Y4i4K6u0V1j5$3!0F1N6X3g2F1N6r3W2G2L8W2)9J5k6r3c8W2k6X3q4#2L8s2c8K6
在调试器内下断点,获取水印字符串指针,直接看内存获得水印具体内容

其中的 ? 为随机内容,疑似为保留随机性的行为,同时,这里的内容其实为十六进制表示的二进制内容,所以我们在搜索的时候需要将程序转化为 hex 形式的字符串再进行搜索,概念验证代码如下
可以看到成功识别了 VMP 的水印

换用某厂加了 VMP 壳的 UnityPlayer 进行测试,一样成功检测,并且 ExeinfoPe 没有检测到 VMP 的存在 (已使用 ExeinfoPe 最新版本)


朋友赏饭吃,我只是个蹭饭的 ()
顺便给个 Star 呗 () : da8K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6Y4K9i4c8Z5N6h3u0Q4x3X3g2U0L8$3#2Q4x3V1k6p5e0V1I4u0e0W2W2v1i4K6u0r3c8r3g2@1k6h3y4@1g2V1#2b7x3H3`.`.
uint8_t *version_watermark = NULL;
uint8_t *owner_watermark = NULL;
void BaseFunction::AddWatermark(Watermark *watermark, int copy_count)
{
Watermark secure_watermark(NULL);
std::string value;
uint8_t *internal_watermarks[] = {version_watermark, owner_watermark};
for (size_t k = 0; k < 1 + _countof(internal_watermarks); k++) {
if (k == 0) {
if (!watermark)
continue;
} else {
uint8_t *ptr = internal_watermarks[k - 1];
if (!ptr)
continue;
uint32_t key = *reinterpret_cast<uint32_t *>(ptr);
uint16_t len = *reinterpret_cast<uint16_t *>(ptr + 4);
value.resize(len);
for (size_t i = 0; i < value.size(); i++) {
value[i] = ptr[6 + i] ^ static_cast<uint8_t>(_rotl32(key, (int)i) + i);
}
secure_watermark.set_value(value);
watermark = &secure_watermark;
}
for (int i = 0; i < copy_count; i++) {
watermark->Compile();
ICommand *command = AddCommand(Data(watermark->dump()));
command->include_option(roCreateNewBlock);
}
}
}
uint8_t *version_watermark = NULL;
uint8_t *owner_watermark = NULL;
void BaseFunction::AddWatermark(Watermark *watermark, int copy_count)
{
Watermark secure_watermark(NULL);
std::string value;
uint8_t *internal_watermarks[] = {version_watermark, owner_watermark};
for (size_t k = 0; k < 1 + _countof(internal_watermarks); k++) {
if (k == 0) {
if (!watermark)
continue;
} else {
uint8_t *ptr = internal_watermarks[k - 1];
if (!ptr)
continue;
uint32_t key = *reinterpret_cast<uint32_t *>(ptr);
uint16_t len = *reinterpret_cast<uint16_t *>(ptr + 4);
value.resize(len);
for (size_t i = 0; i < value.size(); i++) {
value[i] = ptr[6 + i] ^ static_cast<uint8_t>(_rotl32(key, (int)i) + i);
}
secure_watermark.set_value(value);
watermark = &secure_watermark;
}
for (int i = 0; i < copy_count; i++) {
watermark->Compile();
ICommand *command = AddCommand(Data(watermark->dump()));
command->include_option(roCreateNewBlock);
}
}
}
uint8_t *version_watermark = NULL;
uint8_t *owner_watermark = NULL;
uint8_t *version_watermark = NULL;
uint8_t *owner_watermark = NULL;
void BaseFunction::AddWatermark(Watermark *watermark, int copy_count)
void BaseFunction::AddWatermark(Watermark *watermark, int copy_count)
Watermark secure_watermark(NULL);
std::string value;
Watermark secure_watermark(NULL);
std::string value;
uint8_t *internal_watermarks[] = {version_watermark, owner_watermark};
uint8_t *internal_watermarks[] = {version_watermark, owner_watermark};
for (size_t k = 0; k < 1 + _countof(internal_watermarks); k++) {
if (k == 0) {
if (!watermark)
continue;
} else {
uint8_t *ptr = internal_watermarks[k - 1];
if (!ptr)
continue;
for (size_t k = 0; k < 1 + _countof(internal_watermarks); k++) {
if (k == 0) {
if (!watermark)
continue;
} else {
uint8_t *ptr = internal_watermarks[k - 1];
if (!ptr)
continue;
uint32_t key = *reinterpret_cast<uint32_t *>(ptr);
uint16_t len = *reinterpret_cast<uint16_t *>(ptr + 4);
value.resize(len);
for (size_t i = 0; i < value.size(); i++) {
value[i] = ptr[6 + i] ^ static_cast<uint8_t>(_rotl32(key, (int)i) + i);
}
secure_watermark.set_value(value);
watermark = &secure_watermark;
uint32_t key = *reinterpret_cast<uint32_t *>(ptr);
uint16_t len = *reinterpret_cast<uint16_t *>(ptr + 4);
value.resize(len);
for (size_t i = 0; i < value.size(); i++) {
value[i] = ptr[6 + i] ^ static_cast<uint8_t>(_rotl32(key, (int)i) + i);
}
secure_watermark.set_value(value);
watermark = &secure_watermark;
for (int i = 0; i < copy_count; i++) {
watermark->Compile();
ICommand *command = AddCommand(Data(watermark->dump()));
command->include_option(roCreateNewBlock);
}
}
for (int i = 0; i < copy_count; i++) {
watermark->Compile();
ICommand *command = AddCommand(Data(watermark->dump()));
command->include_option(roCreateNewBlock);
}
}
void Watermark::ReadFromIni(IniFile &file, size_t id)
{
id_ = id;
name_ = file.ReadString("Watermarks", string_format("Name%d", id).c_str());
value_ = file.ReadString("Watermarks", string_format("Value%d", id).c_str());
use_count_ = file.ReadInt("Watermarks", string_format("UseCount%d", id).c_str());
enabled_ = file.ReadBool("Watermarks", string_format("Enabled%d", id).c_str(), true);
Compile();
}
void Watermark::ReadFromIni(IniFile &file, size_t id)
{
id_ = id;
name_ = file.ReadString("Watermarks", string_format("Name%d", id).c_str());
value_ = file.ReadString("Watermarks", string_format("Value%d", id).c_str());
use_count_ = file.ReadInt("Watermarks", string_format("UseCount%d", id).c_str());
enabled_ = file.ReadBool("Watermarks", string_format("Enabled%d", id).c_str(), true);
Compile();
}
void Watermark::Compile()
{
dump_.clear();
mask_.clear();
if (value_.size() == 0)
return;
for (size_t i = 0; i < x.size(); i++) {
size_t p = i / 2;
if (p >= dump_.size()) {
dump_.push_back(0);
mask_.push_back(0);
}
uint8_t m = 0xff;
uint8_t b;
char c = value_[i];
if ((c >= '0') && (c <= '9')) {
b = c - '0';
} else if ((c >= 'A') && (c <= 'F')) {
b = c - 'A' + 0x0a;
} else if ((c >= 'a') && (c <= 'f')) {
b = c - 'a' + 0x0a;
} else {
m = 0;
b = rand();
}
if ((i & 1) == 0) {
dump_[p] = (dump_[p] & 0x0f) | (b << 4);
mask_[p] = (mask_[p] & 0x0f) | (m << 4);
} else {
dump_[p] = (dump_[p] & 0xf0) | (b & 0x0f);
mask_[p] = (mask_[p] & 0xf0) | (m & 0x0f);
}
}
}
void Watermark::Compile()
{
dump_.clear();
mask_.clear();
if (value_.size() == 0)
return;
for (size_t i = 0; i < x.size(); i++) {
size_t p = i / 2;
if (p >= dump_.size()) {
dump_.push_back(0);
mask_.push_back(0);
}
uint8_t m = 0xff;
uint8_t b;
char c = value_[i];
if ((c >= '0') && (c <= '9')) {
b = c - '0';
} else if ((c >= 'A') && (c <= 'F')) {
b = c - 'A' + 0x0a;
} else if ((c >= 'a') && (c <= 'f')) {
b = c - 'a' + 0x0a;
} else {
m = 0;
b = rand();
}
if ((i & 1) == 0) {
dump_[p] = (dump_[p] & 0x0f) | (b << 4);
mask_[p] = (mask_[p] & 0x0f) | (m << 4);
} else {
dump_[p] = (dump_[p] & 0xf0) | (b & 0x0f);
mask_[p] = (mask_[p] & 0xf0) | (m & 0x0f);
}
}
}
char __fastcall Watermark::Compile(_QWORD *this)
{
_QWORD *mask_;
__int64 size_value_;
unsigned __int64 i;
unsigned __int64 p;
unsigned __int64 new_size;
unsigned __int64 dump_condition;
char *value_pointer;
_BYTE *dump_bytes;
_BYTE *mask_bytes;
unsigned __int64 mask_new_size;
char *mask_pointer;
_BYTE *mask_data;
_BYTE *dump_expansion;
char mask_byte;
_QWORD *value_data;
char c_value;
char hex_value;
__int64 dump_offset;
char current_byte;
char v22[8];
mask_ = this + 16;
this[14] = this[13];
this[17] = this[16];
size_value_ = this[9];
if ( size_value_ )
{
i = 0i64;
do
{
p = i >> 1;
if ( p >= this[14] - this[13] )
{
new_size = this[14];
v22[0] = 0;
if ( (unsigned __int64)v22 >= new_size || (dump_condition = this[13], dump_condition > (unsigned __int64)v22) )
{
if ( new_size == this[15] )
ResizeDump(this + 13, 1i64);
mask_bytes = (_BYTE *)this[14];
if ( mask_bytes )
*mask_bytes = 0;
}
else
{
value_pointer = &v22[-dump_condition];
if ( new_size == this[15] )
ResizeDump(this + 13, 1i64);
dump_bytes = (_BYTE *)this[14];
if ( dump_bytes )
*dump_bytes = value_pointer[this[13]];
}
++this[14];
mask_new_size = mask_[1];
v22[0] = 0;
if ( (unsigned __int64)v22 >= mask_new_size || *mask_ > (unsigned __int64)v22 )
{
if ( mask_new_size == mask_[2] )
ResizeMask(mask_, 1i64);
dump_expansion = (_BYTE *)mask_[1];
if ( dump_expansion )
*dump_expansion = 0;
}
else
{
mask_pointer = &v22[-*mask_];
if ( mask_new_size == mask_[2] )
ResizeMask(mask_, 1i64);
mask_data = (_BYTE *)mask_[1];
if ( mask_data )
*mask_data = mask_pointer[*mask_];
}
++mask_[1];
}
mask_byte = -1;
if ( this[10] < 0x10ui64 )
value_data = this + 7;
else
value_data = (_QWORD *)this[7];
c_value = *((_BYTE *)value_data + i);
if ( (unsigned __int8)(c_value - 48) > 9u )
{
if ( (unsigned __int8)(c_value - 65) > 5u )
{
if ( (unsigned __int8)(c_value - 97) > 5u )
{
mask_byte = 0;
hex_value = RandomFunction();
}
else
{
hex_value = c_value - 87;
}
}
else
{
hex_value = c_value - 55;
}
}
else
{
hex_value = c_value - 48;
}
dump_offset = this[13];
current_byte = *(_BYTE *)(dump_offset + p);
if ( (i & 1) != 0 )
{
*(_BYTE *)(dump_offset + p) ^= (hex_value ^ current_byte) & 0xF;
LOBYTE(size_value_) = (mask_byte ^ *(_BYTE *)(*mask_ + p)) & 0xF;
*(_BYTE *)(*mask_ + p) ^= size_value_;
}
else
{
*(_BYTE *)(dump_offset + p) = (16 * hex_value) | current_byte & 0xF;
LOBYTE(size_value_) = (16 * mask_byte) | *(_BYTE *)(*mask_ + p) & 0xF;
*(_BYTE *)(*mask_ + p) = size_value_;
}
++i;
}
while ( i < this[9] );
}
return size_value_;
}
char __fastcall Watermark::Compile(_QWORD *this)
{
_QWORD *mask_;
__int64 size_value_;
unsigned __int64 i;
unsigned __int64 p;
[培训]传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!