---
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開始接收檔案。