本帖最后由 半斤八兩 于 2012-7-23 21:51 编辑
反内存监视集绵
/**************************************
/* 作者:半斤八兩
/* 博客:http://hi.baidu.com/bjblcracked
/* 日期:2012-07-22 01:00
/**************************************
IDA,">只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
相信不少朋友辛辛苦苦找到的补丁数据,被别人轻而易举的窃取了...
本文就来讲讲怎么防止别人窃取我们的补丁数据...(这也是个矛与盾的话题,本文只是把常见的方法一一列举出来...)
前言:
我们先讲API HOOK.
API HOOK 技术,对于新手来说可能非常的神秘,其实它并不神秘,用一句话来概述就是 [抢在它前面,做些小动作.] 或 [修改EIP] 等.
现在的家用电脑的U普遍都是x86. 在保护模式下,每个进程都是独立的虚拟4GB内存空间.它们彼此都互不干扰.
如果我们想访问或者修改某个进程的某个内存空间.我们就必须得到那个进程的EPROCESS , 然后通过EPROCESS 找到 CR3 再把想要访问或者读写的内存地址按CR4里的PAE位拆分. 然后再把CR3 加上 拆分后的 PDE 加上拆分后的PTE 加上OFFSET.等等...我们才能实现R/W一段内存空间. 但是仅仅知道这些是不行的,因为这些我们用户层是没有权限访问CR3的,因此我们又不得不去了解段机制. 天呐~~ 仅仅一个读写内存就这么麻烦??? 是的,就是有这么麻烦..不过还好.MS的人考虑的周到.他们把这些烦锁的步骤,封装成一个方法.供我们调用.欲称Windows API.
No1: Anti Hook API(先斩后奏)
上面说的烦锁的过程,我们可以通过2个API轻松实现.
Kernel32!ReadProcessMemory // 读指定进程内存
Kernel32!WriteProcessMemory // 写指定进程内存
知已知彼 百战不殆!
通过Kernel32!WriteProcessMemory 就能制作我们的补丁程序了. 如果想知道别人写什么数据,我们可以通过HOOK Kernel32!WriteProcessMemory 这样的函数. 来得到别人的补丁数据.
这样的偷窃也很好反.我在这里就举两三个小例子.
1) 设置分页属性.
我们知道物理页(PA), 一般有
---------------------------PTE-------------------------------------------------
第0位 P位: 为1时 存在 | 为0时 不存在.
第1位 R/W位: 为1时 可写 | 为0时 不可写
第2位 U/S位: 为1时 用户权限 | 为0时 系统权限
---------------------------------------------------------PTE-------------------
之区分.
这些属性在 PTE(页表项) 对应的 第0位 第1位 第2位.
P位为0时,我们在OD下,访问会显示 "No memory on the specified address"
在Windbg下访问,会显示 "?? ?? ?? ?? ...."
R/W 或 U/S 位为0时, 我们访问,会提示一个 0xC0000005 写,异常.
P位 R/W位和U/S位,我们是没有权限操作的.(本章主要讲解应用层的).
但是 R/W 位, MS的人,也给我们留了一个操作函数.
Kernel32!VirtualProtect //设置本进程指定低2G内存分页属性(范围4KB 即0x1000)
有了这个函数,我们就可以"间接"的修改R/W位了.
代码:
// 这里是MSG 硬编码,(本人当前机器是XP Sp2 英文系统,硬编码的)
BYTE szWriteBuffer[] = {
0x6a, 0x40, 0x68, 0xe2, 0x24, 0xa6, 0x7c, 0x68, 0xe2, 0x24, 0xa6, 0x7c, 0x6a, 0x00, 0xe8, 0x0c,
0xe0, 0x31, 0xfb, 0xc2, 0x10, 0x00, 0x90, 0xb0, 0xeb, 0xbd, 0xef, 0xb0, 0xcb, 0xc1, 0xbd, 0xb7,
0xb4, 0xb2, 0xb9, 0xb6, 0xa1, 0x00}
HMODULE hModule = GetModuleHandle("Kernel32.dll")
LPVOID lpWriteFun = GetProcAddress(hModule, "WriteProcessMemory")
// 英文计算器
HWND hWnd = ::FindWindow(NULL, "Calculator")
DWORD dwPID = 0
GetWindowThreadProcessId(hWnd, &dwPID)
HANDLE hTarget = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)
WriteProcessMemory(hTarget, TARGET_ADDRESS, szWriteBuffer, sizeof(szWriteBuffer), NULL)
DWORD dwOldProtect = 0
VirtualProtect(lpWriteFun, 0x1, PAGE_NOACCESS, &dwOldProtect)
这样做,是在我们执行完补丁后,才修改属性.这样时机得早.否则,一切都是瞎忙活.只要我们时机早.
对方在HOOK 这个函数的时候,就会产生一个 0xC0000005 访问异常. 这样,对方就不能偷窃我
们的数据了.
试问,如果我们能在对方HOOK之前写入目标数据.我们还修改分页属性, 是不是多此一举?
这里只是提供一条思路.
检测HOOK指令.
在前面我们已经说过了, HOOK就是在做某事前,先干些我们自己的事.也就是修改EIP.
想想修改EIP的,无非就是 JCC push/ret call 这几种.
但是当下系统之多,不得不考虑兼容性. 所以一般HOOK,我们都会HOOK,函数的前5个字节.
为什么正好是前5个字节.不是前4个字节,6个字节呢? 我们来看看 WriteProcessMemory 函数
代码:7C80220F > 8BFF mov edi,edi
7C802211 55 push ebp
7C802212 8BEC mov ebp,esp
7C802214 |. 51 push ecx
7C802215 |. 51 push ecx
7C802216 |. 8B45 0C mov eax,[arg.2]
我们用JMP指令来修改到任意一个EIP.
7C80220F > - E9 EBDD7F03 jmp 7FFFFFFF
7C802214 |. 51 push ecx
7C802215 |. 51 push ecx
7C802216 |. 8B45 0C mov eax,[arg.2]
我们可以看的出来, JMP 指令 0xe9 1个字节, 加上 (目标地址 - 当前地址 - 5) 结果4个字节 ,加起来正好5个字节. 我们就可以用JMP 0xxxxxxxx 来代替前面的5个字节. 这样的,最方便,也最健壮.当然,也最容易被发现了 :(
如此一来,我们只要检测 WriteProcessMemory 第1个字节,是不是为 0xe9, 就可以轻松检测到是否 HOOK.
当然了,此法在这里要排除那些不考虑程序兼容性乱HOOK的朋友.
也要排除那些为了防止被发现HOOK,将所有不同系统的硬编码都加到程序里面判断的朋友.(如此一来,他可以不用再前5个字节修改EIP. 他可以争对不同系统,在不同地方,用不同的方法修改EIP.不一定非要用JCC, 也可以用 push/ret 之类的指令,修改EIP. )
Shadow Function(影子函数)
相信不少朋友应该看过火影忍者.那里面的鸣人练就了一个本领,[多重影分身] 每当快要被敌人打到的时候,他就会使用多重影分身,将本体藏起来,被敌人打到的,只是一个影分身而已.
在计算机中,也有这么一个多重影分身术.Shadow Function.. 这个并不是当下什么流行的技术. 当下的一些流行壳,像比如 ZP SE 等知名壳.都有使用Shadow Function技术.
我们也可以使用这个技术反内存监视.让监视者们,监视我们的影分身.而我们真身写入的数据.敌人浑然不知. 听起来是不是很cool ? 但是,要实现Shadow 必须要熟知PE结构.自己模拟PE装载模块. 像我们小菜不懂PE怎么办?
在这里,我在教大家一种简单的方法.不知道大家有没有注意,我在前面写的每个函数前面都有加一个模块名. 那个模块名,就是函数的所在模块. 一般都是在 SYSTEM32 目录下.
我们可以直接将他拷贝出来, 改一下名字.然后我们直接调用它,就可以了.
代码:// 这里是MSG 硬编码,(本人当前机器是XP Sp2 英文系统,硬编码的)
BYTE szWriteBuffer[] = {
0x6a, 0x40, 0x68, 0xe2, 0x24, 0xa6, 0x7c, 0x68, 0xe2, 0x24, 0xa6, 0x7c, 0x6a, 0x00, 0xe8, 0x0c,
0xe0, 0x31, 0xfb, 0xc2, 0x10, 0x00, 0x90, 0xb0, 0xeb, 0xbd, 0xef, 0xb0, 0xcb, 0xc1, 0xbd, 0xb7,
0xb4, 0xb2, 0xb9, 0xb6, 0xa1, 0x00}
void CPage1::OnAntiMonitor()
{
HMODULE hModule = LoadLibrary("WriteMem.dll")
DWORD WriteFun = (DWORD)GetProcAddress(hModule, "WriteProcessMemory")
BOOL (WINAPI *lpWriteFun)(HANDLE , LPVOID , LPCVOID , SIZE_T , SIZE_T *)
lpWriteFun = (int (__stdcall *)(void *,void *,const void *,
unsigned long,unsigned long *))WriteFun
// 英文计算器
HWND hWnd = ::FindWindow(NULL, "Calculator")
DWORD dwPID = 0
GetWindowThreadProcessId(hWnd, &dwPID)
HANDLE hTarget = ::OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)
// WriteProcessMemory
lpWriteFun(hTarget, TARGET_ADDRESS, szWriteBuffer, sizeof(szWriteBuffer), NULL)
}
这样子,别人再HOOK Kernel32!WriteProcessMemory ,就是HOOK我们的多重影分身了. :)
而我们本体函数,是可以正常的执行,不受任何影响的.
No2: Killer Driver(半路杀出个程咬金)
前面我们说的都是反应用层的HOOK. 但是,当遇到系统层的,前面的方法都彻底失效了.
在之前我有写过一个反R0内存监视的小教程.
http://www.52pojie.cn/thread-154535-1-1.html
那里面说的方法,只是自动枚举停止按钮的句柄.然后自动按了一下而已.这个方法有很多弊端.
比如监视补丁工具改了一下窗口名. 或者按钮的名字修改了一下,就都无效了.
今天我们再来说几种新的方法.
1) 利用[停止]按钮句柄,发WM_LBUTTONDOWN消息
2) 定位特征码,枚举所有进程,读取特征码.
3) 检测符号链接
4) 调用IRP派遣消息
1) 利用[停止]按钮句柄,发WM_LBUTTONDOWN消息
第一种方法之前的贴子有讲过,这里就不再赘述了.
定位特征码,枚举所有进程,读取特征码.
世上的东西都是相生相克的.就像矛与盾一样.没有矛,何来的盾? 没有盾,又何来的矛?
定位特征码的话,我们就必须得有内存监视的程序样本. 否则我们是无法定位其特征码的.
本篇,重点不是讲解如何提取特征码.所以细节忽略...
本篇[内存写入监视器 v2.0]特征数据如下:
004E6716 A1 EC2E4F00 mov eax,dword ptr ds:[4F2EEC]
特征码地址:
004E6716.
特征数据:
0xA1EC2E4F(为了方便,我们就提取四个字节 DWORD 类型,来效验)
代码:
#define SIGNATURE_ADDRESS (LPVOID)0x004E6716
#define SIGNATURE_SIZE 0x4
BYTE szSignature[MAXBYTE] = {0};
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 Process32 = {0};
Process32.dwSize = sizeof(PROCESSENTRY32);
BOOL bRect = Process32First(hSnapshot, &Process32);
while(bRect)
{
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,
FALSE, Process32.th32ProcessID);
if(!hProcess)
{
bRect = Process32Next(hSnapshot, &Process32);
continue;
}
ReadProcessMemory(hProcess, SIGNATURE_ADDRESS,
szSignature, SIGNATURE_SIZE, NULL);
if(*szSignature == 0xa1)
{
AfxMessageBox("扫描到特征码, 表示检测到内存监视",
MB_ICONINFORMATION);
}
bRect = Process32Next(hSnapshot, &Process32);
}
我们利用MS给出的进程快照 Kernel32!CreateToolhelp32Snapshot 得到进程PID
利用 Kernel32!OpenProcess 打开进程句柄
利用 Kernel32!ReadProcessMemory 读取特征码.
优点:
此法,简单,实用. :)
缺点:
对于线程 或者堆中, 动态分配的代码,不适宜.
检测符号链接
既然是检测驱动,那我们也要简单的知道一些驱动相关的信息.
驱动大体分为两类,
一类是PNP(Plug-and-play)类型, 这类多半是USB. 摄像头, 狗, U盾 等等,即插即用类型的.
一类是NT 类型. 非即插即用驱动.
这个工具很显然是后者,即NT式驱动.
应用程序想要和驱动程序之间进行"通信".驱动程序就会暴露一个符号链接,让应用程序去访问它.
我们可以通过 Kernel32!CreateFile 来访问这个符号链接.
我们先用OD载入 [内存写入监视器 V2.0.exe]
代码:00400154 > 8725 D45F6200 xchg dword ptr ds:[625FD4],esp
0040015A 61 popad
0040015B 94 xchg eax,esp
0040015C 55 push ebp
0040015D A4 movs byte ptr es:[edi],byte ptr ds:[esi]
0040015E B6 80 mov dh,80
然后我们下一个 CreateFileA/W 断点. F9, 让程序跑起来.
这时候单字节的API断下来了,我们看一下堆栈信息.
代码:
0013FDC8 004E55DD /CALL to CreateFileA from 内存写入.004E55D8
0013FDCC 004E64C4 |FileName = "MemWrite.sys"
0013FDD0 C0000000 |Access = GENERIC_READ|GENERIC_WRITE
0013FDD4 00000001 |ShareMode = FILE_SHARE_READ
0013FDD8 00000000 |pSecurity = NULL
0013FDDC 00000002 |Mode = CREATE_ALWAYS
0013FDE0 00000000 |Attributes = 0
0013FDE4 00000000 \hTemplateFile = NULL
我们重点看这一个地址.
0013FDCC 004E64C4 |FileName = "MemWrite.sys"
这就是他的驱动程序了.这个驱动加载完毕,就删除了,我们找不到它.这里我们就无视它了.
我们重点是要找到这个驱动暴露给应用程序的符号链接.
我们继续 F9 运行程序. 然后程序又断下来了.我们继续看堆栈信息.
代码:0013FDB0 004E5E4F /CALL to CreateFileA from 内存写入.004E5E4A
0013FDB4 00B338FC |FileName = "\\.\MemWrite"
0013FDB8 C0000000 |Access = GENERIC_READ|GENERIC_WRITE
0013FDBC 00000000 |ShareMode = 0
0013FDC0 00000000 |pSecurity = NULL
0013FDC4 00000003 |Mode = OPEN_EXISTING
0013FDC8 00000080 |Attributes = NORMAL
0013FDCC 00000000 \hTemplateFile = NULL
这一下我们就看到了暴露的符号链接了.
0013FDB4 00B338FC |FileName = "\\.\MemWrite"
"\\.\MemWrite" 这个就是NT驱动暴露给客户端的符号链接.
下面,我们就可以跨进程用同样的方法来调用它.
在这里,可能有的朋友好奇了,为什么跨进程还能调用它.
原因很简单, 每个进程虽然都是"独立的", 但是这里的独立的,是指低2G内存空间是独立的.
但是高2G是共享的.而符号链接,就保存在高2G内存空间中,所以,我们任何一个进程,都是可以访问这个符号链接的.
代码: HANDLE hSymbolic = CreateFile("\\\\.\\MemWrite",
GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(INVALID_HANDLE_VALUE != hSymbolic)
{
AfxMessageBox("检测到符号链接...:)", MB_ICONERROR);
}
else
{
AfxMessageBox("没有发现符号链接~~~:)", MB_ICONINFORMATION);
}
优点:
屡试不爽. :)
缺点:
如果驱动暴露在外的符号链接修改了,或者改成随机的,那此法无效 :(
调用IRP派遣消息
IRP派遣消息是通过 Kernel32!DeviceIoControl 函数实现的.
我们再来用OD运行内存监视器.我们随便选择一个计算器程序, 然后再OD命令行处,下 DeviceIoControl 断点. 然后我们点监视器上的 [开始监控]按钮.
程序断点,堆栈信息:0013FBEC 004E672A /CALL to DeviceIoControl from 内存写入.004E6725
0013FBF0 000000C0 |hDevice = 000000C0 (window)
0013FBF4 5800C007 |IoControlCode = 5800C007
0013FBF8 004FF9E0 |InBuffer = 内存写入.004FF9E0
0013FBFC 00000004 |InBufferSize = 4
0013FC00 00000000 |OutBuffer = NULL
0013FC04 00000000 |OutBufferSize = 0
0013FC08 0013FC34 |pBytesReturned = 0013FC34
0013FC0C 00000000 \pOverlapped = NULL
再运行.又中断.
代码:
0013FBEC 004E674C /CALL to DeviceIoControl from 内存写入.004E6747
0013FBF0 000000C0 |hDevice = 000000C0 (window)
0013FBF4 5800C00B |IoControlCode = 5800C00B
0013FBF8 00000000 |InBuffer = NULL
0013FBFC 00000000 |InBufferSize = 0
0013FC00 00000000 |OutBuffer = NULL
0013FC04 00000000 |OutBufferSize = 0
0013FC08 0013FC34 |pBytesReturned = 0013FC34
0013FC0C 00000000 \pOverlapped = NULL
程序中断两次,说明,客户端通知驱动开启监视,要通知两次才能完成开启.下面,我们再点[停止监控]按钮.
程序又中断了,堆栈信息:
代码:0013FBFC 004E68B7 /CALL to DeviceIoControl from 内存写入.004E68B2
0013FC00 000000C0 |hDevice = 000000C0 (window)
0013FC04 5800C00F |IoControlCode = 5800C00F
0013FC08 00000000 |InBuffer = NULL
0013FC0C 00000000 |InBufferSize = 0
0013FC10 00000000 |OutBuffer = NULL
0013FC14 00000000 |OutBufferSize = 0
0013FC18 0013FC38 |pBytesReturned = 0013FC38
0013FC1C 00000000 \pOverlapped = NULL
我们对比一下三次,可以看出,除了 IoControlCode InBuffer InBufferSize pBytesReturned 这四个参数不一样外,其它的参数,全部是一样的.
InBuffer InBufferSize
这两个,前面一个是传进去要监控的PID值, 以及大小.我们无视之.
pBytesReturned 是一个返回值,我们也无视. 重要的就是 IoControlCode .
IoControlCode 我们可以称之为 消息码 或者 控制码. 其值是固定不变的,不会因为机器重启,或者换了个平台, 而改变控制码.
有过windows开发经验的,都知道windows消息机制. 而这个 IoControlCode 就相当于是Windows中的,自定义消息. 驱动那头,就相当于是一个处理消息的过程函数.
这边发送什么样的消息码过去.驱动那边,就会有怎样的反应.
当我们点 [停止监控] 时,发送的控制码是 IoControlCode = 5800C00F
知道了,[停止监控] 的 "控制码"了, 我们自己也可以发送这个消息过去了.
这里,大家要注意的是, DeviceIoControl 的第一个参数 hDevice = 000000C0 (window)
他的值是 CreateFile 的返回值, 大家要注意,必须是 MemWrite.sys 的文件句柄才行.
但是,问题又来了.我们如何发送这个控制码呢? 我们也可以跨进程发送这个控制码吗?
答案,当然是可行的喽~~ :)
代码: HANDLE hSymbolic = CreateFile("\\\\.\\MemWrite",
GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if(INVALID_HANDLE_VALUE != hSymbolic)
{
AfxMessageBox("检测到符号链接...:)", MB_ICONERROR);
DWORD dwCount = 0;
DeviceIoControl(hSymbolic, 0x5800C00F, NULL, 0,
NULL, 0, &dwCount, NULL);
}
else
{
AfxMessageBox("没有发现符号链接~~~:)", MB_ICONINFORMATION);
}
主要是作者在驱动层没有做严格判断.所以我们可以跨进程发送 [停止监控] 的IRP请求.
No3: CRC(1 byte Cyclic Redundancy Check)
对付这类的内存监视,有点恶心.
为什么说它恶心呢?因为它是扫描目标进程代码段,看是否有修改过的.如果有修改过的,则直接显示出来.这样的内存扫描工具,我们就没有办法吗?
兵来将挡,水来土淹.
对于这样的程序,还是有不少办法的.
1) 异常页
2) 复制术
3) 一字节异常数据
异常页
这类的内存监视工具,很显然就是利用我们前面讲到的,跨进程内存存取的方式实现的. 那么自然就扫不了要调用 Kernel32!ReadProcessMemory 函数. 将目标进程的代码全部读取出来, 保存到内存空间, 或保存在磁盘上.等.
我们来看看这个函数的参数.
代码:BOOL ReadProcessMemory(
HANDLE hProcess,
LPCVOID lpBaseAddress,
LPVOID lpBuffer,
SIZE_T nSize,
SIZE_T * lpNumberOfBytesRead
);
我们可以看出,并没有"权限" 这一个参数.那么我们可以设想.如果我们把 0x00400000 这个地址开始的分页属性修改成 PAGE_NOACCESS(没有访问权限) 那么这个内存扫描工具是不是就会报错了? 这个想都不用想,当然是不可能的了.
事实上,正常的内存遍历工具,应该都会先调用 Kernel32!VirtualQueryEx 查看一下,分页是否可读或可写权. 再决定是否调用 Kernel32!ReadProcessMemory 读取此分页. 当我分析此程序时, 下 Kernel32!VirtualQueryEx 断点,并没有断点, 当然,函数也没有 Sahdow 处理. 看来此内存监视工具,写的还是不够完善.
知道此程序的BUG,那么,我们就可以做手脚了.如何做手脚呢?
很简单,我们可以在 Magic Jump 那个分页属性,修改成 PAGE_NOACCESS. 这样不是就大功告成了? 是不是感觉哪里还有问题? 没错, 不仅有问题,而且问题还很严重. 如果属性改成无权限,那么,被打补丁的目标程序,自己执行到那段代码的时候,也是要访问,并且执行的啊,执行到我们修改成无访问权限的那段,代码,不是要抛一个0xc0000005 访问异常?
是吖,就是这个问题,很纠结. 因此, 我们最多也只能修改 0x00400000 这一个分页.这个页从磁盘映射到内存空间,囊括了 DOS头和PE头和节等信息.我们修改属性是没有问题. 但是其它代码修改了属性,我们不能保证他不会被目标程序本身执行.
所以我们只能找100% 不会被目标程序本身 访问, 执行 的分页来修改其属性. 这样做,也只是能做一些干扰码. 因为这款内存扫描工具,没有判断分页属性,直接读内存,所以结果读了没有内存访问权限的分页,就会返回很多干扰码. 其中本小节开始的截图,就是读取的干扰码.
修改分页属性,看似没有什么实质帮助,其实可以用在其他地方,本小节,只是起一个抛砖引玉的作用.
再附上代码:
void CPage3::OnAntiMonitor4()
{
DWORD dwOldProtect = 0;
VirtualProtect((LPVOID)0x00400000, 1, PAGE_READWRITE, &dwOldProtect);
*(PBYTE)(0x00400000) = 0x11;
VirtualProtect((LPVOID)0x00400000, 1, PAGE_NOACCESS, &dwOldProtect);
}
优点:
好像木有啥优点. :(
当然了,也不是没有优点,如果可以和下面说的 1字节异常 结合起来使用,就真是完美搭配了.:)
缺点:
只适合于这类没有调用 Kernel32!VirtualQueryEx 函数,判断分页属性的,内存监视工具
复制术
复制术? 听起来很cool.. 名字是本人随便起的, 并不是什么专业术语,勿较真. :)
相信有不少玩Hacker的朋友在渗透踩点某伺服器的时候,为了避免被IDS检测到,都会在人流峰高潮的节假日的时候,进行渗透踩点.这样才能尽量避免触发警报.
而本节的内存监视工具,就相当于是夜间工作的IDS. 专门监视代码段. 如果我们在半夜突然像伺服器发送一个握手协议.就能被IDS盯死. 但是,如果我们在双休日的时候,WEB访问量巨增的时候,在数亿个合法包里面,藏匿我们自己的踩点包,还是可以轻而易举的绕过去的.
在这里, 代码段 == 夜间踩点 那么,反过来, 双休踩点 == ????
双休踩点, 就相当于是 进程中的 堆空间 或 线程中. 变动的 无规则的.
我们从本节的图片中,可以看出,此内存监视工具,缺省设置的扫描地址,就是 0x00400000 - 0x00490000 那么,我们可以大胆的设为,在低2G内存空间中, 除了这上面的缺省范围的内存空间外,其它的内存空间,都会活动的. 无规则的
说到这里,相信还有不少人有点迷糊,不知道 所谓的 "活动的" "无规则的" 到底是什么意思.
其实意思就是说,被经常被读 或被写 或被访问, 或被修改 的代码段. 如果搜索那段"活动的" 地址页. 那么,程序扫描出的结果数量,将是非常 "惊人"的.
那么,清楚了这些,我们就可以把我们要修改数据的那段上下文, 或者子函数,全部拷贝一份出来.到我们的堆空间,或者线程中. 那么,就能避免被内存监视工具发现.
现在还最后剩一个问题.就是如何将EIP,指向我们的复制函数里面?
我们可以暂停目标进程,设置其上下文, 或者干脆创建一个远程线程函数. :)
代码:void Hello(int nValue)
{
if(nValue == 0x520)
{
MessageBox(NULL, "Register successful~",
"By 半斤八兩", MB_ICONINFORMATION);
}
else
{
MessageBox(NULL, "Register failure~",
"By 半斤八兩", MB_ICONERROR);
}
}
void Shadow(int nValue)
{
MessageBox(NULL, "successful~",
"By 半斤八兩", MB_ICONINFORMATION);
}
void CPage3::OnAntiMonitor()
{
AfxMessageBox("||||使用复制术前||||", MB_ICONINFORMATION);
Hello(0x0);
AfxMessageBox("--==使用复制术后==--", MB_ICONINFORMATION);
Shadow(0x0);
}
一字节异常
One Byte Patch. 看到这个字节.相信大家,第1时间想到的就是. 会不会是 0x74 改成 0x75
0x85 改换 0x84 或者改成 NOP 这类的 单字节指令呢?
当然不是了.这里的1字节,肯定会被补丁检测到的.但是,这是一个无用的字节,我们可以改成 0xcc .. 有的人就会想了, 0xcc 那不是 int3异常吗? 答案是的. 我们的目的就是让程序产生异常. 产生异常后,我们再接管异常处理. 即可. 听起来,是不是很神奇.~~
MS给出了多种异常处理的方案.其中常用的就三种.
VEH UEF SEH (详细的区别和功能大家可以GG,这里就不赘述了.)
其中SEH 和 UEF 一个范围限制,一个权限限制.所以本章使用 VEH.向量异常处理.
代码:
LONG WINAPI VectoredHandler(PEXCEPTION_POINTERS ExceptionInfo)
{
if(ExceptionInfo->ExceptionRecord->ExceptionCode == 0x80000003)
{
MessageBox(NULL, "Exception Handler~",
"By 半斤八兩", MB_ICONINFORMATION);
ExceptionInfo->ContextRecord->Eip = 0x60000008;
return EXCEPTION_CONTINUE_EXECUTION;
}
return EXCEPTION_CONTINUE_SEARCH;
}
void IsOneByteException(int nParameter)
{
if(nParameter)
{
MessageBox(NULL, "Register successful~",
"By 半斤八兩", MB_ICONINFORMATION);
}
else
{
MessageBox(NULL, "Register failure~",
"By 半斤八兩", MB_ICONERROR);
}
}
void CPage3::OnAntiMonitor3()
{
LPVOID lpMem = VirtualAlloc((LPVOID)0x60000000, 0x1000,
MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
LPVOID (WINAPI *AddVectoredExceptionHandler)(ULONG, LPVOID);
HMODULE hModule = GetModuleHandle("Kernel32.dll");
AddVectoredExceptionHandler =
(void *(__stdcall *)(unsigned long,void *))
GetProcAddress(hModule, "AddVectoredExceptionHandler");
AddVectoredExceptionHandler(1, VectoredHandler);
memcpy(lpMem, IsOneByteException, 0x1000);
DWORD dwAddress = 0x60000000;
*(PBYTE)(dwAddress+6) = 0xcc;
_asm
{
push 0
mov eax, dwAddress
call eax
}
VirtualFree(lpMem, 0, MEM_RELEASE);
}
// 本例因没有对Debug 跳转表做处理,所以只适合Release 编译,
我们来看一下运行结果.
在这里,我们看到了一片的数据,而没有看到 0xcc, 这个原因,我们前面有分析过了,因为本内存监视程序
在跨进程取内存时 没有使用 Kernel32!VirtualQuery 做检查分页属性,所以会读取一片错误信息.弱暴了~~~
End...
测试Bin的源码.7z
(322.96 KB, 下载次数: 244)
反内存监视集绵.doc
(203.5 KB, 下载次数: 219)
内存监视器.7z
(526.2 KB, 下载次数: 922)
内存写入监视器 V2.0.exe.7z
(603.96 KB, 下载次数: 693)
文章中的测试Bin.7z
(456.07 KB, 下载次数: 142)
|