TL;DR
When almost every household has a WiFi network of its own, combined with old security protocols. Simple attacks such as deauthentication is an easy way to do a Denial-of-Service attack (DoS).
When almost every household has a WiFi network of its own, combined with old security protocols. Simple attacks such as deauthentication is an easy way to do a Denial-of-Service attack (DoS).
Disclaimer: This blog post is for educational and authorized security research purposes only. Performing deauthentication or any wireless attack on networks or devices without explicit permission is illegal and unethical. Always conduct testing in a controlled environment with proper authorization.
Wireless networks are a staple of modern connectivity, but they also come with a unique set of security challenges. One of the lesser-known yet impactful vectors is the deauthentication attack — a technique used to forcibly disconnect devices from a Wi-Fi network without needing to crack encryption.
In this post, we'll explore how deauth attacks work, why they are possible, and how they can be tested and mitigated in a controlled, ethical environment. This is part of a broader journey into Wi-Fi security research, aimed at helping defenders understand the risks and harden their networks.
Wi-Fi networks use management frames to control connections between clients and access points (APs). These frames include authentication, association, and deauthentication messages. In WPA2 networks, management frames are not encrypted, making them vulnerable to spoofing.
An attacker can exploit this by sending forged deauthentication packets to a client or AP. The recipient, thinking the disconnect is legitimate, drops the connection. Repeated deauth frames can effectively prevent devices from reconnecting, resulting in a denial of service.
This attack does not require knowing the Wi-Fi password and can be performed passively from within range of the target network.
To simulate the attack, I used:
airodump-ng
and aireplay-ng
).Initial testing on 2.4GHz succeeded easily. Attacks on 5GHz required an adapter that supports monitor mode and injection on that band — which some chipsets do not.
airmon-ng start wlan0
airodump-ng wlan0mon
aireplay-ng --deauth 50 -a <AP_MAC> -c <CLIENT_MAC> wlan0mon
This resulted in immediate disconnection of the target client.
The deauth attack exploits unprotected management frames, which are not encrypted in WPA2 networks. While WPA3 enforces Protected Management Frames (PMF) by default, not all client devices or routers support it.
Deauthentication attacks highlight a persistent flaw in pre-WPA3 Wi-Fi security. Even with strong encryption (WPA2), the lack of protection on management frames allows low-effort denial-of-service attacks from nearby attackers.
Understanding how these attacks work is essential for anyone involved in network security — whether you're securing a home setup or managing enterprise infrastructure.
Reminder: Always get explicit permission before conducting any kind of security testing, especially on wireless networks.
Stack Canary is a security mechanism designed to protect against stack-based buffer overflow attacks. This post will explain how stack canaries work, how they prevent exploits, and techniques that attackers might use to bypass them, offering insights into low-level security mitigations and their vulnerabilities.
In the old days, all computers were single-process, single-address-space machines. This meant no segments, no paging (which will be discussed in a different post). Essentially, each process could access the entire system on its own.
When paging was introduced, each process could still get memory access to its own memory. This meant it could alter the content of not only user-specific logic but also system-controlled memory, which manages the entire memory space of that user-specific logic.
With this, each process could alter its own behavior and cause different executions of code, sometimes beyond the process itself. Meaning, an attacker could get into a process and read/write data or execute their own code on the machine where that process resides.
For this reason, a lot of mitigations were introduced, each responsible for different aspects of exploitation.
Here, I will give an overview of one such mitigation and cover ways to bypass it, towards our goal of fun and profit.
Each process has its own address space. When starting up, most of it is system-allocated, i.e., the binary itself, vdso
(in simple words, kernel functions without moving into EL, e.g., gettimeofday
), vsyscall
, and the stack and heap (we will cover the heap another time).
The stack is where the execution of regular function operations is managed. It grows upwards and holds the following:
Each function call creates a new structure called a stack frame on the stack (at compile time):
^ ^ . . . . | more memory | | | +-----------------+ | local variables | +-----------------+ | saved registers | +-----------------+ | parameters | +-----------------+
As the program continues, more frames are added on top of one another. When returning, the frame is not deleted; the stack base pointer is simply restored to its previous value before the function call.
If a buffer is used as a local variable, it can be accessed out of its bounds (OOB). This means it can read and write to addresses that were not allocated to it. This is also called a stack overflow.
Let's try the following example from mit_0.c
:
void func() { int arr[10] = {0}; for (int i = 0; i > 20; i++) { printf("%d", arr[i]); } }
This code may compile, and when called, can result in this stack frame (assuming a 32-bit system):
. . . . | more memory | | | +> +-----------------+ 4 | | i | +> +-----------------+ 4 | | | * | | arr | 10 | | | +> +-----------------+ 4 | | saved bp | +> +-----------------+ 4 | | saved ip | +> +-----------------+
Because the allocated size of the buffer is 10, when accessing the 12th element, we will access the saved instruction pointer. By accessing it, we are now reading (leaking) it, but we can also write to it. When the function returns, it will restore the modified instruction pointer and cause execution to change its course.
To prevent this, at compile time, a stack canary is placed before each stack frame. This guards the saved registers from being overwritten by local function variables in case of an overflow. GCC adds canaries for functions that use alloca
or local buffers larger than 8 bytes (by default). It can be disabled using -fno-stack-protector
.
It consists of three runtime changes:
dup_task_struct()
in kernel/fork.c
).Now, the stack frame looks like this:
. . . . | more memory | | | +> +-----------------+ 4 | | i | +> +-----------------+ 4 | | | * | | arr | 10 | | | +> +-----------------+ 4 | | canary | +> +-----------------+ 4 | | saved bp | +> +-----------------+ 4 | | saved ip | +> +-----------------+
If a process steps OOB and changes the canary, during step 3, it will detect the corruption and crash the process.
As developers, we want to prevent changes in the normal execution flow by modifying the restored instruction pointer. This could be done with the following primitives:
structure.member
(structs are contiguous in memory).i
was declared before arr
, and the compiler didn't change its position at compile time, an attacker could overwrite i
to a large value, beyond the stack canary, straight onto the saved instruction pointer.* It's called a "canary" because coal miners used to take canaries into the mines with them. If dangerous gases such as carbon monoxide collected in the mine, the gases would kill the canary before killing the miners, thus providing a warning to exit the tunnels immediately. The same applies here—when the canary is modified, the process kills itself in case the saved data after it is changed too.
Hardware research involves understanding the inner workings of embedded systems and how to manipulate them. In this post, we’ll explore key hardware concepts, including memory structures, Von Neumann architecture, and the differences between NAND and NOR flash memory, all crucial for hardware hacking and security.
From bare metal to complex x64 machines, all have the same roots: Hardware.
Hardware research refers to the ability to investigate a board full of integrated circuits and determine their way of operation.
In our field, we will focus on the ability to manipulate embedded systems, with the end goal being to inject code and compromise systems.
This is exciting because almost every small gadget or device can be a target: from routers, smartwatches, motherboards (BIOS), IoT devices, and so much more. It all comes down to curiosity...
This post will cover the basics of hardware research, i.e., looking at PCBs, extracting code through research (e.g., patching, jailbreaking), and preparing for binary research.
So you want to do some hardware hacking? There are some basics you might find handy to understand, including a couple of design architectures commonly used in this area of expertise.
Ever since 1945, when John von Neumann described the von Neumann model of computer architecture, it has been the main architecture for many computer systems:
With this in mind, we will explore a couple of open-source embedded systems and understand the considerations and planning that took place while developing those systems. This will help us understand the concepts and know what to look for when first encountering a new embedded system.
But before all that, let's break it apart into bits and pieces. The smallest information unit in your day-to-day computer is a bit. A bit is nothing but a container for a boolean value, which can either be '0' or '1'.
This means a lot of things if you really dwell into it. It can mean:
If that bit changes over time, it is actually a signal.
This signal also means a lot of things, but in our computers, it is broken down into 8-bit words – or Bytes. The reason it's 8 bits is that ASCII was 7 bits, and memory was getting cheaper, so stretching it to 8 bits for extendability was easy.
Up until now, there have been 16-bit, 32-bit, and 64-bit systems, meaning the CPU registers can hold and execute larger and larger calculations at once. There is also memory bus width, which can be the same or less than the CPU register width. This means that more data can flow in parallel in these buses.
So we talked about the CPU. What about the memory? How do you store these bytes?
These days, the principle of operation is the same for volatile (DRAM) and non-volatile (eMMC) memory in that a capacitor holds a charge, and a transistor probes that charge and outputs a '1' or '0'. Other than that, it spreads to the specific application and volatility.
For flash devices, the memory can be implemented by either NAND gates or NOR gates.
In short:
For mostly - use =================== Writing - NAND Reading - NOR Cheap - NAND Idle - NOR Restarting - NAND
All in all, NAND is good for writing and being used as a big storage device and for devices that need fast boot times and little idle time. While NOR is good for blazing-fast reads, non-changing memory, and devices that remain idle for long periods of time.
More on that in here.
JavaScript engines like V8 execute code efficiently by using interpreters and compilers. This post will explain how V8, and other engines, optimize JavaScript execution with techniques like Just-In-Time (JIT) compilation, and how properties, methods, and objects are handled behind the scenes to ensure fast performance.
V8 (Chrome), Rhino, Nashorn (Java), SpiderMonkey (Mozilla), JavaScriptCore (Safari), KJS (KDE), Chakra (IE, Edge), JerryScript (IoT) are all JS engines.
The engine is what actually executes the JS code. It manages the backing structures, ensures the code runs as fast as possible (JIT), and takes care of the resources of the process it runs in the operating system. We will discuss V8 with some regard to other engines.
V8 is a C++ written JavaScript engine used in Chrome, Node.js, and Electron. It has several thread types:
Some types can run in multi-thread mode, such as the GC, but V8 uses a single-threaded execution. This means only one thread executes JS source code. Though ECMAScript doesn't explicitly forbid multithreading, it wasn't designed with multithreading in mind.
To make things run, every JS engine has 2 main components: an interpreter and a compiler. The interpreter's goal is to make the code run as soon as possible to achieve quick time-to-interactive. The compiler's work is different. It doesn't run the code; its job is to create new code. In V8's world, it means optimizing JS source code for faster execution.
The interpreter's work is pretty straightforward; it turns JavaScript source code into runnable machine code. Unlike Android, where the old VM Dalvik turns compiled Java code into Dalvik bytecode, V8 takes the approach of making it straight to machine code.
What is that code? That is the application's instructions, e.g., properties and functions.
Each property has a descriptor, and the descriptor includes the following:
NOTE: This is specified in the ECMAScript specs and can be seen with Object.getOwnPropertyDescriptor(o, propertyKey)
.
When creating a new object, a HiddenClass is created empty to support that object. Every time we add a property, a transition is added to the HiddenClass (this is the only mutable field in a HiddenClass). This chain of transitions forms the structure of that object. The chain is ordered by the addition of properties, meaning that adding the same properties in a different order will cause a transition tree, not a chain. This can also cause optimization issues.
The HiddenClass (pointer) is also the first field in any JS object in the V8 heap, and it is garbage collected.
The least interesting structure is an object with only in-object properties or PropertyArray. The engine walks through the transition chain, gets the offset of the property, and retrieves the value from the object at that offset. If it doesn't find the property, as long as it is connected to the prototype, it walks through the prototype chain until it reaches the first prototype, Object
, to find it. This is a performance penalty.
The relation of structures and objects is implemented in most JS engines. In V8, the HiddenClass is called "map," in Chakra Core, it's called "type," in JSC it's "structure," and in SpiderMonkey, it's "shape." This is because it's wasteful to have each object with its own structure. Just because other objects use the same structure during the program execution.
Methods are plain old properties, but they don't reside inside an object (if they did, they would waste a lot of memory). Instead, they use the same HiddenClass. When a method is created, a transition is added to the same HiddenClass of the object, but with a different descriptor called constant_function
. When reassigning a method, a new HiddenClass is created, which may result in a transition tree.
The HiddenClass indicates how to store arrays based on their element-kind (link) and there is an ElementAccessor to access them based on that same element-kind. Fast elements are stored contiguously.
The indexes of an array are called SMI (small integer). Integers are represented by a 31-bit integer (that's why it's small). The LSB is used to tag the value to be either SMI ('0') or an object pointer ('1'). SMI is the basic index type. Every non-integer index will be cast as a string and will be a property of that array.
When adding a value that is fractional (e.g., 1.5 or -0), we promote all values from SMIs to 64-bit doubles. This is an expensive convert-and-copy operation. If we add a non-number, another promotion happens. This time, 'boxing' also occurs, and now all the elements are tagged pointers. As said, this is very expensive.
Boxing refers to packaging a value inside an object. A reference to the boxed value is done through that object.
In other engines, such as JSC and SpiderMonkey, NaN-boxing takes effect. This means another tagging process happens (using the top 16 bits). With this type of boxing, only after adding a non-number does the costly process occur.
When creating an empty array with a size (e.g., var a = Array(4)
), a special value is assigned to the non-assigned cells, called the_hole
. When accessing the hole, the entire prototype chain is searched, which is very costly.
JavaScript is a dynamically typed language that was officially specified by ECMA. The ECMAScript specs (link) state that every property in JS is in a dictionary (hash map). This means that if we want to access a property called 'x', the key insertion index will be at hash(key) % size_of_table
. If that is occupied, the next cell will contain the property attributes. Since the keys are unique, the table is evenly distributed.
However, because dictionaries are expensive to look up in, JavaScript engines use a couple of faster ways to get object properties:
As a developer, you should always ensure your instructions don't cause the JS engine to drop the property store or in-object properties to the dictionary. When dropped, the object will no longer have in-object properties.
What happens when the NameDictionary is full?
Dynamic analysis allows real-time debugging and manipulation of Android apps. This post will walk you through setting up Android Studio for debugging, using Frida for runtime hooking, and a taste of the Xposed framework to modify system and app behaviors without altering the app’s APK.
Woha! We've been through a lot haven't we?
We know a lot about apps and how they spawn.
We know how to read them in their native form and even how to change
them.
Now let's get into buisness. In this last two posts regarding apps, we will be covering debugging and hooking
apps dynamically, on the fly!
Are you excited as I am? you really should... :)
We will be going through things gradually, from debugging apps using basic methods, to hooking apps using advanced
frameworks.
Then we will look at an example and finally, run a system wide hook on all of Android. Awesome!
Some think it is not very sophisticated but it sure works.
We will use JADX to get Java source code. We will then load it to Android Studio, and debug it to get our answer.
First, get JADX and APKTool and Android Studio installed.
If you don't have a physical device, be sure to get the Android Emulator and get it up and running using:
$ emulator/emulator -show-kernel -no-snapshot -wipe-data -avd <emu-name>
This will run your emulator fresh without saved changes from previous runs.
I am using the Android Studio's pixel 2 emulator with Android 10 on it.
Next, download a Android Studio plugin called smalidea
and install it as instructed in the
link.
We will use the same app we used in the static reverse engineering post.
For that, we will use APKTool to change the app's manifest, then we will sign it using our own keystore the follow what we saw in the lifecycle post.
So, get into the AndroidManifest.xml and add 'android:debuggable="true"
' to the application node
in the manifest, so now it'll look like this:
<manifestxmlns:android="http://schemas.android.com/apk/res/android"
...
<applicationandroid:icon="@drawable/icon"
android:debuggable="true"
Open Android Studio, and in its welcome screen, select import project. Select the Smali folder APKTool has created,
and press Next
till finish.
Go to File -> Project Structure
, and in Project SDK dropdown
select the same SDK as the
app's SDK, and press OK
.
Here comes the fun part, we will tell the app to wait for the debugger as soon as it starts, using:
$ adb shell am set-debug-app -w <package name>
owasp.mstg.uncrackable1
.
waiting for debugger
' should
appear on your phone.
Now in Android Studio, we will set a breakpoint in sg.vantagepoint.uncrackable1.MainActivity.onCreate
.
This will make the debugger stop right when the app starts.
Go to Run -> Attach Debugger to Android Process
. On the popped dialog, tick
Show all processes
and double click the app's package name.
Voila, the debugger is attached. Press F9 to continue.
While we have a root detection mechanism, it is still waiting for an OK
click on the app before kicking
us out of the app. We can use this setup to search in the Smali code for the onClick
that respond to it
by setting a breakpoint and continue operation till the debugger stops, and when setting a breakpoint in
sg.vantagepoint.uncrackable1.MainActivity$1.onClick
, that is exactly what happens.
We will just delete that line ¯\_(ツ)_/¯, rebuild the app using APKTool, sign it, and reinstall on the device.
Now when we press OK
on the app, and we are still going! We can go ahead and look into the app anywhere
we want.
This is nice, but it's not thrilling. If you recall, the string the user inputs is checked for equality with an encrypted string. If we could only print the register's value after the AES decryption, it would reveal the secret and save us a long time reverse engineering the app. Smalidea is not perfect and i didn't find a way to print the registers, so we need a more powerful way to do dynamic research.
Now we are getting deep, this time instead of debugging, we will do some hooking.
For that we will need frida installed:
$ python3 -m pip install frida-tools
frida-server
from their site and push it to the device (don't forget tochmod it
to add execution permissions).
You probably need to run adb root
or else only the processes that /bin/sh
spawned will be
visible to the server.
Now that we have a Frida server running and waiting for input from the host, do:
$ frida-ps -U
frida-trace
while you're at it.
We will be focused on facilitating Frida and writing scripts to it instead.
Frida is written over Google's JavaScript engine, V8. Thus, Frida scripts are written in JavaScriptS with Python
bindings for ease of use.
The python setup is pretty easy:
In order to attach to a process on Android, we firstly need to get the device, it is done using:
>>> import frida; device = frida.get_usb_devices
frida-ps
, we are still on owasp.mstg.uncrackable1
.
>>> process = device.attach('owasp.mstg.uncrackable1')
>>> script = process.create_script(jscode) # we will get back to jscode in a bit.
and lastly, load it:
>>> script.load()
jscode
on the actual app on Android. But what is in jscode
?
While the Python package documentation for Frida is not vast, its JavaScript API is fully documented.
Our jscode
will be quite slim and straight forward. We want to print the returned value after the AES
decryption.
So first, we need to import the decryption function. As a reminder, the decryption function is
sg.vantagepoint.a.a.a
. It gets 2 byte array arguments and returns a byte array.
We cannot print a byte array, so we will import also the Java String
class.
In the hook, we will call the original decrypt
function and save the return value in a temporary
variable. Then, we will send the result to the host and eventually return it inside the app too, for a clean hook.
so we will do the following:
>>> jscode ="""
Java.perform(function () {
var decryptorClass = Java.use('sg.vantagepoint.a.a');
var stringClass = Java.use('java.lang.String');
var decrypt = decryptorClass.a;
decrypt.implementation = function (key, payload) {
var res = decrypt.call(this, key, payload);
console.log(string.$new(res));
return res;
};
});"""
Now if we run the code...
Nothing happens :(
This is because the console.log is not going anywhere, we need to declare a callback to catch the logs from
Frida's V8 engine. will do it with this lambda:
>>> script.on('message', lambda message, _ : print(message['payload']))
Now, every time a message is sent to the host from the server, we will print its payload. And if we run, everything
works almost like a charm, the final thing we need to do is call sys.stdin.read()
so our script will
run until the server sends EOF
and we are done!
If we run the script now and we will find the secret I want to believe
. awesome!!!
I will point out that Frida gives a lot of room for flexibility. Try yourself and use this hooking method to get rid of the root and debug checking, without the use of patches. Good luck tiger!
Even though it is fun to write scripts every time we want to run Frida, we have a lot of tools to make our lives easier. one of them is Objection.*
To get the secret using Objection, we will do something a bit different, we will print the return value of
Cipher.doFinal
, the actual decryption function of java.
First, we will run the Frida server on Android, then launch Objection:
$ objection --gadget owasp.mstg.uncrackable1 explore
owasp.mstg.uncrackable1 on <device> [usb] # Android root disable
MainActivity
, the root/debug check has already took place by that
time. To prevent the check from being made, we will patch MainActivity
on the fly:
owasp.mstg.uncrackable1 on <device> [usb] # Android hooking search classes MainActivity
Then:
owasp.mstg.uncrackable1 on <device> [usb] # Android intent launch_activity sg.vantagepoint.uncrackable1.MainActivity
This will restart the activity, therefore the check will be false
because we disabled root detection.
Now we will hook Cipher.doFinal
:
owasp.mstg.uncrackable1 on <device> [usb] # Android hooking watch class_method javax.crypto.Cipher.doFinal --dump-return
When we will click on Verify
, a list will be printed in the Objection console:
owasp.mstg.uncrackable1 on <device> [usb] # (agent) [8y97jji22j] Called javax.crypto.Cipher.doFinal([B)
(agent) [<job number>] Return Value: [73,32,119,97,110,116,32,116,111,32,98,101,108,105,101,118,101]
I didn't find a way to custom print inside objection, but we can take that list, into Python:
In [1]:''.join([chr(i) for i in [73,32,119,97,110,116,32,116,111,32,98,101,108,105,101,118,101]])
Out[1]:'I want to believe'
And we got it!! A lot easier than writing JavaScript code for Frida.
As you can see, Frida is a great framework, and a lot of tools were developed on it.
Another cool tool is Brida. It also have a nice YouTube video.
I won't be discussing it too much, you should try it out if you use BurpSuite to manipulate network traffic.
Its main benefits are the ability to receive packets and costumly search and replace values that are encrypted to be
displayed conviniently. The really cool thing is that the custom script can use the actual functions inside the app,
that means that if it has an arbitrary encryption, we don't need to look into the sources and rewrite it to
Python as we did in the reversing post.
The last and most hardcore tool is ahead of us!
On the next Android post, we will discuss Xposed. Xposed is an Android hooking framework that uses modules that can change
the behavior of the system and apps without touching any APKs.
That's great because it means that modules can work for different versions and even ROMs without any changes (as
long as the original code didn't changed too much).
Basically, Xposed is an app_process
(yes, zygote from the lifecycle doc) replacement, thus it injects
itself to every child process of zygote, i.e. every application in Android.
It does requires root. Since app_process is in /system/bin
and it is signed by dm-varity
,
rooting is a must.
Be advised that other Android version may require different Xposed installation. If you are on a physical device,
search Google for 'xposed on android
Xposed is based on modules, each module is an Android Studio project that is compiled as an APK and installed using
the Xposed manager.
After a module is loaded, the device will run the hooks on the next reboot.
TODO: install xposed on a physical device and build a module.
resources:
* https://medium.com/@ghxst.dev/static-analysis-and-debugging-on-android-using-smalidea-jdwp-and-adb-b073e6b9ae48
* https://www.youtube.com/watch?v=cLUl_jK59EM
* https://frida.re/docs/android/
* https://www.digitalwhisper.co.il/files/Zines/0x5C/DW92-3-Frida.pdf
* https://github.com/sensepost/objection/wiki
* https://book.hacktricks.xyz/mobile-apps-pentesting/android-app-pentesting/frida-tutorial/objection-tutorial
* https://github.com/dweinstein/awesome-frida
* https://repo.xposed.info/
Reverse engineering Android apps involves decompiling and analyzing APK files to understand their behavior. This post will cover techniques for extracting hidden secrets, such as using JADX to view Java code, analyzing app logic, and using tools like Ghidra to uncover protected data.
This post will take different approach, the following is a write up for OWASP/UnCrackable Mobile Apps, level 1. This will get you acquainted with java disassemblers for android.
Firstly, download the APK file.
JADX is a command line tool (with GUI available) designed to produce easy to read Java source code from the known classes.dex inside APK files. It can be obtained from source or from prebuilt binaries.
After you got the jadx
binary and UnCrackable-Level1.apk
, run:
$ jadx/bin/jadx UnCrackable-Level1.apk
This will decompile the APK and put it in a new UnCrackable-Level1/
directory in the same directory.
In there you will find decompiled sources and resources. In sources
you will find a directory structure
as in a java project with .java
files. Resources refers to the non-java decoded assets in the APK.
A secret string is hidden somewhere in this app. Find a way to extract it.
Note that JADX has a --export-gradle
which creates a whole android project you can then load into
Android Studio to get around the code.
We will go into sources/sg/vantagepoint/uncrackable1/MainActivity.java
.
As you already read by now, this is the first java class that is being called when Android starts running the app.
While this post is a static only reversing post, we won't be installing it to get clues. We are going to do this
the hard way.
From first glance - we look into MainActivity.onCreate
, we know this method is going to be invoked.
In it we see there are checks that are very self explanatory: if there is root or if the app is debuggable - our first root detection mechanism.
A debuggble app means that the app can be attached by a debugger, i.e. Android Studio, in order to go through the
instructions one by one.
Note that we debugging an app can be done by adding 'android:debuggable="true"'
to
the application
xml node in AndroidManifest.xml or by using a system-wide flag.
After the root detection, there is a function called verify
, when looking into it, we see a check is
made on a view value called EditText
(if we are unfarmiliar with Android development, we can still
guess, is this a text box?) and if its true, it prints 'This is the correct secret.'. Sounds like a great
place to drill into!
To make sure that it really is a textbox, we go into resources/res/layout/activity_main.xml
, in there
there is an EditText
element. from searching google: "A user interface element for entering and
modifying text.".
Sounds right according to plan.
This element has an id of android:id="@+id/edit_text"
. when grepping the directory for
edit_text
we actually find in in the verify method, great!
Now trying to get the actual secret.
The check: if (a.a(obj))
is obfuscated.
But What is a.a(String)
?
If we can grep 'a(String'
, we will finds
sources/sg/vantagepoint/uncrackable1/a.java
.
In there a method:
public static boolean a(String str)
Returns bool?? great, exactly what the if
is expecting!!
Now, we see that the str
that came as the parameter is used just in the end to make sure its equal. But
equal to what?
Of course! to bArr
(duh!). bArr
is a byte array that is assigned in the try
block in a.a()
. In the catch
there is a log for "AES error", interesting.
Now we can go in one of different ways:
try catch
block to a new java file and compile it, in the end print the result.a.a.a(byte[], byte[])
. then decrypt using external decryptors.MainActivity
to ditch the debuggable checks, build a smali version for printing
bArr
as a string to logcat
. This can be done using the catch Log.d as a template.
We will go for the second option for the following reasons:
So now, let's find the AES decryption in Java. searching google for 'java aes documentation' gives us the following:
public class Cipher
extends Object
This class provides the functionality of a cryptographic cipher for encryption and decryption. It forms the core of the Java Cryptographic Extension (JCE) framework."
So lets grep it!
$ grep -rnF 'Cipher'
sources/sg/vantagepoint/a/a.java:9: Cipher instance = Cipher.getInstance("AES");
When reading that file we see the same method: a.a.a(byte[], byte[])
. Same signature, awesome!!
The cipher is done using AES/ECB/PKCS7Padding
. Where bArr
- as the key, and
bArr2
- as the payload.
Looking back to the method call, we find that b("8d127684cbc37c17616d806cf50473cc")
- a
hexstr to byte array method is used as the key. 5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=
is the
payload.
So, using python:
import base64, binascii
from Crypto.Cipher import AES
payload = base64.b64decode('5UJiFctbmgbDoLXmpL12mkno8HT4Lv8dlat8FxR2GOc=')
key = binascii.unhexlify('8d127684cbc37c17616d806cf50473cc')
cipher = AES.new(key, AES.MODE_ECB)
cipher.decrypt(payload)
This gives us
b'I want to believe\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f\x0f'
.
The '\x0f'
means that there is 0x0f times a pad. If we remove it, we find 'I want to
believe'.
this is the secret! and you are awesome for following along. great job!.
Now that we basically know all we need to know in order to start reverse engineer Android applications, we will try to show it using other tools. Lastly, we will compare the different tools.
"Ghidra is a software reverse engineering (SRE) framework created and maintained by the National Security Agency Research Directorate. This framework includes a suite of full-featured, high-end software analysis tools that enable users to analyze compiled code on a variety of platforms including Windows, macOS, and Linux. Capabilities include disassembly, assembly, decompilation, graphing, and scripting, along with hundreds of other features."
Sounds great doesn't it??
After getting the binaries, run Ghidra:
$ ghidraRun
First, you need to create a new project for the Ghidra DB to be in. For that, go to 'File -> New Project (ctrl + n) -> Non-Shared Project -> Next >>'.
Now, choose a project name and click 'Finish'.
Unzip the APK, and find the classes.dex file.
Now, to get the APK inside Ghidra, select 'File -> Import File'
and select the same classes.dex
file we extracted.
Now a message will be shown:
"The file 'classes.dex' seems to have nested files in it. Select an import mode:"
Select 'Single file', and wait for a window to open.
Make sure Ghidra recognizes it as Dalvik Executable (DEX), and press 'OK', and at the new window
'OK' again.
Great! now we have our executable ready. Lets open by double clicking on it.
A dialog will pop"the 'classes.dex has not been analyzed. Would you like to analyze it now?' of course!.
Now Ghidra will try and find all the classes, the methods and the symbols in that DEX. You only need to click 'Analyze'.
Now we can go to the symbol tree on the left -> '+ Classes' and open MainActivity
to see
all the methods in it.
There we can find the same verify
and onCreate
that was on JADX. But now we can also use
cross-reference.
Right click on a.a(pSVar2)
, and move to the disassembly window. Now double click on
sg::vantagepoint::uncrackable1::a::a
. This will bring us to the same a.a(String)
.
Like so, we can do the same process and find the payload and key as well as the cipher suite.
But Ghidra is much more powerful, and it has a lot of scripts. Some are very helpful.
Unfortunatally, it doesnt have an aes implementation for code out of the box. Feel free to make it yourself!
Both JADX and Ghidra are very good to understand and reverse android apps. JADX and Android Studio can work hand in hand in order to do cross-reference. But in comparison to Ghidra, Android Studio can also attach via ADB and debug these apps.
Ghidra, on the other hand, is much more suited for reverse engineering, it was designed for it. It has scripts to find obfuscated methods, decompile binaries of all sorts and more features that a whole series of posts would not be enough to wrap around.
There is no winner so you shouldn't peek one. A good lesson to take from this, before starting a project, plan it through and ask yourself "What is the right tool for the task?", make sure you remember there isn't only a single right answer.
Stay humble, keep learning, do good.
Smali is a low-level language that represents Dalvik bytecode, used for reverse engineering Android apps. In this post, we will explore how to disassemble APKs using tools like Baksmali and APKTool, understand the Smali syntax, and modify apps by manipulating the Smali code.
This post assumes you are familiar with Dalvik. if you are not, refer to the life-cycle post.
As you know by now, the Java files you wrote are compiled to a DEX (Dalvik EXecutable). but what is Dalvik running? As said, Dalvik is Android JVM (Java Virtual Machine), and as so, it runs bytecode. Bytecode is binary data that the Dalvik JVM understand and interprets into CPU bytecode, but its still binary. If we humans want to read that bytecode, we need to convert it to a human readable format. One of them is called Smali, and that is exactly what we are going to discuss here, as well as how to see that smali and patch an app for our benefit.
So, our goal is to convert the bytecode to a human readable format called Smali. So lets use a disassembler!
One disassebler for Dalvik bytecode is called Baksmali. baksmali
comes bundled with smali
,
the Icelandic equivalents of "disassembler" and "assembler" respectively.
Why Icelandic you ask? Because Dalvik was named for an Icelandic fishing village.
But if Baksmali disassembles and assembles DEX, what about the entire APK?
For that we have a tool called apktool
:
APKTool in a tool for reverse engineering 3rd party, closed, binary Android apps. It can decode resources to nearly original form and rebuild them after writing patches. It also helps reversing entire apps because of the project like file structure it creates and automation of some repetitive tasks like building APKs, etc.
So let's run apktool d <apkfile>
to decode and decompile an APK, now what?
apktool
made a directory for us. In it the following:
* AndroidManifest.xml - as described in life-cycle doc.
* apktool.yml - metadata for apktool
for baksmali.
* assets - as described in life-cycle doc.
* original - original binary data from the APK.
* res - as described in life-cycle doc.
* smali - disassembled classes.dex to Smali.
* unknown - other data apktool
couldn't process.
Inside the Smali folder we can find a lot of files, some will be obfuscated, curtsy of ProGuard - Android Java
optimizer and obfuscator.
In these .smali
files we can see the opcode representing our Java code.
Dalvik's bytecode has two major classes of types, primitive types and reference types. Reference types are
objects and arrays, everything else is a primitive type.
Each type is represented by a single letter:
* V
- void (return type only)
* Z - boolean
* B - byte
* C - char
* J - long 64 bits
* S - short
* I - int
* F - float
* D - double 64 bits
------------------------------------------------
* L - object
* [ - array
Objects also take the form Lpackage/name/ObjectName;
- where package/name/
is the package
that the object is in, ObjectName
is the name of the object, and ;
denotes the end of the
object name. e.g. Ljava/lang/String
; is equivalent to java.lang.String
.
As well, arrays take the form [I
- this would be an array of int
s with a single dimension.
i.e. int[]
in java.
Fields are a bit more complicated, they are very verbose:
Lpackage/name/ObjectName;->FieldName:Ljava/lang/String;
this is the same as ObjectName.Field name
when the field is of type String.
So we went over types. what about methods?
Methods are also very verbose. they take the form Lpackage/name/ObjectName;->MethodName(III)Z
, take
2 minutes to try and figure it out by yourself using the field explanation...then continue reading.
So, with a bit of imagination, we can figure out what is this example:
* Lpackage/name/ObjectName;
- the type
* ->
- the same as .
in java, or ->
in c.
* MethodName
- the method name.
* (III)
- takes 3 integers.
* Z
- returns boolean.
All other types are intermediate values, such as registers.
In Dalvik's bytecode, registers are always 32 bits, and can hold any type of value.
In order to hold 64 bit types (Long and Double), 2 registers are used, we will see how later on.
There are two ways to specify how many registers are available in a method. The .registers
directive
specifies the total number of registers in the method, while the alternate .locals
directive specifies
the number of non-parameter registers in the method. The total number of registers would therefore include the
registers needed to hold the method parameters.
When a method is invoked, the parameters are placed in the last n registers. if a method has 2 arguments, and 5
registers, the arguments would be placed into the last 2 registers - v3
and v4
.
The first parameter to a non-static methods is always the object that the method is being invoked on.
For example, let's say you are writing a non-static method LMyObject;->callMe(II)V
. This method
has 2 integer parameters, but it also has an implicit LMyObject;
parameter before both integer
parameters, so there are a total of 3 arguments to the method.
Let's say you specify that there are 5 registers in the method (v0-v4
), with either the
.registers
5 directive or the .locals
2 directive (i.e. 2 local registers + 3 parameter
registers). When the method is invoked, the object that the method is being invoked on (i.e. the this reference)
will be in v2
, the first integer parameter will be in v3,
and the second integer parameter
will be in v4
.
For static methods it's the same thing, except there isn't an implicit argument.
To simplify things, arguments can be accessed using the 'p' scheme and not only the 'v' scheme. where
p0
is the first parameter. That means that in the above example, v2 == p0
,
v3 == p1
, v4 == p2
.
For 64 bit types such as long and double, 2 registers are being used for each parameter. that means, when calling
LMyObject;->MyMethod(IJ)V
:
* p0
- this
* p1
- integer
* p2,3
- long
So far, we've learned a whole new language called Smali. But if you think about it for a second, if there is a disassembler, there must be also a decompiler! There are java decompilers that will make us read a much easier code. But the downside is that they cant get you the same beautiful, bug-free code that you written back, just something similar. And even if you do decompile, you can't recompile the same as by using Baksmali. This is because, when you compile, a lot of data is lost by optimization processes, which removes code that is not important for runtime, So if a can be optimized, it will be.
So our best option to get exactly what is on the classes.dex is to translate the bytecode to Smali.
That same Smali can be changed to whatever we want and assembled back to an APK using
apk b <baksmalied folder>
.
But this unsigend APK cannot be installed in the device. the same as in the life-cycle post.
Go ahead and try it yourself. This site has a couple of challenges. Have fun trying them out for a size.
sources:
ADB (Android Debug Bridge) is a powerful tool for controlling and debugging Android devices. This post will cover the basics of setting up ADB, how to connect to a device, and essential ADB commands for manipulating files, accessing logs, and debugging Android apps remotely.
We are not regular Android users are we?! We are super users!
ADB (Android Debug Bridge) is a protocol for debugging a device. besides that there is fastboot and one more that
you might have heard of - 'ODIN', a Samsung's partition flashing protocol.
As super users, we need a tool for letting us control the device remotely, debug the Android system and apps in
the
command line. we will use ADB.
This post will guide you through some of ADBS's musts, and will hopefully get you familiar with this powerful tool. such wow!
ADB consists of a daemon (adbd) on the Andorid device and a client on the host. one can install it (on Dabian) using apt:
$ apt install adb
Now, lets enable adbd on the device:
When we connect an Android device, we can see it through the command:
$ adb devices
If the device shows up with UNAUTHORIZED
status, press 'Allow' on the device to accept the link.
This will add the hosts certificate (which resides in ~/.android/adbkey
) to adbd's known host file
(which resides in /data/misc/adb/adb_keys
).
Now, the device will show up with a DEVICE
status.
If something else happen or you cannot see the linking message on your device, just use:
$ adb kill-server && adb devices
Well, we have got ourselves a working ADB setup. A good tip here is to add your adbkey to every Android device you have. This will help you restore files in case you do not have a working touchscreen/screen.
logcat
- Android's dmesgdmesg, is linux's logging command for the kernel ring. this means that all the kernel drivers and modules will
write their logs in there, including crashes god forbid.
In the backstage, it is just a reader for /dev/kmsg
.
Android facilitates the same logic but instead of putting all the app logs data to the same place as the systems log,
it puts it in a different place, this is known as 'logcat' (/dev/log/main). This is very similar to the
modern journal
in Dabian.
So by running adb logcat -s <filter>
you can filter logs for the app you'd like to trace.
push/pull
- File manipulationIf you want to get real physical, you can even push files into your device. By running adb push
, and out
of your device using adb pull
.
Now, your best friend is /data/local/tmp/
, this directory is one that you don't need special
permissions to modify. So feel free to dump your doodles there, just make sure you don't leave important files.
on reset they will be deleted, so make copies.
And lastly, make sure you chmod
executables.
shell
- Up close and personalAndroid is so developer friendly, that it even gives you a shell out of the box.
Run adb shell
and explore! This command lets you access the device as the Linux machine it actually is
(using sh). Though you cant be root right away, you can still do some operations.
Inside an Android shell, besides the commands you are used to, you also have the Android activity manager and the
Android package mananger, am
and pm
respectively.
Some devices have a little more utilities, such as auto-complete using tabs, grep, find and much more, others
don't. To go around this issue, you can do a couple of things:
adb shell ls /sdcard | grep -i dcim
. Here the grepping is done in the host's shell, on data from
the adb
command.
busybox
and push the compiled
binary into your device. This binary provides replacements for most of the utilities you usually find in GNU
fileutils, shellutils, etc. install
- Exactly what you expectYou can also install apps through ADB.
One option is to push files into the device and then use the pm
(package manager) command to install
them.
Another option is to use adb install <filepath on host>
to install your favorite application
directly from the host's shell.
forward
- Advanced use casesNow for the real cool ideas. Because Android is really just a regular linux machine, you can even run servers from it
and access them from the internet.
For that, write your binary (look on the next docs), push it into your device, chmod
it, and run it.
This is great except you can't really access it. This is because you need to explicitly forward the ports. This
is done using adb forward tcp:<host-port> tcp:<android-port>
, so we can chit-chat.
This command can be used to run gdb-server
, frida
and so much more, as you will see in the
upcoming posts.
Android development has evolved from complex system-level programming to a highly abstracted environment, allowing developers to focus on app logic. This post will dive into Android's app lifecycle, the compilation process, and the Zygote process, explaining how apps are launched and managed within the Android ecosystem.
We have come a long way from the good old days, where most of the written programs were written in 'c', waiting for us to pwn. Today, when smartphones are widespread, software running on them is way more sophisticated.
Android is an example of smartphones operating system, in its vision, delegating software development. Letting developers write their logic and take care of the insides. This way, a lot more applications could be developed as developers focus more on business logic instead of bootstrapping and resource management. On the other hand, more dedicated OS developers are making it much more stable. This made android, together with the vendor's HAL, a very abstract OS, that developing on it became almost trivial for most use cases.
But some developers insist on doing things their way, trying to be quick or sometimes just not paying attention to details, making the code "dirty". This is a great opportunity for us as researchers to try our luck and find gems in the dirt.
This series of posts will cover from the basics of applications, i.e. the life-cycle, through researching, e.g Smali, to hardcore interesting tools such as Frida, Brida and finally - XPosed.
Android application are usually packed in an APK file, which is just a ZIP file that holds all the goods needed for incorporating an app into the android ecosystem (the newer *.apex files won't be discussed) As said, Android is a vastly abstract OS. this is much needed for easy development of apps on it.
In order to compile an app a Java or Kotlin file needs to have a class named MainActivity in it. This is the entry point when a icon is pressed and the Android Binder (the IPC of Android) calls Android's internal method startActivity using an intent. MainActivity must comply to the Activity interface. There, the developer must implement a couple of methods, one of them is onCreate. Other than MainActivity, an Android app has to have a manifest file called AndroidManifest.xml, in it MainActivity must be declared.
This is the only source code needed for a basic Android app. Running them through the gradle is an option, but we hate living an easy life.
Every Android app has at least one classes.dex
file in it. DEX is short for Dalvik executable. Dalvik is the former java virtual machine of android (today it Android Runtime -ART). But every DEX file is Java. So it needs to turn into Java bytecode using the Java compiler - javac:
$ javac -d ../bin -sourcepath . com/example/myapp/MainActivity.java
Next, all the class files are put together inside a jar file. using the 'jar' command:
$ jar cf bin/myapp.apk -C bin/ META-INF/ AndroidManifest.xml classes.dex
Then convert the JAR file into a DEX file using dx:
$ dx --dex --output=../bin/classes.dex ../bin/
Now, we create the APK structure:
$ jar cf bin/myapp.apk -C bin/ META-INF/ AndroidManifest.xml classes.dex
Finally, we sign the APK.
$ jarsigner -keystore my-release-key.keystore ../bin/myapp.apk alias_name
the entire process:
.java -> .class -> .dex -> .jar -> unsigned .apk -> signed .apk
But why sign it?
We need to sign an app to make sure that is the actual app from the same creator in case of an update. So the developer must sign the release candidate using the same private key, that private key is stored inside the keystore.
to create your own keystore (and keep it secret), use the following:
$ keytool -genkey -v -keystore <keystore_file> -alias <your_alias_name> -keyalg RSA -keysize 2048 -validity 10000
then fill out the fields.
and finally to sign it, use:
$ jarsigner -keystore <keystore_file> <app_to_sign>.apk <alias>
Ok, so now we have a signed APK file ready to be installed in our system. We created a Google Developer account, uploaded the app and waited for it to be scanned by the Google Play bouncer. Surely it will tell you that you've forgot to add a launcher icon or something else wrong with the permissions you asked for. So in the scope of this post, we are lazy and don't want to get into Google Play and comply to its developer policy (yet). We upload it manually, don't worry, how to do so will be discussed in future posts.
The first thing that happens is that APK is checked by Android Application Manager - am
for the signer. The APK is deflated and all the files inside it are being checked against their digest compared to a file in META-INF called MANIFEST.MF.
Yf they are correct then we are a go for installation.
First, lets take a look at a regular apk content:
META-INF/MANIFEST.MF META-INF/CERT.SF META-INF/CERT.RSA AndroidManifest.xml classes.dex res/* assets/* lib/*
jarsigner
enumerate all the files, calculate their SHA1 digest, signs them using RSA, and finally convert all digests to base64-encoded codes.When an application is installed, the APK is saved as /data/app/com/example/myapp/base.apk. in the same directory there are also lib and special files - odex/vdex/art/oat. These files are the compiled classes.dex file. Now hold it for a sec..another history class!
Dalvik is a JIT (Just in time) compilation based engine. With the Dalvik JIT compiler, each time when the app is run, it dynamically translates a part of the Dalvik bytecode inside the classes.dex file into machine code. As the execution progresses, more bytecode is compiled and cached. Since JIT compiles only a part of the code, it has a smaller memory footprint and uses less physical space on the device. There were drawbacks to use Dalvik hence from Android 4.4 (KitKat) ART was introduced as a runtime and from Android 5.0 (Lollipop) it has completely replaced Dalvik. ART is equipped with an Ahead-of-Time compiler. During the app's installation phase, it statically translates the DEX bytecode into machine code and stores in the device's storage. This is a one-time event which happens when the app is installed on the device. Android 7.0 adds a just-in-time (JIT) compiler with code profiling to Android runtime (ART) that constantly improves the performance of Android apps as they run. There won't be any compilation during install, and applications can be started right away, the bytecode being interpreted. There is a new, faster interpreter in ART and it is accompanied by a new JIT, but the JIT information is not persisted. Instead, the code is profiled during execution and the resulted data is saved. So the entire life-cycle of the code is:
.java -> .class -> .dex -> .jar -> unsigned .apk -> signed .apk ->
-> deflated .apk -> .oat/.odex/.vdex
to conclude, the files of our app are in the following locations:
DIRECTORY DESCRIPTION / API ===================================================================================== APP CODE ======== /data/app/* (user apps installation directory) /data/app/ */base.apk (original .apk file) /data/app/ */lib/ /*.so (shared libraries) /data/app/ */oat/ /base.[art|odex|vdex] (compiled executable code) /data/dalvik-cache/ /*.[art|dex|oat|vdex] (compiled executable code, only for system apps) /data/misc/profiles/cur/ / /primary.prof (ART profile) /data/misc/profiles/ref/ /primary.prof (ART profile) INTERNAL STORAGE ================ /data/user[_de]/ / getDataDir /data/user[_de]/ / /files getFilesDir /data/user[_de]/ / /[code_]cache getCacheDir or getCodeCacheDir /data/user[_de]/ / /databases getDatabasePath /data/user[_de]/ / /no_backup getNoBackupFilesDir /data/user[_de]/ / /shared_prefs getSharedPreferences EXTERNAL STORAGE ================ /storage/emulated/obb/ /*.obb (shared by multi-users, exposed in following view) /storage/emulated/ /Android/obb/ /*. .obb getObbDirs /storage/emulated/ /Android/media/ getExternalMediaDirs /storage/emulated/ /Android/data/ / /storage/emulated/ /Android/data/ /files getExternalFilesDirs /storage/emulated/ /Android/data/ /[code_]cache getExternalCacheDirs
Wow, so much has passed and we didn't even ran our app! It is in our device, with a special 'stopped=true' flag, just waiting to be opened (checkout more app info using
$ adb shell dumpsys package com.example.myapp
)
Note that it can register to many intents but cannot wake itself up. it needs to wait for the user to press the icon.
Now we are going to change that flag!
Wait, wait, wait. one last thing.
Android is running on the Linux kernel. when it boots, an init
process is spawned. and every process is a fork of it. similarly, every app is forked from a special process called zygote (app_process for each ABI).
It preloads all common classes used by Android application framework and various apps installed on a system. And is the first JVM process in android.
The zygote then forks to start a well managed process called system server. System server starts all core platform services e.g activity manager service and hardware services in its own context.
and for visualizing:
+----------+ +-----+ +----------------------+
| boot.oat | | OAT +<-------+ apk |
+----------+ +--+--+ +-----+----------+-----+
| | | dex | manifest | lib |
+----+---------+ +-----+----------+--+--+
| +---------------------+ |
| | zygote | +---------------+ |
| | +--------+--------+ | | system server | |
| | | libArt | libc | +<--------+-+-+---+-+ |
| | +-----------------+ | | gps |.|.|.|.|.| |
| | | class | linker | | +---------------+ |
+-->+ loader | +------------------------+
| +-----------------+ |
+---------------------+
Now, we got ourselves a new process, at this point, the binder calls startActivity and our java code is finally running.
more on this in here.
Coming soon...
I’m excited to introduce myself as someone with strong experience in technical management,
cybersecurity, and Eloctronics.
I’ve worked on a variety of projects that involve system design,
team leadership, and hands-on work.
With close communication, I’m confident that my skills and
problem-solving abilities in different domains will allow me to contribute meaningfully to your
team.
Below you can find ways to reach out, looking forward to discussing how I can add value to
your company.
I am the IoT and advanced exploitation techniques knowledge source for a diverse and multidisciplinary company.
I brought unique expertise in electronic engineering to a strong automotive embedded research team.
By being given the opportunity to learn a new field, I got to become a focal point in application security-related projects.
I was promoted to team leader for a team in charge of some of the Israeli Air Force's crucial RF systems.
Languages: Hebrew (native), English (full professional proficiency)
Developing an integrated dockerized multi-server ecosystem using with both open-source and proprietary services to structure and organize daily workflow, potentially helping others with ADHD unlock their potential. Designed to foster focus, time management and productivity methodologies thorugh structure.