mitmproxy/mitmdump/mitmweb抓取websocket包并解码输出

 代理  mitmproxy/mitmdump/mitmweb抓取websocket包并解码输出已关闭评论
11月 292019
 

mitmproxy系列工具可以抓取websocket包并将它解码输出,需要怎么做呢?

 

mitmproxy/mitmdump/mitmweb命令行支持 -s  脚本文件.py,此脚本文件(addons)支持的Events事件见

https://docs.mitmproxy.org/stable/addons-events/#supported-events

 

mitmproxy 自带的addons实例文件可参考:https://github.com/mitmproxy/mitmproxy/tree/master/mitmproxy/addons

 

举例:(抓取并解码websocket的包并输出)

snifferWS.py

#!mitmdump -s

import mitmproxy.addonmanager

import mitmproxy.connections

import mitmproxy.http

import mitmproxy.log

import mitmproxy.tcp

import mitmproxy.websocket

import mitmproxy.proxy.protocol

class SniffWebSocket:

    def __init__(self):

        pass

    # Websocket lifecycle

    def websocket_handshake(self, flow: mitmproxy.http.HTTPFlow):

        “””

            Called when a client wants to establish a WebSocket connection. The

            WebSocket-specific headers can be manipulated to alter the

            handshake. The flow object is guaranteed to have a non-None request

            attribute.

        “””

    def websocket_start(self, flow: mitmproxy.websocket.WebSocketFlow):

        “””

            A websocket connection has commenced.

        “””

    def websocket_message(self, flow: mitmproxy.websocket.WebSocketFlow):

        “””

            Called when a WebSocket message is received from the client or

            server. The most recent message will be flow.messages[-1]. The

            message is user-modifiable. Currently there are two types of

            messages, corresponding to the BINARY and TEXT frame types.

        “””

        for flow_msg in flow.messages:

            packet = flow_msg.content

            from_client = flow_msg.from_client

            print(“[” + (“Sended” if from_client else “Reveived”) + “]: decode the packet here: %r…” % packet)

    def websocket_error(self, flow: mitmproxy.websocket.WebSocketFlow):

        “””

            A websocket connection has had an error.

        “””

        print(“websocket_error, %r” % flow)

    def websocket_end(self, flow: mitmproxy.websocket.WebSocketFlow):

        “””

            A websocket connection has ended.

        “””

addons = [

    SniffWebSocket()

]

 

使用命令行 mitmproxy或mitmdump或mitmweb 加 -s snifferWS.py(如: mitmweb -s snifferWS.py) 即可以看到websocket解码的输出了.

实际使用中我一般使用mitmweb -s snifferWS.py 方式,因为这样在web浏览器里可以窗口化显示http/https的详细输入输出,而在终端运行的窗口可以看到websocket的信息。

 

注意:

在使用中发现在我的ios 13.1的iphone机器上无法抓到websocket的解码内容,但android的机器可以,目前还没有找到好的方法,有知道的朋友可以分享下

SSH隧道技术—-端口转发,socket代理

 代理  SSH隧道技术—-端口转发,socket代理已关闭评论
3月 012017
 

本文的受众

如果你遇到了以下问题,那么你应该阅读这篇文章

  1. 我听说过这种技术,我对它很感兴趣
  2. 我想在家里访问我在公司的机器(写程序,查数据,下电影)。
  3. 公司为了防止我们用XX软件封锁了它的端口或者服务器地址。
  4. 公司不让我们上XX网站,限制了网址甚至IP。
  5. 公司不让我们看关于XX的信息,甚至花血本买了XX设备,能够对内容进行过滤。一看XX内容,链接就中断了。
  6. 我爸是搞电脑的,他在家里的路由器上动了手脚,我不能看XXX了。

带着这些问题,我们先从什么是ssh隧道开始。

 

什么是SSH隧道

首 先看下面这张图,我们所面临的大部分情况都和它类似。我们的电脑在左上角,通过公司带有防火墙功能的路由器接入互联网(当然可能还有交换机什么的在中间连 接着你和路由器,但是在我们的问题中交换机并不起到什么关键性的作用)。右下脚的部分是一个网站的服务器,它是我们公司防火墙策略的一部分,也就是说公司 不希望我们访问这个服务器。在右上角还有一台机器,它也是属于我们的。但是这台机器并不在我们公司里面,换句话说他不受到公司防火墙的限制。最后也是最重 要的一点是,我们能够在公司通过互联网直接访问这台机器。或者说这台位于公司防火墙外面的机器需要拥有一个独立的互联网IP,同时公司的防火墙规则不会屏 蔽这台机器,并且这台机器运行着一个OpenSSH服务器。

 

现 在,我们清楚地知道了自己所处的网络环境。并且不难理解我们在公司无法访问那个服务器的原因是:线路A-B-C上A-B之间的防火墙屏蔽了对那个服务器的 访问。与此同时,我们也很快注意到,线路A-B-D之间、D-C之间是不受阻碍的。相信你已经想到了,在A-B之间的防火墙不会屏蔽对机器d的访问。因此 我们可以通过机器d建立一个通道A-B-D-C,从而访问到机器c上的数据。

这条通道可以用很多技术来建立,这里我们仅仅介绍如何使用SSH服务器来建立这样一个通道-他被称为SSH隧道。

 

一、如何建立本地SSH隧道

在我们计划建立一个本地SSH隧道之前,我们必须清楚下面这些数据:

  1. 中间服务器d的IP地址
  2. 要访问服务器c的IP地址
  3. 要访问服务器c的端口

现在,我们把上面这张图变得具体一些,给这些机器加上IP地址。并且根据下面这张图列出我们的计划:

 

  1. 需要访问234.234.234.234的FTP服务,也就是端口21
  2. 中间服务器是123.123.123.123

现在我们使用下面这条命令来达成我们的目的

 

ssh -N -f -L 2121:234.234.234.234:21 123.123.123.123 ftp localhost:2121 # 现在访问本地2121端口,就能连接234.234.234.234的21端口了

 

这里我们用到了SSH客户端的三个参数,下面我们一一做出解释:

  • -N 告诉SSH客户端,这个连接不需要执行任何命令。仅仅做端口转发
  • -f 告诉SSH客户端在后台运行
  • -L 做本地映射端口,被冒号分割的三个部分含义分别是最后一个参数是我们用来建立隧道的中间机器的IP地址(IP: 123.123.123.123)

    • 需要使用的本地端口号
    • 需要访问的目标机器IP地址(IP: 234.234.234.234)
    • 需要访问的目标机器端口(端口: 21)

我们再重复一下-L参数的行为。-L X:Y:Z的含义是,将IP为Y的机器的Z端口通过中间服务器映射到本地机器的X端口。

在这条命令成功执行之后,我们已经具有绕过公司防火墙的能力,并且成功访问到了我们喜欢的一个FTP服务器了。

 

二、如何建立远程SSH隧道

通 过建立本地SSH隧道,我们成功地绕过防火墙开始下载FTP上的资源了。那么当我们在家里的时候想要察看下载进度怎么办呢?大多数公司的网络是通过路由器 接入互联网的,公司内部的机器不会直接与互联网连接,也就是不能通过互联网直接访问。通过线路D-B-A访问公司里的机器a便是不可能的。也许你已经注意 到了,虽然D-B-A这个方向的连接不通,但是A-B-D这个方向的连接是没有问题的。那么,我们能否利用一条已经连接好的A-B-D方向的连接来完成 D-B-A方向的访问呢?答案是肯定的,这就是远程SSH隧道的用途。

与本地SSH一样,我们在建立远程SSH隧道之前要清楚下面几个参数:

  • 需要访问内部机器的远程机器的IP地址(这里是123.123.123.123)
  • 需要让远程机器能访问的内部机器的IP地址(这里因为是想把本机映射出去,因此IP是127.0.0.1)
  • 需要让远程机器能访问的内部机器的端口号(端口:22)

在清楚了上面的参数后,我们使用下面的命令来建立一个远程SSH隧道,在192.168.0.100的主机上执行下面的命令:

ssh -Nf -R 2222:127.0.0.1:22 123.123.123.123

 

现在,在IP是123.123.123.123的机器上我们用下面的命令就可以登陆公司的IP是192.168.0.100的机器了。

ssh -p 2222 localhost

 

-N,-f 这两个参数我们已经在本地SSH隧道中介绍过了。我们现在重点说说参数-R。该参数的三个部分的含义分别是:

  • 远程机器使用的端口(2222)
  • 需要映射的内部机器的IP地址(127.0.0.1)
  • 需要映射的内部机器的端口(22)

例如:-R X:Y:Z 就是把我们内部的Y机器的Z端口映射到远程机器的X端口上。

 

三、建立SSH隧道的几个技巧

自动重连

隧道可能因为某些原因断开,例如:机器重启,长时间没有数据通信而被路由器切断等等。因此我们可以用程序控制隧道的重新连接,例如一个简单的循环或者使用 djb’s daemontools . 不管用哪种方法,重连时都应避免因输入密码而卡死程序。关于如何安全的避免输入密码的方法,请参考我的如何实现安全的免密码ssh登录 。这里请注意,如果通过其他程序控制隧道连接,应当避免将SSH客户端放到后台执行,也就是去掉-f参数。

保持长时间连接

有些路由器会把长时间没有通信的连接断开。SSH客户端的TCPKeepAlive选项可以避免这个问题的发生,默认情况下它是被开启的。如果它被关闭了,可以在ssh的命令上加上-o TCPKeepAlive=yes来开启。

另一种方法是,去掉-N参数,加入一个定期能产生输出的命令。例如: top或者vmstat。下面给出一个这种方法的例子:

ssh -R 2222:localhost:22 123.123.123.123 "vmstat 30" 

 

检查隧道状态

有些时候隧道会因为一些原因通信不畅而卡死,例如:由于传输数据量太大,被路由器带入stalled状态。这种时候,往往SSH客户端并不退出,而是卡死在 那里。一种应对方法是,使用SSH客户端的ServerAliveInterval和ServerAliveCountMax选项。 ServerAliveInterval会在隧道无通信后的一段设置好的时间后发送一个请求给服务器要求服务器响应。如果服务器在 ServerAliveCountMax次请求后都没能响应,那么SSH客户端就自动断开连接并退出,将控制权交给你的监控程序。这两个选项的设置方法分 别是在ssh时加入-o ServerAliveInterval=n和-o ServerAliveCountMax=m。其中n, m可以自行定义。

如何将端口绑定到外部地址上

使用上面的方法,映射的端口只能绑定在127.0.0.1这个接口上。也就是说,只能被本机自己访问到。如何才能让其他机器访问这个端口呢?我们可以把这个 映射的端口绑定在0.0.0.0的接口上,方法是加上参数-b 0.0.0.0。同时还需要打开SSH服务器端的一个选项-GatewayPorts。默认情况下它应当是被打开的。如果被关闭的话,可以在/etc /sshd_config中修改GatewayPorts no为GatewayPorts yes来打开它。

如何寻找中间服务器

如果你家里使用ADSL上网,多半你会比较幸运。一般的ADSL(例如 联通 的ADSL)都是有互联网地址的。你只需要在家里的路由器上一台装有OpenSSH server机器的SSH端口映射出去即可。同时一些提供SSH访问的虚拟主机也可以用于这一用途。例如: Hostmonser 或者 Dreamhost .

 

四、通过SSH隧道建立SOCKS服务器

如果我们需要借助一台中间服务器访问很多资源,一个个映射显然不是高明的办法(事实上,高明确实没有用这个方法)。幸好,SSH客户端为我们提供了通过SSH隧道建立SOCKS服务器的功能。

通过下面的命令我们可以建立一个通过123.123.123.123的SOCKS服务器。

ssh -N -f -D 1080 123.123.123 # 将端口绑定在127.0.0.1上 ssh -N -f -D 0.0.0.0:1080 123.123.123.123 # 将端口绑定在0.0.0.0上

 

通过SSH建立的SOCKS服务器使用的是SOCKS5协议,在为应用程序设置SOCKS代理的时候要特别注意。

 

五、总结

至 此,我们已经对如何利用SSH隧道有一个基本的认识了。现在,文章开始时的那些问题应该迎刃而解了吧。这里要特别说一下,由于SSH隧道也使用了SSH加 密协议,因此是不会被防火墙上的内容过滤器监控到的。也就是说一切在隧道中传输的数据都是被加密的。当然,离开隧道后的数据还是会保持自己原有的样子,没 有加密的数据还是会被后续的路由设备监控到。

 

 

PS:编者另注

在大多数情况下,我们建立ssh隧道的时候,往往是想通过一台公网的主机或者是大家都可以访问的主机做跳转机,来访问内部或者外部不能直接访问的机器。所以一般像这种情况下,请将跳转机中的ssh服务器中的GatewayPorts设为yes

1.建立本地的ssh隧道时,可以指定本地主机的地址,如下:

ssh -Nf -L 192.168.0.100:2121:234.234.234.234:21 123.123.123.123

  那么本地局域网的任何机器访问192.168.0.100:2121都会自动被映射到234.234.234.234:21

2.建立远程的ssh隧道时,可以指定公网的主机地址,不过一般情况是要访问内网的主机,所以这条命令应该在任何一台内网主机上执行,比如在192.168.0.102的主机上运行:

ssh -Nf -R 123.123.123.123:2222:192.168.0.100:22 123.123.123.123

 只要在局域网里192.168.0.102可以直接连接内网主机192.168.0.100,且192.168.0.102可以直接与公网主机123.123.123.123建立ssh连接。那么任何外网主机通过访问公网主机123.123.123.123:2222就会被连接到192.168.0.100:22,从而可以完成外网穿越NAT到内网的访问,而不需要在内网网关和路由器上做任何操作。

 

另外,你也可以通过Xshell、Putty等工具来实现linux主机与本地windows的端口转发、socket代理,还有一些像ProxyChains 工具的使用。


转自:http://www.cnblogs.com/fbwfbi/p/3702896.html

4月 022015
 

 转自:http://liuxiang.logdown.com/posts/192057-use-mitmproxy-to-monitor-http-requests

Web 开发者经常需要通过查看页面被打开之后所发送的请求来调试自己开发的程序,现代浏览器,包括 Firefox, Chrome, Safari 都自带了开发工具,可以帮助开发者监控 HTTP 请求。但是有时候这些工具仍不能满足我们的需求,例如在做某些古老的浏览器(IE)上的兼容性调试时,就需要一个专门用于监控 HTTP 请求的工具才行。最近发现了一个强大的 HTTP 请求监控工具 ———— mitmproxy Home Page

1. 介绍

mitmproxy 是用 Python 和 C 开发的一个中间人代理软件(man-in-the-middle proxy),它可以用来拦截、修改、重放和保存 HTTP/HTTPS 请求。

它提供了两个命令行工具:

  • mitmproxy 具备交互界面
  • mitmdump 不具备交互界面,类似 tcpdump

本文只介绍 mitmproxy。

mitmproxy 支持两种工作模式:

  • HTTP 代理模式,也就是 mitmproxy 作为一个 HTTP 代理运行,类似于 HTTPSpy。
  • 透明模式,mitmproxy 通过 iptables/pf 作为一个 TCP 层代理运行,好处是不需要修改 HTTP 客户端的配置。

本文只介绍 HTTP 代理模式。

2. 安装

使用 pip 进行安装:

pip install mitmproxy

考虑到包括我朝在内的四大文(读作:zhuān)明(读作:zhì)国家所特有的网络环境,pip可能会出现网络连接超时等错误,可以加上 –proxy 选项:

pip install mitmproxy --proxy=127.0.0.1:8087

我在 OS X Mavericks 上安装还会遇到一个编译错误,可以通过添加 ARCHFALGS 环境来忽略此错误:

ARCHFLAGS=-Wno-error=unused-command-line-argument-hard-error-in-future pip install mitmproxy --proxy=127.0.0.1:8087

3. HTTP 客户端配置

mitmproxy 安装完成之后,默认以 HTTP 代理模式工作,就需要 HTTP 客户端将代理配置修改为 mitmproxy 的地址。

启动 mitmproxy:

# 使用 -p 选项指定 HTTP 代理所监听的端口号,默认为 8080  mitmproxy -p 8080

以 Firefox + AutoProxy 插件为例,客户端的配置如下:

Snip20140402_13.png

4. 请求列表

在 Firefox 中打开一个网页,如:http://ruby-china.org/topics

Snip20140402_14.png

可以在 mitmproxy 中看到一个 HTTP 请求的列表:

Snip20140402_15.png

在 mitmproxy 中可以按 ? 进入到帮助信息界面,如需返回到请求列表界面则按 q。

在请求列表界面,黄色的箭头 >> 指示当前选择的请求,可以使用 vi 的快捷键 k, j 来移动箭头,PgUp、PgDown为上下翻页,此外空格键也可用来向下翻页。

如要清空列表,则按大写的 C。

5. 过滤请求列表

如果请求列表页面中的请求数量太多,则可以使用 mitmproxy 提供的过滤功能。

在请求列表界面按 l,此时列表界面的左下方会提示 Limit:,需要在此输出过滤表达式,过滤表达式的语法列在帮助信息界面,可以按 ? 进行查看。

例如,只显示所有的 JS 文件的请求,即请求的 URL 匹配 .js 的请求,则此处应该输入:~u .js

如需清除过滤,则同样按 l,然后删除过滤表达式即可。

6. 查看请求的具体信息

若要查看某个请求的具体信息,则在请求列表界面选中此请求后,按回车即可进入到查看请求的详细信息的界面:

Snip20140402_16.png

详细信息界面包括了 Request 和 Response 两个 Tab,可以按 tab 键切换,分别查看 Request 和 Response 的详细信息。

界面的左上方还显示了此次请求的发送时间。

mitmproxy 会使用合适的方式显示Request 和 Response 的 body 部分,例如对于压缩过的 JS ,mitmproxy 会解压缩后显示。如需要切换显示方式,可以在此界面按 m 来选择不同的显示方式。例如,对于包含了中文的 HTML 页面,如需要显示中文,可以使用 urlencoded 模式。

在详细信息界面可以按 / 对 body 部分进行搜索。

7. 拦截请求

mitmproxy 支持对请求进行拦截,拦截后还可以修改 Request 或 Response 的内容。

在请求列表界面按 i,在左下角会显示 Intercept filter:,要求输入过滤表达式,用于指示拦截哪些请求,此处的过滤表达式的语法同请求列表过滤表达式相同。

例如,如要拦截所有的 JS 文件的请求,则在此处输入 ~u .js。

再次访问 http://ruby-china.org/topics 页面,在 mitmproxy 的请求列表界面中可以看到对 JS 的请求都显示为橙色,表示这些请求被拦截了。

请求被拦截后,可以进入到该请求的详细信息界面,然后按 e,对请求进入编辑,编辑完成后按 ESC 退出编辑界面。按 a 放行该请求(也可以按大写的 A来放行所有被拦截的请求),请求被放行后,Server 收到的将是被编辑过的 Request。

当 Server 的 Response 返回到 mitmproxy 时,将再次被拦截,此时在详细信息界面按 e 可以对 Response 进行编辑,编辑完成后,同样按 ESC 退出编辑,同样按 a 或 A 放行 Response,客户端收到的 Response 将是被编辑过的 Response。

关于 mitmproxy 的更多用法,请参照其官网的文档:http://mitmproxy.org/doc/index.html

4月 012015
 

关于代理的文章,分享下,转自:http://z00w00.blog.51cto.com/515114/1031287

一、正向代理(Forward Proxy)

   一般情况下,如果没有特别说明,代理技术默认说的是正向代理技术。关于正向代理的概念如下:
正向代理(forward)是一个位于客户端【用户A】和原始服务器(origin server)【服务器B】之间的服务器【代理服务器Z】,为了从原始服务器取得内容,用户A向代理服务器Z发送一个请求并指定目标(服务器B),然后代理服务器Z向服务器B转交请求并将获得的内容返回给客户端。客户端必须要进行一些特别的设置才能使用正向代理。如下图1.1

从上面的概念中,我们看出,文中所谓的正向代理就是代理服务器替代访问方【用户A】去访问目标服务器【服务器B
这就是正向代理的意义所在。而为什么要用代理服务器去代替访问方【用户A】去访问服务器B呢?这就要从代理服务器使用的意义说起。
使用正向代理服务器作用主要有以下几点:

1、访问本无法访问的服务器B,如下图1.2


 

我们抛除复杂的网络路由情节来看图1.2,假设图中路由器从左到右命名为R1,R2

假设最初用户A要访问服务器B需要经过R1R2路由器这样一个路由节点,如果路由器R1或者路由器R2发生故障,那么就无法访问服务器B了。但是如果用户A让代理服务器Z去代替自己访问服务器B,由于代理服务器Z没有在路由器R1R2节点中,而是通过其它的路由节点访问服务器B,那么用户A就可以得到服务器B的数据了。

现实中的例子就是“翻墙”。不过自从VPN技术被广泛应用外,“翻墙”不但使用了传统的正向代理技术,有的还使用了VPN技术

 

2、加速访问服务器B

假设用户A到服务器B,经过R1路由器和R2路由器,而R1R2路由器的链路是一个低带宽链路。而用户A到代理服务器Z,从代理服务器Z到服务器B都是高带宽链路。那么很显然就可以加速访问服务器B了。
 

3Cache作用



4、客户端访问授权


这方面的内容现今使用的还是比较多的,例如一些公司采用ISA SERVER做为正向代理服务器来授权用户是否有权限访问互联网,挼下图1.3



(图1.3

1.3防火墙作为网关,用来过滤外网对其的访问。假设用户A和用户B都设置了代理服务器,用户A允许访问互联网,而用户B不允许访问互联网(这个在代理服务器Z上做限制)这样用户A因为授权,可以通过代理服务器访问到服务器B,而用户B因为没有被代理服务器Z授权,所以访问服务器B时,数据包会被直接丢弃。

5、隐藏访问者的行踪

如下图1.4 我们可以看出服务器B并不知道访问自己的实际是用户A,因为代理服务器Z代替用户A去直接与服务器B进行交互。如果代理服务器Z被用户A完全控制(或不完全控制),会惯以“肉鸡”术语称呼。



(图1.4)

 

二、反向代理(reverse proxy

反向代理正好与正向代理相反,对于客户端而言代理服务器就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原始服务器)转交请求,并将获得的内容返回给客户端。

使用反向代理服务器的作用如下:

1、  保护和隐藏原始资源服务器

如下图2.1


(图2.1
用户A始终认为它访问的是原始服务器B而不是代理服务器Z,但实用际上反向代理服务器接受用户A的应答,从原始资源服务器B中取得用户A的需求资源,然后发送给用户A。由于防火墙的作用,只允许代理服务器Z访问原始资源服务器B。尽管在这个虚拟的环境下,防火墙和反向代理的共同作用保护了原始资源服务器B,但用户A并不知情。


2、  负载均衡

 

   当反向代理服务器不止一个的时候,我们甚至可以把它们做成集群,当更多的用户访问资源服务器B的时候,让不同的代理服务器Zx)去应答不同的用户,然后发送不同用户需要的资源。
当然反向代理服务器像正向代理服务器一样拥有CACHE的作用,它可以缓存原始资源服务器B的资源,而不是每次都要向原始资源服务器B请求数据,特别是一些静态的数据,比如图片和文件,如果这些反向代理服务器能够做到和用户X来自同一个网络,那么用户X访问反向代理服务器X,就会得到很高质量的速度。这正是CDN技术的核心。如下图2.3
(图2.3
 
我们并不是讲解CDN,所以去掉了CDN最关键的核心技术智能DNS。只是展示CDN技术实际上利用的正是反向代理原理这块。
反向代理结论与正向代理正好相反,对于客户端而言它就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理的命名空间(name-space)中的内容发送普通请求,接着反向代理将判断向何处(原始服务器)转交请求,并将获得的内容返回给客户端,就像这些内容原本就是它自己的一样。
基本上,网上做正反向代理的程序很多,能做正向代理的软件大部分也可以做反向代理。开源软件中最流行的就是squid,既可以做正向代理,也有很多人用来做反向代理的前端服务器。另外MS ISA也可以用来在WINDOWS平台下做正向代理。反向代理中最主要的实践就是WEB服务,近些年来最火的就是Nginx了。网上有人说NGINX不能做正向代理,其实是不对的。NGINX也可以做正向代理,不过用的人比较少了。
 


三、透明代理

    如果把正向代理、反向代理和透明代理按照人类血缘关系来划分的话。那么正向代理和透明代理是很明显堂亲关系,而正向代理和反向代理就是表亲关系了 

透明代理的意思是客户端根本不需要知道有代理服务器的存在,它改编你的request fields(报文),并会传送真实IP。注意,加密的透明代理则是属于匿名代理,意思是不用设置使用代理了。

透明代理实践的例子就是时下很多公司使用的行为管理软件。如下图3.1

(图3.1