Using a Typescript Frida-gadget for usb debugging
Recently on a project I was looking to do some “low level” analysis of between an application and a usb device plugged directly into the phone. This wasn’t anything I had ever actually done before, so it took a little bit of digging to figure out what was actually needed. On top of that, it had been a little bit since I had written a frida
gadget. For the heck of it, I decided to just try and write everything in Typescript. Mostly this was a challenge to myself, but also an attempt to see if this would speed up any gadget writing in the future since it would allow me to rely on strong typing — or so I hoped.
Boilerplate gadget code
oleavr, as always, was kind enough to have a good example of what a gadget should look like for frida
which my code is based off of at frida-agent-example. The main benefit I’ve found to utilizing this approach is being able to have VSCode
open on your project, the watch
command running, and the interact repl for your agent also running. Between the typescript bindings and the repl, you can quickly debug and test while just saving a file and having everything recompile at once.
Finding the Usb Device
While splunking in the Android docs, we can find that android.hardware.usb.UsbAccessory.openAccessory() is the likely method we will want to hook. Specifically from the docs;
1 | public ParcelFileDescriptor openAccessory (UsbAccessory accessory) |
So we will want to hook this, intercept the return value of type ParcelFileDescriptor
then utilize the getFd() method. This means, based on the docs, from a hooking/app perspective all the USB interaction is occuring transparently over a file descriptor. Great - so much like any type of file descriptor hooking, we only really care now about a few things;
- What is the file descriptor I care about
- Hook the
read()
andwrite()
methods and filter by the file descriptor.
To perform this hooking in the gadget, we will use essentially the code below;
1 | Java.perform(() => { |
Above the accessoryFD
variable would just be something that is scoped to be accessible from other hooks we create later for the read
and write
functionality.
Stitching it all together
For a read
and write
hook, all we need to do is a simple native Interceptor.attach()
to libc.so
, something like the following;
1 | const libcWrite = Module.findExportByName('libc.so', 'write') |
Now from here on out we will be able to simply dump any of the “traffic” which is being sent to or received from the usb device. Blammo!
Unsolved
In my example code which is linked below, I was able to define and track down all the proper types, except for one. While utilizing a java.lang.Thread
to generate stacktraces - which is something I often to do to highlight “where” in the code some function is called, it is annoyingly hard to figure out how to type this object.
1 | // eslint-disable-next-line @typescript-eslint/ban-types |
While the typescript compiler itself doesn’t seem to have any issues with this (which might be a misnomer since it is frida-compile
doing some of the lifting?) the linter hates this throwing the following error;
1 |
|
Which, to be honest, I’m not sure I fully understand. Obviously it wants a proper type, though none of those seem correct.
The autogenerated documents seem to indicate that this is the actual return value, so I’m pretty unclear on what is happening here.function Java.use<{}>(className: string): Java.Wrapper<{}>
If anyone has ever solved for this, I’d be curious what the solution ends up being.
TLDR - Give me the code
A fully working example to dump read
and writes
between an Android APK and a USB device can be found in this repository, usb-accessory-gadget.