这学期选了软件安全课,做题的过程中获得了不少新姿♂势,在这里记录一下。

虽然做题搞的我每个周一晚上都熬夜QAQ

ptrace以及shellcode

有一道题是这样的,一个进程通过ptrace(PTRACE_TRACEME, 0, 1, 0)来检查是否有调试器,没有源代码,只有二进制文件;程序属于特别的组,要求以这个组的身份调用一个外部程序。

自己ptrace自己意味着用gdb会被发现。我选择LD_PRELOAD来hook掉ptrace。具体方法是写一个假冒的ptrace函数:

只要永远返回0(即成功)就可以了。然后-shared参数编译成动态库。

进入gdb之后,set environment LD_PRELOAD=./fake_ptrace.so就可以了。然后找栈的偏移,溢出。

注意在shellcode中需要调用setregid(getegid(), getegid())来把进程的egid变成我们需要的组id。有关于syscall的代号可以查询linux内核源文件。注意要选择合适自己所在的平台以及内核版本。

有一点要注意的是,setuid/setgid的程序,是无视LD_PRELOAD的,所以在gdb外我们没法撸掉ptrace。

最要命的是在gdb中能够用shellcode调起外部程序,出了gdb就变成了类似

[1]+  Stopped                 ./shell3

这样的输出。想来想去应该是ptrace的问题。看了一下文档才知道,当一个进程被ptrace之后,每次它调用exec(或者system什么的)就会收到一个SIGTRAP让它停止运行(stopped状态)。这时候父进程可以通过ptrace(PTRACE_CONT, child_pid , 0, 0)来恢复其运行。当然这个设计的初衷是让父进程(tracer)可以对被调试进程(tracee)做修改或者审查寄存器等等。

这样我们接下来要做的就是启动这个进程,然后给它发PTRACE_CONT信号(注意,和SIGCONT并不一样)。

 

具体的,我们可以通过如下代码实现:

注意execl不能换成system,因为system启动的进程,parent pid并不是当前进程,而是1,这将导致父进程收不到提醒信号以及无法发送PTRACE_CONT。

exec及各种变体在调用程序时不会改变pid,可以看做是将这个新的程序的映像加载到了内存里。system实际上是fork,exec,wait的一个打包,所以生出了新的进程。

wait(&status);表明父进程会阻塞直到接收到了任意信号。此时fork出来的子进程已经在运行/path/to/vulnerabled的代码,而由于我们的栈溢出,这时运行到了shellcode里的exec,收到一个SIGTRAP而停止;父进程收到了一个信号表明子进程停止了,从wait中出来,运行到ptrace(PTRACE_CONT, pid , 0, 0),恢复子进程。

 

有一个不解的问题是,这种情况下不能在shellcode中启动一个shell,而只能启动一个立即退出的程序(比如ls之类)。难道是因为没有tty?

参考:

  1. ptrace() Tutorial | Cvet’s Blog
  2. Tracing tricks with ptrace

x64栈溢出

除了地址长度有了不同以外,和x86相比,我们还需要注意寄存器的字长。进入shellcode之后,如果栈空间充裕,尽量先将所有通用寄存器(用xor rax, rax的形式)清零;以防给eax或者al赋了值,高位还不是0。

 

 

其他的好像都是基本的套路。欢迎交♂流。