Skip to content

Commit cbab54c

Browse files
committed
[DSC] Resolve cross-image stub functions to their target image in context menu actions
This ensures the "Load /usr/lib/libFoo.dylib" context menu item will be offered when right-clicking on a stub function that calls into libFoo.dylib.
1 parent 639e929 commit cbab54c

1 file changed

Lines changed: 60 additions & 6 deletions

File tree

view/sharedcache/ui/SharedCacheUINotifications.cpp

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
#include "SharedCacheUINotifications.h"
66
#include <sharedcacheapi.h>
7+
#include "mediumlevelilinstruction.h"
78
#include "ui/sidebar.h"
89
#include "ui/linearview.h"
910
#include "ui/viewframe.h"
@@ -15,6 +16,57 @@ using namespace SharedCacheAPI;
1516

1617
UINotifications* UINotifications::m_instance = nullptr;
1718

19+
// Resolve a stub function at `addr` to the constant target of its jump or tail call. Stub functions
20+
// are identified by the `j_` symbol prefix that the shared cache workflow applies when renaming them.
21+
static std::optional<uint64_t> ResolveStubTarget(BinaryView& view, uint64_t addr)
22+
{
23+
auto symbol = view.GetSymbolByAddress(addr);
24+
if (!symbol || symbol->GetShortName().rfind("j_", 0) != 0)
25+
return std::nullopt;
26+
27+
auto func = view.GetAnalysisFunction(view.GetDefaultPlatform(), addr);
28+
if (!func)
29+
return std::nullopt;
30+
31+
// Skip any function that is very clearly not a stub (not a single basic block with only a few instructions).
32+
const auto blocks = func->GetBasicBlocks();
33+
constexpr uint64_t maxStubLength = 0x20;
34+
if (blocks.size() != 1 || blocks[0]->GetLength() > maxStubLength)
35+
return std::nullopt;
36+
37+
auto mlil = func->GetMediumLevelIL();
38+
if (!mlil)
39+
return std::nullopt;
40+
41+
const auto mlilBlocks = mlil->GetBasicBlocks();
42+
if (mlilBlocks.size() != 1 || mlilBlocks[0]->GetEnd() == mlilBlocks[0]->GetStart())
43+
return std::nullopt;
44+
45+
// The jump or tail call terminates the stub's single basic block, so it can only be the last instruction.
46+
const auto instr = mlil->GetInstruction(mlilBlocks[0]->GetEnd() - 1);
47+
if (instr.operation != MLIL_JUMP && instr.operation != MLIL_TAILCALL)
48+
return std::nullopt;
49+
50+
const auto dest = instr.GetDestExpr();
51+
if (dest.operation != MLIL_CONST_PTR && dest.operation != MLIL_CONST)
52+
return std::nullopt;
53+
54+
return dest.GetConstant();
55+
}
56+
57+
// The address a token-based load action should operate on. A stub function's address is in an
58+
// already-loaded image, so resolve it to its target and offer to load what the stub jumps to.
59+
static uint64_t TokenAddress(const UIActionContext& ctx)
60+
{
61+
uint64_t addr = ctx.token.token.value;
62+
if (!ctx.binaryView->GetSectionsAt(addr).empty())
63+
{
64+
if (auto target = ResolveStubTarget(*ctx.binaryView, addr))
65+
return *target;
66+
}
67+
return addr;
68+
}
69+
1870
void UINotifications::init()
1971
{
2072
m_instance = new UINotifications;
@@ -95,19 +147,21 @@ void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const Q
95147
};
96148

97149
auto loadRegionTokenAction = [](const UIActionContext& ctx) {
150+
uint64_t addr = TokenAddress(ctx);
98151
BackgroundThread::create(ctx.context->mainWindow())
99-
->thenBackground([ctx](){ loadRegionAtAddr(*ctx.binaryView, ctx.token.token.value); })
152+
->thenBackground([ctx, addr](){ loadRegionAtAddr(*ctx.binaryView, addr); })
100153
->start();
101154
};
102155

103156
auto loadImageTokenAction = [](const UIActionContext& ctx) {
157+
uint64_t addr = TokenAddress(ctx);
104158
BackgroundThread::create(ctx.context->mainWindow())
105-
->thenBackground([ctx](){ loadImageAtAddr(*ctx.binaryView, ctx.token.token.value); })
159+
->thenBackground([ctx, addr](){ loadImageAtAddr(*ctx.binaryView, addr); })
106160
->start();
107161
};
108162

109163
auto isValidUnloadedRegionAction = [](const UIActionContext& ctx) {
110-
uint64_t addr = ctx.token.token.value;
164+
uint64_t addr = TokenAddress(ctx);
111165
// Check if the region is already loaded in the view.
112166
if (!ctx.binaryView->GetSectionsAt(addr).empty())
113167
return false;
@@ -118,7 +172,7 @@ void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const Q
118172
};
119173

120174
auto isValidUnloadedImageAction = [](const UIActionContext& ctx) {
121-
uint64_t addr = ctx.token.token.value;
175+
uint64_t addr = TokenAddress(ctx);
122176
// Check if the image is already loaded in the view.
123177
if (!ctx.binaryView->GetSectionsAt(addr).empty())
124178
return false;
@@ -139,7 +193,7 @@ void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const Q
139193
auto controller = SharedCacheController::GetController(*ctx.binaryView);
140194
if (!controller)
141195
return QString("NO CONTROLLER");
142-
uint64_t addr = ctx.token.token.value;
196+
uint64_t addr = TokenAddress(ctx);
143197
auto region = controller->GetRegionContaining(addr);
144198
if (!region)
145199
return QString("NO REGION");
@@ -150,7 +204,7 @@ void UINotifications::OnViewChange(UIContext* context, ViewFrame* frame, const Q
150204
auto controller = SharedCacheController::GetController(*ctx.binaryView);
151205
if (!controller)
152206
return QString("NO CONTROLLER");
153-
uint64_t addr = ctx.token.token.value;
207+
uint64_t addr = TokenAddress(ctx);
154208
auto image = controller->GetImageContaining(addr);
155209
if (!image)
156210
return QString("NO IMAGE");

0 commit comments

Comments
 (0)