Bypass root + Frida
Bypass Android root protection with frida.

1. Install frida and root your emulator/device

Android Dynamic Analysis
Red Teaming and Malware Analysis

2. Use frida scripts to bypass (fast way)

Frida CodeShare
It works in the hardest applications. So, you need to create a file called "frida.js", and paste the code below.
1
Java.perform(function() {
2
var RootPackages = ["com.noshufou.android.su", "com.noshufou.android.su.elite", "eu.chainfire.supersu",
3
"com.koushikdutta.superuser", "com.thirdparty.superuser", "com.yellowes.su", "com.koushikdutta.rommanager",
4
"com.koushikdutta.rommanager.license", "com.dimonvideo.luckypatcher", "com.chelpus.lackypatch",
5
"com.ramdroid.appquarantine", "com.ramdroid.appquarantinepro", "com.devadvance.rootcloak", "com.devadvance.rootcloakplus",
6
"de.robv.android.xposed.installer", "com.saurik.substrate", "com.zachspong.temprootremovejb", "com.amphoras.hidemyroot",
7
"com.amphoras.hidemyrootadfree", "com.formyhm.hiderootPremium", "com.formyhm.hideroot", "me.phh.superuser",
8
"eu.chainfire.supersu.pro", "com.kingouser.com"
9
];
10
11
var RootBinaries = ["su", "busybox", "supersu", "Superuser.apk", "KingoUser.apk", "SuperSu.apk"];
12
13
var RootProperties = {
14
"ro.build.selinux": "1",
15
"ro.debuggable": "0",
16
"service.adb.root": "0",
17
"ro.secure": "1"
18
};
19
20
var RootPropertiesKeys = [];
21
22
for (var k in RootProperties) RootPropertiesKeys.push(k);
23
24
var PackageManager = Java.use("android.app.ApplicationPackageManager");
25
26
var Runtime = Java.use('java.lang.Runtime');
27
28
var NativeFile = Java.use('java.io.File');
29
30
var String = Java.use('java.lang.String');
31
32
var SystemProperties = Java.use('android.os.SystemProperties');
33
34
var BufferedReader = Java.use('java.io.BufferedReader');
35
36
var ProcessBuilder = Java.use('java.lang.ProcessBuilder');
37
38
var StringBuffer = Java.use('java.lang.StringBuffer');
39
40
var loaded_classes = Java.enumerateLoadedClassesSync();
41
42
send("Loaded " + loaded_classes.length + " classes!");
43
44
var useKeyInfo = false;
45
46
var useProcessManager = false;
47
48
send("loaded: " + loaded_classes.indexOf('java.lang.ProcessManager'));
49
50
if (loaded_classes.indexOf('java.lang.ProcessManager') != -1) {
51
try {
52
//useProcessManager = true;
53
//var ProcessManager = Java.use('java.lang.ProcessManager');
54
} catch (err) {
55
send("ProcessManager Hook failed: " + err);
56
}
57
} else {
58
send("ProcessManager hook not loaded");
59
}
60
61
var KeyInfo = null;
62
63
if (loaded_classes.indexOf('android.security.keystore.KeyInfo') != -1) {
64
try {
65
//useKeyInfo = true;
66
//var KeyInfo = Java.use('android.security.keystore.KeyInfo');
67
} catch (err) {
68
send("KeyInfo Hook failed: " + err);
69
}
70
} else {
71
send("KeyInfo hook not loaded");
72
}
73
74
PackageManager.getPackageInfo.implementation = function(pname, flags) {
75
var shouldFakePackage = (RootPackages.indexOf(pname) > -1);
76
if (shouldFakePackage) {
77
send("Bypass root check for package: " + pname);
78
pname = "set.package.name.to.a.fake.one.so.we.can.bypass.it";
79
}
80
return this.getPackageInfo.call(this, pname, flags);
81
};
82
83
NativeFile.exists.implementation = function() {
84
var name = NativeFile.getName.call(this);
85
var shouldFakeReturn = (RootBinaries.indexOf(name) > -1);
86
if (shouldFakeReturn) {
87
send("Bypass return value for binary: " + name);
88
return false;
89
} else {
90
return this.exists.call(this);
91
}
92
};
93
94
var exec = Runtime.exec.overload('[Ljava.lang.String;');
95
var exec1 = Runtime.exec.overload('java.lang.String');
96
var exec2 = Runtime.exec.overload('java.lang.String', '[Ljava.lang.String;');
97
var exec3 = Runtime.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;');
98
var exec4 = Runtime.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.io.File');
99
var exec5 = Runtime.exec.overload('java.lang.String', '[Ljava.lang.String;', 'java.io.File');
100
101
exec5.implementation = function(cmd, env, dir) {
102
if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") {
103
var fakeCmd = "grep";
104
send("Bypass " + cmd + " command");
105
return exec1.call(this, fakeCmd);
106
}
107
if (cmd == "su") {
108
var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
109
send("Bypass " + cmd + " command");
110
return exec1.call(this, fakeCmd);
111
}
112
return exec5.call(this, cmd, env, dir);
113
};
114
115
exec4.implementation = function(cmdarr, env, file) {
116
for (var i = 0; i < cmdarr.length; i = i + 1) {
117
var tmp_cmd = cmdarr[i];
118
if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") {
119
var fakeCmd = "grep";
120
send("Bypass " + cmdarr + " command");
121
return exec1.call(this, fakeCmd);
122
}
123
124
if (tmp_cmd == "su") {
125
var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
126
send("Bypass " + cmdarr + " command");
127
return exec1.call(this, fakeCmd);
128
}
129
}
130
return exec4.call(this, cmdarr, env, file);
131
};
132
133
exec3.implementation = function(cmdarr, envp) {
134
for (var i = 0; i < cmdarr.length; i = i + 1) {
135
var tmp_cmd = cmdarr[i];
136
if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") {
137
var fakeCmd = "grep";
138
send("Bypass " + cmdarr + " command");
139
return exec1.call(this, fakeCmd);
140
}
141
142
if (tmp_cmd == "su") {
143
var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
144
send("Bypass " + cmdarr + " command");
145
return exec1.call(this, fakeCmd);
146
}
147
}
148
return exec3.call(this, cmdarr, envp);
149
};
150
151
exec2.implementation = function(cmd, env) {
152
if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") {
153
var fakeCmd = "grep";
154
send("Bypass " + cmd + " command");
155
return exec1.call(this, fakeCmd);
156
}
157
if (cmd == "su") {
158
var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
159
send("Bypass " + cmd + " command");
160
return exec1.call(this, fakeCmd);
161
}
162
return exec2.call(this, cmd, env);
163
};
164
165
exec.implementation = function(cmd) {
166
for (var i = 0; i < cmd.length; i = i + 1) {
167
var tmp_cmd = cmd[i];
168
if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id" || tmp_cmd == "sh") {
169
var fakeCmd = "grep";
170
send("Bypass " + cmd + " command");
171
return exec1.call(this, fakeCmd);
172
}
173
174
if (tmp_cmd == "su") {
175
var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
176
send("Bypass " + cmd + " command");
177
return exec1.call(this, fakeCmd);
178
}
179
}
180
181
return exec.call(this, cmd);
182
};
183
184
exec1.implementation = function(cmd) {
185
if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id" || cmd == "sh") {
186
var fakeCmd = "grep";
187
send("Bypass " + cmd + " command");
188
return exec1.call(this, fakeCmd);
189
}
190
if (cmd == "su") {
191
var fakeCmd = "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled";
192
send("Bypass " + cmd + " command");
193
return exec1.call(this, fakeCmd);
194
}
195
return exec1.call(this, cmd);
196
};
197
198
String.contains.implementation = function(name) {
199
if (name == "test-keys") {
200
send("Bypass test-keys check");
201
return false;
202
}
203
return this.contains.call(this, name);
204
};
205
206
var get = SystemProperties.get.overload('java.lang.String');
207
208
get.implementation = function(name) {
209
if (RootPropertiesKeys.indexOf(name) != -1) {
210
send("Bypass " + name);
211
return RootProperties[name];
212
}
213
return this.get.call(this, name);
214
};
215
216
Interceptor.attach(Module.findExportByName("libc.so", "fopen"), {
217
onEnter: function(args) {
218
var path = Memory.readCString(args[0]);
219
path = path.split("/");
220
var executable = path[path.length - 1];
221
var shouldFakeReturn = (RootBinaries.indexOf(executable) > -1)
222
if (shouldFakeReturn) {
223
Memory.writeUtf8String(args[0], "/notexists");
224
send("Bypass native fopen");
225
}
226
},
227
onLeave: function(retval) {
228
229
}
230
});
231
232
Interceptor.attach(Module.findExportByName("libc.so", "system"), {
233
onEnter: function(args) {
234
var cmd = Memory.readCString(args[0]);
235
send("SYSTEM CMD: " + cmd);
236
if (cmd.indexOf("getprop") != -1 || cmd == "mount" || cmd.indexOf("build.prop") != -1 || cmd == "id") {
237
send("Bypass native system: " + cmd);
238
Memory.writeUtf8String(args[0], "grep");
239
}
240
if (cmd == "su") {
241
send("Bypass native system: " + cmd);
242
Memory.writeUtf8String(args[0], "justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled");
243
}
244
},
245
onLeave: function(retval) {
246
247
}
248
});
249
250
/*
251
252
TO IMPLEMENT:
253
254
Exec Family
255
256
int execl(const char *path, const char *arg0, ..., const char *argn, (char *)0);
257
int execle(const char *path, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);
258
int execlp(const char *file, const char *arg0, ..., const char *argn, (char *)0);
259
int execlpe(const char *file, const char *arg0, ..., const char *argn, (char *)0, char *const envp[]);
260
int execv(const char *path, char *const argv[]);
261
int execve(const char *path, char *const argv[], char *const envp[]);
262
int execvp(const char *file, char *const argv[]);
263
int execvpe(const char *file, char *const argv[], char *const envp[]);
264
265
*/
266
267
268
BufferedReader.readLine.implementation = function() {
269
var text = this.readLine.call(this);
270
if (text === null) {
271
// just pass , i know it's ugly as hell but test != null won't work :(
272
} else {
273
var shouldFakeRead = (text.indexOf("ro.build.tags=test-keys") > -1);
274
if (shouldFakeRead) {
275
send("Bypass build.prop file read");
276
text = text.replace("ro.build.tags=test-keys", "ro.build.tags=release-keys");
277
}
278
}
279
return text;
280
};
281
282
var executeCommand = ProcessBuilder.command.overload('java.util.List');
283
284
ProcessBuilder.start.implementation = function() {
285
var cmd = this.command.call(this);
286
var shouldModifyCommand = false;
287
for (var i = 0; i < cmd.size(); i = i + 1) {
288
var tmp_cmd = cmd.get(i).toString();
289
if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd.indexOf("mount") != -1 || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd.indexOf("id") != -1) {
290
shouldModifyCommand = true;
291
}
292
}
293
if (shouldModifyCommand) {
294
send("Bypass ProcessBuilder " + cmd);
295
this.command.call(this, ["grep"]);
296
return this.start.call(this);
297
}
298
if (cmd.indexOf("su") != -1) {
299
send("Bypass ProcessBuilder " + cmd);
300
this.command.call(this, ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"]);
301
return this.start.call(this);
302
}
303
304
return this.start.call(this);
305
};
306
307
if (useProcessManager) {
308
var ProcManExec = ProcessManager.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.io.File', 'boolean');
309
var ProcManExecVariant = ProcessManager.exec.overload('[Ljava.lang.String;', '[Ljava.lang.String;', 'java.lang.String', 'java.io.FileDescriptor', 'java.io.FileDescriptor', 'java.io.FileDescriptor', 'boolean');
310
311
ProcManExec.implementation = function(cmd, env, workdir, redirectstderr) {
312
var fake_cmd = cmd;
313
for (var i = 0; i < cmd.length; i = i + 1) {
314
var tmp_cmd = cmd[i];
315
if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id") {
316
var fake_cmd = ["grep"];
317
send("Bypass " + cmdarr + " command");
318
}
319
320
if (tmp_cmd == "su") {
321
var fake_cmd = ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"];
322
send("Bypass " + cmdarr + " command");
323
}
324
}
325
return ProcManExec.call(this, fake_cmd, env, workdir, redirectstderr);
326
};
327
328
ProcManExecVariant.implementation = function(cmd, env, directory, stdin, stdout, stderr, redirect) {
329
var fake_cmd = cmd;
330
for (var i = 0; i < cmd.length; i = i + 1) {
331
var tmp_cmd = cmd[i];
332
if (tmp_cmd.indexOf("getprop") != -1 || tmp_cmd == "mount" || tmp_cmd.indexOf("build.prop") != -1 || tmp_cmd == "id") {
333
var fake_cmd = ["grep"];
334
send("Bypass " + cmdarr + " command");
335
}
336
337
if (tmp_cmd == "su") {
338
var fake_cmd = ["justafakecommandthatcannotexistsusingthisshouldthowanexceptionwheneversuiscalled"];
339
send("Bypass " + cmdarr + " command");
340
}
341
}
342
return ProcManExecVariant.call(this, fake_cmd, env, directory, stdin, stdout, stderr, redirect);
343
};
344
}
345
346
if (useKeyInfo) {
347
KeyInfo.isInsideSecureHardware.implementation = function() {
348
send("Bypass isInsideSecureHardware");
349
return true;
350
}
351
}
352
353
});
Copied!
Next, after installing the target apk on your mobile device, you need to start frida server and execute the script from your attacker machine.
1
.\adb.exe connect localhost:21503
2
.\adb.exe shell '/data/local/temp/frida-server-14.2.18-android-x86 &'
3
4
frida-ps.exe -U (to obtain the app)
5
frida.exe -U -f 'com.xxx.xxx.xxx' -l .\frida.js --no-pause
Copied!
For more details about frida, you can check the next section.
Android Dynamic Analysis
Red Teaming and Malware Analysis

3. Dynamic analysis and frida script (manual)

Many times, when the root protection is made manually, it's difficult to bypass the protection. In that scenarios, you need:
    Analyse the APK java code to understand how and where the root validation is performed
    Create a frida specially crafted script to bypass it

Souce-code analysis

To start, we can use an online decompiler like this:
Java decompiler online / APK decompiler - Decompiler.com
or just doing it by using the tool dex2jar:
GitHub - pxb1988/dex2jar: Tools to work with android .dex and java .class files
GitHub
My favorite approach is: jadx Java decompiler:
GitHub - skylot/jadx: Dex to Java decompiler
GitHub
There is also a prepared VM you can find with all the tools installed called Mobexler:
Mobexler - Mobile Application Penetration Testing Platform
BONUS: A lot of times I use Bytecode Viewer - a lightweight user friendly Java Bytecode Viewer. This tool allow you compare the decompiled Java code using different tools, and choosing the best one for you.
After that, using the ByteCode Viewer or JADX, you can export your project (source files) and use Visual Studio Code in order to analyze the code and taking advantage of cross-references, and all the available plugins to better understand the Android code. For instance, you can also install a Java Decompiler plugin.

Finding the root validation

By using the "Search" feature, we try to get some information about where the root function is called.
and ... we got it!
😇
We can see that an object (r0) is returned if the "Device is rooted", and r0 is returned with "none" when the device is not rooted. Next, we are presenting the general block of code we analyzed.
1
package d.a.a.a.c.g;
2
3
(...)
4
public enum g {
5
(...)
6
7
public static final class e extends g {
8
public e(String str, int i) {
9
super(str, i, (q.v.c.f) null);
10
}
11
12
13
public java.lang.Object doProcedure(android.content.Context r17, q.s.d<? super d.a.a.a.c.g.a> r18) {
14
(...)
15
16
d.a.a.a.c.g.a$a r0 = d.a.a.a.c.g.a.g
17
java.lang.String r2 = "Device rooted"
18
d.a.a.a.c.g.a r0 = r0.a(r1, r2)
19
return r0
20
L_0x01ee:
21
d.a.a.a.c.g.a$a r0 = d.a.a.a.c.g.a.g
22
java.lang.String r2 = "none"
23
d.a.a.a.c.g.a r0 = r0.a(r1, r2)
24
return r0
25
26
(...)
27
}
28
}
Copied!
To bypass this protection, we need to:
    Create the target path: Package of the class + Add the "public enum g" + Add the target class (d.a.a.a.c.g.g$e)
    Implement the code on the target method (doProcedure)
    Create an object from "d.a.a.a.c.g.a" to return something using its constructor
We can see the constructor can be "empty" or we can passing it 5 args. Analysing the code we can understand that. For instance:
1
return new d.a.a.a.c.g.a(r6, r7, r8, (android.content.DialogInterface.OnClickListener) null, 8);
Copied!

Frida script (hook)

Now, it's time to write our frida script:
1
console.log("Starting hook ...");
2
Java.perform(function() {
3
//package d.a.a.a.c.g;
4
//d.a.a.a.c.g.g$e
5
var my = Java.use("d.a.a.a.c.g.g$e");
6
my.doProcedure.implementation = function (func1, func2, func3) {
7
console.log('..doing some stuff..');
8
//var x = Java.use("d.a.a.a.c.g.a").$new();
9
var x = Java.use("d.a.a.a.c.g.a").$new("none", "none", null, null, 8);
10
return x;
11
}
12
});
Copied!
another approach overloading the method:
1
Java.perform(function () {
2
3
var hookclass = Java.use("d.a.a.a.c.g.g$e");
4
5
hookclass.doProcedure.overload("android.content.Context", "q.s.d").implementation = function(ctx, rsd) {
6
var p = Java.use("d.a.a.a.c.g.a").$new("none", "none", null, null, 8);
7
var ret = p;
8
console.log(ret);
9
return ret;
10
}
11
console.log("hook created!");
12
});
Copied!
To execute it:
1
.\adb.exe connect localhost:21503
2
.\adb.exe shell '/data/local/temp/frida-server-14.2.18-android-x86 &'
3
4
frida.exe -U -f 'com.xxx.xxx.xxxx' -l .\frida.js --no-pause
Copied!
and, we got it! We bypassed root validations!
😎

BONUS - PATCH the APK

Instead using frida, we can patch directly the APK using this approach:
Backdooring/patch APKs
Red Teaming and Malware Analysis
So, we just need, in this case, changing the content of the r2 (v2 smali) variable.
Before
After
After that, we need to follow the steps to build the APK, sign it and align it.
😎
Last modified 3mo ago