From d5c7c6f6a081ca0b29b8bb31da3e0b1791b919bf Mon Sep 17 00:00:00 2001 From: "Mr. eXoDia" Date: Mon, 13 Jul 2015 03:51:54 +0200 Subject: [PATCH] DBG: more fixes in analysis + added ExceptionDirectoryAnalysis class --- x64_dbg_dbg/FunctionPass.cpp | 3 + x64_dbg_dbg/controlflowanalysis.cpp | 133 +++++++++++++++--- x64_dbg_dbg/controlflowanalysis.h | 11 +- x64_dbg_dbg/exceptiondirectoryanalysis.cpp | 106 ++++++++++++++ x64_dbg_dbg/exceptiondirectoryanalysis.h | 26 ++++ x64_dbg_dbg/instruction.cpp | 24 +++- x64_dbg_dbg/instruction.h | 1 + ...unctionanalysis.cpp => linearanalysis.cpp} | 18 +-- .../{functionanalysis.h => linearanalysis.h} | 10 +- x64_dbg_dbg/x64_dbg.cpp | 3 +- x64_dbg_dbg/x64_dbg_dbg.vcxproj | 6 +- x64_dbg_dbg/x64_dbg_dbg.vcxproj.filters | 18 ++- 12 files changed, 310 insertions(+), 49 deletions(-) create mode 100644 x64_dbg_dbg/exceptiondirectoryanalysis.cpp create mode 100644 x64_dbg_dbg/exceptiondirectoryanalysis.h rename x64_dbg_dbg/{functionanalysis.cpp => linearanalysis.cpp} (87%) rename x64_dbg_dbg/{functionanalysis.h => linearanalysis.h} (74%) diff --git a/x64_dbg_dbg/FunctionPass.cpp b/x64_dbg_dbg/FunctionPass.cpp index e5853abe..4d2d1084 100644 --- a/x64_dbg_dbg/FunctionPass.cpp +++ b/x64_dbg_dbg/FunctionPass.cpp @@ -98,6 +98,8 @@ bool FunctionPass::Analyse() std::sort(funcs.begin(), funcs.end()); funcs.erase(std::unique(funcs.begin(), funcs.end()), funcs.end()); + dprintf("%u functions\n", funcs.size()); + FunctionClear(); for(auto & func : funcs) { @@ -168,6 +170,7 @@ void FunctionPass::AnalysisWorker(uint Start, uint End, std::vector void FunctionPass::FindFunctionWorkerPrepass(uint Start, uint End, std::vector* Blocks) { + return; const uint minFunc = std::next(m_MainBlocks.begin(), Start)->VirtualStart; const uint maxFunc = std::next(m_MainBlocks.begin(), End - 1)->VirtualEnd; diff --git a/x64_dbg_dbg/controlflowanalysis.cpp b/x64_dbg_dbg/controlflowanalysis.cpp index 6d46e188..70b2063b 100644 --- a/x64_dbg_dbg/controlflowanalysis.cpp +++ b/x64_dbg_dbg/controlflowanalysis.cpp @@ -1,28 +1,81 @@ #include "controlflowanalysis.h" #include "console.h" +#include "module.h" +#include "TitanEngine/TitanEngine.h" +#include "memory.h" -ControlFlowAnalysis::ControlFlowAnalysis(uint base, uint size) : Analysis(base, size) +ControlFlowAnalysis::ControlFlowAnalysis(uint base, uint size, bool exceptionDirectory) : Analysis(base, size) { -} + _functionInfoData = nullptr; +#ifdef _WIN64 + // This will only be valid if the address range is within a loaded module + _moduleBase = ModBaseFromAddr(base); -void ControlFlowAnalysis::Analyse() -{ - dputs("Starting analysis..."); - DWORD ticks = GetTickCount(); + if(exceptionDirectory && _moduleBase != 0) + { + char modulePath[MAX_PATH]; + memset(modulePath, 0, sizeof(modulePath)); - BasicBlockStarts(); - dprintf("Basic block starts in %ums!\n", GetTickCount() - ticks); - ticks = GetTickCount(); - - BasicBlocks(); - dprintf("Basick blocks in %ums!\n", GetTickCount() - ticks); - ticks = GetTickCount(); - - Functions(); - dprintf("Functions in %ums!\n", GetTickCount() - ticks); - - dprintf("Analysis finished!\n"); -} + ModPathFromAddr(_moduleBase, modulePath, ARRAYSIZE(modulePath)); + + HANDLE fileHandle; + DWORD fileSize; + HANDLE fileMapHandle; + ULONG_PTR fileMapVa; + if(StaticFileLoadW( + StringUtils::Utf8ToUtf16(modulePath).c_str(), + UE_ACCESS_READ, + false, + &fileHandle, + &fileSize, + &fileMapHandle, + &fileMapVa)) + { + // Find a pointer to IMAGE_DIRECTORY_ENTRY_EXCEPTION for later use + ULONG_PTR virtualOffset = GetPE32DataFromMappedFile(fileMapVa, IMAGE_DIRECTORY_ENTRY_EXCEPTION, UE_SECTIONVIRTUALOFFSET); + _functionInfoSize = (uint)GetPE32DataFromMappedFile(fileMapVa, IMAGE_DIRECTORY_ENTRY_EXCEPTION, UE_SECTIONVIRTUALSIZE); + + // Unload the file + StaticFileUnloadW(nullptr, false, fileHandle, fileSize, fileMapHandle, fileMapVa); + + // Get a copy of the function table + if(virtualOffset) + { + // Read the table into a buffer + _functionInfoData = emalloc(_functionInfoSize); + + if(_functionInfoData) + MemRead(virtualOffset + _moduleBase, _functionInfoData, _functionInfoSize); + } + } + } +#endif //_WIN64 +} + +ControlFlowAnalysis::~ControlFlowAnalysis() +{ + if(_functionInfoData) + efree(_functionInfoData); +} + +void ControlFlowAnalysis::Analyse() +{ + dputs("Starting analysis..."); + DWORD ticks = GetTickCount(); + + BasicBlockStarts(); + dprintf("Basic block starts in %ums!\n", GetTickCount() - ticks); + ticks = GetTickCount(); + + BasicBlocks(); + dprintf("Basic blocks in %ums!\n", GetTickCount() - ticks); + ticks = GetTickCount(); + + Functions(); + dprintf("Functions in %ums!\n", GetTickCount() - ticks); + + dprintf("Analysis finished!\n"); +} void ControlFlowAnalysis::SetMarkers() { @@ -83,7 +136,7 @@ void ControlFlowAnalysis::BasicBlockStarts() _blockStarts.insert(addr); } } - else if(_cp.InGroup(CS_GRP_RET) || _cp.InGroup(CS_GRP_INT)) //RET/INT break control flow + else if(_cp.InGroup(CS_GRP_RET) || _cp.GetId() == X86_INS_INT3) //RET/INT3 break control flow { bSkipFilling = true; //skip INT3/NOP/whatever filling bytes (those are not part of the control flow) } @@ -139,7 +192,7 @@ void ControlFlowAnalysis::BasicBlocks() prevaddr = addr; if(_cp.Disassemble(addr, TranslateAddress(addr), MAX_DISASM_BUFFER)) { - if(_cp.InGroup(CS_GRP_RET)) + if(_cp.InGroup(CS_GRP_RET) || _cp.GetId() == X86_INS_INT3) { insertBlock(BasicBlock(start, addr, 0, 0)); //leaf block break; @@ -166,6 +219,23 @@ void ControlFlowAnalysis::BasicBlocks() } } _blockStarts.clear(); + +#ifdef _WIN64 + int count = 0; + EnumerateFunctionRuntimeEntries64([&](PRUNTIME_FUNCTION Function) + { + const uint funcAddr = _moduleBase + Function->BeginAddress; + const uint funcEnd = _moduleBase + Function->EndAddress; + + // If within limits... + if(funcAddr >= _base && funcAddr < _base + _size) + _functionStarts.insert(funcAddr); + count++; + return true; + }); + dprintf("%u functions from the exception directory...\n", count); +#endif // _WIN64 + dprintf("%u basic blocks, %u function starts detected...\n", _blocks.size(), _functionStarts.size()); } @@ -309,4 +379,23 @@ uint ControlFlowAnalysis::GetReferenceOperand() } } return 0; -} \ No newline at end of file +} + +#ifdef _WIN64 +void ControlFlowAnalysis::EnumerateFunctionRuntimeEntries64(std::function Callback) +{ + if(!_functionInfoData) + return; + + // Get the table pointer and size + auto functionTable = (PRUNTIME_FUNCTION)_functionInfoData; + uint totalCount = (_functionInfoSize / sizeof(RUNTIME_FUNCTION)); + + // Enumerate each entry + for(ULONG i = 0; i < totalCount; i++) + { + if(!Callback(&functionTable[i])) + break; + } +} +#endif // _WIN64 \ No newline at end of file diff --git a/x64_dbg_dbg/controlflowanalysis.h b/x64_dbg_dbg/controlflowanalysis.h index 4e827375..d5a82a97 100644 --- a/x64_dbg_dbg/controlflowanalysis.h +++ b/x64_dbg_dbg/controlflowanalysis.h @@ -4,11 +4,13 @@ #include "_global.h" #include "analysis.h" #include "addrinfo.h" +#include class ControlFlowAnalysis : public Analysis { public: - explicit ControlFlowAnalysis(uint base, uint size); + explicit ControlFlowAnalysis(uint base, uint size, bool exceptionDirectory); + ~ControlFlowAnalysis(); void Analyse() override; void SetMarkers() override; @@ -47,6 +49,10 @@ private: typedef std::set UintSet; + uint _moduleBase; + uint _functionInfoSize; + void* _functionInfoData; + UintSet _blockStarts; UintSet _functionStarts; std::map _blocks; //start of block -> block @@ -63,6 +69,9 @@ private: uint findFunctionStart(BasicBlock* block, UintSet* parents); String blockToString(BasicBlock* block); uint GetReferenceOperand(); +#ifdef _WIN64 + void EnumerateFunctionRuntimeEntries64(std::function Callback); +#endif // _WIN64 }; #endif //_CONTROLFLOWANALYSIS_H \ No newline at end of file diff --git a/x64_dbg_dbg/exceptiondirectoryanalysis.cpp b/x64_dbg_dbg/exceptiondirectoryanalysis.cpp new file mode 100644 index 00000000..ccaea1a2 --- /dev/null +++ b/x64_dbg_dbg/exceptiondirectoryanalysis.cpp @@ -0,0 +1,106 @@ +#include "exceptiondirectoryanalysis.h" +#include "module.h" +#include "TitanEngine/TitanEngine.h" +#include "memory.h" +#include "console.h" +#include "function.h" + +ExceptionDirectoryAnalysis::ExceptionDirectoryAnalysis(uint base, uint size) : Analysis(base, size) +{ + _functionInfoData = nullptr; +#ifdef _WIN64 + // This will only be valid if the address range is within a loaded module + _moduleBase = ModBaseFromAddr(base); + + if(_moduleBase != 0) + { + char modulePath[MAX_PATH]; + memset(modulePath, 0, sizeof(modulePath)); + + ModPathFromAddr(_moduleBase, modulePath, ARRAYSIZE(modulePath)); + + HANDLE fileHandle; + DWORD fileSize; + HANDLE fileMapHandle; + ULONG_PTR fileMapVa; + if(StaticFileLoadW( + StringUtils::Utf8ToUtf16(modulePath).c_str(), + UE_ACCESS_READ, + false, + &fileHandle, + &fileSize, + &fileMapHandle, + &fileMapVa)) + { + // Find a pointer to IMAGE_DIRECTORY_ENTRY_EXCEPTION for later use + ULONG_PTR virtualOffset = GetPE32DataFromMappedFile(fileMapVa, IMAGE_DIRECTORY_ENTRY_EXCEPTION, UE_SECTIONVIRTUALOFFSET); + _functionInfoSize = (uint)GetPE32DataFromMappedFile(fileMapVa, IMAGE_DIRECTORY_ENTRY_EXCEPTION, UE_SECTIONVIRTUALSIZE); + + // Unload the file + StaticFileUnloadW(nullptr, false, fileHandle, fileSize, fileMapHandle, fileMapVa); + + // Get a copy of the function table + if(virtualOffset) + { + // Read the table into a buffer + _functionInfoData = emalloc(_functionInfoSize); + + if(_functionInfoData) + MemRead(virtualOffset + _moduleBase, _functionInfoData, _functionInfoSize); + } + } + } +#endif //_WIN64 +} + +ExceptionDirectoryAnalysis::~ExceptionDirectoryAnalysis() +{ + if(_functionInfoData) + efree(_functionInfoData); +} + +void ExceptionDirectoryAnalysis::Analyse() +{ +#ifdef _WIN64 + EnumerateFunctionRuntimeEntries64([&](PRUNTIME_FUNCTION Function) + { + const uint funcAddr = _moduleBase + Function->BeginAddress; + const uint funcEnd = _moduleBase + Function->EndAddress; + + // If within limits... + if(funcAddr >= _base && funcAddr < _base + _size) + _functions.push_back({ funcAddr, funcEnd }); + + return true; + }); + dprintf("%u functions discovered!\n", _functions.size()); +#else //x32 + dprintf("This kind of analysis doesn't work on x32 executables...\n"); +#endif // _WIN64 +} + +void ExceptionDirectoryAnalysis::SetMarkers() +{ + FunctionDelRange(_base, _base + _size); + for(const auto & function : _functions) + FunctionAdd(function.first, function.second, false); +} + +#ifdef _WIN64 +void ExceptionDirectoryAnalysis::EnumerateFunctionRuntimeEntries64(std::function Callback) +{ + if(!_functionInfoData) + return; + + // Get the table pointer and size + auto functionTable = (PRUNTIME_FUNCTION)_functionInfoData; + uint totalCount = (_functionInfoSize / sizeof(RUNTIME_FUNCTION)); + + // Enumerate each entry + for(uint i = 0; i < totalCount; i++) + { + if(!Callback(&functionTable[i])) + break; + } +} +#endif // _WIN64 \ No newline at end of file diff --git a/x64_dbg_dbg/exceptiondirectoryanalysis.h b/x64_dbg_dbg/exceptiondirectoryanalysis.h new file mode 100644 index 00000000..3aa039d9 --- /dev/null +++ b/x64_dbg_dbg/exceptiondirectoryanalysis.h @@ -0,0 +1,26 @@ +#ifndef _EXCEPTIONDIRECTORYANALYSIS_H +#define _EXCEPTIONDIRECTORYANALYSIS_H + +#include "analysis.h" +#include + +class ExceptionDirectoryAnalysis : public Analysis +{ +public: + explicit ExceptionDirectoryAnalysis(uint base, uint size); + ~ExceptionDirectoryAnalysis(); + void Analyse() override; + void SetMarkers() override; + +private: + uint _moduleBase; + uint _functionInfoSize; + void* _functionInfoData; + std::vector> _functions; + +#ifdef _WIN64 + void EnumerateFunctionRuntimeEntries64(std::function Callback); +#endif // _WIN64 +}; + +#endif //_EXCEPTIONDIRECTORYANALYSIS_H \ No newline at end of file diff --git a/x64_dbg_dbg/instruction.cpp b/x64_dbg_dbg/instruction.cpp index 0ff9870a..c724d608 100644 --- a/x64_dbg_dbg/instruction.cpp +++ b/x64_dbg_dbg/instruction.cpp @@ -26,9 +26,10 @@ #include "module.h" #include "stringformat.h" #include "filereader.h" -#include "functionanalysis.h" +#include "linearanalysis.h" #include "controlflowanalysis.h" #include "analysis_nukem.h" +#include "exceptiondirectoryanalysis.h" static bool bRefinit = false; @@ -1865,6 +1866,7 @@ CMDRESULT cbInstrCapstone(int argc, char* argv[]) const cs_x86 & x86 = cp.x86(); int argcount = x86.op_count; dprintf("%s %s\n", instr->mnemonic, instr->op_str); + dprintf("%d, NOP=%d\n", cp.GetId(), X86_INS_NOP); for(int i = 0; i < argcount; i++) { const cs_x86_op & op = x86.operands[i]; @@ -1915,7 +1917,7 @@ CMDRESULT cbInstrAnalyse(int argc, char* argv[]) GuiSelectionGet(GUI_DISASSEMBLY, &sel); uint size = 0; uint base = MemFindBaseAddr(sel.start, &size); - FunctionAnalysis anal(base, size); + LinearAnalysis anal(base, size); anal.Analyse(); anal.SetMarkers(); GuiUpdateAllViews(); @@ -1924,17 +1926,33 @@ CMDRESULT cbInstrAnalyse(int argc, char* argv[]) CMDRESULT cbInstrCfanalyse(int argc, char* argv[]) { + bool exceptionDirectory = false; + if(argc > 1) + exceptionDirectory = true; SELECTIONDATA sel; GuiSelectionGet(GUI_DISASSEMBLY, &sel); uint size = 0; uint base = MemFindBaseAddr(sel.start, &size); - ControlFlowAnalysis anal(base, size); + ControlFlowAnalysis anal(base, size, exceptionDirectory); anal.Analyse(); //anal.SetMarkers(); GuiUpdateAllViews(); return STATUS_CONTINUE; } +CMDRESULT cbInstrExanalyse(int argc, char* argv[]) +{ + SELECTIONDATA sel; + GuiSelectionGet(GUI_DISASSEMBLY, &sel); + uint size = 0; + uint base = MemFindBaseAddr(sel.start, &size); + ExceptionDirectoryAnalysis anal(base, size); + anal.Analyse(); + anal.SetMarkers(); + GuiUpdateAllViews(); + return STATUS_CONTINUE; +} + CMDRESULT cbInstrVisualize(int argc, char* argv[]) { if(argc < 3) diff --git a/x64_dbg_dbg/instruction.h b/x64_dbg_dbg/instruction.h index a9e8eeee..035547fd 100644 --- a/x64_dbg_dbg/instruction.h +++ b/x64_dbg_dbg/instruction.h @@ -72,5 +72,6 @@ CMDRESULT cbInstrAnalyse(int argc, char* argv[]); CMDRESULT cbInstrVisualize(int argc, char* argv[]); CMDRESULT cbInstrMeminfo(int argc, char* argv[]); CMDRESULT cbInstrCfanalyse(int argc, char* argv[]); +CMDRESULT cbInstrExanalyse(int argc, char* argv[]); #endif // _INSTRUCTIONS_H diff --git a/x64_dbg_dbg/functionanalysis.cpp b/x64_dbg_dbg/linearanalysis.cpp similarity index 87% rename from x64_dbg_dbg/functionanalysis.cpp rename to x64_dbg_dbg/linearanalysis.cpp index 2631ef07..a620c287 100644 --- a/x64_dbg_dbg/functionanalysis.cpp +++ b/x64_dbg_dbg/linearanalysis.cpp @@ -1,13 +1,13 @@ -#include "functionanalysis.h" +#include "linearanalysis.h" #include "console.h" #include "memory.h" #include "function.h" -FunctionAnalysis::FunctionAnalysis(uint base, uint size) : Analysis(base, size) +LinearAnalysis::LinearAnalysis(uint base, uint size) : Analysis(base, size) { } -void FunctionAnalysis::Analyse() +void LinearAnalysis::Analyse() { dputs("Starting analysis..."); DWORD ticks = GetTickCount(); @@ -19,7 +19,7 @@ void FunctionAnalysis::Analyse() dprintf("Analysis finished in %ums!\n", GetTickCount() - ticks); } -void FunctionAnalysis::SetMarkers() +void LinearAnalysis::SetMarkers() { FunctionDelRange(_base, _base + _size); for(auto & function : _functions) @@ -30,7 +30,7 @@ void FunctionAnalysis::SetMarkers() } } -void FunctionAnalysis::SortCleanup() +void LinearAnalysis::SortCleanup() { //sort & remove duplicates std::sort(_functions.begin(), _functions.end()); @@ -38,7 +38,7 @@ void FunctionAnalysis::SortCleanup() _functions.erase(last, _functions.end()); } -void FunctionAnalysis::PopulateReferences() +void LinearAnalysis::PopulateReferences() { //linear immediate reference scan (call , push , mov [somewhere], ) for(uint i = 0; i < _size;) @@ -57,7 +57,7 @@ void FunctionAnalysis::PopulateReferences() SortCleanup(); } -void FunctionAnalysis::AnalyseFunctions() +void LinearAnalysis::AnalyseFunctions() { for(size_t i = 0; i < _functions.size(); i++) { @@ -79,7 +79,7 @@ void FunctionAnalysis::AnalyseFunctions() } } -uint FunctionAnalysis::FindFunctionEnd(uint start, uint maxaddr) +uint LinearAnalysis::FindFunctionEnd(uint start, uint maxaddr) { //disassemble first instruction for some heuristics if(_cp.Disassemble(start, TranslateAddress(start), MAX_DISASM_BUFFER)) @@ -132,7 +132,7 @@ uint FunctionAnalysis::FindFunctionEnd(uint start, uint maxaddr) return end < jumpback ? jumpback : end; } -uint FunctionAnalysis::GetReferenceOperand() +uint LinearAnalysis::GetReferenceOperand() { for(int i = 0; i < _cp.x86().op_count; i++) { diff --git a/x64_dbg_dbg/functionanalysis.h b/x64_dbg_dbg/linearanalysis.h similarity index 74% rename from x64_dbg_dbg/functionanalysis.h rename to x64_dbg_dbg/linearanalysis.h index 42bf126b..d472c5b6 100644 --- a/x64_dbg_dbg/functionanalysis.h +++ b/x64_dbg_dbg/linearanalysis.h @@ -1,13 +1,13 @@ -#ifndef _FUNCTIONANALYSIS_H -#define _FUNCTIONANALYSIS_H +#ifndef _LINEARANALYSIS_H +#define _LINEARANALYSIS_H #include "_global.h" #include "analysis.h" -class FunctionAnalysis : public Analysis +class LinearAnalysis : public Analysis { public: - explicit FunctionAnalysis(uint base, uint size); + explicit LinearAnalysis(uint base, uint size); void Analyse() override; void SetMarkers() override; @@ -37,4 +37,4 @@ private: uint GetReferenceOperand(); }; -#endif //_FUNCTIONANALYSIS_H \ No newline at end of file +#endif //_LINEARANALYSIS_H \ No newline at end of file diff --git a/x64_dbg_dbg/x64_dbg.cpp b/x64_dbg_dbg/x64_dbg.cpp index c1dd20fa..1ac2e1e0 100644 --- a/x64_dbg_dbg/x64_dbg.cpp +++ b/x64_dbg_dbg/x64_dbg.cpp @@ -189,7 +189,6 @@ static void registercommands() dbgcmdnew("yara", cbInstrYara, true); //yara test command dbgcmdnew("yaramod", cbInstrYaramod, true); //yara rule on module dbgcmdnew("analyse\1analyze\1anal", cbInstrAnalyse, true); //secret analysis command - dbgcmdnew("analyse_nukem\1analyze_nukem\1anal_nukem", cbInstrAnalyseNukem, true); //secret analysis command #2 //undocumented dbgcmdnew("bench", cbDebugBenchmark, true); //benchmark test (readmem etc) @@ -202,6 +201,8 @@ static void registercommands() dbgcmdnew("visualize", cbInstrVisualize, true); //visualize analysis dbgcmdnew("meminfo", cbInstrMeminfo, true); //command to debug memory map bugs dbgcmdnew("cfanal\1cfanalyse\1cfanalyze", cbInstrCfanalyse, true); //control flow analysis + dbgcmdnew("analyse_nukem\1analyze_nukem\1anal_nukem", cbInstrAnalyseNukem, true); //secret analysis command #2 + dbgcmdnew("exanal\1exanalyse\1exanalyze", cbInstrExanalyse, true); //exception directory analysis } static bool cbCommandProvider(char* cmd, int maxlen) diff --git a/x64_dbg_dbg/x64_dbg_dbg.vcxproj b/x64_dbg_dbg/x64_dbg_dbg.vcxproj index 7b323e4e..2615bbdf 100644 --- a/x64_dbg_dbg/x64_dbg_dbg.vcxproj +++ b/x64_dbg_dbg/x64_dbg_dbg.vcxproj @@ -40,10 +40,11 @@ + - + @@ -119,10 +120,11 @@ + - + diff --git a/x64_dbg_dbg/x64_dbg_dbg.vcxproj.filters b/x64_dbg_dbg/x64_dbg_dbg.vcxproj.filters index e303580f..4903b667 100644 --- a/x64_dbg_dbg/x64_dbg_dbg.vcxproj.filters +++ b/x64_dbg_dbg/x64_dbg_dbg.vcxproj.filters @@ -216,9 +216,6 @@ Source Files\Utilities - - Source Files\Analysis - Source Files\Utilities @@ -276,6 +273,12 @@ Source Files\Analysis + + Source Files\Analysis + + + Source Files\Analysis + @@ -566,9 +569,6 @@ Header Files\Third Party\capstone - - Header Files\Analysis - Header Files\Utilities @@ -632,5 +632,11 @@ Header Files\Interfaces/Exports\_scriptapi + + Header Files\Analysis + + + Header Files\Analysis + \ No newline at end of file