《Python 黑帽子》学习笔记 - 命令行选项和参数处理 - Day 4

在学习书中 netcat 代码的时候,发现其命令行选项和参数的处理存在一些小问题,由于调用 getopt 模块的 getopt() 函数时参数设置不当,会引起代码执行时获取不到参数值或引发异常。该问题在 Python2.7 和 Python3.6 版本都存在。虽然对脚本功能来说无关紧要,但是其代码会对初学者造成一定困扰,所以我简单调试了下代码,修改成正确的方式,也是对 Python 命令行选项和参数处理的一次学习。

问题代码

netcat 脚本通过调用 getopt 模块的 getopt() 函数对命令行选项和参数进行解析,并在 usage() 函数里说明了脚本功能和参数传递。

原代码如下:

def usage ():
    ......
    print "-l --listen              - listen on [host]:[port] for incoming connections"
    print "-e --execute=file_to_run - execute the given file upon receiving a connection"
    print "-c --command             - initialize a command shell"
    print "-u --upload=destination  - upon receiving a connection upload a file and write to [destination]"

    print "bhpnet.py -t 192.168.0.1 -p 5555 -l -u=C:\\target.exe"
    print "bhpnet.py -t 192.168.0.1 -p 5555 -l -e=\"cat /etc/passwd\""
    .....
    sys.exit(0)
    ......
    # read the commandline options    
    try:
        opts, args = getopt.getopt(sys.argv[1:],"hle:t:p:c:u:",["help","listen","execute","target","port","command","upload"])
    except getopt.GetoptError as err:
        print str(err)
        usage()

    for o,a in opts:
        if o in ("-h", "--help"):
            usage()
        elif o in ("-l", "--listen"):
            listen = True

    ......

为了更清楚看到参数解析的结果,我们加上一条语句,打印 getopt() 函数的返回值。

print "opts = %s, args = %s" % (opts, args)

脚本运行时,传入类似 -u=C:\\target.exe 的命令行参数,其命令行选项对应的参数会包括 = ,而所需参数只是 = 后面的数据。

python.exe bhpnet.py -u=C:\\target.exe

opts = [('-u', '=C:\\\\target.exe')], args = []

传入类似 --execute=file_to_run 的命令行参数,会引发 GetoptError 异常,错误是 option --execute must not have an argument

python.exe bhpnet.py --execute=file_to_run

option --execute must not have an argument

上述异常的原因,我们在认识 getopt() 函数后再进行解释。

getopt 模块

Python 提供了一个 getopt 模块,其 getopt() 函数用于解析命令行选项和参数。一般都是在 main() 函数开始的地方调用 getopt() ,解析脚本运行时传入的命令行参数后,根据参数值再转到相应的功能函数去执行。

命令行参数列表是通过 sys 模块的 argv 属性获取,即 sys.argv 。

  • sys.argv[0] 是程序名称,sys.argv[1:] 是参数列表。
  • len(sys.argv) 是命令行参数的数量,包括程序名在内。

看下 getopt() 函数的说明文档:

def getopt(args, shortopts, longopts = []):
    """getopt(args, options[, long_options]) -> opts, args

    Parses command line options and parameter list.  args is the
    argument list to be parsed, without the leading reference to the
    running program.  Typically, this means "sys.argv[1:]".  shortopts
    is the string of option letters that the script wants to
    recognize, with options that require an argument followed by a
    colon (i.e., the same format that Unix getopt() uses).  If
    specified, longopts is a list of strings with the names of the
    long options which should be supported.  The leading '--'
    characters should not be included in the option name.  Options
    which require an argument should be followed by an equal sign
    ('=').

    The return value consists of two elements: the first is a list of
    (option, value) pairs; the second is the list of program arguments
    left after the option list was stripped (this is a trailing slice
    of the first argument).  Each option-and-value pair returned has
    the option as its first element, prefixed with a hyphen (e.g.,
    '-x'), and the option argument as its second element, or an empty
    string if the option has no argument.  The options occur in the
    list in the same order in which they were found, thus allowing
    multiple occurrences.  Long and short options may be mixed.

    """

脚本调用 getopt() 函数,需要三个参数,getopt(args, shortopts, longopts = []).

  • 第一个 args 参数,是需要解析的命令行选项和参数列表,即 “sys.argv[1:]”.
  • 第二个 shortopts 参数,是一个包含命令行短选项(一个字符表示一个选项)的字符串,如 "hle:t:p:cu:", 如果某个选项需要参数的话,该选项字符后面要加上冒号 :. 传递参数的时候,要用 - 作为前缀,加上相应选项字符,该选项如果有参数的话,直接或空格后跟在后面。如 -h, 即命令行选项为 h, 如 -p80-p 80, 即命令行选项为 p, 参数为 80.
  • 第三个 longopts = [] 参数,是一个包含命令行长选项(一个字符串表示一个选项)的字符串列表,如 ["help", "listen", "execute=", "target=", "port=", "command=", "upload="], 后面有 = 的字符串表示该选项需要参数,列表里字符串顺序和第二个参数里字符顺序要一致,不然会容易混淆。传递参数的时候,要用 -- 作为前缀,加上相应选项字符串,该选项如果有参数的话,用 = 或空格加上参数跟在后面。如 --help, 即命令行选项为 help, 如 --port=80--port 80, 即命令行选项为 port, 参数为 80.

getopt() 函数的返回值由两个元素组成,第一个元素是 (option, value), 即命令行选项和对应参数的键值对,是一个元组。第二个元素是参数列表,这里的参数列表不包括第二个和第三个参数里的选项和相应参数值。是选项及其值之后剩下的参数列表。如:

python args_test.py -h --listen -p 90 --upload=c:\\a.exe aaa 111

opts = [('-h', ''), ('--listen', ''), ('-p', '90'), ('--upload', 'c:\\\\a.exe')], args = ['aaa', '111']

args = ['aaa', '111'] 即为 getopt() 函数返回值的第二个元素。如果传递给脚本的选项及其值,与 getopt() 函数设置的选项不匹配,后面所有传递的值都将成为第二个元素的一部分。如:

python args_test.py -h hh  --listen -p 90 --upload=c:\\a.exe aaa 111

opts = [('-h', '')], args = ['hh', '--listen', '-p', '90', '--upload=c:\\\\a.exe', 'aaa', '111']

另外,getopt 模块提供了一个 GetoptError 异常处理,当传入给脚本的命令行参数有无法识别的选项,或者当需要参数的选项没有参数时,会引发异常。异常的参数是一个字符串,指示错误的原因。

getopt 模块的主体即 getopt() 函数。

def getopt(args, shortopts, longopts = []):

    opts = []
    if type(longopts) == type(""):
        longopts = [longopts]
    else:
        longopts = list(longopts)
    while args and args[0].startswith('-') and args[0] != '-':
        if args[0] == '--':
            args = args[1:]
            break
        if args[0].startswith('--'):
            opts, args = do_longs(opts, args[0][2:], longopts, args[1:])
        else:
            opts, args = do_shorts(opts, args[0][1:], shortopts, args[1:])

    return opts, args

其他函数包括:

  • short_has_arg(opt, shortopts) 判断传入的命令行参数是否在短选项里。
  • do_shorts(opts, optstring, shortopts, args) 处理传入的短选项及参数。
  • long_has_args(opt, longopts) 判断传入的命令行参数是否在长选项里。
  • do_longs(opts, opt, longopts, args) 处理传入的长选项及参数
  • class GetoptError(Exception) 异常处理类

总结

回到原书代码存在的问题,传入命令行短选项时用了 =, 调用 getopt() 函数时命令行长选项参数设置不规范。只要修改 getopt() 函数的第三个参数,如下:["help", "listen", "execute=", "target=", "port=", "command=", "upload="], 并把 usage() 函数里的示例描述准确。

运行脚本时,一定要注意传入正确的参数,写 usage() 函数的时候,说明使用方法和示例的时候要准确。不过,在写渗透测试脚本的时候,最好不写 usage(), ,甚至不要出现任何字符串,不然很容易被检测到。

写了几篇笔记,记录的都是黑帽子之外的一些内容,好像不是在读《Python 黑帽子》一样,好在还是用 Python. 下步要聚焦信息安全的内容,重点学习渗透测试的思路并实践,学会用 Python 实现一些重要的脚本。

苹果春季发布会马上要来了,MacBook 的草在心里也种了很久了,如果自己能持续学习和记录,就血本撸一台,给老婆用。

你可能感兴趣的:(Python黑帽子,命令行选项和参数,getopt,学习笔记)