pax_global_header00006660000000000000000000000064136057550470014526gustar00rootroot0000000000000052 comment=bcf25d957657414d025ff488889cdef8d4fcae06 voltron-master/000077500000000000000000000000001360575504700140665ustar00rootroot00000000000000voltron-master/.gitignore000066400000000000000000000005521360575504700160600ustar00rootroot00000000000000### Python ### *.py[cod] # C extensions *.so # Packages *.egg *.egg-info dist build eggs parts bin var sdist develop-eggs .installed.cfg lib lib64 __pycache__ # Installer logs pip-log.txt # Unit test / coverage reports .coverage .tox nosetests.xml # Translations *.mo # Mr Developer .mr.developer.cfg .project .pydevproject # Rope .ropeproject .DS_Store voltron-master/.travis.yml000066400000000000000000000022651360575504700162040ustar00rootroot00000000000000language: python matrix: include: - os: linux sudo: required dist: trusty python: "3.3" # - os: linux # sudo: required # dist: trusty # python: "3.4" # - os: linux # sudo: required # dist: trusty # python: "3.5" # - os: osx # osx_image: xcode7.2 # language: generic addons: apt: packages: - build-essential - gdb before_install: - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; brew install python ; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo apt-get update -qq ; sudo apt-get install lldb-3.4 ; fi install: - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then pip install mock pexpect nose ; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install mock pexpect nose --user; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then pip install . ; fi - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then pip install . --user; fi script: - python --version - mkdir ~/.voltron - echo '{"general":{"debug_logging":true}}' >~/.voltron/config - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then python -m nose -sv tests/gdb_cli_tests.py ; fi - python -m nose -sv tests/lldb_cli_tests.py voltron-master/LICENSE000066400000000000000000000020601360575504700150710ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 snare Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. voltron-master/README.md000066400000000000000000000211641360575504700153510ustar00rootroot00000000000000Voltron ======= [![build](https://travis-ci.org/snare/voltron.svg?branch=master)](https://travis-ci.org/snare/voltron/) Voltron is an extensible debugger UI toolkit written in Python. It aims to improve the user experience of various debuggers (LLDB, GDB, VDB and WinDbg) by enabling the attachment of utility views that can retrieve and display data from the debugger host. By running these views in other TTYs, you can build a customised debugger user interface to suit your needs. Voltron does not aim to be everything to everyone. It's not a wholesale replacement for your debugger's CLI. Rather, it aims to complement your existing setup and allow you to extend your CLI debugger as much or as little as you like. If you just want a view of the register contents in a window alongside your debugger, you can do that. If you want to go all out and have something that looks more like OllyDbg, you can do that too. Built-in views are provided for: - Registers - Disassembly - Stack - Memory - Breakpoints - Backtrace The author's setup looks something like this: ![voltron example LLDB](http://i.imgur.com/9nukztA.png) Any debugger command can be split off into a view and highlighted with a specified Pygments lexer: ![command views](http://i.imgur.com/RbYQYXp.png) More screenshots are [here](https://github.com/snare/voltron/wiki/Screenshots). Support ------- Voltron supports LLDB, GDB, VDB and WinDbg/CDB (via [PyKD](https://pykd.codeplex.com/)) and runs on macOS, Linux and Windows. WinDbg support is still fairly new, please [open an issue](https://github.com/snare/voltron/issues) if you have problems. The following architectures are supported: | | lldb | gdb | vdb | windbg | |---------|------|-----|-----|--------| | x86 | ✓ | ✓ | ✓ | ✓ | | x86_64 | ✓ | ✓ | ✓ | ✓ | | arm | ✓ | ✓ | ✓ | ✗ | | arm64 | ✓ | ✗ | ✗ | ✗ | | powerpc | ✗ | ✓ | ✗ | ✗ | Installation ------------ **Note:** Only macOS and Debian derivatives are fully supported by the install script. It should hopefully not fail on other Linux distros, but it won't try to install package dependencies. If you're using another distro, have a look at `install.sh` to work out what dependencies you might need to install before running it. Download the source and run the install script: $ git clone https://github.com/snare/voltron $ cd voltron $ ./install.sh By default, the install script will install into the user's `site-packages` directory. If you want to install into the system `site-packages`, use the `-s` flag: $ ./install.sh -s You can also install into a virtual environment (for LLDB only) like this: $ ./install.sh -v /path/to/venv -b lldb If you are on Windows without a shell, have problems installing, or would prefer to install manually, please see the [manual installation documentation](https://github.com/snare/voltron/wiki/Installation). Quick Start ----------- 1. If your debugger has an init script (`.lldbinit` for LLDB or `.gdbinit` for GDB) configure it to load Voltron when it starts by sourcing the `entry.py` entry point script. The full path will be inside the `voltron` package. For example, on macOS it might be */Library/Python/2.7/site-packages/voltron/entry.py*. The `install.sh` script will add this to your `.gdbinit` or `.lldbinit` file automatically if it detects GDB or LLDB in your path. LLDB: command script import /path/to/voltron/entry.py GDB: source /path/to/voltron/entry.py 2. Start your debugger and initialise Voltron manually if necessary. On recent versions of LLDB you do not need to initialise Voltron manually: $ lldb target_binary On older versions of LLDB you need to call `voltron init` after you load the inferior: $ lldb target_binary (lldb) voltron init GDB: $ gdb target_binary VDB: $ ./vdbbin target_binary > script /path/to/voltron/entry.py WinDbg/CDB is only supported run via Bash with a Linux userland. The author tests with [Git Bash](https://git-for-windows.github.io) and [ConEmu](http://conemu.github.io). PyKD and Voltron can be loaded in one command when launching the debugger: $ cdb -c '.load C:\path\to\pykd.pyd ; !py --global C:\path\to\voltron\entry.py' target_binary 3. In another terminal (I use iTerm panes) start one of the UI views. On LLDB, WinDbg and GDB the views will update immediately. On VDB they will not update until the inferior stops (at a breakpoint, after a step, etc): $ voltron view register $ voltron view stack $ voltron view disasm $ voltron view backtrace 4. Set a breakpoint and run your inferior. (*db) b main (*db) run 5. When the debugger hits the breakpoint, the views will be updated to reflect the current state of registers, stack, memory, etc. Views are updated after each command is executed in the debugger CLI, using the debugger's "stop hook" mechanism. So each time you step, or continue and hit a breakpoint, the views will update. Documentation ------------- See the [wiki](https://github.com/snare/voltron/wiki) on github. FAQ --- **Q.** Why am I getting an `ImportError` loading Voltron? **A.** You might have multiple versions of Python installed and have installed Voltron using the wrong one. See the more detailed [installation instructions](https://github.com/snare/voltron/wiki/Installation). **Q.** [GEF](https://github.com/hugsy/gef)? [PEDA](https://github.com/longld/peda)? [PwnDbg](https://github.com/pwndbg/pwndbg)? [fG's gdbinit](https://github.com/gdbinit/gdbinit)? **A.** All super great extensions for GDB. These tools primarily provide sets of additional commands for exploitation tasks, but each also provides a "context" display with a view of registers, stack, code, etc, like Voltron. These tools print their context display in the debugger console each time the debugger stops. Voltron takes a different approach by embedding an RPC server implant in the debugger and enabling the attachment of views from other terminals (or even web browsers, or now [synchronising with Binary Ninja](https://github.com/snare/binja)), which allows the user to build a cleaner multi-window interface to their debugger. Voltron works great alongside all of these tools. You can just disable the context display in your GDB extension of choice and hook up some Voltron views, while still getting all the benefits of the useful commands added by these tools. Bugs and Errata --------------- See the [issue tracker](https://github.com/snare/voltron/issues) on github for more information or to submit issues. If you're experiencing an `ImportError` loading Voltron, please ensure you've followed the [installation instructions](https://github.com/snare/voltron/wiki/Installation) for your platform. ### LLDB On older versions of LLDB, the `voltron init` command must be run manually after loading the debug target, as a target must be loaded before Voltron's hooks can be installed. Voltron will attempt to automatically register its event handler, and it will inform the user if `voltron init` is required. ### WinDbg More information about WinDbg/CDB support [here](https://github.com/snare/voltron/wiki/Installation#windbg). ### Misc The authors primarily use Voltron with the most recent version of LLDB on macOS. We will try to test everything on as many platforms and architectures as possible before releases, but LLDB/macOS/x64 is going to be by far the most frequently-used combination. Hopefully Voltron doesn't set your pets on fire, but YMMV. License ------- See the [LICENSE](https://github.com/snare/voltron/blob/master/LICENSE) file. If you use this and don't hate it, buy me a beer at a conference some time. This license also extends to other contributors - [richo](http://github.com/richo) definitely deserves a few beers for his contributions. Credits ------- Thanks to my former employers Assurance and Azimuth Security for giving me time to spend working on this. Props to [richo](http://github.com/richo) for all his contributions to Voltron. [fG!](http://github.com/gdbinit)'s gdbinit was the original inspiration for this project. Thanks to [Willi](http://github.com/williballenthin) for implementing the VDB support. Voltron now uses [Capstone](http://www.capstone-engine.org) for disassembly as well as the debugger hosts' internal disassembly mechanism. [Capstone](http://www.capstone-engine.org) is a powerful, open source, multi-architecture disassembler upon which the next generation of reverse engineering and debugging tools are being built. Check it out. Thanks to [grazfather](http://github.com/grazfather) for ongoing contributions. voltron-master/examples/000077500000000000000000000000001360575504700157045ustar00rootroot00000000000000voltron-master/examples/angularview/000077500000000000000000000000001360575504700202305ustar00rootroot00000000000000voltron-master/examples/angularview/angularview.py000066400000000000000000000017211360575504700231270ustar00rootroot00000000000000import logging import pygments from voltron.plugin import * from voltron.lexers import * from voltron.api import * from flask import * log = logging.getLogger('api') class AngularViewPlugin(WebPlugin): name = 'angularview' class FormatDisassemblyRequest(APIRequest): _fields = {'disassembly': True} def dispatch(self): try: res = FormatDisassemblyResponse( disassembly=pygments.highlight(self.disassembly.strip(), LLDBIntelLexer(), pygments.formatters.HtmlFormatter())) except Exception as e: msg = "Exception formatting disassembly: {}".format(repr(e)) log.exception(msg) res = APIGenericErrorResponse(msg) return res class FormatDisassemblyResponse(APIResponse): _fields = {'disassembly': True} class FormatDisassemblyPlugin(APIPlugin): request = "format_disasm" request_class = FormatDisassemblyRequest response_class = FormatDisassemblyResponse voltron-master/examples/angularview/static/000077500000000000000000000000001360575504700215175ustar00rootroot00000000000000voltron-master/examples/angularview/static/css/000077500000000000000000000000001360575504700223075ustar00rootroot00000000000000voltron-master/examples/angularview/static/css/solarized.css000066400000000000000000000105551360575504700250230ustar00rootroot00000000000000/* Solarized Dark For use with Jekyll and Pygments http://ethanschoonover.com/solarized SOLARIZED HEX ROLE --------- -------- ------------------------------------------ base03 #002b36 background base01 #586e75 comments / secondary content base1 #93a1a1 body text / default code / primary content orange #cb4b16 constants red #dc322f regex, special keywords blue #268bd2 reserved keywords cyan #2aa198 strings, numbers green #859900 operators, other keywords */ /*.highlight { background-color: #002b36; color: #93a1a1 }*/ .highlight { background-color: #073642; color: #93a1a1 } .highlight .c { color: #586e75 } /* Comment */ .highlight .err { color: #93a1a1 } /* Error */ .highlight .g { color: #93a1a1 } /* Generic */ .highlight .k { color: #859900 } /* Keyword */ .highlight .l { color: #93a1a1 } /* Literal */ .highlight .n { color: #93a1a1 } /* Name */ .highlight .o { color: #859900 } /* Operator */ .highlight .x { color: #cb4b16 } /* Other */ .highlight .p { color: #93a1a1 } /* Punctuation */ .highlight .cm { color: #586e75 } /* Comment.Multiline */ .highlight .cp { color: #859900 } /* Comment.Preproc */ .highlight .c1 { color: #586e75 } /* Comment.Single */ .highlight .cs { color: #859900 } /* Comment.Special */ .highlight .gd { color: #2aa198 } /* Generic.Deleted */ .highlight .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */ .highlight .gr { color: #dc322f } /* Generic.Error */ .highlight .gh { color: #cb4b16 } /* Generic.Heading */ .highlight .gi { color: #859900 } /* Generic.Inserted */ .highlight .go { color: #93a1a1 } /* Generic.Output */ .highlight .gp { color: #93a1a1 } /* Generic.Prompt */ .highlight .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #cb4b16 } /* Generic.Subheading */ .highlight .gt { color: #93a1a1 } /* Generic.Traceback */ .highlight .kc { color: #cb4b16 } /* Keyword.Constant */ .highlight .kd { color: #268bd2 } /* Keyword.Declaration */ .highlight .kn { color: #859900 } /* Keyword.Namespace */ .highlight .kp { color: #859900 } /* Keyword.Pseudo */ .highlight .kr { color: #268bd2 } /* Keyword.Reserved */ .highlight .kt { color: #dc322f } /* Keyword.Type */ .highlight .ld { color: #93a1a1 } /* Literal.Date */ .highlight .m { color: #2aa198 } /* Literal.Number */ .highlight .s { color: #2aa198 } /* Literal.String */ .highlight .na { color: #93a1a1 } /* Name.Attribute */ .highlight .nb { color: #B58900 } /* Name.Builtin */ .highlight .nc { color: #268bd2 } /* Name.Class */ .highlight .no { color: #cb4b16 } /* Name.Constant */ .highlight .nd { color: #268bd2 } /* Name.Decorator */ .highlight .ni { color: #cb4b16 } /* Name.Entity */ .highlight .ne { color: #cb4b16 } /* Name.Exception */ .highlight .nf { color: #268bd2 } /* Name.Function */ .highlight .nl { color: #93a1a1 } /* Name.Label */ .highlight .nn { color: #93a1a1 } /* Name.Namespace */ .highlight .nx { color: #93a1a1 } /* Name.Other */ .highlight .py { color: #93a1a1 } /* Name.Property */ .highlight .nt { color: #268bd2 } /* Name.Tag */ .highlight .nv { color: #268bd2 } /* Name.Variable */ .highlight .ow { color: #859900 } /* Operator.Word */ .highlight .w { color: #93a1a1 } /* Text.Whitespace */ .highlight .mf { color: #2aa198 } /* Literal.Number.Float */ .highlight .mh { color: #2aa198 } /* Literal.Number.Hex */ .highlight .mi { color: #2aa198 } /* Literal.Number.Integer */ .highlight .mo { color: #2aa198 } /* Literal.Number.Oct */ .highlight .sb { color: #586e75 } /* Literal.String.Backtick */ .highlight .sc { color: #2aa198 } /* Literal.String.Char */ .highlight .sd { color: #93a1a1 } /* Literal.String.Doc */ .highlight .s2 { color: #2aa198 } /* Literal.String.Double */ .highlight .se { color: #cb4b16 } /* Literal.String.Escape */ .highlight .sh { color: #93a1a1 } /* Literal.String.Heredoc */ .highlight .si { color: #2aa198 } /* Literal.String.Interpol */ .highlight .sx { color: #2aa198 } /* Literal.String.Other */ .highlight .sr { color: #dc322f } /* Literal.String.Regex */ .highlight .s1 { color: #2aa198 } /* Literal.String.Single */ .highlight .ss { color: #2aa198 } /* Literal.String.Symbol */ .highlight .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ .highlight .vc { color: #268bd2 } /* Name.Variable.Class */ .highlight .vg { color: #268bd2 } /* Name.Variable.Global */ .highlight .vi { color: #268bd2 } /* Name.Variable.Instance */ .highlight .il { color: #2aa198 } /* Literal.Number.Integer.Long */ voltron-master/examples/angularview/static/css/style.css000066400000000000000000000005711360575504700241640ustar00rootroot00000000000000.reg_label { color: #b58900; font-weight: bold; } .reg_highlight { color: #dc322f; } .reg_normal { } #tile { display:inline-block; vertical-align: top; background-color: #073642; padding-left: 0.5em; padding-right: 0.5em; padding-top: 0.3em; padding-bottom: 0.3em; } pre { margin: 0; } #disasm { background-color: #073642; }voltron-master/examples/angularview/static/index.html000066400000000000000000000017571360575504700235260ustar00rootroot00000000000000 voltron
registers
{{reg.name}} {{reg.value}}
disassembly
voltron-master/examples/angularview/static/js/000077500000000000000000000000001360575504700221335ustar00rootroot00000000000000voltron-master/examples/angularview/static/js/app.js000066400000000000000000000002361360575504700232520ustar00rootroot00000000000000angular.module('VoltronApp', [ 'VoltronApp.controllers', 'VoltronApp.services' ]) .config(function($sceProvider) { $sceProvider.enabled(false); }); voltron-master/examples/angularview/static/js/controllers.js000066400000000000000000000053211360575504700250400ustar00rootroot00000000000000function f_hex(number, pad) { return ("000000000000000000000000000000" + number.toString(16)).substr(-pad).toUpperCase(); } angular.module('VoltronApp.controllers', []). controller('voltronController', function($scope, voltronAPIservice) { $scope.version = null; $scope.registers = []; $scope.disassembly = null; var state = null; var new_regs = null; var old_regs = []; voltronAPIservice.version().success(function (response) { $scope.version = response.data; }); var format_registers = function(new_regs, old_regs, arch) { fmt = [] if (arch == 'x86_64') { regs = ['rip','rax','rbx','rcx','rdx','rbp','rsp','rdi','rsi','r8','r9','r10','r11','r12','r13','r14','r15'] for (i = 0; i < regs.length; i++) { fmt.push({ name: (regs[i].length == 2 ? String.fromCharCode(160) + regs[i] : regs[i]), value: f_hex(new_regs[regs[i]], 16), class: (new_regs[regs[i]] != old_regs[regs[i]] ? "reg_highlight" : "reg_normal") }) } } return fmt } var format_disasm = function(disasm) { lines = disasm.split(); // trim lldb's "inferior`main:" so hljs works if (lines[0].indexOf("`") > -1) { lines.splice(0, 1); } return lines.join('\n'); } var update = function() { // get target info voltronAPIservice.targets().success(function (response) { targets = response.data.targets; // make sure we have a target if (targets[0]['arch'] != null) { // update registers voltronAPIservice.registers().success(function (response) { // get new register values new_regs = response.data.registers // format registers $scope.registers = format_registers(new_regs, old_regs, targets[0]['arch']); // keep old registers old_regs = new_regs; }); // update disassembly voltronAPIservice.disassemble(null, 32).success(function (response) { $scope.disassembly = response.data.formatted; }); } }); } var poll = function() { // wait for the next state change response = voltronAPIservice.wait(30).success(function (response) { if (response.status == "success") { update(); } // wait for the next state change poll(); }); }; // initial update update(); // start waiting for debugger stops poll(); });voltron-master/examples/angularview/static/js/services.js000066400000000000000000000033721360575504700243210ustar00rootroot00000000000000angular.module('VoltronApp.services', []). factory('voltronAPIservice', function($http) { var voltronAPI = {}; function createRequest(requestType, data) { return {type: "request", request: requestType, data: data} } voltronAPI.request = function(request) { return $http({ method: 'POST', url: '/api/request', data: request }); } voltronAPI.disassemble_format = function(data) { res = voltronAPI.request(createRequest('disassemble', {address: address, count: count})) return $http({ method: 'POST', url: '/api/request', data: createRequest('format_disasm', {disassembly: res.data.disassembly}) }); } voltronAPI.disassemble = function(address, count) { return voltronAPI.request(createRequest('disassemble', {address: address, count: count})) } voltronAPI.command = function(command) { return voltronAPI.request(createRequest('command', {command: command})) } voltronAPI.targets = function() { return voltronAPI.request(createRequest('targets', {})) } voltronAPI.memory = function(address, length) { return voltronAPI.request(createRequest('memory', {address: address, length: length})) } voltronAPI.registers = function() { return voltronAPI.request(createRequest('registers', {})) } voltronAPI.stack = function(length) { return voltronAPI.request(createRequest('stack', {length: length})) } voltronAPI.state = function() { return voltronAPI.request(createRequest('state', {})) } voltronAPI.version = function() { return voltronAPI.request(createRequest('version', {})) } return voltronAPI; }); voltron-master/examples/client.py000066400000000000000000000037111360575504700175360ustar00rootroot00000000000000#!/usr/bin/env python """ Example Voltron client. Start your debugger as follows: $ lldb /tmp/inferior Voltron loaded. Run `voltron init` after you load a target. (lldb) target create "/tmp/inferior" Current executable set to '/tmp/inferior' (x86_64). (lldb) voltron init Registered stop-hook (lldb) b main Breakpoint 1: where = inferior`main, address = 0x0000000100000cf0 (lldb) run Process 13185 launched: '/Volumes/Data/Users/snare/code/voltron/repo/tests/inferior' (x86_64) Process 13185 stopped * thread #1: tid = 0x1ee63, 0x0000000100000cf0 inferior`main, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: 0x0000000100000cf0 inferior`main inferior`main: -> 0x100000cf0: push rbp 0x100000cf1: mov rbp, rsp 0x100000cf4: sub rsp, 0x50 0x100000cf8: mov dword ptr [rbp - 0x4], 0x0 Run this client in another terminal. Each time you `stepi` in the debugger, the client will output the current RIP: $ python client.py Instruction pointer is: 0x100000CFF Instruction pointer is: 0x100000D02 Instruction pointer is: 0x100000D06 Instruction pointer is: 0x100000D0D Instruction pointer is: 0x100000D15 Instruction pointer is: 0x100000D1C """ import voltron from voltron.core import Client def main(): # Create a client and connect to the server client = Client() # Main event loop while True: # Wait for the debugger to stop again res = client.perform_request('version', block=True) if res.is_success: # If nothing went wrong, get the instruction pointer and print it res = client.perform_request('registers', registers=['rip']) if res.is_success: print("Instruction pointer is: 0x{:X}".format(res.registers['rip'])) else: print("Failed to get registers: {}".format(res)) else: print("Error waiting for the debugger to stop: {}".format(res)) break if __name__ == "__main__": main() voltron-master/examples/command.py000066400000000000000000000037631360575504700177050ustar00rootroot00000000000000""" Example command plugin Copy this to your ~/.voltron/plugins directory. It will be loaded when Voltron is initialised, and register a debugger command that works as follows: $ lldb /tmp/inferior Voltron loaded. Run `voltron init` after you load a target. (lldb) target create "/tmp/inferior" Current executable set to '/tmp/inferior' (x86_64). (lldb) b main Breakpoint 1: where = inferior`main, address = 0x0000000100000cf0 (lldb) run Process 12561 launched: '/tmp/inferior' (x86_64) Process 12561 stopped * thread #1: tid = 0x1d33f, 0x0000000100000cf0 inferior`main, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: 0x0000000100000cf0 inferior`main inferior`main: -> 0x100000cf0: push rbp 0x100000cf1: mov rbp, rsp 0x100000cf4: sub rsp, 0x50 0x100000cf8: mov dword ptr [rbp - 0x4], 0x0 (lldb) example rax 0000000100000CF0 rbx 0000000000000000 rcx 00007FFF5FBFFA70 rdx 00007FFF5FBFF978 rbp 00007FFF5FBFF958 rsp 00007FFF5FBFF948 rdi 0000000000000001 rsi 00007FFF5FBFF968 rip 0000000100000CF0 r8 0000000000000000 r9 00007FFF5FBFEA08 r10 0000000000000032 r11 0000000000000246 r12 0000000000000000 r13 0000000000000000 r14 0000000000000000 r15 0000000000000000 Provided you stick to the adaptor API that is implemented in every *DBAdaptor class, custom command plugins should work across all debugger hosts. This is a quick example that will only work on an x86_64 target. """ import blessed import voltron from voltron.plugin import CommandPlugin from voltron.command import VoltronCommand class ExampleCommand(VoltronCommand): def invoke(self, *args): regs = voltron.debugger.registers() reg_list = ['rax','rbx','rcx','rdx','rbp','rsp','rdi','rsi','rip', 'r8','r9','r10','r11','r12','r13','r14','r15'] for name in reg_list: print("{t.bold}{:3} {t.normal}{:0=16X}".format(name, regs[name], t=blessed.Terminal())) class ExampleCommandPlugin(CommandPlugin): name = 'example' command_class = ExampleCommandvoltron-master/examples/view.py000066400000000000000000000041051360575504700172300ustar00rootroot00000000000000""" Example Voltron view. Copy this to your ~/.voltron/plugins directory. When the `voltron view` command is executed, 'example' should be visible in the list of valid view names. Start your debugger as follows: $ lldb /tmp/inferior Voltron loaded. Run `voltron init` after you load a target. (lldb) target create "/tmp/inferior" Current executable set to '/tmp/inferior' (x86_64). (lldb) voltron init Registered stop-hook (lldb) b main Breakpoint 1: where = inferior`main, address = 0x0000000100000cf0 (lldb) run Process 13185 launched: '/Volumes/Data/Users/snare/code/voltron/repo/tests/inferior' (x86_64) Process 13185 stopped * thread #1: tid = 0x1ee63, 0x0000000100000cf0 inferior`main, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1 frame #0: 0x0000000100000cf0 inferior`main inferior`main: -> 0x100000cf0: push rbp 0x100000cf1: mov rbp, rsp 0x100000cf4: sub rsp, 0x50 0x100000cf8: mov dword ptr [rbp - 0x4], 0x0 Run this view in another terminal (as follows). Each time you `stepi` in the debugger, the view will update and display the current register values. $ voltron view example """ from voltron.view import TerminalView from voltron.plugin import ViewPlugin class ExampleView(TerminalView): def render(self, *args, **kwargs): # Perform the request res = self.client.perform_request('registers') if res.is_success: # Process the registers and set the body to the formatted list reg_list = ['rax','rbx','rcx','rdx','rbp','rsp','rdi','rsi','rip', 'r8','r9','r10','r11','r12','r13','r14','r15'] lines = map(lambda x: '{:3}: {:016X}'.format(x, res.registers[x]), reg_list) self.body = '\n'.join(lines) else: self.body = "Failed to get registers: {}".format(res) # Set the title and info self.title = '[example]' self.info = 'some infoz' # Let the parent do the rendering super(ExampleView, self).render() class ExampleViewPlugin(ViewPlugin): name = 'example' view_class = ExampleView voltron-master/install.sh000077500000000000000000000243511360575504700161000ustar00rootroot00000000000000#!/bin/bash # # Install Voltron for whichever debuggers are detected (only GDB and LLDB so # far). # function usage { cat </dev/null 2>&1 if [ $? -ne 0 ]; then # If not, attempt to install it using ensurepip echo "Attempting to install pip using 'ensurepip'." ${SUDO} ${LLDB_PYTHON} -m ensurepip $USER_MODE || return $? fi # Some really old pip installations default to the old pypi.python.org, which no longer works. echo "Attempting to upgrade pip." ${SUDO} ${LLDB_PYTHON} -m pip install "pip>=$PIP_MIN_VER" $USER_MODE -U --index-url "$PYPI_URL" if [ $? != 0 ]; then # We may still fail here due to TLS incompatibility # TLS 1.x got turned off 2018-04-11 # https://status.python.org/incidents/hdx7w97m5hr8 # Curl may be new enough to support TLS 1.2, so try to curl the pip installer from pypa.io # It's able to download and install pip without TLS errors somehow echo "Failed to upgrade pip." echo "Attempting to fall back to installation via curl." curl_get_pip || return $? fi } function get_lldb_python_exe { # Find the Python version used by LLDB local lldb_pyver=$(${LLDB} -Q -x -b --one-line 'script import platform; print(".".join(platform.python_version_tuple()[:2]))'|tail -1) local lldb_python=$(${LLDB} -Q -x -b --one-line 'script import sys; print(sys.executable)'|tail -1) lldb_python=$(${LLDB} -Q -x -b --one-line 'script import sys; print(sys.executable)'|tail -1) local lldb_python_basename=$(basename "${lldb_python}") if [ "python" = "$lldb_python_basename" ]; then lldb_python="${lldb_python/%$lldb_pyver/}${lldb_pyver}" elif [ "lldb" = "$lldb_python_basename" ]; then # newer lldb versions report sys.path as /path/to/lldb instead of python # sys.exec_path still appears to be the parent path of bin/python though local lldb_python_exec_prefix=$(${LLDB} -Q -x -b --one-line 'script import sys; print(sys.exec_prefix)'|tail -1) lldb_python="$lldb_python_exec_prefix/bin/python" lldb_python="${lldb_python/%$lldb_pyver/}${lldb_pyver}" fi echo "$lldb_python" } if [ "${BACKEND_GDB}" -eq 1 ]; then # Find the Python version used by GDB GDB_PYVER=$(${GDB} -batch -q --nx -ex 'pi import platform; print(".".join(platform.python_version_tuple()[:2]))') GDB_PYTHON=$(${GDB} -batch -q --nx -ex 'pi import sys; print(sys.executable)') GDB_PYTHON="${GDB_PYTHON/%$GDB_PYVER/}${GDB_PYVER}" install_packages if [ -z $USER_MODE ]; then GDB_SITE_PACKAGES=$(${GDB} -batch -q --nx -ex 'pi import site; print(site.getsitepackages()[0])') else GDB_SITE_PACKAGES=$(${GDB} -batch -q --nx -ex 'pi import site; print(site.getusersitepackages())') fi # Install Voltron and dependencies ${SUDO} ${GDB_PYTHON} -m pip install -U $USER_MODE $DEV_MODE . # Add Voltron to gdbinit GDB_INIT_FILE="${HOME}/.gdbinit" if [ -e ${GDB_INIT_FILE} ]; then sed -i.bak '/voltron/d' ${GDB_INIT_FILE} fi if [ -z $DEV_MODE ]; then GDB_ENTRY_FILE="$GDB_SITE_PACKAGES/voltron/entry.py" else GDB_ENTRY_FILE="$(pwd)/voltron/entry.py" fi echo "source $GDB_ENTRY_FILE" >> ${GDB_INIT_FILE} fi if [ "${BACKEND_LLDB}" -eq 1 ]; then LLDB_PYTHON=$(get_lldb_python_exe) || quit "Failed to locate python interpreter." 1 ensure_pip || quit "Failed to install pip." 1 ${LLDB_PYTHON} -m pip install --user --upgrade six || quit "Failed to install or upgrade 'six' python package." 1 if [ -n "${VENV}" ]; then echo "Creating virtualenv..." ${LLDB_PYTHON} -m pip install --user virtualenv ${LLDB_PYTHON} -m virtualenv "${VENV}" LLDB_PYTHON="${VENV}/bin/python" LLDB_SITE_PACKAGES=$(find "${VENV}" -name site-packages) elif [ -z "${USER_MODE}" ]; then LLDB_SITE_PACKAGES=$(${LLDB} -Q -x -b --one-line 'script import site; print(site.getsitepackages()[0])'|tail -1) || quit "Failed to locate site-packages." 1 else LLDB_SITE_PACKAGES=$(${LLDB} -Q -x -b --one-line 'script import site; print(site.getusersitepackages())'|tail -1) || quit "Failed to locate site-packages." 1 fi install_packages || quit "Failed to install packages." 1 if [ "$LLDB_SITE_PACKAGES" == "$GDB_SITE_PACKAGES" ]; then echo "Skipping installation for LLDB - same site-packages directory" else # Install Voltron and dependencies ${SUDO} ${LLDB_PYTHON} -m pip install -U $USER_MODE $DEV_MODE . || quit "Failed to install voltron." 1 fi # Add Voltron to lldbinit LLDB_INIT_FILE="${HOME}/.lldbinit" if [ -e ${LLDB_INIT_FILE} ]; then sed -i.bak '/voltron/d' ${LLDB_INIT_FILE} fi if [ -z "${DEV_MODE}" ]; then LLDB_ENTRY_FILE="$LLDB_SITE_PACKAGES/voltron/entry.py" else LLDB_ENTRY_FILE="$(pwd)/voltron/entry.py" fi if [ -n "${VENV}" ]; then echo "script import sys;sys.path.append('${LLDB_SITE_PACKAGES}')" >> ${LLDB_INIT_FILE} fi echo "command script import $LLDB_ENTRY_FILE" >> ${LLDB_INIT_FILE} fi if [ "${BACKEND_GDB}" -ne 1 ] && [ "${BACKEND_LLDB}" -ne 1 ]; then # Find system Python PYTHON=$(command -v python) PYVER=$(${PYTHON} -c 'import platform; print(".".join(platform.python_version_tuple()[:2]))') if [ -z $USER_MODE ]; then PYTHON_SITE_PACKAGES=$(${PYTHON} -c 'import site; print(site.getsitepackages()[0])') else PYTHON_SITE_PACKAGES=$(${PYTHON} -c 'import site; print(site.getusersitepackages())') fi install_packages # Install Voltron and dependencies ${SUDO} ${PYTHON} -m pip install -U $USER_MODE $DEV_MODE . fi set +x echo "==============================================================" if [ "${BACKEND_GDB}" -eq 1 ]; then echo "Installed for GDB (${GDB}):" echo " Python: $GDB_PYTHON" echo " Packages directory: $GDB_SITE_PACKAGES" echo " Added voltron to: $GDB_INIT_FILE" echo " Entry point: $GDB_ENTRY_FILE" fi if [ "${BACKEND_LLDB}" -eq 1 ]; then echo "Installed for LLDB (${LLDB}):" echo " Python: $LLDB_PYTHON" echo " Packages directory: $LLDB_SITE_PACKAGES" echo " Added voltron to: $LLDB_INIT_FILE" echo " Entry point: $LLDB_ENTRY_FILE" fi if [ "${BACKEND_GDB}" -ne 1 ] && [ "${BACKEND_LLDB}" -ne 1 ]; then if [ -z "${GDB}" ] && [ -z "${LLDB}" ]; then echo -n "Couldn't find any debuggers. " else echo -n "No debuggers selected. " fi echo "Installed using the Python in your path:" echo " Python: $PYTHON" echo " Packages directory: $PYTHON_SITE_PACKAGES" echo " Did not add Voltron to any debugger init files." fi voltron-master/setup.cfg000066400000000000000000000000321360575504700157020ustar00rootroot00000000000000[bdist_wheel] universal=1 voltron-master/setup.py000077500000000000000000000071361360575504700156120ustar00rootroot00000000000000from __future__ import print_function import sys import platform import textwrap from subprocess import check_output from setuptools import setup, find_packages def check_install(): """ Try to detect the two most common installation errors: 1. Installing on macOS using a Homebrew version of Python 2. Installing on Linux using Python 2 when GDB is linked with Python 3 """ if platform.system() == 'Darwin' and sys.executable != '/usr/bin/python': print("*" * 79) print(textwrap.fill( "WARNING: You are not using the version of Python included with " "macOS. If you intend to use Voltron with the LLDB included " "with Xcode, or GDB installed with Homebrew, it will not work " "unless it is installed using the system's default Python. If " "you intend to use Voltron with a debugger installed by some " "other method, it may be safe to ignore this warning. See the " "following documentation for more detailed installation " "instructions: " "https://github.com/snare/voltron/wiki/Installation", 79)) print("*" * 79) elif platform.system() == 'Linux': try: output = check_output([ "gdb", "-batch", "-q", "--nx", "-ex", "pi print(sys.version_info.major)" ]).decode("utf-8") gdb_python = int(output) if gdb_python != sys.version_info.major: print("*" * 79) print(textwrap.fill( "WARNING: You are installing Voltron using Python {0}.x " "and GDB is linked with Python {1}.x. GDB will not be " "able to load Voltron. Please install using Python {1} " "if you intend to use Voltron with the copy of GDB that " "is installed. See the following documentation for more " "detailed installation instructions: " "https://github.com/snare/voltron/wiki/Installation" .format(sys.version_info.major, gdb_python), 79)) print("*" * 79) except: pass check_install() requirements = [ 'scruffington>=0.3.6', 'flask', 'flask_restful', 'blessed', 'pygments', 'requests', 'requests_unixsocket', 'six', 'pysigset', 'pygments', ] if sys.platform == 'win32': requirements.append('cursor') setup( name="voltron", version="0.1.7", author="snare", author_email="snare@ho.ax", description="A debugger UI", license="MIT", keywords="voltron debugger ui gdb lldb vdb " "vivisect vtrace windbg cdb pykd", url="https://github.com/snare/voltron", packages=find_packages(exclude=['tests', 'examples']), install_requires=requirements, package_data={'voltron': ['config/*']}, entry_points={ 'console_scripts': ['voltron=voltron:main'], 'pygments.lexers': [ 'lldb_intel = voltron.lexers:LLDBIntelLexer', 'lldb_att = voltron.lexers:LLDBATTLexer', 'gdb_intel = voltron.lexers:GDBIntelLexer', 'gdb_att = voltron.lexers:GDBATTLexer', 'vdb_intel = voltron.lexers:VDBIntelLexer', 'vdb_att = voltron.lexers:VDBATTLexer', 'windbg_intel = voltron.lexers:WinDbgIntelLexer', 'windbg_att = voltron.lexers:WinDbgATTLexer', 'capstone_intel = voltron.lexers:CapstoneIntelLexer', ], 'pygments.styles': [ 'volarized = voltron.styles:VolarizedStyle', ] }, zip_safe=False ) voltron-master/tests/000077500000000000000000000000001360575504700152305ustar00rootroot00000000000000voltron-master/tests/Vagrantfile000066400000000000000000000014641360575504700174220ustar00rootroot00000000000000Vagrant.configure(2) do |config| config.vm.hostname = "voltron" config.vm.box = "ubuntu/trusty64" config.vm.provider :virtualbox do |v| v.memory = 1024 v.linked_clone = true end config.vm.network "forwarded_port", guest: 5555, host: 5556 config.vm.synced_folder "../", "/voltron" config.vm.synced_folder "~/shared", "/shared" config.vm.provision "shell", inline: <<-SHELL apt-get update apt-get upgrade -y apt-get install -y \ build-essential \ gdb \ zsh \ python3-pip \ lldb-3.8 pip3 install six --upgrade su - vagrant -c "cd /voltron ; ./install.sh" mkdir -p /home/vagrant/.voltron chown vagrant:vagrant /home/vagrant/.voltron -R cat >/home/vagrant/.voltron/config < Server -> LLDBAdaptor Using an instantiated SBDebugger instance """ import tempfile import sys import json import time import logging import subprocess import time from mock import Mock from nose.tools import * import voltron from voltron.core import * from voltron.api import * from voltron.plugin import * import platform if platform.system() == 'Darwin': sys.path.append("/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python") try: import lldb from .common import * log = logging.getLogger("tests") def setup(): global server, client, target, pm, adaptor, methods log.info("setting up API tests") # set up voltron voltron.setup_env() pm = PluginManager() plugin = pm.debugger_plugin_for_host('lldb') adaptor = plugin.adaptor_class() voltron.debugger = adaptor # start up a voltron server server = Server() # import pdb;pdb.set_trace() server.start() time.sleep(0.1) # set up client client = Client() # compile and load the test inferior subprocess.call("cc -o tests/inferior tests/inferior.c", shell=True) target = adaptor.host.CreateTargetWithFileAndArch("tests/inferior", lldb.LLDB_ARCH_DEFAULT) main_bp = target.BreakpointCreateByName("main", target.GetExecutable().GetFilename()) def teardown(): server.stop() time.sleep(5) def test_state_no_target(): req = api_request('state') res = client.send_request(req) assert res.is_error assert res.code == 4101 def test_state_stopped(): process = target.LaunchSimple(None, None, os.getcwd()) req = api_request('state') res = client.send_request(req) assert res.status == 'success' assert res.state == "stopped" target.process.Destroy() def test_targets(): req = api_request('targets') res = client.send_request(req) assert res.status == 'success' t = res.targets[0] assert t["id"] == 0 assert t["arch"] == "x86_64" assert t["file"].endswith("inferior") def test_registers(): process = target.LaunchSimple(None, None, os.getcwd()) req = api_request('registers') res = client.send_request(req) assert res.status == 'success' assert len(res.registers) > 0 assert res.registers['rip'] != 0 target.process.Destroy() def test_memory(): process = target.LaunchSimple(None, None, os.getcwd()) res = client.perform_request('registers') rsp = res.registers['rsp'] res = client.perform_request('memory', address=rsp, length=0x40) assert res.status == 'success' assert len(res.memory) > 0 res = client.perform_request('memory', address=rsp, length=0x40, deref=True) assert res.status == 'success' assert len(res.deref) > 0 target.process.Destroy() def test_stack(): process = target.LaunchSimple(None, None, os.getcwd()) req = api_request('stack', length=0x40) res = client.send_request(req) assert res.status == 'success' assert len(res.memory) > 0 target.process.Destroy() def test_command(): process = target.LaunchSimple(None, None, os.getcwd()) req = api_request('command', command="reg read") res = client.send_request(req) assert res.status == 'success' assert len(res.output) > 0 assert 'rax' in res.output target.process.Destroy() def test_disassemble(): process = target.LaunchSimple(None, None, os.getcwd()) req = api_request('disassemble', count=16) res = client.send_request(req) assert res.status == 'success' assert len(res.disassembly) > 0 assert 'push' in res.disassembly req = api_request('disassemble', count=16, use_capstone=True) res = client.send_request(req) assert res.status == 'success' assert len(res.disassembly) > 0 assert 'push' in res.disassembly target.process.Destroy() def test_dereference(): process = target.LaunchSimple(None, None, os.getcwd()) res = client.perform_request('registers') res = client.perform_request('dereference', pointer=res.registers['rsp']) assert res.status == 'success' assert res.output[0][0] == 'pointer' assert res.output[-1][1] == 'start + 0x1' target.process.Destroy() def test_breakpoints(): process = target.LaunchSimple(None, None, os.getcwd()) res = client.perform_request('breakpoints') assert res.status == 'success' assert len(res.breakpoints) == 1 assert res.breakpoints[0]['one_shot'] == False assert res.breakpoints[0]['enabled'] assert res.breakpoints[0]['id'] == 1 assert res.breakpoints[0]['hit_count'] > 0 assert res.breakpoints[0]['locations'][0]['name'] == "inferior`main" target.process.Destroy() def test_multi_request(): process = target.LaunchSimple(None, None, os.getcwd()) reg_res, dis_res = client.send_requests(api_request('registers'), api_request('disassemble', count=16)) assert reg_res.status == 'success' assert len(reg_res.registers) > 0 assert reg_res.registers['rip'] != 0 assert dis_res.status == 'success' assert len(dis_res.disassembly) > 0 assert 'push' in dis_res.disassembly target.process.Destroy() except: print("No LLDB") voltron-master/tests/gdb_cli_tests.py000066400000000000000000000101531360575504700204070ustar00rootroot00000000000000""" Tests that test voltron in the gdb cli driver Tests: Client -> Server -> GDBAdaptor Inside a GDB instance """ from __future__ import print_function import tempfile import sys import json import time import logging import pexpect import os import six from mock import Mock from nose.tools import * import voltron from voltron.core import * from voltron.api import * from voltron.plugin import PluginManager, DebuggerAdaptorPlugin from .common import * log = logging.getLogger('tests') p = None client = None def setup(): global p, client, pm log.info("setting up GDB CLI tests") voltron.setup_env() # compile test inferior pexpect.run("cc -o tests/inferior tests/inferior.c") # start debugger start_debugger() def teardown(): read_data() p.terminate(True) def start_debugger(do_break=True): global p, client p = pexpect.spawn('gdb') p.sendline("python import sys;sys.path.append('/home/travis/virtualenv/python3.5.0/lib/python3.5/site-packages')") p.sendline("python import sys;sys.path.append('/home/travis/virtualenv/python3.4.3/lib/python3.4/site-packages')") p.sendline("python import sys;sys.path.append('/home/travis/virtualenv/python3.3.6/lib/python3.3/site-packages')") p.sendline("python import sys;sys.path.append('/home/travis/virtualenv/python2.7.10/lib/python2.7/site-packages')") p.sendline("source voltron/entry.py") p.sendline("file tests/inferior") p.sendline("set disassembly-flavor intel") p.sendline("voltron init") if do_break: p.sendline("b main") p.sendline("run loop") read_data() time.sleep(5) client = Client() def stop_debugger(): # p.sendline("kill") read_data() p.terminate(True) def read_data(): try: while True: data = p.read_nonblocking(size=64, timeout=1) print(data.decode('UTF-8'), end='') except: pass def restart_debugger(do_break=True): stop_debugger() start_debugger(do_break) def test_bad_request(): req = client.create_request('version') req.request = 'xxx' res = client.send_request(req) assert res.is_error assert res.code == 0x1002 def test_version(): req = client.create_request('version') res = client.send_request(req) assert res.api_version == 1.1 assert 'gdb' in res.host_version def test_registers(): global registers read_data() res = client.perform_request('registers') registers = res.registers assert res.status == 'success' assert len(registers) > 0 assert registers['rip'] != 0 def test_memory(): res = client.perform_request('memory', address=registers['rip'], length=0x40) assert res.status == 'success' assert len(res.memory) > 0 def test_state_stopped(): res = client.perform_request('state') assert res.is_success assert res.state == "stopped" def test_targets(): res = client.perform_request('targets') assert res.is_success assert res.targets[0]['state'] == "stopped" assert res.targets[0]['arch'] == "x86_64" assert res.targets[0]['id'] == 0 assert res.targets[0]['file'].endswith('tests/inferior') def test_stack(): res = client.perform_request('stack', length=0x40) assert res.status == 'success' assert len(res.memory) > 0 def test_command(): res = client.perform_request('command', command="info reg") assert res.status == 'success' assert len(res.output) > 0 assert 'rax' in res.output def test_disassemble(): res = client.perform_request('disassemble', count=0x20) assert res.status == 'success' assert len(res.disassembly) > 0 assert 'DWORD' in res.disassembly def test_backtrace(): res = client.perform_request('backtrace') print(res) assert res.is_success assert res.frames[0]['name'] == "main" assert res.frames[0]['index'] == 0 # def test_write_memory(): # value = six.b("AAAAAAAA") # res = client.perform_request('write_memory', address=registers['rsp'], value=value) # assert res.is_success # res = client.perform_request('memory', address=registers['rsp'], length=len(value)) # assert res.memory == value voltron-master/tests/http_api_tests.py000066400000000000000000000101451360575504700206350ustar00rootroot00000000000000""" Tests that emulate the debugger adaptor and just test the interaction between the front end and back end API classes. HTTP edition! Tests: Server (via HTTP) """ import logging import sys import json import time import subprocess from nose.tools import * import voltron from voltron.core import * from voltron.api import * from voltron.plugin import * import platform if platform.system() == 'Darwin': sys.path.append("/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python") from .common import * import requests log = logging.getLogger('tests') class APIHostNotSupportedRequest(APIRequest): @server_side def dispatch(self): return APIDebuggerHostNotSupportedErrorResponse() class APIHostNotSupportedPlugin(APIPlugin): request = "host_not_supported" request_class = APIHostNotSupportedRequest response_class = APIResponse def setup(): global server, client, target, pm, adaptor, methods log.info("setting up API tests") # set up voltron voltron.setup_env() voltron.config['server'] = { "listen": { "tcp": ["127.0.0.1", 5555] } } pm = PluginManager() plugin = pm.debugger_plugin_for_host('mock') adaptor = plugin.adaptor_class() voltron.debugger = adaptor # inject mock methods inject_mock(adaptor) # start up a voltron server server = Server() server.start() time.sleep(2) def teardown(): server.stop() time.sleep(2) def test_disassemble(): data = requests.get('http://localhost:5555/api/disassemble?count=16').text res = APIResponse(data=data) assert res.is_success assert res.disassembly == disassemble_response def test_command(): data = requests.get('http://localhost:5555/api/command?command=reg%20read').text res = APIResponse(data=data) assert res.is_success assert res.output == command_response def test_targets(): data = requests.get('http://localhost:5555/api/targets').text res = api_response('targets', data=data) assert res.is_success assert res.targets == targets_response def test_memory(): data = requests.get('http://localhost:5555/api/registers').text res = api_response('registers', data=data) url = 'http://localhost:5555/api/memory?address={}&length=64'.format(res.registers['rip']) data = requests.get(url).text res = api_response('memory', data=data) assert res.is_success assert res.memory == memory_response def test_registers(): data = requests.get('http://localhost:5555/api/registers').text res = api_response('registers', data=data) assert res.is_success assert res.registers == registers_response def test_stack_length_missing(): data = requests.get('http://localhost:5555/api/stack').text res = APIErrorResponse(data=data) assert res.is_error assert res.message == 'length' def test_stack(): data = requests.get('http://localhost:5555/api/stack?length=64').text res = api_response('stack', data=data) assert res.is_success assert res.memory == stack_response def test_state(): data = requests.get('http://localhost:5555/api/state').text res = api_response('state', data=data) assert res.is_success assert res.state == state_response def test_version(): data = requests.get('http://localhost:5555/api/version').text res = api_response('version', data=data) assert res.is_success assert res.api_version == 1.1 assert res.host_version == 'lldb-something' def test_bad_json(): data = requests.post('http://localhost:5555/api/request', data='xxx').text res = APIResponse(data=data) assert res.is_error assert res.code == 0x1001 def test_bad_request(): data = requests.post('http://localhost:5555/api/request', data='{"type":"request","request":"no_such_request"}').text res = APIResponse(data=data) assert res.is_error assert res.code == 0x1002 def test_breakpoints(): data = requests.get('http://localhost:5555/api/breakpoints').text res = api_response('breakpoints', data=data) assert res.is_success assert res.breakpoints == breakpoints_response voltron-master/tests/inferior.c000066400000000000000000000016361360575504700172170ustar00rootroot00000000000000#include #include #include void test_function() { printf("*** test_function()\n"); } int main(int argc, char **argv) { int a = 0; int *b = NULL; int c=0,d=0,e=0; if (argc > 1 && strcmp(argv[1], "sleep") == 0) { printf("*** Sleeping for 5 seconds\n"); sleep(5); } else if (argc > 1 && strcmp(argv[1], "loop") == 0) { printf("*** Looping forever()\n"); while(1) { c++; d+=2; e+=3; } sleep(1); } else if (argc > 1 && strcmp(argv[1], "function") == 0) { printf("*** Calling test_function()\n"); test_function(); } else if (argc > 1 && strcmp(argv[1], "crash") == 0) { printf("*** Crashing\n"); a = *b; } else { printf("Usage: inferior < sleep | loop | function | crash >\n"); } return 0; } voltron-master/tests/lldb_api_tests.py000066400000000000000000000113241360575504700205730ustar00rootroot00000000000000""" Tests that exercise the LLDB backend directly by loading an inferior and then poking at it with the LLDBAdaptor class. Tests: LLDBAdaptor """ import tempfile import sys import json import time import logging import subprocess import threading from mock import Mock from nose.tools import * import voltron from voltron.core import * from voltron.api import * from voltron.plugin import PluginManager, DebuggerAdaptorPlugin import platform if platform.system() == 'Darwin': sys.path.append("/Applications/Xcode.app/Contents/SharedFrameworks/LLDB.framework/Resources/Python") try: import lldb from common import * voltron.setup_env() log = logging.getLogger('tests') def setup(): global adaptor, dbg, target log.info("setting up LLDB API tests") # create an LLDBAdaptor pm = PluginManager() plugin = pm.debugger_plugin_for_host('lldb') adaptor = plugin.adaptor_class() # compile and load the test inferior subprocess.call("cc -o tests/inferior tests/inferior.c", shell=True) target = adaptor.host.CreateTargetWithFileAndArch("tests/inferior", lldb.LLDB_ARCH_DEFAULT) main_bp = target.BreakpointCreateByName("main", target.GetExecutable().GetFilename()) def teardown(): time.sleep(2) def test_version(): assert 'lldb' in adaptor.version() def test_state_invalid(): try: adaptor.state() exception = False except NoSuchTargetException: exception = True except: exception = False assert exception def test_targets_not_running(): t = adaptor.targets()[0] assert t["state"] == "invalid" assert t["arch"] == "x86_64" assert t["id"] == 0 assert len(t["file"]) > 0 assert 'inferior' in t["file"] def test_targets_stopped(): process = target.LaunchSimple(None, None, os.getcwd()) t = adaptor.targets()[0] assert t["state"] == "stopped" process.Destroy() def test_registers(): process = target.LaunchSimple(None, None, os.getcwd()) regs = adaptor.registers() assert regs is not None assert len(regs) > 0 assert regs['rip'] != 0 process.Destroy() def test_stack_pointer(): process = target.LaunchSimple(None, None, os.getcwd()) sp = adaptor.stack_pointer() assert sp != 0 process.Destroy() def test_program_counter(): process = target.LaunchSimple(None, None, os.getcwd()) pc_name, pc = adaptor.program_counter() assert pc != 0 process.Destroy() def test_memory(): process = target.LaunchSimple(None, None, os.getcwd()) regs = adaptor.registers() mem = adaptor.memory(address=regs['rip'], length=0x40) assert len(mem) == 0x40 process.Destroy() def test_stack(): process = target.LaunchSimple(None, None, os.getcwd()) stack = adaptor.stack(length=0x40) assert len(stack) == 0x40 process.Destroy() def test_disassemble(): process = target.LaunchSimple(None, None, os.getcwd()) output = adaptor.disassemble(count=0x20) assert len(output) > 0 process.Destroy() def test_command(): process = target.LaunchSimple(None, None, os.getcwd()) output = adaptor.command("reg read") assert len(output) > 0 assert 'rax' in output process.Destroy() def test_dereference_main(): process = target.LaunchSimple(None, None, os.getcwd()) regs = adaptor.registers() output = adaptor.dereference(regs['rip']) assert ('symbol', 'main + 0x0') in output process.Destroy() def test_dereference_rsp(): process = target.LaunchSimple(None, None, os.getcwd()) regs = adaptor.registers() output = adaptor.dereference(regs['rsp']) assert ('symbol', 'start + 0x1') in output process.Destroy() def test_dereference_string(): process = target.LaunchSimple(None, None, os.getcwd()) regs = adaptor.registers() output = adaptor.dereference(regs['rsp'] + 0x20) assert 'inferior' in list(output[-1])[-1] process.Destroy() def test_breakpoints(): process = target.LaunchSimple(None, None, os.getcwd()) bps = adaptor.breakpoints() assert len(bps) == 1 assert bps[0]['one_shot'] == False assert bps[0]['enabled'] assert bps[0]['id'] == 1 assert bps[0]['hit_count'] > 0 assert bps[0]['locations'][0]['name'] == "inferior`main" process.Destroy() def test_capabilities(): assert adaptor.capabilities() == ['async'] except: print("No LLDB")voltron-master/tests/lldb_cli_tests.py000066400000000000000000000145201360575504700205720ustar00rootroot00000000000000""" Tests that test voltron in the lldb cli driver Tests: Client -> Server -> LLDBAdaptor Inside an LLDB CLI driver instance """ from __future__ import print_function import tempfile import sys import json import time import logging import pexpect import os import tempfile import six from mock import Mock from nose.tools import * import voltron from voltron.core import * from voltron.api import * from voltron.plugin import PluginManager, DebuggerAdaptorPlugin from .common import * log = logging.getLogger('tests') p = None client = None def setup(): global p, client, pm log.info("setting up LLDB CLI tests") voltron.setup_env() # compile test inferior pexpect.run("cc -o tests/inferior tests/inferior.c") # start debugger start_debugger() time.sleep(10) def teardown(): read_data() p.terminate(True) time.sleep(2) def start_debugger(do_break=True): global p, client if sys.platform == 'darwin': p = pexpect.spawn('lldb') else: p = pexpect.spawn('lldb-3.4') # for travis (f, tmpname) = tempfile.mkstemp('.py') os.write(f, six.b('\n'.join([ "import sys", "sys.path.append('/home/travis/virtualenv/python3.5.0/lib/python3.5/site-packages')", "sys.path.append('/home/travis/virtualenv/python3.4.3/lib/python3.4/site-packages')", "sys.path.append('/home/travis/virtualenv/python3.3.6/lib/python3.3/site-packages')", "sys.path.append('/home/travis/virtualenv/python2.7.10/lib/python2.7/site-packages')"]))) p.sendline("command script import {}".format(tmpname)) print("pid == {}".format(p.pid)) p.sendline("settings set target.x86-disassembly-flavor intel") p.sendline("command script import voltron/entry.py") time.sleep(2) p.sendline("file tests/inferior") time.sleep(2) p.sendline("voltron init") time.sleep(1) p.sendline("process kill") p.sendline("break delete 1") if do_break: p.sendline("b main") p.sendline("run loop") read_data() client = Client() def stop_debugger(): p.terminate(True) def read_data(): try: while True: data = p.read_nonblocking(size=64, timeout=1) print(data.decode('UTF-8'), end='') except: pass def restart(do_break=True): # stop_debugger() # start_debugger(do_break) p.sendline("process kill") p.sendline("break delete -f") if do_break: p.sendline("b main") p.sendline("run loop") def test_bad_request(): req = client.create_request('version') req.request = 'xxx' res = client.send_request(req) assert res.is_error assert res.code == 0x1002 time.sleep(2) def test_version(): req = client.create_request('version') res = client.send_request(req) assert res.api_version == 1.1 assert 'lldb' in res.host_version def test_registers(): global registers restart() time.sleep(1) read_data() res = client.perform_request('registers') registers = res.registers assert res.status == 'success' assert len(registers) > 0 if 'rip' in registers: assert registers['rip'] != 0 else: assert registers['eip'] != 0 def test_memory(): restart() time.sleep(1) res = client.perform_request('memory', address=registers['rip'], length=0x40) assert res.status == 'success' assert len(res.memory) > 0 def test_state_stopped(): restart() time.sleep(1) res = client.perform_request('state') assert res.is_success assert res.state == "stopped" def test_targets(): restart() time.sleep(1) res = client.perform_request('targets') assert res.is_success assert res.targets[0]['state'] == "stopped" assert res.targets[0]['arch'] == "x86_64" assert res.targets[0]['id'] == 0 assert res.targets[0]['file'].endswith('tests/inferior') def test_stack(): restart() time.sleep(1) res = client.perform_request('stack', length=0x40) assert res.status == 'success' assert len(res.memory) > 0 def test_command(): restart() time.sleep(1) res = client.perform_request('command', command="reg read") assert res.status == 'success' assert len(res.output) > 0 assert 'rax' in res.output def test_disassemble(): restart() time.sleep(1) res = client.perform_request('disassemble', count=0x20) assert res.status == 'success' assert len(res.disassembly) > 0 assert 'push' in res.disassembly def test_dereference(): restart() time.sleep(1) res = client.perform_request('registers') res = client.perform_request('dereference', pointer=res.registers['rsp']) assert res.status == 'success' assert res.output[0][0] == 'pointer' assert 'start' in res.output[-1][1] or 'main' in res.output[-1][1] def test_breakpoints(): restart(True) time.sleep(1) res = client.perform_request('breakpoints') assert res.status == 'success' # assert len(res.breakpoints) == 1 assert res.breakpoints[0]['one_shot'] == False assert res.breakpoints[0]['enabled'] # assert res.breakpoints[0]['id'] == 1 assert res.breakpoints[0]['hit_count'] > 0 assert res.breakpoints[0]['locations'][0]['name'] == "inferior`main" # def test_multi(): # global r1, r2 # restart(True) # time.sleep(1) # r1, r2 = None, None # def send_req(): # global r1, r2 # r1, r2 = client.send_requests(api_request('targets', block=True), api_request('registers', block=True)) # print "sent requests" # t = threading.Thread(target=send_req) # t.start() # time.sleep(5) # p.sendline("stepi") # time.sleep(5) # t.join() # print r1 # print r2 # assert r1.is_success # assert r1.targets[0]['state'] == "stopped" # assert r1.targets[0]['arch'] == "x86_64" # assert r1.targets[0]['id'] == 0 # assert r1.targets[0]['file'].endswith('tests/inferior') # assert r2.status == 'success' # assert len(r2.registers) > 0 # assert r2.registers['rip'] != 0 def test_capabilities(): restart(True) res = client.perform_request('version') assert res.capabilities == ['async'] def test_backtrace(): restart(True) time.sleep(1) res = client.perform_request('backtrace') print(res) assert res.frames[0]['name'] == "inferior`main + 0" assert res.frames[0]['index'] == 0 voltron-master/tests/test.c000066400000000000000000000001141360575504700163470ustar00rootroot00000000000000#include int main(void) { printf("hello\n"); return 0; }voltron-master/tests/testinit.lldb000066400000000000000000000000621360575504700177300ustar00rootroot00000000000000file tests/inferior voltron init b main run loop voltron-master/voltron/000077500000000000000000000000001360575504700155715ustar00rootroot00000000000000voltron-master/voltron/__init__.py000066400000000000000000000047731360575504700177150ustar00rootroot00000000000000import os import logging import logging.config import voltron from .main import main from scruffy import Environment, Directory, File, ConfigFile, PluginDirectory, PackageDirectory # scruffy environment containing config, plugins, etc env = None config = None debugger = None command = None commands = None server = None loaded = False def setup_env(): global env, config env = Environment(setup_logging=False, voltron_dir=Directory('~/.voltron', create=True, config=ConfigFile('config', defaults=File('config/default.cfg', parent=PackageDirectory()), apply_env=True), sock=File('{config:server.listen.domain}'), history=File('history'), user_plugins=PluginDirectory('plugins') ), pkg_plugins=PluginDirectory('plugins', parent=PackageDirectory()) ) config = env.config voltron.plugin.pm.register_plugins() LOGGER_DEFAULT = { 'handlers': ['null'], 'level': 'DEBUG', 'propagate': False } LOG_CONFIG = { 'version': 1, 'formatters': { 'standard': {'format': 'voltron: [%(levelname)s] %(message)s'} }, 'handlers': { 'default': { 'class': 'logging.StreamHandler', 'formatter': 'standard' }, 'null': { 'class': 'logging.NullHandler' } }, 'loggers': { '': LOGGER_DEFAULT, 'debugger': LOGGER_DEFAULT, 'core': LOGGER_DEFAULT, 'main': LOGGER_DEFAULT, 'api': LOGGER_DEFAULT, 'view': LOGGER_DEFAULT, 'plugin': LOGGER_DEFAULT, } } def setup_logging(logname=None): # configure logging logging.config.dictConfig(LOG_CONFIG) # enable the debug_file in all the loggers if the config says to if config and 'general' in config and config['general']['debug_logging']: if logname: filename = '{}.log'.format(logname) else: filename = 'voltron.log' for name in LOG_CONFIG['loggers']: h = logging.FileHandler(voltron.env.voltron_dir.path_to(filename), delay=True) h.setFormatter(logging.Formatter(fmt="%(asctime)s %(levelname)-7s %(filename)12s:%(lineno)-4s %(funcName)20s -- %(message)s")) logging.getLogger(name).addHandler(h) logging.info("======= VOLTRON - DEFENDER OF THE UNIVERSE [debug log] =======") return logging.getLogger(logname) # Python 3 shim if not hasattr(__builtins__, "xrange"): xrange = range # Setup the Voltron environment setup_env() voltron-master/voltron/__main__.py000066400000000000000000000000371360575504700176630ustar00rootroot00000000000000from .main import main main() voltron-master/voltron/api.py000066400000000000000000000254301360575504700167200ustar00rootroot00000000000000import os import logging import socket import select import threading import logging import logging.config import json import inspect import base64 import six from collections import defaultdict from scruffy.plugin import Plugin import voltron from .plugin import APIPlugin log = logging.getLogger('api') version = 1.1 class InvalidRequestTypeException(Exception): """ Exception raised when the client is requested to send an invalid request type. """ pass class InvalidDebuggerHostException(Exception): """ Exception raised when the debugger host is invalid. """ pass class InvalidViewNameException(Exception): """ Exception raised when an invalid view name is specified. """ pass class InvalidMessageException(Exception): """ Exception raised when an invalid API message is received. """ pass class ServerSideOnlyException(Exception): """ Exception raised when a server-side method is called on an APIMessage subclass that exists on the client-side. See @server_side decorator. """ pass class ClientSideOnlyException(Exception): """ Exception raised when a server-side method is called on an APIMessage subclass that exists on the client-side. See @client_side decorator. """ pass class DebuggerNotPresentException(Exception): """ Raised when an APIRequest is dispatched without a valid debugger present. """ pass class NoSuchTargetException(Exception): """ Raised when an APIRequest specifies an invalid target. """ pass class TargetBusyException(Exception): """ Raised when an APIRequest specifies a target that is currently busy and cannot be queried. """ pass class MissingFieldError(Exception): """ Raised when an APIMessage is validated and has a required field missing. """ pass class NoSuchThreadException(Exception): """ Raised when the specified thread ID or index does not exist. """ pass class UnknownArchitectureException(Exception): """ Raised when the debugger host is running in an unknown architecture. """ pass class BlockingNotSupportedError(Exception): """ Raised when a view that does not support blocking connects to a debugger host that does not support async mode. """ pass def server_side(func): """ Decorator to designate an API method applicable only to server-side instances. This allows us to use the same APIRequest and APIResponse subclasses on the client and server sides without too much confusion. """ def inner(*args, **kwargs): if args and hasattr(args[0], 'is_server') and not voltron.debugger: raise ServerSideOnlyException("This method can only be called on a server-side instance") return func(*args, **kwargs) return inner def client_side(func): """ Decorator to designate an API method applicable only to client-side instances. This allows us to use the same APIRequest and APIResponse subclasses on the client and server sides without too much confusion. """ def inner(*args, **kwargs): if args and hasattr(args[0], 'is_server') and voltron.debugger: raise ClientSideOnlyException("This method can only be called on a client-side instance") return func(*args, **kwargs) return inner def cast_b(val): if isinstance(val, six.binary_type): return val elif isinstance(val, six.text_type): return val.encode('latin1') return six.binary_type(val) def cast_s(val): if type(val) == six.text_type: return val elif type(val) == six.binary_type: return val.decode('latin1') return six.text_type(val) class APIMessage(object): """ Top-level API message class. """ _top_fields = ['type'] _fields = {} _encode_fields = [] type = None def __init__(self, data=None, *args, **kwargs): # process any data that was passed in if data: self.from_json(data) # any other kwargs are treated as field values for field in kwargs: setattr(self, field, kwargs[field]) def __str__(self): """ Return a string (JSON) representation of the API message properties. """ return self.to_json() def to_dict(self): """ Return a transmission-safe dictionary representation of the API message properties. """ d = {field: getattr(self, field) for field in self._top_fields if hasattr(self, field)} # set values of data fields d['data'] = {} for field in self._fields: if hasattr(self, field): # base64 encode the field for transmission if necessary if field in self._encode_fields: val = getattr(self, field) if val: val = cast_s(base64.b64encode(cast_b(val))) d['data'][field] = val else: d['data'][field] = getattr(self, field) return d def from_dict(self, d): """ Initialise an API message from a transmission-safe dictionary. """ for key in d: if key == 'data': for dkey in d['data']: if dkey in self._encode_fields: setattr(self, str(dkey), base64.b64decode(d['data'][dkey])) else: setattr(self, str(dkey), d['data'][dkey]) else: setattr(self, str(key), d[key]) def to_json(self): """ Return a JSON representation of the API message properties. """ return json.dumps(self.to_dict()) def from_json(self, data): """ Initialise an API message from a JSON representation. """ try: d = json.loads(data) except ValueError: raise InvalidMessageException() self.from_dict(d) def __getattr__(self, name): """ Attribute accessor. If a defined field is requested that doesn't have a value set, return None. """ if name in self._fields: return None def validate(self): """ Validate the message. Ensure all the required fields are present and not None. """ required_fields = list(filter(lambda x: self._fields[x], self._fields.keys())) for field in (self._top_fields + required_fields): if not hasattr(self, field) or hasattr(self, field) and getattr(self, field) == None: raise MissingFieldError(field) class APIRequest(APIMessage): """ An API request object. Contains functions and accessors common to all API request types. Subclasses of APIRequest are used on both the client and server sides. On the server side they are instantiated by Server's `handle_request()` method. On the client side they are instantiated by whatever class is doing the requesting (probably a view class). """ _top_fields = ['type', 'request', 'block', 'timeout'] _fields = {} type = 'request' request = None block = False timeout = 10 response = None wait_event = None timed_out = False @server_side def dispatch(self): """ In concrete subclasses this method will actually dispatch the request to the debugger host and return a response. In this case it raises an exception. """ raise NotImplementedError("Subclass APIRequest") @server_side def wait(self): """ Wait for the request to be dispatched. """ self.wait_event = threading.Event() timeout = int(self.timeout) if self.timeout else None self.timed_out = not self.wait_event.wait(timeout) def signal(self): """ Signal that the request has been dispatched and can return. """ self.wait_event.set() class APIBlockingRequest(APIRequest): """ An API request that blocks by default. """ block = True class APIResponse(APIMessage): """ An API response object. Contains functions and accessors common to all API response types. Subclasses of APIResponse are used on both the client and server sides. On the server side they are instantiated by the APIRequest's `dispatch` method in order to serialise and send to the client. On the client side they are instantiated by the Client class and returned by `send_request`. """ _top_fields = ['type', 'status'] _fields = {} type = 'response' status = None @property def is_success(self): return self.status == 'success' @property def is_error(self): return self.status == 'error' def __repr__(self): return "<%s: success = %s, error = %s, body: %s>" % ( str(self.__class__), self.is_success, self.is_error, {f: getattr(self, f) for f in self._top_fields + list(self._fields.keys())} ) class APISuccessResponse(APIResponse): """ A generic API success response. """ status = 'success' class APIErrorResponse(APIResponse): """ A generic API error response. """ _fields = {'code': True, 'message': True} status = 'error' @property def timed_out(self): return self.code == APITimedOutErrorResponse.code class APIGenericErrorResponse(APIErrorResponse): code = 0x1000 message = "An error occurred" def __init__(self, message=None): super(APIGenericErrorResponse, self).__init__() if message: self.message = message class APIInvalidRequestErrorResponse(APIErrorResponse): code = 0x1001 message = "Invalid API request" class APIPluginNotFoundErrorResponse(APIErrorResponse): code = 0x1002 message = "Plugin was not found for request" class APIDebuggerHostNotSupportedErrorResponse(APIErrorResponse): code = 0x1003 message = "The targeted debugger host is not supported by this plugin" class APITimedOutErrorResponse(APIErrorResponse): code = 0x1004 message = "The request timed out" class APIDebuggerNotPresentErrorResponse(APIErrorResponse): code = 0x1004 message = "No debugger host was found" class APINoSuchTargetErrorResponse(APIErrorResponse): code = 0x1005 message = "No such target" class APITargetBusyErrorResponse(APIErrorResponse): code = 0x1006 message = "Target busy" class APIMissingFieldErrorResponse(APIGenericErrorResponse): code = 0x1007 message = "Missing field" class APIEmptyResponseErrorResponse(APIGenericErrorResponse): code = 0x1008 message = "Empty response" class APIServerNotRunningErrorResponse(APIGenericErrorResponse): code = 0x1009 message = "Server is not running" voltron-master/voltron/colour.py000066400000000000000000000015211360575504700174450ustar00rootroot00000000000000ESCAPES = { # reset 'reset': 0, # colours 'grey': 30, 'red': 31, 'green': 32, 'yellow': 33, 'blue': 34, 'magenta': 35, 'cyan': 36, 'white': 37, # background 'b_grey': 40, 'b_red': 41, 'b_green': 42, 'b_yellow': 43, 'b_blue': 44, 'b_magenta': 45, 'b_cyan': 46, 'b_white': 47, # attributes 'a_bold': 1, 'a_dark': 2, 'a_underline': 4, 'a_blink': 5, 'a_reverse': 7, 'a_concealed': 8 } ESC_TEMPLATE = '\033[{}m' def escapes(): return ESCAPES def get_esc(name): return ESCAPES[name] def fmt_esc(name): return ESC_TEMPLATE.format(escapes()[name]) FMT_ESCAPES = dict((k, fmt_esc(k)) for k in ESCAPES) voltron-master/voltron/config/000077500000000000000000000000001360575504700170365ustar00rootroot00000000000000voltron-master/voltron/config/default.cfg000066400000000000000000000106001360575504700211400ustar00rootroot00000000000000general: debug_logging: false server: listen: domain: "~/.voltron/sock" tcp: - 127.0.0.1 - 5555 view: #api_url: "http+unix://~%2f.voltron%2fsock/api/request", api_url: http://localhost:5555/api/request reconnect: true all_views: clear: true update_on: stop format: pygments_style: volarized pygments_formatter: terminal256 header: show: true pad: " " colour: blue bg_colour: grey attrs: [] label_left: name: info colour: blue bg_colour: grey attrs: [] label_right: name: title colour: white bg_colour: grey attrs: - bold footer: show: false pad: " " colour: blue bg_colour: grey attrs: [] label_left: name: colour: blue bg_colour: grey attrs: [] label_right: name: colour: blue bg_colour: grey attrs: - bold pad: pad_right: 0 pad_bottom: 0 keymap: q: exit p: page_up n: page_down KEY_PPAGE: page_up KEY_NPAGE: page_down KEY_UP: line_up KEY_DOWN: line_down KEY_ENTER: reset register_view: format: label_format: "{0}" label_func: str_upper label_colour: green label_colour_en: true value_format: "{0:0=16X}" value_func: value_colour: reset value_colour_mod: red value_colour_en: true format_name: addr_colour: blue divider_colour: green string_colour: white symbol_colour: cyan sections: - general orientation: vertical disassembly_view: header: show: true label_left: name: title colour: white bg_colour: grey attrs: - bold label_right: name: stack_view: header: show: false footer: show: true label_left: name: title colour: white bg_colour: grey attrs: - bold label_right: name: info colour: blue bg_colour: grey attrs: [] format: addr_format: "{0:0=16X}" memory_view: header: show: false footer: show: true label_left: name: title colour: white bg_colour: grey attrs: - bold label_right: name: info colour: blue bg_colour: grey attrs: [] format: addr_format: "{0:0=16X}" backtrace_view: header: show: false footer: show: true label_left: name: title colour: white bg_colour: grey attrs: - bold label_right: name: info colour: blue bg_colour: grey attrs: [] breakpoints_view: format: row: "{disabled}{one_shot}{t.bold}{id}{t.normal} {hit}{t.blue}0x{address:0=16X}{t.normal} {t.green}h:{t.normal}{hit_count:<4} {t.cyan}{name}{t.normal}" disabled: "{t.red}" one_shot: "{t.underline}" hit: "{t.standout}" some_named_stack_view: header: show: true label_left: name: title colour: red bg_colour: grey attrs: - bold label_right: name: info colour: white bg_colour: grey attrs: [] footer: show: false console: prompt: format: "{red}voltron>{reset} " voltron-master/voltron/core.py000066400000000000000000000454741360575504700171110ustar00rootroot00000000000000import errno import json import logging import logging.config import os import os.path import pkgutil import select import signal import socket import sys import threading import time import six import voltron from flask import Flask, Response, make_response, redirect, render_template, request from werkzeug.serving import BaseWSGIServer, ThreadedWSGIServer, WSGIRequestHandler from werkzeug.wsgi import DispatcherMiddleware, SharedDataMiddleware from requests import ConnectionError # import pysigset from .api import * from .plugin import * try: import requests_unixsocket as requests except: import requests if six.PY2: if sys.platform == 'win32': from SocketServer import ThreadingMixIn else: from SocketServer import UnixStreamServer, ThreadingMixIn from BaseHTTPServer import HTTPServer else: if sys.platform == 'win32': from six.moves.socketserver import ThreadingMixIn else: from six.moves.socketserver import UnixStreamServer, ThreadingMixIn from six.moves.BaseHTTPServer import HTTPServer ThreadingMixIn.daemon_threads = True try: from voltron_web import app as ui_app except: ui_app = None logging.getLogger("requests").setLevel(logging.WARNING) def get_loader(name): try: return orig_get_loader(name) except AttributeError: pass orig_get_loader = pkgutil.get_loader pkgutil.get_loader = get_loader # make sure we use HTTP 1.1 for keep-alive WSGIRequestHandler.protocol_version = "HTTP/1.1" log = logging.getLogger("core") if sys.version_info.major == 2: STRTYPES = (str, unicode) elif sys.version_info.major == 3: STRTYPES = (str, bytes) else: raise RuntimeError("Not sure what strings look like on python %d" % sys.version_info.major) class APIFlaskApp(Flask): """ A Flask app for the API. """ def __init__(self, *args, **kwargs): if 'server' in kwargs: self.server = kwargs['server'] del kwargs['server'] super(APIFlaskApp, self).__init__('voltron_api', *args, **kwargs) def api_post(): res = self.server.handle_request(request.data.decode('UTF-8')) return Response(str(res), status=200, mimetype='application/json') def api_get(): res = self.server.handle_request(str(api_request(request.path.split('/')[-1], **request.args.to_dict()))) return Response(str(res), status=200, mimetype='application/json') # Handle API POST requests at /api/request api_post.methods = ["POST"] self.add_url_rule('/request', 'request', api_post) # Handle API GET requests at /api/ e.g. /api/version for plugin in voltron.plugin.pm.api_plugins: self.add_url_rule('/{}'.format(plugin), plugin, api_get) class RootFlaskApp(Flask): """ A Flask app for /. """ def __init__(self, *args, **kwargs): super(RootFlaskApp, self).__init__('voltron', *args, **kwargs) def index(): if ui_app: return redirect('/ui') else: return "The Voltron web interface is not installed. Install the voltron-web package with pip." self.add_url_rule('/', 'index', index) class Server(object): """ Main server class instantiated by the debugger host. Responsible for controlling the background thread that communicates with clients, and handling requests forwarded from that thread. """ def __init__(self): self.threads = [] self.listeners = [] self.is_running = False self.queue = [] self.queue_lock = threading.Lock() def start(self): """ Start the server. """ plugins = voltron.plugin.pm.web_plugins self.app = DispatcherMiddleware( RootFlaskApp(), { "/api": APIFlaskApp(server=self), "/view": SharedDataMiddleware( None, {'/{}'.format(n): os.path.join(p._dir, 'static') for (n, p) in six.iteritems(plugins)} ), "/ui": ui_app } ) def run_listener(name, cls, arg): # with pysigset.suspended_signals(signal.SIGCHLD): log.debug("Starting listener for {} socket on {}".format(name, str(arg))) s = cls(*arg) t = threading.Thread(target=s.serve_forever) t.daemon = True t.start() self.threads.append(t) self.listeners.append(s) if voltron.config.server.listen.tcp: run_listener('tcp', ThreadedVoltronWSGIServer, list(voltron.config.server.listen.tcp) + [self.app]) if voltron.config.server.listen.domain and sys.platform != 'win32': path = os.path.expanduser(str(voltron.config.server.listen.domain)) try: os.unlink(path) except: pass run_listener('domain', ThreadedUnixWSGIServer, [path, self.app]) self.is_running = True def stop(self): """ Stop the server. """ log.debug("Stopping listeners") self.queue_lock.acquire() for s in self.listeners: log.debug("Stopping {}".format(s)) s.shutdown() s.socket.close() self.cancel_queue() for t in self.threads: t.join() self.listeners = [] self.threads = [] self.is_running = False self.queue_lock.release() log.debug("Listeners stopped and threads joined") def handle_request(self, data): req = None res = None if self.is_running: # make sure we have a debugger, or we're gonna have a bad time if voltron.debugger: # parse incoming request with the top level APIRequest class so we can determine the request type try: req = APIRequest(data=data) except Exception as e: req = None log.exception("Exception raised while parsing API request: {} {}".format(type(e), e)) if req: # instantiate the request class try: log.debug("data = {}".format(data)) req = api_request(req.request, data=data) except Exception as e: log.exception("Exception raised while creating API request: {} {}".format(type(e), e)) req = None if not req: res = APIPluginNotFoundErrorResponse() else: res = APIInvalidRequestErrorResponse() else: res = APIDebuggerNotPresentErrorResponse() if not res: # no errors so far, queue the request and wait if req and req.block: self.queue_lock.acquire() self.queue.append(req) self.queue_lock.release() # When this returns the request will have been processed by the dispatch_queue method on the main # thread (or timed out). We have to do it this way because GDB sucks. req.wait() if req.timed_out: res = APITimedOutErrorResponse() else: res = req.response # Remove the request from the queue self.queue_lock.acquire() if req in self.queue: self.queue.remove(req) self.queue_lock.release() else: # non-blocking, dispatch request straight away res = self.dispatch_request(req) else: res = APIServerNotRunningErrorResponse() return res def cancel_queue(self): """ Cancel all requests in the queue so we can exit. """ q = list(self.queue) self.queue = [] log.debug("Canceling requests: {}".format(q)) for req in q: req.response = APIServerNotRunningErrorResponse() for req in q: req.signal() def dispatch_queue(self): """ Dispatch any queued requests. Called by the debugger when it stops. """ self.queue_lock.acquire() q = list(self.queue) self.queue = [] self.queue_lock.release() log.debug("Dispatching requests: {}".format(q)) for req in q: req.response = self.dispatch_request(req) for req in q: req.signal() def dispatch_request(self, req): """ Dispatch a request object. """ log.debug("Dispatching request: {}".format(str(req))) # make sure it's valid res = None try: req.validate() except MissingFieldError as e: res = APIMissingFieldErrorResponse(str(e)) # dispatch the request if not res: try: res = req.dispatch() except Exception as e: msg = "Exception raised while dispatching request: {}".format(repr(e)) log.exception(msg) res = APIGenericErrorResponse(msg) log.debug("Response: {}".format(str(res))) return res class VoltronWSGIServer(BaseWSGIServer): """ Custom version of the werkzeug WSGI server. This just needs to exist so we can swallow errors when clients disconnect. """ clients = [] def finish_request(self, *args): self.clients.append(args[0]) log.debug("finish_request({})".format(args)) try: super(VoltronWSGIServer, self).finish_request(*args) except socket.error as e: log.error("Error in finish_request: {}".format(e)) def shutdown(self): super(VoltronWSGIServer, self).shutdown() for c in self.clients: try: c.shutdown(socket.SHUT_RD) c.close() except: pass class ThreadedVoltronWSGIServer(ThreadingMixIn, VoltronWSGIServer): """ Threaded WSGI server to replace werkzeug's """ pass if sys.platform != 'win32': class UnixWSGIServer(UnixStreamServer, VoltronWSGIServer): """ A subclass of BaseWSGIServer that does sane things with Unix domain sockets. """ def __init__(self, sockfile=None, app=None): self.address_family = socket.AF_UNIX UnixStreamServer.__init__(self, sockfile, UnixWSGIRequestHandler) self.app = app self.passthrough_errors = None self.shutdown_signal = False self.ssl_context = None self.host = 'localhost' self.port = 0 class UnixWSGIRequestHandler(WSGIRequestHandler): """ A WSGIRequestHandler that does sane things with Unix domain sockets. """ def make_environ(self, *args, **kwargs): self.client_address = ('127.0.0.1', 0) return super(UnixWSGIRequestHandler, self).make_environ(*args, **kwargs) class ThreadedUnixWSGIServer(ThreadingMixIn, UnixWSGIServer): """ Threaded HTTP server that works over Unix domain sockets. Note: this intentionally does not inherit from HTTPServer. Go look at the source and you'll see why. """ multithread = True class ClientThread(threading.Thread): """ A thread that performs an API request with a client. """ def __init__(self, client, request, *args, **kwargs): self.request = request self.response = None self.exception = None self.client = client super(ClientThread, self).__init__(*args, **kwargs) def run(self): try: self.response = self.client.send_request(self.request) except Exception as e: self.exception = e class Client(object): """ Used by a client (ie. a view) to communicate with the server. """ def __init__(self, host='127.0.0.1', port=5555, sockfile=None, url=None, build_requests=None, callback=None, supports_blocking=True): """ Initialise a new client """ self.session = requests.Session() if url: self.url = url elif sockfile: self.url = 'http+unix://{}/api/request'.format(sockfile.replace('/', '%2F')) elif voltron.config.view.api_url: self.url = voltron.config.view.api_url else: self.url = 'http://{}:{}/api/request'.format(host, port) self.url = self.url.replace('~', os.path.expanduser('~').replace('/', '%2f')) self.callback = callback self.build_requests = build_requests self.done = False self.server_version = None self.block = False self.supports_blocking = supports_blocking def send_request(self, request): """ Send a request to the server. `request` is an APIRequest subclass. Returns an APIResponse or subclass instance. If an error occurred, it will be an APIErrorResponse, if the request was successful it will be the plugin's specified response class if one exists, otherwise it will be an APIResponse. """ # default to an empty response error res = APIEmptyResponseErrorResponse() # perform the request log.debug("Client sending request: " + str(request)) response = self.session.post(self.url, data=str(request)) data = response.text if response.status_code != 200: res = APIGenericErrorResponse(response.text) elif data and len(data) > 0: log.debug('Client received message: ' + data) try: # parse the response data generic_response = APIResponse(data=data) # if there's an error, return an error response if generic_response.is_error: res = APIErrorResponse(data=data) else: # success; generate a proper response plugin = voltron.plugin.pm.api_plugin_for_request(request.request) if plugin and plugin.response_class: # found a plugin for the request we sent, use its response type res = plugin.response_class(data=data) else: # didn't find a plugin, just return the generic APIResponse we already generated res = generic_response except Exception as e: log.exception('Exception parsing message: ' + str(e)) log.error('Invalid message: ' + data) else: res = APIEmptyResponseErrorResponse() return res def send_requests(self, *args): """ Send a set of requests. Each request is sent over its own connection and the function will return when all the requests have been fulfilled. """ threads = [ClientThread(self, req) for req in args] for t in threads: t.start() for t in threads: t.join() exceptions = [t.exception for t in threads if t.exception] if len(exceptions): raise exceptions[0] return [t.response for t in threads] def create_request(self, request_type, *args, **kwargs): """ Create a request. `request_type` is the request type (string). This is used to look up a plugin, whose request class is instantiated and passed the remaining arguments passed to this function. """ return api_request(request_type, *args, **kwargs) def perform_request(self, request_type, *args, **kwargs): """ Create and send a request. `request_type` is the request type (string). This is used to look up a plugin, whose request class is instantiated and passed the remaining arguments passed to this function. """ # create a request req = api_request(request_type, *args, **kwargs) # send it res = self.send_request(req) return res def update(self): """ Update the display """ # build requests for this iteration reqs = self.build_requests() for r in reqs: r.block = self.block results = self.send_requests(*reqs) # call callback with the results self.callback(results) def run(self, build_requests=None, callback=None): """ Run the client in a loop, calling the callback each time the debugger stops. """ if callback: self.callback = callback if build_requests: self.build_requests = build_requests def normalise_requests_err(e): try: msg = e.message.args[1].strerror except: try: msg = e.message.args[0] except: msg = str(e) return msg while not self.done: try: # get the server version info if not self.server_version: self.server_version = self.perform_request('version') # if the server supports async mode, use it, as some views may only work in async mode if self.server_version.capabilities and 'async' in self.server_version.capabilities: self.update() self.block = False elif self.supports_blocking: self.block = True else: raise BlockingNotSupportedError("Debugger requires blocking mode") if self.block: # synchronous requests self.update() else: # async requests, block using a null request until the debugger stops again res = self.perform_request('null', block=True) if res.is_success: self.server_version = res self.update() except ConnectionError as e: self.callback(error='Error: {}'.format(normalise_requests_err(e))) self.server_version = None time.sleep(1) def start(self, build_requests=None, callback=None): """ Run the client using a background thread. """ if callback: self.callback = callback if build_requests: self.build_requests = build_requests # spin off requester thread self.sw = threading.Thread(target=self.run) self.sw.start() def stop(self): """ Stop the background thread. """ self.done = True voltron-master/voltron/dbg.py000066400000000000000000000174261360575504700167110ustar00rootroot00000000000000try: import capstone except: capstone = None from voltron.api import * from voltron.plugin import * class InvalidPointerError(Exception): """ Raised when attempting to dereference an invalid pointer. """ pass def validate_target(func, *args, **kwargs): """ A decorator that ensures that the specified target_id exists and is valid. Expects the target ID to be either the 'target_id' param in kwargs, or the first positional parameter. Raises a NoSuchTargetException if the target does not exist. """ def inner(self, *args, **kwargs): # find the target param target_id = None if 'target_id' in kwargs and kwargs['target_id'] != None: target_id = kwargs['target_id'] else: target_id = 0 # if there was a target specified, check that it's valid if not self.target_is_valid(target_id): raise NoSuchTargetException() # call the function return func(self, *args, **kwargs) return inner def validate_busy(func, *args, **kwargs): """ A decorator that raises an exception if the specified target is busy. Expects the target ID to be either the 'target_id' param in kwargs, or the first positional parameter. Raises a TargetBusyException if the target does not exist. """ def inner(self, *args, **kwargs): # find the target param target_id = None if 'target_id' in kwargs and kwargs['target_id'] != None: target_id = kwargs['target_id'] else: target_id = 0 # if there was a target specified, ensure it's not busy if self.target_is_busy(target_id): raise TargetBusyException() # call the function return func(self, *args, **kwargs) return inner def lock_host(func, *args, **kwargs): """ A decorator that acquires a lock before accessing the debugger to avoid API locking related errors with the debugger host. """ def inner(self, *args, **kwargs): self.host_lock.acquire() try: res = func(self, *args, **kwargs) self.host_lock.release() except Exception as e: self.host_lock.release() raise e return res return inner class DebuggerAdaptor(object): """ Base debugger adaptor class. Debugger adaptors implemented in plugins for specific debuggers inherit from this. """ reg_names = { "x86": {"pc": "eip", "sp": "esp"}, "x86_64": {"pc": "rip", "sp": "rsp"}, "arm": {"pc": "pc", "sp": "sp"}, "armv6": {"pc": "pc", "sp": "sp"}, "armv7": {"pc": "pc", "sp": "sp"}, "armv7s": {"pc": "pc", "sp": "sp"}, "arm64": {"pc": "pc", "sp": "sp"}, "powerpc": {"pc": "pc", "sp": "r1"}, } cs_archs = {} if capstone: cs_archs = { "x86": (capstone.CS_ARCH_X86, capstone.CS_MODE_32), "x86_64": (capstone.CS_ARCH_X86, capstone.CS_MODE_64), "arm": (capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM), "armv6": (capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM), "armv7": (capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM), "armv7s": (capstone.CS_ARCH_ARM, capstone.CS_MODE_ARM), "arm64": (capstone.CS_ARCH_ARM64, capstone.CS_MODE_ARM), "powerpc": (capstone.CS_ARCH_PPC, capstone.CS_MODE_32), } def __init__(self, *args, **kwargs): self.listeners = [] def target_exists(self, target_id=0): """ Returns True or False indicating whether or not the specified target is present and valid. `target_id` is a target ID (or None for the first target) """ try: target = self._target(target_id=target_id) except Exception as e: log.error("Exception checking if target exists: {} {}".format(type(e), e)) return False return target is not None def target_is_valid(self, target_id=0): """ Returns True or False indicating whether or not the specified target is present and valid. `target_id` is a target ID (or None for the first target) """ try: target = self._target(target_id=target_id) except: return False return target['state'] != "invalid" def target_is_busy(self, target_id=0): """ Returns True or False indicating whether or not the specified target is busy. `target_id` is a target ID (or None for the first target) """ try: target = self._target(target_id=target_id) except: raise NoSuchTargetException() return target['state'] == "running" def add_listener(self, callback, state_changes=["stopped"]): """ Add a listener for state changes. """ self.listeners.append({"callback": callback, "state_changes": state_changes}) def remove_listener(self, callback): """ Remove a listener. """ listeners = filter(lambda x: x['callback'] == callback, self.listeners) for l in listeners: self.listeners.remove(l) def update_state(self): """ Notify all the listeners (probably `wait` plugins) that the state has changed. This is called by the debugger's stop-hook. """ for listener in self.listeners: listener['callback']() def register_command_plugin(self, name, cls): pass def capabilities(self): """ Return a list of the debugger's capabilities. Thus far only the 'async' capability is supported. This indicates that the debugger host can be queried from a background thread, and that views can use non-blocking API requests without queueing requests to be dispatched next time the debugger stops. """ return [] def pc(self, target_id=0, thread_id=None): return self.program_counter(target_id, thread_id) def sp(self, target_id=0, thread_id=None): return self.stack_pointer(target_id, thread_id) def disassemble_capstone(self, target_id=0, address=None, count=None): """ Disassemble with capstone. """ target = self._target(target_id) if not address: pc_name, address = self.pc() mem = self.memory(address, count * 16, target_id=target_id) md = capstone.Cs(*self.cs_archs[target['arch']]) output = [] for idx, i in enumerate(md.disasm(mem, address)): if idx >= count: break output.append("0x%x:\t%s\t%s" % (i.address, i.mnemonic, i.op_str)) return '\n'.join(output) class DebuggerCommand (object): """ The `voltron` command in the debugger. """ def __init__(self, *args, **kwargs): super(DebuggerCommand, self).__init__(*args, **kwargs) self.adaptor = voltron.debugger self.registered = False def handle_command(self, command): global log if 'debug' in command: if 'enable' in command: log.setLevel(logging.DEBUG) print("Debug logging enabled") elif 'disable' in command: log.setLevel(logging.INFO) print("Debug logging disabled") else: enabled = "enabled" if log.getEffectiveLevel() == logging.DEBUG else "disabled" print("Debug logging is currently " + enabled) elif 'init' in command: self.register_hooks() elif 'stopped' in command or 'update' in command: self.adaptor.update_state() voltron.server.dispatch_queue() else: print("Usage: voltron ") voltron-master/voltron/entry.py000066400000000000000000000065151360575504700173130ustar00rootroot00000000000000""" This is the main entry point for Voltron from the debugger host's perspective. This file is loaded into the debugger through whatever means the given host supports. LLDB: (lldb) command script import /path/to/voltron/entry.py GDB: (gdb) source /path/to/voltron/entry.py VDB: (vdb) script /path/to/voltron/entry.py WinDbg/CDB (via PyKD): > .load pykd.pyd > !py --global C:\path\to\voltron\entry.py """ log = None try: # fix path if it's clobbered by brew import sys if sys.platform == 'darwin': py_base = '/System/Library/Frameworks/Python.framework/Versions/2.7/' new_path = ['lib/python27.zip', 'lib/python2.7', 'lib/python2.7/plat-darwin', 'lib/python2.7/plat-mac', 'lib/python2.7/plat-mac/lib-scriptpackages', 'Extras/lib/python', 'lib/python2.7/lib-tk', 'lib/python2.7/lib-old', 'lib/python2.7/lib-dynload'] sys.path = [p for p in sys.path if 'Cellar' not in p] + [py_base + p for p in new_path] except: pass try: import logging import os import sys blessed = None import blessed # add vtrace to the path so that dbg_vdb.py can import from vdb/vtrace. if "vtrace" in locals(): def parent_directory(the_path): return os.path.abspath(os.path.join(the_path, os.pardir)) def add_vdb_to_path(vtrace): sys.path.append(parent_directory(parent_directory(vtrace.__file__))) add_vdb_to_path(vtrace) else: pass import voltron from voltron.plugin import pm from voltron.core import Server log = voltron.setup_logging('debugger') # figure out in which debugger host we are running args = [] host = None try: import lldb host = "lldb" def invoke(*args): voltron.command._invoke(*args) except ImportError: pass try: import gdb host = "gdb" except ImportError: pass try: import pykd host = "windbg" except: pass if "vtrace" in locals(): host = "vdb" args = [db] if not host: raise Exception("No debugger host is present") # register any plugins that were loaded pm.register_plugins() # get the debugger plugin for the host we're in plugin = pm.debugger_plugin_for_host(host) if not voltron.server: # set up command and adaptor instances voltron.debugger = plugin.adaptor_class(*args) voltron.command = plugin.command_class(*args) # register command plugins now that we have a debugger host loaded pm.register_command_plugins() # create and start the voltron server voltron.server = Server() voltron.server.start() print(blessed.Terminal().bold_red("Voltron loaded.")) if host == 'lldb' and not voltron.command.registered: print("Run `voltron init` after you load a target.") except Exception as e: import traceback msg = ("An error occurred while loading Voltron:\n\n{}" "\nPlease ensure Voltron is installed correctly per the documentation: " "https://github.com/snare/voltron/wiki/Installation").format(traceback.format_exc()) if blessed: msg = blessed.Terminal().bold_red(msg) if log: log.exception("Exception raised while loading Voltron") print(msg) voltron-master/voltron/lexers.py000066400000000000000000000220131360575504700174430ustar00rootroot00000000000000import re from pygments.lexer import RegexLexer, include, bygroups, using, DelegatingLexer from pygments.lexers import get_lexer_by_name from pygments.token import * class DisassemblyLexer(RegexLexer): """ For Nasm (Intel) disassembly from LLDB. Based on the NasmLexer included with Pygments """ name = 'LLDB Intel syntax disassembly' aliases = ['lldb_intel'] filenames = [] mimetypes = [] identifier = r'[]*' hexn = r'(?:0[xX][0-9a-f]+|$0[0-9a-f]*|[0-9]+[0-9a-f]*h)' octn = r'[0-7]+q' binn = r'[01]+b' decn = r'[0-9]+' floatn = decn + r'\.e?' + decn string = r'"(\\"|[^"\n])*"|' + r"'(\\'|[^'\n])*'|" + r"`(\\`|[^`\n])*`" declkw = r'(?:res|d)[bwdqt]|times' register = (r'r[0-9]+[bwd]{0,1}|' r'[a-d][lh]|[er]?[a-d]x|[er]?[sbi]p|[er]?[sd]i|[c-gs]s|st[0-7]|' r'mm[0-7]|cr[0-4]|dr[0-367]|tr[3-7]|.mm\d+') wordop = r'seg|wrt|strict' type = r'byte|[dq]?word|ptr|xmmword|opaque' flags = re.IGNORECASE | re.MULTILINE tokens = { 'root': [ (identifier + '`' + identifier, Name.Function), ('->', Generic.Prompt), include('whitespace'), (r'^\s*%', Comment.Preproc, 'preproc'), (identifier + ':', Name.Label), (r'(%s)(\s+)(equ)' % identifier, bygroups(Name.Constant, Keyword.Declaration, Keyword.Declaration), 'instruction-args'), (declkw, Keyword.Declaration, 'instruction-args'), (identifier, Keyword.Declaration, 'instruction-args'), (r' *' + hexn, Name.Label), (r'[:]', Text), (r'^->', Error), (r'[\r\n]+', Text) ], 'instruction-args': [ (register, Name.Builtin), (string, String), (hexn, Number.Hex), (octn, Number.Oct), (binn, Number.Bin), (floatn, Number.Float), (decn, Number.Integer), include('punctuation'), (identifier, Name.Variable), (r'[\r\n]+', Text, '#pop'), include('whitespace') ], 'preproc': [ (r'[^;\n]+', Comment.Preproc), (r';.*?\n', Comment.Single, '#pop'), (r'\n', Comment.Preproc, '#pop'), ], 'whitespace': [ (r'\n', Text), (r'[ \t]+', Text), (r';.*', Comment.Single), (r'#.*', Comment.Single) ], 'punctuation': [ (r'[,():\[\]]+', Punctuation), (r'[&|^<>+*/%~-]+', Operator), (r'[$]+', Keyword.Constant), (wordop, Operator.Word), (type, Keyword.Type) ], } class LLDBIntelLexer(DisassemblyLexer): name = 'LLDB Intel syntax disassembly' aliases = ['lldb_intel'] class LLDBATTLexer(DisassemblyLexer): name = 'LLDB AT&T syntax disassembly' aliases = ['lldb_att'] class GDBATTLexer(DisassemblyLexer): name = 'GDB AT&T syntax disassembly' aliases = ['gdb_att'] class GDBIntelLexer(DisassemblyLexer): name = 'GDB Intel syntax disassembly' aliases = ['gdb_intel'] class VDBATTLexer(DisassemblyLexer): name = 'VDB AT&T syntax disassembly' aliases = ['vdb_att'] class CapstoneIntelLexer(DisassemblyLexer): name = 'Capstone Intel syntax disassembly' aliases = ['capstone_intel'] class VDBIntelLexer(RegexLexer): """ For Nasm (Intel) disassembly from VDB. Based on the LLDBIntelLexer above. major difference is the raw instruction hex after the instruction address. example: rip 0x000000000056eb4f: 4885ff test rdi,rdi ;0x7f4f8740ca50,0x7f4f8740ca50 0x000000000056eb52: 740f jz 0x0056eb63 """ name = 'VDB Intel syntax disassembly' aliases = ['vdb_intel'] filenames = [] mimetypes = [] space = r'[ \t]+' identifier = r'[]*' hexn = r'(?:0[xX][0-9a-f]+|$0[0-9a-f]*|[0-9]+[0-9a-f]*h)' # hex number hexr = r'(?:[0-9a-f]+)' # hex raw (no leader/trailer) octn = r'[0-7]+q' binn = r'[01]+b' decn = r'[0-9]+' floatn = decn + r'\.e?' + decn string = r'"(\\"|[^"\n])*"|' + r"'(\\'|[^'\n])*'|" + r"`(\\`|[^`\n])*`" register = (r'r[0-9]+[bwd]{0,1}|' r'[a-d][lh]|[er]?[a-d]x|[er]?[sbi]p|[er]?[sd]i|[c-gs]s|st[0-7]|' r'mm[0-7]|cr[0-4]|dr[0-367]|tr[3-7]|.mm\d*') wordop = r'seg|wrt|strict' type = r'byte|[dq]?word|ptr' flags = re.IGNORECASE | re.MULTILINE tokens = { 'root': [ (r'^(%s)(%s)(%s)(: )(%s)(%s)' % (register, space, hexn, hexr, space), bygroups(Name.Builtin, Text, Name.Label, Text, Number.Hex, Text), "instruction"), (r'^(%s)(%s)(: )(%s)(%s)' % (space, hexn, hexr, space), bygroups(Text, Name.Label, Text, Number.Hex, Text), "instruction") ], 'instruction': [ (space, Text), (r"(rep[a-z]*)( )", bygroups(Name.Function, Text)), (r"(%s)" % identifier, Name.Function, ("#pop", "instruction-args")), ], 'instruction-args': [ (space, Text), (string, String), (hexn, Number.Hex), (octn, Number.Oct), (binn, Number.Bin), (floatn, Number.Float), (decn, Number.Integer), include('punctuation'), (register, Name.Builtin), (identifier, Name.Variable), (r'[\r\n]+', Text, '#pop'), (r';', Text, ("#pop", 'comment')), ], 'comment': [ (space, Text), (string, Comment.Single), (hexn, Number.Hex), (octn, Number.Oct), (binn, Number.Bin), (floatn, Number.Float), (decn, Number.Integer), include('punctuation'), (register, Name.Builtin), (identifier, Name.Variable), (r'[\r\n]+', Text, '#pop'), ], 'punctuation': [ (r'[,():\[\]]+', Punctuation), (r'[&|^<>+*/%~-]+', Operator), (r'[$]+', Keyword.Constant), (wordop, Operator.Word), (type, Keyword.Type) ], } class WinDbgIntelLexer(RegexLexer): name = 'WinDbg Intel syntax disassembly' aliases = ['windbg_intel'] filenames = [] mimetypes = [] identifier = r'[]*' hexn = r'(0[xX])?([0-9a-f]+|$0[0-9a-f`]*|[0-9]+[0-9a-f]*h)' addr = r'(0[xX])?([0-9a-f`]+|$0[0-9a-f`]*|[0-9]+[0-9a-f`]*h)' octn = r'[0-7]+q' binn = r'[01]+b' decn = r'[0-9]+' floatn = decn + r'\.e?' + decn string = r'"(\\"|[^"\n])*"|' + r"'(\\'|[^'\n])*'|" + r"`(\\`|[^`\n])*`" declkw = r'(?:res|d)[bwdqt]|times' register = (r'r[0-9]+?[bwd]{0,1}|' r'[a-d][lh]|[er]?[a-d]x|[er]?[sbi]p|[er]?[sd]i|[c-gs]s|st[0-7]|' r'mm[0-7]|cr[0-4]|dr[0-367]|tr[3-7]|.mm\d*') wordop = r'seg|wrt|strict' type = r'byte|[dq]?word|ptr' func = r'[a-zA-Z]*\!?' flags = re.IGNORECASE | re.MULTILINE tokens = { 'root': [ (addr, Number.Hex, 'instruction-line'), include('whitespace'), (identifier, Name.Class, 'label'), (r'[:]', Text), (r'[\r\n]+', Text) ], 'instruction-line': [ (r' ', Text), (hexn, Text, 'instruction'), ], 'instruction': [ include('whitespace'), (r'(%s)(\s+)(equ)' % identifier, bygroups(Name.Constant, Keyword.Declaration, Keyword.Declaration), 'instruction-args'), (declkw, Keyword.Declaration, 'instruction-args'), (identifier, Name.Function, 'instruction-args'), ], 'label': [ (r'[!+]', Operator), (identifier, Name.Function), (hexn, Number.Hex), (r'[:]', Text, '#pop'), ], 'instruction-args': [ (string, String), include('punctuation'), (register, Name.Builtin), include('label'), (identifier, Name.Variable), (r'[\r\n]+', Text, '#pop:3'), include('whitespace'), (hexn, Number.Hex), (addr, Number.Hex), (octn, Number.Oct), (binn, Number.Bin), (floatn, Number.Float), (decn, Number.Integer), ], 'preproc': [ (r'[^;\n]+', Comment.Preproc), (r';.*?\n', Comment.Single, '#pop'), (r'\n', Comment.Preproc, '#pop'), ], 'whitespace': [ (r'\n', Text), (r'[ \t]+', Text), (r';.*', Comment.Single), (r'#.*', Comment.Single) ], 'punctuation': [ (r'[,():\[\]]+', Punctuation), (r'[&|^<>+*/%~-]+', Operator), (r'[$]+', Keyword.Constant), (wordop, Operator.Word), (type, Keyword.Type) ], } class WinDbgATTLexer(WinDbgIntelLexer): name = 'WinDbg ATT syntax disassembly' aliases = ['windbg_att'] voltron-master/voltron/main.py000066400000000000000000000036761360575504700171030ustar00rootroot00000000000000import os import argparse import logging import traceback import logging import logging.config import voltron from .view import * from .core import * log = logging.getLogger('main') def main(debugger=None): voltron.setup_logging('main') # Set up command line arg parser parser = argparse.ArgumentParser() parser.register('action', 'parsers', AliasedSubParsersAction) parser.add_argument('--debug', '-d', action='store_true', help='print debug logging') parser.add_argument('-o', action='append', help='override config variable', default=[]) top_level_sp = parser.add_subparsers(title='subcommands', description='valid subcommands', dest='subcommand') top_level_sp.required = True view_parser = top_level_sp.add_parser('view', help='display a view', aliases=('v')) view_parser.register('action', 'parsers', AliasedSubParsersAction) view_sp = view_parser.add_subparsers(title='views', description='valid view types', help='additional help', dest='view') view_sp.required = True # Set up a subcommand for each view class pm = PluginManager() pm.register_plugins() for plugin in pm.view_plugins: pm.view_plugins[plugin].view_class.configure_subparser(view_sp) # Parse args args = parser.parse_args() if args.debug: voltron.config['general']['debug_logging'] = True voltron.setup_logging('main') voltron.config.update(options=dict((tuple(x.split('=')) for x in args.o))) # Instantiate and run the appropriate module inst = args.func(args, loaded_config=voltron.config) inst.pm = pm try: inst.run() except Exception as e: log.exception("Exception running module {}: {}".format(inst.__class__.__name__, traceback.format_exc())) print("Encountered an exception while running the view '{}':\n{}".format(inst.__class__.__name__, traceback.format_exc())) except KeyboardInterrupt: suppress_exit_log = True inst.cleanup() voltron-master/voltron/plugin.py000066400000000000000000000260761360575504700174540ustar00rootroot00000000000000import logging import inspect import os from collections import defaultdict from scruffy.plugin import Plugin import voltron log = logging.getLogger('plugin') class PluginManager(object): """ Collects and validates API, debugger and view plugins. Provides methods to access and search the plugin collection. Plugin loading itself is handled by scruffy, which is configured in the environment specification in `env.py`. """ def __init__(self): """ Initialise a new PluginManager. """ self._api_plugins = defaultdict(lambda: None) self._debugger_plugins = defaultdict(lambda: None) self._view_plugins = defaultdict(lambda: None) self._web_plugins = defaultdict(lambda: None) self._command_plugins = defaultdict(lambda: None) def register_plugins(self): for p in voltron.env.plugins: self.register_plugin(p) def register_command_plugins(self): for p in voltron.env.plugins: if issubclass(p, CommandPlugin): self.register_plugin(p) @property def api_plugins(self): return self._api_plugins @property def debugger_plugins(self): return self._debugger_plugins @property def view_plugins(self): return self._view_plugins @property def web_plugins(self): return self._web_plugins @property def command_plugins(self): return self._command_plugins def register_plugin(self, plugin): """ Register a new plugin with the PluginManager. `plugin` is a subclass of scruffy's Plugin class. This is called by __init__(), but may also be called by the debugger host to load a specific plugin at runtime. """ if hasattr(plugin, 'initialise'): plugin.initialise() if self.valid_api_plugin(plugin): log.debug("Registering API plugin: {}".format(plugin)) self._api_plugins[plugin.request] = plugin() elif self.valid_debugger_plugin(plugin): log.debug("Registering debugger plugin: {}".format(plugin)) self._debugger_plugins[plugin.host] = plugin() elif self.valid_view_plugin(plugin): log.debug("Registering view plugin: {}".format(plugin)) self._view_plugins[plugin.name] = plugin() elif self.valid_web_plugin(plugin): log.debug("Registering web plugin: {}".format(plugin)) self._web_plugins[plugin.name] = plugin() elif self.valid_command_plugin(plugin): log.debug("Registering command plugin: {}".format(plugin)) self._command_plugins[plugin.name] = plugin() if voltron.debugger: voltron.debugger.register_command_plugin(plugin.name, plugin.command_class) else: log.debug("Ignoring invalid plugin: {}".format(plugin)) def valid_api_plugin(self, plugin): """ Validate an API plugin, ensuring it is an API plugin and has the necessary fields present. `plugin` is a subclass of scruffy's Plugin class. """ if (issubclass(plugin, APIPlugin) and hasattr(plugin, 'plugin_type') and plugin.plugin_type == 'api' and hasattr(plugin, 'request') and plugin.request != None and hasattr(plugin, 'request_class') and plugin.request_class != None and hasattr(plugin, 'response_class') and plugin.response_class != None): return True return False def valid_debugger_plugin(self, plugin): """ Validate a debugger plugin, ensuring it is a debugger plugin and has the necessary fields present. `plugin` is a subclass of scruffy's Plugin class. """ if (issubclass(plugin, DebuggerAdaptorPlugin) and hasattr(plugin, 'plugin_type') and plugin.plugin_type == 'debugger' and hasattr(plugin, 'host') and plugin.host != None): return True return False def valid_view_plugin(self, plugin): """ Validate a view plugin, ensuring it is a view plugin and has the necessary fields present. `plugin` is a subclass of scruffy's Plugin class. """ if (issubclass(plugin, ViewPlugin) and hasattr(plugin, 'plugin_type') and plugin.plugin_type == 'view' and hasattr(plugin, 'name') and plugin.name != None and hasattr(plugin, 'view_class') and plugin.view_class != None): return True return False def valid_web_plugin(self, plugin): """ Validate a web plugin, ensuring it is a web plugin and has the necessary fields present. `plugin` is a subclass of scruffy's Plugin class. """ if (issubclass(plugin, WebPlugin) and hasattr(plugin, 'plugin_type') and plugin.plugin_type == 'web' and hasattr(plugin, 'name') and plugin.name != None): return True return False def valid_command_plugin(self, plugin): """ Validate a command plugin, ensuring it is a command plugin and has the necessary fields present. `plugin` is a subclass of scruffy's Plugin class. """ if (issubclass(plugin, CommandPlugin) and hasattr(plugin, 'plugin_type') and plugin.plugin_type == 'command' and hasattr(plugin, 'name') and plugin.name != None): return True return False def api_plugin_for_request(self, request=None): """ Find an API plugin that supports the given request type. """ return self.api_plugins[request] def debugger_plugin_for_host(self, host=None): """ Find a debugger plugin that supports the debugger host. """ return self.debugger_plugins[host] def view_plugin_with_name(self, name=None): """ Find a view plugin that for the given view name. """ return self.view_plugins[name] def web_plugin_with_name(self, name=None): """ Find a web plugin that for the given view name. """ return self.web_plugins[name] def command_plugin_with_name(self, name=None): """ Find a command plugin that for the given view name. """ return self.command_plugins[name] class VoltronPlugin(Plugin): @classmethod def initialise(cls): pass class APIPlugin(VoltronPlugin): """ Abstract API plugin class. API plugins subclass this. `plugin_type` is 'api' `request` is the request type (e.g. 'version') `request_class` is the APIRequest subclass (e.g. APIVersionRequest) `response_class` is the APIResponse subclass (e.g. APIVersionResponse) `supported_hosts` is an array of debugger adaptor plugins that this plugin supports (e.g. 'lldb', 'gdb'). There is also a special host type called 'core' If the plugin requires only the 'core' debugger host, it can be used with any debugger adaptor plugin that implements the full interface (ie. the included 'lldb' and 'gdb' plugins). If it requires a specifically named debugger host plugin, then it will only work with those plugins specified. This allows developers to add custom API plugins that communicate directly with their chosen debugger host API, to do things that the standard debugger adaptor plugins don't support. See the core API plugins in voltron/plugins/api/ for examples. """ plugin_type = 'api' request = None request_class = None response_class = None supported_hosts = ['core'] @classmethod def initialise(cls): if cls.request_class: cls.request_class._plugin = cls cls.request_class.request = cls.request class DebuggerAdaptorPlugin(VoltronPlugin): """ Debugger adaptor plugin parent class. `plugin_type` is 'debugger' `host` is the name of the debugger host (e.g. 'lldb' or 'gdb') `adaptor_class` is the debugger adaptor class that can be queried See the core debugger plugins in voltron/plugins/debugger/ for examples. """ plugin_type = 'debugger' host = None adaptor_class = None supported_hosts = ['core'] @classmethod def initialise(cls): if cls.adaptor_class: cls.adaptor_class._plugin = cls class ViewPlugin(VoltronPlugin): """ View plugin parent class. `plugin_type` is 'view' `name` is the name of the view (e.g. 'register' or 'disassembly') `view_class` is the main view class See the core view plugins in voltron/plugins/view/ for examples. """ plugin_type = 'view' name = None view_class = None @classmethod def initialise(cls): if cls.view_class: cls.view_class._plugin = cls cls.view_class.view_type = cls.name class WebPlugin(VoltronPlugin): """ Web plugin parent class. `plugin_type` is 'web' `name` is the name of the web plugin (e.g. 'webview') `app` is a Flask app (or whatever, optional) """ _dir = None plugin_type = 'web' name = None app = None def __init__(self): self._dir = os.path.dirname(inspect.getfile(self.__class__)) class CommandPlugin(VoltronPlugin): """ Command plugin parent class. `plugin_type` is 'command' `name` is the name of the command plugin """ plugin_type = 'command' name = None class VoltronCommand(object): pass # # Shared plugin manager and convenience methods # pm = PluginManager() def api_request(request, *args, **kwargs): """ Create an API request. `request_type` is the request type (string). This is used to look up a plugin, whose request class is instantiated and passed the remaining arguments passed to this function. """ plugin = pm.api_plugin_for_request(request) if plugin and plugin.request_class: req = plugin.request_class(*args, **kwargs) else: raise Exception("Invalid request type") return req def api_response(request, *args, **kwargs): plugin = pm.api_plugin_for_request(request) if plugin and plugin.response_class: req = plugin.response_class(*args, **kwargs) else: raise Exception("Invalid request type") return req def debugger_adaptor(host, *args, **kwargs): plugin = pm.debugger_plugin_for_host(host) if plugin and plugin.adaptor_class: adaptor = plugin.adaptor_class(*args, **kwargs) else: raise Exception("Invalid debugger host") return adaptor def view(name, *args, **kwargs): plugin = pm.view_plugin_with_name(name) if plugin and plugin.view_class: view = plugin.view_class(*args, **kwargs) else: raise Exception("Invalid view name") return view def command(name, *args, **kwargs): plugin = pm.command_plugin_with_name(name) if plugin and plugin.command_class: command = plugin.command_class(*args, **kwargs) else: raise Exception("Invalid command name") return command def web_plugins(): return pm.web_plugins voltron-master/voltron/plugins/000077500000000000000000000000001360575504700172525ustar00rootroot00000000000000voltron-master/voltron/plugins/__init__.py000066400000000000000000000000001360575504700213510ustar00rootroot00000000000000voltron-master/voltron/plugins/api/000077500000000000000000000000001360575504700200235ustar00rootroot00000000000000voltron-master/voltron/plugins/api/__init__.py000066400000000000000000000000731360575504700221340ustar00rootroot00000000000000if not hasattr(__builtins__, "xrange"): xrange = range voltron-master/voltron/plugins/api/backtrace.py000066400000000000000000000025201360575504700223130ustar00rootroot00000000000000import voltron from voltron.api import * import logging import voltron from voltron.api import * from scruffy.plugin import Plugin log = logging.getLogger('api') class APIBacktraceRequest(APIRequest): """ API backtrace request. { "type": "request", "request": "backtrace" } """ @server_side def dispatch(self): try: bt = voltron.debugger.backtrace() res = APIBacktraceResponse(frames=bt) except NoSuchTargetException: res = APINoSuchTargetErrorResponse() except Exception as e: msg = "Exception getting backtrace: {}".format(repr(e)) log.exception(msg) res = APIGenericErrorResponse(msg) return res class APIBacktraceResponse(APISuccessResponse): """ API backtrace response. { "type": "response", "status": "success", "data": { "frames": [ { "index": 0, "addr": 0xffff, "name": "inferior`main + 0" } ] } } """ _fields = {'frames': True} frames = [] class APIBacktracePlugin(APIPlugin): request = "backtrace" request_class = APIBacktraceRequest response_class = APIBacktraceResponse voltron-master/voltron/plugins/api/breakpoints.py000066400000000000000000000031761360575504700227250ustar00rootroot00000000000000import voltron from voltron.api import * import logging import voltron from voltron.api import * from scruffy.plugin import Plugin log = logging.getLogger('api') class APIBreakpointsRequest(APIRequest): """ API breakpoints request. { "type": "request", "request": "breakpoints" } """ @server_side def dispatch(self): try: bps = voltron.debugger.breakpoints() res = APIBreakpointsResponse(breakpoints=bps) except NoSuchTargetException: res = APINoSuchTargetErrorResponse() except Exception as e: msg = "Exception getting breakpoints: {}".format(repr(e)) log.exception(msg) res = APIGenericErrorResponse(msg) return res class APIBreakpointsResponse(APISuccessResponse): """ API breakpoints response. { "type": "response", "status": "success", "data": { "breakpoints": [ { "id": 1, "enabled": True, "one_shot": False, "hit_count": 5, "locations": [ { "address": 0x100000cf0, "name": 'main' } ] } ] } } """ _fields = {'breakpoints': True} breakpoints = [] class APIBreakpointsPlugin(APIPlugin): request = "breakpoints" request_class = APIBreakpointsRequest response_class = APIBreakpointsResponse voltron-master/voltron/plugins/api/command.py000066400000000000000000000024211360575504700220120ustar00rootroot00000000000000import logging import voltron from voltron.api import * from scruffy.plugin import Plugin log = logging.getLogger('api') class APICommandRequest(APIRequest): """ API execute command request. { "type": "request", "request": "command" "data": { "command": "break list" } } """ _fields = {'command': True} @server_side def dispatch(self): try: output = voltron.debugger.command(self.command) res = APICommandResponse() res.output = output except NoSuchTargetException: res = APINoSuchTargetErrorResponse() except Exception as e: msg = "Exception executing debugger command: {}".format(repr(e)) log.exception(msg) res = APIGenericErrorResponse(msg) return res class APICommandResponse(APISuccessResponse): """ API list targets response. { "type": "response", "status": "success", "data": { "output": "stuff" } } """ _fields = {'output': True} output = None class APICommandPlugin(APIPlugin): request = "command" request_class = APICommandRequest response_class = APICommandResponse voltron-master/voltron/plugins/api/dereference.py000066400000000000000000000025621360575504700226510ustar00rootroot00000000000000import logging import voltron from voltron.api import * from scruffy.plugin import Plugin log = logging.getLogger('api') class APIDerefRequest(APIRequest): """ API dereference pointer request. { "type": "request", "request": "dereference" "data": { "pointer": 0xffffff8012341234 } } """ _fields = {'pointer': True} @server_side def dispatch(self): try: output = voltron.debugger.dereference(self.pointer) log.debug('output: {}'.format(str(output))) res = APIDerefResponse() res.output = output except NoSuchTargetException: res = APINoSuchTargetErrorResponse() except Exception as e: msg = "Exception dereferencing pointer: {}".format(repr(e)) log.exception(msg) res = APIGenericErrorResponse(msg) return res class APIDerefResponse(APISuccessResponse): """ API dereference pointer response. { "type": "response", "status": "success", "data": { "output": [0xffffff8055555555, "main + 0x123"] } } """ _fields = {'output': True} output = None class APIDerefPlugin(APIPlugin): request = "dereference" request_class = APIDerefRequest response_class = APIDerefResponse voltron-master/voltron/plugins/api/disassemble.py000066400000000000000000000052331360575504700226730ustar00rootroot00000000000000import voltron import logging from voltron.api import * from voltron.plugin import * log = logging.getLogger('api') class APIDisassembleRequest(APIRequest): """ API disassemble request. { "type": "request", "request": "disassemble" "data": { "target_id": 0, "address": 0x12341234, "count": 16, "use_capstone": False } } `target_id` is optional. `address` is the address at which to start disassembling. Defaults to instruction pointer if not specified. `count` is the number of instructions to disassemble. `use_capstone` a flag to indicate whether or not Capstone should be used instead of the debugger's disassembler. """ _fields = {'target_id': False, 'address': False, 'count': True, 'use_capstone': False} target_id = 0 address = None count = 16 @server_side def dispatch(self): try: if self.address == None: pc_name, self.address = voltron.debugger.program_counter(target_id=self.target_id) if self.use_capstone: disasm = voltron.debugger.disassemble_capstone(target_id=self.target_id, address=self.address, count=self.count) else: disasm = voltron.debugger.disassemble(target_id=self.target_id, address=self.address, count=self.count) res = APIDisassembleResponse() res.disassembly = disasm try: res.flavor = voltron.debugger.disassembly_flavor() except: res.flavor = 'NA' res.host = voltron.debugger._plugin.host except NoSuchTargetException: res = APINoSuchTargetErrorResponse() except TargetBusyException: res = APITargetBusyErrorResponse() except Exception as e: msg = "Unhandled exception {} disassembling: {}".format(type(e), e) log.exception(msg) res = APIErrorResponse(code=0, message=msg) return res class APIDisassembleResponse(APISuccessResponse): """ API disassemble response. { "type": "response", "status": "success", "data": { "disassembly": "mov blah blah" } } """ _fields = {'disassembly': True, 'formatted': False, 'flavor': False, 'host': False} disassembly = None formatted = None flavor = None host = None class APIDisassemblePlugin(APIPlugin): request = 'disassemble' request_class = APIDisassembleRequest response_class = APIDisassembleResponse voltron-master/voltron/plugins/api/memory.py000066400000000000000000000110251360575504700217040ustar00rootroot00000000000000import voltron import logging import six import struct from voltron.api import * log = logging.getLogger('api') class APIMemoryRequest(APIRequest): """ API read memory request. { "type": "request", "request": "memory", "data": { "target_id":0, "address": 0x12341234, "length": 0x40 } } `target_id` is optional. If not present, the currently selected target will be used. `address` is the address at which to start reading. `length` is the number of bytes to read. `register` is the register containing the address at which to start reading. `command` is the debugger command to execute to calculate the address at which to start reading. `deref` is a flag indicating whether or not to dereference any pointers within the memory region read. `offset` is an offset to add to the address at which to start reading. """ _fields = { 'target_id': False, 'address': False, 'length': False, 'words': False, 'register': False, 'command': False, 'deref': False, 'offset': False } target_id = 0 @server_side def dispatch(self): try: target = voltron.debugger.target(self.target_id) # if 'words' was specified, get the addr_size and calculate the length to read if self.words: self.length = self.words * target['addr_size'] # calculate the address at which to begin reading if self.address: addr = self.address elif self.command: output = voltron.debugger.command(self.command) if output: for item in reversed(output.split()): log.debug("checking item: {}".format(item)) try: addr = int(item) break except: try: addr = int(item, 16) break except: pass elif self.register: regs = voltron.debugger.registers(registers=[self.register]) addr = list(regs.values())[0] if self.offset: if self.words: addr += self.offset * target['addr_size'] else: addr += self.offset # read memory memory = voltron.debugger.memory(address=int(addr), length=int(self.length), target_id=int(self.target_id)) # deref pointers deref = None if self.deref: fmt = ('<' if target['byte_order'] == 'little' else '>') + {2: 'H', 4: 'L', 8: 'Q'}[target['addr_size']] deref = [] for chunk in zip(*[six.iterbytes(memory)] * target['addr_size']): chunk = ''.join([six.unichr(x) for x in chunk]).encode('latin1') p = list(struct.unpack(fmt, chunk))[0] if p > 0: try: deref.append(voltron.debugger.dereference(pointer=p)) except: deref.append([]) else: deref.append([]) res = APIMemoryResponse() res.address = addr res.memory = six.u(memory) res.bytes = len(memory) res.deref = deref except TargetBusyException: res = APITargetBusyErrorResponse() except NoSuchTargetException: res = APINoSuchTargetErrorResponse() except Exception as e: msg = "Exception getting memory from debugger: {}".format(repr(e)) log.exception(msg) res = APIGenericErrorResponse(msg) return res class APIMemoryResponse(APISuccessResponse): """ API read memory response. { "type": "response", "status": "success", "data": { "memory": "ABCDEF" # base64 encoded memory } } """ _fields = { 'address': True, 'memory': True, 'bytes': True, 'deref': False } _encode_fields = ['memory'] address = None memory = None bytes = None deref = None class APIReadMemoryPlugin(APIPlugin): request = 'memory' request_class = APIMemoryRequest response_class = APIMemoryResponse voltron-master/voltron/plugins/api/null.py000066400000000000000000000011501360575504700213440ustar00rootroot00000000000000import voltron import voltron.api from voltron.api import * from scruffy.plugin import Plugin class APINullRequest(APIRequest): """ API null request. { "type": "request", "request": "null" } """ @server_side def dispatch(self): return APINullResponse() class APINullResponse(APISuccessResponse): """ API null response. { "type": "response", "status": "success" } """ class APINullPlugin(APIPlugin): request = 'null' request_class = APINullRequest response_class = APINullResponse voltron-master/voltron/plugins/api/plugins.py000066400000000000000000000031111360575504700220520ustar00rootroot00000000000000import voltron import voltron.api from voltron.api import * from scruffy.plugin import Plugin class APIPluginsRequest(APIRequest): """ API plugins request. { "type": "request", "request": "plugins" } """ @server_side def dispatch(self): res = APIPluginsResponse() return res class APIPluginsResponse(APISuccessResponse): """ API plugins response. { "type": "response", "status": "success", "data": { "plugins": { "api": { "version": ["api_version", "host_version", "capabilities"] ... }, "debugger": { ... }, ... } } } """ _fields = { 'plugins': True } def __init__(self, *args, **kwargs): super(APIPluginsResponse, self).__init__(*args, **kwargs) self.plugins = { 'api': {n: {'request': p.request_class._fields, 'response': p.response_class._fields} for (n, p) in voltron.plugin.pm.api_plugins.iteritems()}, 'debugger': [n for n in voltron.plugin.pm.debugger_plugins], 'view': [n for n in voltron.plugin.pm.view_plugins], 'command': [n for n in voltron.plugin.pm.command_plugins], 'web': [n for n in voltron.plugin.pm.web_plugins], } class APIPluginsPlugin(APIPlugin): request = 'plugins' request_class = APIPluginsRequest response_class = APIPluginsResponse voltron-master/voltron/plugins/api/registers.py000066400000000000000000000044301360575504700224050ustar00rootroot00000000000000import voltron import logging from voltron.api import * log = logging.getLogger('api') class APIRegistersRequest(APIRequest): """ API state request. { "type": "request", "request": "registers", "data": { "target_id": 0, "thread_id": 123456, "registers": ['rsp'] } } `target_id` and `thread_id` are optional. If not present, the currently selected target and thread will be used. `registers` is optional. If it is not included all registers will be returned. """ _fields = {'target_id': False, 'thread_id': False, 'registers': False} target_id = 0 thread_id = None registers = [] @server_side def dispatch(self): try: regs = voltron.debugger.registers(target_id=self.target_id, thread_id=self.thread_id, registers=self.registers) res = APIRegistersResponse() res.registers = regs res.deref = {} for reg, val in regs.items(): try: if val > 0: try: res.deref[reg] = voltron.debugger.dereference(pointer=val) except: res.deref[reg] = [] else: res.deref[reg] = [] except TypeError: res.deref[reg] = [] except TargetBusyException: res = APITargetBusyErrorResponse() except NoSuchTargetException: res = APINoSuchTargetErrorResponse() except Exception as e: msg = "Exception getting registers from debugger: {}".format(repr(e)) log.exception(msg) res = APIGenericErrorResponse(msg) return res class APIRegistersResponse(APISuccessResponse): """ API status response. { "type": "response", "status": "success", "data": { "registers": { "rip": 0x12341234, ... }, "deref": {"rip": [(pointer, 0x12341234), ...]} } } """ _fields = {'registers': True, 'deref': False} class APIRegistersPlugin(APIPlugin): request = 'registers' request_class = APIRegistersRequest response_class = APIRegistersResponse voltron-master/voltron/plugins/api/stack.py000066400000000000000000000036641360575504700215130ustar00rootroot00000000000000import voltron import logging import base64 from voltron.api import * log = logging.getLogger('api') class APIStackRequest(APIRequest): """ API read stack request. { "type": "request", "request": "stack" "data": { "target_id": 0, "thread_id": 123456, "length": 0x40 } } `target_id` and `thread_id` are optional. If not present, the currently selected target and thread will be used. `length` is the number of bytes to read. """ _fields = {'target_id': False, 'thread_id': False, 'length': True} target_id = 0 thread_id = None length = None @server_side def dispatch(self): try: sp_name, sp = voltron.debugger.stack_pointer(target_id=self.target_id) memory = voltron.debugger.memory(address=sp, length=self.length, target_id=self.target_id) res = APIStackResponse() res.memory = memory res.stack_pointer = sp except NoSuchTargetException: res = APINoSuchTargetErrorResponse() except TargetBusyException: res = APITargetBusyErrorResponse() except Exception as e: msg = "Unhandled exception {} reading stack: {}".format(type(e), e) log.exception(msg) res = APIErrorResponse(code=0, message=msg) return res class APIStackResponse(APISuccessResponse): """ API read stack response. { "type": "response", "status": "success", "data": { "memory": "\xff...", "stack_pointer": 0x12341234 } } """ _fields = {'memory': True, 'stack_pointer': True} _encode_fields = ['memory'] memory = None stack_pointer = None class APIStackPlugin(APIPlugin): request = 'stack' request_class = APIStackRequest response_class = APIStackResponse voltron-master/voltron/plugins/api/state.py000066400000000000000000000022331360575504700215150ustar00rootroot00000000000000import voltron import logging from voltron.api import * log = logging.getLogger('api') class APIStateRequest(APIRequest): """ API state request. { "type": "request", "request": "state", "data": { "target_id": 0 } } """ _fields = {'target_id': False} target_id = 0 @server_side def dispatch(self): try: state = voltron.debugger.state(target_id=self.target_id) log.debug("Got state from debugger: {}".format(state)) res = APIStateResponse() res.state = state except TargetBusyException: res = APITargetBusyErrorResponse() except NoSuchTargetException: res = APINoSuchTargetErrorResponse() return res class APIStateResponse(APISuccessResponse): """ API status response. { "type": "response", "data": { "state": "stopped" } } """ _fields = {'state': True} state = None class APIStatePlugin(APIPlugin): request = 'state' request_class = APIStateRequest response_class = APIStateResponse voltron-master/voltron/plugins/api/targets.py000066400000000000000000000026341360575504700220530ustar00rootroot00000000000000import logging import voltron from voltron.api import * from scruffy.plugin import Plugin log = logging.getLogger('api') class APITargetsRequest(APIRequest): """ API list targets request. { "type": "request", "request": "targets" } """ _fields = {} @server_side def dispatch(self): try: res = APITargetsResponse() res.targets = voltron.debugger.targets() except NoSuchTargetException: res = APINoSuchTargetErrorResponse() except Exception as e: msg = "Exception getting targets from debugger: {}".format(repr(e)) log.exception(msg) res = APIGenericErrorResponse(msg) return res class APITargetsResponse(APISuccessResponse): """ API list targets response. { "type": "response", "status": "success", "data": { "targets": [{ "id": 0, # ID that can be used in other funcs "file": "/bin/ls", # target's binary file "arch": "x86_64", # target's architecture "state: "stopped" # state }] } } """ _fields = {'targets': True} targets = [] class APITargetsPlugin(APIPlugin): request = 'targets' request_class = APITargetsRequest response_class = APITargetsResponse voltron-master/voltron/plugins/api/version.py000066400000000000000000000022011360575504700220550ustar00rootroot00000000000000import voltron import voltron.api from voltron.api import * from scruffy.plugin import Plugin class APIVersionRequest(APIRequest): """ API version request. { "type": "request", "request": "version" } """ @server_side def dispatch(self): res = APIVersionResponse() res.api_version = voltron.api.version res.host_version = voltron.debugger.version() res.capabilities = voltron.debugger.capabilities() return res class APIVersionResponse(APISuccessResponse): """ API version response. { "type": "response", "status": "success", "data": { "api_version": 1.0, "host_version": 'lldb-something', "capabilities": ["async"] } } """ _fields = { 'api_version': True, 'host_version': True, 'capabilities': False } api_version = None host_version = None capabilities = None class APIVersionPlugin(APIPlugin): request = 'version' request_class = APIVersionRequest response_class = APIVersionResponse voltron-master/voltron/plugins/api/write_memory.py000066400000000000000000000030131360575504700231140ustar00rootroot00000000000000import voltron import logging import six import struct from voltron.api import * log = logging.getLogger('api') class APIWriteMemoryRequest(APIRequest): """ API write memory request. { "type": "request", "request": "write_memory", "data": { "target_id":0, "address": 0x12341234, "data": "\xcc" } } `target_id` is optional. If not present, the currently selected target will be used. `address` is the address at which to start writing. `data` is the data to write. """ _fields = { 'target_id': False, 'address': True, 'value': True } _encode_fields = ['value'] target_id = 0 @server_side def dispatch(self): try: target = voltron.debugger.target(self.target_id) voltron.debugger.write_memory(address=int(self.address), data=self.value, target_id=int(self.target_id)) res = APISuccessResponse() except TargetBusyException: res = APITargetBusyErrorResponse() except NoSuchTargetException: res = APINoSuchTargetErrorResponse() except Exception as e: msg = "Exception writing memory in debugger: {}".format(repr(e)) log.exception(msg) res = APIGenericErrorResponse(msg) return res class APIWriteMemoryPlugin(APIPlugin): request = 'write_memory' request_class = APIWriteMemoryRequest response_class = APISuccessResponse voltron-master/voltron/plugins/debugger/000077500000000000000000000000001360575504700210365ustar00rootroot00000000000000voltron-master/voltron/plugins/debugger/__init__.py000066400000000000000000000000001360575504700231350ustar00rootroot00000000000000voltron-master/voltron/plugins/debugger/dbg_gdb.py000066400000000000000000000617401360575504700227700ustar00rootroot00000000000000from __future__ import print_function import logging import threading import re import struct from six.moves.queue import Queue from voltron.api import * from voltron.plugin import * from voltron.dbg import * try: import gdb HAVE_GDB = True except ImportError: HAVE_GDB = False log = logging.getLogger('debugger') if HAVE_GDB: def post_event(func): """ Decorator to wrap a GDB adaptor method in a mechanism to run the method on the main thread at the next possible time. """ def inner(self, *args, **kwargs): if self.use_post_event: # create ephemeral queue q = Queue() # create an invocation that calls the decorated function class Invocation(object): def __call__(killme): # when the invocation is called, we call the function and stick the result into the queue try: res = func(self, *args, **kwargs) except Exception as e: # if we got an exception, just queue that instead res = e q.put(res) # post this invocation to be called on the main thread at the next opportunity gdb.post_event(Invocation()) # now we wait until there's something in the queue, which indicates that the invocation has run and return # the result that was pushed onto the queue by the invocation res = q.get() # if we got an exception back from the posted event, raise it if isinstance(res, Exception): raise res return res else: return func(self, *args, **kwargs) return inner class GDBAdaptor(DebuggerAdaptor): archs = { 'i386': 'x86', 'i386:intel': 'x86', 'i386:x64-32': 'x86', 'i386:x64-32:intel': 'x86', 'i8086': 'x86', 'i386:x86-64': 'x86_64', 'i386:x86-64:intel': 'x86_64', 'arm': 'arm', 'armv2': 'arm', 'armv2a': 'arm', 'armv3': 'arm', 'armv3m': 'arm', 'armv4': 'arm', 'armv4t': 'arm', 'armv5': 'arm', 'armv5t': 'arm', 'armv5te': 'arm', 'powerpc:common': 'powerpc' } sizes = { 'x86': 4, 'x86_64': 8, 'arm': 4, 'powerpc': 4, } max_frame = 64 max_string = 128 use_post_event = True """ The interface with an instance of GDB """ def __init__(self, *args, **kwargs): self.listeners = [] self.host_lock = threading.RLock() self.host = gdb self.busy = False def target_is_busy(self, target_id=0): """ Returns True or False indicating if the inferior is busy. The busy flag is set by the stop and continue handlers registered in the debugger command class. """ return self.busy @post_event def version(self): """ Get the debugger's version. Returns a string containing the debugger's version (e.g. 'GNU gdb (GDB) 7.8') """ output = gdb.execute('show version', to_string=True) try: version = output.split('\n')[0] except: version = None return version def _target(self, target_id=0): """ Return information about the specified target. Returns data in the following structure: { "id": 0, # ID that can be used in other funcs "file": "/bin/ls", # target's binary file "arch": "x86_64", # target's architecture "state: "stopped" # state } """ # get target target = gdb.selected_inferior() # get target properties d = {} d["id"] = 0 d["num"] = target.num # get target state d["state"] = self._state() # get inferior file (doesn't seem to be available through the API) lines = list(filter(lambda x: x != '', gdb.execute('info inferiors', to_string=True).split('\n'))) if len(lines) > 1: info = list(filter(lambda x: '*' in x[0], map(lambda x: x.split(), lines[1:]))) d["file"] = info[0][-1] else: log.debug("No inferiors in `info inferiors`") raise NoSuchTargetException() # get arch d["arch"] = self.get_arch() d['byte_order'] = self.get_byte_order() d['addr_size'] = self.get_addr_size() return d @post_event def target(self, target_id=0): """ Return information about the current inferior. GDB only supports querying the currently selected inferior, rather than an arbitrary target like LLDB, because the API kinda sucks. `target_id` is ignored. """ return self._target() @post_event def targets(self, target_ids=None): """ Return information about the debugger's current targets. `target_ids` is ignored. Only the current target is returned. This method is only implemented to maintain API compatibility with the LLDBAdaptor. """ return [self._target()] @validate_target @post_event def state(self, target_id=0): """ Get the state of a given target. """ return self._state() @validate_busy @validate_target @post_event def registers(self, target_id=0, thread_id=None, registers=[]): """ Get the register values for a given target/thread. """ arch = self.get_arch() log.debug("xxx") # if we got 'sp' or 'pc' in registers, change it to whatever the right name is for the current arch if arch in self.reg_names: if 'pc' in registers: registers.remove('pc') registers.append(self.reg_names[arch]['pc']) if 'sp' in registers: registers.remove('sp') registers.append(self.reg_names[arch]['sp']) else: raise Exception("Unsupported architecture: {}".format(target['arch'])) # get registers if registers != []: regs = {} for reg in registers: regs[reg] = self.get_register(reg) else: log.debug('Getting registers for arch {}'.format(arch)) if arch == "x86_64": regs = self.get_registers_x86_64() elif arch == "x86": regs = self.get_registers_x86() elif arch == "arm": regs = self.get_registers_arm() elif arch == "powerpc": regs = self.get_registers_powerpc() else: raise UnknownArchitectureException() return regs @validate_busy @validate_target @post_event def stack_pointer(self, target_id=0, thread_id=None): """ Get the value of the stack pointer register. """ arch = self.get_arch() if arch in self.reg_names: sp_name = self.reg_names[arch]['sp'] sp = self.get_register(sp_name) else: raise UnknownArchitectureException() return sp_name, sp @validate_busy @validate_target @post_event def program_counter(self, target_id=0, thread_id=None): """ Get the value of the program counter register. """ return self._program_counter(target_id, thread_id) def _program_counter(self, target_id=0, thread_id=None): """ Implementation of getting PC to avoid recursive decorators """ arch = self.get_arch() if arch in self.reg_names: pc_name = self.reg_names[arch]['pc'] pc = self.get_register(pc_name) else: raise UnknownArchitectureException() return pc_name, pc @validate_busy @validate_target @post_event def memory(self, address, length, target_id=0): """ Read memory from the inferior. `address` is the address at which to start reading `length` is the number of bytes to read """ # read memory log.debug('Reading 0x{:x} bytes of memory at 0x{:x}'.format(length, address)) memory = bytes(gdb.selected_inferior().read_memory(address, length)) return memory @validate_busy @validate_target @post_event def write_memory(self, address, data, target_id=0): """ Write to the inferior's memory. `address` is the address at which to start write `data` is the data to write """ log.debug('Writing 0x{:x} bytes of memory at 0x{:x}'.format(len(data), address)) memory = bytes(gdb.selected_inferior().write_memory(address, data)) @validate_busy @validate_target @post_event def disassemble(self, target_id=0, address=None, count=16): """ Get a disassembly of the instructions at the given address. `address` is the address at which to disassemble. If None, the current program counter is used. `count` is the number of instructions to disassemble. """ # make sure we have an address if address == None: pc_name, address = self._program_counter(target_id=target_id) # disassemble output = gdb.execute('x/{}i 0x{:x}'.format(count, address), to_string=True) return output @validate_busy @validate_target @post_event def dereference(self, pointer, target_id=0): """ Recursively dereference a pointer for display """ if isinstance(pointer, six.integer_types): fmt = ('<' if self.get_byte_order() == 'little' else '>') + {2: 'H', 4: 'L', 8: 'Q'}[self.get_addr_size()] addr = pointer chain = [] # recursively dereference while True: try: mem = gdb.selected_inferior().read_memory(addr, self.get_addr_size()) (ptr,) = struct.unpack(fmt, mem) if ptr in [x[1] for x in chain]: break chain.append(('pointer', addr)) addr = ptr except gdb.MemoryError: log.exception("Dereferencing pointer 0x{:X}".format(addr)) break except OverflowError: log.exception("Dereferencing pointer 0x{:X}".format(addr)) break # get some info for the last pointer # first try to resolve a symbol context for the address if len(chain): p, addr = chain[-1] output = gdb.execute('info symbol 0x{:x}'.format(addr), to_string=True) log.debug('output = {}'.format(output)) if 'No symbol matches' not in output: chain.append(('symbol', output.strip())) log.debug("symbol context: {}".format(str(chain[-1]))) else: log.debug("no symbol context, trying as a string") mem = gdb.selected_inferior().read_memory(addr, 2) if ord(mem[0]) <= 127 and ord(mem[0]) != 0: a = [] for i in range(0, self.max_string): mem = gdb.selected_inferior().read_memory(addr + i, 1) if ord(mem[0]) == 0 or ord(mem[0]) > 127: break if isinstance(mem, memoryview): a.append(mem.tobytes().decode('latin1')) else: a.append(str(mem)) chain.append(('string', ''.join(a))) log.debug("chain: {}".format(chain)) else: chain = [] return chain @post_event def command(self, command=None): """ Execute a command in the debugger. `command` is the command string to execute. """ if command: res = gdb.execute(command, to_string=True) else: raise Exception("No command specified") return res @post_event def disassembly_flavor(self): """ Return the disassembly flavor setting for the debugger. Returns 'intel' or 'att' """ flavor = re.search('flavor is "(.*)"', gdb.execute("show disassembly-flavor", to_string=True)).group(1) return flavor @post_event def breakpoints(self, target_id=0): """ Return a list of breakpoints. Returns data in the following structure: [ { "id": 1, "enabled": True, "one_shot": False, "hit_count": 5, "locations": [ { "address": 0x100000cf0, "name": 'main' } ] } ] """ breakpoints = [] # hahahahaha GDB sucks so much for b in (gdb.breakpoints() or ()): try: if b.location.startswith('*'): addr = int(b.location[1:], 16) else: output = gdb.execute('info addr {}'.format(b.location), to_string=True) m = re.match('.*is at ([^ ]*) .*', output) if not m: m = re.match('.*at address ([^ ]*)\..*', output) if m: addr = int(m.group(1), 16) else: addr = 0 except: addr = 0 breakpoints.append({ 'id': b.number, 'enabled': b.enabled, 'one_shot': b.temporary, 'hit_count': b.hit_count, 'locations': [{ "address": addr, "name": b.location }] }) return breakpoints @post_event def backtrace(self, target_id=0, thread_id=None): """ Return a list of stack frames. """ frames = [] f = gdb.newest_frame() for i in range(self.max_frame): if not f: break frames.append({'index': i, 'addr': f.pc(), 'name': f.name()}) f = f.older() return frames def capabilities(self): """ Return a list of the debugger's capabilities. Thus far only the 'async' capability is supported. This indicates that the debugger host can be queried from a background thread, and that views can use non-blocking API requests without queueing requests to be dispatched next time the debugger stops. """ return ['async'] # # Private functions # def _state(self): """ Get the state of a given target. Internal use. """ target = gdb.selected_inferior() if target.is_valid(): try: output = gdb.execute('info program', to_string=True) if "not being run" in output: state = "invalid" elif "stopped" in output: state = "stopped" except gdb.error as e: if 'Selected thread is running.' == str(e): state = "running" else: state = "invalid" return state def get_register(self, reg_name): arch = self.get_arch() if arch == "x86_64": reg = self.get_register_x86_64(reg_name) elif arch == "x86": reg = self.get_register_x86(reg_name) elif arch == "arm": reg = self.get_register_arm(reg_name) elif arch == "powerpc": reg = self.get_register_powerpc(reg_name) else: raise UnknownArchitectureException() return reg def get_registers_x86_64(self): # Get regular registers regs = ['rax','rbx','rcx','rdx','rbp','rsp','rdi','rsi','rip','r8','r9','r10','r11','r12','r13','r14','r15', 'cs','ds','es','fs','gs','ss'] vals = {} for reg in regs: try: vals[reg] = self.get_register_x86_64(reg) except: log.debug('Failed getting reg: ' + reg) vals[reg] = 'N/A' # Get flags try: vals['rflags'] = int(gdb.execute('info reg $eflags', to_string=True).split()[1], 16) except: log.debug('Failed getting reg: eflags') vals['rflags'] = 'N/A' # Get SSE registers try: sse = self.get_registers_sse(16) vals = dict(list(vals.items()) + list(sse.items())) except gdb.error: log.exception("Failed to get SSE registers") # Get FPU registers try: fpu = self.get_registers_fpu() vals = dict(list(vals.items()) + list(fpu.items())) except gdb.error: log.exception("Failed to get FPU registers") return vals def get_register_x86_64(self, reg): return int(gdb.parse_and_eval('(long long)$'+reg)) & 0xFFFFFFFFFFFFFFFF def get_registers_x86(self): # Get regular registers regs = ['eax','ebx','ecx','edx','ebp','esp','edi','esi','eip','cs','ds','es','fs','gs','ss'] vals = {} for reg in regs: try: vals[reg] = self.get_register_x86(reg) except: log.debug('Failed getting reg: ' + reg) vals[reg] = 'N/A' # Get flags try: vals['eflags'] = int(gdb.execute('info reg $eflags', to_string=True).split()[1], 16) except: log.debug('Failed getting reg: eflags') vals['eflags'] = 'N/A' # Get SSE registers try: sse = self.get_registers_sse(8) vals = dict(list(vals.items()) + list(sse.items())) except gdb.error: log.exception("Failed to get SSE registers") # Get FPU registers try: fpu = self.get_registers_fpu() vals = dict(list(vals.items()) + list(fpu.items())) except gdb.error: log.exception("Failed to get SSE registers") return vals def get_register_x86(self, reg): log.debug('Getting register: ' + reg) return int(gdb.parse_and_eval('(long)$'+reg)) & 0xFFFFFFFF def get_registers_sse(self, num=8): # the old way of doing this randomly crashed gdb or threw a python exception regs = {} for line in gdb.execute('info all-registers', to_string=True).split('\n'): m = re.match('^([xyz]mm\d+)\s.*uint128 = (0x[0-9a-f]+)\}', line) if m: regs[m.group(1)] = int(m.group(2), 16) return regs def get_registers_fpu(self): regs = {} for i in range(8): reg = 'st'+str(i) try: regs[reg] = int(gdb.execute('info reg '+reg, to_string=True).split()[-1][2:-1], 16) except: log.debug('Failed getting reg: ' + reg) regs[reg] = 'N/A' return regs def get_registers_arm(self): log.debug('Getting registers') regs = ['pc','sp','lr','cpsr','r0','r1','r2','r3','r4','r5','r6', 'r7','r8','r9','r10','r11','r12'] vals = {} for reg in regs: try: vals[reg] = self.get_register_arm(reg) except: log.debug('Failed getting reg: ' + reg) vals[reg] = 'N/A' return vals def get_register_arm(self, reg): log.debug('Getting register: ' + reg) return int(gdb.parse_and_eval('(long)$'+reg)) & 0xFFFFFFFF def get_registers_powerpc(self): log.debug('Getting registers') # TODO This could ideally pull from a single definition for the arch regs = ['pc','msr','cr','lr', 'ctr', 'r0','r1','r2','r3','r4','r5','r6', 'r7', 'r8','r9','r10','r11','r12','r13','r14', 'r15', 'r16','r17','r18','r19','r20','r21','r22', 'r23', 'r24','r25','r26','r27','r28','r29','r30', 'r31'] vals = {} for reg in regs: try: vals[reg] = self.get_register_powerpc(reg) except: log.debug('Failed getting reg: ' + reg) vals[reg] = 'N/A' return vals def get_register_powerpc(self, reg): log.debug('Getting register: ' + reg) return int(gdb.parse_and_eval('(long)$'+reg)) & 0xFFFFFFFF def get_next_instruction(self): return self.get_disasm().split('\n')[0].split(':')[1].strip() def get_arch(self): try: arch = gdb.selected_frame().architecture().name() except: arch = re.search('\(currently (.*)\)', gdb.execute('show architecture', to_string=True)).group(1) return self.archs[arch] def get_addr_size(self): arch = self.get_arch() return self.sizes[arch] def get_byte_order(self): return 'little' if 'little' in gdb.execute('show endian', to_string=True) else 'big' class GDBCommand(DebuggerCommand, gdb.Command): """ Debugger command class for GDB """ def __init__(self): super(GDBCommand, self).__init__("voltron", gdb.COMMAND_NONE, gdb.COMPLETE_NONE) self.adaptor = voltron.debugger self.registered = False self.register_hooks() def invoke(self, arg, from_tty): self.handle_command(arg) def register_hooks(self): if not self.registered: gdb.events.stop.connect(self.stop_handler) gdb.events.exited.connect(self.stop_and_exit_handler) gdb.events.cont.connect(self.cont_handler) self.registered = True def unregister_hooks(self): if self.registered: gdb.events.stop.disconnect(self.stop_handler) gdb.events.exited.disconnect(self.stop_and_exit_handler) gdb.events.cont.disconnect(self.cont_handler) self.registered = False def stop_handler(self, event): self.adaptor.update_state() voltron.debugger.busy = False voltron.server.dispatch_queue() log.debug('Inferior stopped') def exit_handler(self, event): log.debug('Inferior exited') voltron.debugger.busy = False def stop_and_exit_handler(self, event): log.debug('Inferior stopped and exited') voltron.debugger.busy = False self.stop_handler(event) self.exit_handler(event) def cont_handler(self, event): log.debug('Inferior continued') voltron.debugger.busy = True class GDBAdaptorPlugin(DebuggerAdaptorPlugin): host = 'gdb' adaptor_class = GDBAdaptor command_class = GDBCommand voltron-master/voltron/plugins/debugger/dbg_lldb.py000066400000000000000000000504051360575504700231450ustar00rootroot00000000000000from __future__ import print_function import struct import logging import threading import codecs from collections import namedtuple from voltron.api import * from voltron.plugin import * from voltron.dbg import * try: import lldb HAVE_LLDB = True except ImportError: HAVE_LLDB = False log = logging.getLogger('debugger') MAX_DEREF = 16 if HAVE_LLDB: class LLDBAdaptor(DebuggerAdaptor): """ The interface with an instance of LLDB """ def __init__(self, host=None): self.listeners = [] self.host_lock = threading.RLock() if host: log.debug("Passed a debugger host") self.host = host elif lldb.debugger: log.debug("lldb.debugger is valid - probably running inside LLDB") self.host = lldb.debugger else: log.debug("No debugger host found - creating one") self.host = lldb.SBDebugger.Create() self.host.SetAsync(False) @property def host(self): """ Get the debugger host object that this adaptor talks to. Used by custom API plugins to talk directly to the debugger. """ return self._host @host.setter def host(self, value): self._host = value def normalize_triple(self, triple): """ Returns a (cpu, platform, abi) triple Returns None for any fields that can't be elided """ s = triple.split("-") arch, platform, abi = s[0], s[1], '-'.join(s[2:]) if arch == "x86_64h": arch = "x86_64" return (arch, platform, abi) def version(self): """ Get the debugger's version. Returns a string containing the debugger's version (e.g. 'lldb-310.2.37') """ return self.host.GetVersionString() def _target(self, target_id=0): """ Return information about the specified target. Returns data in the following structure: { "id": 0, # ID that can be used in other funcs "file": "/bin/ls", # target's binary file "arch": "x86_64", # target's architecture "state: "stopped" # state } """ # get target t = self.host.GetTargetAtIndex(target_id) # get target properties d = {} d["id"] = target_id d["state"] = self.host.StateAsCString(t.process.GetState()) d["file"] = t.GetExecutable().fullpath try: d["arch"], _, _ = self.normalize_triple(t.triple) except: d["arch"] = None if d["arch"] == 'i386': d["arch"] = 'x86' d["byte_order"] = 'little' if t.byte_order == lldb.eByteOrderLittle else 'big' d["addr_size"] = t.addr_size return d @lock_host def target(self, target_id=0): """ Return information about the specified target. """ return self._target(target_id=target_id) @lock_host def targets(self, target_ids=None): """ Return information about the debugger's current targets. `target_ids` is an array of target IDs (or None for all targets) Returns data in the following structure: [ { "id": 0, # ID that can be used in other funcs "file": "/bin/ls", # target's binary file "arch": "x86_64", # target's architecture "state: "stopped" # state } ] """ # initialise returned data targets = [] # if we didn't get any target IDs, get info for all targets if not target_ids: n = self.host.GetNumTargets() target_ids = range(n) # iterate through targets log.debug("Getting info for {} targets".format(len(target_ids))) for i in target_ids: targets.append(self._target(i)) return targets @validate_target @lock_host def state(self, target_id=0): """ Get the state of a given target. `target_id` is a target ID (or None for the first target) """ target = self.host.GetTargetAtIndex(target_id) state = self.host.StateAsCString(target.process.GetState()) return state @validate_busy @validate_target @lock_host def registers(self, target_id=0, thread_id=None, registers=[]): """ Get the register values for a given target/thread. `target_id` is a target ID (or None for the first target) `thread_id` is a thread ID (or None for the selected thread) """ # get the target target = self.host.GetTargetAtIndex(target_id) t_info = self._target(target_id) # get the thread if not thread_id: thread_id = target.process.selected_thread.id try: thread = target.process.GetThreadByID(thread_id) except: raise NoSuchThreadException() # if we got 'sp' or 'pc' in registers, change it to whatever the right name is for the current arch if t_info['arch'] in self.reg_names: if 'pc' in registers: registers.remove('pc') registers.append(self.reg_names[t_info['arch']]['pc']) if 'sp' in registers: registers.remove('sp') registers.append(self.reg_names[t_info['arch']]['sp']) else: raise Exception("Unsupported architecture: {}".format(t_info['arch'])) # get the registers regs = thread.GetFrameAtIndex(0).GetRegisters() # extract the actual register values objs = [] for i in xrange(len(regs)): objs += regs[i] regs = {} for reg in objs: val = 'n/a' if reg.value is not None: try: val = reg.GetValueAsUnsigned() except: reg = None elif reg.num_children > 0: try: children = [] for i in xrange(reg.GetNumChildren()): children.append(int(reg.GetChildAtIndex(i, lldb.eNoDynamicValues, True).value, 16)) if t_info['byte_order'] == 'big': children = list(reversed(children)) val = int(codecs.encode(struct.pack('{}B'.format(len(children)), *children), 'hex'), 16) except: pass if registers == [] or reg.name in registers: regs[reg.name] = val return regs @validate_busy @validate_target @lock_host def stack_pointer(self, target_id=0, thread_id=None): """ Get the value of the stack pointer register. `target_id` is a target ID (or None for the first target) `thread_id` is a thread ID (or None for the selected thread) """ # get registers and targets regs = self.registers(target_id=target_id, thread_id=thread_id) target = self._target(target_id=target_id) # get stack pointer register if target['arch'] in self.reg_names: sp_name = self.reg_names[target['arch']]['sp'] sp = regs[sp_name] else: raise Exception("Unsupported architecture: {}".format(target['arch'])) return (sp_name, sp) @validate_busy @validate_target @lock_host def program_counter(self, target_id=0, thread_id=None): """ Get the value of the program counter register. `target_id` is a target ID (or None for the first target) `thread_id` is a thread ID (or None for the selected thread) """ # get registers and targets regs = self.registers(target_id=target_id, thread_id=thread_id) target = self._target(target_id=target_id) # get stack pointer register if target['arch'] in self.reg_names: pc_name = self.reg_names[target['arch']]['pc'] pc = regs[pc_name] else: raise Exception("Unsupported architecture: {}".format(target['arch'])) return (pc_name, pc) @validate_busy @validate_target @lock_host def memory(self, address, length, target_id=0): """ Get the register values for . `address` is the address at which to start reading `length` is the number of bytes to read `target_id` is a target ID (or None for the first target) """ # get the target target = self.host.GetTargetAtIndex(target_id) # read memory log.debug('Reading 0x{:x} bytes of memory at 0x{:x}'.format(length, address)) error = lldb.SBError() memory = target.process.ReadMemory(address, length, error) if not error.Success(): raise Exception("Failed reading memory: {}".format(error.GetCString())) return memory @validate_busy @validate_target @lock_host def stack(self, length, target_id=0, thread_id=None): """ Get the register values for . `length` is the number of bytes to read `target_id` is a target ID (or None for the first target) `thread_id` is a thread ID (or None for the selected thread) """ # get the stack pointer sp_name, sp = self.stack_pointer(target_id=target_id, thread_id=thread_id) # read memory memory = self.memory(sp, length, target_id=target_id) return memory @validate_busy @validate_target @lock_host def disassemble(self, target_id=0, address=None, count=None): """ Get a disassembly of the instructions at the given address. `address` is the address at which to disassemble. If None, the current program counter is used. `count` is the number of instructions to disassemble. """ # make sure we have an address if address == None: pc_name, address = self.program_counter(target_id=target_id) # disassemble res = lldb.SBCommandReturnObject() output = self.command('disassemble -s {} -c {}'.format(address, count)) return output @validate_busy @validate_target @lock_host def dereference(self, pointer, target_id=0): """ Recursively dereference a pointer for display """ t = self.host.GetTargetAtIndex(target_id) error = lldb.SBError() addr = pointer chain = [] # recursively dereference for i in range(0, MAX_DEREF): ptr = t.process.ReadPointerFromMemory(addr, error) if error.Success(): if ptr in chain: chain.append(('circular', 'circular')) break chain.append(('pointer', addr)) addr = ptr else: break if len(chain) == 0: raise InvalidPointerError("0x{:X} is not a valid pointer".format(pointer)) # get some info for the last pointer # first try to resolve a symbol context for the address p, addr = chain[-1] sbaddr = lldb.SBAddress(addr, t) ctx = t.ResolveSymbolContextForAddress(sbaddr, lldb.eSymbolContextEverything) if ctx.IsValid() and ctx.GetSymbol().IsValid(): # found a symbol, store some info and we're done for this pointer fstart = ctx.GetSymbol().GetStartAddress().GetLoadAddress(t) offset = addr - fstart chain.append(('symbol', '{} + 0x{:X}'.format(ctx.GetSymbol().name, offset))) log.debug("symbol context: {}".format(str(chain[-1]))) else: # no symbol context found, see if it looks like a string log.debug("no symbol context") s = t.process.ReadCStringFromMemory(addr, 256, error) for i in range(0, len(s)): if ord(s[i]) >= 128: s = s[:i] break if len(s): chain.append(('string', s)) return chain @lock_host def command(self, command=None): """ Execute a command in the debugger. `command` is the command string to execute. """ # for some reason this doesn't work - figure it out if command: res = lldb.SBCommandReturnObject() ci = self.host.GetCommandInterpreter() ci.HandleCommand(str(command), res, False) if res.Succeeded(): output = res.GetOutput() return output.strip() if output else "" else: raise Exception(res.GetError().strip()) else: raise Exception("No command specified") @lock_host def disassembly_flavor(self): """ Return the disassembly flavor setting for the debugger. Returns 'intel' or 'att' """ res = lldb.SBCommandReturnObject() ci = self.host.GetCommandInterpreter() ci.HandleCommand('settings show target.x86-disassembly-flavor', res) if res.Succeeded(): output = res.GetOutput().strip() flavor = output.split()[-1] if flavor == 'default': flavor = 'att' else: raise Exception(res.GetError().strip()) return flavor @validate_busy @validate_target @lock_host def breakpoints(self, target_id=0): """ Return a list of breakpoints. Returns data in the following structure: [ { "id": 1, "enabled": True, "one_shot": False, "hit_count": 5, "locations": [ { "address": 0x100000cf0, "name": 'main' } ] } ] """ breakpoints = [] t = self.host.GetTargetAtIndex(target_id) s = lldb.SBStream() for i in range(0, t.GetNumBreakpoints()): b = t.GetBreakpointAtIndex(i) locations = [] for j in range(0, b.GetNumLocations()): l = b.GetLocationAtIndex(j) s.Clear() l.GetAddress().GetDescription(s) desc = s.GetData() locations.append({ 'address': l.GetLoadAddress(), 'name': desc }) breakpoints.append({ 'id': b.id, 'enabled': b.enabled, 'one_shot': b.one_shot, 'hit_count': b.GetHitCount(), 'locations': locations }) return breakpoints @validate_busy @validate_target @lock_host def backtrace(self, target_id=0, thread_id=None): """ Return a list of stack frames. """ target = self.host.GetTargetAtIndex(target_id) if not thread_id: thread_id = target.process.selected_thread.id try: thread = target.process.GetThreadByID(thread_id) except: raise NoSuchThreadException() frames = [] for frame in thread: start_addr = frame.GetSymbol().GetStartAddress().GetFileAddress() offset = frame.addr.GetFileAddress() - start_addr ctx = frame.GetSymbolContext(lldb.eSymbolContextEverything) mod = ctx.GetModule() name = '{mod}`{symbol} + {offset}'.format(mod=os.path.basename(str(mod.file)), symbol=frame.name, offset=offset) frames.append({'index': frame.idx, 'addr': frame.addr.GetFileAddress(), 'name': name}) return frames def capabilities(self): """ Return a list of the debugger's capabilities. Thus far only the 'async' capability is supported. This indicates that the debugger host can be queried from a background thread, and that views can use non-blocking API requests without queueing requests to be dispatched next time the debugger stops. """ return ["async"] def register_command_plugin(self, name, cls): """ Register a command plugin with the LLDB adaptor. """ # make sure we have a commands object if not voltron.commands: voltron.commands = namedtuple('VoltronCommands', []) # method invocation creator def create_invocation(obj): @staticmethod def invoker(debugger, command, result, env_dict): obj.invoke(*command.split()) return invoker # store the invocation in `voltron.commands` to pass to LLDB setattr(voltron.commands, name, create_invocation(cls())) # register the invocation as a command script handler thing self.host.HandleCommand("command script add -f voltron.commands.{} {}".format(name, name)) class LLDBCommand(DebuggerCommand): """ Debugger command class for LLDB """ @staticmethod def _invoke(debugger, command, *args): voltron.command.handle_command(command) def __init__(self): super(LLDBCommand, self).__init__() self.hook_idx = None self.adaptor = voltron.debugger # install the voltron command handler self.adaptor.command("script import voltron") self.adaptor.command('command script add -f entry.invoke voltron') # try to register hooks automatically, as this works on new LLDB versions self.register_hooks(True) def invoke(self, debugger, command, result, dict): self.handle_command(command) def register_hooks(self, quiet=False): try: output = self.adaptor.command("target stop-hook list") if 'voltron' not in output: output = self.adaptor.command('target stop-hook add -o \'voltron stopped\'') try: # hahaha this sucks self.hook_idx = int(res.GetOutput().strip().split()[2][1:]) except: pass self.registered = True if not quiet: print("Registered stop-hook") except: if not quiet: print("No targets") def unregister_hooks(self): self.adaptor.command('target stop-hook delete {}'.format(self.hook_idx if self.hook_idx else '')) self.registered = False class LLDBAdaptorPlugin(DebuggerAdaptorPlugin): host = 'lldb' adaptor_class = LLDBAdaptor command_class = LLDBCommand voltron-master/voltron/plugins/debugger/dbg_mock.py000066400000000000000000000003121360575504700231510ustar00rootroot00000000000000from voltron.plugin import * from voltron.dbg import * class MockAdaptor(DebuggerAdaptor): pass class MockAdaptorPlugin(DebuggerAdaptorPlugin): host = 'mock' adaptor_class = MockAdaptor voltron-master/voltron/plugins/debugger/dbg_vdb.py000066400000000000000000000411161360575504700230020ustar00rootroot00000000000000from __future__ import print_function import struct import string import logging import threading from voltron.api import * from voltron.plugin import * from voltron.dbg import * try: import vtrace import vdb import envi HAVE_VDB = True except ImportError: HAVE_VDB = False log = logging.getLogger('debugger') if HAVE_VDB: class NotAStringError(Exception): pass class FailedToReadMemoryError(Exception): pass class VDBAdaptor(DebuggerAdaptor): """ The interface with an instance of VDB """ archs = { "i386": "x86", "amd64": "x86_64", "arm": "arm", } sizes = { 'x86': 4, 'x86_64': 8, 'arm': 4 } reg_names = { "x86_64": { "pc": "rip", "sp": "rsp", }, "x86": { "pc": "eip", "sp": "esp", } } def __init__(self, host, *args, **kwargs): self.listeners = [] self.host_lock = threading.RLock() self._vdb = host self._vtrace = vtrace def version(self): """ Get the debugger's version. Returns a string containing the debugger's version (e.g. 'GNU gdb (GDB) 7.8') """ return "VDB/version-unknown" def _target(self, target_id=0): """ Return information about the specified target. Returns data in the following structure: { "id": 0, # ID that can be used in other funcs "file": "/bin/ls", # target's binary file "arch": "x86_64", # target's architecture "state: "stopped" # state } """ d = {} d["id"] = 0 d["state"] = self._state() d["file"] = self._vdb.getTrace().metadata['ExeName'] d["arch"] = self.get_arch() d['byte_order'] = self.get_byte_order() d['addr_size'] = self.get_addr_size() return d @lock_host def target(self, target_id=0): """ Return information about the current inferior. `target_id` is ignored. """ return self._target() @lock_host def targets(self, target_ids=None): """ Return information about the debugger's current targets. `target_ids` is ignored. Only the current target is returned. This method is only implemented to maintain API compatibility with the LLDBAdaptor. """ return [self._target()] @validate_target @lock_host def state(self, target_id=0): """ Get the state of a given target. `target_id` is ignored. """ return self._state() @validate_busy @validate_target @lock_host def registers(self, target_id=0, thread_id=None, registers=[]): """ Get the register values for a given target/thread. `target_id` is ignored. """ arch = self.get_arch() if arch in self.reg_names: if 'pc' in registers: registers.remove('pc') registers.append(self.reg_names[arch]['pc']) if 'sp' in registers: registers.remove('sp') registers.append(self.reg_names[arch]['sp']) else: raise Exception("Unsupported architecture: {}".format(target['arch'])) if registers != []: regs = {} for reg in registers: regs[reg] = self.get_register(reg) else: log.debug('Getting registers for arch {}'.format(arch)) if arch == "x86_64": regs = self.get_registers_x86_64() elif arch == "x86": regs = self.get_registers_x86() elif arch == "arm": regs = self.get_registers_arm() else: raise UnknownArchitectureException() return regs @validate_busy @validate_target @lock_host def stack_pointer(self, target_id=0, thread_id=None): """ Get the value of the stack pointer register. `target_id` is ignored. """ arch = self.get_arch() if arch in self.reg_names: sp_name = self.reg_names[arch]['sp'] sp = self.get_register(sp_name) else: raise UnknownArchitectureException() return sp_name, sp @validate_busy @validate_target @lock_host def program_counter(self, target_id=0, thread_id=None): """ Get the value of the program counter register. `target_id` is ignored. """ arch = self.get_arch() if arch in self.reg_names: pc_name = self.reg_names[arch]['pc'] pc = self.get_register(pc_name) else: raise UnknownArchitectureException() return pc_name, pc @validate_busy @validate_target @lock_host def memory(self, address, length, target_id=0): """ Get the register values for . Raises `FailedToReadMemoryError` if... that happens. `address` is the address at which to start reading `length` is the number of bytes to read `target_id` is ignored. """ log.debug('Reading 0x{:x} bytes of memory at 0x{:x}'.format(length, address)) t = self._vdb.getTrace() try: return t.readMemory(address, length) except: raise FailedToReadMemoryError() @validate_busy @validate_target @lock_host def stack(self, length, target_id=0, thread_id=None): """ Get the register values for . `length` is the number of bytes to read `target_id` is a target ID (or None for the first target) `thread_id` is a thread ID (or None for the selected thread) """ # get the stack pointer sp_name, sp = self.stack_pointer(target_id=target_id, thread_id=thread_id) # read memory memory = self.memory(sp, length, target_id=target_id) return memory def _get_n_opcodes_length(self, address, count): """ Get the number of bytes used to represent the `n` instructions at `address`. `address` is the starting address of the sequence of instructions. `count` is the number of instructions to decode. """ length = 0 t = self._vdb.getTrace() arch = self._vdb.arch.getArchId() for i in xrange(count): op = t.parseOpcode(address + length, arch=arch) length += op.size return length @validate_busy @validate_target @lock_host def disassemble(self, target_id=0, address=None, count=16): """ Get a disassembly of the instructions at the given address. `address` is the address at which to disassemble. If None, the current program counter is used. `count` is the number of instructions to disassemble. """ if address == None: pc_name, address = self.program_counter(target_id=target_id) length = self._get_n_opcodes_length(address, count) can = envi.memcanvas.StringMemoryCanvas(self._vdb.memobj, self._vdb.symobj) can.renderMemory(address, length, self._vdb.opcoderend) return str(can) def _get_ascii_string(self, address, min_length=4, max_length=32): """ Get the ASCII string of length at least `min_length`, but not more than `max_length` of it, or raise `NotAStringError` if it doesnt look like an ASCII string. """ cs = [] for i in xrange(max_length): try: c = self.memory(address + i, 1)[0] except FailedToReadMemoryError: break if ord(c) == 0: break elif c not in string.printable: break else: cs.append(c) if len(cs) >= min_length: return "".join(cs) else: raise NotAStringError() def _get_unicode_string(self, address, min_length=4, max_length=32): """ Get the *simple* Unicode string of length at least `min_length` characters, but not more than `max_length` characters of it, or raise `NotAStringError` if it doesnt look like a *simple* Unicode string. *simple* Unicode is ASCII with interspersed NULLs """ cs = [] for i in xrange(max_length): try: b = self.memory(address + (i * 2), 2) except FailedToReadMemoryError: break # need every other byte to be a NULL if ord(b[1]) != 0: break c = b[0] if ord(c) == 0: break elif c not in string.printable: break else: cs.append(c) if len(cs) >= min_length: return "".join(cs) else: raise NotAStringError() @validate_busy @validate_target @lock_host def dereference(self, pointer, target_id=0): """ Recursively dereference a pointer for display `target_id` is ignored. """ fmt = ('<' if self.get_byte_order() == 'little' else '>') + {2: 'H', 4: 'L', 8: 'Q'}[self.get_addr_size()] addr = pointer chain = [] # recursively dereference while True: try: mem = self.memory(addr, self.get_addr_size()) except FailedToReadMemoryError: break except Exception as e: print(e) print(type(e)) print(e.__class__.__name__) break log.debug("read mem: {}".format(mem)) (ptr,) = struct.unpack(fmt, mem) if ptr in chain: break chain.append(('pointer', addr)) addr = ptr # get some info for the last pointer # first try to resolve a symbol context for the address p, addr = chain[-1] output = self._vdb.reprPointer(addr) if "Who knows?!?!!?" not in output: chain.append(('symbol', output)) log.debug("symbol context: {}".format(str(chain[-1]))) else: log.debug("no symbol context") try: chain.append(("string", self._get_ascii_string(addr))) except NotAStringError: try: chain.append(("string", self._get_unicode_string(addr))) except NotAStringError: pass log.debug("chain: {}".format(chain)) return chain @lock_host def command(self, command=None): """ Execute a command in the debugger. `command` is the command string to execute. """ if command: # well, this is hacky... # hook the canvas to capture a command's output oldcan = self._vdb.canvas newcan = envi.memcanvas.StringMemoryCanvas(self._vdb.memobj, self._vdb.symobj) try: self._vdb.canvas = newcan self._vdb.onecmd(command) finally: self._vdb.canvas = oldcan return str(newcan).rstrip("\n") else: raise Exception("No command specified") return res @lock_host def disassembly_flavor(self): """ Return the disassembly flavor setting for the debugger. Returns 'intel' or 'att' """ return "intel" def capabilities(self): """ Return a list of the debugger's capabilities. Thus far only the 'async' capability is supported. This indicates that the debugger host can be queried from a background thread, and that views can use non-blocking API requests without queueing requests to be dispatched next time the debugger stops. """ return ['async'] # # Private functions # def _state(self): """ Get the state of a given target. Internal use. """ if not self._vdb.getTrace().isAttached(): state = "invalid" else: if self._vdb.getTrace().isRunning(): state = "running" else: state = "stopped" return state def get_registers(self): return self._vdb.getTrace().getRegisters() def get_register(self, reg_name): return self.get_registers()[reg_name] def get_registers_x86_64(self): return self.get_registers() def get_registers_x86(self): return self.get_registers() def get_registers_arm(self): return self.get_registers() def get_registers_sse(self, num=8): sse = {} for k, v in self.get_registers().items(): if k.startswith("xmm"): sse[k] = v return sse def get_registers_fpu(self): fpu = {} for k, v in self.get_registers().items(): if k.startswith("st"): fpu[k] = v return fpu def get_next_instruction(self): dis = self.disassemble(address=self.program_counter()[1], count=1) return dis.partition("\n")[0].strip() def get_arch(self): arch = self._vdb.getTrace().getMeta("Architecture") return self.archs[arch] def get_addr_size(self): arch = self.get_arch() return self.sizes[arch] def get_byte_order(self): return "little" class VDBCommand(DebuggerCommand, vtrace.Notifier): """ Debugger command class for VDB """ def __init__(self, host): """ vdb is the debugger instance vtrace is the vtrace module? """ super(VDBCommand, self).__init__() self._vdb = host self._vtrace = vtrace self.register_hooks() def invoke(self, arg, from_tty): self.handle_command(arg) def register_hooks(self): self._vdb.registerNotifier(vtrace.NOTIFY_ALL, self) def unregister_hooks(self): self._vdb.deregisterNotifier(vtrace.NOTIFY_ALL, self) def notify(self, event, trace): if event == self._vtrace.NOTIFY_DETACH: self.exit_handler(event) elif event == self._vtrace.NOTIFY_EXIT: self.exit_handler(event) elif event == self._vtrace.NOTIFY_BREAK: self.stop_handler(event) elif event == self._vtrace.NOTIFY_STEP: self.stop_handler(event) elif event == self._vtrace.NOTIFY_CONTINUE: self.cont_handler(event) def stop_handler(self, event): self.adaptor.update_state() voltron.server.dispatch_queue() log.debug('Inferior stopped') def exit_handler(self, event): log.debug('Inferior exited') voltron.server.cancel_queue() voltron.server.stop() def cont_handler(self, event): log.debug('Inferior continued') class VDBAdaptorPlugin(DebuggerAdaptorPlugin): host = 'vdb' adaptor_class = VDBAdaptor command_class = VDBCommand voltron-master/voltron/plugins/debugger/dbg_windbg.py000066400000000000000000000367341360575504700235130ustar00rootroot00000000000000from __future__ import print_function import logging import threading import array from voltron.api import * from voltron.plugin import * from voltron.dbg import * try: in_windbg = False import pykd try: import vtrace except: in_windbg = True except ImportError: pass log = logging.getLogger('debugger') if in_windbg: class WinDbgAdaptor(DebuggerAdaptor): sizes = { 'x86': 4, 'x86_64': 8, } max_deref = 24 max_string = 128 def __init__(self, *args, **kwargs): self.listeners = [] self.host_lock = threading.RLock() self.host = pykd def version(self): """ Get the debugger's version. Returns a string containing the debugger's version (e.g. 'Microsoft (R) Windows Debugger Version whatever, pykd 0.3.0.38') """ try: [windbg] = [line for line in pykd.dbgCommand('version').split('\n') if 'Microsoft (R) Windows Debugger Version' in line] except: windbg = 'WinDbg ' return '{}, {}'.format(windbg, 'pykd {}'.format(pykd.version)) def _target(self, target_id=0): """ Return information about the specified target. Returns data in the following structure: { "id": 0, # ID that can be used in other funcs "file": "/bin/ls", # target's binary file "arch": "x86_64", # target's architecture "state: "stopped" # state } """ # get target properties d = {} d["id"] = pykd.getCurrentProcessId() d["num"] = d['id'] # get target state d["state"] = self._state() d["file"] = pykd.getProcessExeName() # get arch d["arch"] = self.get_arch() d['byte_order'] = self.get_byte_order() d['addr_size'] = self.get_addr_size() return d @lock_host def target(self, target_id=0): """ Return information about the current inferior. We only support querying the current inferior with WinDbg. `target_id` is ignored. """ return self._target() @lock_host def targets(self, target_ids=None): """ Return information about the debugger's current targets. `target_ids` is ignored. Only the current target is returned. This method is only implemented to maintain API compatibility with the LLDBAdaptor. """ return [self._target()] @validate_target @lock_host def state(self, target_id=0): """ Get the state of a given target. """ return self._state() @validate_busy @validate_target @lock_host def registers(self, target_id=0, thread_id=None, registers=[]): """ Get the register values for a given target/thread. """ arch = self.get_arch() # if we got 'sp' or 'pc' in registers, change it to whatever the right name is for the current arch if arch in self.reg_names: if 'pc' in registers: registers.remove('pc') registers.append(self.reg_names[arch]['pc']) if 'sp' in registers: registers.remove('sp') registers.append(self.reg_names[arch]['sp']) else: raise Exception("Unsupported architecture: {}".format(target['arch'])) # get registers if registers != []: vals = {} for reg in registers: vals[reg] = pykd.reg(reg) else: log.debug('Getting registers for arch {}'.format(arch)) if arch == "x86_64": reg_names = ['rax', 'rbx', 'rcx', 'rdx', 'rbp', 'rsp', 'rdi', 'rsi', 'rip', 'r8', 'r9', 'r10', 'r11', 'r12', 'r13', 'r14', 'r15', 'cs', 'ds', 'es', 'fs', 'gs', 'ss'] elif arch == "x86": reg_names = ['eax', 'ebx', 'ecx', 'edx', 'ebp', 'esp', 'edi', 'esi', 'eip', 'cs', 'ds', 'es', 'fs', 'gs', 'ss'] else: raise UnknownArchitectureException() vals = {} for reg in reg_names: try: vals[reg] = pykd.reg(reg) except: log.debug('Failed getting reg: ' + reg) vals[reg] = 'N/A' # Get flags try: vals['rflags'] = pykd.reg(reg) except: log.debug('Failed getting reg: eflags') vals['rflags'] = 'N/A' # Get SSE registers try: vals.update(self.get_registers_sse(16)) except: log.exception("Failed to get SSE registers") # Get FPU registers try: vals.update(self.get_registers_fpu()) except: log.exception("Failed to get FPU registers") return vals @validate_busy @validate_target @lock_host def stack_pointer(self, target_id=0, thread_id=None): """ Get the value of the stack pointer register. """ arch = self.get_arch() if arch in self.reg_names: sp_name = self.reg_names[arch]['sp'] sp = pykd.reg(sp_name) else: raise UnknownArchitectureException() return sp_name, sp @validate_busy @validate_target @lock_host def program_counter(self, target_id=0, thread_id=None): """ Get the value of the program counter register. """ arch = self.get_arch() if arch in self.reg_names: pc_name = self.reg_names[arch]['pc'] pc = pykd.reg(pc_name) else: raise UnknownArchitectureException() return pc_name, pc @validate_busy @validate_target @lock_host def memory(self, address, length, target_id=0): """ Get the register values for . `address` is the address at which to start reading `length` is the number of bytes to read """ # read memory log.debug('Reading 0x{:x} bytes of memory at 0x{:x}'.format(length, address)) memory = array.array('B', pykd.loadBytes(address, length)).tostring() return memory @validate_busy @validate_target @lock_host def stack(self, length, target_id=0, thread_id=None): """ Get the register values for . `length` is the number of bytes to read `target_id` is a target ID (or None for the first target) `thread_id` is a thread ID (or None for the selected thread) """ # get the stack pointer sp_name, sp = self.stack_pointer(target_id=target_id, thread_id=thread_id) # read memory memory = self.memory(sp, length, target_id=target_id) return memory @validate_busy @validate_target @lock_host def disassemble(self, target_id=0, address=None, count=16): """ Get a disassembly of the instructions at the given address. `address` is the address at which to disassemble. If None, the current program counter is used. `count` is the number of instructions to disassemble. """ # make sure we have an address if address is None: pc_name, address = self.program_counter(target_id=target_id) # disassemble output = pykd.dbgCommand('u 0x{:x} l{}'.format(address, count)) return output @validate_busy @validate_target @lock_host def dereference(self, pointer, target_id=0): """ Recursively dereference a pointer for display """ fmt = ('<' if self.get_byte_order() == 'little' else '>') + {2: 'H', 4: 'L', 8: 'Q'}[self.get_addr_size()] addr = pointer chain = [] # recursively dereference for i in range(0, self.max_deref): try: [ptr] = pykd.loadPtrs(addr, 1) if ptr in chain: break chain.append(('pointer', addr)) addr = ptr except: log.exception("Dereferencing pointer 0x{:X}".format(addr)) break # get some info for the last pointer # first try to resolve a symbol context for the address if len(chain): p, addr = chain[-1] output = pykd.findSymbol(addr) sym = True try: # if there's no symbol found, pykd returns a hex string of the address if int(output, 16) == addr: sym = False log.debug("no symbol context") except: pass if sym: chain.append(('symbol', output.strip())) else: log.debug("no symbol context") mem = pykd.loadBytes(addr, 2) if mem[0] < 127: if mem[1] == 0: a = [] for i in range(0, self.max_string, 2): mem = pykd.loadBytes(addr + i, 2) if mem == [0, 0]: break a.extend(mem) output = array.array('B', a).tostring().decode('UTF-16').encode('latin1') chain.append(('unicode', output)) else: output = pykd.loadCStr(addr) chain.append(('string', output)) log.debug("chain: {}".format(chain)) return chain @lock_host def command(self, command=None): """ Execute a command in the debugger. `command` is the command string to execute. """ if command: res = pykd.dbgCommand(command) else: raise Exception("No command specified") return res @lock_host def disassembly_flavor(self): """ Return the disassembly flavor setting for the debugger. Returns 'intel' or 'att' """ return 'intel' @lock_host def breakpoints(self, target_id=0): """ Return a list of breakpoints. Returns data in the following structure: [ { "id": 1, "enabled": True, "one_shot": False, "hit_count": 5, "locations": [ { "address": 0x100000cf0, "name": 'main' } ] } ] """ breakpoints = [] for i in range(0, pykd.getNumberBreakpoints()): b = pykd.getBp(i) addr = b.getOffset() name = hex(addr) try: name = pykd.findSymbol(addr) except: log.exception("No symbol found for address {}".format(addr)) pass breakpoints.append({ 'id': i, 'enabled': True, 'one_shot': False, 'hit_count': '-', 'locations': [{ "address": addr, "name": name }] }) return breakpoints def capabilities(self): """ Return a list of the debugger's capabilities. Thus far only the 'async' capability is supported. This indicates that the debugger host can be queried from a background thread, and that views can use non-blocking API requests without queueing requests to be dispatched next time the debugger stops. """ return ['async'] # # Private functions # def _state(self): """ Get the state of a given target. Internal use. """ s = pykd.getExecutionStatus() if s == pykd.executionStatus.Break: state = 'stopped' elif s == pykd.executionStatus.Go: state = 'running' else: state = 'invalid' return state def get_registers_sse(self, num=8): regs = {} for i in range(0, 16): try: reg = 'xmm{}'.format(i) regs[reg] = pykd.reg(reg) except: break return regs def get_registers_fpu(self): regs = {} for i in range(0, 8): try: reg = 'st{}'.format(i) regs[reg] = pykd.reg(reg) except: break return regs def get_next_instruction(self): return str(pykd.disasm()) def get_arch(self): t = pykd.getCPUType() if t == pykd.CPUType.I386: return 'x86' else: return 'x86_64' return arch def get_addr_size(self): arch = self.get_arch() return self.sizes[arch] def get_byte_order(self): return 'little' class EventHandler(pykd.eventHandler): """ Event handler for WinDbg/PyKD events. """ def __init__(self, adaptor, *args, **kwargs): super(EventHandler, self).__init__(*args, **kwargs) self.adaptor = adaptor def onExecutionStatusChange(self, status): if status == pykd.executionStatus.Break: self.adaptor.update_state() voltron.server.dispatch_queue() class WinDbgCommand(DebuggerCommand): """ Debugger command class for WinDbg """ def __init__(self): super(WinDbgCommand, self).__init__() self.register_hooks() def invoke(self, debugger, command, result, dict): self.handle_command(command) def register_hooks(self): self.handler = EventHandler(self.adaptor) def unregister_hooks(self): del self.handler self.handler = None class WinDbgAdaptorPlugin(DebuggerAdaptorPlugin): host = 'windbg' adaptor_class = WinDbgAdaptor command_class = WinDbgCommand voltron-master/voltron/plugins/view/000077500000000000000000000000001360575504700202245ustar00rootroot00000000000000voltron-master/voltron/plugins/view/__init__.py000066400000000000000000000000001360575504700223230ustar00rootroot00000000000000voltron-master/voltron/plugins/view/backtrace.py000066400000000000000000000020241360575504700225130ustar00rootroot00000000000000import logging from voltron.view import * from voltron.plugin import * from voltron.api import * log = logging.getLogger('view') class BacktraceView (TerminalView): def build_requests(self): return [api_request('command', block=self.block, command='bt')] def render(self, results): [res] = results # Set up header and error message if applicable self.title = '[backtrace]' # don't render if it timed out, probably haven't stepped the debugger again if res.timed_out: return if res and res.is_success: # Get the command output self.body = res.output else: log.error("Error getting backtrace: {}".format(res.message)) self.body = self.colour(res.message, 'red') # Call parent's render method super(BacktraceView, self).render(results) class BacktraceViewPlugin(ViewPlugin): plugin_type = 'view' name = 'backtrace' aliases = ('t', 'bt', 'back') view_class = BacktraceView voltron-master/voltron/plugins/view/breakpoints.py000066400000000000000000000044321360575504700231220ustar00rootroot00000000000000import logging from blessed import Terminal from voltron.view import * from voltron.plugin import * from voltron.api import * log = logging.getLogger('view') class BreakpointsView (TerminalView): def build_requests(self): return [ api_request('registers', registers=['pc']), api_request('breakpoints', block=self.block) ] def render(self, results): self.title = '[breakpoints]' r_res, b_res = results # get PC first so we can highlight a breakpoint we're at if r_res and r_res.is_success and len(r_res.registers) > 0: pc = r_res.registers[list(r_res.registers.keys())[0]] else: pc = -1 if b_res and b_res.is_success: fmtd = [] term = Terminal() for bp in b_res.breakpoints: # prepare formatting dictionary for the breakpoint d = bp.copy() d['locations'] = None d['t'] = term d['id'] = '#{:<2}'.format(d['id']) if d['one_shot']: d['one_shot'] = self.config.format.one_shot.format(t=term) else: d['one_shot'] = '' if d['enabled']: d['disabled'] = '' else: d['disabled'] = self.config.format.disabled.format(t=term) # add a row for each location for location in bp['locations']: # add location data to formatting dict and format the row d.update(location) if pc == d['address']: d['hit'] = self.config.format.hit.format(t=term) else: d['hit'] = '' f = self.config.format.row.format(**d) fmtd.append(f) d['id'] = ' ' self.body = '\n'.join(fmtd) else: log.error("Error getting breakpoints: {}".format(b_res.message)) self.body = self.colour(b_res.message, 'red') super(BreakpointsView, self).render(results) class BreakpointsViewPlugin(ViewPlugin): plugin_type = 'view' name = 'breakpoints' aliases = ('b', 'bp', 'break') view_class = BreakpointsView voltron-master/voltron/plugins/view/command.py000066400000000000000000000036321360575504700222200ustar00rootroot00000000000000import logging import pygments from pygments.lexers import get_lexer_by_name from voltron.view import * from voltron.plugin import * from voltron.api import * log = logging.getLogger('view') class CommandView (TerminalView): @classmethod def configure_subparser(cls, subparsers): sp = subparsers.add_parser('command', aliases=('c', 'cmd'), help='run a command each time the debugger host stops') VoltronView.add_generic_arguments(sp) sp.add_argument('command', action='store', help='command to run') sp.add_argument('--lexer', '-l', action='store', help='apply a Pygments lexer to the command output (e.g. "c")', default=None) sp.set_defaults(func=CommandView) def build_requests(self): return [api_request('command', block=self.block, command=self.args.command)] def render(self, results): [res] = results # Set up header and error message if applicable self.title = '[cmd:' + self.args.command + ']' if res and res.is_success: if get_lexer_by_name and self.args.lexer: try: lexer = get_lexer_by_name(self.args.lexer, stripall=True) self.body = pygments.highlight(res.output, lexer, pygments.formatters.TerminalFormatter()) except Exception as e: log.warning('Failed to highlight view contents: ' + repr(e)) self.body = res.output else: self.body = res.output else: log.error("Error executing command: {}".format(res.message)) self.body = self.colour(res.message, 'red') # Call parent's render method super(CommandView, self).render(results) class CommandViewPlugin(ViewPlugin): plugin_type = 'view' name = 'command' view_class = CommandView voltron-master/voltron/plugins/view/disasm.py000066400000000000000000000053741360575504700220670ustar00rootroot00000000000000from voltron.view import * from voltron.plugin import * from voltron.api import * from voltron.lexers import * import pygments import pygments.formatters class DisasmView(TerminalView): @classmethod def configure_subparser(cls, subparsers): sp = subparsers.add_parser('disasm', help='disassembly view', aliases=('d', 'dis')) VoltronView.add_generic_arguments(sp) sp.set_defaults(func=DisasmView) sp.add_argument('--use-capstone', '-c', action='store_true', default=False, help='use capstone') sp.add_argument('--address', '-a', action='store', default=None, help='address (in hex or decimal) from which to start disassembly') def build_requests(self): if self.args.address: if self.args.address.startswith('0x'): addr = int(self.args.address, 16) else: try: addr = int(self.args.address, 10) except: addr = int(self.args.address, 16) else: addr = None req = api_request('disassemble', block=self.block, use_capstone=self.args.use_capstone, offset=self.scroll_offset, address=addr) req.count = self.body_height() return [req] def render(self, results): [res] = results # Set up header & error message if applicable self.title = '[disassembly]' # don't render if it timed out, probably haven't stepped the debugger again if res.timed_out: return if res and res.is_success: # Get the disasm disasm = res.disassembly disasm = '\n'.join(disasm.split('\n')[:self.body_height()]) # Highlight output try: host = 'capstone' if self.args.use_capstone else res.host lexer = get_lexer_by_name('{}_{}'.format(host, res.flavor)) disasm = pygments.highlight(disasm, lexer, pygments.formatters.get_formatter_by_name( self.config.format.pygments_formatter, style=self.config.format.pygments_style)) except Exception as e: log.warning('Failed to highlight disasm: ' + str(e)) log.info(self.config.format) # Build output self.body = disasm.rstrip() else: log.error("Error disassembling: {}".format(res.message)) self.body = self.colour(res.message, 'red') # Call parent's render method super(DisasmView, self).render(results) class DisasmViewPlugin(ViewPlugin): plugin_type = 'view' name = 'disassembly' aliases = ('d', 'dis', 'disasm') view_class = DisasmView voltron-master/voltron/plugins/view/memory.py000066400000000000000000000230501360575504700221060ustar00rootroot00000000000000import logging import six import pygments import pygments.formatters from pygments.token import * from voltron.view import TerminalView, VoltronView from voltron.plugin import ViewPlugin, api_request log = logging.getLogger("view") class MemoryView(TerminalView): printable_filter = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]) asynchronous = True last_memory = None last_address = 0 @classmethod def configure_subparser(cls, subparsers): sp = subparsers.add_parser('memory', help='display a chunk of memory', aliases=('m', 'mem')) VoltronView.add_generic_arguments(sp) group = sp.add_mutually_exclusive_group(required=False) group.add_argument('--deref', '-d', action='store_true', help='display the data in a column one CPU word wide and dereference any valid pointers', default=False) group.add_argument('--bytes', '-b', action='store', type=int, help='bytes per line (default 16)', default=16) group.add_argument('--words', '-w', action='store', type=int, help='machine words per line', default=0) sp.add_argument('--reverse', '-v', action='store_true', help='reverse the output', default=False) sp.add_argument('--track', '-t', action='store_true', help='track and highlight changes', default=True) sp.add_argument('--no-track', '-T', action='store_false', help='don\'t track and highlight changes') group = sp.add_mutually_exclusive_group(required=False) group.add_argument('--address', '-a', action='store', help='address (in hex or decimal) from which to start reading memory') group.add_argument('--command', '-c', action='store', help='command to execute resulting in the address from which to start reading memory. ' 'voltron will do his almighty best to find an address. e.g. "print \$rip + 0x1234"', default=None) group.add_argument('--register', '-r', action='store', help='register containing the address from which to start reading memory', default=None) sp.set_defaults(func=MemoryView) def build_requests(self): height, width = self.window_size() # check args if self.args.register: args = {'register': self.args.register} elif self.args.command: args = {'command': self.args.command} elif self.args.address: if self.args.address.startswith('0x'): addr = int(self.args.address, 16) else: try: addr = int(self.args.address, 10) except: addr = int(self.args.address, 16) args = {'address': addr} else: args = {'register': 'sp'} if self.args.deref: args['words'] = height args['offset'] = self.scroll_offset if self.args.reverse else -self.scroll_offset else: args['length'] = height * self.args.bytes * 2 args['offset'] = self.scroll_offset * self.args.bytes * (1 if self.args.reverse else -1) # get memory and target info return [ api_request('targets'), api_request('memory', deref=self.args.deref is True, **args) ] def generate_tokens(self, results): t_res, m_res = results if t_res and t_res.is_success and len(t_res.targets) > 0: target = t_res.targets[0] if m_res and m_res.is_success: bytes_per_chunk = self.args.words*target['addr_size'] if self.args.words else self.args.bytes for c in range(0, m_res.bytes, bytes_per_chunk): chunk = m_res.memory[c:c + bytes_per_chunk] yield (Name.Label, self.format_address(m_res.address + c, size=target['addr_size'], pad=False)) yield (Name.Label, ': ') # Hex bytes byte_array = [] for i, x in enumerate(six.iterbytes(chunk)): n = "%02X" % x token = Text if x else Comment if self.args.track and self.last_memory and self.last_address == m_res.address: if x != six.indexbytes(self.last_memory, c + i): token = Error byte_array.append((token, n)) if self.args.words: if target['byte_order'] =='little': byte_array_words = [byte_array[i:i+ target['addr_size']] for i in range(0, bytes_per_chunk, target['addr_size'])] for word in byte_array_words: word.reverse() for x in word: yield x yield (Text, ' ') else: for x in byte_array: yield x yield (Text, ' ') # ASCII representation yield (Punctuation, '| ') for i, x in enumerate(six.iterbytes(chunk)): token = String.Char if x else Comment if self.args.track and self.last_memory and self.last_address == m_res.address: if x != six.indexbytes(self.last_memory, c + i): token = Error yield (token, ((x <= 127 and self.printable_filter[x]) or '.')) yield (Punctuation, ' | ') # Deref chain if self.args.deref: chain = m_res.deref.pop(0) for i, (t, item) in enumerate(chain): if t == "pointer": yield (Number.Hex, self.format_address(item, size=target['addr_size'], pad=False)) elif t == "string": for r in ['\n', '\r', '\v']: item = item.replace(r, '\\{:x}'.format(ord(r))) yield (String.Double, '"' + item + '"') elif t == "unicode": for r in ['\n', '\r', '\v']: item = item.replace(r, '\\{:x}'.format(ord(r))) yield (String.Double, 'u"' + item + '"') elif t == "symbol": yield (Name.Function, '`' + item + '`') elif t == "circular": yield (Text, '(circular)') if i < len(chain) - 1: yield (Punctuation, ' => ') yield (Text, '\n') def render(self, results): target = None self.trunc_top = self.args.reverse t_res, m_res = results if t_res and t_res.is_success and len(t_res.targets) > 0: target = t_res.targets[0] if self.args.deref or self.args.words: self.args.bytes = target['addr_size'] f = pygments.formatters.get_formatter_by_name(self.config.format.pygments_formatter, style=self.config.format.pygments_style) if m_res and m_res.is_success: lines = pygments.format(self.generate_tokens(results), f).split('\n') self.body = '\n'.join(reversed(lines)).strip() if self.args.reverse else '\n'.join(lines) self.info = '[0x{0:0=4x}:'.format(len(m_res.memory)) + self.config.format.addr_format.format(m_res.address) + ']' else: log.error("Error reading memory: {}".format(m_res.message)) self.body = pygments.format([(Error, m_res.message)], f) self.info = '' # Store the memory if self.args.track: self.last_address = m_res.address self.last_memory = m_res.memory else: self.body = self.colour("Failed to get targets", 'red') if not self.title: self.title = "[memory]" super(MemoryView, self).render(results) def format_address(self, address, size=8, pad=True, prefix='0x'): fmt = '{:' + ('0=' + str(size * 2) if pad else '') + 'X}' addr_str = fmt.format(address) if prefix: addr_str = prefix + addr_str return addr_str class MemoryViewPlugin(ViewPlugin): plugin_type = 'view' name = 'memory' view_class = MemoryView class StackView(MemoryView): @classmethod def configure_subparser(cls, subparsers): sp = subparsers.add_parser('stack', help='display a chunk of stack memory', aliases=('s', 'st')) VoltronView.add_generic_arguments(sp) sp.set_defaults(func=StackView) sp.add_argument('--reverse', '-v', action='store_false', help='(un)reverse the output', default=True) sp.add_argument('--track', '-t', action='store_true', help='track and highlight changes', default=True) sp.add_argument('--no-track', '-T', action='store_false', help='don\'t track and highlight changes') def build_requests(self): self.args.deref = True self.args.words = False self.args.register = 'sp' self.args.command = None self.args.address = None self.args.bytes = None return super(StackView, self).build_requests() def render(self, results): self.title = '[stack]' super(StackView, self).render(results) class StackViewPlugin(ViewPlugin): plugin_type = 'view' name = 'stack' view_class = StackView voltron-master/voltron/plugins/view/register.py000066400000000000000000000777661360575504700224510ustar00rootroot00000000000000import six import struct import logging import pygments from pygments.token import * from numbers import Number as NumberType from voltron.core import STRTYPES from voltron.view import * from voltron.plugin import * from voltron.api import * log = logging.getLogger("view") class RegisterView (TerminalView): FORMAT_INFO = { 'x86_64': [ { 'regs': ['rax','rbx','rcx','rdx','rbp','rsp','rdi','rsi','rip', 'r8','r9','r10','r11','r12','r13','r14','r15'], 'label_format': '{0:3s}', 'category': 'general', }, { 'regs': ['cs','ds','es','fs','gs','ss'], 'value_format': SHORT_ADDR_FORMAT_16, 'category': 'general', }, { 'regs': ['rflags'], 'value_format': '{}', 'value_func': 'format_flags', 'value_colour_en': False, 'category': 'general', }, { 'regs': ['rflags'], 'value_format': '{}', 'value_func': 'format_jump', 'value_colour_en': False, 'category': 'general', 'format_name': 'jump' }, { 'regs': ['xmm0','xmm1','xmm2','xmm3','xmm4','xmm5','xmm6','xmm7','xmm8', 'xmm9','xmm10','xmm11','xmm12','xmm13','xmm14','xmm15'], 'value_format': SHORT_ADDR_FORMAT_128, 'value_func': 'format_xmm', 'category': 'sse', }, { 'regs': ['st0','st1','st2','st3','st4','st5','st6','st7'], 'value_format': '{0:0=20X}', 'value_func': 'format_fpu', 'category': 'fpu', }, ], 'x86': [ { 'regs': ['eax','ebx','ecx','edx','ebp','esp','edi','esi','eip'], 'label_format': '{0:3s}', 'value_format': SHORT_ADDR_FORMAT_32, 'category': 'general', }, { 'regs': ['cs','ds','es','fs','gs','ss'], 'value_format': SHORT_ADDR_FORMAT_16, 'category': 'general', }, { 'regs': ['eflags'], 'value_format': '{}', 'value_func': 'format_flags', 'value_colour_en': False, 'category': 'general', }, { 'regs': ['eflags'], 'value_format': '{}', 'value_func': 'format_jump', 'value_colour_en': False, 'category': 'general', 'format_name': 'jump' }, { 'regs': ['xmm0','xmm1','xmm2','xmm3','xmm4','xmm5','xmm6','xmm7'], 'value_format': SHORT_ADDR_FORMAT_128, 'value_func': 'format_xmm', 'category': 'sse', }, { 'regs': ['st0','st1','st2','st3','st4','st5','st6','st7'], 'value_format': '{0:0=20X}', 'value_func': 'format_fpu', 'category': 'fpu', }, ], 'arm': [ { 'regs': ['pc','sp','lr','cpsr','r0','r1','r2','r3','r4','r5','r6', 'r7','r8','r9','r10','r11','r12'], 'label_format': '{0:>3s}', 'value_format': SHORT_ADDR_FORMAT_32, 'category': 'general', } ], 'arm64': [ { 'regs': ['pc', 'sp', 'x0', 'x1', 'x2', 'x3', 'x4', 'x5', 'x6', 'x7', 'x8', 'x9', 'x10', 'x11', 'x12', 'x13', 'x14', 'x15', 'x16', 'x17', 'x18', 'x19', 'x20', 'x21', 'x22', 'x23', 'x24', 'x25', 'x26', 'x27', 'x28', 'x29', 'x30'], 'label_format': '{0:3s}', 'value_format': SHORT_ADDR_FORMAT_64, 'category': 'general', }, ], 'powerpc': [ { 'regs': ['pc','msr','cr','lr', 'ctr', 'r0','r1','r2','r3','r4','r5','r6', 'r7', 'r8','r9','r10','r11','r12','r13','r14', 'r15', 'r16','r17','r18','r19','r20','r21','r22', 'r23', 'r24','r25','r26','r27','r28','r29','r30', 'r31'], 'label_format': '{0:>3s}', 'value_format': SHORT_ADDR_FORMAT_32, 'category': 'general', } ], } TEMPLATES = { 'x86_64': { 'horizontal': { 'general': ( "{raxl} {rax} {rbxl} {rbx} {rbpl} {rbp} {rspl} {rsp} {rflags}\n" "{rdil} {rdi} {rsil} {rsi} {rdxl} {rdx} {rcxl} {rcx} {ripl} {rip}\n" "{r8l} {r8} {r9l} {r9} {r10l} {r10} {r11l} {r11} {r12l} {r12}\n" "{r13l} {r13} {r14l} {r14} {r15l} {r15}\n" "{csl} {cs} {dsl} {ds} {esl} {es} {fsl} {fs} {gsl} {gs} {ssl} {ss} {jump}" ), 'sse': ( "{xmm0l} {xmm0} {xmm1l} {xmm1} {xmm2l} {xmm2}\n" "{xmm3l} {xmm3} {xmm4l} {xmm4} {xmm5l} {xmm5}\n" "{xmm6l} {xmm6} {xmm7l} {xmm7} {xmm8l} {xmm8}\n" "{xmm9l} {xmm9} {xmm10l} {xmm10} {xmm11l} {xmm11}\n" "{xmm12l} {xmm12} {xmm13l} {xmm13} {xmm14l} {xmm14}\n" "{xmm15l} {xmm15}\n" ), 'fpu': ( "{st0l} {st0} {st1l} {st1} {st2l} {st2} {st3l} {st2}\n" "{st4l} {st4} {st5l} {st5} {st6l} {st6} {st7l} {st7}\n" ) }, 'vertical': { 'general': ( "{rflags}\n{jump}\n" "{ripl} {rip}{ripinfo}\n" "{raxl} {rax}{raxinfo}\n" "{rbxl} {rbx}{rbxinfo}\n" "{rbpl} {rbp}{rbpinfo}\n" "{rspl} {rsp}{rspinfo}\n" "{rdil} {rdi}{rdiinfo}\n" "{rsil} {rsi}{rsiinfo}\n" "{rdxl} {rdx}{rdxinfo}\n" "{rcxl} {rcx}{rcxinfo}\n" "{r8l} {r8}{r8info}\n" "{r9l} {r9}{r9info}\n" "{r10l} {r10}{r10info}\n" "{r11l} {r11}{r11info}\n" "{r12l} {r12}{r12info}\n" "{r13l} {r13}{r13info}\n" "{r14l} {r14}{r14info}\n" "{r15l} {r15}{r15info}\n" "{csl} {cs} {dsl} {ds}\n{esl} {es} {fsl} {fs}\n{gsl} {gs} {ssl} {ss}" ), 'sse': ( "{xmm0l} {xmm0}\n{xmm1l} {xmm1}\n{xmm2l} {xmm2}\n{xmm3l} {xmm3}\n" "{xmm4l} {xmm4}\n{xmm5l} {xmm5}\n{xmm6l} {xmm6}\n{xmm7l} {xmm7}\n" "{xmm8l} {xmm8}\n{xmm9l} {xmm9}\n{xmm10l} {xmm10}\n{xmm11l} {xmm11}\n" "{xmm12l} {xmm12}\n{xmm13l} {xmm13}\n{xmm14l} {xmm14}\n{xmm15l} {xmm15}" ), 'fpu': ( "{st0l} {st0}\n{st1l} {st1}\n{st2l} {st2}\n{st3l} {st2}\n" "{st4l} {st4}\n{st5l} {st5}\n{st6l} {st6}\n{st7l} {st7}\n" ) } }, 'x86': { 'horizontal': { 'general': ( "{eaxl} {eax} {ebxl} {ebx} {ebpl} {ebp} {espl} {esp} {eflags}\n" "{edil} {edi} {esil} {esi} {edxl} {edx} {ecxl} {ecx} {eipl} {eip}\n" "{csl} {cs} {dsl} {ds} {esl} {es} {fsl} {fs} {gsl} {gs} {ssl} {ss} {jump}" ), 'sse': ( "{xmm0l} {xmm0} {xmm1l} {xmm1} {xmm2l} {xmm2}\n" "{xmm3l} {xmm3} {xmm4l} {xmm4} {xmm5l} {xmm5}\n" "{xmm6l} {xmm6} {xmm7l}" ), 'fpu': ( "{st0l} {st0} {st1l} {st1} {st2l} {st2} {st3l} {st2}\n" "{st4l} {st4} {st5l} {st5} {st6l} {st6} {st7l} {st7}\n" ) }, 'vertical': { 'general': ( "{eflags}\n{jump}\n" "{eipl} {eip}{eipinfo}\n" "{eaxl} {eax}{eaxinfo}\n" "{ebxl} {ebx}{ebxinfo}\n" "{ebpl} {ebp}{ebpinfo}\n" "{espl} {esp}{espinfo}\n" "{edil} {edi}{ediinfo}\n" "{esil} {esi}{esiinfo}\n" "{edxl} {edx}{edxinfo}\n" "{ecxl} {ecx}{ecxinfo}\n" "{csl} {cs}\n{dsl} {ds}\n{esl} {es}\n{fsl} {fs}\n{gsl} {gs}\n{ssl} {ss}" ), 'sse': ( "{xmm0l} {xmm0}\n{xmm1l} {xmm1}\n{xmm2l} {xmm2}\n{xmm3l} {xmm3}\n" "{xmm4l} {xmm4}\n{xmm5l} {xmm5}\n{xmm6l} {xmm6}\n{xmm7l} {xmm7}\n" ), 'fpu': ( "{st0l} {st0}\n{st1l} {st1}\n{st2l} {st2}\n{st3l} {st2}\n" "{st4l} {st4}\n{st5l} {st5}\n{st6l} {st6}\n{st7l} {st7}\n" ) } }, 'arm': { 'horizontal': { 'general': ( "{pcl} {pc} {spl} {sp} {lrl} {lr} {cpsrl} {cpsr}\n" "{r0l} {r0} {r1l} {r1} {r2l} {r2} {r3l} {r3} {r4l} {r4} {r5l} {r5} {r6l} {r6}\n" "{r7l} {r7} {r8l} {r8} {r9l} {r9} {r10l} {r10} {r11l} {r11} {r12l} {r12}" ), }, 'vertical': { 'general': ( "{pcl} {pc}{pcinfo}\n" "{spl} {sp}{spinfo}\n" "{lrl} {lr}{lrinfo}\n" "{r0l} {r0}{r0info}\n" "{r1l} {r1}{r1info}\n" "{r2l} {r2}{r2info}\n" "{r3l} {r3}{r3info}\n" "{r4l} {r4}{r4info}\n" "{r5l} {r5}{r5info}\n" "{r6l} {r6}{r6info}\n" "{r7l} {r7}{r7info}\n" "{r8l} {r8}{r8info}\n" "{r9l} {r9}{r9info}\n" "{r10l} {r10}{r10info}\n" "{r11l} {r11}{r11info}\n" "{r12l} {r12}{r12info}\n" "{cpsrl}{cpsr}" ), } }, 'powerpc': { 'horizontal': { 'general': ( "{pcl} {pc} {crl} {cr} {lrl} {lr} {msrl} {msr} {ctrl} {ctr}\n" "{r0l} {r0} {r1l} {r1} {r2l} {r2} {r3l} {r3}\n" "{r4l} {r4} {r5l} {r5} {r6l} {r6} {r7l} {r7}\n" "{r8l} {r8} {r9l} {r9} {r10l} {r10} {r11l} {r11}\n" "{r12l} {r12} {r13l} {r13} {r14l} {r14} {r15l} {r15}\n" "{r16l} {r16} {r17l} {r17} {r18l} {r18} {r19l} {r19}\n" "{r20l} {r20} {r21l} {r21} {r22l} {r22} {r23l} {r23}\n" "{r24l} {r24} {r25l} {r25} {r26l} {r26} {r27l} {r27}\n" "{r28l} {r28} {r29l} {r29} {r30l} {r30} {r31l} {r31}" ), }, 'vertical': { 'general': ( "{pcl} {pc}{pcinfo}\n" "{crl} {cr}{crinfo}\n" "{lrl} {lr}{lrinfo}\n" "{msrl} {msr}{msrinfo}\n" "{ctrl} {ctr}{ctrinfo}\n" "{r0l} {r0}{r0info}\n" "{r1l} {r1}{r1info}\n" "{r2l} {r2}{r2info}\n" "{r3l} {r3}{r3info}\n" "{r4l} {r4}{r4info}\n" "{r5l} {r5}{r5info}\n" "{r6l} {r6}{r6info}\n" "{r7l} {r7}{r7info}\n" "{r8l} {r8}{r8info}\n" "{r9l} {r9}{r9info}\n" "{r10l} {r10}{r10info}\n" "{r11l} {r11}{r11info}\n" "{r12l} {r12}{r12info}\n" "{r13l} {r13}{r13info}\n" "{r14l} {r14}{r14info}\n" "{r15l} {r15}{r15info}\n" "{r16l} {r16}{r16info}\n" "{r17l} {r17}{r17info}\n" "{r18l} {r18}{r18info}\n" "{r19l} {r19}{r19info}\n" "{r20l} {r20}{r20info}\n" "{r21l} {r21}{r21info}\n" "{r22l} {r22}{r22info}\n" "{r23l} {r23}{r23info}\n" "{r24l} {r24}{r24info}\n" "{r25l} {r25}{r25info}\n" "{r26l} {r26}{r26info}\n" "{r27l} {r27}{r27info}\n" "{r28l} {r28}{r28info}\n" "{r29l} {r29}{r29info}\n" "{r30l} {r30}{r30info}\n" "{r31l} {r31}{r31info}" ), } }, 'arm64': { 'horizontal': { 'general': ( "{pcl} {pc}{pcinfo}\n" "{spl} {sp}{spinfo}\n" "{x0l} {x0}{x0info}\n" "{x1l} {x1}{x1info}\n" "{x2l} {x2}{x2info}\n" "{x3l} {x3}{x3info}\n" "{x4l} {x4}{x4info}\n" "{x5l} {x5}{x5info}\n" "{x6l} {x6}{x6info}\n" "{x7l} {x7}{x7info}\n" "{x8l} {x8}{x8info}\n" "{x9l} {x9}{x9info}\n" "{x10l} {x10}{x10info}\n" "{x11l} {x11}{x11info}\n" "{x12l} {x12}{x12info}\n" "{x13l} {x13}{x13info}\n" "{x14l} {x14}{x14info}\n" "{x15l} {x15}{x15info}\n" "{x16l} {x16}{x16info}\n" "{x17l} {x17}{x17info}\n" "{x18l} {x18}{x18info}\n" "{x19l} {x19}{x19info}\n" "{x20l} {x20}{x20info}\n" "{x21l} {x21}{x21info}\n" "{x22l} {x22}{x22info}\n" "{x23l} {x23}{x23info}\n" "{x24l} {x24}{x24info}\n" "{x25l} {x25}{x25info}\n" "{x26l} {x26}{x26info}\n" "{x27l} {x27}{x27info}\n" "{x28l} {x28}{x28info}\n" "{x29l} {x29}{x29info}\n" "{x30l} {x30}{x30info}\n" ), }, 'vertical': { 'general': ( "{pcl} {pc}{pcinfo}\n" "{spl} {sp}{spinfo}\n" "{x0l} {x0}{x0info}\n" "{x1l} {x1}{x1info}\n" "{x2l} {x2}{x2info}\n" "{x3l} {x3}{x3info}\n" "{x4l} {x4}{x4info}\n" "{x5l} {x5}{x5info}\n" "{x6l} {x6}{x6info}\n" "{x7l} {x7}{x7info}\n" "{x8l} {x8}{x8info}\n" "{x9l} {x9}{x9info}\n" "{x10l} {x10}{x10info}\n" "{x11l} {x11}{x11info}\n" "{x12l} {x12}{x12info}\n" "{x13l} {x13}{x13info}\n" "{x14l} {x14}{x14info}\n" "{x15l} {x15}{x15info}\n" "{x16l} {x16}{x16info}\n" "{x17l} {x17}{x17info}\n" "{x18l} {x18}{x18info}\n" "{x19l} {x19}{x19info}\n" "{x20l} {x20}{x20info}\n" "{x21l} {x21}{x21info}\n" "{x22l} {x22}{x22info}\n" "{x23l} {x23}{x23info}\n" "{x24l} {x24}{x24info}\n" "{x25l} {x25}{x25info}\n" "{x26l} {x26}{x26info}\n" "{x27l} {x27}{x27info}\n" "{x28l} {x28}{x28info}\n" "{x29l} {x29}{x29info}\n" "{x30l} {x30}{x30info}\n" ), } } } FLAG_BITS = {'c': 0, 'p': 2, 'a': 4, 'z': 6, 's': 7, 't': 8, 'i': 9, 'd': 10, 'o': 11} FLAG_TEMPLATE = "{o} {d} {i} {t} {s} {z} {a} {p} {c}" XMM_INDENT = 7 last_regs = None last_flags = None @classmethod def configure_subparser(cls, subparsers): sp = subparsers.add_parser('registers', help='register values', aliases=('r', 'reg', 'register')) VoltronView.add_generic_arguments(sp) sp.set_defaults(func=RegisterView) g = sp.add_mutually_exclusive_group() g.add_argument('--horizontal', '-o', dest="orientation", action='store_const', const="horizontal", help='horizontal orientation') g.add_argument('--vertical', '-v', dest="orientation", action='store_const', const="vertical", help='vertical orientation (default)') sp.add_argument('--general', '-g', dest="sections", action='append_const', const="general", help='show general registers') sp.add_argument('--no-general', '-G', dest="sections", action='append_const', const="no_general", help='hide general registers') sp.add_argument('--sse', '-s', dest="sections", action='append_const', const="sse", help='show sse registers') sp.add_argument('--no-sse', '-S', dest="sections", action='append_const', const="no_sse", help='hide sse registers') sp.add_argument('--fpu', '-p', dest="sections", action='append_const', const="fpu", help='show fpu registers') sp.add_argument('--no-fpu', '-P', dest="sections", action='append_const', const="no_fpu", help='hide fpu registers') sp.add_argument('--hide-info', '-I',dest="hide_info", action='store_true', help='hide extra info (pointer derefs, ascii) for registers', default=False) def __init__(self, *args, **kwargs): super(RegisterView, self).__init__(*args, **kwargs) self.str_upper = str.upper def apply_cli_config(self): super(RegisterView, self).apply_cli_config() if self.args.orientation is not None: self.config.orientation = self.args.orientation if self.args.sections is not None: a = filter(lambda x: 'no_' + x not in self.args.sections and not x.startswith('no_'), list(self.config.sections) + self.args.sections) config_sections = [] for sec in a: if sec not in config_sections: config_sections.append(sec) self.config.sections = config_sections def build_requests(self): return [ api_request('targets', block=self.block), api_request('disassemble', count=1, block=self.block), api_request('registers', block=self.block) ] def render(self, results): error = None t_res, d_res, r_res = results formatter = pygments.formatters.get_formatter_by_name(self.config.format.pygments_formatter, style=self.config.format.pygments_style) def format(tok, tik=None): if tik: tok = (tok, tik) if isinstance(tok, tuple): return pygments.format([tok], formatter) else: return pygments.format(tok, formatter) self.f = format if t_res and t_res.is_error: error = t_res.message elif t_res is None or t_res and len(t_res.targets) == 0: error = "No such target" else: arch = t_res.targets[0]['arch'] self.curr_arch = arch # ensure the architecture is supported if arch not in self.FORMAT_INFO: error = "Architecture '{}' not supported".format(arch) else: # get next instruction try: self.curr_inst = d_res.disassembly.strip().split('\n')[-1].split(':')[1].strip() except: self.curr_inst = None # get registers for target if r_res.is_error: error = r_res.message # if everything is ok, render the view if not error: # Build template template = '\n'.join(map(lambda x: self.TEMPLATES[arch][self.config.orientation][x], self.config.sections)) # Process formatting settings addr_size = t_res.targets[0]['addr_size'] data = defaultdict(lambda: 'n/a') data.update(r_res.registers) formats = self.FORMAT_INFO[arch] formatted = {} for fmt in formats: # Apply defaults where they're missing fmt = dict(list(self.config.format.items()) + list(fmt.items())) # Format the data for each register for reg in fmt['regs']: # Format the label label = fmt['label_format'].format(reg) if fmt['label_func'] != None: formatted[reg + 'l'] = getattr(self, fmt['label_func'])(str(label)) if fmt['label_colour_en']: formatted[reg + 'l'] = format(Name.Label, formatted[reg + 'l']) # Format the value val = data[reg] if isinstance(val, STRTYPES): temp = fmt['value_format'].format(0) if len(val) < len(temp): val += (len(temp) - len(val)) * ' ' formatted_reg = format(Text, val) else: token = Text if self.last_regs is None or self.last_regs is not None and val != self.last_regs[reg]: token = Error formatted_reg = val if fmt['value_format'] != None and isinstance(formatted_reg, NumberType): formatted_reg = fmt['value_format'].format(formatted_reg) if fmt['value_func'] != None: if isinstance(fmt['value_func'], STRTYPES): formatted_reg = getattr(self, fmt['value_func'])(formatted_reg) else: formatted_reg = fmt['value_func'](formatted_reg) formatted_reg = format(token, formatted_reg) if fmt['format_name'] == None: formatted[reg] = formatted_reg else: formatted[fmt['format_name']] = formatted_reg # Format the info if not self.args.hide_info: info = "" try: l = {2: 'H', 4: 'L', 8: 'Q'}[t_res.targets[0]['addr_size']] x = '{}{}'.format(('<' if t_res.targets[0]['byte_order'] == 'little' else '>'), l) chunk = struct.pack(x, data[reg]) printable_filter = ''.join([(len(repr(chr(x))) == 3) and chr(x) or '.' for x in range(256)]) ascii_str = ''.join(["%s" % ((x <= 127 and printable_filter[x]) or '.') for x in six.iterbytes(chunk)]) pipe = format(Punctuation, '|') info += ' ' + pipe + ' ' + ascii_str + ' ' + pipe except: pass try: fmtd = [(Punctuation, ' => ')] for t, item in r_res.deref[reg][1:]: if t == "pointer": fmtd.append((Number.Hex, self.format_address(item, size=addr_size, pad=False))) elif t == "string": item = item.replace('\n', '\\n') fmtd.append((String.Double, '"' + item + '"')) elif t == "unicode": item = item.replace('\n', '\\n') fmtd.append((String.Double, 'u"' + item + '"')) elif t == "symbol": fmtd.append((Name.Function, '`' + item + '`')) elif t == "circular": fmtd.append((Text, '(circular)')) fmtd.append((Punctuation, ' => ')) if len(r_res.deref[reg][1:]): info += format(fmtd[:-1]) except KeyError: pass except IndexError: pass else: info = '' formatted[reg + 'info'] = info # Prepare output log.debug('Formatted: ' + str(formatted)) self.body = template.format(**formatted) # Store the regs self.last_regs = data else: # Set body to error message if appropriate self.body = format(Error, error) # Prepare headers and footers height, width = self.window_size() self.title = '[regs:{}]'.format('|'.join(self.config.sections)) if len(self.title) > width: self.title = '[regs]' # Call parent's render method super(RegisterView, self).render(results) def format_address(self, address, size=8, pad=True, prefix='0x'): fmt = '{:' + ('0=' + str(size * 2) if pad else '') + 'X}' addr_str = fmt.format(address) if prefix: addr_str = prefix + addr_str return addr_str def format_flags(self, val): values = {} # Get formatting info for flags if self.curr_arch == 'x86_64': reg = 'rflags' elif self.curr_arch == 'x86': reg = 'eflags' fmt = dict(list(self.config.format.items()) + list(list(filter(lambda x: reg in x['regs'], self.FORMAT_INFO[self.curr_arch]))[0].items())) # Handle each flag bit val = int(val, 10) formatted = {} for flag in self.FLAG_BITS.keys(): values[flag] = (val & (1 << self.FLAG_BITS[flag]) > 0) log.debug("Flag {} value {} (for flags 0x{})".format(flag, values[flag], val)) formatted[flag] = str.upper(flag) if values[flag] else flag if self.last_flags is not None and self.last_flags[flag] != values[flag]: token = Error else: token = Text formatted[flag] = self.f(token, formatted[flag]) # Store the flag values for comparison self.last_flags = values # Format with template flags = self.FLAG_TEMPLATE.format(**formatted) return self.f(Text, '[ ') + flags + self.f(Text, ' ]') def format_jump(self, val): # Grab flag bits val = int(val, 10) values = {} for flag in self.FLAG_BITS.keys(): values[flag] = (val & (1 << self.FLAG_BITS[flag]) > 0) # If this is a jump instruction, see if it will be taken j = None if self.curr_inst: inst = self.curr_inst.split()[0] if inst in ['ja', 'jnbe']: if not values['c'] and not values['z']: j = (True, '!c && !z') else: j = (False, 'c || z') elif inst in ['jae', 'jnb', 'jnc']: if not values['c']: j = (True, '!c') else: j = (False, 'c') elif inst in ['jb', 'jc', 'jnae']: if values['c']: j = (True, 'c') else: j = (False, '!c') elif inst in ['jbe', 'jna']: if values['c'] or values['z']: j = (True, 'c || z') else: j = (False, '!c && !z') elif inst in ['jcxz', 'jecxz', 'jrcxz']: if self.get_arch() == 'x64': cx = regs['rcx'] elif self.get_arch() == 'x86': cx = regs['ecx'] if cx == 0: j = (True, cx + '==0') else: j = (False, cx + '!=0') elif inst in ['je', 'jz']: if values['z']: j = (True, 'z') else: j = (False, '!z') elif inst in ['jnle', 'jg']: if not values['z'] and values['s'] == values['o']: j = (True, '!z && s==o') else: j = (False, 'z || s!=o') elif inst in ['jge', 'jnl']: if values['s'] == values['o']: j = (True, 's==o') else: j = (False, 's!=o') elif inst in ['jl', 'jnge']: if values['s'] == values['o']: j = (False, 's==o') else: j = (True, 's!=o') elif inst in ['jle', 'jng']: if values['z'] or values['s'] == values['o']: j = (True, 'z || s==o') else: j = (False, '!z && s!=o') elif inst in ['jne', 'jnz']: if not values['z']: j = (True, '!z') else: j = (False, 'z') elif inst in ['jno']: if not values['o']: j = (True, '!o') else: j = (False, 'o') elif inst in ['jnp', 'jpo']: if not values['p']: j = (True, '!p') else: j = (False, 'p') elif inst in ['jns']: if not values['s']: j = (True, '!s') else: j = (False, 's') elif inst in ['jo']: if values['o']: j = (True, 'o') else: j = (False, '!o') elif inst in ['jp', 'jpe']: if values['p']: j = (True, 'p') else: j = (False, '!p') elif inst in ['js']: if values['s']: j = (True, 's') else: j = (False, '!s') # Construct message if j is not None: taken, reason = j if taken: jump = 'Jump ({})'.format(reason) else: jump = '!Jump ({})'.format(reason) else: jump = '' # Pad out jump = '{:^19}'.format(jump) # Colour if j is not None: jump = self.f(Error, jump) else: jump = self.f(Text, jump) return self.f(Text, '[') + jump + self.f(Text, ']') def format_xmm(self, val): if self.config.orientation == 'vertical': height, width = self.window_size() if width < len(SHORT_ADDR_FORMAT_128.format(0)) + self.XMM_INDENT: return val[:16] + '\n' + ' ' * self.XMM_INDENT + val[16:] else: return val[:16] + ':' + val[16:] else: return val def format_fpu(self, val): if self.config.orientation == 'vertical': return val else: return val class RegisterViewPlugin(ViewPlugin): plugin_type = 'view' name = 'register' view_class = RegisterView voltron-master/voltron/rdb.py000066400000000000000000000014661360575504700167210ustar00rootroot00000000000000import pdb import socket import sys # Trying to debug a quirk in some code that gets called async by {ll,g}db? # # from .rdb import Rdb # Rdb().set_trace() # # Then: telnet localhost 4444 socks = {} # Only bind the socket once def _sock(port): if port in socks: return socks[port] s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind(("127.0.0.1", port)) socks[port] = s return s class Rdb(pdb.Pdb): def __init__(self, port=4444): self.old_stdout = sys.stdout self.old_stdin = sys.stdin self.skt = _sock(port) self.skt.listen(1) (clientsocket, address) = self.skt.accept() handle = clientsocket.makefile('rw') pdb.Pdb.__init__(self, completekey='tab', stdin=handle, stdout=handle) sys.stdout = sys.stdin = handle voltron-master/voltron/repl.py000066400000000000000000000041051360575504700171050ustar00rootroot00000000000000from .core import Client class REPLClient(Client): """ A Voltron client for use in the Python REPL (e.g. Calculon). """ def __getattr__(self, key): try: res = self.perform_request('registers', registers=[key]) if res.is_success: return res.registers[key] else: print("Error getting register: {}".format(res.message)) except Exception as e: print("Exception getting register: {}".format(repr(e))) def __getitem__(self, key): try: d = {} if isinstance(key, slice): d['address'] = key.start d['length'] = key.stop - key.start else: d['address'] = key d['length'] = 1 res = self.perform_request('memory', **d) if res.is_success: return res.memory else: print("Error reading memory: {}".format(res.message)) except Exception as e: print("Exception reading memory: {}".format(repr(e))) def __setitem__(self, key, value): try: d = {} if isinstance(key, slice): d['address'] = key.start d['value'] = ((key.stop - key.start) * value)[:key.stop - key.start] else: d['address'] = key d['value'] = value res = self.perform_request('write_memory', **d) if res.is_success: return None else: print("Error writing memory: {}".format(res.message)) except Exception as e: print("Exception writing memory: {}".format(repr(e))) def __call__(self, command): try: res = self.perform_request('command', command=command) if res.is_success: return res.output else: print("Error executing command: {}".format(res.message)) except Exception as e: print("Exception executing command: {}".format(repr(e))) V = REPLClient() voltron-master/voltron/styles.py000066400000000000000000000045161360575504700174740ustar00rootroot00000000000000from pygments.style import Style from pygments.token import Token, Comment, Name, Keyword, Generic, Number, Operator, String, Punctuation, Error BASE03 = '#002b36' BASE02 = '#073642' BASE01 = '#586e75' BASE00 = '#657b83' BASE0 = '#839496' BASE1 = '#93a1a1' BASE2 = '#eee8d5' BASE3 = '#fdf6e3' YELLOW = '#b58900' ORANGE = '#cb4b16' RED = '#dc322f' MAGENTA = '#d33682' VIOLET = '#6c71c4' BLUE = '#268bd2' CYAN = '#2aa198' GREEN = '#859900' class VolarizedStyle(Style): background_color = BASE03 styles = { Keyword: GREEN, Keyword.Constant: ORANGE, Keyword.Declaration: BASE1, Keyword.Namespace: ORANGE, # Keyword.Pseudo Keyword.Reserved: BLUE, Keyword.Type: VIOLET, Name: BASE1, Name.Attribute: BASE1, Name.Builtin: YELLOW, Name.Builtin.Pseudo: YELLOW, Name.Class: BLUE, Name.Constant: ORANGE, Name.Decorator: BLUE, Name.Entity: ORANGE, Name.Exception: YELLOW, Name.Function: BLUE, Name.Label: BASE01, # Name.Namespace # Name.Other Name.Tag: BLUE, Name.Variable: BLUE, # Name.Variable.Class # Name.Variable.Global # Name.Variable.Instance # Literal # Literal.Date String: BASE1, String.Backtick: BASE01, String.Char: BASE1, String.Doc: CYAN, # String.Double String.Escape: RED, String.Heredoc: CYAN, # String.Interpol # String.Other String.Regex: RED, # String.Single # String.Symbol Number: CYAN, # Number.Float # Number.Hex # Number.Integer # Number.Integer.Long # Number.Oct Operator: GREEN, Operator.Word: GREEN, Punctuation: BASE00, Comment: BASE00, # Comment.Multiline Comment.Preproc: GREEN, # Comment.Single Comment.Special: GREEN, # Generic Generic.Deleted: CYAN, Generic.Emph: 'italic', Generic.Error: RED, Generic.Heading: ORANGE, Generic.Inserted: GREEN, # Generic.Output Generic.Prompt: RED, Generic.Strong: 'bold', Generic.Subheading: ORANGE, # Generic.Traceback Token: BASE1, Token.Other: ORANGE, Error: RED } voltron-master/voltron/view.py000066400000000000000000000344211360575504700171210ustar00rootroot00000000000000from __future__ import print_function import os import sys import logging import pprint import re import signal import argparse import subprocess import socket from blessed import Terminal try: import urwid except: urwid = None try: import cursor except: cursor = None import voltron from .core import Client from .colour import fmt_esc from .plugin import * from .api import BlockingNotSupportedError log = logging.getLogger("view") ADDR_FORMAT_128 = '0x{0:0=32X}' ADDR_FORMAT_64 = '0x{0:0=16X}' ADDR_FORMAT_32 = '0x{0:0=8X}' ADDR_FORMAT_16 = '0x{0:0=4X}' SHORT_ADDR_FORMAT_128 = '{0:0=32X}' SHORT_ADDR_FORMAT_64 = '{0:0=16X}' SHORT_ADDR_FORMAT_32 = '{0:0=8X}' SHORT_ADDR_FORMAT_16 = '{0:0=4X}' # https://gist.github.com/sampsyo/471779 class AliasedSubParsersAction(argparse._SubParsersAction): class _AliasedPseudoAction(argparse.Action): def __init__(self, name, aliases, help): dest = name if aliases: dest += ' (%s)' % ','.join(aliases) sup = super(AliasedSubParsersAction._AliasedPseudoAction, self) sup.__init__(option_strings=[], dest=dest, help=help) def add_parser(self, name, **kwargs): aliases = kwargs.pop('aliases', []) parser = super(AliasedSubParsersAction, self).add_parser(name, **kwargs) # Make the aliases work. for alias in aliases: self._name_parser_map[alias] = parser # Make the help text reflect them, first removing old help entry. if 'help' in kwargs: help = kwargs.pop('help') self._choices_actions.pop() pseudo_action = self._AliasedPseudoAction(name, aliases, help) self._choices_actions.append(pseudo_action) return parser class AnsiString(object): def __init__(self, string): chunks = string.split('\033') self.chars = [] chars = list(chunks[0]) if len(chunks) > 1: for chunk in chunks[1:]: if chunk == '(B': chars.append('\033' + chunk) else: p = chunk.find('m') if p > 0: chars.append('\033' + chunk[:p + 1]) chars.extend(list(chunk[p + 1:])) else: chars.extend(list(chunk)) # roll up ansi sequences ansi = [] for char in chars: if char[0] == '\033': ansi.append(char) else: self.chars.append(''.join(ansi) + char) ansi = [] if len(self.chars) > 2: if self.chars[-1][0] == '\033': self.chars[-2] = self.chars[-2] + self.chars[-1] self.chars = self.chars[:-1] def __getitem__(self, key): if isinstance(key, slice): return ''.join(self.chars[key.start:key.stop]) + '\033[0m' else: return self.chars[key] def __str__(self): return ''.join(self.chars) def __len__(self): return len(self.chars) def clean(self): return re.sub('\033\[.{1,2}m', '', str(self)) def requires_async(func): def inner(self, *args, **kwargs): if not self.block: return func(self, *args, **kwargs) else: sys.stdout.write('\a') sys.stdout.flush() return inner class VoltronView (object): """ Parent class for all views. Views may or may not support blocking mode. LLDB can be queried from a background thread, which means requests can (if it makes sense) be fulfilled as soon as they're received. GDB cannot be queried from a background thread, so requests have to be queued and dispatched by the main thread when the debugger stops. This means that GDB requires blocking mode. In blocking mode, the view's `render` method must make a single call to Client's send_request/send_request method, with the request(s) flagged as blocking (block=True). If a view (or, more likely, a more complex client) has multiple calls to send_request/send_requests, then it cannot support blocking mode and should be flagged as such by including `supports_blocking = False` (see below, views are flagged as supporting blocking by default). All of the included Voltron views support blocking mode, but this distinction has been made so that views can be written for LLDB only without making compromises to support GDB. """ view_type = None block = False supports_blocking = True @classmethod def add_generic_arguments(cls, sp): sp.add_argument('--show-header', '-e', dest="header", action='store_true', help='show header', default=None) sp.add_argument('--hide-header', '-E', dest="header", action='store_false', help='hide header') sp.add_argument('--show-footer', '-f', dest="footer", action='store_true', help='show footer', default=None) sp.add_argument('--hide-footer', '-F', dest="footer", action='store_false', help='hide footer') sp.add_argument('--name', '-n', action='store', help='named configuration to use', default=None) @classmethod def configure_subparser(cls, subparsers): if hasattr(cls._plugin, 'aliases'): sp = subparsers.add_parser(cls.view_type, aliases=cls._plugin.aliases, help='{} view'.format(cls.view_type)) else: sp = subparsers.add_parser(cls.view_type, help='{} view'.format(cls.view_type)) VoltronView.add_generic_arguments(sp) sp.set_defaults(func=cls) def __init__(self, args={}, loaded_config={}): log.debug('Loading view: ' + self.__class__.__name__) self.client = Client(url=voltron.config.view.api_url) self.pm = None self.args = args self.loaded_config = loaded_config self.server_version = None # Commonly set by render method for header and footer formatting self.title = '' self.info = '' self.body = '' # Build configuration self.build_config() log.debug("View config: " + pprint.pformat(self.config)) log.debug("Args: " + str(self.args)) # Let subclass do any setup it needs to do self.setup() # Override settings from command line args if self.args.header != None: self.config.header.show = self.args.header if self.args.footer != None: self.config.footer.show = self.args.footer # Setup a SIGWINCH handler so we do reasonable things on resize try: signal.signal(signal.SIGWINCH, self.sigwinch_handler) except: pass def build_config(self): # Start with all_views config self.config = self.loaded_config.view.all_views # Add view-specific config self.config.type = self.view_type name = self.view_type + '_view' if 'view' in self.loaded_config and name in self.loaded_config.view: self.config.update(self.loaded_config.view[name]) # Add named config if self.args.name != None: self.config.update(self.loaded_config[self.args.name]) # Apply view-specific command-line args self.apply_cli_config() def apply_cli_config(self): if self.args.header != None: self.config.header.show = self.args.header if self.args.footer != None: self.config.footer.show = self.args.footer def setup(self): log.debug('Base view class setup') def cleanup(self): log.debug('Base view class cleanup') def render(self, results): log.warning('Might wanna implement render() in this view eh') def do_render(error=None): pass def should_reconnect(self): try: return self.loaded_config.view.reconnect except: return True def sigwinch_handler(self, sig, stack): pass class TerminalView (VoltronView): valid_key_funcs = ["exit", "page_up", "page_down", "page_up", "page_down", "line_up", "line_down", "reset"] def __init__(self, *a, **kw): self.init_window() self.trunc_top = False self.done = False self.last_body = None self.scroll_offset = 0 super(TerminalView, self).__init__(*a, **kw) def init_window(self): self.t = Terminal() print(self.t.civis) if cursor: cursor.hide() def cleanup(self): log.debug('Cleaning up view') print(self.t.cnorm) if cursor: cursor.show() def clear(self): # blessed's clear doesn't work properly on windaz # maybe figure out the right way to do it some time os.system('clear') def render(self, results): self.do_render() def do_render(self, error=None): # If we got an error, we'll use that as the body if error: self.body = self.colour(error, 'red') # Refresh the formatted body self.fmt_body = self.body # Pad and truncate the body self.pad_body() self.truncate_body() if self.body != self.last_body: # Clear the screen self.clear() # Print the header, body and footer try: if self.config.header.show: print(self.format_header_footer(self.config.header)) print(self.fmt_body, end='') if self.config.footer.show: print('\n' + self.format_header_footer(self.config.footer), end='') sys.stdout.flush() except IOError as e: # if we get an EINTR while printing, just do it again if e.errno == socket.EINTR: self.do_render() self.last_body = self.body def sigwinch_handler(self, sig, stack): self.do_render() def window_size(self): height, width = subprocess.check_output(['stty', 'size']).split() height = int(height) - int(self.config.pad.pad_bottom) width = int(width) - int(self.config.pad.pad_right) return (height, width) def body_height(self): height, width = self.window_size() if self.config.header.show: height -= 1 if self.config.footer.show: height -= 1 return height def colour(self, text='', colour=None, background=None, attrs=[]): s = '' if colour: s += fmt_esc(colour) if background: s += fmt_esc('b_' + background) if attrs != []: s += ''.join(map(lambda x: fmt_esc('a_' + x), attrs)) s += text s += fmt_esc('reset') return s def format_header_footer(self, c): height, width = self.window_size() # Get values for labels l = getattr(self, c.label_left.name) if c.label_left.name != None else '' r = getattr(self, c.label_right.name) if c.label_right.name != None else '' p = c.pad llen = len(l) rlen = len(r) # Add colour l = self.colour(l, c.label_left.colour, c.label_left.bg_colour, c.label_left.attrs) r = self.colour(r, c.label_right.colour, c.label_right.bg_colour, c.label_right.attrs) p = self.colour(p, c.colour, c.bg_colour, c.attrs) # Build data = l + (width - llen - rlen) * p + r return data def pad_body(self): height, width = self.window_size() lines = self.fmt_body.split('\n') pad = self.body_height() - len(lines) if pad < 0: pad = 0 self.fmt_body += int(pad) * '\n' def truncate_body(self): height, width = self.window_size() # truncate lines horizontally lines = [] for line in self.fmt_body.split('\n'): s = AnsiString(line) if len(s) > width: line = s[:width - 1] + self.colour('>', 'red') lines.append(line) # truncate body vertically if len(lines) > self.body_height(): if self.trunc_top: lines = lines[len(lines) - self.body_height():] else: lines = lines[:self.body_height()] self.fmt_body = '\n'.join(lines) def build_requests(self): """ Build requests for this view. Concrete view subclasses must implement this. """ return [] def run(self): """ Run the view event loop. """ def render(results=[], error=None): if len(results) and not results[0].timed_out: self.render(results) elif error: self.do_render(error=error) # start the client self.client.start(self.build_requests, render) # handle keyboard input try: with self.t.cbreak(): val = '' while not self.done: val = self.t.inkey(timeout=1) if val: self.handle_key(val) except KeyboardInterrupt: self.exit() def handle_key(self, key): """ Handle a keypress. Concrete subclasses can implement this method if custom keypresses need to be handled other than for exit and scrolling. """ try: func = None if key.is_sequence: try: func = self.config.keymap[key.name] except: try: func = self.config.keymap[key.code] except: func = self.config.keymap[str(key)] else: func = self.config.keymap[str(key)] if func in self.valid_key_funcs: getattr(self, func)() except: raise def exit(self): self.cleanup() os._exit(0) @requires_async def page_up(self): self.scroll_offset += self.body_height() self.client.update() @requires_async def page_down(self): self.scroll_offset -= self.body_height() self.client.update() @requires_async def line_up(self): self.scroll_offset += 1 self.client.update() @requires_async def line_down(self): self.scroll_offset -= 1 self.client.update() @requires_async def reset(self): self.scroll_offset = 0 self.client.update()