IDAPython for IDA 7.6 SP1

This commit is contained in:
Arnaud Diederen
2021-05-03 15:25:57 +02:00
parent c22e071853
commit e8e2a7bb8c
93 changed files with 4311 additions and 7257 deletions
+2 -1
View File
@@ -1,2 +1,3 @@
obj/
*.pyc
*.pyc
idapyswitch*
+5
View File
@@ -30,3 +30,8 @@ test as well.
There should be no such thing as a non-tested example.
## Updating the examples index
All examples are automatically integrated in the examples index.
In order to show user-friendly & relevant information, a proper
header (docstring) needs to be present.
+8 -1
View File
@@ -1,4 +1,11 @@
# get information about function(s)
"""
summary: dump (some) information about the current function.
description:
Dump some of the most interesting bits of information about
the function we are currently looking at.
"""
import binascii
import ida_kernwin
+20
View File
@@ -1,3 +1,23 @@
"""
summary: custom actions, with icons & tooltips
description:
How to create user actions, that once created can be
inserted in menus, toolbars, context menus, ...
Those actions, when triggered, will be passed a 'context'
that contains some of the most frequently needed bits of
information.
In addition, custom actions can determine when they want
to be available (through their
`ida_kernwin.action_handler_t.update` callback)
keywords: actions
see_also: add_hotkey
"""
from __future__ import print_function
import ida_kernwin
+19 -8
View File
@@ -1,12 +1,23 @@
"""
summary: triggering bits of code by pressing a shortcut
description:
`ida_kernwin.add_hotkey` is a simpler, but much less flexible
alternative to `ida_kernwin.register_action` (though it does
use the same mechanism under the hood.)
It's particularly useful during prototyping, but note that the
actions that are created cannot be inserted in menus, toolbars
or cannot provide a custom `ida_kernwin.action_handler_t.update`
callback.
keywords: actions
see_also: actions
"""
from __future__ import print_function
#---------------------------------------------------------------------
# This script demonstrates the usage of hotkeys.
#
# 'ida_kernwin.add_hotkey' offers a simpler alternative to
# 'ida_kernwin.register_action', but is much less flexible.
#
# Author: IDAPython team
#---------------------------------------------------------------------
import ida_kernwin
def hotkey_pressed():
+13 -8
View File
@@ -1,12 +1,17 @@
"""
summary: triggering bits of code by pressing a shortcut (older version)
description:
This is a somewhat ancient way of registering actions & binding
shortcuts. It's still here for reference, but "fresher" alternatives
should be preferred.
keywords: actions
see_also: actions, add_hotkey
"""
from __future__ import print_function
#---------------------------------------------------------------------
# This script demonstrates the usage of hotkeys, using an alternative API.
# See also:
# add_hotkey.py
# actions.py
#
# Author: Gergely Erdelyi <gergely.erdelyi@d-dome.net>
#---------------------------------------------------------------------
import ida_expr
import ida_kernwin
+26 -20
View File
@@ -1,3 +1,28 @@
"""
summary: better integrating custom widgets in the desktop layout
description:
This is an example demonstrating how one can create widgets from a plugin,
and have them re-created automatically at IDA startup-time or at desktop load-time.
This example should be placed in the 'plugins' directory of the
IDA installation, for it to work.
There are 2 ways to use this example:
1) reloading an IDB, where the widget was opened
- open the widget ('View > Open subview > ...')
- save this IDB, and close IDA
- restart IDA with this IDB
=> the widget will be visible
2) reloading a desktop, where the widget was opened
- open the widget ('View > Open subview > ...')
- save the desktop ('Windows > Save desktop...') under, say, the name 'with_auto'
- start another IDA instance with some IDB, and load that desktop
=> the widget will be visible
keywords: desktop
"""
import ida_idaapi
import ida_kernwin
@@ -13,26 +38,7 @@ class auto_inst_t(ida_kernwin.simplecustviewer_t):
if not ida_kernwin.simplecustviewer_t.Create(self, title):
return False
text = r"""
This is an example demonstrating how one can create widgets from a plugin,
and have them re-created automatically at IDA startup-time or at desktop load-time.
This example should be placed in the 'plugins' directory of the
IDA installation, for it to work.
There are 2 ways to use this example:
1) reloading an IDB, where the widget was opened
- open the widget ('View > Open subview > """ + title + """')
- save this IDB, and close IDA
- restart IDA with this IDB
=> the widget will be visible
2) reloading a desktop, where the widget was opened
- open the widget ('View > Open subview > """ + title + """')
- save the desktop ('Windows > Save desktop...') under, say, the name 'with_auto'
- start another IDA instance with some IDB, and load that desktop
=> the widget will be visible
"""
text = __doc__
for l in text.split("\n"):
self.AddLine(l)
return True
+17 -9
View File
@@ -1,14 +1,22 @@
"""
summary: showcasing `ida_bytes.bin_search`
description:
IDAPython's ida_bytes.bin_search function is pretty powerful,
but can be tough to figure out at first. This example introduces
* `ida_bytes.bin_search`, and
* `ida_bytes.parse_binpat_str`
in order to implement a simple replacement for the
'Search > Sequence of bytes...' dialog, that lets users
search for sequences of bytes that compose string literals
in the binary file (either in the default 1-byte-per-char
encoding, or as UTF-16.)
"""
from __future__ import print_function
# IDAPython's ida_bytes.bin_search function is pretty powerful,
# but can be tough to figure out at first. This example introduces
# * ida_bytes.bin_search, and
# * ida_bytes.parse_binpat_str
# in order to implement a simple replacement for the
# 'Search > Sequence of bytes...' dialog, that lets users
# search for sequences of bytes that compose string literals
# in the binary file (either in the default 1-byte-per-char
# encoding, or as UTF-16.)
import ida_kernwin
import ida_bytes
@@ -1,3 +1,11 @@
"""
summary: programmatically create & populate a structure
description:
Usage of the API to create & populate a structure with
members of different types.
"""
from __future__ import print_function
#---------------------------------------------------------------------
# Structure test
+11
View File
@@ -1,3 +1,14 @@
"""
summary: a custom command-line interpreter
description:
Illustrates how one can add command-line interpreters to IDA
This custom interpreter doesn't actually run any code; it's
there as a 'getting started'.
It provides an example tab completion support.
"""
from __future__ import print_function
# -----------------------------------------------------------------------
# This is an example illustrating how to implement a CLI
+13 -4
View File
@@ -1,8 +1,17 @@
"""
summary: using custom data types & printers
description:
IDA can be extended to support certain data types that it
does not know about out-of-the-box.
A 'custom data type' provide information about the type &
size of a piece of data, while a 'custom data format' is in
charge of formatting that data (there can be more than
one format for a specific 'custom data type'.)
"""
from __future__ import print_function
# -----------------------------------------------------------------------
# This is an example illustrating how to use custom data types in Python
# (c) Hex-Rays
#
import ida_bytes
import ida_idaapi
+11 -9
View File
@@ -1,13 +1,15 @@
"""
summary: retrieve extra comments
description:
Use the `ida_lines.get_extra_cmt` API to retrieve anterior
and posterior extra comments.
This script registers two actions, that can be used to dump
the previous and next extra comments.
"""
from __future__ import print_function
# -----------------------------------------------------------------------
# This example illustrates how to use the 'get_extra_cmt' API,
# to retrieve anterior and posterior extra comments.
#
# After running this script, use Ctrl+Shift+Y when in the disassembly
# view to print previous extra comment, and Ctrl+Shift+Z to print next
# extra comments.
#
# (c) Hex-Rays
import ida_lines
import ida_kernwin
+10
View File
@@ -1,4 +1,14 @@
# -*- coding: utf-8 -*-
"""
summary: dump function flowchart
description:
Dumps the current function's flowchart, using 2 methods:
* the low-level `ida_gdl.qflow_chart_t` type
* the somewhat higher-level, and slightly more pythonic
`ida_gdl.FlowChart` type.
"""
from __future__ import print_function
+16 -9
View File
@@ -1,15 +1,22 @@
"""
summary: retrieve & dump current selection
description:
Shows how to retrieve the selection from a listing
widget ("IDA View-A", "Hex View-1", "Pseudocode-A", ...) as
two "cursors", and from there retrieve (in fact, generate)
the corresponding text.
After running this script:
* select some text in one of the listing widgets (i.e.,
"IDA View-*", "Enums", "Structures", "Pseudocode-*")
* press Ctrl+Shift+S to dump the selection
"""
from __future__ import print_function
# This example illustrates how to accurately retrieve the current selection.
#
# After running this script:
# * select some text in one of the listing widgets (i.e.,
# "IDA View-*", "Enums", "Structures", "Pseudocode-*")
# * press Ctrl+Shift+S to dump the selection
#
# (c) Hex-Rays
import ida_kernwin
import ida_lines
+16 -4
View File
@@ -1,8 +1,20 @@
"""
summary: add functions to the IDC runtime from IDAPython
description:
You can add IDC functions to IDA, whose "body" consists of
IDAPython statements!
We'll register a 'pow' function, available to all IDC code,
that when invoked will call back into IDAPython, and execute
the provided function body.
After running this script, try switching to the IDC interpreter
(using the button on the lower-left corner of IDA) and executing
`pow(3, 7)`
"""
from __future__ import print_function
# -----------------------------------------------------------------------
# This is an example illustrating how to extend IDC from Python
# (c) Hex-Rays
#
import ida_expr
+12 -6
View File
@@ -1,9 +1,15 @@
#---------------------------------------------------------------------
# Example user initialisation script: idapythonrc.py
#
# Place this script to ~/.idapro/ or to
# %APPDATA%\Hex-Rays\IDA Pro
#---------------------------------------------------------------------
"""
summary: code to be run right after IDAPython initialization
description:
The `idapythonrc.py` file:
* %APPDATA%\Hex-Rays\IDA Pro\idapythonrc.py (on Windows)
* ~/.idapro/idapythonrc.py (on Linux & Mac)
can contain any IDAPython code that will be run as soon as
IDAPython is done successfully initializing.
"""
# Add your favourite script to ScriptBox for easy access
# scriptbox.addscript("/here/is/my/favourite/script.py")
@@ -1,3 +1,13 @@
"""
summary: inserting information into disassembly prefixes
description:
By default, disassembly line prefixes contain segment + address
information (e.g., '.text:08047718'), but it is possible to
"inject" other bits of information in there, thanks to the
`ida_lines.user_defined_prefix_t` helper type.
"""
from __future__ import print_function
import ida_lines
+8 -4
View File
@@ -1,8 +1,12 @@
"""
summary: enumerate file imports
description:
Using the API to enumerate file imports.
"""
from __future__ import print_function
# -----------------------------------------------------------------------
# This is an example illustrating how to enumerate imports
# (c) Hex-Rays
#
import ida_nalt
nimps = ida_nalt.get_import_module_qty()
+8 -3
View File
@@ -1,7 +1,12 @@
"""
summary: enumerate patched bytes
description:
Using the API to iterate over all the places in the file,
that were patched using IDA.
"""
from __future__ import print_function
# -------------------------------------------------------------------------
# This is an example illustrating how to visit all patched bytes in Python
# (c) Hex-Rays
import ida_bytes
import ida_idaapi
+7
View File
@@ -1,3 +1,10 @@
"""
summary: enumerate problems
description:
Using the API to list all problem[atic situation]s that IDA
encountered during analysis.
"""
import ida_ida
import ida_idaapi
+12
View File
@@ -1,3 +1,15 @@
"""
summary: list all functions (and xrefs) in segment
description:
List all the functions in the current segment, as well as
all the cross-references to them.
keywords: xrefs
see_also: list_segment_functions_using_idautils
"""
from __future__ import print_function
#
# Reference Lister
@@ -1,3 +1,18 @@
"""
summary: list all functions (and xrefs) in segment
description:
List all the functions in the current segment, as well as
all the cross-references to them.
Contrary to @list_segment_functions, this uses the somewhat
higher-level `idautils` module.
keywords: xrefs
see_also: list_segment_functions
"""
from __future__ import print_function
#
# Reference Lister
+10 -11
View File
@@ -1,14 +1,13 @@
#
# This example demonstrates how to retrieve all xrefs to
# a stack variable within a function.
# Contrary to (in-memory) data & code xrefs, retrieving
# stack variables xrefs require a bit more work than just
# using ida_xref's first_to(), next_to() (or higher level
# utilities such as idautils.XrefsTo)
#
# Press Ctrl+Shift+F7 to invoke the action that will print xrefs
# to the variable name that's under the cursor.
#
"""
summary: list all xrefs to a function stack variable
description:
Contrary to (in-memory) data & code xrefs, retrieving stack variables
xrefs requires a bit more work than just using ida_xref's first_to(),
next_to() (or higher level utilities such as idautils.XrefsTo)
keywords: xrefs
"""
ACTION_NAME = "list_stkvar_xrefs:list"
ACTION_SHORTCUT = "Ctrl+Shift+F7"
+11
View File
@@ -1,3 +1,14 @@
"""
summary: retrieve the strings that are present in the IDB
description:
This uses `idautils.Strings` to iterate over the string literals
that are present in the IDB. Contrary to @show_selected_strings,
this will not require that the "Strings" window is opened & available.
see_also: show_selected_strings
"""
from __future__ import print_function
import idautils
+14 -9
View File
@@ -1,15 +1,20 @@
"""
This example demonstrates how one can automate IDA to perform auto-analysis
on a file and, as soon as it is finished, produce a .c file containing the
decompilation of all the functions that the file contains.
summary: decompile entire file
Run like so:
ida -A "-S...path/to/produce_c_file.py" <binary-file>
description:
automate IDA to perform auto-analysis on a file and,
once that is done, produce a .c file containing the
decompilation of all the functions in that file.
where:
-A instructs IDA to run in non-interactive mode
-S holds a path to the script to run (note this is a single token;
there is no space between '-S' and its path.)
Run like so:
ida -A "-S...path/to/produce_c_file.py" <binary-file>
where:
* -A instructs IDA to run in non-interactive mode
* -S holds a path to the script to run (note this is a single token;
there is no space between '-S' and its path.)
"""
import ida_pro
+13 -9
View File
@@ -1,15 +1,19 @@
"""
This example demonstrates how one can automate IDA to perform auto-analysis
on a file and, as soon as it is finished, produce a .lst file containing the
disassembly.
summary: produce listing
Run like so:
ida -A "-S...path/to/produce_lst_file.py" <binary-file>
description:
automate IDA to perform auto-analysis on a file and,
once that is done, produce a .lst file with the disassembly.
where:
-A instructs IDA to run in non-interactive mode
-S holds a path to the script to run (note this is a single token;
there is no space between '-S' and its path.)
Run like so:
ida -A "-S...path/to/produce_lst_file.py" <binary-file>
where:
* -A instructs IDA to run in non-interactive mode
* -S holds a path to the script to run (note this is a single token;
there is no space between '-S' and its path.)
"""
import ida_auto
+7 -3
View File
@@ -1,7 +1,11 @@
"""
summary: using timers for delayed execution
description:
Register (possibly repeating) timers.
"""
from __future__ import print_function
# -------------------------------------------------------------------------
# This is an example illustrating how to use timers
# (c) Hex-Rays
import ida_kernwin
@@ -1,15 +1,21 @@
"""
summary: execute existing actions programmatically
description:
It's possible to invoke any action programmatically, by using
either of those two:
* ida_kernwin.execute_ui_requests()
* ida_kernwin.process_ui_action()
Ideally, this script should be run through the "File > Script file..."
menu, so as to keep focus on "IDA View-A" and have the
'ProcessUiActions' part work as intended.
keywords: actions
"""
from __future__ import print_function
# -----------------------------------------------------------------------
# This is an example illustrating how to use
# * ida_kernwin.execute_ui_requests()
# * ida_kernwin.process_ui_action()
#
# Ideally, this script should be run through the "File > Script file..."
# menu, so as to keep focus on "IDA View-A" and have the
# 'ProcessUiActions' part work as intended.
#
# (c) Hex-Rays
#
import ida_kernwin
@@ -1,20 +1,26 @@
"""
summary: executing code into the application being debugged (on Linux)
description:
Using the `ida_idd.Appcall` utility to execute code in
the process being debugged.
This example will run the test program and stop wherever
the cursor currently is, and then perform an appcall to
execute the `ref4` and `ref8` functions.
To use this example:
* run `ida64` on test program `simple_appcall_linux64`, or
`ida` on test program `simple_appcall_linux32`, and wait for
auto-analysis to finish
* select the 'linux debugger' (either local, or remote)
* run this script
Note: the real body of code is in `simple_appcall_common.py`.
"""
from __future__ import print_function
#
# This sample illustrates how to use appcall, with the
# 'simple_appcall_linux32' or 'simple_appcall_linux64' test
# programs (see subdirectories.)
#
# This example will run the test program and stop wherever
# the cursor currently is, and then perform an appcall to
# `ref4` and `ref8`
#
# To use this example:
# * run `ida64` on test program `simple_appcall_linux64`, or
# `ida` on test program `simple_appcall_linux32`, and wait for
# auto-analysis to finish
# * select the 'linux debugger' (either local, or remote)
# * run this script
#
import os
import sys
@@ -1,20 +1,26 @@
"""
summary: executing code into the application being debugged (on Windows)
description:
Using the `ida_idd.Appcall` utility to execute code in
the process being debugged.
This example will run the test program and stop wherever
the cursor currently is, and then perform an appcall to
execute the `ref4` and `ref8` functions.
To use this example:
* run `ida64` on test program `simple_appcall_win64.exe`, or
`ida` on test program `simple_appcall_win32.exe`, and wait for
auto-analysis to finish
* select the 'windows debugger' (either local, or remote)
* run this script
Note: the real body of code is in `simple_appcall_common.py`.
"""
from __future__ import print_function
#
# This sample illustrates how to use appcall, with the
# 'simple_appcall_win32.exe' or 'simple_appcall_win64.exe' test
# programs (see subdirectories.)
#
# This example will run the test program and stop wherever
# the cursor currently is, and then perform an appcall to
# `ref4` and `ref8`
#
# To use this example:
# * run `ida64` on test program `simple_appcall_win64.exe`, or
# `ida` on test program `simple_appcall_win32.exe`, and wait for
# auto-analysis to finish
# * select the 'windows debugger' (either local, or remote)
# * run this script
#
import os
import sys
+9 -11
View File
@@ -1,15 +1,13 @@
"""
summary: programmatically drive a debugging session
description:
Start a debugging session, step through the first five
instructions. Each instruction is disassembled after
execution.
"""
from __future__ import print_function
#---------------------------------------------------------------------
# Debug notification hook test
#
# This script start the executable and steps through the first five
# instructions. Each instruction is disassembled after execution.
#
# Original Author: Gergely Erdelyi <gergely.erdelyi@d-dome.net>
#
# Maintained By: IDAPython Team
#
#---------------------------------------------------------------------
import ida_dbg
import ida_ida
+8 -2
View File
@@ -1,7 +1,13 @@
"""
This script demonstrates using the low-level tracing hook (dbg_trace)
It can be run like: ida[t].exe -B -Sdbg_trace.py -Ltrace.log file.exe
summary: using the low-level tracing hook
description:
This script demonstrates using the low-level tracing hook
(ida_dbg.DBG_Hooks.dbg_trace). It can be run like so:
ida[t].exe -B -Sdbg_trace.py -Ltrace.log file.exe
"""
import time
import ida_dbg
@@ -1,3 +1,13 @@
"""
summary: adding actions to the "registers" widget(s)
description:
It's possible to add actions to the context menu of
pretty much all widgets in IDA.
This example shows how to do just that for
registers-displaying widgets (e.g., "General registers")
"""
import ida_dbg
import ida_idd
+8
View File
@@ -1,3 +1,11 @@
"""
summary: retrieving & dumping debuggee symbols
description:
Queries the debugger (possibly remotely) for the list of
symbols that the process being debugged, provides.
"""
from __future__ import print_function
import ida_dbg
+11 -5
View File
@@ -1,10 +1,16 @@
"""
This example shows how one can dynamically alter the lines background
rendering for pseudocode listings (as opposed to using
ida_hexrays.cfunc_t.pseudocode[N].bgcolor)
summary: interactively color certain pseudocode lines
After running this script, pressing 'M' on a line in a "Pseudocode-?"
widget, will cause that line to be rendered with a special background color.
description:
Provides an action that can be used to dynamically alter the
lines background rendering for pseudocode listings (as opposed to
using `ida_hexrays.cfunc_t.pseudocode[N].bgcolor`)
After running this script, pressing 'M' on a line in a
"Pseudocode-?" widget, will cause that line to be rendered
with a special background color.
keywords: colors
"""
import ida_kernwin
+12 -9
View File
@@ -1,13 +1,16 @@
from __future__ import print_function
"""
summary: automatic decompilation of functions
#
# This example tries to load a decompiler plugin corresponding to the current
# architecture (and address size) right after auto-analysis is performed,
# and then tries to decompile the function at the first entrypoint.
#
# It is particularly suited for use with the '-S' flag, for example:
# idat -Ldecompile.log -Sdecompile_entry_points.py -c file
#
description:
Attempts to load a decompiler plugin corresponding to the current
architecture (and address size) right after auto-analysis is performed,
and then tries to decompile the function at the first entrypoint.
It is particularly suited for use with the '-S' flag, for example:
idat -Ldecompile.log -Sdecompile_entry_points.py -c file
"""
from __future__ import print_function
import ida_ida
import ida_auto
+4
View File
@@ -1,3 +1,7 @@
"""
summary: decompile & print current function.
"""
from __future__ import print_function
import ida_hexrays
+15 -15
View File
@@ -1,18 +1,18 @@
#
# Hex-Rays Decompiler project
# Copyright (c) 2007-2021 by Hex-Rays, support@hex-rays.com
# ALL RIGHTS RESERVED.
#
# Sample plugin for Hex-Rays Decompiler.
# It installs a custom microcode optimization rule:
# call !DbgRaiseAssertionFailure <fast:>.0
# =>
# call !DbgRaiseAssertionFailure <fast:"char *" "assertion text">.0
#
# To see this plugin in action please use arm64_brk.i64, in the hexrays sdk
#
# This is a rewrite in Python of the vds10 example that comes with hexrays sdk.
#
"""
summary: a custom microcode instruction optimization rule
description:
Installs a custom microcode instruction optimization rule,
to transform:
call !DbgRaiseAssertionFailure <fast:>.0
into
call !DbgRaiseAssertionFailure <fast:"char *" "assertion text">.0
To see this plugin in action please use arm64_brk.i64
"""
import ida_bytes
import ida_range
+19 -18
View File
@@ -1,21 +1,22 @@
#
# Hex-Rays Decompiler project
# Copyright (c) 2007-2021 by Hex-Rays, support@hex-rays.com
# ALL RIGHTS RESERVED.
#
# Sample plugin for Hex-Rays Decompiler.
# It installs a custom block optimization rule:
#
# goto L1 => goto L2
# ...
# L1:
# goto L2
#
# In other words we fix a goto target if it points to a chain of gotos.
# This improves the decompiler output in some cases.
#
# This is a rewrite in Python of the vds11 example that comes with hexrays sdk.
#
"""
summary: a custom microcode block optimization rule (resolve `goto` chains)
description:
Installs a custom microcode block optimization rule,
to transform:
goto L1
...
L1:
goto L2
into
goto L2
In other words we fix a goto target if it points to a chain of gotos.
This improves the decompiler output in some cases.
"""
import ida_bytes
import ida_range
+7 -11
View File
@@ -1,14 +1,10 @@
#
# Hex-Rays Decompiler project
# Copyright (c) 2007-2021 by Hex-Rays, support@hex-rays.com
# ALL RIGHTS RESERVED.
#
# Sample script for Hex-Rays Decompiler.
# It shows list of direct references to a register from the current
# instruction.
#
# This is a rewrite in Python of the vds12 example that comes with hexrays sdk.
#
"""
summary: list instruction registers
description:
Shows a list of direct references to a register from the
current instruction.
"""
import ida_pro
import ida_hexrays
+6 -10
View File
@@ -1,13 +1,9 @@
#
# Hex-Rays Decompiler project
# Copyright (c) 2007-2021 by Hex-Rays, support@hex-rays.com
# ALL RIGHTS RESERVED.
#
# Sample script for Hex-Rays Decompiler.
# It generates microcode for selection and dumps it to the output window.
#
# This is a rewrite in Python of the vds13 example that comes with hexrays sdk.
#
"""
summary: generates microcode for selection
description:
Generates microcode for selection and dumps it to the output window.
"""
import ida_bytes
import ida_range
+12 -12
View File
@@ -1,15 +1,15 @@
#
# Hex-Rays Decompiler project
# Copyright (c) 2007-2021 by Hex-Rays, support@hex-rays.com
# ALL RIGHTS RESERVED.
#
# Sample plugin for Hex-Rays Decompiler.
# It shows how to use "Select offsets" widget (select_udt_by_offset() call).
# This plugin repeats the Alt-Y functionality.
# Usage: place cursor on the union field and press Shift-T
#
# This is a rewrite in Python of the vds17 example that comes with hexrays sdk.
#
"""
summary: using the "Select offsets" widget
description:
Registers an action opens the "Select offsets" widget
(select_udt_by_offset() call).
This effectively repeats the functionality already available
through Alt+Y.
Place cursor on the union field and press Shift+T
"""
import ida_idaapi
import ida_hexrays
+15 -11
View File
@@ -1,14 +1,18 @@
#
# Hex-Rays Decompiler project
# Copyright (c) 2007-2021 by Hex-Rays, support@hex-rays.com
# ALL RIGHTS RESERVED.
#
# Sample plugin for Hex-Rays Decompiler.
# It installs a custom microcode optimization rule:
# x | ~x => -1
#
# To see this plugin in action please use be_ornot_be.idb
#
"""
summary: a custom microcode instruction optimization rule (`x | ~x => -1`)
description:
Installs a custom microcode instruction optimization rule,
to transform:
x | ~x
into
-1
To see this plugin in action please use be_ornot_be.idb
"""
import ida_hexrays
import ida_idaapi
+18 -17
View File
@@ -1,26 +1,27 @@
""" Example: provide custom call type dynamically
"""
summary: dynamically provide a custom call type
This plugin can greatly improve decompilation of indirect calls:
description:
This plugin can greatly improve decompilation of indirect calls:
call [eax+4]
call [eax+4]
For them, the decompiler has to guess the prototype of the called function.
This has to be done at a very early phase of decompilation because
the function prototype influences the data flow analysis. On the other
hand, we do not have global data flow analysis results yet because
we haven't analyzed all calls in the function. It is a chicked-and-egg
problem.
For them, the decompiler has to guess the prototype of the called function.
This has to be done at a very early phase of decompilation because
the function prototype influences the data flow analysis. On the other
hand, we do not have global data flow analysis results yet because
we haven't analyzed all calls in the function. It is a chicked-and-egg
problem.
The decompiler uses various techniques to guess the called function
prototype. While it works very well, it may fail in some cases.
The decompiler uses various techniques to guess the called function
prototype. While it works very well, it may fail in some cases.
To fix, the user can specify the call prototype manually, using
"Edit, Operand types, Set operand type" at the call instruction.
This plugin illustrates another approach to the problem:
if you happen to be able to calculate the call prototypes dynamically,
this is how to inform the decompiler about them.
To fix, the user can specify the call prototype manually, using
"Edit, Operand types, Set operand type" at the call instruction.
This plugin illustrates another approach to the problem:
if you happen to be able to calculate the call prototypes dynamically,
this is how to inform the decompiler about them.
"""
import ida_idaapi
+34 -5
View File
@@ -1,9 +1,38 @@
""" Invert the then and else blocks of a cif_t.
Author: EiNSTeiN_ <einstein@g3nius.org>
This is a rewrite in Python of the vds3 example that comes with hexrays sdk.
"""
summary: invert if/else blocks
description:
Registers an action that can be used to invert the `if`
and `else` blocks of a `ida_hexrays.cif_t`.
For example, a statement like
if ( cond )
{
statements1;
}
else
{
statements2;
}
will be displayed as
if ( !cond )
{
statements2;
}
else
{
statements1;
}
The modifications are persistent: the user can quit & restart
IDA, and the changes will be present.
author: EiNSTeiN_ <einstein@g3nius.org>
"""
from __future__ import print_function
import idautils
+14 -5
View File
@@ -1,9 +1,18 @@
""" Print user-defined details to the output window.
Author: EiNSTeiN_ <einstein@g3nius.org>
This is a rewrite in Python of the vds4 example that comes with hexrays sdk.
"""
summary: dump user-defined information
description:
Prints user-defined information to the "Output" window.
Namely:
* user defined label names
* user defined indented comments
* user defined number formats
* user defined local variable names, types, comments
author: EiNSTeiN_ <einstein@g3nius.org>
"""
from __future__ import print_function
import ida_kernwin
+14
View File
@@ -1,3 +1,17 @@
"""
summary: show ctree graph
description:
Registers an action that can be used to show the graph of the ctree.
The current item will be highlighted in the graph.
The command shortcut is `Ctrl+Shift+G`, and is also added
to the context menu.
To display the graph, we produce a .gdl file, and
request that ida displays that using `ida_gdl.display_gdl`.
"""
from __future__ import print_function
import ida_pro
+7 -4
View File
@@ -1,10 +1,13 @@
"""
This is a crude (and not very pythonic) reimplementation of the example
hexrays plugin 'hexrays_sample6.cpp', shipped with the Hex-Rays decompiler.
summary: superficially modify the decompilation output
It modifies the decompilation output: removes some space characters.
description:
modifies the decompilation output in a superficial manner,
by removing some white spaces
Note: this is rather crude, not quite "pythonic" code.
"""
from __future__ import print_function
import idautils
+9 -5
View File
@@ -1,9 +1,13 @@
""" It demonstrates how to iterate a cblock_t object.
Author: EiNSTeiN_ <einstein@g3nius.org>
This is a rewrite in Python of the vds7 example that comes with hexrays sdk.
"""
summary: iterate a cblock_t object
description:
Using a `ida_hexrays.ctree_visitor_t`, search for
`ida_hexrays.cit_block` instances and dump them.
author: EiNSTeiN_ <einstein@g3nius.org>
"""
from __future__ import print_function
import ida_hexrays
+12 -10
View File
@@ -1,14 +1,16 @@
"""
summary: using `ida_hexrays.udc_filter_t`
# Hex-Rays Decompiler project
# Copyright (c) 2007-2021 by Hex-Rays, support@hex-rays.com
# ALL RIGHTS RESERVED.
#
# Sample script for Hex-Rays Decompiler usage of udc_filter_t
# class: decompile svc 0x900001 and svc 0x9000F8 as function calls to
# svc_exit() and svc_exit_group() respectively.
# NOTE: You will need to have an ARM + Linux IDB for this script to be usable
#
# It is also added into the right-click menu as "vds8.py:Toggle UDC"
description:
Registers an action that uses a `ida_hexrays.udc_filter_t` to decompile
`svc 0x900001` and `svc 0x9000F8` as function calls to
`svc_exit()` and `svc_exit_group()` respectively.
You will need to have an ARM + Linux IDB for this script to be usable
In addition to having a shortcut, the action will be present
in the context menu.
"""
import ida_idaapi
import ida_hexrays
+13 -6
View File
@@ -1,10 +1,17 @@
"""'Hints' example for Hexrays Decompiler
"""
summary: decompiler hints
Handle 'hxe_create_hint' notification using hooks, to return our own.
If the object under the cursor is:
- a function call, prefix the original decompiler hint with "==> "
- a local variable declaration, replace the hint with our own in the form of "!{varname}" (where '{varname}' is replaced w/ the variable name)
- an 'if' statement, replace the hint with our own, saying "condition"
description:
Handle `ida_hexrays.hxe_create_hint` notification using hooks,
to return our own.
If the object under the cursor is:
* a function call, prefix the original decompiler hint with `==> `
* a local variable declaration, replace the hint with our own in
the form of `!{varname}` (where `{varname}` is replaced with the
variable name)
* an `if` statement, replace the hint with our own, saying "condition"
"""
import ida_idaapi
+10 -1
View File
@@ -1,5 +1,14 @@
"""
Various hooks for Hexrays Decompiler
summary: various decompiler hooks
description:
Shows how to hook to many notifications sent by the decompiler.
This plugin doesn't really accomplish anything: it just prints
the parameters.
Also, the list of notifications handled below, isn't exhaustive.
Please investigate `ida_hexrays.Hexrays_Hooks` for a full list.
"""
from __future__ import print_function
@@ -1,3 +1,10 @@
"""
summary: modifying local variables
description:
Use a `ida_hexrays.user_lvar_modifier_t` to modify names,
comments and/or types of local variables.
"""
import ida_hexrays
import ida_typeinf
+8 -6
View File
@@ -1,12 +1,14 @@
""" Xref script for Hexrays Decompiler
"""
summary: show decompiler xrefs
Author: EiNSTeiN_ <einstein@g3nius.org>
description:
Show decompiler-style Xref when the `Ctrl+X` key is
pressed in the Decompiler window.
Show decompiler-style Xref when the X key is pressed in the Decompiler window.
- It supports any global name: functions, strings, integers, etc.
- It supports structure member.
* supports any global name: functions, strings, integers, ...
* supports structure member.
author: EiNSTeiN_ <einstein@g3nius.org>
"""
from __future__ import print_function
+6 -2
View File
@@ -1,7 +1,11 @@
"""
This example shows notifications whenever the user changes
an instruction's operand, or a data item.
summary: notify the user when an instruction operand changes
description:
Show notifications whenever the user changes
an instruction's operand, or a data item.
"""
import binascii
import ida_idp
+11 -3
View File
@@ -1,7 +1,15 @@
"""
summary: override some parts of the processor module
description:
Implements disassembly of BUG_INSTR used in Linux kernel
BUG() macro, which is architecturally undefined and is not
disassembled by IDA's ARM module
See Linux/arch/arm/include/asm/bug.h for more info
"""
from __future__ import print_function
# this script implements disassembly of BUG_INSTR used in Linux kernel BUG() macro
# normally it's architecturally undefined and is not disassembled by IDA's ARM module
# see Linux/arch/arm/include/asm/bug.h
import ida_idp
import ida_bytes
+10 -11
View File
@@ -1,18 +1,17 @@
"""
summary: an `ida_idp.IDP_Hooks.assembly` implementation
description:
We add support for assembling the following pseudo instructions:
* "zero eax" -> xor eax, eax
* "nothing" -> nop
"""
from __future__ import print_function
import ida_idp
import idautils
"""
This is a sample script for extending the assemble() hook.
We add support for assembling the following pseudo instructions:
- "zero eax" -> xor eax, eax
- "nothing" -> nop
(c) Hex-Rays
"""
#--------------------------------------------------------------------------
class assemble_idp_hook_t(ida_idp.IDP_Hooks):
def assemble(self, ea, cs, ip, use32, line):
+28
View File
@@ -0,0 +1,28 @@
body
{
margin: 3%;
}
.exp-col
{
cursor: pointer;
padding: 0 4px 0 6px;
}
.collapsed-entry .details
{
display: none;
}
.example-entry .details
{
margin-left: 3%;
padding: 6px;
background-color: #eef;
}
a
{
text-decoration: none;
}
+2928 -4024
View File
File diff suppressed because it is too large Load Diff
+73
View File
@@ -0,0 +1,73 @@
function on_see_also(see_also)
{
if ( is_expanded() )
{
// likely it is, since I clicked on "see also";
// close it
expand_toggle(name_expanded);
}
expand_toggle(see_also);
document.getElementById('IMG_' + see_also).scrollIntoView();
return true;
}
function find_parent_with_class(el, klass)
{
while ( el )
{
if ( el.className && el.className.indexOf(klass) > -1 )
return el;
el = el.parentNode;
}
}
function find_child_with_class(el, klass)
{
return el.querySelector("." + klass);
}
function find_entry_el(el) { return find_parent_with_class(el, "example-entry"); }
function find_expander(entry_el) { return find_child_with_class(entry_el, "expander"); }
function find_collapser(entry_el) { return find_child_with_class(entry_el, "collapser"); }
function set_entry_state(entry_el, expanded)
{
var collapser_el = find_collapser(entry_el);
var expander_el = find_expander(entry_el);
collapser_el.style.display = expanded ? "" : "none";
expander_el.style.display = expanded ? "none" : "";
if ( expanded )
entry_el.classList.remove("collapsed-entry");
else
entry_el.classList.add("collapsed-entry");
}
function handle_click(e)
{
e = e || window.event;
var el = e.target || e.srcElement;
var entry_el = find_entry_el(el);
var ok = false;
if ( el.classList.contains("collapser") )
set_entry_state(entry_el, false);
else if ( el.classList.contains("expander") )
set_entry_state(entry_el, true);
else
return;
e.stopPropagation();
}
function handle_toplevel_action(e)
{
e = e || window.event;
var el = e.target || e.srcElement;
var expanded = el.classList.contains("expand-all");
var els = document.getElementsByClassName("example-entry");
for ( var idx = 0, n = els.length; idx < n; ++idx )
set_entry_state(els[idx], expanded);
e.stopPropagation();
}
+288 -150
View File
File diff suppressed because it is too large Load Diff
+20 -16
View File
@@ -1,19 +1,23 @@
#
# This example illustrates how one can execute commands in the
# "Output" window, from their own widgets.
#
# In order to do so, we have to be careful that:
# - the original, underlying 'cli:Execute' action, that has to be
# triggered for the code present in the input field to execute
# and be placed in the history, requires that the input field
# has focus (otherwise it simply won't do anything.)
# - this, in turn, forces us to do "delayed" execution of that action,
# hence the need for a QTimer
# - the IDA/SWiG 'TWidget' type that we retrieve through
# `ida_kernwin.find_widget`, is not the same type as a
# `QtWidgets.QWidget`. We therefore need to convert it using
# `ida_kernwin.PluginForm.TWidgetToPyQtWidget`
#
"""
summary: injecting commands is the "Output" window
description:
This example illustrates how one can execute commands in the
"Output" window, from their own widgets.
A few notes:
* the original, underlying `cli:Execute` action, that has to be
triggered for the code present in the input field to execute
and be placed in the history, requires that the input field
has focus (otherwise it simply won't do anything.)
* this, in turn, forces us to do "delayed" execution of that action,
hence the need for a `QTimer`
* the IDA/SWiG 'TWidget' type that we retrieve through
`ida_kernwin.find_widget`, is not the same type as a
`QtWidgets.QWidget`. We therefore need to convert it using
`ida_kernwin.PluginForm.TWidgetToPyQtWidget`
"""
from PyQt5 import QtCore
from PyQt5 import QtGui
+8
View File
@@ -1,3 +1,11 @@
"""
summary: custom painting on top of the navigation band
description:
Using an "event filter", we'll intercept paint events
targeted at the navigation band widget, let it paint itself,
and then add our own markers on top.
"""
import random
@@ -1,3 +1,12 @@
"""
summary: adding PyQt5 widgets into an `ida_kernwin.PluginForm`
description:
Using `ida_kernwin.PluginForm.FormToPyQtWidget`, this script
converts IDA's own dockable widget into a type that is
recognized by PyQt5, which then enables populating it with
regular Qt widgets.
"""
from PyQt5 import QtCore, QtGui, QtWidgets
+5 -2
View File
@@ -1,6 +1,9 @@
"""
We color the function in the Function window according to its size.
The larger the function, the darker the color.
summary: using `ida_kernwin.UI_Hooks.get_chooser_item_attrs` to override some defaults
description:
color the function in the Function window according to its size.
The larger the function, the darker the color.
"""
import ida_kernwin
+7 -4
View File
@@ -1,8 +1,11 @@
"""
This example shows how one can dynamically alter the lines background
rendering (as opposed to, say, using ida_nalt.set_item_color()), and
also shows how that rendering can be limited to just a few glyphs,
not the whole line.
summary: dynamically colorize lines backgrounds (or parts of them)
description:
shows how one can dynamically alter the lines background
rendering (as opposed to, say, using ida_nalt.set_item_color()),
and also shows how that rendering can be limited to just a few
glyphs, not the whole line.
"""
import ida_kernwin
+8 -8
View File
@@ -1,12 +1,12 @@
"""
summary: being notified, and logging a few UI events
description:
hooks to be notified about certain UI events, and
dump their information to the "Output" window
"""
from __future__ import print_function
#---------------------------------------------------------------------
# UI hook example
#
# (c) Hex-Rays
#
# Maintained By: IDAPython Team
#
#---------------------------------------------------------------------
import ida_kernwin
+6 -2
View File
@@ -1,6 +1,10 @@
"""
This example shows how to use ida_kernwin.UI_Hooks, to respond to a
command instead of the action that would otherwise do it.
summary: taking precedence over actions
description:
Using `ida_kernwin.UI_Hooks.preprocess_action`, it is possible
to respond to a command instead of the action that would
otherwise do it.
"""
import ida_kernwin
+13
View File
@@ -1,3 +1,16 @@
"""
summary: Non-trivial uses of the `ida_kernwin.Form` helper class
description:
How to query for complex user input, using IDA's built-in forms.
Note: while this example produces full-fledged forms for complex input,
simpler types of inputs might can be retrieved by using
`ida_kernwin.ask_str` and similar functions.
keywords: forms
"""
from __future__ import print_function
# -----------------------------------------------------------------------
# This is an example illustrating how to use the Form class
@@ -1,3 +1,13 @@
"""
summary: drawing custom graphs
description:
Showing custom graphs, using `ida_graph.GraphViewer`. In addition,
show how to write actions that can be performed on those.
keywords: graph, actions
"""
from __future__ import print_function
# -----------------------------------------------------------------------
# This is an example illustrating how to use the user graphing functionality
@@ -1,3 +1,19 @@
"""
summary: follow the movements of a disassembly graph, in another.
description:
Since it is possible to be notified of movements that happen
take place in a widget, it's possible to "replay" those
movements in another.
In this case, "IDA View-B" (will be opened if necessary) will
show the same contents as "IDA View-A", slightly zoomed out.
keywords: graph, idaview
see_also: wrap_idaview
"""
from __future__ import print_function
import ida_kernwin
+1 -2
View File
@@ -3,14 +3,13 @@ summary: manipulate IDAView and graph
description:
This is an example illustrating how to manipulate an existing IDA-provided
view (and thus its graph), in Python.
view (and thus possibly its graph), in Python.
keywords: idaview, graph
see_also: custom_graph_with_actions, sync_two_graphs
"""
from __future__ import print_function
# -----------------------------------------------------------------------
+15 -4
View File
@@ -1,8 +1,19 @@
"""
summary: create custom listings in IDA
description:
How to create simple listings, that will share many of the features
as the built-in IDA widgets (highlighting, copy & paste,
notifications, ...)
In addition, creates actions that will be bound to the
freshly-created widget (using `ida_kernwin.attach_action_to_popup`.)
keywords: listing, actions
"""
from __future__ import print_function
# -----------------------------------------------------------------------
# This is an example illustrating how to use customview in Python
# (c) Hex-Rays
#
import ida_kernwin
import ida_lines
@@ -1,7 +1,5 @@
"""
summary:
This example illustrates how one can implement a "jump to next comment"
action within IDA's disassembly view.
summary: implement a "jump to next comment" action within IDA's disassembly view.
description:
We want our action not only to find the next line containing a comment,
@@ -19,6 +17,8 @@ description:
but can also be very handy for spotting tokens of interest (registers,
addresses, comments, prefixes, instruction mnemonics, ...)
keywords: idaview, actions
see_also: save_and_restore_listing_pos
"""
@@ -1,8 +1,21 @@
"""
summary: save, and then restore, positions in a listing
#
# This example lets the user save the current position of
# the current listing widget, and restore it later
#
description:
Shows how it is possible re-implement IDA's bookmark capability,
using 2 custom actions: one action saves the current location,
and the other restores it.
Note that, contrary to actual bookmarks, this example:
* remembers only 1 saved position
* doesn't save that position in the IDB (and therefore cannot
be restored if IDA is closed & reopened.)
keywords: listing, actions
see_also: jump_next_comment
"""
import ida_kernwin
import ida_moves
+15 -5
View File
@@ -1,8 +1,18 @@
# -----------------------------------------------------------------------
# This is an example illustrating how to add custom menus to IDA, either
# at the toplevel (i.e., the menubar), or as submenus in existing menus.
# (c) Hex-Rays
#
"""
summary: adding custom menus to IDA
description:
It is possible to add custom menus to IDA, either at the
toplevel (i.e., into the menubar), or as submenus of existing
menus.
Notes:
* the same action can be present in more than 1 menu
* this example does not deal with context menus
keywords: actions
"""
import ida_kernwin
@@ -1,3 +1,16 @@
"""
summary: A widget showing data in a tabular fashion
description:
Shows how to subclass the ida_kernwin.Choose class to
show data organized in a simple table.
In addition, registers a couple actions that can be applied to it.
keywords: chooser, actions
see_also: choose_multi, chooser_with_folders
"""
from __future__ import print_function
import ida_kernwin
from ida_kernwin import Choose
@@ -1,5 +1,12 @@
"""
summary: choose multi
summary: A widget showing data in a tabular fashion, providing multiple selection
description:
Similar to @{choose}, but with multiple selection
keywords: chooser, actions
see_also: choose, chooser_with_folders
"""
from __future__ import print_function
@@ -1,3 +1,22 @@
"""
summary: A widget that can show tabular data either as a simple table,
or with a tree-like structure.
description:
By adding the necessary bits to a ida_kernwin.Choose subclass,
IDA can show the otherwise tabular data, in a tree-like fashion.
The important bits to enable this are:
* ida_dirtree.dirspec_t (and my_dirspec_t)
* ida_kernwin.CH_HAS_DIRTREE
* ida_kernwin.Choose.OnGetDirTree
* ida_kernwin.Choose.OnIndexToInode
keywords: chooser, folders, actions
see_also: choose, choose_multi
"""
import inspect
@@ -1,3 +1,15 @@
"""
summary: An alternative view over the list of functions
description:
Partially re-implements the "Functions" widget present in
IDA, with a custom widget.
keywords: chooser, functions
see_also: choose, choose_multi, chooser_with_folders
"""
from __future__ import print_function
import idautils
import idc
@@ -1,8 +1,19 @@
"""
summary: retrieve the strings that are selected in the "Strings" window.
#
# This example lets the user programmatically retrieve
# the strings currently selected in the "Strings" window
#
description:
In IDA it's possible to write actions that can be applied even to
core (i.e., "standard") widgets. The actions in this example use the
action "context" to know what the current selection is.
This example shows how you can either retrieve string literals data
directly from the chooser (`ida_kernwin.get_chooser_data`), or
by querying the IDB (`ida_bytes.get_strlit_contents`)
keywords: actions
see_also: list_strings
"""
import ida_kernwin
import ida_strlist
@@ -1,10 +1,11 @@
"""
summary: showing, updating & hiding the progress dialog
#
# A simple example showing how to use:
# ida_kernwin.show_wait_box
# ida_kernwin.hide_wait_box
# ida_kernwin.replace_wait_box
#
description:
Using the progress dialog (aka 'wait box') primitives.
keywords: actions
"""
import time
import random
-782
View File
@@ -1,782 +0,0 @@
/*
* This utility is meant to be used to let IDA switch between
* installed versions of Python3.
*
* See the documentation placed in 'opts.epilog', near the bottom
* of this file.
*/
#ifdef __NT__
# include <windows.h>
#endif
//lint -esym(1788, iinc) is referenced only by its constructor or destructor
//lint -e754 local struct member 'pylib_entries_t::path_history' not referenced
#include <pro.h>
#include <err.h>
#include <fpro.h>
#include <prodir.h>
#include <diskio.hpp>
#include <network.hpp>
#define EXIT_CODE_FORCE_PATH_FAILED 110
#define EXIT_CODE_NO_INSTALLS 120
#define EXIT_CODE_APPLY_FAILED 121
#ifdef __LINUX__
# define EXIT_CODE_SPLIT_DEBUG_EXPAND_DT_NEEDED_ROOM_FAILED 140
#endif
#ifdef __GNUC__ // gcc defines those macros, that are in our way
# undef major
# undef minor
#endif
#ifdef __NT__
# define PY_MODULE_EXT ".pyd"
#else
# define PY_MODULE_EXT ".so"
#endif
//-------------------------------------------------------------------------
struct user_args_t
{
qstring force_path;
bool verbose;
bool auto_apply;
bool dry_run;
#ifdef __UNIX__
bool ignore_python_config;
uint32 major_version;
#endif
#ifdef __LINUX__
qstring split_debug_expand_libpython3_dtneeded_room; //lint !e958 padding of 4 bytes needed to align member on a 8 byte boundary
#endif
user_args_t()
: verbose(false),
auto_apply(false),
dry_run(false)
#ifdef __UNIX__
, ignore_python_config(false),
major_version(3)
#endif
{}
};
static user_args_t args;
//-------------------------------------------------------------------------
static int out_ident = 0;
struct out_ident_inc_t
{
out_ident_inc_t() { ++out_ident; }
~out_ident_inc_t() { --out_ident; }
};
//-------------------------------------------------------------------------
AS_PRINTF(1, 0) int vout(const char *format, va_list va)
{
for ( int i = 0; i < out_ident; ++i )
printf(" ");
return vprintf(format, va);
}
//-------------------------------------------------------------------------
AS_PRINTF(1, 2) int out(const char *format, ...)
{
va_list va;
va_start(va, format);
int rc = vout(format, va);
va_end(va);
return rc;
}
//-------------------------------------------------------------------------
AS_PRINTF(1, 2) int out_verb(const char *format, ...)
{
int rc = 0;
if ( args.verbose )
{
out("V: ");
va_list va;
va_start(va, format);
rc = vout(format, va);
va_end(va);
}
return rc;
}
//-------------------------------------------------------------------------
NORETURN AS_PRINTF(2, 3) void error(int exit_code, const char *format, ...)
{
va_list va;
va_start(va, format);
vout(format, va);
va_end(va);
qexit(exit_code);
}
//-------------------------------------------------------------------------
struct pylib_version_t
{
int major;
int minor;
int revision;
qstring modifiers; //lint !e958 padding of 4 bytes needed to align member on a 8 byte boundary
qstring raw;
pylib_version_t(
int _major=0,
int _minor=0,
int _revision=0,
const char *_modifiers=nullptr,
const char *_raw=nullptr)
: major(_major),
minor(_minor),
revision(_revision),
modifiers(_modifiers),
raw(_raw) {}
bool valid() const { return major > 0; }
const char *str(qstring *out) const
{
out->sprnt("%d.%d.%d%s ('%s')",
major, minor, revision,
modifiers.c_str(), raw.c_str());
return out->c_str();
}
DECLARE_COMPARISONS(pylib_version_t)
{
if ( major != r.major )
return major - r.major;
if ( minor != r.minor )
return minor - r.minor;
if ( revision != r.revision )
return revision - r.revision;
// when it comes to modifiers, we'll consider:
// - that a debug version is lesser than a non-debug one
// - that a version with more modifiers is "greater" than
// one with fewer modifiers.
const int has_debug = qstrstr(modifiers.c_str(), "d") != nullptr;
const int r_has_debug = qstrstr(r.modifiers.c_str(), "d") != nullptr;
if ( has_debug != r_has_debug )
return r_has_debug - has_debug;
return modifiers.length() - r.modifiers.length();
}
};
DECLARE_TYPE_AS_MOVABLE(pylib_version_t);
//-------------------------------------------------------------------------
struct pylib_entry_t
{
pylib_version_t version;
#ifdef __MAC__
pylib_version_t compatibility_version; // only for OSX
#endif
#ifdef __NT__
qstring display_name;
#endif
qstrvec_t paths;
bool preferred;
pylib_entry_t(const pylib_version_t &_version)
: version(_version), preferred(false) {}
const char *str(qstring *out) const
{
qstring pbuf, vbuf;
for ( auto const &p : paths )
{
if ( !pbuf.empty() )
pbuf.append(", ", 2);
pbuf.append(p);
}
out->sprnt("Version: %s; paths: %s", version.str(&vbuf), pbuf.c_str());
if ( preferred )
out->append(" (PREFERRED)");
return out->c_str();
}
bool operator ==(const pylib_entry_t &r) const { return paths == r.paths; }
bool operator !=(const pylib_entry_t &r) const { return !(*this == r); }
};
DECLARE_TYPE_AS_MOVABLE(pylib_entry_t);
typedef qvector<pylib_entry_t> pylib_entry_vec_t;
//-------------------------------------------------------------------------
struct pylib_entries_t
{
pylib_entry_vec_t entries;
qstrvec_t path_history;
pylib_entry_t *get_entry_for_version(const pylib_version_t &version)
{
for ( auto &e : entries )
if ( e.version == version )
return &e;
return nullptr;
}
pylib_entry_t &add_entry(const pylib_version_t &version, const qstrvec_t &paths)
{
pylib_entry_t ne(version);
ne.paths = paths;
entries.push_back(ne);
pylib_entry_t *e = &entries.back();
return *e;
}
pylib_entry_t &add_entry(const pylib_version_t &version, const char *path)
{
qstrvec_t paths;
paths.push_back(path);
return add_entry(version, paths);
}
};
#ifdef __UNIX__
static void set_preferred_pylib_version(pylib_entries_t *result);
#endif
//-------------------------------------------------------------------------
struct pyver_tool_t
{
static bool reverse_compare_entries(
const pylib_entry_t &e0,
const pylib_entry_t &e1)
{
if ( e0.preferred )
return true;
if ( e1.preferred )
return false;
int rc = e0.version.compare(e1.version);
if ( rc > 0 )
return true;
return false;
}
bool path_to_pylib_entry(
pylib_entry_t *out,
const char *path,
qstring *errbuf) const
{
return do_path_to_pylib_entry(out, path, errbuf);
}
void find_python_libs(pylib_entries_t *result) const
{
do_find_python_libs(result);
#ifdef __UNIX__
set_preferred_pylib_version(result);
#endif
std::sort(result->entries.begin(), result->entries.end(), reverse_compare_entries);
qstring buf;
if ( args.verbose )
{
out_ident_inc_t iinc;
for ( auto const &e : result->entries )
out_verb("%s\n", e.str(&buf));
}
}
bool apply_version(
const pylib_entry_t &entry,
qstring *errbuf) const
{
return do_pick_sip(entry, errbuf)
&& do_apply_version(entry, errbuf);
}
private:
// These three need to be implemented in different
// ways on __NT__, __LINUX__ and __MAC__
// Look on the filesystem (or the registry on Windows)
// for available Python3 installations.
void do_find_python_libs(pylib_entries_t *out) const;
// Given a path to a .so, .dll or .dylib, try and parse the
// Python3 version and produce an entry with it.
bool do_path_to_pylib_entry(pylib_entry_t *entry, const char *path, qstring *errbuf) const;
// Pick the right sip.so for the selected version, and
// copy it in the PyQt5 directory
bool do_pick_sip(const pylib_entry_t &entry, qstring *errbuf) const;
// Do patch the idapython.[so|dylib] binary (or the
// registry on Windows) so that they refer to the right
// Python3 version.
bool do_apply_version(const pylib_entry_t &entry, qstring *errbuf) const;
};
//-------------------------------------------------------------------------
bool pyver_tool_t::do_pick_sip(
const pylib_entry_t &entry,
qstring *errbuf) const
{
#ifdef __MAC__
if ( entry.version.major == 2 )
return true; // nothing to do for Python2
#endif
const char *src_sip_subdir = entry.version.minor >= 10 ? "python_3.10"
: entry.version.minor >= 9 ? "python_3.9"
: entry.version.minor >= 8 ? "python_3.8"
: "python_3.4";
char src_sip_path[QMAXPATH];
qmakepath(src_sip_path, sizeof(src_sip_path), idadir(""),
"python", "3", "PyQt5", src_sip_subdir, "sip" PY_MODULE_EXT, nullptr);
if ( !qfileexist(src_sip_path) )
{
errbuf->sprnt("File not found: \"%s\"", src_sip_path);
return false;
}
char dst_sip_path[QMAXPATH];
qmakepath(dst_sip_path, sizeof(dst_sip_path), idadir(""),
"python", "3", "PyQt5", "sip" PY_MODULE_EXT, nullptr);
if ( args.dry_run )
{
out("Would copy %s to %s\n", src_sip_path, dst_sip_path);
}
else
{
out_verb("Copying \"%s\" to \"%s\"\n", src_sip_path, dst_sip_path);
const int code = qcopyfile(src_sip_path, dst_sip_path, /*overwrite=*/ true);
if ( code != 0 )
{
errbuf->sprnt("Couldn't copy file \"%s\" to \"%s\": %s",
src_sip_path, dst_sip_path, qstrerror(-1));
return false;
}
}
return true;
}
//-------------------------------------------------------------------------
// Accepts:
// "3.7"
// "3.7.1"
// "3.7m"
// "3.7.1dm"
//lint -esym(528, parse_python_version_str) not referenced
static bool parse_python_version_str(pylib_version_t *out, const char *raw)
{
int major = 0;
int minor = 0;
int revision = 0;
int nchars_read;
const char *p = raw;
if ( qsscanf(raw, "%d.%d%n", &major, &minor, &nchars_read) != 2 )
return false;
p += nchars_read;
if ( p[0] == '.' && qisdigit(p[1]) )
{
if ( qsscanf(p, ".%d%n", &revision, &nchars_read) != 1 )
return false;
p += nchars_read;
}
*out = pylib_version_t(major, minor, revision, p, raw);
return true;
}
#ifdef __UNIX__
//-------------------------------------------------------------------------
static bool extract_version_from_str(
pylib_version_t *out,
const char *p,
const char *stem,
const char *end_delimiter)
{
const size_t stemlen = qstrlen(stem);
if ( !strneq(p, stem, stemlen) )
return false;
p += stemlen;
const char *p2 = qstrstr(p, end_delimiter);
if ( p2 == nullptr )
p2 = tail(p);
size_t nbytes = p2 - p;
char raw[MAXSTR];
if ( nbytes > sizeof(raw) - 1 )
nbytes = sizeof(raw) - 1;
memmove(raw, p, nbytes);
raw[nbytes] = '\0';
parse_python_version_str(out, raw);
return true;
}
//-------------------------------------------------------------------------
static void set_preferred_pylib_version(pylib_entries_t *result)
{
// If python(3)-config exists, use it to determine the preferred version
if ( args.ignore_python_config )
return;
const char *config_util = args.major_version == 3 ? "python3-config" : "python-config";
qstring cmd;
cmd.sprnt("%s --libs", config_util);
qstring error;
qstring verbuf;
FILE *fp = popen(cmd.c_str(), "r");
if ( fp != nullptr )
{
char outbuf[MAXSTR];
/*ssize_t nread =*/ qfread(fp, outbuf, sizeof(outbuf));
int rc = pclose(fp);
if ( rc == 0 )
{
pylib_version_t version;
const char *p = qstrstr(outbuf, "-lpython");
if ( p != nullptr
&& extract_version_from_str(&version, p, "-lpython", " ") )
{
#ifdef __MAC__
// python3-config output on OSX will contain modifiers, i.e. "-lpython3.7m", but when detecting pylibs on OSX
// we extract the python version from the LC_ID_DYLIB load command, which does not contain modifiers.
// it seems safe to ignore the modifiers on OSX, they appear to be only used for compatibility purposes.
version.modifiers.clear();
#endif
out_verb("Preferred version, as reported by \"%s\": %s\n", cmd.c_str(), version.str(&verbuf));
pylib_entry_t *e = result->get_entry_for_version(version);
if ( e != nullptr )
e->preferred = true;
else
error.sprnt("\"%s\" reports preferred "
"version \"%s\", but no corresponding library file "
"was found.\n", cmd.c_str(), version.str(&verbuf));
}
else
{
error.sprnt("Error parsing \"%s\" output", cmd.c_str());
}
}
else
{
error.sprnt("Error calling \"%s\"", cmd.c_str());
}
}
else
{
error.sprnt("\"%s\" is not available", config_util);
}
if ( !error.empty() )
out_verb("%s. Cannot determine preferred version this way.\n",
error.c_str());
}
#endif // __UNIX__
#if defined(__LINUX__) || defined(__MAC__)
# ifdef __LINUX__
# define SOSFX "so"
# else
# define SOSFX "dylib"
# endif
//-------------------------------------------------------------------------
struct file_visitor_t
{
virtual int visit_file(const char *path) = 0;
};
//-------------------------------------------------------------------------
static int visit_files(
file_visitor_t &dv,
const char *dir,
const char *pattern,
int attr = 0)
{
char path[QMAXPATH];
qmakepath(path, sizeof(path), dir, pattern, nullptr);
qffblk64_t fb;
for ( int code = qfindfirst(path, &fb, attr);
code == 0;
code = qfindnext(&fb) )
{
qmakepath(path, sizeof(path), dir, fb.ff_name, nullptr);
int ret = dv.visit_file(path);
if ( ret != 0 )
return ret;
}
return 0;
}
//-------------------------------------------------------------------------
//lint -esym(528, for_all_plugin_files) not referenced
static int for_all_plugin_files(file_visitor_t &dv, qstring *errbuf)
{
char path[QMAXPATH];
char found = 0;
for ( int is_ea64 = 0; is_ea64 < 2; ++is_ea64 )
{
// Only patch for actually available IDAs (e.g. don't fail in std edition)
qmakepath(path, sizeof(path), idadir(""), is_ea64 ? "ida64" : "ida", nullptr);
if ( qfileexist(path) )
{
found++;
char fname[QMAXPATH];
qsnprintf(fname, sizeof(fname), "idapython%d%s.%s", args.major_version, is_ea64 ? "_64" : "", SOSFX);
qmakepath(path, sizeof(path), idadir(""), "plugins", fname, nullptr);
int ret = dv.visit_file(path);
if ( ret != 0 )
return ret;
qmakepath(path, sizeof(path), idadir(""), "python", args.major_version == 3 ? "3" : "2", is_ea64 ? "ida_64" : "ida_32", nullptr);
ret = visit_files(dv, path, "_ida_*.so");
if ( ret != 0 )
return ret;
}
}
if ( !found )
{
errbuf->sprnt("Nothing to patch in %s", idadir(""));
return -1;
}
return 0;
}
#endif
#ifdef __LINUX__
# include "idapyswitch_linux.cpp"
#else
# ifdef __NT__
# include "idapyswitch_win.cpp"
# else
# include "idapyswitch_mac.cpp"
# endif
#endif
//-------------------------------------------------------------------------
static void set_verbose(const char *, void *) { args.verbose = true; }
static void set_auto_apply(const char *, void *) { args.auto_apply = true; }
static void set_dry_run(const char *, void *) { args.dry_run = true; }
static void set_force_path(const char *arg, void *) { args.force_path = arg; }
#ifdef __UNIX__
static void set_ignore_python_config(const char *, void *) { args.ignore_python_config = true; }
#endif
#ifdef __LINUX__
static void set_split_debug_expand_libpython3_dtneeded_room(const char *arg, void *)
{
args.split_debug_expand_libpython3_dtneeded_room = arg;
}
#endif
#ifdef __MAC__
static void set_use_python2(const char *, void *) { args.major_version = 2; }
#endif
//-------------------------------------------------------------------------
static const cliopt_t _opts[] =
{
{ 'v', "verbose", "Verbose mode", set_verbose, 0 },
{ 'a', "auto-apply", "Run non-interactively; automatically apply the preferred version (if found)", set_auto_apply, 0 },
{ 'r', "dry-run", "Only report what would happen; don't do it", set_dry_run, 0 },
{ 's', "force-path",
#ifdef __LINUX__
"Have IDAPython use the specified \"/path/to/libpython[...]so\" shared object",
#else
# ifdef __NT__
"Have IDAPython use the specified \"\\path\\to\\python3.dll\" DLL",
# else
"Have IDAPython use the specified \"/path/to/libpython[...]dylib\" dylib",
# endif
#endif
set_force_path,
1
},
#ifdef __UNIX__
{ 'k', "ignore-python-config", "Do not use python-config to find out the preferred version number", set_ignore_python_config, 0 },
#endif
#ifdef __LINUX__
{ 'x', "split-debug-and-expand-libpython3-dtneeded-room", "Expand the DT_NEEDED room to N bytes, using the local `patchelf` (needed at build-time only)", set_split_debug_expand_libpython3_dtneeded_room, 1 },
#endif
#ifdef __MAC__
{ 't', "use-python2", "Search for alternate Python2 installations, and patch the Python2 version of idapython", set_use_python2, 0 },
#endif
};
//-------------------------------------------------------------------------
static const char usage_epilog[] =
"Switch between available installations of Python3\n"
"\n"
"Because Python3 does not systematically install a single,\n"
"always-available \"python3.dll\", \"libpython3.so\" or \"python3.dylib\",\n"
"but rather allows for multiple versions of Python3 to be\n"
"installed in parallel on a given system, many tools\n"
"provide a way to switch between those versions.\n"
"\n"
"IDA is no exception, and this tool is one such Python3 'switcher'.\n"
"\n"
"It can be run in 3 ways:\n"
"\n"
" 1) The default, interactive way\n"
" -------------------------------\n"
" > $ idapyswitch\n"
" will look on the filesystem for available Python3 installations,\n"
" present the user with a list of found versions (sorted according\n"
" to preferability), and let the user pick which one IDA should use.\n"
"\n"
" 2) The 'automatic' way\n"
" ----------------------\n"
" > $ idapyswitch --auto-apply\n"
" will look on the filesystem for available Python3 installations,\n"
" and automatically pick the one it deemed the most preferable.\n"
"\n"
" 3) The 'manual' way\n"
" -------------------\n"
#ifdef __NT__
" > $ idapyswitch --force-path C:\\Python37\\python3.dll\n"
#else
# ifdef __LINUX__
" > $ idapyswitch --force-path /path/to/libpython3.7dm.so.1.2\n"
# else
" > $ idapyswitch --force-path /path/to/Python.framework/Versions/3.7/Python\n"
# endif
#endif
" will pick the path that the user provided.\n"
"\n"
"Once a version is picked, this tool will do the following:\n"
"\n"
#ifdef __NT__
" * place the path to the directory containing python3.dll\n"
" into the registry. IDA will pick it up at launch-time,\n"
" and add it to the list of paths that have to be looked\n"
" into by the DLL loader.\n"
#else
# ifdef __LINUX__
" * patch 'idapython.so' and 'idapython64.so' so that they\n"
" have a DT_NEEDED corresponding to the DT_SONAME of the\n"
" library that was selected.\n"
# else
" * patch 'idapython.dylib' and 'idapython64.dylib' so that\n"
" they refer to the right Python3 dylib.\n"
# endif
#endif
;
//-------------------------------------------------------------------------
int main(int argc, const char **argv)
{
cliopts_t opts(out);
opts.epilog = usage_epilog;
opts.add(_opts, qnumber(_opts));
opts.apply(argc, argv);
qstring errbuf;
#ifdef __LINUX__
if ( !args.split_debug_expand_libpython3_dtneeded_room.empty() )
{
if ( !split_debug_expand_libpython3_dtneeded_room(
args.split_debug_expand_libpython3_dtneeded_room.c_str(),
&errbuf) )
{
error(EXIT_CODE_SPLIT_DEBUG_EXPAND_DT_NEEDED_ROOM_FAILED,
"Cannot split debug/expand DT_NEEDED room: %s\n",
errbuf.c_str());
}
return 0;
}
#endif
pyver_tool_t tool;
if ( !args.force_path.empty() )
{
const char *path = args.force_path.c_str();
pylib_version_t dummy_version;
pylib_entry_t entry(dummy_version);
if ( !tool.path_to_pylib_entry(&entry, path, &errbuf) )
{
error(EXIT_CODE_FORCE_PATH_FAILED,
"Cannot determine python library version for \"%s\": %s\n",
path, errbuf.c_str());
}
if ( !tool.apply_version(entry, &errbuf) )
{
qstring buf;
error(EXIT_CODE_FORCE_PATH_FAILED,
"Applying \"%s\" (extracted from path \"%s\") failed: %s\n",
entry.str(&buf), path, errbuf.c_str());
}
}
else
{
pylib_entries_t entries;
tool.find_python_libs(&entries);
const size_t nentries = entries.entries.size();
if ( nentries > 0 )
{
qstring buf;
const pylib_entry_t *preferred = nullptr;
if ( args.auto_apply )
{
preferred = &entries.entries[0];
}
else
{
out("The following Python installations were found:\n");
for ( size_t i = 0; i < nentries; ++i )
{
out_ident_inc_t iinc;
const pylib_entry_t &e = entries.entries[i];
out("#%" FMT_Z ": %s (%s)\n",
i,
e.version.str(&buf),
!e.paths.empty() ? e.paths[0].c_str() : "<unavailable path>");
}
size_t picked = size_t(-1);
while ( picked >= nentries )
{
out("Please pick a number between 0 and %" FMT_Z " (default: 0)\n", nentries-1);
char numbuf[MAXSTR];
qfgets(numbuf, sizeof(numbuf), stdin);
qstring qnumbuf(numbuf);
qnumbuf.rtrim('\n');
if ( qnumbuf.empty() )
picked = 0;
else
picked = qatoll(qnumbuf.c_str());
}
preferred = &entries.entries[picked];
}
out("Applying version %s\n", preferred->version.str(&buf));
if ( !tool.apply_version(*preferred, &errbuf) )
{
error(EXIT_CODE_APPLY_FAILED,
"Apply failed: %s\n",
errbuf.c_str());
}
}
else
{
error(EXIT_CODE_NO_INSTALLS,
"No Python installations were found\n");
}
}
return 0;
}
-477
View File
@@ -1,477 +0,0 @@
//-------------------------------------------------------------------------
static bool extract_version_from_libpython_filename(
pylib_version_t *out,
const char *p)
{
return extract_version_from_str(out, p, "libpython", ".so");
}
//-------------------------------------------------------------------------
void pyver_tool_t::do_find_python_libs(pylib_entries_t *result) const
{
//
// Find all libpython3*so* present on disk
//
static const char lib_pattern[] = "libpython3*.so*";
static const char *dirs[] =
{
"/usr/lib/x86_64-linux-gnu", // Debian/Ubuntu
"/usr/lib64", // RedHat - FHS
#ifdef _DEBUG
"/opt/test-python-libs"
#endif
};
qstring verbuf;
for ( size_t i = 0; i < qnumber(dirs); ++i )
{
const char *d = dirs[i];
out_verb("Searching for \"%s\" in \"%s\"\n", lib_pattern, d);
{
out_ident_inc_t iinc;
char path[QMAXPATH];
qmakepath(path, sizeof(path), d, lib_pattern, nullptr);
qffblk64_t fb;
for ( int code = qfindfirst(path, &fb, 0);
code == 0;
code = qfindnext(&fb) )
{
pylib_version_t version;
if ( extract_version_from_libpython_filename(&version, fb.ff_name) )
{
qmakepath(path, sizeof(path), d, fb.ff_name, nullptr);
out_verb("Found: \"%s\" (version: %s)\n", path, version.str(&verbuf));
result->add_entry(version, path);
}
}
}
}
}
//-------------------------------------------------------------------------
bool pyver_tool_t::do_path_to_pylib_entry(
pylib_entry_t *entry,
const char *path,
qstring *errbuf) const
{
const char *fname = qbasename(path);
const bool ok = fname != nullptr && qfileexist(path);
if ( ok )
{
extract_version_from_libpython_filename(&entry->version, fname);
entry->paths.push_back(path);
}
else
{
errbuf->sprnt("Couldn't parse file name \"%s\"", fname);
}
return ok;
}
#include "../../ldr/elf/reader.cpp"
//-------------------------------------------------------------------------
static bool read_ident_and_header_and_get_dyninfo(
dynamic_info_t *out_dyninfo,
reader_t::dyninfo_tags_t *out_dyninfo_tags,
reader_t &reader,
qstring *errbuf)
{
if ( !reader.read_ident() )
{
*errbuf = "Couldn't parse ELF file ident";
return false;
}
if ( !reader.read_header() )
{
*errbuf = "Couldn't parse ELF file header";
return false;
}
dynamic_linking_tables_t dlt;
if ( reader.read_section_headers()
&& reader.sections.has_valid_dynamic_linking_tables_info() )
{
dlt = reader.sections.get_dynamic_linking_tables_info();
}
else if ( reader.read_program_headers()
&& reader.pheaders.has_valid_dynamic_linking_tables_info() )
{
dlt = reader.pheaders.get_dynamic_linking_tables_info();
}
return dlt.is_valid()
&& reader.read_dynamic_info_tags(out_dyninfo_tags, dlt)
&& reader.parse_dynamic_info(out_dyninfo, *out_dyninfo_tags);
}
//-------------------------------------------------------------------------
static bool find_libpython_dt_needed_info(
char out_dt_needed_buf[MAXSTR],
qoff64_t *out_dt_needed_off,
const reader_t::dyninfo_tags_t &dyninfo_tags,
reader_t &reader,
const char *path,
qstring *errbuf)
{
static const char needed_stem[] = "libpython";
for ( const auto &dyn : dyninfo_tags )
{
if ( dyn.d_tag == DT_NEEDED )
{
const qoff64_t off = reader.dyn_strtab.offset + dyn.d_un;
input_status_t save_excursion(reader);
if ( save_excursion.seek(off) == -1 )
{
errbuf->sprnt("Couldn't seek to offset %" FMT_64 "u in \"%s\"", off, path);
return false;
}
out_dt_needed_buf[0] = '\0';
qlread(reader.get_linput(), out_dt_needed_buf, MAXSTR);
out_verb("DT_NEEDED at offset %" FMT_64 "u is: \"%s\"\n", off, out_dt_needed_buf);
if ( strneq(out_dt_needed_buf, needed_stem, sizeof(needed_stem)-1) )
{
*out_dt_needed_off = off;
return true;
}
}
}
errbuf->sprnt("No DT_NEEDED starting with \"%s\" found", needed_stem);
return false;
}
//-------------------------------------------------------------------------
static bool patch_dt_needed(
const char *path,
const qstring &_replacement,
qstring *errbuf)
{
out_verb("Setting relevant DT_NEEDED of \"%s\" to \"%s\"\n", path, _replacement.c_str());
out_ident_inc_t iinc;
linput_t *linput = open_linput(path, /*remote=*/ false);
if ( linput == nullptr )
{
errbuf->sprnt("File not found: %s", path);
return false;
}
linput_janitor_t lj(linput);
reader_t reader(linput);
dynamic_info_t dyninfo;
reader_t::dyninfo_tags_t dyninfo_tags;
if ( !read_ident_and_header_and_get_dyninfo(
&dyninfo,
&dyninfo_tags,
reader,
errbuf) )
{
return false;
}
char dt_needed[MAXSTR];
qoff64_t dt_needed_off;
if ( !find_libpython_dt_needed_info(
dt_needed,
&dt_needed_off,
dyninfo_tags,
reader,
path,
errbuf) )
{
return false;
}
out_verb("Found DT_NEEDED; currently: \"%s\"\n", dt_needed);
// count the maximum number of bytes we can store in there
size_t room = 0;
{
input_status_t save_excursion(reader);
if ( save_excursion.seek(dt_needed_off) == -1 )
{
errbuf->sprnt("Couldn't seek to offset %" FMT_64 "u in \"%s\"", dt_needed_off, path);
return false;
}
// find the end of the current DT_NEEDED
uint8 byte;
const int64 filesz = qlsize(reader.get_linput());
while ( qltell(reader.get_linput()) < filesz && reader.read_byte(&byte) == 0 )
if ( byte == 0 )
break;
// then find the beginning of the next string, or the end of file
while ( qltell(reader.get_linput()) < filesz && reader.read_byte(&byte) == 0 )
if ( byte != 0 )
break;
room = qltell(reader.get_linput()) - dt_needed_off - 1;
}
bytevec_t replacement;
replacement.append(_replacement.c_str(), _replacement.length() + 1);
size_t nbytes = replacement.size();
out_verb("We have room for %" FMT_Z " bytes, and need to write %" FMT_Z "\n",
room, nbytes);
// and patch
if ( room >= nbytes )
{
if ( room > nbytes )
{
out_verb("Expanding replacement with %" FMT_Z " '\\0' bytes, to "
"override possible previous soname that could derail "
"later computation of available room.\n", room - nbytes);
replacement.resize(room, 0);
nbytes = replacement.size();
}
FILE *fp = openM(path);
if ( fp != nullptr )
{
file_janitor_t fpj(fp);
if ( qfseek(fp, dt_needed_off, SEEK_SET) == 0 )
{
if ( !args.dry_run )
{
// we want to write the zero as well!
if ( qfwrite(fp, replacement.begin(), nbytes) == nbytes )
{
out_verb("File \"%s\" successfully patched\n", path);
}
else
{
errbuf->sprnt("Couldn't write %" FMT_Z " bytes to \"%s\"", nbytes, path);
return false;
}
}
else
{
out("Would write %" FMT_Z " bytes (\"%s\") to file\n",
nbytes, replacement.begin());
}
}
else
{
errbuf->sprnt("Cannot seek to position %" FMT_64 "u in \"%s\"", dt_needed_off, path);
return false;
}
}
else
{
errbuf->sprnt("Couldn't open \"%s\" for writing", path);
return false;
}
}
else
{
errbuf->sprnt("Replacement \"%s\" has a length of %" FMT_Z
" bytes, but there is only room for %" FMT_Z ""
" bytes in the file. Cannot proceed.\n",
replacement.begin(), nbytes, room);
return false;
}
return true;
}
//-------------------------------------------------------------------------
bool pyver_tool_t::do_apply_version(
const pylib_entry_t &entry,
qstring *errbuf) const
{
qstring soname;
for ( const auto &path : entry.paths )
{
out_verb("Trying to find out DT_SONAME from file \"%s\"\n", path.c_str());
linput_t *linput = open_linput(path.c_str(), /*remote=*/ false);
linput_janitor_t lj(linput);
reader_t reader(linput);
dynamic_info_t dyninfo;
reader_t::dyninfo_tags_t dyninfo_tags;
qstring nonfatal_errbuf;
if ( read_ident_and_header_and_get_dyninfo(
&dyninfo,
&dyninfo_tags,
reader,
&nonfatal_errbuf) )
{
for ( const auto &dyn : dyninfo_tags )
{
if ( dyn.d_tag == DT_SONAME )
{
soname = dyninfo.d_un_str(reader, dyn.d_tag, dyn.d_un);
out_verb("Found DT_SONAME: \"%s\"\n", soname.c_str());
break;
}
}
}
else
{
out_verb("%s: %s", path.c_str(), nonfatal_errbuf.c_str());
}
if ( !soname.empty() )
break;
}
if ( soname.empty() )
{
*errbuf = "No SONAME found";
return false;
}
// Now, do patch
struct ida_local patcher_t : public file_visitor_t
{
const qstring &lsoname;
qstring *lerrbuf;
patcher_t(const qstring &_soname, qstring *_errbuf)
: lsoname(_soname), lerrbuf(_errbuf) {}
virtual int visit_file(const char *path) override
{
return patch_dt_needed(path, lsoname, lerrbuf) ? 0 : -1;
}
};
patcher_t patcher(soname, errbuf);
return for_all_plugin_files(patcher, patcher.lerrbuf) == 0;
}
//-------------------------------------------------------------------------
static int run_command(const char *_cmd, qstring *errbuf)
{
qstring cmd(_cmd);
if ( args.dry_run )
cmd.insert("echo ");
int rc = -1;
out_verb("Running: \"%s\"\n", cmd.c_str());
FILE *fp = popen(cmd.c_str(), "r");
if ( fp != nullptr )
{
char outbuf[MAXSTR];
/*ssize_t nread =*/ qfread(fp, outbuf, sizeof(outbuf));
rc = pclose(fp);
if ( rc != 0 )
errbuf->sprnt("Error calling \"%s\"; output is: %s", cmd.c_str(), outbuf);
}
else
{
errbuf->sprnt("Command \"%s\" couldn't be run", cmd.c_str());
}
return rc;
}
//-------------------------------------------------------------------------
#define SLOT_SIZE 64
static bool split_debug_expand_libpython3_dtneeded_room(
const char *path,
qstring *errbuf)
{
qstring cmdline;
char dt_needed[MAXSTR];
{
qstring debug_path(path);
debug_path.append(".debug");
char path_dir[QMAXPATH];
if ( !qdirname(path_dir, sizeof(path_dir), path) )
{
errbuf->sprnt("Cannot obtain directory name for path \"%s\"", path);
return false;
}
{
char cwd[QMAXPATH];
qgetcwd(cwd, sizeof(cwd));
if ( qchdir(path_dir) == 0 )
{
out_verb("Changed directory to: \"%s\"\n", path_dir);
}
else
{
errbuf->sprnt("Cannot chdir to \"%s\"", path_dir);
return false;
}
cmdline.sprnt("objcopy --only-keep-debug %s %s", qbasename(path), qbasename(debug_path.c_str()));
if ( run_command(cmdline.c_str(), errbuf) != 0 )
return false;
cmdline.sprnt("strip -s -x %s", qbasename(path));
if ( run_command(cmdline.c_str(), errbuf) != 0 )
return false;
cmdline.sprnt("objcopy --add-gnu-debuglink=%s %s", qbasename(debug_path.c_str()), qbasename(path));
if ( run_command(cmdline.c_str(), errbuf) != 0 )
return false;
if ( qchdir(cwd) == 0 )
{
out_verb("Back to directory: \"%s\"\n", cwd);
}
else
{
errbuf->sprnt("Cannot chdir back to \"%s\"", cwd);
return -1;
}
}
linput_t *linput = open_linput(path, /*remote=*/ false);
if ( linput == nullptr )
{
errbuf->sprnt("File not found: %s", path);
return false;
}
linput_janitor_t lj(linput);
reader_t reader(linput);
dynamic_info_t dyninfo;
reader_t::dyninfo_tags_t dyninfo_tags;
if ( !read_ident_and_header_and_get_dyninfo(
&dyninfo,
&dyninfo_tags,
reader,
errbuf) )
{
return false;
}
qoff64_t dt_needed_off;
if ( !find_libpython_dt_needed_info(
dt_needed,
&dt_needed_off,
dyninfo_tags,
reader,
path,
errbuf) )
{
return false;
}
}
qstring replacement(dt_needed);
replacement.resize(SLOT_SIZE, '_');
// call patchelf to replace the DT_NEEDED with a padded one
out_verb("Found DT_NEEDED: \"%s\"; replacing with \"%s\"\n",
dt_needed, replacement.c_str());
cmdline.sprnt("patchelf --replace-needed %s %s %s",
dt_needed,
replacement.c_str(),
path);
if ( run_command(cmdline.c_str(), errbuf) == 0 )
{
out_verb("\"%s\" command successful. Restoring the "
"original DT_NEEDED of \"%s\"\n",
cmdline.c_str(), dt_needed);
if ( !patch_dt_needed(path, dt_needed, errbuf) )
return false;
}
else
{
return false;
}
return true;
}
-522
View File
@@ -1,522 +0,0 @@
#include <sys/mman.h>
#define BUILD_IDAPYSWITCH
#include "../../ldr/ar/ar.hpp"
#include "../../ldr/ar/aixar.hpp"
#include "../../ldr/ar/arcmn.cpp" // for is_ar_file
#include "../../ldr/mach-o/common.cpp"
//-------------------------------------------------------------------------
static void get_python_version(pylib_version_t *out, uint32 mask)
{
out->revision = uint8(mask);
out->minor = uint8(mask >> 8);
out->major = uint8(mask >> 16);
}
//-------------------------------------------------------------------------
static uint32 get_python_version_mask(const pylib_version_t &version)
{
return version.revision
| version.minor << 8
| version.major << 16;
}
//-------------------------------------------------------------------------
static bool get_pylib_entry_for_macho(
pylib_entry_t *entry,
const char *path,
qstring *errbuf)
{
linput_t *li = open_linput(path, false);
if ( li == nullptr )
{
errbuf->sprnt("Failed to open file: %s", winerr(errno));
return false;
}
linput_janitor_t lij(li);
macho_file_t mfile(li);
if ( !mfile.parse_header() )
{
errbuf->sprnt("Failed to parse Mach-O header");
return false;
}
size_t n = 0;
size_t nfat = mfile.get_fat_subfiles();
if ( nfat == 0 )
{
if ( mfile.get_subfile_type(0) != macho_file_t::SUBFILE_MACH_64 )
{
errbuf->sprnt("File is not 64-bit Mach-O");
return false;
}
}
else
{
bool found_x64 = false;
for ( size_t i = 0; i < nfat; i++ )
{
if ( mfile.get_subfile_type(i) == macho_file_t::SUBFILE_MACH_64 )
{
found_x64 = true;
n = i;
break;
}
}
if ( !found_x64 )
{
errbuf->sprnt("No 64-bit arch found in FAT header");
return false;
}
}
if ( !mfile.set_subfile(n) )
{
errbuf->sprnt("Failed to parse load commands");
return false;
}
struct ida_local lcid_finder_t : public macho_lc_visitor_t
{
pylib_entry_t *entry;
lcid_finder_t(pylib_entry_t *_entry) : entry(_entry) {}
virtual int visit_dylib(
const struct dylib_command *dl,
const char *begin,
const char *end) override
{
if ( dl->cmd == LC_ID_DYLIB )
{
get_python_version(&entry->version, dl->dylib.current_version);
get_python_version(&entry->compatibility_version, dl->dylib.compatibility_version);
return 1;
}
return 0;
}
};
lcid_finder_t finder(entry);
if ( !mfile.visit_load_commands(finder) )
{
errbuf->sprnt("failed to determine libpython version: LC_ID_DYLIB not found");
return false;
}
if ( entry->version.major != args.major_version )
{
qstring verbuf;
errbuf->sprnt("unsupported python version %s", entry->version.str(&verbuf));
return false;
}
entry->paths.push_back(path);
return true;
}
//-------------------------------------------------------------------------
static int extract_pylib_bin(pylib_entries_t *result, const char *version_dir)
{
struct ida_local pylib_finder_t : public file_visitor_t
{
pylib_entries_t *result;
pylib_finder_t(pylib_entries_t *_result) : result(_result) {}
virtual int visit_file(const char *_binpath) override
{
// macOS is absurdly dependent on symlinks. remove them to limit noise.
char buf[PATH_MAX];
const char *binpath = realpath(_binpath, buf);
if ( binpath == nullptr )
{
out_verb("Skipping %s: realpath() failed: %s\n", _binpath, winerr(errno));
return 0;
}
if ( result->path_history.find(binpath) != result->path_history.end() )
{
out_verb("Skipping %s: duplicate of %s\n", _binpath, binpath);
return 0;
}
result->path_history.push_back(binpath);
qstring errbuf;
pylib_version_t dummy;
pylib_entry_t entry(dummy);
if ( !get_pylib_entry_for_macho(&entry, binpath, &errbuf) )
{
out_verb("Skipping %s: %s\n", binpath, errbuf.c_str());
return 0;
}
qstring verbuf;
out_verb("Found: \"%s\" (version: %s)\n", binpath, entry.version.str(&verbuf));
result->entries.add_unique(entry);
return 1;
}
};
// the name of the Framework binary can vary. just be safe and examine all files.
pylib_finder_t f(result);
return visit_files(f, version_dir, "*");
}
//-------------------------------------------------------------------------
static void extract_pylib_versions(pylib_entries_t *result, const char *framework)
{
struct ida_local version_visitor_t : public file_visitor_t
{
pylib_entries_t *result;
version_visitor_t(pylib_entries_t *_result) : result(_result) {}
virtual int visit_file(const char *version_dir) override
{
extract_pylib_bin(result, version_dir);
return 0;
}
};
// examine all Python versions in the Framework
version_visitor_t v(result);
char versions[QMAXPATH];
qmakepath(versions, sizeof(versions), framework, "Versions", nullptr);
visit_files(v, versions, "*", FA_DIREC);
}
//-------------------------------------------------------------------------
void pyver_tool_t::do_find_python_libs(pylib_entries_t *result) const
{
// find all instances of Python.framework on the system
static const char *system_fwks[] =
{
"/Library/Frameworks",
"/System/Library/Frameworks",
"/Library/Developer/CommandLineTools/Library/Frameworks",
"/Applications/Xcode.app/Contents/Developer/Library/Frameworks",
"/opt/local/Library/Frameworks",
};
qstrvec_t framework_dirs;
for ( size_t i = 0; i < qnumber(system_fwks); i++ )
framework_dirs.push_back(system_fwks[i]);
struct ida_local homebrew_handler_t : public file_visitor_t
{
qstrvec_t *framework_dirs;
homebrew_handler_t(qstrvec_t *_framework_dirs) : framework_dirs(_framework_dirs) {}
virtual int visit_file(const char *path) override
{
framework_dirs->push_back(qstring(path) + "/Frameworks");
return 0;
}
};
// homebrew keeps python installations in /usr/local/opt/python@X.X/Frameworks
homebrew_handler_t hh(&framework_dirs);
visit_files(hh, "/usr/local/opt", "python*", FA_DIREC);
struct ida_local python_framework_finder_t : public file_visitor_t
{
pylib_entries_t *result;
python_framework_finder_t(pylib_entries_t *_result) : result(_result) {}
virtual int visit_file(const char *framework) override
{
extract_pylib_versions(result, framework);
return 0;
}
};
// check for a PythonX.framework in each framework dir
python_framework_finder_t pff(result);
for ( size_t i = 0, n = framework_dirs.size(); i < n; i++ )
visit_files(pff, framework_dirs[i].c_str(), "Python*.framework", FA_DIREC);
}
//-------------------------------------------------------------------------
bool pyver_tool_t::do_path_to_pylib_entry(
pylib_entry_t *entry,
const char *path,
qstring *errbuf) const
{
return get_pylib_entry_for_macho(entry, path, errbuf);
}
//-------------------------------------------------------------------------
// misc information required to patch the load command for libpython
struct python_lc_info_t
{
uint32 off; // offset of libpython load command
uint32 size; // size of libpython load command
qstring path; // libpython path
pylib_version_t version; // libpython version
bytevec_t header; // header data: mach_header + all load commands
uint32 headerpadsz; // size of header, including all padded bytes
python_lc_info_t(void) : off(0), size(0), headerpadsz(UINT_MAX) {}
};
//-------------------------------------------------------------------------
static bool get_python_lc_info(python_lc_info_t *plc, const char *path, qstring *errbuf)
{
linput_t *li = open_linput(path, false);
if ( li == nullptr )
{
errbuf->sprnt("Failed to open file: %s", winerr(errno));
return false;
}
linput_janitor_t lij(li);
// here we are assuming the target binary was built by us, which means it is a non-fat,
// 64-bit Mach-O file that links against a PythonX.X framework, and has its header padded
// so that we can patch the load commands without issue.
macho_file_t mfile(li);
if ( !mfile.parse_header() )
{
errbuf->sprnt("Failed to parse Mach-O header");
return false;
}
if ( mfile.get_fat_subfiles() > 0 || mfile.get_subfile_type(0) != macho_file_t::SUBFILE_MACH_64 )
{
errbuf->sprnt("Unexpected filetype (expected 64-bit Mach-O)");
return false;
}
if ( !mfile.set_subfile(0) )
{
errbuf->sprnt("Failed to parse load commands");
return false;
}
const secvec_t &sects = mfile.get_sections();
// find the section with the smallest fileoff. it will tell us the size of the padded header.
for ( size_t i = 0, nsects = sects.size(); i < nsects; i++ )
{
const section_64 &s = sects[i];
if ( s.size != 0
&& (s.flags & S_ZEROFILL) == 0
&& (s.flags & S_THREAD_LOCAL_ZEROFILL) == 0
&& s.offset < plc->headerpadsz )
{
plc->headerpadsz = s.offset;
}
}
if ( plc->headerpadsz == UINT_MAX )
{
errbuf->sprnt("Failed to determine padded size of the Mach-O header");
return false;
}
// extract the libpython load command
struct ida_local python_lc_finder_t : public macho_lc_visitor_t
{
python_lc_info_t *plc;
python_lc_finder_t(python_lc_info_t *_plc) : plc(_plc)
{
plc->off = sizeof(mach_header_64);
}
virtual int visit_any_load_command(
const struct load_command *lc,
const char *begin,
const char *end) override
{
if ( lc->cmd == LC_LOAD_DYLIB )
{
const struct dylib_command *dl = (const struct dylib_command *)begin;
const char *p = begin + dl->dylib.name.offset;
if ( p < end )
{
qstring _path = qstring(p, end-p);
const char *basename = qbasename(_path.c_str());
if ( strneq(basename, "Python", 6) || strneq(basename, "libpython", 9) )
{
plc->path = _path;
plc->size = dl->cmdsize;
get_python_version(&plc->version, dl->dylib.current_version);
return 1;
}
}
}
// not libpython, keep looking
plc->off += lc->cmdsize;
return 0;
}
};
python_lc_finder_t finder(plc);
if ( !mfile.visit_load_commands(finder) )
{
errbuf->sprnt("No libpython dependency found");
return false;
}
mfile.get_mach_header_data(&plc->header);
return true;
}
//-------------------------------------------------------------------------
typedef janitor_t<int> fd_janitor_t;
template <> inline fd_janitor_t::~janitor_t()
{
qclose(resource);
}
//-------------------------------------------------------------------------
static bool do_patch(
void *map,
const pylib_entry_t &entry,
const python_lc_info_t &plc,
qstring *errbuf)
{
const char *path = entry.paths[0].c_str();
size_t path_len = entry.paths[0].length();
// validate the size of the new load command
mach_header_64 *mheader = (mach_header_64 *)map;
uint32 sizeofcmds = mheader->sizeofcmds;
uint32 newcmdsize = align_up(sizeof(dylib_command) + path_len + 1, 8);
int32 diff = newcmdsize - plc.size;
sizeofcmds += diff;
if ( sizeofcmds + sizeof(mach_header_64) > plc.headerpadsz )
{
errbuf->sprnt("Updated load commands do not fit in the Mach-O header");
return false;
}
// patch the mach header
mheader->sizeofcmds = sizeofcmds;
// patch the libpython load command
dylib_command *cmd = (dylib_command *)((uchar *)map + plc.off);
cmd->cmdsize = newcmdsize;
cmd->dylib.current_version = get_python_version_mask(entry.version);
cmd->dylib.compatibility_version = get_python_version_mask(entry.compatibility_version);
// write the new libpython path
uchar *ptr = (uchar *)cmd + sizeof(dylib_command);
memcpy(ptr, path, path_len);
ptr += path_len;
size_t npad = newcmdsize - (sizeof(dylib_command) + path_len);
memset(ptr, 0, npad);
ptr += npad;
// copy the original load commands after libpython
const uchar *org = plc.header.begin() + plc.off + plc.size;
const uchar *end = plc.header.end();
size_t norg = end - org;
memcpy(ptr, org, norg);
ptr += norg;
// if the new sizeofcmds is smaller, fill excess space with 0s
if ( diff < 0 )
memset(ptr, 0, -diff);
return true;
}
//-------------------------------------------------------------------------
static bool patch_python_dylib_cmd(
const char *path,
const pylib_entry_t &entry,
qstring *errbuf)
{
if ( args.dry_run )
{
out("Would patch: %s\n", path);
return true;
}
out_verb("Patching: %s\n", path);
python_lc_info_t plc;
if ( !get_python_lc_info(&plc, path, errbuf) )
return false;
// be careful that we're not patching the wrong build of idapython
if ( entry.version.major != plc.version.major )
{
errbuf->sprnt("idapython binary %s was not built against python %d", path, entry.version.major);
return false;
}
int fd = qopen(path, O_RDWR);
if ( fd < 0 )
{
errbuf->sprnt("Failed to open file: %s", winerr(errno));
return false;
}
fd_janitor_t fdj(fd);
qstatbuf sbuf;
memset(&sbuf, 0, sizeof(sbuf));
if ( qfstat(fd, &sbuf) != 0 )
{
errbuf->sprnt("fstat() failed: %s", winerr(errno));
return false;
}
void *map = mmap(0, sbuf.qst_size, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
if ( map == MAP_FAILED )
{
errbuf->sprnt("mmap() failed: %s", winerr(errno));
return false;
}
bool ok = do_patch(map, entry, plc, errbuf);
munmap(map, sbuf.qst_size);
return ok;
}
//-------------------------------------------------------------------------
bool pyver_tool_t::do_apply_version(
const pylib_entry_t &entry,
qstring *errbuf) const
{
#ifdef __APPLE_SILICON__
// modify the libpython symlink in idabin so that it points to the given libpython path
qstring link_name;
link_name.sprnt("libpython%d.link.dylib", entry.version.major);
char link_path[QMAXPATH];
qmakepath(link_path, sizeof(link_path), idadir(""), link_name.c_str(), nullptr);
if ( qfileexist(link_path) )
{
out_verb("Removing existing \"%s\"\n", link_path);
int rc = qunlink(link_path);
if ( rc != 0 )
{
errbuf->sprnt("Unlinking \"%s\" failed: %s", link_path, winerr(errno));
return false;
}
}
const char *target = entry.paths[0].c_str();
out_verb("Linking \"%s\" -> \"%s\"\n", link_path, target);
int rc = symlink(target, link_path);
if ( rc != 0 )
{
errbuf->sprnt("Linking to \"%s\" failed: %s", target, winerr(errno));
return false;
}
return true;
#else
// patch the libpython load commands in all idapython modules
struct ida_local patcher_t : public file_visitor_t
{
const pylib_entry_t &entry;
qstring *lerrbuf;
patcher_t(const pylib_entry_t &_entry, qstring *_errbuf) : entry(_entry), lerrbuf(_errbuf) {}
virtual int visit_file(const char *path) override
{
return patch_python_dylib_cmd(path, entry, lerrbuf) ? 0 : -1;
}
};
patcher_t patcher(entry, errbuf);
return for_all_plugin_files(patcher, patcher.lerrbuf) == 0;
#endif
}
-672
View File
@@ -1,672 +0,0 @@
#define PYTHON3_DLL "python3.dll"
//-------------------------------------------------------------------------
#define IDA_HKEY HKEY_CURRENT_USER
#define IDA_ADDLIB_SUBKEY L"Software\\Hex-Rays\\IDA"
#define IDA_ADDLIB_VALUE L"Python3TargetDLL"
#define PYTHON_INSTALLS_KEY L"Software\\Python"
#define PYTHON_INSTALL_PATH_SUBKEY L"InstallPath"
#define PYTHON_DISPLAY_NAME_SUBKEY L"DisplayName"
#define PYTHON_SYSVER_SUBKEY L"SysVersion"
#define PYTHON_SYSARCH_SUBKEY L"SysArchitecture"
#define PYTHON_INSTALL_PATH_DEFAULT_VALUE L""
//-------------------------------------------------------------------------
static bool open_ida_addlib_subkey(HKEY *out, REGSAM samDesired)
{
DWORD err = RegOpenKeyExW(IDA_HKEY, IDA_ADDLIB_SUBKEY, 0, samDesired, out);
if ( err == ERROR_SUCCESS )
return true;
if ( samDesired == KEY_READ )
return false;
// opening for write failed; create the subkeys
err = RegCreateKeyExW(IDA_HKEY, IDA_ADDLIB_SUBKEY, 0, NULL, REG_OPTION_NON_VOLATILE, samDesired, NULL, out, NULL);
return err == ERROR_SUCCESS;
}
//-------------------------------------------------------------------------
static bool read_string(
qstring *out,
HKEY key,
const wchar16_t *value,
qstring *errbuf=nullptr)
{
DWORD type;
DWORD size;
if ( RegQueryValueExW(key, value, nullptr, &type, nullptr, &size) == ERROR_SUCCESS && type == REG_SZ )
{
bytevec_t buf;
buf.resize(size);
if ( RegQueryValueExW(key, value, nullptr, &type, buf.begin(), &size) == ERROR_SUCCESS && type == REG_SZ )
return utf16_utf8(out, (wchar16_t *) buf.begin(), buf.size() / sizeof(wchar16_t));
}
if ( errbuf != nullptr )
errbuf->sprnt("Couldn't query value \"%ls\"\n", value);
return false;
}
//-------------------------------------------------------------------------
static bool write_string(
HKEY key,
const wchar16_t *value,
const char *str,
qstring *errbuf=nullptr)
{
qwstring wstr;
bool ok = utf8_utf16(&wstr, str)
&& RegSetValueExW(key, value, 0, REG_SZ,
(LPBYTE) wstr.c_str(),
wstr.length() * sizeof(wstr[0])) == ERROR_SUCCESS;
if ( !ok )
errbuf->sprnt("Couldn't write string data \"%s\" to value \"%ls\"", str, value);
return ok;
}
//-------------------------------------------------------------------------
static bool extract_version_from_path(
pylib_version_t *out,
const char *_path)
{
qstring qpath(_path);
qpath.rtrim('\\');
const char *path = qpath.c_str();
const char *p = qbasename(path);
if ( p == nullptr )
return false;
if ( !strnieq(p, "Python", 6) )
return false;
p += 6;
if ( !qisdigit(p[0]) || !qisdigit(p[1]) )
return false;
out->raw = p;
int major = p[0] - '0';
int minor = p[1] - '0';
int revision = 0;
p += 2;
if ( qisdigit(p[0]) )
{
revision = p[0] - '0';
++p;
}
out->major = major;
out->minor = minor;
out->revision = revision;
out->modifiers = p;
return true;
}
#pragma comment(lib, "version.lib")
//-------------------------------------------------------------------------
DWORD GetFileVersionNumber(const char *filename, DWORD *pdwMSVer, DWORD *pdwLSVer)
{
DWORD dwResult = NOERROR;
unsigned uiSize;
DWORD dwVerInfoSize;
DWORD dwHandle;
PBYTE prgbVersionInfo = NULL;
VS_FIXEDFILEINFO *lpVSFixedFileInfo = NULL;
DWORD dwMSVer = 0xffffffff;
DWORD dwLSVer = 0xffffffff;
qwstring wfilename;
if ( !utf8_utf16(&wfilename, filename) )
{
dwResult = ERROR_INVALID_PARAMETER;
goto Finish;
}
dwVerInfoSize = GetFileVersionInfoSizeW(wfilename.c_str(), &dwHandle);
if ( dwVerInfoSize != 0 )
{
prgbVersionInfo = (PBYTE)qalloc(dwVerInfoSize);
if ( prgbVersionInfo == NULL )
{
dwResult = ERROR_NOT_ENOUGH_MEMORY;
goto Finish;
}
// Read version stamping info
if ( GetFileVersionInfoW(wfilename.c_str(), dwHandle, dwVerInfoSize, prgbVersionInfo) )
{
// get the value for VS_FIXEDFILEINFO
if ( VerQueryValueW(prgbVersionInfo, L"\\", (LPVOID*)&lpVSFixedFileInfo, &uiSize) && (uiSize != 0) )
{
dwMSVer = lpVSFixedFileInfo->dwFileVersionMS;
dwLSVer = lpVSFixedFileInfo->dwFileVersionLS;
}
}
else
{
dwResult = GetLastError();
goto Finish;
}
}
else
{
dwResult = GetLastError();
}
out_verb("%s is version %d.%d.%d.%d\n", filename, HIWORD(dwMSVer), LOWORD(dwMSVer), HIWORD(dwLSVer), LOWORD(dwLSVer));
Finish:
if ( prgbVersionInfo != NULL )
qfree(prgbVersionInfo);
if ( pdwMSVer != NULL )
*pdwMSVer = dwMSVer;
if ( pdwLSVer != NULL )
*pdwLSVer = dwLSVer;
return dwResult;
}
//-------------------------------------------------------------------------
static bool extract_version_from_dll(
pylib_version_t *out,
const char *fname)
{
DWORD dwMSVer, dwLSVer;
DWORD res = GetFileVersionNumber(fname, &dwMSVer, &dwLSVer);
if ( res == NOERROR )
{
out->raw.sprnt("%d.%d.%d.%d", HIWORD(dwMSVer), LOWORD(dwMSVer), HIWORD(dwLSVer), LOWORD(dwLSVer));
// Python DLL versions look like 3.7.4150.1013 -> 3.7.4
out->major = HIWORD(dwMSVer);
out->minor = LOWORD(dwMSVer);
out->revision = HIWORD(dwLSVer) / 1000;
return true;
}
else
{
out_verb("error getting version of \"%s\": %s\n", fname, winerr(res));
return false;
}
}
//-------------------------------------------------------------------------
static bool is_python3Y_dll_file_name(const char *fname)
{
return fname != nullptr
&& strnieq(fname, "python3", 7)
&& qisdigit(fname[7])
&& strieq(get_file_ext(fname), "dll");
}
#include <exehdr.h>
#include "../../ldr/pe/pe.h"
#include "../../ldr/pe/common.cpp"
//-------------------------------------------------------------------------
static bool check_dll_x86_64(const char *path)
{
linput_t *linput = open_linput(path, /*remote=*/ false);
if ( linput == nullptr )
return false;
linput_janitor_t lj(linput);
pe_loader_t pl;
return pl.read_header(linput, /*silent=*/ true)
&& pl.pe.machine == PECPU_AMD64;
}
//-------------------------------------------------------------------------
static bool probe_python_install_dir_from_dll_path(
qstrvec_t *out_paths,
pylib_version_t *out_version,
const char *path,
qstring *errbuf)
{
char dir[QMAXPATH];
if ( !qdirname(dir, sizeof(dir), path) )
{
errbuf->sprnt("Couldn't retrieve directory name from \"%s\"", path);
return false;
}
extract_version_from_path(out_version, dir); // not fatal if this fails
qstring found_python3_dll;
qstring found_python3Y_dll;
static const char dll_pattern[] = "python3*.dll";
char pattern_path[QMAXPATH];
qmakepath(pattern_path, sizeof(pattern_path), dir, dll_pattern, nullptr);
qstring verbuf;
qffblk64_t fb;
for ( int code = qfindfirst(pattern_path, &fb, 0);
code == 0;
code = qfindnext(&fb) )
{
const char *ext = get_file_ext(fb.ff_name);
if ( ext != nullptr && strieq(ext, "dll") )
{
char dll_path[QMAXPATH];
qmakepath(dll_path, sizeof(dll_path), dir, fb.ff_name, nullptr);
pylib_version_t tmp;
if ( extract_version_from_dll(&tmp, path) )
*out_version = tmp;
out_verb("Found: \"%s\" (version: %s)\n", dll_path, out_version->str(&verbuf));
out_paths->push_back(dll_path);
if ( found_python3_dll.empty() && strieq(fb.ff_name, PYTHON3_DLL) )
found_python3_dll = dll_path;
if ( found_python3Y_dll.empty() && is_python3Y_dll_file_name(fb.ff_name) )
found_python3Y_dll = dll_path;
}
}
if ( found_python3_dll.empty() )
{
errbuf->sprnt("No \"" PYTHON3_DLL "\" file found in directory \"%s\"", dir);
return false;
}
if ( found_python3Y_dll.empty() )
{
errbuf->sprnt("No \"python3[0-9].dll\" file found in directory \"%s\"", dir);
return false;
}
if ( !check_dll_x86_64(found_python3_dll.c_str())
|| !check_dll_x86_64(found_python3Y_dll.c_str()) )
{
errbuf->sprnt("DLLs in directory \"%s\" do not have the x86_64 architecture", dir);
return false;
}
return true;
}
static bool has_appx_path(qstrvec_t paths)
{
static qstring appx_path;
if ( appx_path.empty() )
{
HKEY hkey;
bool ok = false;
if ( RegOpenKeyExW(HKEY_LOCAL_MACHINE, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Appx", 0, KEY_READ, &hkey) == ERROR_SUCCESS )
{
ok = read_string(&appx_path, hkey, L"PackageRoot");
RegCloseKey(hkey);
}
if ( !ok )
{
// no Appx support, set to dummy value which doesn't occur in paths
appx_path = "<none>";
}
}
for ( const qstring &path : paths )
if ( path.find(appx_path) != qstring::npos )
return true;
return false;
}
//-------------------------------------------------------------------------
// ignore known bad Pythons:
// 3.8.0 release (https://bugs.python.org/issue37633)
// Anaconda 2019.10, 2020.02 and 2020.11 (https://github.com/ContinuumIO/anaconda-issues/issues/11374)
// AppStore Python on Windows 10 (dll can't be loaded from outside of Appx package)
static bool bad_entry(const pylib_entry_t &e)
{
if ( e.version.major == 3
&& e.version.minor == 8
&& e.version.revision == 0 )
{
out("Ignoring unusable Python 3.8.0 \"%s\"\n", !e.paths.empty() ? e.paths[0].c_str() : "?");
return true;
}
if ( e.display_name == "Anaconda 2019.10"
|| e.display_name == "Anaconda 2020.02"
|| e.display_name == "Anaconda 2020.11" )
{
out("Ignoring unusable %s \"%s\"\n", e.display_name.c_str(), !e.paths.empty() ? e.paths[0].c_str() : "?");
return true;
}
if ( has_appx_path(e.paths) )
{
out("Ignoring unusable AppStore Python \"%s\"\n", !e.paths.empty() ? e.paths[0].c_str() : "?");
return true;
}
return false;
}
//-------------------------------------------------------------------------
static void remove_bad_entries(pylib_entries_t *result)
{
auto p = result->entries.begin();
while ( p != result->entries.end() )
{
if ( bad_entry(*p) )
p = result->entries.erase(p);
else
++p;
}
}
//-------------------------------------------------------------------------
// given a key to a path like HKLM\SOFTWARE\Python\PythonCore,
// - enumerate subkeys and their check InstallPath subkey, using the default value as the directory to the installation
// e.g.
// HKLM\SOFTWARE\Python\PythonCore\3.6\InstallPath -> (Default) = C:\Python36\
// - check for python3.dll and python3Y.dll in it and add them to 'result'
static void enum_python_key(pylib_entries_t *result, const HKEY hkey, qstring *_errbuf, qstring *_verbuf)
{
qstring &errbuf = *_errbuf;
qstring &verbuf = *_verbuf;
int index = 0;
WCHAR subkey[MAXSTR];
qstring displayname;
if ( read_string(&displayname, hkey, PYTHON_DISPLAY_NAME_SUBKEY) )
{
out("Checking installs from \"%s\"\n", displayname.c_str());
}
while ( true )
{
DWORD subkey_sz = qnumber(subkey);
if ( RegEnumKeyExW(hkey, index++, subkey, &subkey_sz,
nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS )
{
// no more installs
break;
}
HKEY ihkey;
if ( RegOpenKeyExW(hkey, subkey, 0, KEY_READ, &ihkey) == ERROR_SUCCESS )
{
out_verb("Opened \"%ls\"\n", subkey);
// opened an install. get its version from SysVersion value
qstring sysver;
pylib_version_t version;
bool ok = read_string(&sysver, ihkey, PYTHON_SYSVER_SUBKEY);
ok = ok && parse_python_version_str(&version, sysver.c_str()) && version.major >= 3;
qstring arch;
if ( read_string(&arch, ihkey, PYTHON_SYSARCH_SUBKEY) && arch != "64bit" )
ok = false;
if ( ok )
{
if ( read_string(&displayname, ihkey, PYTHON_DISPLAY_NAME_SUBKEY) )
{
out("Checking \"%s\" (%s)\n", displayname.c_str(), sysver.c_str());
}
HKEY vhkey;
if ( RegOpenKeyExW(ihkey, PYTHON_INSTALL_PATH_SUBKEY, 0, KEY_READ, &vhkey) == ERROR_SUCCESS )
{
qstring install_path;
if ( read_string(&install_path, vhkey, PYTHON_INSTALL_PATH_DEFAULT_VALUE, &errbuf) )
{
char probe[QMAXPATH];
qmakepath(probe, sizeof(probe), install_path.c_str(), PYTHON3_DLL, nullptr);
pylib_version_t unused;
qstrvec_t paths;
if ( probe_python_install_dir_from_dll_path(
&paths,
&version,
probe,
&errbuf) )
{
out("Found: \"%s\" (version: %s)\n", install_path.c_str(), version.str(&verbuf));
pylib_entry_t &e = result->add_entry(version, paths);
e.display_name = displayname;
}
else
{
out_verb("Ignoring directory \"%s\": %s\n", install_path.c_str(), errbuf.c_str());
}
}
else
{
out_verb("Couldn't query \"%s\"'s value \"%ls\": %s\n",
subkey,
PYTHON_INSTALL_PATH_DEFAULT_VALUE,
errbuf.c_str());
}
RegCloseKey(vhkey);
}
else
{
out("Couldn't open \"%s\"\n", subkey);
}
}
else
{
out_verb("Not a 64-bit Python 3.x or no version info, skipping\n");
}
RegCloseKey(ihkey);
}
}
}
//-------------------------------------------------------------------------
void pyver_tool_t::do_find_python_libs(pylib_entries_t *result) const
{
qstring errbuf;
qstring verbuf;
//
// Enumerate:
// * HKEY_LOCAL_MACHINE\Software\Python\PythonCore\* versions
// * HKEY_CURRENT_USER\Software\Python\PythonCore\* versions
//
static const HKEY top_keys[] =
{
HKEY_LOCAL_MACHINE,
HKEY_CURRENT_USER,
};
static const char * knames[] =
{
"HLKM",
"HKCU",
};
for ( size_t i = 0; i < qnumber(top_keys); ++i )
{
HKEY hkey_python;
out_verb("Searching for subkeys of \"%s\\%ls\"\n", knames[i], PYTHON_INSTALLS_KEY);
if ( RegOpenKeyExW(top_keys[i], PYTHON_INSTALLS_KEY, 0, KEY_READ, &hkey_python) == ERROR_SUCCESS )
{
WCHAR subkey[MAXSTR];
int index = 0;
while ( true )
{
DWORD subkey_sz = qnumber(subkey);
if ( RegEnumKeyExW(hkey_python, index++, subkey, &subkey_sz,
nullptr, nullptr, nullptr, nullptr) != ERROR_SUCCESS )
{
break;
}
HKEY hkey;
if ( RegOpenKeyExW(hkey_python, subkey, 0, KEY_READ, &hkey) == ERROR_SUCCESS )
{
out_verb("Found \"%s\\%ls\\%ls\"\n", knames[i], PYTHON_INSTALLS_KEY, subkey);
enum_python_key(result, hkey, &errbuf, &verbuf);
RegCloseKey(hkey);
}
}
RegCloseKey(hkey_python);
}
}
remove_bad_entries(result);
//
// See if we already have one registered for IDA
//
{
HKEY idahkey;
if ( open_ida_addlib_subkey(&idahkey, KEY_READ) )
{
qstring existing;
if ( read_string(&existing, idahkey, IDA_ADDLIB_VALUE) )
{
out_verb("Previously-used DLL: \"%s\"\n", existing.c_str());
pylib_version_t version;
qstrvec_t paths;
if ( probe_python_install_dir_from_dll_path(
&paths,
&version,
existing.c_str(),
&errbuf) )
{
out("IDA previously used: \"%s\" (guessed version: %s). "
"Making this the preferred version.\n",
existing.c_str(), version.str(&verbuf));
// do we have it in the list?
bool found = false;
for ( pylib_entry_t &e : result->entries )
{
if ( e.paths.has(existing) )
{
found = true;
e.preferred = true;
}
}
if ( !found )
{
// add a new one
pylib_entry_t e(version);
e.paths.swap(paths);
e.preferred = true;
result->entries.push_back(e);
}
}
else
{
out_verb("Ignoring directory \"%s\": %s\n",
existing.c_str(), errbuf.c_str());
}
}
else
{
out("\"%ls\" exists, but no \"%ls\" value found\n",
IDA_ADDLIB_SUBKEY, IDA_ADDLIB_VALUE);
}
RegCloseKey(idahkey);
}
else
{
out("No \"%ls\" key found\n", IDA_ADDLIB_SUBKEY);
}
}
}
//-------------------------------------------------------------------------
bool pyver_tool_t::do_path_to_pylib_entry(
pylib_entry_t *entry,
const char *path,
qstring *errbuf) const
{
return probe_python_install_dir_from_dll_path(&entry->paths, &entry->version, path, errbuf);
}
//-------------------------------------------------------------------------
bool pyver_tool_t::do_apply_version(
const pylib_entry_t &entry,
qstring *errbuf) const
{
HKEY idahkey;
if ( !open_ida_addlib_subkey(&idahkey, KEY_WRITE) )
{
errbuf->sprnt("Couldn't open \"%ls\" key for writing\n", IDA_ADDLIB_SUBKEY);
return false;
}
qstring replacement;
bool ok = false;
for ( const auto &path : entry.paths )
{
const char *candidate = qbasename(path.c_str());
if ( is_python3Y_dll_file_name(candidate) )
{
replacement = candidate;
ok = write_string(idahkey, IDA_ADDLIB_VALUE, path.c_str(), errbuf);
break;
}
}
RegCloseKey(idahkey);
if ( !ok )
{
errbuf->sprnt("Couldn't find a suitable python3Y.dll file");
return false;
}
// Now, let's handle sip.pyd
out_verb("Handling sip" PY_MODULE_EXT "\n");
char path[QMAXPATH];
qmakepath(path, sizeof(path), idadir(""), "python", "3", "PyQt5", "sip" PY_MODULE_EXT, nullptr);
linput_t *linput = open_linput(path, /*remote=*/ false);
if ( linput == nullptr )
{
errbuf->sprnt("File not found: %s", path);
return false;
}
linput_janitor_t lj(linput);
pe_loader_t pl;
if ( !pl.read_header(linput, /*silent=*/ true)
|| pl.process_sections(linput) != 0 )
{
errbuf->sprnt("%s: couldn't read header, or process sections", path);
return false;
}
for ( int ni = 0; ; ++ni )
{
peimpdir_t tmp;
off_t off = pl.pe.impdir.rva + ni*sizeof(peimpdir_t);
out_verb("Reading import table at %u (0x%x)\n", uint32(off), uint32(off));
if ( !pl.vmread(linput, off, &tmp, sizeof(tmp)) )
{
errbuf->sprnt("%s: failed reading import table", path);
}
if ( tmp.dllname == 0 || tmp.looktab == 0 )
break;
char dll[MAXSTR];
bool ok = true;
pl.asciiz(linput, tmp.dllname, dll, sizeof(dll), &ok);
if ( !ok )
break;
out_verb("Import table entry #%d; dll name: \"%s\"\n", ni, dll);
if ( is_python3Y_dll_file_name(dll) )
{
out_verb("Found python3Y.dll: \"%s\" at offset %u (0x%x)\n",
dll, tmp.dllname, tmp.dllname);
FILE *fp = openM(path);
if ( fp != nullptr )
{
file_janitor_t fpj(fp);
if ( qfseek(fp, pl.map_ea(tmp.dllname), SEEK_SET) == 0 )
{
const size_t nbytes = replacement.size(); // we want to write the zero as well!
if ( !args.dry_run )
{
if ( qfwrite(fp, replacement.c_str(), nbytes) == nbytes )
{
out_verb("File \"%s\" successfully patched (with \"%s\")\n", path, replacement.c_str());
}
else
{
errbuf->sprnt("Couldn't write %" FMT_Z " bytes to \"%s\"", nbytes, path);
return false;
}
}
else
{
out("Would write %" FMT_Z " bytes (\"%s\") to file\n",
nbytes, replacement.c_str());
}
}
else
{
errbuf->sprnt("Cannot seek to position %u in \"%s\"", tmp.dllname, path);
return false;
}
}
else
{
errbuf->sprnt("Couldn't open \"%s\" for writing", path);
return false;
}
break;
}
}
return true;
}
+12 -13
View File
@@ -28,20 +28,19 @@ include ../../allmake.mak
all: configs modules pyfiles deployed_modules idapython_modules api_contents pydoc_injections pyqt sip bins examples_index # public_tree test_idc docs
ifeq ($(OUT_OF_TREE_BUILD),)
IDAPYSWITCH:=$(R)idapyswitch$(B)
IDAPYSWITCH_DEP:=$(IDAPYSWITCH)
IDAPYSWITCH_PATH:=$(IDAPYSWITCH)
BINS += $(IDAPYSWITCH)
else
# when out-of-tree (i.e., from github), we only build idapyswitch64,
# and rely on it even for the __EA32__ build
ifdef __EA64__
BINS += $(IDAPYSWITCH)
ifdef __NT__
IDAPYSWITCH_PATH:=$(IDA_INSTALL)/idapyswitch.exe
else
IDAPYSWITCH_64_HACK := 64
IDAPYSWITCH_PATH:=$(IDA_INSTALL)/idapyswitch
endif
endif
IDAPYSWITCH:=$(R)idapyswitch$(IDAPYSWITCH_64_HACK)$(B)
BINS += $(IDAPYSWITCH)
BINS += $(IDAPYSWITCH_DEP)
bins: $(BINS)
#----------------------------------------------------------------------
@@ -116,12 +115,12 @@ else
# build-time, let's use our tool (which will in turn use patchelf)
# to expand the DT_NEEDED 'slot' size.
ifeq ($(PYTHON_VERSION_MAJOR),3)
IDAPYSWITCH_MODULE_DEP := $(IDAPYSWITCH)
IDAPYSWITCH_MODULE_DEP := $(IDAPYSWITCH_DEP)
ifndef __CODE_CHECKER__
# this is for idapython[64].so
POSTACTION=$(Q)$(IDAPYSWITCH) --split-debug-and-expand-libpython3-dtneeded-room $(MODULE)
POSTACTION=$(Q)$(IDAPYSWITCH_PATH) --split-debug-and-expand-libpython3-dtneeded-room $(MODULE)
# and this for _ida_*.so
POSTACTION_IDA_X_SO=$(Q)$(IDAPYSWITCH) --split-debug-and-expand-libpython3-dtneeded-room
POSTACTION_IDA_X_SO=$(Q)$(IDAPYSWITCH_PATH) --split-debug-and-expand-libpython3-dtneeded-room
endif
endif
endif
@@ -964,9 +963,9 @@ $(R)idapyswitch$(B): $(call dumb_target, pro, $(IDAPYSWITCH_OBJS))
ifdef __APPLE_SILICON__
tbd: $(TBD_MODULE_DEP)
# copy the tbd library to idabin, and instruct idapyswitch to create the symlink to libpython
$(TBD_MODULE_DEP): $(TBD_FILE) $(IDAPYSWITCH)
$(TBD_MODULE_DEP): $(TBD_FILE) $(IDAPYSWITCH_DEP)
$(Q)$(CP) $< $@
cd $(R) && $(IDAPYSWITCH) $(TBD_IDAPYSWITCH_ARGS) --force-path $(shell $(PYTHON)-config --prefix)/Python
cd $(R) && $(IDAPYSWITCH_PATH) $(TBD_IDAPYSWITCH_ARGS) --force-path $(shell $(PYTHON)-config --prefix)/Python
else
tbd: ;
endif
Binary file not shown.
+2 -2
View File
@@ -15057,7 +15057,7 @@ class abstract_graph_t(ida_gdl.gdl_graph_t)
| create_tree_layout(self, *args) -> 'bool'
| create_tree_layout(self) -> bool
|
| get_edge(self, *args) -> 'edge_info_t'
| get_edge(self, *args) -> 'edge_info_t *'
| get_edge(self, e) -> edge_info_t
| @param e (C++: edge_t)
|
@@ -16146,7 +16146,7 @@ class mutable_graph_t(abstract_graph_t)
| create_tree_layout(self, *args) -> 'bool'
| create_tree_layout(self) -> bool
|
| get_edge(self, *args) -> 'edge_info_t'
| get_edge(self, *args) -> 'edge_info_t *'
| get_edge(self, e) -> edge_info_t
| @param e (C++: edge_t)
|
+5 -5
View File
@@ -31,9 +31,9 @@
%ignore abstract_graph_t::create_orthogonal_layout;
%ignore abstract_graph_t::clone;
%ignore abstract_graph_t::nrect;
%rename (nrect) my_nrect;
%rename (nrect) novirt_nrect;
%ignore abstract_graph_t::get_edge;
%rename (get_edge) my_get_edge;
%rename (get_edge) novirt_get_edge;
%ignore edge_info_t::add_layout_point;
%ignore edge_infos_wrapper_t::edge_infos_wrapper_t;
@@ -69,11 +69,11 @@ public:
%extend abstract_graph_t {
public:
virtual edge_info_t my_get_edge(edge_t e)
edge_info_t *novirt_get_edge(edge_t e)
{
return *($self->get_edge(e));
return $self->get_edge(e);
}
virtual rect_t my_nrect(int n)
rect_t novirt_nrect(int n)
{
return $self->nrect(n);
}
+4 -4
View File
@@ -398,10 +398,10 @@ SWIG_DECLARE_PY_CLINKED_OBJECT(textctrl_info_t)
%newobject place_t::as_structplace_t;
%newobject place_t::as_simpleline_place_t;
%extend place_t {
static idaplace_t *as_idaplace_t(place_t *p) { return (idaplace_t *) p->clone(); }
static enumplace_t *as_enumplace_t(place_t *p) { return (enumplace_t *) p->clone(); }
static structplace_t *as_structplace_t(place_t *p) { return (structplace_t *) p->clone(); }
static simpleline_place_t *as_simpleline_place_t(place_t *p) { return (simpleline_place_t *) p->clone(); }
static idaplace_t *as_idaplace_t(place_t *p) { return p != nullptr ? (idaplace_t *) p->clone() : nullptr; }
static enumplace_t *as_enumplace_t(place_t *p) { return p != nullptr ? (enumplace_t *) p->clone() : nullptr; }
static structplace_t *as_structplace_t(place_t *p) { return p != nullptr ? (structplace_t *) p->clone() : nullptr; }
static simpleline_place_t *as_simpleline_place_t(place_t *p) { return p != nullptr ? (simpleline_place_t *) p->clone() : nullptr; }
PyObject *py_generate(void *ud, int maxsize)
{
+51 -204
View File
@@ -2,228 +2,75 @@
<head>
<meta charset="UTF-8">
<title>IDAPython examples</title>
<script type="text/javascript">
collapse_normal = "data:image/png;base64,"
+"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgI"
+"fAhkiAAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAE9JREFUSIntz8EJwFAIA9AY"
+"upiTtX8wwcm01/YW+JcWfEeJGIExxv+ZEoqIS8mRTHfP5+yQWpidSq6qAOB1"
+"gMriDumD7l5KjmRutRljfNQNRgQNjM3h6lA=";
collapse_hover = "data:image/png;base64,"
+"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgI"
+"fAhkiAAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAbVJREFUSInVlT1v01AUhp/3"
+"ioUoEiOIAl2QWDpCB4qIbKfZGPgB/AXE1ERiyYSo+BcsLAxVw4avrEgoUkBM"
+"UKEKsUbKxpjB9WGgRWnspLcuS9/x6Ph5fK4/Dlz2KLRxMBg0ms1mx8wi4BbQ"
+"AKbAAbAfx/FhLcFoNLo6m81eAF3g2opWD3TjOP4aLPDer0naA+6fdSPHOZL0"
+"Moqi3TMFWZbdMLMxcCcQ/i+SeicSV9XQ7/edmb2vAwcws1dpmm7Dkgm8988k"
+"va0Dn8uhpI3KCSTtXBAOcK8oiiclQZZld4GN/yBA0tMri0Uz26yo7S7WlgAf"
+"AVtzpc0qwU3p9KNJkqQXIvDe9yXNC9ZKR+ScsxBYYIrSBEVRTBYnyLKsG0Iz"
+"s62F0qQkAD5XXPg6RFCRcemIkiT5ZWbfagJPRdJe5XfgnAt6a1bFzH4AHyoF"
+"w+HwHfDpAvwj59zzKIrypT+7NE2vO+fGwHoNwU4cx29gyc8OoN1uT/M8fyjp"
+"yznAuaTeCXylAKDT6UyAx/xdNr9X9Ur6KOnB/C6Ac6zM4822LSkys9uSGkVR"
+"TJ1z351z+61W62co63LlD5ogjVIofsWl";
expand_normal = "data:image/png;base64,"
+"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgI"
+"fAhkiAAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAKpJREFUSIntk9ERgjAMhr/0"
+"HMANdARGIF3EUdRNXIQrbsAKbsAExBfg0FMsVR686/eUNvkvaZJCJvMtslQQ"
+"QtgCRX9sVbWZi98kFFWYWejtK1DOBbuEBItYPUHUDKqqOo0CkT1wADCzG3AZ"
+"fM65WlXrqTZqBiJyfHO/A0Zf13UADwlWb1HUC8zsPNifWvSsTfkH5XRNvffl"
+"XPz/r2nKT25ERHu7/WUxmcxr7pBbLDAu4/t2";
expand_hover = "data:image/png;base64,"
+"iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAABHNCSVQICAgI"
+"fAhkiAAAAAlwSFlzAAAWJQAAFiUBSVIk8AAAAgxJREFUSInVlbFrFFEQxn/z"
+"2MbjQOQKLUwCYiUiiJgm4c59exErwVLwP7AREc/OrcSQUvAfsEgjiLnydnm5"
+"5iBCKo0gokWQwHEHsbC44m7H5k73cruXjdjkg4XHzDfzzZt9bx6cdkhRYrPZ"
+"LJXL5duq6gMXgRLQBfaALWvtl38S6HQ6ZwaDwSOgAZydQ42BhrV2t7BAu91e"
+"GA6H70Xk+nGFjDFU1cdBELw6VsA5d0FVd4DFgsn/QESe+b6/DmCyCGEYGlV9"
+"m5P8EIjG326GH1V9EUXRGuTswDn3QFXf5BS4ba31x7xbqupyeJ/7/f61zB2o"
+"aiMn6CS4UqlU7s4IOOcuA1f/gwAics87alTV5Qzbeiro+2Q9Go32RSTtWwVW"
+"UqHLM/8gjuMnIrKRtllrC13IOI5DEXmeMv2aaZExRoskK4hkpkVJkhyITBfs"
+"nGuk/PtBEGwCtFqtRc/z7k98qrrCNA5mBIAPRw2q+nKyFpFtYBPA87xLaV9W"
+"rpkWBUHwTVU/zgkqDBF5l3kPRGReVUWx1+v1mpmnIwxDU61W28BqhvuQvyPi"
+"HHAjgzMSkTu+70e5xy+KovPGmB1g6YSVAzy11m5AzrADqNfr3fHFyRxoORgC"
+"DyfJ5woA+L7/YyzSAH7O44pIS0RuWmtfT9mLljZ+2dZExFfVBREpJUnSNcZ8"
+"MsZs1Wq1r0VznS78BtXqtYTFW0oe";
var name_expanded = null;
function is_expanded()
{
return name_expanded != null;
}
function set_expanded(name)
{
name_expanded = name;
}
function init()
{
set_all_images("c_expand_gadget", expand_normal);
}
function image_with_hover(name)
{
set_image(name, name == name_expanded ? collapse_hover : expand_hover);
}
function image_without_hover(name)
{
set_image(name, name == name_expanded ? collapse_normal : expand_normal);
}
function expand_click(name)
{
if ( is_expanded() && name_expanded != name )
{
// something else is expanded - close it
expand_toggle(name_expanded);
}
expand_toggle(name, true);
}
function expand_toggle(name, cursor_is_there=false)
{
set_expanded(is_expanded() ? null : name);
if ( cursor_is_there )
image_with_hover(name);
else
image_without_hover(name);
// actual expansion / collapse
var div = document.getElementById('DIV_' + name);
div.style.display = is_expanded() ? 'initial' : 'none';
// scroll a little for divs at the bottom of the page
if ( is_expanded() )
{
var margin = 100;
var rect = div.getBoundingClientRect();
var delta = rect.top + margin - window.innerHeight;
//console.log('rect.top = ' + rect.top);
//console.log('window.innerHeight = ' + window.innerHeight);
//console.log('delta = ' + delta);
if ( delta > 0 )
window.scrollBy(0, delta);
}
}
function on_see_also(see_also)
{
if ( is_expanded() )
{
// likely it is, since I clicked on "see also";
// close it
expand_toggle(name_expanded);
}
expand_toggle(see_also);
document.getElementById('IMG_' + see_also).scrollIntoView();
return true;
}
function set_image(name, source)
{
document.getElementById('IMG_' + name).src = source;
}
function set_all_images(classname, source)
{
var all = document.getElementsByClassName(classname);
for ( var i in all )
all[i].src = source;
}
function gotoMD()
{
location.href = "https://github.com/idapython/src/blob/master/examples/index.md";
}
</script>
<link rel="stylesheet" href="index.css">
<script type="text/javascript" src="index.js"></script>
</head>
<body onload="init();">
<body>
<div style="margin:20px 20px 50px 20px">
<span style="border:solid black 1px;padding:10px;font-size:small;cursor:pointer" onclick="gotoMD();">
Switch to MarkDown</span>
<a href="http://htmlpreview.github.io/?https://github.com/idapython/src/blob/master/examples/index.html">View on GitHub</a>
</div>
<div class="toplevel-actions" onclick="handle_toplevel_action()">
<a href="#" class="exp-col-all expand-all">Expand all</a>
<a href="#" class="exp-col-all collapse-all">collapse all</a>
</div>
<h1>IDAPython examples:</h1>
<!--gen:group:category-->
<h2>Category: <!--gen:category--></h2>
<div class="c_list">
<div class="c_list" onclick="handle_click()">
<!--gen:block-->
<a name="<!--gen:name-->"/>
<div>
<img id="IMG_<!--gen:name-->" class="c_expand_gadget"
onmouseover="image_with_hover('<!--gen:name-->');"
onmouseout="image_without_hover('<!--gen:name-->');"
onclick="expand_click('<!--gen:name-->');"/>
<!--gen:name-->: <i><!--gen:summary--></i>
</div>
<div id="DIV_<!--gen:name-->" style="display:none">
<div class="example-entry collapsed-entry" name="<!--gen:name-->">
<div>
<span class="exp-col expander">&#x25B9;</span>
<span class="exp-col collapser" style="display:none">&#x25BF;</span>
<a href="<!--gen:path-->"><!--gen:name--></a>: <i><!--gen:summary--></i>
</div>
<div class="details" id="DIV_<!--gen:name-->">
<hr/>
<h2><!--gen:name--></h2>
<h3>Category</h3>
<indent><!--gen:category--></indent>
<h3>Summary</h3>
<indent><!--gen:summary--></indent>
<h3>Source code</h3>
<indent><a target="_blank"
  href="https://github.com/idapython/src/blob/master/examples/<!--gen:path-->">
Jump to GitHub</a></indent>
<h3>Description</h3>
<indent>
<pre>
<!--gen:description-->
</pre>
</indent>
<!-- "Keywords" heading produced only if there is data for it -->
<!--gen:block-->
<!--gen:first-->
<h3>Keywords</h3>
<!--gen:end-->
<span><!--gen:keywords--></span>
<!--gen:end-->
<h3>Uses</h3>
<ul>
<ul>
<li>Category: <!--gen:category--></li>
<li>Summary: <!--gen:summary--></li>
<li>View on <a href="https://github.com/idapython/src/blob/master/examples/<!--gen:path-->">GitHub</a></li>
<!-- only if there is data for it -->
<li>Keywords:
<!--gen:block-->
<li><!--gen:uses--></li>
<span>
<!--gen:first-->
<!--gen:end-->
<!--gen:keywords-->
</span>
<!--gen:end-->
</ul>
<!-- "See also" heading produced only if there is data for it -->
<!--gen:block-->
<!--gen:first-->
<h3>See also</h3>
<ul>
<!--gen:end-->
<li><a href="#<!--gen:see_also-->"
onclick="on_see_also('<!--gen:see_also-->');">
<!--gen:see_also--></a></li>
<!--gen:last-->
</ul>
<!--gen:end-->
<!--gen:end-->
<hr/>
<li>APIs used
<ul>
<!--gen:block-->
<li><!--gen:uses--></li>
<!--gen:end-->
</ul>
</li>
<li>Summary: <!--gen:summary--></li>
<!-- only if there is data for it -->
<!--gen:block-->
<!--gen:first-->
<li>See also:
<ul>
<!--gen:end-->
<li><a href="#<!--gen:see_also-->"
onclick="on_see_also('<!--gen:see_also-->');">
<!--gen:see_also--></a></li>
<!--gen:last-->
</ul>
<!--gen:end-->
</li>
<!--gen:end-->
</ul>
</div>
</div>
<!-- end block (per example in this category) -->
<!--gen:end-->
+1 -1
View File
@@ -1,4 +1,4 @@
[Switch to HTML](http://htmlpreview.github.io/?https://github.com/idapython/src/blob/master/examples/index.html)
[HTML version](http://htmlpreview.github.io/?https://github.com/idapython/src/blob/master/examples/index.html)
# IDAPython examples