首页
论坛
课程
招聘
[原创] 如何在驱动层完美隐藏内存
2021-1-12 13:17 3110

[原创] 如何在驱动层完美隐藏内存

2021-1-12 13:17
3110

转载请注明链接!

关键字:驱动隐藏内存

废话不多说直接开干.众所周知现在那些"见不得人的产品"都是层层防护用来对抗游戏公司和同行!
其中不乏很多产品都是带着驱动上线的,但玩具是玩具产品就要有个产品的样子.
首先稳定性和兼容性一定要达标.下面我分享的这些技术方案是从运行了几年的成熟产品当中提取出来的并非凭空臆想请放心.
一个真正"见不得人"的产品其中包括几项必做 :

  1. 无模块
  2. 无内存
  3. 无线程
  4. "无通信". 带引号的.至于猥琐通信各家有方案,我也就不班门弄斧
  5. 无hook
  6. 有真正的键鼠消息 (带驱动的产品没有什么做不了,如果有就来看雪逛一圈)

我们今天分享的主要内容就是无内存.

真正的无内存 = 看不到地址 + 即使有地址访问时崩溃
检验无内存是否合格的标准很简单,使用PCHunter看内存属性以及是否能拷贝出来.如果看不到并且直接输入地址也拷贝不到,编写驱动也无法访问就OK了

正式开始讲解之前我要发一个站外的链接,因为我不知道看雪是否允许.如果不允许站外链接请版主通知我.马上修改,请勿删帖封号.多谢啦

当初我的方案也是在搜索引擎输入了 “驱动隐藏内存” 找到了一篇文章对我的启发很大.最终经过几天的修改测试才得以落地!
理论知识下面这个链接说的很明白了我就不废话了:
https://www.cnblogs.com/onetrainee/p/11750274.html

同时借看雪这个平台对这个文章的作者表示感谢!

但是他的这个方案有几个问题 :

  1. 是在XP上面实现的 win10蓝屏
  2. 这篇文章的2个方案都有0x1000字节的泄露.可能被检测到
  3. 内存可以通过指定地址的方式直接读取, PCHunter也可以直接拷贝内存到文件

另外我的代码基础是建立在Blackbone上面升级的.(github搜索Blackbone)
它是一个成熟的驱动程序,包括ring3通信.重点看 src/BlackBoneDrv/
BlackBoneTest 写了很多demo 简单看看就能学会如何加载并通信!


先看原理图

再看效果图


上图粉色框里面的3块内存是游戏的.很多人一看就知道是什么游戏了
下面蓝色框里面的2块内存是我们的.进程内仅当前内存块中可读写


 

下面开始阅读驱动代码 :

在 BlackBoneDef.h 添加命令,用于ring3通知驱动干活

1
2
3
4
5
6
/**
 *    隐藏内存
 *    IOCTL_BLACKBONE_HIDE_VAD 只能隐藏 Image
 */
#define IOCTL_BLACKBONE_HIDE_MEMORY  (ULONG)CTL_CODE(FILE_DEVICE_BLACKBONE, 0x80F, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)
#define IOCTL_BLACKBONE_HIDE_MEMORYEX  (ULONG)CTL_CODE(FILE_DEVICE_BLACKBONE, 0x810, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)

在 BlackBoneDef.h 添加一个结构体,ring3也传递过来参数

1
2
3
4
5
6
7
8
9
/// <summary>
/// hide memory ex
/// </summary>
typedef struct _HIDE_MEMORYEX
{
    ULONGLONG beginAddress;
    ULONGLONG endinAddress;
    ULONG pid;                  // Target process ID
} HIDE_MEMORYEX, *PHIDE_MEMORYEX;

Dispatch.c 当中找到BBDispatch函数.在switch添加派遣过程

1
2
3
4
5
6
7
8
9
10
11
12
case IOCTL_BLACKBONE_HIDE_MEMORYEX:
                    {
                        if (inputBufferLength >= sizeof(HIDE_MEMORYEX) && ioBuffer)
                        {
                            Irp->IoStatus.Status = BBHideMemoryEx((PHIDE_MEMORYEX)ioBuffer);
                        }
                        else
                        {
                            Irp->IoStatus.Status = STATUS_INFO_LENGTH_MISMATCH;
                        }
                    }
                    break;

Routines.h和Routines.c 分别添加函数声明和实现

1
2
3
4
5
6
/// <summary>
/// Hide Memory containing target address
/// </summary>
/// <param name="pData">Address info</param>
/// <returns>Status code</returns>
NTSTATUS BBHideMemoryEx(IN PHIDE_MEMORYEX pData);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
NTSTATUS BBHideMemoryEx(IN PHIDE_MEMORYEX pData)
{
    NTSTATUS status = STATUS_SUCCESS;
    PEPROCESS pProcess = NULL;
    NTSTATUS status1 = STATUS_SUCCESS;
    NTSTATUS status2 = STATUS_SUCCESS;
 
    status = PsLookupProcessByProcessId((HANDLE)pData->pid, &pProcess);
    if (NT_SUCCESS(status))
    {
        // 检查是不是已经保存了PID, pid在游戏的父进程当中保存过来
        // 下面这个BBIsMemoryProcess,是我项目当中的检查函数.大家无需关心.因为我不想驱动谁给它一个指令都能工作
        if (BBIsMemoryProcess(pData->pid))
        {
            PMMVAD_SHORT pVadShort1 = NULL;
            PMMVAD_SHORT pVadShort2 = NULL;
            status1 = BBFindVAD(pProcess, pData->beginAddress, &pVadShort1);
            status2 = BBFindVAD(pProcess, pData->endinAddress, &pVadShort2);
            if (NT_SUCCESS(status1) && NT_SUCCESS(status2))
            {
                if (BBHideMemoryProcess(pData->pid, pVadShort1, pVadShort1->StartingVpn))
                {
                    // 隐藏内存
                    pVadShort1->StartingVpn = pVadShort2->EndingVpn;
                }
                else
                {
                    status = STATUS_INVALID_PARAMETER;
                }
            }
            else
            {
                status = STATUS_INVALID_PARAMETER;
            }
        }
    }
    else
    {
        DPRINT("BlackBone: %s: PsLookupProcessByProcessId failed with status 0x%X\n", __FUNCTION__, status);
    }
 
    if (pProcess) {
        ObDereferenceObject(pProcess);
    }
 
    return status;
}

Routines.c再添加一个函数,查找和确认process id的过程大家可以自己去掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/// <summary>
/// Save Hide Memory Process id
/// </summary>
/// <param name="pid">pid</param>
/// <returns>Status code</returns>
BOOLEAN BBHideMemoryProcess(IN size_t PID, IN VOID* pVadShort, IN size_t StartingVpn)
{
    //KdPrint(("Hide Memory Process : %d\n", PID));
 
    // 确认是否已经保存过
    for (int i = 0; i < CLIENT_NUMBER; i++)
    {
        ClientProcess * client = &clientList[i];
        if (client->pid == PID)
        {
            for (int j = 0; j < HIDE_MEMORY; j++)
            {
                ClientMemory *memory = &client->hideList[j];
                if (memory->pVadShort != NULL && memory->startingVpn != 0)
                {
                    if (memory->pVadShort == pVadShort && memory->startingVpn == StartingVpn)
                        return TRUE;
                }
            }
            for (int j = 0; j < HIDE_MEMORY; j++)
            {
                ClientMemory *memory = &client->hideList[j];
                if (memory->pVadShort == NULL && memory->startingVpn == 0)
                {
                    memory->pVadShort = pVadShort;
                    memory->startingVpn = StartingVpn;
                    return TRUE;
                }
            }
            return FALSE;
        }
    }
    return FALSE;
}

到这为止驱动可以工作了,但是关闭进程的时候WIN10直接蓝屏

所以我们要再关闭一个进程的时候还原回来

打开BlackBoneDrv.c 查找 DriverEnter函数

向下滚动找到 PsSetCreateProcessNotifyRoutine

它的第一个参数是一个callback. 当有进程关闭的时候被回调

在这里调用下面这个函数,同样的查找PID的过程大家自己实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/// <summary>
/// Delete Hide Memory Process id
/// </summary>
/// <param name="pid">pid</param>
/// <returns>Status code</returns>
VOID BBShowMemoryProcess(IN size_t PID)
{
    //KdPrint(("Close Process : %d\n", PID));
    // 确认是否已经保存过
    for (int i = 0; i < CLIENT_NUMBER; i++)
    {
        ClientProcess * client = &clientList[i];
        if (client && client->pid == PID)
        {
            client->pid = 0;
            client->shellCodeAddress = 0;
            //KdPrint(("Show Memory : %d\n", PID));
            for (int j = 0; j < HIDE_MEMORY; j++)
            {
                ClientMemory *memory = &client->hideList[j];
                if (memory->pVadShort != NULL && memory->startingVpn != 0)
                {
                    // 恢复内存, 否则WIN10蓝屏
                    PMMVAD_SHORT pVadShort = (PMMVAD_SHORT)memory->pVadShort;
#if !defined( _WIN81_ ) && !defined ( _WIN10_ )
                    pVadShort->StartingVpn = memory->startingVpn;
#else
                    pVadShort->StartingVpn = (unsigned long)memory->startingVpn;
#endif
                    memory->pVadShort = NULL;
                    memory->startingVpn = 0;
                }
            }
            break;
        }
    }
}

 

OK了。同学们快去调通源代码吧.
至于驱动怎么签名别来问我,但是我可以告诉你也就是几百块钱的事。一条烟钱吧
另外WIN10平台需要过PG,否则半小时蓝屏.
动态过pg推荐去看看(github.com/9176324/Shark)
不想编译的同学们可以直接点击右面的 releases 下载一个exe和Shark.sys
双击的时候自动加载Shark.sys帮你过PG,当然这个Shark.sys也需要找人去签名.

最后祝看雪21岁生日快乐,小伙子很年轻加油!


[招聘] 欢迎你加入看雪团队!

收藏
点赞7
打赏
分享
最新回复 (6)
雪    币: 289
活跃值: 活跃值 (589)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
默NJ 活跃值 2021-1-12 17:43
2
0
感谢分享,有空研究一下
雪    币: 295
活跃值: 活跃值 (331)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
yy虫子yy 活跃值 2021-1-13 01:57
3
0
驱动里面隐藏内存,差不多就这两种,vad隐藏,pagefault隐藏
不管哪一种,都要先过pg
雪    币: 316
活跃值: 活跃值 (54)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
mllaopang 活跃值 2021-1-13 08:09
4
0
感谢分享
雪    币: 6295
活跃值: 活跃值 (3445)
能力值: ( LV12,RANK:200 )
在线值:
发帖
回帖
粉丝
pureGavin 活跃值 2 2021-1-13 10:10
5
0
感谢分享
雪    币: 720
活跃值: 活跃值 (882)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
丿一叶知秋 活跃值 2021-1-13 10:57
6
0
hh, 估计他都不知道文章被引用了,自我感觉内存隐藏就是利用内存管理的碎片,虚拟内存都是以区为单位管理,但是实际分为区块、区段,从某种角度来讲,只要抓住了后1种就可以实现间接的隐藏(虚拟内存管理的操作)。如果是从物理内存管理的角度,那么就要自己去操作PFN和映射相关。。。
当时知道这个也没往下研究!!感谢大佬分享。
雪    币:
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
一只米 活跃值 3天前
7
0
感谢分享
游客
登录 | 注册 方可回帖
返回