大神论坛

找回密码
快速注册
查看: 252 | 回复: 0

[原创] 逆向分析之win7 32位绕过写拷贝教程 附源码

主题

帖子

0

积分

初入江湖

UID
683
积分
0
精华
威望
0 点
违规
大神币
68 枚
注册时间
2023-10-14 10:55
发表于 2023-12-31 15:47
本帖最后由 kangdiwu 于 2023-12-31 15:47 编辑

首先明白什么是写拷贝?
在三环Hook api的时候,比如hook MessageBoxA函数,只会影响当前进程,对其他进程并不影响,就是因为有写拷贝的存在,写拷贝是vad树里页的属性,可以看到有个EXECUTE_WRITECOPY的属性

0x7727ea11是我虚拟机MessageBoxA函数的地址,通过windbg可以看到pte属性 R/W位是0,说明页是只读,但是为啥能hook 修改内存呢,hook的时候由于页不可写会引发异常,cpu就会去查询vad树,发现属性是EXECUTE_WRITECOPY,
进入异常处理后会重新分配一份新的物理页,修改线性地址对应的pte此时线性地址0x7727ea11对应的物理页已经发生改变,所以Hook之后,并不会影响进程,那么如何绕过写拷贝呢,我这里的方法是修改pte,将R/W位置1,这样写的
时候不会引发异常,就不会触发写拷贝,欧克,了解了原理以及知道如何绕过下面开始看代码实现分为驱动和应用层两部分。

先看驱动部分

#include "LDT_ENTRY.h"
#include <ntddk.h>
#define OPER1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
typedef NTSTATUS(*PsLookupProcessByProcessId)(HANDLE ProcessId, PEPROCESS Process);
typedef PVOID(*PsGetProcessPeb)(PEPROCESS Process);
typedef NTSTATUS (*MmCopyVirtualMemory)(PEPROCESS UserProcess,UINT32 UserAddress, PEPROCESS KerProcess,PVOID targetBuffer,UINT32 Size,KPROCESSOR_MODE AccessMode,PUINT32 PDwSize);
typedef VOID (*KeStackAttachProcess)(PKPROCESS Process,PKAPC_STATE ApcState);
typedef VOID (*KeUnstackDetachProcess)(PKAPC_STATE ApcStatee);
UINT32 NtosBase;
//寻找内核模块
NTSTATUS FingNtosKernelModule(PDRIVER_OBJECT driver, PUINT32 NtosBase) {
UNICODE_STRING osstr;
USHORT buff[] = L"ntoskrnl.exe";
RtlInitUnicodeString(&osstr, buff);
PLDR_DATA_TABLE_ENTRY pkernel_ldr = driver->DriverSection;//获取LDR_DATA_TABLE_ENTRY结构所在地址
while (RtlCompareUnicodeString(&osstr, &pkernel_ldr->BaseDllName, TRUE)) {
pkernel_ldr = pkernel_ldr->InLoadOrderLinks.Flink;
}
*NtosBase = pkernel_ldr->DllBase;
return STATUS_SUCCESS;
}
//读取用户进程数据
NTSTATUS ReadUserMemory(HANDLE processId, UINT32 sourceAddress, PVOID targetBuffer, SIZE_T size) {
NTSTATUS status = STATUS_SUCCESS;
PEPROCESS ProcessPointer = NULL;
KAPC_STATE apcState;
UINT32 DwSize;
//函数指针 基址加偏移的方式得到
PsLookupProcessByProcessId PsGetProcessEpro = NtosBase + 0x269608;
KeStackAttachProcess KeSAP = NtosBase + 0x9987C;
MmCopyVirtualMemory MmCopyMem = NtosBase + 0x26E418;
KeUnstackDetachProcess KeUDP = NtosBase + 0x94A51;
// 根据进程ID获取目标进程的EPROCESS结构
status = PsGetProcessEpro(processId, &ProcessPointer);
if (status != STATUS_SUCCESS) {
DbgPrint("获取进程的EPROCESS指针失败\r\n");
return status;
}

// 通过APC锁定目标地址空间
KeSAP(ProcessPointer, &apcState);
MmCopyMem(ProcessPointer, sourceAddress, PsGetCurrentProcess(), targetBuffer, size, KernelMode,&DwSize);
// 释放APC锁
KeUDP(&apcState);
//释放对目标进程的引用
ObDereferenceObject(ProcessPointer);
return status;

}
//向用户进程写数据

NTSTATUS WriteUserMemory(HANDLE processId, UINT32 sourceAddress, PVOID targetBuffer, SIZE_T size) {
NTSTATUS status = STATUS_SUCCESS;
PEPROCESS ProcessPointer = NULL;
KAPC_STATE apcState;
UINT32 DwSize;
//函数指针 基址加偏移的方式得到
PsLookupProcessByProcessId PsGetProcessEpro = NtosBase + 0x269608;
KeStackAttachProcess KeSAP = NtosBase + 0x9987C;
MmCopyVirtualMemory MmCopyMem = NtosBase + 0x26E418;
KeUnstackDetachProcess KeUDP = NtosBase + 0x94A51;
status = PsGetProcessEpro(processId, &ProcessPointer);
if (status != STATUS_SUCCESS) {
DbgPrint("获取进程的EPROCESS指针失败\r\n");
return status;
}
KeSAP(ProcessPointer, &apcState);
MmCopyMem(PsGetCurrentProcess(), targetBuffer, ProcessPointer, sourceAddress, size, KernelMode, &DwSize);
KeUDP(&apcState);
//释放对目标进程的引用
ObDereferenceObject(ProcessPointer);
return status;

}

//pae分页模式下得到线性地址pte所在的线性地址
UINT32 __declspec(naked) get_offset(UINT32 LineAddress) {
_asm {
mov edx, [esp + 0x4] //得到线性地址
shr edx, 0x9
and edx, 0x7ffff8
sub edx, 0x40000000
mov eax,edx
ret

}

}
VOID DriverUnload(PDRIVER_OBJECT driver)
{

DbgPrint("exit\r\n");
}

NTSTATUS CreateDeviceCall(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
DbgPrint("设备打开成功\r\n");
//设置返回状态
pIrp->IoStatus.Status = STATUS_SUCCESS; // getlasterror()得到的就是这个值
pIrp->IoStatus.Information = 0; // 返回给3环多少数据
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

NTSTATUS CloseDeviceCall(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{

DbgPrint("设备关闭\r\n");
//设置返回状态
pIrp->IoStatus.Status = STATUS_SUCCESS; // getlasterror()得到的就是这个值
pIrp->IoStatus.Information = 0; // 返回给3环多少数据
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

NTSTATUS ControlDeviceCall(PDEVICE_OBJECT pDevObj, PIRP pIrp)
{
UINT32 IoControlCode;
PVOID pBuff;
PsGetProcessPeb PsGetPeb = NtosBase + 0xC3E39;
//获取IRP数据
PIO_STACK_LOCATION PIrpStack = IoGetCurrentIrpStackLocation(pIrp);
UINT32 LineAddress = 0;
IoControlCode = PIrpStack->Parameters.DeviceIoControl.IoControlCode;
if (IoControlCode == OPER1) {
//获取缓冲区地址
pBuff = pIrp->AssociatedIrp.SystemBuffer;
UINT32 ProcessId = *(PUINT32)pBuff;
DbgPrint("进程ID为:%x\r\n", ProcessId);
PVOID buffer = ExAllocatePoolWithTag(NonPagedPool, 0x4, "yoyoTag");
if (!buffer) {
DbgPrint("申请内存失败\r\n");
}
else {

LineAddress = *((PUINT32)pBuff + 1);
//MessageBoxA函数对应的线性地址,该地址由应用程序提供 用到了通信 此部分自行了解
LineAddress = get_offset(LineAddress);
ReadUserMemory(ProcessId, LineAddress, buffer, 4);//得到线性地址对应pte属性
DbgPrint("%x:%x\r\n", LineAddress ,*(PUINT32)buffer);
*(PUINT32)buffer = *(PUINT32)buffer | 2 //异或,修改R/W位,将该位置1
WriteUserMemory(ProcessId, LineAddress, buffer, 4);//回填
DbgPrint("%x:%x\r\n", LineAddress ,*(PUINT32)buffer);
//shellcoed所在线性地址,也是由应用程序提供,该线性地址必须是USER32.dll对应的物理页(可以思考一下为啥)
LineAddress = *((PUINT32)pBuff + 2);
LineAddress = get_offset(LineAddress);
ReadUserMemory(ProcessId, LineAddress, buffer, 4);
DbgPrint("%x:%x\r\n", LineAddress ,*(PUINT32)buffer);
*(PUINT32)buffer = *(PUINT32)buffer | 2;
WriteUserMemory(ProcessId, LineAddress, buffer, 4);//将shellcode所在物理页对应的pte进行修改,不修改会引发写拷贝
DbgPrint("%x:%x\r\n", LineAddress, *(PUINT32)buffer);
ExFreePoolWithTag(buffer, "yoyoTag");
}

}

//设置返回状态
pIrp->IoStatus.Status = STATUS_SUCCESS; // getlasterror()得到的就是这个值
pIrp->IoStatus.Information = 0; // 返回给3环多少数据
IoCompleteRequest(pIrp, IO_NO_INCREMENT);
return STATUS_SUCCESS;
}

VOID CreateDevice(PDRIVER_OBJECT driver) {

//创建设备名称
UNICODE_STRING DeviceString, SymbolicLinkName;
NTSTATUS status = 0;
PDEVICE_OBJECT PDeviceObject;
RtlInitUnicodeString(&DeviceString, L"\\Device\\yoyo_device");
//创建设备对象
status = IoCreateDevice(driver, 0, &DeviceString, FILE_DEVICE_UNKNOWN, FILE_DEVICE_SECURE_OPEN, FALSE, &PDeviceObject);
if (status) {
DbgPrint("设备对象创建失败\r\n");
return STATUS_SUCCESS;
}
//设置交互数据的方式
PDeviceObject->Flags = DO_BUFFERED_IO;
//创建符号链接名称
RtlInitUnicodeString(&SymbolicLinkName, L"\\??\\yoyo_Driver");
//创建符号链接
IoCreateSymbolicLink(&SymbolicLinkName, &DeviceString);
//设置派遣函数
driver->MajorFunction[IRP_MJ_CREATE] = CreateDeviceCall;
driver->MajorFunction[IRP_MJ_CLOSE] = CloseDeviceCall;
driver->MajorFunction[IRP_MJ_DEVICE_CONTROL] = ControlDeviceCall;
DbgPrint("设备对象创建成功\r\n");

}
// DriverEntry,入口函数相当于main。
NTSTATUS DriverEntry(PDRIVER_OBJECT driver, PUNICODE_STRING reg_path)
{
FingNtosKernelModule(driver, &NtosBase);
DbgPrint("内核模块基址:%x\r\n", NtosBase);
CreateDevice(driver);
driver->DriverUnload = DriverUnload;
return STATUS_SUCCESS;
}

接下来是应用程序部分,只写了一个hook操作,

#include "stdafx.h"
#include <winioctl.h>
#define OPER CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define DeviceName L"\\\\.\\yoyo_Driver"
#define HOOKDLLNAME "user32.dll"
#define HOOKCALL "MessageBoxA"
DWORD HookApi(){
char shellcode [] = {0x60,0x9c,0xe8,0x00,0x00,0x00,0x00,0x5b,0x81,0xeb,0x07,0x00,0x00,0x00,0x8d,0x83,
0x1f,0x00,0x00,0x00,0x89,0x44,0x24,0x30,0x9d,0x61,0xe9,0xf4,0x5a,0xff,0xff,0x79,0x6f,0x79,0x6f,0x00};
HMODULE hDll = LoadLibraryA(HOOKDLLNAME);
if(!hDll){
return 0;
}
FARPROC CallAddress = GetProcAddress(hDll,HOOKCALL);
if(!CallAddress){
return 0;
}
DWORD CurrentProcessId = GetCurrentProcessId();

HANDLE hDevice = CreateFileW(DeviceName,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hDevice == INVALID_HANDLE_VALUE){
return 0;
}
int* Inbuff = (int*)malloc(12);
memset(Inbuff,0,12);
*Inbuff = *(int*)CallAddress;//读取一下该线性地址,否则操作系统不会挂物理页,pte是0
*Inbuff = CurrentProcessId;
*(Inbuff+1) = DWORD(CallAddress);
*(Inbuff+2) = DWORD(CallAddress)+0xA4EF;
DWORD BytesReturned;
DeviceIoControl(hDevice,OPER,Inbuff,12,NULL,NULL,&BytesReturned,NULL);
//修改跳转
__asm{
mov ebx,CallAddress
mov word ptr [ebx],0xf9eb
sub ebx,5
mov dword ptr [ebx],0xa4efe9
add ebx,4
mov byte ptr [ebx],0x0

}
//将shellcode复制到user32.dll代码段物理页的空闲位置
memcpy((char*)(DWORD(CallAddress)+0xA4EF),shellcode,sizeof(shellcode));
return 1;
}

接下来看效果
shellcode只是修改了标题,比较懒,不想去实现其他的功能了

最终效果

可以看到a进程和b进程标题已经全部被改,执行了shellcode,以上就是全部内容,当然有很多不足之处,大佬轻喷!


注:若转载请注明大神论坛来源(本贴地址)与作者信息。

返回顶部