刷题笔记
刷题笔记
山林川泽close(1)关闭标准输出流
解决方法 exec 1 > &0
在Linux中
- 一切都可以作为文件,文件描述符(file descriptor)是内核为了高效管理已被打开的文件所创建的索引,是一个非负整数(通常是小整数),用于指代被打开的文件,所有执行I/O操作的系统调用都通过文件描述符。程序刚刚启动的时候,0是标准输入,1是标准输出,2是标准错误。 如果此时去打开一个新的文件,它的文件描述符会是3。
- 而标准输入输出的指向是默认的,我们也可以去修改他的指向,也就是重定位文件描述符。 例如,可以用
exec 1>myoutput
把标准输出重定向到myoutput文件中,也可以用exec 0<myinput
把标准输入重定向到myinput文件中,而且,文件名字可以用&+文件描述符
来代替。
close(1);close(2);
便是关闭了 标准输出和 标准错误输出
但我们就可以使用重定位文件描述符的办法,将标准输出重定位到标准输入上来达到返回shell的目的(因为默认打开一个终端后,0,1,2都指向的是当前终端,所以该语句相当于重启了标准输出,也就可以看到程序的输出了)
例如exec 1 >&0将标准输出流定位到标准输出流
格式化字符串漏洞
可以通过aaaa-%p%p%p%p%p%p%p测量偏移量
a的ASCII码为61 所以我们只需要找到0x61616161的位置,开始部分到这部分的地址数就是偏移
偏移指的是从格式化字符串开始,到第一个用户可控参数在栈上的相对位置。换句话说,就是确定在格式化字符串函数的参数列表里,用户输入的字符串是第几个参数。例如,当偏移为 3 时,意味着用户输入的格式化字符串是函数参数列表中的第 3 个参数。
针对pie保护的应对方法
开启pie后,系统的低三位地址和原来的低三位地址一样
我们要计算pro_base(pie的基地址)
第一种方法———利用printf的截断机制
C语言中,printf会打印内容到 “”\x00” 截断符号为止
当我们输入的数据超过溢出点所能承受的最大范围后,读入的数据会溢出覆盖这个截断符号,此时溢出字符后
Stack Smash
在做此类题目时,可以在题目文件同一目录下创建flag文件
然后在gdb调试中 使用search flag找到flag的地址
此地址就是flag在远程服务器的真实地址
高glibc版本失效此攻击方法
伪随机数
rand()函数在调用时会使用srand()函数
srand函数设置种子,按照种子生成随机数,但是有一定范围
默认种子为1
call qword ptr [r12+rbx*8]
1. 操作数部分
**
qword ptr
**:这是一个操作数大小修饰符,明确指定操作数的大小为 64 位(8 字节)。在 x86 - 64 架构中,由于可以处理不同大小的数据,使用qword ptr
可以确保从内存中读取的数据是 64 位的。[r12 + rbx*8]
:这是一个内存寻址方式,属于基址 - 变址寻址。其中:
r12
是基址寄存器,提供一个基础地址。rbx
是变址寄存器,它的值会乘以 8(因为是*8
),然后与r12
的值相加,得到最终的内存地址。
2. 指令执行步骤
- 保存返回地址:将当前
call
指令的下一条指令的地址压入栈中。在 x86 - 64 架构中,栈指针寄存器rsp
会自动减 8(因为返回地址是 64 位),然后将返回地址存入rsp
指向的内存位置。 - 计算目标地址:计算
r12 + rbx*8
的值,得到内存地址。 - 读取目标地址:从计算得到的内存地址处读取一个 64 位的值,这个值就是要调用的子程序的地址。
- 跳转执行:将程序的控制权转移到读取到的目标地址处,开始执行子程序
1 | # 假设 r12 和 rbx 已经有值 |
- 内存访问权限:如果计算得到的内存地址没有有效的访问权限,会引发内存访问错误(如段错误)。
- 栈管理:子程序执行完毕后,需要使用
ret
指令从栈中弹出返回地址,并将程序控制权转移回调用处。
通过这种间接调用的方式,可以实现更灵活的程序设计,例如实现函数指针数组等。
寄存器的值对程序的影响(ret2cus&&泄露libc)
rbx
(值为 0):- 在
__libc_csu_init
函数中的特定代码逻辑里,rbx
通常用作计数器。当rbx
为 0 时,配合后续操作可以避免一些不必要的跳转和错误计算,确保代码按照预期流程执行。
- 在
rbp
(值为 1):rbp
通常用于控制循环或者条件跳转。设置为 1 是为了避免在__libc_csu_init
函数中的某个条件判断后发生跳转,使得代码能够继续执行后续用于设置寄存器参数的操作。
r12
(值为 1):- 在
ret2csu
利用过程中,r12
最终会被用于设置目标函数的第一个参数。这里将其设置为 1,是因为要调用write
函数,write
函数的第一个参数fd
表示文件描述符,1
代表标准输出(stdout),即要将数据输出到标准输出。
- 在
r13
(值为write_got
):r13
最终会被用于设置目标函数的第二个参数。对于write
函数,第二个参数buf
是要写入的数据的缓冲区地址。write_got
是write
函数在全局偏移表(GOT)中的地址,这里将其作为缓冲区地址,意味着要将write
函数在 GOT 表中的内容写入到标准输出。
r14
(值为 8):r14
最终会被用于设置目标函数的第三个参数。对于write
函数,第三个参数count
表示要写入的字节数。设置为 8 是因为在 64 位系统中,地址通常是 8 字节,这里要写入write
函数在 GOT 表中的地址(8 字节)。
r15
(值为write_got
):r15
一般用于存储要调用的函数的地址。这里将其设置为write_got
,表示要调用write
函数。
func
(值为main_addr
):- 调用完目标函数(这里是
write
函数)后,程序会跳转到func
所指定的地址继续执行。将其设置为main_addr
,意味着调用完write
函数后,程序会回到main
函数重新开始执行,这样可以进行多次利用或者获取更多信息。
- 调用完目标函数(这里是
堆中的canary
在堆内存管理中,canary(金丝雀值) 是一种用于检测堆溢出的安全机制,其设计思想与栈溢出保护中的 栈 canary 类似,但实现方式和作用场景有所不同。以下是关于堆中 canary 的详细说明:
- 堆 canary 的作用
堆 canary 的核心目标是检测堆块的溢出,防止攻击者通过覆盖相邻堆块的数据(如堆块头部、元数据等)来执行恶意操作(如劫持控制流、篡改内存等)。它通常通过在堆块之间插入特定值,并在释放或访问堆块时验证这些值是否被破坏。 - 堆 canary 的常见实现方式
不同的堆分配器(如 glibc 的 ptmalloc、Google 的 tcmalloc 等)对堆 canary 的实现有所差异。以下是两种典型实现:
(1)Guard Bytes(保护字节)
原理:在堆块的末尾插入固定值(如 0x00、0x55、0xAA 等),或随机生成的 canary 值。当堆块被释放时,检查这些值是否被修改,若被修改则判定发生溢出。
示例(ptmalloc):
在堆块的 prev_size 和 size 字段之后,可能插入一个 guard byte(如 0x00)。
当释放堆块时,检查该 guard byte 是否被破坏。
若堆块被溢出覆盖,guard byte 会被修改,触发程序崩溃。
(2)Heap Canary(显式随机值)
原理:在堆块的头部或尾部插入随机生成的 canary 值(类似栈 canary),并在访问或释放堆块时验证该值是否被篡改。
示例(某些自定义分配器):
1 | struct heap_chunk { |
分配堆块时,canary 字段被填充为随机值。
释放堆块时,检查 canary 是否与原始值一致,若不一致则判定溢出。
与栈 canary 的区别
特性 栈 canary 堆 canary
位置 位于栈帧的返回地址前 位于堆块的头部、尾部或数据区域边界
生成方式 随机生成(通常由编译器插入) 随机生成或固定值(由分配器决定)
检测时机 函数返回前检查 堆块释放或访问时检查
防御目标 防止栈溢出覆盖返回地址 防止堆溢出破坏相邻堆块或元数据堆溢出攻击与 canary 的绕过
攻击方式:
攻击者可能通过覆盖堆块的元数据(如 size、prev_size)或相邻堆块的 canary 值,绕过保护机制。
绕过手段:
部分覆盖:仅修改部分 canary 字节,使其仍通过校验。
信息泄露:通过其他漏洞泄露 canary 值,再构造精准攻击。
利用未检查的路径:某些分配器在特定操作(如分配小内存块)时可能跳过 canary 检查。