objection icon indicating copy to clipboard operation
objection copied to clipboard

[bug] Android screenshot fails

Open sdcampbell opened this issue 4 years ago • 2 comments

Describe the bug Error when attempting to screenshot on Android. This is a different error message than the one found in #222.

WARNING: Ignoring this template and not completing the fields could result in your issue simply being closed.

To Reproduce Steps to reproduce the behavior:

  1. Run command 'android ui screenshot test.png'

Expected behavior Expected to find screenshot in current directory but instead received an error.

Evidence / Logs / Screenshots Any output from objection, such as stack traces or errors that occurred. Be sure to run objection with the --debug flag so that errors from the agent are verbose enough to debug. For example:

[~]$ objection --debug -g [redacted] explore
[debug] Agent path is: /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/objection/agent.js
[debug] Injecting agent...
Using USB device `Pixel`
[debug] Attempting to attach to process: `[redacted]`
[debug] Process attached!
Agent injected and responds ok!

     _   _         _   _
 ___| |_|_|___ ___| |_|_|___ ___
| . | . | | -_|  _|  _| | . |   |
|___|___| |___|___|_| |_|___|_|_|
      |___|(object)inject(ion) v1.9.1

     Runtime Mobile Exploration
        by: @leonjza from @sensepost

[tab] for command suggestions
[redacted] on (google: 9) [usb] # android ui screenshot test.png
A Frida agent exception has occurred.
Error: Current thread is not attached to the Java VM; please move this code inside a Java.perform() callback
    at frida/node_modules/frida-java-bridge/lib/vm.js:28
    at frida/node_modules/frida-java-bridge/lib/types.js:463
    at frida/node_modules/frida-java-bridge/lib/types.js:481
    at stringify (native)
    at [anon] (/_frida.js:43)
    at frida/runtime/core.js:48
    at i (frida/runtime/message-dispatcher.js:28)
    at frida/runtime/message-dispatcher.js:17
    at node_modules/core-js/library/modules/es6.promise.js:21

Python stack trace: Traceback (most recent call last):
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/objection/console/repl.py", line 371, in start_repl
    self.run_command(document)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/objection/console/repl.py", line 185, in run_command
    exec_method(arguments)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/objection/commands/ui.py", line 110, in android_screenshot
    data = api.android_ui_screenshot()
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/frida/core.py", line 401, in method
    return script._rpc_request('call', js_name, args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/frida/core.py", line 26, in wrapper
    return f(*args, **kwargs)
  File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/frida/core.py", line 333, in _rpc_request
    raise result[2]
frida.core.RPCException: Error: Current thread is not attached to the Java VM; please move this code inside a Java.perform() callback
    at frida/node_modules/frida-java-bridge/lib/vm.js:28
    at frida/node_modules/frida-java-bridge/lib/types.js:463
    at frida/node_modules/frida-java-bridge/lib/types.js:481
    at stringify (native)
    at [anon] (/_frida.js:43)
    at frida/runtime/core.js:48
    at i (frida/runtime/message-dispatcher.js:28)
    at frida/runtime/message-dispatcher.js:17
    at node_modules/core-js/library/modules/es6.promise.js:21

Environment (please complete the following information):

  • Device: Android Pixel
  • OS: Android 9 (Sailfish)
  • Frida Version 12.8.16
  • Objection Version 1.9.1

Additional context Add any other context about the problem here.

sdcampbell avatar Apr 07 '20 18:04 sdcampbell

I'll need help fixing this.

leonjza avatar Apr 07 '20 18:04 leonjza

I could isolate the issue to the function export const screenshot = (): Promise<any> in userinterface.ts returning a Java object. Here is a minimal self contained sample running directly within frida -U gadget:

[SM-G950F::gadget]-> const wrapJavaPerform = function (fn) {
                       return new Promise(function (resolve, reject) {
                         Java.perform(function () {
                           try {
                             resolve(fn());
                           } catch (e) {
                             reject(e);
                           }
                         });
                       });
                     };
                     
                     var result;
                     wrapJavaPerform(function(){
                     
                           const activityThread = Java.use("android.app.ActivityThread");
                           const activity = Java.use("android.app.Activity");
                           const activityClientRecord = Java.use("android.app.ActivityThread$ActivityClientRecord");
                           const bitmap = Java.use("android.graphics.Bitmap");
                           const byteArrayOutputStream = Java.use("java.io.ByteArrayOutputStream");
                           const compressFormat = Java.use("android.graphics.Bitmap$CompressFormat");
                     
                           var bytes;
                     
                           const currentActivityThread = activityThread.currentActivityThread();
                           const activityRecords = currentActivityThread.mActivities.value.values().toArray();
                           
                           var currentActivity;
                     
                           for (var i = 0; i < activityRecords.length; i++) {
                             const activityRecord = Java.cast(activityRecords[i], activityClientRecord);
                             if (!activityRecord.paused.value) {
                               currentActivity = Java.cast(Java.cast(activityRecord, activityClientRecord).activity.value, activity);
                               break;
                             }
                           }
                           
                           if (currentActivity) {
                             const view = currentActivity.getWindow().getDecorView().getRootView();
                             view.setDrawingCacheEnabled(true);
                             const bitmapInstance = bitmap.createBitmap(view.getDrawingCache());
                             view.setDrawingCacheEnabled(false);
                     
                             const outputStream = byteArrayOutputStream.$new();
                             bitmapInstance.compress(compressFormat.PNG.value, 100, outputStream);
                             bytes = outputStream.buf.value;
                           }
                           
                           return bytes;
                     
                     }).then(function(value) { result = value; });

This works, but when one then tries to access the returned Java object in the result variable outside of a Java.perform call, the crash happens:

[SM-G950F::gadget]-> result
Traceback (most recent call last):
  File "/home/martin/.local/bin/frida", line 8, in <module>
    sys.exit(main())
  File "/home/martin/.local/lib/python3.5/site-packages/frida_tools/repl.py", line 693, in main
    app.run()
  File "/home/martin/.local/lib/python3.5/site-packages/frida_tools/application.py", line 166, in run
    self._reactor.run()
  File "/home/martin/.local/lib/python3.5/site-packages/frida_tools/application.py", line 397, in run
    self._run_until_return(self)
  File "/home/martin/.local/lib/python3.5/site-packages/frida_tools/repl.py", line 261, in _process_input
    if not self._eval_and_print(expression):
  File "/home/martin/.local/lib/python3.5/site-packages/frida_tools/repl.py", line 267, in _eval_and_print
    (t, value) = self._evaluate(expression)
  File "/home/martin/.local/lib/python3.5/site-packages/frida_tools/repl.py", line 423, in _evaluate
    result = self._script.exports.evaluate(text)
  File "/home/martin/.local/lib/python3.5/site-packages/frida/core.py", line 401, in method
    return script._rpc_request('call', js_name, args, **kwargs)
  File "/home/martin/.local/lib/python3.5/site-packages/frida/core.py", line 26, in wrapper
    return f(*args, **kwargs)
  File "/home/martin/.local/lib/python3.5/site-packages/frida/core.py", line 333, in _rpc_request
    raise result[2]
frida.core.RPCException: Error: Current thread is not attached to the Java VM; please move this code inside a Java.perform() callback
    at frida/node_modules/frida-java-bridge/lib/vm.js:28
    at frida/node_modules/frida-java-bridge/lib/types.js:463
    at frida/node_modules/frida-java-bridge/lib/types.js:481
    at stringify (native)
    at [anon] (/_frida.js:43)
    at frida/runtime/core.js:48
    at i (frida/runtime/message-dispatcher.js:28)
    at frida/runtime/message-dispatcher.js:20
    at o (frida/runtime/message-dispatcher.js:25)

The issue could be fixed by copying the Java bytes return value into a pure JavaScript array and returning the JavaScript array. But I am not a frida expert and there might be a cleaner solution...

Also, the screenshots produced in this way are somewhat lacking, not capturing dialog boxes, maps or other parts of the UI which might be relevant to the user. Perhaps the Media Projection API offers a better solution (https://medium.com/jamesob-com/recording-your-android-screen-7e0e75aae260)?

mtschirs avatar May 13 '20 15:05 mtschirs