零地址
如果我们有比较好的C编程基础,我们就会知道,我们在代码中定义了一个零地址或者空指针,那么它实际上会指向虚拟内存的零地址,多数操作系统,包括Win,在进程创建的时候,都会空出前64k的空间大小,来确保NULL等值不指向任何地方。
我们使用CE来打开一个记事本,可以看见0地址处确实被分配了64k的大小(0x10000),这里提一下,操作系统的内存分配最低是64k,哪怕你只是申请一个字节,那么也会至少给你64k。
这个零地址处,我们正常来说是不能利用的,这里随便写一段代码试一试
// 0_Address_Page_Alloc.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include<windows.h>
int* x = 0;
int _tmain(int argc, _TCHAR* argv[])
{
system("pause");
*x = 100;
return 0;
}
我们正常运行这段代码,报错C0005,再看下面的内存全部是??,这就是说明这一块内存并没有被挂上页
那么,针对这一块内存,我们既然之前学习了页的一些知识,我们是不是可以想办法利用一下呢?
实操
思路
修改我们的代码,申请一块内存空间之后,用这块已经挂上页的内存空间的PTE写入到我们零地址对应的PTE处,实现可以修改零地址处的值(实际上还是共享了页)
// 0_Address_Page_Alloc.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include<windows.h>
int* x = 0;
int _tmain(int argc, _TCHAR* argv[])
{
int* Base =(int*) VirtualAlloc(NULL,0x1000,MEM_COMMIT,PAGE_READWRITE);
memset(Base,0,0x1000);
printf("%x\r\n",Base);
system("pause");
*x = 100;
system("pause");
printf("Base is %d\r\n",*Base);
return 0;
}
我们首先知道了地址是d0000
先看看我们的零地址处,果不其然没挂页
1: kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 86cdd8e8 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 8ec01b28 HandleCount: 524.
Image: System
................................................
PROCESS 9525cc50 SessionId: 1 Cid: 0cb8 Peb: 7ffd7000 ParentCid: 02b8
DirBase: 8872c000 ObjectTable: b5fcfa98 HandleCount: 24.
Image: 0_Address_Page_Alloc.exe
PROCESS 8780d3a8 SessionId: 1 Cid: 0de0 Peb: 7ffdc000 ParentCid: 01a8
DirBase: 2875c000 ObjectTable: c6538298 HandleCount: 62.
Image: conhost.exe
PROCESS 87591c08 SessionId: 1 Cid: 0f50 Peb: 7ffdf000 ParentCid: 0cb8
DirBase: 2d9d6000 ObjectTable: ba055e68 HandleCount: 30.
Image: cmd.exe
1: kd> !dd 8872c000
#8872c000 3e885867 25abb867 1d784867 00000000
#8872c010 00000000 00000000 00000000 00000000
#8872c020 00000000 00000000 00000000 00000000
#8872c030 00000000 00000000 00000000 00000000
#8872c040 00000000 00000000 00000000 00000000
#8872c050 00000000 00000000 00000000 00000000
#8872c060 00000000 00000000 00000000 00000000
#8872c070 00000000 00000000 00000000 00000000
1: kd> !dd 3e885000
#3e885000 00000000 00000000 00000000 00000000
#3e885010 00000000 00000000 00000000 00000000
#3e885020 00000000 00000000 00000000 00000000
#3e885030 00000000 00000000 00000000 00000000
#3e885040 261a0867 00000000 00000000 00000000
#3e885050 00000000 00000000 00000000 00000000
#3e885060 00000000 00000000 00000000 00000000
#3e885070 00000000 00000000 00000000 00000000
然后再看我们申请的内存,相关区域以及全部被置为了0
1: kd> !dd 3e885000 + d0*4
#3e885340 419d3867 00000000 00000000 00000000
#3e885350 00000000 00000000 00000000 00000000
#3e885360 00000000 00000000 00000000 00000000
#3e885370 00000000 00000000 00000000 00000000
#3e885380 00000000 00000000 00000000 00000000
#3e885390 00000000 00000000 00000000 00000000
#3e8853a0 00000000 00000000 00000000 00000000
#3e8853b0 00000000 00000000 00000000 00000000
1: kd> !dd 419d3867
#419d3864 00000000 00000000 00000000 00000000
#419d3874 00000000 00000000 00000000 00000000
#419d3884 00000000 00000000 00000000 00000000
#419d3894 00000000 00000000 00000000 00000000
#419d38a4 00000000 00000000 00000000 00000000
#419d38b4 00000000 00000000 00000000 00000000
#419d38c4 00000000 00000000 00000000 00000000
#419d38d4 00000000 00000000 00000000 00000000
这时候,把我们的PTE(419d3867),放到零地址对应的PTT里面
1: kd> !ed 3e885000 419d3867
1: kd> !dd 3e885000
#3e885000 419d3867 00000000 00000000 00000000
#3e885010 00000000 00000000 00000000 00000000
#3e885020 00000000 00000000 00000000 00000000
#3e885030 00000000 00000000 00000000 00000000
#3e885040 261a0867 00000000 00000000 00000000
#3e885050 00000000 00000000 00000000 00000000
#3e885060 00000000 00000000 00000000 00000000
#3e885070 00000000 00000000 00000000 00000000
继续放行程序,可以看见我们的代码就没有报错,成功将0地址处的值改为了100
所以我们就知道,在10-10-12分页的条件下,MMU本身并不会直接验证所接收到的地址的合法性,只要指向的物理页是真实存在的即可
继续深入的想想
上面我们通过了手动修改PTE向零地址里面存储字节,那么这个存储的字节能不能是一段shellcode呢,我们先手动得到MessageBox的地址,然后用硬编码来调用
#include "stdafx.h"
#include<windows.h>
#include<cstdio>
typedef void(__stdcall * FuncProc)();
int _tmain(int argc, _TCHAR* argv[])
{
FuncProc func = NULL;
int* Base = (int*)VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (Base == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
memset(Base, 0, 0x1000);
char bufcode[] = {
0x6a, 0, // push 0
0x6a, 0, // push 0
0x6a, 0, // push 0
0x6a, 0, // push 0
0xb8, 0, 0, 0, 0, // mov eax, <address of MessageBoxA>
0xff, 0xd0, // call eax
0xc3, // ret
};
// 将 MessageBoxA 的地址写入 bufcode 的适当位置
*(void**)(&bufcode[9]) = (void*)MessageBoxA;
// 将 bufcode 复制到 Base
memcpy(Base, bufcode, sizeof(bufcode));
printf("%x\r\n",Base);
system("pause");
// 将 Base 转换为 FuncProc 类型并调用
func = (FuncProc)Base;
func();
// 释放分配的内存
VirtualFree(Base, 0, MEM_RELEASE);
return 0;
}
这是能够正常弹出框的
那我们按照之前的思路,注释掉bufcode对fun的赋值,然后运行程序,修改零地址处的PTE,来调用MessageBoxA
按照上面的偏移找到我们所申请的地址,里面的硬编码已经存好了
0: kd> !process 0 0
**** NT ACTIVE PROCESS DUMP ****
PROCESS 86cdd8e8 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 8ec01b28 HandleCount: 520.
Image: System
...............................................................
PROCESS 875428a8 SessionId: 1 Cid: 0e34 Peb: 7ffdb000 ParentCid: 02b8
DirBase: 31f7c000 ObjectTable: aef17ab8 HandleCount: 30.
Image: 0_Address_Page_Alloc.exe
PROCESS 8779b838 SessionId: 1 Cid: 03b4 Peb: 7ffd5000 ParentCid: 01a8
DirBase: 6cda0000 ObjectTable: baaaf2d0 HandleCount: 62.
Image: conhost.exe
PROCESS 873e9d40 SessionId: 1 Cid: 09d4 Peb: 7ffda000 ParentCid: 0e34
DirBase: 15210000 ObjectTable: c6538298 HandleCount: 30.
Image: cmd.exe
0: kd> !dd 31f7c000
#31f7c000 8bf9d867 319e4867 2349c867 00000000
#31f7c010 00000000 00000000 00000000 00000000
#31f7c020 00000000 00000000 00000000 00000000
#31f7c030 00000000 00000000 00000000 00000000
#31f7c040 00000000 00000000 00000000 00000000
#31f7c050 00000000 00000000 00000000 00000000
#31f7c060 00000000 00000000 00000000 00000000
#31f7c070 00000000 00000000 00000000 00000000
0: kd> !dd 8bf9d000 + 1f0*4
#8bf9d7c0 1940f867 00000000 00000000 00000000
#8bf9d7d0 00000000 00000000 00000000 00000000
#8bf9d7e0 00000000 00000000 00000000 00000000
#8bf9d7f0 00000000 00000000 00000000 00000000
#8bf9d800 00000000 00000000 00000000 00000000
#8bf9d810 00000000 00000000 00000000 00000000
#8bf9d820 00000000 00000000 00000000 00000000
#8bf9d830 00000000 00000000 00000000 00000000
0: kd> !db 1940f000
#1940f000 6a 00 6a 00 6a 00 6a 00-b8 11 ea f4 75 ff d0 c3 j.j.j.j.....u...
#1940f010 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#1940f020 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#1940f030 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#1940f040 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#1940f050 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#1940f060 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#1940f070 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
此时我们在代码里面将fun声明为NULL,所以我们还是一样,把我们的PTE填入零地址对应的PTT里面
0: kd> !dd 8bf9d000
#8bf9d000 00000000 00000000 00000000 00000000
#8bf9d010 00000000 00000000 00000000 00000000
#8bf9d020 00000000 00000000 00000000 00000000
#8bf9d030 00000000 00000000 00000000 00000000
#8bf9d040 65fb9847 00000000 00000000 00000000
#8bf9d050 00000000 00000000 00000000 00000000
#8bf9d060 00000000 00000000 00000000 00000000
#8bf9d070 00000000 00000000 00000000 00000000
0: kd> !ed 8bf9d000 1940f867
0: kd> !dd 8bf9d000
#8bf9d000 1940f867 00000000 00000000 00000000
#8bf9d010 00000000 00000000 00000000 00000000
#8bf9d020 00000000 00000000 00000000 00000000
#8bf9d030 00000000 00000000 00000000 00000000
#8bf9d040 65fb9847 00000000 00000000 00000000
#8bf9d050 00000000 00000000 00000000 00000000
#8bf9d060 00000000 00000000 00000000 00000000
#8bf9d070 00000000 00000000 00000000 00000000
之后我们继续放行程序,可以看见被声明为了NULL的函数指针还是调用了我们的shellcode,弹窗成功了
这更进一步验证了我们的学习,CPU读这些数据的时候不会真的去验证虚拟地址的真实性,只要对应的物理地址是可以被访问的,那么虚拟地址即使被更改只要不触发页异常就不会有问题
更加深入的想想
既然我们已经可以向一块被标记为Free的内存写入字节,那么,我们可不可以向一个进程里面的零地址写入字节然后远程线程跑我们的shellcode呢
先把代码贴出来
#include "stdafx.h"
#include<windows.h>
#include<cstdio>
typedef void(__stdcall * FuncProc)();
int _tmain(int argc, _TCHAR* argv[])
{
FuncProc func = NULL;
char* Base = (char*)VirtualAlloc(NULL, 0x1000, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (Base == NULL) {
printf("Memory allocation failed!\n");
return 1;
}
memset(Base, 0, 0x1000);
char bufcode[] = {
0x6a, 0, // push 0
0x6a, 0, // push 0
0x6a, 0, // push 0
0x6a, 0, // push 0
0xb8, 0, 0, 0, 0, // mov eax, <address of MessageBoxA>
0xff, 0xd0, // call eax
0xc3, // ret
};
// 将 MessageBoxA 的地址写入 bufcode 的适当位置
*(void**)(&bufcode[9]) = (void*)MessageBoxA;
// 将 bufcode 复制到 Base
memcpy(Base+0x200, bufcode, sizeof(bufcode));
printf("%x\r\n",Base);
system("pause");
// 将 Base 转换为 FuncProc 类型并调用
//func = (FuncProc)Base;
//func();
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS,FALSE,3016);
HANDLE hThread = CreateRemoteThread(hProcess,NULL,NULL,(LPTHREAD_START_ROUTINE)0x200,NULL,NULL,NULL);
CloseHandle(hThread);
CloseHandle(hProcess);
system("pause");
// 释放分配的内存
VirtualFree(Base, 0, MEM_RELEASE);
return 0;
}
上面的代码打开了一个notepad(3016)进程,然后朝里面写入了一个shellcode
首先还是先查到我们所申请内存的PTE
1: kd> !process 0 0//找到我们需要的进程
**** NT ACTIVE PROCESS DUMP ****
PROCESS 86cdd8e8 SessionId: none Cid: 0004 Peb: 00000000 ParentCid: 0000
DirBase: 00185000 ObjectTable: 8ec01b28 HandleCount: 529.
Image: System
......................................................
PROCESS 87372b20 SessionId: 1 Cid: 0bc8 Peb: 7ffd5000 ParentCid: 062c
DirBase: 2e224000 ObjectTable: af7f5fc0 HandleCount: 63.
Image: notepad.exe
.........................................
PROCESS 878d65f0 SessionId: 1 Cid: 0b10 Peb: 7ffdf000 ParentCid: 02b8
DirBase: 41ffb000 ObjectTable: b9f3aa50 HandleCount: 30.
Image: 0_Address_Page_Alloc.exe
我们可以看见,在页中我们也挂到了对应偏移0x200的位置, 这个2c7e0847就是我们要找的值
1: kd> !dd 41ffb000+ 1*4
#41ffb004 00ab5867 ae4e1867 0be6d867 00000000
#41ffb014 00000000 00000000 00000000 00000000
#41ffb024 00000000 00000000 00000000 00000000
#41ffb034 00000000 00000000 00000000 00000000
#41ffb044 00000000 00000000 00000000 00000000
#41ffb054 00000000 00000000 00000000 00000000
#41ffb064 00000000 00000000 00000000 00000000
#41ffb074 00000000 00000000 00000000 00000000
1: kd> !dd 00ab5000 + 4*e0
# ab5380 2c7e0847 00000000 00000000 00000000
# ab5390 00000000 00000000 00000000 00000000
# ab53a0 00000000 00000000 00000000 00000000
# ab53b0 00000000 00000000 00000000 00000000
# ab53c0 00000000 00000000 00000000 00000000
# ab53d0 00000000 00000000 00000000 00000000
# ab53e0 00000000 00000000 00000000 00000000
# ab53f0 00000000 00000000 00000000 00000000
1: kd> !dd 2c7e0000
#2c7e0000 00000000 00000000 00000000 00000000
#2c7e0010 00000000 00000000 00000000 00000000
#2c7e0020 00000000 00000000 00000000 00000000
#2c7e0030 00000000 00000000 00000000 00000000
#2c7e0040 00000000 00000000 00000000 00000000
#2c7e0050 00000000 00000000 00000000 00000000
#2c7e0060 00000000 00000000 00000000 00000000
#2c7e0070 00000000 00000000 00000000 00000000
1: kd> !db 2c7e0000+0x200
#2c7e0200 6a 00 6a 00 6a 00 6a 00-b8 11 ea f4 75 ff d0 c3 j.j.j.j.....u...
#2c7e0210 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#2c7e0220 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#2c7e0230 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#2c7e0240 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#2c7e0250 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#2c7e0260 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#2c7e0270 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
我们打开notepad的零地址,把这个值填进去
1: kd> !dd 2e224000
#2e224000 3d720867 6c21f867 11cc2867 00000000
#2e224010 3f78c867 7468d847 2e4c8867 40304867
#2e224020 0c707867 06e09867 00000000 00000000
#2e224030 00000000 00000000 00000000 00000000
#2e224040 00000000 00000000 00000000 00000000
#2e224050 00000000 00000000 00000000 00000000
#2e224060 00000000 00000000 00000000 00000000
#2e224070 00000000 00000000 00000000 00000000
1: kd> !dd 3d720000
#3d720000 00000000 00000000 00000000 00000000
#3d720010 00000000 00000000 00000000 00000000
#3d720020 00000000 00000000 00000000 00000000
#3d720030 00000000 00000000 00000000 00000000
#3d720040 41e37847 00000000 00000000 00000000
#3d720050 00000000 00000000 00000000 00000000
#3d720060 00000000 00000000 00000000 00000000
#3d720070 00000000 00000000 00000000 00000000
1: kd> !ed 3d720000 2c7e0847
1: kd> !dd 3d720000
#3d720000 2c7e0847 00000000 00000000 00000000
#3d720010 00000000 00000000 00000000 00000000
#3d720020 00000000 00000000 00000000 00000000
#3d720030 00000000 00000000 00000000 00000000
#3d720040 41e37847 00000000 00000000 00000000
#3d720050 00000000 00000000 00000000 00000000
#3d720060 00000000 00000000 00000000 00000000
#3d720070 00000000 00000000 00000000 00000000
放行程序,继续运行,这时候我们的弹框就跑在notepad的进程中了
最后
我们现在是用windbg手动去修改PTE,但是随着我们学习的深入,我们就可以使用程序来自动化这一过程,来达到我们注入shellcode的效果