lracker's Studio.

安全开发面试

Word count: 1.6kReading time: 6 min
2020/01/27 Share

月初的时候,接受到某公司的面试,不过大多数题目都是根据我项目来问的,所以仅供参考。

下面只是一部分题目。

1. 如何判断用户层的地址是否有效?内核层的呢?

使用ProbeForRead

在wdk中我们可以看到它是这样的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void ProbeForRead(
const volatile VOID *Address,
SIZE_T Length,
ULONG Alignment
)
{
ASSERT(((Alignment) == 1) || ((Alignment) == 2) || ((Alignment) == 4) || \ ((Alignment) == 8) || ((Alignment) == 16));
if ((Length) != 0) {
if (((ULONG_PTR)(Address) & ((Alignment) - 1)) != 0) {
ExRaiseDatatypeMisalignment();
}
if ((((ULONG_PTR)(Address) + (Length)) > (ULONG_PTR)MM_USER_PROBE_ADDRESS) || (((ULONG_PTR)(Address) + (Length)) < (ULONG_PTR)(Address))) {
*(volatile UCHAR * const)MM_USER_PROBE_ADDRESS = 0;
}
}
}

可以看到,他首先是对Alignment进行了判断,也就是指定用户模式缓冲区开头所需的对齐方式一定是1、2、4、8、16,否则的话直接退出了。

接着就判断Length长度是否为0,不为0的话判断一下这个Address地址是否按照Alignment指定字节数对齐,如果没有对齐的话,则会抛出异常。

msdn上是这么说的:

如果地址范围的开头未与Alignment指定的字节边界对齐,则ProbeForRead会引发STATUS_DATATYPE_MISALIGNMENT异常

接着判断一下这个地址是否在用户态范围内。

msdn上是这么说的:

如果指定的内存范围不在用户模式地址范围内,ProbeForRead会引发STATUS_ACCESS_VIOLATION异常。

因此我们使用这个ProbeForRead的时候需要在try/except内,方便接收异常。

在msdn上还有这样的一段话:

不要在内核模式地址上使用此例程;它将引发异常。

如果Irp-> RequestorMode = KernelMode,则Irp-> AssociatedIrp.SystemBufferIrp-> UserBuffer字段不包含用户模式地址,并且调用ProbeForRead以探查由任一字段指向的缓冲区将引发异常。

因此它并不能检测内核层的地址是否有效。

而内核层的话,我们应该使用这个MmIsAddressValid

实际上它又调用了MiIsAddressValid (VirtualAddress, FALSE);

MiIsAddressValid 如下:

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
{
PMMPTE PointerPte;
UNREFERENCED_PARAMETER (UseForceIfPossible);

#if defined (_AMD64_)

//
// If this is within the physical addressing range, just return TRUE.
//
// 如果这在物理寻址范围内,则返回TRUE。

if (MI_IS_PHYSICAL_ADDRESS(VirtualAddress)) {

PFN_NUMBER PageFrameIndex;

//
// Only bound with MmHighestPhysicalPage once Mm has initialized.
//

if (MmHighestPhysicalPage != 0) {

PageFrameIndex = MI_CONVERT_PHYSICAL_TO_PFN(VirtualAddress);

if (PageFrameIndex > MmHighestPhysicalPage) {
return FALSE;
}
}

return TRUE;
}

#endif

//
// If the address is not canonical then return FALSE as the caller (which
// may be the kernel debugger) is not expecting to get an unimplemented
// address bit fault.
//
// 判断地址是否规范

if (MI_RESERVED_BITS_CANONICAL(VirtualAddress) == FALSE) {
return FALSE;
}

#if (_MI_PAGING_LEVELS >= 4)
PointerPte = MiGetPxeAddress (VirtualAddress);
if (PointerPte->u.Hard.Valid == 0) {
return FALSE;
}
#endif

#if (_MI_PAGING_LEVELS >= 3)
PointerPte = MiGetPpeAddress (VirtualAddress);

if (PointerPte->u.Hard.Valid == 0) {
return FALSE;
}
#endif

PointerPte = MiGetPdeAddress (VirtualAddress);
if (PointerPte->u.Hard.Valid == 0) {
return FALSE;
}

if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) {
return TRUE;
}

PointerPte = MiGetPteAddress (VirtualAddress);
if (PointerPte->u.Hard.Valid == 0) {
return FALSE;
}

//
// Make sure we're not treating a page directory as a page table here for
// the case where the page directory is mapping a large page. This is
// because the large page bit is valid in PDE formats, but reserved in
// PTE formats and will cause a trap. A virtual address like c0200000 (on
// x86) triggers this case.
//

if (MI_PDE_MAPS_LARGE_PAGE (PointerPte)) {
return FALSE;
}

return TRUE;
}

也就是检查了是否在物理寻址范围内和利用了分页机制去查询。

msdn上提到:

如果在给定的虚拟地址上进行读取或写入操作不会发生页面错误,则MmIsAddressValid返回TRUE

但是也提到了这一点:

即使MmIsAddressValid返回TRUE,访问该地址也可能导致页面错误,除非内存已被锁定或该地址是有效的非页面缓冲池地址。

对于这一点,我也没找到方法,引用看雪上qihoocom的一句话

没有100% OK的方法。

不过调用这个函数的话就不需要使用try/except来接收异常了

2. tf标志位的作用是什么

​ 当软件断点被命中的时候,这个程序的控制就交到用户手上了。而程序还没执行指令,所以我们设置tf标志位后,程序会在那一瞬间单步步入一下。并且将这个控制权立即返回给用户。

3. 附加进程的那个api有没有研究过

DebugActiveProcess

这里有一篇文章说的挺好的,可以参考一下这个。

https://www.cnblogs.com/zibility/p/5683280.html

4. 内存释放的原理

参考https://www.cnblogs.com/kex1n/archive/2011/01/26/2286427.html

使用SetProcessWorkingSetSize。

msdn上说

使用这个函数来设置应用程序最小和最大的运行空间,只会保留需要的内存。当应用程序被闲置或系统内存太低时,操作系统会自动调用这个机制来设置应用程序的内存。应用程序也可以使用 VirtualLock 来锁住一定范围的内存不被系统释放。

当你加大运行空间给应用程序,你能够得到的物理内存取决于系统,这会造成其他应用程序降低性能或系统总体降低性能,这也可能导致请求物理内存的操作失败,例如:建立 进程,线程,内核池,就必须小心的使用该函数。

事实上,这并不会提高什么性能,也不会真的节省内存。只是暂时的将应用程序占用的内存移至虚拟内存,一旦应用程序被级货或者有操作请求,这些内存会被重新占用。如果你用着方法来设置程序占用的内存,反而可能会降低系统性能,因为这个系统需要频繁的进行内存和硬盘间的页面交换。

5. 栈在应用层和内核层最大大小

Windows:

应用栈:默认1M,编译指令/stack可改设其他值

内核栈:系统根据CPU架构而定,x86系统上为12KB,x64系统上为24KB,安腾系统上为32KB

6. pae模式下最大范围 存在意义

最大支持64G

32位的虚拟地址(线性地址)则没有变,所以一般的应用软件可以继续使用地址为32位的指令;如果用平面内存模式的话,这些软件的地址空间也被限制为4GiB。操作系统用页表将这4GiB的地址空间映射到大小为64GiB的实体内存,而这个映射对各个进程一般是不一样的。这样一来,即使不能为单单一个程序所用,那些增加了的物理内存仍然可以发挥作用。

CATALOG
  1. 1. 1. 如何判断用户层的地址是否有效?内核层的呢?
  2. 2. 2. tf标志位的作用是什么
  3. 3. 3. 附加进程的那个api有没有研究过
  4. 4. 4. 内存释放的原理
  5. 5. 5. 栈在应用层和内核层最大大小
  6. 6. 6. pae模式下最大范围 存在意义