精华内容
下载资源
问答
  • 探索各种安全步骤,以帮助您为组织创建更广泛的安全方法。 了解如何预测和阻止攻击者渗透到您的系统。 此内容不再被更新或维护。 全文以PDF格式“按原样”提供。 随着技术的飞速发展,某些内容,步骤或插图可能已...

    等保三全面安全检查表

    存档日期:2019年5月15日 | 首次发布:2013年2月19日

    探索各种安全步骤,以帮助您为组织创建更广泛的安全方法。 了解如何预测和阻止攻击者渗透到您的系统。

    此内容不再被更新或维护。 全文以PDF格式“按原样”提供。 随着技术的飞速发展,某些内容,步骤或插图可能已更改。

    翻译自: https://www.ibm.com/developerworks/security/library/se-sweep/index.html

    等保三全面安全检查表

    展开全文
  • 一般情况下,下面的几种方法实现这种通信任务:使用全局变量(上一节的例子其实使用的就是这种方法)、使用事件对象、使用消息。这里我们主要介绍后两种方法。    (一) 利用用户定义的消息通信    在...

      一、线程之间的通信

     

      通常情况下,一个次级线程要为主线程完成某种特定类型的任务,这就隐含着表示在主线程和次级线程之间需要建立一个通信的通道。一般情况下,有下面的几种方法实现这种通信任务:使用全局变量(上一节的例子其实使用的就是这种方法)、使用事件对象、使用消息。这里我们主要介绍后两种方法。

     

      (一) 利用用户定义的消息通信

     

      在Windows程序设计中,应用程序的每一个线程都拥有自己的消息队列,甚至工作线程也不例外,这样一来,就使得线程之间利用消息来传递信息就变的非常简单。首先用户要定义一个用户消息,如下所示:#define WM_USERMSG WMUSER+100;在需要的时候,在一个线程中调用::PostMessage((HWND)param,WM_USERMSG,0,0)或CwinThread::PostThradMessage()来向另外一个线程发送这个消息,上述函数的四个参数分别是消息将要发送到的目的窗口的句柄、要发送的消息标志符、消息的参数WPARAM和LPARAM。下面的代码是对上节代码的修改,修改后的结果是在线程结束时显示一个对话框,提示线程结束:

     

    UINT ThreadFunction(LPVOID pParam)

    {

     while(!bend)

     {

      Beep(100,100);

      Sleep(1000);

     }

     ::PostMessage(hWnd,WM_USERMSG,0,0);

     return 0;

    }

    WM_USERMSG消息的响应函数为OnThreadended(WPARAM wParam,

    LPARAM lParam)

    LONG CTestView::OnThreadended(WPARAMwParam,LPARAM lParam)

    {

     AfxMessageBox("Threadended.");

     Retrun 0;

    }

     

      上面的例子是工作者线程向用户界面线程发送消息,对于工作者线程,如果它的设计模式也是消息驱动的,那么调用者可以向它发送初始化、退出、执行某种特定的处理等消息,让它在后台完成。在控制函数中可以直接使用::GetMessage()这个SDK函数进行消息分检和处理,自己实现一个消息循环。GetMessage()函数在判断该线程的消息队列为空时,线程将系统分配给它的时间片让给其它线程,不无效的占用CPU的时间,如果消息队列不为空,就获取这个消息,判断这个消息的内容并进行相应的处理。

     

      (二)用事件对象实现通信

     

      在线程之间传递信号进行通信比较复杂的方法是使用事件对象,用MFC的Cevent类的对象来表示。事件对象处于两种状态之一:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。上述例子代码修改如下:

     

    Cevent threadStart ,threadEnd;

    UINT ThreadFunction(LPVOID pParam)

    {

     ::WaitForSingleObject(threadStart.m_hObject,INFINITE);

     AfxMessageBox("Threadstart.");

     while(!bend)

     {

      Beep(100,100);

      Sleep(1000);

      Intresult=::WaitforSingleObject(threadEnd.m_hObject,0);

      //等待threadEnd事件有信号,无信号时线程在这里悬停

      If(result==Wait_OBJECT_0)

       Bend=TRUE;

     }

     ::PostMessage(hWnd,WM_USERMSG,0,0);

     return 0;

    }

    /

    Void CtestView::OninitialUpdate()

    {

     hWnd=GetSafeHwnd();

     threadStart.SetEvent();//threadStart事件有信号

     pThread=AfxBeginThread(ThreadFunction,hWnd);//启动线程

     pThread->m_bAutoDelete=FALSE;

     Cview::OnInitialUpdate();

    }

    Void CtestView::OnDestroy()

    {

     threadEnd.SetEvent();

     WaitForSingleObject(pThread->m_hThread,INFINITE);

     deletepThread;

     Cview::OnDestroy();

    }

      运行这个程序,当关闭程序时,才显示提示框,显示"Thread ended"。



    二、线程之间的同步

     

      前面我们讲过,各个线程可以访问进程中的公共变量,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据,以免破坏数据的完整性。保证各个线程可以在一起适当的协调工作称为线程之间的同步。前面一节介绍的事件对象实际上就是一种同步形式。Visual C++中使用同步类来解决操作系统的并行性而引起的数据不安全的问题,MFC支持的七个多线程的同步类可以分成两大类:同步对象(CsyncObject、Csemaphore、Cmutex、CcriticalSection和Cevent)和同步访问对象(CmultiLock和CsingleLock)。本节主要介绍临界区(critical section)、互斥(mutexe)、信号量(semaphore),这些同步对象使各个线程协调工作,程序运行起来更安全。

     

      (一) 临界区

     

      临界区是保证在某一个时间只有一个线程可以访问数据的方法。使用它的过程中,需要给各个线程提供一个共享的临界区对象,无论哪个线程占有临界区对象,都可以访问受到保护的数据,这时候其它的线程需要等待,直到该线程释放临界区对象为止,临界区被释放后,另外的线程可以强占这个临界区,以便访问共享的数据。临界区对应着一个CcriticalSection对象,当线程需要访问保护数据时,调用临界区对象的Lock()成员函数;当对保护数据的操作完成之后,调用临界区对象的Unlock()成员函数释放对临界区对象的拥有权,以使另一个线程可以夺取临界区对象并访问受保护的数据。同时启动两个线程,它们对应的函数分别为WriteThread()和ReadThread(),用以对公共数组组array[]操作,下面的代码说明了如何使用临界区对象:

     

    #include "afxmt.h"

    int array[10],destarray[10];

    CCriticalSection Section;

    UINT WriteThread(LPVOID param)

    {

     Section.Lock();

     for(intx=0;x<10;x++)

      array[x]=x;

     Section.Unlock();

    }

    UINT ReadThread(LPVOID param)

    {

     Section.Lock();

     For(intx=0;x<10;x++)

      Destarray[x]=array[x];

      Section.Unlock();

    }

     

      上述代码运行的结果应该是Destarray数组中的元素分别为1-9,而不是杂乱无章的数,如果不使用同步,则不是这个结果,有兴趣的读者可以实验一下。

     

      (二)互斥

     

      互斥与临界区很相似,但是使用时相对复杂一些,它不仅可以在同一应用程序的线程间实现同步,还可以在不同的进程间实现同步,从而实现资源的安全共享。互斥与Cmutex类的对象相对应,使用互斥对象时,必须创建一个CSingleLock或CMultiLock对象,用于实际的访问控制,因为这里的例子只处理单个互斥,所以我们可以使用CSingleLock对象,该对象的Lock()函数用于占有互斥,Unlock()用于释放互斥。实现代码如下:

     

    #include "afxmt.h"

    int array[10],destarray[10];

    CMutex Section;

     

    UINT WriteThread(LPVOID param)

    {

     CsingleLocksinglelock;

     singlelock(&Section);

     singlelock.Lock();

     for(

    int x=0;x<10;x++)

      array[x]=x;

     singlelock.Unlock();

    }

     

     

    UINT ReadThread(LPVOID param)

    {

     CsingleLocksinglelock;

     singlelock(&Section);

     singlelock.Lock();

     For(intx=0;x<10;x++)

      Destarray[x]=array[x];

      singlelock.Unlock();

    }

     

      (三)信号量

     

      信号量的用法和互斥的用法很相似,不同的是它可以同一时刻允许多个线程访问同一个资源,创建一个信号量需要用Csemaphore类声明一个对象,一旦创建了一个信号量对象,就可以用它来对资源的访问技术。要实现计数处理,先创建一个CsingleLock或CmltiLock对象,然后用该对象的Lock()函数减少这个信号量的计数值,Unlock()反之。下面的代码分别启动三个线程,执行时同时显示二个消息框,然后10秒后第三个消息框才得以显示。

     

    /

    Csemaphore *semaphore;

    Semaphore=new Csemaphore(2,2);

    HWND hWnd=GetSafeHwnd();

    AfxBeginThread(threadProc1,hWnd);

    AfxBeginThread(threadProc2,hWnd);

    AfxBeginThread(threadProc3,hWnd);

    UINT ThreadProc1(LPVOID param)

    {

     CsingleLocksingelLock(semaphore);

     singleLock.Lock();

     Sleep(10000);

     ::MessageBox((HWND)param,"Thread1had access","Thread1",MB_OK);

     return 0;

    }

    UINT ThreadProc2(LPVOID param)

    {

     CSingleLocksingelLock(semaphore);

     singleLock.Lock();

     Sleep(10000);

     ::MessageBox((HWND)param,"Thread2had access","Thread2",MB_OK);

     return 0;

    }

     

    UINT ThreadProc3(LPVOID param)

    {

     CsingleLocksingelLock(semaphore);

     singleLock.Lock();

     Sleep(10000);

     ::MessageBox((HWND)param,"Thread3had access","Thread3",MB_OK);

     return 0;

    }

     

      二、 编程步骤

     

      1、 启动Visual C++6.0,生成一个32位的控制台程序,将该程序命名为"sequence"

     

      2、 输入要排续的数字,声明四个子线程;

     

      3、 输入代码,编译运行程序。

    三、 程序代码

     

    //

    // sequence.cpp : Defines the entry pointfor the console application.

    /*

    主要用到的WINAPI线程控制函数,有关详细说明请查看MSDN;

    线程建立函数:

    HANDLE CreateThread(

     LPSECURITY_ATTRIBUTESlpThreadAttributes, // 安全属性结构指针,可为NULL;

     DWORDdwStackSize, // 线程栈大小,若为0表示使用默认值;

     LPTHREAD_START_ROUTINElpStartAddress, // 指向线程函数的指针;

     LPVOID lpParameter,// 传递给线程函数的参数,可以保存一个指针值;

     DWORDdwCreationFlags, // 线程建立是的初始标记,运行或挂起;

     LPDWORDlpThreadId // 指向接收线程号的DWORD变量;

    );

     

    对临界资源控制的多线程控制的信号函数:

     

    HANDLE CreateEvent(

     LPSECURITY_ATTRIBUTESlpEventAttributes, // 安全属性结构指针,可为NULL;

     BOOLbManualReset, // 手动清除信号标记,TRUE在WaitForSingleObject后必须手动//调用RetEvent清除信号。若为 FALSE则在WaitForSingleObject

     //后,系统自动清除事件信号;

     BOOLbInitialState, // 初始状态,TRUE有信号,FALSE无信号;

     LPCTSTRlpName // 信号量的名称,字符数不可多于MAX_PATH;

     //如果遇到同名的其他信号量函数就会失败,如果遇

     //到同类信号同名也要注意变化;

    );

     

    HANDLE CreateMutex(

     LPSECURITY_ATTRIBUTESlpMutexAttributes, // 安全属性结构指针,可为NULL

     BOOLbInitialOwner, // 当前建立互斥量是否占有该互斥量TRUE表示占有,

     //这样其他线程就不能获得此互斥量也就无法进入由

     //该互斥量控制的临界区。FALSE表示不占有该互斥量

     LPCTSTRlpName // 信号量的名称,字符数不可多于MAX_PATH如果

     //遇到同名的其他信号量函数就会失败,

     //如果遇到同类信号同名也要注意变化;

    );

     

    //初始化临界区信号,使用前必须先初始化

    VOID InitializeCriticalSection(

     LPCRITICAL_SECTIONlpCriticalSection // 临界区变量指针

    );

     

    //阻塞函数

    //如果等待的信号量不可用,那么线程就会挂起,直到信号可用

    //线程才会被唤醒,该函数会自动修改信号,如Event,线程被唤醒之后

    //Event信号会变得无信号,Mutex、Semaphore等也会变。

    DWORD WaitForSingleObject(

     HANDLEhHandle, // 等待对象的句柄

     DWORDdwMilliseconds // 等待毫秒数,INFINITE表示无限等待

    );

    //如果要等待多个信号可以使用WaitForMutipleObject函数

    */

     

    #include "stdafx.h"

    #include "stdlib.h"

    #include "memory.h"

    HANDLE evtTerminate; //事件信号,标记是否所有子线程都执行完

    /*

    下面使用了三种控制方法,你可以注释其中两种,使用其中一种。

    注意修改时要连带修改临界区PrintResult里的相应控制语句

    */

    HANDLE evtPrint; //事件信号,标记事件是否已发生

    //CRITICAL_SECTION csPrint; //临界区

    //HANDLE mtxPrint; //互斥信号,如有信号表明已经有线程进入临界区并拥有此信号

    static long ThreadCompleted = 0;

    /*用来标记四个子线程中已完成线程的个数,当一个子线程完成时就对ThreadCompleted进行加一操作, 要使用InterlockedIncrement(long*lpAddend)和InterlockedDecrement(long*lpAddend)进行加减操作*/

     

    //下面的结构是用于传送排序的数据给各个排序子线程

    struct MySafeArray

    {

     long* data;

     int iLength;

    };

     

    //打印每一个线程的排序结果

    void PrintResult(long* Array, int iLength,const char* HeadStr = "sort");

     

    //排序函数

    unsigned long __stdcall BubbleSort(void*theArray); //冒泡排序

    unsigned long __stdcall SelectSort(void*theArray); //选择排序

    unsigned long __stdcall HeapSort(void*theArray); //堆排序

    unsigned long __stdcall InsertSort(void*theArray); //插入排序

    /*以上四个函数的声明必须适合作为一个线程函数的必要条件才可以使用CreateThread

    建立一个线程。

    (1)调用方法必须是__stdcall,即函数参数压栈顺序由右到左,而且由函数本身负责

    栈的恢复, C和C++默认是__cdecl, 所以要显式声明是__stdcall

    (2)返回值必须是unsigned long

    (3)参数必须是一个32位值,如一个指针值或long类型

    (4) 如果函数是类成员函数,必须声明为static函数,在CreateThread时函数指针有特殊的写法。如下(函数是类CThreadTest的成员函数中):

    static unsigned long _stdcallMyThreadFun(void* pParam);

    handleRet = CreateThread(NULL, 0,&CThreadTestDlg::MyThreadFun, NULL, 0, &ThreadID);

    之所以要声明为static是由于,该函数必须要独立于对象实例来使用,即使没有声明实例也可以使用。*/

     

    int QuickSort(long* Array, int iLow, intiHigh); //快速排序

     

    int main(int argc, char* argv[])

    {

     long data[] ={123,34,546,754,34,74,3,56};

     int iDataLen= 8;

     //为了对各个子线程分别对原始数据进行排序和保存排序结果

     //分别分配内存对data数组的数据进行复制

     long *data1,*data2, *data3, *data4, *data5;

     MySafeArrayStructData1, StructData2, StructData3, StructData4;

     data1 = newlong[iDataLen];

     memcpy(data1,data, iDataLen << 2); //把data中的数据复制到data1中

     //内存复制 memcpy(目标内存指针, 源内存指针, 复制字节数), 因为long的长度

     //为4字节,所以复制的字节数为iDataLen<< 2, 即等于iDataLen*4

     StructData1.data= data1;

     StructData1.iLength= iDataLen;

     data2 = newlong[iDataLen];

     memcpy(data2,data, iDataLen << 2);

     StructData2.data= data2;

     StructData2.iLength= iDataLen;

     data3 = newlong[iDataLen];

     memcpy(data3,data, iDataLen << 2);

     StructData3.data= data3;

     StructData3.iLength= iDataLen;

     data4 = newlong[iDataLen];

     memcpy(data4,data, iDataLen << 2);

     StructData4.data= data4;

     StructData4.iLength= iDataLen;

     data5 = newlong[iDataLen];

     memcpy(data5,data, iDataLen << 2);

     unsigned longTID1, TID2, TID3, TID4;

     //对信号量进行初始化

     evtTerminate= CreateEvent(NULL, FALSE, FALSE, "Terminate");

     evtPrint =CreateEvent(NULL, FALSE, TRUE, "PrintResult");

     //分别建立各个子线程

     CreateThread(NULL,0, &BubbleSort, &StructData1, NULL, &TID1);

     CreateThread(NULL,0, &SelectSort, &StructData2, NULL, &TID2);

     CreateThread(NULL,0, &HeapSort, &StructData3, NULL, &TID3);

     CreateThread(NULL,0, &InsertSort, &StructData4, NULL, &TID4);

     //在主线程中执行行快速排序,其他排序在子线程中执行

     QuickSort(data5,0, iDataLen - 1);

     PrintResult(data5,iDataLen, "Quick Sort");

     WaitForSingleObject(evtTerminate,INFINITE); //等待所有的子线程结束

     //所有的子线程结束后,主线程才可以结束

     delete[]data1;

     delete[]data2;

     delete[]data3;

     delete[]data4;

     CloseHandle(evtPrint);

     return 0;

    }

     

    /*

    冒泡排序思想(升序,降序同理,后面的算法一样都是升序):从头到尾对数据进行两两比较进行交换,小的放前大的放后。这样一次下来,最大的元素就会被交换的最后,然后下一次

    循环就不用对最后一个元素进行比较交换了,所以呢每一次比较交换的次数都比上一次循环的次数少一,这样N次之后数据就变得升序排列了*/

    unsigned long __stdcall BubbleSort(void*theArray)

    {

     long* Array =((MySafeArray*)theArray)->data;

     int iLength =((MySafeArray*)theArray)->iLength;

     int i, j=0;

     long swap;

     for (i =iLength-1; i > 0; i--)

     {

      for(j = 0; j< i; j++)

      {

       if(Array[j]> Array[j+1]) //前比后大,交换

       {

        swap =Array[j];

        Array[j] =Array[j+1];

        Array[j+1]= swap;

       }

      }

     }

     PrintResult(Array,iLength, "Bubble Sort"); //向控制台打印排序结果

     InterlockedIncrement(&ThreadCompleted);//返回前使线程完成数标记加1

     if(ThreadCompleted== 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完

     //若都执行

    完则设置程序结束信号量

     return 0;

    }

     

    /*选择排序思想:每一次都从无序的数据中找出最小的元素,然后和前面已经有序的元素序列的后一个元素进行交换,这样整个源序列就会分成两部分,前面一部分是已经排好序的有序序列,后面一部分是无序的,用于选出最小的元素。循环N次之后,前面的有序序列加长到跟源序列一样长,后面的无序部分长度变为0,排序就完成了。*/

    unsigned long __stdcall SelectSort(void*theArray)

    {

     long* Array =((MySafeArray*)theArray)->data;

     int iLength =((MySafeArray*)theArray)->iLength;

     long lMin,lSwap;

     int i, j,iMinPos;

     for(i=0; i< iLength-1; i++)

     {

      lMin =Array[i];

      iMinPos = i;

      for(j=i + 1;j <= iLength-1; j++) //从无序的元素中找出最小的元素

      {

       if(Array[j]< lMin)

       {

        iMinPos =j;

        lMin =Array[j];

       }

      }

      //把选出的元素交换拼接到有序序列的最后

      lSwap =Array[i];

      Array[i] =Array[iMinPos];

      Array[iMinPos]= lSwap;

     }

     PrintResult(Array,iLength, "Select Sort"); //向控制台打印排序结果

     InterlockedIncrement(&ThreadCompleted);//返回前使线程完成数标记加1

     if(ThreadCompleted== 4) SetEvent(evtTerminate);//检查是否其他线程都已执行完

     //若都执行完则设置程序结束信号量

     return 0;

    }

     

    /*堆排序思想:堆:数据元素从1到N排列成一棵二叉树,而且这棵树的每一个子树的根都是该树中的元素的最小或最大的元素这样如果一个无序数据集合是一个堆那么,根元素就是最小或最大的元素堆排序就是不断对剩下的数据建堆,把最小或最大的元素析透出来。下面的算法,就是从最后一个元素开始,依据一个节点比父节点数值大的原则对所有元素进行调整,这样调整一次就形成一个堆,第一个元素就是最小的元素。然后再对剩下的无序数据再进行建堆,注意这时后面的无序数据元素的序数都要改变,如第一次建堆后,第二个元素就会变成堆的第一个元素。*/

    unsigned long __stdcall HeapSort(void*theArray)

    {

     long* Array =((MySafeArray*)theArray)->data;

     int iLength =((MySafeArray*)theArray)->iLength;

     int i, j, p;

     long swap;

     for(i=0;i<iLength-1; i++)

     {

      for(j =iLength - 1; j>i; j--) //从最后倒数上去比较字节点和父节点

      {

       p = (j - i- 1)/2 + i; //计算父节点数组下标

       //注意到树节点序数跟数组下标不是等同的,因为建堆的元素个数逐个递减

       if(Array[j]< Array[p]) //如果父节点数值大则交换父节点和字节点

       {

        swap =Array[j];

        Array[j] =Array[p];

        Array[p] =swap;

       }

      }

     }

     PrintResult(Array,iLength, "Heap Sort"); //向控制台打印排序结果

     InterlockedIncrement(&ThreadCompleted);//返回前使线程完成数标记加1

     if(ThreadCompleted== 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完

     //若都执行完则设置程序结束信号量

     return 0;

    }

     

    /*插入排序思想:把源数据序列看成两半,前面一半是有序的,后面一半是无序的,把无序的数据从头到尾逐个逐个的插入到前面的有序数据中,使得有序的数据的个数不断增大,同时无序的数据个数就越来越少,最后所有元素都会变得有序。*/

    unsigned long __stdcall InsertSort(void*theArray)

    {

     long* Array =((MySafeArray*)theArray)->data;

     int iLength =((MySafeArray*)theArray)->iLength;

     int i=1, j=0;

     long temp;

     for(i=1;i<iLength; i++)

     {

      temp =Array[i]; //取出序列后面无序数据的第一个元素值

      for(j=i;j>0; j--) //和前面的有序数据逐个进行比较找出合适的插入位置

      {

       if(Array[j- 1] > temp) //如果该元素比插入值大则后移

        Array[j] =Array[j - 1];

       else //如果该元素比插入值小,那么该位置的后一位就是插入元素的位置

        break;

      }

      Array[j] =temp;

     }

     PrintResult(Array,iLength, "Insert Sort"); //向控制台打印排序结果

     InterlockedIncrement(&ThreadCompleted);//返回前使线程完成数标记加1

     if(ThreadCompleted== 4) SetEvent(evtTerminate); //检查是否其他线程都已执行完

      //若都执行完则设置程序结束信号量

     return 0;

    }

     

    /*快速排序思想:快速排序是分治思想的一种应用,它先选取一个支点,然后把小于支点的元素交换到支点的前边,把大于支点的元素交换到支点的右边。然后再对支点左边部分和右

    边部分进行同样的处理,这样若干次之后,数据就会变得有序。下面的实现使用了递归

    建立两个游标:iLow,iHigh;iLow指向序列的第一个元素,iHigh指向最后一个先选第一个元素作为支点,并把它的值存贮在一个辅助变量里。那么第一个位置就变为空并可以放置其他的元素。这样从iHigh指向的元素开始向前移动游标,iHigh查找比支点小的元素,如果找到,则把它放置到空置了的位置(现在是第一个位置),然后iHigh游标停止移动,这时iHigh指向的位置被空置,然后移动iLow游标寻找比支点大的元素放置到iHigh指向的空置的位置,如此往复直到iLow与iHigh相等。最后使用递归对左右两部分进行同样处理*/

     

    int QuickSort(long* Array, int iLow, intiHigh)

    {

     if(iLow >=iHigh) return 1; //递归结束条件

     long pivot =Array[iLow];

     int iLowSaved= iLow, iHighSaved = iHigh; //保未改变的iLow,iHigh值保存起来

     while (iLow< iHigh)

     {

      while(Array[iHigh] >= pivot && iHigh > iLow) //寻找比支点大的元素

       iHigh -- ;

      Array[iLow]= Array[iHigh]; //把找到的元素放置到空置的位置

      while(Array[iLow] < pivot && iLow < iHigh) //寻找比支点小的元素

       iLow ++ ;

      Array[iHigh]= Array[iLow]; //把找到的元素放置到空置的位置

     }

     Array[iLow] =pivot; //把支点值放置到支点位置,这时支点位置是空置的

     //对左右部分分别进行递归处理

     QuickSort(Array,iLowSaved, iHigh-1);

     QuickSort(Array,iLow+1, iHighSaved);

     return 0;

    }

     

    //每一个线程都要使用这个函数进行输出,而且只有一个显示器,产生多个线程

    //竞争对控制台的使用权。

    void PrintResult(long* Array, int iLength,const char* HeadStr)

    {

     WaitForSingleObject(evtPrint,INFINITE); //等待事件有信号

     //EnterCriticalSection(&csPrint);//标记有线程进入临界区

     //WaitForSingleObject(mtxPrint,INFINITE); //等待互斥量空置(没有线程拥有它)

     int i;

     printf("%s:", HeadStr);

     for (i=0;i<iLength-1; i++)

     {

      printf("%d,",Array[i]);

      Sleep(100);//延时(可以去掉)

    /*只是使得多线程对临界区访问的问题比较容易看得到

    如果你把临界控制的语句注释掉,输出就会变得很凌乱,各个排序的结果会

    分插间隔着输出,如果不延时就不容易看到这种不对临界区控制的结果

    */

     }

     printf("%d\n",Array[i]);

     SetEvent(evtPrint);//把事件信号量恢复,变为有信号

    展开全文
  • 三种解密 HTTPS 流量的方法介绍

    千次阅读 2018-01-08 11:37:39
    三种解密 HTTPS 流量的方法介绍 Web 安全是一项系统工程,任何细微疏忽都可能导致整个安全堡垒土崩瓦解。拿 HTTPS 来说,它的「内容加密、数据完整性、身份认证」安全保证,也会受到非法根证书、服务端配置...

    三种解密 HTTPS 流量的方法介绍

    Web 安全是一项系统工程,任何细微疏忽都可能导致整个安全堡垒土崩瓦解。拿 HTTPS 来说,它的「内容加密、数据完整性、身份认证」三大安全保证,也会受到非法根证书、服务端配置错误、SSL 库漏洞、私钥被盗等等风险的影响。很多同学认为只要访问的网站地址前有一把小绿锁就绝对安全,其实不然。本文通过介绍三种最常规的 HTTPS 流量解密方法及原理,浅谈一下 HTTPS 的安全风险。

    Man-in-the-middle

    Man-in-the-middle(中间人,简称为 MITM),能够与网络通讯两端分别创建连接,交换其收到的数据,使得通讯两端都认为自己直接与对方对话,事实上整个会话都被中间人所控制。简而言之,在真正的服务端看来,中间人是客户端;而真正的客户端会认为中间人是服务端。

    实现中间人攻击有各种各样的手段,这里不展开讨论。一些常见的 HTTP/HTTPS 抓包调试工具,都是通过创建本地 Proxy 服务,再修改浏览器 Proxy 设置来达到拦截流量的目的,他们的工作原理与中间人攻击一致。我用过的这一类工具有:FiddlerCharles  whistle

    本文主要讨论 HTTPS 中间人,简单示意如下:

    Server <---> Local Proxy <---> Browser
             ^                 ^
           HTTPS(1)          HTTPS(2)
    

    上述 HTTPS(1) 连接,是中间人冒充客户端,与服务端建立的连接,由于 HTTPS 服务端一般不认证客户端身份,这一步通常没有问题。而对于 HTTPS(2) 连接来说,中间人想要冒充服务端,必须拥有对应域名的证书私钥,而攻击者要拿到私钥,只能通过这些手段:1)去网站服务器上拿;2)从 CA 处签发证书;3)自己签发证书。

    要防范前两点,需要网站做好各个方面的安全防护,从主机安全到网站安全(避免私钥被盗),从域名解析安全到域名邮箱安全(避免攻击者重签证书)。而攻击者自己签发的证书,无法通过系统内置根证书的验证,默认无法用于中间人攻击。

    对于 Fiddler 这一类调试工具来说,能够解密 HTTPS 流量的关键在于他们会往系统受信任的根证书列表导入自己的证书,这样他们的自签证书就能被浏览器信任。进入 Fiddler 设置中的「HTTPS」Tab,勾选相关功能后,就可以顺利解密和修改 HTTPS 流量。

    RSA Private Key

    Wireshark 的抓包原理是直接读取并分析网卡数据,要想让它解密 HTTPS 流量,有两个办法:1)如果你拥有 HTTPS 网站的加密私钥,可以用来解密这个网站的加密流量;2)某些浏览器支持将 TLS 会话中使用的对称密钥保存在外部文件中,可供 Wireshark 加密使用。那篇文章介绍了第二种方案,本文简单介绍第一种。

    打开 Wireshark 的 SSL 协议设置,参考下图,把 IP、端口、协议和证书私钥都配上(私钥必须存为 PEM 格式)

    然后访问私钥对应的网站,可以看到流量已被解密

    可以看到截图中的 HTTP/1 加密数据已被还原为明文。那么这种方式能解密 HTTP/2 流量吗?先说结论:不能!具体原因下面慢慢分析。

    我们知道,TLS 握手阶段需要进行密钥交换和服务端认证这两个重要的操作,密钥交换是为了在不安全数据通道中产生一个只有通信双方知道的共享密钥 Premaster Secret,进而生成 Master Secret 以及后续对称加密 Session Key 和 MAC Key。而客户端之所以要进行服务端认证,是为了确保连接到拥有网站私钥的合法服务器。

    Client Random 和 Server Random 明文传输,中间人可以直接查看。客户端生成 Premaster Secret 后,用服务端证书公钥加密后发送,如果服务端拥有对应的私钥,就可以成功解密得到 Premaster Secret。这时,客户端和服务端拥有相同的 Client Random、Server Random 和 Premaster Secret,可以各自算出相同的后续所需 Key。

    可以看到,这种方式合并了密钥交换和服务端认证两个步骤,如果服务端能解出 Premaster Secret,也就意味着服务端拥有正确的私钥。中间人没有私钥,无法得到 Premaster Secret,也就无法解密后续流量。

    对于 Wireshark 来说,配置某个网站的私钥后,能解密这个网站「使用 RSA 进行密钥交换」的加密流量就很容易理解了。

    显然,RSA 密钥交换有一个很大的问题:没有前向安全性(Forward Secrecy)。这意味着攻击者可以把监听到的加密流量先存起来,后续一旦拿到了私钥,之前所有流量都可以成功解密。

    实际上,目前大部分 HTTPS 流量用的都是 ECDHE 密钥交换。ECDHE 是使用椭圆曲线(ECC)的 DH(Diffie-Hellman)算法。

    上图中的 Server DH Parameter 是用证书私钥签名的,客户端使用证书公钥就可以验证服务端合法性。相比 RSA 密钥交换,DH 由传递 Premaster Scret 变成了传递 DH 算法所需的 Parameter,然后双方各自算出 Premaster Secret。

    对于这种情况,由于 Premaster Secret 无需交换,中间人就算有私钥也无法获得 Premaster Secret 和 Master Secret。也就是说 Wireshark 无法通过配置 RSA Private Key 的方式解密「使用 ECDHE 进行密钥交换」的加密流量。当然,使用 ECDHE 后,如果被中间人拿到私钥,尽管无法用于解密之前流量,但他可以实施 MITM 攻击来解密之后的流量,所以私钥还是要保管好。

    在 ECDHE 密钥交换中,签名算法可以使用 RSA 或 ECDSA(取决于证书类型),也就是目前密钥交换 + 签名有三种主流选择:

    • RSA 密钥交换(无需数字签名);
    • ECDHE 密钥交换、RSA 数字签名;
    • ECDHE 密钥交换、ECDSA 数字签名;

    HTTP/2 中只能使用 TLSv1.2+,还禁用了几百种 CipherSuite(详见:TLS 1.2 Cipher Suite Black List)。实际上,HTTP/2 允许使用的 CipherSuite 必须采用具有前向安全性的密钥交换算法,不允许使用 RSA 密钥交换。这也是为什么 RSA Private Key 无法用于解密 HTTP/2 加密流量。

    SSLKEYLOGFILE

    Firefox 和 Chrome 都会在系统环境变量存在 SSLKEYLOGFILE 文件路径时,将每个 HTTPS 连接产生的 Premaster Secret 或 Master Secret 存下来。有了这个文件,Wireshark 就可以轻松解密 HTTPS 流量,即使是使用了 ECDHE 这种具有前向安全性的密钥交换

    SSLKEYLOGFILE 文件记录的是 HTTPS 数据传输中最重要的加密信息,如果不是出于调试目的,一般也没人会主动配置这个环境变量,所以这个方案基本不会对 HTTPS 安全性产生影响。

    总结

    Fiddler 这类工具通过往系统导入根证书来实现 HTTPS 流量解密,充当中间人角色。要防范真正的 HTTPS 中间人攻击,网站方需要保管好自己的证书私钥和域名认证信息,为了防范不良 CA 非法向第三方签发自己的网站证书,还要尽可能启用 Certificate TransparencyHTTP Public Key Pinning 等策略;用户方不要随便信任来历不明的证书,更不要随意导入证书到根证书列表,还要养成经常检查常用网站证书链的习惯。

    RSA 密钥交换没有前向安全性,这意味着一旦私钥泄漏,之前所有加密流量都可以解开。为此,网站方需要启用使用 ECDHE 作为密钥交换的 CipherSuite,或者直接使用 ECC 证书;用户方需要弃用不支持 ECDHE 的古董操作系统及浏览器。

    对于浏览器而言,HTTPS 毫无秘密,通过浏览器生成的 SSLKEYLOGFILE 文件,Wireshark 可以轻松解密 HTTPS 流量。另外,如果浏览器被安装恶意扩展,即使访问安全的 HTTPS 网站,提交的数据一样可以被截获。这种客户端被攻击者控制引发的安全问题,无法通过 HTTPS 来解决。


    原文转载至:https://imququ.com/post/how-to-decrypt-https.html

    展开全文
  • Linux和Windows共享文件的三种方法

    万次阅读 2017-03-28 16:41:35
    Linux和Windows共享文件的三种方法

    作者:华清远见讲师

    第一种 samba共享

    1、安装samba:可以先检查下是否已经安装:rpm -qa | grep samba,没有的话自己安装下,这里介绍下基于RPM包的一种在线安装模式yum yum是一种快速安装模式,它会自动解决软件安装时的依赖问题并自动去特定的服务器下载相应的软件进行安装,安装命令:yum install samba

    2、创建共享文件夹 mkdir -m 777 /home/default/share

    3、修改/etc/samba/smb.conf,这里面的参数就比较多,主要是

    workgroup = WORKGROUP(这个就是windows工作组模式,还有一种是域模式)

    hosts allow = 192.168.1.100(可以放问的IP地址,这里写的时windows地址)

    然后在文件的结尾加上

    [public](共享名,就是windows访问时会显示的名称)

    comment = Public Stuff(注释)

    path = /home/def/share(共享名)

    public = yes(公开)

    writable = yes(可写)

    注:vim /etc/samba/smb.conf. 将security=share. 本人win7系统。

    当然这里只是些基础的参数,还有其他的参数根据情况设置,比如会出现乱码,还要指定编码格式。

    4、重启smb服务

    service smb restart

    5、创建samba客户

    smbpasswd -a def,回车后会提示输入密码。这个就是将来远程主机登录时需要的密码,这里的def帐号必须是系统已经有的帐号,没有的话会报错,然后新输的密码就是远程登录密码,这样做的好处就是自己的密码和远程登录的密码分开。

    6、windows下连接,启动运行,输入\\192.168.1.101,输入smb帐号密码就可以了

    第二种 通过NFS来访问主机端共享文件夹

    NFS简介:NFS,是Network File System的简写,即网络文件系统。网络文件系统是FreeBSD支持的文件系统中的一种,也被称为NFS. NFS允许一个系统在网络上与他人共享目录和文件。通过使用NFS,用户和程序可以像访问本地文件一样访问远端系统上的文件。

    NFS最显而易见的好处:

    1>本地工作站使用更少的磁盘空间,因为通常的数据可以存放在一台机器上而且可以通过网络访问到。

    2>用户不必在每个网络上机器里头都有一个home目录。Home目录 可以被放在NFS服务器上并且在网络上处处可用。

    3>诸如软驱,CDROM,和 Zip(是指一种高储存密度的磁盘驱动器与磁盘)之类的存储设备可以在网络上面被别的机器使用。这可减少整个网络上的可移动介质设备的数量。

    1、安装nfs服务器

    Sudo apt-get install nfs-kernel-server

    2、配置NFS服务器

    Sudo vi /etc/exports

    /source/rootfs *(rw,sync,no_subtree_check,no_root_squash)

    Rw:具有读写权限

    Sync:文件同步写入到内存和硬盘

    no_subtree_check:如果共享的是根目录,则不检查子目录

    No_root_squash:如果客户端是root的话,那么他对这个目录具有root的权限

    3、重启NFS服务器

    Sudo service nfs-kernel-server restart

    4、进行挂载测试

    Sudo mount -t nfs localhost:/source/rootfs /mnt

    Ls -l /mnt

    5、卸载

    Sudo umount /mnt

    第三种 通过9p-virtio来访问主机端共享文件夹

    9p-virtio简介: 英文全称为:Plan 9 folder sharing over Virtio - I/O virtualization framework,是一种host和guest端共享文件夹的文件系统服务。目前guest端暂不支持windos系统(系统不支持virtio服务,且mount命令不支持挂载该格式的文件系统)。我测试guest端用的是ubuntu-13.10。

    1, 检查host端是否支持9p-virtio。

    lsmod | grep 9p

    如果已经加载 9p.ko,9pnet_virtio.ko,9pnet.ko,说明已经支持virtio-9p。

    如果不支持,需要重新编译kernel或9p对应的模块,并加载到host系统。编译前需要在kernel中添加如下配置选项:

    CONFIG_NET_9P=y

    CONFIG_NET_9P_VIRTIO=y

    CONFIG_9P_FS=y

    CONFIG_9P_FS_POSIX_ACL=y

    注:如果是模块,将y换成m,建议编译为驱动模块,省时间,’安全性高’。

    2,客户端安装linux系统, 我安装的是ubuntu13-10版本。

    测试时需要用到root账户,所以先把root账户密码激活,

    ubuntu默认root密码不启动,密码激活:sudo passwd root 输入你

    装系统时用户的密码,设置root 密码。

    将guest关机,通过host端命令启动guest。

    3,Host端启动虚拟机命令参数如下:

    qemu-kvm –cpu host -m 1024 -enable-kvm

    -drive file=/var/lib/libvirt/images/zxc_linux1.img,cache=writeback,if=virtio -localtime

    -fsdev local,id=test_dev,path=/var/share,security_model=none

    -device virtio-9p-pci,fsdev=test_dev,mount_tag=testmount

    -vnc 0.0.0.0:0 -vga cirrus -monitor stdio

    启用9p的参数解释如下:

    -fsdev fsdriver,id=[id],path=[pathtoshare]

    ,security_model=[mapped|passthrough|none][,writeout=writeout]

    [,readonly][,socket=socket|sock_fd=sock_fd]

    -device virtio-9p-pci,fsdev=[id],mount_tag=[mount tag]

    Fsdriver: 该选项指定fs驱动端使用,目前支持”only”,”handle”,”proxy”文件 系统驱动。

    Id: 用来关联fsdev

    Path:host端用来共享的文件夹。

    Security_model:应该是共享权限,官方解释是:有效选项被映射,一种是透传模式,一种是none。Proxy文件系统驱动时不需要指定该选项。

    -device 指定设备virtio-9p-pci.

    Mount-tag:一个标识,用来在guest端mount时的tag。

    4,在host端创建要和guest端共享的文件夹

    Mkdir /var/share

    5,在客户端执行如下命令:

    mount -t 9p -o trans=virtio testmount /tmp/shared/ -oversion=9p2000.L,posixacl,cache=loose

    -t 标识mount 类型;

    -o 标识共享传输方式;

    Testmount是mount_tag;

    /tmp/shared 是guest端将要挂载到host端共享文件夹的目录。

    -oversion是标识9p的版本信息。

    展开全文
  • 身份认证的三种方法

    千次阅读 2017-03-15 10:39:40
    不同的身份验证方法安全性也各高低。 身份验证的目的是确认当前所声称为某种身份的用户,确实是所声称的用户。在日常生活中,身份验证并不罕见;比如,通过检查对方的证件,我们一般可以确信对方的身
  • 服务器配置https协议,三种免费的方法

    万次阅读 多人点赞 2018-05-30 12:58:42
    所以,分享一下我尝试过的三种方法。 1.Linux自签(OPENSSL生成SSL自签证书) 2.阿里云免费证书 3.Let’s Encrypt永久免费SSL证书【墙裂推荐】 一、Linux自签(OPENSSL生成SSL自签证书) ...
  • Set、Map、List三种集合的差别

    万次阅读 多人点赞 2019-05-30 16:15:27
    1.集合类型主要3:set(集)、list(列表)和map(映射)。 2.者关系 3.Set set接口时Collection接口的一个子接口,是无序的,set中不包含重复的元素,也就是说set中不存在两个这样的元素a1.equals(a2)结果为...
  • 需求挖掘的十三种方法

    万次阅读 2018-12-05 19:01:28
    不管是使用哪种交谈方式,都要注意做相应的记录,不遗漏每一个需求信息,这也为后面的需求分析提供一定的依据。使用自由一对一交流的方式的话,不是很方便不断地记录,这样会影响交谈的气氛。但是在结束交谈之后,也...
  • 一、检查系统密码文件,查看文件修改日期 # ls -l /etc/passwd 二、查看 passwd 文件中...、查看系统里没有空口令帐户 # awk -F: 'length($2)==0 {print $1}' /etc/shadow 四、检查系统守护进程 # cat
  • 那么以前实现这类功能的方法有,第一种方法最简单,就是把成员访问符从“private”改为“public”即可;而另一个就是提供公有的成员访问函数来进行访问。那么现在用C#编写程序,就不再需要采用前面所说的两...
  • 手机探测安检门的选择方法

    万次阅读 2019-05-20 16:14:35
    各位网友大家好 今天很高兴博睿勤小编为大家分享一下手机探测安检门的选择方法! 手机探测安检门也叫手机安检门 手机探测门 手机检测门 电子产品金属安检门 电子设备金属安检门 电子设备金属探测门等等各种与电子类...
  • C# 三种图像处理方法 耗时比较

    千次阅读 2014-03-19 12:24:57
    方法原理: 像素法(GDI+的getPixel等) 内存法(将锁定的图像区域复制到另一块内存上修改后再复制回去) 指针法(直接在锁定区域进行像素的指针操作) 运行一次耗时比较: 像素法:最慢的方法,耗时平均...
  • CentOS下svn迁移备份的三种方法

    千次阅读 2014-02-04 12:57:44
    一般采用三种方式: 1、svnadmin dump 2、svnadmin hotcopy 3)svnsync 注意,svn备份不宜采用普通的文件拷贝方式(除非你备份的时候将库暂停),如copy、rsync命令。 曾经用rsync命令来做增量和全量备份,在...
  • 检查空字符串的另一种方法是用正则表达式。这对于Java Bean验证来说非常方便: 给定的正则表达式会确保空字符串或空白符串无效。 六、使用Apache Commons 如果可以添加依赖项,我们可以使用 Apache Commons Lang...
  • 在网上找了好多种方法,有用的是以下三种(末尾附上网盘链接,都是我亲测可用的):1、gutouxiangcev2018.5:骨头下载器,以自己的QQ空间为例,可下载QQ空间内所有相册。未购买情况下,每个相册可下30张。注意下载时...
  • 它包含检查和更新这些绑定的方法。 Context context = null ; try { context = new InitialContext(); dataSource = (DataSource) context.lookup( "java:comp/env/jdbc/myDataSource" ); } catch ...
  • java 创建线程的三种方式、创建线程池的四方式

    万次阅读 多人点赞 2019-02-23 21:01:44
    java创建线程的三种方式: 继承Thread类创建线程类 实现Runnable接口 通过Callable和Future创建线程 java创建线程池的四方式: newCachedThreadPool 创建一个可缓存的线程池,如果线程池长度超过处理...
  • iOS安全之ipa 包重签名的3种方法

    万次阅读 2019-07-05 20:01:25
    重签名的意义:ipa 重签名最大的用处是,不必重新打包,和配置其它第方获取 appkey 等操作,直接重签名之后依然可以拥有这些功能,更快的发布测试或者灰度版本。 方法一、终端命令:sigh resign 1. 明白两个东西...
  • 通过几次调试后,找到以下几种方法,这些方法都参考网络各个介绍,并通过了本人的测试。希望能给大家减少IIS安装故障带来的烦恼。一般来说,对于此故障通常都发生在这么一情况,即windows xp ghost版本。很容易...
  • Iterator主要有三方法:hasNext()、next()、remove()详解

    万次阅读 多人点赞 2016-01-14 11:13:13
    一、Iterator的API 关于Iterator主要有三方法:hasNext()、next()、remove() hasNext:没有指针下移操作,只是判断是否存在下一个元素 next:指针下移,返回该指针所指向的元素 remove:删除当前指针所指向的元素,...
  • 在下载群文件的时候,偶尔会遇到上图的情况,明明是正经文件,却没有通过安全检查。解决方法如下: 第一步、首先在手机上找到该文件,点击下载的话提示我们禁止下载。 第二步、点击右上角的个点,找到 “收藏” 把...
  • 等多方面,介绍三种可以找回比特币钱包私钥的方法, 暴力破解 钱包找回服务公司(Wallet Recovery Services),这是一家线上公司,可以 恢复密码和修复损坏的加密货币钱包 。该公司的四名匿名员工使用假名Dave ...
  • 2020年科大讯飞X光安检图像识别前名队伍分享

    万次阅读 多人点赞 2020-12-04 17:52:01
    3.2.5 GridMask 3.2.6 Mixup 3.2.7 同比例缩放 3.2.8 SWA 3.2.9 WBF 3.3 实验结果 3.4 方案总结 3.5 后序优化思路 3.5 Q&A   评委点评:稍微一些可惜,没有用到无标注的数据,而后面两支队伍通过无监督的方法对...
  • final修饰符的三种使用场景

    千次阅读 2015-07-14 17:40:57
    final有三种使用场景,分别是修饰变量、方法和类,无论哪种修饰,一旦声明为final类型,你将不能改变这个引用了,编译器会检查代码,如果你试图再次初始化,编译器会报错。下面我来具体说说每一修饰场景。 1、...
  • 本文试图概述几种方法,程序员可用这几种方法来创建高效的线程安全类。 并发性 只有当要解决的问题需要一定程度的并发性时,程序员才会从多线程应用程序中受益。例如,如果打印队列应用程序仅支持一台打印机和...
  • 安全的http方法

    千次阅读 2019-09-06 01:14:40
    本章节所需工具burp和curl ... 漏洞简介 什么是http方法? 根据HTTP标准,HTTP请求可以使用...HTTP1.0定义了三种请求方法: GET、POST、HEAD HTTP1.1新增了五请求方法:OPTIONS、PUT、DELETE、TRACE 、CONNEC...
  • 在网上搜索了许多解决方法,主要: 第一种方法:打开 项目–&gt;属性–&gt;C/C++–&gt;常规–&gt;SDL检查,将默认的“是”改成“否”,然后重新跑一下程序,具体操作如下所示: 第二种方法:...
  • 常见防火墙的类型主要有三种:包过滤、电路层网关、应用层网关,每各自的优缺点。 包过滤是第一代防火墙技术,它按照安全规则,检查所有进来的数据包,而这些安全规则大都是基于底层协议的,如IP、TCP。...
  • Java多线程有哪实现方式? Java中的类如何保证线程安全? 请说明ThreadLocal的用法和适用场景 Java多线程有三种实现方式: (1)继承Thread类,重写run函数 (2)实现Runnable接口,重写run函数 开启线程:Thread ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 485,088
精华内容 194,035
关键字:

安全检查方法有哪三种