0% found this document useful (0 votes)
49 views61 pages

The Power of Variant Analysis in Software Vulnerability Discovery

Uploaded by

dajunbenmo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
49 views61 pages

The Power of Variant Analysis in Software Vulnerability Discovery

Uploaded by

dajunbenmo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 61

The Power of Variant Analysis in

Software Vulnerability Discovery


Tielei Wang
Software vulnerability & exploit

• Vulnerability: aaw or weakness in a system's design, implementation, or


operation and management that could be exploited to violate the system's
security polic

• Exploit: a piece of software, a chunk of data, or a sequence of commands


that takes advantage of a vulnerability to cause unintended or
unanticipated behavior within the target systems
y

fl
A glance of vulnerability&exploit market

Zerodium
A glance of vulnerability&exploit market

Zerodium
A glance of vulnerability&exploit market

天府杯
A glance of vulnerability&exploit market

天府杯
A glance of vulnerability&exploit market

Apple Bug Bounty Program


So how to nd vulnerabilities?

• Static Analysi

• Dynamic Analysi

• Fuzzin

• Manuel Auditing Source Code or Reverse engineerin

•…
g

fi
g

Our focus today

• Static Analysi

• Dynamic Analysi

• Fuzzin

• Manuel Auditing Source Code or Reverse engineerin

• Variant analysis
g

Variant analysis

• Refers to the process of studying a known security bug and then looking
for code which is vulnerable in a similar wa

• A concept that was widely accepted by industry researcher

• Sounds easy?

Variant analysis

• Requires

• Deep understanding to the known vulnerabilitie

• Deep understanding to the target system

• Open and curious mind


:

Outline

• Introductio

• UNIX Socket Bind Race Vulnerability in XN

• How to Apply Variant Analysi

• Conclusion
n

Background

• XNU is the OS kernel developed by Apple and used in iOS and macOS
product

• A UNIX socket is an inter-process communication mechanism that allows


bidirectional data exchange between processes running on the same
machine

• We already discussed this vulnerability at Blackhat USA 2019.


s

Take a deep breat


A lot of C code is coming
h

int sock; int sock;


struct sockaddr_un name; struct sockaddr_un name;
char buf[1024]; char buf[1024];
/* Create socket from which to read. */ /* Create socket from which to write. */
sock = socket(AF_UNIX, SOCK_DGRAM, 0); sock = socket(AF_UNIX, SOCK_DGRAM, 0);

/* Create name. */ /* Create name. */


name.sun_family = AF_UNIX; name.sun_family = AF_UNIX;
strcpy(name.sun_path, "1.txt"); strcpy(name.sun_path, "1.txt");
name.sun_len = strlen(name.sun_path) name.sun_len = strlen(name.sun_path);

/* Bind socket to the path. */ /* Connect the socket to the path. */


bind(sock, (struct sockaddr *)&name, connect(sock, (struct sockaddr *)&name,
SUN_LEN(&name)); SUN_LEN(&name));

/* Read from the socket. */ /* Write to the socket. */


read(sock, buf, 1024); write(sock, buf, 1024);

close(sock); close(sock);

A simple server A simple client


int sock;
struct sockaddr_un name;
char buf[1024];
/* Create socket from which to read. */
sock = socket(AF_UNIX, SOCK_DGRAM, 0);

/* Create name. */
name.sun_family = AF_UNIX;
strcpy(name.sun_path, "1.txt");
name.sun_len = strlen(name.sun_path)

/* Bind socket to the path. */


bind(sock, (struct sockaddr *)&name,
SUN_LEN(&name));

/* Read from the socket. */


read(sock, buf, 1024);

close(sock);

A simple server From the kernel point of view


please refer to xnu source code for more details

int sock;
struct sockaddr_un name;
char buf[1024];
/* Create socket from which to read. */
sock = socket(AF_UNIX, SOCK_DGRAM, 0);
socket
/* Create name. */
name.sun_family = AF_UNIX; socket_common
strcpy(name.sun_path, "1.txt");
name.sun_len = strlen(name.sun_path) socreate_internal
/* Bind socket to the path. */ soalloc
bind(sock, (struct sockaddr *)&name,
SUN_LEN(&name)); unp_attach
/* Read from the socket. */
read(sock, buf, 1024);

close(sock);

A simple server From the kernel point of view


proc_t struct filedesc


fd_ofiles struct fileproc * 0
p_fd … struct fileproc * 1

int sock; … struct fileproc * 2



struct sockaddr_un name;
struct fileproc *
char buf[1024];
/* Create socket from which to read. */
struct fileglob struct fileproc
sock = socket(AF_UNIX, SOCK_DGRAM, 0);
… …
/* Create name. */ fg_data f_fglob
… …
name.sun_family = AF_UNIX;
strcpy(name.sun_path, "1.txt");
name.sun_len = strlen(name.sun_path)
struct socket struct unpcb


/* Bind socket to the path. */
so_proto unp_socket
bind(sock, (struct sockaddr *)&name,
so_pcb unp_vnode
SUN_LEN(&name)); …
so_usecount
/* Read from the socket. */
read(sock, buf, 1024); struct protosw


close(sock); pr_lock a number of
pr_unlock function pointers

A simple server From the kernel point of view


int sock;
struct sockaddr_un name; struct socket struct unpcb
char buf[1024];
/* Create socket from which to read. */ …
sock = socket(AF_UNIX, SOCK_DGRAM, 0); so_proto unp_socket

/* Create name. */ so_pcb unp_vnode


name.sun_family = AF_UNIX; …
strcpy(name.sun_path, "1.txt"); so_usecount
name.sun_len = strlen(name.sun_path)

/* Bind socket to the path. */


bind(sock, (struct sockaddr *)&name,
SUN_LEN(&name));

/* Read from the socket. */


read(sock, buf, 1024);

close(sock);

A simple server From the kernel point of view


int sock;
struct sockaddr_un name;
char buf[1024];
/* Create socket from which to read. */
sock = socket(AF_UNIX, SOCK_DGRAM, 0);
bind
/* Create name. */
name.sun_family = AF_UNIX;
strcpy(name.sun_path, "1.txt"); sobindlock
name.sun_len = strlen(name.sun_path)
socket_lock
/* Bind socket to the path. */
bind(sock, (struct sockaddr *)&name,
SUN_LEN(&name));
unp_bind
/* Read from the socket. */ socket_unlock
read(sock, buf, 1024);

close(sock);

A simple server From the kernel point of view


Note that unp_bind is surrounded by socket_(un)lock


int sock;
struct sockaddr_un name; so it is unraceable?
char buf[1024];
/* Create socket from which to read. */
sock = socket(AF_UNIX, SOCK_DGRAM, 0);
bind
/* Create name. */
name.sun_family = AF_UNIX;
strcpy(name.sun_path, "1.txt"); sobindlock
name.sun_len = strlen(name.sun_path)
socket_lock
/* Bind socket to the path. */
bind(sock, (struct sockaddr *)&name,
SUN_LEN(&name));
unp_bind
/* Read from the socket. */ socket_unlock
read(sock, buf, 1024);

close(sock);

A simple server From the kernel point of view


int sock; struct socket struct unpcb


struct sockaddr_un name;
char buf[1024]; …
/* Create socket from which to read. */ so_proto unp_socket
sock = socket(AF_UNIX, SOCK_DGRAM, 0); so_pcb unp_vnode

/* Create name. */ …
name.sun_family = AF_UNIX; so_usecount
strcpy(name.sun_path, "1.txt");
name.sun_len = strlen(name.sun_path)

/* Bind socket to the path. */ struct vnode


bind(sock, (struct sockaddr *)&name,
SUN_LEN(&name)); VSOCK
v_socket
/* Read from the socket. */ …
read(sock, buf, 1024);

close(sock);

A simple server From the kernel point of view


Race Condition

• The creation of a vnode is time


consumin

• unp_bind has a temporary unlock


g

This unlock makes bind raceable


bind
int sock; sobindlock
struct sockaddr_un name;
char buf[1024]; socket_lock
/* Create socket from which to read. */
sock = socket(AF_UNIX, SOCK_DGRAM, 0); unp_bind

/* Create name. */ socket_unlock


name.sun_family = AF_UNIX;
strcpy(name.sun_path, "1.txt");
name.sun_len = strlen(name.sun_path) vnode_create
/* Bind socket to the path. */
bind(sock, (struct sockaddr *)&name, socket_lock
SUN_LEN(&name));

/* Read from the socket. */


vp->v_socket = unp->unp_socket;
read(sock, buf, 1024); unp->unp_vnode = vp;
close(sock);
socket_unlock

A simple server From the kernel point of view


int sock; int sock;


struct sockaddr_un name; struct sockaddr_un name;
char buf[1024]; char buf[1024];
/* Create socket from which to read. */ /* Create socket from which to read. */
sock = socket(AF_UNIX, SOCK_DGRAM, 0); sock = socket(AF_UNIX, SOCK_DGRAM, 0);

/* Create name. */ /* Create name. */


name.sun_family = AF_UNIX; name.sun_family = AF_UNIX;
strcpy(name.sun_path, "1.txt"); strcpy(name.sun_path, “2.txt");
name.sun_len = strlen(name.sun_path) name.sun_len = strlen(name.sun_path)

/* Bind socket to the path. */ /* Bind socket to the path. */


bind(sock, (struct sockaddr *)&name, bind(sock, (struct sockaddr *)&name,
SUN_LEN(&name)); SUN_LEN(&name));

/* Read from the socket. */ /* Read from the socket. */


read(sock, buf, 1024); Thread 1 Thread 2 buf, 1024);
read(sock,

close(sock); close(sock);

bind the socket to two le paths in parallel


fi

struct socket struct unpcb


we can make a socket
so_proto unp_socket binding to two vnodes
so_pcb unp_vnode (two references)

so_usecount

struct vnode

VSOCK
v_socket

struct vnode
VSOCK
v_socket

bind the socket to two le paths in parallel


fi
int sock;
struct sockaddr_un name;
char buf[1024];
/* Create socket from which to read. */
sock = socket(AF_UNIX, SOCK_DGRAM, 0);

/* Create name. */
name.sun_family = AF_UNIX;
strcpy(name.sun_path, "1.txt");
close
name.sun_len = strlen(name.sun_path)
soo_close
/* Bind socket to the path. */
bind(sock, (struct sockaddr *)&name, soclose
SUN_LEN(&name));

/* Read from the socket. */


read(sock, buf, 1024);

close(sock);

A simple server From the kernel point of view


One of the vnodes will hold a dangling pointer

int sock; freed memory freed memory


struct sockaddr_un name; …
char buf[1024];
so_proto unp_socket
/* Create socket from which to read. */
sock = socket(AF_UNIX, SOCK_DGRAM, 0); so_pcb unp_vnode

/* Create name. */ so_usecount
name.sun_family = AF_UNIX;
strcpy(name.sun_path, "1.txt");
name.sun_len = strlen(name.sun_path)
struct vnode

/* Bind socket to the path. */ VSOCK


bind(sock, (struct sockaddr *)&name, 0
SUN_LEN(&name)); …

/* Read from the socket. */


read(sock, buf, 1024); struct vnode
VSOCK
close(sock);
v_socket

A simple server From the kernel point of view


int sock;
sock = socket(AF_UNIX, SOCK_DGRAM, 0);

/* Connect the socket to the path1. */


connect(sock, (struct sockaddr *)&name1, connect
SUN_LEN(&name))
/* Connect the socket to the path2. */
connect(sock, (struct sockaddr *)&name2,
SUN_LEN(&name)); unp_connect

Trigger UAF by connecting two names From the kernel point of view
;

freed memory freed memory


so_proto unp_socket
so_pcb unp_vnode

so_usecount

struct vnode

VSOCK
0

struct vnode
VSOCK
v_socket

The dangling pointer in one of the vnodes will pass into socket_lock()
sock = socket(AF_UNIX, SOCK_DGRAM, 0)
sock2 = socket(AF_UNIX, SOCK_DGRAM, 0)

in parallel
bind(sock, (struct sockaddr *) &server1, bind(sock, (struct sockaddr *) &server2,
sizeof(struct sockaddr_un))) sizeof(struct sockaddr_un)))

close(sock

connect(sock2, (struct sockaddr *) &server1, sizeof(struct sockaddr_un))


connect(sock2, (struct sockaddr *) &server2, sizeof(struct sockaddr_un))

The race condition bug results in a UAF


)

The x
• Fixed in iOS 12.

• Still raceable, but adding extra checks to make sure two vnodes will only
keep one reference to the socket
fi
2

Exploitation
Exploitation
fetch and
call a
function
pointer
through
two
deferences
to a freed
socket
Exploitation
fetch and
call a
function
pointer
through save a
two return
deferences address to
to a freed the freed
socket socket
Binary version may be better

By controlling X8, we can easily chain


ROP/JOP gadgets
JOP/ROP does NOT work on A12
due to the PAC mitigation

(*so->so_proto->pr_lock)(so, refcount, lr_saved);

Instructions on old devices Instructions on A12 devices


(*so->so_proto->pr_lock)(so, refcount, lr_saved);

Instructions on old devices Instructions on A12 devices

Hijack control ow by controlling X8 Cannot hijack control ow by controlling X8

• Please refer to our talk at Black Hat USA 2019 for more details regarding how to
exploit this vulnerability and bypass PAC
fl
fl
Outline

• Introductio

• UNIX Socket Bind Race Vulnerability in XN

• How to Apply Variant Analysi

• Conclusion
n

Dimensions of variant analysis


granularity of the vulnerability pattern

other lock issues

unsafe *_unlock

unsafe socket_unlock

search space
l i t y
rabi same subsystem other subsystems other operating systems
l ne
v u
w n
no
k
Case 1: check the same patten in the same subsystem

granularity of the vulnerability pattern

other lock issues

unsafe *_unlock

unsafe socket_unlock

search space
l i t y
rabi same subsystem other subsystems other operating systems
l ne
v u
w n
no
k
check temporary unlocks in unp_connect
int sock;
struct sockaddr_un name;
char buf[1024]; connect
/* Create socket from which to write. */
sock = socket(AF_UNIX, SOCK_DGRAM, 0);
connectit
/* Create name. */
name.sun_family = AF_UNIX; socket_lock
strcpy(name.sun_path, "1.txt");
name.sun_len = strlen(name.sun_path);
soconnectlock
/* Connect the socket to the path. */
connect(sock, (struct sockaddr *)&name,
SUN_LEN(&name));
unp_connect
/* Write to the socket. */ socket_unlock
write(sock, buf, 1024);

close(sock);

check temporary unlocks in unp_connect


unp_connect
• socket_lock and socket_unlock are called many
socket_unlock time
namei
• But the developers are very caution. Every time
socket_lock the socket is re-locked, unp_connect performs
checks on any change of the socket state.

socket_unlock

sonewconn

socket_lock


s

Normal execution
server socket
• A new socket object is created and
inserted into the server socket’s
so_comp queu so_proto
so_pcb
• so_incomp: q of partially
unaccepted conns
so_incomp
so_comp new socket
• so_comp: q of complete
unaccepted conns
e

The vulnerability
• The error handling code for race condition leads to a mistake

• sofreelastref is supposed to free the newly-created socket object so3, but


unfortunately it fails to deallocate the object due to incomplete ag setting

fl
Abnormal execution with race condition detected

• A new socket object is created and server socket


inserted into the server socket’s
so_incomp queu so_proto
so_pcb
• The locked socket records the
thread_t pointe
so_incomp locked socket
so_comp
• After the thread is terminated,
the thread_t pointer is invalid
r

Abnormal execution with race condition detected

• Closing the server socket will lead to server socket


cleaning the so_incomp queu

so_proto
• Cleaning the so_incomp queue will try
to relock the socket objec so_pcb

• The relock operation will trigger the so_incomp locked socket


thread_t UAF (use-after-free) issue so_comp

• Please refer to https://


blog.pangu.io/?p=230 for more
details. Apple xed this issue in iOS
13.7 after we reported it.
fi
t

Case 2: check the same patten in other subsystems

granularity of the vulnerability pattern

other lock issues

unsafe *_unlock

unsafe socket_unlock

search space
l i t y
rabi same subsystem other subsystems other operating systems
l ne
v u
w n
no
k
ow-divert socket UAF

• ow-divert is a subsystem in the XNU kernel for ow diversion and


network traf c management

• the temporary unlock of the socket in function ow_divert_pcb_insert


leads to a socket UAF vulnerability
fl
fl
fi
.

fl
fl
work ow
socket_lock

flow_divert_pcb_init

MALLOC_ZONE(new_pcb

new_pcb->so = so

socket_unlock

socket_lock
fl
Normal Execution
flow_divert_pcb


so

socket
Abnormal Execution under race condition
flow_divert_pcb two flow_divert_pcb pointing to
… the same socket, eventually
so
leading to socket UAF

socket

flow_divert_pcb


so Apple fixed the
issue in iOS 14
Case 3: check similar pattens in other subsystems
granularity of the vulnerability pattern

other lock issues

unsafe *_unlock

unsafe socket_unlock

search space
l i t y
rabi same subsystem other subsystems other operating systems
l ne
v u
w n
no
k
temporary unlocks in other subsystems
• More and more bugs caused by temporary unlocks were discovered, implying an
important bug pattern

• Race condition in VM subsyste

• CVE-2019-6205, Ian Bee

• https://googleprojectzero.blogspot.com/2019/04/splitting-atoms-in-xnu.html

• Race condition in IOSurface kernel extension

• CVE-2017-6979, Adam Donenfel

• https://blog.zimperium.com/ziva-video-audio-ios-kernel-exploit/

Case 4: check relative pattens in other OS


granularity of the vulnerability pattern

other lock issues

unsafe *_unlock

unsafe socket_unlock

search space
l i t y
rabi same subsystem other subsystems other operating systems
l ne
v u
w n
no
k
vsock race condition in the Linux kernel

• CVE-2021-26708

• by Alexander Popov

vsock race condition in the Linux kernel


• vsk->transport
pointer, is copied into
a local variable, which
is not protected by the
lock_soc

• vsk->transport may
be changed/freed by
another thread while
being used by current
thread
k

Don’t limit your imagination


granularity of the vulnerability pattern

other lock issues

unsafe *_unlock

unsafe socket_unlock

search space
l i t y
rabi same subsystem other subsystems other operating systems
l ne
v u
w n
no
k
Conclusion

• People usually make similar mistake

• Programmers usually make similar bug

• How to automate variant analysis?


s

Thank you!

You might also like