Aggregator
技艺丛谈推出有声版
第一次勘误及为啥暂时不建立读者群
漫谈漏洞扫描器的设计与开发
Segment Heap的简单分析和Windbg Extension
Author: k0shl of 360 Vulcan Team
简述微软在Windows 10启用了一种新的堆管理机制Low Fragmentation Heap(LFH),在常规的环三应用进程中,Windows使用Nt Heap,而在特定进程,例如lsass.exe,svchost.exe等系统进程中,Windows采用Segment Heap,关于Nt Heap,可以参考Angel boy在WCTF赛后的分享Windows 10 Nt Heap Exploitation,而Segment Heap可以参考MarkYason在16年Blackhat上的议题Windows 10 Segment Heap Internals。
在Yason的议题中对于Segment Heap的分析已经足够详细,NT Heap和Segment Heap的结构差异较大,我在这篇文章中只对Segment Heap在Windows ntdll中的代码逻辑实现进行简单分析,以及我针对Segment Heap编写的windbg extension简单介绍。
Segment Heap的创建Windows在系统进程中使用Segment Heap,部分应用也使用了Segment heap,比如Edge,如果想调试自己的程序,可以在注册表中添加相应键值开启Segment Heap。
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\(executable) FrontEndHeapDebugOptions = (DWORD)0x08通过windbg !heap命令可以看到当前进程的堆布局。
2: kd> !process 1f0 0 Searching for Process with Cid == 1f0 PROCESS ffffcf026f1cc0c0 SessionId: 0 Cid: 01f0 Peb: 1803b03000 ParentCid: 01e8 DirBase: 01850002 ObjectTable: ffffbd0dfbaea080 HandleCount: 574. Image: csrss.exe 2: kd> .process /i /p ffffcf026f1cc0c0 You need to continue execution (press 'g' <enter>) for the context to be switched. When the debugger breaks in again, you will be in the new process context. 2: kd> g 0: kd> .reload /user Loading User Symbols .................... 0: kd> !heap Heap Address NT/Segment Heap 14bff720000 Segment Heap 7df42cce0000 NT Heap关于Segment Heap和Nt Heap通过其头部结构的Signature成员变量区分,Signature保存在Heap Header+0x10位置,当Signature为0xDDEEDDEE时,该堆为Segment Heap,而当Signature为0xFFEEFFEE时,该堆为Nt Heap。
0: kd> dq 14bff720000 l3//Segment Heap 0000014b`ff720000 00000000`01000000 00000000`00000000 0000014b`ff720010 00000000`ddeeddee 0: kd> dq 7df42cce0000 l3//Nt Heap 00007df4`2cce0000 00000000`00000000 01009ba1`00f60fd8 00007df4`2cce0010 00000001`ffeeffee当进程初始化时,进程会调用RtlInitializeHeapManager函数创建堆管理结构,内层函数调用RtlpHpOptIntoSegmentHeap决定是否创建SegmentHeap,在RtlpHpOptIntoSegmentHeap函数中会检查进程明程等内容,当属于指定系统进程或者Package时,会设置对应的Feature,最后创建Segement Heap设置_SEGMENT_HEAP->Signature值为0xDDEEDDEE。
__int64 __fastcall RtlpHpOptIntoSegmentHeap(unsigned __int16 *a1) { v1 = a1; v16 = L"svchost.exe"; //----->指定的系统进程 v2 = 0; v17 = L"runtimebroker.exe";//----->指定的系统进程 v18 = L"csrss.exe";//----->指定的系统进程 v19 = L"smss.exe";//----->指定的系统进程 v20 = L"services.exe";//----->指定的系统进程 v21 = L"lsass.exe";//----->指定的系统进程 ... } //调用路径 LdrpInitializeProcess |__RtlInitializeHeapManager |__RtlpHpOptIntoSegmentHeap //最终在RtlpHpHeapCreate函数中将+0x10 Signature值置为0xDDEEDDEE __int64 __fastcall RtlpHpHeapCreate(unsigned __int32 a1, unsigned __int64 a2, __int64 a3, __m128i *a4) { v9 = (__m128i *)RtlpHpHeapAllocate(v6, v7, (__m128i *)&v36); v9[1].m128i_i32[0] = 0xDDEEDDEE;//mov dword ptr [rax+10h], 0DDEEDDEEh }因此我在编写segment heap的windbg extension时,通过查看的Bucket Block地址找到Segment Heap Header之后通过查看对应Signature是否为0xDDEEDDEE用于确认查找的地址是否是一个有效的Bucket地址。
Segment Heap LFHAllocate接下来对Segment Heap的分配和释放进行简单分析,首先我们需要了解_SEGMENT_HEAP中的一个关键结构_HEAP_LFH_CONTEXT,其成员在偏移0x340位置,在_HEAP_LFH_CONTEXT结构偏移0x80位置存放着一个Bucket Table,其结构关系如下。
0: kd> dt _SEGMENT_HEAP LfhContext ntdll!_SEGMENT_HEAP +0x340 LfhContext : _HEAP_LFH_CONTEXT 0: kd> dt _HEAP_LFH_CONTEXT Buckets ntdll!_HEAP_LFH_CONTEXT +0x080 Buckets : [129] Ptr64 _HEAP_LFH_BUCKET在BucketTable中存放不同Size的Bucket Manager pointer,其实LFH并非在最开始就处于待分配状态,在堆最开始分配的时候是通过正常的Variable Size分配,关于vs heap的分配可以参考Yason的slide,当进程申请堆时会调用ntdll!RtlAllocateHeap,在分配时会检查Signature是否是SegmentHeap。
__int64 __fastcall RtlAllocateHeap(_SEGMENT_HEAP *a1, unsigned int a2, __int64 a3) { if ( !a1 ) RtlpLogHeapFailure(19i64, 0i64); if ( a1->Signature == 0xDDEEDDEE ) return RtlpHpAllocWithExceptionProtection((__int64)a1, a3, a2); if ( RtlpHpHeapFeatures & 2 ) return RtlpHpTagAllocateHeap((__int64)a1, a3, a2); return RtlpAllocateHeapInternal(a1, a3, a2, 0i64); }若Signature值为0xDDEEDDEE时,会调用RtlpHpAllocWithExceptionProtection创建segment heap block,在最开始的时候,会检查Bucket Table中lfh是否已经激活,也就是第一比特是否为1,当第一比特为1时,当前Bucket处于未激活lfh的情况,会创建vs heap,我们暂不讨论vs heap的申请。
3: kd> dq 116abf90000+340+80//Bucket Table 00000116`abf903c0 00000000`00000001 00000000`00000001 00000116`abf903d0 00000000`026e0001 00000116`abf90900//已经激活LFH索引的指针 00000116`abf903e0 00000000`01ee0001 00000000`030f0001//未激活的索引 00000116`abf903f0 00000000`04100001 00000000`00820001 00000116`abf90400 00000000`01280001 00000000`00e30001 00000116`abf90410 00000000`00210001 00000000`00410001Segment Heap的分配实现在RtlpAllocateHeapInternal函数中,由于代码逻辑较长但并不复杂,我这里只标明与我本文相关的逻辑部分,具体逻辑需要感兴趣的读者自行逆向。
__int64 __fastcall RtlpAllocateHeapInternal(_SEGMENT_HEAP *HeapBase, unsigned __int64 InSize, __int64 a3, __int64 a4) { …… if ( InSize <= (unsigned int)WORD2(HeapBase->LfhContext.Buckets[0x13]) - 0x10 )//--->(0) { if(!(BucketTable[SizeIndex] & 1){//--->(1) RtlpHpLfhSlotAllocate() } else if(Allocate enough blocks){ //--->(2) RtlpHpLfhBucketActivate() } else{ do something//--->(3) } } if ( InSize > 0x20000 ) { RtlpHpLargeAlloc()//--->(4) } else{ RtlpHpVsContextAllocateInternal()//--->(5) } …… }接下来我会就代码中的逻辑进行简要说明。
(0) 分配时首先判断申请堆的大小是否小于等于0x4000-0x10,也就是0x3ff0,若大于0x4000且小于等于0x20000,则直接使用Variable Size Heap Allocate,如果大于0x20000则使用Large Heap Allocate。 (1) 若申请堆大小小于等于0x3ff0,则会在Bucket Table中找到分配大小对应Size的索引,之后判断其是否已经激活LFH(第一比特是否为1),当LFH已经激活时,if语句判断返回TRUE,直接调用RtlpHpLfhSlotAllocate申请Block。 (2) 否则检查当前申请的堆大小的已申请数量是否已经满足激活LFH所需的数量,若满足,则调用RtlpHpLfhBucketActivate函数激活Bucket,此时Bucket Table对应位置会被Bucket Header赋值。 (3) 如果分配数量还不满足则进行一些Flag的赋值后跳出if语句。 (4) 当申请堆大小大于0x20000时,则调用RtlpHpLargeAlloc申请Large Heap。 (5) 当满足(0)条件或者在(3)中没有达到激活LFH条件时,调用RtlpHpVsContextAllocateInternal申请VS Heap,也就是说(5)不一定只满足大于0x4000小于等于0x20000的情况,小于等于0x4000时也有可能会走VS Heap,这取决于已分配Block的数量。这里我们不讨论VS Heap和Large Heap,只讨论LFH Heap的情况。当LFH被激活时,RtlpHpLfhBucketActivate会创建一个Bucket Manager,并且将这个Manager指针放到Bucket Table对应Size Index的位置,我们要研究申请堆的Block的分配需要从这个Bucket Manager入手。
Block的申请在RtlpHpLfhSlotAllocate()函数中,关于这个函数代码逻辑比较复杂,我将从Bucket Manager入手结合关键的代码逻辑和大家分享LFH Block的分配过程。由于调试过程比较复杂,这里我不再贴出调试步骤记录占用篇幅,感兴趣的读者可以在RtlpHpLfhSlotAllocate单步跟踪加以印证。
Bucket Manager是一个名为_HEAP_LFH_BUCKET的结构,其成员变量包含一个重要结构_HEAP_LFH_AFFINITY_SLOT,该结构中包含的重要成员变量结构为_HEAP_LFH_SUBSEGMENT_OWNER,关于结构关系如下(重要结构我用*表示)。
1: kd> dt _HEAP_LFH_BUCKET 116`abf90b00 ntdll!_HEAP_LFH_BUCKET +0x000 State : _HEAP_LFH_SUBSEGMENT_OWNER +0x038 TotalBlockCount : 0x5b7 +0x040 TotalSubsegmentCount : 0x10 +0x048 ReciprocalBlockSize : 0x3333334 +0x04c Shift : 0x20 ' ' +0x04d ContentionCount : 0 '' +0x050 AffinityMappingLock : 0 +0x058 ProcAffinityMapping : 0x00000116`abf90b80 "" * +0x060 AffinitySlots : 0x00000116`abf90b88 -> 0x00000116`abf90bc0 _HEAP_LFH_AFFINITY_SLOT 1: kd> dt _HEAP_LFH_AFFINITY_SLOT 116`abf90bc0 ntdll!_HEAP_LFH_AFFINITY_SLOT * +0x000 State : _HEAP_LFH_SUBSEGMENT_OWNER +0x038 ActiveSubsegment : _HEAP_LFH_FAST_REF 1: kd> dt _HEAP_LFH_SUBSEGMENT_OWNER 116`abf90bc0 ntdll!_HEAP_LFH_SUBSEGMENT_OWNER +0x000 IsBucket : 0y0 +0x000 Spare0 : 0y0000000 (0) * +0x001 BucketIndex : 0x5 '' +0x002 SlotCount : 0 '' +0x002 SlotIndex : 0 '' +0x003 Spare1 : 0 '' * +0x008 AvailableSubsegmentCount : 1 +0x010 Lock : 0 * +0x018 AvailableSubsegmentList : _LIST_ENTRY [ 0x00000116`ac5d4000 - 0x00000116`ac5d4000 ] * +0x028 FullSubsegmentList : _LIST_ENTRY [ 0x00000116`ac0f7000 - 0x00000116`ac5d0000 ]LHF的Bucket是通过双向链表的方法管理,AvailableSubsegmentList是存在Free状态的Block的Bucket链表,FullSubsegmentList是已经满了的Bucket的链表,这两个链表存放的就是各个Bucket的Bucket Header,当LFH分配Block时,会检查Bucket Manager中AvailableSubsegementCount的值,若其值小于等于0,则继续判断AvailableSubsegementList,在AvailableSubsegmentList中没有可用的Bucket header时,其值指向自己。
1: kd> dq 116`abf90bc0//_HEAP_LFH_SUBSEGMENT_OWNER结构 00000116`abf90bc0 00000000`00000500 00000000`00000001//有可用的Bucket 00000116`abf90bd0 00000000`00000000 00000116`ac5d4000//AvailableSubsegmentList 00000116`abf90be0 00000116`ac5d4000 00000116`ac0f7000//FullSubsegmentList 00000116`abf90bf0 00000116`ac5d0000 00000000`00000000 3: kd> dq 116`abf908c0//_HEAP_LFH_SUBSEGMENT_OWNER结构 00000116`abf908c0 00000000`00000c00 00000000`00000000//可用的Count为0 00000116`abf908d0 00000000`00000000 00000116`abf908d8//AvailableSubsegmentList指向本身 00000116`abf908e0 00000116`abf908d8 00000116`abf908e8//FullSubsegmentList指向本身 00000116`abf908f0 00000116`abf908e8 00000000`00000000 v10 = &a3->State.AvailableSubsegmentCount; if ( a3->State.AvailableSubsegmentCount <= 0 )//当Count小于0 { …… v121 = (__int64 **)&a2->State.AvailableSubsegmentList; if ( *v121 == (__int64 *)v121//链表指针指向本身 || ((RtlAcquireSRWLockExclusive(&a2->State.Lock), *v121 == (__int64 *)v121) ? (_RSI = 0i64) : (_RSI = RtlpHpLfhOwnerMoveSubsegment((__int64)a2, *v121, 2)), RtlReleaseSRWLockExclusive(&a2->State.Lock), !_RSI) ) { _RSI = (__int64 *)RtlpHpLfhSubsegmentCreate(a1, a2, a5); if ( !_RSI ) goto LABEL_52; } …… }如果满足上述条件,则当前没有可用的Bucket,LFH调用RtlpHpLfhSubsegmentCreate创建一个新的Bucket,在RtlpHpLfhSubsegmentCreate函数中,我们可以看到实际上在_HEAP_LFH_SUBSEGMENT_OWNER中的BucketIndex成员变量用于在ntdll的一个全局变量RtlpBucketBlockSizes中获取这个Bucket Manager所管理的Bucket中Block的Size,也就是我们申请堆的Size。
v3 = a2->State.BucketIndex; v4 = RtlpHpLfhPerfFlags; v10 = a3; v8 = (unsigned __int16)RtlpBucketBlockSizes[v3]; v33 = (unsigned __int16)RtlpBucketBlockSizes[v3]; 1: kd> dq ntdll!RtlpBucketBlockSizes 00007ffc`5cbe1270 00300020`00100000 00700060`00500040//Block Size 00007ffc`5cbe1280 00b000a0`00900080 00f000e0`00d000c0 00007ffc`5cbe1290 01300120`01100100 01700160`01500140 00007ffc`5cbe12a0 01b001a0`01900180 01f001e0`01d001c0 00007ffc`5cbe12b0 02300220`02100200 02700260`02500240 00007ffc`5cbe12c0 02b002a0`02900280 02f002e0`02d002c0在RtlpHpLfhSubsegmentCreate函数最终会分配出一个Bucket,将Bucket Header赋值给AvailableSubsegementList,同时这个函数中会按照RtlpBucketBlockSizes对应BlockIndex的地址,返回Size,最终切割好Block。
一旦存在可用的Bucket,则来到分配的最后一步,实际上理解分配最后一步非常简单,在Bucket创建时,所有可用的堆已经被切割好,LFH会随机取一块Block,并且将这个Block的地址返回,这个地址就是我们申请堆的地址,这一步全部依靠Bucket Header完成。
在Segment Heap LFH中,堆不再具有头部,取而代之的是通过Bucket Header来管理Bucket中的所有Block。Bucket Header结构体叫做_HEAP_LFH_SUBSEGMENT
1: kd> dt _HEAP_LFH_SUBSEGMENT 116`ac0f7000 FreeCount, BlockCount, BlockBitmap ntdll!_HEAP_LFH_SUBSEGMENT +0x020 FreeCount : 0 +0x022 BlockCount : 0x32 +0x030 BlockBitmap : [1] 0x55555555`55555555 1: kd> dq 116`ac0f7000 00000116`ac0f7000 00000116`ac1f9000 00000116`abf90be8//List_Entry 00000116`ac0f7010 00000116`abf90bc0 00000000`00000000 00000116`ac0f7020 0001002c`00320000 0040010c`60b53c07 00000116`ac0f7030 55555555`55555555 fffffff5`55555555 00000116`ac0f7040 00000000`00000001 00000000`00000000在Bucket Header中,Bitmap中存放的是这个Bucket中所有Block的状态,关于这个状态在Yason的slide中有相关介绍,这里我就不赘述了,值得一提的是,当你申请堆的大小恰好和RtlpBucketBlockSizes中存放的大小相等时,Bitmap的01代表已分配状态,00代表空闲状态,而当你申请的大小与RtlpBucketBlockSizes中存放大小不等时,则Bucket依然会按照RtlpBucketBlockSizes中存放的大小切割,但11代表已分配状态,10代表空闲状态,比方说我申请0xc10大小,但实际Block大小会按照0xC80切割,同时bitmap中高位会置1,这一切都取决于Bucket的索引在RtlpBucketBlockSizes数组中对应位置存放的Size。
分配时,会在bitmap中找到随机一个空闲状态的Block并返回,同时会将bitmap中对应位置置成分配状态(低位置1),并且FreeCount减1,当FreeCount减到0时,证明Bucket全部分配满,LFH会将该Bucket从AvailableSubsegmentList链表中unlink,并插入FullSubsegmentList中。
同理释放时,会将bitmap对应的位置置成空闲状态,FreeCount加1,若当前Bucket在FullSubsegmentList中,则会从该链表unlink,并加入到AvailableSubsegmentList中。
最后,关于创建Bucket的时候到底分配多少Block,这个并不是固定的,而是根据_HEAP_LFH_BUCKET中的TotalSubsegmentCount以及申请堆的大小决定的,其函数实现在RtlpGetSubSegmentBlockCount中。
__int64 __fastcall RtlpGetSubSegmentBlockCount(unsigned int HeapSize, unsigned int TotalSubSegmentCount, char AlwaysZero, int IsFirstBucket) { v5 = AlwaysZero - 1; if ( HeapSize >= 0x100 ) v5 = AlwaysZero; v6 = v5 - 1; if ( !IsFirstBucket )//如果是这个Size的第一个Bucket v6 = v5; if ( TotalSubSegmentCount < 1 << (3 - v6) ) TotalSubSegmentCount = 1 << (3 - v6); if ( TotalSubSegmentCount < 4 ) TotalSubSegmentCount = 4; if ( TotalSubSegmentCount > 0x400 ) TotalSubSegmentCount = 0x400; return TotalSubSegmentCount; }随着该Size分配的堆数量的增加,最终一个Bucket中创建的Blocks也会增加。
在我的Windbg Extension中,由于Bucket Header都是按页对齐,因此通过查询的堆地址直接与0xff..f000做与运算后就可以找到页头部,假设该头部是Bucket Header时,其_HEAP_LFH_SUBSEGMENT的_HEAP_LFH_SUBSEGMENT_OWNER成员变量指向Bucket Manager,之后可以找到整个Segment Heap的头部,通过Signature就可以判断Bucket Header是否是有效的Bucket Header,如果不是,则将当前页头部-0x1000,继续按页查找,因为当前分配的Block可能不止一页。
之后根据Bucket Header的Bucket Index可以在全局变量RtlpBucketBlockSizes数组中找到当前Bucket的Size,通过bitmap可以打印最终的Bucket布局。
1: kd> !heapinfo 116`ac0f7060 Try to find Bucket Manager. Bucket Header: 0x00000116ac0f7000 Bucket Flink: 0x00000116ac1f9000 Bucket Blink: 0x00000116abf90be8 Bucket Manager: 0x00000116abf90bc0 ---------------------Bucket Info--------------------- Free Heap Count: 0 Total Heap Count: 50 Block Size: 0x50 --Index-- | -----Heap Address----- | --Size-- | --State-- 0000 | *0x00000116ac0f7050 | 0x0050 | Busy --------- | ---------------------- | -------- | --------- 0001 | 0x00000116ac0f70a0 | 0x0050 | Busy --------- | ---------------------- | -------- | --------- 0002 | 0x00000116ac0f70f0 | 0x0050 | Busy --------- | ---------------------- | -------- | --------- 0003 | 0x00000116ac0f7140 | 0x0050 | Busy --------- | ---------------------- | -------- | --------- 0004 | 0x00000116ac0f7190 | 0x0050 | Busy --------- | ---------------------- | -------- | --------- 0005 | 0x00000116ac0f71e0 | 0x0050 | Busy --------- | ---------------------- | -------- | --------- 0006 | 0x00000116ac0f7230 | 0x0050 | Busy --------- | ---------------------- | -------- | --------- 0007 | 0x00000116ac0f7280 | 0x0050 | Busy --------- | ---------------------- | -------- | --------- 引用MarkYason, "Windows 10 Segment Heap Internals"
My Project: SegmentHeapExt
Still Mystified by APIs? What They Are (Really) and Why They Matter
使用自定义ClassLoader解决反序列化serialVesionUID不一致问题
The Five Cybersecurity Practices Every Organization Should Adopt
我所认知的甲方信息安全建设经验
鱼叉攻击-炮轰马的制作
The Importance of Culture and Collaboration
免费领取《风控筑基系列长文》带目录pdf
漏洞复现|F5 BIG-IP TMUI(CVE-2020-5902)
Fintech学习资料 - Afant1
关于一次智能手表的探索
BIG IP CVE-2020-5902 漏洞检测和利用
解决OSSEC Agent 3.6.0 无法注册问题
Cybersecurity Myths That Are Harming Your Business
2020 Firefox 安全、隐私、实用扩展指南
新版本的 Firefox 加入了越来越多的隐私和安全相关功能,不过单凭 Firefox 自带功能还远远不能提供理想的安全上网体验。本篇文章将会介绍一些我目前使用的安全、隐私和实用相关的 Firefox 扩展,让网页浏览变得更加安全快捷。本文将着重介绍安全和隐私相关扩展,而对开发相关和实用性扩展一笔带过。扩展按字母顺序排序,可以通过右侧的目录来快速浏览。本文专业内容较多,如果有不正确或者可以改进的地方,请联系作者,我会立即更改。
安全隐私相关扩展 Behave!- 防止 DNS rebind 攻击
- 防止浏览器访问私有 IP 地址
- 防止端口扫描
当浏览器企图用 JavaScript 进行 IP 地址端口扫描时,Behave! 会记录并警告用户
可以到项目 GitHub 了解技术细节:https://github.com/mindedsecurity/behave
Canvas BlockerCanvas Blocker 扩展可以生成虚假的随机信息来欺骗各种追踪器。它可以伪装以下 API 的信息:
- canvas 2d
- webGL
- audio
- history
- window(默认禁用)
- DOMRect
- navigator(默认禁用)
- screen
Canvas Blocker 的设置较为复杂,需要了解上述的 API 才能较好地进行定制。
ClearURLs不少网站会在用户分享的链接中植入 UTM 参数以跟踪访问来源等。例如在如下的 Reddit 分享链接中,?utm_medium=android_app&utm_source=share 就是 UTM 参数。这两个参数告诉网站,这次的用户访问是通过分享链接访问的,而分享这个链接的设备运行的是 Android 系统。
下面的例子来源于 Amazon,其中 ref=sr_1_2?dchild=1&keywords=cisco&qid=1392922149&sr=8-2 的部分记录了各种信息,例如用户是搜索 Cisco 关键词后找到的这个商品。
作为一个对隐私比较偏执的用户,我并不希望网站知道类似于我是从谁获得的链接,分享链接给我的人使用了什么关键字找到的这个链接这些多余的信息。ClearURLs 会自动将链接中的此类追踪参数全数删除,让网站只知道您请求了该网页,而无从获知这些多余的信息。
例如上述的 Amazon 页面就会被清理成这样,既简洁又保护了隐私。
https://www.amazon.com/SYSTEMS-10-Port-Gigabit-Managed-SG35010K9NA/dp/B01HYA36SG
Decentraleyes很多网站会从 jQuery CDN 或者 Google Hosted Libraries 这样的第三方 CDN 加载流行的 JavaScript 框架。这样可以有效减轻网站的服务成本,同时也可以使用 CDN 的资源来加快加载速度。
例如下面的代码就会从 Google Hosted Libraries 加载 3.5.1 版本的 jQuery,而无需开发者在网页上额外保存一份 jQuery 副本。
1 <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>Decentraleyes 本地缓存了很多这样的文件。网站在向 CDN 请求文件的时候,Decentraleyes 就会拦截这些请求并且使用本地已经缓存好的文件。这么做既可以节省带宽,加快加载速度,也可以让用户摆脱对集中式网络的依赖以保护隐私。
Decentraleyes 工作原理
当您通过点击 URL 打开新的标签页时(target=_blank),新打开的窗口会拥有一个 window.opener 对象指向前一个网页的 window 对象。简而言之,新打开的标签页面可以控制前一个网页上的内容。
您可以通过 https://mathiasbynens.github.io/rel-noopener/ 这个网站更好的了解并体验这个机制。在禁止这个特性之前,可以通过点击图中的 URL 打开新的非同源页面。
rel=noopener 演示页面
如果该特性没有被禁止,网页会让您返回查看先前的页面。
打开的新标签页
此时可以发现先前的页面已经被新的页面篡改。这个问题的存在意味着恶意网站可以向打开它的页面中植入恶意代码。
被篡改的原网页
向网页中的 HTML 标签中加入 rel="noopener" 属性可以让新打开的页面中的 window.opener 等于 null,使新的页面无法访问先前的页面。然而此项功能是可选的,并且依赖您访问的网站来添加此标签。Don’t touch my tabs! 扩展会自动在非同源链接后加上 rel="noopener" 来确保您的网页不会被此方法篡改。
扩展自动在 HTML 标签里加入了 rel=“noopener” 属性
Firefox 联合 Cloudflare 出品的代理扩展,目前仅在美国地区进行公测。如果需要加入公测需要 IP 地址在美国,可以通过代理或者 VPN 做到。早期加入公测的用户可以无限时地使用 FPN,但是新用户每个月仅可使用 12 小时。我很幸运在早期就了解到了这款扩展,所以目前可以无限白嫖使用。
Firefox Private Network 处于运行状态
在使用了此扩展之后,所有浏览器的流量将从 Cloudflare 网络代理,网站仅能看到 Cloudflare 节点的地址。
IPInfo 显示浏览器 IP 地址为 Cloudflare 节点 IP
Forget Me Not(勿忘我)是一款可以清理浏览器 Cookie、浏览历史、网站缓存等数据的扩展。您可以自定义清理规则,例如清理时忽略某个网站,或者 Firefox 启动时清理清理 Cookie 和 历史。您也可以把这款扩展当作 Cookie 自动清理器来使用,在离开某个网站 N 分钟之后自动删除该网站的 Cookie、Local Storage、历史等。
下面的截图中,*.mozilla.org(所有 mozilla.org 网页)的清理作业被全部忽略,自动和手动清理时就相应的不会清理 Mozilla 网站的数据。
Forget Me Not 网站规则
下面的规则页面规定了何种数据应该在自动清理时被清除。
Forget Me Not 规则
当用户访问某个网站并且没有指定使用不安全的 HTTP 协议还是使用安全的 HTTPS 协议时,EFF(电子前哨基金会)开发的 HTTPS Everywhere 扩展会自动判断目标网站是否支持 HTTPS,并且如果网页服务器支持 HTTPS 则自动跳转到 HTTPS 页面。
HTTPS Everywhere 原理
在此之上,HTTPS Everywhere 还能够强制仅加载支持安全连接的网页元素,所有不使用 HTTPS 加载的图片、脚本等元素全部都会被过滤。虽然这样做可以极大增强浏览的安全性,但是很有可能会让很多网站无法正常显示,所以此功能请谨慎使用。
HTTPS Everywhere 设置
LastPass 是一款较为著名及常用的密码管理器。使用密码管理器可以为每个网站生成、储存并填充非常复杂难记的密码。这样做的好处是可以避免您在某个网站的密码泄漏后该密码被用来尝试登陆您在其他网站使用相同密码的帐号。同时,密码管理器还可以节省记很多密码的麻烦。当切换到不同的设备时,只需要在该设备上登陆密码管理器即可自动填充所有网站的密码。LastPass 同时还有两款手机应用分别可以做到在手机管理和填充密码以及管理六位数的两步验证一次性密码。
LastPass 填充 Google 登陆信息
与之相似的密码管理器还有 1Password 等,可以自行尝试各种不同的软件寻找最合适的密码管理器。
Privacy BadgerPrivacy Badger 是一款可以自动学习并屏蔽跟踪器的扩展,同样为 EFF 所开发。与传统的跟踪器黑名单不同,Privacy Badger 通过观察域名的行为来自动学习哪些网页域名是跟踪器的域名并且自动屏蔽这些域名。
以下是 Privacy Badger 对 Microsoft Docs 某个页面显示的屏蔽设置,其中对 github.com 的规则是默认的自动屏蔽第三方 Cookie,而对已知为跟踪器的 www.google-analytics.com 域名则采取完全屏蔽。这些设置可以手动调整,也可以通过浏览让 Privacy Badger 自动学习。
Privacy Badger 设置
相信很多人都在阅读部分网站的服务条款之前就盲目点击了 “我同意”,我也不例外。按道理说,这并不是一个好习惯,例如当您了解到 2019 年 Discord 在服务条款中加入了新的段落以防止您起诉 Discord 售卖您的数据时,您可能就像我一样不会那么想继续使用 Discord 了。原则上来说,用户既有阅读服务条款的义务,也应该阅读服务条款来判断是否应该使用某项服务。
然而,大多数网站和软件的服务条款都充斥着法律用语,极难阅读和理解。Terms of Service; Didn’t Read(TOSDR)这款扩展包含了许多网页简化版的服务条款,并给网站的条款打上一个评分,让您很容易就能了解该网站的服务条款是否对用户有利。
下图中展示的就是 TOSDR 显示的精简版的 GitHub 服务条款,详细地列出了条款中各个对用户有利和不利的款项,很容易就能看出该网站的条款中有哪些不足。同时,TOSDR 还给 GitHub 的服务条款打了一个 B 的评分,显示该网站的条款对用户较公平,但是仍有改进的空间。
Terms of Service; Didn’t Read 显示 GitHub 精简后的服务协议
uBlock Origin 是一款非常知名的广告屏蔽器,可以屏蔽网络上大部分的广告,让网站干净整洁。您可以自定义屏蔽列表,用自带的选取工具快速屏蔽某个网页元素,也可以用其自带的隐私保护功能保护浏览隐私。
(看下图背景猜我最近在玩哪款游戏)
uBlock Origin 状态页面
使用 uBlock Origin 时,我个人有两个推荐设置,可以到 Settings > Privacy 中勾选 Prevent WebRTC from leaking local IP address 以及 Block CSP report 来开启额外的 WebRTC 防 IP 泄漏保护以及禁用 CSP 报告。
uBlock Origin 隐私设置
选择广告屏蔽器时常有的另一个疑问是:uBlock Origin 和 AdBlock Plus 这两款扩展哪个更好?3D Insider 对此做过一个详细的对比,总结来说就是 uBlock Origin 会使用更少的资源,并且默认屏蔽所有广告。AdBlock 不仅会使用默认资源,而且默认不屏蔽所谓的 “可接受广告”,所以仍会显示一部分广告。
uMatrixuMatrix 可能是此篇指南中最硬核的一款扩展了,它可以让您自定义过滤每个网站上每个域名每种元素的加载。例如,您可以允许 Microsoft Docs 从 GitHub 加载图片,但是禁止其从 GitHub 加载脚本。除此之外,uMatrix 还可以伪造 Referer 字段以及 noscript 标签。
Microsoft Docs 页面上的 uMatrix 设置
虽然此扩展极为强大,但是其使用也极其繁琐。您可能需要一段时间才能学会如何使用此扩展,并且每到一个新的网站甚至页面都要手动调整该页面应用的规则,否则页面就大概率无法正确加载,功能也无法正常使用。如果您像我一样愿意花额外的精力和时间来调试每个网页以保证隐私和安全,该扩展则是您不二的选择。
开发和实用性扩展 Cookie Quick Manager一款 Cookie 编辑器,相较于 EditThisCookie2 的好处是可以全局搜索 Cookie。
Dark Reader浏览器的夜间模式扩展,我一般使用日间模式加上以下设定。同时需要注意一下这款扩展主要用 Filter、Static 和 Dynamic 三种模式,其中 Filter 模式最快,并且效果可观,但是可能让某些网站显示出错;Static 模式我个人并不喜欢用,因为效果很奇怪;Dynamic 模式虽说更不容易让页面崩坏,但是会显著提高页面加载速度,并且在开了很多标签页后会令浏览器卡顿。
DownThemAll!一款可以列出网页上可以下载的资源链接(图片,视频等)的扩展,和 Chrome 上被下架的 DownThemAll 扩展如出一辙。
EditThisCookie2Cookie 编辑器,和 Chrome 上的 EditThisCookie 一模一样。
Export Tabs URLs可以列出所有打开的标签页的 URL,批量复制标签页地址很方便。
FoxyTab标签页增强扩展,可以右键标签页进行各种操作。
Get Website IP显示当前网站的 IP 地址,省去了手动 DNS 查询,并且保证了在网站有多个 CDN 的情况下得到的 IP 地址和实际访问 IP 地址一致。
HTTP Header Live实时监控所有 HTTP 请求的 Header 字段,并提供保存功能。
HackBar提供了在渗透测试时会使用到的各种功能,例如添加自定义 Referer 字段/Cookie/User-Agent、计算 MD5/ROT13/URL/Base64 编码等。
Multithreaded Download Manager多线程下载器,在下载大文件时使用多线程下载,极大提高通量(throughput,有时也被松散翻译为带宽)利用率。
Offline QR Code Generator离线 QR 码生成器,可以快速生成当前 URL 的 QR 码或者任意内容的 QR 码,需要从桌面电脑分享网址给身边的朋友的时候非常好用。该扩展也有更高级的使用方式,例如根据 ISO/IEC 18004:2015 标准手动生成 Wi-Fi QR 码等。
Proxy SwitchyOmegaChrome 上著名的代理管理软件的 Firefox 移植版本。
SingleFile将整个网页保存为一个 HTML 文件并且完整保留网页的原貌。
Tamper Data for FF Quantum网页请求篡改器,可以拦截并篡改任意 Firefox HTTP 请求。
Tampermonkey人尽皆知的网页脚本框架,可以从 Greasyfork 等网站安装各类脚本来自定义网页。
Tweak New TwitterTwitter 简化扩展,可以让 Twitter 通过时间顺序显示 Tweet、删除 “Trends” 版块、分开显示 Tweet 和 Retweet 等。目前该扩展还在早期开发阶段,不是非常稳定。
User-Agent Switcher and Manager快速切换 User-Agent,在很多时候非常好用,例如用 Windows 的 UA 访问 Windows 下载页面,则页面会显示下载升级工具的链接;而将 UA 改为 Linux 之后,即可显示下载 Windows ISO 镜像的链接。
Wappalyzer非常强大的网页框架识别扩展,可以识别网页后端使用的服务器操作系统、开发框架等。例如下图中 Wappalyzer 就例举出了 Twitter 使用的框架等信息。
Wappalyzer 显示 Twitter 框架和其它信息
十分强大的网页开发辅助工具,可以对页面进行各种调试,也可以在申请软件试用的时候通过 Populate Form Fields 功能自动填充表单。
Web Developer 主界面