aarch64 kgdb显示进程名
作者:retme 发布时间:March 23, 2016 分类:Notes No Comments
print ((struct task_struct*)(*(size_t*)(((size_t)$sp & ~(0x4000 - 1)) + 0x10)))->comm
世界線の収束には、逆らえない
print ((struct task_struct*)(*(size_t*)(((size_t)$sp & ~(0x4000 - 1)) + 0x10)))->comm
by retme
这是一篇去年的笔记,1805是一个非常强力的提权漏洞. Google已经发了这个漏洞的advisory , 所以我现在可以把这个贴出来
CVE details
http://www.cvedetails.com/cve-details.php?t=1&cve_id=cve-2015-1805
commit which bring bug in
http://permalink.gmane.org/gmane.linux.kernel.commits.head/78321
fix
http://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/?id=637b58c2887e5e57850865839cc75f59184b23d1
0x1.综述
pipe.c
pipe_iov_copy_to/from_user在处理readv/writev时,
对当前已经拷贝的buffer长度统计可能与pipe_read/pipe_write不同步,
导致"iovec overrun",构造内存后可造成任意内核地址写
0x2.atomic copy的逻辑
static ssize_t pipe_read(struct kiocb *iocb, const struct iovec *_iov, unsigned long nr_segs, loff_t pos) { ...snip... for (;;) {//循环读取内存到iovec,直到读取缓冲区(iovecs)用尽 int bufs = pipe->nrbufs; if (bufs) { ...snip... if (chars > total_len) chars = total_len; error = ops->confirm(pipe, buf); if (error) { if (!ret) ret = error; break; } //检查iovecs中的每一个iov->base是否是一个可写的用户态内存页 //如果全部可写,那么atomic=1,接下来会直接使用__copy_to_user,不对目标地址再作检查 atomic = !iov_fault_in_pages_write(iov, chars); redo: addr = ops->map(pipe, buf, atomic); //对iovec进行copy,如果atomic=1,直接调用__copy_to_user,否则使用copy_to_user进行地址检查(access_ok) // !!!注意!!! //pipe_iov_copy_to_user会在每次copy完一个iov的时候对iov->len的长度进行更新 //如果copy到len=X,出错返回,那么已经copy成功的iov->len会被减去; //但是读取缓冲区的长度total_len,不会同步减少。 //进入redo逻辑后,pipe_iov_copy_to_user还会继续copy 长度为total_len的字节 //那么最终会向后越界copy len=X个长度的iov,aka "iovec overrun" error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic); ops->unmap(pipe, buf, addr); if (unlikely(error)) { /* * Just retry with the slow path if we failed. */ //如果本来是atomic=1,却copy失败了,那么使用atomic=0的方法重新copy //假设total_len=0x1010,第一次copy已经让iovecs总长度用掉了len=0x20 //那么redo开始的时候total_len还是0x1010,但是iovces的长度已经被减少了0x20 //所以最终pipe_iov_copy_to_user会越界读取iov,并向其中copy内存 if (atomic) { atomic = 0; goto redo; } if (!ret) ret = error; break; } ret += chars; buf->offset += chars; buf->len -= chars; /* Was it a packet buffer? Clean up and exit */ if (buf->flags & PIPE_BUF_FLAG_PACKET) { total_len = chars; buf->len = 0; } if (!buf->len) { buf->ops = NULL; ops->release(pipe, buf); curbuf = (curbuf + 1) & (pipe->buffers - 1); pipe->curbuf = curbuf; pipe->nrbufs = --bufs; do_wakeup = 1; } //走到这里才对total_len进行更新,也就是只有copy成功才会更新。redo时不会更新 total_len -= chars; if (!total_len) break; /* common path: read succeeded */ } if (bufs) /* More to do? */ continue;
0x3. 利用难点,造成redo
redo的逻辑不是那么好进的,如果要进入redo,需要做一个race condition on page table:
static ssize_t pipe_read(struct kiocb *iocb, const struct iovec *_iov, unsigned long nr_segs, loff_t pos) { ...snip... for (;;) { int bufs = pipe->nrbufs; if (bufs) { ...snip... if (chars > total_len) chars = total_len; error = ops->confirm(pipe, buf); if (error) { if (!ret) ret = error; break; } //loop_time = 1时,iov_fault_in_pages_write 这里必须执行成功 //也就是所有的iov->base指向的内存页有效 atomic = !iov_fault_in_pages_write(iov, chars); redo: addr = ops->map(pipe, buf, atomic); //loop_time = 1时,pipe_iov_copy_to_user 这里必须中途执行失败 //也就是其中某一个iov->base指向的内存页无效。 //那么进入redo //redo,也就是loop_time = 2时,pipe_iov_copy_to_user必须成功, //需要让无效的iov->base重新生效 // !!!注意!!! //不能在loop_time = 2的时候触发overrun, //否则overrun会使用copy_to_user而不是__copy_to_user,那么还是无法写内核地址 //解决办法是让total_len 稍稍大于 buf->len(0x1000) //这样loop_time = 2的时候能保证把一个合法的buf->len读完。 //并且会因为buf->len被读完,tolen_len却还有剩余,而进入第三个loop //然后在loop_time = 3的时候走atomic=1的路线,进行越界使用iov error = pipe_iov_copy_to_user(iov, addr + buf->offset, chars, atomic); ops->unmap(pipe, buf, addr); if (unlikely(error)) { /* * Just retry with the slow path if we failed. */ if (atomic) { atomic = 0; goto redo; } if (!ret) ret = error; break; } ret += chars; buf->offset += chars; buf->len -= chars; ...snip... total_len -= chars; if (!total_len) break; } if (bufs) /* More to do? */ //进入loop_time=3 continue; 假设readv使用iovec[512] = 8k,正好占据一个kmalloc-8192 iovec[0].len=0 iovec[1].len=0x20 iovec[2→511].len=8 race时间轴如下 thread1 thread2 map(iovec[0→511].base) loop_time = 1 iov_fault_in_pages_write return 1 unmap(iovec[2].base) pipe_iov_copy_to_user __copy_from_user return -EFAULT goto redo map(iovec[2].base) loop_time = 2(redo) pipe_iov_copy_to_user copy_from_user return 0 loop_time = 3(More to do) pipe_iov_copy_to_user __copy_to_user OVER_RUN 如果我加上每次loop iovec长度的标记,时间轴如下: total_len = 0x1010 chars=0x1000 iovec[0].len=0 iovec[1].len=0x20 iovec[2→511].len=8 thread1 thread2 map(iovec[0→511].base) loop_time = 1 iov_fault_in_pages_write return 1 total_len = 0x1010 chars=0x1000 iovec[0].len=0 iovec[1].len=0x20 iovec[2→511].len=8 unmap(iovec[2].base) pipe_iov_copy_to_user __copy_from_user return -EFAULT goto redo total_len = 0x1010 chars=0xff0 iovec[0].len=0 iovec[1].len=0 iovec[2→511].len=8 map(iovec[2].base) loop_time = 2(redo) pipe_iov_copy_to_user copy_from_user return 0 total_len = 0x1010 - 0xff0 =0x20 chars=0xff0 - 0xff0=0 iovec[0].len=0 iovec[1].len=0 iovec[2→511].len=0 loop_time = 3(More to do) total_len =0x20 chars=min(buf->len,total_len)=0x20 iovec[513] aka overun_iov[0] overun_iov[0].len=0 overun_iov[1].len=8 overun_iov[1].base=KERNEL_ADDR overun_iov[2->511].len=4096 pipe_iov_copy_to_user __copy_to_user OVER_RUN
overun_iov[1].base就是要写的内核地址,overun_iov就是8k内存页的下一页。
iov是在kmallc-8192的slab里面的,可以用sendmmsg去喷overun_iov
以上
讲道理的话你可以看这个,然而看过还是会有坑。因为他会给你讲一堆没卵用参数,但不告诉你这些参数怎么set
https://gcc.gnu.org/wiki/InstallingGCC
sudo apt-get install xxx #经常编译东西的机器应该不用装啥了,反正缺啥补啥
ln -s /usr/aarch64-linux-gnu /usr/aarch64-linux-gnu/usr
mkdir build
cd build
../configure --prefix=/ --target=aarch64-linux-gnu --enable-languages=c,c++ --disable-multilib --with-sysroot=/usr/aarch64-linux-gnu/
make
sodu make install
也可以修改这个脚本
https://gist.github.com/preshing/41d5c7248dea16238b60