2023Ciscn华东南分区赛遇到的一题 觉得题目的考点非常新颖和有趣 所以打算仔细研究一下
signal
题目用到了singal函数 该函数可以捕捉对应的信号 并且调用指定的信号处理函数
来看一下题目中所涉及到的两个single函数
signal(6, handler); |
第一个参数代表的是产生的信号 这里的6是SIGABRT 当进程非正常终止 调用abort函数的时候会触发
14是SIGALRM 如果程序设置的alarm函数超时 就会触发
下面是截取网上的大部分信号表格及其描述
Signal | Description |
---|---|
SIGABRT | 由调用abort函数产生,进程非正常退出 |
SIGALRM | 用alarm函数设置的timer超时或setitimer函数设置的interval timer超时 |
SIGBUS | 某种特定的硬件异常,通常由内存访问引起 |
SIGCANCEL | 由Solaris Thread Library内部使用,通常不会使用 |
SIGCHLD | 进程Terminate或Stop的时候,SIGCHLD会发送给它的父进程。缺省情况下该Signal会被忽略 |
SIGCONT | 当被stop的进程恢复运行的时候,自动发送 |
SIGEMT | 和实现相关的硬件异常 |
SIGFPE | 数学相关的异常,如被0除,浮点溢出,等等 |
SIGFREEZE | Solaris专用,Hiberate或者Suspended时候发送 |
SIGHUP | 发送给具有Terminal的Controlling Process,当terminal被disconnect时候发送 |
SIGILL | 非法指令异常 |
SIGINFO | BSD signal。由Status Key产生,通常是CTRL+T。发送给所有Foreground Group的进程 |
SIGINT | 由Interrupt Key产生,通常是CTRL+C或者DELETE。发送给所有ForeGround Group的进程 |
SIGIO | 异步IO事件 |
SIGIOT | 实现相关的硬件异常,一般对应SIGABRT |
SIGKILL | 无法处理和忽略。中止某个进程 |
SIGLWP | 由Solaris Thread Libray内部使用 |
SIGPIPE | 在reader中止之后写Pipe的时候发送 |
SIGPOLL | 当某个事件发送给Pollable Device的时候发送 |
SIGPROF | Setitimer指定的Profiling Interval Timer所产生 |
SIGPWR | 和系统相关。和UPS相关。 |
SIGQUIT | 输入Quit Key的时候(CTRL+\)发送给所有Foreground Group的进程 |
SIGSEGV | 非法内存访问 |
SIGSTKFLT | Linux专用,数学协处理器的栈异常 |
SIGSTOP | 中止进程。无法处理和忽略。 |
SIGSYS | 非法系统调用 |
SIGTERM | 请求中止进程,kill命令缺省发送 |
SIGTHAW | Solaris专用,从Suspend恢复时候发送 |
SIGTRAP | 实现相关的硬件异常。一般是调试异常 |
SIGTSTP | Suspend Key,一般是Ctrl+Z。发送给所有Foreground Group的进程 |
SIGTTIN | 当Background Group的进程尝试读取Terminal的时候发送 |
SIGTTOU | 当Background Group的进程尝试写Terminal的时候发送 |
SIGURG | 当out-of-band data接收的时候可能发送 |
SIGUSR1 | 用户自定义signal 1 |
SIGUSR2 | 用户自定义signal 2 |
SIGVTALRM | setitimer函数设置的Virtual Interval Timer超时的时候 |
SIGWAITING | Solaris Thread Library内部实现专用 |
SIGWINCH | 当Terminal的窗口大小改变的时候,发送给Foreground Group的所有进程 |
SIGXCPU | 当CPU时间限制超时的时候 |
SIGXFSZ | 进程超过文件大小限制 |
SIGXRES | Solaris专用,进程超过资源限制的时候发送 |
readlink
该函数用于获取当前程序的绝对路径
其一共有三个参数 第一个参数代表一个符号链接 第二个参数代表要存储的空间 第三个参数代表多少字节
而/proc/self/exe指向当前程序的绝对路径 所以原题中的
readlink("/proc/self/exe", v2, 0x4FuLL); |
就相当于获取当前程序的绝对路径 并且存储到v2内存空间中
execve
配合上面我们得到v2中存储的是当前程序的绝对路径
题目中的这一行 相当于就是重新运行了程序
execve(v2, *&buf[56], *&buf[48]); |
第二个和第三个参数用来规定新线程中的argv和envp参数
argv
简单来说 argv就是传入main函数的参数数组 其中argv[0]是程序名 本题中main函数一开就对argv的参数做了检测
v4 = a2[1]; |
如果为dbg 那么就可以进入sub_1b70函数 这个函数中存在任意写的机会
而如果程序的argv[1]参数不是run 就会终止程序 在远程环境中 默认就是以run为参数运行的程序 在我们本地复现的时候 需要注意手动加上参数
io = process(["./pwn",'run']) |
envp
envp是main函数的第三个参数 用来存储环境变量的指针 而有这样一个环境变量 LD_DEBUG=all
其原本是用来查看链接库的情况 从而诊断报错 但是其在输出一系列信息的同时 会顺带输出libc地址
解题思路
到此为之 题目的核心考点其实比较明了了 就是利用信号14的handler函数来重新启动程序 并且我们可以控制argv的参数 使其为dbg 我们就可以进入漏洞函数
为了实现这一点 我们需要触发abort 而本题开启了canary 并且在输入index或者是size时 存在了单字节的溢出 可以覆盖到canary的最后一个字节 那么当check触发时 调用__stack_chk_fail函数时 其内部会通过libc_message函数来输出报错信息 其中就会调用到abort函数
但是还有一个问题我们没有办法解决 虽然dgb函数中给了我们任意地址读和任意地址写的功能 但是开启了PIE保护的情况下 我们要如何获取libc地址来构造rop链呢
就是利用将程序的环境变量指针替换为指向LD_DEBUG=all字符串的指针 从而泄露libc
你可以注意到 在我们输入username后的下一个输入点 存在2字节的溢出 而后面的内容正好是envp环境变量
同时你可以发现sub_14b0函数 可以泄露出栈地址的后两个字节
而我们如果在一开始的username中输入LD_DEBUG=all字符串 那么此时就可以在对应的偏移中看到指向该字符串的指针
配合该偏移 我们可以在后续的溢出中将环境变量的指针替换成指向LD_DEBUG=all字符串 从而在程序重新运行的时候输出libc基址
exit调用链
void __noreturn sub_1B70() |
可以看到dbg函数虽然给了任意地址读和任意地址写 看起来好像我们可以往栈上写入rop链 随后劫持程序执行流 但是实际不会执行到leave和ret来结束栈帧 就会触发exit 所以我们这里只能通过布局tls结构体来控制exit函数
2.35以上的版本 exit函数内部调用了__run_exit_handlers函数
而该函数内部又调用了一个关键的函数 可以供我们实现任意函数调用
下面跟着我来分析一下如何构造tls结构体 以此来实现system函数调用
这里的rbx的值通常是不会变化的 其取决于rip索引 我们这里需要使得je跳转失败 从而才能进入任意函数调用的部分
这里的rbp取值是根据tls结构体低地址处 我们计算好偏移以后 利用dgb函数的任意写机会将其修改不为0后即可不跳转
这部分的内容是新版本加入的指针保护机制 通过循环右移11位后和tls结构体中的key异或后解密指针
当然我们拥有tls结构体任意写的权限话 很容易就能绕过 我们只需要使得rax一开始为0 设置fs:0x30处为system函数
异或过后rax的值就被设置成了system函数地址
而rdi寄存器的参数取决于rbp寄存器 rbp寄存器的值取决于我们一开始任意写的地址 fs_base-88处存放的值
计算好偏移 我们可以得到下面的payload 其可以实现call system 并且rdi参数为binsh
payload = p64(target+0x8)+p64(0)+p64(target+0x18)+b'/bin/sh\x00' |
但是你会发现system函数会卡在这里
这是因为我们破坏了tls结构体 我们把断点打在pthread_setcancelstate
看看rdx寄存器是怎么样赋值的
于是fs_base+0x10处的值不能被破坏 我们通过观察原本的tls结构会发现 其和fs_base+0x8处 是构成一个链表 由偏移0x10处指向偏移0x8处 而偏移0x8处指向自身
但是经过我实际测试 我们只需要保证fs:0x10处的地址+0x971后 是一个可读的内存地址即可 也就是说libc_addr-0x972也可以通过
那么最后的payload即为:
payload = p64(target+0x8)+p64(0)+p64(target+0x18)+b'/bin/sh\x00' |
完整exp
from pwn import* |