├── DataExportPlus.py ├── LICENSE ├── README.md └── README.zh_CN.md /DataExportPlus.py: -------------------------------------------------------------------------------- 1 | from os import getcwd, path 2 | 3 | import idc 4 | import idaapi 5 | import ida_ida 6 | from ida_kernwin import add_hotkey 7 | from ida_bytes import get_flags 8 | 9 | VERSION = "1.1.2" 10 | 11 | class DEP_Conversion(): 12 | 13 | @classmethod 14 | def get_list(self): 15 | Data_base_list = {"Hexadecimal":16,"Decimal":10,"Octal":8,"Binary":2,} 16 | Data_type_list = {"Byte":1,"Word":2,"Dword":3,"Qword":4,"String literal":5,"Assembly Code":6,"Raw bytes":7} 17 | Data_exported_format_list = {"String":0,"C variable":1,"Python variable":2} 18 | return (Data_base_list,Data_type_list,Data_exported_format_list) 19 | 20 | # data(bytes) big_endian(Bool) data_type(int) base(int) delimiter(str) prefix(str) suffix(str) 21 | def __init__(self, address, data_bytes, data_type_key = 1, export_type_key = 0,big_endian = False, base_key = 0, delimiter = " ",prefix = "",suffix = "",keep_comments = False, keep_names = False): 22 | self.address = address 23 | self.Data_base_list, self.Data_type_list, _ = self.get_list() 24 | self.data_bytes = data_bytes 25 | self.export_type_key = export_type_key 26 | self.big_endian = big_endian 27 | self.base_key = base_key 28 | self.data_type_key = data_type_key 29 | self.delimiter = delimiter 30 | self.prefix = prefix 31 | self.suffix = suffix 32 | self.keep_comments = keep_comments 33 | self.keep_names = keep_names 34 | 35 | @classmethod 36 | def dict_key_to_list(self,dictionary): 37 | t = list(dictionary.items()) 38 | key = [i[0] for i in t] 39 | return key 40 | 41 | @classmethod 42 | def dict_value_to_list(self,dictionary): 43 | t = list(dictionary.items()) 44 | value = [i[1] for i in t] 45 | return value 46 | 47 | 48 | def activate(self): 49 | self.base = self.dict_value_to_list(self.Data_base_list)[self.base_key] 50 | self.type = self.dict_key_to_list(self.Data_type_list)[self.data_type_key] 51 | 52 | output = "" 53 | if(self.type in ['Byte','Word','Dword','Qword']): 54 | output = self.NumberConversion() 55 | 56 | elif(self.type == "String literal"): 57 | output = self.StringLiteralConversion() 58 | 59 | elif(self.type == "Assembly Code"): 60 | output = self.AssemblyCodeConversion() 61 | 62 | elif(self.type == "Raw bytes"): 63 | return "Cannot preview binary data" 64 | 65 | # String 66 | if(self.export_type_key == 0): 67 | return output 68 | 69 | # C variable 70 | elif(self.export_type_key == 1): 71 | if(self.type in ['Byte','Word','Dword','Qword']): 72 | C_type = {"Byte":"unsigned char","Word":"unsigned short","Dword":"unsigned int","Qword":"unsigned long long int"}[self.type] 73 | return C_type+" IDA_"+ hex(self.address)[2:] + "[] = {" + output + "};" 74 | 75 | elif(self.type == "String literal"): 76 | return "unsigned char IDA_"+ hex(self.address)[2:] + "[] = \"" + output + "\";" 77 | 78 | elif(self.type == "Assembly Code"): 79 | output = '\\n\"\n\"'.join([line for line in output.splitlines()])[:-1] 80 | return "std:string IDA_"+ hex(self.address)[2:] + " = \"" + output + "\";" 81 | 82 | return None 83 | 84 | # Python variable 85 | elif(self.export_type_key == 2): 86 | if(self.type in ['Byte','Word','Dword','Qword']): 87 | return "IDA_" + hex(self.address)[2:] + " = [" +output + "]" 88 | 89 | elif(self.type == "String literal"): 90 | return "IDA_" + hex(self.address)[2:] + " = b\'" + output + '\'' 91 | 92 | elif(self.type == "Assembly Code"): 93 | return "IDA_" + hex(self.address)[2:] + " = \'\'\'" + output + '\'\'\'' 94 | 95 | return None 96 | 97 | def convert_base(self, number, target_base): 98 | digit_map = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' 99 | result = '' 100 | while number: 101 | remainder = number % target_base 102 | result = digit_map[remainder] + result 103 | number //= target_base 104 | 105 | if(result == ''): 106 | return "0" 107 | return result 108 | 109 | # base on Byte/Word/Dword/Qword convert byte stream to number 110 | # parameter: base big_endian prefix suffix 111 | def NumberConversion(self): 112 | Number_array = [] 113 | type_len_list = {"Byte":1,"Word":2,"Dword":4,"Qword":8} 114 | Number_len = type_len_list[self.type] 115 | if(not self.big_endian): 116 | for i in range(0, len(self.data_bytes), Number_len): 117 | Number_array.append(int.from_bytes(self.data_bytes[i:i+Number_len],byteorder='little')) 118 | else: 119 | for i in range(0, len(self.data_bytes), Number_len): 120 | Number_array.append(int.from_bytes(self.data_bytes[i:i+Number_len],byteorder='big')) 121 | if(self.base > 0 and self.base < 36): 122 | return self.delimiter.join("{0}{1}{2}".format(self.prefix,str(self.convert_base(i,self.base)),self.suffix) for i in Number_array) 123 | return None 124 | 125 | def StringLiteralConversion(self): 126 | return str(self.data_bytes)[2:-1] 127 | 128 | 129 | def AssemblyCodeConversion(self): 130 | assembly_code_start = self.address 131 | assembly_code_end = self.address + len(self.data_bytes) 132 | 133 | output = "" 134 | i = assembly_code_start 135 | if(assembly_code_start > ida_ida.inf_get_max_ea() or assembly_code_start < ida_ida.inf_get_min_ea()): 136 | return "" 137 | while i < assembly_code_end: 138 | if(self.keep_names): 139 | addr_name = idc.get_name(i) 140 | if addr_name: 141 | output += "\n" + addr_name + ":\n" 142 | if(self.keep_comments): 143 | output += idc.generate_disasm_line(i,0) 144 | else: 145 | output += idc.generate_disasm_line(i,0).split(";")[0] 146 | output += "\n" 147 | 148 | i = idc.find_code(i, 1) 149 | return output 150 | 151 | 152 | class DEP_Form(idaapi.Form): 153 | # idaapi information 154 | min_ea = ida_ida.inf_get_min_ea() 155 | max_ea = ida_ida.inf_get_max_ea() 156 | 157 | # list 158 | Data_base_list,Data_type_list,Data_exported_format_list = DEP_Conversion.get_list() 159 | 160 | def __init__(self,select_addr,select_len): 161 | self.export_address = select_addr 162 | self.export_address_len = select_len 163 | self.Data_bytes = b"" 164 | self.export_data_type_key = 0 165 | 166 | 167 | # Format Options 168 | self.export_big_endian = False 169 | self.export_base_key = 0 170 | self.export_delimiter = "," 171 | self.export_prefix = "0x" 172 | self.export_suffix = "" 173 | self.export_type_key = 0 174 | self.export_keep_comments = False 175 | self.export_keep_names = False 176 | 177 | self.export_data = None 178 | self.export_file_path = getcwd() + "\\export_results.txt" 179 | 180 | 181 | data_type_flag = get_flags(self.export_address) 182 | checks = [ 183 | (idc.is_byte, 0), 184 | (idc.is_word, 1), 185 | (idc.is_dword, 2), 186 | (idc.is_qword, 3), 187 | (idc.is_strlit, 4), 188 | (idc.is_code, 5) 189 | ] 190 | self.export_data_type_key = next((key for func, key in checks if func(data_type_flag)), 0) 191 | 192 | super(DEP_Form, self).__init__( 193 | 194 | r'''STARTITEM 0 195 | BUTTON YES* Export 196 | Export Plus: Export Data 197 | 198 | {FormChangeCb} 199 | 200 | 201 | <~Selected Data~ :{_select_data}> 202 | < Data Type :{_data_type}> 203 | < Export As :{_export_type}> 204 | 205 | Export Format: 206 | <##- Endianness :{_endianness}> 207 | <##- Base :{_base}> 208 | <##- Delimiter :{_delimiter}> 209 | <##- Prefix :{_prefix}> 210 | <##- Suffix :{_suffix}> 211 | 212 | <##- Keep Comments:{_keep_comments}> 213 | <##- Keep Names :{_keep_names}> 214 | <~Export Window~: {_export_text}> 215 | 216 | ''', 217 | { 218 | "FormChangeCb": self.FormChangeCb(self.OnFormChange), 219 | 220 | "_address": self.NumericInput(value = self.export_address, tp=self.FT_ADDR, swidth=30), 221 | "_length": self.NumericInput(value = self.export_address_len, swidth = 30), 222 | "_select_data": self.StringInput(value = "",swidth = 30), 223 | "_data_type": self.DropdownListControl(items = list(self.Data_type_list.keys()),selval = self.export_data_type_key), 224 | "_export_type": self.DropdownListControl(items =list(self.Data_exported_format_list.keys())), 225 | 226 | "_endianness": self.DropdownListControl(items = ["Little-endian","Big-endian"]), 227 | "_base": self.DropdownListControl(items = list(self.Data_base_list.keys())), 228 | "_delimiter": self.StringInput(value = self.export_delimiter,swidth = 30), 229 | "_prefix": self.StringInput(value = self.export_prefix,swidth = 30), 230 | "_suffix": self.StringInput(value = self.export_suffix,swidth = 30), 231 | "_keep_comments": self.DropdownListControl(items = ["False", "True"]), 232 | "_keep_names": self.DropdownListControl(items = ["False", "True"]), 233 | 234 | 235 | "_export_text": self.MultiLineTextControl(text = "",swidth = 48), 236 | "_export_file_path": self.FileInput(value = self.export_file_path, save = True,swidth = 30), 237 | } 238 | 239 | ) 240 | self.Compile() 241 | 242 | def OnFormChange(self,fid): 243 | # initialization 244 | data_str = "" 245 | if(fid == -1): 246 | self.EnableField(self._select_data,False) 247 | try: 248 | input_export_address = self.GetControlValue(self._address) 249 | input_export_address_len = self.GetControlValue(self._length) 250 | 251 | self.min_ea = ida_ida.inf_get_min_ea() 252 | self.max_ea = ida_ida.inf_get_max_ea() 253 | 254 | if(self.min_ea < input_export_address and self.max_ea > input_export_address and self.max_ea > input_export_address + input_export_address_len): 255 | self.Data_bytes,data_str =self.GetEAData(input_export_address,input_export_address_len) 256 | self.SetControlValue(self._select_data,data_str) 257 | 258 | self.export_address = input_export_address 259 | self.export_address_len = input_export_address_len 260 | except: 261 | return 1 262 | 263 | # change Selected information 264 | elif(fid == self._address.id or fid == self._length.id): 265 | try: 266 | input_export_address = self.GetControlValue(self._address) 267 | input_export_address_len = self.GetControlValue(self._length) 268 | 269 | self.min_ea = ida_ida.inf_get_min_ea() 270 | self.max_ea = ida_ida.inf_get_max_ea() 271 | 272 | if(self.min_ea < input_export_address and self.max_ea > input_export_address and self.max_ea > input_export_address + input_export_address_len): 273 | self.Data_bytes,data_str =self.GetEAData(input_export_address,input_export_address_len) 274 | self.SetControlValue(self._select_data,data_str) 275 | 276 | self.export_address = input_export_address 277 | self.export_address_len = input_export_address_len 278 | except: 279 | return 1 280 | 281 | # change export format or export type 282 | if(fid in [-1, self._data_type.id, self._export_type.id]): 283 | self.export_data_type_key = self.GetControlValue(self._data_type) 284 | self.export_type_key = self.GetControlValue(self._export_type) 285 | 286 | # change Form Controls property 287 | 288 | # Byte 289 | if(self.export_data_type_key == 0): 290 | self.ShowField(self._export_type,True) 291 | 292 | self.ShowField(self._endianness,False) 293 | self.ShowField(self._base,True) 294 | self.ShowField(self._delimiter,True) 295 | self.ShowField(self._prefix,True) 296 | self.ShowField(self._suffix,True) 297 | self.ShowField(self._keep_comments,False) 298 | self.ShowField(self._keep_names,False) 299 | 300 | # export as string 301 | if(self.export_type_key == 0): 302 | self.EnableField(self._delimiter,True) 303 | self.EnableField(self._prefix,True) 304 | self.EnableField(self._suffix,True) 305 | 306 | # Word,Dword,Qword 307 | elif(self.export_data_type_key in [1,2,3]): 308 | self.ShowField(self._export_type,True) 309 | 310 | self.ShowField(self._endianness,True) 311 | self.ShowField(self._base,True) 312 | self.ShowField(self._delimiter,True) 313 | self.ShowField(self._prefix,True) 314 | self.ShowField(self._suffix,True) 315 | self.ShowField(self._keep_comments,False) 316 | self.ShowField(self._keep_names,False) 317 | 318 | # export as string 319 | if(self.export_type_key == 0): 320 | self.EnableField(self._endianness,True) 321 | self.EnableField(self._delimiter,True) 322 | self.EnableField(self._prefix,True) 323 | self.EnableField(self._suffix,True) 324 | 325 | # String literal 326 | elif(self.export_data_type_key == 4): 327 | self.ShowField(self._export_type,True) 328 | 329 | self.ShowField(self._endianness,False) 330 | self.ShowField(self._base,False) 331 | self.ShowField(self._delimiter,False) 332 | self.ShowField(self._prefix,False) 333 | self.ShowField(self._suffix,False) 334 | self.ShowField(self._keep_comments,False) 335 | self.ShowField(self._keep_names,False) 336 | 337 | # Assembly Code 338 | elif(self.export_data_type_key == 5): 339 | self.ShowField(self._export_type,True) 340 | 341 | self.ShowField(self._endianness,False) 342 | self.ShowField(self._base,False) 343 | self.ShowField(self._delimiter,False) 344 | self.ShowField(self._prefix,False) 345 | self.ShowField(self._suffix,False) 346 | self.ShowField(self._keep_comments,True) 347 | self.ShowField(self._keep_names,True) 348 | 349 | # Raw bytes 350 | elif(self.export_data_type_key == 6): 351 | self.ShowField(self._export_type,False) 352 | 353 | self.ShowField(self._endianness,False) 354 | self.ShowField(self._base,False) 355 | self.ShowField(self._delimiter,False) 356 | self.ShowField(self._prefix,False) 357 | self.ShowField(self._suffix,False) 358 | self.ShowField(self._keep_comments,False) 359 | self.ShowField(self._keep_names,False) 360 | 361 | # export as string 362 | if(self.export_type_key == 0): 363 | pass 364 | 365 | # export as C varible 366 | elif(self.export_type_key == 1): 367 | self.EnableField(self._delimiter,False) 368 | self.EnableField(self._prefix,False) 369 | self.EnableField(self._suffix,False) 370 | 371 | # export as Python varible 372 | elif(self.export_type_key == 2): 373 | self.EnableField(self._delimiter,False) 374 | self.EnableField(self._prefix,False) 375 | self.EnableField(self._suffix,False) 376 | 377 | # change default value 378 | self.SetControlsDefaultValue() 379 | 380 | 381 | 382 | 383 | 384 | elif(fid in [self._endianness.id,self._base.id,self._delimiter.id,self._prefix.id,self._suffix.id,self._keep_comments.id,self._keep_names.id]): 385 | self.export_big_endian = self.GetControlValue(self._endianness) 386 | self.export_base_key = self.GetControlValue(self._base) 387 | self.export_delimiter = self.GetControlValue(self._delimiter) 388 | self.export_prefix = self.GetControlValue(self._prefix) 389 | self.export_suffix = self.GetControlValue(self._suffix) 390 | self.export_keep_comments = {0:False,1:True}[self.GetControlValue(self._keep_comments)] 391 | self.export_keep_names = {0:False,1:True}[self.GetControlValue(self._keep_names)] 392 | 393 | 394 | if(fid == self._base.id): 395 | self.SetControlsDefaultValue() 396 | 397 | 398 | elif(fid == self._export_file_path.id): 399 | self.export_file_path = self.GetControlValue(self._export_file_path) 400 | 401 | 402 | 403 | 404 | # refresh MultiLineTextControl 405 | self.RefreshExportWindow() 406 | 407 | return 1 408 | 409 | 410 | def GetEAData(self,address,length): 411 | data_byte = idc.get_bytes(address,length) 412 | data_str = ' '.join([f"{i:02X}" for i in bytearray(data_byte)]) 413 | 414 | return data_byte,data_str.strip() 415 | 416 | 417 | def RefreshExportWindow(self): 418 | t = DEP_Conversion(address = self.export_address, data_bytes = self.Data_bytes, 419 | data_type_key = self.export_data_type_key, 420 | export_type_key = self.export_type_key, 421 | big_endian = self.export_big_endian, 422 | base_key = self.export_base_key, 423 | delimiter = self.export_delimiter, 424 | prefix = self.export_prefix, 425 | suffix = self.export_suffix, 426 | keep_comments = self.export_keep_comments, 427 | keep_names = self.export_keep_names) 428 | self.export_data = t.activate() 429 | self.SetControlValue(self._export_text, idaapi.textctrl_info_t(text = self.export_data, flags = 32, tabsize = 0)) 430 | 431 | 432 | def SetControlsDefaultValue(self): 433 | if(self.export_data_type_key in [0,1,2,3]): 434 | 435 | Prefix_list = {0:"0x",1:"",2:"0o",3:"0b"} 436 | self.export_prefix = Prefix_list[self.export_base_key] 437 | self.SetControlValue(self._prefix,self.export_prefix) 438 | 439 | if(self.export_type_key == 1): 440 | self.export_delimiter = ", " 441 | self.SetControlValue(self._delimiter,", ") 442 | self.export_suffix = "" 443 | self.SetControlValue(self._suffix,"") 444 | # export as C array 445 | if(self.export_data_type_key == 3): 446 | self.export_suffix = "ULL" 447 | self.SetControlValue(self._suffix,"ULL") 448 | if(self.export_base_key == 2): 449 | self.export_prefix = "0" 450 | self.SetControlValue(self._prefix,self.export_prefix) 451 | 452 | 453 | if(self.export_type_key == 2): 454 | self.export_delimiter = ", " 455 | self.SetControlValue(self._delimiter,", ") 456 | self.export_suffix = "" 457 | self.SetControlValue(self._suffix,"") 458 | # export as Python array 459 | 460 | 461 | 462 | class DataExportPlus(idaapi.plugin_t): 463 | flags = idaapi.PLUGIN_DRAW 464 | comment = "Export Data" 465 | help = "" 466 | wanted_name = "Data Export Plus" 467 | version = VERSION 468 | 469 | def init(self): 470 | print("=" * 80) 471 | print("Start Data Export Plus plugin") 472 | 473 | idc.del_idc_hotkey("Shift+E") 474 | add_hotkey("Shift-E", self.hotkeystart) 475 | 476 | return idaapi.PLUGIN_OK 477 | 478 | def term(self): 479 | return 480 | 481 | def hotkeystart(self): 482 | self.run(None) 483 | 484 | def run(self, args): 485 | ea_addr,ea_item_size = self.GetEAItem() 486 | form = DEP_Form(ea_addr,ea_item_size) 487 | IsExport = form.Execute() 488 | 489 | 490 | if(IsExport): 491 | if(path.exists(form.export_file_path)): 492 | k = idc.ask_yn(1,"Export file already exists, Do you want to overwrite it?") 493 | if(k == -1 or k == 0): 494 | form.Free() 495 | return 1 496 | try: 497 | if(form.export_data_type_key == 6): 498 | with open(form.export_file_path, "wb") as file_handle: 499 | file_handle.write(form.Data_bytes) 500 | else: 501 | with open(form.export_file_path, "w", encoding="utf-8") as file_handle: 502 | file_handle.write(form.export_data) 503 | 504 | print("Stored export results in",form.export_file_path) 505 | 506 | except: 507 | idc.warning("Export file failed") 508 | 509 | form.Free() 510 | return 1 511 | 512 | 513 | def GetEAItem(self): 514 | selection, ea_addr, ea_addr_end = idaapi.read_range_selection(None) 515 | 516 | if (selection): 517 | ea_item_size = ea_addr_end - ea_addr 518 | else: 519 | ea_addr = idc.get_screen_ea() 520 | ea_item_size = idc.get_item_size(idc.get_screen_ea()) 521 | 522 | return ea_addr,ea_item_size 523 | 524 | 525 | 526 | 527 | def PLUGIN_ENTRY(): 528 | return DataExportPlus() 529 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Krietz7 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # IDA-DataExportPlus 2 | [中文](README.zh_CN.md) 3 | 4 | a IDA Pro plugin for exporting data, it can replace the IDA Pro original Data Export Window. 5 | 6 | The plugin supports export data base on different data type. Additional it also supports export assembly code. 7 | 8 | ## Installation 9 | Copy `DataExportPlus.py` to IDA plugins directory. Requires IDA Pro version 7.6 or above 10 | 11 | ## Usage 12 | Use hotkey `Shift+E` to Call out the plugin window 13 | 14 | ![image1](https://github.com/user-attachments/assets/cad9ce0b-2680-4f1f-ad52-3053bd9a174d) 15 | 16 | You can change the format of the exported data in this window 17 | 18 | ![image2](https://github.com/user-attachments/assets/618d52c0-575b-44a8-837d-2173814f5e30) 19 | 20 | Exporting assembly code 21 | 22 | ![image3](https://github.com/user-attachments/assets/4ef63c52-6cdf-43e0-b885-d1cde70117ef) 23 | -------------------------------------------------------------------------------- /README.zh_CN.md: -------------------------------------------------------------------------------- 1 | # IDA-DataExportPlus 2 | 一个用于导出数据的IDA PRO插件,它可以替换IDA Pro原始的数据导出窗口。 3 | 4 | ## 安装 5 | 复制`dataexportplus.py`到IDA插件目录下。需要IDA Pro版本7.6或以上 6 | 7 | ## 使用方法 8 | 使用快捷键`Shift+E`以唤出插件窗口 9 | 10 | ![image1](https://github.com/user-attachments/assets/cad9ce0b-2680-4f1f-ad52-3053bd9a174d) 11 | 12 | 您可以在此窗口中更改导出数据的格式 13 | 14 | ![image2](https://github.com/user-attachments/assets/618d52c0-575b-44a8-837d-2173814f5e30) 15 | 16 | 导出汇编代码 17 | 18 | ![image3](https://github.com/user-attachments/assets/4ef63c52-6cdf-43e0-b885-d1cde70117ef) 19 | --------------------------------------------------------------------------------