Arm Linux Kernel Hacks

rousalome.egloos.com

포토로그 Kernel Crash


통계 위젯 (화이트)

793
557
422266


[ARM프로세서] 익셉션 클래스: 리눅스 커널 LKDTM으로 강제 커널 크래시 Arm: Exception Overview

이번 포스팅에서는 LKDTM 기능을 활용해 강제 크래시를 유발한 후 얻은 익셉션 클래스 레지스터의 값에 대해 정리한다.

오버뷰

"DABT (current EL)"

익셉션 클래스가 37인 경우 데이터 어보트(Data Abort taken without a change in Exception level)로 인식된다.

아래 노드에 접근할 때 커널 데이터 어보트가 발생한다.

   "ACCESS_NULL, ACCESS_USERSPACE, WRITE_KERN"

"IABT (current EL)"

익셉션 클래스가 33인 경우 인스트럭션 어보트(Instruction Abort taken without a change in Exception level)로 인식된다.

아래 노드에 접근할 때 커널 데이터 어보트가 발생한다.

   "EXEC_DATA, EXEC_KMALLOC, EXEC_NULL, EXEC_STACK, EXEC_USERSPACE"

이제부터 코드를 분석하자.

"DABT (current EL)"를 유발하는 코드 분석

데이터 어보트가 발생하는 코드는 각각 다음과 같다.

ACCESS_NULL

lkdtm_ACCESS_NULL() 함수의 구현부를 보자.

https://elixir.bootlin.com/linux/v4.9.200/source/drivers/misc/lkdtm_perms.c
void lkdtm_ACCESS_NULL(void)
{
unsigned long tmp;
unsigned long *ptr = (unsigned long *)NULL;

pr_info("attempting bad read at %px\n", ptr);
tmp = *ptr;
tmp += 0xc0dec0de;

pr_info("attempting bad write at %px\n", ptr);
*ptr = tmp;
}

코드의 내용은 0x0(NULL)에 0xc0dec0de를 저장하는 동작이다.

ACCESS_USERSPACE

이어서 lkdtm_ACCESS_USERSPACE() 함수를 보자.

https://elixir.bootlin.com/linux/v4.9.200/source/drivers/misc/lkdtm_perms.c
void lkdtm_ACCESS_USERSPACE(void)
{
unsigned long user_addr, tmp = 0;
unsigned long *ptr;

user_addr = vm_mmap(NULL, 0, PAGE_SIZE,
    PROT_READ | PROT_WRITE | PROT_EXEC,
    MAP_ANONYMOUS | MAP_PRIVATE, 0);
if (user_addr >= TASK_SIZE) {
pr_warn("Failed to allocate user memory\n");
return;
}

if (copy_to_user((void __user *)user_addr, &tmp, sizeof(tmp))) {
pr_warn("copy_to_user failed\n");
vm_munmap(user_addr, PAGE_SIZE);
return;
}

ptr = (unsigned long *)user_addr;

pr_info("attempting bad read at %p\n", ptr);
tmp = *ptr;
tmp += 0xc0dec0de;

pr_info("attempting bad write at %p\n", ptr);
*ptr = tmp;

vm_munmap(user_addr, PAGE_SIZE);
}

역시 유저 공간의 주소에 커널이 직접 엑세스하는 동작이다.

WRITE_KERN

lkdtm_WRITE_KERN() 함수를 보자.

https://elixir.bootlin.com/linux/v4.9.200/source/drivers/misc/lkdtm_perms.c
void lkdtm_WRITE_KERN(void)
{
size_t size;
unsigned char *ptr;

size = (unsigned long)do_overwritten - (unsigned long)do_nothing;
ptr = (unsigned char *)do_overwritten;

pr_info("attempting bad %zu byte write at %p\n", size, ptr);
memcpy(ptr, (unsigned char *)do_nothing, size);
flush_icache_range((unsigned long)ptr, (unsigned long)(ptr + size));

do_overwritten();
}

do_nothing() 함수의 코드를 do_overwritten() 함수에 메모리를 복사해 커널 코드 영역을 오염시킨다.

do_overwritten()/do_nothing() 함수의 구현부는 각각 다음과 같다.

/* Must immediately follow do_nothing for size calculuations to work out. */
static void do_overwritten(void)
{
pr_info("do_overwritten wasn't overwritten!\n");
return;
}

static void do_nothing(void)
{
return;
}

"IABT (current EL)"를 유발하는 코드 분석

EXEC_DATA

lkdtm_EXEC_DATA() 함수를 보자.

https://elixir.bootlin.com/linux/v4.9.200/source/drivers/misc/lkdtm_perms.c
void lkdtm_EXEC_DATA(void)
{
execute_location(data_area, CODE_WRITE);
}

data_area[] 배열에 있는 값을 실행시키는 동작이다.

EXEC_STACK

lkdtm_EXEC_STACK() 함수를 보자.

https://elixir.bootlin.com/linux/v4.9.200/source/drivers/misc/lkdtm_perms.c
void lkdtm_EXEC_STACK(void)
{
u8 stack_area[EXEC_SIZE];
execute_location(stack_area, CODE_WRITE);
}

스택에 있는 데이터를 실행하는 코드이다.

EXEC_KMALLOC

lkdtm_EXEC_KMALLOC() 함수를 분석하자.

https://elixir.bootlin.com/linux/v4.9.200/source/drivers/misc/lkdtm_perms.c
void lkdtm_EXEC_KMALLOC(void)
{
u32 *kmalloc_area = kmalloc(EXEC_SIZE, GFP_KERNEL);
execute_location(kmalloc_area, CODE_WRITE);
kfree(kmalloc_area);
}

동적 메모리를 할당받아 이를 프로그램 카운터로 지정한다.

EXEC_NULL

lkdtm_EXEC_NULL() 함수를 보겠다.

https://elixir.bootlin.com/linux/v4.9.200/source/drivers/misc/lkdtm_perms.c
void lkdtm_EXEC_NULL(void)
{
execute_location(NULL, CODE_AS_IS);
}

프로그램 카운터에 0x0을 지정하는 동작이다.

EXEC_USERSPACE

lkdtm_EXEC_USERSPACE() 함수를 보겠다.

https://elixir.bootlin.com/linux/v4.9.200/source/drivers/misc/lkdtm_perms.c
void lkdtm_EXEC_USERSPACE(void)
{
unsigned long user_addr;

user_addr = vm_mmap(NULL, 0, PAGE_SIZE,
    PROT_READ | PROT_WRITE | PROT_EXEC,
    MAP_ANONYMOUS | MAP_PRIVATE, 0);
if (user_addr >= TASK_SIZE) {
pr_warn("Failed to allocate user memory\n");
return;
}
execute_user_location((void *)user_addr);
vm_munmap(user_addr, PAGE_SIZE);
}

유저 공간에 위치한 주소를 바로 실행한다.

Written by <디버깅을 통해 배우는 리눅스 커널의 구조와 원리> 저자



덧글

댓글 입력 영역