刷题笔记

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. 指令执行步骤

  1. 保存返回地址:将当前 call 指令的下一条指令的地址压入栈中。在 x86 - 64 架构中,栈指针寄存器 rsp 会自动减 8(因为返回地址是 64 位),然后将返回地址存入 rsp 指向的内存位置。
  2. 计算目标地址:计算 r12 + rbx*8 的值,得到内存地址。
  3. 读取目标地址:从计算得到的内存地址处读取一个 64 位的值,这个值就是要调用的子程序的地址。
  4. 跳转执行:将程序的控制权转移到读取到的目标地址处,开始执行子程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 假设 r12 和 rbx 已经有值
r12 = 0x1000
rbx = 0x2

# 计算内存地址
memory_address = r12 + rbx * 8

# 从内存地址处读取目标地址
target_address = read_memory(memory_address, 8) # 读取 8 字节

# 保存返回地址到栈
rsp = rsp - 8
write_memory(rsp, next_instruction_address)

# 跳转到目标地址
pc = target_address
  • 内存访问权限:如果计算得到的内存地址没有有效的访问权限,会引发内存访问错误(如段错误)。
  • 栈管理:子程序执行完毕后,需要使用 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_gotwrite 函数在全局偏移表(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 的详细说明:

  1. 堆 canary 的作用
    堆 canary 的核心目标是检测堆块的溢出,防止攻击者通过覆盖相邻堆块的数据(如堆块头部、元数据等)来执行恶意操作(如劫持控制流、篡改内存等)。它通常通过在堆块之间插入特定值,并在释放或访问堆块时验证这些值是否被破坏。
  2. 堆 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
2
3
4
5
6
struct heap_chunk {
size_t prev_size; // 前一个堆块的大小
size_t size; // 当前堆块的大小
uint64_t canary; // 随机生成的canary值
char data[]; // 用户数据区域
};

分配堆块时,canary 字段被填充为随机值。
释放堆块时,检查 canary 是否与原始值一致,若不一致则判定溢出。

与栈 canary 的区别
特性 栈 canary 堆 canary
位置 位于栈帧的返回地址前 位于堆块的头部、尾部或数据区域边界
生成方式 随机生成(通常由编译器插入) 随机生成或固定值(由分配器决定)
检测时机 函数返回前检查 堆块释放或访问时检查
防御目标 防止栈溢出覆盖返回地址 防止堆溢出破坏相邻堆块或元数据

堆溢出攻击与 canary 的绕过
攻击方式:
攻击者可能通过覆盖堆块的元数据(如 size、prev_size)或相邻堆块的 canary 值,绕过保护机制。
绕过手段:
部分覆盖:仅修改部分 canary 字节,使其仍通过校验。
信息泄露:通过其他漏洞泄露 canary 值,再构造精准攻击。
利用未检查的路径:某些分配器在特定操作(如分配小内存块)时可能跳过 canary 检查。