前言
前面已经对这个软件的验证进行了比较清晰的分析,
接下来就该写出一个完整工具了。
思路呢?
首先总不能把fiddler
作为工具的一部分吧。
那么需要模拟收发包才行,
在开始分析时就提过了原程序使用的cpprestsdk
,故 cpprestsdk
将成为突破口。
方案一:cpprestsdk
内部使用的是 WinHttpxxx
函数收发包,
由于 cpprestsdk
利用的是异步收发,
在尝试了 HOOK
WinHttpxxx
系列函数直接返回后,
总会出现莫名其妙的崩溃,
就算尝试交付其完成函数也是一个结果。
为了测试这种方案,耗费了数个小时,有兴趣的可以试试。
里面涉及多线程及锁重入问题,就不再细说了。
方案二:
既然从底层劫持收发包不行,那能不能从库本身入手呢?
答案是可以的。
下面是 cpprestsdk
在编写客户端时常用的写法,
http_client client(U("http://www.bing.com/"));
// Build request URI and start the request.
uri_builder builder(U("/search"));
builder.append_query(U("q"), U("cpprestsdk github"));
return client.request(methods::GET, builder.to_string());
如果我们能替换掉 "http://www.bing.com/" 为指向本地的通信,不就能替换掉 Response
包了么?
从cpprestsdk
的导出函数下断可以轻松定位到相关代码。
首先尝试修改了 uri
给定的参数,
但发现程序会出现失败提示,
原因是,在调用uri
时,传入的字符串只是一个副本,
修改副本并不能改变本体,在其他地方可能不会使用这个副本,所以通信会失败。
通过继续分析,发现真正的本体会经过 mf140u.#1663
,就是上图画红线那个函数调用。
于是继续尝试编写代码 HOOK
之并替换。
需要注意的是,替换的串最好不要长于原始串,
由于没有分析原始串内存是位于哪里,溢出可能会崩溃。
注入进程
这里使用常见的DLL搜索顺序劫持,劫持掉 winhttp.dll
关于劫持怎么写,
可以去看我之前发的
简单改下入口就行了
BOOL APIENTRY DllMain(HMODULE hModule, DWORD dwReason, PVOID pvReserved)
{
if (dwReason == DLL_PROCESS_ATTACH)
{
DisableThreadLibraryCalls(hModule);
if (Load() && Init())
{
// 功能入口
CloseHandle(CreateThread(NULL, NULL, Do, NULL, NULL, NULL));
}
}
else if (dwReason == DLL_PROCESS_DETACH)
{
Free();
}
return TRUE;
}
HOOK与转向
这段代码使用了 Detour
框架来 hook
static PVOID cpprest_uri = 0;
static const wchar_t* newUri = L"http://127.0.0.1:13303/";
static wchar_t** sstr = NULL;
static bool bfix = false;
inline void FixUrl() {
if(!wcscmp(*sstr, L"https://www.codecode.net/")) {
memcpy(*sstr, newUri, (wcslen(newUri) + 1) * 2);
bfix = true;
}
}
void __declspec(naked) FixFunc() {
__asm {
mov sstr,ecx
pushad
pushfd
}
if (!bfix) FixUrl();
__asm {
popfd
popad
jmp cpprest_uri
}
}
bool dropHook() {
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
if (cpprest_uri) DetourDetach((PVOID*)&cpprest_uri, FixFunc);
return DetourTransactionCommit() == NO_ERROR;
}
bool setHook() {
auto moduleBase = LoadLibraryA("mfc140u.dll");
if (!moduleBase)
return FALSE;
cpprest_uri = GetProcAddress(moduleBase, (char*)1663);
if (!cpprest_uri)
return FALSE;
// _CIP<IBindStatusCallback, &_GUID const IID_IBindStatusCallback>::operator IBindStatusCallback * (void)mfc140u
DetourTransactionBegin();
DetourUpdateThread(GetCurrentThread());
if (cpprest_uri) DetourAttach((PVOID*)&cpprest_uri, FixFunc);
return DetourTransactionCommit() == NO_ERROR;
}
DWORD WINAPI Do(LPVOID lpThreadParameter) {
setHook();
// 后面才用到
startServer();
return 0;
}
注意 FixUrl
的用处是防止 release
编译时给优化了,inline
在此并不会起作用,可有可无。dropHook
并未调用,有兴趣可以自己试试。
http simple server
我直接考虑使用第三方框架,而没有在以前写的 server
上改
开始时打算使用程序自带的 cpprestsdk
,后面感觉还是用更具备通用性的框架比较好。
对于这个第三方的server
库有几点要求
- 轻量级,易嵌入的,不需要过于复杂的功能,不需要考虑并发,异步
- 最好支持
route
,便于编写 如果能支持https
以及代理转发就更好了
翻来覆去看了几个框架,最终选择了基于mongoose
的简易server
在这份代码的基础上进行简单修改,使其能符合使用需求。
// startServer.cpp
static char szCurName[MAX_PATH];
static std::string jkey;
static const char* head =
"Content-Type: application/json; charset=utf-8\r\n"
"Connection: close\r\n"
"Cache-Control : no-store\r\n"
"Pragma : no-cache\r\n";
bool handle_token(std::string url, std::string body, mg_connection* c, OnRspCallback rsp_callback)
{
static const char* token =
"{\"access_token\": \"\"}";
mg_send_response_line(c, 200, head);
mg_printf(c, "%s", token);
c->flags |= MG_F_SEND_AND_CLOSE;
return true;
}
bool handle_key(std::string url, std::string body, mg_connection* c, OnRspCallback rsp_callback)
{
mg_send_response_line(c, 200, head);
mg_printf(c, "%s", jkey.c_str());
c->flags |= MG_F_SEND_AND_CLOSE;
return true;
}
void startServer()
{
GetModuleFileNameA(NULL, szCurName, MAX_PATH);
PathStripPathA(szCurName);
{ // 利用 RAII 回收相关资源
std::string defkey = "[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]";
using nlohmann::json;
std::ifstream in("winhttp_3deskey.json");
if (in) // 有该文件
{
std::string sjson((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>());
try {
auto js = json::parse(sjson)[szCurName];
// 不依赖库异常,自己拆开判断一下
if (!js.empty()) defkey = js.get<std::string>();
}
catch (json::exception&) {
// do nothing
}
}
jkey =
std::string("{"
"\"message\":\"0\","
"\"number\" : 0,"
"\"ban_count\":0,"
"\"email\":\"\","
"\"name\":\"\","
"\"username\":\"Welcome\","
"\"inforId\":\"\","
"\"inforContent\":\"Welcome!\","
"\"threedeskey\":") +
defkey +
std::string(","
"\"auth_state\":true"
"}");
}
auto http_server = std::shared_ptr<HttpServer>(new HttpServer);
http_server->Init("13303");
// add handler
http_server->AddHandler("/oauth/token", handle_token);
http_server->AddHandler("/api/v4/clientauthorizedcode", handle_key);
http_server->Start();
}
// http_server.cpp
void HttpServer::HandleHttpEvent(mg_connection* connection, http_message* http_req)
{
std::string req_str = std::string(http_req->message.p, http_req->message.len);
// 先过滤是否已注册的函数回调
std::string url = std::string(http_req->uri.p, http_req->uri.len);
std::string body = std::string(http_req->body.p, http_req->body.len);
auto it = s_handler_map.find(url);
if (it != s_handler_map.end())
{
ReqHandler handle_func = it->second;
handle_func(url, body, connection, &HttpServer::SendHttpRsp);
}
// 其他请求 正常应该是转发逻辑,这里就临时这样实现一下
else if (route_check(http_req, (char*)"/")) // index page
{
mg_send_response_line(connection, 200, "Content-Type: text/html\r\n"
"Connection: close\r\n");
mg_printf(connection, "%s", "<script>location.href = 'https://www.codecode.net';</script>");
connection->flags |= MG_F_SEND_AND_CLOSE;
}
else
{
SendHttpRsp(connection, "welcome to jsonapi");
}
}
编译生成
配置格式:
{
"//": " 进程名 : 3deskey 当不填key时会使用默认的key 0-15,在需要key的几款软件中会崩溃",
"CP Lab.exe" : "[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]"
}
验证:
其中 CP
,CS
,DS
等均能正常工作,但并未彻底测试。
由于使用了 SMC
所有授权的key
都是一样的 不存在身份泄露风险
当然这个key
只有在相应的版本(强制更新版本)下才有意义
另外这个key
不能轻易更换,否则会导致大量用户需要升级,根据包内容看,并没有强制升级功能
如果你愿意分享key
来供研究学习测试
欢迎在评论区留言 软件 + 版本号 + key
当前版本号:
Engintime ASM Lab Setup_3.0.9.msi
Engintime C&C++ Lab Setup_3.0.16.msi
Engintime CP Lab Setup_3.0.9.msi
Engintime CS Lab Setup_3.0.7.msi
Engintime Dream Logic 2019 Setup_3.0.24.msi
Engintime DS Lab Setup_3.0.13.msi
Engintime Eclipse Setup_windows_x64_3.0.1.msi
Engintime Linux Lab Setup_3.0.12.msi
Engintime OS Lab Setup_3.0.8.msi
总结
这个系列到这里就算完整的结束了。
中间遇到了许多细微的细节问题,由于篇幅就不一一道来。
工程本身就不公开了,中了夹杂了很多测试代码没整理,贸然开源反而混乱。
上文中的代码片段包含了所有经过整理的核心代码,更具备参考价值。
总结成一句话,逆向分析靠猜想和验证来驱动,正向开发靠需求驱动。
其他内容
另外在分析完成之后,还简单分析了一下它是如何实现调试的。
利用了 GDB MI
接口。
关键词:GDB MI
CreateProcessW
CreatePipe
ReadFile
WriteFile
管道通信
文章免责声明
本系列文章仅供研究学习,切勿用做非法之事。
如果喜欢本系列软件并准备长期使用,请购买正版,支持软件开发者继续改进和增强本软件的功能。
网站免责声明
本文链接:https://www.holdheart.com/archives/reverse_engineering/95.html
本文为 faTe 原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
你好朋友,收到OS lab可以打开,放入pj文件显示用户名密码不对。是不是官方修改了后台程序?之前文件打不开可能是没安装好,重新安装高点版本的Dream Logic 2019 还是显示用户名密码不对。唉。
By liyicom at December 26th, 2020 at 02:05 pm.
你好,我又试了一下以前能用的几个比您这个高点的版本,最近都没法用了,不知道咋回事。以前的版本能否发我一份呢?谢谢!是不是升级屏蔽了呢?不放这两个pj文件可以打开,放进去就打不开了。显示 无法找到函数winhttpPacjswokerMain 程序无法运行。
By liyicom at December 18th, 2020 at 01:17 pm.
@liyicom
发到你的邮箱了
By faTe at December 25th, 2020 at 02:58 pm.
你好 请问 现在显示无法找到函数winhttpPacjswokerMain 程序无法运行是咋回事呢?是把Engintime.rar这两个文件直接放到安装目录的把?非常感谢!
By liyicom at November 26th, 2020 at 12:47 am.
@liyicom
是的,直接将 Engintime.rar 中的文件解压至安装后的根目录即可,若无法运行,需要根据具体的信息进行分析,在测试时,只有 CP,CS,DS 几款软件是不需要正确的key就能运行的。
By faTe at December 4th, 2020 at 04:06 pm.