1

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 strstrfunction or the fgetsfunction 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('------------------------------------------------------------')
                
            }

        }

    })

});

goodbye's address

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:

  1. Why the function name from frida is seemingly a bit more gibberish than ghidra? How does ghidra know how to 'cleanup' the function name?
  2. 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
                }
                
            }

        }

    })

})

1 Answers1

0

Functions like check_instrumentation have never been tested if they work at all after the goodby call because they usually never reach it. Also those uStack_244 values are looking suspicious. Most likely they are used as return value identifying the result of check_instrumentation. So the crash may occur later not because of a problem but intentionally for crashing the process.

So it may be more wise to hook a method that allows you to modify the execution of check_instrumentation in a way that it not calls goodby at all (e.g. replace check_instrumentation or intercept it and in onEnter use Frida to modify the string frida so the check passes.

Some languages like C++ encode other data into the function name which is called name mangling. Ghidra is able to demange such names automatically may be this is the difference. Look into the assembler code Ghidra writes the the mangled and demangled name.

Robert
  • 39,162
  • 17
  • 99
  • 152
  • Sorry for the late reply, I tried some of your suggestion and unfortunately, so far, no progress. Thank you for the suggestion though! Although I'm still not sure how to do "intercept it and in onEnter use Frida to modify the string frida so the check passes" as this is beyong my coding knowledge. I might get back to this when I have the time! – Reflected Name May 05 '23 at 13:12