CheatSheet之二进制漏洞利用[挖坑]

Published: 五 10 十二月 2021

In Misc.

很久没碰这个了,打算复习下,挖个新坑慢慢更新...

准备

工具

  • qira:之前常用的无时间调试器,也可以使用rr
  • libformatstr:格式化串漏洞工具,也可以直接使用pwntools
  • ROPgadgetropperRopper:用于查找ROP的工具,在静态链接时,会存在syscall/int80指令,因此可使用one gadget查找gadget,不过虽然它比较简单但是生成的payload比较大
  • pwntools:这个自不必多说
  • libc-databaselibcSearcher:通过一些地址确定libc版本的工具

保护机制(mitigation)

应用可使用checksec可检查保护机制,常见的有如下机制:

表. 应用防护措施
防护名称说明绕过方式
PIE/PIC程序/对象本身是否支持地址无关,支持时加载段地址可随机依赖于ASLR,绕过和ASLR一致
Canary-fno-stack-protector/-fstack-protector-all1. 劫持__stack_chk_fail
2. 爆破canary:若重启不变,如fork则可逐位爆破
3. 覆写canary:canary存在于TLS中若能覆盖到tls则可爆破
4. 泄漏canary:在函数返回前泄漏它,之后溢出再返回
5. 进行信息泄漏(stack smash):若会输出,则覆盖到argv[0]可泄漏信息
6. 覆写栈上的其他信息,不覆盖那么多就没事了
NX-z exestack可开启栈执行权限1. 直接找读写执行区域
2. ROP
3. 通过mprotect修改权限位
RELROPARTIAL时GOT可写,FULL时初始化后GOT会修改为只读
Fortify位置参数必须连续使用,且格式化串位于可写的区域时不能包含%n此时没法写了,还能用于信息泄漏

而操作系统也有很多:

表. 系统防护措施
防护名称说明绕过方式
ASLR从/proc/sys/kernel/randomize_va_space可看,0关,1栈,加载基址(PIC/PIE),mmap,vdso随机化,2堆也随机1. 爆破:如32位下若通过fork服务,那么可爆破约512次即可获得正确地址
2. 信息泄漏:如pwntools的DynELF先定位到文件的头部,解析文件后获取实际的位置
KCANARY同CANARY信息泄露
SMEP/SMAP特权模式(Ring 0)下禁止执行用户页(User bit)代码/访问数据,由CR4 bit20/21激活,可通过cat /proc/cpuinfo |grep "smep|smap" (普通权限)查看,可以直接使用nosmep启动参数关闭它 1. ROP
2. 修改CR4关闭SMEP
KASLR内核空间的随机化,启动时可使用nokaslr参数关闭1. 信息泄漏
2. 利用dmesg/kallsyms获取地址
KPTI(KAISER/PTI)用户态和内核态使用不同的页表,用户态只保留进出内核必需的内核页,可通过nopti参数关闭
MMAP_MIN_ADDRmmap最映射的最低地址,防止NULL指针解引用,可从/proc/sys/vm/mmap_min_addr查看当前值此时没法写了,还能用于信息泄漏
RANDSTACK为内核栈基址加入一些随机偏移
STACKLEAK进出内核态时擦除内核栈

除此之外,在内核层还有很多防护措施,如:

  1. kptr_restrict:之前所有权限可以直接看kallsyms,该功能限制了权限。该值为0且perf_event_paranoid<=1(需支持PERF_EVENTS)则普通权限可以查看,有CAP_SYSLOG权限的可在它为01时查看,为2表示所有用户都不可查看
  2. dmesg_restrict:dmesg中可能会出现内核符号的地址,若该值为1则只有CAP_SYSLOG权限的用户可见

其他可参考:https://www.anquanke.com/post/id/238363

危险函数

类型 函数 说明
bcopy
输入 scanf 溢出,直到EOF/\x09/\x0A/\x0B/\x0C/\x0D/\x20结束并加\x00
sscanf 直到EOF/\x09/\x0A/\x0B/\x0C/\x0D/\x20/\x00结束并加\x00
gets/fgets 溢出,直到读到\x0A/EOF结束,\x00结束
read 指定长度/EOF结束
getchar 循环读时,依据实现
getline
格式化输出 *printf 缓冲区溢出和格式化串
setproctitle/syslog/err, verr, warn, vwarn... 格式化串
字符串 strcpy/strcat \x00截断
strncpy/strncat \x00和到达长度时截断,会补上\x00,不会溢出
内存

训练

上次小小找我要题玩,收集了一些训练网站,先扔这里:

PWN:pwnable.twpwnable.kreonew

RE:reversing.krcrackmes

WEB:juice-shopskfPortSwigger all lab

CRYPTO:cryptohack

ALL:rootmepicoctfw3challsbill’s security site

Other:gamesmashthestackvulnhubpenlaboverthewiremicrocorruptionbackdoorhacktheboxpwnable.xyzhacker101iowargameexploit.education

过程

不多说,可参考下图

image-20211210202709854

漏洞类型

缓冲区溢出

字符串:无界字符串复制(unbounded string copy)、差一错误(off-by-one error)、空结尾错误(null termination error)以及字符串截断(string truncation)

栈溢出

  1. 覆盖返回地址:直接劫持到PC
  2. 覆盖BP:返回前会从栈上恢复上一个BP,于是可以修改BP,在下次返回时会把BP给SP于是能转移栈
  3. 覆盖其他信息:随意咯
  4. 覆盖线程栈:当能在线程上进行大量的向上溢出或向下溢出时,绕过保护页即可操作另外的线程栈,可能会迷惑于保护页是4096字节,而栈缺页只能处理约32字节的异常,但其实它们没什么关系...

其他位置溢出

  1. 堆溢出:
  2. BSS/DATA段溢出:

堆漏洞

首先简单看看ptmalloc2的数据处理流程:

1.free:符合fastbin大小的直接扔进去,否则先尝试合并前面的,再尝试合并后面的,之后若失topchunk就酱,否则扔unsortedbin

2.malloc:满足fastbin先从这里取,没有且满足smallbin就从它里面取,还没有就去unsortedbin找(边找边往smallbinlargebin里扔,找到正和一致的停止,否则空了或到达阈值后,从small/large里找,切分的也扔unsortedbin,没有再去unsortedbin直到为空),没有尝试topchunk,若大小不够先malloc_consolidate清空fastbin再试,还不行就申请空间。

3.remalloc:缩小时若多的大于最小chunk则分割并释放,扩大时若后面为topchunk尝试直接扩,为满足大小的空闲chunk也将其解链并扩张,否则新分配一块区域。

#define PREV_INUSE 0x1
#define IS_MMAPPED 0x2 
#define NON_MAIN_ARENA 0x4

struct malloc_state
{
  __libc_lock_define (, mutex);   /* Serialize access.  */
  int flags; /* Flags (formerly in max_fast).  */

  /* Set if the fastbin chunks contain recently inserted free blocks.  */
  /* Note this is a bool but not all targets support atomics on booleans.  */
  int have_fastchunks;

  mfastbinptr fastbinsY[NFASTBINS];  /* Fastbins */
  mchunkptr top; /* Base of the topmost chunk -- not otherwise kept in a bin */
  mchunkptr last_remainder;  /* The remainder from the most recent split of a small request */
  mchunkptr bins[NBINS * 2 - 2];  /* Normal bins packed as described above */
  unsigned int binmap[BINMAPSIZE];  /* Bitmap of bins */
  struct malloc_state *next;  /* Linked list */

  /* Linked list for free arenas.  Access to this field is serialized
     by free_list_lock in arena.c.  */
  struct malloc_state *next_free;

  /* Number of threads attached to this arena.  0 if the arena is on
     the free list.  Access to this field is serialized by
     free_list_lock in arena.c.  */
  INTERNAL_SIZE_T attached_threads;

  /* Memory allocated from the system in this arena.  */
  INTERNAL_SIZE_T system_mem;
  INTERNAL_SIZE_T max_system_mem;
};

详细可看图。再来康康堆上常见的漏洞/攻击手法,它们大多都有🏠:

名称 说明
unlink 最原始的,在将一个chunk从双链中取出时,会由unlink宏修改链表中前后chunk的指针,只要让这个宏作用于某可控区域即可对任意地址写一个指针大小(此处有个副作用需要处理),如溢出到size的最低位即可认为相邻chunk未使用,再控制前一个的大小与fd/bk即可在下一个chunk被释放并触发合并操作时修改数据。
UAF 释放后使用,有两种情况,chunk被释放后再分配前被使用,此时可以修改链表指针搞事情,另一种是被再分配后之前的指针再使用,就会有类型混淆的问题,除了写,它也能泄漏指针。
Double Free 可转化为UAF,即在释放两次后,再分配一次,此时就处于UAF的状态了...
House of Prime 修改chunk的size为8就会在处理fastbins时下标为-1,操作到main_area的max_fast,它存储fastbin存放的最大chunk大小
House of Mind
House of Force 先通过某种方式修改top chunk的size(如溢出)为极大值,之后malloc一个小雨top chunk size的大数,造成整数溢出,让top chunk环绕到想要控制的位置,下次再从top chunk里分配内存则会分配到想要控制的区域。
House of Lore
House of Spirit 改写free的参数,令其指向想要控制的符合fastbin结构的区域,再次分配时则可以分配到该区域,如栈溢出改写free的参数,并在栈上构造fastbin chunk,则可以在下次分配时分配到栈上的空间。
House of Roman
House of Einherjar 类似于House of Force,但是它作用于临近top chunk的chunk,只需要将pre_inuse位去掉,并伪造合适的大小,就会在该chunk被释放时,三个chunk合并成一个topchunk,从而将top chunk前移到想要控制的区域。
House of Orange

这里把"The"去掉了...更多可见how2pwn...

不同的堆实现和防护方式不尽相同,这里只记录ptmalloc2默认设置下的防护与绕过方式:

表. 堆防护措施
利用点防护方式绕过方式
Fast Bin分配时只会检查分配的chunk size位是否正确fastbin是单链表很好利用,找或构造含size值的区域
在释放时只会检查当前释放的和上次释放的是否一致间隔释放
.........

可安装对应的源码方便调试,如ubuntu下用 apt install glibc-source 再调试时指定目录

格式化串

格式化串函数是变参函数,由函数内部根据格式化串确定参数个数,详见维基百科,它支持三类格式指示符,如%d这种能直接输出栈上的值,%s这种将栈上的值作为指针输出指向的数据,%n这种把输出的个数写入栈上对应处所指示的位置(注:x64需要多参数)。 另外它的还支持位置参数%X$d与宽度%[width]d可用于指定哪个位置的数据,输出数据的长度。 ​

在利用时,分两种情况:

  1. 格式化串在栈上:这时一般可以直接构造任意读写的payload
  2. 格式化串不在栈上:这时需要用间接的方式,格式化串操作的目标在栈上,则可在栈上寻找指向栈地址B的指针A,通过修改A来构造B,通过B来实现任意地址读写。实际的利用一般是找A->B->C的三个位置,这样A只需要改B的一字节即可控制C的所有位

整型溢出

这里包括宽度溢出与符号转换出错,这本身只是BUG,但是一般会造成其他漏洞,如转换为缓冲区溢出或数组越界(特别容易出现个数*单个长度这种溢出)。 ​

未初始化漏洞

在某条件下若一个变量未被初始化,则可以想办法控制它的值,如它在栈上则在其他条件或在其他函数下为其赋值,之后再进入漏洞函数触发未初始化逻辑,就可能造成其他漏洞...

另外,未初始化区域可读时存在信息泄漏,可利用它泄漏指针从而绕过地址随机化...

访问越界

能控制下标时,访问到其他位置去啦.... ​

空指针解引用

在用户空间一般是直接出错,正常情况虚拟地址空间的0地址不会也不能被映射,因此很多代码会把0认为不存在/NULL,解引用也是抛异常崩溃,若修改了mmap_min_addr的值则可映射0地址,此时的解引用可能就会造成其他问题了,这一版用于内核漏洞的提权,一些函数指针未赋值时会是0,若能映射0并修改就能劫持PC。

条件竞争

存在多种类型,如double fetch即对某资源校验与使用时分开读区,资源被共享访问时就能在通过校验后更改数据从而绕过校验...

利用技术

OffByOne

只修改部分位,来绕过某些限制,如修改地址时可能由于ASLR无法知道具体地址,但由于它只有高位可变,因此只写低位可绕过该限制... ​

Shellcode

写shellcode一般会有两个限制:

  1. shellcode长度
  2. 坏字符

Spray

就是在无法确定具体位置/偏移时,扔大量重复数据,增大命中正确位置的概率,如:

  1. 栈喷射(stack spray):一般是布置很多RET的Gadget
  2. 堆喷射(heap spray):一般是布置很多NOP让其滑行到最终的位置

具体布置什么需要根据实际情况来,这是一种思维。

返回导向编程(ROP)

利用技巧 说明
Ret2text 已有代码就能利用时,直接rop到此处
ret2shellcode 若有些区域有WX权限,则把shellcode写到那里面在rop过去
ret2syscall 若目标代码中有syscall/int80则可通过rop构造syscall,这一般在静态链接的程序中才会出现,此时可尝试onegadget一键生成
ret2libc libc中一定有syscall/int80,不过也有封装好的syscall,若PLT中有就可以直接用,若没有则由于libc是动态库位置可能不固定需要想办法获取,如通过已解析过的GOT泄露某地址(如__libc_start_main )再由相对偏移求得
ret2csu x64一般由寄存器传参,需要自己去找Gadget,不过__libc_csu_init是个大宝藏,它含能控制多个寄存器的Gadget
ret2xxx
ret2usr 内核态与用户态内存只存在单向隔离,由于用户态内存完全可控(任意地址读写),因此在用户态部署任意代码,再在内核态找到合适的Gadget返回到该处
ret2dir 由于SMAP/SMEP没法在特权模式下直接读/执行用户态数据/代码了,于是就想找片用户态与内核态都能映射了的区域,比如说直接映射区,在64位下物理内存基本都会映射进去,于是用户态分配大量(堆喷)的内存后,内核态用直接内存映射就能相应的读/执行啦。注,在v3.8.13之前有RWX,之后只有RW啦...另外还有个就是找别名,除了这里的堆喷,还可以利用proc计算,或有任意读时去根据关键模式串进行搜索。

类型混淆(Type Confusion)

通过某种方式,让一个结构被当作另一种结构,比如UAF的利用,或有时某对象根据某位定义类型,复写该位后转换为其他类型...

指针覆盖

  1. 最常见的的返回地址覆盖
  2. 函数指针覆盖:GOT,虚函数表(文件IO),__free_hook__malloc_hook__realloc_hook, __libc_atexit(需要有rw),_rtld_global里的函数指针,prepare_handler_fini_array(一般不可写 -z norelro)

内核利用

内核漏洞一般用于提权,这时和用户态的不同是它位于本地,此时已经有部分权限,因此可获取部分信息:

image.png

具体的,可以通过

修改cred

该结构用于访问控制,含主体权限(cred)与客体权限(real_cred),这里只关注前者,将里面的uid,主要是euid修改为0,若存在任意写则可直接为其赋值,若使用ROP则一般是用如下两个函数完成:

commit_creds(prepare_kernel_cred (0));

劫持vdso

能够任意读写时,可以找一片有写权限,root进程有执行权限,root进程会自动触发的区域,劫持它去反弹一个shell。嗯没错就是vdso(vsyscall也可),这片区域在之前是内核rw,用户态rx的,且它存在有两个目的,自适应系统调用指令与避免某些不敏感的系统调用,故它会被频繁访问,因此可劫持它,如把它里面的代码修改为shellcode,shellcode判断若是调用进程uid为0则弹reverse shell,则在root进程访问时会以它的身份执行这段shellcode。昂,现在这片区域内核不可写啦...

call_usermodehelper

该函数可以以root权限创建用户进程,故可让它执行编译的反弹shell,它的定义如下:

static inline int
call_usermodehelper(char *path, char **argv, char **envp, enum umh_wait wait)
{
    struct subprocess_info *info;
    gfp_t gfp_mask = (wait == UMH_NO_WAIT) ? GFP_ATOMIC : GFP_KERNEL;

    info = call_usermodehelper_setup(path, argv, envp, gfp_mask);
    if (info == NULL)
        return -ENOMEM;
    return call_usermodehelper_exec(info, wait);
}

它会被内联,不过不重要,跟进一步还可以去找到run_cmd,它只有一个参数即可执行文件路径,若有任意读写漏洞且调用这些函数的路径位于RW区域时,也可直接修改路径字符串,修改后调用函数即可。这里隆重介绍下prctl函数,它的参数很多,且调用了函数指针:

SYSCALL_DEFINE5(prctl, int, option, unsigned long, arg2, unsigned long, arg3, unsigned long, arg4, unsigned long arg5)
{
    error = security_task_prctl(option, arg2, arg3, arg4, arg5);
    ...
}
int security_task_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5)
{
    struct security_hook_list *hp;
    list_for_each_entry(hp, &security_hook_heads.task_prctl, list) {
        thisrc = hp->hook.task_prctl(option, arg2, arg3, arg4, arg5);
        ...
        }
    }
}

struct security_hook_list {
    struct list_head        list;
    struct list_head        *head;
    union security_list_options hook;
    char                *lsm;
} __randomize_layout;

因此可通过覆写它劫持控制流。

小技巧

一个小总结吧...

1.libc里的__environ指向栈上的环境变量数组

2.栈上会有__libc_start_main,它是libc里的地址

3.__libc_csu_init_dl_runtime_resolve等含万能gadget,不过前者是binary本身的,后者属于ld,因此后者需要泄漏出地址

4.无libc时,且需要libc时,要么完全靠猜加爆破(如猜为ubuntu系统的,根据流行度尝试),通过泄漏的库地址猜测(libcsearch),要么直接用Dynelf全部泄漏,若不需要libc,如只需使用它的某个导出函数,可用ret2

5.libc可尝试用one_gadget,挑出满足约束的一个即可

6.双链的首尾chunk会指向bins,属于main_area的chunk可泄漏main_area的地址,它位于libc的.data段,因此可泄漏libc地址,可从__malloc_trim定位到main_area的偏移,如small chunk的doublefree/uaf泄漏...

7.mmap/mprotect获取RWX的页,可布局shellcode

参考

[1] C和C++安全编码(原书第2版) -- Robert C.Seacord[著]; 卢涛[译]

[2] 程序员的自我修养: 链接、装载与库-- 俞甲子, 石凡, 潘爱民[著]

[3] CTF-Wiki

[4] 容器环境相关的内核漏洞缓解技术

[5] https://github.com/shellphish/how2heap

[6] New Reliable Android Kernel Root Exploitation Techniques -- INetCop Security dong-hoon you (x82)

[7] ret2dir: Rethinking Kernel Isolation -- Vasileios P. Kemerlis, Michalis Polychronakis, Angelos D. Keromytis

[8] Glibc 堆利用的若干方法 -- 裴中煜,张超,段海新

[9] House of IO - Heap Reuse -- Maxwell Dulin (ꓘ)