Background

Lately, my focus has been on discovering any potential vulnerabilities in KEPServerEX. KEPServerEX is the industry’s leading connectivity platform that provides a single source of industrial automation data to all your applications. Users can connect, manage, monitor, and control diverse automation devices and software applications through one intuitive user interface.

This software employs multiple anti-debugging measures, making it challenging to discover any vulnerabilities and performing fuzzing on it. In this regard, I would like to share my perspective on the issue and my strategy for circumventing these measures.

Anti-debuger and anti attach

KEPServerEX comes with several default services, as shown in the image below:

1

However, when attempting to attach a debugger to the server_runtime.exe process, it crashes instantly.

To identify the cause of the crash, I configured Windows to collect a crash dump of the process. You can learn more about how to do this here.

The collected crash dump provided me with a stack trace, which helped to identify the instructions responsible for the crash.

(185c.1460): Security check failure or stack buffer overrun - code c0000409 (first/second chance not available)
For analysis of this file, run !analyze -v
eax=00000001 ebx=028af068 ecx=00000007 edx=000001e9 esi=00000003 edi=6bde5a94
eip=76caeddb esp=028aee38 ebp=028aee4c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ucrtbase!abort+0x4b:
76caeddb cd29            int     29h
0:000> k
 # ChildEBP RetAddr  
00 028aee4c 76cae17b ucrtbase!abort+0x4b
01 028aee7c 6d9f5ee4 ucrtbase!terminate+0x3b
02 028aeef4 6d9f6405 VCRUNTIME140!FindHandler<__FrameHandler3>+0x13c [D:\a01\_work\4\s\src\vctools\crt\vcruntime\src\eh\frame.cpp @ 508] 
03 028aef28 6d9fe416 VCRUNTIME140!__InternalCxxFrameHandler<__FrameHandler3>+0xf7 [D:\a01\_work\4\s\src\vctools\crt\vcruntime\src\eh\frame.cpp @ 350] 
04 028aef64 76fc87a2 VCRUNTIME140!__CxxFrameHandler2+0x26 [D:\a01\_work\4\s\src\vctools\crt\vcruntime\src\eh\i386\trnsctrl.cpp @ 214] 
05 028aef88 76fc8774 ntdll!ExecuteHandler2+0x26
06 028af050 76fb4e96 ntdll!ExecuteHandler+0x24
07 028af050 761ea6e2 ntdll!KiUserExceptionDispatcher+0x26
08 028af570 6d9f7586 KERNELBASE!RaiseException+0x62
09 028af5a0 6e9e10fd VCRUNTIME140!_CxxThrowException+0x66 [D:\a01\_work\4\s\src\vctools\crt\vcruntime\src\eh\throw.cpp @ 74] 
WARNING: Stack unwind information not available. Following frames may be wrong.
0a 028af5e4 6bda4d6b libplatform!kepplatform::CException::Throw+0x1d
0b 028af618 6bdb74f3 thingworxinterface!QueryInterface+0x34b
0c 028af644 6bdcf882 thingworxinterface!QueryInterface+0x12ad3
0d 028af688 76c3a9ac thingworxinterface!QueryInterface+0x2ae62
0e 028af6bc 76c3a97a ucrtbase!__crt_seh_guarded_call<int>::operator()<<lambda_69a2805e680e0e292e8ba93315fe43a8>,<lambda_f03950bc5685219e0bcd2087efbe011e> &,<lambda_03fcd07e894ec930e3f35da366ca99d6> >+0x30
0f 028af6dc 6bdb9a3f ucrtbase!_execute_onexit_table+0x2a
10 028af71c 6bdba374 thingworxinterface!QueryInterface+0x1501f
11 028af728 6bdba5e5 thingworxinterface!QueryInterface+0x15954
12 028af768 6bdba683 thingworxinterface!QueryInterface+0x15bc5
13 028af77c 76fb2946 thingworxinterface!QueryInterface+0x15c63
14 028af79c 76f8dcf2 ntdll!LdrxCallInitRoutine+0x16
15 028af7e8 76f9d795 ntdll!LdrpCallInitRoutine+0x51
16 028af880 76f9d685 ntdll!LdrShutdownProcess+0xf5
17 028af950 75d14112 ntdll!RtlExitUserProcess+0xb5
18 028af964 6dfa75ae kernel32!ExitProcessImplementation+0x12
19 028af9a4 6dfa70d2 libserver!kepserver::CUniqueId::operator=+0xa07e // [1]
1a 028af9d0 6ea2362e libserver!kepserver::CUniqueId::operator=+0x9ba2
1b 028af9e4 6ea237f1 libthread!kepthread::CKEPThread::_ThreadProc+0x4e
1c 028afa0c 76c54f9f libthread!kepthread::CKEPThread::_ThreadProc+0x61
1d 028afa44 75d0fa29 ucrtbase!thread_start<unsigned int (__stdcall*)(void *),1>+0x3f
1e 028afa54 76fa7a4e kernel32!BaseThreadInitThunk+0x19
1f 028afab0 76fa7a1e ntdll!__RtlUserThreadStart+0x2f
20 028afac0 00000000 ntdll!_RtlUserThreadStart+0x1b

Upon analyzing the stack trace, I have observed that libserver.dll is responsible for calling exit process and generating an exception. By examining the address mentioned in [1], it has been confirmed that it belongs to the libserver!sub_10087540 function as shown below.

void __noreturn sub_10087540()
{
  NTSTATUS (__stdcall *v0)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); // esi
  HANDLE CurrentProcess; // eax
  NTSTATUS (__stdcall *v2)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); // esi
  HANDLE v3; // eax
  NTSTATUS (__stdcall *v4)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG); // esi
  HANDLE v5; // eax
  int v6; // [esp+10h] [ebp-24h] BYREF
  int v7; // [esp+14h] [ebp-20h] BYREF
  int ProcessInformation; // [esp+18h] [ebp-1Ch] BYREF
  CPPEH_RECORD ms_exc; // [esp+1Ch] [ebp-18h]

  v0 = NtQueryInformationProcess;
  if ( NtQueryInformationProcess )
  {
    ProcessInformation = 0;
    CurrentProcess = GetCurrentProcess();
    if ( !v0(CurrentProcess, ProcessDebugPort, &ProcessInformation, 4, 0) && ProcessInformation )
      ExitProcess(1u);
    v7 = 0;
    v2 = NtQueryInformationProcess;
    v3 = GetCurrentProcess();
    if ( !v2(v3, ProcessDebugObjectHandle, &v7, 4, 0) && v7 )
      ExitProcess(2u);
    v6 = 1;
    v4 = NtQueryInformationProcess;
    v5 = GetCurrentProcess();
    if ( !v4(v5, ProcessDebugFlags, &v6, 4, 0) && !v6 )
      ExitProcess(3u);
  }
  ms_exc.registration.TryLevel = 0;
  __asm { int     2Dh; Windows NT - debugging services: eax = type }
  ExitProcess(4u);
}

Upon initial examination, it is apparent that the following methods are employed:

  • The utilization of the function ntdll!NtQueryInformationProcess with ProcessInformationClass ProcessDebugPort, ProcessDebugObjectHandle, ProcessDebugFlags for the purposes of anti-debugging. You can read more with regards to this trick here for better understanding. For more information on other Anti-Debug tricks from CheckPoint, do check it out here.
  • The implementation of the instruction int 2D in order to check to if a debugger is attached to the current process.

First attempt at Bypassing of Checks

To fix this function, I plan to simply patch it by removing the bottom checks.

xor eax, eax 
ret

I begin by patching the start of the libserver!sub_10087540 function with xor and ret.

.text:10087540 sub_10087540    proc near               ; CODE XREF: sub_100836A0+27p
.text:10087540                                         ; sub_10084080+45p ...
.text:10087540                 xor     eax, eax // patch
.text:10087542                 retn
.text:10087542 sub_10087540    endp
.text:10087542
.text:10087542 ; ---------------------------------------------------------------------------
.text:10087543                 db  6Ah ; j
.text:10087544                 db 0FEh

Checking of file signature

After patching libserver.dll, I attempted to restart the KEPServerEX service runtime. Unfortunately, the process failed to start, as it appeared to be checking something and exiting immediately.

In order to gain more information, I utilized Procmon to observe the behavior of the process upon startup.

Based on the fact that the process failed to start after updating libserver.dll, it is likely that it was checking the hash of the files.

2

To further investigate, I filtered the Process Name to server_runtime.exe and set the operation to ReadFile. This allowed me to observe that the process was reading various dll and exe files within the installation directory.

By examining an event, I was able to see the stack trace indicating that the process was utilizing WinVerifyTrust to check the files in the installation directory.

3

Specifically, I analyzed the code at server_runtime.exe + 0x34101.

int __thiscall sub_33000(int this, int a2)
{
  // [COLLAPSED LOCAL DECLARATIONS. PRESS KEYPAD CTRL-"+" TO EXPAND]

  v37 = a2;
  pCertContext = 0;
  p_pCertContext = &pCertContext;
  v31 = 0;
  if ( *(_DWORD *)(this + 20) || (v3 = sub_333E0(), v3 >= 0) )
  {
    v33.pcwszFilePath = *(LPCWSTR *)(this + 4);
    pWVTData.pFile = &v33;
    v33.cbStruct = 16;
    v33.hFile = 0;
    v33.pgKnownSubject = 0;
    pgActionID.Data1 = 0xAAC56B;
    *(_DWORD *)&pgActionID.Data2 = 0x11D0CD44;
    *(_DWORD *)pgActionID.Data4 = 0xC000C28C;
    *(_DWORD *)&pgActionID.Data4[4] = 0xEE95C24F;
    v35 = 0;
    pWVTData.cbStruct = 52;
    pWVTData.pPolicyCallbackData = 0;
    pWVTData.pSIPClientData = 0;
    pWVTData.dwUIChoice = 2;
    pWVTData.fdwRevocationChecks = 0;
    pWVTData.dwUnionChoice = 1;
    pWVTData.dwStateAction = WTD_STATEACTION_VERIFY;
    pWVTData.hWVTStateData = 0;
    pWVTData.pwszURLReference = 0;
    pWVTData.dwProvFlags = 16;
    pWVTData.dwUIContext = 0;
    v4 = (kepplatform *)WinVerifyTrust(0, &pgActionID, &pWVTData); // [2]
    v3 = (int)v4;
    v36 = this + 8;
    if ( (int)v4 < 0 )
    {
      v5 = kepplatform::XlatErrorCode(v4);
      v6 = ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>(
             v40,
             v5);
      ATL::CStringT<wchar_t,StrTraitMFC_DLL<wchar_t,ATL::ChTraitsCRT<wchar_t>>>::operator=(this + 8, v6);
      LOBYTE(v31) = 0;
      CMFCRibbonInfo::XID::~XID(v40);
      pWVTData.dwStateAction = WTD_STATEACTION_CLOSE;
      WinVerifyTrust(0xFFFFFFFFh, &pgActionID, &pWVTData);
      goto done;
    }
    pWVTData.dwStateAction = WTD_STATEACTION_CLOSE;
    WinVerifyTrust(0xFFFFFFFFh, &pgActionID, &pWVTData);
    ...
    ...
  }
  ...
  ...
}

Upon examination of the code provided, it is evident that the WinVerifyTrust function is invoked at [2] and checks the return value. According to MS, If the trust provider verifies that the subject is trusted for the specified action, the return value is zero. No other value besides zero should be considered a successful return. As a result, modifying the return value of WinVerifyTrust to zero is sufficient to bypass the signature check and proceed with the intended operation.

Second attempt at Bypassing of Checks

Observe the ASM code:

.text:000330A8                 mov     [ebp+70h+var_2C], 0
.text:000330AF                 mov     [ebp+70h+pWVTData.cbStruct], 34h ; '4'
.text:000330B6                 mov     [ebp+70h+pWVTData.pPolicyCallbackData], 0
.text:000330BD                 mov     [ebp+70h+pWVTData.pSIPClientData], 0
.text:000330C4                 mov     [ebp+70h+pWVTData.dwUIChoice], 2
.text:000330CB                 mov     [ebp+70h+pWVTData.fdwRevocationChecks], 0
.text:000330D2                 mov     [ebp+70h+pWVTData.dwUnionChoice], 1
.text:000330D9                 mov     [ebp+70h+pWVTData.dwStateAction], 1
.text:000330E0                 mov     [ebp+70h+pWVTData.hWVTStateData], 0
.text:000330E7                 mov     [ebp+70h+pWVTData.pwszURLReference], 0
.text:000330EE                 mov     [ebp+70h+pWVTData.dwProvFlags], 10h
.text:000330F5                 mov     [ebp+70h+pWVTData.dwUIContext], 0
.text:000330FC                 call    WinVerifyTrust
.text:00033101                 mov     esi, eax // [3] =>> patch xor esi, esi
.text:00033103                 lea     edi, [ebx+8]
.text:00033106                 mov     [ebp+70h+var_28], edi
.text:00033109                 test    esi, esi
.text:0003310B                 jns     short loc_33156

Upon executing WinVerifyTrust, the resulting value of eax is subsequently moved to esi at [3], where it is then verified on the esi register.

Upon examining the events recorded in Procmon, it was revealed that libsecure.dll leverages the WinVerifyTrust function to authenticate the signatures of files located in the installation directory. Additionally, I implemented the patch method as described earlier.

Hiding the Debugger

After successfully attaching to the server_runtime.exe process, I attempted to set a breakpoint. However, upon reaching the breakpoint, the program promptly crashed. Upon examining the crash, the resulting stack trace was as follows:

eax=02dfa770 ebx=02dfa74c ecx=71c83004 edx=0237f8fc esi=02dfa7c4 edi=02dfa74c
eip=71bbbc00 esp=0237f8e8 ebp=0237f97c iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
libua!kepua::nodeids::NodeHash::operator=+0xa0:
71bbbc00 cc              int     3
0:009> k
 # ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0237f97c 71bf086e libua!kepua::nodeids::NodeHash::operator=+0xa0
01 0237f9e8 71b87af7 libua!kepua::types::node_id_t::isString+0x22de
02 0237fa6c 74bb9572 libua!kepua::kepuaclient::CUaClientTransportFactory::Build+0x1bb7
03 0237faa4 74bbe0d0 libsocket!kepsocket::IConnection::ProcessReadEvent+0x72
04 0237fad4 74bbdbb0 libsocket!kepsocket::CBaseSocket::UnregisterCommDiagCallback+0xe00
05 0237fb04 74f537f1 libsocket!kepsocket::CBaseSocket::UnregisterCommDiagCallback+0x8e0
06 0237fb2c 75c54f9f libthread!kepthread::CKEPThread::_ThreadProc+0x61
07 0237fb64 76e3fa29 ucrtbase!thread_start<unsigned int (__stdcall*)(void *),1>+0x3f
08 0237fb74 77717a4e kernel32!BaseThreadInitThunk+0x19
09 0237fbd0 77717a1e ntdll!__RtlUserThreadStart+0x2f
0a 0237fbe0 00000000 ntdll!_RtlUserThreadStart+0x1b
0:009> u eip
libua!kepua::nodeids::NodeHash::operator=+0xa0:
71bbbc00 cc              int     3
71bbbc01 8bec            mov     ebp,esp
71bbbc03 6aff            push    0FFFFFFFFh
71bbbc05 689f2ac171      push    offset libua!kepua::CEndpointUrl::ToString+0x1119f (71c12a9f)
71bbbc0a 64a100000000    mov     eax,dword ptr fs:[00000000h]
71bbbc10 50              push    eax
71bbbc11 81ec9c000000    sub     esp,9Ch
71bbbc17 a12830c871      mov     eax,dword ptr [libua!kepua::types::status_change_notification_t::`vftable'+0x51b58 (71c83028)]

By setting the breakpoint at 71bbc00, the debugger is unable to receive the breakpoint exception and unable to handle it. As a result, the exception remains unhandled, and if the process fails to handle it, the entire application will terminate. For further details on this issue, please refer to this resource.

In essence, it will iterate through all the threads in the target process and set the hidefromdebugger flag wherever it exists.

KEPServerEX uses the functions present in libthread.dll when initializing the thread. The function libthread!kepthread::CKEPThread::Start initializes the thread and invokes the function libthread!sub_10003960. This function sets the flag THREAD_INFORMATION_CLASS::ThreadHideFromDebugger (0x11) through the function ntdll!NtSetInformationThread.

bool __cdecl sub_10003960(int a1)
{
  HMODULE ModuleHandleW; // eax

  if ( dword_100091E0 <= *(_DWORD *)(*((_DWORD *)NtCurrentTeb()->ThreadLocalStoragePointer + TlsIndex) + 4) )
    return NtSetInformationThread && !NtSetInformationThread(a1, 0x11, 0, 0);
  _Init_thread_header(&dword_100091E0);
  if ( dword_100091E0 != -1 )
    return NtSetInformationThread && !NtSetInformationThread(a1, 0x11, 0, 0);
  ModuleHandleW = GetModuleHandleW(L"ntdll.dll");
  NtSetInformationThread = (int (__stdcall *)(_DWORD, _DWORD, _DWORD, _DWORD))GetProcAddress(
                                                                                ModuleHandleW,
                                                                                "NtSetInformationThread");
  _Init_thread_footer(&dword_100091E0);
  return NtSetInformationThread && !NtSetInformationThread(a1, 0x11, 0, 0);
}

Third attempt at Bypassing of Checks

The method to bypass this technique is similar to the anti-debug bypass mentioned earlier. All we have to do is make a patch at the start of the libthread!sub_10003960 function.

.text:10003960
.text:10003960 loc_10003960:                           ; CODE XREF: sub_10001010+7p
.text:10003960                                         ; kepthread::CKEPThread::Start(bool)+CDp
.text:10003960 ; __unwind { // SEH_10003960
.text:10003960                 xor     eax, eax // patch
.text:10003962                 retn
.text:10003963 ; ---------------------------------------------------------------------------
.text:10003963                 push    0FFFFFFFFh
.text:10003965                 push    offset SEH_10003960
.text:1000396A                 mov     eax, large fs:0

Once this is done, we can easily attach and debug the KEPServerEX process.

(18f8.1210): Break instruction exception - code 80000003 (first chance)
eax=00e41000 ebx=00000000 ecx=7775db60 edx=7775db60 esi=7775db60 edi=7775db60
eip=77724ce0 esp=0382fefc ebp=0382ff28 iopl=0         nv up ei pl zr na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000246
ntdll!DbgBreakPoint:
77724ce0 cc              int     3
0:037> bp libua+5BC00
0:037> g
Breakpoint 0 hit
eax=7a9277e0 ebx=7a9277bc ecx=7ad73004 edx=0b17f518 esi=7a927834 edi=7a9277bc
eip=7acabc00 esp=0b17f504 ebp=0b17f598 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
libua!kepua::nodeids::NodeHash::operator=+0xa0:
7acabc00 55              push    ebp
0:007> k
 # ChildEBP RetAddr  
WARNING: Stack unwind information not available. Following frames may be wrong.
00 0b17f500 7acab3b9 libua!kepua::nodeids::NodeHash::operator=+0xa0
01 0b17f598 7ace086e libua!kepua::tcp::CServer::AddEndpoint+0x1339
02 0b17f604 7ac77af7 libua!kepua::types::node_id_t::isString+0x22de
03 0b17f688 74bf9572 libua!kepua::kepuaclient::CUaClientTransportFactory::Build+0x1bb7
04 0b17f6c0 74bfe0d0 libsocket!kepsocket::IConnection::ProcessReadEvent+0x72
05 0b17f6f0 74bfdbb0 libsocket!kepsocket::CBaseSocket::UnregisterCommDiagCallback+0xe00
06 0b17f720 74a137f1 libsocket!kepsocket::CBaseSocket::UnregisterCommDiagCallback+0x8e0
07 0b17f748 75c54f9f libthread!kepthread::CKEPThread::_ThreadProc+0x61
08 0b17f780 76e3fa29 ucrtbase!thread_start<unsigned int (__stdcall*)(void *),1>+0x3f
09 0b17f790 77717a4e KERNEL32!BaseThreadInitThunk+0x19
0a 0b17f7ec 77717a1e ntdll!__RtlUserThreadStart+0x2f
0b 0b17f7fc 00000000 ntdll!_RtlUserThreadStart+0x1b
0:007> !heap -p -a 7a9277e0
    address 7a9277e0 found in
    _HEAP @ 5ab0000
      HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
        7a927788 0022 0000  [00]   7a9277b0    000d8 - (busy)
          ? libua!kepua::types::monitoring_filter_t::`vftable'+470
        73bbc24a verifier!AVrfpDphNormalHeapAllocate+0x000000ba
        73bba9da verifier!AVrfDebugPageHeapAllocate+0x0000036a
        7778ef3e ntdll!RtlDebugAllocateHeap+0x00000039
        776f7080 ntdll!RtlpAllocateHeap+0x000000f0
        776f6ddc ntdll!RtlpAllocateHeapInternal+0x0000104c
        776f5d7e ntdll!RtlAllocateHeap+0x0000003e
        75c40166 ucrtbase!_malloc_base+0x00000026
        750bfed1 mfc140u!operator new+0x00000036
        7aca627f libua!kepua::security::EncryptUserPassword+0x0000169f
        7aca516a libua!kepua::security::EncryptUserPassword+0x0000058a
        7aca5cd8 libua!kepua::security::EncryptUserPassword+0x000010f8
        74bfc468 libsocket!kepsocket::CSocketServer::_Accept+0x00000088
        74bfc62f libsocket!kepsocket::CSocketServer::_DropStoppedClients+0x0000012f
        74a1362e libthread!kepthread::CKEPThread::_ThreadProc+0x0000004e

0:007> p
Breakpoint 0 hit
eax=7a9277e0 ebx=7a9277bc ecx=7ad73004 edx=0b17f518 esi=7a927834 edi=7a9277bc
eip=7acabc00 esp=0b17f504 ebp=0b17f598 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
libua!kepua::nodeids::NodeHash::operator=+0xa0:
7acabc00 55              push    ebp
0:007> 
eax=7a9277e0 ebx=7a9277bc ecx=7ad73004 edx=0b17f518 esi=7a927834 edi=7a9277bc
eip=7acabc01 esp=0b17f500 ebp=0b17f598 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
libua!kepua::nodeids::NodeHash::operator=+0xa1:
7acabc01 8bec            mov     ebp,esp
0:007> 
eax=7a9277e0 ebx=7a9277bc ecx=7ad73004 edx=0b17f518 esi=7a927834 edi=7a9277bc
eip=7acabc03 esp=0b17f500 ebp=0b17f500 iopl=0         nv up ei pl nz na pe nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000206
libua!kepua::nodeids::NodeHash::operator=+0xa3:
7acabc03 6aff            push    0FFFFFFFFh

Code Patching

import argparse
import os

#offset for KEPServerEX6-6.12.361.0

def patch(_dir):
    server_runtime = os.path.join(_dir, "server_runtime.exe")
    
    libsecure = os.path.join(_dir, "libsecure.dll")
    libserver = os.path.join(_dir, "libserver.dll")
    libthread = os.path.join(_dir, "libthread.dll")

    #
    offset_server_runtime = 0x34101 - 0x1000+0x400 #xor     esi, esi
    offset_libsecure = 0x16A41 - 0x1000+0x400 #xor     esi, esi
    offset_libserver = 0x87540 - 0x1000+0x400 #xor eax,eax ret
    offset_libthread = 0x3960 - 0x1000+0x400 #xor eax,eax ret

    # patch check signature file
    # 
    with open(server_runtime, 'rb+') as f:
        f.seek(offset_server_runtime)
        f.write(b'\x31\xf6')

    with open(libsecure, 'rb+') as f:
        f.seek(offset_libsecure)
        f.write(b'\x31\xf6')

    # patch anti debug/attach
    with open(libserver, 'rb+') as f:
        f.seek(offset_libserver)
        f.write(b'\x31\xC0\xC3')

    #patch hidedebuger
    with open(libthread, 'rb+') as f:
        f.seek(offset_libthread)
        f.write(b'\x31\xC0\xC3')



if __name__ == "__main__":

    parser = argparse.ArgumentParser()
    #
    # define each option with: parser.add_argument
    #
    parser.add_argument("-d", "--dir", help="directory keperserver")
    args = parser.parse_args() # automatically looks at sys.argv
    #
    # access results with: args.argumentName
    #
    if(args.dir != None):
        patch(args.dir)

Bonus

After successfully bypassing all security measures, I was able to debug the process and take on further tasks, such as fuzzing. My focus was on the parsing of OPCs UA TCP in the server_runtime, which is processed in the libua.dll.

To carry out the fuzzing, I utilized the wtf fuzzer. However, my input for mutation was not effective, leading me to fail in discovering anything of interest.

During the packet receiving process, I noticed that it begins at the libua!sub_100907C0 function. It is important to note that each time the process receives a packet, it can only receive a maximum of 0xff bytes. Therefore, when developing the harness, it is essential to hook the WS2_32!recv function to pass the correct input sequentially.

Do check out Axel Souchet’s tool, KEPaboo, which is also meant for neutralising KEPServerEx’s anti-debugging techniques.

Conclusion

I would like to express my gratitude to all who took the time to read this piece. I want to extend a heartfelt appreciation to my team members who provided valuable assistance in the KEPServer setup and reviewed this blog post.

In conclusion, my earnest hope is that this write-up will prove to be an useful for anyone facing challenges with KEPServerEx’s anti-debugging techniques.

References