pwn题工具整理
目录
语言
汇编
- mov和lea
mov:取内存单元的值
lea:取内存单元的偏移地址
偏移地址由基址加变址组成
C语言
指针
-
指针就是地址,地址就是指针
-
指针地址,指针的值,指针指向的值
数据类型
- 二进制数据在内存中没有类型,在变量层面分为整形和字符等类型,二者转换时需要处理,考虑可能的隐式转换
- python字节流两种表示形式:数值和字符,可以使用“\数值”来表示单个字符
文件缓冲区和流
- FILE是文件缓冲区的结构,fp也是指向文件缓冲区的指针
- 流向文件缓冲区,buf自定义缓冲区
- read本身不识别回车,有多少读多少,tty驱动器以一次一行的形式向read提供输入,read不用缓冲区
- stdin和stdout是指向FILE结构体的指针
操作系统
编译链接
- plt和got表:动态链接延迟绑定
- 符号表:保存自定义函数和全局变量的名字和地址
- 字符串表:保存用到的字符串
- 调用者保存寄存器和被调用者保存寄存器
加载运行
- 查看加载后的内存映射
cat /proc/id/maps
保护机制
- PIE和ASLR都是地址随机化
- PIE是编译时生效的,给程序一个固定的加载基址,如果不开PIE程序运行后的txt段等地址同静态文件的一样
- ASLR是操作系统赋予的,0不开启任何随机化;1开启stack、libraries以及加载基址(如果开了PIE)的随机化;2开启heap随机化
格式化字符串漏洞
printf函数族
- %p、%s、%d
- %n:将已经打印的字符数量写入指定地址
- N$:偏移N处(从函数的第二个参数算起)
- .N(最大宽度)、N(最小宽度)
x86和x64
- x86从栈上第二个参数开始打印
- x64从第二个寄存器开始打印(一共六个)
使程序崩溃
- %s将栈上内容做为地址读取
读取栈上数据
- %p读取栈上内容
任意地址读取
char buf[100];
read(0, buf, 100);
printf(buf);
- 找到buf变量地址同printf的buf参数的偏移N
- 输入"arbitrary_addr+%N$s"
任意地址写
char buf[100];
read(0, buf, 100);
printf(buf);
- 找到buf变量地址同printf的buf参数的偏移N
- 输入"arbitrary_addr+%.Mp+%N$n"
工具
#自动计算偏移法一
def exec_fmt(pad):
p = process("./fmt")
p.send(pad) #send还是sendline以程序为准
return p.recv()
fmt = FmtStr(exec_fmt)
print("offset ===> ", fmt.offset)
#自动计算偏移法二
fmtarg:在call printf处断下,fmtarg addr
#自动生成payload
fmtstr_payload(offset, address:value)
PwnTools
shellcraft
shellcode = asm(shellcraft.sh())
#另:调用system的shellcode
code0 = asm('xor rax,rax')
code1 = asm('mov eax,0x3B')
code2 = asm('xor rsi,rsi')
code3 = asm('xor rdx,rdx')
code4 = asm('syscall')
LibcSearcher
- 该工具很久没更新,可能搜不到,建议在线查用泄露地址的最后三位在线查libc-database
from LibcSearcher import *
libc=LibcSearcher('write',write_addr)
libc_base=write_addr-libc.dump('write')
system_addr=libc_base+libc.dump('system')
sh_addr = libc.dump("str_bin_sh") + libc_base
调用c函数
from ctypes import *
lib = cdll.LoadLibrary("libc.so.6")
lib.srand(1)
设置环境
context(os = 'linux', arch='amd64', log_level='debug')
搜索ELF文件
#搜索程序
elf = ELF("./bin")
system_addr = elf.symbols["name"]
elf.search("/bin/sh")
#搜索库
lib = ELF("./libc.so.6")
lib.symbols["write"]
next(lib.search("/bin/sh"))或lib.search("/bin/sh").next()
本地加载指定libc
- 本地加载的libc和远程libc大多数时候是不同的,libc不同,函数不同,行为不同
#查看libc版本
strings libc_32.so.6 | grep "LIBC"
#利用patchelf,libc和ld都可在glibc-all-in-one文件夹中找到
patchelf --replace-needed libc.so.6 你要换的libc的硬路径 ./pwn
patchelf --set-interpreter ld的硬路径 ./pwn
#或者
p = process(['./ld-2.23.so','./pwn'], env = {'LD_PRELOAD' : './libc-2.23.so'})
#或者
找一个使用低版本libc的系统
其他命令
payload = payload.ljust(260, b'a') #填充
strings bin | grep "/bin/sh" #搜索目标程序可打印字符串
recv()[:4] #切片
recvuntil不但可以用来定位输入,返回值还可以用来捕获输出
u64(p.recv(6).ljust(8, '\x00')) #解包输出地址
interactive() #取得shell后直接进行交互
注意区分send和sendline!二者都可以按行发送给远端(类似tty),sendline多发送一个换行符,对于read用send就行
远程拿不到shell的原因
- libc给的不对
- shell命令不支持
- send发送字符的问题
gdb
gdb attach技巧
- 脚本外attach
目标:查看程序运行到p函数前后的环境
原则:让程序等待数据发送,不能破坏输入和程序运行的前后时间关系
步骤:
1)proc.pidof(p)把进程号打印出来,gdb attach
2)脚本里在p之后最近的一次sendline处pause
3)b p下断点,查看调用前,finish查看调用后
4)c继续往下执行,程序等待输入,脚本回车pause
- 脚本内attach
gdb.attach(p)
pause()
# attach和断点调试时,需要注意下断点或者attach的时候还未执行到目的地
- 子进程attach
用ps -aux | grep查询子进程号
gdb attach子进程
gdb调试子进程
- follow-fork-mode
set follow-fork-mode parent #fork后跟进父进程
set follow-fork-mode child #fork后跟进子进程,默认是这个
- detach-on-fork
set detach-on-fork on #只调试父/子进程中的一个,默认是这个
set detach-on-fork off #父/子进程都在gdb控制下,其中一个正常调试,另一个被设置为暂停,结合inforior来回切换
- inforior
info inferiors #查看当前所有进程信息
inferior [ID] #切换到指定进程
查看内存映射
- vmmap查看内存段映射地址和权限
加载libc源码调试
- 下载源码(已下载到本地glibc-all-in-one中),通过directory命令将libc源码加载进来,如果patch过libc,则仍然看不到源码
编译可调试版本的libc
/home/dfl/tools/glibc/glibc-2.23/configure --prefix=/home/dfl/tools/glibc/lib/ --enable-debug=yes --disable-werror --disable-profile CFLAGS="-O1 -g" CPPFLAGS="-O1 -g"
make -j8
make install
小工具
ROPgadget
ROPgadget --binary 文件名 --only 'pop|ret' | grep 'eax'
ROPgadget --binary 文件名 --string '/bin/sh'
objdump
- 查看导出符号偏移地址
objdump -T libc | grep puts
- 查看所有符号地址
objdump -t bin
- 反汇编指定段
objdump -j .bss -d bin
- 查看各段偏移
objdump -h bin
readelf
- 查看文件头信息
readelf -h bin
- 查看符号表
readelf -s bin
one_gadget
- 查看libc中one_gadget地址
one_gadget libc