2021년에도 Black Hat Aisa 리뷰 보드로 활동을 하게 되어 보게 된 논문인데, 보안 관련 3대 학회로 손꼽히는 ACM CCS 2020에 채택된 논문입니다. 제목에서도 알 수 있듯이 리눅스 커널 공격에 사용되는 elastic object, 즉 우리말로 옮기면 크기가 가변적인 객체와 관련해서 체계화 연구를 수행한 내용입니다.

Elastic object의 핵심은 구조체 내부에 버퍼나 버퍼의 주소를 가리키는 포인터가 있고, 그리고 길이를 나타내는 필드가 있다는 점입니다. 이러한 특징으로 인해 elastic object의 버퍼 포인터나 길이를 조절하면 임의의 커널 메모리 읽기(arbitrary read)나 범위를 넘겨 읽기(out-of-bound read, OOB read)가 가능하고, KASLR(Kernel Address Space Layout Randomization)을 무력화할 수 있는 커널 함수 포인터나 Stack canary 등을 읽어낼 수 있죠.

Elastic object 공격으로 인한 영향 - 출처: A Systematic Study of Elastic Objects in Kernel Exploitation

사실 이러한 객체는 흔한 듯하면서도 흔치 않은데요, 커널 구조체의 대부분은 크기가 고정되어 있거든요. ^^;;; 이 논문에서는 아래처럼 elastic object를 찾아서 리스트를 만들어 놓고, 이와 관련해서 커널 데이터를 유출할 수 있는 함수들도 정리해두었습니다. 이 과정에서 정적/동적 분석 기법을 활용했다고 하는데, 오탐을 줄이기 위해 함수 리스트 같은 경우는 수동으로(manual) 선정했다고 하는군요.

Elastic kernel objects - 출처: A Systematic Study of Elastic Objects in Kernel Exploitation 
커널에서 유저로 데이터를 전달할 수 있는 함수 목록 - 출처:A Systematic Study of Elastic Objects in Kernel Exploitation

개인적으로 이 논문의 의의는 elastic object 관련 내용을 체계화했다는 것에 있다고 생각합니다. 임의 메모리 읽기나 범위 넘겨 읽기 같은 경우는 논문의 위협 모델(thread model)에도 나와 있지만, 공격자가 이미 임의 메모리 쓰기 취약점(arbitrary memory write)을 가지고 있다고 가정하기 때문에 영향력이 크지 않다고 생각합니다. 사실 임의 메모리 쓰기 취약점이 임의 메모리 읽기 취약점보다 귀하기 때문에 이런 취약점이 더 영향력이 크고 더 유용하거든요. 물론, 제대로 쓰기 위해서는 논문에서 기술한 것처럼, 임의 메모리 읽기 취약점을 이용해서 KASLR을 무력화해야 좀 더 공격 성공 확률이 올라가긴 하지만요. ^^;;;

해결책(mitigation)의 경우도 격리(isoloation) 기법을 사용했는데요, 이 방법 또한 일반적으로 활용하는 방법입니다. 격리를 하면 elastic object만 따로 떨어지는데, 인접해있는 kernel object를 통해 주요 정보를 유출할 수 있는 기회가 적어지긴 합니다. 다만, 메모리 풀(pool)을 분리해야 하므로 코드 수정이 필요하고, 이로 인한 부수효과(side effect)가 어떠한 형태로 나타나는지 심도 깊은 평가도 필요하지요. 논문에서도 성능 등의 여러 관점에서 평가를 하는데요, 실험실(lab) 환경과 실제 운영 환경은 다르기 때문에 논문의 결과가 실제 필드로 적용되기까지는 많은 시간이 필요합니다. ^^;; 참고로 최근에 도입된 꽤 유명하고 대표적인 격리 기법에는 KPTI(Kernel Page Table Isolation)가 있습니다. 바로, 세간을 떠들썩하게 한 멜트다운(meltdown) 취약점을 막기 위해 도입한 것이죠.

리뷰보드 활동 때문에 새해 첫날부터 머리가 아프... 여튼 흥미로운 논문이었습니다.

그럼 새해 복 많이 받으세요!!

최근에 팀을 옮기면서 리눅스 관련 일을 하게 되었습니다. ^^;;; 리눅스는 사실 MINT64 OS를 만들 때 리눅스 커널 구조를 본 게 전부라... 제가 잘 할 수 있을 까 걱정이 많이 되었는데요, 아직까지는 별 탈 없이 잘 지내고 있습니다. ^^;;;

최근에 옮긴 팀에서는 주로 리눅스 커널 관련 일을 하고 있는데요, 그러다보니 간단한 커널 모듈을 만들 일이 많아 코드를 남겨둡니다. 아래 코드는 Loadable Kernel Module 형태로 insmod를 통해 로딩되면 /dev/hello 디바이스를 생성하고 통신하는 역할을 합니다. ^^

1. 커널 모듈 소스 코드 작성 및 Makefile 작성

터미널에서 vi hello.c를 입력한 뒤 아래 코드를 붙여넣고 저장합니다.

/**
 *  본 파일은 프레임워크 예제 파일임
 */
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/syscalls.h>
#include <linux/fcntl.h>
#include <linux/cred.h>

int in_use = 0;

// 모듈 정보
MODULE_LICENSE("HIDDEN");
MODULE_AUTHOR("root");
MODULE_VERSION("1.0");
MODULE_DESCRIPTION("Hello World");

/**
 *  /dev 밑에 등록한 파일을 누가 열었을 때 호출되는 함수
 */
static int my_open(struct inode *inode, struct file *file)
{
    // 해당 파일을 동시에 여러개를 열지 못하도록 하는 코드
    if(in_use)
    {
        return -EBUSY;
    }

    in_use++;
    printk(KERN_INFO "Hello Open\n");

    return 0;
}

/**
 *  /dev 밑에 등록한 파일을 누가 닫았을 때 호출되는 함수
 */    
static int my_release(struct inode *inode, struct file *file)
{
    in_use--;
    printk(KERN_INFO "Hello Closed\n");
    return 0;
}

/**
 *  /dev 밑에 등록한 파일을 누가 닫았을 때 호출되는 함수
 */
static int my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int retval = 0;
    printk(KERN_INFO "Hello Ioctl~!!\n");
    return retval;
}

/**
 *  /dev 밑에 등록한 파일을 누가 읽었을 때 호출되는 함수
 */
static ssize_t my_read(struct file *filp, char *buffer, size_t length, loff_t *offset)
{
    printk (KERN_INFO "Hello Read~!!.\n");
    //printk (KERN_INFO "%X\n", sys_call_table);
    return 0;

    // 실제로 Read 명령을 처리하는 코드, 일단 예제로 남겨둠
    /*
    // Number of bytes actually written to the buffer
    int bytes_read = 0;

    // If we're at the end of the message, return 0 signifying end of file
    if (*msg_Ptr == 0) return 0;

    // Actually put the data into the buffer
    while (length && *msg_Ptr)  
    {
        put_user(*(msg_Ptr++), buffer++);

        length--;
        bytes_read++;
    }

    // Most read functions return the number of bytes put into the buffer
    return bytes_read;
    */
}

/**
 *  /dev 밑에 등록한 파일에 누가 썼었을 때 호출되는 함수
 */
static ssize_t my_write(struct file *filp, const char *buff, size_t len, loff_t *off)
{
    printk (KERN_INFO "Hello Write~!!.\n");
    return -EINVAL;
}

/**
 *  /dev/hello 파일 등록시 필요한 file operation 구조체
 */
static const struct file_operations my_fops = \
{
    .owner = THIS_MODULE,
    .open = &my_open,
    .read = &my_read,
    .write = &my_write,
    .release = &my_release,
    .unlocked_ioctl = (void*) &my_ioctl,
    .compat_ioctl = (void*) &my_ioctl
};

/**
 *  /dev/hello 파일 등록시 필요한 device 구조체
 */
static struct miscdevice my_device = \
{
    MISC_DYNAMIC_MINOR,
    "hello",
    &my_fops
};

/**
 *  커널 드라이버 시작 함수
 *      - 커널 드라이버가 로딩될 때 호출됨
 */
static int __init hello_init(void)
{
    printk(KERN_INFO "Hello, world\n");    

    int retval = misc_register(&my_device);
    return 0;
}

/**
 *  커널 드라이버 종료 함수
 *      - 커널 드라이버가 언로딩될 때 호출됨
 */
static void __exit hello_exit(void)
{   
    printk(KERN_INFO "Goodbye, world\n");
    misc_deregister(&my_device);
}

// 시작 함수와 종료 함수 지정 매크로
module_init(hello_init);
module_exit(hello_exit);    

코드를 다 붙여넣었으면 이제 터미널에서 vi Makefile을 입력한 뒤 아래 코드를 붙여넣고 저장합니다.

obj-m += hello.o

all:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
        make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Makefile을 생성한 후에 make를 입력하면 자동으로 빌드가 시작되고 다음과 같이 hello.ko가 생성됩니다.

[root@BuildMachine module]# ll
합계 244
-rw-r--r-- 1 root root    154  2월 24 09:39 Makefile
-rw-r--r-- 1 root root      0  2월 24 09:39 Module.symvers
-rw-r--r-- 1 root root    351  2월 24 09:42 hello.c
-rw-r--r-- 1 root root 112027  2월 24 09:41 hello.ko
-rw-r--r-- 1 root root    454  2월 24 09:39 hello.mod.c
-rw-r--r-- 1 root root  63104  2월 24 09:39 hello.mod.o
-rw-r--r-- 1 root root  50384  2월 24 09:41 hello.o
-rw-r--r-- 1 root root     29  2월 24 09:41 modules.order

3. 커널 모듈 시험 및 결과 확인

커널 모듈은 insmod를 이용하여 커널에 동적으로 올릴 수 있구요, rmmod를 이용하여 커널에서 내릴 수 있습니다. 그리고 로딩된 모듈의 목록은 lsmod를 이용하여 확인할 수 있습니다.

커널 모듈은 슈퍼유저(root) 권한으로만 로딩할 수 있으므로, 커널 모듈 시험 전에 root 계정으로 로그인하거나 sudo와 함께 insmod를 실행합니다. 다음은 커널 모듈을 로딩하고 삭제한 뒤 그 결과를 dmesg를 통해 확인한 것입니다. printk() 출력은 dmesg나 cat /proc/kmsg 를 통해 확인할 수 있습니다.

$>insmod hello.ko
$>ls /dev/hello -l
crw------- 1 root root 10, 57  2월 26 13:59 /dev/hello

$>chmod 666 /dev/hello           <== /dev/hello를 유저 권한으로 읽고 쓸 수 있게 만듬

4. 커널 모듈 테스트 프로그램 구현 및 빌드

커널 모듈이 정상적으로 로딩되었으니 이제 테스트 프로그램을 만들 차례입니다. ^^ 프레임워크 커널 모듈은 /dev/hello에 접근하면 메시지를 출력하게 되어있습니다. 커널 모듈 테스트 프로그램의 소스 코드는 다음과 같고 터미널에서 vi test.c를 입력한 뒤 저장합니다.

/**
 * 본 파일은 프레임워크 테스트 파일임
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>

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

    fd = open("/dev/hello", O_RDONLY);
    if (fd == -1)
    {   
        printf("Kernel Module Open Error\n");
    }   
    else
    {   
        printf("Kernel Module Open Success\n");
    }   
    close(fd);

    return 0;
}        

저장한 테스트 프로그램의 소스코드 코드는 다음과 같이 빌드할 수 있습니다.

$>gcc -o hello hello.c

5. 커널 모듈 기능 테스트

./hello를 실행하면 테스트 프로그램이 커널 모듈이 생성한 파일을 열어서 메시지를 표시합니다. 테스트 프로그램 실행 후 dmesg나 cat /dev/kmsg를 입력하여 커널 메시지를 확인하면 정상적으로 커널 모듈이 실행되었음을 알 수 있습니다. ^^

그럼 좋은 하루 되세요 ^^

리눅스 커널 부팅과정(Linux Kernel Booting Sequence) 분석

윈도우에 이어 리눅스도 부팅과정을 분석하고 있는데요, 소스코드가 있으니 윈도우보다는 그나마 조금 낫네요. ^^;;;; 여기저기서 자료를 좀 모았는데, 나름 정리해봅니다.

1.GRUB

윈도우에는 BOOTMGR이 있듯이 리눅스에는 GRUB이 있습니다. 이 GRUB의 역할은 Disk의 MBR 영역에 있다가 PC 부팅할 때 리눅스 커널(Linux Kernel)을 메모리에 올려주는 것입니다. GRUB은 Stage 1과 Stage 2로 구성되어 있는데요, Stage 1은 우리가 이야기하는 MBR 영역에 있는 1 Sector를 말하고 Stage 2는 Disk에 접근해서 커널을 로딩하는 파트를 말합니다.

  • Stage 1에서는 간단히 몇가지 BIOS 관련 Parameter를 설정하고 Stage 2로 넘어감
  • Stage 2에서는 리눅스 커널 파일(vmlinuz)을 분석해서 리눅스 커널의 부트로더(Bootloader)를 0x90000 ~ 에 로딩하고, 압축된 커널과 커널의 압축을 푸는 부분을 0x100000(1MB) ~ 에 로딩
  • Stage 2 후에는 커널 부트로더를 뛰어 넘어 0x90200 위치로 Jump(?)

사실 마지막 부분에 대한 내용은 제가 제대로 확인을 못해서 좀 애매하긴한데, 한빛미디어에서 나온 리눅스 커널의 이해를 보면 LILO 시절부터 0x90200 어드레스로 Jump를 하고 있습니다. 사실 GRUB 역시 크게 다르지 않을 것이라 생각합니다만... 좀 더 시간을 갖고 확인할 필요가 있을 것 같네요. ^^;;;;

GRUB에 대한 자세한 내용은 The GRUB MBR을 참고하세요 ;)

2. 리눅스 커널 부트로더

리눅스 커널 부트로더는 arch/x86/boot/header.S에 있는데요, 크기가 2 Sector입니다. ^^;;; 따라서 앞부분의 512Byte는 MBR 역할을 하고 그 뒤에 512Byte는 커널 부트로더와 커널을 이어주는 역할을 합니다.

그런데... 예전에는 바로 부팅할 수 있도록 했나 본데, 지금은 LILO나 GRUB의 등장으로 지원하지 않는 것 같습니다. 아래는 header.S에 있는 코드의 일부분인데요, 보시면 플로피를 통한 직접부팅을 지원 안하니까 다시 디스켓 빼라는 메시지와 함께 재부팅하는 코드만 달랑 있습니다. ^^;;;; 사실 이 부분의 코드가 GRUB에 의해 0x90000에 로딩되는 부분입니다.

 18 #include <asm/segment.h>
 19 #include <linux/utsrelease.h>
 20 #include <asm/boot.h>
 21 #include <asm/e820.h>
 22 #include <asm/page_types.h>
 23 #include <asm/setup.h>
 24 #include "boot.h"
 25 #include "offsets.h"
 26 
 27 BOOTSEG         = 0x07C0                /* original address of boot-sector */
 28 SYSSEG          = 0x1000                /* historical load address >> 4 */
 29 
 30 #ifndef SVGA_MODE
 31 #define SVGA_MODE ASK_VGA
 32 #endif
 33 
 34 #ifndef RAMDISK
 35 #define RAMDISK 0
 36 #endif
 37 
 38 #ifndef ROOT_RDONLY
 39 #define ROOT_RDONLY 1
 40 #endif
 41 
 42         .code16
 43         .section ".bstext", "ax"
 44 
 45         .global bootsect_start
 46 bootsect_start:
 47 
 48         # Normalize the start address
 49         ljmp    $BOOTSEG, $start2
 50 
 51 start2:
 52         movw    %cs, %ax
 53         movw    %ax, %ds
 54         movw    %ax, %es
 55         movw    %ax, %ss
 56         xorw    %sp, %sp
 57         sti
 58         cld
 59 
 60         movw    $bugger_off_msg, %si
 61 
 62 msg_loop:
 63         lodsb
 64         andb    %al, %al
 65         jz      bs_die             // <<= 무조건 Jump하면 메시지 출력후 bs_die로 이동~!!
 66         movb    $0xe, %ah
 67         movw    $7, %bx
 68         int     $0x10
 69         jmp     msg_loop
 70 
 71 bs_die:
 72         # Allow the user to press a key, then reboot
 73         xorw    %ax, %ax
 74         int     $0x16
 75         int     $0x19
 76 
 77         # int 0x19 should never return.  In case it does anyway,
 78         # invoke the BIOS reset code...
 79         ljmp    $0xf000,$0xfff0
 80 
 81         .section ".bsdata", "a"
 82 bugger_off_msg:
 83         .ascii  "Direct booting from floppy is no longer supported.\r\n"
 84         .ascii  "Please use a boot loader program instead.\r\n"
 85         .ascii  "\n"
 86         .ascii  "Remove disk and press any key to reboot . . .\r\n"
 87         .byte   0
 88 
 89 
 90         # Kernel attributes; used by setup.  This is part 1 of the
 91         # header, from the old boot sector.
 92 
 93         .section ".header", "a"
 94         .globl  hdr

이 바로 뒤는 당연히 0x90200에 위치하는데요, _start로 시작하며 커널의 bss 영역을 초기화하고 main 함수로 Jump하는 부분이 있습니다. 해당 부분의 코드는 arch/x86/boot/header.S를 참고하세요 ;)

104         # offset 512, entry point
105 
106         .globl  _start
107 _start:
108                 # Explicitly enter this as bytes, or the assembler
109                 # tries to generate a 3-byte jump here, which causes
110                 # everything else to push off to the wrong offset.
111                 .byte   0xeb            # short (2-byte) jump
112                 .byte   start_of_setup-1f
113 1:
114 
115         # Part 2 of the header, from the old setup.S

                             ... 생략 ...

224         .section ".inittext", "ax"
225 start_of_setup:
226 #ifdef SAFE_RESET_DISK_CONTROLLER
227 # Reset the disk controller.
228         movw    $0x0000, %ax            # Reset disk controller
229         movb    $0x80, %dl              # All disks
230         int     $0x13
231 #endif
232 
                             ... 생략 ...
271 
272 # Check signature at end of setup
273         cmpl    $0x5a5aaa55, setup_sig
274         jne     setup_bad
275 
276 # Zero the bss
277         movw    $__bss_start, %di
278         movw    $_end+3, %cx
279         xorl    %eax, %eax
280         subw    %di, %cx
281         shrw    $2, %cx
282         rep; stosl
283 
284 # Jump to C code (should not return)
285         calll   main       // <== 여기가 다음 스텝인 리눅스 커널 로더로 jump하는 부분

3. 리눅스 커널 로더

리눅스 커널 로더는 arch/x86/boot/main.c에 있습니다. 제가 이 코드에 리눅스 "리눅스 커널로더"라는 명칭을 사용해도 될지 모르겠지만, 일단 이 시점에서 32bit 모드로 변환하는 코드가 들어 있어서 커널로더라고 했습니다. ^^;;; 이 코드의 역할은 32bit 커널이 실행될 준비를 하는 건데요, BIOS를 통해 메모리 크기를 확인하고 키보드를 초기화한다음 32bit 커널로 Jump합니다. 이때 Jump하는 어드레스는 32bit 커널이 로딩되어 있는 0x100000(1MB)입니다. ^^;;; 해당 위치에 GRUB이 이미 올려놓았거든요 ;)

아래는 arch/x86/boot/main.c 파일의 일부분입니다. 좀더 자세한 코드는 arch/x86/boot/main.c를 통해서 확인할 수 있습니다. ^^;;;

125 void main(void)
126 {
127         /* First, copy the boot header into the "zeropage" */
128         copy_boot_params();
129 
130         /* End of heap check */
131         init_heap();
132 
133         /* Make sure we have all the proper CPU support */
134         if (validate_cpu()) {
135                 puts("Unable to boot - please use a kernel appropriate "
136                      "for your CPU.\n");
137                 die();
138         }
139 
140         /* Tell the BIOS what CPU mode we intend to run in. */
141         set_bios_mode();
142 
143         /* Detect memory layout */
144         detect_memory();
145 
146         /* Set keyboard repeat rate (why?) */
147         keyboard_set_repeat();
148 
149         /* Query MCA information */
150         query_mca();
151 
152         /* Query Intel SpeedStep (IST) information */
153         query_ist();
154 
155         /* Query APM information */
156 #if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
157         query_apm_bios();
158 #endif
159 
160         /* Query EDD information */
161 #if defined(CONFIG_EDD) || defined(CONFIG_EDD_MODULE)
162         query_edd();
163 #endif
164 
165         /* Set the video mode */
166         set_video();
167 
168         /* Parse command line for 'quiet' and pass it to decompressor. */
169         if (cmdline_find_option_bool("quiet"))
170                 boot_params.hdr.loadflags |= QUIET_FLAG;
171 
172         /* Do the last things and invoke protected mode */
173         go_to_protected_mode();    // <== 여기가 32bit 커널로 Jump하는 부분이에요 :)
174 }

4. 리눅스 커널 - 압축된 커널

커널 로더에 의해서 32bit 커널로 jmp(0x100000)하면 이제 본격적으로 커널이 시작되는데요, 이때 수행되는 함수는 arch/x86/boot/compressed/head_32.S에 있는 가장 첫 번째 함수, startup_32입니다. 파일의 가장 앞부분에 있는 함수기 때문에 0x100000 어드레스로 Jump하는 것만으로 바로 저 함수에 도달하는 것이지요. ^^;;;

startup_32() 함수는 여러가지 일을 하는데요, 가장 중요한 역할은 자신에게 붙어있는 압축된 커널을 안전한 버퍼로 옮긴다음 압축을 푸는 겁니다. >ㅁ<)-b 그리고 압축을 풀고나면 이를 다시 0x400000 어드레스로 옮기고 jump하는 것이지요. 안전한 버퍼라는 말이 조금 애매하긴한데 대략 0x400000 어드레스 부근인 것 같습니다(정확하게는 _end 오프셋 뒷쪽인데요, 이 값은 빌드 타임에 정해지는 값이라 compressed 디렉터리의 코드량에 따라서 좀 달라질 수도 있을 것 같습니다. ^^;;; (정확하지는 않아요 ㅠㅠ)

다음은 압축된 커널을 안전한 곳으로 복사하고 압축을 푼 뒤에 압축이 해제된 곳으로 jump하는 코드입니다. 전체 내용은 arch/x86/boot/compressed/head_32.S를 참조하세요.


 26 #include <linux/linkage.h>
 27 #include <asm/segment.h>
 28 #include <asm/page_types.h>
 29 #include <asm/boot.h>
 30 #include <asm/asm-offsets.h>
 31 
 32 .section ".text.head","ax",@progbits
 33 ENTRY(startup_32)
 34         cld
 35         /* test KEEP_SEGMENTS flag to see if the bootloader is asking
 36          * us to not reload segments */
 37         testb $(1<<6), BP_loadflags(%esi)
 38         jnz 1f
 39 
 40         cli
 41         movl $(__BOOT_DS),%eax
 42         movl %eax,%ds
 43         movl %eax,%es
 44         movl %eax,%fs
 45         movl %eax,%gs
 46         movl %eax,%ss
 47 1:
                     ... 생략 ...

 87 /* Copy the compressed kernel to the end of our buffer
 88  * where decompression in place becomes safe.
 89  */
 90         pushl %esi
 91         leal _end(%ebp), %esi
 92         leal _end(%ebx), %edi
 93         movl $(_end - startup_32), %ecx
 94         std
 95         rep
 96         movsb
 97         cld
 98         popl %esi
 99 
100 /* Compute the kernel start address.
101  */
102 #ifdef CONFIG_RELOCATABLE
103         addl    $(CONFIG_PHYSICAL_ALIGN - 1), %ebp
104         andl    $(~(CONFIG_PHYSICAL_ALIGN - 1)), %ebp
105 #else
106         movl    $LOAD_PHYSICAL_ADDR, %ebp
107 #endif
108 
109 /*
110  * Jump to the relocated address.
111  */
112         leal relocated(%ebx), %eax
113         jmp *%eax
114 ENDPROC(startup_32)
115 
116 .section ".text"
117 relocated:
118 
119 /*
120  * Clear BSS
121  */
122         xorl %eax,%eax
123         leal _edata(%ebx),%edi
124         leal _end(%ebx), %ecx
125         subl %edi,%ecx
126         cld
127         rep
128         stosb
129 
130 /*
131  * Setup the stack for the decompressor
132  */
133         leal boot_stack_end(%ebx), %esp
134 
135 /*
136  * Do the decompression, and jump to the new kernel..
137  */
138         movl output_len(%ebx), %eax
139         pushl %eax
140                         # push arguments for decompress_kernel:
141         pushl %ebp      # output address
142         movl input_len(%ebx), %eax
143         pushl %eax      # input_len
144         leal input_data(%ebx), %eax
145         pushl %eax      # input_data
146         leal boot_heap(%ebx), %eax
147         pushl %eax      # heap area
148         pushl %esi      # real mode pointer
149         call decompress_kernel       <<= 요기가 실제로 압축 해제를 담당하는 부분
150         addl $20, %esp
151         popl %ecx
152 
153 #if CONFIG_RELOCATABLE
154 /* Find the address of the relocations.
155  */
156         movl %ebp, %edi
157         addl %ecx, %edi
158 
159 /* Calculate the delta between where vmlinux was compiled to run
160  * and where it was actually loaded.
161  */
162         movl %ebp, %ebx
163         subl $LOAD_PHYSICAL_ADDR, %ebx
164         jz   2f         /* Nothing to be done if loaded at compiled addr. */
165 /*
166  * Process relocations.
167  */
168 
169 1:      subl $4, %edi
170         movl 0(%edi), %ecx
171         testl %ecx, %ecx
172         jz 2f
173         addl %ebx, -__PAGE_OFFSET(%ebx, %ecx)
174         jmp 1b
175 2:
176 #endif
177 
178 /*
179  * Jump to the decompressed kernel.
180  */
181         xorl %ebx,%ebx
182         jmp *%ebp                   // <<= 압축을 푼 어드레스로 jump하는 부분

실제로 압축 해제는 decompress_kernel() 함수를 호출해서 처리하는데, 이 함수는 arch/x86/boot/compressed/misc.c 파일에 있습니다. decompress_kernel() 함수의 원형은 아래와 같습니다.

304 asmlinkage void decompress_kernel(void *rmode, memptr heap,
305                                   unsigned char *input_data,
306                                   unsigned long input_len,
307                                   unsigned char *output)

위의 함수 원형을 보면 결국 우리가 넘겨주는 output buffer에 차곡차곡 압축을 풀어줌을 알 수 있는데요, 저 output 버퍼의 위치는 decompress_kernel()을 호출하는 곳에서 LOAD_PHYSICAL_ADDR로 대략 정의하고 있는데요, 정확하게 말하기는 힘들지만 대략 0x400000(4MB) 어드레스 부근이라고 생각됩니다.

리눅스 커널의 이해를 보면 압축을 해제한 뒤에 다시 1MB 영역의 어드레스로 덮어쓴다는 이야기가 있던데, 실제 코드에서는 바로 jump하는 부분만 확인했을 뿐 데이터를 이동하는 부분은 찾지 못했습니다. ㅠㅠ 그렇다는 이야기는 이제는 더이상 안옮기거나 제가 못찾았다는 이야기겠지요. 혹시 찾으신 분 있으시면 덧글로 제보 부탁드릴께요 ㅠㅠ

5. 리눅스 커널 - 실제 커널

이제 압축까지 다 해제된 실제 커널로 왔습니다. 커널의 실제 엔트리 포인트는 startup_32() 함수이구요, arch/x86/kernel/head_32.S에 있습니다. 이 함수를 보면 페이지 테이블을 임시로 설정하고 start_kernel() 함수를 호출하는 것을 볼 수 있습니다. start_kernel()은 32bit 커널에서 C 코드가 시작되는 부분이지요. 여기까지만 오면 사실 부팅은 거의 끝난 겁니다. ^^;;;;

다음은 startup_32() 함수의 코드입니다. 전체 코드는 arch/x86/kernel/head_32.S 파일을 참조하세요.

269 #ifdef CONFIG_SMP
270 ENTRY(startup_32_smp)
271         cld
272         movl $(__BOOT_DS),%eax
273         movl %eax,%ds
274         movl %eax,%es
275         movl %eax,%fs
276         movl %eax,%gs
277 #endif /* CONFIG_SMP */

             ... 생략 ...

451         movl $(__KERNEL_STACK_CANARY),%eax
452         movl %eax,%gs
453 
454         xorl %eax,%eax                  # Clear LDT
455         lldt %ax
456 
457         cld                     # gcc2 wants the direction flag cleared at all times
458         pushl $0                # fake return address for unwinder
459 #ifdef CONFIG_SMP
460         movb ready, %cl
461         movb $1, ready
462         cmpb $0,%cl             # the first CPU calls start_kernel
463         je   1f
464         movl (stack_start), %esp
465 1:
466 #endif /* CONFIG_SMP */
467         jmp *(initial_code)     // <<= start_kernel() 함수의 어드레스가 들어 있음
468 

6. 리눅스 커널 - C 함수 시작

start_kernel() 함수는 init/main.c 파일에 있으며, 각종 커널 관련 자료구조(페이지 테이블 포함)를 다시 한번 초기화하고 init 프로세스를 생성하는 역할을 합니다. start_kernel() 함수의 가장 마지막은 rest_init() 함수인데요, 이 함수는 kernel_thread() 함수를 호출해서 별도의 스레드를 만든 다음 kernel_init 함수를 수행하게 만듭니다. 그리고 자기 자신은 계속 코드를 수행하면서 cpu_idle() 함수를 수행하여 idle process가 됩니다.

다음은 start_kernel() 함수의 일부분과 rest_init() 함수의 일부분을 나타낸 것입니다.

535 
536 asmlinkage void __init start_kernel(void)
537 {
538         char * command_line;
539         extern struct kernel_param __start___param[], __stop___param[];
540 
541         smp_setup_processor_id();
542 
543         /*
544          * Need to run as early as possible, to initialize the
545          * lockdep hash:
546          */
547         lockdep_init();
548         debug_objects_early_init();
549 
550         /*
551          * Set up the the initial canary ASAP:
552          */
553         boot_init_stack_canary();
554 
555         cgroup_init_early();
556 
557         local_irq_disable();
558         early_boot_irqs_off();
559         early_init_irq_lock_class();
560 
561 /*
562  * Interrupts are still disabled. Do necessary setups, then
563  * enable them
564  */
565         lock_kernel();
566         tick_init();
567         boot_cpu_init();
568         page_address_init();
569         printk(KERN_NOTICE "%s", linux_banner);
570         setup_arch(&command_line);
571         mm_init_owner(&init_mm, &init_task);
572         setup_command_line(command_line);
573         setup_per_cpu_areas();
574         setup_nr_cpu_ids();
575         smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */
576 
577         /*
578          * Set up the scheduler prior starting any interrupts (such as the
579          * timer interrupt). Full topology setup happens at smp_init()
580          * time - but meanwhile we still have a functioning scheduler.
581          */
582         sched_init();
583         /*
584          * Disable preemption - early bootup scheduling is extremely
585          * fragile until we cpu_idle() for the first time.
586          */
587         preempt_disable();
588         build_all_zonelists();
589         page_alloc_init();
590         printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
591         parse_early_param();
592         parse_args("Booting kernel", static_command_line, __start___param,
593                    __stop___param - __start___param,
594                    &unknown_bootoption);
595         if (!irqs_disabled()) {
596                 printk(KERN_WARNING "start_kernel(): bug: interrupts were "
597                                 "enabled *very* early, fixing it\n");
598                 local_irq_disable();
599         }
600         sort_main_extable();
601         trap_init();
602         rcu_init();
603         /* init some links before init_ISA_irqs() */
604         early_irq_init();
605         init_IRQ();
606         pidhash_init();
607         init_timers();
608         hrtimers_init();
609         softirq_init();
610         timekeeping_init();
611         time_init();
612         sched_clock_init();
613         profile_init();
614         if (!irqs_disabled())
615                 printk(KERN_CRIT "start_kernel(): bug: interrupts were "
616                                  "enabled early\n");
617         early_boot_irqs_on();
618         local_irq_enable();
619 
620         /*
621          * HACK ALERT! This is early. We're enabling the console before
622          * we've done PCI setups etc, and console_init() must be aware of
623          * this. But we do want output early, in case something goes wrong.
624          */
625         console_init();
626         if (panic_later)
627                 panic(panic_later, panic_param);
628 
629         lockdep_info();
630 
631         /*
632          * Need to run this when irqs are enabled, because it wants
633          * to self-test [hard/soft]-irqs on/off lock inversion bugs
634          * too:
635          */
636         locking_selftest();
637 
638 #ifdef CONFIG_BLK_DEV_INITRD
639         if (initrd_start && !initrd_below_start_ok &&
640             page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
641                 printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
642                     "disabling it.\n",
643                     page_to_pfn(virt_to_page((void *)initrd_start)),
644                     min_low_pfn);
645                 initrd_start = 0;
646         }
647 #endif
648         vmalloc_init();
649         vfs_caches_init_early();
650         cpuset_init_early();
651         page_cgroup_init();
652         mem_init();
653         enable_debug_pagealloc();
654         cpu_hotplug_init();
655         kmem_cache_init();
656         kmemtrace_init();
657         debug_objects_mem_init();
658         idr_init_cache();
659         setup_per_cpu_pageset();
660         numa_policy_init();
661         if (late_time_init)
662                 late_time_init();
663         calibrate_delay();
664         pidmap_init();
665         pgtable_cache_init();
666         prio_tree_init();
667         anon_vma_init();
668 #ifdef CONFIG_X86
669         if (efi_enabled)
670                 efi_enter_virtual_mode();
671 #endif
672         thread_info_cache_init();
673         cred_init();
674         fork_init(num_physpages);
675         proc_caches_init();
676         buffer_init();
677         key_init();
678         security_init();
679         vfs_caches_init(num_physpages);
680         radix_tree_init();
681         signals_init();
682         /* rootfs populating might need page-writeback */
683         page_writeback_init();
684 #ifdef CONFIG_PROC_FS
685         proc_root_init();
686 #endif
687         cgroup_init();
688         cpuset_init();
689         taskstats_init_early();
690         delayacct_init();
691 
692         check_bugs();
693 
694         acpi_early_init(); /* before LAPIC and SMP init */
695 
696         ftrace_init();
697 
698         /* Do the rest non-__init'ed, we're now alive */
699         rest_init();               // <<= 요기가 실제 init 프로세스 생성으로 가는 부분
700 }

아래는 rest_init() 함수의 내용입니다. 

451 static noinline void __init_refok rest_init(void)
452         __releases(kernel_lock)
453 {
454         int pid;
455 
            // init 프로세스를 생성하는 kernel_init 스레드를 만드는 부분
456         kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
457         numa_default_policy();
458         pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
459         kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
460         unlock_kernel();
461 
462         /*
463          * The boot idle thread must execute schedule()
464          * at least once to get things moving:
465          */
466         init_idle_bootup_task(current);
467         rcu_scheduler_starting();
468         preempt_enable_no_resched();
469         schedule();
470         preempt_disable();
471 
472         /* Call into cpu_idle with preempt disabled */
473         cpu_idle();              // <<= Idle Process가 되는 부분
474 }

kernel_thread()는 아래 코드에서 볼 수 있듯이 마지막에 init_post() 함수를 호출하는데 이 함수가 최종적으로 init process를 생성합니다. 다음은 kernel_thread() 함수와 init_post() 함수의 코드입니다. 리눅스가 실행될 때 일반적으로 initrd와 같은 RAMDISK 이미지를 사용하기 때문에, init_post() 함수 안의 ramdisk_execute_command 변수에는 "/init" 이 들어가 있으며, 따라서 initrd의 루트 디렉터리의 init 프로세스를 실행합니다.

initrd에 있는 init은 사실 스크립트로 기본적인 드라이버를 로딩하고 루트 디스크를 마운트한 뒤, 실제 init process로 제어를 넘깁니다. 그러면 본격적으로 커널이 실행되는 것이죠. ^^;;;;

847 static int __init kernel_init(void * unused)
848 {
849         lock_kernel();
850         /*
851          * init can run on any cpu.
852          */
853         set_cpus_allowed_ptr(current, cpu_all_mask);
854         /*
855          * Tell the world that we're going to be the grim
856          * reaper of innocent orphaned children.
857          *
858          * We don't want people to have to make incorrect
859          * assumptions about where in the task array this
860          * can be found.
861          */
862         init_pid_ns.child_reaper = current;
863 
864         cad_pid = task_pid(current);
865 
866         smp_prepare_cpus(setup_max_cpus);
867 
868         do_pre_smp_initcalls();
869         start_boot_trace();
870 
871         smp_init();
872         sched_init_smp();
873 
874         do_basic_setup();
875 
876         /*
877          * check if there is an early userspace init.  If yes, let it do all
878          * the work
879          */
880 
881         if (!ramdisk_execute_command)
882                 ramdisk_execute_command = "/init";
883 
884         if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
885                 ramdisk_execute_command = NULL;
886                 prepare_namespace();
887         }
888 
889         /*
890          * Ok, we have completed the initial bootup, and
891          * we're essentially up and running. Get rid of the
892          * initmem segments and start the user-mode stuff..
893          */
894 
895         init_post();       // <<= init process 생성으로 가는 부분
896         return 0;
897 }

803 static noinline int init_post(void)
804         __releases(kernel_lock)
805 {
806         /* need to finish all async __init code before freeing the memory */
807         async_synchronize_full();
808         free_initmem();
809         unlock_kernel();
810         mark_rodata_ro();
811         system_state = SYSTEM_RUNNING;
812         numa_default_policy();
813 
814         if (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
815                 printk(KERN_WARNING "Warning: unable to open an initial console.\n");
816 
817         (void) sys_dup(0);
818         (void) sys_dup(0);
819 
820         current->signal->flags |= SIGNAL_UNKILLABLE;
821 
822         if (ramdisk_execute_command) {   // <<= initrd와 같이 RAMDISK 이미지를 사용하는 경우 이 코드로 진입하고, 이때 ramdisk_execute_command는 /init이 들어 있음
823                 run_init_process(ramdisk_execute_command);
824                 printk(KERN_WARNING "Failed to execute %s\n",
825                                 ramdisk_execute_command);
826         }
827 
828         /*
829          * We try each of these until one succeeds.
830          *
831          * The Bourne shell can be used instead of init if we are
832          * trying to recover a really broken machine.
833          */
834         if (execute_command) {
835                 run_init_process(execute_command);
836                 printk(KERN_WARNING "Failed to execute %s.  Attempting "
837                                         "defaults...\n", execute_command);
838         }
839         run_init_process("/sbin/init");
840         run_init_process("/etc/init");
841         run_init_process("/bin/init");
842         run_init_process("/bin/sh");
843 
844         panic("No init found.  Try passing init= option to kernel.");
845 }

start_kernel() 함수 이하에 있는 함수들의 전체 코드는 init/main.c를 참고하기 바랍니다. ^^

약간 보기 불편한 점은 있지만 그래도 한번 정리해놓으니 좋군요.
그럼 다들 좋은 밤 되세요 ;)

참고 자료

 이번주는 결혼식이다 뭐다해서 그렇게 많은 시간을 투자하지 못했습니다만, 막판 스퍼트해서  파일시스템을 추가하는데 성공했습니다. >ㅁ<)-b 사실 대부분의 코드는 윈도우 환경에서 시뮬레이션하면서 검증했기 때문에, 그냥 컨트롤 C+V 신공으로 끝냈습니다. ㅎㅎ

 그리고 내친김에 GUI 쉘에 dir 기능도 추가했습니다. GUI 쉘을 만든지는 좀 됬지만 귀찮아서 그냥 뒀는데, 점점 불편함이 커지더군요. 쉘 기능이 필요할때는 콘솔 모드 커널로 빌드해서 쉘을 쓰다가 다시 GUI 테스트할때는 윈도우 모드로 빌드해서 부팅하고... 이걸 수백번 정도 반복하다보니 그냥 만드는게 편하겠다는 생각이 들었습니다. 그래서 뚝딱~!!! 만들었습니다(사실 이 일이 더 많이 걸렸다는... ㅠㅠ).

 아래는 GUI 쉘로 루트 폴더와 이미지 폴더를 본 화면입니다. 깔끔하게 잘 나오는 걸 볼 수 있습니다. ㅎㅎ (이미지 윈도우는 /image/image0.img 파일을 읽어서 화면에 표시하도록 수정되었습니다)

 이거 뭐 점점 기능이 추가될수록 갈아 엎는 코드의 양도 많아지는군요. 이번에도 몇군데나 갈아 엎었습니다. ㅠㅠ 처음에는 간단한 OS를 만들자는 생각이었는데, 욕심이 자꾸 생겨서 이것도 추가하고 저것도 추가하게 되네요. 이렇다 언제 거사(?)를 시작하게 될지... ㅡ_ㅡa...

 진짜 내일 동적 메모리 할당쪽 알고리즘만 좀 더 수정하고, 거사를 시작해야겠습니다. 고칠게 있으면 거사를 진행하면서 고치던가 해야겠군요. ㅎㅎ

 내일 교육은... 또 물건너 간 것 같습니다. 푹 자다 올듯... ㅠㅠ
 다들 좋은 밤 되시길 ;)

 오늘도 집에 오자마자 컴퓨터를 키고 열심히 파일 시스템에 캐시를 추가하는 작업을 했습니다. 일단 어제 고민했던 문제 중에 FAT 영역 즉 클러스터 맵이 있는 영역에서 이상하게 캐시 히트가 낮게 나오는 문제를 뒤져봤는데... 역시나 문제가 있더군요. ㅎㅎ

 클러스터를 할당하는 코드가 클러스터 맵의 첫번째부터 마지막까지 검색하며 빈 클러스터를 찾게 되어있는데, 이런 경우 하드에 용량이 어느정도 차게되면 앞부분은 이미 대부분의 클러스터가 할당되어있음에도 불구하고 우직하게(?) 검색합니다. 따라서 클러스터 맵 영역의 캐시 또한 순차적으로 플러시되면서 거의 재사용되지 못하고 버려지게 되더군요.

 위와 같은 문제가 있어서 마지막으로 할당한 클러스터가 있는 클러스터 맵 인덱스를 저장하고, 클러스터 할당 요청이 올 때마다 가장 마지막으로 클러스터를 할당해준 클러스터 맵부터 검색을 시작하도록 수정했습니다. 이렇게 하니 거의 대부분 마지막으로 할당한 클러스터 맵에서 할당되서 거의 90%에 해당하는 캐시 히트가 나오더군요. 클러스터 맵을 쓰는 경우는 클러스터 할당/해제의 특성상 클러스터를 할당하려면 해당 맵을 읽어서 검색한 후, 할당했다고 마크를 해야하기 때문에 쓰는 경우는 100% 가 나왔습니다. 실로 엄청난 결과~!!! 이걸 실제 HDD에 적용할 경우 10번당 1번 하드를 읽는 것과 마찬가지기 때문에 시간을 엄청나게 절약할 수 있을 것 같습니다. >ㅁ<)-b

 하지만 이 기쁨도 잠시... 클러스터 영역을 캐시하는 기능도 추가했는데, 캐시 히트가... 20% 정도... ㅠㅠ 캐시 블럭의 개수를 128개 정도로 설정한 상태인데, 이를 더 늘리면 히트는 올라가겠지만 메모리 소모가 늘어나는 문제가 있기에 적당히 타협을 해야할 것 같습니다. 테스트 환경에서 캐시 블럭의 수를 2048개까지 늘리니까 40%까지는 히트가 올라가던데, 메모리 소모에 비해서 히트의 상승폭이 좀 낮아서 일단 다시 낮춰 놨습니다. 테스트는 가상으로 파일 3개를 만들고 랜덤한 파일 오프셋에서 랜덤한 사이즈를 읽고 쓰도록 하는 방법으로 하고 있는데, 아무래도 랜덤하게 읽고 쓰다보니 같은 클러스터가 다시 쓰이는 경우가 적은 것도 하나의 이유인 듯 합니다. 좀 더 고민해보고 캐시 블럭의 개수를 결정해야겠습니다. >ㅁ<)-b

 뭐든 구현하고 테스트할 때가 제일 재미있는 것 같습니다. 특히나 오늘처럼 예상하지 못한 결과가 나왔을 때는 더 즐겁습니다. 하핫~!!!
 헛.. 글고보니 내일은 고향에 가야되는데... 또 이렇게 시간이... ㅠㅠ 그만 자야겠습니다
 다들 좋은 밤 되세요. >ㅁ<)/~~


 어이쿠 또 시간이 이렇게나 흘렀군요. ㅠㅠ 방금 파일 시스템에 FAT(File Allocation Table) 영역에 해당하는 부분에 캐시 버퍼를 추가했는데, 대충 캐시 히트률이 60% 정도 나왔습니다. 생각보다 좀 저조한듯 하기도 하고... 일단 Sequential Write를 랜덤하게 돌려서 쓰고 읽고 검증하는 테스트를 돌리고 있는데, 에러가 안나오는거 보면 큰 문제는 없는 것 같습니다. 약간 문제라면... 캐시 히트률이 좀 낮은 게 문제라면 문제랄까요? ^^;;;;

 일단 캐시 알고리즘은 LRU(Least Recently Used)을 선택했고, Dirty 비트를 추가해서 Dirty 또는 Clean 중에 왠만하면 Clean인 놈을 선택하도록 했습니다. 물론 단순히 Age 만을 비교하는 것도 괜찮지만, 만약 쓰기가 수행된 캐시 버퍼 즉 Dirty 비트가 켜진 캐시 버퍼를 선택하게 되면 하드에 한번 더 써야하는 문제가 발생할 수 있어서 Clean을 우선적으로 선택하게 만들었습니다. Clean 인 놈은 쓰기가 수행되지 않고 읽기만 수행된 버퍼기 때문에 플러쉬(캐쉬에서 내보낼때)할 때 HDD에 안써도 되기 때문이지요. ㅎㅎ

 아흑... 원래는 더 일찍 끝났어야 하는데, lseek를 추가한다고 시간이 이렇게 흘러버렸군요. 처음에 예상했던 크기보다 점점 커지고 있어서 살짝 걱정이 됩니다. 어휴... 이걸 어떻게 해야될지... ㅠㅠ 그래도 일단 하던 작업은 끝내는게 맞는 듯 하니 이것까지만 해야겠습니다. ^^;;;

 내일은 알고리즘을 좀 손보고 데이터가 있는 클러스터 영역을 캐싱하는 기능도 추가해야겠습니다. 그리고 OS에 바로 옮겨봐야겠군요. ㅎㅎ 살짝 기대가 된다는... 과연 캐시를 사용하는 것과 사용하지 않는 것이 가상 머신에서 어떻게 차이가날지... 완전 기대됩니다. ㅎㅎㅎ

 그럼 내일 출근을 위해 전 이만 취침하러... ㅎㅎ
 다들 좋은 밤 되시길~ ;)


 주말 동안 쉬엄 쉬엄 파일 시스템을 설계했습니다. FAT 파일 시스템처럼 클러스터의 링크 형태로 말이지요. ^^ 최대한 간단하게 구현하려고 디렉토리 구조는 더욱 더 단순화시켰습니다. 그리고 코딩에 들어갔는데, 구현 초기에는 쉽게 쉽게 끝나는가 싶더니... 결국 또 복잡해지더군요.

 일단 멀티 태스크나 멀티 코어 환경에서 동기화 문제는 고려하지 않고, 단순히 파일 및 디렉토리 관련 API 만 만드는데, 꼬박 이틀이 걸렸습니다. 사실 API는 간단하게 만들고, 응용 프로그램이 잘 알아서 쓰도록하면 정말 간단한 구조를 유지할 수 있는데... 욕심이 생기다보니 이것 저것 많이 만들었습니다. 끄응.... ㅡ_ㅡa...

 덕분에 쓰기는 편해졌습니다만... 설명할게 많아져서 고민입니다. 현재 opendir, readdir, rewinddir, closedir, openfile, readfile, writefile, closefile, rewindfile, createfile, deletefile, createdirectory, deletedirectory까지 만들어져 있는데, 소스가 장난이 아니네요. 클러스터를 걸치는 녀석들 읽는거 처리하랴, Path 관련 처리하랴... 어휴... ㅠㅠ 하는 김에 rewindfile은 좀 더 고민해서 lseek로 만들어야겠습니다. 원래 lseek는 고려하지 않았지만... 만들다 보니 lseek 빼고는 다 만들었군요. ㅡ_ㅡa...

 만들긴 했는데... 과연 이 API가 얼마나 사용될지.... 삽질한건 아닌가 걱정이됩니다. ㅠㅠ
 억... 또 벌써 시간이 이렇게 됬군요.

 다들 좋은 밤 되시길.... ^^)-b

 ps) 아흑... 파일 동기화 문제는 또 어떻게 해결하지... ㅠㅠ


 아이쿠... 원래 계획은 좀 더 진도를 나가는 거였는데... 그 동안 무리를 좀 했더니 저녁에 잠깐 졸았습니다. 한 40분 잤으니 존거 치고는 시간이 좀 길군요. ^^;;;; 아무리 피곤해도 존적은 별로 없는데... 몸이 좀 피곤하긴 한가 봅니다. 그러거나 말거나 할꺼는 해야하니 세수 한판하고 또 진도 나갔습니다.

 일단 어제 대충 만든 테트리스를 좀 수정해서 블럭 색깔하고, 내부 알고리즘을 좀 정리했습니다. 원래 테트리스가 그리 복잡한 알고리즘이 필요없는 게임이라... 알고리즘이라는 말을 쓰기도 부끄럽습니다만... ㅡ_ㅡa... 발로 짜다보니 도저히 안되겠더군요. ㅎㅎ 내일쯤 정리되면 한번 올리겠습니다. ^^;;;;

 테트리스를 수정한 뒤에 한참을 고민했습니다. 이제 더 뭘 구현해야 하는가.... 가만히 보니 3개 정도가 더 남았더군요. 디지털 시계, 텍스트 뷰어, 이미지 뷰어 정도 남았던데... 디지털 시계는 텍스트로 날짜와 시간만 표시해줄 생각이니 크게 어렵지는 않을 것 같고... 텍스트 뷰어는 말 그대로 뷰어니까 그냥 텍스트만 출력해주면되니 이것도 별로 어렵지 않을 것 같았습니다.

 그렇다면 남은 것은 이미지 뷰어인데, 어떤 파일 포맷을 사용할 것인가가 문제더군요. 가장 쉽게 처리할 수 있는게 BMP 파일 형식인데... BMP도 다양한 버전이 있기 때문에 어떤걸 해야할지 고민이 됬습니다. 그러다가 문득 NDS 홈브루를 개발할때 만들었던 KNG(kkamagui NDS Graphic) 파일 포맷이 생각나서 그걸 사용하기로 했습니다. NDS 홈브루도 16bit Color로 설정되어있고, 마침 지금 만드는 OS도 16bit Color로 설정되어있으니 큰 수정없이 사용할 수 있을 것 같았습니다. KNG를 사용하는 NDS 홈브루는 http://kkamagui.tistory.com/49http://kkamagui.tistory.com/468 에서 보실 수 있습니다.


 위의 화면은 수정된 테트리스와 그 뒤로 이미지 뷰어가 실행된 화면입니다. 이미지는 KNG로 변환해서 OS에 시리얼로 전송하는 방식으로 했습니다. KNG를 사용한 덕분에 작업이 상당히 빨리 끝났습니다. ^^;;; 이것 저것 많이 만들다보니 이런 날도 오는군요~ 좋구로 ㅎㅎ

 작업을 진행하면서 느끼는 QEMU의 장점은 TCPIP로 가상 시리얼을 연결할 수 있어서 굉장히 편리하게 시리얼 통신을 할 수 있는 점입니다. 속도도 굉장히 빠르고 QEMU 내부적으로 버퍼 관리도 해주니 OS에서 구현된 허접한 시리얼 드라이버로도 잘 됩니다. 덕분에 귀찮은 작업이 얼마나 줄었는지... ㅠㅠ)-b QEMU 만세~!!!

 아아... 벌써 또 시간이 이렇게 됬군요. ㅠㅠ 언제쯤 일찍 자려나... 그러려면 집에 굉징히 일찍오던지... 아니면 컴퓨터를 안켜야하는데, 둘다 실현 가능성이 낮군요. ㅠㅠ 거의 다 완료될때까지 잠을 줄이는 수 밖에는 없을 것 같습니다. 아흑....

 그럼 다들 좋은 밤 되세요. ^^)/~

ps) 헛... 이런 잠결에 만들다보니 화면에 있는 이미지 뷰어에 스펠링이 틀렸군요. 이런 ㅠㅠ

 헉헉... 아이구 머리야... 예전에 만든 소스를 긁어다 붙이는 것도 힘들군요. ㅎㅎ 금방 끝날줄 알았는데... 벌써 시간이 이렇게... 일단 아래는 실행한 화면입니다. 아직 프로토타입의 형태라서 버그도 좀 있고, 상태도 별로 안좋습니다. ㅎㅎ


 자세한 내용은 내일 올리겠습니다. 좀 늦어서... ㅠㅠ)-b
 그럼 다들 좋은 밤 되세요 >ㅁ<)-b



 간단히 시작한 일인데... 버그가 나오는 바람에 연짱 7시간 정도 디버깅한 것 같습니다. ^^;;; 멀티 코어라는걸 자꾸 까먹고 작업을 하다보니 이상한 고정관념에 사로잡혀서 제대로 코딩을 안했더군요. ㅠㅠ)-b 크윽... 오늘은 꼭 기억할껍니다. ㅠㅠ 이렇게 허접할수가...

 멀티 코어임에도 불구하고 CPU가 초기화되는 순서가 0번부터 순차적일꺼라는 말도 안되는 생각을 계속 하고 있었습니다. 그래서 코딩도 순차적으로 CPU가 초기화 되니 스택도 순차적으로 할당해 주도록 했더군요. 허나 실상은... 초기화 되는 순서를 아무도 모른다는... ㅠㅠ 그래서 CPU의 ID를 직접보도록 수정했습니다. 끄응... ㅡ_ㅡa...

 그리고 막판에 짬을 좀 내서 CPU Monitoring Application을 추가했습니다. 원래 이게 목적이었는데, 장난삼아 Dual Core에서 Quad Core로 QEMU 옵션을 변경했다가 버그를 발견하고 디버깅만 주구장창했다는... 내일 출근은 또 어찌할지... 머리도 띵하고... 흑흑....

 아래는 급히 찍은 스크린샷입니다. Quad Core로 만들어 놓고 실행시킨 화면인데, 원체 허접하게 CPU Load를 계산하는지라... 별로 믿음직스럽지 못하군요. ㅎㅎ 그래도 된다는데 의의를... ^^;;; 시험삼에 Hexa Core(16개)까지 QEMU에서 테스트해봤는데, PC도 버벅거리고 QEMU에서 속도가 제대로 안나오더군요. 그나마 Quad Core까지는 쓸만한 것 같습니다.
사용자 삽입 이미지

 어휴... 테스트를 제대로 안해보고 거사를 시작했다면 어찌됬을까 아찔합니다. ㅠㅠ 역시 여유를 갖고 검토하는 시간을 갖길 잘한 듯 합니다. 에혀~

 이제 자야겠군요. 내일 하루종일 좀비모드로 지낼 것 같다는... ㅠㅠ
 다들 좋은 밤 되세요 ;)


 거의 4일 동안 문서를 뒤지고 직접 테스트를 수행한 결과, IO APIC와 APIC간의 관계와 8259A(PIC) 칩간의 관계에 대해서 알아냈습니다. 더불어 BIOS에 존재하는 MP 관련 정보와 IO APIC의 Redirection Table, CPU의 APIC의 레지스터에 의미에 대해서도 대충 알아냈습니다. ㅜ_ㅜ)-b

 어떻게 테스트 했는지는 나중에 올리겠습니다. 나름 복잡한 내용을 포함하고 있고, 또 시간이 너무 늦은지라 ㅎㅎ 대신 아래의 스샷을 첨부합니다. 제가 만든 OS(KKAMAGUI OS)에서 IO APIC를 통해 BSP(Bootstrap Processor)에 키보드와 타이머 이벤트를 밀어넣는 화면입니다. 볼품없지만 이게 4일 동안 작업한 것이라는... OTL....
사용자 삽입 이미지

 그래도 IO APIC와 Local APIC에 대해 잘 모르면 Sysmmetric I/O로 갈 수 없기 때문에 어쩔 수 없었습니다. ㅜ_ㅜ)-b 결국 하긴 했군요. 흑흑.... 8259A(PIC) 컨트롤러의 타이머/키보드 인터럽트를 발생 못하도록 mask 했는데도 인터럽트 방식으로 잘 동작하는 화면을 보니 신기하군요. ^^;;;;

 그럼 이만 자야겠습니다. 좋은 밤 되시길~ ;)
 Immunity Debugger를 만드는 Immunity 사에서 Kernel Exploit 관련 자료들을 공유하고 있습니다. ^^)/~ 원문은 http://immunityinc.com/resources-papers.shtml 에서 보실 수 있습니다.

 아직 보지 않아서 무슨 자료인지는 정확히 알 수 없지만, 제목만 훓어봐도 꽤 괜찮겠다 싶은 내용이 있더군요. 아래는 위 사이트에서 소개한 목차와 내용입니다.
June 11, 2008: Exploiting Kernel Pool Overflows (Kostya Kortchinsky)
ODP

June 11, 2008: The I2OMGMT Driver Impersonation Attack (Justin Seitz)
PDF
ODT

March 28, 2008: The Hacker Strategy (updated for Harvard ABCD meeting)
PDF

February 29, 2008: IO Immunity Style (Sinan Eren)
PDF

January 26, 2008: The Hacker Strategy (S4 SCADA conference keynote)
PDF

January 22, 2008: Going Against The Gradient
PDF
OpenOffice

December 14, 2007: Beyond Fast Flux
OpenOffice (presentation)
PDF

November 23, 2007: Exploit Development with Immunity Debugger
OpenOffice (presentation)

August 10, 2007: Damian Gomez - Intelligent Debugging
PDF (presentation)
Openoffice (presentation)

July 6, 2007: Nicolas Waisman - Understanding and Bypassing Windows Heap Protection
PDF (presentation)
Openoffice (presentation)

July 6, 2007: Justine Aitel - The IPO of 0days
PDF (presentation)
Openoffice (presentation)

April 8, 2007: Dave Aitel - CANVAS Command Line Executer
Openoffice (paper)

April 8, 2007: Kostya Kortchinsky - Macro-Reliability in Win32 Exploits
Openoffice (presentation)

December 6, 2006: Dave Aitel - Remote Language Detection
Openoffice (paper)

August 30, 2006: Dave Aitel - MSRPC Fuzzing
OpenOffice (paper)
OpenOffice (presentation)

February 13, 2006: Dave Aitel - Resilience
PDF
OpenDocument
TGZ source

January 26, 2006: Dave Aitel - Nematodes (updated)
PDF
OpenDocument
VisualSploit Preliminary Flash Demo

September 29, 2005 Dave Aitel - Nematodes
OpenOffice PDF

May 9th, 2005 Dave Aitel - Practical IDS Evasion
OpenOffice

Feb, 28th, 2005 Bas Alberts - Exploiting the PHP_Limit bug
PDF

Jan 29, 2005 Dave Aitel - 0days: How hacking really works
Open Office PDF HTML

Oct 12, 2004 Dave Aitel - The CANVAS Reference Implementation
Open Office

Oct 12, 2004 Dave Aitel - Advanced Ordnance 2
Open Office PDF

August 12, 2004 Dave Aitel - Microsoft Windows, a lower Total Cost of Ownership
Open Office    |   PDF

June 21, 2004 Dave Aitel - Beyond Best Practices (Given at OWASP AppSec 2004)
Open Office

May 19, 2004 Dave Aitel - Rapid Application Development in Linux using pyGTK
Open Office

March 1, 2004 Dave Aitel - Enterprise Secific Software Security Issues:
Open Office

Feb 4, 2003 The Advantages of Block-Based Protocol Analysis for Security Testing:
Open Office   |    HTML   |    Text   |    Post Script   |    PDF

July 29, 2002 Using SPIKE 101
Power Point   |    Real Media

Feb 24, 2003 - Vivisection of an Exploit Development Process
Power Point   |    Real Media

May 1, 2003 - Windows Exploitation for Unix Hackers
Power Point

Sep 29, 2003 - MOSDEF
Power Point

Jan 27, 2004 - Advanced MOSDEF
Open Office

March 1, 2003 Nicolas Waisman - Linux Heap Overflow Techniques
Power Point

Sep 12, 2003 Microsoft Heap Overflows I/II
PDF    |   Open Office

Sep 12, 2003 Microsoft Heap Overflows II/II
PDF    |   Open Office

 완전 득템이군요. ^^;;; 그럼 즐거운 주말 보내시길~ ;)


예전에 개발하면서 적어놓았던 것인데, 블로그가 이전되서 다시 옮겨왔습니다. ^^

< 2004 05 11 GUI ( Graphic User Interface) >


으옷... @0@/~ 드뎌 네모난 커서에서 벗어났습니다 ㅋㅋㅋ 이틀동안 삽질한 끝에

Icon 파일을 읽어 낼 수가 있게 되었네요.. 화면 중앙에 보시면 콘솔위에 삼각형의

하늘색 커서가 있음을 알 수 있습니다. 요 아래쪽의 스샷을 보시면 파란색 네모가

동동~ 떠있는데, 고게 Icon 파일의 힘으로 삼각형으로.. ㅡ0ㅠ.. 감동..



< 2004 05 08 GUI ( Graphic User Interface) >

윈도우의 폰트가 기존의 래스터에서 돋움체로 바뀌어서 한컷 찍어 올립니다. 양쪽에

는 뽀대를 살리기위한 비트맵 로더가 떠있구요, 가운데 보시면 돋움으로 무장한

GUI Shell이 오랜지색 커서와 함께 두둥 떠 있는 걸 보실 수 있습니다.

으흣.. 폰트가 바뀌니 훨씬 나아 보이는군요.. ㅋㅋㅋ

앗쌀~ 홧팅 @0@/~



< 2004 05 06 GUI ( Graphic User Interface) >


아아.. 오래간만에 한컷 올립니다. 에궁에궁 출장이다 머다 해서 한동안 손을 조금

놓고있었기때문에, 헐헐헐..

이번에 스샷의 관전 포인트(??)는 제 마스코트랑 열혈강호가 올라가있는 BMP Viewer

가 아니라, 요것들이 모두 Application, 즉 User Level의 프로그램들이라는 것에 초점

을 맞추어야 한다고.. 쿠.. 쿨럭..;;;;

그리고 GUI Console에 오랜지색 네모 커서도 넣었고, 윈도우 테두리도 녹색에서

오랜지 빛으로 바꾸었습니다. 이게 더 보기가 좋군요.. ㅋㅋㅋ

일단 곧 커널이랑 하드 이미지도 릴리즈를.. 글고.. 곧 소스도..

쿠.. 쿨럭..;;; 정리는 안하고 지저분하게 삽질만 계속 하고 있는..

까.. 마.. 구.. @0@/~




< 2004 04 11 GUI ( Graphic User Interface) >


위에서 보시는 스샷은 아래의 4월 3일자의 스샷과 별 차이가 없는데요, 사실 비슷하긴

한데, 내부적으로는 상당한 변화가 있었기때문에 기념으로 한컷 올립니다.

으읏 진짜 이번엔 문제가 너무 심각해서 며칠째 고심해서 고쳤는지 ㅡ0ㅠ...

이번에 수정하면서 그런생각이 들더군요.. 과연 이 삽질의 끝에는 머가 있을까..

굉장히 궁금합니다 그려.. ㅋㅋㅋㅋ

4월 3일자에서 있었던 키의 문제는 해결한 스샷입니다. 사실 외관은 똑같군요..

( 아아.. 암것도 안한거 같오.. ㅡ0ㅠ... 억울..크윽.. )





< 2004 04 03 GUI ( Graphic User Interface) >



이번에 구현된 GUI system call을 테스트 하기 위해 만든 Application입니다.

기본적으로 하는일은, CUI에서 보여지는 80 * 25의 Text 화면을 그대로 옮겨서 보여

주는 것이지요.

아직 Key 문제가 확실히 해결되지 않아서 약간 문제가 있지만 그런대로

볼만은 하군요.. ㅋㅋㅋㅋ


< 2004 03 23 ETC ( ^ㅠ^ ) >



제가 목표로 하고 있는 GUI의 모습입니다.

Evil WM 이라고 굉장히 가볍다고 하는군요. 저는 윈도우를 쓰기때문에 잘 모름..

그러나 딱 봤을때 이미 필이 왔죠.. 보면 볼수록 멋집니다.

@0@/~~





< 2004 03 27 CUI ( Console User Interface) >




첫번째 화면은 이번에 릴리즈된 커널을 Bochs에서 돌린 화면입니다.

showdevice 했을때, com1, HDD, RamDisk 순으로 보이는 군요.

기본적으로 다 Mount된 상태로 실행되게 했습니다.

이게 테스트 하긴 더 편하더라구요..

두번째 화면은 KKAMAGUI Editor를 돌린 화면입니다. 간단한 텍스트를 입력하고,

/a.txt로 저장했어요.

세번째 화면은 KKAMAGUI Editor를 돌려 시리얼로 전송받은 소스코드를 읽은

화면이에요. 제가 짠건 아니지만 걍 있길래 전송해서 열어봤죠.. >_<



< 2004 03 24 CUI ( Console User Interface) >


이번에 릴리즈된 커널을 Bochs에서 돌린 화면입니다. showdevice 했을때,

램 디스크 하나랑 시리얼 포트, 그리고 추가된 하드가 보이는군요.

아참 램 디스크( rd0 )는 이번 커널에는 아니고.. 곧 릴리즈될 커널에 있는 건데..

Screen shot이 잘못됬네요.. 여튼 '/'에 Mount 한다음 내용물을 보여준겁니다.

므흣.. 좋네요~ ^0^/~


< 2004 03 23 ETC ( ^ㅠ^ ) >


제가 주로 쓰는 kkamagui를 그려봤습니다. 아마 지난 설인걸로 기억하는데요..

ㅋㅋㅋ 그때 별로 할일이 없어서 그림판으로 그렸던듯 하군요

그냥 한번 웃어보시라구요 ^0^/~



< 2004 03 15 CUI ( Console User Interface) >



CUI Screen shot 입니다. 맨 위에 화면은 처음 부팅했을때 보이는 화면입니다.

오늘 페이지를 추가하면서 생긴 화면이네요.. ^^

두번째 화면은 Worms라고 제가 공유메모리를 구현하고 그걸 테스트하기위해 만든

프로그램입니다. 처음에 녹색 한마리가 이리저리 돌아다니는데요, 이게 일정시간

지나면 상태도 변하고 그에 따라 먹이도 먹고 분열도 하고 싸움도 하는 그런 프로그램

입니다. 머 알고리즘이 그리 복잡하지 않기 때문에 크게 변화는 없구요.

주기적으로 늘었다 줄었다 하는군요. 심심하신 분은 한번 실행해 보심이.. ㅋㅋ


< 2004 03 14 CUI ( Console User Interface) >


CUI Screen shot 입니다. 맨 위에 화면을 보시면 Device 목록에 hda0 및 hdc0 두개의

하드디스크와 시리얼 포트 2개가 있음을 알 수 있습니다.

mount 명령으로 /에 hdc0를 mount하고 ls를 통해 파일을 조회한 화면입니다.

두번째 화면은 B2OS를 만드신 분이 CUI버전으로 테트리스를 제작하셨는데요, 그걸

그대로 포팅해서 제 OS에 돌린 화면입니다. ^^

B2OS 주인장님께 거듭 감사를 드립니다. (_ _)


< 2004 03 14 BoxBox Prototype>

GUI Prototype Screen shot 입니다. 이름은 BoxBox이구요. 사각형으로 이루어진

간단한 GUI를 목표로 하고 있기 때문에 이름을 이렇게 지었습니다.

커서( 가운데의 하얀 사각형 )의 모양까지 현재는 사각형을 띄고 있는데요,

조만간 커서는 삼각형으로 만들어볼까 생각중입니다. ㅋㅋ

윈도우는 evilwm에 영향을 받아서 녹색의 1 pixel로 이루어져 있구요, 내부는

녹색을 약간띄는 어두운 색으로 맞추어놨습니다.

테스트를 위해 여러 윈도우를 겹쳐놨는데, 생각난김에 한컷 잡아서 올립니다.

볼수록 모노크롬 모니터 시절을 생각나게 하는군요. ^^

개인적으로는 아주 맘에 드는데 말이죠. ㅋㅋ

이런 멋진 GUI를 만들게 해주신 Linefeed 님과 Ed 님께 감사를.. ㅎㅎ


 안녕하십니까 까마굽니다 (__)
 크윽, 소스를 밤낮 할꺼 없이 좀 무리해서 정리를 했더니만, 동작이 이상하군요.ㅡ0ㅠ 분명 정리하다가 멀 같이 지워버린거 같습니다.

 크윽 젠장, 두군데나 벌써 이상이 생겼는데요, 막막하군요. 정리를 마저하고 디버깅을 해야 할지, 아니면 일단 지금 해결하고 다시 정리를 시작해야 할지...한가지 분명한건, 잘못 정리했다는 것이죠.

 ㅡ0ㅠ.. 아아, 젠장 젠장...소스는 정신이 말짱할때만 봐야 겠네요.
 그럼 좋은하루 되세요 (__)
 
 ps) 요즘 질답란의 폭주로 인해 상당히 뿌듯하다는...모두들 홧팅입니다. @0@/~

+ Recent posts