Skip to content

Commit 845dcf2

Browse files
committed
Preliminary Itanium RTTI virtual function table support
Still needs to support edge cases and multiple vtables, as well as naming needs to be adjusted
1 parent 31ef836 commit 845dcf2

File tree

2 files changed

+243
-4
lines changed

2 files changed

+243
-4
lines changed

plugins/rtti/itanium.cpp

Lines changed: 238 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,35 @@ std::optional<TypeInfoVariant> ReadTypeInfoVariant(BinaryView *view, uint64_t ob
238238
}
239239

240240

241+
Ref<Metadata> ItaniumRTTIProcessor::SerializedMetadata()
242+
{
243+
std::map<std::string, Ref<Metadata> > classesMeta;
244+
for (auto &[coLocatorAddr, classInfo]: m_classInfo)
245+
{
246+
auto addrStr = std::to_string(coLocatorAddr);
247+
classesMeta[addrStr] = classInfo.SerializedMetadata();
248+
}
249+
250+
std::map<std::string, Ref<Metadata> > msvcMeta;
251+
msvcMeta["classes"] = new Metadata(classesMeta);
252+
return new Metadata(msvcMeta);
253+
}
254+
255+
256+
void ItaniumRTTIProcessor::DeserializedMetadata(const Ref<Metadata> &metadata)
257+
{
258+
std::map<std::string, Ref<Metadata> > msvcMeta = metadata->GetKeyValueStore();
259+
if (msvcMeta.find("classes") != msvcMeta.end())
260+
{
261+
for (auto &[objectAddrStr, classInfoMeta]: msvcMeta["classes"]->GetKeyValueStore())
262+
{
263+
uint64_t objectAddr = std::stoull(objectAddrStr);
264+
m_classInfo[objectAddr] = ClassInfo::DeserializedMetadata(classInfoMeta);
265+
}
266+
}
267+
}
268+
269+
241270
std::optional<ClassInfo> ItaniumRTTIProcessor::ProcessRTTI(uint64_t objectAddr)
242271
{
243272
// TODO: You cant get subobject offsets from rtti, its stored above this ptr in vtable.
@@ -292,6 +321,122 @@ std::optional<ClassInfo> ItaniumRTTIProcessor::ProcessRTTI(uint64_t objectAddr)
292321
}
293322

294323

324+
std::optional<VirtualFunctionTableInfo> ItaniumRTTIProcessor::ProcessVTT(uint64_t vttAddr, const ClassInfo &classInfo)
325+
{
326+
VirtualFunctionTableInfo vttInfo = {vttAddr};
327+
// Gather all virtual functions
328+
BinaryReader reader = BinaryReader(m_view);
329+
reader.Seek(vttAddr);
330+
std::vector<Ref<Function> > virtualFunctions = {};
331+
while (true)
332+
{
333+
uint64_t vFuncAddr = reader.ReadPointer();
334+
auto funcs = m_view->GetAnalysisFunctionsForAddress(vFuncAddr);
335+
if (funcs.empty())
336+
{
337+
Ref<Segment> segment = m_view->GetSegmentAt(vFuncAddr);
338+
if (segment == nullptr || !(segment->GetFlags() & (SegmentExecutable | SegmentDenyWrite)))
339+
{
340+
// Last CompleteObjectLocator or hit the next CompleteObjectLocator
341+
break;
342+
}
343+
// TODO: Is likely a function check here?
344+
m_logger->LogDebug("Discovered function from virtual function table... %llx", vFuncAddr);
345+
auto vFunc = m_view->AddFunctionForAnalysis(m_view->GetDefaultPlatform(), vFuncAddr, true);
346+
funcs.emplace_back(vFunc);
347+
}
348+
// Only ever add one function.
349+
virtualFunctions.emplace_back(funcs.front());
350+
}
351+
352+
if (virtualFunctions.empty())
353+
{
354+
m_logger->LogDebug("Skipping empty virtual function table... %llx", vttAddr);
355+
return std::nullopt;
356+
}
357+
358+
for (auto &func: virtualFunctions)
359+
vttInfo.virtualFunctions.emplace_back(VirtualFunctionInfo{func->GetStart()});
360+
361+
// Create virtual function table type
362+
auto vftTypeName = fmt::format("{}::VTable", classInfo.className);
363+
if (classInfo.baseClassName.has_value())
364+
{
365+
vftTypeName = fmt::format("{}::{}", classInfo.baseClassName.value(), vftTypeName);
366+
// TODO: What is the correct form for the name?
367+
}
368+
// TODO: Hack the debug type id is used here to allow the PDB type (debug info) to overwrite the RTTI vtable type.
369+
auto typeId = Type::GenerateAutoDebugTypeId(vftTypeName);
370+
Ref<Type> vftType = m_view->GetTypeById(typeId);
371+
372+
if (vftType == nullptr)
373+
{
374+
size_t addrSize = m_view->GetAddressSize();
375+
StructureBuilder vftBuilder = {};
376+
vftBuilder.SetPropagateDataVariableReferences(true);
377+
size_t vFuncIdx = 0;
378+
379+
// Until https://github.com/Vector35/binaryninja-api/issues/5982 is fixed
380+
auto vftSize = virtualFunctions.size() * addrSize;
381+
vftBuilder.SetWidth(vftSize);
382+
383+
if (auto baseVft = classInfo.baseVft)
384+
{
385+
if (classInfo.baseVft->virtualFunctions.size() <= virtualFunctions.size())
386+
{
387+
// Adjust the current vFunc index to the end of the shared vFuncs.
388+
vFuncIdx = classInfo.baseVft->virtualFunctions.size();
389+
virtualFunctions.erase(virtualFunctions.begin(), virtualFunctions.begin() + vFuncIdx);
390+
// We should set the vtable as a base class so that xrefs are propagated (among other things).
391+
// NOTE: this means that `this` params will be assumed pre-adjusted, this is normally fine assuming type propagation
392+
// NOTE: never occurs on the vft types. Other-wise we need to change this.
393+
auto baseVftTypeName = fmt::format("{}::VTable", classInfo.baseClassName.value());
394+
NamedTypeReferenceBuilder baseVftNTR;
395+
baseVftNTR.SetName(baseVftTypeName);
396+
// Width is unresolved here so that we can keep non-base vfuncs un-inherited.
397+
auto baseVftSize = vFuncIdx * addrSize;
398+
vftBuilder.SetBaseStructures({ BaseStructure(baseVftNTR.Finalize(), 0, baseVftSize) });
399+
}
400+
else
401+
{
402+
LogWarn("Skipping adjustments for base VFT with more functions than sub VFT... %llx", vttAddr);
403+
}
404+
}
405+
406+
for (auto &&vFunc: virtualFunctions)
407+
{
408+
auto vFuncName = fmt::format("vFunc_{}", vFuncIdx);
409+
// If we have a better name, use it.
410+
auto vFuncSymName = vFunc->GetSymbol()->GetShortName();
411+
if (vFuncSymName.compare(0, 4, "sub_") != 0)
412+
vFuncName = vFunc->GetSymbol()->GetShortName();
413+
// MyClass::func -> func
414+
std::size_t pos = vFuncName.rfind("::");
415+
if (pos != std::string::npos)
416+
vFuncName = vFuncName.substr(pos + 2);
417+
418+
// NOTE: The analyzed function type might not be available here.
419+
auto vFuncOffset = vFuncIdx * addrSize;
420+
vftBuilder.AddMemberAtOffset(
421+
Type::PointerType(addrSize, vFunc->GetType(), true), vFuncName, vFuncOffset);
422+
vFuncIdx++;
423+
}
424+
m_view->DefineType(typeId, vftTypeName,
425+
Confidence(TypeBuilder::StructureType(vftBuilder.Finalize()).Finalize(), RTTI_CONFIDENCE));
426+
}
427+
428+
auto vftName = fmt::format("_vtable_for_", classInfo.className);
429+
if (classInfo.baseClassName.has_value())
430+
vftName += fmt::format("{{for `{}'}}", classInfo.baseClassName.value());
431+
auto vttSymbol = m_view->GetSymbolByAddress(vttAddr);
432+
if (vttSymbol != nullptr)
433+
m_view->UndefineAutoSymbol(vttSymbol);
434+
m_view->DefineAutoSymbol(new Symbol{DataSymbol, vftName, vttAddr});
435+
m_view->DefineDataVariable(vttAddr, Confidence(Type::NamedType(m_view, vftTypeName), RTTI_CONFIDENCE));
436+
return vttInfo;
437+
}
438+
439+
295440
ItaniumRTTIProcessor::ItaniumRTTIProcessor(const Ref<BinaryView> &view, bool useMangled, bool checkRData, bool vftSweep) : m_view(view)
296441
{
297442
m_logger = new Logger("Itanium RTTI");
@@ -303,9 +448,8 @@ ItaniumRTTIProcessor::ItaniumRTTIProcessor(const Ref<BinaryView> &view, bool use
303448
auto metadata = view->QueryMetadata(VIEW_METADATA_RTTI);
304449
if (metadata != nullptr)
305450
{
306-
// TODO: This will pull in microsoft RTTI, which is really weird behavior possibly.
307451
// Load in metadata to the processor.
308-
// DeserializedMetadata(metadata);
452+
DeserializedMetadata(metadata);
309453
}
310454
}
311455

@@ -338,4 +482,96 @@ void ItaniumRTTIProcessor::ProcessRTTI()
338482
auto end_time = std::chrono::high_resolution_clock::now();
339483
std::chrono::duration<double> elapsed_time = end_time - start_time;
340484
m_logger->LogInfo("ProcessRTTI took %f seconds", elapsed_time.count());
485+
}
486+
487+
488+
void ItaniumRTTIProcessor::ProcessVTT()
489+
{
490+
std::map<uint64_t, uint64_t> vftMap = {};
491+
std::map<uint64_t, std::optional<VirtualFunctionTableInfo>> vftFinishedMap = {};
492+
auto start_time = std::chrono::high_resolution_clock::now();
493+
for (auto &[coLocatorAddr, classInfo]: m_classInfo)
494+
{
495+
for (auto &ref: m_view->GetDataReferences(coLocatorAddr))
496+
{
497+
// TODO: This is not pointing at where it should, remember that the vtable will be inside another structure.
498+
auto vftAddr = ref + m_view->GetAddressSize();
499+
vftMap[coLocatorAddr] = vftAddr;
500+
}
501+
}
502+
503+
if (virtualFunctionTableSweep)
504+
{
505+
BinaryReader optReader = BinaryReader(m_view);
506+
auto addrSize = m_view->GetAddressSize();
507+
auto scan = [&](const Ref<Segment> &segment) {
508+
uint64_t startAddr = segment->GetStart();
509+
uint64_t endAddr = segment->GetEnd();
510+
for (uint64_t vtableAddr = startAddr; vtableAddr < endAddr - 0x10; vtableAddr += addrSize)
511+
{
512+
optReader.Seek(vtableAddr);
513+
uint64_t coLocatorAddr = optReader.ReadPointer();
514+
auto coLocator = m_classInfo.find(coLocatorAddr);
515+
if (coLocator == m_classInfo.end())
516+
continue;
517+
// Found a vtable reference to colocator.
518+
vftMap[coLocatorAddr] = vtableAddr + addrSize;
519+
}
520+
};
521+
522+
// Scan data sections for virtual function tables.
523+
auto rdataSection = m_view->GetSectionByName(".rdata");
524+
for (const Ref<Segment> &segment: m_view->GetSegments())
525+
{
526+
if (segment->GetFlags() == (SegmentReadable | SegmentContainsData))
527+
{
528+
m_logger->LogDebug("Attempting to find VirtualFunctionTables in segment %llx", segment->GetStart());
529+
scan(segment);
530+
}
531+
else if (checkWritableRData && rdataSection && rdataSection->GetStart() == segment->GetStart())
532+
{
533+
m_logger->LogDebug("Attempting to find VirtualFunctionTables in writable rdata segment %llx",
534+
segment->GetStart());
535+
scan(segment);
536+
}
537+
}
538+
}
539+
540+
auto GetCachedVFTInfo = [&](uint64_t vftAddr, const ClassInfo& classInfo) {
541+
// Check in the cache so that we don't process vfts more than once.
542+
auto cachedVftInfo = vftFinishedMap.find(vftAddr);
543+
if (cachedVftInfo != vftFinishedMap.end())
544+
return cachedVftInfo->second;
545+
auto vftInfo = ProcessVTT(vftAddr, classInfo);
546+
vftFinishedMap[vftAddr] = vftInfo;
547+
return vftInfo;
548+
};
549+
550+
for (const auto &[coLocatorAddr, vftAddr]: vftMap)
551+
{
552+
auto classInfo = m_classInfo.find(coLocatorAddr)->second;
553+
if (classInfo.baseClassName.has_value())
554+
{
555+
// Process base vtable and add it to the class info.
556+
for (auto& [baseCoLocAddr, baseClassInfo] : m_classInfo)
557+
{
558+
if (baseClassInfo.className == classInfo.baseClassName.value())
559+
{
560+
uint64_t baseVftAddr = vftMap[baseCoLocAddr];
561+
if (auto baseVftInfo = GetCachedVFTInfo(baseVftAddr, baseClassInfo))
562+
{
563+
classInfo.baseVft = baseVftInfo.value();
564+
break;
565+
}
566+
}
567+
}
568+
}
569+
570+
if (auto vftInfo = GetCachedVFTInfo(vftAddr, classInfo))
571+
classInfo.vft = vftInfo.value();
572+
}
573+
574+
auto end_time = std::chrono::high_resolution_clock::now();
575+
std::chrono::duration<double> elapsed_time = end_time - start_time;
576+
m_logger->LogInfo("ProcessVFT took %f seconds", elapsed_time.count());
341577
}

plugins/rtti/plugin.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,10 @@ void RTTIAnalysis(const Ref<AnalysisContext>& analysisContext)
3131
else
3232
{
3333
// TODO: We currently only want to check for itanium rtti on non windows platforms
34+
// TODO: This needs to always run.
3435
auto processor = RTTI::Itanium::ItaniumRTTIProcessor(view);
3536
processor.ProcessRTTI();
36-
// view->StoreMetadata(VIEW_METADATA_RTTI, processor.SerializedMetadata(), true);
37+
view->StoreMetadata(VIEW_METADATA_RTTI, processor.SerializedMetadata(), true);
3738
}
3839
}
3940

@@ -46,17 +47,19 @@ void VFTAnalysis(const Ref<AnalysisContext>& analysisContext)
4647
// TODO: Run for both itanium and ms (depending on platform)
4748
auto processor = RTTI::Microsoft::MicrosoftRTTIProcessor(view);
4849
processor.ProcessVFT();
50+
auto itaniumProcessor = RTTI::Itanium::ItaniumRTTIProcessor(view);
51+
itaniumProcessor.ProcessVTT();
4952
view->StoreMetadata(VIEW_METADATA_RTTI, processor.SerializedMetadata(), true);
5053
}
5154

55+
5256
void MakeItaniumRTTIHere(Ref<BinaryView> view, uint64_t addr)
5357
{
5458
auto processor = RTTI::Itanium::ItaniumRTTIProcessor(view);
5559
processor.ProcessRTTI(addr);
5660
}
5761

5862

59-
6063
extern "C" {
6164
BN_DECLARE_CORE_ABI_VERSION
6265

0 commit comments

Comments
 (0)