Skip to content

Instantly share code, notes, and snippets.

@DavidBuchanan314
Last active April 15, 2025 14:01
Show Gist options
  • Save DavidBuchanan314/58635425eed2b3ce10f23c5b0c5f98af to your computer and use it in GitHub Desktop.
Save DavidBuchanan314/58635425eed2b3ce10f23c5b0c5f98af to your computer and use it in GitHub Desktop.
JNIC Reversing Notes https://jnic.dev/

I looked at a JAR file protected using JNIC, version jnic.dev v3.6.0. I haven't written a full-auto deobfuscater yet, but these notes should be useful for anyone reversing it.

The first layer is a LZMA2 compressed .dat file, from which a native library is extracted into a temp dir, and then loaded using System.load.

The sample I looked at had 4 different library versions (for different platforms/architectures), and the script I wrote to extract them looks like this:

import lzma

# from JNICLoader.java
offsets = { # (start, end)
	"lib_win_x86_64.dll": (0, 1413120),
	"lib_win_aarch64.dll": (1413120, 2123776),
	"lib_lin_x86_64.so": (2123776, 3487136),
	"lib_lin_aarch64.so": (3487136, 4192248),
}

lzma2_64M = [{
	"id": lzma.FILTER_LZMA2,
	"dict_size": 64 * 1024 * 1024,  # 64 MiB dictionary size (perhaps overkill)
}]

lzmaf = lzma.open("dev/jnic/lib/5f174765-6a65-4541-98b7-7620b554e31d.dat", format=lzma.FORMAT_RAW, filters=lzma2_64M)

for name, (start, end) in offsets.items():
	print(f"decompressing {name}")
	lzmaf.seek(start)
	with open(name, "wb") as outf:
		outf.write(lzmaf.read(end - start))

If you want to use this script, you'll need to adjust the offsets. Or you can just copy the file out of the temp dir...

On initialization (JNI_OnLoad), the native library uses a ChaCha20 variant to generate a keystream of length 0x1337b bytes, saving it into a buffer.

I haven't bothered replicating their ChaCha20 variant yet, I just dumped the keystream from memory. The keystream is used to obfuscate strings and other constants (through simple XORing).

Using Ghidra, I loaded the keystream into memory, pointed the pointer at it (in my case, this pointer was the first thing in the .bss section) and marked it as constant. Ghidra's decompiler does constant folding, thus deobfuscating the strings "for free".

The other native methods, with names formatted like Java_{classname}__00024jnicLoader, end with calls to JNIEnv->RegisterNatives. By inspecting the arguments to this function, you can map the rest of the native functions (which do not have symbol names) with their Java method names and call signatures. (I might automate this process, at some point)

@micartey
Copy link

@glektarssza my condolence. I hope you find a new (and better!) job fast 😊

@glektarssza
Copy link

@micartey Many thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment