- Published on
A cheap way to pack, UPX in Android.
- Authors
- Name
- Ajin Deepak
Hey all,
This post isn’t too technical , just wanted to share that we can use UPX to pack Android libraries. UPX has added the support for android some years ago but i don't much people using it. If you don’t know what UPX is, it’s a packer that can be used with ELF files as well as PE files. We can use this to pack library files (.so) for android.
Let's first see a library file without packing.

Let's load it in Ghidra and see.

We can see that ghidra automatically detected the architecture and everything, let's press OK and continue.

Let's take a look at the GeneratePassword function.

We can see the decompilation and assembly pretty neatly as it's a non-packed library.
UPX and Packing
Let's pack this using UPX.
Here I'm using the ARM64 one (Coz I have a MacBook). It works fine. You can try this on an x64 .so
file, but you might need to tweak some things. I'm not sure if it works for ARM32 and x86.
So i have downloaded UPX. My version is upx 5.0.2.
ajindeepak@Ajins-MacBook-Pro UPX % brew install upx
Let's try to pack the .so using UPX.
First we have to give executable persmission to the library.
chmod +x libctf0x1.so
Now let's pack this.
ajindeepak@Ajins-MacBook-Pro arm64-v8a % chmod +x libctf0x1.so
ajindeepak@Ajins-MacBook-Pro arm64-v8a % upx --android-shlib libctf0x1.so -o libctf-packed.so
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2025
UPX 5.0.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jul 20th 2025
File size Ratio Format Name
-------------------- ------ ----------- -----------
note: use --android-shlib if appropriate
290160 -> 195812 67.48% linux/arm64 libctf-packed.so
Packed 1 file.
ajindeepak@Ajins-MacBook-Pro arm64-v8a %
To pack .so
files, it's better to use --android-shlib
.
Let's open this in ghidra and see.


We can see that ghidra is failing to disassemble it properly.
Now we can just put this library back and recompile our APK back. For this you can use APKTool or whatever you are using.
You can also the ultra brute option to make it more compressed.
ajindeepak@Ajins-MacBook-Pro arm64-v8a % upx --android-shlib --ultra-brute libctf0x1.so -o libctf-brute.so
Now you can recompile and test it out.
ajindeepak@Ajins-MacBook-Pro UPX % apktool b app-debug -o file.apk
I: Using Apktool 2.12.0 on app-debug.apk with 8 threads
I: Checking whether sources have changed...
I: Smaling smali folder into classes.dex...
I: Checking whether sources have changed...
I: Smaling smali_classes2 folder into classes2.dex...
I: Checking whether sources have changed...
I: Smaling smali_classes3 folder into classes3.dex...
I: Checking whether sources have changed...
I: Smaling smali_classes4 folder into classes4.dex...
I: Checking whether resources have changed...
I: Building resources with aapt2...
I: Building apk file...
I: Importing lib...
I: Importing unknown files...
I: Built apk into: file.apk
ajindeepak@Ajins-MacBook-Pro UPX % ../apk.sh/apk.sh sign
[*] apk.sh v1.1
[*] home dir is /Users/ajindeepak/.apk.sh
grep: invalid option -- P
usage: grep [-abcdDEFGHhIiJLlMmnOopqRSsUVvwXxZz] [-A num] [-B num] [-C[num]]
[-e pattern] [-f file] [--binary-files=value] [--color=when]
[--context[=num]] [--directories=action] [--label] [--line-buffered]
[--null] [pattern] [file ...]
[*] apktool v exist in /Users/ajindeepak/.apk.sh
[*] apksigner v0.9 exist in /Users/ajindeepak/.apk.sh/sdk_root/build-tools/33.0.1
[*] zipalign exist in /Users/ajindeepak/.apk.sh/sdk_root/build-tools/33.0.1
[*] aapt exist in /Users/ajindeepak/.apk.sh/sdk_root/build-tools/33.0.1
[*] DEXpatch v0.1 exist in /Users/ajindeepak/.apk.sh
Pass the apk name!
./apk sign <apkname.apk>
[>] Bye!
ajindeepak@Ajins-MacBook-Pro UPX % ../apk.sh/apk.sh sign file.apk
[*] apk.sh v1.1
[*] home dir is /Users/ajindeepak/.apk.sh
grep: invalid option -- P
usage: grep [-abcdDEFGHhIiJLlMmnOopqRSsUVvwXxZz] [-A num] [-B num] [-C[num]]
[-e pattern] [-f file] [--binary-files=value] [--color=when]
[--context[=num]] [--directories=action] [--label] [--line-buffered]
[--null] [pattern] [file ...]
[*] apktool v exist in /Users/ajindeepak/.apk.sh
[*] apksigner v0.9 exist in /Users/ajindeepak/.apk.sh/sdk_root/build-tools/33.0.1
[*] zipalign exist in /Users/ajindeepak/.apk.sh/sdk_root/build-tools/33.0.1
[*] aapt exist in /Users/ajindeepak/.apk.sh/sdk_root/build-tools/33.0.1
[*] DEXpatch v0.1 exist in /Users/ajindeepak/.apk.sh
[>] Aligning with zipalign -p 4 ....
[>] Done!
[>] A Keystore exist!
[>] Signing file.apk with apksigner...
with /Users/ajindeepak/.apk.sh/sdk_root/build-tools/33.0.1/apksigner sign --ks /Users/ajindeepak/.apk.sh/my-new.keystore --ks-pass pass:password file.apk-aligned.apk
WARNING: A restricted method in java.lang.System has been called
WARNING: java.lang.System::loadLibrary has been called by org.conscrypt.NativeLibraryUtil in an unnamed module (file:/Users/ajindeepak/.apk.sh/sdk_root/build-tools/33.0.1/lib/apksigner.jar)
WARNING: Use --enable-native-access=ALL-UNNAMED to avoid a warning for callers in this module
WARNING: Restricted methods will be blocked in a future release unless native access is enabled
[>] Done!
[>] file.apk ready!
ajindeepak@Ajins-MacBook-Pro UPX %
It will work fine.
Unpacking UPX
To unpack you can just use UPX itself, let's try that.
ajindeepak@Ajins-MacBook-Pro arm64-v8a % upx -d libctf0x1.so
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2025
UPX 5.0.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jul 20th 2025
File size Ratio Format Name
-------------------- ------ ----------- -----------
290160 <- 187620 64.66% linux/arm64 libctf0x1.so
Unpacked 1 file.
ajindeepak@Ajins-MacBook-Pro arm64-v8a % ls
libctf0x1.so
ajindeepak@Ajins-MacBook-Pro arm64-v8a %
Let's load this in ghidra and see if the dissasembly is successful.

Nice it works ( it should ).
Making the unpacking HARDer
What happens when people try to fuck with the headers. why they are touching it in the first place ?
Right now we just unpacked using the upx itself but what if the people who packed it doesn't want you to unpack it in the first place. Then they will modify the headers. The most common one is people try to change the magic bytes in the file.
Let's see this.
ajindeepak@Ajins-MacBook-Pro arm64-v8a % strings libctf0x1.so| grep -i "UPX"
UPX!
UPX-5.0 wants meX
$Info: This file is packed with the UPX executable packer http://upx.sf.net $
$Id: UPX 5.02 Copyright (C) 1996-2025 the UPX Team. All Rights Reserved. $
UPX!
UPX!
ajindeepak@Ajins-MacBook-Pro arm64-v8a %
This string "UPX!" is the magic byte. We can change it to something else and the unpacking will fail.
Let's do that. I will be using 010 editor.

We have mutliple hits.

i think this is the magic header, let's modify and save it.

Now let's try unpacking it.

We have a error now. Let's check in ghidra if it's unpacked properly.

Well it didn't. Now let's replace all the UPX string with something else.
For some reason why i am trying this in 010 editor it's crashing so let's just use python.
open("libctf0x1-changed-magic.so", "wb").write(open("libctf0x1.so", "rb").read().replace(b"UPX", b"XXX"))
Python 3.11.8 (v3.11.8:db85d51d3e, Feb 6 2024, 18:02:37) [Clang 13.0.0 (clang-1300.0.29.30)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> open("libctf0x1-changed-magic.so", "wb").write(open("libctf0x1.so", "rb").read().replace(b"UPX", b"XXX"))
290160
>>> exit()
ajindeepak@Ajins-MacBook-Pro arm64-v8a % upx -d libctf0x1-changed-magic.so
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2025
UPX 5.0.2 Markus Oberhumer, Laszlo Molnar & John Reiser Jul 20th 2025
File size Ratio Format Name
-------------------- ------ ----------- -----------
upx: libctf0x1-changed-magic.so: NotPackedException: not packed by UPX
Unpacked 0 files.
ajindeepak@Ajins-MacBook-Pro arm64-v8a %
This will run fine in android cause these markers are not used by android for unpacking.
The next question is how to unpack this ? Go through the '!' markers, and add the 'UPX' string back. Yeah i know this is painful.
There are good articles on this, i won't be writing about that here. You can follow the below one.
https://cujo.com/blog/upx-anti-unpacking-techniques-in-iot-malware/
One thing I want to mention is, when you pack these .so files, try to pack all the architectures. If you leave something unpacked, people can easily reverse engineer it or just compile only the architecture they want. These days, most devices are arm64, so you can just pack that, and you can try packing x64 too for emulators. But you have to make some tweaks to make it work. Also, you can strip the symbols in the library too.
Okay that's pretty much it.