
Forschungsprojekt zum Reverse-Engineering der COM-Schnittstellen des Windows Security Centers, um die AV-Registrierung über ATL, vtable, WSCAPI und RPC nachzuverfolgen, mit Laufzeitverifizierung über WMI.
OWN-Defender ist ein Windows-Sicherheitsforschungsprojekt, das sich darauf konzentriert zu verstehen, wie das Windows Security Center (WSC) Antiviren-Sicherheitsprodukte über seine COM-Schnittstellen darstellt und verwaltet.
Das Projekt begann als Untersuchung des Verhaltens, das von DefendNot demonstriert wurde, aber anstatt die vorhandene Implementierung als Blackbox zu behandeln, habe ich sie als Ausgangspunkt für unabhängiges Reverse Engineering und Verifikation genutzt.
Das Ziel dieses Projekts ist es, den vollständigen Ausführungspfad zu verstehen:
COM
↓
CLSID / IID
↓
CoCreateInstance
↓
QueryInterface
↓
ATL Interface Map
↓
vtable
↓
IWscAVStatus4
↓
CWscIsv
↓
WSCAPI.dll
↓
RPC
↓
Windows Security Center
Das Projekt wurde in einer kontrollierten Windows-Forschungsumgebung entwickelt und getestet.
Nur für Forschungs-/Bildungszwecke
Dieses Projekt ist für Windows-Interna-Forschung, Reverse Engineering, Sicherheitsbildung und autorisierte Sicherheitstests gedacht. Verwenden Sie es nicht, um Sicherheitssoftware auf Systemen zu beeinträchtigen, die Sie nicht besitzen oder für die Sie keine ausdrückliche Testberechtigung haben.
Die anfängliche Frage war einfach:
Woher weiß das Windows Security Center, dass ein Antivirenprodukt existiert?
Anstatt bei der öffentlichen API-Dokumentation stehen zu bleiben, wollte ich verstehen, was unterhalb der API geschieht.
Dies führte zu mehreren Fragen:
QueryInterface() die Schnittstelle auf?__int64 a1 für eine Methode?Register()-Funktion ist tatsächlich die AV-Registrierungsfunktion?Der erste Schritt war die Identifizierung der COM-Klasse des Windows Security Center.
Das Projekt verwendet die WSC-COM-Klasse:
CLSID_WscIsv
F2102C37-90C3-450C-B3F6-92BE1693BDF2
Die Implementierung enthält außerdem Logik, um die CLSID dynamisch aus der Windows-Registrierung zu ermitteln, anstatt sich ausschließlich auf einen hartcodierten Wert zu verlassen.
Konzeptionell:
HKLM
└── SOFTWARE
└── Classes
└── CLSID
└── {CLSID}
└── Windows Security Center ISV API
Dies lieferte die erste wichtige Beziehung:
Registry
↓
CLSID
↓
Windows Security Center ISV API
Die nächste Herausforderung bestand darin, zu bestimmen, welche COM-Schnittstelle angefordert werden sollte.
Das Projekt verwendet:
IWscAVStatus4
mit:
4DCBAFAC-29BA-46B1-80FC-B8BDE3C0AE4D
Eine der wichtigen Lektionen aus der Forschung war:
Ein GUID-Name allein ist kein ausreichender Beweis.
Ich habe die Beziehung durch Reverse Engineering verifiziert, anstatt anzunehmen, dass Schnittstellenname und GUID korrekt sind.
Die Untersuchung umfasste:
QueryInterface_ATL_INTMAP_ENTRYQueryInterfaceEiner der nützlichsten Reversing-Schritte war das Verfolgen der Implementierung von:
CComAggObject<CWscIsv>::QueryInterface()
die schließlich erreicht:
ATL::CComObjectRootBase::InternalQueryInterface()
Die ATL-Interface-Map wird verwendet, um die angeforderte IID mit registrierten Schnittstelleneinträgen zu vergleichen.
Konzeptionell:
Angeforderte IID
↓
QueryInterface()
↓
InternalQueryInterface()
↓
ATL Interface Map
↓
GUID-Vergleich
↓
Übereinstimmende Schnittstelle
↓
Schnittstellenzeiger
Dies lieferte unabhängige Beweise dafür, dass die untersuchte GUID tatsächlich der erwarteten COM-Schnittstelle entsprach.
Register()-VerwirrungEine der größten Reversing-Herausforderungen war das Verständnis, warum IDA/Ghidra nicht immer die von mir erwartete Methodensignatur anzeigte.
Die rekonstruierte Schnittstelle enthält:
virtual HRESULT __stdcall Register(
BSTR path,
BSTR name,
unsigned int,
unsigned int
) = 0;
Der Dekompiler konnte jedoch eine Implementierung wie folgt anzeigen:
_IWscAVStatus4<CWscIsv>::Register(__int64 a1)
Zunächst sah dies inkonsistent aus.
Weitere Untersuchungen zeigten, dass die Dekompiler-Darstellung einen Wrapper/Thunk und den zugrunde liegenden indirekten vtable-Aufruf beschrieb, anstatt die vollständige logische Schnittstellensignatur darzustellen.
Dies wurde zu einer wichtigen Lektion:
Die Dekompiler-Ausgabe ist eine Interpretation von Maschinencode, nicht die ursprüngliche Wahrheit auf Quellcode-Ebene.
Um diese Diskrepanzen aufzulösen, habe ich verglichen:
COM-Schnittstellendefinition
↓
vtable-Layout
↓
Assembly
↓
Wrapper/Thunk
↓
Aufrufkonvention
↓
Tatsächliche Zielfunktion
Register()-FunktionenEine weitere Quelle der Verwirrung war das Vorhandensein mehrerer Funktionen mit Namen wie:
IWscAVStatus2::Register
IWscAVStatus4::Register
IWscFWStatus2::Register
RegisterAV
Die wichtige Erkenntnis war, dass ähnliche Namen nicht identische Schnittstellen bedeuten.
Zum Beispiel:
AV
↓
IWscAVStatus4
↓
AV-Registrierung
während:
Firewall
↓
IWscFWStatus2
↓
Firewall-Registrierung
Die Schnittstellennummer und die umgebende Implementierung mussten verifiziert werden, anstatt eine Funktion nur deshalb auszuwählen, weil ihr Name Register enthielt.
Dies war einer der nützlichsten Teile der Forschung, weil es mich zwang, zu korrelieren:
Schnittstelle
+
IID
+
vtable
+
Implementierung
+
Parameterlayout
+
Aufrufziel
Nach der Identifizierung der korrekten Schnittstelle verfolgte ich die Registrierungsoperation tiefer in die Binärdatei.
Der beobachtete Pfad war ungefähr:
IWscAVStatus4::Register()
↓
CWscIsv
↓
RegisterSecurityProductFunction
↓
wscRegisterSecurityProduct()
↓
WSCAPI.dll
↓
s_wscRegisterSecurityProduct()
↓
NdrClientCall3()
↓
RPC
↓
Windows Security Center
Dies war besonders wichtig, weil die COM-Methode selbst nicht die endgültige Operation war.
Der Aufruf überschritt schließlich eine RPC-Grenze.
Das veränderte meine Sichtweise auf die Architektur:
COM
≠
endgültige Implementierung
Stattdessen:
COM
↓
lokale Implementierung
↓
WSC API
↓
RPC-Client
↓
Windows-Komponente
WSCAPI.dllDie nächste Ebene war WSCAPI.dll.
Der reverse-engineerte Pfad erreichte:
wscRegisterSecurityProduct()
die schließlich aufrief:
s_wscRegisterSecurityProduct()
und dann:
NdrClientCall3()
Dies war der Punkt, an dem sich die Untersuchung von einem normalen COM-Aufruf in die Windows-RPC-Infrastruktur verlagerte.
Das Verständnis dieser Ebene half zu erklären, warum das Verhalten nicht vollständig verstanden werden konnte, wenn man nur die ursprüngliche COM-DLL betrachtete.