首页
社区
课程
招聘
[原创]Everedit的试用限制绕过
2020-8-10 14:59 13397

[原创]Everedit的试用限制绕过

2020-8-10 14:59
13397

 因为蜜汁自信导致我在短短两天内重装了7次系统,导致Everedit当年的注册码生成次数耗尽,所以想着先把试用限制过了凑合着用到明年。

1.分析

从4.0版本开始授权文件就没有保存在本地的选项了,都是保存在注册表里,试用限制也是如此,先跟一下。

其实都不用查壳都知道是vmp,先拉到x64dbg里面对(RegQueryValueExW)跨模块调用里面的关键函数下断:

跑起来以后断下了:

   RDX应该是第二个参数: lpValueName(在x64下对函数的调用都是优先使用寄存器的,第一个参数是RCX),license不是我们想要的,继续跑。F9以后直接跑飞了,说明试用限制的call被加密了。直接对kernelbase.RegQueryValueExW这个函数下断,并记录他的RBX。跑了一遍以后发现了一个可疑的字符串:

跟踪他的来源,发现调用果然被加密了:

但是我们的目的已经达到了,打开注册表编辑器,看看这个项到底有什么玄机:

我觉得这个默认项很像时间戳啊,于是转换之:

5ECCEFBD -> 1590489021 -> 2020-05-26 18:30:21

那我们把试用条件改成今天0点试试:

2020-08-10  00:00:00 -> 1596988800 -> 5F301D80

发现还是提示过期,尝试将Value这个值删除,能正常打开了。

分析部分就到此结束了,下面开始敲代码

2.编码

采用劫持导入表hook的方案。

由前面的分析已经可以大致确定几个关键点了:

RegQueryValueExW

L"ZQBKAF8AMQA0ADIANGA4"

由于我们并没有去找机器码的算法,所以要做一些额外的工作。

大致思路如下:

  1. hook advapi32.RegOpenKeyW获取项的名称和HKey用于比对。

  2. hook kernelbase.RegQueryValueExW 欺骗程序,告诉它:值“Value”并不存在;生成一个合法的“时间点”,然后改写函数的参数。


dllmain.cpp:

// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "framework.h"
#include "DllModule.h"

extern "C" __declspec(dllexport) void Out();
void Out()
{
	OutputDebugStringW(L"Out");
}
namespace
{
	bool is_init = false;
}

BOOL APIENTRY DllMain(HMODULE hModule,
	DWORD  ul_reason_for_call,
	LPVOID lpReserved
)
{
	if (DLL_PROCESS_ATTACH == ul_reason_for_call)
	{
		UNREFERENCED_PARAMETER(lpReserved);
		DisableThreadLibraryCalls(hModule);
		if (is_init)
		{
			return TRUE;
		}
		if (nullptr == g_DMod)
		{
			g_DMod = std::make_unique<DllModule>();
		}
		g_DMod->Attach();
		is_init = true;
	}
	else if (DLL_PROCESS_DETACH == ul_reason_for_call)
	{
		if (nullptr == g_DMod)
		{
			return TRUE;
		}
		g_DMod->Detach();
		if (is_init)
		{
			is_init = false;
		}
	}
	return TRUE;
}

创建一个名为“Out”的导出函数用于导入表劫持。

DllModule.h:

#pragma once
#include <cstdint>
#include <memory>

class DllModule
{
public:
	DllModule() = default;
	virtual ~DllModule() = default;
	static void Attach();
	static void Detach();
private:
	
};
using DllModulePtr = std::unique_ptr<DllModule>;
extern DllModulePtr g_DMod;

DllModule.cpp:

#include "DllModule.h"
#include "hooks.h"

DllModulePtr g_DMod;

void DllModule::Attach()
{
	hooks::Install();
}

void DllModule::Detach()
{
	hooks::UnInstall();
}

hooks.cpp:

#include "hooks.h"
#include "date_time.h"
#include <base/hook/fp_call.h>
#include <base/hook/inline.h>
#include <base/util/xorstr.hpp>
#include <base/util/Debug.hpp>
#include <windows.h>
#include <string>
#include <string_view>

#pragma comment(lib,"advapi32.lib")

namespace
{
	int32_t g_reg_open_key_w_call_count = 0;
	DWORD g_value_length = 0;
	PHKEY g_phkey = nullptr;
	bool g_is_reg_open_key_w_uninstall = false;
	bool g_is_reg_query_value_w_uninstall = false;
	std::wstring g_sub_key;
}

namespace hooks
{
	namespace hook_t
	{
		base::hook::hook_t RegOpenKeyW;
		base::hook::hook_t RegQueryValueExW;
	}
	namespace real
	{
		uintptr_t RegOpenKeyW = 0;
		uintptr_t RegQueryValueExW = 0;
	}

	namespace fake
	{
		static LSTATUS
			APIENTRY
			RegQueryValueExW(
				_In_ HKEY hKey,
				_In_opt_ LPCWSTR lpValueName,
				_Reserved_ LPDWORD lpReserved,
				_Out_opt_ LPDWORD lpType,
				_Out_writes_bytes_to_opt_(*lpcbData, *lpcbData) __out_data_source(REGISTRY) LPBYTE lpData,
				_When_(lpData == NULL, _Out_opt_) _When_(lpData != NULL, _Inout_opt_) LPDWORD lpcbData
			)
		{
			static bool only_call_once = false;

			auto ret_val = base::std_call<LSTATUS>(\
				real::RegQueryValueExW, \
				hKey, lpValueName, lpReserved, \
				lpType, lpData, lpcbData);

			if (only_call_once)
			{
				return ret_val;
			}
			//ERROR_FILE_NOT_FOUND
			if (nullptr != lpValueName)
			{
				std::wstring_view ValueName{ lpValueName };
				if (ValueName == xorstr_(L"Value"))
				{
					const auto ret = ERROR_FILE_NOT_FOUND;
					return ret;
				}
			}
			if (nullptr == g_phkey || nullptr == hKey)
			{
				return ret_val;
			}
			if (INVALID_HANDLE_VALUE == hKey)
			{
				return ret_val;
			}

			if (TRUE == IsBadReadPtr(g_phkey, sizeof(HKEY)))
			{
				return ret_val;
			}
			if (*g_phkey != hKey)
			{
				return ret_val;
			}

			if (0 == g_value_length)
			{
				if (nullptr != lpcbData)
				{
					if (FALSE == IsBadReadPtr(lpcbData, sizeof(DWORD)))
					{
						g_value_length = *lpcbData;
						OutputDebugStringEx(L"DEBUG_INFO | g_value_length: %d", g_value_length);
					}
				}
			}

			if (nullptr != lpData)
			{
				if (FALSE == IsBadReadPtr(lpData, g_value_length))
				{
					const auto arr_size = static_cast<size_t>(g_value_length);
					auto& arr = lpData;

					ev_date_time::DateTime d;
					auto time = d.Gen();
					auto str = d.GenString();

					OutputDebugStringEx("DEBUG_INFO | str: %s", str.c_str());
					memcpy(arr, &time, 4);
					//for (size_t i = 0; i < arr_size; ++i)
					//{

					//	OutputDebugStringEx(L"DEBUG_INFO | arr[i]: %X", arr[i]);
					//}

					only_call_once = true;
					UnInstall();
					return ret_val;
				}
			}

			return ret_val;
		}
		static LSTATUS
			APIENTRY
			RegOpenKeyW(
				_In_ HKEY hKey,
				_In_opt_ LPCWSTR lpSubKey,
				_Out_ PHKEY phkResult
			)
		{
			g_reg_open_key_w_call_count += 1;
			auto ret_val = base::std_call<LSTATUS>(real::RegOpenKeyW, hKey, lpSubKey, phkResult);
			if (g_reg_open_key_w_call_count < 3)
			{
				return ret_val;
			}
			if (!g_is_reg_open_key_w_uninstall)
			{
				if (nullptr != hook_t::RegOpenKeyW)
				{
					base::hook::uninstall(&hook_t::RegOpenKeyW);
					g_is_reg_open_key_w_uninstall = true;
				}
			}
			if (nullptr == lpSubKey)
			{
				return ret_val;
			}

			//如果能取到
			const std::wstring sub_key{ lpSubKey };
			const std::wstring_view sub_key_front{ xorstr_(L"Software\\Classes\\") };
			auto start_pos = sub_key.find(sub_key_front);

			//找不到就返回
			if (std::wstring::npos == start_pos)
			{
				return ret_val;
			}
			const auto sub_key_front_length = sub_key_front.length();
			start_pos += sub_key_front_length;
			OutputDebugStringEx(L"DEBUG_INFO | start_pos: %d", start_pos);
			const auto key_length = sub_key.length() - sub_key_front.length();
			//机器码长度(?)应为20个
			//e.g.: L"Software\\Classes\\ZQBKAF8AMQA0ADIANGA4"
			if (20 != key_length)
			{
				return ret_val;
			}
			OutputDebugStringEx(L"DEBUG_INFO | sub_key: %s", sub_key.c_str());
			auto key{ sub_key.substr(start_pos,20) };
			OutputDebugStringEx(L"DEBUG_INFO | key: %s", key.c_str());
			if (g_sub_key.empty())
			{
				g_sub_key = sub_key;
			}
			if (nullptr == g_phkey)
			{
				g_phkey = phkResult;
			}
			return ret_val;
		}
	}
	void Install()
	{
		if (0 != real::RegOpenKeyW)
		{
			return;
		}
		auto* const adv_api_32 = GetModuleHandleW(xorstr_(L"advapi32.dll"));
		if (nullptr == adv_api_32)
		{
			return;
		}
		real::RegOpenKeyW = reinterpret_cast<uintptr_t>(GetProcAddress(adv_api_32, xorstr_("RegOpenKeyW")));
		if (0 == real::RegOpenKeyW)
		{
			return;
		}
		base::hook::install(static_cast<uintptr_t*>(&real::RegOpenKeyW), \
			reinterpret_cast<uintptr_t>(fake::RegOpenKeyW), &hook_t::RegOpenKeyW);

		auto* const kernel_base = GetModuleHandleW(xorstr_(L"kernelbase.dll"));
		if (nullptr == kernel_base)
		{
			return;
		}
		real::RegQueryValueExW = reinterpret_cast<uintptr_t>(GetProcAddress(kernel_base, xorstr_("RegQueryValueExW")));
		if (0 == real::RegQueryValueExW)
		{
			return;
		}
		base::hook::install(static_cast<uintptr_t*>(&real::RegQueryValueExW), \
			reinterpret_cast<uintptr_t>(fake::RegQueryValueExW), &hook_t::RegQueryValueExW);
	}

	void UnInstall()
	{
		if (nullptr == hook_t::RegQueryValueExW)
		{
			return;
		}
		if (g_is_reg_query_value_w_uninstall)
		{
			return;
		}
		base::hook::uninstall(&hook_t::RegQueryValueExW);
		g_is_reg_query_value_w_uninstall = true;
	}
}

detour RegOpenKeyW的流程大致如下:

  1. 使用一个全局静态变量g_reg_open_key_w_call_count记录其被调用的次数。

  2. 当次数为3的时候进入我们的流程。

  3. 找到关键项并记录、记录HKey用于比对。

  4. 卸载hook。

detour RegQueryValueExW的流程大致如下:

     1. 定一个静态局部变量only_call_once用于确保流程只执行一次。

     2. 先判断lpValueName是不是L“Value”,若是,直接返回ERROR_FILE_NOT_FOUND告诉程序不存在这个项。

     3.调用原函数。

     4.用在detour RegOpenKeyW中获得的 HKEY和sub_key确保当前流程是读取试用起始日期。

     5.获取默认值的长度。

     6.获取由函数填充的lpData。

     7.生成一个合法的日期,然后替换它。

date_time.h:

#pragma once
#include <boost/date_time.hpp>

namespace ev_date_time
{
	using namespace boost::gregorian;
	class DateTime
	{
	public:
		DateTime();
		~DateTime() = default;
		date GetNow() const;
		static date::ymd_type GetTodayBase();
		static boost::posix_time::ptime GetToDay();
		int Gen() const;
		std::string GenString() const;

	private:
		date now_;
	};
}

date_time.cpp:

#include "date_time.h"

namespace ev_date_time
{
	DateTime::DateTime()
		:now_(day_clock::universal_day())
	{
	}

	date DateTime::GetNow() const
	{
		return now_;
	}

	date::ymd_type DateTime::GetTodayBase()
	{
		return day_clock::universal_day_ymd();
	}

	boost::posix_time::ptime DateTime::GetToDay()
	{
		const auto base = GetTodayBase();
		const boost::posix_time::ptime p(date(base.year, base.month, base.day));
		return p;
	}

	int DateTime::Gen() const
	{
		auto base = to_time_t(GetToDay());
		return static_cast<int>(base);
	}

	std::string DateTime::GenString() const
	{
		auto base = to_time_t(GetToDay());
		return std::to_string(base);
	}
}

大致阐述一下思路:

使用boost的day_clock::universal_day_ymd();获取当前的日期(精确到天)。

使用boost::posix_time::ptime创建“今天0点”这个时间点(有bug,东8区要减去8,不过无伤大雅)。

使用to_time_t()将它转换为unix时间戳。

3.总结

麻雀虽小,五脏俱全。



[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

最后于 2020-8-12 17:04 被黑洛编辑 ,原因:
上传的附件:
收藏
点赞3
打赏
分享
最新回复 (22)
雪    币: 6528
活跃值: (2618)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
Kisesy 2020-8-11 09:58
2
0
兄弟,有生成好的文件吗?
雪    币: 429
活跃值: (3487)
能力值: ( LV5,RANK:69 )
在线值:
发帖
回帖
粉丝
小菜鸟一 2020-8-12 06:13
3
0
写的很好,收藏了
雪    币: 1195
活跃值: (1125)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
初学小潘 2020-8-12 08:56
4
0
厉害了,收藏学习
雪    币: 1412
活跃值: (3246)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
方向感 2020-8-12 12:49
5
0

能否上传完整代码学习一下,这几个文件找不到

#include <base/hook/fp_call.h>

#include <base/hook/inline.h>

#include <base/util/xorstr.hpp>

#include <base/util/Debug.hpp>


最后于 2020-8-12 12:49 被方向感编辑 ,原因:
雪    币: 6124
活跃值: (4121)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
黑洛 1 2020-8-12 16:49
6
0
Kisesy 兄弟,有生成好的文件吗?
国产软件的话,不能提供bin。
雪    币: 6124
活跃值: (4121)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
黑洛 1 2020-8-12 16:53
7
0

代码上传到附件了。

config.h:

#define _BASE_API

Debug.hpp是调试用的,可以全部注释掉。

xorstr_字符串混淆库,可以去除,直接使用字符串

编译这份需要vs2019最新版本,并将C++语言标准设置为最新草案标准(C++20)


最后于 2020-8-12 17:07 被黑洛编辑 ,原因:
雪    币: 6124
活跃值: (4121)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
黑洛 1 2020-8-12 16:55
8
0
方向感 能否上传完整代码学习一下,这几个文件找不到#include&nbsp;&lt;base/hook/fp_call.h&gt;#include&nbsp;&lt; ...

xorstr_()不是必须的,直接用字符串就行了。

最后于 2020-8-12 16:55 被黑洛编辑 ,原因:
雪    币: 1378
活跃值: (3067)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
风中小筑V 2020-8-12 18:28
9
0

让我学到一招搜索call.不错.

2天之内7次重装系统,哈哈哈...

最后于 2020-8-12 18:35 被风中小筑V编辑 ,原因:
雪    币: 6124
活跃值: (4121)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
黑洛 1 2020-8-12 19:09
10
0
风中小筑V 让我学到一招搜索call.不错.2天之内7次重装系统,哈哈哈...
搜索跨模块调用挺有用的,你看看调用了哪些api就大概能知道程序做了什么,还包括程序自身的模块间的调用,这样分析起来定位比较快一点。
雪    币: 3630
活跃值: (3595)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yaoguen 2020-8-14 20:01
11
0
谢谢,能不能把hooks.h也传一下
雪    币: 3630
活跃值: (3595)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yaoguen 2020-8-14 20:27
12
0

还差base::hook吧


最后于 2020-8-14 22:55 被yaoguen编辑 ,原因:
雪    币: 3630
活跃值: (3595)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yaoguen 2020-8-14 22:12
13
0
黑洛 搜索跨模块调用挺有用的,你看看调用了哪些api就大概能知道程序做了什么,还包括程序自身的模块间的调用,这样分析起来定位比较快一点。
能不能把hooks.h也传一下,谢谢
雪    币: 6124
活跃值: (4121)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
黑洛 1 2020-8-15 01:21
14
0
yaoguen 能不能把hooks.h也传一下,谢谢
只有两个函数定义void Install()和void Uninstall()
雪    币: 3630
活跃值: (3595)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yaoguen 2020-8-16 08:51
15
0
黑洛 只有两个函数定义void Install()和void Uninstall()
base::hook::install(static_cast<uintptr_t*>(&real::RegOpenKeyW), \
你好,是不是还有一个类,base::hook::install(static_cast<uintptr_t*>(&real::RegOpenKeyW), \reinterpret_cast<uintptr_t>(fake::RegOpenKeyW), &hook_t::RegOpenKeyW);
base::hook::uninstall(&hook_t::RegQueryValueExW);这两个函数没找到
雪    币: 3630
活跃值: (3595)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yaoguen 2020-8-16 09:26
16
0
现在是出现"unsigned __int64 hooks::real::RegQueryValueExW" (?RegQueryValueExW@real@hooks@@3_KA) 已经在 DllModule.obj 中定义错误
雪    币: 3630
活跃值: (3595)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yaoguen 2020-8-16 10:34
17
0
编译成功,但是没起作用,不知道怎么回事,我是用的64位程序
雪    币: 3630
活跃值: (3595)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yaoguen 2020-8-16 11:20
18
0
可以用了,但有两个问题,第一个是要删除注册表中value键值,第二个是点关于后不管点确定还是关闭程序都会退出。
雪    币: 2664
活跃值: (3822)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
值得怀疑 2020-8-16 11:47
19
0
风中小筑V 让我学到一招搜索call.不错.2天之内7次重装系统,哈哈哈...
哪里有搜call?
雪    币: 1378
活跃值: (3067)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
风中小筑V 2020-8-17 09:42
20
0
值得怀疑 哪里有搜call?
就第一步.   右键 搜索->当前模块-> 调用.    他第一张图片在第二步过滤了.
雪    币: 4024
活跃值: (5843)
能力值: ( LV7,RANK:102 )
在线值:
发帖
回帖
粉丝
fjqisba 2020-8-20 20:36
21
0
不是特别好用,经常解析不出xml,json。得结合其他编辑器,例如NotePad++,010Editor一起使用
雪    币: 1866
活跃值: (2722)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
Nudest 2022-9-14 12:06
22
0
网上有个一字节爆破的    通过32位分析64位的
雪    币: 2875
活跃值: (830)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
A 'ゞ.不走尋常路 2022-11-7 14:45
23
0
64位的分析在哪里呢
游客
登录 | 注册 方可回帖
返回