写在前面

  此系列是本人一个字一个字码出来的,包括示例和实验截图。由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新。 如有好的建议,欢迎反馈。码字不易,如果本篇文章有帮助你的,如有闲钱,可以打赏支持我的创作。如想转载,请把我的转载信息附在文章后面,并声明我的个人信息和本人博客地址即可,但必须事先通知我

你如果是从中间插过来看的,请仔细阅读 羽夏看Win系统内核——简述 ,方便学习本教程。

  看此教程之前,问几个问题,基础知识储备好了吗?保护模式篇学会了吗?练习做完了吗?没有的话就不要继续了。


华丽的分割线


概述

  在64位下,有两种CPU模式,一种是IA-32e模式,是IA-32模式的扩展,另一个是Legacy模式。IA-32e模式是指内核64位,用户64或32位均可,它强制平坦段,不支持任务切换;而Legacy模式指内核32位,用户32位支持非平坦段、任务切换、虚拟8086、实模式等。

  在IA-32e模式下,代码段和数据段仍使用64位描述符,强制平坦(FSGS除外);TSS段描述符扩展到128位,TSS段不用来任务切换,主要保存一堆rsp备用指针;中断门描述符扩展到128位。

  有些寄存器我们需要知道的,如下所示:

MSR 寄存器名 索引数值
IA32_EFER_MSR C0000080H
IA32_FS_BASE C0000100H
IA32_GS_BASE C0000101H
IA32_KERNEL_GS_BASE C0000102H

IA32_EFER_MSR

  该MSR结构如下所示:

  索引0位表示SYSCALL/SYSRET这类指令是否启用,索引8位表示IA-32e模式是否启用,索引10位表示IA-32e模式是否处于处于活动状态,索引11位知识是否启用PAE分页的XD位是否有效。如果到这里有不会的地方,请回去复习。

  下面我们在虚拟机中读取该寄存器的值:

kd> rdmsr C0000080
msr[c0000080] = 00000000`00000d01
kd> .formats 00000000`00000d01
Evaluate expression:
Hex: 00000000`00000d01
Decimal: 3329
Octal: 0000000000000000006401
Binary: 00000000 00000000 00000000 00000000 00000000 00000000 00001101 00000001
Chars: ........
Time: Thu Jan 1 08:55:29 1970
Float: low 4.66492e-042 high 0
Double: 1.64474e-320

  从上面的值我们可以看出寄存器里面的所有的功能处于启用状态。

IA32_FS_BASE

  这个MSR寄存器保存的是fs的基址,由于是64位内核,直接读取的话该值是0,我们可以读取一下:

kd> rdmsr C0000100
msr[c0000100] = 00000000`00000000

IA32_GS_BASE

  这个MSR寄存器保存的是gs的基址,内核指向KPCR结构体(和32位的fs一样的作用),我们可以尝试以下:

kd> rdmsr C0000101
msr[c0000101] = fffff805`5e089000 kd> dt _KPCR fffff805`5e089000
nt!_KPCR
+0x000 NtTib : _NT_TIB
+0x000 GdtBase : 0xfffff805`65690fb0 _KGDTENTRY64
+0x008 TssBase : 0xfffff805`6568f000 _KTSS64
+0x010 UserRsp : 0
+0x018 Self : 0xfffff805`5e089000 _KPCR
+0x020 CurrentPrcb : 0xfffff805`5e089180 _KPRCB
+0x028 LockArray : 0xfffff805`5e089870 _KSPIN_LOCK_QUEUE
+0x030 Used_Self : 0x00000079`22c2a000 Void
+0x038 IdtBase : 0xfffff805`6568e000 _KIDTENTRY64
+0x040 Unused : [2] 0
+0x050 Irql : 0 ''
+0x051 SecondLevelCacheAssociativity : 0xc ''
+0x052 ObsoleteNumber : 0 ''
+0x053 Fill0 : 0 ''
+0x054 Unused0 : [3] 0
+0x060 MajorVersion : 1
+0x062 MinorVersion : 1
+0x064 StallScaleFactor : 0xb58
+0x068 Unused1 : [3] (null)
+0x080 KernelReserved : [15] 0
+0x0bc SecondLevelCacheSize : 0x900000
+0x0c0 HalReserved : [16] 0xad178600
+0x100 Unused2 : 0
+0x108 KdVersionBlock : (null)
+0x110 Unused3 : (null)
+0x118 PcrAlign1 : [24] 0
+0x180 Prcb : _KPRCB

IA32_KERNEL_GS_BASE

  这个MSR的作用是作为GS基址的交换目标。说人话的话就是一个缓存,交换比较方便。我们从3环到0环。从0环到3环,GS内核指向KPCR,3环指向TEB,是如何做到呢?这个就是一个非常重要的寄存器.进0环的时候,把3环的交给它;出0环的时候,把0环的交给它。

  我们来读一下:

kd> rdmsr C0000102
msr[c0000102] = 00000079`22c2a000

  按照上述说法,这个就是TEB了,但由于这个是3环的地址,我们无法读取里面的结构体内容。

  由于现在64位CPU一般安装的系统都是IA-32e模式下的,即内核64位的,故后续所有介绍都会以IA-32e模式为主。

段与段描述符

  在64位系统下,段的保护被进一步弱化。绝大多数的段被强制平坦。何为强制平坦,就是段的首地址和界限的值不再起作用,被保留置0。首地址默认是0,界限默认十六进制全是F。但也有例外,比如GSFS,因为它们指向一些结构体,这些结构体的首地址肯定不是0。

  64位的段和32位的段大体上就上述变化,但有一个段比较特别,那就是代码段,我们来看一下它的结构:

  代码段比32位的多了个位L,它的作用是来指示是32位的还是64位的,如果是0表示兼容模式(x86模式),为1则表示x64模式。

  里面还有一个D位,它的作用是指示默认大小的。如果L == 0,如果该位是0,默认的数据和地址大小为16位;反之为32位。如果L == 1D位必须为0,否则会触发通用保护异常。

  最后要介绍的段就是TSS段,被扩展为128位,用于满足寻址要求,如下结构所示:

  TSS和32位的操作系统一样,只是用来保存一堆寄存器的,并不用于任务切换。

  在64位下分页不再是PAE分页,而是被Intel称之为4级分页(4-LEVEL PAGING)的东西。当CR0.PG == 1 && CR4.PAE == 1 && IA32_EFER.LME == 1时会启用该分页模式。4级分页将48位线性地址转换为52位物理地址。虽然52位对应于4 PBytes,但线性地址仅限于48位,最多可以访问256 TBytes的线性地址空间。也就是说剩余的16位是被保留的。

  4级分页使用分页结构的层次结构来生成线性地址的转换。CR3用于定位第一个分页结构,即PML4表。将CR3与4级分页一起使用取决于是否已通过设置CR4.PCIDE启用进程上下文标识符。

  这个CR3也是结构的,并不直接是物理地址的,如果CR4.PCIDE为0,则它的结构如下:

  如果CR4.PCIDE为1,则它的结构如下:

  其中MMAXPHYADDR的缩写,最大是52,这个值通过CPUID.80000008H:EAX[7:0]进行查询,在我的虚拟机操作系统这个值为十进制的39

kd> r cr4
cr4=0000000000070678 kd> .formats 0000000000070678
Evaluate expression:
Hex: 00000000`00070678
Decimal: 460408
Octal: 0000000000000001603170
Binary: 00000000 00000000 00000000 00000000 00000000 00000111 00000110 01111000
Chars: .......x
Time: Tue Jan 6 15:53:28 1970
Float: low 6.45169e-040 high 0
Double: 2.27472e-318

  首先我们得确认这个CR4.PCIDE是在哪个位上,发现是在索引17位:

  这个位被置1了,说明是第二种形式。有关这部分细节,对于我们不写操作系统的人来说似乎没啥用处,就不追究了。

  下面我们来看看4级分页线性地址的结构,如下是线性地址如何翻译成物理地址的:

  按照之前的说法套路,给一个非正式的名字9-9-9-9-12分页。又了之前的底子,我就不赘述内部的详细结构,直接用一张图进行总结(在Intel白皮书的第2832页):

  细心的你可能会发现protection key这个东西,这是啥呢?这个就是4级分页的一个新的机制,提供更完全的保护,当CR4.PKE == 1时有效,不幸的是,在我的虚拟机的操作系统并不使用该位,所以无效,如果对这块技术感兴趣请仔细阅读Intel白皮书的第2835页。

  当然上述分页还有大页的情形,如下所示:

  下面我们就用x86下的实验方式来学习分页,首先你得有新版的CheatEngine,下面开始:

  如上图所示,我打开了一个CheatEngine和一个Notepad,并且在记事本里面写上hello,world!。然后我们开始老套路搜索内存:

  把,改为%,定位到指定的地址,如下图所示:

  我们先用vtop执行偷懒看看结果:

kd> !process 0 0 notepad.exe
PROCESS ffff8f0fec1be080
SessionId: 1 Cid: 091c Peb: 4e42d08000 ParentCid: 0b70
DirBase: 9bd65002 ObjectTable: ffffdd8291064c40 HandleCount: 356.
Image: notepad.exe kd> !vtop 9bd65000 1DEFB0044B0
Amd64VtoP: Virt 000001defb0044b0, pagedir 000000009bd65000
Amd64VtoP: PML4E 000000009bd65018
Amd64VtoP: PDPE 000000009d581bd8
Amd64VtoP: PDE 00000000a3882ec0
Amd64VtoP: PTE 00000000827ae020
Amd64VtoP: Mapped phys 0000000027fca4b0
Virtual address 1defb0044b0 translates to physical address 27fca4b0.
kd> !db 27fca4b0
#27fca4b0 68 00 65 00 6c 00 6c 00-6f 00 25 00 77 00 6f 00 h.e.l.l.o.%.w.o.
#27fca4c0 72 00 6c 00 64 00 21 00-0d 00 0a 00 00 00 00 00 r.l.d.!.........
#27fca4d0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#27fca4e0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#27fca4f0 00 00 00 00 00 00 00 00-08 00 79 fe de 01 00 00 ..........y.....
#27fca500 00 00 00 00 00 00 00 00-2a f8 73 48 50 71 00 10 ........*.sHPq..
#27fca510 30 48 00 fb de 01 00 00-00 42 00 fb de 01 00 00 0H.......B......
#27fca520 40 48 00 fb de 01 00 00-10 42 00 fb de 01 00 00 @H.......B......

  为什么我在遍历进程的后面加上notepad.exe呢?是因为在Win10上,就算你原装最低配置,也有一大串的进程,带来很大的烦恼。我明确是记事本的进程叫这个名字,我就在后面限定它。

  你或许还有一个疑问,为什么命名目录表基址的值为9bd65002,但为什么vtop的时候后面的2没了呢?那么我们再回过头来看看上图的Cr3结构你就会明白为什么。

  在做这个实验的时候,发现了一个十分有意思的现象,当你恢复虚拟机执行其他操作的时候,再次中断回到Windbg,注意我没有退出记事本,回来发现它的DirBase发生了变化。

  好,我们继续我们的实验:

kd> !process 0 0 notepad.exe
PROCESS ffff8f0fec1be080
SessionId: 1 Cid: 091c Peb: 4e42d08000 ParentCid: 0b70
DirBase: 9c065002 ObjectTable: ffffdd8291064c40 HandleCount: 359.
Image: notepad.exe kd> !vtop 9c065000 1DEFB0044B0
Amd64VtoP: Virt 000001defb0044b0, pagedir 000000009c065000
Amd64VtoP: PML4E 000000009c065018
Amd64VtoP: PDPE 0000000036881bd8
Amd64VtoP: PDE 00000000a3982ec0
Amd64VtoP: PTE 00000000828ae020
Amd64VtoP: Mapped phys 0000000027fca4b0

  好我们手动拆分虚拟地址并用访问物理地址的方式进行该实验。首先拆分以下我们的虚拟地址:

000000011 = 0x3
101111011 = 0x17B
111011000 = 0x1D8
000000100 = 0x4
010010110000 = 0x4B0

  拆解完毕后,我们来开始进行测试:

kd> !dq 9c065000
#9c065000 8a000000`13a79867 00000000`00000000
#9c065010 00000000`00000000 8a000000`36881867
#9c065020 00000000`00000000 00000000`00000000
#9c065030 00000000`00000000 00000000`00000000
#9c065040 00000000`00000000 00000000`00000000
#9c065050 00000000`00000000 00000000`00000000
#9c065060 00000000`00000000 00000000`00000000
#9c065070 00000000`00000000 00000000`00000000
kd> !dq 36881000+17b*8
#36881bd8 0a000000`a3982867 00000000`00000000
#36881be8 00000000`00000000 00000000`00000000
#36881bf8 00000000`00000000 00000000`00000000
#36881c08 00000000`00000000 00000000`00000000
#36881c18 00000000`00000000 00000000`00000000
#36881c28 00000000`00000000 00000000`00000000
#36881c38 00000000`00000000 00000000`00000000
#36881c48 00000000`00000000 00000000`00000000
kd> !dq A3982000+1D8*8
#a3982ec0 0a000000`828ae867 0a000000`27aa6867
#a3982ed0 0a000000`1222f867 00000000`00000000
#a3982ee0 00000000`00000000 00000000`00000000
#a3982ef0 00000000`00000000 00000000`00000000
#a3982f00 00000000`00000000 00000000`00000000
#a3982f10 00000000`00000000 00000000`00000000
#a3982f20 0a000000`99493867 0a000000`91a7b867
#a3982f30 00000000`00000000 00000000`00000000
kd> !dq 828ae000+4*8
#828ae020 81000000`27fca867 81000000`10be0867
#828ae030 81000000`892f5867 81000000`980f7847
#828ae040 81000000`2aef6867 81000000`93dfe867
#828ae050 81000000`12f06867 81000000`5210f867
#828ae060 81000000`98211867 81000000`a4310847
#828ae070 81000000`b011f867 81000000`2b920867
#828ae080 81000000`95d23847 81000000`6da22847
#828ae090 81000000`0e728847 81000000`88827867
kd> !db 27fca000+4b0
#27fca4b0 68 00 65 00 6c 00 6c 00-6f 00 25 00 77 00 6f 00 h.e.l.l.o.%.w.o.
#27fca4c0 72 00 6c 00 64 00 21 00-0d 00 0a 00 00 00 00 00 r.l.d.!.........
#27fca4d0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#27fca4e0 00 00 00 00 00 00 00 00-00 00 00 00 00 00 00 00 ................
#27fca4f0 00 00 00 00 00 00 00 00-08 00 79 fe de 01 00 00 ..........y.....
#27fca500 00 00 00 00 00 00 00 00-2a f8 73 48 50 71 00 10 ........*.sHPq..
#27fca510 30 48 00 fb de 01 00 00-00 42 00 fb de 01 00 00 0H.......B......
#27fca520 40 48 00 fb de 01 00 00-10 42 00 fb de 01 00 00 @H.......B......

  当然上述我们有不严谨的地方,我们并没有判断是否是大页的情况,但对于一般变量来说,大页的情况还是比较少的。

  学过我之前写的教程都知道,操作系统可是不能这么直接访问物理内存的,它依旧通过线性地址进行访问内容,必然在某个地址放着这几张表。按照之前的学习路线,我们必然得需要逆向MmIsAddressValid,下面我们开始继续:

; BOOLEAN __stdcall MmIsAddressValid(PVOID VirtualAddress)
public MmIsAddressValid
MmIsAddressValid proc near ; CODE XREF: KiMarkBugCheckRegions+B3↑p
; KiMarkBugCheckRegions+1E8↑p ...
sub rsp, 28h
call MmIsAddressValidEx
add rsp, 28h
retn
MmIsAddressValid endp

  可以看出,这个啥也没做,只是调用了MmIsAddressValidEx这个函数,我们点进去看看:

; BOOLEAN __fastcall MmIsAddressValidEx(PVOID VirtualAddress)
MmIsAddressValidEx proc near ; CODE XREF: MiIncreaseUsedPtesCount+64↑p
; MiCommitExistingVad+65D↓p ... PXE = qword ptr -28h
PPE = qword ptr -20h
PDE = qword ptr -18h
PTE = qword ptr -10h
VirtualAddress = qword ptr 8 ; FUNCTION CHUNK AT .text:00000001402234DC SIZE 00000056 BYTES mov [rsp+VirtualAddress], rbx
push rdi
sub rsp, 20h
mov rax, rcx ; VirtualAddress
mov r10, rcx
sar rax, 47 ; 实现的虚拟地址是48位的,如果是内核地址,则最后是-1,用户地址是0
inc rax ; 自增1
cmp rax, 1 ; 如果大于1,有问题
ja InvalidAddr
mov rdx, rcx ; VirtualAddress
mov rdi, 0FFFFF68000000000h ; PML4 表基址
shr rdx, 9
mov rcx, 7FFFFFFFF8h
and rdx, rcx
mov rax, rdi
add rdx, rax
mov [rsp+28h+PXE], rdx ; 获取 PXE
shr rdx, 9
and rdx, rcx
mov rax, rdi
add rdx, rax
mov [rsp+28h+PPE], rdx ; 获取 PPE
shr rdx, 9
and rdx, rcx
mov rax, rdi
add rdx, rax
mov [rsp+28h+PDE], rdx ; 获取 PDE
shr rdx, 9
and rdx, rcx
mov rax, rdi
add rdx, rax
mov r11, 0FFFFF6FB7DBED000h
mov [rsp+28h+PTE], rdx ; 获取 PTE
mov edx, 4
mov rbx, 0FFFFF6FB7DBED7F8h loc_1400AB248: ; CODE XREF: MmIsAddressValidEx+BF↓j
mov r9, [rsp+rdx*8-8] ; 初始值 rdx = 4
dec rdx
mov rcx, [r9]
mov rax, r11
cmp r9, rax
jb short loc_1400AB263
mov rax, rbx
cmp r9, rax
jbe short loc_1400AB27E loc_1400AB263: ; CODE XREF: MmIsAddressValidEx+A9↑j
; MmIsAddressValidEx+D9↓j ...
test cl, 1
jz short InvalidAddr
test cl, cl
js short loc_1400AB2BD
test rdx, rdx
jnz short loc_1400AB248 loc_1400AB271: ; CODE XREF: MmIsAddressValidEx+113↓j
; MmIsAddressValidEx+122↓j
mov al, 1 loc_1400AB273: ; CODE XREF: MmIsAddressValidEx+126↓j
mov rbx, [rsp+28h+VirtualAddress]
add rsp, 20h
pop rdi
retn
; --------------------------------------------------------------------------- loc_1400AB27E: ; CODE XREF: MmIsAddressValidEx+B1↑j
mov eax, cs:MiFlags
test eax, 0C00000h
jz short loc_1400AB263
mov rax, gs:_KPCR.Prcb.CurrentThread
mov r8, [rax+_KTHREAD.___u25.ApcState.Process]
cmp [r8+_EPROCESS.Pcb.AddressPolicy], 1
jz short loc_1400AB263
test cl, 1
jz short InvalidAddr
test cl, 20h
jz loc_1402234DC
test cl, 42h
jnz short loc_1400AB263
jmp loc_1402234DC
; --------------------------------------------------------------------------- loc_1400AB2BD: ; CODE XREF: MmIsAddressValidEx+BA↑j
mov rax, rdi
cmp r10, rax
jb short loc_1400AB271
mov rax, 0FFFFF6FFFFFFFFFFh
cmp r10, rax
ja short loc_1400AB271 InvalidAddr: ; CODE XREF: MmIsAddressValidEx+1B↑j
; MmIsAddressValidEx+B6↑j ...
xor al, al
jmp short loc_1400AB273
MmIsAddressValidEx endp

  但不幸的是,我根本无法弄懂这块代码的所有细节,完全弄懂的部分均用注释标注,主要是如下代码看起来十分奇怪:

loc_1400AB248:                          ; CODE XREF: MmIsAddressValidEx+BF↓j
mov r9, [rsp+rdx*8-8] ; 初始值 rdx = 4
dec rdx
mov rcx, [r9]
mov rax, r11
cmp r9, rax
jb short loc_1400AB263
mov rax, rbx
cmp r9, rax
jbe short loc_1400AB27E

  根据堆栈图,它是在该函数直接提栈的局部变量区,而我未发现任何初始化该区域数据的汇编代码。该代码里面还有ShadowMapping这个东西,从名字上来看是影子映射相关的,但我同样弄不出来相关细节。网络上有关x64的资料少之又少,我无法解决,所以有关页的相关内容,只能在此告一段落。

  门在x86上是十分重要的东西,尤其是中断门在Win系统扮演了十分重要的角色。鉴于之前在基础篇保护模式介绍的内容基础,这里就只讲调用门和中断门。

  注意:我不太建议做和学WinXP一样的内核实验,因为PG有可能会被触发导致蓝屏,搞懂原理即可。

调用门

  在x64下,调用门的结构如下:

  可以看出,调用门被扩展为128位,基本内容并没有发生任何变化。我们看看GDT表里面的内容:

kd> dq gdtr
fffff803`86c90fb0 00000000`00000000 00000000`00000000
fffff803`86c90fc0 00209b00`00000000 00409300`00000000
fffff803`86c90fd0 00cffb00`0000ffff 00cff300`0000ffff
fffff803`86c90fe0 0020fb00`00000000 00000000`00000000
fffff803`86c90ff0 86008bc8`f0000067 00000000`fffff803
fffff803`86c91000 0040f300`00003c00 00000000`00000000
fffff803`86c91010 00000000`00000000 00000000`00000000
fffff803`86c91020 00000000`00000000 00000000`00000000

  里面的内容少了不少,相比于XP系统来说。有关调用门细节就介绍这些。

中断门

  中断门也被同样的扩展为128位,其余的功能并没有发生任何变化。

  然后我们再看一下IDT表:

kd> dq idtr
fffff803`86c8e000 81158e00`00101100 00000000`fffff803
fffff803`86c8e010 81158e04`00101180 00000000`fffff803
fffff803`86c8e020 81158e03`00101200 00000000`fffff803
fffff803`86c8e030 8115ee00`00101280 00000000`fffff803
fffff803`86c8e040 8115ee00`00101300 00000000`fffff803
fffff803`86c8e050 81158e00`00101380 00000000`fffff803
fffff803`86c8e060 81158e00`00101400 00000000`fffff803
fffff803`86c8e070 81158e00`00101480 00000000`fffff803

  我们可以用!idt获取每一个索引对应的函数,如下为部分展示,后面有stack的说明该函数有自己独占的栈。

kd> !idt

Dumping IDT: fffff80386c8e000

00: fffff80381151100 nt!KiDivideErrorFaultShadow
01: ffff80381151180 nt!KiDebugTrapOrFaultShadow Stack = 0xFFFFF80386C929D0
02: fffff80381151200 nt!KiNmiInterruptShadow Stack = 0xFFFFF80386C927D0
03: fffff80381151280 nt!KiBreakpointTrapShadow
04: fffff80381151300 nt!KiOverflowTrapShadow
05: fffff80381151380 nt!KiBoundFaultShadow
06: fffff80381151400 nt!KiInvalidOpcodeFaultShadow
07: fffff80381151480 nt!KiNpxNotAvailableFaultShadow
08: fffff80381151500 nt!KiDoubleFaultAbortShadow Stack = 0xFFFFF80386C923D0
……

  有关中断门相关内容,暂时就这么多。

SMEP 与 SMAP

  x64CPU又增加了一些标识来增加安全性:SMEPSMAP。这两个标识在CR4中,我们再拿来看看:

  SMEP英文全称是supervisor-mode execution prevention,看意思就是限制执行代码的。SMAP英文全称为supervisor-mode access prevention,看意思就是限制访问内存的。这些限制是限制谁的,限制内核的。在WinXP下,只要是内核就具有至高无上的权限,我想读哪块内存就读,想执行啥代码就执行。

  说这两个标志位的功能倒是挺容易的,为什么要说这个东西呢?之前的实验应用层程序通过调用门提权并执行代码就会出现问题,我们需要对这两个标志位进行清0操作,否则就会出错。

  有一个特殊的汇编指令CLAC,它的作用是清除EFLAGS寄存器中的AC标志位。这将禁用用户模式数据访问的任何对齐检查。如果在CR4寄存器中设置了SMAP位,这个位就不会起作用;STAC可以恢复使用SMAP。有关这两个位介绍就这么多。

下一篇

  x64 番外篇—— WOW64

x64 番外篇——保护模式相关的更多相关文章

  1. x64 番外篇——知识铺垫

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  2. 羽夏看Win系统内核—— x64 番外篇

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  3. 《手把手教你》系列技巧篇(三十一)-java+ selenium自动化测试- Actions的相关操作-番外篇(详解教程)

    1.简介 上一篇中,宏哥说的宏哥在最后提到网站的反爬虫机制,那么宏哥在自己本地做一个网页,没有那个反爬虫的机制,谷歌浏览器是不是就可以验证成功了,宏哥就想验证一下自己想法,于是写了这一篇文章,另外也是 ...

  4. 给深度学习入门者的Python快速教程 - 番外篇之Python-OpenCV

    这次博客园的排版彻底残了..高清版请移步: https://zhuanlan.zhihu.com/p/24425116 本篇是前面两篇教程: 给深度学习入门者的Python快速教程 - 基础篇 给深度 ...

  5. 同步IO与一部IO、IO多路复用(番外篇)select、poll、epoll三者的区别;blocking和non-blocking的区别 synchronous IO和asynchronous IO的区别

    Python之路,Day9 , IO多路复用(番外篇)   同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的.所以先限定一下本文的上下文. ...

  6. 羽夏看Win系统内核—— VT 入门番外篇

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  7. [uboot] (番外篇)uboot 驱动模型(转)重要

    [uboot] uboot流程系列:[project X] tiny210(s5pv210)上电启动流程(BL0-BL2)[project X] tiny210(s5pv210)从存储设备加载代码到D ...

  8. VT 入门番外篇——初识 VT

    写在前面   此系列是本人一个字一个字码出来的,包括示例和实验截图.由于系统内核的复杂性,故可能有错误或者不全面的地方,如有错误,欢迎批评指正,本教程将会长期更新. 如有好的建议,欢迎反馈.码字不易, ...

  9. 【番外篇】ASP.NET MVC快速入门之免费jQuery控件库(MVC5+EF6)

    目录 [第一篇]ASP.NET MVC快速入门之数据库操作(MVC5+EF6) [第二篇]ASP.NET MVC快速入门之数据注解(MVC5+EF6) [第三篇]ASP.NET MVC快速入门之安全策 ...

随机推荐

  1. 为什么 Vue3.js / Element+ 组件属性前面有的需要添加冒号,有的不需要?

    背景 使用 Element+ Layout 布局: <el-row> <el-col :span="12"><div class="grid ...

  2. 搞懂MySQL(各种)索引类型及其区别

    索引的概念介绍: 1.聚集索引 聚集索引:指索引项的排序方式和表中数据记录排序方式一致的索引  也就是说聚集索引的顺序就是数据的物理存储顺序.它会根据聚集索引键的顺序来存储表中的数据,即对表的数据按索 ...

  3. Bugku CTF_web3

    1.打开网页,得到信息如下 2.在hackbar里构造url如下 3.得到flag 4.本题的考点在于读的懂PHP代码,并且学会构造get参数.

  4. linux作业--第一周

    1.按系列罗列Linux的发行版,并描述不同发行版之间的联系与区别. 目前三大主流发行版分别为Debian.Redhat.SUSE. redhat: RHEL: Red Hat公司发布的面向企业用户的 ...

  5. PHP魔术方法11种

    1.构造函数:__construct(): 构造函数是类中的一个特殊函数,当我们使用new关键字实例化对象时,相当于调用了类的构造函数. function __construct($name){ $t ...

  6. 番茄钟的实现(基于Xilinx EGO1学习板)

    番茄钟设计 一.总体设计 1.番茄工作法简介 番茄工作法由意大利的奇列洛创造.其内容就是:工作25分钟休息5分钟,循环四次后休息15分钟. 本项目就是基于Xilinx Ego1开发板实现一个计时器,该 ...

  7. 一比一还原axios源码(四)—— Axios类

    axios源码的分析,到目前为止,算上第0章已经四章了,但是实际上,还都没有进入axios真正的主线,我们来简单回顾下.最开始我们构建了get请求,写了重要的buildURL方法,然后我们处理请求体请 ...

  8. 用注册表清除Office Word文档杀手病毒

    不久前,笔者打开word文件时遇到了一件离奇的怪事,常用的Word文件怎么也打不开,总是出现提示框:"版本冲突:无法打开高版本的word文档".再仔细查看,文件夹里竟然有两个名字一 ...

  9. 反射getattr

    @property   将类中的方法伪装成属性 与@property相关的俩个   @方法.setter   修改操作    @方法.deleter   删除一个property属性的时候会执行被de ...

  10. 5月2日 python学习总结 IO模型

    IO模型 1.阻塞IO 2.非阻塞IO 3.多路复用IO 4.异步IO 一.阻塞IO blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了. 实际上,除非 ...