pwn杂记
基础知识
许多编译器使用帧指针寄存器FP(Frame Pointer)记录栈帧基地址。局部变量和函数参数都可通过帧指针引用,因为它们到FP的距离不会受到压栈和出栈操作的影响。有些资料将帧指针称作局部基指针(LB-local base pointer)。
在Intel CPU中,寄存器BP(EBP)用作帧指针。
当堆栈向下(低地址)增长时,以FP地址为基准,函数参数的偏移量是正值,而局部变量的偏移量是负值。
栈中的argument和local varliable顺序分别向高地址和低地址排列,即函数的入栈顺序是实参N
1→主调函数返回地址→主调函数帧基指针EBP→被调函数局部变量1N. 注,局部变量的布局依赖于编译器实现等因素, 且局部变量并不总在栈中,有时出于性能(速度)考虑会存放在寄存器中。数组/结构体型的局部变量通常分配在栈内存中。寄存器%eax、%edx和%ecx为主调函数保存寄存器(caller-saved registers),当函数调用时,若主调函数希望保持这些寄存器的值,则必须在调用前显式地将其保存在栈中;被调函数可以覆盖这些寄存器,而不会破坏主调函数所需的数据。寄存器%ebx、%esi和%edi为被调函数保存寄存器(callee-saved registers),即被调函数在覆盖这些寄存器的值时,必须先将寄存器原值压入栈中保存起来,并在函数返回前从栈中恢复其原值,因为主调函数可能也在使用这些寄存器。此外,被调函数必须保持寄存器%ebp和%esp,并在函数返回后将其恢复到调用前的值,亦即必须恢复主调函数的栈帧。
函数返回值是结构体或者联合体的时候,第一个参数将位于12(%ebp)处
扩展: 与函数调用约定规定参数如何传入不同,局部变量以何种方式布局并未规定。编译器计算函数局部变量所需要的空间总数,并确定这些变量存储在寄存器上还是分配在程序栈上(甚至被优化掉)——某些处理器并没有堆栈。局部变量的空间分配与主调函数和被调函数无关,仅仅从函数源代码上无法确定该函数的局部变量分布情况。基于不同的编译器版本(gcc3.4中局部变量按照定义顺序依次入栈,gcc4及以上版本则不定)、优化级别、目标处理器架构、栈安全性等,相邻定义的两个变量在内存位置上可能相邻,也可能不相邻,前后关系也不固定。若要确保两个对象在内存上相邻且前后关系固定,可使用结构体或数组定义。
x86平台将参数压入调用栈中。而x86_64平台具有16个通用64位寄存器,故调用函数时前6个参数通常由寄存器传递,其余参数才通过栈传递。
__stack_chk_fail的 got 值劫持流程(在glibc中的函数,默认情况下经过 ELF 的延迟绑定是不是意味着都可以进行got值劫持流程)
栈的 TLS 结构, stack_guard, TLS中保存canary值, TLS中的值又由security_init初始化
fsb 漏洞 ELF重映射
canary的解法:
各种保护的实现
integer overflow, off-by-one
format参考列表:
- %c:输出字符,配上%n可用于向指定地址写数据。
- %d:输出十进制整数,配上%n可用于向指定地址写数据。
- %x:输出16进制数据,如%i$x表示要泄漏偏移i处4字节长的16进制数据,%i$lx表示要泄漏偏移i处8字节长的16进制数据,32bit和64bit环境下一样。
- %p:输出16进制数据,与%x基本一样,只是附加了前缀0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32bit还是64bit。
- %s:输出的内容是字符串,即将偏移处指针指向的字符串输出,如%i$s表示输出偏移i处地址所指向的字符串,在32bit和64bit环境下一样,可用于读取GOT表等信息。
- %n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,如%100×10$n表示将0x64写入偏移10处保存的指针所指向的地址(4字节),而%$hn表示写入的地址空间为2字节,%$hhn表示写入的地址空间为1字节,%$lln表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过%$hn或%$hhn来适时调整。
- %n是通过格式化字符串漏洞改变程序流程的关键方式,而其他格式化字符串参数可用于读取信息或配合%n写数据。
- %c:输出字符,配上%n可用于向指定地址写数据。
汇编语言, gdb调试命令和技巧, pwntools的具体功能
chown, charp, chmod,
NX Bit
- 内存区域不可执行, 可执区域不可写.(使数据,堆栈和堆段不可执行,而代码段不可写).
- 所以绕过NX位并实现任意代码执行的基本思路:寻找原有代码中有”漏洞”的函数,在堆栈中写入相关参数后调用它(即跳跃到它的地址).这就是return-to-libc.可以理解为直接执行一段shellcode的时候会被打断.
- readelf -l 文件可查看文件性质
- ESP Lifiting技术(没解决),Frame Faking技术.
- seteuid
- leave ret
- libc函数地址本身包含一个NULL字节 (?坏字符) 零也是一个坏字符
- 查看libc库函数的位置
- sprintf函数, strcpy不能用于漏洞代码 ???
ASLR
ldd
aslr影响对象:
return-to-plt
共享库,动态链接器,plt,got,重定位, 第一次
exit函数
readelf -s libc.so.6 | grep system
Brute Force
假定一个libc地址 用ldd ./vuln | grep libc
限于x86
爆破栈和堆
GOT overwrite and GOT dereference
ropeme ROPgadget rp++
objdump -R vuln 查看函数GOT条目位置,IDA
使用unlink的堆溢出
- 对于不是 mmap 的块,会向前或向后合并。???
- 通常堆内存的第一个块的前面那个块是分配的(即使它不存在)???
- 攻击者的覆盖操作的具体设置原因
- prev_size为偶数,因此PREV_INUSE是未设置的 ???
- unlink的具体操作 ,FD和BK相聚12 ???
- exp
- RELRO
canary
栈的TLS结构
概念性问题
0xa
canary 4位
泄露canary
爆破canary ??? 结合NX的爆破来看
劫持_stack_chk_fail函数
IDA 使用技巧总结
ROP
ret2text 即控制程序执行程序本身已有的的代码 (.text)
ret2shellcode,即控制程序执行 shellcode 代码
vmmap 查看各个段的情况 (具体区分???)
ret2syscall,即控制程序执行系统调用,获取 shell。
当系统调用所需参数的个数不超过5个的时候,执行”int$0x80”指令时,需在eax中存放系统调用的功能号,传递给系统调用的参数则按照参数顺序依次存放到寄存器ebx,ecx,edx,esi,edi中,当系统调用完成之后,返回值存放在eax中;64位也是
一些敏感函数源码
ret2libc 即控制函数的执行 libc 中的函数,通常是返回至某个函数的 plt 处或者函数的具体位置 (即函数对应的 got 表项的内容)
注意正常的调用函数和非正常的调用函数对栈结构的影响
泄露的时候注意返回地址是symbol的main函数,因为还要执行嘛
怎样查看程序编译进去的函数
ret2csu 利用 x64 下的 __libc_csu_init 中的 gadgets
ret2reg
BROP 是没有对应应用程序的源代码或者二进制文件下,对程序进行攻击,劫持程序的执行流。
在 libc_csu_init 的结尾一长串的 gadgets,我们可以通过偏移来获取 write 函数调用的前两个参数。可以找到 strcmp 函数,从而来控制 rdx
ret2libc_csu_init 可以知道该地址减去 0x1a 就会得到其上一个 gadgets ???
plt 表项的慢路径
ret2_dl_runtime_reslove
linux 中是利用_dl_runtime_resolve(link_map_obj, reloc_index) 来对动态链接的函数进行重定位
plt[0]
栈迁移到bss段来控制write函数
readelf -S
为什么/bin/sh要填在中间, stack构造的大小等数据怎么选择,
objdump -d -j .plt
objdump -s -j .rel.plt
SROP
retVDSO
stack pivoting 持栈指针指向攻击者所能控制的内存处,然后再在相应的位置进行 ROP
frame faking构造一个虚假的栈帧来控制程序的执行流
read 并不会给输入末尾补上 ‘\0’, gets读取回补\x00 为什么不给末尾加\0 可以leak栈上内容 一个地址空间接收时从低位开始填
pwndbg的寻找偏移量
可以布置调用 execve(“/bin/sh”, 0, 0) 的利用链, 这种方法更稳妥 (system(“/bin/sh”) 可能会因为 env 被破坏而失效), 不过由于利用过程中栈的结构会发生变化, 所以一些关键的偏移还需要通过调试来确定
stack smash
partial overwrite
pwndbg 暴露canary值
页内偏移
pwntools的context.terminal等
2018 - 安恒杯 - babypie 为什么不直接用该函数地址是因为aslr吗 那为什么不覆盖低12位增大概率?
我用 pwndbg gef peda能干什么
如何查看__libc_start_main+240 位于 libc 中,_dl_init+139 位于 ld 中 libc基址 ld基址
我们一般要覆盖字节的话,至少要覆盖 1 个半字节才能够获取跳到onegadget。而我们覆盖字节的时候必须覆盖整数倍个数,即至少会覆盖 3 个字节
memory map查看
格式化字符串漏洞
泄露内存
泄露栈内存: 获取某个变量值, 获取某个变量对应地址的内存
泄露任意地址内存: 利用GOT表得到libc函数地址,进而获取libc, 进而获取其他libc函数地址
盲打, dump整个程序,获取有用信息
gef 中 直接打 got出来got表
gdb.attach(sh)
gdb相同的函数怎么下断点
有时候,我们需要对我们输入的格式化字符串进行填充,使得我们想要打印的地址内容的地址位于机器字长整数倍的地址处
覆盖内存
例题为什么要达到16个字符, 对齐? payload哪有再次12个字符?
覆盖任意地址内存
覆盖小数字 为什么要把%k 和 $n分开? 因为是两个参数?
意思差不多是我们没有必要必须把地址放在最前面,放在那里都可以,只要我们可以找到其对应的偏移即可。
覆盖大数字
objdump -R 查看一个程序的GOT函数的地址
PLT[0]是一个函数, 这个函数的作用是通过GOT[1],GOT[2]来正确绑定一个函数的正式地址到GOT表中来, 同时在一个函数没有运行一次之前, GOT表中的数据位@plt函数中下一条指令的地址.
64位程序格式化字符串漏洞
注意栈上的参数是在6个寄存器参数之后的
hijack GOT
在没有开启 RELRO 保护的前提下,每个 libc 的函数对应的 GOT 表项是可以被修改的 ???
一般默认远程都是开启 ASLR 保护的
hijack retaddr
(存疑)修改地址那里 这个信息怎么看….
格式化字符串盲打
由于程序是 64 位,所以我们从 0x400000 处开始泄露