前几天做了个cpp程序但是考虑安全做了些防护,来看看怎么做的。
#define _WIN32_WINNT 0x0600
#include <windows.h>
#include <wininet.h>
#include <string>
#include <sstream>
#include <iomanip>
#include <ctime>
#include <wincrypt.h>
#include "VMProtectSDK.h"
#pragma comment(lib, "wininet.lib")
#pragma comment(lib, "crypt32.lib")
using namespace std;
string GetSecretKey(){
//这里原来是和服务器通信的token这里我给hide了
const char* part1 = "";
const char* part2 = "";
const char* part3 = "";
const char* part4 = "";
return string(part1) + part2 + part3 + part4;
}
void SecureClearString(string& str) {
if (str.empty()) return;
VirtualLock((void*)str.c_str(), str.length());
RtlZeroMemory((void*)str.c_str(), str.length());
VirtualUnlock((void*)str.c_str(), str.length());
str.clear();
}
bool IsApiHooked(const char* module, const char* func) {
HMODULE hMod = GetModuleHandleA(module);
if (!hMod) return false;
FARPROC addr = GetProcAddress(hMod, func);
if (!addr) return false;
unsigned char* p = (unsigned char*)addr;
if (*p == 0xCC) return true;
if (*p == 0xE9) return true;
if (*p == 0x68 && p[5] == 0xC3) return true;
return false;
}
bool IsHardwareBreakpointPresent() {
CONTEXT ctx = { 0 };
ctx.ContextFlags = CONTEXT_DEBUG_REGISTERS;
if (GetThreadContext(GetCurrentThread(), &ctx)) {
if (ctx.Dr0 || ctx.Dr1 || ctx.Dr2 || ctx.Dr3) {
return true;
}
}
return false;
}
bool IsRunningInVM() {
if (GetModuleHandleA("VBoxGuest")) return true;
return false;
}
string md5_multipart(const string& part1, const string& part2, const string& part3) {
HCRYPTPROV hProv = 0;
HCRYPTHASH hHash = 0;
BYTE pbHash[16];
DWORD dwHashLen = 16;
string result = "";
if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT)) {
return "";
}
if (!CryptCreateHash(hProv, CALG_MD5, 0, 0, &hHash)) {
CryptReleaseContext(hProv, 0);
return "";
}
CryptHashData(hHash, (PBYTE)(part2.c_str()), part2.length(), 0);
CryptHashData(hHash, (PBYTE)(part1.c_str()), part1.length(), 0);
CryptHashData(hHash, (PBYTE)(part3.c_str()), part3.length(), 0);
CryptGetHashParam(hHash, HP_HASHVAL, pbHash, &dwHashLen, 0);
stringstream ss;
for (int i = 0; i < dwHashLen; i++) {
ss << hex << setw(2) << setfill('0') << (int)pbHash[i];
}
result = ss.str();
CryptDestroyHash(hHash);
CryptReleaseContext(hProv, 0);
return result;
}
string fetchCipher() {
VMProtectBeginUltra("fetchCipher");
if (IsRunningInVM()) {
VMProtectEnd();
return "ERROR_VM_DETECTED";
}
if (IsHardwareBreakpointPresent()) {
VMProtectEnd();
return "ERROR_HARDWARE_BP";
}
if (IsApiHooked("crypt32.dll", "CryptHashData") ||
IsApiHooked("wininet.dll", "HttpOpenRequestA") ||
IsApiHooked("wininet.dll", "InternetConnectA")) {
VMProtectEnd();
return "ERROR_HOOK_DETECTED";
}
string token = VMProtectDecryptStringA("Sakurazuki291A");
string secret = GetSecretKey();
string timestamp = to_string(time(nullptr));
string sign = md5_multipart(token, timestamp, secret);
string base_path = VMProtectDecryptStringA("/proxy.php");
string url = base_path + "?token=" + token + "&t=" + timestamp + "&sign=" + sign;
HINTERNET hSession = InternetOpenA("User-Agent", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0);
HINTERNET hConnect = InternetConnectA(hSession, "moongs.asia", INTERNET_DEFAULT_HTTPS_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0);
HINTERNET hRequest = HttpOpenRequestA(hConnect, "GET", url.c_str(), NULL, NULL, NULL, INTERNET_FLAG_SECURE, 0);
BOOL bSent = HttpSendRequestA(hRequest, NULL, 0, NULL, 0);
string response;
if (bSent) {
char buffer[4096];
DWORD bytesRead;
while (InternetReadFile(hRequest, buffer, sizeof(buffer) - 1, &bytesRead) && bytesRead > 0) {
buffer[bytesRead] = '\0';
response += buffer;
}
}
InternetCloseHandle(hRequest);
InternetCloseHandle(hConnect);
InternetCloseHandle(hSession);
string key = "\"cipher\":\"";
size_t start = response.find(key);
if (start != string::npos) {
start += key.length();
size_t end = response.find("\"", start);
if (end != string::npos) {
string result = response.substr(start, end - start);
SecureClearString(token);
SecureClearString(secret);
SecureClearString(timestamp);
SecureClearString(sign);
SecureClearString(url);
VMProtectEnd();
return result;
}
}
SecureClearString(token);
SecureClearString(secret);
SecureClearString(timestamp);
SecureClearString(sign);
SecureClearString(url);
VMProtectEnd();
return "ERROR";
}
HWND resultLabel = NULL;
HWND statusLabel = NULL;
DWORD WINAPI fetchCipherThread(LPVOID lpParam) {
SetWindowTextA(statusLabel, VMProtectDecryptStringA("Requesting..."));
string result = fetchCipher();
if (result == "ERROR" || result == "ERROR_HOOK_DETECTED" ||
result == "ERROR_HARDWARE_BP" || result == "ERROR_VM_DETECTED") {
SetWindowTextA(statusLabel, VMProtectDecryptStringA("Failed"));
SetWindowTextA(resultLabel, "");
} else {
SetWindowTextA(statusLabel, VMProtectDecryptStringA("Success"));
SetWindowTextA(resultLabel, ("Cipher: " + result).c_str());
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_CREATE: {
CreateWindowA("STATIC", VMProtectDecryptStringA("Cipher Tool"), WS_VISIBLE | WS_CHILD | SS_CENTER,
0, 10, 400, 30, hwnd, NULL, NULL, NULL);
CreateWindowA("BUTTON", VMProtectDecryptStringA("Get Cipher"), WS_VISIBLE | WS_CHILD,
140, 50, 120, 40, hwnd, (HMENU)1, NULL, NULL);
statusLabel = CreateWindowA("STATIC", VMProtectDecryptStringA("Click to get cipher"), WS_VISIBLE | WS_CHILD | SS_CENTER,
0, 110, 400, 25, hwnd, NULL, NULL, NULL);
resultLabel = CreateWindowA("STATIC", "", WS_VISIBLE | WS_CHILD | SS_CENTER,
0, 150, 400, 35, hwnd, NULL, NULL, NULL);
CreateWindowA("STATIC", VMProtectDecryptStringA("(C) Moon STUDIO / Sakurazuki"), WS_VISIBLE | WS_CHILD | SS_CENTER,
0, 195, 400, 20, hwnd, NULL, NULL, NULL);
break;
}
case WM_COMMAND: {
if (LOWORD(wParam) == 1) {
SetWindowTextA(statusLabel, VMProtectDecryptStringA("Requesting..."));
SetWindowTextA(resultLabel, "");
CreateThread(NULL, 0, fetchCipherThread, NULL, 0, NULL);
}
break;
}
case WM_DESTROY: {
PostQuitMessage(0);
return 0;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
VMProtectBeginUltra("WinMain");
WNDCLASSA wc = {};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = "CipherWindowClass";
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClassA(&wc);
HWND hwnd = CreateWindowExA(
0,
"CipherWindowClass",
VMProtectDecryptStringA("Cipher Getter"),
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX,
CW_USEDEFAULT, CW_USEDEFAULT, 410, 270,
NULL, NULL, hInstance, NULL
);
ShowWindow(hwnd, nCmdShow);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
VMProtectEnd();
return 0;
}
程序本身其实并没有什么 所以主要就是来看这上面做的一些安全措施
string timestamp = to_string(time(nullptr));
string sign = md5(timestamp + token + secret);
string url = "/proxy.php?token=" + token + "&t=" + timestamp + "&sign=" + sign;
这里用当前时间戳 + Token + Secret 拼成字符串计算MD5,然后发起请求,从服务器返回的json中提取出所需的片段再在UI里表示出来
好的那么再来看网页端:
if ($token !== '-----') {
//403
}
这里是对客户端发送的GET进行校验,如果token参数不等于这个值那么就返回403,
if (abs($now - intval($timestamp)) > 0.7) {
// 403
}
这里是校验时间窗口,为了防止重放只留了0.7s(所以会导致某些网特别卡的使用不了)
$expected_sign = md5($timestamp . $token . $secret);
if ($sign !== $expected_sign) {
// 403
}
确保请求没有被篡改,只有知道 Secret 的客户端才能生成正确的签名
$nonce_file = '/tmp/proxy_nonces/' . md5($token . '_' . $timestamp);
if (file_exists($nonce_file)) {
// 返回 403
}
file_put_contents($nonce_file, 'used');
touch($nonce_file, $timestamp + 10);
nonce防止重放,一个token只能用10s,过了10s自动过期
下一步就是给另外一层服务器转发并且将结果返回了,这里就不放了。
那么cpp端还使用VMP超级模式进行了虚拟化和变异这点在代码中也是可以看出来的。