Background
I'm currently doing OWASP Uncrackable3.
I know that there are many writeups online, the only problem I have with these writeups online is when they're doing root bypass detection, all of them seem to bypass it by instrumenting the strstr
function or the fgets
function which I personally found a bit odd. Isn't the most obvious thing to do is to intrument the function that exits the program to do nothing instead (in this case the function is called goodbye())?
The problem
I need to bypass a native function called "goodbye()" loaded from "libfoo.so". Static analysis with Ghidra shows that goodbye() is called if instrumentation is detected.
NOTE: check instrumentation is a name I choose based on what it does
goodbye called when instrumentation is detected
I want to instrument this goodbye() function to NOT do anything except print to the console and return to caller. I've located the goodbye()'s address with this frida snippet:
Java.perform(function(){
Interceptor.attach(Module.findExportByName('libc.so', 'open'), {
onEnter: function(){
var module = Process.findModuleByName('libfoo.so')
if(module != null){
var enum_export = module.enumerateExports()
console.log(JSON.stringify(enum_export, null, 2))
console.log('------------------------------------------------------------')
}
}
})
});
Also, for some reason, when using frida the function name is "_Z7goodbyev" instead of "goodbye". Although I'm sure that "_Z7goodbyev" and "goodbye" refers to the same function because their offset matches.
Anyway, all I need to do is replace the function. But this snippet gives me an error
let once_only = 0
Java.perform(function(){
Interceptor.attach(Module.findExportByName('libc.so', 'open'), {
onEnter: function(){
var module = Process.findModuleByName('libfoo.so')
if(module != null){
if(once_only == 0){
let goodbye_address = Module.findExportByName('libfoo.so', '_Z7goodbyev')
console.log('goodbye() addresses: ' + goodbye_address)
Interceptor.replace(Module.findExportByName('libfoo.so', '_Z7goodbyev'), new NativeCallback(function() {
console.log('do nothing')
return
}, 'void', ['void']));
once_only = 1
console.log('------------------------------------------------------------')
}
}
}
})
});
My main problem is that, it correctly enters the function (as shown by the 'do nothing' string correctly outputted) but for some reason still crashes the program. The stack trace provided also does not give enough clue
// a bunch of 'do nothing' string
do nothing
do nothing
do nothing
Process crashed: Bad access due to invalid address
***
*** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
Build fingerprint: 'Android/sdk_phone_x86/generic_x86:11/RSR1.210210.001.A1/7193139:userdebug/dev-keys'
Revision: '0'
ABI: 'x86'
Timestamp: 2023-05-05 18:38:46+0700
pid: 19763, tid: 19797, name: tg.uncrackable3 >>> owasp.mstg.uncrackable3 <<<
uid: 10123
signal 11 (SIGSEGV), code 1 (SEGV_MAPERR), fault addr 0x0
Cause: null pointer dereference
eax b706511d ebx b7269fa4 ecx 00000000 edx 80000000
edi b725ff5c esi e7d0438c
ebp b7260178 esp b725ff44 eip 00000000
backtrace:
#00 pc 00000000 <unknown>
***
[Android Emulator 5554::owasp.mstg.uncrackable3 ]->
Thank you for using Frida!
one thing that I notice is the EIP (instruction pointer) is set to 00000000 which is weird because from my understanding, when we call a function, the stack should store the instruction addresses after the function call (i.e the next instruction after the function call).
The question:
- Why the function name from frida is seemingly a bit more gibberish than ghidra? How does ghidra know how to 'cleanup' the function name?
- Why can't I bypass the root detection with hooking the goodbye function? Is it impossible to do so? If so why?
EDIT 1: Change stack trace to text format as suggested by @Robert.
I also tried hooking check_instrumentation
but so far no progress. not sure what I'm doing wrong
let once_only = 0
Java.perform(function(){
Interceptor.attach(Module.findExportByName('libc.so', 'open'), {
onEnter: function(){
var base_address = Module.findBaseAddress('libfoo.so')
if(base_address != null){
if(once_only == 0){
var check_instrumentation_address = base_address.add(0x080)
Interceptor.replace(check_instrumentation_address, new NativeCallback(function(){
console.log('check_instrumentation do nothing!')
return
}, 'void', ['void'] ))
once_only = 1
}
}
}
})
})