First, extract the whole APK (it's a renamed ZIP) and check whether there's not a x86 version of the binary, in addition of the ARM. It will depend on the best option to choose.
The following options assume that you have analyzed the binary using a tool such as IDA, Hex-Rays or Hopper and that you could identify the functions that you want to reach.
If you need to emulate the calling conventions between a GLIB-based Linux system and a .so
Android-targeted library, and that emulating the architecture is not a problem, for example because you have a x86 version of the library available in the APK:
One quite common, but sometimes painful option is using libhybris, a thin wrapper around Android's custom libc (Bionic) calling conventions that will allow you to call functions from the reverse engineered JNI from a regular GCC or Clang-compiled GLIBC program. Tutorials for libhybris are not numerous, but you'll find a couple of open source projects on the Internet that rely on it and will serve as sample code.
However, libhybris will emulate the calling conventions but not the architecture.
If you need to emulate both the architecture and the Android ABIs:
- You have a few options here, either running your part of the program on an ARM host - this could be the Android emulator, your smartphone, a cheap ARM VPS, a Raspberry Pi or something else - either using an emulator such as QEMU in user mode (that allows you to emulate a single ELF file in a chrooted environment without emulating a whole system). Also, you'll likely have to cross-compile your code.
If you need to emulate the architecture, the Android ABIs and you wish to have more control over the emulated binary at the expense of parsing the ELF/handling the memory layout with code yourself:
Another option is using Unicorn engine, a library based on QEMU's code that allows you to emulate machine code on a foreign host in a way similar to using a debugger, by reading/writing registers, memory and setting breakpoints.
Unicorn's API is pretty bare-metal: to use it, you parse the elf(5) structure yourself, then, you allocate and write the relevant sections of the binary yourself, you set the registers (PC for the target function, SP at a point with free space, R0, R1 and so forth for the arguments, LR at a point where you set a hook) and your start the emulator.You can find a few easy-to-understand examples here, in C and in Python.