1+ """
2+ Minimum Python version: 3.9
3+
4+ This Binary Ninja script adds comments in the decompiler view for all references to constants in
5+ BCC functions.
6+
7+ IMPORTANT: For this to work, the pointer variable must be of type QWORD* in every function.
8+ Unlike the IDA one, the first dereference is not our target expressions, instead, it follows the pattern:
9+ r12 = *(arg1 + (sx.q(*(arg1 + 0x10)) << 3) + 0x10)
10+
11+ Also unlike IDA, there are no v4[1], v4[2], v4[3]... Instead, Binary Ninja display those access as pointer arithmetics like:
12+ int64_t rsi = (*(rax_4 + 0x1d0))()
13+ """
14+
15+ import re
16+ import json
17+ from typing import Optional
18+
19+ # This should be the path to your *.elf.json
20+ JSON_PATH = ""
21+
22+ def wait_modification ():
23+ return bv .update_analysis_and_wait ()
24+
25+ def open_json_file (json_path : str ):
26+ if not json_path :
27+ return
28+ with open (json_path , 'r' ) as fp :
29+ print (f"Opened file { json_path } with read permissions." )
30+ return json .load (fp )
31+
32+
33+ def create_and_rename_function (offset : int , name : str ) -> None :
34+ """Create function at offset and rename it."""
35+ existing_func = bv .get_function_at (offset )
36+ if existing_func :
37+ print (f"Function already exists at { hex (offset )} , renaming to '{ name } '" )
38+ existing_func .name = name
39+ return
40+
41+ func = bv .create_user_function (offset )
42+ if not func :
43+ print (f"Failed to create function at { hex (offset )} " )
44+ return
45+
46+ wait_modification ()
47+
48+ func .name = name
49+ print (f"Created and named function '{ name } ' at { hex (offset )} " )
50+
51+ class ConstsAnnotator ():
52+
53+ def __init__ (self , func , consts : list ) -> None :
54+ self .alias_map : dict [str , str ] = {}
55+ self .comments_added = 0
56+ self .func = func
57+ self .consts = consts
58+ self .target_var = None
59+
60+ def find_target_constant (self ):
61+ """Find the target constant pointer and set consts_base."""
62+ for block in self .func .hlil :
63+ for instr in block :
64+ if (type (instr ) == HighLevelILVarInit and
65+ hasattr (instr , 'detailed_operands' ) and
66+ type (instr .detailed_operands [1 ][1 ]) == HighLevelILDeref ): # HighLevelILDeref (arg1 + (sx.q(*(arg1 + 0x10)) << 3) + 0x10)
67+
68+ target_var = instr .operands [0 ]
69+
70+ print (f"Found consts pointer identifier { instr } " )
71+ return target_var
72+ return None
73+
74+ def map_constant_xrefs (self , target_var ):
75+ refs = self .func .get_hlil_var_refs (target_var )
76+ print (f"Found XRefs: { refs } " if refs else "There are no XRefs" )
77+
78+ for ref in refs :
79+ ref_dest = ref .func .hlil [ref .expr_id ].operands [0 ]
80+ if hasattr (ref_dest , 'identifier' ): # Variable type
81+ ref_dest_id = ref_dest .identifier
82+ elif hasattr (ref_dest , 'var' ) and hasattr (ref_dest .var , 'identifier' ): # HighLevelILVar
83+ ref_dest_id = ref_dest .var .identifier
84+ else :
85+ continue
86+
87+ ref_src = ref .func .hlil [ref .expr_id ].operands [1 ]
88+ # We will only track variable aliases, like v1012 = v4
89+ # Deref's won't be tracked
90+ if hasattr (ref_src , 'var' ) and hasattr (ref_src .var , 'identifier' ): # HighLevelILVar
91+ ref_src_id = ref_src .var .identifier
92+ else :
93+ continue
94+
95+ if ref_src_id == target_var .identifier :
96+ self .alias_map [ref_dest_id ] = ref_src_id
97+
98+ def find_constant (self ):
99+ self .target_var = self .find_target_constant ()
100+ if self .target_var :
101+ self .map_constant_xrefs (self .target_var )
102+
103+ print ("\n Alias Mapping Results:" )
104+ if self .alias_map :
105+ for dest_var , src_var in self .alias_map .items ():
106+ print (f" { dest_var } -> { src_var } " )
107+ else :
108+ print (" No aliases found" )
109+
110+ def place_comments (self ):
111+ def visit_expr (expr ):
112+ # Check if this is a dereference of our target variable + offset
113+ if (type (expr ) == HighLevelILDeref and
114+ type (expr .src ) == HighLevelILAdd and
115+ type (expr .src .left ) == HighLevelILVar and
116+ type (expr .src .right ) == HighLevelILConst ):
117+
118+ var_id = expr .src .left .var .identifier
119+ offset = expr .src .right .constant
120+
121+ # Check if this is our target variable or an alias to it
122+ if (var_id == self .target_var .identifier or
123+ self .alias_map .get (var_id ) == self .target_var .identifier ):
124+
125+ # Convert byte offset to array index (offset >= 24 means index >= 3)
126+ if offset >= 24 and (offset - 24 ) % 8 == 0 :
127+ array_index = (offset - 24 ) // 8 + 3
128+
129+ if 0 <= array_index - 3 < len (self .consts ):
130+ comment = self .consts [array_index - 3 ]
131+ addr = getattr (expr , 'address' , None ) or getattr (expr .instr , 'address' , None )
132+ if addr :
133+ try :
134+ self .func .set_comment_at (addr , comment )
135+ self .comments_added += 1
136+ print (f"Added comment '{ comment } ' at { hex (addr )} " )
137+ except Exception as e :
138+ print (f"Failed to set comment: { e } " )
139+
140+ if hasattr (expr , 'operands' ):
141+ for operand in expr .operands :
142+ if hasattr (operand , '__iter__' ) and not isinstance (operand , str ):
143+ for sub_op in operand :
144+ visit_expr (sub_op )
145+ elif hasattr (operand , 'operands' ):
146+ visit_expr (operand )
147+
148+ for block in self .func .hlil :
149+ for instr in block :
150+ visit_expr (instr )
151+
152+
153+ def annotate_consts_in_decompilation (ea : int , consts : list ) -> None :
154+ func = bv .get_function_at (ea )
155+ if not func :
156+ print (f"No function found at { hex (ea )} " )
157+ return
158+
159+ if not func .hlil :
160+ print (f"No HLIL for function at { hex (ea )} " )
161+ return
162+
163+ annotator = ConstsAnnotator (func , consts )
164+ annotator .find_constant ()
165+ annotator .place_comments ()
166+
167+ print (f"Added { annotator .comments_added } comments to function at { hex (ea )} " )
168+
169+ wait_modification ()
170+
171+ def main () -> None :
172+ data = open_json_file (JSON_PATH )
173+ if data is None :
174+ print ("Failed to load JSON data" )
175+ return
176+
177+ print (f"Processing { len (data )} entries..." )
178+
179+ for entry in data :
180+ offset = entry ['offset' ]
181+ name = entry ['name' ]
182+ consts = entry ['consts' ]
183+
184+ print ("-------------------------------------------------------" )
185+ print (f"Offset: { hex (offset )} , Name: { name } , Constants: { len (consts )} " )
186+
187+ create_and_rename_function (offset , name )
188+ annotate_consts_in_decompilation (offset , consts )
189+
190+ main ()
0 commit comments