作者也学C++啊,加个好友吧
背景:
不知为何,儿时记忆突然如潮水般涌入心头,使我感慨万分。
记忆其中之一的关键字就为'锁机',其二就为'破解',由此而延伸回忆出儿时曾想过的目标 '编写自动破解锁机工具'。 以前儿时在某个与破解有关的QQ群中看到过有人使用易语言编写的自动破解锁机程序,儿时的我也想编写一个,不过由于回忆不起的未知原因并未完成。 此文就为儿时记忆所激发而成,实现那儿时愿景~~~
使用工具:
Visual Studio 2022
VMware
ollydbg
Xenos
首先肯定是需要知锁机病毒其原理,再根据其原理编写应对方针
中文网络中常见的锁机病毒皆为易语言编写,其源码很容易在互联网中找到。具体的锁机病毒还有更详细的分类:|用户锁|硬盘锁|屏幕锁| 前两个最为常见,逐个分析
用户锁:
典型例子为
如何用易语言制作简单的锁机软件_360新知 (XXXXXX)
将例子编译成可执行文件,将其上传至云沙箱可知
(据我所知只有此云沙箱会显示具体的api,其余的云沙箱都模糊的写到"创建进程")
调用了名为"CreateProcessInternalW"的api
其中的CommandLine参数就为执行的命令 “C:\Windows\system32\net1 user admin 123456”
不过此文并不是HOOK “CreateProcessInternalW” 而是最基础的 "CreateProcessA"
打开OD,断点 "CreateProcessA",运行程序等待触发断点,断下后可发现清晰的结构,其中的“CommandLine”即为执行的命令。
知道其调用的api后就可使用C++编写针对性程序
编写用户锁C++ DLL程序:
最常见的用于|拦截|监视|劫持|api的方法为'Inline Hook',此程序就是依据此原理展开的。
首先需要去学习学习(抄袭抄袭)有关"C++ HOOK api"的信息 搜索相关信息可发现(找半天)
C/C++ Inline Hook 钩子编写技巧 - lyshark - 博客园 (XXXXXXXXXXX)
Basic Windows API Hooking. API hooking has been covered… | by Jayson Hurst | Geek Culture | Medium (我会认为这个更加直白易懂)
[原创]万字长文!inlinehook看这一篇足够了!-软件逆向-看雪-安全社区|安全招聘|XXXXXXXXXX(此程序参考[抄袭]此贴)
更为具体为此:
CreateProcessA 函数 (processthreadsapi.h) - Win32 apps | Microsoft Learn
将SetWindowTextA 更换为我们的 CreateProcessA 得以下代码
#include <Windows.h> #include <string> #include <iostream> #include "pch.h" #include "stdio.h" #include<cstring> #include<sstream> using namespace std; DWORD jump = 0; __declspec(naked) bool _stdcall Transfer( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation) { __asm { mov edi, edi push ebp mov ebp, esp mov ebx, jump jmp ebx } } BOOL WINAPI hookedCreateProcessA( LPCSTR lpApplicationName, LPSTR lpCommandLine, LPSECURITY_ATTRIBUTES lpProcessAttributes, LPSECURITY_ATTRIBUTES lpThreadAttributes, BOOL bInheritHandles, DWORD dwCreationFlags, LPVOID lpEnvironment, LPCSTR lpCurrentDirectory, LPSTARTUPINFOA lpStartupInfo, LPPROCESS_INFORMATION lpProcessInformation ) { std::string messageA = " Commnd: " + std::string(lpCommandLine); //std::string messageB = std::string(lpApplicationName) + messageA; MessageBoxA(NULL, messageA.c_str(), "捕获执行命令", MB_OK); LPSTR lpCommandLineA = const_cast<LPSTR>(""); BOOL result = Transfer(lpApplicationName, lpCommandLineA, lpProcessAttributes, lpThreadAttributes, bInheritHandles, dwCreationFlags, lpEnvironment, lpCurrentDirectory, lpStartupInfo, lpProcessInformation); return result; } bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid) { HMODULE hwnd = GetModuleHandle(TEXT("kernel32.dll")); DWORD base = (DWORD)GetProcAddress(hwnd, "CreateProcessA"); DWORD oldProtect = 0; char message[256]; //sprintf_s(message, "CreateProcessA address: %08X", base); //MessageBoxA(NULL, message, "Information", MB_OK); if (VirtualProtect((LPVOID)base, 5, PAGE_EXECUTE_READWRITE, &oldProtect)) { DWORD value = (DWORD)hookedCreateProcessA - base - 5; jump = base + 5; __asm { mov eax, base mov byte ptr[eax], 0xe9 inc eax mov ebx, value mov dword ptr[eax], ebx } VirtualProtect((LPVOID)base, 5, oldProtect, &oldProtect); } return true; }
由于对方运行在32位 所以我们的DLL也得编译成32位的
编译得
使用现成工具 Xenos 进行注入 可以看见成功的拦截了执行命令
顺带看看是否符合相关帖子描述
很显然的符合
跳回代码就是像原来的一样,前面三个像是一个固定的'头'。后面的就是将jump赋值给ebx, jmp ebx 就是跳到ebx的位置 。这个jump值就是 75842D70 +5 就等于POP的位置,相当于跳过了上面的 “jmp到dll函数”,再往下走就是跳转到正常的"CreateProcessA"了。
为什么有前面这三个东西:
Win32API起始处的mov edi, edi与用户空间InlineHook - 孤影对酌 - 博客园 (XXXXXXXXXXX)
栈帧ebp,esp详解_压入ret后,继续压入当前ebp,然后用当前esp代替ebp-CSDN博客
硬盘锁:
硬盘锁几乎都是使用一个名叫'无名模块'中的一个函数'物理硬盘_新版无名神锁',易语言的模块实际上是可以直接转换为源代码的。
通过网络搜索的转换工具转换可得:
其中最关键的为 "物理硬盘_写硬盘扇区"
可以直观的看到他调用了什么api,具体搜索搜相关api的功能就会发现,我们只需要hook WriteFile 即可。
WriteFile 函数 (fileapi.h) - Win32 apps | Microsoft Learn
查看官方文件可知其lpBuffer为写入数据的地址
先用OD看一看
第一次
搜索内存 然后转到对应地址
可以发现并没有什么有用信息,因为在上面的易语言源码中可以看到,写了两次数据,第一个是 "匿名局部变量_4 = 物理硬盘_读硬盘扇区 (0, 1)" ,第二个才是真正的有关数据。
第二次触发断点
搜索内存然后转到对应地址
成功找到,其中AAAAA123为密码 BBBB234为显示信息 错误信息查看上面的源码可知 实际上是'参考' 也就类似windows api的那种out 返回有没有加密成功的 那时候我没看见所以添加了一串字符串
编写硬盘锁C++ DLL程序:
如何实现C++读写进程内存值_c++读取内存中的数值-CSDN博客
由于我后面的fwrite是需要提供指针的,而这个文章内是int变量,我就改成了 char*,结果并未返回出正确的指针或者是fwirte读不了那个指针。
询问了一下chatgpt,直接变成主动提供了,创建了一个指针,然后输入进了为out的lpBuffer。结果居然还成功了。
chatgpt提供的方法
之后就更离奇了
char *bufferContentB = new char[nNumberOfBytesToWrite]; 我抄了一下上面的代码创建了一个空的新指针给到下面的
BOOL result = Transfer(hFile, bufferContentB, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped);
结果写出的是和lpBuffer一模一样的数据,重启一样锁机了。。。 (我怀疑都不用添加ReadProcessMemory,直接用定义的新指针就可以写出了)
问了一下chatgpt后给我加上了memset,结果一看output.txt全是空格了,写出txt可是在前面,并且还是追加。。。
顺带我加上了个信息框 看看它通过了几次,结果就是弹出了无数次的信息框。。。 (实际上在DllMain添加个信息框也会发现被执行了好几遍)
我还尝试过直接return true,结果就是写出的文本内容不对,写出的应该只有第一次执行的“物理硬盘_写硬盘扇区”。
要找出这些原因怕是耗时巨大,所以就直接蒙着眼睛当看不见说:“算了虚拟机无所谓,点个恢复快照的事情,让它正常写出,能读出数据写到txt文本就行,直接不做了睡大觉。”
#include <Windows.h> #include <string> #include <iostream> #include "pch.h" #include "stdio.h" #include <cstring> #include <sstream> #include <fstream> using namespace std; DWORD jump = 0; __declspec(naked) BOOL _stdcall Transfer( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped ) { __asm { mov edi, edi push ebp mov ebp, esp mov ebx, jump jmp ebx } } BOOL WINAPI hookedWriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped ) { char* bufferContent = new char[nNumberOfBytesToWrite]; SIZE_T bytesRead; if (ReadProcessMemory(GetCurrentProcess(), lpBuffer, bufferContent, nNumberOfBytesToWrite, &bytesRead)) { FILE* file = fopen("output.txt", "ab"); if (file == NULL) { return 1; } fwrite(bufferContent, bytesRead, 1, file); fclose(file); } delete[] bufferContent; //char* bufferContentB = new char[nNumberOfBytesToWrite]; //memset(bufferContentB, 0, nNumberOfBytesToWrite); BOOL result = Transfer(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped); //delete[] bufferContentB; return result; } bool APIENTRY DllMain(HANDLE handle, DWORD dword, LPVOID lpvoid) { HMODULE hwnd = GetModuleHandle(TEXT("kernel32.dll")); DWORD base = (DWORD)GetProcAddress(hwnd, "WriteFile"); DWORD oldProtect = 0; char message[256]; sprintf_s(message, "CreateProcessA address: 0x%08X", base); MessageBoxA(NULL, message, "Information", MB_OK); if (VirtualProtect((LPVOID)base, 5, PAGE_EXECUTE_READWRITE, &oldProtect)) { DWORD value = (DWORD)hookedWriteFile - base - 5; jump = base + 5; __asm { mov eax, base mov byte ptr[eax], 0xe9 inc eax mov ebx, value mov dword ptr[eax], ebx } VirtualProtect((LPVOID)base, 5, oldProtect, &oldProtect); } return true; }
失败尝试的之一 这样后的output.txt全都是空格
char* bufferContentB = new char[nNumberOfBytesToWrite]; memset(bufferContentB, 0, nNumberOfBytesToWrite); BOOL result = Transfer(hFile, lpBuffer, nNumberOfBytesToWrite, lpNumberOfBytesWritten, lpOverlapped); delete[] bufferContentB;
DLL注入器:
我一开始想着直接找chatgpt写一个,懒得去找这找那了,结果运行后发现弹出了注入成功,但是实际上并没有注入成功,主要是还没有任何报错,我就去全互联网寻了个遍,花费了大量时间,发现都没有用。
然后我看了一遍后,在想这个 "LoadLibraryW"为什么是W不是A或者空。然后我就去替换成了"LoadLibraryA",结果。。。就成功了。
“A” 后缀:代表 ANSI 版本。这个版本的函数使用单字节字符集,主要是 ASCII。例如,WNDCLASSA 是一个处理 ANSI 窗口类的结构。
“W” 后缀:代表 Unicode 版本。这个版本的函数使用双字节字符集,支持多种语言字符。例如,WNDCLASSW 是一个处理 Unicode 窗口类的结构。
我想起了之前我因为发现有些字符串会标红了,搜出来告诉我改成多字节集就行了 ,然后我一改,果然不标红了,没想到这个是个暗坑
#include <iostream> #include <tchar.h> #include <tlhelp32.h> // 定义注入 DLL 的路径 PROCESS_INFORMATION pi; BOOL InjectDLL(DWORD processID) { // 获取目标进程的句柄 HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID); if (hProcess == NULL) { MessageBox(NULL, L"Failed to open target process.", L"Error", MB_OK | MB_ICONERROR); return FALSE; } // 在目标进程中分配内存空间 LPVOID pRemoteBuffer = VirtualAllocEx(hProcess, 0, 4096, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (pRemoteBuffer == NULL) { MessageBox(NULL, L"Failed to allocate memory in target process.", L"Error", MB_OK | MB_ICONERROR); CloseHandle(hProcess); return FALSE; } LPCTSTR dllPath = L"CommandHook.dll"; // 替换为你的 DLL 路径 // 将 DLL 路径写入目标进程 if (!WriteProcessMemory(hProcess, pRemoteBuffer, (LPCVOID)dllPath, (_tcslen(dllPath) + 1) * sizeof(TCHAR), NULL)) { MessageBox(NULL, L"Failed to write DLL path into target process.", L"Error", MB_OK | MB_ICONERROR); VirtualFreeEx(hProcess, pRemoteBuffer, 0, MEM_RELEASE); CloseHandle(hProcess); return FALSE; } // 获取 LoadLibraryA 函数地址 HMODULE hNtdll = LoadLibrary(L"kernel32.dll"); if (!hNtdll) { CloseHandle(hProcess); return 0; } void* pLoadLibrary = nullptr; pLoadLibrary = GetProcAddress(hNtdll, "LoadLibraryW"); if (pLoadLibrary == nullptr) { CloseHandle(hProcess); return 0; } DWORD dwThreadId = 0; HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibrary, (LPVOID)pRemoteBuffer, 0, &dwThreadId); if (!hRemoteThread) { CloseHandle(hProcess); return 0; } //MessageBox(NULL, L"DLL injected successfully.", L"Success", MB_OK | MB_ICONINFORMATION); MessageBox(NULL, L"CommandHook.dll 注入成功", L"Success", MB_OK | MB_ICONINFORMATION); //printf("CommandHook.dll 注入成功"); //getchar(); //getchar(); CloseHandle(hProcess); return TRUE; } int main(int argc, char* argv[]) { // 目标进程信息 STARTUPINFOA si = { sizeof(si) }; if (argv[1]==NULL) { printf("使用命令行: ./hook.exe 同目录需要破解的程序名"); return false; } else { printf("正在注入程序名:: %s\n", argv[1]); } // 创建目标进程 "C:\\Users\\Administrator\\Desktop\\AAAAAAAAAAAA.exe" if (!CreateProcessA(argv[1], NULL, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) { //MessageBox(NULL, L"Failed to create target process.", L"Error", MB_OK | MB_ICONERROR); printf("注入目标失败 请检查权限或文件名称" ); return 1; } // 注入 DLL if (!InjectDLL(pi.dwProcessId)) { TerminateProcess(pi.hProcess, 1); return 1; } // 继续目标进程 ResumeThread(pi.hThread); WaitForSingleObject(pi.hProcess, INFINITE); // 等待目标进程结束 // 清理资源 CloseHandle(pi.hThread); CloseHandle(pi.hProcess); return 0; }
注入程序需要使用 PowerShell(管理员) 或是通过赋予了管理员权限的cmd窗口 要不然无法注入成功
结语:
算是初步的实现了之前儿时的想法,这算是一个基本原理展示, 实际上这玩意不弄个GUI,不实现“把文件一拖然后点击运行后自动出结果”的话并不会比直接使用OD简便。
以前想到一个东西就立马去做了,现在还会想想做这个有没有什么意义,动力明显少了很多。
[修改于 2个月1天前 - 2024/07/08 20:59:42]
时段 | 个数 |
---|---|
{{f.startingTime}}点 - {{f.endTime}}点 | {{f.fileCount}} |
200字以内,仅用于支线交流,主线讨论请采用回复功能。