1+ from __future__ import annotations
2+ from collections .abc import Callable
13import os
24from typing import Optional , Union
35from pathlib import Path
911from binaryninja .function import Function
1012from binaryninja .lowlevelil import LowLevelILFunction
1113from binaryninja .mediumlevelil import MediumLevelILFunction
12- from binaryninja .highlevelil import HighLevelILFunction
14+ from binaryninja .highlevelil import HighLevelILFunction , HighLevelILInstruction , \
15+ HighLevelILVarInit
1316from binaryninja .settings import Settings
1417from binaryninja import log , BinaryView
1518
1922
2023class Agent :
2124
22- question : str = '''
25+ function_question : str = '''
2326 This is a function that was decompiled with Binary Ninja.
2427 It is in IL_FORM. What does this function do?
2528 '''
2629
30+ rename_variable_question : str = "In one word, what should the variable " \
31+ "be for the variable that is assigned to the result of the C " \
32+ "expression:\n "
33+
34+
2735 # A mapping of IL forms to their names.
2836 il_name : dict [type , str ] = {
2937 LowLevelILFunction : 'Low Level Intermediate Language' ,
@@ -34,28 +42,17 @@ class Agent:
3442
3543 def __init__ (self ,
3644 bv : BinaryView ,
37- function : Union [Function , LowLevelILFunction ,
38- MediumLevelILFunction , HighLevelILFunction ],
3945 path_to_api_key : Optional [Path ]= None ) -> None :
4046
4147 # Read the API key from the environment variable.
4248 openai .api_key = self .read_api_key (path_to_api_key )
4349
44- # Ensure that a function type was passed in.
45- if not isinstance (
46- function ,
47- (Function , LowLevelILFunction , MediumLevelILFunction ,
48- HighLevelILFunction )):
49- raise TypeError (f'Expected a BNIL function of type '
50- f'Function, LowLevelILFunction, '
51- f'MediumLevelILFunction, or HighLevelILFunction, '
52- f'got { type (function )} .' )
53-
5450 assert bv is not None , 'BinaryView is None. Check how you called this function.'
5551 # Set instance attributes.
5652 self .bv = bv
57- self .function = function
5853 self .model = self .get_model ()
54+ # Used for the callback function.
55+ self .instruction = None
5956
6057 def read_api_key (self , filename : Optional [Path ]= None ) -> str :
6158 '''Checks for the API key in three locations.
@@ -72,7 +69,7 @@ def read_api_key(self, filename: Optional[Path]=None) -> str:
7269 settings : Settings = Settings ()
7370 if settings .contains ('openai.api_key' ):
7471 if key := settings .get_string ('openai.api_key' ):
75- return key
72+ return str ( key )
7673
7774 # If the settings don't exist, contain the key, or the key is empty,
7875 # check the environment variable.
@@ -111,7 +108,7 @@ def get_model(self) -> str:
111108 if model := settings .get_string ('openai.model' ):
112109 # Check that is a valid model by querying the OpenAI API.
113110 if self .is_valid_model (model ):
114- return model
111+ return str ( model )
115112 # Return a valid, default model.
116113 assert self .is_valid_model ('text-davinci-003' )
117114 return 'text-davinci-003'
@@ -124,7 +121,7 @@ def get_token_count(self) -> int:
124121 if settings .contains ('openai.max_tokens' ):
125122 # Check that the value is not None.
126123 if (max_tokens := settings .get_integer ('openai.max_tokens' )) is not None :
127- return max_tokens
124+ return int ( max_tokens )
128125 return 1_024
129126
130127 def instruction_list (self , function : Union [LowLevelILFunction ,
@@ -133,6 +130,15 @@ def instruction_list(self, function: Union[LowLevelILFunction,
133130 '''Generates a list of instructions in string representation given a
134131 BNIL function.
135132 '''
133+
134+ # Ensure that a function type was passed in.
135+ if not isinstance (function , (Function , LowLevelILFunction ,
136+ MediumLevelILFunction , HighLevelILFunction )):
137+ raise TypeError (f'Expected a BNIL function of type '
138+ f'Function, LowLevelILFunction, '
139+ f'MediumLevelILFunction, or HighLevelILFunction, '
140+ f'got { type (function )} .' )
141+
136142 if isinstance (function , Function ):
137143 return Pseudo_C (self .bv , function ).get_c_source ()
138144 instructions : list [str ] = []
@@ -144,21 +150,64 @@ def generate_query(self, function: Union[Function,
144150 LowLevelILFunction ,
145151 MediumLevelILFunction ,
146152 HighLevelILFunction ]) -> str :
147- '''Generates a query string given a BNIL function. Reads the file
148- prompt.txt and replaces the IL form with the name of the IL form .
153+ '''Generates a query string given a BNIL function. Returns the query as
154+ a string .
149155 '''
150- prompt : str = self .question
156+ prompt : str = self .function_question
151157 # Read the prompt from the text file.
152- prompt = self . question .replace ('IL_FORM' , self .il_name [type (function )])
158+ prompt = prompt .replace ('IL_FORM' , self .il_name [type (function )])
153159 # Add some new lines. Maybe not necessary.
154160 prompt += '\n \n '
155161 # Add the instructions to the prompt.
156162 prompt += '\n ' .join (self .instruction_list (function ))
157163 return prompt
158164
159- def send_query (self , query : str ) -> None :
165+ def generate_rename_variable_query (self ,
166+ instruction : HighLevelILInstruction ) -> str :
167+ '''Generates a query string given a BNIL instruction. Returns the query
168+ as a string.
169+ '''
170+ if not isinstance (instruction , HighLevelILVarInit ):
171+ raise TypeError (f'Expected a BNIL instruction of type '
172+ f'HighLevelILVarInit got { type (instruction )} .' )
173+ # Assign the instruction to the Agent instance. This is used for the
174+ # callback function so we don't need to pass in the instruction to the
175+ # Query instance. This is kind of janky and should be examined in future
176+ # versions.
177+ self .instruction = instruction
178+
179+ prompt : str = self .rename_variable_question
180+ # Get the disassembly lines and add them to the prompt.
181+ for line in instruction .instruction_operands :
182+ prompt += str (line )
183+
184+ return prompt
185+
186+ def rename_variable (self , response : str ) -> None :
187+ '''Renames the variable of the instruction saved in the Agent instance
188+ to the response passed in as an argument.
189+ '''
190+ if self .instruction is None :
191+ raise TypeError ('No instruction was saved in the Agent instance.' )
192+ if response is None or response == '' :
193+ raise TypeError (f'No response was returned from OpenAI; got type { type (response )} .' )
194+ # Get just one word from the response. Remove spaces and quotes.
195+ try :
196+ response = response .split ()[0 ]
197+ response = response .replace (' ' , '' )
198+ response = response .replace ('"' , '' )
199+ response = response .replace ('\' ' , '' )
200+ except IndexError as error :
201+ raise IndexError (f'Could not split the response: `{ response } `.' ) from error
202+ # Assign the variable name to the response.
203+ log .log_debug (f'Renaming variable in expression { self .instruction } to { response } .' )
204+ self .instruction .dest .name = response
205+
206+
207+ def send_query (self , query : str , callback : Optional [Callable ]= None ) -> None :
160208 '''Sends a query to the engine and prints the response.'''
161209 query = Query (query_string = query ,
162210 model = self .model ,
163- max_token_count = self .get_token_count ())
211+ max_token_count = self .get_token_count (),
212+ callback_function = callback )
164213 query .start ()
0 commit comments