• [pwn基础]Pwntools学习


    [pwn基础]Pwntools学习

    Pwntools介绍

    Pwntools是一个非常著名的CTF框架漏洞利用开发库,可以让使用者快速的编写exp

    它拥有本地执行远程连接读写shellcode生成ROP链构建ELF解析符号泄漏等众多强大的功能。

    Pwntools安装

    因为他是个python库,所以直接用pip来管理安装即可。

    #提前安装pip
    sudo apt-get install python3-pip
    #安装pwntools
    pip install pwntools -i https://pypi.tuna.tsinghua.edu.cn/simple
    

    测试是否安装成功

    image-20220620180907115

    Pwntools常用模块和函数

    Pwntools分为两个模块,一个是pwn,简单的用from pwn import *就能把所有子模块和一些常用的系统库导入当前命名空间中,是专门为了CTF比赛优化的。

    另外一个模块是pwnlib,它适合开发成产品,根据自己需要来导入不同的子模块。

    • pwnlib.adb: 安卓adb
    • pwnlib.asm: 汇编和反汇编
    • pwnlib.constans: 包含各种体系结构和操作系统中的系统调用号常量(来自头文件),constants.linux.i386.SYS_stat
    • pwnlib.context: 设置运行环境
    • pwnlib.dynelf: 利用信息泄漏远程解析函数
    • pwnlib.encoders: 对shellcode进行编码,如encoders.encoder.null('xxx')
    • pwnlib.elf: 操作ELF可执行文件和共享库
    • pwnlib.fmtstr: 格式化字符串利用工具
    • pwnlib.gdb: 调试,配合gdb使用
    • pwnlib.libcbd: libc数据库,入libcdb.search_by_build_id('xxx')
    • pwnlib.log: 日志记录管理,比如log.info('hello')
    • pwnlib.memleak: 内存泄漏工具,将泄漏的内存缓存起来,可作为Payload
    • pwnlib.qume: QEMU模拟相关,一般用来模拟不同架构的指令或运行程序
    • pwnlib.rop: ROP利用工具,包括rop,srop等
    • pwnlib.runner: 运行Shellcode,例如:run_assembly('mov eax,SYS_exit;int 0x80;')
    • pwnlib.shellcraft: Shellcode生成器
    • pwnlib.tubes: scokets、ssh、进程管道通信
    • pwnlib.utils: 一些实用小工具,比如CRC计算,cyclic字符串生成等

    pwnlib.tubes模块学习

    tubes模块是主要用来通信的模块,应该是pwn题中用的最广泛的交互方式,他主要有下面4中通信方式。

    1. pwnlib.tubes.process: 进程通信
    2. pwnlib.tubes.serialtube: 串口通信
    3. pwnlib.tubes.sock: socket套接字通信
    4. pwnlib.tubes.ssh: SSH连接通信

    tubes.process

    这里我一直好奇这个进程通信他是怎么弄的,为什么我用代码p=process('./mydemo'),然后就可以用send和recv对程序进行发送,然后看了他源码注释 Spawns a new process, and wraps it with a tube for communication.,他应该是用了比较hack的方法,自己模拟了系统加载本地程序变成进程的操作,并且封装了一层管道通信在上面,这样我们就可以通过send、recv来和他创建的进程来进行通信了,所以就给我们创造了无数可能,比如我之前文章[二进制漏洞]栈(Stack)溢出漏洞 Linux篇中里面的题目scanf只能输入ASCII码,这样我们无法构造一个地址Payload,而有了tubes.process则可以轻松做到。

    gcc hack2.c -m32 -fno-stack-protector -z noexecstack -o hack2
    

    正常情况下:无法输入0x01、0x02、0x03这种数据。

    image-20220620190740108

    而用tubes.process则可以用send发送原始十六进制数据。

    #导入pwntools模块
    from pwn import *
    context(arch = 'i386',os='linux')
    p = process("./hack2")
    
    #显示程序运行的第一条回显
    print(p.recv())
    
    #利用pipe管道发送带 十六进制的数据
    p.sendline(b'AAAA'+b'\x01\x02\x03\x04')
    
    #回显结果
    print(p.recvline())
    print(p.recvline())
    

    image-20220620191153963

    所以[二进制漏洞]栈(Stack)溢出漏洞 Linux篇文章的题,用tubes.process搞起来就方便多了。

    打印进程装载起始地址。

    p = process("./hack")
    imageBase = p.libs()["/home/ubuntu/hack"]
    

    远程的话使用如下命令:

    conn = remote('exploitme.example',31337)
    conn.recv()
    conn.sendline('test')
    

    pwnlib.context(运行环境)

    这个模块主要是用来设置进程运行时的环境,比如目标是什么CPU架构,多少位数,什么平台,是否开启日志等等。

    #架构32位X86,平台Linux
    context(arch='i386',os='linux')
    #设置tmux分屏
    context.terminal['tmux','splitw','-h']
    #开启日志信息
    context.log_level = 'debug'
    

    CPU架构如下:

    位数:

    平台:

    pwnlib.elf(ELF文件操作)

    pwnlib.elf模块还是挺实用的,虽然linux下有<elf.h>头文件可以用来解析ELF文件,但是很多代码都要自己实现,这个模块就解决了这些实现,可以进行符号查找、虚拟内存、文件偏移、修改和保存二进制等等。

    from pwnlib.elf import ELF
    #构造类
    elf = ELF('./hack_dyn')
    
    #架构,位数,平台
    print("---------------------------------------------")
    print("[+]架构:{0} 位数:{1} 系统:{2}".format(elf.arch,elf.bits,elf.os))
    print("")
    #打印装载地址
    print("[*]装载地址:",hex(elf.address))
    #打印GOT表
    print("[*]GOT表:")
    for kv in elf.got.items():
        print(kv)
    #打印PLT表
    print("")
    print("[*]PLT表:")
    for kv in elf.plt.items():
        print(kv)
    print("[*]hack函数偏移:",hex(elf.symbols['hack']))
    print("---------------------------------------------")
    

    可以看到打印出了GOT表、PLT表、符号表中hack函数的偏移,其中装载地址为0是因为这是个动态链接程序,装载地址不确定,改成静态编译就能显示装载地址。

    image-20220621142628339

    静态链接

    image-20220621142802198

    pwnlib.asm(汇编模块)

    这是个很强大的模块,可以进行汇编和反汇编,通常用来开发Shellcode的时候非常有用。

    可以用pwnlib.context先设置CPU架构字节序位数。

    asm()函数进行汇编,用disasm()函数进行反汇编

    from pwnlib.asm import *
    #汇编
    print(asm('mov eax, 0'))
    print(asm('mov ebx, 1'))
    print(asm('add eax, ebx'))
    print(asm('mov eax, SYS_execve'))
    print(asm('nop'))
    

    image-20220621150013820

    disasm()反汇编

    from pwnlib.asm  import *
    from pwnlib.util.fiddling import *
    #反汇编
    print(disasm(unhex('E007BFA9E20FBFA9E417BFA9E61FBFA9E827BFA9FA6FBFA9FC77BFA9FE0F1FF8C81580D2010000D440050035881580D2010000D41F040071C1040054000080D261FCFF10021880D2E3031FAA080780D2010000D4E003F837FF4300D1481680D2010000D4E00B00B9881B80D2E4230091E3031FAAE2031FAAE1031FAA200280D20024A0F2010000D4E00B00B9FF43009100020035000080D241FAFF300201A0D2E3031FAA080780D2010000D4E40300AAC81B80D2000080D2230080D2E5031FAAA20080D2010094D20100A0F2010000D4FE0741F8FC77C1A8FA6FC1A8E827C1A8E61FC1A8E417C1A8E20FC1A8E007C1A8FD7BBEA9010000142C010000000000000000000000000000000000000000000000000000AA0400000000F1FF'),arch='aarch64',bits=64))
    

    当汇编反汇编,其他架构平台时候,记得要安装对应的Binutils,安装教程:https://docs.pwntools.com/en/stable/install/binutils.html

    image-20220621151710861

    image-20220621151821857

    反编译效果,真的很强大!

    image-20220621151843674

    pwnlib.shellcraft(Shellcode生成器)

    这个模块可以用来生成Shellcode代码,这种模块简直太爱了,他可以生成aarch64、arm、thumb、mips、i386、amd64、powerpc架构的shellcode代码,基本上的架构都有了。

    生成Shellcode代码。

    >>> from pwn import *
    >>> print(shellcraft.i386.nop())
        nop
       #生成了一个x86架构平台的nop
    

    接下来生成一个Android手机打开/data/local/tmp/test.txt的Shellcode,这模块太强大了。

    #设置CPU架构  aarch64
    #设置系统平台 android
    print(shellcraft.aarch64.android.open('/data/local/tmp/test.txt'))
    

    image-20220621153632963

    还能配合asm输出不同格式shellcode,实在是太方便、太好用了!

    from pwn import *
    
    shellcode = shellcraft.aarch64.android.open('/data/local/tmp/test.txt')
    print("输出字符串格式Shellcode:")
    print(asm(shellcode,arch='aarch64',bits=64,os='android'))
    print("")
    print("输出十六进制格式Shellcode:")
    print(asm(shellcode,arch='aarch64',bits=64,os='android').hex())
    

    image-20220621154304667

    官方的例子。

    from pwnlib.shellcraft import *
    context.clear()
    context.arch = 'amd64'
    sc = 'push rbp; mov rbp, rsp;'
    sc += shellcraft.echo('Hello\n')
    sc += 'mov rsp, rbp; pop rbp; ret'
    solib = make_elf_from_assembly(sc, shared=1)
    subprocess.check_output(['echo', 'World'], env={'LD_PRELOAD': solib}, universal_newlines = True)
    'Hello\nWorld\n'
    

    pwnlib.util(小工具)

    这个模块是一些常用的功能函数,比如之前用到过的unhex就来自这模块,除此之外还有packing、hashes、net、misc、sh_string、cyclic等函数。

    #用的最多的应该是pack函数了吧
    p8(0) #打包1字节
    b'\x00'
    p32(0xdeadbeef) #32位最常用的,打包4字节
    b'\xef\xbe\xad\xde'
    p64(0xdeadbeef)
    b'\xef\xbe\xad\xde\x00\x00\x00\x00'
    #可设置大小端序
    >>> p32(0xdeadbeef,endian='little')
    b'\xef\xbe\xad\xde'
    >>> p32(0xdeadbeef,endian='big')
    b'\xde\xad\xbe\xef'
    #解包
    unpack(b'\xaa\x55',16,endian='little')
    '0x55aa'
    u32('\xaa\x55\x00\x00')
    21930
    u64('\xaa\x55\x00\x00\x00\x00\x00\x00')
    21930
    
    
    #生成溢出字符串(cyclic)
    cyclic(20)
    b'aaaabaaacaaadaaaeaaa'
    cyclic(20, alphabet=string.ascii_uppercase) #全大写
    b'AAAABAAACAAADAAAEAAA'  
    cyclic(20, n=8)		    #8字符对齐
    b'aaaaaaaabaaaaaaacaaa' 
    cyclic(20, n=2)         #2字符对齐
    b'aabacadaeafagahaiaja' 
    cyclic(alphabet = "ABC", n = 3)#设置成ABC对齐
    b'AAABAACABBABCACBACCBBBCBCCC'
    context.cyclic_alphabet = "ABC" #全局修改
    cyclic(10)
    b'AAAABAAACA'
    #查找偏移
    cyclic_find('daaa')
    12
    cyclic_find(0x61616162)
    4
    
    #unhex
    unhex('0102030405060708')
    b'\x01\x02\x03\x04\x05\x06\x07\x08'
    

    pwnlib.rop

    rop利用模块,包括rop,srop等。

    现在的exploit是越来越难,一般起手题都得是NX开启的,ROP这种以前都能出400分题的技术现在也就出50-100分题了非常惨,也许跟这个工具简化了ROP过程有关系?「误」

    先简单回顾一下ROP的原理,由于NX开启不能在栈上执行shellcode,我们可以在栈上布置一系列的返回地址与参数,这样可以进行多次的函数调用,通过函数尾部的ret语句控制程序的流程,而用程序中的一些pop/ret的代码块(称之为gadget)来平衡堆栈。其完成的事情无非就是放上/bin/sh,覆盖程序中某个函数的GOT为system的,然后ret到那个函数的plt就可以触发system('/bin/sh')。由于是利用ret指令的exploit,所以叫Return-Oriented Programming。(如果没有开启ASLR,可以直接使用ret2libc技术)

    好,这样来看,这种技术的难点自然就是如何在栈上布置返回地址以及函数参数了。而ROP模块的作用,就是自动地寻找程序里的gadget,自动在栈上部署对应的参数。

    from pwn import *
    
    elf = ELF('ropasaurusrex')
    rop = ROP(elf)
    rop.read(0, elf.bss(0x80))
    rop.dump()
    # ['0x0000:        0x80482fc (read)',
    #  '0x0004:       0xdeadbeef',
    #  '0x0008:              0x0',
    #  '0x000c:        0x80496a8']
    str(rop)
    # '\xfc\x82\x04\x08\xef\xbe\xad\xde\x00\x00\x00\x00\xa8\x96\x04\x08'
    

    使用ROP(elf)来产生一个rop的对象,这时rop链还是空的,需要在其中添加函数。

    因为ROP对象实现了__getattr__的功能,可以直接通过func call的形式来添加函数,rop.read(0, elf.bss(0x80))实际相当于rop.call('read', (0, elf.bss(0x80)))。 通过多次添加函数调用,最后使用str将整个rop chain dump出来就可以了。

    • call(resolvable, arguments=()) : 添加一个调用,resolvable可以是一个符号,也可以是一个int型地址,注意后面的参数必须是元组否则会报错,即使只有一个参数也要写成元组的形式(在后面加上一个逗号)
    • chain() : 返回当前的字节序列,即payload
    • dump() : 直观地展示出当前的rop chain
    • raw() : 在rop chain中加上一个整数或字符串
    • search(move=0, regs=None, order=’size’) : 按特定条件搜索gadget,没仔细研究过
    • unresolve(value) : 给出一个地址,反解析出符号

    参考文章:

    http://unbelievable.cool/2021/07/25/pwntools学习/#pwnlib-asm (pwntools学习)

    http://brieflyx.me/2015/python-module/pwntools-intro/ (Exploit利器--Pwntools)

    http://www.leonlist.top/2020/09/02/pwn基本工具-pwntools/ (pwn基本工具-pwntools)

    https://xuanxuanblingbling.github.io/ctf/pwn/2020/12/13/getshell3/ (Getshell远程:真·RCE 正连?反连?不连?)

    https://github.com/Gallopsled/pwntools (pwntools源码)

    最后大家可以多看看pwntools源码去熟悉熟悉,每个模块的功能,他注释一般有demo写的还是非常详细的。

    PWN菜鸡小分队

    最后感谢大家的阅读,本菜鸡也是刚学,文章中如有错误请及时指出。

    大家也可以来群里骂我哈哈哈,群里有PWN、RE、WEB大佬,欢迎交流

    img


    __EOF__

  • 本文作者: VxerLee
  • 本文链接: https://www.cnblogs.com/VxerLee/p/16397405.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。
  • 相关阅读:
    Python博客系统源代码,基于Django + Vue.js +MySql,毕业设计
    一文带你读懂scala中的隐式转换
    MES如何提升企业数字化能力?
    从原始边列表到邻接矩阵Python实现图数据处理的完整指南
    2022.11.3 英语背诵
    tRNA甲基化偶联3-甲基胞嘧啶(m3C)|tRNA-m3C (3-methylcy- tidine)
    达梦数据库使用IPV6连接
    会计学期末题库 含WORD版
    交易中最佳的建仓时机,fpmarkets一个指标搞定
    希尔排序(Shell Sort)
  • 原文地址:https://www.cnblogs.com/VxerLee/p/16397405.html