坚决抵制盗版行为,本文仅作 OSX 软件逆向分析及 OSX 软件安全相关的研究目的。
前言
前面已经对这个软件的验证进行了比较清晰的分析,
接下来就该写出一个完整工具了。
思路呢?
首先总不能把fiddler
作为工具的一部分吧。
那么需要模拟收发包才行,
在开始分析时就提过了原程序使用的cpprestsdk
,故 cpprestsdk
将成为突破口。
方案一:
cpprestsdk
内部使用的是 WinHttpxxx
函数收发包,
由于 cpprestsdk
利用的是异步收发,
在尝试了 HOOK
WinHttpxxx
系列函数直接返回后,
总会出现莫名其妙的崩溃,
就算尝试交付其完成函数也是一个结果。
为了测试这种方案,耗费了数个小时,有兴趣的可以试试。
里面涉及多线程及锁重入问题,就不再细说了。
方案二:
既然从底层劫持收发包不行,那能不能从库本身入手呢?
答案是可以的。
下面是 cpprestsdk
在编写客户端时常用的写法,
1 2 3 4 5
| http_client client(U("http://www.bing.com/"));
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
包了么?
![[Engintime_protocol_analysis_3/image-20221010175446393.png]]
从cpprestsdk
的导出函数下断可以轻松定位到相关代码。
首先尝试修改了 uri
给定的参数,
但发现程序会出现失败提示,
原因是,在调用uri
时,传入的字符串只是一个副本,
修改副本并不能改变本体,在其他地方可能不会使用这个副本,所以通信会失败。
通过继续分析,发现真正的本体会经过 mf140u.#1663
,就是上图画红线那个函数调用。
于是继续尝试编写代码 HOOK
之并替换。
需要注意的是,替换的串最好不要长于原始串,
由于没有分析原始串内存是位于哪里,溢出可能会崩溃。
注入进程
这里使用常见的映像劫持,劫持掉 winhttp.dll
关于劫持怎么写,
在 [巧用DLL劫持做黑盒分析] 一文中已详细描述。
简单改下入口就行了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| 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;
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
在这份代码的基础上进行简单修改,使其能符合使用需求。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81
| 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);
{ 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&) { } }
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"); http_server->AddHandler("/oauth/token", handle_token); http_server->AddHandler("/api/v4/clientauthorizedcode", handle_key); http_server->Start(); }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
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*)"/")) { 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"); } }
|
编译生成
配置格式:
1 2 3 4
| { "//": " 进程名 : 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
管道通信
文章免责声明
本系列文章仅供研究学习,切勿用做非法之事。
如果喜欢本系列软件并准备长期使用,请购买正版,支持软件开发者继续改进和增强本软件的功能。