作者也学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简便。
以前想到一个东西就立马去做了,现在还会想想做这个有没有什么意义,动力明显少了很多。
[修改于 6个月23天前 - 2024/07/08 20:59:42]
200字以内,仅用于支线交流,主线讨论请采用回复功能。