POC详情: bc73ad5b2c1709de4e2b4648aa66002269575a9e

来源
关联漏洞
标题: Google Pixel 安全漏洞 (CVE-2022-20474)
描述:Google Pixel是美国谷歌(Google)公司的一款智能手机。 Google Pixel 存在安全漏洞。目前尚无此漏洞的相关信息,请随时关注CNNVD或厂商公告。
描述
PoC of CVE-2022-20474
介绍




# CVE-2022-20474

### 前言

最近正仔细学习[michalbednarski](https://github.com/michalbednarski)的[LeakValue文章](https://github.com/michalbednarski/LeakValue)。在与Canyie讨论的时候,他说在这篇文章中还提到了一种在LazyValue场景下Self-changing Bundle的情况,然后我就去翻找原文,果真有这么一段,而我在读Michal的文章的时候直接漏掉了。原文中如下说到:

> (Also `LazyValue` with negative length specified can be used (without using other bugs described in this writeup) to create self-changing `Bundle`, the thing `LazyValue` was created to eliminate. But that is another story (and separately reported to Google), in this exploit I'm aiming for more)

Michal指的应该就是CVE-2022-20474 ([bulletin](https://source.android.com/docs/security/bulletin/2022-12-01#framework), [patch](https://android.googlesource.com/platform/frameworks/base/+/569c3023f839bca077cd3cccef0a3bef9c31af63^!/)),我瞅了一眼patch:

```diff
@@ -4388,6 +4388,9 @@
    public Object readLazyValue(@Nullable ClassLoader loader) {
         int start = dataPosition();
         int type = readInt();
         if (isLengthPrefixed(type)) {
             int objectLength = readInt();
+            if (objectLength < 0) {
+                return null;
+            }
             int end = MathUtils.addOrThrow(dataPosition(), objectLength);
             int valueLength = end - start;
             setDataPosition(end);
             return new LazyValue(this, start, valueLength, type, loader);
         } else {
            return readValue(type, loader, /* clazz */ null);
         }
    }             
```

补丁链接中函数并没有很完整,让我来补全它之后在仔细看一下。这里的`objectLength`就是`LazyValue`中的`length`,事实上只代表了LazyValue中包含的**可变对象的长度**,而整个LazyValue的长度应该是`mLength`字段来控制。而`valueLength`才代表了整个`LazyValue`长度,在`LazyValue`对象中记录为`mLength`。

让我们在对照一下`LazyValue`的布局格式如下:

```java
    		 /**
         *                      |   4B   |   4B   |
         * mSource = Parcel{... |  type  | length | object | ...}
         *                      a        b        c        d
         * length = d - c
         * mPosition = a
         * mLength = d - a
         */
```

基于上述的内容,我们可以得到如下事实:

1. `mLength`代表整个`LazyValue`的长度,`mLength` = `objectLength` + 8字节。
2. `objectLength`应该大于等于0。
3. `LazyValue`对象中只保存了`mLength`,而没保存`objectLength`,因为`LazyValue`作内存拷贝的时候是基于整个对象来做拷贝的。
4. 读完之后的指针是前移的,可能还会再读一遍LazyValue

然后呢,经过深思熟虑,我们一致认为,这些事实没!啥!X!用!因为基于上面的事实,也只能在read的时候修改一次,而我们知道`Self-changed Bundle`的核心思想是read完成后再修改,才能绕过安全检查。

正当我们准备放弃的时候,突然发现了补丁的描述中有一些细节:

>Addresses a security vulnerability where a (-8) length object would
>cause dataPosition to be reset back to the statt of the value, and be
>re-read again.

### 异常的`objectLength`

其中提到,`objectLength``-8`的时候,会存在一些问题,这个给了我们一些额外的启示。此时`LazyValue`还能正常的apply吗?

```java
 				@Override
        public Object apply(@Nullable Class<?> clazz, @Nullable Class<?>[] itemTypes) {
            Parcel source = mSource;
            if (source != null) {
                synchronized (source) {
                    // Check mSource != null guarantees callers won't ever see different objects.
                    if (mSource != null) {
                        int restore = source.dataPosition();
                        try {
                            source.setDataPosition(mPosition);
                            mObject = source.readValue(mLoader, clazz, itemTypes);
                        } finally {
                            source.setDataPosition(restore);
                        }
                        mSource = null;
                    }
                }
            }
            return mObject;
        }

  	/**
     * @see #readValue(int, ClassLoader, Class, Class[])
     */
    @Nullable
    private <T> T readValue(@Nullable ClassLoader loader, @Nullable Class<T> clazz,
            @Nullable Class<?>... itemTypes) {
        int type = readInt();
        final T object;
        if (isLengthPrefixed(type)) {
            int length = readInt();
            int start = dataPosition();
            object = readValue(type, loader, clazz, itemTypes);
            int actual = dataPosition() - start;
            if (actual != length) {
                Slog.wtfStack(TAG,
                        "Unparcelling of " + object + " of type " + Parcel.valueTypeToString(type)
                                + "  consumed " + actual + " bytes, but " + length + " expected.");
            }
        } else {
            object = readValue(type, loader, clazz, itemTypes);
        }
        return object;
    }
```

可以看到真正的`readValue`会从`mPosition`开始读,然后依次读取`LazyType``LazyLength`,然后就进入正常的`Value`读取流程了,例如`Parcelable`需要读取`ClassName`,然后执行`createFromParcel`,读完了之后普通的`Key-Value`没有任何区别,也不会对后续的序列化产生影响。再度回顾一下,`Self-changed Bundle`的核心思想是read完成后再修改,这里只不过是一个普通的越界读取而已,看来这个方向行不通。

那么`LazyValue`在此过程中没有`apply`呢,换言之就以`LazyValue`的身份继续参与IPC,此时会调用其`writeToParcel`函数:

```java
     public void writeToParcel(Parcel out) {
            Parcel source = mSource;
            if (source != null) {
                synchronized (source) {
                    if (mSource != null) {
                        out.appendFrom(source, mPosition, mLength);
                        return;
                    }
                }
            }
            out.writeValue(mObject);
        }
```

整个`LazyValue`会直接复制过去,除非`mLength = 0`。等等!上文提到`mLength` = `objectLength` + 8字节,而从patch信息中可以知道,要想处罚漏洞`objectLength`应为-8,那么此时`mLength = 0`就成立了。换言之,在这个场景下整个`LazyValue`就直接没了,只会拷贝`String Key`,这样就造成了一个缺失的写入,`Self-changed Bundle`的条件直接满足。

### 复现

| 值              | 说明                                                         |
| --------------- | ------------------------------------------------------------ |
| "Cxxsheng"      | 第一个key                                                    |
| 4               | 会读取两轮:第一轮代表`VAL_PARCELABLE`;第二轮变成第二个Key的String Length |
| -8              | 会读取两轮:第一轮代表`LazyValue``objectLength`,会导致读取指针前移,导致两轮读取;第二轮变成第二个Key 的String Value |
| 0               | 第二个Key 的String Value                                     |
| 0               | 第二个Key 的String Value                                     |
| 1               | `VAL_INTEGER`                                                |
| 0               | 第二个Value                                                  |
| 11              | 第三个Key的String Length                                     |
| 32              | 第三个Key的String Value                                      |
| 0               | 第三个Key的String Value                                      |
| 0               | 第三个Key的String Value                                      |
| number1         | 第三个Key的String Value,这两个值用于调整排序                |
| number2         | 第三个Key的String Value,这两个值用于调整排序                |
| 0               | 第三个Key的String Value                                      |
| 13              | `VAL_BYTEARRAY`                                              |
| LazyValue的长度 | 计算得出                                                     |
| ByteArray的长度 | 计算得出                                                     |
| ByteArray       | 其中包含了了恶意的Key-Value,即`Intent.EXTRA_INTENT``Intent` |

可以通过`number1``numbder2`来操作第三个值在`ArrayMap`中的排序。因为`ArrayMap`是依据`key``hashcode`来排序的,这样可以让第三个的值在反序列化后变成第二个,紧跟在第一个"Cxxsheng"的后面,如下所示:

```txt
Bundle[{Cxxsheng=Supplier{VAL_PARCELABLE@28+0},[一段乱码]=[恶意的ByteArray], [一段乱码]=0}]
```

可以看到我们读取的顺序也会和写入的顺序不一样。在完成写入后,上文我们分析过一整个LazyValue都被丢了,而第三个`Key-Value`被重新排序到第二个,其中也包括`type``objectLength`,因此,页面布局将变成如下所示:

| 值                                  | 说明                                                         |
| :---------------------------------- | :----------------------------------------------------------- |
| "Cxxsheng"                          | 第一个key                                                    |
| 11                                  | VAL_LIST                                                     |
| 32                                  | 第一个Value的长度,后面的合不合法已经都不重要(反正不会去apply这个LazyValue),这个直接指到ByteArray中恶意Intent的前面 |
| 0                                   | LazyValue中的值                                              |
| 0                                   | LazyValue中的值                                              |
| number1                             | LazyValue中的值                                              |
| number2                             | LazyValue中的值                                              |
| 0                                   | LazyValue中的值                                              |
| 13                                  | LazyValue中的值                                              |
| LazyValue的长度                     | LazyValue中的值                                              |
| ByteArray的长度                     | LazyValue中的值                                              |
| ByteArray开始/`Intent.EXTRA_INTENT` | 第二个Key                                                    |
| Intent                              | 第二个Value                                                  |
| 第三个Key-Value                     | 被排到了最后                                                 |

最后欣赏一下通过模拟的输出图:
![description](https://raw.githubusercontent.com/cxxsheng/CVE-2022-20474/refs/heads/main/img.png)

文件快照
 [4.0K]  /data/pocs/bc73ad5b2c1709de4e2b4648aa66002269575a9e
├── [4.0K]  app
│   ├── [ 980]  build.gradle
│   ├── [ 750]  proguard-rules.pro
│   └── [4.0K]  src
│       ├── [4.0K]  androidTest
│       │   └── [4.0K]  java
│       │       └── [4.0K]  com
│       │           └── [4.0K]  cxxsheng
│       │               └── [4.0K]  cve_2022_20474
│       │                   └── [ 768]  ExampleInstrumentedTest.java
│       ├── [4.0K]  main
│       │   ├── [ 936]  AndroidManifest.xml
│       │   ├── [4.0K]  java
│       │   │   └── [4.0K]  com
│       │   │       └── [4.0K]  cxxsheng
│       │   │           └── [4.0K]  cve_2022_20474
│       │   │               └── [6.2K]  MainActivity.java
│       │   └── [4.0K]  res
│       │       ├── [4.0K]  drawable
│       │       │   ├── [5.5K]  ic_launcher_background.xml
│       │       │   └── [1.7K]  ic_launcher_foreground.xml
│       │       ├── [4.0K]  layout
│       │       │   └── [ 805]  activity_main.xml
│       │       ├── [4.0K]  mipmap-anydpi
│       │       │   ├── [ 343]  ic_launcher_round.xml
│       │       │   └── [ 343]  ic_launcher.xml
│       │       ├── [4.0K]  mipmap-hdpi
│       │       │   ├── [2.8K]  ic_launcher_round.webp
│       │       │   └── [1.4K]  ic_launcher.webp
│       │       ├── [4.0K]  mipmap-mdpi
│       │       │   ├── [1.7K]  ic_launcher_round.webp
│       │       │   └── [ 982]  ic_launcher.webp
│       │       ├── [4.0K]  mipmap-xhdpi
│       │       │   ├── [3.8K]  ic_launcher_round.webp
│       │       │   └── [1.9K]  ic_launcher.webp
│       │       ├── [4.0K]  mipmap-xxhdpi
│       │       │   ├── [5.8K]  ic_launcher_round.webp
│       │       │   └── [2.8K]  ic_launcher.webp
│       │       ├── [4.0K]  mipmap-xxxhdpi
│       │       │   ├── [7.6K]  ic_launcher_round.webp
│       │       │   └── [3.8K]  ic_launcher.webp
│       │       ├── [4.0K]  values
│       │       │   ├── [ 147]  colors.xml
│       │       │   ├── [  76]  strings.xml
│       │       │   └── [ 408]  themes.xml
│       │       ├── [4.0K]  values-night
│       │       │   └── [ 332]  themes.xml
│       │       └── [4.0K]  xml
│       │           ├── [ 478]  backup_rules.xml
│       │           └── [ 551]  data_extraction_rules.xml
│       └── [4.0K]  test
│           └── [4.0K]  java
│               └── [4.0K]  com
│                   └── [4.0K]  cxxsheng
│                       └── [4.0K]  cve_2022_20474
│                           └── [ 388]  ExampleUnitTest.java
├── [ 163]  build.gradle
├── [4.0K]  gradle
│   ├── [ 939]  libs.versions.toml
│   └── [4.0K]  wrapper
│       ├── [ 58K]  gradle-wrapper.jar
│       └── [ 230]  gradle-wrapper.properties
├── [1.2K]  gradle.properties
├── [5.6K]  gradlew
├── [2.7K]  gradlew.bat
├── [ 74K]  img.png
├── [ 11K]  README.md
└── [ 537]  settings.gradle

31 directories, 37 files
神龙机器人已为您缓存
备注
    1. 建议优先通过来源进行访问。
    2. 如果因为来源失效或无法访问,请发送邮箱到 f.jinxu#gmail.com 索取本地快照(把 # 换成 @)。
    3. 神龙已为您对POC代码进行快照,为了长期维护,请考虑为本地POC付费,感谢您的支持。