最近,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 调用约定,可参考微软官方文档 https://learn.microsoft.com/zh-cn/cpp/build/x64-calling-convention?view=msvc-170#calling-convention-defaults
在调试器内下断点,获取水印字符串指针,直接看内存获得水印具体内容
其中的 ?
为随机内容,疑似为保留随机性的行为,同时,这里的内容其实为十六进制表示的二进制内容,所以我们在搜索的时候需要将程序转化为 hex 形式的字符串再进行搜索,概念验证代码如下
可以看到成功识别了 VMP 的水印
换用某厂加了 VMP 壳的 UnityPlayer 进行测试,一样成功检测,并且 ExeinfoPe 没有检测到 VMP 的存在 (已使用 ExeinfoPe 最新版本)
朋友赏饭吃,我只是个蹭饭的 ()
顺便给个 Star 呗 () : https://github.com/DNLINYJ/DetectVMP3
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;
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)