题目中上,但各种乱象毁了这个比赛。

NotFormat

概览

程序是静态链接的,利用缓解机制只启用了NX,程序只是一个简单的读取输入并返回。

1
2
3
4
5
6
$ checksec NotFormat
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE
1
2
3
4
$ ./NotFormat
Have fun!
test
test

漏洞

程序main函数很短,如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void main()
{
double v8; // xmm4_8@1
double v9; // xmm5_8@1
char buf[264]; // [rsp+0h] [rbp-110h]@1
__int64 v11; // [rsp+108h] [rbp-8h]@1
v11 = *MK_FP(__FS__, 40LL);
setvbuf((int *)off_6CB740, 0LL, 2, 0LL);
puts("Have fun!");
read_line(buf);
printf(buf, 0LL);
exit(0LL);
}

存在一个很明显的格式化字符串漏洞。

利用

因为格式化字符串使用之后立即就调用exit退出了,所以目标是直接通过一次printf劫持控制流。

控制RIP

我用到的是修改FILE *stdout结构的vtable到我们构造的位置。由于没有开启PIE,所以这些内容的位置都可以确定。

栈迁移

当控制RIP之后,程序的栈的位置与我们输入内容的位置差距很大,但是正好找到这个gadget可以迁移到我们输入的buffer。

1
0x43f17d : ret 0x6b8

ret之后正好会跳到0x0457fc1位置,实现栈迁移。

1
0x457fc1 : add rsp, 0x2120 ; mov eax, r12d ; pop rbx ; pop rbp ; pop r12 ; pop r13 ; pop r14 ; re

之后可以再次调用read,并再次迁移到一个大buffer。最后调用execve("/bin/sh", 0, 0)

uctf2017_notformat.py

babydriver

概览

这是一个简单的驱动题,只开启了SMEP(貌似也用不到……)。

提供了/dev/babydriver设备:

  • read/write操作可以将buf从babydev_struct.device_buf读取或写入,长度限制为babydev_struct.device_buf_len
  • ioctl的cmd为0x10001时可以对babydev_struct.device_buf重新分配。
  • close时会将babydev_struct.device_buf释放。

漏洞

一个最明显的漏洞是babydev_struct是一个全局变量,所有的文件描述符都共享一个babydev_struct,当一个文件描述符被调用close时,babydev_struct.device_buf会被释放。当其他未关闭的文件描述符调用时,会触发UAF,可以对一段内核空间进行读写。

利用

当device_buf被释放掉后,这个指针成为悬空指针,扔可以进行读写,这时候希望可以有一个结构被申请然后占到原来的这个位置。最直观的想法就是如果能有cred结构直接占上来,然后就可以对cred内容修改,达到获取root权限的目的。

cred结构如下

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
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
};

它的大小是0xa8。于是利用的步骤如下:

  1. open两个fd,fd1及fd2。通过ioctl设置device_buf为0xa8大小。
  2. close(fd1),device_buf指针被释放。
  3. 现在可以通过fd2继续操作device_buf指向的内容。
  4. 通过fork一堆进程试图在申请cred结构的时候占到device_buf指向的位置。
  5. 修改占上的cred,获取root。

做的时候粗暴地试试结果发现就可以占上了……然后粗暴的把ctf用户的所有的id为0x3e8的都改成了0。

uctf2017_babydriver.py

P.S. 出题时希望可以把网卡驱动带进去,不然传程序进去也挺麻烦……