pwn題命令列解題指令碼

LynneHuan發表於2021-04-05

指令碼說明

這是專門為本地除錯與遠端答題準備的指令碼,依靠命令列引數進行控制。
本指令碼支援的功能有:

  • 本地除錯
    • 開啟tmux除錯
    • 設定gdb斷點,支援函式名斷點地址斷點檔名:行號斷點$rebase(0x00)斷點
    • 設定gdb script,可以設定任何內建的gdb命令
  • 遠端答題
    • 預設支援buuctf上解題的主機,只需要指定遠端port
    • 顯式指定遠端ipport
  • 可使用裝飾器函式、偏函式
    • 在執行函式前、後執行緒休眠指定秒數
    • 記錄函式執行日誌
    • int16解析16進位制字串為整數
  • 可自由定製
    • 依託click模組,可以自行定製更多功能

指令碼內容

#!/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: %s' % GDB_BREAKPOINT)
    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後顯示的幫助資訊:

筆記本螢幕有點小······

以下為全部顯示內容:

==========================================================================================
本指令碼為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"

自動下斷點mallocfree
輸入:python3 exp.py ./ycb_2020_babypwn -t 1 -gb "\$rebase(0xc1f)",這裡要加上轉義字元,否會失敗。

同樣命中斷點!

遠端攻擊:
輸入:python3 exp.py ./ycb_2020_babypwn -i "127.0.0.1" -p 6666

參考與引用

相關文章