---
# System prepended metadata

title: 07_DECQUEUE

---

---
title : 07_DECQUEUE
---
# DECQUEUE程式說明
by : CY, Tsai
Date : 2021/10/15

---

## 壹、簡介
這個程式主要用來解密檔案，但是不同於一般的解密，因為要與視窗進行訊息傳遞，解密後的檔名要傳到視窗顯示，如果用解一個檔案就傳一次檔名給視窗的方式，會浪費很多時間。從訊息傳遞就可以知道，視窗與解密程式是兩個不同的執行續，所以如果用上述的方式在執行續之間頻繁的切換很浪費時間。DECQUEUE的主要作用就是使用QUEUE，設定QUEUE的容量是64，也就是最多可以掃描64個檔案，當然QUEUE裡面放的是檔名，不是檔案，節省不必要的空間浪費。而視窗一樣可以從QUEUE取出檔名，只要QUEUE不為空。

## 貳、標頭檔
```cpp=
#pragma once
#include <Windows.h>
#include <tchar.h>
#include <iostream>

#define DECQUEUE_SCANONLY

#ifndef DECQUEUE_SCANONLY
#include "../WannaTry/WanaDecryptor.h"
#endif

// queue裡面的最大檔案數量
#define MAXQUEUE 64  
// 初始狀態
#define IDC_DECQUEUE_NONE 0 
// STOP 跟 START 沒有用到
#define IDC_DECQUEUE_START 1
#define IDC_DECQUEUE_STOP 2  // 開始解密
// 當結束整個掃描後會傳送訊息給視窗
#define IDC_DECQUEUE_DONE 3  
// 執行續讀取檔案後用來傳遞訊息
#define IDC_DECQUEUE_DATA 4 
// 傳遞檔名和檔案屬性
struct _FILEINFO {
    TCHAR m_szName[MAX_PATH + 1];
    DWORD m_dwFileAttributes;
};

class DECQUEUE {
private:
    HANDLE m_hStopEvent;    // 事件
    HANDLE m_hFillCount = NULL;  // 號誌1
    HANDLE m_hEmptyCount = NULL; // 號誌2
    CRITICAL_SECTION m_CriticalSection;  // 臨界區域
    _FILEINFO m_aFileInfo[MAXQUEUE];  // 檔名的queue
    // queue head
    int m_iHead;
    // queue tail
    int m_iTail;
    // 數量計算
    int m_nCount;
    // 目前狀態
    int m_Status;
    // 接收指令
    int m_Command;

#ifndef DECQUEUE_SCANONLY
    PWanaDecryptor m_pDecryptor;
#endif
    HWND m_hWnd;
public:
    // 解密起始目錄
    LPCTSTR m_Start;  

    DECQUEUE(HWND);
    ~DECQUEUE();
    // 檔名入queue
    void SendData(LPCTSTR, DWORD);
    // 對話框從queue接收檔名
    BOOL RecvData(TCHAR*, PDWORD);
    // 掃描目錄
    BOOL Traverse(LPCTSTR, DWORD, DWORD);
    // 對話框停止傳送訊
    void Stop();
    // 檢查對話框是否傳來停止訊息
    BOOL CheckStopEvent();
};

typedef DECQUEUE* PDECQUEUE;

DWORD WINAPI DecQueueThread(LPVOID);
```
標頭檔有定義一個巨集DECQUEUE_SCANONLY，用來測試用，如果使用這個巨集就只會掃描目錄，不會進行解密。另外定義幾個狀態，IDC_DECQUEUE_NONE、IDC_DECQUEUE_START、IDC_DEQUEUE_STOP等，用來代表初始狀態、開始加密、暫停加密等等。 \_FILEINFO結構裡面有黨名與檔案屬性，放入檔案屬性是要判斷是否為目錄。<br>
在31~45行是在定義DEQUEUE會用到的變數，其中開頭三個HANDLE分別為事件與兩個號誌。特別提出來的原因是視窗與解密程式這樣的關系其實就是常見的"消費者生產者問題"，這邊是參考維基百科的解法，使用兩個號誌解決同步問題。另外有定義資料傳送、接收檔名、掃描目錄等函數。

## 參、類別實作
### 01. 建構子
```cpp=4
DECQUEUE::DECQUEUE(HWND hWnd = NULL)
{
    // 產生事件
    m_hStopEvent = CreateEvent(
        NULL,
        0,
        FALSE,
        NULL);
    // 產生號誌
    m_hFillCount = CreateSemaphore(
        NULL,
        0,
        MAXQUEUE,
        NULL);
    // 產生號誌
    m_hEmptyCount = CreateSemaphore(
        NULL,
        MAXQUEUE,
        MAXQUEUE,
        NULL);
    // 初始化臨界區域
    InitializeCriticalSection(&m_CriticalSection);
    m_iHead = 0;
    m_iTail = 0;
    m_nCount = 0;
    // 因為還沒開始掃描，所以放NONE
    m_Status = IDC_DECQUEUE_NONE;
    m_Command = IDC_DECQUEUE_NONE;

#ifndef DECQUEUE_SCANONLY
    m_pDecryptor = new WanaDecryptor();
#endif
    m_hWnd = hWnd;
}
```
建構函數基本上就只有初始化變數而已，然後產生停止事件、兩個Semaphore與臨界區域。

### 02. 解構子
```cpp=39
DECQUEUE::~DECQUEUE()
{
    if (m_hFillCount) {
        CloseHandle(m_hFillCount);
    }
    if (m_hEmptyCount) {
        CloseHandle(m_hEmptyCount);
    }
    if (m_hStopEvent) {
        CloseHandle(m_hStopEvent);
    }
    DeleteCriticalSection(&m_CriticalSection);
#ifndef DECQUEUE_SCANONLY
    delete m_pDecryptor;
#endif
}
```
解構函數關閉所有HANDLE與臨界區域。

### 03. 信息傳送
```cpp=56
void DECQUEUE::SendData(
    LPCTSTR pName,
    DWORD dwFileAttributes)
{
    // 等待queue空位
    WaitForSingleObject(m_hEmptyCount, INFINITE);
    // 進入臨界區域，一次只能一個執行續進入臨界區域
    EnterCriticalSection(&m_CriticalSection);

    m_aFileInfo[m_iHead].m_szName[0] = 0;
    m_aFileInfo[m_iHead].m_dwFileAttributes = 0;
    if (!pName) {  // 如果檔名為NULL代表掃描結束
        m_Status = IDC_DECQUEUE_DONE;
    }
    else if (m_nCount < MAXQUEUE) {
        m_Status = IDC_DECQUEUE_DATA;
        if (pName) {
            _tcscpy_s(m_aFileInfo[m_iHead].m_szName,
                MAX_PATH,
                pName);
        }
        m_aFileInfo[m_iHead].m_dwFileAttributes =
            dwFileAttributes;
        m_iHead = (m_iHead + 1) % MAXQUEUE;
        m_nCount++;
    }
    LeaveCriticalSection(&m_CriticalSection);
    ReleaseSemaphore(m_hFillCount, 1, NULL);
    if (m_hWnd) {
        SendMessage(m_hWnd, WM_USER, m_Status, NULL);
    }
}
```
首先要等待m_hEmptyCount信號才能進入CriticalSection。然後這邊比較特別的是使用環狀隊列，所以m_iHead的前進與m_iTail後退都要取MAXQUEUE的餘數。

### 04. 接收資料
```cpp=89
BOOL DECQUEUE::RecvData(
    TCHAR* pName,
    PDWORD pdwFileAttributes)
{
    WaitForSingleObject(m_hFillCount, INFINITE);
    EnterCriticalSection(&m_CriticalSection);
    BOOL bResult = TRUE;
    if (m_Status == IDC_DECQUEUE_DONE) {
        bResult = FALSE;
    }
    if (m_nCount > 0) {
        if (pName) {
            _tcscpy_s(pName,
                MAX_PATH,
                m_aFileInfo[m_iTail].m_szName);
        }
        if (pdwFileAttributes) {
            *pdwFileAttributes =
                m_aFileInfo[m_iTail].m_dwFileAttributes;
        }
        m_iTail = (m_iTail + 1) % MAXQUEUE;
        m_nCount--;
    }
    LeaveCriticalSection(&m_CriticalSection);
    ReleaseSemaphore(m_hEmptyCount, 1, NULL);
    return bResult;
}
```
接收資料也要等待號誌，只不過是m_hFillCount，內容跟傳送信號差不多，只不過一個是要倒退一個是前進而已。

### 05. 掃描目錄
```cpp=117
BOOL DECQUEUE::Traverse(
    LPCTSTR pPath,
    DWORD dwAttributes = 0,
    DWORD nLevel = 0)
{
    BOOL bResult = TRUE;
    if (CheckStopEvent()) {
        return FALSE;
    }
    if (!dwAttributes) {
        dwAttributes = GetFileAttributes(pPath);
        if (dwAttributes == INVALID_FILE_ATTRIBUTES) {
            return TRUE;
        }
    }
    if (!(dwAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
#ifndef DECQUEUE_SCANONLY
        LPCTSTR pSuffix = _tcsrchr(pPath, _T('.'));
        if (pSuffix) {
            if (!_tcsicmp(pSuffix, WZIP_SUFFIX_CIPHER) ||
                !_tcsicmp(pSuffix, WZIP_SUFFIX_WRITESRC)) {
                m_pDecryptor->Decrypt(pPath);
                SendData(pPath, dwAttributes);
            }
            // 刪除解密站存檔
            else if (!_tcsicmp(pSuffix, WZIP_SUFFIX_TEMP)) {
                DeleteFile(pPath);
            }
        }
#else
        SendData(pPath, dwAttributes);
#endif
    }
    else {
        SendData(pPath, dwAttributes);
        TCHAR szFullPath[MAX_PATH + 1];
        WIN32_FIND_DATA FindFileData;
        _stprintf_s(szFullPath, _T("%s\\*.*"), pPath);
        HANDLE hFind = FindFirstFile(
            szFullPath,
            &FindFileData);
        if (INVALID_HANDLE_VALUE == hFind) {
            return TRUE;
        }
        do {
            if (!_tcscmp(FindFileData.cFileName, _T(".")) ||
                !_tcscmp(FindFileData.cFileName, _T(".."))) {
                continue;
            }
            _stprintf_s(szFullPath,
                _T("%s\\%s"),
                pPath,
                FindFileData.cFileName);
            bResult = Traverse(
                szFullPath,
                FindFileData.dwFileAttributes,
                nLevel + 1);
        } while (bResult &&
            FindNextFile(hFind, &FindFileData) != 0);
        FindClose(hFind);
    }
    if (nLevel <= 0) {
        SendData(NULL, 0);
    }
    return bResult;
}
```
掃描會用到檔案、目錄處理、檔案尋找的相關函數，[說明在這篇文章裡](https://hackmd.io/@hack-Ransomware/H1fIE1_Ku)。<br>
另外判斷那個路徑是目錄還是檔案可以用檔案屬性來看，如果是目錄，檔案屬性就會是FILE_ATTRIBUTE_DIRECTORY。

### 06. 停止相關函數
```cpp=184
void DECQUEUE::Stop()
{
    SetEvent(m_hStopEvent);
}

BOOL DECQUEUE::CheckStopEvent()
{
    DWORD retval = WaitForSingleObject(m_hStopEvent, 0);
    if (WAIT_OBJECT_0 == retval) {
        return TRUE;
    }
    return FALSE;
}
```
用來結束掃描用的，沒有特別的內容。

### 07. 解密程式
```cpp=198
#ifndef ENCRYPT_ROOT_PATH
#define ENCRYPT_ROOT_PATH _T("C:\\")
#endif

DWORD WINAPI DecQueueThread(
    _In_ LPVOID lpParameter
)
{
    PDECQUEUE pQueue = (PDECQUEUE)lpParameter;
    BOOL bResult = TRUE;
    if (pQueue->m_Start) {
        bResult = pQueue->Traverse(pQueue->m_Start);
    }
    else {
        TCHAR szRootPathName[16] = ENCRYPT_ROOT_PATH;
        for (INT DiskNO = 25; DiskNO >= 0; DiskNO--) {
            DWORD Drives = GetLogicalDrives();
            if ((Drives >> DiskNO) & 1) {
                szRootPathName[0] = DiskNO + 65;
                bResult = pQueue->Traverse(szRootPathName);
                if (!bResult) {
                    break;
                }
            }
        }
    }
    ExitThread(0);
}
```
這是解密的主程式，使用上面建構的函數來掃描目錄，另外有用到一個函數GetLogicalDrives()是用來取的磁碟機的。


### 08. main函數
```cpp=227
#ifdef DECQUEUE_SCANONLY
int main()
{
    PDECQUEUE pQueue = new DECQUEUE();
    HANDLE hThread;
    TCHAR szFileName[MAX_PATH + 1];
    DWORD dwAttributes;
    hThread = CreateThread(NULL, 0, DecQueueThread, pQueue, 0, NULL);
    int i = 0;
    while (TRUE) {
        if (!pQueue->RecvData(szFileName, &dwAttributes)) {
            break;
        }
        _tprintf(_T("%d Recv %s\n"), i, szFileName);
        i++;
        if (i >= 100) {
            pQueue->Stop();
        }
    }
    return 0;
}
#endif
```
main函數建立一個執行緒，指定給函數DecQueueThread。然後使用while loop開始接收檔案。
