pwn入门-canary&PIE bypass

  1. 主函数没有get shel怎么办?(ret2text)
  2. 整个程序没有system函数怎么办?(ret2libc)64位程序就这么简单?(ret2csu)
  3. 静态编译怎么办?(ret2syscall)
  4. 保护不全有没有好办法?(ret2shellcode)
  5. 栈与非栈的格式化字符串

image-20250112150236462

image-20250112150336860

canary

canary:是一种用来防护栈溢出的保护机制。其原理是在一个函数的入口处,先从fs/gs寄存器中取出一个4字节(eax)或者8字节(rax)的值存到栈上,当函数结束时会检查这个栈上的值是否和存进去的值一致

image-20241231092620963

canary保存在tls结构体中。

image-20241231092904910

image-20250112150542603

all和部分是有区别的

all是在所有方法都会加Cancary,部分只会在char那边加

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int func_1(){
char buf1[8];
puts("this is func_1");
return 0;
}
int func_2(){
int buf2;
puts("this is func_2");
return 0;
}
int func_3(){
puts("this is func_3");
return 0;
}
int main(){
func_1();
func_2();
func_3();
return 0;
}
//gcc canary_test.c -o canary_off -fno-stack-protector
//gcc canary_test.c -o canary_on -fstack-protector
//gcc canary_test.c -o canary_all -fstack-protector-all

image-20250112152228417

绕过方法:

  1. 格式化字符串绕过:canary通过格式化字符串读取canary的值

  2. Canary爆破(针对有fork函数的程序)

    fok作用相当于自我复制,每一次复制出来的程序,内存布局都是一样的,当然canary值也
    一样。那我们就可以逐位爆破,如果程序崩溃了就说明这一位不对,如果程序正常就可以
    接着跑下一位,直到跑出正确的canary

  3. 劫持stack_chk_fail

修改got表中stack chk fail函数的地址,在栈谥出后执行该函数,但由于该函数的地址
被修改,所以程序会跳转到我们想要执行的地址

  1. TLS

  2. stack smashing

格式化字符串

%s就是把一串字符串打印出来,而判断字符串结束的依据是结尾的x00,所以如果我们把输入的内容和canary连在一起,那么打印的时候就会将canary一起打印出来。需要注意的是canaryl的最后一位一定是%00,用于防止连带输出,所以我们改的时候要把canary最后一位也改了

在输入1后连带着把canary的值打印出来(c语言总是将00作为结束而上一步把00改为b)

简单来说就是,把\x00覆盖掉,让printf把canary也输出出来,我们接受之后的7字节或者3字节

例题:

image-20241231095837657

1:print,2:read0x64,3:break

下断点read处,运行,stack40

image-20241231100108475

输入长值

image-20241231100150988

exp:

image-20241231100227929

z:用于发送/接收数据或与程序进行交互。

backdoor = 0x4007d6:这是程序中后门函数的地址。

canary:栈保护值(Stack Canary),通常用于检测栈溢出攻击。

1
2
3
4
5
ru = lambda x: p.recvuntil(x)  # 接收直到指定字符串
rc = lambda: p.recv() # 接收所有内容
sl = lambda x: p.sendline(x) # 发送一行字符串
sd = lambda x: p.send(x) # 发送字符串
ia = lambda: p.interactive() # 进入交互模式
1
2
3
4
# 1.执行单条汇编指令
pwndbg> ni
# 2.步入汇编指令
pwndbg> si

0x18是buf到canary的长度,之后是8字节canaey,8字节rbp,8字节返回地址

canary的值在栈上,位于rbp寄存器之上,也就是说buff之后的8字节就是cancry,在之后是rbp最后是rsp(函数返回地址)

pwndbg 中,输入 stack 40 的目的是查看当前栈顶(rsp 指向的位置)附近的 40 个栈单元(每个单元通常是 8 字节)。每个单元显示一个内存地址及其内容。

1
2
3
4
5
6
7
8
9
10
11
高地址

0x7ffffffffdd00 (rbp)
0x7ffffffffdcf8
0x7ffffffffdcf0
0x7ffffffffdce8
0x7ffffffffdce0 (rsi)
0x7ffffffffdcd8
0x7ffffffffdcd0 (rsp)

低地址

ebp/rbp之上是返回地址

返回地址和canary间还有8个字节

image-20241231103238914

PIE

PIE技术是一个针对代码段(.text)、数据段(.data)、未初始化全局变量段(.bss)等固定地址的一个防护技术,如果程序开启了PE保护的话,在每次加载程序时都变换加载地址,从而不能通过ROPgadget等一些工具来帮助解题

image-20241231103419452

ctrl+s,shift+f7

image-20241231103451206

所有代码段的地址都只有最后三个数是已知的,这里有一点要知道的是,程序的加载地址一般都是以内存页为单位的,所以程序的基地址最后三个数字一定是0,这也就是说那些地址已知的最后三个数就是实际地址的最后三个
数。知道这一点之后我们就有了绕过pi的思路,、虽然我并不知完整的地址,但我知道最后三个数,那么我们是不是可以利用栈上己有的地址,只修改他们最后两个字节(最后四个数)即可。

所以对于绕过PIE保护的核心思想就是partial writing(部分写地址)

例题

断点下到return

image-20241231103758329

image-20241231103859030

在启用了 PIE (Position Independent Executable) 的程序中,程序的基地址在每次运行时可能不同。

$rebase(0x933) 的作用是:将相对地址 0x933 映射到实际的内存基址。

$rebase(0x933) 会将 0x933 加到当前程序的基地址上。

在 x86_64 中,栈地址一般是8字节对齐。在很多情况下,我们只需要覆盖低字节低两字节,以改变特定的地址偏移。

地址是 0x7fffffffdcf0,如果只想修改 f0 部分,那么实际上需要写入 \xf0\x88,因为地址是小端序存储的。