
Research project reverse-engineering Windows Security Center COM interfaces to trace AV registration through ATL, vtable, WSCAPI, and RPC, with runtime verification via WMI.
OWN-Defender is a Windows security research project focused on understanding how Windows Security Center (WSC) represents and manages antivirus security products through its COM interfaces.
The project began as an investigation into the behavior demonstrated by DefendNot, but rather than treating the existing implementation as a black box, I used it as a starting point for independent reverse engineering and verification.
The goal of this project is to understand the complete execution path:
COM
↓
CLSID / IID
↓
CoCreateInstance
↓
QueryInterface
↓
ATL Interface Map
↓
vtable
↓
IWscAVStatus4
↓
CWscIsv
↓
WSCAPI.dll
↓
RPC
↓
Windows Security Center
The project was developed and tested in a controlled Windows research environment.
Research / Educational Use Only
This project is intended for Windows internals research, reverse engineering, security education, and authorized security testing. Do not use it to interfere with security software on systems you do not own or have explicit permission to test.
The initial question was simple:
How does Windows Security Center know that an antivirus product exists?
Instead of stopping at the public API documentation, I wanted to understand what happens underneath the API.
This led to several questions:
QueryInterface() resolve the interface?__int64 a1 for a method?Register() function is actually the AV registration function?The first step was identifying the Windows Security Center COM class.
The project uses the WSC COM class:
CLSID_WscIsv
F2102C37-90C3-450C-B3F6-92BE1693BDF2
The implementation also contains logic to locate the CLSID dynamically from the Windows registry instead of relying exclusively on a hardcoded value.
Conceptually:
HKLM
└── SOFTWARE
└── Classes
└── CLSID
└── {CLSID}
└── Windows Security Center ISV API
This provided the first important relationship:
Registry
↓
CLSID
↓
Windows Security Center ISV API
The next challenge was determining which COM interface should be requested.
The project uses:
IWscAVStatus4
with:
4DCBAFAC-29BA-46B1-80FC-B8BDE3C0AE4D
One of the important lessons from the research was:
A GUID name alone is not enough evidence.
I verified the relationship through reverse engineering rather than assuming that the interface name and GUID were correct.
The investigation included:
QueryInterface_ATL_INTMAP_ENTRYQueryInterfaceOne of the most useful reversing steps was following the implementation of:
CComAggObject<CWscIsv>::QueryInterface()
which eventually reaches:
ATL::CComObjectRootBase::InternalQueryInterface()
The ATL interface map is used to compare the requested IID against registered interface entries.
Conceptually:
Requested IID
↓
QueryInterface()
↓
InternalQueryInterface()
↓
ATL Interface Map
↓
GUID comparison
↓
Matching interface
↓
Interface pointer
This provided independent evidence that the GUID being investigated actually corresponded to the expected COM interface.
Register() ConfusionOne of the biggest reversing challenges was understanding why IDA/Ghidra did not always display the method signature I expected.
The reconstructed interface contains:
virtual HRESULT __stdcall Register(
BSTR path,
BSTR name,
unsigned int,
unsigned int
) = 0;
However, the decompiler could show an implementation such as:
_IWscAVStatus4<CWscIsv>::Register(__int64 a1)
At first this looked inconsistent.
Further investigation showed that the decompiler representation was describing a wrapper/thunk and the underlying indirect vtable call rather than presenting the complete logical interface signature.
This became an important lesson:
Decompiler output is an interpretation of machine code, not the original source-level truth.
To resolve these discrepancies, I compared:
COM interface definition
↓
vtable layout
↓
assembly
↓
wrapper/thunk
↓
calling convention
↓
actual target function
Register() FunctionsAnother source of confusion was the presence of multiple functions with names such as:
IWscAVStatus2::Register
IWscAVStatus4::Register
IWscFWStatus2::Register
RegisterAV
The important realization was that similar names do not mean identical interfaces.
For example:
AV
↓
IWscAVStatus4
↓
AV registration
while:
Firewall
↓
IWscFWStatus2
↓
Firewall registration
The interface number and surrounding implementation had to be verified instead of selecting a function simply because its name contained Register.
This was one of the most useful parts of the research because it forced me to correlate:
Interface
+
IID
+
vtable
+
implementation
+
parameter layout
+
call target
After identifying the correct interface, I traced the registration operation deeper into the binary.
The observed path was approximately:
IWscAVStatus4::Register()
↓
CWscIsv
↓
RegisterSecurityProductFunction
↓
wscRegisterSecurityProduct()
↓
WSCAPI.dll
↓
s_wscRegisterSecurityProduct()
↓
NdrClientCall3()
↓
RPC
↓
Windows Security Center
This was particularly important because the COM method itself was not the final operation.
The call eventually crossed an RPC boundary.
That changed the way I viewed the architecture:
COM
≠
final implementation
Instead:
COM
↓
local implementation
↓
WSC API
↓
RPC client
↓
Windows component
WSCAPI.dllThe next layer was WSCAPI.dll.
The reverse-engineered path reached:
wscRegisterSecurityProduct()
which eventually invoked:
s_wscRegisterSecurityProduct()
and then:
NdrClientCall3()
This was the point where the investigation moved from a normal COM call into Windows RPC infrastructure.
Understanding this layer helped explain why the behavior could not be fully understood by looking only at the original COM DLL.
Static analysis was only one part of the research.
After reconstructing the relevant interface and call path, I created my own controlled implementation and compared the resulting behavior with Windows Security Center.
I used Windows Security Center / WMI information such as:
ROOT\SecurityCenter2
AntiVirusProduct
to independently observe the registered product information.
The verification process was: