sensepost / objection

📱 objection - runtime mobile exploration
GNU General Public License v3.0
7.58k stars 856 forks source link

How to run jobs on application startup #335

Closed rhyek closed 1 month ago

rhyek commented 4 years ago

Following this guide on disabling ssl pinning on obfuscated apps and this guide on working with jobs, I'm trying to understand the steps to get the following script working:

Java.performNow(function() {
  var Pinner = Java.use("n/l$a"); // this is what the class is called for the obfuscated app i'm working on
  Pinner.a.overload(
    "java.lang.String",
    "[Ljava.lang.String;"
  ).implementation = function(a, b) {
    console.log("Disabling pin for " + a);
    return this;
  };
});

That script basically intercepts the CertificatePinner.Builder.add() method so that no hostname and pins are added at all, but i believe that is something that is run at application start-up. My workflow is currently this:

1) install objection patched apk (com.test.app) to emulator 2) open app in emulator 3) create hook.js script with above code 3) in my terminal run objection --gadget com.test.app explore 4) in REPL, run import hook.js

This doesn't do anything and it makes sense to me since the CertificatePinner.Builder.add() would have already been called before I import my script.

What do i need to change here? Ideally there would be a way to schedule my script to be run on application start up somehow. Thanks.

rhyek commented 4 years ago

Ok, I just went through https://github.com/sensepost/objection/wiki/Early-Instrumentation and it all seems simple enough, but my patched application is not starting in a paused state. Am i missing something? A missing option, perhaps?

I did have to patch the apk with these options: objection patchapk --source test.apk --skip-resources --pause to avoid some errors I was getting. Might this be why my patched version of the apk is starting up without waiting for my REPL session?

I am using objection 1.8.4, apktool 2.4.1, openjdk 1.8.0_242

rhyek commented 4 years ago

Ok, I went ahead and just edited the Smali code myself, built the apk, signed it and everything works as expected. Feel free to close the issue, but objection did not work for me in this regard.

leonjza commented 4 years ago

Hey! So the early instrumentation in objection is at fault here, and not the way you had to patch the agent in. Launching objection and then running the import command means that app would have been resumed already so that objections own agent logic can be run before your script comes along. From the early instrumentation article, really whats happening behind the scenes is a crappy race condition to just run some agent logic before others.

There is actually a PR to improve this here which still needs to be properly tested.

I am considering adding another change to mimic the frida command-line tool's behaviour that would allow importing your script, then manually resuming when you are ready before the rest of the agent logic comes along.

rhyek commented 4 years ago

ok, seems good. what's odd to me is from the docs I had understood that running a patched apk would have it be in a paused state immediately giving me all the time in a world to run the explore command and only then resume. that is not happening at all. the app starts up normally. is this what you're talking about as well? you're saying the race condition is what's preventing the app from pausing?

leonjza commented 4 years ago

When you patch an app, the launchable activity gets a loadLibrary call (unless you specify another class to target) in its constructor. After you install the patched app on your device and tap to launch it, as soon as the launchable activities constructor is run, the Frida gadget loads, affectively pausing further execution. This is the paused state that is referred to in the docs where the Frida Gadget is waiting for a client to connect and tell it to resume. The moment you connect objection, it will inject its own agent and resume the application to interact with it.

This "connect -> resume" is the part that needs improvement, which is tricky at the moment as some of the REPL is populated from responses received from the agent and needs some refactoring.

Hope that helps!

rhyek commented 4 years ago

ok, then what I am saying is my patched apk is not opening in a paused state.

leonjza commented 4 years ago

Gotcha. So I think there could be a few reasons and it would definitely need some debugging. I would consider:

rhyek commented 4 years ago

The original apk is not mine, but what I can tell you is the code is obfuscated.

leonjza commented 4 years ago

Patching should be ok even if the app is obfuscated. Last thing I can suggest is to try patch the class defined in the app manifests app:name key in the application tag with --target-class flag.

T3rm1 commented 4 years ago

I'm currently struggling with nearly the same problem. I want to patch the same method as rhyek. I'm doing this on a rooted phone and after many hours of trying different things I think this should be the correct approach: objection --gadget "com.car2go" -N -h 192.168.178.24 explore --startup-script start.js So this should start the app with frida and then automatically load my script at startup through objection.

However it gives me this error:

Using networked device @`192.168.178.24:27042`
Agent injected and responds ok!
Importing and running startup script at: <_io.TextIOWrapper name='start.js' mode='r' encoding='cp1252'>
Traceback (most recent call last):
  File "c:\python37-32\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "c:\python37-32\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\Python37-32\Scripts\objection.exe\__main__.py", line 7, in <module>
  File "c:\python37-32\lib\site-packages\click\core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "c:\python37-32\lib\site-packages\click\core.py", line 782, in main
    rv = self.invoke(ctx)
  File "c:\python37-32\lib\site-packages\click\core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "c:\python37-32\lib\site-packages\click\core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "c:\python37-32\lib\site-packages\click\core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "c:\python37-32\lib\site-packages\objection\console\cli.py", line 149, in explore
    response = agent.single(startup_script.read())
  File "c:\python37-32\lib\site-packages\objection\utils\agent.py", line 249, in single
    self.device.resume(self.spawned_pid)
  File "c:\python37-32\lib\site-packages\frida\core.py", line 26, in wrapper
    return f(*args, **kwargs)
  File "c:\python37-32\lib\site-packages\frida\core.py", line 148, in resume
    self._impl.resume(self._pid_of(target))
frida.InvalidArgumentError: invalid PID
Asking jobs to stop...
Unloading objection agent...

I'm not sure if this fault is Frida or Objection. The same command does work if the app was already launched before. I assume the early execution is problematic.

leonjza commented 4 years ago

I'm not sure if this fault is Frida or Objection.

This looks like objections fault for not getting the right PID to resume the app when you provide a startup script. Maybe this is the culprit. I am not sure and will have to debug that as well.

T3rm1 commented 4 years ago

I'm not sure if this fault is Frida or Objection.

This looks like objections fault for not getting the right PID to resume the app when you provide a startup script. Maybe this is the culprit. I am not sure and will have to debug that as well.

Here is the log with --debug

[debug] Injecting agent...
Using networked device @`192.168.178.24:27042`
[debug] Attempting to attach to process: `com.car2go`
[debug] Unable to find process: `com.car2go`, attempting spawn
[debug] PID `24764` spawned, attaching...
[debug] Resuming PID `24764`
Agent injected and responds ok!
Importing and running startup script at: <_io.TextIOWrapper name='start.js' mode='r' encoding='cp1252'>
[debug] Resuming PID `24764`

Maybe resuming twice is the bug? The process is resumed after attaching and once more after the startup script has been loaded.

leonjza commented 4 years ago

Maybe resuming twice is the bug? The process is resumed after attaching and once more after the startup script has been loaded.

Ah that may very well be it. Try and comment out the block I quoted maybe and see if that helps? If you are unsure how to get a dev environment up, this may help.

Keep in mind the original context of this ticket though. Early instrumentation needs some love.

T3rm1 commented 4 years ago

I added self.resumed = True after this line

But now I get another error:

Importing and running startup script at: <_io.TextIOWrapper name='sharenowHooks.js' mode='r' encoding='cp1252'>
[]
Traceback (most recent call last):
  File "c:\python37-32\lib\runpy.py", line 193, in _run_module_as_main
    "__main__", mod_spec)
  File "c:\python37-32\lib\runpy.py", line 85, in _run_code
    exec(code, run_globals)
  File "C:\Python37-32\Scripts\objection.exe\__main__.py", line 7, in <module>
  File "c:\python37-32\lib\site-packages\click\core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "c:\python37-32\lib\site-packages\click\core.py", line 782, in main
    rv = self.invoke(ctx)
  File "c:\python37-32\lib\site-packages\click\core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "c:\python37-32\lib\site-packages\click\core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "c:\python37-32\lib\site-packages\click\core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "c:\python37-32\lib\site-packages\objection\console\cli.py", line 156, in explore
    device_info = get_device_info()
  File "c:\python37-32\lib\site-packages\objection\commands\device.py", line 41, in get_device_info
    package_info = api.env_android()
  File "c:\python37-32\lib\site-packages\frida\core.py", line 401, in method
    return script._rpc_request('call', js_name, args, **kwargs)
  File "c:\python37-32\lib\site-packages\frida\core.py", line 26, in wrapper
    return f(*args, **kwargs)
  File "c:\python37-32\lib\site-packages\frida\core.py", line 333, in _rpc_request
    raise result[2]
frida.InvalidOperationError: script is destroyed
Asking jobs to stop...
Unloading objection agent...
[debug] Calling unload()
Unable to run cleanups: script is destroyed

So, when will this love happen you are speaking of? :D

0xkasper commented 4 years ago

I have been having the same problem. I'm using the same script as @rhyek where I change the CertificatePinner.Builder.add function on startup. My application is obfuscated and using Jadx I found that the function is called a in class m.h$a. My code is:

console.log("Waiting for Java..");

while(!Java.available) {
    console.log("Java not available yet");
}

console.log("Java available..");

Java.perform(function(){
    console.log("Entered perform.."); 
    var Pinner = Java.use("m.h$a");
    console.log(Pinner);
    console.log(Pinner.a.overload('java.lang.String', '[Ljava.lang.String;'));
    Pinner.a.overload('java.lang.String', '[Ljava.lang.String;').implementation = function(a, b)
    {
        console.log("Disabling pin for " + a);
        return this;
    }
    console.log('Done');
});

Then I run the following command: objection --gadget XXX explore --startup-script hook.js with the following output:

Using USB device `Android Emulator 5554`
Agent injected and responds ok!
Importing and running startup script at: <_io.TextIOWrapper name='hook.js' mode='r' encoding='cp1252'>
Waiting for Java..
Java available..
Entered perform..
<class: m.h$a>
function e() { [ecmascript code] }
Done
Traceback (most recent call last):
  File "C:\Users\Kasper\AppData\Local\Programs\Python\Python38-32\Scripts\objection-script.py", line 11, in <module>
    load_entry_point('objection==1.9.6', 'console_scripts', 'objection')()
  File "c:\users\kasper\appdata\local\programs\python\python38-32\lib\site-packages\click\core.py", line 829, in __call__
    return self.main(*args, **kwargs)
  File "c:\users\kasper\appdata\local\programs\python\python38-32\lib\site-packages\click\core.py", line 782, in main
    rv = self.invoke(ctx)
  File "c:\users\kasper\appdata\local\programs\python\python38-32\lib\site-packages\click\core.py", line 1259, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
  File "c:\users\kasper\appdata\local\programs\python\python38-32\lib\site-packages\click\core.py", line 1066, in invoke
    return ctx.invoke(self.callback, **ctx.params)
  File "c:\users\kasper\appdata\local\programs\python\python38-32\lib\site-packages\click\core.py", line 610, in invoke
    return callback(*args, **kwargs)
  File "c:\users\kasper\appdata\local\programs\python\python38-32\lib\site-packages\objection\console\cli.py", line 149, in explore
    response = agent.single(startup_script.read())
  File "c:\users\kasper\appdata\local\programs\python\python38-32\lib\site-packages\objection\utils\agent.py", line 249, in single
    self.device.resume(self.spawned_pid)
  File "c:\users\kasper\appdata\local\programs\python\python38-32\lib\site-packages\frida\core.py", line 26, in wrapper
    return f(*args, **kwargs)
  File "c:\users\kasper\appdata\local\programs\python\python38-32\lib\site-packages\frida\core.py", line 148, in resume
    self._impl.resume(self._pid_of(target))
frida.InvalidArgumentError: invalid PID
Asking jobs to stop...
Unloading objection agent...

The app also starts, but certificate pinning is still enabled. From the output I can see that the script works as expected, since the correct class and function are found. But the console.log() inside of the overwritten function is never fired. So I assume that the function has already been called before the script was injected, and making it useless. Is there anyway to get this script running before the app creates it's CertificatePinner instance?

The article from NVISO states that doing import hook.js when objection is already running should work, but as @rhyek said, that doesn't make sense to me because the app should already instantiated it when it's up and running. I'm sure it does work and the function is overwritten, but it should happen before the app starts.

I'm not sure about the invalid PID error, but the script doesn't work when I fire it directly with Frida: frida -U -f XXX -l hook.js --no-pause This starts the app and immediately crashes it. What is strange though, is that I get output from the first two console.log()'s, but but not from the console.log()'s inside of the Java.perform(). Am I missing something? I'm quite new to this.

leonjza commented 4 years ago

Regarding the hook, hooking post instantiation is usualy ok. It's tricky in that you are hooking an add method which probably only happens once, hence the need for early instrumentation. I'd suggest you hunt for the actual verification logic so that you can hook at any time. As for the hook itself, the return this means this function itself is being returned, instead of the return value the function called is expecting which is why your hook is probably not working. For example, here we return an empty ArrayList which is the expected return type.

Regarding the PID error, this is probably a bug in the current early instrumentation logic when using a script. I am expecting an import hook.js to work just fine if you hunt down the verification logic, assuming your hook is correct. If your hook is not firing using the default frida tools, it will most certainly fail in objection as well.

The best early instrumentation implementation is going to be using the frida command line and a valid hook for now.

andyacer commented 3 years ago

For anyone that encounters this, I found a workaround that might sound obvious but took me a long while to figure out. Just use Frida natively to run your early instrumentation script, then attach to the process with Objection after that. This requires two terminal windows, or starting frida as a background job.

# After running the below line, you can interact with the application using Objection
#  if you use its PID as the Gadget ID.
frida -U -f $targetapp -l ./frida_okhttp_pinner_bypass.js --no-pause

# -- In a separate terminal window
# Get PID of app while its running and instrumented with the above Frida script
frida-ps -Ua

# Attach to the target app with Objection
objection -g 11987 explore
abevol commented 3 years ago

For anyone that encounters this, I found a workaround that might sound obvious but took me a long while to figure out. Just use Frida natively to run your early instrumentation script, then attach to the process with Objection after that. This requires two terminal windows, or starting frida as a background job.

# After running the below line, you can interact with the application using Objection
#  if you use its PID as the Gadget ID.
frida -U -f $targetapp -l ./frida_okhttp_pinner_bypass.js --no-pause

# -- In a separate terminal window
# Get PID of app while its running and instrumented with the above Frida script
frida-ps -Ua

# Attach to the target app with Objection
objection -g 11987 explore

To be honest, I have given up objection, and then found that objection is not irreplaceable, and only using Frida scripts gave me more freedom.

venkata16sidhartha commented 3 years ago

Well IDK if i was right or wrong, you can load your app by running objection -g explore and once its loaded enter import <script .js>, its throwing same error, but working too, so you could do like this, hope this helps.

rudSarkar commented 2 years ago

For anyone that encounters this, I found a workaround that might sound obvious but took me a long while to figure out. Just use Frida natively to run your early instrumentation script, then attach to the process with Objection after that. This requires two terminal windows, or starting frida as a background job.

# After running the below line, you can interact with the application using Objection
#  if you use its PID as the Gadget ID.
frida -U -f $targetapp -l ./frida_okhttp_pinner_bypass.js --no-pause

# -- In a separate terminal window
# Get PID of app while its running and instrumented with the above Frida script
frida-ps -Ua

# Attach to the target app with Objection
objection -g 11987 explore

It's really helped.

Oldman19 commented 1 year ago

But how can I find frida_okhttp_pinner_bypass.js, who can share

kin9-0rz commented 1 year ago

For anyone that encounters this, I found a workaround that might sound obvious but took me a long while to figure out. Just use Frida natively to run your early instrumentation script, then attach to the process with Objection after that. This requires two terminal windows, or starting frida as a background job.

# After running the below line, you can interact with the application using Objection
#  if you use its PID as the Gadget ID.
frida -U -f $targetapp -l ./frida_okhttp_pinner_bypass.js --no-pause

# -- In a separate terminal window
# Get PID of app while its running and instrumented with the above Frida script
frida-ps -Ua

# Attach to the target app with Objection
objection -g 11987 explore

It's not beautiful and not a good way, but it works.

kin9-0rz commented 1 year ago

For anyone that encounters this, I found a workaround that might sound obvious but took me a long while to figure out. Just use Frida natively to run your early instrumentation script, then attach to the process with Objection after that. This requires two terminal windows, or starting frida as a background job.

# After running the below line, you can interact with the application using Objection
#  if you use its PID as the Gadget ID.
frida -U -f $targetapp -l ./frida_okhttp_pinner_bypass.js --no-pause

# -- In a separate terminal window
# Get PID of app while its running and instrumented with the above Frida script
frida-ps -Ua

# Attach to the target app with Objection
objection -g 11987 explore

It's not beautiful and not a good way, but it works.

Don't use --no-pause, please enter the command resume to resume the process.

$ objection version
objection: 1.11.0
$ objection -s -n $target_package start -S $your_srcipt.js
 ... (run) on (Android: 9) [usb] # resume
 ... (run) on (Android: 9) [usb] #
IPMegladon commented 1 month ago

Closing issue as stale, feel free to reopen.