As promised in the previous post, Go Assembly on the arm64, I have been working on a very special project for the past couple months, and I’m very pleased to announce the Universal Loader!
This Golang library provides a consistent interface across all platforms for loading shared libraries from memory and without using CGO. When I say “all platforms”, I mean Linux, Windows, and OSX including the new M1 Apple chip.
Until someone tells me differently, I am claiming that this is the very first loader to work on the M1, Golang or not. I haven’t tried it myself yet, but it will likely also work on any POSIX system, like BSD variants, without changes. If you try this on a new platform, let me know!
Additionally, the Linux backend of the loader does not use memfd, and I believe this is the first Golang loader to do that as well. The Linux ramdisk implementation memfd is very easy to use, but it’s also relatively easy to detect.
Consistent Interface
On all platforms, this is a basic example of how to use Universal to load a shared library from memory and call an exported symbol inside it:
|
image, err := ioutil.ReadFile("myLibrary.so") loader, err := universal.NewLoader() if err != nil { log.Fatal(err) } library, err := loader.LoadLibrary("myLibrary", &image) if err != nil { log.Fatal(err) } val, err := library.Call("myFunction", 7) if err != nil { log.Fatal(err) } |
Just pass in your library as a byte array, call LoadLibrary on it and give it a name, then you can Call()
any exported symbol in that library with whatever arguments you want.
All you have to do differently on a different platform is load the right type of library for your platform. On OSX, you would load myLibrary.dyld
, on Linux myLibrary.so
, and on Windows myLibrary.DLL
.
Check out the examples/ folder in the repo and the tests for more details.
Algorithms and References
The Linux and Windows implementations are very straight-forward and based on the same basic algorithms that have been widely used for years, and I used malisal’s excellent repo as a reference, with some minor changes.
For the OSX loader, I referred heavily to MalwareUnicorn’s wonderful training, but I did have to make a few updates. For one thing, dyld is not guaranteed to be the next image in memory after the base image.
Also have to give a heartful thank you to C-Sto and lesnuages, who contributed code to the Windows and OSX loaders, respectively.
Last but not least, this library makes heavy use of our own fork of the debug library, so this would not have been possible without contributions over the years from the whole Binject crew, and it’s a perfect example of the power of the tools we’ve made.
This isn’t the first tool to come out of our work in Binject, and it definitely will not be the last.
Once you get locked into a serious m*****e collection, the tendency is to push it as far as you can.