墙外通道:http://bits-please.blogspot.com/2015/08/effectively-bypassing-kptrrestrict-on.html

In this blog post, we'll take a look at a few ways that I've discovered in order to bypass kptr_restrict on Android, allowing for easier exploitation of vulnerabilities that require some information on the virtual addresses in which the kernel is loaded. But first, for those of you who aren't familiar with the "protection" offered by kptr_restrict, let's get you up to speed on the subject.

What's kptr_restrict?
As we've seen in the previous blog post, sometimes exploits require knowledge of internal kernel pointers - either in order to hijack them, or in order to corrupt them in a controllable manner.

This fact has been known for quite some time - enough time, in fact, for it to be addressed directly. The Linux kernel contains a feature which enables it to filter out such addresses in order to avoid leaking them to a potential attacker. This configurable feature is called "kptr_restrict", and has been present in the Android kernel source tree for at least two years.

As with nearly all configurable kernel parameters, there exists a special file which allows to set the way in which this feature behaves when attempting to filter kernel addresses. In the case of kptr_restrict, the file resides in "/proc/sys/kernel/kptr_restrict", but has some daunting permissions set:

Essentially, only root can modify its value, but any user can read it.

So how does kptr_restrict work? Well, first of all, kernel developers needed a way to mark kernel pointers as such, whenever those are outputted. This is achieved by using a new format specifier, "%pK", which is used to denote that the value written into that specifier contains a kernel pointer, and as such, should be protected.

There are three different values which control the protection offered by kptr_restrict:

  • 0 - The feature is completely disabled
  • 1 - Kernel pointers which are printed using "%pK" are hidden (replaced with zeroes), unless the user has the CAP_SYSLOG capability, and has not changed their UID/GID (to prevent leaking pointers from files opened before dropping permissions).
  • 2 - All kernel pointers printed using "%pK" are hidden

The default value of this configuration is chosen when building the kernel (via CONFIG_SECURITY_KPTR_RESTRICT), but for all modern Android devices that I've ever encountered, this value is always set to "2".

However - how many kernel developers actually know of the need to protect kernel pointers by using "%pK"? The can be easily answered by grepping the kernel for this format string. The answer is, as expected, quite sad:

Merely 35 times (in 23 files) within the entire kernel source code. Needless to say, kernel pointers are very often printed using the "normal" pointer format specifier, "%p" - a simple search shows many hundreds of such uses.

So now that we've set the stage, let's see why the protection offered by kptr_restrict is insufficient on it's own.

Method #1 - Getting dmesg from shell
All log messages printed by the kernel are written to a circular buffer held within the kernel's memory. Users may read from this buffer by invoking the "dmesg" (display message) command. This command actually accesses the buffer by invoking the syslog system call, as you can see from this strace output:

However, the syslog system call can't be accessed by just any user - specifically, the caller must either posses the extremely powerful CAP_SYS_ADMIN capability, or the weaker (and more specific) capability of CAP_SYSLOG.

Either way, most Android processes do not, in fact, have these capabilities, and therefore can't access the kernel log. Or can they? :)

Recall that within Android, the "init" process maintains a list of "services" which can be started or stopped as needed. These services are loaded by "init" upon boot, from a hard-coded list of configuration files, which are almost always stored on the root (read-only) partition, and are therefore read-only.

The configuration files are actually written using a language specific to Android, called the "Android Init Language". This language is pretty simple and easy to use, and allows full control over the permissions with which services are launched (UID/GID) as well as their parameters and "type" (for more information about the language itself, check out the link above).

Another feature of Android are "system properties" - these are key-value pairs which are maintained by the "property service", which is also a thread within the init process. This service allows basic access-control on various "sensitive" system properties, which prevents users from freely modifying any property they please.

These access-permissions for most properties used to be (until Android 4.4) hard-coded within the property service (since Android 5, the permissions are handled by using SELinux labels instead):

However, some properties get special treatment, namely - the "ctl.start" and "ctl.stop" system properties, which are used to either start or stop system services (defined, as mentioned before, using the "Android Init Language").

These properties are checked strictly using SELinux labels, in order to make sure that the privilege of modifying the status of system services is reserved strictly to certain users.

But here comes the surprising part - when connecting locally to the device using "adb" (Android Debug Bridge), we gain execution as the "shell" user. This user is always permitted start and stop one particular service - "dumpstate". Actually, this is used by a feature offered by the "adb" command-line utility, which enables developers to create bug reports containing full information from the device.

Running "adb" with this command-line argument (or simply executing "bugreport" from the adb shell), actually starts the "dumpstate" service by setting the "ctl.start" system property:

So let's take a look at the configuration for the "dumpstate" service:

Since the service has no "user" or "group" configurations, it is actually executed with the root user-ID and group-ID, which could be quite dangerous...

Luckily, the developers of the service were well aware of the potential security risks of running with such high capabilities, and therefore immediately after starting, the service drops its capabilities by modifying its user-ID, group-ID and capabilities, like so:

In short, the service sets the user and group IDs to those of the shell user, but makes sure that it keeps the CAP_SYSLOG capability explicitly.

Reading on reveals that "dumpstate" actually reads the kernel log using the syslog system call (which it is capable of executing since it has the CAP_SYSLOG capability), and writes the contents read back to the caller. Essentially, this means that within the context of the "adb shell", we can freely read the kernel log simply by executing the "bugreport" program. Nice.

However, this still doesn't solve the problem of getting needed symbols for exploits - since, as mentioned earlier, these symbols should generally be printed using the "%pK" format specifier, which means they would appear "censored" in the kernel log.

But alas, most pointers within the kernel are certainly not printed using the special format specifier, but instead use the regular "%p" format, and are therefore left uncensored. This means that the kernel log is typically a treasure trove of useful kernel pointers.

For example, when the kernel boots, the memory map of the kernel's different segments is printed, like so:

Now, assuming there's a single symbol we would like to find, we could simply dump the list of all kernel symbols using the virtual file containing all the symbols - /proc/kallsyms. When kptr_restrict is enabled, the list returned by kallsyms is censored (since it is printed using "%pK"), and therefore won't show any kernel pointers.

Censored symbols from kallsyms

However, the symbols returned by kallsyms are ordered by their addresses, even if those addresses aren't shown. Moreover, this task is made easier due to the fact that each segment is prefixed and postfixed by specially named marker symbols:

Segment Name           Start Marker                    End Marker         
.text _text _etext
.init __init_begin __init_end
.data _sdata _edata
.bss ___bss_start __bss_end

We can then use this list to deduce the location of different symbols by simply counting the number of symbols from the start or end marker to our wanted symbol, while adding up the sizes of each of the symbols encountered.

Another technique would be to cause a wanted kernel pointer to be written to the kernel log. For example, on Qualcomm-based devices (based on the "msm" kernel), whenever the video device is opened, the kernel virtual address of the video device is written to the kernel log:

msm_vidc_open leaks the pointer to the kernel log

Method #2 - Retrieving the kernel symbols statically

Why use this method?
In many cases, although the device itself is accessible, it may be heavily locked - for example, in extreme cases, adb access may be disabled (however poorly), which would complicate the usage of the first method (unless we manage to gain shell access). In this case, we may wish to build the complete list of kernel symbols from the kernel image itself, statically, without interacting directly with the device.
Also, since KASLR (Kernel Address Space Layout Randomization) is currently still unused in Android devices, there is no need to consider any kind of runtime modification to the location of the symbols present in the kernel image. This means that the kernel image must contain all the information needed to build the complete list of symbols, including their addresses, exactly as they would appear on a real "live" device.

How do I get a kernel image?

Assuming you have the full access to a live device, you could read the kernel image directly from the MMC, via /dev/block. However, in most cases reading the MMC blocks directly requires root permissions, which would make this method pretty obsolete, since with root access we could already disable kptr_restrict.

The more reasonable path to obtaining the kernel image would be to simply download the firmware file for your particular device, and unpack it. There are many tools which enable firmware unpacking for different devices (for example, I wrote a script to unpack to Nexus 5's bootloader - here), but many such tools are available, and are typically a google-search away.

Just one word of caution - make sure you download the exact kernel image matching the kernel on your device. You can find the running kernel's version by simply running "uname -a":

I have the image - now what?
In order to understand how to extract the full symbol list from a kernel image, we must first inspect the way in which a kernel image is built. Looking over the code, reveals that a special program is used to emit the symbols needed in a special format into the kernel's image, as part of the build process.  The program which receives the symbol map containing the location of each kernel symbol in the kernel's virtual address space, and outputs an assembly file containing the compressed symbol table, which is assembled into the resulting kernel image.

This means that all we need to do in order to rebuild this table from a raw kernel binary is to understand the exact format in which this symbol table is written. However, for a normally compiled kernel with no additional symbols, this turns out to be a little tricky.

Since the labels written by the script are not visible in the resulting kernel binary, the first thing we'd have to solve is how to find the beginning of the symbol table within the binary. Luckily, the solution turns out to be pretty simple - remember when we previously had a look at the symbol table from kallsyms? The first two symbols were marker symbols pointing to the beginning of the kernel's text segment. Since the kernel's code is loaded at a known address (typically, 0xC0008000), we can search for this value appearing at least twice consecutively within the binary, and attempt to parse the symbol table's structure starting at that address.

Going over the symbol table itself, reveals that it is terminated by a NULL address. Then, immediately following the symbol table, the actual number of symbols is written, which means we can easily verify that the table is actually well-formed.

Then, two tables of "markers" and "symbols" are written into the file. This is done in order to compress the size of the symbols within the table, and by doing so reduce the size of the kernel binary. The compression maps the 256 most used substrings (which are called tokens), into a single byte value. Then, each symbol's name is compressed into a pascal-style string of bytes (meaning, a byte marking the length of the string, then an actual string of characters). Each byte in the compressed name maps to a single tokens, which in turn corresponds to a single "most commonly used" substring. Putting it together, it looks like this:

According to kernel developers, this usually produces a compression ratio of about 50%.

I've written a python script which, given a raw kernel binary, extracts the full symbol table from the binary, in the exact same format as they are written within kallsyms. You can find it here. Please let me know if you find the script useful! 

Method #3 - Finding information disclosures within the kernel

This is the "classical" method which is commonly used in order to bypass the restrictions imposed by kptr_restrict. For a remote attacker wishing to target a wide variety of devices, it is quite often the best choice, since:

  • The first method typically requires shell access to the device, in order to execute the "bugreport" service
  • The second method requires you to obtain the kernel image, which could be tiresome to do for a very wide variety of devices

Sadly, it appears that kernel developers are far less aware of the possible risks of leaking kernel pointers than they are of other (e.g., memory corruption) vulnerabilities.

As a result, finding a kernel memory leak is usually a very short and simple task. To prove this point, after poking around for five minutes on a live device, I've come across such a leak, which is accessible from any context.

Whenever a socket is opened within Android, it is tagged using a netfilter driver called "qtaguid". This driver accounts for all the data sent or received by every socket (and tag), and allows some restrictions to be placed on sockets, based on the tag assigned to them. Android uses this feature in order to account for data usage by the device. The actual per-process breakdown is done by assigning each process a specific tag, and monitoring the data used by the process based on that tag.

The driver also exposes a control interface, with which a user can query the current sockets and their tags, along with the user-ID and process-ID from which the socket has been opened. This control interface is facilitated by a world-accessible file, under /proc/net/xt_qtaguid/ctrl.

However, reading this file reveals that it actually contains the kernel virtual address for each of the sockets which completely uncensored:

Looking at the source code for the virtual file's "read" implementation, reveals that the address is written without using the special "%pK" format specifier:

For those interested - the actual pointer written is to the "sock" structure, which is the kernel structure containing the actual "socket" structure, which in turns contains all the function pointers to the operations within this socket.

This means that if, for example, we have a vulnerability that allows us to overwrite a specific kernel address (like the vulnerability presented in the previous blog post), we could simply:

  • Open a socket and tag it with "qtaguid"
  • Look for the socket's address within /proc/net/xt_qtaguid/ctrl
  • Overwrite the pointer to the "socket" structure to an address within our address-space
  • Populate the overwritten address with a dummy "socket" structure containing fully controller function pointers
  • Perform any operation on the socket (like closing it), in order to cause the kernel to execute our own code

Summing it all up
Just like any other mitigation, kptr_restrict adds a layer of defence which can sometimes slow down an attacker, but is generally not a show-stopper for anyone determined enough. However, unlike most other mitigations, kptr_restrict requires the cooperation of kernel developers to be effective. Right now, things aren't so great. Hopefully this changes :)

转载于:https://www.cnblogs.com/lanrenxinxin/p/6226047.html

Effectively bypassing kptr_restrict on Android相关推荐

  1. Sci-Hub是什么?

    Sci-Hub是一个检索,免费下载论文的网站. 从其官网获悉: 其目的: At this time the widest possible distribution of research paper ...

  2. 这7行代码变成360亿帝国的两个原因

    重点 (Top highlight) On June 24 last year, Patrick Collison, the co-founder of Stripe, posted a Tweet ...

  3. 论文笔记:Bypassing DRM protection in e-book applications on Android

    一.基本信息 论文题目:<Bypassing DRM protection in e-book applications on Android> 发表时间:ICOIN 2018 作者及单位 ...

  4. Wayland utilizing Android GPU drivers on glibc based systems, Part 1

    一个大神的实验. http://mer-project.blogspot.se/2013/04/wayland-utilizing-android-gpu-drivers.html In this b ...

  5. android登陆加载加载_如何在Android上侧面加载应用

    android登陆加载加载 Just because an app isn't available from the Google Play Store doesn't mean it's not a ...

  6. Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析

    在Android系统中,Activity窗口的大小是由WindowManagerService服务来计算的.WindowManagerService服务会根据屏幕及其装饰区的大小来决定Activity ...

  7. android 九宫格绘制,Android draw9patch.bat 九宫格绘制工具使用

    9patch是随文字大小可以缩放的图片. 定义了拉伸区域,则除拉伸区域可变外,其他区域保持原来的形状大小不变. 9patch 九宫格 9分图 它的四个角不作任何伸展,四个边分别只作水平和垂直伸展,中间 ...

  8. Enabling HierarchyViewer on Rooted Android Devices

    转自http://blog.apkudo.com/2012/07/26/enabling-hierarchyviewer-on-rooted-android-devices/. The Hierarc ...

  9. Android刷新机制-View绘制原理

    Android刷新机制-View绘制原理 Android刷新机制-SurfaceFlinger原理 Android刷新机制-Choreographer原理 一.概述 本文将从startActivity ...

  10. 无法为新的Android ActionBar支持找到Theme.AppCompat.Light

    本文翻译自:Can't Find Theme.AppCompat.Light for New Android ActionBar Support I am trying to implement th ...

最新文章

  1. 总结:一些关于 CPU 的基本知识
  2. MySQL唯一约束(UNIQUE KEY)
  3. 用WebCollector爬取新浪微博数据
  4. React-Native学习笔记
  5. java scipt 对象 函数_java script 基本函数
  6. 通道设置_关于上下学时间和通道设置的通知
  7. 【CCCC】L2-017 人以群分 (25分),,模拟水题
  8. 统计局:2018年全国规模以上工业企业利润增长10.3%
  9. 谈谈网络蜘蛛 爬开心网001的一些体会
  10. 数据结构:八种数据结构大全
  11. 石子合并,四边形不等式优化(Buy Candy,玲珑杯 Round#6 A lonlife 1066)
  12. dsp6657的helloworld例程测试-第二篇-CFG文件
  13. 小武学fpgastep3
  14. 会计三张主要报表(资产负债表、利润表和现金流量表)
  15. Monte Carlo Counterfactual Regret Minimization
  16. 如何解决安装CCS 10 时 Unicode Character Check 报错
  17. 记录10--fedora 14下安装tftp软件
  18. React高阶组件(HOC)
  19. 毛球修剪器方案开发的工作原理和构成
  20. java 移动图片_Java写的一个简单的图片移动小程序

热门文章

  1. 最小生成树:Kruskal算法
  2. 大学生免费查题公众号_诺奖作家英文作品赏析尔雅2020年答案查题公众号
  3. Linux c中一些常用函数总结(c语言中文网。。。)
  4. java的finalize_Java中finalize()方法
  5. 【模版题】树状数组(单点修改+区间查询)
  6. android中横向滑动功能,Android开发基于ViewPager+GridView实现仿大众点评横向滑动功能...
  7. java调用一个外部url_java 从程序内部调用外部url/接口
  8. 易筋SpringBoot 2.1 | 第三十六篇:Spring Boot RestTemplate超时配置示例
  9. 2021-08-30缺失的第一个正数
  10. Adam自适应矩估计