MindShaRE: Using lldbinit to Enhance the LLDB Debugger
April 21, 2020 | Ziad BadawiMindShaRE is our periodic look at various reverse engineering tips and tricks. The goal is to keep things small and discuss some everyday aspects of reversing. You can view previous entries in this series here.
Recently, I have been heavily using LLDB for debugging all the things in macOS like WebKit and system daemons. Coming from a WinDBG background, LLDB is quite different. I expected similar binary module handling in LLDB, but unfortunately that wasn’t the case. The nice thing about LLDB is its customizability. Its API can be used directly from Python, which makes it very flexible in adding features that help speed up debugging and make it a pleasant experience – both visually and practically. Fortunately, someone already made a script for doing just that. They describe it as “A gdbinit clone for LLDB”. Using this script is very simple. Just download lldbinit.py
and import it in your .lldbinit
file such as command script import ~/lldbinit.py
.
It would look something like this when starting LLDB with a binary or attaching it to one:
If you type in lldbinitcmds
, it will list all new commands that were introduced in this script making debugging bearable and visually pleasing.
One feature I miss in LLDB that is present in WinDBG is the ability to use module names in breakpoints with offsets, for example, bp addresslib+1EE6
where addresslib.dll
is a loaded module. The way to use offsets in LLDB is by fetching the modules base address and then adding the offset. This would lead to the following:
This gets tedious. So I thought, why not add support to lldbinit.py
for module names? The idea is quite trivial:
1- Get the module name from input.
2- Get a list of loaded modules.
3- Filter list with module name and return its base address.
I started with the command bpt
, which is located in function cmd_bpt()
. Input will be available in the command
parameter which is passed to evaluate()
for parsing. The first action I did was fetching a list of loaded modules:
To get hold of the current execution context, it should be passed from cmd_bpt()
according to the docs, thus adding exe_ctx
as a parameter. This function returns a dictionary with the loaded modules in the current context.
Next is a function that filters and returns the base address of the module plus its offset:
This should be self-explanatory. It will return the final expression which is composed of the module base address plus the offset (e.g. 0x00007fff50c2a000+0x23351
) returned from input such as audiotoolboxcore+0x23351
.
To piece things together, cmd_bpt()
becomes:
Declarations are required as well, such as importing shlex
and defining loaded_mods
globally.
Nice.
Conclusion
Intricate tools like LLDB may hinder productivity by being tedious, especially with its verbose command structure. Granted that command shortcuts can be used, certain other aspects are still tiresome. The people that are helping in making such tools a little less complicated and more enjoyable are always appreciated.
You can find me on Twitter at @ziadrb, and follow the team for the latest in exploit techniques and security patches.