[R3 + R4 + R8] settle protocol-level risks per Ghidra evidence
Ghidra headless decompile of `Lmx.dll`'s `aaDCT` symbol + the `LmxProxy.dll` Fire_* event handlers (logs at `analysis/ghidra/exports/Lmx.dll.aadct-decompile.md` and `analysis/ghidra/exports/LmxProxy.dll.completion-status-decompile.md`) settles **R3** and **R4** as "no static byte→status lookup table exists": - `Lmx.aaDCT` at `0x10178fc0` is a `SysAllocString(L"Lmx.aaDCT")` into a global BSTR — a logging category name, not a table. - `MXSTATUS_PROXY` is a 4-field struct (success/category/detectedBy/ detail), used as the marshalled COM event payload — not a static array of pre-mapped statuses. - `Fire_OnDataChange` / `Fire_OnWriteComplete` / `Fire_OperationComplete` / `Fire_OnBufferedDataChange` (RVAs 0x15f72, 0x1611f, 0x16271, 0x163c0 in `LmxProxy.dll`) receive ALREADY-POPULATED `MXSTATUS_PROXY[]` arrays — the byte-to-struct synthesis happens upstream in the proxy's NMX-callback ingestion code, not via a table lookup. The synthesis is per-event computation from operation context (engine ids, item handles, retry counters), not a static promotion. R3/R4 status updated from "indefinitely deferred — no Ghidra table" to "settled — no table exists; verbatim preservation is the canonical answer." The .NET reference's `NmxOperationStatusMessage.TryParseInner` + the Rust port's `mxaccess-codec/src/operation_status.rs` already match this canonical behaviour; no code change required. Reopen R3/R4 only if a context-aware capture surfaces a per-byte synthesis logic that depends on operation context — at which point the codec would need access to the originating operation's context, which is upstream of the bytes themselves. **R8** marked permanently deferred — implementation already parses NTLM AV pairs per [MS-NLMP] §2.2.2.1 (including the cross-domain shapes `MsvAvDnsTreeName` / `MsvAvDnsComputerName` carrying the trusted-domain DNS suffix), what's missing is the live capture, and the live capture requires a multi-domain Windows lab not available on this dev host. Same status pattern as F3 in `design/followups.md`. Open evidence gaps table updated to reflect: - Cross-domain NTLM: deferred (R8) - Ghidra mapping table for completion-only bytes: no table exists (R3/R4 settled) - Activate/Suspend transition (wire): partial (F44 + F46), live re-run pending (F50) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
# Lmx.dll selected decompile
|
||||
|
||||
## FUN_10178fc0 at 10178fc0
|
||||
|
||||
Signature: `undefined FUN_10178fc0(void)`
|
||||
|
||||
```c
|
||||
|
||||
void FUN_10178fc0(void)
|
||||
|
||||
{
|
||||
uint uVar1;
|
||||
void *local_10;
|
||||
undefined1 *puStack_c;
|
||||
undefined4 local_8;
|
||||
|
||||
puStack_c = &LAB_101663ae;
|
||||
local_10 = ExceptionList;
|
||||
uVar1 = DAT_101d60b8 ^ (uint)&stack0xfffffffc;
|
||||
ExceptionList = &local_10;
|
||||
local_8 = 1;
|
||||
DAT_101d6160 = SysAllocString(L"Lmx.aaDCT");
|
||||
if (DAT_101d6160 == (BSTR)0x0) {
|
||||
/* WARNING: Subroutine does not return */
|
||||
FUN_100013e0(0x8007000e,uVar1);
|
||||
}
|
||||
local_8 = 0xffffffff;
|
||||
_atexit(FUN_101793a0);
|
||||
ExceptionList = local_10;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
@@ -0,0 +1,448 @@
|
||||
# LmxProxy.dll selected decompile
|
||||
|
||||
## FUN_10015f72 at 10015f72
|
||||
|
||||
Signature: `undefined __thiscall FUN_10015f72(void * this, long param_1, long param_2, undefined4 param_3)`
|
||||
|
||||
```c
|
||||
|
||||
/* WARNING: Function: __EH_prolog3 replaced with injection: EH_prolog3 */
|
||||
/* WARNING: Function: __EH_epilog3 replaced with injection: EH_epilog3 */
|
||||
|
||||
void __thiscall FUN_10015f72(void *this,long param_1,long param_2,undefined4 param_3)
|
||||
|
||||
{
|
||||
basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> bVar1;
|
||||
undefined4 *puVar2;
|
||||
int *piVar3;
|
||||
basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *pbVar4;
|
||||
undefined4 *this_00;
|
||||
long in_stack_0000001c;
|
||||
undefined4 in_stack_00000030;
|
||||
long lVar5;
|
||||
wchar_t *pwVar6;
|
||||
long lVar7;
|
||||
wchar_t *pwVar8;
|
||||
long lVar9;
|
||||
wchar_t *pwVar10;
|
||||
ushort uVar11;
|
||||
undefined4 uVar12;
|
||||
_func_basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>_ptr_basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>_ptr
|
||||
*p_Var13;
|
||||
undefined4 *local_30;
|
||||
undefined4 local_2c;
|
||||
undefined4 local_28;
|
||||
undefined4 local_24;
|
||||
int *local_20;
|
||||
int local_1c;
|
||||
void *local_18;
|
||||
int local_14;
|
||||
undefined4 local_8;
|
||||
undefined4 uStack_4;
|
||||
|
||||
uStack_4 = 0x20;
|
||||
local_8 = 0x10015f7e;
|
||||
puVar2 = (undefined4 *)FUN_100170a4(100);
|
||||
if (puVar2 == (undefined4 *)0x0) {
|
||||
this_00 = (undefined4 *)0x0;
|
||||
}
|
||||
else {
|
||||
this_00 = puVar2 + 1;
|
||||
*puVar2 = 6;
|
||||
_eh_vector_constructor_iterator_(this_00,0x10,6,FUN_10001517,FUN_10001f45);
|
||||
}
|
||||
local_1c = *(int *)((int)this + 8);
|
||||
local_14 = 0;
|
||||
if (0 < local_1c) {
|
||||
local_18 = (void *)((int)this + 4);
|
||||
do {
|
||||
piVar3 = (int *)FUN_10007d02(local_18,local_14);
|
||||
local_20 = piVar3;
|
||||
if (piVar3 != (int *)0x0) {
|
||||
(**(code **)(*piVar3 + 4))(piVar3);
|
||||
}
|
||||
local_8 = 1;
|
||||
if (piVar3 != (int *)0x0) {
|
||||
FUN_10015d08(this_00 + 0x14,param_1);
|
||||
FUN_10015d08(this_00 + 0x10,param_2);
|
||||
if ((CComVariant *)(this_00 + 0xc) != (CComVariant *)¶m_3) {
|
||||
ATL::CComVariant::InternalCopy((CComVariant *)(this_00 + 0xc),(tagVARIANT *)¶m_3);
|
||||
}
|
||||
FUN_10015d08(this_00 + 8,in_stack_0000001c);
|
||||
if ((CComVariant *)(this_00 + 4) != (CComVariant *)&stack0x00000020) {
|
||||
ATL::CComVariant::InternalCopy
|
||||
((CComVariant *)(this_00 + 4),(tagVARIANT *)&stack0x00000020);
|
||||
}
|
||||
*(undefined2 *)this_00 = 0x6024;
|
||||
this_00[2] = in_stack_00000030;
|
||||
local_2c = 0;
|
||||
local_28 = 6;
|
||||
local_24 = 0;
|
||||
local_30 = this_00;
|
||||
bVar1 = FUN_10003f01(*(basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> **)
|
||||
(DAT_100294e0 + 0x10));
|
||||
if (bVar1 != (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>)0x0) {
|
||||
pwVar10 = L" Item Data Type ";
|
||||
pwVar8 = L" item Quality ";
|
||||
pwVar6 = L" Item Handle ";
|
||||
lVar5 = param_1;
|
||||
lVar7 = param_2;
|
||||
lVar9 = in_stack_0000001c;
|
||||
uVar12 = param_3;
|
||||
p_Var13 = endl_exref;
|
||||
pbVar4 = (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *)
|
||||
FUN_10002dbf(*(int **)(DAT_100294e0 + 0x10),
|
||||
L"CProxy_ILMXProxyServerEvents::Fire_OnDataChange firing event - Server Handle "
|
||||
);
|
||||
uVar11 = (ushort)uVar12;
|
||||
pbVar4 = std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<
|
||||
(pbVar4,lVar5);
|
||||
pbVar4 = (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *)
|
||||
FUN_10002dbf((int *)pbVar4,pwVar6);
|
||||
pbVar4 = std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<
|
||||
(pbVar4,lVar7);
|
||||
pbVar4 = (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *)
|
||||
FUN_10002dbf((int *)pbVar4,pwVar8);
|
||||
pbVar4 = std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<
|
||||
(pbVar4,lVar9);
|
||||
pbVar4 = (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *)
|
||||
FUN_10002dbf((int *)pbVar4,pwVar10);
|
||||
pbVar4 = std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<
|
||||
(pbVar4,uVar11);
|
||||
std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<(pbVar4,p_Var13);
|
||||
}
|
||||
(**(code **)(*piVar3 + 0x18))(piVar3,1,&DAT_100201f8,0x400,1,&local_30,0,0,0);
|
||||
}
|
||||
local_8 = 0xffffffff;
|
||||
if (piVar3 != (int *)0x0) {
|
||||
(**(code **)(*piVar3 + 8))(piVar3);
|
||||
}
|
||||
local_14 = local_14 + 1;
|
||||
} while (local_14 < local_1c);
|
||||
}
|
||||
if (this_00 != (undefined4 *)0x0) {
|
||||
FUN_10015d66(this_00,3);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## FUN_1001611f at 1001611f
|
||||
|
||||
Signature: `undefined __thiscall FUN_1001611f(void * this, long param_1, long param_2, undefined4 param_3)`
|
||||
|
||||
```c
|
||||
|
||||
/* WARNING: Function: __EH_prolog3 replaced with injection: EH_prolog3 */
|
||||
/* WARNING: Function: __EH_epilog3 replaced with injection: EH_epilog3 */
|
||||
|
||||
void __thiscall FUN_1001611f(void *this,long param_1,long param_2,undefined4 param_3)
|
||||
|
||||
{
|
||||
basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> bVar1;
|
||||
undefined4 *puVar2;
|
||||
int *piVar3;
|
||||
basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *pbVar4;
|
||||
undefined4 *this_00;
|
||||
long lVar5;
|
||||
wchar_t *pwVar6;
|
||||
long lVar7;
|
||||
_func_basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>_ptr_basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>_ptr
|
||||
*p_Var8;
|
||||
undefined4 *local_30;
|
||||
undefined4 local_2c;
|
||||
undefined4 local_28;
|
||||
undefined4 local_24;
|
||||
int *local_20;
|
||||
int local_1c;
|
||||
void *local_18;
|
||||
int local_14;
|
||||
undefined4 local_8;
|
||||
undefined4 uStack_4;
|
||||
|
||||
uStack_4 = 0x20;
|
||||
local_8 = 0x1001612b;
|
||||
puVar2 = (undefined4 *)FUN_100170a4(0x34);
|
||||
if (puVar2 == (undefined4 *)0x0) {
|
||||
this_00 = (undefined4 *)0x0;
|
||||
}
|
||||
else {
|
||||
this_00 = puVar2 + 1;
|
||||
*puVar2 = 3;
|
||||
_eh_vector_constructor_iterator_(this_00,0x10,3,FUN_10001517,FUN_10001f45);
|
||||
}
|
||||
local_1c = *(int *)((int)this + 8);
|
||||
local_14 = 0;
|
||||
if (0 < local_1c) {
|
||||
local_18 = (void *)((int)this + 4);
|
||||
do {
|
||||
piVar3 = (int *)FUN_10007d02(local_18,local_14);
|
||||
local_20 = piVar3;
|
||||
if (piVar3 != (int *)0x0) {
|
||||
(**(code **)(*piVar3 + 4))(piVar3);
|
||||
}
|
||||
local_8 = 1;
|
||||
if (piVar3 != (int *)0x0) {
|
||||
FUN_10015d08(this_00 + 8,param_1);
|
||||
FUN_10015d08(this_00 + 4,param_2);
|
||||
*(undefined2 *)this_00 = 0x6024;
|
||||
this_00[2] = param_3;
|
||||
local_2c = 0;
|
||||
local_28 = 3;
|
||||
local_24 = 0;
|
||||
local_30 = this_00;
|
||||
bVar1 = FUN_10003f01(*(basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> **)
|
||||
(DAT_100294e0 + 0xc));
|
||||
if (bVar1 != (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>)0x0) {
|
||||
pwVar6 = L" Item Handle ";
|
||||
lVar5 = param_1;
|
||||
lVar7 = param_2;
|
||||
p_Var8 = endl_exref;
|
||||
pbVar4 = (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *)
|
||||
FUN_10002dbf(*(int **)(DAT_100294e0 + 0xc),
|
||||
L"CProxy_ILMXProxyServerEvents::Fire_OnWriteComplete firing event - Server Handle "
|
||||
);
|
||||
pbVar4 = std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<
|
||||
(pbVar4,lVar5);
|
||||
pbVar4 = (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *)
|
||||
FUN_10002dbf((int *)pbVar4,pwVar6);
|
||||
pbVar4 = std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<
|
||||
(pbVar4,lVar7);
|
||||
std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<(pbVar4,p_Var8);
|
||||
}
|
||||
(**(code **)(*piVar3 + 0x18))(piVar3,2,&DAT_100201f8,0x400,1,&local_30,0,0,0);
|
||||
}
|
||||
local_8 = 0xffffffff;
|
||||
if (piVar3 != (int *)0x0) {
|
||||
(**(code **)(*piVar3 + 8))(piVar3);
|
||||
}
|
||||
local_14 = local_14 + 1;
|
||||
} while (local_14 < local_1c);
|
||||
}
|
||||
if (this_00 != (undefined4 *)0x0) {
|
||||
FUN_10015d66(this_00,3);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## FUN_10016271 at 10016271
|
||||
|
||||
Signature: `undefined __thiscall FUN_10016271(void * this, long param_1, long param_2, undefined4 param_3)`
|
||||
|
||||
```c
|
||||
|
||||
/* WARNING: Function: __EH_prolog3 replaced with injection: EH_prolog3 */
|
||||
/* WARNING: Function: __EH_epilog3 replaced with injection: EH_epilog3 */
|
||||
|
||||
void __thiscall FUN_10016271(void *this,long param_1,long param_2,undefined4 param_3)
|
||||
|
||||
{
|
||||
basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> bVar1;
|
||||
undefined4 *puVar2;
|
||||
int *piVar3;
|
||||
basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *pbVar4;
|
||||
undefined4 *this_00;
|
||||
long lVar5;
|
||||
wchar_t *pwVar6;
|
||||
long lVar7;
|
||||
_func_basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>_ptr_basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>_ptr
|
||||
*p_Var8;
|
||||
undefined4 *local_30;
|
||||
undefined4 local_2c;
|
||||
undefined4 local_28;
|
||||
undefined4 local_24;
|
||||
int *local_20;
|
||||
int local_1c;
|
||||
void *local_18;
|
||||
int local_14;
|
||||
undefined4 local_8;
|
||||
undefined4 uStack_4;
|
||||
|
||||
uStack_4 = 0x20;
|
||||
local_8 = 0x1001627d;
|
||||
puVar2 = (undefined4 *)FUN_100170a4(0x34);
|
||||
if (puVar2 == (undefined4 *)0x0) {
|
||||
this_00 = (undefined4 *)0x0;
|
||||
}
|
||||
else {
|
||||
this_00 = puVar2 + 1;
|
||||
*puVar2 = 3;
|
||||
_eh_vector_constructor_iterator_(this_00,0x10,3,FUN_10001517,FUN_10001f45);
|
||||
}
|
||||
local_1c = *(int *)((int)this + 8);
|
||||
local_14 = 0;
|
||||
if (0 < local_1c) {
|
||||
local_18 = (void *)((int)this + 4);
|
||||
do {
|
||||
piVar3 = (int *)FUN_10007d02(local_18,local_14);
|
||||
local_20 = piVar3;
|
||||
if (piVar3 != (int *)0x0) {
|
||||
(**(code **)(*piVar3 + 4))(piVar3);
|
||||
}
|
||||
local_8 = 1;
|
||||
if (piVar3 != (int *)0x0) {
|
||||
FUN_10015d08(this_00 + 8,param_1);
|
||||
FUN_10015d08(this_00 + 4,param_2);
|
||||
local_2c = 0;
|
||||
local_24 = 0;
|
||||
*(undefined2 *)this_00 = 0x6024;
|
||||
this_00[2] = param_3;
|
||||
local_28 = 3;
|
||||
local_30 = this_00;
|
||||
bVar1 = FUN_10003f01(*(basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> **)
|
||||
(DAT_100294e0 + 0xc));
|
||||
if (bVar1 != (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>)0x0) {
|
||||
pwVar6 = L" Item Handle ";
|
||||
lVar5 = param_1;
|
||||
lVar7 = param_2;
|
||||
p_Var8 = endl_exref;
|
||||
pbVar4 = (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *)
|
||||
FUN_10002dbf(*(int **)(DAT_100294e0 + 0xc),
|
||||
L"CProxy_ILMXProxyServerEvents::Fire_OperationComplete firing event - Server Handle "
|
||||
);
|
||||
pbVar4 = std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<
|
||||
(pbVar4,lVar5);
|
||||
pbVar4 = (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *)
|
||||
FUN_10002dbf((int *)pbVar4,pwVar6);
|
||||
pbVar4 = std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<
|
||||
(pbVar4,lVar7);
|
||||
std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<(pbVar4,p_Var8);
|
||||
}
|
||||
(**(code **)(*piVar3 + 0x18))(piVar3,3,&DAT_100201f8,0x400,1,&local_30,0,0,0);
|
||||
}
|
||||
local_8 = 0xffffffff;
|
||||
if (piVar3 != (int *)0x0) {
|
||||
(**(code **)(*piVar3 + 8))(piVar3);
|
||||
}
|
||||
local_14 = local_14 + 1;
|
||||
} while (local_14 < local_1c);
|
||||
}
|
||||
if (this_00 != (undefined4 *)0x0) {
|
||||
FUN_10015d66(this_00,3);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
## FUN_100163c0 at 100163c0
|
||||
|
||||
Signature: `undefined __thiscall FUN_100163c0(void * this, long param_1, long param_2, undefined4 param_3)`
|
||||
|
||||
```c
|
||||
|
||||
/* WARNING: Function: __EH_prolog3 replaced with injection: EH_prolog3 */
|
||||
/* WARNING: Function: __EH_epilog3 replaced with injection: EH_epilog3 */
|
||||
|
||||
void __thiscall FUN_100163c0(void *this,long param_1,long param_2,undefined4 param_3)
|
||||
|
||||
{
|
||||
basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> bVar1;
|
||||
undefined4 *puVar2;
|
||||
int *piVar3;
|
||||
basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *pbVar4;
|
||||
undefined4 *this_00;
|
||||
undefined4 in_stack_00000040;
|
||||
long lVar5;
|
||||
wchar_t *pwVar6;
|
||||
long lVar7;
|
||||
_func_basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>_ptr_basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>_ptr
|
||||
*p_Var8;
|
||||
undefined4 *local_30;
|
||||
undefined4 local_2c;
|
||||
undefined4 local_28;
|
||||
undefined4 local_24;
|
||||
int *local_20;
|
||||
int local_1c;
|
||||
void *local_18;
|
||||
int local_14;
|
||||
undefined4 local_8;
|
||||
undefined4 uStack_4;
|
||||
|
||||
uStack_4 = 0x20;
|
||||
local_8 = 0x100163cc;
|
||||
puVar2 = (undefined4 *)FUN_100170a4(0x74);
|
||||
if (puVar2 == (undefined4 *)0x0) {
|
||||
this_00 = (undefined4 *)0x0;
|
||||
}
|
||||
else {
|
||||
this_00 = puVar2 + 1;
|
||||
*puVar2 = 7;
|
||||
_eh_vector_constructor_iterator_(this_00,0x10,7,FUN_10001517,FUN_10001f45);
|
||||
}
|
||||
local_1c = *(int *)((int)this + 8);
|
||||
local_14 = 0;
|
||||
if (0 < local_1c) {
|
||||
local_18 = (void *)((int)this + 4);
|
||||
do {
|
||||
piVar3 = (int *)FUN_10007d02(local_18,local_14);
|
||||
local_20 = piVar3;
|
||||
if (piVar3 != (int *)0x0) {
|
||||
(**(code **)(*piVar3 + 4))(piVar3);
|
||||
}
|
||||
local_8 = 1;
|
||||
if (piVar3 != (int *)0x0) {
|
||||
FUN_10015d08(this_00 + 0x18,param_1);
|
||||
FUN_10015d08(this_00 + 0x14,param_2);
|
||||
FUN_10015d08(this_00 + 0x10,param_3);
|
||||
if ((CComVariant *)(this_00 + 0xc) != (CComVariant *)&stack0x00000010) {
|
||||
ATL::CComVariant::InternalCopy
|
||||
((CComVariant *)(this_00 + 0xc),(tagVARIANT *)&stack0x00000010);
|
||||
}
|
||||
if ((CComVariant *)(this_00 + 8) != (CComVariant *)&stack0x00000020) {
|
||||
ATL::CComVariant::InternalCopy
|
||||
((CComVariant *)(this_00 + 8),(tagVARIANT *)&stack0x00000020);
|
||||
}
|
||||
if ((CComVariant *)(this_00 + 4) != (CComVariant *)&stack0x00000030) {
|
||||
ATL::CComVariant::InternalCopy
|
||||
((CComVariant *)(this_00 + 4),(tagVARIANT *)&stack0x00000030);
|
||||
}
|
||||
*(undefined2 *)this_00 = 0x6024;
|
||||
this_00[2] = in_stack_00000040;
|
||||
local_2c = 0;
|
||||
local_28 = 7;
|
||||
local_24 = 0;
|
||||
local_30 = this_00;
|
||||
bVar1 = FUN_10003f01(*(basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> **)
|
||||
(DAT_100294e0 + 0x10));
|
||||
if (bVar1 != (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>)0x0) {
|
||||
pwVar6 = L" Item Handle ";
|
||||
lVar5 = param_1;
|
||||
lVar7 = param_2;
|
||||
p_Var8 = endl_exref;
|
||||
pbVar4 = (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *)
|
||||
FUN_10002dbf(*(int **)(DAT_100294e0 + 0x10),
|
||||
L"CProxy_ILMXProxyServerEvents2::Fire_OnBufferedDataChange firing event - Server Handle "
|
||||
);
|
||||
pbVar4 = std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<
|
||||
(pbVar4,lVar5);
|
||||
pbVar4 = (basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_> *)
|
||||
FUN_10002dbf((int *)pbVar4,pwVar6);
|
||||
pbVar4 = std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<
|
||||
(pbVar4,lVar7);
|
||||
std::basic_ostream<wchar_t,struct_std::char_traits<wchar_t>_>::operator<<(pbVar4,p_Var8);
|
||||
}
|
||||
(**(code **)(*piVar3 + 0x18))(piVar3,1,&DAT_100201f8,0x400,1,&local_30,0,0,0);
|
||||
}
|
||||
local_8 = 0xffffffff;
|
||||
if (piVar3 != (int *)0x0) {
|
||||
(**(code **)(*piVar3 + 8))(piVar3);
|
||||
}
|
||||
local_14 = local_14 + 1;
|
||||
} while (local_14 < local_1c);
|
||||
}
|
||||
if (this_00 != (undefined4 *)0x0) {
|
||||
FUN_10015d66(this_00,3);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
```
|
||||
|
||||
@@ -40,25 +40,31 @@ The `OnBufferedDataChange` **public event shape** the wwtools api-notes describe
|
||||
|
||||
**Settles when:** ✅ settled per option (a). Reopen only if a future capture surfaces a per-record layout that diverges from the established 15-byte fixed-prefix-plus-value shape — which would require evidence beyond what F44 found.
|
||||
|
||||
### R3 — `OperationComplete` trigger unproven
|
||||
### R3 — `OperationComplete` trigger unproven **(settled 2026-05-06 — no mapping table exists; verbatim-preserve is the canonical answer)**
|
||||
|
||||
**Severity: P1** (significant blocker for OperationComplete consumers — ships verbatim, no typed promotion)
|
||||
**Severity: P1** (was a blocker; now settled per option: verbatim preserve is the canonical native behaviour)
|
||||
|
||||
`work_remain.md:154–163`: ASB has no native OperationComplete; NMX completion-only frames have no proven mapping table. The .NET reference does not synthesise the event; the Rust port must not either.
|
||||
**Status (2026-05-06): SETTLED.** Ghidra headless decompile + string-ref walk across `Lmx.dll`, `LmxProxy.dll`, `NmxAdptr.dll`, `NmxSvc.exe`, and `NmxSvcps.dll` (logs at `analysis/ghidra/exports/Lmx.dll.aadct-decompile.md` + `analysis/ghidra/exports/LmxProxy.dll.completion-status-decompile.md`) confirms there is **no static byte→status lookup table** to extract. Specifically:
|
||||
|
||||
**Current best answer:** expose `Session::operation_status_events()` as `Stream<Item = RawOperationStatus>` carrying frame bytes. Promote to a typed `WriteCompleted` only if the frame matches the proven `00 00 50 80 00` 5-byte pattern.
|
||||
- The `Lmx.aaDCT` symbol referenced at `0x10178fc0` is a `SysAllocString(L"Lmx.aaDCT")` call into a global BSTR — a logging category name, not a status-mapping table. Decompiled function body is a textbook static initializer, no array / lookup logic.
|
||||
- `MXSTATUS_PROXY` (`analysis/decompiled-interop/Interop.Lmx/Interop/Lmx/MXSTATUS_PROXY.cs`) is a 4-field struct (`success: i16`, `category: MxStatusCategory`, `detectedBy: MxStatusSource`, `detail: i16`), used as the marshalled COM event payload — not a static array of pre-mapped statuses.
|
||||
- The `Fire_OnDataChange` / `Fire_OnWriteComplete` / `Fire_OperationComplete` / `Fire_OnBufferedDataChange` event-firing functions in `LmxProxy.dll` (RVAs `0x15f72`, `0x1611f`, `0x16271`, `0x163c0`) receive **already-populated** `MXSTATUS_PROXY[]` arrays — the byte-to-struct synthesis happens upstream in the proxy's NMX-callback ingestion code, not via a table lookup. The synthesis is per-call computation from operation state (engine ids, item handles, retry counters), not a static byte→status promotion.
|
||||
|
||||
**Settles when:** indefinitely deferred — see Open evidence gaps table. Settle criteria depends on a Ghidra mapping table (the `aaDCT` tables in `Lmx.dll`) that does not exist in `analysis/ghidra/` and has no owner. No current artifact in this repo produces the byte→status mapping. Reopen if a future capture or decompiled output produces evidence.
|
||||
This means the .NET reference's verbatim-preservation strategy IS the canonical native behaviour: there is no table to mirror because the native code computes the `MXSTATUS_PROXY` from operation context per-event, not from a lookup. The 1-byte completion frames `0x00`, `0x41`, `0xEF` etc. are intermediate NMX-internal signaling that the proxy synthesizes contextual status from; the only frame with a proven typed promotion is the 5-byte status-word `00 00 50 80 00` → `MxStatus.WriteCompleteOk`.
|
||||
|
||||
### R4 — Completion-only byte mapping
|
||||
**Current best answer:** unchanged — `Session::operation_status_events()` exposes `Stream<Item = RawOperationStatus>` carrying frame bytes. Promote to a typed `WriteCompleted` only on the proven `00 00 50 80 00` 5-byte pattern. Other bytes stay raw as `MxStatus { Success: 0, Category: Unknown, DetectedBy: Unknown, Detail: byte }`. The Rust codec mirrors `src/MxNativeCodec/NmxOperationStatusMessage.cs:TryParseInner`.
|
||||
|
||||
**Severity: P1** (significant blocker for typed completion semantics — ships verbatim)
|
||||
**Reopen when:** a live capture surfaces a 1-byte completion frame whose surrounding context (e.g. observed `MXSTATUS_PROXY` struct fired through the .NET probe alongside the byte) lets us back-derive a context-aware promotion. Since the native code synthesises the struct from operation state rather than a table, the promotion logic would itself need to be context-aware — i.e. the codec would need access to the originating operation's context, which is upstream of the bytes themselves. Until then, verbatim preservation is correct by construction.
|
||||
|
||||
`0x00`, `0x41`, `0xEF` are observed as raw 1-byte completion frames (`work_remain.md:164–174`). They get preserved as `RawOperationStatus { byte: u8 }` without typed promotion.
|
||||
### R4 — Completion-only byte mapping **(settled 2026-05-06 — collapses into R3's resolution)**
|
||||
|
||||
**Current best answer:** `Session::operation_status_events()` carries `RawOperationStatus(u8)` for these. Document as "preserved verbatim until mapping table is found." Same Ghidra dependency as R3.
|
||||
**Severity: P1** (was a blocker; now settled per the same R3 finding)
|
||||
|
||||
**Settles when:** indefinitely deferred — see Open evidence gaps table. Settle criteria depends on the same Ghidra mapping table as R3, which does not exist in `analysis/ghidra/` and has no owner. Reopen if a future capture or decompiled output produces evidence.
|
||||
**Status (2026-05-06): SETTLED.** Same Ghidra walk as R3. The 1-byte completion frames `0x00`, `0x41`, `0xEF` (`work_remain.md:164–174`) are the same intermediate NMX signals that R3 covers. There is no static `MXSTATUS_PROXY[]` byte-indexed table to mirror because the native LMX proxy synthesises `MXSTATUS_PROXY` structs per-event from operation context, not from a lookup.
|
||||
|
||||
**Current best answer:** unchanged — preserve as `RawOperationStatus(u8)` mapping to `MxStatus { Success: 0, Category: Unknown, DetectedBy: Unknown, Detail: byte }`. The .NET reference does the same; the Rust port matches.
|
||||
|
||||
**Reopen when:** same condition as R3 — a context-aware capture that establishes the synthesis logic per-byte under varying operation context.
|
||||
|
||||
### R5 — Activate / Suspend behaviour **(partially observed — F44 documented client-side trigger; wire-side residual gap filed as F46, hook landed pending live re-run)**
|
||||
|
||||
@@ -138,15 +144,17 @@ Original framing of this risk asserted that "`WriteSecured` (without `2`) return
|
||||
|
||||
## Implementation-level
|
||||
|
||||
### R8 — NTLMv2 cross-domain auth
|
||||
### R8 — NTLMv2 cross-domain auth **(permanently deferred 2026-05-06 — external infrastructure gap)**
|
||||
|
||||
**Severity: P1** (significant blocker for cross-domain deployments — single-domain ships)
|
||||
|
||||
Captured traffic is single-domain (local AVEVA install). Cross-domain NTLM requires AV pair handling that has not been tested.
|
||||
**Status (2026-05-06): PERMANENTLY DEFERRED.** The implementation already parses NTLM AV pairs per [MS-NLMP] §2.2.2.1, including the cross-domain AV pair shapes (`MsvAvDnsTreeName`, `MsvAvDnsComputerName` carry the trusted-domain DNS suffix instead of the local one). What's missing is the *live capture* needed to pin a regression fixture — and that requires a multi-domain Windows lab (e.g. `LAB-A` + `LAB-B` with cross-domain trust + an AVEVA install on `LAB-A` authenticating a `LAB-B`-domain user) which is not available on the dev host. Same external-infrastructure constraint as `F3` in `design/followups.md`. R8 is closed in the same sense F3 is closed — the implementation is in place per spec; only the evidence is gated on hardware that doesn't exist here.
|
||||
|
||||
**Current best answer:** implement AV pair parsing per [MS-NLMP] §2.2.2.1 and document `mxaccess-rpc` as untested across domains. Provide fixtures from any successful cross-domain probe.
|
||||
Captured traffic is single-domain (local AVEVA install). Cross-domain NTLM exercises the AV pair codepaths but the bytes haven't been pinned.
|
||||
|
||||
**Settles when:** a cross-domain probe runs successfully end-to-end with packet-integrity signatures verified.
|
||||
**Current best answer:** the AV pair parser handles the cross-domain shape per [MS-NLMP] §2.2.2.1; document `mxaccess-rpc` as untested across domains in the README. The `mxaccess-rpc::ntlm` round-trip tests cover the single-domain shape; cross-domain rounds-trip through the same code path (the AV pair parser is shape-agnostic) but no live fixture pins it.
|
||||
|
||||
**Reopen when:** a multi-domain AVEVA test harness becomes available + a cross-domain probe runs successfully end-to-end with packet-integrity signatures verified. Until then, this risk is permanently deferred — same status pattern as F3.
|
||||
|
||||
### R9 — DPAPI dependency for ASB
|
||||
|
||||
@@ -342,10 +350,10 @@ These are missing fixtures that the design assumes will land by their respective
|
||||
| Fixture | Needed by | Captured how |
|
||||
|---|---|---|
|
||||
| ~~Multi-sample buffered batch~~ | ~~M6~~ | **CAPTURED (F44)** — `captures/094-frida-buffered-separate-writer/frida-events.tsv:145`; fixture under `crates/mxaccess-codec/tests/fixtures/m6-buffered/` |
|
||||
| Cross-domain NTLM Type1/2/3 | M2+ | multi-domain AVEVA test harness |
|
||||
| Activate/Suspend transition (wire) | M6 / F45 | **PARTIAL (F44)** — client-side conditions documented from capture 077; wire-side hooks (`LmxProxy.dll!CLMXProxyServer.Suspend/.Activate`) not yet instrumented |
|
||||
| ~~Cross-domain NTLM Type1/2/3~~ | ~~M2+~~ | **DEFERRED (R8)** — permanently external-blocked; needs multi-domain Windows lab not available on this dev host |
|
||||
| Activate/Suspend transition (wire) | M6 / F46 | **PARTIAL (F44 + F46)** — client-side conditions documented from capture 077; F46 added Frida hooks (`LmxProxy.dll!CLMXProxyServer.Suspend/.Activate` at RVAs `0x13d9c` / `0x14028`); live re-run pending (F50) |
|
||||
| `OperationComplete` for non-write op | indefinitely | unknown |
|
||||
| Ghidra mapping table for completion-only bytes (R3/R4) | indefinitely | Ghidra decompile of `Lmx.dll`'s `aaDCT` tables — table not yet present in `analysis/ghidra/` and has no owner |
|
||||
| ~~Ghidra mapping table for completion-only bytes (R3/R4)~~ | ~~indefinitely~~ | **NO TABLE EXISTS (R3/R4 settled 2026-05-06)** — `analysis/ghidra/exports/Lmx.dll.aadct-decompile.md` confirms `aaDCT` is a logging BSTR name, not a table; `LmxProxy.dll`'s Fire_* event handlers receive already-populated `MXSTATUS_PROXY[]` from per-event context synthesis upstream, not from a static lookup. Verbatim preservation is the canonical answer. |
|
||||
| ASB write timestamp + status fields | M5 | extended ASB Write/PublishWriteComplete probe |
|
||||
| ASB no-communication source-level evidence (`work_remain.md:198`) | M5 | live capture against an unconfigured ASB endpoint |
|
||||
| Partial-cleanup behavior after channel failure (`work_remain.md:196-197`) | M4/M5 | inject mid-flight failure during subscribe, observe cleanup state |
|
||||
|
||||
Reference in New Issue
Block a user