{"id":1394,"date":"2021-03-25T13:45:56","date_gmt":"2021-03-25T12:45:56","guid":{"rendered":"https:\/\/www.thomaskeller.biz\/blog\/?p=1394"},"modified":"2021-03-25T13:46:49","modified_gmt":"2021-03-25T12:46:49","slug":"disabling-samsung-android-system-services-a-post-mortem","status":"publish","type":"post","link":"https:\/\/www.thomaskeller.biz\/blog\/2021\/03\/25\/disabling-samsung-android-system-services-a-post-mortem\/","title":{"rendered":"Disabling Samsung Android System Services &#8211; A post-mortem"},"content":{"rendered":"\n<p>I&#8217;m currently working in a project where we build an Android App that gets installed on an EMM (Enterprise Mobility Managed) devices work profile. The devices are mostly Samsung devices, running Android 9 or Android 10.<\/p>\n\n\n\n<p>More recently we got a big influx of crashes that left us back puzzled. Apparently when people took screenshots in the private profile, opened some app (like the browser) and then returned immediately to the work profile to our app, the application crashed as soon as they set the focus on an <code>EditText<\/code> field, with this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">Uncaught exception thrown in the UI: java.lang.SecurityException: No access to content:\/\/com.sec.android.semclipboardprovider\/images: neither user 1010241 nor current process has android.permission.INTERACT_ACROSS_USERS_FULL or android.permission.INTERACT_ACROSS_USERS\nat android.os.Parcel.createException(Parcel.java:2088)\nat android.os.Parcel.readException(Parcel.java:2056)\nat android.os.Parcel.readException(Parcel.java:2004)\nat android.sec.clipboard.IClipboardService$Stub$Proxy.getClipData(IClipboardService.java:959)\nat com.samsung.android.content.clipboard.SemClipboardManager.getLatestClip(SemClipboardManager.java:609)\nat android.widget.EditText.updateClipboardFilter(EditText.java:316)\nat android.view.inputmethod.InputMethodManager.startInputInner(InputMethodManager.java:2131)\n... <\/pre>\n\n\n\n<p>A quick Google search came back with almost nothing, only <a rel=\"noreferrer noopener\" data-ss1616673565=\"1\" href=\"https:\/\/stackoverflow.com\/questions\/28134128\/android-permission-interact-across-users-full\" target=\"_blank\">one stack overflow post<\/a> suggested that one should simply add the missing permission with <code>protectionLevel=\"signature\"<\/code> &#8211; which is of course non-sense for a non-system app that is not signed with the same key as the rest of the system framework. So, what do?<\/p>\n\n\n\n<p>Staring at the stacktrace I fired up the <a rel=\"noreferrer noopener\" data-ss1616673565=\"1\" href=\"https:\/\/cs.android.com\/android\/platform\/superproject\/+\/master:frameworks\/base\/core\/java\/android\/widget\/EditText.java\" target=\"_blank\">Google Android CodeSearch<\/a> and checked the sources of <code>EditText<\/code> &#8211; just to find a possible way to somehow disable \/ prevent the call to <code>updateClipboardFilter<\/code>. However, to my surprise, this API was completely nonexistant in AOSP!<\/p>\n\n\n\n<p>So, apparently we&#8217;ve had to deal with a completely proprietary Samsung API. Firing up Google for <code>SemClipboardManager<\/code> pointed me to <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/fonix232\/SCoverRE\/blob\/master\/Reversed\/framework\/com\/samsung\/android\/content\/clipboard\/SemClipboardManager.java\" data-type=\"URL\" data-id=\"https:\/\/github.com\/fonix232\/SCoverRE\/blob\/master\/Reversed\/framework\/com\/samsung\/android\/content\/clipboard\/SemClipboardManager.java\" target=\"_blank\">a several years old repository that partially disassembled the said class<\/a>, so I could have a closer look of what is actually going on.<\/p>\n\n\n\n<p>From what I saw there, the manager&#8217;s functionality could be disabled if I somehow found a way to overwrite the <code>isEnabled<\/code> method in this class to permanently return false &#8211; which the method usually only does if the device is in &#8220;emergency&#8221; or &#8220;ultra low power&#8221; mode. Ok, we have an attack vector!<\/p>\n\n\n\n<p>From my usual Android trickery I knew the easiest way to fumble with system services is to create a custom <code>ContextWrapper<\/code> and wrap any given base context in my <code>Activity<\/code>s <code>attachBaseContext<\/code> method, like so:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">class SomeActivity : AppCompatActivity {\n  ...\n  override fun attachBaseContext(newBase: Context) {\n    super.attachBaseContext(FixSamsungStuff(newBase))\n  }\n  ...\n}<\/pre>\n\n\n\n<p>Now, one could think &#8220;why deal with the internal service workings at all, wouldn&#8217;t it be enough to simply disable \/ null the service instead&#8221;, i.e. like this?<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">class FixSamsungStuff(base: Context): ContextWrapper(base) {\n  override fun getSystemService(name: String): Any {\n    \/\/ the name is from `adb shell service list`\n    return if (name == \"semclipboard\") {\n      null\n    } else {\n      super.getSystemService(name)\n    }\n  }\n}<\/pre>\n\n\n\n<p>But the fine folks at Samsung of course don&#8217;t check for the non-existance of their service and instead of receiving the above <code>SecurityException<\/code> I was presented a <code>NullPointerException<\/code> instead.<\/p>\n\n\n\n<p>So, now it got interesting &#8211; how would I actually proxy a method of a class to return a different value? From my testing adventures I knew this must be possible, because <code>Mockito.spy(instance)<\/code> exactly allows to do that, on the JVM and on ART.<\/p>\n\n\n\n<p>So I came across <a rel=\"noreferrer noopener\" data-ss1616673565=\"1\" href=\"https:\/\/github.com\/raphw\/byte-buddy\/tree\/master\/byte-buddy-android\" data-type=\"URL\" data-id=\"https:\/\/github.com\/raphw\/byte-buddy\/tree\/master\/byte-buddy-android\" target=\"_blank\">ByteBuddy for Android<\/a>, by the fantastic Rafael Winterhalter. His example on his front page of the repo was easy enough to adapt for my use case:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">class FixSamsungStuff(base: Context): ContextWrapper(base) {\n  override fun getSystemService(name: String): Any {\n    val service = super.getSystemService(name)\n    return if (name == \"semclipboard\") {\n      interceptClipboardService(service)\n    } else {\n      service\n    }\n  }\n  \/\/\n  private fun interceptClipboardService(service: Any): Any {\n    val strategy = new AndroidClassLoadingStrategy.Wrapping(\n      getDir(\"generated\", Context.MODE_PRIVATE)\n    )\n    val dynamicType: Class&lt;Any> = new ByteBuddy()\n      .subclass(service.javaClass)\n      .method(ElementMatchers.named(\"isEnabled\"))\n      .intercept(FixedValue.value(false))\n      .make()\n      .load(service.javaClass.classLoader, strategy)\n      .getLoaded()\n    \/\/ constructor definition from the decompiled sources\n    val constructor = dynamicType.getConstructor(\n      Context::class.java, Handler::class.java\n    )\n    return constructor.newInstance(this, Handler())\n  }\n}<\/pre>\n\n\n\n<p>But when I tried to ran this, I got a <code>NoSuchFieldException<\/code> because the given constructor was unknown. Hrm&#8230; well, I thought, maybe the decompiled sources where just too old, so I debugged into the code and checked for <code>service.javaClass.getConstructors()<\/code> and <code>service.javaClass.getDeclaredConstructors()<\/code>, but both returned an empty list! How on earth could a Java class be instantiated without a constructor?!<\/p>\n\n\n\n<p>I learned that there are possibilities and that the JVM spec itself does actually not dictate the existance of a constructor for a class! So I contacted Rafael Winterhalter and he told me that there was probably some native code trickery going on, so my best bet should be to use <code>sun.reflect.ReflectionFactory<\/code> on my JVM. But this &#8211; of course &#8211; was not available on Android.<\/p>\n\n\n\n<p>A hint in the Android Study Group slack then pointed me into the right direction &#8211; <a rel=\"noreferrer noopener\" data-ss1616673565=\"1\" href=\"http:\/\/objenesis.org\/\" data-type=\"URL\" data-id=\"http:\/\/objenesis.org\/\" target=\"_blank\">objenesis<\/a>! This magic wand apparently allows you to create an instance of any class, regardless whether it has a constructor or not. So instantiating my ByteBuddy fake instance was as easy as doing<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">val objenesis = ObjenesisStd()\nreturn objenesis.newInstance(dynamicType)<\/pre>\n\n\n\n<p>And as awesome as it is, that worked instantly!<\/p>\n\n\n\n<p>This was a struggle-some, but in the end very worthy journey and I learned quite a few things on my way.<\/p>\n\n\n\n<p>Thanks for reading! <\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;m currently working in a project where we build an Android App that gets installed on an EMM (Enterprise Mobility Managed) devices work profile. The devices are mostly Samsung devices, running Android 9 or Android 10. More recently we got a big influx of crashes that left us back puzzled. Apparently when people took screenshots &hellip; <a href=\"https:\/\/www.thomaskeller.biz\/blog\/2021\/03\/25\/disabling-samsung-android-system-services-a-post-mortem\/\" class=\"more-link\">Continue reading <span class=\"screen-reader-text\">Disabling Samsung Android System Services &#8211; A post-mortem<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-1394","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/posts\/1394","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/comments?post=1394"}],"version-history":[{"count":3,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/posts\/1394\/revisions"}],"predecessor-version":[{"id":1397,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/posts\/1394\/revisions\/1397"}],"wp:attachment":[{"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/media?parent=1394"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/categories?post=1394"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.thomaskeller.biz\/blog\/wp-json\/wp\/v2\/tags?post=1394"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}