3

[pwn基础]Pwntools学习 - VxerLee昵称已被使用

 2 years ago
source link: https://www.cnblogs.com/VxerLee/p/16397405.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

[pwn基础]Pwntools学习

Pwntools介绍

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

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

Pwntools安装

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

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

测试是否安装成功

2080041-20220620182827177-714452455.png

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则可以轻松做到。

c
#include <stdio.h>

void hack()
{
    printf("Hack Success!!!!\n");
}

int main()
{
    printf("Hello,Please Start Hack!\n");
    char buf[20]={0};
    scanf("%s",buf);
    printf("Your input:%s\n",buf);
    int i;
    for(i=0;i<sizeof(buf);i++)
    {
        printf("0x%x,",buf[i]);
    }
    return 0;
}
bash
gcc hack2.c -m32 -fno-stack-protector -z noexecstack -o hack2

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

2080041-20220620190759998-1300770342.png

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

python
#导入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())

2080041-20220620191213649-81142361.png

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

打印进程装载起始地址。

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

远程的话使用如下命令:

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

pwnlib.context(运行环境)

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

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

CPU架构如下:

c
architectures = _longest({
        'aarch64':   little_64,
        'alpha':     little_64,
        'avr':       little_8,
        'amd64':     little_64,
        'arm':       little_32,
        'cris':      little_32,
        'i386':      little_32,
        'ia64':      big_64,
        'm68k':      big_32,
        'mips':      little_32,
        'mips64':    little_64,
        'msp430':    little_16,
        'powerpc':   big_32,
        'powerpc64': big_64,
        'riscv':     little_32,
        's390':      big_32,
        'sparc':     big_32,
        'sparc64':   big_64,
        'thumb':     little_32,
        'vax':       little_32,
        'none':      {},
    })
transform = [('ppc64', 'powerpc64'),
             ('ppc', 'powerpc'),
             ('x86-64', 'amd64'),
             ('x86_64', 'amd64'),
             ('x86', 'i386'),
             ('i686', 'i386'),
             ('armv7l', 'arm'),
             ('armeabi', 'arm'),
             ('arm64', 'aarch64')]
c
    big_32    = {'endian': 'big', 'bits': 32}
    big_64    = {'endian': 'big', 'bits': 64}
    little_8  = {'endian': 'little', 'bits': 8}
    little_16 = {'endian': 'little', 'bits': 16}
    little_32 = {'endian': 'little', 'bits': 32}
    little_64 = {'endian': 'little', 'bits': 64}
c
 oses = sorted(('linux','freebsd','windows','cgc','android','baremetal'))

pwnlib.elf(ELF文件操作)

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

python
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是因为这是个动态链接程序,装载地址不确定,改成静态编译就能显示装载地址。

2080041-20220621142647363-266245477.png

2080041-20220621142821283-1108686208.png
c
asm(address,assembly) # 汇编指令assembly插入ELF的address地址处,需要使用save函数来保存
bss(offset) # 返回.bss段加上offset后的地址
checksec() # 查看文件开启的安全保护
disable_nx() # 关闭NX
disasm(address,n_bytes) # 返回地址address反汇编n字节的字符串
offset_to_vaddr(offset) # 将偏移offset转换为虚拟地址
vaddr_to_offset(address) # 从虚拟地址address转换为文件偏移
read(address,count) # 从虚拟地址address读取count个字节的数据
write(address,data) # 在虚拟地址address写入data
section(name) # 获取name段的数据
debug() # 使用gdb.debug()进行调试

pwnlib.asm(汇编模块)

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

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

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

python
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'))

2080041-20220621150033959-1227082929.png

disasm()反汇编

python
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

2080041-20220621151729987-1093002885.png

2080041-20220621151840794-543991765.png

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

2080041-20220621151902688-1426134314.png

pwnlib.shellcraft(Shellcode生成器)

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

生成Shellcode代码。

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

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

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

2080041-20220621153653033-1158816129.png

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

python
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())

2080041-20220621154323734-750884671.png

官方的例子。

python
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等函数。

python
#用的最多的应该是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,自动在栈上部署对应的参数。

python
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大佬,欢迎交流

2080041-20220621164108602-316027446.png

__EOF__


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK