Vulnerability Research

COM-pletely Unplanned: A Windows Bug Hunter’s Journey to LPE

COM-pletely Unplanned: A Windows Bug Hunter’s Journey to LPE

EnkiWhiteHat

2025. 5. 12.

1. Introduction

Hello!

We are Jongseong Kim and Dongjun Kim security researchers at ENKI WhiteHat and Windows bug hunters. Since 2024 and into 2025, we’ve been conducting research focused on uncovering local privilege escalation vulnerabilities in Windows.

Our work mainly targeted services that run with SYSTEM or user-level privileges, as well as attack surfaces accessible from a restricted Windows AppContainer environment. Through this research, we ended up reporting a total of 10+ vulnerabilities to Microsoft.

In this blog post, we’d like to share the story of our “COM-pletely Unplanned” journey—how we chose which components to look at, how we approached them, and what kinds of interesting vulnerabilities we discovered along the way.

Our findings were also presented at Off-By-One 2025, a security conference hosted by StartLabs in Singapore on May 8–9, 2025.


1.1 Why Local Privilege Escalation Still Matters

First, let us explain why we became interested in Windows LPE. Windows is one of the most widely used operating systems in the world, and it has long been a major target for both attackers and security researchers. Because of its large codebase, complex architecture, and various permission models, Windows has a structure that can lead to many types of vulnerabilities.

Among them, privilege escalation vulnerabilities have been a high-priority research topic for a long time. They are often used not only to gain system-level access, but also to escape sandboxes.

Modern exploit chains often need to combine multiple vulnerabilities to cross security boundaries, rather than relying on a single bug. ️

1.2 Alternative Attack Vectors

For a long time, most local privilege escalation (LPE) vulnerabilities have centered around the Windows kernel—especially components like win32k, ALPC, ntoskrnl, and various Windows Device Drivers. These parts of the OS sit right at the boundary between user mode and kernel mode, making them relatively accessible to attackers. As a result, they’ve remained key targets for exploitation for years.

In response, Microsoft has patched countless vulnerabilities in these areas and introduced a range of security mitigations. As these defenses grow stronger, both attackers and security researchers are finding it increasingly difficult to uncover new bugs in these traditional vectors.

https://cloud.google.com/blog/topics/threat-intelligence/2024-zero-day-trends

Naturally, this has led to a shift: more researchers are exploring alternative paths for privilege escalation—routes that don't rely on the usual kernel-level attack surfaces. We, too, began with that same question in mind:

Could there still be overlooked privilege boundaries outside the traditional kernel attack vectors?

That question led us to focus on components that can be accessed from low-privileged contexts but internally execute with higher privileges than the caller. Specifically, we chose to look for targets with the following characteristics:

  1. Services that run with SYSTEM privileges

  2. Services that can be accessed even from low-privileged contexts, such as Normal Integrity users or AppContainers

With those conditions as our filter, we started analyzing the Windows architecture—and one particular structure caught our attention:

COM (Component Object Model).

We didn’t set out to study COM from the beginning. But during our exploration, we found that several services—especially those built on Windows Runtime and certain local system services—use COM to receive requests from low-privileged clients and process them in higher-privileged processes.

This small but powerful observation became the starting point for our journey. And so began our COM-pletely Unplanned journey—an unintentional but surprisingly fruitful deep dive into the world of COM-based privilege escalations.


2. COM

Many of the Windows features we use every day are built on COM.Windows Explorer, Microsoft Office, Windows Firewall, and the Speech API all run on top of COM. So, what exactly is COM?

2.1 Basic Concepts of COM

Before diving into COM vulnerabilities, we first needed to understand exactly what COM is and how it works. So let’s start with a quick overview together.

COM (Component Object Model) is a component-based software architecture developed by Microsoft. It allows objects to be created and interacted with across different processes or modules. One of COM’s biggest strengths is its language independence—developers can create and use COM objects in many different programming languages. COM provides a unified interface that can be accessed consistently from languages like C/C++, C#, PowerShell, Python, and more.

For example, here’s how you can use PowerShell and Python to automate Excel—demonstrating how COM enables language-agnostic access to Windows components:

But how is that possible?

COM uses CLSID (Class ID) and IID (Interface ID) to identify objects. The CLSID is a unique identifier for a COM class, and it’s registered in the system registry to define how and where the object can be created. The IID uniquely identifies each interface that the COM class provides. Using these identifiers, a client can create an object with a given CLSID and access its interfaces using the corresponding IID. Thanks to this mechanism, even if the COM object is called from different languages, they all interact with the same binary interface.

2.2 COM Server Models

So, if we can use Excel features through COM, like we showed earlier,how do we know where the COM object actually runs and what privileges it has? The way a COM server runs can be divided into three main types.

First, there is the In-Process Server model. This is called the InProcServer model, and it’s implemented as a DLL.When you use a COM object through InProcServer,it runs inside the same process as the client. Because of this, it operates with the same privileges as the client. Simply put, your process loads the DLL that contains the COM server and uses it directly.

Second, there is the Out-of-Process Server model. This is also called the LocalServer model, and it’s implemented as an EXE. Unlike the InProcServer the LocalServer runs in a separate process. Because of this, the client’s access is controlled by the security settings of the COM server. Most of the time, Windows hosts these COM servers inside svchost.exe, which runs background services. Since the client sends requests to another process, the LocalServer uses a Proxy and Stub. We’ll explain that part later.

Finally, there is the Remote Server model. A Remote Server runs on a different computer from the client application.It supports remote calls over the network by using Distributed COM. We will not cover this in detail in this post.

2.3 COM Launch/Access Permission

Alright. We have now briefly gone over the three models of COM servers. But let's stop for a moment and think. What would happen if any process could call an Out-of-Process Server or a Remote Server without any checks?

If a low-privileged user could freely launch a COM server running as SYSTEM, and freely call methods provided by its interface, this could lead to serious privilege escalation or even remote code execution. To prevent this, COM server performs two important security checks. It checks for Launch Permissions and Access Permissions.

2.3.1 Launch Permission

First, let’s talk about Launch Permission. Launch Permission checks whether a client application is allowed to start, or activate, a COM server.

For example, if a user has Launch Permission for a certain COM class, they can start the COM server by calling a function like CoCreateInstance.

Then, what is the difference between Launch Permission and Access Permission?

2.3.2 Access Permission

Access Permission checks whether a client can call methods on an already running COM server.

If the client has Access Permission,they can connect to the running COM server and call methods provided by its interfaces.

On the other hand,if the client has only Launch Permission but not Access Permission,they can activate the COM server, but they cannot access the object or call its methods.

2.4 COM Data Transfer

Now, we have covered the COM server models and the security model. Next, let’s take a look at how COM sends and receives data.

COM allows communication between objects inside a single process,by using the In-Process Server model we talked about earlier. However, in the case of an Out-of-Process Server or a Remote Server, the processes or systems are different. So, an extra step is needed to pass data between them. This is where Proxy/Stub and Marshalling come into play.

2.4.1 Proxy/Stub

Proxy and Stub work together as a pair to enable remote object calls. The Proxy acts as a helper on the client side. The Stub acts as a receiver on the server side. For example, when a client wants to call a method on a COM object in another process, it cannot call the remote object directly. Instead, it calls a Proxy object inside the client process. The Proxy marshals the necessary data and sends it to the server. Then, the Stub on the server side unmarshals the data and calls the actual method on the real object. The result from the server also travels back along the same path and is returned to the client. So, what exactly are marshalling and unmarshalling?

2.4.2 Marshalling/Unmarshalling

Marshalling is the process of converting data from one process, so that it can be used by another process. In COM, marshalling follows the rules defined in the IDL, it transforms both the interface and its parameters into a format that can be sent over RPC.

Unlike general serialization, COM marshalling includes not just simple data types, but also complex objects like interface pointers and runtime object.

Unmarshalling is the reverse process. It takes the marshalled data and reconstructs it into its original structure, so the receiving process can use it just like the original object. To work correctly, the unmarshalled data must match the original. For this reason both sides must follow the types and order defined in the IDL.

2.5 COM Threading Model

Finally, we will take a quick look at COM’s threading model before wrapping up this chapter.

In COM, the concept of an Apartment is used to define the execution context between threads, and to manage thread synchronization. COM objects use different apartment models depending on their specific needs.

In COM, there are two mainly threading models.

2.5.1 STA (Single-Threaded Apartment)

STA (Single-Threaded Apartment) is a model where only one thread belongs to an Apartment. A COM object inside an STA can only receive method calls from the single thread that belongs to that Apartment. Also, the COM Runtime ensures thread safety internally, so when implementing a COM interface in STA, you don’t need to handle thread synchronization yourself.

2.5.2 MTA (Multi-Threaded Apartment)

MTA (Multi-Threaded Apartment) allows multiple threads to run within an Apartment. COM objects inside an MTA can receive method calls from multiple threads at the same time. Because of this, the COM server must handle thread synchronization for the objects very carefully. If not properly handled, this can lead to issues like race conditions.

3. Learning from the Past

3.1 Prior Work

We have now covered the basic background of COM. Now, let’s talk about how we started looking for vulnerabilities in COM. Before jumping into vulnerability discovery, we first reviewed previous research on COM vulnerabilities. ️

We searched through academic papers, and we found two studies / that stood out to us.

The first one is "Detecting Union Type Confusion in Component Object Model," also known as COMFUSION.

The second is “COMRace: Detecting Data Race Vulnerabilities in COM Objects," and both were published at USENIX Security.

3.2.1 COMFUSION

First, let me introduce COMFUSION. The COMFUSION paper highlights that improper use of union types and VARIANT structures can lead to critical type confusion in COM. You might already be familiar with union types. But VARIANT structures might feel a bit less familiar.

A VARIANT is a structure that contains a vt field, which specifies the actual data type, and a second field / that holds the corresponding data. To use a VARIANT safely, the server must always check the vt field first, and then read the correct data field based on that type. If this step is skipped or implemented incorrectly, it can lead to a type confusion vulnerability. For example, if you store VT_LPSTR, which represents a char * type, in the vt field, and store a regular integer in the data field, the integer will be interpreted as a pointer, leading to an Arbitrary Address Read.

You might wonder: when sending requests to an Out-of-Process Server or a Remote Server, isn’t the data always interpreted correctly because of marshalling and unmarshalling?

The answer is no. Marshalling and unmarshalling follow the rules defined in the IDL, but they don’t validate the internal contents of a VARIANT structure. So if the COM server doesn’t properly check VARIANT structures, a type confusion vulnerability can still occur.

3.2.2 COMRace

Alright, now let’s take a look at the next paper — COMRace. The COMRace paper explains race conditions that can occur in COM objects running in an MTA model. It also describes how the researchers built a tool to detect these issues, and uncovered several vulnerabilities. One interesting finding from this research was that some of the vulnerabilities followed a specific pattern.

Here’s an example. This code shows the point where CVE-2020-1146 occurs. If you look closely, the put_AuthData method is called on the object, and it triggers the Set function of the HSTRING class. ️

Inside the Set function, the field stored in this is first released using WindowsDeleteString, and then re-assigned using WindowsDuplicateString.

At first glance, this code looks fine. However, in an MTA model, two threads can enter put_AuthData at the same time and both call WindowsDeleteString. This can lead to a Double-Free bug.

3.3 What We Learned and …

Through these two studies, we learned that type confusion can happen with VARIANT structures, and that race conditions can occur in the MTA model. We thought both papers were very impressive.

At first, we were ready to move on with these insights. But when we checked when the vulnerabilities were found, we noticed something interesting. Although more than 30 vulnerabilities were reported in the two papers, almost all of them were discovered around 2020.

Also, since the implementations in COMFUSION and COMRace were manually created, we began to wonder if there was something the earlier researchers might have missed.

So, in our own research, we mainly focused on the LocalServer model and the MTA threading model, where we expected to find new cases of Type Confusion and Race Condition. With that question in mind, we began our first COM bug hunting journey.

4. Discovered Vulnerabilities

From this point on, we will walk you through the vulnerabilities we discovered during our bug hunting journey. To give you a quick preview: by the end of this journey, we reported over 10 vulnerabilities to MSRC and successfully earned several CVEs.

4.1 Four Key Vulnerabilities

Next, we’ll introduce the four vulnerabilities we discovered.

  • Case 87975: Type Confusion in LxpSvc

  • CVE-2025-27475: Double Free Bug in InstallService

  • CVE-2024-49095: Use-After-Free in PrintWorkflowUserSvc

  • CVE-2025-21234: Improper Input Validation in PrintWorkflowUserSvc

4.1.1 Case 87975: Type Confusion in LxpSvc

Let's move on to the first vulnerability. This vulnerability is a type confusion issue that occurs in the Windows LxpSvc (Language Experience Service). The vulnerability is triggered in the SetLanguageOperationState method of an internal class called DeviceLanguageManager.

The SetLanguageOperationState method takes four parameters, and the fourth parameter is a pointer to a VARIANT structure provided by the user. At steps [1] and [2], the method stores the vt field (the type information of the VARIANT) and the associated data field into v12 and v12 + 0x8, respectively. Later, this data is passed as the third argument to the SetLanguageOperationStateInRegistry method, where it is used without proper validation.

In the SetLanguageOperationStateInRegistry method, if a2 is zero at step [1], the method stores the user-provided VARIANT data field into the data variable at step [2].

Then, the address of data is passed as the fifth argument to the SetRegValue function

So, what is the RegSetKeyValueW function? The RegSetKeyValueW function is used to create or modify a value under a specified registry key.

In other words, at this point, the Status registry key is updated by writing 4 bytes of data from the VARIANT's data field that we provided.

But doesn’t something seem strange here? If you think back to the code we’ve seen so far, you’ll notice that when a2 is zero in the SetLanguageOperationStateInRegistry method, there is no validation of the VARIANT's data type at all!

Now, what would happen if the VARIANT's type was something like BSTR, which holds a pointer instead of a simple integer? This is exactly where Type Confusion occurs. Instead of writing an integer value like 0 or 1, the lower 4 bytes of the pointer address are written into the registry key.

Since this registry path is readable from a user-level context, it allows an attacker to leak the lower 4 bytes of a pointer address.

Personally, I think this was a really interesting vulnerability. Although MSRC determined that the report did not meet their bar for immediate security servicing, they committed to addressing the issue in a future version

4.1.2 CVE-2025-27475: Double Free Bug in InstallService

The next vulnerability is a Double Free Bug found in the Windows Update Stack. The vulnerability occurs in the put_CrossGenSetId method of an internal class called FulfillmentDataInfo.

To trigger the vulnerable function, we first need to call the CreateFulfillmentData method of the InstallControl class, which will return an IFulfillmentDataInfo object. The condition for obtaining this object is very simple: At step [1], it checks whether the length of the string we provide as a2 is exactly 12. If the condition is met, the object is created at step [2] and returned to the client.

// Hidden C++ exception states: #wind=63
__int64 __fastcall WindowsUpdate::Internal::InstallControl::CreateFulfillmentData(
        WindowsUpdate::Internal::InstallControl *this,
        HSTRING a2,
        struct WindowsUpdate::Internal::IFulfillmentDataInfo **a3)
{
  HSTRING v5; // [rsp+38h] [rbp+10h] BYREF

  v5 = a2;
  if ( Utils::IsValidProductId(a2, a2) ) // [1] Verify that a2 is 0xC bytes 
    return Microsoft::WRL::Details::MakeAndInitialize<WindowsUpdate::Internal::FulfillmentDataInfo,WindowsUpdate::Internal::IFulfillmentDataInfo,HSTRING__ *>(
             a3,
             &v5); // [2] Return IFulfillmentDataInfo object
  *a3 = 0LL;
  return 2147942487LL;
}

Now, let's take a look at the put_CrossGenSetId method of the FulfillmentDataInfo object. At step [1], the method loads the HSTRING variable stored at this + 200 into v3. Then, at step [2], it frees the memory pointed to by v3. Wait.. doesn’t this look like a pattern we've seen before?

__int64 __fastcall WindowsUpdate::Internal::FulfillmentDataInfo::put_CrossGenSetId(
        WindowsUpdate::Internal::FulfillmentDataInfo *this,
        HSTRING a2)
{
  unsigned int v2; // ebx
  HSTRING *v3; // rdi

  v2 = 0;
  v3 = (HSTRING *)((char *)this + 200); // [1] Get HSTRING from this + 200
  if ( !a2 || a2 != *v3 )
  {
    WindowsDeleteString(*v3); // [2] Free HSTRING
    *v3 = 0LL;
    return (unsigned int)WindowsDuplicateString(a2, v3);
  }
  return v2;
}

This issue is very similar to CVE-2020-1146, which was previously introduced in COMRace paper. Even after five years, vulnerabilities following the same pattern still remain. This was the moment when our initial suspicions, based on prior work, turned into certainty — there are still many types of race condition vulnerabilities left to be discovered.

Windows::System::Internal::SignInContext::put_AuthData(*this, HSTRING a2) {
    Microsoft::WRL::Wrappers::HString::Set(this + 15, &a2);
}

__int64 HString::Set(HSTRING *newString, HSTRING *a2) {
    unsigned int v2;
    v2 = 0;
    if(!*a2 || *a2 != *newString) {
        WindowsDeleteString(*newString); // Delete
        *newString = 0;
        v2 = WindowsDuplicateString(*a2, newString);
    }
}

4.1.3 CVE-2024-49095: Use-After-Free in PrintWorkflowUserSvc

To continue hunting for race condition vulnerabilities, we turned our attention to PrintWorkflowUserSvc, the service responsible for managing the Windows Print Workflow. In this service, we were able to discover several vulnerabilities. Among them, we will introduce two of the most interesting cases.

The first vulnerability is a Use-After-Free issue. It occurs in the SetPrintTicket method of the CWorkflowSession object.

Looking at the code, we can see that *((_QWORD *)this + 10) holds a heap address that was allocated during a previous call to SetPrintTicket. However, because the CWorkflowSession COM interface belongs to the MTA (Multi-Threaded Apartment) model, the object can be accessed concurrently by different threads.

We attempted to exploit this vulnerability, but due to several constraints, we were not able to achieve a successful exploitation.

However, based on our analysis, we believe that with certain techniques and ideas, exploitation could be conceptually possible.

First, let's talk about bypass ASLR. In the case of ASLR, if the attacker-controlled process and the higher-privileged process load the same DLL, the DLL will be mapped at the same virtual address in both processes. This allows the attacker to infer a sufficient amount of address information.

This idea forms the fundamental basis for many LPE and sandbox escape techniques using IPC mechanisms.

Second, we needed a leak some heap addresss. Since the CWorkflowSession object exposes several other methods, I checked whether any of them could access the this + 10 address.

Eventually, I found a function that does exactly that. By using the GetPrintTicket method, we can copy the value located at this + 10 — which may already have been freed — into a output buffer. In other words, we can leak useful heap addresses through the following scenario.

First, we need a of three threads. Each thread will call different COM interface methods, as illustrated in the diagram. In Thread 2, if a value already exists at this + 10, calling SetPrintTicket will free the heap memory that was previously allocated there. Then, before a new allocation occurs, Thread 3 may allocate an object — such as one containing a vtable or any heap address — into the freed space.

After that, when Thread 1 calls GetPrintTicket, it will reference the this + 10 address, effectively leaking the value from the victim object back to the attacker. Since the size field for heap allocation can be fully controlled through SetPrintTicket, we can reliably place a victim object into the freed hole within the race window, without any size constraints.

Next, we move on to RIP control. Again, three threads are used. Unlike the previous leak scenario, here two of the threads start a race by both calling SetPrintTicket.

First, if this + 10 is not NULL, SetPrintTicket will free the heap memory stored there. During this window, an attacker can allocate an arbitrary object to occupy the freed hole. After that, the attacker can overwrite the victim object with fully controlled data and size. If the victim object contains a vtable, this can lead to successful control flow hijacking.

Even though we came up with these ideas, we were not able to develop a fully working exploit. We encountered several challenges:

First, even if we successfully allocated a victim object into the freed space through the race condition and managed to overwrite it, it was extremely difficult to avoid a double free situation.

Second, because both threads must call the same interface to trigger the race, and the race window is extremely narrow, precise control over the timing was very difficult to achieve.


4.1.4 CVE-2025-21234: Improper Input Validation in PrintWorkflowUserSvc

Now, we would like to introduce the final vulnerability. Just like the previous cases, this vulnerability was also found in PrintWorkflowUserSvc.

Let's take a closer look at the RequestSpoolingHandlesForWrite method of the IPrintSupportSourceSession class. This method takes six parameters, from a2 to a7, and all of them are used as output parameters. Now, let me ask you:When you're hunting for bugs, do you usually bother analyzing functions that have no input parameters at all? Our answer was no — at least, until we discovered this vulnerability.

In this function, at step [1], the address of the WorkflowSessionCommon's vftable, which is stored in this, is loaded into v9. Then, using this address as an argument, the RequestSpoolingHandlesForWrite method of the WorkflowSessionCommon class is called at step [2].

In this method, at step [1], it retrieves the client's PID (Process ID). At step [2], it obtains a handle to the client's process. Then, at step [3], it references this again through v22 and stores some value. Finally, at step [4], it calls the DuplicateHandle function. Now it's time to see what Duplicatehandle function really works.

The DuplicateHandle function is a Windows API that duplicates a handle to an object. To briefly summarize the function arguments: the first and second parameters are the handle to the source process and the handle to be duplicated, respectively. The third parameter is the handle to the target process that will receive the duplicated handle, and the fourth parameter is where the newly duplicated handle will be returned. According to the MSDN documentation, this operation effectively means that the handle from v22 inside PrintWorkflowUserSvc is duplicated into the client process. While this pattern can sometimes be seen in COM, it is usually used to return event handles to the client process for implementing callbacks or method notifications.

It was confirmed by simple reverse engineering that the v22 value was actually set where. Through analysis, we found that it is initialized in the constructor of the PrintSupportSession class. The v22 value is set to -1. In Windows, a handle value of -1 represents a pseudo-handle that points to the current process itself.

This means that in the DuplicateHandle call, the handle of the PrintWorkflowUserSvc process is duplicated into the client process. In other words, if a lower-privileged client calls this method, it can successfully duplicate the PrintWorkflowUserSvc process handle into its own process.

Next, we checked which processes could access PrintWorkflowUserSvc by reviewing its access permissions. As a result, we found that this COM object is accessible not only by all UWP apps, but also by low-privileged AppContainer processes that have specific capabilities. This means that even a sandboxed process can duplicate the PrintWorkflowUserSvc process handle into its own process.

This vulnerability is truly a perfect logic bug. There is no need to exploit the complex Use-After-Free vulnerabilities we discussed earlier to achieve privilege escalation. Now, by using the duplicated process handle, we can write shellcode into the memory space of PrintWorkflowUserSvc and execute it by calling the CreateRemoteThread API.

Now it’s time for the demo. To demonstrate the vulnerability, we assumed a compromised renderer in the Adobe Acrobat sandbox and performed a sandbox escape by injecting shellcode, achieving a privilege escalation from AppContainer to Medium integrity level.

Surprisingly, the bug was declared "Exploit Less Likelly." We still don't know why the MSRC made this decision :D

5. Results

Finally, here’s our conclusion. From 2024 until now, we have reported more than 10 vulnerabilities to Microsoft, including Type Confusion and Race Condition issues, and we successfully earned CVEs for our findings.

These achievements were made possible thanks to the information shared by previous researchers and the availability of outstanding tools like oleviewdotnet. We would especially like to express our deep gratitude to the earlier researchers and to James Forshaw, who laid the foundation that allowed us to begin our journey into COM vulnerability research.

Finally, before we close, we would like to share some reflections from our journey.

We believe that a thorough analysis of prior research is essential when trying to discover new vulnerabilities in a new domain. It is important not to simply follow existing research, but to focus on the areas that were not fully explored, and to seek out possibilities for further expansion.

Of course, there were some regrets as well. Although we were able to discover several race condition vulnerabilities, we were not able to achieve complete exploitation in some cases — and we recognize this as an area we need to improve moving forward.

For our future work, we aim to develop a systematic tool for detecting these types of vulnerability patterns, and through it, discover even more vulnerabilities. Our journey is far from over — it will continue 😄

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.