(kernel) get sys\_call\_table
아키텍쳐마다, OS마다 동작이 다르기 때문에 상황에 맞게 생각해야 한다. 다음 환경에서 테스트
1
2
root@kali32:~/add\_syscall# uname -a
Linux kali32 4.13.0-kali1-686-pae #1 SMP Debian 4.13.4-1kali1 (2017-10-03) i686 GNU/Linux
get sys_call_table
sys_call_table의 각 entry는 syscall function의 address이며 syscall()
은 이를 참조하기 때문에, 이를 변경해주면 victim syscall을 호출할 때 마다 변경된 syscall이 호출된다. 그러나 다음과 같은 제약이 있다.
- SYS_CALL_TABLE의 주소를 알아야 한다.
- SYS_CALL_TABLE entry에 접근하기 위해서는 kernel mode여야하기 때문에, kernel mode에서 sys_call_table을 변경할 방법이 필요하다.
1. kallsyms : kernel exported symbol
1
2
3
4
root@kali32:~# cat /proc/kallsyms | grep sys\_call\_table
root@kali32:~/add\_syscall# cat /proc/kallsyms | grep getpid
c2072f60 T sys\_getpid
// PAGE\_OFFSET (0xc0000000) + phys\_base (0x02000000) + offset
모든 공개 심볼을 조회할 수 있다.
kallsyms는 kASLR이 적용된 이후 reloc된 주소를 반환하기 때문에 kallsyms에 있는 주소가 해당 symbol이 실제로 위치한 virtual address다.
2.4.14 부터는 커널 컴파일 시 따로 옵션을 주거나, EXPORT\_SYMBOL()
이나 EXPORT\_SYMBOL_GPL()
로 직접 export해야 공개되면서 kallsyms에서 조회되기 때문에 조회되지 않을 수도 있다. * export는 extern
으로 사용할 수 있음을 의미한다.
2. System.map : kernel symbol table
1
2
3
4
5
root@kali32:~/add\_syscall# cat /boot/System.map-$(uname -r) | grep sys\_call\_table
c15d3160 R sys\_call\_table
root@kali32:~/add\_syscall# cat /boot/System.map-$(uname -r) | grep getpid
c1072f60 T sys\_getpid
// PAGE\_OFFSET (0xc0000000) + static (0x01000000) + offset
커널 컴파일 시 생성되는 심볼 테이블 파일로, /boot/
디렉토리에 있지만 부팅 과정에서 사용되는게 아니라 디버깅에 사용된다. kallsyms와 달리 System.map은 항상 고정값을 반환하기 때문에, 반환된 주소에 해당 symbol이 실제로 위치해 있을거라는 보장이 없다.
x86_64의 경우 같은 physical address를 가리키는 두 virtual address를 반환하는데, kallsyms는 Kernel text area를 반환하고 System.map은 direct mapping area를 반환한다. 따라서 kASLR이 없더라도 서로 다른 주소를 반환한다. https://www.kernel.org/doc/Documentation/x86/x86_64/mm.txt참고.
구버전 kernel은 System.map으로 얻은 주소를 바로 사용해도 되는 경우가 있는 듯 하지만, 대체로 안된다. LKM으로 조회해보면, kallsyms로 얻은 주소에는 binary가 제대로 찍혀 있는데, System.map으로 얻은 주소에는 이상한 값이 들어있다.
1
2
3
root@kali32:~/add\_syscall# dmesg | tail -2
[ 9015.850279] kallsyms sys\_getpid c2072f60 : 26748d3e
[ 9015.850280] System.map sys\_getpid c1072f60 : 9a67ff72
그러나 kallsyms와 동시에 공개되어 있는 symbol (e.g., getpid
)로 대조해 보고, 하위 몇 byte가 일치한다면 System.map의 sys_call_table 주소에서 offset을 얻어낼 수 있다.
1
2
3
4
5
6
7
8
9
10
root@kali32:~/add\_syscall# cat /boot/System.map-4.13.0-kali1-686-pae | grep sys\_call\_table
c15d3160 R sys\_call\_table
PAGE\_OFFSET(0xc0000000) + phys\_base (0x02000000) + offset(0x005d3160) = 0xc25d3160
root@kali32:~/add\_syscall# dmesg | tail -3
[ 9015.850279] kallsyms sys\_getpid c2072f60 : 26748d3e
[ 9015.850280] System.map sys\_getpid c1072f60 : 9a67ff72
[ 9015.850280] sct[20(\_\_NR\_getpid)] c25d31b0 : c2072f60 : 26748d3e
3. dynamic : using exported symbol
kallsyms, System.map 모두 사용할 수 없을 때 사용할만한 방법. 근처에 있는 공개 심볼과, sys_call_table 내의 entry로 존재하는 공개 심볼(주로 sys_close
)을 이용해 반복문으로 값을 비교해가며 찾는다.
http://hkpco.kr/paper/crtlk.txt
exploit 활용도가 높지는 않은 방법인데, 이 방법을 사용할 수 있다는건
addr_limit
가 해제되어 있거나,- 내가 작성한 코드를 kernel mode에서 실행시킬 수 있음을 의미한다. 후자의 경우, 그냥 그걸 이용해 exploit하면 되는거니까 굳이 sys_call_table을 찾을 필요가 없을 듯.
4. 64bit system call tracing
1
2
3
4
ENTRY(entry\_SYSCALL\_64)
...
call \*sys\_call\_table(, %rax, 8)
SYSCALL
의 경우 ENTRY(entry\_SYSCALL\_64)
에서 바로 call \*sys\_call_table(, %rax, 8)
하기 때문에 사용할 수 있다. MSR을 읽어오는 rdmsr
instruction을 user mode에서 호출 시 General Protection Fault가 발생하기 때문에 그냥 kernel mode에서 다음 매크로를 사용해 읽어온다.
1
#define rdmsrl(msr, val) ((val) = native\_read\_msr((msr)))
32bit system call tracing
1
2
3
4
5
6
int main(void){
struct gdt\_ptr idtr = {0,0};
asm volatile("sidt %0" : "=m"(idtr));
printf("%d %p\n", idtr.len, idtr.ptr);
return 0;
}
int $0x80
에서 사용하는 IDTR
은 user mode에서도 읽어올 수 있지만 ENTRY(entry\_INT80_32)
부터 system call handler까지 따라가는게 좀 많이 복잡하기도 하고, 커널 버전이 변경되면서 언제 바이너리 패턴이 변경될지 모르는거라서 사실상 쓰기가 조금 그렇다.
1
idt → call do\_int80\_syscall\_32 → call do\_syscall\_32\_irqs\_on → call ia32\_sys\_call\_table
SYSENTER
도 system call handler까지 따라가는 과정이 int $0x80
과 비슷해 사용하기가 좀 그렇다.