Asad

ASAD

Bypassing Android Root Detection Using Smali Patching — Part 3

In the previous two blogs, we bypassed root detection by modifying the return value and then manipulating the loop counter. Both techniques worked perfectly — but today we are going to do something even more surgical.

But in this blog, we are going to do something even more surgical — we will change just one word in the entire Smali code.

What is Jump Redirect?

Instead of modifying the return value or manipulating the loop counter, we change the jump destination itself. We redirect the code to jump directly to the label that returns false — before it ever reaches return true.

One word. One line. Same result.

If you haven’t read the previous blogs yet, I highly recommend starting there as this blog builds on the same concepts:

👉 Part 1: https://cybersecasad.com/blogs/bypass-root-with-smali-1/ 👉 Part 2: https://cybersecasad.com/blogs/bypass-root-with-smali-2/

Target Application: OWASP UnCrackable Level 1 — same app, same code, different technique!

Walkthrough

Now let’s look at the most critical line in the entire root detection code — the line that controls where the program goes next.

if-eqz v4, :cond_0 — Let’s break this down word by word:

  • if-eqz — “if equal to zero” — in simple terms, “if the result is false”
  • v4 — the register that stores the result of exists() — did we find su or not?
  • :cond_0 — the label to jump to if the condition is true

So the full instruction reads:

“If su was NOT found (v4 = false = zero) → jump to :cond_0

How the Flow Works — Step by Step

invoke-virtual {v5}, Ljava/io/File;->exists()Z   # check if su exists
move-result v4                                    # store result in v4

if-eqz v4, :cond_0     # if su NOT found → jump to cond_0
                        # if su IS found  → continue below ↓

const/4 v0, 0x1        # load true into v0
return v0              # return true → ROOT DETECTED! ❌

:cond_0
add-int/lit8 v3, v3, 0x1   # increment loop counter
goto :goto_0               # go back and check next PATH entry

:cond_1
return v2              # return false → BYPASS! ✅

Two possible paths:

Path 1 — su IS found:

exists() returns truev4 = 1if-eqz condition fails → code falls through → const/4 v0, 0x1return trueRoot Detected!

Path 2 — su is NOT found:

exists() returns falsev4 = 0if-eqz condition passes → jumps to :cond_0 → loop counter increments → next PATH entry checked

Now that we understand exactly how the jump works, the fix becomes obvious.

The Key Observation

:cond_1 is already sitting right there in the code, and it directly returns v2 which is false:

:cond_1
return v2    # v2 = 0x0 = false ✅

Right now, the code jumps to :cond_0 when su is not found. What if we make it jump to :cond_1 instead — regardless of whether su is found or not?

# Original
if-eqz v4, :cond_0    # su not found → cond_0 (loop continue)

# Patched
if-eqz v4, :cond_1    # su not found → cond_1 (return false!)

The moment exists() returns false — which it will, since our emulator’s su is not in the app’s PATH — the code jumps straight to :cond_1 and returns false.

If you look closely at the three methods, you will notice something important — a() and c() use a loop, but b() does not.

This is a critical observation because our Loop Skip technique only works on methods that have a loop. Since b() has no loop, we need a different approach.

Why b() is Different?

Before applying the patch, let’s understand why we cannot use the Loop Skip technique on b().

Take a look at b() in JADX-GUI:

public static boolean b() {
    String str = Build.TAGS;
    return str != null && str.contains("test-keys");
}

Unlike a() and c(), method b() does not use a loop at all. It simply checks two conditions:

  1. Is Build.TAGS null?
  2. Does Build.TAGS contain the string "test-keys"?

There is no loop counter, no v3, no goto — nothing to manipulate.

Key Insight: The Loop Skip technique works by manipulating the loop counter v3. Since b() has no loop and no v3, this technique simply cannot be applied here.

This is an important lesson in Smali patching — always analyze the code structure before choosing a technique. A patch that works perfectly on one method may be completely useless on another.

For b(), we fall back to our Technique 1 — Early Return from the previous blog:

const/4 v0, 0x0 return v0

In method c(), we apply the exact same patch as a() — replace :cond_0 with :cond_1 in the if-eqz line. Save the file once done.

Let’s recompile the patched APK

Command: apktool b .\\UnCrackable-Level1\\ -o .\\UnCrackable-Level1-R3.apk

Time to sign the APK

Note: here i had created a shortcut of that jar file.

Finally, install the patched APK

As soon as we open the app — no root detection dialog! The app launches normally on our rooted emulator. Root detection successfully bypassed using the Loop Skip technique!

Key Takeaways

  • Jump Redirect is the most surgical technique — we didn’t touch the return value, we didn’t manipulate the loop counter, we simply changed where the code goes next.
  • One word change:cond_0 to :cond_1 — that’s all it took.
  • b() method again required a different approach — because it has no :cond_1 label, the Jump Redirect technique cannot be applied. Always analyze the code structure before choosing a technique.
  • Three techniques, same result — this is the beauty of Smali patching. There is never just one way.

For Developers — How to Make Root Detection Stronger

If you are a developer reading this series, here is the hard truth:

Root detection implemented purely in Java/Smali can always be patched. A determined attacker only needs to find one weak point.

To make root detection significantly harder:

  • Move checks to Native (C/C++) code — Smali patching won’t work on native libraries
  • Use Google Play Integrity API — much harder to bypass than manual checks
  • Implement APK integrity checks — detect if your app has been tampered with
  • Layer multiple defenses — never rely on a single mechanism

Conclusion

Over the course of this three-part series, we bypassed the exact same root detection mechanism using three completely different Smali patching techniques:

Blog Technique Change
Part 1 Early Return 2 lines added
Part 2 Loop Skip 1 line changed
Part 3 Jump Redirect 1 word changed

Same app. Same code. Three different approaches. This is what real Smali analysis looks like. 🔥

Happy Hacking! Stay ethical, stay legal. 🔐

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top