Since approximately Android 10,
libart (Android Runtime) has been moved to the
apex (Android Pony EXpress) directory. This was a design decision by Google which allowed them to perform updates to the DVM/ART subsystem without requiring an OEM to push a full update – and leveraging Google Play to push down smaller packages. This is a win for users, the ecosystem, etc - for both speed, security. Though this sometimes presents an issue for non-standard usecases. One of which is some long standing code I use often for debugging and attacking different applications, the native-shim. While it isn’t anything super special, mostly just some boilerplate code and some
dlopen calls, it’s an approach I’ve utilized for almost 10 years.
Nothing really “broke”, just we no longer know where to look for
libart. If we dig into the linker code we will see that the default paths generally look like we would expect;
This is generally speaking, where we would expect to find all the libraries needed, so utilizing
dlopen("libart") seemingly would continue to work. Except now we can see that this is no longer where it is held;
beyond1q:/ # find / -name libart.so
The dead simple fix would be to just ensure the correct path, depending on if the binary is 64 bit or not, is added to our
beyond1q:/data/local/tmp # LD_LIBRARY_PATH=/system/apex/com.android.runtime.release/lib64/ ./shim ./libHelper.so
While the above would suffice, it’s not exactly great. I’m pretty lazy and will easily forget this the next time around. So let’s make this programmatic which brings us to another minor issue. Prior to exploring this problem, I wasn’t positive you could change the
LD_LIBRARY_PATH during execution. Technically, you can in a few different hacky ways, but we will actually opt for not modifying it on the fly, but just setting the variable if needed and re-executing ourselves correctly.
The above snippet allows us to properly detect which path would be required to find
libart, then looks to see if our path contains this already. If not, we set the environment we are currently running in and then re-execute ourselves via
execv and retain the original arugments. This will transparently allow all
dlsym, etc, calls to work as originally intended and have the extra paths we need. This style of code isn’t anything ground shaking, however it took me a while to both track down the issue and figure out a programmatic way of tackling it.
The full commit can be found here for building locally.
Now viola! We can continue running the
native-shim as we always have. Have fun doing more unpacking on newer systems.