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

List

Debugging kernel and modules

  • 커널을 디버깅하는 방법은 다양하게 존재하며, 여기에서는 VMware를 이용한 디버깅을 설명하겠습니다.

Debug Symbol Packages

  • 분석의 편의성을 위해 Debug Symbol를 설치합니다.

Getting -dbgsym.ddeb packages

  • 다음과 같이 저장소 정보를 "/etc/apt/sources.list.d/ddebs.list" 파일에 저장합니다.

Create an /etc/apt/sources.list.d/ddebs.list
echo "deb http://ddebs.ubuntu.com $(lsb_release -cs) main restricted universe multiverse
deb http://ddebs.ubuntu.com $(lsb_release -cs)-updates main restricted universe multiverse
deb http://ddebs.ubuntu.com $(lsb_release -cs)-proposed main restricted universe multiverse" | \
sudo tee -a /etc/apt/sources.list.d/ddebs.list
  • Ubuntu 서버에서 Debug Symbol 아카이브 서명 키를 가져옵니다
Ubuntu 18.04 LTS 버전 이상의 경우
sudo apt install ubuntu-dbgsym-keyring

Manual install of debug packages

  • 해당 시스템의 커널 버전에 맞는 Debug packages를 설치합니다.
Install Debug Symball
sudo apt-get update
sudo apt-get install linux-image-$(uname -r)-dbgsym
  • 설치된 Debug Symbol 파일을 다음과 같은 경로에 위치합니다.
Path to Debug symbol
/usr/lib/debug/boot/vmlinux-$(uname -r)

Disable KASLR

  • 다음과 같이 "/etc/default/grub"에 "GRUB_CMDLINE_LINUX_DEFAULT" 필드에 "nokaslr"을 추가하여 KASLR을 비활성화 합니다.
/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_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet nokaslr"
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

Debugging Kernel and Modules with VMware

Debugging Preferences to VMware - debugStub

  • VMware를 이용하여 커널을 디버깅하기 위해 디버깅 대상 VMware의 *.vmx 파일을 열어 다음과 같은 설정을 추가합니다.
    • 디버깅 대상 시스템 환경(32bit, 64bit)에  맞게 해당 내용을 추가합니다.
x86
debugStub.listen.guest32 = "TRUE"
debugStub.listen.guest32.remote = "TRUE"
debugStub.hideBreakpoints = "FALSE"
monitor.debugOnStartGuest32 = "TRUE"
x64
debugStub.listen.guest64 = "TRUE"
debugStub.listen.guest64.remote = "TRUE"
debugStub.hideBreakpoints = "FALSE"
monitor.debugOnStartGuest64 = "TRUE"

Connecting Debugging to the Kernel

  • 디버깅 설정이 저장된 VMware를 기동하면 다음과 같은 화면에서 대기하게 됩니다.
  • 해당 상태에서 다른 VM에서 Root 권한으로 GDB를 실행시켜 해당 VM에 연결합니다.
    • gdb를 연결하기 전에 반드시 해당 시스템의 architecture를 설정해야 합니다.
    • 디버거가 정상적으로 연결되면 디버깅 할 VMware는 정지되며, gdb에서 "continue" 명령을 입력하면 운영체제가 정상적으로 부팅됩니다.
  • gdb연결시 사용되는 Port는 다음과 같습니다.
    • 32bit : 8832
    • 64bit : 8864
Connect GDB
root@ubuntu:/home/lazenca0x0# gdb -q /usr/lib/debug/boot/vmlinux-4.18.0-12-generic 
Reading symbols from /usr/lib/debug/boot/vmlinux-4.18.0-12-generic...done.
(gdb) set disassembly-flavor intel 
(gdb) set architecture i386:x86-64:intel 
The target architecture is assumed to be i386:x86-64:intel
(gdb) target remote 192.168.2.44:8864
Remote debugging using 192.168.2.44:8864
0x0000000001000200 in ?? ()
(gdb) c
Continuing.

Kernel Address Display Restriction(KADR)

  • 디버깅을 설명하기 전에 "Kernel Address Display Restriction(KADR)"에 대한 이해가 필요합니다.
    • 공격자가 커널 취약성을 이용하여 Exploit을 개발하려고 할 때 User Space에서의 Exploit과 같이 필요한 가젯, 커널 함수들의 주소,등의 정보가 필요합니다.
    • 커널에서는 KADR에 의해 커널 영역의 주소를 민감한 정보로 처리하고 있으며, 일반 로컬 사용자에게 보이지 않습니다.
    • 루트 사용자만 다양한 파일 및 디렉토리를 읽을 수 있도록 설정하였다. 
      • /boot/vmlinuz*, /boot/System.map*, /sys/kernel/debug/, /proc/slabinfo
    • Ubuntu 11.04부터 "/proc/sys/kernel/kptr_rr_rrestrict"는 알려진 커널 주소 출력을 차단하기 위해 "1"로 설정되어 있다.
      • 비활성화하기 위해 해당 값을 "0"으로 변경하면 됩니다.
  • 다음과 같이 일반 유저로 커널의 모든 심볼 목록을 보관하고 있는 "/proc/kallsyms" 파일을 읽을 경우 해당 심볼의 주소가 "0000000000000000"으로 출력됩니다.
    • Root 권한으로 해당 파일을 읽을 경우 각 심볼들의 주소 값이 출력되는 것을 확인 할 수 있습니다.
Regular local users
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ cat /proc/kallsyms |grep escalation
0000000000000000 t chardev_release	[escalation]
0000000000000000 t chardev_open	[escalation]
0000000000000000 t chardev_write	[escalation]
0000000000000000 t chardev_read	[escalation]
0000000000000000 t chardev_ioctl	[escalation]
0000000000000000 b info	[escalation]
0000000000000000 t chardev_init	[escalation]
0000000000000000 b chardev_cdev	[escalation]
0000000000000000 b chardev_major	[escalation]
0000000000000000 b __key.28909	[escalation]
0000000000000000 b chardev_class	[escalation]
0000000000000000 t chardev_exit	[escalation]
0000000000000000 d __this_module	[escalation]
0000000000000000 t cleanup_module	[escalation]
0000000000000000 t init_module	[escalation]
0000000000000000 d s_chardev_fops	[escalation]
lazenca0x0@ubuntu:~/Kernel/Module/escalation$
Root user
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ sudo cat /proc/kallsyms |grep escalation
ffffffffc0301000 t chardev_release	[escalation]
ffffffffc0301019 t chardev_open	[escalation]
ffffffffc0301032 t chardev_write	[escalation]
ffffffffc0301051 t chardev_read	[escalation]
ffffffffc0301070 t chardev_ioctl	[escalation]
ffffffffc0303440 b info	[escalation]
ffffffffc0301154 t chardev_init	[escalation]
ffffffffc03034e0 b chardev_cdev	[escalation]
ffffffffc0303548 b chardev_major	[escalation]
ffffffffc0303440 b __key.28909	[escalation]
ffffffffc03034c8 b chardev_class	[escalation]
ffffffffc03012ac t chardev_exit	[escalation]
ffffffffc0303100 d __this_module	[escalation]
ffffffffc03012ac t cleanup_module	[escalation]
ffffffffc0301154 t init_module	[escalation]
ffffffffc0303000 d s_chardev_fops	[escalation]
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ 
  • 하지만 다음과 같이 "kptr_restrict"의 값을 "0"으로 변경해도 일반 유저 권한으로 커널 심볼들의 주소를 확인 할 수 없습니다.

lazenca0x0@ubuntu:~/Kernel/Module/escalation$ sudo sysctl -w kernel.kptr_restrict=0
kernel.kptr_restrict = 0
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ cat /proc/kallsyms |grep escalation
0000000000000000 t chardev_release	[escalation]
0000000000000000 t chardev_open	[escalation]
0000000000000000 t chardev_write	[escalation]
0000000000000000 t chardev_read	[escalation]
0000000000000000 t chardev_ioctl	[escalation]
0000000000000000 b info	[escalation]
0000000000000000 t chardev_init	[escalation]
0000000000000000 b chardev_cdev	[escalation]
0000000000000000 b chardev_major	[escalation]
0000000000000000 b __key.28909	[escalation]
0000000000000000 b chardev_class	[escalation]
0000000000000000 t chardev_exit	[escalation]
0000000000000000 d __this_module	[escalation]
0000000000000000 t cleanup_module	[escalation]
0000000000000000 t init_module	[escalation]
0000000000000000 d s_chardev_fops	[escalation]
lazenca0x0@ubuntu:~/Kernel/Module/escalation$

perf_event_paranoid

  • "kptr_restrict"의 값을 "0"으로 변경해도 일반 유저 권한으로 커널 심볼들의 주소를 확인 할 수 없는 이유는 바로 "perf_event_paranoid" 때문입니다.
  • "perf_event_paranoid"는 Performance counters(커널의 성능 모니터링)의 액세스를 제한 권한을 관리하고 있습니다.
Option
ValueDescription
2사용자 공간 측정만 허용(Linux 4.6 이후 기본값)
1커널과 사용자 측정을 모두 허용(Linux 4.6 이전 기본값)
0원시 추적점 샘플이 아닌 CPU별 데이터에 대한 액세스를 허용
-1제한 없음
  • 다음과 같이 "perf_event_paranoid"의 값을 1 또는 0, -1로 변경하면, 일반 유저 권한으로 커널 심볼들의 주소를 확인 할 수 있습니다.
sudo sysctl -w kernel.perf_event_paranoid=0
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ sudo sysctl kernel.perf_event_paranoid
kernel.perf_event_paranoid = 3
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ sudo sysctl -w kernel.perf_event_paranoid=0
kernel.perf_event_paranoid = 0
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ cat /proc/kallsyms |grep escalation
ffffffffc0a5e000 t chardev_release	[escalation]
ffffffffc0a5e019 t chardev_open	[escalation]
ffffffffc0a5e032 t chardev_write	[escalation]
ffffffffc0a5e051 t chardev_read	[escalation]
ffffffffc0a5e070 t chardev_ioctl	[escalation]
ffffffffc0a60440 b info	[escalation]
ffffffffc0a5e154 t chardev_init	[escalation]
ffffffffc0a604e0 b chardev_cdev	[escalation]
ffffffffc0a60548 b chardev_major	[escalation]
ffffffffc0a60440 b __key.28909	[escalation]
ffffffffc0a604c8 b chardev_class	[escalation]
ffffffffc0a5e2ac t chardev_exit	[escalation]
ffffffffc0a60100 d __this_module	[escalation]
ffffffffc0a5e2ac t cleanup_module	[escalation]
ffffffffc0a5e154 t init_module	[escalation]
ffffffffc0a60000 d s_chardev_fops	[escalation]
lazenca0x0@ubuntu:~/Kernel/Module/escalation$

Get section address of module

Register a module
lazenca0x0@ubuntu:~$ cd Kernel/Module/escalation/
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ sudo insmod escalation.ko 
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ lsmod |grep escalation
escalation             16384  0
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ ls -al /dev/chardev0 
crw-r--r-- 1 root root 240, 0 Dec 14 01:10 /dev/chardev0
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ 
  • 앞에서 설명한 "/proc/kallsyms" 파일을 이용하여 디버깅 할 함수들의 주소값을 확인 합니다.
    • 그리고 "/sys/module/" 하위에 등록된 모듈들의 ".text", ".bss", ".data" , 등 해당 영역의 시작주소 정보를 얻을 수 있습니다.
      • "/sys/module/(모듈 명)/sections/.text.unlikely"
      • "/sys/module/(모듈 명)/sections/.bss"
      • "/sys/module/(모듈 명)/sections/.data"
Get section address of module
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ sudo cat /proc/kallsyms |grep escalation
ffffffffc0301000 t chardev_release	[escalation]
ffffffffc0301019 t chardev_open	[escalation]
ffffffffc0301032 t chardev_write	[escalation]
ffffffffc0301051 t chardev_read	[escalation]
ffffffffc0301070 t chardev_ioctl	[escalation]
ffffffffc0303440 b info	[escalation]
ffffffffc0301154 t chardev_init	[escalation]
ffffffffc03034e0 b chardev_cdev	[escalation]
ffffffffc0303548 b chardev_major	[escalation]
ffffffffc0303440 b __key.28909	[escalation]
ffffffffc03034c8 b chardev_class	[escalation]
ffffffffc03012ac t chardev_exit	[escalation]
ffffffffc0303100 d __this_module	[escalation]
ffffffffc03012ac t cleanup_module	[escalation]
ffffffffc0301154 t init_module	[escalation]
ffffffffc0303000 d s_chardev_fops	[escalation]
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ sudo cat /sys/module/escalation/sections/.text.unlikely 
0xffffffffc0301000
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ sudo cat /sys/module/escalation/sections/.bss 
0xffffffffc0303440
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ sudo cat /sys/module/escalation/sections/.data 
0xffffffffc0303000
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ 
  • 다음 예제 에서는 "chardev_ioctl" 함수의 시작 주소를 전달하였으며, 해당 함수의 코드를 확인 할 수 있습니다.

Disassembly the chardev_ioctl function
(gdb) c
Continuing.
^C
Program received signal SIGINT, Interrupt.
0xffffffff819f1ac6 in native_safe_halt () at /build/linux-ChQIyb/linux-4.18.0/arch/x86/include/asm/irqflags.h:57
57	/build/linux-ChQIyb/linux-4.18.0/arch/x86/include/asm/irqflags.h: No such file or directory.
(gdb) x/10i 0xffffffffc0301070
   0xffffffffc0301070:	nop    DWORD PTR [rax+rax*1+0x0]
   0xffffffffc0301075:	push   rbp
   0xffffffffc0301076:	mov    rdi,0xffffffffc03020e8
   0xffffffffc030107d:	mov    rbp,rsp
   0xffffffffc0301080:	push   r12
   0xffffffffc0301082:	mov    r12,rdx
   0xffffffffc0301085:	push   rbx
   0xffffffffc0301086:	mov    ebx,esi
   0xffffffffc0301088:	call   0xffffffff810f4e33 <printk>
   0xffffffffc030108d:	cmp    ebx,0x40884702
(gdb)
  • 다음과 같이 objdump를 이용하여 메모리에 저장된 "chardev_ioctl" 함수 코드와 덤프된 코드가 일치하는 것을 확인 할 수 있습니다.

objdump -d escalation.ko
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ objdump -M intel -d escalation.ko 

escalation.ko:     file format elf64-x86-64

Disassembly of section .text.unlikely:

0000000000000000 <chardev_release>:
   0:	e8 00 00 00 00       	callq  5 <chardev_release+0x5>
...

0000000000000019 <chardev_open>:
  19:	e8 00 00 00 00       	callq  1e <chardev_open+0x5>
...
0000000000000032 <chardev_write>:
  32:	e8 00 00 00 00       	callq  37 <chardev_write+0x5>
...

0000000000000051 <chardev_read>:
  51:	e8 00 00 00 00       	callq  56 <chardev_read+0x5>
...

0000000000000070 <chardev_ioctl>:
  70:	e8 00 00 00 00       	call   75 <chardev_ioctl+0x5>
  75:	55                   	push   rbp
  76:	48 c7 c7 00 00 00 00 	mov    rdi,0x0
  7d:	48 89 e5             	mov    rbp,rsp
  80:	41 54                	push   r12
  82:	49 89 d4             	mov    r12,rdx
  85:	53                   	push   rbx
  86:	89 f3                	mov    ebx,esi
  88:	e8 00 00 00 00       	call   8d <chardev_ioctl+0x1d>
  8d:	81 fb 02 47 88 40    	cmp    ebx,0x40884702
  93:	74 36                	je     cb <chardev_ioctl+0x5b>
  95:	81 fb 03 47 88 80    	cmp    ebx,0x80884703
  9b:	74 71                	je     10e <chardev_ioctl+0x9e>
  9d:	81 fb 00 47 00 00    	cmp    ebx,0x4700
  a3:	0f 85 8e 00 00 00    	jne    137 <chardev_ioctl+0xc7>
  a9:	48 c7 c7 00 00 00 00 	mov    rdi,0x0
  b0:	e8 00 00 00 00       	call   b5 <chardev_ioctl+0x45>
  b5:	31 ff                	xor    edi,edi
  b7:	e8 00 00 00 00       	call   bc <chardev_ioctl+0x4c>
  bc:	48 89 c7             	mov    rdi,rax
  bf:	e8 00 00 00 00       	call   c4 <chardev_ioctl+0x54>
  c4:	31 d2                	xor    edx,edx
  c6:	e9 81 00 00 00       	jmp    14c <chardev_ioctl+0xdc>
  cb:	48 c7 c7 00 00 00 00 	mov    rdi,0x0
  d2:	e8 00 00 00 00       	call   d7 <chardev_ioctl+0x67>
  d7:	ba 88 00 00 00       	mov    edx,0x88
  dc:	4c 89 e6             	mov    rsi,r12
  df:	48 c7 c7 00 00 00 00 	mov    rdi,0x0
  e6:	e8 00 00 00 00       	call   eb <chardev_ioctl+0x7b>
  eb:	48 85 c0             	test   rax,rax
  ee:	75 55                	jne    145 <chardev_ioctl+0xd5>
  f0:	48 8b 35 00 00 00 00 	mov    rsi,QWORD PTR [rip+0x0]        # f7 <chardev_ioctl+0x87>
  f7:	48 c7 c2 00 00 00 00 	mov    rdx,0x0
  fe:	48 c7 c7 00 00 00 00 	mov    rdi,0x0
 105:	e8 00 00 00 00       	call   10a <chardev_ioctl+0x9a>
 10a:	31 d2                	xor    edx,edx
 10c:	eb 3e                	jmp    14c <chardev_ioctl+0xdc>
 10e:	48 c7 c7 00 00 00 00 	mov    rdi,0x0
 115:	e8 00 00 00 00       	call   11a <chardev_ioctl+0xaa>
 11a:	ba 88 00 00 00       	mov    edx,0x88
 11f:	48 c7 c6 00 00 00 00 	mov    rsi,0x0
 126:	4c 89 e7             	mov    rdi,r12
 129:	e8 00 00 00 00       	call   12e <chardev_ioctl+0xbe>
 12e:	31 d2                	xor    edx,edx
 130:	48 85 c0             	test   rax,rax
 133:	75 10                	jne    145 <chardev_ioctl+0xd5>
 135:	eb 15                	jmp    14c <chardev_ioctl+0xdc>
 137:	89 de                	mov    esi,ebx
 139:	48 c7 c7 00 00 00 00 	mov    rdi,0x0
 140:	e8 00 00 00 00       	call   145 <chardev_ioctl+0xd5>
 145:	48 c7 c2 f2 ff ff ff 	mov    rdx,0xfffffffffffffff2
 14c:	5b                   	pop    rbx
 14d:	48 89 d0             	mov    rax,rdx
 150:	41 5c                	pop    r12
 152:	5d                   	pop    rbp
 153:	c3                   	ret    

0000000000000154 <init_module>:
...

00000000000002ac <cleanup_module>:
...

lazenca0x0@ubuntu:~/Kernel/Module/escalation$

Debugging Modules

  • 동적으로 디버깅을 하기 위해 다음과 같이 "chardev_ioctl" 함수의 시작 주소에 Break point를 설정합니다.
Set Break point
(gdb) b *0xffffffffc0301075
Breakpoint 1 at 0xffffffffc0301075
(gdb) c
Continuing.
  • 모듈을 동작시키키 위해 "Exploit" 프로그램을 실행합니다.
Run Exploit
lazenca0x0@ubuntu:~/Kernel/Module/escalation$ ./Exploit
  • 다음과 같이 등록한 Breakpoint가 동작하게되며 해당 모듈을 동적으로 분석할 수 있게 됩니다.
    • 디버깅 심볼 파일에 의해 모듈이 사용하고 있는 함수명이 출력됩니다.
Breakpoint 1, 0xffffffffc0301075 in ??
Breakpoint 1, 0xffffffffc0301075 in ?? ()
(gdb) i r rip
rip            0xffffffffc0301075  0xffffffffc0301075
(gdb) x/22i $rip
=> 0xffffffffc0301075:	push   rbp
   0xffffffffc0301076:	mov    rdi,0xffffffffc03020e8
   0xffffffffc030107d:	mov    rbp,rsp
   0xffffffffc0301080:	push   r12
   0xffffffffc0301082:	mov    r12,rdx
   0xffffffffc0301085:	push   rbx
   0xffffffffc0301086:	mov    ebx,esi
   0xffffffffc0301088:	call   0xffffffff810f4e33 <printk>
   0xffffffffc030108d:	cmp    ebx,0x40884702
   0xffffffffc0301093:	je     0xffffffffc03010cb
   0xffffffffc0301095:	cmp    ebx,0x80884703
   0xffffffffc030109b:	je     0xffffffffc030110e
   0xffffffffc030109d:	cmp    ebx,0x4700
   0xffffffffc03010a3:	jne    0xffffffffc0301137
   0xffffffffc03010a9:	mov    rdi,0xffffffffc03021e1
   0xffffffffc03010b0:	call   0xffffffff810f4e33 <printk>
   0xffffffffc03010b5:	xor    edi,edi
   0xffffffffc03010b7:	call   0xffffffff810b5a60 <prepare_kernel_cred>
   0xffffffffc03010bc:	mov    rdi,rax
   0xffffffffc03010bf:	call   0xffffffff810b56b0 <commit_creds>
   0xffffffffc03010c4:	xor    edx,edx
   0xffffffffc03010c6:	jmp    0xffffffffc030114c
(gdb)
  • 해당 모듈에 대한 동적 분석을 조금더 진행해 보겠습니다.
    • "cmp ebx,0x40884702" 명령어를 처리하는 0xffffffffc030108d 영역에 Break point를 설정하고 프로그램을 진행합니다.
    • 해당 영역에서는 EBX 레지스터에 저장된 값과 0x80884703 값이 같은지 확인합니다.
    • EBX 레지스터에는 0x4700 값이 저장되어 있으며, 해당 값은 0xffffffffc030109d 영역의 코드 조건에 만족합니다.
Second brake point
(gdb) b *0xffffffffc030108d
Breakpoint 2 at 0xffffffffc030108d
(gdb) c
Continuing.

Breakpoint 2, 0xffffffffc030108d in ?? ()
(gdb) x/i $rip
=> 0xffffffffc030108d:	cmp    ebx,0x40884702
(gdb) i r ebx
ebx            0x4700              18176
(gdb) x/10i $rip
=> 0xffffffffc030108d:	cmp    ebx,0x40884702
   0xffffffffc0301093:	je     0xffffffffc03010cb
   0xffffffffc0301095:	cmp    ebx,0x80884703
   0xffffffffc030109b:	je     0xffffffffc030110e
   0xffffffffc030109d:	cmp    ebx,0x4700
   0xffffffffc03010a3:	jne    0xffffffffc0301137
   0xffffffffc03010a9:	mov    rdi,0xffffffffc03021e1
   0xffffffffc03010b0:	call   0xffffffff810f4e33 <printk>
   0xffffffffc03010b5:	xor    edi,edi
   0xffffffffc03010b7:	call   0xffffffff810b5a60 <prepare_kernel_cred>
(gdb)
  • "cmp ebx,0x4700" 명령어를 처리하는 0xffffffffc030109d 영역에 Break point를 설정하고 프로그램을 진행합니다.

    • EBX 레지스터의 값이 0x4700 이기 때문에 JNE 명령어를 통과하여 RDI 레지스터에 값을 저장합니다.

    • RDI 레지스터에 저장되는 값은 0xffffffffc03021e1 이며, 해당 영역에 저장된 값은 "GIVE_ME_ROOT\n" 입니다.

    • 즉, printk 함수를 이용하여 출력된 메시지의 인자 값을 전달하는 것입니다.
Third brake point
(gdb) b *0xffffffffc030109d
Breakpoint 3 at 0xffffffffc030109d
(gdb) c
Continuing.

Breakpoint 3, 0xffffffffc030109d in ?? ()
(gdb) x/i $rip
=> 0xffffffffc030109d:	cmp    ebx,0x4700
(gdb) i r ebx
ebx            0x4700              18176
(gdb) x/10i $rip
=> 0xffffffffc030109d:	cmp    ebx,0x4700
   0xffffffffc03010a3:	jne    0xffffffffc0301137
   0xffffffffc03010a9:	mov    rdi,0xffffffffc03021e1
   0xffffffffc03010b0:	call   0xffffffff810f4e33 <printk>
   0xffffffffc03010b5:	xor    edi,edi
   0xffffffffc03010b7:	call   0xffffffff810b5a60 <prepare_kernel_cred>
   0xffffffffc03010bc:	mov    rdi,rax
   0xffffffffc03010bf:	call   0xffffffff810b56b0 <commit_creds>
   0xffffffffc03010c4:	xor    edx,edx
   0xffffffffc03010c6:	jmp    0xffffffffc030114c
(gdb) si
0xffffffffc03010a3 in ?? ()
(gdb) si
0xffffffffc03010a9 in ?? ()
(gdb) x/i $rip
=> 0xffffffffc03010a9:	mov    rdi,0xffffffffc03021e1
(gdb) x/s 0xffffffffc03021e1
0xffffffffc03021e1:	"GIVE_ME_ROOT\n"
(gdb) si
0xffffffffc03010b0 in ?? ()
(gdb) si
printk (fmt=0xffffffffc03021e1 "GIVE_ME_ROOT\n") at /build/linux-ChQIyb/linux-4.18.0/kernel/printk/printk.c:1985
1985	in /build/linux-ChQIyb/linux-4.18.0/kernel/printk/printk.c
(gdb)
  • 다음으로 prepare_kernel_cred() 함수 호출 후 리턴되는 값을 확인해 보겠습니다.

    • prepare_kernel_cred() 함수 호출 후 리턴 값을 RDI 레지스터에 저장하는 코드 영역(0xffffffffc03010bc)에 Break point를 설정합니다.

    • RAX 레지스터에는 prepare_kernel_cred() 함수 호출 후 리턴 값이 저장되어 있는 주소값이 저장 되어 있습니다.

  • 다음과 같이 RAX 레지스터에 struct cred 구조체를 적용하면 uid, gid, suid, sgid,등의 값이 0으로 설정되어 있습니다.

    • 즉, 이 정보에 의해 Root 권한은 획득하게 되는 것입니다.

Structure Data
(gdb) b *0xffffffffc03010bc
Breakpoint 4 at 0xffffffffc03010bc
(gdb) c
Continuing.

Breakpoint 4, 0xffffffffc03010bc in ?? ()
(gdb) i r rax
rax            0xffff880101b3c900  -131937071806208
(gdb) p *(struct cred*)$rax
$3 = {usage = {counter = 1}, uid = {val = 0}, gid = {val = 0}, suid = {val = 0}, sgid = {
    val = 0}, euid = {val = 0}, egid = {val = 0}, fsuid = {val = 0}, fsgid = {val = 0}, 
  securebits = 0, cap_inheritable = {cap = {0, 0}}, cap_permitted = {cap = {4294967295, 63}}, 
  cap_effective = {cap = {4294967295, 63}}, cap_bset = {cap = {4294967295, 63}}, cap_ambient = {
    cap = {0, 0}}, jit_keyring = 1 '\001', session_keyring = 0x0 <irq_stack_union>, 
  process_keyring = 0x0 <irq_stack_union>, thread_keyring = 0x0 <irq_stack_union>, 
  request_key_auth = 0x0 <irq_stack_union>, security = 0xffff8801390132a8, 
  user = 0xffffffff82453d40 <root_user>, user_ns = 0xffffffff82453de0 <init_user_ns>, 
  group_info = 0xffffffff8245b1a8 <init_groups>, rcu = {next = 0x0 <irq_stack_union>, 
    func = 0x0 <irq_stack_union>}}
(gdb) p (*(struct cred*)$rax).uid
$3 = {val = 0}
(gdb) p (*(struct cred*)$rax).gid
$4 = {val = 0}
(gdb) p (*(struct cred*)$rax).suid
$5 = {val = 0}
(gdb) p (*(struct cred*)$rax).sgid
$6 = {val = 0}
(gdb)

Debugging Preferences to VMware - serial port

  • 커널을 디버깅할 때 앞에서 설명한 것 처럼 VMware의 debugStub를 이용할 수 있지만, Serial Port도 이용할 수 있습니다.
  • 다음과 같이 각 VMware의  *.vmx 파일에 설정을 추가합니다.
Server(Debug)
serial0.present = "TRUE"
serial0.fileType = "pipe"
serial0.fileName = "/private/tmp/com1"
serial0.tryNoRxLoss = "FALSE"
serial0.pipe.endPoint = "server"


Client(gdb)
serial0.present = "TRUE"
serial0.fileType = "pipe"
serial0.fileName = "/private/tmp/com1"
serial0.tryNoRxLoss = "FALSE"
serial0.pipe.endPoint = "client"
  • 그리고 다음과 같이 "/etc/default/grub"에 "GRUB_CMDLINE_LINUX_DEFAULT" 필드에 "kgdbwait kgdboc/ttyS0,115200"을 추가 합니다.

/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_TIMEOUT_STYLE=hidden
GRUB_TIMEOUT=0
GRUB_DISTRIBUTOR=`lsb_release -i -s 2> /dev/null || echo Debian`
GRUB_CMDLINE_LINUX_DEFAULT="quiet nokaslr"
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
  • 다음과 같이 IP, Port가 아닌 Serial Port로 연결을 진행합니다.
(gdb) target remote /dev/ttyS0

References