-
Notifications
You must be signed in to change notification settings - Fork 8
Home
Driver Attack Platform (working title) is a suite of tools to trace and analyze the communication between userland processes and kernel drivers in order to find and exploit kernel driver vulnerabilities. It aims to be an essential tool for code-assisted pentests or black-box hacking of kernel drivers.
The inspiration for this research stems from a number of device assessment gigs where I had to review a large amount of drivers in a short period of time.
Kernel drivers commonly expose interfaces to userland via the ioctl, read, and write system calls, and sometimes others. Driver interfaces can sometimes be very complex, enough that active testing could find more bugs than through code review alone. For example:
- When the interface requires many fields and it would be burdensome to figure out how to populate them manually.
- When the interface requires file descriptors obtained from other drivers or some other kind of external context.
- When the driver communicates with peripherals, sometimes the bugs are in hardware/firmware.
- When the driver makes TEE service calls based on user inputs, and you don't have TrustZone source code.
- When you don't have a deep understanding of kernel subsystems which the driver communicates with.
The objective of the tool is to provide a kernel driver assessment platform that—with the help of dynamic instrumentation, analysis, and active testing utilities—allows the security researcher to find more bugs, faster!
Users can list the processes that are running on the target device and choose which one(s) they want to trace.
Once the user selects a process to trace, they are presented with a dashboard that is populated with events that are relevant to userland/kernel drivers communication (e.g. ioctl, mmap, write). The goal of this UI is to summarize/aggregate system call events in real-time. The interface should start by presenting the user with a high-level overview of syscall events, but allow the user to drill down in more detail.
- In some cases, applications make many thousands of ioctls and other syscalls in a short period of time. The UI cannot render every single event in real-time for performance reasons. As such system calls need to be grouped/aggregated in a sane manner. When aggregating syscalls it should be done in a way that helps the user prioritize where to drill down in more detail.
- Some metadata could be shown for all system calls, such as fd/filename, timestamp, duration,
return code, and call stack.
- Note: filename can be extracted by listing /proc/pid/fd/ and hooking
open. Some file descriptors are not backed by actual files; we can name these by hooking each syscall that returns a file descriptor.
- Note: filename can be extracted by listing /proc/pid/fd/ and hooking
- ioctl events need to show the following fields:
- mode: read, write, r/w, none
- opcode: some integer chosen by the driver developer to represent the operation
- size: size of arguments in bytes
- param (IN/OUT): arbitrary binary data pass to and returned from driver. This will need its own view.
- Note that these fields may be garbage, as their validity relies on developers properly using the IOC APIs to define ioctl commands. Size in particular can be finnicky, so users should be able to override the autodetected settings.
- clicking on a system call event will in most cases reveal more detailed data,
i.e. binary data relevant to the syscall. By default, it will show a hex dump
of the data, but if users have defined a type that matches the event it will
decode to a human-readable structure.
- ioctl: dump the ioctl argument (request and response)
- read: show the data that was read
- write: show that data that was written
- allow user to filter events of interest by any field.
- similarly to wireshark, allow setting up a "capture filter", for performance, so that events are only saved if they match a filter.
- possibly allow users to write SQL or some query language directly
- track handles for important kernel subsystems - e.g. ashmem, ion, binder - and present them in a manner that gives context for other interesting events.
- when a type that contains pointers matches an event, dereference the pointer and display them somehow.
Users define a structure, map it to a field of a system call event, and define a set of criteria where the structure matches the system call. When a user views an event that matches a type's criteria, its field will be decoded into a human-readable structure.
- pre-package the tool with some known structures, like common subsystems in Android.
- copy/paste struct definition from source code
- bonus - given a kernel source tree, import all structs using static analysis.
- bonus - even better, find structures that are relevant to a command automagically.
-
Protocol analysis/auto type detection - If you don't have the source
code, have the tool be able to try to guess what the structure might be.
There are some common field types / patterns that may be easily identified
or guessed from sampling enough inputs.
- magic header
- version string/bytes
- length
- offset
- type
- command/subcommand opcode
- file descriptor or other opaque handle
- memory address - userland, kernel VA, physical, DMA,
- null-terminated string, length-prefixed string
- array of objects
- pointers
- checksum, signature
- probably a lot more that are specific to driver functionality. e.g. display drivers and wifi drivers likely have common fields.
Given a previous message you've observed, be able to send it again and optionally modify it
- Use frida to inject code in target process so that the user has access to any context in the target.
- Allow users to tamper human-readable structures. The application should re-encode user inputs appropriately before repeating the input.
- Chain multiple system calls
- Have an
lsofwrapper that filters out the things you don't care about - Have a view that lets you see the current open descriptions and memory maps (/proc//maps wrapper)
- PoC generator - similar to CSRF PoC generator in Burp or copy as cURL, generate code or binary that runs an exploit
- Passive scanner - try to detect possible vulnerabilities or insecure patterns
by passively analyzing other tools (proxy, repeater, etc)
- Leaking uninitialized memory
- Leaking kernel addresses
- Race conditions
- Intruder
- Allow user to select strategies for fuzzing inputs.
- Maybe auto-select the best strategy based on field type (e.g. try huge and tiny values for length/offset fields, iterate through a bunch of commands for an opcode field)
- Have a dumb, totally random fuzzing mode. Good for when you have no idea what the structure might be, and a lot of drivers are so bad you can still find bugs this way.
- Have intruder be multi-threaded and try to detect race conditions.
- Or exhaust memory if you keep testing a specific thing over and over.
- Scripting/plugin engine
- Be able to write code that operates directly on the traced inputs and execute on the target process
- Support for other operating systems
User interface
- Is it really possible to make the tool easy to use for non-experts?
- I am not very experienced with UI development, so I am unsure about my ability to write a well-designed, stable, and performant UI.
- Not sure which technology would be best suited for the user interface. Some
suggestions:
- Angular (been playing around with it lately, seems like a good candidate)
- Ava: https://github.com/gizak/termui
- qt (I've used pyqt before and it was not fun)
- Electron
- Burp extension
Performance
- There are cases where you're tracing a process with high value of ioctl calls or that pass a large amount of data to individual ioctl commands. The UI should be responsive enough to handle heavy load.
Tracing
- Tracing/instruction code would be difficult to implemented by hand. Fortunately, Frida provides an excellent framework that makes it easy.
Things that could cause bugs/issues
- Different Android versions
- Different architecture variants (arm32, arm64)
- Difference between Frida versions
- Assumptions about adb running as root or not root
- Install frida and possibly another daemon on the target (typically rooted) device with a few simple adb commands or wrapper script.
- Install the UI on your host
- Open the UI. There should be a very visible indicator as to whether connection to the daemon is successful.
- Select the process(es) that you want to instrument. There should be feedback as to whether tracing succeeds with a helpful error message if not.
- Start seeing ioctl commands from the target process in proxy history. Now you can use any of the application's tools (repeater, type editor, etc)
You typically need to be root.
- Usually this isn't a problem unless you're testing a device where you can't unlock the bootloader or if there's no publicly known way to root it.
- For the pentests we do, typically we're given developer devices so we have a root shell.
- Many Android devices out there support unlocking the boot loader, can root it legitimately then run tools like this.
- Frida supports a non-root mode where it can inject a lib into an APK file then
install that APK file, now you can hook that app without rooting the device.
- So if you know a particular app that talks through a driver, could be a way to do the same sort of hooking.
- This probably isn't useful - when you're testing kernel drivers, a lot of the time the application that consumes it is a system app, service, or daemon - which often makes it quite difficult to tamper.