(15)[驱动开发]过写拷贝

写拷贝是什么

写拷贝是一种内存权限,是3环用的
在这里插入图片描述
使用 !vad EPROCESS.VadRoot 查看内存权限,主要关注的地址是0x77d507ea(MessageBoxA)
下方蓝条范围内,是执行_写拷贝权限
在这里插入图片描述
什么是写拷贝,就是你要往里面修改数据的时候,就会拷贝一份内容放入另一个内存且自己独占,并修改独占的内存,不会直接修改原来的内存
大概就是我不喜欢流水线大批量生产的东西,于是我自己定制了一份只有自己能用的

 别人用               自己用
 内存123		    内存567    
+-------+       +--------------+
|内容1  | --->  |修改后的内容1  |
|内容2  |       |内容2         |
|内容3  |       |内容3         |

思路

当异常产生时,就会转入异常处理函数,判断当前操作是否合法。我们之前学到了缺页的时候会触发异常,读写权限不对的时候也会出现异常,但是只要不触发异常,他就不会检测这个操作是否合法。

而罪魁祸首居然是PTE没有 r/w权限(0B 0000 0010)的位置
在这里插入图片描述
相信大家已经轻车熟路了。刚才没注意,代码里没写MessageBoxA,所以用不到这个函数,所以PTE表里不需要加载这块物理内存,所以没有执行MessageBoxA之前,PTE是空的

3环代码

我们知道call后会push EIP
所以call MessageBoxA后的地址应该是

MessageBoxA(参数1, 参数2, 参数3, 参数4)
EIP     <-- ESP
参数1   +0x4
参数2   +0x8
参数3   +0xC
参数4   +0x10

我的shell code 非常简单,只是把第4个参数改了
shell code 到OD里写出人能看懂的语句,然后照抄机器码就行

#include<windows.h>
#include<winioctl.h>
#include<stdio.h>
#define IN_BUFFER_MAXLENGTH  0x10
#define OUT_BUFFER_MAXLENGTH  0x10
//宏定义之获取一个32位的宏控制码  参数:设备类型(鼠标,键盘...Unkonwn);0x000-0x7FF保留,0x800-0xfff随便填一个;数据交互类型(缓冲区,IO,其他);对这个设备的权限
#define OPER1 CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS)
#define OPER2 CTL_CODE(FILE_DEVICE_UNKNOWN,0x900,METHOD_BUFFERED,FILE_ANY_ACCESS)

#define SYMBOLICLINK_NAME "\\\\.\\MyTestDriver"  

HANDLE g_hDevice;   //全局驱动句柄

//打开驱动服务句柄
//3环链接名:\\\\.\\AABB

BOOL Open(PCHAR pLinkName)
{
	//在3环获取设备句柄
	TCHAR szBuffer[10] = { 0 };
	//CreateFile  打开的是内核的设备对象 
	g_hDevice = CreateFile(pLinkName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
	if (g_hDevice != INVALID_HANDLE_VALUE)
		return TRUE;
	else
		return FALSE;

}
//dword写到byte[]里,由于要用两次,所以写了函数
//主要用来填充跳转地址的
void writeDword2Byte(BYTE* dst, DWORD src){
	dst[0] = src >> 0;
	dst[1] = src >> 8;
	dst[2] = src >> 16;
	dst[3] = src >> 24;
}

int main(int argc, char* argv[])
{   //通过这个jmp,跳转到要执行的shell code
	BYTE shellCode[] = {0xe9, 00, 00, 00, 00}; //jump xxxx to shell code (hook)
	//玩完记得弄回去
	BYTE shellCode_writeback[5]; // restore
	//真正的shell code
	BYTE shellCode_run[] = {
		0xC7, 0x44, 0x24, 0x10, 0x01, 0x00, 0x00, 0x00, //mov dword ptr[esp + 0x10], 1
		0x55,					//push ebp 
		0x8B, 0xEC,				//mov ebp, esp 由于hook覆盖了两条语句,所以要写回来
		0xe9, 00, 00, 00, 00};  //jmp back
	DWORD dwInBuffer[2], szOutBuffer[4];
	DWORD Sz0utBuffer = 0;
	DWORD ByteReturned = 0;
	DWORD my_PID;
	DWORD msg_addr;
	BYTE* run_addr;
	DWORD offset_jmp;
	DWORD offset_jmpback;
	BYTE *pMsg_addr;
	int i; //C代码声明要放最前面,不然报错

	//MessageBoxA( NULL, "before run", NULL, 0); //观察PTE用
	msg_addr = (DWORD) MessageBoxA; //这个拿去做加减运算
	pMsg_addr = (BYTE*)MessageBoxA; //这个拿去写入
	//给ring 0传递的PID
	my_PID = (DWORD)GetCurrentProcessId();
	
	printf("addr: %X\tPID: %X", msg_addr, my_PID);
	//保存写回去的地址
	memcpy(shellCode_writeback, pMsg_addr, sizeof(shellCode));
	//申请可执行内存,这里申请的内存不能被其他进程访问,所以要想完美完成实验,
	//需要在高2G申请内存,并使其能被用户进程访问
	run_addr = (BYTE*)VirtualAlloc(NULL, sizeof(shellCode_run), MEM_COMMIT, PAGE_EXECUTE_READWRITE);

	// offset = dst - (src + codeLength)
	offset_jmp = (DWORD)run_addr - (msg_addr + 5);
	writeDword2Byte(shellCode + 1, offset_jmp);
	//这里加上sizeof(shellCode)是为了防止跳到自己写的jmp上死循环
	offset_jmpback = msg_addr - ((DWORD)run_addr  + sizeof(shellCode_run)) + sizeof(shellCode);
	writeDword2Byte(shellCode_run + sizeof(shellCode_run)-4, offset_jmpback);
	//将要执行的 shell code 写入可执行内存
	memcpy(run_addr, shellCode_run, sizeof(shellCode_run));
	//准备冻手,准备冻手
	//1.通过符号链接,打开设备
	if (!Open(SYMBOLICLINK_NAME))
	{
		printf("设备对象打开失败!!!");
		getchar();
		return 0;
	}
	//传入这俩
	dwInBuffer[0] = msg_addr;
	dwInBuffer[1] = my_PID;
	//2.测试通信
	DeviceIoControl(g_hDevice, OPER2, dwInBuffer, IN_BUFFER_MAXLENGTH, szOutBuffer, OUT_BUFFER_MAXLENGTH, &ByteReturned, NULL);
	//写入jmp xxxx
	memcpy(pMsg_addr, shellCode, sizeof(shellCode));

	//3.关闭设备
	getchar();
	CloseHandle(g_hDevice);
	//玩完了写回去
	memcpy(pMsg_addr, shellCode_writeback, sizeof(shellCode));
	return 0;
}

0环代码

由于0环代码通信部分又臭又长,是我从网上扒下来的。感谢这些热心网友提供的代码(不记得从哪里扒的了,只是一直扒扒到能用)

这里指提供关键代码

//ProcessID:3环进程的PID
//addr: 要修改权限的地址
NTSTATUS changePageAttribute(HANDLE ProcessId, DWORD32 addr)
{
    NTSTATUS	Status = STATUS_SUCCESS;
    PEPROCESS	pEProcess = NULL;
    //恢复状态用的,我也不太清楚这个
    KAPC_STATE	ApcState = { 0 };
    DWORD32* pte;

    _asm int 3;
    //根据之前所学的公式直接套用即可
    pte = (DWORD32*)(((addr >> 9) & 0x7FFFF8) + 0xC0000000);//x的pte
    //获取EPROCESS结构体,很重要
    Status = PsLookupProcessByProcessId((HANDLE)ProcessId, &pEProcess);
    if (!NT_SUCCESS(Status) && !MmIsAddressValid(pEProcess)) { return STATUS_UNSUCCESSFUL; }
    //加上这个try不会蓝屏,但是有些问题依然要重启的
    __try {
    	//进程挂靠,就是把CR3改得和3环应用一样的,这样就能直接读3环应用的地址了
    	//有上层的API干嘛要自己搞
        KeStackAttachProcess(pEProcess, &ApcState);
        //好吧,其实0环做的所有事情就只有这一件,加上读写属性
        *pte |= 0x2; //0000 0010
		//结束挂靠
        KeUnstackDetachProcess(&ApcState);
    }
    __except (EXCEPTION_EXECUTE_HANDLER) { 
        KeUnstackDetachProcess(&ApcState);
        Status = STATUS_UNSUCCESSFUL;
    }
    ObDereferenceObject(pEProcess);
    return Status;
}

如果觉得麻烦,完全可以在0环修改GDT表或者IDT表,弄个门,然后让3环调用,然后复用以前的代码

若果你对上面的函数不满意,还有两个更底层的进程挂靠函数可以用
甚至你可以在
EPROCESS[0]的地方找到KPROCESS
KPROCESS[0x18]的地方找到DirectoryTableBase,这就是CR3
然后就用汇编代码mov CR3, mycr3,改完PTE在弄回去

先执行驱动,在执行程序


好吧,其他进程调用MessageBoxA会直接崩溃,这说明我成功了,但也失败了
写保护过了,但是 shell code 没有成功,总归是有些失落的
原因是virtualAlloc申请的内存并不能被其他进程拥有。所以接下来要别人执行自己的shellcode,就需要申请高2G的内存,这样线性地址就是固定的了。
然后再把高2G的PTE改写一下,使他可以被用户程序访问

懒得搞了,有机会的话,会来补更的。
失败了但也有收获不是吗

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值