Skip to content

Commit 55151a7

Browse files
committed
feat: Set up plugin and register plugin action
1 parent 42e9b1b commit 55151a7

File tree

4 files changed

+152
-119
lines changed

4 files changed

+152
-119
lines changed

__init__.py

Lines changed: 7 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -1,121 +1,9 @@
1-
from dataclasses import dataclass
2-
from pprint import pformat
3-
from typing import List, Optional
1+
try:
2+
import importlib
43

5-
from binaryninja.binaryview import BinaryView, DataVariable
6-
from binaryninja.log import Logger
7-
from binaryninja.types import IntegerType, PointerType
4+
importlib.import_module("binaryninja")
5+
from .binja_plugin.plugin import plugin_init
86

9-
logger = Logger(session_id=0, logger_name="rust_string_slicer")
10-
11-
12-
@dataclass
13-
class StringSlice:
14-
address: int
15-
length: int
16-
data: bytes
17-
18-
def __repr__(self):
19-
return f"StringSlice(address={self.address:#x}, length={self.length:#x}, data={self.data})"
20-
21-
22-
def recover_string_slices_from_readonly_data(
23-
bv: BinaryView,
24-
) -> Optional[List[StringSlice]]:
25-
if bv.arch is None:
26-
logger.log_error("Could not get architecture of current binary view, exiting")
27-
return None
28-
29-
readonly_segments = list(
30-
filter(
31-
lambda segment: segment.readable
32-
and not segment.writable
33-
and not segment.executable,
34-
bv.segments,
35-
)
36-
)
37-
if len(readonly_segments) == 0:
38-
logger.log_error("Could not find any read-only segment in binary, exiting")
39-
return None
40-
41-
# Obtain all data vars which are pointers to data in readonly data segments
42-
data_vars_to_ro_segment_data: List[DataVariable] = []
43-
for _data_var_addr, candidate_string_slice_data_ptr in bv.data_vars.items():
44-
if isinstance(candidate_string_slice_data_ptr.type, PointerType):
45-
for readonly_segment in readonly_segments:
46-
if candidate_string_slice_data_ptr.value in readonly_segment:
47-
data_vars_to_ro_segment_data.append(candidate_string_slice_data_ptr)
48-
logger.log_debug(
49-
f"Found pointer var at {candidate_string_slice_data_ptr.address:#x} ({candidate_string_slice_data_ptr}) pointing to {candidate_string_slice_data_ptr.value:#x} "
50-
)
51-
52-
recovered_string_slices: List[StringSlice] = []
53-
for candidate_string_slice_data_ptr in data_vars_to_ro_segment_data:
54-
# Try to read an integer following the data var,
55-
# and treat it as a candidate for a string slice length.
56-
candidate_string_slice_len_addr = (
57-
candidate_string_slice_data_ptr.address
58-
+ candidate_string_slice_data_ptr.type.width
59-
)
60-
61-
# Filter out anything at the candidate address
62-
# that's already defined as any data var type which is not an integer.
63-
existing_data_var_at_candidate_string_slice_len_addr = bv.get_data_var_at(
64-
candidate_string_slice_len_addr
65-
)
66-
if existing_data_var_at_candidate_string_slice_len_addr is not None:
67-
if not isinstance(
68-
existing_data_var_at_candidate_string_slice_len_addr.type, IntegerType
69-
):
70-
continue
71-
72-
candidate_string_slice_len = bv.read_int(
73-
address=candidate_string_slice_len_addr,
74-
size=bv.arch.default_int_size,
75-
sign=False,
76-
endian=bv.arch.endianness,
77-
)
78-
79-
logger.log_debug(
80-
f"Pointer var at {candidate_string_slice_data_ptr.address:#x} is followed by integer with value {candidate_string_slice_len:#x}"
81-
)
82-
83-
# Filter out any potential string slice which has length 0
84-
if candidate_string_slice_len == 0:
85-
continue
86-
87-
# Attempt to read out the pointed to value as a string slice, with the length obtained above.
88-
candidate_string_slice = bv.read(
89-
addr=candidate_string_slice_data_ptr.value,
90-
length=candidate_string_slice_len,
91-
)
92-
93-
logger.log_debug(
94-
f"Obtained candidate string slice with addr {candidate_string_slice_data_ptr.value:#x}, len {candidate_string_slice_len:#x}: {candidate_string_slice}"
95-
)
96-
97-
# Sanity check whether the recovered string is valid UTF-8
98-
try:
99-
candidate_utf8_string = candidate_string_slice.decode("utf-8")
100-
logger.log_info(
101-
f'Recovered string at addr {candidate_string_slice_data_ptr.value:#x}, len {candidate_string_slice_len:#x}: "{candidate_utf8_string}"'
102-
)
103-
104-
# Append the final string slice object to the list of recovered strings.
105-
recovered_string_slices.append(
106-
StringSlice(
107-
address=candidate_string_slice_data_ptr.value,
108-
length=candidate_string_slice_len,
109-
data=candidate_string_slice,
110-
)
111-
)
112-
except UnicodeDecodeError as err:
113-
logger.log_warn(
114-
f"Candidate string slice {candidate_string_slice} does not decode to a valid UTF-8 string; excluding from final results: {err}"
115-
)
116-
continue
117-
118-
return recovered_string_slices
119-
120-
121-
logger.log_info(pformat(recover_string_slices_from_readonly_data(bv)))
7+
plugin_init()
8+
except ImportError:
9+
pass

binja_plugin/__init__.py

Whitespace-only changes.

binja_plugin/actions.py

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
from dataclasses import dataclass
2+
from pprint import pformat
3+
from typing import List, Optional
4+
5+
from binaryninja.binaryview import BinaryView, DataVariable
6+
from binaryninja.log import Logger
7+
from binaryninja.types import IntegerType, PointerType
8+
9+
logger = Logger(session_id=0, logger_name=__name__)
10+
11+
12+
@dataclass
13+
class StringSlice:
14+
address: int
15+
length: int
16+
data: bytes
17+
18+
def __repr__(self):
19+
return f"StringSlice(address={self.address:#x}, length={self.length:#x}, data={self.data})"
20+
21+
22+
def recover_string_slices_from_readonly_data(
23+
bv: BinaryView,
24+
) -> Optional[List[StringSlice]]:
25+
if bv.arch is None:
26+
logger.log_error("Could not get architecture of current binary view, exiting")
27+
return None
28+
29+
readonly_segments = list(
30+
filter(
31+
lambda segment: segment.readable
32+
and not segment.writable
33+
and not segment.executable,
34+
bv.segments,
35+
)
36+
)
37+
if len(readonly_segments) == 0:
38+
logger.log_error("Could not find any read-only segment in binary, exiting")
39+
return None
40+
41+
# Obtain all data vars which are pointers to data in readonly data segments
42+
data_vars_to_ro_segment_data: List[DataVariable] = []
43+
for _data_var_addr, candidate_string_slice_data_ptr in bv.data_vars.items():
44+
if isinstance(candidate_string_slice_data_ptr.type, PointerType):
45+
for readonly_segment in readonly_segments:
46+
if candidate_string_slice_data_ptr.value in readonly_segment:
47+
data_vars_to_ro_segment_data.append(candidate_string_slice_data_ptr)
48+
logger.log_debug(
49+
f"Found pointer var at {candidate_string_slice_data_ptr.address:#x} ({candidate_string_slice_data_ptr}) pointing to {candidate_string_slice_data_ptr.value:#x} "
50+
)
51+
52+
recovered_string_slices: List[StringSlice] = []
53+
for candidate_string_slice_data_ptr in data_vars_to_ro_segment_data:
54+
# Try to read an integer following the data var,
55+
# and treat it as a candidate for a string slice length.
56+
candidate_string_slice_len_addr = (
57+
candidate_string_slice_data_ptr.address
58+
+ candidate_string_slice_data_ptr.type.width
59+
)
60+
61+
# Filter out anything at the candidate address
62+
# that's already defined as any data var type which is not an integer.
63+
existing_data_var_at_candidate_string_slice_len_addr = bv.get_data_var_at(
64+
candidate_string_slice_len_addr
65+
)
66+
if existing_data_var_at_candidate_string_slice_len_addr is not None:
67+
if not isinstance(
68+
existing_data_var_at_candidate_string_slice_len_addr.type, IntegerType
69+
):
70+
continue
71+
72+
candidate_string_slice_len = bv.read_int(
73+
address=candidate_string_slice_len_addr,
74+
size=bv.arch.default_int_size,
75+
sign=False,
76+
endian=bv.arch.endianness,
77+
)
78+
79+
logger.log_debug(
80+
f"Pointer var at {candidate_string_slice_data_ptr.address:#x} is followed by integer with value {candidate_string_slice_len:#x}"
81+
)
82+
83+
# Filter out any potential string slice which has length 0
84+
if candidate_string_slice_len == 0:
85+
continue
86+
87+
# Attempt to read out the pointed to value as a string slice, with the length obtained above.
88+
candidate_string_slice = bv.read(
89+
addr=candidate_string_slice_data_ptr.value,
90+
length=candidate_string_slice_len,
91+
)
92+
93+
logger.log_debug(
94+
f"Obtained candidate string slice with addr {candidate_string_slice_data_ptr.value:#x}, len {candidate_string_slice_len:#x}: {candidate_string_slice}"
95+
)
96+
97+
# Sanity check whether the recovered string is valid UTF-8
98+
try:
99+
candidate_utf8_string = candidate_string_slice.decode("utf-8")
100+
logger.log_info(
101+
f'Recovered string at addr {candidate_string_slice_data_ptr.value:#x}, len {candidate_string_slice_len:#x}: "{candidate_utf8_string}"'
102+
)
103+
104+
# Append the final string slice object to the list of recovered strings.
105+
recovered_string_slices.append(
106+
StringSlice(
107+
address=candidate_string_slice_data_ptr.value,
108+
length=candidate_string_slice_len,
109+
data=candidate_string_slice,
110+
)
111+
)
112+
except UnicodeDecodeError as err:
113+
logger.log_warn(
114+
f"Candidate string slice {candidate_string_slice} does not decode to a valid UTF-8 string; excluding from final results: {err}"
115+
)
116+
continue
117+
118+
return recovered_string_slices
119+
120+
121+
def action_recover_string_slices_from_readonly_data(bv: BinaryView):
122+
logger.log_info(pformat(recover_string_slices_from_readonly_data(bv)))

binja_plugin/plugin.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
from binaryninja.log import Logger
2+
from binaryninja.plugin import PluginCommand
3+
4+
from . import actions
5+
6+
logger = Logger(session_id=0, logger_name=__name__)
7+
8+
PLUGIN_NAME = "Rust String Slicer"
9+
10+
plugin_commands = [
11+
(
12+
f"{PLUGIN_NAME}\\Recover String Slices from Readonly Data",
13+
"Recover String Slices from Readonly Data",
14+
actions.action_recover_string_slices_from_readonly_data,
15+
)
16+
]
17+
18+
19+
def plugin_init():
20+
for command_name, command_description, command_action in plugin_commands:
21+
PluginCommand.register(
22+
name=command_name, description=command_description, action=command_action
23+
)

0 commit comments

Comments
 (0)