Skip to content

Commit 63e460d

Browse files
authored
fix: fixed two bugs in the ipc ghidra script (#1467)
1 parent 1ff1ff3 commit 63e460d

3 files changed

Lines changed: 181 additions & 2 deletions

File tree

src/plugins/analysis/ipc/docker/ipc_analyzer/ipc_analysis/helper_functions.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,8 @@ def get_vars_from_varnode(ghidra_analysis, func, varnode):
114114
:return: list
115115
"""
116116
result = []
117+
if varnode is None:
118+
return result
117119
addr_size = int(ghidra_analysis.current_program.getMetadata()['Address Size'])
118120
bitmask = 2**addr_size - 1
119121
local_variables = func.getAllVariables()
@@ -159,10 +161,18 @@ def find_source_value(ghidra_analysis, func, var, sources):
159161
# Handle p-code CAST of source_varnode
160162
if len(source_vars) == 0:
161163
varnode = source_varnode
162-
def_op = source_varnode.getDef()
164+
if varnode is None:
165+
continue
166+
def_op = varnode.getDef()
167+
if def_op is None:
168+
continue
163169
while def_op.getOpcode() == PcodeOp.CAST:
164170
varnode = def_op.getInput(0)
171+
if varnode is None:
172+
break
165173
def_op = varnode.getDef()
174+
if def_op is None:
175+
break
166176
if def_op is not None:
167177
source_vars = get_vars_from_varnode(ghidra_analysis, func, varnode)
168178
else:

src/plugins/analysis/ipc/docker/ipc_analyzer/resolve_format_strings/format_strings.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,11 @@ def filter_relevant_indices(start, arg_values, indices, format_types):
159159
"""
160160
result = []
161161
for i in indices:
162-
argument = arg_values[start + i]
162+
arg_index = start + i
163+
if arg_index >= len(arg_values):
164+
logging.warning('arg_index {} out of range for arg_values (length: {})'.format(arg_index, len(arg_values)))
165+
continue
166+
argument = arg_values[arg_index]
163167
for arg in argument:
164168
if isinstance(arg, format_types[i]) and string_is_printable(str(arg)):
165169
result.append(str(arg))
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import sys
2+
from unittest.mock import MagicMock
3+
4+
import pytest
5+
6+
7+
@pytest.fixture(autouse=True)
8+
def mock_ghidra_modules(monkeypatch):
9+
ghidra_mock = MagicMock()
10+
pcode_op_mock = MagicMock()
11+
pcode_op_mock.CALL = 1
12+
pcode_op_mock.CAST = 2
13+
pcode_op_mock.INT_ADD = 3
14+
pcode_op_mock.INT_SUB = 4
15+
pcode_op_mock.INT_MULT = 5
16+
pcode_op_mock.INT_DIV = 6
17+
pcode_op_mock.INT_AND = 7
18+
pcode_op_mock.INT_OR = 8
19+
pcode_op_mock.INT_XOR = 9
20+
pcode_op_mock.INT_EQUAL = 10
21+
pcode_op_mock.INT_NEGATE = 11
22+
pcode_op_mock.INT_ZEXT = 12
23+
pcode_op_mock.INT_SEXT = 13
24+
pcode_op_mock.INT_2COMP = 14
25+
pcode_op_mock.COPY = 15
26+
pcode_op_mock.CALLIND = 16
27+
pcode_op_mock.PIECE = 17
28+
pcode_op_mock.PTRSUB = 18
29+
pcode_op_mock.MULTIEQUAL = 19
30+
pcode_op_mock.INDIRECT = 20
31+
pcode_op_mock.LOAD = 21
32+
pcode_op_mock.RETURN = 22
33+
34+
mock_modules = {
35+
'ghidra': ghidra_mock,
36+
'ghidra.program': ghidra_mock.program,
37+
'ghidra.program.model': ghidra_mock.program.model,
38+
'ghidra.program.model.pcode': ghidra_mock.program.model.pcode,
39+
'ghidra.program.model.pcode.PcodeOp': pcode_op_mock,
40+
'ghidra.program.model.block': ghidra_mock.program.model.block,
41+
'ghidra.program.model.block.BasicBlockModel': MagicMock(),
42+
'ghidra.program.model.symbol': ghidra_mock.program.model.symbol,
43+
'ghidra.program.model.symbol.RefType': MagicMock(),
44+
'decompile': MagicMock(),
45+
'ipc_analysis.decompile': MagicMock(),
46+
'ipc_analysis.helper_functions': MagicMock(),
47+
}
48+
49+
for mod_name, mock in mock_modules.items():
50+
monkeypatch.setitem(sys.modules, mod_name, mock)
51+
52+
yield
53+
54+
for key in list(sys.modules):
55+
if 'ipc_analysis' in key or 'format_strings' in key:
56+
monkeypatch.delitem(sys.modules, key, raising=False)
57+
58+
59+
@pytest.fixture
60+
def format_strings():
61+
from plugins.analysis.ipc.docker.ipc_analyzer.resolve_format_strings import format_strings # noqa: PLC0415
62+
63+
return format_strings
64+
65+
66+
@pytest.fixture
67+
def helper_functions():
68+
from plugins.analysis.ipc.docker.ipc_analyzer.ipc_analysis import helper_functions # noqa: PLC0415
69+
70+
return helper_functions
71+
72+
73+
class TestFilterRelevantIndices:
74+
def test_index_out_of_range_handling(self, format_strings):
75+
"""
76+
Regression test: skip values that exceed arg_values length
77+
"""
78+
start = 1
79+
arg_values = [['arg1'], ['arg2']]
80+
indices = [0, 1, 2, 3]
81+
# Index 2 and 3 are out of range for arg_values
82+
format_types = [str, str, str, str]
83+
84+
result = format_strings.filter_relevant_indices(start, arg_values, indices, format_types)
85+
assert isinstance(result, list)
86+
assert len(result) == 1
87+
88+
def test_empty_arg_values(self, format_strings):
89+
start = 0
90+
arg_values = []
91+
indices = [0, 1]
92+
format_types = [str, str]
93+
94+
result = format_strings.filter_relevant_indices(start, arg_values, indices, format_types)
95+
96+
assert result == []
97+
98+
def test_negative_start_value(self, format_strings):
99+
start = -1
100+
arg_values = [['arg1'], ['arg2'], ['arg3']]
101+
indices = [0, 1, 2]
102+
format_types = [str, str, str]
103+
104+
# With start=-1, indices would be -1, 0, 1
105+
# -1 is a valid Python index (last element)
106+
result = format_strings.filter_relevant_indices(start, arg_values, indices, format_types)
107+
108+
assert isinstance(result, list)
109+
110+
111+
class MockGhidraAnalysis:
112+
def __init__(self):
113+
self.current_program = MockCurrentProgram()
114+
115+
class flat_api: # noqa: N801
116+
@staticmethod
117+
def getFunctionContaining(addr): # noqa: ARG004, N802
118+
return None
119+
120+
121+
class MockCurrentProgram:
122+
def getMetadata(self): # noqa: N802
123+
return {'Address Size': 64}
124+
125+
126+
class MockFunc:
127+
def getAllVariables(self): # noqa: N802
128+
return []
129+
130+
131+
class MockVarnode:
132+
def getDef(self): # noqa: N802
133+
return None # No definition, should normally return empty list
134+
135+
136+
class MockVar:
137+
pass
138+
139+
140+
@pytest.mark.parametrize('varnode', [None, MockVarnode()])
141+
def test_get_vars_from_varnode(helper_functions, varnode):
142+
"""
143+
Regression test: When varnode is None, the function should return an empty list
144+
instead of raising AttributeError: 'NoneType' object has no attribute 'getDef'.
145+
"""
146+
ghidra_analysis = MockGhidraAnalysis()
147+
func = MockFunc()
148+
149+
result = helper_functions.get_vars_from_varnode(ghidra_analysis, func, varnode)
150+
151+
assert result == []
152+
153+
154+
@pytest.mark.parametrize('varnode', [None, MockVarnode()])
155+
def test_none_source_varnode_handling(helper_functions, varnode):
156+
class MockSource:
157+
def getInput(self, index): # noqa: N802
158+
if index == 1:
159+
return varnode
160+
return None
161+
162+
sources = [MockSource()]
163+
result = helper_functions.find_source_value(MockGhidraAnalysis(), MockFunc(), MockVar(), sources)
164+
165+
assert result is None

0 commit comments

Comments
 (0)