pwn题命令行解题脚本

注意
本文最后更新于 2021-04-02,文中内容可能已过时。

这是专门为本地调试与远程答题准备的脚本,依靠命令行参数进行控制。 本脚本支持的功能有:

  • 本地调试
    • 开启tmux调试
    • 设置gdb断点,支持函数名断点地址断点文件名:行号断点$rebase(0x00)断点
    • 设置gdb script,可以设置任何内置的gdb命令
  • 远程答题
    • 默认支持buuctf,只需要指定远程port
    • 显式指定远程ipport
  • 使用的装饰器函数、偏函数
    • 在执行函数前、后线程休眠指定秒数
    • 记录函数执行日志
    • int16,字符串与整数转化
  • 可自由定制
    • 依托click模块进行自由定制

脚本内容

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
#!/usr/bin/python3
# -*- encoding: utf-8 -*-

# @File    : do_pwn_template.py
# @Time    : 2021/04/02 21:15:43
# @Author  : Roderick Chan
# @Email   : ch22166@163.com
# @Desc    : pwn题本地调试、远程攻击脚本

'''
==========================================================================================
本脚本为pwn题所编写,利用click模块配置命令行参数,
能方便地进行本地调试和远程解题。
本地命令示例:
    python3 exp.py filename --tmux 1 --gdb-breakpoint 0x804802a --gdb-breakpoint printf
    python3 exp.py filename -t 1 -gb 0x804802a -gb printf
    python3 exp.py filename -t 1 -gs "x /12gx \$rebase(0x202080)" -sf 0 -pl "warn"
    即可开始本地调试,并且会断在地址或函数处。先启动tmux后,--tmux才会有效。

远程命令示例:
    python3 exp.py filename -i 127.0.0.1 -p 22164
    python3 exp.py filename -p 22164
    可以连接指定的IP和端口。目前在刷buuctf上的题,所以填了默认ip,只指定端口即可。

==========================================================================================
'''

from pwn import *
from LibcSearcher import LibcSearcher
import click
import sys
import os
import time
import functools

print(__doc__)

FILENAME = '#' # 要执行的文件名
DEBUG = 1 # 是否为调试模式
TMUX = 0 # 是否开启TMUX
GDB_BREAKPOINT = None # 当tmux开启的时候,断点的设置
GDB_SCRIPT = None # 当tmux开启的时候, gdb_script的设置,可以是任意有效的语句
IP = None # 远程连接的IP
PORT = None # 远程连接的端口
LOCAL_LOG = 1 # 本地LOG是否开启
PWN_LOG_LEVEL = 'debug' # pwntools的log级别设置
STOP_FUNCTION = 1 # STOP方法是否开启

CONTEXT_SETTINGS = dict(help_option_names=['-h', '--help'])

@click.command(context_settings=CONTEXT_SETTINGS, short_help='Do pwn!')
@click.argument('filename', nargs=1, type=str, required=0, default=None)
@click.option('-d', '--debug', default=True, type=bool, nargs=1, help='Excute program at local env or remote env. Default value: True.')
@click.option('-t', '--tmux', default=False, type=bool, nargs=1, help='Excute program at tmux or not. Default value: False.')
@click.option('-gb', '--gdb-breakpoint', default=[], type=str, multiple=True, help="Set a gdb breakpoint while tmux is enabled, is a hex address or '\$rebase' addr or a function name. Multiple setting supported. Default value:'[]'")
@click.option('-gs', '--gdb-script', default=None, type=str, help="Set a gdb script while tmux is enabled, the script will be passed to gdb and use '\\n' or ';' to split lines. Default value:None")
@click.option('-i', '--ip', default=None, type=str, nargs=1, help='The remote ip addr. Default value: None.')
@click.option('-p', '--port', default=None, type=int, nargs=1, help='The remote port. Default value: None.')
@click.option('-ll', '--local-log', default=True, type=bool, nargs=1, help='Set local log enabled or not. Default value: True.')
@click.option('-pl', '--pwn-log', type=click.Choice(['debug', 'info', 'warn', 'error', 'notset']), nargs=1, default='debug', help='Set pwntools log level. Default value: debug.')
@click.option('-sf', '--stop-function', default=True, type=bool, nargs=1, help='Set stop function enabled or not. Default value: True.')
def parse_command_args(filename, debug, tmux, gdb_breakpoint, gdb_script,
                       ip, port, local_log, pwn_log, stop_function):
    '''FILENAME: The filename of current directory to pwn'''
    global FILENAME, DEBUG, TMUX, GDB_BREAKPOINT, GDB_SCRIPT, IP, PORT, LOCAL_LOG, PWN_LOG_LEVEL, STOP_FUNCTION
    # assign
    FILENAME = filename
    DEBUG = debug
    TMUX = tmux
    GDB_BREAKPOINT = gdb_breakpoint
    GDB_SCRIPT = gdb_script
    IP = ip
    PORT = port
    LOCAL_LOG = local_log
    PWN_LOG_LEVEL = pwn_log
    STOP_FUNCTION = stop_function

    # change
    if PORT: # 远程下这些是需要关闭的
        DEBUG = 0
        TMUX = 0
        STOP_FUNCTION = 0
        GDB_BREAKPOINT = None
        GDB_SCRIPT = None
        if IP is None:
            IP = 'node3.buuoj.cn'
    
    if DEBUG:
        IP = None
        PORT = None
    
    # assert
    assert not (FILENAME is None and PORT is None), 'para error'
    assert not (FILENAME is None and DEBUG == 1), 'para error'
    assert not (PORT is not None and DEBUG == 1), 'para error'
    assert not (DEBUG == 0 and TMUX == 1), 'para error'
    
    # print
    click.echo('=' * 50)
    click.echo(' [+] Args info:\n')
    if FILENAME:
        click.echo('  filename: %s' % FILENAME)
    click.echo('  debug enabled: %d' % DEBUG)
    click.echo('  tmux enabled: %d' % TMUX)
    if GDB_BREAKPOINT:
        click.echo('  gdb breakpoint: {}'.format(GDB_BREAKPOINT))
	if GDB_SCRIPT:
		click.echo("  gdb script: {}".format(GDB_SCRIPT))
    if IP:
        click.echo('  remote ip: %s' % IP)
    if PORT:
        click.echo('  remote port: %d' % PORT)
    click.echo('  local log enabled: %d' % LOCAL_LOG)
    click.echo('  pwn log_level: %s' % PWN_LOG_LEVEL)
    click.echo('  stop function enabled: %d' % STOP_FUNCTION)
    click.echo('=' * 50)
    

parse_command_args.main(standalone_mode=False)

# 退出条件,只要参数有 -h 或 --help就退出
if len(sys.argv) > 1:
    for arg in sys.argv:
        if '-h' == arg or '--help' == arg:
            sys.exit(0)

if DEBUG:
    io = process('{}'.format(FILENAME))
else:
    io = remote(IP, PORT)

if TMUX:
    context.update(terminal=['tmux', 'splitw', '-h'])
    tmp_all_gdb = ""
    if GDB_BREAKPOINT is not None or len(GDB_BREAKPOINT) > 0:
        # 解析每一条gdb-breakpoint
        for gb in GDB_BREAKPOINT:
            if gb.startswith('0x') or gb.startswith('$rebase('):
                tmp_all_gdb += "b *{}\n".format(gb) # 带上*
            else: # 传入函数
                tmp_all_gdb += "b {}\n".format(gb) # 不带*
    if GDB_SCRIPT is not None:
        tmp_all_gdb += GDB_SCRIPT.replace("\\n", "\n").replace(";", "\n") + "\n"
    tmp_all_gdb += "c\n"
    gdb.attach(io, gdbscript=tmp_all_gdb)


if FILENAME:
    cur_elf = ELF('{}'.format(FILENAME))
    print('[+] libc used ===> {}'.format(cur_elf.libc))



def LOG_ADDR(addr_name:str, addr:int):
    if LOCAL_LOG:
        log.success("{} ===> {}".format(addr_name, hex(addr)))
    else:
        pass


def LOG_ADDR_EX(addr_name:str):
    '''
    存储地址的变量名,字符串
    如:a = 0xdeadbeef 
    调用: LOG_ADDR_EX('a')
    
    '''
    if LOCAL_LOG:
        # 利用eval函数, 首先检索一下
        if addr_name in globals() or addr_name in vars():
            tmp_var = eval(addr_name)
            log.success("{} ===> {}".format(addr_name, hex(tmp_var)))
        else:
            log.warn("No variable named: '" + addr_name + "'")
    else:
        pass
    


def STOP():
    if not STOP_FUNCTION:
        return
    print("stop...{}  {}".format(sys._getframe().f_lineno, proc.pidof(io)))
    pause()


############### 定义一些偏函数 ###################

int16 = functools.partial(int, base=16)

#################### END ########################


############### 定义一些装饰器函数 ###############

def time_count(func):
    '''
    装饰器:统计函数运行时间
    '''
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        print('=' * 50)
        print('function #{}# start...'.format(func.__name__))
        start = time.time()
        res = func(*args, **kwargs)
        end = time.time()
        print('function #{}# end...execute time: {} s / {} min'.format(func.__name__, end - start, (end - start) / 60))
        return res
    return wrapper


def sleep_call(second:int=1, mod:int=1):
    """
    装饰器:在调用函数前后线程先睡眠指定秒数
    
    Args:
        second: 休眠秒数
        mod: 0 不休眠; 1 为调用前休眠; 2 为调用后休眠; 3 为前后均修眠
    """
    if mod > 3 or mod < 0:
        mod = 1
    def wrapper1(func):
        @functools.wraps(func)
        def wrapper2(*args, **kwargs):
            if mod & 1:
                time.sleep(second)
            res = func(*args, **kwargs)
            if mod & 2:
                time.sleep(second)
            return res
        return wrapper2
    return wrapper1
    
#################### END ########################

context.update(log_level=PWN_LOG_LEVEL)

# 一般需要带上文件,可注释改行语句
assert FILENAME is not None, 'give me a file!'
##################################################
##############以下为攻击代码#######################
##################################################

使用

使用详情基本在注释里面写得很清楚了,这里展示一下输入python exp.py -h后显示的帮助信息:

https://image.roderickchan.cn/img/20210402233749.png

https://image.roderickchan.cn/img/20210402233915.png

https://image.roderickchan.cn/img/20210402234008.png

笔记本屏幕有点小······

以下为全部显示内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
==========================================================================================
本脚本为pwn题所编写,利用click模块配置命令行参数,
能方便地进行本地调试和远程解题。
本地命令示例:
    python3 exp.py filename --tmux 1 --gdb-breakpoint 0x804802a --gdb-breakpoint printf
    python3 exp.py filename -t 1 -gb 0x804802a -gb printf
    python3 exp.py filename -t 1 -gs "x /12gx $rebase(0x202080)" -sf 0 -pl "warn"
    即可开始本地调试,并且会断在地址或函数处。先启动tmux后,--tmux才会有效。

远程命令示例:
    python3 exp.py filename -i 127.0.0.1 -p 22164
    python3 exp.py filename -p 22164
    可以连接指定的IP和端口。目前在刷buuctf上的题,所以填了默认ip,只指定端口即可。

==========================================================================================

Usage: do_pwn_template.py [OPTIONS] [FILENAME]

  FILENAME: The filename of current directory to pwn

Options:
  -d, --debug BOOLEAN             Excute program at local env or remote env.
                                  Default value: True.

  -t, --tmux BOOLEAN              Excute program at tmux or not. Default
                                  value: False.

  -gb, --gdb-breakpoint TEXT      Set a gdb breakpoint while tmux is enabled,
                                  is a hex address or '$rebase' addr or a
                                  function name. Multiple setting supported.
                                  Default value:'[]'

  -gs, --gdb-script TEXT          Set a gdb script while tmux is enabled, the
                                  script will be passed to gdb and cannot be
                                  identified with gdb-breakpoint
                                  simultaneously. Default value:None

  -i, --ip TEXT                   The remote ip addr. Default value: None.
  -p, --port INTEGER              The remote port. Default value: None.
  -ll, --local-log BOOLEAN        Set local log enabled or not. Default value:
                                  True.

  -pl, --pwn-log [debug|info|warn|error|notset]
                                  Set pwntools log level. Default value:
                                  debug.

  -sf, --stop-function BOOLEAN    Set stop function enabled or not. Default
                                  value: True.

  -h, --help                      Show this message and exit.

使用示例

本地调试: 输入:python3 exp.py ./ycb_2020_babypwn -t 1 -gs "b malloc\nb free" https://image.roderickchan.cn/img/20210405125931.png 自动下断点mallocfree 输入:python3 exp.py ./ycb_2020_babypwn -t 1 -gb "\$rebase(0xc1f)",这里要加上转义字符,否会失败。 https://image.roderickchan.cn/img/20210405133859.png 的确断住了。 远程攻击: 输入:python3 exp.py ./ycb_2020_babypwn -i "127.0.0.1" -p 6666 https://image.roderickchan.cn/img/20210405134223.png

参考与引用

Buy me a coffee~
roderick 支付宝支付宝
roderick 微信微信
0%