Skip to content

CTF

general

return oriented programming

利用 x86 的特性,可以找到一系列 gadget,几条简单的指令,其中最后一条指令是 ret。在栈溢出的时候,通过构造栈,把要执行的一系列的 gadget 的地址放在栈上,使得函数在返回了以后,会按照顺序执行各个 gadget。

stack pivoting

如果栈溢出可以覆盖的部分比较少,无法放下完整的 return oriented programming 攻击,可以利用一个简单的 gadget,修改 rsp 到一个可控的可以放比较多数据的位置,再进行后续的攻击。

glibc

fake FILE

构建一个假的 struct _IO_FILE_plus 结构体,添加到 _IO_list_all 链表里,当 main 返回的时候,会 flush 它。通过构造特定的结构体内容,可以使得自定义的 _IO_wdoallocbuf 指针被调用,把它指向 system,第一个参数也可以被控制为 /bin/sh 字符串的指针,进而 get shell。

glibc 2.38+:_IO_cleanup 中会调用 _IO_flockfile 获取锁,所以 lock 字段也需要是合法的。

malloc hook

__malloc_hook 指向 system,控制 size 参数为 /bin/sh 字符串的指针,从而实现 get shell。

glibc 2.34+:移除了 __malloc_hook,无法继续使用该办法。

malloc got

覆盖 .got.plt 中 malloc@plt 的指针,指向 system,控制 size 参数为 /bin/sh 字符串的指针,从而实现 get shell。

要求可写的 .got.plt,即 checksec 报告 No RELROPartial RELRO

heap

glibc 2.32+:检查 chunk 的对齐,并且添加了 safe linking。

详见 glibc 内存分配器

linux

modprobe path

修改内核的 modprobe_path 内容,它指向 modprobe 路径,当内核遇到各种可能在尚未加载的内核模块中实现的功能时,会用 root 权限运行它去寻找合适的内核模块。把它指向攻击者控制的程序,然后用各种方法触发内核的自动 modprobe:

override cred

task_struct.cred 字段记录了进程的权限(uid/gid 等),如果能覆盖 uid/gid 为 0,就得到了 root 权限。所以要做的是,找到当前进程的 cred 然后进行覆盖。方法:

  • 在堆上寻找当前进程的名字(可以预先用 prctl(PR_SET_NAME, name) 设置),它保存在 task_struct.comm 字段,在它前面不远就是这个进程的 task_struct.cred 字段
  • 在内核态运行 commit_creds(prepare_kernel_cred(NULL)) 函数(在 Linux 6.2+,改为 commit_creds(prepare_kernel_cred(&init_task)),由于 cred: Do not default to init_cred in prepare_kernel_cred() 这一改动)

return to user

在内核态进行 return oriented programming 比较困难,因此可以在用户态构造指令序列,然后在内核态下跳转到用户态地址上执行指令,首先利用内核态进行提权,然后用 swapgs+iretq 回到用户态,此时可以用 root 权限 get shell。这个方法会被如下的缓解措施阻止:

  • Supervisor Mode Access Prevention:无法在内核态下读写用户态的地址
  • Supervisor Mode Execute Prevention:无法在内核态下执行用户态的代码
  • Kernel Page Table Isolation:Linux 的实现会让用户态的代码在内核态的页表中被标记为 Not Executable

Comments